swap: HTLCV3 added

In this commit we add the version 3 htlc, which is implemented with
taproot script spending the two payment paths: the claim path case, and
the timeout case.
pull/475/head
Harsha Goli 2 years ago committed by Carla Kirk-Cohen
parent 9d2d5a78d4
commit d22b064e54
No known key found for this signature in database
GPG Key ID: 4CA7FE54A6213C91

@ -9,10 +9,11 @@ require (
github.com/btcsuite/btcwallet/wtxmgr v1.5.0 github.com/btcsuite/btcwallet/wtxmgr v1.5.0
github.com/coreos/bbolt v1.3.3 github.com/coreos/bbolt v1.3.3
github.com/davecgh/go-spew v1.1.1 github.com/davecgh/go-spew v1.1.1
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1
github.com/fortytw2/leaktest v1.3.0 github.com/fortytw2/leaktest v1.3.0
github.com/grpc-ecosystem/grpc-gateway/v2 v2.5.0 github.com/grpc-ecosystem/grpc-gateway/v2 v2.5.0
github.com/jessevdk/go-flags v1.4.0 github.com/jessevdk/go-flags v1.4.0
github.com/lightninglabs/aperture v0.1.17-beta.0.20220325093943-42b9d4c1be7f github.com/lightninglabs/aperture v0.1.17-beta.0.20220328072456-4a2632d0be38
github.com/lightninglabs/lndclient v0.15.0-0 github.com/lightninglabs/lndclient v0.15.0-0
github.com/lightninglabs/loop/swapserverrpc v1.0.0 github.com/lightninglabs/loop/swapserverrpc v1.0.0
github.com/lightninglabs/protobuf-hex-display v1.4.3-hex-display github.com/lightninglabs/protobuf-hex-display v1.4.3-hex-display

@ -472,8 +472,8 @@ github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lib/pq v1.10.3 h1:v9QZf2Sn6AmjXtQeFpdoq/eaNtYP6IN+7lcrygsIAtg= github.com/lib/pq v1.10.3 h1:v9QZf2Sn6AmjXtQeFpdoq/eaNtYP6IN+7lcrygsIAtg=
github.com/lib/pq v1.10.3/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.3/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lightninglabs/aperture v0.1.17-beta.0.20220325093943-42b9d4c1be7f h1:wfyZ3sZXoGayB+V8icHl6uoqOX6wiyncKFK9pTleGRw= github.com/lightninglabs/aperture v0.1.17-beta.0.20220328072456-4a2632d0be38 h1:ht1wzuSmScXJEnW30Cp2vUBHYzJZvme5DHxNMyg+LT0=
github.com/lightninglabs/aperture v0.1.17-beta.0.20220325093943-42b9d4c1be7f/go.mod h1:lDjRKhndRH0CzZQ2m8dWODdqp/ejEW7esb2u2nlvrw4= github.com/lightninglabs/aperture v0.1.17-beta.0.20220328072456-4a2632d0be38/go.mod h1:lDjRKhndRH0CzZQ2m8dWODdqp/ejEW7esb2u2nlvrw4=
github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf h1:HZKvJUHlcXI/f/O0Avg7t8sqkPo78HFzjmeYFl6DPnc= github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf h1:HZKvJUHlcXI/f/O0Avg7t8sqkPo78HFzjmeYFl6DPnc=
github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf/go.mod h1:vxmQPeIQxPf6Jf9rM8R+B4rKBqLA2AjttNxkFBL2Plk= github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf/go.mod h1:vxmQPeIQxPf6Jf9rM8R+B4rKBqLA2AjttNxkFBL2Plk=
github.com/lightninglabs/lightning-node-connect/hashmailrpc v1.0.2/go.mod h1:antQGRDRJiuyQF6l+k6NECCSImgCpwaZapATth2Chv4= github.com/lightninglabs/lightning-node-connect/hashmailrpc v1.0.2/go.mod h1:antQGRDRJiuyQF6l+k6NECCSImgCpwaZapATth2Chv4=

@ -966,7 +966,7 @@ func (s *loopInSwap) publishTimeoutTx(ctx context.Context,
} }
witnessFunc := func(sig []byte) (wire.TxWitness, error) { witnessFunc := func(sig []byte) (wire.TxWitness, error) {
return s.htlc.GenTimeoutWitness(sig), nil return s.htlc.GenTimeoutWitness(sig)
} }
sequence := uint32(0) sequence := uint32(0)

