mirror of
https://github.com/lightningnetwork/lnd.git
synced 2024-11-19 09:53:54 +01:00
503 lines
14 KiB
Go
503 lines
14 KiB
Go
package blob
|
|
|
|
import (
|
|
"bytes"
|
|
"io"
|
|
|
|
"github.com/btcsuite/btcd/btcec/v2"
|
|
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
|
"github.com/btcsuite/btcd/txscript"
|
|
"github.com/btcsuite/btcd/wire"
|
|
secp "github.com/decred/dcrd/dcrec/secp256k1/v4"
|
|
"github.com/lightningnetwork/lnd/input"
|
|
"github.com/lightningnetwork/lnd/lnwallet"
|
|
"github.com/lightningnetwork/lnd/lnwire"
|
|
)
|
|
|
|
// 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)
|
|
|
|
// 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)
|
|
|
|
// HasCommitToRemoteOutput returns true if the kit does include the
|
|
// information required to sweep the to-remote output.
|
|
HasCommitToRemoteOutput() bool
|
|
|
|
// AddToLocalSig adds the to-local signature to the kit.
|
|
AddToLocalSig(sig lnwire.Sig)
|
|
|
|
// AddToRemoteSig adds the to-remote signature to the kit.
|
|
AddToRemoteSig(sig lnwire.Sig)
|
|
|
|
// SweepAddress returns the sweep address to be used on the justice tx
|
|
// output.
|
|
SweepAddress() []byte
|
|
|
|
// PlainTextSize is the size of the encoded-but-unencrypted blob in
|
|
// bytes.
|
|
PlainTextSize() int
|
|
|
|
encode(w io.Writer) error
|
|
decode(r io.Reader) error
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// 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,
|
|
}
|
|
|
|
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.
|
|
//
|
|
// 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, nil, err
|
|
}
|
|
|
|
localDelayedPubKey, err := btcec.ParsePubKey(l.localDelayPubKey[:])
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
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.
|
|
//
|
|
// 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
|
|
}
|
|
|
|
pk, err := btcec.ParsePubKey(a.commitToRemotePubKey[:])
|
|
if err != nil {
|
|
return nil, nil, 0, err
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
// taprootJusticeKit is an implementation of the JusticeKit interface which can
|
|
// be used for backing up commitments of taproot channels.
|
|
type taprootJusticeKit struct {
|
|
justiceKitPacketV1
|
|
}
|
|
|
|
// A compile-time check to ensure that taprootJusticeKit implements the
|
|
// JusticeKit interface.
|
|
var _ JusticeKit = (*taprootJusticeKit)(nil)
|
|
|
|
// newTaprootJusticeKit constructs a new taprootJusticeKit.
|
|
func newTaprootJusticeKit(sweepScript []byte,
|
|
breachInfo *lnwallet.BreachRetribution,
|
|
withToRemote bool) (*taprootJusticeKit, error) {
|
|
|
|
keyRing := breachInfo.KeyRing
|
|
|
|
tree, err := input.NewLocalCommitScriptTree(
|
|
breachInfo.RemoteDelay, keyRing.ToLocalKey,
|
|
keyRing.RevocationKey,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
packet := justiceKitPacketV1{
|
|
sweepAddress: sweepScript,
|
|
revocationPubKey: toBlobSchnorrPubKey(
|
|
keyRing.RevocationKey,
|
|
),
|
|
localDelayPubKey: toBlobSchnorrPubKey(keyRing.ToLocalKey),
|
|
delayScriptHash: tree.SettleLeaf.TapHash(),
|
|
}
|
|
|
|
if withToRemote {
|
|
packet.commitToRemotePubKey = toBlobPubKey(keyRing.ToRemoteKey)
|
|
}
|
|
|
|
return &taprootJusticeKit{packet}, nil
|
|
}
|
|
|
|
// ToLocalOutputSpendInfo returns the info required to send the to-local
|
|
// output. It returns the output pubkey script and the witness required
|
|
// to spend the output.
|
|
//
|
|
// NOTE: This is part of the JusticeKit interface.
|
|
func (t *taprootJusticeKit) ToLocalOutputSpendInfo() (*txscript.PkScript,
|
|
wire.TxWitness, error) {
|
|
|
|
revocationPubKey, err := schnorr.ParsePubKey(t.revocationPubKey[:])
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
localDelayedPubKey, err := schnorr.ParsePubKey(t.localDelayPubKey[:])
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
revokeScript, err := input.TaprootLocalCommitRevokeScript(
|
|
localDelayedPubKey, revocationPubKey,
|
|
)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
revokeLeaf := txscript.NewBaseTapLeaf(revokeScript)
|
|
revokeLeafHash := revokeLeaf.TapHash()
|
|
rootHash := tapBranchHash(revokeLeafHash[:], t.delayScriptHash[:])
|
|
|
|
outputKey := txscript.ComputeTaprootOutputKey(
|
|
&input.TaprootNUMSKey, rootHash[:],
|
|
)
|
|
|
|
scriptPk, err := input.PayToTaprootScript(outputKey)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
ctrlBlock := txscript.ControlBlock{
|
|
InternalKey: &input.TaprootNUMSKey,
|
|
OutputKeyYIsOdd: isOddPub(outputKey),
|
|
LeafVersion: revokeLeaf.LeafVersion,
|
|
InclusionProof: t.delayScriptHash[:],
|
|
}
|
|
|
|
ctrlBytes, err := ctrlBlock.ToBytes()
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
toLocalSig, err := t.commitToLocalSig.ToSignature()
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
witness := make([][]byte, 3)
|
|
witness[0] = toLocalSig.Serialize()
|
|
witness[1] = revokeScript
|
|
witness[2] = ctrlBytes
|
|
|
|
pkScript, err := txscript.ParsePkScript(scriptPk)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
return &pkScript, witness, nil
|
|
}
|
|
|
|
// ToRemoteOutputSpendInfo returns the info required to send the to-remote
|
|
// output. It returns the output pubkey script, the witness required to spend
|
|
// the output and the sequence to apply.
|
|
//
|
|
// NOTE: This is part of the JusticeKit interface.
|
|
func (t *taprootJusticeKit) ToRemoteOutputSpendInfo() (*txscript.PkScript,
|
|
wire.TxWitness, uint32, error) {
|
|
|
|
if len(t.commitToRemotePubKey[:]) == 0 {
|
|
return nil, nil, 0, ErrNoCommitToRemoteOutput
|
|
}
|
|
|
|
toRemotePk, err := btcec.ParsePubKey(t.commitToRemotePubKey[:])
|
|
if err != nil {
|
|
return nil, nil, 0, err
|
|
}
|
|
|
|
scriptTree, err := input.NewRemoteCommitScriptTree(toRemotePk)
|
|
if err != nil {
|
|
return nil, nil, 0, err
|
|
}
|
|
|
|
script, err := input.PayToTaprootScript(scriptTree.TaprootKey)
|
|
if err != nil {
|
|
return nil, nil, 0, err
|
|
}
|
|
|
|
settleControlBlock := input.MakeTaprootCtrlBlock(
|
|
scriptTree.SettleLeaf.Script, &input.TaprootNUMSKey,
|
|
scriptTree.TapscriptTree,
|
|
)
|
|
|
|
ctrl, err := settleControlBlock.ToBytes()
|
|
if err != nil {
|
|
return nil, nil, 0, err
|
|
}
|
|
|
|
toRemoteSig, err := t.commitToRemoteSig.ToSignature()
|
|
if err != nil {
|
|
return nil, nil, 0, err
|
|
}
|
|
|
|
witness := make([][]byte, 3)
|
|
witness[0] = toRemoteSig.Serialize()
|
|
witness[1] = scriptTree.SettleLeaf.Script
|
|
witness[2] = ctrl
|
|
|
|
pkScript, err := txscript.ParsePkScript(script)
|
|
if err != nil {
|
|
return nil, nil, 0, err
|
|
}
|
|
|
|
return &pkScript, witness, 1, nil
|
|
}
|
|
|
|
// HasCommitToRemoteOutput returns true if the blob contains a to-remote pubkey.
|
|
//
|
|
// NOTE: This is part of the JusticeKit interface.
|
|
func (t *taprootJusticeKit) HasCommitToRemoteOutput() bool {
|
|
return btcec.IsCompressedPubKey(t.commitToRemotePubKey[:])
|
|
}
|
|
|
|
// AddToLocalSig adds the to-local signature to the kit.
|
|
//
|
|
// NOTE: This is part of the JusticeKit interface.
|
|
func (t *taprootJusticeKit) AddToLocalSig(sig lnwire.Sig) {
|
|
t.commitToLocalSig = sig
|
|
}
|
|
|
|
// AddToRemoteSig adds the to-remote signature to the kit.
|
|
//
|
|
// NOTE: This is part of the JusticeKit interface.
|
|
func (t *taprootJusticeKit) AddToRemoteSig(sig lnwire.Sig) {
|
|
t.commitToRemoteSig = sig
|
|
}
|
|
|
|
// SweepAddress returns the sweep address to be used on the justice tx output.
|
|
//
|
|
// NOTE: This is part of the JusticeKit interface.
|
|
func (t *taprootJusticeKit) SweepAddress() []byte {
|
|
return t.sweepAddress
|
|
}
|
|
|
|
// PlainTextSize is the size of the encoded-but-unencrypted blob in bytes.
|
|
//
|
|
// NOTE: This is part of the JusticeKit interface.
|
|
func (t *taprootJusticeKit) PlainTextSize() int {
|
|
return V1PlaintextSize
|
|
}
|
|
|
|
func tapBranchHash(l, r []byte) chainhash.Hash {
|
|
if bytes.Compare(l, r) > 0 {
|
|
l, r = r, l
|
|
}
|
|
|
|
return *chainhash.TaggedHash(chainhash.TagTapBranch, l, r)
|
|
}
|
|
|
|
func isOddPub(key *btcec.PublicKey) bool {
|
|
return key.SerializeCompressed()[0] == secp.PubKeyFormatCompressedOdd
|
|
}
|