Skip to content

Commit

Permalink
psbt: add new input/output types and structs
Browse files Browse the repository at this point in the history
  • Loading branch information
guggero committed May 2, 2022
1 parent e6367b2 commit db6cb69
Show file tree
Hide file tree
Showing 2 changed files with 260 additions and 0 deletions.
206 changes: 206 additions & 0 deletions btcutil/psbt/taproot.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
package psbt

import (
"bytes"
"github.com/btcsuite/btcd/btcec/v2/schnorr"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
)

const (
// schnorrSigMinLength is the minimum length of a Schnorr signature
// which is 64 bytes.
schnorrSigMinLength = schnorr.SignatureSize

// schnorrSigMaxLength is the maximum length of a Schnorr signature
// which is 64 bytes plus one byte for the appended sighash flag.
schnorrSigMaxLength = schnorrSigMinLength + 1
)

// TaprootScriptSpendSig encapsulates an individual Schnorr signature for a
// given public key and leaf hash.
type TaprootScriptSpendSig struct {
XOnlyPubKey []byte
LeafHash []byte
Signature []byte
SigHash txscript.SigHashType
}

// checkValid checks that both the pubkey and the signature are valid.
func (s *TaprootScriptSpendSig) checkValid() bool {
return validateXOnlyPubkey(s.XOnlyPubKey) &&
validateSchnorrSignature(s.Signature)
}

// EqualKey returns true if this script spend signature's key data is the same
// as the given script spend signature.
func (s *TaprootScriptSpendSig) EqualKey(other *TaprootScriptSpendSig) bool {
return bytes.Equal(s.XOnlyPubKey, other.XOnlyPubKey) &&
bytes.Equal(s.LeafHash, other.LeafHash)
}

// SortBefore returns true if this script spend signature's key is
// lexicographically smaller than the given other script spend signature's key
// and should come first when being sorted.
func (s *TaprootScriptSpendSig) SortBefore(other *TaprootScriptSpendSig) bool {
return bytes.Compare(s.XOnlyPubKey, other.XOnlyPubKey) < 0 &&
bytes.Compare(s.LeafHash, other.LeafHash) < 0
}

// TaprootTapLeafScript represents a single taproot leaf script that is
// identified by its control block.
type TaprootTapLeafScript struct {
ControlBlock []byte
Script []byte
LeafVersion txscript.TapscriptLeafVersion
}

// checkValid checks that the control block is valid.
func (s *TaprootTapLeafScript) checkValid() bool {
return validateControlBlock(s.ControlBlock)
}

// SortBefore returns true if this leaf script's key is lexicographically
// smaller than the given other leaf script's key and should come first when
// being sorted.
func (s *TaprootTapLeafScript) SortBefore(other *TaprootTapLeafScript) bool {
return bytes.Compare(s.ControlBlock, other.ControlBlock) < 0
}

// TaprootBip32Derivation encapsulates the data for the input and output
// taproot specific BIP-32 derivation key-value fields.
type TaprootBip32Derivation struct {
// XOnlyPubKey is the raw public key serialized in the x-only BIP-340
// format.
XOnlyPubKey []byte

// LeafHashes is a list of leaf hashes that the given public key is
// involved in.
LeafHashes [][]byte

// MasterKeyFingerprint is the fingerprint of the master pubkey.
MasterKeyFingerprint uint32

// Bip32Path is the BIP 32 path with child index as a distinct integer.
Bip32Path []uint32
}

// SortBefore returns true if this derivation info's key is lexicographically
// smaller than the given other derivation info's key and should come first when
// being sorted.
func (s *TaprootBip32Derivation) SortBefore(other *TaprootBip32Derivation) bool {
return bytes.Compare(s.XOnlyPubKey, other.XOnlyPubKey) < 0
}

// readTaprootBip32Derivation deserializes a byte slice containing the Taproot
// BIP32 derivation info that consists of a list of leaf hashes as well as the
// normal BIP32 derivation info.
func readTaprootBip32Derivation(xOnlyPubKey,
value []byte) (*TaprootBip32Derivation, error) {

// The taproot key BIP 32 derivation path is defined as:
// <hashes len> <leaf hash>* <4 byte fingerprint> <32-bit uint>*
// So we get at least 5 bytes for the length and the 4 byte fingerprint.
if len(value) < 5 {
return nil, ErrInvalidPsbtFormat
}

// The first element is the number of hashes that will follow.
reader := bytes.NewReader(value)
numHashes, err := wire.ReadVarInt(reader, 0)
if err != nil {
return nil, ErrInvalidPsbtFormat
}

// A hash is 32 bytes in size, so we need at least numHashes*32 + 5
// bytes to be present.
if len(value) < (int(numHashes)*32)+5 {
return nil, ErrInvalidPsbtFormat
}

derivation := TaprootBip32Derivation{
XOnlyPubKey: xOnlyPubKey,
LeafHashes: make([][]byte, int(numHashes)),
}

for i := 0; i < int(numHashes); i++ {
derivation.LeafHashes[i] = make([]byte, 32)
n, err := reader.Read(derivation.LeafHashes[i])
if err != nil || n != 32 {
return nil, ErrInvalidPsbtFormat
}
}

// Extract the remaining bytes from the reader (we don't actually know
// how many bytes we read due to the compact size integer at the
// beginning).
var leftoverBuf bytes.Buffer
_, err = reader.WriteTo(&leftoverBuf)
if err != nil {
return nil, err
}

// Read the BIP32 derivation info.
fingerprint, path, err := readBip32Derivation(leftoverBuf.Bytes())
if err != nil {
return nil, err
}

derivation.MasterKeyFingerprint = fingerprint
derivation.Bip32Path = path

return &derivation, nil
}

