diff --git a/CHANGELOG.md b/CHANGELOG.md index 39f60b08..fdfe99af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed + +- Replaced `AsBackend`/`FromBackend`, `.inner()`, `.new()`, and `pub backend` with derived `AsRef`/`From`/`Into` where appropriate. (#[103]) +- Using a workaround with `wasm-bindgen-derive` to support `Option<&T>` and `&Vec` arguments, and `Vec` return values in WASM bindings. Generating correct TypeScript signatures in all the relevant cases. Affected API: `Capsule.decryptReencrypted()`, `KeyFrag.verify()`, `generate_kfrags()`. (#[103]) +- Removed `serde` usage in WASM bindings. ([#103]) +- `encrypt()` now returns an actual tuple in WASM bindings instead of a special object. (#[103]) + + ### Added - `Eq` markers for the types that only had `PartialEq` before. ([#100]) @@ -20,6 +28,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [#100]: https://github.com/nucypher/rust-umbral/pull/100 [#101]: https://github.com/nucypher/rust-umbral/pull/101 [#102]: https://github.com/nucypher/rust-umbral/pull/102 +[#103]: https://github.com/nucypher/rust-umbral/pull/103 ## [0.6.0] - 2022-08-15 diff --git a/umbral-pre-wasm/README.md b/umbral-pre-wasm/README.md index 513ae2af..1a8c852f 100644 --- a/umbral-pre-wasm/README.md +++ b/umbral-pre-wasm/README.md @@ -36,10 +36,7 @@ let bob_pk = bob_sk.publicKey(); let plaintext = "Plaintext message"; let plaintext_bytes = enc.encode(plaintext); -// The API here slightly differs from that in Rust. -// Since wasm-pack does not support returning tuples, we return an object containing -// the ciphertext and the capsule. -let result = umbral.encrypt(alice_pk, plaintext_bytes); +let [capsule, ciphertext] = umbral.encrypt(alice_pk, plaintext_bytes); let ciphertext = result.ciphertext; let capsule = result.capsule; @@ -75,14 +72,8 @@ let cfrag1 = umbral.reencrypt(capsule, kfrags[1]); // Finally, Bob opens the capsule by using at least `threshold` cfrags, // and then decrypts the re-encrypted ciphertext. - -// Another deviation from the Rust API. -// wasm-pack does not support taking arrays as arguments, -// so we build a capsule+cfrags object before decryption. -let plaintext_bob = capsule - .withCFrag(cfrag0) - .withCFrag(cfrag1) - .decryptReencrypted(bob_sk, alice_pk, ciphertext); +let plaintext_bob = umbral.decryptReencrypted( + bob_sk, alice_pk, capsule, [cfrag0, cfrag1], ciphertext); console.assert(dec.decode(plaintext_bob) === plaintext, "decryptReencrypted() failed"); ``` diff --git a/umbral-pre-wasm/examples/bundler/index.js b/umbral-pre-wasm/examples/bundler/index.js index 07f011fd..c5bc7b3e 100644 --- a/umbral-pre-wasm/examples/bundler/index.js +++ b/umbral-pre-wasm/examples/bundler/index.js @@ -24,12 +24,7 @@ let bob_pk = bob_sk.publicKey(); let plaintext = "Plaintext message"; let plaintext_bytes = enc.encode(plaintext); -// The API here slightly differs from that in Rust. -// Since wasm-pack does not support returning tuples, we return an object containing -// the ciphertext and the capsule. -let result = umbral.encrypt(alice_pk, plaintext_bytes); -let ciphertext = result.ciphertext; -let capsule = result.capsule; +let [capsule, ciphertext] = umbral.encrypt(alice_pk, plaintext_bytes); // Since data was encrypted with Alice's public key, Alice can open the capsule // and decrypt the ciphertext with her private key. @@ -73,10 +68,8 @@ let cfrag1 = umbral.reencrypt(capsule, kfrags[1]); // Another deviation from the Rust API. // wasm-pack does not support taking arrays as arguments, // so we build a capsule+cfrags object before decryption. -let plaintext_bob = capsule - .withCFrag(cfrag0) - .withCFrag(cfrag1) - .decryptReencrypted(bob_sk, alice_pk, ciphertext); +let plaintext_bob = umbral.decryptReencrypted( + bob_sk, alice_pk, capsule, [cfrag0, cfrag1], ciphertext); console.assert(dec.decode(plaintext_bob) === plaintext, "decryptReencrypted() failed"); diff --git a/umbral-pre-wasm/examples/node/index.js b/umbral-pre-wasm/examples/node/index.js index 07f011fd..c5bc7b3e 100644 --- a/umbral-pre-wasm/examples/node/index.js +++ b/umbral-pre-wasm/examples/node/index.js @@ -24,12 +24,7 @@ let bob_pk = bob_sk.publicKey(); let plaintext = "Plaintext message"; let plaintext_bytes = enc.encode(plaintext); -// The API here slightly differs from that in Rust. -// Since wasm-pack does not support returning tuples, we return an object containing -// the ciphertext and the capsule. -let result = umbral.encrypt(alice_pk, plaintext_bytes); -let ciphertext = result.ciphertext; -let capsule = result.capsule; +let [capsule, ciphertext] = umbral.encrypt(alice_pk, plaintext_bytes); // Since data was encrypted with Alice's public key, Alice can open the capsule // and decrypt the ciphertext with her private key. @@ -73,10 +68,8 @@ let cfrag1 = umbral.reencrypt(capsule, kfrags[1]); // Another deviation from the Rust API. // wasm-pack does not support taking arrays as arguments, // so we build a capsule+cfrags object before decryption. -let plaintext_bob = capsule - .withCFrag(cfrag0) - .withCFrag(cfrag1) - .decryptReencrypted(bob_sk, alice_pk, ciphertext); +let plaintext_bob = umbral.decryptReencrypted( + bob_sk, alice_pk, capsule, [cfrag0, cfrag1], ciphertext); console.assert(dec.decode(plaintext_bob) === plaintext, "decryptReencrypted() failed"); diff --git a/umbral-pre-wasm/examples/react/src/App.js b/umbral-pre-wasm/examples/react/src/App.js index 3141a11c..81d24a99 100644 --- a/umbral-pre-wasm/examples/react/src/App.js +++ b/umbral-pre-wasm/examples/react/src/App.js @@ -49,12 +49,7 @@ function App() { let plaintext_bytes = enc.encode(plaintext); - // The API here slightly differs from that in Rust. - // Since wasm-pack does not support returning tuples, we return an object containing - // the ciphertext and the capsule. - let result = umbral.encrypt(alice_pk, plaintext_bytes); - let ciphertext = result.ciphertext; - let capsule = result.capsule; + let [capsule, ciphertext] = umbral.encrypt(alice_pk, plaintext_bytes); // Since data was encrypted with Alice's public key, Alice can open the capsule // and decrypt the ciphertext with her private key. @@ -106,10 +101,8 @@ function App() { // Another deviation from the Rust API. // wasm-pack does not support taking arrays as arguments, // so we build a capsule+cfrags object before decryption. - let plaintext_bob = capsule - .withCFrag(cfrag0) - .withCFrag(cfrag1) - .decryptReencrypted(bob_sk, alice_pk, ciphertext); + let plaintext_bob = umbral.decryptReencrypted( + bob_sk, alice_pk, capsule, [cfrag0, cfrag1], ciphertext); console.assert( dec.decode(plaintext_bob) === plaintext, diff --git a/umbral-pre-wasm/examples/webpack-5-experiments/index.js b/umbral-pre-wasm/examples/webpack-5-experiments/index.js index 07f011fd..aab52dcd 100644 --- a/umbral-pre-wasm/examples/webpack-5-experiments/index.js +++ b/umbral-pre-wasm/examples/webpack-5-experiments/index.js @@ -27,7 +27,7 @@ let plaintext_bytes = enc.encode(plaintext); // The API here slightly differs from that in Rust. // Since wasm-pack does not support returning tuples, we return an object containing // the ciphertext and the capsule. -let result = umbral.encrypt(alice_pk, plaintext_bytes); +let {capsule, ciphertext} = umbral.encrypt(alice_pk, plaintext_bytes); let ciphertext = result.ciphertext; let capsule = result.capsule; @@ -73,10 +73,8 @@ let cfrag1 = umbral.reencrypt(capsule, kfrags[1]); // Another deviation from the Rust API. // wasm-pack does not support taking arrays as arguments, // so we build a capsule+cfrags object before decryption. -let plaintext_bob = capsule - .withCFrag(cfrag0) - .withCFrag(cfrag1) - .decryptReencrypted(bob_sk, alice_pk, ciphertext); +let plaintext_bob = umbral.decryptReencrypted( + bob_sk, alice_pk, capsule, [cfrag0, cfrag1], ciphertext); console.assert(dec.decode(plaintext_bob) === plaintext, "decryptReencrypted() failed"); diff --git a/umbral-pre/Cargo.toml b/umbral-pre/Cargo.toml index 2be47f6a..71f36e19 100644 --- a/umbral-pre/Cargo.toml +++ b/umbral-pre/Cargo.toml @@ -19,7 +19,9 @@ serde = { version = "1", default-features = false, features = ["derive"], option base64 = { version = "0.13", default-features = false, features = ["alloc"] } pyo3 = { version = "0.16", optional = true } js-sys = { version = "0.3", optional = true } -wasm-bindgen = {version = "0.2.74", optional = true } +wasm-bindgen = { version = "0.2.74", optional = true } +derive_more = { version = "0.99", optional = true, default_features = false, features = ["as_ref", "from", "into"] } +wasm-bindgen-derive = { version = "0.1", optional = true } # These packages are among the dependencies of the packages above. # Their versions should be updated when the main packages above are updated. @@ -43,8 +45,8 @@ rmp-serde = "0.15" [features] default = ["default-rng"] bench-internals = ["default-rng"] -bindings-python = ["pyo3", "std"] -bindings-wasm = [ "serde-support", "js-sys", "wasm-bindgen"] +bindings-python = ["pyo3", "std", "derive_more"] +bindings-wasm = ["js-sys", "wasm-bindgen", "derive_more", "wasm-bindgen-derive"] default-rng = ["getrandom", "rand_core/getrandom"] serde-support = ["serde"] std = [] diff --git a/umbral-pre/src/bindings_python.rs b/umbral-pre/src/bindings_python.rs index c8398f00..3e2f3fb5 100644 --- a/umbral-pre/src/bindings_python.rs +++ b/umbral-pre/src/bindings_python.rs @@ -26,22 +26,12 @@ use crate::{ SerializableToSecretArray, }; -// Helper traits to generalize implementing various Python protocol functions for our types. - -trait AsBackend { - fn as_backend(&self) -> &T; -} - -trait FromBackend { - fn from_backend(backend: T) -> Self; -} - fn to_bytes(obj: &T) -> PyResult where - T: AsBackend, + T: AsRef, U: SerializableToArray, { - let serialized = obj.as_backend().to_array(); + let serialized = obj.as_ref().to_array(); Python::with_gil(|py| -> PyResult { Ok(PyBytes::new(py, serialized.as_slice()).into()) }) @@ -50,11 +40,11 @@ where // Can't keep the secret in Python anymore, so this function does the same as `to_bytes()` fn to_secret_bytes(obj: &T) -> PyResult where - T: AsBackend, + T: AsRef, U: SerializableToSecretArray, { // Dereferencing a secret. - let serialized = obj.as_backend().to_secret_array().as_secret().clone(); + let serialized = obj.as_ref().to_secret_array().as_secret().clone(); Python::with_gil(|py| -> PyResult { Ok(PyBytes::new(py, serialized.as_slice()).into()) }) @@ -62,20 +52,20 @@ where fn from_bytes(data: &[u8]) -> PyResult where - T: FromBackend, + T: From, U: DeserializableFromArray + HasTypeName, { U::from_bytes(data) - .map(T::from_backend) + .map(T::from) .map_err(|err| PyValueError::new_err(format!("{}", err))) } fn hash(obj: &T) -> PyResult where - T: AsBackend, + T: AsRef, U: SerializableToArray + HasTypeName, { - let serialized = obj.as_backend().to_array(); + let serialized = obj.as_ref().to_array(); // call `hash((class_name, bytes(obj)))` Python::with_gil(|py| { @@ -88,7 +78,7 @@ where fn richcmp(obj: &T, other: PyRef<'_, T>, op: CompareOp) -> PyResult where - T: PyClass + PartialEq + AsBackend, + T: PyClass + PartialEq + AsRef, U: HasTypeName, { match op { @@ -104,20 +94,9 @@ where create_exception!(umbral, VerificationError, PyException); #[pyclass(module = "umbral")] +#[derive(derive_more::AsRef, derive_more::From)] pub struct SecretKey { - pub backend: umbral_pre::SecretKey, -} - -impl AsBackend for SecretKey { - fn as_backend(&self) -> &umbral_pre::SecretKey { - &self.backend - } -} - -impl FromBackend for SecretKey { - fn from_backend(backend: umbral_pre::SecretKey) -> Self { - Self { backend } - } + backend: umbral_pre::SecretKey, } #[pymethods] @@ -141,7 +120,7 @@ impl SecretKey { #[staticmethod] pub fn from_bytes(data: &[u8]) -> PyResult { - from_bytes(data) + from_bytes::<_, umbral_pre::SecretKey>(data) } #[staticmethod] @@ -155,29 +134,16 @@ impl SecretKey { } #[pyclass(module = "umbral")] +#[derive(derive_more::AsRef, derive_more::From)] pub struct SecretKeyFactory { backend: umbral_pre::SecretKeyFactory, } -impl AsBackend for SecretKeyFactory { - fn as_backend(&self) -> &umbral_pre::SecretKeyFactory { - &self.backend - } -} - -impl FromBackend for SecretKeyFactory { - fn from_backend(backend: umbral_pre::SecretKeyFactory) -> Self { - Self { backend } - } -} - #[pymethods] impl SecretKeyFactory { #[staticmethod] pub fn random() -> Self { - Self { - backend: umbral_pre::SecretKeyFactory::random(), - } + umbral_pre::SecretKeyFactory::random().into() } #[staticmethod] @@ -188,22 +154,16 @@ impl SecretKeyFactory { #[staticmethod] pub fn from_secure_randomness(seed: &[u8]) -> PyResult { umbral_pre::SecretKeyFactory::from_secure_randomness(seed) - .map(|backend_sk| SecretKeyFactory { - backend: backend_sk, - }) + .map(SecretKeyFactory::from) .map_err(|err| PyValueError::new_err(format!("{}", err))) } pub fn make_key(&self, label: &[u8]) -> SecretKey { - SecretKey { - backend: self.backend.make_key(label), - } + self.backend.make_key(label).into() } pub fn make_factory(&self, label: &[u8]) -> Self { - Self { - backend: self.backend.make_factory(label), - } + self.backend.make_factory(label).into() } pub fn to_secret_bytes(&self) -> PyResult { @@ -212,7 +172,7 @@ impl SecretKeyFactory { #[staticmethod] pub fn from_bytes(data: &[u8]) -> PyResult { - from_bytes(data) + from_bytes::<_, umbral_pre::SecretKeyFactory>(data) } #[staticmethod] @@ -226,28 +186,16 @@ impl SecretKeyFactory { } #[pyclass(module = "umbral")] -#[derive(Clone, PartialEq, Eq)] +#[derive(Clone, PartialEq, Eq, derive_more::AsRef, derive_more::From, derive_more::Into)] pub struct PublicKey { - pub backend: umbral_pre::PublicKey, -} - -impl AsBackend for PublicKey { - fn as_backend(&self) -> &umbral_pre::PublicKey { - &self.backend - } -} - -impl FromBackend for PublicKey { - fn from_backend(backend: umbral_pre::PublicKey) -> Self { - Self { backend } - } + backend: umbral_pre::PublicKey, } #[pymethods] impl PublicKey { #[staticmethod] pub fn from_bytes(data: &[u8]) -> PyResult { - from_bytes(data) + from_bytes::<_, umbral_pre::PublicKey>(data) } #[staticmethod] @@ -273,35 +221,24 @@ impl PublicKey { } #[pyclass(module = "umbral")] +#[derive(derive_more::AsRef, derive_more::From)] pub struct Signer { - pub backend: umbral_pre::Signer, -} - -impl AsBackend for Signer { - fn as_backend(&self) -> &umbral_pre::Signer { - &self.backend - } + backend: umbral_pre::Signer, } #[pymethods] impl Signer { #[new] pub fn new(sk: &SecretKey) -> Self { - Self { - backend: umbral_pre::Signer::new(sk.backend.clone()), - } + umbral_pre::Signer::new(sk.backend.clone()).into() } pub fn sign(&self, message: &[u8]) -> Signature { - Signature { - backend: self.backend.sign(message), - } + self.backend.sign(message).into() } pub fn verifying_key(&self) -> PublicKey { - PublicKey { - backend: self.backend.verifying_key(), - } + self.backend.verifying_key().into() } fn __str__(&self) -> PyResult { @@ -310,28 +247,16 @@ impl Signer { } #[pyclass(module = "umbral")] -#[derive(PartialEq, Eq)] +#[derive(PartialEq, Eq, derive_more::AsRef, derive_more::From)] pub struct Signature { backend: umbral_pre::Signature, } -impl AsBackend for Signature { - fn as_backend(&self) -> &umbral_pre::Signature { - &self.backend - } -} - -impl FromBackend for Signature { - fn from_backend(backend: umbral_pre::Signature) -> Self { - Self { backend } - } -} - #[pymethods] impl Signature { #[staticmethod] pub fn from_bytes(data: &[u8]) -> PyResult { - from_bytes(data) + from_bytes::<_, umbral_pre::Signature>(data) } pub fn verify(&self, verifying_pk: &PublicKey, message: &[u8]) -> bool { @@ -361,28 +286,16 @@ impl Signature { } #[pyclass(module = "umbral")] -#[derive(Clone, PartialEq)] +#[derive(Clone, PartialEq, derive_more::AsRef, derive_more::From, derive_more::Into)] pub struct Capsule { - pub backend: umbral_pre::Capsule, -} - -impl AsBackend for Capsule { - fn as_backend(&self) -> &umbral_pre::Capsule { - &self.backend - } -} - -impl FromBackend for Capsule { - fn from_backend(backend: umbral_pre::Capsule) -> Self { - Self { backend } - } + backend: umbral_pre::Capsule, } #[pymethods] impl Capsule { #[staticmethod] pub fn from_bytes(data: &[u8]) -> PyResult { - from_bytes(data) + from_bytes::<_, umbral_pre::Capsule>(data) } #[staticmethod] @@ -415,12 +328,7 @@ pub fn encrypt( ) -> PyResult<(Capsule, PyObject)> { umbral_pre::encrypt(&delegating_pk.backend, plaintext) .map(|(backend_capsule, ciphertext)| { - ( - Capsule { - backend: backend_capsule, - }, - PyBytes::new(py, &ciphertext).into(), - ) + (backend_capsule.into(), PyBytes::new(py, &ciphertext).into()) }) .map_err(|err| PyValueError::new_err(format!("{}", err))) } @@ -438,23 +346,11 @@ pub fn decrypt_original( } #[pyclass(module = "umbral")] -#[derive(PartialEq)] +#[derive(PartialEq, derive_more::AsRef, derive_more::From)] pub struct KeyFrag { backend: umbral_pre::KeyFrag, } -impl AsBackend for KeyFrag { - fn as_backend(&self) -> &umbral_pre::KeyFrag { - &self.backend - } -} - -impl FromBackend for KeyFrag { - fn from_backend(backend: umbral_pre::KeyFrag) -> Self { - Self { backend } - } -} - #[pymethods] impl KeyFrag { pub fn verify( @@ -471,9 +367,7 @@ impl KeyFrag { receiving_pk.map(|pk| &pk.backend), ) .map_err(|(err, _kfrag)| VerificationError::new_err(format!("{}", err))) - .map(|backend_vkfrag| VerifiedKeyFrag { - backend: backend_vkfrag, - }) + .map(VerifiedKeyFrag::from) } pub fn skip_verification(&self) -> VerifiedKeyFrag { @@ -484,7 +378,7 @@ impl KeyFrag { #[staticmethod] pub fn from_bytes(data: &[u8]) -> PyResult { - from_bytes(data) + from_bytes::<_, umbral_pre::KeyFrag>(data) } #[staticmethod] @@ -510,15 +404,9 @@ impl KeyFrag { } #[pyclass(module = "umbral")] -#[derive(PartialEq, Clone)] +#[derive(PartialEq, Clone, derive_more::AsRef, derive_more::From, derive_more::Into)] pub struct VerifiedKeyFrag { - pub backend: umbral_pre::VerifiedKeyFrag, -} - -impl AsBackend for VerifiedKeyFrag { - fn as_backend(&self) -> &umbral_pre::VerifiedKeyFrag { - &self.backend - } + backend: umbral_pre::VerifiedKeyFrag, } #[pymethods] @@ -526,7 +414,7 @@ impl VerifiedKeyFrag { #[staticmethod] pub fn from_verified_bytes(data: &[u8]) -> PyResult { umbral_pre::VerifiedKeyFrag::from_verified_bytes(data) - .map(|vkfrag| Self { backend: vkfrag }) + .map(Self::from) .map_err(|err| PyValueError::new_err(format!("{}", err))) } @@ -587,23 +475,11 @@ pub fn generate_kfrags( } #[pyclass(module = "umbral")] -#[derive(Clone, PartialEq)] +#[derive(Clone, PartialEq, derive_more::AsRef, derive_more::From)] pub struct CapsuleFrag { backend: umbral_pre::CapsuleFrag, } -impl AsBackend for CapsuleFrag { - fn as_backend(&self) -> &umbral_pre::CapsuleFrag { - &self.backend - } -} - -impl FromBackend for CapsuleFrag { - fn from_backend(backend: umbral_pre::CapsuleFrag) -> Self { - Self { backend } - } -} - #[pymethods] impl CapsuleFrag { pub fn verify( @@ -622,9 +498,7 @@ impl CapsuleFrag { &receiving_pk.backend, ) .map_err(|(err, _cfrag)| VerificationError::new_err(format!("{}", err))) - .map(|backend_vcfrag| VerifiedCapsuleFrag { - backend: backend_vcfrag, - }) + .map(VerifiedCapsuleFrag::from) } pub fn skip_verification(&self) -> VerifiedCapsuleFrag { @@ -635,7 +509,7 @@ impl CapsuleFrag { #[staticmethod] pub fn from_bytes(data: &[u8]) -> PyResult { - from_bytes(data) + from_bytes::<_, umbral_pre::CapsuleFrag>(data) } #[staticmethod] @@ -661,15 +535,9 @@ impl CapsuleFrag { } #[pyclass(module = "umbral")] -#[derive(PartialEq, Clone)] +#[derive(PartialEq, Clone, derive_more::AsRef, derive_more::From, derive_more::Into)] pub struct VerifiedCapsuleFrag { - pub backend: umbral_pre::VerifiedCapsuleFrag, -} - -impl AsBackend for VerifiedCapsuleFrag { - fn as_backend(&self) -> &umbral_pre::VerifiedCapsuleFrag { - &self.backend - } + backend: umbral_pre::VerifiedCapsuleFrag, } #[pymethods] @@ -689,7 +557,7 @@ impl VerifiedCapsuleFrag { #[staticmethod] pub fn from_verified_bytes(data: &[u8]) -> PyResult { umbral_pre::VerifiedCapsuleFrag::from_verified_bytes(data) - .map(|vcfrag| Self { backend: vcfrag }) + .map(Self::from) .map_err(|err| PyValueError::new_err(format!("{}", err))) } @@ -712,9 +580,7 @@ impl VerifiedCapsuleFrag { #[pyfunction] pub fn reencrypt(capsule: &Capsule, kfrag: &VerifiedKeyFrag) -> VerifiedCapsuleFrag { let backend_vcfrag = umbral_pre::reencrypt(&capsule.backend, kfrag.backend.clone()); - VerifiedCapsuleFrag { - backend: backend_vcfrag, - } + VerifiedCapsuleFrag::from(backend_vcfrag) } #[pyfunction] @@ -726,11 +592,11 @@ pub fn decrypt_reencrypted( verified_cfrags: Vec, ciphertext: &[u8], ) -> PyResult { - let backend_cfrags: Vec = verified_cfrags + let backend_cfrags = verified_cfrags .iter() .cloned() .map(|vcfrag| vcfrag.backend) - .collect(); + .collect::>(); umbral_pre::decrypt_reencrypted( &receiving_sk.backend, &delegating_pk.backend, diff --git a/umbral-pre/src/bindings_wasm.rs b/umbral-pre/src/bindings_wasm.rs index 7b84c237..89145472 100644 --- a/umbral-pre/src/bindings_wasm.rs +++ b/umbral-pre/src/bindings_wasm.rs @@ -11,22 +11,77 @@ extern crate alloc; use alloc::boxed::Box; use alloc::format; use alloc::string::String; -use alloc::{vec, vec::Vec}; +use alloc::vec::Vec; use core::fmt; -use js_sys::Error; +use js_sys::{Error, Uint8Array}; use wasm_bindgen::prelude::{wasm_bindgen, JsValue}; - -use serde::{Deserialize, Serialize}; +use wasm_bindgen::JsCast; +use wasm_bindgen_derive::TryFromJsValue; use crate as umbral_pre; use crate::{DeserializableFromArray, SerializableToArray, SerializableToSecretArray}; -fn map_js_err(err: T) -> JsValue { - Error::new(&format!("{}", err)).into() +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(typescript_type = "VerifiedCapsuleFrag[]")] + pub type VerifiedCapsuleFragArray; + + #[wasm_bindgen(typescript_type = "PublicKey | null")] + pub type OptionPublicKey; + + #[wasm_bindgen(typescript_type = "VerifiedKeyFrag[]")] + pub type VerifiedKeyFragArray; + + #[wasm_bindgen(typescript_type = "[Capsule, Uint8Array]")] + pub type EncryptionResult; +} + +fn map_js_err(err: T) -> Error { + Error::new(&format!("{}", err)) +} + +/// Tries to convert an optional value (either `null` or a `#[wasm_bindgen]` marked structure) +/// from `JsValue` to the Rust type. +// TODO (#25): This is necessary since wasm-bindgen does not support +// having a parameter of `Option<&T>`, and using `Option` consumes the argument +// (see https://github.com/rustwasm/wasm-bindgen/issues/2370). +fn try_from_js_option<'a, T>(value: &'a JsValue) -> Result, Error> +where + T: TryFrom<&'a JsValue>, + >::Error: core::fmt::Display, +{ + let typed_value = if value.is_null() { + None + } else { + Some(T::try_from(value).map_err(map_js_err)?) + }; + Ok(typed_value) +} + +/// Tries to convert a JS array from `JsValue` to a vector of Rust type elements. +// TODO (#23): This is necessary since wasm-bindgen does not support +// having a parameter of `Vec<&T>` +// (see https://github.com/rustwasm/wasm-bindgen/issues/111). +fn try_from_js_array(value: &JsValue) -> Result, Error> +where + for<'a> T: TryFrom<&'a JsValue>, + for<'a> >::Error: core::fmt::Display, +{ + let array: &js_sys::Array = value + .dyn_ref() + .ok_or_else(|| Error::new("Got a non-array argument where an array was expected"))?; + let length: usize = array.length().try_into().map_err(map_js_err)?; + let mut result = Vec::::with_capacity(length); + for js in array.iter() { + let typed_elem = T::try_from(&js).map_err(map_js_err)?; + result.push(typed_elem); + } + Ok(result) } #[wasm_bindgen] +#[derive(derive_more::AsRef)] pub struct SecretKey(umbral_pre::SecretKey); #[wasm_bindgen] @@ -52,7 +107,7 @@ impl SecretKey { } #[wasm_bindgen(js_name = fromBytes)] - pub fn from_bytes(data: &[u8]) -> Result { + pub fn from_bytes(data: &[u8]) -> Result { umbral_pre::SecretKey::from_bytes(data) .map(Self) .map_err(map_js_err) @@ -65,12 +120,6 @@ impl SecretKey { } } -impl SecretKey { - pub fn inner(&self) -> &umbral_pre::SecretKey { - &self.0 - } -} - #[wasm_bindgen] pub struct SecretKeyFactory(umbral_pre::SecretKeyFactory); @@ -87,7 +136,7 @@ impl SecretKeyFactory { } #[wasm_bindgen(js_name = fromSecureRandomness)] - pub fn from_secure_randomness(seed: &[u8]) -> Result { + pub fn from_secure_randomness(seed: &[u8]) -> Result { umbral_pre::SecretKeyFactory::from_secure_randomness(seed) .map(Self) .map_err(map_js_err) @@ -113,7 +162,7 @@ impl SecretKeyFactory { } #[wasm_bindgen(js_name = fromBytes)] - pub fn from_bytes(data: &[u8]) -> Result { + pub fn from_bytes(data: &[u8]) -> Result { umbral_pre::SecretKeyFactory::from_bytes(data) .map(Self) .map_err(map_js_err) @@ -126,8 +175,9 @@ impl SecretKeyFactory { } } +#[derive(TryFromJsValue)] #[wasm_bindgen] -#[derive(Serialize, Deserialize)] +#[derive(Clone, derive_more::AsRef, derive_more::From, derive_more::Into)] pub struct PublicKey(umbral_pre::PublicKey); #[wasm_bindgen] @@ -138,7 +188,7 @@ impl PublicKey { } #[wasm_bindgen(js_name = fromBytes)] - pub fn from_bytes(data: &[u8]) -> Result { + pub fn from_bytes(data: &[u8]) -> Result { umbral_pre::PublicKey::from_bytes(data) .map(PublicKey) .map_err(map_js_err) @@ -155,17 +205,8 @@ impl PublicKey { } } -impl PublicKey { - pub fn new(pk: umbral_pre::PublicKey) -> Self { - PublicKey(pk) - } - - pub fn inner(&self) -> &umbral_pre::PublicKey { - &self.0 - } -} - #[wasm_bindgen] +#[derive(derive_more::AsRef)] pub struct Signer(umbral_pre::Signer); #[wasm_bindgen] @@ -191,12 +232,6 @@ impl Signer { } } -impl Signer { - pub fn inner(&self) -> &umbral_pre::Signer { - &self.0 - } -} - #[wasm_bindgen] pub struct Signature(umbral_pre::Signature); @@ -212,7 +247,7 @@ impl Signature { } #[wasm_bindgen(js_name = fromBytes)] - pub fn from_bytes(data: &[u8]) -> Result { + pub fn from_bytes(data: &[u8]) -> Result { umbral_pre::Signature::from_bytes(data) .map(Self) .map_err(map_js_err) @@ -229,30 +264,20 @@ impl Signature { } } +#[derive(TryFromJsValue)] #[wasm_bindgen] -#[derive(Clone, Copy, Serialize, Deserialize)] +#[derive(Clone, Copy, derive_more::AsRef, derive_more::From, derive_more::Into)] pub struct Capsule(umbral_pre::Capsule); #[wasm_bindgen] impl Capsule { - // TODO (#23): have to add cfrags one by one since `wasm_bindgen` currently does not support - // Vec as a parameter. - // Will probably be fixed along with https://github.com/rustwasm/wasm-bindgen/issues/111 - #[wasm_bindgen(js_name = withCFrag)] - pub fn with_cfrag(&self, cfrag: &VerifiedCapsuleFrag) -> CapsuleWithFrags { - CapsuleWithFrags { - capsule: *self, - cfrags: vec![cfrag.clone()], - } - } - #[wasm_bindgen(js_name = toBytes)] pub fn to_bytes(&self) -> Box<[u8]> { self.0.to_array().to_vec().into_boxed_slice() } #[wasm_bindgen(js_name = fromBytes)] - pub fn from_bytes(data: &[u8]) -> Result { + pub fn from_bytes(data: &[u8]) -> Result { umbral_pre::Capsule::from_bytes(data) .map(Capsule) .map_err(map_js_err) @@ -269,16 +294,6 @@ impl Capsule { } } -impl Capsule { - pub fn new(capsule: umbral_pre::Capsule) -> Self { - Capsule(capsule) - } - - pub fn inner(&self) -> &umbral_pre::Capsule { - &self.0 - } -} - #[wasm_bindgen] #[derive(Clone)] pub struct CapsuleFrag(umbral_pre::CapsuleFrag); @@ -292,7 +307,7 @@ impl CapsuleFrag { verifying_pk: &PublicKey, delegating_pk: &PublicKey, receiving_pk: &PublicKey, - ) -> Result { + ) -> Result { self.0 .verify( &capsule.0, @@ -311,7 +326,7 @@ impl CapsuleFrag { } #[wasm_bindgen(js_name = fromBytes)] - pub fn from_bytes(data: &[u8]) -> Result { + pub fn from_bytes(data: &[u8]) -> Result { umbral_pre::CapsuleFrag::from_bytes(data) .map(Self) .map_err(map_js_err) @@ -328,14 +343,15 @@ impl CapsuleFrag { } } +#[derive(TryFromJsValue)] #[wasm_bindgen] -#[derive(Clone, Serialize, Deserialize, PartialEq, Debug)] +#[derive(Clone, PartialEq, Debug, derive_more::AsRef, derive_more::From, derive_more::Into)] pub struct VerifiedCapsuleFrag(umbral_pre::VerifiedCapsuleFrag); #[wasm_bindgen] impl VerifiedCapsuleFrag { #[wasm_bindgen(js_name = fromVerifiedBytes)] - pub fn from_verified_bytes(bytes: &[u8]) -> Result { + pub fn from_verified_bytes(bytes: &[u8]) -> Result { umbral_pre::VerifiedCapsuleFrag::from_verified_bytes(bytes) .map(VerifiedCapsuleFrag) .map_err(map_js_err) @@ -357,83 +373,19 @@ impl VerifiedCapsuleFrag { } } -impl VerifiedCapsuleFrag { - pub fn new(verified_cfrag: umbral_pre::VerifiedCapsuleFrag) -> Self { - VerifiedCapsuleFrag(verified_cfrag) - } - - pub fn inner(&self) -> umbral_pre::VerifiedCapsuleFrag { - self.0.clone() - } -} - -#[wasm_bindgen] -pub struct CapsuleWithFrags { - capsule: Capsule, - cfrags: Vec, -} - -#[wasm_bindgen] -impl CapsuleWithFrags { - #[wasm_bindgen(js_name = withCFrag)] - pub fn with_cfrag(&self, cfrag: &VerifiedCapsuleFrag) -> CapsuleWithFrags { - let mut new_cfrags = self.cfrags.clone(); - new_cfrags.push(cfrag.clone()); - Self { - capsule: self.capsule, - cfrags: new_cfrags, - } - } - - #[wasm_bindgen(js_name = decryptReencrypted)] - pub fn decrypt_reencrypted( - &self, - receiving_sk: &SecretKey, - delegating_pk: &PublicKey, - ciphertext: &[u8], - ) -> Result, JsValue> { - let backend_cfrags: Vec = - self.cfrags.iter().cloned().map(|x| x.0).collect(); - umbral_pre::decrypt_reencrypted( - &receiving_sk.0, - &delegating_pk.0, - &self.capsule.0, - backend_cfrags, - ciphertext, - ) - .map_err(map_js_err) - } -} - -#[wasm_bindgen] -pub struct EncryptionResult { - ciphertext: Box<[u8]>, - pub capsule: Capsule, -} - -#[wasm_bindgen] -impl EncryptionResult { - fn new(ciphertext: Box<[u8]>, capsule: Capsule) -> Self { - Self { - ciphertext, - capsule, - } - } - - // TODO (#24): currently can't just make the field public because `Box` doesn't implement `Copy`. - // See https://github.com/rustwasm/wasm-bindgen/issues/439 - #[wasm_bindgen(getter)] - pub fn ciphertext(&self) -> Box<[u8]> { - self.ciphertext.clone() - } -} - #[wasm_bindgen] -pub fn encrypt(delegating_pk: &PublicKey, plaintext: &[u8]) -> Result { +pub fn encrypt(delegating_pk: &PublicKey, plaintext: &[u8]) -> Result { let backend_pk = delegating_pk.0; - umbral_pre::encrypt(&backend_pk, plaintext) - .map(|(capsule, ciphertext)| EncryptionResult::new(ciphertext, Capsule(capsule))) - .map_err(map_js_err) + let (capsule, ciphertext) = umbral_pre::encrypt(&backend_pk, plaintext).map_err(map_js_err)?; + + // TODO (#24): wasm-bindgen does not allow one to return a tuple directly. + // Have to cast it manually. + let capsule_js: JsValue = Capsule::from(capsule).into(); + let ciphertext_js: JsValue = Uint8Array::from(ciphertext.as_ref()).into(); + Ok([capsule_js, ciphertext_js] + .into_iter() + .collect::() + .unchecked_into::()) } #[wasm_bindgen(js_name = decryptOriginal)] @@ -441,73 +393,55 @@ pub fn decrypt_original( delegating_sk: &SecretKey, capsule: &Capsule, ciphertext: &[u8], -) -> Result, JsValue> { +) -> Result, Error> { umbral_pre::decrypt_original(&delegating_sk.0, &capsule.0, ciphertext).map_err(map_js_err) } +#[wasm_bindgen(js_name = decryptReencrypted)] +pub fn decrypt_reencrypted( + receiving_sk: &SecretKey, + delegating_pk: &PublicKey, + capsule: &Capsule, + vcfrags: &VerifiedCapsuleFragArray, + ciphertext: &[u8], +) -> Result, Error> { + // TODO (#23): using a custom type since `wasm_bindgen` currently does not support + // Vec as a parameter. + // Will probably be fixed along with https://github.com/rustwasm/wasm-bindgen/issues/111 + let typed_vcfrags = try_from_js_array::(vcfrags.as_ref())?; + let backend_vcfrags = typed_vcfrags.into_iter().map(|vcfrag| vcfrag.0); + umbral_pre::decrypt_reencrypted( + &receiving_sk.0, + &delegating_pk.0, + &capsule.0, + backend_vcfrags, + ciphertext, + ) + .map_err(map_js_err) +} + #[wasm_bindgen] pub struct KeyFrag(umbral_pre::KeyFrag); #[wasm_bindgen] impl KeyFrag { - // TODO (#25): `Option<&PublicKey> are currently not supported. - // See https://github.com/rustwasm/wasm-bindgen/issues/2370 - // So we have to use 4 functions instead of 1. Yikes. - #[wasm_bindgen] - pub fn verify(self, verifying_pk: &PublicKey) -> Result { - self.0 - .verify(&verifying_pk.0, None, None) - .map(VerifiedKeyFrag) - .map_err(|(err, _)| err) - .map_err(map_js_err) - } - - #[wasm_bindgen(js_name = verifyWithDelegatingKey)] - pub fn verify_with_delegating_key( - self, - verifying_pk: &PublicKey, - delegating_pk: &PublicKey, - ) -> Result { - let backend_delegating_pk = delegating_pk.0; - - self.0 - .verify(&verifying_pk.0, Some(&backend_delegating_pk), None) - .map(VerifiedKeyFrag) - .map_err(|(err, _)| err) - .map_err(map_js_err) - } - - #[wasm_bindgen(js_name = verifyWithReceivingKey)] - pub fn verify_with_receiving_key( - self, - verifying_pk: &PublicKey, - receiving_pk: &PublicKey, - ) -> Result { - let backend_receiving_pk = receiving_pk.0; - - self.0 - .verify(&verifying_pk.0, None, Some(&backend_receiving_pk)) - .map(VerifiedKeyFrag) - .map_err(|(err, _)| err) - .map_err(map_js_err) - } - - #[wasm_bindgen(js_name = verifyWithDelegatingAndReceivingKeys)] - pub fn verify_with_delegating_and_receiving_keys( + pub fn verify( self, verifying_pk: &PublicKey, - delegating_pk: &PublicKey, - receiving_pk: &PublicKey, - ) -> Result { - let backend_delegating_pk = delegating_pk.0; - let backend_receiving_pk = receiving_pk.0; + // TODO: replace with `Option<&PublicKey>` when `wasm-bindgen` supports it. + // See https://github.com/rustwasm/wasm-bindgen/issues/2370 + delegating_pk: &OptionPublicKey, + receiving_pk: &OptionPublicKey, + ) -> Result { + let typed_delegating_pk = try_from_js_option::(delegating_pk.as_ref())?; + let typed_receiving_pk = try_from_js_option::(receiving_pk.as_ref())?; self.0 .verify( &verifying_pk.0, - Some(&backend_delegating_pk), - Some(&backend_receiving_pk), + typed_delegating_pk.as_ref().map(|pk| &pk.0), + typed_receiving_pk.as_ref().map(|pk| &pk.0), ) .map(VerifiedKeyFrag) .map_err(|(err, _)| err) @@ -520,7 +454,7 @@ impl KeyFrag { } #[wasm_bindgen(js_name = fromBytes)] - pub fn from_bytes(data: &[u8]) -> Result { + pub fn from_bytes(data: &[u8]) -> Result { umbral_pre::KeyFrag::from_bytes(data) .map(Self) .map_err(map_js_err) @@ -537,14 +471,15 @@ impl KeyFrag { } } +#[derive(TryFromJsValue)] #[wasm_bindgen] -#[derive(Serialize, Deserialize, Clone)] +#[derive(Clone, derive_more::AsRef, derive_more::From, derive_more::Into)] pub struct VerifiedKeyFrag(umbral_pre::VerifiedKeyFrag); #[wasm_bindgen] impl VerifiedKeyFrag { #[wasm_bindgen(js_name = fromVerifiedBytes)] - pub fn from_verified_bytes(bytes: &[u8]) -> Result { + pub fn from_verified_bytes(bytes: &[u8]) -> Result { umbral_pre::VerifiedKeyFrag::from_verified_bytes(bytes) .map(VerifiedKeyFrag) .map_err(map_js_err) @@ -566,16 +501,6 @@ impl VerifiedKeyFrag { } } -impl VerifiedKeyFrag { - pub fn new(vkfrag: umbral_pre::VerifiedKeyFrag) -> Self { - Self(vkfrag) - } - - pub fn inner(&self) -> &umbral_pre::VerifiedKeyFrag { - &self.0 - } -} - #[allow(clippy::too_many_arguments)] #[wasm_bindgen(js_name = generateKFrags)] pub fn generate_kfrags( @@ -586,7 +511,7 @@ pub fn generate_kfrags( shares: usize, sign_delegating_key: bool, sign_receiving_key: bool, -) -> Vec { +) -> VerifiedKeyFragArray { let backend_kfrags = umbral_pre::generate_kfrags( &delegating_sk.0, &receiving_pk.0, @@ -598,14 +523,16 @@ pub fn generate_kfrags( ); // TODO (#26): Apparently we cannot just return a vector of things, - // so we have to convert them to JsValues manually. + // so we have to convert them to JsValues manually and use a custom return type + // to generate a correct signature for TypeScript. // See https://github.com/rustwasm/wasm-bindgen/issues/111 backend_kfrags - .iter() - .cloned() + .into_vec() + .into_iter() .map(VerifiedKeyFrag) .map(JsValue::from) - .collect() + .collect::() + .unchecked_into::() } #[wasm_bindgen] diff --git a/umbral-pre/src/capsule_frag.rs b/umbral-pre/src/capsule_frag.rs index 79efdcdc..e5dac99b 100644 --- a/umbral-pre/src/capsule_frag.rs +++ b/umbral-pre/src/capsule_frag.rs @@ -315,7 +315,6 @@ impl CapsuleFrag { /// Can be serialized, but cannot be deserialized directly. /// It can only be obtained from [`CapsuleFrag::verify`] or [`CapsuleFrag::skip_verification`]. #[derive(Debug, Clone, PartialEq)] -#[cfg_attr(feature = "bindings-wasm", derive(Serialize, Deserialize))] pub struct VerifiedCapsuleFrag { cfrag: CapsuleFrag, } diff --git a/umbral-pre/src/key_frag.rs b/umbral-pre/src/key_frag.rs index 47d0a985..4ec260e8 100644 --- a/umbral-pre/src/key_frag.rs +++ b/umbral-pre/src/key_frag.rs @@ -372,7 +372,6 @@ impl KeyFrag { /// Can be serialized, but cannot be deserialized directly. /// It can only be obtained from [`KeyFrag::verify`] or [`KeyFrag::skip_verification`]. #[derive(Debug, Clone, PartialEq)] -#[cfg_attr(feature = "bindings-wasm", derive(Serialize, Deserialize))] pub struct VerifiedKeyFrag { kfrag: KeyFrag, } diff --git a/umbral-pre/src/lib.rs b/umbral-pre/src/lib.rs index 01466f01..02fbcca9 100644 --- a/umbral-pre/src/lib.rs +++ b/umbral-pre/src/lib.rs @@ -139,7 +139,7 @@ mod pre; mod secret_box; mod traits; -#[cfg(any(feature = "serde-support", feature = "bindings-wasm"))] +#[cfg(feature = "serde-support")] #[cfg_attr(docsrs, doc(cfg(feature = "serde-support")))] pub mod serde_bytes;