1use crate::api::v1::node::models::{
5 LegacyHostInformationV1, LegacyHostInformationV2, LegacyHostInformationV3,
6};
7use crate::error::Error;
8use nym_crypto::asymmetric::ed25519;
9use schemars::JsonSchema;
10use serde::{Deserialize, Serialize};
11use std::fmt::{Display, Formatter};
12use std::ops::Deref;
13
14#[cfg(feature = "client")]
15pub mod client;
16pub mod helpers;
17pub mod v1;
18pub mod v2;
19
20#[cfg(feature = "client")]
21pub use client::Client;
22
23pub type SignedHostInformation = SignedData<crate::api::v1::node::models::HostInformation>;
25pub type SignedLewesProtocol = SignedData<crate::api::v1::lewes_protocol::models::LewesProtocol>;
26
27#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
28pub struct SignedDataHostInfo {
29 pub data: crate::api::v1::node::models::HostInformation,
31 pub signature: String,
32}
33
34#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
35pub struct SignedLewesProtocolInfo {
36 pub data: crate::api::v1::lewes_protocol::models::LewesProtocol,
38 pub signature: String,
39}
40
41#[derive(Debug, Clone, Serialize, Deserialize)]
42pub struct SignedData<T> {
43 pub data: T,
45
46 #[serde(with = "ed25519::bs58_ed25519_signature")]
47 pub signature: ed25519::Signature,
48}
49
50impl<T> SignedData<T> {
51 pub fn new(data: T, key: &ed25519::PrivateKey) -> Result<Self, Error>
52 where
53 T: Serialize,
54 {
55 let plaintext = serde_json::to_string(&data)?;
56
57 let signature = key.sign(plaintext);
58 Ok(SignedData { data, signature })
59 }
60
61 pub fn verify(&self, key: &ed25519::PublicKey) -> bool
62 where
63 T: Serialize,
64 {
65 let Ok(plaintext) = serde_json::to_string(&self.data) else {
66 return false;
67 };
68
69 key.verify(plaintext, &self.signature).is_ok()
70 }
71}
72
73impl SignedHostInformation {
74 pub fn verify_host_information(&self) -> bool {
75 if self.verify(&self.keys.ed25519_identity) {
76 return true;
77 }
78
79 let legacy_v3 = SignedData {
82 data: LegacyHostInformationV3::from(self.data.clone()),
83 signature: self.signature,
84 };
85
86 if legacy_v3.verify(&self.keys.ed25519_identity) {
87 return true;
88 }
89
90 let legacy_v3 = SignedData {
92 data: LegacyHostInformationV3::from(self.data.clone()),
93 signature: self.signature,
94 };
95
96 if legacy_v3.verify(&self.keys.ed25519_identity) {
97 return true;
98 }
99
100 let legacy_v2 = SignedData {
101 data: LegacyHostInformationV2::from(legacy_v3.data),
102 signature: self.signature,
103 };
104
105 if legacy_v2.verify(&self.keys.ed25519_identity) {
106 return true;
107 }
108
109 SignedData {
110 data: LegacyHostInformationV1::from(legacy_v2.data),
111 signature: self.signature,
112 }
113 .verify(&self.keys.ed25519_identity)
114 }
115}
116
117impl<T> Deref for SignedData<T> {
118 type Target = T;
119
120 fn deref(&self) -> &Self::Target {
121 &self.data
122 }
123}
124
125#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema)]
126pub struct ErrorResponse {
127 pub message: String,
128}
129
130impl Display for ErrorResponse {
131 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
132 self.message.fmt(f)
133 }
134}
135
136#[allow(deprecated)]
137#[cfg(test)]
138mod tests {
139 use super::*;
140 use crate::api::v1::node::models::{HostKeys, SphinxKey};
141 use nym_crypto::asymmetric::{ed25519, x25519};
142 use nym_noise_keys::{NoiseVersion, VersionedNoiseKeyV1};
143 use nym_test_utils::helpers::deterministic_rng;
144
145 #[test]
146 fn dummy_signed_host_verification() {
147 let mut rng = deterministic_rng();
148 let ed22519 = ed25519::KeyPair::new(&mut rng);
149 let x25519_sphinx = x25519::KeyPair::new(&mut rng);
150 let x25519_sphinx2 = x25519::KeyPair::new(&mut rng);
151 let x25519_versioned_noise = VersionedNoiseKeyV1 {
152 supported_version: NoiseVersion::V1,
153 x25519_pubkey: *x25519::KeyPair::new(&mut rng).public_key(),
154 };
155
156 let current_rotation_id = 1234;
157
158 let host_info = crate::api::v1::node::models::HostInformation {
160 ip_address: vec!["1.1.1.1".parse().unwrap()],
161 hostname: Some("foomp.com".to_string()),
162 keys: crate::api::v1::node::models::HostKeys {
163 ed25519_identity: *ed22519.public_key(),
164 x25519_sphinx: *x25519_sphinx.public_key(),
165 primary_x25519_sphinx_key: SphinxKey {
166 rotation_id: current_rotation_id,
167 public_key: *x25519_sphinx.public_key(),
168 },
169 pre_announced_x25519_sphinx_key: None,
170 x25519_versioned_noise: None,
171 },
172 };
173
174 let signed_info = SignedHostInformation::new(host_info, ed22519.private_key()).unwrap();
175 assert!(signed_info.verify(ed22519.public_key()));
176 assert!(signed_info.verify_host_information());
177
178 let host_info_with_noise = crate::api::v1::node::models::HostInformation {
179 ip_address: vec!["1.1.1.1".parse().unwrap()],
180 hostname: Some("foomp.com".to_string()),
181 keys: crate::api::v1::node::models::HostKeys {
182 ed25519_identity: *ed22519.public_key(),
183 x25519_sphinx: *x25519_sphinx.public_key(),
184 primary_x25519_sphinx_key: SphinxKey {
185 rotation_id: current_rotation_id,
186 public_key: *x25519_sphinx.public_key(),
187 },
188 pre_announced_x25519_sphinx_key: None,
189 x25519_versioned_noise: Some(x25519_versioned_noise),
190 },
191 };
192
193 let signed_info =
194 SignedHostInformation::new(host_info_with_noise, ed22519.private_key()).unwrap();
195 assert!(signed_info.verify(ed22519.public_key()));
196 assert!(signed_info.verify_host_information());
197
198 let host_info = crate::api::v1::node::models::HostInformation {
200 ip_address: vec!["1.1.1.1".parse().unwrap()],
201 hostname: Some("foomp.com".to_string()),
202 keys: crate::api::v1::node::models::HostKeys {
203 ed25519_identity: *ed22519.public_key(),
204 x25519_sphinx: *x25519_sphinx.public_key(),
205 primary_x25519_sphinx_key: SphinxKey {
206 rotation_id: current_rotation_id,
207 public_key: *x25519_sphinx.public_key(),
208 },
209 pre_announced_x25519_sphinx_key: Some(SphinxKey {
210 rotation_id: current_rotation_id + 1,
211 public_key: *x25519_sphinx2.public_key(),
212 }),
213 x25519_versioned_noise: None,
214 },
215 };
216
217 let signed_info = SignedHostInformation::new(host_info, ed22519.private_key()).unwrap();
218 assert!(signed_info.verify(ed22519.public_key()));
219 assert!(signed_info.verify_host_information());
220
221 let host_info_with_noise = crate::api::v1::node::models::HostInformation {
222 ip_address: vec!["1.1.1.1".parse().unwrap()],
223 hostname: Some("foomp.com".to_string()),
224 keys: crate::api::v1::node::models::HostKeys {
225 ed25519_identity: *ed22519.public_key(),
226 x25519_sphinx: *x25519_sphinx.public_key(),
227 primary_x25519_sphinx_key: SphinxKey {
228 rotation_id: current_rotation_id,
229 public_key: *x25519_sphinx.public_key(),
230 },
231 pre_announced_x25519_sphinx_key: Some(SphinxKey {
232 rotation_id: current_rotation_id + 1,
233 public_key: *x25519_sphinx2.public_key(),
234 }),
235 x25519_versioned_noise: Some(x25519_versioned_noise),
236 },
237 };
238
239 let signed_info =
240 SignedHostInformation::new(host_info_with_noise, ed22519.private_key()).unwrap();
241 assert!(signed_info.verify(ed22519.public_key()));
242 assert!(signed_info.verify_host_information());
243 }
244
245 #[test]
246 fn dummy_legacy_v3_signed_host_verification() {
247 let mut rng = deterministic_rng();
248 let ed22519 = ed25519::KeyPair::new(&mut rng);
249 let x25519_sphinx = x25519::KeyPair::new(&mut rng);
250 let x25519_noise = x25519::KeyPair::new(&mut rng);
251
252 let legacy_info_no_noise = crate::api::v1::node::models::LegacyHostInformationV3 {
253 ip_address: vec!["1.1.1.1".parse().unwrap()],
254 hostname: Some("foomp.com".to_string()),
255 keys: crate::api::v1::node::models::LegacyHostKeysV3 {
256 ed25519_identity: *ed22519.public_key(),
257 x25519_sphinx: *x25519_sphinx.public_key(),
258 x25519_noise: None,
259 },
260 };
261
262 let current_struct = crate::api::v1::node::models::HostInformation {
264 ip_address: vec!["1.1.1.1".parse().unwrap()],
265 hostname: Some("foomp.com".to_string()),
266 keys: HostKeys {
267 ed25519_identity: *ed22519.public_key(),
268 x25519_sphinx: *x25519_sphinx.public_key(),
269 primary_x25519_sphinx_key: SphinxKey {
270 rotation_id: u32::MAX,
271 public_key: *x25519_sphinx.public_key(),
272 },
273 pre_announced_x25519_sphinx_key: None,
274 x25519_versioned_noise: None,
275 },
276 };
277
278 let signature = SignedData::new(legacy_info_no_noise, ed22519.private_key())
280 .unwrap()
281 .signature;
282
283 let current_struct = SignedData {
285 data: current_struct,
286 signature,
287 };
288
289 assert!(!current_struct.verify(ed22519.public_key()));
290 assert!(current_struct.verify_host_information());
291
292 let legacy_info_noise = crate::api::v1::node::models::LegacyHostInformationV3 {
294 ip_address: vec!["1.1.1.1".parse().unwrap()],
295 hostname: Some("foomp.com".to_string()),
296 keys: crate::api::v1::node::models::LegacyHostKeysV3 {
297 ed25519_identity: *ed22519.public_key(),
298 x25519_sphinx: *x25519_sphinx.public_key(),
299 x25519_noise: Some(*x25519_noise.public_key()),
300 },
301 };
302
303 let current_struct_noise = crate::api::v1::node::models::HostInformation {
305 ip_address: vec!["1.1.1.1".parse().unwrap()],
306 hostname: Some("foomp.com".to_string()),
307 keys: HostKeys {
308 ed25519_identity: *ed22519.public_key(),
309 x25519_sphinx: *x25519_sphinx.public_key(),
310 primary_x25519_sphinx_key: SphinxKey {
311 rotation_id: u32::MAX,
312 public_key: *x25519_sphinx.public_key(),
313 },
314 pre_announced_x25519_sphinx_key: None,
315 x25519_versioned_noise: Some(VersionedNoiseKeyV1 {
316 supported_version: NoiseVersion::V1,
317 x25519_pubkey: legacy_info_noise.keys.x25519_noise.unwrap(),
318 }),
319 },
320 };
321
322 let signature_noise = SignedData::new(legacy_info_noise, ed22519.private_key())
325 .unwrap()
326 .signature;
327
328 let current_struct_noise = SignedData {
331 data: current_struct_noise,
332 signature: signature_noise,
333 };
334
335 assert!(!current_struct_noise.verify(ed22519.public_key()));
336 assert!(current_struct_noise.verify_host_information())
337 }
338
339 #[test]
340 fn dummy_legacy_v2_signed_host_verification() {
341 let mut rng = deterministic_rng();
342 let ed22519 = ed25519::KeyPair::new(&mut rng);
343 let x25519_sphinx = x25519::KeyPair::new(&mut rng);
344 let x25519_noise = x25519::KeyPair::new(&mut rng);
345
346 let legacy_info_no_noise = crate::api::v1::node::models::LegacyHostInformationV2 {
347 ip_address: vec!["1.1.1.1".parse().unwrap()],
348 hostname: Some("foomp.com".to_string()),
349 keys: crate::api::v1::node::models::LegacyHostKeysV2 {
350 ed25519_identity: ed22519.public_key().to_base58_string(),
351 x25519_sphinx: x25519_sphinx.public_key().to_base58_string(),
352 x25519_noise: "".to_string(),
353 },
354 };
355
356 let legacy_info_noise = crate::api::v1::node::models::LegacyHostInformationV2 {
357 ip_address: vec!["1.1.1.1".parse().unwrap()],
358 hostname: Some("foomp.com".to_string()),
359 keys: crate::api::v1::node::models::LegacyHostKeysV2 {
360 ed25519_identity: ed22519.public_key().to_base58_string(),
361 x25519_sphinx: x25519_sphinx.public_key().to_base58_string(),
362 x25519_noise: x25519_noise.public_key().to_base58_string(),
363 },
364 };
365
366 let host_info_no_noise = crate::api::v1::node::models::HostInformation {
368 ip_address: legacy_info_no_noise.ip_address.clone(),
369 hostname: legacy_info_no_noise.hostname.clone(),
370 keys: crate::api::v1::node::models::HostKeys {
371 ed25519_identity: legacy_info_no_noise.keys.ed25519_identity.parse().unwrap(),
372 x25519_sphinx: *x25519_sphinx.public_key(),
373 primary_x25519_sphinx_key: SphinxKey {
374 rotation_id: u32::MAX,
375 public_key: *x25519_sphinx.public_key(),
376 },
377 pre_announced_x25519_sphinx_key: None,
378 x25519_versioned_noise: None,
379 },
380 };
381
382 let host_info_noise = crate::api::v1::node::models::HostInformation {
384 ip_address: legacy_info_noise.ip_address.clone(),
385 hostname: legacy_info_noise.hostname.clone(),
386 keys: crate::api::v1::node::models::HostKeys {
387 ed25519_identity: legacy_info_noise.keys.ed25519_identity.parse().unwrap(),
388 x25519_sphinx: *x25519_sphinx.public_key(),
389 primary_x25519_sphinx_key: SphinxKey {
390 rotation_id: u32::MAX,
391 public_key: *x25519_sphinx.public_key(),
392 },
393 pre_announced_x25519_sphinx_key: None,
394 x25519_versioned_noise: Some(VersionedNoiseKeyV1 {
395 supported_version: NoiseVersion::V1,
396 x25519_pubkey: legacy_info_noise.keys.x25519_noise.parse().unwrap(),
397 }),
398 },
399 };
400
401 let signature_no_noise = SignedData::new(legacy_info_no_noise, ed22519.private_key())
403 .unwrap()
404 .signature;
405
406 let signature_noise = SignedData::new(legacy_info_noise, ed22519.private_key())
407 .unwrap()
408 .signature;
409
410 let current_struct_no_noise = SignedData {
412 data: host_info_no_noise,
413 signature: signature_no_noise,
414 };
415
416 let current_struct_noise = SignedData {
417 data: host_info_noise,
418 signature: signature_noise,
419 };
420
421 assert!(!current_struct_no_noise.verify(ed22519.public_key()));
422 assert!(current_struct_no_noise.verify_host_information());
423
424 assert!(!current_struct_noise.verify(ed22519.public_key()));
425 assert!(current_struct_noise.verify_host_information())
426 }
427
428 #[test]
429 fn dummy_legacy_v1_signed_host_verification() {
430 let mut rng = deterministic_rng();
431 let ed22519 = ed25519::KeyPair::new(&mut rng);
432 let x25519_sphinx = x25519::KeyPair::new(&mut rng);
433
434 let legacy_info = crate::api::v1::node::models::LegacyHostInformationV1 {
435 ip_address: vec!["1.1.1.1".parse().unwrap()],
436 hostname: Some("foomp.com".to_string()),
437 keys: crate::api::v1::node::models::LegacyHostKeysV1 {
438 ed25519: ed22519.public_key().to_base58_string(),
439 x25519: x25519_sphinx.public_key().to_base58_string(),
440 },
441 };
442
443 let host_info = crate::api::v1::node::models::HostInformation {
445 ip_address: legacy_info.ip_address.clone(),
446 hostname: legacy_info.hostname.clone(),
447 keys: crate::api::v1::node::models::HostKeys {
448 ed25519_identity: legacy_info.keys.ed25519.parse().unwrap(),
449 x25519_sphinx: *x25519_sphinx.public_key(),
450 primary_x25519_sphinx_key: SphinxKey {
451 rotation_id: u32::MAX,
452 public_key: *x25519_sphinx.public_key(),
453 },
454 pre_announced_x25519_sphinx_key: None,
455 x25519_versioned_noise: None,
456 },
457 };
458
459 let signature = SignedData::new(legacy_info, ed22519.private_key())
461 .unwrap()
462 .signature;
463
464 let current_struct = SignedData {
466 data: host_info,
467 signature,
468 };
469
470 assert!(!current_struct.verify(ed22519.public_key()));
471 assert!(current_struct.verify_host_information())
472 }
473}