1use rns_crypto::hkdf;
7use rns_crypto::identity::Identity;
8use rns_crypto::sha256;
9
10pub const IFAC_SALT: [u8; 32] = [
12 0xad, 0xf5, 0x4d, 0x88, 0x2c, 0x9a, 0x9b, 0x80, 0x77, 0x1e, 0xb4, 0x99, 0x5d, 0x70, 0x2d, 0x4a,
13 0x3e, 0x73, 0x33, 0x91, 0xb2, 0xa0, 0xf5, 0x3f, 0x41, 0x6d, 0x9f, 0x90, 0x7e, 0x55, 0xcf, 0xf8,
14];
15
16pub const IFAC_MIN_SIZE: usize = 1;
17
18pub struct IfacState {
20 pub size: usize,
21 pub key: [u8; 64],
22 pub identity: Identity,
23}
24
25pub fn derive_ifac(
35 netname: Option<&str>,
36 netkey: Option<&str>,
37 size: usize,
38) -> Result<IfacState, String> {
39 let mut ifac_origin = Vec::new();
40
41 if let Some(name) = netname {
42 let hash = sha256::sha256(name.as_bytes());
43 ifac_origin.extend_from_slice(&hash);
44 }
45
46 if let Some(key) = netkey {
47 let hash = sha256::sha256(key.as_bytes());
48 ifac_origin.extend_from_slice(&hash);
49 }
50
51 let ifac_origin_hash = sha256::sha256(&ifac_origin);
52 let ifac_key_vec = hkdf::hkdf(64, &ifac_origin_hash, Some(&IFAC_SALT), None)
53 .map_err(|err| format!("failed to derive IFAC key: {}", err))?;
54
55 let mut ifac_key = [0u8; 64];
56 ifac_key.copy_from_slice(&ifac_key_vec);
57
58 let identity = Identity::from_private_key(&ifac_key);
59
60 Ok(IfacState {
61 size: size.max(IFAC_MIN_SIZE),
62 key: ifac_key,
63 identity,
64 })
65}
66
67pub fn mask_outbound(raw: &[u8], state: &IfacState) -> Vec<u8> {
75 if raw.len() < 2 {
76 return raw.to_vec();
77 }
78
79 let sig = match state.identity.sign(raw) {
81 Ok(sig) => sig,
82 Err(err) => {
83 log::warn!("failed to sign outbound IFAC packet: {}", err);
84 return raw.to_vec();
85 }
86 };
87 let ifac = &sig[64 - state.size..];
88
89 let mask = match hkdf::hkdf(raw.len() + state.size, ifac, Some(&state.key), None) {
91 Ok(mask) => mask,
92 Err(err) => {
93 log::warn!("failed to derive outbound IFAC mask: {}", err);
94 return raw.to_vec();
95 }
96 };
97
98 let mut new_raw = Vec::with_capacity(raw.len() + state.size);
100 new_raw.push(raw[0] | 0x80); new_raw.push(raw[1]);
102 new_raw.extend_from_slice(ifac);
103 new_raw.extend_from_slice(&raw[2..]);
104
105 let mut masked = Vec::with_capacity(new_raw.len());
107 for (i, &byte) in new_raw.iter().enumerate() {
108 if i == 0 {
109 masked.push((byte ^ mask[i]) | 0x80);
111 } else if i == 1 || i > state.size + 1 {
112 masked.push(byte ^ mask[i]);
114 } else {
115 masked.push(byte);
117 }
118 }
119
120 masked
121}
122
123pub fn unmask_inbound(raw: &[u8], state: &IfacState) -> Option<Vec<u8>> {
127 if raw.len() <= 2 + state.size {
129 return None;
130 }
131
132 if raw[0] & 0x80 != 0x80 {
134 return None;
135 }
136
137 let ifac = &raw[2..2 + state.size];
139
140 let mask = match hkdf::hkdf(raw.len(), ifac, Some(&state.key), None) {
142 Ok(mask) => mask,
143 Err(err) => {
144 log::warn!("failed to derive inbound IFAC mask: {}", err);
145 return None;
146 }
147 };
148
149 let mut unmasked = Vec::with_capacity(raw.len());
151 for (i, &byte) in raw.iter().enumerate() {
152 if i <= 1 || i > state.size + 1 {
153 unmasked.push(byte ^ mask[i]);
155 } else {
156 unmasked.push(byte);
158 }
159 }
160
161 let flags_cleared = unmasked[0] & 0x7F;
163 let hops = unmasked[1];
164
165 let mut new_raw = Vec::with_capacity(raw.len() - state.size);
167 new_raw.push(flags_cleared);
168 new_raw.push(hops);
169 new_raw.extend_from_slice(&unmasked[2 + state.size..]);
170
171 let expected_sig = match state.identity.sign(&new_raw) {
173 Ok(sig) => sig,
174 Err(err) => {
175 log::warn!("failed to verify inbound IFAC packet: {}", err);
176 return None;
177 }
178 };
179 let expected_ifac = &expected_sig[64 - state.size..];
180
181 if ifac == expected_ifac {
182 Some(new_raw)
183 } else {
184 None
185 }
186}
187
188#[cfg(test)]
189mod tests {
190 use super::*;
191
192 #[test]
193 fn derive_ifac_netname_only() {
194 let state = derive_ifac(Some("testnet"), None, 8).unwrap();
195 assert_eq!(state.size, 8);
196 assert_eq!(state.key.len(), 64);
197 assert!(state.identity.get_private_key().is_some());
199 }
200
201 #[test]
202 fn derive_ifac_netkey_only() {
203 let state = derive_ifac(None, Some("secretpassword"), 16).unwrap();
204 assert_eq!(state.size, 16);
205 assert!(state.identity.get_private_key().is_some());
206 }
207
208 #[test]
209 fn derive_ifac_both() {
210 let state = derive_ifac(Some("testnet"), Some("mypassword"), 8).unwrap();
211 assert_eq!(state.size, 8);
212 let state2 = derive_ifac(Some("testnet"), Some("mypassword"), 8).unwrap();
214 assert_eq!(state.key, state2.key);
215 }
216
217 #[test]
218 fn mask_unmask_roundtrip() {
219 let state = derive_ifac(Some("testnet"), Some("password"), 8).unwrap();
220
221 let mut raw = vec![0x00, 0x01]; raw.extend_from_slice(&[0x42u8; 32]);
224
225 let masked = mask_outbound(&raw, &state);
226 assert_ne!(masked, raw);
227 assert!(masked.len() > raw.len()); let recovered = unmask_inbound(&masked, &state).expect("unmask should succeed");
230 assert_eq!(recovered, raw);
231 }
232
233 #[test]
234 fn mask_sets_ifac_flag() {
235 let state = derive_ifac(Some("testnet"), None, 8).unwrap();
236
237 let raw = vec![0x00, 0x01, 0x42, 0x43, 0x44, 0x45];
238 let masked = mask_outbound(&raw, &state);
239
240 assert_eq!(masked[0] & 0x80, 0x80);
242 }
243
244 #[test]
245 fn unmask_rejects_bad_ifac() {
246 let state = derive_ifac(Some("testnet"), Some("password"), 8).unwrap();
247
248 let mut raw = vec![0x00, 0x01];
249 raw.extend_from_slice(&[0x42u8; 32]);
250
251 let mut masked = mask_outbound(&raw, &state);
252
253 masked[3] ^= 0xFF;
255
256 let result = unmask_inbound(&masked, &state);
257 assert!(result.is_none());
258 }
259
260 #[test]
261 fn unmask_rejects_missing_flag() {
262 let state = derive_ifac(Some("testnet"), None, 8).unwrap();
263
264 let raw = vec![
266 0x00, 0x01, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x50,
267 ];
268 let result = unmask_inbound(&raw, &state);
269 assert!(result.is_none());
270 }
271
272 #[test]
273 fn unmask_rejects_too_short() {
274 let state = derive_ifac(Some("testnet"), None, 8).unwrap();
275
276 let raw = vec![0x80, 0x01, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48];
278 let result = unmask_inbound(&raw, &state);
279 assert!(result.is_none());
280 }
281}