From 2b2c8b5a1090ee8672e690710ebd3dfd1520b077 Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Sun, 5 Apr 2020 17:06:01 -0700 Subject: [PATCH 1/5] lnwallet/interface_test: wait for bitcoind startup Flakes locally for me on darwin. --- lnwallet/interface_test.go | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/lnwallet/interface_test.go b/lnwallet/interface_test.go index 2fe3da53..1b6a1f28 100644 --- a/lnwallet/interface_test.go +++ b/lnwallet/interface_test.go @@ -38,6 +38,7 @@ import ( "github.com/lightningnetwork/lnd/channeldb/kvdb" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/keychain" + "github.com/lightningnetwork/lnd/lntest/wait" "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwallet/btcwallet" "github.com/lightningnetwork/lnd/lnwallet/chainfee" @@ -3062,21 +3063,25 @@ func runTests(t *testing.T, walletDriver *lnwallet.WalletDriver, defer bitcoind.Process.Kill() // Wait for the bitcoind instance to start up. - time.Sleep(time.Second) host := fmt.Sprintf("127.0.0.1:%d", rpcPort) - chainConn, err := chain.NewBitcoindConn( - netParams, host, "weks", "weks", zmqBlockHost, - zmqTxHost, 100*time.Millisecond, - ) + var chainConn *chain.BitcoindConn + err = wait.NoError(func() error { + chainConn, err = chain.NewBitcoindConn( + netParams, host, "weks", "weks", + zmqBlockHost, zmqTxHost, + 100*time.Millisecond, + ) + if err != nil { + return err + } + + return chainConn.Start() + }, 10*time.Second) if err != nil { t.Fatalf("unable to establish connection to "+ "bitcoind: %v", err) } - if err := chainConn.Start(); err != nil { - t.Fatalf("unable to establish connection to "+ - "bitcoind: %v", err) - } defer chainConn.Stop() // Create a btcwallet bitcoind client for both Alice and From 37dffb225a495af38f86bbcb8bd47ef3d74c8556 Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Sun, 5 Apr 2020 17:06:14 -0700 Subject: [PATCH 2/5] input: introduce Signature iface This commit introduces the Signature interface which will be used by our witness construction methods instead of passing in raw byte slices. This will be used later to inject various kinds of mock signatures, e.g. 73-byte signatures for simulating worst-case witness weight. --- input/script_utils.go | 15 +++++++++++---- input/script_utils_test.go | 18 ++++++++++++++---- lnwallet/channel.go | 15 ++++++++++++--- 3 files changed, 37 insertions(+), 11 deletions(-) diff --git a/input/script_utils.go b/input/script_utils.go index d8f80f31..56bfbe97 100644 --- a/input/script_utils.go +++ b/input/script_utils.go @@ -23,6 +23,13 @@ var ( SequenceLockTimeSeconds = uint32(1 << 22) ) +// Signature is an interface for objects that can populate signatures during +// witness construction. +type Signature interface { + // Serialize returns a DER-encoded ECDSA signature. + Serialize() []byte +} + // WitnessScriptHash generates a pay-to-witness-script-hash public key script // paying to a version 0 witness program paying to the passed redeem script. func WitnessScriptHash(witnessScript []byte) ([]byte, error) { @@ -343,7 +350,7 @@ func SenderHtlcSpendRedeem(signer Signer, signDesc *SignDescriptor, // HTLC to activate the time locked covenant clause of a soon to be expired // HTLC. This script simply spends the multi-sig output using the // pre-generated HTLC timeout transaction. -func SenderHtlcSpendTimeout(receiverSig []byte, +func SenderHtlcSpendTimeout(receiverSig Signature, receiverSigHash txscript.SigHashType, signer Signer, signDesc *SignDescriptor, htlcTimeoutTx *wire.MsgTx) ( wire.TxWitness, error) { @@ -359,7 +366,7 @@ func SenderHtlcSpendTimeout(receiverSig []byte, // original OP_CHECKMULTISIG. witnessStack := wire.TxWitness(make([][]byte, 5)) witnessStack[0] = nil - witnessStack[1] = append(receiverSig, byte(receiverSigHash)) + witnessStack[1] = append(receiverSig.Serialize(), byte(receiverSigHash)) witnessStack[2] = append(sweepSig, byte(signDesc.HashType)) witnessStack[3] = nil witnessStack[4] = signDesc.WitnessScript @@ -508,7 +515,7 @@ func ReceiverHTLCScript(cltvExpiry uint32, senderHtlcKey, // signed has a relative timelock delay enforced by its sequence number. This // delay give the sender of the HTLC enough time to revoke the output if this // is a breach commitment transaction. -func ReceiverHtlcSpendRedeem(senderSig []byte, +func ReceiverHtlcSpendRedeem(senderSig Signature, senderSigHash txscript.SigHashType, paymentPreimage []byte, signer Signer, signDesc *SignDescriptor, htlcSuccessTx *wire.MsgTx) ( wire.TxWitness, error) { @@ -527,7 +534,7 @@ func ReceiverHtlcSpendRedeem(senderSig []byte, // order to consume the extra pop within OP_CHECKMULTISIG. witnessStack := wire.TxWitness(make([][]byte, 5)) witnessStack[0] = nil - witnessStack[1] = append(senderSig, byte(senderSigHash)) + witnessStack[1] = append(senderSig.Serialize(), byte(senderSigHash)) witnessStack[2] = append(sweepSig, byte(signDesc.HashType)) witnessStack[3] = paymentPreimage witnessStack[4] = signDesc.WitnessScript diff --git a/input/script_utils_test.go b/input/script_utils_test.go index be1e2d0f..a76183dc 100644 --- a/input/script_utils_test.go +++ b/input/script_utils_test.go @@ -226,7 +226,7 @@ func TestHTLCSenderSpendValidation(t *testing.T) { htlcOutput *wire.TxOut sweepTxSigHashes *txscript.TxSigHashes senderCommitTx, sweepTx *wire.MsgTx - bobRecvrSig []byte + bobRecvrSig *btcec.Signature bobSigHash txscript.SigHashType ) @@ -303,10 +303,15 @@ func TestHTLCSenderSpendValidation(t *testing.T) { SigHashes: sweepTxSigHashes, InputIndex: 0, } - bobRecvrSig, err = bobSigner.SignOutputRaw(sweepTx, &bobSignDesc) + bobSigBytes, err := bobSigner.SignOutputRaw(sweepTx, &bobSignDesc) if err != nil { t.Fatalf("unable to generate alice signature: %v", err) } + + bobRecvrSig, err = btcec.ParseDERSignature(bobSigBytes, btcec.S256()) + if err != nil { + t.Fatalf("unable to parse signature: %v", err) + } } testCases := []struct { @@ -622,7 +627,7 @@ func TestHTLCReceiverSpendValidation(t *testing.T) { htlcOutput *wire.TxOut receiverCommitTx, sweepTx *wire.MsgTx sweepTxSigHashes *txscript.TxSigHashes - aliceSenderSig []byte + aliceSenderSig *btcec.Signature aliceSigHash txscript.SigHashType ) @@ -695,10 +700,15 @@ func TestHTLCReceiverSpendValidation(t *testing.T) { SigHashes: sweepTxSigHashes, InputIndex: 0, } - aliceSenderSig, err = aliceSigner.SignOutputRaw(sweepTx, &aliceSignDesc) + aliceSigBytes, err := aliceSigner.SignOutputRaw(sweepTx, &aliceSignDesc) if err != nil { t.Fatalf("unable to generate alice signature: %v", err) } + + aliceSenderSig, err = btcec.ParseDERSignature(aliceSigBytes, btcec.S256()) + if err != nil { + t.Fatalf("unable to parse signature: %v", err) + } } // TODO(roasbeef): modify valid to check precise script errors? diff --git a/lnwallet/channel.go b/lnwallet/channel.go index c9ca227d..11eb1159 100644 --- a/lnwallet/channel.go +++ b/lnwallet/channel.go @@ -5459,11 +5459,16 @@ func newOutgoingHtlcResolution(signer input.Signer, InputIndex: 0, } + htlcSig, err := btcec.ParseDERSignature(htlc.Signature, btcec.S256()) + if err != nil { + return nil, err + } + // With the sign desc created, we can now construct the full witness // for the timeout transaction, and populate it as well. sigHashType := HtlcSigHashType(chanType) timeoutWitness, err := input.SenderHtlcSpendTimeout( - htlc.Signature, sigHashType, signer, &timeoutSignDesc, timeoutTx, + htlcSig, sigHashType, signer, &timeoutSignDesc, timeoutTx, ) if err != nil { return nil, err @@ -5585,14 +5590,18 @@ func newIncomingHtlcResolution(signer input.Signer, InputIndex: 0, } + htlcSig, err := btcec.ParseDERSignature(htlc.Signature, btcec.S256()) + if err != nil { + return nil, err + } + // Next, we'll construct the full witness needed to satisfy the input of // the success transaction. Don't specify the preimage yet. The preimage // will be supplied by the contract resolver, either directly or when it // becomes known. sigHashType := HtlcSigHashType(chanType) successWitness, err := input.ReceiverHtlcSpendRedeem( - htlc.Signature, sigHashType, nil, signer, &successSignDesc, - successTx, + htlcSig, sigHashType, nil, signer, &successSignDesc, successTx, ) if err != nil { return nil, err From 0f94b8dc624cf0e96ddc8fe1b8e3bf4b3fc4c074 Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Sun, 5 Apr 2020 17:06:38 -0700 Subject: [PATCH 3/5] multi: return input.Signature from SignOutputRaw --- chancloser.go | 2 +- contractcourt/htlc_timeout_resolver_test.go | 25 +++++++++++---- discovery/gossiper_test.go | 3 +- fundingmanager.go | 26 +++++++++++---- fundingmanager_test.go | 6 ++-- htlcswitch/mock.go | 6 ++-- input/script_utils.go | 32 +++++++++++-------- input/script_utils_test.go | 12 ++++--- input/signer.go | 3 +- input/size.go | 16 +++++++--- input/test_utils.go | 6 ++-- lnwallet/btcwallet/signer.go | 6 ++-- lnwallet/channel.go | 7 ++-- lnwallet/channel_test.go | 16 +++++----- lnwallet/interface.go | 3 +- lnwallet/interface_test.go | 4 +-- lnwallet/reservation.go | 16 ++++++---- lnwallet/sigpool.go | 2 +- lnwallet/wallet.go | 26 +++++---------- lnwire/signature.go | 3 +- mock.go | 4 +-- netann/channel_update_test.go | 3 +- netann/node_signer.go | 7 ++-- netann/sign.go | 3 +- peer_test.go | 16 +++++----- server.go | 2 +- sweep/test_utils.go | 15 +++++++-- watchtower/lookout/justice_descriptor_test.go | 4 +-- watchtower/wtmock/signer.go | 4 +-- 29 files changed, 168 insertions(+), 110 deletions(-) diff --git a/chancloser.go b/chancloser.go index 4dc28d40..befb6ccd 100644 --- a/chancloser.go +++ b/chancloser.go @@ -589,7 +589,7 @@ func (c *channelCloser) proposeCloseSigned(fee btcutil.Amount) (*lnwire.ClosingS // party responds we'll be able to decide if we've agreed on fees or // not. c.lastFeeProposal = fee - parsedSig, err := lnwire.NewSigFromRawSignature(rawSig) + parsedSig, err := lnwire.NewSigFromSignature(rawSig) if err != nil { return nil, err } diff --git a/contractcourt/htlc_timeout_resolver_test.go b/contractcourt/htlc_timeout_resolver_test.go index 52324698..923fb3ea 100644 --- a/contractcourt/htlc_timeout_resolver_test.go +++ b/contractcourt/htlc_timeout_resolver_test.go @@ -7,6 +7,7 @@ import ( "testing" "time" + "github.com/btcsuite/btcd/btcec" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/chainntnfs" @@ -15,12 +16,22 @@ import ( "github.com/lightningnetwork/lnd/lnwallet" ) +type dummySignature struct{} + +func (s *dummySignature) Serialize() []byte { + return []byte{} +} + +func (s *dummySignature) Verify(_ []byte, _ *btcec.PublicKey) bool { + return true +} + type mockSigner struct { } func (m *mockSigner) SignOutputRaw(tx *wire.MsgTx, - signDesc *input.SignDescriptor) ([]byte, error) { - return nil, nil + signDesc *input.SignDescriptor) (input.Signature, error) { + return &dummySignature{}, nil } func (m *mockSigner) ComputeInputScript(tx *wire.MsgTx, @@ -145,8 +156,8 @@ func TestHtlcTimeoutResolver(t *testing.T) { timeout: true, txToBroadcast: func() (*wire.MsgTx, error) { witness, err := input.SenderHtlcSpendTimeout( - nil, txscript.SigHashAll, signer, - fakeSignDesc, sweepTx, + &dummySignature{}, txscript.SigHashAll, + signer, fakeSignDesc, sweepTx, ) if err != nil { return nil, err @@ -165,9 +176,9 @@ func TestHtlcTimeoutResolver(t *testing.T) { timeout: false, txToBroadcast: func() (*wire.MsgTx, error) { witness, err := input.ReceiverHtlcSpendRedeem( - nil, txscript.SigHashAll, - fakePreimageBytes, signer, - fakeSignDesc, sweepTx, + &dummySignature{}, txscript.SigHashAll, + fakePreimageBytes, signer, fakeSignDesc, + sweepTx, ) if err != nil { return nil, err diff --git a/discovery/gossiper_test.go b/discovery/gossiper_test.go index f927a960..75d3cef6 100644 --- a/discovery/gossiper_test.go +++ b/discovery/gossiper_test.go @@ -23,6 +23,7 @@ import ( "github.com/go-errors/errors" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/channeldb" + "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/lnpeer" "github.com/lightningnetwork/lnd/lntest/wait" "github.com/lightningnetwork/lnd/lnwire" @@ -96,7 +97,7 @@ type mockSigner struct { } func (n *mockSigner) SignMessage(pubKey *btcec.PublicKey, - msg []byte) (*btcec.Signature, error) { + msg []byte) (input.Signature, error) { if !pubKey.IsEqual(n.privKey.PubKey()) { return nil, fmt.Errorf("unknown public key") diff --git a/fundingmanager.go b/fundingmanager.go index 23126c30..761227c9 100644 --- a/fundingmanager.go +++ b/fundingmanager.go @@ -254,7 +254,8 @@ type fundingConfig struct { // // TODO(roasbeef): should instead pass on this responsibility to a // distinct sub-system? - SignMessage func(pubKey *btcec.PublicKey, msg []byte) (*btcec.Signature, error) + SignMessage func(pubKey *btcec.PublicKey, + msg []byte) (input.Signature, error) // CurrentNodeAnnouncement should return the latest, fully signed node // announcement from the backing Lightning Network node. @@ -1726,7 +1727,7 @@ func (f *fundingManager) continueFundingAccept(resCtx *reservationWithCtx, PendingChannelID: pendingChanID, FundingPoint: *outPoint, } - fundingCreated.CommitSig, err = lnwire.NewSigFromRawSignature(sig) + fundingCreated.CommitSig, err = lnwire.NewSigFromSignature(sig) if err != nil { fndgLog.Errorf("Unable to parse signature: %v", err) f.failFundingFlow(resCtx.peer, pendingChanID, err) @@ -1775,14 +1776,21 @@ func (f *fundingManager) handleFundingCreated(fmsg *fundingCreatedMsg) { fndgLog.Infof("completing pending_id(%x) with ChannelPoint(%v)", pendingChanID[:], fundingOut) + commitSig, err := fmsg.msg.CommitSig.ToSignature() + if err != nil { + fndgLog.Errorf("unable to parse signature: %v", err) + f.failFundingFlow(fmsg.peer, pendingChanID, err) + return + } + // With all the necessary data available, attempt to advance the // funding workflow to the next stage. If this succeeds then the // funding transaction will broadcast after our next message. // CompleteReservationSingle will also mark the channel as 'IsPending' // in the database. - commitSig := fmsg.msg.CommitSig.ToSignatureBytes() completeChan, err := resCtx.reservation.CompleteReservationSingle( - &fundingOut, commitSig) + &fundingOut, commitSig, + ) if err != nil { // TODO(roasbeef): better error logging: peerID, channelID, etc. fndgLog.Errorf("unable to complete single reservation: %v", err) @@ -1837,7 +1845,7 @@ func (f *fundingManager) handleFundingCreated(fmsg *fundingCreatedMsg) { // With their signature for our version of the commitment transaction // verified, we can now send over our signature to the remote peer. _, sig := resCtx.reservation.OurSignatures() - ourCommitSig, err := lnwire.NewSigFromRawSignature(sig) + ourCommitSig, err := lnwire.NewSigFromSignature(sig) if err != nil { fndgLog.Errorf("unable to parse signature: %v", err) f.failFundingFlow(fmsg.peer, pendingChanID, err) @@ -1950,7 +1958,13 @@ func (f *fundingManager) handleFundingSigned(fmsg *fundingSignedMsg) { // The remote peer has responded with a signature for our commitment // transaction. We'll verify the signature for validity, then commit // the state to disk as we can now open the channel. - commitSig := fmsg.msg.CommitSig.ToSignatureBytes() + commitSig, err := fmsg.msg.CommitSig.ToSignature() + if err != nil { + fndgLog.Errorf("Unable to parse signature: %v", err) + f.failFundingFlow(fmsg.peer, pendingChanID, err) + return + } + completeChan, err := resCtx.reservation.CompleteReservation( nil, commitSig, ) diff --git a/fundingmanager_test.go b/fundingmanager_test.go index c7247136..b222bf94 100644 --- a/fundingmanager_test.go +++ b/fundingmanager_test.go @@ -334,7 +334,9 @@ func createTestFundingManager(t *testing.T, privKey *btcec.PrivateKey, Wallet: lnw, Notifier: chainNotifier, FeeEstimator: estimator, - SignMessage: func(pubKey *btcec.PublicKey, msg []byte) (*btcec.Signature, error) { + SignMessage: func(pubKey *btcec.PublicKey, + msg []byte) (input.Signature, error) { + return testSig, nil }, SendAnnouncement: func(msg lnwire.Message, @@ -474,7 +476,7 @@ func recreateAliceFundingManager(t *testing.T, alice *testNode) { Notifier: oldCfg.Notifier, FeeEstimator: oldCfg.FeeEstimator, SignMessage: func(pubKey *btcec.PublicKey, - msg []byte) (*btcec.Signature, error) { + msg []byte) (input.Signature, error) { return testSig, nil }, SendAnnouncement: func(msg lnwire.Message, diff --git a/htlcswitch/mock.go b/htlcswitch/mock.go index cefb673a..c65e5fb0 100644 --- a/htlcswitch/mock.go +++ b/htlcswitch/mock.go @@ -852,7 +852,9 @@ type mockSigner struct { key *btcec.PrivateKey } -func (m *mockSigner) SignOutputRaw(tx *wire.MsgTx, signDesc *input.SignDescriptor) ([]byte, error) { +func (m *mockSigner) SignOutputRaw(tx *wire.MsgTx, + signDesc *input.SignDescriptor) (input.Signature, error) { + amt := signDesc.Output.Value witnessScript := signDesc.WitnessScript privKey := m.key @@ -877,7 +879,7 @@ func (m *mockSigner) SignOutputRaw(tx *wire.MsgTx, signDesc *input.SignDescripto return nil, err } - return sig[:len(sig)-1], nil + return btcec.ParseDERSignature(sig[:len(sig)-1], btcec.S256()) } func (m *mockSigner) ComputeInputScript(tx *wire.MsgTx, signDesc *input.SignDescriptor) (*input.Script, error) { diff --git a/input/script_utils.go b/input/script_utils.go index 56bfbe97..97938ce8 100644 --- a/input/script_utils.go +++ b/input/script_utils.go @@ -28,6 +28,10 @@ var ( type Signature interface { // Serialize returns a DER-encoded ECDSA signature. Serialize() []byte + + // Verify return true if the ECDSA signature is valid for the passed + // message digest under the provided public key. + Verify([]byte, *btcec.PublicKey) bool } // WitnessScriptHash generates a pay-to-witness-script-hash public key script @@ -290,7 +294,7 @@ func SenderHtlcSpendRevokeWithKey(signer Signer, signDesc *SignDescriptor, // manner in order to encode the revocation contract into a sig+key // pair. witnessStack := wire.TxWitness(make([][]byte, 3)) - witnessStack[0] = append(sweepSig, byte(signDesc.HashType)) + witnessStack[0] = append(sweepSig.Serialize(), byte(signDesc.HashType)) witnessStack[1] = revokeKey.SerializeCompressed() witnessStack[2] = signDesc.WitnessScript @@ -339,7 +343,7 @@ func SenderHtlcSpendRedeem(signer Signer, signDesc *SignDescriptor, // generated above under the receiver's public key, and the payment // pre-image. witnessStack := wire.TxWitness(make([][]byte, 3)) - witnessStack[0] = append(sweepSig, byte(signDesc.HashType)) + witnessStack[0] = append(sweepSig.Serialize(), byte(signDesc.HashType)) witnessStack[1] = paymentPreimage witnessStack[2] = signDesc.WitnessScript @@ -367,7 +371,7 @@ func SenderHtlcSpendTimeout(receiverSig Signature, witnessStack := wire.TxWitness(make([][]byte, 5)) witnessStack[0] = nil witnessStack[1] = append(receiverSig.Serialize(), byte(receiverSigHash)) - witnessStack[2] = append(sweepSig, byte(signDesc.HashType)) + witnessStack[2] = append(sweepSig.Serialize(), byte(signDesc.HashType)) witnessStack[3] = nil witnessStack[4] = signDesc.WitnessScript @@ -535,7 +539,7 @@ func ReceiverHtlcSpendRedeem(senderSig Signature, witnessStack := wire.TxWitness(make([][]byte, 5)) witnessStack[0] = nil witnessStack[1] = append(senderSig.Serialize(), byte(senderSigHash)) - witnessStack[2] = append(sweepSig, byte(signDesc.HashType)) + witnessStack[2] = append(sweepSig.Serialize(), byte(signDesc.HashType)) witnessStack[3] = paymentPreimage witnessStack[4] = signDesc.WitnessScript @@ -562,7 +566,7 @@ func ReceiverHtlcSpendRevokeWithKey(signer Signer, signDesc *SignDescriptor, // witness stack in order to force script execution to the HTLC // revocation clause. witnessStack := wire.TxWitness(make([][]byte, 3)) - witnessStack[0] = append(sweepSig, byte(signDesc.HashType)) + witnessStack[0] = append(sweepSig.Serialize(), byte(signDesc.HashType)) witnessStack[1] = revokeKey.SerializeCompressed() witnessStack[2] = signDesc.WitnessScript @@ -627,7 +631,7 @@ func ReceiverHtlcSpendTimeout(signer Signer, signDesc *SignDescriptor, } witnessStack := wire.TxWitness(make([][]byte, 3)) - witnessStack[0] = append(sweepSig, byte(signDesc.HashType)) + witnessStack[0] = append(sweepSig.Serialize(), byte(signDesc.HashType)) witnessStack[1] = nil witnessStack[2] = signDesc.WitnessScript @@ -732,7 +736,7 @@ func HtlcSpendSuccess(signer Signer, signDesc *SignDescriptor, // witness script), in order to force execution to the second portion // of the if clause. witnessStack := wire.TxWitness(make([][]byte, 3)) - witnessStack[0] = append(sweepSig, byte(signDesc.HashType)) + witnessStack[0] = append(sweepSig.Serialize(), byte(signDesc.HashType)) witnessStack[1] = nil witnessStack[2] = signDesc.WitnessScript @@ -757,7 +761,7 @@ func HtlcSpendRevoke(signer Signer, signDesc *SignDescriptor, // witness script), in order to force execution to the revocation // clause in the second level HTLC script. witnessStack := wire.TxWitness(make([][]byte, 3)) - witnessStack[0] = append(sweepSig, byte(signDesc.HashType)) + witnessStack[0] = append(sweepSig.Serialize(), byte(signDesc.HashType)) witnessStack[1] = []byte{1} witnessStack[2] = signDesc.WitnessScript @@ -788,7 +792,7 @@ func HtlcSecondLevelSpend(signer Signer, signDesc *SignDescriptor, // witness script), in order to force execution to the second portion // of the if clause. witnessStack := wire.TxWitness(make([][]byte, 3)) - witnessStack[0] = append(sweepSig, byte(txscript.SigHashAll)) + witnessStack[0] = append(sweepSig.Serialize(), byte(txscript.SigHashAll)) witnessStack[1] = nil witnessStack[2] = signDesc.WitnessScript @@ -892,7 +896,7 @@ func CommitSpendTimeout(signer Signer, signDesc *SignDescriptor, // place an empty byte in order to ensure our script is still valid // from the PoV of nodes that are enforcing minimal OP_IF/OP_NOTIF. witnessStack := wire.TxWitness(make([][]byte, 3)) - witnessStack[0] = append(sweepSig, byte(signDesc.HashType)) + witnessStack[0] = append(sweepSig.Serialize(), byte(signDesc.HashType)) witnessStack[1] = nil witnessStack[2] = signDesc.WitnessScript @@ -917,7 +921,7 @@ func CommitSpendRevoke(signer Signer, signDesc *SignDescriptor, // Place a 1 as the first item in the evaluated witness stack to // force script execution to the revocation clause. witnessStack := wire.TxWitness(make([][]byte, 3)) - witnessStack[0] = append(sweepSig, byte(signDesc.HashType)) + witnessStack[0] = append(sweepSig.Serialize(), byte(signDesc.HashType)) witnessStack[1] = []byte{1} witnessStack[2] = signDesc.WitnessScript @@ -951,7 +955,7 @@ func CommitSpendNoDelay(signer Signer, signDesc *SignDescriptor, // exact same as a regular p2wkh witness, depending on the value of the // tweakless bool. witness := make([][]byte, 2) - witness[0] = append(sweepSig, byte(signDesc.HashType)) + witness[0] = append(sweepSig.Serialize(), byte(signDesc.HashType)) switch tweakless { // If we're tweaking the key, then we use the tweaked public key as the @@ -1028,7 +1032,7 @@ func CommitSpendToRemoteConfirmed(signer Signer, signDesc *SignDescriptor, // Finally, we'll manually craft the witness. The witness here is the // signature and the redeem script. witnessStack := make([][]byte, 2) - witnessStack[0] = append(sweepSig, byte(signDesc.HashType)) + witnessStack[0] = append(sweepSig.Serialize(), byte(signDesc.HashType)) witnessStack[1] = signDesc.WitnessScript return witnessStack, nil @@ -1084,7 +1088,7 @@ func CommitSpendAnchor(signer Signer, signDesc *SignDescriptor, // The witness here is just a signature and the redeem script. witnessStack := make([][]byte, 2) - witnessStack[0] = append(sweepSig, byte(signDesc.HashType)) + witnessStack[0] = append(sweepSig.Serialize(), byte(signDesc.HashType)) witnessStack[1] = signDesc.WitnessScript return witnessStack, nil diff --git a/input/script_utils_test.go b/input/script_utils_test.go index a76183dc..5978d3ee 100644 --- a/input/script_utils_test.go +++ b/input/script_utils_test.go @@ -303,12 +303,14 @@ func TestHTLCSenderSpendValidation(t *testing.T) { SigHashes: sweepTxSigHashes, InputIndex: 0, } - bobSigBytes, err := bobSigner.SignOutputRaw(sweepTx, &bobSignDesc) + bobSig, err := bobSigner.SignOutputRaw(sweepTx, &bobSignDesc) if err != nil { t.Fatalf("unable to generate alice signature: %v", err) } - bobRecvrSig, err = btcec.ParseDERSignature(bobSigBytes, btcec.S256()) + bobRecvrSig, err = btcec.ParseDERSignature( + bobSig.Serialize(), btcec.S256(), + ) if err != nil { t.Fatalf("unable to parse signature: %v", err) } @@ -700,12 +702,14 @@ func TestHTLCReceiverSpendValidation(t *testing.T) { SigHashes: sweepTxSigHashes, InputIndex: 0, } - aliceSigBytes, err := aliceSigner.SignOutputRaw(sweepTx, &aliceSignDesc) + aliceSig, err := aliceSigner.SignOutputRaw(sweepTx, &aliceSignDesc) if err != nil { t.Fatalf("unable to generate alice signature: %v", err) } - aliceSenderSig, err = btcec.ParseDERSignature(aliceSigBytes, btcec.S256()) + aliceSenderSig, err = btcec.ParseDERSignature( + aliceSig.Serialize(), btcec.S256(), + ) if err != nil { t.Fatalf("unable to parse signature: %v", err) } diff --git a/input/signer.go b/input/signer.go index cd320255..86622638 100644 --- a/input/signer.go +++ b/input/signer.go @@ -14,7 +14,8 @@ type Signer interface { // according to the data within the passed SignDescriptor. // // NOTE: The resulting signature should be void of a sighash byte. - SignOutputRaw(tx *wire.MsgTx, signDesc *SignDescriptor) ([]byte, error) + SignOutputRaw(tx *wire.MsgTx, + signDesc *SignDescriptor) (Signature, error) // ComputeInputScript generates a complete InputIndex for the passed // transaction with the signature as defined within the passed diff --git a/input/size.go b/input/size.go index 32a89bcd..74f1247d 100644 --- a/input/size.go +++ b/input/size.go @@ -410,6 +410,14 @@ const ( OfferedHtlcPenaltyWitnessSize = 1 + 1 + 73 + 1 + 33 + 1 + OfferedHtlcScriptSize ) +type dummySignature struct{} + +func (d *dummySignature) Serialize() []byte { + // Always return worst-case signature length, excluding the one byte + // sighash flag. + return make([]byte, 73-1) +} + // dummySigner is a fake signer used for size (upper bound) calculations. type dummySigner struct { Signer @@ -417,12 +425,10 @@ type dummySigner struct { // SignOutputRaw generates a signature for the passed transaction according to // the data within the passed SignDescriptor. -func (s *dummySigner) SignOutputRaw(tx *wire.MsgTx, signDesc *SignDescriptor) ( - []byte, error) { +func (s *dummySigner) SignOutputRaw(tx *wire.MsgTx, + signDesc *SignDescriptor) (Signature, error) { - // Always return worst-case signature length, excluding the one byte - // sighash flag. - return make([]byte, 73-1), nil + return &dummySignature{}, nil } var ( diff --git a/input/test_utils.go b/input/test_utils.go index b9719438..5b004417 100644 --- a/input/test_utils.go +++ b/input/test_utils.go @@ -50,7 +50,9 @@ type MockSigner struct { // SignOutputRaw generates a signature for the passed transaction according to // the data within the passed SignDescriptor. -func (m *MockSigner) SignOutputRaw(tx *wire.MsgTx, signDesc *SignDescriptor) ([]byte, error) { +func (m *MockSigner) SignOutputRaw(tx *wire.MsgTx, + signDesc *SignDescriptor) (Signature, error) { + pubkey := signDesc.KeyDesc.PubKey switch { case signDesc.SingleTweak != nil: @@ -72,7 +74,7 @@ func (m *MockSigner) SignOutputRaw(tx *wire.MsgTx, signDesc *SignDescriptor) ([] return nil, err } - return sig[:len(sig)-1], nil + return btcec.ParseDERSignature(sig[:len(sig)-1], btcec.S256()) } // ComputeInputScript generates a complete InputIndex for the passed transaction diff --git a/lnwallet/btcwallet/signer.go b/lnwallet/btcwallet/signer.go index 35e158c0..a79a5fd9 100644 --- a/lnwallet/btcwallet/signer.go +++ b/lnwallet/btcwallet/signer.go @@ -225,7 +225,7 @@ func maybeTweakPrivKey(signDesc *input.SignDescriptor, // // This is a part of the WalletController interface. func (b *BtcWallet) SignOutputRaw(tx *wire.MsgTx, - signDesc *input.SignDescriptor) ([]byte, error) { + signDesc *input.SignDescriptor) (input.Signature, error) { witnessScript := signDesc.WitnessScript @@ -256,7 +256,7 @@ func (b *BtcWallet) SignOutputRaw(tx *wire.MsgTx, } // Chop off the sighash flag at the end of the signature. - return sig[:len(sig)-1], nil + return btcec.ParseDERSignature(sig[:len(sig)-1], btcec.S256()) } // ComputeInputScript generates a complete InputScript for the passed @@ -358,7 +358,7 @@ var _ input.Signer = (*BtcWallet)(nil) // // NOTE: This is a part of the MessageSigner interface. func (b *BtcWallet) SignMessage(pubKey *btcec.PublicKey, - msg []byte) (*btcec.Signature, error) { + msg []byte) (input.Signature, error) { // First attempt to fetch the private key which corresponds to the // specified public key. diff --git a/lnwallet/channel.go b/lnwallet/channel.go index 11eb1159..33380deb 100644 --- a/lnwallet/channel.go +++ b/lnwallet/channel.go @@ -3402,7 +3402,7 @@ func (lc *LightningChannel) SignNextCommitment() (lnwire.Sig, []lnwire.Sig, []ch close(cancelChan) return sig, htlcSigs, nil, err } - sig, err = lnwire.NewSigFromRawSignature(rawSig) + sig, err = lnwire.NewSigFromSignature(rawSig) if err != nil { close(cancelChan) return sig, htlcSigs, nil, err @@ -5075,7 +5075,7 @@ func (lc *LightningChannel) getSignedCommitTx() (*wire.MsgTx, error) { return nil, err } - ourSig := append(ourSigRaw, byte(txscript.SigHashAll)) + ourSig := append(ourSigRaw.Serialize(), byte(txscript.SigHashAll)) // With the final signature generated, create the witness stack // required to spend from the multi-sig output. @@ -5950,7 +5950,8 @@ func NewLocalForceCloseSummary(chanState *channeldb.OpenChannel, signer input.Si // settle any in flight. func (lc *LightningChannel) CreateCloseProposal(proposedFee btcutil.Amount, localDeliveryScript []byte, - remoteDeliveryScript []byte) ([]byte, *chainhash.Hash, btcutil.Amount, error) { + remoteDeliveryScript []byte) (input.Signature, *chainhash.Hash, + btcutil.Amount, error) { lc.Lock() defer lc.Unlock() diff --git a/lnwallet/channel_test.go b/lnwallet/channel_test.go index c266db1c..8a7711fe 100644 --- a/lnwallet/channel_test.go +++ b/lnwallet/channel_test.go @@ -616,7 +616,7 @@ func TestCooperativeChannelClosure(t *testing.T) { if err != nil { t.Fatalf("unable to create alice coop close proposal: %v", err) } - aliceCloseSig := append(aliceSig, byte(txscript.SigHashAll)) + aliceCloseSig := append(aliceSig.Serialize(), byte(txscript.SigHashAll)) bobFee := bobChannel.CalcFee(bobFeeRate) bobSig, _, _, err := bobChannel.CreateCloseProposal( @@ -625,7 +625,7 @@ func TestCooperativeChannelClosure(t *testing.T) { if err != nil { t.Fatalf("unable to create bob coop close proposal: %v", err) } - bobCloseSig := append(bobSig, byte(txscript.SigHashAll)) + bobCloseSig := append(bobSig.Serialize(), byte(txscript.SigHashAll)) // With the proposals created, both sides should be able to properly // process the other party's signature. This indicates that the @@ -2059,7 +2059,7 @@ func TestCooperativeCloseDustAdherence(t *testing.T) { if err != nil { t.Fatalf("unable to close channel: %v", err) } - aliceCloseSig := append(aliceSig, byte(txscript.SigHashAll)) + aliceCloseSig := append(aliceSig.Serialize(), byte(txscript.SigHashAll)) bobFee := btcutil.Amount(bobChannel.CalcFee(bobFeeRate)) + 1000 bobSig, _, _, err := bobChannel.CreateCloseProposal(bobFee, @@ -2067,7 +2067,7 @@ func TestCooperativeCloseDustAdherence(t *testing.T) { if err != nil { t.Fatalf("unable to close channel: %v", err) } - bobCloseSig := append(bobSig, byte(txscript.SigHashAll)) + bobCloseSig := append(bobSig.Serialize(), byte(txscript.SigHashAll)) closeTx, _, err := bobChannel.CompleteCooperativeClose( bobCloseSig, aliceCloseSig, @@ -2098,14 +2098,14 @@ func TestCooperativeCloseDustAdherence(t *testing.T) { if err != nil { t.Fatalf("unable to close channel: %v", err) } - aliceCloseSig = append(aliceSig, byte(txscript.SigHashAll)) + aliceCloseSig = append(aliceSig.Serialize(), byte(txscript.SigHashAll)) bobSig, _, _, err = bobChannel.CreateCloseProposal(bobFee, bobDeliveryScript, aliceDeliveryScript) if err != nil { t.Fatalf("unable to close channel: %v", err) } - bobCloseSig = append(bobSig, byte(txscript.SigHashAll)) + bobCloseSig = append(bobSig.Serialize(), byte(txscript.SigHashAll)) closeTx, _, err = bobChannel.CompleteCooperativeClose( bobCloseSig, aliceCloseSig, @@ -2141,7 +2141,7 @@ func TestCooperativeCloseDustAdherence(t *testing.T) { if err != nil { t.Fatalf("unable to close channel: %v", err) } - aliceCloseSig = append(aliceSig, byte(txscript.SigHashAll)) + aliceCloseSig = append(aliceSig.Serialize(), byte(txscript.SigHashAll)) bobSig, _, _, err = bobChannel.CreateCloseProposal( bobFee, bobDeliveryScript, aliceDeliveryScript, @@ -2149,7 +2149,7 @@ func TestCooperativeCloseDustAdherence(t *testing.T) { if err != nil { t.Fatalf("unable to close channel: %v", err) } - bobCloseSig = append(bobSig, byte(txscript.SigHashAll)) + bobCloseSig = append(bobSig.Serialize(), byte(txscript.SigHashAll)) closeTx, _, err = bobChannel.CompleteCooperativeClose( bobCloseSig, aliceCloseSig, diff --git a/lnwallet/interface.go b/lnwallet/interface.go index 1c6c2bd7..41052503 100644 --- a/lnwallet/interface.go +++ b/lnwallet/interface.go @@ -10,6 +10,7 @@ import ( "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcutil" "github.com/btcsuite/btcwallet/wallet/txauthor" + "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/lnwallet/chainfee" ) @@ -292,7 +293,7 @@ type MessageSigner interface { // that corresponds to the passed public key. If the target private key // is unable to be found, then an error will be returned. The actual // digest signed is the double SHA-256 of the passed message. - SignMessage(pubKey *btcec.PublicKey, msg []byte) (*btcec.Signature, error) + SignMessage(pubKey *btcec.PublicKey, msg []byte) (input.Signature, error) } // WalletDriver represents a "driver" for a particular concrete diff --git a/lnwallet/interface_test.go b/lnwallet/interface_test.go index 1b6a1f28..16fc7ce4 100644 --- a/lnwallet/interface_test.go +++ b/lnwallet/interface_test.go @@ -1591,7 +1591,7 @@ func txFromOutput(tx *wire.MsgTx, signer input.Signer, fromPubKey, return nil, fmt.Errorf("unable to generate signature: %v", err) } witness := make([][]byte, 2) - witness[0] = append(spendSig, byte(txscript.SigHashAll)) + witness[0] = append(spendSig.Serialize(), byte(txscript.SigHashAll)) witness[1] = fromPubKey.SerializeCompressed() tx1.TxIn[0].Witness = witness @@ -1976,7 +1976,7 @@ func testSignOutputUsingTweaks(r *rpctest.Harness, t.Fatalf("unable to generate signature: %v", err) } witness := make([][]byte, 2) - witness[0] = append(spendSig, byte(txscript.SigHashAll)) + witness[0] = append(spendSig.Serialize(), byte(txscript.SigHashAll)) witness[1] = tweakedKey.SerializeCompressed() sweepTx.TxIn[0].Witness = witness diff --git a/lnwallet/reservation.go b/lnwallet/reservation.go index 19251351..22ef08b3 100644 --- a/lnwallet/reservation.go +++ b/lnwallet/reservation.go @@ -134,8 +134,8 @@ type ChannelReservation struct { theirFundingInputScripts []*input.Script // Our signature for their version of the commitment transaction. - ourCommitmentSig []byte - theirCommitmentSig []byte + ourCommitmentSig input.Signature + theirCommitmentSig input.Signature ourContribution *ChannelContribution theirContribution *ChannelContribution @@ -538,7 +538,9 @@ func (r *ChannelReservation) TheirContribution() *ChannelContribution { // // NOTE: These signatures will only be populated after a call to // .ProcessContribution() -func (r *ChannelReservation) OurSignatures() ([]*input.Script, []byte) { +func (r *ChannelReservation) OurSignatures() ([]*input.Script, + input.Signature) { + r.RLock() defer r.RUnlock() return r.ourFundingInputScripts, r.ourCommitmentSig @@ -558,7 +560,7 @@ func (r *ChannelReservation) OurSignatures() ([]*input.Script, []byte) { // confirmations. Once the method unblocks, a LightningChannel instance is // returned, marking the channel available for updates. func (r *ChannelReservation) CompleteReservation(fundingInputScripts []*input.Script, - commitmentSig []byte) (*channeldb.OpenChannel, error) { + commitmentSig input.Signature) (*channeldb.OpenChannel, error) { // TODO(roasbeef): add flag for watch or not? errChan := make(chan error, 1) @@ -585,7 +587,7 @@ func (r *ChannelReservation) CompleteReservation(fundingInputScripts []*input.Sc // called as a response to a single funder channel, only a commitment signature // will be populated. func (r *ChannelReservation) CompleteReservationSingle(fundingPoint *wire.OutPoint, - commitSig []byte) (*channeldb.OpenChannel, error) { + commitSig input.Signature) (*channeldb.OpenChannel, error) { errChan := make(chan error, 1) completeChan := make(chan *channeldb.OpenChannel, 1) @@ -608,7 +610,9 @@ func (r *ChannelReservation) CompleteReservationSingle(fundingPoint *wire.OutPoi // // NOTE: These attributes will be unpopulated before a call to // .CompleteReservation(). -func (r *ChannelReservation) TheirSignatures() ([]*input.Script, []byte) { +func (r *ChannelReservation) TheirSignatures() ([]*input.Script, + input.Signature) { + r.RLock() defer r.RUnlock() return r.theirFundingInputScripts, r.theirCommitmentSig diff --git a/lnwallet/sigpool.go b/lnwallet/sigpool.go index 57b84988..ec1d4f34 100644 --- a/lnwallet/sigpool.go +++ b/lnwallet/sigpool.go @@ -205,7 +205,7 @@ func (s *SigPool) poolWorker() { } } - sig, err := lnwire.NewSigFromRawSignature(rawSig) + sig, err := lnwire.NewSigFromSignature(rawSig) select { case sigMsg.Resp <- SignJobResp{ Sig: sig, diff --git a/lnwallet/wallet.go b/lnwallet/wallet.go index f7d66b6b..3349d29e 100644 --- a/lnwallet/wallet.go +++ b/lnwallet/wallet.go @@ -215,7 +215,7 @@ type addCounterPartySigsMsg struct { // This should be 1/2 of the signatures needed to successfully spend our // version of the commitment transaction. - theirCommitmentSig []byte + theirCommitmentSig input.Signature // This channel is used to return the completed channel after the wallet // has completed all of its stages in the funding process. @@ -240,7 +240,7 @@ type addSingleFunderSigsMsg struct { // theirCommitmentSig are the 1/2 of the signatures needed to // successfully spend our version of the commitment transaction. - theirCommitmentSig []byte + theirCommitmentSig input.Signature // This channel is used to return the completed channel after the wallet // has completed all of its stages in the funding process. @@ -1406,18 +1406,13 @@ func (l *LightningWallet) handleFundingCounterPartySigs(msg *addCounterPartySigs // Verify that we've received a valid signature from the remote party // for our version of the commitment transaction. - theirCommitSig := msg.theirCommitmentSig - sig, err := btcec.ParseSignature(theirCommitSig, btcec.S256()) - if err != nil { - msg.err <- err - msg.completeChan <- nil - return - } else if !sig.Verify(sigHash, theirKey.PubKey) { + if !msg.theirCommitmentSig.Verify(sigHash, theirKey.PubKey) { msg.err <- fmt.Errorf("counterparty's commitment signature is invalid") msg.completeChan <- nil return } - res.partialState.LocalCommitment.CommitSig = theirCommitSig + theirCommitSigBytes := msg.theirCommitmentSig.Serialize() + res.partialState.LocalCommitment.CommitSig = theirCommitSigBytes // Funding complete, this entry can be removed from limbo. l.limboMtx.Lock() @@ -1566,19 +1561,14 @@ func (l *LightningWallet) handleSingleFunderSigs(req *addSingleFunderSigsMsg) { // Verify that we've received a valid signature from the remote party // for our version of the commitment transaction. - sig, err := btcec.ParseSignature(req.theirCommitmentSig, btcec.S256()) - if err != nil { - req.err <- err - req.completeChan <- nil - return - } - if !sig.Verify(sigHash, theirKey.PubKey) { + if !req.theirCommitmentSig.Verify(sigHash, theirKey.PubKey) { req.err <- fmt.Errorf("counterparty's commitment signature " + "is invalid") req.completeChan <- nil return } - chanState.LocalCommitment.CommitSig = req.theirCommitmentSig + theirCommitSigBytes := req.theirCommitmentSig.Serialize() + chanState.LocalCommitment.CommitSig = theirCommitSigBytes // With their signature for our version of the commitment transactions // verified, we can now generate a signature for their version, diff --git a/lnwire/signature.go b/lnwire/signature.go index 012d5a91..13a2f25c 100644 --- a/lnwire/signature.go +++ b/lnwire/signature.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/btcsuite/btcd/btcec" + "github.com/lightningnetwork/lnd/input" ) // Sig is a fixed-sized ECDSA signature. Unlike Bitcoin, we use fixed sized @@ -64,7 +65,7 @@ func NewSigFromRawSignature(sig []byte) (Sig, error) { // NewSigFromSignature creates a new signature as used on the wire, from an // existing btcec.Signature. -func NewSigFromSignature(e *btcec.Signature) (Sig, error) { +func NewSigFromSignature(e input.Signature) (Sig, error) { if e == nil { return Sig{}, fmt.Errorf("cannot decode empty signature") } diff --git a/mock.go b/mock.go index bf9559d7..0b71a6dc 100644 --- a/mock.go +++ b/mock.go @@ -33,7 +33,7 @@ type mockSigner struct { } func (m *mockSigner) SignOutputRaw(tx *wire.MsgTx, - signDesc *input.SignDescriptor) ([]byte, error) { + signDesc *input.SignDescriptor) (input.Signature, error) { amt := signDesc.Output.Value witnessScript := signDesc.WitnessScript privKey := m.key @@ -58,7 +58,7 @@ func (m *mockSigner) SignOutputRaw(tx *wire.MsgTx, return nil, err } - return sig[:len(sig)-1], nil + return btcec.ParseDERSignature(sig[:len(sig)-1], btcec.S256()) } func (m *mockSigner) ComputeInputScript(tx *wire.MsgTx, diff --git a/netann/channel_update_test.go b/netann/channel_update_test.go index 0bc48e5c..1fff1d51 100644 --- a/netann/channel_update_test.go +++ b/netann/channel_update_test.go @@ -6,6 +6,7 @@ import ( "time" "github.com/btcsuite/btcd/btcec" + "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/netann" @@ -17,7 +18,7 @@ type mockSigner struct { } func (m *mockSigner) SignMessage(pk *btcec.PublicKey, - msg []byte) (*btcec.Signature, error) { + msg []byte) (input.Signature, error) { if m.err != nil { return nil, m.err diff --git a/netann/node_signer.go b/netann/node_signer.go index 8946c2c6..2b97c937 100644 --- a/netann/node_signer.go +++ b/netann/node_signer.go @@ -5,6 +5,7 @@ import ( "github.com/btcsuite/btcd/btcec" "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/lnwallet" ) @@ -31,7 +32,7 @@ func NewNodeSigner(key *btcec.PrivateKey) *NodeSigner { // resident node's private key. If the target public key is _not_ the node's // private key, then an error will be returned. func (n *NodeSigner) SignMessage(pubKey *btcec.PublicKey, - msg []byte) (*btcec.Signature, error) { + msg []byte) (input.Signature, error) { // If this isn't our identity public key, then we'll exit early with an // error as we can't sign with this key. @@ -41,12 +42,12 @@ func (n *NodeSigner) SignMessage(pubKey *btcec.PublicKey, // Otherwise, we'll sign the dsha256 of the target message. digest := chainhash.DoubleHashB(msg) - sign, err := n.privKey.Sign(digest) + sig, err := n.privKey.Sign(digest) if err != nil { return nil, fmt.Errorf("can't sign the message: %v", err) } - return sign, nil + return sig, nil } // SignCompact signs a double-sha256 digest of the msg parameter under the diff --git a/netann/sign.go b/netann/sign.go index 5a669620..6e0bce98 100644 --- a/netann/sign.go +++ b/netann/sign.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/btcsuite/btcd/btcec" + "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwire" ) @@ -11,7 +12,7 @@ import ( // SignAnnouncement signs any type of gossip message that is announced on the // network. func SignAnnouncement(signer lnwallet.MessageSigner, pubKey *btcec.PublicKey, - msg lnwire.Message) (*btcec.Signature, error) { + msg lnwire.Message) (input.Signature, error) { var ( data []byte diff --git a/peer_test.go b/peer_test.go index 14cb1979..4669168b 100644 --- a/peer_test.go +++ b/peer_test.go @@ -96,7 +96,7 @@ func TestPeerChannelClosureAcceptFeeResponder(t *testing.T) { t.Fatalf("error creating close proposal: %v", err) } - parsedSig, err := lnwire.NewSigFromRawSignature(initiatorSig) + parsedSig, err := lnwire.NewSigFromSignature(initiatorSig) if err != nil { t.Fatalf("error parsing signature: %v", err) } @@ -184,7 +184,7 @@ func TestPeerChannelClosureAcceptFeeInitiator(t *testing.T) { if err != nil { t.Fatalf("unable to create close proposal: %v", err) } - parsedSig, err := lnwire.NewSigFromRawSignature(closeSig) + parsedSig, err := lnwire.NewSigFromSignature(closeSig) if err != nil { t.Fatalf("unable to parse signature: %v", err) } @@ -296,7 +296,7 @@ func TestPeerChannelClosureFeeNegotiationsResponder(t *testing.T) { t.Fatalf("error creating close proposal: %v", err) } - parsedSig, err := lnwire.NewSigFromRawSignature(initiatorSig) + parsedSig, err := lnwire.NewSigFromSignature(initiatorSig) if err != nil { t.Fatalf("error parsing signature: %v", err) } @@ -340,7 +340,7 @@ func TestPeerChannelClosureFeeNegotiationsResponder(t *testing.T) { t.Fatalf("error creating close proposal: %v", err) } - parsedSig, err = lnwire.NewSigFromRawSignature(initiatorSig) + parsedSig, err = lnwire.NewSigFromSignature(initiatorSig) if err != nil { t.Fatalf("error parsing signature: %v", err) } @@ -385,7 +385,7 @@ func TestPeerChannelClosureFeeNegotiationsResponder(t *testing.T) { t.Fatalf("error creating close proposal: %v", err) } - parsedSig, err = lnwire.NewSigFromRawSignature(initiatorSig) + parsedSig, err = lnwire.NewSigFromSignature(initiatorSig) if err != nil { t.Fatalf("error parsing signature: %v", err) } @@ -477,7 +477,7 @@ func TestPeerChannelClosureFeeNegotiationsInitiator(t *testing.T) { if err != nil { t.Fatalf("unable to create close proposal: %v", err) } - parsedSig, err := lnwire.NewSigFromRawSignature(closeSig) + parsedSig, err := lnwire.NewSigFromSignature(closeSig) if err != nil { t.Fatalf("unable to parse signature: %v", err) } @@ -543,7 +543,7 @@ func TestPeerChannelClosureFeeNegotiationsInitiator(t *testing.T) { t.Fatalf("error creating close proposal: %v", err) } - parsedSig, err = lnwire.NewSigFromRawSignature(responderSig) + parsedSig, err = lnwire.NewSigFromSignature(responderSig) if err != nil { t.Fatalf("error parsing signature: %v", err) } @@ -589,7 +589,7 @@ func TestPeerChannelClosureFeeNegotiationsInitiator(t *testing.T) { t.Fatalf("error creating close proposal: %v", err) } - parsedSig, err = lnwire.NewSigFromRawSignature(responderSig) + parsedSig, err = lnwire.NewSigFromSignature(responderSig) if err != nil { t.Fatalf("error parsing signature: %v", err) } diff --git a/server.go b/server.go index a1dce6ec..fba2dd6f 100644 --- a/server.go +++ b/server.go @@ -981,7 +981,7 @@ func newServer(listenAddrs []net.Addr, chanDB *channeldb.DB, Notifier: cc.chainNotifier, FeeEstimator: cc.feeEstimator, SignMessage: func(pubKey *btcec.PublicKey, - msg []byte) (*btcec.Signature, error) { + msg []byte) (input.Signature, error) { if pubKey.IsEqual(privKey.PubKey()) { return s.nodeSigner.SignMessage(pubKey, msg) diff --git a/sweep/test_utils.go b/sweep/test_utils.go index 6b9b0d72..7c28710b 100644 --- a/sweep/test_utils.go +++ b/sweep/test_utils.go @@ -6,6 +6,7 @@ import ( "testing" "time" + "github.com/btcsuite/btcd/btcec" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/chainntnfs" @@ -18,13 +19,23 @@ var ( mockChainHeight = int32(100) ) +type dummySignature struct{} + +func (s *dummySignature) Serialize() []byte { + return []byte{} +} + +func (s *dummySignature) Verify(_ []byte, _ *btcec.PublicKey) bool { + return true +} + type mockSigner struct { } func (m *mockSigner) SignOutputRaw(tx *wire.MsgTx, - signDesc *input.SignDescriptor) ([]byte, error) { + signDesc *input.SignDescriptor) (input.Signature, error) { - return []byte{}, nil + return &dummySignature{}, nil } func (m *mockSigner) ComputeInputScript(tx *wire.MsgTx, diff --git a/watchtower/lookout/justice_descriptor_test.go b/watchtower/lookout/justice_descriptor_test.go index 7fd93c8c..6785f1b6 100644 --- a/watchtower/lookout/justice_descriptor_test.go +++ b/watchtower/lookout/justice_descriptor_test.go @@ -262,7 +262,7 @@ func testJusticeDescriptor(t *testing.T, blobType blob.Type) { toRemoteSigRaw := toRemoteWitness[0][:len(toRemoteWitness[0])-1] // Convert the DER to-local sig into a fixed-size signature. - toLocalSig, err := lnwire.NewSigFromRawSignature(toLocalSigRaw) + toLocalSig, err := lnwire.NewSigFromSignature(toLocalSigRaw) if err != nil { t.Fatalf("unable to parse to-local signature: %v", err) } @@ -310,7 +310,7 @@ func testJusticeDescriptor(t *testing.T, blobType blob.Type) { // Construct the test's to-local witness. justiceTxn.TxIn[0].Witness = make([][]byte, 3) - justiceTxn.TxIn[0].Witness[0] = append(toLocalSigRaw, + justiceTxn.TxIn[0].Witness[0] = append(toLocalSigRaw.Serialize(), byte(txscript.SigHashAll)) justiceTxn.TxIn[0].Witness[1] = []byte{1} justiceTxn.TxIn[0].Witness[2] = toLocalScript diff --git a/watchtower/wtmock/signer.go b/watchtower/wtmock/signer.go index c41e4f2f..89421d6a 100644 --- a/watchtower/wtmock/signer.go +++ b/watchtower/wtmock/signer.go @@ -30,7 +30,7 @@ func NewMockSigner() *MockSigner { // in the sign descriptor. The returned signature is the raw DER-encoded // signature without the signhash flag. func (s *MockSigner) SignOutputRaw(tx *wire.MsgTx, - signDesc *input.SignDescriptor) ([]byte, error) { + signDesc *input.SignDescriptor) (input.Signature, error) { s.mu.Lock() defer s.mu.Unlock() @@ -50,7 +50,7 @@ func (s *MockSigner) SignOutputRaw(tx *wire.MsgTx, return nil, err } - return sig[:len(sig)-1], nil + return btcec.ParseDERSignature(sig[:len(sig)-1], btcec.S256()) } // ComputeInputScript is not implemented. From f2b6e2af04724936088303d3ba3daa1edf658230 Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Sun, 5 Apr 2020 17:07:01 -0700 Subject: [PATCH 4/5] input: pass input.Signature to multisig spend Modifies SpendMultiSig to accept input.Signature, so that we can ultimately assert the size of multisig witnesses. --- chancloser.go | 13 ++++--- input/script_utils.go | 12 +++--- lnwallet/channel.go | 25 ++++++++----- lnwallet/channel_test.go | 51 +++++++++++++------------- lnwallet/chanvalidate/validate_test.go | 18 ++++++++- lnwallet/test_utils.go | 18 +++++++-- 6 files changed, 87 insertions(+), 50 deletions(-) diff --git a/chancloser.go b/chancloser.go index befb6ccd..b261a365 100644 --- a/chancloser.go +++ b/chancloser.go @@ -4,7 +4,6 @@ import ( "bytes" "fmt" - "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcutil" "github.com/davecgh/go-spew/spew" @@ -510,11 +509,15 @@ func (c *channelCloser) ProcessCloseMsg(msg lnwire.Message) ([]lnwire.Message, b // transaction! We'll craft the final closing transaction so // we can broadcast it to the network. matchingSig := c.priorFeeOffers[remoteProposedFee].Signature - localSigBytes := matchingSig.ToSignatureBytes() - localSig := append(localSigBytes, byte(txscript.SigHashAll)) + localSig, err := matchingSig.ToSignature() + if err != nil { + return nil, false, err + } - remoteSigBytes := closeSignedMsg.Signature.ToSignatureBytes() - remoteSig := append(remoteSigBytes, byte(txscript.SigHashAll)) + remoteSig, err := closeSignedMsg.Signature.ToSignature() + if err != nil { + return nil, false, err + } closeTx, _, err := c.cfg.channel.CompleteCooperativeClose( localSig, remoteSig, c.localDeliveryScript, diff --git a/input/script_utils.go b/input/script_utils.go index 97938ce8..318d1fae 100644 --- a/input/script_utils.go +++ b/input/script_utils.go @@ -96,7 +96,9 @@ func GenFundingPkScript(aPub, bPub []byte, amt int64) ([]byte, *wire.TxOut, erro // SpendMultiSig generates the witness stack required to redeem the 2-of-2 p2wsh // multi-sig output. -func SpendMultiSig(witnessScript, pubA, sigA, pubB, sigB []byte) [][]byte { +func SpendMultiSig(witnessScript, pubA []byte, sigA Signature, + pubB []byte, sigB Signature) [][]byte { + witness := make([][]byte, 4) // When spending a p2wsh multi-sig script, rather than an OP_0, we add @@ -108,11 +110,11 @@ func SpendMultiSig(witnessScript, pubA, sigA, pubB, sigB []byte) [][]byte { // ensure the signatures appear on the Script Virtual Machine stack in // the correct order. if bytes.Compare(pubA, pubB) == 1 { - witness[1] = sigB - witness[2] = sigA + witness[1] = append(sigB.Serialize(), byte(txscript.SigHashAll)) + witness[2] = append(sigA.Serialize(), byte(txscript.SigHashAll)) } else { - witness[1] = sigA - witness[2] = sigB + witness[1] = append(sigA.Serialize(), byte(txscript.SigHashAll)) + witness[2] = append(sigB.Serialize(), byte(txscript.SigHashAll)) } // Finally, add the preimage as the last witness element. diff --git a/lnwallet/channel.go b/lnwallet/channel.go index 33380deb..5f6eef92 100644 --- a/lnwallet/channel.go +++ b/lnwallet/channel.go @@ -5065,17 +5065,21 @@ func (lc *LightningChannel) getSignedCommitTx() (*wire.MsgTx, error) { // for the transaction. localCommit := lc.channelState.LocalCommitment commitTx := localCommit.CommitTx.Copy() - theirSig := append(localCommit.CommitSig, byte(txscript.SigHashAll)) - // With this, we then generate the full witness so the caller can - // broadcast a fully signed transaction. - lc.signDesc.SigHashes = txscript.NewTxSigHashes(commitTx) - ourSigRaw, err := lc.Signer.SignOutputRaw(commitTx, lc.signDesc) + theirSig, err := btcec.ParseDERSignature( + localCommit.CommitSig, btcec.S256(), + ) if err != nil { return nil, err } - ourSig := append(ourSigRaw.Serialize(), byte(txscript.SigHashAll)) + // With this, we then generate the full witness so the caller can + // broadcast a fully signed transaction. + lc.signDesc.SigHashes = txscript.NewTxSigHashes(commitTx) + ourSig, err := lc.Signer.SignOutputRaw(commitTx, lc.signDesc) + if err != nil { + return nil, err + } // With the final signature generated, create the witness stack // required to spend from the multi-sig output. @@ -6017,7 +6021,8 @@ func (lc *LightningChannel) CreateCloseProposal(proposedFee btcutil.Amount, // // NOTE: The passed local and remote sigs are expected to be fully complete // signatures including the proper sighash byte. -func (lc *LightningChannel) CompleteCooperativeClose(localSig, remoteSig []byte, +func (lc *LightningChannel) CompleteCooperativeClose( + localSig, remoteSig input.Signature, localDeliveryScript, remoteDeliveryScript []byte, proposedFee btcutil.Amount) (*wire.MsgTx, btcutil.Amount, error) { @@ -6070,8 +6075,10 @@ func (lc *LightningChannel) CompleteCooperativeClose(localSig, remoteSig []byte, SerializeCompressed() theirKey := lc.channelState.RemoteChanCfg.MultiSigKey.PubKey. SerializeCompressed() - witness := input.SpendMultiSig(lc.signDesc.WitnessScript, ourKey, - localSig, theirKey, remoteSig) + witness := input.SpendMultiSig( + lc.signDesc.WitnessScript, ourKey, localSig, theirKey, + remoteSig, + ) closeTx.TxIn[0].Witness = witness // Validate the finalized transaction to ensure the output script is diff --git a/lnwallet/channel_test.go b/lnwallet/channel_test.go index 8a7711fe..318c0b96 100644 --- a/lnwallet/channel_test.go +++ b/lnwallet/channel_test.go @@ -616,7 +616,6 @@ func TestCooperativeChannelClosure(t *testing.T) { if err != nil { t.Fatalf("unable to create alice coop close proposal: %v", err) } - aliceCloseSig := append(aliceSig.Serialize(), byte(txscript.SigHashAll)) bobFee := bobChannel.CalcFee(bobFeeRate) bobSig, _, _, err := bobChannel.CreateCloseProposal( @@ -625,14 +624,13 @@ func TestCooperativeChannelClosure(t *testing.T) { if err != nil { t.Fatalf("unable to create bob coop close proposal: %v", err) } - bobCloseSig := append(bobSig.Serialize(), byte(txscript.SigHashAll)) // With the proposals created, both sides should be able to properly // process the other party's signature. This indicates that the // transaction is well formed, and the signatures verify. aliceCloseTx, _, err := bobChannel.CompleteCooperativeClose( - bobCloseSig, aliceCloseSig, bobDeliveryScript, - aliceDeliveryScript, bobFee, + bobSig, aliceSig, bobDeliveryScript, aliceDeliveryScript, + bobFee, ) if err != nil { t.Fatalf("unable to complete alice cooperative close: %v", err) @@ -640,8 +638,8 @@ func TestCooperativeChannelClosure(t *testing.T) { bobCloseSha := aliceCloseTx.TxHash() bobCloseTx, _, err := aliceChannel.CompleteCooperativeClose( - aliceCloseSig, bobCloseSig, aliceDeliveryScript, - bobDeliveryScript, aliceFee, + aliceSig, bobSig, aliceDeliveryScript, bobDeliveryScript, + aliceFee, ) if err != nil { t.Fatalf("unable to complete bob cooperative close: %v", err) @@ -2054,24 +2052,25 @@ func TestCooperativeCloseDustAdherence(t *testing.T) { // balances. As a result, performing a cooperative closure now result // in both sides having an output within the closure transaction. aliceFee := btcutil.Amount(aliceChannel.CalcFee(aliceFeeRate)) + 1000 - aliceSig, _, _, err := aliceChannel.CreateCloseProposal(aliceFee, - aliceDeliveryScript, bobDeliveryScript) + aliceSig, _, _, err := aliceChannel.CreateCloseProposal( + aliceFee, aliceDeliveryScript, bobDeliveryScript, + ) if err != nil { t.Fatalf("unable to close channel: %v", err) } - aliceCloseSig := append(aliceSig.Serialize(), byte(txscript.SigHashAll)) bobFee := btcutil.Amount(bobChannel.CalcFee(bobFeeRate)) + 1000 - bobSig, _, _, err := bobChannel.CreateCloseProposal(bobFee, - bobDeliveryScript, aliceDeliveryScript) + bobSig, _, _, err := bobChannel.CreateCloseProposal( + bobFee, bobDeliveryScript, aliceDeliveryScript, + ) if err != nil { t.Fatalf("unable to close channel: %v", err) } - bobCloseSig := append(bobSig.Serialize(), byte(txscript.SigHashAll)) closeTx, _, err := bobChannel.CompleteCooperativeClose( - bobCloseSig, aliceCloseSig, - bobDeliveryScript, aliceDeliveryScript, bobFee) + bobSig, aliceSig, bobDeliveryScript, aliceDeliveryScript, + bobFee, + ) if err != nil { t.Fatalf("unable to accept channel close: %v", err) } @@ -2093,23 +2092,24 @@ func TestCooperativeCloseDustAdherence(t *testing.T) { // Attempt another cooperative channel closure. It should succeed // without any issues. - aliceSig, _, _, err = aliceChannel.CreateCloseProposal(aliceFee, - aliceDeliveryScript, bobDeliveryScript) + aliceSig, _, _, err = aliceChannel.CreateCloseProposal( + aliceFee, aliceDeliveryScript, bobDeliveryScript, + ) if err != nil { t.Fatalf("unable to close channel: %v", err) } - aliceCloseSig = append(aliceSig.Serialize(), byte(txscript.SigHashAll)) - bobSig, _, _, err = bobChannel.CreateCloseProposal(bobFee, - bobDeliveryScript, aliceDeliveryScript) + bobSig, _, _, err = bobChannel.CreateCloseProposal( + bobFee, bobDeliveryScript, aliceDeliveryScript, + ) if err != nil { t.Fatalf("unable to close channel: %v", err) } - bobCloseSig = append(bobSig.Serialize(), byte(txscript.SigHashAll)) closeTx, _, err = bobChannel.CompleteCooperativeClose( - bobCloseSig, aliceCloseSig, - bobDeliveryScript, aliceDeliveryScript, bobFee) + bobSig, aliceSig, bobDeliveryScript, aliceDeliveryScript, + bobFee, + ) if err != nil { t.Fatalf("unable to accept channel close: %v", err) } @@ -2141,7 +2141,6 @@ func TestCooperativeCloseDustAdherence(t *testing.T) { if err != nil { t.Fatalf("unable to close channel: %v", err) } - aliceCloseSig = append(aliceSig.Serialize(), byte(txscript.SigHashAll)) bobSig, _, _, err = bobChannel.CreateCloseProposal( bobFee, bobDeliveryScript, aliceDeliveryScript, @@ -2149,11 +2148,11 @@ func TestCooperativeCloseDustAdherence(t *testing.T) { if err != nil { t.Fatalf("unable to close channel: %v", err) } - bobCloseSig = append(bobSig.Serialize(), byte(txscript.SigHashAll)) closeTx, _, err = bobChannel.CompleteCooperativeClose( - bobCloseSig, aliceCloseSig, - bobDeliveryScript, aliceDeliveryScript, bobFee) + bobSig, aliceSig, bobDeliveryScript, aliceDeliveryScript, + bobFee, + ) if err != nil { t.Fatalf("unable to accept channel close: %v", err) } diff --git a/lnwallet/chanvalidate/validate_test.go b/lnwallet/chanvalidate/validate_test.go index 12bb5c09..e979b014 100644 --- a/lnwallet/chanvalidate/validate_test.go +++ b/lnwallet/chanvalidate/validate_test.go @@ -98,7 +98,7 @@ func newChannelTestCtx(chanSize int64) (*channelTestCtx, error) { } sigHashes := txscript.NewTxSigHashes(commitTx) - aliceSig, err := txscript.RawTxInWitnessSignature( + aliceSigRaw, err := txscript.RawTxInWitnessSignature( commitTx, sigHashes, 0, chanSize, multiSigScript, txscript.SigHashAll, alicePriv, ) @@ -106,7 +106,14 @@ func newChannelTestCtx(chanSize int64) (*channelTestCtx, error) { return nil, err } - bobSig, err := txscript.RawTxInWitnessSignature( + aliceSig, err := btcec.ParseDERSignature( + aliceSigRaw, btcec.S256(), + ) + if err != nil { + return nil, err + } + + bobSigRaw, err := txscript.RawTxInWitnessSignature( commitTx, sigHashes, 0, chanSize, multiSigScript, txscript.SigHashAll, bobPriv, ) @@ -114,6 +121,13 @@ func newChannelTestCtx(chanSize int64) (*channelTestCtx, error) { return nil, err } + bobSig, err := btcec.ParseDERSignature( + bobSigRaw, btcec.S256(), + ) + if err != nil { + return nil, err + } + commitTx.TxIn[0].Witness = input.SpendMultiSig( multiSigScript, alicePub.SerializeCompressed(), aliceSig, bobPub.SerializeCompressed(), bobSig, diff --git a/lnwallet/test_utils.go b/lnwallet/test_utils.go index cbe1b2b3..55d8a342 100644 --- a/lnwallet/test_utils.go +++ b/lnwallet/test_utils.go @@ -1,7 +1,6 @@ package lnwallet import ( - "bytes" "crypto/rand" "encoding/binary" "encoding/hex" @@ -81,6 +80,19 @@ var ( }, LockTime: 5, } + + // A valid, DER-encoded signature (taken from btcec unit tests). + testSigBytes = []byte{ + 0x30, 0x44, 0x02, 0x20, 0x4e, 0x45, 0xe1, 0x69, + 0x32, 0xb8, 0xaf, 0x51, 0x49, 0x61, 0xa1, 0xd3, + 0xa1, 0xa2, 0x5f, 0xdf, 0x3f, 0x4f, 0x77, 0x32, + 0xe9, 0xd6, 0x24, 0xc6, 0xc6, 0x15, 0x48, 0xab, + 0x5f, 0xb8, 0xcd, 0x41, 0x02, 0x20, 0x18, 0x15, + 0x22, 0xec, 0x8e, 0xca, 0x07, 0xde, 0x48, 0x60, + 0xa4, 0xac, 0xdd, 0x12, 0x90, 0x9d, 0x83, 0x1c, + 0xc5, 0x6c, 0xbb, 0xac, 0x46, 0x22, 0x08, 0x22, + 0x21, 0xa8, 0x76, 0x8d, 0x1d, 0x09, + } ) // CreateTestChannels creates to fully populated channels to be used within @@ -257,7 +269,7 @@ func CreateTestChannels(chanType channeldb.ChannelType) ( CommitFee: commitFee, FeePerKw: btcutil.Amount(feePerKw), CommitTx: aliceCommitTx, - CommitSig: bytes.Repeat([]byte{1}, 71), + CommitSig: testSigBytes, } bobCommit := channeldb.ChannelCommitment{ CommitHeight: 0, @@ -266,7 +278,7 @@ func CreateTestChannels(chanType channeldb.ChannelType) ( CommitFee: commitFee, FeePerKw: btcutil.Amount(feePerKw), CommitTx: bobCommitTx, - CommitSig: bytes.Repeat([]byte{1}, 71), + CommitSig: testSigBytes, } var chanIDBytes [8]byte From c1b9b272cda02948a0941a04da1b58fd6d164397 Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Sun, 5 Apr 2020 17:07:14 -0700 Subject: [PATCH 5/5] input/size: assert witness size constants This commit introduces a new test case that asserts all of the witness size constants currently in the codebase. We also reintroduce the AcceptedHtlcSuccessWitnessSize and OfferedHtlcTimeoutWitnessSize constants that were recently removed for the sake of completeness. In asserting the witnes sizes, there were three uncovered discrepancies: * OfferedHtlcSuccessWitnessSize overestimated by about 30% because it included an extra signature in the calculation. * ToLocalPenaltyWitnessSize was underestimated by one byte, because it was missing the length byte for the OP_TRUE. This has implications the watchtower protocol since the client and server are assumed to share the same weight estimates used for signing. This commit keeps the current behavior, with the intention of rolling out negotiation for which weight estimate to use for a given session. * AcceptedHtlcScriptSize was underestimated by one byte because it was missing a length byte for the value 32 pushed on the stack when asserting the preimage's length. This affects all AcceptedHtlc* witness sizes. --- input/size.go | 113 ++-- input/size_test.go | 573 +++++++++++++++++- lnrpc/signrpc/signer_server.go | 2 +- sweep/txgenerator_test.go | 2 +- watchtower/lookout/justice_descriptor.go | 14 +- watchtower/lookout/justice_descriptor_test.go | 8 +- watchtower/wtclient/backup_task.go | 9 +- 7 files changed, 648 insertions(+), 73 deletions(-) diff --git a/input/size.go b/input/size.go index 74f1247d..6cebc282 100644 --- a/input/size.go +++ b/input/size.go @@ -1,12 +1,8 @@ package input import ( - "math/big" - "github.com/btcsuite/btcd/blockchain" - "github.com/btcsuite/btcd/btcec" "github.com/btcsuite/btcd/wire" - "github.com/lightningnetwork/lnd/keychain" ) const ( @@ -94,7 +90,7 @@ const ( // - OP_CHECKMULTISIG: 1 byte MultiSigSize = 1 + 1 + 33 + 1 + 33 + 1 + 1 - // WitnessSize 222 bytes + // MultiSigWitnessSize 222 bytes // - NumberOfWitnessElements: 1 byte // - NilLength: 1 byte // - sigAliceLength: 1 byte @@ -103,7 +99,7 @@ const ( // - sigBob: 73 bytes // - WitnessScriptLength: 1 byte // - WitnessScript (MultiSig) - WitnessSize = 1 + 1 + 1 + 73 + 1 + 73 + 1 + MultiSigSize + MultiSigWitnessSize = 1 + 1 + 1 + 73 + 1 + 73 + 1 + MultiSigSize // InputSize 41 bytes // - PreviousOutPoint: @@ -177,7 +173,7 @@ const ( BaseCommitmentTxWeight = witnessScaleFactor * BaseCommitmentTxSize // WitnessCommitmentTxWeight 224 weight - WitnessCommitmentTxWeight = WitnessHeaderSize + WitnessSize + WitnessCommitmentTxWeight = WitnessHeaderSize + MultiSigWitnessSize // BaseAnchorCommitmentTxSize 225 + 43 * num-htlc-outputs bytes // - Version: 4 bytes @@ -264,14 +260,15 @@ const ( // - witness_script (to_local_script) ToLocalTimeoutWitnessSize = 1 + 1 + 73 + 1 + 1 + ToLocalScriptSize - // ToLocalPenaltyWitnessSize 156 bytes + // ToLocalPenaltyWitnessSize 157 bytes // - number_of_witness_elements: 1 byte // - revocation_sig_length: 1 byte // - revocation_sig: 73 bytes + // - OP_TRUE_length: 1 byte // - OP_TRUE: 1 byte // - witness_script_length: 1 byte // - witness_script (to_local_script) - ToLocalPenaltyWitnessSize = 1 + 1 + 73 + 1 + 1 + ToLocalScriptSize + ToLocalPenaltyWitnessSize = 1 + 1 + 73 + 1 + 1 + 1 + ToLocalScriptSize // ToRemoteConfirmedScriptSize 37 bytes // - OP_DATA: 1 byte @@ -289,7 +286,7 @@ const ( // - witness_script (to_remote_delayed_script) ToRemoteConfirmedWitnessSize = 1 + 1 + 73 + 1 + ToRemoteConfirmedScriptSize - // AcceptedHtlcScriptSize 142 bytes + // AcceptedHtlcScriptSize 143 bytes // - OP_DUP: 1 byte // - OP_HASH160: 1 byte // - OP_DATA: 1 byte (RIPEMD160(SHA256(revocationkey)) length) @@ -302,6 +299,7 @@ const ( // - remotekey: 33 bytes // - OP_SWAP: 1 byte // - OP_SIZE: 1 byte + // - OP_DATA: 1 byte (32 length) // - 32: 1 byte // - OP_EQUAL: 1 byte // - OP_IF: 1 byte @@ -327,7 +325,7 @@ const ( // - OP_CSV: 1 byte // HTLC script types. The size won't be correct in all cases, // - OP_DROP: 1 byte // but it is just an upper bound used for fee estimation in any case. // - OP_ENDIF: 1 byte - AcceptedHtlcScriptSize = 3*1 + 20 + 5*1 + 33 + 7*1 + 20 + 4*1 + + AcceptedHtlcScriptSize = 3*1 + 20 + 5*1 + 33 + 8*1 + 20 + 4*1 + 33 + 5*1 + 4 + 8*1 // AcceptedHtlcTimeoutWitnessSize 219 @@ -349,6 +347,20 @@ const ( // - witness_script (accepted_htlc_script) AcceptedHtlcPenaltyWitnessSize = 1 + 1 + 73 + 1 + 33 + 1 + AcceptedHtlcScriptSize + // AcceptedHtlcSuccessWitnessSize 322 bytes + // - number_of_witness_elements: 1 byte + // - nil_length: 1 byte + // - sig_alice_length: 1 byte + // - sig_alice: 73 bytes + // - sig_bob_length: 1 byte + // - sig_bob: 73 bytes + // - preimage_length: 1 byte + // - preimage: 32 bytes + // - witness_script_length: 1 byte + // - witness_script (accepted_htlc_script) + AcceptedHtlcSuccessWitnessSize = 1 + 1 + 1 + 73 + 1 + 73 + 1 + 32 + 1 + + AcceptedHtlcScriptSize + // OfferedHtlcScriptSize 136 bytes // - OP_DUP: 1 byte // - OP_HASH160: 1 byte @@ -386,18 +398,27 @@ const ( // - OP_ENDIF: 1 byte OfferedHtlcScriptSize = 3*1 + 20 + 5*1 + 33 + 10*1 + 33 + 5*1 + 20 + 7*1 - // OfferedHtlcSuccessWitnessSize 320 bytes + // OfferedHtlcSuccessWitnessSize 245 bytes // - number_of_witness_elements: 1 byte - // - nil_length: 1 byte // - receiver_sig_length: 1 byte // - receiver_sig: 73 bytes - // - sender_sig_length: 1 byte - // - sender_sig: 73 bytes // - payment_preimage_length: 1 byte // - payment_preimage: 32 bytes // - witness_script_length: 1 byte // - witness_script (offered_htlc_script) - OfferedHtlcSuccessWitnessSize = 1 + 1 + 1 + 73 + 1 + 73 + 1 + 32 + 1 + OfferedHtlcScriptSize + OfferedHtlcSuccessWitnessSize = 1 + 1 + 73 + 1 + 32 + 1 + OfferedHtlcScriptSize + + // OfferedHtlcTimeoutWitnessSize 285 bytes + // - number_of_witness_elements: 1 byte + // - nil_length: 1 byte + // - sig_alice_length: 1 byte + // - sig_alice: 73 bytes + // - sig_bob_length: 1 byte + // - sig_bob: 73 bytes + // - nil_length: 1 byte + // - witness_script_length: 1 byte + // - witness_script (offered_htlc_script) + OfferedHtlcTimeoutWitnessSize = 1 + 1 + 1 + 73 + 1 + 73 + 1 + 1 + OfferedHtlcScriptSize // OfferedHtlcPenaltyWitnessSize 246 bytes // - number_of_witness_elements: 1 byte @@ -408,53 +429,25 @@ const ( // - witness_script_length: 1 byte // - witness_script (offered_htlc_script) OfferedHtlcPenaltyWitnessSize = 1 + 1 + 73 + 1 + 33 + 1 + OfferedHtlcScriptSize -) -type dummySignature struct{} - -func (d *dummySignature) Serialize() []byte { - // Always return worst-case signature length, excluding the one byte - // sighash flag. - return make([]byte, 73-1) -} - -// dummySigner is a fake signer used for size (upper bound) calculations. -type dummySigner struct { - Signer -} - -// SignOutputRaw generates a signature for the passed transaction according to -// the data within the passed SignDescriptor. -func (s *dummySigner) SignOutputRaw(tx *wire.MsgTx, - signDesc *SignDescriptor) (Signature, error) { - - return &dummySignature{}, nil -} - -var ( - // dummyPubKey is a pubkey used in script size calculation. - dummyPubKey = btcec.PublicKey{ - X: &big.Int{}, - Y: &big.Int{}, - } - - // dummyAnchorScript is a script used for size calculation. - dummyAnchorScript, _ = CommitScriptAnchor(&dummyPubKey) - - // dummyAnchorWitness is a witness used for size calculation. - dummyAnchorWitness, _ = CommitSpendAnchor( - &dummySigner{}, - &SignDescriptor{ - KeyDesc: keychain.KeyDescriptor{ - PubKey: &dummyPubKey, - }, - WitnessScript: dummyAnchorScript, - }, - nil, - ) + // AnchorScriptSize 40 bytes + // - pubkey_length: 1 byte + // - pubkey: 33 bytes + // - OP_CHECKSIG: 1 byte + // - OP_IFDUP: 1 byte + // - OP_NOTIF: 1 byte + // - OP_16: 1 byte + // - OP_CSV 1 byte + // - OP_ENDIF: 1 byte + AnchorScriptSize = 1 + 33 + 6*1 // AnchorWitnessSize 116 bytes - AnchorWitnessSize = dummyAnchorWitness.SerializeSize() + // - number_of_witnes_elements: 1 byte + // - signature_length: 1 byte + // - signature: 73 bytes + // - witness_script_length: 1 byte + // - witness_script (anchor_script) + AnchorWitnessSize = 1 + 1 + 73 + 1 + AnchorScriptSize ) // EstimateCommitTxWeight estimate commitment transaction weight depending on diff --git a/input/size_test.go b/input/size_test.go index fb57427c..c7c2dc1b 100644 --- a/input/size_test.go +++ b/input/size_test.go @@ -1,15 +1,45 @@ package input_test import ( + "math/big" "testing" "github.com/btcsuite/btcd/blockchain" + "github.com/btcsuite/btcd/btcec" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcutil" "github.com/lightningnetwork/lnd/input" + "github.com/lightningnetwork/lnd/keychain" +) + +const ( + testCSVDelay = (1 << 31) - 1 + + testCLTVExpiry = 500000000 + + // maxDERSignatureSize is the largest possible DER-encoded signature + // without the trailing sighash flag. + maxDERSignatureSize = 72 +) + +var ( + testPubkeyBytes = make([]byte, 33) + + testHash160 = make([]byte, 20) + testPreimage = make([]byte, 32) + + // testPubkey is a pubkey used in script size calculation. + testPubkey = &btcec.PublicKey{ + X: &big.Int{}, + Y: &big.Int{}, + } + + testPrivkey, _ = btcec.PrivKeyFromBytes(btcec.S256(), make([]byte, 32)) + + testTx = wire.NewMsgTx(2) ) // TestTxWeightEstimator tests that transaction weight estimates are calculated @@ -204,7 +234,7 @@ func TestTxWeightEstimator(t *testing.T) { for j := 0; j < test.numP2PKHInputs; j++ { weightEstimate.AddP2PKHInput() - signature := make([]byte, 73) + signature := make([]byte, maxDERSignatureSize+1) compressedPubKey := make([]byte, 33) scriptSig, err := txscript.NewScriptBuilder().AddData(signature). AddData(compressedPubKey).Script() @@ -217,7 +247,7 @@ func TestTxWeightEstimator(t *testing.T) { for j := 0; j < test.numP2WKHInputs; j++ { weightEstimate.AddP2WKHInput() - signature := make([]byte, 73) + signature := make([]byte, maxDERSignatureSize+1) compressedPubKey := make([]byte, 33) witness := wire.TxWitness{signature, compressedPubKey} tx.AddTxIn(&wire.TxIn{Witness: witness}) @@ -232,7 +262,7 @@ func TestTxWeightEstimator(t *testing.T) { for j := 0; j < test.numNestedP2WKHInputs; j++ { weightEstimate.AddNestedP2WKHInput() - signature := make([]byte, 73) + signature := make([]byte, maxDERSignatureSize+1) compressedPubKey := make([]byte, 33) witness := wire.TxWitness{signature, compressedPubKey} scriptSig, err := txscript.NewScriptBuilder().AddData(p2wkhScript). @@ -281,10 +311,537 @@ func TestTxWeightEstimator(t *testing.T) { } } -// TestSizes guards calculated constants to make sure their values remain -// unchanged. -func TestSizes(t *testing.T) { - if input.AnchorWitnessSize != 116 { - t.Fatal("unexpected anchor witness size") +type maxDERSignature struct{} + +func (s *maxDERSignature) Serialize() []byte { + // Always return worst-case signature length, excluding the one byte + // sighash flag. + return make([]byte, maxDERSignatureSize) +} + +func (s *maxDERSignature) Verify(_ []byte, _ *btcec.PublicKey) bool { + return true +} + +// dummySigner is a fake signer used for size (upper bound) calculations. +type dummySigner struct { + input.Signer +} + +// SignOutputRaw generates a signature for the passed transaction according to +// the data within the passed SignDescriptor. +func (s *dummySigner) SignOutputRaw(tx *wire.MsgTx, + signDesc *input.SignDescriptor) (input.Signature, error) { + + return &maxDERSignature{}, nil +} + +type witnessSizeTest struct { + name string + expSize int + genWitness func(t *testing.T) wire.TxWitness +} + +var witnessSizeTests = []witnessSizeTest{ + { + name: "funding", + expSize: input.MultiSigWitnessSize, + genWitness: func(t *testing.T) wire.TxWitness { + witnessScript, _, err := input.GenFundingPkScript( + testPubkeyBytes, testPubkeyBytes, 1, + ) + if err != nil { + t.Fatal(err) + } + + return input.SpendMultiSig( + witnessScript, + testPubkeyBytes, &maxDERSignature{}, + testPubkeyBytes, &maxDERSignature{}, + ) + }, + }, + { + name: "to local timeout", + expSize: input.ToLocalTimeoutWitnessSize, + genWitness: func(t *testing.T) wire.TxWitness { + witnessScript, err := input.CommitScriptToSelf( + testCSVDelay, testPubkey, testPubkey, + ) + if err != nil { + t.Fatal(err) + } + + signDesc := &input.SignDescriptor{ + WitnessScript: witnessScript, + } + + witness, err := input.CommitSpendTimeout( + &dummySigner{}, signDesc, testTx, + ) + if err != nil { + t.Fatal(err) + } + + return witness + }, + }, + { + name: "to local revoke", + expSize: input.ToLocalPenaltyWitnessSize, + genWitness: func(t *testing.T) wire.TxWitness { + witnessScript, err := input.CommitScriptToSelf( + testCSVDelay, testPubkey, testPubkey, + ) + if err != nil { + t.Fatal(err) + } + + signDesc := &input.SignDescriptor{ + WitnessScript: witnessScript, + } + + witness, err := input.CommitSpendRevoke( + &dummySigner{}, signDesc, testTx, + ) + if err != nil { + t.Fatal(err) + } + + return witness + }, + }, + { + name: "to remote confirmed", + expSize: input.ToRemoteConfirmedWitnessSize, + genWitness: func(t *testing.T) wire.TxWitness { + witScript, err := input.CommitScriptToRemoteConfirmed( + testPubkey, + ) + if err != nil { + t.Fatal(err) + } + + signDesc := &input.SignDescriptor{ + WitnessScript: witScript, + KeyDesc: keychain.KeyDescriptor{ + PubKey: testPubkey, + }, + } + + witness, err := input.CommitSpendToRemoteConfirmed( + &dummySigner{}, signDesc, testTx, + ) + if err != nil { + t.Fatal(err) + } + + return witness + }, + }, + { + name: "anchor", + expSize: input.AnchorWitnessSize, + genWitness: func(t *testing.T) wire.TxWitness { + witScript, err := input.CommitScriptAnchor( + testPubkey, + ) + if err != nil { + t.Fatal(err) + } + + signDesc := &input.SignDescriptor{ + WitnessScript: witScript, + KeyDesc: keychain.KeyDescriptor{ + PubKey: testPubkey, + }, + } + + witness, err := input.CommitSpendAnchor( + &dummySigner{}, signDesc, testTx, + ) + if err != nil { + t.Fatal(err) + } + + return witness + }, + }, + { + name: "anchor anyone", + expSize: 43, + genWitness: func(t *testing.T) wire.TxWitness { + witScript, err := input.CommitScriptAnchor( + testPubkey, + ) + if err != nil { + t.Fatal(err) + } + + witness, _ := input.CommitSpendAnchorAnyone(witScript) + + return witness + }, + }, + { + name: "offered htlc revoke", + expSize: input.OfferedHtlcPenaltyWitnessSize - 3, + genWitness: func(t *testing.T) wire.TxWitness { + witScript, err := input.SenderHTLCScript( + testPubkey, testPubkey, testPubkey, + testHash160, false, + ) + if err != nil { + t.Fatal(err) + } + + signDesc := &input.SignDescriptor{ + WitnessScript: witScript, + KeyDesc: keychain.KeyDescriptor{ + PubKey: testPubkey, + }, + DoubleTweak: testPrivkey, + } + + witness, err := input.SenderHtlcSpendRevoke( + &dummySigner{}, signDesc, testTx, + ) + if err != nil { + t.Fatal(err) + } + + return witness + }, + }, + { + name: "offered htlc revoke confirmed", + expSize: input.OfferedHtlcPenaltyWitnessSize, + genWitness: func(t *testing.T) wire.TxWitness { + hash := make([]byte, 20) + + witScript, err := input.SenderHTLCScript( + testPubkey, testPubkey, testPubkey, + hash, true, + ) + if err != nil { + t.Fatal(err) + } + + signDesc := &input.SignDescriptor{ + WitnessScript: witScript, + KeyDesc: keychain.KeyDescriptor{ + PubKey: testPubkey, + }, + DoubleTweak: testPrivkey, + } + + witness, err := input.SenderHtlcSpendRevoke( + &dummySigner{}, signDesc, testTx, + ) + if err != nil { + t.Fatal(err) + } + + return witness + }, + }, + { + name: "offered htlc timeout", + expSize: input.OfferedHtlcTimeoutWitnessSize - 3, + genWitness: func(t *testing.T) wire.TxWitness { + witScript, err := input.SenderHTLCScript( + testPubkey, testPubkey, testPubkey, + testHash160, false, + ) + if err != nil { + t.Fatal(err) + } + + signDesc := &input.SignDescriptor{ + WitnessScript: witScript, + } + + witness, err := input.SenderHtlcSpendTimeout( + &maxDERSignature{}, txscript.SigHashAll, + &dummySigner{}, signDesc, testTx, + ) + if err != nil { + t.Fatal(err) + } + + return witness + }, + }, + { + name: "offered htlc timeout confirmed", + expSize: input.OfferedHtlcTimeoutWitnessSize, + genWitness: func(t *testing.T) wire.TxWitness { + witScript, err := input.SenderHTLCScript( + testPubkey, testPubkey, testPubkey, + testHash160, true, + ) + if err != nil { + t.Fatal(err) + } + + signDesc := &input.SignDescriptor{ + WitnessScript: witScript, + } + + witness, err := input.SenderHtlcSpendTimeout( + &maxDERSignature{}, txscript.SigHashAll, + &dummySigner{}, signDesc, testTx, + ) + if err != nil { + t.Fatal(err) + } + + return witness + }, + }, + { + name: "offered htlc success", + expSize: input.OfferedHtlcSuccessWitnessSize - 3, + genWitness: func(t *testing.T) wire.TxWitness { + witScript, err := input.SenderHTLCScript( + testPubkey, testPubkey, testPubkey, + testHash160, false, + ) + if err != nil { + t.Fatal(err) + } + + signDesc := &input.SignDescriptor{ + WitnessScript: witScript, + } + + witness, err := input.SenderHtlcSpendRedeem( + &dummySigner{}, signDesc, testTx, testPreimage, + ) + if err != nil { + t.Fatal(err) + } + + return witness + }, + }, + { + name: "offered htlc success confirmed", + expSize: input.OfferedHtlcSuccessWitnessSize, + genWitness: func(t *testing.T) wire.TxWitness { + witScript, err := input.SenderHTLCScript( + testPubkey, testPubkey, testPubkey, + testHash160, true, + ) + if err != nil { + t.Fatal(err) + } + + signDesc := &input.SignDescriptor{ + WitnessScript: witScript, + } + + witness, err := input.SenderHtlcSpendRedeem( + &dummySigner{}, signDesc, testTx, testPreimage, + ) + if err != nil { + t.Fatal(err) + } + + return witness + }, + }, + { + name: "accepted htlc revoke", + expSize: input.AcceptedHtlcPenaltyWitnessSize - 3, + genWitness: func(t *testing.T) wire.TxWitness { + witScript, err := input.ReceiverHTLCScript( + testCLTVExpiry, testPubkey, testPubkey, + testPubkey, testHash160, false, + ) + if err != nil { + t.Fatal(err) + } + + signDesc := &input.SignDescriptor{ + WitnessScript: witScript, + KeyDesc: keychain.KeyDescriptor{ + PubKey: testPubkey, + }, + DoubleTweak: testPrivkey, + } + + witness, err := input.ReceiverHtlcSpendRevoke( + &dummySigner{}, signDesc, testTx, + ) + if err != nil { + t.Fatal(err) + } + + return witness + }, + }, + { + name: "accepted htlc revoke confirmed", + expSize: input.AcceptedHtlcPenaltyWitnessSize, + genWitness: func(t *testing.T) wire.TxWitness { + witScript, err := input.ReceiverHTLCScript( + testCLTVExpiry, testPubkey, testPubkey, + testPubkey, testHash160, true, + ) + if err != nil { + t.Fatal(err) + } + + signDesc := &input.SignDescriptor{ + WitnessScript: witScript, + KeyDesc: keychain.KeyDescriptor{ + PubKey: testPubkey, + }, + DoubleTweak: testPrivkey, + } + + witness, err := input.ReceiverHtlcSpendRevoke( + &dummySigner{}, signDesc, testTx, + ) + if err != nil { + t.Fatal(err) + } + + return witness + }, + }, + { + name: "accepted htlc timeout", + expSize: input.AcceptedHtlcTimeoutWitnessSize - 3, + genWitness: func(t *testing.T) wire.TxWitness { + + witScript, err := input.ReceiverHTLCScript( + testCLTVExpiry, testPubkey, testPubkey, + testPubkey, testHash160, false, + ) + if err != nil { + t.Fatal(err) + } + + signDesc := &input.SignDescriptor{ + WitnessScript: witScript, + } + + witness, err := input.ReceiverHtlcSpendTimeout( + &dummySigner{}, signDesc, testTx, + testCLTVExpiry, + ) + if err != nil { + t.Fatal(err) + } + + return witness + }, + }, + { + name: "accepted htlc timeout confirmed", + expSize: input.AcceptedHtlcTimeoutWitnessSize, + genWitness: func(t *testing.T) wire.TxWitness { + witScript, err := input.ReceiverHTLCScript( + testCLTVExpiry, testPubkey, testPubkey, + testPubkey, testHash160, true, + ) + if err != nil { + t.Fatal(err) + } + + signDesc := &input.SignDescriptor{ + WitnessScript: witScript, + } + + witness, err := input.ReceiverHtlcSpendTimeout( + &dummySigner{}, signDesc, testTx, + testCLTVExpiry, + ) + if err != nil { + t.Fatal(err) + } + + return witness + }, + }, + { + name: "accepted htlc success", + expSize: input.AcceptedHtlcSuccessWitnessSize - 3, + genWitness: func(t *testing.T) wire.TxWitness { + witScript, err := input.ReceiverHTLCScript( + testCLTVExpiry, testPubkey, testPubkey, + testPubkey, testHash160, false, + ) + if err != nil { + t.Fatal(err) + } + + signDesc := &input.SignDescriptor{ + WitnessScript: witScript, + KeyDesc: keychain.KeyDescriptor{ + PubKey: testPubkey, + }, + } + + witness, err := input.ReceiverHtlcSpendRedeem( + &maxDERSignature{}, txscript.SigHashAll, + testPreimage, &dummySigner{}, signDesc, testTx, + ) + if err != nil { + t.Fatal(err) + } + + return witness + }, + }, + { + name: "accepted htlc success confirmed", + expSize: input.AcceptedHtlcSuccessWitnessSize, + genWitness: func(t *testing.T) wire.TxWitness { + witScript, err := input.ReceiverHTLCScript( + testCLTVExpiry, testPubkey, testPubkey, + testPubkey, testHash160, true, + ) + if err != nil { + t.Fatal(err) + } + + signDesc := &input.SignDescriptor{ + WitnessScript: witScript, + KeyDesc: keychain.KeyDescriptor{ + PubKey: testPubkey, + }, + } + + witness, err := input.ReceiverHtlcSpendRedeem( + &maxDERSignature{}, txscript.SigHashAll, + testPreimage, &dummySigner{}, signDesc, testTx, + ) + if err != nil { + t.Fatal(err) + } + + return witness + }, + }, +} + +// TestWitnessSizes asserts the correctness of our magic witness constants. +// Witnesses involving signatures will have maxDERSignatures injected so that we +// can determine upper bounds for the witness sizes. These constants are +// predominately used for fee estimation, so we want to be certain that we +// aren't under estimating or our transactions could get stuck. +func TestWitnessSizes(t *testing.T) { + for _, test := range witnessSizeTests { + test := test + t.Run(test.name, func(t *testing.T) { + size := test.genWitness(t).SerializeSize() + if size != test.expSize { + t.Fatalf("size mismatch, want: %v, got: %v", + test.expSize, size) + } + }) } } diff --git a/lnrpc/signrpc/signer_server.go b/lnrpc/signrpc/signer_server.go index e51fe5d0..127d113e 100644 --- a/lnrpc/signrpc/signer_server.go +++ b/lnrpc/signrpc/signer_server.go @@ -321,7 +321,7 @@ func (s *Server) SignOutputRaw(ctx context.Context, in *SignReq) (*SignResp, err return nil, err } - resp.RawSigs[i] = sig + resp.RawSigs[i] = sig.Serialize() } return resp, nil diff --git a/sweep/txgenerator_test.go b/sweep/txgenerator_test.go index 91641e5e..575ae65a 100644 --- a/sweep/txgenerator_test.go +++ b/sweep/txgenerator_test.go @@ -15,7 +15,7 @@ var ( input.HtlcOfferedRemoteTimeout, input.WitnessKeyHash, } - expectedWeight = int64(1462) + expectedWeight = int64(1463) expectedSummary = "0000000000000000000000000000000000000000000000000000000000000000:10 (CommitmentTimeLock), " + "0000000000000000000000000000000000000000000000000000000000000001:11 (HtlcAcceptedSuccessSecondLevel), " + "0000000000000000000000000000000000000000000000000000000000000002:12 (HtlcOfferedRemoteTimeout), " + diff --git a/watchtower/lookout/justice_descriptor.go b/watchtower/lookout/justice_descriptor.go index 40e5a479..cea3ae03 100644 --- a/watchtower/lookout/justice_descriptor.go +++ b/watchtower/lookout/justice_descriptor.go @@ -225,6 +225,12 @@ func (p *JusticeDescriptor) assembleJusticeTxn(txWeight int64, // CreateJusticeTxn computes the justice transaction that sweeps a breaching // commitment transaction. The justice transaction is constructed by assembling // the witnesses using data provided by the client in a prior state update. +// +// NOTE: An older version of ToLocalPenaltyWitnessSize underestimated the size +// of the witness by one byte, which could cause the signature(s) to break if +// the tower is reconstructing with the newer constant because the output values +// might differ. This method retains that original behavior to not invalidate +// historical signatures. func (p *JusticeDescriptor) CreateJusticeTxn() (*wire.MsgTx, error) { var ( sweepInputs = make([]*breachedInput, 0, 2) @@ -256,7 +262,13 @@ func (p *JusticeDescriptor) CreateJusticeTxn() (*wire.MsgTx, error) { if err != nil { return nil, err } - weightEstimate.AddWitnessInput(input.ToLocalPenaltyWitnessSize) + + // An older ToLocalPenaltyWitnessSize constant used to underestimate the + // size by one byte. The diferrence in weight can cause different output + // values on the sweep transaction, so we mimic the original bug to + // avoid invalidating signatures by older clients. + weightEstimate.AddWitnessInput(input.ToLocalPenaltyWitnessSize - 1) + sweepInputs = append(sweepInputs, toLocalInput) // If the justice kit specifies that we have to sweep the to-remote diff --git a/watchtower/lookout/justice_descriptor_test.go b/watchtower/lookout/justice_descriptor_test.go index 6785f1b6..afc4dacd 100644 --- a/watchtower/lookout/justice_descriptor_test.go +++ b/watchtower/lookout/justice_descriptor_test.go @@ -144,7 +144,13 @@ func testJusticeDescriptor(t *testing.T, blobType blob.Type) { // Compute the weight estimate for our justice transaction. var weightEstimate input.TxWeightEstimator - weightEstimate.AddWitnessInput(input.ToLocalPenaltyWitnessSize) + + // An older ToLocalPenaltyWitnessSize constant used to underestimate the + // size by one byte. The diferrence in weight can cause different output + // values on the sweep transaction, so we mimic the original bug and + // create signatures using the original weight estimate. + weightEstimate.AddWitnessInput(input.ToLocalPenaltyWitnessSize - 1) + weightEstimate.AddWitnessInput(input.P2WKHWitnessSize) weightEstimate.AddP2WKHOutput() if blobType.Has(blob.FlagReward) { diff --git a/watchtower/wtclient/backup_task.go b/watchtower/wtclient/backup_task.go index c112c101..302a6bc3 100644 --- a/watchtower/wtclient/backup_task.go +++ b/watchtower/wtclient/backup_task.go @@ -141,7 +141,14 @@ func (t *backupTask) bindSession(session *wtdb.ClientSessionBody) error { // Next, add the contribution from the inputs that are present on this // breach transaction. if t.toLocalInput != nil { - weightEstimate.AddWitnessInput(input.ToLocalPenaltyWitnessSize) + // An older ToLocalPenaltyWitnessSize constant used to + // underestimate the size by one byte. The diferrence in weight + // can cause different output values on the sweep transaction, + // so we mimic the original bug and create signatures using the + // original weight estimate. + weightEstimate.AddWitnessInput( + input.ToLocalPenaltyWitnessSize - 1, + ) } if t.toRemoteInput != nil { weightEstimate.AddWitnessInput(input.P2WKHWitnessSize)