1use crate::{
2 api::{http_get_string, ApiClient},
3 error::Error,
4};
5use ruint::aliases::U256;
6use serde::{Deserialize, Serialize};
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct LookupCfg {
13 pub bucket_url: String,
15 pub api_key: String,
17 pub cap_url: String,
19 pub subtree_height: usize,
21 pub cap_height: usize,
23 pub tree_height: usize,
25}
26
27impl LookupCfg {
28 pub async fn from_url(url: &str) -> Result<LookupCfg, Error> {
30 let val = http_get_string(url, "").await?;
31 let cfg: LookupCfg = serde_json::from_str(&val)?;
32 Ok(cfg)
33 }
34
35 pub async fn from_json(json: &str) -> Result<LookupCfg, Error> {
37 let cfg: LookupCfg = serde_json::from_str(&json)?;
38 Ok(cfg)
39 }
40}
41
42#[derive(Debug, Clone, Serialize, Deserialize)]
44pub struct ProofStep {
45 pub value: String,
47 pub pos: usize,
50}
51
52impl ProofStep {
53 pub fn u256(&self) -> U256 {
55 to_u256(&self.value)
56 }
57}
58
59fn to_u256(value: &str) -> U256 {
61 U256::from_be_bytes::<32>(hex::decode(&value[2..]).unwrap().try_into().unwrap())
62}
63
64fn get_subtree_indices(lookup_cfg: &LookupCfg, identity_idx: usize) -> Vec<String> {
66 let mut keys_to_fetch = Vec::new();
67 let mut cur_level = lookup_cfg.tree_height - lookup_cfg.subtree_height;
68 while cur_level >= lookup_cfg.cap_height - 1 {
69 let idx_within_level = identity_idx >> (lookup_cfg.tree_height - 1 - cur_level);
70 let key = format!("{}-{}", cur_level, idx_within_level);
71 keys_to_fetch.push(key);
72
73 if cur_level >= lookup_cfg.subtree_height {
74 cur_level -= lookup_cfg.subtree_height - 1;
75 } else {
76 break;
77 }
78 }
79
80 keys_to_fetch
81}
82
83fn get_subproof(tree: &[String], tree_height: usize, idx: usize) -> Vec<ProofStep> {
85 let mut out = Vec::new();
86 for level in 1..tree_height {
87 let mut idx_within_level = idx >> (tree_height - 1 - level);
88 idx_within_level ^= 1; let tree_idx = (1 << level) - 1 + idx_within_level;
91 out.push(ProofStep {
92 value: tree[tree_idx].clone(),
93 pos: idx_within_level & 1,
94 });
95 }
96 out.reverse();
97 return out;
98}
99
100fn construct_merkle_proof(
102 lookup_cfg: &LookupCfg,
103 identity_idx: usize,
104 subtrees: &[Vec<String>],
105) -> Vec<ProofStep> {
106 let mut cur_level = lookup_cfg.tree_height - lookup_cfg.subtree_height;
107 let mut outer_idx = 0;
108
109 let mut proof = Vec::new();
110 while cur_level >= lookup_cfg.cap_height - 1 {
111 let subtree = &subtrees[outer_idx];
112 outer_idx += 1;
113 let idx_within_level = identity_idx >> (lookup_cfg.tree_height - 1 - cur_level);
114 let idx_within_subtree = (identity_idx
115 >> (lookup_cfg.tree_height - 1 - (cur_level + lookup_cfg.subtree_height - 1)))
116 - idx_within_level * (1 << (lookup_cfg.subtree_height - 1));
117
118 let proof_part = get_subproof(subtree, lookup_cfg.subtree_height, idx_within_subtree);
119 proof.extend(proof_part.into_iter());
120
121 if cur_level >= lookup_cfg.subtree_height {
122 cur_level -= lookup_cfg.subtree_height - 1;
123 } else {
124 break;
125 }
126 }
127
128 proof
129}
130
131async fn get_cap(url: &str) -> Result<Vec<String>, Error> {
133 let val = http_get_string(url, "").await?;
134 let cap: Vec<String> = serde_json::from_str(&val)?;
135 Ok(cap)
136}
137
138fn get_idx_within_cap(identity_idx: usize, tree_height: usize, cap_height: usize) -> usize {
140 let idx_within_level = identity_idx >> ((tree_height - 1) - (cap_height - 1));
141 idx_within_level
142}
143
144async fn fetch_merkle_proof_at_idx(
146 client: &mut ApiClient,
147 lookup_cfg: &LookupCfg,
148 identity_idx: usize,
149) -> Result<Vec<ProofStep>, Error> {
150 let cap = get_cap(&lookup_cfg.cap_url).await?;
151 let subtrees_to_query = get_subtree_indices(lookup_cfg, identity_idx);
152 let subtrees = client.private_read(&subtrees_to_query).await?;
153 let mut subtrees_as_strs = Vec::new();
154 for s in subtrees {
155 let s: Vec<String> = serde_json::from_slice(&s)?;
156 subtrees_as_strs.push(s);
157 }
158 let mut proof = construct_merkle_proof(lookup_cfg, identity_idx, &subtrees_as_strs);
159 let cap_proof_part = get_subproof(
160 &cap,
161 lookup_cfg.cap_height,
162 get_idx_within_cap(identity_idx, lookup_cfg.tree_height, lookup_cfg.cap_height),
163 );
164 proof.extend(cap_proof_part.into_iter());
165
166 Ok(proof)
167}
168
169async fn fetch_idx_for_identity(
171 client: &mut ApiClient,
172 identity_commitment: &str,
173) -> Result<usize, Error> {
174 let result = client
175 .private_read(&[identity_commitment.to_owned()])
176 .await?;
177 let index: usize = serde_json::from_slice(&result[0])?;
178
179 Ok(index)
180}
181
182async fn private_fetch_merkle_proof_with_cfg(
184 identity_commitment: &str,
185 lookup_cfg: &LookupCfg,
186) -> Result<Vec<ProofStep>, Error> {
187 let mut owned_ic = identity_commitment.to_owned();
188 if !owned_ic.starts_with("0x") {
189 owned_ic = format!("0x{}", identity_commitment);
190 }
191 owned_ic = owned_ic.to_lowercase();
192
193 let mut client = ApiClient::new(&lookup_cfg.bucket_url, &lookup_cfg.api_key).await?;
194 client.setup().await?;
195
196 let index = fetch_idx_for_identity(&mut client, &owned_ic).await?;
197 let proof = fetch_merkle_proof_at_idx(&mut client, lookup_cfg, index).await?;
198 Ok(proof)
199}
200
201pub async fn private_fetch_merkle_proof(
207 identity_commitment: &str,
208 lookup_cfg_url: &str,
209) -> Result<Vec<ProofStep>, Error> {
210 let lookup_cfg = LookupCfg::from_url(lookup_cfg_url).await?;
211 private_fetch_merkle_proof_with_cfg(identity_commitment, &lookup_cfg).await
212}
213
214#[cfg(test)]
215mod tests {
216 use super::*;
217
218 use semaphore::poseidon;
219
220 fn to_str(value: &U256) -> String {
221 format!("0x{}", hex::encode(value.to_be_bytes::<32>()))
222 }
223
224 fn verify_proof(input: &str, proof: &[ProofStep], root: &str) {
225 let mut cur_hash = to_u256(&input);
226 for step in proof.iter() {
227 let step_hash = step.u256();
228
229 let new_hash = if step.pos == 0 {
230 poseidon::hash2(step_hash, cur_hash)
231 } else {
232 poseidon::hash2(cur_hash, step_hash)
233 };
234
235 cur_hash = new_hash;
236 }
237 assert_eq!(to_str(&cur_hash), root);
238 }
239
240 #[test]
241 fn proof_works() {
242 let sample_proof = vec![
243 ProofStep {
244 value: "0x0000000000000000000000000000000000000000000000000000000000000000"
245 .to_string(),
246 pos: 1,
247 },
248 ProofStep {
249 value: "0x1df013c70209502f348ea55e649fd86687163959177e3c64eb81101982d46e05"
250 .to_string(),
251 pos: 1,
252 },
253 ProofStep {
254 value: "0x1f4a2f66f412222c3b1d6c5ade414a3d8e4b2ebcd4b7500f88ee8284914d3aa3"
255 .to_string(),
256 pos: 1,
257 },
258 ProofStep {
259 value: "0x16fc91b5ffdf1e4be6b6c8ba467017e605aaa4edf68c5a0419876fb12f558fc4"
260 .to_string(),
261 pos: 1,
262 },
263 ProofStep {
264 value: "0x04650054c0e366b4fd06d65b5fe0b96f3e7e9169f50c176fa25dd50e7c52852f"
265 .to_string(),
266 pos: 1,
267 },
268 ProofStep {
269 value: "0x1cebe7a720c3454e5f2e9780f9da6f46bc82db4345bea44f2a816f6d988a6d7e"
270 .to_string(),
271 pos: 1,
272 },
273 ProofStep {
274 value: "0x101af632d69b4d1a993b04a53775cf7f12d65cc751355c3bb5bb540548c8de47"
275 .to_string(),
276 pos: 1,
277 },
278 ProofStep {
279 value: "0x2187d3e9d93033adb4f250a6a33da9b24223f27c3dd2962082b62cd04c01a6e2"
280 .to_string(),
281 pos: 1,
282 },
283 ProofStep {
284 value: "0x1c1a0b78b55b21366a680c8f1c412fbe93864a286195de2ae069e229b4f35874"
285 .to_string(),
286 pos: 1,
287 },
288 ProofStep {
289 value: "0x19346ce3cc2e5d46c37aa750f1fbd2363d8546b27ceec21903a7b3dc180cabbf"
290 .to_string(),
291 pos: 1,
292 },
293 ProofStep {
294 value: "0x0ffb8ffc70abd907f6548d7043654334dcac5675450b79a9a949b6d68482ce53"
295 .to_string(),
296 pos: 1,
297 },
298 ProofStep {
299 value: "0x133028e8db184a9ec1d21cf5617909b6ccf2002ea95bf1f5b70532fc9f217d12"
300 .to_string(),
301 pos: 1,
302 },
303 ProofStep {
304 value: "0x21e5840d5e45d43dac5b39ca1620979655c31732be490ae3180baa9d94603ef3"
305 .to_string(),
306 pos: 1,
307 },
308 ProofStep {
309 value: "0x2d63fd584ac6c7ecc9fde3eab063f40f789ddae1336b256487b4f0f42403250a"
310 .to_string(),
311 pos: 1,
312 },
313 ProofStep {
314 value: "0x23285ed2421e0cd54cf7722ba0478e5386bfba97054f2c00b7ee8ff1e5ae0224"
315 .to_string(),
316 pos: 1,
317 },
318 ProofStep {
319 value: "0x21fc8d14983eab9b40668e78f816a785568973e2f7c5c70a921398abb804377b"
320 .to_string(),
321 pos: 1,
322 },
323 ProofStep {
324 value: "0x11117446426ca7b68db951a0e90b75fa3895dfdb1e18d76dc009e1446be9bddb"
325 .to_string(),
326 pos: 1,
327 },
328 ProofStep {
329 value: "0x10a801cdd8b09b93c1c4763547416bf3a6a1b501af87f2e222e00689c82d3a6d"
330 .to_string(),
331 pos: 1,
332 },
333 ProofStep {
334 value: "0x0ea484bbc862f222255634d1859c1c3e3602571e958b0e3acf29f1634f3b45a9"
335 .to_string(),
336 pos: 1,
337 },
338 ProofStep {
339 value: "0x1a8c640e78d2e23c36fca18cb69d1ed36ccfa691a26674f34c4077080cbbb16f"
340 .to_string(),
341 pos: 1,
342 },
343 ];
344
345 let root = "0x205aff5d8fc468b111f6fba374f5ba3bdaf02b37a741fd675fac334350f19880";
346 verify_proof(
347 "0x0000000000000000000000000000000000000000000000000000000000000000",
348 &sample_proof,
349 root,
350 );
351 }
352}