mirror of
https://github.com/lightninglabs/loop
synced 2024-11-04 06:00:21 +00:00
bdb4b773ed
This commit is a refactor of how we construct htlcs to make it possible to pass in internal keys for the sender and receiver when creating P2TR htlcs. Furthermore the commit also cleans up constructors to not pass in script versions and output types to make the code more readable.
603 lines
14 KiB
Go
603 lines
14 KiB
Go
package swap
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/sha256"
|
|
"fmt"
|
|
"testing"
|
|
|
|
"github.com/btcsuite/btcd/btcec/v2"
|
|
"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"
|
|
"github.com/lightningnetwork/lnd/lntypes"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// assertEngineExecution executes the VM returned by the newEngine closure,
|
|
// asserting the result matches the validity expectation. In the case where it
|
|
// doesn't match the expectation, it executes the script step-by-step and
|
|
// prints debug information to stdout.
|
|
// This code is adopted from: lnd/input/script_utils_test.go .
|
|
func assertEngineExecution(t *testing.T, valid bool,
|
|
newEngine func() (*txscript.Engine, error)) {
|
|
|
|
t.Helper()
|
|
|
|
// Get a new VM to execute.
|
|
vm, err := newEngine()
|
|
require.NoError(t, err, "unable to create engine")
|
|
|
|
// Execute the VM, only go on to the step-by-step execution if it
|
|
// doesn't validate as expected.
|
|
vmErr := vm.Execute()
|
|
executionValid := vmErr == nil
|
|
if valid == executionValid {
|
|
return
|
|
}
|
|
|
|
// Now that the execution didn't match what we expected, fetch a new VM
|
|
// to step through.
|
|
vm, err = newEngine()
|
|
require.NoError(t, err, "unable to create engine")
|
|
|
|
// This buffer will trace execution of the Script, dumping out to
|
|
// stdout.
|
|
var debugBuf bytes.Buffer
|
|
|
|
done := false
|
|
for !done {
|
|
dis, err := vm.DisasmPC()
|
|
if err != nil {
|
|
t.Fatalf("stepping (%v)\n", err)
|
|
}
|
|
debugBuf.WriteString(fmt.Sprintf("stepping %v\n", dis))
|
|
|
|
done, err = vm.Step()
|
|
if err != nil && valid {
|
|
fmt.Println(debugBuf.String())
|
|
t.Fatalf("spend test case failed, spend "+
|
|
"should be valid: %v", err)
|
|
} else if err == nil && !valid && done {
|
|
fmt.Println(debugBuf.String())
|
|
t.Fatalf("spend test case succeed, spend "+
|
|
"should be invalid: %v", err)
|
|
}
|
|
|
|
debugBuf.WriteString(
|
|
fmt.Sprintf("Stack: %v", vm.GetStack()),
|
|
)
|
|
debugBuf.WriteString(
|
|
fmt.Sprintf("AltStack: %v", vm.GetAltStack()),
|
|
)
|
|
}
|
|
|
|
// If we get to this point the unexpected case was not reached
|
|
// during step execution, which happens for some checks, like
|
|
// the clean-stack rule.
|
|
validity := "invalid"
|
|
if valid {
|
|
validity = "valid"
|
|
}
|
|
|
|
fmt.Println(debugBuf.String())
|
|
t.Fatalf(
|
|
"%v spend test case execution ended with: %v", validity, vmErr,
|
|
)
|
|
}
|
|
|
|
// TestHtlcV2 tests the HTLC V2 script success and timeout spend cases.
|
|
func TestHtlcV2(t *testing.T) {
|
|
const (
|
|
htlcValue = btcutil.Amount(1 * 10e8)
|
|
testCltvExpiry = 24
|
|
)
|
|
|
|
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
|
|
// doesn't need to exist, as we'll only be validating spending from the
|
|
// transaction that references this.
|
|
fundingOut := &wire.OutPoint{
|
|
Hash: chainhash.Hash(sha256.Sum256([]byte{1, 2, 3})),
|
|
Index: 50,
|
|
}
|
|
fakeFundingTxIn := wire.NewTxIn(fundingOut, nil, nil)
|
|
|
|
sweepTx := wire.NewMsgTx(2)
|
|
sweepTx.AddTxIn(fakeFundingTxIn)
|
|
sweepTx.AddTxOut(
|
|
&wire.TxOut{
|
|
PkScript: []byte("doesn't matter"),
|
|
Value: int64(htlcValue),
|
|
},
|
|
)
|
|
|
|
// Create sender and receiver keys.
|
|
senderPrivKey, senderPubKey := test.CreateKey(1)
|
|
receiverPrivKey, receiverPubKey := test.CreateKey(2)
|
|
|
|
copy(receiverKey[:], receiverPubKey.SerializeCompressed())
|
|
copy(senderKey[:], senderPubKey.SerializeCompressed())
|
|
|
|
hash := sha256.Sum256(testPreimage[:])
|
|
|
|
// Create the htlc.
|
|
htlc, err := NewHtlcV2(
|
|
testCltvExpiry, senderKey, receiverKey, hash,
|
|
&chaincfg.MainNetParams,
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
// Create the htlc output we'll try to spend.
|
|
htlcOutput := &wire.TxOut{
|
|
Value: int64(htlcValue),
|
|
PkScript: htlc.PkScript,
|
|
}
|
|
|
|
// Create signers for sender and receiver.
|
|
senderSigner := &input.MockSigner{
|
|
Privkeys: []*btcec.PrivateKey{senderPrivKey},
|
|
}
|
|
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, witnessScript []byte) (
|
|
input.Signature, error) {
|
|
|
|
signDesc := &input.SignDescriptor{
|
|
KeyDesc: keychain.KeyDescriptor{
|
|
PubKey: pubkey,
|
|
},
|
|
|
|
WitnessScript: witnessScript,
|
|
Output: htlcOutput,
|
|
HashType: htlc.SigHash(),
|
|
SigHashes: txscript.NewTxSigHashes(
|
|
tx, prevOutFetcher,
|
|
),
|
|
InputIndex: 0,
|
|
}
|
|
|
|
return signer.SignOutputRaw(tx, signDesc)
|
|
}
|
|
|
|
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 {
|
|
sweepTx.TxIn[0].Sequence = htlc.SuccessSequence()
|
|
sweepSig, err := signTx(
|
|
sweepTx, receiverPubKey, receiverSigner,
|
|
htlc.SuccessScript(),
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
witness, err := htlc.GenSuccessWitness(
|
|
sweepSig.Serialize(), testPreimage,
|
|
)
|
|
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 {
|
|
sweepTx.TxIn[0].Sequence = 0
|
|
sweepSig, err := signTx(
|
|
sweepTx, receiverPubKey, receiverSigner,
|
|
htlc.SuccessScript(),
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
witness, err := htlc.GenSuccessWitness(
|
|
sweepSig.Serialize(), testPreimage,
|
|
)
|
|
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 {
|
|
sweepTx.LockTime = testCltvExpiry - 1
|
|
sweepSig, err := signTx(
|
|
sweepTx, senderPubKey, senderSigner,
|
|
htlc.TimeoutScript(),
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
witness, err := htlc.GenTimeoutWitness(
|
|
sweepSig.Serialize(),
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
return witness
|
|
}, false,
|
|
},
|
|
{
|
|
// Sender can spend after timeout.
|
|
"timeout case spend after timeout",
|
|
func(t *testing.T) wire.TxWitness {
|
|
sweepTx.LockTime = testCltvExpiry
|
|
sweepSig, err := signTx(
|
|
sweepTx, senderPubKey, senderSigner,
|
|
htlc.TimeoutScript(),
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
witness, err := htlc.GenTimeoutWitness(
|
|
sweepSig.Serialize(),
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
return witness
|
|
}, true,
|
|
},
|
|
{
|
|
// Receiver can't spend via timeout path.
|
|
"timeout case receiver cannot spend",
|
|
func(t *testing.T) wire.TxWitness {
|
|
sweepTx.LockTime = testCltvExpiry
|
|
sweepSig, err := signTx(
|
|
sweepTx, receiverPubKey, receiverSigner,
|
|
htlc.TimeoutScript(),
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
witness, err := htlc.GenTimeoutWitness(
|
|
sweepSig.Serialize(),
|
|
)
|
|
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}
|
|
|
|
// Create the htlc with the bogus key.
|
|
htlc, err = NewHtlcV2(
|
|
testCltvExpiry, bogusKey, receiverKey,
|
|
hash, &chaincfg.MainNetParams,
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
// Create the htlc output we'll try to spend.
|
|
htlcOutput = &wire.TxOut{
|
|
Value: int64(htlcValue),
|
|
PkScript: htlc.PkScript,
|
|
}
|
|
|
|
sweepTx.LockTime = testCltvExpiry
|
|
sweepSig, err := signTx(
|
|
sweepTx, senderPubKey, senderSigner,
|
|
htlc.TimeoutScript(),
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
witness, err := htlc.GenTimeoutWitness(
|
|
sweepSig.Serialize(),
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
return witness
|
|
}, false,
|
|
},
|
|
}
|
|
|
|
for _, testCase := range testCases {
|
|
testCase := testCase
|
|
|
|
t.Run(testCase.name, func(t *testing.T) {
|
|
sweepTx.TxIn[0].Witness = testCase.witness(t)
|
|
|
|
newEngine := func() (*txscript.Engine, error) {
|
|
return txscript.NewEngine(
|
|
htlc.PkScript, sweepTx, 0,
|
|
txscript.StandardVerifyFlags, nil,
|
|
nil, int64(htlcValue), prevOutFetcher,
|
|
)
|
|
}
|
|
|
|
assertEngineExecution(t, testCase.valid, newEngine)
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestHtlcV3 tests the HTLC V3 script success and timeout spend cases.
|
|
func TestHtlcV3(t *testing.T) {
|
|
var (
|
|
receiverKey [33]byte
|
|
senderKey [33]byte
|
|
)
|
|
|
|
preimage := [32]byte{1, 2, 3}
|
|
p := lntypes.Preimage(preimage)
|
|
hashedPreimage := sha256.Sum256(p[:])
|
|
|
|
value := int64(800_000)
|
|
|
|
cltvExpiry := int32(10)
|
|
|
|
senderPrivKey, senderPubKey := test.CreateKey(1)
|
|
receiverPrivKey, receiverPubKey := test.CreateKey(2)
|
|
|
|
copy(receiverKey[:], receiverPubKey.SerializeCompressed())
|
|
copy(senderKey[:], senderPubKey.SerializeCompressed())
|
|
|
|
htlc, err := NewHtlcV3(
|
|
cltvExpiry, senderKey, receiverKey, senderKey, receiverKey,
|
|
hashedPreimage, &chaincfg.MainNetParams,
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
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, value,
|
|
)
|
|
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,
|
|
htlc.SigHash(), 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)
|
|
|
|
trHtlc, ok := htlc.HtlcScript.(*HtlcScriptV3)
|
|
require.True(t, ok)
|
|
|
|
sig := signTx(
|
|
tx, receiverPrivKey,
|
|
txscript.NewBaseTapLeaf(
|
|
trHtlc.SuccessScript(),
|
|
),
|
|
)
|
|
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
|
|
|
|
trHtlc, ok := htlc.HtlcScript.(*HtlcScriptV3)
|
|
require.True(t, ok)
|
|
|
|
sig := signTx(
|
|
tx, receiverPrivKey,
|
|
txscript.NewBaseTapLeaf(
|
|
trHtlc.SuccessScript(),
|
|
),
|
|
)
|
|
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
|
|
|
|
trHtlc, ok := htlc.HtlcScript.(*HtlcScriptV3)
|
|
require.True(t, ok)
|
|
|
|
sig := signTx(
|
|
tx, senderPrivKey,
|
|
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)
|
|
|
|
trHtlc, ok := htlc.HtlcScript.(*HtlcScriptV3)
|
|
require.True(t, ok)
|
|
|
|
sig := signTx(
|
|
tx, senderPrivKey,
|
|
txscript.NewBaseTapLeaf(
|
|
trHtlc.TimeoutScript(),
|
|
),
|
|
)
|
|
|
|
witness, err := htlc.GenTimeoutWitness(sig)
|
|
require.NoError(t, err)
|
|
|
|
return witness
|
|
}, true,
|
|
},
|
|
{
|
|
// Receiver can't spend via timeout path.
|
|
"timeout case receiver cannot spend",
|
|
func(t *testing.T) wire.TxWitness {
|
|
tx.TxIn[0].Sequence = htlc.SuccessSequence()
|
|
tx.LockTime = uint32(cltvExpiry)
|
|
|
|
trHtlc, ok := htlc.HtlcScript.(*HtlcScriptV3)
|
|
require.True(t, ok)
|
|
|
|
sig := signTx(
|
|
tx, receiverPrivKey,
|
|
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 {
|
|
var bogusKeyBytes [33]byte
|
|
|
|
_, bogusKey := test.CreateKey(5)
|
|
|
|
copy(
|
|
bogusKeyBytes[:],
|
|
bogusKey.SerializeCompressed(),
|
|
)
|
|
|
|
htlc, err := NewHtlcV3(
|
|
cltvExpiry, senderKey,
|
|
receiverKey, bogusKeyBytes, receiverKey,
|
|
hashedPreimage, &chaincfg.MainNetParams,
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
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(
|
|
senderPubKey, 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)
|
|
})
|
|
}
|
|
}
|