seal_open/seal_open.rs
1//! Demonstrates ChaCha20-Poly1305 authenticated encryption: seal any
2//! `bincode`-encodable struct into an opaque, authenticated byte blob and open
3//! it back with the same key.
4//!
5//! What ChaCha20-Poly1305 (IETF AEAD) guarantees:
6//! - **Confidentiality** — ciphertext reveals nothing about the plaintext without the key
7//! - **Integrity & authenticity** — any bit-level modification is detected by the Poly1305 tag
8//! - **Semantic security** — a fresh random nonce is generated per seal call, so encrypting
9//! the same value twice produces different ciphertexts
10//!
11//! Format: `nonce (12 B) ‖ ciphertext ‖ Poly1305 tag (16 B)`
12//!
13//! Run with:
14//! ```sh
15//! cargo run --example seal_open --features serialization
16//! ```
17
18use bincode::{Encode, Decode};
19use toolkit_zero::serialization::{seal, open};
20
21// Any struct that derives bincode::Encode + bincode::Decode can be sealed.
22#[derive(Debug, PartialEq, Encode, Decode)]
23struct Payload {
24 user: String,
25 score: u32,
26 tags: Vec<String>,
27}
28
29fn main() {
30 let key = "my-secret-key";
31
32 let original = Payload {
33 user: "alice".into(),
34 score: 9001,
35 tags: vec!["rust".into(), "crypto".into()],
36 };
37
38 println!("Original : {original:?}");
39
40 // ── Seal ──────────────────────────────────────────────────────────────────
41 // String literals and &str both work; K: AsRef<str> handles the conversion.
42 let blob = seal(&original, Some(key)).expect("seal failed");
43 println!("Sealed : {} bytes (nonce ‖ ciphertext ‖ Poly1305 tag)", blob.len());
44
45 // ── Semantic security ─────────────────────────────────────────────────────
46 // A fresh random 12-byte nonce is generated on every seal call, so identical
47 // plaintext + key still produces a different ciphertext each time.
48 let blob2 = seal(&original, Some(key)).expect("seal failed");
49 assert_ne!(blob, blob2, "ciphertexts should differ (different nonces)");
50 println!("Semantic security : two seals of the same value differ ✓");
51
52 // ── Open ──────────────────────────────────────────────────────────────────
53 // Reconstructs Payload from the opaque blob using the same key.
54 let recovered: Payload = open(&blob, Some(key)).expect("open failed");
55 println!("Recovered: {recovered:?}");
56
57 assert_eq!(original, recovered, "round-trip mismatch!");
58 println!("\nRound-trip successful ✓");
59
60 // ── Wrong key rejects ─────────────────────────────────────────────────────
61 let bad: Result<Payload, _> = open(&blob, Some("wrong-key"));
62 assert!(bad.is_err(), "wrong key should fail to open");
63 println!("Wrong-key rejection ✓");
64
65 // ── Default key ───────────────────────────────────────────────────────────
66 // Pass None::<&str> to use the built-in default key.
67 let blob3 = seal(&original, None::<&str>).expect("seal with default key failed");
68 let back3: Payload = open(&blob3, None::<&str>).expect("open with default key failed");
69 assert_eq!(original, back3);
70 println!("Default-key round-trip ✓");
71}