@@ -137,6 +137,12 @@ type batchConfig struct {
137
137
// the caller has to update it in the source of SweepInfo (interface
138
138
// SweepFetcher) and re-add the sweep by calling AddSweep.
139
139
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
140
146
}
141
147
142
148
// rbfCache stores data related to our last fee bump.
@@ -503,9 +509,12 @@ func (b *batch) Run(ctx context.Context) error {
503
509
close (b .finished )
504
510
}()
505
511
506
- if b .muSig2SignSweep == nil {
512
+ if b .muSig2SignSweep == nil && b . cfg . customMuSig2Signer == nil {
507
513
return fmt .Errorf ("no musig2 signer available" )
508
514
}
515
+ if b .muSig2SignSweep != nil && b .cfg .customMuSig2Signer != nil {
516
+ return fmt .Errorf ("both musig2 signers provided" )
517
+ }
509
518
510
519
blockChan , blockErrChan , err :=
511
520
b .chainNotifier .RegisterBlockEpochNtfn (runCtx )
@@ -727,10 +736,11 @@ func (b *batch) publishBatch(ctx context.Context) (btcutil.Amount, error) {
727
736
728
737
weightEstimate .AddP2TROutput ()
729
738
730
- fee = b .rbfCache .FeeRate .FeeForWeight (weightEstimate .Weight ())
739
+ weight := weightEstimate .Weight ()
740
+ feeForWeight := b .rbfCache .FeeRate .FeeForWeight (weight )
731
741
732
742
// Clamp the calculated fee to the max allowed fee amount for the batch.
733
- fee = clampBatchFee (fee , batchAmt )
743
+ fee = clampBatchFee (feeForWeight , batchAmt )
734
744
735
745
// Add the batch transaction output, which excludes the fees paid to
736
746
// miners.
@@ -761,8 +771,9 @@ func (b *batch) publishBatch(ctx context.Context) (btcutil.Amount, error) {
761
771
}
762
772
763
773
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 )
766
777
767
778
b .debugLogTx ("serialized non-coop sweep" , batchTx )
768
779
@@ -857,10 +868,11 @@ func (b *batch) publishBatchCoop(ctx context.Context) (btcutil.Amount,
857
868
858
869
weightEstimate .AddP2TROutput ()
859
870
860
- fee = b .rbfCache .FeeRate .FeeForWeight (weightEstimate .Weight ())
871
+ weight := weightEstimate .Weight ()
872
+ feeForWeight := b .rbfCache .FeeRate .FeeForWeight (weight )
861
873
862
874
// Clamp the calculated fee to the max allowed fee amount for the batch.
863
- fee = clampBatchFee (fee , batchAmt )
875
+ fee = clampBatchFee (feeForWeight , batchAmt )
864
876
865
877
// Add the batch transaction output, which excludes the fees paid to
866
878
// miners.
@@ -896,19 +908,18 @@ func (b *batch) publishBatchCoop(ctx context.Context) (btcutil.Amount,
896
908
return fee , err , false
897
909
}
898
910
899
- prevOutputFetcher := txscript .NewMultiPrevOutFetcher (prevOuts )
900
-
901
911
// Attempt to cooperatively sign the batch tx with the server.
902
912
err = b .coopSignBatchTx (
903
- ctx , packet , sweeps , prevOutputFetcher , prevOuts , psbtBuf ,
913
+ ctx , packet , sweeps , prevOuts , psbtBuf . Bytes () ,
904
914
)
905
915
if err != nil {
906
916
return fee , err , false
907
917
}
908
918
909
919
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 )
912
923
913
924
b .debugLogTx ("serialized coop sweep" , batchTx )
914
925
@@ -942,119 +953,84 @@ func (b *batch) debugLogTx(msg string, tx *wire.MsgTx) {
942
953
// coopSignBatchTx collects the necessary signatures from the server in order
943
954
// to cooperatively sweep the funds.
944
955
func (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 {
947
958
948
959
for i , sweep := range sweeps {
949
960
sweep := sweep
950
961
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 ,
958
964
)
959
965
if err != nil {
960
966
return err
961
967
}
962
968
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 ,
982
971
}
972
+ }
983
973
984
- htlcScript , ok := sweep .htlc .HtlcScript .(* swap.HtlcScriptV3 )
985
- if ! ok {
986
- return fmt .Errorf ("invalid htlc script version" )
987
- }
974
+ return nil
975
+ }
988
976
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 ) {
1001
981
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 )
1015
983
1016
- var serverPublicNonce [musig2 .PubNonceSize ]byte
1017
- copy (serverPublicNonce [:], serverNonce )
984
+ sigHashes := txscript .NewTxSigHashes (unsignedTx , prevOutputFetcher )
1018
985
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
+ )
1028
998
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 :],
1033
1012
}
1013
+ }
1034
1014
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
+ }
1037
1019
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 )
1046
1022
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 ,
1051
1030
)
1052
1031
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 )
1058
1034
}
1059
1035
1060
1036
// 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,
1064
1040
htlcScript .TaprootKey , sigHash , finalSig ,
1065
1041
)
1066
1042
if err != nil {
1067
- return err
1043
+ return nil , fmt .Errorf ("verifySchnorrSig failed: %w" ,
1044
+ err )
1068
1045
}
1069
1046
1070
- packet .UnsignedTx .TxIn [i ].Witness = wire.TxWitness {
1071
- finalSig ,
1072
- }
1047
+ return finalSig , nil
1073
1048
}
1074
1049
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
1076
1125
}
1077
1126
1078
1127
// updateRbfRate updates the fee rate we should use for the new batch
0 commit comments