You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1468 lines
42 KiB
1468 lines
42 KiB
package input |
|
|
|
import ( |
|
"bytes" |
|
"crypto/sha256" |
|
"encoding/hex" |
|
"fmt" |
|
"testing" |
|
|
|
"github.com/btcsuite/btcd/btcec" |
|
"github.com/btcsuite/btcd/chaincfg/chainhash" |
|
"github.com/btcsuite/btcd/txscript" |
|
"github.com/btcsuite/btcd/wire" |
|
"github.com/btcsuite/btcutil" |
|
"github.com/lightningnetwork/lnd/keychain" |
|
) |
|
|
|
// 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. |
|
func assertEngineExecution(t *testing.T, testNum int, valid bool, |
|
newEngine func() (*txscript.Engine, error)) { |
|
t.Helper() |
|
|
|
// Get a new VM to execute. |
|
vm, err := newEngine() |
|
if err != nil { |
|
t.Fatalf("unable to create engine: %v", err) |
|
} |
|
|
|
// Execute the VM, only go on to the step-by-step execution if |
|
// it doesn't validate as expected. |
|
vmErr := vm.Execute() |
|
if valid == (vmErr == nil) { |
|
return |
|
} |
|
|
|
// Now that the execution didn't match what we expected, fetch a new VM |
|
// to step through. |
|
vm, err = newEngine() |
|
if err != nil { |
|
t.Fatalf("unable to create engine: %v", err) |
|
} |
|
|
|
// 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 #%v failed, spend "+ |
|
"should be valid: %v", testNum, err) |
|
} else if err == nil && !valid && done { |
|
fmt.Println(debugBuf.String()) |
|
t.Fatalf("spend test case #%v succeed, spend "+ |
|
"should be invalid: %v", testNum, 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 #%v execution ended with: %v", validity, testNum, vmErr) |
|
} |
|
|
|
// 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) { |
|
t.Parallel() |
|
|
|
// First, we'll generate a commitment point, and a commitment secret. |
|
// These will be used to derive the ultimate revocation keys. |
|
revocationPreimage := testHdSeed.CloneBytes() |
|
commitSecret, commitPoint := btcec.PrivKeyFromBytes(btcec.S256(), |
|
revocationPreimage) |
|
|
|
// With the commitment secrets generated, we'll now create the base |
|
// keys we'll use to derive the revocation key from. |
|
basePriv, basePub := btcec.PrivKeyFromBytes(btcec.S256(), |
|
testWalletPrivKey) |
|
|
|
// With the point and key obtained, we can now derive the revocation |
|
// key itself. |
|
revocationPub := DeriveRevocationPubkey(basePub, commitPoint) |
|
|
|
// The revocation public key derived from the original public key, and |
|
// the one derived from the private key should be identical. |
|
revocationPriv := DeriveRevocationPrivKey(basePriv, commitSecret) |
|
if !revocationPub.IsEqual(revocationPriv.PubKey()) { |
|
t.Fatalf("derived public keys don't match!") |
|
} |
|
} |
|
|
|
// TestTweakKeyDerivation tests that given a public key, and commitment tweak, |
|
// then we're able to properly derive a tweaked private key that corresponds to |
|
// the computed tweak public key. This scenario ensure that our key derivation |
|
// for any of the non revocation keys on the commitment transaction is correct. |
|
func TestTweakKeyDerivation(t *testing.T) { |
|
t.Parallel() |
|
|
|
// First, we'll generate a base public key that we'll be "tweaking". |
|
baseSecret := testHdSeed.CloneBytes() |
|
basePriv, basePub := btcec.PrivKeyFromBytes(btcec.S256(), baseSecret) |
|
|
|
// With the base key create, we'll now create a commitment point, and |
|
// from that derive the bytes we'll used to tweak the base public key. |
|
commitPoint := ComputeCommitmentPoint(bobsPrivKey) |
|
commitTweak := SingleTweakBytes(commitPoint, basePub) |
|
|
|
// Next, we'll modify the public key. When we apply the same operation |
|
// to the private key we should get a key that matches. |
|
tweakedPub := TweakPubKey(basePub, commitPoint) |
|
|
|
// Finally, attempt to re-generate the private key that matches the |
|
// tweaked public key. The derived key should match exactly. |
|
derivedPriv := TweakPrivKey(basePriv, commitTweak) |
|
if !derivedPriv.PubKey().IsEqual(tweakedPub) { |
|
t.Fatalf("pub 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 fatally. |
|
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) { |
|
t.Parallel() |
|
|
|
// 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. |
|
txid, err := chainhash.NewHash(testHdSeed.CloneBytes()) |
|
if err != nil { |
|
t.Fatalf("unable to create txid: %v", err) |
|
} |
|
fundingOut := &wire.OutPoint{ |
|
Hash: *txid, |
|
Index: 50, |
|
} |
|
fakeFundingTxIn := wire.NewTxIn(fundingOut, nil, nil) |
|
|
|
// Next we'll the commitment secret for our commitment tx and also the |
|
// revocation key that we'll use as well. |
|
revokePreimage := testHdSeed.CloneBytes() |
|
commitSecret, commitPoint := btcec.PrivKeyFromBytes(btcec.S256(), |
|
revokePreimage) |
|
|
|
// Generate a payment preimage to be used below. |
|
paymentPreimage := revokePreimage |
|
paymentPreimage[0] ^= 1 |
|
paymentHash := sha256.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) |
|
|
|
aliceLocalKey := TweakPubKey(aliceKeyPub, commitPoint) |
|
bobLocalKey := TweakPubKey(bobKeyPub, commitPoint) |
|
|
|
// As we'll be modeling spends from Alice's commitment transaction, |
|
// we'll be using Bob's base point for the revocation key. |
|
revocationKey := DeriveRevocationPubkey(bobKeyPub, commitPoint) |
|
|
|
bobCommitTweak := SingleTweakBytes(commitPoint, bobKeyPub) |
|
aliceCommitTweak := SingleTweakBytes(commitPoint, aliceKeyPub) |
|
|
|
// Finally, we'll create mock signers for both of them based on their |
|
// private keys. This test simplifies a bit and uses the same key as |
|
// the base point for all scripts and derivations. |
|
bobSigner := &MockSigner{Privkeys: []*btcec.PrivateKey{bobKeyPriv}} |
|
aliceSigner := &MockSigner{Privkeys: []*btcec.PrivateKey{aliceKeyPriv}} |
|
|
|
var ( |
|
htlcWitnessScript, htlcPkScript []byte |
|
htlcOutput *wire.TxOut |
|
sweepTxSigHashes *txscript.TxSigHashes |
|
senderCommitTx, sweepTx *wire.MsgTx |
|
bobRecvrSig *btcec.Signature |
|
bobSigHash txscript.SigHashType |
|
) |
|
|
|
// genCommitTx generates a commitment tx where the htlc output requires |
|
// confirmation to be spent according to 'confirmed'. |
|
genCommitTx := func(confirmed bool) { |
|
// Generate the raw HTLC redemption scripts, and its p2wsh |
|
// counterpart. |
|
htlcWitnessScript, err = SenderHTLCScript( |
|
aliceLocalKey, bobLocalKey, revocationKey, |
|
paymentHash[:], confirmed, |
|
) |
|
if err != nil { |
|
t.Fatalf("unable to create htlc sender script: %v", err) |
|
} |
|
htlcPkScript, err = WitnessScriptHash(htlcWitnessScript) |
|
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 path |
|
// to (could be Bob, could be multiple hops down, it doesn't |
|
// really matter). |
|
htlcOutput = &wire.TxOut{ |
|
Value: int64(paymentAmt), |
|
PkScript: htlcPkScript, |
|
} |
|
senderCommitTx = wire.NewMsgTx(2) |
|
senderCommitTx.AddTxIn(fakeFundingTxIn) |
|
senderCommitTx.AddTxOut(htlcOutput) |
|
} |
|
|
|
// genSweepTx generates a sweep of the senderCommitTx, and sets the |
|
// sequence and sighash single|anyonecanspend if confirmed is true. |
|
genSweepTx := func(confirmed bool) { |
|
prevOut := &wire.OutPoint{ |
|
Hash: senderCommitTx.TxHash(), |
|
Index: 0, |
|
} |
|
|
|
sweepTx = wire.NewMsgTx(2) |
|
|
|
sweepTx.AddTxIn(wire.NewTxIn(prevOut, nil, nil)) |
|
if confirmed { |
|
sweepTx.TxIn[0].Sequence = LockTimeToSequence(false, 1) |
|
} |
|
|
|
sweepTx.AddTxOut( |
|
&wire.TxOut{ |
|
PkScript: []byte("doesn't matter"), |
|
Value: 1 * 10e8, |
|
}, |
|
) |
|
|
|
sweepTxSigHashes = txscript.NewTxSigHashes(sweepTx) |
|
|
|
bobSigHash = txscript.SigHashAll |
|
if confirmed { |
|
bobSigHash = txscript.SigHashSingle | txscript.SigHashAnyOneCanPay |
|
} |
|
|
|
// We'll also generate a signature on the sweep transaction above |
|
// that will act as Bob's signature to Alice for the second level HTLC |
|
// transaction. |
|
bobSignDesc := SignDescriptor{ |
|
KeyDesc: keychain.KeyDescriptor{ |
|
PubKey: bobKeyPub, |
|
}, |
|
SingleTweak: bobCommitTweak, |
|
WitnessScript: htlcWitnessScript, |
|
Output: htlcOutput, |
|
HashType: bobSigHash, |
|
SigHashes: sweepTxSigHashes, |
|
InputIndex: 0, |
|
} |
|
bobSig, err := bobSigner.SignOutputRaw(sweepTx, &bobSignDesc) |
|
if err != nil { |
|
t.Fatalf("unable to generate alice signature: %v", err) |
|
} |
|
|
|
bobRecvrSig, err = btcec.ParseDERSignature( |
|
bobSig.Serialize(), btcec.S256(), |
|
) |
|
if err != nil { |
|
t.Fatalf("unable to parse signature: %v", err) |
|
} |
|
} |
|
|
|
testCases := []struct { |
|
witness func() wire.TxWitness |
|
valid bool |
|
}{ |
|
{ |
|
// revoke w/ sig |
|
// TODO(roasbeef): test invalid revoke |
|
makeWitnessTestCase(t, func() (wire.TxWitness, error) { |
|
genCommitTx(false) |
|
genSweepTx(false) |
|
|
|
signDesc := &SignDescriptor{ |
|
KeyDesc: keychain.KeyDescriptor{ |
|
PubKey: bobKeyPub, |
|
}, |
|
DoubleTweak: commitSecret, |
|
WitnessScript: htlcWitnessScript, |
|
Output: htlcOutput, |
|
HashType: txscript.SigHashAll, |
|
SigHashes: sweepTxSigHashes, |
|
InputIndex: 0, |
|
} |
|
|
|
return SenderHtlcSpendRevokeWithKey(bobSigner, signDesc, |
|
revocationKey, sweepTx) |
|
}), |
|
true, |
|
}, |
|
{ |
|
// HTLC with invalid preimage size |
|
makeWitnessTestCase(t, func() (wire.TxWitness, error) { |
|
genCommitTx(false) |
|
genSweepTx(false) |
|
|
|
signDesc := &SignDescriptor{ |
|
KeyDesc: keychain.KeyDescriptor{ |
|
PubKey: bobKeyPub, |
|
}, |
|
SingleTweak: bobCommitTweak, |
|
WitnessScript: htlcWitnessScript, |
|
Output: htlcOutput, |
|
HashType: txscript.SigHashAll, |
|
SigHashes: sweepTxSigHashes, |
|
InputIndex: 0, |
|
} |
|
|
|
return SenderHtlcSpendRedeem(bobSigner, signDesc, |
|
sweepTx, |
|
// Invalid preimage length |
|
bytes.Repeat([]byte{1}, 45)) |
|
}), |
|
false, |
|
}, |
|
{ |
|
// HTLC with valid preimage size + sig |
|
// TODO(roasbeef): invalid preimage |
|
makeWitnessTestCase(t, func() (wire.TxWitness, error) { |
|
genCommitTx(false) |
|
genSweepTx(false) |
|
|
|
signDesc := &SignDescriptor{ |
|
KeyDesc: keychain.KeyDescriptor{ |
|
PubKey: bobKeyPub, |
|
}, |
|
SingleTweak: bobCommitTweak, |
|
WitnessScript: htlcWitnessScript, |
|
Output: htlcOutput, |
|
HashType: txscript.SigHashAll, |
|
SigHashes: sweepTxSigHashes, |
|
InputIndex: 0, |
|
} |
|
|
|
return SenderHtlcSpendRedeem(bobSigner, signDesc, |
|
sweepTx, paymentPreimage) |
|
}), |
|
true, |
|
}, |
|
{ |
|
// HTLC with valid preimage size + sig, and with |
|
// enforced locktime in HTLC script. |
|
makeWitnessTestCase(t, func() (wire.TxWitness, error) { |
|
// Make a commit tx that needs confirmation for |
|
// HTLC output to be spent. |
|
genCommitTx(true) |
|
|
|
// Generate a sweep with the locktime set. |
|
genSweepTx(true) |
|
|
|
signDesc := &SignDescriptor{ |
|
KeyDesc: keychain.KeyDescriptor{ |
|
PubKey: bobKeyPub, |
|
}, |
|
SingleTweak: bobCommitTweak, |
|
WitnessScript: htlcWitnessScript, |
|
Output: htlcOutput, |
|
HashType: txscript.SigHashAll, |
|
SigHashes: sweepTxSigHashes, |
|
InputIndex: 0, |
|
} |
|
|
|
return SenderHtlcSpendRedeem(bobSigner, signDesc, |
|
sweepTx, paymentPreimage) |
|
}), |
|
true, |
|
}, |
|
{ |
|
// HTLC with valid preimage size + sig, but trying to |
|
// spend CSV output without sequence set. |
|
makeWitnessTestCase(t, func() (wire.TxWitness, error) { |
|
// Generate commitment tx with 1 CSV locked |
|
// HTLC. |
|
genCommitTx(true) |
|
|
|
// Generate sweep tx that doesn't have locktime |
|
// enabled. |
|
genSweepTx(false) |
|
|
|
signDesc := &SignDescriptor{ |
|
KeyDesc: keychain.KeyDescriptor{ |
|
PubKey: bobKeyPub, |
|
}, |
|
SingleTweak: bobCommitTweak, |
|
WitnessScript: htlcWitnessScript, |
|
Output: htlcOutput, |
|
HashType: txscript.SigHashAll, |
|
SigHashes: sweepTxSigHashes, |
|
InputIndex: 0, |
|
} |
|
|
|
return SenderHtlcSpendRedeem(bobSigner, signDesc, |
|
sweepTx, paymentPreimage) |
|
}), |
|
false, |
|
}, |
|
|
|
{ |
|
// valid spend to the transition the state of the HTLC |
|
// output with the second level HTLC timeout |
|
// transaction. |
|
makeWitnessTestCase(t, func() (wire.TxWitness, error) { |
|
genCommitTx(false) |
|
genSweepTx(false) |
|
|
|
signDesc := &SignDescriptor{ |
|
KeyDesc: keychain.KeyDescriptor{ |
|
PubKey: aliceKeyPub, |
|
}, |
|
SingleTweak: aliceCommitTweak, |
|
WitnessScript: htlcWitnessScript, |
|
Output: htlcOutput, |
|
HashType: txscript.SigHashAll, |
|
SigHashes: sweepTxSigHashes, |
|
InputIndex: 0, |
|
} |
|
|
|
return SenderHtlcSpendTimeout( |
|
bobRecvrSig, bobSigHash, aliceSigner, |
|
signDesc, sweepTx, |
|
) |
|
}), |
|
true, |
|
}, |
|
{ |
|
// valid spend to the transition the state of the HTLC |
|
// output with the second level HTLC timeout |
|
// transaction. |
|
makeWitnessTestCase(t, func() (wire.TxWitness, error) { |
|
// Make a commit tx that needs confirmation for |
|
// HTLC output to be spent. |
|
genCommitTx(true) |
|
|
|
// Generate a sweep with the locktime set. |
|
genSweepTx(true) |
|
|
|
signDesc := &SignDescriptor{ |
|
KeyDesc: keychain.KeyDescriptor{ |
|
PubKey: aliceKeyPub, |
|
}, |
|
SingleTweak: aliceCommitTweak, |
|
WitnessScript: htlcWitnessScript, |
|
Output: htlcOutput, |
|
HashType: txscript.SigHashAll, |
|
SigHashes: sweepTxSigHashes, |
|
InputIndex: 0, |
|
} |
|
|
|
return SenderHtlcSpendTimeout( |
|
bobRecvrSig, bobSigHash, aliceSigner, |
|
signDesc, sweepTx, |
|
) |
|
}), |
|
true, |
|
}, |
|
{ |
|
// valid spend to the transition the state of the HTLC |
|
// output with the second level HTLC timeout |
|
// transaction. |
|
makeWitnessTestCase(t, func() (wire.TxWitness, error) { |
|
// Generate commitment tx with 1 CSV locked |
|
// HTLC. |
|
genCommitTx(true) |
|
|
|
// Generate sweep tx that doesn't have locktime |
|
// enabled. |
|
genSweepTx(false) |
|
|
|
signDesc := &SignDescriptor{ |
|
KeyDesc: keychain.KeyDescriptor{ |
|
PubKey: aliceKeyPub, |
|
}, |
|
SingleTweak: aliceCommitTweak, |
|
WitnessScript: htlcWitnessScript, |
|
Output: htlcOutput, |
|
HashType: txscript.SigHashAll, |
|
SigHashes: sweepTxSigHashes, |
|
InputIndex: 0, |
|
} |
|
|
|
return SenderHtlcSpendTimeout( |
|
bobRecvrSig, bobSigHash, aliceSigner, |
|
signDesc, sweepTx, |
|
) |
|
}), |
|
false, |
|
}, |
|
} |
|
|
|
// TODO(roasbeef): set of cases to ensure able to sign w/ keypath and |
|
// not |
|
|
|
for i, testCase := range testCases { |
|
sweepTx.TxIn[0].Witness = testCase.witness() |
|
|
|
newEngine := func() (*txscript.Engine, error) { |
|
return txscript.NewEngine(htlcPkScript, |
|
sweepTx, 0, txscript.StandardVerifyFlags, nil, |
|
nil, int64(paymentAmt)) |
|
} |
|
|
|
assertEngineExecution(t, i, testCase.valid, newEngine) |
|
} |
|
} |
|
|
|
// 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) { |
|
t.Parallel() |
|
|
|
// 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. |
|
txid, err := chainhash.NewHash(testHdSeed.CloneBytes()) |
|
if err != nil { |
|
t.Fatalf("unable to create txid: %v", err) |
|
} |
|
fundingOut := &wire.OutPoint{ |
|
Hash: *txid, |
|
Index: 50, |
|
} |
|
fakeFundingTxIn := wire.NewTxIn(fundingOut, nil, nil) |
|
|
|
// Next we'll the commitment secret for our commitment tx and also the |
|
// revocation key that we'll use as well. |
|
revokePreimage := testHdSeed.CloneBytes() |
|
commitSecret, commitPoint := btcec.PrivKeyFromBytes(btcec.S256(), |
|
revokePreimage) |
|
|
|
// Generate a payment preimage to be used below. |
|
paymentPreimage := revokePreimage |
|
paymentPreimage[0] ^= 1 |
|
paymentHash := sha256.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) |
|
|
|
aliceLocalKey := TweakPubKey(aliceKeyPub, commitPoint) |
|
bobLocalKey := TweakPubKey(bobKeyPub, commitPoint) |
|
|
|
// As we'll be modeling spends from Bob's commitment transaction, we'll |
|
// be using Alice's base point for the revocation key. |
|
revocationKey := DeriveRevocationPubkey(aliceKeyPub, commitPoint) |
|
|
|
bobCommitTweak := SingleTweakBytes(commitPoint, bobKeyPub) |
|
aliceCommitTweak := SingleTweakBytes(commitPoint, aliceKeyPub) |
|
|
|
// Finally, we'll create mock signers for both of them based on their |
|
// private keys. This test simplifies a bit and uses the same key as |
|
// the base point for all scripts and derivations. |
|
bobSigner := &MockSigner{Privkeys: []*btcec.PrivateKey{bobKeyPriv}} |
|
aliceSigner := &MockSigner{Privkeys: []*btcec.PrivateKey{aliceKeyPriv}} |
|
|
|
var ( |
|
htlcWitnessScript, htlcPkScript []byte |
|
htlcOutput *wire.TxOut |
|
receiverCommitTx, sweepTx *wire.MsgTx |
|
sweepTxSigHashes *txscript.TxSigHashes |
|
aliceSenderSig *btcec.Signature |
|
aliceSigHash txscript.SigHashType |
|
) |
|
|
|
genCommitTx := func(confirmed bool) { |
|
// Generate the raw HTLC redemption scripts, and its p2wsh |
|
// counterpart. |
|
htlcWitnessScript, err = ReceiverHTLCScript( |
|
cltvTimeout, aliceLocalKey, bobLocalKey, revocationKey, |
|
paymentHash[:], confirmed, |
|
) |
|
if err != nil { |
|
t.Fatalf("unable to create htlc sender script: %v", err) |
|
} |
|
htlcPkScript, err = WitnessScriptHash(htlcWitnessScript) |
|
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 path to (could be Bob, could be |
|
// multiple hops down, it doesn't really matter). |
|
htlcOutput = &wire.TxOut{ |
|
Value: int64(paymentAmt), |
|
PkScript: htlcWitnessScript, |
|
} |
|
|
|
receiverCommitTx = wire.NewMsgTx(2) |
|
receiverCommitTx.AddTxIn(fakeFundingTxIn) |
|
receiverCommitTx.AddTxOut(htlcOutput) |
|
} |
|
|
|
genSweepTx := func(confirmed bool) { |
|
prevOut := &wire.OutPoint{ |
|
Hash: receiverCommitTx.TxHash(), |
|
Index: 0, |
|
} |
|
|
|
sweepTx = wire.NewMsgTx(2) |
|
sweepTx.AddTxIn(&wire.TxIn{ |
|
PreviousOutPoint: *prevOut, |
|
}) |
|
if confirmed { |
|
sweepTx.TxIn[0].Sequence = LockTimeToSequence(false, 1) |
|
} |
|
|
|
sweepTx.AddTxOut( |
|
&wire.TxOut{ |
|
PkScript: []byte("doesn't matter"), |
|
Value: 1 * 10e8, |
|
}, |
|
) |
|
sweepTxSigHashes = txscript.NewTxSigHashes(sweepTx) |
|
|
|
aliceSigHash = txscript.SigHashAll |
|
if confirmed { |
|
aliceSigHash = txscript.SigHashSingle | txscript.SigHashAnyOneCanPay |
|
} |
|
|
|
// We'll also generate a signature on the sweep transaction above |
|
// that will act as Alice's signature to Bob for the second level HTLC |
|
// transaction. |
|
aliceSignDesc := SignDescriptor{ |
|
KeyDesc: keychain.KeyDescriptor{ |
|
PubKey: aliceKeyPub, |
|
}, |
|
SingleTweak: aliceCommitTweak, |
|
WitnessScript: htlcWitnessScript, |
|
Output: htlcOutput, |
|
HashType: aliceSigHash, |
|
SigHashes: sweepTxSigHashes, |
|
InputIndex: 0, |
|
} |
|
aliceSig, err := aliceSigner.SignOutputRaw(sweepTx, &aliceSignDesc) |
|
if err != nil { |
|
t.Fatalf("unable to generate alice signature: %v", err) |
|
} |
|
|
|
aliceSenderSig, err = btcec.ParseDERSignature( |
|
aliceSig.Serialize(), btcec.S256(), |
|
) |
|
if err != nil { |
|
t.Fatalf("unable to parse signature: %v", err) |
|
} |
|
} |
|
|
|
// TODO(roasbeef): modify valid to check precise script errors? |
|
testCases := []struct { |
|
witness func() wire.TxWitness |
|
valid bool |
|
}{ |
|
{ |
|
// HTLC redemption w/ invalid preimage size |
|
makeWitnessTestCase(t, func() (wire.TxWitness, error) { |
|
genCommitTx(false) |
|
genSweepTx(false) |
|
|
|
signDesc := &SignDescriptor{ |
|
KeyDesc: keychain.KeyDescriptor{ |
|
PubKey: bobKeyPub, |
|
}, |
|
SingleTweak: bobCommitTweak, |
|
WitnessScript: htlcWitnessScript, |
|
Output: htlcOutput, |
|
HashType: txscript.SigHashAll, |
|
SigHashes: sweepTxSigHashes, |
|
InputIndex: 0, |
|
} |
|
|
|
return ReceiverHtlcSpendRedeem( |
|
aliceSenderSig, aliceSigHash, |
|
bytes.Repeat([]byte{1}, 45), bobSigner, |
|
signDesc, sweepTx, |
|
) |
|
|
|
}), |
|
false, |
|
}, |
|
{ |
|
// HTLC redemption w/ valid preimage size |
|
makeWitnessTestCase(t, func() (wire.TxWitness, error) { |
|
genCommitTx(false) |
|
genSweepTx(false) |
|
|
|
signDesc := &SignDescriptor{ |
|
KeyDesc: keychain.KeyDescriptor{ |
|
PubKey: bobKeyPub, |
|
}, |
|
SingleTweak: bobCommitTweak, |
|
WitnessScript: htlcWitnessScript, |
|
Output: htlcOutput, |
|
HashType: txscript.SigHashAll, |
|
SigHashes: sweepTxSigHashes, |
|
InputIndex: 0, |
|
} |
|
|
|
return ReceiverHtlcSpendRedeem( |
|
aliceSenderSig, aliceSigHash, |
|
paymentPreimage, bobSigner, |
|
signDesc, sweepTx, |
|
) |
|
}), |
|
true, |
|
}, |
|
{ |
|
// revoke w/ sig |
|
makeWitnessTestCase(t, func() (wire.TxWitness, error) { |
|
genCommitTx(false) |
|
genSweepTx(false) |
|
|
|
signDesc := &SignDescriptor{ |
|
KeyDesc: keychain.KeyDescriptor{ |
|
PubKey: aliceKeyPub, |
|
}, |
|
DoubleTweak: commitSecret, |
|
WitnessScript: htlcWitnessScript, |
|
Output: htlcOutput, |
|
HashType: txscript.SigHashAll, |
|
SigHashes: sweepTxSigHashes, |
|
InputIndex: 0, |
|
} |
|
|
|
return ReceiverHtlcSpendRevokeWithKey(aliceSigner, |
|
signDesc, revocationKey, sweepTx) |
|
}), |
|
true, |
|
}, |
|
{ |
|
// HTLC redemption w/ valid preimage size, and with |
|
// enforced locktime in HTLC scripts. |
|
makeWitnessTestCase(t, func() (wire.TxWitness, error) { |
|
// Make a commit tx that needs confirmation for |
|
// HTLC output to be spent. |
|
genCommitTx(true) |
|
|
|
// Generate a sweep with the locktime set. |
|
genSweepTx(true) |
|
|
|
signDesc := &SignDescriptor{ |
|
KeyDesc: keychain.KeyDescriptor{ |
|
PubKey: bobKeyPub, |
|
}, |
|
SingleTweak: bobCommitTweak, |
|
WitnessScript: htlcWitnessScript, |
|
Output: htlcOutput, |
|
HashType: txscript.SigHashAll, |
|
SigHashes: sweepTxSigHashes, |
|
InputIndex: 0, |
|
} |
|
|
|
return ReceiverHtlcSpendRedeem( |
|
aliceSenderSig, aliceSigHash, |
|
paymentPreimage, bobSigner, |
|
signDesc, sweepTx, |
|
) |
|
}), |
|
true, |
|
}, |
|
{ |
|
// HTLC redemption w/ valid preimage size, but trying |
|
// to spend CSV output without sequence set. |
|
makeWitnessTestCase(t, func() (wire.TxWitness, error) { |
|
// Generate commitment tx with 1 CSV locked |
|
// HTLC. |
|
genCommitTx(true) |
|
|
|
// Generate sweep tx that doesn't have locktime |
|
// enabled. |
|
genSweepTx(false) |
|
|
|
signDesc := &SignDescriptor{ |
|
KeyDesc: keychain.KeyDescriptor{ |
|
PubKey: bobKeyPub, |
|
}, |
|
SingleTweak: bobCommitTweak, |
|
WitnessScript: htlcWitnessScript, |
|
Output: htlcOutput, |
|
HashType: txscript.SigHashAll, |
|
SigHashes: sweepTxSigHashes, |
|
InputIndex: 0, |
|
} |
|
|
|
return ReceiverHtlcSpendRedeem( |
|
aliceSenderSig, aliceSigHash, |
|
paymentPreimage, bobSigner, signDesc, |
|
sweepTx, |
|
) |
|
}), |
|
false, |
|
}, |
|
|
|
{ |
|
// refund w/ invalid lock time |
|
makeWitnessTestCase(t, func() (wire.TxWitness, error) { |
|
genCommitTx(false) |
|
genSweepTx(false) |
|
|
|
signDesc := &SignDescriptor{ |
|
KeyDesc: keychain.KeyDescriptor{ |
|
PubKey: aliceKeyPub, |
|
}, |
|
SingleTweak: aliceCommitTweak, |
|
WitnessScript: htlcWitnessScript, |
|
Output: htlcOutput, |
|
HashType: txscript.SigHashAll, |
|
SigHashes: sweepTxSigHashes, |
|
InputIndex: 0, |
|
} |
|
|
|
return ReceiverHtlcSpendTimeout(aliceSigner, signDesc, |
|
sweepTx, int32(cltvTimeout-2)) |
|
}), |
|
false, |
|
}, |
|
{ |
|
// refund w/ valid lock time |
|
makeWitnessTestCase(t, func() (wire.TxWitness, error) { |
|
genCommitTx(false) |
|
genSweepTx(false) |
|
|
|
signDesc := &SignDescriptor{ |
|
KeyDesc: keychain.KeyDescriptor{ |
|
PubKey: aliceKeyPub, |
|
}, |
|
SingleTweak: aliceCommitTweak, |
|
WitnessScript: htlcWitnessScript, |
|
Output: htlcOutput, |
|
HashType: txscript.SigHashAll, |
|
SigHashes: sweepTxSigHashes, |
|
InputIndex: 0, |
|
} |
|
|
|
return ReceiverHtlcSpendTimeout(aliceSigner, signDesc, |
|
sweepTx, int32(cltvTimeout)) |
|
}), |
|
true, |
|
}, |
|
{ |
|
// refund w/ valid lock time, and enforced locktime in |
|
// HTLC scripts. |
|
makeWitnessTestCase(t, func() (wire.TxWitness, error) { |
|
// Make a commit tx that needs confirmation for |
|
// HTLC output to be spent. |
|
genCommitTx(true) |
|
|
|
// Generate a sweep with the locktime set. |
|
genSweepTx(true) |
|
|
|
signDesc := &SignDescriptor{ |
|
KeyDesc: keychain.KeyDescriptor{ |
|
PubKey: aliceKeyPub, |
|
}, |
|
SingleTweak: aliceCommitTweak, |
|
WitnessScript: htlcWitnessScript, |
|
Output: htlcOutput, |
|
HashType: txscript.SigHashAll, |
|
SigHashes: sweepTxSigHashes, |
|
InputIndex: 0, |
|
} |
|
|
|
return ReceiverHtlcSpendTimeout(aliceSigner, signDesc, |
|
sweepTx, int32(cltvTimeout)) |
|
}), |
|
true, |
|
}, |
|
{ |
|
// refund w/ valid lock time, but no sequence set in |
|
// sweep tx trying to spend CSV locked HTLC output. |
|
makeWitnessTestCase(t, func() (wire.TxWitness, error) { |
|
// Generate commitment tx with 1 CSV locked |
|
// HTLC. |
|
genCommitTx(true) |
|
|
|
// Generate sweep tx that doesn't have locktime |
|
// enabled. |
|
genSweepTx(false) |
|
|
|
signDesc := &SignDescriptor{ |
|
KeyDesc: keychain.KeyDescriptor{ |
|
PubKey: aliceKeyPub, |
|
}, |
|
SingleTweak: aliceCommitTweak, |
|
WitnessScript: htlcWitnessScript, |
|
Output: htlcOutput, |
|
HashType: txscript.SigHashAll, |
|
SigHashes: sweepTxSigHashes, |
|
InputIndex: 0, |
|
} |
|
|
|
return ReceiverHtlcSpendTimeout(aliceSigner, signDesc, |
|
sweepTx, int32(cltvTimeout)) |
|
}), |
|
false, |
|
}, |
|
} |
|
|
|
for i, testCase := range testCases { |
|
sweepTx.TxIn[0].Witness = testCase.witness() |
|
|
|
newEngine := func() (*txscript.Engine, error) { |
|
return txscript.NewEngine(htlcPkScript, |
|
sweepTx, 0, txscript.StandardVerifyFlags, nil, |
|
nil, int64(paymentAmt)) |
|
} |
|
|
|
assertEngineExecution(t, i, testCase.valid, newEngine) |
|
} |
|
} |
|
|
|
// TestSecondLevelHtlcSpends tests all the possible redemption clauses from the |
|
// HTLC success and timeout covenant transactions. |
|
func TestSecondLevelHtlcSpends(t *testing.T) { |
|
t.Parallel() |
|
|
|
// We'll start be creating a creating a 2BTC HTLC. |
|
const htlcAmt = btcutil.Amount(2 * 10e8) |
|
|
|
// In all of our scenarios, the CSV timeout to claim a self output will |
|
// be 5 blocks. |
|
const claimDelay = 5 |
|
|
|
// First we'll set up some initial key state for Alice and Bob that |
|
// will be used in the scripts we created below. |
|
aliceKeyPriv, aliceKeyPub := btcec.PrivKeyFromBytes(btcec.S256(), |
|
testWalletPrivKey) |
|
bobKeyPriv, bobKeyPub := btcec.PrivKeyFromBytes(btcec.S256(), |
|
bobsPrivKey) |
|
|
|
revokePreimage := testHdSeed.CloneBytes() |
|
commitSecret, commitPoint := btcec.PrivKeyFromBytes( |
|
btcec.S256(), revokePreimage) |
|
|
|
// As we're modeling this as Bob sweeping the HTLC on-chain from his |
|
// commitment transaction after a period of time, we'll be using a |
|
// revocation key derived from Alice's base point and his secret. |
|
revocationKey := DeriveRevocationPubkey(aliceKeyPub, commitPoint) |
|
|
|
// Next, craft a fake HTLC outpoint that we'll use to generate the |
|
// sweeping transaction using. |
|
txid, err := chainhash.NewHash(testHdSeed.CloneBytes()) |
|
if err != nil { |
|
t.Fatalf("unable to create txid: %v", err) |
|
} |
|
htlcOutPoint := &wire.OutPoint{ |
|
Hash: *txid, |
|
Index: 0, |
|
} |
|
sweepTx := wire.NewMsgTx(2) |
|
sweepTx.AddTxIn(wire.NewTxIn(htlcOutPoint, nil, nil)) |
|
sweepTx.AddTxOut( |
|
&wire.TxOut{ |
|
PkScript: []byte("doesn't matter"), |
|
Value: 1 * 10e8, |
|
}, |
|
) |
|
sweepTxSigHashes := txscript.NewTxSigHashes(sweepTx) |
|
|
|
// The delay key will be crafted using Bob's public key as the output |
|
// we created will be spending from Alice's commitment transaction. |
|
delayKey := TweakPubKey(bobKeyPub, commitPoint) |
|
|
|
// The commit tweak will be required in order for Bob to derive the |
|
// proper key need to spend the output. |
|
commitTweak := SingleTweakBytes(commitPoint, bobKeyPub) |
|
|
|
// Finally we'll generate the HTLC script itself that we'll be spending |
|
// from. The revocation clause can be claimed by Alice, while Bob can |
|
// sweep the output after a particular delay. |
|
htlcWitnessScript, err := SecondLevelHtlcScript(revocationKey, |
|
delayKey, claimDelay) |
|
if err != nil { |
|
t.Fatalf("unable to create htlc script: %v", err) |
|
} |
|
htlcPkScript, err := WitnessScriptHash(htlcWitnessScript) |
|
if err != nil { |
|
t.Fatalf("unable to create htlc output: %v", err) |
|
} |
|
|
|
htlcOutput := &wire.TxOut{ |
|
PkScript: htlcPkScript, |
|
Value: int64(htlcAmt), |
|
} |
|
|
|
// TODO(roasbeef): make actually use timeout/success txns? |
|
|
|
// Finally, we'll create mock signers for both of them based on their |
|
// private keys. This test simplifies a bit and uses the same key as |
|
// the base point for all scripts and derivations. |
|
bobSigner := &MockSigner{Privkeys: []*btcec.PrivateKey{bobKeyPriv}} |
|
aliceSigner := &MockSigner{Privkeys: []*btcec.PrivateKey{aliceKeyPriv}} |
|
|
|
testCases := []struct { |
|
witness func() wire.TxWitness |
|
valid bool |
|
}{ |
|
{ |
|
// Sender of the HTLC attempts to activate the |
|
// revocation clause, but uses the wrong key (fails to |
|
// use the double tweak in this case). |
|
makeWitnessTestCase(t, func() (wire.TxWitness, error) { |
|
signDesc := &SignDescriptor{ |
|
KeyDesc: keychain.KeyDescriptor{ |
|
PubKey: aliceKeyPub, |
|
}, |
|
WitnessScript: htlcWitnessScript, |
|
Output: htlcOutput, |
|
HashType: txscript.SigHashAll, |
|
SigHashes: sweepTxSigHashes, |
|
InputIndex: 0, |
|
} |
|
|
|
return HtlcSpendRevoke(aliceSigner, signDesc, |
|
sweepTx) |
|
}), |
|
false, |
|
}, |
|
{ |
|
// Sender of HTLC activates the revocation clause. |
|
makeWitnessTestCase(t, func() (wire.TxWitness, error) { |
|
signDesc := &SignDescriptor{ |
|
KeyDesc: keychain.KeyDescriptor{ |
|
PubKey: aliceKeyPub, |
|
}, |
|
DoubleTweak: commitSecret, |
|
WitnessScript: htlcWitnessScript, |
|
Output: htlcOutput, |
|
HashType: txscript.SigHashAll, |
|
SigHashes: sweepTxSigHashes, |
|
InputIndex: 0, |
|
} |
|
|
|
return HtlcSpendRevoke(aliceSigner, signDesc, |
|
sweepTx) |
|
}), |
|
true, |
|
}, |
|
{ |
|
// Receiver of the HTLC attempts to sweep, but tries to |
|
// do so pre-maturely with a smaller CSV delay (2 |
|
// blocks instead of 5 blocks). |
|
makeWitnessTestCase(t, func() (wire.TxWitness, error) { |
|
signDesc := &SignDescriptor{ |
|
KeyDesc: keychain.KeyDescriptor{ |
|
PubKey: bobKeyPub, |
|
}, |
|
SingleTweak: commitTweak, |
|
WitnessScript: htlcWitnessScript, |
|
Output: htlcOutput, |
|
HashType: txscript.SigHashAll, |
|
SigHashes: sweepTxSigHashes, |
|
InputIndex: 0, |
|
} |
|
|
|
return HtlcSpendSuccess(bobSigner, signDesc, |
|
sweepTx, claimDelay-3) |
|
}), |
|
false, |
|
}, |
|
{ |
|
// Receiver of the HTLC sweeps with the proper CSV |
|
// delay, but uses the wrong key (leaves off the single |
|
// tweak). |
|
makeWitnessTestCase(t, func() (wire.TxWitness, error) { |
|
signDesc := &SignDescriptor{ |
|
KeyDesc: keychain.KeyDescriptor{ |
|
PubKey: bobKeyPub, |
|
}, |
|
WitnessScript: htlcWitnessScript, |
|
Output: htlcOutput, |
|
HashType: txscript.SigHashAll, |
|
SigHashes: sweepTxSigHashes, |
|
InputIndex: 0, |
|
} |
|
|
|
return HtlcSpendSuccess(bobSigner, signDesc, |
|
sweepTx, claimDelay) |
|
}), |
|
false, |
|
}, |
|
{ |
|
// Receiver of the HTLC sweeps with the proper CSV |
|
// delay, and the correct key. |
|
makeWitnessTestCase(t, func() (wire.TxWitness, error) { |
|
signDesc := &SignDescriptor{ |
|
KeyDesc: keychain.KeyDescriptor{ |
|
PubKey: bobKeyPub, |
|
}, |
|
SingleTweak: commitTweak, |
|
WitnessScript: htlcWitnessScript, |
|
Output: htlcOutput, |
|
HashType: txscript.SigHashAll, |
|
SigHashes: sweepTxSigHashes, |
|
InputIndex: 0, |
|
} |
|
|
|
return HtlcSpendSuccess(bobSigner, signDesc, |
|
sweepTx, claimDelay) |
|
}), |
|
true, |
|
}, |
|
} |
|
|
|
for i, testCase := range testCases { |
|
sweepTx.TxIn[0].Witness = testCase.witness() |
|
|
|
newEngine := func() (*txscript.Engine, error) { |
|
return txscript.NewEngine(htlcPkScript, |
|
sweepTx, 0, txscript.StandardVerifyFlags, nil, |
|
nil, int64(htlcAmt)) |
|
} |
|
|
|
assertEngineExecution(t, i, testCase.valid, newEngine) |
|
} |
|
} |
|
|
|
// TestCommitSpendToRemoteConfirmed checks that the delayed version of the |
|
// to_remote version can only be spent by the owner, and after one |
|
// confirmation. |
|
func TestCommitSpendToRemoteConfirmed(t *testing.T) { |
|
t.Parallel() |
|
|
|
const outputVal = btcutil.Amount(2 * 10e8) |
|
|
|
aliceKeyPriv, aliceKeyPub := btcec.PrivKeyFromBytes(btcec.S256(), |
|
testWalletPrivKey) |
|
|
|
txid, err := chainhash.NewHash(testHdSeed.CloneBytes()) |
|
if err != nil { |
|
t.Fatalf("unable to create txid: %v", err) |
|
} |
|
commitOut := &wire.OutPoint{ |
|
Hash: *txid, |
|
Index: 0, |
|
} |
|
commitScript, err := CommitScriptToRemoteConfirmed(aliceKeyPub) |
|
if err != nil { |
|
t.Fatalf("unable to create htlc script: %v", err) |
|
} |
|
commitPkScript, err := WitnessScriptHash(commitScript) |
|
if err != nil { |
|
t.Fatalf("unable to create htlc output: %v", err) |
|
} |
|
|
|
commitOutput := &wire.TxOut{ |
|
PkScript: commitPkScript, |
|
Value: int64(outputVal), |
|
} |
|
|
|
sweepTx := wire.NewMsgTx(2) |
|
sweepTx.AddTxIn(wire.NewTxIn(commitOut, nil, nil)) |
|
sweepTx.AddTxOut( |
|
&wire.TxOut{ |
|
PkScript: []byte("doesn't matter"), |
|
Value: 1 * 10e8, |
|
}, |
|
) |
|
|
|
aliceSigner := &MockSigner{Privkeys: []*btcec.PrivateKey{aliceKeyPriv}} |
|
|
|
testCases := []struct { |
|
witness func() wire.TxWitness |
|
valid bool |
|
}{ |
|
{ |
|
// Alice can spend after the a CSV delay has passed. |
|
makeWitnessTestCase(t, func() (wire.TxWitness, error) { |
|
sweepTx.TxIn[0].Sequence = LockTimeToSequence(false, 1) |
|
sweepTxSigHashes := txscript.NewTxSigHashes(sweepTx) |
|
|
|
signDesc := &SignDescriptor{ |
|
KeyDesc: keychain.KeyDescriptor{ |
|
PubKey: aliceKeyPub, |
|
}, |
|
WitnessScript: commitScript, |
|
Output: commitOutput, |
|
HashType: txscript.SigHashAll, |
|
SigHashes: sweepTxSigHashes, |
|
InputIndex: 0, |
|
} |
|
|
|
return CommitSpendToRemoteConfirmed(aliceSigner, signDesc, |
|
sweepTx) |
|
}), |
|
true, |
|
}, |
|
{ |
|
// Alice cannot spend output without sequence set. |
|
makeWitnessTestCase(t, func() (wire.TxWitness, error) { |
|
sweepTx.TxIn[0].Sequence = wire.MaxTxInSequenceNum |
|
sweepTxSigHashes := txscript.NewTxSigHashes(sweepTx) |
|
|
|
signDesc := &SignDescriptor{ |
|
KeyDesc: keychain.KeyDescriptor{ |
|
PubKey: aliceKeyPub, |
|
}, |
|
WitnessScript: commitScript, |
|
Output: commitOutput, |
|
HashType: txscript.SigHashAll, |
|
SigHashes: sweepTxSigHashes, |
|
InputIndex: 0, |
|
} |
|
|
|
return CommitSpendToRemoteConfirmed(aliceSigner, signDesc, |
|
sweepTx) |
|
}), |
|
false, |
|
}, |
|
} |
|
|
|
for i, testCase := range testCases { |
|
sweepTx.TxIn[0].Witness = testCase.witness() |
|
|
|
newEngine := func() (*txscript.Engine, error) { |
|
return txscript.NewEngine(commitPkScript, |
|
sweepTx, 0, txscript.StandardVerifyFlags, nil, |
|
nil, int64(outputVal)) |
|
} |
|
|
|
assertEngineExecution(t, i, testCase.valid, newEngine) |
|
} |
|
} |
|
|
|
// TestSpendAnchor checks that we can spend the anchors using the various spend |
|
// paths. |
|
func TestSpendAnchor(t *testing.T) { |
|
t.Parallel() |
|
|
|
const anchorSize = 294 |
|
|
|
// First we'll set up some initial key state for Alice. |
|
aliceKeyPriv, aliceKeyPub := btcec.PrivKeyFromBytes(btcec.S256(), |
|
testWalletPrivKey) |
|
|
|
// Create a fake anchor outpoint that we'll use to generate the |
|
// sweeping transaction. |
|
txid, err := chainhash.NewHash(testHdSeed.CloneBytes()) |
|
if err != nil { |
|
t.Fatalf("unable to create txid: %v", err) |
|
} |
|
anchorOutPoint := &wire.OutPoint{ |
|
Hash: *txid, |
|
Index: 0, |
|
} |
|
|
|
sweepTx := wire.NewMsgTx(2) |
|
sweepTx.AddTxIn(wire.NewTxIn(anchorOutPoint, nil, nil)) |
|
sweepTx.AddTxOut( |
|
&wire.TxOut{ |
|
PkScript: []byte("doesn't matter"), |
|
Value: 1 * 10e8, |
|
}, |
|
) |
|
|
|
// Generate the anchor script that can be spent by Alice immediately, |
|
// or by anyone after 16 blocks. |
|
anchorScript, err := CommitScriptAnchor(aliceKeyPub) |
|
if err != nil { |
|
t.Fatalf("unable to create htlc script: %v", err) |
|
} |
|
anchorPkScript, err := WitnessScriptHash(anchorScript) |
|
if err != nil { |
|
t.Fatalf("unable to create htlc output: %v", err) |
|
} |
|
|
|
anchorOutput := &wire.TxOut{ |
|
PkScript: anchorPkScript, |
|
Value: int64(anchorSize), |
|
} |
|
|
|
// Create mock signer for Alice. |
|
aliceSigner := &MockSigner{Privkeys: []*btcec.PrivateKey{aliceKeyPriv}} |
|
|
|
testCases := []struct { |
|
witness func() wire.TxWitness |
|
valid bool |
|
}{ |
|
{ |
|
// Alice can spend immediately. |
|
makeWitnessTestCase(t, func() (wire.TxWitness, error) { |
|
sweepTx.TxIn[0].Sequence = wire.MaxTxInSequenceNum |
|
sweepTxSigHashes := txscript.NewTxSigHashes(sweepTx) |
|
|
|
signDesc := &SignDescriptor{ |
|
KeyDesc: keychain.KeyDescriptor{ |
|
PubKey: aliceKeyPub, |
|
}, |
|
WitnessScript: anchorScript, |
|
Output: anchorOutput, |
|
HashType: txscript.SigHashAll, |
|
SigHashes: sweepTxSigHashes, |
|
InputIndex: 0, |
|
} |
|
|
|
return CommitSpendAnchor(aliceSigner, signDesc, |
|
sweepTx) |
|
}), |
|
true, |
|
}, |
|
{ |
|
// Anyone can spend after 16 blocks. |
|
makeWitnessTestCase(t, func() (wire.TxWitness, error) { |
|
sweepTx.TxIn[0].Sequence = LockTimeToSequence(false, 16) |
|
return CommitSpendAnchorAnyone(anchorScript) |
|
}), |
|
true, |
|
}, |
|
{ |
|
// Anyone cannot spend before 16 blocks. |
|
makeWitnessTestCase(t, func() (wire.TxWitness, error) { |
|
sweepTx.TxIn[0].Sequence = LockTimeToSequence(false, 15) |
|
return CommitSpendAnchorAnyone(anchorScript) |
|
}), |
|
false, |
|
}, |
|
} |
|
|
|
for i, testCase := range testCases { |
|
sweepTx.TxIn[0].Witness = testCase.witness() |
|
|
|
newEngine := func() (*txscript.Engine, error) { |
|
return txscript.NewEngine(anchorPkScript, |
|
sweepTx, 0, txscript.StandardVerifyFlags, nil, |
|
nil, int64(anchorSize)) |
|
} |
|
|
|
assertEngineExecution(t, i, testCase.valid, newEngine) |
|
} |
|
} |
|
|
|
// TestSpecificationKeyDerivation implements the test vectors provided in |
|
// BOLT-03, Appendix E. |
|
func TestSpecificationKeyDerivation(t *testing.T) { |
|
const ( |
|
baseSecretHex = "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f" |
|
perCommitmentSecretHex = "1f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100" |
|
basePointHex = "036d6caac248af96f6afa7f904f550253a0f3ef3f5aa2fe6838a95b216691468e2" |
|
perCommitmentPointHex = "025f7117a78150fe2ef97db7cfc83bd57b2e2c0d0dd25eaf467a4a1c2a45ce1486" |
|
) |
|
|
|
baseSecret, err := privkeyFromHex(baseSecretHex) |
|
if err != nil { |
|
t.Fatalf("Failed to parse serialized privkey: %v", err) |
|
} |
|
perCommitmentSecret, err := privkeyFromHex(perCommitmentSecretHex) |
|
if err != nil { |
|
t.Fatalf("Failed to parse serialized privkey: %v", err) |
|
} |
|
basePoint, err := pubkeyFromHex(basePointHex) |
|
if err != nil { |
|
t.Fatalf("Failed to parse serialized pubkey: %v", err) |
|
} |
|
perCommitmentPoint, err := pubkeyFromHex(perCommitmentPointHex) |
|
if err != nil { |
|
t.Fatalf("Failed to parse serialized pubkey: %v", err) |
|
} |
|
|
|
// name: derivation of key from basepoint and per_commitment_point |
|
const expectedLocalKeyHex = "0235f2dbfaa89b57ec7b055afe29849ef7ddfeb1cefdb9ebdc43f5494984db29e5" |
|
actualLocalKey := TweakPubKey(basePoint, perCommitmentPoint) |
|
actualLocalKeyHex := pubkeyToHex(actualLocalKey) |
|
if actualLocalKeyHex != expectedLocalKeyHex { |
|
t.Errorf("Incorrect derivation of local public key: "+ |
|
"expected %v, got %v", expectedLocalKeyHex, actualLocalKeyHex) |
|
} |
|
|
|
// name: derivation of secret key from basepoint secret and per_commitment_secret |
|
const expectedLocalPrivKeyHex = "cbced912d3b21bf196a766651e436aff192362621ce317704ea2f75d87e7be0f" |
|
tweak := SingleTweakBytes(perCommitmentPoint, basePoint) |
|
actualLocalPrivKey := TweakPrivKey(baseSecret, tweak) |
|
actualLocalPrivKeyHex := privkeyToHex(actualLocalPrivKey) |
|
if actualLocalPrivKeyHex != expectedLocalPrivKeyHex { |
|
t.Errorf("Incorrect derivation of local private key: "+ |
|
"expected %v, got %v, %v", expectedLocalPrivKeyHex, |
|
actualLocalPrivKeyHex, hex.EncodeToString(tweak)) |
|
} |
|
|
|
// name: derivation of revocation key from basepoint and per_commitment_point |
|
const expectedRevocationKeyHex = "02916e326636d19c33f13e8c0c3a03dd157f332f3e99c317c141dd865eb01f8ff0" |
|
actualRevocationKey := DeriveRevocationPubkey(basePoint, perCommitmentPoint) |
|
actualRevocationKeyHex := pubkeyToHex(actualRevocationKey) |
|
if actualRevocationKeyHex != expectedRevocationKeyHex { |
|
t.Errorf("Incorrect derivation of revocation public key: "+ |
|
"expected %v, got %v", expectedRevocationKeyHex, |
|
actualRevocationKeyHex) |
|
} |
|
|
|
// name: derivation of revocation secret from basepoint_secret and per_commitment_secret |
|
const expectedRevocationPrivKeyHex = "d09ffff62ddb2297ab000cc85bcb4283fdeb6aa052affbc9dddcf33b61078110" |
|
actualRevocationPrivKey := DeriveRevocationPrivKey(baseSecret, |
|
perCommitmentSecret) |
|
actualRevocationPrivKeyHex := privkeyToHex(actualRevocationPrivKey) |
|
if actualRevocationPrivKeyHex != expectedRevocationPrivKeyHex { |
|
t.Errorf("Incorrect derivation of revocation private key: "+ |
|
"expected %v, got %v", expectedRevocationPrivKeyHex, |
|
actualRevocationPrivKeyHex) |
|
} |
|
}
|
|
|