@ -7,17 +7,26 @@ import (
"fmt" "fmt"
"github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/schnorr"
"github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcd/wire"
secp "github.com/decred/dcrd/dcrec/secp256k1/v4"
"github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/lntypes"
) )
// ErrNoSharedKey is returned when a script version does not support use of a var (
// shared key. // ErrNoSharedKey is returned when a script version does not support
var ErrNoSharedKey = errors.New("shared key not supported for script version") // use of a shared key.
ErrNoSharedKey = errors.New("shared key not supported for script " +
"version")
// ErrSharedKeyRequired is returned when a script version requires a
// shared key.
ErrSharedKeyRequired = errors.New("shared key required")
)
// HtlcOutputType defines the output type of the htlc that is published. // HtlcOutputType defines the output type of the htlc that is published.
type HtlcOutputType uint8 type HtlcOutputType uint8
@ -29,6 +38,9 @@ const (
// HtlcNP2WSH is a nested pay-to-witness-script-hash output that can be // HtlcNP2WSH is a nested pay-to-witness-script-hash output that can be
// paid to be legacy wallets. // paid to be legacy wallets.
HtlcNP2WSH HtlcNP2WSH
// HtlcP2TR is a pay-to-taproot output with three separate spend paths.
HtlcP2TR
) )
// ScriptVersion defines the HTLC script version. // ScriptVersion defines the HTLC script version.
@ -40,17 +52,21 @@ const (
// HtlcV2 refers to the improved version of the HTLC script. // HtlcV2 refers to the improved version of the HTLC script.
HtlcV2 HtlcV2
// HtlcV3 refers to an upgraded version of HtlcV2 implemented with
// tapscript.
HtlcV3
) )
// htlcScript defines an interface for the different HTLC implementations. // htlcScript defines an interface for the different HTLC implementations.
type HtlcScript interface { type HtlcScript interface {
// genSuccessWitness returns the success script to spend this htlc with // genSuccessWitness returns the success script to spend this htlc with
// the preimage. // the preimage.
genSuccessWitness(receiverSig []byte, preimage lntypes.Preimage) wire.TxWitness genSuccessWitness(receiverSig []byte, preimage lntypes.Preimage) (wire.TxWitness, error)
// GenTimeoutWitness returns the timeout script to spend this htlc after // GenTimeoutWitness returns the timeout script to spend this htlc after
// timeout. // timeout.
GenTimeoutWitness(senderSig []byte) wire.TxWitness GenTimeoutWitness(senderSig []byte) (wire.TxWitness, error)
// IsSuccessWitness checks whether the given stack is valid for // IsSuccessWitness checks whether the given stack is valid for
// redeeming the htlc. // redeeming the htlc.
@ -111,12 +127,17 @@ func (h HtlcOutputType) String() string {
case HtlcNP2WSH: case HtlcNP2WSH:
return "NP2WSH" return "NP2WSH"
case HtlcP2TR:
return "P2TR"
default: default:
return "unknown" return "unknown"
} }
} }
// NewHtlc returns a new instance. // NewHtlc returns a new instance. For V1 and V2 scripts, receiver and sender
// keys are expected to be in ecdsa compressed format. For V3 scripts, keys are
// expected to be raw private key bytes.
func NewHtlc(version ScriptVersion, cltvExpiry int32, func NewHtlc(version ScriptVersion, cltvExpiry int32,
senderKey, receiverKey [33]byte, sharedKey *btcec.PublicKey, senderKey, receiverKey [33]byte, sharedKey *btcec.PublicKey,
hash lntypes.Hash, outputType HtlcOutputType, hash lntypes.Hash, outputType HtlcOutputType,
@ -145,6 +166,14 @@ func NewHtlc(version ScriptVersion, cltvExpiry int32,
htlc, err = newHTLCScriptV2( htlc, err = newHTLCScriptV2(
cltvExpiry, senderKey, receiverKey, hash, cltvExpiry, senderKey, receiverKey, hash,
) )
case HtlcV3:
if sharedKey == nil {
return nil, ErrSharedKeyRequired
}
htlc, err = newHTLCScriptV3(
cltvExpiry, senderKey, receiverKey, sharedKey, hash,
)
default: default:
return nil, ErrInvalidScriptVersion return nil, ErrInvalidScriptVersion
@ -154,18 +183,18 @@ func NewHtlc(version ScriptVersion, cltvExpiry int32,
return nil, err return nil, err
} }
p2wshPkScript, err := input.WitnessScriptHash(htlc.Script())
if err != nil {
return nil, err
}
var pkScript, sigScript []byte var pkScript, sigScript []byte
var address btcutil.Address var address btcutil.Address
switch outputType { switch outputType {
case HtlcNP2WSH: case HtlcNP2WSH:
pks, err := input.WitnessScriptHash(htlc.Script())
if err != nil {
return nil, err
}
// Generate p2sh script for p2wsh (nested). // Generate p2sh script for p2wsh (nested).
p2wshPkScriptHash := sha256.Sum256(p2wshPkScript) p2wshPkScriptHash := sha256.Sum256(pks)
hash160 := input.Ripemd160H(p2wshPkScriptHash[:]) hash160 := input.Ripemd160H(p2wshPkScriptHash[:])
builder := txscript.NewScriptBuilder() builder := txscript.NewScriptBuilder()
@ -184,29 +213,53 @@ func NewHtlc(version ScriptVersion, cltvExpiry int32,
// the p2wsh witness program corresponding to the matching // the p2wsh witness program corresponding to the matching
// public key of this address. // public key of this address.
sigScript, err = txscript.NewScriptBuilder(). sigScript, err = txscript.NewScriptBuilder().
AddData(p2wshPkScript). AddData(pks).
Script() Script()
if err != nil { if err != nil {
return nil, err return nil, err
} }
address, err = btcutil.NewAddressScriptHash( address, err = btcutil.NewAddressScriptHash(
p2wshPkScript, chainParams, pkScript, chainParams,
) )
if err != nil { if err != nil {
return nil, err return nil, err
} }
case HtlcP2WSH: case HtlcP2WSH:
pkScript = p2wshPkScript pkScript, err = input.WitnessScriptHash(htlc.Script())
if err != nil {
return nil, err
}
address, err = btcutil.NewAddressWitnessScriptHash( address, err = btcutil.NewAddressWitnessScriptHash(
p2wshPkScript[2:], pkScript[2:],
chainParams, chainParams,
) )
if err != nil { if err != nil {
return nil, err return nil, err
} }
case HtlcP2TR:
// Confirm we have a v3 htlc.
var trHtlc *HtlcScriptV3
trHtlc, ok := htlc.(*HtlcScriptV3)
if !ok {
return nil, errors.New("taproot output selected for nontaproot htlc")
}
// Generate a tapscript address from our tree.
address, err = btcutil.NewAddressTaproot(
schnorr.SerializePubKey(trHtlc.TaprootKey), &chaincfg.RegressionNetParams,
)
if err != nil {
return nil, err
}
// Generate locking script.
pkScript, err = txscript.PayToAddrScript(address)
if err != nil {
return nil, err
}
default: default:
return nil, errors.New("unknown output type") return nil, errors.New("unknown output type")
} }
@ -232,7 +285,7 @@ func (h *Htlc) GenSuccessWitness(receiverSig []byte,
return nil, errors.New("preimage doesn't match hash") return nil, errors.New("preimage doesn't match hash")
} }
return h.genSuccessWitness(receiverSig, preimage), nil return h.genSuccessWitness(receiverSig, preimage)
} }
// AddSuccessToEstimator adds a successful spend to a weight estimator. // AddSuccessToEstimator adds a successful spend to a weight estimator.
@ -322,26 +375,27 @@ func newHTLCScriptV1(cltvExpiry int32, senderHtlcKey,
// genSuccessWitness returns the success script to spend this htlc with // genSuccessWitness returns the success script to spend this htlc with
// the preimage. // the preimage.
func (h *HtlcScriptV1) genSuccessWitness(receiverSig []byte, func (h *HtlcScriptV1) genSuccessWitness(receiverSig []byte,
preimage lntypes.Preimage) wire.TxWitness { preimage lntypes.Preimage) (wire.TxWitness, error) {
witnessStack := make(wire.TxWitness, 3) witnessStack := make(wire.TxWitness, 3)
witnessStack[0] = append(receiverSig, byte(txscript.SigHashAll)) witnessStack[0] = append(receiverSig, byte(txscript.SigHashAll))
witnessStack[1] = preimage[:] witnessStack[1] = preimage[:]
witnessStack[2] = h.script witnessStack[2] = h.script
return witnessStack return witnessStack, nil
} }
// GenTimeoutWitness returns the timeout script to spend this htlc after // GenTimeoutWitness returns the timeout script to spend this htlc after
// timeout. // timeout.
func (h *HtlcScriptV1) GenTimeoutWitness(senderSig []byte) wire.TxWitness { func (h *HtlcScriptV1) GenTimeoutWitness(
senderSig []byte) (wire.TxWitness, error) {
witnessStack := make(wire.TxWitness, 3) witnessStack := make(wire.TxWitness, 3)
witnessStack[0] = append(senderSig, byte(txscript.SigHashAll)) witnessStack[0] = append(senderSig, byte(txscript.SigHashAll))
witnessStack[1] = []byte{0} witnessStack[1] = []byte{0}
witnessStack[2] = h.script witnessStack[2] = h.script
return witnessStack return witnessStack, nil
} }
// IsSuccessWitness checks whether the given stack is valid for redeeming the // IsSuccessWitness checks whether the given stack is valid for redeeming the
@ -456,14 +510,14 @@ func newHTLCScriptV2(cltvExpiry int32, senderHtlcKey,
// genSuccessWitness returns the success script to spend this htlc with // genSuccessWitness returns the success script to spend this htlc with
// the preimage. // the preimage.
func (h *HtlcScriptV2) genSuccessWitness(receiverSig []byte, func (h *HtlcScriptV2) genSuccessWitness(receiverSig []byte,
preimage lntypes.Preimage) wire.TxWitness { preimage lntypes.Preimage) (wire.TxWitness, error) {
witnessStack := make(wire.TxWitness, 3) witnessStack := make(wire.TxWitness, 3)
witnessStack[0] = preimage[:] witnessStack[0] = preimage[:]
witnessStack[1] = append(receiverSig, byte(txscript.SigHashAll)) witnessStack[1] = append(receiverSig, byte(txscript.SigHashAll))
witnessStack[2] = h.script witnessStack[2] = h.script
return witnessStack return witnessStack, nil
} }
// IsSuccessWitness checks whether the given stack is valid for redeeming the // IsSuccessWitness checks whether the given stack is valid for redeeming the
@ -476,7 +530,8 @@ func (h *HtlcScriptV2) IsSuccessWitness(witness wire.TxWitness) bool {
// GenTimeoutWitness returns the timeout script to spend this htlc after // GenTimeoutWitness returns the timeout script to spend this htlc after
// timeout. // timeout.
func (h *HtlcScriptV2) GenTimeoutWitness(senderSig []byte) wire.TxWitness { func (h *HtlcScriptV2) GenTimeoutWitness(
senderSig []byte) (wire.TxWitness, error) {
witnessStack := make(wire.TxWitness, 4) witnessStack := make(wire.TxWitness, 4)
witnessStack[0] = append(senderSig, byte(txscript.SigHashAll)) witnessStack[0] = append(senderSig, byte(txscript.SigHashAll))
@ -484,7 +539,7 @@ func (h *HtlcScriptV2) GenTimeoutWitness(senderSig []byte) wire.TxWitness {
witnessStack[2] = []byte{} witnessStack[2] = []byte{}
witnessStack[3] = h.script witnessStack[3] = h.script
return witnessStack return witnessStack, nil
} }
// Script returns the htlc script. // Script returns the htlc script.
@ -525,3 +580,207 @@ func (h *HtlcScriptV2) MaxTimeoutWitnessSize() int {
func (h *HtlcScriptV2) SuccessSequence() uint32 { func (h *HtlcScriptV2) SuccessSequence() uint32 {
return 1 return 1
} }
// HtlcScriptV3 encapsulates the htlc v3 script.
type HtlcScriptV3 struct {
timeoutScript []byte
claimScript []byte
TaprootKey *secp.PublicKey
InternalPubKey *secp.PublicKey
SenderKey [33]byte
}
// newHTLCScriptV3 constructs a HtlcScipt with the HTLC V3 taproot script.
func newHTLCScriptV3(cltvExpiry int32, senderHtlcKey,
receiverHtlcKey [33]byte, sharedKey *btcec.PublicKey,
swapHash lntypes.Hash) (*HtlcScriptV3, error) {
// Schnorr keys have implicit sign, remove the sign byte from our
// compressed key.
var schnorrSenderKey, schnorrReceiverKey [32]byte
copy(schnorrSenderKey[:], senderHtlcKey[1:])
copy(schnorrReceiverKey[:], receiverHtlcKey[1:])
// Create our claim path script, we'll use this separately
// later on to generate the claim path leaf.
claimPathScript, err := GenClaimPathScript(schnorrReceiverKey, swapHash)
if err != nil {
return nil, err
}
// Create our timeout path leaf, we'll use this separately
// later on to generate the timeout path leaf.
timeoutPathScript, err := GenTimeoutPathScript(
schnorrSenderKey, int64(cltvExpiry),
)
if err != nil {
return nil, err
}
// Assemble our taproot script tree from our leaves.
tree := txscript.AssembleTaprootScriptTree(
txscript.NewBaseTapLeaf(claimPathScript),
txscript.NewBaseTapLeaf(timeoutPathScript),
)
rootHash := tree.RootNode.TapHash()
// Calculate top level taproot key, using our shared key as the internal
// key.
taprootKey := txscript.ComputeTaprootOutputKey(
sharedKey, rootHash[:],
)
return &HtlcScriptV3{
timeoutScript: timeoutPathScript,
claimScript: claimPathScript,
TaprootKey: taprootKey,
InternalPubKey: sharedKey,
SenderKey: senderHtlcKey,
}, nil
}
// GenTimeoutPathScript constructs an HtlcScript for the timeout payment path.
//
// <senderHtlcKey> OP_CHECKSIGVERIFY <cltvExpiry> OP_CHECKLOCKTIMEVERIFY
func GenTimeoutPathScript(
senderHtlcKey [32]byte, cltvExpiry int64) ([]byte, error) {
builder := txscript.NewScriptBuilder()
builder.AddData(senderHtlcKey[:])
builder.AddOp(txscript.OP_CHECKSIGVERIFY)
builder.AddInt64(cltvExpiry)
builder.AddOp(txscript.OP_CHECKLOCKTIMEVERIFY)
return builder.Script()
}
// GenClaimPathScript constructs an HtlcScript for the claim payment path.
//
// <receiverHtlcKey> OP_CHECKSIGVERIFY
// OP_SIZE 32 OP_EQUALVERIFY
// OP_HASH160 <ripemd160h(swapHash)> OP_EQUALVERIFY
// 1
// OP_CHECKSEQUENCEVERIFY
func GenClaimPathScript(
receiverHtlcKey [32]byte, swapHash lntypes.Hash) ([]byte, error) {
builder := txscript.NewScriptBuilder()
builder.AddData(receiverHtlcKey[:])
builder.AddOp(txscript.OP_CHECKSIGVERIFY)
builder.AddOp(txscript.OP_SIZE)
builder.AddInt64(32)
builder.AddOp(txscript.OP_EQUALVERIFY)
builder.AddOp(txscript.OP_HASH160)
builder.AddData(input.Ripemd160H(swapHash[:]))
builder.AddOp(txscript.OP_EQUALVERIFY)
builder.AddInt64(1)
builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY)
return builder.Script()
}
// genControlBlock constructs the control block with the specified.
func (h *HtlcScriptV3) genControlBlock(leafScript []byte) ([]byte, error) {
var outputKeyYIsOdd bool
if h.TaprootKey.SerializeCompressed()[0] == secp.PubKeyFormatCompressedOdd {
outputKeyYIsOdd = true
}
leaf := txscript.NewBaseTapLeaf(leafScript)
proof := leaf.TapHash()
controlBlock := txscript.ControlBlock{
InternalKey: h.InternalPubKey,
OutputKeyYIsOdd: outputKeyYIsOdd,
LeafVersion: txscript.BaseLeafVersion,
InclusionProof: proof[:],
}
controlBlockBytes, err := controlBlock.ToBytes()
if err != nil {
return nil, err
}
return controlBlockBytes, nil
}
// genSuccessWitness returns the success script to spend this htlc with
// the preimage.
func (h *HtlcScriptV3) genSuccessWitness(
receiverSig []byte, preimage lntypes.Preimage) (wire.TxWitness, error) {
controlBlockBytes, err := h.genControlBlock(h.timeoutScript)
if err != nil {
return nil, err
}
return wire.TxWitness{
preimage[:],
receiverSig,
h.claimScript,
controlBlockBytes,
}, nil
}
// GenTimeoutWitness returns the timeout script to spend this htlc after
// timeout.
func (h *HtlcScriptV3) GenTimeoutWitness(
senderSig []byte) (wire.TxWitness, error) {
controlBlockBytes, err := h.genControlBlock(h.claimScript)
if err != nil {
return nil, err
}
return wire.TxWitness{
senderSig,
h.timeoutScript,
controlBlockBytes,
}, nil
}
// IsSuccessWitness checks whether the given stack is valid for
// redeeming the htlc.
func (h *HtlcScriptV3) IsSuccessWitness(witness wire.TxWitness) bool {
return len(witness) == 4
}
// Script is not implemented, but necessary to conform to interface.
func (h *HtlcScriptV3) Script() []byte {
return nil
}
// MaxSuccessWitnessSize returns the maximum witness size for the
// success case witness.
func (h *HtlcScriptV3) MaxSuccessWitnessSize() int {
// Calculate maximum success witness size
//
// - number_of_witness_elements: 1 byte
// - sig_length: 1 byte
// - sig: 73 bytes
// - preimage_length: 1 byte
// - preimage: 32 bytes
// - witness_script_length: 1 byte
// - witness_script: len(script) bytes
// - control_block_length: 1 byte
// - control_block: 4129 bytes
return 1 + 1 + 73 + 1 + 32 + 1 + len(h.claimScript) + 1 + 4129
}
// MaxTimeoutWitnessSize returns the maximum witness size for the
// timeout case witness.
func (h *HtlcScriptV3) MaxTimeoutWitnessSize() int {
// Calculate maximum timeout witness size
//
// - number_of_witness_elements: 1 byte
// - sig_length: 1 byte
// - sig: 73 bytes
// - witness_script_length: 1 byte
// - witness_script: len(script) bytes
// - control_block_length: 1 byte
// - control_block: 4129 bytes
return 1 + 1 + 73 + 1 + len(h.timeoutScript) + 1 + 4129
}
// SuccessSequence returns the sequence to spend this htlc in the
// success case.
func (h *HtlcScriptV3) SuccessSequence() uint32 {
return 1
}

@ -7,11 +7,13 @@ import (
"testing" "testing"
"github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/schnorr"
"github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcd/wire"
secp "github.com/decred/dcrd/dcrec/secp256k1/v4"
"github.com/lightninglabs/loop/test" "github.com/lightninglabs/loop/test"
"github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/keychain"
@ -101,6 +103,8 @@ func TestHtlcV2(t *testing.T) {
var ( var (
testPreimage = lntypes.Preimage([32]byte{1, 2, 3}) testPreimage = lntypes.Preimage([32]byte{1, 2, 3})
err error err error
receiverKey [33]byte
senderKey [33]byte
) )
// We generate a fake output, and the corresponding txin. This output // We generate a fake output, and the corresponding txin. This output
@ -125,12 +129,8 @@ func TestHtlcV2(t *testing.T) {
senderPrivKey, senderPubKey := test.CreateKey(1) senderPrivKey, senderPubKey := test.CreateKey(1)
receiverPrivKey, receiverPubKey := test.CreateKey(2) receiverPrivKey, receiverPubKey := test.CreateKey(2)
var (
senderKey [33]byte
receiverKey [33]byte
)
copy(senderKey[:], senderPubKey.SerializeCompressed())
copy(receiverKey[:], receiverPubKey.SerializeCompressed()) copy(receiverKey[:], receiverPubKey.SerializeCompressed())
copy(senderKey[:], senderPubKey.SerializeCompressed())
hash := sha256.Sum256(testPreimage[:]) hash := sha256.Sum256(testPreimage[:])
@ -155,6 +155,9 @@ func TestHtlcV2(t *testing.T) {
receiverSigner := &input.MockSigner{ receiverSigner := &input.MockSigner{
Privkeys: []*btcec.PrivateKey{receiverPrivKey}, Privkeys: []*btcec.PrivateKey{receiverPrivKey},
} }
prevOutFetcher := txscript.NewCannedPrevOutputFetcher(
htlc.PkScript, 800_000,
)
signTx := func(tx *wire.MsgTx, pubkey *btcec.PublicKey, signTx := func(tx *wire.MsgTx, pubkey *btcec.PublicKey,
signer *input.MockSigner) (input.Signature, error) { signer *input.MockSigner) (input.Signature, error) {
@ -167,8 +170,10 @@ func TestHtlcV2(t *testing.T) {
WitnessScript: htlc.Script(), WitnessScript: htlc.Script(),
Output: htlcOutput, Output: htlcOutput,
HashType: txscript.SigHashAll, HashType: txscript.SigHashAll,
SigHashes: txscript.NewTxSigHashes(tx), SigHashes: txscript.NewTxSigHashes(
InputIndex: 0, tx, prevOutFetcher,
),
InputIndex: 0,
} }
return signer.SignOutputRaw(tx, signDesc) return signer.SignOutputRaw(tx, signDesc)
@ -227,9 +232,12 @@ func TestHtlcV2(t *testing.T) {
) )
require.NoError(t, err) require.NoError(t, err)
return htlc.GenTimeoutWitness( witness, err := htlc.GenTimeoutWitness(
sweepSig.Serialize(), sweepSig.Serialize(),
) )
require.NoError(t, err)
return witness
}, false, }, false,
}, },
{ {
@ -242,9 +250,12 @@ func TestHtlcV2(t *testing.T) {
) )
require.NoError(t, err) require.NoError(t, err)
return htlc.GenTimeoutWitness( witness, err := htlc.GenTimeoutWitness(
sweepSig.Serialize(), sweepSig.Serialize(),
) )
require.NoError(t, err)
return witness
}, true, }, true,
}, },
{ {
@ -257,9 +268,12 @@ func TestHtlcV2(t *testing.T) {
) )
require.NoError(t, err) require.NoError(t, err)
return htlc.GenTimeoutWitness( witness, err := htlc.GenTimeoutWitness(
sweepSig.Serialize(), sweepSig.Serialize(),
) )
require.NoError(t, err)
return witness
}, false, }, false,
}, },
{ {
@ -272,7 +286,7 @@ func TestHtlcV2(t *testing.T) {
// Create the htlc with the bogus key. // Create the htlc with the bogus key.
htlc, err = NewHtlc( htlc, err = NewHtlc(
HtlcV2, testCltvExpiry, HtlcV2, testCltvExpiry,
bogusKey, receiverKey, hash, bogusKey, receiverKey, nil, hash,
HtlcP2WSH, &chaincfg.MainNetParams, HtlcP2WSH, &chaincfg.MainNetParams,
) )
require.NoError(t, err) require.NoError(t, err)
@ -289,9 +303,12 @@ func TestHtlcV2(t *testing.T) {
) )
require.NoError(t, err) require.NoError(t, err)
return htlc.GenTimeoutWitness( witness, err := htlc.GenTimeoutWitness(
sweepSig.Serialize(), sweepSig.Serialize(),
) )
require.NoError(t, err)
return witness
}, false, }, false,
}, },
} }
@ -306,8 +323,281 @@ func TestHtlcV2(t *testing.T) {
return txscript.NewEngine( return txscript.NewEngine(
htlc.PkScript, sweepTx, 0, htlc.PkScript, sweepTx, 0,
txscript.StandardVerifyFlags, nil, txscript.StandardVerifyFlags, nil,
nil, int64(htlcValue), nil, int64(htlcValue), prevOutFetcher,
)
}
assertEngineExecution(t, testCase.valid, newEngine)
})
}
}
// TestHtlcV3 tests the HTLC V3 script success and timeout spend cases.
// TODO: update tests to use private keys, not public keys and use non-nil
// shared key.
func TestHtlcV3(t *testing.T) {
preimage := [32]byte{1, 2, 3}
p := lntypes.Preimage(preimage)
hashedPreimage := sha256.Sum256(p[:])
value := int64(800_000 - 500) // TODO(guggero): Calculate actual fee.
senderPrivKey := [33]byte{
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, byte(1),
}
receiverPrivKey := [33]byte{
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, byte(2),
}
cltvExpiry := int32(10)
htlc, err := NewHtlc(
HtlcV3, cltvExpiry, senderPrivKey, receiverPrivKey, nil,
hashedPreimage, HtlcP2TR, &chaincfg.MainNetParams,
)
require.NoError(t, err)
var trAddress *btcutil.AddressTaproot
trAddress, ok := htlc.Address.(*btcutil.AddressTaproot)
require.True(t, ok)
p2trPkScript, err := txscript.PayToAddrScript(trAddress)
require.NoError(t, err)
tx := wire.NewMsgTx(2)
tx.TxIn = []*wire.TxIn{{
PreviousOutPoint: wire.OutPoint{
Hash: chainhash.Hash(sha256.Sum256([]byte{1, 2, 3})),
Index: 50,
},
}}
tx.TxOut = []*wire.TxOut{{
PkScript: []byte{0, 20, 2, 141, 221, 230, 144, 171, 89, 230, 219, 198, 90, 157, 110, 89, 89, 67, 128, 16, 150, 186},
Value: value,
}}
prevOutFetcher := txscript.NewCannedPrevOutputFetcher(
p2trPkScript, 800_000,
)
hashCache := txscript.NewTxSigHashes(
tx, prevOutFetcher,
)
signTx := func(
tx *wire.MsgTx, privateKey *secp.PrivateKey, leaf txscript.TapLeaf) []byte {
sig, err := txscript.RawTxInTapscriptSignature(
tx, hashCache, 0, value, p2trPkScript, leaf,
txscript.SigHashDefault, privateKey,
)
require.NoError(t, err)
return sig
}
testCases := []struct {
name string
witness func(*testing.T) wire.TxWitness
valid bool
}{
{
// Receiver can spend with valid preimage.
"success case spend with valid preimage",
func(t *testing.T) wire.TxWitness {
tx.TxIn[0].Sequence = htlc.SuccessSequence()
tx.LockTime = uint32(cltvExpiry)
var trHtlc *HtlcScriptV3
trHtlc, ok := htlc.HtlcScript.(*HtlcScriptV3)
require.True(t, ok)
senderPrivKey, _ := btcec.PrivKeyFromBytes(
senderPrivKey[:],
)
sig := signTx(
tx, senderPrivKey, txscript.NewBaseTapLeaf(trHtlc.claimScript),
) )
witness, err := htlc.genSuccessWitness(
sig, preimage,
)
require.NoError(t, err)
return witness
}, true,
},
{
// Receiver can't spend with the valid preimage and with
// zero sequence.
"success case no spend with valid preimage and zero sequence",
func(t *testing.T) wire.TxWitness {
tx.TxIn[0].Sequence = 0
var trHtlc *HtlcScriptV3
trHtlc, ok := htlc.HtlcScript.(*HtlcScriptV3)
require.True(t, ok)
senderPrivKey, _ := btcec.PrivKeyFromBytes(
senderPrivKey[:],
)
sig := signTx(
tx, senderPrivKey, txscript.NewBaseTapLeaf(trHtlc.claimScript),
)
witness, err := htlc.genSuccessWitness(
sig, preimage,
)
require.NoError(t, err)
return witness
}, false,
},
{
// Sender can't spend when haven't yet timed out.
"timeout case no spend before timeout",
func(t *testing.T) wire.TxWitness {
tx.TxIn[0].Sequence = htlc.SuccessSequence()
tx.LockTime = uint32(cltvExpiry) - 1
var trHtlc *HtlcScriptV3
trHtlc, ok := htlc.HtlcScript.(*HtlcScriptV3)
require.True(t, ok)
receiverPrivKey, _ := btcec.PrivKeyFromBytes(
receiverPrivKey[:],
)
sig := signTx(
tx, receiverPrivKey, txscript.NewBaseTapLeaf(trHtlc.timeoutScript),
)
witness, err := htlc.GenTimeoutWitness(sig)
require.NoError(t, err)
return witness
}, false,
},
{
// Sender can spend after timeout.
"timeout case spend after timeout",
func(t *testing.T) wire.TxWitness {
tx.TxIn[0].Sequence = htlc.SuccessSequence()
tx.LockTime = uint32(cltvExpiry)
var trHtlc *HtlcScriptV3
trHtlc, ok := htlc.HtlcScript.(*HtlcScriptV3)
require.True(t, ok)
receiverPrivKey, _ := btcec.PrivKeyFromBytes(
receiverPrivKey[:],
)
sig := signTx(
tx, receiverPrivKey, txscript.NewBaseTapLeaf(trHtlc.timeoutScript),
)
witness, err := htlc.GenTimeoutWitness(sig)
require.NoError(t, err)
return witness
}, true,
},
{
// Receiver can't spend after timeout.
"timeout case receiver cannot spend",
func(t *testing.T) wire.TxWitness {
tx.TxIn[0].Sequence = htlc.SuccessSequence()
tx.LockTime = uint32(cltvExpiry)
var trHtlc *HtlcScriptV3
trHtlc, ok := htlc.HtlcScript.(*HtlcScriptV3)
require.True(t, ok)
senderPrivKey, _ := btcec.PrivKeyFromBytes(
senderPrivKey[:],
)
sig := signTx(
tx, senderPrivKey, txscript.NewBaseTapLeaf(trHtlc.timeoutScript),
)
witness, err := htlc.GenTimeoutWitness(sig)
require.NoError(t, err)
return witness
}, false,
},
{
// Sender can't spend after timeout with wrong sender
// key.
"timeout case cannot spend with wrong key",
func(t *testing.T) wire.TxWitness {
bogusKey := [33]byte{0xb, 0xa, 0xd}
senderPrivKey, senderPubKey := btcec.PrivKeyFromBytes(
senderPrivKey[:],
)
var shnorrSenderKey [32]byte
copy(
shnorrSenderKey[:],
schnorr.SerializePubKey(senderPubKey),
)
htlc, err := NewHtlc(
HtlcV3, cltvExpiry, bogusKey,
receiverPrivKey, nil,
hashedPreimage, HtlcP2TR,
&chaincfg.MainNetParams,
)
require.NoError(t, err)
var trAddress *btcutil.AddressTaproot
trAddress, ok := htlc.Address.(*btcutil.AddressTaproot)
require.True(t, ok)
p2trPkScript, err := txscript.PayToAddrScript(trAddress)
require.NoError(t, err)
prevOutFetcher := txscript.NewCannedPrevOutputFetcher(
p2trPkScript, 800_000,
)
hashCache = txscript.NewTxSigHashes(
tx, prevOutFetcher,
)
timeoutScript, err := GenTimeoutPathScript(
shnorrSenderKey, int64(cltvExpiry),
)
require.NoError(t, err)
sig := signTx(
tx, senderPrivKey, txscript.NewBaseTapLeaf(timeoutScript),
)
witness, err := htlc.genSuccessWitness(
sig, preimage,
)
require.NoError(t, err)
return witness
}, false,
},
}
for _, testCase := range testCases {
testCase := testCase
t.Run(testCase.name, func(t *testing.T) {
tx.TxIn[0].Witness = testCase.witness(t)
newEngine := func() (*txscript.Engine, error) {
return txscript.NewEngine(
p2trPkScript, tx, 0,
txscript.StandardVerifyFlags, nil,
hashCache, value, prevOutFetcher)
} }
assertEngineExecution(t, testCase.valid, newEngine) assertEngineExecution(t, testCase.valid, newEngine)

Loading…
Cancel
Save