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};
16
17#[derive(Debug, Clone)]
23pub struct VerifierConfig {
24 pub network: Option<NetworkName>,
26 pub registry_address: Option<Address>,
28 pub rpc_url: Option<String>,
30 pub max_age_ms: Option<u64>,
32 pub cache_ttl_ms: Option<u64>,
34 pub max_agents_per_human: Option<u64>,
36 pub include_credentials: Option<bool>,
38 pub require_self_provider: Option<bool>,
40 pub enable_replay_protection: Option<bool>,
42 pub replay_cache_max_entries: Option<usize>,
44 pub minimum_age: Option<u64>,
46 pub require_ofac_passed: Option<bool>,
48 pub allowed_nationalities: Option<Vec<String>>,
50 pub rate_limit_config: Option<RateLimitConfig>,
52}
53
54impl Default for VerifierConfig {
55 fn default() -> Self {
56 Self {
57 network: None,
58 registry_address: None,
59 rpc_url: None,
60 max_age_ms: None,
61 cache_ttl_ms: None,
62 max_agents_per_human: None,
63 include_credentials: None,
64 require_self_provider: None,
65 enable_replay_protection: None,
66 replay_cache_max_entries: None,
67 minimum_age: None,
68 require_ofac_passed: None,
69 allowed_nationalities: None,
70 rate_limit_config: None,
71 }
72 }
73}
74
75#[derive(Debug, Clone)]
77pub struct RateLimitConfig {
78 pub per_minute: Option<u32>,
80 pub per_hour: Option<u32>,
82}
83
84#[derive(Debug, Clone, Default)]
86pub struct VerifierFromConfig {
87 pub network: Option<NetworkName>,
88 pub registry_address: Option<String>,
89 pub rpc_url: Option<String>,
90 pub require_age: Option<u64>,
91 pub require_ofac: Option<bool>,
92 pub require_nationality: Option<Vec<String>>,
93 pub require_self_provider: Option<bool>,
94 pub sybil_limit: Option<u64>,
95 pub rate_limit: Option<RateLimitConfig>,
96 pub replay_protection: Option<bool>,
97 pub max_age_ms: Option<u64>,
98 pub cache_ttl_ms: Option<u64>,
99}
100
101#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
107pub struct AgentCredentials {
108 pub issuing_state: String,
109 pub name: Vec<String>,
110 pub id_number: String,
111 pub nationality: String,
112 pub date_of_birth: String,
113 pub gender: String,
114 pub expiry_date: String,
115 pub older_than: U256,
116 pub ofac: Vec<bool>,
117}
118
119#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
121pub struct VerificationResult {
122 pub valid: bool,
123 pub agent_address: Address,
125 pub agent_key: B256,
127 pub agent_id: U256,
128 pub agent_count: U256,
130 pub nullifier: U256,
132 pub credentials: Option<AgentCredentials>,
134 pub error: Option<String>,
135 pub retry_after_ms: Option<u64>,
137}
138
139impl VerificationResult {
140 fn empty_with_error(error: &str) -> Self {
141 Self {
142 valid: false,
143 agent_address: Address::ZERO,
144 agent_key: B256::ZERO,
145 agent_id: U256::ZERO,
146 agent_count: U256::ZERO,
147 nullifier: U256::ZERO,
148 credentials: None,
149 error: Some(error.to_string()),
150 retry_after_ms: None,
151 }
152 }
153}
154
155struct RateBucket {
160 timestamps: Vec<u64>,
161}
162
163struct RateLimitResult {
164 error: String,
165 retry_after_ms: u64,
166}
167
168struct RateLimiter {
170 per_minute: u32,
171 per_hour: u32,
172 buckets: HashMap<String, RateBucket>,
173}
174
175impl RateLimiter {
176 fn new(config: &RateLimitConfig) -> Self {
177 Self {
178 per_minute: config.per_minute.unwrap_or(0),
179 per_hour: config.per_hour.unwrap_or(0),
180 buckets: HashMap::new(),
181 }
182 }
183
184 fn check(&mut self, agent_address: &str) -> Option<RateLimitResult> {
186 let now = now_millis();
187 let key = agent_address.to_ascii_lowercase();
188 let bucket = self
189 .buckets
190 .entry(key)
191 .or_insert_with(|| RateBucket { timestamps: Vec::new() });
192
193 let one_hour_ago = now.saturating_sub(60 * 60 * 1000);
195 bucket.timestamps.retain(|t| *t > one_hour_ago);
196
197 if self.per_minute > 0 {
199 let one_minute_ago = now.saturating_sub(60 * 1000);
200 let recent_minute: Vec<u64> = bucket
201 .timestamps
202 .iter()
203 .filter(|t| **t > one_minute_ago)
204 .copied()
205 .collect();
206 if recent_minute.len() >= self.per_minute as usize {
207 let oldest = recent_minute[0];
208 let retry_after = (oldest + 60 * 1000).saturating_sub(now).max(1);
209 return Some(RateLimitResult {
210 error: format!("Rate limit exceeded ({}/min)", self.per_minute),
211 retry_after_ms: retry_after,
212 });
213 }
214 }
215
216 if self.per_hour > 0 && bucket.timestamps.len() >= self.per_hour as usize {
218 let oldest = bucket.timestamps[0];
219 let retry_after = (oldest + 60 * 60 * 1000).saturating_sub(now).max(1);
220 return Some(RateLimitResult {
221 error: format!("Rate limit exceeded ({}/hr)", self.per_hour),
222 retry_after_ms: retry_after,
223 });
224 }
225
226 bucket.timestamps.push(now);
228 None
229 }
230}
231
232#[derive(Default)]
251pub struct VerifierBuilder {
252 network: Option<NetworkName>,
253 registry_address: Option<String>,
254 rpc_url: Option<String>,
255 max_age_ms: Option<u64>,
256 cache_ttl_ms: Option<u64>,
257 max_agents_per_human: Option<u64>,
258 include_credentials: Option<bool>,
259 require_self_provider: Option<bool>,
260 enable_replay_protection: Option<bool>,
261 minimum_age: Option<u64>,
262 require_ofac_passed: bool,
263 allowed_nationalities: Option<Vec<String>>,
264 rate_limit_config: Option<RateLimitConfig>,
265}
266
267impl VerifierBuilder {
268 pub fn network(mut self, name: NetworkName) -> Self {
270 self.network = Some(name);
271 self
272 }
273
274 pub fn registry(mut self, addr: &str) -> Self {
276 self.registry_address = Some(addr.to_string());
277 self
278 }
279
280 pub fn rpc(mut self, url: &str) -> Self {
282 self.rpc_url = Some(url.to_string());
283 self
284 }
285
286 pub fn require_age(mut self, n: u64) -> Self {
288 self.minimum_age = Some(n);
289 self
290 }
291
292 pub fn require_ofac(mut self) -> Self {
294 self.require_ofac_passed = true;
295 self
296 }
297
298 pub fn require_nationality(mut self, codes: &[&str]) -> Self {
300 self.allowed_nationalities = Some(codes.iter().map(|s| s.to_string()).collect());
301 self
302 }
303
304 pub fn require_self_provider(mut self) -> Self {
306 self.require_self_provider = Some(true);
307 self
308 }
309
310 pub fn sybil_limit(mut self, n: u64) -> Self {
312 self.max_agents_per_human = Some(n);
313 self
314 }
315
316 pub fn rate_limit(mut self, per_minute: u32, per_hour: u32) -> Self {
318 self.rate_limit_config = Some(RateLimitConfig {
319 per_minute: Some(per_minute),
320 per_hour: Some(per_hour),
321 });
322 self
323 }
324
325 pub fn replay_protection(mut self) -> Self {
327 self.enable_replay_protection = Some(true);
328 self
329 }
330
331 pub fn include_credentials(mut self) -> Self {
333 self.include_credentials = Some(true);
334 self
335 }
336
337 pub fn max_age(mut self, ms: u64) -> Self {
339 self.max_age_ms = Some(ms);
340 self
341 }
342
343 pub fn cache_ttl(mut self, ms: u64) -> Self {
345 self.cache_ttl_ms = Some(ms);
346 self
347 }
348
349 pub fn build(self) -> SelfAgentVerifier {
354 let needs_credentials = self.minimum_age.is_some()
356 || self.require_ofac_passed
357 || self
358 .allowed_nationalities
359 .as_ref()
360 .map_or(false, |v| !v.is_empty());
361
362 let registry_address = self
363 .registry_address
364 .and_then(|s| s.parse::<Address>().ok());
365
366 SelfAgentVerifier::new(VerifierConfig {
367 network: self.network,
368 registry_address,
369 rpc_url: self.rpc_url,
370 max_age_ms: self.max_age_ms,
371 cache_ttl_ms: self.cache_ttl_ms,
372 max_agents_per_human: self.max_agents_per_human,
373 include_credentials: if needs_credentials || self.include_credentials.unwrap_or(false) {
374 Some(true)
375 } else {
376 self.include_credentials
377 },
378 require_self_provider: self.require_self_provider,
379 enable_replay_protection: self.enable_replay_protection,
380 replay_cache_max_entries: None,
381 minimum_age: self.minimum_age,
382 require_ofac_passed: if self.require_ofac_passed {
383 Some(true)
384 } else {
385 None
386 },
387 allowed_nationalities: self.allowed_nationalities,
388 rate_limit_config: self.rate_limit_config,
389 })
390 }
391}
392
393struct CacheEntry {
398 is_verified: bool,
399 is_proof_fresh: bool,
400 agent_id: U256,
401 agent_count: U256,
402 nullifier: U256,
403 provider_address: Address,
404 expires_at: u64,
405}
406
407struct OnChainStatus {
408 is_verified: bool,
409 is_proof_fresh: bool,
410 agent_id: U256,
411 agent_count: U256,
412 nullifier: U256,
413 provider_address: Address,
414}
415
416pub struct SelfAgentVerifier {
457 registry_address: Address,
458 rpc_url: String,
459 max_age_ms: u64,
460 cache_ttl_ms: u64,
461 max_agents_per_human: u64,
462 include_credentials: bool,
463 require_self_provider: bool,
464 enable_replay_protection: bool,
465 replay_cache_max_entries: usize,
466 minimum_age: Option<u64>,
467 require_ofac_passed: bool,
468 allowed_nationalities: Option<Vec<String>>,
469 rate_limiter: Option<RateLimiter>,
470 cache: HashMap<B256, CacheEntry>,
471 replay_cache: HashMap<String, u64>,
472 self_provider_cache: Option<(Address, u64)>,
473}
474
475impl SelfAgentVerifier {
476 pub fn new(config: VerifierConfig) -> Self {
478 let net = network_config(config.network.unwrap_or(DEFAULT_NETWORK));
479 Self {
480 registry_address: config.registry_address.unwrap_or(net.registry_address),
481 rpc_url: config.rpc_url.unwrap_or_else(|| net.rpc_url.to_string()),
482 max_age_ms: config.max_age_ms.unwrap_or(DEFAULT_MAX_AGE_MS),
483 cache_ttl_ms: config.cache_ttl_ms.unwrap_or(DEFAULT_CACHE_TTL_MS),
484 max_agents_per_human: config.max_agents_per_human.unwrap_or(1),
485 include_credentials: config.include_credentials.unwrap_or(false),
486 require_self_provider: config.require_self_provider.unwrap_or(true),
487 enable_replay_protection: config.enable_replay_protection.unwrap_or(true),
488 replay_cache_max_entries: config.replay_cache_max_entries.unwrap_or(10_000),
489 minimum_age: config.minimum_age,
490 require_ofac_passed: config.require_ofac_passed.unwrap_or(false),
491 allowed_nationalities: config.allowed_nationalities,
492 rate_limiter: config.rate_limit_config.as_ref().map(RateLimiter::new),
493 cache: HashMap::new(),
494 replay_cache: HashMap::new(),
495 self_provider_cache: None,
496 }
497 }
498
499 pub fn create() -> VerifierBuilder {
501 VerifierBuilder::default()
502 }
503
504 pub fn from_config(cfg: VerifierFromConfig) -> Self {
509 let needs_credentials = cfg.require_age.is_some()
510 || cfg.require_ofac.unwrap_or(false)
511 || cfg
512 .require_nationality
513 .as_ref()
514 .map_or(false, |v| !v.is_empty());
515
516 let registry_address = cfg
517 .registry_address
518 .and_then(|s| s.parse::<Address>().ok());
519
520 Self::new(VerifierConfig {
521 network: cfg.network,
522 registry_address,
523 rpc_url: cfg.rpc_url,
524 max_age_ms: cfg.max_age_ms,
525 cache_ttl_ms: cfg.cache_ttl_ms,
526 max_agents_per_human: cfg.sybil_limit,
527 include_credentials: if needs_credentials { Some(true) } else { None },
528 require_self_provider: cfg.require_self_provider,
529 enable_replay_protection: cfg.replay_protection,
530 replay_cache_max_entries: None,
531 minimum_age: cfg.require_age,
532 require_ofac_passed: cfg.require_ofac,
533 allowed_nationalities: cfg.require_nationality,
534 rate_limit_config: cfg.rate_limit,
535 })
536 }
537
538 fn make_provider(
539 &self,
540 ) -> Result<impl alloy::providers::Provider + Clone, crate::Error> {
541 let url: reqwest::Url = self
542 .rpc_url
543 .parse()
544 .map_err(|_| crate::Error::InvalidRpcUrl)?;
545 Ok(ProviderBuilder::new().connect_http(url))
546 }
547
548 pub async fn verify(
552 &mut self,
553 signature: &str,
554 timestamp: &str,
555 method: &str,
556 url: &str,
557 body: Option<&str>,
558 ) -> VerificationResult {
559 let ts: u64 = match timestamp.parse() {
561 Ok(v) => v,
562 Err(_) => return VerificationResult::empty_with_error("Timestamp expired or invalid"),
563 };
564 let now = now_millis();
565 let diff = if now > ts { now - ts } else { ts - now };
566 if diff > self.max_age_ms {
567 return VerificationResult::empty_with_error("Timestamp expired or invalid");
568 }
569
570 let message = compute_signing_message(timestamp, method, url, body);
572 let message_key = format!("{:#x}", message);
573
574 let signer_address = match recover_address(&message, signature) {
576 Ok(addr) => addr,
577 Err(_) => return VerificationResult::empty_with_error("Invalid signature"),
578 };
579
580 if self.enable_replay_protection {
582 if let Some(err) = self.check_and_record_replay(signature, &message_key, ts, now) {
583 return VerificationResult {
584 valid: false,
585 agent_address: signer_address,
586 agent_key: address_to_agent_key(signer_address),
587 agent_id: U256::ZERO,
588 agent_count: U256::ZERO,
589 nullifier: U256::ZERO,
590 credentials: None,
591 error: Some(err),
592 retry_after_ms: None,
593 };
594 }
595 }
596
597 let agent_key = address_to_agent_key(signer_address);
599
600 let on_chain = match self.check_on_chain(agent_key).await {
602 Ok(v) => v,
603 Err(e) => {
604 return VerificationResult {
605 valid: false,
606 agent_address: signer_address,
607 agent_key,
608 agent_id: U256::ZERO,
609 agent_count: U256::ZERO,
610 nullifier: U256::ZERO,
611 credentials: None,
612 error: Some(format!("RPC error: {}", e)),
613 retry_after_ms: None,
614 };
615 }
616 };
617
618 if !on_chain.is_verified {
619 return VerificationResult {
620 valid: false,
621 agent_address: signer_address,
622 agent_key,
623 agent_id: on_chain.agent_id,
624 agent_count: on_chain.agent_count,
625 nullifier: on_chain.nullifier,
626 credentials: None,
627 error: Some("Agent not verified on-chain".to_string()),
628 retry_after_ms: None,
629 };
630 }
631
632 if !on_chain.is_proof_fresh {
634 return VerificationResult {
635 valid: false,
636 agent_address: signer_address,
637 agent_key,
638 agent_id: on_chain.agent_id,
639 agent_count: on_chain.agent_count,
640 nullifier: on_chain.nullifier,
641 credentials: None,
642 error: Some("Agent's human proof has expired".to_string()),
643 retry_after_ms: None,
644 };
645 }
646
647 if self.require_self_provider && on_chain.agent_id > U256::ZERO {
649 let self_provider = match self.get_self_provider_address().await {
650 Ok(addr) => addr,
651 Err(_) => {
652 return VerificationResult {
653 valid: false,
654 agent_address: signer_address,
655 agent_key,
656 agent_id: on_chain.agent_id,
657 agent_count: on_chain.agent_count,
658 nullifier: on_chain.nullifier,
659 credentials: None,
660 error: Some(
661 "Unable to verify proof provider — RPC error".to_string(),
662 ),
663 retry_after_ms: None,
664 };
665 }
666 };
667 if on_chain.provider_address != self_provider {
668 return VerificationResult {
669 valid: false,
670 agent_address: signer_address,
671 agent_key,
672 agent_id: on_chain.agent_id,
673 agent_count: on_chain.agent_count,
674 nullifier: on_chain.nullifier,
675 credentials: None,
676 error: Some(
677 "Agent was not verified by Self — proof provider mismatch".to_string(),
678 ),
679 retry_after_ms: None,
680 };
681 }
682 }
683
684 if self.max_agents_per_human > 0
686 && on_chain.agent_count > U256::from(self.max_agents_per_human)
687 {
688 return VerificationResult {
689 valid: false,
690 agent_address: signer_address,
691 agent_key,
692 agent_id: on_chain.agent_id,
693 agent_count: on_chain.agent_count,
694 nullifier: on_chain.nullifier,
695 credentials: None,
696 error: Some(format!(
697 "Human has {} agents (max {})",
698 on_chain.agent_count, self.max_agents_per_human
699 )),
700 retry_after_ms: None,
701 };
702 }
703
704 let credentials = if self.include_credentials && on_chain.agent_id > U256::ZERO {
706 self.fetch_credentials(on_chain.agent_id).await.ok()
707 } else {
708 None
709 };
710
711 if let Some(ref creds) = credentials {
713 if let Some(min_age) = self.minimum_age {
714 if creds.older_than < U256::from(min_age) {
715 return VerificationResult {
716 valid: false,
717 agent_address: signer_address,
718 agent_key,
719 agent_id: on_chain.agent_id,
720 agent_count: on_chain.agent_count,
721 nullifier: on_chain.nullifier,
722 credentials: credentials.clone(),
723 error: Some(format!(
724 "Agent's human does not meet minimum age (required: {}, got: {})",
725 min_age, creds.older_than
726 )),
727 retry_after_ms: None,
728 };
729 }
730 }
731
732 if self.require_ofac_passed && !creds.ofac.first().copied().unwrap_or(false) {
733 return VerificationResult {
734 valid: false,
735 agent_address: signer_address,
736 agent_key,
737 agent_id: on_chain.agent_id,
738 agent_count: on_chain.agent_count,
739 nullifier: on_chain.nullifier,
740 credentials: credentials.clone(),
741 error: Some("Agent's human did not pass OFAC screening".to_string()),
742 retry_after_ms: None,
743 };
744 }
745
746 if let Some(ref allowed) = self.allowed_nationalities {
747 if !allowed.is_empty() && !allowed.contains(&creds.nationality) {
748 return VerificationResult {
749 valid: false,
750 agent_address: signer_address,
751 agent_key,
752 agent_id: on_chain.agent_id,
753 agent_count: on_chain.agent_count,
754 nullifier: on_chain.nullifier,
755 credentials: credentials.clone(),
756 error: Some(format!(
757 "Nationality \"{}\" not in allowed list",
758 creds.nationality
759 )),
760 retry_after_ms: None,
761 };
762 }
763 }
764 }
765
766 if let Some(ref mut limiter) = self.rate_limiter {
768 let addr_str = format!("{:#x}", signer_address);
769 if let Some(limited) = limiter.check(&addr_str) {
770 return VerificationResult {
771 valid: false,
772 agent_address: signer_address,
773 agent_key,
774 agent_id: on_chain.agent_id,
775 agent_count: on_chain.agent_count,
776 nullifier: on_chain.nullifier,
777 credentials,
778 error: Some(limited.error),
779 retry_after_ms: Some(limited.retry_after_ms),
780 };
781 }
782 }
783
784 VerificationResult {
785 valid: true,
786 agent_address: signer_address,
787 agent_key,
788 agent_id: on_chain.agent_id,
789 agent_count: on_chain.agent_count,
790 nullifier: on_chain.nullifier,
791 credentials,
792 error: None,
793 retry_after_ms: None,
794 }
795 }
796
797 async fn check_on_chain(&mut self, agent_key: B256) -> Result<OnChainStatus, crate::Error> {
799 let now = now_millis();
800 if let Some(cached) = self.cache.get(&agent_key) {
801 if cached.expires_at > now {
802 return Ok(OnChainStatus {
803 is_verified: cached.is_verified,
804 is_proof_fresh: cached.is_proof_fresh,
805 agent_id: cached.agent_id,
806 agent_count: cached.agent_count,
807 nullifier: cached.nullifier,
808 provider_address: cached.provider_address,
809 });
810 }
811 }
812
813 let provider = self.make_provider()?;
814 let registry = IAgentRegistry::new(self.registry_address, &provider);
815
816 let is_verified = registry
817 .isVerifiedAgent(agent_key)
818 .call()
819 .await
820 .map_err(|e| crate::Error::RpcError(e.to_string()))?;
821 let agent_id = registry
822 .getAgentId(agent_key)
823 .call()
824 .await
825 .map_err(|e| crate::Error::RpcError(e.to_string()))?;
826
827 let mut agent_count = U256::ZERO;
828 let mut nullifier = U256::ZERO;
829 let mut provider_address = Address::ZERO;
830 let mut is_proof_fresh = false;
831
832 if agent_id > U256::ZERO {
833 is_proof_fresh = registry
834 .isProofFresh(agent_id)
835 .call()
836 .await
837 .map_err(|e| crate::Error::RpcError(e.to_string()))?;
838
839 if self.max_agents_per_human > 0 {
840 nullifier = registry
841 .getHumanNullifier(agent_id)
842 .call()
843 .await
844 .map_err(|e| crate::Error::RpcError(e.to_string()))?;
845 agent_count = registry
846 .getAgentCountForHuman(nullifier)
847 .call()
848 .await
849 .map_err(|e| crate::Error::RpcError(e.to_string()))?;
850 }
851
852 if self.require_self_provider {
853 provider_address = registry
854 .getProofProvider(agent_id)
855 .call()
856 .await
857 .map_err(|e| crate::Error::RpcError(e.to_string()))?;
858 }
859 }
860
861 self.cache.insert(
862 agent_key,
863 CacheEntry {
864 is_verified,
865 is_proof_fresh,
866 agent_id,
867 agent_count,
868 nullifier,
869 provider_address,
870 expires_at: now + self.cache_ttl_ms,
871 },
872 );
873
874 Ok(OnChainStatus {
875 is_verified,
876 is_proof_fresh,
877 agent_id,
878 agent_count,
879 nullifier,
880 provider_address,
881 })
882 }
883
884 async fn get_self_provider_address(&mut self) -> Result<Address, crate::Error> {
886 let now = now_millis();
887 if let Some((addr, expires_at)) = self.self_provider_cache {
888 if expires_at > now {
889 return Ok(addr);
890 }
891 }
892
893 let provider = self.make_provider()?;
894 let registry = IAgentRegistry::new(self.registry_address, &provider);
895
896 let address = registry
897 .selfProofProvider()
898 .call()
899 .await
900 .map_err(|e| crate::Error::RpcError(e.to_string()))?;
901
902 self.self_provider_cache = Some((address, now + self.cache_ttl_ms * 12));
904
905 Ok(address)
906 }
907
908 async fn fetch_credentials(&self, agent_id: U256) -> Result<AgentCredentials, crate::Error> {
910 let provider = self.make_provider()?;
911 let registry = IAgentRegistry::new(self.registry_address, &provider);
912
913 let raw = registry
914 .getAgentCredentials(agent_id)
915 .call()
916 .await
917 .map_err(|e| crate::Error::RpcError(e.to_string()))?;
918
919 Ok(AgentCredentials {
920 issuing_state: raw.issuingState,
921 name: raw.name,
922 id_number: raw.idNumber,
923 nationality: raw.nationality,
924 date_of_birth: raw.dateOfBirth,
925 gender: raw.gender,
926 expiry_date: raw.expiryDate,
927 older_than: raw.olderThan,
928 ofac: raw.ofac.to_vec(),
929 })
930 }
931
932 pub fn clear_cache(&mut self) {
934 self.cache.clear();
935 self.replay_cache.clear();
936 self.self_provider_cache = None;
937 }
938
939 fn check_and_record_replay(
940 &mut self,
941 signature: &str,
942 message: &str,
943 ts: u64,
944 now: u64,
945 ) -> Option<String> {
946 self.prune_replay_cache(now);
947
948 let key = format!(
949 "{}:{}",
950 signature.to_ascii_lowercase(),
951 message.to_ascii_lowercase()
952 );
953 if let Some(expires_at) = self.replay_cache.get(&key) {
954 if *expires_at > now {
955 return Some("Replay detected".to_string());
956 }
957 }
958
959 self.replay_cache.insert(key, ts.saturating_add(self.max_age_ms));
960 None
961 }
962
963 fn prune_replay_cache(&mut self, now: u64) {
964 self.replay_cache.retain(|_, exp| *exp > now);
965
966 if self.replay_cache.len() <= self.replay_cache_max_entries {
967 return;
968 }
969
970 let overflow = self.replay_cache.len() - self.replay_cache_max_entries;
971 let mut items: Vec<(String, u64)> =
972 self.replay_cache.iter().map(|(k, v)| (k.clone(), *v)).collect();
973 items.sort_by_key(|(_, exp)| *exp);
974
975 for (key, _) in items.into_iter().take(overflow) {
976 self.replay_cache.remove(&key);
977 }
978 }
979}
980
981fn recover_address(message: &B256, signature_hex: &str) -> Result<Address, crate::Error> {
985 let sig_bytes = hex::decode(signature_hex.strip_prefix("0x").unwrap_or(signature_hex))
986 .map_err(|_| crate::Error::InvalidSignature)?;
987
988 let signature = Signature::try_from(sig_bytes.as_slice())
989 .map_err(|_| crate::Error::InvalidSignature)?;
990
991 let prefixed = alloy::primitives::eip191_hash_message(message.as_slice());
993
994 let recovered = signature
995 .recover_address_from_prehash(&prefixed)
996 .map_err(|_| crate::Error::InvalidSignature)?;
997
998 Ok(recovered)
999}
1000
1001fn now_millis() -> u64 {
1002 SystemTime::now()
1003 .duration_since(UNIX_EPOCH)
1004 .expect("system clock before UNIX epoch")
1005 .as_millis() as u64
1006}
1007
1008#[cfg(test)]
1013mod tests {
1014 use super::*;
1015
1016 #[test]
1017 fn create_build_default() {
1018 let v = SelfAgentVerifier::create().build();
1019 assert_eq!(v.max_agents_per_human, 1);
1021 assert!(v.require_self_provider);
1022 assert!(v.enable_replay_protection);
1023 assert!(!v.include_credentials);
1024 assert!(v.minimum_age.is_none());
1025 assert!(!v.require_ofac_passed);
1026 assert!(v.allowed_nationalities.is_none());
1027 assert!(v.rate_limiter.is_none());
1028 }
1029
1030 #[test]
1031 fn create_build_testnet() {
1032 let v = SelfAgentVerifier::create()
1033 .network(NetworkName::Testnet)
1034 .build();
1035 let expected = network_config(NetworkName::Testnet);
1036 assert_eq!(v.registry_address, expected.registry_address);
1037 assert_eq!(v.rpc_url, expected.rpc_url);
1038 }
1039
1040 #[test]
1041 fn chain_credentials() {
1042 let v = SelfAgentVerifier::create()
1043 .network(NetworkName::Testnet)
1044 .require_age(18)
1045 .require_ofac()
1046 .require_nationality(&["US", "GB"])
1047 .build();
1048
1049 assert!(v.include_credentials);
1051 assert_eq!(v.minimum_age, Some(18));
1052 assert!(v.require_ofac_passed);
1053 assert_eq!(
1054 v.allowed_nationalities.as_deref(),
1055 Some(vec!["US".to_string(), "GB".to_string()].as_slice())
1056 );
1057 }
1058
1059 #[test]
1060 fn auto_enable_credentials_age_only() {
1061 let v = SelfAgentVerifier::create()
1062 .require_age(21)
1063 .build();
1064 assert!(v.include_credentials);
1065 assert_eq!(v.minimum_age, Some(21));
1066 }
1067
1068 #[test]
1069 fn auto_enable_credentials_ofac_only() {
1070 let v = SelfAgentVerifier::create()
1071 .require_ofac()
1072 .build();
1073 assert!(v.include_credentials);
1074 assert!(v.require_ofac_passed);
1075 }
1076
1077 #[test]
1078 fn auto_enable_credentials_nationality_only() {
1079 let v = SelfAgentVerifier::create()
1080 .require_nationality(&["DE"])
1081 .build();
1082 assert!(v.include_credentials);
1083 }
1084
1085 #[test]
1086 fn no_auto_credentials_without_requirements() {
1087 let v = SelfAgentVerifier::create()
1088 .network(NetworkName::Testnet)
1089 .sybil_limit(3)
1090 .build();
1091 assert!(!v.include_credentials);
1092 }
1093
1094 #[test]
1095 fn explicit_include_credentials() {
1096 let v = SelfAgentVerifier::create()
1097 .include_credentials()
1098 .build();
1099 assert!(v.include_credentials);
1100 }
1101
1102 #[test]
1103 fn from_config_works() {
1104 let v = SelfAgentVerifier::from_config(VerifierFromConfig {
1105 network: Some(NetworkName::Testnet),
1106 require_age: Some(18),
1107 require_ofac: Some(true),
1108 sybil_limit: Some(1),
1109 ..Default::default()
1110 });
1111 assert!(v.include_credentials);
1112 assert_eq!(v.minimum_age, Some(18));
1113 assert!(v.require_ofac_passed);
1114 assert_eq!(v.max_agents_per_human, 1);
1115 }
1116
1117 #[test]
1118 fn from_config_auto_credentials_disabled() {
1119 let v = SelfAgentVerifier::from_config(VerifierFromConfig {
1120 network: Some(NetworkName::Testnet),
1121 sybil_limit: Some(5),
1122 ..Default::default()
1123 });
1124 assert!(!v.include_credentials);
1125 }
1126
1127 #[test]
1128 fn from_config_nationality() {
1129 let v = SelfAgentVerifier::from_config(VerifierFromConfig {
1130 require_nationality: Some(vec!["FR".to_string(), "IT".to_string()]),
1131 ..Default::default()
1132 });
1133 assert!(v.include_credentials);
1134 assert_eq!(
1135 v.allowed_nationalities.as_deref(),
1136 Some(vec!["FR".to_string(), "IT".to_string()].as_slice())
1137 );
1138 }
1139
1140 #[test]
1141 fn rate_limit_builder() {
1142 let v = SelfAgentVerifier::create()
1143 .rate_limit(10, 100)
1144 .build();
1145 assert!(v.rate_limiter.is_some());
1146 let limiter = v.rate_limiter.as_ref().unwrap();
1147 assert_eq!(limiter.per_minute, 10);
1148 assert_eq!(limiter.per_hour, 100);
1149 }
1150
1151 #[test]
1152 fn rate_limit_from_config() {
1153 let v = SelfAgentVerifier::from_config(VerifierFromConfig {
1154 rate_limit: Some(RateLimitConfig {
1155 per_minute: Some(5),
1156 per_hour: Some(50),
1157 }),
1158 ..Default::default()
1159 });
1160 assert!(v.rate_limiter.is_some());
1161 }
1162
1163 #[test]
1164 fn rate_limiter_allows_within_limit() {
1165 let config = RateLimitConfig {
1166 per_minute: Some(3),
1167 per_hour: None,
1168 };
1169 let mut limiter = RateLimiter::new(&config);
1170 assert!(limiter.check("0xabc").is_none());
1171 assert!(limiter.check("0xabc").is_none());
1172 assert!(limiter.check("0xabc").is_none());
1173 let result = limiter.check("0xabc");
1175 assert!(result.is_some());
1176 let r = result.unwrap();
1177 assert!(r.error.contains("3/min"));
1178 assert!(r.retry_after_ms > 0);
1179 }
1180
1181 #[test]
1182 fn rate_limiter_separate_agents() {
1183 let config = RateLimitConfig {
1184 per_minute: Some(1),
1185 per_hour: None,
1186 };
1187 let mut limiter = RateLimiter::new(&config);
1188 assert!(limiter.check("0xabc").is_none());
1189 assert!(limiter.check("0xdef").is_none());
1190 assert!(limiter.check("0xabc").is_some());
1192 assert!(limiter.check("0xghi").is_none());
1194 }
1195
1196 #[test]
1197 fn builder_custom_max_age_and_cache_ttl() {
1198 let v = SelfAgentVerifier::create()
1199 .max_age(10_000)
1200 .cache_ttl(30_000)
1201 .build();
1202 assert_eq!(v.max_age_ms, 10_000);
1203 assert_eq!(v.cache_ttl_ms, 30_000);
1204 }
1205
1206 #[test]
1207 fn builder_sybil_limit_zero_disables() {
1208 let v = SelfAgentVerifier::create()
1209 .sybil_limit(0)
1210 .build();
1211 assert_eq!(v.max_agents_per_human, 0);
1212 }
1213
1214 #[test]
1215 fn builder_replay_protection() {
1216 let v = SelfAgentVerifier::create()
1217 .replay_protection()
1218 .build();
1219 assert!(v.enable_replay_protection);
1220 }
1221
1222 #[test]
1223 fn builder_require_self_provider() {
1224 let v = SelfAgentVerifier::create()
1225 .require_self_provider()
1226 .build();
1227 assert!(v.require_self_provider);
1228 }
1229
1230 #[test]
1231 fn new_constructor_still_works() {
1232 let v = SelfAgentVerifier::new(VerifierConfig::default());
1233 assert_eq!(v.max_age_ms, DEFAULT_MAX_AGE_MS);
1234 assert_eq!(v.cache_ttl_ms, DEFAULT_CACHE_TTL_MS);
1235 assert_eq!(v.max_agents_per_human, 1);
1236 assert!(v.require_self_provider);
1237 }
1238
1239 #[test]
1240 fn new_constructor_with_credentials() {
1241 let v = SelfAgentVerifier::new(VerifierConfig {
1242 minimum_age: Some(21),
1243 require_ofac_passed: Some(true),
1244 include_credentials: Some(true),
1245 ..Default::default()
1246 });
1247 assert!(v.include_credentials);
1248 assert_eq!(v.minimum_age, Some(21));
1249 assert!(v.require_ofac_passed);
1250 }
1251
1252 #[test]
1253 fn verification_result_has_retry_after() {
1254 let r = VerificationResult::empty_with_error("test");
1255 assert!(r.retry_after_ms.is_none());
1256 }
1257}