Skip to content

Commit 9714cb4

Browse files
committed
Randomize initial onion packet data.
This avoids at least the trivial hop count discovery attack, though other obvious ones remain and are slightly harder to avoid. See lightning/bolts#697
1 parent 65f7c9f commit 9714cb4

File tree

7 files changed

+35
-17
lines changed

7 files changed

+35
-17
lines changed

lightning/src/chain/keysinterface.rs

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,8 @@ pub trait KeysInterface: Send + Sync {
7777
/// Get a new set of ChannelKeys for per-channel secrets. These MUST be unique even if you
7878
/// restarted with some stale data!
7979
fn get_channel_keys(&self, inbound: bool) -> ChannelKeys;
80-
/// Get a secret for construting an onion packet
81-
fn get_session_key(&self) -> SecretKey;
80+
/// Get a secret and PRNG seed for construting an onion packet
81+
fn get_onion_rand(&self) -> (SecretKey, [u8; 32]);
8282
/// Get a unique temporary channel id. Channels will be referred to by this until the funding
8383
/// transaction is created, at which point they will use the outpoint in the funding
8484
/// transaction.
@@ -258,13 +258,20 @@ impl KeysInterface for KeysManager {
258258
}
259259
}
260260

261-
fn get_session_key(&self) -> SecretKey {
261+
fn get_onion_rand(&self) -> (SecretKey, [u8; 32]) {
262262
let mut sha = self.unique_start.clone();
263263

264264
let child_ix = self.session_child_index.fetch_add(1, Ordering::AcqRel);
265265
let child_privkey = self.session_master_key.ckd_priv(&self.secp_ctx, ChildNumber::from_hardened_idx(child_ix as u32).expect("key space exhausted")).expect("Your RNG is busted");
266266
sha.input(&child_privkey.private_key.key[..]);
267-
SecretKey::from_slice(&Sha256::from_engine(sha).into_inner()).expect("Your RNG is busted")
267+
268+
let mut rng_seed = sha.clone();
269+
// Not exactly the most ideal construction, but the second value will get fed into
270+
// ChaCha so it is another step harder to break.
271+
rng_seed.input(b"RNG Seed Salt");
272+
sha.input(b"Session Key Salt");
273+
(SecretKey::from_slice(&Sha256::from_engine(sha).into_inner()).expect("Your RNG is busted"),
274+
Sha256::from_engine(rng_seed).into_inner())
268275
}
269276

270277
fn get_channel_id(&self) -> [u8; 32] {

lightning/src/ln/channel.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4157,7 +4157,7 @@ mod tests {
41574157
}
41584158

41594159
fn get_channel_keys(&self, _inbound: bool) -> ChannelKeys { self.chan_keys.clone() }
4160-
fn get_session_key(&self) -> SecretKey { panic!(); }
4160+
fn get_onion_rand(&self) -> (SecretKey, [u8; 32]) { panic!(); }
41614161
fn get_channel_id(&self) -> [u8; 32] { [0; 32] }
41624162
}
41634163

lightning/src/ln/channelmanager.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1082,14 +1082,14 @@ impl<'a> ChannelManager<'a> {
10821082
}
10831083
}
10841084

1085-
let session_priv = self.keys_manager.get_session_key();
1085+
let (session_priv, prng_seed) = self.keys_manager.get_onion_rand();
10861086

10871087
let cur_height = self.latest_block_height.load(Ordering::Acquire) as u32 + 1;
10881088

10891089
let onion_keys = secp_call!(onion_utils::construct_onion_keys(&self.secp_ctx, &route, &session_priv),
10901090
APIError::RouteError{err: "Pubkey along hop was maliciously selected"});
10911091
let (onion_payloads, htlc_msat, htlc_cltv) = onion_utils::build_onion_payloads(&route, cur_height)?;
1092-
let onion_packet = onion_utils::construct_onion_packet(onion_payloads, onion_keys, &payment_hash);
1092+
let onion_packet = onion_utils::construct_onion_packet(onion_payloads, onion_keys, prng_seed, &payment_hash);
10931093

