qualia_core_db/modalities/
delegation.rs1use crate::{q_hash, NQuin};
10
11pub const MAX_DELEGATION_NODES: usize = 256;
13
14const NO_REVOCATION: u64 = u64::MAX;
15
16#[inline]
18pub fn delegates_predicate() -> u64 {
19 q_hash("q42:delegatesTo")
20}
21
22fn reaches(edges: &[NQuin], root_authority: u64, agent: u64, revoked: u64) -> bool {
25 if agent == revoked || root_authority == revoked {
26 return false;
27 }
28 if agent == root_authority {
29 return true;
30 }
31 let p = delegates_predicate();
32 let mut frontier = [0u64; MAX_DELEGATION_NODES];
33 let mut visited = [0u64; MAX_DELEGATION_NODES];
34 let mut fl = 1usize;
35 let mut vl = 0usize;
36 frontier[0] = root_authority;
37 while fl > 0 {
38 fl -= 1;
39 let cur = frontier[fl];
40 if visited[..vl].contains(&cur) {
41 continue;
42 }
43 if vl < MAX_DELEGATION_NODES {
44 visited[vl] = cur;
45 vl += 1;
46 } else {
47 break;
48 }
49 for e in edges {
50 if e.predicate == p && e.subject == cur && e.subject != revoked && e.object != revoked {
51 let nxt = e.object;
52 if nxt == agent {
53 return true;
54 }
55 if fl < MAX_DELEGATION_NODES && !visited[..vl].contains(&nxt) {
56 frontier[fl] = nxt;
57 fl += 1;
58 }
59 }
60 }
61 }
62 false
63}
64
65#[inline]
68pub fn has_delegated_authority(edges: &[NQuin], root_authority: u64, agent: u64) -> bool {
69 reaches(edges, root_authority, agent, NO_REVOCATION)
70}
71
72#[inline]
75pub fn authority_after_revocation(
76 edges: &[NQuin],
77 root_authority: u64,
78 revoked: u64,
79 agent: u64,
80) -> bool {
81 reaches(edges, root_authority, agent, revoked)
82}
83
84pub fn revoked_descendants(
87 edges: &[NQuin],
88 root_authority: u64,
89 revoked: u64,
90 candidates: &[u64],
91 out: &mut [u64],
92) -> usize {
93 let mut n = 0usize;
94 for &c in candidates {
95 if has_delegated_authority(edges, root_authority, c)
96 && !authority_after_revocation(edges, root_authority, revoked, c)
97 {
98 if n >= out.len() {
99 break;
100 }
101 out[n] = c;
102 n += 1;
103 }
104 }
105 n
106}
107
108pub fn attenuates(parent: &[u64], child: &[u64]) -> bool {
114 child.iter().all(|c| parent.contains(c))
115}
116
117#[inline]
121pub fn is_revoked(crl: &[u64], node: u64) -> bool {
122 crl.contains(&node)
123}
124
125pub fn authority_after_crl(edges: &[NQuin], root_authority: u64, crl: &[u64], agent: u64) -> bool {
128 if is_revoked(crl, agent) || is_revoked(crl, root_authority) {
129 return false;
130 }
131 if agent == root_authority {
132 return true;
133 }
134 let p = delegates_predicate();
135 let mut frontier = [0u64; MAX_DELEGATION_NODES];
136 let mut visited = [0u64; MAX_DELEGATION_NODES];
137 let mut fl = 1usize;
138 let mut vl = 0usize;
139 frontier[0] = root_authority;
140 while fl > 0 {
141 fl -= 1;
142 let cur = frontier[fl];
143 if visited[..vl].contains(&cur) {
144 continue;
145 }
146 if vl < MAX_DELEGATION_NODES {
147 visited[vl] = cur;
148 vl += 1;
149 } else {
150 break;
151 }
152 for e in edges {
153 if e.predicate == p
154 && e.subject == cur
155 && !is_revoked(crl, e.subject)
156 && !is_revoked(crl, e.object)
157 {
158 let nxt = e.object;
159 if nxt == agent {
160 return true;
161 }
162 if fl < MAX_DELEGATION_NODES && !visited[..vl].contains(&nxt) {
163 frontier[fl] = nxt;
164 fl += 1;
165 }
166 }
167 }
168 }
169 false
170}
171
172pub fn delegation_in_force(from: u32, until: u32, now: u32) -> bool {
177 (from == 0 || now >= from) && (until == 0 || now <= until)
178}
179
180pub fn delegation_in_region(scope_region: u64, location: u64) -> bool {
183 scope_region == 0 || scope_region == location
184}
185
186#[cfg(test)]
187mod tests {
188 use super::*;
189
190 fn edge(delegator: u64, delegatee: u64) -> NQuin {
191 let mut q = NQuin {
192 subject: delegator,
193 predicate: delegates_predicate(),
194 object: delegatee,
195 context: 0,
196 metadata: 0,
197 parity: 0,
198 };
199 q.parity = q.subject ^ q.predicate ^ q.object ^ q.context;
200 q
201 }
202
203 #[test]
204 fn authority_flows_along_the_chain() {
205 let root = q_hash("did:state");
207 let agency = q_hash("did:agency");
208 let officer = q_hash("did:officer");
209 let ngo = q_hash("did:ngo");
210 let edges = [edge(root, agency), edge(agency, officer), edge(root, ngo)];
211 assert!(
212 has_delegated_authority(&edges, root, officer),
213 "transitive delegation"
214 );
215 assert!(has_delegated_authority(&edges, root, ngo));
216 assert!(!has_delegated_authority(
218 &edges,
219 root,
220 q_hash("did:stranger")
221 ));
222 }
223
224 #[test]
225 fn revocation_cascades_to_descendants() {
226 let root = q_hash("did:state");
227 let agency = q_hash("did:agency");
228 let officer = q_hash("did:officer");
229 let ngo = q_hash("did:ngo");
230 let edges = [edge(root, agency), edge(agency, officer), edge(root, ngo)];
231 assert!(!authority_after_revocation(&edges, root, agency, officer));
233 assert!(authority_after_revocation(&edges, root, agency, ngo));
234 let mut out = [0u64; 8];
235 let n = revoked_descendants(&edges, root, agency, &[agency, officer, ngo], &mut out);
236 assert!(out[..n].contains(&officer));
238 assert!(out[..n].contains(&agency));
239 assert!(!out[..n].contains(&ngo));
240 }
241
242 #[test]
243 fn attenuation_never_broadens_authority() {
244 let (read, write, admin) = (q_hash("cap:read"), q_hash("cap:write"), q_hash("cap:admin"));
245 assert!(attenuates(&[read, write, admin], &[read, write]));
246 assert!(
247 !attenuates(&[read], &[read, admin]),
248 "cannot grant a capability the delegator lacks"
249 );
250 assert!(attenuates(&[read], &[]));
251 }
252
253 #[test]
254 fn crl_excises_every_revoked_node() {
255 let root = q_hash("did:state");
256 let agency = q_hash("did:agency");
257 let officer = q_hash("did:officer");
258 let ngo = q_hash("did:ngo");
259 let edges = [edge(root, agency), edge(agency, officer), edge(root, ngo)];
260 assert!(authority_after_crl(&edges, root, &[], officer));
262 assert!(!authority_after_crl(&edges, root, &[agency], officer));
264 assert!(authority_after_crl(&edges, root, &[agency], ngo));
265 assert!(!authority_after_crl(&edges, root, &[agency, ngo], ngo));
267 }
268
269 #[test]
270 fn spatial_and_temporal_bounds() {
271 assert!(delegation_in_force(100, 200, 150));
273 assert!(!delegation_in_force(100, 200, 50), "before start");
274 assert!(!delegation_in_force(100, 200, 250), "after end");
275 assert!(delegation_in_force(0, 0, 999), "open-ended");
276 let region = q_hash("region:au");
278 assert!(delegation_in_region(region, region));
279 assert!(!delegation_in_region(region, q_hash("region:us")));
280 assert!(
281 delegation_in_region(0, q_hash("region:anywhere")),
282 "global scope"
283 );
284 }
285}