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