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/coreos/bbolt v1.3.3
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/grpc-ecosystem/grpc-gateway/v2 v2.5.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/loop/swapserverrpc v1.0.0
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.3 h1:v9QZf2Sn6AmjXtQeFpdoq/eaNtYP6IN+7lcrygsIAtg=
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.20220325093943-42b9d4c1be7f/go.mod h1:lDjRKhndRH0CzZQ2m8dWODdqp/ejEW7esb2u2nlvrw4=
github.com/lightninglabs/aperture v0.1.17-beta.0.20220328072456-4a2632d0be38 h1:ht1wzuSmScXJEnW30Cp2vUBHYzJZvme5DHxNMyg+LT0=
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/go.mod h1:vxmQPeIQxPf6Jf9rM8R+B4rKBqLA2AjttNxkFBL2Plk=
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) {
return s.htlc.GenTimeoutWitness(sig), nil
return s.htlc.GenTimeoutWitness(sig)
}
sequence := uint32(0)

@ -7,17 +7,26 @@ import (
"fmt"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/schnorr"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
secp "github.com/decred/dcrd/dcrec/secp256k1/v4"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/lntypes"
)
// ErrNoSharedKey is returned when a script version does not support use of a
// shared key.
var ErrNoSharedKey = errors.New("shared key not supported for script version")
var (
// ErrNoSharedKey is returned when a script version does not support
// 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.
type HtlcOutputType uint8
@ -29,6 +38,9 @@ const (
// HtlcNP2WSH is a nested pay-to-witness-script-hash output that can be
// paid to be legacy wallets.
HtlcNP2WSH
// HtlcP2TR is a pay-to-taproot output with three separate spend paths.
HtlcP2TR
)
// ScriptVersion defines the HTLC script version.
@ -40,17 +52,21 @@ const (
// HtlcV2 refers to the improved version of the HTLC script.
HtlcV2
// HtlcV3 refers to an upgraded version of HtlcV2 implemented with
// tapscript.
HtlcV3
)
// htlcScript defines an interface for the different HTLC implementations.
type HtlcScript interface {
// genSuccessWitness returns the success script to spend this htlc with
// 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
// timeout.
GenTimeoutWitness(senderSig []byte) wire.TxWitness
GenTimeoutWitness(senderSig []byte) (wire.TxWitness, error)
// IsSuccessWitness checks whether the given stack is valid for
// redeeming the htlc.
@ -111,12 +127,17 @@ func (h HtlcOutputType) String() string {
case HtlcNP2WSH:
return "NP2WSH"
case HtlcP2TR:
return "P2TR"
default:
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,
senderKey, receiverKey [33]byte, sharedKey *btcec.PublicKey,
hash lntypes.Hash, outputType HtlcOutputType,
@ -145,6 +166,14 @@ func NewHtlc(version ScriptVersion, cltvExpiry int32,
htlc, err = newHTLCScriptV2(
cltvExpiry, senderKey, receiverKey, hash,
)
case HtlcV3:
if sharedKey == nil {
return nil, ErrSharedKeyRequired
}
htlc, err = newHTLCScriptV3(
cltvExpiry, senderKey, receiverKey, sharedKey, hash,
)
default:
return nil, ErrInvalidScriptVersion
@ -154,18 +183,18 @@ func NewHtlc(version ScriptVersion, cltvExpiry int32,
return nil, err
}
p2wshPkScript, err := input.WitnessScriptHash(htlc.Script())
if err != nil {
return nil, err
}
var pkScript, sigScript []byte
var address btcutil.Address
switch outputType {
case HtlcNP2WSH:
pks, err := input.WitnessScriptHash(htlc.Script())
if err != nil {
return nil, err
}
// Generate p2sh script for p2wsh (nested).
p2wshPkScriptHash := sha256.Sum256(p2wshPkScript)
p2wshPkScriptHash := sha256.Sum256(pks)
hash160 := input.Ripemd160H(p2wshPkScriptHash[:])
builder := txscript.NewScriptBuilder()
@ -184,29 +213,53 @@ func NewHtlc(version ScriptVersion, cltvExpiry int32,
// the p2wsh witness program corresponding to the matching
// public key of this address.
sigScript, err = txscript.NewScriptBuilder().
AddData(p2wshPkScript).
AddData(pks).
Script()
if err != nil {
return nil, err
}
address, err = btcutil.NewAddressScriptHash(
p2wshPkScript, chainParams,
pkScript, chainParams,
)
if err != nil {
return nil, err
}
case HtlcP2WSH:
pkScript = p2wshPkScript
pkScript, err = input.WitnessScriptHash(htlc.Script())
if err != nil {
return nil, err
}
address, err = btcutil.NewAddressWitnessScriptHash(
p2wshPkScript[2:],
pkScript[2:],
chainParams,
)
if err != nil {
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:
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 h.genSuccessWitness(receiverSig, preimage), nil
return h.genSuccessWitness(receiverSig, preimage)
}
// 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
// the preimage.
func (h *HtlcScriptV1) genSuccessWitness(receiverSig []byte,
preimage lntypes.Preimage) wire.TxWitness {
preimage lntypes.Preimage) (wire.TxWitness, error) {
witnessStack := make(wire.TxWitness, 3)
witnessStack[0] = append(receiverSig, byte(txscript.SigHashAll))
witnessStack[1] = preimage[:]
witnessStack[2] = h.script
return witnessStack
return witnessStack, nil
}
// GenTimeoutWitness returns the timeout script to spend this htlc after
// timeout.
func (h *HtlcScriptV1) GenTimeoutWitness(senderSig []byte) wire.TxWitness {
func (h *HtlcScriptV1) GenTimeoutWitness(
senderSig []byte) (wire.TxWitness, error) {
witnessStack := make(wire.TxWitness, 3)
witnessStack[0] = append(senderSig, byte(txscript.SigHashAll))
witnessStack[1] = []byte{0}
witnessStack[2] = h.script
return witnessStack
return witnessStack, nil
}
// 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
// the preimage.
func (h *HtlcScriptV2) genSuccessWitness(receiverSig []byte,
preimage lntypes.Preimage) wire.TxWitness {
preimage lntypes.Preimage) (wire.TxWitness, error) {
witnessStack := make(wire.TxWitness, 3)
witnessStack[0] = preimage[:]
witnessStack[1] = append(receiverSig, byte(txscript.SigHashAll))
witnessStack[2] = h.script
return witnessStack
return witnessStack, nil
}
// 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
// timeout.
func (h *HtlcScriptV2) GenTimeoutWitness(senderSig []byte) wire.TxWitness {
func (h *HtlcScriptV2) GenTimeoutWitness(
senderSig []byte) (wire.TxWitness, error) {
witnessStack := make(wire.TxWitness, 4)
witnessStack[0] = append(senderSig, byte(txscript.SigHashAll))
@ -484,7 +539,7 @@ func (h *HtlcScriptV2) GenTimeoutWitness(senderSig []byte) wire.TxWitness {
witnessStack[2] = []byte{}
witnessStack[3] = h.script
return witnessStack
return witnessStack, nil
}
// Script returns the htlc script.
@ -525,3 +580,207 @@ func (h *HtlcScriptV2) MaxTimeoutWitnessSize() int {
func (h *HtlcScriptV2) SuccessSequence() uint32 {
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"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/schnorr"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
secp "github.com/decred/dcrd/dcrec/secp256k1/v4"
"github.com/lightninglabs/loop/test"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/keychain"
@ -101,6 +103,8 @@ func TestHtlcV2(t *testing.T) {
var (
testPreimage = lntypes.Preimage([32]byte{1, 2, 3})
err error
receiverKey [33]byte
senderKey [33]byte
)
// 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)
receiverPrivKey, receiverPubKey := test.CreateKey(2)
var (
senderKey [33]byte
receiverKey [33]byte
)
copy(senderKey[:], senderPubKey.SerializeCompressed())
copy(receiverKey[:], receiverPubKey.SerializeCompressed())
copy(senderKey[:], senderPubKey.SerializeCompressed())
hash := sha256.Sum256(testPreimage[:])
@ -155,6 +155,9 @@ func TestHtlcV2(t *testing.T) {
receiverSigner := &input.MockSigner{
Privkeys: []*btcec.PrivateKey{receiverPrivKey},
}
prevOutFetcher := txscript.NewCannedPrevOutputFetcher(
htlc.PkScript, 800_000,
)
signTx := func(tx *wire.MsgTx, pubkey *btcec.PublicKey,
signer *input.MockSigner) (input.Signature, error) {
@ -167,8 +170,10 @@ func TestHtlcV2(t *testing.T) {
WitnessScript: htlc.Script(),
Output: htlcOutput,
HashType: txscript.SigHashAll,
SigHashes: txscript.NewTxSigHashes(tx),
InputIndex: 0,
SigHashes: txscript.NewTxSigHashes(
tx, prevOutFetcher,
),
InputIndex: 0,
}
return signer.SignOutputRaw(tx, signDesc)
@ -227,9 +232,12 @@ func TestHtlcV2(t *testing.T) {
)
require.NoError(t, err)
return htlc.GenTimeoutWitness(
witness, err := htlc.GenTimeoutWitness(
sweepSig.Serialize(),
)
require.NoError(t, err)
return witness
}, false,
},
{
@ -242,9 +250,12 @@ func TestHtlcV2(t *testing.T) {
)
require.NoError(t, err)
return htlc.GenTimeoutWitness(
witness, err := htlc.GenTimeoutWitness(
sweepSig.Serialize(),
)
require.NoError(t, err)
return witness
}, true,
},
{
@ -257,9 +268,12 @@ func TestHtlcV2(t *testing.T) {
)
require.NoError(t, err)
return htlc.GenTimeoutWitness(
witness, err := htlc.GenTimeoutWitness(
sweepSig.Serialize(),
)
require.NoError(t, err)
return witness
}, false,
},
{
@ -272,7 +286,7 @@ func TestHtlcV2(t *testing.T) {
// Create the htlc with the bogus key.
htlc, err = NewHtlc(
HtlcV2, testCltvExpiry,
bogusKey, receiverKey, hash,
bogusKey, receiverKey, nil, hash,
HtlcP2WSH, &chaincfg.MainNetParams,
)
require.NoError(t, err)
@ -289,9 +303,12 @@ func TestHtlcV2(t *testing.T) {
)
require.NoError(t, err)
return htlc.GenTimeoutWitness(
witness, err := htlc.GenTimeoutWitness(
sweepSig.Serialize(),
)
require.NoError(t, err)
return witness
}, false,
},
}
@ -306,8 +323,281 @@ func TestHtlcV2(t *testing.T) {
return txscript.NewEngine(
htlc.PkScript, sweepTx, 0,
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)

Loading…
Cancel
Save