Skip to main content

hashiverse_lib/tools/
client_id.rs

1//! # Stable client (user-account) identity on the network
2//!
3//! A [`ClientId`] is the self-describing identity of a single user. It bundles:
4//! - the Ed25519 public key (`verification_key_bytes`) used to verify everything the client
5//!   signs (posts, feedback, follows, …);
6//! - a 32-byte post-quantum commitment (`pq_commitment_bytes`) — see
7//!   [`crate::tools::keys_post_quantum`] — that future-proofs the identity against Ed25519
8//!   breakage without requiring a new identity;
9//! - a derived 32-byte [`crate::tools::types::Id`] that is the Blake3 hash of the two above.
10//!
11//! Because the `id` is derived from the other two fields, any tampering is detectable by
12//! [`ClientId::verify`]: the recomputed id simply won't match. Wherever the protocol refers
13//! to "who authored this" (post headers, RPC responses, peer records) it is referring to a
14//! `ClientId`.
15//!
16//! This is distinct from [`crate::tools::server_id::ServerId`], which is the analogous
17//! identity for a server and additionally carries a proof-of-work "birth certificate".
18
19use crate::tools::types::{Id, PQCommitmentBytes, VerificationKeyBytes};
20use crate::tools::{hashing};
21use serde::{Deserialize, Serialize};
22use std::fmt;
23
24/// The stable identity of a single client (user account) on the network.
25///
26/// A `ClientId` binds together three pieces:
27/// - an Ed25519 `verification_key_bytes`, the public half of the client's [`crate::tools::types::SignatureKey`],
28/// - a `pq_commitment_bytes` — a 32-byte post-quantum commitment (Falcon + Dilithium concatenation)
29///   that future-proofs the identity against breakage of Ed25519, and
30/// - a derived 32-byte [`crate::tools::types::Id`] which is the Blake3 hash of the other two
31///   fields concatenated.
32///
33/// Because the `id` is deterministically derived from the other two fields, tampering with
34/// either half is detectable via [`ClientId::verify`]: the recomputed id will not match the
35/// one carried on the struct. This is the identity anchor used wherever the protocol refers
36/// to "who authored this" — e.g. on posts, RPC responses, and peer records.
37#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
38pub struct ClientId {
39    pub verification_key_bytes: VerificationKeyBytes,
40    pub pq_commitment_bytes: PQCommitmentBytes,
41    pub id: Id,
42}
43
44impl ClientId {
45    pub fn id_hex(&self) -> String {
46        hex::encode(self.id)
47    }
48}
49
50impl fmt::Display for ClientId {
51    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
52        write!(f, "id={}", self.id_hex(), )
53    }
54}
55
56impl ClientId {
57    pub fn new(verification_key_bytes: VerificationKeyBytes, pq_commitment_bytes: PQCommitmentBytes) -> anyhow::Result<Self> {
58        let id = ClientId::id_from_parts(&verification_key_bytes, &pq_commitment_bytes)?;
59        Ok(Self {
60            verification_key_bytes,
61            pq_commitment_bytes,
62            id,
63        })
64    }
65
66    pub fn verify(&self) -> anyhow::Result<()> {
67        let id = ClientId::id_from_parts(&self.verification_key_bytes, &self.pq_commitment_bytes)?;
68        if id != self.id {
69            anyhow::bail!("ClientID pow does not verify");
70        }
71
72        Ok(())
73    }
74
75    pub fn id_from_parts(verification_key_bytes: &VerificationKeyBytes, pq_commitment_bytes: &PQCommitmentBytes) -> anyhow::Result<Id> {
76        let hash = hashing::hash_multiple(&[verification_key_bytes.as_ref(), pq_commitment_bytes.as_ref()]);
77        Id::from_hash(hash)
78    }
79}
80
81#[cfg(test)]
82mod tests {
83    use crate::tools::client_id::ClientId;
84    use crate::tools::keys::Keys;
85
86    #[tokio::test]
87    async fn verify_test() -> anyhow::Result<()> {
88        let keys = Keys::from_rnd(false)?;
89        let client_id = ClientId::new(keys.verification_key_bytes, keys.pq_commitment_bytes)?;
90        client_id.verify()?;
91
92        // Now mess with something in the verification key
93        {
94            let mut client_id = client_id.clone();
95            client_id.verification_key_bytes.0[0] = 255 - client_id.verification_key_bytes.0[0];
96            let result = client_id.verify();
97            anyhow::ensure!(result.is_err(), "Verification should fail when verification key is modified");
98        }
99
100        // Now mess with something in the pq_commitment_bytes
101        {
102            let mut client_id = client_id.clone();
103            client_id.pq_commitment_bytes.0[0] = 255 - client_id.pq_commitment_bytes.0[0];
104            let result = client_id.verify();
105            anyhow::ensure!(result.is_err(), "Verification should fail when pq_commitment_bytes is modified");
106        }
107
108        Ok(())
109    }
110}