Enable signing a justice tx using the channel monitor
authorAlec Chen <alecchendev@gmail.com>
Thu, 15 Jun 2023 03:58:10 +0000 (22:58 -0500)
committerAlec Chen <alecchendev@gmail.com>
Wed, 23 Aug 2023 17:33:11 +0000 (12:33 -0500)
lightning/src/chain/chainmonitor.rs
lightning/src/chain/channelmonitor.rs

index 16a02b54a3bbe4e848f194f376a541c6bafa860a..7d698a220676b8dfd7c9a0df41b0b09f0799e2e9 100644 (file)
@@ -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<ChannelSigner: WriteableEcdsaChannelSigner> {
        /// 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.
index 0c207fa8b17429b32e31f1eda65ea0270dcdad82..da3970da8eb887f6e33d3e9981eacdb77609e8f9 100644 (file)
@@ -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<Signer: WriteableEcdsaChannelSigner> ChannelMonitor<Signer> {
        /// 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<Signer: WriteableEcdsaChannelSigner> ChannelMonitor<Signer> {
                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<Transaction, ()> {
+               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<Signer: WriteableEcdsaChannelSigner> ChannelMonitorImpl<Signer> {
                }).collect()
        }
 
+       pub(crate) fn sign_to_local_justice_tx(
+               &self, mut justice_tx: Transaction, input_idx: usize, value: u64, commitment_number: u64
+       ) -> Result<Transaction, ()> {
+               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)