mirror of
https://github.com/lightningnetwork/lnd.git
synced 2024-11-19 09:53:54 +01:00
lnwallet: move code to transactions files
This commit is contained in:
parent
1863dcef1f
commit
667474db75
@ -3,7 +3,6 @@ package lnwallet
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
@ -22,28 +21,6 @@ var (
|
||||
// SequenceLockTimeSeconds is the 22nd bit which indicates the lock
|
||||
// time is in seconds.
|
||||
SequenceLockTimeSeconds = uint32(1 << 22)
|
||||
|
||||
// TimelockShift is used to make sure the commitment transaction is
|
||||
// spendable by setting the locktime with it so that it is larger than
|
||||
// 500,000,000, thus interpreting it as Unix epoch timestamp and not
|
||||
// a block height. It is also smaller than the current timestamp which
|
||||
// has bit (1 << 30) set, so there is no risk of having the commitment
|
||||
// transaction be rejected. This way we can safely use the lower 24 bits
|
||||
// of the locktime field for part of the obscured commitment transaction
|
||||
// number.
|
||||
TimelockShift = uint32(1 << 29)
|
||||
)
|
||||
|
||||
const (
|
||||
// StateHintSize is the total number of bytes used between the sequence
|
||||
// number and locktime of the commitment transaction use to encode a hint
|
||||
// to the state number of a particular commitment transaction.
|
||||
StateHintSize = 6
|
||||
|
||||
// maxStateHint is the maximum state number we're able to encode using
|
||||
// StateHintSize bytes amongst the sequence number and locktime fields
|
||||
// of the commitment transaction.
|
||||
maxStateHint uint64 = (1 << 48) - 1
|
||||
)
|
||||
|
||||
// WitnessScriptHash generates a pay-to-witness-script-hash public key script
|
||||
@ -622,108 +599,6 @@ func receiverHtlcSpendTimeout(signer Signer, signDesc *SignDescriptor,
|
||||
return witnessStack, nil
|
||||
}
|
||||
|
||||
// createHtlcTimeoutTx creates a transaction that spends the HTLC output on the
|
||||
// commitment transaction of the peer that created an HTLC (the sender). This
|
||||
// transaction essentially acts as an off-chain covenant as it spends a 2-of-2
|
||||
// multi-sig output. This output requires a signature from both the sender and
|
||||
// receiver of the HTLC. By using a distinct transaction, we're able to
|
||||
// uncouple the timeout and delay clauses of the HTLC contract. This
|
||||
// transaction is locked with an absolute lock-time so the sender can only
|
||||
// attempt to claim the output using it after the lock time has passed.
|
||||
//
|
||||
// In order to spend the HTLC output, the witness for the passed transaction
|
||||
// should be:
|
||||
// * <0> <sender sig> <receiver sig> <0>
|
||||
//
|
||||
// NOTE: The passed amount for the HTLC should take into account the required
|
||||
// fee rate at the time the HTLC was created. The fee should be able to
|
||||
// entirely pay for this (tiny: 1-in 1-out) transaction.
|
||||
func createHtlcTimeoutTx(htlcOutput wire.OutPoint, htlcAmt btcutil.Amount,
|
||||
cltvExpiry, csvDelay uint32,
|
||||
revocationKey, delayKey *btcec.PublicKey) (*wire.MsgTx, error) {
|
||||
|
||||
// Create a version two transaction (as the success version of this
|
||||
// spends an output with a CSV timeout), and set the lock-time to the
|
||||
// specified absolute lock-time in blocks.
|
||||
timeoutTx := wire.NewMsgTx(2)
|
||||
timeoutTx.LockTime = cltvExpiry
|
||||
|
||||
// The input to the transaction is the outpoint that creates the
|
||||
// original HTLC on the sender's commitment transaction.
|
||||
timeoutTx.AddTxIn(&wire.TxIn{
|
||||
PreviousOutPoint: htlcOutput,
|
||||
})
|
||||
|
||||
// Next, we'll generate the script used as the output for all second
|
||||
// level HTLC which forces a covenant w.r.t what can be done with all
|
||||
// HTLC outputs.
|
||||
witnessScript, err := secondLevelHtlcScript(revocationKey, delayKey,
|
||||
csvDelay)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pkScript, err := WitnessScriptHash(witnessScript)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Finally, the output is simply the amount of the HTLC (minus the
|
||||
// required fees), paying to the regular second level HTLC script.
|
||||
timeoutTx.AddTxOut(&wire.TxOut{
|
||||
Value: int64(htlcAmt),
|
||||
PkScript: pkScript,
|
||||
})
|
||||
|
||||
return timeoutTx, nil
|
||||
}
|
||||
|
||||
// createHtlcSuccessTx creates a transaction that spends the output on the
|
||||
// commitment transaction of the peer that receives an HTLC. This transaction
|
||||
// essentially acts as an off-chain covenant as it's only permitted to spend
|
||||
// the designated HTLC output, and also that spend can _only_ be used as a
|
||||
// state transition to create another output which actually allows redemption
|
||||
// or revocation of an HTLC.
|
||||
//
|
||||
// In order to spend the HTLC output, the witness for the passed transaction
|
||||
// should be:
|
||||
// * <0> <sender sig> <recvr sig> <preimage>
|
||||
func createHtlcSuccessTx(htlcOutput wire.OutPoint, htlcAmt btcutil.Amount,
|
||||
csvDelay uint32,
|
||||
revocationKey, delayKey *btcec.PublicKey) (*wire.MsgTx, error) {
|
||||
|
||||
// Create a version two transaction (as the success version of this
|
||||
// spends an output with a CSV timeout).
|
||||
successTx := wire.NewMsgTx(2)
|
||||
|
||||
// The input to the transaction is the outpoint that creates the
|
||||
// original HTLC on the sender's commitment transaction.
|
||||
successTx.AddTxIn(&wire.TxIn{
|
||||
PreviousOutPoint: htlcOutput,
|
||||
})
|
||||
|
||||
// Next, we'll generate the script used as the output for all second
|
||||
// level HTLC which forces a covenant w.r.t what can be done with all
|
||||
// HTLC outputs.
|
||||
witnessScript, err := secondLevelHtlcScript(revocationKey, delayKey,
|
||||
csvDelay)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pkScript, err := WitnessScriptHash(witnessScript)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Finally, the output is simply the amount of the HTLC (minus the
|
||||
// required fees), paying to the timeout script.
|
||||
successTx.AddTxOut(&wire.TxOut{
|
||||
Value: int64(htlcAmt),
|
||||
PkScript: pkScript,
|
||||
})
|
||||
|
||||
return successTx, nil
|
||||
}
|
||||
|
||||
// secondLevelHtlcScript is the uniform script that's used as the output for
|
||||
// the second-level HTLC transactions. The second level transaction act as a
|
||||
// sort of covenant, ensuring that a 2-of-2 multi-sig output can only be
|
||||
@ -1235,71 +1110,6 @@ func DeriveRevocationPrivKey(revokeBasePriv *btcec.PrivateKey,
|
||||
return priv
|
||||
}
|
||||
|
||||
// SetStateNumHint encodes the current state number within the passed
|
||||
// commitment transaction by re-purposing the locktime and sequence fields in
|
||||
// the commitment transaction to encode the obfuscated state number. The state
|
||||
// number is encoded using 48 bits. The lower 24 bits of the lock time are the
|
||||
// lower 24 bits of the obfuscated state number and the lower 24 bits of the
|
||||
// sequence field are the higher 24 bits. Finally before encoding, the
|
||||
// obfuscator is XOR'd against the state number in order to hide the exact
|
||||
// state number from the PoV of outside parties.
|
||||
func SetStateNumHint(commitTx *wire.MsgTx, stateNum uint64,
|
||||
obfuscator [StateHintSize]byte) error {
|
||||
|
||||
// With the current schema we are only able to encode state num
|
||||
// hints up to 2^48. Therefore if the passed height is greater than our
|
||||
// state hint ceiling, then exit early.
|
||||
if stateNum > maxStateHint {
|
||||
return fmt.Errorf("unable to encode state, %v is greater "+
|
||||
"state num that max of %v", stateNum, maxStateHint)
|
||||
}
|
||||
|
||||
if len(commitTx.TxIn) != 1 {
|
||||
return fmt.Errorf("commitment tx must have exactly 1 input, "+
|
||||
"instead has %v", len(commitTx.TxIn))
|
||||
}
|
||||
|
||||
// Convert the obfuscator into a uint64, then XOR that against the
|
||||
// targeted height in order to obfuscate the state number of the
|
||||
// commitment transaction in the case that either commitment
|
||||
// transaction is broadcast directly on chain.
|
||||
var obfs [8]byte
|
||||
copy(obfs[2:], obfuscator[:])
|
||||
xorInt := binary.BigEndian.Uint64(obfs[:])
|
||||
|
||||
stateNum = stateNum ^ xorInt
|
||||
|
||||
// Set the height bit of the sequence number in order to disable any
|
||||
// sequence locks semantics.
|
||||
commitTx.TxIn[0].Sequence = uint32(stateNum>>24) | wire.SequenceLockTimeDisabled
|
||||
commitTx.LockTime = uint32(stateNum&0xFFFFFF) | TimelockShift
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetStateNumHint recovers the current state number given a commitment
|
||||
// transaction which has previously had the state number encoded within it via
|
||||
// setStateNumHint and a shared obfuscator.
|
||||
//
|
||||
// See setStateNumHint for further details w.r.t exactly how the state-hints
|
||||
// are encoded.
|
||||
func GetStateNumHint(commitTx *wire.MsgTx, obfuscator [StateHintSize]byte) uint64 {
|
||||
// Convert the obfuscator into a uint64, this will be used to
|
||||
// de-obfuscate the final recovered state number.
|
||||
var obfs [8]byte
|
||||
copy(obfs[2:], obfuscator[:])
|
||||
xorInt := binary.BigEndian.Uint64(obfs[:])
|
||||
|
||||
// Retrieve the state hint from the sequence number and locktime
|
||||
// of the transaction.
|
||||
stateNumXor := uint64(commitTx.TxIn[0].Sequence&0xFFFFFF) << 24
|
||||
stateNumXor |= uint64(commitTx.LockTime & 0xFFFFFF)
|
||||
|
||||
// Finally, to obtain the final state number, we XOR by the obfuscator
|
||||
// value to de-obfuscate the state number.
|
||||
return stateNumXor ^ xorInt
|
||||
}
|
||||
|
||||
// ComputeCommitmentPoint generates a commitment point given a commitment
|
||||
// secret. The commitment point for each state is used to randomize each key in
|
||||
// the key-ring and also to used as a tweak to derive new public+private keys
|
||||
|
@ -6,7 +6,6 @@ import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
@ -16,211 +15,6 @@ import (
|
||||
"github.com/lightningnetwork/lnd/keychain"
|
||||
)
|
||||
|
||||
// TestCommitmentSpendValidation test the spendability of both outputs within
|
||||
// the commitment transaction.
|
||||
//
|
||||
// The following spending cases are covered by this test:
|
||||
// * Alice's spend from the delayed output on her commitment transaction.
|
||||
// * Bob's spend from Alice's delayed output when she broadcasts a revoked
|
||||
// commitment transaction.
|
||||
// * Bob's spend from his unencumbered output within Alice's commitment
|
||||
// transaction.
|
||||
func TestCommitmentSpendValidation(t *testing.T) {
|
||||
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)
|
||||
|
||||
const channelBalance = btcutil.Amount(1 * 10e8)
|
||||
const csvTimeout = uint32(5)
|
||||
|
||||
// We also set up set some resources for the commitment transaction.
|
||||
// Each side currently has 1 BTC within the channel, with a total
|
||||
// channel capacity of 2BTC.
|
||||
aliceKeyPriv, aliceKeyPub := btcec.PrivKeyFromBytes(btcec.S256(),
|
||||
testWalletPrivKey)
|
||||
bobKeyPriv, bobKeyPub := btcec.PrivKeyFromBytes(btcec.S256(),
|
||||
bobsPrivKey)
|
||||
|
||||
revocationPreimage := testHdSeed.CloneBytes()
|
||||
commitSecret, commitPoint := btcec.PrivKeyFromBytes(btcec.S256(),
|
||||
revocationPreimage)
|
||||
revokePubKey := DeriveRevocationPubkey(bobKeyPub, commitPoint)
|
||||
|
||||
aliceDelayKey := TweakPubKey(aliceKeyPub, commitPoint)
|
||||
bobPayKey := TweakPubKey(bobKeyPub, commitPoint)
|
||||
|
||||
aliceCommitTweak := SingleTweakBytes(commitPoint, aliceKeyPub)
|
||||
bobCommitTweak := SingleTweakBytes(commitPoint, bobKeyPub)
|
||||
|
||||
aliceSelfOutputSigner := &mockSigner{
|
||||
privkeys: []*btcec.PrivateKey{aliceKeyPriv},
|
||||
}
|
||||
|
||||
// With all the test data set up, we create the commitment transaction.
|
||||
// We only focus on a single party's transactions, as the scripts are
|
||||
// identical with the roles reversed.
|
||||
//
|
||||
// This is Alice's commitment transaction, so she must wait a CSV delay
|
||||
// of 5 blocks before sweeping the output, while bob can spend
|
||||
// immediately with either the revocation key, or his regular key.
|
||||
keyRing := &CommitmentKeyRing{
|
||||
DelayKey: aliceDelayKey,
|
||||
RevocationKey: revokePubKey,
|
||||
NoDelayKey: bobPayKey,
|
||||
}
|
||||
commitmentTx, err := CreateCommitTx(*fakeFundingTxIn, keyRing, csvTimeout,
|
||||
channelBalance, channelBalance, DefaultDustLimit())
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create commitment transaction: %v", nil)
|
||||
}
|
||||
|
||||
delayOutput := commitmentTx.TxOut[0]
|
||||
regularOutput := commitmentTx.TxOut[1]
|
||||
|
||||
// We're testing an uncooperative close, output sweep, so construct a
|
||||
// transaction which sweeps the funds to a random address.
|
||||
targetOutput, err := CommitScriptUnencumbered(aliceKeyPub)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create target output: %v", err)
|
||||
}
|
||||
sweepTx := wire.NewMsgTx(2)
|
||||
sweepTx.AddTxIn(wire.NewTxIn(&wire.OutPoint{
|
||||
Hash: commitmentTx.TxHash(),
|
||||
Index: 0,
|
||||
}, nil, nil))
|
||||
sweepTx.AddTxOut(&wire.TxOut{
|
||||
PkScript: targetOutput,
|
||||
Value: 0.5 * 10e8,
|
||||
})
|
||||
|
||||
// First, we'll test spending with Alice's key after the timeout.
|
||||
delayScript, err := CommitScriptToSelf(csvTimeout, aliceDelayKey,
|
||||
revokePubKey)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to generate alice delay script: %v", err)
|
||||
}
|
||||
sweepTx.TxIn[0].Sequence = lockTimeToSequence(false, csvTimeout)
|
||||
signDesc := &SignDescriptor{
|
||||
WitnessScript: delayScript,
|
||||
KeyDesc: keychain.KeyDescriptor{
|
||||
PubKey: aliceKeyPub,
|
||||
},
|
||||
SingleTweak: aliceCommitTweak,
|
||||
SigHashes: txscript.NewTxSigHashes(sweepTx),
|
||||
Output: &wire.TxOut{
|
||||
Value: int64(channelBalance),
|
||||
},
|
||||
HashType: txscript.SigHashAll,
|
||||
InputIndex: 0,
|
||||
}
|
||||
aliceWitnessSpend, err := CommitSpendTimeout(aliceSelfOutputSigner,
|
||||
signDesc, sweepTx)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to generate delay commit spend witness: %v", err)
|
||||
}
|
||||
sweepTx.TxIn[0].Witness = aliceWitnessSpend
|
||||
vm, err := txscript.NewEngine(delayOutput.PkScript,
|
||||
sweepTx, 0, txscript.StandardVerifyFlags, nil,
|
||||
nil, int64(channelBalance))
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create engine: %v", err)
|
||||
}
|
||||
if err := vm.Execute(); err != nil {
|
||||
t.Fatalf("spend from delay output is invalid: %v", err)
|
||||
}
|
||||
|
||||
bobSigner := &mockSigner{privkeys: []*btcec.PrivateKey{bobKeyPriv}}
|
||||
|
||||
// Next, we'll test bob spending with the derived revocation key to
|
||||
// simulate the scenario when Alice broadcasts this commitment
|
||||
// transaction after it's been revoked.
|
||||
signDesc = &SignDescriptor{
|
||||
KeyDesc: keychain.KeyDescriptor{
|
||||
PubKey: bobKeyPub,
|
||||
},
|
||||
DoubleTweak: commitSecret,
|
||||
WitnessScript: delayScript,
|
||||
SigHashes: txscript.NewTxSigHashes(sweepTx),
|
||||
Output: &wire.TxOut{
|
||||
Value: int64(channelBalance),
|
||||
},
|
||||
HashType: txscript.SigHashAll,
|
||||
InputIndex: 0,
|
||||
}
|
||||
bobWitnessSpend, err := CommitSpendRevoke(bobSigner, signDesc,
|
||||
sweepTx)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to generate revocation witness: %v", err)
|
||||
}
|
||||
sweepTx.TxIn[0].Witness = bobWitnessSpend
|
||||
vm, err = txscript.NewEngine(delayOutput.PkScript,
|
||||
sweepTx, 0, txscript.StandardVerifyFlags, nil,
|
||||
nil, int64(channelBalance))
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create engine: %v", err)
|
||||
}
|
||||
if err := vm.Execute(); err != nil {
|
||||
t.Fatalf("revocation spend is invalid: %v", err)
|
||||
}
|
||||
|
||||
// In order to test the final scenario, we modify the TxIn of the sweep
|
||||
// transaction to instead point to the regular output (non delay)
|
||||
// within the commitment transaction.
|
||||
sweepTx.TxIn[0] = &wire.TxIn{
|
||||
PreviousOutPoint: wire.OutPoint{
|
||||
Hash: commitmentTx.TxHash(),
|
||||
Index: 1,
|
||||
},
|
||||
}
|
||||
|
||||
// Finally, we test bob sweeping his output as normal in the case that
|
||||
// Alice broadcasts this commitment transaction.
|
||||
bobScriptP2WKH, err := CommitScriptUnencumbered(bobPayKey)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create bob p2wkh script: %v", err)
|
||||
}
|
||||
signDesc = &SignDescriptor{
|
||||
KeyDesc: keychain.KeyDescriptor{
|
||||
PubKey: bobKeyPub,
|
||||
},
|
||||
SingleTweak: bobCommitTweak,
|
||||
WitnessScript: bobScriptP2WKH,
|
||||
SigHashes: txscript.NewTxSigHashes(sweepTx),
|
||||
Output: &wire.TxOut{
|
||||
Value: int64(channelBalance),
|
||||
PkScript: bobScriptP2WKH,
|
||||
},
|
||||
HashType: txscript.SigHashAll,
|
||||
InputIndex: 0,
|
||||
}
|
||||
bobRegularSpend, err := CommitSpendNoDelay(bobSigner, signDesc,
|
||||
sweepTx)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create bob regular spend: %v", err)
|
||||
}
|
||||
sweepTx.TxIn[0].Witness = bobRegularSpend
|
||||
vm, err = txscript.NewEngine(regularOutput.PkScript,
|
||||
sweepTx, 0, txscript.StandardVerifyFlags, nil,
|
||||
nil, int64(channelBalance))
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create engine: %v", err)
|
||||
}
|
||||
if err := vm.Execute(); err != nil {
|
||||
t.Fatalf("bob p2wkh spend is invalid: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestRevocationKeyDerivation tests that given a public key, and a revocation
|
||||
// hash, the homomorphic revocation public and private key derivation work
|
||||
// properly.
|
||||
@ -1053,97 +847,6 @@ func TestSecondLevelHtlcSpends(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommitTxStateHint(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
stateHintTests := []struct {
|
||||
name string
|
||||
from uint64
|
||||
to uint64
|
||||
inputs int
|
||||
shouldFail bool
|
||||
}{
|
||||
{
|
||||
name: "states 0 to 1000",
|
||||
from: 0,
|
||||
to: 1000,
|
||||
inputs: 1,
|
||||
shouldFail: false,
|
||||
},
|
||||
{
|
||||
name: "states 'maxStateHint-1000' to 'maxStateHint'",
|
||||
from: maxStateHint - 1000,
|
||||
to: maxStateHint,
|
||||
inputs: 1,
|
||||
shouldFail: false,
|
||||
},
|
||||
{
|
||||
name: "state 'maxStateHint+1'",
|
||||
from: maxStateHint + 1,
|
||||
to: maxStateHint + 10,
|
||||
inputs: 1,
|
||||
shouldFail: true,
|
||||
},
|
||||
{
|
||||
name: "commit transaction with two inputs",
|
||||
inputs: 2,
|
||||
shouldFail: true,
|
||||
},
|
||||
}
|
||||
|
||||
var obfuscator [StateHintSize]byte
|
||||
copy(obfuscator[:], testHdSeed[:StateHintSize])
|
||||
timeYesterday := uint32(time.Now().Unix() - 24*60*60)
|
||||
|
||||
for _, test := range stateHintTests {
|
||||
commitTx := wire.NewMsgTx(2)
|
||||
|
||||
// Add supplied number of inputs to the commitment transaction.
|
||||
for i := 0; i < test.inputs; i++ {
|
||||
commitTx.AddTxIn(&wire.TxIn{})
|
||||
}
|
||||
|
||||
for i := test.from; i <= test.to; i++ {
|
||||
stateNum := uint64(i)
|
||||
|
||||
err := SetStateNumHint(commitTx, stateNum, obfuscator)
|
||||
if err != nil && !test.shouldFail {
|
||||
t.Fatalf("unable to set state num %v: %v", i, err)
|
||||
} else if err == nil && test.shouldFail {
|
||||
t.Fatalf("Failed(%v): test should fail but did not", test.name)
|
||||
}
|
||||
|
||||
locktime := commitTx.LockTime
|
||||
sequence := commitTx.TxIn[0].Sequence
|
||||
|
||||
// Locktime should not be less than 500,000,000 and not larger
|
||||
// than the time 24 hours ago. One day should provide a good
|
||||
// enough buffer for the tests.
|
||||
if locktime < 5e8 || locktime > timeYesterday {
|
||||
if !test.shouldFail {
|
||||
t.Fatalf("The value of locktime (%v) may cause the commitment "+
|
||||
"transaction to be unspendable", locktime)
|
||||
}
|
||||
}
|
||||
|
||||
if sequence&wire.SequenceLockTimeDisabled == 0 {
|
||||
if !test.shouldFail {
|
||||
t.Fatalf("Sequence locktime is NOT disabled when it should be")
|
||||
}
|
||||
}
|
||||
|
||||
extractedStateNum := GetStateNumHint(commitTx, obfuscator)
|
||||
if extractedStateNum != stateNum && !test.shouldFail {
|
||||
t.Fatalf("state number mismatched, expected %v, got %v",
|
||||
stateNum, extractedStateNum)
|
||||
} else if extractedStateNum == stateNum && test.shouldFail {
|
||||
t.Fatalf("Failed(%v): test should fail but did not", test.name)
|
||||
}
|
||||
}
|
||||
t.Logf("Passed: %v", test.name)
|
||||
}
|
||||
}
|
||||
|
||||
// TestSpecificationKeyDerivation implements the test vectors provided in
|
||||
// BOLT-03, Appendix E.
|
||||
func TestSpecificationKeyDerivation(t *testing.T) {
|
||||
|
201
lnwallet/transactions.go
Normal file
201
lnwallet/transactions.go
Normal file
@ -0,0 +1,201 @@
|
||||
package lnwallet
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/btcutil"
|
||||
)
|
||||
|
||||
const (
|
||||
// StateHintSize is the total number of bytes used between the sequence
|
||||
// number and locktime of the commitment transaction use to encode a hint
|
||||
// to the state number of a particular commitment transaction.
|
||||
StateHintSize = 6
|
||||
|
||||
// MaxStateHint is the maximum state number we're able to encode using
|
||||
// StateHintSize bytes amongst the sequence number and locktime fields
|
||||
// of the commitment transaction.
|
||||
maxStateHint uint64 = (1 << 48) - 1
|
||||
)
|
||||
|
||||
var (
|
||||
// TimelockShift is used to make sure the commitment transaction is
|
||||
// spendable by setting the locktime with it so that it is larger than
|
||||
// 500,000,000, thus interpreting it as Unix epoch timestamp and not
|
||||
// a block height. It is also smaller than the current timestamp which
|
||||
// has bit (1 << 30) set, so there is no risk of having the commitment
|
||||
// transaction be rejected. This way we can safely use the lower 24 bits
|
||||
// of the locktime field for part of the obscured commitment transaction
|
||||
// number.
|
||||
TimelockShift = uint32(1 << 29)
|
||||
)
|
||||
|
||||
// createHtlcSuccessTx creates a transaction that spends the output on the
|
||||
// commitment transaction of the peer that receives an HTLC. This transaction
|
||||
// essentially acts as an off-chain covenant as it's only permitted to spend
|
||||
// the designated HTLC output, and also that spend can _only_ be used as a
|
||||
// state transition to create another output which actually allows redemption
|
||||
// or revocation of an HTLC.
|
||||
//
|
||||
// In order to spend the HTLC output, the witness for the passed transaction
|
||||
// should be:
|
||||
// * <0> <sender sig> <recvr sig> <preimage>
|
||||
func createHtlcSuccessTx(htlcOutput wire.OutPoint, htlcAmt btcutil.Amount,
|
||||
csvDelay uint32,
|
||||
revocationKey, delayKey *btcec.PublicKey) (*wire.MsgTx, error) {
|
||||
|
||||
// Create a version two transaction (as the success version of this
|
||||
// spends an output with a CSV timeout).
|
||||
successTx := wire.NewMsgTx(2)
|
||||
|
||||
// The input to the transaction is the outpoint that creates the
|
||||
// original HTLC on the sender's commitment transaction.
|
||||
successTx.AddTxIn(&wire.TxIn{
|
||||
PreviousOutPoint: htlcOutput,
|
||||
})
|
||||
|
||||
// Next, we'll generate the script used as the output for all second
|
||||
// level HTLC which forces a covenant w.r.t what can be done with all
|
||||
// HTLC outputs.
|
||||
witnessScript, err := secondLevelHtlcScript(revocationKey, delayKey,
|
||||
csvDelay)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pkScript, err := WitnessScriptHash(witnessScript)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Finally, the output is simply the amount of the HTLC (minus the
|
||||
// required fees), paying to the timeout script.
|
||||
successTx.AddTxOut(&wire.TxOut{
|
||||
Value: int64(htlcAmt),
|
||||
PkScript: pkScript,
|
||||
})
|
||||
|
||||
return successTx, nil
|
||||
}
|
||||
|
||||
// createHtlcTimeoutTx creates a transaction that spends the HTLC output on the
|
||||
// commitment transaction of the peer that created an HTLC (the sender). This
|
||||
// transaction essentially acts as an off-chain covenant as it spends a 2-of-2
|
||||
// multi-sig output. This output requires a signature from both the sender and
|
||||
// receiver of the HTLC. By using a distinct transaction, we're able to
|
||||
// uncouple the timeout and delay clauses of the HTLC contract. This
|
||||
// transaction is locked with an absolute lock-time so the sender can only
|
||||
// attempt to claim the output using it after the lock time has passed.
|
||||
//
|
||||
// In order to spend the HTLC output, the witness for the passed transaction
|
||||
// should be:
|
||||
// * <0> <sender sig> <receiver sig> <0>
|
||||
//
|
||||
// NOTE: The passed amount for the HTLC should take into account the required
|
||||
// fee rate at the time the HTLC was created. The fee should be able to
|
||||
// entirely pay for this (tiny: 1-in 1-out) transaction.
|
||||
func createHtlcTimeoutTx(htlcOutput wire.OutPoint, htlcAmt btcutil.Amount,
|
||||
cltvExpiry, csvDelay uint32,
|
||||
revocationKey, delayKey *btcec.PublicKey) (*wire.MsgTx, error) {
|
||||
|
||||
// Create a version two transaction (as the success version of this
|
||||
// spends an output with a CSV timeout), and set the lock-time to the
|
||||
// specified absolute lock-time in blocks.
|
||||
timeoutTx := wire.NewMsgTx(2)
|
||||
timeoutTx.LockTime = cltvExpiry
|
||||
|
||||
// The input to the transaction is the outpoint that creates the
|
||||
// original HTLC on the sender's commitment transaction.
|
||||
timeoutTx.AddTxIn(&wire.TxIn{
|
||||
PreviousOutPoint: htlcOutput,
|
||||
})
|
||||
|
||||
// Next, we'll generate the script used as the output for all second
|
||||
// level HTLC which forces a covenant w.r.t what can be done with all
|
||||
// HTLC outputs.
|
||||
witnessScript, err := secondLevelHtlcScript(revocationKey, delayKey,
|
||||
csvDelay)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pkScript, err := WitnessScriptHash(witnessScript)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Finally, the output is simply the amount of the HTLC (minus the
|
||||
// required fees), paying to the regular second level HTLC script.
|
||||
timeoutTx.AddTxOut(&wire.TxOut{
|
||||
Value: int64(htlcAmt),
|
||||
PkScript: pkScript,
|
||||
})
|
||||
|
||||
return timeoutTx, nil
|
||||
}
|
||||
|
||||
// SetStateNumHint encodes the current state number within the passed
|
||||
// commitment transaction by re-purposing the locktime and sequence fields in
|
||||
// the commitment transaction to encode the obfuscated state number. The state
|
||||
// number is encoded using 48 bits. The lower 24 bits of the lock time are the
|
||||
// lower 24 bits of the obfuscated state number and the lower 24 bits of the
|
||||
// sequence field are the higher 24 bits. Finally before encoding, the
|
||||
// obfuscator is XOR'd against the state number in order to hide the exact
|
||||
// state number from the PoV of outside parties.
|
||||
func SetStateNumHint(commitTx *wire.MsgTx, stateNum uint64,
|
||||
obfuscator [StateHintSize]byte) error {
|
||||
|
||||
// With the current schema we are only able to encode state num
|
||||
// hints up to 2^48. Therefore if the passed height is greater than our
|
||||
// state hint ceiling, then exit early.
|
||||
if stateNum > maxStateHint {
|
||||
return fmt.Errorf("unable to encode state, %v is greater "+
|
||||
"state num that max of %v", stateNum, maxStateHint)
|
||||
}
|
||||
|
||||
if len(commitTx.TxIn) != 1 {
|
||||
return fmt.Errorf("commitment tx must have exactly 1 input, "+
|
||||
"instead has %v", len(commitTx.TxIn))
|
||||
}
|
||||
|
||||
// Convert the obfuscator into a uint64, then XOR that against the
|
||||
// targeted height in order to obfuscate the state number of the
|
||||
// commitment transaction in the case that either commitment
|
||||
// transaction is broadcast directly on chain.
|
||||
var obfs [8]byte
|
||||
copy(obfs[2:], obfuscator[:])
|
||||
xorInt := binary.BigEndian.Uint64(obfs[:])
|
||||
|
||||
stateNum = stateNum ^ xorInt
|
||||
|
||||
// Set the height bit of the sequence number in order to disable any
|
||||
// sequence locks semantics.
|
||||
commitTx.TxIn[0].Sequence = uint32(stateNum>>24) | wire.SequenceLockTimeDisabled
|
||||
commitTx.LockTime = uint32(stateNum&0xFFFFFF) | TimelockShift
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetStateNumHint recovers the current state number given a commitment
|
||||
// transaction which has previously had the state number encoded within it via
|
||||
// setStateNumHint and a shared obfuscator.
|
||||
//
|
||||
// See setStateNumHint for further details w.r.t exactly how the state-hints
|
||||
// are encoded.
|
||||
func GetStateNumHint(commitTx *wire.MsgTx, obfuscator [StateHintSize]byte) uint64 {
|
||||
// Convert the obfuscator into a uint64, this will be used to
|
||||
// de-obfuscate the final recovered state number.
|
||||
var obfs [8]byte
|
||||
copy(obfs[2:], obfuscator[:])
|
||||
xorInt := binary.BigEndian.Uint64(obfs[:])
|
||||
|
||||
// Retrieve the state hint from the sequence number and locktime
|
||||
// of the transaction.
|
||||
stateNumXor := uint64(commitTx.TxIn[0].Sequence&0xFFFFFF) << 24
|
||||
stateNumXor |= uint64(commitTx.LockTime & 0xFFFFFF)
|
||||
|
||||
// Finally, to obtain the final state number, we XOR by the obfuscator
|
||||
// value to de-obfuscate the state number.
|
||||
return stateNumXor ^ xorInt
|
||||
}
|
@ -5,10 +5,12 @@ import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec"
|
||||
"github.com/btcsuite/btcd/chaincfg"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/btcutil"
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
@ -910,3 +912,299 @@ func htlcViewFromHTLCs(htlcs []channeldb.HTLC) *htlcView {
|
||||
}
|
||||
return &theHTLCView
|
||||
}
|
||||
|
||||
func TestCommitTxStateHint(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
stateHintTests := []struct {
|
||||
name string
|
||||
from uint64
|
||||
to uint64
|
||||
inputs int
|
||||
shouldFail bool
|
||||
}{
|
||||
{
|
||||
name: "states 0 to 1000",
|
||||
from: 0,
|
||||
to: 1000,
|
||||
inputs: 1,
|
||||
shouldFail: false,
|
||||
},
|
||||
{
|
||||
name: "states 'maxStateHint-1000' to 'maxStateHint'",
|
||||
from: maxStateHint - 1000,
|
||||
to: maxStateHint,
|
||||
inputs: 1,
|
||||
shouldFail: false,
|
||||
},
|
||||
{
|
||||
name: "state 'maxStateHint+1'",
|
||||
from: maxStateHint + 1,
|
||||
to: maxStateHint + 10,
|
||||
inputs: 1,
|
||||
shouldFail: true,
|
||||
},
|
||||
{
|
||||
name: "commit transaction with two inputs",
|
||||
inputs: 2,
|
||||
shouldFail: true,
|
||||
},
|
||||
}
|
||||
|
||||
var obfuscator [StateHintSize]byte
|
||||
copy(obfuscator[:], testHdSeed[:StateHintSize])
|
||||
timeYesterday := uint32(time.Now().Unix() - 24*60*60)
|
||||
|
||||
for _, test := range stateHintTests {
|
||||
commitTx := wire.NewMsgTx(2)
|
||||
|
||||
// Add supplied number of inputs to the commitment transaction.
|
||||
for i := 0; i < test.inputs; i++ {
|
||||
commitTx.AddTxIn(&wire.TxIn{})
|
||||
}
|
||||
|
||||
for i := test.from; i <= test.to; i++ {
|
||||
stateNum := uint64(i)
|
||||
|
||||
err := SetStateNumHint(commitTx, stateNum, obfuscator)
|
||||
if err != nil && !test.shouldFail {
|
||||
t.Fatalf("unable to set state num %v: %v", i, err)
|
||||
} else if err == nil && test.shouldFail {
|
||||
t.Fatalf("Failed(%v): test should fail but did not", test.name)
|
||||
}
|
||||
|
||||
locktime := commitTx.LockTime
|
||||
sequence := commitTx.TxIn[0].Sequence
|
||||
|
||||
// Locktime should not be less than 500,000,000 and not larger
|
||||
// than the time 24 hours ago. One day should provide a good
|
||||
// enough buffer for the tests.
|
||||
if locktime < 5e8 || locktime > timeYesterday {
|
||||
if !test.shouldFail {
|
||||
t.Fatalf("The value of locktime (%v) may cause the commitment "+
|
||||
"transaction to be unspendable", locktime)
|
||||
}
|
||||
}
|
||||
|
||||
if sequence&wire.SequenceLockTimeDisabled == 0 {
|
||||
if !test.shouldFail {
|
||||
t.Fatalf("Sequence locktime is NOT disabled when it should be")
|
||||
}
|
||||
}
|
||||
|
||||
extractedStateNum := GetStateNumHint(commitTx, obfuscator)
|
||||
if extractedStateNum != stateNum && !test.shouldFail {
|
||||
t.Fatalf("state number mismatched, expected %v, got %v",
|
||||
stateNum, extractedStateNum)
|
||||
} else if extractedStateNum == stateNum && test.shouldFail {
|
||||
t.Fatalf("Failed(%v): test should fail but did not", test.name)
|
||||
}
|
||||
}
|
||||
t.Logf("Passed: %v", test.name)
|
||||
}
|
||||
}
|
||||
|
||||
// TestCommitmentSpendValidation test the spendability of both outputs within
|
||||
// the commitment transaction.
|
||||
//
|
||||
// The following spending cases are covered by this test:
|
||||
// * Alice's spend from the delayed output on her commitment transaction.
|
||||
// * Bob's spend from Alice's delayed output when she broadcasts a revoked
|
||||
// commitment transaction.
|
||||
// * Bob's spend from his unencumbered output within Alice's commitment
|
||||
// transaction.
|
||||
func TestCommitmentSpendValidation(t *testing.T) {
|
||||
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)
|
||||
|
||||
const channelBalance = btcutil.Amount(1 * 10e8)
|
||||
const csvTimeout = uint32(5)
|
||||
|
||||
// We also set up set some resources for the commitment transaction.
|
||||
// Each side currently has 1 BTC within the channel, with a total
|
||||
// channel capacity of 2BTC.
|
||||
aliceKeyPriv, aliceKeyPub := btcec.PrivKeyFromBytes(btcec.S256(),
|
||||
testWalletPrivKey)
|
||||
bobKeyPriv, bobKeyPub := btcec.PrivKeyFromBytes(btcec.S256(),
|
||||
bobsPrivKey)
|
||||
|
||||
revocationPreimage := testHdSeed.CloneBytes()
|
||||
commitSecret, commitPoint := btcec.PrivKeyFromBytes(btcec.S256(),
|
||||
revocationPreimage)
|
||||
revokePubKey := DeriveRevocationPubkey(bobKeyPub, commitPoint)
|
||||
|
||||
aliceDelayKey := TweakPubKey(aliceKeyPub, commitPoint)
|
||||
bobPayKey := TweakPubKey(bobKeyPub, commitPoint)
|
||||
|
||||
aliceCommitTweak := SingleTweakBytes(commitPoint, aliceKeyPub)
|
||||
bobCommitTweak := SingleTweakBytes(commitPoint, bobKeyPub)
|
||||
|
||||
aliceSelfOutputSigner := &mockSigner{
|
||||
privkeys: []*btcec.PrivateKey{aliceKeyPriv},
|
||||
}
|
||||
|
||||
// With all the test data set up, we create the commitment transaction.
|
||||
// We only focus on a single party's transactions, as the scripts are
|
||||
// identical with the roles reversed.
|
||||
//
|
||||
// This is Alice's commitment transaction, so she must wait a CSV delay
|
||||
// of 5 blocks before sweeping the output, while bob can spend
|
||||
// immediately with either the revocation key, or his regular key.
|
||||
keyRing := &CommitmentKeyRing{
|
||||
DelayKey: aliceDelayKey,
|
||||
RevocationKey: revokePubKey,
|
||||
NoDelayKey: bobPayKey,
|
||||
}
|
||||
commitmentTx, err := CreateCommitTx(*fakeFundingTxIn, keyRing, csvTimeout,
|
||||
channelBalance, channelBalance, DefaultDustLimit())
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create commitment transaction: %v", nil)
|
||||
}
|
||||
|
||||
delayOutput := commitmentTx.TxOut[0]
|
||||
regularOutput := commitmentTx.TxOut[1]
|
||||
|
||||
// We're testing an uncooperative close, output sweep, so construct a
|
||||
// transaction which sweeps the funds to a random address.
|
||||
targetOutput, err := CommitScriptUnencumbered(aliceKeyPub)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create target output: %v", err)
|
||||
}
|
||||
sweepTx := wire.NewMsgTx(2)
|
||||
sweepTx.AddTxIn(wire.NewTxIn(&wire.OutPoint{
|
||||
Hash: commitmentTx.TxHash(),
|
||||
Index: 0,
|
||||
}, nil, nil))
|
||||
sweepTx.AddTxOut(&wire.TxOut{
|
||||
PkScript: targetOutput,
|
||||
Value: 0.5 * 10e8,
|
||||
})
|
||||
|
||||
// First, we'll test spending with Alice's key after the timeout.
|
||||
delayScript, err := CommitScriptToSelf(csvTimeout, aliceDelayKey,
|
||||
revokePubKey)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to generate alice delay script: %v", err)
|
||||
}
|
||||
sweepTx.TxIn[0].Sequence = lockTimeToSequence(false, csvTimeout)
|
||||
signDesc := &SignDescriptor{
|
||||
WitnessScript: delayScript,
|
||||
KeyDesc: keychain.KeyDescriptor{
|
||||
PubKey: aliceKeyPub,
|
||||
},
|
||||
SingleTweak: aliceCommitTweak,
|
||||
SigHashes: txscript.NewTxSigHashes(sweepTx),
|
||||
Output: &wire.TxOut{
|
||||
Value: int64(channelBalance),
|
||||
},
|
||||
HashType: txscript.SigHashAll,
|
||||
InputIndex: 0,
|
||||
}
|
||||
aliceWitnessSpend, err := CommitSpendTimeout(aliceSelfOutputSigner,
|
||||
signDesc, sweepTx)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to generate delay commit spend witness: %v", err)
|
||||
}
|
||||
sweepTx.TxIn[0].Witness = aliceWitnessSpend
|
||||
vm, err := txscript.NewEngine(delayOutput.PkScript,
|
||||
sweepTx, 0, txscript.StandardVerifyFlags, nil,
|
||||
nil, int64(channelBalance))
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create engine: %v", err)
|
||||
}
|
||||
if err := vm.Execute(); err != nil {
|
||||
t.Fatalf("spend from delay output is invalid: %v", err)
|
||||
}
|
||||
|
||||
bobSigner := &mockSigner{privkeys: []*btcec.PrivateKey{bobKeyPriv}}
|
||||
|
||||
// Next, we'll test bob spending with the derived revocation key to
|
||||
// simulate the scenario when Alice broadcasts this commitment
|
||||
// transaction after it's been revoked.
|
||||
signDesc = &SignDescriptor{
|
||||
KeyDesc: keychain.KeyDescriptor{
|
||||
PubKey: bobKeyPub,
|
||||
},
|
||||
DoubleTweak: commitSecret,
|
||||
WitnessScript: delayScript,
|
||||
SigHashes: txscript.NewTxSigHashes(sweepTx),
|
||||
Output: &wire.TxOut{
|
||||
Value: int64(channelBalance),
|
||||
},
|
||||
HashType: txscript.SigHashAll,
|
||||
InputIndex: 0,
|
||||
}
|
||||
bobWitnessSpend, err := CommitSpendRevoke(bobSigner, signDesc,
|
||||
sweepTx)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to generate revocation witness: %v", err)
|
||||
}
|
||||
sweepTx.TxIn[0].Witness = bobWitnessSpend
|
||||
vm, err = txscript.NewEngine(delayOutput.PkScript,
|
||||
sweepTx, 0, txscript.StandardVerifyFlags, nil,
|
||||
nil, int64(channelBalance))
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create engine: %v", err)
|
||||
}
|
||||
if err := vm.Execute(); err != nil {
|
||||
t.Fatalf("revocation spend is invalid: %v", err)
|
||||
}
|
||||
|
||||
// In order to test the final scenario, we modify the TxIn of the sweep
|
||||
// transaction to instead point to the regular output (non delay)
|
||||
// within the commitment transaction.
|
||||
sweepTx.TxIn[0] = &wire.TxIn{
|
||||
PreviousOutPoint: wire.OutPoint{
|
||||
Hash: commitmentTx.TxHash(),
|
||||
Index: 1,
|
||||
},
|
||||
}
|
||||
|
||||
// Finally, we test bob sweeping his output as normal in the case that
|
||||
// Alice broadcasts this commitment transaction.
|
||||
bobScriptP2WKH, err := CommitScriptUnencumbered(bobPayKey)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create bob p2wkh script: %v", err)
|
||||
}
|
||||
signDesc = &SignDescriptor{
|
||||
KeyDesc: keychain.KeyDescriptor{
|
||||
PubKey: bobKeyPub,
|
||||
},
|
||||
SingleTweak: bobCommitTweak,
|
||||
WitnessScript: bobScriptP2WKH,
|
||||
SigHashes: txscript.NewTxSigHashes(sweepTx),
|
||||
Output: &wire.TxOut{
|
||||
Value: int64(channelBalance),
|
||||
PkScript: bobScriptP2WKH,
|
||||
},
|
||||
HashType: txscript.SigHashAll,
|
||||
InputIndex: 0,
|
||||
}
|
||||
bobRegularSpend, err := CommitSpendNoDelay(bobSigner, signDesc,
|
||||
sweepTx)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create bob regular spend: %v", err)
|
||||
}
|
||||
sweepTx.TxIn[0].Witness = bobRegularSpend
|
||||
vm, err = txscript.NewEngine(regularOutput.PkScript,
|
||||
sweepTx, 0, txscript.StandardVerifyFlags, nil,
|
||||
nil, int64(channelBalance))
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create engine: %v", err)
|
||||
}
|
||||
if err := vm.Execute(); err != nil {
|
||||
t.Fatalf("bob p2wkh spend is invalid: %v", err)
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user