use base64::Engine;
use base64::engine::general_purpose::STANDARD;
use crate::bundle::artifact::{MAX_LINKS, VERIFICATION_BUNDLE_FORMAT, VerificationBundle};
use crate::checkpoint::{CheckpointBody, NoteVerifierKey, verify_checkpoint};
use crate::error::{TrustError, TrustResult};
use crate::receipt::verify_receipt_bytes;
use crate::tlog::verify_inclusion_artifact;
fn reject() -> TrustError {
TrustError::BundleVerification
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Notarization {
anchor_root: [u8; 32],
anchor_tree_size: u64,
leaf_index: u64,
}
impl Notarization {
pub fn anchor_root(&self) -> [u8; 32] {
self.anchor_root
}
pub fn anchor_tree_size(&self) -> u64 {
self.anchor_tree_size
}
pub fn leaf_index(&self) -> u64 {
self.leaf_index
}
}
#[derive(Clone, Debug)]
pub struct VerifiedBundle {
leaf: Vec<u8>,
log_checkpoint: CheckpointBody,
notarizations: Vec<Notarization>,
}
impl VerifiedBundle {
pub fn leaf(&self) -> &[u8] {
&self.leaf
}
pub fn log_checkpoint(&self) -> &CheckpointBody {
&self.log_checkpoint
}
pub fn notarizations(&self) -> &[Notarization] {
&self.notarizations
}
}
pub fn verify_bundle(
bundle: &VerificationBundle,
log_verifier: &NoteVerifierKey,
anchors: &[NoteVerifierKey],
) -> TrustResult<VerifiedBundle> {
if bundle.format != VERIFICATION_BUNDLE_FORMAT {
return Err(reject());
}
if bundle.links.len() > MAX_LINKS || anchors.len() != bundle.links.len() {
return Err(reject());
}
if bundle.counter_anchor.is_some() {
return Err(reject());
}
let leaf = STANDARD.decode(&bundle.leaf).map_err(|_err| reject())?;
let log_checkpoint = verify_inclusion_artifact(&bundle.inclusion_proof, &leaf, log_verifier)
.map_err(|_err| reject())?;
let mut notarizations = Vec::with_capacity(bundle.links.len());
for (index, link) in bundle.links.iter().enumerate() {
if index == 0 && link.checkpoint != bundle.inclusion_proof.checkpoint {
return Err(reject());
}
let receipt_bytes = STANDARD.decode(&link.receipt).map_err(|_err| reject())?;
let anchor = anchors.get(index).ok_or_else(reject)?;
let receipt = verify_receipt_bytes(
&receipt_bytes,
link.checkpoint.as_bytes(),
&anchor.public_key(),
)
.map_err(|_err| reject())?;
let anchor_root = receipt
.reconstructed_root(link.checkpoint.as_bytes())
.map_err(|_err| reject())?;
if let Some(next) = bundle.links.get(index + 1) {
let next_body =
verify_checkpoint(next.checkpoint.as_bytes(), anchor).map_err(|_err| reject())?;
if next_body.root_hash() != anchor_root || next_body.tree_size() != receipt.tree_size {
return Err(reject());
}
}
notarizations.push(Notarization {
anchor_root,
anchor_tree_size: receipt.tree_size,
leaf_index: receipt.leaf_index,
});
}
Ok(VerifiedBundle {
leaf,
log_checkpoint,
notarizations,
})
}
#[cfg(test)]
#[path = "verify_tests.rs"]
mod tests;