mirror of
https://github.com/lightningnetwork/lnd.git
synced 2024-11-19 09:53:54 +01:00
1aae47fd71
In this commit, we update all the taproot scripts to also accept an optional aux leaf. This aux leaf can be used to add more redemption paths for advanced channels, or just as an extra commitment space.
508 lines
14 KiB
Go
508 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/fn"
|
|
"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
|
|
|
|
// TODO(roasbeef): aux leaf tower updates needed
|
|
|
|
tree, err := input.NewLocalCommitScriptTree(
|
|
breachInfo.RemoteDelay, keyRing.ToLocalKey,
|
|
keyRing.RevocationKey, fn.None[txscript.TapLeaf](),
|
|
)
|
|
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, fn.None[txscript.TapLeaf](),
|
|
)
|
|
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
|
|
}
|