lnwallet: extract SignDescriptor serialization and witness generator

This commit is contained in:
Philip Hayes 2017-05-07 04:04:28 -07:00 committed by Olaoluwa Osuntokun
parent f0aa186a56
commit d8c56433e3
5 changed files with 324 additions and 50 deletions

View File

@ -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"
)

134
lnwallet/signdescriptor.go Normal file
View File

@ -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
}

View File

@ -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)
}
}
}

59
lnwallet/witnessgen.go Normal file
View File

@ -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)
}
}
}

View File

@ -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
}