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}