Skip to content

Commit 64e25d8

Browse files
committed
Fix bugs and readme
1 parent f13006f commit 64e25d8

File tree

5 files changed

+121
-26
lines changed

5 files changed

+121
-26
lines changed

.github/workflows/publish.yml

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,11 @@ jobs:
6060
- uses: actions/setup-python@v4
6161
with:
6262
python-version: '3.x'
63-
- run: pip install maturin
63+
- name: Create virtual environment and install maturin
64+
run: |
65+
python -m venv .venv
66+
source .venv/bin/activate
67+
pip install maturin
6468
- name: Build wheels
6569
uses: PyO3/maturin-action@v1
6670
with:
@@ -87,7 +91,11 @@ jobs:
8791
- uses: actions/setup-python@v4
8892
with:
8993
python-version: '3.x'
90-
- run: pip install maturin
94+
- name: Create virtual environment and install maturin
95+
run: |
96+
python -m venv .venv
97+
source .venv/bin/activate
98+
pip install maturin
9199
- name: Build sdist
92100
uses: PyO3/maturin-action@v1
93101
with:

README.md

Lines changed: 91 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
# `libpep`: Library for polymorphic pseudonymization and encryption
22
[![Crates.io](https://img.shields.io/crates/v/libpep.svg)](https://crates.io/crates/libpep)
33
[![Downloads](https://img.shields.io/crates/d/libpep.svg)](https://crates.io/crates/libpep)
4+
[![PyPI](https://img.shields.io/pypi/v/libpep.svg)](https://pypi.org/project/libpep/)
5+
[![PyPI Downloads](https://img.shields.io/pypi/dm/libpep.svg)](https://pypi.org/project/libpep/)
46
[![npm](https://img.shields.io/npm/v/@nolai/libpep-wasm.svg)](https://www.npmjs.com/package/@nolai/libpep-wasm)
5-
[![Downloads](https://img.shields.io/npm/dm/@nolai/libpep-wasm.svg)](https://www.npmjs.com/package/@nolai/libpep-wasm)
7+
[![npm Downloads](https://img.shields.io/npm/dm/@nolai/libpep-wasm.svg)](https://www.npmjs.com/package/@nolai/libpep-wasm)
68
[![License](https://img.shields.io/crates/l/libpep.svg)](https://crates.io/crates/libpep)
79
[![Documentation](https://docs.rs/libpep/badge.svg)](https://docs.rs/libpep)
810
[![Dependencies](https://deps.rs/repo/github/NOLAI/libpep/status.svg)](https://deps.rs/repo/github/NOLAI/libpep)
@@ -17,7 +19,7 @@ The library supports three homomorphic operations on ciphertext `in` (= `Enc(b,
1719
Decryption will both result in message `M`. Specifically, `in = Enc(r, M, Y)` is transformed to `out = Enc(r, M, k*Y)`.
1820
- `out = reshuffle(in, s)`: modifies a ciphertext `in` (an encrypted form of `M`), so that after decryption of `out` the decrypted message will be equal to `s*M`.
1921
Specifically, `in = Enc(r, M, Y)` is transformed to `out = Enc(r, n*M, Y)`.
20-
- `out = rerandomize(in, r)`: scrambles a ciphertext.
22+
- `o = rerandomize(in, r)`: scrambles a ciphertext.
2123
Both `in` and `out` can be decrypted by the same secret key `y`, both resulting in the same decrypted message `M`.
2224
However, the binary form of `in` and `out` differs. Spec: `in = Enc(b, M, Y)` is transformed to `out = Enc(r+b, M, Y)`;
2325

@@ -29,7 +31,65 @@ The key idea behind this form of cryptography is that the pseudonymization and r
2931
This means that during initial encryption, the ultimate receiver(s) do(es) not yet need to be known.
3032
Data can initially be encrypted for one key, and later rekeyed and potentially reshuffled (in case of identifiers) for another key, leading to asynchronous end-to-end encryption with built-in pseudonymisation.
3133

32-
Apart from a Rust crate, this library also contains a WASM library for usage in the browser or web applications with a similar API, enabled with the `wasm` feature.
34+
Apart from a Rust crate, this library provides bindings for multiple platforms:
35+
36+
## Language Bindings
37+
38+
### Python
39+
40+
Install from PyPI:
41+
```bash
42+
pip install libpep
43+
```
44+
45+
Use with direct imports from submodules:
46+
```python
47+
from libpep.high_level import Pseudonym, DataPoint, make_global_keys
48+
from libpep.arithmetic import GroupElement, ScalarNonZero
49+
50+
# Generate keys
51+
keys = make_global_keys()
52+
53+
# Create and work with pseudonyms
54+
pseudonym = Pseudonym.random()
55+
print(f"Pseudonym: {pseudonym.as_hex()}")
56+
57+
# Create data points
58+
data = DataPoint.random()
59+
print(f"Data point: {data.as_hex()}")
60+
```
61+
62+
### WebAssembly (WASM)
63+
64+
Install from npm:
65+
```bash
66+
npm install @nolai/libpep-wasm
67+
```
68+
69+
Use in Node.js or browser applications:
70+
```javascript
71+
import * as libpep from '@nolai/libpep-wasm';
72+
73+
// Generate keys
74+
const keys = libpep.make_global_keys();
75+
76+
// Create and work with pseudonyms
77+
const pseudonym = libpep.Pseudonym.random();
78+
console.log(`Pseudonym: ${pseudonym.as_hex()}`);
79+
80+
// Create data points
81+
const data = libpep.DataPoint.random();
82+
console.log(`Data point: ${data.as_hex()}`);
83+
```
84+
85+
### API Structure
86+
87+
Both Python and WASM bindings mirror the Rust API structure with the same modules:
88+
- `arithmetic` - Basic arithmetic operations on scalars and group elements
89+
- `elgamal` - ElGamal encryption and decryption
90+
- `primitives` - Core PEP operations (`rekey`, `reshuffle`, `rerandomize`)
91+
- `high_level` - User-friendly API with `Pseudonym` and `DataPoint` classes
92+
- `distributed` - Distributed n-PEP operations with multiple servers
3393

3494
## Applications
3595

@@ -70,15 +130,39 @@ We offer APIs at different abstraction levels.
70130

71131
Depending on the use case, you can choose the appropriate level of abstraction.
72132

73-
## Building and running
133+
## Development
74134

75-
Build using cargo: `cargo build` and test using `cargo test`.
135+
Build and test the core Rust library:
136+
```bash
137+
cargo build
138+
cargo test
139+
cargo doc --no-deps
140+
```
141+
142+
## Building Bindings
143+
144+
### Python
76145

77-
To build the WASM library, use either `npm run build:nodejs` or `npm run build:web` (which will call `wasm-pack build --features wasm` for the preferred target).
146+
To build Python bindings for testing:
147+
```bash
148+
python -m venv .venv
149+
source .venv/bin/activate
150+
pip install -e ".[dev]"
151+
maturin develop --features python
152+
python -m pytest tests/python/ -v
153+
```
154+
155+
### WASM
78156

79-
The wasm library can be tested using the Node.js `jest` framework, after compiling the wasm library for Node.js: `npm run test`.
157+
To build WASM bindings for testing:
158+
```bash
159+
npm install
160+
npm run build # Builds both Node.js and web targets
161+
npm test
162+
```
80163

81164
The following features are available:
165+
- `python`: enables the Python bindings.
82166
- `wasm`: enables the WASM library.
83167
- `elgamal3`: enables longer ElGamal for debugging purposes or backward compatibility, but with being less efficient.
84168
- `legacy-pep-repo-compatible`: enables the legacy PEP repository compatible mode, which uses a different function to derive scalars from domains, contexts and secrets.

src/lib/python/distributed.rs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ use crate::distributed::systems::*;
33
use crate::high_level::contexts::*;
44
use crate::high_level::data_types::*;
55
use crate::high_level::keys::*;
6-
use crate::internal::arithmetic::ScalarNonZero;
76
use crate::python::arithmetic::*;
87
use crate::python::high_level::*;
98
use derive_more::{Deref, From, Into};
@@ -191,11 +190,11 @@ pub fn py_make_blinded_global_secret_key(
191190
.into_iter()
192191
.map(|x| BlindingFactor(x.0 .0))
193192
.collect();
194-
let result = make_blinded_global_secret_key(
195-
&GlobalSecretKey::from(ScalarNonZero::from(global_secret_key.0 .0)),
196-
&bs,
197-
)
198-
.ok_or_else(|| pyo3::exceptions::PyValueError::new_err("Product of blinding factors is 1"))?;
193+
let result =
194+
make_blinded_global_secret_key(&GlobalSecretKey::from(global_secret_key.0 .0), &bs)
195+
.ok_or_else(|| {
196+
pyo3::exceptions::PyValueError::new_err("Product of blinding factors is 1")
197+
})?;
199198
Ok(PyBlindedGlobalSecretKey(result))
200199
}
201200

src/lib/python/high_level.rs

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@ use crate::high_level::contexts::*;
22
use crate::high_level::data_types::*;
33
use crate::high_level::keys::*;
44
use crate::high_level::ops::*;
5-
use crate::internal::arithmetic::{GroupElement, ScalarNonZero};
6-
use crate::low_level::elgamal::ElGamal;
5+
use crate::internal::arithmetic::GroupElement;
76
use crate::python::arithmetic::{PyGroupElement, PyScalarNonZero};
87
use crate::python::elgamal::PyElGamal;
98
use derive_more::{Deref, From, Into};
@@ -52,7 +51,7 @@ impl PyGlobalPublicKey {
5251
/// Creates a new global public key from a group element.
5352
#[new]
5453
fn new(x: PyGroupElement) -> Self {
55-
Self(GroupElement::from(x.0).into())
54+
Self(x.0.into())
5655
}
5756

5857
/// Returns the group element associated with this public key.
@@ -123,7 +122,7 @@ impl PyPseudonym {
123122
/// Create from a [`PyGroupElement`].
124123
#[new]
125124
fn new(x: PyGroupElement) -> Self {
126-
Self(Pseudonym::from_point(GroupElement::from(x.0)))
125+
Self(Pseudonym::from_point(x.0))
127126
}
128127

129128
/// Convert to a [`PyGroupElement`].
@@ -273,7 +272,7 @@ impl PyDataPoint {
273272
/// Create from a [`PyGroupElement`].
274273
#[new]
275274
fn new(x: PyGroupElement) -> Self {
276-
Self(DataPoint::from_point(GroupElement::from(x.0)))
275+
Self(DataPoint::from_point(x.0))
277276
}
278277

279278
/// Convert to a [`PyGroupElement`].
@@ -422,7 +421,7 @@ impl PyEncryptedPseudonym {
422421
/// Create from an [`PyElGamal`].
423422
#[new]
424423
fn new(x: PyElGamal) -> Self {
425-
Self(EncryptedPseudonym::from(ElGamal::from(x.0)))
424+
Self(EncryptedPseudonym::from(x.0))
426425
}
427426

428427
/// Encode the encrypted pseudonym as a byte array.
@@ -474,7 +473,7 @@ impl PyEncryptedDataPoint {
474473
/// Create from an [`PyElGamal`].
475474
#[new]
476475
fn new(x: PyElGamal) -> Self {
477-
Self(EncryptedDataPoint::from(ElGamal::from(x.0)))
476+
Self(EncryptedDataPoint::from(x.0))
478477
}
479478

480479
/// Encode the encrypted data point as a byte array.
@@ -577,7 +576,7 @@ pub fn py_encrypt_pseudonym(
577576
let mut rng = rand::thread_rng();
578577
PyEncryptedPseudonym(encrypt(
579578
&message.0,
580-
&SessionPublicKey::from(GroupElement::from(public_key.0 .0)),
579+
&SessionPublicKey::from(public_key.0 .0),
581580
&mut rng,
582581
))
583582
}
@@ -591,7 +590,7 @@ pub fn py_decrypt_pseudonym(
591590
) -> PyPseudonym {
592591
PyPseudonym(decrypt(
593592
&encrypted.0,
594-
&SessionSecretKey::from(ScalarNonZero::from(secret_key.0 .0)),
593+
&SessionSecretKey::from(secret_key.0 .0),
595594
))
596595
}
597596

@@ -605,7 +604,7 @@ pub fn py_encrypt_data(
605604
let mut rng = rand::thread_rng();
606605
PyEncryptedDataPoint(encrypt(
607606
&message.0,
608-
&SessionPublicKey::from(GroupElement::from(public_key.0 .0)),
607+
&SessionPublicKey::from(public_key.0 .0),
609608
&mut rng,
610609
))
611610
}
@@ -619,7 +618,7 @@ pub fn py_decrypt_data(
619618
) -> PyDataPoint {
620619
PyDataPoint(decrypt(
621620
&EncryptedDataPoint::from(encrypted.value),
622-
&SessionSecretKey::from(ScalarNonZero::from(secret_key.0 .0)),
621+
&SessionSecretKey::from(secret_key.0 .0),
623622
))
624623
}
625624

src/lib/python/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@
99
//!
1010
//! This module is only available when the `python` feature is enabled.
1111
12+
// PyO3 code triggers clippy warnings that don't apply to Python bindings:
13+
// - PyResult<T> type aliases appear as "useless conversions" to clippy
14+
// - Methods like to_point(&self) appear to have "wrong self convention" but PyO3 objects can't be moved
15+
#![allow(clippy::useless_conversion, clippy::wrong_self_convention)]
16+
1217
pub mod arithmetic;
1318
pub mod distributed;
1419
pub mod elgamal;

0 commit comments

Comments
 (0)