1use crate::error::LxmfError;
2use crate::message::Payload;
3use alloc::format;
4use alloc::string::String;
5use alloc::string::ToString;
6use alloc::vec;
7use alloc::vec::Vec;
8use base64::Engine;
9use ed25519_dalek::Signature;
10use rand_core::CryptoRngCore;
11use rns_core::crypt::fernet::{Fernet, PlainText, FERNET_MAX_PADDING_SIZE, FERNET_OVERHEAD_SIZE};
12use rns_core::identity::{DerivedKey, Identity, PrivateIdentity, PUBLIC_KEY_LENGTH};
13use serde::Deserialize;
14use sha2::{Digest, Sha256};
15use x25519_dalek::{EphemeralSecret, PublicKey};
16
17pub const SIGNATURE_LENGTH: usize = ed25519_dalek::SIGNATURE_LENGTH;
18pub const LXM_URI_PREFIX: &str = "lxm://";
19const STORAGE_MAGIC: &[u8; 8] = b"LXMFSTR0";
20const STORAGE_VERSION: u8 = 1;
21const STORAGE_FLAG_HAS_SIGNATURE: u8 = 0x01;
22
23#[derive(Debug, Deserialize)]
24struct PythonStorageContainer {
25 lxmf_bytes: serde_bytes::ByteBuf,
26}
27
28#[derive(Debug, Clone)]
29pub struct WireMessage {
30 pub destination: [u8; 16],
31 pub source: [u8; 16],
32 pub signature: Option<[u8; SIGNATURE_LENGTH]>,
33 pub payload: Payload,
34}
35
36impl WireMessage {
37 pub fn new(destination: [u8; 16], source: [u8; 16], payload: Payload) -> Self {
38 Self { destination, source, signature: None, payload }
39 }
40
41 pub fn message_id(&self) -> [u8; 32] {
42 let mut hasher = Sha256::new();
43 hasher.update(self.destination);
44 hasher.update(self.source);
45 hasher.update(self.payload.to_msgpack_without_stamp().unwrap_or_default());
46 let bytes = hasher.finalize();
47 let mut out = [0u8; 32];
48 out.copy_from_slice(&bytes);
49 out
50 }
51
52 pub fn sign(&mut self, signer: &PrivateIdentity) -> Result<(), LxmfError> {
53 let payload = self.payload.to_msgpack_without_stamp()?;
54 let mut data = Vec::with_capacity(16 + 16 + payload.len() + 32);
55 data.extend_from_slice(&self.destination);
56 data.extend_from_slice(&self.source);
57 data.extend_from_slice(&payload);
58 data.extend_from_slice(&self.message_id());
59
60 let signature = signer.sign(&data);
61 self.signature = Some(signature.to_bytes());
62 Ok(())
63 }
64
65 pub fn verify(&self, identity: &Identity) -> Result<bool, LxmfError> {
66 let Some(sig_bytes) = self.signature else {
67 return Ok(false);
68 };
69 let signature = Signature::from_slice(&sig_bytes)
70 .map_err(|e: ed25519_dalek::SignatureError| LxmfError::Decode(e.to_string()))?;
71
72 let payload = self.payload.to_msgpack_without_stamp()?;
73 let mut data = Vec::with_capacity(16 + 16 + payload.len() + 32);
74 data.extend_from_slice(&self.destination);
75 data.extend_from_slice(&self.source);
76 data.extend_from_slice(&payload);
77 data.extend_from_slice(&self.message_id());
78
79 Ok(identity.verify(&data, &signature).is_ok())
80 }
81
82 pub fn pack(&self) -> Result<Vec<u8>, LxmfError> {
83 let signature =
84 self.signature.ok_or_else(|| LxmfError::Encode("missing signature".into()))?;
85 let mut out = Vec::new();
86 out.extend_from_slice(&self.destination);
87 out.extend_from_slice(&self.source);
88 out.extend_from_slice(&signature);
89 let payload = self.payload.to_msgpack()?;
90 out.extend_from_slice(&payload);
91 Ok(out)
92 }
93
94 pub fn pack_storage(&self) -> Result<Vec<u8>, LxmfError> {
95 let payload = self.payload.to_msgpack()?;
96 let mut out = Vec::with_capacity(
97 STORAGE_MAGIC.len()
98 + 1
99 + 1
100 + 16
101 + 16
102 + self.signature.map(|_| SIGNATURE_LENGTH).unwrap_or(0)
103 + payload.len(),
104 );
105 out.extend_from_slice(STORAGE_MAGIC);
106 out.push(STORAGE_VERSION);
107 let mut flags = 0u8;
108 if self.signature.is_some() {
109 flags |= STORAGE_FLAG_HAS_SIGNATURE;
110 }
111 out.push(flags);
112 out.extend_from_slice(&self.destination);
113 out.extend_from_slice(&self.source);
114 if let Some(signature) = self.signature {
115 out.extend_from_slice(&signature);
116 }
117 out.extend_from_slice(&payload);
118 Ok(out)
119 }
120
121 pub fn unpack(bytes: &[u8]) -> Result<Self, LxmfError> {
122 let min_len = 16 + 16 + SIGNATURE_LENGTH;
123 if bytes.len() < min_len {
124 return Err(LxmfError::Decode("wire message too short".into()));
125 }
126 let mut dest = [0u8; 16];
127 let mut src = [0u8; 16];
128 let mut signature = [0u8; SIGNATURE_LENGTH];
129 dest.copy_from_slice(&bytes[0..16]);
130 src.copy_from_slice(&bytes[16..32]);
131 signature.copy_from_slice(&bytes[32..32 + SIGNATURE_LENGTH]);
132 let payload = Payload::from_msgpack(&bytes[32 + SIGNATURE_LENGTH..])?;
133 Ok(Self { destination: dest, source: src, signature: Some(signature), payload })
134 }
135
136 #[cfg(feature = "std")]
137 pub fn unpack_from_file(path: impl AsRef<std::path::Path>) -> Result<Self, LxmfError> {
138 let bytes = std::fs::read(path).map_err(|e| LxmfError::Io(e.to_string()))?;
139 Self::unpack(&bytes)
140 }
141
142 pub fn unpack_storage(bytes: &[u8]) -> Result<Self, LxmfError> {
143 let magic_len = STORAGE_MAGIC.len();
144 if bytes.len() >= magic_len && bytes.starts_with(STORAGE_MAGIC) {
145 if bytes.len() < magic_len + 1 + 1 + 16 + 16 {
146 return Err(LxmfError::Decode("storage message too short".into()));
147 }
148 let version = bytes[magic_len];
149 if version != STORAGE_VERSION {
150 return Err(LxmfError::Decode("unsupported storage version".into()));
151 }
152 let flags = bytes[magic_len + 1];
153 let mut idx = magic_len + 2;
154 let mut dest = [0u8; 16];
155 let mut src = [0u8; 16];
156 dest.copy_from_slice(&bytes[idx..idx + 16]);
157 idx += 16;
158 src.copy_from_slice(&bytes[idx..idx + 16]);
159 idx += 16;
160 let signature = if flags & STORAGE_FLAG_HAS_SIGNATURE != 0 {
161 if bytes.len() < idx + SIGNATURE_LENGTH {
162 return Err(LxmfError::Decode("storage signature missing".into()));
163 }
164 let mut sig = [0u8; SIGNATURE_LENGTH];
165 sig.copy_from_slice(&bytes[idx..idx + SIGNATURE_LENGTH]);
166 idx += SIGNATURE_LENGTH;
167 Some(sig)
168 } else {
169 None
170 };
171 let payload = Payload::from_msgpack(&bytes[idx..])?;
172 return Ok(Self { destination: dest, source: src, signature, payload });
173 }
174
175 if let Ok(container) = rmp_serde::from_slice::<PythonStorageContainer>(bytes) {
176 return Self::unpack(container.lxmf_bytes.as_ref());
177 }
178
179 Self::unpack(bytes)
180 }
181
182 #[cfg(feature = "std")]
183 pub fn unpack_storage_from_file(path: impl AsRef<std::path::Path>) -> Result<Self, LxmfError> {
184 let bytes = std::fs::read(path).map_err(|e| LxmfError::Io(e.to_string()))?;
185 Self::unpack_storage(&bytes)
186 }
187
188 #[cfg(feature = "std")]
189 pub fn pack_to_file(&self, path: impl AsRef<std::path::Path>) -> Result<(), LxmfError> {
190 let bytes = self.pack()?;
191 std::fs::write(path, bytes).map_err(|e| LxmfError::Io(e.to_string()))
192 }
193
194 #[cfg(feature = "std")]
195 pub fn pack_storage_to_file(&self, path: impl AsRef<std::path::Path>) -> Result<(), LxmfError> {
196 let bytes = self.pack_storage()?;
197 std::fs::write(path, bytes).map_err(|e| LxmfError::Io(e.to_string()))
198 }
199
200 pub fn pack_propagation_with_rng<R: CryptoRngCore + Copy>(
201 &self,
202 destination: &Identity,
203 timestamp: f64,
204 rng: R,
205 ) -> Result<Vec<u8>, LxmfError> {
206 let (envelope, _) =
207 self.pack_propagation_with_options_and_rng(destination, timestamp, None, rng)?;
208 Ok(envelope)
209 }
210
211 pub fn pack_propagation_with_options_and_rng<R: CryptoRngCore + Copy>(
212 &self,
213 destination: &Identity,
214 timestamp: f64,
215 propagation_stamp: Option<&[u8]>,
216 rng: R,
217 ) -> Result<(Vec<u8>, [u8; 32]), LxmfError> {
218 let (lxmf_data, transient_id) =
219 self.pack_propagation_transient_with_rng(destination, rng)?;
220 let packed = Self::pack_propagation_envelope(timestamp, &lxmf_data, propagation_stamp)?;
221 Ok((packed, transient_id))
222 }
223
224 pub fn pack_propagation_transient_with_rng<R: CryptoRngCore + Copy>(
225 &self,
226 destination: &Identity,
227 rng: R,
228 ) -> Result<(Vec<u8>, [u8; 32]), LxmfError> {
229 let packed = self.pack()?;
230 let encrypted = encrypt_for_identity(destination, &packed[16..], rng)?;
231
232 let mut lxmf_data = Vec::with_capacity(16 + encrypted.len());
233 lxmf_data.extend_from_slice(&packed[..16]);
234 lxmf_data.extend_from_slice(&encrypted);
235 let transient_id = Sha256::digest(&lxmf_data);
236 let mut transient_id_bytes = [0u8; 32];
237 transient_id_bytes.copy_from_slice(transient_id.as_slice());
238 Ok((lxmf_data, transient_id_bytes))
239 }
240
241 pub fn pack_propagation_envelope(
242 timestamp: f64,
243 lxmf_data: &[u8],
244 propagation_stamp: Option<&[u8]>,
245 ) -> Result<Vec<u8>, LxmfError> {
246 let mut transient_payload = Vec::with_capacity(
247 lxmf_data.len() + propagation_stamp.map(|stamp| stamp.len()).unwrap_or(0),
248 );
249 transient_payload.extend_from_slice(lxmf_data);
250 if let Some(stamp) = propagation_stamp {
251 transient_payload.extend_from_slice(stamp);
252 }
253
254 let envelope = (timestamp, vec![serde_bytes::ByteBuf::from(transient_payload)]);
255 rmp_serde::to_vec(&envelope).map_err(|e| LxmfError::Encode(e.to_string()))
256 }
257
258 pub fn pack_paper_with_rng<R: CryptoRngCore + Copy>(
259 &self,
260 destination: &Identity,
261 rng: R,
262 ) -> Result<Vec<u8>, LxmfError> {
263 let packed = self.pack()?;
264 let encrypted = encrypt_for_identity(destination, &packed[16..], rng)?;
265 let mut out = Vec::with_capacity(16 + encrypted.len());
266 out.extend_from_slice(&packed[..16]);
267 out.extend_from_slice(&encrypted);
268 Ok(out)
269 }
270
271 pub fn pack_paper_uri_with_rng<R: CryptoRngCore + Copy>(
272 &self,
273 destination: &Identity,
274 rng: R,
275 ) -> Result<String, LxmfError> {
276 let packed = self.pack_paper_with_rng(destination, rng)?;
277 Ok(Self::encode_lxm_uri(&packed))
278 }
279
280 pub fn encode_lxm_uri(paper_bytes: &[u8]) -> String {
281 let encoded = base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(paper_bytes);
282 format!("{LXM_URI_PREFIX}{encoded}")
283 }
284
285 pub fn decode_lxm_uri(uri: &str) -> Result<Vec<u8>, LxmfError> {
286 let encoded = uri
287 .strip_prefix(LXM_URI_PREFIX)
288 .ok_or_else(|| LxmfError::Decode("invalid lxm uri prefix".into()))?;
289
290 base64::engine::general_purpose::URL_SAFE_NO_PAD
291 .decode(encoded)
292 .or_else(|_| base64::engine::general_purpose::URL_SAFE.decode(encoded))
293 .map_err(|e| LxmfError::Decode(format!("invalid lxm uri payload: {e}")))
294 }
295}
296
297fn encrypt_for_identity<R: CryptoRngCore + Copy>(
298 destination: &Identity,
299 plaintext: &[u8],
300 rng: R,
301) -> Result<Vec<u8>, LxmfError> {
302 let secret = EphemeralSecret::random_from_rng(rng);
303 let ephemeral_public = PublicKey::from(&secret);
304 let shared = secret.diffie_hellman(&destination.public_key);
305 let derived = DerivedKey::new(&shared, Some(destination.address_hash.as_slice()));
306 let key_bytes = derived.as_bytes();
307 let split = key_bytes.len() / 2;
308
309 let fernet = Fernet::new_from_slices(&key_bytes[..split], &key_bytes[split..], rng);
310 let token_capacity = plaintext.len() + FERNET_OVERHEAD_SIZE + FERNET_MAX_PADDING_SIZE;
312 let mut out = vec![0u8; PUBLIC_KEY_LENGTH + token_capacity];
313 out[..PUBLIC_KEY_LENGTH].copy_from_slice(ephemeral_public.as_bytes());
314 let token = fernet
315 .encrypt(PlainText::from(plaintext), &mut out[PUBLIC_KEY_LENGTH..])
316 .map_err(|e| LxmfError::Encode(format!("{e:?}")))?;
317 let total = PUBLIC_KEY_LENGTH + token.len();
318 out.truncate(total);
319 Ok(out)
320}
321
322#[cfg(test)]
323mod tests {
324 use super::WireMessage;
325 use crate::message::Payload;
326 use rand_core::OsRng;
327 use rns_core::identity::{DecryptIdentity, PrivateIdentity, PUBLIC_KEY_LENGTH};
328 use serde_bytes::ByteBuf;
329 use sha2::{Digest, Sha256};
330 use x25519_dalek::PublicKey;
331
332 fn address_hash_bytes(identity: &PrivateIdentity) -> [u8; 16] {
333 let mut out = [0u8; 16];
334 out.copy_from_slice(identity.address_hash().as_slice());
335 out
336 }
337
338 #[test]
339 fn propagation_pack_derives_transient_id_from_lxm_data() {
340 let sender = PrivateIdentity::new_from_name("propagation-pack-sender");
341 let receiver = PrivateIdentity::new_from_name("propagation-pack-receiver");
342 let payload =
343 Payload::new(1.0, Some(b"content".to_vec()), Some(b"title".to_vec()), None, None);
344 let mut wire =
345 WireMessage::new(address_hash_bytes(&receiver), address_hash_bytes(&sender), payload);
346 wire.sign(&sender).expect("sign");
347
348 let (envelope, transient_id) = wire
349 .pack_propagation_with_options_and_rng(receiver.as_identity(), 2.0, None, OsRng)
350 .expect("pack propagation");
351 let (_timestamp, entries): (f64, Vec<ByteBuf>) =
352 rmp_serde::from_slice(&envelope).expect("decode propagation envelope");
353 assert_eq!(entries.len(), 1);
354
355 let expected = Sha256::digest(entries[0].as_ref());
356 assert_eq!(transient_id.as_slice(), expected.as_slice());
357 }
358
359 #[test]
360 fn propagation_pack_appends_optional_stamp_after_lxm_data() {
361 let sender = PrivateIdentity::new_from_name("propagation-pack-stamp-sender");
362 let receiver = PrivateIdentity::new_from_name("propagation-pack-stamp-receiver");
363 let payload = Payload::new(1.0, Some(vec![0x55; 48]), Some(b"title".to_vec()), None, None);
364 let mut wire =
365 WireMessage::new(address_hash_bytes(&receiver), address_hash_bytes(&sender), payload);
366 wire.sign(&sender).expect("sign");
367
368 let propagation_stamp = vec![0xAB; 32];
369 let (envelope, transient_id) = wire
370 .pack_propagation_with_options_and_rng(
371 receiver.as_identity(),
372 3.0,
373 Some(propagation_stamp.as_slice()),
374 OsRng,
375 )
376 .expect("pack propagation with stamp");
377 let (_timestamp, entries): (f64, Vec<ByteBuf>) =
378 rmp_serde::from_slice(&envelope).expect("decode propagation envelope");
379 let transient_payload = entries[0].as_ref();
380
381 assert!(transient_payload.ends_with(propagation_stamp.as_slice()));
382 let lxm_data = &transient_payload[..transient_payload.len() - propagation_stamp.len()];
383 let expected = Sha256::digest(lxm_data);
384 assert_eq!(transient_id.as_slice(), expected.as_slice());
385 }
386
387 #[test]
388 fn propagation_transient_helper_matches_envelope_transient_id() {
389 let sender = PrivateIdentity::new_from_name("propagation-pack-helper-sender");
390 let receiver = PrivateIdentity::new_from_name("propagation-pack-helper-receiver");
391 let payload = Payload::new(1.0, Some(vec![0x11; 32]), Some(b"title".to_vec()), None, None);
392 let mut wire =
393 WireMessage::new(address_hash_bytes(&receiver), address_hash_bytes(&sender), payload);
394 wire.sign(&sender).expect("sign");
395
396 let (lxmf_data, transient_id) = wire
397 .pack_propagation_transient_with_rng(receiver.as_identity(), OsRng)
398 .expect("pack propagation transient");
399 let propagation_stamp = vec![0xCD; 32];
400 let envelope = WireMessage::pack_propagation_envelope(
401 4.0,
402 &lxmf_data,
403 Some(propagation_stamp.as_slice()),
404 )
405 .expect("pack propagation envelope");
406 let (_timestamp, entries): (f64, Vec<ByteBuf>) =
407 rmp_serde::from_slice(&envelope).expect("decode propagation envelope");
408 let transient_payload = entries[0].as_ref();
409
410 assert!(transient_payload.ends_with(propagation_stamp.as_slice()));
411 assert_eq!(
412 &transient_payload[..transient_payload.len() - propagation_stamp.len()],
413 lxmf_data.as_slice()
414 );
415 let expected = Sha256::digest(&lxmf_data);
416 assert_eq!(transient_id.as_slice(), expected.as_slice());
417 }
418
419 #[test]
420 fn propagation_transient_can_be_decrypted_by_recipient_identity() {
421 let sender = PrivateIdentity::new_from_name("propagation-pack-decrypt-sender");
422 let receiver = PrivateIdentity::new_from_name("propagation-pack-decrypt-receiver");
423 let payload =
424 Payload::new(1.0, Some(b"content".to_vec()), Some(b"title".to_vec()), None, None);
425 let mut wire =
426 WireMessage::new(address_hash_bytes(&receiver), address_hash_bytes(&sender), payload);
427 wire.sign(&sender).expect("sign");
428
429 let packed = wire.pack().expect("pack");
430 let (lxmf_data, _transient_id) = wire
431 .pack_propagation_transient_with_rng(receiver.as_identity(), OsRng)
432 .expect("pack propagation transient");
433 let encrypted = &lxmf_data[16..];
434 let mut ephemeral_pub = [0u8; PUBLIC_KEY_LENGTH];
435 ephemeral_pub.copy_from_slice(&encrypted[..PUBLIC_KEY_LENGTH]);
436 let derived = receiver
437 .derive_key(&PublicKey::from(ephemeral_pub), Some(receiver.address_hash().as_slice()));
438 let mut plaintext = vec![0u8; packed.len()];
439 let decrypted = receiver
440 .decrypt(OsRng, &encrypted[PUBLIC_KEY_LENGTH..], &derived, &mut plaintext)
441 .expect("decrypt propagation payload");
442
443 assert_eq!(&lxmf_data[..16], &packed[..16]);
444 assert_eq!(decrypted, &packed[16..]);
445 }
446
447 #[test]
448 fn unpack_storage_accepts_python_msgpack_container() {
449 let sender = PrivateIdentity::new_from_name("python-storage-sender");
450 let receiver = PrivateIdentity::new_from_name("python-storage-receiver");
451 let payload = Payload::new(
452 1_773_999_123.25,
453 Some(b"content".to_vec()),
454 Some(b"title".to_vec()),
455 None,
456 None,
457 );
458 let mut wire =
459 WireMessage::new(address_hash_bytes(&receiver), address_hash_bytes(&sender), payload);
460 wire.sign(&sender).expect("sign");
461
462 let packed_wire = wire.pack().expect("pack");
463 let python_container = rmp_serde::to_vec(&rmpv::Value::Map(vec![
464 (rmpv::Value::String("state".into()), rmpv::Value::Integer(4_i64.into())),
465 (rmpv::Value::String("lxmf_bytes".into()), rmpv::Value::Binary(packed_wire.clone())),
466 (rmpv::Value::String("transport_encrypted".into()), rmpv::Value::Boolean(true)),
467 (
468 rmpv::Value::String("transport_encryption".into()),
469 rmpv::Value::String("Curve25519".into()),
470 ),
471 (rmpv::Value::String("method".into()), rmpv::Value::Integer(2_i64.into())),
472 ]))
473 .expect("pack python container");
474
475 let unpacked = WireMessage::unpack_storage(&python_container).expect("unpack storage");
476 assert_eq!(unpacked.destination, wire.destination);
477 assert_eq!(unpacked.source, wire.source);
478 assert_eq!(unpacked.signature, wire.signature);
479 assert_eq!(
480 unpacked.payload.to_msgpack().expect("payload msgpack"),
481 wire.payload.to_msgpack().expect("payload msgpack")
482 );
483 }
484}