Skip to content

Commit

Permalink
Merge pull request #41 from fjarri/interfacing
Browse files Browse the repository at this point in the history
Interfacing with Python
  • Loading branch information
fjarri authored May 5, 2021
2 parents 128eb0c + 0e66920 commit 45b2b85
Show file tree
Hide file tree
Showing 21 changed files with 820 additions and 697 deletions.
2 changes: 1 addition & 1 deletion umbral-pre-python/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@ edition = "2018"
crate-type = ["cdylib"]

[dependencies]
pyo3 = { version = "0.12.3", features = ["extension-module"] }
pyo3 = { version = "0.13", features = ["extension-module"] }
umbral-pre = { path = "../umbral-pre" }
generic-array = "0.14"
28 changes: 16 additions & 12 deletions umbral-pre-python/docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -26,34 +26,39 @@ API reference
Generates a new secret key.

.. py:class:: PublicKey
.. py:class:: SecretKeyFactory
An ``umbral-pre`` public key object.
A deterministic generator of :py:class:`SecretKey` objects.

.. py:staticmethod:: from_secret_key(sk: SecretKey) -> PublicKey
.. py:staticmethod:: random() -> SecretKeyFactory
Creates a public key corresponding to the given secret key.
Generates a new random factory.

.. py:method:: secret_key_by_label(label: bytes) -> SecretKey
Generates a new :py:class:`SecretKey` using ``label`` as a seed.

.. py:class:: PublicKey
.. py:class:: Parameters()
An ``umbral-pre`` public key object.

A scheme parameters object.
.. py:staticmethod:: from_secret_key(sk: SecretKey) -> PublicKey
Creates a public key corresponding to the given secret key.

.. py:class:: Capsule
An encapsulated symmetric key.


.. py:function:: encrypt(params: Parameters, pk: PublicKey, plaintext: bytes) -> Tuple[Capsule, bytes]
.. 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.

.. py:function:: decrypt_original(sk: SecretKey, capsule: Capsule, ciphertext: bytes) -> bytes
Decrypts ``ciphertext`` with the key used to encrypt it.

.. py:function:: generate_kfrags(params: Parameters, delegating_sk: SecretKey, receiving_pk: PublicKey, signing_sk: SecretKey, threshold: int, num_kfrags: int, sign_delegating_key: bool, sign_receiving_key: bool) -> List[KeyFrag]
.. py:function:: generate_kfrags(delegating_sk: SecretKey, receiving_pk: PublicKey, signing_sk: SecretKey, threshold: int, num_kfrags: int, sign_delegating_key: bool, sign_receiving_key: bool) -> List[KeyFrag]
Generates ``num_kfrags`` key fragments that can be used to reencrypt the capsule for the holder of the secret key corresponding to ``receiving_pk``. ``threshold`` fragments will be enough for decryption.

Expand All @@ -62,8 +67,7 @@ API reference
.. py:function:: reencrypt(capsule: Capsule, kfrag: KeyFrag, metadata: Optional[bytes]) -> CapsuleFrag
Reencrypts a capsule using a key fragment.
May include optional ``metadata`` in the resulting capsule fragment.

May include optional ``metadata`` to sign.

.. py:function:: decrypt_reencrypted(decrypting_sk: SecretKey, delegating_pk: PublicKey, capsule: Capsule, cfrags: Sequence[CapsuleFrag], ciphertext: bytes) -> Optional[bytes]
Expand All @@ -81,7 +85,7 @@ API reference
A reencrypted fragment of an encapsulated symmetric key.

.. py:method:: verify(capsule: Capsule, signing_pk: PublicKey, delegating_pk: PublicKey, receiving_pk: PublicKey) -> bool
.. py:method:: verify(capsule: Capsule, delegating_pk: PublicKey, receiving_pk: PublicKey, signing_pk: PublicKey, metadata: Optional[bytes]) -> bool
Verifies the integrity of the fragment.

Expand Down
18 changes: 8 additions & 10 deletions umbral-pre-python/example/example.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,8 @@
# Note that anyone with Alice's public key
# can perform this operation.

params = umbral_pre.Parameters()
plaintext = b"peace at dawn"
capsule, ciphertext = umbral_pre.encrypt(
params, alice_pk, plaintext)
capsule, ciphertext = umbral_pre.encrypt(alice_pk, plaintext)

# Since data was encrypted with Alice's public key,
# Alice can open the capsule and decrypt the ciphertext
Expand All @@ -43,7 +41,7 @@

