iuna

iuna - experimental devnet protocol
git clone https://iuna.jhx.app/git/iuna.git
Log | Files | Refs | README | LICENSE

identity.rs (4349B)


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