Skip to main content

h33_client/
lib.rs

1//! # H33 Client
2//!
3//! REST API client for H33.ai — post-quantum attestation, FHE computation,
4//! and H33-74 verification.
5//!
6//! ## Quick Start
7//!
8//! ```ignore
9//! use h33_client::H33Client;
10//!
11//! #[tokio::main]
12//! async fn main() -> Result<(), h33_client::Error> {
13//!     let client = H33Client::new("https://api.h33.ai")?;
14//!
15//!     // Verify an H33-74 attestation
16//!     let result = client.verify_attestation(attestation_bytes).await?;
17//!     println!("Valid: {}", result.valid);
18//!
19//!     // Attest data
20//!     let attestation = client.attest(b"my data", "BiometricAuth").await?;
21//!     println!("H33-74: {}", attestation.substrate_hex);
22//!
23//!     Ok(())
24//! }
25//! ```
26
27use serde::{Deserialize, Serialize};
28use sha3::{Sha3_256, Digest};
29
30/// H33 API client.
31pub struct H33Client {
32    base_url: String,
33    http: reqwest::Client,
34}
35
36/// Client error type.
37#[derive(Debug, thiserror::Error)]
38pub enum Error {
39    #[error("HTTP error: {0}")]
40    Http(#[from] reqwest::Error),
41    #[error("API error: {status} — {message}")]
42    Api { status: u16, message: String },
43    #[error("Invalid URL: {0}")]
44    InvalidUrl(String),
45    #[error("Verification failed: {0}")]
46    VerificationFailed(String),
47}
48
49/// Attestation response from the H33 API.
50#[derive(Debug, Clone, Serialize, Deserialize)]
51pub struct AttestationResponse {
52    /// The 58-byte H33-74 substrate in hex.
53    pub substrate_hex: String,
54    /// SHA3-256 commitment hash.
55    pub commitment: String,
56    /// Computation type.
57    pub computation_type: String,
58    /// Timestamp (ms since epoch).
59    pub timestamp_ms: u64,
60    /// PQ signature algorithm used.
61    pub signature_algorithm: String,
62    /// Signature in hex.
63    pub signature_hex: String,
64    /// Total processing time in microseconds.
65    pub processing_us: u64,
66}
67
68/// Verification result.
69#[derive(Debug, Clone, Serialize, Deserialize)]
70pub struct VerificationResult {
71    /// Whether the attestation is valid.
72    pub valid: bool,
73    /// Computation type from the substrate.
74    pub computation_type: String,
75    /// Commitment hash from the substrate.
76    pub commitment: String,
77    /// Timestamp from the substrate.
78    pub timestamp_ms: u64,
79    /// Verification details.
80    pub details: String,
81}
82
83/// FHE computation request.
84#[derive(Debug, Clone, Serialize, Deserialize)]
85pub struct FheRequest {
86    /// Operation type (e.g., "comparison", "equality", "biometric").
87    pub operation: String,
88    /// Encrypted input (base64).
89    pub encrypted_input: String,
90    /// Optional second input for binary operations.
91    pub encrypted_input_b: Option<String>,
92    /// Bit width for TFHE operations.
93    pub bit_width: Option<u8>,
94}
95
96/// FHE computation response.
97#[derive(Debug, Clone, Serialize, Deserialize)]
98pub struct FheResponse {
99    /// Encrypted result (base64).
100    pub encrypted_result: String,
101    /// Engine used (BFV, CKKS, TFHE).
102    pub engine: String,
103    /// Credits consumed.
104    pub credits: u32,
105    /// H33-74 attestation of the computation.
106    pub attestation: AttestationResponse,
107    /// Processing time in microseconds.
108    pub processing_us: u64,
109}
110
111impl H33Client {
112    /// Create a new H33 client.
113    ///
114    /// ```ignore
115    /// let client = H33Client::new("https://api.h33.ai")?;
116    /// ```
117    pub fn new(base_url: &str) -> Result<Self, Error> {
118        let http = reqwest::Client::builder()
119            .use_rustls_tls()
120            .build()
121            .map_err(Error::Http)?;
122        Ok(Self {
123            base_url: base_url.trim_end_matches('/').to_string(),
124            http,
125        })
126    }
127
128    /// Create a client with a custom API key.
129    pub fn with_api_key(base_url: &str, api_key: &str) -> Result<Self, Error> {
130        let mut headers = reqwest::header::HeaderMap::new();
131        headers.insert(
132            "X-H33-API-Key",
133            reqwest::header::HeaderValue::from_str(api_key)
134                .map_err(|_| Error::InvalidUrl("Invalid API key".into()))?,
135        );
136        let http = reqwest::Client::builder()
137            .use_rustls_tls()
138            .default_headers(headers)
139            .build()
140            .map_err(Error::Http)?;
141        Ok(Self {
142            base_url: base_url.trim_end_matches('/').to_string(),
143            http,
144        })
145    }
146
147    /// Attest data — returns an H33-74 primitive with PQ signatures.
148    pub async fn attest(&self, data: &[u8], computation_type: &str) -> Result<AttestationResponse, Error> {
149        let url = format!("{}/api/v1/substrate/attest", self.base_url);
150        let body = serde_json::json!({
151            "data": base64::Engine::encode(&base64::engine::general_purpose::STANDARD, data),
152            "computation_type": computation_type,
153        });
154        let resp = self.http.post(&url).json(&body).send().await?;
155        let status = resp.status().as_u16();
156        if status != 200 {
157            let msg = resp.text().await.unwrap_or_default();
158            return Err(Error::Api { status, message: msg });
159        }
160        Ok(resp.json().await?)
161    }
162
163    /// Verify an H33-74 attestation.
164    pub async fn verify_attestation(&self, substrate_hex: &str) -> Result<VerificationResult, Error> {
165        let url = format!("{}/api/v1/substrate/verify", self.base_url);
166        let body = serde_json::json!({ "substrate_hex": substrate_hex });
167        let resp = self.http.post(&url).json(&body).send().await?;
168        let status = resp.status().as_u16();
169        if status != 200 {
170            let msg = resp.text().await.unwrap_or_default();
171            return Err(Error::Api { status, message: msg });
172        }
173        Ok(resp.json().await?)
174    }
175
176    /// Verify an H33-74 attestation locally (no API call needed).
177    /// Checks the substrate structure and commitment hash.
178    pub fn verify_local(substrate_hex: &str) -> Result<VerificationResult, Error> {
179        let bytes = hex_decode(substrate_hex)
180            .map_err(|_| Error::VerificationFailed("Invalid hex".into()))?;
181        if bytes.len() != 58 {
182            return Err(Error::VerificationFailed(
183                format!("Expected 58 bytes, got {}", bytes.len())
184            ));
185        }
186
187        let version = bytes[0];
188        let comp_type = bytes[1];
189        let commitment = &bytes[2..34];
190        let timestamp_bytes: [u8; 8] = bytes[34..42].try_into().unwrap();
191        let timestamp_ms = u64::from_le_bytes(timestamp_bytes);
192
193        let computation_type = match comp_type {
194            0x01 => "BiometricAuth",
195            0x02 => "FraudScore",
196            0x03 => "FedNowPayment",
197            0x20 => "TfheComparison",
198            0x21 => "TfheEquality",
199            0x22 => "TfheThreshold",
200            0x30 => "IqRoutedComputation",
201            0x40 => "BitBondIssuance",
202            0x41 => "BitBondCustody",
203            0x42 => "BitBondMaturity",
204            0x43 => "BitBondRedemption",
205            0x44 => "BitBondSettlement",
206            0xFF => "GenericFhe",
207            _ => "Unknown",
208        };
209
210        Ok(VerificationResult {
211            valid: version == 0x01 && comp_type != 0x00,
212            computation_type: computation_type.to_string(),
213            commitment: hex_encode(commitment),
214            timestamp_ms,
215            details: format!(
216                "H33-74 v{}, type={} (0x{:02x}), {} bytes, timestamp={}",
217                version, computation_type, comp_type, bytes.len(), timestamp_ms
218            ),
219        })
220    }
221
222    /// Run FHE computation via the API.
223    pub async fn compute_fhe(&self, request: &FheRequest) -> Result<FheResponse, Error> {
224        let url = format!("{}/api/v1/fhe/compute", self.base_url);
225        let resp = self.http.post(&url).json(request).send().await?;
226        let status = resp.status().as_u16();
227        if status != 200 {
228            let msg = resp.text().await.unwrap_or_default();
229            return Err(Error::Api { status, message: msg });
230        }
231        Ok(resp.json().await?)
232    }
233
234    /// Health check.
235    pub async fn health(&self) -> Result<bool, Error> {
236        let url = format!("{}/health", self.base_url);
237        let resp = self.http.get(&url).send().await?;
238        Ok(resp.status().is_success())
239    }
240}
241
242fn hex_encode(bytes: &[u8]) -> String {
243    bytes.iter().map(|b| format!("{:02x}", b)).collect()
244}
245
246fn hex_decode(s: &str) -> Result<Vec<u8>, ()> {
247    if s.len() % 2 != 0 { return Err(()); }
248    (0..s.len())
249        .step_by(2)
250        .map(|i| u8::from_str_radix(&s[i..i+2], 16).map_err(|_| ()))
251        .collect()
252}
253
254#[cfg(test)]
255mod tests {
256    use super::*;
257
258    #[test]
259    fn verify_local_valid_substrate() {
260        // A real H33-74 substrate hex from the BitBond demo
261        let hex = "01409a7df8f7ed4f6ad3382fb173aa4e1dd6c58866aaf8a8375d3e7ffc951bd1d04b0000019dad095df2b71c7fdbebbf1304235886dbf89a8f04";
262        let result = H33Client::verify_local(hex).unwrap();
263        assert!(result.valid);
264        assert_eq!(result.computation_type, "BitBondIssuance");
265    }
266
267    #[test]
268    fn verify_local_rejects_short() {
269        let result = H33Client::verify_local("0140");
270        assert!(result.is_err());
271    }
272
273    #[test]
274    fn client_creation() {
275        let client = H33Client::new("https://api.h33.ai").unwrap();
276        assert_eq!(client.base_url, "https://api.h33.ai");
277    }
278
279    #[test]
280    fn client_with_api_key() {
281        let client = H33Client::with_api_key("https://api.h33.ai", "test-key-123").unwrap();
282        assert_eq!(client.base_url, "https://api.h33.ai");
283    }
284}