Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Interfacing with Python #41

Merged
merged 21 commits into from
May 5, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
5df4d02
Expand comments in Parameters::new()
fjarri Mar 13, 2021
cd5a764
Make all the hash-based calculations use domain tags
fjarri Mar 13, 2021
d69bbea
Bump PyO3 version
fjarri Mar 13, 2021
8ce9adb
Use XChaCha instead of ChaCha (extended nonce size)
fjarri Mar 15, 2021
0fdbdf8
Rename UmbralDEM to DEM
fjarri Mar 15, 2021
ea5f177
Remove Debug from SecretKey, add some comments
fjarri Mar 15, 2021
c7743af
Do not bundle Parameters with serialized objects and use a fixed set
fjarri Mar 13, 2021
74e62c7
Add a comment for Signature deserialization
fjarri Mar 17, 2021
cbdbad5
Expose more types and serialization in Python bindings
fjarri Mar 15, 2021
417dfde
Minor internal refactoring for KeyFrag
fjarri Mar 18, 2021
3d16483
Don't store metadata in CapsuleFragProof, and instead require it on v…
fjarri Mar 21, 2021
dc5f379
Move key-related code to its own module
fjarri Mar 21, 2021
6d183d7
dem: parametrize kdf() so that it could derive keys of different sizes
fjarri Mar 21, 2021
bd43c0e
Add SecretKeyFactory
fjarri Mar 21, 2021
4aaa0dc
py-bindings: expose SecretKeyFactory
fjarri Mar 21, 2021
480b905
Rename Capsule::from_pubkey -> from_public_key for uniformity with fr…
fjarri Mar 21, 2021
0a4e973
Bump typenum version
fjarri Mar 26, 2021
4cac50f
Fix argument order in the Python binding of `CapsuleFrag.verify`
fjarri Mar 27, 2021
e34ede2
Style fixes
fjarri Apr 13, 2021
8fc7939
Remove TODO for #43 (closed)
fjarri Apr 13, 2021
0e66920
Remove unwraps from Python module initialization
fjarri Apr 22, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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"] }
fjarri marked this conversation as resolved.
Show resolved Hide resolved
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