Merge pull request #4576 from cfromknecht/anchor-justice-txn
watchtower: conditionally reconstruct justice txns for anchor channels
This commit is contained in:
commit
50976e5418
@ -100,6 +100,15 @@ type PubKey [33]byte
|
|||||||
// and for a watchtower to later decrypt if action must be taken. The encoding
|
// and for a watchtower to later decrypt if action must be taken. The encoding
|
||||||
// format is versioned to allow future extensions.
|
// format is versioned to allow future extensions.
|
||||||
type JusticeKit struct {
|
type JusticeKit struct {
|
||||||
|
// BlobType encodes a bitfield that inform the tower of various features
|
||||||
|
// requested by the client when resolving a breach. Examples include
|
||||||
|
// whether the justice transaction contains a reward for the tower, or
|
||||||
|
// whether the channel is a legacy or anchor channel.
|
||||||
|
//
|
||||||
|
// NOTE: This value is not serialized in the encrypted payload. It is
|
||||||
|
// stored separately and added to the JusticeKit after decryption.
|
||||||
|
BlobType Type
|
||||||
|
|
||||||
// SweepAddress is the witness program of the output where the client's
|
// SweepAddress is the witness program of the output where the client's
|
||||||
// fund will be deposited. This value is included in the blobs, as
|
// fund will be deposited. This value is included in the blobs, as
|
||||||
// opposed to the session info, such that the sweep addresses can't be
|
// opposed to the session info, such that the sweep addresses can't be
|
||||||
@ -187,17 +196,33 @@ func (b *JusticeKit) HasCommitToRemoteOutput() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CommitToRemoteWitnessScript returns the witness script for the commitment
|
// CommitToRemoteWitnessScript returns the witness script for the commitment
|
||||||
// to-remote p2wkh output, which is the pubkey itself.
|
// to-remote output given the blob type. The script returned will either be for
|
||||||
|
// a p2wpkh to-remote output or an p2wsh anchor to-remote output which includes
|
||||||
|
// a CSV delay.
|
||||||
func (b *JusticeKit) CommitToRemoteWitnessScript() ([]byte, error) {
|
func (b *JusticeKit) CommitToRemoteWitnessScript() ([]byte, error) {
|
||||||
if !btcec.IsCompressedPubKey(b.CommitToRemotePubKey[:]) {
|
if !btcec.IsCompressedPubKey(b.CommitToRemotePubKey[:]) {
|
||||||
return nil, ErrNoCommitToRemoteOutput
|
return nil, ErrNoCommitToRemoteOutput
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If this is a blob for an anchor channel, we'll return the p2wsh
|
||||||
|
// output containing a CSV delay of 1.
|
||||||
|
if b.BlobType.IsAnchorChannel() {
|
||||||
|
pk, err := btcec.ParsePubKey(
|
||||||
|
b.CommitToRemotePubKey[:], btcec.S256(),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return input.CommitScriptToRemoteConfirmed(pk)
|
||||||
|
}
|
||||||
|
|
||||||
return b.CommitToRemotePubKey[:], nil
|
return b.CommitToRemotePubKey[:], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CommitToRemoteWitnessStack returns a witness stack spending the commitment
|
// CommitToRemoteWitnessStack returns a witness stack spending the commitment
|
||||||
// to-remote output, which is a regular p2wkh.
|
// to-remote output, which consists of a single signature satisfying either the
|
||||||
|
// legacy or anchor witness scripts.
|
||||||
// <to-remote-sig>
|
// <to-remote-sig>
|
||||||
func (b *JusticeKit) CommitToRemoteWitnessStack() ([][]byte, error) {
|
func (b *JusticeKit) CommitToRemoteWitnessStack() ([][]byte, error) {
|
||||||
toRemoteSig, err := b.CommitToRemoteSig.ToSignature()
|
toRemoteSig, err := b.CommitToRemoteSig.ToSignature()
|
||||||
@ -218,11 +243,11 @@ func (b *JusticeKit) CommitToRemoteWitnessStack() ([][]byte, error) {
|
|||||||
//
|
//
|
||||||
// NOTE: It is the caller's responsibility to ensure that this method is only
|
// NOTE: It is the caller's responsibility to ensure that this method is only
|
||||||
// called once for a given (nonce, key) pair.
|
// called once for a given (nonce, key) pair.
|
||||||
func (b *JusticeKit) Encrypt(key BreachKey, blobType Type) ([]byte, error) {
|
func (b *JusticeKit) Encrypt(key BreachKey) ([]byte, error) {
|
||||||
// Encode the plaintext using the provided version, to obtain the
|
// Encode the plaintext using the provided version, to obtain the
|
||||||
// plaintext bytes.
|
// plaintext bytes.
|
||||||
var ptxtBuf bytes.Buffer
|
var ptxtBuf bytes.Buffer
|
||||||
err := b.encode(&ptxtBuf, blobType)
|
err := b.encode(&ptxtBuf, b.BlobType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -236,7 +261,7 @@ func (b *JusticeKit) Encrypt(key BreachKey, blobType Type) ([]byte, error) {
|
|||||||
// Allocate the ciphertext, which will contain the nonce, encrypted
|
// Allocate the ciphertext, which will contain the nonce, encrypted
|
||||||
// plaintext and MAC.
|
// plaintext and MAC.
|
||||||
plaintext := ptxtBuf.Bytes()
|
plaintext := ptxtBuf.Bytes()
|
||||||
ciphertext := make([]byte, Size(blobType))
|
ciphertext := make([]byte, Size(b.BlobType))
|
||||||
|
|
||||||
// Generate a random 24-byte nonce in the ciphertext's prefix.
|
// Generate a random 24-byte nonce in the ciphertext's prefix.
|
||||||
nonce := ciphertext[:NonceSize]
|
nonce := ciphertext[:NonceSize]
|
||||||
@ -284,7 +309,9 @@ func Decrypt(key BreachKey, ciphertext []byte,
|
|||||||
|
|
||||||
// If decryption succeeded, we will then decode the plaintext bytes
|
// If decryption succeeded, we will then decode the plaintext bytes
|
||||||
// using the specified blob version.
|
// using the specified blob version.
|
||||||
boj := &JusticeKit{}
|
boj := &JusticeKit{
|
||||||
|
BlobType: blobType,
|
||||||
|
}
|
||||||
err = boj.decode(bytes.NewReader(plaintext), blobType)
|
err = boj.decode(bytes.NewReader(plaintext), blobType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -13,6 +13,7 @@ import (
|
|||||||
"github.com/lightningnetwork/lnd/input"
|
"github.com/lightningnetwork/lnd/input"
|
||||||
"github.com/lightningnetwork/lnd/lnwire"
|
"github.com/lightningnetwork/lnd/lnwire"
|
||||||
"github.com/lightningnetwork/lnd/watchtower/blob"
|
"github.com/lightningnetwork/lnd/watchtower/blob"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func makePubKey(i uint64) blob.PubKey {
|
func makePubKey(i uint64) blob.PubKey {
|
||||||
@ -149,6 +150,7 @@ func TestBlobJusticeKitEncryptDecrypt(t *testing.T) {
|
|||||||
|
|
||||||
func testBlobJusticeKitEncryptDecrypt(t *testing.T, test descriptorTest) {
|
func testBlobJusticeKitEncryptDecrypt(t *testing.T, test descriptorTest) {
|
||||||
boj := &blob.JusticeKit{
|
boj := &blob.JusticeKit{
|
||||||
|
BlobType: test.encVersion,
|
||||||
SweepAddress: test.sweepAddr,
|
SweepAddress: test.sweepAddr,
|
||||||
RevocationPubKey: test.revPubKey,
|
RevocationPubKey: test.revPubKey,
|
||||||
LocalDelayPubKey: test.delayPubKey,
|
LocalDelayPubKey: test.delayPubKey,
|
||||||
@ -169,7 +171,7 @@ func testBlobJusticeKitEncryptDecrypt(t *testing.T, test descriptorTest) {
|
|||||||
|
|
||||||
// Encrypt the blob plaintext using the generated key and
|
// Encrypt the blob plaintext using the generated key and
|
||||||
// target version for this test.
|
// target version for this test.
|
||||||
ctxt, err := boj.Encrypt(key, test.encVersion)
|
ctxt, err := boj.Encrypt(key)
|
||||||
if err != test.encErr {
|
if err != test.encErr {
|
||||||
t.Fatalf("unable to encrypt blob: %v", err)
|
t.Fatalf("unable to encrypt blob: %v", err)
|
||||||
} else if test.encErr != nil {
|
} else if test.encErr != nil {
|
||||||
@ -213,15 +215,48 @@ func testBlobJusticeKitEncryptDecrypt(t *testing.T, test descriptorTest) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type remoteWitnessTest struct {
|
||||||
|
name string
|
||||||
|
blobType blob.Type
|
||||||
|
expWitnessScript func(pk *btcec.PublicKey) []byte
|
||||||
|
}
|
||||||
|
|
||||||
// TestJusticeKitRemoteWitnessConstruction tests that a JusticeKit returns the
|
// TestJusticeKitRemoteWitnessConstruction tests that a JusticeKit returns the
|
||||||
// proper to-remote witnes script and to-remote witness stack. This should be
|
// proper to-remote witnes script and to-remote witness stack. This should be
|
||||||
// equivalent to p2wkh spend.
|
// equivalent to p2wkh spend.
|
||||||
func TestJusticeKitRemoteWitnessConstruction(t *testing.T) {
|
func TestJusticeKitRemoteWitnessConstruction(t *testing.T) {
|
||||||
|
tests := []remoteWitnessTest{
|
||||||
|
{
|
||||||
|
name: "legacy commitment",
|
||||||
|
blobType: blob.Type(blob.FlagCommitOutputs),
|
||||||
|
expWitnessScript: func(pk *btcec.PublicKey) []byte {
|
||||||
|
return pk.SerializeCompressed()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "anchor commitment",
|
||||||
|
blobType: blob.Type(blob.FlagCommitOutputs |
|
||||||
|
blob.FlagAnchorChannel),
|
||||||
|
expWitnessScript: func(pk *btcec.PublicKey) []byte {
|
||||||
|
script, _ := input.CommitScriptToRemoteConfirmed(pk)
|
||||||
|
return script
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, test := range tests {
|
||||||
|
test := test
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
testJusticeKitRemoteWitnessConstruction(t, test)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testJusticeKitRemoteWitnessConstruction(
|
||||||
|
t *testing.T, test remoteWitnessTest) {
|
||||||
|
|
||||||
// Generate the to-remote pubkey.
|
// Generate the to-remote pubkey.
|
||||||
toRemotePrivKey, err := btcec.NewPrivateKey(btcec.S256())
|
toRemotePrivKey, err := btcec.NewPrivateKey(btcec.S256())
|
||||||
if err != nil {
|
require.Nil(t, err)
|
||||||
t.Fatalf("unable to generate to-remote priv key: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy the to-remote pubkey into the format expected by our justice
|
// Copy the to-remote pubkey into the format expected by our justice
|
||||||
// kit.
|
// kit.
|
||||||
@ -232,19 +267,15 @@ func TestJusticeKitRemoteWitnessConstruction(t *testing.T) {
|
|||||||
// doesn't matter as we won't be validating the signature's validity.
|
// doesn't matter as we won't be validating the signature's validity.
|
||||||
digest := bytes.Repeat([]byte("a"), 32)
|
digest := bytes.Repeat([]byte("a"), 32)
|
||||||
rawToRemoteSig, err := toRemotePrivKey.Sign(digest)
|
rawToRemoteSig, err := toRemotePrivKey.Sign(digest)
|
||||||
if err != nil {
|
require.Nil(t, err)
|
||||||
t.Fatalf("unable to generate to-remote signature: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert the DER-encoded signature into a fixed-size sig.
|
// Convert the DER-encoded signature into a fixed-size sig.
|
||||||
commitToRemoteSig, err := lnwire.NewSigFromSignature(rawToRemoteSig)
|
commitToRemoteSig, err := lnwire.NewSigFromSignature(rawToRemoteSig)
|
||||||
if err != nil {
|
require.Nil(t, err)
|
||||||
t.Fatalf("unable to convert raw to-remote signature to "+
|
|
||||||
"Sig: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Populate the justice kit fields relevant to the to-remote output.
|
// Populate the justice kit fields relevant to the to-remote output.
|
||||||
justiceKit := &blob.JusticeKit{
|
justiceKit := &blob.JusticeKit{
|
||||||
|
BlobType: test.blobType,
|
||||||
CommitToRemotePubKey: toRemotePubKey,
|
CommitToRemotePubKey: toRemotePubKey,
|
||||||
CommitToRemoteSig: commitToRemoteSig,
|
CommitToRemoteSig: commitToRemoteSig,
|
||||||
}
|
}
|
||||||
@ -252,29 +283,16 @@ func TestJusticeKitRemoteWitnessConstruction(t *testing.T) {
|
|||||||
// Now, compute the to-remote witness script returned by the justice
|
// Now, compute the to-remote witness script returned by the justice
|
||||||
// kit.
|
// kit.
|
||||||
toRemoteScript, err := justiceKit.CommitToRemoteWitnessScript()
|
toRemoteScript, err := justiceKit.CommitToRemoteWitnessScript()
|
||||||
if err != nil {
|
require.Nil(t, err)
|
||||||
t.Fatalf("unable to compute to-remote witness script: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assert this is exactly the to-remote, compressed pubkey.
|
// Assert this is exactly the to-remote, compressed pubkey.
|
||||||
if !bytes.Equal(toRemoteScript, toRemotePubKey[:]) {
|
expToRemoteScript := test.expWitnessScript(toRemotePrivKey.PubKey())
|
||||||
t.Fatalf("to-remote witness script should be equal to "+
|
require.Equal(t, expToRemoteScript, toRemoteScript)
|
||||||
"to-remote pubkey, want: %x, got %x",
|
|
||||||
toRemotePubKey[:], toRemoteScript)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Next, compute the to-remote witness stack, which should be a p2wkh
|
// Next, compute the to-remote witness stack, which should be a p2wkh
|
||||||
// witness stack consisting solely of a signature.
|
// witness stack consisting solely of a signature.
|
||||||
toRemoteWitnessStack, err := justiceKit.CommitToRemoteWitnessStack()
|
toRemoteWitnessStack, err := justiceKit.CommitToRemoteWitnessStack()
|
||||||
if err != nil {
|
require.Nil(t, err)
|
||||||
t.Fatalf("unable to compute to-remote witness stack: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assert that the witness stack only has one element.
|
|
||||||
if len(toRemoteWitnessStack) != 1 {
|
|
||||||
t.Fatalf("to-remote witness stack should be of length 1, is %d",
|
|
||||||
len(toRemoteWitnessStack))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compute the expected first element, by appending a sighash all byte
|
// Compute the expected first element, by appending a sighash all byte
|
||||||
// to our raw DER-encoded signature.
|
// to our raw DER-encoded signature.
|
||||||
@ -282,13 +300,11 @@ func TestJusticeKitRemoteWitnessConstruction(t *testing.T) {
|
|||||||
rawToRemoteSig.Serialize(), byte(txscript.SigHashAll),
|
rawToRemoteSig.Serialize(), byte(txscript.SigHashAll),
|
||||||
)
|
)
|
||||||
|
|
||||||
// Assert that the expected signature matches the first element in the
|
// Assert that the expected witness stack is returned.
|
||||||
// witness stack.
|
expWitnessStack := [][]byte{
|
||||||
if !bytes.Equal(rawToRemoteSigWithSigHash, toRemoteWitnessStack[0]) {
|
rawToRemoteSigWithSigHash,
|
||||||
t.Fatalf("mismatched sig in to-remote witness stack, want: %v, "+
|
|
||||||
"got: %v", rawToRemoteSigWithSigHash,
|
|
||||||
toRemoteWitnessStack[0])
|
|
||||||
}
|
}
|
||||||
|
require.Equal(t, expWitnessStack, toRemoteWitnessStack)
|
||||||
|
|
||||||
// Finally, set the CommitToRemotePubKey to be a blank value.
|
// Finally, set the CommitToRemotePubKey to be a blank value.
|
||||||
justiceKit.CommitToRemotePubKey = blob.PubKey{}
|
justiceKit.CommitToRemotePubKey = blob.PubKey{}
|
||||||
@ -297,9 +313,7 @@ func TestJusticeKitRemoteWitnessConstruction(t *testing.T) {
|
|||||||
// ErrNoCommitToRemoteOutput since a valid pubkey could not be parsed
|
// ErrNoCommitToRemoteOutput since a valid pubkey could not be parsed
|
||||||
// from CommitToRemotePubKey.
|
// from CommitToRemotePubKey.
|
||||||
_, err = justiceKit.CommitToRemoteWitnessScript()
|
_, err = justiceKit.CommitToRemoteWitnessScript()
|
||||||
if err != blob.ErrNoCommitToRemoteOutput {
|
require.Error(t, blob.ErrNoCommitToRemoteOutput, err)
|
||||||
t.Fatalf("expected ErrNoCommitToRemoteOutput, got: %v", err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestJusticeKitToLocalWitnessConstruction tests that a JusticeKit returns the
|
// TestJusticeKitToLocalWitnessConstruction tests that a JusticeKit returns the
|
||||||
@ -310,14 +324,10 @@ func TestJusticeKitToLocalWitnessConstruction(t *testing.T) {
|
|||||||
|
|
||||||
// Generate the revocation and delay private keys.
|
// Generate the revocation and delay private keys.
|
||||||
revPrivKey, err := btcec.NewPrivateKey(btcec.S256())
|
revPrivKey, err := btcec.NewPrivateKey(btcec.S256())
|
||||||
if err != nil {
|
require.Nil(t, err)
|
||||||
t.Fatalf("unable to generate revocation priv key: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
delayPrivKey, err := btcec.NewPrivateKey(btcec.S256())
|
delayPrivKey, err := btcec.NewPrivateKey(btcec.S256())
|
||||||
if err != nil {
|
require.Nil(t, err)
|
||||||
t.Fatalf("unable to generate delay priv key: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy the revocation and delay pubkeys into the format expected by our
|
// Copy the revocation and delay pubkeys into the format expected by our
|
||||||
// justice kit.
|
// justice kit.
|
||||||
@ -331,16 +341,11 @@ func TestJusticeKitToLocalWitnessConstruction(t *testing.T) {
|
|||||||
// doesn't matter as we won't be validating the signature's validity.
|
// doesn't matter as we won't be validating the signature's validity.
|
||||||
digest := bytes.Repeat([]byte("a"), 32)
|
digest := bytes.Repeat([]byte("a"), 32)
|
||||||
rawRevSig, err := revPrivKey.Sign(digest)
|
rawRevSig, err := revPrivKey.Sign(digest)
|
||||||
if err != nil {
|
require.Nil(t, err)
|
||||||
t.Fatalf("unable to generate revocation signature: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert the DER-encoded signature into a fixed-size sig.
|
// Convert the DER-encoded signature into a fixed-size sig.
|
||||||
commitToLocalSig, err := lnwire.NewSigFromSignature(rawRevSig)
|
commitToLocalSig, err := lnwire.NewSigFromSignature(rawRevSig)
|
||||||
if err != nil {
|
require.Nil(t, err)
|
||||||
t.Fatalf("unable to convert raw revocation signature to "+
|
|
||||||
"Sig: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Populate the justice kit with fields relevant to the to-local output.
|
// Populate the justice kit with fields relevant to the to-local output.
|
||||||
justiceKit := &blob.JusticeKit{
|
justiceKit := &blob.JusticeKit{
|
||||||
@ -355,52 +360,29 @@ func TestJusticeKitToLocalWitnessConstruction(t *testing.T) {
|
|||||||
expToLocalScript, err := input.CommitScriptToSelf(
|
expToLocalScript, err := input.CommitScriptToSelf(
|
||||||
csvDelay, delayPrivKey.PubKey(), revPrivKey.PubKey(),
|
csvDelay, delayPrivKey.PubKey(), revPrivKey.PubKey(),
|
||||||
)
|
)
|
||||||
if err != nil {
|
require.Nil(t, err)
|
||||||
t.Fatalf("unable to generate expected to-local script: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compute the to-local script that is returned by the justice kit.
|
// Compute the to-local script that is returned by the justice kit.
|
||||||
toLocalScript, err := justiceKit.CommitToLocalWitnessScript()
|
toLocalScript, err := justiceKit.CommitToLocalWitnessScript()
|
||||||
if err != nil {
|
require.Nil(t, err)
|
||||||
t.Fatalf("unable to compute to-local witness script: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assert that the expected to-local script matches the actual script.
|
// Assert that the expected to-local script matches the actual script.
|
||||||
if !bytes.Equal(expToLocalScript, toLocalScript) {
|
require.Equal(t, expToLocalScript, toLocalScript)
|
||||||
t.Fatalf("mismatched to-local witness script, want: %v, got %v",
|
|
||||||
expToLocalScript, toLocalScript)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Next, compute the to-local witness stack returned by the justice kit.
|
// Next, compute the to-local witness stack returned by the justice kit.
|
||||||
toLocalWitnessStack, err := justiceKit.CommitToLocalRevokeWitnessStack()
|
toLocalWitnessStack, err := justiceKit.CommitToLocalRevokeWitnessStack()
|
||||||
if err != nil {
|
require.Nil(t, err)
|
||||||
t.Fatalf("unable to compute to-local witness stack: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// A valid witness that spends the revocation path should have exactly
|
// Compute the expected signature in the bottom element of the stack, by
|
||||||
// two elements on the stack.
|
// appending a sighash all flag to the raw DER signature.
|
||||||
if len(toLocalWitnessStack) != 2 {
|
|
||||||
t.Fatalf("to-local witness stack should be of length 2, is %d",
|
|
||||||
len(toLocalWitnessStack))
|
|
||||||
}
|
|
||||||
|
|
||||||
// First, we'll verify that the top element is 0x01, which triggers the
|
|
||||||
// revocation path within the to-local witness script.
|
|
||||||
if !bytes.Equal(toLocalWitnessStack[1], []byte{0x01}) {
|
|
||||||
t.Fatalf("top item on witness stack should be 0x01, found: %v",
|
|
||||||
toLocalWitnessStack[1])
|
|
||||||
}
|
|
||||||
|
|
||||||
// Next, compute the expected signature in the bottom element of the
|
|
||||||
// stack, by appending a sighash all flag to the raw DER signature.
|
|
||||||
rawRevSigWithSigHash := append(
|
rawRevSigWithSigHash := append(
|
||||||
rawRevSig.Serialize(), byte(txscript.SigHashAll),
|
rawRevSig.Serialize(), byte(txscript.SigHashAll),
|
||||||
)
|
)
|
||||||
|
|
||||||
// Assert that the second element on the stack matches our expected
|
// Finally, validate against our expected witness stack.
|
||||||
// signature under the revocation pubkey.
|
expWitnessStack := [][]byte{
|
||||||
if !bytes.Equal(rawRevSigWithSigHash, toLocalWitnessStack[0]) {
|
rawRevSigWithSigHash,
|
||||||
t.Fatalf("mismatched sig in to-local witness stack, want: %v, "+
|
{1},
|
||||||
"got: %v", rawRevSigWithSigHash, toLocalWitnessStack[0])
|
|
||||||
}
|
}
|
||||||
|
require.Equal(t, expWitnessStack, toLocalWitnessStack)
|
||||||
}
|
}
|
||||||
|
@ -14,11 +14,16 @@ const (
|
|||||||
// include the reward script negotiated during session creation. Without
|
// include the reward script negotiated during session creation. Without
|
||||||
// the flag, there is only one output sweeping clients funds back to
|
// the flag, there is only one output sweeping clients funds back to
|
||||||
// them solely.
|
// them solely.
|
||||||
FlagReward Flag = 1 << iota
|
FlagReward Flag = 1
|
||||||
|
|
||||||
// FlagCommitOutputs signals that the blob contains the information
|
// FlagCommitOutputs signals that the blob contains the information
|
||||||
// required to sweep commitment outputs.
|
// required to sweep commitment outputs.
|
||||||
FlagCommitOutputs
|
FlagCommitOutputs Flag = 1 << 1
|
||||||
|
|
||||||
|
// FlagAnchorChannel signals that this blob is meant to spend an anchor
|
||||||
|
// channel, and therefore must expect a P2WSH-style to-remote output if
|
||||||
|
// one exists.
|
||||||
|
FlagAnchorChannel Flag = 1 << 2
|
||||||
)
|
)
|
||||||
|
|
||||||
// Type returns a Type consisting solely of this flag enabled.
|
// Type returns a Type consisting solely of this flag enabled.
|
||||||
@ -33,6 +38,8 @@ func (f Flag) String() string {
|
|||||||
return "FlagReward"
|
return "FlagReward"
|
||||||
case FlagCommitOutputs:
|
case FlagCommitOutputs:
|
||||||
return "FlagCommitOutputs"
|
return "FlagCommitOutputs"
|
||||||
|
case FlagAnchorChannel:
|
||||||
|
return "FlagAnchorChannel"
|
||||||
default:
|
default:
|
||||||
return "FlagUnknown"
|
return "FlagUnknown"
|
||||||
}
|
}
|
||||||
@ -50,6 +57,11 @@ const (
|
|||||||
// controlled by the user, and does not give the tower a reward.
|
// controlled by the user, and does not give the tower a reward.
|
||||||
TypeAltruistCommit = Type(FlagCommitOutputs)
|
TypeAltruistCommit = Type(FlagCommitOutputs)
|
||||||
|
|
||||||
|
// TypeAltruistAnchorCommit sweeps only commitment outputs from an
|
||||||
|
// anchor commitment to a sweep address controlled by the user, and does
|
||||||
|
// not give the tower a reward.
|
||||||
|
TypeAltruistAnchorCommit = Type(FlagCommitOutputs | FlagAnchorChannel)
|
||||||
|
|
||||||
// TypeRewardCommit sweeps only commitment outputs to a sweep address
|
// TypeRewardCommit sweeps only commitment outputs to a sweep address
|
||||||
// controlled by the user, and pays a negotiated reward to the tower.
|
// controlled by the user, and pays a negotiated reward to the tower.
|
||||||
TypeRewardCommit = Type(FlagCommitOutputs | FlagReward)
|
TypeRewardCommit = Type(FlagCommitOutputs | FlagReward)
|
||||||
@ -70,10 +82,16 @@ func TypeFromFlags(flags ...Flag) Type {
|
|||||||
return typ
|
return typ
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsAnchorChannel returns true if the blob type is for an anchor channel.
|
||||||
|
func (t Type) IsAnchorChannel() bool {
|
||||||
|
return t.Has(FlagAnchorChannel)
|
||||||
|
}
|
||||||
|
|
||||||
// knownFlags maps the supported flags to their name.
|
// knownFlags maps the supported flags to their name.
|
||||||
var knownFlags = map[Flag]struct{}{
|
var knownFlags = map[Flag]struct{}{
|
||||||
FlagReward: {},
|
FlagReward: {},
|
||||||
FlagCommitOutputs: {},
|
FlagCommitOutputs: {},
|
||||||
|
FlagAnchorChannel: {},
|
||||||
}
|
}
|
||||||
|
|
||||||
// String returns a human readable description of a Type.
|
// String returns a human readable description of a Type.
|
||||||
|
@ -18,17 +18,17 @@ var typeStringTests = []typeStringTest{
|
|||||||
{
|
{
|
||||||
name: "commit no-reward",
|
name: "commit no-reward",
|
||||||
typ: blob.TypeAltruistCommit,
|
typ: blob.TypeAltruistCommit,
|
||||||
expStr: "[FlagCommitOutputs|No-FlagReward]",
|
expStr: "[No-FlagAnchorChannel|FlagCommitOutputs|No-FlagReward]",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "commit reward",
|
name: "commit reward",
|
||||||
typ: blob.TypeRewardCommit,
|
typ: blob.TypeRewardCommit,
|
||||||
expStr: "[FlagCommitOutputs|FlagReward]",
|
expStr: "[No-FlagAnchorChannel|FlagCommitOutputs|FlagReward]",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "unknown flag",
|
name: "unknown flag",
|
||||||
typ: unknownFlag.Type(),
|
typ: unknownFlag.Type(),
|
||||||
expStr: "0000000000010000[No-FlagCommitOutputs|No-FlagReward]",
|
expStr: "0000000000010000[No-FlagAnchorChannel|No-FlagCommitOutputs|No-FlagReward]",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,6 +48,7 @@ type breachedInput struct {
|
|||||||
txOut *wire.TxOut
|
txOut *wire.TxOut
|
||||||
outPoint wire.OutPoint
|
outPoint wire.OutPoint
|
||||||
witness [][]byte
|
witness [][]byte
|
||||||
|
sequence uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
// commitToLocalInput extracts the information required to spend the commit
|
// commitToLocalInput extracts the information required to spend the commit
|
||||||
@ -104,20 +105,35 @@ func (p *JusticeDescriptor) commitToRemoteInput() (*breachedInput, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Since the to-remote witness script should just be a regular p2wkh
|
var (
|
||||||
// output, we'll parse it to retrieve the public key.
|
toRemoteScriptHash []byte
|
||||||
toRemotePubKey, err := btcec.ParsePubKey(toRemoteScript, btcec.S256())
|
toRemoteSequence uint32
|
||||||
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 {
|
if p.JusticeKit.BlobType.IsAnchorChannel() {
|
||||||
return nil, err
|
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, btcec.S256())
|
||||||
|
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.
|
// Locate the to-remote output on the breaching commitment transaction.
|
||||||
@ -146,6 +162,7 @@ func (p *JusticeDescriptor) commitToRemoteInput() (*breachedInput, error) {
|
|||||||
txOut: toRemoteTxOut,
|
txOut: toRemoteTxOut,
|
||||||
outPoint: toRemoteOutPoint,
|
outPoint: toRemoteOutPoint,
|
||||||
witness: buildWitness(witnessStack, toRemoteScript),
|
witness: buildWitness(witnessStack, toRemoteScript),
|
||||||
|
sequence: toRemoteSequence,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -164,6 +181,7 @@ func (p *JusticeDescriptor) assembleJusticeTxn(txWeight int64,
|
|||||||
totalAmt += btcutil.Amount(input.txOut.Value)
|
totalAmt += btcutil.Amount(input.txOut.Value)
|
||||||
justiceTxn.AddTxIn(&wire.TxIn{
|
justiceTxn.AddTxIn(&wire.TxIn{
|
||||||
PreviousOutPoint: input.outPoint,
|
PreviousOutPoint: input.outPoint,
|
||||||
|
Sequence: input.sequence,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -266,8 +284,13 @@ func (p *JusticeDescriptor) CreateJusticeTxn() (*wire.MsgTx, error) {
|
|||||||
// An older ToLocalPenaltyWitnessSize constant used to underestimate the
|
// An older ToLocalPenaltyWitnessSize constant used to underestimate the
|
||||||
// size by one byte. The diferrence in weight can cause different output
|
// size by one byte. The diferrence in weight can cause different output
|
||||||
// values on the sweep transaction, so we mimic the original bug to
|
// values on the sweep transaction, so we mimic the original bug to
|
||||||
// avoid invalidating signatures by older clients.
|
// avoid invalidating signatures by older clients. For anchor channels
|
||||||
weightEstimate.AddWitnessInput(input.ToLocalPenaltyWitnessSize - 1)
|
// we correct this and use the correct witness size.
|
||||||
|
if p.JusticeKit.BlobType.IsAnchorChannel() {
|
||||||
|
weightEstimate.AddWitnessInput(input.ToLocalPenaltyWitnessSize)
|
||||||
|
} else {
|
||||||
|
weightEstimate.AddWitnessInput(input.ToLocalPenaltyWitnessSize - 1)
|
||||||
|
}
|
||||||
|
|
||||||
sweepInputs = append(sweepInputs, toLocalInput)
|
sweepInputs = append(sweepInputs, toLocalInput)
|
||||||
|
|
||||||
@ -279,8 +302,13 @@ func (p *JusticeDescriptor) CreateJusticeTxn() (*wire.MsgTx, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
weightEstimate.AddWitnessInput(input.P2WKHWitnessSize)
|
|
||||||
sweepInputs = append(sweepInputs, toRemoteInput)
|
sweepInputs = append(sweepInputs, toRemoteInput)
|
||||||
|
|
||||||
|
if p.JusticeKit.BlobType.IsAnchorChannel() {
|
||||||
|
weightEstimate.AddWitnessInput(input.ToRemoteConfirmedWitnessSize)
|
||||||
|
} else {
|
||||||
|
weightEstimate.AddWitnessInput(input.P2WKHWitnessSize)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(conner): sweep htlc outputs
|
// TODO(conner): sweep htlc outputs
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package lookout_test
|
package lookout_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"reflect"
|
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -11,7 +10,6 @@ import (
|
|||||||
"github.com/btcsuite/btcd/wire"
|
"github.com/btcsuite/btcd/wire"
|
||||||
"github.com/btcsuite/btcutil"
|
"github.com/btcsuite/btcutil"
|
||||||
"github.com/btcsuite/btcutil/txsort"
|
"github.com/btcsuite/btcutil/txsort"
|
||||||
"github.com/davecgh/go-spew/spew"
|
|
||||||
"github.com/lightningnetwork/lnd/input"
|
"github.com/lightningnetwork/lnd/input"
|
||||||
"github.com/lightningnetwork/lnd/keychain"
|
"github.com/lightningnetwork/lnd/keychain"
|
||||||
"github.com/lightningnetwork/lnd/lnwire"
|
"github.com/lightningnetwork/lnd/lnwire"
|
||||||
@ -20,6 +18,7 @@ import (
|
|||||||
"github.com/lightningnetwork/lnd/watchtower/wtdb"
|
"github.com/lightningnetwork/lnd/watchtower/wtdb"
|
||||||
"github.com/lightningnetwork/lnd/watchtower/wtmock"
|
"github.com/lightningnetwork/lnd/watchtower/wtmock"
|
||||||
"github.com/lightningnetwork/lnd/watchtower/wtpolicy"
|
"github.com/lightningnetwork/lnd/watchtower/wtpolicy"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
const csvDelay uint32 = 144
|
const csvDelay uint32 = 144
|
||||||
@ -51,6 +50,8 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
altruistCommitType = blob.FlagCommitOutputs.Type()
|
altruistCommitType = blob.FlagCommitOutputs.Type()
|
||||||
|
|
||||||
|
altruistAnchorCommitType = blob.TypeAltruistAnchorCommit
|
||||||
)
|
)
|
||||||
|
|
||||||
// TestJusticeDescriptor asserts that a JusticeDescriptor is able to produce the
|
// TestJusticeDescriptor asserts that a JusticeDescriptor is able to produce the
|
||||||
@ -68,6 +69,10 @@ func TestJusticeDescriptor(t *testing.T) {
|
|||||||
name: "altruist and commit type",
|
name: "altruist and commit type",
|
||||||
blobType: altruistCommitType,
|
blobType: altruistCommitType,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "altruist anchor commit type",
|
||||||
|
blobType: altruistAnchorCommitType,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
@ -78,6 +83,8 @@ func TestJusticeDescriptor(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func testJusticeDescriptor(t *testing.T, blobType blob.Type) {
|
func testJusticeDescriptor(t *testing.T, blobType blob.Type) {
|
||||||
|
isAnchorChannel := blobType.IsAnchorChannel()
|
||||||
|
|
||||||
const (
|
const (
|
||||||
localAmount = btcutil.Amount(100000)
|
localAmount = btcutil.Amount(100000)
|
||||||
remoteAmount = btcutil.Amount(200000)
|
remoteAmount = btcutil.Amount(200000)
|
||||||
@ -106,20 +113,59 @@ func testJusticeDescriptor(t *testing.T, blobType blob.Type) {
|
|||||||
toLocalScript, err := input.CommitScriptToSelf(
|
toLocalScript, err := input.CommitScriptToSelf(
|
||||||
csvDelay, toLocalPK, revPK,
|
csvDelay, toLocalPK, revPK,
|
||||||
)
|
)
|
||||||
if err != nil {
|
require.Nil(t, err)
|
||||||
t.Fatalf("unable to create to-local script: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compute the to-local witness script hash.
|
// Compute the to-local witness script hash.
|
||||||
toLocalScriptHash, err := input.WitnessScriptHash(toLocalScript)
|
toLocalScriptHash, err := input.WitnessScriptHash(toLocalScript)
|
||||||
if err != nil {
|
require.Nil(t, err)
|
||||||
t.Fatalf("unable to create to-local witness script hash: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compute the to-remote witness script hash.
|
// Compute the to-remote redeem script, witness script hash, and
|
||||||
toRemoteScriptHash, err := input.CommitScriptUnencumbered(toRemotePK)
|
// sequence numbers.
|
||||||
if err != nil {
|
//
|
||||||
t.Fatalf("unable to create to-remote script: %v", err)
|
// NOTE: This is pretty subtle.
|
||||||
|
//
|
||||||
|
// The actual redeem script for a p2wkh output is just the pubkey, but
|
||||||
|
// the witness sighash calculation injects the classic p2kh script:
|
||||||
|
// OP_DUP OP_HASH160 <pubkey-hash160> OP_EQUALVERIFY OP_CHECKSIG. When
|
||||||
|
// signing for p2wkh we don't pass the raw pubkey as the witness script
|
||||||
|
// to the sign descriptor (since that's also not a valid script).
|
||||||
|
// Instead we give it the _pkscript_ of the form OP_0 <pubkey-hash160>
|
||||||
|
// from which pubkey-hash160 is extracted during sighash calculation.
|
||||||
|
//
|
||||||
|
// On the other hand, signing for the anchor p2wsh to-remote outputs
|
||||||
|
// requires the sign descriptor to contain the redeem script ver batim.
|
||||||
|
// This difference in behavior forces us to use a distinct
|
||||||
|
// toRemoteSigningScript to handle both cases.
|
||||||
|
var (
|
||||||
|
toRemoteSequence uint32
|
||||||
|
toRemoteRedeemScript []byte
|
||||||
|
toRemoteScriptHash []byte
|
||||||
|
toRemoteSigningScript []byte
|
||||||
|
)
|
||||||
|
if isAnchorChannel {
|
||||||
|
toRemoteSequence = 1
|
||||||
|
toRemoteRedeemScript, err = input.CommitScriptToRemoteConfirmed(
|
||||||
|
toRemotePK,
|
||||||
|
)
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
toRemoteScriptHash, err = input.WitnessScriptHash(
|
||||||
|
toRemoteRedeemScript,
|
||||||
|
)
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
// As it should be.
|
||||||
|
toRemoteSigningScript = toRemoteRedeemScript
|
||||||
|
|
||||||
|
} else {
|
||||||
|
toRemoteRedeemScript = toRemotePK.SerializeCompressed()
|
||||||
|
toRemoteScriptHash, err = input.CommitScriptUnencumbered(
|
||||||
|
toRemotePK,
|
||||||
|
)
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
// NOTE: This is the _pkscript_.
|
||||||
|
toRemoteSigningScript = toRemoteScriptHash
|
||||||
}
|
}
|
||||||
|
|
||||||
// Construct the breaching commitment txn, containing the to-local and
|
// Construct the breaching commitment txn, containing the to-local and
|
||||||
@ -146,10 +192,19 @@ func testJusticeDescriptor(t *testing.T, blobType blob.Type) {
|
|||||||
// An older ToLocalPenaltyWitnessSize constant used to underestimate the
|
// An older ToLocalPenaltyWitnessSize constant used to underestimate the
|
||||||
// size by one byte. The diferrence in weight can cause different output
|
// size by one byte. The diferrence in weight can cause different output
|
||||||
// values on the sweep transaction, so we mimic the original bug and
|
// values on the sweep transaction, so we mimic the original bug and
|
||||||
// create signatures using the original weight estimate.
|
// create signatures using the original weight estimate. For anchor
|
||||||
weightEstimate.AddWitnessInput(input.ToLocalPenaltyWitnessSize - 1)
|
// channels we fix this and use the correct witness size.
|
||||||
|
if isAnchorChannel {
|
||||||
|
weightEstimate.AddWitnessInput(input.ToLocalPenaltyWitnessSize)
|
||||||
|
} else {
|
||||||
|
weightEstimate.AddWitnessInput(input.ToLocalPenaltyWitnessSize - 1)
|
||||||
|
}
|
||||||
|
|
||||||
weightEstimate.AddWitnessInput(input.P2WKHWitnessSize)
|
if isAnchorChannel {
|
||||||
|
weightEstimate.AddWitnessInput(input.ToRemoteConfirmedWitnessSize)
|
||||||
|
} else {
|
||||||
|
weightEstimate.AddWitnessInput(input.P2WKHWitnessSize)
|
||||||
|
}
|
||||||
weightEstimate.AddP2WKHOutput()
|
weightEstimate.AddP2WKHOutput()
|
||||||
if blobType.Has(blob.FlagReward) {
|
if blobType.Has(blob.FlagReward) {
|
||||||
weightEstimate.AddP2WKHOutput()
|
weightEstimate.AddP2WKHOutput()
|
||||||
@ -174,6 +229,7 @@ func testJusticeDescriptor(t *testing.T, blobType blob.Type) {
|
|||||||
// Begin to assemble the justice kit, starting with the sweep address,
|
// Begin to assemble the justice kit, starting with the sweep address,
|
||||||
// pubkeys, and csv delay.
|
// pubkeys, and csv delay.
|
||||||
justiceKit := &blob.JusticeKit{
|
justiceKit := &blob.JusticeKit{
|
||||||
|
BlobType: blobType,
|
||||||
SweepAddress: makeAddrSlice(22),
|
SweepAddress: makeAddrSlice(22),
|
||||||
CSVDelay: csvDelay,
|
CSVDelay: csvDelay,
|
||||||
}
|
}
|
||||||
@ -199,6 +255,7 @@ func testJusticeDescriptor(t *testing.T, blobType blob.Type) {
|
|||||||
Hash: breachTxID,
|
Hash: breachTxID,
|
||||||
Index: 1,
|
Index: 1,
|
||||||
},
|
},
|
||||||
|
Sequence: toRemoteSequence,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -207,9 +264,7 @@ func testJusticeDescriptor(t *testing.T, blobType blob.Type) {
|
|||||||
totalAmount, int64(txWeight), justiceKit.SweepAddress,
|
totalAmount, int64(txWeight), justiceKit.SweepAddress,
|
||||||
sessionInfo.RewardAddress,
|
sessionInfo.RewardAddress,
|
||||||
)
|
)
|
||||||
if err != nil {
|
require.Nil(t, err)
|
||||||
t.Fatalf("unable to compute justice txouts: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Attach the txouts and BIP69 sort the resulting transaction.
|
// Attach the txouts and BIP69 sort the resulting transaction.
|
||||||
justiceTxn.TxOut = outputs
|
justiceTxn.TxOut = outputs
|
||||||
@ -235,7 +290,7 @@ func testJusticeDescriptor(t *testing.T, blobType blob.Type) {
|
|||||||
KeyLocator: toRemoteKeyLoc,
|
KeyLocator: toRemoteKeyLoc,
|
||||||
PubKey: toRemotePK,
|
PubKey: toRemotePK,
|
||||||
},
|
},
|
||||||
WitnessScript: toRemoteScriptHash,
|
WitnessScript: toRemoteSigningScript,
|
||||||
Output: breachTxn.TxOut[1],
|
Output: breachTxn.TxOut[1],
|
||||||
SigHashes: hashCache,
|
SigHashes: hashCache,
|
||||||
InputIndex: 1,
|
InputIndex: 1,
|
||||||
@ -244,38 +299,26 @@ func testJusticeDescriptor(t *testing.T, blobType blob.Type) {
|
|||||||
|
|
||||||
// Verify that our test justice transaction is sane.
|
// Verify that our test justice transaction is sane.
|
||||||
btx := btcutil.NewTx(justiceTxn)
|
btx := btcutil.NewTx(justiceTxn)
|
||||||
if err := blockchain.CheckTransactionSanity(btx); err != nil {
|
err = blockchain.CheckTransactionSanity(btx)
|
||||||
t.Fatalf("justice txn is not sane: %v", err)
|
require.Nil(t, err)
|
||||||
}
|
|
||||||
|
|
||||||
// Compute a DER-encoded signature for the to-local input.
|
// Compute a DER-encoded signature for the to-local input.
|
||||||
toLocalSigRaw, err := signer.SignOutputRaw(justiceTxn, toLocalSignDesc)
|
toLocalSigRaw, err := signer.SignOutputRaw(justiceTxn, toLocalSignDesc)
|
||||||
if err != nil {
|
require.Nil(t, err)
|
||||||
t.Fatalf("unable to sign to-local input: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compute the witness for the to-remote input. The first element is a
|
// 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
|
// DER-encoded signature under the to-remote pubkey. The sighash flag is
|
||||||
// also present, so we trim it.
|
// also present, so we trim it.
|
||||||
toRemoteWitness, err := input.CommitSpendNoDelay(
|
toRemoteSigRaw, err := signer.SignOutputRaw(justiceTxn, toRemoteSignDesc)
|
||||||
signer, toRemoteSignDesc, justiceTxn, false,
|
require.Nil(t, err)
|
||||||
)
|
|
||||||
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.
|
// Convert the DER to-local sig into a fixed-size signature.
|
||||||
toLocalSig, err := lnwire.NewSigFromSignature(toLocalSigRaw)
|
toLocalSig, err := lnwire.NewSigFromSignature(toLocalSigRaw)
|
||||||
if err != nil {
|
require.Nil(t, err)
|
||||||
t.Fatalf("unable to parse to-local signature: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert the DER to-remote sig into a fixed-size signature.
|
// Convert the DER to-remote sig into a fixed-size signature.
|
||||||
toRemoteSig, err := lnwire.NewSigFromRawSignature(toRemoteSigRaw)
|
toRemoteSig, err := lnwire.NewSigFromSignature(toRemoteSigRaw)
|
||||||
if err != nil {
|
require.Nil(t, err)
|
||||||
t.Fatalf("unable to parse to-remote signature: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Complete our justice kit by copying the signatures into the payload.
|
// Complete our justice kit by copying the signatures into the payload.
|
||||||
copy(justiceKit.CommitToLocalSig[:], toLocalSig[:])
|
copy(justiceKit.CommitToLocalSig[:], toLocalSig[:])
|
||||||
@ -300,9 +343,7 @@ func testJusticeDescriptor(t *testing.T, blobType blob.Type) {
|
|||||||
// Exact retribution on the offender. If no error is returned, we expect
|
// Exact retribution on the offender. If no error is returned, we expect
|
||||||
// the justice transaction to be published via the channel.
|
// the justice transaction to be published via the channel.
|
||||||
err = punisher.Punish(justiceDesc, nil)
|
err = punisher.Punish(justiceDesc, nil)
|
||||||
if err != nil {
|
require.Nil(t, err)
|
||||||
t.Fatalf("unable to punish breach: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Retrieve the published justice transaction.
|
// Retrieve the published justice transaction.
|
||||||
var wtJusticeTxn *wire.MsgTx
|
var wtJusticeTxn *wire.MsgTx
|
||||||
@ -321,14 +362,10 @@ func testJusticeDescriptor(t *testing.T, blobType blob.Type) {
|
|||||||
|
|
||||||
// Construct the test's to-remote witness.
|
// Construct the test's to-remote witness.
|
||||||
justiceTxn.TxIn[1].Witness = make([][]byte, 2)
|
justiceTxn.TxIn[1].Witness = make([][]byte, 2)
|
||||||
justiceTxn.TxIn[1].Witness[0] = append(toRemoteSigRaw,
|
justiceTxn.TxIn[1].Witness[0] = append(toRemoteSigRaw.Serialize(),
|
||||||
byte(txscript.SigHashAll))
|
byte(txscript.SigHashAll))
|
||||||
justiceTxn.TxIn[1].Witness[1] = toRemotePK.SerializeCompressed()
|
justiceTxn.TxIn[1].Witness[1] = toRemoteRedeemScript
|
||||||
|
|
||||||
// Assert that the watchtower derives the same justice txn.
|
// Assert that the watchtower derives the same justice txn.
|
||||||
if !reflect.DeepEqual(justiceTxn, wtJusticeTxn) {
|
require.Equal(t, justiceTxn, wtJusticeTxn)
|
||||||
t.Fatalf("expected justice txn: %v\ngot %v",
|
|
||||||
spew.Sdump(justiceTxn),
|
|
||||||
spew.Sdump(wtJusticeTxn))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -137,7 +137,9 @@ func TestLookoutBreachMatching(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Construct a justice kit for each possible breach transaction.
|
// Construct a justice kit for each possible breach transaction.
|
||||||
|
blobType := blob.FlagCommitOutputs.Type()
|
||||||
blob1 := &blob.JusticeKit{
|
blob1 := &blob.JusticeKit{
|
||||||
|
BlobType: blobType,
|
||||||
SweepAddress: makeAddrSlice(22),
|
SweepAddress: makeAddrSlice(22),
|
||||||
RevocationPubKey: makePubKey(1),
|
RevocationPubKey: makePubKey(1),
|
||||||
LocalDelayPubKey: makePubKey(1),
|
LocalDelayPubKey: makePubKey(1),
|
||||||
@ -145,6 +147,7 @@ func TestLookoutBreachMatching(t *testing.T) {
|
|||||||
CommitToLocalSig: makeArray64(1),
|
CommitToLocalSig: makeArray64(1),
|
||||||
}
|
}
|
||||||
blob2 := &blob.JusticeKit{
|
blob2 := &blob.JusticeKit{
|
||||||
|
BlobType: blobType,
|
||||||
SweepAddress: makeAddrSlice(22),
|
SweepAddress: makeAddrSlice(22),
|
||||||
RevocationPubKey: makePubKey(2),
|
RevocationPubKey: makePubKey(2),
|
||||||
LocalDelayPubKey: makePubKey(2),
|
LocalDelayPubKey: makePubKey(2),
|
||||||
@ -156,13 +159,13 @@ func TestLookoutBreachMatching(t *testing.T) {
|
|||||||
key2 := blob.NewBreachKeyFromHash(&hash2)
|
key2 := blob.NewBreachKeyFromHash(&hash2)
|
||||||
|
|
||||||
// Encrypt the first justice kit under breach key one.
|
// Encrypt the first justice kit under breach key one.
|
||||||
encBlob1, err := blob1.Encrypt(key1, blob.FlagCommitOutputs.Type())
|
encBlob1, err := blob1.Encrypt(key1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to encrypt sweep detail 1: %v", err)
|
t.Fatalf("unable to encrypt sweep detail 1: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Encrypt the second justice kit under breach key two.
|
// Encrypt the second justice kit under breach key two.
|
||||||
encBlob2, err := blob2.Encrypt(key2, blob.FlagCommitOutputs.Type())
|
encBlob2, err := blob2.Encrypt(key2)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to encrypt sweep detail 2: %v", err)
|
t.Fatalf("unable to encrypt sweep detail 2: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -194,6 +194,7 @@ func (t *backupTask) craftSessionPayload(
|
|||||||
// to-local script, and the remote CSV delay.
|
// to-local script, and the remote CSV delay.
|
||||||
keyRing := t.breachInfo.KeyRing
|
keyRing := t.breachInfo.KeyRing
|
||||||
justiceKit := &blob.JusticeKit{
|
justiceKit := &blob.JusticeKit{
|
||||||
|
BlobType: t.blobType,
|
||||||
SweepAddress: t.sweepPkScript,
|
SweepAddress: t.sweepPkScript,
|
||||||
RevocationPubKey: toBlobPubKey(keyRing.RevocationKey),
|
RevocationPubKey: toBlobPubKey(keyRing.RevocationKey),
|
||||||
LocalDelayPubKey: toBlobPubKey(keyRing.ToLocalKey),
|
LocalDelayPubKey: toBlobPubKey(keyRing.ToLocalKey),
|
||||||
@ -299,7 +300,7 @@ func (t *backupTask) craftSessionPayload(
|
|||||||
// Then, we'll encrypt the computed justice kit using the full breach
|
// Then, we'll encrypt the computed justice kit using the full breach
|
||||||
// transaction id, which will allow the tower to recover the contents
|
// transaction id, which will allow the tower to recover the contents
|
||||||
// after the transaction is seen in the chain or mempool.
|
// after the transaction is seen in the chain or mempool.
|
||||||
encBlob, err := justiceKit.Encrypt(key, t.blobType)
|
encBlob, err := justiceKit.Encrypt(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return hint, nil, err
|
return hint, nil, err
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user