mirror of
https://github.com/btcsuite/btcd.git
synced 2025-03-11 17:57:50 +01:00
txscript: introduce new ControlBlock struct along w/ parsing routine
In this commit, we add a new struct to represent the ControlBlock structure used to feed in the tapscript leaf inclusion proof into the witness tack. The `ParseControlBlock` parses a would-be control block and returns an error if it's incorrectly formatted.
This commit is contained in:
parent
11dd820740
commit
5c4a29b9d1
3 changed files with 287 additions and 1 deletions
|
@ -28,7 +28,8 @@ const (
|
|||
TaprootAnnexTag = 0x50
|
||||
|
||||
// TaprootLeafMask is the mask applied to the control block to extract
|
||||
// the leaf versions of the taproot script leaf being spent.
|
||||
// the leaf version and parity of the y-coordinate of the output key if
|
||||
// the taproot script leaf being spent.
|
||||
TaprootLeafMask = 0xfe
|
||||
)
|
||||
|
||||
|
|
|
@ -1,11 +1,53 @@
|
|||
// Copyright (c) 2013-2022 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package txscript
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
secp "github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
)
|
||||
|
||||
// TapscriptLeafVersion represents the various possible versions of a tapscript
|
||||
// leaf version. Leaf versions are used to define, or introduce new script
|
||||
// semantics, under the base taproot execution model.
|
||||
//
|
||||
// TODO(roasbeef): add validation here as well re proper prefix, etc?
|
||||
type TapscriptLeafVersion uint8
|
||||
|
||||
const (
|
||||
// BaseLeafVersion is the base tapscript leaf version. The semantics of
|
||||
// this version are defined in BIP 342.
|
||||
BaseLeafVersion TapscriptLeafVersion = 0xc0
|
||||
)
|
||||
|
||||
const (
|
||||
// ControlBlockBaseSize is the base size of a control block. This
|
||||
// includes the initial byte for the leaf version, and then serialized
|
||||
// schnorr public key.
|
||||
ControlBlockBaseSize = 33
|
||||
|
||||
// ControlBlockNodeSize is the size of a given merkle branch hash in
|
||||
// the control block.
|
||||
ControlBlockNodeSize = 32
|
||||
|
||||
// ControlBlockMaxNodeCount is the max number of nodes that can be
|
||||
// included in a control block. This value represents a merkle tree of
|
||||
// depth 2^128.
|
||||
ControlBlockMaxNodeCount = 128
|
||||
|
||||
// ControlBlockMaxSize is the max possible size of a control block.
|
||||
// This simulates revealing a leaf from the largest possible tapscript
|
||||
// tree.
|
||||
ControlBlockMaxSize = ControlBlockBaseSize + (ControlBlockNodeSize *
|
||||
ControlBlockMaxNodeCount)
|
||||
)
|
||||
|
||||
// VerifyTaprootKeySpend attempts to verify a top-level taproot key spend,
|
||||
// returning a non-nil error if the passed signature is invalid. If a sigCache
|
||||
// is passed in, then the sig cache will be consulted to skip full verification
|
||||
|
@ -48,3 +90,113 @@ func VerifyTaprootKeySpend(witnessProgram []byte, rawSig []byte, tx *wire.MsgTx,
|
|||
// TODO(roasbeef): add proper error
|
||||
return fmt.Errorf("invalid sig")
|
||||
}
|
||||
|
||||
// ControlBlock houses the structured witness input for a taproot spend. This
|
||||
// includes the internal taproot key, the leaf version, and finally a nearly
|
||||
// complete merkle inclusion proof for the main taproot commitment.
|
||||
type ControlBlock struct {
|
||||
// InternalKey is the internal public key in the taproot commitment.
|
||||
InternalKey *secp.PublicKey
|
||||
|
||||
// OutputKeyYIsOdd denotes if the y coordinate of the output key (the
|
||||
// key placed in the actual taproot output is odd.
|
||||
OutputKeyYIsOdd bool
|
||||
|
||||
// LeafVersion is the specified leaf version of the tapscript leaf that
|
||||
// the InclusionProof below is based off of.
|
||||
LeafVersion TapscriptLeafVersion
|
||||
|
||||
// InclusionProof is a series of merkle branches that when hashed
|
||||
// pairwise, starting with the revealed script, will yield the taproot
|
||||
// commitment root.
|
||||
InclusionProof []byte
|
||||
}
|
||||
|
||||
// ToBytes returns the control block in a format suitable for using as part of
|
||||
// a witness spending a tapscript output.
|
||||
func (c *ControlBlock) ToBytes() ([]byte, error) {
|
||||
var b bytes.Buffer
|
||||
|
||||
// The first byte of the control block is the leaf version byte XOR'd with
|
||||
// the parity of the y coordinate of the public key.
|
||||
yParity := byte(0)
|
||||
if c.OutputKeyYIsOdd {
|
||||
yParity = 1
|
||||
}
|
||||
|
||||
// The first byte is a combination of the leaf version, using the lowest
|
||||
// bit to encode the single bit that denotes if the yo coordinate if odd or
|
||||
// even.
|
||||
leafVersionAndParity := byte(c.LeafVersion) | yParity
|
||||
if err := b.WriteByte(leafVersionAndParity); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Next, we encode the raw 32 byte schnorr public key
|
||||
if _, err := b.Write(schnorr.SerializePubKey(c.InternalKey)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Finally, we'll write out the inclusion proof as is, without any length
|
||||
// prefix.
|
||||
if _, err := b.Write(c.InclusionProof); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return b.Bytes(), nil
|
||||
}
|
||||
|
||||
// ParseControlBlock attempts to parse the raw bytes of a control block. An
|
||||
// error is returned if the control block isn't well formed, or can't be
|
||||
// parsed.
|
||||
func ParseControlBlock(ctrlBlock []byte) (*ControlBlock, error) {
|
||||
// The control block minimally must contain 33 bytes (for the leaf
|
||||
// version and internal key) along with at least a single value
|
||||
// comprising the merkle proof. If not, then it's invalid.
|
||||
switch {
|
||||
// The control block must minimally have 33 bytes for the internal
|
||||
// public key and script leaf version.
|
||||
case len(ctrlBlock) < ControlBlockBaseSize:
|
||||
return nil, fmt.Errorf("invalid control block size")
|
||||
|
||||
// The control block can't be larger than a proof for the largest
|
||||
// possible tapscript merkle tree with 2^128 leaves.
|
||||
case len(ctrlBlock) > ControlBlockMaxSize:
|
||||
return nil, fmt.Errorf("invalid max block size")
|
||||
|
||||
// Ignoring the fixed sized portion, we expect the total number of
|
||||
// remaining bytes to be a multiple of the node size, which is 32
|
||||
// bytes.
|
||||
case (len(ctrlBlock)-ControlBlockBaseSize)%ControlBlockNodeSize != 0:
|
||||
return nil, fmt.Errorf("invalid max block size")
|
||||
}
|
||||
|
||||
// With the basic sanity checking complete, we can now parse the
|
||||
// control block.
|
||||
leafVersion := TapscriptLeafVersion(ctrlBlock[0] & TaprootLeafMask)
|
||||
|
||||
// Extract the parity of the y coordinate of the internal key.
|
||||
var yIsOdd bool
|
||||
if ctrlBlock[0]&0x01 == 0x01 {
|
||||
yIsOdd = true
|
||||
}
|
||||
|
||||
// Next, we'll parse the public key, which is the 32 bytes following
|
||||
// the leaf version.
|
||||
rawKey := ctrlBlock[1:33]
|
||||
pubKey, err := schnorr.ParsePubKey(rawKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// The rest of the bytes are the control block itself, which encodes a
|
||||
// merkle proof of inclusion.
|
||||
proofBytes := ctrlBlock[33:]
|
||||
|
||||
return &ControlBlock{
|
||||
InternalKey: pubKey,
|
||||
OutputKeyYIsOdd: yIsOdd,
|
||||
LeafVersion: leafVersion,
|
||||
InclusionProof: proofBytes,
|
||||
}, nil
|
||||
}
|
||||
|
|
133
txscript/taproot_test.go
Normal file
133
txscript/taproot_test.go
Normal file
|
@ -0,0 +1,133 @@
|
|||
// Copyright (c) 2013-2022 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package txscript
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
||||
secp "github.com/decred/dcrd/dcrec/secp256k1/v4"
|
||||
)
|
||||
|
||||
var (
|
||||
testPubBytes, _ = hex.DecodeString("F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9")
|
||||
)
|
||||
|
||||
// TestControlBlockParsing tests that we're able to generate and parse a valid
|
||||
// control block.
|
||||
func TestControlBlockParsing(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var testCases = []struct {
|
||||
controlBlockGen func() []byte
|
||||
valid bool
|
||||
}{
|
||||
// An invalid control block, it's only 5 bytes and needs to be
|
||||
// at least 33 bytes.
|
||||
{
|
||||
controlBlockGen: func() []byte {
|
||||
return bytes.Repeat([]byte{0x00}, 5)
|
||||
},
|
||||
valid: false,
|
||||
},
|
||||
|
||||
// An invalid control block, it's greater than the largest
|
||||
// accepted control block.
|
||||
{
|
||||
controlBlockGen: func() []byte {
|
||||
return bytes.Repeat([]byte{0x00}, ControlBlockMaxSize+1)
|
||||
},
|
||||
valid: false,
|
||||
},
|
||||
|
||||
// An invalid control block, it isn't a multiple of 32 bytes
|
||||
// enough though it has a valid starting byte length.
|
||||
{
|
||||
controlBlockGen: func() []byte {
|
||||
return bytes.Repeat([]byte{0x00}, ControlBlockBaseSize+34)
|
||||
},
|
||||
valid: false,
|
||||
},
|
||||
|
||||
// A valid control block, of the largest possible size.
|
||||
{
|
||||
controlBlockGen: func() []byte {
|
||||
privKey, _ := btcec.NewPrivateKey()
|
||||
pubKey := privKey.PubKey()
|
||||
|
||||
yIsOdd := (pubKey.SerializeCompressed()[0] ==
|
||||
secp.PubKeyFormatCompressedOdd)
|
||||
|
||||
ctrl := ControlBlock{
|
||||
InternalKey: pubKey,
|
||||
OutputKeyYIsOdd: yIsOdd,
|
||||
LeafVersion: BaseLeafVersion,
|
||||
InclusionProof: bytes.Repeat(
|
||||
[]byte{0x00},
|
||||
ControlBlockMaxSize-ControlBlockBaseSize,
|
||||
),
|
||||
}
|
||||
|
||||
ctrlBytes, _ := ctrl.ToBytes()
|
||||
return ctrlBytes
|
||||
},
|
||||
valid: true,
|
||||
},
|
||||
|
||||
// A valid control block, only has a single element in the
|
||||
// proof as the tree only has a single element.
|
||||
{
|
||||
controlBlockGen: func() []byte {
|
||||
privKey, _ := btcec.NewPrivateKey()
|
||||
pubKey := privKey.PubKey()
|
||||
|
||||
yIsOdd := (pubKey.SerializeCompressed()[0] ==
|
||||
secp.PubKeyFormatCompressedOdd)
|
||||
|
||||
ctrl := ControlBlock{
|
||||
InternalKey: pubKey,
|
||||
OutputKeyYIsOdd: yIsOdd,
|
||||
LeafVersion: BaseLeafVersion,
|
||||
InclusionProof: bytes.Repeat(
|
||||
[]byte{0x00}, ControlBlockNodeSize,
|
||||
),
|
||||
}
|
||||
|
||||
ctrlBytes, _ := ctrl.ToBytes()
|
||||
return ctrlBytes
|
||||
},
|
||||
valid: true,
|
||||
},
|
||||
}
|
||||
for i, testCase := range testCases {
|
||||
ctrlBlockBytes := testCase.controlBlockGen()
|
||||
|
||||
ctrlBlock, err := ParseControlBlock(ctrlBlockBytes)
|
||||
switch {
|
||||
case testCase.valid && err != nil:
|
||||
t.Fatalf("#%v: unable to parse valid control block: %v", i, err)
|
||||
|
||||
case !testCase.valid && err == nil:
|
||||
t.Fatalf("#%v: invalid control block should have failed: %v", i, err)
|
||||
}
|
||||
|
||||
if !testCase.valid {
|
||||
continue
|
||||
}
|
||||
|
||||
// If we serialize the control block, we should get the exact same
|
||||
// set of bytes as the input.
|
||||
ctrlBytes, err := ctrlBlock.ToBytes()
|
||||
if err != nil {
|
||||
t.Fatalf("#%v: unable to encode bytes: %v", i, err)
|
||||
}
|
||||
if !bytes.Equal(ctrlBytes, ctrlBlockBytes) {
|
||||
t.Fatalf("#%v: encoding mismatch: expected %x, "+
|
||||
"got %x", i, ctrlBlockBytes, ctrlBytes)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue