1use crate::derivation::DerivationTree;
8use crate::error::ProofError;
9use crate::table::CapabilityTable;
10use rvm_types::CapRights;
11
12const NONCE_RING_SIZE: usize = 4096;
17
18#[derive(Debug, Clone, Copy)]
20pub struct PolicyContext {
21 pub expected_owner: u32,
23 pub region_base: u64,
25 pub region_limit: u64,
27 pub lease_expiry_ns: u64,
29 pub current_time_ns: u64,
31 pub max_delegation_depth: u8,
33 pub nonce: u64,
35}
36
37pub struct ProofVerifier<const N: usize> {
41 current_epoch: u32,
43 nonce_ring: [u64; NONCE_RING_SIZE],
45 nonce_hash: [u64; NONCE_RING_SIZE],
48 nonce_write_pos: usize,
50 nonce_watermark: u64,
54 allow_zero_nonce: bool,
60}
61
62impl<const N: usize> ProofVerifier<N> {
63 #[must_use]
69 #[allow(clippy::large_stack_arrays)]
70 pub const fn new(epoch: u32) -> Self {
71 Self {
72 current_epoch: epoch,
73 nonce_ring: [0u64; NONCE_RING_SIZE],
74 nonce_hash: [0u64; NONCE_RING_SIZE],
75 nonce_write_pos: 0,
76 nonce_watermark: 0,
77 allow_zero_nonce: false,
78 }
79 }
80
81 pub fn set_allow_zero_nonce(&mut self, allow: bool) {
83 self.allow_zero_nonce = allow;
84 }
85
86 pub fn set_epoch(&mut self, epoch: u32) {
88 self.current_epoch = epoch;
89 }
90
91 #[inline]
104 pub fn verify_p1(
105 &self,
106 table: &CapabilityTable<N>,
107 cap_index: u32,
108 cap_generation: u32,
109 required_rights: CapRights,
110 ) -> Result<(), ProofError> {
111 let mut fail_mask: u8 = 0;
114
115 let lookup_result = table.lookup(cap_index, cap_generation);
116
117 let (epoch_match, rights_match) = if let Ok(slot) = &lookup_result {
119 let e = slot.token.epoch() == self.current_epoch;
121 let r = slot.token.has_rights(required_rights);
123 (e, r)
124 } else {
125 fail_mask |= 1;
126 (false, false)
130 };
131
132 if !epoch_match {
133 fail_mask |= 2;
134 }
135 if !rights_match {
136 fail_mask |= 4;
137 }
138
139 if fail_mask == 0 {
140 Ok(())
141 } else if fail_mask & 1 != 0 {
142 Err(ProofError::InvalidHandle)
143 } else if fail_mask & 2 != 0 {
144 Err(ProofError::StaleCapability)
145 } else {
146 Err(ProofError::InsufficientRights)
147 }
148 }
149
150 pub fn verify_p2(
162 &mut self,
163 table: &CapabilityTable<N>,
164 tree: &DerivationTree<N>,
165 cap_index: u32,
166 cap_generation: u32,
167 ctx: &PolicyContext,
168 ) -> Result<(), ProofError> {
169 let mut valid = true;
170
171 let owner_ok = table
173 .lookup(cap_index, cap_generation)
174 .is_ok_and(|slot| slot.owner.as_u32() == ctx.expected_owner);
175 valid &= owner_ok;
176
177 valid &= ctx.region_base < ctx.region_limit;
179
180 valid &= ctx.current_time_ns <= ctx.lease_expiry_ns;
182
183 let depth_ok = tree
185 .depth(cap_index)
186 .is_ok_and(|d| d <= ctx.max_delegation_depth);
187 valid &= depth_ok;
188
189 let nonce_ok = self.check_nonce(ctx.nonce);
191 valid &= nonce_ok;
192
193 if valid {
194 self.mark_nonce(ctx.nonce);
195 Ok(())
196 } else {
197 Err(ProofError::PolicyViolation)
198 }
199 }
200
201 pub fn verify_p3(
218 &self,
219 table: &CapabilityTable<N>,
220 tree: &DerivationTree<N>,
221 cap_index: u32,
222 cap_generation: u32,
223 max_depth: u8,
224 ) -> Result<(), ProofError> {
225 let _slot = table
227 .lookup(cap_index, cap_generation)
228 .map_err(|_| ProofError::DerivationChainBroken)?;
229
230 let node = tree
232 .get(cap_index)
233 .ok_or(ProofError::DerivationChainBroken)?;
234 if !node.is_valid {
235 return Err(ProofError::DerivationChainBroken);
236 }
237
238 if node.depth == 0 {
240 return Ok(());
241 }
242
243 let mut current_depth = node.depth;
245 let mut current_epoch = node.epoch;
246 let mut steps = 0u8;
247
248 let mut current_idx = cap_index;
252 loop {
253 steps += 1;
254 if steps > max_depth {
255 return Err(ProofError::DerivationChainBroken);
256 }
257
258 let parent_idx = tree.find_parent(current_idx);
260 match parent_idx {
261 Some(pidx) => {
262 let Some(parent) = tree.get(pidx) else {
263 return Err(ProofError::DerivationChainBroken);
264 };
265
266 if !parent.is_valid {
268 return Err(ProofError::DerivationChainBroken);
269 }
270 if parent.depth >= current_depth {
272 return Err(ProofError::DerivationChainBroken);
273 }
274 if parent.epoch > current_epoch {
277 return Err(ProofError::DerivationChainBroken);
278 }
279
280 if parent.depth == 0 {
281 return Ok(());
283 }
284
285 current_depth = parent.depth;
286 current_epoch = parent.epoch;
287 current_idx = pidx;
288 }
289 None => {
290 return Err(ProofError::DerivationChainBroken);
292 }
293 }
294 }
295 }
296
297 fn check_nonce(&self, nonce: u64) -> bool {
307 if nonce == 0 {
308 return self.allow_zero_nonce;
309 }
310 if nonce <= self.nonce_watermark {
312 return false;
313 }
314 let hash_slot = usize::try_from(nonce % NONCE_RING_SIZE as u64).unwrap_or(0);
316 if self.nonce_hash[hash_slot] == nonce {
317 return false;
318 }
319 true
320 }
321
322 fn mark_nonce(&mut self, nonce: u64) {
324 if nonce == 0 {
325 return;
326 }
327 self.nonce_ring[self.nonce_write_pos] = nonce;
328 let hash_slot = usize::try_from(nonce % NONCE_RING_SIZE as u64).unwrap_or(0);
330 self.nonce_hash[hash_slot] = nonce;
331 self.nonce_write_pos = (self.nonce_write_pos + 1) % NONCE_RING_SIZE;
332 if self.nonce_write_pos == 0 {
336 let mut min_val = u64::MAX;
339 for entry in &self.nonce_ring {
340 if *entry != 0 && *entry < min_val {
341 min_val = *entry;
342 }
343 }
344 if min_val != u64::MAX && min_val > self.nonce_watermark {
345 self.nonce_watermark = min_val;
346 }
347 }
348 }
349}
350
351#[cfg(test)]
352mod tests {
353 use super::*;
354 use rvm_types::{CapToken, CapType, PartitionId};
355
356 fn setup() -> (CapabilityTable<64>, DerivationTree<64>, ProofVerifier<64>) {
357 let table = CapabilityTable::<64>::new();
358 let tree = DerivationTree::<64>::new();
359 let verifier = ProofVerifier::<64>::new(0);
360 (table, tree, verifier)
361 }
362
363 fn all_rights() -> CapRights {
364 CapRights::READ
365 .union(CapRights::WRITE)
366 .union(CapRights::EXECUTE)
367 .union(CapRights::GRANT)
368 .union(CapRights::REVOKE)
369 }
370
371 #[test]
372 fn test_p1_valid() {
373 let (mut table, _, verifier) = setup();
374 let owner = PartitionId::new(1);
375 let token = CapToken::new(100, CapType::Region, all_rights(), 0);
376 let (idx, gen) = table.insert_root(token, owner, 0).unwrap();
377 assert!(verifier
378 .verify_p1(&table, idx, gen, CapRights::READ)
379 .is_ok());
380 }
381
382 #[test]
383 fn test_p1_invalid_handle() {
384 let (table, _, verifier) = setup();
385 assert_eq!(
386 verifier.verify_p1(&table, 99, 0, CapRights::READ),
387 Err(ProofError::InvalidHandle)
388 );
389 }
390
391 #[test]
392 fn test_p1_stale_epoch() {
393 let (mut table, _, verifier) = setup();
394 let token = CapToken::new(100, CapType::Region, all_rights(), 5);
395 let (idx, gen) = table.insert_root(token, PartitionId::new(1), 0).unwrap();
396 assert_eq!(
397 verifier.verify_p1(&table, idx, gen, CapRights::READ),
398 Err(ProofError::StaleCapability)
399 );
400 }
401
402 #[test]
403 fn test_p1_insufficient_rights() {
404 let (mut table, _, verifier) = setup();
405 let token = CapToken::new(100, CapType::Region, CapRights::READ, 0);
406 let (idx, gen) = table.insert_root(token, PartitionId::new(1), 0).unwrap();
407 assert_eq!(
408 verifier.verify_p1(&table, idx, gen, CapRights::WRITE),
409 Err(ProofError::InsufficientRights)
410 );
411 }
412
413 #[test]
414 fn test_p2_all_pass() {
415 let (mut table, mut tree, mut verifier) = setup();
416 let token = CapToken::new(100, CapType::Region, all_rights(), 0);
417 let (idx, gen) = table.insert_root(token, PartitionId::new(1), 0).unwrap();
418 tree.add_root(idx, 0).unwrap();
419
420 let ctx = PolicyContext {
421 expected_owner: 1,
422 region_base: 0x1000,
423 region_limit: 0x2000,
424 lease_expiry_ns: 1_000_000_000,
425 current_time_ns: 500_000_000,
426 max_delegation_depth: 8,
427 nonce: 42,
428 };
429 assert!(verifier.verify_p2(&table, &tree, idx, gen, &ctx).is_ok());
430 }
431
432 #[test]
433 fn test_p2_nonce_replay() {
434 let (mut table, mut tree, mut verifier) = setup();
435 let token = CapToken::new(100, CapType::Region, all_rights(), 0);
436 let (idx, gen) = table.insert_root(token, PartitionId::new(1), 0).unwrap();
437 tree.add_root(idx, 0).unwrap();
438
439 let ctx = PolicyContext {
440 expected_owner: 1,
441 region_base: 0x1000,
442 region_limit: 0x2000,
443 lease_expiry_ns: 1_000_000_000,
444 current_time_ns: 500_000_000,
445 max_delegation_depth: 8,
446 nonce: 55,
447 };
448 assert!(verifier.verify_p2(&table, &tree, idx, gen, &ctx).is_ok());
449 assert_eq!(
450 verifier.verify_p2(&table, &tree, idx, gen, &ctx),
451 Err(ProofError::PolicyViolation)
452 );
453 }
454
455 #[test]
456 fn test_p3_root_passes() {
457 let (mut table, mut tree, verifier) = setup();
458 let token = CapToken::new(100, CapType::Region, all_rights(), 0);
459 let (idx, gen) = table.insert_root(token, PartitionId::new(1), 0).unwrap();
460 tree.add_root(idx, 0).unwrap();
461
462 assert!(verifier.verify_p3(&table, &tree, idx, gen, 8).is_ok());
463 }
464
465 #[test]
466 fn test_p3_one_level_derivation() {
467 let (mut table, mut tree, verifier) = setup();
468 let owner = PartitionId::new(1);
469
470 let root_token = CapToken::new(100, CapType::Region, all_rights(), 0);
472 let (root_idx, _root_gen) = table.insert_root(root_token, owner, 0).unwrap();
473 tree.add_root(root_idx, 0).unwrap();
474
475 let child_token = CapToken::new(200, CapType::Region, CapRights::READ, 0);
477 let (child_idx, child_gen) = table.insert_root(child_token, owner, 0).unwrap();
478 tree.add_child(root_idx, child_idx, 1, 1).unwrap();
479
480 assert!(verifier
482 .verify_p3(&table, &tree, child_idx, child_gen, 8)
483 .is_ok());
484 }
485
486 #[test]
487 fn test_p3_nonexistent_fails() {
488 let (table, tree, verifier) = setup();
489 assert_eq!(
490 verifier.verify_p3(&table, &tree, 99, 0, 8),
491 Err(ProofError::DerivationChainBroken),
492 );
493 }
494
495 #[test]
496 fn test_p3_revoked_ancestor_fails() {
497 let (mut table, mut tree, verifier) = setup();
498 let owner = PartitionId::new(1);
499
500 let root_token = CapToken::new(100, CapType::Region, all_rights(), 0);
501 let (root_idx, _) = table.insert_root(root_token, owner, 0).unwrap();
502 tree.add_root(root_idx, 0).unwrap();
503
504 let child_token = CapToken::new(200, CapType::Region, CapRights::READ, 0);
505 let (child_idx, child_gen) = table.insert_root(child_token, owner, 0).unwrap();
506 tree.add_child(root_idx, child_idx, 1, 1).unwrap();
507
508 tree.revoke(root_idx).unwrap();
510
511 assert_eq!(
513 verifier.verify_p3(&table, &tree, child_idx, child_gen, 8),
514 Err(ProofError::DerivationChainBroken),
515 );
516 }
517
518 #[test]
519 fn test_nonce_ring_4096_churn() {
520 let (mut table, mut tree, mut verifier) = setup();
523 let token = CapToken::new(100, CapType::Region, all_rights(), 0);
524 let (idx, gen) = table.insert_root(token, PartitionId::new(1), 0).unwrap();
525 tree.add_root(idx, 0).unwrap();
526
527 for i in 1..=4096u64 {
529 let ctx = PolicyContext {
530 expected_owner: 1,
531 region_base: 0x1000,
532 region_limit: 0x2000,
533 lease_expiry_ns: 1_000_000_000,
534 current_time_ns: 500_000_000,
535 max_delegation_depth: 8,
536 nonce: i,
537 };
538 assert!(verifier.verify_p2(&table, &tree, idx, gen, &ctx).is_ok());
539 }
540
541 let ctx_new = PolicyContext {
543 expected_owner: 1,
544 region_base: 0x1000,
545 region_limit: 0x2000,
546 lease_expiry_ns: 1_000_000_000,
547 current_time_ns: 500_000_000,
548 max_delegation_depth: 8,
549 nonce: 4097,
550 };
551 assert!(verifier
552 .verify_p2(&table, &tree, idx, gen, &ctx_new)
553 .is_ok());
554
555 let ctx_old = PolicyContext {
558 expected_owner: 1,
559 region_base: 0x1000,
560 region_limit: 0x2000,
561 lease_expiry_ns: 1_000_000_000,
562 current_time_ns: 500_000_000,
563 max_delegation_depth: 8,
564 nonce: 1,
565 };
566 assert_eq!(
567 verifier.verify_p2(&table, &tree, idx, gen, &ctx_old),
568 Err(ProofError::PolicyViolation)
569 );
570 }
571
572 #[test]
573 fn test_watermark_rejects_below_minimum() {
574 let mut verifier = ProofVerifier::<64>::new(0);
575 for i in 100..100 + 4096u64 {
578 verifier.mark_nonce(i);
579 }
580 assert!(!verifier.check_nonce(1));
582 assert!(!verifier.check_nonce(99));
583 }
584}