1use core::convert::TryInto;
2use js_sys::{Array, JsString, Uint8Array};
3use wasm_bindgen::__rt::std::collections::btree_map::BTreeMap;
4use wasm_bindgen::prelude::*;
6
7use ark_bn254::Fr as Bn254Fr;
8use ark_crypto_primitives::Error;
9use ark_ff::{BigInteger, PrimeField};
10use arkworks_setups::utxo::Utxo;
11use arkworks_setups::{Curve as ArkCurve, VAnchorProver};
12use rand::rngs::OsRng;
13
14use crate::note::JsNote;
15use crate::types::{Backend, Curve, HashFunction, NoteProtocol, NoteVersion, OpStatusCode, OperationError};
16use crate::utxo::JsUtxo;
17use crate::{VAnchorR1CSProverBn254_30_2_16_2, VAnchorR1CSProverBn254_30_2_2_2, DEFAULT_LEAF};
18
19const SUPPORTED_INPUT_COUNT: [usize; 2] = [2, 16];
20const SUPPORTED_VANCHOR_COUNT: [usize; 3] = [2, 8, 16];
21
22#[wasm_bindgen]
23#[derive(Debug, Clone)]
24pub struct VAnchorProof {
25 #[wasm_bindgen(skip)]
26 pub proof: Vec<u8>,
27 #[wasm_bindgen(skip)]
28 pub public_inputs: Vec<Vec<u8>>,
29 #[wasm_bindgen(skip)]
30 pub output_notes: Vec<JsNote>,
31 #[wasm_bindgen(skip)]
32 pub input_utxos: Vec<JsUtxo>,
33 #[wasm_bindgen(skip)]
34 pub public_amount: [u8; 32],
35}
36#[wasm_bindgen]
37impl VAnchorProof {
38 #[wasm_bindgen(getter)]
39 #[wasm_bindgen(js_name = publicInputs)]
40 pub fn public_inputs_raw(&self) -> Array {
41 let inputs: Array = self
42 .public_inputs
43 .iter()
44 .map(|x| JsString::from(hex::encode(x)))
45 .collect();
46 inputs
47 }
48
49 #[wasm_bindgen(getter)]
50 #[wasm_bindgen(js_name = outputNotes)]
51 pub fn output_notes(&self) -> Array {
52 let inputs: Array = self.output_notes.clone().into_iter().map(JsValue::from).collect();
53 inputs
54 }
55
56 #[wasm_bindgen(getter)]
57 #[wasm_bindgen(js_name = inputUtxos)]
58 pub fn inputs_utxos(&self) -> Array {
59 let inputs: Array = self.input_utxos.clone().into_iter().map(JsValue::from).collect();
60 inputs
61 }
62
63 #[wasm_bindgen(getter)]
64 pub fn proof(&self) -> JsString {
65 JsString::from(hex::encode(&self.proof))
66 }
67
68 #[wasm_bindgen(js_name = publicAmount)]
69 #[wasm_bindgen(getter)]
70 pub fn public_amount(&self) -> Uint8Array {
71 Uint8Array::from(self.public_amount.to_vec().as_slice())
72 }
73}
74
75#[derive(Debug, Clone)]
76pub struct VAnchorProofPayload {
77 pub exponentiation: i8,
78 pub width: usize,
79 pub curve: Curve,
80 pub backend: Backend,
81 pub pk: Vec<u8>,
82 pub leaves: BTreeMap<u64, Vec<Vec<u8>>>,
83 pub ext_data_hash: Vec<u8>,
84 pub roots: Vec<Vec<u8>>,
87 pub secret: Vec<JsUtxo>,
89 pub indices: Vec<u64>,
91 pub chain_id: u64,
93 pub public_amount: i128,
95 pub output_utxos: [JsUtxo; 2],
97}
98
99#[derive(Debug, Clone, Default)]
100pub struct VAnchorProofInput {
101 pub exponentiation: Option<i8>,
102 pub width: Option<usize>,
103 pub curve: Option<Curve>,
104 pub backend: Option<Backend>,
105 pub pk: Option<Vec<u8>>,
106 pub leaves: Option<BTreeMap<u64, Vec<Vec<u8>>>>,
107 pub ext_data_hash: Option<Vec<u8>>,
108 pub roots: Option<Vec<Vec<u8>>>,
111 pub secret: Option<Vec<JsUtxo>>,
113 pub indices: Option<Vec<u64>>,
115 pub chain_id: Option<u128>,
117 pub public_amount: Option<i128>,
119 pub output_utxos: Option<[JsUtxo; 2]>,
121}
122
123impl VAnchorProofInput {
124 pub fn build(self) -> Result<VAnchorProofPayload, OperationError> {
125 let pk = self.pk.ok_or(OpStatusCode::InvalidProvingKey)?;
126 let secret = self.secret.ok_or(OpStatusCode::InvalidNoteSecrets)?;
127 let leaves = self.leaves.ok_or(OpStatusCode::InvalidLeaves)?;
128 let ext_data_hash = self.ext_data_hash.ok_or(OpStatusCode::InvalidExtDataHash)?;
129 let roots = self.roots.ok_or(OpStatusCode::InvalidRoots)?;
130 let chain_id = self.chain_id.ok_or(OpStatusCode::InvalidChainId)?;
131 let indices = self.indices.ok_or(OpStatusCode::InvalidIndices)?;
132 let public_amount = self.public_amount.ok_or(OpStatusCode::InvalidPublicAmount)?;
133 let output_utxos = self.output_utxos.ok_or(OpStatusCode::InvalidPublicAmount)?;
134
135 let exponentiation = self.exponentiation.unwrap_or(5);
136 let width = self.width.unwrap_or(3);
137 let curve = self.curve.unwrap_or(Curve::Bn254);
138 let backend = self.backend.unwrap_or(Backend::Arkworks);
139
140 let mut invalid_utxo_chain_id_indices = vec![];
145 let mut invalid_utxo_dublicate_nullifiers = vec![];
146 let utxos_chain_id = secret[0].clone().chain_id_raw();
147 secret.iter().enumerate().for_each(|(index, utxo)| {
149 if utxo.get_chain_id_raw() != utxos_chain_id {
150 invalid_utxo_chain_id_indices.push(index)
151 }
152 });
153 let non_default_utxo = secret
154 .iter()
155 .enumerate()
156 .filter(|(_, utxo)| {
157 utxo.get_amount_raw() != 0 && utxo.get_index().unwrap_or(0) != 0
159 })
160 .collect::<Vec<_>>();
161 non_default_utxo.iter().for_each(|(index, utxo)| {
162 let has_dublicate = non_default_utxo
163 .iter()
164 .find(|(root_index, root_utxo)| root_index != index && root_utxo.get_index() == utxo.get_index());
165 if has_dublicate.is_some() {
166 invalid_utxo_dublicate_nullifiers.push(index)
167 }
168 });
169 if !invalid_utxo_chain_id_indices.is_empty() || !invalid_utxo_dublicate_nullifiers.is_empty() {
170 let message = format!(
171 "Invalid UTXOs: utxo indices has invalid chain_id {:?}, non-default utxos with an duplicate index {:?}",
172 invalid_utxo_chain_id_indices, invalid_utxo_dublicate_nullifiers
173 );
174 let mut op: OperationError =
175 OperationError::new_with_message(OpStatusCode::InvalidProofParameters, message);
176 op.data = Some(format!(
177 "{{ duplicateIndices:{:?} , invalidChainId:{:?} }}",
178 invalid_utxo_chain_id_indices, invalid_utxo_dublicate_nullifiers
179 ));
180 return Err(op);
181 }
182
183 let mut in_amount = public_amount;
185 secret.iter().for_each(|utxo| {
186 let utxo_amount: i128 = utxo
187 .get_amount_raw()
188 .try_into()
189 .expect("Failed to convert utxo value to i128");
190 in_amount += utxo_amount
191 });
192 let mut out_amount = 0u128;
193 output_utxos
194 .iter()
195 .for_each(|output_config| out_amount += output_config.get_amount_raw());
196 let out_amount: i128 = out_amount
197 .try_into()
198 .expect("Failed to convert accumulated in amounts value to i128");
199 if out_amount != in_amount {
200 let message = format!(
201 "Output amount and input amount don't match input({}) != output({})",
202 in_amount, out_amount
203 );
204 let mut oe = OperationError::new_with_message(OpStatusCode::InvalidProofParameters, message);
205 oe.data = Some(format!("{{ inputAmount:{} ,outputAmount:{}}}", in_amount, out_amount));
206 return Err(oe);
207 }
208 Ok(VAnchorProofPayload {
209 exponentiation,
210 width,
211 curve,
212 backend,
213 pk,
214 leaves,
215 ext_data_hash,
216 roots,
217 secret,
218 indices,
219 chain_id: chain_id.try_into().unwrap(),
220 public_amount,
221 output_utxos,
222 })
223 }
224}
225
226fn get_output_notes(
227 anchor_proof_input: &VAnchorProofPayload,
228 output_config: [JsUtxo; 2],
229) -> Result<[JsNote; 2], OperationError> {
230 output_config
231 .into_iter()
232 .map(|c| {
233 let mut note = JsNote {
234 scheme: NoteProtocol::VAnchor.to_string(),
235 protocol: NoteProtocol::VAnchor,
236 version: NoteVersion::V1,
237 source_chain_id: c.chain_id_raw().to_string(),
238 target_chain_id: c.chain_id_raw().to_string(),
239 source_identifying_data: "".to_string(),
240 target_identifying_data: "".to_string(),
241 secrets: vec![],
243 curve: Some(anchor_proof_input.curve),
244 exponentiation: Some(anchor_proof_input.exponentiation),
245 width: Some(anchor_proof_input.width),
246 token_symbol: Some("ANY".to_string()),
248 amount: Some(c.get_amount_raw().to_string()),
249 denomination: None,
250 backend: Some(anchor_proof_input.backend),
251 hash_function: Some(HashFunction::Poseidon),
252 index: None,
253 };
254 note.update_vanchor_utxo(c)?;
255 Ok(note)
256 })
257 .collect::<Result<Vec<JsNote>, OperationError>>()?
258 .try_into()
259 .map_err(|_| {
260 OperationError::new_with_message(
261 OpStatusCode::Unknown,
262 "proof::vanchor: Failed to generate the notes".to_string(),
263 )
264 })
265}
266
267pub fn create_proof(vanchor_proof_input: VAnchorProofPayload, rng: &mut OsRng) -> Result<VAnchorProof, OperationError> {
268 let VAnchorProofPayload {
269 public_amount,
270 backend,
271 curve,
272 width,
273 secret,
274 indices,
275 leaves,
276 exponentiation,
277 roots,
278 pk,
279 chain_id,
280 output_utxos,
281 ext_data_hash,
282 } = vanchor_proof_input.clone();
283 let public_amount_bytes = Bn254Fr::from(public_amount)
284 .into_repr()
285 .to_bytes_le()
286 .try_into()
287 .expect("proof::vanchor: Failed to wrap public amount to bytes");
288 let in_utxos: Vec<JsUtxo> = if SUPPORTED_INPUT_COUNT.contains(&secret.len()) {
290 secret
291 } else {
292 let message = format!(
293 "proof::vanchor: Input set has {} UTXOs while the supported set length should be one of {:?}",
294 &secret.len(),
295 &SUPPORTED_INPUT_COUNT,
296 );
297 return Err(OperationError::new_with_message(
298 OpStatusCode::InvalidProofParameters,
299 message,
300 ));
301 };
302 if indices.len() != in_utxos.len() {
304 let message = format!(
305 "proof::vanchor: Indices Array don't match with the Input size , supplied {} indices while there are {} utxos in the input ",
306 indices.len(),
307 in_utxos.len(),
308 );
309 return Err(OperationError::new_with_message(
310 OpStatusCode::InvalidProofParameters,
311 message,
312 ));
313 }
314 if !SUPPORTED_VANCHOR_COUNT.contains(&roots.len()) {
316 let message = format!(
317 "proof::vanchor: Input set has {} roots while the supported set length should be one of {:?}",
318 &roots.len(),
319 &SUPPORTED_VANCHOR_COUNT,
320 );
321 return Err(OperationError::new_with_message(
322 OpStatusCode::InvalidProofParameters,
323 message,
324 ));
325 };
326 let output_notes = get_output_notes(&vanchor_proof_input, output_utxos.clone())?.to_vec();
328 let proof = match (backend, curve, roots.len()) {
329 (Backend::Arkworks, Curve::Bn254, 2) => {
330 let utxos_out = output_utxos
331 .iter()
332 .map(|js_utx| js_utx.get_bn254_utxo())
333 .collect::<Result<Vec<_>, OpStatusCode>>()?
334 .try_into()
335 .map_err(|_| OpStatusCode::InvalidProofParameters)?;
336 match (exponentiation, width, in_utxos.len()) {
337 (5, 5, 2) => {
338 let utxos_in: [Utxo<Bn254Fr>; 2] = [in_utxos[0].get_bn254_utxo()?, in_utxos[1].get_bn254_utxo()?];
339 let indices = indices.try_into().map_err(|_| OpStatusCode::InvalidIndices)?;
340 let roots = roots.try_into().map_err(|_| OpStatusCode::InvalidRoots)?;
341 VAnchorR1CSProverBn254_30_2_2_2::create_proof(
342 ArkCurve::Bn254,
343 chain_id,
344 public_amount,
345 ext_data_hash,
346 roots,
347 indices,
348 leaves,
349 utxos_in,
350 utxos_out,
351 pk,
352 DEFAULT_LEAF,
353 rng,
354 )
355 }
356 (5, 5, 16) => {
357 let in_utxos = in_utxos
358 .iter()
359 .map(|utxo| utxo.get_bn254_utxo())
360 .collect::<Result<Vec<_>, _>>()?;
361 let utxos_slice = in_utxos.try_into().map_err(|_| OpStatusCode::InvalidNoteSecrets)?;
362 let indices = indices.try_into().map_err(|_| OpStatusCode::InvalidIndices)?;
363 let roots = roots.try_into().map_err(|_| OpStatusCode::InvalidRoots)?;
364
365 VAnchorR1CSProverBn254_30_2_16_2::create_proof(
366 ArkCurve::Bn254,
367 chain_id,
368 public_amount,
369 ext_data_hash,
370 roots,
371 indices,
372 leaves,
373 utxos_slice,
374 utxos_out,
375 pk,
376 DEFAULT_LEAF,
377 rng,
378 )
379 }
380 _ => {
381 let message = format!(
382 "proof::vanchor: The proofing setup for backend {} curve {} width {} exp {} input size {} isn't implemented!",
383 backend,
384 curve,
385 width,
386 exponentiation,
387 &in_utxos.len(),
388 );
389 Err(Error::from(message))
390 }
391 }
392 }
393 _ => {
394 let message = format!(
395 "proof::vanchor: The proofing setup for backend {} curve {} width {} exp {} input size {} isn't implemented!",
396 backend,
397 curve,
398 width,
399 exponentiation,
400 &in_utxos.len(),
401 );
402 Err(Error::from(message))
403 }
404 }
405 .map_err(|e| {
406 let message = format!("proof::vanchor: {}", e);
407 OperationError::new_with_message(OpStatusCode::InvalidProofParameters, message)
408 })?;
409 Ok(VAnchorProof {
410 proof: proof.proof,
411 public_inputs: proof.public_inputs_raw,
412 output_notes: output_notes.to_vec(),
413 input_utxos: in_utxos,
414 public_amount: public_amount_bytes,
415 })
416}