diff --git a/chancloser.go b/chancloser.go index 4dc28d40e..b261a3657 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, @@ -589,7 +592,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 52324698a..923fb3eaf 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 f927a9609..75d3cef67 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 23126c30c..761227c93 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 c7247136d..b222bf94b 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 cefb673a9..c65e5fb01 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 d8f80f313..318d1faed 100644 --- a/input/script_utils.go +++ b/input/script_utils.go @@ -23,6 +23,17 @@ 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 + + // 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 // paying to a version 0 witness program paying to the passed redeem script. func WitnessScriptHash(witnessScript []byte) ([]byte, error) { @@ -85,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 @@ -97,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. @@ -283,7 +296,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 @@ -332,7 +345,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 @@ -343,7 +356,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,8 +372,8 @@ func SenderHtlcSpendTimeout(receiverSig []byte, // original OP_CHECKMULTISIG. witnessStack := wire.TxWitness(make([][]byte, 5)) witnessStack[0] = nil - witnessStack[1] = append(receiverSig, byte(receiverSigHash)) - witnessStack[2] = append(sweepSig, byte(signDesc.HashType)) + witnessStack[1] = append(receiverSig.Serialize(), byte(receiverSigHash)) + witnessStack[2] = append(sweepSig.Serialize(), byte(signDesc.HashType)) witnessStack[3] = nil witnessStack[4] = signDesc.WitnessScript @@ -508,7 +521,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,8 +540,8 @@ 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[2] = append(sweepSig, byte(signDesc.HashType)) + witnessStack[1] = append(senderSig.Serialize(), byte(senderSigHash)) + witnessStack[2] = append(sweepSig.Serialize(), byte(signDesc.HashType)) witnessStack[3] = paymentPreimage witnessStack[4] = signDesc.WitnessScript @@ -555,7 +568,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 @@ -620,7 +633,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 @@ -725,7 +738,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 @@ -750,7 +763,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 @@ -781,7 +794,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 @@ -885,7 +898,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 @@ -910,7 +923,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 @@ -944,7 +957,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 @@ -1021,7 +1034,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 @@ -1077,7 +1090,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 be1e2d0fb..5978d3eec 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,17 @@ func TestHTLCSenderSpendValidation(t *testing.T) { SigHashes: sweepTxSigHashes, InputIndex: 0, } - bobRecvrSig, 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( + bobSig.Serialize(), btcec.S256(), + ) + if err != nil { + t.Fatalf("unable to parse signature: %v", err) + } } testCases := []struct { @@ -622,7 +629,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 +702,17 @@ func TestHTLCReceiverSpendValidation(t *testing.T) { SigHashes: sweepTxSigHashes, InputIndex: 0, } - aliceSenderSig, 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( + aliceSig.Serialize(), 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/input/signer.go b/input/signer.go index cd3202556..86622638e 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 32a89bcda..6cebc2824 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,47 +429,25 @@ const ( // - witness_script_length: 1 byte // - witness_script (offered_htlc_script) OfferedHtlcPenaltyWitnessSize = 1 + 1 + 73 + 1 + 33 + 1 + OfferedHtlcScriptSize -) -// 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) ( - []byte, error) { - - // Always return worst-case signature length, excluding the one byte - // sighash flag. - return make([]byte, 73-1), 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 fb57427c3..c7c2dc1b3 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/input/test_utils.go b/input/test_utils.go index b9719438d..5b0044172 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/lnrpc/signrpc/signer_server.go b/lnrpc/signrpc/signer_server.go index e51fe5d00..127d113e7 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/lnwallet/btcwallet/signer.go b/lnwallet/btcwallet/signer.go index 35e158c02..a79a5fd92 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 c9ca227db..5f6eef922 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 @@ -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, 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. @@ -5459,11 +5463,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 +5594,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 @@ -5941,7 +5954,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() @@ -6007,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) { @@ -6060,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 c266db1c8..318c0b960 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, 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, 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, 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, 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, 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, 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, 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, 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 12bb5c093..e979b014b 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/interface.go b/lnwallet/interface.go index 1c6c2bd70..41052503c 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 2fe3da53b..16fc7ce43 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" @@ -1590,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 @@ -1975,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 @@ -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 diff --git a/lnwallet/reservation.go b/lnwallet/reservation.go index 192513518..22ef08b3a 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 57b849888..ec1d4f346 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/test_utils.go b/lnwallet/test_utils.go index cbe1b2b3e..55d8a3420 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 diff --git a/lnwallet/wallet.go b/lnwallet/wallet.go index f7d66b6ba..3349d29ec 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 012d5a913..13a2f25c3 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 bf9559d74..0b71a6dc3 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 0bc48e5c7..1fff1d513 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 8946c2c6b..2b97c9379 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 5a6696201..6e0bce981 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 14cb19798..4669168be 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 b56e43adc..f8c36b306 100644 --- a/server.go +++ b/server.go @@ -980,7 +980,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 6b9b0d723..7c28710be 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/sweep/txgenerator_test.go b/sweep/txgenerator_test.go index 91641e5e5..575ae65aa 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 40e5a479e..cea3ae032 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 7fd93c8c8..afc4dacd6 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) { @@ -262,7 +268,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 +316,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/wtclient/backup_task.go b/watchtower/wtclient/backup_task.go index c112c1016..302a6bc3b 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) diff --git a/watchtower/wtmock/signer.go b/watchtower/wtmock/signer.go index c41e4f2f4..89421d6ad 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.