mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-08-03 10:12:28 +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()
|
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
|
// 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
|
// 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.
|
// 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()
|
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
|
// CommitSpendTimeout constructs a valid witness allowing the owner of a
|
||||||
// particular commitment transaction to spend the output returning settled
|
// particular commitment transaction to spend the output returning settled
|
||||||
// funds back to themselves after a relative block timeout. In order to
|
// 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()
|
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
|
// CommitSpendToRemoteConfirmed constructs a valid witness allowing a node to
|
||||||
// spend their settled output on the counterparty's commitment transaction when
|
// spend their settled output on the counterparty's commitment transaction when
|
||||||
// it has one confirmetion. This is used for the anchor channel type. The
|
// 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/btcd/wire"
|
||||||
"github.com/btcsuite/btcutil"
|
"github.com/btcsuite/btcutil"
|
||||||
"github.com/lightningnetwork/lnd/keychain"
|
"github.com/lightningnetwork/lnd/keychain"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
// assertEngineExecution executes the VM returned by the newEngine closure,
|
// 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
|
// TestCommitSpendToRemoteConfirmed checks that the delayed version of the
|
||||||
// to_remote version can only be spent by the owner, and after one
|
// to_remote version can only be spent by the owner, and after one
|
||||||
// confirmation.
|
// 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
|
// TestSpendAnchor checks that we can spend the anchors using the various spend
|
||||||
// paths.
|
// paths.
|
||||||
func TestSpendAnchor(t *testing.T) {
|
func TestSpendAnchor(t *testing.T) {
|
||||||
|
Reference in New Issue
Block a user