10941094
let _ = self.total_consistency_lock.read().unwrap();
10951095

lightning/src/ln/functional_tests.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1435,7 +1435,7 @@ fn do_channel_reserve_test(test_recv: bool) {
14351435
let cur_height = nodes[0].node.latest_block_height.load(Ordering::Acquire) as u32 + 1;
14361436
let onion_keys = onion_utils::construct_onion_keys(&secp_ctx, &route, &session_priv).unwrap();
14371437
let (onion_payloads, htlc_msat, htlc_cltv) = onion_utils::build_onion_payloads(&route, cur_height).unwrap();
1438-
let onion_packet = onion_utils::construct_onion_packet(onion_payloads, onion_keys, &our_payment_hash);
1438+
let onion_packet = onion_utils::construct_onion_packet(onion_payloads, onion_keys, [0; 32], &our_payment_hash);
14391439
let msg = msgs::UpdateAddHTLC {
14401440
channel_id: chan_1.2,
14411441
htlc_id,
@@ -4815,7 +4815,7 @@ fn test_onion_failure() {
48154815
let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route, &session_priv).unwrap();
48164816
let (mut onion_payloads, _htlc_msat, _htlc_cltv) = onion_utils::build_onion_payloads(&route, cur_height).unwrap();
48174817
onion_payloads[0].realm = 3;
4818-
msg.onion_routing_packet = onion_utils::construct_onion_packet(onion_payloads, onion_keys, &payment_hash);
4818+
msg.onion_routing_packet = onion_utils::construct_onion_packet(onion_payloads, onion_keys, [0; 32], &payment_hash);
48194819
}, ||{}, true, Some(PERM|1), Some(msgs::HTLCFailChannelUpdate::ChannelClosed{short_channel_id: channels[1].0.contents.short_channel_id, is_permanent: true}));//XXX incremented channels idx here
48204820

48214821
// final node failure
@@ -4825,7 +4825,7 @@ fn test_onion_failure() {
48254825
let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route, &session_priv).unwrap();
48264826
let (mut onion_payloads, _htlc_msat, _htlc_cltv) = onion_utils::build_onion_payloads(&route, cur_height).unwrap();
48274827
onion_payloads[1].realm = 3;
4828-
msg.onion_routing_packet = onion_utils::construct_onion_packet(onion_payloads, onion_keys, &payment_hash);
4828+
msg.onion_routing_packet = onion_utils::construct_onion_packet(onion_payloads, onion_keys, [0; 32], &payment_hash);
48294829
}, ||{}, false, Some(PERM|1), Some(msgs::HTLCFailChannelUpdate::ChannelClosed{short_channel_id: channels[1].0.contents.short_channel_id, is_permanent: true}));
48304830

48314831
// the following three with run_onion_failure_test_with_fail_intercept() test only the origin node
@@ -5003,7 +5003,7 @@ fn test_onion_failure() {
50035003
route.hops[1].cltv_expiry_delta += CLTV_FAR_FAR_AWAY + route.hops[0].cltv_expiry_delta + 1;
50045004
let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route, &session_priv).unwrap();
50055005
let (onion_payloads, _, htlc_cltv) = onion_utils::build_onion_payloads(&route, height).unwrap();
5006-
let onion_packet = onion_utils::construct_onion_packet(onion_payloads, onion_keys, &payment_hash);
5006+
let onion_packet = onion_utils::construct_onion_packet(onion_payloads, onion_keys, [0; 32], &payment_hash);
50075007
msg.cltv_expiry = htlc_cltv;
50085008
msg.onion_routing_packet = onion_packet;
50095009
}, ||{}, true, Some(21), None);
@@ -5253,7 +5253,7 @@ fn test_update_add_htlc_bolt2_receiver_check_max_htlc_limit() {
52535253
let cur_height = nodes[0].node.latest_block_height.load(Ordering::Acquire) as u32 + 1;
52545254
let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::signing_only(), &route, &session_priv).unwrap();
52555255
let (onion_payloads, _htlc_msat, htlc_cltv) = onion_utils::build_onion_payloads(&route, cur_height).unwrap();
5256-
let onion_packet = onion_utils::construct_onion_packet(onion_payloads, onion_keys, &our_payment_hash);
5256+
let onion_packet = onion_utils::construct_onion_packet(onion_payloads, onion_keys, [0; 32], &our_payment_hash);
52575257

