direct_e2ee_interop_cli/
direct_e2ee_interop_cli.rs1use anp::authentication::{create_did_wba_document, DidDocumentOptions, DidProfile};
2use anp::direct_e2ee::{
3 build_prekey_bundle, signed_prekey_from_private_key, ApplicationPlaintext, DirectE2eeSession,
4 DirectEnvelopeMetadata,
5};
6use anp::PrivateKeyMaterial;
7use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine as _};
8use serde_json::json;
9use x25519_dalek::{PublicKey as X25519PublicKey, StaticSecret as X25519StaticSecret};
10
11fn main() {
12 let args = std::env::args().skip(1).collect::<Vec<String>>();
13 if args.first().map(String::as_str) != Some("fixture") {
14 eprintln!("Usage: cargo run --example direct_e2ee_interop_cli -- fixture");
15 std::process::exit(1);
16 }
17
18 let alice = create_did_wba_document(
19 "a.example",
20 DidDocumentOptions::default()
21 .with_profile(DidProfile::E1)
22 .with_path_segments(["agents", "alice"]),
23 )
24 .expect("alice did");
25 let bob = create_did_wba_document(
26 "b.example",
27 DidDocumentOptions::default()
28 .with_profile(DidProfile::E1)
29 .with_path_segments(["agents", "bob"]),
30 )
31 .expect("bob did");
32
33 let alice_did = alice.did_document["id"].as_str().unwrap().to_string();
34 let bob_did = bob.did_document["id"].as_str().unwrap().to_string();
35
36 let alice_static = load_x25519_secret(&alice.keys["key-3"].private_key_pem);
37 let bob_static = load_x25519_secret(&bob.keys["key-3"].private_key_pem);
38 let bob_spk = X25519StaticSecret::from([55u8; 32]);
39
40 let bob_signing_key =
41 PrivateKeyMaterial::from_pem(&bob.keys["key-1"].private_key_pem).expect("bob signing key");
42 let bundle = build_prekey_bundle(
43 "bundle-001",
44 &bob_did,
45 &format!("{bob_did}#key-3"),
46 signed_prekey_from_private_key("spk-001", &bob_spk, "2026-04-07T00:00:00Z"),
47 &bob_signing_key,
48 &format!("{bob_did}#key-1"),
49 Some("2026-03-31T09:58:58Z"),
50 )
51 .expect("bundle");
52
53 let init_metadata = metadata(&alice_did, &bob_did, "msg-init");
54 let (mut alice_session, _pending, init_body) = DirectE2eeSession::initiate_session(
55 &init_metadata,
56 "op-init",
57 &format!("{alice_did}#key-3"),
58 &alice_static,
59 &bundle,
60 &X25519PublicKey::from(&bob_static).to_bytes(),
61 &X25519PublicKey::from(&bob_spk).to_bytes(),
62 &ApplicationPlaintext::new_text("text/plain", "hello bob"),
63 )
64 .expect("initiate");
65
66 let follow_up_metadata = metadata(&alice_did, &bob_did, "msg-2");
67 let (_pending, cipher_body) = DirectE2eeSession::encrypt_follow_up(
68 &mut alice_session,
69 &follow_up_metadata,
70 "op-2",
71 &ApplicationPlaintext::new_json("application/json", json!({"event": "wave"})),
72 )
73 .expect("follow up");
74
75 println!(
76 "{}",
77 serde_json::to_string(&json!({
78 "alice_did_document": alice.did_document,
79 "bob_did_document": bob.did_document,
80 "bundle": serde_json::to_value(&bundle).expect("bundle json"),
81 "bob_static_private_key_b64u": encode_secret(&bob_static),
82 "bob_signed_prekey_private_key_b64u": encode_secret(&bob_spk),
83 "init_metadata": serde_json::to_value(&init_metadata).expect("metadata json"),
84 "follow_up_metadata": serde_json::to_value(&follow_up_metadata).expect("metadata json"),
85 "init_body": serde_json::to_value(&init_body).expect("init json"),
86 "cipher_body": serde_json::to_value(&cipher_body).expect("cipher json"),
87 "init_plaintext": json!({"application_content_type": "text/plain", "text": "hello bob"}),
88 "follow_up_plaintext": json!({"application_content_type": "application/json", "payload": {"event": "wave"}}),
89 }))
90 .expect("fixture json")
91 );
92}
93
94fn metadata(sender: &str, recipient: &str, message_id: &str) -> DirectEnvelopeMetadata {
95 DirectEnvelopeMetadata {
96 sender_did: sender.to_owned(),
97 recipient_did: recipient.to_owned(),
98 message_id: message_id.to_owned(),
99 profile: "anp.direct.e2ee.v1".to_owned(),
100 security_profile: "direct-e2ee".to_owned(),
101 }
102}
103
104fn load_x25519_secret(pem: &str) -> X25519StaticSecret {
105 match PrivateKeyMaterial::from_pem(pem).expect("private key") {
106 PrivateKeyMaterial::X25519(secret) => secret,
107 _ => panic!("expected X25519 private key"),
108 }
109}
110
111fn encode_secret(secret: &X25519StaticSecret) -> String {
112 URL_SAFE_NO_PAD.encode(secret.to_bytes())
113}