1#![doc = include_str!("../README.md")]
2#![no_std]
3
4extern crate alloc;
5
6mod keystate;
7mod macros;
8
9#[doc(inline)]
10pub use keystate::{NodeState, PersistState};
11use macros::{
12 _create_x25519_base_key_type, create_ed25519_keypair_types, create_ed25519_private_key_type,
13 create_ed25519_public_key_type, create_x25519_keypair_types, create_x25519_private_key_type,
14 create_x25519_public_key_type,
15};
16
17#[derive(Debug, Copy, Clone, PartialEq, Eq, thiserror::Error)]
19pub enum ParseError {
20 #[error("key string was formatted incorrectly")]
22 InvalidFormat,
23
24 #[error("key was the wrong length")]
26 WrongLength,
27
28 #[error("parsed prefix did not match the key type")]
30 BadPrefix,
31}
32
33create_x25519_public_key_type!(
36 ChallengePublicKey,
38 "chalpub"
39);
40
41create_x25519_public_key_type!(
44 DerpServerPublicKey,
46 "derp"
47);
48create_x25519_keypair_types!(
49 DiscoPublicKey,
51 "discokey",
52 DiscoPrivateKey,
54 "privkey",
55 DiscoKeyPair
57);
58
59create_x25519_keypair_types!(
60 MachinePublicKey,
64 "mkey",
65 MachinePrivateKey,
67 "privkey",
68 MachineKeyPair
71);
72
73create_ed25519_keypair_types!(
74 NetworkLockPublicKey,
76 "nlpub",
77 NetworkLockPrivateKey,
79 "nlpriv",
80 NetworkLockKeyPair
82);
83
84create_x25519_keypair_types!(
85 NodePublicKey,
87 "nodekey",
88 NodePrivateKey,
90 "privkey",
91 NodeKeyPair
93);
94
95#[cfg(test)]
96mod debug_redaction_tests {
97 use alloc::format;
98
99 use super::{
100 DiscoPrivateKey, MachinePrivateKey, NetworkLockPrivateKey, NodePrivateKey, NodePublicKey,
101 };
102
103 #[test]
106 fn private_key_debug_is_redacted() {
107 let secret = [0xABu8; 32];
108
109 let m = MachinePrivateKey::from(secret);
110 let n = NodePrivateKey::from(secret);
111 let d = DiscoPrivateKey::from(secret);
112 let nl = NetworkLockPrivateKey::from(secret);
113
114 for (label, dbg) in [
115 ("MachinePrivateKey", format!("{m:?}")),
116 ("NodePrivateKey", format!("{n:?}")),
117 ("DiscoPrivateKey", format!("{d:?}")),
118 ("NetworkLockPrivateKey", format!("{nl:?}")),
119 ] {
120 assert!(
121 dbg.contains("<redacted>"),
122 "{label} Debug should be redacted, got {dbg:?}"
123 );
124 assert!(
125 !dbg.contains("abab"),
126 "{label} Debug leaked secret bytes: {dbg:?}"
127 );
128 assert!(
131 format!("{m}").contains("abab"),
132 "Display must still expose the key bytes"
133 );
134 }
135 }
136
137 #[test]
139 fn public_key_debug_shows_hex() {
140 let pubk = NodePublicKey::from([0xABu8; 32]);
141 let dbg = format!("{pubk:?}");
142 assert!(
143 dbg.contains("abab"),
144 "public key Debug should show hex: {dbg:?}"
145 );
146 assert_eq!(dbg, format!("{pubk}"), "public Debug == Display");
147 }
148
149 #[test]
154 fn private_key_zeroize_wipes_bytes() {
155 use zeroize::Zeroize;
156
157 let mut k = NodePrivateKey::from([0xABu8; 32]);
158 assert_eq!(
159 k.to_bytes(),
160 [0xABu8; 32],
161 "precondition: key holds its bytes"
162 );
163 k.zeroize();
164 assert_eq!(
165 k.to_bytes(),
166 [0u8; 32],
167 "zeroize must wipe the secret bytes to zero"
168 );
169 }
170
171 #[test]
175 fn public_key_derivation_borrows_private() {
176 let k = NodePrivateKey::from([0x11u8; 32]);
177 let p1 = k.public_key();
178 let p2 = k.public_key();
180 assert_eq!(p1, p2, "repeated derivation from the same key agrees");
181 assert_eq!(k.clone().public_key(), p1);
183 }
184}
185
186#[cfg(all(test, feature = "serde"))]
187mod nl_tests {
188 use core::str::FromStr;
189
190 use super::{NetworkLockKeyPair, NetworkLockPrivateKey, NetworkLockPublicKey};
191
192 #[test]
194 fn nl_key_roundtrip_serde() {
195 let kp = NetworkLockKeyPair::new();
196
197 let priv_str = alloc::format!("{}", kp.private);
198 let pub_str = alloc::format!("{}", kp.public);
199 assert!(priv_str.starts_with("nlpriv:"));
200 assert!(pub_str.starts_with("nlpub:"));
201
202 let parsed_priv = NetworkLockPrivateKey::from_str(&priv_str).unwrap();
203 let parsed_pub = NetworkLockPublicKey::from_str(&pub_str).unwrap();
204 assert_eq!(parsed_priv, kp.private);
205 assert_eq!(parsed_pub, kp.public);
206 }
207
208 #[test]
211 fn nl_public_derivation_is_deterministic() {
212 let seed = [7u8; 32];
213 let sk = NetworkLockPrivateKey::from(seed);
214 let p1 = sk.public_key();
215 let p2 = sk.public_key();
216 assert_eq!(p1, p2);
217
218 let dalek = ed25519_dalek::SigningKey::from_bytes(&seed)
219 .verifying_key()
220 .to_bytes();
221 assert_eq!(p1.to_bytes(), dalek);
222 }
223
224 #[test]
230 fn nl_public_matches_rfc8032_test1() {
231 fn unhex(s: &str) -> [u8; 32] {
232 let b = s.as_bytes();
233 let mut out = [0u8; 32];
234 let mut i = 0;
235 while i < 32 {
236 let hi = (b[2 * i] as char).to_digit(16).unwrap() as u8;
237 let lo = (b[2 * i + 1] as char).to_digit(16).unwrap() as u8;
238 out[i] = (hi << 4) | lo;
239 i += 1;
240 }
241 out
242 }
243 let seed = unhex("9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60");
244 let public = unhex("d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a");
245 let derived = NetworkLockPrivateKey::from(seed).public_key();
246 assert_eq!(derived.to_bytes(), public);
247
248 assert_eq!(
251 alloc::format!("{derived}"),
252 "nlpub:d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a"
253 );
254 }
255
256 #[test]
259 fn nl_keypair_derivation_is_consistent() {
260 let kp = NetworkLockKeyPair::new();
261 assert_eq!(kp.public, kp.private.public_key());
262 let from_priv = NetworkLockKeyPair::from(kp.private.clone());
265 assert_eq!(from_priv.public, kp.public);
266 assert_eq!(from_priv.private, kp.private);
267 }
268
269 #[test]
272 fn nl_key_is_not_x25519() {
273 let seed = [7u8; 32];
274 let ed = NetworkLockPrivateKey::from(seed).public_key().to_bytes();
275 let x = x25519_dalek::PublicKey::from(&x25519_dalek::StaticSecret::from(seed)).to_bytes();
276 assert_ne!(ed, x);
277 }
278}