Skip to content

Commit 7972740

Browse files
committed
Enforce that revocation can only occur after we validated a new commitment
1 parent a2b1906 commit 7972740

File tree

5 files changed

+55
-11
lines changed

5 files changed

+55
-11
lines changed

lightning/src/chain/keysinterface.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,11 @@ pub trait BaseSign {
212212
/// Note that the commitment number starts at (1 << 48) - 1 and counts backwards.
213213
// TODO: return a Result so we can signal a validation error
214214
fn release_commitment_secret(&self, idx: u64) -> [u8; 32];
215+
/// Validate the counterparty's signatures on the holder commitment transaction and HTLCs.
216+
///
217+
/// This is required in order for the signer to make sure that releasing a commitment
218+
/// secret won't leave us without a broadcastable holder transaction.
219+
fn validate_holder_commitment(&self, holder_tx: &HolderCommitmentTransaction);
215220
/// Gets the holder's channel public keys and basepoints
216221
fn pubkeys(&self) -> &ChannelPublicKeys;
217222
/// Gets an arbitrary identifier describing the set of keys which are provided back to you in
@@ -558,6 +563,9 @@ impl BaseSign for InMemorySigner {
558563
chan_utils::build_commitment_secret(&self.commitment_seed, idx)
559564
}
560565

566+
fn validate_holder_commitment(&self, _holder_tx: &HolderCommitmentTransaction) {
567+
}
568+
561569
fn pubkeys(&self) -> &ChannelPublicKeys { &self.holder_channel_pubkeys }
562570
fn channel_keys_id(&self) -> [u8; 32] { self.channel_keys_id }
563571

lightning/src/ln/channel.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1791,6 +1791,8 @@ impl<Signer: Sign> Channel<Signer> {
17911791
self.counterparty_funding_pubkey()
17921792
);
17931793

1794+
self.holder_signer.validate_holder_commitment(&holder_commitment_tx);
1795+
17941796
// Now that we're past error-generating stuff, update our local state:
17951797

17961798
let funding_redeemscript = self.get_funding_redeemscript();
@@ -1865,6 +1867,7 @@ impl<Signer: Sign> Channel<Signer> {
18651867
self.counterparty_funding_pubkey()
18661868
);
18671869

1870+
self.holder_signer.validate_holder_commitment(&holder_commitment_tx);
18681871

18691872
let funding_redeemscript = self.get_funding_redeemscript();
18701873
let funding_txo = self.get_funding_txo().unwrap();
@@ -2502,6 +2505,7 @@ impl<Signer: Sign> Channel<Signer> {
25022505
);
25032506

25042507
let next_per_commitment_point = self.holder_signer.get_per_commitment_point(self.cur_holder_commitment_transaction_number - 1, &self.secp_ctx);
2508+
self.holder_signer.validate_holder_commitment(&holder_commitment_tx);
25052509
let per_commitment_secret = self.holder_signer.release_commitment_secret(self.cur_holder_commitment_transaction_number + 1);
25062510

25072511
// Update state now that we've passed all the can-fail calls...

lightning/src/ln/functional_tests.rs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1301,6 +1301,9 @@ fn test_fee_spike_violation_fails_htlc() {
13011301
let chan_lock = nodes[0].node.channel_state.lock().unwrap();
13021302
let local_chan = chan_lock.by_id.get(&chan.2).unwrap();
13031303
let chan_signer = local_chan.get_signer();
1304+
// Make the signer believe we validated another commitment, so we can release the secret
1305+
chan_signer.get_enforcement_state().last_holder_commitment -= 1;
1306+
13041307
let pubkeys = chan_signer.pubkeys();
13051308
(pubkeys.revocation_basepoint, pubkeys.htlc_basepoint,
13061309
chan_signer.release_commitment_secret(INITIAL_COMMITMENT_NUMBER),
@@ -7981,7 +7984,7 @@ fn test_counterparty_raa_skip_no_crash() {
79817984
// commitment transaction, we would have happily carried on and provided them the next
79827985
// commitment transaction based on one RAA forward. This would probably eventually have led to
79837986
// channel closure, but it would not have resulted in funds loss. Still, our
7984-
// EnforcingSigner would have paniced as it doesn't like jumps into the future. Here, we
7987+
// EnforcingSigner would have panicked as it doesn't like jumps into the future. Here, we
79857988
// check simply that the channel is closed in response to such an RAA, but don't check whether
79867989
// we decide to punish our counterparty for revoking their funds (as we don't currently
79877990
// implement that).
@@ -7992,11 +7995,19 @@ fn test_counterparty_raa_skip_no_crash() {
79927995
let channel_id = create_announced_chan_between_nodes(&nodes, 0, 1, InitFeatures::known(), InitFeatures::known()).2;
79937996

79947997
let mut guard = nodes[0].node.channel_state.lock().unwrap();
7995-
let keys = &guard.by_id.get_mut(&channel_id).unwrap().get_signer();
7998+
let keys = guard.by_id.get_mut(&channel_id).unwrap().get_signer();
7999+
79968000
const INITIAL_COMMITMENT_NUMBER: u64 = (1 << 48) - 1;
8001+
8002+
// Make signer believe we got a counterparty signature, so that it allows the revocation
8003+
keys.get_enforcement_state().last_holder_commitment -= 1;
79978004
let per_commitment_secret = keys.release_commitment_secret(INITIAL_COMMITMENT_NUMBER);
8005+
79988006
// Must revoke without gaps
8007+
keys.get_enforcement_state().last_holder_commitment -= 1;
79998008
keys.release_commitment_secret(INITIAL_COMMITMENT_NUMBER - 1);
8009+
8010+
keys.get_enforcement_state().last_holder_commitment -= 1;
80008011
let next_per_commitment_point = PublicKey::from_secret_key(&Secp256k1::new(),
80018012
&SecretKey::from_slice(&keys.release_commitment_secret(INITIAL_COMMITMENT_NUMBER - 2)).unwrap());
80028013

lightning/src/ln/msgs.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ pub struct FundingCreated {
194194
pub funding_txid: Txid,
195195
/// The specific output index funding this channel
196196
pub funding_output_index: u16,
197-
/// The signature of the channel initiator (funder) on the funding transaction
197+
/// The signature of the channel initiator (funder) on the initial commitment transaction
198198
pub signature: Signature,
199199
}
200200

@@ -203,7 +203,7 @@ pub struct FundingCreated {
203203
pub struct FundingSigned {
204204
/// The channel ID
205205
pub channel_id: [u8; 32],
206-
/// The signature of the channel acceptor (fundee) on the funding transaction
206+
/// The signature of the channel acceptor (fundee) on the initial commitment transaction
207207
pub signature: Signature,
208208
}
209209

lightning/src/util/enforcing_trait_impls.rs

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ use io;
1515
use prelude::*;
1616
use core::cmp;
1717
use sync::{Mutex, Arc};
18+
#[cfg(test)] use sync::MutexGuard;
1819

1920
use bitcoin::blockdata::transaction::{Transaction, SigHashType};
2021
use bitcoin::util::bip143;
@@ -35,12 +36,17 @@ pub const INITIAL_REVOKED_COMMITMENT_NUMBER: u64 = 1 << 48;
3536
/// - When signing, the holder transaction has not been revoked
3637
/// - When revoking, the holder transaction has not been signed
3738
/// - The holder commitment number is monotonic and without gaps
39+
/// - The revoked holder commitment number is monotonic and without gaps
40+
/// - There is at least one unrevoked holder transaction at all times
3841
/// - The counterparty commitment number is monotonic and without gaps
3942
/// - The pre-derived keys and pre-built transaction in CommitmentTransaction were correctly built
4043
///
4144
/// Eventually we will probably want to expose a variant of this which would essentially
4245
/// be what you'd want to run on a hardware wallet.
4346
///
47+
/// Note that counterparty signatures on the holder transaction are not checked, but it should
48+
/// be in a complete implementation.
49+
///
4450
/// Note that before we do so we should ensure its serialization format has backwards- and
4551
/// forwards-compatibility prefix/suffixes!
4652
#[derive(Clone)]
@@ -74,6 +80,11 @@ impl EnforcingSigner {
7480
disable_revocation_policy_check
7581
}
7682
}
83+
84+
#[cfg(test)]
85+
pub fn get_enforcement_state(&self) -> MutexGuard<EnforcementState> {
86+
self.state.lock().unwrap()
87+
}
7788
}
7889

7990
impl BaseSign for EnforcingSigner {
@@ -84,12 +95,20 @@ impl BaseSign for EnforcingSigner {
8495
fn release_commitment_secret(&self, idx: u64) -> [u8; 32] {
8596
{
8697
let mut state = self.state.lock().unwrap();
87-
assert!(idx == state.revoked_commitment || idx == state.revoked_commitment - 1, "can only revoke the current or next unrevoked commitment - trying {}, revoked {}", idx, state.revoked_commitment);
88-
state.revoked_commitment = idx;
98+
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);
99+
assert!(idx > state.last_holder_commitment, "cannot revoke the last holder commitment - attempted to revoke {} last commitment {}", idx, state.last_holder_commitment);
100+
state.last_holder_revoked_commitment = idx;
89101
}
90102
self.inner.release_commitment_secret(idx)
91103
}
92104

105+
fn validate_holder_commitment(&self, holder_tx: &HolderCommitmentTransaction) {
106+
let mut state = self.state.lock().unwrap();
107+
let idx = holder_tx.commitment_number();
108+
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);
109+
state.last_holder_commitment = idx;
110+
}
111+
93112
fn pubkeys(&self) -> &ChannelPublicKeys { self.inner.pubkeys() }
94113
fn channel_keys_id(&self) -> [u8; 32] { self.inner.channel_keys_id() }
95114

@@ -116,10 +135,10 @@ impl BaseSign for EnforcingSigner {
116135

117136
let state = self.state.lock().unwrap();
118137
let commitment_number = trusted_tx.commitment_number();
119-
if state.revoked_commitment - 1 != commitment_number && state.revoked_commitment - 2 != commitment_number {
138+
if state.last_holder_revoked_commitment - 1 != commitment_number && state.last_holder_revoked_commitment - 2 != commitment_number {
120139
if !self.disable_revocation_policy_check {
121140
panic!("can only sign the next two unrevoked commitment numbers, revoked={} vs requested={} for {}",
122-
state.revoked_commitment, commitment_number, self.inner.commitment_seed[0])
141+
state.last_holder_revoked_commitment, commitment_number, self.inner.commitment_seed[0])
123142
}
124143
}
125144

@@ -212,16 +231,18 @@ pub struct EnforcementState {
212231
/// The last counterparty commitment number we signed, backwards counting
213232
pub last_counterparty_commitment: u64,
214233
/// The last holder commitment number we revoked, backwards counting
215-
pub revoked_commitment: u64,
216-
234+
pub last_holder_revoked_commitment: u64,
235+
/// The last validated holder commitment number, backwards counting
236+
pub last_holder_commitment: u64,
217237
}
218238

219239
impl EnforcementState {
220240
/// Enforcement state for a new channel
221241
pub fn new() -> Self {
222242
EnforcementState {
223243
last_counterparty_commitment: INITIAL_REVOKED_COMMITMENT_NUMBER,
224-
revoked_commitment: INITIAL_REVOKED_COMMITMENT_NUMBER,
244+
last_holder_revoked_commitment: INITIAL_REVOKED_COMMITMENT_NUMBER,
245+
last_holder_commitment: INITIAL_REVOKED_COMMITMENT_NUMBER,
225246
}
226247
}
227248
}

0 commit comments

Comments
 (0)