use crate::cbor;
use crate::error::{TrustError, TrustResult};
use crate::keys::identity::Ed25519Identity;
use crate::merkle;
use super::encoding::{self, DIGEST_LEN, MAX_PATH_ELEMENTS};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ConsistencyReceipt {
pub anchor_public_key: [u8; DIGEST_LEN],
pub tree_size_1: u64,
pub tree_size_2: u64,
pub consistency_path: Vec<[u8; DIGEST_LEN]>,
pub signature: [u8; 64],
}
impl ConsistencyReceipt {
#[must_use]
pub fn to_cose_bytes(&self) -> Vec<u8> {
encoding::consistency_artifact_bytes(
&self.anchor_public_key,
self.tree_size_1,
self.tree_size_2,
&self.consistency_path,
&self.signature,
)
}
pub fn from_cose_bytes(bytes: &[u8]) -> TrustResult<Self> {
let fields = encoding::decode_consistency_fields(bytes)?;
let canonical = encoding::consistency_artifact_bytes(
&fields.anchor_public_key,
fields.tree_size_1,
fields.tree_size_2,
&fields.consistency_path,
&fields.signature,
);
if canonical != bytes {
return Err(TrustError::ReceiptVerification);
}
Ok(Self {
anchor_public_key: fields.anchor_public_key,
tree_size_1: fields.tree_size_1,
tree_size_2: fields.tree_size_2,
consistency_path: fields.consistency_path,
signature: fields.signature,
})
}
fn flattened_path(&self) -> Vec<u8> {
let mut flattened = Vec::with_capacity(self.consistency_path.len() * DIGEST_LEN);
for node in &self.consistency_path {
flattened.extend_from_slice(node);
}
flattened
}
}
pub fn sign_consistency_receipt(
old_root: &[u8; DIGEST_LEN],
tree_size_1: u64,
tree_size_2: u64,
consistency_path: &[[u8; DIGEST_LEN]],
anchor_key: &Ed25519Identity,
) -> TrustResult<ConsistencyReceipt> {
if consistency_path.len() > MAX_PATH_ELEMENTS {
return Err(TrustError::MerkleTree {
reason: format!(
"consistency path of {} nodes exceeds the {MAX_PATH_ELEMENTS}-node maximum any \
tree of up to u64::MAX leaves can require",
consistency_path.len()
),
});
}
let mut flattened = Vec::with_capacity(consistency_path.len() * DIGEST_LEN);
for node in consistency_path {
flattened.extend_from_slice(node);
}
let new_root =
merkle::root_from_consistency_path(old_root, tree_size_1, tree_size_2, &flattened)?;
let protected = encoding::protected_bytes(
encoding::CONSISTENCY_CONTENT_TYPE,
&anchor_key.public_key_bytes(),
);
let signature = anchor_key.sign(&cbor::sig_structure_bytes(&protected, &new_root));
Ok(ConsistencyReceipt {
anchor_public_key: anchor_key.public_key_bytes(),
tree_size_1,
tree_size_2,
consistency_path: consistency_path.to_vec(),
signature,
})
}
pub fn verify_consistency_receipt(
receipt: &ConsistencyReceipt,
expected_anchor_public_key: &[u8; DIGEST_LEN],
old_root: &[u8; DIGEST_LEN],
) -> TrustResult<[u8; DIGEST_LEN]> {
if receipt.anchor_public_key != *expected_anchor_public_key {
return Err(TrustError::ReceiptVerification);
}
let new_root = merkle::root_from_consistency_path(
old_root,
receipt.tree_size_1,
receipt.tree_size_2,
&receipt.flattened_path(),
)
.map_err(|_err| TrustError::ReceiptVerification)?;
let protected = encoding::protected_bytes(
encoding::CONSISTENCY_CONTENT_TYPE,
&receipt.anchor_public_key,
);
let sig_structure = cbor::sig_structure_bytes(&protected, &new_root);
Ed25519Identity::verify(
&receipt.anchor_public_key,
&sig_structure,
&receipt.signature,
)
.map_err(|_err| TrustError::ReceiptVerification)?;
Ok(new_root)
}
pub fn verify_consistency_receipt_bytes(
bytes: &[u8],
expected_anchor_public_key: &[u8; DIGEST_LEN],
old_root: &[u8; DIGEST_LEN],
) -> TrustResult<(ConsistencyReceipt, [u8; DIGEST_LEN])> {
let receipt = ConsistencyReceipt::from_cose_bytes(bytes)?;
let new_root = verify_consistency_receipt(&receipt, expected_anchor_public_key, old_root)?;
Ok((receipt, new_root))
}
#[cfg(test)]
#[path = "consistency_tests.rs"]
mod tests;