From 2cb25576697f368f1807d6a1f2db3d6086beb84b Mon Sep 17 00:00:00 2001 From: Alec Chen Date: Wed, 14 Jun 2023 22:58:10 -0500 Subject: [PATCH] Enable signing a justice tx using the channel monitor --- lightning/src/chain/chainmonitor.rs | 11 ++++++ lightning/src/chain/channelmonitor.rs | 51 +++++++++++++++++++++++++-- 2 files changed, 60 insertions(+), 2 deletions(-) diff --git a/lightning/src/chain/chainmonitor.rs b/lightning/src/chain/chainmonitor.rs index 16a02b54a..7d698a220 100644 --- a/lightning/src/chain/chainmonitor.rs +++ b/lightning/src/chain/chainmonitor.rs @@ -94,6 +94,17 @@ impl MonitorUpdateId { /// [`ChannelMonitorUpdateStatus::PermanentFailure`], in which case the channel will likely be /// closed without broadcasting the latest state. See /// [`ChannelMonitorUpdateStatus::PermanentFailure`] for more details. +/// +/// Third-party watchtowers may be built as a part of an implementation of this trait, with the +/// advantage that you can control whether to resume channel operation depending on if an update +/// has been persisted to a watchtower. For this, you may find the following methods useful: +/// [`ChannelMonitor::initial_counterparty_commitment_tx`], +/// [`ChannelMonitor::counterparty_commitment_txs_from_update`], +/// [`ChannelMonitor::sign_to_local_justice_tx`], [`TrustedCommitmentTransaction::revokeable_output_index`], +/// [`TrustedCommitmentTransaction::build_to_local_justice_tx`]. +/// +/// [`TrustedCommitmentTransaction::revokeable_output_index`]: crate::ln::chan_utils::TrustedCommitmentTransaction::revokeable_output_index +/// [`TrustedCommitmentTransaction::build_to_local_justice_tx`]: crate::ln::chan_utils::TrustedCommitmentTransaction::build_to_local_justice_tx pub trait Persist { /// Persist a new channel's data in response to a [`chain::Watch::watch_channel`] call. This is /// called by [`ChannelManager`] for new channels, or may be called directly, e.g. on startup. diff --git a/lightning/src/chain/channelmonitor.rs b/lightning/src/chain/channelmonitor.rs index 0c207fa8b..da3970da8 100644 --- a/lightning/src/chain/channelmonitor.rs +++ b/lightning/src/chain/channelmonitor.rs @@ -31,7 +31,7 @@ use bitcoin::hash_types::{Txid, BlockHash, WPubkeyHash}; use bitcoin::secp256k1::{Secp256k1, ecdsa::Signature}; use bitcoin::secp256k1::{SecretKey, PublicKey}; -use bitcoin::secp256k1; +use bitcoin::{secp256k1, EcdsaSighashType}; use crate::ln::channel::INITIAL_COMMITMENT_NUMBER; use crate::ln::{PaymentHash, PaymentPreimage}; @@ -1430,7 +1430,8 @@ impl ChannelMonitor { /// This is provided so that watchtower clients in the persistence pipeline are able to build /// justice transactions for each counterparty commitment upon each update. It's intended to be /// used within an implementation of [`Persist::update_persisted_channel`], which is provided - /// with a monitor and an update. + /// with a monitor and an update. Once revoked, signing a justice transaction can be done using + /// [`Self::sign_to_local_justice_tx`]. /// /// It is expected that a watchtower client may use this method to retrieve the latest counterparty /// commitment transaction(s), and then hold the necessary data until a later update in which @@ -1446,6 +1447,27 @@ impl ChannelMonitor { self.inner.lock().unwrap().counterparty_commitment_txs_from_update(update) } + /// Wrapper around [`EcdsaChannelSigner::sign_justice_revoked_output`] to make + /// signing the justice transaction easier for implementors of + /// [`chain::chainmonitor::Persist`]. On success this method returns the provided transaction + /// signing the input at `input_idx`. This method will only produce a valid signature for + /// a transaction spending the `to_local` output of a commitment transaction, i.e. this cannot + /// be used for revoked HTLC outputs. + /// + /// `Value` is the value of the output being spent by the input at `input_idx`, committed + /// in the BIP 143 signature. + /// + /// This method will only succeed if this monitor has received the revocation secret for the + /// provided `commitment_number`. If a commitment number is provided that does not correspond + /// to the commitment transaction being revoked, this will return a signed transaction, but + /// the signature will not be valid. + /// + /// [`EcdsaChannelSigner::sign_justice_revoked_output`]: crate::sign::EcdsaChannelSigner::sign_justice_revoked_output + /// [`Persist`]: crate::chain::chainmonitor::Persist + pub fn sign_to_local_justice_tx(&self, justice_tx: Transaction, input_idx: usize, value: u64, commitment_number: u64) -> Result { + self.inner.lock().unwrap().sign_to_local_justice_tx(justice_tx, input_idx, value, commitment_number) + } + pub(crate) fn get_min_seen_secret(&self) -> u64 { self.inner.lock().unwrap().get_min_seen_secret() } @@ -2810,6 +2832,31 @@ impl ChannelMonitorImpl { }).collect() } + pub(crate) fn sign_to_local_justice_tx( + &self, mut justice_tx: Transaction, input_idx: usize, value: u64, commitment_number: u64 + ) -> Result { + let secret = self.get_secret(commitment_number).ok_or(())?; + let per_commitment_key = SecretKey::from_slice(&secret).map_err(|_| ())?; + let their_per_commitment_point = PublicKey::from_secret_key( + &self.onchain_tx_handler.secp_ctx, &per_commitment_key); + + let revocation_pubkey = chan_utils::derive_public_revocation_key( + &self.onchain_tx_handler.secp_ctx, &their_per_commitment_point, + &self.holder_revocation_basepoint); + let delayed_key = chan_utils::derive_public_key(&self.onchain_tx_handler.secp_ctx, + &their_per_commitment_point, + &self.counterparty_commitment_params.counterparty_delayed_payment_base_key); + let revokeable_redeemscript = chan_utils::get_revokeable_redeemscript(&revocation_pubkey, + self.counterparty_commitment_params.on_counterparty_tx_csv, &delayed_key); + + let sig = self.onchain_tx_handler.signer.sign_justice_revoked_output( + &justice_tx, input_idx, value, &per_commitment_key, &self.onchain_tx_handler.secp_ctx)?; + justice_tx.input[input_idx].witness.push_bitcoin_signature(&sig.serialize_der(), EcdsaSighashType::All); + justice_tx.input[input_idx].witness.push(&[1u8]); + justice_tx.input[input_idx].witness.push(revokeable_redeemscript.as_bytes()); + Ok(justice_tx) + } + /// Can only fail if idx is < get_min_seen_secret fn get_secret(&self, idx: u64) -> Option<[u8; 32]> { self.commitment_secrets.get_secret(idx) -- 2.39.5