diff --git a/CHANGELOG.md b/CHANGELOG.md index eef96979..8cd27438 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `SecretKey`, `SecretKeyFactory` and `Signer` do not implement `PartialEq` anymore. Corresponding methods in the bindings were removed as well. ([#53]) - Bumped `k256` to `0.9` and `ecdsa` to `0.12.2`. ([#53]) - Bumped `pyo3` to `0.14`. ([#65]) +- Reduced the size of key material in `SecretKeyFactory` from 64 to 32 bytes. ([#64]) ### Added @@ -21,6 +22,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Feature `default-rng` (enabled by default). When disabled, the library can be compiled on targets not supported by `getrandom` (e.g., ARM), but only the functions taking an explicit RNG as a parameter will be available. ([#55]) - Added benchmarks for the main usage scenario and a feature `bench-internals` to expose some internals for benchmarking. ([#54]) - Added `VerifiedCapsuleFrag::from_verified_bytes()`. ([#63]) +- Added `SecretKeyFactory::secret_key_factory_by_label()`. ([#64]) +- Added `SecretKeyFactory::from_secure_randomness()` and `seed_size()`. ([#64]) ### Fixed @@ -35,6 +38,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [#56]: https://github.com/nucypher/rust-umbral/pull/56 [#60]: https://github.com/nucypher/rust-umbral/pull/60 [#63]: https://github.com/nucypher/rust-umbral/pull/63 +[#64]: https://github.com/nucypher/rust-umbral/pull/64 [#65]: https://github.com/nucypher/rust-umbral/pull/65 diff --git a/umbral-pre-python/docs/index.rst b/umbral-pre-python/docs/index.rst index 6068292a..cc8a8d98 100644 --- a/umbral-pre-python/docs/index.rst +++ b/umbral-pre-python/docs/index.rst @@ -52,10 +52,26 @@ API reference Generates a new random factory. - .. py:method:: secret_key_by_label(label: bytes) -> SecretKeyFactory + .. py:staticmethod:: seed_size() -> int + + Returns the seed size required by :py:meth:`~SecretKeyFactory.from_secure_randomness`. + + .. py:staticmethod:: from_secure_randomness(seed: bytes) -> SecretKeyFactory + + Creates a secret key factory using the given random bytes. + The length of the bytestring must be the one returned by :py:meth:`~SecretKeyFactory.seed_size`. + + **Warning:** make sure the given seed has been obtained + from a cryptographically secure source of randomness! + + .. py:method:: secret_key_by_label(label: bytes) -> SecretKey Generates a new :py:class:`SecretKey` using ``label`` as a seed. + .. py:method:: secret_key_factory_by_label(label: bytes) -> SecretKeyFactory + + Generates a new :py:class:`SecretKeyFactory` using ``label`` as a seed. + .. py:method:: to_secret_bytes() -> bytes Serializes the object into a bytestring. diff --git a/umbral-pre-python/src/lib.rs b/umbral-pre-python/src/lib.rs index 971a7a75..fca2c172 100644 --- a/umbral-pre-python/src/lib.rs +++ b/umbral-pre-python/src/lib.rs @@ -169,6 +169,20 @@ impl SecretKeyFactory { } } + #[staticmethod] + pub fn seed_size() -> usize { + umbral_pre::SecretKeyFactory::seed_size() + } + + #[staticmethod] + pub fn from_secure_randomness(seed: &[u8]) -> PyResult { + umbral_pre::SecretKeyFactory::from_secure_randomness(seed) + .map(|backend_sk| SecretKeyFactory { + backend: backend_sk, + }) + .map_err(|err| PyValueError::new_err(format!("{}", err))) + } + pub fn secret_key_by_label(&self, label: &[u8]) -> PyResult { self.backend .secret_key_by_label(label) @@ -178,6 +192,12 @@ impl SecretKeyFactory { .map_err(|err| PyValueError::new_err(format!("{}", err))) } + pub fn secret_key_factory_by_label(&self, label: &[u8]) -> Self { + Self { + backend: self.backend.secret_key_factory_by_label(label), + } + } + pub fn to_secret_bytes(&self) -> PyResult { to_secret_bytes(self) } diff --git a/umbral-pre-python/umbral_pre/__init__.pyi b/umbral-pre-python/umbral_pre/__init__.pyi index bb41591a..df3734be 100644 --- a/umbral-pre-python/umbral_pre/__init__.pyi +++ b/umbral-pre-python/umbral_pre/__init__.pyi @@ -28,9 +28,20 @@ class SecretKeyFactory: def random() -> SecretKeyFactory: ... + @staticmethod + def seed_size() -> int: + ... + + @staticmethod + def from_secure_randomness(seed: bytes) -> SecretKeyFactory: + ... + def secret_key_by_label(self, label: bytes) -> SecretKey: ... + def secret_key_factory_by_label(self, label: bytes) -> SecretKeyFactory: + ... + def to_secret_bytes(self) -> bytes: ... diff --git a/umbral-pre-wasm/src/lib.rs b/umbral-pre-wasm/src/lib.rs index cd4d1e9c..7486d564 100644 --- a/umbral-pre-wasm/src/lib.rs +++ b/umbral-pre-wasm/src/lib.rs @@ -71,6 +71,18 @@ impl SecretKeyFactory { Self(umbral_pre::SecretKeyFactory::random()) } + #[wasm_bindgen(js_name = seedSize)] + pub fn seed_size() -> usize { + umbral_pre::SecretKeyFactory::seed_size() + } + + #[wasm_bindgen(js_name = fromSecureRandomness)] + pub fn from_secure_randomness(seed: &[u8]) -> Result { + umbral_pre::SecretKeyFactory::from_secure_randomness(seed) + .map(Self) + .map_err(map_js_err) + } + #[wasm_bindgen(js_name = secretKeyByLabel)] pub fn secret_key_by_label(&self, label: &[u8]) -> Result { self.0 @@ -79,6 +91,11 @@ impl SecretKeyFactory { .map_err(map_js_err) } + #[wasm_bindgen(js_name = secretKeyFactoryByLabel)] + pub fn secret_key_factory_by_label(&self, label: &[u8]) -> Self { + Self(self.0.secret_key_factory_by_label(label)) + } + #[wasm_bindgen(js_name = toSecretBytes)] pub fn to_secret_bytes(&self) -> Box<[u8]> { self.0 diff --git a/umbral-pre/src/keys.rs b/umbral-pre/src/keys.rs index 15611a83..bf24e6e8 100644 --- a/umbral-pre/src/keys.rs +++ b/umbral-pre/src/keys.rs @@ -1,5 +1,6 @@ use alloc::boxed::Box; use alloc::vec::Vec; +use core::cmp::Ordering; use core::fmt; use digest::Digest; @@ -8,7 +9,7 @@ use elliptic_curve::{PublicKey as BackendPublicKey, SecretKey as BackendSecretKe use generic_array::GenericArray; use rand_core::{CryptoRng, RngCore}; use signature::{DigestVerifier, RandomizedDigestSigner, Signature as SignatureTrait}; -use typenum::{U32, U64}; +use typenum::{Unsigned, U32, U64}; #[cfg(feature = "default-rng")] use rand_core::OsRng; @@ -19,7 +20,7 @@ use crate::hashing::{BackendDigest, Hash, ScalarDigest}; use crate::secret_box::{CanBeZeroizedOnDrop, SecretBox}; use crate::traits::{ fmt_public, fmt_secret, ConstructionError, DeserializableFromArray, HasTypeName, - RepresentableAsArray, SerializableToArray, SerializableToSecretArray, + RepresentableAsArray, SerializableToArray, SerializableToSecretArray, SizeMismatchError, }; /// ECDSA signature object. @@ -265,7 +266,7 @@ impl fmt::Display for SecretKeyFactoryError { } } -type SecretKeyFactorySeedSize = U64; // the size of the seed material for key derivation +type SecretKeyFactorySeedSize = U32; // the size of the seed material for key derivation type SecretKeyFactoryDerivedSize = U64; // the size of the derived key (before hashing to scalar) type SecretKeyFactorySeed = GenericArray; @@ -288,7 +289,30 @@ impl SecretKeyFactory { Self::random_with_rng(&mut OsRng) } - /// Creates a `SecretKey` from the given label. + /// Returns the seed size required by + /// [`from_secure_randomness`](`SecretKeyFactory::from_secure_randomness`). + pub fn seed_size() -> usize { + SecretKeyFactorySeedSize::to_usize() + } + + /// Creates a secret key factory using the given random bytes. + /// + /// **Warning:** make sure the given seed has been obtained + /// from a cryptographically secure source of randomness! + pub fn from_secure_randomness(seed: &[u8]) -> Result { + let received_size = seed.len(); + let expected_size = Self::seed_size(); + match received_size.cmp(&expected_size) { + Ordering::Greater | Ordering::Less => { + Err(SizeMismatchError::new(received_size, expected_size)) + } + Ordering::Equal => Ok(Self(SecretBox::new(*SecretKeyFactorySeed::from_slice( + seed, + )))), + } + } + + /// Creates a `SecretKey` deterministically from the given label. pub fn secret_key_by_label(&self, label: &[u8]) -> Result { let prefix = b"KEY_DERIVATION/"; let info: Vec = prefix @@ -304,6 +328,19 @@ impl SecretKeyFactory { // TODO (#39) when we can hash to nonzero scalars, we can get rid of returning Result SecretKey::from_scalar(&scalar).ok_or(SecretKeyFactoryError::ZeroHash) } + + /// Creates a `SecretKeyFactory` deterministically from the given label. + pub fn secret_key_factory_by_label(&self, label: &[u8]) -> Self { + let prefix = b"FACTORY_DERIVATION/"; + let info: Vec = prefix + .iter() + .cloned() + .chain(label.iter().cloned()) + .collect(); + let derived_seed = + kdf::(&self.0, None, Some(&info)); + Self(derived_seed) + } } #[cfg(test)]