diff --git a/fuzz/src/chanmon_consistency.rs b/fuzz/src/chanmon_consistency.rs index 7eacf25365b..88826d65570 100644 --- a/fuzz/src/chanmon_consistency.rs +++ b/fuzz/src/chanmon_consistency.rs @@ -41,7 +41,7 @@ use lightning::ln::channel::FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE; use lightning::ln::features::{ChannelFeatures, InitFeatures, NodeFeatures}; use lightning::ln::msgs::{CommitmentUpdate, ChannelMessageHandler, DecodeError, UpdateAddHTLC, Init}; use lightning::ln::script::ShutdownScript; -use lightning::util::enforcing_trait_impls::{EnforcingSigner, INITIAL_REVOKED_COMMITMENT_NUMBER}; +use lightning::util::enforcing_trait_impls::{EnforcingSigner, EnforcementState}; use lightning::util::errors::APIError; use lightning::util::events; use lightning::util::logger::Logger; @@ -161,7 +161,7 @@ impl chain::Watch for TestChainMonitor { struct KeyProvider { node_id: u8, rand_bytes_id: atomic::AtomicU32, - revoked_commitments: Mutex>>>, + enforcement_states: Mutex>>>, } impl KeysInterface for KeyProvider { type Signer = EnforcingSigner; @@ -198,7 +198,7 @@ impl KeysInterface for KeyProvider { channel_value_satoshis, [0; 32], ); - let revoked_commitment = self.make_revoked_commitment_cell(keys.commitment_seed); + let revoked_commitment = self.make_enforcement_state_cell(keys.commitment_seed); EnforcingSigner::new_with_revoked(keys, revoked_commitment, false) } @@ -213,14 +213,11 @@ impl KeysInterface for KeyProvider { let mut reader = std::io::Cursor::new(buffer); let inner: InMemorySigner = Readable::read(&mut reader)?; - let revoked_commitment = self.make_revoked_commitment_cell(inner.commitment_seed); - - let last_commitment_number = Readable::read(&mut reader)?; + let state = self.make_enforcement_state_cell(inner.commitment_seed); Ok(EnforcingSigner { inner, - last_commitment_number: Arc::new(Mutex::new(last_commitment_number)), - revoked_commitment, + state, disable_revocation_policy_check: false, }) } @@ -231,10 +228,10 @@ impl KeysInterface for KeyProvider { } impl KeyProvider { - fn make_revoked_commitment_cell(&self, commitment_seed: [u8; 32]) -> Arc> { - let mut revoked_commitments = self.revoked_commitments.lock().unwrap(); + fn make_enforcement_state_cell(&self, commitment_seed: [u8; 32]) -> Arc> { + let mut revoked_commitments = self.enforcement_states.lock().unwrap(); if !revoked_commitments.contains_key(&commitment_seed) { - revoked_commitments.insert(commitment_seed, Arc::new(Mutex::new(INITIAL_REVOKED_COMMITMENT_NUMBER))); + revoked_commitments.insert(commitment_seed, Arc::new(Mutex::new(EnforcementState::new()))); } let cell = revoked_commitments.get(&commitment_seed).unwrap(); Arc::clone(cell) @@ -351,7 +348,7 @@ pub fn do_test(data: &[u8], out: Out) { macro_rules! make_node { ($node_id: expr, $fee_estimator: expr) => { { let logger: Arc = Arc::new(test_logger::TestLogger::new($node_id.to_string(), out.clone())); - let keys_manager = Arc::new(KeyProvider { node_id: $node_id, rand_bytes_id: atomic::AtomicU32::new(0), revoked_commitments: Mutex::new(HashMap::new()) }); + let keys_manager = Arc::new(KeyProvider { node_id: $node_id, rand_bytes_id: atomic::AtomicU32::new(0), enforcement_states: Mutex::new(HashMap::new()) }); let monitor = Arc::new(TestChainMonitor::new(broadcast.clone(), logger.clone(), $fee_estimator.clone(), Arc::new(TestPersister{}), Arc::clone(&keys_manager))); let mut config = UserConfig::default(); diff --git a/fuzz/src/full_stack.rs b/fuzz/src/full_stack.rs index 510966f0d4a..28592ffda51 100644 --- a/fuzz/src/full_stack.rs +++ b/fuzz/src/full_stack.rs @@ -42,7 +42,7 @@ use lightning::routing::network_graph::NetGraphMsgHandler; use lightning::util::config::UserConfig; use lightning::util::errors::APIError; use lightning::util::events::Event; -use lightning::util::enforcing_trait_impls::EnforcingSigner; +use lightning::util::enforcing_trait_impls::{EnforcingSigner, EnforcementState}; use lightning::util::logger::Logger; use lightning::util::ser::Readable; @@ -315,8 +315,15 @@ impl KeysInterface for KeyProvider { (ctr >> 8*7) as u8, (ctr >> 8*6) as u8, (ctr >> 8*5) as u8, (ctr >> 8*4) as u8, (ctr >> 8*3) as u8, (ctr >> 8*2) as u8, (ctr >> 8*1) as u8, 14, (ctr >> 8*0) as u8] } - fn read_chan_signer(&self, data: &[u8]) -> Result { - EnforcingSigner::read(&mut std::io::Cursor::new(data)) + fn read_chan_signer(&self, mut data: &[u8]) -> Result { + let inner: InMemorySigner = Readable::read(&mut data)?; + let state = Arc::new(Mutex::new(EnforcementState::new())); + + Ok(EnforcingSigner::new_with_revoked( + inner, + state, + false + )) } fn sign_invoice(&self, _invoice_preimage: Vec) -> Result { diff --git a/lightning/src/chain/keysinterface.rs b/lightning/src/chain/keysinterface.rs index c2e78a79394..44a03d09f8e 100644 --- a/lightning/src/chain/keysinterface.rs +++ b/lightning/src/chain/keysinterface.rs @@ -212,6 +212,13 @@ pub trait BaseSign { /// Note that the commitment number starts at (1 << 48) - 1 and counts backwards. // TODO: return a Result so we can signal a validation error fn release_commitment_secret(&self, idx: u64) -> [u8; 32]; + /// Validate the counterparty's signatures on the holder commitment transaction and HTLCs. + /// + /// This is required in order for the signer to make sure that releasing a commitment + /// secret won't leave us without a broadcastable holder transaction. + /// Policy checks should be implemented in this function, including checking the amount + /// sent to us and checking the HTLCs. + fn validate_holder_commitment(&self, holder_tx: &HolderCommitmentTransaction) -> Result<(), ()>; /// Gets the holder's channel public keys and basepoints fn pubkeys(&self) -> &ChannelPublicKeys; /// Gets an arbitrary identifier describing the set of keys which are provided back to you in @@ -222,9 +229,17 @@ pub trait BaseSign { /// Create a signature for a counterparty's commitment transaction and associated HTLC transactions. /// /// Note that if signing fails or is rejected, the channel will be force-closed. + /// + /// Policy checks should be implemented in this function, including checking the amount + /// sent to us and checking the HTLCs. // // TODO: Document the things someone using this interface should enforce before signing. fn sign_counterparty_commitment(&self, commitment_tx: &CommitmentTransaction, secp_ctx: &Secp256k1) -> Result<(Signature, Vec), ()>; + /// Validate the counterparty's revocation. + /// + /// This is required in order for the signer to make sure that the state has moved + /// forward and it is safe to sign the next counterparty commitment. + fn validate_counterparty_revocation(&self, idx: u64, secret: &SecretKey) -> Result<(), ()>; /// Create a signatures for a holder's commitment transaction and its claiming HTLC transactions. /// This will only ever be called with a non-revoked commitment_tx. This will be called with the @@ -558,6 +573,10 @@ impl BaseSign for InMemorySigner { chan_utils::build_commitment_secret(&self.commitment_seed, idx) } + fn validate_holder_commitment(&self, _holder_tx: &HolderCommitmentTransaction) -> Result<(), ()> { + Ok(()) + } + fn pubkeys(&self) -> &ChannelPublicKeys { &self.holder_channel_pubkeys } fn channel_keys_id(&self) -> [u8; 32] { self.channel_keys_id } @@ -584,6 +603,10 @@ impl BaseSign for InMemorySigner { Ok((commitment_sig, htlc_sigs)) } + fn validate_counterparty_revocation(&self, _idx: u64, _secret: &SecretKey) -> Result<(), ()> { + Ok(()) + } + fn sign_holder_commitment_and_htlcs(&self, commitment_tx: &HolderCommitmentTransaction, secp_ctx: &Secp256k1) -> Result<(Signature, Vec), ()> { let funding_pubkey = PublicKey::from_secret_key(secp_ctx, &self.funding_key); let funding_redeemscript = make_funding_redeemscript(&funding_pubkey, &self.counterparty_pubkeys().funding_pubkey); diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index 03968118a16..e2a19618be5 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -1791,6 +1791,9 @@ impl Channel { self.counterparty_funding_pubkey() ); + self.holder_signer.validate_holder_commitment(&holder_commitment_tx) + .map_err(|_| ChannelError::Close("Failed to validate our commitment".to_owned()))?; + // Now that we're past error-generating stuff, update our local state: let funding_redeemscript = self.get_funding_redeemscript(); @@ -1865,6 +1868,9 @@ impl Channel { self.counterparty_funding_pubkey() ); + self.holder_signer.validate_holder_commitment(&holder_commitment_tx) + .map_err(|_| ChannelError::Close("Failed to validate our commitment".to_owned()))?; + let funding_redeemscript = self.get_funding_redeemscript(); let funding_txo = self.get_funding_txo().unwrap(); @@ -2502,6 +2508,8 @@ impl Channel { ); let next_per_commitment_point = self.holder_signer.get_per_commitment_point(self.cur_holder_commitment_transaction_number - 1, &self.secp_ctx); + self.holder_signer.validate_holder_commitment(&holder_commitment_tx) + .map_err(|_| (None, ChannelError::Close("Failed to validate our commitment".to_owned())))?; let per_commitment_secret = self.holder_signer.release_commitment_secret(self.cur_holder_commitment_transaction_number + 1); // Update state now that we've passed all the can-fail calls... @@ -2738,8 +2746,10 @@ impl Channel { return Err(ChannelError::Close("Peer sent revoke_and_ack after we'd started exchanging closing_signeds".to_owned())); } + let secret = secp_check!(SecretKey::from_slice(&msg.per_commitment_secret), "Peer provided an invalid per_commitment_secret".to_owned()); + if let Some(counterparty_prev_commitment_point) = self.counterparty_prev_commitment_point { - if PublicKey::from_secret_key(&self.secp_ctx, &secp_check!(SecretKey::from_slice(&msg.per_commitment_secret), "Peer provided an invalid per_commitment_secret".to_owned())) != counterparty_prev_commitment_point { + if PublicKey::from_secret_key(&self.secp_ctx, &secret) != counterparty_prev_commitment_point { return Err(ChannelError::Close("Got a revoke commitment secret which didn't correspond to their current pubkey".to_owned())); } } @@ -2761,6 +2771,11 @@ impl Channel { *self.next_remote_commitment_tx_fee_info_cached.lock().unwrap() = None; } + self.holder_signer.validate_counterparty_revocation( + self.cur_counterparty_commitment_transaction_number + 1, + &secret + ).map_err(|_| ChannelError::Close("Failed to validate revocation from peer".to_owned()))?; + self.commitment_secrets.provide_secret(self.cur_counterparty_commitment_transaction_number + 1, msg.per_commitment_secret) .map_err(|_| ChannelError::Close("Previous secrets did not match new one".to_owned()))?; self.latest_monitor_update_id += 1; diff --git a/lightning/src/ln/functional_tests.rs b/lightning/src/ln/functional_tests.rs index 1328939570e..7391523485d 100644 --- a/lightning/src/ln/functional_tests.rs +++ b/lightning/src/ln/functional_tests.rs @@ -1301,6 +1301,9 @@ fn test_fee_spike_violation_fails_htlc() { let chan_lock = nodes[0].node.channel_state.lock().unwrap(); let local_chan = chan_lock.by_id.get(&chan.2).unwrap(); let chan_signer = local_chan.get_signer(); + // Make the signer believe we validated another commitment, so we can release the secret + chan_signer.get_enforcement_state().last_holder_commitment -= 1; + let pubkeys = chan_signer.pubkeys(); (pubkeys.revocation_basepoint, pubkeys.htlc_basepoint, chan_signer.release_commitment_secret(INITIAL_COMMITMENT_NUMBER), @@ -7981,7 +7984,7 @@ fn test_counterparty_raa_skip_no_crash() { // commitment transaction, we would have happily carried on and provided them the next // commitment transaction based on one RAA forward. This would probably eventually have led to // channel closure, but it would not have resulted in funds loss. Still, our - // EnforcingSigner would have paniced as it doesn't like jumps into the future. Here, we + // EnforcingSigner would have panicked as it doesn't like jumps into the future. Here, we // check simply that the channel is closed in response to such an RAA, but don't check whether // we decide to punish our counterparty for revoking their funds (as we don't currently // implement that). @@ -7992,11 +7995,19 @@ fn test_counterparty_raa_skip_no_crash() { let channel_id = create_announced_chan_between_nodes(&nodes, 0, 1, InitFeatures::known(), InitFeatures::known()).2; let mut guard = nodes[0].node.channel_state.lock().unwrap(); - let keys = &guard.by_id.get_mut(&channel_id).unwrap().get_signer(); + let keys = guard.by_id.get_mut(&channel_id).unwrap().get_signer(); + const INITIAL_COMMITMENT_NUMBER: u64 = (1 << 48) - 1; + + // Make signer believe we got a counterparty signature, so that it allows the revocation + keys.get_enforcement_state().last_holder_commitment -= 1; let per_commitment_secret = keys.release_commitment_secret(INITIAL_COMMITMENT_NUMBER); + // Must revoke without gaps + keys.get_enforcement_state().last_holder_commitment -= 1; keys.release_commitment_secret(INITIAL_COMMITMENT_NUMBER - 1); + + keys.get_enforcement_state().last_holder_commitment -= 1; let next_per_commitment_point = PublicKey::from_secret_key(&Secp256k1::new(), &SecretKey::from_slice(&keys.release_commitment_secret(INITIAL_COMMITMENT_NUMBER - 2)).unwrap()); diff --git a/lightning/src/ln/msgs.rs b/lightning/src/ln/msgs.rs index f94909a9ae5..5b49f43b118 100644 --- a/lightning/src/ln/msgs.rs +++ b/lightning/src/ln/msgs.rs @@ -194,7 +194,7 @@ pub struct FundingCreated { pub funding_txid: Txid, /// The specific output index funding this channel pub funding_output_index: u16, - /// The signature of the channel initiator (funder) on the funding transaction + /// The signature of the channel initiator (funder) on the initial commitment transaction pub signature: Signature, } @@ -203,7 +203,7 @@ pub struct FundingCreated { pub struct FundingSigned { /// The channel ID pub channel_id: [u8; 32], - /// The signature of the channel acceptor (fundee) on the funding transaction + /// The signature of the channel acceptor (fundee) on the initial commitment transaction pub signature: Signature, } diff --git a/lightning/src/util/enforcing_trait_impls.rs b/lightning/src/util/enforcing_trait_impls.rs index baa9e7cd16d..b7dfe767057 100644 --- a/lightning/src/util/enforcing_trait_impls.rs +++ b/lightning/src/util/enforcing_trait_impls.rs @@ -11,10 +11,10 @@ use ln::chan_utils::{HTLCOutputInCommitment, ChannelPublicKeys, HolderCommitment use ln::{chan_utils, msgs}; use chain::keysinterface::{Sign, InMemorySigner, BaseSign}; -use io; use prelude::*; use core::cmp; use sync::{Mutex, Arc}; +#[cfg(test)] use sync::MutexGuard; use bitcoin::blockdata::transaction::{Transaction, SigHashType}; use bitcoin::util::bip143; @@ -22,9 +22,8 @@ use bitcoin::util::bip143; use bitcoin::secp256k1; use bitcoin::secp256k1::key::{SecretKey, PublicKey}; use bitcoin::secp256k1::{Secp256k1, Signature}; -use util::ser::{Writeable, Writer, Readable}; +use util::ser::{Writeable, Writer}; use io::Error; -use ln::msgs::DecodeError; /// Initial value for revoked commitment downward counter pub const INITIAL_REVOKED_COMMITMENT_NUMBER: u64 = 1 << 48; @@ -35,31 +34,34 @@ pub const INITIAL_REVOKED_COMMITMENT_NUMBER: u64 = 1 << 48; /// - When signing, the holder transaction has not been revoked /// - When revoking, the holder transaction has not been signed /// - The holder commitment number is monotonic and without gaps +/// - The revoked holder commitment number is monotonic and without gaps +/// - There is at least one unrevoked holder transaction at all times /// - The counterparty commitment number is monotonic and without gaps /// - The pre-derived keys and pre-built transaction in CommitmentTransaction were correctly built /// /// Eventually we will probably want to expose a variant of this which would essentially /// be what you'd want to run on a hardware wallet. /// +/// Note that counterparty signatures on the holder transaction are not checked, but it should +/// be in a complete implementation. +/// /// Note that before we do so we should ensure its serialization format has backwards- and /// forwards-compatibility prefix/suffixes! #[derive(Clone)] pub struct EnforcingSigner { pub inner: InMemorySigner, - /// The last counterparty commitment number we signed, backwards counting - pub last_commitment_number: Arc>>, - /// The last holder commitment number we revoked, backwards counting - pub revoked_commitment: Arc>, + /// Channel state used for policy enforcement + pub state: Arc>, pub disable_revocation_policy_check: bool, } impl EnforcingSigner { /// Construct an EnforcingSigner pub fn new(inner: InMemorySigner) -> Self { + let state = Arc::new(Mutex::new(EnforcementState::new())); Self { inner, - last_commitment_number: Arc::new(Mutex::new(None)), - revoked_commitment: Arc::new(Mutex::new(INITIAL_REVOKED_COMMITMENT_NUMBER)), + state, disable_revocation_policy_check: false } } @@ -67,16 +69,20 @@ impl EnforcingSigner { /// Construct an EnforcingSigner with externally managed storage /// /// Since there are multiple copies of this struct for each channel, some coordination is needed - /// so that all copies are aware of revocations. A pointer to this state is provided here, usually - /// by an implementation of KeysInterface. - pub fn new_with_revoked(inner: InMemorySigner, revoked_commitment: Arc>, disable_revocation_policy_check: bool) -> Self { + /// so that all copies are aware of enforcement state. A pointer to this state is provided + /// here, usually by an implementation of KeysInterface. + pub fn new_with_revoked(inner: InMemorySigner, state: Arc>, disable_revocation_policy_check: bool) -> Self { Self { inner, - last_commitment_number: Arc::new(Mutex::new(None)), - revoked_commitment, + state, disable_revocation_policy_check } } + + #[cfg(test)] + pub fn get_enforcement_state(&self) -> MutexGuard { + self.state.lock().unwrap() + } } impl BaseSign for EnforcingSigner { @@ -86,13 +92,22 @@ impl BaseSign for EnforcingSigner { fn release_commitment_secret(&self, idx: u64) -> [u8; 32] { { - let mut revoked = self.revoked_commitment.lock().unwrap(); - assert!(idx == *revoked || idx == *revoked - 1, "can only revoke the current or next unrevoked commitment - trying {}, revoked {}", idx, *revoked); - *revoked = idx; + let mut state = self.state.lock().unwrap(); + assert!(idx == state.last_holder_revoked_commitment || idx == state.last_holder_revoked_commitment - 1, "can only revoke the current or next unrevoked commitment - trying {}, last revoked {}", idx, state.last_holder_revoked_commitment); + assert!(idx > state.last_holder_commitment, "cannot revoke the last holder commitment - attempted to revoke {} last commitment {}", idx, state.last_holder_commitment); + state.last_holder_revoked_commitment = idx; } self.inner.release_commitment_secret(idx) } + fn validate_holder_commitment(&self, holder_tx: &HolderCommitmentTransaction) -> Result<(), ()> { + let mut state = self.state.lock().unwrap(); + let idx = holder_tx.commitment_number(); + assert!(idx == state.last_holder_commitment || idx == state.last_holder_commitment - 1, "expecting to validate the current or next holder commitment - trying {}, current {}", idx, state.last_holder_commitment); + state.last_holder_commitment = idx; + Ok(()) + } + fn pubkeys(&self) -> &ChannelPublicKeys { self.inner.pubkeys() } fn channel_keys_id(&self) -> [u8; 32] { self.inner.channel_keys_id() } @@ -100,29 +115,39 @@ impl BaseSign for EnforcingSigner { self.verify_counterparty_commitment_tx(commitment_tx, secp_ctx); { - let mut last_commitment_number_guard = self.last_commitment_number.lock().unwrap(); + let mut state = self.state.lock().unwrap(); let actual_commitment_number = commitment_tx.commitment_number(); - let last_commitment_number = last_commitment_number_guard.unwrap_or(actual_commitment_number); + let last_commitment_number = state.last_counterparty_commitment; // These commitment numbers are backwards counting. We expect either the same as the previously encountered, // or the next one. assert!(last_commitment_number == actual_commitment_number || last_commitment_number - 1 == actual_commitment_number, "{} doesn't come after {}", actual_commitment_number, last_commitment_number); - *last_commitment_number_guard = Some(cmp::min(last_commitment_number, actual_commitment_number)) + // Ensure that the counterparty doesn't get more than two broadcastable commitments - + // the last and the one we are trying to sign + assert!(actual_commitment_number >= state.last_counterparty_revoked_commitment - 2, "cannot sign a commitment if second to last wasn't revoked - signing {} revoked {}", actual_commitment_number, state.last_counterparty_revoked_commitment); + state.last_counterparty_commitment = cmp::min(last_commitment_number, actual_commitment_number) } Ok(self.inner.sign_counterparty_commitment(commitment_tx, secp_ctx).unwrap()) } + fn validate_counterparty_revocation(&self, idx: u64, _secret: &SecretKey) -> Result<(), ()> { + let mut state = self.state.lock().unwrap(); + assert!(idx == state.last_counterparty_revoked_commitment || idx == state.last_counterparty_revoked_commitment - 1, "expecting to validate the current or next counterparty revocation - trying {}, current {}", idx, state.last_counterparty_revoked_commitment); + state.last_counterparty_revoked_commitment = idx; + Ok(()) + } + fn sign_holder_commitment_and_htlcs(&self, commitment_tx: &HolderCommitmentTransaction, secp_ctx: &Secp256k1) -> Result<(Signature, Vec), ()> { let trusted_tx = self.verify_holder_commitment_tx(commitment_tx, secp_ctx); let commitment_txid = trusted_tx.txid(); let holder_csv = self.inner.counterparty_selected_contest_delay(); - let revoked = self.revoked_commitment.lock().unwrap(); + let state = self.state.lock().unwrap(); let commitment_number = trusted_tx.commitment_number(); - if *revoked - 1 != commitment_number && *revoked - 2 != commitment_number { + if state.last_holder_revoked_commitment - 1 != commitment_number && state.last_holder_revoked_commitment - 2 != commitment_number { if !self.disable_revocation_policy_check { panic!("can only sign the next two unrevoked commitment numbers, revoked={} vs requested={} for {}", - *revoked, commitment_number, self.inner.commitment_seed[0]) + state.last_holder_revoked_commitment, commitment_number, self.inner.commitment_seed[0]) } } @@ -174,26 +199,15 @@ impl Sign for EnforcingSigner {} impl Writeable for EnforcingSigner { fn write(&self, writer: &mut W) -> Result<(), Error> { + // EnforcingSigner has two fields - `inner` ([`InMemorySigner`]) and `state` + // ([`EnforcementState`]). `inner` is serialized here and deserialized by + // [`KeysInterface::read_chan_signer`]. `state` is managed by [`KeysInterface`] + // and will be serialized as needed by the implementation of that trait. self.inner.write(writer)?; - let last = *self.last_commitment_number.lock().unwrap(); - last.write(writer)?; Ok(()) } } -impl Readable for EnforcingSigner { - fn read(reader: &mut R) -> Result { - let inner = Readable::read(reader)?; - let last_commitment_number = Readable::read(reader)?; - Ok(EnforcingSigner { - inner, - last_commitment_number: Arc::new(Mutex::new(last_commitment_number)), - revoked_commitment: Arc::new(Mutex::new(INITIAL_REVOKED_COMMITMENT_NUMBER)), - disable_revocation_policy_check: false, - }) - } -} - impl EnforcingSigner { fn verify_counterparty_commitment_tx<'a, T: secp256k1::Signing + secp256k1::Verification>(&self, commitment_tx: &'a CommitmentTransaction, secp_ctx: &Secp256k1) -> TrustedCommitmentTransaction<'a> { commitment_tx.verify(&self.inner.get_channel_parameters().as_counterparty_broadcastable(), @@ -207,3 +221,31 @@ impl EnforcingSigner { .expect("derived different per-tx keys or built transaction") } } + +/// The state used by [`EnforcingSigner`] in order to enforce policy checks +/// +/// This structure is maintained by KeysInterface since we may have multiple copies of +/// the signer and they must coordinate their state. +#[derive(Clone)] +pub struct EnforcementState { + /// The last counterparty commitment number we signed, backwards counting + pub last_counterparty_commitment: u64, + /// The last counterparty commitment they revoked, backwards counting + pub last_counterparty_revoked_commitment: u64, + /// The last holder commitment number we revoked, backwards counting + pub last_holder_revoked_commitment: u64, + /// The last validated holder commitment number, backwards counting + pub last_holder_commitment: u64, +} + +impl EnforcementState { + /// Enforcement state for a new channel + pub fn new() -> Self { + EnforcementState { + last_counterparty_commitment: INITIAL_REVOKED_COMMITMENT_NUMBER, + last_counterparty_revoked_commitment: INITIAL_REVOKED_COMMITMENT_NUMBER, + last_holder_revoked_commitment: INITIAL_REVOKED_COMMITMENT_NUMBER, + last_holder_commitment: INITIAL_REVOKED_COMMITMENT_NUMBER, + } + } +} diff --git a/lightning/src/util/test_utils.rs b/lightning/src/util/test_utils.rs index 15157e50e55..64b88acb008 100644 --- a/lightning/src/util/test_utils.rs +++ b/lightning/src/util/test_utils.rs @@ -20,7 +20,7 @@ use ln::features::{ChannelFeatures, InitFeatures}; use ln::msgs; use ln::msgs::OptionalField; use ln::script::ShutdownScript; -use util::enforcing_trait_impls::{EnforcingSigner, INITIAL_REVOKED_COMMITMENT_NUMBER}; +use util::enforcing_trait_impls::{EnforcingSigner, EnforcementState}; use util::events; use util::logger::{Logger, Level, Record}; use util::ser::{Readable, ReadableArgs, Writer, Writeable}; @@ -76,8 +76,15 @@ impl keysinterface::KeysInterface for OnlyReadsKeysInterface { fn get_channel_signer(&self, _inbound: bool, _channel_value_satoshis: u64) -> EnforcingSigner { unreachable!(); } fn get_secure_random_bytes(&self) -> [u8; 32] { [0; 32] } - fn read_chan_signer(&self, reader: &[u8]) -> Result { - EnforcingSigner::read(&mut io::Cursor::new(reader)) + fn read_chan_signer(&self, mut reader: &[u8]) -> Result { + let inner: InMemorySigner = Readable::read(&mut reader)?; + let state = Arc::new(Mutex::new(EnforcementState::new())); + + Ok(EnforcingSigner::new_with_revoked( + inner, + state, + false + )) } fn sign_invoice(&self, _invoice_preimage: Vec) -> Result { unreachable!(); } } @@ -452,7 +459,7 @@ pub struct TestKeysInterface { pub override_session_priv: Mutex>, pub override_channel_id_priv: Mutex>, pub disable_revocation_policy_check: bool, - revoked_commitments: Mutex>>>, + enforcement_states: Mutex>>>, expectations: Mutex>>, } @@ -474,8 +481,8 @@ impl keysinterface::KeysInterface for TestKeysInterface { fn get_channel_signer(&self, inbound: bool, channel_value_satoshis: u64) -> EnforcingSigner { let keys = self.backing.get_channel_signer(inbound, channel_value_satoshis); - let revoked_commitment = self.make_revoked_commitment_cell(keys.commitment_seed); - EnforcingSigner::new_with_revoked(keys, revoked_commitment, self.disable_revocation_policy_check) + let state = self.make_enforcement_state_cell(keys.commitment_seed); + EnforcingSigner::new_with_revoked(keys, state, self.disable_revocation_policy_check) } fn get_secure_random_bytes(&self) -> [u8; 32] { @@ -497,16 +504,13 @@ impl keysinterface::KeysInterface for TestKeysInterface { let mut reader = io::Cursor::new(buffer); let inner: InMemorySigner = Readable::read(&mut reader)?; - let revoked_commitment = self.make_revoked_commitment_cell(inner.commitment_seed); - - let last_commitment_number = Readable::read(&mut reader)?; + let state = self.make_enforcement_state_cell(inner.commitment_seed); - Ok(EnforcingSigner { + Ok(EnforcingSigner::new_with_revoked( inner, - last_commitment_number: Arc::new(Mutex::new(last_commitment_number)), - revoked_commitment, - disable_revocation_policy_check: self.disable_revocation_policy_check, - }) + state, + self.disable_revocation_policy_check + )) } fn sign_invoice(&self, invoice_preimage: Vec) -> Result { @@ -522,7 +526,7 @@ impl TestKeysInterface { override_session_priv: Mutex::new(None), override_channel_id_priv: Mutex::new(None), disable_revocation_policy_check: false, - revoked_commitments: Mutex::new(HashMap::new()), + enforcement_states: Mutex::new(HashMap::new()), expectations: Mutex::new(None), } } @@ -538,16 +542,17 @@ impl TestKeysInterface { pub fn derive_channel_keys(&self, channel_value_satoshis: u64, id: &[u8; 32]) -> EnforcingSigner { let keys = self.backing.derive_channel_keys(channel_value_satoshis, id); - let revoked_commitment = self.make_revoked_commitment_cell(keys.commitment_seed); - EnforcingSigner::new_with_revoked(keys, revoked_commitment, self.disable_revocation_policy_check) + let state = self.make_enforcement_state_cell(keys.commitment_seed); + EnforcingSigner::new_with_revoked(keys, state, self.disable_revocation_policy_check) } - fn make_revoked_commitment_cell(&self, commitment_seed: [u8; 32]) -> Arc> { - let mut revoked_commitments = self.revoked_commitments.lock().unwrap(); - if !revoked_commitments.contains_key(&commitment_seed) { - revoked_commitments.insert(commitment_seed, Arc::new(Mutex::new(INITIAL_REVOKED_COMMITMENT_NUMBER))); + fn make_enforcement_state_cell(&self, commitment_seed: [u8; 32]) -> Arc> { + let mut states = self.enforcement_states.lock().unwrap(); + if !states.contains_key(&commitment_seed) { + let state = EnforcementState::new(); + states.insert(commitment_seed, Arc::new(Mutex::new(state))); } - let cell = revoked_commitments.get(&commitment_seed).unwrap(); + let cell = states.get(&commitment_seed).unwrap(); Arc::clone(cell) } }