Skip to content

Commit a3f9312

Browse files
committed
Add serialization of LongPseudonyms and LongAttributes
1 parent 38f22e6 commit a3f9312

File tree

6 files changed

+596
-7
lines changed

6 files changed

+596
-7
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[package]
22
name = "libpep"
33
edition = "2021"
4-
version = "0.7.4"
4+
version = "0.7.5"
55
authors = ["Bernard van Gastel <bvgastel@bitpowder.com>", "Job Doesburg <job@jobdoesburg.nl>"]
66
homepage = "https://github.com/NOLAI/libpep"
77
repository = "https://github.com/NOLAI/libpep"

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@nolai/libpep-wasm",
3-
"version": "0.7.4",
3+
"version": "0.7.5",
44
"description": "The WebAssembly version of the libpep library",
55
"repository": {
66
"type": "git",

src/lib/high_level/padding.rs

Lines changed: 160 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
use crate::high_level::data_types::{
2-
Attribute, Encryptable, EncryptedAttribute, EncryptedPseudonym, Pseudonym,
2+
Attribute, Encryptable, Encrypted, EncryptedAttribute, EncryptedPseudonym, Pseudonym,
33
};
44
use derive_more::{Deref, From};
5+
use serde::{Deserialize, Deserializer, Serialize, Serializer};
56
use std::io::{Error, ErrorKind};
67

78
/// A trait for encryptable types that support PKCS#7 padding for single-block (16 byte) encoding.
@@ -272,6 +273,164 @@ impl LongAttribute {
272273
}
273274
}
274275

