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

@@ -4,7 +4,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/chaincfg"
@@ -247,25 +246,11 @@ func (t *backupTask) craftSessionPayload(
var hint blob.BreachHint
// First, copy over the sweep pkscript, the pubkeys used to derive the
// to-local script, and the remote CSV delay.
keyRing := t.breachInfo.KeyRing
justiceKit := &blob.JusticeKit{
BlobType: t.blobType,
SweepAddress: t.sweepPkScript,
RevocationPubKey: toBlobPubKey(keyRing.RevocationKey),
LocalDelayPubKey: toBlobPubKey(keyRing.ToLocalKey),
CSVDelay: t.breachInfo.RemoteDelay,
}
// If this commitment has an output that pays to us, copy the to-remote
// pubkey into the justice kit. This serves as the indicator to the
// tower that we expect the breaching transaction to have a non-dust
// output to spend from.
if t.toRemoteInput != nil {
justiceKit.CommitToRemotePubKey = toBlobPubKey(
keyRing.ToRemoteKey,
)
justiceKit, err := t.commitmentType.NewJusticeKit(
t.sweepPkScript, t.breachInfo, t.toRemoteInput != nil,
)
if err != nil {
return hint, nil, err
}
// Now, begin construction of the justice transaction. We'll start with
@@ -348,9 +333,9 @@ func (t *backupTask) craftSessionPayload(
// field
switch inp.WitnessType() {
case toLocalWitnessType:
justiceKit.CommitToLocalSig = signature
justiceKit.AddToLocalSig(signature)
case toRemoteWitnessType:
justiceKit.CommitToRemoteSig = signature
justiceKit.AddToRemoteSig(signature)
default:
return hint, nil, fmt.Errorf("invalid witness type: %v",
inp.WitnessType())
@@ -365,18 +350,10 @@ func (t *backupTask) craftSessionPayload(
// Then, we'll encrypt the computed justice kit using the full breach
// transaction id, which will allow the tower to recover the contents
// after the transaction is seen in the chain or mempool.
encBlob, err := justiceKit.Encrypt(key)
encBlob, err := blob.Encrypt(justiceKit, key)
if err != nil {
return hint, nil, err
}
return hint, encBlob, nil
}
// toBlobPubKey serializes the given pubkey into a blob.PubKey that can be set
// as a field on a blob.JusticeKit.
func toBlobPubKey(pubKey *btcec.PublicKey) blob.PubKey {
var blobPubKey blob.PubKey
copy(blobPubKey[:], pubKey.SerializeCompressed())
return blobPubKey
}

View File

@@ -1,7 +1,7 @@
package wtclient
import (
"bytes"
"encoding/binary"
"testing"
"github.com/btcsuite/btcd/btcec/v2"
@@ -25,8 +25,7 @@ import (
const csvDelay uint32 = 144
var (
zeroPK [33]byte
zeroSig [64]byte
zeroSig = makeSig(0)
revPrivBytes = []byte{
0x8f, 0x4b, 0x51, 0x83, 0xa9, 0x34, 0xbd, 0x5f,
@@ -65,6 +64,7 @@ type backupTaskTest struct {
expSweepScript []byte
signer input.Signer
chanType channeldb.ChannelType
commitType blob.CommitmentType
}
// genTaskTest creates a instance of a backupTaskTest using the passed
@@ -210,6 +210,7 @@ func genTaskTest(
expSweepScript: sweepAddr,
signer: signer,
chanType: chanType,
commitType: commitType,
}
}
@@ -567,60 +568,35 @@ func testBackupTask(t *testing.T, test backupTaskTest) {
require.NoError(t, err, "unable to decrypt blob")
keyRing := test.breachInfo.KeyRing
expToLocalPK := keyRing.ToLocalKey.SerializeCompressed()
expRevPK := keyRing.RevocationKey.SerializeCompressed()
expToRemotePK := keyRing.ToRemoteKey.SerializeCompressed()
expToLocalPK := keyRing.ToLocalKey
expRevPK := keyRing.RevocationKey
expToRemotePK := keyRing.ToRemoteKey
// Assert that the blob contained the serialized revocation and to-local
// pubkeys.
require.Equal(t, expRevPK, jKit.RevocationPubKey[:])
require.Equal(t, expToLocalPK, jKit.LocalDelayPubKey[:])
// Determine if the breach transaction has a to-remote output and/or
// to-local output to spend from. Note the seemingly-reversed
// nomenclature.
hasToRemote := test.breachInfo.LocalOutputSignDesc != nil
hasToLocal := test.breachInfo.RemoteOutputSignDesc != nil
// If the to-remote output is present, assert that the to-remote public
// key was included in the blob. Otherwise assert that a blank public
// key was inserted.
if hasToRemote {
require.Equal(t, expToRemotePK, jKit.CommitToRemotePubKey[:])
} else {
require.Equal(t, zeroPK[:], jKit.CommitToRemotePubKey[:])
breachInfo := &lnwallet.BreachRetribution{
RemoteDelay: csvDelay,
KeyRing: &lnwallet.CommitmentKeyRing{
ToLocalKey: expToLocalPK,
RevocationKey: expRevPK,
ToRemoteKey: expToRemotePK,
},
}
// Assert that the CSV is encoded in the blob.
require.Equal(t, test.breachInfo.RemoteDelay, jKit.CSVDelay)
// Assert that the sweep pkscript is included.
require.Equal(t, test.expSweepScript, jKit.SweepAddress)
// Finally, verify that the signatures are encoded in the justice kit.
// We don't validate the actual signatures produced here, since at the
// moment, it is tested indirectly by other packages and integration
// tests.
// TODO(conner): include signature validation checks
emptyToLocalSig := bytes.Equal(
jKit.CommitToLocalSig.RawBytes(), zeroSig[:],
expectedKit, err := test.commitType.NewJusticeKit(
test.expSweepScript, breachInfo, test.expToRemoteInput != nil,
)
if hasToLocal {
require.False(t, emptyToLocalSig, "to-local signature should "+
"not be empty")
} else {
require.True(t, emptyToLocalSig, "to-local signature should "+
"be empty")
}
require.NoError(t, err)
emptyToRemoteSig := bytes.Equal(
jKit.CommitToRemoteSig.RawBytes(), zeroSig[:],
)
if hasToRemote {
require.False(t, emptyToRemoteSig, "to-remote signature "+
"should not be empty")
} else {
require.True(t, emptyToRemoteSig, "to-remote signature "+
"should be empty")
}
jKit.AddToLocalSig(zeroSig)
jKit.AddToRemoteSig(zeroSig)
require.Equal(t, expectedKit, jKit)
}
func makeSig(i int) lnwire.Sig {
var sigBytes [64]byte
binary.BigEndian.PutUint64(sigBytes[:8], uint64(i))
sig, _ := lnwire.NewSigFromWireECDSA(sigBytes[:])
return sig
}