use base64::Engine;
use base64::engine::general_purpose::STANDARD;
use crate::checkpoint::{CheckpointBody, NoteVerifierKey, sign_note};
use crate::error::{TrustError, TrustResult};
use crate::keys::Ed25519Identity;
use crate::merkle::{AppendOnlyTree, RawLeaf};
use super::artifact::{
CONSISTENCY_PROOF_FORMAT, ConsistencyProofArtifact, INCLUSION_PROOF_FORMAT,
InclusionProofArtifact, check_json_safe_tree_size,
};
use super::verify::{verify_consistency_artifact, verify_inclusion_artifact};
pub fn build_inclusion_artifact(
tree: &AppendOnlyTree<RawLeaf>,
leaf_bytes: &[u8],
origin: &str,
identity: &Ed25519Identity,
leaf_index: u64,
) -> TrustResult<InclusionProofArtifact> {
let root = tree.root();
check_json_safe_tree_size(root.num_leaves())?;
let proof = tree.prove_inclusion(leaf_index)?;
let hashes = chunk_proof_bytes(proof.as_bytes())?;
let body = CheckpointBody::from_root(origin, &root)?;
let checkpoint = sign_note(&body.encode(), origin, identity)?;
let artifact = InclusionProofArtifact {
format: INCLUSION_PROOF_FORMAT.to_string(),
tree_size: root.num_leaves(),
leaf_index,
hashes,
checkpoint,
};
let verifier = self_verifier(origin, identity)?;
verify_inclusion_artifact(&artifact, leaf_bytes, &verifier).map_err(|_err| {
TrustError::LogArtifactEncoding {
reason: "self-verification of freshly built inclusion artifact failed \
(the supplied leaf bytes do not match the leaf at the given index, \
or the tree is internally inconsistent)"
.to_string(),
}
})?;
Ok(artifact)
}
pub fn build_consistency_artifact(
old_tree: &AppendOnlyTree<RawLeaf>,
new_tree: &AppendOnlyTree<RawLeaf>,
origin: &str,
identity: &Ed25519Identity,
) -> TrustResult<ConsistencyProofArtifact> {
let old_size = old_tree.len();
let new_size = new_tree.len();
check_json_safe_tree_size(new_size)?;
if old_size == 0 {
return Err(TrustError::LogArtifactEncoding {
reason: "consistency artifact requires the old tree size to be at least 1; \
RFC 6962 has no consistency proof from an empty tree"
.to_string(),
});
}
if old_size >= new_size {
return Err(TrustError::LogArtifactEncoding {
reason: format!(
"consistency artifact requires old size strictly below new size: \
old={old_size}, new={new_size}"
),
});
}
let proof = new_tree.prove_consistency(old_size, new_size)?;
let hashes = chunk_proof_bytes(proof.as_bytes())?;
let body_1 = CheckpointBody::from_root(origin, &old_tree.root())?;
let body_2 = CheckpointBody::from_root(origin, &new_tree.root())?;
let checkpoint_1 = sign_note(&body_1.encode(), origin, identity)?;
let checkpoint_2 = sign_note(&body_2.encode(), origin, identity)?;
let artifact = ConsistencyProofArtifact {
format: CONSISTENCY_PROOF_FORMAT.to_string(),
tree_size_1: old_size,
tree_size_2: new_size,
hashes,
checkpoint_1,
checkpoint_2,
};
let verifier = self_verifier(origin, identity)?;
verify_consistency_artifact(&artifact, &verifier).map_err(|_err| {
TrustError::LogArtifactEncoding {
reason: "self-verification of freshly built consistency artifact failed \
(the old tree is not a prefix of the new tree)"
.to_string(),
}
})?;
Ok(artifact)
}
fn self_verifier(origin: &str, identity: &Ed25519Identity) -> TrustResult<NoteVerifierKey> {
NoteVerifierKey::new(origin, identity.public_key_bytes()).map_err(|e| {
TrustError::LogArtifactEncoding {
reason: format!("could not build self-verification key: {e}"),
}
})
}
fn chunk_proof_bytes(proof_bytes: &[u8]) -> TrustResult<Vec<String>> {
let chunks = proof_bytes.chunks_exact(32);
if !chunks.remainder().is_empty() {
return Err(TrustError::LogArtifactEncoding {
reason: format!(
"proof byte length {} is not a multiple of the 32-byte digest size",
proof_bytes.len()
),
});
}
Ok(chunks.map(|chunk| STANDARD.encode(chunk)).collect())
}
#[cfg(test)]
#[path = "build_tests.rs"]
mod tests;