use core::fmt;
use core::fmt::Formatter;
use core::str::FromStr;
use ark_bn254::Fr as Bn254Fr;
use ark_ff::{BigInteger, PrimeField};
use ark_std::UniformRand;
use arkworks_setups::utxo::Utxo;
use arkworks_setups::{Curve as ArkCurve, VAnchorProver};
use js_sys::{JsString, Uint8Array};
use rand::rngs::OsRng;
use wasm_bindgen::prelude::*;
use crate::types::{Backend, Curve, OpStatusCode, OperationError, WasmCurve, BE};
use crate::VAnchorR1CSProverBn254_30_2_2_2;
#[derive(Clone)]
pub enum JsUtxoInner {
Bn254(Utxo<Bn254Fr>),
}
impl fmt::Debug for JsUtxoInner {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "JsUtxoInner")
}
}
#[wasm_bindgen]
#[derive(Clone, Debug)]
pub struct JsUtxo {
#[wasm_bindgen(skip)]
pub inner: JsUtxoInner,
}
impl JsUtxo {
pub(crate) fn new(
curve: Curve,
backend: Backend,
amount: u128,
chain_id: u64,
index: Option<u64>,
private_key: Option<Vec<u8>>,
blinding: Option<Vec<u8>>,
) -> Result<JsUtxo, OperationError> {
let mut rng = OsRng;
let utxo = match (curve, backend) {
(Curve::Bn254, Backend::Arkworks) => {
let pk = private_key.unwrap_or_else(|| Bn254Fr::rand(&mut rng).into_repr().to_bytes_le());
let blinding = blinding.unwrap_or_else(|| Bn254Fr::rand(&mut rng).into_repr().to_bytes_le());
VAnchorR1CSProverBn254_30_2_2_2::create_leaf_with_privates(
ArkCurve::Bn254,
chain_id,
amount,
index,
pk,
blinding,
)
}
.map_err(|_| OpStatusCode::InvalidOutputUtxoConfig)
.map(JsUtxo::new_from_bn254_utxo),
_ => Err(OpStatusCode::InvalidNoteProtocol),
}?;
Ok(utxo)
}
pub fn get_amount_raw(&self) -> u128 {
let amount_bytes = self.get_amount();
let mut amount_slice = [0u8; 16];
amount_slice.copy_from_slice(amount_bytes[..16].to_vec().as_slice());
u128::from_le_bytes(amount_slice)
}
pub fn default_bn254_utxo() -> Self {
let utxo =
VAnchorR1CSProverBn254_30_2_2_2::create_random_utxo(ArkCurve::Bn254, 0, 0, None, &mut OsRng).unwrap();
Self {
inner: JsUtxoInner::Bn254(utxo),
}
}
pub fn new_from_bn254_utxo(utxo: Utxo<Bn254Fr>) -> Self {
Self {
inner: JsUtxoInner::Bn254(utxo),
}
}
pub fn get_chain_id_raw(&self) -> u64 {
match &self.inner {
JsUtxoInner::Bn254(bn254_utxo) => bn254_utxo.chain_id_raw,
}
}
pub fn get_chain_id_bytes(&self) -> Vec<u8> {
self.chain_id_raw().to_be_bytes().to_vec()
}
pub fn get_amount(&self) -> Vec<u8> {
match &self.inner {
JsUtxoInner::Bn254(bn254_utxo) => bn254_utxo.amount.into_repr().to_bytes_le(),
}
}
pub fn get_blinding(&self) -> Vec<u8> {
match &self.inner {
JsUtxoInner::Bn254(bn254_utxo) => bn254_utxo.blinding.into_repr().to_bytes_le(),
}
}
pub fn get_secret_key(&self) -> Vec<u8> {
match &self.inner {
JsUtxoInner::Bn254(bn254_utxo) => bn254_utxo.keypair.secret_key.into_repr().to_bytes_le(),
}
}
pub fn get_index(&self) -> Option<u64> {
match &self.inner {
JsUtxoInner::Bn254(bn254_utxo) => bn254_utxo.index,
}
}
pub fn get_index_bytes(&self) -> Vec<u8> {
match &self.inner {
JsUtxoInner::Bn254(bn254_utxo) => match bn254_utxo.index {
None => 0u64.to_le_bytes().to_vec(),
Some(index) => index.to_le_bytes().to_vec(),
},
}
}
pub fn get_nullifier(&self) -> Option<Vec<u8>> {
match &self.inner {
JsUtxoInner::Bn254(bn254_utxo) => &bn254_utxo.nullifier,
}
.map(|value| value.into_repr().to_bytes_le())
}
pub fn get_commitment(&self) -> Vec<u8> {
match &self.inner {
JsUtxoInner::Bn254(bn254_utxo) => bn254_utxo.commitment.into_repr().to_bytes_le(),
}
}
pub fn get_bn254_utxo(&self) -> Result<Utxo<Bn254Fr>, OpStatusCode> {
match self.inner.clone() {
JsUtxoInner::Bn254(utxo) => Ok(utxo),
}
}
pub fn deserialize(utxo_str: &str) -> Result<JsUtxo, OperationError> {
utxo_str.parse().map_err(Into::into)
}
}
#[wasm_bindgen]
impl JsUtxo {
pub fn serialize(&self) -> JsString {
JsString::from(self.to_string())
}
#[wasm_bindgen(js_name = deserialize)]
pub fn js_deserialize(utxo_str: JsString) -> Result<JsUtxo, JsValue> {
let utxo_str: String = utxo_str.into();
Self::deserialize(&utxo_str).map_err(Into::into)
}
#[wasm_bindgen(constructor)]
#[allow(clippy::too_many_arguments)]
pub fn construct(
curve: WasmCurve,
backend: BE,
amount: JsString,
chain_id: JsString,
index: Option<JsString>,
private_key: Option<Uint8Array>,
blinding: Option<Uint8Array>,
) -> Result<JsUtxo, JsValue> {
let curve: Curve = JsValue::from(curve)
.as_string()
.unwrap()
.parse()
.map_err(|_| OpStatusCode::InvalidCurve)?;
let backend: Backend = JsValue::from(backend)
.as_string()
.unwrap()
.parse()
.map_err(|_| OpStatusCode::InvalidBackend)?;
let chain_id: String = chain_id.into();
let chain_id = chain_id.parse().map_err(|_| OpStatusCode::InvalidChainId)?;
let amount: String = amount.into();
let amount: u128 = amount.parse().map_err(|_| OpStatusCode::InvalidAmount)?;
let index = match index {
None => None,
Some(index) => {
let index: String = index.into();
let index: u64 = index.parse().map_err(|_| OpStatusCode::InvalidUTXOIndex)?;
Some(index)
}
};
let mut rng = OsRng;
let utxo = match (curve, backend) {
(Curve::Bn254, Backend::Arkworks) => {
let pk = private_key
.map(|p| p.to_vec())
.unwrap_or_else(|| Bn254Fr::rand(&mut rng).into_repr().to_bytes_le());
let blinding = blinding
.map(|b| b.to_vec())
.unwrap_or_else(|| Bn254Fr::rand(&mut rng).into_repr().to_bytes_le());
VAnchorR1CSProverBn254_30_2_2_2::create_leaf_with_privates(
ArkCurve::Bn254,
chain_id,
amount,
index,
pk,
blinding,
)
}
.map_err(|_| OpStatusCode::InvalidOutputUtxoConfig)
.map(JsUtxo::new_from_bn254_utxo),
_ => Err(OpStatusCode::InvalidNoteProtocol),
}?;
Ok(utxo)
}
#[wasm_bindgen(getter)]
#[wasm_bindgen(js_name = chainIdRaw)]
pub fn chain_id_raw(&self) -> u64 {
self.get_chain_id_raw()
}
#[wasm_bindgen(getter)]
#[wasm_bindgen(js_name = chainId)]
pub fn chain_id(&self) -> JsString {
let chain_id_bytes = self.get_chain_id_bytes();
hex::encode(chain_id_bytes).into()
}
#[wasm_bindgen(getter)]
pub fn amount(&self) -> JsString {
let amount = self.get_amount();
hex::encode(amount).into()
}
#[wasm_bindgen(getter)]
pub fn blinding(&self) -> JsString {
let blinding = self.get_blinding();
hex::encode(blinding).into()
}
#[wasm_bindgen(getter)]
pub fn secret_key(&self) -> JsString {
let secret_key = self.get_secret_key();
hex::encode(secret_key).into()
}
#[wasm_bindgen(getter)]
pub fn index(&self) -> JsValue {
let index = self.get_index().unwrap_or(0);
JsValue::from(index)
}
#[wasm_bindgen(getter)]
pub fn nullifier(&self) -> JsString {
let nullifier = self
.get_nullifier()
.map(|value| hex::encode(value.as_slice()))
.unwrap_or_else(|| "".to_string());
JsString::from(nullifier)
}
#[wasm_bindgen(getter)]
pub fn commitment(&self) -> Uint8Array {
let commitment = self.get_commitment();
Uint8Array::from(commitment.as_slice())
}
}
impl fmt::Display for JsUtxo {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
let curve = match self.inner {
JsUtxoInner::Bn254(_) => Curve::Bn254.to_string(),
};
let backend = Backend::Arkworks.to_string();
let amount = self.get_amount_raw().to_string();
let chain_id = self.get_chain_id_raw().to_string();
let index = self
.get_index()
.map(|v| v.to_string())
.unwrap_or_else(|| "None".to_string());
let blinding = hex::encode(self.get_blinding());
let private_key = hex::encode(self.get_secret_key());
let sec = vec![curve, backend, amount, chain_id, index, blinding, private_key].join("&");
write!(f, "{}", sec)
}
}
impl FromStr for JsUtxo {
type Err = OperationError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let parts: Vec<_> = s.split('&').collect();
let curve: Curve = parts[0].parse().map_err(|_| OpStatusCode::InvalidCurve)?;
let backend: Backend = parts[1].parse().map_err(|_| OpStatusCode::InvalidBackend)?;
let amount = parts[2].parse().map_err(|_| OpStatusCode::InvalidAmount)?;
let chain_id = parts[3].parse().map_err(|_| OpStatusCode::InvalidChainId)?;
let index = match parts[4] {
"None" => None,
v => {
let index: u64 = v.parse().map_err(|_| OpStatusCode::InvalidUTXOIndex)?;
Some(index)
}
};
let blinding = hex::decode(parts[5]).map_err(|_| OpStatusCode::Unknown)?;
let private_key = hex::decode(parts[6]).map_err(|_| OpStatusCode::Unknown)?;
JsUtxo::new(
curve,
backend,
amount,
chain_id,
index,
Some(private_key),
Some(blinding),
)
}
}