mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-08-31 17:51:33 +02:00
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:
@@ -172,3 +172,42 @@ func (c CommitmentType) ParseRawSig(witness wire.TxWitness) (lnwire.Sig,
|
||||
c)
|
||||
}
|
||||
}
|
||||
|
||||
// NewJusticeKit can be used to construct a new JusticeKit depending on the
|
||||
// CommitmentType.
|
||||
func (c CommitmentType) NewJusticeKit(sweepScript []byte,
|
||||
breachInfo *lnwallet.BreachRetribution, withToRemote bool) (JusticeKit,
|
||||
error) {
|
||||
|
||||
switch c {
|
||||
case LegacyCommitment, LegacyTweaklessCommitment:
|
||||
return newLegacyJusticeKit(
|
||||
sweepScript, breachInfo, withToRemote,
|
||||
), nil
|
||||
|
||||
case AnchorCommitment:
|
||||
return newAnchorJusticeKit(
|
||||
sweepScript, breachInfo, withToRemote,
|
||||
), nil
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown commitment type: %v", c)
|
||||
}
|
||||
}
|
||||
|
||||
// EmptyJusticeKit returns the appropriate empty justice kit for the given
|
||||
// CommitmentType.
|
||||
func (c CommitmentType) EmptyJusticeKit() (JusticeKit, error) {
|
||||
switch c {
|
||||
case LegacyTweaklessCommitment, LegacyCommitment:
|
||||
return &legacyJusticeKit{}, nil
|
||||
|
||||
case AnchorCommitment:
|
||||
return &anchorJusticeKit{
|
||||
legacyJusticeKit: legacyJusticeKit{},
|
||||
}, nil
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown commitment type: %v", c)
|
||||
}
|
||||
}
|
||||
|
@@ -1,153 +1,288 @@
|
||||
package blob
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/lightningnetwork/lnd/input"
|
||||
"github.com/lightningnetwork/lnd/lnwallet"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
)
|
||||
|
||||
// JusticeKit is lé Blob of Justice. The JusticeKit contains information
|
||||
// required to construct a justice transaction, that sweeps a remote party's
|
||||
// revoked commitment transaction. It supports encryption and decryption using
|
||||
// chacha20poly1305, allowing the client to encrypt the contents of the blob,
|
||||
// and for a watchtower to later decrypt if action must be taken. The encoding
|
||||
// format is versioned to allow future extensions.
|
||||
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
|
||||
// JusticeKit is an interface that describes lé Blob of Justice. An
|
||||
// implementation of the JusticeKit contains information required to construct
|
||||
// a justice transaction, that sweeps a remote party's revoked commitment
|
||||
// transaction. It supports encryption and decryption using chacha20poly1305,
|
||||
// allowing the client to encrypt the contents of the blob, and for a
|
||||
// watchtower to later decrypt if action must be taken.
|
||||
type JusticeKit interface {
|
||||
// ToLocalOutputSpendInfo returns the info required to send the to-local
|
||||
// output. It returns the output pub key script and the witness required
|
||||
// to spend the output.
|
||||
ToLocalOutputSpendInfo() (*txscript.PkScript, wire.TxWitness, error)
|
||||
|
||||
// SweepAddress is the witness program of the output where the client's
|
||||
// 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
|
||||
// correlated across sessions and/or towers.
|
||||
//
|
||||
// NOTE: This is chosen to be the length of a maximally sized witness
|
||||
// program.
|
||||
SweepAddress []byte
|
||||
// ToRemoteOutputSpendInfo returns the info required to send the
|
||||
// to-remote output. It returns the output pub key script, the witness
|
||||
// required to spend the output and the sequence to apply.
|
||||
ToRemoteOutputSpendInfo() (*txscript.PkScript, wire.TxWitness, uint32,
|
||||
error)
|
||||
|
||||
// RevocationPubKey is the compressed pubkey that guards the revocation
|
||||
// clause of the remote party's to-local output.
|
||||
RevocationPubKey PubKey
|
||||
// HasCommitToRemoteOutput returns true if the kit does include the
|
||||
// information required to sweep the to-remote output.
|
||||
HasCommitToRemoteOutput() bool
|
||||
|
||||
// LocalDelayPubKey is the compressed pubkey in the to-local script of
|
||||
// the remote party, which guards the path where the remote party
|
||||
// claims their commitment output.
|
||||
LocalDelayPubKey PubKey
|
||||
// AddToLocalSig adds the to-local signature to the kit.
|
||||
AddToLocalSig(sig lnwire.Sig)
|
||||
|
||||
// CSVDelay is the relative timelock in the remote party's to-local
|
||||
// output, which the remote party must wait out before sweeping their
|
||||
// commitment output.
|
||||
CSVDelay uint32
|
||||
// AddToRemoteSig adds the to-remote signature to the kit.
|
||||
AddToRemoteSig(sig lnwire.Sig)
|
||||
|
||||
// CommitToLocalSig is a signature under RevocationPubKey using
|
||||
// SIGHASH_ALL.
|
||||
CommitToLocalSig lnwire.Sig
|
||||
// SweepAddress returns the sweep address to be used on the justice tx
|
||||
// output.
|
||||
SweepAddress() []byte
|
||||
|
||||
// CommitToRemotePubKey is the public key in the to-remote output of the
|
||||
// revoked commitment transaction.
|
||||
//
|
||||
// NOTE: This value is only used if it contains a valid compressed
|
||||
// public key.
|
||||
CommitToRemotePubKey PubKey
|
||||
// PlainTextSize is the size of the encoded-but-unencrypted blob in
|
||||
// bytes.
|
||||
PlainTextSize() int
|
||||
|
||||
// CommitToRemoteSig is a signature under CommitToRemotePubKey using
|
||||
// SIGHASH_ALL.
|
||||
//
|
||||
// NOTE: This value is only used if CommitToRemotePubKey contains a
|
||||
// valid compressed public key.
|
||||
CommitToRemoteSig lnwire.Sig
|
||||
encode(w io.Writer) error
|
||||
decode(r io.Reader) error
|
||||
}
|
||||
|
||||
// CommitToLocalWitnessScript returns the serialized witness script for the
|
||||
// commitment to-local output.
|
||||
func (b *JusticeKit) CommitToLocalWitnessScript() ([]byte, error) {
|
||||
revocationPubKey, err := btcec.ParsePubKey(
|
||||
b.RevocationPubKey[:],
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
localDelayedPubKey, err := btcec.ParsePubKey(
|
||||
b.LocalDelayPubKey[:],
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return input.CommitScriptToSelf(
|
||||
b.CSVDelay, localDelayedPubKey, revocationPubKey,
|
||||
)
|
||||
// legacyJusticeKit is an implementation of the JusticeKit interface which can
|
||||
// be used for backing up commitments of legacy (pre-anchor) channels.
|
||||
type legacyJusticeKit struct {
|
||||
justiceKitPacketV0
|
||||
}
|
||||
|
||||
// CommitToLocalRevokeWitnessStack constructs a witness stack spending the
|
||||
// revocation clause of the commitment to-local output.
|
||||
// A compile-time check to ensure that legacyJusticeKit implements the
|
||||
// JusticeKit interface.
|
||||
var _ JusticeKit = (*legacyJusticeKit)(nil)
|
||||
|
||||
// newLegacyJusticeKit constructs a new legacyJusticeKit.
|
||||
func newLegacyJusticeKit(sweepScript []byte,
|
||||
breachInfo *lnwallet.BreachRetribution,
|
||||
withToRemote bool) *legacyJusticeKit {
|
||||
|
||||
keyRing := breachInfo.KeyRing
|
||||
|
||||
packet := justiceKitPacketV0{
|
||||
sweepAddress: sweepScript,
|
||||
revocationPubKey: toBlobPubKey(keyRing.RevocationKey),
|
||||
localDelayPubKey: toBlobPubKey(keyRing.ToLocalKey),
|
||||
csvDelay: breachInfo.RemoteDelay,
|
||||
commitToRemotePubKey: pubKey{},
|
||||
}
|
||||
|
||||
if withToRemote {
|
||||
packet.commitToRemotePubKey = toBlobPubKey(
|
||||
keyRing.ToRemoteKey,
|
||||
)
|
||||
}
|
||||
|
||||
return &legacyJusticeKit{packet}
|
||||
}
|
||||
|
||||
// ToLocalOutputSpendInfo returns the info required to send the to-local output.
|
||||
// It returns the output pub key script and the witness required to spend the
|
||||
// output.
|
||||
//
|
||||
// <revocation-sig> 1
|
||||
func (b *JusticeKit) CommitToLocalRevokeWitnessStack() ([][]byte, error) {
|
||||
toLocalSig, err := b.CommitToLocalSig.ToSignature()
|
||||
// NOTE: This is part of the JusticeKit interface.
|
||||
func (l *legacyJusticeKit) ToLocalOutputSpendInfo() (*txscript.PkScript,
|
||||
wire.TxWitness, error) {
|
||||
|
||||
revocationPubKey, err := btcec.ParsePubKey(l.revocationPubKey[:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
witnessStack := make([][]byte, 2)
|
||||
witnessStack[0] = append(toLocalSig.Serialize(),
|
||||
byte(txscript.SigHashAll))
|
||||
witnessStack[1] = []byte{1}
|
||||
localDelayedPubKey, err := btcec.ParsePubKey(l.localDelayPubKey[:])
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return witnessStack, nil
|
||||
script, err := input.CommitScriptToSelf(
|
||||
l.csvDelay, localDelayedPubKey, revocationPubKey,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
scriptPubKey, err := input.WitnessScriptHash(script)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
toLocalSig, err := l.commitToLocalSig.ToSignature()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
witness := make(wire.TxWitness, 3)
|
||||
witness[0] = append(toLocalSig.Serialize(), byte(txscript.SigHashAll))
|
||||
witness[1] = []byte{1}
|
||||
witness[2] = script
|
||||
|
||||
pkScript, err := txscript.ParsePkScript(scriptPubKey)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return &pkScript, witness, nil
|
||||
}
|
||||
|
||||
// ToRemoteOutputSpendInfo returns the info required to spend the to-remote
|
||||
// output. It returns the output pub key script, the witness required to spend
|
||||
// the output and the sequence to apply.
|
||||
//
|
||||
// NOTE: This is part of the JusticeKit interface.
|
||||
func (l *legacyJusticeKit) ToRemoteOutputSpendInfo() (*txscript.PkScript,
|
||||
wire.TxWitness, uint32, error) {
|
||||
|
||||
if !btcec.IsCompressedPubKey(l.commitToRemotePubKey[:]) {
|
||||
return nil, nil, 0, ErrNoCommitToRemoteOutput
|
||||
}
|
||||
|
||||
toRemoteScript := l.commitToRemotePubKey[:]
|
||||
|
||||
// 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, nil, 0, err
|
||||
}
|
||||
|
||||
// Compute the witness script hash from the to-remote pubkey, which will
|
||||
// be used to locate the output on the breach commitment transaction.
|
||||
toRemoteScriptHash, err := input.CommitScriptUnencumbered(
|
||||
toRemotePubKey,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, 0, err
|
||||
}
|
||||
|
||||
toRemoteSig, err := l.commitToRemoteSig.ToSignature()
|
||||
if err != nil {
|
||||
return nil, nil, 0, err
|
||||
}
|
||||
|
||||
witness := make(wire.TxWitness, 2)
|
||||
witness[0] = append(toRemoteSig.Serialize(), byte(txscript.SigHashAll))
|
||||
witness[1] = toRemoteScript
|
||||
|
||||
pkScript, err := txscript.ParsePkScript(toRemoteScriptHash)
|
||||
if err != nil {
|
||||
return nil, nil, 0, err
|
||||
}
|
||||
|
||||
return &pkScript, witness, 0, nil
|
||||
}
|
||||
|
||||
// HasCommitToRemoteOutput returns true if the blob contains a to-remote p2wkh
|
||||
// pubkey.
|
||||
func (b *JusticeKit) HasCommitToRemoteOutput() bool {
|
||||
return btcec.IsCompressedPubKey(b.CommitToRemotePubKey[:])
|
||||
}
|
||||
|
||||
// CommitToRemoteWitnessScript returns the witness script for the commitment
|
||||
// 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) {
|
||||
if !btcec.IsCompressedPubKey(b.CommitToRemotePubKey[:]) {
|
||||
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[:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return input.CommitScriptToRemoteConfirmed(pk)
|
||||
}
|
||||
|
||||
return b.CommitToRemotePubKey[:], nil
|
||||
}
|
||||
|
||||
// CommitToRemoteWitnessStack returns a witness stack spending the commitment
|
||||
// to-remote output, which consists of a single signature satisfying either the
|
||||
// legacy or anchor witness scripts.
|
||||
//
|
||||
// <to-remote-sig>
|
||||
func (b *JusticeKit) CommitToRemoteWitnessStack() ([][]byte, error) {
|
||||
toRemoteSig, err := b.CommitToRemoteSig.ToSignature()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
// NOTE: This is part of the JusticeKit interface.
|
||||
func (l *legacyJusticeKit) HasCommitToRemoteOutput() bool {
|
||||
return btcec.IsCompressedPubKey(l.commitToRemotePubKey[:])
|
||||
}
|
||||
|
||||
// SweepAddress returns the sweep address to be used on the justice tx
|
||||
// output.
|
||||
//
|
||||
// NOTE: This is part of the JusticeKit interface.
|
||||
func (l *legacyJusticeKit) SweepAddress() []byte {
|
||||
return l.sweepAddress
|
||||
}
|
||||
|
||||
// AddToLocalSig adds the to-local signature to the kit.
|
||||
//
|
||||
// NOTE: This is part of the JusticeKit interface.
|
||||
func (l *legacyJusticeKit) AddToLocalSig(sig lnwire.Sig) {
|
||||
l.commitToLocalSig = sig
|
||||
}
|
||||
|
||||
// AddToRemoteSig adds the to-remote signature to the kit.
|
||||
//
|
||||
// NOTE: This is part of the JusticeKit interface.
|
||||
func (l *legacyJusticeKit) AddToRemoteSig(sig lnwire.Sig) {
|
||||
l.commitToRemoteSig = sig
|
||||
}
|
||||
|
||||
// PlainTextSize is the size of the encoded-but-unencrypted blob in
|
||||
// bytes.
|
||||
//
|
||||
// NOTE: This is part of the JusticeKit interface.
|
||||
func (l *legacyJusticeKit) PlainTextSize() int {
|
||||
return V0PlaintextSize
|
||||
}
|
||||
|
||||
// anchorJusticeKit is an implementation of the JusticeKit interface which can
|
||||
// be used for backing up commitments of anchor channels. It inherits most of
|
||||
// the methods from the legacyJusticeKit and overrides the
|
||||
// ToRemoteOutputSpendInfo method since the to-remote output of an anchor
|
||||
// output is a P2WSH instead of the P2WPKH used by the legacy channels.
|
||||
type anchorJusticeKit struct {
|
||||
legacyJusticeKit
|
||||
}
|
||||
|
||||
// A compile-time check to ensure that legacyJusticeKit implements the
|
||||
// JusticeKit interface.
|
||||
var _ JusticeKit = (*anchorJusticeKit)(nil)
|
||||
|
||||
// newAnchorJusticeKit constructs a new anchorJusticeKit.
|
||||
func newAnchorJusticeKit(sweepScript []byte,
|
||||
breachInfo *lnwallet.BreachRetribution,
|
||||
withToRemote bool) *anchorJusticeKit {
|
||||
|
||||
legacyKit := newLegacyJusticeKit(sweepScript, breachInfo, withToRemote)
|
||||
|
||||
return &anchorJusticeKit{
|
||||
legacyJusticeKit: *legacyKit,
|
||||
}
|
||||
}
|
||||
|
||||
// ToRemoteOutputSpendInfo returns the info required to send the to-remote
|
||||
// output. It returns the output pub key script, the witness required to spend
|
||||
// the output and the sequence to apply.
|
||||
//
|
||||
// NOTE: This is part of the JusticeKit interface.
|
||||
func (a *anchorJusticeKit) ToRemoteOutputSpendInfo() (*txscript.PkScript,
|
||||
wire.TxWitness, uint32, error) {
|
||||
|
||||
if !btcec.IsCompressedPubKey(a.commitToRemotePubKey[:]) {
|
||||
return nil, nil, 0, ErrNoCommitToRemoteOutput
|
||||
}
|
||||
|
||||
witnessStack := make([][]byte, 1)
|
||||
witnessStack[0] = append(toRemoteSig.Serialize(),
|
||||
byte(txscript.SigHashAll))
|
||||
pk, err := btcec.ParsePubKey(a.commitToRemotePubKey[:])
|
||||
if err != nil {
|
||||
return nil, nil, 0, err
|
||||
}
|
||||
|
||||
return witnessStack, nil
|
||||
toRemoteScript, err := input.CommitScriptToRemoteConfirmed(pk)
|
||||
if err != nil {
|
||||
return nil, nil, 0, err
|
||||
}
|
||||
|
||||
toRemoteScriptHash, err := input.WitnessScriptHash(toRemoteScript)
|
||||
if err != nil {
|
||||
return nil, nil, 0, err
|
||||
}
|
||||
|
||||
toRemoteSig, err := a.commitToRemoteSig.ToSignature()
|
||||
if err != nil {
|
||||
return nil, nil, 0, err
|
||||
}
|
||||
|
||||
witness := make([][]byte, 2)
|
||||
witness[0] = append(toRemoteSig.Serialize(), byte(txscript.SigHashAll))
|
||||
witness[1] = toRemoteScript
|
||||
|
||||
pkScript, err := txscript.ParsePkScript(toRemoteScriptHash)
|
||||
if err != nil {
|
||||
return nil, nil, 0, err
|
||||
}
|
||||
|
||||
return &pkScript, witness, 1, nil
|
||||
}
|
||||
|
@@ -74,22 +74,68 @@ var (
|
||||
// nonce: 24 bytes
|
||||
// enciphered plaintext: n bytes
|
||||
// MAC: 16 bytes
|
||||
func Size(blobType Type) int {
|
||||
return NonceSize + PlaintextSize(blobType) + CiphertextExpansion
|
||||
func Size(kit JusticeKit) int {
|
||||
return NonceSize + kit.PlainTextSize() + CiphertextExpansion
|
||||
}
|
||||
|
||||
// PlaintextSize returns the size of the encoded-but-unencrypted blob in bytes.
|
||||
func PlaintextSize(blobType Type) int {
|
||||
switch {
|
||||
case blobType.Has(FlagCommitOutputs):
|
||||
return V0PlaintextSize
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
// pubKey is a 33-byte, serialized compressed public key.
|
||||
type pubKey [33]byte
|
||||
|
||||
// toBlobPubKey serializes the given public key into a pubKey that can be set
|
||||
// as a field on a JusticeKit.
|
||||
func toBlobPubKey(pk *btcec.PublicKey) pubKey {
|
||||
var blobPubKey pubKey
|
||||
copy(blobPubKey[:], pk.SerializeCompressed())
|
||||
return blobPubKey
|
||||
}
|
||||
|
||||
// PubKey is a 33-byte, serialized compressed public key.
|
||||
type PubKey [33]byte
|
||||
// justiceKitPacketV0 is lé Blob of Justice. The JusticeKit contains information
|
||||
// required to construct a justice transaction, that sweeps a remote party's
|
||||
// revoked commitment transaction. It supports encryption and decryption using
|
||||
// chacha20poly1305, allowing the client to encrypt the contents of the blob,
|
||||
// and for a watchtower to later decrypt if action must be taken.
|
||||
type justiceKitPacketV0 struct {
|
||||
// sweepAddress is the witness program of the output where the client's
|
||||
// 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
|
||||
// correlated across sessions and/or towers.
|
||||
//
|
||||
// NOTE: This is chosen to be the length of a maximally sized witness
|
||||
// program.
|
||||
sweepAddress []byte
|
||||
|
||||
// revocationPubKey is the compressed pubkey that guards the revocation
|
||||
// clause of the remote party's to-local output.
|
||||
revocationPubKey pubKey
|
||||
|
||||
// localDelayPubKey is the compressed pubkey in the to-local script of
|
||||
// the remote party, which guards the path where the remote party
|
||||
// claims their commitment output.
|
||||
localDelayPubKey pubKey
|
||||
|
||||
// csvDelay is the relative timelock in the remote party's to-local
|
||||
// output, which the remote party must wait out before sweeping their
|
||||
// commitment output.
|
||||
csvDelay uint32
|
||||
|
||||
// commitToLocalSig is a signature under RevocationPubKey using
|
||||
// SIGHASH_ALL.
|
||||
commitToLocalSig lnwire.Sig
|
||||
|
||||
// commitToRemotePubKey is the public key in the to-remote output of the
|
||||
// revoked commitment transaction.
|
||||
//
|
||||
// NOTE: This value is only used if it contains a valid compressed
|
||||
// public key.
|
||||
commitToRemotePubKey pubKey
|
||||
|
||||
// commitToRemoteSig is a signature under CommitToRemotePubKey using
|
||||
// SIGHASH_ALL.
|
||||
//
|
||||
// NOTE: This value is only used if CommitToRemotePubKey contains a
|
||||
// valid compressed public key.
|
||||
commitToRemoteSig lnwire.Sig
|
||||
}
|
||||
|
||||
// Encrypt encodes the blob of justice using encoding version, and then
|
||||
// creates a ciphertext using chacha20poly1305 under the chosen (nonce, key)
|
||||
@@ -97,11 +143,11 @@ type PubKey [33]byte
|
||||
//
|
||||
// NOTE: It is the caller's responsibility to ensure that this method is only
|
||||
// called once for a given (nonce, key) pair.
|
||||
func (b *JusticeKit) Encrypt(key BreachKey) ([]byte, error) {
|
||||
func Encrypt(kit JusticeKit, key BreachKey) ([]byte, error) {
|
||||
// Encode the plaintext using the provided version, to obtain the
|
||||
// plaintext bytes.
|
||||
var ptxtBuf bytes.Buffer
|
||||
err := b.encode(&ptxtBuf, b.BlobType)
|
||||
err := kit.encode(&ptxtBuf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -115,7 +161,7 @@ func (b *JusticeKit) Encrypt(key BreachKey) ([]byte, error) {
|
||||
// Allocate the ciphertext, which will contain the nonce, encrypted
|
||||
// plaintext and MAC.
|
||||
plaintext := ptxtBuf.Bytes()
|
||||
ciphertext := make([]byte, Size(b.BlobType))
|
||||
ciphertext := make([]byte, Size(kit))
|
||||
|
||||
// Generate a random 24-byte nonce in the ciphertext's prefix.
|
||||
nonce := ciphertext[:NonceSize]
|
||||
@@ -134,7 +180,7 @@ func (b *JusticeKit) Encrypt(key BreachKey) ([]byte, error) {
|
||||
// chacha20poly1305 with the chosen (nonce, key) pair. The internal plaintext is
|
||||
// then deserialized using the given encoding version.
|
||||
func Decrypt(key BreachKey, ciphertext []byte,
|
||||
blobType Type) (*JusticeKit, error) {
|
||||
blobType Type) (JusticeKit, error) {
|
||||
|
||||
// Fail if the blob's overall length is less than required for the nonce
|
||||
// and expansion factor.
|
||||
@@ -161,42 +207,27 @@ func Decrypt(key BreachKey, ciphertext []byte,
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// If decryption succeeded, we will then decode the plaintext bytes
|
||||
// using the specified blob version.
|
||||
boj := &JusticeKit{
|
||||
BlobType: blobType,
|
||||
}
|
||||
err = boj.decode(bytes.NewReader(plaintext), blobType)
|
||||
commitment, err := blobType.CommitmentType(nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return boj, nil
|
||||
}
|
||||
|
||||
// encode serializes the JusticeKit according to the version, returning an
|
||||
// error if the version is unknown.
|
||||
func (b *JusticeKit) encode(w io.Writer, blobType Type) error {
|
||||
switch {
|
||||
case blobType.Has(FlagCommitOutputs):
|
||||
return b.encodeV0(w)
|
||||
default:
|
||||
return ErrUnknownBlobType
|
||||
kit, err := commitment.EmptyJusticeKit()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// decode deserializes the JusticeKit according to the version, returning an
|
||||
// error if the version is unknown.
|
||||
func (b *JusticeKit) decode(r io.Reader, blobType Type) error {
|
||||
switch {
|
||||
case blobType.Has(FlagCommitOutputs):
|
||||
return b.decodeV0(r)
|
||||
default:
|
||||
return ErrUnknownBlobType
|
||||
// If decryption succeeded, we will then decode the plaintext bytes
|
||||
// using the specified blob version.
|
||||
err = kit.decode(bytes.NewReader(plaintext))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return kit, nil
|
||||
}
|
||||
|
||||
// encodeV0 encodes the JusticeKit using the version 0 encoding scheme to the
|
||||
// encode encodes the JusticeKit using the version 0 encoding scheme to the
|
||||
// provided io.Writer. The encoding supports sweeping of the commit to-local
|
||||
// output, and optionally the commit to-remote output. The encoding produces a
|
||||
// constant-size plaintext size of 274 bytes.
|
||||
@@ -211,21 +242,21 @@ func (b *JusticeKit) decode(r io.Reader, blobType Type) error {
|
||||
// commit to-local revocation sig: 64 bytes
|
||||
// commit to-remote pubkey: 33 bytes, maybe blank
|
||||
// commit to-remote sig: 64 bytes, maybe blank
|
||||
func (b *JusticeKit) encodeV0(w io.Writer) error {
|
||||
func (b *justiceKitPacketV0) encode(w io.Writer) error {
|
||||
// Assert the sweep address length is sane.
|
||||
if len(b.SweepAddress) > MaxSweepAddrSize {
|
||||
if len(b.sweepAddress) > MaxSweepAddrSize {
|
||||
return ErrSweepAddressToLong
|
||||
}
|
||||
|
||||
// Write the actual length of the sweep address as a single byte.
|
||||
err := binary.Write(w, byteOrder, uint8(len(b.SweepAddress)))
|
||||
err := binary.Write(w, byteOrder, uint8(len(b.sweepAddress)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Pad the sweep address to our maximum length of 42 bytes.
|
||||
var sweepAddressBuf [MaxSweepAddrSize]byte
|
||||
copy(sweepAddressBuf[:], b.SweepAddress)
|
||||
copy(sweepAddressBuf[:], b.sweepAddress)
|
||||
|
||||
// Write padded 42-byte sweep address.
|
||||
_, err = w.Write(sweepAddressBuf[:])
|
||||
@@ -234,42 +265,42 @@ func (b *JusticeKit) encodeV0(w io.Writer) error {
|
||||
}
|
||||
|
||||
// Write 33-byte revocation public key.
|
||||
_, err = w.Write(b.RevocationPubKey[:])
|
||||
_, err = w.Write(b.revocationPubKey[:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Write 33-byte local delay public key.
|
||||
_, err = w.Write(b.LocalDelayPubKey[:])
|
||||
_, err = w.Write(b.localDelayPubKey[:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Write 4-byte CSV delay.
|
||||
err = binary.Write(w, byteOrder, b.CSVDelay)
|
||||
err = binary.Write(w, byteOrder, b.csvDelay)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Write 64-byte revocation signature for commit to-local output.
|
||||
_, err = w.Write(b.CommitToLocalSig.RawBytes())
|
||||
_, err = w.Write(b.commitToLocalSig.RawBytes())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Write 33-byte commit to-remote public key, which may be blank.
|
||||
_, err = w.Write(b.CommitToRemotePubKey[:])
|
||||
_, err = w.Write(b.commitToRemotePubKey[:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Write 64-byte commit to-remote signature, which may be blank.
|
||||
_, err = w.Write(b.CommitToRemoteSig.RawBytes())
|
||||
_, err = w.Write(b.commitToRemoteSig.RawBytes())
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// decodeV0 reconstructs a JusticeKit from the io.Reader, using version 0
|
||||
// decode reconstructs a JusticeKit from the io.Reader, using version 0
|
||||
// encoding scheme. This will parse a constant size input stream of 274 bytes to
|
||||
// recover information for the commit to-local output, and possibly the commit
|
||||
// to-remote output.
|
||||
@@ -284,7 +315,7 @@ func (b *JusticeKit) encodeV0(w io.Writer) error {
|
||||
// commit to-local revocation sig: 64 bytes
|
||||
// commit to-remote pubkey: 33 bytes, maybe blank
|
||||
// commit to-remote sig: 64 bytes, maybe blank
|
||||
func (b *JusticeKit) decodeV0(r io.Reader) error {
|
||||
func (b *justiceKitPacketV0) decode(r io.Reader) error {
|
||||
// Read the sweep address length as a single byte.
|
||||
var sweepAddrLen uint8
|
||||
err := binary.Read(r, byteOrder, &sweepAddrLen)
|
||||
@@ -305,23 +336,23 @@ func (b *JusticeKit) decodeV0(r io.Reader) error {
|
||||
}
|
||||
|
||||
// Parse sweep address from padded buffer.
|
||||
b.SweepAddress = make([]byte, sweepAddrLen)
|
||||
copy(b.SweepAddress, sweepAddressBuf[:])
|
||||
b.sweepAddress = make([]byte, sweepAddrLen)
|
||||
copy(b.sweepAddress, sweepAddressBuf[:])
|
||||
|
||||
// Read 33-byte revocation public key.
|
||||
_, err = io.ReadFull(r, b.RevocationPubKey[:])
|
||||
_, err = io.ReadFull(r, b.revocationPubKey[:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Read 33-byte local delay public key.
|
||||
_, err = io.ReadFull(r, b.LocalDelayPubKey[:])
|
||||
_, err = io.ReadFull(r, b.localDelayPubKey[:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Read 4-byte CSV delay.
|
||||
err = binary.Read(r, byteOrder, &b.CSVDelay)
|
||||
err = binary.Read(r, byteOrder, &b.csvDelay)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -333,13 +364,13 @@ func (b *JusticeKit) decodeV0(r io.Reader) error {
|
||||
return err
|
||||
}
|
||||
|
||||
b.CommitToLocalSig, err = lnwire.NewSigFromWireECDSA(localSig[:])
|
||||
b.commitToLocalSig, err = lnwire.NewSigFromWireECDSA(localSig[:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var (
|
||||
commitToRemotePubkey PubKey
|
||||
commitToRemotePubkey pubKey
|
||||
commitToRemoteSig [64]byte
|
||||
)
|
||||
|
||||
@@ -358,8 +389,8 @@ func (b *JusticeKit) decodeV0(r io.Reader) error {
|
||||
// Only populate the commit to-remote fields in the decoded blob if a
|
||||
// valid compressed public key was read from the reader.
|
||||
if btcec.IsCompressedPubKey(commitToRemotePubkey[:]) {
|
||||
b.CommitToRemotePubKey = commitToRemotePubkey
|
||||
b.CommitToRemoteSig, err = lnwire.NewSigFromWireECDSA(
|
||||
b.commitToRemotePubKey = commitToRemotePubkey
|
||||
b.commitToRemoteSig, err = lnwire.NewSigFromWireECDSA(
|
||||
commitToRemoteSig[:],
|
||||
)
|
||||
if err != nil {
|
||||
|
@@ -1,30 +1,25 @@
|
||||
package blob_test
|
||||
package blob
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/btcsuite/btcd/btcec/v2/ecdsa"
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/lightningnetwork/lnd/input"
|
||||
"github.com/lightningnetwork/lnd/lnwallet"
|
||||
"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 makePubKey() *btcec.PublicKey {
|
||||
priv, _ := btcec.NewPrivateKey()
|
||||
return priv.PubKey()
|
||||
}
|
||||
|
||||
func makeSig(i int) lnwire.Sig {
|
||||
@@ -46,15 +41,15 @@ func makeAddr(size int) []byte {
|
||||
|
||||
type descriptorTest struct {
|
||||
name string
|
||||
encVersion blob.Type
|
||||
decVersion blob.Type
|
||||
encVersion Type
|
||||
decVersion Type
|
||||
sweepAddr []byte
|
||||
revPubKey blob.PubKey
|
||||
delayPubKey blob.PubKey
|
||||
revPubKey *btcec.PublicKey
|
||||
delayPubKey *btcec.PublicKey
|
||||
csvDelay uint32
|
||||
commitToLocalSig lnwire.Sig
|
||||
hasCommitToRemote bool
|
||||
commitToRemotePubKey blob.PubKey
|
||||
commitToRemotePubKey *btcec.PublicKey
|
||||
commitToRemoteSig lnwire.Sig
|
||||
encErr error
|
||||
decErr error
|
||||
@@ -63,79 +58,79 @@ type descriptorTest struct {
|
||||
var descriptorTests = []descriptorTest{
|
||||
{
|
||||
name: "to-local only",
|
||||
encVersion: blob.TypeAltruistCommit,
|
||||
decVersion: blob.TypeAltruistCommit,
|
||||
encVersion: TypeAltruistCommit,
|
||||
decVersion: TypeAltruistCommit,
|
||||
sweepAddr: makeAddr(22),
|
||||
revPubKey: makePubKey(0),
|
||||
delayPubKey: makePubKey(1),
|
||||
revPubKey: makePubKey(),
|
||||
delayPubKey: makePubKey(),
|
||||
csvDelay: 144,
|
||||
commitToLocalSig: makeSig(1),
|
||||
},
|
||||
{
|
||||
name: "to-local and p2wkh",
|
||||
encVersion: blob.TypeRewardCommit,
|
||||
decVersion: blob.TypeRewardCommit,
|
||||
encVersion: TypeRewardCommit,
|
||||
decVersion: TypeRewardCommit,
|
||||
sweepAddr: makeAddr(22),
|
||||
revPubKey: makePubKey(0),
|
||||
delayPubKey: makePubKey(1),
|
||||
revPubKey: makePubKey(),
|
||||
delayPubKey: makePubKey(),
|
||||
csvDelay: 144,
|
||||
commitToLocalSig: makeSig(1),
|
||||
hasCommitToRemote: true,
|
||||
commitToRemotePubKey: makePubKey(2),
|
||||
commitToRemotePubKey: makePubKey(),
|
||||
commitToRemoteSig: makeSig(2),
|
||||
},
|
||||
{
|
||||
name: "unknown encrypt version",
|
||||
encVersion: 0,
|
||||
decVersion: blob.TypeAltruistCommit,
|
||||
decVersion: TypeAltruistCommit,
|
||||
sweepAddr: makeAddr(34),
|
||||
revPubKey: makePubKey(0),
|
||||
delayPubKey: makePubKey(1),
|
||||
revPubKey: makePubKey(),
|
||||
delayPubKey: makePubKey(),
|
||||
csvDelay: 144,
|
||||
commitToLocalSig: makeSig(1),
|
||||
encErr: blob.ErrUnknownBlobType,
|
||||
encErr: ErrUnknownBlobType,
|
||||
},
|
||||
{
|
||||
name: "unknown decrypt version",
|
||||
encVersion: blob.TypeAltruistCommit,
|
||||
encVersion: TypeAltruistCommit,
|
||||
decVersion: 0,
|
||||
sweepAddr: makeAddr(34),
|
||||
revPubKey: makePubKey(0),
|
||||
delayPubKey: makePubKey(1),
|
||||
revPubKey: makePubKey(),
|
||||
delayPubKey: makePubKey(),
|
||||
csvDelay: 144,
|
||||
commitToLocalSig: makeSig(1),
|
||||
decErr: blob.ErrUnknownBlobType,
|
||||
decErr: ErrUnknownBlobType,
|
||||
},
|
||||
{
|
||||
name: "sweep addr length zero",
|
||||
encVersion: blob.TypeAltruistCommit,
|
||||
decVersion: blob.TypeAltruistCommit,
|
||||
encVersion: TypeAltruistCommit,
|
||||
decVersion: TypeAltruistCommit,
|
||||
sweepAddr: makeAddr(0),
|
||||
revPubKey: makePubKey(0),
|
||||
delayPubKey: makePubKey(1),
|
||||
revPubKey: makePubKey(),
|
||||
delayPubKey: makePubKey(),
|
||||
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),
|
||||
encVersion: TypeAltruistCommit,
|
||||
decVersion: TypeAltruistCommit,
|
||||
sweepAddr: makeAddr(MaxSweepAddrSize),
|
||||
revPubKey: makePubKey(),
|
||||
delayPubKey: makePubKey(),
|
||||
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),
|
||||
encVersion: TypeAltruistCommit,
|
||||
decVersion: TypeAltruistCommit,
|
||||
sweepAddr: makeAddr(MaxSweepAddrSize + 1),
|
||||
revPubKey: makePubKey(),
|
||||
delayPubKey: makePubKey(),
|
||||
csvDelay: 144,
|
||||
commitToLocalSig: makeSig(1),
|
||||
encErr: blob.ErrSweepAddressToLong,
|
||||
encErr: ErrSweepAddressToLong,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -152,30 +147,43 @@ func TestBlobJusticeKitEncryptDecrypt(t *testing.T) {
|
||||
}
|
||||
|
||||
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,
|
||||
commitmentType, err := test.encVersion.CommitmentType(nil)
|
||||
if err != nil {
|
||||
require.ErrorIs(t, err, test.encErr)
|
||||
return
|
||||
}
|
||||
|
||||
breachInfo := &lnwallet.BreachRetribution{
|
||||
RemoteDelay: test.csvDelay,
|
||||
KeyRing: &lnwallet.CommitmentKeyRing{
|
||||
ToLocalKey: test.delayPubKey,
|
||||
ToRemoteKey: test.commitToRemotePubKey,
|
||||
RevocationKey: test.revPubKey,
|
||||
},
|
||||
}
|
||||
|
||||
kit, err := commitmentType.NewJusticeKit(
|
||||
test.sweepAddr, breachInfo, test.hasCommitToRemote,
|
||||
)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
kit.AddToLocalSig(test.commitToLocalSig)
|
||||
kit.AddToRemoteSig(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[:])
|
||||
var key BreachKey
|
||||
_, err = rand.Read(key[:])
|
||||
require.NoError(t, err, "unable to generate blob encryption key")
|
||||
|
||||
// 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 {
|
||||
ctxt, err := Encrypt(kit, key)
|
||||
require.ErrorIs(t, err, test.encErr)
|
||||
|
||||
if test.encErr != nil {
|
||||
// If the test expected an encryption failure, we can
|
||||
// continue to the next test.
|
||||
return
|
||||
@@ -183,19 +191,15 @@ func testBlobJusticeKitEncryptDecrypt(t *testing.T, test descriptorTest) {
|
||||
|
||||
// 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))
|
||||
|
||||
}
|
||||
require.Len(t, ctxt, Size(kit))
|
||||
|
||||
// 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 {
|
||||
boj2, err := Decrypt(key, ctxt, test.decVersion)
|
||||
require.ErrorIs(t, err, test.decErr)
|
||||
|
||||
if test.decErr != nil {
|
||||
// If the test expected an decryption failure, we can
|
||||
// continue to the next test.
|
||||
return
|
||||
@@ -210,15 +214,12 @@ func testBlobJusticeKitEncryptDecrypt(t *testing.T, test descriptorTest) {
|
||||
|
||||
// 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)
|
||||
}
|
||||
require.Equal(t, kit, boj2)
|
||||
}
|
||||
|
||||
type remoteWitnessTest struct {
|
||||
name string
|
||||
blobType blob.Type
|
||||
blobType Type
|
||||
expWitnessScript func(pk *btcec.PublicKey) []byte
|
||||
}
|
||||
|
||||
@@ -229,15 +230,14 @@ func TestJusticeKitRemoteWitnessConstruction(t *testing.T) {
|
||||
tests := []remoteWitnessTest{
|
||||
{
|
||||
name: "legacy commitment",
|
||||
blobType: blob.Type(blob.FlagCommitOutputs),
|
||||
blobType: TypeAltruistCommit,
|
||||
expWitnessScript: func(pk *btcec.PublicKey) []byte {
|
||||
return pk.SerializeCompressed()
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "anchor commitment",
|
||||
blobType: blob.Type(blob.FlagCommitOutputs |
|
||||
blob.FlagAnchorChannel),
|
||||
name: "anchor commitment",
|
||||
blobType: TypeAltruistAnchorCommit,
|
||||
expWitnessScript: func(pk *btcec.PublicKey) []byte {
|
||||
script, _ := input.CommitScriptToRemoteConfirmed(pk)
|
||||
return script
|
||||
@@ -257,12 +257,13 @@ func testJusticeKitRemoteWitnessConstruction(
|
||||
|
||||
// Generate the to-remote pubkey.
|
||||
toRemotePrivKey, err := btcec.NewPrivateKey()
|
||||
require.Nil(t, err)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Copy the to-remote pubkey into the format expected by our justice
|
||||
// kit.
|
||||
var toRemotePubKey blob.PubKey
|
||||
copy(toRemotePubKey[:], toRemotePrivKey.PubKey().SerializeCompressed())
|
||||
revKey, err := btcec.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
|
||||
toLocalKey, err := btcec.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
|
||||
// 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.
|
||||
@@ -273,26 +274,29 @@ func testJusticeKitRemoteWitnessConstruction(
|
||||
commitToRemoteSig, err := lnwire.NewSigFromSignature(rawToRemoteSig)
|
||||
require.Nil(t, err)
|
||||
|
||||
// Populate the justice kit fields relevant to the to-remote output.
|
||||
justiceKit := &blob.JusticeKit{
|
||||
BlobType: test.blobType,
|
||||
CommitToRemotePubKey: toRemotePubKey,
|
||||
CommitToRemoteSig: commitToRemoteSig,
|
||||
commitType, err := test.blobType.CommitmentType(nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
breachInfo := &lnwallet.BreachRetribution{
|
||||
KeyRing: &lnwallet.CommitmentKeyRing{
|
||||
ToRemoteKey: toRemotePrivKey.PubKey(),
|
||||
RevocationKey: revKey.PubKey(),
|
||||
ToLocalKey: toLocalKey.PubKey(),
|
||||
},
|
||||
}
|
||||
|
||||
justiceKit, err := commitType.NewJusticeKit(nil, breachInfo, true)
|
||||
require.NoError(t, err)
|
||||
justiceKit.AddToRemoteSig(commitToRemoteSig)
|
||||
|
||||
// Now, compute the to-remote witness script returned by the justice
|
||||
// kit.
|
||||
toRemoteScript, err := justiceKit.CommitToRemoteWitnessScript()
|
||||
require.Nil(t, err)
|
||||
_, witness, _, err := justiceKit.ToRemoteOutputSpendInfo()
|
||||
require.NoError(t, err)
|
||||
|
||||
// Assert this is exactly the to-remote, compressed pubkey.
|
||||
expToRemoteScript := test.expWitnessScript(toRemotePrivKey.PubKey())
|
||||
require.Equal(t, expToRemoteScript, toRemoteScript)
|
||||
|
||||
// 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)
|
||||
require.Equal(t, expToRemoteScript, witness[1])
|
||||
|
||||
// Compute the expected first element, by appending a sighash all byte
|
||||
// to our raw DER-encoded signature.
|
||||
@@ -301,19 +305,10 @@ func testJusticeKitRemoteWitnessConstruction(
|
||||
)
|
||||
|
||||
// Assert that the expected witness stack is returned.
|
||||
expWitnessStack := [][]byte{
|
||||
expWitnessStack := wire.TxWitness{
|
||||
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)
|
||||
require.Equal(t, expWitnessStack, witness[:1])
|
||||
}
|
||||
|
||||
// TestJusticeKitToLocalWitnessConstruction tests that a JusticeKit returns the
|
||||
@@ -324,18 +319,10 @@ func TestJusticeKitToLocalWitnessConstruction(t *testing.T) {
|
||||
|
||||
// Generate the revocation and delay private keys.
|
||||
revPrivKey, err := btcec.NewPrivateKey()
|
||||
require.Nil(t, err)
|
||||
require.NoError(t, err)
|
||||
|
||||
delayPrivKey, err := btcec.NewPrivateKey()
|
||||
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())
|
||||
require.NoError(t, err)
|
||||
|
||||
// Sign a message using the revocation private key. The exact message
|
||||
// doesn't matter as we won't be validating the signature's validity.
|
||||
@@ -344,33 +331,36 @@ func TestJusticeKitToLocalWitnessConstruction(t *testing.T) {
|
||||
|
||||
// Convert the DER-encoded signature into a fixed-size sig.
|
||||
commitToLocalSig, err := lnwire.NewSigFromSignature(rawRevSig)
|
||||
require.Nil(t, err)
|
||||
require.NoError(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,
|
||||
commitType, err := TypeAltruistCommit.CommitmentType(nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
breachInfo := &lnwallet.BreachRetribution{
|
||||
RemoteDelay: csvDelay,
|
||||
KeyRing: &lnwallet.CommitmentKeyRing{
|
||||
RevocationKey: revPrivKey.PubKey(),
|
||||
ToLocalKey: delayPrivKey.PubKey(),
|
||||
},
|
||||
}
|
||||
|
||||
justiceKit, err := commitType.NewJusticeKit(nil, breachInfo, false)
|
||||
require.NoError(t, err)
|
||||
justiceKit.AddToLocalSig(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)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Compute the to-local script that is returned by the justice kit.
|
||||
toLocalScript, err := justiceKit.CommitToLocalWitnessScript()
|
||||
require.Nil(t, err)
|
||||
_, witness, err := justiceKit.ToLocalOutputSpendInfo()
|
||||
require.NoError(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)
|
||||
require.Equal(t, expToLocalScript, witness[2])
|
||||
|
||||
// Compute the expected signature in the bottom element of the stack, by
|
||||
// appending a sighash all flag to the raw DER signature.
|
||||
@@ -379,9 +369,9 @@ func TestJusticeKitToLocalWitnessConstruction(t *testing.T) {
|
||||
)
|
||||
|
||||
// Finally, validate against our expected witness stack.
|
||||
expWitnessStack := [][]byte{
|
||||
expWitnessStack := wire.TxWitness{
|
||||
rawRevSigWithSigHash,
|
||||
{1},
|
||||
}
|
||||
require.Equal(t, expWitnessStack, toLocalWitnessStack)
|
||||
require.Equal(t, expWitnessStack, witness[:2])
|
||||
}
|
||||
|
Reference in New Issue
Block a user