lnwallet: extract SignDescriptor serialization and witness generator
This commit is contained in:
parent
f0aa186a56
commit
d8c56433e3
@ -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
134
lnwallet/signdescriptor.go
Normal 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
|
||||
}
|
122
lnwallet/signdescriptor_test.go
Normal file
122
lnwallet/signdescriptor_test.go
Normal 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
59
lnwallet/witnessgen.go
Normal 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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user