Skip to main content

dig_nat/
identity.rs

1//! Peer identity — `peer_id = SHA-256(TLS SubjectPublicKeyInfo DER)`.
2//!
3//! This matches `dig-gossip`'s [`peer_id_from_tls_spki_der`] exactly: the identifier is the
4//! SHA-256 digest of the ASN.1 `SubjectPublicKeyInfo` sequence (algorithm id + subjectPublicKey
5//! bit string) lifted from the peer's leaf X.509 certificate. Every node-to-node connection is
6//! mutually-authenticated TLS, so each side derives the other's `PeerId` from the certificate
7//! presented during the TLS handshake.
8//!
9//! `dig-nat` deliberately **re-implements** this (rather than depending on the `dig-gossip` crate)
10//! because `dig-nat` is a foundational transport crate and `dig-gossip` pulls the entire L2
11//! gossip/consensus/Chia TLS stack. The digest is trivial and pinned by a cross-crate conformance
12//! test (`tests/identity.rs`) so the two never drift. The superproject `SYSTEM.md` records the
13//! change-impact edge.
14//!
15//! [`peer_id_from_tls_spki_der`]: https://github.com/DIG-Network/dig-gossip
16
17use std::fmt;
18
19use sha2::{Digest, Sha256};
20
21/// A peer's stable network identity: the 32-byte SHA-256 of its TLS SPKI DER.
22///
23/// Byte-for-byte equal to `dig-gossip`'s `PeerId` value (there it is a `chia-protocol::Bytes32`).
24/// `dig-nat` keeps its own thin newtype so it does not depend on the Chia crate stack, but the
25/// underlying 32 bytes and the derivation are identical.
26#[derive(Clone, Copy, PartialEq, Eq, Hash)]
27pub struct PeerId([u8; 32]);
28
29impl PeerId {
30    /// Construct from raw 32 bytes.
31    pub const fn from_bytes(bytes: [u8; 32]) -> Self {
32        PeerId(bytes)
33    }
34
35    /// The raw 32 bytes.
36    pub const fn as_bytes(&self) -> &[u8; 32] {
37        &self.0
38    }
39
40    /// Lowercase hex encoding (the canonical string form used in the relay wire's `peer_id` field
41    /// and in `control.relayStatus`).
42    pub fn to_hex(&self) -> String {
43        let mut s = String::with_capacity(64);
44        for b in self.0 {
45            s.push(char::from_digit((b >> 4) as u32, 16).unwrap());
46            s.push(char::from_digit((b & 0x0f) as u32, 16).unwrap());
47        }
48        s
49    }
50
51    /// Parse a 64-char lowercase/uppercase hex string. Returns `None` if the length or alphabet is
52    /// wrong — callers map that to [`crate::NatError::InvalidConfig`].
53    pub fn from_hex(hex: &str) -> Option<Self> {
54        if hex.len() != 64 {
55            return None;
56        }
57        let mut out = [0u8; 32];
58        let bytes = hex.as_bytes();
59        for (i, chunk) in bytes.chunks(2).enumerate() {
60            let hi = (chunk[0] as char).to_digit(16)?;
61            let lo = (chunk[1] as char).to_digit(16)?;
62            out[i] = ((hi << 4) | lo) as u8;
63        }
64        Some(PeerId(out))
65    }
66}
67
68impl fmt::Debug for PeerId {
69    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
70        write!(f, "PeerId({})", self.to_hex())
71    }
72}
73
74impl fmt::Display for PeerId {
75    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
76        f.write_str(&self.to_hex())
77    }
78}
79
80/// Derive a [`PeerId`] from a TLS **SubjectPublicKeyInfo** block in PKIX DER form.
81///
82/// `spki_der` must be the full ASN.1 `SubjectPublicKeyInfo` sequence (algorithm id + subjectPublicKey
83/// bit string) — **not** the bare public-key bit string. This is the same input `dig-gossip` uses;
84/// [`peer_id_from_leaf_cert_der`] extracts it from a whole leaf certificate for you.
85pub fn peer_id_from_tls_spki_der(spki_der: &[u8]) -> PeerId {
86    let digest = Sha256::digest(spki_der);
87    let bytes: [u8; 32] = digest.into();
88    PeerId(bytes)
89}
90
91/// Extract the SubjectPublicKeyInfo DER from a leaf X.509 certificate (DER-encoded) and derive the
92/// [`PeerId`]. This is what the mTLS layer calls on the certificate the peer presents.
93///
94/// Returns `None` if the certificate cannot be parsed as X.509.
95pub fn peer_id_from_leaf_cert_der(cert_der: &[u8]) -> Option<PeerId> {
96    let (_, x509) = x509_parser::parse_x509_certificate(cert_der).ok()?;
97    let spki_der = x509.tbs_certificate.subject_pki.raw;
98    Some(peer_id_from_tls_spki_der(spki_der))
99}