use crate::client::{AuthorityClient, ChoraResponse};
use serde::Serialize;
use std::sync::Arc;
use vex_core::segment::{Capsule, CryptoData, IdentityData, IntentData, WitnessData};
use vex_hardware::api::AgentIdentity;
#[derive(Debug, Clone)]
pub struct AuthorityBridge {
pub client: Arc<dyn AuthorityClient>,
pub identity: Option<Arc<AgentIdentity>>,
}
impl AuthorityBridge {
pub fn new(client: Arc<dyn AuthorityClient>) -> Self {
Self {
client,
identity: None,
}
}
pub fn with_identity(mut self, identity: Arc<AgentIdentity>) -> Self {
self.identity = Some(identity);
self
}
pub async fn perform_handshake(
&self,
intent: IntentData,
nonce: &str,
) -> Result<Capsule, String> {
let payload = Self::canonicalize(&intent)?;
let response: ChoraResponse = self.client.request_attestation(&payload, nonce).await?;
let identity = if let Some(hw) = &self.identity {
IdentityData {
aid: hw.agent_id.clone(),
identity_type: "hardware-rooted".to_string(),
pcrs: None, metadata: vex_core::segment::SchemaValue(serde_json::Value::Null),
}
} else {
tracing::warn!(
"AuthorityBridge: No hardware identity attached. Using restricted IdentityData. \
Call .with_identity() to enable real attestation."
);
IdentityData {
aid: "mock-aid-01".to_string(),
identity_type: "unbound".to_string(),
pcrs: None,
metadata: vex_core::segment::SchemaValue(serde_json::Value::Null),
}
};
let now = chrono::Utc::now().timestamp() as u64;
let witness = WitnessData {
chora_node_id: response.authority.capsule_id.clone(),
receipt_hash: response.signature.clone(),
timestamp: now,
metadata: vex_core::segment::SchemaValue(serde_json::Value::Null),
};
let intent_hash = intent.to_jcs_hash()?.to_hex();
fn hash_seg<T: Serialize>(seg: &T) -> Result<String, String> {
let jcs = serde_jcs::to_vec(seg).map_err(|e| e.to_string())?;
let mut hasher = sha2::Sha256::new();
use sha2::Digest;
hasher.update(&jcs);
Ok(hex::encode(hasher.finalize()))
}
let authority_hash = hash_seg(&response.authority)?;
let identity_hash = hash_seg(&identity)?;
let witness_hash = witness.to_commitment_hash()?.to_hex();
let mut capsule = Capsule {
capsule_id: response.authority.capsule_id.clone(),
intent,
authority: response.authority,
identity,
witness,
intent_hash,
authority_hash,
identity_hash,
witness_hash,
capsule_root: String::new(), crypto: CryptoData {
algo: "ed25519".to_string(),
public_key_endpoint: "/public_key".to_string(),
signature_scope: "capsule_root".to_string(),
signature_b64: base64::Engine::encode(
&base64::engine::general_purpose::STANDARD,
hex::decode(&response.signature).unwrap_or_default(),
),
},
request_commitment: None,
};
let root = capsule.to_composite_hash()?;
capsule.capsule_root = root.to_hex();
Ok(capsule)
}
pub fn canonicalize<T: Serialize>(payload: &T) -> Result<Vec<u8>, String> {
serde_jcs::to_vec(payload).map_err(|e| format!("JCS Canonicalization failed: {}", e))
}
pub async fn verify_continuation_token(
&self,
token: &vex_core::ContinuationToken,
expected_aid: Option<&str>,
expected_intent_hash: Option<&str>,
expected_circuit_id: Option<&str>,
expected_nonce: Option<&str>,
expected_source_capsule_root: Option<&str>,
) -> Result<bool, String> {
self.client
.verify_continuation_token(
token,
expected_aid,
expected_intent_hash,
expected_circuit_id,
expected_nonce,
expected_source_capsule_root,
)
.await
}
}