52585258
let mut msg = msgs::UpdateAddHTLC {
52595259
channel_id: chan.2,

lightning/src/ln/onion_utils.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -161,11 +161,19 @@ fn xor_bufs(dst: &mut[u8], src: &[u8]) {
161161
}
162162
}
163163

164+
164165
const ZERO:[u8; 21*65] = [0; 21*65];
165-
pub(super) fn construct_onion_packet(mut payloads: Vec<msgs::OnionHopData>, onion_keys: Vec<OnionKeys>, associated_data: &PaymentHash) -> msgs::OnionPacket {
166+
pub(super) fn construct_onion_packet(payloads: Vec<msgs::OnionHopData>, onion_keys: Vec<OnionKeys>, prng_seed: [u8; 32], associated_data: &PaymentHash) -> msgs::OnionPacket {
166167
let mut buf = Vec::with_capacity(21*65);
167168
buf.resize(21*65, 0);
168169

170+
let mut chacha = ChaCha20::new(&prng_seed, &[0; 8]);
171+
chacha.process(&[0; 21*65], &mut buf);
172+
173+
construct_onion_packet_with_init_noise(payloads, onion_keys, buf, associated_data)
174+
}
175+
176+
fn construct_onion_packet_with_init_noise(mut payloads: Vec<msgs::OnionHopData>, onion_keys: Vec<OnionKeys>, mut buf: Vec<u8>, associated_data: &PaymentHash) -> msgs::OnionPacket {
169177
let filler = {
170178
let iters = payloads.len() - 1;
171179
let end_len = iters * 65;
@@ -547,7 +555,9 @@ mod tests {
547555
},
548556
);
549557

550-
let packet = super::construct_onion_packet(payloads, onion_keys, &PaymentHash([0x42; 32]));
558+
let mut buf = Vec::with_capacity(21*65);
559+
buf.resize(21*65, 0);
560+
let packet = super::construct_onion_packet_with_init_noise(payloads, onion_keys, buf, &PaymentHash([0x42; 32]));
551561
// Just check the final packet encoding, as it includes all the per-hop vectors in it
552562
// anyway...
553563
assert_eq!(packet.encode(), hex::decode("0002eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619e5f14350c2a76fc232b5e46d421e9615471ab9e0bc887beff8c95fdb878f7b3a716a996c7845c93d90e4ecbb9bde4ece2f69425c99e4bc820e44485455f135edc0d10f7d61ab590531cf08000179a333a347f8b4072f216400406bdf3bf038659793d4a1fd7b246979e3150a0a4cb052c9ec69acf0f48c3d39cd55675fe717cb7d80ce721caad69320c3a469a202f1e468c67eaf7a7cd8226d0fd32f7b48084dca885d56047694762b67021713ca673929c163ec36e04e40ca8e1c6d17569419d3039d9a1ec866abe044a9ad635778b961fc0776dc832b3a451bd5d35072d2269cf9b040f6b7a7dad84fb114ed413b1426cb96ceaf83825665ed5a1d002c1687f92465b49ed4c7f0218ff8c6c7dd7221d589c65b3b9aaa71a41484b122846c7c7b57e02e679ea8469b70e14fe4f70fee4d87b910cf144be6fe48eef24da475c0b0bcc6565ae82cd3f4e3b24c76eaa5616c6111343306ab35c1fe5ca4a77c0e314ed7dba39d6f1e0de791719c241a939cc493bea2bae1c1e932679ea94d29084278513c77b899cc98059d06a27d171b0dbdf6bee13ddc4fc17a0c4d2827d488436b57baa167544138ca2e64a11b43ac8a06cd0c2fba2d4d900ed2d9205305e2d7383cc98dacb078133de5f6fb6bed2ef26ba92cea28aafc3b9948dd9ae5559e8bd6920b8cea462aa445ca6a95e0e7ba52961b181c79e73bd581821df2b10173727a810c92b83b5ba4a0403eb710d2ca10689a35bec6c3a708e9e92f7d78ff3c5d9989574b00c6736f84c199256e76e19e78f0c98a9d580b4a658c84fc8f2096c2fbea8f5f8c59d0fdacb3be2802ef802abbecb3aba4acaac69a0e965abd8981e9896b1f6ef9d60f7a164b371af869fd0e48073742825e9434fc54da837e120266d53302954843538ea7c6c3dbfb4ff3b2fdbe244437f2a153ccf7bdb4c92aa08102d4f3cff2ae5ef86fab4653595e6a5837fa2f3e29f27a9cde5966843fb847a4a61f1e76c281fe8bb2b0a181d096100db5a1a5ce7a910238251a43ca556712eaadea167fb4d7d75825e440f3ecd782036d7574df8bceacb397abefc5f5254d2722215c53ff54af8299aaaad642c6d72a14d27882d9bbd539e1cc7a527526ba89b8c037ad09120e98ab042d3e8652b31ae0e478516bfaf88efca9f3676ffe99d2819dcaeb7610a626695f53117665d267d3f7abebd6bbd6733f645c72c389f03855bdf1e4b8075b516569b118233a0f0971d24b83113c0b096f5216a207ca99a7cddc81c130923fe3d91e7508c9ac5f2e914ff5dccab9e558566fa14efb34ac98d878580814b94b73acbfde9072f30b881f7f0fff42d4045d1ace6322d86a97d164aa84d93a60498065cc7c20e636f5862dc81531a88c60305a2e59a985be327a6902e4bed986dbf4a0b50c217af0ea7fdf9ab37f9ea1a1aaa72f54cf40154ea9b269f1a7c09f9f43245109431a175d50e2db0132337baa0ef97eed0fcf20489da36b79a1172faccc2f7ded7c60e00694282d93359c4682135642bc81f433574aa8ef0c97b4ade7ca372c5ffc23c7eddd839bab4e0f14d6df15c9dbeab176bec8b5701cf054eb3072f6dadc98f88819042bf10c407516ee58bce33fbe3b3d86a54255e577db4598e30a135361528c101683a5fcde7e8ba53f3456254be8f45fe3a56120ae96ea3773631fcb3873aa3abd91bcff00bd38bd43697a2e789e00da6077482e7b1b1a677b5afae4c54e6cbdf7377b694eb7d7a5b913476a5be923322d3de06060fd5e819635232a2cf4f0731da13b8546d1d6d4f8d75b9fce6c2341a71b0ea6f780df54bfdb0dd5cd9855179f602f9172307c7268724c3618e6817abd793adc214a0dc0bc616816632f27ea336fb56dfd").unwrap());

lightning/src/util/chacha20.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,7 @@ mod real_chacha {
224224
self.offset = 0;
225225
}
226226

227+
#[inline] // Useful cause input may be 0s on stack that should be optimized out
227228
pub fn process(&mut self, input: &[u8], output: &mut [u8]) {
228229
assert!(input.len() == output.len());
229230
let len = input.len();

lightning/src/util/test_utils.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -226,10 +226,10 @@ impl keysinterface::KeysInterface for TestKeysInterface {
226226
fn get_shutdown_pubkey(&self) -> PublicKey { self.backing.get_shutdown_pubkey() }
227227
fn get_channel_keys(&self, inbound: bool) -> keysinterface::ChannelKeys { self.backing.get_channel_keys(inbound) }
228228

229-
fn get_session_key(&self) -> SecretKey {
229+
fn get_onion_rand(&self) -> (SecretKey, [u8; 32]) {
230230
match *self.override_session_priv.lock().unwrap() {
231-
Some(key) => key.clone(),
232-
None => self.backing.get_session_key()
231+
Some(key) => (key.clone(), [0; 32]),
232+
None => self.backing.get_onion_rand()
233233
}
234234
}
235235

0 commit comments

Comments
 (0)