276+
impl LongEncryptedPseudonym {
277+
/// Serializes a `LongEncryptedPseudonym` to a string by concatenating the base64-encoded
278+
/// individual `EncryptedPseudonym` items with "|" as a delimiter.
279+
///
280+
/// # Example
281+
///
282+
/// ```no_run
283+
/// use libpep::high_level::padding::LongEncryptedPseudonym;
284+
///
285+
/// let long_enc_pseudo = LongEncryptedPseudonym(vec![/* ... */]);
286+
/// let serialized = long_enc_pseudo.serialize();
287+
/// ```
288+
pub fn serialize(&self) -> String {
289+
self.0
290+
.iter()
291+
.map(|item| item.as_base64())
292+
.collect::<Vec<_>>()
293+
.join("|")
294+
}
295+
296+
/// Deserializes a `LongEncryptedPseudonym` from a string by splitting on "|" and
297+
/// decoding each base64-encoded `EncryptedPseudonym`.
298+
///
299+
/// # Errors
300+
///
301+
/// Returns an error if any of the base64-encoded parts cannot be decoded.
302+
///
303+
/// # Example
304+
///
305+
/// ```no_run
306+
/// use libpep::high_level::padding::LongEncryptedPseudonym;
307+
///
308+
/// let serialized = "base64_1|base64_2|base64_3";
309+
/// let long_enc_pseudo = LongEncryptedPseudonym::deserialize(serialized).unwrap();
310+
///
311+
/// // Empty string deserializes to empty vector
312+
/// let empty = LongEncryptedPseudonym::deserialize("").unwrap();
313+
/// assert_eq!(empty.0.len(), 0);
314+
/// ```
315+
pub fn deserialize(s: &str) -> Result<Self, Error> {
316+
if s.is_empty() {
317+
return Ok(LongEncryptedPseudonym(vec![]));
318+
}
319+
320+
let items: Result<Vec<EncryptedPseudonym>, Error> = s
321+
.split('|')
322+
.map(|part| {
323+
EncryptedPseudonym::from_base64(part).ok_or_else(|| {
324+
Error::new(
325+
ErrorKind::InvalidData,
326+
format!("Invalid base64 encoding: {}", part),
327+
)
328+
})
329+
})
330+
.collect();
331+
332+
items.map(LongEncryptedPseudonym)
333+
}
334+
}
335+
336+
impl LongEncryptedAttribute {
337+
/// Serializes a `LongEncryptedAttribute` to a string by concatenating the base64-encoded
338+
/// individual `EncryptedAttribute` items with "|" as a delimiter.
339+
///
340+
/// # Example
341+
///
342+
/// ```no_run
343+
/// use libpep::high_level::padding::LongEncryptedAttribute;
344+
///
345+
/// let long_enc_attr = LongEncryptedAttribute(vec![/* ... */]);
346+
/// let serialized = long_enc_attr.serialize();
347+
/// ```
348+
pub fn serialize(&self) -> String {
349+
self.0
350+
.iter()
351+
.map(|item| item.as_base64())
352+
.collect::<Vec<_>>()
353+
.join("|")
354+
}
355+
356+
/// Deserializes a `LongEncryptedAttribute` from a string by splitting on "|" and
357+
/// decoding each base64-encoded `EncryptedAttribute`.
358+
///
359+
/// # Errors
360+
///
361+
/// Returns an error if any of the base64-encoded parts cannot be decoded.
362+
///
363+
/// # Example
364+
///
365+
/// ```no_run
366+
/// use libpep::high_level::padding::LongEncryptedAttribute;
367+
///
368+
/// let serialized = "base64_1|base64_2|base64_3";
369+
/// let long_enc_attr = LongEncryptedAttribute::deserialize(serialized).unwrap();
370+
///
371+
/// // Empty string deserializes to empty vector
372+
/// let empty = LongEncryptedAttribute::deserialize("").unwrap();
373+
/// assert_eq!(empty.0.len(), 0);
374+
/// ```
375+
pub fn deserialize(s: &str) -> Result<Self, Error> {
376+
if s.is_empty() {
377+
return Ok(LongEncryptedAttribute(vec![]));
378+
}
379+
380+
let items: Result<Vec<EncryptedAttribute>, Error> = s
381+
.split('|')
382+
.map(|part| {
383+
EncryptedAttribute::from_base64(part).ok_or_else(|| {
384+
Error::new(
385+
ErrorKind::InvalidData,
386+
format!("Invalid base64 encoding: {}", part),
387+
)
388+
})
389+
})
390+
.collect();
391+
392+
items.map(LongEncryptedAttribute)
393+
}
394+
}
395+
396+
impl Serialize for LongEncryptedPseudonym {
397+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
398+
where
399+
S: Serializer,
400+
{
401+
serializer.serialize_str(&self.serialize())
402+
}
403+
}
404+
405+
impl<'de> Deserialize<'de> for LongEncryptedPseudonym {
406+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
407+
where
408+
D: Deserializer<'de>,
409+
{
410+
let s = String::deserialize(deserializer)?;
411+
Self::deserialize(&s).map_err(serde::de::Error::custom)
412+
}
413+
}
414+
415+
impl Serialize for LongEncryptedAttribute {
416+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
417+
where
418+
S: Serializer,
419+
{
420+
serializer.serialize_str(&self.serialize())
421+
}
422+
}
423+
424+
impl<'de> Deserialize<'de> for LongEncryptedAttribute {
425+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
426+
where
427+
D: Deserializer<'de>,
428+
{
429+
let s = String::deserialize(deserializer)?;
430+
Self::deserialize(&s).map_err(serde::de::Error::custom)
431+
}
432+
}
433+
275434
/// Internal helper function to encode bytes with PKCS#7 padding
276435
fn from_bytes_padded_impl<T: Encryptable>(data: &[u8]) -> Result<Vec<T>, Error> {
277436
// Handle empty data

src/lib/python/high_level.rs

Lines changed: 109 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ 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::high_level::padding::{LongAttribute, LongPseudonym, Padded};
5+
use crate::high_level::padding::{
6+
LongAttribute, LongEncryptedAttribute, LongEncryptedPseudonym, LongPseudonym, Padded,
7+
};
68
use crate::high_level::secrets::{EncryptionSecret, PseudonymizationSecret};
79
use crate::internal::arithmetic::GroupElement;
810
use crate::python::arithmetic::{PyGroupElement, PyScalarNonZero};
@@ -782,6 +784,110 @@ impl PyEncryptedAttribute {
782784
}
783785
}
784786