# Split Re-Encryption Key Generation (aka Delegation)
kfrags = umbral_pre.generate_kfrags(
params, alice_sk, bob_pk, signing_sk, m, n,
alice_sk, bob_pk, signing_sk, m, n,
True, # add the delegating key (alice_pk) to the signature
True, # add the receiving key (bob_pk) to the signature
)
Expand All @@ -61,24 +59,24 @@
# Ursulas can optionally check that the received kfrags
# are valid and perform the reencryption.

metadata = b"metadata"

# Ursula 0
metadata0 = b"metadata0"
assert kfrags[0].verify(signing_pk, alice_pk, bob_pk)
cfrag0 = umbral_pre.reencrypt(capsule, kfrags[0], metadata)
cfrag0 = umbral_pre.reencrypt(capsule, kfrags[0], metadata0)

# Ursula 1
metadata1 = b"metadata1"
assert kfrags[1].verify(signing_pk, alice_pk, bob_pk)
cfrag1 = umbral_pre.reencrypt(capsule, kfrags[1], metadata)
cfrag1 = umbral_pre.reencrypt(capsule, kfrags[1], metadata1)

# ...

# Finally, Bob opens the capsule by using at least `m` cfrags,
# and then decrypts the re-encrypted ciphertext.

# Bob can optionally check that cfrags are valid
assert cfrag0.verify(capsule, alice_pk, bob_pk, signing_pk)
assert cfrag1.verify(capsule, alice_pk, bob_pk, signing_pk)
assert cfrag0.verify(capsule, alice_pk, bob_pk, signing_pk, metadata0)
assert cfrag1.verify(capsule, alice_pk, bob_pk, signing_pk, metadata1)

# Decryption by Bob
plaintext_bob = umbral_pre.decrypt_reencrypted(
Expand Down
176 changes: 140 additions & 36 deletions umbral-pre-python/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
use pyo3::class::basic::CompareOp;
use pyo3::exceptions::PyTypeError;
use pyo3::prelude::*;
use pyo3::types::PyBytes;
use pyo3::wrap_pyfunction;
use pyo3::PyObjectProtocol;

use umbral_pre::SerializableToArray;

#[pyclass(module = "umbral")]
#[derive(PartialEq)]
pub struct SecretKey {
backend: umbral_pre::SecretKey,
}
Expand All @@ -15,59 +21,131 @@ impl SecretKey {
backend: umbral_pre::SecretKey::random(),
}
}

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<Self> {
let backend_key = umbral_pre::SecretKey::from_bytes(bytes)?;
Some(Self {
backend: backend_key,
})
}
}

#[pyproto]
impl PyObjectProtocol for SecretKey {
fn __richcmp__(&self, other: PyRef<SecretKey>, op: CompareOp) -> PyResult<bool> {
match op {
CompareOp::Eq => Ok(self == &*other),
CompareOp::Ne => Ok(self != &*other),
_ => Err(PyTypeError::new_err("SecretKey objects are not ordered")),
}
}
}

