Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 8e879e1

Browse files
committed
psbt: support finalizing MuSig2 partial signatures
1 parent 53dfb99 commit 8e879e1

1 file changed

Lines changed: 153 additions & 2 deletions

File tree

btcutil/psbt/finalizer.go

Lines changed: 153 additions & 2 deletions
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 {
@@ -133,7 +144,8 @@ func isFinalizable(p *Packet, inIndex int) bool {
133144

134145
// The input cannot be finalized without any signatures.
135146
if pInput.PartialSigs == nil && pInput.TaprootKeySpendSig == nil &&
136-
pInput.TaprootScriptSpendSig == nil {
147+
pInput.TaprootScriptSpendSig == nil &&
148+
pInput.MuSig2PartialSigs == nil {
137149

138150
return false
139151
}
@@ -577,6 +589,91 @@ func finalizeTaprootInput(p *Packet, inIndex int) error {
577589

578590
serializedWitness, err = writeWitness(witnessStack...)
579591

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

0 commit comments

Comments
 (0)