mirror of
https://github.com/btcsuite/btcd.git
synced 2025-03-13 11:35:52 +01:00
Merge 8e879e1b39
into c7191d2913
This commit is contained in:
commit
e9a838b50b
8 changed files with 620 additions and 8 deletions
|
@ -14,6 +14,9 @@ package psbt
|
|||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
|
@ -47,11 +50,19 @@ func isFinalizableWitnessInput(pInput *PInput) bool {
|
|||
|
||||
case txscript.IsPayToTaproot(pkScript):
|
||||
if pInput.TaprootKeySpendSig == nil &&
|
||||
pInput.TaprootScriptSpendSig == nil {
|
||||
pInput.TaprootScriptSpendSig == nil &&
|
||||
pInput.MuSig2PartialSigs == nil {
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// For each participant, we need a corresponding
|
||||
// MuSig2 partial signature.
|
||||
if len(pInput.MuSig2PartialSigs) > 0 {
|
||||
return len(pInput.MuSig2PartialSigs) ==
|
||||
len(pInput.MuSig2PubNonces)
|
||||
}
|
||||
|
||||
// For each of the script spend signatures we need a
|
||||
// corresponding tap script leaf with the control block.
|
||||
for _, sig := range pInput.TaprootScriptSpendSig {
|
||||
|
@ -133,7 +144,8 @@ func isFinalizable(p *Packet, inIndex int) bool {
|
|||
|
||||
// The input cannot be finalized without any signatures.
|
||||
if pInput.PartialSigs == nil && pInput.TaprootKeySpendSig == nil &&
|
||||
pInput.TaprootScriptSpendSig == nil {
|
||||
pInput.TaprootScriptSpendSig == nil &&
|
||||
pInput.MuSig2PartialSigs == nil {
|
||||
|
||||
return false
|
||||
}
|
||||
|
@ -577,6 +589,91 @@ func finalizeTaprootInput(p *Packet, inIndex int) error {
|
|||
|
||||
serializedWitness, err = writeWitness(witnessStack...)
|
||||
|
||||
// MuSig2 spend path.
|
||||
case len(pInput.MuSig2PartialSigs) > 0:
|
||||
if len(pInput.MuSig2PubNonces) !=
|
||||
len(pInput.MuSig2PartialSigs) {
|
||||
|
||||
return fmt.Errorf("number of MuSig2 pub nonces " +
|
||||
"does not match number of partial signatures")
|
||||
}
|
||||
|
||||
// We'll need to combine MuSig2 partial signatures into a single
|
||||
// one, which requires the message that was signed over.
|
||||
firstSig := pInput.MuSig2PartialSigs[0]
|
||||
|
||||
// We don't (yet) support signing over a tap leaf hash.
|
||||
// TODO(guggero): Add support for signing over a tap leaf hash.
|
||||
if len(firstSig.TapLeafHash) > 0 {
|
||||
return fmt.Errorf("combining partial MuSig2 " +
|
||||
"signatures for a tap leaf is not supported")
|
||||
}
|
||||
|
||||
prevOutFetcher := PrevOutputFetcher(p)
|
||||
sigHashes := txscript.NewTxSigHashes(
|
||||
p.UnsignedTx, prevOutFetcher,
|
||||
)
|
||||
sigHash, err := txscript.CalcTaprootSignatureHash(
|
||||
sigHashes, pInput.SighashType, p.UnsignedTx,
|
||||
inIndex, prevOutFetcher,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error calculating signature hash: "+
|
||||
"%w", err)
|
||||
}
|
||||
|
||||
var sigHashMsg [32]byte
|
||||
copy(sigHashMsg[:], sigHash)
|
||||
|
||||
var (
|
||||
pubNonces = make(
|
||||
[][musig2.PubNonceSize]byte,
|
||||
len(pInput.MuSig2PubNonces),
|
||||
)
|
||||
keys = make(
|
||||
[]*btcec.PublicKey, len(pInput.MuSig2PubNonces),
|
||||
)
|
||||
partialSigs = make(
|
||||
[]*musig2.PartialSignature,
|
||||
len(pInput.MuSig2PartialSigs),
|
||||
)
|
||||
)
|
||||
for i, pubNonce := range pInput.MuSig2PubNonces {
|
||||
copy(pubNonces[i][:], pubNonce.PubNonce[:])
|
||||
keys[i] = pubNonce.PubKey
|
||||
|
||||
partialSigs[i] = &pInput.MuSig2PartialSigs[i].PartialSig
|
||||
}
|
||||
aggregateNonce, err := musig2.AggregateNonces(pubNonces)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error aggregating pub nonces: %w",
|
||||
err)
|
||||
}
|
||||
|
||||
aggKey, _, _, err := musig2.AggregateKeys(
|
||||
keys, true, musig2.WithBIP86KeyTweak(),
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error aggregating keys: %w", err)
|
||||
}
|
||||
|
||||
combinedNonce, err := computeSigningNonce(
|
||||
aggregateNonce, aggKey.FinalKey, sigHashMsg,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error computing signing nonce: %w",
|
||||
err)
|
||||
}
|
||||
|
||||
combineOpt := musig2.WithBip86TweakedCombine(
|
||||
sigHashMsg, keys, true,
|
||||
)
|
||||
schnorrSig := musig2.CombineSigs(
|
||||
combinedNonce, partialSigs, combineOpt,
|
||||
)
|
||||
|
||||
serializedWitness, err = writeWitness(schnorrSig.Serialize())
|
||||
|
||||
default:
|
||||
return ErrInvalidPsbtFormat
|
||||
}
|
||||
|
@ -595,3 +692,57 @@ func finalizeTaprootInput(p *Packet, inIndex int) error {
|
|||
p.Inputs[inIndex] = *newInput
|
||||
return nil
|
||||
}
|
||||
|
||||
// computeSigningNonce calculates the final nonce used for signing. This will
|
||||
// be the R value used in the final signature.
|
||||
func computeSigningNonce(combinedNonce [musig2.PubNonceSize]byte,
|
||||
combinedKey *btcec.PublicKey, msg [32]byte) (*btcec.PublicKey, error) {
|
||||
|
||||
// Next we'll compute the value b, that blinds our second public
|
||||
// nonce:
|
||||
// * b = h(tag=NonceBlindTag, combinedNonce || combinedKey || m).
|
||||
var (
|
||||
nonceMsgBuf bytes.Buffer
|
||||
nonceBlinder btcec.ModNScalar
|
||||
)
|
||||
nonceMsgBuf.Write(combinedNonce[:])
|
||||
nonceMsgBuf.Write(schnorr.SerializePubKey(combinedKey))
|
||||
nonceMsgBuf.Write(msg[:])
|
||||
nonceBlindHash := chainhash.TaggedHash(
|
||||
musig2.NonceBlindTag, nonceMsgBuf.Bytes(),
|
||||
)
|
||||
nonceBlinder.SetByteSlice(nonceBlindHash[:])
|
||||
|
||||
// Next, we'll parse the public nonces into R1 and R2.
|
||||
r1J, err := btcec.ParseJacobian(
|
||||
combinedNonce[:btcec.PubKeyBytesLenCompressed],
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r2J, err := btcec.ParseJacobian(
|
||||
combinedNonce[btcec.PubKeyBytesLenCompressed:],
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// With our nonce blinding value, we'll now combine both the public
|
||||
// nonces, using the blinding factor to tweak the second nonce:
|
||||
// * R = R_1 + b*R_2
|
||||
var nonce btcec.JacobianPoint
|
||||
btcec.ScalarMultNonConst(&nonceBlinder, &r2J, &r2J)
|
||||
btcec.AddNonConst(&r1J, &r2J, &nonce)
|
||||
|
||||
// If the combined nonce is the point at infinity, we'll use the
|
||||
// generator point instead.
|
||||
var infinityPoint btcec.JacobianPoint
|
||||
if nonce == infinityPoint {
|
||||
G := btcec.Generator()
|
||||
G.AsJacobian(&nonce)
|
||||
}
|
||||
|
||||
nonce.ToAffine()
|
||||
|
||||
return btcec.NewPublicKey(&nonce.X, &nonce.Y), nil
|
||||
}
|
||||
|
|
|
@ -4,11 +4,11 @@ go 1.22
|
|||
|
||||
require (
|
||||
github.com/btcsuite/btcd v0.23.5-0.20231219003633-4c2ce6daed8f
|
||||
github.com/btcsuite/btcd/btcec/v2 v2.1.3
|
||||
github.com/btcsuite/btcd/btcec/v2 v2.3.3
|
||||
github.com/btcsuite/btcd/btcutil v1.1.4
|
||||
github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0
|
||||
github.com/davecgh/go-spew v1.1.1
|
||||
github.com/stretchr/testify v1.7.0
|
||||
github.com/stretchr/testify v1.8.0
|
||||
)
|
||||
|
||||
require (
|
||||
|
@ -18,5 +18,5 @@ require (
|
|||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 // indirect
|
||||
golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
|
|
@ -5,8 +5,9 @@ github.com/btcsuite/btcd v0.23.5-0.20231215221805-96c9fd8078fd/go.mod h1:nm3Bko6
|
|||
github.com/btcsuite/btcd v0.23.5-0.20231219003633-4c2ce6daed8f h1:E+dQ8sNtK/lOdfeflUKkRLXe/zW7I333C7HhaoASjZA=
|
||||
github.com/btcsuite/btcd v0.23.5-0.20231219003633-4c2ce6daed8f/go.mod h1:KVEB81PybLGYzpf1db/kKNi1ZEbUsiVGeTGhKuOl5AM=
|
||||
github.com/btcsuite/btcd/btcec/v2 v2.1.0/go.mod h1:2VzYrv4Gm4apmbVVsSq5bqf1Ec8v56E48Vt0Y/umPgA=
|
||||
github.com/btcsuite/btcd/btcec/v2 v2.1.3 h1:xM/n3yIhHAhHy04z4i43C8p4ehixJZMsnrVJkgl+MTE=
|
||||
github.com/btcsuite/btcd/btcec/v2 v2.1.3/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE=
|
||||
github.com/btcsuite/btcd/btcec/v2 v2.3.3 h1:6+iXlDKE8RMtKsvK0gshlXIuPbyWM/h84Ensb7o3sC0=
|
||||
github.com/btcsuite/btcd/btcec/v2 v2.3.3/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04=
|
||||
github.com/btcsuite/btcd/btcutil v1.0.0/go.mod h1:Uoxwv0pqYWhD//tfTiipkxNfdhG9UrLwaeswfjfdF0A=
|
||||
github.com/btcsuite/btcd/btcutil v1.1.0/go.mod h1:5OapHB7A2hBBWLm48mmw4MOHNJCcUBTwmWH/0Jn8VHE=
|
||||
github.com/btcsuite/btcd/btcutil v1.1.4 h1:mWvWRLRIPuoeZsVRpc0xNCkfeNxWy1E4jIZ06ZpGI1A=
|
||||
|
@ -65,8 +66,11 @@ github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1y
|
|||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc=
|
||||
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
|
@ -107,5 +111,6 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWD
|
|||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
|
282
btcutil/psbt/musig2.go
Normal file
282
btcutil/psbt/musig2.go
Normal file
|
@ -0,0 +1,282 @@
|
|||
package psbt
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"io"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
|
||||
)
|
||||
|
||||
// MuSig2Participants represents a set of participants in a MuSig2 signing
|
||||
// session.
|
||||
type MuSig2Participants struct {
|
||||
// AggregateKey is the plain (non-tweaked) aggregate public key of all
|
||||
// participants, from the `KeyAgg` algorithm as described in the MuSig2
|
||||
// BIP. This key may or may not be in the script directly (x-only). It
|
||||
// may instead be a parent public key from which the public key in the
|
||||
// script were derived.
|
||||
AggregateKey *btcec.PublicKey
|
||||
|
||||
// Keys is a list of the public keys of the participants in the MuSig2
|
||||
// aggregate key in the order required for aggregation. If sorting was
|
||||
// done, then the keys must be in the sorted order.
|
||||
Keys []*btcec.PublicKey
|
||||
}
|
||||
|
||||
// KeyData returns the key data for the MuSig2Participants struct.
|
||||
func (m *MuSig2Participants) KeyData() []byte {
|
||||
return m.AggregateKey.SerializeCompressed()
|
||||
}
|
||||
|
||||
// ReadMuSig2Participants reads a set of MuSig2 participants from a key-value
|
||||
// pair in a PSBT.
|
||||
func ReadMuSig2Participants(keyData,
|
||||
value []byte) (*MuSig2Participants, error) {
|
||||
|
||||
if len(keyData) != btcec.PubKeyBytesLenCompressed {
|
||||
return nil, ErrInvalidKeyData
|
||||
}
|
||||
|
||||
if len(value) == 0 || len(value)%btcec.PubKeyBytesLenCompressed != 0 {
|
||||
return nil, ErrInvalidPsbtFormat
|
||||
}
|
||||
|
||||
numKeys := len(value) / btcec.PubKeyBytesLenCompressed
|
||||
participants := &MuSig2Participants{
|
||||
Keys: make([]*btcec.PublicKey, numKeys),
|
||||
}
|
||||
|
||||
var err error
|
||||
participants.AggregateKey, err = btcec.ParsePubKey(keyData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for idx := 0; idx < numKeys; idx++ {
|
||||
start := idx * btcec.PubKeyBytesLenCompressed
|
||||
participants.Keys[idx], err = btcec.ParsePubKey(
|
||||
value[start : start+btcec.PubKeyBytesLenCompressed],
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return participants, nil
|
||||
}
|
||||
|
||||
// SerializeMuSig2Participants serializes a set of MuSig2 participants to a
|
||||
// key-value pair in a PSBT.
|
||||
func SerializeMuSig2Participants(w io.Writer, typ uint8,
|
||||
participants *MuSig2Participants) error {
|
||||
|
||||
value := make(
|
||||
[]byte, len(participants.Keys)*btcec.PubKeyBytesLenCompressed,
|
||||
)
|
||||
|
||||
for idx, key := range participants.Keys {
|
||||
copy(
|
||||
value[idx*btcec.PubKeyBytesLenCompressed:],
|
||||
key.SerializeCompressed(),
|
||||
)
|
||||
}
|
||||
|
||||
return serializeKVPairWithType(w, typ, participants.KeyData(), value)
|
||||
}
|
||||
|
||||
// MuSig2PubNonce represents a public nonce provided by a participant in a
|
||||
// MuSig2 signing session.
|
||||
type MuSig2PubNonce struct {
|
||||
// PubKey is the public key of the participant providing this nonce.
|
||||
PubKey *btcec.PublicKey
|
||||
|
||||
// AggregateKey is the plain (non-tweaked) aggregate public key the
|
||||
// participant is providing the nonce for. This must be the key found in
|
||||
// the script and not the aggregate public key that it was derived from,
|
||||
// if it was derived from an aggregate key.
|
||||
AggregateKey *btcec.PublicKey
|
||||
|
||||
// TapLeafHash is the optional hash of the BIP-0341 tap leaf hash of the
|
||||
// Taproot leaf script that will be signed. If the aggregate key is the
|
||||
// taproot internal key or the taproot output key, then the tap leaf
|
||||
// hash must be omitted.
|
||||
TapLeafHash []byte
|
||||
|
||||
// PUbNonce is the public nonce provided by the participant, produced
|
||||
// by the `NonceGen` algorithm as described in the MuSig2 BIP.
|
||||
PubNonce [musig2.PubNonceSize]byte
|
||||
}
|
||||
|
||||
// KeyData returns the key data for the MuSig2PubNonce struct.
|
||||
func (m *MuSig2PubNonce) KeyData() []byte {
|
||||
// The tap leaf hash is optional.
|
||||
keyLen := 2*btcec.PubKeyBytesLenCompressed + len(m.TapLeafHash)
|
||||
keyData := make([]byte, keyLen)
|
||||
|
||||
copy(keyData, m.PubKey.SerializeCompressed())
|
||||
copy(
|
||||
keyData[btcec.PubKeyBytesLenCompressed:],
|
||||
m.AggregateKey.SerializeCompressed(),
|
||||
)
|
||||
|
||||
if len(m.TapLeafHash) != 0 {
|
||||
copy(keyData[2*btcec.PubKeyBytesLenCompressed:], m.TapLeafHash)
|
||||
}
|
||||
|
||||
return keyData
|
||||
}
|
||||
|
||||
// ReadMuSig2PubNonce reads a MuSig2 public nonce from a key-value pair in a
|
||||
// PSBT.
|
||||
func ReadMuSig2PubNonce(keyData, value []byte) (*MuSig2PubNonce, error) {
|
||||
const pubKeyLen = btcec.PubKeyBytesLenCompressed
|
||||
const minLength = 2 * pubKeyLen
|
||||
const maxLength = minLength + sha256.Size
|
||||
|
||||
if len(keyData) != minLength && len(keyData) != maxLength {
|
||||
return nil, ErrInvalidKeyData
|
||||
}
|
||||
|
||||
if len(value) != musig2.PubNonceSize {
|
||||
return nil, ErrInvalidPsbtFormat
|
||||
}
|
||||
|
||||
var (
|
||||
nonce MuSig2PubNonce
|
||||
err error
|
||||
)
|
||||
nonce.PubKey, err = btcec.ParsePubKey(keyData[0:pubKeyLen])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
nonce.AggregateKey, err = btcec.ParsePubKey(
|
||||
keyData[pubKeyLen : 2*pubKeyLen],
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(keyData) == maxLength {
|
||||
nonce.TapLeafHash = make([]byte, sha256.Size)
|
||||
copy(nonce.TapLeafHash, keyData[2*pubKeyLen:])
|
||||
}
|
||||
|
||||
copy(nonce.PubNonce[:], value)
|
||||
|
||||
return &nonce, nil
|
||||
}
|
||||
|
||||
// SerializeMuSig2PubNonce serializes a MuSig2 public nonce to a key-value pair
|
||||
// in a PSBT.
|
||||
func SerializeMuSig2PubNonce(w io.Writer, typ uint8,
|
||||
nonce *MuSig2PubNonce) error {
|
||||
|
||||
return serializeKVPairWithType(
|
||||
w, typ, nonce.KeyData(), nonce.PubNonce[:],
|
||||
)
|
||||
}
|
||||
|
||||
// MuSig2PartialSig represents a partial signature provided by a participant in
|
||||
// a MuSig2 signing session.
|
||||
type MuSig2PartialSig struct {
|
||||
// PubKey is the public key of the participant providing this nonce.
|
||||
PubKey *btcec.PublicKey
|
||||
|
||||
// AggregateKey is the plain (non-tweaked) aggregate public key the
|
||||
// participant is providing the nonce for. This must be the key found in
|
||||
// the script and not the aggregate public key that it was derived from,
|
||||
// if it was derived from an aggregate key.
|
||||
AggregateKey *btcec.PublicKey
|
||||
|
||||
// TapLeafHash is the optional hash of the BIP-0341 tap leaf hash of the
|
||||
// Taproot leaf script that will be signed. If the aggregate key is the
|
||||
// taproot internal key or the taproot output key, then the tap leaf
|
||||
// hash must be omitted.
|
||||
TapLeafHash []byte
|
||||
|
||||
// PartialSig is the partial signature provided by the participant,
|
||||
// produced by the `Sign` algorithm as described in the MuSig2 BIP.
|
||||
PartialSig musig2.PartialSignature
|
||||
}
|
||||
|
||||
// KeyData returns the key data for the MuSig2PartialSig struct.
|
||||
func (m *MuSig2PartialSig) KeyData() []byte {
|
||||
// The tap leaf hash is optional.
|
||||
keyLen := 2*btcec.PubKeyBytesLenCompressed + len(m.TapLeafHash)
|
||||
keyData := make([]byte, keyLen)
|
||||
|
||||
copy(keyData, m.PubKey.SerializeCompressed())
|
||||
copy(
|
||||
keyData[btcec.PubKeyBytesLenCompressed:],
|
||||
m.AggregateKey.SerializeCompressed(),
|
||||
)
|
||||
|
||||
if len(m.TapLeafHash) != 0 {
|
||||
copy(keyData[2*btcec.PubKeyBytesLenCompressed:], m.TapLeafHash)
|
||||
}
|
||||
|
||||
return keyData
|
||||
}
|
||||
|
||||
// ReadMuSig2PartialSig reads a MuSig2 partial signature from a key-value pair
|
||||
// in a PSBT.
|
||||
func ReadMuSig2PartialSig(keyData, value []byte) (*MuSig2PartialSig, error) {
|
||||
const pubKeyLen = btcec.PubKeyBytesLenCompressed
|
||||
const minLength = 2 * pubKeyLen
|
||||
const maxLength = minLength + sha256.Size
|
||||
|
||||
if len(keyData) != minLength && len(keyData) != maxLength {
|
||||
return nil, ErrInvalidKeyData
|
||||
}
|
||||
|
||||
if len(value) != 32 {
|
||||
return nil, ErrInvalidPsbtFormat
|
||||
}
|
||||
|
||||
var (
|
||||
partialSig MuSig2PartialSig
|
||||
err error
|
||||
)
|
||||
partialSig.PubKey, err = btcec.ParsePubKey(keyData[0:pubKeyLen])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
partialSig.AggregateKey, err = btcec.ParsePubKey(
|
||||
keyData[pubKeyLen : 2*pubKeyLen],
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(keyData) == maxLength {
|
||||
partialSig.TapLeafHash = make([]byte, sha256.Size)
|
||||
copy(partialSig.TapLeafHash, keyData[2*pubKeyLen:])
|
||||
}
|
||||
|
||||
err = partialSig.PartialSig.Decode(bytes.NewReader(value))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &partialSig, nil
|
||||
}
|
||||
|
||||
// SerializeMuSig2PartialSig serializes a MuSig2 partial signature to a
|
||||
// key-value pair in a PSBT.
|
||||
func SerializeMuSig2PartialSig(w io.Writer, typ uint8,
|
||||
partialSig *MuSig2PartialSig) error {
|
||||
|
||||
var buf bytes.Buffer
|
||||
err := partialSig.PartialSig.Encode(&buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return serializeKVPairWithType(
|
||||
w, typ, partialSig.KeyData(), buf.Bytes(),
|
||||
)
|
||||
}
|
|
@ -28,6 +28,9 @@ type PInput struct {
|
|||
TaprootBip32Derivation []*TaprootBip32Derivation
|
||||
TaprootInternalKey []byte
|
||||
TaprootMerkleRoot []byte
|
||||
MuSig2Participants []*MuSig2Participants
|
||||
MuSig2PubNonces []*MuSig2PubNonce
|
||||
MuSig2PartialSigs []*MuSig2PartialSig
|
||||
Unknowns []*Unknown
|
||||
}
|
||||
|
||||
|
@ -363,6 +366,60 @@ func (pi *PInput) deserialize(r io.Reader) error {
|
|||
|
||||
pi.TaprootMerkleRoot = value
|
||||
|
||||
case MuSig2ParticipantsInputType:
|
||||
participants, err := ReadMuSig2Participants(
|
||||
keyData, value,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Duplicate keys are not allowed.
|
||||
newKey := participants.KeyData()
|
||||
for _, x := range pi.MuSig2Participants {
|
||||
if bytes.Equal(x.KeyData(), newKey) {
|
||||
return ErrDuplicateKey
|
||||
}
|
||||
}
|
||||
|
||||
pi.MuSig2Participants = append(
|
||||
pi.MuSig2Participants, participants,
|
||||
)
|
||||
|
||||
case MuSig2PubNoncesInputType:
|
||||
nonce, err := ReadMuSig2PubNonce(keyData, value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Duplicate keys are not allowed.
|
||||
newKey := nonce.KeyData()
|
||||
for _, x := range pi.MuSig2PubNonces {
|
||||
if bytes.Equal(x.KeyData(), newKey) {
|
||||
return ErrDuplicateKey
|
||||
}
|
||||
}
|
||||
|
||||
pi.MuSig2PubNonces = append(pi.MuSig2PubNonces, nonce)
|
||||
|
||||
case MuSig2PartialSigsInputType:
|
||||
partialSig, err := ReadMuSig2PartialSig(keyData, value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Duplicate keys are not allowed.
|
||||
newKey := partialSig.KeyData()
|
||||
for _, x := range pi.MuSig2PartialSigs {
|
||||
if bytes.Equal(x.KeyData(), newKey) {
|
||||
return ErrDuplicateKey
|
||||
}
|
||||
}
|
||||
|
||||
pi.MuSig2PartialSigs = append(
|
||||
pi.MuSig2PartialSigs, partialSig,
|
||||
)
|
||||
|
||||
default:
|
||||
// A fall through case for any proprietary types.
|
||||
keyCodeAndData := append(
|
||||
|
@ -572,6 +629,36 @@ func (pi *PInput) serialize(w io.Writer) error {
|
|||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, participants := range pi.MuSig2Participants {
|
||||
err := SerializeMuSig2Participants(
|
||||
w, uint8(MuSig2ParticipantsInputType),
|
||||
participants,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, nonce := range pi.MuSig2PubNonces {
|
||||
err := SerializeMuSig2PubNonce(
|
||||
w, uint8(MuSig2PubNoncesInputType),
|
||||
nonce,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, sig := range pi.MuSig2PartialSigs {
|
||||
err := SerializeMuSig2PartialSig(
|
||||
w, uint8(MuSig2PartialSigsInputType),
|
||||
sig,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if pi.FinalScriptSig != nil {
|
||||
|
|
|
@ -17,6 +17,7 @@ type POutput struct {
|
|||
TaprootInternalKey []byte
|
||||
TaprootTapTree []byte
|
||||
TaprootBip32Derivation []*TaprootBip32Derivation
|
||||
MuSig2Participants []*MuSig2Participants
|
||||
Unknowns []*Unknown
|
||||
}
|
||||
|
||||
|
@ -144,6 +145,26 @@ func (po *POutput) deserialize(r io.Reader) error {
|
|||
po.TaprootBip32Derivation, taprootDerivation,
|
||||
)
|
||||
|
||||
case MuSig2ParticipantsOutputType:
|
||||
participants, err := ReadMuSig2Participants(
|
||||
keyData, value,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Duplicate keys are not allowed.
|
||||
newKey := participants.AggregateKey
|
||||
for _, x := range po.MuSig2Participants {
|
||||
if x.AggregateKey.IsEqual(newKey) {
|
||||
return ErrDuplicateKey
|
||||
}
|
||||
}
|
||||
|
||||
po.MuSig2Participants = append(
|
||||
po.MuSig2Participants, participants,
|
||||
)
|
||||
|
||||
default:
|
||||
// A fall through case for any proprietary types.
|
||||
keyCodeAndData := append(
|
||||
|
@ -246,6 +267,15 @@ func (po *POutput) serialize(w io.Writer) error {
|
|||
}
|
||||
}
|
||||
|
||||
for _, participants := range po.MuSig2Participants {
|
||||
err := SerializeMuSig2Participants(
|
||||
w, uint8(MuSig2ParticipantsOutputType), participants,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Unknown is a special case; we don't have a key type, only a key and
|
||||
// a value field
|
||||
for _, kv := range po.Unknowns {
|
||||
|
|
|
@ -151,6 +151,24 @@ const (
|
|||
// 32-byte hash denoting the root hash of a merkle tree of scripts.
|
||||
TaprootMerkleRootType InputType = 0x18
|
||||
|
||||
// MuSig2ParticipantsInputType is a type that carries the participant
|
||||
// public keys and aggregated key for a MuSig2 signing session
|
||||
// ({0x1a}|{aggregate_key}). The value is a list of 33-byte compressed
|
||||
// public keys in the order required for aggregation.
|
||||
MuSig2ParticipantsInputType InputType = 0x1a
|
||||
|
||||
// MuSig2PubNoncesInputType is a type that carries the public nonces
|
||||
// provided by participants in a MuSig2 signing session
|
||||
// ({0x1b}|{participant_key}|{aggregate_key}[|{tapleaf_hash}]). The
|
||||
// value is the 66-byte public nonces provided by the participant.
|
||||
MuSig2PubNoncesInputType InputType = 0x1b
|
||||
|
||||
// MuSig2PartialSigsInputType is a type that carries the partial
|
||||
// signatures provided by participants in a MuSig2 signing session
|
||||
// ({0x1c}|{participant_key}|{aggregate_key}[|{tapleaf_hash}]). The
|
||||
// value is the 32-byte partial signature provided by the participant.
|
||||
MuSig2PartialSigsInputType InputType = 0x1c
|
||||
|
||||
// ProprietaryInputType is a custom type for use by devs.
|
||||
//
|
||||
// The key ({0xFC}|<prefix>|{subtype}|{key data}), is a Variable length
|
||||
|
@ -200,4 +218,10 @@ const (
|
|||
// followed by said number of 32-byte leaf hashes. The rest of the value
|
||||
// is then identical to the Bip32DerivationInputType value.
|
||||
TaprootBip32DerivationOutputType OutputType = 7
|
||||
|
||||
// MuSig2ParticipantsOutputType is a type that carries the participant
|
||||
// public keys and aggregated key for a MuSig2 signing session
|
||||
// ({0x08}|{aggregate_key}). The value is a list of 33-byte compressed
|
||||
// public keys in the order required for aggregation.
|
||||
MuSig2ParticipantsOutputType OutputType = 0x08
|
||||
)
|
||||
|
|
|
@ -478,3 +478,36 @@ func FindLeafScript(pInput *PInput,
|
|||
return nil, fmt.Errorf("leaf script for target leaf hash %x not "+
|
||||
"found in input", targetLeafHash)
|
||||
}
|
||||
|
||||
// PrevOutputFetcher returns a txscript.PrevOutFetcher built from the UTXO
|
||||
// information in a PSBT packet.
|
||||
func PrevOutputFetcher(packet *Packet) *txscript.MultiPrevOutFetcher {
|
||||
fetcher := txscript.NewMultiPrevOutFetcher(nil)
|
||||
for idx, txIn := range packet.UnsignedTx.TxIn {
|
||||
in := packet.Inputs[idx]
|
||||
|
||||
// Skip any input that has no UTXO.
|
||||
if in.WitnessUtxo == nil && in.NonWitnessUtxo == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if in.NonWitnessUtxo != nil {
|
||||
prevIndex := txIn.PreviousOutPoint.Index
|
||||
fetcher.AddPrevOut(
|
||||
txIn.PreviousOutPoint,
|
||||
in.NonWitnessUtxo.TxOut[prevIndex],
|
||||
)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
// Fall back to witness UTXO only for older wallets.
|
||||
if in.WitnessUtxo != nil {
|
||||
fetcher.AddPrevOut(
|
||||
txIn.PreviousOutPoint, in.WitnessUtxo,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return fetcher
|
||||
}
|
Loading…
Add table
Reference in a new issue