|
|
@ -896,11 +896,9 @@ func (b *batch) publishBatchCoop(ctx context.Context) (btcutil.Amount,
|
|
|
|
return fee, err, false
|
|
|
|
return fee, err, false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
prevOutputFetcher := txscript.NewMultiPrevOutFetcher(prevOuts)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Attempt to cooperatively sign the batch tx with the server.
|
|
|
|
// Attempt to cooperatively sign the batch tx with the server.
|
|
|
|
err = b.coopSignBatchTx(
|
|
|
|
err = b.coopSignBatchTx(
|
|
|
|
ctx, packet, sweeps, prevOutputFetcher, prevOuts, psbtBuf,
|
|
|
|
ctx, packet, sweeps, prevOuts, psbtBuf.Bytes(),
|
|
|
|
)
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
if err != nil {
|
|
|
|
return fee, err, false
|
|
|
|
return fee, err, false
|
|
|
@ -942,138 +940,148 @@ func (b *batch) debugLogTx(msg string, tx *wire.MsgTx) {
|
|
|
|
// coopSignBatchTx collects the necessary signatures from the server in order
|
|
|
|
// coopSignBatchTx collects the necessary signatures from the server in order
|
|
|
|
// to cooperatively sweep the funds.
|
|
|
|
// to cooperatively sweep the funds.
|
|
|
|
func (b *batch) coopSignBatchTx(ctx context.Context, packet *psbt.Packet,
|
|
|
|
func (b *batch) coopSignBatchTx(ctx context.Context, packet *psbt.Packet,
|
|
|
|
sweeps []sweep, prevOutputFetcher *txscript.MultiPrevOutFetcher,
|
|
|
|
sweeps []sweep, prevOuts map[wire.OutPoint]*wire.TxOut,
|
|
|
|
prevOuts map[wire.OutPoint]*wire.TxOut, psbtBuf bytes.Buffer) error {
|
|
|
|
psbt []byte) error {
|
|
|
|
|
|
|
|
|
|
|
|
for i, sweep := range sweeps {
|
|
|
|
for i, sweep := range sweeps {
|
|
|
|
sweep := sweep
|
|
|
|
sweep := sweep
|
|
|
|
|
|
|
|
|
|
|
|
sigHashes := txscript.NewTxSigHashes(
|
|
|
|
finalSig, err := b.musig2sign(
|
|
|
|
packet.UnsignedTx, prevOutputFetcher,
|
|
|
|
ctx, i, sweep, packet.UnsignedTx, prevOuts, psbt,
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
sigHash, err := txscript.CalcTaprootSignatureHash(
|
|
|
|
|
|
|
|
sigHashes, txscript.SigHashDefault, packet.UnsignedTx,
|
|
|
|
|
|
|
|
i, prevOutputFetcher,
|
|
|
|
|
|
|
|
)
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var (
|
|
|
|
packet.UnsignedTx.TxIn[i].Witness = wire.TxWitness{
|
|
|
|
signers [][]byte
|
|
|
|
finalSig,
|
|
|
|
muSig2Version input.MuSig2Version
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Depending on the MuSig2 version we either pass 32 byte
|
|
|
|
|
|
|
|
// Schnorr public keys or normal 33 byte public keys.
|
|
|
|
|
|
|
|
if sweep.protocolVersion >= loopdb.ProtocolVersionMuSig2 {
|
|
|
|
|
|
|
|
muSig2Version = input.MuSig2Version100RC2
|
|
|
|
|
|
|
|
signers = [][]byte{
|
|
|
|
|
|
|
|
sweep.htlcKeys.SenderInternalPubKey[:],
|
|
|
|
|
|
|
|
sweep.htlcKeys.ReceiverInternalPubKey[:],
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
muSig2Version = input.MuSig2Version040
|
|
|
|
|
|
|
|
signers = [][]byte{
|
|
|
|
|
|
|
|
sweep.htlcKeys.SenderInternalPubKey[1:],
|
|
|
|
|
|
|
|
sweep.htlcKeys.ReceiverInternalPubKey[1:],
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
htlcScript, ok := sweep.htlc.HtlcScript.(*swap.HtlcScriptV3)
|
|
|
|
return nil
|
|
|
|
if !ok {
|
|
|
|
}
|
|
|
|
return fmt.Errorf("invalid htlc script version")
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Now we're creating a local MuSig2 session using the receiver
|
|
|
|
// musig2sign signs one sweep using musig2.
|
|
|
|
// key's key locator and the htlc's root hash.
|
|
|
|
func (b *batch) musig2sign(ctx context.Context, inputIndex int, sweep sweep,
|
|
|
|
musig2SessionInfo, err := b.signerClient.MuSig2CreateSession(
|
|
|
|
unsignedTx *wire.MsgTx, prevOuts map[wire.OutPoint]*wire.TxOut,
|
|
|
|
ctx, muSig2Version,
|
|
|
|
psbt []byte) ([]byte, error) {
|
|
|
|
&sweep.htlcKeys.ClientScriptKeyLocator, signers,
|
|
|
|
|
|
|
|
lndclient.MuSig2TaprootTweakOpt(
|
|
|
|
|
|
|
|
htlcScript.RootHash[:], false,
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
|
|
return fmt.Errorf("signerClient.MuSig2CreateSession "+
|
|
|
|
|
|
|
|
"failed: %w", err)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// With the session active, we can now send the server our
|
|
|
|
prevOutputFetcher := txscript.NewMultiPrevOutFetcher(prevOuts)
|
|
|
|
// public nonce and the sig hash, so that it can create it's own
|
|
|
|
|
|
|
|
// MuSig2 session and return the server side nonce and partial
|
|
|
|
|
|
|
|
// signature.
|
|
|
|
|
|
|
|
serverNonce, serverSig, err := b.muSig2SignSweep(
|
|
|
|
|
|
|
|
ctx, sweep.protocolVersion, sweep.swapHash,
|
|
|
|
|
|
|
|
sweep.swapInvoicePaymentAddr,
|
|
|
|
|
|
|
|
musig2SessionInfo.PublicNonce[:], psbtBuf.Bytes(),
|
|
|
|
|
|
|
|
prevOuts,
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
|
|
return err
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var serverPublicNonce [musig2.PubNonceSize]byte
|
|
|
|
sigHashes := txscript.NewTxSigHashes(unsignedTx, prevOutputFetcher)
|
|
|
|
copy(serverPublicNonce[:], serverNonce)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Register the server's nonce before attempting to create our
|
|
|
|
sigHash, err := txscript.CalcTaprootSignatureHash(
|
|
|
|
// partial signature.
|
|
|
|
sigHashes, txscript.SigHashDefault, unsignedTx, inputIndex,
|
|
|
|
haveAllNonces, err := b.signerClient.MuSig2RegisterNonces(
|
|
|
|
prevOutputFetcher,
|
|
|
|
ctx, musig2SessionInfo.SessionID,
|
|
|
|
)
|
|
|
|
[][musig2.PubNonceSize]byte{serverPublicNonce},
|
|
|
|
if err != nil {
|
|
|
|
)
|
|
|
|
return nil, err
|
|
|
|
if err != nil {
|
|
|
|
}
|
|
|
|
return err
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Sanity check that we have all the nonces.
|
|
|
|
var (
|
|
|
|
if !haveAllNonces {
|
|
|
|
signers [][]byte
|
|
|
|
return fmt.Errorf("invalid MuSig2 session: " +
|
|
|
|
muSig2Version input.MuSig2Version
|
|
|
|
"nonces missing")
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Depending on the MuSig2 version we either pass 32 byte
|
|
|
|
|
|
|
|
// Schnorr public keys or normal 33 byte public keys.
|
|
|
|
|
|
|
|
if sweep.protocolVersion >= loopdb.ProtocolVersionMuSig2 {
|
|
|
|
|
|
|
|
muSig2Version = input.MuSig2Version100RC2
|
|
|
|
|
|
|
|
signers = [][]byte{
|
|
|
|
|
|
|
|
sweep.htlcKeys.SenderInternalPubKey[:],
|
|
|
|
|
|
|
|
sweep.htlcKeys.ReceiverInternalPubKey[:],
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
muSig2Version = input.MuSig2Version040
|
|
|
|
|
|
|
|
signers = [][]byte{
|
|
|
|
|
|
|
|
sweep.htlcKeys.SenderInternalPubKey[1:],
|
|
|
|
|
|
|
|
sweep.htlcKeys.ReceiverInternalPubKey[1:],
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var digest [32]byte
|
|
|
|
htlcScript, ok := sweep.htlc.HtlcScript.(*swap.HtlcScriptV3)
|
|
|
|
copy(digest[:], sigHash)
|
|
|
|
if !ok {
|
|
|
|
|
|
|
|
return nil, fmt.Errorf("invalid htlc script version")
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Since our MuSig2 session has all nonces, we can now create
|
|
|
|
// Now we're creating a local MuSig2 session using the receiver key's
|
|
|
|
// the local partial signature by signing the sig hash.
|
|
|
|
// key locator and the htlc's root hash.
|
|
|
|
_, err = b.signerClient.MuSig2Sign(
|
|
|
|
keyLocator := &sweep.htlcKeys.ClientScriptKeyLocator
|
|
|
|
ctx, musig2SessionInfo.SessionID, digest, false,
|
|
|
|
musig2SessionInfo, err := b.signerClient.MuSig2CreateSession(
|
|
|
|
)
|
|
|
|
ctx, muSig2Version, keyLocator, signers,
|
|
|
|
if err != nil {
|
|
|
|
lndclient.MuSig2TaprootTweakOpt(htlcScript.RootHash[:], false),
|
|
|
|
return err
|
|
|
|
)
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
|
|
|
|
return nil, fmt.Errorf("signerClient.MuSig2CreateSession "+
|
|
|
|
|
|
|
|
"failed: %w", err)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Now combine the partial signatures to use the final combined
|
|
|
|
// With the session active, we can now send the server our
|
|
|
|
// signature in the sweep transaction's witness.
|
|
|
|
// public nonce and the sig hash, so that it can create it's own
|
|
|
|
haveAllSigs, finalSig, err := b.signerClient.MuSig2CombineSig(
|
|
|
|
// MuSig2 session and return the server side nonce and partial
|
|
|
|
ctx, musig2SessionInfo.SessionID, [][]byte{serverSig},
|
|
|
|
// signature.
|
|
|
|
)
|
|
|
|
serverNonce, serverSig, err := b.muSig2SignSweep(
|
|
|
|
if err != nil {
|
|
|
|
ctx, sweep.protocolVersion, sweep.swapHash,
|
|
|
|
return err
|
|
|
|
sweep.swapInvoicePaymentAddr,
|
|
|
|
}
|
|
|
|
musig2SessionInfo.PublicNonce[:], psbt, prevOuts,
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if !haveAllSigs {
|
|
|
|
var serverPublicNonce [musig2.PubNonceSize]byte
|
|
|
|
return fmt.Errorf("failed to combine signatures")
|
|
|
|
copy(serverPublicNonce[:], serverNonce)
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// To be sure that we're good, parse and validate that the
|
|
|
|
// Register the server's nonce before attempting to create our
|
|
|
|
// combined signature is indeed valid for the sig hash and the
|
|
|
|
// partial signature.
|
|
|
|
// internal pubkey.
|
|
|
|
haveAllNonces, err := b.signerClient.MuSig2RegisterNonces(
|
|
|
|
err = b.verifySchnorrSig(
|
|
|
|
ctx, musig2SessionInfo.SessionID,
|
|
|
|
htlcScript.TaprootKey, sigHash, finalSig,
|
|
|
|
[][musig2.PubNonceSize]byte{serverPublicNonce},
|
|
|
|
)
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
packet.UnsignedTx.TxIn[i].Witness = wire.TxWitness{
|
|
|
|
// Sanity check that we have all the nonces.
|
|
|
|
finalSig,
|
|
|
|
if !haveAllNonces {
|
|
|
|
}
|
|
|
|
return nil, fmt.Errorf("invalid MuSig2 session: " +
|
|
|
|
|
|
|
|
"nonces missing")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
var digest [32]byte
|
|
|
|
|
|
|
|
copy(digest[:], sigHash)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Since our MuSig2 session has all nonces, we can now create
|
|
|
|
|
|
|
|
// the local partial signature by signing the sig hash.
|
|
|
|
|
|
|
|
_, err = b.signerClient.MuSig2Sign(
|
|
|
|
|
|
|
|
ctx, musig2SessionInfo.SessionID, digest, false,
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Now combine the partial signatures to use the final combined
|
|
|
|
|
|
|
|
// signature in the sweep transaction's witness.
|
|
|
|
|
|
|
|
haveAllSigs, finalSig, err := b.signerClient.MuSig2CombineSig(
|
|
|
|
|
|
|
|
ctx, musig2SessionInfo.SessionID, [][]byte{serverSig},
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if !haveAllSigs {
|
|
|
|
|
|
|
|
return nil, fmt.Errorf("failed to combine signatures")
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// To be sure that we're good, parse and validate that the
|
|
|
|
|
|
|
|
// combined signature is indeed valid for the sig hash and the
|
|
|
|
|
|
|
|
// internal pubkey.
|
|
|
|
|
|
|
|
err = b.verifySchnorrSig(htlcScript.TaprootKey, sigHash, finalSig)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return finalSig, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// updateRbfRate updates the fee rate we should use for the new batch
|
|
|
|
// updateRbfRate updates the fee rate we should use for the new batch
|
|
|
|