pub use techne_lexicon_check::proof::{
atproto_key_in_doc, build_proof, key_layer, multikey, read_car, verify_record, verify_record_proof, ProofError, Proven, ProvenRecord,
RepoKey,
};
#[cfg(feature = "net")]
use serde_json::Value;
pub const MAX_PROOF_BYTES: u64 = 4 * 1024 * 1024;
#[cfg(feature = "net")]
pub async fn fetch_proven(
http: &reqwest::Client,
pds: &str,
did: &str,
collection: &str,
rkey: &str,
key: &RepoKey,
) -> anyhow::Result<Option<ProvenRecord>> {
let resp = http
.get(format!("{}/xrpc/com.atproto.sync.getRecord", pds.trim_end_matches('/')))
.query(&[("did", did), ("collection", collection), ("rkey", rkey)])
.send()
.await?;
let status = resp.status();
if resp.content_length().is_some_and(|n| n > MAX_PROOF_BYTES) {
anyhow::bail!("sync.getRecord answered more than {MAX_PROOF_BYTES} bytes");
}
let mut resp = resp;
let mut body: Vec<u8> = Vec::new();
while let Some(chunk) = resp.chunk().await? {
body.extend_from_slice(&chunk);
anyhow::ensure!(body.len() as u64 <= MAX_PROOF_BYTES, "sync.getRecord answered more than {MAX_PROOF_BYTES} bytes");
}
if !status.is_success() {
let err: Value = serde_json::from_slice(&body).unwrap_or(Value::Null);
let absent = matches!(err["error"].as_str(), Some("RecordNotFound" | "RepoNotFound" | "RepoDeactivated" | "RepoTakendown"));
if absent && (status == reqwest::StatusCode::BAD_REQUEST || status == reqwest::StatusCode::NOT_FOUND) {
return Ok(None);
}
anyhow::bail!("sync.getRecord answered {status} {}", err["error"].as_str().unwrap_or_default());
}
match verify_record_proof(&body, did, collection, rkey, key) {
Ok(Proven::Present(r)) => Ok(Some(r)),
Ok(Proven::Absent { .. }) => Ok(None),
Err(e) => {
tracing::warn!(%did, %collection, %rkey, %pds, error = %e, "a sync.getRecord proof was refused");
Err(anyhow::Error::new(e).context(format!("verifying {did}/{collection}/{rkey}")))
}
}
}