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