From 9c872cc191601516173dfcd519b1d063df80572d Mon Sep 17 00:00:00 2001 From: Bogdan Opanchuk Date: Mon, 3 Nov 2025 23:40:06 -0800 Subject: [PATCH 1/9] Bump MSRV --- .github/workflows/umbral-pre.yml | 8 ++++---- CHANGELOG.md | 2 ++ umbral-pre/Cargo.toml | 1 + 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/.github/workflows/umbral-pre.yml b/.github/workflows/umbral-pre.yml index 6c6e565..b63343b 100644 --- a/.github/workflows/umbral-pre.yml +++ b/.github/workflows/umbral-pre.yml @@ -27,7 +27,7 @@ jobs: strategy: matrix: rust: - - 1.70.0 # MSRV + - 1.81.0 # MSRV - stable target: - wasm32-unknown-unknown @@ -45,7 +45,7 @@ jobs: strategy: matrix: rust: - - 1.70.0 # MSRV + - 1.81.0 # MSRV - stable target: - thumbv7em-none-eabi @@ -63,7 +63,7 @@ jobs: strategy: matrix: rust: - - 1.70.0 # MSRV + - 1.81.0 # MSRV - stable steps: - uses: actions/checkout@v2 @@ -79,7 +79,7 @@ jobs: matrix: include: - target: x86_64-unknown-linux-gnu - rust: 1.70.0 # MSRV + rust: 1.81.0 # MSRV - target: x86_64-unknown-linux-gnu rust: stable diff --git a/CHANGELOG.md b/CHANGELOG.md index 83b0a23..6b06ad9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,9 +10,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `SecretKey::try_from_be_bytes()` takes just a slice reference instead of a `SecretBox`. ([#134]) - Bumped MSRV to 1.70. ([#134]) +- Bumped MSRV to 1.81. ([#135]) [#134]: https://github.com/nucypher/rust-umbral/pull/134 +[#135]: https://github.com/nucypher/rust-umbral/pull/135 ## [0.11.0] - 2023-08-01 diff --git a/umbral-pre/Cargo.toml b/umbral-pre/Cargo.toml index 985663c..dd188a7 100644 --- a/umbral-pre/Cargo.toml +++ b/umbral-pre/Cargo.toml @@ -8,6 +8,7 @@ description = "Implementation of Umbral proxy reencryption algorithm" repository = "https://github.com/nucypher/rust-umbral/tree/master/umbral-pre" readme = "README.md" categories = ["cryptography", "no-std"] +rust-version = "1.81" [dependencies] k256 = { version = "0.13", default-features = false, features = ["ecdsa", "arithmetic", "hash2curve"] } From 9e0f36397a6072aed49f4e6786db3da4942ac1e1 Mon Sep 17 00:00:00 2001 From: Bogdan Opanchuk Date: Mon, 3 Nov 2025 09:08:06 -0800 Subject: [PATCH 2/9] Update `zeroize` and fix clippy warnings --- Cargo.toml | 1 + umbral-pre/Cargo.toml | 4 ++-- umbral-pre/src/bindings_python.rs | 6 +++--- umbral-pre/src/bindings_wasm.rs | 4 ++-- umbral-pre/src/capsule_frag.rs | 2 +- umbral-pre/src/lib.rs | 8 ++++---- 6 files changed, 13 insertions(+), 12 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 50afcba..b18f79a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,3 +4,4 @@ members = [ "umbral-pre-wasm", "umbral-pre-python", ] +resolver = "2" diff --git a/umbral-pre/Cargo.toml b/umbral-pre/Cargo.toml index dd188a7..27aecb8 100644 --- a/umbral-pre/Cargo.toml +++ b/umbral-pre/Cargo.toml @@ -22,7 +22,7 @@ rmp-serde = { version = "1", optional = true } pyo3 = { version = "0.18", optional = true } js-sys = { version = "0.3.63", optional = true } wasm-bindgen = { version = "0.2.86", optional = true } -derive_more = { version = "0.99", optional = true, default_features = false, features = ["as_ref", "from", "into"] } +derive_more = { version = "0.99", optional = true, default-features = false, features = ["as_ref", "from", "into"] } wasm-bindgen-derive = { version = "0.2.0", optional = true } # These packages are among the dependencies of the packages above. @@ -31,7 +31,7 @@ generic-array = { version = "0.14.6", features = ["zeroize"] } rand_core = { version = "0.6", default-features = false } getrandom = { version = "0.2", optional = true, default-features = false } subtle = { version = "2.4", default-features = false } -zeroize = { version = "1.5", default-features = false, features = ["derive"] } +zeroize = { version = "1.8", default-features = false, features = ["derive"] } [dev-dependencies] criterion = { version = "=0.4.0", features = ["html_reports"] } # forcing version to avoid bumping MSRV diff --git a/umbral-pre/src/bindings_python.rs b/umbral-pre/src/bindings_python.rs index 2fb275c..0fa3392 100644 --- a/umbral-pre/src/bindings_python.rs +++ b/umbral-pre/src/bindings_python.rs @@ -189,7 +189,7 @@ impl PublicKey { } fn __hash__(&self) -> i64 { - hash(&self.backend.to_compressed_bytes()) + hash(self.backend.to_compressed_bytes()) } fn __str__(&self) -> PyResult { @@ -264,7 +264,7 @@ impl Signature { } fn __hash__(&self) -> i64 { - hash(&self.backend.to_der_bytes()) + hash(self.backend.to_der_bytes()) } fn __str__(&self) -> PyResult { @@ -297,7 +297,7 @@ impl RecoverableSignature { } fn __hash__(&self) -> i64 { - hash(&self.backend.to_be_bytes()) + hash(self.backend.to_be_bytes()) } fn __str__(&self) -> PyResult { diff --git a/umbral-pre/src/bindings_wasm.rs b/umbral-pre/src/bindings_wasm.rs index b315756..4e22ef1 100644 --- a/umbral-pre/src/bindings_wasm.rs +++ b/umbral-pre/src/bindings_wasm.rs @@ -52,7 +52,7 @@ fn map_js_err(err: T) -> Error { fn try_from_js_option<'a, T>(value: &'a JsValue) -> Result, Error> where T: TryFrom<&'a JsValue>, - >::Error: core::fmt::Display, + >::Error: fmt::Display, { let typed_value = if value.is_null() { None @@ -69,7 +69,7 @@ where fn try_from_js_array(value: &JsValue) -> Result, Error> where for<'a> T: TryFrom<&'a JsValue>, - for<'a> >::Error: core::fmt::Display, + for<'a> >::Error: fmt::Display, { let array: &js_sys::Array = value .dyn_ref() diff --git a/umbral-pre/src/capsule_frag.rs b/umbral-pre/src/capsule_frag.rs index 91f318a..5c22ad3 100644 --- a/umbral-pre/src/capsule_frag.rs +++ b/umbral-pre/src/capsule_frag.rs @@ -142,7 +142,7 @@ impl CapsuleFrag { /// - `u2`, the kfrag PoK (compressed curve point, 33 bytes), /// - `signature` (big-endian scalar, 32 bytes), /// - `kfrag_signature` (ECDSA signature serialized as `r` and `s`, - /// each a 32 byte big-endian scalar). + /// each a 32 byte big-endian scalar). pub fn to_bytes_simple(&self) -> Box<[u8]> { let e1 = self.point_e1.to_compressed_array(); let v1 = self.point_v1.to_compressed_array(); diff --git a/umbral-pre/src/lib.rs b/umbral-pre/src/lib.rs index e16fa08..c5feff9 100644 --- a/umbral-pre/src/lib.rs +++ b/umbral-pre/src/lib.rs @@ -12,13 +12,13 @@ //! //! * `default-rng` - adds methods that use the system RNG (default). //! * `default-serialization` - adds methods for default binary serialization -//! that matches the serialization in the bindings. -//! MessagePack, `serde`-based. +//! that matches the serialization in the bindings. +//! MessagePack, `serde`-based. //! * `serde` - implements `serde`-based serialization and deserialization. //! * `bindings-python` - adds a `bindings_python` submodule allowing dependent crates -//! to use and re-export some of the Python-wrapped Umbral types. +//! to use and re-export some of the Python-wrapped Umbral types. //! * `bindings-wasm` - adds a `bindings_wasm` submodule allowing dependent crates -//! to use and re-export some of the WASM-wrapped Umbral types. +//! to use and re-export some of the WASM-wrapped Umbral types. //! //! # Usage //! From a5bbc326798767ea77d6d038a4bc94c3ff339f9f Mon Sep 17 00:00:00 2001 From: Bogdan Opanchuk Date: Mon, 3 Nov 2025 09:15:25 -0800 Subject: [PATCH 3/9] Bump pyo3 to 0.27 --- umbral-pre-python/Cargo.toml | 2 +- umbral-pre-python/src/lib.rs | 2 +- umbral-pre/Cargo.toml | 2 +- umbral-pre/src/bindings_python.rs | 76 +++++++++++++++---------------- 4 files changed, 41 insertions(+), 41 deletions(-) diff --git a/umbral-pre-python/Cargo.toml b/umbral-pre-python/Cargo.toml index b61e755..4e1d3bf 100644 --- a/umbral-pre-python/Cargo.toml +++ b/umbral-pre-python/Cargo.toml @@ -13,4 +13,4 @@ umbral-pre = { path = "../umbral-pre", features = ["bindings-python"] } # Unfortunately, we (for the time being?) cannot use a re-exported `pyo3` # from the main `umbral-pre`, since `pyo3` macros and `pip` build need an explicit dependency. # This version has to be matched with the one in `umbral-pre`. -pyo3 = "0.18" +pyo3 = "0.27" diff --git a/umbral-pre-python/src/lib.rs b/umbral-pre-python/src/lib.rs index 1c5b188..639db85 100644 --- a/umbral-pre-python/src/lib.rs +++ b/umbral-pre-python/src/lib.rs @@ -4,7 +4,7 @@ use umbral_pre::bindings_python::*; /// A Python module implemented in Rust. #[pymodule] -fn _umbral(py: Python, m: &PyModule) -> PyResult<()> { +fn _umbral(py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_class::()?; m.add_class::()?; m.add_class::()?; diff --git a/umbral-pre/Cargo.toml b/umbral-pre/Cargo.toml index 27aecb8..c2ebcfa 100644 --- a/umbral-pre/Cargo.toml +++ b/umbral-pre/Cargo.toml @@ -19,7 +19,7 @@ hex = { version = "0.4", default-features = false, features = ["alloc"] } serde = { version = "1", default-features = false, features = ["derive"], optional = true } base64 = { version = "0.21", default-features = false, features = ["alloc"] } rmp-serde = { version = "1", optional = true } -pyo3 = { version = "0.18", optional = true } +pyo3 = { version = "0.27", optional = true } js-sys = { version = "0.3.63", optional = true } wasm-bindgen = { version = "0.2.86", optional = true } derive_more = { version = "0.99", optional = true, default-features = false, features = ["as_ref", "from", "into"] } diff --git a/umbral-pre/src/bindings_python.rs b/umbral-pre/src/bindings_python.rs index 0fa3392..f1396d7 100644 --- a/umbral-pre/src/bindings_python.rs +++ b/umbral-pre/src/bindings_python.rs @@ -26,13 +26,13 @@ fn map_py_value_err(err: T) -> PyErr { PyValueError::new_err(format!("{err}")) } -fn to_bytes(obj: &T) -> PyResult +fn to_bytes(obj: &T) -> PyResult> where T: AsRef, U: DefaultSerialize, { let serialized = obj.as_ref().to_bytes().map_err(map_py_value_err)?; - Python::with_gil(|py| -> PyResult { Ok(PyBytes::new(py, &serialized).into()) }) + Python::attach(|py| -> PyResult> { Ok(PyBytes::new(py, &serialized).into()) }) } fn from_bytes<'de, T, U>(data: &'de [u8]) -> PyResult @@ -90,9 +90,9 @@ impl SecretKey { } } - pub fn to_be_bytes(&self) -> PyObject { + pub fn to_be_bytes(&self) -> Py { let serialized = self.backend.to_be_bytes(); - Python::with_gil(|py| PyBytes::new(py, serialized.as_secret()).into()) + Python::attach(|py| PyBytes::new(py, serialized.as_secret()).into()) } #[staticmethod] @@ -138,10 +138,10 @@ impl SecretKeyFactory { .map_err(map_py_value_err) } - pub fn make_secret(&self, label: &[u8]) -> PyObject { + pub fn make_secret(&self, label: &[u8]) -> Py { let secret = self.backend.make_secret(label); let bytes: &[u8] = secret.as_secret().as_ref(); - Python::with_gil(|py| PyBytes::new(py, bytes).into()) + Python::attach(|py| PyBytes::new(py, bytes).into()) } pub fn make_key(&self, label: &[u8]) -> SecretKey { @@ -179,9 +179,9 @@ impl PublicKey { .map(Self::from) } - fn to_compressed_bytes(&self) -> PyObject { + fn to_compressed_bytes(&self) -> Py { let serialized = self.backend.to_compressed_bytes(); - Python::with_gil(|py| PyBytes::new(py, &serialized).into()) + Python::attach(|py| PyBytes::new(py, &serialized).into()) } fn __richcmp__(&self, other: &Self, op: CompareOp) -> PyResult { @@ -238,9 +238,9 @@ impl Signature { .map(Self::from) } - fn to_der_bytes(&self) -> PyObject { + fn to_der_bytes(&self) -> Py { let serialized = self.backend.to_der_bytes(); - Python::with_gil(|py| PyBytes::new(py, &serialized).into()) + Python::attach(|py| PyBytes::new(py, &serialized).into()) } #[staticmethod] @@ -250,9 +250,9 @@ impl Signature { .map(Self::from) } - fn to_be_bytes(&self) -> PyObject { + fn to_be_bytes(&self) -> Py { let serialized = self.backend.to_be_bytes(); - Python::with_gil(|py| PyBytes::new(py, &serialized).into()) + Python::attach(|py| PyBytes::new(py, &serialized).into()) } fn verify(&self, verifying_pk: &PublicKey, message: &[u8]) -> bool { @@ -287,9 +287,9 @@ impl RecoverableSignature { .map(Self::from) } - fn to_be_bytes(&self) -> PyObject { + fn to_be_bytes(&self) -> Py { let serialized = self.backend.to_be_bytes(); - Python::with_gil(|py| PyBytes::new(py, &serialized).into()) + Python::attach(|py| PyBytes::new(py, &serialized).into()) } fn __richcmp__(&self, other: &Self, op: CompareOp) -> PyResult { @@ -318,13 +318,13 @@ impl Capsule { from_bytes::<_, umbral_pre::Capsule>(data) } - fn __bytes__(&self) -> PyResult { + fn __bytes__(&self) -> PyResult> { to_bytes(self) } - fn to_bytes_simple(&self) -> PyObject { + fn to_bytes_simple(&self) -> Py { let serialized = self.backend.to_bytes_simple(); - Python::with_gil(|py| PyBytes::new(py, &serialized).into()) + Python::attach(|py| PyBytes::new(py, &serialized).into()) } fn __richcmp__(&self, other: &Self, op: CompareOp) -> PyResult { @@ -345,7 +345,7 @@ pub fn encrypt( py: Python<'_>, delegating_pk: &PublicKey, plaintext: &[u8], -) -> PyResult<(Capsule, PyObject)> { +) -> PyResult<(Capsule, Py)> { umbral_pre::encrypt(&delegating_pk.backend, plaintext) .map(|(backend_capsule, ciphertext)| { (backend_capsule.into(), PyBytes::new(py, &ciphertext).into()) @@ -359,7 +359,7 @@ pub fn decrypt_original( delegating_sk: &SecretKey, capsule: &Capsule, ciphertext: &[u8], -) -> PyResult { +) -> PyResult> { umbral_pre::decrypt_original(&delegating_sk.backend, &capsule.backend, ciphertext) .map(|plaintext| PyBytes::new(py, &plaintext).into()) .map_err(map_py_value_err) @@ -403,7 +403,7 @@ impl KeyFrag { from_bytes::<_, umbral_pre::KeyFrag>(data) } - fn __bytes__(&self) -> PyResult { + fn __bytes__(&self) -> PyResult> { to_bytes(self) } @@ -434,7 +434,7 @@ impl VerifiedKeyFrag { } } - fn __bytes__(&self) -> PyResult { + fn __bytes__(&self) -> PyResult> { to_bytes(self) } @@ -517,13 +517,13 @@ impl CapsuleFrag { from_bytes::<_, umbral_pre::CapsuleFrag>(data) } - fn __bytes__(&self) -> PyResult { + fn __bytes__(&self) -> PyResult> { to_bytes(self) } - fn to_bytes_simple(&self) -> PyObject { + fn to_bytes_simple(&self) -> Py { let serialized = self.backend.to_bytes_simple(); - Python::with_gil(|py| PyBytes::new(py, &serialized).into()) + Python::attach(|py| PyBytes::new(py, &serialized).into()) } fn __richcmp__(&self, other: &Self, op: CompareOp) -> PyResult { @@ -565,13 +565,13 @@ impl VerifiedCapsuleFrag { } } - fn __bytes__(&self) -> PyResult { + fn __bytes__(&self) -> PyResult> { to_bytes(self) } - fn to_bytes_simple(&self) -> PyObject { + fn to_bytes_simple(&self) -> Py { let serialized = self.backend.to_bytes_simple(); - Python::with_gil(|py| PyBytes::new(py, &serialized).into()) + Python::attach(|py| PyBytes::new(py, &serialized).into()) } } @@ -589,7 +589,7 @@ pub fn decrypt_reencrypted( capsule: &Capsule, verified_cfrags: Vec, ciphertext: &[u8], -) -> PyResult { +) -> PyResult> { let backend_cfrags = verified_cfrags .iter() .cloned() @@ -611,23 +611,23 @@ pub fn decrypt_reencrypted( // needs `#[pyfunction]` in the same module, we need these trampolines // to build modules externally. -pub fn register_encrypt(m: &PyModule) -> PyResult<()> { +pub fn register_encrypt(m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_function(wrap_pyfunction!(encrypt, m)?) } -pub fn register_decrypt_original(m: &PyModule) -> PyResult<()> { +pub fn register_decrypt_original(m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_function(wrap_pyfunction!(decrypt_original, m)?) } -pub fn register_generate_kfrags(m: &PyModule) -> PyResult<()> { +pub fn register_generate_kfrags(m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_function(wrap_pyfunction!(generate_kfrags, m)?) } -pub fn register_reencrypt(m: &PyModule) -> PyResult<()> { +pub fn register_reencrypt(m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_function(wrap_pyfunction!(reencrypt, m)?) } -pub fn register_decrypt_reencrypted(m: &PyModule) -> PyResult<()> { +pub fn register_decrypt_reencrypted(m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_function(wrap_pyfunction!(decrypt_reencrypted, m)?) } @@ -665,9 +665,9 @@ pub struct CurvePoint { #[pymethods] impl CurvePoint { #[getter] - fn coordinates(&self) -> Option<(PyObject, PyObject)> { + fn coordinates(&self) -> Option<(Py, Py)> { let coords = self.backend.coordinates(); - Python::with_gil(|py| -> Option<(PyObject, PyObject)> { + Python::attach(|py| -> Option<(Py, Py)> { coords.map(|(x, y)| { ( PyBytes::new(py, x.as_ref()).into(), @@ -710,7 +710,7 @@ impl ReencryptionEvidence { from_bytes::<_, umbral_pre::ReencryptionEvidence>(data) } - fn __bytes__(&self) -> PyResult { + fn __bytes__(&self) -> PyResult> { to_bytes(self) } @@ -774,8 +774,8 @@ impl ReencryptionEvidence { } #[getter] - fn kfrag_validity_message_hash(&self) -> PyObject { - Python::with_gil(|py| -> PyObject { + fn kfrag_validity_message_hash(&self) -> Py { + Python::attach(|py| -> Py { PyBytes::new(py, self.backend.kfrag_validity_message_hash.as_ref()).into() }) } From ce231b60c2e7a1dc8641e624465678a17d454b7f Mon Sep 17 00:00:00 2001 From: Bogdan Opanchuk Date: Tue, 4 Nov 2025 12:05:28 -0800 Subject: [PATCH 4/9] Add vector tests --- umbral-pre/Cargo.toml | 1 + umbral-pre/src/capsule.rs | 22 ++++++++-- umbral-pre/src/capsule_frag.rs | 74 ++++++++++++++++++++++++++++------ umbral-pre/src/key_frag.rs | 54 ++++++++++++++++++++----- umbral-pre/src/keys.rs | 48 ++++++++++++++++++---- umbral-pre/src/serde_bytes.rs | 11 ++++- 6 files changed, 173 insertions(+), 37 deletions(-) diff --git a/umbral-pre/Cargo.toml b/umbral-pre/Cargo.toml index c2ebcfa..f374478 100644 --- a/umbral-pre/Cargo.toml +++ b/umbral-pre/Cargo.toml @@ -37,6 +37,7 @@ zeroize = { version = "1.8", default-features = false, features = ["derive"] } criterion = { version = "=0.4.0", features = ["html_reports"] } # forcing version to avoid bumping MSRV serde_json = "1" rmp-serde = "1" +rand_chacha = "0.3" [features] default = ["default-rng"] diff --git a/umbral-pre/src/capsule.rs b/umbral-pre/src/capsule.rs index a67f54d..9d6eb0e 100644 --- a/umbral-pre/src/capsule.rs +++ b/umbral-pre/src/capsule.rs @@ -270,6 +270,9 @@ mod tests { #[cfg(feature = "serde")] use crate::serde_bytes::tests::check_serialization_roundtrip; + #[cfg(feature = "serde")] + use ::{rand_chacha::ChaCha12Rng, rand_core::SeedableRng}; + #[test] fn test_open_reencrypted() { let delegating_sk = SecretKey::random(); @@ -334,10 +337,23 @@ mod tests { #[cfg(feature = "serde")] #[test] fn test_serde_serialization() { - let delegating_sk = SecretKey::random(); + let mut rng = ChaCha12Rng::seed_from_u64(12345); + let delegating_sk = SecretKey::random_with_rng(&mut rng); let delegating_pk = delegating_sk.public_key(); - let (capsule, _key_seed) = Capsule::from_public_key(&mut OsRng, &delegating_pk); + let (capsule, _key_seed) = Capsule::from_public_key(&mut rng, &delegating_pk); + + let expected_json = concat![ + "{\"point_e\":\"0x0343f37dbe85f2f51faa27c5467618a045828e305a5011d7426f40c835d04244b5\",", + "\"point_v\":\"0x0254a1c5b154243c80fc6351c3286f457f104eaa75ea363572dc21dc95d9ab6a26\",", + "\"signature\":\"0xdadc13a4766c5cda2eed47556cb92d6f02bdef0192d9159f49fbb1e3ccaa7cb7\"}" + ]; + let expected_rmp_hex = concat![ + "93c4210343f37dbe85f2f51faa27c5467618a045828e305a5011d7426f40c835", + "d04244b5c4210254a1c5b154243c80fc6351c3286f457f104eaa75ea363572dc", + "21dc95d9ab6a26c420dadc13a4766c5cda2eed47556cb92d6f02bdef0192d915", + "9f49fbb1e3ccaa7cb7" + ]; - check_serialization_roundtrip(&capsule); + check_serialization_roundtrip(&capsule, expected_json, expected_rmp_hex); } } diff --git a/umbral-pre/src/capsule_frag.rs b/umbral-pre/src/capsule_frag.rs index 5c22ad3..6df31a4 100644 --- a/umbral-pre/src/capsule_frag.rs +++ b/umbral-pre/src/capsule_frag.rs @@ -304,37 +304,55 @@ mod tests { use alloc::boxed::Box; use alloc::vec::Vec; - use super::VerifiedCapsuleFrag; + use rand_core::{CryptoRngCore, OsRng}; - use crate::{encrypt, generate_kfrags, reencrypt, Capsule, PublicKey, SecretKey, Signer}; + use super::VerifiedCapsuleFrag; + use crate::{ + encrypt_with_rng, generate_kfrags_with_rng, reencrypt_with_rng, Capsule, PublicKey, + SecretKey, Signer, + }; #[cfg(feature = "serde")] use crate::serde_bytes::tests::check_serialization_roundtrip; - fn prepare_cfrags() -> ( + #[cfg(feature = "serde")] + use ::{rand_chacha::ChaCha12Rng, rand_core::SeedableRng}; + + fn prepare_cfrags( + rng: &mut impl CryptoRngCore, + ) -> ( PublicKey, PublicKey, PublicKey, Capsule, Box<[VerifiedCapsuleFrag]>, ) { - let delegating_sk = SecretKey::random(); + let delegating_sk = SecretKey::random_with_rng(rng); let delegating_pk = delegating_sk.public_key(); - let signer = Signer::new(SecretKey::random()); + let signer = Signer::new(SecretKey::random_with_rng(rng)); let verifying_pk = signer.verifying_key(); - let receiving_sk = SecretKey::random(); + let receiving_sk = SecretKey::random_with_rng(rng); let receiving_pk = receiving_sk.public_key(); let plaintext = b"peace at dawn"; - let (capsule, _ciphertext) = encrypt(&delegating_pk, plaintext).unwrap(); - - let kfrags = generate_kfrags(&delegating_sk, &receiving_pk, &signer, 2, 3, true, true); + let (capsule, _ciphertext) = encrypt_with_rng(rng, &delegating_pk, plaintext).unwrap(); + + let kfrags = generate_kfrags_with_rng( + rng, + &delegating_sk, + &receiving_pk, + &signer, + 2, + 3, + true, + true, + ); let verified_cfrags: Vec<_> = kfrags .iter() - .map(|kfrag| reencrypt(&capsule, kfrag.clone())) + .map(|kfrag| reencrypt_with_rng(rng, &capsule, kfrag.clone())) .collect(); ( @@ -349,7 +367,7 @@ mod tests { #[test] fn test_verify() { let (delegating_pk, receiving_pk, verifying_pk, capsule, verified_cfrags) = - prepare_cfrags(); + prepare_cfrags(&mut OsRng); for verified_cfrag in verified_cfrags.iter() { let cfrag = verified_cfrag.clone().unverify(); @@ -364,8 +382,10 @@ mod tests { #[cfg(feature = "serde")] #[test] fn test_serde_serialization() { + let mut rng = ChaCha12Rng::seed_from_u64(12345); + let (_delegating_pk, _receiving_pk, _verifying_pk, _capsule, verified_cfrags) = - prepare_cfrags(); + prepare_cfrags(&mut rng); let cfrag = verified_cfrags[0].clone().unverify(); @@ -374,6 +394,34 @@ mod tests { let vcfrag_bytes = rmp_serde::to_vec(&verified_cfrags[0]).unwrap(); assert_eq!(vcfrag_bytes, cfrag_bytes); - check_serialization_roundtrip(&cfrag); + let expected_json = concat![ + "{\"point_e1\":\"0x0300f0e3704235018e1ee413e2c309b8ea539106dec3c871beb821c891eddb2da4\",", + "\"point_v1\":\"0x02cba2bdc29647a8997242e9f4ece8de071d9d63b42df456d48129ffc8dcfa5042\",", + "\"kfrag_id\":\"0xcb118de29ffd6aeaa84f6249ff7a9e2c89b77bf1ced81370fe377a1f1fd5d039\",", + "\"precursor\":\"0x02e66f6cc2b3caa64b51098faada4f73de2948416522ca301afa4e5682288fc106\",", + "\"proof\":{\"point_e2\":\"0x0364de0d2b868228b8cced8aa7a527d708b59669e3fa2b99963242e0daba718fe9\",", + "\"point_v2\":\"0x031d75e0c706202673bbe860f936b8cf8668996e94e7b27ddca95acb6bd7d70b34\",", + "\"kfrag_commitment\":\"0x036e8c78f6439c428bbfba49874cb8be3d7af7905de024447f8811bcbead032eb5\",", + "\"kfrag_pok\":\"0x02ae4ab8f1ee0eefe04d788d8e7b51ee92b72b384d0c914d80c67625303ce6e4e4\",", + "\"signature\":\"0x1347aff126ed0f80f8215baa8188e3774ef0f725283f4247fc5fc559a6fda0b0\",", + "\"kfrag_signature\":\"0x4bc49063be3c6f542b27a15874d8e2621357ffca69dea8ded236106e90fc4d87", + "3ce0cfdfea2f30412f9b50e88d87bd46773c1e7f55fe8cd1940462992e4c9f49\"}}" + ]; + let expected_rmp_hex = concat![ + "95c4210300f0e3704235018e1ee413e2c309b8ea539106dec3c871beb821c891", + "eddb2da4c42102cba2bdc29647a8997242e9f4ece8de071d9d63b42df456d481", + "29ffc8dcfa5042c420cb118de29ffd6aeaa84f6249ff7a9e2c89b77bf1ced813", + "70fe377a1f1fd5d039c42102e66f6cc2b3caa64b51098faada4f73de29484165", + "22ca301afa4e5682288fc10696c4210364de0d2b868228b8cced8aa7a527d708", + "b59669e3fa2b99963242e0daba718fe9c421031d75e0c706202673bbe860f936", + "b8cf8668996e94e7b27ddca95acb6bd7d70b34c421036e8c78f6439c428bbfba", + "49874cb8be3d7af7905de024447f8811bcbead032eb5c42102ae4ab8f1ee0eef", + "e04d788d8e7b51ee92b72b384d0c914d80c67625303ce6e4e4c4201347aff126", + "ed0f80f8215baa8188e3774ef0f725283f4247fc5fc559a6fda0b0c4404bc490", + "63be3c6f542b27a15874d8e2621357ffca69dea8ded236106e90fc4d873ce0cf", + "dfea2f30412f9b50e88d87bd46773c1e7f55fe8cd1940462992e4c9f49" + ]; + + check_serialization_roundtrip(&cfrag, expected_json, expected_rmp_hex); } } diff --git a/umbral-pre/src/key_frag.rs b/umbral-pre/src/key_frag.rs index fdd7f2d..a0a0763 100644 --- a/umbral-pre/src/key_frag.rs +++ b/umbral-pre/src/key_frag.rs @@ -414,7 +414,7 @@ mod tests { use alloc::boxed::Box; - use rand_core::OsRng; + use rand_core::{CryptoRngCore, OsRng}; use super::{KeyFragBase, KeyFragVerificationError, VerifiedKeyFrag}; @@ -423,24 +423,28 @@ mod tests { #[cfg(feature = "serde")] use crate::serde_bytes::tests::check_serialization_roundtrip; + #[cfg(feature = "serde")] + use ::{rand_chacha::ChaCha12Rng, rand_core::SeedableRng}; + fn prepare_kfrags( + rng: &mut impl CryptoRngCore, sign_delegating_key: bool, sign_receiving_key: bool, ) -> (PublicKey, PublicKey, PublicKey, Box<[VerifiedKeyFrag]>) { - let delegating_sk = SecretKey::random(); + let delegating_sk = SecretKey::random_with_rng(rng); let delegating_pk = delegating_sk.public_key(); - let signer = Signer::new(SecretKey::random()); + let signer = Signer::new(SecretKey::random_with_rng(rng)); let verifying_pk = signer.verifying_key(); - let receiving_sk = SecretKey::random(); + let receiving_sk = SecretKey::random_with_rng(rng); let receiving_pk = receiving_sk.public_key(); - let base = KeyFragBase::new(&mut OsRng, &delegating_sk, &receiving_pk, &signer, 2); + let base = KeyFragBase::new(rng, &delegating_sk, &receiving_pk, &signer, 2); let vkfrags = [ - VerifiedKeyFrag::from_base(&mut OsRng, &base, sign_delegating_key, sign_receiving_key), - VerifiedKeyFrag::from_base(&mut OsRng, &base, sign_delegating_key, sign_receiving_key), - VerifiedKeyFrag::from_base(&mut OsRng, &base, sign_delegating_key, sign_receiving_key), + VerifiedKeyFrag::from_base(rng, &base, sign_delegating_key, sign_receiving_key), + VerifiedKeyFrag::from_base(rng, &base, sign_delegating_key, sign_receiving_key), + VerifiedKeyFrag::from_base(rng, &base, sign_delegating_key, sign_receiving_key), ]; (delegating_pk, receiving_pk, verifying_pk, Box::new(vkfrags)) @@ -451,7 +455,7 @@ mod tests { for sign_dk in [false, true].iter().copied() { for sign_rk in [false, true].iter().copied() { let (delegating_pk, receiving_pk, verifying_pk, vkfrags) = - prepare_kfrags(sign_dk, sign_rk); + prepare_kfrags(&mut OsRng, sign_dk, sign_rk); let kfrag = vkfrags[0].clone().unverify(); @@ -497,8 +501,10 @@ mod tests { #[cfg(feature = "serde")] #[test] fn test_serde_serialization() { + let mut rng = ChaCha12Rng::seed_from_u64(12345); + let (_delegating_pk, _receiving_pk, _verifying_pk, verified_kfrags) = - prepare_kfrags(true, true); + prepare_kfrags(&mut rng, true, true); let kfrag = verified_kfrags[0].clone().unverify(); @@ -507,6 +513,32 @@ mod tests { let vkfrag_bytes = rmp_serde::to_vec(&verified_kfrags[0]).unwrap(); assert_eq!(vkfrag_bytes, kfrag_bytes); - check_serialization_roundtrip(&kfrag); + let expected_json = concat![ + "{\"params\":{\"u\":\"0x03079390d0fbe220fc34aab8ecbf49098098036820fadb98c03143e55db8ec73cd\"},", + "\"id\":\"0x20c38b31f7ef25ff8724fdad8663ad53a385e5b2729fb196d13b3190f5cc8af9\",", + "\"key\":\"0xf739c142d42ecc5c8e23b13a00c60126e0e79f5376fa157e5fc13d79579fc6a1\",", + "\"precursor\":\"0x0369e9d6a5bedb9dca6ce33ee2e9984c5c3c592e85a6fa9f8ac634f38234474f42\",", + "\"proof\":{\"commitment\":\"0x03e23a121bd20da50c9ab7d24062004d7fb06d88259fbf768fd05e62f755442f28\",", + "\"signature_for_proxy\":\"0xbdd4c1cc66ef140cf002fefdc85dae21a2519062a1992ac29345732b2606ca81", + "439b40936bb0bd21f79aeac77ab1368e87f58b0e9f6c00a73b170b9867585754\",", + "\"signature_for_receiver\":\"0xb50de49e7738000cfeba816c0dd0aaca379abc291cac35d253666dc0c7db605c", + "5fa0e365abd1ae9b3819074c6695e51252f74d7dcb2345d70680e182465b470e\",", + "\"delegating_key_signed\":true,", + "\"receiving_key_signed\":true}}" + ]; + let expected_rmp_hex = concat![ + "9591c42103079390d0fbe220fc34aab8ecbf49098098036820fadb98c03143e5", + "5db8ec73cdc42020c38b31f7ef25ff8724fdad8663ad53a385e5b2729fb196d1", + "3b3190f5cc8af9c420f739c142d42ecc5c8e23b13a00c60126e0e79f5376fa15", + "7e5fc13d79579fc6a1c4210369e9d6a5bedb9dca6ce33ee2e9984c5c3c592e85", + "a6fa9f8ac634f38234474f4295c42103e23a121bd20da50c9ab7d24062004d7f", + "b06d88259fbf768fd05e62f755442f28c440bdd4c1cc66ef140cf002fefdc85d", + "ae21a2519062a1992ac29345732b2606ca81439b40936bb0bd21f79aeac77ab1", + "368e87f58b0e9f6c00a73b170b9867585754c440b50de49e7738000cfeba816c", + "0dd0aaca379abc291cac35d253666dc0c7db605c5fa0e365abd1ae9b3819074c", + "6695e51252f74d7dcb2345d70680e182465b470ec3c3" + ]; + + check_serialization_roundtrip(&kfrag, expected_json, expected_rmp_hex); } } diff --git a/umbral-pre/src/keys.rs b/umbral-pre/src/keys.rs index f0bded8..66bcc60 100644 --- a/umbral-pre/src/keys.rs +++ b/umbral-pre/src/keys.rs @@ -506,6 +506,8 @@ mod tests { #[cfg(feature = "serde")] use crate::serde_bytes::tests::check_serialization_roundtrip; + #[cfg(feature = "serde")] + use ::{rand_chacha::ChaCha12Rng, rand_core::SeedableRng}; #[test] fn test_secret_key_factory() { @@ -571,29 +573,59 @@ mod tests { #[cfg(feature = "serde")] #[test] fn test_serialize_signature() { + let mut rng = ChaCha12Rng::seed_from_u64(12345); + let message = b"asdafdahsfdasdfasd"; - let signer = Signer::new(SecretKey::random()); - let signature = signer.sign(message); + let signer = Signer::new(SecretKey::random_with_rng(&mut rng)); + let signature = signer.sign_with_rng(&mut rng, message); - check_serialization_roundtrip(&signature); + let expected_json = concat![ + "\"0x6b111e01ce5654c7e6807454c648bc64d39da141d7d76f4d9f2270e02dc89a90", + "3ac9a5f7094ede31703c2499824b93d08d5bcca8027386c53b733df1aa224604\"" + ]; + let expected_rmp_hex = concat![ + "c4406b111e01ce5654c7e6807454c648bc64d39da141d7d76f4d9f2270e02dc89a90", + "3ac9a5f7094ede31703c2499824b93d08d5bcca8027386c53b733df1aa224604" + ]; + + check_serialization_roundtrip(&signature, expected_json, expected_rmp_hex); } #[cfg(feature = "serde")] #[test] fn test_serialize_recoverable_signature() { + let mut rng = ChaCha12Rng::seed_from_u64(12345); + let message = b"asdafdahsfdasdfasd"; - let signer = Signer::new(SecretKey::random()); - let signature = signer.sign(message); + let signer = Signer::new(SecretKey::random_with_rng(&mut rng)); + let signature = signer.sign_with_rng(&mut rng, message); let rsig = RecoverableSignature::from_normalized(signature, true); - check_serialization_roundtrip(&rsig); + + let expected_json = concat![ + "\"0x6b111e01ce5654c7e6807454c648bc64d39da141d7d76f4d9f2270e02dc89a90", + "3ac9a5f7094ede31703c2499824b93d08d5bcca8027386c53b733df1aa22460401\"" + ]; + let expected_rmp_hex = concat![ + "c4416b111e01ce5654c7e6807454c648bc64d39da141d7d76f4d9f2270e02dc89a90", + "3ac9a5f7094ede31703c2499824b93d08d5bcca8027386c53b733df1aa22460401" + ]; + + check_serialization_roundtrip(&rsig, expected_json, expected_rmp_hex); } #[cfg(feature = "serde")] #[test] fn test_serialize_public_key() { - let signer = Signer::new(SecretKey::random()); + let mut rng = ChaCha12Rng::seed_from_u64(12345); + + let signer = Signer::new(SecretKey::random_with_rng(&mut rng)); let pk = signer.verifying_key(); - check_serialization_roundtrip(&pk); + let expected_json = + "\"0x0246003fc57f66ab3634fb0409f91dacd14bf0200443b5bcc0d4a5673076354579\""; + let expected_rmp_hex = + "c4210246003fc57f66ab3634fb0409f91dacd14bf0200443b5bcc0d4a5673076354579"; + + check_serialization_roundtrip(&pk, expected_json, expected_rmp_hex); } } diff --git a/umbral-pre/src/serde_bytes.rs b/umbral-pre/src/serde_bytes.rs index 7f5dc86..b8a58bc 100644 --- a/umbral-pre/src/serde_bytes.rs +++ b/umbral-pre/src/serde_bytes.rs @@ -252,19 +252,26 @@ pub(crate) mod tests { use serde::de::DeserializeOwned; use serde::Serialize; - pub(crate) fn check_serialization_roundtrip(obj: &T) - where + pub(crate) fn check_serialization_roundtrip( + obj: &T, + expected_json: &str, + expected_rmp_hex: &str, + ) where T: fmt::Debug + PartialEq + Serialize + DeserializeOwned, { // Check serialization to JSON (human-readable) let serialized = serde_json::to_string(obj).unwrap(); + assert_eq!(serialized, expected_json); + let deserialized: T = serde_json::from_str(&serialized).unwrap(); assert_eq!(obj, &deserialized); // Check serialization to MessagePack (binary) let serialized = rmp_serde::to_vec(obj).unwrap(); + assert_eq!(hex::encode(&serialized), expected_rmp_hex); + let deserialized: T = rmp_serde::from_slice(&serialized).unwrap(); assert_eq!(obj, &deserialized); } From 88b4a02040fc20aa237b69950f3f59159a01587c Mon Sep 17 00:00:00 2001 From: Bogdan Opanchuk Date: Mon, 3 Nov 2025 12:40:05 -0800 Subject: [PATCH 5/9] Use `serde-encoded-bytes` --- umbral-pre/Cargo.toml | 3 +- umbral-pre/src/capsule.rs | 2 +- umbral-pre/src/capsule_frag.rs | 2 +- umbral-pre/src/curve.rs | 20 ++- umbral-pre/src/evidence.rs | 5 +- umbral-pre/src/key_frag.rs | 52 ++---- umbral-pre/src/keys.rs | 31 ++-- umbral-pre/src/lib.rs | 4 +- umbral-pre/src/serde_bytes.rs | 278 --------------------------------- umbral-pre/src/serde_test.rs | 25 +++ 10 files changed, 69 insertions(+), 353 deletions(-) delete mode 100644 umbral-pre/src/serde_bytes.rs create mode 100644 umbral-pre/src/serde_test.rs diff --git a/umbral-pre/Cargo.toml b/umbral-pre/Cargo.toml index f374478..8392d2d 100644 --- a/umbral-pre/Cargo.toml +++ b/umbral-pre/Cargo.toml @@ -24,6 +24,7 @@ js-sys = { version = "0.3.63", optional = true } wasm-bindgen = { version = "0.2.86", optional = true } derive_more = { version = "0.99", optional = true, default-features = false, features = ["as_ref", "from", "into"] } wasm-bindgen-derive = { version = "0.2.0", optional = true } +serde-encoded-bytes = { version = "0.2", optional = true, features = ["hex", "generic-array-014"] } # These packages are among the dependencies of the packages above. # Their versions should be updated when the main packages above are updated. @@ -46,7 +47,7 @@ bindings-python = ["pyo3", "std", "derive_more", "default-serialization"] bindings-wasm = ["js-sys", "default-serialization", "wasm-bindgen", "derive_more", "wasm-bindgen-derive", "getrandom/js"] default-rng = ["getrandom", "rand_core/getrandom"] default-serialization = ["serde", "rmp-serde"] -serde = ["dep:serde"] +serde = ["dep:serde", "serde-encoded-bytes"] std = [] # What features to use when building documentation on docs.rs diff --git a/umbral-pre/src/capsule.rs b/umbral-pre/src/capsule.rs index 9d6eb0e..89f1500 100644 --- a/umbral-pre/src/capsule.rs +++ b/umbral-pre/src/capsule.rs @@ -268,7 +268,7 @@ mod tests { use crate::{generate_kfrags, reencrypt, SecretKey, Signer}; #[cfg(feature = "serde")] - use crate::serde_bytes::tests::check_serialization_roundtrip; + use crate::serde_test::check_serialization_roundtrip; #[cfg(feature = "serde")] use ::{rand_chacha::ChaCha12Rng, rand_core::SeedableRng}; diff --git a/umbral-pre/src/capsule_frag.rs b/umbral-pre/src/capsule_frag.rs index 6df31a4..168d946 100644 --- a/umbral-pre/src/capsule_frag.rs +++ b/umbral-pre/src/capsule_frag.rs @@ -313,7 +313,7 @@ mod tests { }; #[cfg(feature = "serde")] - use crate::serde_bytes::tests::check_serialization_roundtrip; + use crate::serde_test::check_serialization_roundtrip; #[cfg(feature = "serde")] use ::{rand_chacha::ChaCha12Rng, rand_core::SeedableRng}; diff --git a/umbral-pre/src/curve.rs b/umbral-pre/src/curve.rs index 8236a5b..fab5a85 100644 --- a/umbral-pre/src/curve.rs +++ b/umbral-pre/src/curve.rs @@ -34,9 +34,7 @@ use k256::elliptic_curve::group::ff::PrimeField; use serde::{Deserialize, Deserializer, Serialize, Serializer}; #[cfg(feature = "serde")] -use crate::serde_bytes::{ - deserialize_with_encoding, serialize_with_encoding, Encoding, TryFromBytes, -}; +use serde_encoded_bytes::{Hex, SliceLike}; pub(crate) type CurveType = Secp256k1; pub(crate) type CompressedPointSize = @@ -89,7 +87,7 @@ impl Serialize for CurveScalar { where S: Serializer, { - serialize_with_encoding(&self.0.to_bytes(), serializer, Encoding::Hex) + SliceLike::::serialize(&self.0.to_bytes(), serializer) } } @@ -99,15 +97,15 @@ impl<'de> Deserialize<'de> for CurveScalar { where D: Deserializer<'de>, { - deserialize_with_encoding(deserializer, Encoding::Hex) + SliceLike::::deserialize(deserializer) } } #[cfg(feature = "serde")] -impl TryFromBytes for CurveScalar { +impl<'a> TryFrom<&'a [u8]> for CurveScalar { type Error = String; - fn try_from_bytes(bytes: &[u8]) -> Result { + fn try_from(bytes: &'a [u8]) -> Result { Self::try_from_bytes(bytes) } } @@ -238,7 +236,7 @@ impl Serialize for CurvePoint { where S: Serializer, { - serialize_with_encoding(&self.to_compressed_array(), serializer, Encoding::Hex) + SliceLike::::serialize(&self.to_compressed_array(), serializer) } } @@ -248,15 +246,15 @@ impl<'de> Deserialize<'de> for CurvePoint { where D: Deserializer<'de>, { - deserialize_with_encoding(deserializer, Encoding::Hex) + SliceLike::::deserialize(deserializer) } } #[cfg(feature = "serde")] -impl TryFromBytes for CurvePoint { +impl<'a> TryFrom<&'a [u8]> for CurvePoint { type Error = String; - fn try_from_bytes(bytes: &[u8]) -> Result { + fn try_from(bytes: &'a [u8]) -> Result { Self::try_from_compressed_bytes(bytes) } } diff --git a/umbral-pre/src/evidence.rs b/umbral-pre/src/evidence.rs index d7bcbd2..b53f15e 100644 --- a/umbral-pre/src/evidence.rs +++ b/umbral-pre/src/evidence.rs @@ -5,6 +5,9 @@ use sha2::digest::Digest; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; +#[cfg(feature = "serde")] +use serde_encoded_bytes::{GenericArray014, Hex}; + use crate::curve::CurvePoint; use crate::hashing::BackendDigestOutput; use crate::hashing_ds::{hash_to_cfrag_verification, kfrag_signature_message}; @@ -81,7 +84,7 @@ pub struct ReencryptionEvidence { pub u2: CurvePoint, /// The hashed message used to create `kfrag_signature` in /// [`CapsuleFrag::to_bytes_simple`]. - #[cfg_attr(feature = "serde", serde(with = "crate::serde_bytes::as_hex"))] + #[cfg_attr(feature = "serde", serde(with = "GenericArray014::"))] pub kfrag_validity_message_hash: BackendDigestOutput, /// The recovery byte corresponding to `kfrag_signature` in [`CapsuleFrag::to_bytes_simple`] /// (`true` corresponds to `0x01` and `false` to `0x00`). diff --git a/umbral-pre/src/key_frag.rs b/umbral-pre/src/key_frag.rs index a0a0763..7b9f28e 100644 --- a/umbral-pre/src/key_frag.rs +++ b/umbral-pre/src/key_frag.rs @@ -1,6 +1,3 @@ -#[cfg(feature = "serde")] -use alloc::string::String; - use alloc::boxed::Box; use alloc::vec::Vec; use core::fmt; @@ -9,7 +6,10 @@ use generic_array::{typenum::U32, GenericArray}; use rand_core::{CryptoRng, RngCore}; #[cfg(feature = "serde")] -use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use serde::{Deserialize, Serialize}; + +#[cfg(feature = "serde")] +use serde_encoded_bytes::{GenericArray014, Hex}; use crate::curve::{CurvePoint, CurveScalar, NonZeroCurveScalar}; use crate::hashing_ds::{hash_to_polynomial_arg, hash_to_shared_secret, kfrag_signature_message}; @@ -21,17 +21,16 @@ use crate::traits::fmt_public; #[cfg(feature = "default-serialization")] use crate::{DefaultDeserialize, DefaultSerialize}; -#[cfg(feature = "serde")] -use crate::serde_bytes::{ - deserialize_with_encoding, serialize_with_encoding, Encoding, TryFromBytes, -}; - #[allow(clippy::upper_case_acronyms)] type KeyFragIDSize = U32; #[allow(clippy::upper_case_acronyms)] #[derive(Clone, Copy, Debug, PartialEq)] -pub(crate) struct KeyFragID(GenericArray); +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub(crate) struct KeyFragID( + #[cfg_attr(feature = "serde", serde(with = "GenericArray014::"))] + GenericArray, +); impl KeyFragID { fn random(rng: &mut impl RngCore) -> Self { @@ -47,37 +46,6 @@ impl AsRef<[u8]> for KeyFragID { } } -#[cfg(feature = "serde")] -impl Serialize for KeyFragID { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - serialize_with_encoding(&self.0, serializer, Encoding::Hex) - } -} - -#[cfg(feature = "serde")] -impl<'de> Deserialize<'de> for KeyFragID { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - deserialize_with_encoding(deserializer, Encoding::Hex) - } -} - -#[cfg(feature = "serde")] -impl TryFromBytes for KeyFragID { - type Error = String; - - fn try_from_bytes(bytes: &[u8]) -> Result { - let arr = GenericArray::::from_exact_iter(bytes.iter().cloned()) - .ok_or("Invalid length of a key frag ID")?; - Ok(Self(arr)) - } -} - #[derive(Clone, Debug, PartialEq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub(crate) struct KeyFragProof { @@ -421,7 +389,7 @@ mod tests { use crate::{PublicKey, SecretKey, Signer}; #[cfg(feature = "serde")] - use crate::serde_bytes::tests::check_serialization_roundtrip; + use crate::serde_test::check_serialization_roundtrip; #[cfg(feature = "serde")] use ::{rand_chacha::ChaCha12Rng, rand_core::SeedableRng}; diff --git a/umbral-pre/src/keys.rs b/umbral-pre/src/keys.rs index 66bcc60..59d1047 100644 --- a/umbral-pre/src/keys.rs +++ b/umbral-pre/src/keys.rs @@ -32,9 +32,7 @@ use crate::secret_box::SecretBox; use crate::traits::{fmt_public, fmt_secret, SizeMismatchError}; #[cfg(feature = "serde")] -use crate::serde_bytes::{ - deserialize_with_encoding, serialize_with_encoding, Encoding, TryFromBytes, -}; +use serde_encoded_bytes::{Hex, SliceLike}; /// ECDSA signature object. #[derive(Clone, Debug, PartialEq, Eq)] @@ -100,7 +98,7 @@ impl Serialize for Signature { where S: Serializer, { - serialize_with_encoding(&self.to_be_bytes(), serializer, Encoding::Hex) + SliceLike::::serialize(&self.to_be_bytes(), serializer) } } @@ -110,15 +108,15 @@ impl<'de> Deserialize<'de> for Signature { where D: Deserializer<'de>, { - deserialize_with_encoding(deserializer, Encoding::Hex) + SliceLike::::deserialize(deserializer) } } #[cfg(feature = "serde")] -impl TryFromBytes for Signature { +impl<'a> TryFrom<&'a [u8]> for Signature { type Error = String; - fn try_from_bytes(bytes: &[u8]) -> Result { + fn try_from(bytes: &'a [u8]) -> Result { Self::try_from_be_bytes(bytes) } } @@ -188,7 +186,7 @@ impl Serialize for RecoverableSignature { where S: Serializer, { - serialize_with_encoding(&self.to_be_bytes(), serializer, Encoding::Hex) + SliceLike::::serialize(&self.to_be_bytes(), serializer) } } @@ -198,15 +196,15 @@ impl<'de> Deserialize<'de> for RecoverableSignature { where D: Deserializer<'de>, { - deserialize_with_encoding(deserializer, Encoding::Hex) + SliceLike::::deserialize(deserializer) } } #[cfg(feature = "serde")] -impl TryFromBytes for RecoverableSignature { +impl<'a> TryFrom<&'a [u8]> for RecoverableSignature { type Error = String; - fn try_from_bytes(bytes: &[u8]) -> Result { + fn try_from(bytes: &'a [u8]) -> Result { Self::try_from_be_bytes(bytes) } } @@ -381,7 +379,7 @@ impl Serialize for PublicKey { where S: Serializer, { - serialize_with_encoding(&self.to_compressed_bytes(), serializer, Encoding::Hex) + SliceLike::::serialize(&self.to_compressed_bytes(), serializer) } } @@ -391,15 +389,15 @@ impl<'de> Deserialize<'de> for PublicKey { where D: Deserializer<'de>, { - deserialize_with_encoding(deserializer, Encoding::Hex) + SliceLike::::deserialize(deserializer) } } #[cfg(feature = "serde")] -impl TryFromBytes for PublicKey { +impl<'a> TryFrom<&'a [u8]> for PublicKey { type Error = String; - fn try_from_bytes(bytes: &[u8]) -> Result { + fn try_from(bytes: &'a [u8]) -> Result { Self::try_from_compressed_bytes(bytes) } } @@ -505,7 +503,8 @@ mod tests { }; #[cfg(feature = "serde")] - use crate::serde_bytes::tests::check_serialization_roundtrip; + use crate::serde_test::check_serialization_roundtrip; + #[cfg(feature = "serde")] use ::{rand_chacha::ChaCha12Rng, rand_core::SeedableRng}; diff --git a/umbral-pre/src/lib.rs b/umbral-pre/src/lib.rs index c5feff9..c0b9285 100644 --- a/umbral-pre/src/lib.rs +++ b/umbral-pre/src/lib.rs @@ -140,8 +140,8 @@ mod pre; mod secret_box; mod traits; -#[cfg(feature = "serde")] -pub mod serde_bytes; +#[cfg(all(test, feature = "serde"))] +pub(crate) mod serde_test; pub use capsule::{Capsule, OpenReencryptedError}; pub use capsule_frag::{CapsuleFrag, CapsuleFragVerificationError, VerifiedCapsuleFrag}; diff --git a/umbral-pre/src/serde_bytes.rs b/umbral-pre/src/serde_bytes.rs deleted file mode 100644 index b8a58bc..0000000 --- a/umbral-pre/src/serde_bytes.rs +++ /dev/null @@ -1,278 +0,0 @@ -//! Utility functions for efficient bytestring serialization with `serde` -//! (by default they are serialized as vectors of integers). - -use alloc::boxed::Box; -use alloc::format; -use alloc::string::String; -use core::any::type_name; -use core::fmt; -use core::marker::PhantomData; - -use base64::{engine::general_purpose, Engine as _}; -use generic_array::{ArrayLength, GenericArray}; -use serde::{de, Deserializer, Serializer}; - -pub(crate) enum Encoding { - /// Use base64 representation for byte arrays. - Base64, - /// Use hex representation for byte arrays. - Hex, -} - -struct B64Visitor(PhantomData); - -impl<'de, T> de::Visitor<'de> for B64Visitor -where - T: TryFromBytes, -{ - type Value = T; - - fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "b64-encoded {} bytes", type_name::()) - } - - fn visit_str(self, v: &str) -> Result - where - E: de::Error, - { - let bytes = general_purpose::STANDARD_NO_PAD - .decode(v) - .map_err(de::Error::custom)?; - T::try_from_bytes(&bytes).map_err(de::Error::custom) - } -} - -struct HexVisitor(PhantomData); - -impl<'de, T> de::Visitor<'de> for HexVisitor -where - T: TryFromBytes, -{ - type Value = T; - - fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "0x-prefixed hex-encoded bytes of {}", type_name::()) - } - - fn visit_str(self, v: &str) -> Result - where - E: de::Error, - { - if v.len() < 2 { - return Err(de::Error::invalid_length( - v.len(), - &"0x-prefixed hex-encoded bytes", - )); - } - if &v[..2] != "0x" { - return Err(de::Error::invalid_value( - de::Unexpected::Str(v), - &"0x-prefixed hex-encoded bytes", - )); - } - let bytes = hex::decode(&v[2..]).map_err(de::Error::custom)?; - T::try_from_bytes(&bytes).map_err(de::Error::custom) - } -} - -struct BytesVisitor(PhantomData); - -impl<'de, T> de::Visitor<'de> for BytesVisitor -where - T: TryFromBytes, -{ - type Value = T; - - fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{} bytes", type_name::()) - } - - fn visit_bytes(self, v: &[u8]) -> Result - where - E: de::Error, - { - T::try_from_bytes(v).map_err(de::Error::custom) - } -} - -/// A helper function that will serialize a byte array efficiently -/// depending on whether the target format is text or binary based. -pub(crate) fn serialize_with_encoding( - obj: &T, - serializer: S, - encoding: Encoding, -) -> Result -where - T: AsRef<[u8]>, - S: Serializer, -{ - if serializer.is_human_readable() { - let encoded = match encoding { - Encoding::Base64 => general_purpose::STANDARD_NO_PAD.encode(obj.as_ref()), - Encoding::Hex => format!("0x{}", hex::encode(obj.as_ref())), - }; - serializer.serialize_str(&encoded) - } else { - serializer.serialize_bytes(obj.as_ref()) - } -} - -/// A helper function that will deserialize from a byte array, -/// matching the format used by [`serde_serialize`]. -pub(crate) fn deserialize_with_encoding<'de, T, D>( - deserializer: D, - encoding: Encoding, -) -> Result -where - D: Deserializer<'de>, - T: TryFromBytes, -{ - if deserializer.is_human_readable() { - match encoding { - Encoding::Base64 => deserializer.deserialize_str(B64Visitor::(PhantomData)), - Encoding::Hex => deserializer.deserialize_str(HexVisitor::(PhantomData)), - } - } else { - deserializer.deserialize_bytes(BytesVisitor::(PhantomData)) - } -} - -pub mod as_hex { - //! A module containing serialization and deserialization function - //! that use hex (`0x`-prefixed) representation for bytestrings in human-readable formats. - //! - //! To be used in `[serde(with)]` field attribute. - - use super::*; - - /// Serialize an object representable as bytes using `0x`-prefixed hex encoding - /// if the target format is human-readable, and plain bytes otherwise. - pub fn serialize(obj: &T, serializer: S) -> Result - where - T: AsRef<[u8]>, - S: Serializer, - { - serialize_with_encoding(obj, serializer, Encoding::Hex) - } - - /// Deserialize an object representable as bytes assuming `0x`-prefixed hex encoding - /// if the source format is human-readable, and plain bytes otherwise. - pub fn deserialize<'de, T, D>(deserializer: D) -> Result - where - D: Deserializer<'de>, - T: TryFromBytes, - { - deserialize_with_encoding(deserializer, Encoding::Hex) - } -} - -pub mod as_base64 { - //! A module containing serialization and deserialization function - //! that use hex (`0x`-prefixed) representation for bytestrings. - //! - //! To be used in `[serde(with)]` field attribute. - - use super::*; - - /// Serialize an object representable as bytes using `base64` encoding - /// if the target format is human-readable, and plain bytes otherwise. - pub fn serialize(obj: &T, serializer: S) -> Result - where - T: AsRef<[u8]>, - S: Serializer, - { - serialize_with_encoding(obj, serializer, Encoding::Base64) - } - - /// Deserialize an object representable as bytes assuming `base64` encoding - /// if the source format is human-readable, and plain bytes otherwise. - pub fn deserialize<'de, T, D>(deserializer: D) -> Result - where - D: Deserializer<'de>, - T: TryFromBytes, - { - deserialize_with_encoding(deserializer, Encoding::Base64) - } -} - -/* -Ideally, we would generalize `deserialize()` for anything supporting `TryFrom<&[u8]>`. -But we want the associated `Error` to be `Display`, and for some reason `serde` -does not realize that `<<[u8; N]> as TryFrom<&'a [u8]>>::Error` -(which is equal to `TryFromSliceError`) is `Display`. -So we have to introduce our own trait with an `Error` that is definitely `Display`, -and generalize on that. -See https://github.com/serde-rs/serde/issues/2241 -*/ - -/// A trait providing a way to construct an object from a byte slice. -pub trait TryFromBytes: Sized { - /// The error returned on construction failure. - type Error: fmt::Display; - - /// Attempts to construct an object from a byte slice. - fn try_from_bytes(bytes: &[u8]) -> Result; -} - -impl TryFromBytes for [u8; N] { - type Error = core::array::TryFromSliceError; - - fn try_from_bytes(bytes: &[u8]) -> Result { - Self::try_from(bytes) - } -} - -impl TryFromBytes for Box<[u8]> { - type Error = core::convert::Infallible; - - fn try_from_bytes(bytes: &[u8]) -> Result { - Ok(bytes.into()) - } -} - -impl> TryFromBytes for GenericArray { - type Error = String; - - fn try_from_bytes(bytes: &[u8]) -> Result { - Self::from_exact_iter(bytes.iter().cloned()).ok_or_else(|| { - format!( - "Failed to instantiate a GenericArray: expected size {}, got {}", - T::to_usize(), - bytes.len() - ) - }) - } -} - -#[cfg(test)] -pub(crate) mod tests { - - use core::fmt; - - use serde::de::DeserializeOwned; - use serde::Serialize; - - pub(crate) fn check_serialization_roundtrip( - obj: &T, - expected_json: &str, - expected_rmp_hex: &str, - ) where - T: fmt::Debug + PartialEq + Serialize + DeserializeOwned, - { - // Check serialization to JSON (human-readable) - - let serialized = serde_json::to_string(obj).unwrap(); - assert_eq!(serialized, expected_json); - - let deserialized: T = serde_json::from_str(&serialized).unwrap(); - assert_eq!(obj, &deserialized); - - // Check serialization to MessagePack (binary) - - let serialized = rmp_serde::to_vec(obj).unwrap(); - assert_eq!(hex::encode(&serialized), expected_rmp_hex); - - let deserialized: T = rmp_serde::from_slice(&serialized).unwrap(); - assert_eq!(obj, &deserialized); - } -} diff --git a/umbral-pre/src/serde_test.rs b/umbral-pre/src/serde_test.rs new file mode 100644 index 0000000..5159454 --- /dev/null +++ b/umbral-pre/src/serde_test.rs @@ -0,0 +1,25 @@ +use core::fmt; + +use serde::de::DeserializeOwned; +use serde::Serialize; + +pub(crate) fn check_serialization_roundtrip(obj: &T, expected_json: &str, expected_rmp_hex: &str) +where + T: fmt::Debug + PartialEq + Serialize + DeserializeOwned, +{ + // Check serialization to JSON (human-readable) + + let serialized = serde_json::to_string(obj).unwrap(); + assert_eq!(serialized, expected_json); + + let deserialized: T = serde_json::from_str(&serialized).unwrap(); + assert_eq!(obj, &deserialized); + + // Check serialization to MessagePack (binary) + + let serialized = rmp_serde::to_vec(obj).unwrap(); + assert_eq!(hex::encode(&serialized), expected_rmp_hex); + + let deserialized: T = rmp_serde::from_slice(&serialized).unwrap(); + assert_eq!(obj, &deserialized); +} From b5938748ceb2d9cea257ae2d236a8d6726370194 Mon Sep 17 00:00:00 2001 From: Bogdan Opanchuk Date: Mon, 3 Nov 2025 13:36:21 -0800 Subject: [PATCH 6/9] Use `secrecy` --- umbral-pre/Cargo.toml | 1 + umbral-pre/src/bench.rs | 2 +- umbral-pre/src/bindings_python.rs | 5 +- umbral-pre/src/bindings_wasm.rs | 5 +- umbral-pre/src/capsule.rs | 44 ++++++++++-------- umbral-pre/src/capsule_frag.rs | 12 ++--- umbral-pre/src/dem.rs | 25 +++++----- umbral-pre/src/hashing.rs | 4 +- umbral-pre/src/key_frag.rs | 23 +++++----- umbral-pre/src/keys.rs | 51 ++++++++++----------- umbral-pre/src/lib.rs | 2 - umbral-pre/src/pre.rs | 7 +-- umbral-pre/src/secret_box.rs | 76 ------------------------------- 13 files changed, 93 insertions(+), 164 deletions(-) delete mode 100644 umbral-pre/src/secret_box.rs diff --git a/umbral-pre/Cargo.toml b/umbral-pre/Cargo.toml index 8392d2d..9c05d60 100644 --- a/umbral-pre/Cargo.toml +++ b/umbral-pre/Cargo.toml @@ -25,6 +25,7 @@ wasm-bindgen = { version = "0.2.86", optional = true } derive_more = { version = "0.99", optional = true, default-features = false, features = ["as_ref", "from", "into"] } wasm-bindgen-derive = { version = "0.2.0", optional = true } serde-encoded-bytes = { version = "0.2", optional = true, features = ["hex", "generic-array-014"] } +secrecy = { version = "0.10", default-features = false } # These packages are among the dependencies of the packages above. # Their versions should be updated when the main packages above are updated. diff --git a/umbral-pre/src/bench.rs b/umbral-pre/src/bench.rs index b5d9c17..86509fa 100644 --- a/umbral-pre/src/bench.rs +++ b/umbral-pre/src/bench.rs @@ -3,11 +3,11 @@ //! Should not be used by regular users. use rand_core::OsRng; +use secrecy::SecretBox; use crate::capsule::{Capsule, KeySeed, OpenReencryptedError}; use crate::capsule_frag::CapsuleFrag; use crate::keys::{PublicKey, SecretKey}; -use crate::secret_box::SecretBox; /// Exported `Capsule::from_public_key()` for benchmark purposes. pub fn capsule_from_public_key(delegating_pk: &PublicKey) -> (Capsule, SecretBox) { diff --git a/umbral-pre/src/bindings_python.rs b/umbral-pre/src/bindings_python.rs index f1396d7..b8e24d5 100644 --- a/umbral-pre/src/bindings_python.rs +++ b/umbral-pre/src/bindings_python.rs @@ -17,6 +17,7 @@ use pyo3::prelude::*; use pyo3::pyclass::PyClass; use pyo3::types::PyBytes; use pyo3::wrap_pyfunction; +use secrecy::ExposeSecret; use sha2::{digest::Update, Digest, Sha256}; use crate as umbral_pre; @@ -92,7 +93,7 @@ impl SecretKey { pub fn to_be_bytes(&self) -> Py { let serialized = self.backend.to_be_bytes(); - Python::attach(|py| PyBytes::new(py, serialized.as_secret()).into()) + Python::attach(|py| PyBytes::new(py, serialized.expose_secret()).into()) } #[staticmethod] @@ -140,7 +141,7 @@ impl SecretKeyFactory { pub fn make_secret(&self, label: &[u8]) -> Py { let secret = self.backend.make_secret(label); - let bytes: &[u8] = secret.as_secret().as_ref(); + let bytes: &[u8] = secret.expose_secret().as_ref(); Python::attach(|py| PyBytes::new(py, bytes).into()) } diff --git a/umbral-pre/src/bindings_wasm.rs b/umbral-pre/src/bindings_wasm.rs index 4e22ef1..0b8d66c 100644 --- a/umbral-pre/src/bindings_wasm.rs +++ b/umbral-pre/src/bindings_wasm.rs @@ -15,6 +15,7 @@ use alloc::vec::Vec; use core::fmt; use js_sys::{Error, Uint8Array}; +use secrecy::ExposeSecret; use wasm_bindgen::prelude::{wasm_bindgen, JsValue}; use wasm_bindgen::JsCast; use wasm_bindgen_derive::TryFromJsValue; @@ -97,7 +98,7 @@ impl SecretKey { #[wasm_bindgen(js_name = toBEBytes)] pub fn to_be_bytes(&self) -> Box<[u8]> { let serialized = self.0.to_be_bytes(); - let bytes: &[u8] = serialized.as_secret().as_ref(); + let bytes: &[u8] = serialized.expose_secret().as_ref(); bytes.into() } @@ -150,7 +151,7 @@ impl SecretKeyFactory { #[wasm_bindgen(js_name = makeSecret)] pub fn make_secret(&self, label: &[u8]) -> Vec { let secret = self.0.make_secret(label); - let bytes: &[u8] = secret.as_secret().as_ref(); + let bytes: &[u8] = secret.expose_secret().as_ref(); bytes.into() } diff --git a/umbral-pre/src/capsule.rs b/umbral-pre/src/capsule.rs index 89f1500..a5d4a83 100644 --- a/umbral-pre/src/capsule.rs +++ b/umbral-pre/src/capsule.rs @@ -7,6 +7,7 @@ use core::fmt; use generic_array::GenericArray; use rand_core::{CryptoRng, RngCore}; +use secrecy::{ExposeSecret, SecretBox}; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; @@ -16,7 +17,6 @@ use crate::curve::{CompressedPointSize, CurvePoint, CurveScalar, NonZeroCurveSca use crate::hashing_ds::{hash_capsule_points, hash_to_polynomial_arg, hash_to_shared_secret}; use crate::keys::{PublicKey, SecretKey}; use crate::params::Parameters; -use crate::secret_box::SecretBox; use crate::traits::fmt_public; #[cfg(feature = "default-serialization")] @@ -158,33 +158,34 @@ impl Capsule { ) -> (Capsule, SecretBox) { let g = CurvePoint::generator(); - let priv_r = SecretBox::new(NonZeroCurveScalar::random(rng)); - let pub_r = &g * priv_r.as_secret(); + let priv_r = SecretBox::init_with(|| NonZeroCurveScalar::random(rng)); + let pub_r = &g * priv_r.expose_secret(); - let priv_u = SecretBox::new(NonZeroCurveScalar::random(rng)); - let pub_u = &g * priv_u.as_secret(); + let priv_u = SecretBox::init_with(|| NonZeroCurveScalar::random(rng)); + let pub_u = &g * priv_u.expose_secret(); let h = hash_capsule_points(&pub_r, &pub_u); - let s = priv_u.as_secret() + &(priv_r.as_secret() * &h); + let s = priv_u.expose_secret() + &(priv_r.expose_secret() * &h); - let shared_key = - SecretBox::new(&delegating_pk.to_point() * &(priv_r.as_secret() + priv_u.as_secret())); + let shared_key = SecretBox::init_with(|| { + &delegating_pk.to_point() * &(priv_r.expose_secret() + priv_u.expose_secret()) + }); let capsule = Self::new(pub_r, pub_u, s); ( capsule, - SecretBox::new(shared_key.as_secret().to_compressed_array()), + SecretBox::init_with(|| shared_key.expose_secret().to_compressed_array()), ) } /// Derive the same symmetric key pub(crate) fn open_original(&self, delegating_sk: &SecretKey) -> SecretBox { - let shared_key = SecretBox::new( - &(&self.point_e + &self.point_v) * delegating_sk.to_secret_scalar().as_secret(), - ); - SecretBox::new(shared_key.as_secret().to_compressed_array()) + let shared_key = SecretBox::init_with(|| { + &(&self.point_e + &self.point_v) * delegating_sk.to_secret_scalar().expose_secret() + }); + SecretBox::init_with(|| shared_key.expose_secret().to_compressed_array()) } #[allow(clippy::many_single_char_names)] @@ -205,7 +206,7 @@ impl Capsule { } let pub_key = receiving_sk.public_key().to_point(); - let dh_point = &precursor * receiving_sk.to_secret_scalar().as_secret(); + let dh_point = &precursor * receiving_sk.to_secret_scalar().expose_secret(); // Combination of CFrags via Shamir's Secret Sharing reconstruction let mut lc = Vec::::with_capacity(cfrags.len()); @@ -239,8 +240,10 @@ impl Capsule { return Err(OpenReencryptedError::ValidationFailed); } - let shared_key = SecretBox::new(&(&e_prime + &v_prime) * &d); - Ok(SecretBox::new(shared_key.as_secret().to_compressed_array())) + let shared_key = SecretBox::init_with(|| &(&e_prime + &v_prime) * &d); + Ok(SecretBox::init_with(|| { + shared_key.expose_secret().to_compressed_array() + })) } } @@ -262,6 +265,7 @@ mod tests { use alloc::vec::Vec; use rand_core::OsRng; + use secrecy::ExposeSecret; use super::{Capsule, OpenReencryptedError}; @@ -297,12 +301,12 @@ mod tests { let key_seed_reenc = capsule .open_reencrypted(&receiving_sk, &delegating_pk, &cfrags) .unwrap(); - assert_eq!(key_seed.as_secret(), key_seed_reenc.as_secret()); + assert_eq!(key_seed.expose_secret(), key_seed_reenc.expose_secret()); // Empty cfrag vector let result = capsule.open_reencrypted(&receiving_sk, &delegating_pk, &[]); assert_eq!( - result.map(|x| *x.as_secret()), + result.map(|x| *x.expose_secret()), Err(OpenReencryptedError::NoCapsuleFrags) ); @@ -321,7 +325,7 @@ mod tests { let result = capsule.open_reencrypted(&receiving_sk, &delegating_pk, &mismatched_cfrags); assert_eq!( - result.map(|x| *x.as_secret()), + result.map(|x| *x.expose_secret()), Err(OpenReencryptedError::MismatchedCapsuleFrags) ); @@ -329,7 +333,7 @@ mod tests { let (capsule2, _key_seed) = Capsule::from_public_key(&mut OsRng, &delegating_pk); let result = capsule2.open_reencrypted(&receiving_sk, &delegating_pk, &cfrags); assert_eq!( - result.map(|x| *x.as_secret()), + result.map(|x| *x.expose_secret()), Err(OpenReencryptedError::ValidationFailed) ); } diff --git a/umbral-pre/src/capsule_frag.rs b/umbral-pre/src/capsule_frag.rs index 168d946..7313525 100644 --- a/umbral-pre/src/capsule_frag.rs +++ b/umbral-pre/src/capsule_frag.rs @@ -2,6 +2,7 @@ use alloc::boxed::Box; use core::fmt; use rand_core::{CryptoRng, RngCore}; +use secrecy::{ExposeSecret, SecretBox}; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; @@ -11,7 +12,6 @@ use crate::curve::{CurvePoint, CurveScalar, NonZeroCurveScalar}; use crate::hashing_ds::{hash_to_cfrag_verification, kfrag_signature_message}; use crate::key_frag::{KeyFrag, KeyFragID}; use crate::keys::{PublicKey, Signature}; -use crate::secret_box::SecretBox; use crate::traits::fmt_public; #[cfg(feature = "default-serialization")] @@ -40,7 +40,7 @@ impl CapsuleFragProof { let params = capsule.params; let rk = kfrag.key; - let t = SecretBox::new(NonZeroCurveScalar::random(rng)); + let t = SecretBox::init_with(|| NonZeroCurveScalar::random(rng)); // Here are the formulaic constituents shared with `CapsuleFrag::verify()`. @@ -53,15 +53,15 @@ impl CapsuleFragProof { let u = params.u; let u1 = kfrag.proof.commitment; - let e2 = &e * t.as_secret(); - let v2 = &v * t.as_secret(); - let u2 = &u * t.as_secret(); + let e2 = &e * t.expose_secret(); + let v2 = &v * t.expose_secret(); + let u2 = &u * t.expose_secret(); let h = hash_to_cfrag_verification(&e, e1, &e2, &v, v1, &v2, &u, &u1, &u2); //////// - let z3 = &(&rk * &h) + t.as_secret(); + let z3 = &(&rk * &h) + t.expose_secret(); Self { point_e2: e2, diff --git a/umbral-pre/src/dem.rs b/umbral-pre/src/dem.rs index 4efac31..5deaa63 100644 --- a/umbral-pre/src/dem.rs +++ b/umbral-pre/src/dem.rs @@ -8,11 +8,10 @@ use chacha20poly1305::{ use generic_array::{typenum::Unsigned, ArrayLength, GenericArray}; use hkdf::Hkdf; use rand_core::{CryptoRng, RngCore}; +use secrecy::{ExposeSecret, ExposeSecretMut, SecretBox}; use sha2::Sha256; use zeroize::ZeroizeOnDrop; -use crate::secret_box::SecretBox; - /// Errors that can happen during symmetric encryption. #[derive(Debug, PartialEq, Eq)] pub enum EncryptionError { @@ -62,12 +61,12 @@ pub(crate) fn kdf>( ) -> SecretBox> { let hk = Hkdf::::new(salt, seed); - let mut okm = SecretBox::new(GenericArray::::default()); + let mut okm = SecretBox::init_with(GenericArray::::default); let def_info = info.unwrap_or(&[]); // We can only get an error here if `S` is too large, and it's known at compile-time. - hk.expand(def_info, okm.as_mut_secret()).unwrap(); + hk.expand(def_info, okm.expose_secret_mut()).unwrap(); okm } @@ -86,8 +85,8 @@ impl DEM { let key_bytes = kdf::(key_seed, None, None); // Note that unlike `XChaCha20Poly1305`, `Key` is *not* zeroized automatically, // so we are wrapping it into a secret box. - let key = SecretBox::new(*Key::from_slice(key_bytes.as_secret())); - let cipher = XChaCha20Poly1305::new(key.as_secret()); + let key = SecretBox::init_with(|| *Key::from_slice(key_bytes.expose_secret())); + let cipher = XChaCha20Poly1305::new(key.expose_secret()); Self { cipher } } @@ -145,22 +144,22 @@ impl DEM { mod tests { use generic_array::typenum::U32; + use secrecy::{ExposeSecret, SecretBox}; use super::kdf; use crate::curve::CurvePoint; - use crate::secret_box::SecretBox; #[test] fn test_kdf() { let p1 = CurvePoint::generator(); let salt = b"abcdefg"; let info = b"sdasdasd"; - let seed = SecretBox::new(p1.to_compressed_array()); - let key = kdf::(seed.as_secret(), Some(&salt[..]), Some(&info[..])); - let key_same = kdf::(seed.as_secret(), Some(&salt[..]), Some(&info[..])); - assert_eq!(key.as_secret(), key_same.as_secret()); + let seed = SecretBox::init_with(|| p1.to_compressed_array()); + let key = kdf::(seed.expose_secret(), Some(&salt[..]), Some(&info[..])); + let key_same = kdf::(seed.expose_secret(), Some(&salt[..]), Some(&info[..])); + assert_eq!(key.expose_secret(), key_same.expose_secret()); - let key_diff = kdf::(seed.as_secret(), None, Some(&info[..])); - assert_ne!(key.as_secret(), key_diff.as_secret()); + let key_diff = kdf::(seed.expose_secret(), None, Some(&info[..])); + assert_ne!(key.expose_secret(), key_diff.expose_secret()); } } diff --git a/umbral-pre/src/hashing.rs b/umbral-pre/src/hashing.rs index 6bd7572..cdfa630 100644 --- a/umbral-pre/src/hashing.rs +++ b/umbral-pre/src/hashing.rs @@ -1,4 +1,5 @@ use generic_array::GenericArray; +use secrecy::{ExposeSecret, SecretBox}; use sha2::{ digest::{Digest, OutputSizeUser, Update}, Sha256, @@ -6,7 +7,6 @@ use sha2::{ use zeroize::Zeroize; use crate::curve::{CurvePoint, NonZeroCurveScalar}; -use crate::secret_box::SecretBox; // Our hash of choice. pub(crate) type BackendDigest = Sha256; @@ -35,7 +35,7 @@ impl Hash { bytes: &SecretBox, ) -> Self { // Assuming here that the bytes are not saved in `BackendDigest`. - Self(self.0.chain(bytes.as_secret())) + Self(self.0.chain(bytes.expose_secret())) } pub fn digest(self) -> BackendDigest { diff --git a/umbral-pre/src/key_frag.rs b/umbral-pre/src/key_frag.rs index 7b9f28e..8c38a28 100644 --- a/umbral-pre/src/key_frag.rs +++ b/umbral-pre/src/key_frag.rs @@ -4,6 +4,7 @@ use core::fmt; use generic_array::{typenum::U32, GenericArray}; use rand_core::{CryptoRng, RngCore}; +use secrecy::{ExposeSecret, ExposeSecretMut, SecretBox}; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; @@ -15,7 +16,6 @@ use crate::curve::{CurvePoint, CurveScalar, NonZeroCurveScalar}; use crate::hashing_ds::{hash_to_polynomial_arg, hash_to_shared_secret, kfrag_signature_message}; use crate::keys::{PublicKey, SecretKey, Signature, Signer}; use crate::params::Parameters; -use crate::secret_box::SecretBox; use crate::traits::fmt_public; #[cfg(feature = "default-serialization")] @@ -334,22 +334,23 @@ impl<'a> KeyFragBase<'a> { // The precursor point is used as an ephemeral public key in a DH key exchange, // and the resulting shared secret 'dh_point' is used to derive other secret values - let private_precursor = SecretBox::new(NonZeroCurveScalar::random(rng)); - let precursor = &g * private_precursor.as_secret(); + let private_precursor = SecretBox::init_with(|| NonZeroCurveScalar::random(rng)); + let precursor = &g * private_precursor.expose_secret(); - let dh_point = &receiving_pk_point * private_precursor.as_secret(); + let dh_point = &receiving_pk_point * private_precursor.expose_secret(); // Secret value 'd' allows to make Umbral non-interactive let d = hash_to_shared_secret(&precursor, &receiving_pk_point, &dh_point); // Coefficients of the generating polynomial - let coefficient0 = - SecretBox::new(delegating_sk.to_secret_scalar().as_secret() * &(d.invert())); + let coefficient0 = SecretBox::init_with(|| { + delegating_sk.to_secret_scalar().expose_secret() * &(d.invert()) + }); let mut coefficients = Vec::>::with_capacity(threshold); coefficients.push(coefficient0); for _i in 1..threshold { - coefficients.push(SecretBox::new(NonZeroCurveScalar::random(rng))); + coefficients.push(SecretBox::init_with(|| NonZeroCurveScalar::random(rng))); } Self { @@ -367,14 +368,14 @@ impl<'a> KeyFragBase<'a> { // Coefficients of the generating polynomial fn poly_eval(coeffs: &[SecretBox], x: &NonZeroCurveScalar) -> CurveScalar { let mut result: SecretBox = - SecretBox::new(coeffs[coeffs.len() - 1].as_secret().into()); + SecretBox::init_with(|| coeffs[coeffs.len() - 1].expose_secret().into()); for i in (0..coeffs.len() - 1).rev() { // Keeping the intermediate results zeroized as well - let temp = SecretBox::new(result.as_secret() * x); - *result.as_mut_secret() = temp.as_secret() + coeffs[i].as_secret(); + let temp = SecretBox::init_with(|| result.expose_secret() * x); + *result.expose_secret_mut() = temp.expose_secret() + coeffs[i].expose_secret(); } // This is not a secret anymore - *result.as_secret() + *result.expose_secret() } #[cfg(test)] diff --git a/umbral-pre/src/keys.rs b/umbral-pre/src/keys.rs index 59d1047..bc2e4b8 100644 --- a/umbral-pre/src/keys.rs +++ b/umbral-pre/src/keys.rs @@ -16,8 +16,8 @@ use k256::{ elliptic_curve::{FieldBytes, PublicKey as BackendPublicKey, SecretKey as BackendSecretKey}, }; use rand_core::{CryptoRng, RngCore}; +use secrecy::{ExposeSecret, ExposeSecretMut, SecretBox}; use sha2::digest::{Digest, FixedOutput}; -use zeroize::ZeroizeOnDrop; #[cfg(feature = "default-rng")] use rand_core::OsRng; @@ -28,7 +28,6 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer}; use crate::curve::{CompressedPointSize, CurvePoint, CurveType, NonZeroCurveScalar, ScalarSize}; use crate::dem::kdf; use crate::hashing::{BackendDigest, Hash, ScalarDigest}; -use crate::secret_box::SecretBox; use crate::traits::{fmt_public, fmt_secret, SizeMismatchError}; #[cfg(feature = "serde")] @@ -216,7 +215,7 @@ impl fmt::Display for RecoverableSignature { } /// A secret key. -#[derive(Clone, ZeroizeOnDrop, PartialEq, Eq)] +#[derive(Clone, PartialEq, Eq)] pub struct SecretKey(BackendSecretKey); impl SecretKey { @@ -241,30 +240,30 @@ impl SecretKey { } fn from_nonzero_scalar(scalar: SecretBox) -> Self { - let backend_scalar_ref = scalar.as_secret().as_backend_scalar(); + let backend_scalar_ref = scalar.expose_secret().as_backend_scalar(); Self::new(BackendSecretKey::::from(backend_scalar_ref)) } /// Returns a reference to the underlying scalar of the secret key. pub(crate) fn to_secret_scalar(&self) -> SecretBox { - let backend_scalar = SecretBox::new(self.0.to_nonzero_scalar()); - SecretBox::new(NonZeroCurveScalar::from_backend_scalar( - *backend_scalar.as_secret(), - )) + let backend_scalar = SecretBox::init_with(|| self.0.to_nonzero_scalar()); + SecretBox::init_with(|| { + NonZeroCurveScalar::from_backend_scalar(*backend_scalar.expose_secret()) + }) } /// Serializes the secret key as a scalar in the big-endian representation. pub fn to_be_bytes(&self) -> SecretBox> { - SecretBox::new(self.0.to_bytes()) + SecretBox::init_with(|| self.0.to_bytes()) } /// Deserializes the secret key from a scalar in the big-endian representation. pub fn try_from_be_bytes(bytes: &[u8]) -> Result { - let arr = SecretBox::new( + let arr = SecretBox::try_init_with(|| { GenericArray::::from_exact_iter(bytes.iter().cloned()) - .ok_or("Invalid length of a curve scalar")?, - ); - BackendSecretKey::::from_bytes(arr.as_secret()) + .ok_or("Invalid length of a curve scalar") + })?; + BackendSecretKey::::from_bytes(arr.expose_secret()) .map(Self::new) .map_err(|err| format!("{err}")) } @@ -282,7 +281,7 @@ pub(crate) fn digest_for_signing(message: &[u8]) -> BackendDigest { /// An object used to sign messages. /// For security reasons cannot be serialized. -#[derive(Clone, ZeroizeOnDrop)] +#[derive(Clone)] pub struct Signer(SigningKey); impl Signer { @@ -414,14 +413,13 @@ type SecretKeyFactorySeed = GenericArray; /// This class handles keyring material for Umbral, by allowing deterministic /// derivation of `SecretKey` objects based on labels. -#[derive(Clone, ZeroizeOnDrop, PartialEq)] pub struct SecretKeyFactory(SecretBox); impl SecretKeyFactory { /// Creates a secret key factory using the given RNG. pub fn random_with_rng(rng: &mut (impl CryptoRng + RngCore)) -> Self { - let mut bytes = SecretBox::new(SecretKeyFactorySeed::default()); - rng.fill_bytes(bytes.as_mut_secret()); + let mut bytes = SecretBox::init_with(SecretKeyFactorySeed::default); + rng.fill_bytes(bytes.expose_secret_mut()); Self(bytes) } @@ -448,9 +446,9 @@ impl SecretKeyFactory { Ordering::Greater | Ordering::Less => { Err(SizeMismatchError::new(received_size, expected_size)) } - Ordering::Equal => Ok(Self(SecretBox::new(*SecretKeyFactorySeed::from_slice( - seed, - )))), + Ordering::Equal => Ok(Self(SecretBox::init_with(|| { + *SecretKeyFactorySeed::from_slice(seed) + }))), } } @@ -462,19 +460,19 @@ impl SecretKeyFactory { ) -> SecretBox> { let prefix = b"SECRET_DERIVATION/"; let info = [prefix, label].concat(); - kdf::(self.0.as_secret(), None, Some(&info)) + kdf::(self.0.expose_secret(), None, Some(&info)) } /// Creates a `SecretKey` deterministically from the given label. pub fn make_key(&self, label: &[u8]) -> SecretKey { let prefix = b"KEY_DERIVATION/"; let info = [prefix, label].concat(); - let key = kdf::(self.0.as_secret(), None, Some(&info)); - let nz_scalar = SecretBox::new( + let key = kdf::(self.0.expose_secret(), None, Some(&info)); + let nz_scalar = SecretBox::init_with(|| { ScalarDigest::new_with_dst(&info) .chain_secret_bytes(&key) - .finalize(), - ); + .finalize() + }); SecretKey::from_nonzero_scalar(nz_scalar) } @@ -482,7 +480,8 @@ impl SecretKeyFactory { pub fn make_factory(&self, label: &[u8]) -> Self { let prefix = b"FACTORY_DERIVATION/"; let info = [prefix, label].concat(); - let derived_seed = kdf::(self.0.as_secret(), None, Some(&info)); + let derived_seed = + kdf::(self.0.expose_secret(), None, Some(&info)); Self(derived_seed) } } diff --git a/umbral-pre/src/lib.rs b/umbral-pre/src/lib.rs index c0b9285..1fba11d 100644 --- a/umbral-pre/src/lib.rs +++ b/umbral-pre/src/lib.rs @@ -137,7 +137,6 @@ mod key_frag; mod keys; mod params; mod pre; -mod secret_box; mod traits; #[cfg(all(test, feature = "serde"))] @@ -156,7 +155,6 @@ pub use pre::{ decrypt_original, decrypt_reencrypted, encrypt_with_rng, generate_kfrags_with_rng, reencrypt_with_rng, ReencryptionError, }; -pub use secret_box::SecretBox; #[cfg(feature = "default-rng")] pub use pre::{encrypt, generate_kfrags, reencrypt}; diff --git a/umbral-pre/src/pre.rs b/umbral-pre/src/pre.rs index 2440107..c0f2e49 100644 --- a/umbral-pre/src/pre.rs +++ b/umbral-pre/src/pre.rs @@ -3,6 +3,7 @@ use core::fmt; use rand_core::{CryptoRng, RngCore}; +use secrecy::ExposeSecret; #[cfg(feature = "default-rng")] use rand_core::OsRng; @@ -43,7 +44,7 @@ pub fn encrypt_with_rng( plaintext: &[u8], ) -> Result<(Capsule, Box<[u8]>), EncryptionError> { let (capsule, key_seed) = Capsule::from_public_key(rng, delegating_pk); - let dem = DEM::new(key_seed.as_secret()); + let dem = DEM::new(key_seed.expose_secret()); dem.encrypt(rng, plaintext, &capsule.to_bytes_simple()) .map(|ciphertext| (capsule, ciphertext)) } @@ -64,7 +65,7 @@ pub fn decrypt_original( ciphertext: impl AsRef<[u8]>, ) -> Result, DecryptionError> { let key_seed = capsule.open_original(delegating_sk); - let dem = DEM::new(key_seed.as_secret()); + let dem = DEM::new(key_seed.expose_secret()); dem.decrypt(ciphertext, &capsule.to_bytes_simple()) } @@ -180,7 +181,7 @@ pub fn decrypt_reencrypted( let key_seed = capsule .open_reencrypted(receiving_sk, delegating_pk, &cfrags) .map_err(ReencryptionError::OnOpen)?; - let dem = DEM::new(key_seed.as_secret()); + let dem = DEM::new(key_seed.expose_secret()); dem.decrypt(&ciphertext, &capsule.to_bytes_simple()) .map_err(ReencryptionError::OnDecryption) } diff --git a/umbral-pre/src/secret_box.rs b/umbral-pre/src/secret_box.rs deleted file mode 100644 index bc9fe58..0000000 --- a/umbral-pre/src/secret_box.rs +++ /dev/null @@ -1,76 +0,0 @@ -/* -This module implements a similar API to what the crate `secrecy` provides. -So, why our own implementation? - -First `secrecy::Secret` does not put its contents in a `Box`. -Using `Box` is a general recommendation of working with secret data, -because it prevents the compiler from putting it on stack, thus avoiding possible copies on borrow. - -Now, one could use `secrecy::Secret>`. -The problem here is that `secrecy::Secret` requires its type parameter to implement `Zeroize`. -This means that for a foreign type `F` (even if it does implement `Zeroize`) -we need to define `impl Zeroize for Box`. -But the compiler does not allow impls of foreign traits on foreign types. -This means that we also need to wrap `F` in a local type, impl `Zeroize` for the wrapper, -and then for the box of the wrapper. -This is too much boilerplate. - -Additionally, `secrecy::Secret>` means that after each `expose_secret()` -we will need to deal with opening the `Box` as well. -It's an inconvenience, albeit a minor one. - -The situation may improve in the future, and `secrecy` will actually become usable. -*/ - -use alloc::boxed::Box; - -use zeroize::{Zeroize, ZeroizeOnDrop}; - -/// A container for secret data. -/// Makes the usage of secret data explicit and easy to track, -/// prevents the secret data from being put on stack, -/// and zeroizes the contents on drop. -#[derive(Clone)] // No Debug derivation, to avoid exposing the secret data accidentally. -pub struct SecretBox(Box) -where - T: Zeroize + Clone; - -impl PartialEq> for SecretBox { - fn eq(&self, other: &Self) -> bool { - self.0 == other.0 - } -} - -impl SecretBox -where - T: Zeroize + Clone, -{ - pub(crate) fn new(val: T) -> Self { - Self(Box::new(val)) - } - - /// Returns an immutable reference to the secret data. - pub fn as_secret(&self) -> &T { - self.0.as_ref() - } - - /// Returns a mutable reference to the secret data. - pub fn as_mut_secret(&mut self) -> &mut T { - self.0.as_mut() - } -} - -impl Drop for SecretBox -where - T: Zeroize + Clone, -{ - fn drop(&mut self) { - self.0.zeroize() - } -} - -// Alternatively, it could be derived automatically, -// but there's some compilation problem in the macro. -// See https://github.com/RustCrypto/utils/issues/786 -// So we're asserting that this object is zeroized on drop, since there is a Drop impl just above. -impl ZeroizeOnDrop for SecretBox where T: Zeroize + Clone {} From 8f8db5ca8523cb94035a3a985c595d34f48eda61 Mon Sep 17 00:00:00 2001 From: Bogdan Opanchuk Date: Mon, 3 Nov 2025 13:40:35 -0800 Subject: [PATCH 7/9] Bump `wasm-bindgen-derive` to 0.3 --- umbral-pre/Cargo.toml | 2 +- umbral-pre/src/bindings_wasm.rs | 49 ++++----------------------------- 2 files changed, 7 insertions(+), 44 deletions(-) diff --git a/umbral-pre/Cargo.toml b/umbral-pre/Cargo.toml index 9c05d60..dae5410 100644 --- a/umbral-pre/Cargo.toml +++ b/umbral-pre/Cargo.toml @@ -23,7 +23,7 @@ pyo3 = { version = "0.27", optional = true } js-sys = { version = "0.3.63", optional = true } wasm-bindgen = { version = "0.2.86", optional = true } derive_more = { version = "0.99", optional = true, default-features = false, features = ["as_ref", "from", "into"] } -wasm-bindgen-derive = { version = "0.2.0", optional = true } +wasm-bindgen-derive = { version = "0.3", optional = true } serde-encoded-bytes = { version = "0.2", optional = true, features = ["hex", "generic-array-014"] } secrecy = { version = "0.10", default-features = false } diff --git a/umbral-pre/src/bindings_wasm.rs b/umbral-pre/src/bindings_wasm.rs index 0b8d66c..1d0b385 100644 --- a/umbral-pre/src/bindings_wasm.rs +++ b/umbral-pre/src/bindings_wasm.rs @@ -18,7 +18,7 @@ use js_sys::{Error, Uint8Array}; use secrecy::ExposeSecret; use wasm_bindgen::prelude::{wasm_bindgen, JsValue}; use wasm_bindgen::JsCast; -use wasm_bindgen_derive::TryFromJsValue; +use wasm_bindgen_derive::{try_from_js_array, try_from_js_option, TryFromJsValue}; use crate as umbral_pre; use crate::{DefaultDeserialize, DefaultSerialize}; @@ -45,45 +45,6 @@ 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: 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: 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); @@ -473,7 +434,7 @@ pub fn decrypt_reencrypted( // 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 typed_vcfrags = try_from_js_array::(vcfrags).map_err(map_js_err)?; let backend_vcfrags = typed_vcfrags.into_iter().map(|vcfrag| vcfrag.0); umbral_pre::decrypt_reencrypted( &receiving_sk.0, @@ -499,8 +460,10 @@ impl KeyFrag { 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())?; + let typed_delegating_pk = + try_from_js_option::(delegating_pk).map_err(map_js_err)?; + let typed_receiving_pk = + try_from_js_option::(receiving_pk).map_err(map_js_err)?; self.0 .verify( From 4d70a64727ebaf8b65902ea64fe8fcd7fcce205a Mon Sep 17 00:00:00 2001 From: Bogdan Opanchuk Date: Mon, 3 Nov 2025 16:38:40 -0800 Subject: [PATCH 8/9] Pin generic-array to avoid deprecation warnings --- umbral-pre/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/umbral-pre/Cargo.toml b/umbral-pre/Cargo.toml index dae5410..63afbca 100644 --- a/umbral-pre/Cargo.toml +++ b/umbral-pre/Cargo.toml @@ -29,7 +29,7 @@ secrecy = { version = "0.10", default-features = false } # These packages are among the dependencies of the packages above. # Their versions should be updated when the main packages above are updated. -generic-array = { version = "0.14.6", features = ["zeroize"] } +generic-array = { version = "=0.14.7", features = ["zeroize"] } rand_core = { version = "0.6", default-features = false } getrandom = { version = "0.2", optional = true, default-features = false } subtle = { version = "2.4", default-features = false } From 3acb109c97324e76e74a7e60356e70c26e974f9b Mon Sep 17 00:00:00 2001 From: Bogdan Opanchuk Date: Mon, 3 Nov 2025 16:51:34 -0800 Subject: [PATCH 9/9] Fix Python stubs --- .github/workflows/umbral-pre.yml | 2 +- umbral-pre-python/stubtest-allowlist.txt | 1 - umbral-pre-python/umbral_pre/__init__.pyi | 8 ++++---- umbral-pre/src/bindings_python.rs | 4 ++-- 4 files changed, 7 insertions(+), 8 deletions(-) delete mode 100644 umbral-pre-python/stubtest-allowlist.txt diff --git a/.github/workflows/umbral-pre.yml b/.github/workflows/umbral-pre.yml index b63343b..f506491 100644 --- a/.github/workflows/umbral-pre.yml +++ b/.github/workflows/umbral-pre.yml @@ -124,7 +124,7 @@ jobs: run: pip install mypy ruff - name: Run mypy.stubtest - run: python -m mypy.stubtest umbral_pre --allowlist stubtest-allowlist.txt + run: python -m mypy.stubtest umbral_pre working-directory: umbral-pre-python - name: Run ruff diff --git a/umbral-pre-python/stubtest-allowlist.txt b/umbral-pre-python/stubtest-allowlist.txt deleted file mode 100644 index 024750a..0000000 --- a/umbral-pre-python/stubtest-allowlist.txt +++ /dev/null @@ -1 +0,0 @@ -umbral_pre.Parameters.__init__ diff --git a/umbral-pre-python/umbral_pre/__init__.pyi b/umbral-pre-python/umbral_pre/__init__.pyi index 5ee27d6..6e64f5f 100644 --- a/umbral-pre-python/umbral_pre/__init__.pyi +++ b/umbral-pre-python/umbral_pre/__init__.pyi @@ -64,7 +64,7 @@ class PublicKey: @final class Signer: - def __init__(self, secret_key: SecretKey): + def __new__(cls, secret_key: SecretKey): ... def sign(self, message: bytes) -> Signature: @@ -237,7 +237,7 @@ class CurvePoint: @final class Parameters: - def __init__(self) -> None: + def __new__(cls): ... u: CurvePoint @@ -246,8 +246,8 @@ class Parameters: @final class ReencryptionEvidence: - def __init__( - self, + def __new__( + cls, capsule: Capsule, vcfrag: VerifiedCapsuleFrag, verifying_pk: PublicKey, diff --git a/umbral-pre/src/bindings_python.rs b/umbral-pre/src/bindings_python.rs index b8e24d5..89ef1cf 100644 --- a/umbral-pre/src/bindings_python.rs +++ b/umbral-pre/src/bindings_python.rs @@ -207,8 +207,8 @@ pub struct Signer { #[pymethods] impl Signer { #[new] - pub fn new(sk: &SecretKey) -> Self { - umbral_pre::Signer::new(sk.backend.clone()).into() + pub fn new(secret_key: &SecretKey) -> Self { + umbral_pre::Signer::new(secret_key.backend.clone()).into() } pub fn sign(&self, message: &[u8]) -> Signature {