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 ($($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 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 let scheme = "webb://";
189 let authority = vec![self.version.to_string(), self.protocol.to_string()].join(":");
191 let chain_ids = vec![self.source_chain_id.to_string(), self.target_chain_id.to_string()].join(":");
193 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 #[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 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 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 #[wasm_bindgen(skip)]
294 pub secrets: Vec<Vec<u8>>,
295
296 #[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 #[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 #[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 let version = self.version.ok_or(OpStatusCode::InvalidNoteVersion)?;
501 let protocol = self.protocol.ok_or(OpStatusCode::InvalidNoteProtocol)?;
502
503 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 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 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 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 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(¬e_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 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 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(¬e_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 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 note.get_leaf_commitment().unwrap();
917 assert_eq!(note.serialize(), vanchor_note_str);
918 }
919}