lnwallet: move code to transactions files
This commit is contained in:
parent
1863dcef1f
commit
667474db75
@ -3,7 +3,6 @@ package lnwallet
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/binary"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
||||||
@ -22,28 +21,6 @@ var (
|
|||||||
// SequenceLockTimeSeconds is the 22nd bit which indicates the lock
|
// SequenceLockTimeSeconds is the 22nd bit which indicates the lock
|
||||||
// time is in seconds.
|
// time is in seconds.
|
||||||
SequenceLockTimeSeconds = uint32(1 << 22)
|
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
|
// WitnessScriptHash generates a pay-to-witness-script-hash public key script
|
||||||
@ -622,108 +599,6 @@ func receiverHtlcSpendTimeout(signer Signer, signDesc *SignDescriptor,
|
|||||||
return witnessStack, nil
|
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
|
// secondLevelHtlcScript is the uniform script that's used as the output for
|
||||||
// the second-level HTLC transactions. The second level transaction act as a
|
// 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
|
// 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
|
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
|
// ComputeCommitmentPoint generates a commitment point given a commitment
|
||||||
// secret. The commitment point for each state is used to randomize each key in
|
// 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
|
// the key-ring and also to used as a tweak to derive new public+private keys
|
||||||
|
@ -6,7 +6,6 @@ import (
|
|||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/btcsuite/btcd/btcec"
|
"github.com/btcsuite/btcd/btcec"
|
||||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||||
@ -16,211 +15,6 @@ import (
|
|||||||
"github.com/lightningnetwork/lnd/keychain"
|
"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
|
// TestRevocationKeyDerivation tests that given a public key, and a revocation
|
||||||
// hash, the homomorphic revocation public and private key derivation work
|
// hash, the homomorphic revocation public and private key derivation work
|
||||||
// properly.
|
// 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
|
// TestSpecificationKeyDerivation implements the test vectors provided in
|
||||||
// BOLT-03, Appendix E.
|
// BOLT-03, Appendix E.
|
||||||
func TestSpecificationKeyDerivation(t *testing.T) {
|
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"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/btcsuite/btcd/btcec"
|
"github.com/btcsuite/btcd/btcec"
|
||||||
"github.com/btcsuite/btcd/chaincfg"
|
"github.com/btcsuite/btcd/chaincfg"
|
||||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||||
|
"github.com/btcsuite/btcd/txscript"
|
||||||
"github.com/btcsuite/btcd/wire"
|
"github.com/btcsuite/btcd/wire"
|
||||||
"github.com/btcsuite/btcutil"
|
"github.com/btcsuite/btcutil"
|
||||||
"github.com/davecgh/go-spew/spew"
|
"github.com/davecgh/go-spew/spew"
|
||||||
@ -910,3 +912,299 @@ func htlcViewFromHTLCs(htlcs []channeldb.HTLC) *htlcView {
|
|||||||
}
|
}
|
||||||
return &theHTLCView
|
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