use std::{path::Path, sync::Arc};
use world_id_core::artifacts::{
error::ZkArtifactError, ZkArtifactKind, ZkArtifactSourceExt,
};
use world_id_proof::{
artifacts::{
cached::CachedZkArtifactSource, embedded::EmbeddedZkArtifacts, ZkArtifactSource,
},
CircomGroth16Material, CircomGroth16MaterialBuilder, OwnershipProver,
OwnershipVerifier, ZkeyError,
};
use crate::{error::WalletKitError, storage::StoragePaths};
use super::WalletKitZkArtifactSource;
#[derive(uniffi::Object)]
pub struct CachingZkArtifacts(CachedZkArtifactSource);
#[uniffi::export]
impl CachingZkArtifacts {
#[uniffi::constructor]
#[must_use]
pub fn new(storage_paths: Arc<StoragePaths>) -> Self {
let inner = CachingZkArtifactsInner::new(storage_paths).cached();
Self(inner)
}
#[must_use]
pub fn as_zk_artifact_source(
self: Arc<Self>,
) -> Arc<dyn WalletKitZkArtifactSource> {
self
}
pub fn preload(&self) -> Result<(), WalletKitError> {
let _query_material = self.query_material().map_err(|error| {
WalletKitError::Groth16MaterialEmbeddedLoad {
error: format!("Failed to preload query material: {error}"),
}
})?;
let _nullifier_material = self.nullifier_material().map_err(|error| {
WalletKitError::Groth16MaterialEmbeddedLoad {
error: format!("Failed to preload nullifier material: {error}"),
}
})?;
Ok(())
}
}
impl ZkArtifactSource for CachingZkArtifacts {
fn query_material(&self) -> Result<Arc<CircomGroth16Material>, ZkArtifactError> {
self.0.query_material()
}
fn nullifier_material(
&self,
) -> Result<Arc<CircomGroth16Material>, ZkArtifactError> {
self.0.nullifier_material()
}
fn ownership_prover(&self) -> Result<OwnershipProver, ZkArtifactError> {
self.0.ownership_prover()
}
fn ownership_verifier(&self) -> Result<OwnershipVerifier, ZkArtifactError> {
self.0.ownership_verifier()
}
}
#[derive(Clone, uniffi::Object)]
struct CachingZkArtifactsInner {
storage_paths: Arc<StoragePaths>,
inner: Arc<dyn ZkArtifactSource>,
}
impl CachingZkArtifactsInner {
#[must_use]
fn new(storage_paths: Arc<StoragePaths>) -> Self {
Self {
storage_paths,
inner: Arc::new(EmbeddedZkArtifacts),
}
}
}
impl ZkArtifactSource for CachingZkArtifactsInner {
fn query_material(&self) -> Result<Arc<CircomGroth16Material>, ZkArtifactError> {
let maybe_query_material = self.try_query_material_from_cache()?;
if let Some(query_material) = maybe_query_material {
return Ok(query_material);
}
let query_material = self.inner.query_material()?;
Self::cache_material(
&query_material,
ZkArtifactKind::QueryMaterial,
self.storage_paths.query_zkey_path(),
self.storage_paths.query_graph_path(),
)?;
Ok(query_material)
}
fn nullifier_material(
&self,
) -> Result<Arc<CircomGroth16Material>, ZkArtifactError> {
let maybe_nullifier_material = self.try_nullifier_material_from_cache()?;
if let Some(nullifier_material) = maybe_nullifier_material {
return Ok(nullifier_material);
}
let nullifier_material = self.inner.nullifier_material()?;
Self::cache_material(
&nullifier_material,
ZkArtifactKind::NullifierMaterial,
self.storage_paths.nullifier_zkey_path(),
self.storage_paths.nullifier_graph_path(),
)?;
Ok(nullifier_material)
}
fn ownership_prover(&self) -> Result<OwnershipProver, ZkArtifactError> {
self.inner.ownership_prover()
}
fn ownership_verifier(&self) -> Result<OwnershipVerifier, ZkArtifactError> {
self.inner.ownership_verifier()
}
}
impl CachingZkArtifactsInner {
fn try_query_material_from_cache(
&self,
) -> Result<Option<Arc<CircomGroth16Material>>, ZkArtifactError> {
Self::try_material_from_cache(
ZkArtifactKind::QueryMaterial,
self.storage_paths.query_zkey_path(),
self.storage_paths.query_graph_path(),
)
}
fn try_nullifier_material_from_cache(
&self,
) -> Result<Option<Arc<CircomGroth16Material>>, ZkArtifactError> {
Self::try_material_from_cache(
ZkArtifactKind::NullifierMaterial,
self.storage_paths.nullifier_zkey_path(),
self.storage_paths.nullifier_graph_path(),
)
}
fn cache_material(
material: &CircomGroth16Material,
kind: ZkArtifactKind,
zkey_path: impl AsRef<Path>,
graph_path: impl AsRef<Path>,
) -> Result<(), ZkArtifactError> {
let zkey_path = zkey_path.as_ref();
let graph_path = graph_path.as_ref();
ensure_parent_dir_of(kind, zkey_path)?;
ensure_parent_dir_of(kind, graph_path)?;
material
.serializer()
.to_paths(zkey_path, graph_path)
.map_err(|error| {
ZkArtifactError::Load {
kind,
message: error.to_string(),
}
})
}
fn try_material_from_cache(
kind: ZkArtifactKind,
zkey_path: impl AsRef<Path>,
graph_path: impl AsRef<Path>,
) -> Result<Option<Arc<CircomGroth16Material>>, ZkArtifactError> {
let zkey_path = zkey_path.as_ref();
let graph_path = graph_path.as_ref();
if !(zkey_path.exists() && graph_path.exists()) {
return Ok(None);
}
let (zkey_fingerprint, graph_fingerprint) = match kind {
ZkArtifactKind::QueryMaterial => (
world_id_proof::QUERY_ZKEY_FINGERPRINT,
world_id_proof::QUERY_GRAPH_FINGERPRINT,
),
ZkArtifactKind::NullifierMaterial => (
world_id_proof::NULLIFIER_ZKEY_FINGERPRINT,
world_id_proof::NULLIFIER_GRAPH_FINGERPRINT,
),
_ => ("", ""),
};
match CircomGroth16MaterialBuilder::new()
.fingerprint_graph(graph_fingerprint.to_string())
.fingerprint_zkey(zkey_fingerprint.to_string())
.bbf_num_2_bits_helper()
.bbf_inv()
.bbf_legendre()
.bbf_sqrt_input()
.bbf_sqrt_unchecked()
.build_from_paths(zkey_path, graph_path)
{
Ok(material) => Ok(Some(Arc::new(material))),
Err(
ZkeyError::GraphFingerprintMismatch(_)
| ZkeyError::ZkeyFingerprintMismatch(_)
| ZkeyError::ZkeyInvalid(_)
| ZkeyError::GraphInvalid(_),
) => Ok(None),
Err(other) => Err(ZkArtifactError::Load {
kind,
message: other.to_string(),
}),
}
}
}
fn ensure_parent_dir_of(
kind: ZkArtifactKind,
zkey_path: impl AsRef<Path>,
) -> Result<(), ZkArtifactError> {
let zkey_path = zkey_path.as_ref();
if let Some(parent) = zkey_path.parent() {
std::fs::create_dir_all(parent).map_err(|error| ZkArtifactError::Load {
kind,
message: format!(
"Failed to create parent directory for {}: {error}",
zkey_path.display()
),
})?;
}
Ok(())
}