From d84a98e3dbe1400de9a42446f3650ef83a719a7e Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Wed, 31 May 2023 08:14:08 +0200 Subject: [PATCH] watchtower/blob: add taprootJusticeKit implementation --- watchtower/blob/justice_kit.go | 224 +++++++++++++++++++++++++- watchtower/blob/justice_kit_packet.go | 9 ++ 2 files changed, 228 insertions(+), 5 deletions(-) diff --git a/watchtower/blob/justice_kit.go b/watchtower/blob/justice_kit.go index 7741ff026..8b6c20194 100644 --- a/watchtower/blob/justice_kit.go +++ b/watchtower/blob/justice_kit.go @@ -1,11 +1,15 @@ 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" @@ -69,11 +73,10 @@ func newLegacyJusticeKit(sweepScript []byte, keyRing := breachInfo.KeyRing packet := justiceKitPacketV0{ - sweepAddress: sweepScript, - revocationPubKey: toBlobPubKey(keyRing.RevocationKey), - localDelayPubKey: toBlobPubKey(keyRing.ToLocalKey), - csvDelay: breachInfo.RemoteDelay, - commitToRemotePubKey: pubKey{}, + sweepAddress: sweepScript, + revocationPubKey: toBlobPubKey(keyRing.RevocationKey), + localDelayPubKey: toBlobPubKey(keyRing.ToLocalKey), + csvDelay: breachInfo.RemoteDelay, } if withToRemote { @@ -286,3 +289,214 @@ func (a *anchorJusticeKit) ToRemoteOutputSpendInfo() (*txscript.PkScript, 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 +} diff --git a/watchtower/blob/justice_kit_packet.go b/watchtower/blob/justice_kit_packet.go index cd42124b8..fd799cd1f 100644 --- a/watchtower/blob/justice_kit_packet.go +++ b/watchtower/blob/justice_kit_packet.go @@ -9,6 +9,7 @@ import ( "io" "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/schnorr" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/lightningnetwork/lnd/lnwire" "golang.org/x/crypto/chacha20poly1305" @@ -93,6 +94,14 @@ func Size(kit JusticeKit) int { // schnorrPubKey is a 32-byte serialized x-only public key. type schnorrPubKey [32]byte +// toBlobSchnorrPubKey serializes the given public key into a schnorrPubKey that +// can be set as a field on a JusticeKit. +func toBlobSchnorrPubKey(pubKey *btcec.PublicKey) schnorrPubKey { + var blobPubKey schnorrPubKey + copy(blobPubKey[:], schnorr.SerializePubKey(pubKey)) + return blobPubKey +} + // pubKey is a 33-byte, serialized compressed public key. type pubKey [33]byte