iuna

iuna

iuna - experimental mainnet-candidate protocol
git clone https://getiuna.org/git/iuna.git
Log | Files | Refs | README | LICENSE

identity.rs (4243B)


      1 use std::{
      2     collections::BTreeMap,
      3     sync::{Mutex as StdMutex, OnceLock},
      4 };
      5 
      6 use anyhow::Result;
      7 use secrecy::{ExposeSecret, SecretBox};
      8 
      9 use crate::{
     10     app::{GossipEnvelope, NETWORK_ID},
     11     domain::{ed25519_public_key, sign_ed25519, verify_ed25519},
     12 };
     13 
     14 use super::GossipNetwork;
     15 
     16 static NODE_SIGNING_KEYS: OnceLock<StdMutex<BTreeMap<String, SecretBox<[u8; 32]>>>> =
     17     OnceLock::new();
     18 
     19 pub(super) fn new_node_id() -> String {
     20     let signing_seed = SecretBox::init_with_mut(|bytes: &mut [u8; 32]| {
     21         getrandom::getrandom(bytes).expect("secure randomness unavailable for p2p node id");
     22     });
     23     let node_id = hex_encode(&ed25519_public_key(signing_seed.expose_secret()));
     24     node_signing_keys()
     25         .lock()
     26         .expect("node signing key registry mutex poisoned")
     27         .insert(node_id.clone(), signing_seed);
     28     node_id
     29 }
     30 
     31 fn node_signing_keys() -> &'static StdMutex<BTreeMap<String, SecretBox<[u8; 32]>>> {
     32     NODE_SIGNING_KEYS.get_or_init(|| StdMutex::new(BTreeMap::new()))
     33 }
     34 
     35 pub(super) fn hex_encode(bytes: &[u8]) -> String {
     36     const HEX: &[u8; 16] = b"0123456789abcdef";
     37     let mut encoded = String::with_capacity(bytes.len() * 2);
     38     for byte in bytes {
     39         encoded.push(HEX[(byte >> 4) as usize] as char);
     40         encoded.push(HEX[(byte & 0x0f) as usize] as char);
     41     }
     42     encoded
     43 }
     44 
     45 pub(super) fn decode_hex_array<const N: usize>(value: &str) -> Result<[u8; N]> {
     46     if value.len() != N * 2 {
     47         anyhow::bail!("hex value has {} chars, expected {}", value.len(), N * 2);
     48     }
     49     let mut bytes = [0_u8; N];
     50     for (index, chunk) in value.as_bytes().chunks_exact(2).enumerate() {
     51         let high = hex_nibble(chunk[0])?;
     52         let low = hex_nibble(chunk[1])?;
     53         bytes[index] = (high << 4) | low;
     54     }
     55     Ok(bytes)
     56 }
     57 
     58 pub(super) fn hex_nibble(byte: u8) -> Result<u8> {
     59     match byte {
     60         b'0'..=b'9' => Ok(byte - b'0'),
     61         b'a'..=b'f' => Ok(byte - b'a' + 10),
     62         b'A'..=b'F' => Ok(byte - b'A' + 10),
     63         _ => anyhow::bail!("invalid hex digit"),
     64     }
     65 }
     66 
     67 pub(super) fn new_verification_nonce() -> String {
     68     let mut bytes = [0_u8; 32];
     69     getrandom::getrandom(&mut bytes)
     70         .expect("secure randomness unavailable for p2p verification nonce");
     71     hex_encode(&bytes)
     72 }
     73 
     74 pub(super) fn peer_verification_payload(address: &str, nonce: &str, node_id: &str) -> String {
     75     format!("iuna-peer-verification:v1:{NETWORK_ID}:{node_id}:{address}:{nonce}")
     76 }
     77 
     78 pub(super) fn peer_verification_response(
     79     network: &GossipNetwork,
     80     address: &str,
     81     nonce: &str,
     82 ) -> Option<GossipEnvelope> {
     83     peer_verification_response_for_node_id(&network.inner.node_id, address, nonce)
     84 }
     85 
     86 pub(super) fn peer_verification_response_for_node_id(
     87     node_id: &str,
     88     address: &str,
     89     nonce: &str,
     90 ) -> Option<GossipEnvelope> {
     91     let keys = node_signing_keys()
     92         .lock()
     93         .expect("node signing key registry mutex poisoned");
     94     let signing_seed = keys.get(node_id)?;
     95     let payload = peer_verification_payload(address, nonce, node_id);
     96     let signature = sign_ed25519(signing_seed.expose_secret(), payload.as_bytes());
     97     Some(GossipEnvelope::PeerVerificationResponse {
     98         address: address.to_string(),
     99         nonce: nonce.to_string(),
    100         node_id: node_id.to_string(),
    101         signature: hex_encode(&signature),
    102     })
    103 }
    104 
    105 pub(super) fn peer_verification_response_is_valid(
    106     response_address: &str,
    107     response_nonce: &str,
    108     response_node_id: &str,
    109     signature: &str,
    110     expected_address: &str,
    111     expected_nonce: &str,
    112     expected_node_id: &str,
    113 ) -> bool {
    114     if response_address != expected_address
    115         || response_nonce != expected_nonce
    116         || response_node_id != expected_node_id
    117     {
    118         return false;
    119     }
    120     let public_key = match decode_hex_array::<32>(response_node_id) {
    121         Ok(public_key) => public_key,
    122         Err(_) => return false,
    123     };
    124     let signature = match decode_hex_array::<64>(signature) {
    125         Ok(signature) => signature,
    126         Err(_) => return false,
    127     };
    128     verify_ed25519(
    129         &public_key,
    130         peer_verification_payload(expected_address, expected_nonce, expected_node_id).as_bytes(),
    131         &signature,
    132         "peer verification",
    133     )
    134     .is_ok()
    135 }