Skip to content

Commit 72d8a8f

Browse files
committed
psbt: support finalizing MuSig2 partial signatures
1 parent 51321c8 commit 72d8a8f

1 file changed

Lines changed: 151 additions & 1 deletion

File tree

btcutil/psbt/finalizer.go

Lines changed: 151 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ package psbt
1414
import (
1515
"bytes"
1616
"fmt"
17+
"github.com/btcsuite/btcd/btcec/v2"
18+
"github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
19+
"github.com/btcsuite/btcd/chaincfg/chainhash"
1720

1821
"github.com/btcsuite/btcd/btcec/v2/schnorr"
1922
"github.com/btcsuite/btcd/txscript"
@@ -47,11 +50,19 @@ func isFinalizableWitnessInput(pInput *PInput) bool {
4750

4851
case txscript.IsPayToTaproot(pkScript):
4952
if pInput.TaprootKeySpendSig == nil &&
50-
pInput.TaprootScriptSpendSig == nil {
53+
pInput.TaprootScriptSpendSig == nil &&
54+
pInput.MuSig2PartialSigs == nil {
5155

5256
return false
5357
}
5458

59+
// For each participant, we need a corresponding
60+
// MuSig2 partial signature.
61+
if len(pInput.MuSig2PartialSigs) > 0 {
62+
return len(pInput.MuSig2PartialSigs) ==
63+
len(pInput.MuSig2PubNonces)
64+
}
65+
5566
// For each of the script spend signatures we need a
5667
// corresponding tap script leaf with the control block.
5768
for _, sig := range pInput.TaprootScriptSpendSig {
@@ -577,6 +588,91 @@ func finalizeTaprootInput(p *Packet, inIndex int) error {
577588

578589
serializedWitness, err = writeWitness(witnessStack...)
579590

591+
// MuSig2 spend path.
592+
case len(pInput.MuSig2PartialSigs) > 0:
593+
if len(pInput.MuSig2PubNonces) !=
594+
len(pInput.MuSig2PartialSigs) {
595+
596+
return fmt.Errorf("number of MuSig2 pub nonces " +
597+
"does not match number of partial signatures")
598+
}
599+
600+
// We'll need to combine MuSig2 partial signatures into a single
601+
// one, which requires the message that was signed over.
602+
firstSig := pInput.MuSig2PartialSigs[0]
603+
604+
// We don't (yet) support signing over a tap leaf hash.
605+
// TODO(guggero): Add support for signing over a tap leaf hash.
606+
if len(firstSig.TapLeafHash) > 0 {
607+
return fmt.Errorf("combining partial MuSig2 " +
608+
"signatures for a tap leaf is not supported")
609+
}
610+
611+
prevOutFetcher := PrevOutputFetcher(p)
612+
sigHashes := txscript.NewTxSigHashes(
613+
p.UnsignedTx, prevOutFetcher,
614+
)
615+
sigHash, err := txscript.CalcTaprootSignatureHash(
616+
sigHashes, pInput.SighashType, p.UnsignedTx,
617+
inIndex, prevOutFetcher,
618+
)
619+
if err != nil {
620+
return fmt.Errorf("error calculating signature hash: "+
621+
"%w", err)
622+
}
623+
624+
var sigHashMsg [32]byte
625+
copy(sigHashMsg[:], sigHash)
626+
627+
var (
628+
pubNonces = make(
629+
[][musig2.PubNonceSize]byte,
630+
len(pInput.MuSig2PubNonces),
631+
)
632+
keys = make(
633+
[]*btcec.PublicKey, len(pInput.MuSig2PubNonces),
634+
)
635+
partialSigs = make(
636+
[]*musig2.PartialSignature,
637+
len(pInput.MuSig2PartialSigs),
638+
)
639+
)
640+
for i, pubNonce := range pInput.MuSig2PubNonces {
641+
copy(pubNonces[i][:], pubNonce.PubNonce[:])
642+
keys[i] = pubNonce.PubKey
643+
644+
partialSigs[i] = &pInput.MuSig2PartialSigs[i].PartialSig
645+
}
646+
aggregateNonce, err := musig2.AggregateNonces(pubNonces)
647+
if err != nil {
648+
return fmt.Errorf("error aggregating pub nonces: %w",
649+
err)
650+
}
651+
652+
aggKey, _, _, err := musig2.AggregateKeys(
653+
keys, true, musig2.WithBIP86KeyTweak(),
654+
)
655+
if err != nil {
656+
return fmt.Errorf("error aggregating keys: %w", err)
657+
}
658+
659+
combinedNonce, err := computeSigningNonce(
660+
aggregateNonce, aggKey.FinalKey, sigHashMsg,
661+
)
662+
if err != nil {
663+
return fmt.Errorf("error computing signing nonce: %w",
664+
err)
665+
}
666+
667+
combineOpt := musig2.WithBip86TweakedCombine(
668+
sigHashMsg, keys, true,
669+
)
670+
schnorrSig := musig2.CombineSigs(
671+
combinedNonce, partialSigs, combineOpt,
672+
)
673+
674+
serializedWitness, err = writeWitness(schnorrSig.Serialize())
675+
580676
default:
581677
return ErrInvalidPsbtFormat
582678
}
@@ -595,3 +691,57 @@ func finalizeTaprootInput(p *Packet, inIndex int) error {
595691
p.Inputs[inIndex] = *newInput
596692
return nil
597693
}
694+
695+
// computeSigningNonce calculates the final nonce used for signing. This will
696+
// be the R value used in the final signature.
697+
func computeSigningNonce(combinedNonce [musig2.PubNonceSize]byte,
698+
combinedKey *btcec.PublicKey, msg [32]byte) (*btcec.PublicKey, error) {
699+
700+
// Next we'll compute the value b, that blinds our second public
701+
// nonce:
702+
// * b = h(tag=NonceBlindTag, combinedNonce || combinedKey || m).
703+
var (
704+
nonceMsgBuf bytes.Buffer
705+
nonceBlinder btcec.ModNScalar
706+
)
707+
nonceMsgBuf.Write(combinedNonce[:])
708+
nonceMsgBuf.Write(schnorr.SerializePubKey(combinedKey))
709+
nonceMsgBuf.Write(msg[:])
710+
nonceBlindHash := chainhash.TaggedHash(
711+
musig2.NonceBlindTag, nonceMsgBuf.Bytes(),
712+
)
713+
nonceBlinder.SetByteSlice(nonceBlindHash[:])
714+
715+
// Next, we'll parse the public nonces into R1 and R2.
716+
r1J, err := btcec.ParseJacobian(
717+
combinedNonce[:btcec.PubKeyBytesLenCompressed],
718+
)
719+
if err != nil {
720+
return nil, err
721+
}
722+
r2J, err := btcec.ParseJacobian(
723+
combinedNonce[btcec.PubKeyBytesLenCompressed:],
724+
)
725+
if err != nil {
726+
return nil, err
727+
}
728+
729+
// With our nonce blinding value, we'll now combine both the public
730+
// nonces, using the blinding factor to tweak the second nonce:
731+
// * R = R_1 + b*R_2
732+
var nonce btcec.JacobianPoint
733+
btcec.ScalarMultNonConst(&nonceBlinder, &r2J, &r2J)
734+
btcec.AddNonConst(&r1J, &r2J, &nonce)
735+
736+
// If the combined nonce is the point at infinity, we'll use the
737+
// generator point instead.
738+
var infinityPoint btcec.JacobianPoint
739+
if nonce == infinityPoint {
740+
G := btcec.Generator()
741+
G.AsJacobian(&nonce)
742+
}
743+
744+
nonce.ToAffine()
745+
746+
return btcec.NewPublicKey(&nonce.X, &nonce.Y), nil
747+
}

0 commit comments

Comments
 (0)