watchtower: convert JusticeKit to interface

In this commit, we convert the `JusticeKit` struct to an interface.
Then, we add two implementations of that interface:
1) The `legacyJusticeKit` which implements all the methods of
   `JusticeKit`
2) The `anchorJusticKit` which wraps the `legacyJusticeKit` and just
   re-implements the `ToRemoteOutputSpendInfo` method since.
This commit is contained in:
Elle Mouton
2023-08-23 10:51:57 +02:00
parent 048dc54110
commit 154e9fafec
14 changed files with 666 additions and 520 deletions

View File

@@ -5,7 +5,6 @@ import (
"fmt"
"github.com/btcsuite/btcd/blockchain"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/btcutil/txsort"
"github.com/btcsuite/btcd/txscript"
@@ -41,7 +40,7 @@ type JusticeDescriptor struct {
// JusticeKit contains the decrypted blob and information required to
// construct the transaction scripts and witnesses.
JusticeKit *blob.JusticeKit
JusticeKit blob.JusticeKit
}
// breachedInput contains the required information to construct and spend
@@ -56,22 +55,17 @@ type breachedInput struct {
// commitToLocalInput extracts the information required to spend the commit
// to-local output.
func (p *JusticeDescriptor) commitToLocalInput() (*breachedInput, error) {
// Retrieve the to-local witness script from the justice kit.
toLocalScript, err := p.JusticeKit.CommitToLocalWitnessScript()
if err != nil {
return nil, err
}
kit := p.JusticeKit
// Compute the witness script hash, which will be used to locate the
// input on the breaching commitment transaction.
toLocalWitnessHash, err := input.WitnessScriptHash(toLocalScript)
// Retrieve the to-local output script and witness from the justice kit.
toLocalPkScript, witness, err := kit.ToLocalOutputSpendInfo()
if err != nil {
return nil, err
}
// Locate the to-local output on the breaching commitment transaction.
toLocalIndex, toLocalTxOut, err := findTxOutByPkScript(
p.BreachedCommitTx, toLocalWitnessHash,
p.BreachedCommitTx, toLocalPkScript,
)
if err != nil {
return nil, err
@@ -84,63 +78,28 @@ func (p *JusticeDescriptor) commitToLocalInput() (*breachedInput, error) {
Index: toLocalIndex,
}
// Retrieve to-local witness stack, which primarily includes a signature
// under the revocation pubkey.
witnessStack, err := p.JusticeKit.CommitToLocalRevokeWitnessStack()
if err != nil {
return nil, err
}
return &breachedInput{
txOut: toLocalTxOut,
outPoint: toLocalOutPoint,
witness: buildWitness(witnessStack, toLocalScript),
witness: witness,
}, nil
}
// commitToRemoteInput extracts the information required to spend the commit
// to-remote output.
func (p *JusticeDescriptor) commitToRemoteInput() (*breachedInput, error) {
// Retrieve the to-remote witness script from the justice kit.
toRemoteScript, err := p.JusticeKit.CommitToRemoteWitnessScript()
kit := p.JusticeKit
// Retrieve the to-remote output script, witness script and sequence
// from the justice kit.
toRemotePkScript, witness, seq, err := kit.ToRemoteOutputSpendInfo()
if err != nil {
return nil, err
}
var (
toRemoteScriptHash []byte
toRemoteSequence uint32
)
if p.JusticeKit.BlobType.IsAnchorChannel() {
toRemoteScriptHash, err = input.WitnessScriptHash(
toRemoteScript,
)
if err != nil {
return nil, err
}
toRemoteSequence = 1
} else {
// Since the to-remote witness script should just be a regular p2wkh
// output, we'll parse it to retrieve the public key.
toRemotePubKey, err := btcec.ParsePubKey(toRemoteScript)
if err != nil {
return nil, err
}
// Compute the witness script hash from the to-remote pubkey, which will
// be used to locate the input on the breach commitment transaction.
toRemoteScriptHash, err = input.CommitScriptUnencumbered(
toRemotePubKey,
)
if err != nil {
return nil, err
}
}
// Locate the to-remote output on the breaching commitment transaction.
toRemoteIndex, toRemoteTxOut, err := findTxOutByPkScript(
p.BreachedCommitTx, toRemoteScriptHash,
p.BreachedCommitTx, toRemotePkScript,
)
if err != nil {
return nil, err
@@ -153,18 +112,11 @@ func (p *JusticeDescriptor) commitToRemoteInput() (*breachedInput, error) {
Index: toRemoteIndex,
}
// Retrieve the to-remote witness stack, which is just a signature under
// the to-remote pubkey.
witnessStack, err := p.JusticeKit.CommitToRemoteWitnessStack()
if err != nil {
return nil, err
}
return &breachedInput{
txOut: toRemoteTxOut,
outPoint: toRemoteOutPoint,
witness: buildWitness(witnessStack, toRemoteScript),
sequence: toRemoteSequence,
witness: witness,
sequence: seq,
}, nil
}
@@ -193,7 +145,7 @@ func (p *JusticeDescriptor) assembleJusticeTxn(txWeight int64,
// reward sweep, there will be two outputs, one of which pays back to
// the victim while the other gives a cut to the tower.
outputs, err := p.SessionInfo.Policy.ComputeJusticeTxOuts(
totalAmt, txWeight, p.JusticeKit.SweepAddress[:],
totalAmt, txWeight, p.JusticeKit.SweepAddress(),
p.SessionInfo.RewardAddress,
)
if err != nil {
@@ -271,7 +223,7 @@ func (p *JusticeDescriptor) CreateJusticeTxn() (*wire.MsgTx, error) {
// Add the sweep address's contribution, depending on whether it is a
// p2wkh or p2wsh output.
switch len(p.JusticeKit.SweepAddress) {
switch len(p.JusticeKit.SweepAddress()) {
case input.P2WPKHSize:
weightEstimate.AddP2WKHOutput()
@@ -344,9 +296,9 @@ func (p *JusticeDescriptor) CreateJusticeTxn() (*wire.MsgTx, error) {
//
// NOTE: The search stops after the first match is found.
func findTxOutByPkScript(txn *wire.MsgTx,
pkScript []byte) (uint32, *wire.TxOut, error) {
pkScript *txscript.PkScript) (uint32, *wire.TxOut, error) {
found, index := input.FindScriptOutputIndex(txn, pkScript)
found, index := input.FindScriptOutputIndex(txn, pkScript.Script())
if !found {
return 0, nil, ErrOutputNotFound
}
@@ -354,15 +306,6 @@ func findTxOutByPkScript(txn *wire.MsgTx,
return index, txn.TxOut[index], nil
}
// buildWitness appends the witness script to a given witness stack.
func buildWitness(witnessStack [][]byte, witnessScript []byte) [][]byte {
witness := make([][]byte, len(witnessStack)+1)
lastIdx := copy(witness, witnessStack)
witness[lastIdx] = witnessScript
return witness
}
// prevOutFetcher returns a txscript.MultiPrevOutFetcher for the given set
// of inputs.
func prevOutFetcher(inputs []*breachedInput) (*txscript.MultiPrevOutFetcher,

View File

@@ -12,6 +12,7 @@ import (
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/input"
"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"
@@ -221,16 +222,19 @@ func testJusticeDescriptor(t *testing.T, blobType blob.Type) {
RewardAddress: makeAddrSlice(22),
}
// Begin to assemble the justice kit, starting with the sweep address,
// pubkeys, and csv delay.
justiceKit := &blob.JusticeKit{
BlobType: blobType,
SweepAddress: makeAddrSlice(22),
CSVDelay: csvDelay,
breachInfo := &lnwallet.BreachRetribution{
RemoteDelay: csvDelay,
KeyRing: &lnwallet.CommitmentKeyRing{
ToLocalKey: toLocalPK,
ToRemoteKey: toRemotePK,
RevocationKey: revPK,
},
}
copy(justiceKit.RevocationPubKey[:], revPK.SerializeCompressed())
copy(justiceKit.LocalDelayPubKey[:], toLocalPK.SerializeCompressed())
copy(justiceKit.CommitToRemotePubKey[:], toRemotePK.SerializeCompressed())
justiceKit, err := commitType.NewJusticeKit(
makeAddrSlice(22), breachInfo, true,
)
require.NoError(t, err)
// Create a transaction spending from the outputs of the breach
// transaction created earlier. The inputs are always ordered w/
@@ -256,7 +260,7 @@ func testJusticeDescriptor(t *testing.T, blobType blob.Type) {
}
outputs, err := policy.ComputeJusticeTxOuts(
totalAmount, int64(txWeight), justiceKit.SweepAddress,
totalAmount, int64(txWeight), justiceKit.SweepAddress(),
sessionInfo.RewardAddress,
)
require.NoError(t, err)
@@ -316,8 +320,8 @@ func testJusticeDescriptor(t *testing.T, blobType blob.Type) {
require.Nil(t, err)
// Complete our justice kit by copying the signatures into the payload.
justiceKit.CommitToLocalSig = toLocalSig
justiceKit.CommitToRemoteSig = toRemoteSig
justiceKit.AddToLocalSig(toLocalSig)
justiceKit.AddToRemoteSig(toRemoteSig)
justiceDesc := &lookout.JusticeDescriptor{
BreachedCommitTx: breachTxn,

View File

@@ -8,8 +8,10 @@ import (
"testing"
"time"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/watchtower/blob"
"github.com/lightningnetwork/lnd/watchtower/lookout"
@@ -30,10 +32,9 @@ func (p *mockPunisher) Punish(
return nil
}
func makeArray32(i uint64) [32]byte {
var arr [32]byte
binary.BigEndian.PutUint64(arr[:], i)
return arr
func makeRandomPK() *btcec.PublicKey {
pk, _ := btcec.NewPrivateKey()
return pk.PubKey()
}
func makeArray33(i uint64) [33]byte {
@@ -142,32 +143,49 @@ func TestLookoutBreachMatching(t *testing.T) {
// Construct a justice kit for each possible breach transaction.
blobType := blob.FlagCommitOutputs.Type()
blob1 := &blob.JusticeKit{
BlobType: blobType,
SweepAddress: makeAddrSlice(22),
RevocationPubKey: makePubKey(1),
LocalDelayPubKey: makePubKey(1),
CSVDelay: 144,
CommitToLocalSig: makeTestSig(1),
breachInfo1 := &lnwallet.BreachRetribution{
RemoteDelay: 144,
KeyRing: &lnwallet.CommitmentKeyRing{
ToLocalKey: makeRandomPK(),
RevocationKey: makeRandomPK(),
},
}
blob2 := &blob.JusticeKit{
BlobType: blobType,
SweepAddress: makeAddrSlice(22),
RevocationPubKey: makePubKey(2),
LocalDelayPubKey: makePubKey(2),
CSVDelay: 144,
CommitToLocalSig: makeTestSig(2),
commitment1, err := blobType.CommitmentType(nil)
require.NoError(t, err)
blob1, err := commitment1.NewJusticeKit(
makeAddrSlice(22), breachInfo1, false,
)
require.NoError(t, err)
blob1.AddToLocalSig(makeTestSig(1))
breachInfo2 := &lnwallet.BreachRetribution{
RemoteDelay: 144,
KeyRing: &lnwallet.CommitmentKeyRing{
ToLocalKey: makeRandomPK(),
RevocationKey: makeRandomPK(),
},
}
commitment2, err := blobType.CommitmentType(nil)
require.NoError(t, err)
blob2, err := commitment2.NewJusticeKit(
makeAddrSlice(22), breachInfo2, false,
)
require.NoError(t, err)
blob2.AddToLocalSig(makeTestSig(1))
key1 := blob.NewBreachKeyFromHash(&hash1)
key2 := blob.NewBreachKeyFromHash(&hash2)
// Encrypt the first justice kit under breach key one.
encBlob1, err := blob1.Encrypt(key1)
encBlob1, err := blob.Encrypt(blob1, key1)
require.NoError(t, err, "unable to encrypt sweep detail 1")
// Encrypt the second justice kit under breach key two.
encBlob2, err := blob2.Encrypt(key2)
encBlob2, err := blob.Encrypt(blob2, key2)
require.NoError(t, err, "unable to encrypt sweep detail 2")
// Add both state updates to the tower's database.