From 81b63a4b151270181a0507410a839ff7daace586 Mon Sep 17 00:00:00 2001 From: Bogdan Opanchuk Date: Fri, 26 Mar 2021 12:20:26 -0700 Subject: [PATCH 1/4] Return Result instead of Option, and raise corresponding exceptions in Python --- umbral-pre-python/src/lib.rs | 351 +++++++++++++++++------ umbral-pre-python/umbral_pre/__init__.py | 1 + umbral-pre-wasm/src/lib.rs | 1 + umbral-pre/src/capsule.rs | 64 +++-- umbral-pre/src/capsule_frag.rs | 10 +- umbral-pre/src/curve.rs | 17 +- umbral-pre/src/dem.rs | 40 ++- umbral-pre/src/hashing.rs | 4 +- umbral-pre/src/key_frag.rs | 14 +- umbral-pre/src/keys.rs | 35 ++- umbral-pre/src/lib.rs | 13 +- umbral-pre/src/pre.rs | 28 +- umbral-pre/src/traits.rs | 59 ++-- 13 files changed, 451 insertions(+), 186 deletions(-) diff --git a/umbral-pre-python/src/lib.rs b/umbral-pre-python/src/lib.rs index 531c456a..b0d26326 100644 --- a/umbral-pre-python/src/lib.rs +++ b/umbral-pre-python/src/lib.rs @@ -1,11 +1,63 @@ use pyo3::class::basic::CompareOp; -use pyo3::exceptions::PyTypeError; +use pyo3::create_exception; +use pyo3::exceptions::{PyException, PyTypeError, PyValueError}; use pyo3::prelude::*; +use pyo3::pyclass::PyClass; use pyo3::types::PyBytes; use pyo3::wrap_pyfunction; use pyo3::PyObjectProtocol; -use umbral_pre::SerializableToArray; +use umbral_pre::{ + DecryptionError, DeserializationError, EncryptionError, OpenReencryptedError, + ReencryptionError, SecretKeyFactoryError, SerializableToArray, +}; + +// A helper trait to generalize implementing various Python protocol functions for our types. +trait HasSerializableBackend { + fn as_backend(&self) -> &T; + fn from_backend(backend: T) -> Self; + fn name() -> &'static str; +} + +fn to_bytes, U: SerializableToArray>(obj: &T) -> PyResult { + let serialized = obj.as_backend().to_array(); + Python::with_gil(|py| -> PyResult { + Ok(PyBytes::new(py, serialized.as_slice()).into()) + }) +} + +fn from_bytes, U: SerializableToArray>(bytes: &[u8]) -> PyResult { + U::from_bytes(bytes) + .map(T::from_backend) + .map_err(|err| match err { + DeserializationError::ConstructionFailure => { + PyValueError::new_err(format!("Failed to deserialize a {} object", T::name())) + } + DeserializationError::TooManyBytes => { + PyValueError::new_err("The given bytestring is too long") + } + DeserializationError::NotEnoughBytes => { + PyValueError::new_err("The given bytestring is too short") + } + }) +} + +fn richcmp + PyClass + PartialEq, U>( + obj: &T, + other: PyRef, + op: CompareOp, +) -> PyResult { + match op { + CompareOp::Eq => Ok(obj == &*other), + CompareOp::Ne => Ok(obj != &*other), + _ => Err(PyTypeError::new_err(format!( + "{} objects are not ordered", + T::name() + ))), + } +} + +create_exception!(umbral, GenericError, PyException); #[pyclass(module = "umbral")] #[derive(PartialEq)] @@ -13,6 +65,20 @@ pub struct SecretKey { backend: umbral_pre::SecretKey, } +impl HasSerializableBackend for SecretKey { + fn as_backend(&self) -> &umbral_pre::SecretKey { + &self.backend + } + + fn from_backend(backend: umbral_pre::SecretKey) -> Self { + Self { backend } + } + + fn name() -> &'static str { + "SecretKey" + } +} + #[pymethods] impl SecretKey { #[staticmethod] @@ -22,36 +88,43 @@ impl SecretKey { } } - pub fn __bytes__(&self, py: Python) -> PyObject { - let serialized = self.backend.to_array(); - PyBytes::new(py, serialized.as_slice()).into() - } - #[staticmethod] - pub fn from_bytes(bytes: &[u8]) -> Option { - let backend_key = umbral_pre::SecretKey::from_bytes(bytes)?; - Some(Self { - backend: backend_key, - }) + pub fn from_bytes(bytes: &[u8]) -> PyResult { + from_bytes(bytes) } } #[pyproto] impl PyObjectProtocol for SecretKey { fn __richcmp__(&self, other: PyRef, op: CompareOp) -> PyResult { - match op { - CompareOp::Eq => Ok(self == &*other), - CompareOp::Ne => Ok(self != &*other), - _ => Err(PyTypeError::new_err("SecretKey objects are not ordered")), - } + richcmp(self, other, op) + } + + fn __bytes__(&self) -> PyResult { + to_bytes(self) } } #[pyclass(module = "umbral")] +#[derive(PartialEq)] pub struct SecretKeyFactory { backend: umbral_pre::SecretKeyFactory, } +impl HasSerializableBackend for SecretKeyFactory { + fn as_backend(&self) -> &umbral_pre::SecretKeyFactory { + &self.backend + } + + fn from_backend(backend: umbral_pre::SecretKeyFactory) -> Self { + Self { backend } + } + + fn name() -> &'static str { + "SecretKeyFactory" + } +} + #[pymethods] impl SecretKeyFactory { #[staticmethod] @@ -61,24 +134,34 @@ impl SecretKeyFactory { } } - pub fn secret_key_by_label(&self, label: &[u8]) -> Option { - let backend_sk = self.backend.secret_key_by_label(label)?; - Some(SecretKey { - backend: backend_sk, - }) + pub fn secret_key_by_label(&self, label: &[u8]) -> PyResult { + self.backend + .secret_key_by_label(label) + .map(|backend_sk| SecretKey { + backend: backend_sk, + }) + .map_err(|err| match err { + // Will be removed when #39 is fixed + SecretKeyFactoryError::ZeroHash => { + GenericError::new_err("Resulting secret key is zero") + } + }) } - pub fn __bytes__(&self, py: Python) -> PyObject { - let serialized = self.backend.to_array(); - PyBytes::new(py, serialized.as_slice()).into() + #[staticmethod] + pub fn from_bytes(bytes: &[u8]) -> PyResult { + from_bytes(bytes) } +} - #[staticmethod] - pub fn from_bytes(bytes: &[u8]) -> Option { - let backend_factory = umbral_pre::SecretKeyFactory::from_bytes(bytes)?; - Some(Self { - backend: backend_factory, - }) +#[pyproto] +impl PyObjectProtocol for SecretKeyFactory { + fn __richcmp__(&self, other: PyRef, op: CompareOp) -> PyResult { + richcmp(self, other, op) + } + + fn __bytes__(&self) -> PyResult { + to_bytes(self) } } @@ -88,6 +171,20 @@ pub struct PublicKey { backend: umbral_pre::PublicKey, } +impl HasSerializableBackend for PublicKey { + fn as_backend(&self) -> &umbral_pre::PublicKey { + &self.backend + } + + fn from_backend(backend: umbral_pre::PublicKey) -> Self { + Self { backend } + } + + fn name() -> &'static str { + "PublicKey" + } +} + #[pymethods] impl PublicKey { #[staticmethod] @@ -97,28 +194,20 @@ impl PublicKey { } } - pub fn __bytes__(&self, py: Python) -> PyObject { - let serialized = self.backend.to_array(); - PyBytes::new(py, serialized.as_slice()).into() - } - #[staticmethod] - pub fn from_bytes(bytes: &[u8]) -> Option { - let backend_pubkey = umbral_pre::PublicKey::from_bytes(bytes)?; - Some(Self { - backend: backend_pubkey, - }) + pub fn from_bytes(bytes: &[u8]) -> PyResult { + from_bytes(bytes) } } #[pyproto] impl PyObjectProtocol for PublicKey { fn __richcmp__(&self, other: PyRef, op: CompareOp) -> PyResult { - match op { - CompareOp::Eq => Ok(self == &*other), - CompareOp::Ne => Ok(self != &*other), - _ => Err(PyTypeError::new_err("PublicKey objects are not ordered")), - } + richcmp(self, other, op) + } + + fn __bytes__(&self) -> PyResult { + to_bytes(self) } } @@ -127,29 +216,64 @@ pub struct Capsule { backend: umbral_pre::Capsule, } +impl HasSerializableBackend for Capsule { + fn as_backend(&self) -> &umbral_pre::Capsule { + &self.backend + } + + fn from_backend(backend: umbral_pre::Capsule) -> Self { + Self { backend } + } + + fn name() -> &'static str { + "Capsule" + } +} + #[pymethods] impl Capsule { - pub fn __bytes__(&self, py: Python) -> PyObject { - let serialized = self.backend.to_array(); - PyBytes::new(py, serialized.as_slice()).into() + #[staticmethod] + pub fn from_bytes(bytes: &[u8]) -> PyResult { + from_bytes(bytes) } +} - #[staticmethod] - pub fn from_bytes(bytes: &[u8]) -> Option { - let backend_capsule = umbral_pre::Capsule::from_bytes(bytes)?; - Some(Self { - backend: backend_capsule, - }) +#[pyproto] +impl PyObjectProtocol for Capsule { + fn __bytes__(&self) -> PyResult { + to_bytes(self) } } #[pyfunction] -pub fn encrypt(py: Python, pk: &PublicKey, plaintext: &[u8]) -> (Capsule, PyObject) { - let (capsule, ciphertext) = umbral_pre::encrypt(&pk.backend, plaintext).unwrap(); - ( - Capsule { backend: capsule }, - PyBytes::new(py, &ciphertext).into(), - ) +pub fn encrypt(py: Python, pk: &PublicKey, plaintext: &[u8]) -> PyResult<(Capsule, PyObject)> { + umbral_pre::encrypt(&pk.backend, plaintext) + .map(|(backend_capsule, ciphertext)| { + ( + Capsule { + backend: backend_capsule, + }, + PyBytes::new(py, &ciphertext).into(), + ) + }) + .map_err(|err| match err { + EncryptionError::PlaintextTooLarge => { + GenericError::new_err("Plaintext is too large to encrypt") + } + }) +} + +fn map_decryption_err(err: DecryptionError) -> PyErr { + match err { + DecryptionError::CiphertextTooShort => { + PyValueError::new_err("The ciphertext must include the nonce") + } + DecryptionError::AuthenticationFailed => GenericError::new_err( + "Decryption of ciphertext failed: \ + either someone tampered with the ciphertext or \ + you are using an incorrect decryption key.", + ), + } } #[pyfunction] @@ -158,10 +282,10 @@ pub fn decrypt_original( sk: &SecretKey, capsule: &Capsule, ciphertext: &[u8], -) -> PyObject { - let plaintext = - umbral_pre::decrypt_original(&sk.backend, &capsule.backend, &ciphertext).unwrap(); - PyBytes::new(py, &plaintext).into() +) -> PyResult { + umbral_pre::decrypt_original(&sk.backend, &capsule.backend, &ciphertext) + .map(|plaintext| PyBytes::new(py, &plaintext).into()) + .map_err(map_decryption_err) } #[pyclass(module = "umbral")] @@ -169,6 +293,20 @@ pub struct KeyFrag { backend: umbral_pre::KeyFrag, } +impl HasSerializableBackend for KeyFrag { + fn as_backend(&self) -> &umbral_pre::KeyFrag { + &self.backend + } + + fn from_backend(backend: umbral_pre::KeyFrag) -> Self { + Self { backend } + } + + fn name() -> &'static str { + "KeyFrag" + } +} + #[pymethods] impl KeyFrag { pub fn verify( @@ -184,17 +322,16 @@ impl KeyFrag { ) } - pub fn __bytes__(&self, py: Python) -> PyObject { - let serialized = self.backend.to_array(); - PyBytes::new(py, serialized.as_slice()).into() + #[staticmethod] + pub fn from_bytes(bytes: &[u8]) -> PyResult { + from_bytes(bytes) } +} - #[staticmethod] - pub fn from_bytes(bytes: &[u8]) -> Option { - let backend_kfrag = umbral_pre::KeyFrag::from_bytes(bytes)?; - Some(Self { - backend: backend_kfrag, - }) +#[pyproto] +impl PyObjectProtocol for KeyFrag { + fn __bytes__(&self) -> PyResult { + to_bytes(self) } } @@ -232,6 +369,20 @@ pub struct CapsuleFrag { backend: umbral_pre::CapsuleFrag, } +impl HasSerializableBackend for CapsuleFrag { + fn as_backend(&self) -> &umbral_pre::CapsuleFrag { + &self.backend + } + + fn from_backend(backend: umbral_pre::CapsuleFrag) -> Self { + Self { backend } + } + + fn name() -> &'static str { + "CapsuleFrag" + } +} + #[pymethods] impl CapsuleFrag { pub fn verify( @@ -251,17 +402,16 @@ impl CapsuleFrag { ) } - pub fn __bytes__(&self, py: Python) -> PyObject { - let serialized = self.backend.to_array(); - PyBytes::new(py, serialized.as_slice()).into() + #[staticmethod] + pub fn from_bytes(bytes: &[u8]) -> PyResult { + from_bytes(bytes) } +} - #[staticmethod] - pub fn from_bytes(bytes: &[u8]) -> Option { - let backend_cfrag = umbral_pre::CapsuleFrag::from_bytes(bytes)?; - Some(Self { - backend: backend_cfrag, - }) +#[pyproto] +impl PyObjectProtocol for CapsuleFrag { + fn __bytes__(&self) -> PyResult { + to_bytes(self) } } @@ -281,31 +431,50 @@ pub fn decrypt_reencrypted( capsule: &Capsule, cfrags: Vec, ciphertext: &[u8], -) -> Option { +) -> PyResult { let backend_cfrags: Vec = cfrags.iter().cloned().map(|cfrag| cfrag.backend).collect(); - let res = umbral_pre::decrypt_reencrypted( + umbral_pre::decrypt_reencrypted( &decrypting_sk.backend, &delegating_pk.backend, &capsule.backend, &backend_cfrags, ciphertext, - ); - match res { - Some(plaintext) => Some(PyBytes::new(py, &plaintext).into()), - None => None, - } + ) + .map(|plaintext| PyBytes::new(py, &plaintext).into()) + .map_err(|err| match err { + ReencryptionError::OnOpen(err) => match err { + OpenReencryptedError::NoCapsuleFrags => { + PyValueError::new_err("Empty CapsuleFrag sequence") + } + OpenReencryptedError::MismatchedCapsuleFrags => { + PyValueError::new_err("CapsuleFrags are not pairwise consistent") + } + OpenReencryptedError::RepeatingCapsuleFrags => { + PyValueError::new_err("Some of the CapsuleFrags are repeated") + } + // Will be removed when #39 is fixed + OpenReencryptedError::ZeroHash => { + GenericError::new_err("An internally hashed value is zero") + } + OpenReencryptedError::ValidationFailed => { + GenericError::new_err("Internal validation failed") + } + }, + ReencryptionError::OnDecryption(err) => map_decryption_err(err), + }) } /// A Python module implemented in Rust. #[pymodule] -fn _umbral(_py: Python, m: &PyModule) -> PyResult<()> { +fn _umbral(py: Python, m: &PyModule) -> PyResult<()> { m.add_class::()?; m.add_class::()?; m.add_class::()?; m.add_class::()?; m.add_class::()?; m.add_class::()?; + m.add("GenericError", py.get_type::())?; m.add_function(wrap_pyfunction!(encrypt, m)?)?; m.add_function(wrap_pyfunction!(decrypt_original, m)?)?; m.add_function(wrap_pyfunction!(generate_kfrags, m)?)?; diff --git a/umbral-pre-python/umbral_pre/__init__.py b/umbral-pre-python/umbral_pre/__init__.py index 4b2a39c3..940210a6 100644 --- a/umbral-pre-python/umbral_pre/__init__.py +++ b/umbral-pre-python/umbral_pre/__init__.py @@ -5,6 +5,7 @@ Capsule, KeyFrag, CapsuleFrag, + GenericError, encrypt, decrypt_original, decrypt_reencrypted, diff --git a/umbral-pre-wasm/src/lib.rs b/umbral-pre-wasm/src/lib.rs index 4ead1a8b..f71d9041 100644 --- a/umbral-pre-wasm/src/lib.rs +++ b/umbral-pre-wasm/src/lib.rs @@ -114,6 +114,7 @@ impl CapsuleWithFrags { backend_cfrags.as_slice(), ciphertext, ) + .ok() } } diff --git a/umbral-pre/src/capsule.rs b/umbral-pre/src/capsule.rs index f508b049..4cd0a50a 100644 --- a/umbral-pre/src/capsule.rs +++ b/umbral-pre/src/capsule.rs @@ -3,7 +3,7 @@ use crate::curve::{CurvePoint, CurveScalar}; 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::traits::SerializableToArray; +use crate::traits::{DeserializationError, SerializableToArray}; use alloc::vec::Vec; @@ -11,6 +11,25 @@ use generic_array::sequence::Concat; use generic_array::GenericArray; use typenum::op; +/// Errors that can happen when opening a `Capsule` using reencrypted `CapsuleFrag` objects. +#[derive(Debug, PartialEq)] +pub enum OpenReencryptedError { + /// An empty capsule fragment list is given. + NoCapsuleFrags, + /// Capsule fragments are mismatched (originated from [`KeyFrag`](crate::KeyFrag) objects + /// generated by different [`generate_kfrags`](crate::generate_kfrags) calls). + MismatchedCapsuleFrags, + /// Some of the given capsule fragments are repeated. + RepeatingCapsuleFrags, + /// An internally hashed value is zero. + /// See [rust-umbral#39](https://github.com/nucypher/rust-umbral/issues/39). + ZeroHash, + /// Internal validation of the result has failed. + /// Can be caused by an incorrect (possibly modified) capsule + /// or some of the capsule fragments. + ValidationFailed, +} + /// Encapsulated symmetric key used to encrypt the plaintext. #[derive(Clone, Copy, Debug, PartialEq)] pub struct Capsule { @@ -34,11 +53,12 @@ impl SerializableToArray for Capsule { .concat(self.signature.to_array()) } - fn from_array(arr: &GenericArray) -> Option { + fn from_array(arr: &GenericArray) -> Result { let (point_e, rest) = CurvePoint::take(*arr)?; let (point_v, rest) = CurvePoint::take(rest)?; let signature = CurveScalar::take_last(rest)?; Self::new_verified(point_e, point_v, signature) + .ok_or(DeserializationError::ConstructionFailure) } } @@ -104,15 +124,15 @@ impl Capsule { receiving_sk: &SecretKey, delegating_pk: &PublicKey, cfrags: &[CapsuleFrag], - ) -> Option { + ) -> Result { if cfrags.is_empty() { - return None; + return Err(OpenReencryptedError::NoCapsuleFrags); } let precursor = cfrags[0].precursor; if !cfrags.iter().all(|cfrag| cfrag.precursor == precursor) { - return None; + return Err(OpenReencryptedError::MismatchedCapsuleFrags); } let pub_key = PublicKey::from_secret_key(receiving_sk).to_point(); @@ -128,9 +148,10 @@ impl Capsule { let mut e_prime = CurvePoint::identity(); let mut v_prime = CurvePoint::identity(); for (i, cfrag) in (&cfrags).iter().enumerate() { - // There is a minuscule probability that two elements of `lc` are equal, + // There is a minuscule probability that coefficients for two different frags are equal, // in which case we'd rather fail gracefully. - let lambda_i = lambda_coeff(&lc, i)?; + let lambda_i = + lambda_coeff(&lc, i).ok_or(OpenReencryptedError::RepeatingCapsuleFrags)?; e_prime = &e_prime + &(&cfrag.point_e1 * &lambda_i); v_prime = &v_prime + &(&cfrag.point_v1 * &lambda_i); } @@ -149,14 +170,14 @@ impl Capsule { // Technically, it is supposed to be non-zero by the choice of `precursor`, // but if is was somehow replaced by an incorrect value, // we'd rather fail gracefully than panic. - let inv_d = inv_d_opt?; + let inv_d = inv_d_opt.ok_or(OpenReencryptedError::ZeroHash)?; if &orig_pub_key * &(&s * &inv_d) != &(&e_prime * &h) + &v_prime { - return None; + return Err(OpenReencryptedError::ValidationFailed); } let shared_key = &(&e_prime + &v_prime) * &d; - Some(shared_key) + Ok(shared_key) } } @@ -177,7 +198,7 @@ mod tests { use alloc::vec::Vec; - use super::Capsule; + use super::{Capsule, OpenReencryptedError}; use crate::{ encrypt, generate_kfrags, reencrypt, CapsuleFrag, PublicKey, SecretKey, SerializableToArray, }; @@ -220,9 +241,10 @@ mod tests { assert_eq!(key_seed, key_seed_reenc); // Empty cfrag vector - assert!(capsule - .open_reencrypted(&receiving_sk, &delegating_pk, &[]) - .is_none()); + assert_eq!( + capsule.open_reencrypted(&receiving_sk, &delegating_pk, &[]), + Err(OpenReencryptedError::NoCapsuleFrags) + ); // Mismatched cfrags - each `generate_kfrags()` uses new randoms. let kfrags2 = generate_kfrags(&delegating_sk, &receiving_pk, &signing_sk, 2, 3, true, true); @@ -237,14 +259,16 @@ mod tests { .cloned() .chain(cfrags2[1..2].iter().cloned()) .collect(); - assert!(capsule - .open_reencrypted(&receiving_sk, &delegating_pk, &mismatched_cfrags) - .is_none()); + assert_eq!( + capsule.open_reencrypted(&receiving_sk, &delegating_pk, &mismatched_cfrags), + Err(OpenReencryptedError::MismatchedCapsuleFrags) + ); // Mismatched capsule let (capsule2, _key_seed) = Capsule::from_public_key(&delegating_pk); - assert!(capsule2 - .open_reencrypted(&receiving_sk, &delegating_pk, &cfrags) - .is_none()); + assert_eq!( + capsule2.open_reencrypted(&receiving_sk, &delegating_pk, &cfrags), + Err(OpenReencryptedError::ValidationFailed) + ); } } diff --git a/umbral-pre/src/capsule_frag.rs b/umbral-pre/src/capsule_frag.rs index 92189054..3561bd73 100644 --- a/umbral-pre/src/capsule_frag.rs +++ b/umbral-pre/src/capsule_frag.rs @@ -3,7 +3,7 @@ use crate::curve::{CurvePoint, CurveScalar}; use crate::hashing_ds::{hash_to_cfrag_signature, hash_to_cfrag_verification}; use crate::key_frag::{KeyFrag, KeyFragID}; use crate::keys::{PublicKey, Signature}; -use crate::traits::SerializableToArray; +use crate::traits::{DeserializationError, SerializableToArray}; use generic_array::sequence::Concat; use generic_array::GenericArray; @@ -38,14 +38,14 @@ impl SerializableToArray for CapsuleFragProof { .concat(self.kfrag_signature.to_array()) } - fn from_array(arr: &GenericArray) -> Option { + fn from_array(arr: &GenericArray) -> Result { let (point_e2, rest) = CurvePoint::take(*arr)?; let (point_v2, rest) = CurvePoint::take(rest)?; let (kfrag_commitment, rest) = CurvePoint::take(rest)?; let (kfrag_pok, rest) = CurvePoint::take(rest)?; let (signature, rest) = CurveScalar::take(rest)?; let kfrag_signature = Signature::take_last(rest)?; - Some(Self { + Ok(Self { point_e2, point_v2, kfrag_commitment, @@ -126,13 +126,13 @@ impl SerializableToArray for CapsuleFrag { .concat(self.proof.to_array()) } - fn from_array(arr: &GenericArray) -> Option { + fn from_array(arr: &GenericArray) -> Result { let (point_e1, rest) = CurvePoint::take(*arr)?; let (point_v1, rest) = CurvePoint::take(rest)?; let (kfrag_id, rest) = KeyFragID::take(rest)?; let (precursor, rest) = CurvePoint::take(rest)?; let proof = CapsuleFragProof::take_last(rest)?; - Some(Self { + Ok(Self { point_e1, point_v1, kfrag_id, diff --git a/umbral-pre/src/curve.rs b/umbral-pre/src/curve.rs index 5a978716..d416b07b 100644 --- a/umbral-pre/src/curve.rs +++ b/umbral-pre/src/curve.rs @@ -14,7 +14,7 @@ use k256::Secp256k1; use rand_core::OsRng; use subtle::CtOption; -use crate::traits::SerializableToArray; +use crate::traits::{DeserializationError, SerializableToArray}; pub(crate) type CurveType = Secp256k1; @@ -80,8 +80,10 @@ impl SerializableToArray for CurveScalar { self.0.to_bytes() } - fn from_array(arr: &GenericArray) -> Option { - Scalar::::from_repr(*arr).map(Self) + fn from_array(arr: &GenericArray) -> Result { + Scalar::::from_repr(*arr) + .map(Self) + .ok_or(DeserializationError::ConstructionFailure) } } @@ -160,9 +162,12 @@ impl SerializableToArray for CurvePoint { ) } - fn from_array(arr: &GenericArray) -> Option { - let ep = EncodedPoint::::from_bytes(arr.as_slice()).ok()?; + fn from_array(arr: &GenericArray) -> Result { + let ep = EncodedPoint::::from_bytes(arr.as_slice()) + .or(Err(DeserializationError::ConstructionFailure))?; let cp_opt: Option = BackendPoint::from_encoded_point(&ep); - cp_opt.map(Self) + cp_opt + .map(Self) + .ok_or(DeserializationError::ConstructionFailure) } } diff --git a/umbral-pre/src/dem.rs b/umbral-pre/src/dem.rs index 0cb1c923..e1a74607 100644 --- a/umbral-pre/src/dem.rs +++ b/umbral-pre/src/dem.rs @@ -10,6 +10,26 @@ use rand_core::RngCore; use sha2::Sha256; use typenum::Unsigned; +/// Errors that can happen during symmetric encryption. +#[derive(Debug, PartialEq)] +pub enum EncryptionError { + /// Given plaintext is too large for the backend to handle. + PlaintextTooLarge, +} + +/// Errors that can happend during symmetric decryption. +#[derive(Debug, PartialEq)] +pub enum DecryptionError { + /// Ciphertext (which should be prepended by the nonce) is shorter than the nonce length. + CiphertextTooShort, + /// The ciphertext and the attached authentication data are inconsistent. + /// This can happen if: + /// - an incorrect key is used, + /// - the ciphertext is modified or cut short, + /// - an incorrect authentication data is provided on decryption. + AuthenticationFailed, +} + pub(crate) fn kdf>( seed: &[u8], salt: Option<&[u8]>, @@ -42,7 +62,11 @@ impl DEM { Self { cipher } } - pub fn encrypt(&self, data: &[u8], authenticated_data: &[u8]) -> Option> { + pub fn encrypt( + &self, + data: &[u8], + authenticated_data: &[u8], + ) -> Result, EncryptionError> { let mut nonce = GenericArray::::default(); OsRng.fill_bytes(&mut nonce); let nonce = XNonce::from_slice(&nonce); @@ -52,23 +76,27 @@ impl DEM { }; let mut result = nonce.to_vec(); - let enc_data = self.cipher.encrypt(nonce, payload).ok()?; + let enc_data = self + .cipher + .encrypt(nonce, payload) + .or(Err(EncryptionError::PlaintextTooLarge))?; + // Somewhat inefficient, but it doesn't seem that you can pass // a mutable view of a vector to encrypt_in_place(). result.extend(enc_data); - Some(result.into_boxed_slice()) + Ok(result.into_boxed_slice()) } pub fn decrypt( &self, ciphertext: impl AsRef<[u8]>, authenticated_data: &[u8], - ) -> Option> { + ) -> Result, DecryptionError> { let nonce_size = ::to_usize(); let buf_size = ciphertext.as_ref().len(); if buf_size < nonce_size { - return None; + return Err(DecryptionError::CiphertextTooShort); } let nonce = XNonce::from_slice(&ciphertext.as_ref()[..nonce_size]); @@ -78,8 +106,8 @@ impl DEM { }; self.cipher .decrypt(nonce, payload) - .ok() .map(|pt| pt.into_boxed_slice()) + .or(Err(DecryptionError::AuthenticationFailed)) } } diff --git a/umbral-pre/src/hashing.rs b/umbral-pre/src/hashing.rs index 33558691..077d0c72 100644 --- a/umbral-pre/src/hashing.rs +++ b/umbral-pre/src/hashing.rs @@ -40,8 +40,8 @@ pub fn unsafe_hash_to_point(dst: &[u8], data: &[u8]) -> Option { let maybe_point_bytes = sign_prefix.concat(result); let maybe_point = CurvePoint::from_bytes(&maybe_point_bytes); - if maybe_point.is_some() { - return maybe_point; + if maybe_point.is_ok() { + return maybe_point.ok(); } i += 1 diff --git a/umbral-pre/src/key_frag.rs b/umbral-pre/src/key_frag.rs index 645c291a..3d80fca6 100644 --- a/umbral-pre/src/key_frag.rs +++ b/umbral-pre/src/key_frag.rs @@ -2,7 +2,7 @@ use crate::curve::{CurvePoint, CurveScalar}; use crate::hashing_ds::{hash_to_cfrag_signature, hash_to_polynomial_arg, hash_to_shared_secret}; use crate::keys::{PublicKey, SecretKey, Signature}; use crate::params::Parameters; -use crate::traits::SerializableToArray; +use crate::traits::{DeserializationError, SerializableToArray}; use alloc::boxed::Box; use alloc::vec::Vec; @@ -38,8 +38,8 @@ impl SerializableToArray for KeyFragID { self.0 } - fn from_array(arr: &GenericArray) -> Option { - Some(Self(*arr)) + fn from_array(arr: &GenericArray) -> Result { + Ok(Self(*arr)) } } @@ -69,13 +69,13 @@ impl SerializableToArray for KeyFragProof { .concat(self.receiving_key_signed.to_array()) } - fn from_array(arr: &GenericArray) -> Option { + fn from_array(arr: &GenericArray) -> Result { let (commitment, rest) = CurvePoint::take(*arr)?; let (signature_for_proxy, rest) = Signature::take(rest)?; let (signature_for_receiver, rest) = Signature::take(rest)?; let (delegating_key_signed, rest) = bool::take(rest)?; let receiving_key_signed = bool::take_last(rest)?; - Some(Self { + Ok(Self { commitment, signature_for_proxy, signature_for_receiver, @@ -161,13 +161,13 @@ impl SerializableToArray for KeyFrag { .concat(self.proof.to_array()) } - fn from_array(arr: &GenericArray) -> Option { + fn from_array(arr: &GenericArray) -> Result { let params = Parameters::new(); let (id, rest) = KeyFragID::take(*arr)?; let (key, rest) = CurveScalar::take(rest)?; let (precursor, rest) = CurvePoint::take(rest)?; let proof = KeyFragProof::take_last(rest)?; - Some(Self { + Ok(Self { params, id, key, diff --git a/umbral-pre/src/keys.rs b/umbral-pre/src/keys.rs index 8db96a47..88c9565e 100644 --- a/umbral-pre/src/keys.rs +++ b/umbral-pre/src/keys.rs @@ -11,7 +11,7 @@ use typenum::{U32, U64}; use crate::curve::{BackendNonZeroScalar, CurvePoint, CurveScalar, CurveType}; use crate::dem::kdf; use crate::hashing::ScalarDigest; -use crate::traits::SerializableToArray; +use crate::traits::{DeserializationError, SerializableToArray}; #[derive(Clone, Debug, PartialEq)] pub struct Signature(BackendSignature); @@ -23,12 +23,12 @@ impl SerializableToArray for Signature { *GenericArray::::from_slice(self.0.as_bytes()) } - fn from_array(arr: &GenericArray) -> Option { + fn from_array(arr: &GenericArray) -> Result { // Note that it will not normalize `s` automatically, // and if it is not normalized, verification will fail. BackendSignature::::from_bytes(arr.as_slice()) - .ok() .map(Self) + .or(Err(DeserializationError::ConstructionFailure)) } } @@ -83,10 +83,10 @@ impl SerializableToArray for SecretKey { self.0.to_bytes() } - fn from_array(arr: &GenericArray) -> Option { + fn from_array(arr: &GenericArray) -> Result { BackendSecretKey::::from_bytes(arr.as_slice()) - .ok() .map(Self) + .or(Err(DeserializationError::ConstructionFailure)) } } @@ -123,13 +123,22 @@ impl SerializableToArray for PublicKey { self.to_point().to_array() } - fn from_array(arr: &GenericArray) -> Option { + fn from_array(arr: &GenericArray) -> Result { let cp = CurvePoint::from_array(&arr)?; - let backend_pk = BackendPublicKey::::from_affine(cp.to_affine()).ok()?; - Some(Self(backend_pk)) + let backend_pk = BackendPublicKey::::from_affine(cp.to_affine()) + .or(Err(DeserializationError::ConstructionFailure))?; + Ok(Self(backend_pk)) } } +/// Errors that can happen when using a [`SecretKeyFactory`]. +#[derive(Debug, PartialEq)] +pub enum SecretKeyFactoryError { + /// An internally hashed value is zero. + /// See [rust-umbral#39](https://github.com/nucypher/rust-umbral/issues/39). + ZeroHash, +} + type SecretKeyFactorySeedSize = U64; // the size of the seed material for key derivation type SecretKeyFactoryDerivedSize = U64; // the size of the derived key (before hashing to scalar) @@ -147,7 +156,7 @@ impl SecretKeyFactory { } /// Creates a `SecretKey` from the given label. - pub fn secret_key_by_label(&self, label: &[u8]) -> Option { + pub fn secret_key_by_label(&self, label: &[u8]) -> Result { let prefix = b"KEY_DERIVATION/"; let info: Vec = prefix .iter() @@ -158,8 +167,8 @@ impl SecretKeyFactory { let scalar = ScalarDigest::new_with_dst(&info) .chain_bytes(&key) .finalize(); - // TODO (#39) when we can hash to nonzero scalars, we can get rid of returning Option - SecretKey::from_scalar(&scalar) + // TODO (#39) when we can hash to nonzero scalars, we can get rid of returning Result + SecretKey::from_scalar(&scalar).ok_or(SecretKeyFactoryError::ZeroHash) } } @@ -171,8 +180,8 @@ impl SerializableToArray for SecretKeyFactory { self.0 } - fn from_array(arr: &GenericArray) -> Option { - Some(Self(*arr)) + fn from_array(arr: &GenericArray) -> Result { + Ok(Self(*arr)) } } diff --git a/umbral-pre/src/lib.rs b/umbral-pre/src/lib.rs index a7ac86a1..1691c5d1 100644 --- a/umbral-pre/src/lib.rs +++ b/umbral-pre/src/lib.rs @@ -104,11 +104,10 @@ mod params; mod pre; mod traits; -pub use key_frag::generate_kfrags; -pub use pre::{decrypt_original, decrypt_reencrypted, encrypt, reencrypt}; - -pub use capsule::Capsule; +pub use capsule::{Capsule, OpenReencryptedError}; pub use capsule_frag::CapsuleFrag; -pub use key_frag::KeyFrag; -pub use keys::{PublicKey, SecretKey, SecretKeyFactory}; -pub use traits::SerializableToArray; +pub use dem::{DecryptionError, EncryptionError}; +pub use key_frag::{generate_kfrags, KeyFrag}; +pub use keys::{PublicKey, SecretKey, SecretKeyFactory, SecretKeyFactoryError}; +pub use pre::{decrypt_original, decrypt_reencrypted, encrypt, reencrypt, ReencryptionError}; +pub use traits::{DeserializationError, SerializableToArray}; diff --git a/umbral-pre/src/pre.rs b/umbral-pre/src/pre.rs index 271a36fc..37baac3b 100644 --- a/umbral-pre/src/pre.rs +++ b/umbral-pre/src/pre.rs @@ -1,23 +1,32 @@ //! The high-level functional reencryption API. -use crate::capsule::Capsule; +use crate::capsule::{Capsule, OpenReencryptedError}; use crate::capsule_frag::CapsuleFrag; -use crate::dem::DEM; +use crate::dem::{DecryptionError, EncryptionError, DEM}; use crate::key_frag::KeyFrag; use crate::keys::{PublicKey, SecretKey}; use crate::traits::SerializableToArray; use alloc::boxed::Box; +/// Errors that can happen when decrypting a reencrypted ciphertext. +#[derive(Debug, PartialEq)] +pub enum ReencryptionError { + /// An error when opening a capsule. See [`OpenReencryptedError`] for the options. + OnOpen(OpenReencryptedError), + /// An error when decrypting the ciphertext. See [`DecryptionError`] for the options. + OnDecryption(DecryptionError), +} + /// Encrypts the given plaintext message using a DEM scheme, /// and encapsulates the key for later reencryption. /// Returns the KEM [`Capsule`] and the ciphertext. -pub fn encrypt(pk: &PublicKey, plaintext: &[u8]) -> Option<(Capsule, Box<[u8]>)> { +pub fn encrypt(pk: &PublicKey, plaintext: &[u8]) -> Result<(Capsule, Box<[u8]>), EncryptionError> { let (capsule, key_seed) = Capsule::from_public_key(pk); let dem = DEM::new(&key_seed.to_array()); let capsule_bytes = capsule.to_array(); - let ciphertext = dem.encrypt(plaintext, &capsule_bytes)?; - Some((capsule, ciphertext)) + dem.encrypt(plaintext, &capsule_bytes) + .map(|ciphertext| (capsule, ciphertext)) } /// Attempts to decrypt the ciphertext using the original encryptor's @@ -26,7 +35,7 @@ pub fn decrypt_original( decrypting_sk: &SecretKey, capsule: &Capsule, ciphertext: impl AsRef<[u8]>, -) -> Option> { +) -> Result, DecryptionError> { let key_seed = capsule.open_original(decrypting_sk); let dem = DEM::new(&key_seed.to_array()); dem.decrypt(ciphertext, &capsule.to_array()) @@ -58,10 +67,13 @@ pub fn decrypt_reencrypted( capsule: &Capsule, cfrags: &[CapsuleFrag], ciphertext: impl AsRef<[u8]>, -) -> Option> { - let key_seed = capsule.open_reencrypted(decrypting_sk, delegating_pk, cfrags)?; +) -> Result, ReencryptionError> { + let key_seed = capsule + .open_reencrypted(decrypting_sk, delegating_pk, cfrags) + .map_err(ReencryptionError::OnOpen)?; let dem = DEM::new(&key_seed.to_array()); dem.decrypt(&ciphertext, &capsule.to_array()) + .map_err(ReencryptionError::OnDecryption) } #[cfg(test)] diff --git a/umbral-pre/src/traits.rs b/umbral-pre/src/traits.rs index 338b9e10..ab786610 100644 --- a/umbral-pre/src/traits.rs +++ b/umbral-pre/src/traits.rs @@ -1,8 +1,20 @@ +use core::cmp::Ordering; use core::ops::Sub; use generic_array::sequence::Split; use generic_array::{ArrayLength, GenericArray}; use typenum::{Diff, Unsigned, U1}; +/// Errors that can happen during object deserialization. +#[derive(Debug, PartialEq)] +pub enum DeserializationError { + /// Failed to construct the object from a given bytestring (with the correct length). + ConstructionFailure, + /// The given bytestring is too short. + NotEnoughBytes, + /// The given bytestring is too long. + TooManyBytes, +} + /// A trait denoting that the object can be serialized to/from an array of bytes /// with size known at compile time. pub trait SerializableToArray @@ -20,16 +32,19 @@ where fn to_array(&self) -> GenericArray; /// Attempts to produce the object back from the serialized form. - fn from_array(arr: &GenericArray) -> Option; + fn from_array(arr: &GenericArray) -> Result; /// Attempts to produce the object back from a dynamically sized byte array, /// checking that its length is correct. - fn from_bytes(bytes: impl AsRef<[u8]>) -> Option { + fn from_bytes(bytes: impl AsRef<[u8]>) -> Result { let bytes_slice = bytes.as_ref(); - if bytes_slice.len() != Self::Size::to_usize() { - return None; + match bytes_slice.len().cmp(&Self::Size::to_usize()) { + Ordering::Greater => Err(DeserializationError::TooManyBytes), + Ordering::Less => Err(DeserializationError::NotEnoughBytes), + Ordering::Equal => { + Self::from_array(GenericArray::::from_slice(bytes_slice)) + } } - Self::from_array(GenericArray::::from_slice(bytes_slice)) } /// Used to implement [`from_array()`](`Self::from_array()`) for structs whose fields @@ -39,7 +54,9 @@ where /// [`from_array()`](`Self::from_array()`), /// and if it succeeds, returns the resulting object and the rest of the array. #[allow(clippy::type_complexity)] - fn take(arr: GenericArray) -> Option<(Self, GenericArray>)> + fn take( + arr: GenericArray, + ) -> Result<(Self, GenericArray>), DeserializationError> where U: ArrayLength + Sub, Diff: ArrayLength, @@ -51,7 +68,7 @@ where /// A variant of [`take()`](`Self::take()`) to be called for the last field of the struct, /// where no remainder of the array is expected. - fn take_last(arr: GenericArray) -> Option { + fn take_last(arr: GenericArray) -> Result { Self::from_array(&arr) } } @@ -63,12 +80,12 @@ impl SerializableToArray for bool { GenericArray::::from([*self as u8]) } - fn from_array(arr: &GenericArray) -> Option { + fn from_array(arr: &GenericArray) -> Result { let bytes_slice = arr.as_slice(); match bytes_slice[0] { - 0u8 => Some(false), - 1u8 => Some(true), - _ => None, + 0u8 => Ok(false), + 1u8 => Ok(true), + _ => Err(DeserializationError::ConstructionFailure), } } } @@ -80,7 +97,7 @@ mod tests { use generic_array::GenericArray; use typenum::{op, U1, U2}; - use super::SerializableToArray; + use super::{DeserializationError, SerializableToArray}; impl SerializableToArray for u8 { type Size = U1; @@ -89,8 +106,8 @@ mod tests { GenericArray::::from([*self]) } - fn from_array(arr: &GenericArray) -> Option { - Some(arr.as_slice()[0]) + fn from_array(arr: &GenericArray) -> Result { + Ok(arr.as_slice()[0]) } } @@ -101,10 +118,10 @@ mod tests { GenericArray::::from([(self >> 8) as u8, *self as u8]) } - fn from_array(arr: &GenericArray) -> Option { + fn from_array(arr: &GenericArray) -> Result { let b1 = arr.as_slice()[0]; let b2 = arr.as_slice()[1]; - Some(((b1 as u16) << 8) + (b2 as u16)) + Ok(((b1 as u16) << 8) + (b2 as u16)) } } @@ -131,12 +148,12 @@ mod tests { .concat(self.f4.to_array()) } - fn from_array(arr: &GenericArray) -> Option { + fn from_array(arr: &GenericArray) -> Result { let (f1, rest) = u16::take(*arr)?; let (f2, rest) = u8::take(rest)?; let (f3, rest) = u16::take(rest)?; let f4 = bool::take_last(rest)?; - Some(Self { f1, f2, f3, f4 }) + Ok(Self { f1, f2, f3, f4 }) } } @@ -165,14 +182,14 @@ mod tests { // invalid value for `f4` (`bool` must be either 0 or 1) let s_arr: [u8; 6] = [0x00, 0x01, 0x02, 0x00, 0x03, 0x02]; let s = SomeStruct::from_bytes(&s_arr); - assert!(s.is_none()) + assert_eq!(s, Err(DeserializationError::ConstructionFailure)) } #[test] fn test_invalid_length() { - // invalid value for `f4` (`bool` must be either 0 or 1) + // An excessive byte at the end let s_arr: [u8; 7] = [0x00, 0x01, 0x02, 0x00, 0x03, 0x01, 0x00]; let s = SomeStruct::from_bytes(&s_arr); - assert!(s.is_none()) + assert_eq!(s, Err(DeserializationError::TooManyBytes)) } } From 38e932e1688d3ff3a62555b27fdf380554d079cc Mon Sep 17 00:00:00 2001 From: Bogdan Opanchuk Date: Sat, 27 Mar 2021 23:59:29 -0700 Subject: [PATCH 2/4] Add __hash__(), __str__() and __eq()__ where appropriate --- umbral-pre-python/Cargo.toml | 1 + umbral-pre-python/src/lib.rs | 79 +++++++++++++++++++++++++++++++++++- 2 files changed, 78 insertions(+), 2 deletions(-) diff --git a/umbral-pre-python/Cargo.toml b/umbral-pre-python/Cargo.toml index 8b9f21ff..3b330329 100644 --- a/umbral-pre-python/Cargo.toml +++ b/umbral-pre-python/Cargo.toml @@ -11,3 +11,4 @@ crate-type = ["cdylib"] pyo3 = { version = "0.13", features = ["extension-module"] } umbral-pre = { path = "../umbral-pre" } generic-array = "0.14" +hex = "0.4" diff --git a/umbral-pre-python/src/lib.rs b/umbral-pre-python/src/lib.rs index b0d26326..aaa01780 100644 --- a/umbral-pre-python/src/lib.rs +++ b/umbral-pre-python/src/lib.rs @@ -3,7 +3,7 @@ use pyo3::create_exception; use pyo3::exceptions::{PyException, PyTypeError, PyValueError}; use pyo3::prelude::*; use pyo3::pyclass::PyClass; -use pyo3::types::PyBytes; +use pyo3::types::{PyBytes, PyUnicode}; use pyo3::wrap_pyfunction; use pyo3::PyObjectProtocol; @@ -42,6 +42,27 @@ fn from_bytes, U: SerializableToArray>(bytes: &[u8] }) } +fn hash, U: SerializableToArray>(obj: &T) -> PyResult { + let serialized = obj.as_backend().to_array(); + + // call `hash((class_name, bytes(obj)))` + Python::with_gil(|py| { + let builtins = PyModule::import(py, "builtins")?; + let arg1 = PyUnicode::new(py, T::name()); + let arg2: PyObject = PyBytes::new(py, serialized.as_slice()).into(); + builtins.getattr("hash")?.call1(((arg1, arg2),))?.extract() + }) +} + +// For some reason this lint is not recognized in Rust 1.46 (the one in CI) +// remove when CI is updated to a newer Rust version. +#[allow(clippy::unknown_clippy_lints)] +#[allow(clippy::unnecessary_wraps)] // Don't want to wrap it in Ok() on every call +fn hexstr, U: SerializableToArray>(obj: &T) -> PyResult { + let hex_str = hex::encode(obj.as_backend().to_array().as_slice()); + Ok(format!("{}:{}", T::name(), &hex_str[0..16])) +} + fn richcmp + PyClass + PartialEq, U>( obj: &T, other: PyRef, @@ -103,6 +124,10 @@ impl PyObjectProtocol for SecretKey { fn __bytes__(&self) -> PyResult { to_bytes(self) } + + fn __str__(&self) -> PyResult { + Ok(format!("{}:...", Self::name())) + } } #[pyclass(module = "umbral")] @@ -163,6 +188,10 @@ impl PyObjectProtocol for SecretKeyFactory { fn __bytes__(&self) -> PyResult { to_bytes(self) } + + fn __str__(&self) -> PyResult { + Ok(format!("{}:...", Self::name())) + } } #[pyclass(module = "umbral")] @@ -209,9 +238,18 @@ impl PyObjectProtocol for PublicKey { fn __bytes__(&self) -> PyResult { to_bytes(self) } + + fn __hash__(&self) -> PyResult { + hash(self) + } + + fn __str__(&self) -> PyResult { + hexstr(self) + } } #[pyclass(module = "umbral")] +#[derive(PartialEq)] pub struct Capsule { backend: umbral_pre::Capsule, } @@ -240,9 +278,21 @@ impl Capsule { #[pyproto] impl PyObjectProtocol for Capsule { + fn __richcmp__(&self, other: PyRef, op: CompareOp) -> PyResult { + richcmp(self, other, op) + } + fn __bytes__(&self) -> PyResult { to_bytes(self) } + + fn __hash__(&self) -> PyResult { + hash(self) + } + + fn __str__(&self) -> PyResult { + hexstr(self) + } } #[pyfunction] @@ -289,6 +339,7 @@ pub fn decrypt_original( } #[pyclass(module = "umbral")] +#[derive(PartialEq)] pub struct KeyFrag { backend: umbral_pre::KeyFrag, } @@ -330,9 +381,21 @@ impl KeyFrag { #[pyproto] impl PyObjectProtocol for KeyFrag { + fn __richcmp__(&self, other: PyRef, op: CompareOp) -> PyResult { + richcmp(self, other, op) + } + fn __bytes__(&self) -> PyResult { to_bytes(self) } + + fn __hash__(&self) -> PyResult { + hash(self) + } + + fn __str__(&self) -> PyResult { + hexstr(self) + } } #[allow(clippy::too_many_arguments)] @@ -364,7 +427,7 @@ pub fn generate_kfrags( } #[pyclass(module = "umbral")] -#[derive(Clone)] +#[derive(Clone, PartialEq)] pub struct CapsuleFrag { backend: umbral_pre::CapsuleFrag, } @@ -410,9 +473,21 @@ impl CapsuleFrag { #[pyproto] impl PyObjectProtocol for CapsuleFrag { + fn __richcmp__(&self, other: PyRef, op: CompareOp) -> PyResult { + richcmp(self, other, op) + } + fn __bytes__(&self) -> PyResult { to_bytes(self) } + + fn __hash__(&self) -> PyResult { + hash(self) + } + + fn __str__(&self) -> PyResult { + hexstr(self) + } } #[pyfunction] From e48da2e8c0bfe0af63dc0558d005adaddc4de371 Mon Sep 17 00:00:00 2001 From: Bogdan Opanchuk Date: Wed, 21 Apr 2021 21:00:11 -0700 Subject: [PATCH 3/4] Split out HasName from HasSerializableBackend --- umbral-pre-python/src/lib.rs | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/umbral-pre-python/src/lib.rs b/umbral-pre-python/src/lib.rs index aaa01780..0e8ace63 100644 --- a/umbral-pre-python/src/lib.rs +++ b/umbral-pre-python/src/lib.rs @@ -16,6 +16,9 @@ use umbral_pre::{ trait HasSerializableBackend { fn as_backend(&self) -> &T; fn from_backend(backend: T) -> Self; +} + +trait HasName { fn name() -> &'static str; } @@ -26,7 +29,9 @@ fn to_bytes, U: SerializableToArray>(obj: &T) -> Py }) } -fn from_bytes, U: SerializableToArray>(bytes: &[u8]) -> PyResult { +fn from_bytes + HasName, U: SerializableToArray>( + bytes: &[u8], +) -> PyResult { U::from_bytes(bytes) .map(T::from_backend) .map_err(|err| match err { @@ -42,7 +47,9 @@ fn from_bytes, U: SerializableToArray>(bytes: &[u8] }) } -fn hash, U: SerializableToArray>(obj: &T) -> PyResult { +fn hash + HasName, U: SerializableToArray>( + obj: &T, +) -> PyResult { let serialized = obj.as_backend().to_array(); // call `hash((class_name, bytes(obj)))` @@ -58,12 +65,14 @@ fn hash, U: SerializableToArray>(obj: &T) -> PyResu // remove when CI is updated to a newer Rust version. #[allow(clippy::unknown_clippy_lints)] #[allow(clippy::unnecessary_wraps)] // Don't want to wrap it in Ok() on every call -fn hexstr, U: SerializableToArray>(obj: &T) -> PyResult { +fn hexstr + HasName, U: SerializableToArray>( + obj: &T, +) -> PyResult { let hex_str = hex::encode(obj.as_backend().to_array().as_slice()); Ok(format!("{}:{}", T::name(), &hex_str[0..16])) } -fn richcmp + PyClass + PartialEq, U>( +fn richcmp( obj: &T, other: PyRef, op: CompareOp, @@ -94,7 +103,9 @@ impl HasSerializableBackend for SecretKey { fn from_backend(backend: umbral_pre::SecretKey) -> Self { Self { backend } } +} +impl HasName for SecretKey { fn name() -> &'static str { "SecretKey" } @@ -144,7 +155,9 @@ impl HasSerializableBackend for SecretKeyFactory { fn from_backend(backend: umbral_pre::SecretKeyFactory) -> Self { Self { backend } } +} +impl HasName for SecretKeyFactory { fn name() -> &'static str { "SecretKeyFactory" } @@ -208,7 +221,9 @@ impl HasSerializableBackend for PublicKey { fn from_backend(backend: umbral_pre::PublicKey) -> Self { Self { backend } } +} +impl HasName for PublicKey { fn name() -> &'static str { "PublicKey" } @@ -262,7 +277,9 @@ impl HasSerializableBackend for Capsule { fn from_backend(backend: umbral_pre::Capsule) -> Self { Self { backend } } +} +impl HasName for Capsule { fn name() -> &'static str { "Capsule" } @@ -352,7 +369,9 @@ impl HasSerializableBackend for KeyFrag { fn from_backend(backend: umbral_pre::KeyFrag) -> Self { Self { backend } } +} +impl HasName for KeyFrag { fn name() -> &'static str { "KeyFrag" } @@ -440,7 +459,9 @@ impl HasSerializableBackend for CapsuleFrag { fn from_backend(backend: umbral_pre::CapsuleFrag) -> Self { Self { backend } } +} +impl HasName for CapsuleFrag { fn name() -> &'static str { "CapsuleFrag" } From deb846e6a3d205eec3930d82e5d413c1bc20d334 Mon Sep 17 00:00:00 2001 From: Bogdan Opanchuk Date: Sun, 28 Mar 2021 11:37:23 -0700 Subject: [PATCH 4/4] Add __bytes__, from_bytes() and __hash__() to docs --- umbral-pre-python/docs/index.rst | 56 ++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/umbral-pre-python/docs/index.rst b/umbral-pre-python/docs/index.rst index acb94f43..179577fe 100644 --- a/umbral-pre-python/docs/index.rst +++ b/umbral-pre-python/docs/index.rst @@ -38,6 +38,14 @@ API reference Generates a new :py:class:`SecretKey` using ``label`` as a seed. + .. py:method:: __bytes__() -> bytes + + Serializes the object into a bytestring. + + .. py:staticmethod:: from_bytes(data: bytes) -> SecretKey + + Restores the object from a bytestring. + .. py:class:: PublicKey An ``umbral-pre`` public key object. @@ -46,10 +54,34 @@ API reference Creates a public key corresponding to the given secret key. + .. py:method:: __bytes__() -> bytes + + Serializes the object into a bytestring. + + .. py:staticmethod:: from_bytes(data: bytes) -> PublicKey + + Restores the object from a bytestring. + + .. py:method:: __hash__() -> int + + Returns a hash of self. + .. py:class:: Capsule An encapsulated symmetric key. + .. py:method:: __bytes__() -> bytes + + Serializes the object into a bytestring. + + .. py:staticmethod:: from_bytes(data: bytes) -> Capsule + + Restores the object from a bytestring. + + .. py:method:: __hash__() -> int + + Returns a hash of self. + .. py:function:: encrypt(pk: PublicKey, plaintext: bytes) -> Tuple[Capsule, bytes] Creates a symmetric key, encrypts ``plaintext`` with it, and returns the encapsulated symmetric key along with the ciphertext. ``pk`` is the public key of the recipient. @@ -81,6 +113,18 @@ API reference Verifies the integrity of the fragment using the signing key and, optionally, the delegating and the receiving keys (if they were included in the signature in :py:func:`generate_kfrags`). + .. py:method:: __bytes__() -> bytes + + Serializes the object into a bytestring. + + .. py:staticmethod:: from_bytes(data: bytes) -> KeyFrag + + Restores the object from a bytestring. + + .. py:method:: __hash__() -> int + + Returns a hash of self. + .. py:class:: CapsuleFrag A reencrypted fragment of an encapsulated symmetric key. @@ -89,6 +133,18 @@ API reference Verifies the integrity of the fragment. + .. py:method:: __bytes__() -> bytes + + Serializes the object into a bytestring. + + .. py:staticmethod:: from_bytes(data: bytes) -> CapsuleFrag + + Restores the object from a bytestring. + + .. py:method:: __hash__() -> int + + Returns a hash of self. + Indices and tables ==================