confium_net_noise/
keys.rs1use sha2::{Digest, Sha256};
4use snow::Builder;
5
6#[derive(Clone)]
9pub struct NoiseIdentity {
10 pub(crate) private: Vec<u8>,
11 pub(crate) public: [u8; 32],
12}
13
14impl NoiseIdentity {
15 pub fn generate() -> Self {
17 let keypair = Builder::new(noise_params())
18 .generate_keypair()
19 .expect("snow generates keypairs from the OS RNG");
20 let mut public = [0u8; 32];
21 public.copy_from_slice(&keypair.public);
22 Self {
23 private: keypair.private.clone(),
24 public,
25 }
26 }
27
28 pub fn public(&self) -> &[u8; 32] {
30 &self.public
31 }
32
33 pub fn to_hex(&self) -> String {
36 hex(&self.private)
37 }
38
39 pub fn from_hex(hex_private: &str) -> Result<Self, String> {
43 let bytes: [u8; 32] = unhex(hex_private)?
44 .try_into()
45 .map_err(|_| "noise private key must be 32 bytes".to_string())?;
46 let secret = x25519_dalek::StaticSecret::from(bytes);
47 let public = x25519_dalek::PublicKey::from(&secret);
48 Ok(Self {
49 private: bytes.to_vec(),
50 public: {
51 let mut p = [0u8; 32];
52 p.copy_from_slice(public.as_bytes());
53 p
54 },
55 })
56 }
57
58 pub fn fingerprint(&self) -> [u8; 32] {
61 fingerprint_of(&self.public)
62 }
63}
64
65impl std::fmt::Debug for NoiseIdentity {
66 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
67 f.debug_struct("NoiseIdentity")
69 .field("fingerprint", &hex(&fingerprint_of(&self.public)))
70 .finish()
71 }
72}
73
74pub(crate) fn noise_params() -> snow::params::NoiseParams {
75 "Noise_XX_25519_ChaChaPoly_BLAKE2s"
76 .parse()
77 .expect("built-in noise pattern")
78}
79
80pub(crate) fn fingerprint_of(public: &[u8; 32]) -> [u8; 32] {
81 let mut h = Sha256::new();
82 h.update(b"confium-noise-static-v1");
83 h.update(public);
84 h.finalize().into()
85}
86
87pub(crate) fn hex(bytes: &[u8]) -> String {
88 bytes.iter().map(|b| format!("{b:02x}")).collect()
89}
90
91pub(crate) fn unhex(s: &str) -> Result<Vec<u8>, String> {
92 if s.len() % 2 != 0 {
93 return Err("hex string has odd length".into());
94 }
95 (0..s.len() / 2)
96 .map(|i| u8::from_str_radix(&s[2 * i..2 * i + 2], 16).map_err(|e| format!("bad hex: {e}")))
97 .collect()
98}
99
100#[cfg(test)]
101mod tests {
102 use super::*;
103
104 #[test]
105 fn round_trip_identity() {
106 let id = NoiseIdentity::generate();
107 let restored = NoiseIdentity::from_hex(&id.to_hex()).unwrap();
108 assert_eq!(id.public(), restored.public());
109 }
110
111 #[test]
112 fn bad_hex_rejected() {
113 assert!(NoiseIdentity::from_hex("zz").is_err());
114 assert!(NoiseIdentity::from_hex("abc").is_err());
115 }
116}