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

Skip to content

Commit b793c36

Browse files
committed
psbt: support finalizing MuSig2 partial signatures
1 parent 0b97b95 commit b793c36

1 file changed

Lines changed: 142 additions & 0 deletions

File tree

btcutil/psbt/finalizer.go

Lines changed: 142 additions & 0 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"
@@ -577,6 +580,91 @@ func finalizeTaprootInput(p *Packet, inIndex int) error {
577580

578581
serializedWitness, err = writeWitness(witnessStack...)
579582

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

0 commit comments

Comments
 (0)