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}