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
150#[cfg(all(test, feature = "serde"))]
151mod nl_tests {
152 use core::str::FromStr;
153
154 use super::{NetworkLockKeyPair, NetworkLockPrivateKey, NetworkLockPublicKey};
155
156 #[test]
158 fn nl_key_roundtrip_serde() {
159 let kp = NetworkLockKeyPair::new();
160
161 let priv_str = alloc::format!("{}", kp.private);
162 let pub_str = alloc::format!("{}", kp.public);
163 assert!(priv_str.starts_with("nlpriv:"));
164 assert!(pub_str.starts_with("nlpub:"));
165
166 let parsed_priv = NetworkLockPrivateKey::from_str(&priv_str).unwrap();
167 let parsed_pub = NetworkLockPublicKey::from_str(&pub_str).unwrap();
168 assert_eq!(parsed_priv, kp.private);
169 assert_eq!(parsed_pub, kp.public);
170 }
171
172 #[test]
175 fn nl_public_derivation_is_deterministic() {
176 let seed = [7u8; 32];
177 let sk = NetworkLockPrivateKey::from(seed);
178 let p1 = sk.public_key();
179 let p2 = sk.public_key();
180 assert_eq!(p1, p2);
181
182 let dalek = ed25519_dalek::SigningKey::from_bytes(&seed)
183 .verifying_key()
184 .to_bytes();
185 assert_eq!(p1.to_bytes(), dalek);
186 }
187
188 #[test]
194 fn nl_public_matches_rfc8032_test1() {
195 fn unhex(s: &str) -> [u8; 32] {
196 let b = s.as_bytes();
197 let mut out = [0u8; 32];
198 let mut i = 0;
199 while i < 32 {
200 let hi = (b[2 * i] as char).to_digit(16).unwrap() as u8;
201 let lo = (b[2 * i + 1] as char).to_digit(16).unwrap() as u8;
202 out[i] = (hi << 4) | lo;
203 i += 1;
204 }
205 out
206 }
207 let seed = unhex("9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60");
208 let public = unhex("d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a");
209 let derived = NetworkLockPrivateKey::from(seed).public_key();
210 assert_eq!(derived.to_bytes(), public);
211
212 assert_eq!(
215 alloc::format!("{derived}"),
216 "nlpub:d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a"
217 );
218 }
219
220 #[test]
223 fn nl_keypair_derivation_is_consistent() {
224 let kp = NetworkLockKeyPair::new();
225 assert_eq!(kp.public, kp.private.public_key());
226 let from_priv = NetworkLockKeyPair::from(kp.private);
227 assert_eq!(from_priv.public, kp.public);
228 assert_eq!(from_priv.private, kp.private);
229 }
230
231 #[test]
234 fn nl_key_is_not_x25519() {
235 let seed = [7u8; 32];
236 let ed = NetworkLockPrivateKey::from(seed).public_key().to_bytes();
237 let x = x25519_dalek::PublicKey::from(&x25519_dalek::StaticSecret::from(seed)).to_bytes();
238 assert_ne!(ed, x);
239 }
240}