use crate::hash::{hash_leaf, hash_node_ref, NodeHash, NULL_HASH};
use crate::node::{is_leaf_node, is_wrapper_node};
use crate::proof::{Proof, ProofBranch, ProofHash, ProofNode};
use crate::tally::{combine_tally, has_one_vote, TallyList};
use crate::utilstring::to_hex;
use crate::{NodeRef, Validation, VoteReference};
use sha2::{Digest, Sha256};
use std::collections::HashSet;
#[derive(Clone, Debug, PartialEq, Copy)]
pub enum TraverseDirection {
Left = 0,
Right = 1,
}
pub enum CollapseBranchConstrains {
None = 0,
RejectLeftPath = 1,
RejectRightPath = 2,
}
pub fn is_vote_in_proof(vote: &VoteReference, proof: &Proof) -> bool {
if let Some(leaf_branch) = proof.branches.get(0) {
match leaf_branch {
(None, None) => false,
(Some(l), None) => &l.0 == vote,
(Some(l), Some(r)) => &l.0 == vote || &r.0 == vote,
(None, Some(r)) => &r.0 == vote,
}
} else {
false
}
}
pub fn are_neighboors(
left: (&VoteReference, &Proof),
right: (&VoteReference, &Proof),
v: &Validation,
) -> Result<bool, String> {
let (left_vote, left_proof) = left;
let (right_vote, right_proof) = right;
if left_vote == right_vote {
return Ok(false);
}
if !is_vote_in_proof(left_vote, left_proof) {
return Err("Left vote reference does not belong to proof".to_string());
}
if !is_vote_in_proof(right_vote, right_proof) {
return Err("Right vote reference does not belong to proof".to_string());
}
if is_vote_in_proof(left_vote, right_proof) {
if !is_vote_in_proof(right_vote, left_proof) {
return Err("If left vote reference is in right proof, then right vote refrence should be in left proof".to_string());
}
return Ok(true);
}
if left_proof.branches.len() != right_proof.branches.len() {
return Err(
"Expected the branch length of left and right proof to be the same".to_string(),
);
}
let ((left_merkle_root, _), left_directions) = collapse_branches(
left_proof.branches.clone(),
&CollapseBranchConstrains::None,
v,
)?;
let ((right_merkle_root, _), right_directions) = collapse_branches(
right_proof.branches.clone(),
&CollapseBranchConstrains::None,
v,
)?;
if left_merkle_root != right_merkle_root {
return Err(format!(
"Merkle root hash for proofs don't match ({} != {})",
to_hex(&left_merkle_root)?,
to_hex(&right_merkle_root)?
));
}
let left_index = find_node_index(
left_vote,
left_proof.branches.get(0).ok_or("Empty proof")?,
&left_directions,
)?;
let right_index = find_node_index(
right_vote,
right_proof.branches.get(0).ok_or("Empty proof")?,
&right_directions,
)?;
Ok(left_index + 1 == right_index || left_index != 0 && left_index - 1 == right_index)
}
pub fn collapse_branches(
mut branches: Vec<ProofBranch>,
constraints: &CollapseBranchConstrains,
v: &Validation,
) -> Result<(ProofNode, Vec<TraverseDirection>), String> {
if branches.is_empty() {
return Err("Can't collapse empty branches".to_string());
}
let has_leaf = branches.len() == 1;
if has_leaf {
let branch = derive_wrapper_branch(&branches[0], v)?;
let dir = if branch.1.as_ref().unwrap().0 == *NULL_HASH.as_array() {
TraverseDirection::Left
} else {
TraverseDirection::Right
};
return Ok((
tally_and_hash(
&branch.0.ok_or("Invalid leaf branch")?,
&branch.1.ok_or("Invalid leaf branch")?,
)?,
vec![dir],
));
};
let (proof_left, proof_right) = branches.pop().unwrap();
if proof_left.is_none() && proof_right.is_none() {
return Err("Both left and right nodes cannot be None".to_string());
}
if proof_left.is_some() && proof_right.is_some() {
return Err("Both left and right cannot exist for non-leaf branches".to_string());
}
let mut directions: Vec<TraverseDirection> = vec![];
let left = if let Some(l) = proof_left {
l
} else {
if matches!(constraints, CollapseBranchConstrains::RejectLeftPath) {
let (hash, _) = proof_right.as_ref().ok_or("Invalid right proof")?;
if hash != NULL_HASH.as_array() {
return Err("Proof is constrained from collapsing to the left".to_string());
}
}
let (n, d) = collapse_branches(branches.clone(), constraints, v)?;
directions = [vec![TraverseDirection::Left], d].concat();
n
};
let right = if let Some(r) = proof_right {
r
} else {
if matches!(constraints, CollapseBranchConstrains::RejectRightPath) {
return Err("Proof is constrained from collapsing to the right".to_string());
}
let (n, d) = collapse_branches(branches, constraints, v)?;
directions = [vec![TraverseDirection::Right], d].concat();
n
};
Ok((tally_and_hash(&left, &right)?, directions))
}
pub fn derive_wrapper_branch(branch: &ProofBranch, v: &Validation) -> Result<ProofBranch, String> {
let (left_proof_hash, left_tally) = branch.0.as_ref().ok_or("Invalid left leaf")?;
let (right_proof_hash, right_tally) = branch.1.as_ref().ok_or("Invalid right leaf")?;
let left_tally = match left_tally {
None => Err("Invalid proof, no tally on left node.".to_string()),
Some(t) => {
if !matches!(v, Validation::Relaxed) && !has_one_vote(t) {
Err("Invalid proof. Left node should have exactly one vote.".to_string())
} else {
Ok(t)
}
}
}?;
if right_proof_hash != NULL_HASH.as_array()
&& !has_one_vote(right_tally.as_ref().unwrap_or(&vec![]))
{
return Err(
"Invalid proof. Right node should be null or have exactly one vote.".to_string(),
);
}
let left_hash = derive_wrapper_hash(&hash_leaf(left_proof_hash, left_tally), left_tally);
let right_hash = if right_proof_hash == NULL_HASH.as_array() {
*NULL_HASH.as_array()
} else {
derive_wrapper_hash(
&hash_leaf(right_proof_hash, right_tally.as_ref().unwrap()),
right_tally.as_ref().unwrap(),
)
};
Ok((
Some((left_hash, Some(left_tally.clone()))),
Some((right_hash, right_tally.clone())),
))
}
pub fn derive_wrapper_hash(hash: &NodeHash, tally: &[u32]) -> ProofHash {
let mut hasher = Sha256::new();
tally
.iter()
.map(|t| t.to_be_bytes())
.for_each(|b| hasher.update(b));
hasher.update(hash.as_slice());
hasher
.finalize()
.as_slice()
.try_into()
.expect("Expected hash to be 32 bytes")
}
pub fn directions_to_node_index(directions: &[TraverseDirection]) -> usize {
let mut index: usize = 0;
for dir in directions.iter() {
index <<= 1;
index |= *dir as usize;
}
index
}
pub fn create_merkle_proof(path: &[NodeRef], v: &Validation) -> Result<Proof, String> {
let mut proof = Proof::default();
let mut added_nodes: HashSet<ProofHash> = HashSet::new();
for b in path.iter().rev() {
if is_leaf_node(b) || is_wrapper_node(b) {
continue;
}
if let Some(h) = hash_node_ref(b, v)? {
added_nodes.insert(*h.0.as_array());
}
let none_if_added = |node: Option<(NodeHash, Option<TallyList>)>| {
if let Some((hash, tally)) = node {
if added_nodes.contains(hash.as_array()) {
None
} else {
Some((*hash.as_array(), tally))
}
} else {
None
}
};
let leaf_to_proof_node = |leaf: &NodeRef| -> Result<Option<ProofNode>, String> {
let leaf = leaf.as_ref().ok_or("Missing leaf node")?;
let vote = leaf
.vote
.as_ref()
.ok_or("Invalid leaf node, vote missing")?;
Ok(Some((vote.0, Some(vote.1.clone()))))
};
let b = b.as_ref().ok_or("None node in path")?;
let left = if is_wrapper_node(&b.left) {
leaf_to_proof_node(&b.left.as_ref().unwrap().left)?
} else {
none_if_added(hash_node_ref(&b.left, v)?)
};
let right = if is_wrapper_node(&b.right) {
leaf_to_proof_node(&b.right.as_ref().unwrap().left)?
} else {
none_if_added(hash_node_ref(&b.right, v)?)
};
proof.branches.push((left, right))
}
Ok(proof)
}
pub fn find_node_index(
reference: &VoteReference,
leaf_branch: &ProofBranch,
directions: &[TraverseDirection],
) -> Result<usize, String> {
let directions_index = directions_to_node_index(directions);
let (left, right) = leaf_branch;
let left = left.as_ref().ok_or("Left proof node missing")?;
let right = right.as_ref().ok_or("Right proof node missing")?;
if &right.0 != NULL_HASH.as_array() && &left.0 == reference {
Ok(directions_index - 1)
} else {
Ok(directions_index)
}
}
pub fn tally_and_hash(left: &ProofNode, right: &ProofNode) -> Result<ProofNode, String> {
let mut hasher = Sha256::new();
let tally = combine_tally(&left.1, &right.1)?;
tally
.iter()
.map(|t| t.to_be_bytes())
.for_each(|b| hasher.update(b));
hasher.update(left.0.as_slice());
hasher.update(right.0.as_slice());
let hash: [u8; 32] = hasher
.finalize()
.as_slice()
.try_into()
.expect("Expected hash to be 32 bytes");
Ok((hash, Some(tally)))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::generate::generate_tree;
use crate::navigate::find_node_by_vote_reference;
use crate::proof::votecount::create_proof_of_vote_count;
#[test]
fn test_are_neighboors() {
let a_vote: VoteReference = [0xaa; 32];
let b_vote: VoteReference = [0xbb; 32];
let c_vote: VoteReference = [0xcc; 32];
let root = generate_tree(
vec![
(a_vote, vec![1, 0]),
(b_vote, vec![1, 0]),
(c_vote, vec![0, 1]),
],
true,
)
.unwrap();
let v = &Validation::Strict;
let a_proof =
create_merkle_proof(&find_node_by_vote_reference(&root, &a_vote).unwrap(), v).unwrap();
let b_proof =
create_merkle_proof(&find_node_by_vote_reference(&root, &b_vote).unwrap(), v).unwrap();
let c_proof =
create_merkle_proof(&find_node_by_vote_reference(&root, &c_vote).unwrap(), v).unwrap();
assert!(are_neighboors((&a_vote, &a_proof), (&b_vote, &b_proof), v).unwrap());
assert!(!are_neighboors((&a_vote, &a_proof), (&c_vote, &c_proof), v).unwrap());
assert!(are_neighboors((&b_vote, &b_proof), (&c_vote, &c_proof), v).unwrap());
let err = are_neighboors((&a_vote, &a_proof), (&c_vote, &a_proof), v);
assert_eq!(
err,
Err("Right vote reference does not belong to proof".to_string())
);
}
#[test]
fn test_create_merkle_proof() {
let root = generate_tree(
vec![
([0xaa; 32], vec![1, 0]),
([0xbb; 32], vec![1, 0]),
([0xcc; 32], vec![0, 1]),
],
true,
)
.unwrap();
let a = root.clone();
let b = a.as_ref().unwrap().left.clone();
let c = a.as_ref().unwrap().right.clone();
let d = b.as_ref().unwrap().left.clone();
let v1 = d.as_ref().unwrap().left.clone();
let path = [a, b, d, v1.clone()];
let proof = create_merkle_proof(&path, &Validation::Strict).unwrap();
assert_eq!(proof.branches.len(), 2);
let (proof_v1, proof_v2) = &proof.branches[0];
assert_eq!(proof_v1.as_ref().unwrap(), &([0xaa; 32], Some(vec![1, 0])));
assert_eq!(proof_v2.as_ref().unwrap(), &([0xbb; 32], Some(vec![1, 0])));
let (left, proof_c) = &proof.branches[1];
assert!(left.is_none());
let (c_hash, c_tally) = hash_node_ref(&c, &Validation::Strict).unwrap().unwrap();
assert_eq!(proof_c, &Some((*c_hash.as_array(), c_tally)));
}
#[test]
fn test_collapse_branches_directions() {
let root = generate_tree(
vec![
([0xaa; 32], vec![1, 0]),
([0xbb; 32], vec![0, 1]),
([0xcc; 32], vec![0, 1]),
([0xdd; 32], vec![1, 0]),
([0xee; 32], vec![1, 0]),
],
true,
)
.unwrap();
let proof = create_proof_of_vote_count(&root, &Validation::Strict).unwrap();
let (_, directions) = collapse_branches(
proof.branches,
&CollapseBranchConstrains::RejectLeftPath,
&Validation::Strict,
)
.unwrap();
assert_eq!(
directions,
vec![
TraverseDirection::Right,
TraverseDirection::Left,
TraverseDirection::Left
]
);
}
#[test]
fn test_directions_to_node_index() {
let root = generate_tree(
vec![
([0xaa; 32], vec![1, 0]),
([0xbb; 32], vec![0, 1]),
([0xcc; 32], vec![0, 1]),
([0xdd; 32], vec![1, 0]),
([0xee; 32], vec![1, 0]),
],
true,
)
.unwrap();
let proof = create_proof_of_vote_count(&root, &Validation::Strict).unwrap();
let (_, directions) = collapse_branches(
proof.branches,
&CollapseBranchConstrains::RejectLeftPath,
&Validation::Strict,
)
.unwrap();
assert_eq!(directions_to_node_index(&directions), 4);
}
#[test]
fn test_collapse_branches_constraints() {
let tree = generate_tree(
vec![
([0xaa; 32], vec![1, 0]),
([0xbb; 32], vec![1, 0]),
([0xcc; 32], vec![0, 1]),
],
true,
)
.unwrap();
let v = &Validation::Strict;
let root_hash_and_tally = hash_node_ref(&tree, v).unwrap().unwrap();
let expected = (*root_hash_and_tally.0.as_array(), root_hash_and_tally.1);
let left_most_node = find_node_by_vote_reference(&tree, &[0xaa; 32]).unwrap();
let middle_node = find_node_by_vote_reference(&tree, &[0xbb; 32]).unwrap();
let right_most_node = find_node_by_vote_reference(&tree, &[0xcc; 32]).unwrap();
let left_proof = create_merkle_proof(&left_most_node, v).unwrap();
let middle_proof = create_merkle_proof(&middle_node, v).unwrap();
let right_proof = create_merkle_proof(&right_most_node, &v).unwrap();
assert_eq!(
expected,
collapse_branches(
left_proof.branches.clone(),
&CollapseBranchConstrains::None,
&v
)
.unwrap()
.0
);
assert_eq!(
Err("Proof is constrained from collapsing to the left".to_string()),
collapse_branches(
left_proof.branches.clone(),
&CollapseBranchConstrains::RejectLeftPath,
&v
)
);
assert_eq!(
expected,
collapse_branches(
left_proof.branches,
&CollapseBranchConstrains::RejectRightPath,
&v
)
.unwrap()
.0
);
assert_eq!(
expected,
collapse_branches(
middle_proof.branches.clone(),
&CollapseBranchConstrains::None,
&v
)
.unwrap()
.0
);
assert_eq!(
Err("Proof is constrained from collapsing to the left".to_string()),
collapse_branches(
middle_proof.branches.clone(),
&CollapseBranchConstrains::RejectLeftPath,
&v
)
);
assert_eq!(
expected,
collapse_branches(
middle_proof.branches,
&CollapseBranchConstrains::RejectRightPath,
&v
)
.unwrap()
.0
);
assert_eq!(
expected,
collapse_branches(
right_proof.branches.clone(),
&CollapseBranchConstrains::None,
&v
)
.unwrap()
.0
);
assert_eq!(
expected,
collapse_branches(
right_proof.branches.clone(),
&CollapseBranchConstrains::RejectLeftPath,
&v
)
.unwrap()
.0
);
assert_eq!(
Err("Proof is constrained from collapsing to the right".to_string()),
collapse_branches(
right_proof.branches,
&CollapseBranchConstrains::RejectRightPath,
&v
)
);
}
#[test]
fn test_find_node_index() {
let root = generate_tree(
vec![
([0xaa; 32], vec![1, 0]),
([0xbb; 32], vec![1, 0]),
([0xcc; 32], vec![0, 1]),
],
true,
)
.unwrap();
let v = &Validation::Strict;
let a_proof =
create_merkle_proof(&find_node_by_vote_reference(&root, &[0xaa; 32]).unwrap(), v)
.unwrap();
let b_proof =
create_merkle_proof(&find_node_by_vote_reference(&root, &[0xbb; 32]).unwrap(), v)
.unwrap();
let c_proof =
create_merkle_proof(&find_node_by_vote_reference(&root, &[0xcc; 32]).unwrap(), v)
.unwrap();
let (_, a_directions) =
collapse_branches(a_proof.branches.clone(), &CollapseBranchConstrains::None, v)
.unwrap();
let (_, b_directions) =
collapse_branches(b_proof.branches.clone(), &CollapseBranchConstrains::None, v)
.unwrap();
let (_, c_directions) =
collapse_branches(c_proof.branches.clone(), &CollapseBranchConstrains::None, v)
.unwrap();
assert_eq!(
0,
find_node_index(&[0xaa; 32], &a_proof.branches[0], &a_directions).unwrap()
);
assert_eq!(
1,
find_node_index(&[0xbb; 32], &b_proof.branches[0], &b_directions).unwrap()
);
assert_eq!(
2,
find_node_index(&[0xcc; 32], &c_proof.branches[0], &c_directions).unwrap()
);
}
}