@@ -137,6 +137,12 @@ type batchConfig struct {
137137 // the caller has to update it in the source of SweepInfo (interface
138138 // SweepFetcher) and re-add the sweep by calling AddSweep.
139139 noBumping bool
140+
141+ // customMuSig2Signer is a custom signer. If it is set, it is used to
142+ // create musig2 signatures instead of musig2SignSweep and signerClient.
143+ // Note that musig2SignSweep must be nil in this case, however signer
144+ // client must still be provided, as it is used for non-coop spendings.
145+ customMuSig2Signer SignMuSig2
140146}
141147
142148// rbfCache stores data related to our last fee bump.
@@ -503,9 +509,12 @@ func (b *batch) Run(ctx context.Context) error {
503509 close (b .finished )
504510 }()
505511
506- if b .muSig2SignSweep == nil {
512+ if b .muSig2SignSweep == nil && b . cfg . customMuSig2Signer == nil {
507513 return fmt .Errorf ("no musig2 signer available" )
508514 }
515+ if b .muSig2SignSweep != nil && b .cfg .customMuSig2Signer != nil {
516+ return fmt .Errorf ("both musig2 signers provided" )
517+ }
509518
510519 blockChan , blockErrChan , err :=
511520 b .chainNotifier .RegisterBlockEpochNtfn (runCtx )
@@ -727,10 +736,11 @@ func (b *batch) publishBatch(ctx context.Context) (btcutil.Amount, error) {
727736
728737 weightEstimate .AddP2TROutput ()
729738
730- fee = b .rbfCache .FeeRate .FeeForWeight (weightEstimate .Weight ())
739+ weight := weightEstimate .Weight ()
740+ feeForWeight := b .rbfCache .FeeRate .FeeForWeight (weight )
731741
732742 // Clamp the calculated fee to the max allowed fee amount for the batch.
733- fee = clampBatchFee (fee , batchAmt )
743+ fee = clampBatchFee (feeForWeight , batchAmt )
734744
735745 // Add the batch transaction output, which excludes the fees paid to
736746 // miners.
@@ -761,8 +771,9 @@ func (b *batch) publishBatch(ctx context.Context) (btcutil.Amount, error) {
761771 }
762772
763773 b .log .Infof ("attempting to publish non-coop tx=%v with feerate=%v, " +
764- "totalfee=%v, sweeps=%d, destAddr=%s" , batchTx .TxHash (),
765- b .rbfCache .FeeRate , fee , len (batchTx .TxIn ), address )
774+ "weight=%v, feeForWeight=%v, fee=%v, sweeps=%d, destAddr=%s" ,
775+ batchTx .TxHash (), b .rbfCache .FeeRate , weight , feeForWeight , fee ,
776+ len (batchTx .TxIn ), address )
766777
767778 b .debugLogTx ("serialized non-coop sweep" , batchTx )
768779
@@ -857,10 +868,11 @@ func (b *batch) publishBatchCoop(ctx context.Context) (btcutil.Amount,
857868
858869 weightEstimate .AddP2TROutput ()
859870
860- fee = b .rbfCache .FeeRate .FeeForWeight (weightEstimate .Weight ())
871+ weight := weightEstimate .Weight ()
872+ feeForWeight := b .rbfCache .FeeRate .FeeForWeight (weight )
861873
862874 // Clamp the calculated fee to the max allowed fee amount for the batch.
863- fee = clampBatchFee (fee , batchAmt )
875+ fee = clampBatchFee (feeForWeight , batchAmt )
864876
865877 // Add the batch transaction output, which excludes the fees paid to
866878 // miners.
@@ -896,19 +908,18 @@ func (b *batch) publishBatchCoop(ctx context.Context) (btcutil.Amount,
896908 return fee , err , false
897909 }
898910
899- prevOutputFetcher := txscript .NewMultiPrevOutFetcher (prevOuts )
900-
901911 // Attempt to cooperatively sign the batch tx with the server.
902912 err = b .coopSignBatchTx (
903- ctx , packet , sweeps , prevOutputFetcher , prevOuts , psbtBuf ,
913+ ctx , packet , sweeps , prevOuts , psbtBuf . Bytes () ,
904914 )
905915 if err != nil {
906916 return fee , err , false
907917 }
908918
909919 b .log .Infof ("attempting to publish coop tx=%v with feerate=%v, " +
910- "totalfee=%v, sweeps=%d, destAddr=%s" , batchTx .TxHash (),
911- b .rbfCache .FeeRate , fee , len (batchTx .TxIn ), address )
920+ "weight=%v, feeForWeight=%v, fee=%v, sweeps=%d, destAddr=%s" ,
921+ batchTx .TxHash (), b .rbfCache .FeeRate , weight , feeForWeight , fee ,
922+ len (batchTx .TxIn ), address )
912923
913924 b .debugLogTx ("serialized coop sweep" , batchTx )
914925
@@ -942,119 +953,84 @@ func (b *batch) debugLogTx(msg string, tx *wire.MsgTx) {
942953// coopSignBatchTx collects the necessary signatures from the server in order
943954// to cooperatively sweep the funds.
944955func (b * batch ) coopSignBatchTx (ctx context.Context , packet * psbt.Packet ,
945- sweeps []sweep , prevOutputFetcher * txscript. MultiPrevOutFetcher ,
946- prevOuts map [wire. OutPoint ] * wire. TxOut , psbtBuf bytes. Buffer ) error {
956+ sweeps []sweep , prevOuts map [wire. OutPoint ] * wire. TxOut ,
957+ psbt [] byte ) error {
947958
948959 for i , sweep := range sweeps {
949960 sweep := sweep
950961
951- sigHashes := txscript .NewTxSigHashes (
952- packet .UnsignedTx , prevOutputFetcher ,
953- )
954-
955- sigHash , err := txscript .CalcTaprootSignatureHash (
956- sigHashes , txscript .SigHashDefault , packet .UnsignedTx ,
957- i , prevOutputFetcher ,
962+ finalSig , err := b .musig2sign (
963+ ctx , i , sweep , packet .UnsignedTx , prevOuts , psbt ,
958964 )
959965 if err != nil {
960966 return err
961967 }
962968
963- var (
964- signers [][]byte
965- muSig2Version input.MuSig2Version
966- )
967-
968- // Depending on the MuSig2 version we either pass 32 byte
969- // Schnorr public keys or normal 33 byte public keys.
970- if sweep .protocolVersion >= loopdb .ProtocolVersionMuSig2 {
971- muSig2Version = input .MuSig2Version100RC2
972- signers = [][]byte {
973- sweep .htlcKeys .SenderInternalPubKey [:],
974- sweep .htlcKeys .ReceiverInternalPubKey [:],
975- }
976- } else {
977- muSig2Version = input .MuSig2Version040
978- signers = [][]byte {
979- sweep .htlcKeys .SenderInternalPubKey [1 :],
980- sweep .htlcKeys .ReceiverInternalPubKey [1 :],
981- }
969+ packet .UnsignedTx .TxIn [i ].Witness = wire.TxWitness {
970+ finalSig ,
982971 }
972+ }
983973
984- htlcScript , ok := sweep .htlc .HtlcScript .(* swap.HtlcScriptV3 )
985- if ! ok {
986- return fmt .Errorf ("invalid htlc script version" )
987- }
974+ return nil
975+ }
988976
989- // Now we're creating a local MuSig2 session using the receiver
990- // key's key locator and the htlc's root hash.
991- musig2SessionInfo , err := b .signerClient .MuSig2CreateSession (
992- ctx , muSig2Version ,
993- & sweep .htlcKeys .ClientScriptKeyLocator , signers ,
994- lndclient .MuSig2TaprootTweakOpt (
995- htlcScript .RootHash [:], false ,
996- ),
997- )
998- if err != nil {
999- return err
1000- }
977+ // musig2sign signs one sweep using musig2.
978+ func (b * batch ) musig2sign (ctx context.Context , inputIndex int , sweep sweep ,
979+ unsignedTx * wire.MsgTx , prevOuts map [wire.OutPoint ]* wire.TxOut ,
980+ psbt []byte ) ([]byte , error ) {
1001981
1002- // With the session active, we can now send the server our
1003- // public nonce and the sig hash, so that it can create it's own
1004- // MuSig2 session and return the server side nonce and partial
1005- // signature.
1006- serverNonce , serverSig , err := b .muSig2SignSweep (
1007- ctx , sweep .protocolVersion , sweep .swapHash ,
1008- sweep .swapInvoicePaymentAddr ,
1009- musig2SessionInfo .PublicNonce [:], psbtBuf .Bytes (),
1010- prevOuts ,
1011- )
1012- if err != nil {
1013- return err
1014- }
982+ prevOutputFetcher := txscript .NewMultiPrevOutFetcher (prevOuts )
1015983
1016- var serverPublicNonce [musig2 .PubNonceSize ]byte
1017- copy (serverPublicNonce [:], serverNonce )
984+ sigHashes := txscript .NewTxSigHashes (unsignedTx , prevOutputFetcher )
1018985
1019- // Register the server's nonce before attempting to create our
1020- // partial signature.
1021- haveAllNonces , err := b .signerClient .MuSig2RegisterNonces (
1022- ctx , musig2SessionInfo .SessionID ,
1023- [][musig2 .PubNonceSize ]byte {serverPublicNonce },
1024- )
1025- if err != nil {
1026- return err
1027- }
986+ sigHash , err := txscript .CalcTaprootSignatureHash (
987+ sigHashes , txscript .SigHashDefault , unsignedTx , inputIndex ,
988+ prevOutputFetcher ,
989+ )
990+ if err != nil {
991+ return nil , err
992+ }
993+
994+ var (
995+ signers [][]byte
996+ muSig2Version input.MuSig2Version
997+ )
1028998
1029- // Sanity check that we have all the nonces.
1030- if ! haveAllNonces {
1031- return fmt .Errorf ("invalid MuSig2 session: " +
1032- "nonces missing" )
999+ // Depending on the MuSig2 version we either pass 32 byte
1000+ // Schnorr public keys or normal 33 byte public keys.
1001+ if sweep .protocolVersion >= loopdb .ProtocolVersionMuSig2 {
1002+ muSig2Version = input .MuSig2Version100RC2
1003+ signers = [][]byte {
1004+ sweep .htlcKeys .SenderInternalPubKey [:],
1005+ sweep .htlcKeys .ReceiverInternalPubKey [:],
1006+ }
1007+ } else {
1008+ muSig2Version = input .MuSig2Version040
1009+ signers = [][]byte {
1010+ sweep .htlcKeys .SenderInternalPubKey [1 :],
1011+ sweep .htlcKeys .ReceiverInternalPubKey [1 :],
10331012 }
1013+ }
10341014
1035- var digest [32 ]byte
1036- copy (digest [:], sigHash )
1015+ htlcScript , ok := sweep .htlc .HtlcScript .(* swap.HtlcScriptV3 )
1016+ if ! ok {
1017+ return nil , fmt .Errorf ("invalid htlc script version" )
1018+ }
10371019
1038- // Since our MuSig2 session has all nonces, we can now create
1039- // the local partial signature by signing the sig hash.
1040- _ , err = b .signerClient .MuSig2Sign (
1041- ctx , musig2SessionInfo .SessionID , digest , false ,
1042- )
1043- if err != nil {
1044- return err
1045- }
1020+ var digest [32 ]byte
1021+ copy (digest [:], sigHash )
10461022
1047- // Now combine the partial signatures to use the final combined
1048- // signature in the sweep transaction's witness.
1049- haveAllSigs , finalSig , err := b .signerClient .MuSig2CombineSig (
1050- ctx , musig2SessionInfo .SessionID , [][]byte {serverSig },
1023+ // If a custom signer is installed, use it instead of b.signerClient
1024+ // and b.muSig2SignSweep.
1025+ if b .cfg .customMuSig2Signer != nil {
1026+ // Produce a signature.
1027+ finalSig , err := b .cfg .customMuSig2Signer (
1028+ ctx , muSig2Version , sweep .swapHash ,
1029+ htlcScript .RootHash , digest ,
10511030 )
10521031 if err != nil {
1053- return err
1054- }
1055-
1056- if ! haveAllSigs {
1057- return fmt .Errorf ("failed to combine signatures" )
1032+ return nil , fmt .Errorf ("customMuSig2Signer failed: %w" ,
1033+ err )
10581034 }
10591035
10601036 // To be sure that we're good, parse and validate that the
@@ -1064,15 +1040,88 @@ func (b *batch) coopSignBatchTx(ctx context.Context, packet *psbt.Packet,
10641040 htlcScript .TaprootKey , sigHash , finalSig ,
10651041 )
10661042 if err != nil {
1067- return err
1043+ return nil , fmt .Errorf ("verifySchnorrSig failed: %w" ,
1044+ err )
10681045 }
10691046
1070- packet .UnsignedTx .TxIn [i ].Witness = wire.TxWitness {
1071- finalSig ,
1072- }
1047+ return finalSig , nil
10731048 }
10741049
1075- return nil
1050+ // Now we're creating a local MuSig2 session using the receiver key's
1051+ // key locator and the htlc's root hash.
1052+ keyLocator := & sweep .htlcKeys .ClientScriptKeyLocator
1053+ musig2SessionInfo , err := b .signerClient .MuSig2CreateSession (
1054+ ctx , muSig2Version , keyLocator , signers ,
1055+ lndclient .MuSig2TaprootTweakOpt (htlcScript .RootHash [:], false ),
1056+ )
1057+ if err != nil {
1058+ return nil , fmt .Errorf ("signerClient.MuSig2CreateSession " +
1059+ "failed: %w" , err )
1060+ }
1061+
1062+ // With the session active, we can now send the server our
1063+ // public nonce and the sig hash, so that it can create it's own
1064+ // MuSig2 session and return the server side nonce and partial
1065+ // signature.
1066+ serverNonce , serverSig , err := b .muSig2SignSweep (
1067+ ctx , sweep .protocolVersion , sweep .swapHash ,
1068+ sweep .swapInvoicePaymentAddr ,
1069+ musig2SessionInfo .PublicNonce [:], psbt , prevOuts ,
1070+ )
1071+ if err != nil {
1072+ return nil , err
1073+ }
1074+
1075+ var serverPublicNonce [musig2 .PubNonceSize ]byte
1076+ copy (serverPublicNonce [:], serverNonce )
1077+
1078+ // Register the server's nonce before attempting to create our
1079+ // partial signature.
1080+ haveAllNonces , err := b .signerClient .MuSig2RegisterNonces (
1081+ ctx , musig2SessionInfo .SessionID ,
1082+ [][musig2 .PubNonceSize ]byte {serverPublicNonce },
1083+ )
1084+ if err != nil {
1085+ return nil , err
1086+ }
1087+
1088+ // Sanity check that we have all the nonces.
1089+ if ! haveAllNonces {
1090+ return nil , fmt .Errorf ("invalid MuSig2 session: " +
1091+ "nonces missing" )
1092+ }
1093+
1094+ // Since our MuSig2 session has all nonces, we can now create
1095+ // the local partial signature by signing the sig hash.
1096+ _ , err = b .signerClient .MuSig2Sign (
1097+ ctx , musig2SessionInfo .SessionID , digest , false ,
1098+ )
1099+ if err != nil {
1100+ return nil , err
1101+ }
1102+
1103+ // Now combine the partial signatures to use the final combined
1104+ // signature in the sweep transaction's witness.
1105+ haveAllSigs , finalSig , err := b .signerClient .MuSig2CombineSig (
1106+ ctx , musig2SessionInfo .SessionID , [][]byte {serverSig },
1107+ )
1108+ if err != nil {
1109+ return nil , err
1110+ }
1111+
1112+ if ! haveAllSigs {
1113+ return nil , fmt .Errorf ("failed to combine signatures" )
1114+ }
1115+
1116+ // To be sure that we're good, parse and validate that the
1117+ // combined signature is indeed valid for the sig hash and the
1118+ // internal pubkey.
1119+ err = b .verifySchnorrSig (htlcScript .TaprootKey , sigHash , finalSig )
1120+ if err != nil {
1121+ return nil , err
1122+ }
1123+
1124+ return finalSig , nil
10761125}
10771126
10781127// updateRbfRate updates the fee rate we should use for the new batch
0 commit comments