watchtower/lookout/justice_descriptor_test: add create txn test
This commit is contained in:
parent
fa63ba2e37
commit
8bc896427d
348
watchtower/lookout/justice_descriptor_test.go
Normal file
348
watchtower/lookout/justice_descriptor_test.go
Normal file
@ -0,0 +1,348 @@
|
||||
// +build dev
|
||||
|
||||
package lookout_test
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/blockchain"
|
||||
"github.com/btcsuite/btcd/btcec"
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/btcutil"
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/lightningnetwork/lnd/keychain"
|
||||
"github.com/lightningnetwork/lnd/lnwallet"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"github.com/lightningnetwork/lnd/watchtower/blob"
|
||||
"github.com/lightningnetwork/lnd/watchtower/lookout"
|
||||
"github.com/lightningnetwork/lnd/watchtower/wtdb"
|
||||
)
|
||||
|
||||
const csvDelay uint32 = 144
|
||||
|
||||
var (
|
||||
revPrivBytes = []byte{
|
||||
0x8f, 0x4b, 0x51, 0x83, 0xa9, 0x34, 0xbd, 0x5f,
|
||||
0x74, 0x6c, 0x9d, 0x5c, 0xae, 0x88, 0x2d, 0x31,
|
||||
0x06, 0x90, 0xdd, 0x8c, 0x9b, 0x31, 0xbc, 0xd1,
|
||||
0x78, 0x91, 0x88, 0x2a, 0xf9, 0x74, 0xa0, 0xef,
|
||||
}
|
||||
|
||||
toLocalPrivBytes = []byte{
|
||||
0xde, 0x17, 0xc1, 0x2f, 0xdc, 0x1b, 0xc0, 0xc6,
|
||||
0x59, 0x5d, 0xf9, 0xc1, 0x3e, 0x89, 0xbc, 0x6f,
|
||||
0x01, 0x85, 0x45, 0x76, 0x26, 0xce, 0x9c, 0x55,
|
||||
0x3b, 0xc9, 0xec, 0x3d, 0xd8, 0x8b, 0xac, 0xa8,
|
||||
}
|
||||
|
||||
toRemotePrivBytes = []byte{
|
||||
0x28, 0x59, 0x6f, 0x36, 0xb8, 0x9f, 0x19, 0x5d,
|
||||
0xcb, 0x07, 0x48, 0x8a, 0xe5, 0x89, 0x71, 0x74,
|
||||
0x70, 0x4c, 0xff, 0x1e, 0x9c, 0x00, 0x93, 0xbe,
|
||||
0xe2, 0x2e, 0x68, 0x08, 0x4c, 0xb4, 0x0f, 0x4f,
|
||||
}
|
||||
)
|
||||
|
||||
type mockSigner struct {
|
||||
index uint32
|
||||
keys map[keychain.KeyLocator]*btcec.PrivateKey
|
||||
}
|
||||
|
||||
func newMockSigner() *mockSigner {
|
||||
return &mockSigner{
|
||||
keys: make(map[keychain.KeyLocator]*btcec.PrivateKey),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *mockSigner) SignOutputRaw(tx *wire.MsgTx,
|
||||
signDesc *lnwallet.SignDescriptor) ([]byte, error) {
|
||||
|
||||
witnessScript := signDesc.WitnessScript
|
||||
amt := signDesc.Output.Value
|
||||
|
||||
privKey, ok := s.keys[signDesc.KeyDesc.KeyLocator]
|
||||
if !ok {
|
||||
panic("cannot sign w/ unknown key")
|
||||
}
|
||||
|
||||
sig, err := txscript.RawTxInWitnessSignature(
|
||||
tx, signDesc.SigHashes, signDesc.InputIndex, amt,
|
||||
witnessScript, signDesc.HashType, privKey,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return sig[:len(sig)-1], nil
|
||||
}
|
||||
|
||||
func (s *mockSigner) ComputeInputScript(tx *wire.MsgTx,
|
||||
signDesc *lnwallet.SignDescriptor) (*lnwallet.InputScript, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (s *mockSigner) addPrivKey(privKey *btcec.PrivateKey) keychain.KeyLocator {
|
||||
keyLoc := keychain.KeyLocator{
|
||||
Index: s.index,
|
||||
}
|
||||
s.index++
|
||||
|
||||
s.keys[keyLoc] = privKey
|
||||
|
||||
return keyLoc
|
||||
}
|
||||
|
||||
func TestJusticeDescriptor(t *testing.T) {
|
||||
const (
|
||||
localAmount = btcutil.Amount(100000)
|
||||
remoteAmount = btcutil.Amount(200000)
|
||||
totalAmount = localAmount + remoteAmount
|
||||
)
|
||||
|
||||
// Parse the key pairs for all keys used in the test.
|
||||
revSK, revPK := btcec.PrivKeyFromBytes(
|
||||
btcec.S256(), revPrivBytes,
|
||||
)
|
||||
_, toLocalPK := btcec.PrivKeyFromBytes(
|
||||
btcec.S256(), toLocalPrivBytes,
|
||||
)
|
||||
toRemoteSK, toRemotePK := btcec.PrivKeyFromBytes(
|
||||
btcec.S256(), toRemotePrivBytes,
|
||||
)
|
||||
|
||||
// Create the signer, and add the revocation and to-remote privkeys.
|
||||
signer := newMockSigner()
|
||||
var (
|
||||
revKeyLoc = signer.addPrivKey(revSK)
|
||||
toRemoteKeyLoc = signer.addPrivKey(toRemoteSK)
|
||||
)
|
||||
|
||||
// Construct the to-local witness script.
|
||||
toLocalScript, err := lnwallet.CommitScriptToSelf(
|
||||
csvDelay, toLocalPK, revPK,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create to-local script: %v", err)
|
||||
}
|
||||
|
||||
// Compute the to-local witness script hash.
|
||||
toLocalScriptHash, err := lnwallet.WitnessScriptHash(toLocalScript)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create to-local witness script hash: %v", err)
|
||||
}
|
||||
|
||||
// Compute the to-remote witness script hash.
|
||||
toRemoteScriptHash, err := lnwallet.CommitScriptUnencumbered(toRemotePK)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create to-remote script: %v", err)
|
||||
}
|
||||
|
||||
// Construct the breaching commitment txn, containing the to-local and
|
||||
// to-remote outputs. We don't need any inputs for this test.
|
||||
breachTxn := &wire.MsgTx{
|
||||
Version: 2,
|
||||
TxIn: []*wire.TxIn{},
|
||||
TxOut: []*wire.TxOut{
|
||||
{
|
||||
Value: int64(localAmount),
|
||||
PkScript: toLocalScriptHash,
|
||||
},
|
||||
{
|
||||
Value: int64(remoteAmount),
|
||||
PkScript: toRemoteScriptHash,
|
||||
},
|
||||
},
|
||||
}
|
||||
breachTxID := breachTxn.TxHash()
|
||||
|
||||
// Compute the weight estimate for our justice transaction.
|
||||
var weightEstimate lnwallet.TxWeightEstimator
|
||||
weightEstimate.AddP2WKHOutput()
|
||||
weightEstimate.AddP2WKHOutput()
|
||||
weightEstimate.AddWitnessInput(lnwallet.ToLocalPenaltyWitnessSize)
|
||||
weightEstimate.AddWitnessInput(lnwallet.P2WKHWitnessSize)
|
||||
txWeight := weightEstimate.Weight()
|
||||
|
||||
// Create a session info so that simulate agreement of the sweep
|
||||
// parameters that should be used in constructing the justice
|
||||
// transaction.
|
||||
sessionInfo := &wtdb.SessionInfo{
|
||||
SweepFeeRate: 2000,
|
||||
RewardRate: 900000,
|
||||
RewardAddress: makeAddrSlice(22),
|
||||
}
|
||||
|
||||
// Given the total input amount and the weight estimate, compute the
|
||||
// amount that should be swept for the victim and the amount taken as a
|
||||
// reward by the watchtower.
|
||||
sweepAmt, rewardAmt, err := sessionInfo.ComputeSweepOutputs(
|
||||
totalAmount, int64(txWeight),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to compute sweep outputs: %v", err)
|
||||
}
|
||||
|
||||
// Begin to assemble the justice kit, starting with the sweep address,
|
||||
// pubkeys, and csv delay.
|
||||
justiceKit := &blob.JusticeKit{
|
||||
SweepAddress: makeAddrSlice(22),
|
||||
CSVDelay: csvDelay,
|
||||
}
|
||||
copy(justiceKit.RevocationPubKey[:], revPK.SerializeCompressed())
|
||||
copy(justiceKit.LocalDelayPubKey[:], toLocalPK.SerializeCompressed())
|
||||
copy(justiceKit.CommitToRemotePubKey[:], toRemotePK.SerializeCompressed())
|
||||
|
||||
// Create a transaction spending from the outputs of the breach
|
||||
// transaction created earlier. The inputs are always ordered w/
|
||||
// to-local and then to-remote. The outputs are always added as the
|
||||
// sweep address then reward address.
|
||||
justiceTxn := &wire.MsgTx{
|
||||
Version: 2,
|
||||
TxIn: []*wire.TxIn{
|
||||
{
|
||||
PreviousOutPoint: wire.OutPoint{
|
||||
Hash: breachTxID,
|
||||
Index: 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
PreviousOutPoint: wire.OutPoint{
|
||||
Hash: breachTxID,
|
||||
Index: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
TxOut: []*wire.TxOut{
|
||||
{
|
||||
|
||||
Value: int64(sweepAmt),
|
||||
PkScript: justiceKit.SweepAddress,
|
||||
},
|
||||
{
|
||||
|
||||
Value: int64(rewardAmt),
|
||||
PkScript: sessionInfo.RewardAddress,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
hashCache := txscript.NewTxSigHashes(justiceTxn)
|
||||
|
||||
// Create the sign descriptor used to sign for the to-local input.
|
||||
toLocalSignDesc := &lnwallet.SignDescriptor{
|
||||
KeyDesc: keychain.KeyDescriptor{
|
||||
KeyLocator: revKeyLoc,
|
||||
},
|
||||
WitnessScript: toLocalScript,
|
||||
Output: breachTxn.TxOut[0],
|
||||
SigHashes: hashCache,
|
||||
InputIndex: 0,
|
||||
HashType: txscript.SigHashAll,
|
||||
}
|
||||
|
||||
// Create the sign descriptor used to sign for the to-remote input.
|
||||
toRemoteSignDesc := &lnwallet.SignDescriptor{
|
||||
KeyDesc: keychain.KeyDescriptor{
|
||||
KeyLocator: toRemoteKeyLoc,
|
||||
PubKey: toRemotePK,
|
||||
},
|
||||
WitnessScript: toRemoteScriptHash,
|
||||
Output: breachTxn.TxOut[1],
|
||||
SigHashes: hashCache,
|
||||
InputIndex: 1,
|
||||
HashType: txscript.SigHashAll,
|
||||
}
|
||||
|
||||
// Verify that our test justice transaction is sane.
|
||||
btx := btcutil.NewTx(justiceTxn)
|
||||
if err := blockchain.CheckTransactionSanity(btx); err != nil {
|
||||
t.Fatalf("justice txn is not sane: %v", err)
|
||||
}
|
||||
|
||||
// Compute a DER-encoded signature for the to-local input.
|
||||
toLocalSigRaw, err := signer.SignOutputRaw(justiceTxn, toLocalSignDesc)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to sign to-local input: %v", err)
|
||||
}
|
||||
|
||||
// Compute the witness for the to-remote input. The first element is a
|
||||
// DER-encoded signature under the to-remote pubkey. The sighash flag is
|
||||
// also present, so we trim it.
|
||||
toRemoteWitness, err := lnwallet.CommitSpendNoDelay(
|
||||
signer, toRemoteSignDesc, justiceTxn,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to sign to-remote input: %v", err)
|
||||
}
|
||||
toRemoteSigRaw := toRemoteWitness[0][:len(toRemoteWitness[0])-1]
|
||||
|
||||
// Convert the DER to-local sig into a fixed-size signature.
|
||||
toLocalSig, err := lnwire.NewSigFromRawSignature(toLocalSigRaw)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to parse to-local signature: %v", err)
|
||||
}
|
||||
|
||||
// Convert the DER to-remote sig into a fixed-size signature.
|
||||
toRemoteSig, err := lnwire.NewSigFromRawSignature(toRemoteSigRaw)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to parse to-remote signature: %v", err)
|
||||
}
|
||||
|
||||
// Complete our justice kit by copying the signatures into the payload.
|
||||
copy(justiceKit.CommitToLocalSig[:], toLocalSig[:])
|
||||
copy(justiceKit.CommitToRemoteSig[:], toRemoteSig[:])
|
||||
|
||||
justiceDesc := &lookout.JusticeDescriptor{
|
||||
BreachedCommitTx: breachTxn,
|
||||
SessionInfo: sessionInfo,
|
||||
JusticeKit: justiceKit,
|
||||
}
|
||||
|
||||
// Construct a breach punisher that will feed published transactions
|
||||
// over the buffered channel.
|
||||
publications := make(chan *wire.MsgTx, 1)
|
||||
punisher := lookout.NewBreachPunisher(&lookout.PunisherConfig{
|
||||
PublishTx: func(tx *wire.MsgTx) error {
|
||||
publications <- tx
|
||||
return nil
|
||||
},
|
||||
})
|
||||
|
||||
// Exact retribution on the offender. If no error is returned, we expect
|
||||
// the justice transaction to be published via the channel.
|
||||
err = punisher.Punish(justiceDesc, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to punish breach: %v", err)
|
||||
}
|
||||
|
||||
// Retrieve the published justice transaction.
|
||||
var wtJusticeTxn *wire.MsgTx
|
||||
select {
|
||||
case wtJusticeTxn = <-publications:
|
||||
case <-time.After(50 * time.Millisecond):
|
||||
t.Fatalf("punisher did not publish justice txn")
|
||||
}
|
||||
|
||||
// Construct the test's to-local witness.
|
||||
justiceTxn.TxIn[0].Witness = make([][]byte, 3)
|
||||
justiceTxn.TxIn[0].Witness[0] = append(toLocalSigRaw,
|
||||
byte(txscript.SigHashAll))
|
||||
justiceTxn.TxIn[0].Witness[1] = []byte{1}
|
||||
justiceTxn.TxIn[0].Witness[2] = toLocalScript
|
||||
|
||||
// Construct the test's to-remote witness.
|
||||
justiceTxn.TxIn[1].Witness = make([][]byte, 2)
|
||||
justiceTxn.TxIn[1].Witness[0] = append(toRemoteSigRaw,
|
||||
byte(txscript.SigHashAll))
|
||||
justiceTxn.TxIn[1].Witness[1] = toRemotePK.SerializeCompressed()
|
||||
|
||||
// Assert that the watchtower derives the same justice txn.
|
||||
if !reflect.DeepEqual(justiceTxn, wtJusticeTxn) {
|
||||
t.Fatalf("expected justice txn: %v\ngot %v",
|
||||
spew.Sdump(justiceTxn),
|
||||
spew.Sdump(wtJusticeTxn))
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user