lnd.xprv/watchtower/blob/justice_kit_test.go
Conner Fromknecht ffe15e2820
watchtower/blob/justice_kit: add BlobType to JusticeKit struct
This is preparation for later commits where the values returned by
member methods will need to be conditioned on the blob type used during
decryption.
2020-09-15 12:43:37 -04:00

352 lines
11 KiB
Go

package blob_test
import (
"bytes"
"crypto/rand"
"encoding/binary"
"io"
"reflect"
"testing"
"github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcd/txscript"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/watchtower/blob"
"github.com/stretchr/testify/require"
)
func makePubKey(i uint64) blob.PubKey {
var pk blob.PubKey
pk[0] = 0x02
if i%2 == 1 {
pk[0] |= 0x01
}
binary.BigEndian.PutUint64(pk[1:9], i)
return pk
}
func makeSig(i int) lnwire.Sig {
var sig lnwire.Sig
binary.BigEndian.PutUint64(sig[:8], uint64(i))
return sig
}
func makeAddr(size int) []byte {
addr := make([]byte, size)
if _, err := io.ReadFull(rand.Reader, addr); err != nil {
panic("unable to create addr")
}
return addr
}
type descriptorTest struct {
name string
encVersion blob.Type
decVersion blob.Type
sweepAddr []byte
revPubKey blob.PubKey
delayPubKey blob.PubKey
csvDelay uint32
commitToLocalSig lnwire.Sig
hasCommitToRemote bool
commitToRemotePubKey blob.PubKey
commitToRemoteSig lnwire.Sig
encErr error
decErr error
}
var descriptorTests = []descriptorTest{
{
name: "to-local only",
encVersion: blob.TypeAltruistCommit,
decVersion: blob.TypeAltruistCommit,
sweepAddr: makeAddr(22),
revPubKey: makePubKey(0),
delayPubKey: makePubKey(1),
csvDelay: 144,
commitToLocalSig: makeSig(1),
},
{
name: "to-local and p2wkh",
encVersion: blob.TypeRewardCommit,
decVersion: blob.TypeRewardCommit,
sweepAddr: makeAddr(22),
revPubKey: makePubKey(0),
delayPubKey: makePubKey(1),
csvDelay: 144,
commitToLocalSig: makeSig(1),
hasCommitToRemote: true,
commitToRemotePubKey: makePubKey(2),
commitToRemoteSig: makeSig(2),
},
{
name: "unknown encrypt version",
encVersion: 0,
decVersion: blob.TypeAltruistCommit,
sweepAddr: makeAddr(34),
revPubKey: makePubKey(0),
delayPubKey: makePubKey(1),
csvDelay: 144,
commitToLocalSig: makeSig(1),
encErr: blob.ErrUnknownBlobType,
},
{
name: "unknown decrypt version",
encVersion: blob.TypeAltruistCommit,
decVersion: 0,
sweepAddr: makeAddr(34),
revPubKey: makePubKey(0),
delayPubKey: makePubKey(1),
csvDelay: 144,
commitToLocalSig: makeSig(1),
decErr: blob.ErrUnknownBlobType,
},
{
name: "sweep addr length zero",
encVersion: blob.TypeAltruistCommit,
decVersion: blob.TypeAltruistCommit,
sweepAddr: makeAddr(0),
revPubKey: makePubKey(0),
delayPubKey: makePubKey(1),
csvDelay: 144,
commitToLocalSig: makeSig(1),
},
{
name: "sweep addr max size",
encVersion: blob.TypeAltruistCommit,
decVersion: blob.TypeAltruistCommit,
sweepAddr: makeAddr(blob.MaxSweepAddrSize),
revPubKey: makePubKey(0),
delayPubKey: makePubKey(1),
csvDelay: 144,
commitToLocalSig: makeSig(1),
},
{
name: "sweep addr too long",
encVersion: blob.TypeAltruistCommit,
decVersion: blob.TypeAltruistCommit,
sweepAddr: makeAddr(blob.MaxSweepAddrSize + 1),
revPubKey: makePubKey(0),
delayPubKey: makePubKey(1),
csvDelay: 144,
commitToLocalSig: makeSig(1),
encErr: blob.ErrSweepAddressToLong,
},
}
// TestBlobJusticeKitEncryptDecrypt asserts that encrypting and decrypting a
// plaintext blob produces the original. The tests include negative assertions
// when passed invalid combinations, and that all successfully encrypted blobs
// are of constant size.
func TestBlobJusticeKitEncryptDecrypt(t *testing.T) {
for _, test := range descriptorTests {
t.Run(test.name, func(t *testing.T) {
testBlobJusticeKitEncryptDecrypt(t, test)
})
}
}
func testBlobJusticeKitEncryptDecrypt(t *testing.T, test descriptorTest) {
boj := &blob.JusticeKit{
BlobType: test.encVersion,
SweepAddress: test.sweepAddr,
RevocationPubKey: test.revPubKey,
LocalDelayPubKey: test.delayPubKey,
CSVDelay: test.csvDelay,
CommitToLocalSig: test.commitToLocalSig,
CommitToRemotePubKey: test.commitToRemotePubKey,
CommitToRemoteSig: test.commitToRemoteSig,
}
// Generate a random encryption key for the blob. The key is
// sized at 32 byte, as in practice we will be using the remote
// party's commitment txid as the key.
var key blob.BreachKey
_, err := rand.Read(key[:])
if err != nil {
t.Fatalf("unable to generate blob encryption key: %v", err)
}
// Encrypt the blob plaintext using the generated key and
// target version for this test.
ctxt, err := boj.Encrypt(key)
if err != test.encErr {
t.Fatalf("unable to encrypt blob: %v", err)
} else if test.encErr != nil {
// If the test expected an encryption failure, we can
// continue to the next test.
return
}
// Ensure that all encrypted blobs are padded out to the same
// size: 282 bytes for version 0.
if len(ctxt) != blob.Size(test.encVersion) {
t.Fatalf("expected blob to have size %d, got %d instead",
blob.Size(test.encVersion), len(ctxt))
}
// Decrypt the encrypted blob, reconstructing the original
// blob plaintext from the decrypted contents. We use the target
// decryption version specified by this test case.
boj2, err := blob.Decrypt(key, ctxt, test.decVersion)
if err != test.decErr {
t.Fatalf("unable to decrypt blob: %v", err)
} else if test.decErr != nil {
// If the test expected an decryption failure, we can
// continue to the next test.
return
}
// Check that the decrypted blob properly reports whether it has
// a to-remote output or not.
if boj2.HasCommitToRemoteOutput() != test.hasCommitToRemote {
t.Fatalf("expected blob has_to_remote to be %v, got %v",
test.hasCommitToRemote, boj2.HasCommitToRemoteOutput())
}
// Check that the original blob plaintext matches the
// one reconstructed from the encrypted blob.
if !reflect.DeepEqual(boj, boj2) {
t.Fatalf("decrypted plaintext does not match original, "+
"want: %v, got %v", boj, boj2)
}
}
// TestJusticeKitRemoteWitnessConstruction tests that a JusticeKit returns the
// proper to-remote witnes script and to-remote witness stack. This should be
// equivalent to p2wkh spend.
func TestJusticeKitRemoteWitnessConstruction(t *testing.T) {
// Generate the to-remote pubkey.
toRemotePrivKey, err := btcec.NewPrivateKey(btcec.S256())
require.Nil(t, err)
// Copy the to-remote pubkey into the format expected by our justice
// kit.
var toRemotePubKey blob.PubKey
copy(toRemotePubKey[:], toRemotePrivKey.PubKey().SerializeCompressed())
// Sign a message using the to-remote private key. The exact message
// doesn't matter as we won't be validating the signature's validity.
digest := bytes.Repeat([]byte("a"), 32)
rawToRemoteSig, err := toRemotePrivKey.Sign(digest)
require.Nil(t, err)
// Convert the DER-encoded signature into a fixed-size sig.
commitToRemoteSig, err := lnwire.NewSigFromSignature(rawToRemoteSig)
require.Nil(t, err)
// Populate the justice kit fields relevant to the to-remote output.
justiceKit := &blob.JusticeKit{
CommitToRemotePubKey: toRemotePubKey,
CommitToRemoteSig: commitToRemoteSig,
}
// Now, compute the to-remote witness script returned by the justice
// kit.
toRemoteScript, err := justiceKit.CommitToRemoteWitnessScript()
require.Nil(t, err)
// Assert this is exactly the to-remote, compressed pubkey.
require.Equal(t, toRemoteScript, toRemotePubKey[:])
// Next, compute the to-remote witness stack, which should be a p2wkh
// witness stack consisting solely of a signature.
toRemoteWitnessStack, err := justiceKit.CommitToRemoteWitnessStack()
require.Nil(t, err)
// Compute the expected first element, by appending a sighash all byte
// to our raw DER-encoded signature.
rawToRemoteSigWithSigHash := append(
rawToRemoteSig.Serialize(), byte(txscript.SigHashAll),
)
// Assert that the expected witness stack is returned.
expWitnessStack := [][]byte{
rawToRemoteSigWithSigHash,
}
require.Equal(t, expWitnessStack, toRemoteWitnessStack)
// Finally, set the CommitToRemotePubKey to be a blank value.
justiceKit.CommitToRemotePubKey = blob.PubKey{}
// When trying to compute the witness script, this should now return
// ErrNoCommitToRemoteOutput since a valid pubkey could not be parsed
// from CommitToRemotePubKey.
_, err = justiceKit.CommitToRemoteWitnessScript()
require.Error(t, blob.ErrNoCommitToRemoteOutput, err)
}
// TestJusticeKitToLocalWitnessConstruction tests that a JusticeKit returns the
// proper to-local witness script and to-local witness stack for spending the
// revocation path.
func TestJusticeKitToLocalWitnessConstruction(t *testing.T) {
csvDelay := uint32(144)
// Generate the revocation and delay private keys.
revPrivKey, err := btcec.NewPrivateKey(btcec.S256())
require.Nil(t, err)
delayPrivKey, err := btcec.NewPrivateKey(btcec.S256())
require.Nil(t, err)
// Copy the revocation and delay pubkeys into the format expected by our
// justice kit.
var revPubKey blob.PubKey
copy(revPubKey[:], revPrivKey.PubKey().SerializeCompressed())
var delayPubKey blob.PubKey
copy(delayPubKey[:], delayPrivKey.PubKey().SerializeCompressed())
// Sign a message using the revocation private key. The exact message
// doesn't matter as we won't be validating the signature's validity.
digest := bytes.Repeat([]byte("a"), 32)
rawRevSig, err := revPrivKey.Sign(digest)
require.Nil(t, err)
// Convert the DER-encoded signature into a fixed-size sig.
commitToLocalSig, err := lnwire.NewSigFromSignature(rawRevSig)
require.Nil(t, err)
// Populate the justice kit with fields relevant to the to-local output.
justiceKit := &blob.JusticeKit{
CSVDelay: csvDelay,
RevocationPubKey: revPubKey,
LocalDelayPubKey: delayPubKey,
CommitToLocalSig: commitToLocalSig,
}
// Compute the expected to-local script, which is a function of the CSV
// delay, revocation pubkey and delay pubkey.
expToLocalScript, err := input.CommitScriptToSelf(
csvDelay, delayPrivKey.PubKey(), revPrivKey.PubKey(),
)
require.Nil(t, err)
// Compute the to-local script that is returned by the justice kit.
toLocalScript, err := justiceKit.CommitToLocalWitnessScript()
require.Nil(t, err)
// Assert that the expected to-local script matches the actual script.
require.Equal(t, expToLocalScript, toLocalScript)
// Next, compute the to-local witness stack returned by the justice kit.
toLocalWitnessStack, err := justiceKit.CommitToLocalRevokeWitnessStack()
require.Nil(t, err)
// Compute the expected signature in the bottom element of the stack, by
// appending a sighash all flag to the raw DER signature.
rawRevSigWithSigHash := append(
rawRevSig.Serialize(), byte(txscript.SigHashAll),
)
// Finally, validate against our expected witness stack.
expWitnessStack := [][]byte{
rawRevSigWithSigHash,
{1},
}
require.Equal(t, expWitnessStack, toLocalWitnessStack)
}