// serializeTaprootBip32Derivation serializes a TaprootBip32Derivation to its
// raw byte representation.
func serializeTaprootBip32Derivation(d *TaprootBip32Derivation) ([]byte,
error) {

var buf bytes.Buffer

// The taproot key BIP 32 derivation path is defined as:
// <hashes len> <leaf hash>* <4 byte fingerprint> <32-bit uint>*
err := wire.WriteVarInt(&buf, 0, uint64(len(d.LeafHashes)))
if err != nil {
return nil, ErrInvalidPsbtFormat
}

for _, hash := range d.LeafHashes {
n, err := buf.Write(hash)
if err != nil || n != 32 {
return nil, ErrInvalidPsbtFormat
}
}

_, err = buf.Write(SerializeBIP32Derivation(
d.MasterKeyFingerprint, d.Bip32Path,
))
if err != nil {
return nil, ErrInvalidPsbtFormat
}

return buf.Bytes(), nil
}

// validateXOnlyPubkey checks if pubKey is *any* valid pubKey serialization in a
// BIP-340 context (x-only serialization).
func validateXOnlyPubkey(pubKey []byte) bool {
_, err := schnorr.ParsePubKey(pubKey)
return err == nil
}

// validateSchnorrSignature checks that the passed byte slice is a valid Schnorr
// signature, _NOT_ including the sighash flag. It does *not* of course
// validate the signature against any message or public key.
func validateSchnorrSignature(sig []byte) bool {
_, err := schnorr.ParseSignature(sig)
return err == nil
}

// validateControlBlock checks that the passed byte slice is a valid control
// block as it would appear in a BIP-341 witness stack as the last element.
func validateControlBlock(controlBlock []byte) bool {
_, err := txscript.ParseControlBlock(controlBlock)
return err == nil
}
54 changes: 54 additions & 0 deletions btcutil/psbt/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,43 @@ const (
// scripts necessary for the input to pass validation.
FinalScriptWitnessType InputType = 8

// TaprootKeySpendSignatureType is an empty key ({0x13}). The value is
// a 64-byte Schnorr signature or a 65-byte Schnorr signature with the
// one byte sighash type appended to it.
TaprootKeySpendSignatureType InputType = 0x13

// TaprootScriptSpendSignatureType is a type that carries the
// x-only pubkey and leaf hash along with the key
// ({0x14}|{xonlypubkey}|{leafhash}).
//
// The value is a 64-byte Schnorr signature or a 65-byte Schnorr
// signature with the one byte sighash type appended to it.
TaprootScriptSpendSignatureType InputType = 0x14

// TaprootLeafScriptType is a type that carries the control block along
// with the key ({0x15}|{control block}).
//
// The value is a script followed by a one byte unsigned integer that
// represents the leaf version.
TaprootLeafScriptType InputType = 0x15

// TaprootBip32DerivationInputType is a type that carries the x-only
// pubkey along with the key ({0x16}|{xonlypubkey}).
//
// The value is a compact integer denoting the number of hashes,
// followed by said number of 32-byte leaf hashes. The rest of the value
// is then identical to the Bip32DerivationInputType value.
TaprootBip32DerivationInputType InputType = 0x16

// TaprootInternalKeyInputType is an empty key ({0x17}). The value is
// an x-only pubkey denoting the internal public key used for
// constructing a taproot key.
TaprootInternalKeyInputType InputType = 0x17

// TaprootMerkleRootType is an empty key ({0x18}). The value is a
// 32-byte hash denoting the root hash of a merkle tree of scripts.
TaprootMerkleRootType InputType = 0x18

// ProprietaryInputType is a custom type for use by devs.
//
// The key ({0xFC}|<prefix>|{subtype}|{key data}), is a Variable length
Expand Down Expand Up @@ -146,4 +183,21 @@ const (
// little endian unsigned integer indexes concatenated with each other.
// Public keys are those needed to spend this output.
Bip32DerivationOutputType OutputType = 2

// TaprootInternalKeyOutputType is an empty key ({0x05}). The value is
// an x-only pubkey denoting the internal public key used for
// constructing a taproot key.
TaprootInternalKeyOutputType OutputType = 5

// TaprootTapTreeType is an empty key ({0x06}). The value is a
// serialized taproot tree.
TaprootTapTreeType OutputType = 6

// TaprootBip32DerivationOutputType is a type that carries the x-only
// pubkey along with the key ({0x07}|{xonlypubkey}).
//
// The value is a compact integer denoting the number of hashes,
// followed by said number of 32-byte leaf hashes. The rest of the value
// is then identical to the Bip32DerivationInputType value.
TaprootBip32DerivationOutputType OutputType = 7
)

0 comments on commit db6cb69

Please sign in to comment.