#[pyclass(module = "umbral")]
pub struct PublicKey {
backend: umbral_pre::PublicKey,
pub struct SecretKeyFactory {
backend: umbral_pre::SecretKeyFactory,
}

#[pymethods]
impl PublicKey {
impl SecretKeyFactory {
#[staticmethod]
pub fn from_secret_key(sk: &SecretKey) -> Self {
pub fn random() -> Self {
Self {
backend: umbral_pre::PublicKey::from_secret_key(&sk.backend),
backend: umbral_pre::SecretKeyFactory::random(),
}
}

pub fn secret_key_by_label(&self, label: &[u8]) -> Option<SecretKey> {
let backend_sk = self.backend.secret_key_by_label(label)?;
Some(SecretKey {
backend: backend_sk,
})
}

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<Self> {
let backend_factory = umbral_pre::SecretKeyFactory::from_bytes(bytes)?;
Some(Self {
backend: backend_factory,
})
}
}

#[pyclass(module = "umbral")]
pub struct Parameters {
backend: umbral_pre::Parameters,
#[derive(PartialEq)]
pub struct PublicKey {
backend: umbral_pre::PublicKey,
}

#[pymethods]
impl Parameters {
#[new]
pub fn new() -> Self {
impl PublicKey {
#[staticmethod]
pub fn from_secret_key(sk: &SecretKey) -> Self {
Self {
backend: umbral_pre::Parameters::new(),
backend: umbral_pre::PublicKey::from_secret_key(&sk.backend),
}
}

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<Self> {
let backend_pubkey = umbral_pre::PublicKey::from_bytes(bytes)?;
Some(Self {
backend: backend_pubkey,
})
}
}

impl Default for Parameters {
fn default() -> Self {
Self::new()
#[pyproto]
impl PyObjectProtocol for PublicKey {
fn __richcmp__(&self, other: PyRef<PublicKey>, op: CompareOp) -> PyResult<bool> {
match op {
CompareOp::Eq => Ok(self == &*other),
CompareOp::Ne => Ok(self != &*other),
_ => Err(PyTypeError::new_err("PublicKey objects are not ordered")),
}
}
}

#[pyclass(module = "umbral")]
#[derive(Clone)]
pub struct Capsule {
backend: umbral_pre::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]) -> Option<Self> {
let backend_capsule = umbral_pre::Capsule::from_bytes(bytes)?;
Some(Self {
backend: backend_capsule,
})
}
}

#[pyfunction]
pub fn encrypt(
py: Python,
params: &Parameters,
pk: &PublicKey,
plaintext: &[u8],
) -> (Capsule, PyObject) {
let (capsule, ciphertext) =
umbral_pre::encrypt(&params.backend, &pk.backend, plaintext).unwrap();
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(),
Expand Down Expand Up @@ -105,12 +183,24 @@ impl KeyFrag {
receiving_pk.map(|pk| &pk.backend),
)
}

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<Self> {
let backend_kfrag = umbral_pre::KeyFrag::from_bytes(bytes)?;
Some(Self {
backend: backend_kfrag,
})
}
}

#[allow(clippy::too_many_arguments)]
#[pyfunction]
pub fn generate_kfrags(
params: &Parameters,
delegating_sk: &SecretKey,
receiving_pk: &PublicKey,
signing_sk: &SecretKey,
Expand All @@ -120,7 +210,6 @@ pub fn generate_kfrags(
sign_receiving_key: bool,
) -> Vec<KeyFrag> {
let backend_kfrags = umbral_pre::generate_kfrags(
&params.backend,
&delegating_sk.backend,
&receiving_pk.backend,
&signing_sk.backend,
Expand Down Expand Up @@ -148,17 +237,32 @@ impl CapsuleFrag {
pub fn verify(
&self,
capsule: &Capsule,
signing_pk: &PublicKey,
delegating_pk: &PublicKey,
receiving_pk: &PublicKey,
signing_pk: &PublicKey,
metadata: Option<&[u8]>,
) -> bool {
self.backend.verify(
&capsule.backend,
&signing_pk.backend,
&delegating_pk.backend,
&receiving_pk.backend,
&signing_pk.backend,
metadata,
)
}

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<Self> {
let backend_cfrag = umbral_pre::CapsuleFrag::from_bytes(bytes)?;
Some(Self {
backend: backend_cfrag,
})
}
}

#[pyfunction]
Expand Down Expand Up @@ -197,15 +301,15 @@ pub fn decrypt_reencrypted(
#[pymodule]
fn _umbral(_py: Python, m: &PyModule) -> PyResult<()> {
m.add_class::<SecretKey>()?;
m.add_class::<SecretKeyFactory>()?;
m.add_class::<PublicKey>()?;
m.add_class::<Parameters>()?;
m.add_function(wrap_pyfunction!(encrypt, m)?).unwrap();
m.add_function(wrap_pyfunction!(decrypt_original, m)?)
.unwrap();
m.add_function(wrap_pyfunction!(generate_kfrags, m)?)
.unwrap();
m.add_function(wrap_pyfunction!(reencrypt, m)?).unwrap();
m.add_function(wrap_pyfunction!(decrypt_reencrypted, m)?)
.unwrap();
m.add_class::<Capsule>()?;
m.add_class::<KeyFrag>()?;
m.add_class::<CapsuleFrag>()?;
m.add_function(wrap_pyfunction!(encrypt, m)?)?;
m.add_function(wrap_pyfunction!(decrypt_original, m)?)?;
m.add_function(wrap_pyfunction!(generate_kfrags, m)?)?;
m.add_function(wrap_pyfunction!(reencrypt, m)?)?;
m.add_function(wrap_pyfunction!(decrypt_reencrypted, m)?)?;
Ok(())
}
Loading

0 comments on commit 45b2b85

Please sign in to comment.