Skip to main content

libpep/data/
simple.rs

1//! Core data types for pseudonyms and attributes, their encrypted versions,
2//! and session-key based encryption and decryption operations.
3
4use crate::arithmetic::group_elements::GroupElement;
5use crate::arithmetic::scalars::ScalarNonZero;
6use crate::core::elgamal::{ElGamal, ELGAMAL_LENGTH};
7use crate::data::traits::{Encryptable, Encrypted, Pseudonymizable, Rekeyable, Transcryptable};
8use crate::factors::TranscryptionInfo;
9use crate::factors::{
10    AttributeRekeyInfo, PseudonymRekeyInfo, PseudonymizationInfo, RerandomizeFactor,
11};
12use crate::keys::*;
13use derive_more::{Deref, From};
14use rand_core::{CryptoRng, RngCore};
15#[cfg(feature = "serde")]
16use serde::{Deserialize, Serialize};
17
18/// A pseudonym (in the background, this is a [`GroupElement`]) that can be used to identify a user
19/// within a specific context, which can be encrypted, rekeyed and reshuffled.
20#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
21#[cfg_attr(feature = "serde", serde(transparent))]
22#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Deref, From)]
23pub struct Pseudonym {
24    pub value: GroupElement,
25}
26/// An attribute (in the background, this is a [`GroupElement`]), which should not be identifiable
27/// and can be encrypted and rekeyed, but not reshuffled.
28#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
29#[cfg_attr(feature = "serde", serde(transparent))]
30#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Deref, From)]
31pub struct Attribute {
32    pub value: GroupElement,
33}
34/// An encrypted pseudonym, which is an [`ElGamal`] encryption of a [`Pseudonym`].
35#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
36#[cfg_attr(feature = "serde", serde(transparent))]
37#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Deref, From)]
38pub struct EncryptedPseudonym {
39    pub value: ElGamal,
40}
41/// An encrypted attribute, which is an [`ElGamal`] encryption of an [`Attribute`].
42#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
43#[cfg_attr(feature = "serde", serde(transparent))]
44#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Deref, From)]
45pub struct EncryptedAttribute {
46    pub value: ElGamal,
47}
48
49/// A marker trait for encrypted types that use ElGamal encryption with a single ciphertext value.
50/// This enables access to ElGamal-specific operations like serialization.
51pub trait ElGamalEncrypted: Encrypted {
52    type UnencryptedType: ElGamalEncryptable<EncryptedType = Self>;
53
54    /// Get the [ElGamal] ciphertext value.
55    fn value(&self) -> &ElGamal;
56    /// Create from an [ElGamal] ciphertext.
57    fn from_value(value: ElGamal) -> Self
58    where
59        Self: Sized;
60
61    /// Encode as a byte array.
62    fn to_bytes(&self) -> [u8; ELGAMAL_LENGTH] {
63        self.value().to_bytes()
64    }
65
66    /// Decode from a byte array.
67    fn from_bytes(bytes: &[u8; ELGAMAL_LENGTH]) -> Option<Self>
68    where
69        Self: Sized,
70    {
71        ElGamal::from_bytes(bytes).map(Self::from_value)
72    }
73
74    /// Decode from a byte slice.
75    fn from_slice(slice: &[u8]) -> Option<Self>
76    where
77        Self: Sized,
78    {
79        ElGamal::from_slice(slice).map(Self::from_value)
80    }
81
82    /// Convert to base64 string.
83    fn to_base64(&self) -> String {
84        self.value().to_base64()
85    }
86
87    /// Convert from base64 string.
88    fn from_base64(s: &str) -> Option<Self>
89    where
90        Self: Sized,
91    {
92        ElGamal::from_base64(s).map(Self::from_value)
93    }
94}
95
96/// A marker trait for encryptable types that use ElGamal encryption with a single plaintext value.
97/// This enables access to ElGamal-specific operations like serialization and special encodings.
98pub trait ElGamalEncryptable: Encryptable {
99    /// Get the [`GroupElement`] plaintext value.
100    fn value(&self) -> &GroupElement;
101    /// Create from a [`GroupElement`].
102    fn from_value(value: GroupElement) -> Self
103    where
104        Self: Sized;
105
106    /// Create from a [`GroupElement`].
107    fn from_point(value: GroupElement) -> Self
108    where
109        Self: Sized,
110    {
111        Self::from_value(value)
112    }
113
114    /// Create with a random value.
115    fn random<R: RngCore + CryptoRng>(rng: &mut R) -> Self
116    where
117        Self: Sized,
118    {
119        Self::from_point(GroupElement::random(rng))
120    }
121    /// Encode as a byte array of length 32.
122    /// See [`GroupElement::to_bytes`].
123    fn to_bytes(&self) -> [u8; 32] {
124        self.value().to_bytes()
125    }
126    /// Convert to a hexadecimal string of 64 characters.
127    fn to_hex(&self) -> String {
128        self.value().to_hex()
129    }
130    /// Create from a byte array of length 32.
131    /// Returns `None` if the input is not a valid encoding of a [`GroupElement`].
132    fn from_bytes(bytes: &[u8; 32]) -> Option<Self>
133    where
134        Self: Sized,
135    {
136        GroupElement::from_bytes(bytes).map(Self::from_point)
137    }
138    /// Create from a slice of bytes.
139    /// Returns `None` if the input is not a valid encoding of a [`GroupElement`].
140    fn from_slice(slice: &[u8]) -> Option<Self>
141    where
142        Self: Sized,
143    {
144        GroupElement::from_slice(slice).map(Self::from_point)
145    }
146    /// Create from a hexadecimal string.
147    /// Returns `None` if the input is not a valid encoding of a [`GroupElement`].
148    fn from_hex(hex: &str) -> Option<Self>
149    where
150        Self: Sized,
151    {
152        GroupElement::from_hex(hex).map(Self::from_point)
153    }
154    /// Create from a hash value.
155    /// See [`GroupElement::from_hash`].
156    fn from_hash(hash: &[u8; 64]) -> Self
157    where
158        Self: Sized,
159    {
160        Self::from_point(GroupElement::from_hash(hash))
161    }
162    /// Create from a byte array of length 16 using lizard encoding.
163    /// This is useful for creating a pseudonym from an existing identifier or encoding attributes,
164    /// as it accepts any 16-byte value.
165    /// See [`GroupElement::from_lizard`].
166    fn from_lizard(data: &[u8; 16]) -> Self
167    where
168        Self: Sized,
169    {
170        Self::from_point(GroupElement::from_lizard(data))
171    }
172    /// Encode as a byte array of length 16 using lizard encoding.
173    /// Returns `None` if the point is not a valid lizard encoding of a 16-byte value.
174    /// See [`GroupElement::to_lizard`].
175    /// If the value was created using [`ElGamalEncryptable::from_lizard`], this will return a valid value,
176    /// but otherwise it will most likely return `None`.
177    fn to_lizard(&self) -> Option<[u8; 16]> {
178        self.value().to_lizard()
179    }
180}
181
182impl Encryptable for Pseudonym {
183    type EncryptedType = EncryptedPseudonym;
184    type PublicKeyType = PseudonymSessionPublicKey;
185    #[cfg(feature = "offline")]
186    type GlobalPublicKeyType = PseudonymGlobalPublicKey;
187
188    fn encrypt<R>(&self, public_key: &Self::PublicKeyType, rng: &mut R) -> Self::EncryptedType
189    where
190        R: RngCore + CryptoRng,
191    {
192        EncryptedPseudonym::from_value(crate::core::elgamal::encrypt(
193            self.value(),
194            public_key.value(),
195            rng,
196        ))
197    }
198
199    #[cfg(feature = "offline")]
200    fn encrypt_global<R>(
201        &self,
202        public_key: &Self::GlobalPublicKeyType,
203        rng: &mut R,
204    ) -> Self::EncryptedType
205    where
206        R: RngCore + CryptoRng,
207    {
208        EncryptedPseudonym::from_value(crate::core::elgamal::encrypt(
209            self.value(),
210            public_key.value(),
211            rng,
212        ))
213    }
214}
215
216impl Encryptable for Attribute {
217    type EncryptedType = EncryptedAttribute;
218    type PublicKeyType = AttributeSessionPublicKey;
219    #[cfg(feature = "offline")]
220    type GlobalPublicKeyType = AttributeGlobalPublicKey;
221
222    fn encrypt<R>(&self, public_key: &Self::PublicKeyType, rng: &mut R) -> Self::EncryptedType
223    where
224        R: RngCore + CryptoRng,
225    {
226        EncryptedAttribute::from_value(crate::core::elgamal::encrypt(
227            self.value(),
228            public_key.value(),
229            rng,
230        ))
231    }
232
233    #[cfg(feature = "offline")]
234    fn encrypt_global<R>(
235        &self,
236        public_key: &Self::GlobalPublicKeyType,
237        rng: &mut R,
238    ) -> Self::EncryptedType
239    where
240        R: RngCore + CryptoRng,
241    {
242        EncryptedAttribute::from_value(crate::core::elgamal::encrypt(
243            self.value(),
244            public_key.value(),
245            rng,
246        ))
247    }
248}
249
250impl ElGamalEncryptable for Pseudonym {
251    fn value(&self) -> &GroupElement {
252        &self.value
253    }
254    fn from_value(value: GroupElement) -> Self
255    where
256        Self: Sized,
257    {
258        Self { value }
259    }
260}
261
262impl ElGamalEncryptable for Attribute {
263    fn value(&self) -> &GroupElement {
264        &self.value
265    }
266    fn from_value(value: GroupElement) -> Self
267    where
268        Self: Sized,
269    {
270        Self { value }
271    }
272}
273
274impl Encrypted for EncryptedPseudonym {
275    type UnencryptedType = Pseudonym;
276    type SecretKeyType = PseudonymSessionSecretKey;
277    #[cfg(all(feature = "offline", feature = "insecure"))]
278    type GlobalSecretKeyType = PseudonymGlobalSecretKey;
279
280    #[cfg(feature = "elgamal3")]
281    fn decrypt(&self, secret_key: &Self::SecretKeyType) -> Option<Self::UnencryptedType> {
282        crate::core::elgamal::decrypt(self.value(), secret_key.value()).map(Pseudonym::from_value)
283    }
284
285    #[cfg(not(feature = "elgamal3"))]
286    fn decrypt(&self, secret_key: &Self::SecretKeyType) -> Self::UnencryptedType {
287        Pseudonym::from_value(crate::core::elgamal::decrypt(
288            self.value(),
289            secret_key.value(),
290        ))
291    }
292
293    #[cfg(all(feature = "offline", feature = "insecure", feature = "elgamal3"))]
294    fn decrypt_global(
295        &self,
296        secret_key: &Self::GlobalSecretKeyType,
297    ) -> Option<Self::UnencryptedType> {
298        crate::core::elgamal::decrypt(self.value(), secret_key.value()).map(Pseudonym::from_value)
299    }
300
301    #[cfg(all(feature = "offline", feature = "insecure", not(feature = "elgamal3")))]
302    fn decrypt_global(&self, secret_key: &Self::GlobalSecretKeyType) -> Self::UnencryptedType {
303        Pseudonym::from_value(crate::core::elgamal::decrypt(
304            self.value(),
305            secret_key.value(),
306        ))
307    }
308
309    #[cfg(feature = "elgamal3")]
310    fn rerandomize<R>(&self, rng: &mut R) -> Self
311    where
312        R: RngCore + CryptoRng,
313    {
314        let r = ScalarNonZero::random(rng);
315        self.rerandomize_known(&RerandomizeFactor(r))
316    }
317
318    #[cfg(not(feature = "elgamal3"))]
319    fn rerandomize<R>(
320        &self,
321        public_key: &<Self::UnencryptedType as Encryptable>::PublicKeyType,
322        rng: &mut R,
323    ) -> Self
324    where
325        R: RngCore + CryptoRng,
326    {
327        let r = ScalarNonZero::random(rng);
328        self.rerandomize_known(public_key, &RerandomizeFactor(r))
329    }
330
331    #[cfg(feature = "elgamal3")]
332    fn rerandomize_known(&self, factor: &RerandomizeFactor) -> Self {
333        EncryptedPseudonym::from_value(crate::core::primitives::rerandomize(
334            self.value(),
335            &factor.0,
336        ))
337    }
338
339    #[cfg(not(feature = "elgamal3"))]
340    fn rerandomize_known(
341        &self,
342        public_key: &<Self::UnencryptedType as Encryptable>::PublicKeyType,
343        factor: &RerandomizeFactor,
344    ) -> Self {
345        EncryptedPseudonym::from_value(crate::core::primitives::rerandomize(
346            self.value(),
347            public_key.value(),
348            &factor.0,
349        ))
350    }
351}
352
353impl Encrypted for EncryptedAttribute {
354    type UnencryptedType = Attribute;
355    type SecretKeyType = AttributeSessionSecretKey;
356    #[cfg(all(feature = "offline", feature = "insecure"))]
357    type GlobalSecretKeyType = AttributeGlobalSecretKey;
358
359    #[cfg(feature = "elgamal3")]
360    fn decrypt(&self, secret_key: &Self::SecretKeyType) -> Option<Self::UnencryptedType> {
361        crate::core::elgamal::decrypt(self.value(), secret_key.value()).map(Attribute::from_value)
362    }
363
364    #[cfg(not(feature = "elgamal3"))]
365    fn decrypt(&self, secret_key: &Self::SecretKeyType) -> Self::UnencryptedType {
366        Attribute::from_value(crate::core::elgamal::decrypt(
367            self.value(),
368            secret_key.value(),
369        ))
370    }
371
372    #[cfg(all(feature = "offline", feature = "insecure", feature = "elgamal3"))]
373    fn decrypt_global(
374        &self,
375        secret_key: &Self::GlobalSecretKeyType,
376    ) -> Option<Self::UnencryptedType> {
377        crate::core::elgamal::decrypt(self.value(), secret_key.value()).map(Attribute::from_value)
378    }
379
380    #[cfg(all(feature = "offline", feature = "insecure", not(feature = "elgamal3")))]
381    fn decrypt_global(&self, secret_key: &Self::GlobalSecretKeyType) -> Self::UnencryptedType {
382        Attribute::from_value(crate::core::elgamal::decrypt(
383            self.value(),
384            secret_key.value(),
385        ))
386    }
387
388    #[cfg(feature = "elgamal3")]
389    fn rerandomize<R>(&self, rng: &mut R) -> Self
390    where
391        R: RngCore + CryptoRng,
392    {
393        let r = ScalarNonZero::random(rng);
394        self.rerandomize_known(&RerandomizeFactor(r))
395    }
396
397    #[cfg(not(feature = "elgamal3"))]
398    fn rerandomize<R>(
399        &self,
400        public_key: &<Self::UnencryptedType as Encryptable>::PublicKeyType,
401        rng: &mut R,
402    ) -> Self
403    where
404        R: RngCore + CryptoRng,
405    {
406        let r = ScalarNonZero::random(rng);
407        self.rerandomize_known(public_key, &RerandomizeFactor(r))
408    }
409
410    #[cfg(feature = "elgamal3")]
411    fn rerandomize_known(&self, factor: &RerandomizeFactor) -> Self {
412        EncryptedAttribute::from_value(crate::core::primitives::rerandomize(
413            self.value(),
414            &factor.0,
415        ))
416    }
417
418    #[cfg(not(feature = "elgamal3"))]
419    fn rerandomize_known(
420        &self,
421        public_key: &<Self::UnencryptedType as Encryptable>::PublicKeyType,
422        factor: &RerandomizeFactor,
423    ) -> Self {
424        EncryptedAttribute::from_value(crate::core::primitives::rerandomize(
425            self.value(),
426            public_key.value(),
427            &factor.0,
428        ))
429    }
430}
431
432impl ElGamalEncrypted for EncryptedPseudonym {
433    type UnencryptedType = Pseudonym;
434
435    fn value(&self) -> &ElGamal {
436        &self.value
437    }
438    fn from_value(value: ElGamal) -> Self
439    where
440        Self: Sized,
441    {
442        Self { value }
443    }
444}
445impl ElGamalEncrypted for EncryptedAttribute {
446    type UnencryptedType = Attribute;
447
448    fn value(&self) -> &ElGamal {
449        &self.value
450    }
451    fn from_value(value: ElGamal) -> Self
452    where
453        Self: Sized,
454    {
455        Self { value }
456    }
457}
458
459// Transcryption trait implementations
460
461impl Pseudonymizable for EncryptedPseudonym {
462    fn pseudonymize(&self, info: &PseudonymizationInfo) -> Self {
463        EncryptedPseudonym::from_value(crate::core::primitives::rsk(
464            self.value(),
465            &info.s.0,
466            &info.k.0,
467        ))
468    }
469}
470
471impl Rekeyable for EncryptedPseudonym {
472    type RekeyInfo = PseudonymRekeyInfo;
473
474    fn rekey(&self, info: &Self::RekeyInfo) -> Self {
475        EncryptedPseudonym::from_value(crate::core::primitives::rekey(self.value(), &info.0))
476    }
477}
478
479impl Rekeyable for EncryptedAttribute {
480    type RekeyInfo = AttributeRekeyInfo;
481
482    fn rekey(&self, info: &Self::RekeyInfo) -> Self {
483        EncryptedAttribute::from_value(crate::core::primitives::rekey(self.value(), &info.0))
484    }
485}
486
487impl Transcryptable for EncryptedPseudonym {
488    fn transcrypt(&self, info: &TranscryptionInfo) -> Self {
489        self.pseudonymize(&info.pseudonym)
490    }
491}
492
493impl Transcryptable for EncryptedAttribute {
494    fn transcrypt(&self, info: &TranscryptionInfo) -> Self {
495        self.rekey(&info.attribute)
496    }
497}
498#[cfg(feature = "batch")]
499impl crate::data::traits::HasStructure for EncryptedPseudonym {
500    type Structure = ();
501
502    fn structure(&self) -> Self::Structure {}
503}
504
505#[cfg(feature = "batch")]
506impl crate::data::traits::HasStructure for EncryptedAttribute {
507    type Structure = ();
508
509    fn structure(&self) -> Self::Structure {}
510}
511
512#[cfg(test)]
513#[allow(clippy::unwrap_used, clippy::expect_used)]
514mod tests {
515    use super::*;
516    use crate::client::{decrypt, encrypt};
517    use crate::factors::contexts::EncryptionContext;
518    use crate::factors::EncryptionSecret;
519
520    #[test]
521    fn pseudonym_encode_decode() {
522        let mut rng = rand::rng();
523        let original = Pseudonym::random(&mut rng);
524        let encoded = original.to_bytes();
525        let decoded = Pseudonym::from_bytes(&encoded).expect("decoding should succeed");
526        assert_eq!(decoded, original);
527    }
528
529    #[test]
530    fn attribute_encode_decode() {
531        let mut rng = rand::rng();
532        let original = Attribute::random(&mut rng);
533        let encoded = original.to_bytes();
534        let decoded = Attribute::from_bytes(&encoded).expect("decoding should succeed");
535        assert_eq!(decoded, original);
536    }
537
538    #[test]
539    fn pseudonym_from_lizard_roundtrip() {
540        let data = b"test identifier!";
541        let pseudonym = Pseudonym::from_lizard(data);
542        let decoded = pseudonym
543            .to_lizard()
544            .expect("lizard encoding should succeed");
545        assert_eq!(decoded, *data);
546    }
547
548    #[test]
549    fn attribute_from_lizard_roundtrip() {
550        let data = b"some attribute!!";
551        let attribute = Attribute::from_lizard(data);
552        let decoded = attribute
553            .to_lizard()
554            .expect("lizard encoding should succeed");
555        assert_eq!(decoded, *data);
556    }
557
558    #[test]
559    fn pseudonym_hex_roundtrip() {
560        let mut rng = rand::rng();
561        let original = Pseudonym::random(&mut rng);
562        let hex = original.to_hex();
563        let decoded = Pseudonym::from_hex(&hex).expect("hex decoding should succeed");
564        assert_eq!(decoded, original);
565    }
566
567    #[test]
568    fn encrypt_decrypt_pseudonym() {
569        let mut rng = rand::rng();
570        let (_, global_secret) = make_pseudonym_global_keys(&mut rng);
571        let enc_secret = EncryptionSecret::from("test-secret".as_bytes().to_vec());
572        let session = EncryptionContext::from("session-1");
573        let (session_public, session_secret) =
574            make_pseudonym_session_keys(&global_secret, &session, &enc_secret);
575
576        let original = Pseudonym::random(&mut rng);
577        let encrypted = encrypt(&original, &session_public, &mut rng);
578        #[cfg(feature = "elgamal3")]
579        let decrypted = decrypt(&encrypted, &session_secret).expect("decryption should succeed");
580        #[cfg(not(feature = "elgamal3"))]
581        let decrypted = decrypt(&encrypted, &session_secret);
582
583        assert_eq!(decrypted, original);
584    }
585
586    #[test]
587    fn encrypt_decrypt_attribute() {
588        let mut rng = rand::rng();
589        let (_, global_secret) = make_attribute_global_keys(&mut rng);
590        let enc_secret = EncryptionSecret::from("test-secret".as_bytes().to_vec());
591        let session = EncryptionContext::from("session-1");
592        let (session_public, session_secret) =
593            make_attribute_session_keys(&global_secret, &session, &enc_secret);
594
595        let original = Attribute::random(&mut rng);
596        let encrypted = encrypt(&original, &session_public, &mut rng);
597        #[cfg(feature = "elgamal3")]
598        let decrypted = decrypt(&encrypted, &session_secret).expect("decryption should succeed");
599        #[cfg(not(feature = "elgamal3"))]
600        let decrypted = decrypt(&encrypted, &session_secret);
601
602        assert_eq!(decrypted, original);
603    }
604
605    #[test]
606    fn encrypted_pseudonym_base64_roundtrip() {
607        let mut rng = rand::rng();
608        let (_, global_secret) = make_pseudonym_global_keys(&mut rng);
609        let enc_secret = EncryptionSecret::from("test-secret".as_bytes().to_vec());
610        let session = EncryptionContext::from("session-1");
611        let (session_public, _) =
612            make_pseudonym_session_keys(&global_secret, &session, &enc_secret);
613
614        let pseudonym = Pseudonym::random(&mut rng);
615        let encrypted = encrypt(&pseudonym, &session_public, &mut rng);
616        let base64 = encrypted.to_base64();
617        let decoded =
618            EncryptedPseudonym::from_base64(&base64).expect("base64 decoding should succeed");
619
620        assert_eq!(decoded, encrypted);
621    }
622
623    #[test]
624    fn encrypted_attribute_serde_json() {
625        let mut rng = rand::rng();
626        let (_, global_secret) = make_attribute_global_keys(&mut rng);
627        let enc_secret = EncryptionSecret::from("test-secret".as_bytes().to_vec());
628        let session = EncryptionContext::from("session-1");
629        let (session_public, _) =
630            make_attribute_session_keys(&global_secret, &session, &enc_secret);
631
632        let attribute = Attribute::random(&mut rng);
633        let encrypted = encrypt(&attribute, &session_public, &mut rng);
634        let json = serde_json::to_string(&encrypted).expect("serialization should succeed");
635        let deserialized: EncryptedAttribute =
636            serde_json::from_str(&json).expect("deserialization should succeed");
637
638        assert_eq!(deserialized, encrypted);
639    }
640
641    #[test]
642    fn polymorphic_encrypt_decrypt() {
643        let mut rng = rand::rng();
644        let (_, global_secret) = make_pseudonym_global_keys(&mut rng);
645        let enc_secret = EncryptionSecret::from("test-secret".as_bytes().to_vec());
646        let session = EncryptionContext::from("session-1");
647        let (session_public, session_secret) =
648            make_pseudonym_session_keys(&global_secret, &session, &enc_secret);
649
650        let original = Pseudonym::random(&mut rng);
651        let encrypted = encrypt(&original, &session_public, &mut rng);
652        #[cfg(feature = "elgamal3")]
653        let decrypted = decrypt(&encrypted, &session_secret).expect("decryption should succeed");
654        #[cfg(not(feature = "elgamal3"))]
655        let decrypted = decrypt(&encrypted, &session_secret);
656
657        assert_eq!(decrypted, original);
658    }
659}