Skip to content

Commit d6f5b7d

Browse files
committed
Add OurPeerStorage for serialized Peer Storage backups
Introduce the OurPeerStorage struct to manage serialized channel data for peer storage backups. This struct facilitates the distribution of peer storage to channel partners and includes versioning and timestamping for comparison between retrieved peer storage instances. - Add the OurPeerStorage struct with fields for version, timestamp, and serialized channel data (ser_channels). - Implement methods to encrypt and decrypt peer storage securely. - Add functionality to update channel data within OurPeerStorage.
1 parent 674eafc commit d6f5b7d

File tree

2 files changed

+187
-0
lines changed

2 files changed

+187
-0
lines changed

lightning/src/ln/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ pub mod chan_utils;
2424
mod features;
2525
pub mod script;
2626
pub mod types;
27+
pub mod our_peer_storage;
2728

2829
// TODO: These modules were moved from lightning-invoice and need to be better integrated into this
2930
// crate now:

lightning/src/ln/our_peer_storage.rs

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
// This file is Copyright its original authors, visible in version control
2+
// history.
3+
//
4+
// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
5+
// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
6+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
7+
// You may not use this file except in accordance with one or both of these
8+
// licenses.
9+
10+
//! `OurPeerStorage` enables storage of encrypted serialized channel data.
11+
//! It provides encryption and decryption of data to maintain data integrity and
12+
//! security during transmission.
13+
//!
14+
15+
use bitcoin::hashes::sha256::Hash as Sha256;
16+
use bitcoin::hashes::{Hash, HashEngine, Hmac, HmacEngine};
17+
18+
use crate::sign::PeerStorageKey;
19+
20+
use crate::crypto::chacha20poly1305rfc::ChaCha20Poly1305RFC;
21+
use crate::prelude::*;
22+
23+
/// [`OurPeerStorage`] is used to store encrypted channel information that allows for the creation of a
24+
/// `peer_storage` backup.
25+
///
26+
/// This structure is designed to serialize channel data for backup and supports encryption
27+
/// and decryption using `ChaCha20Poly1305RFC` for transmission.
28+
///
29+
/// # Key Methods
30+
/// - [`OurPeerStorage::new`]: Returns [`OurPeerStorage`] with the given encrypted_data.
31+
/// - [`OurPeerStorage::create_from_data`]: Returns [`OurPeerStorage`] created from encrypting the provided data.
32+
/// - [`OurPeerStorage::decrypt_our_peer_storage`]: Decrypts the [`OurPeerStorage::encrypted_data`] using the key and returns decrypted data.
33+
///
34+
/// ## Example
35+
/// ```
36+
/// use lightning::ln::our_peer_storage::OurPeerStorage;
37+
/// use lightning::sign::PeerStorageKey;
38+
/// let key = PeerStorageKey{inner: [0u8; 32]};
39+
/// let decrypted_ops = OurPeerStorage::DecryptedPeerStorage { data: vec![1, 2, 3]};
40+
/// let our_peer_storage = decrypted_ops.encrypt_peer_storage(&key, [0u8; 32]);
41+
/// let decrypted_data = our_peer_storage.decrypt_peer_storage(&key).unwrap();
42+
/// assert_eq!(decrypted_data.into_vec(), vec![1, 2, 3]);
43+
/// ```
44+
#[derive(PartialEq, Eq, Debug)]
45+
pub enum OurPeerStorage {
46+
/// Contains ciphertext for transmission.
47+
EncryptedPeerStorage {
48+
/// Stores ciphertext.
49+
cipher: Vec<u8>,
50+
},
51+
/// Contains serialised decrypted peer storage backup.
52+
DecryptedPeerStorage {
53+
/// Stores decrypted serialised data.
54+
data: Vec<u8>,
55+
},
56+
}
57+
58+
impl OurPeerStorage {
59+
/// Return [`OurPeerStorage::EncryptedPeerStorage`] or [`OurPeerStorage::DecryptedPeerStorage`]
60+
/// in [`Vec<u8>`] form.
61+
pub fn into_vec(self) -> Vec<u8> {
62+
match self {
63+
OurPeerStorage::DecryptedPeerStorage { data } => data,
64+
OurPeerStorage::EncryptedPeerStorage { cipher } => cipher,
65+
}
66+
}
67+
68+
/// Returns [`OurPeerStorage::EncryptedPeerStorage`] with encrypted `cipher`.
69+
///
70+
/// This function takes a `key` (for encryption) and random_bytes (to derive nonce for encryption)
71+
/// and returns a [`OurPeerStorage::EncryptedPeerStorage`] with encrypted data inside.
72+
/// It should only be called on [`OurPeerStorage::DecryptedPeerStorage`] otherwise it would panic.
73+
///
74+
/// The resulting serialised data is intended to be directly used for transmission to the peers.
75+
pub fn encrypt_peer_storage(
76+
self, key: &PeerStorageKey, random_bytes: [u8; 32],
77+
) -> OurPeerStorage {
78+
match self {
79+
OurPeerStorage::DecryptedPeerStorage { mut data } => {
80+
let plaintext_len = data.len();
81+
let nonce = derive_nonce(key, &random_bytes);
82+
83+
let mut chacha = ChaCha20Poly1305RFC::new(&key.inner, &nonce, b"");
84+
let mut tag = [0; 16];
85+
chacha.encrypt_full_message_in_place(&mut data[0..plaintext_len], &mut tag);
86+
87+
data.extend_from_slice(&tag);
88+
89+
// Prepend `random_bytes` in front of the encrypted_blob.
90+
data.splice(0..0, random_bytes);
91+
Self::EncryptedPeerStorage { cipher: data }
92+
},
93+
OurPeerStorage::EncryptedPeerStorage { cipher: _ } => {
94+
panic!("Expected OurPeerStorage::DecryptedPeerStorage!");
95+
},
96+
}
97+
}
98+
99+
/// This expects a [`OurPeerStorage::EncryptedPeerStorage`] which would be consumed
100+
/// to return a [`OurPeerStorage::DecryptedPeerStorage`] in case of successful decryption.
101+
///
102+
/// It would return error if the ciphertext inside [`OurPeerStorage::EncryptedPeerStorage`] is
103+
/// not correct and panic if it is called on [`OurPeerStorage::DecryptedPeerStorage`].
104+
pub fn decrypt_peer_storage(self, key: &PeerStorageKey) -> Result<Self, ()> {
105+
match self {
106+
OurPeerStorage::EncryptedPeerStorage { mut cipher } => {
107+
let cyphertext_len = cipher.len();
108+
109+
// Ciphertext is of the form: random_bytes(32 bytes) + encrypted_data + tag(16 bytes).
110+
let (data_mut, tag) = cipher.split_at_mut(cyphertext_len - 16);
111+
let (random_bytes, encrypted_data) = data_mut.split_at_mut(32);
112+
113+
let nonce = derive_nonce(key, random_bytes);
114+
115+
let mut chacha = ChaCha20Poly1305RFC::new(&key.inner, &nonce, b"");
116+
117+
if chacha.check_decrypt_in_place(encrypted_data, tag).is_err() {
118+
return Err(());
119+
}
120+
121+
cipher.truncate(cyphertext_len - 16);
122+
cipher.drain(0..32);
123+
124+
Ok(Self::DecryptedPeerStorage { data: cipher })
125+
},
126+
OurPeerStorage::DecryptedPeerStorage { data: _ } => {
127+
panic!("Expected OurPeerStorage::EncryptedPeerStorage!");
128+
},
129+
}
130+
}
131+
}
132+
133+
/// Nonce for encryption and decryption: Hmac(Sha256(key) + random_bytes).
134+
fn derive_nonce(key: &PeerStorageKey, random_bytes: &[u8]) -> [u8; 12] {
135+
let key_hash = Sha256::const_hash(&key.inner);
136+
137+
let mut hmac = HmacEngine::<Sha256>::new(key_hash.as_byte_array());
138+
hmac.input(&random_bytes);
139+
let mut nonce = [0u8; 12];
140+
// First 4 bytes of the nonce should be 0.
141+
nonce[4..].copy_from_slice(&Hmac::from_engine(hmac).to_byte_array()[0..8]);
142+
143+
nonce
144+
}
145+
146+
#[cfg(test)]
147+
mod tests {
148+
use crate::ln::our_peer_storage::{derive_nonce, OurPeerStorage};
149+
use crate::sign::PeerStorageKey;
150+
151+
#[test]
152+
fn test_peer_storage_encryption_decryption() {
153+
let key1 = PeerStorageKey { inner: [0u8; 32] };
154+
let key2 = PeerStorageKey { inner: [1u8; 32] };
155+
let random_bytes1 = [200; 32];
156+
let random_bytes2 = [201; 32];
157+
158+
// Happy Path
159+
let decrypted_ops = OurPeerStorage::DecryptedPeerStorage { data: vec![42u8; 32] };
160+
let decrypted_ops_res = decrypted_ops
161+
.encrypt_peer_storage(&key1, random_bytes1)
162+
.decrypt_peer_storage(&key1)
163+
.unwrap();
164+
assert_eq!(decrypted_ops_res.into_vec(), vec![42u8; 32]);
165+
166+
// Changing Key
167+
let decrypted_ops_wrong_key = OurPeerStorage::DecryptedPeerStorage { data: vec![42u8; 32] };
168+
let decrypted_ops_wrong_key_res = decrypted_ops_wrong_key
169+
.encrypt_peer_storage(&key2, random_bytes2)
170+
.decrypt_peer_storage(&key1);
171+
assert!(decrypted_ops_wrong_key_res.is_err());
172+
173+
// Nonce derivation happy path
174+
let nonce = derive_nonce(&key1, &random_bytes1);
175+
let nonce_happy_path = derive_nonce(&key1, &random_bytes1);
176+
assert_eq!(nonce, nonce_happy_path);
177+
178+
// Nonce derivation with different `random_bytes` & `key`
179+
let nonce_diff_random_bytes = derive_nonce(&key1, &random_bytes2);
180+
let nonce_diff_key = derive_nonce(&key2, &random_bytes1);
181+
let nonce_diff_key_random_bytes = derive_nonce(&key2, &random_bytes2);
182+
assert_ne!(nonce, nonce_diff_random_bytes);
183+
assert_ne!(nonce, nonce_diff_key);
184+
assert_ne!(nonce, nonce_diff_key_random_bytes);
185+
}
186+
}

0 commit comments

Comments
 (0)