1use crate::utils::PublicKey;
2use crate::utils::{bls12_381, misc::get_unix_millis_now, misc::get_unix_nanos_now};
3use crate::{Config, Ver};
4use aes_gcm::aead::{Aead, AeadCore, OsRng};
5use aes_gcm::{Aes256Gcm, Key, KeyInit, Nonce};
6use std::collections::HashMap;
7use std::hash::{Hash as StdHash, Hasher};
8use tokio::sync::RwLock;
9
10#[derive(Debug, thiserror::Error, strum_macros::IntoStaticStr)]
11pub enum Error {
12 #[error("AES encryption error")]
13 AesError,
14 #[error(transparent)]
15 Bls(#[from] bls12_381::Error),
16 #[error(transparent)]
17 ReedSolomon(#[from] crate::utils::reed_solomon::Error),
18 #[error(transparent)]
19 Compression(#[from] std::io::Error),
20 #[error("Invalid message format")]
21 InvalidFormat,
22 #[error("Payload too small for nonce")]
23 PayloadTooSmall,
24}
25
26impl crate::utils::misc::Typename for Error {
27 fn typename(&self) -> &'static str {
28 self.into()
29 }
30}
31
32#[derive(Debug, Clone)]
35pub struct Message {
36 pub version: Ver,
37 pub pk: PublicKey, pub shard_index: u16, pub shard_total: u16, pub ts_nano: u64, pub original_size: u32, pub payload: Vec<u8>, }
44
45impl Message {
46 fn calculate_reed_solomon_params(payload_len: usize) -> (usize, usize, u16, usize) {
48 let data_shards = payload_len.div_ceil(1024);
49 let parity_shards = data_shards;
50 let total_shards = (data_shards + parity_shards) as u16;
51 let shards_to_send = data_shards + 1 + (data_shards / 4);
52 (data_shards, parity_shards, total_shards, shards_to_send)
53 }
54 fn derive_aes_key(shared_secret: &[u8], ts_nano: u64, iv: &[u8]) -> [u8; 32] {
57 use sha2::{Digest, Sha256};
58
59 let mut hasher = Sha256::new();
60 hasher.update(shared_secret);
61 let ts_bytes = ts_nano.to_be_bytes();
63 let first_nonzero = ts_bytes.iter().position(|&b| b != 0).unwrap_or(7);
64 hasher.update(&ts_bytes[first_nonzero..]);
65 hasher.update(iv);
66
67 let result = hasher.finalize();
68 let mut key = [0u8; 32];
69 key.copy_from_slice(&result);
70 key
71 }
72
73 pub fn encrypt(
76 sender_pk: &PublicKey,
77 shared_secret: &[u8],
78 plaintext: &[u8],
79 version: Ver,
80 ) -> Result<Vec<Self>, Error> {
81 let ts_nano = get_unix_nanos_now() as u64;
82
83 let compressed = if version >= Ver::new(1, 2, 3) {
85 zstd::encode_all(plaintext, 3).map_err(|e| Error::Compression(e.into()))?
86 } else {
87 crate::utils::compression::compress_with_zlib(plaintext)?
88 };
89
90 let nonce = Aes256Gcm::generate_nonce(&mut OsRng);
92 let key_bytes = Self::derive_aes_key(shared_secret, ts_nano, &nonce);
93 let cipher = Aes256Gcm::new(Key::<Aes256Gcm>::from_slice(&key_bytes));
94 let ciphertext_with_tag = cipher.encrypt(&nonce, compressed.as_slice()).map_err(|_| Error::AesError)?;
95
96 let (ciphertext, tag) = ciphertext_with_tag.split_at(ciphertext_with_tag.len() - 16);
98 let mut encrypted_payload = Vec::with_capacity(12 + 16 + ciphertext.len());
99 encrypted_payload.extend_from_slice(&nonce);
100 encrypted_payload.extend_from_slice(tag);
101 encrypted_payload.extend_from_slice(ciphertext);
102
103 if encrypted_payload.len() < 1300 {
105 Ok(vec![Self {
107 version,
108 pk: *sender_pk,
109 shard_index: 0,
110 shard_total: 1,
111 ts_nano,
112 original_size: encrypted_payload.len() as u32,
113 payload: encrypted_payload,
114 }])
115 } else {
116 let (data_shards, parity_shards, total_shards, shards_to_send) =
118 Self::calculate_reed_solomon_params(encrypted_payload.len());
119
120 let mut rs = crate::utils::reed_solomon::ReedSolomonResource::new(data_shards, parity_shards)?;
121 let encoded_shards = rs.encode_shards(&encrypted_payload)?;
122
123 let limited_shards: Vec<_> = encoded_shards.into_iter().take(shards_to_send).collect();
124
125 let mut messages = Vec::new();
126 for (shard_index, shard_payload) in limited_shards {
127 messages.push(Self {
128 version,
129 pk: *sender_pk,
130 shard_index: shard_index as u16,
131 shard_total: total_shards,
132 ts_nano,
133 original_size: encrypted_payload.len() as u32,
134 payload: shard_payload,
135 });
136 }
137
138 Ok(messages)
139 }
140 }
141
142 pub fn decrypt(&self, shared_secret: &[u8]) -> Result<Vec<u8>, Error> {
144 let compressed = self.decrypt_raw(shared_secret)?;
145 let plaintext = if self.version >= Ver::new(1, 2, 3) {
148 zstd::decode_all(compressed.as_slice()).map_err(|e| Error::Compression(e.into()))?
149 } else {
150 crate::utils::compression::decompress_with_zlib(&compressed)?
151 };
152 Ok(plaintext)
153 }
154
155 fn decrypt_raw(&self, shared_secret: &[u8]) -> Result<Vec<u8>, Error> {
157 if self.payload.len() < 28 {
158 return Err(Error::PayloadTooSmall);
160 }
161
162 let nonce_bytes = &self.payload[0..12];
164 let tag_bytes = &self.payload[12..28];
165 let ciphertext = &self.payload[28..];
166
167 let mut ciphertext_with_tag = Vec::with_capacity(ciphertext.len() + 16);
169 ciphertext_with_tag.extend_from_slice(ciphertext);
170 ciphertext_with_tag.extend_from_slice(tag_bytes);
171
172 let key_bytes = Self::derive_aes_key(shared_secret, self.ts_nano, nonce_bytes);
174 let cipher = Aes256Gcm::new(Key::<Aes256Gcm>::from_slice(&key_bytes));
175 let nonce = Nonce::from_slice(nonce_bytes);
176
177 cipher.decrypt(nonce, ciphertext_with_tag.as_slice()).map_err(|_e| {
178 tracing::debug!(
179 "AES decrypt failed. pk={} ts_nano={} nonce={} tag={} ciphertext_len={} key={}",
180 hex::encode(&self.pk),
181 self.ts_nano,
182 hex::encode(nonce_bytes),
183 hex::encode(tag_bytes),
184 ciphertext.len(),
185 hex::encode(&key_bytes)
186 );
187 Error::AesError
188 })
189 }
190
191 pub fn to_bytes(&self) -> Vec<u8> {
193 let ver = self.version.as_bytes();
194 let capacity = 3 + 3 + 1 + 48 + 2 + 2 + 8 + 4 + self.payload.len();
195 let mut out = Vec::with_capacity(capacity);
196
197 out.extend_from_slice(b"AMA");
199
200 out.extend_from_slice(&ver);
202
203 out.push(0);
205
206 out.extend_from_slice(self.pk.as_ref());
208
209 out.extend_from_slice(&self.shard_index.to_be_bytes());
211 out.extend_from_slice(&self.shard_total.to_be_bytes());
212
213 out.extend_from_slice(&self.ts_nano.to_be_bytes());
215
216 out.extend_from_slice(&self.original_size.to_be_bytes());
218
219 out.extend_from_slice(&self.payload);
221
222 out
223 }
224}
225
226impl TryFrom<&[u8]> for Message {
227 type Error = Error;
228
229 fn try_from(bin: &[u8]) -> Result<Self, Self::Error> {
230 if bin.len() < 3 + 3 + 1 + 48 + 2 + 2 + 8 + 4 {
232 return Err(Error::InvalidFormat);
233 }
234
235 if &bin[0..3] != b"AMA" {
237 return Err(Error::InvalidFormat);
238 }
239
240 let version_bytes = &bin[3..6];
241 let version = Ver::new(version_bytes[0], version_bytes[1], version_bytes[2]);
242
243 let pk_start = 7; let pk_end = pk_start + 48;
246 let pk = bin[pk_start..pk_end].try_into().expect("pk should be 48 bytes");
247
248 let shard_index = u16::from_be_bytes(bin[pk_end..pk_end + 2].try_into().unwrap());
249 let shard_total = u16::from_be_bytes(bin[pk_end + 2..pk_end + 4].try_into().unwrap());
250
251 let ts_nano = u64::from_be_bytes(bin[pk_end + 4..pk_end + 12].try_into().unwrap());
252 let original_size = u32::from_be_bytes(bin[pk_end + 12..pk_end + 16].try_into().unwrap());
253
254 let payload = bin[pk_end + 16..].to_vec();
255
256 Ok(Self { version, pk, shard_index, shard_total, ts_nano, original_size, payload })
257 }
258}
259
260pub struct ReedSolomonReassembler {
262 reorg: RwLock<HashMap<ReassemblyKey, TimedEntryState>>,
263 cache: RwLock<HashMap<PublicKey, TimedSharedSecret>>,
264}
265
266struct TimedSharedSecret {
267 shared_secret: PublicKey,
268 ts_m: u64,
269}
270
271impl TimedSharedSecret {
272 fn new(shared_secret: PublicKey) -> Self {
273 let ts_m = get_unix_millis_now();
274 Self { shared_secret, ts_m }
275 }
276}
277
278#[derive(Clone, Debug, Eq)]
279struct ReassemblyKey {
280 pk: PublicKey,
281 ts_nano: u64,
282 shard_total: u16,
283 original_size: u32,
284 version: Ver,
285}
286
287impl From<&Message> for ReassemblyKey {
288 fn from(msg: &Message) -> Self {
289 Self {
290 pk: msg.pk,
291 ts_nano: msg.ts_nano,
292 shard_total: msg.shard_total,
293 original_size: msg.original_size,
294 version: msg.version,
295 }
296 }
297}
298
299impl PartialEq for ReassemblyKey {
300 fn eq(&self, other: &Self) -> bool {
301 self.pk == other.pk && self.ts_nano == other.ts_nano && self.shard_total == other.shard_total
302 }
303}
304
305impl StdHash for ReassemblyKey {
306 fn hash<H: Hasher>(&self, state: &mut H) {
307 self.pk.hash(state);
308 self.ts_nano.hash(state);
309 self.shard_total.hash(state);
310 }
311}
312
313#[derive(Debug)]
314enum EntryState {
315 Collecting(HashMap<u16, Vec<u8>>), Spent,
317}
318
319struct TimedEntryState {
320 ts_m: u64,
321 state: EntryState,
322}
323
324impl TimedEntryState {
325 fn new(state: EntryState) -> Self {
326 let ts_m = get_unix_millis_now();
327 Self { ts_m, state }
328 }
329}
330
331impl Default for ReedSolomonReassembler {
332 fn default() -> Self {
333 Self::new()
334 }
335}
336
337impl ReedSolomonReassembler {
338 pub fn new() -> Self {
339 Self { reorg: RwLock::new(HashMap::new()), cache: RwLock::new(HashMap::new()) }
340 }
341
342 pub async fn clear_stale(&self) -> usize {
344 let threshold_ms = get_unix_millis_now().saturating_sub(8_000);
345 let mut map = self.reorg.write().await;
346 let size_before = map.len();
347 map.retain(|_k, v| v.ts_m > threshold_ms);
348 let cleared = size_before - map.len();
349 let mut map = self.cache.write().await;
350 map.retain(|_k, v| v.ts_m > threshold_ms);
351
352 cleared
353 }
354
355 pub async fn add_shard(&self, bin: &[u8], config_sk: &[u8]) -> Result<Option<(Vec<u8>, PublicKey)>, Error> {
358 let encrypted_msg = Message::try_from(bin)?;
359 let key = ReassemblyKey::from(&encrypted_msg);
360
361 if key.shard_total == 1 {
363 let shared_secret = bls12_381::get_shared_secret(&key.pk, config_sk)?;
364 let decrypted_compressed = encrypted_msg.decrypt_raw(shared_secret.as_ref())?;
366 let payload = if key.version >= Ver::new(1, 2, 3) {
368 zstd::decode_all(decrypted_compressed.as_slice()).map_err(|e| Error::Compression(e.into()))?
369 } else {
370 crate::utils::compression::decompress_with_zlib(&decrypted_compressed)?
371 };
372 return Ok(Some((payload, key.pk)));
373 }
374
375 let data_shards = (key.shard_total / 2) as usize;
376
377 let mut maybe_shards: Option<Vec<(usize, Vec<u8>)>> = None;
379 {
380 let mut map = self.reorg.write().await;
381 use std::collections::hash_map::Entry;
382 match map.entry(key.clone()) {
383 Entry::Vacant(v) => {
384 let mut state_map = HashMap::new();
385 state_map.insert(encrypted_msg.shard_index, encrypted_msg.payload.clone());
386 v.insert(TimedEntryState::new(EntryState::Collecting(state_map)));
387 }
388 Entry::Occupied(mut occ) => {
389 match occ.get_mut() {
390 TimedEntryState { state: EntryState::Spent, .. } => {
391 }
393 TimedEntryState { state: EntryState::Collecting(shards_map), .. } => {
394 shards_map.insert(encrypted_msg.shard_index, encrypted_msg.payload.clone());
395 if shards_map.len() >= data_shards {
396 let shards: Vec<(usize, Vec<u8>)> =
397 shards_map.iter().map(|(idx, bytes)| (*idx as usize, bytes.clone())).collect();
398 *occ.get_mut() = TimedEntryState::new(EntryState::Spent);
400 maybe_shards = Some(shards);
401 }
402 }
403 }
404 }
405 }
406 }
407
408 if let Some(shards) = maybe_shards {
409 let mut rs_res = crate::utils::reed_solomon::ReedSolomonResource::new(data_shards, data_shards)?;
411 let encrypted_payload =
414 rs_res.decode_shards(shards, key.shard_total as usize, key.original_size as usize)?;
415
416 let shared_secret = bls12_381::get_shared_secret(&key.pk, config_sk)?;
417
418 let temp_msg = Message {
420 version: key.version,
421 pk: key.pk,
422 shard_index: 0,
423 shard_total: 1,
424 ts_nano: key.ts_nano,
425 original_size: key.original_size,
426 payload: encrypted_payload,
427 };
428
429 let decrypted_compressed = temp_msg.decrypt_raw(shared_secret.as_ref())?;
431 let payload = if key.version >= Ver::new(1, 2, 3) {
433 zstd::decode_all(decrypted_compressed.as_slice()).map_err(|e| Error::Compression(e.into()))?
434 } else {
435 crate::utils::compression::decompress_with_zlib(&decrypted_compressed)?
436 };
437 return Ok(Some((payload, key.pk)));
438 }
439
440 Ok(None)
441 }
442
443 pub async fn build_shards(
446 &self,
447 config: &Config,
448 payload: &[u8],
449 target_pk: &PublicKey,
450 ) -> Result<Vec<Vec<u8>>, Error> {
451 let version = config.get_ver();
452 let sender_pk = config.get_pk();
453 let shared_secret = self.get_shared_secret(config, target_pk).await?;
454 let encrypted_messages = Message::encrypt(&sender_pk, shared_secret.as_ref(), payload, version)?;
455
456 let mut shards = Vec::new();
457 for encrypted_msg in encrypted_messages {
458 shards.push(encrypted_msg.to_bytes());
459 }
460
461 Ok(shards)
462 }
463
464 async fn get_shared_secret(&self, config: &Config, pk: &PublicKey) -> Result<PublicKey, Error> {
465 use std::collections::hash_map::Entry;
466
467 let mut map = self.cache.write().await;
468 match map.entry(pk.clone()) {
469 Entry::Vacant(v) => {
470 let shared_secret = bls12_381::get_shared_secret(pk, &config.get_sk())?;
471 v.insert(TimedSharedSecret::new(shared_secret));
472 Ok(shared_secret)
473 }
474 Entry::Occupied(e) => Ok(e.get().shared_secret),
475 }
476 }
477}
478
479#[cfg(test)]
480mod tests {
481 use super::*;
482 use crate::utils::bls12_381;
483
484 #[test]
485 fn test_encrypted_message_round_trip() {
486 let sk_alice = bls12_381::generate_sk();
488 let sk_bob = bls12_381::generate_sk();
489
490 let pk_alice = bls12_381::get_public_key(&sk_alice).expect("get pk alice");
491 let pk_bob = bls12_381::get_public_key(&sk_bob).expect("get pk bob");
492
493 let shared_secret_alice = bls12_381::get_shared_secret(&pk_bob, &sk_alice).expect("shared secret alice");
495 let shared_secret_bob = bls12_381::get_shared_secret(&pk_alice, &sk_bob).expect("shared secret bob");
496
497 assert_eq!(shared_secret_alice, shared_secret_bob, "Shared secrets should be symmetric");
498
499 let test_message = b"Hello from Alice to Bob via encrypted message!";
501 let version = Ver::new(1, 1, 8);
502
503 let encrypted_messages = Message::encrypt(&pk_alice, &*shared_secret_alice, test_message, version)
505 .expect("encryption should succeed");
506
507 assert_eq!(encrypted_messages.len(), 1, "Should create single message for small payload");
508 let encrypted_msg = &encrypted_messages[0];
509
510 assert_eq!(encrypted_msg.version, version);
512 assert_eq!(*encrypted_msg.pk, *pk_alice);
513 assert_eq!(encrypted_msg.shard_index, 0);
514 assert_eq!(encrypted_msg.shard_total, 1);
515 assert_eq!(encrypted_msg.original_size, encrypted_msg.payload.len() as u32);
517
518 let decrypted = encrypted_msg.decrypt(&*shared_secret_bob).expect("decryption should succeed");
520
521 assert_eq!(decrypted, test_message, "Decrypted message should match original");
522
523 let serialized = encrypted_msg.to_bytes();
525 let deserialized = Message::try_from(serialized.as_slice()).expect("deserialization should succeed");
526
527 assert_eq!(deserialized.version, encrypted_msg.version);
528 assert_eq!(deserialized.pk, encrypted_msg.pk);
529 assert_eq!(deserialized.shard_index, encrypted_msg.shard_index);
530 assert_eq!(deserialized.shard_total, encrypted_msg.shard_total);
531 assert_eq!(deserialized.ts_nano, encrypted_msg.ts_nano);
532 assert_eq!(deserialized.original_size, encrypted_msg.original_size);
533 assert_eq!(deserialized.payload, encrypted_msg.payload);
534
535 let decrypted2 =
537 deserialized.decrypt(&*shared_secret_bob).expect("decryption of deserialized message should succeed");
538 assert_eq!(decrypted2, test_message, "Decrypted deserialized message should match original");
539 }
540
541 #[test]
542 fn test_elixir_compatible_64_byte_keys() {
543 let sk_64_alice = bls12_381::generate_sk();
545 let sk_64_bob = bls12_381::generate_sk();
546
547 let pk_alice = bls12_381::get_public_key(&sk_64_alice).expect("get pk alice 64");
548 let pk_bob = bls12_381::get_public_key(&sk_64_bob).expect("get pk bob 64");
549
550 let shared_secret_alice = bls12_381::get_shared_secret(&pk_bob, &sk_64_alice).expect("shared secret alice 64");
551 let shared_secret_bob = bls12_381::get_shared_secret(&pk_alice, &sk_64_bob).expect("shared secret bob 64");
552
553 assert_eq!(shared_secret_alice, shared_secret_bob, "64-byte shared secrets should be symmetric");
554
555 let test_message = b"64-byte key compatibility test message";
556 let version = Ver::new(1, 1, 7);
557
558 let encrypted_messages = Message::encrypt(&pk_alice, &*shared_secret_alice, test_message, version)
559 .expect("64-byte key encryption should succeed");
560
561 let decrypted =
562 encrypted_messages[0].decrypt(&*shared_secret_bob).expect("64-byte key decryption should succeed");
563
564 assert_eq!(decrypted, test_message, "64-byte key messages should round-trip correctly");
565 }
566
567 #[tokio::test]
568 async fn test_encrypted_message_reassembler() {
569 let sk_alice = bls12_381::generate_sk();
570 let sk_bob = bls12_381::generate_sk();
571
572 let pk_alice = bls12_381::get_public_key(&sk_alice).expect("get pk alice");
573
574 let shared_secret = bls12_381::get_shared_secret(&pk_alice, &sk_bob).expect("shared secret");
575
576 let test_message = b"Test message for reassembler";
577 let version = Ver::new(1, 1, 8);
578
579 let encrypted_messages =
580 Message::encrypt(&pk_alice, &*shared_secret, test_message, version).expect("encryption should succeed");
581
582 let reassembler = ReedSolomonReassembler::new();
583
584 if encrypted_messages.len() == 1 {
586 let serialized = encrypted_messages[0].to_bytes();
587 let result = reassembler.add_shard(&serialized, &sk_bob).await.expect("reassembly should succeed");
588 assert_eq!(result.map(|(msg, _)| msg), Some(test_message.to_vec()));
589 }
590 }
591
592 #[tokio::test]
593 async fn test_build_shards() {
594 use crate::config::Config;
595
596 let sk = bls12_381::generate_sk();
598
599 let config = Config::new_daemonless(sk);
600
601 let target_sk = bls12_381::generate_sk();
603 let target_pk = bls12_381::get_public_key(&target_sk).expect("get target pk");
604
605 let test_payload = b"Test payload for build_shards functionality";
607
608 let reassembler = ReedSolomonReassembler::new();
610 let shards =
611 reassembler.build_shards(&config, test_payload, &target_pk).await.expect("build_shards should succeed");
612
613 assert!(!shards.is_empty(), "Should create at least one shard");
614
615 for shard in &shards {
617 assert!(shard.len() > 20, "Shard should be large enough to contain header");
618 assert_eq!(&shard[0..3], b"AMA", "Shard should start with AMA magic");
619 }
620 }
621
622 #[tokio::test]
623 async fn test_build_broadcast_shards() {
624 use crate::config::Config;
625
626 let sk = bls12_381::generate_sk();
628
629 let config = Config::new_daemonless(sk);
630
631 let test_payload = b"Test payload for broadcast build_shards functionality";
633
634 let sender_pk = config.get_pk();
636 let reassembler = ReedSolomonReassembler::new();
637 let shards =
638 reassembler.build_shards(&config, test_payload, &sender_pk).await.expect("build_shards should succeed");
639
640 assert!(!shards.is_empty(), "Should create at least one shard");
641
642 for shard in &shards {
644 assert!(shard.len() > 20, "Shard should be large enough to contain header");
645 assert_eq!(&shard[0..3], b"AMA", "Shard should start with AMA magic");
646 }
647 }
648
649 #[test]
650 fn special_compatibility_test() {
651 let src_pk = [
652 169, 28, 174, 71, 198, 45, 103, 77, 154, 232, 203, 244, 17, 34, 237, 129, 66, 93, 94, 78, 141, 226, 51,
653 166, 153, 186, 221, 114, 128, 18, 56, 100, 37, 178, 123, 55, 51, 197, 165, 109, 247, 71, 136, 163, 211,
654 255, 114, 7,
655 ];
656 let src_sk = [
657 9, 150, 210, 55, 28, 239, 9, 161, 68, 62, 249, 195, 10, 127, 86, 17, 19, 41, 143, 189, 9, 205, 85, 30, 245,
658 51, 80, 235, 135, 77, 62, 50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
659 0, 0, 0, 0, 0, 0,
660 ];
661 let dst_pk = [
662 169, 61, 121, 32, 15, 191, 174, 241, 143, 231, 124, 53, 186, 69, 28, 212, 233, 130, 22, 18, 34, 244, 13,
663 106, 212, 255, 255, 47, 184, 178, 49, 111, 90, 90, 184, 84, 230, 115, 5, 143, 205, 208, 136, 138, 2, 252,
664 27, 222,
665 ];
666 let dst_sk = [
667 97, 100, 58, 216, 121, 14, 255, 149, 44, 165, 1, 88, 100, 35, 75, 192, 138, 138, 67, 9, 134, 210, 6, 88,
668 155, 3, 21, 197, 119, 155, 33, 163, 103, 4, 46, 229, 62, 157, 185, 90, 19, 106, 206, 72, 245, 133, 133,
669 183, 132, 250, 78, 92, 40, 160, 223, 244, 177, 53, 84, 31, 128, 185, 176, 166,
670 ];
671 let expected_shared_secret = [
672 145, 211, 143, 152, 146, 107, 226, 184, 193, 178, 234, 80, 224, 201, 239, 165, 131, 124, 241, 141, 235,
673 118, 201, 148, 206, 156, 92, 207, 137, 41, 12, 197, 10, 84, 128, 170, 183, 98, 125, 37, 158, 197, 73, 174,
674 140, 4, 177, 64,
675 ];
676 let enc_msg_bin = [
677 65, 77, 65, 1, 1, 8, 0, 169, 28, 174, 71, 198, 45, 103, 77, 154, 232, 203, 244, 17, 34, 237, 129, 66, 93,
678 94, 78, 141, 226, 51, 166, 153, 186, 221, 114, 128, 18, 56, 100, 37, 178, 123, 55, 51, 197, 165, 109, 247,
679 71, 136, 163, 211, 255, 114, 7, 0, 0, 0, 1, 24, 102, 118, 222, 246, 28, 196, 24, 0, 0, 0, 29, 174, 153,
680 105, 150, 110, 19, 115, 132, 10, 128, 192, 116, 95, 183, 109, 90, 36, 47, 94, 235, 25, 153, 6, 60, 1, 52,
681 179, 109, 43, 112, 31, 229, 100, 116, 222, 232, 93, 45, 153, 183, 142, 186, 250, 130, 127, 209, 21, 245,
682 77, 243, 34, 160, 38, 105, 188, 253, 167, 218, 80,
683 ];
684
685 let computed_shared_secret =
687 bls12_381::get_shared_secret(&dst_pk, &src_sk).expect("Should compute shared secret from src to dst");
688 assert_eq!(
689 *computed_shared_secret, expected_shared_secret,
690 "Computed shared secret should match expected value"
691 );
692
693 let symmetric_shared_secret =
695 bls12_381::get_shared_secret(&src_pk, &dst_sk).expect("Should compute shared secret from dst to src");
696 assert_eq!(
697 *symmetric_shared_secret, expected_shared_secret,
698 "Symmetric shared secret should match expected value"
699 );
700
701 let encrypted_msg =
703 Message::try_from(enc_msg_bin.as_slice()).expect("Should parse encrypted message from binary");
704
705 assert_eq!(encrypted_msg.version, Ver::new(1, 1, 8), "Version should be 1.1.8");
707 assert_eq!(*encrypted_msg.pk, src_pk, "Sender public key should match src_pk");
708 assert_eq!(encrypted_msg.shard_index, 0, "Should be single shard (index 0)");
709 assert_eq!(encrypted_msg.shard_total, 1, "Should be single shard (total 1)");
710 assert_eq!(encrypted_msg.original_size, 29, "Original plaintext size should be 37");
711
712 let decrypted = encrypted_msg.decrypt(&*computed_shared_secret).expect("Should decrypt message successfully");
714
715 assert_eq!(decrypted.len(), 29, "Decrypted length should match original_size");
717 assert!(!decrypted.is_empty(), "Decrypted message should not be empty");
718 }
719}