lnd.xprv/lnwallet/script_utils_test.go

657 lines
20 KiB
Go
Raw Normal View History

package lnwallet
import (
"bytes"
"fmt"
"testing"
"time"
"github.com/btcsuite/fastsha256"
"github.com/roasbeef/btcd/btcec"
"github.com/roasbeef/btcd/txscript"
"github.com/roasbeef/btcd/wire"
"github.com/roasbeef/btcutil"
)
// TestCommitmentSpendValidation test the spendability of both outputs within
// the commitment transaction.
//
// The following spending cases are covered by this test:
// * Alice's spend from the delayed output on her commitment transaction.
// * Bob's spend from Alice's delayed output when she broadcasts a revoked
// commitment transaction.
// * Bob's spend from his unencumbered output within Alice's commitment
// transaction.
func TestCommitmentSpendValidation(t *testing.T) {
// 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: testHdSeed,
Index: 50,
}
fakeFundingTxIn := wire.NewTxIn(fundingOut, nil, nil)
// We also set up set some resources for the commitment transaction.
// Each side currently has 1 BTC within the channel, with a total
// channel capacity of 2BTC.
aliceKeyPriv, aliceKeyPub := btcec.PrivKeyFromBytes(btcec.S256(),
testWalletPrivKey)
bobKeyPriv, bobKeyPub := btcec.PrivKeyFromBytes(btcec.S256(),
bobsPrivKey)
channelBalance := btcutil.Amount(1 * 10e8)
csvTimeout := uint32(5)
revocationPreimage := testHdSeed[:]
revokePubKey := DeriveRevocationPubkey(bobKeyPub, revocationPreimage)
aliceSelfOutputSigner := &mockSigner{aliceKeyPriv}
// With all the test data set up, we create the commitment transaction.
// We only focus on a single party's transactions, as the scripts are
// identical with the roles reversed.
//
// This is Alice's commitment transaction, so she must wait a CSV delay
// of 5 blocks before sweeping the output, while bob can spend
// immediately with either the revocation key, or his regular key.
commitmentTx, err := CreateCommitTx(fakeFundingTxIn, aliceKeyPub,
bobKeyPub, revokePubKey, csvTimeout, channelBalance,
channelBalance, DefaultDustLimit())
if err != nil {
t.Fatalf("unable to create commitment transaction: %v", nil)
}
delayOutput := commitmentTx.TxOut[0]
regularOutput := commitmentTx.TxOut[1]
// We're testing an uncooperative close, output sweep, so construct a
// transaction which sweeps the funds to a random address.
targetOutput, err := commitScriptUnencumbered(aliceKeyPub)
if err != nil {
t.Fatalf("unable to create target output: %v", err)
}
sweepTx := wire.NewMsgTx(2)
sweepTx.AddTxIn(wire.NewTxIn(&wire.OutPoint{
Hash: commitmentTx.TxHash(),
Index: 0,
},
nil,
nil),
)
sweepTx.AddTxOut(&wire.TxOut{
PkScript: targetOutput,
Value: 0.5 * 10e8,
})
// First, we'll test spending with Alice's key after the timeout.
delayScript, err := commitScriptToSelf(csvTimeout, aliceKeyPub, revokePubKey)
if err != nil {
t.Fatalf("unable to generate alice delay script: %v", err)
}
sweepTx.TxIn[0].Sequence = lockTimeToSequence(false, csvTimeout)
signDesc := &SignDescriptor{
WitnessScript: delayScript,
SigHashes: txscript.NewTxSigHashes(sweepTx),
Output: &wire.TxOut{
Value: int64(channelBalance),
},
HashType: txscript.SigHashAll,
InputIndex: 0,
}
aliceWitnessSpend, err := CommitSpendTimeout(aliceSelfOutputSigner,
signDesc, sweepTx)
if err != nil {
t.Fatalf("unable to generate delay commit spend witness: %v", err)
}
sweepTx.TxIn[0].Witness = aliceWitnessSpend
vm, err := txscript.NewEngine(delayOutput.PkScript,
sweepTx, 0, txscript.StandardVerifyFlags, nil,
nil, int64(channelBalance))
if err != nil {
t.Fatalf("unable to create engine: %v", err)
}
if err := vm.Execute(); err != nil {
t.Fatalf("spend from delay output is invalid: %v", err)
}
// Next, we'll test bob spending with the derived revocation key to
// simulate the scenario when alice broadcasts this commitment
// transaction after it's been revoked.
revokePrivKey := DeriveRevocationPrivKey(bobKeyPriv, revocationPreimage)
bobRevokeSigner := &mockSigner{revokePrivKey}
signDesc = &SignDescriptor{
WitnessScript: delayScript,
SigHashes: txscript.NewTxSigHashes(sweepTx),
Output: &wire.TxOut{
Value: int64(channelBalance),
},
HashType: txscript.SigHashAll,
InputIndex: 0,
}
bobWitnessSpend, err := CommitSpendRevoke(bobRevokeSigner, signDesc,
sweepTx)
if err != nil {
t.Fatalf("unable to generate revocation witness: %v", err)
}
sweepTx.TxIn[0].Witness = bobWitnessSpend
vm, err = txscript.NewEngine(delayOutput.PkScript,
sweepTx, 0, txscript.StandardVerifyFlags, nil,
nil, int64(channelBalance))
if err != nil {
t.Fatalf("unable to create engine: %v", err)
}
if err := vm.Execute(); err != nil {
t.Fatalf("revocation spend is invalid: %v", err)
}
// Finally, we test bob sweeping his output as normal in the case that
// alice broadcasts this commitment transaction.
bobSigner := &mockSigner{bobKeyPriv}
bobScriptp2wkh, err := commitScriptUnencumbered(bobKeyPub)
if err != nil {
t.Fatalf("unable to create bob p2wkh script: %v", err)
}
signDesc = &SignDescriptor{
WitnessScript: bobScriptp2wkh,
SigHashes: txscript.NewTxSigHashes(sweepTx),
Output: &wire.TxOut{
Value: int64(channelBalance),
PkScript: bobScriptp2wkh,
},
HashType: txscript.SigHashAll,
InputIndex: 0,
}
bobRegularSpend, err := CommitSpendNoDelay(bobSigner, signDesc,
sweepTx)
if err != nil {
t.Fatalf("unable to create bob regular spend: %v", err)
}
sweepTx.TxIn[0].Witness = bobRegularSpend
vm, err = txscript.NewEngine(regularOutput.PkScript,
sweepTx, 0, txscript.StandardVerifyFlags, nil,
nil, int64(channelBalance))
if err != nil {
t.Fatalf("unable to create engine: %v", err)
}
if err := vm.Execute(); err != nil {
t.Fatalf("bob p2wkh spend is invalid: %v", err)
}
}
// TestRevocationKeyDerivation tests that given a public key, and a revocation
// hash, the homomorphic revocation public and private key derivation work
// properly.
func TestRevocationKeyDerivation(t *testing.T) {
revocationPreimage := testHdSeed[:]
priv, pub := btcec.PrivKeyFromBytes(btcec.S256(), testWalletPrivKey)
revocationPub := DeriveRevocationPubkey(pub, revocationPreimage)
revocationPriv := DeriveRevocationPrivKey(priv, revocationPreimage)
x, y := btcec.S256().ScalarBaseMult(revocationPriv.D.Bytes())
derivedRevPub := &btcec.PublicKey{
Curve: btcec.S256(),
X: x,
Y: y,
}
// The the revocation public key derived from the original public key,
// and the one derived from the private key should be identical.
if !revocationPub.IsEqual(derivedRevPub) {
t.Fatalf("derived public keys don't match!")
}
}
// makeWitnessTestCase is a helper function used within test cases involving
// the validity of a crafted witness. This function is a wrapper function which
// allows constructing table-driven tests. In the case of an error while
// constructing the witness, the test fails fataly.
func makeWitnessTestCase(t *testing.T, f func() (wire.TxWitness, error)) func() wire.TxWitness {
return func() wire.TxWitness {
witness, err := f()
if err != nil {
t.Fatalf("unable to create witness test case: %v", err)
}
return witness
}
}
// TestHTLCSenderSpendValidation tests all possible valid+invalid redemption
// paths in the script used within the sender's commitment transaction for an
// outgoing HTLC.
//
// The following cases are exercised by this test:
// sender script:
// * receiver spends
// * revoke w/ sig
// * HTLC with invalid preimage size
// * HTLC with valid preimage size + sig
// * sender spends
// * invalid lock-time for CLTV
// * invalid sequence for CSV
// * valid lock-time+sequence, valid sig
func TestHTLCSenderSpendValidation(t *testing.T) {
// TODO(roasbeef): eliminate duplication with other HTLC tests.
// We generate a fake output, and the coresponding 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: testHdSeed,
Index: 50,
}
fakeFundingTxIn := wire.NewTxIn(fundingOut, nil, nil)
// Generate a payment and revocation preimage to be used below.
revokePreimage := testHdSeed[:]
revokeHash := fastsha256.Sum256(revokePreimage)
paymentPreimage := revokeHash
paymentPreimage[0] ^= 1
paymentHash := fastsha256.Sum256(paymentPreimage[:])
// We'll also need some tests keys for alice and bob, and metadata of
// the HTLC output.
aliceKeyPriv, aliceKeyPub := btcec.PrivKeyFromBytes(btcec.S256(),
testWalletPrivKey)
bobKeyPriv, bobKeyPub := btcec.PrivKeyFromBytes(btcec.S256(),
bobsPrivKey)
paymentAmt := btcutil.Amount(1 * 10e8)
cltvTimeout := uint32(8)
csvTimeout := uint32(5)
// Generate the raw HTLC redemption scripts, and its p2wsh counterpart.
htlcScript, err := senderHTLCScript(cltvTimeout, csvTimeout,
aliceKeyPub, bobKeyPub, revokeHash[:], paymentHash[:])
if err != nil {
t.Fatalf("unable to create htlc sender script: %v", err)
}
htlcWitnessScript, err := witnessScriptHash(htlcScript)
if err != nil {
t.Fatalf("unable to create p2wsh htlc script: %v", err)
}
// This will be Alice's commitment transaction. In this scenario Alice
// is sending an HTLC to a node she has a a path to (could be Bob,
// could be multiple hops down, it doesn't really matter).
senderCommitTx := wire.NewMsgTx(2)
senderCommitTx.AddTxIn(fakeFundingTxIn)
senderCommitTx.AddTxOut(&wire.TxOut{
Value: int64(paymentAmt),
PkScript: htlcWitnessScript,
})
prevOut := &wire.OutPoint{
Hash: senderCommitTx.TxHash(),
Index: 0,
}
sweepTx := wire.NewMsgTx(2)
sweepTx.AddTxIn(wire.NewTxIn(prevOut, nil, nil))
sweepTx.AddTxOut(
&wire.TxOut{
PkScript: []byte("doesn't matter"),
Value: 1 * 10e8,
},
)
testCases := []struct {
witness func() wire.TxWitness
valid bool
}{
{
// revoke w/ sig
// TODO(roasbeef): test invalid revoke
makeWitnessTestCase(t, func() (wire.TxWitness, error) {
return senderHtlcSpendRevoke(htlcScript, paymentAmt,
bobKeyPriv, sweepTx,
revokePreimage)
}),
true,
},
{
// HTLC with invalid preimage size
makeWitnessTestCase(t, func() (wire.TxWitness, error) {
return senderHtlcSpendRedeem(htlcScript, paymentAmt,
bobKeyPriv, sweepTx,
// Invalid preimage length
bytes.Repeat([]byte{1}, 45))
}),
false,
},
{
// HTLC with valid preimage size + sig
// TODO(roabeef): invalid preimage
makeWitnessTestCase(t, func() (wire.TxWitness, error) {
return senderHtlcSpendRedeem(htlcScript, paymentAmt,
bobKeyPriv, sweepTx,
paymentPreimage[:])
}),
true,
},
{
// invalid lock-time for CLTV
makeWitnessTestCase(t, func() (wire.TxWitness, error) {
return senderHtlcSpendTimeout(htlcScript, paymentAmt,
aliceKeyPriv, sweepTx, cltvTimeout-2, csvTimeout)
}),
false,
},
{
// invalid sequence for CSV
makeWitnessTestCase(t, func() (wire.TxWitness, error) {
return senderHtlcSpendTimeout(htlcScript, paymentAmt,
aliceKeyPriv, sweepTx, cltvTimeout, csvTimeout-2)
}),
false,
},
{
// valid lock-time+sequence, valid sig
makeWitnessTestCase(t, func() (wire.TxWitness, error) {
return senderHtlcSpendTimeout(htlcScript, paymentAmt,
aliceKeyPriv, sweepTx, cltvTimeout, csvTimeout)
}),
true,
},
}
for i, testCase := range testCases {
sweepTx.TxIn[0].Witness = testCase.witness()
vm, err := txscript.NewEngine(htlcWitnessScript,
sweepTx, 0, txscript.StandardVerifyFlags, nil,
nil, int64(paymentAmt))
if err != nil {
t.Fatalf("unable to create engine: %v", err)
}
// This buffer will trace execution of the Script, only dumping
// out to stdout in the case that a test fails.
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 && testCase.valid {
fmt.Println(debugBuf.String())
t.Fatalf("spend test case #%v failed, spend should be valid: %v", i, err)
} else if err == nil && !testCase.valid && done {
fmt.Println(debugBuf.String())
t.Fatalf("spend test case #%v succeed, spend should be invalid: %v", i, err)
}
debugBuf.WriteString(fmt.Sprintf("Stack: %v", vm.GetStack()))
debugBuf.WriteString(fmt.Sprintf("AltStack: %v", vm.GetAltStack()))
}
}
}
// TestHTLCReceiverSpendValidation tests all possible valid+invalid redemption
// paths in the script used within the receiver's commitment transaction for an
// incoming HTLC.
//
// The following cases are exercised by this test:
// * receiver spends
// * HTLC redemption w/ invalid preimage size
// * HTLC redemption w/ invalid sequence
// * HTLC redemption w/ valid preimage size
// * sender spends
// * revoke w/ sig
// * refund w/ invalid lock time
// * refund w/ valid lock time
func TestHTLCReceiverSpendValidation(t *testing.T) {
// We generate a fake output, and the coresponding 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: testHdSeed,
Index: 50,
}
fakeFundingTxIn := wire.NewTxIn(fundingOut, nil, nil)
// Generate a payment and revocation preimage to be used below.
revokePreimage := testHdSeed[:]
revokeHash := fastsha256.Sum256(revokePreimage)
paymentPreimage := revokeHash
paymentPreimage[0] ^= 1
paymentHash := fastsha256.Sum256(paymentPreimage[:])
// We'll also need some tests keys for alice and bob, and metadata of
// the HTLC output.
aliceKeyPriv, aliceKeyPub := btcec.PrivKeyFromBytes(btcec.S256(),
testWalletPrivKey)
bobKeyPriv, bobKeyPub := btcec.PrivKeyFromBytes(btcec.S256(),
bobsPrivKey)
paymentAmt := btcutil.Amount(1 * 10e8)
cltvTimeout := uint32(8)
csvTimeout := uint32(5)
// Generate the raw HTLC redemption scripts, and its p2wsh counterpart.
htlcScript, err := receiverHTLCScript(cltvTimeout, csvTimeout,
aliceKeyPub, bobKeyPub, revokeHash[:], paymentHash[:])
if err != nil {
t.Fatalf("unable to create htlc sender script: %v", err)
}
htlcWitnessScript, err := witnessScriptHash(htlcScript)
if err != nil {
t.Fatalf("unable to create p2wsh htlc script: %v", err)
}
// This will be Bob's commitment transaction. In this scenario Alice
// is sending an HTLC to a node she has a a path to (could be Bob,
// could be multiple hops down, it doesn't really matter).
receiverCommitTx := wire.NewMsgTx(2)
receiverCommitTx.AddTxIn(fakeFundingTxIn)
receiverCommitTx.AddTxOut(&wire.TxOut{
Value: int64(paymentAmt),
PkScript: htlcWitnessScript,
})
prevOut := &wire.OutPoint{
Hash: receiverCommitTx.TxHash(),
Index: 0,
}
sweepTx := wire.NewMsgTx(2)
sweepTx.AddTxIn(wire.NewTxIn(prevOut, nil, nil))
sweepTx.AddTxOut(
&wire.TxOut{
PkScript: []byte("doesn't matter"),
Value: 1 * 10e8,
},
)
testCases := []struct {
witness func() wire.TxWitness
valid bool
}{
{
// HTLC redemption w/ invalid preimage size
makeWitnessTestCase(t, func() (wire.TxWitness, error) {
return receiverHtlcSpendRedeem(htlcScript,
paymentAmt, bobKeyPriv, sweepTx,
bytes.Repeat([]byte{1}, 45), csvTimeout,
)
}),
false,
},
{
// HTLC redemption w/ invalid sequence
makeWitnessTestCase(t, func() (wire.TxWitness, error) {
return receiverHtlcSpendRedeem(htlcScript,
paymentAmt, bobKeyPriv, sweepTx,
paymentPreimage[:], csvTimeout-2,
)
}),
false,
},
{
// HTLC redemption w/ valid preimage size
makeWitnessTestCase(t, func() (wire.TxWitness, error) {
return receiverHtlcSpendRedeem(htlcScript,
paymentAmt, bobKeyPriv, sweepTx,
paymentPreimage[:], csvTimeout,
)
}),
true,
},
{
// revoke w/ sig
makeWitnessTestCase(t, func() (wire.TxWitness, error) {
return receiverHtlcSpendRevoke(htlcScript, paymentAmt,
aliceKeyPriv, sweepTx, revokePreimage[:],
)
}),
true,
},
{
// refund w/ invalid lock time
makeWitnessTestCase(t, func() (wire.TxWitness, error) {
return receiverHtlcSpendTimeout(htlcScript, paymentAmt,
aliceKeyPriv, sweepTx, cltvTimeout-2)
}),
false,
},
{
// refund w/ valid lock time
makeWitnessTestCase(t, func() (wire.TxWitness, error) {
return receiverHtlcSpendTimeout(htlcScript, paymentAmt,
aliceKeyPriv, sweepTx, cltvTimeout)
}),
true,
},
}
for i, testCase := range testCases {
sweepTx.TxIn[0].Witness = testCase.witness()
vm, err := txscript.NewEngine(htlcWitnessScript,
sweepTx, 0, txscript.StandardVerifyFlags, nil,
nil, int64(paymentAmt))
if err != nil {
t.Fatalf("unable to create engine: %v", err)
}
// This buffer will trace execution of the Script, only dumping
// out to stdout in the case that a test fails.
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 && testCase.valid {
fmt.Println(debugBuf.String())
t.Fatalf("spend test case #%v failed, spend should be valid: %v", i, err)
} else if err == nil && !testCase.valid && done {
fmt.Println(debugBuf.String())
t.Fatalf("spend test case #%v succeed, spend should be invalid: %v", i, err)
}
debugBuf.WriteString(fmt.Sprintf("Stack: %v", vm.GetStack()))
debugBuf.WriteString(fmt.Sprintf("AltStack: %v", vm.GetAltStack()))
}
}
}
var stateHintTests = []struct {
name string
from uint64
to uint64
inputs int
shouldFail bool
}{
{
name: "states 0 to 1000",
from: 0,
to: 1000,
inputs: 1,
shouldFail: false,
},
{
name: "states 'maxStateHint-1000' to 'maxStateHint'",
from: maxStateHint - 1000,
to: maxStateHint,
inputs: 1,
shouldFail: false,
},
{
name: "state 'maxStateHint+1'",
from: maxStateHint + 1,
to: maxStateHint + 10,
inputs: 1,
shouldFail: true,
},
{
name: "commit transaction with two inputs",
inputs: 2,
shouldFail: true,
},
}
func TestCommitTxStateHint(t *testing.T) {
var obsfucator [StateHintSize]byte
copy(obsfucator[:], testHdSeed[:StateHintSize])
timeYesterday := uint32(time.Now().Unix() - 24*60*60)
for _, test := range stateHintTests {
commitTx := wire.NewMsgTx(2)
// Add supplied number of inputs to the commitment transaction.
for i := 0; i < test.inputs; i++ {
commitTx.AddTxIn(&wire.TxIn{})
}
for i := test.from; i <= test.to; i++ {
stateNum := uint64(i)
err := SetStateNumHint(commitTx, stateNum, obsfucator)
if err != nil && !test.shouldFail {
t.Fatalf("unable to set state num %v: %v", i, err)
} else if err == nil && test.shouldFail {
t.Fatalf("Failed(%v): test should fail but did not", test.name)
}
locktime := commitTx.LockTime
sequence := commitTx.TxIn[0].Sequence
// Locktime should not be less than 500,000,000 and not larger
// than the time 24 hours ago. One day should provide a good
// enough buffer for the tests.
if locktime < 5e8 || locktime > timeYesterday {
if !test.shouldFail {
t.Fatalf("The value of locktime (%v) may cause the commitment "+
"transaction to be unspendable", locktime)
}
}
if sequence&wire.SequenceLockTimeDisabled == 0 {
if !test.shouldFail {
t.Fatalf("Sequence locktime is NOT disabled when it should be")
}
}
extractedStateNum := GetStateNumHint(commitTx, obsfucator)
if extractedStateNum != stateNum && !test.shouldFail {
t.Fatalf("state number mismatched, expected %v, got %v",
stateNum, extractedStateNum)
} else if extractedStateNum == stateNum && test.shouldFail {
t.Fatalf("Failed(%v): test should fail but did not", test.name)
}
}
t.Logf("Passed: %v", test.name)
}
}