1use alloy::primitives::{Address, B256, U256};
6use alloy::providers::ProviderBuilder;
7use alloy::signers::Signature;
8use std::collections::HashMap;
9use std::time::{SystemTime, UNIX_EPOCH};
10
11use crate::agent::{address_to_agent_key, compute_signing_message};
12use crate::constants::{
13 network_config, IAgentRegistry, NetworkName, DEFAULT_CACHE_TTL_MS, DEFAULT_MAX_AGE_MS,
14 DEFAULT_NETWORK,
15};
16use crate::ed25519_agent::derive_address_from_pubkey;
17
18#[derive(Debug, Clone)]
24pub struct VerifierConfig {
25 pub network: Option<NetworkName>,
27 pub registry_address: Option<Address>,
29 pub rpc_url: Option<String>,
31 pub max_age_ms: Option<u64>,
33 pub cache_ttl_ms: Option<u64>,
35 pub max_agents_per_human: Option<u64>,
37 pub include_credentials: Option<bool>,
39 pub require_self_provider: Option<bool>,
41 pub enable_replay_protection: Option<bool>,
43 pub replay_cache_max_entries: Option<usize>,
45 pub minimum_age: Option<u64>,
47 pub require_ofac_passed: Option<bool>,
49 pub allowed_nationalities: Option<Vec<String>>,
51 pub rate_limit_config: Option<RateLimitConfig>,
53}
54
55impl Default for VerifierConfig {
56 fn default() -> Self {
57 Self {
58 network: None,
59 registry_address: None,
60 rpc_url: None,
61 max_age_ms: None,
62 cache_ttl_ms: None,
63 max_agents_per_human: None,
64 include_credentials: None,
65 require_self_provider: None,
66 enable_replay_protection: None,
67 replay_cache_max_entries: None,
68 minimum_age: None,
69 require_ofac_passed: None,
70 allowed_nationalities: None,
71 rate_limit_config: None,
72 }
73 }
74}
75
76#[derive(Debug, Clone)]
78pub struct RateLimitConfig {
79 pub per_minute: Option<u32>,
81 pub per_hour: Option<u32>,
83}
84
85#[derive(Debug, Clone, Default)]
87pub struct VerifierFromConfig {
88 pub network: Option<NetworkName>,
89 pub registry_address: Option<String>,
90 pub rpc_url: Option<String>,
91 pub require_age: Option<u64>,
92 pub require_ofac: Option<bool>,
93 pub require_nationality: Option<Vec<String>>,
94 pub require_self_provider: Option<bool>,
95 pub sybil_limit: Option<u64>,
96 pub rate_limit: Option<RateLimitConfig>,
97 pub replay_protection: Option<bool>,
98 pub max_age_ms: Option<u64>,
99 pub cache_ttl_ms: Option<u64>,
100}
101
102#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
108pub struct AgentCredentials {
109 pub issuing_state: String,
110 pub name: Vec<String>,
111 pub id_number: String,
112 pub nationality: String,
113 pub date_of_birth: String,
114 pub gender: String,
115 pub expiry_date: String,
116 pub older_than: U256,
117 pub ofac: Vec<bool>,
118}
119
120#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
122pub struct VerificationResult {
123 pub valid: bool,
124 pub agent_address: Address,
126 pub agent_key: B256,
128 pub agent_id: U256,
129 pub agent_count: U256,
131 pub nullifier: U256,
133 pub credentials: Option<AgentCredentials>,
135 pub error: Option<String>,
136 pub retry_after_ms: Option<u64>,
138}
139
140impl VerificationResult {
141 fn empty_with_error(error: &str) -> Self {
142 Self {
143 valid: false,
144 agent_address: Address::ZERO,
145 agent_key: B256::ZERO,
146 agent_id: U256::ZERO,
147 agent_count: U256::ZERO,
148 nullifier: U256::ZERO,
149 credentials: None,
150 error: Some(error.to_string()),
151 retry_after_ms: None,
152 }
153 }
154}
155
156struct RateBucket {
161 timestamps: Vec<u64>,
162}
163
164struct RateLimitResult {
165 error: String,
166 retry_after_ms: u64,
167}
168
169struct RateLimiter {
171 per_minute: u32,
172 per_hour: u32,
173 buckets: HashMap<String, RateBucket>,
174}
175
176impl RateLimiter {
177 fn new(config: &RateLimitConfig) -> Self {
178 Self {
179 per_minute: config.per_minute.unwrap_or(0),
180 per_hour: config.per_hour.unwrap_or(0),
181 buckets: HashMap::new(),
182 }
183 }
184
185 fn check(&mut self, agent_address: &str) -> Option<RateLimitResult> {
187 let now = now_millis();
188 let key = agent_address.to_ascii_lowercase();
189 let bucket = self
190 .buckets
191 .entry(key)
192 .or_insert_with(|| RateBucket { timestamps: Vec::new() });
193
194 let one_hour_ago = now.saturating_sub(60 * 60 * 1000);
196 bucket.timestamps.retain(|t| *t > one_hour_ago);
197
198 if self.per_minute > 0 {
200 let one_minute_ago = now.saturating_sub(60 * 1000);
201 let recent_minute: Vec<u64> = bucket
202 .timestamps
203 .iter()
204 .filter(|t| **t > one_minute_ago)
205 .copied()
206 .collect();
207 if recent_minute.len() >= self.per_minute as usize {
208 let oldest = recent_minute[0];
209 let retry_after = (oldest + 60 * 1000).saturating_sub(now).max(1);
210 return Some(RateLimitResult {
211 error: format!("Rate limit exceeded ({}/min)", self.per_minute),
212 retry_after_ms: retry_after,
213 });
214 }
215 }
216
217 if self.per_hour > 0 && bucket.timestamps.len() >= self.per_hour as usize {
219 let oldest = bucket.timestamps[0];
220 let retry_after = (oldest + 60 * 60 * 1000).saturating_sub(now).max(1);
221 return Some(RateLimitResult {
222 error: format!("Rate limit exceeded ({}/hr)", self.per_hour),
223 retry_after_ms: retry_after,
224 });
225 }
226
227 bucket.timestamps.push(now);
229 None
230 }
231}
232
233#[derive(Default)]
252pub struct VerifierBuilder {
253 network: Option<NetworkName>,
254 registry_address: Option<String>,
255 rpc_url: Option<String>,
256 max_age_ms: Option<u64>,
257 cache_ttl_ms: Option<u64>,
258 max_agents_per_human: Option<u64>,
259 include_credentials: Option<bool>,
260 require_self_provider: Option<bool>,
261 enable_replay_protection: Option<bool>,
262 minimum_age: Option<u64>,
263 require_ofac_passed: bool,
264 allowed_nationalities: Option<Vec<String>>,
265 rate_limit_config: Option<RateLimitConfig>,
266}
267
268impl VerifierBuilder {
269 pub fn network(mut self, name: NetworkName) -> Self {
271 self.network = Some(name);
272 self
273 }
274
275 pub fn registry(mut self, addr: &str) -> Self {
277 self.registry_address = Some(addr.to_string());
278 self
279 }
280
281 pub fn rpc(mut self, url: &str) -> Self {
283 self.rpc_url = Some(url.to_string());
284 self
285 }
286
287 pub fn require_age(mut self, n: u64) -> Self {
289 self.minimum_age = Some(n);
290 self
291 }
292
293 pub fn require_ofac(mut self) -> Self {
295 self.require_ofac_passed = true;
296 self
297 }
298
299 pub fn require_nationality(mut self, codes: &[&str]) -> Self {
301 self.allowed_nationalities = Some(codes.iter().map(|s| s.to_string()).collect());
302 self
303 }
304
305 pub fn require_self_provider(mut self) -> Self {
307 self.require_self_provider = Some(true);
308 self
309 }
310
311 pub fn sybil_limit(mut self, n: u64) -> Self {
313 self.max_agents_per_human = Some(n);
314 self
315 }
316
317 pub fn rate_limit(mut self, per_minute: u32, per_hour: u32) -> Self {
319 self.rate_limit_config = Some(RateLimitConfig {
320 per_minute: Some(per_minute),
321 per_hour: Some(per_hour),
322 });
323 self
324 }
325
326 pub fn replay_protection(mut self) -> Self {
328 self.enable_replay_protection = Some(true);
329 self
330 }
331
332 pub fn include_credentials(mut self) -> Self {
334 self.include_credentials = Some(true);
335 self
336 }
337
338 pub fn max_age(mut self, ms: u64) -> Self {
340 self.max_age_ms = Some(ms);
341 self
342 }
343
344 pub fn cache_ttl(mut self, ms: u64) -> Self {
346 self.cache_ttl_ms = Some(ms);
347 self
348 }
349
350 pub fn build(self) -> SelfAgentVerifier {
355 let needs_credentials = self.minimum_age.is_some()
357 || self.require_ofac_passed
358 || self
359 .allowed_nationalities
360 .as_ref()
361 .map_or(false, |v| !v.is_empty());
362
363 let registry_address = self
364 .registry_address
365 .and_then(|s| s.parse::<Address>().ok());
366
367 SelfAgentVerifier::new(VerifierConfig {
368 network: self.network,
369 registry_address,
370 rpc_url: self.rpc_url,
371 max_age_ms: self.max_age_ms,
372 cache_ttl_ms: self.cache_ttl_ms,
373 max_agents_per_human: self.max_agents_per_human,
374 include_credentials: if needs_credentials || self.include_credentials.unwrap_or(false) {
375 Some(true)
376 } else {
377 self.include_credentials
378 },
379 require_self_provider: self.require_self_provider,
380 enable_replay_protection: self.enable_replay_protection,
381 replay_cache_max_entries: None,
382 minimum_age: self.minimum_age,
383 require_ofac_passed: if self.require_ofac_passed {
384 Some(true)
385 } else {
386 None
387 },
388 allowed_nationalities: self.allowed_nationalities,
389 rate_limit_config: self.rate_limit_config,
390 })
391 }
392}
393
394struct CacheEntry {
399 is_verified: bool,
400 is_proof_fresh: bool,
401 agent_id: U256,
402 agent_count: U256,
403 nullifier: U256,
404 provider_address: Address,
405 expires_at: u64,
406}
407
408struct OnChainStatus {
409 is_verified: bool,
410 is_proof_fresh: bool,
411 agent_id: U256,
412 agent_count: U256,
413 nullifier: U256,
414 provider_address: Address,
415}
416
417pub struct SelfAgentVerifier {
458 registry_address: Address,
459 rpc_url: String,
460 max_age_ms: u64,
461 cache_ttl_ms: u64,
462 max_agents_per_human: u64,
463 include_credentials: bool,
464 require_self_provider: bool,
465 enable_replay_protection: bool,
466 replay_cache_max_entries: usize,
467 minimum_age: Option<u64>,
468 require_ofac_passed: bool,
469 allowed_nationalities: Option<Vec<String>>,
470 rate_limiter: Option<RateLimiter>,
471 cache: HashMap<B256, CacheEntry>,
472 replay_cache: HashMap<String, u64>,
473 self_provider_cache: Option<(Address, u64)>,
474}
475
476impl SelfAgentVerifier {
477 pub fn new(config: VerifierConfig) -> Self {
479 let net = network_config(config.network.unwrap_or(DEFAULT_NETWORK));
480 Self {
481 registry_address: config.registry_address.unwrap_or(net.registry_address),
482 rpc_url: config.rpc_url.unwrap_or_else(|| net.rpc_url.to_string()),
483 max_age_ms: config.max_age_ms.unwrap_or(DEFAULT_MAX_AGE_MS),
484 cache_ttl_ms: config.cache_ttl_ms.unwrap_or(DEFAULT_CACHE_TTL_MS),
485 max_agents_per_human: config.max_agents_per_human.unwrap_or(1),
486 include_credentials: config.include_credentials.unwrap_or(false),
487 require_self_provider: config.require_self_provider.unwrap_or(true),
488 enable_replay_protection: config.enable_replay_protection.unwrap_or(true),
489 replay_cache_max_entries: config.replay_cache_max_entries.unwrap_or(10_000),
490 minimum_age: config.minimum_age,
491 require_ofac_passed: config.require_ofac_passed.unwrap_or(false),
492 allowed_nationalities: config.allowed_nationalities,
493 rate_limiter: config.rate_limit_config.as_ref().map(RateLimiter::new),
494 cache: HashMap::new(),
495 replay_cache: HashMap::new(),
496 self_provider_cache: None,
497 }
498 }
499
500 pub fn create() -> VerifierBuilder {
502 VerifierBuilder::default()
503 }
504
505 pub fn from_config(cfg: VerifierFromConfig) -> Self {
510 let needs_credentials = cfg.require_age.is_some()
511 || cfg.require_ofac.unwrap_or(false)
512 || cfg
513 .require_nationality
514 .as_ref()
515 .map_or(false, |v| !v.is_empty());
516
517 let registry_address = cfg
518 .registry_address
519 .and_then(|s| s.parse::<Address>().ok());
520
521 Self::new(VerifierConfig {
522 network: cfg.network,
523 registry_address,
524 rpc_url: cfg.rpc_url,
525 max_age_ms: cfg.max_age_ms,
526 cache_ttl_ms: cfg.cache_ttl_ms,
527 max_agents_per_human: cfg.sybil_limit,
528 include_credentials: if needs_credentials { Some(true) } else { None },
529 require_self_provider: cfg.require_self_provider,
530 enable_replay_protection: cfg.replay_protection,
531 replay_cache_max_entries: None,
532 minimum_age: cfg.require_age,
533 require_ofac_passed: cfg.require_ofac,
534 allowed_nationalities: cfg.require_nationality,
535 rate_limit_config: cfg.rate_limit,
536 })
537 }
538
539 fn make_provider(
540 &self,
541 ) -> Result<impl alloy::providers::Provider + Clone, crate::Error> {
542 let url: reqwest::Url = self
543 .rpc_url
544 .parse()
545 .map_err(|_| crate::Error::InvalidRpcUrl)?;
546 Ok(ProviderBuilder::new().connect_http(url))
547 }
548
549 pub async fn verify(
553 &mut self,
554 signature: &str,
555 timestamp: &str,
556 method: &str,
557 url: &str,
558 body: Option<&str>,
559 ) -> VerificationResult {
560 let ts: u64 = match timestamp.parse() {
562 Ok(v) => v,
563 Err(_) => return VerificationResult::empty_with_error("Timestamp expired or invalid"),
564 };
565 let now = now_millis();
566 let diff = if now > ts { now - ts } else { ts - now };
567 if diff > self.max_age_ms {
568 return VerificationResult::empty_with_error("Timestamp expired or invalid");
569 }
570
571 let message = compute_signing_message(timestamp, method, url, body);
573 let message_key = format!("{:#x}", message);
574
575 let signer_address = match recover_address(&message, signature) {
577 Ok(addr) => addr,
578 Err(_) => return VerificationResult::empty_with_error("Invalid signature"),
579 };
580
581 if self.enable_replay_protection {
583 if let Some(err) = self.check_and_record_replay(signature, &message_key, ts, now) {
584 return VerificationResult {
585 valid: false,
586 agent_address: signer_address,
587 agent_key: address_to_agent_key(signer_address),
588 agent_id: U256::ZERO,
589 agent_count: U256::ZERO,
590 nullifier: U256::ZERO,
591 credentials: None,
592 error: Some(err),
593 retry_after_ms: None,
594 };
595 }
596 }
597
598 let agent_key = address_to_agent_key(signer_address);
600
601 let on_chain = match self.check_on_chain(agent_key).await {
603 Ok(v) => v,
604 Err(e) => {
605 return VerificationResult {
606 valid: false,
607 agent_address: signer_address,
608 agent_key,
609 agent_id: U256::ZERO,
610 agent_count: U256::ZERO,
611 nullifier: U256::ZERO,
612 credentials: None,
613 error: Some(format!("RPC error: {}", e)),
614 retry_after_ms: None,
615 };
616 }
617 };
618
619 if !on_chain.is_verified {
620 return VerificationResult {
621 valid: false,
622 agent_address: signer_address,
623 agent_key,
624 agent_id: on_chain.agent_id,
625 agent_count: on_chain.agent_count,
626 nullifier: on_chain.nullifier,
627 credentials: None,
628 error: Some("Agent not verified on-chain".to_string()),
629 retry_after_ms: None,
630 };
631 }
632
633 if !on_chain.is_proof_fresh {
635 return VerificationResult {
636 valid: false,
637 agent_address: signer_address,
638 agent_key,
639 agent_id: on_chain.agent_id,
640 agent_count: on_chain.agent_count,
641 nullifier: on_chain.nullifier,
642 credentials: None,
643 error: Some("Agent's human proof has expired".to_string()),
644 retry_after_ms: None,
645 };
646 }
647
648 if self.require_self_provider && on_chain.agent_id > U256::ZERO {
650 let self_provider = match self.get_self_provider_address().await {
651 Ok(addr) => addr,
652 Err(_) => {
653 return VerificationResult {
654 valid: false,
655 agent_address: signer_address,
656 agent_key,
657 agent_id: on_chain.agent_id,
658 agent_count: on_chain.agent_count,
659 nullifier: on_chain.nullifier,
660 credentials: None,
661 error: Some(
662 "Unable to verify proof provider — RPC error".to_string(),
663 ),
664 retry_after_ms: None,
665 };
666 }
667 };
668 if on_chain.provider_address != self_provider {
669 return VerificationResult {
670 valid: false,
671 agent_address: signer_address,
672 agent_key,
673 agent_id: on_chain.agent_id,
674 agent_count: on_chain.agent_count,
675 nullifier: on_chain.nullifier,
676 credentials: None,
677 error: Some(
678 "Agent was not verified by Self — proof provider mismatch".to_string(),
679 ),
680 retry_after_ms: None,
681 };
682 }
683 }
684
685 if self.max_agents_per_human > 0
687 && on_chain.agent_count > U256::from(self.max_agents_per_human)
688 {
689 return VerificationResult {
690 valid: false,
691 agent_address: signer_address,
692 agent_key,
693 agent_id: on_chain.agent_id,
694 agent_count: on_chain.agent_count,
695 nullifier: on_chain.nullifier,
696 credentials: None,
697 error: Some(format!(
698 "Human has {} agents (max {})",
699 on_chain.agent_count, self.max_agents_per_human
700 )),
701 retry_after_ms: None,
702 };
703 }
704
705 let credentials = if self.include_credentials && on_chain.agent_id > U256::ZERO {
707 self.fetch_credentials(on_chain.agent_id).await.ok()
708 } else {
709 None
710 };
711
712 if let Some(ref creds) = credentials {
714 if let Some(min_age) = self.minimum_age {
715 if creds.older_than < U256::from(min_age) {
716 return VerificationResult {
717 valid: false,
718 agent_address: signer_address,
719 agent_key,
720 agent_id: on_chain.agent_id,
721 agent_count: on_chain.agent_count,
722 nullifier: on_chain.nullifier,
723 credentials: credentials.clone(),
724 error: Some(format!(
725 "Agent's human does not meet minimum age (required: {}, got: {})",
726 min_age, creds.older_than
727 )),
728 retry_after_ms: None,
729 };
730 }
731 }
732
733 if self.require_ofac_passed && !creds.ofac.first().copied().unwrap_or(false) {
734 return VerificationResult {
735 valid: false,
736 agent_address: signer_address,
737 agent_key,
738 agent_id: on_chain.agent_id,
739 agent_count: on_chain.agent_count,
740 nullifier: on_chain.nullifier,
741 credentials: credentials.clone(),
742 error: Some("Agent's human did not pass OFAC screening".to_string()),
743 retry_after_ms: None,
744 };
745 }
746
747 if let Some(ref allowed) = self.allowed_nationalities {
748 if !allowed.is_empty() && !allowed.contains(&creds.nationality) {
749 return VerificationResult {
750 valid: false,
751 agent_address: signer_address,
752 agent_key,
753 agent_id: on_chain.agent_id,
754 agent_count: on_chain.agent_count,
755 nullifier: on_chain.nullifier,
756 credentials: credentials.clone(),
757 error: Some(format!(
758 "Nationality \"{}\" not in allowed list",
759 creds.nationality
760 )),
761 retry_after_ms: None,
762 };
763 }
764 }
765 }
766
767 if let Some(ref mut limiter) = self.rate_limiter {
769 let addr_str = format!("{:#x}", signer_address);
770 if let Some(limited) = limiter.check(&addr_str) {
771 return VerificationResult {
772 valid: false,
773 agent_address: signer_address,
774 agent_key,
775 agent_id: on_chain.agent_id,
776 agent_count: on_chain.agent_count,
777 nullifier: on_chain.nullifier,
778 credentials,
779 error: Some(limited.error),
780 retry_after_ms: Some(limited.retry_after_ms),
781 };
782 }
783 }
784
785 VerificationResult {
786 valid: true,
787 agent_address: signer_address,
788 agent_key,
789 agent_id: on_chain.agent_id,
790 agent_count: on_chain.agent_count,
791 nullifier: on_chain.nullifier,
792 credentials,
793 error: None,
794 retry_after_ms: None,
795 }
796 }
797
798 pub async fn verify_with_keytype(
804 &mut self,
805 signature: &str,
806 timestamp: &str,
807 method: &str,
808 url: &str,
809 body: Option<&str>,
810 keytype: Option<&str>,
811 agent_key: Option<&str>,
812 ) -> VerificationResult {
813 if keytype == Some("ed25519") {
814 return self
815 .verify_ed25519(signature, timestamp, method, url, body, agent_key)
816 .await;
817 }
818
819 self.verify(signature, timestamp, method, url, body).await
821 }
822
823 async fn verify_ed25519(
829 &mut self,
830 signature: &str,
831 timestamp: &str,
832 method: &str,
833 url: &str,
834 body: Option<&str>,
835 agent_key_hex: Option<&str>,
836 ) -> VerificationResult {
837 let key_hex = match agent_key_hex {
839 Some(k) => k,
840 None => {
841 return VerificationResult::empty_with_error(
842 "Missing agent key for Ed25519 verification",
843 );
844 }
845 };
846
847 let ts: u64 = match timestamp.parse() {
849 Ok(v) => v,
850 Err(_) => {
851 return VerificationResult::empty_with_error("Timestamp expired or invalid");
852 }
853 };
854 let now = now_millis();
855 let diff = if now > ts { now - ts } else { ts - now };
856 if diff > self.max_age_ms {
857 return VerificationResult::empty_with_error("Timestamp expired or invalid");
858 }
859
860 let message = compute_signing_message(timestamp, method, url, body);
862 let message_key = format!("{:#x}", message);
863
864 let key_stripped = key_hex.strip_prefix("0x").unwrap_or(key_hex);
866 let key_bytes = match hex::decode(key_stripped) {
867 Ok(b) => b,
868 Err(_) => {
869 return VerificationResult::empty_with_error("Invalid Ed25519 agent key");
870 }
871 };
872 let key_array: [u8; 32] = match key_bytes.try_into() {
873 Ok(a) => a,
874 Err(_) => {
875 return VerificationResult::empty_with_error("Invalid Ed25519 agent key");
876 }
877 };
878 let verifying_key = match ed25519_dalek::VerifyingKey::from_bytes(&key_array) {
879 Ok(vk) => vk,
880 Err(_) => {
881 return VerificationResult::empty_with_error("Invalid Ed25519 agent key");
882 }
883 };
884
885 let sig_stripped = signature.strip_prefix("0x").unwrap_or(signature);
887 let sig_bytes = match hex::decode(sig_stripped) {
888 Ok(b) => b,
889 Err(_) => {
890 return VerificationResult::empty_with_error("Invalid Ed25519 signature");
891 }
892 };
893 let sig_array: [u8; 64] = match sig_bytes.try_into() {
894 Ok(a) => a,
895 Err(_) => {
896 return VerificationResult::empty_with_error("Invalid Ed25519 signature");
897 }
898 };
899 let ed_signature = ed25519_dalek::Signature::from_bytes(&sig_array);
900
901 {
902 use ed25519_dalek::Verifier;
903 if verifying_key.verify(message.as_ref(), &ed_signature).is_err() {
904 return VerificationResult::empty_with_error("Invalid Ed25519 signature");
905 }
906 }
907
908 let signer_address = derive_address_from_pubkey(&key_array);
910
911 let agent_key = B256::from(key_array);
913
914 if self.enable_replay_protection {
916 if let Some(err) = self.check_and_record_replay(signature, &message_key, ts, now) {
917 return VerificationResult {
918 valid: false,
919 agent_address: signer_address,
920 agent_key,
921 agent_id: U256::ZERO,
922 agent_count: U256::ZERO,
923 nullifier: U256::ZERO,
924 credentials: None,
925 error: Some(err),
926 retry_after_ms: None,
927 };
928 }
929 }
930
931 self.verify_on_chain_and_policy(signer_address, agent_key)
934 .await
935 }
936
937 async fn verify_on_chain_and_policy(
939 &mut self,
940 signer_address: Address,
941 agent_key: B256,
942 ) -> VerificationResult {
943 let on_chain = match self.check_on_chain(agent_key).await {
945 Ok(v) => v,
946 Err(e) => {
947 return VerificationResult {
948 valid: false,
949 agent_address: signer_address,
950 agent_key,
951 agent_id: U256::ZERO,
952 agent_count: U256::ZERO,
953 nullifier: U256::ZERO,
954 credentials: None,
955 error: Some(format!("RPC error: {}", e)),
956 retry_after_ms: None,
957 };
958 }
959 };
960
961 if !on_chain.is_verified {
962 return VerificationResult {
963 valid: false,
964 agent_address: signer_address,
965 agent_key,
966 agent_id: on_chain.agent_id,
967 agent_count: on_chain.agent_count,
968 nullifier: on_chain.nullifier,
969 credentials: None,
970 error: Some("Agent not verified on-chain".to_string()),
971 retry_after_ms: None,
972 };
973 }
974
975 if !on_chain.is_proof_fresh {
976 return VerificationResult {
977 valid: false,
978 agent_address: signer_address,
979 agent_key,
980 agent_id: on_chain.agent_id,
981 agent_count: on_chain.agent_count,
982 nullifier: on_chain.nullifier,
983 credentials: None,
984 error: Some("Agent's human proof has expired".to_string()),
985 retry_after_ms: None,
986 };
987 }
988
989 if self.require_self_provider && on_chain.agent_id > U256::ZERO {
991 let self_provider = match self.get_self_provider_address().await {
992 Ok(addr) => addr,
993 Err(_) => {
994 return VerificationResult {
995 valid: false,
996 agent_address: signer_address,
997 agent_key,
998 agent_id: on_chain.agent_id,
999 agent_count: on_chain.agent_count,
1000 nullifier: on_chain.nullifier,
1001 credentials: None,
1002 error: Some(
1003 "Unable to verify proof provider — RPC error".to_string(),
1004 ),
1005 retry_after_ms: None,
1006 };
1007 }
1008 };
1009 if on_chain.provider_address != self_provider {
1010 return VerificationResult {
1011 valid: false,
1012 agent_address: signer_address,
1013 agent_key,
1014 agent_id: on_chain.agent_id,
1015 agent_count: on_chain.agent_count,
1016 nullifier: on_chain.nullifier,
1017 credentials: None,
1018 error: Some(
1019 "Agent was not verified by Self — proof provider mismatch".to_string(),
1020 ),
1021 retry_after_ms: None,
1022 };
1023 }
1024 }
1025
1026 if self.max_agents_per_human > 0
1028 && on_chain.agent_count > U256::from(self.max_agents_per_human)
1029 {
1030 return VerificationResult {
1031 valid: false,
1032 agent_address: signer_address,
1033 agent_key,
1034 agent_id: on_chain.agent_id,
1035 agent_count: on_chain.agent_count,
1036 nullifier: on_chain.nullifier,
1037 credentials: None,
1038 error: Some(format!(
1039 "Human has {} agents (max {})",
1040 on_chain.agent_count, self.max_agents_per_human
1041 )),
1042 retry_after_ms: None,
1043 };
1044 }
1045
1046 let credentials = if self.include_credentials && on_chain.agent_id > U256::ZERO {
1048 self.fetch_credentials(on_chain.agent_id).await.ok()
1049 } else {
1050 None
1051 };
1052
1053 if let Some(ref creds) = credentials {
1055 if let Some(min_age) = self.minimum_age {
1056 if creds.older_than < U256::from(min_age) {
1057 return VerificationResult {
1058 valid: false,
1059 agent_address: signer_address,
1060 agent_key,
1061 agent_id: on_chain.agent_id,
1062 agent_count: on_chain.agent_count,
1063 nullifier: on_chain.nullifier,
1064 credentials: credentials.clone(),
1065 error: Some(format!(
1066 "Agent's human does not meet minimum age (required: {}, got: {})",
1067 min_age, creds.older_than
1068 )),
1069 retry_after_ms: None,
1070 };
1071 }
1072 }
1073
1074 if self.require_ofac_passed && !creds.ofac.first().copied().unwrap_or(false) {
1075 return VerificationResult {
1076 valid: false,
1077 agent_address: signer_address,
1078 agent_key,
1079 agent_id: on_chain.agent_id,
1080 agent_count: on_chain.agent_count,
1081 nullifier: on_chain.nullifier,
1082 credentials: credentials.clone(),
1083 error: Some("Agent's human did not pass OFAC screening".to_string()),
1084 retry_after_ms: None,
1085 };
1086 }
1087
1088 if let Some(ref allowed) = self.allowed_nationalities {
1089 if !allowed.is_empty() && !allowed.contains(&creds.nationality) {
1090 return VerificationResult {
1091 valid: false,
1092 agent_address: signer_address,
1093 agent_key,
1094 agent_id: on_chain.agent_id,
1095 agent_count: on_chain.agent_count,
1096 nullifier: on_chain.nullifier,
1097 credentials: credentials.clone(),
1098 error: Some(format!(
1099 "Nationality \"{}\" not in allowed list",
1100 creds.nationality
1101 )),
1102 retry_after_ms: None,
1103 };
1104 }
1105 }
1106 }
1107
1108 if let Some(ref mut limiter) = self.rate_limiter {
1110 let addr_str = format!("{:#x}", signer_address);
1111 if let Some(limited) = limiter.check(&addr_str) {
1112 return VerificationResult {
1113 valid: false,
1114 agent_address: signer_address,
1115 agent_key,
1116 agent_id: on_chain.agent_id,
1117 agent_count: on_chain.agent_count,
1118 nullifier: on_chain.nullifier,
1119 credentials,
1120 error: Some(limited.error),
1121 retry_after_ms: Some(limited.retry_after_ms),
1122 };
1123 }
1124 }
1125
1126 VerificationResult {
1127 valid: true,
1128 agent_address: signer_address,
1129 agent_key,
1130 agent_id: on_chain.agent_id,
1131 agent_count: on_chain.agent_count,
1132 nullifier: on_chain.nullifier,
1133 credentials,
1134 error: None,
1135 retry_after_ms: None,
1136 }
1137 }
1138
1139 async fn check_on_chain(&mut self, agent_key: B256) -> Result<OnChainStatus, crate::Error> {
1141 let now = now_millis();
1142 if let Some(cached) = self.cache.get(&agent_key) {
1143 if cached.expires_at > now {
1144 return Ok(OnChainStatus {
1145 is_verified: cached.is_verified,
1146 is_proof_fresh: cached.is_proof_fresh,
1147 agent_id: cached.agent_id,
1148 agent_count: cached.agent_count,
1149 nullifier: cached.nullifier,
1150 provider_address: cached.provider_address,
1151 });
1152 }
1153 }
1154
1155 let provider = self.make_provider()?;
1156 let registry = IAgentRegistry::new(self.registry_address, &provider);
1157
1158 let is_verified = registry
1159 .isVerifiedAgent(agent_key)
1160 .call()
1161 .await
1162 .map_err(|e| crate::Error::RpcError(e.to_string()))?;
1163 let agent_id = registry
1164 .getAgentId(agent_key)
1165 .call()
1166 .await
1167 .map_err(|e| crate::Error::RpcError(e.to_string()))?;
1168
1169 let mut agent_count = U256::ZERO;
1170 let mut nullifier = U256::ZERO;
1171 let mut provider_address = Address::ZERO;
1172 let mut is_proof_fresh = false;
1173
1174 if agent_id > U256::ZERO {
1175 is_proof_fresh = registry
1176 .isProofFresh(agent_id)
1177 .call()
1178 .await
1179 .map_err(|e| crate::Error::RpcError(e.to_string()))?;
1180
1181 if self.max_agents_per_human > 0 {
1182 nullifier = registry
1183 .getHumanNullifier(agent_id)
1184 .call()
1185 .await
1186 .map_err(|e| crate::Error::RpcError(e.to_string()))?;
1187 agent_count = registry
1188 .getAgentCountForHuman(nullifier)
1189 .call()
1190 .await
1191 .map_err(|e| crate::Error::RpcError(e.to_string()))?;
1192 }
1193
1194 if self.require_self_provider {
1195 provider_address = registry
1196 .getProofProvider(agent_id)
1197 .call()
1198 .await
1199 .map_err(|e| crate::Error::RpcError(e.to_string()))?;
1200 }
1201 }
1202
1203 self.cache.insert(
1204 agent_key,
1205 CacheEntry {
1206 is_verified,
1207 is_proof_fresh,
1208 agent_id,
1209 agent_count,
1210 nullifier,
1211 provider_address,
1212 expires_at: now + self.cache_ttl_ms,
1213 },
1214 );
1215
1216 Ok(OnChainStatus {
1217 is_verified,
1218 is_proof_fresh,
1219 agent_id,
1220 agent_count,
1221 nullifier,
1222 provider_address,
1223 })
1224 }
1225
1226 async fn get_self_provider_address(&mut self) -> Result<Address, crate::Error> {
1228 let now = now_millis();
1229 if let Some((addr, expires_at)) = self.self_provider_cache {
1230 if expires_at > now {
1231 return Ok(addr);
1232 }
1233 }
1234
1235 let provider = self.make_provider()?;
1236 let registry = IAgentRegistry::new(self.registry_address, &provider);
1237
1238 let address = registry
1239 .selfProofProvider()
1240 .call()
1241 .await
1242 .map_err(|e| crate::Error::RpcError(e.to_string()))?;
1243
1244 self.self_provider_cache = Some((address, now + self.cache_ttl_ms * 12));
1246
1247 Ok(address)
1248 }
1249
1250 async fn fetch_credentials(&self, agent_id: U256) -> Result<AgentCredentials, crate::Error> {
1252 let provider = self.make_provider()?;
1253 let registry = IAgentRegistry::new(self.registry_address, &provider);
1254
1255 let raw = registry
1256 .getAgentCredentials(agent_id)
1257 .call()
1258 .await
1259 .map_err(|e| crate::Error::RpcError(e.to_string()))?;
1260
1261 Ok(AgentCredentials {
1262 issuing_state: raw.issuingState,
1263 name: raw.name,
1264 id_number: raw.idNumber,
1265 nationality: raw.nationality,
1266 date_of_birth: raw.dateOfBirth,
1267 gender: raw.gender,
1268 expiry_date: raw.expiryDate,
1269 older_than: raw.olderThan,
1270 ofac: raw.ofac.to_vec(),
1271 })
1272 }
1273
1274 pub fn clear_cache(&mut self) {
1276 self.cache.clear();
1277 self.replay_cache.clear();
1278 self.self_provider_cache = None;
1279 }
1280
1281 fn check_and_record_replay(
1282 &mut self,
1283 signature: &str,
1284 message: &str,
1285 ts: u64,
1286 now: u64,
1287 ) -> Option<String> {
1288 self.prune_replay_cache(now);
1289
1290 let key = format!(
1291 "{}:{}",
1292 signature.to_ascii_lowercase(),
1293 message.to_ascii_lowercase()
1294 );
1295 if let Some(expires_at) = self.replay_cache.get(&key) {
1296 if *expires_at > now {
1297 return Some("Replay detected".to_string());
1298 }
1299 }
1300
1301 self.replay_cache.insert(key, ts.saturating_add(self.max_age_ms));
1302 None
1303 }
1304
1305 fn prune_replay_cache(&mut self, now: u64) {
1306 self.replay_cache.retain(|_, exp| *exp > now);
1307
1308 if self.replay_cache.len() <= self.replay_cache_max_entries {
1309 return;
1310 }
1311
1312 let overflow = self.replay_cache.len() - self.replay_cache_max_entries;
1313 let mut items: Vec<(String, u64)> =
1314 self.replay_cache.iter().map(|(k, v)| (k.clone(), *v)).collect();
1315 items.sort_by_key(|(_, exp)| *exp);
1316
1317 for (key, _) in items.into_iter().take(overflow) {
1318 self.replay_cache.remove(&key);
1319 }
1320 }
1321}
1322
1323fn recover_address(message: &B256, signature_hex: &str) -> Result<Address, crate::Error> {
1327 let sig_bytes = hex::decode(signature_hex.strip_prefix("0x").unwrap_or(signature_hex))
1328 .map_err(|_| crate::Error::InvalidSignature)?;
1329
1330 let signature = Signature::try_from(sig_bytes.as_slice())
1331 .map_err(|_| crate::Error::InvalidSignature)?;
1332
1333 let prefixed = alloy::primitives::eip191_hash_message(message.as_slice());
1335
1336 let recovered = signature
1337 .recover_address_from_prehash(&prefixed)
1338 .map_err(|_| crate::Error::InvalidSignature)?;
1339
1340 Ok(recovered)
1341}
1342
1343fn now_millis() -> u64 {
1344 SystemTime::now()
1345 .duration_since(UNIX_EPOCH)
1346 .expect("system clock before UNIX epoch")
1347 .as_millis() as u64
1348}
1349
1350#[cfg(test)]
1355mod tests {
1356 use super::*;
1357
1358 #[test]
1359 fn create_build_default() {
1360 let v = SelfAgentVerifier::create().build();
1361 assert_eq!(v.max_agents_per_human, 1);
1363 assert!(v.require_self_provider);
1364 assert!(v.enable_replay_protection);
1365 assert!(!v.include_credentials);
1366 assert!(v.minimum_age.is_none());
1367 assert!(!v.require_ofac_passed);
1368 assert!(v.allowed_nationalities.is_none());
1369 assert!(v.rate_limiter.is_none());
1370 }
1371
1372 #[test]
1373 fn create_build_testnet() {
1374 let v = SelfAgentVerifier::create()
1375 .network(NetworkName::Testnet)
1376 .build();
1377 let expected = network_config(NetworkName::Testnet);
1378 assert_eq!(v.registry_address, expected.registry_address);
1379 assert_eq!(v.rpc_url, expected.rpc_url);
1380 }
1381
1382 #[test]
1383 fn chain_credentials() {
1384 let v = SelfAgentVerifier::create()
1385 .network(NetworkName::Testnet)
1386 .require_age(18)
1387 .require_ofac()
1388 .require_nationality(&["US", "GB"])
1389 .build();
1390
1391 assert!(v.include_credentials);
1393 assert_eq!(v.minimum_age, Some(18));
1394 assert!(v.require_ofac_passed);
1395 assert_eq!(
1396 v.allowed_nationalities.as_deref(),
1397 Some(vec!["US".to_string(), "GB".to_string()].as_slice())
1398 );
1399 }
1400
1401 #[test]
1402 fn auto_enable_credentials_age_only() {
1403 let v = SelfAgentVerifier::create()
1404 .require_age(21)
1405 .build();
1406 assert!(v.include_credentials);
1407 assert_eq!(v.minimum_age, Some(21));
1408 }
1409
1410 #[test]
1411 fn auto_enable_credentials_ofac_only() {
1412 let v = SelfAgentVerifier::create()
1413 .require_ofac()
1414 .build();
1415 assert!(v.include_credentials);
1416 assert!(v.require_ofac_passed);
1417 }
1418
1419 #[test]
1420 fn auto_enable_credentials_nationality_only() {
1421 let v = SelfAgentVerifier::create()
1422 .require_nationality(&["DE"])
1423 .build();
1424 assert!(v.include_credentials);
1425 }
1426
1427 #[test]
1428 fn no_auto_credentials_without_requirements() {
1429 let v = SelfAgentVerifier::create()
1430 .network(NetworkName::Testnet)
1431 .sybil_limit(3)
1432 .build();
1433 assert!(!v.include_credentials);
1434 }
1435
1436 #[test]
1437 fn explicit_include_credentials() {
1438 let v = SelfAgentVerifier::create()
1439 .include_credentials()
1440 .build();
1441 assert!(v.include_credentials);
1442 }
1443
1444 #[test]
1445 fn from_config_works() {
1446 let v = SelfAgentVerifier::from_config(VerifierFromConfig {
1447 network: Some(NetworkName::Testnet),
1448 require_age: Some(18),
1449 require_ofac: Some(true),
1450 sybil_limit: Some(1),
1451 ..Default::default()
1452 });
1453 assert!(v.include_credentials);
1454 assert_eq!(v.minimum_age, Some(18));
1455 assert!(v.require_ofac_passed);
1456 assert_eq!(v.max_agents_per_human, 1);
1457 }
1458
1459 #[test]
1460 fn from_config_auto_credentials_disabled() {
1461 let v = SelfAgentVerifier::from_config(VerifierFromConfig {
1462 network: Some(NetworkName::Testnet),
1463 sybil_limit: Some(5),
1464 ..Default::default()
1465 });
1466 assert!(!v.include_credentials);
1467 }
1468
1469 #[test]
1470 fn from_config_nationality() {
1471 let v = SelfAgentVerifier::from_config(VerifierFromConfig {
1472 require_nationality: Some(vec!["FR".to_string(), "IT".to_string()]),
1473 ..Default::default()
1474 });
1475 assert!(v.include_credentials);
1476 assert_eq!(
1477 v.allowed_nationalities.as_deref(),
1478 Some(vec!["FR".to_string(), "IT".to_string()].as_slice())
1479 );
1480 }
1481
1482 #[test]
1483 fn rate_limit_builder() {
1484 let v = SelfAgentVerifier::create()
1485 .rate_limit(10, 100)
1486 .build();
1487 assert!(v.rate_limiter.is_some());
1488 let limiter = v.rate_limiter.as_ref().unwrap();
1489 assert_eq!(limiter.per_minute, 10);
1490 assert_eq!(limiter.per_hour, 100);
1491 }
1492
1493 #[test]
1494 fn rate_limit_from_config() {
1495 let v = SelfAgentVerifier::from_config(VerifierFromConfig {
1496 rate_limit: Some(RateLimitConfig {
1497 per_minute: Some(5),
1498 per_hour: Some(50),
1499 }),
1500 ..Default::default()
1501 });
1502 assert!(v.rate_limiter.is_some());
1503 }
1504
1505 #[test]
1506 fn rate_limiter_allows_within_limit() {
1507 let config = RateLimitConfig {
1508 per_minute: Some(3),
1509 per_hour: None,
1510 };
1511 let mut limiter = RateLimiter::new(&config);
1512 assert!(limiter.check("0xabc").is_none());
1513 assert!(limiter.check("0xabc").is_none());
1514 assert!(limiter.check("0xabc").is_none());
1515 let result = limiter.check("0xabc");
1517 assert!(result.is_some());
1518 let r = result.unwrap();
1519 assert!(r.error.contains("3/min"));
1520 assert!(r.retry_after_ms > 0);
1521 }
1522
1523 #[test]
1524 fn rate_limiter_separate_agents() {
1525 let config = RateLimitConfig {
1526 per_minute: Some(1),
1527 per_hour: None,
1528 };
1529 let mut limiter = RateLimiter::new(&config);
1530 assert!(limiter.check("0xabc").is_none());
1531 assert!(limiter.check("0xdef").is_none());
1532 assert!(limiter.check("0xabc").is_some());
1534 assert!(limiter.check("0xghi").is_none());
1536 }
1537
1538 #[test]
1539 fn builder_custom_max_age_and_cache_ttl() {
1540 let v = SelfAgentVerifier::create()
1541 .max_age(10_000)
1542 .cache_ttl(30_000)
1543 .build();
1544 assert_eq!(v.max_age_ms, 10_000);
1545 assert_eq!(v.cache_ttl_ms, 30_000);
1546 }
1547
1548 #[test]
1549 fn builder_sybil_limit_zero_disables() {
1550 let v = SelfAgentVerifier::create()
1551 .sybil_limit(0)
1552 .build();
1553 assert_eq!(v.max_agents_per_human, 0);
1554 }
1555
1556 #[test]
1557 fn builder_replay_protection() {
1558 let v = SelfAgentVerifier::create()
1559 .replay_protection()
1560 .build();
1561 assert!(v.enable_replay_protection);
1562 }
1563
1564 #[test]
1565 fn builder_require_self_provider() {
1566 let v = SelfAgentVerifier::create()
1567 .require_self_provider()
1568 .build();
1569 assert!(v.require_self_provider);
1570 }
1571
1572 #[test]
1573 fn new_constructor_still_works() {
1574 let v = SelfAgentVerifier::new(VerifierConfig::default());
1575 assert_eq!(v.max_age_ms, DEFAULT_MAX_AGE_MS);
1576 assert_eq!(v.cache_ttl_ms, DEFAULT_CACHE_TTL_MS);
1577 assert_eq!(v.max_agents_per_human, 1);
1578 assert!(v.require_self_provider);
1579 }
1580
1581 #[test]
1582 fn new_constructor_with_credentials() {
1583 let v = SelfAgentVerifier::new(VerifierConfig {
1584 minimum_age: Some(21),
1585 require_ofac_passed: Some(true),
1586 include_credentials: Some(true),
1587 ..Default::default()
1588 });
1589 assert!(v.include_credentials);
1590 assert_eq!(v.minimum_age, Some(21));
1591 assert!(v.require_ofac_passed);
1592 }
1593
1594 #[test]
1595 fn verification_result_has_retry_after() {
1596 let r = VerificationResult::empty_with_error("test");
1597 assert!(r.retry_after_ms.is_none());
1598 }
1599}