use crate::types::{VotingError, WitnessData};
use incrementalmerkletree::{Hashable, Level};
use orchard::tree::MerkleHashOrchard;
use subtle::CtOption;
pub fn verify_witness(witness: &WitnessData) -> Result<bool, VotingError> {
if witness.note_commitment.len() != 32 {
return Err(VotingError::InvalidInput {
message: format!(
"note_commitment must be 32 bytes, got {}",
witness.note_commitment.len()
),
});
}
if witness.root.len() != 32 {
return Err(VotingError::InvalidInput {
message: format!("root must be 32 bytes, got {}", witness.root.len()),
});
}
if witness.auth_path.len() != 32 {
return Err(VotingError::InvalidInput {
message: format!(
"auth_path must have 32 levels, got {}",
witness.auth_path.len()
),
});
}
let commitment_bytes: [u8; 32] = witness.note_commitment[..].try_into().unwrap();
let mut current: MerkleHashOrchard =
ct_option_to_result(MerkleHashOrchard::from_bytes(&commitment_bytes), "note_commitment")?;
let root_bytes: [u8; 32] = witness.root[..].try_into().unwrap();
let expected_root: MerkleHashOrchard =
ct_option_to_result(MerkleHashOrchard::from_bytes(&root_bytes), "root")?;
let mut pos = witness.position;
for (level, sibling_bytes) in witness.auth_path.iter().enumerate() {
if sibling_bytes.len() != 32 {
return Err(VotingError::InvalidInput {
message: format!(
"auth_path[{}] must be 32 bytes, got {}",
level,
sibling_bytes.len()
),
});
}
let sibling_arr: [u8; 32] = sibling_bytes[..].try_into().unwrap();
let sibling: MerkleHashOrchard = ct_option_to_result(
MerkleHashOrchard::from_bytes(&sibling_arr),
&format!("auth_path[{}]", level),
)?;
let tree_level = Level::from(level as u8);
current = if pos & 1 == 0 {
MerkleHashOrchard::combine(tree_level, ¤t, &sibling)
} else {
MerkleHashOrchard::combine(tree_level, &sibling, ¤t)
};
pos >>= 1;
}
Ok(current == expected_root)
}
fn ct_option_to_result(
opt: CtOption<MerkleHashOrchard>,
field: &str,
) -> Result<MerkleHashOrchard, VotingError> {
Option::from(opt).ok_or_else(|| VotingError::InvalidInput {
message: format!("{} is not a valid Orchard tree hash", field),
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_verify_witness_validation() {
let bad = WitnessData {
note_commitment: vec![0; 16],
position: 0,
root: vec![0; 32],
auth_path: (0..32).map(|_| vec![0u8; 32]).collect(),
};
assert!(verify_witness(&bad).is_err());
let bad = WitnessData {
note_commitment: vec![0; 32],
position: 0,
root: vec![0; 32],
auth_path: (0..16).map(|_| vec![0u8; 32]).collect(),
};
assert!(verify_witness(&bad).is_err());
}
#[test]
fn test_verify_witness_rejects_wrong_root() {
let witness = WitnessData {
note_commitment: vec![0; 32],
position: 0,
root: vec![0xFF; 32], auth_path: (0..32).map(|_| vec![0u8; 32]).collect(),
};
let result = verify_witness(&witness);
match result {
Ok(valid) => assert!(!valid),
Err(_) => {} }
}
}