1use alloc::vec::Vec;
2
3use rns_crypto::ed25519::{Ed25519PrivateKey, Ed25519PublicKey};
4use rns_crypto::hkdf::hkdf;
5use rns_crypto::x25519::{X25519PrivateKey, X25519PublicKey};
6
7use super::types::{LinkError, LinkId, LinkMode};
8use crate::constants::{LINK_ECPUBSIZE, LINK_MODE_BYTEMASK, LINK_MTU_BYTEMASK, LINK_MTU_SIZE};
9use crate::hash::truncated_hash;
10
11pub fn compute_link_id(hashable_part: &[u8], extra_bytes_len: usize) -> LinkId {
16 let end = if extra_bytes_len > 0 && hashable_part.len() > extra_bytes_len {
17 hashable_part.len() - extra_bytes_len
18 } else {
19 hashable_part.len()
20 };
21 truncated_hash(&hashable_part[..end])
22}
23
24pub fn build_signalling_bytes(mtu: u32, mode: LinkMode) -> [u8; 3] {
29 let mode_bits = ((mode.mode_byte() << 5) & LINK_MODE_BYTEMASK) as u32;
30 let signalling_value = (mtu & LINK_MTU_BYTEMASK) + (mode_bits << 16);
31 let bytes = signalling_value.to_be_bytes();
32 [bytes[1], bytes[2], bytes[3]]
33}
34
35pub fn parse_signalling_bytes(bytes: &[u8; 3]) -> Result<(u32, LinkMode), LinkError> {
37 let mtu = ((bytes[0] as u32) << 16) | ((bytes[1] as u32) << 8) | (bytes[2] as u32);
38 let mode_byte = (bytes[0] & LINK_MODE_BYTEMASK) >> 5;
39 let mtu_val = mtu & LINK_MTU_BYTEMASK;
40 let mode = LinkMode::from_byte(mode_byte)?;
41 Ok((mtu_val, mode))
42}
43
44pub fn build_linkrequest_data(
46 pub_bytes: &[u8; 32],
47 sig_pub_bytes: &[u8; 32],
48 mtu: Option<u32>,
49 mode: LinkMode,
50) -> Vec<u8> {
51 let mut data = Vec::with_capacity(LINK_ECPUBSIZE + LINK_MTU_SIZE);
52 data.extend_from_slice(pub_bytes);
53 data.extend_from_slice(sig_pub_bytes);
54 if let Some(mtu_val) = mtu {
55 let sig_bytes = build_signalling_bytes(mtu_val, mode);
56 data.extend_from_slice(&sig_bytes);
57 }
58 data
59}
60
61#[allow(clippy::type_complexity)]
63pub fn parse_linkrequest_data(
64 data: &[u8],
65) -> Result<([u8; 32], [u8; 32], Option<u32>, LinkMode), LinkError> {
66 if data.len() != LINK_ECPUBSIZE && data.len() != LINK_ECPUBSIZE + LINK_MTU_SIZE {
67 return Err(LinkError::InvalidData);
68 }
69
70 let mut x25519_pub = [0u8; 32];
71 let mut ed25519_pub = [0u8; 32];
72 x25519_pub.copy_from_slice(&data[..32]);
73 ed25519_pub.copy_from_slice(&data[32..64]);
74
75 if data.len() == LINK_ECPUBSIZE + LINK_MTU_SIZE {
76 let mut sig_bytes = [0u8; 3];
77 sig_bytes.copy_from_slice(&data[LINK_ECPUBSIZE..LINK_ECPUBSIZE + LINK_MTU_SIZE]);
78 let (mtu, mode) = parse_signalling_bytes(&sig_bytes)?;
79 Ok((x25519_pub, ed25519_pub, Some(mtu), mode))
80 } else {
81 Ok((x25519_pub, ed25519_pub, None, LinkMode::Aes256Cbc))
82 }
83}
84
85pub fn link_request_stamp_value(
91 dest_hash: &[u8; 16],
92 data: &[u8],
93) -> Option<rns_crypto::stamp_value::StampValue> {
94 let (peer_pub, peer_sig_pub, _, _) = parse_linkrequest_data(data).ok()?;
95 let mut material = [0u8; 48];
96 material[..16].copy_from_slice(dest_hash);
97 material[16..].copy_from_slice(&peer_sig_pub);
98 Some(rns_crypto::identity::stamp_value(&material, &peer_pub))
99}
100
101pub fn link_request_stamp_valid(
106 dest_hash: &[u8; 16],
107 data: &[u8],
108 cost: crate::stamp::StampCost,
109) -> Option<bool> {
110 let (peer_pub, peer_sig_pub, _, _) = parse_linkrequest_data(data).ok()?;
111 let mut material = [0u8; 48];
112 material[..16].copy_from_slice(dest_hash);
113 material[16..].copy_from_slice(&peer_sig_pub);
114 Some(crate::stamp::stamp_valid(&peer_pub, cost, &material))
115}
116
117pub fn build_lrproof(
121 link_id: &LinkId,
122 pub_bytes: &[u8; 32],
123 sig_pub_bytes: &[u8; 32],
124 sig_prv: &Ed25519PrivateKey,
125 mtu: Option<u32>,
126 mode: LinkMode,
127) -> Vec<u8> {
128 let signalling_bytes = if let Some(mtu_val) = mtu {
129 build_signalling_bytes(mtu_val, mode).to_vec()
130 } else {
131 Vec::new()
132 };
133
134 let mut signed_data = Vec::with_capacity(16 + 32 + 32 + signalling_bytes.len());
135 signed_data.extend_from_slice(link_id);
136 signed_data.extend_from_slice(pub_bytes);
137 signed_data.extend_from_slice(sig_pub_bytes);
138 signed_data.extend_from_slice(&signalling_bytes);
139
140 let signature = sig_prv.sign(&signed_data);
141
142 let mut proof_data = Vec::with_capacity(64 + 32 + signalling_bytes.len());
143 proof_data.extend_from_slice(&signature);
144 proof_data.extend_from_slice(pub_bytes);
145 proof_data.extend_from_slice(&signalling_bytes);
146
147 proof_data
148}
149
150pub fn validate_lrproof(
155 proof_data: &[u8],
156 link_id: &LinkId,
157 peer_sig_pub: &Ed25519PublicKey,
158 peer_sig_pub_bytes: &[u8; 32],
159) -> Result<([u8; 32], Option<u32>, LinkMode), LinkError> {
160 let sig_len = 64;
161 let pub_len = 32;
162
163 if proof_data.len() != sig_len + pub_len
164 && proof_data.len() != sig_len + pub_len + LINK_MTU_SIZE
165 {
166 return Err(LinkError::InvalidData);
167 }
168
169 let mut signature = [0u8; 64];
170 signature.copy_from_slice(&proof_data[..sig_len]);
171
172 let mut peer_pub = [0u8; 32];
173 peer_pub.copy_from_slice(&proof_data[sig_len..sig_len + pub_len]);
174
175 let signalling_bytes = &proof_data[sig_len + pub_len..];
176
177 let (mtu, mode) = if signalling_bytes.len() == LINK_MTU_SIZE {
178 let mut sb = [0u8; 3];
179 sb.copy_from_slice(signalling_bytes);
180 let (m, md) = parse_signalling_bytes(&sb)?;
181 (Some(m), md)
182 } else {
183 (None, LinkMode::Aes256Cbc)
184 };
185
186 let mut signed_data = Vec::with_capacity(16 + 32 + 32 + signalling_bytes.len());
187 signed_data.extend_from_slice(link_id);
188 signed_data.extend_from_slice(&peer_pub);
189 signed_data.extend_from_slice(peer_sig_pub_bytes);
190 signed_data.extend_from_slice(signalling_bytes);
191
192 if peer_sig_pub.verify(&signature, &signed_data) {
193 Ok((peer_pub, mtu, mode))
194 } else {
195 Err(LinkError::InvalidSignature)
196 }
197}
198
199pub fn derive_session_key(
205 shared_key: &[u8; 32],
206 link_id: &LinkId,
207 mode: LinkMode,
208) -> Result<Vec<u8>, LinkError> {
209 let length = mode.derived_key_length();
210 hkdf(length, shared_key, Some(link_id), None).map_err(|_| LinkError::CryptoError)
211}
212
213pub fn perform_key_exchange(
215 prv: &X25519PrivateKey,
216 peer_pub_bytes: &[u8; 32],
217 link_id: &LinkId,
218 mode: LinkMode,
219) -> Result<Vec<u8>, LinkError> {
220 let peer_pub = X25519PublicKey::from_bytes(peer_pub_bytes);
221 let shared_key = prv.exchange(&peer_pub);
222 derive_session_key(&shared_key, link_id, mode)
223}
224
225pub fn pack_rtt(rtt: f64) -> Vec<u8> {
227 let mut data = Vec::with_capacity(9);
228 data.push(0xcb);
229 data.extend_from_slice(&rtt.to_be_bytes());
230 data
231}
232
233pub fn unpack_rtt(data: &[u8]) -> Option<f64> {
238 if data.len() == 9 && data[0] == 0xcb {
239 let mut bytes = [0u8; 8];
240 bytes.copy_from_slice(&data[1..9]);
241 let rtt = f64::from_be_bytes(bytes);
242 (rtt.is_finite() && rtt >= 0.0).then_some(rtt)
243 } else {
244 None
245 }
246}
247
248#[cfg(test)]
249mod tests {
250 use super::*;
251 use rns_crypto::FixedRng;
252
253 #[test]
254 fn test_signalling_bytes_roundtrip() {
255 let mtu = 500u32;
256 let mode = LinkMode::Aes256Cbc;
257 let bytes = build_signalling_bytes(mtu, mode);
258 let (parsed_mtu, parsed_mode) = parse_signalling_bytes(&bytes).unwrap();
259 assert_eq!(parsed_mtu, mtu);
260 assert_eq!(parsed_mode, mode);
261 }
262
263 #[test]
264 fn test_signalling_bytes_aes128() {
265 let mtu = 1234u32;
266 let mode = LinkMode::Aes128Cbc;
267 let bytes = build_signalling_bytes(mtu, mode);
268 let (parsed_mtu, parsed_mode) = parse_signalling_bytes(&bytes).unwrap();
269 assert_eq!(parsed_mtu, mtu);
270 assert_eq!(parsed_mode, mode);
271 }
272
273 #[test]
274 fn test_signalling_bytes_max_mtu() {
275 let mtu = LINK_MTU_BYTEMASK; let mode = LinkMode::Aes256Cbc;
277 let bytes = build_signalling_bytes(mtu, mode);
278 let (parsed_mtu, parsed_mode) = parse_signalling_bytes(&bytes).unwrap();
279 assert_eq!(parsed_mtu, mtu);
280 assert_eq!(parsed_mode, mode);
281 }
282
283 #[test]
284 fn test_linkrequest_data_roundtrip() {
285 let pub_bytes = [0xAAu8; 32];
286 let sig_pub_bytes = [0xBBu8; 32];
287 let data =
288 build_linkrequest_data(&pub_bytes, &sig_pub_bytes, Some(500), LinkMode::Aes256Cbc);
289 assert_eq!(data.len(), LINK_ECPUBSIZE + LINK_MTU_SIZE);
290
291 let (p, s, mtu, mode) = parse_linkrequest_data(&data).unwrap();
292 assert_eq!(p, pub_bytes);
293 assert_eq!(s, sig_pub_bytes);
294 assert_eq!(mtu, Some(500));
295 assert_eq!(mode, LinkMode::Aes256Cbc);
296 }
297
298 #[test]
299 fn test_linkrequest_data_no_mtu() {
300 let pub_bytes = [0xAAu8; 32];
301 let sig_pub_bytes = [0xBBu8; 32];
302 let data = build_linkrequest_data(&pub_bytes, &sig_pub_bytes, None, LinkMode::Aes256Cbc);
303 assert_eq!(data.len(), LINK_ECPUBSIZE);
304
305 let (p, s, mtu, mode) = parse_linkrequest_data(&data).unwrap();
306 assert_eq!(p, pub_bytes);
307 assert_eq!(s, sig_pub_bytes);
308 assert_eq!(mtu, None);
309 assert_eq!(mode, LinkMode::Aes256Cbc); }
311
312 #[test]
313 fn test_linkrequest_data_invalid_size() {
314 let data = [0u8; 10];
315 assert_eq!(parse_linkrequest_data(&data), Err(LinkError::InvalidData));
316 }
317
318 #[test]
319 fn test_compute_link_id_no_extra() {
320 let hashable = [0x42u8; 40];
321 let id = compute_link_id(&hashable, 0);
322 assert_eq!(id.len(), 16);
323 }
324
325 #[test]
326 fn test_compute_link_id_with_extra() {
327 let hashable = [0x42u8; 43]; let id_with_extra = compute_link_id(&hashable, 3);
329 let id_base = compute_link_id(&hashable[..40], 0);
330 assert_eq!(id_with_extra, id_base);
331 }
332
333 #[test]
334 fn test_lrproof_sign_verify() {
335 let mut rng = FixedRng::new(&[0x11; 64]);
336 let sig_prv = Ed25519PrivateKey::generate(&mut rng);
337 let sig_pub = sig_prv.public_key();
338 let sig_pub_bytes = sig_pub.public_bytes();
339
340 let mut rng2 = FixedRng::new(&[0x22; 64]);
341 let x_prv = rns_crypto::x25519::X25519PrivateKey::generate(&mut rng2);
342 let pub_bytes = x_prv.public_key().public_bytes();
343
344 let link_id: LinkId = [0xAA; 16];
345 let mtu = Some(500u32);
346 let mode = LinkMode::Aes256Cbc;
347
348 let proof = build_lrproof(&link_id, &pub_bytes, &sig_pub_bytes, &sig_prv, mtu, mode);
349
350 let result = validate_lrproof(&proof, &link_id, &sig_pub, &sig_pub_bytes);
351 assert!(result.is_ok());
352 let (peer_pub, parsed_mtu, parsed_mode) = result.unwrap();
353 assert_eq!(peer_pub, pub_bytes);
354 assert_eq!(parsed_mtu, mtu);
355 assert_eq!(parsed_mode, mode);
356 }
357
358 #[test]
359 fn test_lrproof_wrong_link_id() {
360 let mut rng = FixedRng::new(&[0x11; 64]);
361 let sig_prv = Ed25519PrivateKey::generate(&mut rng);
362 let sig_pub = sig_prv.public_key();
363 let sig_pub_bytes = sig_pub.public_bytes();
364
365 let pub_bytes = [0x33u8; 32];
366 let link_id: LinkId = [0xAA; 16];
367 let wrong_id: LinkId = [0xBB; 16];
368
369 let proof = build_lrproof(
370 &link_id,
371 &pub_bytes,
372 &sig_pub_bytes,
373 &sig_prv,
374 None,
375 LinkMode::Aes256Cbc,
376 );
377 let result = validate_lrproof(&proof, &wrong_id, &sig_pub, &sig_pub_bytes);
378 assert_eq!(result, Err(LinkError::InvalidSignature));
379 }
380
381 #[test]
382 fn test_derive_session_key_aes128() {
383 let shared = [0x42u8; 32];
384 let link_id = [0xAA; 16];
385 let key = derive_session_key(&shared, &link_id, LinkMode::Aes128Cbc).unwrap();
386 assert_eq!(key.len(), 32);
387 }
388
389 #[test]
390 fn test_derive_session_key_aes256() {
391 let shared = [0x42u8; 32];
392 let link_id = [0xAA; 16];
393 let key = derive_session_key(&shared, &link_id, LinkMode::Aes256Cbc).unwrap();
394 assert_eq!(key.len(), 64);
395 }
396
397 #[test]
398 fn test_rtt_pack_unpack() {
399 let rtt = 0.123456789;
400 let packed = pack_rtt(rtt);
401 assert_eq!(packed.len(), 9);
402 assert_eq!(packed[0], 0xcb);
403 let unpacked = unpack_rtt(&packed).unwrap();
404 assert_eq!(unpacked, rtt);
405 }
406
407 #[test]
408 fn test_rtt_unpack_invalid() {
409 assert_eq!(unpack_rtt(&[0xcb, 0x00]), None);
410 assert_eq!(unpack_rtt(&[0xca, 0, 0, 0, 0, 0, 0, 0, 0]), None);
411 }
412
413 #[test]
414 fn test_rtt_pack_zero() {
415 let packed = pack_rtt(0.0);
416 let unpacked = unpack_rtt(&packed).unwrap();
417 assert_eq!(unpacked, 0.0);
418 }
419
420 #[test]
421 fn unpack_rtt_rejects_non_finite_and_negative_values() {
422 for bad in [
423 f64::NAN,
424 f64::INFINITY,
425 f64::NEG_INFINITY,
426 -1.0,
427 -f64::MIN_POSITIVE,
428 ] {
429 let mut data = vec![0xcb];
430 data.extend_from_slice(&bad.to_be_bytes());
431 assert_eq!(unpack_rtt(&data), None, "rtt {bad}");
432 }
433 assert_eq!(unpack_rtt(&pack_rtt(0.0)), Some(0.0));
434 assert_eq!(unpack_rtt(&pack_rtt(-0.0)), Some(-0.0));
435 assert_eq!(unpack_rtt(&pack_rtt(2.5)), Some(2.5));
436 }
437}