From ce326d1751bdd2d487c44a8514f34d90e2636453 Mon Sep 17 00:00:00 2001 From: Mike Turner Date: Tue, 26 Nov 2024 11:14:16 -0600 Subject: [PATCH] Add arithmetic types, plaintext, and conversion to JS objects --- wasm/src/account/address.rs | 9 +- wasm/src/account/compute_key.rs | 97 ++++++++++++ wasm/src/account/graph_key.rs | 103 +++++++++++++ wasm/src/account/mod.rs | 3 + wasm/src/account/private_key.rs | 2 +- wasm/src/account/signature.rs | 21 ++- wasm/src/lib.rs | 22 +++ wasm/src/programs/data/mod.rs | 18 +++ wasm/src/programs/data/plaintext.rs | 205 ++++++++++++++++++++++++++ wasm/src/programs/manager/deploy.rs | 4 +- wasm/src/programs/manager/execute.rs | 10 +- wasm/src/programs/manager/join.rs | 10 +- wasm/src/programs/manager/mod.rs | 4 +- wasm/src/programs/manager/split.rs | 4 +- wasm/src/programs/manager/transfer.rs | 10 +- wasm/src/programs/mod.rs | 3 + wasm/src/programs/program.rs | 23 +-- wasm/src/programs/response.rs | 2 +- wasm/src/programs/transaction.rs | 99 ++++++++++++- wasm/src/record/record_ciphertext.rs | 40 ++++- wasm/src/record/record_plaintext.rs | 174 +++++++++++++++++++--- wasm/src/thread_pool/mod.rs | 2 +- wasm/src/types/field.rs | 91 +++++++++++- wasm/src/types/group.rs | 118 +++++++++++++++ wasm/src/types/helpers/literal.rs | 93 ++++++++++++ wasm/src/types/helpers/mod.rs | 24 +++ wasm/src/types/helpers/plaintext.rs | 69 +++++++++ wasm/src/types/helpers/record.rs | 53 +++++++ wasm/src/types/mod.rs | 10 +- wasm/src/types/native.rs | 15 +- wasm/src/types/scalar.rs | 128 ++++++++++++++++ 31 files changed, 1376 insertions(+), 90 deletions(-) create mode 100644 wasm/src/account/compute_key.rs create mode 100644 wasm/src/account/graph_key.rs create mode 100644 wasm/src/programs/data/mod.rs create mode 100644 wasm/src/programs/data/plaintext.rs create mode 100644 wasm/src/types/group.rs create mode 100644 wasm/src/types/helpers/literal.rs create mode 100644 wasm/src/types/helpers/mod.rs create mode 100644 wasm/src/types/helpers/plaintext.rs create mode 100644 wasm/src/types/helpers/record.rs create mode 100644 wasm/src/types/scalar.rs diff --git a/wasm/src/account/address.rs b/wasm/src/account/address.rs index 4bef81d46..bf255da63 100644 --- a/wasm/src/account/address.rs +++ b/wasm/src/account/address.rs @@ -16,7 +16,7 @@ use crate::account::{PrivateKey, Signature, ViewKey}; -use crate::types::native::AddressNative; +use crate::{account::compute_key::ComputeKey, types::native::AddressNative}; use core::{convert::TryFrom, fmt, ops::Deref, str::FromStr}; use wasm_bindgen::prelude::*; @@ -43,6 +43,13 @@ impl Address { Self(AddressNative::try_from(**view_key).unwrap()) } + /// Derive an Aleo address from a compute key. + /// + /// @param {ComputeKey} compute_key The compute key to derive the address from + pub fn from_compute_key(compute_key: &ComputeKey) -> Self { + compute_key.address() + } + /// Create an aleo address object from a string representation of an address /// /// @param {string} address String representation of an addressm diff --git a/wasm/src/account/compute_key.rs b/wasm/src/account/compute_key.rs new file mode 100644 index 000000000..b7cbf4dd5 --- /dev/null +++ b/wasm/src/account/compute_key.rs @@ -0,0 +1,97 @@ +// Copyright (C) 2019-2023 Aleo Systems Inc. +// This file is part of the Aleo SDK library. + +// The Aleo SDK library is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The Aleo SDK library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with the Aleo SDK library. If not, see . + +use super::PrivateKey; +use crate::{ + Address, + types::{Group, Scalar, native::ComputeKeyNative}, +}; + +use core::{convert::TryFrom, ops::Deref}; +use wasm_bindgen::prelude::*; + +#[wasm_bindgen] +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ComputeKey(ComputeKeyNative); + +#[wasm_bindgen] +impl ComputeKey { + /// Create a new compute key from a private key. + /// + /// @param {PrivateKey} private_key Private key + /// @returns {ComputeKey} Compute key + pub fn from_private_key(private_key: &PrivateKey) -> Self { + Self(ComputeKeyNative::try_from(**private_key).unwrap()) + } + + /// Get the address from the compute key. + /// + /// @returns {Address} + pub fn address(&self) -> Address { + Address::from(self.0.to_address()) + } + + /// Get the sk_prf of the compute key. + pub fn sk_prf(&self) -> Scalar { + Scalar::from(self.0.sk_prf()) + } + + /// Get the pr_tag of the compute key. + /// + /// @returns {Group} pr_tag + pub fn pk_sig(&self) -> Group { + Group::from(self.0.pk_sig()) + } + + /// Get the pr_sig of the compute key. + /// + /// @returns {Group} pr_sig + pub fn pr_sig(&self) -> Group { + Group::from(self.0.pr_sig()) + } +} + +impl Deref for ComputeKey { + type Target = ComputeKeyNative; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl From for ComputeKey { + fn from(compute_key: ComputeKeyNative) -> Self { + Self(compute_key) + } +} + +impl From for ComputeKeyNative { + fn from(compute_key: ComputeKey) -> Self { + compute_key.0 + } +} + +impl From<&ComputeKey> for ComputeKeyNative { + fn from(compute_key: &ComputeKey) -> Self { + compute_key.0.clone() + } +} + +impl From<&ComputeKeyNative> for ComputeKey { + fn from(compute_key: &ComputeKeyNative) -> Self { + Self(compute_key.clone()) + } +} diff --git a/wasm/src/account/graph_key.rs b/wasm/src/account/graph_key.rs new file mode 100644 index 000000000..9b78c2042 --- /dev/null +++ b/wasm/src/account/graph_key.rs @@ -0,0 +1,103 @@ +// Copyright (C) 2019-2023 Aleo Systems Inc. +// This file is part of the Aleo SDK library. + +// The Aleo SDK library is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The Aleo SDK library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with the Aleo SDK library. If not, see . + +use super::ViewKey; +use crate::types::{Field, native::GraphKeyNative}; + +use core::{convert::TryFrom, fmt, ops::Deref, str::FromStr}; +use wasm_bindgen::prelude::*; + +#[wasm_bindgen] +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct GraphKey(GraphKeyNative); + +#[wasm_bindgen] +impl GraphKey { + /// Create a new graph key from a view key. + /// + /// @param {ViewKey} view_key View key + /// @returns {GraphKey} Graph key + pub fn from_view_key(view_key: &ViewKey) -> Self { + Self::from(GraphKeyNative::try_from(**view_key).unwrap()) + } + + /// Create a new graph key from a string representation of a graph key + /// + /// @param {string} graph_key String representation of a graph key + /// @returns {GraphKey} Graph key + pub fn from_string(graph_key: &str) -> Self { + Self::from_str(graph_key).unwrap() + } + + /// Get a string representation of a graph key + /// + /// @returns {string} String representation of a graph key + #[allow(clippy::inherent_to_string_shadow_display)] + pub fn to_string(&self) -> String { + self.0.to_string() + } + + /// Get the sk_tag of the graph key. Used to determine ownership of records. + pub fn sk_tag(&self) -> Field { + Field::from(self.0.sk_tag()) + } +} + +impl FromStr for GraphKey { + type Err = anyhow::Error; + + fn from_str(graph_key: &str) -> Result { + Ok(Self(GraphKeyNative::from_str(graph_key)?)) + } +} + +impl Deref for GraphKey { + type Target = GraphKeyNative; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl fmt::Display for GraphKey { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.0) + } +} + +impl From for GraphKey { + fn from(value: GraphKeyNative) -> Self { + Self(value) + } +} + +impl From for GraphKeyNative { + fn from(value: GraphKey) -> Self { + value.0 + } +} + +impl From<&GraphKey> for GraphKeyNative { + fn from(value: &GraphKey) -> Self { + value.0.clone() + } +} + +impl From<&GraphKeyNative> for GraphKey { + fn from(value: &GraphKeyNative) -> Self { + Self(value.clone()) + } +} diff --git a/wasm/src/account/mod.rs b/wasm/src/account/mod.rs index ca88a9df9..42d80543f 100644 --- a/wasm/src/account/mod.rs +++ b/wasm/src/account/mod.rs @@ -29,5 +29,8 @@ pub use private_key_ciphertext::*; pub mod signature; pub use signature::*; +mod compute_key; +mod graph_key; pub mod view_key; + pub use view_key::*; diff --git a/wasm/src/account/private_key.rs b/wasm/src/account/private_key.rs index 8be4ca796..7daef86cc 100644 --- a/wasm/src/account/private_key.rs +++ b/wasm/src/account/private_key.rs @@ -18,7 +18,7 @@ use crate::account::{Address, Encryptor, PrivateKeyCiphertext, Signature, ViewKe use crate::types::native::{CurrentNetwork, Environment, FromBytes, PrimeField, PrivateKeyNative, ToBytes}; use core::{convert::TryInto, fmt, ops::Deref, str::FromStr}; -use rand::{rngs::StdRng, SeedableRng}; +use rand::{SeedableRng, rngs::StdRng}; use wasm_bindgen::prelude::*; /// Private key of an Aleo account diff --git a/wasm/src/account/signature.rs b/wasm/src/account/signature.rs index 4caccf3bd..1315e6d33 100644 --- a/wasm/src/account/signature.rs +++ b/wasm/src/account/signature.rs @@ -16,9 +16,9 @@ use crate::account::{Address, PrivateKey}; -use crate::types::native::SignatureNative; +use crate::types::{Scalar, native::SignatureNative}; use core::{fmt, ops::Deref, str::FromStr}; -use rand::{rngs::StdRng, SeedableRng}; +use rand::{SeedableRng, rngs::StdRng}; use wasm_bindgen::prelude::*; /// Cryptographic signature of a message signed by an Aleo account @@ -36,6 +36,21 @@ impl Signature { Self(SignatureNative::sign_bytes(private_key, message, &mut StdRng::from_entropy()).unwrap()) } + /// Get an address from a signature. + pub fn to_address(&self) -> Address { + Address::from(self.0.to_address()) + } + + /// Get the challenge of a signature. + pub fn challenge(&self) -> Scalar { + Scalar::from(self.0.challenge()) + } + + /// Get the response of a signature. + pub fn response(&self) -> Scalar { + Scalar::from(self.0.response()) + } + /// Verify a signature of a message with an address /// /// @param {Address} address The address to verify the signature with @@ -88,7 +103,7 @@ impl Deref for Signature { mod tests { use super::*; - use rand::{rngs::StdRng, Rng, SeedableRng}; + use rand::{Rng, SeedableRng, rngs::StdRng}; use wasm_bindgen_test::*; const ITERATIONS: u64 = 1_000; diff --git a/wasm/src/lib.rs b/wasm/src/lib.rs index 9b5eaf4d6..d4d21e519 100644 --- a/wasm/src/lib.rs +++ b/wasm/src/lib.rs @@ -196,6 +196,28 @@ extern "C" { pub fn log(s: &str); } +#[macro_export] +macro_rules! array { + ($($value:expr),*$(,)?) => {{ + let array = Array::new(); + + $(array.push(&JsValue::from($value));)* + + array + }}; + } + +#[macro_export] +macro_rules! object { + ($($key:literal: $value:expr,)*) => {{ + let object = Object::new(); + + $(Reflect::set(&object, &JsValue::from_str($key), &JsValue::from($value)).unwrap();)* + + object + }}; + } + /// A trait providing convenient methods for accessing the amount of Aleo present in a record pub trait Credits { /// Get the amount of credits in the record if the record possesses Aleo credits diff --git a/wasm/src/programs/data/mod.rs b/wasm/src/programs/data/mod.rs new file mode 100644 index 000000000..0107858c3 --- /dev/null +++ b/wasm/src/programs/data/mod.rs @@ -0,0 +1,18 @@ +// Copyright (C) 2019-2023 Aleo Systems Inc. +// This file is part of the Aleo SDK library. + +// The Aleo SDK library is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The Aleo SDK library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with the Aleo SDK library. If not, see . + +mod plaintext; +pub use plaintext::Plaintext; diff --git a/wasm/src/programs/data/plaintext.rs b/wasm/src/programs/data/plaintext.rs new file mode 100644 index 000000000..ac2ac1538 --- /dev/null +++ b/wasm/src/programs/data/plaintext.rs @@ -0,0 +1,205 @@ +// Copyright (C) 2019-2023 Aleo Systems Inc. +// This file is part of the Aleo SDK library. + +// The Aleo SDK library is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The Aleo SDK library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with the Aleo SDK library. If not, see . + +use crate::types::{ + helpers::plaintext::plaintext_to_js_value, + native::{FromBytes, PlaintextNative, ToBytes}, +}; + +use crate::types::native::IdentifierNative; +use js_sys::Uint8Array; +use std::str::FromStr; +use wasm_bindgen::{JsValue, prelude::wasm_bindgen}; + +/// SnarkVM Plaintext object. Plaintext is a fundamental monadic type used to represent Aleo +/// primitive types (boolean, field, group, i8, i16, i32, i64, i128, u8, u16, u32, u64, u128, +/// scalar, and signature), struct types, and array types. +/// +/// In the context of a web or NodeJS application, this type is useful for turning an Aleo type into +/// a JS value, object, or array that might be necessary for performing computations within the +/// application. +/// +/// @example +/// // Get the bond state of an existing address. +/// const bondState = await fetch(https://api.explorer.provable.com/v1/mainnet/program/credits.aleo/mapping/bond_state/aleo12zlythl7htjdtjjjz3ahdj4vl6wk3zuzm37s80l86qpx8fyx95fqnxcn2f); +/// // Convert the bond state to a Plaintext object. +/// const bondStatePlaintext = Plaintext.fromString(bond_state); +/// // Convert the Plaintext object to a JS object. +/// const bondStateObject = bond_state_plaintext.toObject(); +/// // Check if the bond state matches the expected object. +/// const expectedObject = { validator: "aleo12zlythl7htjdtjjjz3ahdj4vl6wk3zuzm37s80l86qpx8fyx95fqnxcn2f", microcredits: 100000000u64 }; +/// assert( JSON.stringify(bondStateObject) === JSON.stringify(expectedObject) ); +/// +/// +#[wasm_bindgen] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct Plaintext(PlaintextNative); + +#[wasm_bindgen] +impl Plaintext { + /// Find plaintext member if the plaintext is a struct. Returns `null` if the plaintext is not + /// a struct or the member does not exist. + pub fn find(&self, name: String) -> Option { + let identifier = IdentifierNative::from_str(&name).ok()?; + match self.0.find(&[identifier]) { + Ok(plaintext) => Some(Plaintext(plaintext)), + Err(_) => None, + } + } + + /// Creates a plaintext object from a string representation of a plaintext. + #[wasm_bindgen(js_name = "fromString")] + pub fn from_string(plaintext: &str) -> Result<Plaintext, String> { + Ok(Self(PlaintextNative::from_str(plaintext).map_err(|e| e.to_string())?)) + } + + /// Get a plaintext object from a series of bytes. + #[wasm_bindgen(js_name = "fromBytesLe")] + pub fn from_bytes_le(bytes: Uint8Array) -> Result<Plaintext, String> { + let rust_bytes = bytes.to_vec(); + let native = PlaintextNative::from_bytes_le(rust_bytes.as_slice()).map_err(|e| e.to_string())?; + Ok(Self(native)) + } + + /// Generate a random plaintext element from a series of bytes. + #[wasm_bindgen(js_name = "toBytesLe")] + pub fn to_bytes_le(&self) -> Result<Uint8Array, String> { + let rust_bytes = self.0.to_bytes_le().map_err(|e| e.to_string())?; + let array = Uint8Array::new_with_length(rust_bytes.len() as u32); + array.copy_from(rust_bytes.as_slice()); + Ok(array) + } + + /// Returns the string representation of the plaintext. + #[wasm_bindgen(js_name = "toString")] + #[allow(clippy::inherent_to_string)] + pub fn to_string(&self) -> String { + self.0.to_string() + } + + /// Attempt to convert the plaintext to a JS object. + #[wasm_bindgen(js_name = "toObject")] + pub fn to_object(&self) -> Result<JsValue, String> { + Ok(plaintext_to_js_value(&self.0)) + } +} + +impl From<PlaintextNative> for Plaintext { + fn from(native: PlaintextNative) -> Self { + Self(native) + } +} + +impl From<Plaintext> for PlaintextNative { + fn from(plaintext: Plaintext) -> Self { + plaintext.0 + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::object; + use js_sys::{Object, Reflect}; + + use wasm_bindgen_test::wasm_bindgen_test; + + const STRUCT: &str = "{ microcredits: 100000000u64, height: 1653124u32 }"; + + const NESTED_STRUCT: &str = "{ player: aleo13nnjqa7h2u4mpl95guz97nhzkhlde750zsjnw59tkgdwc85lyurs295lxc, health: 100u8, inventory: { coins: 5u32, snacks: { candies: 5u64, vegetals: 6u64 } }, secret: 2group, cipher: 2scalar, is_alive: true }"; + + #[wasm_bindgen_test] + fn test_literal_plaintext_to_and_from() { + // address + let plaintext = + Plaintext::from_string("aleo13nnjqa7h2u4mpl95guz97nhzkhlde750zsjnw59tkgdwc85lyurs295lxc").unwrap(); + assert_eq!(plaintext.to_string(), "aleo13nnjqa7h2u4mpl95guz97nhzkhlde750zsjnw59tkgdwc85lyurs295lxc"); + assert!(plaintext.to_object().unwrap().is_string()); + // bool + let plaintext = Plaintext::from_string("true").unwrap(); + assert_eq!(plaintext.to_string(), "true"); + // field + let plaintext = Plaintext::from_string("1field").unwrap(); + assert_eq!(plaintext.to_string(), "1field"); + // group + let plaintext = Plaintext::from_string("2group").unwrap(); + assert_eq!(plaintext.to_string(), "2group"); + // i8 + let plaintext = Plaintext::from_string("100i8").unwrap(); + assert_eq!(plaintext.to_string(), "100i8"); + // i16 + let plaintext = Plaintext::from_string("100i16").unwrap(); + assert_eq!(plaintext.to_string(), "100i16"); + // i32 + let plaintext = Plaintext::from_string("100i32").unwrap(); + assert_eq!(plaintext.to_string(), "100i32"); + // i64 + let plaintext = Plaintext::from_string("100i64").unwrap(); + assert_eq!(plaintext.to_string(), "100i64"); + // i128 + let plaintext = Plaintext::from_string("100i128").unwrap(); + assert_eq!(plaintext.to_string(), "100i128"); + // u8 + let plaintext = Plaintext::from_string("100u8").unwrap(); + assert_eq!(plaintext.to_string(), "100u8"); + // u16 + let plaintext = Plaintext::from_string("100u16").unwrap(); + assert_eq!(plaintext.to_string(), "100u16"); + // u32 + let plaintext = Plaintext::from_string("100u32").unwrap(); + assert_eq!(plaintext.to_string(), "100u32"); + // u64 + let plaintext = Plaintext::from_string("100u64").unwrap(); + assert_eq!(plaintext.to_string(), "100u64"); + // u128 + let plaintext = Plaintext::from_string("100u128").unwrap(); + assert_eq!(plaintext.to_string(), "100u128"); + // scalar + let plaintext = Plaintext::from_string("1scalar").unwrap(); + assert_eq!(plaintext.to_string(), "1scalar"); + } + + #[wasm_bindgen_test] + fn test_struct_find() { + let plaintext = Plaintext::from_string(STRUCT).unwrap(); + assert_eq!(plaintext.to_string(), "{\n microcredits: 100000000u64,\n height: 1653124u32\n}"); + let microcredits = Plaintext::from_string("100000000u64").unwrap(); + let height = Plaintext::from_string("1653124u32").unwrap(); + assert_eq!(plaintext.find("microcredits".to_string()).unwrap(), microcredits); + assert_eq!(plaintext.find("height".to_string()).unwrap(), height); + } + + #[wasm_bindgen_test] + fn test_struct_to_object() { + let plaintext = Plaintext::from_string(NESTED_STRUCT).unwrap().to_object().unwrap(); + let js_object = Object::try_from(&plaintext).unwrap(); + let expected_object = object! { + "player": "aleo13nnjqa7h2u4mpl95guz97nhzkhlde750zsjnw59tkgdwc85lyurs295lxc", + "health": 100u8, + "inventory": object! { + "coins": 5u32, + "snacks": object! { + "candies": 5u64, + "vegetals": 6u64, + }, + }, + "secret": "2group", + "cipher": "2scalar", + "is_alive": true, + }; + assert_eq!(format!("{js_object:?}"), format!("{expected_object:?}")); + } +} diff --git a/wasm/src/programs/manager/deploy.rs b/wasm/src/programs/manager/deploy.rs index 24c120563..bc5d4ab08 100644 --- a/wasm/src/programs/manager/deploy.rs +++ b/wasm/src/programs/manager/deploy.rs @@ -16,7 +16,7 @@ use super::*; -use crate::{execute_fee, log, OfflineQuery, PrivateKey, RecordPlaintext, Transaction}; +use crate::{OfflineQuery, PrivateKey, RecordPlaintext, Transaction, execute_fee, log}; use crate::types::native::{ CurrentAleo, @@ -29,7 +29,7 @@ use crate::types::native::{ TransactionNative, }; use js_sys::Object; -use rand::{rngs::StdRng, SeedableRng}; +use rand::{SeedableRng, rngs::StdRng}; use std::str::FromStr; #[wasm_bindgen] diff --git a/wasm/src/programs/manager/execute.rs b/wasm/src/programs/manager/execute.rs index b24d8f511..ef98106eb 100644 --- a/wasm/src/programs/manager/execute.rs +++ b/wasm/src/programs/manager/execute.rs @@ -18,15 +18,15 @@ use super::*; use core::ops::Add; use crate::{ - execute_fee, - execute_program, - log, - process_inputs, ExecutionResponse, OfflineQuery, PrivateKey, RecordPlaintext, Transaction, + execute_fee, + execute_program, + log, + process_inputs, }; use crate::types::native::{ @@ -38,7 +38,7 @@ use crate::types::native::{ TransactionNative, }; use js_sys::{Array, Object}; -use rand::{rngs::StdRng, SeedableRng}; +use rand::{SeedableRng, rngs::StdRng}; use std::str::FromStr; #[wasm_bindgen] diff --git a/wasm/src/programs/manager/join.rs b/wasm/src/programs/manager/join.rs index 6d589dc68..528ef7792 100644 --- a/wasm/src/programs/manager/join.rs +++ b/wasm/src/programs/manager/join.rs @@ -17,14 +17,14 @@ use super::*; use crate::{ - execute_fee, - execute_program, - log, - process_inputs, OfflineQuery, PrivateKey, RecordPlaintext, Transaction, + execute_fee, + execute_program, + log, + process_inputs, }; use crate::types::native::{ @@ -36,7 +36,7 @@ use crate::types::native::{ TransactionNative, }; use js_sys::Array; -use rand::{rngs::StdRng, SeedableRng}; +use rand::{SeedableRng, rngs::StdRng}; use std::str::FromStr; #[wasm_bindgen] diff --git a/wasm/src/programs/manager/mod.rs b/wasm/src/programs/manager/mod.rs index 7fcd6457c..af5a8da3a 100644 --- a/wasm/src/programs/manager/mod.rs +++ b/wasm/src/programs/manager/mod.rs @@ -25,8 +25,6 @@ const DEFAULT_URL: &str = "https://api.explorer.provable.com/v1"; use crate::{KeyPair, PrivateKey, ProvingKey, RecordPlaintext, VerifyingKey}; use crate::types::native::{ - cost_in_microcredits, - deployment_cost, IdentifierNative, ProcessNative, ProgramIDNative, @@ -34,6 +32,8 @@ use crate::types::native::{ ProvingKeyNative, QueryNative, VerifyingKeyNative, + cost_in_microcredits, + deployment_cost, }; use js_sys::{Object, Reflect}; use std::str::FromStr; diff --git a/wasm/src/programs/manager/split.rs b/wasm/src/programs/manager/split.rs index 8983d29bc..0903d686b 100644 --- a/wasm/src/programs/manager/split.rs +++ b/wasm/src/programs/manager/split.rs @@ -16,11 +16,11 @@ use super::*; -use crate::{execute_program, log, process_inputs, OfflineQuery, PrivateKey, RecordPlaintext, Transaction}; +use crate::{OfflineQuery, PrivateKey, RecordPlaintext, Transaction, execute_program, log, process_inputs}; use crate::types::native::{CurrentAleo, IdentifierNative, ProcessNative, ProgramNative, TransactionNative}; use js_sys::Array; -use rand::{rngs::StdRng, SeedableRng}; +use rand::{SeedableRng, rngs::StdRng}; use std::{ops::Add, str::FromStr}; #[wasm_bindgen] diff --git a/wasm/src/programs/manager/transfer.rs b/wasm/src/programs/manager/transfer.rs index 01195fa28..674691d1e 100644 --- a/wasm/src/programs/manager/transfer.rs +++ b/wasm/src/programs/manager/transfer.rs @@ -17,14 +17,14 @@ use super::*; use crate::{ - execute_fee, - execute_program, - log, - process_inputs, OfflineQuery, PrivateKey, RecordPlaintext, Transaction, + execute_fee, + execute_program, + log, + process_inputs, }; use crate::types::native::{ @@ -35,7 +35,7 @@ use crate::types::native::{ RecordPlaintextNative, TransactionNative, }; -use rand::{rngs::StdRng, SeedableRng}; +use rand::{SeedableRng, rngs::StdRng}; use std::{ops::Add, str::FromStr}; use wasm_bindgen::JsValue; diff --git a/wasm/src/programs/mod.rs b/wasm/src/programs/mod.rs index a8f6f0962..9a3730cd4 100644 --- a/wasm/src/programs/mod.rs +++ b/wasm/src/programs/mod.rs @@ -16,6 +16,9 @@ mod macros; +mod data; +pub use data::*; + pub mod execution; pub use execution::*; diff --git a/wasm/src/programs/program.rs b/wasm/src/programs/program.rs index 236d1f86e..bfc35fc61 100644 --- a/wasm/src/programs/program.rs +++ b/wasm/src/programs/program.rs @@ -21,7 +21,7 @@ use crate::{ use js_sys::{Array, Object, Reflect}; use std::{ops::Deref, str::FromStr}; -use wasm_bindgen::{prelude::wasm_bindgen, JsValue}; +use wasm_bindgen::{JsValue, prelude::wasm_bindgen}; /// Webassembly Representation of an Aleo program #[wasm_bindgen] @@ -501,29 +501,10 @@ impl FromStr for Program { #[cfg(test)] mod tests { use super::*; + use crate::{array, object}; use wasm_bindgen_test::*; - macro_rules! array { - ($($value:expr),*$(,)?) => {{ - let array = Array::new(); - - $(array.push(&JsValue::from($value));)* - - array - }}; - } - - macro_rules! object { - ($($key:literal: $value:expr,)*) => {{ - let object = Object::new(); - - $(Reflect::set(&object, &JsValue::from_str($key), &JsValue::from($value)).unwrap();)* - - object - }}; - } - const TOKEN_ISSUE: &str = r#"program token_issue.aleo; struct token_metadata: diff --git a/wasm/src/programs/response.rs b/wasm/src/programs/response.rs index 6baa6f2e2..81b57fa6f 100644 --- a/wasm/src/programs/response.rs +++ b/wasm/src/programs/response.rs @@ -27,7 +27,7 @@ use crate::types::native::{ use crate::{Execution, KeyPair, Program, ProvingKey, VerifyingKey}; use std::{ops::Deref, str::FromStr}; -use wasm_bindgen::{prelude::wasm_bindgen, JsValue}; +use wasm_bindgen::{JsValue, prelude::wasm_bindgen}; /// Webassembly Representation of an Aleo function execution response /// diff --git a/wasm/src/programs/transaction.rs b/wasm/src/programs/transaction.rs index 48a81d5e5..54cea4e8e 100644 --- a/wasm/src/programs/transaction.rs +++ b/wasm/src/programs/transaction.rs @@ -14,10 +14,12 @@ // You should have received a copy of the GNU General Public License // along with the Aleo SDK library. If not, see <https://www.gnu.org/licenses/>. -use crate::types::native::TransactionNative; +use crate::types::native::{FromBytes, ToBytes, TransactionNative}; +use crate::{Field, RecordCiphertext}; +use js_sys::{Array, Object, Reflect, Uint8Array}; use std::str::FromStr; -use wasm_bindgen::prelude::wasm_bindgen; +use wasm_bindgen::{JsValue, prelude::wasm_bindgen}; /// Webassembly Representation of an Aleo transaction /// @@ -38,6 +40,17 @@ impl Transaction { Transaction::from_str(transaction) } + /// Create a transaction from a Uint8Array of left endian bytes. + /// + /// @param {Uint8Array} Uint8Array of left endian bytes encoding a Transaction. + /// @returns {Transaction} + #[wasm_bindgen(js_name = fromBytesLe)] + pub fn from_bytes_le(bytes: Uint8Array) -> Result<Transaction, String> { + let bytes = bytes.to_vec(); + let transaction = TransactionNative::from_bytes_le(&bytes).map_err(|e| e.to_string())?; + Ok(Transaction(transaction)) + } + /// Get the transaction as a string. If you want to submit this transaction to the Aleo Network /// this function will create the string that should be submitted in the `POST` data. /// @@ -48,6 +61,88 @@ impl Transaction { self.0.to_string() } + /// Get the transaction as a Uint8Array of left endian bytes. + /// + /// @returns {Uint8Array} Uint8Array representation of the transaction + #[wasm_bindgen(js_name = toBytesLe)] + pub fn to_bytes_le(&self) -> Result<Uint8Array, String> { + let bytes = self.0.to_bytes_le().map_err(|e| e.to_string())?; + let array = Uint8Array::new_with_length(bytes.len() as u32); + array.copy_from(bytes.as_slice()); + Ok(array) + } + + /// Returns true if the transaction contains the given serial number. + /// + /// @param {boolean} True if the transaction contains the given serial number. + pub fn contains_serial_number(&self, serial_number: &Field) -> bool { + self.0.contains_serial_number(serial_number) + } + + /// Returns true if the transaction contains the given commitment. + /// + /// @param {boolean} True if the transaction contains the given commitment. + pub fn contains_commitment(&self, commitment: &Field) -> bool { + self.0.contains_commitment(commitment) + } + + /// Returns the transaction's base fee. + pub fn get_base_fee(&self) -> u64 { + self.0.base_fee_amount().map(|fee| *fee).unwrap_or(0) + } + + /// Returns the transaction's total fee. + pub fn get_total_fee(&self) -> u64 { + self.0.fee_amount().map(|fee| *fee).unwrap_or(0) + } + + /// Returns the transaction's priority fee. + /// + /// returns {bigint} The transaction's priority fee. + pub fn get_priority_fee(&self) -> u64 { + self.0.priority_fee_amount().map(|fee| *fee).unwrap_or(0) + } + + /// Find a record in the transaction by the record's commitment. + pub fn find_record(&self, commitment: &Field) -> Option<RecordCiphertext> { + self.0.find_record(commitment).map(|record_ciphertext| RecordCiphertext::from(record_ciphertext)) + } + + /// Get the record ciphertexts present in a transaction. + pub fn get_record_ciphertexts(&self) -> Array { + let array = Array::new(); + self.0.records().for_each(|(commitment, record_ciphertext)| { + let object = Object::new(); + let commitment = Field::from(commitment); + let record_ciphertext = RecordCiphertext::from(record_ciphertext); + Reflect::set(&object, &JsValue::from_str("commitment"), &JsValue::from(commitment)).unwrap(); + Reflect::set(&object, &JsValue::from_str("record"), &JsValue::from(record_ciphertext)).unwrap(); + array.push(&object); + }); + array + } + + /// Returns true if the transaction is a deployment transaction. + /// + /// @returns {boolean} True if the transaction is a deployment transaction + pub fn is_deploy(&self) -> bool { + self.0.is_deploy() + } + + /// Returns true if the transaction is an execution transaction. + /// + /// @returns {boolean} True if the transaction is an execution transaction + pub fn is_execute(&self) -> bool { + self.0.is_execute() + } + + /// Returns true if the transaction is a fee transaction. + /// + /// @returns {boolean} True if the transaction is a fee transaction + pub fn is_fee(&self) -> bool { + self.0.is_fee() + } + /// Get the id of the transaction. This is the merkle root of the transaction's inclusion proof. /// /// This value can be used to query the status of the transaction on the Aleo Network to see diff --git a/wasm/src/record/record_ciphertext.rs b/wasm/src/record/record_ciphertext.rs index d26ebd9d4..7b764f9e2 100644 --- a/wasm/src/record/record_ciphertext.rs +++ b/wasm/src/record/record_ciphertext.rs @@ -67,14 +67,6 @@ impl RecordCiphertext { } } -impl FromStr for RecordCiphertext { - type Err = anyhow::Error; - - fn from_str(ciphertext: &str) -> Result<Self, Self::Err> { - Ok(Self(RecordCiphertextNative::from_str(ciphertext)?)) - } -} - impl Deref for RecordCiphertext { type Target = RecordCiphertextNative; @@ -83,6 +75,38 @@ impl Deref for RecordCiphertext { } } +impl From<RecordCiphertextNative> for RecordCiphertext { + fn from(record: RecordCiphertextNative) -> Self { + Self(record) + } +} + +impl From<RecordCiphertext> for RecordCiphertextNative { + fn from(record: RecordCiphertext) -> Self { + record.0 + } +} + +impl From<&RecordCiphertext> for RecordCiphertextNative { + fn from(record: &RecordCiphertext) -> Self { + record.0.clone() + } +} + +impl From<&RecordCiphertextNative> for RecordCiphertext { + fn from(record: &RecordCiphertextNative) -> Self { + Self(record.clone()) + } +} + +impl FromStr for RecordCiphertext { + type Err = anyhow::Error; + + fn from_str(ciphertext: &str) -> Result<Self, Self::Err> { + Ok(Self(RecordCiphertextNative::from_str(ciphertext)?)) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/wasm/src/record/record_plaintext.rs b/wasm/src/record/record_plaintext.rs index 5a4ddc552..45a0ba918 100644 --- a/wasm/src/record/record_plaintext.rs +++ b/wasm/src/record/record_plaintext.rs @@ -14,9 +14,28 @@ // You should have received a copy of the GNU General Public License // along with the Aleo SDK library. If not, see <https://www.gnu.org/licenses/>. -use crate::{account::PrivateKey, types::Field, Credits}; +use crate::{ + Address, + Credits, + Plaintext, + account::PrivateKey, + types::{ + Field, + helpers::record_to_js_object, + native::{ + CurrentNetwork, + EntryNative, + IdentifierNative, + PlaintextNative, + ProgramIDNative, + RecordPlaintextNative, + }, + }, +}; +use snarkvm_console::program::Owner; -use crate::types::native::{IdentifierNative, ProgramIDNative, RecordPlaintextNative}; +use anyhow::Context; +use js_sys::Object; use std::{ops::Deref, str::FromStr}; use wasm_bindgen::prelude::*; @@ -49,6 +68,83 @@ impl RecordPlaintext { Self::from_str(record).map_err(|_| "The record plaintext string provided was invalid".into()) } + pub fn get_member(&self, input: String) -> Result<Plaintext, String> { + let entry = self + .0 + .data() + .get(&IdentifierNative::from_str(&input).map_err(|_| "".to_string())?) + .context("Record member not found") + .map_err(|e| e.to_string())?; + let entry = match entry { + EntryNative::Public(entry) => entry, + EntryNative::Constant(entry) => entry, + EntryNative::Private(entry) => entry, + }; + Ok(Plaintext::from(entry.clone())) + } + + /// Get the owner of the record. + pub fn owner(&self) -> Result<Address, String> { + match self.0.owner() { + Owner::<CurrentNetwork, PlaintextNative>::Public(owner) => Ok(Address::from(*owner)), + _ => Err("Record is not public".to_string()), + } + } + + /// Get a representation of a record as a javascript object for usage in client side + /// computations. Note that this is not a reversible operation and exists for the convenience + /// of discovering and using properties of the record. + /// + /// The conversion guide is as follows: + /// - u8, u16, u32, i8, i16 i32 --> Number + /// - u64, u128, i64, i128 --> BigInt + /// - Address, Field, Group, Scalar --> String. + /// + /// Address, Field, Group, and Scalar will all be converted to their bech32 string + /// representation. These string representations can be converted back to their respective wasm + /// types using the fromString method on the Address, Field, Group, and Scalar objects in this + /// library. + /// + /// @example + /// # Create a wasm record from a record string. + /// let record_plaintext_wasm = RecordPlainext.from_string("{ + /// owner: aleo1kh5t7m30djl0ecdn4f5vuzp7dx0tcwh7ncquqjkm4matj2p2zqpqm6at48.private, + /// metadata: { + /// player1: aleo1kh5t7m30djl0ecdn4f5vuzp7dx0tcwh7ncquqjkm4matj2p2zqpqm6at48.private, + /// player2: aleo1dreuxnmg9cny8ee9v2u0wr4v4affnwm09u2pytfwz0f2en2shgqsdsfjn6.private, + /// nonce: 660310649780728486489183263981322848354071976582883879926426319832534836534field.private + /// }, + /// id: 1953278585719525811355617404139099418855053112960441725284031425961000152405field.private, + /// positions: 50794271u64.private, + /// attempts: 0u64.private, + /// hits: 0u64.private, + /// _nonce: 5668100912391182624073500093436664635767788874314097667746354181784048204413group.public + /// }"); + /// + /// let expected_object = { + /// owner: "aleo1kh5t7m30djl0ecdn4f5vuzp7dx0tcwh7ncquqjkm4matj2p2zqpqm6at48", + /// metadata: { + /// player1: "aleo1kh5t7m30djl0ecdn4f5vuzp7dx0tcwh7ncquqjkm4matj2p2zqpqm6at48", + /// player2: "aleo1dreuxnmg9cny8ee9v2u0wr4v4affnwm09u2pytfwz0f2en2shgqsdsfjn6", + /// nonce: "660310649780728486489183263981322848354071976582883879926426319832534836534field" + /// }, + /// id: "1953278585719525811355617404139099418855053112960441725284031425961000152405field", + /// positions: 50794271, + /// attempts: 0, + /// hits: 0, + /// _nonce: "5668100912391182624073500093436664635767788874314097667746354181784048204413group" + /// }; + /// + /// # Create the expected object + /// let record_plaintext_object = record_plaintext_wasm.to_js_object(); + /// assert(JSON.stringify(record_plaintext_object) == JSON.stringify(expected_object)); + /// + /// @returns {Object} Javascript object representation of the record + #[wasm_bindgen(js_name = "getRecordMembers")] + pub fn to_js_object(&self) -> Result<Object, String> { + record_to_js_object(&self.0) + } + /// Returns the record plaintext string /// /// @returns {string} String representation of the record plaintext @@ -94,25 +190,43 @@ impl RecordPlaintext { } } +impl Deref for RecordPlaintext { + type Target = RecordPlaintextNative; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + impl From<RecordPlaintextNative> for RecordPlaintext { fn from(record: RecordPlaintextNative) -> Self { Self(record) } } -impl FromStr for RecordPlaintext { - type Err = anyhow::Error; +impl From<RecordPlaintext> for RecordPlaintextNative { + fn from(record: RecordPlaintext) -> Self { + record.0 + } +} - fn from_str(plaintext: &str) -> Result<Self, Self::Err> { - Ok(Self(RecordPlaintextNative::from_str(plaintext)?)) +impl From<&RecordPlaintextNative> for RecordPlaintext { + fn from(record: &RecordPlaintextNative) -> Self { + Self(record.clone()) } } -impl Deref for RecordPlaintext { - type Target = RecordPlaintextNative; +impl From<&RecordPlaintext> for RecordPlaintextNative { + fn from(record: &RecordPlaintext) -> Self { + record.0.clone() + } +} - fn deref(&self) -> &Self::Target { - &self.0 +impl FromStr for RecordPlaintext { + type Err = anyhow::Error; + + fn from_str(plaintext: &str) -> Result<Self, Self::Err> { + Ok(Self(RecordPlaintextNative::from_str(plaintext)?)) } } @@ -120,30 +234,52 @@ impl Deref for RecordPlaintext { mod tests { use super::*; - use wasm_bindgen_test::wasm_bindgen_test; + use wasm_bindgen_test::{console_log, wasm_bindgen_test}; - const RECORD: &str = r"{ + const CREDITS_RECORD: &str = r"{ owner: aleo1j7qxyunfldj2lp8hsvy7mw5k8zaqgjfyr72x2gh3x4ewgae8v5gscf5jh3.private, microcredits: 1500000000000000u64.private, _nonce: 3077450429259593211617823051143573281856129402760267155982965992208217472983group.public }"; + const BATTLESHIP_RECORD: &str = r"{ + owner: aleo1kh5t7m30djl0ecdn4f5vuzp7dx0tcwh7ncquqjkm4matj2p2zqpqm6at48.private, + metadata: { + player1: aleo1kh5t7m30djl0ecdn4f5vuzp7dx0tcwh7ncquqjkm4matj2p2zqpqm6at48.private, + player2: aleo1dreuxnmg9cny8ee9v2u0wr4v4affnwm09u2pytfwz0f2en2shgqsdsfjn6.private, + nonce: 660310649780728486489183263981322848354071976582883879926426319832534836534field.private + }, + id: 1953278585719525811355617404139099418855053112960441725284031425961000152405field.private, + positions: 50794271u64.private, + attempts: 0u64.private, + hits: 0u64.private, + _nonce: 5668100912391182624073500093436664635767788874314097667746354181784048204413group.public +}"; + #[wasm_bindgen_test] fn test_to_and_from_string() { - let record = RecordPlaintext::from_string(RECORD).unwrap(); - assert_eq!(record.to_string(), RECORD); + let record = RecordPlaintext::from_string(CREDITS_RECORD).unwrap(); + assert_eq!(record.to_string(), CREDITS_RECORD); + } + + #[wasm_bindgen_test] + fn test_get_record_member() { + let record = RecordPlaintext::from_string(BATTLESHIP_RECORD).unwrap(); + let positions = record.get_member("positions".to_string()).unwrap(); + let hits = record.get_member("hits".to_string()).unwrap(); + console_log!("Battleship positions: {positions:?} - hits: {hits:?}"); } #[wasm_bindgen_test] fn test_microcredits_from_string() { - let record = RecordPlaintext::from_string(RECORD).unwrap(); + let record = RecordPlaintext::from_string(CREDITS_RECORD).unwrap(); assert_eq!(record.microcredits(), 1500000000000000); } #[wasm_bindgen_test] fn test_serial_number() { let pk = PrivateKey::from_string("APrivateKey1zkpDeRpuKmEtLNPdv57aFruPepeH1aGvTkEjBo8bqTzNUhE").unwrap(); - let record = RecordPlaintext::from_string(RECORD).unwrap(); + let record = RecordPlaintext::from_string(CREDITS_RECORD).unwrap(); let program_id = "credits.aleo"; let record_name = "credits"; let expected_sn = "8170619507075647151199239049653235187042661744691458644751012032123701508940field"; @@ -154,7 +290,7 @@ mod tests { #[wasm_bindgen_test] fn test_serial_number_can_run_twice_with_same_private_key() { let pk = PrivateKey::from_string("APrivateKey1zkpDeRpuKmEtLNPdv57aFruPepeH1aGvTkEjBo8bqTzNUhE").unwrap(); - let record = RecordPlaintext::from_string(RECORD).unwrap(); + let record = RecordPlaintext::from_string(CREDITS_RECORD).unwrap(); let program_id = "credits.aleo"; let record_name = "credits"; let expected_sn = "8170619507075647151199239049653235187042661744691458644751012032123701508940field"; @@ -165,7 +301,7 @@ mod tests { #[wasm_bindgen_test] fn test_serial_number_invalid_program_id_returns_err_string() { let pk = PrivateKey::from_string("APrivateKey1zkpDeRpuKmEtLNPdv57aFruPepeH1aGvTkEjBo8bqTzNUhE").unwrap(); - let record = RecordPlaintext::from_string(RECORD).unwrap(); + let record = RecordPlaintext::from_string(CREDITS_RECORD).unwrap(); let program_id = "not a real program id"; let record_name = "token"; assert!(record.serial_number_string(&pk, program_id, record_name).is_err()); @@ -174,7 +310,7 @@ mod tests { #[wasm_bindgen_test] fn test_serial_number_invalid_record_name_returns_err_string() { let pk = PrivateKey::from_string("APrivateKey1zkpDeRpuKmEtLNPdv57aFruPepeH1aGvTkEjBo8bqTzNUhE").unwrap(); - let record = RecordPlaintext::from_string(RECORD).unwrap(); + let record = RecordPlaintext::from_string(CREDITS_RECORD).unwrap(); let program_id = "token.aleo"; let record_name = "not a real record name"; assert!(record.serial_number_string(&pk, program_id, record_name).is_err()); diff --git a/wasm/src/thread_pool/mod.rs b/wasm/src/thread_pool/mod.rs index d0df6f54b..ddd8ff8fe 100644 --- a/wasm/src/thread_pool/mod.rs +++ b/wasm/src/thread_pool/mod.rs @@ -16,7 +16,7 @@ use futures::{channel::oneshot, future::try_join_all}; use rayon::ThreadBuilder; -use spmc::{channel, Receiver, Sender}; +use spmc::{Receiver, Sender, channel}; use std::future::Future; use wasm_bindgen::prelude::*; use wasm_bindgen_futures::JsFuture; diff --git a/wasm/src/types/field.rs b/wasm/src/types/field.rs index da07d97e9..e523e1f0a 100644 --- a/wasm/src/types/field.rs +++ b/wasm/src/types/field.rs @@ -14,11 +14,16 @@ // You should have received a copy of the GNU General Public License // along with the Aleo SDK library. If not, see <https://www.gnu.org/licenses/>. -use crate::types::native::FieldNative; - -use wasm_bindgen::prelude::wasm_bindgen; +use crate::{ + Plaintext, + types::native::{FieldNative, LiteralNative, PlaintextNative, Uniform}, +}; +use snarkvm_console::prelude::{Double, One, Pow}; +use std::ops::Deref; +use once_cell::sync::OnceCell; use std::str::FromStr; +use wasm_bindgen::prelude::wasm_bindgen; #[wasm_bindgen] #[derive(Clone, Debug, Eq, PartialEq)] @@ -26,15 +31,77 @@ pub struct Field(FieldNative); #[wasm_bindgen] impl Field { + /// Creates a field object from a string representation of a field. + #[wasm_bindgen(js_name = "fromString")] + pub fn from_string(field: &str) -> Result<Field, String> { + Ok(Self(FieldNative::from_str(field).map_err(|e| e.to_string())?)) + } + + /// Create a plaintext element from a group element. + #[wasm_bindgen(js_name = "toPlaintext")] + pub fn to_plaintext(&self) -> Plaintext { + Plaintext::from(PlaintextNative::Literal(LiteralNative::Field(self.0), OnceCell::new())) + } + + /// Returns the string representation of the field. #[wasm_bindgen(js_name = "toString")] #[allow(clippy::inherent_to_string)] pub fn to_string(&self) -> String { self.0.to_string() } - #[wasm_bindgen(js_name = "fromString")] - pub fn from_string(field: &str) -> Result<Field, String> { - Ok(Self(FieldNative::from_str(field).map_err(|e| e.to_string())?)) + /// Generate a random field element. + pub fn random() -> Field { + let rng = &mut rand::thread_rng(); + Field(FieldNative::rand(rng)) + } + + /// Add two field elements. + pub fn add(&self, other: &Field) -> Field { + Field(self.0 + other.0) + } + + /// Subtract two field elements. + pub fn subtract(&self, other: &Field) -> Field { + Field(self.0 - other.0) + } + + /// Multiply two field elements. + pub fn multiply(&self, other: &Field) -> Field { + Field(self.0 * other.0) + } + + /// Divide two field elements. + pub fn divide(&self, other: &Field) -> Field { + Field(self.0 / other.0) + } + + /// Power of a field element. + pub fn pow(&self, other: &Field) -> Field { + Field(self.0.pow(other.0)) + } + + /// Invert the field element. + pub fn inverse(&self) -> Field { + Field(-self.0) + } + + /// Get the one element of the field. + pub fn one() -> Field { + Field(FieldNative::one()) + } + + /// Double the field element. + pub fn double(&self) -> Field { + Field(self.0.double()) + } +} + +impl Deref for Field { + type Target = FieldNative; + + fn deref(&self) -> &Self::Target { + &self.0 } } @@ -49,3 +116,15 @@ impl From<Field> for FieldNative { field.0 } } + +impl From<&FieldNative> for Field { + fn from(native: &FieldNative) -> Self { + Self(*native) + } +} + +impl From<&Field> for FieldNative { + fn from(scalar: &Field) -> Self { + scalar.0 + } +} diff --git a/wasm/src/types/group.rs b/wasm/src/types/group.rs new file mode 100644 index 000000000..040a8aeb0 --- /dev/null +++ b/wasm/src/types/group.rs @@ -0,0 +1,118 @@ +// Copyright (C) 2019-2023 Aleo Systems Inc. +// This file is part of the Aleo SDK library. + +// The Aleo SDK library is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The Aleo SDK library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with the Aleo SDK library. If not, see <https://www.gnu.org/licenses/>. + +use crate::{ + Plaintext, + types::{ + Scalar, + native::{GroupNative, LiteralNative, PlaintextNative, Uniform}, + }, +}; +use snarkvm_console::prelude::Double; +use std::ops::Deref; + +use once_cell::sync::OnceCell; +use std::str::FromStr; +use wasm_bindgen::prelude::wasm_bindgen; + +#[wasm_bindgen] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct Group(GroupNative); + +#[wasm_bindgen] +impl Group { + /// Creates a group object from a string representation of a group. + #[wasm_bindgen(js_name = "fromString")] + pub fn from_string(group: &str) -> Result<Group, String> { + Ok(Self(GroupNative::from_str(group).map_err(|e| e.to_string())?)) + } + + /// Returns the string representation of the group. + #[wasm_bindgen(js_name = "toString")] + #[allow(clippy::inherent_to_string)] + pub fn to_string(&self) -> String { + self.0.to_string() + } + + /// Create a plaintext element from a group element. + #[wasm_bindgen(js_name = "toPlaintext")] + pub fn to_plaintext(&self) -> Plaintext { + Plaintext::from(PlaintextNative::Literal(LiteralNative::Group(self.0), OnceCell::new())) + } + + /// Generate a random group element. + pub fn random() -> Group { + let rng = &mut rand::thread_rng(); + Group(GroupNative::rand(rng)) + } + + /// Add two group elements. + pub fn add(&self, other: &Group) -> Group { + Group(self.0 + other.0) + } + + /// Subtract two group elements (equivalently: add the inverse of an element). + pub fn subtract(&self, other: &Group) -> Group { + Group(self.0 - other.0) + } + + /// Multiply a group element by a scalar element. + pub fn scalar_multiply(&self, scalar: &Scalar) -> Group { + Group(self.0 * **scalar) + } + + /// Double the group element. + pub fn double(&self) -> Group { + Group(self.0.double()) + } + + /// Get the negation of the group element. + pub fn negate(&self) -> Group { + Group(-self.0) + } +} + +impl Deref for Group { + type Target = GroupNative; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl From<GroupNative> for Group { + fn from(native: GroupNative) -> Self { + Self(native) + } +} + +impl From<Group> for GroupNative { + fn from(group: Group) -> Self { + group.0 + } +} + +impl From<&GroupNative> for Group { + fn from(native: &GroupNative) -> Self { + Self(*native) + } +} + +impl From<&Group> for GroupNative { + fn from(group: &Group) -> Self { + group.0 + } +} diff --git a/wasm/src/types/helpers/literal.rs b/wasm/src/types/helpers/literal.rs new file mode 100644 index 000000000..5ab9308b2 --- /dev/null +++ b/wasm/src/types/helpers/literal.rs @@ -0,0 +1,93 @@ +// Copyright (C) 2019-2023 Aleo Systems Inc. +// This file is part of the Aleo SDK library. + +// The Aleo SDK library is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The Aleo SDK library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with the Aleo SDK library. If not, see <https://www.gnu.org/licenses/>. + +use crate::types::native::LiteralNative; + +use wasm_bindgen::JsValue; + +/// Turn a literal into a javascript value. +pub fn literal_to_js_value(literal: &LiteralNative) -> JsValue { + match literal { + LiteralNative::Address(literal) => { + let js_string = literal.to_string(); + (&js_string).into() + } + LiteralNative::Boolean(literal) => { + let js_boolean = **literal; + js_boolean.into() + } + LiteralNative::Field(field) => { + let js_string = field.to_string(); + (&js_string).into() + } + LiteralNative::Group(group) => { + let js_string = group.to_string(); + (&js_string).into() + } + LiteralNative::I8(literal) => { + let js_number = **literal; + js_number.into() + } + LiteralNative::I16(literal) => { + let js_number = **literal; + js_number.into() + } + LiteralNative::I32(literal) => { + let js_number = **literal; + js_number.into() + } + LiteralNative::I64(literal) => { + let js_number = **literal; + js_number.into() + } + LiteralNative::I128(literal) => { + let js_number = **literal; + js_number.into() + } + LiteralNative::U8(literal) => { + let js_number = **literal; + js_number.into() + } + LiteralNative::U16(literal) => { + let js_number = **literal; + js_number.into() + } + LiteralNative::U32(literal) => { + let js_number = **literal; + js_number.into() + } + LiteralNative::U64(literal) => { + let js_number = **literal; + js_number.into() + } + LiteralNative::U128(literal) => { + let js_number = **literal; + js_number.into() + } + LiteralNative::Scalar(literal) => { + let js_string = literal.to_string(); + (&js_string).into() + } + LiteralNative::Signature(literal) => { + let js_string = literal.to_string(); + (&js_string).into() + } + LiteralNative::String(literal) => { + let js_string = literal.to_string(); + (&js_string).into() + } + } +} diff --git a/wasm/src/types/helpers/mod.rs b/wasm/src/types/helpers/mod.rs new file mode 100644 index 000000000..7975b9b9a --- /dev/null +++ b/wasm/src/types/helpers/mod.rs @@ -0,0 +1,24 @@ +// Copyright (C) 2019-2023 Aleo Systems Inc. +// This file is part of the Aleo SDK library. + +// The Aleo SDK library is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The Aleo SDK library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with the Aleo SDK library. If not, see <https://www.gnu.org/licenses/>. + +pub mod literal; +pub use literal::literal_to_js_value; + +pub mod plaintext; +pub use plaintext::insert_plaintext; + +pub mod record; +pub use record::record_to_js_object; diff --git a/wasm/src/types/helpers/plaintext.rs b/wasm/src/types/helpers/plaintext.rs new file mode 100644 index 000000000..39e203c4a --- /dev/null +++ b/wasm/src/types/helpers/plaintext.rs @@ -0,0 +1,69 @@ +// Copyright (C) 2019-2023 Aleo Systems Inc. +// This file is part of the Aleo SDK library. + +// The Aleo SDK library is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The Aleo SDK library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with the Aleo SDK library. If not, see <https://www.gnu.org/licenses/>. + +use crate::types::{ + helpers::literal_to_js_value, + native::{IdentifierNative, PlaintextNative}, +}; + +use indexmap::IndexMap; +use js_sys::{Object, Reflect}; +use wasm_bindgen::JsValue; + +/// Insert a plaintext value into a javascript object. +pub fn insert_plaintext(js_object: &Object, key: &IdentifierNative, plaintext: &PlaintextNative) { + match plaintext { + PlaintextNative::Literal(literal, _) => { + let js_value = literal_to_js_value(literal); + Reflect::set(js_object, &key.to_string().into(), &js_value.into()).unwrap(); + } + PlaintextNative::Struct(struct_members, ..) => { + let struct_object = struct_to_js_object(struct_members); + Reflect::set(&js_object, &key.to_string().into(), &struct_object.into()).unwrap(); + } + PlaintextNative::Array(plaintext, ..) => { + let js_array = js_sys::Array::new(); + for value in plaintext.iter() { + js_array.push(&plaintext_to_js_value(value)); + } + Reflect::set(&js_object, &key.to_string().into(), &js_array.into()).unwrap(); + } + } +} + +/// Convert a plaintext to a javascript value. +pub fn plaintext_to_js_value(plaintext: &PlaintextNative) -> JsValue { + match plaintext { + PlaintextNative::Literal(literal, _) => literal_to_js_value(literal), + PlaintextNative::Struct(struct_members, ..) => JsValue::from(struct_to_js_object(struct_members)), + PlaintextNative::Array(plaintext, ..) => { + let js_array = js_sys::Array::new(); + for value in plaintext.iter() { + js_array.push(&plaintext_to_js_value(value)); + } + JsValue::from(js_array) + } + } +} + +/// Make a struct into a javascript object. +pub fn struct_to_js_object(struct_members: &IndexMap<IdentifierNative, PlaintextNative>) -> Object { + let js_object = Object::new(); + for (key, value) in struct_members { + insert_plaintext(&js_object, key, value); + } + js_object +} diff --git a/wasm/src/types/helpers/record.rs b/wasm/src/types/helpers/record.rs new file mode 100644 index 000000000..50eae34ac --- /dev/null +++ b/wasm/src/types/helpers/record.rs @@ -0,0 +1,53 @@ +// Copyright (C) 2019-2023 Aleo Systems Inc. +// This file is part of the Aleo SDK library. + +// The Aleo SDK library is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The Aleo SDK library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with the Aleo SDK library. If not, see <https://www.gnu.org/licenses/>. + +use crate::types::{ + helpers::insert_plaintext, + native::{EntryNative, RecordPlaintextNative}, +}; +use js_sys::{Object, Reflect}; + +/// Convert a record to a javascript object. +pub fn record_to_js_object(record: &RecordPlaintextNative) -> Result<Object, String> { + // Create a new javascript object to house the record data. + let js_record = Object::new(); + + // Insert the owner into the javascript object. + let owner = record.owner().to_string(); + Reflect::set(&js_record, &"owner".into(), &owner.into()).unwrap(); + + // Get the metadata from the record and insert it into the javascript object. + record.data().iter().for_each(|(key, value)| { + match value { + EntryNative::Public(plaintext) => { + insert_plaintext(&js_record, key, plaintext); + } + EntryNative::Constant(plaintext) => { + insert_plaintext(&js_record, key, plaintext); + } + EntryNative::Private(plaintext) => { + insert_plaintext(&js_record, key, plaintext); + } + }; + }); + + // Insert the nonce into the javascript object. + let nonce = record.nonce().to_string(); + Reflect::set(&js_record, &"_nonce".into(), &nonce.into()).unwrap(); + + // Return the javascript object representation. + Ok(js_record) +} diff --git a/wasm/src/types/mod.rs b/wasm/src/types/mod.rs index 2e5972453..ee613970a 100644 --- a/wasm/src/types/mod.rs +++ b/wasm/src/types/mod.rs @@ -15,8 +15,16 @@ // along with the Aleo SDK library. If not, see <https://www.gnu.org/licenses/>. pub mod field; -pub use field::*; +pub use field::Field; + +pub mod group; +pub use group::Group; + +pub mod helpers; mod networks; pub(crate) mod native; + +mod scalar; +pub use scalar::Scalar; diff --git a/wasm/src/types/native.rs b/wasm/src/types/native.rs index 0533c489b..eefc3b5ee 100644 --- a/wasm/src/types/native.rs +++ b/wasm/src/types/native.rs @@ -17,7 +17,7 @@ pub use super::networks::*; pub use snarkvm_console::{ - account::{Address, PrivateKey, Signature, ViewKey}, + account::{Address, ComputeKey, GraphKey, PrivateKey, Signature, ViewKey}, network::Network, program::{ Ciphertext, @@ -33,16 +33,16 @@ pub use snarkvm_console::{ Response, ValueType, }, - types::Field, + types::{Field, Group, Scalar}, }; pub use snarkvm_ledger_block::{Execution, Transaction}; pub use snarkvm_ledger_query::Query; pub use snarkvm_ledger_store::helpers::memory::BlockMemory; pub use snarkvm_synthesizer::{ - process::{cost_in_microcredits, deployment_cost}, - snark::{ProvingKey, VerifyingKey}, Process, Program, + process::{cost_in_microcredits, deployment_cost}, + snark::{ProvingKey, VerifyingKey}, }; pub use snarkvm_wasm::{ console::network::Environment, @@ -52,16 +52,20 @@ pub use snarkvm_wasm::{ // Account types pub type AddressNative = Address<CurrentNetwork>; +pub type ComputeKeyNative = ComputeKey<CurrentNetwork>; +pub type GraphKeyNative = GraphKey<CurrentNetwork>; pub type PrivateKeyNative = PrivateKey<CurrentNetwork>; pub type SignatureNative = Signature<CurrentNetwork>; pub type ViewKeyNative = ViewKey<CurrentNetwork>; // Algebraic types pub type FieldNative = Field<CurrentNetwork>; +pub type GroupNative = Group<CurrentNetwork>; +pub type ScalarNative = Scalar<CurrentNetwork>; // Record types pub type CiphertextNative = Ciphertext<CurrentNetwork>; -pub type PlaintextNative = Plaintext<CurrentNetwork>; +pub type EntryNative = Entry<CurrentNetwork, PlaintextNative>; pub type RecordCiphertextNative = Record<CurrentNetwork, CiphertextNative>; pub type RecordPlaintextNative = Record<CurrentNetwork, PlaintextNative>; @@ -70,6 +74,7 @@ type CurrentBlockMemory = BlockMemory<CurrentNetwork>; pub type ExecutionNative = Execution<CurrentNetwork>; pub type IdentifierNative = Identifier<CurrentNetwork>; pub type LiteralNative = Literal<CurrentNetwork>; +pub type PlaintextNative = Plaintext<CurrentNetwork>; pub type ProcessNative = Process<CurrentNetwork>; pub type ProgramIDNative = ProgramID<CurrentNetwork>; pub type ProgramNative = Program<CurrentNetwork>; diff --git a/wasm/src/types/scalar.rs b/wasm/src/types/scalar.rs new file mode 100644 index 000000000..b1c9ad997 --- /dev/null +++ b/wasm/src/types/scalar.rs @@ -0,0 +1,128 @@ +// Copyright (C) 2019-2023 Aleo Systems Inc. +// This file is part of the Aleo SDK library. + +// The Aleo SDK library is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The Aleo SDK library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with the Aleo SDK library. If not, see <https://www.gnu.org/licenses/>. + +use crate::types::native::{PlaintextNative, ScalarNative, Uniform}; +use snarkvm_console::prelude::{Double, One, Pow}; +use std::ops::Deref; + +use crate::{Plaintext, types::native::LiteralNative}; +use once_cell::sync::OnceCell; +use std::str::FromStr; +use wasm_bindgen::prelude::wasm_bindgen; + +#[wasm_bindgen] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct Scalar(ScalarNative); + +#[wasm_bindgen] +impl Scalar { + /// Returns the string representation of the group. + #[wasm_bindgen(js_name = "toString")] + #[allow(clippy::inherent_to_string)] + pub fn to_string(&self) -> String { + self.0.to_string() + } + + /// Create a plaintext element from a group element. + #[wasm_bindgen(js_name = "toPlaintext")] + pub fn to_plaintext(&self) -> Plaintext { + Plaintext::from(PlaintextNative::Literal(LiteralNative::Scalar(self.0), OnceCell::new())) + } + + /// Creates a group object from a string representation of a group. + #[wasm_bindgen(js_name = "fromString")] + pub fn from_string(group: &str) -> Result<Scalar, String> { + Ok(Self(ScalarNative::from_str(group).map_err(|e| e.to_string())?)) + } + + /// Generate a random group element. + pub fn random() -> Scalar { + let rng = &mut rand::thread_rng(); + Scalar(ScalarNative::rand(rng)) + } + + /// Add two field elements. + pub fn add(&self, other: &Scalar) -> Scalar { + Scalar(self.0 + other.0) + } + + /// Subtract two field elements. + pub fn subtract(&self, other: &Scalar) -> Scalar { + Scalar(self.0 - other.0) + } + + /// Multiply two field elements. + pub fn multiply(&self, other: &Scalar) -> Scalar { + Scalar(self.0 * other.0) + } + + /// Divide two field elements. + pub fn divide(&self, other: &Scalar) -> Scalar { + Scalar(self.0 / other.0) + } + + /// Double the scalar element. + pub fn double(&self) -> Scalar { + Scalar(self.0.double()) + } + + /// Power of a field element. + pub fn pow(&self, other: &Scalar) -> Scalar { + Scalar(self.0.pow(other.0)) + } + + /// Invert the field element. + pub fn inverse(&self) -> Scalar { + Scalar(-self.0) + } + + /// Creates a one valued element of the scalar field. + pub fn one() -> Scalar { + Scalar(ScalarNative::one()) + } +} + +impl Deref for Scalar { + type Target = ScalarNative; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl From<ScalarNative> for Scalar { + fn from(native: ScalarNative) -> Self { + Self(native) + } +} + +impl From<Scalar> for ScalarNative { + fn from(scalar: Scalar) -> Self { + scalar.0 + } +} + +impl From<&ScalarNative> for Scalar { + fn from(native: &ScalarNative) -> Self { + Self(*native) + } +} + +impl From<&Scalar> for ScalarNative { + fn from(scalar: &Scalar) -> Self { + scalar.0 + } +}