mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-08-03 02:02:17 +02:00
input: add scripts for new script enforced lease commitment type
The new commitment type consists of adding an additional CLTV requirement to guarantee a leased channel's expiration on any commitment and HTLC outputs that pay directly to the channel initiator.
This commit is contained in:
committed by
Olaoluwa Osuntokun
parent
564ec0fd9b
commit
b84307e62e
@@ -775,6 +775,78 @@ func SecondLevelHtlcScript(revocationKey, delayKey *btcec.PublicKey,
|
||||
return builder.Script()
|
||||
}
|
||||
|
||||
// LeaseSecondLevelHtlcScript is the uniform script that's used as the output for
|
||||
// the second-level HTLC transactions. The second level transaction acts as a
|
||||
// sort of covenant, ensuring that a 2-of-2 multi-sig output can only be
|
||||
// spent in a particular way, and to a particular output.
|
||||
//
|
||||
// Possible Input Scripts:
|
||||
// * To revoke an HTLC output that has been transitioned to the claim+delay
|
||||
// state:
|
||||
// * <revoke sig> 1
|
||||
//
|
||||
// * To claim an HTLC output, either with a pre-image or due to a timeout:
|
||||
// * <delay sig> 0
|
||||
//
|
||||
// OP_IF
|
||||
// <revoke key>
|
||||
// OP_ELSE
|
||||
// <lease maturity in blocks>
|
||||
// OP_CHECKLOCKTIMEVERIFY
|
||||
// OP_DROP
|
||||
// <delay in blocks>
|
||||
// OP_CHECKSEQUENCEVERIFY
|
||||
// OP_DROP
|
||||
// <delay key>
|
||||
// OP_ENDIF
|
||||
// OP_CHECKSIG
|
||||
func LeaseSecondLevelHtlcScript(revocationKey, delayKey *btcec.PublicKey,
|
||||
csvDelay, cltvExpiry uint32) ([]byte, error) {
|
||||
|
||||
builder := txscript.NewScriptBuilder()
|
||||
|
||||
// If this is the revocation clause for this script is to be executed,
|
||||
// the spender will push a 1, forcing us to hit the true clause of this
|
||||
// if statement.
|
||||
builder.AddOp(txscript.OP_IF)
|
||||
|
||||
// If this this is the revocation case, then we'll push the revocation
|
||||
// public key on the stack.
|
||||
builder.AddData(revocationKey.SerializeCompressed())
|
||||
|
||||
// Otherwise, this is either the sender or receiver of the HTLC
|
||||
// attempting to claim the HTLC output.
|
||||
builder.AddOp(txscript.OP_ELSE)
|
||||
|
||||
// The channel initiator always has the additional channel lease
|
||||
// expiration constraint for outputs that pay to them which must be
|
||||
// satisfied.
|
||||
builder.AddInt64(int64(cltvExpiry))
|
||||
builder.AddOp(txscript.OP_CHECKLOCKTIMEVERIFY)
|
||||
builder.AddOp(txscript.OP_DROP)
|
||||
|
||||
// In order to give the other party time to execute the revocation
|
||||
// clause above, we require a relative timeout to pass before the
|
||||
// output can be spent.
|
||||
builder.AddInt64(int64(csvDelay))
|
||||
builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY)
|
||||
builder.AddOp(txscript.OP_DROP)
|
||||
|
||||
// If the relative timelock passes, then we'll add the delay key to the
|
||||
// stack to ensure that we properly authenticate the spending party.
|
||||
builder.AddData(delayKey.SerializeCompressed())
|
||||
|
||||
// Close out the if statement.
|
||||
builder.AddOp(txscript.OP_ENDIF)
|
||||
|
||||
// In either case, we'll ensure that only either the party possessing
|
||||
// the revocation private key, or the delay private key is able to
|
||||
// spend this output.
|
||||
builder.AddOp(txscript.OP_CHECKSIG)
|
||||
|
||||
return builder.Script()
|
||||
}
|
||||
|
||||
// HtlcSpendSuccess spends a second-level HTLC output. This function is to be
|
||||
// used by the sender of an HTLC to claim the output after a relative timeout
|
||||
// or the receiver of the HTLC to claim on-chain with the pre-image.
|
||||
@@ -938,6 +1010,66 @@ func CommitScriptToSelf(csvTimeout uint32, selfKey, revokeKey *btcec.PublicKey)
|
||||
return builder.Script()
|
||||
}
|
||||
|
||||
// LeaseCommitScriptToSelf constructs the public key script for the output on the
|
||||
// commitment transaction paying to the "owner" of said commitment transaction.
|
||||
// If the other party learns of the preimage to the revocation hash, then they
|
||||
// can claim all the settled funds in the channel, plus the unsettled funds.
|
||||
//
|
||||
// Possible Input Scripts:
|
||||
// REVOKE: <sig> 1
|
||||
// SENDRSWEEP: <sig> <emptyvector>
|
||||
//
|
||||
// Output Script:
|
||||
// OP_IF
|
||||
// <revokeKey>
|
||||
// OP_ELSE
|
||||
// <absoluteLeaseExpiry> OP_CHECKLOCKTIMEVERIFY OP_DROP
|
||||
// <numRelativeBlocks> OP_CHECKSEQUENCEVERIFY OP_DROP
|
||||
// <timeKey>
|
||||
// OP_ENDIF
|
||||
// OP_CHECKSIG
|
||||
func LeaseCommitScriptToSelf(selfKey, revokeKey *btcec.PublicKey,
|
||||
csvTimeout, leaseExpiry uint32) ([]byte, error) {
|
||||
|
||||
// This script is spendable under two conditions: either the
|
||||
// 'csvTimeout' has passed and we can redeem our funds, or they can
|
||||
// produce a valid signature with the revocation public key. The
|
||||
// revocation public key will *only* be known to the other party if we
|
||||
// have divulged the revocation hash, allowing them to homomorphically
|
||||
// derive the proper private key which corresponds to the revoke public
|
||||
// key.
|
||||
builder := txscript.NewScriptBuilder()
|
||||
|
||||
builder.AddOp(txscript.OP_IF)
|
||||
|
||||
// If a valid signature using the revocation key is presented, then
|
||||
// allow an immediate spend provided the proper signature.
|
||||
builder.AddData(revokeKey.SerializeCompressed())
|
||||
|
||||
builder.AddOp(txscript.OP_ELSE)
|
||||
|
||||
// Otherwise, we can re-claim our funds after once the CLTV lease
|
||||
// maturity has been met, along with the CSV delay of 'csvTimeout'
|
||||
// timeout blocks, and a valid signature.
|
||||
builder.AddInt64(int64(leaseExpiry))
|
||||
builder.AddOp(txscript.OP_CHECKLOCKTIMEVERIFY)
|
||||
builder.AddOp(txscript.OP_DROP)
|
||||
|
||||
builder.AddInt64(int64(csvTimeout))
|
||||
builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY)
|
||||
builder.AddOp(txscript.OP_DROP)
|
||||
|
||||
builder.AddData(selfKey.SerializeCompressed())
|
||||
|
||||
builder.AddOp(txscript.OP_ENDIF)
|
||||
|
||||
// Finally, we'll validate the signature against the public key that's
|
||||
// left on the top of the stack.
|
||||
builder.AddOp(txscript.OP_CHECKSIG)
|
||||
|
||||
return builder.Script()
|
||||
}
|
||||
|
||||
// CommitSpendTimeout constructs a valid witness allowing the owner of a
|
||||
// particular commitment transaction to spend the output returning settled
|
||||
// funds back to themselves after a relative block timeout. In order to
|
||||
@@ -1082,6 +1214,40 @@ func CommitScriptToRemoteConfirmed(key *btcec.PublicKey) ([]byte, error) {
|
||||
return builder.Script()
|
||||
}
|
||||
|
||||
// LeaseCommitScriptToRemoteConfirmed constructs the script for the output on
|
||||
// the commitment transaction paying to the remote party of said commitment
|
||||
// transaction. The money can only be spend after one confirmation.
|
||||
//
|
||||
// Possible Input Scripts:
|
||||
// SWEEP: <sig>
|
||||
//
|
||||
// Output Script:
|
||||
// <key> OP_CHECKSIGVERIFY
|
||||
// <lease maturity in blocks> OP_CHECKLOCKTIMEVERIFY OP_DROP
|
||||
// 1 OP_CHECKSEQUENCEVERIFY
|
||||
func LeaseCommitScriptToRemoteConfirmed(key *btcec.PublicKey,
|
||||
leaseExpiry uint32) ([]byte, error) {
|
||||
|
||||
builder := txscript.NewScriptBuilder()
|
||||
|
||||
// Only the given key can spend the output.
|
||||
builder.AddData(key.SerializeCompressed())
|
||||
builder.AddOp(txscript.OP_CHECKSIGVERIFY)
|
||||
|
||||
// The channel initiator always has the additional channel lease
|
||||
// expiration constraint for outputs that pay to them which must be
|
||||
// satisfied.
|
||||
builder.AddInt64(int64(leaseExpiry))
|
||||
builder.AddOp(txscript.OP_CHECKLOCKTIMEVERIFY)
|
||||
builder.AddOp(txscript.OP_DROP)
|
||||
|
||||
// Check that it has one confirmation.
|
||||
builder.AddOp(txscript.OP_1)
|
||||
builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY)
|
||||
|
||||
return builder.Script()
|
||||
}
|
||||
|
||||
// CommitSpendToRemoteConfirmed constructs a valid witness allowing a node to
|
||||
// spend their settled output on the counterparty's commitment transaction when
|
||||
// it has one confirmetion. This is used for the anchor channel type. The
|
||||
|
@@ -13,6 +13,7 @@ import (
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/btcutil"
|
||||
"github.com/lightningnetwork/lnd/keychain"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// assertEngineExecution executes the VM returned by the newEngine closure,
|
||||
@@ -1184,6 +1185,452 @@ func TestSecondLevelHtlcSpends(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestLeaseSecondLevelHtlcSpends tests all the possible redemption clauses from
|
||||
// the HTLC success and timeout covenant transactions in script enforced lease
|
||||
// commitments.
|
||||
func TestLeaseSecondLevelHtlcSpends(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
|
||||
|
||||
// In all of our scenarios, the CLTV timelock will expire at height
|
||||
// 1337.
|
||||
const leaseExpiry = 1337
|
||||
|
||||
// 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())
|
||||
require.NoError(t, 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 := LeaseSecondLevelHtlcScript(
|
||||
revocationKey, delayKey, claimDelay, leaseExpiry,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
htlcPkScript, err := WitnessScriptHash(htlcWitnessScript)
|
||||
require.NoError(t, err)
|
||||
|
||||
htlcOutput := &wire.TxOut{
|
||||
PkScript: htlcPkScript,
|
||||
Value: int64(htlcAmt),
|
||||
}
|
||||
|
||||
// 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), even after the CLTV
|
||||
// timelock expires.
|
||||
makeWitnessTestCase(t, func() (wire.TxWitness, error) {
|
||||
sweepTx.LockTime = leaseExpiry
|
||||
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
|
||||
// and after the CLTV timelock expires, but uses the
|
||||
// wrong key (leaves off the single tweak).
|
||||
makeWitnessTestCase(t, func() (wire.TxWitness, error) {
|
||||
sweepTx.LockTime = leaseExpiry
|
||||
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, but before the CTLV
|
||||
// timelock expires.
|
||||
makeWitnessTestCase(t, func() (wire.TxWitness, error) {
|
||||
sweepTx.LockTime = 0
|
||||
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,
|
||||
)
|
||||
}),
|
||||
false,
|
||||
},
|
||||
{
|
||||
// Receiver of the HTLC sweeps with the proper CSV
|
||||
// delay, and the correct key after the CTLV timelock
|
||||
// expires.
|
||||
makeWitnessTestCase(t, func() (wire.TxWitness, error) {
|
||||
sweepTx.LockTime = leaseExpiry
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
// TestLeaseCommmitSpendToSelf tests all the possible redemption clauses from
|
||||
// the to_self output in a script enforced lease commitment transaction.
|
||||
func TestLeaseCommmitSpendToSelf(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const (
|
||||
outputVal = btcutil.Amount(2 * 10e8)
|
||||
csvDelay = 5
|
||||
leaseExpiry = 1337
|
||||
)
|
||||
|
||||
// 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,
|
||||
)
|
||||
|
||||
// We'll have Bob take the revocation path in some cases.
|
||||
revokePreimage := testHdSeed.CloneBytes()
|
||||
commitSecret, commitPoint := btcec.PrivKeyFromBytes(
|
||||
btcec.S256(), revokePreimage,
|
||||
)
|
||||
revocationKey := DeriveRevocationPubkey(bobKeyPub, commitPoint)
|
||||
|
||||
// Construct the script enforced lease to_self commitment transaction
|
||||
// output.
|
||||
txid, err := chainhash.NewHash(testHdSeed.CloneBytes())
|
||||
require.NoError(t, err)
|
||||
commitOut := &wire.OutPoint{
|
||||
Hash: *txid,
|
||||
Index: 0,
|
||||
}
|
||||
commitScript, err := LeaseCommitScriptToSelf(
|
||||
aliceKeyPub, revocationKey, csvDelay, leaseExpiry,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
commitPkScript, err := WitnessScriptHash(commitScript)
|
||||
require.NoError(t, 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,
|
||||
},
|
||||
)
|
||||
|
||||
// Create mock signers for both parties to ensure signatures are
|
||||
// produced and verified correctly.
|
||||
aliceSigner := &MockSigner{Privkeys: []*btcec.PrivateKey{aliceKeyPriv}}
|
||||
bobSigner := &MockSigner{Privkeys: []*btcec.PrivateKey{bobKeyPriv}}
|
||||
|
||||
testCases := []struct {
|
||||
witness func() wire.TxWitness
|
||||
valid bool
|
||||
}{
|
||||
{
|
||||
// Bob can spend with his revocation key, but not
|
||||
// without the proper tweak.
|
||||
makeWitnessTestCase(t, func() (wire.TxWitness, error) {
|
||||
sweepTxSigHashes := txscript.NewTxSigHashes(sweepTx)
|
||||
signDesc := &SignDescriptor{
|
||||
KeyDesc: keychain.KeyDescriptor{
|
||||
PubKey: bobKeyPub,
|
||||
},
|
||||
WitnessScript: commitScript,
|
||||
Output: commitOutput,
|
||||
HashType: txscript.SigHashAll,
|
||||
SigHashes: sweepTxSigHashes,
|
||||
InputIndex: 0,
|
||||
}
|
||||
|
||||
return CommitSpendRevoke(
|
||||
bobSigner, signDesc, sweepTx,
|
||||
)
|
||||
}),
|
||||
false,
|
||||
},
|
||||
{
|
||||
// Bob can spend with his revocation key with the proper
|
||||
// tweak.
|
||||
makeWitnessTestCase(t, func() (wire.TxWitness, error) {
|
||||
sweepTxSigHashes := txscript.NewTxSigHashes(sweepTx)
|
||||
signDesc := &SignDescriptor{
|
||||
KeyDesc: keychain.KeyDescriptor{
|
||||
PubKey: bobKeyPub,
|
||||
},
|
||||
DoubleTweak: commitSecret,
|
||||
WitnessScript: commitScript,
|
||||
Output: commitOutput,
|
||||
HashType: txscript.SigHashAll,
|
||||
SigHashes: sweepTxSigHashes,
|
||||
InputIndex: 0,
|
||||
}
|
||||
|
||||
return CommitSpendRevoke(
|
||||
bobSigner, signDesc, sweepTx,
|
||||
)
|
||||
}),
|
||||
true,
|
||||
},
|
||||
{
|
||||
// Alice cannot spend with the proper key before the CSV
|
||||
// delay and after the CLTV timelock has expired.
|
||||
makeWitnessTestCase(t, func() (wire.TxWitness, error) {
|
||||
sweepTx.LockTime = leaseExpiry
|
||||
sweepTx.TxIn[0].Sequence = LockTimeToSequence(
|
||||
false, csvDelay/2,
|
||||
)
|
||||
sweepTxSigHashes := txscript.NewTxSigHashes(sweepTx)
|
||||
signDesc := &SignDescriptor{
|
||||
KeyDesc: keychain.KeyDescriptor{
|
||||
PubKey: aliceKeyPub,
|
||||
},
|
||||
DoubleTweak: commitSecret,
|
||||
WitnessScript: commitScript,
|
||||
Output: commitOutput,
|
||||
HashType: txscript.SigHashAll,
|
||||
SigHashes: sweepTxSigHashes,
|
||||
InputIndex: 0,
|
||||
}
|
||||
|
||||
return CommitSpendTimeout(
|
||||
aliceSigner, signDesc, sweepTx,
|
||||
)
|
||||
}),
|
||||
false,
|
||||
},
|
||||
{
|
||||
// Alice cannot spend with the proper key after the CSV
|
||||
// delay and before the CLTV timelock has expired.
|
||||
makeWitnessTestCase(t, func() (wire.TxWitness, error) {
|
||||
sweepTx.LockTime = 0
|
||||
sweepTx.TxIn[0].Sequence = LockTimeToSequence(
|
||||
false, csvDelay,
|
||||
)
|
||||
sweepTxSigHashes := txscript.NewTxSigHashes(sweepTx)
|
||||
signDesc := &SignDescriptor{
|
||||
KeyDesc: keychain.KeyDescriptor{
|
||||
PubKey: aliceKeyPub,
|
||||
},
|
||||
DoubleTweak: commitSecret,
|
||||
WitnessScript: commitScript,
|
||||
Output: commitOutput,
|
||||
HashType: txscript.SigHashAll,
|
||||
SigHashes: sweepTxSigHashes,
|
||||
InputIndex: 0,
|
||||
}
|
||||
|
||||
return CommitSpendTimeout(
|
||||
aliceSigner, signDesc, sweepTx,
|
||||
)
|
||||
}),
|
||||
false,
|
||||
},
|
||||
{
|
||||
// Alice can spend with the proper key after the CSV
|
||||
// delay and CLTV timelock have expired.
|
||||
makeWitnessTestCase(t, func() (wire.TxWitness, error) {
|
||||
sweepTx.LockTime = leaseExpiry
|
||||
sweepTx.TxIn[0].Sequence = LockTimeToSequence(
|
||||
false, csvDelay,
|
||||
)
|
||||
sweepTxSigHashes := txscript.NewTxSigHashes(sweepTx)
|
||||
signDesc := &SignDescriptor{
|
||||
KeyDesc: keychain.KeyDescriptor{
|
||||
PubKey: aliceKeyPub,
|
||||
},
|
||||
WitnessScript: commitScript,
|
||||
Output: commitOutput,
|
||||
HashType: txscript.SigHashAll,
|
||||
SigHashes: sweepTxSigHashes,
|
||||
InputIndex: 0,
|
||||
}
|
||||
|
||||
return CommitSpendTimeout(
|
||||
aliceSigner, signDesc, sweepTx,
|
||||
)
|
||||
}),
|
||||
true,
|
||||
},
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
// TestCommitSpendToRemoteConfirmed checks that the delayed version of the
|
||||
// to_remote version can only be spent by the owner, and after one
|
||||
// confirmation.
|
||||
@@ -1291,6 +1738,148 @@ func TestCommitSpendToRemoteConfirmed(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestLeaseCommitSpendToRemoteConfirmed checks that the delayed version of the
|
||||
// to_remote version can only be spent by the owner, after one confirmation, and
|
||||
// after the lease expiration has been met.
|
||||
func TestLeaseCommitSpendToRemoteConfirmed(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const (
|
||||
outputVal = btcutil.Amount(2 * 10e8)
|
||||
leaseExpiry = 1337
|
||||
)
|
||||
|
||||
aliceKeyPriv, aliceKeyPub := btcec.PrivKeyFromBytes(
|
||||
btcec.S256(), testWalletPrivKey,
|
||||
)
|
||||
|
||||
txid, err := chainhash.NewHash(testHdSeed.CloneBytes())
|
||||
require.NoError(t, err)
|
||||
commitOut := &wire.OutPoint{
|
||||
Hash: *txid,
|
||||
Index: 0,
|
||||
}
|
||||
commitScript, err := LeaseCommitScriptToRemoteConfirmed(
|
||||
aliceKeyPub, leaseExpiry,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
commitPkScript, err := WitnessScriptHash(commitScript)
|
||||
require.NoError(t, 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 CSV delay and CLTV timelock
|
||||
// has passed.
|
||||
makeWitnessTestCase(t, func() (wire.TxWitness, error) {
|
||||
sweepTx.LockTime = leaseExpiry
|
||||
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, even
|
||||
// once the CLTV timelock has expired.
|
||||
makeWitnessTestCase(t, func() (wire.TxWitness, error) {
|
||||
sweepTx.LockTime = leaseExpiry
|
||||
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,
|
||||
},
|
||||
{
|
||||
// Alice cannot spend output without sequence or
|
||||
// locktime set.
|
||||
makeWitnessTestCase(t, func() (wire.TxWitness, error) {
|
||||
sweepTx.LockTime = 0
|
||||
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) {
|
||||
|
Reference in New Issue
Block a user