From d8c56433e3f74e6c7bee9dff32b955bc373e03f0 Mon Sep 17 00:00:00 2001 From: Philip Hayes Date: Sun, 7 May 2017 04:04:28 -0700 Subject: [PATCH] lnwallet: extract SignDescriptor serialization and witness generator --- lnwallet/interface.go | 1 - lnwallet/signdescriptor.go | 134 ++++++++++++++++++++++++++++++++ lnwallet/signdescriptor_test.go | 122 +++++++++++++++++++++++++++++ lnwallet/witnessgen.go | 59 ++++++++++++++ utxonursery.go | 58 +++----------- 5 files changed, 324 insertions(+), 50 deletions(-) create mode 100644 lnwallet/signdescriptor.go create mode 100644 lnwallet/signdescriptor_test.go create mode 100644 lnwallet/witnessgen.go diff --git a/lnwallet/interface.go b/lnwallet/interface.go index 5dbc4d50..92f6adac 100644 --- a/lnwallet/interface.go +++ b/lnwallet/interface.go @@ -7,7 +7,6 @@ import ( "github.com/roasbeef/btcd/btcec" "github.com/roasbeef/btcd/chaincfg/chainhash" - "github.com/roasbeef/btcd/txscript" "github.com/roasbeef/btcd/wire" "github.com/roasbeef/btcutil" ) diff --git a/lnwallet/signdescriptor.go b/lnwallet/signdescriptor.go new file mode 100644 index 00000000..8e1ff391 --- /dev/null +++ b/lnwallet/signdescriptor.go @@ -0,0 +1,134 @@ +package lnwallet + +import ( + "encoding/binary" + "io" + + "github.com/lightningnetwork/lnd/lnwire" + "github.com/roasbeef/btcd/btcec" + "github.com/roasbeef/btcd/txscript" + "github.com/roasbeef/btcd/wire" +) + +// SignDescriptor houses the necessary information required to successfully sign +// a given output. This struct is used by the Signer interface in order to gain +// access to critical data needed to generate a valid signature. +type SignDescriptor struct { + // Pubkey is the public key to which the signature should be generated + // over. The Signer should then generate a signature with the private + // key corresponding to this public key. + PubKey *btcec.PublicKey + + // PrivateTweak is a scalar value that should be added to the private + // key corresponding to the above public key to obtain the private key + // to be used to sign this input. This value is typically a leaf node + // from the revocation tree. + // + // NOTE: If this value is nil, then the input can be signed using only + // the above public key. + PrivateTweak []byte + + // WitnessScript is the full script required to properly redeem the + // output. This field will only be populated if a p2wsh or a p2sh + // output is being signed. + WitnessScript []byte + + // Output is the target output which should be signed. The PkScript and + // Value fields within the output should be properly populated, + // otherwise an invalid signature may be generated. + Output *wire.TxOut + + // HashType is the target sighash type that should be used when + // generating the final sighash, and signature. + HashType txscript.SigHashType + + // SigHashes is the pre-computed sighash midstate to be used when + // generating the final sighash for signing. + SigHashes *txscript.TxSigHashes + + // InputIndex is the target input within the transaction that should be + // signed. + InputIndex int +} + +// WriteSignDescriptor serializes a SignDescriptor struct into the passed +// io.Writer stream. +// NOTE: We assume the SigHashes and InputIndex fields haven't been assigned +// yet, since that is usually done just before broadcast by the witness +// generator. +func WriteSignDescriptor(w io.Writer, sd *SignDescriptor) error { + serializedPubKey := sd.PubKey.SerializeCompressed() + if err := wire.WriteVarBytes(w, 0, serializedPubKey); err != nil { + return err + } + + if err := wire.WriteVarBytes(w, 0, sd.PrivateTweak); err != nil { + return err + } + + if err := wire.WriteVarBytes(w, 0, sd.WitnessScript); err != nil { + return err + } + + if err := lnwire.WriteTxOut(w, sd.Output); err != nil { + return err + } + + var scratch [4]byte + binary.BigEndian.PutUint32(scratch[:], uint32(sd.HashType)) + if _, err := w.Write(scratch[:]); err != nil { + return err + } + + return nil +} + +// ReadSignDescriptor deserializes a SignDescriptor struct from the passed +// io.Reader stream. +func ReadSignDescriptor(r io.Reader, sd *SignDescriptor) error { + + pubKeyBytes, err := wire.ReadVarBytes(r, 0, 34, "pubkey") + if err != nil { + return err + } + sd.PubKey, err = btcec.ParsePubKey(pubKeyBytes, btcec.S256()) + if err != nil { + return err + } + + privateTweak, err := wire.ReadVarBytes(r, 0, 32, "privateTweak") + if err != nil { + return err + } + + // Serializing a SignDescriptor with a nil-valued PrivateTweak results in + // deserializing a zero-length slice. Since a nil-valued PrivateTweak has + // special meaning and a zero-length slice for a PrivateTweak is invalid, + // we can use the zero-length slice as the flag for a nil-valued + // PrivateTweak. + if len(privateTweak) == 0 { + sd.PrivateTweak = nil + } else { + sd.PrivateTweak = privateTweak + } + + witnessScript, err := wire.ReadVarBytes(r, 0, 100, "witnessScript") + if err != nil { + return err + } + sd.WitnessScript = witnessScript + + txOut := &wire.TxOut{} + if err := lnwire.ReadTxOut(r, txOut); err != nil { + return err + } + sd.Output = txOut + + var hashType [4]byte + if _, err := io.ReadFull(r, hashType[:]); err != nil { + return err + } + sd.HashType = txscript.SigHashType(binary.BigEndian.Uint32(hashType[:])) + + return nil +} diff --git a/lnwallet/signdescriptor_test.go b/lnwallet/signdescriptor_test.go new file mode 100644 index 00000000..ddd22f34 --- /dev/null +++ b/lnwallet/signdescriptor_test.go @@ -0,0 +1,122 @@ +package lnwallet + +import ( + "bytes" + "reflect" + "testing" + + "github.com/roasbeef/btcd/btcec" + "github.com/roasbeef/btcd/txscript" + "github.com/roasbeef/btcd/wire" +) + +func TestSignDescriptorSerialization(t *testing.T) { + keys := [][]byte{ + {0x04, 0x11, 0xdb, 0x93, 0xe1, 0xdc, 0xdb, 0x8a, + 0x01, 0x6b, 0x49, 0x84, 0x0f, 0x8c, 0x53, 0xbc, 0x1e, + 0xb6, 0x8a, 0x38, 0x2e, 0x97, 0xb1, 0x48, 0x2e, 0xca, + 0xd7, 0xb1, 0x48, 0xa6, 0x90, 0x9a, 0x5c, 0xb2, 0xe0, + 0xea, 0xdd, 0xfb, 0x84, 0xcc, 0xf9, 0x74, 0x44, 0x64, + 0xf8, 0x2e, 0x16, 0x0b, 0xfa, 0x9b, 0x8b, 0x64, 0xf9, + 0xd4, 0xc0, 0x3f, 0x99, 0x9b, 0x86, 0x43, 0xf6, 0x56, + 0xb4, 0x12, 0xa3, + }, + {0x04, 0x11, 0xdb, 0x93, 0xe1, 0xdc, 0xdb, 0x8a, + 0x01, 0x6b, 0x49, 0x84, 0x0f, 0x8c, 0x53, 0xbc, 0x1e, + 0xb6, 0x8a, 0x38, 0x2e, 0x97, 0xb1, 0x48, 0x2e, 0xca, + 0xd7, 0xb1, 0x48, 0xa6, 0x90, 0x9a, 0x5c, 0xb2, 0xe0, + 0xea, 0xdd, 0xfb, 0x84, 0xcc, 0xf9, 0x74, 0x44, 0x64, + 0xf8, 0x2e, 0x16, 0x0b, 0xfa, 0x9b, 0x8b, 0x64, 0xf9, + 0xd4, 0xc0, 0x3f, 0x99, 0x9b, 0x86, 0x43, 0xf6, 0x56, + 0xb4, 0x12, 0xa3, + }, + } + + signDescriptors := []SignDescriptor{ + { + PrivateTweak: []byte{ + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, + }, + WitnessScript: []byte{ + 0x00, 0x14, 0xee, 0x91, 0x41, 0x7e, 0x85, 0x6c, 0xde, + 0x10, 0xa2, 0x91, 0x1e, 0xdc, 0xbd, 0xbd, 0x69, 0xe2, + 0xef, 0xb5, 0x71, 0x48, + }, + Output: &wire.TxOut{ + Value: 5000000000, + PkScript: []byte{ + 0x41, // OP_DATA_65 + 0x04, 0xd6, 0x4b, 0xdf, 0xd0, 0x9e, 0xb1, 0xc5, + 0xfe, 0x29, 0x5a, 0xbd, 0xeb, 0x1d, 0xca, 0x42, + 0x81, 0xbe, 0x98, 0x8e, 0x2d, 0xa0, 0xb6, 0xc1, + 0xc6, 0xa5, 0x9d, 0xc2, 0x26, 0xc2, 0x86, 0x24, + 0xe1, 0x81, 0x75, 0xe8, 0x51, 0xc9, 0x6b, 0x97, + 0x3d, 0x81, 0xb0, 0x1c, 0xc3, 0x1f, 0x04, 0x78, + 0x34, 0xbc, 0x06, 0xd6, 0xd6, 0xed, 0xf6, 0x20, + 0xd1, 0x84, 0x24, 0x1a, 0x6a, 0xed, 0x8b, 0x63, + 0xa6, // 65-byte signature + 0xac, // OP_CHECKSIG + }, + }, + HashType: txscript.SigHashAll, + }, + + // Test serializing a SignDescriptor with a nil-valued PrivateTweak + { + PrivateTweak: nil, + WitnessScript: []byte{ + 0x00, 0x14, 0xee, 0x91, 0x41, 0x7e, 0x85, 0x6c, 0xde, + 0x10, 0xa2, 0x91, 0x1e, 0xdc, 0xbd, 0xbd, 0x69, 0xe2, + 0xef, 0xb5, 0x71, 0x48, + }, + Output: &wire.TxOut{ + Value: 5000000000, + PkScript: []byte{ + 0x41, // OP_DATA_65 + 0x04, 0xd6, 0x4b, 0xdf, 0xd0, 0x9e, 0xb1, 0xc5, + 0xfe, 0x29, 0x5a, 0xbd, 0xeb, 0x1d, 0xca, 0x42, + 0x81, 0xbe, 0x98, 0x8e, 0x2d, 0xa0, 0xb6, 0xc1, + 0xc6, 0xa5, 0x9d, 0xc2, 0x26, 0xc2, 0x86, 0x24, + 0xe1, 0x81, 0x75, 0xe8, 0x51, 0xc9, 0x6b, 0x97, + 0x3d, 0x81, 0xb0, 0x1c, 0xc3, 0x1f, 0x04, 0x78, + 0x34, 0xbc, 0x06, 0xd6, 0xd6, 0xed, 0xf6, 0x20, + 0xd1, 0x84, 0x24, 0x1a, 0x6a, 0xed, 0x8b, 0x63, + 0xa6, // 65-byte signature + 0xac, // OP_CHECKSIG + }, + }, + HashType: txscript.SigHashAll, + }, + } + + for i := 0; i < len(signDescriptors); i++ { + // Parse pubkeys for each sign descriptor. + sd := &signDescriptors[i] + pubkey, err := btcec.ParsePubKey(keys[i], btcec.S256()) + if err != nil { + t.Fatalf("unable to parse pubkey: %v", err) + } + sd.PubKey = pubkey + + // Test that serialize -> deserialize yields same result as original. + var buf bytes.Buffer + if err := WriteSignDescriptor(&buf, sd); err != nil { + t.Fatalf("unable to serialize sign descriptor[%v]: %v", i, sd) + } + + desSd := &SignDescriptor{} + if err := ReadSignDescriptor(&buf, desSd); err != nil { + t.Fatalf("unable to deserialize sign descriptor[%v]: %v", i, sd) + } + + if !reflect.DeepEqual(sd, desSd) { + t.Fatalf("original and deserialized sign descriptors not equal:\n"+ + "original : %+v\n"+ + "deserialized : %+v\n", + sd, desSd) + } + } +} diff --git a/lnwallet/witnessgen.go b/lnwallet/witnessgen.go new file mode 100644 index 00000000..77fd6995 --- /dev/null +++ b/lnwallet/witnessgen.go @@ -0,0 +1,59 @@ +package lnwallet + +import ( + "fmt" + + "github.com/roasbeef/btcd/txscript" + "github.com/roasbeef/btcd/wire" +) + +// WitnessType determines how an output's witness will be generated. The +// default commitmentTimeLock type will generate a witness that will allow +// spending of a time-locked transaction enforced by CheckSequenceVerify. +type WitnessType uint16 + +const ( + // Witness that allows us to spend the output of a commitment transaction + // after a relative lock-time lockout. + CommitmentTimeLock WitnessType = 0 + + // Witness that allows us to spend a settled no-delay output immediately on + // a counterparty's commitment transaction. + CommitmentNoDelay WitnessType = 1 + + // Witness that allows us to sweep the settled output of a malicious + // counterparty's who broadcasts a revoked commitment transaction. + CommitmentRevoke WitnessType = 2 +) + +// WitnessGenerator represents a function which is able to generate the final +// witness for a particular public key script. This function acts as an +// abstraction layer, hiding the details of the underlying script. +type WitnessGenerator func(tx *wire.MsgTx, hc *txscript.TxSigHashes, + inputIndex int) ([][]byte, error) + +// GenWitnessFunc will return a WitnessGenerator function that an output +// uses to generate the witness for a sweep transaction. +func (wt WitnessType) GenWitnessFunc(signer *Signer, + descriptor *SignDescriptor) WitnessGenerator { + + return func(tx *wire.MsgTx, hc *txscript.TxSigHashes, + inputIndex int) ([][]byte, error) { + + desc := descriptor + desc.SigHashes = hc + desc.InputIndex = inputIndex + + switch wt { + case CommitmentTimeLock: + return CommitSpendTimeout(*signer, desc, tx) + case CommitmentNoDelay: + return CommitSpendNoDelay(*signer, desc, tx) + case CommitmentRevoke: + return CommitSpendRevoke(*signer, desc, tx) + default: + return nil, fmt.Errorf("unknown witness type: %v", wt) + } + } + +} diff --git a/utxonursery.go b/utxonursery.go index 17780cd6..09cf2b5d 100644 --- a/utxonursery.go +++ b/utxonursery.go @@ -14,7 +14,6 @@ import ( "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/lnwallet" - "github.com/roasbeef/btcd/btcec" "github.com/lightningnetwork/lnd/lnwire" "github.com/roasbeef/btcd/txscript" "github.com/roasbeef/btcd/wire" @@ -77,44 +76,6 @@ var ( ErrContractNotFound = fmt.Errorf("unable to locate contract") ) -// witnessType determines how an output's witness will be generated. The -// default commitmentTimeLock type will generate a witness that will allow -// spending of a time-locked transaction enforced by CheckSequenceVerify. -type witnessType uint16 - -const ( - commitmentTimeLock witnessType = 0 -) - -// witnessGenerator represents a function which is able to generate the final -// witness for a particular public key script. This function acts as an -// abstraction layer, hiding the details of the underlying script from the -// utxoNursery. -type witnessGenerator func(tx *wire.MsgTx, hc *txscript.TxSigHashes, - inputIndex int) ([][]byte, error) - -// generateFunc will return the witnessGenerator function that a kidOutput uses -// to generate the witness for a sweep transaction. Currently there is only one -// witnessType but this will be expanded. -func (wt witnessType) generateFunc(signer *lnwallet.Signer, - descriptor *lnwallet.SignDescriptor) witnessGenerator { - - switch wt { - case commitmentTimeLock: - return func(tx *wire.MsgTx, hc *txscript.TxSigHashes, - inputIndex int) ([][]byte, error) { - - desc := descriptor - desc.SigHashes = hc - desc.InputIndex = inputIndex - - return lnwallet.CommitSpendTimeout(*signer, desc, tx) - } - } - - return nil -} - // utxoNursery is a system dedicated to incubating time-locked outputs created // by the broadcast of a commitment transaction either by us, or the remote // peer. The nursery accepts outputs and "incubates" them until they've reached @@ -292,7 +253,7 @@ func (u *utxoNursery) Stop() error { // kidOutput represents an output that's waiting for a required blockheight // before its funds will be available to be moved into the user's wallet. The -// struct includes a witnessGenerator closure which will be used to generate +// struct includes a WitnessGenerator closure which will be used to generate // the witness required to sweep the output once it's mature. // // TODO(roasbeef): rename to immatureOutput? @@ -302,13 +263,14 @@ type kidOutput struct { amt btcutil.Amount outPoint wire.OutPoint - witnessFunc witnessGenerator - + // TODO(roasbeef): using block timeouts everywhere currently, will need + // to modify logic later to account for MTP based timeouts. blocksToMaturity uint32 confHeight uint32 signDescriptor *lnwallet.SignDescriptor - witnessType witnessType + witnessType lnwallet.WitnessType + witnessFunc lnwallet.WitnessGenerator } // incubationRequest is a request to the utxoNursery to incubate a set of @@ -335,7 +297,7 @@ func (u *utxoNursery) IncubateOutputs(closeSummary *lnwallet.ForceCloseSummary) outPoint: closeSummary.SelfOutpoint, blocksToMaturity: closeSummary.SelfOutputMaturity, signDescriptor: closeSummary.SelfOutputSignDesc, - witnessType: commitmentTimeLock, + witnessType: lnwallet.CommitmentTimeLock, } incReq.outputs = append(incReq.outputs, selfOutput) @@ -805,7 +767,7 @@ func fetchGraduatingOutputs(db *channeldb.DB, wallet *lnwallet.LightningWallet, return nil, err } - // If no time-locked outputs can be swept at this point, ten we can + // If no time-locked outputs can be swept at this point, then we can // exit early. if len(results) == 0 { return nil, nil @@ -1050,8 +1012,7 @@ func serializeKidOutput(w io.Writer, kid *kidOutput) error { return err } - serializedPubKey := kid.signDescriptor.PubKey.SerializeCompressed() - if err := wire.WriteVarBytes(w, 0, serializedPubKey); err != nil { + if err := lnwallet.WriteSignDescriptor(w, kid.signDescriptor); err != nil { return err } @@ -1107,7 +1068,7 @@ func deserializeKidOutput(r io.Reader) (*kidOutput, error) { if _, err := r.Read(scratch[:2]); err != nil { return nil, err } - kid.witnessType = witnessType(byteOrder.Uint16(scratch[:2])) + kid.witnessType = lnwallet.WitnessType(byteOrder.Uint16(scratch[:2])) kid.signDescriptor = &lnwallet.SignDescriptor{} @@ -1143,7 +1104,6 @@ func deserializeKidOutput(r io.Reader) (*kidOutput, error) { if _, err := r.Read(scratch[:4]); err != nil { return nil, err } - kid.signDescriptor.HashType = txscript.SigHashType(byteOrder.Uint32(scratch[:4])) return kid, nil }