pub(crate) mod error;
mod hasher;
use error::{Error, Result};
use hasher::Sha256Hasher;
use sn_protocol::messages::{Hash, MerkleTreeNodesType};
use merkletree::{
hash::Algorithm,
merkle::{next_pow2, MerkleTree},
proof::Proof,
store::VecStore,
};
use std::collections::BTreeMap;
use typenum::{UInt, UTerm, B0, B1};
use xor_name::XorName;
pub type PaymentProofsTrailInfoMap =
BTreeMap<MerkleTreeNodesType, (Vec<MerkleTreeNodesType>, Vec<usize>)>;
type BinaryMerkletreeProofType = Proof<MerkleTreeNodesType, UInt<UInt<UTerm, B1>, B0>>;
pub fn build_payment_proofs<'a>(
content_addrs: impl Iterator<Item = &'a XorName>,
) -> Result<(Hash, PaymentProofsTrailInfoMap)> {
let mut addrs: Vec<_> = content_addrs
.map(|addr| {
let mut arr = MerkleTreeNodesType::default();
arr.copy_from_slice(addr);
arr
})
.collect();
if addrs.is_empty() {
return Err(Error::ProofTree(
"Cannot build payment proofs with an empty list of addresses".to_string(),
));
}
let num_of_leaves = usize::max(2, next_pow2(addrs.len()));
let num_of_addrs = addrs.len();
for _ in num_of_addrs..num_of_leaves {
addrs.push(MerkleTreeNodesType::default());
}
let merkletree = MerkleTree::<MerkleTreeNodesType, Sha256Hasher, VecStore<_>>::new(
addrs.clone().into_iter(),
)
.map_err(|err| Error::ProofTree(err.to_string()))?;
let reason_hash = merkletree.root().into();
let mut payment_proofs = BTreeMap::new();
for (index, addr) in addrs.into_iter().take(num_of_addrs).enumerate() {
let proof = merkletree
.gen_proof(index)
.map_err(|err| Error::GenAuditTrail {
index,
reason: err.to_string(),
})?;
payment_proofs.insert(addr, (proof.lemma().to_vec(), proof.path().to_vec()));
}
Ok((reason_hash, payment_proofs))
}
pub fn validate_payment_proof(
addr_name: XorName,
reason_hash: &Hash,
audit_trail: &[MerkleTreeNodesType],
path: &[usize],
) -> Result<()> {
trace!("Verifying payment proof for chunk store {addr_name:?} ...");
let mut hasher = Sha256Hasher::default();
let leaf_to_validate = hasher.leaf(addr_name.0);
let proof =
BinaryMerkletreeProofType::new::<UTerm, UTerm>(None, audit_trail.to_vec(), path.to_vec())
.map_err(|err| Error::InvalidAuditTrail(err.to_string()))?;
if leaf_to_validate != proof.item() {
return Err(Error::AuditTrailItemMismatch(addr_name));
}
if !proof
.validate::<Sha256Hasher>()
.map_err(|err| Error::AuditTrailSelfValidation(err.to_string()))?
{
return Err(Error::AuditTrailSelfValidation(
"inclusion proof validation failed".to_string(),
));
}
if *reason_hash == proof.root().into() {
Ok(())
} else {
Err(Error::ReasonHashMismatch(reason_hash.to_hex()))
}
}
#[cfg(test)]
mod tests {
use super::*;
use eyre::{eyre, Result};
use tiny_keccak::{Hasher, Sha3};
use xor_name::XorName;
fn hash(prefix: u8, bytes_l: &[u8], bytes_r: &[u8]) -> MerkleTreeNodesType {
let mut sha3 = Sha3::v256();
sha3.update(&[prefix]);
sha3.update(bytes_l);
sha3.update(bytes_r);
let mut hash = MerkleTreeNodesType::default();
sha3.finalize(&mut hash);
hash
}
#[test]
fn test_payment_proof_basic() -> Result<()> {
let name0 = XorName([11; 32]);
let name1 = XorName([22; 32]);
let name2 = XorName([33; 32]);
let leaf0 = hash(0, &name0.0, &[]);
let leaf1 = hash(0, &name1.0, &[]);
let leaf2 = hash(0, &name2.0, &[]);
let leaf3 = hash(0, &MerkleTreeNodesType::default(), &[]);
let node0 = hash(1, &leaf0, &leaf1);
let node1 = hash(1, &leaf2, &leaf3);
let root = hash(1, &node0, &node1);
let addrs = [name0, name1, name2];
let (reason_hash, payment_proofs) = build_payment_proofs(addrs.iter())?;
assert_eq!(payment_proofs.len(), addrs.len());
assert_eq!(reason_hash, root.into());
Ok(())
}
#[test]
fn test_payment_proof_non_power_of_2_input() -> Result<()> {
assert!(
matches!(build_payment_proofs(vec![].iter()), Err(Error::ProofTree(err)) if err == "Cannot build payment proofs with an empty list of addresses")
);
let addrs = [
[11; 32], [22; 32], [33; 32], [44; 32], [55; 32], [66; 32], [77; 32], [88; 32],
]
.into_iter()
.map(XorName)
.collect::<Vec<_>>();
for i in 1..addrs.len() {
let (_, payment_proofs) = build_payment_proofs(addrs.iter().take(i))?;
assert_eq!(payment_proofs.len(), i);
}
Ok(())
}
#[test]
fn test_payment_proof_validation() -> Result<()> {
let name0 = XorName([11; 32]);
let name1 = XorName([22; 32]);
let name2 = XorName([33; 32]);
let addrs = [name0, name1, name2];
let (reason_hash, payment_proofs) = build_payment_proofs(addrs.iter())?;
assert_eq!(payment_proofs.len(), addrs.len());
assert!(
matches!(payment_proofs.get(&name0.0), Some((audit_trail, path)) if validate_payment_proof(name0, &reason_hash, audit_trail, path).is_ok())
);
assert!(
matches!(payment_proofs.get(&name1.0), Some((audit_trail, path)) if validate_payment_proof(name1, &reason_hash, audit_trail, path).is_ok())
);
assert!(
matches!(payment_proofs.get(&name2.0), Some(( audit_trail, path)) if validate_payment_proof(name2, &reason_hash, audit_trail, path).is_ok())
);
let (audit_trail, path) = payment_proofs
.get(&name2.0)
.cloned()
.ok_or_else(|| eyre!("Failed to obtain valid payment proof"))?;
let invalid_name = XorName([99; 32]);
assert!(matches!(
validate_payment_proof(invalid_name, &reason_hash, &audit_trail, &path),
Err(Error::AuditTrailItemMismatch(name)) if name == invalid_name
));
let mut corrupted_audit_trail = audit_trail.clone();
corrupted_audit_trail[1][0] = 0; assert!(matches!(
validate_payment_proof(name2, &reason_hash, &corrupted_audit_trail, &path),
Err(Error::AuditTrailSelfValidation(_))
));
let mut corrupted_audit_trail = audit_trail.clone();
corrupted_audit_trail.push(name0.0); assert!(matches!(
validate_payment_proof(name2, &reason_hash, &corrupted_audit_trail, &path),
Err(Error::InvalidAuditTrail(_))
));
let invalid_reason_hash: Hash = [66; 32].into();
assert!(matches!(
validate_payment_proof(name2, &invalid_reason_hash, &audit_trail, &path),
Err(Error::ReasonHashMismatch(hex)) if hex == invalid_reason_hash.to_hex()
));
Ok(())
}
}