diff --git a/zk-sdk/src/encryption/elgamal.rs b/zk-sdk/src/encryption/elgamal.rs index 89e236a31e5d5c..8d13af8054a79d 100644 --- a/zk-sdk/src/encryption/elgamal.rs +++ b/zk-sdk/src/encryption/elgamal.rs @@ -53,6 +53,7 @@ use { sha3::Sha3_512, std::{convert::TryInto, fmt}, subtle::{Choice, ConstantTimeEq}, + wasm_bindgen::prelude::*, zeroize::Zeroize, }; @@ -143,6 +144,7 @@ impl ElGamal { /// A (twisted) ElGamal encryption keypair. /// /// The instances of the secret key are zeroized on drop. +#[wasm_bindgen] #[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, Zeroize)] pub struct ElGamalKeypair { /// The public half of this keypair. @@ -151,6 +153,20 @@ pub struct ElGamalKeypair { secret: ElGamalSecretKey, } +#[wasm_bindgen] +impl ElGamalKeypair { + /// Generates the public and secret keys for ElGamal encryption. + /// + /// This function is randomized. It internally samples a scalar element using `OsRng`. + pub fn new_rand() -> Self { + ElGamal::keygen() + } + + pub fn pubkey_owned(&self) -> ElGamalPubkey { + self.public + } +} + impl ElGamalKeypair { /// Create an ElGamal keypair from an ElGamal public key and an ElGamal secret key. /// @@ -197,13 +213,6 @@ impl ElGamalKeypair { Ok(Self::new(secret)) } - /// Generates the public and secret keys for ElGamal encryption. - /// - /// This function is randomized. It internally samples a scalar element using `OsRng`. - pub fn new_rand() -> Self { - ElGamal::keygen() - } - pub fn pubkey(&self) -> &ElGamalPubkey { &self.public } @@ -327,6 +336,7 @@ impl EncodableKeypair for ElGamalKeypair { } /// Public key for the ElGamal encryption scheme. +#[wasm_bindgen] #[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize, Zeroize)] pub struct ElGamalPubkey(RistrettoPoint); impl ElGamalPubkey { diff --git a/zk-sdk/src/encryption/mod.rs b/zk-sdk/src/encryption/mod.rs index 28a9ae6bf7fded..3d71628441944f 100644 --- a/zk-sdk/src/encryption/mod.rs +++ b/zk-sdk/src/encryption/mod.rs @@ -26,6 +26,8 @@ pub mod grouped_elgamal; #[cfg(not(target_os = "solana"))] pub mod pedersen; pub mod pod; +#[cfg(not(target_os = "solana"))] +pub mod wasm; /// Byte length of an authenticated encryption secret key pub const AE_KEY_LEN: usize = 16; diff --git a/zk-sdk/src/encryption/wasm/elgamal.rs b/zk-sdk/src/encryption/wasm/elgamal.rs new file mode 100644 index 00000000000000..71064125ef7beb --- /dev/null +++ b/zk-sdk/src/encryption/wasm/elgamal.rs @@ -0,0 +1,82 @@ +use { + crate::{ + encryption::{elgamal::ElGamalPubkey, pod::elgamal::PodElGamalPubkey}, + wasm::display_to_jsvalue, + }, + bytemuck::{Pod, Zeroable}, + js_sys::{Array, Uint8Array}, + wasm_bindgen::{prelude::*, JsCast}, +}; + +#[wasm_bindgen] +#[derive(Clone, Copy, Default, Pod, Zeroable, PartialEq, Eq)] +#[repr(transparent)] +pub struct CompressedElGamalPubkey(PodElGamalPubkey); + +#[allow(non_snake_case)] +#[wasm_bindgen] +impl CompressedElGamalPubkey { + /// Create a new `PodElGamalPubkey` object + /// + /// * `value` - optional public key as a base64 encoded string, `Uint8Array`, `[number]` + #[wasm_bindgen(constructor)] + pub fn constructor(value: JsValue) -> Result { + if let Some(base64_str) = value.as_string() { + base64_str + .parse::() + .map_err(display_to_jsvalue) + .map(CompressedElGamalPubkey) + } else if let Some(uint8_array) = value.dyn_ref::() { + bytemuck::try_from_bytes(&uint8_array.to_vec()) + .map_err(|err| JsValue::from(format!("Invalid Uint8Array ElGamalPubkey: {err:?}"))) + .map(|pubkey| CompressedElGamalPubkey(*pubkey)) + } else if let Some(array) = value.dyn_ref::() { + let mut bytes = vec![]; + let iterator = js_sys::try_iter(&array.values())?.expect("array to be iterable"); + for x in iterator { + let x = x?; + + if let Some(n) = x.as_f64() { + if (0. ..=255.).contains(&n) { + bytes.push(n as u8); + continue; + } + } + return Err(format!("Invalid array argument: {:?}", x).into()); + } + + bytemuck::try_from_bytes(&bytes) + .map_err(|err| JsValue::from(format!("Invalid Array pubkey: {err:?}"))) + .map(|pubkey| CompressedElGamalPubkey(*pubkey)) + } else if value.is_undefined() { + Ok(Self(PodElGamalPubkey::default())) + } else { + Err("Unsupported argument".into()) + } + } + + /// Return the base64 string representation of the public key + pub fn toString(&self) -> String { + self.0.to_string() + } + + /// Checks if two `ElGamalPubkey`s are equal + pub fn equals(&self, other: &CompressedElGamalPubkey) -> bool { + self == other + } + + /// Return the `Uint8Array` representation of the public key + pub fn toBytes(&self) -> Box<[u8]> { + self.0 .0.into() + } + + pub fn compressed(decoded: &ElGamalPubkey) -> Self { + Self((*decoded).into()) + } + + pub fn decompressed(&self) -> Result { + self.0 + .try_into() + .map_err(|err| JsValue::from(format!("Invalid ElGamalPubkey: {err:?}"))) + } +} diff --git a/zk-sdk/src/encryption/wasm/mod.rs b/zk-sdk/src/encryption/wasm/mod.rs new file mode 100644 index 00000000000000..2ebf262efb4b8b --- /dev/null +++ b/zk-sdk/src/encryption/wasm/mod.rs @@ -0,0 +1 @@ +pub mod elgamal; diff --git a/zk-sdk/src/lib.rs b/zk-sdk/src/lib.rs index 8d7388475f2f36..8b0a829bb0d2bd 100644 --- a/zk-sdk/src/lib.rs +++ b/zk-sdk/src/lib.rs @@ -25,6 +25,8 @@ pub mod pod; mod range_proof; mod sigma_proofs; mod transcript; +#[cfg(not(target_os = "solana"))] +pub mod wasm; #[cfg(not(target_arch = "wasm32"))] pub mod zk_elgamal_proof_program; diff --git a/zk-sdk/src/wasm.rs b/zk-sdk/src/wasm.rs new file mode 100644 index 00000000000000..33f61a4daaa7e8 --- /dev/null +++ b/zk-sdk/src/wasm.rs @@ -0,0 +1,5 @@ +use wasm_bindgen::prelude::*; + +pub fn display_to_jsvalue(display: T) -> JsValue { + display.to_string().into() +}