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:
Olaoluwa Osuntokun 2022-01-06 17:39:55 -08:00
parent 11dd820740
commit 5c4a29b9d1
No known key found for this signature in database
GPG key ID: 3BBD59E99B280306
3 changed files with 287 additions and 1 deletions

View file

@ -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
)

View file

@ -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
View 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)
}
}
}