Skip to content

Commit 2cb2557

Browse files
committed
Enable signing a justice tx using the channel monitor
1 parent 04475c8 commit 2cb2557

File tree

2 files changed

+60
-2
lines changed

2 files changed

+60
-2
lines changed

lightning/src/chain/chainmonitor.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,17 @@ impl MonitorUpdateId {
9494
/// [`ChannelMonitorUpdateStatus::PermanentFailure`], in which case the channel will likely be
9595
/// closed without broadcasting the latest state. See
9696
/// [`ChannelMonitorUpdateStatus::PermanentFailure`] for more details.
97+
///
98+
/// Third-party watchtowers may be built as a part of an implementation of this trait, with the
99+
/// advantage that you can control whether to resume channel operation depending on if an update
100+
/// has been persisted to a watchtower. For this, you may find the following methods useful:
101+
/// [`ChannelMonitor::initial_counterparty_commitment_tx`],
102+
/// [`ChannelMonitor::counterparty_commitment_txs_from_update`],
103+
/// [`ChannelMonitor::sign_to_local_justice_tx`], [`TrustedCommitmentTransaction::revokeable_output_index`],
104+
/// [`TrustedCommitmentTransaction::build_to_local_justice_tx`].
105+
///
106+
/// [`TrustedCommitmentTransaction::revokeable_output_index`]: crate::ln::chan_utils::TrustedCommitmentTransaction::revokeable_output_index
107+
/// [`TrustedCommitmentTransaction::build_to_local_justice_tx`]: crate::ln::chan_utils::TrustedCommitmentTransaction::build_to_local_justice_tx
97108
pub trait Persist<ChannelSigner: WriteableEcdsaChannelSigner> {
98109
/// Persist a new channel's data in response to a [`chain::Watch::watch_channel`] call. This is
99110
/// called by [`ChannelManager`] for new channels, or may be called directly, e.g. on startup.

lightning/src/chain/channelmonitor.rs

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ use bitcoin::hash_types::{Txid, BlockHash, WPubkeyHash};
3131

3232
use bitcoin::secp256k1::{Secp256k1, ecdsa::Signature};
3333
use bitcoin::secp256k1::{SecretKey, PublicKey};
34-
use bitcoin::secp256k1;
34+
use bitcoin::{secp256k1, EcdsaSighashType};
3535

3636
use crate::ln::channel::INITIAL_COMMITMENT_NUMBER;
3737
use crate::ln::{PaymentHash, PaymentPreimage};
@@ -1430,7 +1430,8 @@ impl<Signer: WriteableEcdsaChannelSigner> ChannelMonitor<Signer> {
14301430
/// This is provided so that watchtower clients in the persistence pipeline are able to build
14311431
/// justice transactions for each counterparty commitment upon each update. It's intended to be
14321432
/// used within an implementation of [`Persist::update_persisted_channel`], which is provided
1433-
/// with a monitor and an update.
1433+
/// with a monitor and an update. Once revoked, signing a justice transaction can be done using
1434+
/// [`Self::sign_to_local_justice_tx`].
14341435
///
14351436
/// It is expected that a watchtower client may use this method to retrieve the latest counterparty
14361437
/// commitment transaction(s), and then hold the necessary data until a later update in which
@@ -1446,6 +1447,27 @@ impl<Signer: WriteableEcdsaChannelSigner> ChannelMonitor<Signer> {
14461447
self.inner.lock().unwrap().counterparty_commitment_txs_from_update(update)
14471448
}
14481449

1450+
/// Wrapper around [`EcdsaChannelSigner::sign_justice_revoked_output`] to make
1451+
/// signing the justice transaction easier for implementors of
1452+
/// [`chain::chainmonitor::Persist`]. On success this method returns the provided transaction
1453+
/// signing the input at `input_idx`. This method will only produce a valid signature for
1454+
/// a transaction spending the `to_local` output of a commitment transaction, i.e. this cannot
1455+
/// be used for revoked HTLC outputs.
1456+
///
1457+
/// `Value` is the value of the output being spent by the input at `input_idx`, committed
1458+
/// in the BIP 143 signature.
1459+
///
1460+
/// This method will only succeed if this monitor has received the revocation secret for the
1461+
/// provided `commitment_number`. If a commitment number is provided that does not correspond
1462+
/// to the commitment transaction being revoked, this will return a signed transaction, but
1463+
/// the signature will not be valid.
1464+
///
1465+
/// [`EcdsaChannelSigner::sign_justice_revoked_output`]: crate::sign::EcdsaChannelSigner::sign_justice_revoked_output
1466+
/// [`Persist`]: crate::chain::chainmonitor::Persist
1467+
pub fn sign_to_local_justice_tx(&self, justice_tx: Transaction, input_idx: usize, value: u64, commitment_number: u64) -> Result<Transaction, ()> {
1468+
self.inner.lock().unwrap().sign_to_local_justice_tx(justice_tx, input_idx, value, commitment_number)
1469+
}
1470+
14491471
pub(crate) fn get_min_seen_secret(&self) -> u64 {
14501472
self.inner.lock().unwrap().get_min_seen_secret()
14511473
}
@@ -2810,6 +2832,31 @@ impl<Signer: WriteableEcdsaChannelSigner> ChannelMonitorImpl<Signer> {
28102832
}).collect()
28112833
}
28122834

2835+
pub(crate) fn sign_to_local_justice_tx(
2836+
&self, mut justice_tx: Transaction, input_idx: usize, value: u64, commitment_number: u64
2837+
) -> Result<Transaction, ()> {
2838+
let secret = self.get_secret(commitment_number).ok_or(())?;
2839+
let per_commitment_key = SecretKey::from_slice(&secret).map_err(|_| ())?;
2840+
let their_per_commitment_point = PublicKey::from_secret_key(
2841+
&self.onchain_tx_handler.secp_ctx, &per_commitment_key);
2842+
2843+
let revocation_pubkey = chan_utils::derive_public_revocation_key(
2844+
&self.onchain_tx_handler.secp_ctx, &their_per_commitment_point,
2845+
&self.holder_revocation_basepoint);
2846+
let delayed_key = chan_utils::derive_public_key(&self.onchain_tx_handler.secp_ctx,
2847+
&their_per_commitment_point,
2848+
&self.counterparty_commitment_params.counterparty_delayed_payment_base_key);
2849+
let revokeable_redeemscript = chan_utils::get_revokeable_redeemscript(&revocation_pubkey,
2850+
self.counterparty_commitment_params.on_counterparty_tx_csv, &delayed_key);
2851+
2852+
let sig = self.onchain_tx_handler.signer.sign_justice_revoked_output(
2853+
&justice_tx, input_idx, value, &per_commitment_key, &self.onchain_tx_handler.secp_ctx)?;
2854+
justice_tx.input[input_idx].witness.push_bitcoin_signature(&sig.serialize_der(), EcdsaSighashType::All);
2855+
justice_tx.input[input_idx].witness.push(&[1u8]);
2856+
justice_tx.input[input_idx].witness.push(revokeable_redeemscript.as_bytes());
2857+
Ok(justice_tx)
2858+
}
2859+
28132860
/// Can only fail if idx is < get_min_seen_secret
28142861
fn get_secret(&self, idx: u64) -> Option<[u8; 32]> {
28152862
self.commitment_secrets.get_secret(idx)

0 commit comments

Comments
 (0)