diff --git a/zk-sdk/src/encryption/auth_encryption.rs b/zk-sdk/src/encryption/auth_encryption.rs index b8a9d19d630327..800ea7c2de20c7 100644 --- a/zk-sdk/src/encryption/auth_encryption.rs +++ b/zk-sdk/src/encryption/auth_encryption.rs @@ -2,6 +2,8 @@ //! //! This module is a simple wrapper of the `Aes128GcmSiv` implementation specialized for SPL //! token-2022 where the plaintext is always `u64`. +#[cfg(target_arch = "wasm32")] +use wasm_bindgen::prelude::*; use { crate::{ encryption::{AE_CIPHERTEXT_LEN, AE_KEY_LEN}, @@ -85,12 +87,15 @@ impl AuthenticatedEncryption { } } +#[cfg_attr(target_arch = "wasm32", wasm_bindgen)] #[derive(Clone, Debug, Zeroize, Eq, PartialEq)] pub struct AeKey([u8; AE_KEY_LEN]); +#[cfg_attr(target_arch = "wasm32", wasm_bindgen)] impl AeKey { /// Generates a random authenticated encryption key. /// /// This function is randomized. It internally samples a scalar element using `OsRng`. + #[cfg_attr(target_arch = "wasm32", wasm_bindgen(js_name = newRand))] pub fn new_rand() -> Self { AuthenticatedEncryption::keygen() } @@ -240,6 +245,7 @@ type Nonce = [u8; NONCE_LEN]; type Ciphertext = [u8; CIPHERTEXT_LEN]; /// Authenticated encryption nonce and ciphertext +#[cfg_attr(target_arch = "wasm32", wasm_bindgen)] #[derive(Clone, Copy, Debug, Default)] pub struct AeCiphertext { nonce: Nonce, diff --git a/zk-sdk/src/encryption/elgamal.rs b/zk-sdk/src/encryption/elgamal.rs index 9509e91f23a787..cc11c6941cd0b1 100644 --- a/zk-sdk/src/encryption/elgamal.rs +++ b/zk-sdk/src/encryption/elgamal.rs @@ -158,6 +158,7 @@ impl ElGamalKeypair { /// Generates the public and secret keys for ElGamal encryption. /// /// This function is randomized. It internally samples a scalar element using `OsRng`. + #[cfg_attr(target_arch = "wasm32", wasm_bindgen(js_name = newRand))] pub fn new_rand() -> Self { ElGamal::keygen() } @@ -615,6 +616,7 @@ impl ConstantTimeEq for ElGamalSecretKey { } /// Ciphertext for the ElGamal encryption scheme. +#[cfg_attr(target_arch = "wasm32", wasm_bindgen)] #[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] pub struct ElGamalCiphertext { pub commitment: PedersenCommitment, @@ -750,6 +752,7 @@ define_mul_variants!( ); /// Decryption handle for Pedersen commitment. +#[cfg_attr(target_arch = "wasm32", wasm_bindgen)] #[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] pub struct DecryptHandle(RistrettoPoint); impl DecryptHandle { diff --git a/zk-sdk/src/encryption/pedersen.rs b/zk-sdk/src/encryption/pedersen.rs index 2dc20cb0b520bf..9f6a083e0b875c 100644 --- a/zk-sdk/src/encryption/pedersen.rs +++ b/zk-sdk/src/encryption/pedersen.rs @@ -1,5 +1,7 @@ //! Pedersen commitment implementation using the Ristretto prime-order group. +#[cfg(target_arch = "wasm32")] +use wasm_bindgen::prelude::*; use { crate::encryption::{PEDERSEN_COMMITMENT_LEN, PEDERSEN_OPENING_LEN}, core::ops::{Add, Mul, Sub}, @@ -165,6 +167,7 @@ define_mul_variants!( ); /// Pedersen commitment type. +#[cfg_attr(target_arch = "wasm32", wasm_bindgen)] #[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] pub struct PedersenCommitment(RistrettoPoint); impl PedersenCommitment { diff --git a/zk-sdk/src/encryption/pod/auth_encryption.rs b/zk-sdk/src/encryption/pod/auth_encryption.rs index 8496e771155cc4..459b3a10b9d51a 100644 --- a/zk-sdk/src/encryption/pod/auth_encryption.rs +++ b/zk-sdk/src/encryption/pod/auth_encryption.rs @@ -5,21 +5,29 @@ use crate::{encryption::auth_encryption::AeCiphertext, errors::AuthenticatedEncr use { crate::{ encryption::AE_CIPHERTEXT_LEN, - pod::{impl_from_bytes, impl_from_str}, + pod::{impl_from_bytes, impl_from_str, impl_wasm_bindings}, }, base64::{prelude::BASE64_STANDARD, Engine}, bytemuck::{Pod, Zeroable}, std::fmt, }; +#[cfg(target_arch = "wasm32")] +use { + js_sys::{Array, Uint8Array}, + wasm_bindgen::prelude::*, +}; /// Maximum length of a base64 encoded authenticated encryption ciphertext const AE_CIPHERTEXT_MAX_BASE64_LEN: usize = 48; /// The `AeCiphertext` type as a `Pod`. +#[cfg_attr(target_arch = "wasm32", wasm_bindgen)] #[derive(Clone, Copy, PartialEq, Eq)] #[repr(transparent)] pub struct PodAeCiphertext(pub(crate) [u8; AE_CIPHERTEXT_LEN]); +impl_wasm_bindings!(POD_TYPE = PodAeCiphertext, DECODED_TYPE = AeCiphertext); + // `PodAeCiphertext` is a wrapper type for a byte array, which is both `Pod` and `Zeroable`. However, // the marker traits `bytemuck::Pod` and `bytemuck::Zeroable` can only be derived for power-of-two // length byte arrays. Directly implement these traits for `PodAeCiphertext`. diff --git a/zk-sdk/src/encryption/pod/elgamal.rs b/zk-sdk/src/encryption/pod/elgamal.rs index 1dac45378b588c..42c6e7e1ce0bf7 100644 --- a/zk-sdk/src/encryption/pod/elgamal.rs +++ b/zk-sdk/src/encryption/pod/elgamal.rs @@ -1,5 +1,7 @@ //! Plain Old Data types for the ElGamal encryption scheme. +#[cfg(not(target_arch = "wasm32"))] +use bytemuck::Zeroable; #[cfg(not(target_os = "solana"))] use { crate::{ @@ -11,10 +13,9 @@ use { use { crate::{ encryption::{DECRYPT_HANDLE_LEN, ELGAMAL_CIPHERTEXT_LEN, ELGAMAL_PUBKEY_LEN}, - pod::{impl_from_bytes, impl_from_str}, + pod::{impl_from_bytes, impl_from_str, impl_wasm_bindings}, }, base64::{prelude::BASE64_STANDARD, Engine}, - bytemuck::Zeroable, std::fmt, }; #[cfg(target_arch = "wasm32")] @@ -33,10 +34,16 @@ const ELGAMAL_CIPHERTEXT_MAX_BASE64_LEN: usize = 88; const DECRYPT_HANDLE_MAX_BASE64_LEN: usize = 44; /// The `ElGamalCiphertext` type as a `Pod`. +#[cfg_attr(target_arch = "wasm32", wasm_bindgen)] #[derive(Clone, Copy, bytemuck_derive::Pod, bytemuck_derive::Zeroable, PartialEq, Eq)] #[repr(transparent)] pub struct PodElGamalCiphertext(pub(crate) [u8; ELGAMAL_CIPHERTEXT_LEN]); +impl_wasm_bindings!( + POD_TYPE = PodElGamalCiphertext, + DECODED_TYPE = ElGamalCiphertext +); + impl fmt::Debug for PodElGamalCiphertext { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{:?}", self.0) @@ -83,78 +90,12 @@ impl TryFrom for ElGamalCiphertext { } /// The `ElGamalPubkey` type as a `Pod`. +#[cfg_attr(target_arch = "wasm32", wasm_bindgen)] #[derive(Clone, Copy, Default, bytemuck_derive::Pod, bytemuck_derive::Zeroable, PartialEq, Eq)] #[repr(transparent)] -#[cfg_attr(target_arch = "wasm32", wasm_bindgen)] pub struct PodElGamalPubkey(pub(crate) [u8; ELGAMAL_PUBKEY_LEN]); -#[cfg(target_arch = "wasm32")] -#[allow(non_snake_case)] -#[cfg_attr(target_arch = "wasm32", wasm_bindgen)] -impl PodElGamalPubkey { - /// 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(|e| e.to_string().into()) - } 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| *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| *pubkey) - } else if value.is_undefined() { - Ok(PodElGamalPubkey::default()) - } else { - Err("Unsupported argument".into()) - } - } - - /// Return the base64 string representation of the public key - pub fn toString(&self) -> String { - self.to_string() - } - - /// Checks if two `ElGamalPubkey`s are equal - pub fn equals(&self, other: &PodElGamalPubkey) -> bool { - self == other - } - - /// Return the `Uint8Array` representation of the public key - pub fn toBytes(&self) -> Box<[u8]> { - self.0.into() - } - - pub fn compressed(decoded: &ElGamalPubkey) -> PodElGamalPubkey { - (*decoded).into() - } - - pub fn decompressed(&self) -> Result { - (*self) - .try_into() - .map_err(|err| JsValue::from(format!("Invalid ElGamalPubkey: {err:?}"))) - } -} +impl_wasm_bindings!(POD_TYPE = PodElGamalPubkey, DECODED_TYPE = ElGamalPubkey); impl fmt::Debug for PodElGamalPubkey { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { diff --git a/zk-sdk/src/pod.rs b/zk-sdk/src/pod.rs index d939e4735bda7d..85f93cc1d5a8e3 100644 --- a/zk-sdk/src/pod.rs +++ b/zk-sdk/src/pod.rs @@ -62,3 +62,75 @@ macro_rules! impl_from_bytes { }; } pub(crate) use impl_from_bytes; + +macro_rules! impl_wasm_bindings { + (POD_TYPE = $pod_type:ident, DECODED_TYPE = $decoded_type: ident) => { + #[cfg(target_arch = "wasm32")] + #[allow(non_snake_case)] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen)] + impl $pod_type { + #[wasm_bindgen(constructor)] + pub fn constructor(value: JsValue) -> Result<$pod_type, JsValue> { + if let Some(base64_str) = value.as_string() { + base64_str + .parse::<$pod_type>() + .map_err(|e| e.to_string().into()) + } 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: {err:?}"))) + .map(|value| *value) + } 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: {err:?}"))) + .map(|value| *value) + } else if value.is_undefined() { + Ok($pod_type::default()) + } else { + Err("Unsupported argument".into()) + } + } + + pub fn toString(&self) -> String { + self.to_string() + } + + pub fn equals(&self, other: &$pod_type) -> bool { + self == other + } + + pub fn toBytes(&self) -> Box<[u8]> { + self.0.into() + } + + pub fn zeroed() -> Self { + Self::default() + } + + pub fn encode(decoded: &$decoded_type) -> $pod_type { + (*decoded).into() + } + + pub fn decode(&self) -> Result<$decoded_type, JsValue> { + (*self) + .try_into() + .map_err(|err| JsValue::from(format!("Invalid encoding: {err:?}"))) + } + } + }; +} +pub(crate) use impl_wasm_bindings;