qualia_core_db/identity/
agency.rs1use crate::NQuin;
2use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey};
3use sha2::{Digest, Sha256};
4
5#[derive(Debug, PartialEq, Eq)]
6pub enum AgencyError {
7 InvalidSignature,
8 TamperedData,
9}
10
11pub fn compute_scoped_merkle_root(frame: &[NQuin], author_did: u64) -> [u8; 32] {
16 let mut hasher = Sha256::new();
17
18 for quin in frame.iter() {
20 if quin.context == author_did {
22 let bytes: &[u8; 48] = bytemuck::cast_ref(quin);
25 hasher.update(bytes);
26 }
27 }
28
29 let result = hasher.finalize();
31 let mut root_hash = [0u8; 32];
32 root_hash.copy_from_slice(&result);
33 root_hash
34}
35
36pub fn sign_agency_root(signing_key: &SigningKey, sub_root_hash: &[u8; 32]) -> Signature {
39 signing_key.sign(sub_root_hash)
41}
42
43pub fn verify_human_agency(
47 frame: &[NQuin],
48 author_did: u64,
49 verifying_key: &VerifyingKey,
50 signature: &Signature,
51) -> Result<(), AgencyError> {
52 let expected_sub_root = compute_scoped_merkle_root(frame, author_did);
54
55 if verifying_key.verify(&expected_sub_root, signature).is_ok() {
57 Ok(())
58 } else {
59 Err(AgencyError::InvalidSignature)
60 }
61}
62
63pub fn stamp_fiduciary_metadata(quin: &mut NQuin, principal_did_hash: u64, agent_did_hash: u64) {
66 quin.context = principal_did_hash;
67 let agent_lane = agent_did_hash & 0xFFFF;
68 let principal_clock = (principal_did_hash >> 16) & 0x1FFF_FFFF;
69 quin.metadata = agent_lane | (principal_clock << 16);
70 quin.parity = quin.subject ^ quin.predicate ^ quin.object ^ quin.context ^ quin.metadata;
71}
72
73pub fn scrub_quin_volatile(quin: &mut NQuin) {
75 unsafe {
76 std::ptr::write_volatile(&mut quin.subject, 0);
77 std::ptr::write_volatile(&mut quin.predicate, 0);
78 std::ptr::write_volatile(&mut quin.object, 0);
79 std::ptr::write_volatile(&mut quin.context, 0);
80 std::ptr::write_volatile(&mut quin.metadata, 0);
81 std::ptr::write_volatile(&mut quin.parity, 0);
82 }
83}
84
85pub fn sign_graph_mutation(signing_key: &SigningKey, quin: &NQuin) -> Signature {
87 let frame = [*quin];
88 let root = compute_scoped_merkle_root(&frame, quin.context);
89 sign_agency_root(signing_key, &root)
90}
91
92#[cfg(all(feature = "sanctuary-crypto", not(target_arch = "wasm32")))]
96const LANE_KEY_LENGTH: usize = 32;
97
98#[cfg(all(feature = "sanctuary-crypto", not(target_arch = "wasm32")))]
99pub fn derive_lane_key(pin: &str, salt: &[u8]) -> [u8; LANE_KEY_LENGTH] {
100 crate::sanctuary_crypto::derive_lane_cipher_key(
101 pin.as_bytes(),
102 salt,
103 crate::sanctuary_crypto::DEFAULT_PBKDF2_ITERATIONS,
104 )
105}
106
107#[cfg(test)]
108mod tests {
109 use super::*;
110
111 #[test]
112 fn test_human_agency_verification() {
113 let secret = [42u8; 32];
115 let signing_key = SigningKey::from_bytes(&secret);
116 let verifying_key: VerifyingKey = signing_key.verifying_key();
117
118 let author_did_alice = 1001;
119 let author_did_bob = 2002;
120
121 let mut frame = [NQuin {
122 subject: 0,
123 predicate: 0,
124 object: 0,
125 context: 0,
126 metadata: 0,
127 parity: 0,
128 }; 10];
129
130 frame[0].context = author_did_alice;
132 frame[0].subject = 55;
133
134 frame[1].context = author_did_alice;
135 frame[1].subject = 66;
136
137 frame[2].context = author_did_bob;
139 frame[2].subject = 99;
140
141 let alice_root = compute_scoped_merkle_root(&frame, author_did_alice);
143 let alice_sig = sign_agency_root(&signing_key, &alice_root);
144
145 assert_eq!(
147 verify_human_agency(&frame, author_did_alice, &verifying_key, &alice_sig),
148 Ok(())
149 );
150
151 frame[0].subject = 999;
153 assert_eq!(
154 verify_human_agency(&frame, author_did_alice, &verifying_key, &alice_sig),
155 Err(AgencyError::InvalidSignature)
156 );
157 }
158
159 #[cfg(all(feature = "sanctuary-crypto", not(target_arch = "wasm32")))]
160 #[test]
161 fn derive_lane_key_is_deterministic_and_salt_bound() {
162 let pin = "1234";
163 let salt_a = b"sanctuary";
164 let salt_b = b"decoy";
165
166 let key_a1 = derive_lane_key(pin, salt_a);
167 let key_a2 = derive_lane_key(pin, salt_a);
168 let key_b = derive_lane_key(pin, salt_b);
169 let key_c = derive_lane_key("4321", salt_a);
170
171 assert_eq!(key_a1, key_a2);
172 assert_ne!(key_a1, key_b);
173 assert_ne!(key_a1, key_c);
174 assert_eq!(
175 key_a1,
176 crate::sanctuary_crypto::derive_lane_cipher_key(
177 pin.as_bytes(),
178 salt_a,
179 crate::sanctuary_crypto::DEFAULT_PBKDF2_ITERATIONS,
180 )
181 );
182 }
183}