agentmux_common/jekt_sign/
lan_v2.rs1use super::{BASE64, FIELD_SEP};
9use base64::Engine as _;
10use ed25519_dalek::{Signature, Verifier, VerifyingKey};
11
12const LAN_V2_DOMAIN: &str = "amx-jekt-lan-v2";
13
14fn lan_v2_signed_material(
15 msgid: &str,
16 source_agent: &str,
17 source_uid: &str,
18 target_agent: &str,
19 ts_secs: i64,
20 message: &str,
21) -> String {
22 format!(
23 "{LAN_V2_DOMAIN}{FIELD_SEP}{msgid}{FIELD_SEP}{source_agent}{FIELD_SEP}{source_uid}{FIELD_SEP}\
24 {target_agent}{FIELD_SEP}{ts_secs}{FIELD_SEP}{message}"
25 )
26}
27
28pub fn sign_lan_jekt_v2(
31 private_key: &[u8],
32 msgid: &str,
33 source_agent: &str,
34 source_uid: &str,
35 target_agent: &str,
36 ts_secs: i64,
37 message: &str,
38) -> Option<String> {
39 let seed: [u8; 32] = private_key.try_into().ok()?;
40 let signing_key = ed25519_dalek::SigningKey::from_bytes(&seed);
41 let material = lan_v2_signed_material(msgid, source_agent, source_uid, target_agent, ts_secs, message);
42 let signature = ed25519_dalek::Signer::sign(&signing_key, material.as_bytes());
43 Some(BASE64.encode(signature.to_bytes()))
44}
45
46#[allow(clippy::too_many_arguments)]
49pub fn verify_lan_jekt_v2(
50 public_key: &[u8],
51 msgid: &str,
52 source_agent: &str,
53 source_uid: &str,
54 target_agent: &str,
55 ts_secs: i64,
56 message: &str,
57 sig_b64: &str,
58) -> bool {
59 let Ok(pubkey_arr) = <[u8; 32]>::try_from(public_key) else { return false };
60 let Ok(verifying_key) = VerifyingKey::from_bytes(&pubkey_arr) else { return false };
61 let Ok(sig_bytes) = BASE64.decode(sig_b64) else { return false };
62 let Ok(sig_arr) = <[u8; 64]>::try_from(sig_bytes.as_slice()) else { return false };
63 let signature = Signature::from_bytes(&sig_arr);
64 let material = lan_v2_signed_material(msgid, source_agent, source_uid, target_agent, ts_secs, message);
65 verifying_key.verify(material.as_bytes(), &signature).is_ok()
66}
67
68#[cfg(test)]
69mod tests {
70 use super::*;
71 use crate::jekt_sign::{generate_lan_keypair, sign_lan_jekt, verify_lan_jekt};
72
73 const TS: i64 = 1_790_000_000;
74
75 #[test]
76 fn a_lan_v2_signature_verifies_only_for_the_uid_it_binds() {
77 let (public, private) = generate_lan_keypair([3u8; 32]);
78 let sig = sign_lan_jekt_v2(&private, "m1", "aria", "uid-aria", "lark", TS, "hello").unwrap();
79 assert!(verify_lan_jekt_v2(&public, "m1", "aria", "uid-aria", "lark", TS, "hello", &sig));
80 assert!(!verify_lan_jekt_v2(&public, "m1", "aria", "uid-other", "lark", TS, "hello", &sig), "another UID");
81 assert!(!verify_lan_jekt_v2(&public, "m1", "bob", "uid-aria", "lark", TS, "hello", &sig), "another name");
82 assert!(!verify_lan_jekt_v2(&public, "m1", "aria", "uid-aria", "lark", TS, "edited", &sig), "another message");
83 }
84
85 #[test]
86 fn neither_lan_version_verifies_as_the_other() {
87 let (public, private) = generate_lan_keypair([3u8; 32]);
88 let v1 = sign_lan_jekt(&private, "m1", "aria", "lark", TS, "hello").unwrap();
89 assert!(!verify_lan_jekt_v2(&public, "m1", "aria", "", "lark", TS, "hello", &v1));
90 let v2 = sign_lan_jekt_v2(&private, "m1", "aria", "", "lark", TS, "hello").unwrap();
91 assert!(!verify_lan_jekt(&public, "m1", "aria", "lark", TS, "hello", &v2));
92 }
93
94 #[test]
95 fn lan_and_channel_v2_never_verify_as_each_other() {
96 let (public, private) = generate_lan_keypair([3u8; 32]);
97 let lan = sign_lan_jekt_v2(&private, "m1", "aria", "uid-aria", "lark", TS, "hello").unwrap();
98 assert!(!crate::jekt_sign::verify_channel_jekt_v2(
99 &public, "m1", "aria", "uid-aria", "", "lark", TS, "hello", &lan
100 ));
101 }
102}