787+
/// A collection of encrypted pseudonyms that can be serialized as a pipe-delimited string.
788+
#[pyclass(name = "LongEncryptedPseudonym")]
789+
#[derive(Clone, Eq, PartialEq, Debug, From, Deref)]
790+
pub struct PyLongEncryptedPseudonym(pub(crate) LongEncryptedPseudonym);
791+
792+
#[pymethods]
793+
impl PyLongEncryptedPseudonym {
794+
/// Create from a vector of encrypted pseudonyms.
795+
#[new]
796+
fn new(encrypted_pseudonyms: Vec<PyEncryptedPseudonym>) -> Self {
797+
let rust_enc_pseudonyms: Vec<EncryptedPseudonym> =
798+
encrypted_pseudonyms.into_iter().map(|p| p.0).collect();
799+
Self(LongEncryptedPseudonym(rust_enc_pseudonyms))
800+
}
801+
802+
/// Serializes to a pipe-delimited base64 string.
803+
#[pyo3(name = "serialize")]
804+
fn serialize(&self) -> String {
805+
self.0.serialize()
806+
}
807+
808+
/// Deserializes from a pipe-delimited base64 string.
809+
#[staticmethod]
810+
#[pyo3(name = "deserialize")]
811+
fn deserialize(s: &str) -> PyResult<Self> {
812+
LongEncryptedPseudonym::deserialize(s)
813+
.map(Self)
814+
.map_err(|e| {
815+
pyo3::exceptions::PyValueError::new_err(format!("Deserialization failed: {e}"))
816+
})
817+
}
818+
819+
/// Get the underlying encrypted pseudonyms.
820+
#[pyo3(name = "encrypted_pseudonyms")]
821+
fn encrypted_pseudonyms(&self) -> Vec<PyEncryptedPseudonym> {
822+
self.0 .0.iter().map(|p| PyEncryptedPseudonym(*p)).collect()
823+
}
824+
825+
/// Get the number of encrypted pseudonym blocks.
826+
fn __len__(&self) -> usize {
827+
self.0 .0.len()
828+
}
829+
830+
fn __repr__(&self) -> String {
831+
format!("LongEncryptedPseudonym({} blocks)", self.0 .0.len())
832+
}
833+
834+
fn __eq__(&self, other: &PyLongEncryptedPseudonym) -> bool {
835+
self.0 == other.0
836+
}
837+
}
838+
839+
/// A collection of encrypted attributes that can be serialized as a pipe-delimited string.
840+
#[pyclass(name = "LongEncryptedAttribute")]
841+
#[derive(Clone, Eq, PartialEq, Debug, From, Deref)]
842+
pub struct PyLongEncryptedAttribute(pub(crate) LongEncryptedAttribute);
843+
844+
#[pymethods]
845+
impl PyLongEncryptedAttribute {
846+
/// Create from a vector of encrypted attributes.
847+
#[new]
848+
fn new(encrypted_attributes: Vec<PyEncryptedAttribute>) -> Self {
849+
let rust_enc_attributes: Vec<EncryptedAttribute> =
850+
encrypted_attributes.into_iter().map(|a| a.0).collect();
851+
Self(LongEncryptedAttribute(rust_enc_attributes))
852+
}
853+
854+
/// Serializes to a pipe-delimited base64 string.
855+
#[pyo3(name = "serialize")]
856+
fn serialize(&self) -> String {
857+
self.0.serialize()
858+
}
859+
860+
/// Deserializes from a pipe-delimited base64 string.
861+
#[staticmethod]
862+
#[pyo3(name = "deserialize")]
863+
fn deserialize(s: &str) -> PyResult<Self> {
864+
LongEncryptedAttribute::deserialize(s)
865+
.map(Self)
866+
.map_err(|e| {
867+
pyo3::exceptions::PyValueError::new_err(format!("Deserialization failed: {e}"))
868+
})
869+
}
870+
871+
/// Get the underlying encrypted attributes.
872+
#[pyo3(name = "encrypted_attributes")]
873+
fn encrypted_attributes(&self) -> Vec<PyEncryptedAttribute> {
874+
self.0 .0.iter().map(|a| PyEncryptedAttribute(*a)).collect()
875+
}
876+
877+
/// Get the number of encrypted attribute blocks.
878+
fn __len__(&self) -> usize {
879+
self.0 .0.len()
880+
}
881+
882+
fn __repr__(&self) -> String {
883+
format!("LongEncryptedAttribute({} blocks)", self.0 .0.len())
884+
}
885+
886+
fn __eq__(&self, other: &PyLongEncryptedAttribute) -> bool {
887+
self.0 == other.0
888+
}
889+
}
890+
785891
// Pseudonym global key pair
786892
#[pyclass(name = "PseudonymGlobalKeyPair")]
787893
#[derive(Copy, Clone, Debug)]
@@ -977,6 +1083,8 @@ pub fn register_module(m: &Bound<'_, PyModule>) -> PyResult<()> {
9771083
m.add_class::<PyLongAttribute>()?;
9781084
m.add_class::<PyEncryptedPseudonym>()?;
9791085
m.add_class::<PyEncryptedAttribute>()?;
1086+
m.add_class::<PyLongEncryptedPseudonym>()?;
1087+
m.add_class::<PyLongEncryptedAttribute>()?;
9801088
m.add_class::<PyPseudonymGlobalKeyPair>()?;
9811089
m.add_class::<PyAttributeGlobalKeyPair>()?;
9821090
m.add_class::<PyPseudonymSessionKeyPair>()?;

0 commit comments

Comments
 (0)