Skip to main content

qualia_core_db/identity/
webizen_identifiers.rs

1//! Webizen Identity Module for SPARQL-Star
2//!
3//! Implements high-priority lexicon slots and signature verification for Webizen identities.
4//! Webizen IDs are person-controlled actor identifiers with reserved prefix range.
5
6use crate::NQuin;
7use std::collections::HashMap;
8
9/// Webizen ID type (64-bit with TAG_WEBIZEN prefix)
10pub type WebizenId = u64;
11
12/// TAG_WEBIZEN prefix for Webizen IDs (0x8 prefix per architectural decision)
13pub const TAG_WEBIZEN: u64 = 0x8;
14
15/// Webizen ID range: 0x8000_0000_0000_0000 to 0x8FFF_FFFF_FFFF_FFFF
16pub const WEBIZEN_ID_MIN: u64 = 0x8000_0000_0000_0000;
17pub const WEBIZEN_ID_MAX: u64 = 0x8FFF_FFFF_FFFF_FFFF;
18
19/// Webizen identity record
20#[repr(C)]
21#[derive(Debug, Clone)]
22pub struct WebizenIdentity {
23    /// Webizen ID (with TAG_WEBIZEN prefix)
24    pub webizen_id: WebizenId,
25    /// WebID URI string hash
26    pub webid_hash: u64,
27    /// Ed25519 public key (32 bytes, stored as 4 u64)
28    pub public_key: [u64; 4],
29    /// DID hash (if using did:q42)
30    pub did_hash: u64,
31    /// Creation timestamp
32    pub created_at: u64,
33    /// Last updated timestamp
34    pub updated_at: u64,
35}
36
37impl WebizenIdentity {
38    /// Create a new Webizen identity
39    pub fn new(webid_hash: u64, public_key: [u64; 4]) -> Self {
40        let webizen_id = (TAG_WEBIZEN << 60) | (webid_hash & 0x0FFF_FFFF_FFFF_FFFF);
41        Self {
42            webizen_id,
43            webid_hash,
44            public_key,
45            did_hash: 0,
46            created_at: 0,
47            updated_at: 0,
48        }
49    }
50
51    /// Set the DID hash
52    pub fn with_did(mut self, did_hash: u64) -> Self {
53        self.did_hash = did_hash;
54        self
55    }
56
57    /// Check if an ID is a Webizen ID
58    pub fn is_webizen_id(id: u64) -> bool {
59        (id & 0xF000_0000_0000_0000) == (TAG_WEBIZEN << 60)
60    }
61}
62
63/// Webizen identity registry
64pub struct WebizenRegistry {
65    /// High-priority lexicon slots for Webizens
66    webizen_slots: HashMap<WebizenId, WebizenIdentity>,
67    /// WebID hash to Webizen ID mapping
68    webid_to_webizen: HashMap<u64, WebizenId>,
69    /// Signature verification cache
70    signature_cache: HashMap<(WebizenId, Vec<u8>), bool>,
71}
72
73impl WebizenRegistry {
74    /// Create a new Webizen registry
75    pub fn new() -> Self {
76        Self {
77            webizen_slots: HashMap::new(),
78            webid_to_webizen: HashMap::new(),
79            signature_cache: HashMap::new(),
80        }
81    }
82
83    /// Register a Webizen identity
84    pub fn register_webizen(&mut self, identity: WebizenIdentity) -> Result<(), String> {
85        let webizen_id = identity.webizen_id;
86
87        // Verify it's in the Webizen ID range
88        if !WebizenIdentity::is_webizen_id(webizen_id) {
89            return Err("ID not in Webizen range".to_string());
90        }
91
92        // Register in both maps
93        self.webizen_slots.insert(webizen_id, identity.clone());
94        self.webid_to_webizen
95            .insert(identity.webid_hash, webizen_id);
96
97        Ok(())
98    }
99
100    /// Get Webizen identity by ID
101    pub fn get_webizen(&self, webizen_id: WebizenId) -> Option<&WebizenIdentity> {
102        self.webizen_slots.get(&webizen_id)
103    }
104
105    /// Get Webizen ID by WebID hash
106    pub fn get_webizen_by_webid(&self, webid_hash: u64) -> Option<WebizenId> {
107        self.webid_to_webizen.get(&webid_hash).copied()
108    }
109
110    /// Verify an Ed25519 signature over `message` using the Webizen's stored public key.
111    ///
112    /// Uses `ed25519-dalek` `verify_strict`. Results are memoised in `signature_cache`.
113    pub fn verify_signature(
114        &mut self,
115        webizen_id: WebizenId,
116        message: &[u8],
117        signature: &[u8],
118    ) -> Result<bool, String> {
119        let identity = self
120            .webizen_slots
121            .get(&webizen_id)
122            .ok_or_else(|| "Webizen not found".to_string())?;
123
124        // Check cache. The signature is part of the key: a different signature
125        // over the same message must not reuse a prior verdict.
126        let mut cache_input = message.to_vec();
127        cache_input.extend_from_slice(signature);
128        let cache_key = (webizen_id, cache_input);
129        if let Some(&cached) = self.signature_cache.get(&cache_key) {
130            return Ok(cached);
131        }
132
133        // Repack [u64; 4] public key as [u8; 32] (little-endian)
134        let mut key_bytes = [0u8; 32];
135        for (i, &chunk) in identity.public_key.iter().enumerate() {
136            key_bytes[i * 8..(i + 1) * 8].copy_from_slice(&chunk.to_le_bytes());
137        }
138
139        // Parse the verifying key
140        use ed25519_dalek::{Signature, VerifyingKey};
141        let vk =
142            VerifyingKey::from_bytes(&key_bytes).map_err(|e| format!("Invalid public key: {e}"))?;
143
144        // Signature must be exactly 64 bytes
145        let sig_arr: [u8; 64] = signature
146            .try_into()
147            .map_err(|_| format!("Signature must be 64 bytes, got {}", signature.len()))?;
148        let sig = Signature::from_bytes(&sig_arr);
149
150        let verified = vk.verify_strict(message, &sig).is_ok();
151
152        // Cache the result
153        self.signature_cache.insert(cache_key, verified);
154
155        Ok(verified)
156    }
157
158    /// Verify a Webizen signature on a NQuin
159    pub fn verify_quin_signature(
160        &mut self,
161        quin: &NQuin,
162        signature: &[u8],
163    ) -> Result<bool, String> {
164        // Check if the subject is a Webizen
165        if !WebizenIdentity::is_webizen_id(quin.subject) {
166            return Err("Subject is not a Webizen".to_string());
167        }
168
169        // Serialize the Quin for signing (excluding parity and signature metadata)
170        let message = self.serialize_quin_for_signature(quin);
171
172        self.verify_signature(quin.subject, &message, signature)
173    }
174
175    /// Serialize a NQuin for signature verification
176    fn serialize_quin_for_signature(&self, quin: &NQuin) -> Vec<u8> {
177        // Serialize subject, predicate, object, context, and metadata
178        // Exclude parity as it's a checksum
179        let mut bytes = Vec::with_capacity(40);
180        bytes.extend_from_slice(&quin.subject.to_le_bytes());
181        bytes.extend_from_slice(&quin.predicate.to_le_bytes());
182        bytes.extend_from_slice(&quin.object.to_le_bytes());
183        bytes.extend_from_slice(&quin.context.to_le_bytes());
184        bytes.extend_from_slice(&quin.metadata.to_le_bytes());
185        bytes
186    }
187}
188
189impl Default for WebizenRegistry {
190    fn default() -> Self {
191        Self::new()
192    }
193}
194
195/// Webizen signature storage in Q42 format
196/// Ed25519 signatures are stored as Quins with special metadata flags
197#[repr(C)]
198#[derive(Debug, Clone)]
199pub struct WebizenSignatureStorage {
200    /// Signature block ID
201    pub block_id: u64,
202    /// Number of signatures in this block
203    pub signature_count: u32,
204    /// Reserved for future use
205    pub reserved: u32,
206}
207
208impl WebizenSignatureStorage {
209    /// Serialize a signature to Quins
210    pub fn signature_to_quins(
211        webizen_id: WebizenId,
212        quin_hash: u64,
213        signature: &[u8; 64],
214    ) -> Vec<NQuin> {
215        const WEBIZEN_METADATA_FLAG: u64 = 0x1 << 63; // Use bit 63 as Webizen signature flag
216
217        // Split 64-byte signature into 8 u64 values
218        let sig_parts: [u64; 8] = unsafe { std::mem::transmute(*signature) };
219
220        // Create Quins for each part
221        let mut quins = Vec::with_capacity(8);
222        for (i, part) in sig_parts.iter().enumerate() {
223            quins.push(NQuin {
224                subject: webizen_id,
225                predicate: quin_hash,
226                object: *part,
227                context: i as u64, // Index in signature
228                metadata: WEBIZEN_METADATA_FLAG,
229                parity: 0,
230            });
231        }
232
233        quins
234    }
235
236    /// Deserialize Quins to signature
237    pub fn quins_to_signature(quins: &[NQuin]) -> Option<[u8; 64]> {
238        const WEBIZEN_METADATA_FLAG: u64 = 0x1 << 63;
239
240        // Filter Webizen signature Quins
241        let sig_quins: Vec<_> = quins
242            .iter()
243            .filter(|quin| quin.metadata & WEBIZEN_METADATA_FLAG != 0)
244            .collect();
245
246        if sig_quins.len() != 8 {
247            return None;
248        }
249
250        // Sort by context (index)
251        let mut sorted_quins = sig_quins.clone();
252        sorted_quins.sort_by_key(|q| q.context);
253
254        // Reassemble signature
255        let mut sig_parts: [u64; 8] = [0; 8];
256        for (i, quin) in sorted_quins.iter().enumerate() {
257            sig_parts[i] = quin.object;
258        }
259
260        Some(unsafe { std::mem::transmute(sig_parts) })
261    }
262}
263
264#[cfg(test)]
265mod webizen_tests {
266    use super::*;
267
268    #[test]
269    fn test_webizen_id_range() {
270        assert!(WebizenIdentity::is_webizen_id(WEBIZEN_ID_MIN));
271        assert!(WebizenIdentity::is_webizen_id(WEBIZEN_ID_MAX));
272        assert!(!WebizenIdentity::is_webizen_id(0x123456789ABCDEF0));
273    }
274
275    #[test]
276    fn test_webizen_identity_creation() {
277        let public_key = [1u64, 2, 3, 4];
278        let identity = WebizenIdentity::new(0x123456789ABCDEF0, public_key);
279
280        assert!(WebizenIdentity::is_webizen_id(identity.webizen_id));
281        assert_eq!(identity.webid_hash, 0x123456789ABCDEF0);
282    }
283
284    #[test]
285    fn test_registry_registration() {
286        let mut registry = WebizenRegistry::new();
287        let public_key = [1u64, 2, 3, 4];
288        let identity = WebizenIdentity::new(0x123456789ABCDEF0, public_key);
289        let id = identity.webizen_id;
290
291        assert!(registry.register_webizen(identity).is_ok());
292        assert!(registry.get_webizen(id).is_some());
293    }
294
295    #[test]
296    fn test_registry_webid_lookup() {
297        let mut registry = WebizenRegistry::new();
298        let public_key = [1u64, 2, 3, 4];
299        let identity = WebizenIdentity::new(0x123456789ABCDEF0, public_key);
300        let webizen_id = identity.webizen_id;
301
302        registry.register_webizen(identity).unwrap();
303        assert_eq!(
304            registry.get_webizen_by_webid(0x123456789ABCDEF0),
305            Some(webizen_id)
306        );
307    }
308
309    #[test]
310    fn test_signature_storage() {
311        let webizen_id = TAG_WEBIZEN | 0x123456789ABCDEF0;
312        let quin_hash = 0x9876543210FEDCBA;
313        let signature = [1u8; 64];
314
315        let quins = WebizenSignatureStorage::signature_to_quins(webizen_id, quin_hash, &signature);
316        assert_eq!(quins.len(), 8);
317
318        let signature_back = WebizenSignatureStorage::quins_to_signature(&quins);
319        assert!(signature_back.is_some());
320        assert_eq!(signature_back.unwrap(), signature);
321    }
322
323    #[test]
324    fn test_verify_signature_roundtrip() {
325        use ed25519_dalek::{Signer, SigningKey};
326
327        let mut registry = WebizenRegistry::new();
328
329        // Derive a real Ed25519 keypair from a fixed seed.
330        let signing_key = SigningKey::from_bytes(&[7u8; 32]);
331        let vk_bytes = signing_key.verifying_key().to_bytes();
332
333        // Pack the 32-byte verifying key into [u64; 4] little-endian, matching
334        // the repacking done in `verify_signature`.
335        let mut public_key = [0u64; 4];
336        for (i, slot) in public_key.iter_mut().enumerate() {
337            let mut chunk = [0u8; 8];
338            chunk.copy_from_slice(&vk_bytes[i * 8..(i + 1) * 8]);
339            *slot = u64::from_le_bytes(chunk);
340        }
341
342        let identity = WebizenIdentity::new(0x123456789ABCDEF0, public_key);
343        let id = identity.webizen_id;
344        registry.register_webizen(identity).unwrap();
345
346        let message = b"test message";
347        let good_sig = signing_key.sign(message).to_bytes();
348
349        // A valid signature over the message verifies true.
350        assert_eq!(registry.verify_signature(id, message, &good_sig), Ok(true));
351
352        // A signature over a different message must not verify.
353        let other_sig = signing_key.sign(b"other message").to_bytes();
354        assert_eq!(
355            registry.verify_signature(id, message, &other_sig),
356            Ok(false)
357        );
358
359        // A malformed (wrong-length) signature is a hard error.
360        assert!(registry
361            .verify_signature(id, message, b"too short")
362            .is_err());
363    }
364}