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