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
|
TaprootAnnexTag = 0x50
|
||||||
|
|
||||||
// TaprootLeafMask is the mask applied to the control block to extract
|
// 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
|
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
|
package txscript
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
secp "github.com/btcsuite/btcd/btcec/v2"
|
||||||
|
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
||||||
"github.com/btcsuite/btcd/wire"
|
"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,
|
// VerifyTaprootKeySpend attempts to verify a top-level taproot key spend,
|
||||||
// returning a non-nil error if the passed signature is invalid. If a sigCache
|
// 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
|
// 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
|
// TODO(roasbeef): add proper error
|
||||||
return fmt.Errorf("invalid sig")
|
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