diff --git a/asset/asset.go b/asset/asset.go index 3f834af0c..4bdb1297b 100644 --- a/asset/asset.go +++ b/asset/asset.go @@ -20,6 +20,7 @@ import ( "github.com/btcsuite/btcd/btcec/v2/schnorr" "github.com/btcsuite/btcd/btcutil/hdkeychain" "github.com/btcsuite/btcd/btcutil/psbt" + "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" @@ -1142,19 +1143,13 @@ func NewNonSpendableScriptLeaf(version NonSpendLeafVersion, // For the Pedersen commitment based version, we'll use a single // OP_CEHCKSIG with an un-spendable key. case PedersenVersion: - var msg [sha256.Size]byte - copy(msg[:], data) - - // Make a Pedersen opening that uses no mask (we don't carry on - // the random value, as we don't care about hiding here). We'll - // also use the existing NUMs point. - op := pedersen.Opening{ - Msg: msg, + _, commitPoint, err := TweakedNumsKey(data) + if err != nil { + return txscript.TapLeaf{}, fmt.Errorf("failed to "+ + "derive tweaked NUMS key: %w", err) } - commitPoint := pedersen.NewCommitment(op).Point() - - commitBytes := schnorr.SerializePubKey(&commitPoint) + commitBytes := schnorr.SerializePubKey(commitPoint) builder = txscript.NewScriptBuilder().AddData(commitBytes). AddOp(txscript.OP_CHECKSIG) @@ -1202,6 +1197,68 @@ func NewGKRCustomSubtreeRootRecord(root *chainhash.Hash) tlv.Record { return tlv.MakePrimitiveRecord(GKRCustomSubtreeRoot, (*[32]byte)(root)) } +// NumsXPub turns the given NUMS key into an extended public key (using the x +// coordinate of the public key as the chain code), then derives the actual key +// to use from the derivation path 0/0. The extended key always has the mainnet +// version, but can be converted to any network on demand by the caller with +// CloneWithVersion(). +func NumsXPub(numsKey *btcec.PublicKey) (*hdkeychain.ExtendedKey, + *btcec.PublicKey, error) { + + keyBytes := numsKey.SerializeCompressed() + chainCode := keyBytes[1:] + + // We use a depth of 3, emulating BIP44/49/84/86 style derivation for + // xpubs. We also always use mainnet to not require the caller to pass + // in the net params. Converting to another network is possible with + // CloneWithVersion(). + const depth = 3 + extendedNumsKey := hdkeychain.NewExtendedKey( + chaincfg.MainNetParams.HDPublicKeyID[:], keyBytes, chainCode, + []byte{0, 0, 0, 0}, depth, 0, false, + ) + + // Derive the actual key to use from the xpub. + changeBranch, err := extendedNumsKey.Derive(0) + if err != nil { + return nil, nil, err + } + + indexBranch, err := changeBranch.Derive(0) + if err != nil { + return nil, nil, err + } + + actualKey, err := indexBranch.ECPubKey() + if err != nil { + return nil, nil, err + } + + return extendedNumsKey, actualKey, nil +} + +// TweakedNumsKey derives the NUMS key from the given data, then creates the +// extended key from it and derives the actual (derived child) key to use from +// the derivation path 0/0. The extended key always has the mainnet version, but +// can be converted to any network on demand by the caller with +// CloneWithVersion(). +func TweakedNumsKey(data []byte) (*hdkeychain.ExtendedKey, *btcec.PublicKey, + error) { + + var msg [sha256.Size]byte + copy(msg[:], data) + + // Make a Pedersen opening that uses no mask (we don't carry on + // the random value, as we don't care about hiding here). We'll + // also use the existing NUMs point. + op := pedersen.Opening{ + Msg: msg, + } + commitPoint := pedersen.NewCommitment(op).Point() + + return NumsXPub(&commitPoint) +} + // GroupKeyRevealTapscript holds data used to derive the tapscript root, which // is then used to calculate the asset group key. // @@ -1273,7 +1330,9 @@ func NewGKRCustomSubtreeRootRecord(root *chainhash.Hash) tlv.Record { // - One that uses a normal OP_CHECKSIG operator where the pubkey // argument is a key that cannot be signed with. We generate this // special public key using a Pedersen commitment, where the message is -// the asset ID. +// the asset ID. To achieve hardware wallet support, that key is then turned +// into an extended key (xpub) and a child key at path 0/0 is used as the +// actual public key that goes into the OP_CHECKSIG script. // // If `custom_root_hash` is not provided, then there is no sibling to the asset // ID leaf, meaning the tree only has a single leaf. This makes it possible to @@ -1300,7 +1359,9 @@ func NewGKRCustomSubtreeRootRecord(root *chainhash.Hash) tlv.Record { // - One that uses a normal OP_CHECKSIG operator where the pubkey // argument is a key that cannot be signed with. We generate this // special public key using a Pedersen commitment, where the message is -// the asset ID. +// the asset ID. To achieve hardware wallet support, that key is then turned +// into an extended key (xpub) and a child key at path 0/0 is used as the +// actual public key that goes into the OP_CHECKSIG script. type GroupKeyRevealTapscript struct { // version is the version of the group key reveal that determines how // the non-spendable leaf is created.