bitrouter_attestation/
lib.rs1#![forbid(unsafe_code)]
20
21use std::collections::HashMap;
22use std::sync::Arc;
23
24use async_trait::async_trait;
25
26mod cache;
27mod near;
28mod transport;
29mod types;
30
31pub use cache::AttestationCache;
32pub use near::binding::{compose_matches_mr_config, report_data_binds};
33pub use near::dcap::{AciDcapVerifierPolicy, ModelIdentity, PolicyError, model_identity};
34pub use near::eventlog::{event_log_binds_info, replay_rtmr};
35pub use near::nvidia::{
36 NRAS_GPU_URL, NVIDIA_NRAS_JWKS_URL, NrasVerdict, NvidiaEatKey, check_nras_eat, post_nras,
37};
38pub use near::report::{
39 AttestationInfo, AttestationReport, DstackEvent, ModelAttestation, TcbInfo,
40};
41pub use near::signature::{ChatSignature, chat_signing_text, recover_eip191_address, sha256_hex};
42pub use near::tdx::{
43 DcapQuoteVerifier, PHALA_PCCS_URL, QuoteVerifier, TdxMeasurements, parse_tdx_quote,
44 verify_tdx_quote,
45};
46pub use near::{DEFAULT_CACHE_TTL_SECONDS, NearVerifier, TRUST_BOUNDARY};
47pub use transport::{MockTransport, ReportTransport, ReqwestTransport, SIGNING_ALGO};
48pub use types::{
49 AttestationChecks, AttestationVerdict, ExchangeInput, IntegrityProof, VerifiedExchange,
50 VerifyError,
51};
52
53#[async_trait]
60pub trait ConfidentialVerifier: Send + Sync {
61 fn provider(&self) -> &str;
63
64 async fn verify_attestation(
67 &self,
68 model: &str,
69 nonce: &str,
70 now_unix: u64,
71 ) -> Result<AttestationVerdict, VerifyError>;
72
73 async fn verify_exchange(
75 &self,
76 ex: &ExchangeInput<'_>,
77 ) -> Result<VerifiedExchange, VerifyError>;
78
79 async fn attestation_cached(
85 &self,
86 model: &str,
87 now_unix: u64,
88 ) -> Result<AttestationVerdict, VerifyError> {
89 self.verify_attestation(model, &fresh_nonce_hex(), now_unix)
90 .await
91 }
92}
93
94pub(crate) fn fresh_nonce_hex() -> String {
96 use rand::RngCore;
97 let mut nonce = [0u8; 32];
98 rand::rng().fill_bytes(&mut nonce);
99 hex::encode(nonce)
100}
101
102#[derive(Default, Clone)]
107pub struct VerifierRegistry {
108 map: HashMap<String, Arc<dyn ConfidentialVerifier>>,
109}
110
111impl VerifierRegistry {
112 pub fn new() -> Self {
113 Self::default()
114 }
115
116 pub fn with(mut self, verifier: Arc<dyn ConfidentialVerifier>) -> Self {
119 self.map.insert(verifier.provider().to_string(), verifier);
120 self
121 }
122
123 pub fn get(&self, provider: &str) -> Result<&Arc<dyn ConfidentialVerifier>, VerifyError> {
126 self.map
127 .get(provider)
128 .ok_or_else(|| VerifyError::UnknownProvider(provider.to_string()))
129 }
130
131 pub fn handles(&self, provider: &str) -> bool {
133 self.map.contains_key(provider)
134 }
135}
136
137#[cfg(test)]
138mod tests {
139 use super::*;
140
141 struct StubVerifier;
142
143 #[async_trait]
144 impl ConfidentialVerifier for StubVerifier {
145 fn provider(&self) -> &str {
146 "near-ai"
147 }
148 async fn verify_attestation(
149 &self,
150 model: &str,
151 nonce: &str,
152 now_unix: u64,
153 ) -> Result<AttestationVerdict, VerifyError> {
154 Ok(AttestationVerdict::unverified(model, nonce, now_unix))
155 }
156 async fn verify_exchange(
157 &self,
158 _ex: &ExchangeInput<'_>,
159 ) -> Result<VerifiedExchange, VerifyError> {
160 Err(VerifyError::Malformed {
161 what: "exchange",
162 detail: "not implemented in P1".to_string(),
163 })
164 }
165 }
166
167 #[test]
168 fn registry_dispatches_by_provider_and_fails_closed_on_unknown() {
169 let reg = VerifierRegistry::new().with(Arc::new(StubVerifier));
170 assert!(reg.handles("near-ai"));
171 assert!(reg.get("near-ai").is_ok());
172 assert!(!reg.handles("tinfoil"));
173 match reg.get("tinfoil").err() {
174 Some(VerifyError::UnknownProvider(p)) => assert_eq!(p, "tinfoil"),
175 _ => panic!("expected UnknownProvider for an unregistered provider"),
176 }
177 }
178
179 #[test]
180 fn unverified_verdict_is_fail_closed() {
181 let model = "zai-org/GLM-5.1-FP8";
182 let v = AttestationVerdict::unverified(model, format!("test-nonce-{model}"), 42);
185 assert!(!v.verified);
186 assert!(!v.checks.all_pass());
187 assert!(v.attested_addresses.is_empty());
188 }
189}