#![doc = include_str!("../README.md")]
#![no_std]
extern crate alloc;
mod keystate;
mod macros;
#[doc(inline)]
pub use keystate::{NodeState, PersistState};
use macros::{
_create_x25519_base_key_type, create_ed25519_keypair_types, create_ed25519_private_key_type,
create_ed25519_public_key_type, create_x25519_keypair_types, create_x25519_private_key_type,
create_x25519_public_key_type,
};
#[derive(Debug, Copy, Clone, PartialEq, Eq, thiserror::Error)]
pub enum ParseError {
#[error("key string was formatted incorrectly")]
InvalidFormat,
#[error("key was the wrong length")]
WrongLength,
#[error("parsed prefix did not match the key type")]
BadPrefix,
}
create_x25519_public_key_type!(
ChallengePublicKey,
"chalpub"
);
create_x25519_public_key_type!(
DerpServerPublicKey,
"derp"
);
create_x25519_keypair_types!(
DiscoPublicKey,
"discokey",
DiscoPrivateKey,
"privkey",
DiscoKeyPair
);
create_x25519_keypair_types!(
MachinePublicKey,
"mkey",
MachinePrivateKey,
"privkey",
MachineKeyPair
);
create_ed25519_keypair_types!(
NetworkLockPublicKey,
"nlpub",
NetworkLockPrivateKey,
"nlpriv",
NetworkLockKeyPair
);
create_x25519_keypair_types!(
NodePublicKey,
"nodekey",
NodePrivateKey,
"privkey",
NodeKeyPair
);
#[cfg(test)]
mod debug_redaction_tests {
use alloc::format;
use super::{
DiscoPrivateKey, MachinePrivateKey, NetworkLockPrivateKey, NodePrivateKey, NodePublicKey,
};
#[test]
fn private_key_debug_is_redacted() {
let secret = [0xABu8; 32];
let m = MachinePrivateKey::from(secret);
let n = NodePrivateKey::from(secret);
let d = DiscoPrivateKey::from(secret);
let nl = NetworkLockPrivateKey::from(secret);
for (label, dbg) in [
("MachinePrivateKey", format!("{m:?}")),
("NodePrivateKey", format!("{n:?}")),
("DiscoPrivateKey", format!("{d:?}")),
("NetworkLockPrivateKey", format!("{nl:?}")),
] {
assert!(
dbg.contains("<redacted>"),
"{label} Debug should be redacted, got {dbg:?}"
);
assert!(
!dbg.contains("abab"),
"{label} Debug leaked secret bytes: {dbg:?}"
);
assert!(
format!("{m}").contains("abab"),
"Display must still expose the key bytes"
);
}
}
#[test]
fn public_key_debug_shows_hex() {
let pubk = NodePublicKey::from([0xABu8; 32]);
let dbg = format!("{pubk:?}");
assert!(
dbg.contains("abab"),
"public key Debug should show hex: {dbg:?}"
);
assert_eq!(dbg, format!("{pubk}"), "public Debug == Display");
}
}
#[cfg(all(test, feature = "serde"))]
mod nl_tests {
use core::str::FromStr;
use super::{NetworkLockKeyPair, NetworkLockPrivateKey, NetworkLockPublicKey};
#[test]
fn nl_key_roundtrip_serde() {
let kp = NetworkLockKeyPair::new();
let priv_str = alloc::format!("{}", kp.private);
let pub_str = alloc::format!("{}", kp.public);
assert!(priv_str.starts_with("nlpriv:"));
assert!(pub_str.starts_with("nlpub:"));
let parsed_priv = NetworkLockPrivateKey::from_str(&priv_str).unwrap();
let parsed_pub = NetworkLockPublicKey::from_str(&pub_str).unwrap();
assert_eq!(parsed_priv, kp.private);
assert_eq!(parsed_pub, kp.public);
}
#[test]
fn nl_public_derivation_is_deterministic() {
let seed = [7u8; 32];
let sk = NetworkLockPrivateKey::from(seed);
let p1 = sk.public_key();
let p2 = sk.public_key();
assert_eq!(p1, p2);
let dalek = ed25519_dalek::SigningKey::from_bytes(&seed)
.verifying_key()
.to_bytes();
assert_eq!(p1.to_bytes(), dalek);
}
#[test]
fn nl_public_matches_rfc8032_test1() {
fn unhex(s: &str) -> [u8; 32] {
let b = s.as_bytes();
let mut out = [0u8; 32];
let mut i = 0;
while i < 32 {
let hi = (b[2 * i] as char).to_digit(16).unwrap() as u8;
let lo = (b[2 * i + 1] as char).to_digit(16).unwrap() as u8;
out[i] = (hi << 4) | lo;
i += 1;
}
out
}
let seed = unhex("9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60");
let public = unhex("d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a");
let derived = NetworkLockPrivateKey::from(seed).public_key();
assert_eq!(derived.to_bytes(), public);
assert_eq!(
alloc::format!("{derived}"),
"nlpub:d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a"
);
}
#[test]
fn nl_keypair_derivation_is_consistent() {
let kp = NetworkLockKeyPair::new();
assert_eq!(kp.public, kp.private.public_key());
let from_priv = NetworkLockKeyPair::from(kp.private);
assert_eq!(from_priv.public, kp.public);
assert_eq!(from_priv.private, kp.private);
}
#[test]
fn nl_key_is_not_x25519() {
let seed = [7u8; 32];
let ed = NetworkLockPrivateKey::from(seed).public_key().to_bytes();
let x = x25519_dalek::PublicKey::from(&x25519_dalek::StaticSecret::from(seed)).to_bytes();
assert_ne!(ed, x);
}
}