use base64::Engine;
use base64::engine::general_purpose::STANDARD;
use crate::checkpoint::{CheckpointBody, NoteVerifierKey, verify_checkpoint};
use crate::error::{TrustError, TrustResult};
use crate::merkle::{ConsistencyProof, InclusionProof, verify_consistency, verify_inclusion_raw};
use super::artifact::{
CONSISTENCY_PROOF_FORMAT, ConsistencyProofArtifact, INCLUSION_PROOF_FORMAT,
InclusionProofArtifact, MAX_JSON_TREE_SIZE,
};
const MAX_INCLUSION_HASHES: usize = 64;
const MAX_CONSISTENCY_HASHES: usize = 128;
pub fn verify_inclusion_artifact(
artifact: &InclusionProofArtifact,
leaf_bytes: &[u8],
verifier: &NoteVerifierKey,
) -> TrustResult<CheckpointBody> {
if artifact.format != INCLUSION_PROOF_FORMAT {
return Err(TrustError::LogArtifactVerification);
}
if artifact.tree_size >= MAX_JSON_TREE_SIZE {
return Err(TrustError::LogArtifactVerification);
}
if artifact.leaf_index >= artifact.tree_size {
return Err(TrustError::LogArtifactVerification);
}
if artifact.hashes.len() > MAX_INCLUSION_HASHES {
return Err(TrustError::LogArtifactVerification);
}
let proof_bytes = decode_hashes(&artifact.hashes)?;
let body = verify_checkpoint(artifact.checkpoint.as_bytes(), verifier)
.map_err(|_err| TrustError::LogArtifactVerification)?;
if body.tree_size() != artifact.tree_size {
return Err(TrustError::LogArtifactVerification);
}
let proof = InclusionProof::try_from_bytes(proof_bytes)
.map_err(|_err| TrustError::LogArtifactVerification)?;
verify_inclusion_raw(&body.to_root(), leaf_bytes, artifact.leaf_index, &proof)
.map_err(|_err| TrustError::LogArtifactVerification)?;
Ok(body)
}
pub fn verify_consistency_artifact(
artifact: &ConsistencyProofArtifact,
verifier: &NoteVerifierKey,
) -> TrustResult<(CheckpointBody, CheckpointBody)> {
if artifact.format != CONSISTENCY_PROOF_FORMAT {
return Err(TrustError::LogArtifactVerification);
}
if artifact.tree_size_1 == 0
|| artifact.tree_size_1 >= artifact.tree_size_2
|| artifact.tree_size_2 >= MAX_JSON_TREE_SIZE
{
return Err(TrustError::LogArtifactVerification);
}
if artifact.hashes.len() > MAX_CONSISTENCY_HASHES {
return Err(TrustError::LogArtifactVerification);
}
let proof_bytes = decode_hashes(&artifact.hashes)?;
let body_1 = verify_checkpoint(artifact.checkpoint_1.as_bytes(), verifier)
.map_err(|_err| TrustError::LogArtifactVerification)?;
let body_2 = verify_checkpoint(artifact.checkpoint_2.as_bytes(), verifier)
.map_err(|_err| TrustError::LogArtifactVerification)?;
if body_1.tree_size() != artifact.tree_size_1 || body_2.tree_size() != artifact.tree_size_2 {
return Err(TrustError::LogArtifactVerification);
}
let proof = ConsistencyProof::try_from_bytes(proof_bytes)
.map_err(|_err| TrustError::LogArtifactVerification)?;
verify_consistency(&body_1.to_root(), &body_2.to_root(), &proof)
.map_err(|_err| TrustError::LogArtifactVerification)?;
Ok((body_1, body_2))
}
fn decode_hashes(hashes: &[String]) -> TrustResult<Vec<u8>> {
let mut proof_bytes = Vec::with_capacity(hashes.len() * 32);
for entry in hashes {
let decoded = STANDARD
.decode(entry)
.map_err(|_err| TrustError::LogArtifactVerification)?;
if decoded.len() != 32 {
return Err(TrustError::LogArtifactVerification);
}
proof_bytes.extend_from_slice(&decoded);
}
Ok(proof_bytes)
}
#[cfg(test)]
#[path = "verify_tests.rs"]
mod tests;