1use alloc::{string::String, vec::Vec};
2use base64::{Engine as _, engine::general_purpose};
3use ed25519_dalek::{Signer, Verifier, SigningKey, VerifyingKey, Signature};
4use serde_json::Value;
5
6pub fn sign(key: &SigningKey, payload: &[u8]) -> String {
9 let sig: Signature = key.sign(payload);
10 general_purpose::STANDARD.encode(sig.to_bytes())
11}
12
13pub fn verify(pub_key_b64: &str, payload: &[u8], sig_b64: &str) -> bool {
15 let pub_bytes = match general_purpose::STANDARD.decode(pub_key_b64) {
16 Ok(b) => b,
17 Err(_) => return false,
18 };
19 let sig_bytes = match general_purpose::STANDARD.decode(sig_b64) {
20 Ok(b) => b,
21 Err(_) => return false,
22 };
23 let pub_arr: [u8; 32] = match pub_bytes.try_into() {
24 Ok(a) => a,
25 Err(_) => return false,
26 };
27 let sig_arr: [u8; 64] = match sig_bytes.try_into() {
28 Ok(a) => a,
29 Err(_) => return false,
30 };
31 let vk = match VerifyingKey::from_bytes(&pub_arr) {
32 Ok(k) => k,
33 Err(_) => return false,
34 };
35 let sig = Signature::from_bytes(&sig_arr);
36 vk.verify(payload, &sig).is_ok()
37}
38
39pub fn canonical_json(value: &Value) -> Vec<u8> {
42 canonical_json_bytes(value)
43}
44
45fn canonical_json_bytes(value: &Value) -> Vec<u8> {
46 match value {
47 Value::Object(map) => {
48 let mut keys: Vec<&str> = map.keys().map(|s| s.as_str()).collect();
49 keys.sort_unstable();
50 let mut out = Vec::from(b"{" as &[u8]);
51 for (i, k) in keys.iter().enumerate() {
52 let key_json = serde_json::to_vec(&Value::String((*k).into())).unwrap_or_default();
53 out.extend_from_slice(&key_json);
54 out.push(b':');
55 out.extend_from_slice(&canonical_json_bytes(&map[*k]));
56 if i + 1 < keys.len() {
57 out.push(b',');
58 }
59 }
60 out.push(b'}');
61 out
62 }
63 Value::Array(arr) => {
64 let mut out = Vec::from(b"[" as &[u8]);
65 for (i, item) in arr.iter().enumerate() {
66 out.extend_from_slice(&canonical_json_bytes(item));
67 if i + 1 < arr.len() {
68 out.push(b',');
69 }
70 }
71 out.push(b']');
72 out
73 }
74 other => serde_json::to_vec(other).unwrap_or_default(),
75 }
76}
77
78#[cfg(test)]
79mod tests {
80 use super::*;
81
82 fn test_signing_key() -> SigningKey {
84 let seed: [u8; 32] = [
85 1, 2, 3, 4, 5, 6, 7, 8,
86 9, 10, 11, 12, 13, 14, 15, 16,
87 17, 18, 19, 20, 21, 22, 23, 24,
88 25, 26, 27, 28, 29, 30, 31, 32,
89 ];
90 SigningKey::from_bytes(&seed)
91 }
92
93 fn pub_key_b64(sk: &SigningKey) -> String {
95 general_purpose::STANDARD.encode(sk.verifying_key().to_bytes())
96 }
97
98 #[test]
99 fn test_sign_verify_roundtrip() {
100 let sk = test_signing_key();
101 let pk_b64 = pub_key_b64(&sk);
102 let payload = b"hello tpcp";
103
104 let sig = sign(&sk, payload);
105 assert!(
106 verify(&pk_b64, payload, &sig),
107 "valid signature should verify successfully"
108 );
109 }
110
111 #[test]
112 fn test_verify_rejects_tampered() {
113 let sk = test_signing_key();
114 let pk_b64 = pub_key_b64(&sk);
115 let payload = b"original payload";
116 let tampered = b"tampered payload";
117
118 let sig = sign(&sk, payload);
119 assert!(
120 !verify(&pk_b64, tampered, &sig),
121 "signature over different payload must not verify"
122 );
123 }
124
125 #[test]
126 fn test_verify_rejects_malformed_public_key() {
127 let short_key = general_purpose::STANDARD.encode([0u8; 16]);
129 let payload = b"some payload";
130 let sk = test_signing_key();
131 let sig = sign(&sk, payload);
132 assert!(
133 !verify(&short_key, payload, &sig),
134 "malformed (wrong length) public key must fail verification"
135 );
136 }
137
138 #[test]
139 fn test_verify_rejects_invalid_base64_signature() {
140 let sk = test_signing_key();
141 let pk_b64 = pub_key_b64(&sk);
142 let payload = b"test";
143 assert!(
144 !verify(&pk_b64, payload, "not!!!valid===base64"),
145 "invalid base64 signature must fail verification"
146 );
147 }
148
149 #[test]
150 fn test_verify_rejects_invalid_base64_public_key() {
151 let payload = b"test";
152 assert!(
153 !verify("also-not-base64!!!", payload, "AAAA"),
154 "invalid base64 public key must fail verification"
155 );
156 }
157
158 #[test]
159 fn test_canonical_json_empty_object() {
160 let empty: serde_json::Value = serde_json::json!({});
161 let result = canonical_json(&empty);
162 assert_eq!(core::str::from_utf8(&result).unwrap(), "{}");
163 }
164
165 #[test]
166 fn test_canonical_json_nested_objects() {
167 let nested: serde_json::Value = serde_json::json!({
168 "outer": {"z": 1, "a": 2},
169 "arr": [3, 2, 1]
170 });
171 let result = canonical_json(&nested);
172 let s = core::str::from_utf8(&result).unwrap();
173 let pos_arr = s.find("\"arr\"").unwrap();
175 let pos_outer = s.find("\"outer\"").unwrap();
176 assert!(pos_arr < pos_outer, "keys must be sorted: arr before outer");
177 let pos_a = s.find("\"a\"").unwrap();
179 let pos_z = s.find("\"z\"").unwrap();
180 assert!(pos_a < pos_z, "nested keys must be sorted: a before z");
181 }
182
183 #[test]
184 fn test_canonical_json_deterministic() {
185 let a: Value = serde_json::json!({"z": 1, "a": 2, "m": 3});
187 let b: Value = serde_json::json!({"m": 3, "z": 1, "a": 2});
188
189 let bytes_a = canonical_json(&a);
190 let bytes_b = canonical_json(&b);
191
192 assert_eq!(
193 bytes_a, bytes_b,
194 "canonical_json must produce identical output regardless of key insertion order"
195 );
196 let s = core::str::from_utf8(&bytes_a).expect("valid UTF-8");
198 let pos_a = s.find("\"a\"").unwrap();
199 let pos_m = s.find("\"m\"").unwrap();
200 let pos_z = s.find("\"z\"").unwrap();
201 assert!(pos_a < pos_m && pos_m < pos_z, "keys must be sorted alphabetically");
202 }
203}