wasm_utils/note/
mod.rs

1use core::fmt;
2use core::str::FromStr;
3
4use arkworks_setups::common::Leaf;
5use js_sys::{JsString, Uint8Array};
6use rand::rngs::OsRng;
7use wasm_bindgen::prelude::*;
8use wasm_bindgen::JsValue;
9
10use crate::types::{
11	Backend, Curve, HashFunction, NoteProtocol, NoteVersion, OpStatusCode, OperationError, Protocol, Version,
12	WasmCurve, BE, HF,
13};
14use crate::utxo::JsUtxo;
15
16pub mod mixer;
17pub mod vanchor;
18pub mod versioning;
19
20#[allow(unused_macros)]
21macro_rules! console_log {
22	// Note that this is using the `log` function imported above during
23	// `bare_bones`
24	($($t:tt)*) => (crate::types::log(&format_args!($($t)*).to_string()))
25}
26pub enum JsLeafInner {
27	Mixer(Leaf),
28	VAnchor(JsUtxo),
29}
30
31impl Clone for JsLeafInner {
32	fn clone(&self) -> Self {
33		match self {
34			JsLeafInner::Mixer(leaf) => JsLeafInner::Mixer(Leaf {
35				chain_id_bytes: leaf.chain_id_bytes.clone(),
36				secret_bytes: leaf.secret_bytes.clone(),
37				nullifier_bytes: leaf.nullifier_bytes.clone(),
38				leaf_bytes: leaf.leaf_bytes.clone(),
39				nullifier_hash_bytes: leaf.nullifier_hash_bytes.clone(),
40			}),
41			JsLeafInner::VAnchor(utxo) => JsLeafInner::VAnchor(utxo.clone()),
42		}
43	}
44}
45#[wasm_bindgen]
46pub struct JsLeaf {
47	#[wasm_bindgen(skip)]
48	pub inner: JsLeafInner,
49}
50impl JsLeaf {
51	pub fn mixer_leaf(&self) -> Result<Leaf, OperationError> {
52		match self.inner.clone() {
53			JsLeafInner::Mixer(leaf) => Ok(leaf),
54			_ => Err(OpStatusCode::InvalidNoteProtocol.into()),
55		}
56	}
57
58	pub fn vanchor_leaf(&self) -> Result<JsUtxo, OperationError> {
59		match self.inner.clone() {
60			JsLeafInner::VAnchor(leaf) => Ok(leaf),
61			_ => Err(OpStatusCode::InvalidNoteProtocol.into()),
62		}
63	}
64}
65#[wasm_bindgen]
66impl JsLeaf {
67	#[wasm_bindgen(getter)]
68	pub fn protocol(&self) -> Protocol {
69		let protocol = match self.inner {
70			JsLeafInner::Mixer(_) => "mixer",
71			JsLeafInner::VAnchor(_) => "vanchor",
72		};
73
74		JsValue::from(protocol).into()
75	}
76
77	#[wasm_bindgen(getter)]
78	pub fn commitment(&self) -> Uint8Array {
79		match &self.inner {
80			JsLeafInner::Mixer(leaf) => Uint8Array::from(leaf.leaf_bytes.as_slice()),
81			JsLeafInner::VAnchor(vanchor_leaf) => vanchor_leaf.commitment(),
82		}
83	}
84}
85impl JsNote {
86	/// Deseralize note from a string
87	pub fn deserialize(note: &str) -> Result<Self, OperationError> {
88		note.parse().map_err(Into::into)
89	}
90
91	pub fn mutate_index(&mut self, index: u64) -> Result<(), OperationError> {
92		match self.protocol {
93			NoteProtocol::VAnchor => {}
94			_ => {
95				let message = "Index secret can be set only for VAnchor".to_string();
96				let oe = OperationError::new_with_message(OpStatusCode::InvalidNoteProtocol, message);
97				return Err(oe);
98			}
99		}
100
101		self.index = Some(index);
102		Ok(())
103	}
104
105	pub fn get_leaf_and_nullifier(&self) -> Result<JsLeaf, OperationError> {
106		match self.protocol {
107			NoteProtocol::Mixer => {
108				let raw = match self.version {
109					NoteVersion::V1 => {
110						let mut raw = Vec::new();
111						raw.extend_from_slice(&self.secrets[0][..]);
112						raw.extend_from_slice(&self.secrets[1][..]);
113						raw
114					}
115				};
116
117				let mixer_leaf = mixer::get_leaf_with_private_raw(
118					self.curve.unwrap_or(Curve::Bn254),
119					self.width.unwrap_or(5),
120					self.exponentiation.unwrap_or(5),
121					&raw,
122				)?;
123
124				Ok(JsLeaf {
125					inner: JsLeafInner::Mixer(mixer_leaf),
126				})
127			}
128			NoteProtocol::VAnchor => match self.version {
129				NoteVersion::V1 => {
130					if self.secrets.len() == 4 {
131						let chain_id = self.secrets[0].clone();
132
133						let amount = self.secrets[1].clone();
134						let blinding = self.secrets[2].clone();
135						let secret_key = self.secrets[3].clone();
136						let index = self.index;
137
138						let mut amount_slice = [0u8; 16];
139						amount_slice.copy_from_slice(amount[..16].to_vec().as_slice());
140						let amount = u128::from_le_bytes(amount_slice);
141
142						let mut chain_id_slice = [0u8; 8];
143						chain_id_slice.copy_from_slice(chain_id[chain_id.len() - 8..].to_vec().as_slice());
144						let chain_id = u64::from_be_bytes(chain_id_slice);
145
146						let curve = self.curve.unwrap_or(Curve::Bn254);
147						let width = self.width.unwrap_or(2);
148						let exponentiation = self.exponentiation.unwrap_or(5);
149
150						let utxo = vanchor::get_leaf_with_private_raw(
151							curve,
152							width,
153							exponentiation,
154							Some(secret_key),
155							Some(blinding),
156							chain_id,
157							amount,
158							index,
159						)?;
160
161						Ok(JsLeaf {
162							inner: JsLeafInner::VAnchor(utxo),
163						})
164					} else {
165						let message = format!("Invalid secret format for protocol {}", self.protocol);
166						Err(OperationError::new_with_message(
167							OpStatusCode::InvalidNoteSecrets,
168							message,
169						))
170					}
171				}
172			},
173		}
174	}
175
176	pub fn get_js_utxo(&self) -> Result<JsUtxo, OperationError> {
177		let leaf = self.get_leaf_and_nullifier()?;
178		match leaf.inner {
179			JsLeafInner::VAnchor(utxo) => Ok(utxo),
180			_ => Err(OpStatusCode::InvalidNoteProtocol.into()),
181		}
182	}
183}
184
185impl fmt::Display for JsNote {
186	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
187		// Note URI scheme
188		let scheme = "webb://";
189		// Note URI authority
190		let authority = vec![self.version.to_string(), self.protocol.to_string()].join(":");
191		// Note URI chain IDs
192		let chain_ids = vec![self.source_chain_id.to_string(), self.target_chain_id.to_string()].join(":");
193		// Note URI chain identifying data (smart contracts, tree IDs)
194		let chain_identifying_data = vec![
195			self.source_identifying_data.to_string(),
196			self.target_identifying_data.to_string(),
197		]
198		.join(":");
199
200		let secrets = &self.secrets.iter().map(hex::encode).collect::<Vec<String>>().join(":");
201
202		// Note URI miscellaneous queries
203		#[allow(clippy::map_clone)]
204		let misc_values = vec![
205			if self.curve.is_some() {
206				format!("curve={}", self.curve.unwrap())
207			} else {
208				"".to_string()
209			},
210			if self.width.is_some() {
211				format!("width={}", self.width.unwrap())
212			} else {
213				"".to_string()
214			},
215			if self.exponentiation.is_some() {
216				format!("exp={}", self.exponentiation.unwrap())
217			} else {
218				"".to_string()
219			},
220			if self.hash_function.is_some() {
221				format!("hf={}", self.hash_function.unwrap())
222			} else {
223				"".to_string()
224			},
225			if self.backend.is_some() {
226				format!("backend={}", self.backend.unwrap())
227			} else {
228				"".to_string()
229			},
230			if self.token_symbol.is_some() {
231				format!("token={}", self.token_symbol.clone().unwrap())
232			} else {
233				"".to_string()
234			},
235			if self.denomination.is_some() {
236				format!("denom={}", self.denomination.unwrap())
237			} else {
238				"".to_string()
239			},
240			if self.amount.is_some() {
241				format!("amount={}", self.amount.clone().unwrap())
242			} else {
243				"".to_string()
244			},
245			if self.index.is_some() {
246				format!("index={}", self.index.unwrap())
247			} else {
248				"".to_string()
249			},
250		]
251		.iter()
252		.filter(|v| !v.is_empty())
253		.map(|v| v.clone())
254		.collect::<Vec<String>>()
255		.join("&");
256		// Note URI queries are prefixed with `?`
257		let misc = vec!["?".to_string(), misc_values].join("");
258
259		let parts: Vec<String> = vec![authority, chain_ids, chain_identifying_data, secrets.to_string(), misc];
260		// Join the parts with `/` and connect to the scheme as is
261		let note = vec![scheme.to_string(), parts.join("/")].join("");
262		write!(f, "{}", note)
263	}
264}
265
266impl FromStr for JsNote {
267	type Err = OperationError;
268
269	fn from_str(s: &str) -> Result<Self, Self::Err> {
270		versioning::v1::note_from_str(s)
271	}
272}
273
274#[wasm_bindgen]
275#[derive(Debug, Eq, PartialEq, Clone)]
276pub struct JsNote {
277	#[wasm_bindgen(skip)]
278	pub scheme: String,
279	#[wasm_bindgen(skip)]
280	pub protocol: NoteProtocol,
281	#[wasm_bindgen(skip)]
282	pub version: NoteVersion,
283	#[wasm_bindgen(skip)]
284	pub source_chain_id: String,
285	#[wasm_bindgen(skip)]
286	pub target_chain_id: String,
287	#[wasm_bindgen(skip)]
288	pub source_identifying_data: String,
289	#[wasm_bindgen(skip)]
290	pub target_identifying_data: String,
291
292	/// mixer related items
293	#[wasm_bindgen(skip)]
294	pub secrets: Vec<Vec<u8>>,
295
296	/// Misc - zkp related items
297	#[wasm_bindgen(skip)]
298	pub curve: Option<Curve>,
299	#[wasm_bindgen(skip)]
300	pub exponentiation: Option<i8>,
301	#[wasm_bindgen(skip)]
302	pub width: Option<usize>,
303
304	#[wasm_bindgen(skip)]
305	pub token_symbol: Option<String>,
306	#[wasm_bindgen(skip)]
307	pub amount: Option<String>,
308	#[wasm_bindgen(skip)]
309	pub denomination: Option<u8>,
310
311	#[wasm_bindgen(skip)]
312	pub backend: Option<Backend>,
313	#[wasm_bindgen(skip)]
314	pub hash_function: Option<HashFunction>,
315
316	#[wasm_bindgen(skip)]
317	pub index: Option<u64>,
318}
319
320#[wasm_bindgen]
321#[derive(Default)]
322pub struct JsNoteBuilder {
323	#[wasm_bindgen(skip)]
324	pub protocol: Option<NoteProtocol>,
325	#[wasm_bindgen(skip)]
326	pub version: Option<NoteVersion>,
327	#[wasm_bindgen(skip)]
328	pub source_chain_id: Option<String>,
329	#[wasm_bindgen(skip)]
330	pub target_chain_id: Option<String>,
331	#[wasm_bindgen(skip)]
332	pub source_identifying_data: Option<String>,
333	#[wasm_bindgen(skip)]
334	pub target_identifying_data: Option<String>,
335
336	#[wasm_bindgen(skip)]
337	pub amount: Option<String>,
338	#[wasm_bindgen(skip)]
339	pub denomination: Option<u8>,
340	#[wasm_bindgen(skip)]
341	pub secrets: Option<Vec<Vec<u8>>>,
342
343	// Misc - zkp related items
344	#[wasm_bindgen(skip)]
345	pub backend: Option<Backend>,
346	#[wasm_bindgen(skip)]
347	pub hash_function: Option<HashFunction>,
348	#[wasm_bindgen(skip)]
349	pub curve: Option<Curve>,
350	#[wasm_bindgen(skip)]
351	pub token_symbol: Option<String>,
352	#[wasm_bindgen(skip)]
353	pub exponentiation: Option<i8>,
354	#[wasm_bindgen(skip)]
355	pub width: Option<usize>,
356	// Utxo index
357	#[wasm_bindgen(skip)]
358	pub index: Option<u64>,
359	#[wasm_bindgen(skip)]
360	pub private_key: Option<Vec<u8>>,
361	#[wasm_bindgen(skip)]
362	pub blinding: Option<Vec<u8>>,
363}
364
365#[allow(clippy::unused_unit)]
366#[wasm_bindgen]
367impl JsNoteBuilder {
368	#[wasm_bindgen(constructor)]
369	pub fn new() -> Self {
370		Self::default()
371	}
372
373	pub fn protocol(&mut self, protocol: Protocol) -> Result<(), JsValue> {
374		let protocol: String = JsValue::from(&protocol)
375			.as_string()
376			.ok_or(OpStatusCode::InvalidNoteProtocol)?;
377		let note_protocol: NoteProtocol = protocol
378			.as_str()
379			.parse()
380			.map_err(|_| OpStatusCode::InvalidNoteProtocol)?;
381		self.protocol = Some(note_protocol);
382		Ok(())
383	}
384
385	pub fn version(&mut self, version: Version) -> Result<(), JsValue> {
386		let version: String = JsValue::from(&version)
387			.as_string()
388			.ok_or(OpStatusCode::InvalidNoteVersion)?;
389		let note_version: NoteVersion = version.as_str().parse().map_err(|_| OpStatusCode::InvalidNoteVersion)?;
390		self.version = Some(note_version);
391		Ok(())
392	}
393
394	#[wasm_bindgen(js_name = sourceChainId)]
395	pub fn source_chain_id(&mut self, source_chain_id: JsString) {
396		self.source_chain_id = Some(source_chain_id.into());
397	}
398
399	#[wasm_bindgen(js_name = targetChainId)]
400	pub fn target_chain_id(&mut self, target_chain_id: JsString) {
401		self.target_chain_id = Some(target_chain_id.into());
402	}
403
404	#[wasm_bindgen(js_name = sourceIdentifyingData)]
405	pub fn source_identifying_data(&mut self, source_identifying_data: JsString) {
406		self.source_identifying_data = Some(source_identifying_data.into());
407	}
408
409	#[wasm_bindgen(js_name = targetIdentifyingData)]
410	pub fn target_identifying_data(&mut self, target_identifying_data: JsString) {
411		self.target_identifying_data = Some(target_identifying_data.into());
412	}
413
414	pub fn backend(&mut self, backend: BE) {
415		let c: String = JsValue::from(&backend).as_string().unwrap();
416		let backend: Backend = c.parse().unwrap();
417		self.backend = Some(backend);
418	}
419
420	#[wasm_bindgen(js_name = hashFunction)]
421	pub fn hash_function(&mut self, hash_function: HF) -> Result<(), JsValue> {
422		let hash_function: String = JsValue::from(&hash_function)
423			.as_string()
424			.ok_or(OpStatusCode::InvalidHasFunction)?;
425		let hash_function: HashFunction = hash_function.parse().map_err(|_| OpStatusCode::InvalidHasFunction)?;
426		self.hash_function = Some(hash_function);
427		Ok(())
428	}
429
430	pub fn curve(&mut self, curve: WasmCurve) -> Result<(), JsValue> {
431		let curve: String = JsValue::from(&curve).as_string().ok_or(OpStatusCode::InvalidCurve)?;
432		let curve: Curve = curve.parse().map_err(|_| OpStatusCode::InvalidCurve)?;
433		self.curve = Some(curve);
434		Ok(())
435	}
436
437	#[wasm_bindgen(js_name = tokenSymbol)]
438	pub fn token_symbol(&mut self, token_symbol: JsString) {
439		self.token_symbol = Some(token_symbol.into());
440	}
441
442	pub fn amount(&mut self, amount: JsString) {
443		self.amount = Some(amount.into());
444	}
445
446	pub fn denomination(&mut self, denomination: JsString) -> Result<(), JsValue> {
447		let den: String = denomination.into();
448		let denomination = den.parse().map_err(|_| OpStatusCode::InvalidDenomination)?;
449		self.denomination = Some(denomination);
450		Ok(())
451	}
452
453	pub fn index(&mut self, index: JsString) -> Result<(), JsValue> {
454		let index: String = index.into();
455		let index: u64 = index.parse().map_err(|_| OpStatusCode::InvalidUTXOIndex)?;
456		self.index = Some(index);
457		Ok(())
458	}
459
460	pub fn exponentiation(&mut self, exponentiation: JsString) -> Result<(), JsValue> {
461		let exp: String = exponentiation.into();
462		let exponentiation = exp.parse().map_err(|_| OpStatusCode::InvalidExponentiation)?;
463		self.exponentiation = Some(exponentiation);
464		Ok(())
465	}
466
467	pub fn width(&mut self, width: JsString) -> Result<(), JsValue> {
468		let width: String = width.into();
469		let width = width.parse().map_err(|_| OpStatusCode::InvalidWidth)?;
470		self.width = Some(width);
471		Ok(())
472	}
473
474	#[wasm_bindgen(js_name = setSecrets)]
475	pub fn set_secrets(&mut self, secrets: JsString) -> Result<(), JsValue> {
476		let secrets_string: String = secrets.into();
477		let secrets_parts: Vec<String> = secrets_string.split(':').map(String::from).collect();
478		let secs = secrets_parts
479			.iter()
480			.map(|v| hex::decode(v.replace("0x", "")).unwrap_or_default())
481			.collect();
482		self.secrets = Some(secs);
483		Ok(())
484	}
485
486	#[wasm_bindgen(js_name = setPrivateKey)]
487	pub fn set_private_key(&mut self, private_key: Uint8Array) -> Result<(), JsValue> {
488		self.private_key = Some(private_key.to_vec());
489		Ok(())
490	}
491
492	#[wasm_bindgen(js_name = setBlinding)]
493	pub fn set_blinding(&mut self, blinding: Uint8Array) -> Result<(), JsValue> {
494		self.blinding = Some(blinding.to_vec());
495		Ok(())
496	}
497
498	pub fn build(self) -> Result<JsNote, JsValue> {
499		// Authority
500		let version = self.version.ok_or(OpStatusCode::InvalidNoteVersion)?;
501		let protocol = self.protocol.ok_or(OpStatusCode::InvalidNoteProtocol)?;
502
503		// Chain Ids
504		let source_chain_id = self.source_chain_id.ok_or(OpStatusCode::InvalidSourceChain)?;
505		let _: u64 = source_chain_id.parse().map_err(|_| OpStatusCode::InvalidSourceChain)?;
506		let target_chain_id = self.target_chain_id.ok_or(OpStatusCode::InvalidTargetChain)?;
507		let chain_id: u64 = target_chain_id.parse().map_err(|_| OpStatusCode::InvalidTargetChain)?;
508
509		// Chain identifying data
510		let source_identifying_data = self.source_identifying_data.ok_or_else(|| "".to_string())?;
511		let target_identifying_data = self.target_identifying_data.ok_or_else(|| "".to_string())?;
512
513		// Misc
514		let exponentiation = self.exponentiation;
515		let width = self.width;
516		let curve = self.curve;
517		let amount = self.amount.clone();
518		let index = self.index;
519		let backend = self.backend.unwrap_or(Backend::Arkworks);
520
521		if backend == Backend::Circom && self.secrets.is_none() {
522			let message = "Circom backend is supported when the secret value is supplied".to_string();
523			let operation_error = OperationError::new_with_message(OpStatusCode::UnsupportedBackend, message);
524			return Err(operation_error.into());
525		}
526
527		let secrets = match self.secrets {
528			None => match protocol {
529				NoteProtocol::Mixer => {
530					let secrets = mixer::generate_secrets(
531						exponentiation.unwrap_or(5),
532						width.unwrap_or(5),
533						curve.unwrap_or(Curve::Bn254),
534						&mut OsRng,
535					)?;
536
537					secrets.to_vec()
538				}
539				NoteProtocol::VAnchor => {
540					let blinding = self.blinding;
541					let private_key = self.private_key;
542					let utxo = vanchor::get_leaf_with_private_raw(
543						curve.unwrap_or(Curve::Bn254),
544						width.unwrap_or(5),
545						exponentiation.unwrap_or(5),
546						private_key,
547						blinding,
548						chain_id,
549						amount.unwrap_or_else(|| "0".to_string()).parse().unwrap(),
550						index,
551					)?;
552
553					let chain_id = utxo.get_chain_id_bytes();
554					let amount = utxo.get_amount();
555					let blinding = utxo.get_blinding();
556					let secret_key = utxo.get_secret_key();
557
558					// secrets
559					vec![chain_id, amount, blinding, secret_key]
560				}
561			},
562			Some(secrets) => {
563				match protocol {
564					NoteProtocol::Mixer => {
565						if secrets.len() != 1 {
566							let message = "Mixer secrets length should be 1 in length".to_string();
567							let operation_error =
568								OperationError::new_with_message(OpStatusCode::InvalidNoteSecrets, message);
569							return Err(operation_error.into());
570						}
571					}
572					NoteProtocol::VAnchor => {
573						if secrets.len() != 4 {
574							let message = "VAnchor secrets length should be 4 in length".to_string();
575							let operation_error =
576								OperationError::new_with_message(OpStatusCode::InvalidNoteSecrets, message);
577							return Err(operation_error.into());
578						}
579					}
580				};
581
582				secrets
583			}
584		};
585
586		let backend = self.backend;
587		let hash_function = self.hash_function;
588		let token_symbol = self.token_symbol;
589		let amount = self.amount.clone();
590		let denomination = self.denomination;
591
592		let scheme = "webb://".to_string();
593		let note = JsNote {
594			scheme,
595			protocol,
596			version,
597			source_chain_id,
598			target_chain_id,
599			source_identifying_data,
600			target_identifying_data,
601			backend,
602			hash_function,
603			curve,
604			token_symbol,
605			amount,
606			denomination,
607			exponentiation,
608			width,
609			secrets,
610			index,
611		};
612		Ok(note)
613	}
614}
615
616#[allow(clippy::unused_unit)]
617#[wasm_bindgen]
618impl JsNote {
619	#[wasm_bindgen(constructor)]
620	pub fn new(builder: JsNoteBuilder) -> Result<JsNote, JsValue> {
621		builder.build()
622	}
623
624	#[wasm_bindgen(js_name = deserialize)]
625	pub fn js_deserialize(note: JsString) -> Result<JsNote, JsValue> {
626		let n: String = note.into();
627		let n = JsNote::deserialize(&n)?;
628		Ok(n)
629	}
630
631	#[wasm_bindgen(js_name = getLeafCommitment)]
632	pub fn get_leaf_commitment(&self) -> Result<Uint8Array, JsValue> {
633		let leaf = self.get_leaf_and_nullifier()?;
634
635		Ok(leaf.commitment())
636	}
637
638	pub fn serialize(&self) -> JsString {
639		JsString::from(self.to_string())
640	}
641
642	#[wasm_bindgen(getter)]
643	pub fn protocol(&self) -> Protocol {
644		self.protocol.into()
645	}
646
647	#[wasm_bindgen(getter)]
648	pub fn version(&self) -> Version {
649		self.version.into()
650	}
651
652	#[wasm_bindgen(js_name = targetChainId)]
653	#[wasm_bindgen(getter)]
654	pub fn target_chain_id(&self) -> JsString {
655		self.target_chain_id.clone().into()
656	}
657
658	#[wasm_bindgen(js_name = sourceChainId)]
659	#[wasm_bindgen(getter)]
660	pub fn source_chain_id(&self) -> JsString {
661		self.source_chain_id.clone().into()
662	}
663
664	#[wasm_bindgen(js_name = targetIdentifyingData)]
665	#[wasm_bindgen(getter)]
666	pub fn target_identifying_data(&self) -> JsString {
667		self.target_identifying_data.clone().into()
668	}
669
670	#[wasm_bindgen(js_name = sourceIdentifyingData)]
671	#[wasm_bindgen(getter)]
672	pub fn source_identifying_data(&self) -> JsString {
673		self.source_identifying_data.clone().into()
674	}
675
676	#[wasm_bindgen(getter)]
677	pub fn backend(&self) -> BE {
678		self.backend.unwrap_or(Backend::Circom).into()
679	}
680
681	#[wasm_bindgen(getter)]
682	#[wasm_bindgen(js_name = hashFunction)]
683	pub fn hash_function(&self) -> JsString {
684		self.hash_function.unwrap_or(HashFunction::Poseidon).into()
685	}
686
687	#[wasm_bindgen(getter)]
688	pub fn curve(&self) -> WasmCurve {
689		self.curve.unwrap_or(Curve::Bn254).into()
690	}
691
692	#[wasm_bindgen(getter)]
693	pub fn secrets(&self) -> JsString {
694		let secrets = self.secrets.iter().map(hex::encode).collect::<Vec<String>>().join(":");
695		secrets.into()
696	}
697
698	#[wasm_bindgen(getter)]
699	#[wasm_bindgen(js_name = tokenSymbol)]
700	pub fn token_symbol(&self) -> JsString {
701		self.token_symbol.clone().unwrap_or_default().into()
702	}
703
704	#[wasm_bindgen(getter)]
705	pub fn amount(&self) -> JsString {
706		self.amount.clone().unwrap_or_default().into()
707	}
708
709	#[wasm_bindgen(getter)]
710	pub fn denomination(&self) -> JsString {
711		let denomination = self.denomination.unwrap_or_default().to_string();
712		denomination.into()
713	}
714
715	#[wasm_bindgen(getter)]
716	pub fn width(&self) -> JsString {
717		let width = self.width.unwrap_or_default().to_string();
718		width.into()
719	}
720
721	#[wasm_bindgen(getter)]
722	pub fn exponentiation(&self) -> JsString {
723		let exp = self.exponentiation.unwrap_or_default().to_string();
724		exp.into()
725	}
726
727	#[wasm_bindgen(js_name = mutateIndex)]
728	pub fn js_mutate_index(&mut self, index: JsString) -> Result<(), JsValue> {
729		let index: String = index.into();
730		let index: u64 = index.parse().map_err(|_| OpStatusCode::InvalidNoteVersion)?;
731
732		self.mutate_index(index).map_err(|e| e.into())
733	}
734
735	#[wasm_bindgen(js_name = defaultUtxoNote)]
736	pub fn default_utxo_note(note: &JsNote) -> Result<JsNote, OperationError> {
737		let mut new_note = JsNote {
738			scheme: note.scheme.clone(),
739			protocol: note.protocol,
740			version: note.version,
741			source_chain_id: note.source_chain_id.clone(),
742			target_chain_id: note.target_chain_id.clone(),
743			source_identifying_data: note.source_identifying_data.clone(),
744			target_identifying_data: note.target_identifying_data.clone(),
745			secrets: note.secrets.clone(),
746			curve: note.curve,
747			exponentiation: note.exponentiation,
748			width: note.width,
749			token_symbol: note.token_symbol.clone(),
750			amount: Some("0".to_string()),
751			denomination: note.denomination,
752			backend: note.backend,
753			hash_function: note.hash_function,
754			index: Some(0),
755		};
756		let chain_id: u64 = new_note
757			.target_chain_id
758			.parse()
759			.map_err(|_| OpStatusCode::InvalidTargetChain)?;
760
761		let utxo = vanchor::generate_secrets(
762			0,
763			new_note.exponentiation.unwrap_or(5),
764			new_note.width.unwrap_or(5),
765			new_note.curve.unwrap_or(Curve::Bn254),
766			chain_id,
767			Some(0),
768			&mut OsRng,
769		)?;
770		new_note.update_vanchor_utxo(utxo)?;
771		Ok(new_note)
772	}
773
774	#[wasm_bindgen(js_name = getUtxo)]
775	pub fn get_utxo(&self) -> Result<JsUtxo, OperationError> {
776		match self.protocol {
777			NoteProtocol::VAnchor => {
778				let leaf = self.get_leaf_and_nullifier()?;
779				leaf.vanchor_leaf()
780			}
781			_ => Err(OpStatusCode::InvalidNoteProtocol.into()),
782		}
783	}
784
785	// for test and internal usage
786	pub fn update_vanchor_utxo(&mut self, utxo: JsUtxo) -> Result<(), OperationError> {
787		let chain_id = utxo.get_chain_id_bytes();
788		let amount = utxo.get_amount();
789		let blinding = utxo.get_blinding();
790		let secret_key = utxo.get_secret_key();
791		self.amount = Some(utxo.get_amount_raw().to_string());
792		self.secrets = vec![chain_id, amount, blinding, secret_key];
793		Ok(())
794	}
795
796	#[wasm_bindgen(getter)]
797	pub fn index(&self) -> JsString {
798		match self.index {
799			None => JsString::from(""),
800			Some(index) => JsString::from(index.to_string().as_str()),
801		}
802	}
803}
804
805#[cfg(test)]
806mod test {
807	use ark_bn254;
808	use wasm_bindgen_test::*;
809
810	use crate::utils::to_rust_string;
811
812	use super::*;
813
814	type Bn254Fr = ark_bn254::Fr;
815
816	#[wasm_bindgen_test]
817	fn generate_mixer_note() {
818		let mut note_builder = JsNoteBuilder::new();
819		let protocol: Protocol = JsValue::from(NoteProtocol::Mixer.to_string()).into();
820		let version: Version = JsValue::from(NoteVersion::V1.to_string()).into();
821		let backend: BE = JsValue::from(Backend::Arkworks.to_string()).into();
822		let hash_function: HF = JsValue::from(HashFunction::Poseidon.to_string()).into();
823		let curve: WasmCurve = JsValue::from(Curve::Bn254.to_string()).into();
824
825		note_builder.protocol(protocol).unwrap();
826		note_builder.version(version).unwrap();
827		note_builder.source_chain_id(JsString::from("2"));
828		note_builder.target_chain_id(JsString::from("2"));
829		note_builder.source_identifying_data(JsString::from("2"));
830		note_builder.target_identifying_data(JsString::from("2"));
831
832		note_builder.width(JsString::from("3")).unwrap();
833		note_builder.exponentiation(JsString::from("5")).unwrap();
834		note_builder.denomination(JsString::from("18")).unwrap();
835		note_builder.amount(JsString::from("10"));
836		note_builder.token_symbol(JsString::from("EDG"));
837		note_builder.curve(curve).unwrap();
838		note_builder.hash_function(hash_function).unwrap();
839		note_builder.backend(backend);
840		note_builder.index(JsString::from("10")).unwrap();
841
842		let mixer_note = note_builder.build().unwrap();
843		let note_string = mixer_note.to_string();
844		let leaf = mixer_note.get_leaf_commitment().unwrap();
845		let leaf_vec = leaf.to_vec();
846
847		let js_note_2 = JsNote::deserialize(&note_string).unwrap();
848		let js_note_2_string = js_note_2.to_string();
849
850		let leaf_2 = js_note_2.get_leaf_commitment().unwrap();
851		let leaf_2_vec = leaf_2.to_vec();
852
853		// Asserting that with serialization and deserialization lead to the same note
854		assert_eq!(note_string, js_note_2_string);
855		assert_eq!(mixer_note.secrets.len(), 2);
856		assert_eq!(hex::encode(leaf_vec), hex::encode(leaf_2_vec));
857	}
858
859	#[wasm_bindgen_test]
860	fn should_deserialize_mixer_note() {
861		let mixer_note = "webb://v1:mixer/2:2/2:2/fd717cfe463b3ffec71ee6b7606bbd0179170510abf41c9f16c1d20ca9923f0e:18b6b080e6a43262f00f6fb3da0d2409c4871b8f26d89d5c8836358e1af5a41c/?curve=Bn254&width=3&exp=5&hf=Poseidon&backend=Arkworks&token=EDG&denom=18&amount=10&index=10";
862		let note = JsNote::deserialize(mixer_note).unwrap();
863		// Generate leaf to trigger any errors
864		note.get_leaf_commitment().unwrap();
865		assert_eq!(note.serialize(), mixer_note);
866	}
867
868	#[wasm_bindgen_test]
869	fn generate_vanchor_note() {
870		let mut note_builder = JsNoteBuilder::new();
871		let protocol: Protocol = JsValue::from(NoteProtocol::VAnchor.to_string()).into();
872		let version: Version = JsValue::from(NoteVersion::V1.to_string()).into();
873		let backend: BE = JsValue::from(Backend::Arkworks.to_string()).into();
874		let hash_function: HF = JsValue::from(HashFunction::Poseidon.to_string()).into();
875		let curve: WasmCurve = JsValue::from(Curve::Bn254.to_string()).into();
876
877		note_builder.protocol(protocol).unwrap();
878		note_builder.version(version).unwrap();
879		note_builder.source_chain_id(JsString::from("2"));
880		note_builder.target_chain_id(JsString::from("3"));
881		note_builder.source_identifying_data(JsString::from("2"));
882		note_builder.target_identifying_data(JsString::from("3"));
883
884		note_builder.width(JsString::from("5")).unwrap();
885		note_builder.exponentiation(JsString::from("5")).unwrap();
886		note_builder.denomination(JsString::from("18")).unwrap();
887		note_builder.amount(JsString::from("10"));
888		note_builder.token_symbol(JsString::from("EDG"));
889		note_builder.curve(curve).unwrap();
890		note_builder.hash_function(hash_function).unwrap();
891		note_builder.backend(backend);
892		note_builder.index(JsString::from("10")).unwrap();
893
894		let vanchor_note = note_builder.build().unwrap();
895		let note_string = vanchor_note.to_string();
896		let leaf = vanchor_note.get_leaf_commitment().unwrap();
897		let leaf_vec = leaf.to_vec();
898
899		let js_note_2 = JsNote::deserialize(&note_string).unwrap();
900		let js_note_2_string = js_note_2.to_string();
901
902		let leaf_2 = js_note_2.get_leaf_commitment().unwrap();
903		let leaf_2_vec = leaf_2.to_vec();
904
905		// Asserting that with serialization and deserialization lead to the same note
906		assert_eq!(note_string, js_note_2_string);
907		assert_eq!(vanchor_note.secrets.len(), 4);
908		assert_eq!(hex::encode(leaf_vec), hex::encode(leaf_2_vec));
909	}
910
911	#[wasm_bindgen_test]
912	fn should_deserialize_vanchor_note() {
913		let vanchor_note_str = "webb://v1:vanchor/2:3/2:3/0300000000000000000000000000000000000000000000000000000000000000:0a00000000000000000000000000000000000000000000000000000000000000:7798d054444ec463be7d41ad834147b5b2c468182c7cd6a601aec29a273fca05:bf5d780608f5b8a8db1dc87356a225a0324a1db61903540daaedd54ab10a4124/?curve=Bn254&width=5&exp=5&hf=Poseidon&backend=Arkworks&token=EDG&denom=18&amount=10&index=10";
914		let note = JsNote::deserialize(vanchor_note_str).unwrap();
915		// Generate leaf to trigger any errors
916		note.get_leaf_commitment().unwrap();
917		assert_eq!(note.serialize(), vanchor_note_str);
918	}
919}