wasm_utils/proof/
vanchor.rs

1use core::convert::TryInto;
2use js_sys::{Array, JsString, Uint8Array};
3use wasm_bindgen::__rt::std::collections::btree_map::BTreeMap;
4// https://github.com/rustwasm/wasm-bindgen/issues/2231#issuecomment-656293288
5use 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	/// get roots for linkable tree
85	/// Available set can be of length 2 , 16 , 32
86	pub roots: Vec<Vec<u8>>,
87	// Notes for UTXOs
88	pub secret: Vec<JsUtxo>,
89	// Leaf indices
90	pub indices: Vec<u64>,
91	// Chain Id
92	pub chain_id: u64,
93	// Public amount
94	pub public_amount: i128,
95	// utxo config
96	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	/// get roots for linkable tree
109	/// Available set can be of length 2 , 16 , 32
110	pub roots: Option<Vec<Vec<u8>>>,
111	// Notes for UTXOs
112	pub secret: Option<Vec<JsUtxo>>,
113	// Leaf indices
114	pub indices: Option<Vec<u64>>,
115	// Chain Id
116	pub chain_id: Option<u128>,
117	// Public amount
118	pub public_amount: Option<i128>,
119	// ouput utxos
120	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		// Input UTXO should have the same chain_id
141		// For default UTXOS the amount and the index should be `0`
142		// Duplicate indices is ONLY allowed for the default UTXOs
143		// chain_id of the first item in the list
144		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		// validate the all inputs share the same chain_id
148		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				// filter for non-default utxos
158				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		// validate amounts
184		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				// Are set with the utxo mutation
242				secrets: vec![],
243				curve: Some(anchor_proof_input.curve),
244				exponentiation: Some(anchor_proof_input.exponentiation),
245				width: Some(anchor_proof_input.width),
246				// TODO : fix the token_symbol
247				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	// Insure UTXO set has the required/supported input count
289	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	// Insure the length of the indices
303	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	// Insure the Anchor count is supported
315	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	// Initialize the output notes vec
327	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}