1use serde::{Deserialize, Serialize};
17
18use crate::identity::{AgentIdentity, AgentKeyPair};
19use crate::signing::SignedMessage;
20use crate::CryptoError;
21
22#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
24#[serde(tag = "type", content = "value")]
25pub enum Caveat {
26 #[serde(rename = "action_scope")]
28 ActionScope(Vec<String>),
29
30 #[serde(rename = "expires_at")]
32 ExpiresAt(String),
33
34 #[serde(rename = "max_cost")]
36 MaxCost(f64),
37
38 #[serde(rename = "resource")]
41 Resource(String),
42
43 #[serde(rename = "context")]
45 Context { key: String, value: String },
46
47 #[serde(rename = "custom")]
49 Custom {
50 key: String,
51 value: serde_json::Value,
52 },
53}
54
55pub const MAX_CHAIN_DEPTH: usize = 32;
62
63#[derive(Debug, Clone, Serialize, Deserialize)]
64pub struct Delegation {
65 pub issuer_did: String,
67 pub delegate_did: String,
69 pub issuer_public_key: Vec<u8>,
71 pub caveats: Vec<Caveat>,
73 pub parent_proof: Option<Box<Delegation>>,
75 pub proof: SignedMessage,
77}
78
79impl Delegation {
80 pub fn create_root(
84 issuer_keypair: &AgentKeyPair,
85 delegate_did: &str,
86 caveats: Vec<Caveat>,
87 ) -> Result<Self, CryptoError> {
88 let issuer_identity = issuer_keypair.identity();
89 Self::create_inner(
90 issuer_keypair,
91 &issuer_identity.did,
92 delegate_did,
93 caveats,
94 None,
95 )
96 }
97
98 pub fn delegate(
103 issuer_keypair: &AgentKeyPair,
104 delegate_did: &str,
105 additional_caveats: Vec<Caveat>,
106 parent: Delegation,
107 ) -> Result<Self, CryptoError> {
108 let issuer_identity = issuer_keypair.identity();
109
110 if parent.delegate_did != issuer_identity.did {
112 return Err(CryptoError::DelegationChainBroken(
113 "issuer is not the delegate of parent delegation".into(),
114 ));
115 }
116
117 let mut all_caveats = parent.caveats.clone();
119 all_caveats.extend(additional_caveats);
120
121 Self::create_inner(
122 issuer_keypair,
123 &issuer_identity.did,
124 delegate_did,
125 all_caveats,
126 Some(Box::new(parent)),
127 )
128 }
129
130 fn create_inner(
131 issuer_keypair: &AgentKeyPair,
132 issuer_did: &str,
133 delegate_did: &str,
134 caveats: Vec<Caveat>,
135 parent: Option<Box<Delegation>>,
136 ) -> Result<Self, CryptoError> {
137 if let Some(ref p) = parent {
139 if p.depth() >= MAX_CHAIN_DEPTH {
140 return Err(CryptoError::DelegationChainBroken(format!(
141 "chain depth exceeds maximum of {}",
142 MAX_CHAIN_DEPTH
143 )));
144 }
145 }
146
147 let issuer_identity = issuer_keypair.identity();
148 let parent_hash = parent.as_ref().map(|p| p.proof.content_hash());
149
150 let payload = serde_json::json!({
151 "issuer_did": issuer_did,
152 "delegate_did": delegate_did,
153 "caveats": caveats,
154 "parent_hash": parent_hash,
155 });
156
157 let proof = SignedMessage::sign(issuer_keypair, payload)?;
158
159 Ok(Self {
160 issuer_did: issuer_did.to_string(),
161 delegate_did: delegate_did.to_string(),
162 issuer_public_key: issuer_identity.public_key_bytes.clone(),
163 caveats,
164 parent_proof: parent,
165 proof,
166 })
167 }
168
169 pub fn depth(&self) -> usize {
171 let mut depth = 0;
172 let mut current = self;
173 while let Some(ref parent) = current.parent_proof {
174 depth += 1;
175 current = parent;
176 }
177 depth
178 }
179}
180
181#[derive(Debug, Clone, Serialize, Deserialize)]
186pub struct Invocation {
187 pub invoker_did: String,
189 pub action: String,
191 pub args: serde_json::Value,
193 pub delegation: Delegation,
195 pub proof: SignedMessage,
197}
198
199impl Invocation {
200 pub fn create(
204 invoker_keypair: &AgentKeyPair,
205 action: &str,
206 args: serde_json::Value,
207 delegation: Delegation,
208 ) -> Result<Self, CryptoError> {
209 let invoker_identity = invoker_keypair.identity();
210
211 if delegation.delegate_did != invoker_identity.did {
212 return Err(CryptoError::DelegationChainBroken(
213 "invoker is not the delegate of the delegation".into(),
214 ));
215 }
216
217 let payload = serde_json::json!({
218 "invoker_did": invoker_identity.did,
219 "action": action,
220 "args": args,
221 "delegation_hash": delegation.proof.content_hash(),
222 });
223
224 let proof = SignedMessage::sign(invoker_keypair, payload)?;
225
226 Ok(Self {
227 invoker_did: invoker_identity.did,
228 action: action.to_string(),
229 args,
230 delegation,
231 proof,
232 })
233 }
234}
235
236#[derive(Debug)]
238pub struct VerificationResult {
239 pub invoker_did: String,
241 pub root_did: String,
243 pub chain: Vec<String>,
245 pub depth: usize,
247}
248
249pub fn verify_invocation(
253 invocation: &Invocation,
254 invoker_identity: &AgentIdentity,
255 root_identity: &AgentIdentity,
256) -> Result<VerificationResult, CryptoError> {
257 verify_invocation_with_revocation(invocation, invoker_identity, root_identity, |_| false)
258}
259
260pub fn verify_invocation_with_revocation(
276 invocation: &Invocation,
277 invoker_identity: &AgentIdentity,
278 root_identity: &AgentIdentity,
279 is_revoked: impl Fn(&str) -> bool,
280) -> Result<VerificationResult, CryptoError> {
281 invocation.proof.verify(invoker_identity)?;
283
284 if invocation.invoker_did != invocation.delegation.delegate_did {
286 return Err(CryptoError::DelegationChainBroken(
287 "invoker is not the delegate of the delegation".into(),
288 ));
289 }
290
291 let mut chain = vec![invocation.invoker_did.clone()];
293 let mut current = &invocation.delegation;
294 let mut all_caveats: Vec<Caveat> = Vec::new();
295 let mut steps = 0usize;
296
297 loop {
298 steps += 1;
299 if steps > MAX_CHAIN_DEPTH {
300 return Err(CryptoError::DelegationChainBroken(format!(
301 "chain depth exceeds maximum of {}",
302 MAX_CHAIN_DEPTH
303 )));
304 }
305
306 chain.push(current.issuer_did.clone());
307
308 let issuer_identity =
310 AgentIdentity::from_bytes(¤t.issuer_public_key).map_err(|_| {
311 CryptoError::DelegationChainBroken(format!(
312 "invalid embedded public key for '{}'",
313 current.issuer_did
314 ))
315 })?;
316
317 if issuer_identity.did != current.issuer_did {
319 return Err(CryptoError::DelegationChainBroken(format!(
320 "embedded public key produces DID '{}' but delegation claims '{}'",
321 issuer_identity.did, current.issuer_did
322 )));
323 }
324
325 current.proof.verify(&issuer_identity)?;
327
328 let delegation_hash = current.proof.content_hash();
330 if is_revoked(&delegation_hash) {
331 return Err(CryptoError::DelegationRevoked(delegation_hash));
332 }
333
334 if let Some(signed_caveats) = current.proof.payload.get("caveats") {
336 if let Ok(caveats) = serde_json::from_value::<Vec<Caveat>>(signed_caveats.clone()) {
337 all_caveats.extend(caveats);
338 }
339 }
340
341 if current.issuer_did == root_identity.did {
343 if issuer_identity.public_key_bytes != root_identity.public_key_bytes {
345 return Err(CryptoError::DelegationChainBroken(
346 "root public key mismatch".into(),
347 ));
348 }
349 break;
350 }
351
352 match ¤t.parent_proof {
354 Some(parent) => {
355 if parent.delegate_did != current.issuer_did {
356 return Err(CryptoError::DelegationChainBroken(format!(
357 "delegation issuer '{}' is not the delegate of parent delegation '{}'",
358 current.issuer_did, parent.delegate_did
359 )));
360 }
361 current = parent;
362 }
363 None => {
364 return Err(CryptoError::DelegationChainBroken(format!(
365 "chain terminates at '{}', expected root '{}'",
366 current.issuer_did, root_identity.did
367 )));
368 }
369 }
370 }
371
372 let now = chrono::Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Millis, true);
374 for caveat in &all_caveats {
375 check_caveat(caveat, &invocation.action, &invocation.args, &now)?;
376 }
377
378 let depth = chain.len() - 1;
379 Ok(VerificationResult {
380 invoker_did: invocation.invoker_did.clone(),
381 root_did: root_identity.did.clone(),
382 chain,
383 depth,
384 })
385}
386
387pub fn verify_delegation_chain(
389 delegation: &Delegation,
390 root_identity: &AgentIdentity,
391) -> Result<Vec<String>, CryptoError> {
392 verify_delegation_chain_with_revocation(delegation, root_identity, |_| false)
393}
394
395pub fn verify_delegation_chain_with_revocation(
399 delegation: &Delegation,
400 root_identity: &AgentIdentity,
401 is_revoked: impl Fn(&str) -> bool,
402) -> Result<Vec<String>, CryptoError> {
403 let mut chain = Vec::new();
404 let mut current = delegation;
405 let mut steps = 0usize;
406
407 loop {
408 steps += 1;
409 if steps > MAX_CHAIN_DEPTH {
410 return Err(CryptoError::DelegationChainBroken(format!(
411 "chain depth exceeds maximum of {}",
412 MAX_CHAIN_DEPTH
413 )));
414 }
415
416 chain.push(current.delegate_did.clone());
417 chain.push(current.issuer_did.clone());
418
419 let issuer_identity =
421 AgentIdentity::from_bytes(¤t.issuer_public_key).map_err(|_| {
422 CryptoError::DelegationChainBroken(format!(
423 "invalid embedded public key for '{}'",
424 current.issuer_did
425 ))
426 })?;
427
428 if issuer_identity.did != current.issuer_did {
429 return Err(CryptoError::DelegationChainBroken(format!(
430 "embedded public key produces DID '{}' but delegation claims '{}'",
431 issuer_identity.did, current.issuer_did
432 )));
433 }
434
435 current.proof.verify(&issuer_identity)?;
436
437 let delegation_hash = current.proof.content_hash();
438 if is_revoked(&delegation_hash) {
439 return Err(CryptoError::DelegationRevoked(delegation_hash));
440 }
441
442 if current.issuer_did == root_identity.did {
443 if issuer_identity.public_key_bytes != root_identity.public_key_bytes {
444 return Err(CryptoError::DelegationChainBroken(
445 "root public key mismatch".into(),
446 ));
447 }
448 break;
449 }
450
451 match ¤t.parent_proof {
452 Some(parent) => {
453 if parent.delegate_did != current.issuer_did {
454 return Err(CryptoError::DelegationChainBroken(
455 "chain linkage broken: issuer not delegate of parent".into(),
456 ));
457 }
458 current = parent;
459 }
460 None => {
461 return Err(CryptoError::DelegationChainBroken(format!(
462 "chain terminates at '{}', expected root '{}'",
463 current.issuer_did, root_identity.did
464 )));
465 }
466 }
467 }
468
469 chain.dedup();
470 Ok(chain)
471}
472
473fn check_caveat(
474 caveat: &Caveat,
475 action: &str,
476 args: &serde_json::Value,
477 now: &str,
478) -> Result<(), CryptoError> {
479 match caveat {
480 Caveat::ActionScope(allowed) => {
481 if !allowed.iter().any(|a| a == action) {
482 return Err(CryptoError::CaveatViolation(format!(
483 "action '{}' not in allowed scope {:?}",
484 action, allowed
485 )));
486 }
487 }
488 Caveat::ExpiresAt(expiry) => {
489 if now > expiry.as_str() {
490 return Err(CryptoError::CaveatViolation(format!(
491 "delegation expired at {}",
492 expiry
493 )));
494 }
495 }
496 Caveat::MaxCost(max) => match args.get("cost").and_then(|v| v.as_f64()) {
497 Some(cost) if cost > *max => {
498 return Err(CryptoError::CaveatViolation(format!(
499 "cost {} exceeds max {}",
500 cost, max
501 )));
502 }
503 None => {
504 return Err(CryptoError::CaveatViolation(
505 "max_cost caveat requires 'cost' field in args".into(),
506 ));
507 }
508 _ => {}
509 },
510 Caveat::Resource(pattern) => match args.get("resource").and_then(|v| v.as_str()) {
511 Some(resource) if !matches_glob(pattern, resource) => {
512 return Err(CryptoError::CaveatViolation(format!(
513 "resource '{}' does not match pattern '{}'",
514 resource, pattern
515 )));
516 }
517 None => {
518 return Err(CryptoError::CaveatViolation(
519 "resource caveat requires 'resource' field in args".into(),
520 ));
521 }
522 _ => {}
523 },
524 Caveat::Context { key, value } => {
525 let actual = args.get(key).and_then(|v| v.as_str());
526 if actual != Some(value.as_str()) {
527 return Err(CryptoError::CaveatViolation(format!(
528 "context '{}' expected '{}', got '{}'",
529 key,
530 value,
531 actual.unwrap_or("<missing>")
532 )));
533 }
534 }
535 Caveat::Custom { key, value } => {
536 let actual = args.get(key);
537 if actual != Some(value) {
538 return Err(CryptoError::CaveatViolation(format!(
539 "custom caveat '{}' not satisfied",
540 key
541 )));
542 }
543 }
544 }
545 Ok(())
546}
547
548fn matches_glob(pattern: &str, value: &str) -> bool {
551 if let Some(prefix) = pattern.strip_suffix('*') {
552 value.starts_with(prefix)
553 } else {
554 pattern == value
555 }
556}
557
558#[cfg(test)]
559mod tests {
560 use super::*;
561
562 fn keypair() -> AgentKeyPair {
563 AgentKeyPair::generate()
564 }
565
566 #[test]
569 fn test_root_delegation() {
570 let root = keypair();
571 let agent_b = keypair();
572
573 let delegation = Delegation::create_root(
574 &root,
575 &agent_b.identity().did,
576 vec![Caveat::ActionScope(vec!["resolve".into(), "search".into()])],
577 )
578 .unwrap();
579
580 assert_eq!(delegation.issuer_did, root.identity().did);
581 assert_eq!(delegation.delegate_did, agent_b.identity().did);
582 assert_eq!(delegation.depth(), 0);
583 assert!(delegation.parent_proof.is_none());
584 }
585
586 #[test]
587 fn test_chained_delegation() {
588 let root = keypair();
589 let agent_b = keypair();
590 let agent_c = keypair();
591
592 let d1 = Delegation::create_root(
593 &root,
594 &agent_b.identity().did,
595 vec![Caveat::ActionScope(vec!["resolve".into(), "search".into()])],
596 )
597 .unwrap();
598
599 let d2 = Delegation::delegate(
600 &agent_b,
601 &agent_c.identity().did,
602 vec![], d1,
604 )
605 .unwrap();
606
607 assert_eq!(d2.issuer_did, agent_b.identity().did);
608 assert_eq!(d2.delegate_did, agent_c.identity().did);
609 assert_eq!(d2.depth(), 1);
610 assert!(d2.parent_proof.is_some());
611 }
612
613 #[test]
614 fn test_delegate_must_be_parent_delegate() {
615 let root = keypair();
616 let agent_b = keypair();
617 let agent_c = keypair();
618 let unrelated = keypair();
619
620 let d1 = Delegation::create_root(&root, &agent_b.identity().did, vec![]).unwrap();
621
622 let result = Delegation::delegate(&unrelated, &agent_c.identity().did, vec![], d1);
624 assert!(result.is_err());
625 }
626
627 #[test]
630 fn test_invocation_basic() {
631 let root = keypair();
632 let agent_b = keypair();
633
634 let delegation = Delegation::create_root(
635 &root,
636 &agent_b.identity().did,
637 vec![Caveat::ActionScope(vec!["resolve".into()])],
638 )
639 .unwrap();
640
641 let invocation = Invocation::create(
642 &agent_b,
643 "resolve",
644 serde_json::json!({"entity_id": "123"}),
645 delegation,
646 )
647 .unwrap();
648
649 assert_eq!(invocation.invoker_did, agent_b.identity().did);
650 assert_eq!(invocation.action, "resolve");
651 }
652
653 #[test]
654 fn test_invocation_must_be_delegation_delegate() {
655 let root = keypair();
656 let agent_b = keypair();
657 let agent_c = keypair();
658
659 let delegation = Delegation::create_root(&root, &agent_b.identity().did, vec![]).unwrap();
660
661 let result = Invocation::create(&agent_c, "resolve", serde_json::json!({}), delegation);
663 assert!(result.is_err());
664 }
665
666 #[test]
669 fn test_verify_root_invocation() {
670 let root = keypair();
671 let agent_b = keypair();
672
673 let delegation = Delegation::create_root(
674 &root,
675 &agent_b.identity().did,
676 vec![Caveat::ActionScope(vec!["resolve".into()])],
677 )
678 .unwrap();
679
680 let invocation =
681 Invocation::create(&agent_b, "resolve", serde_json::json!({}), delegation).unwrap();
682
683 let result = verify_invocation(&invocation, &agent_b.identity(), &root.identity()).unwrap();
684
685 assert_eq!(result.invoker_did, agent_b.identity().did);
686 assert_eq!(result.root_did, root.identity().did);
687 assert_eq!(result.depth, 1); }
689
690 #[test]
691 fn test_verify_chained_invocation() {
692 let root = keypair();
693 let agent_b = keypair();
694 let agent_c = keypair();
695
696 let d1 = Delegation::create_root(
697 &root,
698 &agent_b.identity().did,
699 vec![Caveat::ActionScope(vec!["resolve".into(), "search".into()])],
700 )
701 .unwrap();
702
703 let d2 = Delegation::delegate(
704 &agent_b,
705 &agent_c.identity().did,
706 vec![], d1,
708 )
709 .unwrap();
710
711 let invocation =
712 Invocation::create(&agent_c, "resolve", serde_json::json!({}), d2).unwrap();
713
714 let result = verify_invocation(&invocation, &agent_c.identity(), &root.identity()).unwrap();
715
716 assert_eq!(result.invoker_did, agent_c.identity().did);
717 assert_eq!(result.root_did, root.identity().did);
718 assert_eq!(result.depth, 2); }
720
721 #[test]
722 fn test_verify_wrong_root_fails() {
723 let root = keypair();
724 let agent_b = keypair();
725 let fake_root = keypair();
726
727 let delegation = Delegation::create_root(&root, &agent_b.identity().did, vec![]).unwrap();
728
729 let invocation =
730 Invocation::create(&agent_b, "resolve", serde_json::json!({}), delegation).unwrap();
731
732 let result = verify_invocation(&invocation, &agent_b.identity(), &fake_root.identity());
733 assert!(result.is_err());
734 }
735
736 #[test]
739 fn test_action_scope_caveat_passes() {
740 let root = keypair();
741 let agent_b = keypair();
742
743 let delegation = Delegation::create_root(
744 &root,
745 &agent_b.identity().did,
746 vec![Caveat::ActionScope(vec!["resolve".into(), "search".into()])],
747 )
748 .unwrap();
749
750 let invocation =
751 Invocation::create(&agent_b, "resolve", serde_json::json!({}), delegation).unwrap();
752
753 assert!(verify_invocation(&invocation, &agent_b.identity(), &root.identity()).is_ok());
754 }
755
756 #[test]
757 fn test_action_scope_caveat_blocks() {
758 let root = keypair();
759 let agent_b = keypair();
760
761 let delegation = Delegation::create_root(
762 &root,
763 &agent_b.identity().did,
764 vec![Caveat::ActionScope(vec!["resolve".into()])],
765 )
766 .unwrap();
767
768 let invocation = Invocation::create(
769 &agent_b,
770 "merge", serde_json::json!({}),
772 delegation,
773 )
774 .unwrap();
775
776 let result = verify_invocation(&invocation, &agent_b.identity(), &root.identity());
777 assert!(matches!(result, Err(CryptoError::CaveatViolation(_))));
778 }
779
780 #[test]
781 fn test_expires_at_caveat_blocks() {
782 let root = keypair();
783 let agent_b = keypair();
784
785 let delegation = Delegation::create_root(
786 &root,
787 &agent_b.identity().did,
788 vec![Caveat::ExpiresAt("2020-01-01T00:00:00.000Z".into())],
789 )
790 .unwrap();
791
792 let invocation =
793 Invocation::create(&agent_b, "resolve", serde_json::json!({}), delegation).unwrap();
794
795 let result = verify_invocation(&invocation, &agent_b.identity(), &root.identity());
796 assert!(matches!(result, Err(CryptoError::CaveatViolation(_))));
797 }
798
799 #[test]
800 fn test_max_cost_caveat_passes() {
801 let root = keypair();
802 let agent_b = keypair();
803
804 let delegation =
805 Delegation::create_root(&root, &agent_b.identity().did, vec![Caveat::MaxCost(5.0)])
806 .unwrap();
807
808 let invocation = Invocation::create(
809 &agent_b,
810 "resolve",
811 serde_json::json!({"cost": 3.50}),
812 delegation,
813 )
814 .unwrap();
815
816 assert!(verify_invocation(&invocation, &agent_b.identity(), &root.identity()).is_ok());
817 }
818
819 #[test]
820 fn test_max_cost_caveat_blocks() {
821 let root = keypair();
822 let agent_b = keypair();
823
824 let delegation =
825 Delegation::create_root(&root, &agent_b.identity().did, vec![Caveat::MaxCost(5.0)])
826 .unwrap();
827
828 let invocation = Invocation::create(
829 &agent_b,
830 "resolve",
831 serde_json::json!({"cost": 10.0}),
832 delegation,
833 )
834 .unwrap();
835
836 let result = verify_invocation(&invocation, &agent_b.identity(), &root.identity());
837 assert!(matches!(result, Err(CryptoError::CaveatViolation(_))));
838 }
839
840 #[test]
841 fn test_resource_caveat_glob() {
842 let root = keypair();
843 let agent_b = keypair();
844
845 let delegation = Delegation::create_root(
846 &root,
847 &agent_b.identity().did,
848 vec![Caveat::Resource("entity:customer:*".into())],
849 )
850 .unwrap();
851
852 let inv_ok = Invocation::create(
854 &agent_b,
855 "resolve",
856 serde_json::json!({"resource": "entity:customer:123"}),
857 delegation.clone(),
858 )
859 .unwrap();
860 assert!(verify_invocation(&inv_ok, &agent_b.identity(), &root.identity()).is_ok());
861
862 let inv_bad = Invocation::create(
864 &agent_b,
865 "resolve",
866 serde_json::json!({"resource": "entity:order:456"}),
867 delegation,
868 )
869 .unwrap();
870 let result = verify_invocation(&inv_bad, &agent_b.identity(), &root.identity());
871 assert!(matches!(result, Err(CryptoError::CaveatViolation(_))));
872 }
873
874 #[test]
875 fn test_context_caveat() {
876 let root = keypair();
877 let agent_b = keypair();
878
879 let delegation = Delegation::create_root(
880 &root,
881 &agent_b.identity().did,
882 vec![Caveat::Context {
883 key: "task_id".into(),
884 value: "task-abc".into(),
885 }],
886 )
887 .unwrap();
888
889 let inv_ok = Invocation::create(
891 &agent_b,
892 "resolve",
893 serde_json::json!({"task_id": "task-abc"}),
894 delegation.clone(),
895 )
896 .unwrap();
897 assert!(verify_invocation(&inv_ok, &agent_b.identity(), &root.identity()).is_ok());
898
899 let inv_bad = Invocation::create(
901 &agent_b,
902 "resolve",
903 serde_json::json!({"task_id": "task-xyz"}),
904 delegation,
905 )
906 .unwrap();
907 assert!(matches!(
908 verify_invocation(&inv_bad, &agent_b.identity(), &root.identity()),
909 Err(CryptoError::CaveatViolation(_))
910 ));
911 }
912
913 #[test]
914 fn test_attenuation_narrows_not_widens() {
915 let root = keypair();
916 let agent_b = keypair();
917 let agent_c = keypair();
918
919 let d1 = Delegation::create_root(
921 &root,
922 &agent_b.identity().did,
923 vec![Caveat::ActionScope(vec!["resolve".into(), "search".into()])],
924 )
925 .unwrap();
926
927 let d2 = Delegation::delegate(
929 &agent_b,
930 &agent_c.identity().did,
931 vec![Caveat::ActionScope(vec!["resolve".into()])],
932 d1,
933 )
934 .unwrap();
935
936 let inv = Invocation::create(&agent_c, "search", serde_json::json!({}), d2).unwrap();
938
939 let result = verify_invocation(&inv, &agent_c.identity(), &root.identity());
940 assert!(matches!(result, Err(CryptoError::CaveatViolation(_))));
941 }
942
943 #[test]
944 fn test_three_level_chain() {
945 let root = keypair();
946 let agent_b = keypair();
947 let agent_c = keypair();
948 let agent_d = keypair();
949
950 let d1 = Delegation::create_root(
951 &root,
952 &agent_b.identity().did,
953 vec![Caveat::ActionScope(vec!["resolve".into()])],
954 )
955 .unwrap();
956
957 let d2 = Delegation::delegate(&agent_b, &agent_c.identity().did, vec![], d1).unwrap();
958
959 let d3 = Delegation::delegate(&agent_c, &agent_d.identity().did, vec![], d2).unwrap();
960
961 let inv = Invocation::create(&agent_d, "resolve", serde_json::json!({}), d3).unwrap();
962
963 let result = verify_invocation(&inv, &agent_d.identity(), &root.identity()).unwrap();
964 assert_eq!(result.depth, 3); }
966
967 #[test]
968 fn test_verify_delegation_chain() {
969 let root = keypair();
970 let agent_b = keypair();
971 let agent_c = keypair();
972
973 let d1 = Delegation::create_root(&root, &agent_b.identity().did, vec![]).unwrap();
974
975 let d2 = Delegation::delegate(&agent_b, &agent_c.identity().did, vec![], d1).unwrap();
976
977 let chain = verify_delegation_chain(&d2, &root.identity()).unwrap();
978 assert!(chain.contains(&root.identity().did));
979 assert!(chain.contains(&agent_b.identity().did));
980 assert!(chain.contains(&agent_c.identity().did));
981 }
982
983 #[test]
984 fn test_delegation_serialization_roundtrip() {
985 let root = keypair();
986 let agent_b = keypair();
987
988 let delegation = Delegation::create_root(
989 &root,
990 &agent_b.identity().did,
991 vec![
992 Caveat::ActionScope(vec!["resolve".into()]),
993 Caveat::ExpiresAt("2030-01-01T00:00:00.000Z".into()),
994 Caveat::MaxCost(10.0),
995 ],
996 )
997 .unwrap();
998
999 let json = serde_json::to_string(&delegation).unwrap();
1000 let restored: Delegation = serde_json::from_str(&json).unwrap();
1001 assert_eq!(restored.issuer_did, delegation.issuer_did);
1002 assert_eq!(restored.delegate_did, delegation.delegate_did);
1003 assert_eq!(restored.caveats.len(), 3);
1004 }
1005
1006 #[test]
1007 fn test_caveat_serialization_roundtrip() {
1008 let caveats = vec![
1009 Caveat::ActionScope(vec!["resolve".into(), "search".into()]),
1010 Caveat::ExpiresAt("2030-01-01T00:00:00.000Z".into()),
1011 Caveat::MaxCost(5.0),
1012 Caveat::Resource("entity:*".into()),
1013 Caveat::Context {
1014 key: "task_id".into(),
1015 value: "t1".into(),
1016 },
1017 Caveat::Custom {
1018 key: "org".into(),
1019 value: serde_json::json!("acme"),
1020 },
1021 ];
1022
1023 for caveat in &caveats {
1024 let json = serde_json::to_string(caveat).unwrap();
1025 let restored: Caveat = serde_json::from_str(&json).unwrap();
1026 assert_eq!(&restored, caveat, "Roundtrip failed for {:?}", caveat);
1027 }
1028 }
1029
1030 #[test]
1031 fn test_glob_matching() {
1032 assert!(matches_glob("entity:*", "entity:customer:123"));
1033 assert!(matches_glob("entity:customer:*", "entity:customer:123"));
1034 assert!(!matches_glob("entity:customer:*", "entity:order:456"));
1035 assert!(matches_glob("exact", "exact"));
1036 assert!(!matches_glob("exact", "other"));
1037 assert!(matches_glob("*", "anything"));
1038 }
1039
1040 #[test]
1041 fn test_max_cost_missing_field_fails() {
1042 let root = keypair();
1043 let agent_b = keypair();
1044
1045 let delegation =
1046 Delegation::create_root(&root, &agent_b.identity().did, vec![Caveat::MaxCost(5.0)])
1047 .unwrap();
1048
1049 let invocation =
1051 Invocation::create(&agent_b, "resolve", serde_json::json!({}), delegation).unwrap();
1052
1053 let result = verify_invocation(&invocation, &agent_b.identity(), &root.identity());
1054 assert!(matches!(result, Err(CryptoError::CaveatViolation(_))));
1055 }
1056
1057 #[test]
1058 fn test_resource_missing_field_fails() {
1059 let root = keypair();
1060 let agent_b = keypair();
1061
1062 let delegation = Delegation::create_root(
1063 &root,
1064 &agent_b.identity().did,
1065 vec![Caveat::Resource("entity:*".into())],
1066 )
1067 .unwrap();
1068
1069 let invocation =
1071 Invocation::create(&agent_b, "resolve", serde_json::json!({}), delegation).unwrap();
1072
1073 let result = verify_invocation(&invocation, &agent_b.identity(), &root.identity());
1074 assert!(matches!(result, Err(CryptoError::CaveatViolation(_))));
1075 }
1076
1077 #[test]
1078 fn test_embedded_public_key_present() {
1079 let root = keypair();
1080 let agent_b = keypair();
1081
1082 let delegation = Delegation::create_root(&root, &agent_b.identity().did, vec![]).unwrap();
1083
1084 assert_eq!(
1085 delegation.issuer_public_key,
1086 root.identity().public_key_bytes
1087 );
1088 }
1089
1090 #[test]
1091 fn test_tampered_delegation_caveats_detected() {
1092 let root = keypair();
1093 let agent_b = keypair();
1094
1095 let mut delegation = Delegation::create_root(
1096 &root,
1097 &agent_b.identity().did,
1098 vec![Caveat::ActionScope(vec!["resolve".into()])],
1099 )
1100 .unwrap();
1101
1102 delegation.caveats = vec![Caveat::ActionScope(vec!["resolve".into(), "merge".into()])];
1104
1105 let invocation =
1106 Invocation::create(&agent_b, "merge", serde_json::json!({}), delegation).unwrap();
1107
1108 let result = verify_invocation(&invocation, &agent_b.identity(), &root.identity());
1111 assert!(matches!(result, Err(CryptoError::CaveatViolation(_))));
1112 }
1113
1114 #[test]
1115 fn test_intermediate_signature_verified() {
1116 let root = keypair();
1117 let agent_b = keypair();
1118 let agent_c = keypair();
1119
1120 let d1 = Delegation::create_root(
1121 &root,
1122 &agent_b.identity().did,
1123 vec![Caveat::ActionScope(vec!["resolve".into()])],
1124 )
1125 .unwrap();
1126
1127 let mut d2 = Delegation::delegate(&agent_b, &agent_c.identity().did, vec![], d1).unwrap();
1128
1129 d2.proof.signature = "00".repeat(64);
1131
1132 let invocation =
1133 Invocation::create(&agent_c, "resolve", serde_json::json!({}), d2).unwrap();
1134
1135 let result = verify_invocation(&invocation, &agent_c.identity(), &root.identity());
1137 assert!(result.is_err());
1138 }
1139
1140 #[test]
1143 fn test_revocation_blocks_invocation() {
1144 let root = keypair();
1145 let agent_b = keypair();
1146
1147 let delegation = Delegation::create_root(&root, &agent_b.identity().did, vec![]).unwrap();
1148
1149 let revoked_hash = delegation.proof.content_hash();
1150
1151 let invocation =
1152 Invocation::create(&agent_b, "resolve", serde_json::json!({}), delegation).unwrap();
1153
1154 assert!(verify_invocation(&invocation, &agent_b.identity(), &root.identity()).is_ok());
1156
1157 let result = verify_invocation_with_revocation(
1159 &invocation,
1160 &agent_b.identity(),
1161 &root.identity(),
1162 |hash| hash == revoked_hash,
1163 );
1164 assert!(matches!(result, Err(CryptoError::DelegationRevoked(_))));
1165 }
1166
1167 #[test]
1168 fn test_revocation_in_chain_blocks() {
1169 let root = keypair();
1170 let agent_b = keypair();
1171 let agent_c = keypair();
1172
1173 let d1 = Delegation::create_root(&root, &agent_b.identity().did, vec![]).unwrap();
1174
1175 let revoked_hash = d1.proof.content_hash();
1176
1177 let d2 = Delegation::delegate(&agent_b, &agent_c.identity().did, vec![], d1).unwrap();
1178
1179 let invocation =
1180 Invocation::create(&agent_c, "resolve", serde_json::json!({}), d2).unwrap();
1181
1182 let result = verify_invocation_with_revocation(
1184 &invocation,
1185 &agent_c.identity(),
1186 &root.identity(),
1187 |hash| hash == revoked_hash,
1188 );
1189 assert!(matches!(result, Err(CryptoError::DelegationRevoked(_))));
1190 }
1191
1192 #[test]
1193 fn test_no_revocation_callback_passes() {
1194 let root = keypair();
1195 let agent_b = keypair();
1196
1197 let delegation = Delegation::create_root(&root, &agent_b.identity().did, vec![]).unwrap();
1198
1199 let invocation =
1200 Invocation::create(&agent_b, "resolve", serde_json::json!({}), delegation).unwrap();
1201
1202 let result = verify_invocation_with_revocation(
1204 &invocation,
1205 &agent_b.identity(),
1206 &root.identity(),
1207 |_| false,
1208 );
1209 assert!(result.is_ok());
1210 }
1211}