ctclient_async/internal/
inclusion.rs1use crate::Error;
2use crate::internal::get_json;
3use crate::jsons::AuditProof;
4use crate::utils::{combine_tree_hash, u8_to_hex};
5use std::convert::TryInto;
6use std::ops::Range;
7
8pub fn inclusion_proof_parts(tree_size: u64, index: u64) -> Vec<Range<u64>> {
26 assert!(index < tree_size);
27 let mut current_subtree = index..(index + 1);
28 let mut result = Vec::new();
29 while current_subtree.end - current_subtree.start < tree_size {
30 let next_subtree_len = (current_subtree.end - current_subtree.start) * 2;
31 let next_subtree_start = current_subtree.start / next_subtree_len * next_subtree_len;
32 let next_subtree = next_subtree_start..(next_subtree_start + next_subtree_len);
33 let mid = next_subtree_start + next_subtree_len / 2;
34 if index < mid {
35 if mid < tree_size {
37 result.push(mid..u64::min(next_subtree.end, tree_size));
38 } else {
39 }
42 } else {
43 result.push(next_subtree_start..mid);
45 }
46 current_subtree = next_subtree;
47 }
48 result
49}
50
51#[test]
52fn test_inclusion_proof_parts() {
53 assert_eq!(inclusion_proof_parts(1, 0), vec![]);
54 assert_eq!(inclusion_proof_parts(2, 0), vec![1..2]);
55 assert_eq!(inclusion_proof_parts(2, 1), vec![0..1]);
56 assert_eq!(inclusion_proof_parts(3, 1), vec![0..1, 2..3]);
57 assert_eq!(inclusion_proof_parts(5, 0), vec![1..2, 2..4, 4..5]);
58 assert_eq!(inclusion_proof_parts(5, 4), vec![0..4]);
59}
60
61pub async fn check_inclusion_proof(
65 client: &reqwest::Client,
66 base_url: &reqwest::Url,
67 tree_size: u64,
68 tree_hash: &[u8; 32],
69 leaf_hash: &[u8; 32],
70) -> Result<u64, Error> {
71 let res = fetch_inclusion_proof(client, base_url, tree_size, leaf_hash).await?;
72 if &res.calculated_tree_hash != tree_hash {
73 return Err(Error::InvalidInclusionProof {
74 tree_size,
75 leaf_index: res.leaf_index,
76 desc: format!(
77 "Expected the proof to yield a tree hash of {}, but instead got {}.",
78 u8_to_hex(tree_hash),
79 u8_to_hex(&res.calculated_tree_hash)
80 ),
81 });
82 }
83 Ok(res.leaf_index)
84}
85
86pub struct FetchInclusionProofResult {
87 pub calculated_tree_hash: [u8; 32],
88 pub leaf_index: u64,
89}
90
91pub async fn fetch_inclusion_proof(
92 client: &reqwest::Client,
93 base_url: &reqwest::Url,
94 tree_size: u64,
95 leaf_hash: &[u8; 32],
96) -> Result<FetchInclusionProofResult, Error> {
97 let json: AuditProof = get_json(
98 client,
99 base_url,
100 &format!(
101 "ct/v1/get-proof-by-hash?{}",
102 serde_urlencoded::to_string(&[
103 ("hash", base64::encode(leaf_hash)),
104 ("tree_size", tree_size.to_string())
105 ])
106 .map_err(|e| Error::Unknown(format!("{}", e)))?
107 ),
108 )
109 .await?;
110 let leaf_index = json.leaf_index;
111 if json.leaf_index >= tree_size {
112 return Err(Error::InvalidInclusionProof {
113 tree_size,
114 leaf_index,
115 desc: "returned leaf_index >= tree_size.".to_owned(),
116 });
117 }
118 let proof_parts = inclusion_proof_parts(tree_size, leaf_index);
119 if proof_parts.len() != json.audit_path.len() {
120 return Err(Error::InvalidInclusionProof {
121 tree_size,
122 leaf_index,
123 desc: format!(
124 "Expected proof with {} parts, got {}.",
125 proof_parts.len(),
126 json.audit_path.len()
127 ),
128 });
129 }
130 let mut provided_proof: Vec<[u8; 32]> = Vec::with_capacity(proof_parts.len());
131 for i in 0..proof_parts.len() {
132 let hash = base64::decode(&json.audit_path[i]).map_err(|e| {
133 Error::MalformedResponseBody(format!("Unable to decode base64 in proof: {}", e))
134 })?;
135 if hash.len() != 32 {
136 return Err(Error::MalformedResponseBody(
137 "One or more component in the proof does not has length 32.".to_owned(),
138 ));
139 }
140 provided_proof.push(hash[..].try_into().unwrap());
141 }
142 let got_hash = hash_inclusion_proof(&proof_parts, &provided_proof, leaf_hash, leaf_index);
143 Ok(FetchInclusionProofResult {
144 calculated_tree_hash: got_hash,
145 leaf_index,
146 })
147}
148
149pub fn hash_inclusion_proof(
153 proof_parts: &[Range<u64>],
154 provided_proof: &[[u8; 32]],
155 leaf_hash: &[u8; 32],
156 leaf_index: u64,
157) -> [u8; 32] {
158 let mut current_hash = *leaf_hash;
159 let mut current_subtree = leaf_index..leaf_index + 1;
160 assert_eq!(proof_parts.len(), provided_proof.len());
161 for (proof_part, proof_hash) in proof_parts.iter().zip(provided_proof.iter()) {
162 if proof_part.start == current_subtree.end {
163 current_hash = combine_tree_hash(¤t_hash, proof_hash);
167 current_subtree = current_subtree.start..proof_part.end;
168 } else if proof_part.end == current_subtree.start {
169 current_hash = combine_tree_hash(proof_hash, ¤t_hash);
171 current_subtree = proof_part.start..current_subtree.end;
172 } else {
173 unreachable!()
174 }
175 }
176 current_hash
177}
178
179#[test]
180fn test() {}