use std::convert::TryInto;
use log::trace;
use crate::Error;
use crate::internal::get_json;
use crate::jsons;
use crate::utils::{combine_tree_hash, largest_power_of_2_smaller_than, u8_to_hex};
pub fn consistency_proof_parts(from_size: u64, to_size: u64) -> Vec<(u64, u64)> {
fn inner(result_store: &mut Vec<(u64, u64)>, subtree: (u64, u64), from_size: u64) {
assert!(subtree.0 < subtree.1);
assert!(from_size <= subtree.1);
if from_size == subtree.1 {
result_store.push(subtree);
return;
}
let subtree_size = subtree.1 - subtree.0;
let start_of_right_branch = largest_power_of_2_smaller_than(subtree_size);
if from_size - subtree.0 <= start_of_right_branch { result_store.push((subtree.0 + start_of_right_branch, subtree.1));
inner(result_store, (subtree.0, subtree.0 + start_of_right_branch), from_size);
} else { result_store.push((subtree.0, subtree.0 + start_of_right_branch));
inner(result_store, (subtree.0 + start_of_right_branch, subtree.1), from_size);
}
}
let mut result_store = Vec::new();
inner(&mut result_store, (0, to_size), from_size);
result_store.reverse();
result_store
}
#[test]
fn consistency_proof_partial_test() {
assert_eq!(consistency_proof_parts(753913835, 753913848).len(), 25);
assert_eq!(consistency_proof_parts(6, 6), vec![(0, 6)]);
assert_eq!(consistency_proof_parts(7, 7), vec![(0, 7)]);
assert_eq!(consistency_proof_parts(4, 7), vec![(0, 4), (4, 7)]);
}
pub struct ConsistencyProofPart {
pub subtree: (u64, u64),
pub server_hash: [u8; 32],
}
pub fn verify_consistency_proof(perv_size: u64, next_size: u64, server_provided_proof: &[[u8; 32]], perv_root: &[u8; 32], next_root: &[u8; 32]) -> Result<Vec<ConsistencyProofPart>, String> {
if perv_size > next_size {
panic!("perv_size must be <= next_size");
}
if perv_size == next_size {
return Ok(Vec::new());
}
if perv_size == 0 {
return Ok(vec![ConsistencyProofPart{
subtree: (0, next_size),
server_hash: *next_root
}]);
}
let calculated_proof = consistency_proof_parts(perv_size, next_size);
let omit_first = u64::is_power_of_two(perv_size);
let mut expected_proof_len = calculated_proof.len();
if omit_first {
expected_proof_len -= 1;
}
if server_provided_proof.len() != expected_proof_len {
return Err(format!("wrong proof length: expected {}, got {}", expected_proof_len, server_provided_proof.len()));
}
let mut hashes = Vec::new();
hashes.reserve(calculated_proof.len());
if omit_first {
hashes.push(perv_root.clone());
}
hashes.extend_from_slice(server_provided_proof);
assert_eq!(hashes.len(), calculated_proof.len());
let mut derived_new_hash = hashes[0];
let mut derived_new_hash_subtree = calculated_proof[0];
for (subtree, hash) in (1..hashes.len()).map(|i| (calculated_proof[i], &hashes[i])) {
assert_ne!(derived_new_hash_subtree.0, subtree.0);
if subtree.0 > derived_new_hash_subtree.0 {
assert_eq!(subtree.0, derived_new_hash_subtree.1);
derived_new_hash = combine_tree_hash(&derived_new_hash, hash);
derived_new_hash_subtree = (derived_new_hash_subtree.0, subtree.1);
} else {
assert_eq!(subtree.1, derived_new_hash_subtree.0);
derived_new_hash = combine_tree_hash(hash, &derived_new_hash);
derived_new_hash_subtree = (subtree.0, derived_new_hash_subtree.1);
}
}
if derived_new_hash != *next_root {
return Err(format!("calculated tree root {} does not match given tree root {}", u8_to_hex(&derived_new_hash), u8_to_hex(next_root)));
}
if omit_first {
trace!("consistency checked from {} to {}; previous tree is complete.", &u8_to_hex(perv_root), &u8_to_hex(next_root));
Ok(hashes.iter().zip(calculated_proof.iter()).skip(1).map(|(hash, subtree)| ConsistencyProofPart{subtree: *subtree, server_hash: *hash}).collect())
} else {
assert!(calculated_proof[0].1 <= perv_size);
let mut derived_old_hash: [u8; 32] = hashes[0];
let mut derived_old_hash_subtree: (u64, u64) = calculated_proof[0];
let mut new_parts = Vec::new();
for (subtree, hash) in (1..hashes.len()).map(|i| (calculated_proof[i], &hashes[i])) {
if subtree.1 <= perv_size { if subtree.0 > derived_old_hash_subtree.0 {
assert_eq!(subtree.0, derived_old_hash_subtree.1);
derived_old_hash = combine_tree_hash(&derived_old_hash, hash);
derived_old_hash_subtree = (derived_old_hash_subtree.0, subtree.1);
} else {
assert_eq!(subtree.1, derived_old_hash_subtree.0);
derived_old_hash = combine_tree_hash(hash, &derived_old_hash);
derived_old_hash_subtree = (subtree.0, derived_old_hash_subtree.1);
}
} else {
assert!(subtree.0 >= perv_size);
new_parts.push(ConsistencyProofPart{
subtree,
server_hash: *hash,
});
}
}
if derived_old_hash != *perv_root {
return Err(format!("calculated perv_root {} does not match given perv_root {}", u8_to_hex(&derived_old_hash), u8_to_hex(perv_root)));
}
trace!("consistency checked from {} to {}", &u8_to_hex(perv_root), &u8_to_hex(next_root));
Ok(new_parts)
}
}
impl ConsistencyProofPart {
pub fn verify(&self, leaf_hashes: &[[u8; 32]]) -> Result<(), String> {
let subtree_size = self.subtree.1 - self.subtree.0;
if leaf_hashes.len() as u64 != subtree_size {
panic!("expected leaf_hashes to have length {}, got {}", subtree_size, leaf_hashes.len());
}
if subtree_size == 1 {
return if leaf_hashes[0] != self.server_hash {
Err(format!("expected leaf_hashes to be [{}], got [{}]", u8_to_hex(&self.server_hash), u8_to_hex(&leaf_hashes[0])))
} else {
Ok(())
}
}
let mut round_hashes = Vec::from(leaf_hashes);
loop {
let mut new_round_hashes = Vec::new();
new_round_hashes.reserve(round_hashes.len() / 2);
for i in 0..(round_hashes.len() / 2) {
let hash_left = round_hashes[2*i];
let hash_right = round_hashes[2*i + 1];
new_round_hashes.push(combine_tree_hash(&hash_left, &hash_right));
}
if round_hashes.len() % 2 != 0 {
new_round_hashes.push(*round_hashes.last().unwrap());
}
round_hashes = new_round_hashes;
if round_hashes.len() == 1 {
break;
}
}
assert_eq!(round_hashes.len(), 1);
let calculated_hash = round_hashes[0];
if self.server_hash == calculated_hash {
Ok(())
} else {
Err(format!("Subtree {:?}: calculated that tree hash should be {}, but got {} from consistency check.", &self.subtree, u8_to_hex(&calculated_hash), u8_to_hex(&self.server_hash)))
}
}
}
#[test]
fn verify_consistency_proof_new_tree_leaf_hashes_test() {
use crate::utils::sha256;
fn h(s: &str) -> [u8; 32] {
sha256(s.as_bytes())
}
fn c(a: &[u8; 32], b: &[u8; 32]) -> [u8; 32] {
combine_tree_hash(a, b)
}
(ConsistencyProofPart{
subtree: (0, 1),
server_hash: h("a")
}).verify(&[h("a")]).unwrap();
(ConsistencyProofPart{
subtree: (0, 1),
server_hash: h("a")
}).verify(&[h("b")]).expect_err("!");
(ConsistencyProofPart{
subtree: (2, 4),
server_hash: c(&h("c"), &h("d"))
}).verify(&[h("c"), h("d")]).unwrap();
(ConsistencyProofPart{
subtree: (0, 6),
server_hash: c(&c(&c(&h("a"), &h("b")), &c(&h("c"), &h("d"))), &c(&h("e"), &h("f")))
}).verify(&[h("a"), h("b"), h("c"), h("d"), h("e"), h("f")]).unwrap();
(ConsistencyProofPart{
subtree: (0, 6),
server_hash: c(&c(&c(&h("a"), &h("b")), &c(&h("c"), &h("d"))), &c(&h("e"), &h("f")))
}).verify(&[h("a"), h("b"), h("c"), h("g"), h("e"), h("f")]).expect_err("!");
(ConsistencyProofPart{
subtree: (0, 6),
server_hash: c(&c(&c(&h("a"), &h("b")), &c(&h("c"), &h("d"))), &c(&h("e"), &h("f")))
}).verify(&[h("a"), h("b"), h("c"), h("d"), h("e"), h("g")]).expect_err("!");
(ConsistencyProofPart{
subtree: (0, 4),
server_hash: c(&c(&h("a"), &h("b")), &c(&h("c"), &h("d")))
}).verify(&[h("a"), h("b"), h("c"), h("d")]).unwrap();
(ConsistencyProofPart{
subtree: (0, 4),
server_hash: c(&c(&h("a"), &h("b")), &c(&h("c"), &h("d")))
}).verify(&[h("c"), h("d"), h("a"), h("b")]).expect_err("!");
}
pub fn check_consistency_proof(client: &reqwest::blocking::Client, base_url: &reqwest::Url, prev_size: u64, next_size: u64, perv_root: &[u8; 32], next_root: &[u8; 32]) -> Result<Vec<ConsistencyProofPart>, Error> {
assert!(prev_size < next_size);
let server_consistency_proof: jsons::ConsistencyProof = get_json(client, base_url, &format!("ct/v1/get-sth-consistency?first={}&second={}", prev_size, next_size))?;
let server_consistency_proof = server_consistency_proof.consistency;
let mut parsed_server_proof: Vec<[u8; 32]> = Vec::new();
parsed_server_proof.reserve(server_consistency_proof.len());
let mut n = 0;
for i in server_consistency_proof.into_iter() {
n += 1;
let decoded = base64::decode(&i).map_err(|e| Error::MalformedResponseBody(format!("Can not base64 decode consistency proof element: {}", &e)))?;
if decoded.len() != 32 {
return Err(Error::MalformedResponseBody("Consistency proof element has length other than 32.".to_owned()));
}
parsed_server_proof.push(decoded[..].try_into().unwrap());
}
assert_eq!(parsed_server_proof.len(), n);
verify_consistency_proof(prev_size, next_size, &parsed_server_proof, perv_root, next_root)
.map_err(|e| Error::InvalidConsistencyProof{prev_size, new_size: next_size, desc: e})
}