authenticated_pseudonyms/
unlinkable_pseudonym.rs

1use bls12_381::{Bls12, G1Affine, G1Projective, G2Affine, Scalar};
2use pairing::group::ff::Field;
3use pairing::group::{Group, GroupEncoding};
4use pairing::Engine;
5use rand_chacha::ChaCha20Rng;
6use rand_core::{CryptoRngCore, SeedableRng};
7use subtle::{Choice, ConstantTimeEq, CtOption};
8
9use std::ops::Neg;
10use std::marker::PhantomData;
11
12use crate::traits::Hasher;
13
14const CLIENT_ISSUANCE_LABEL: &[u8] = b"CLIENT_ISSUANCE";
15const PSEUDONYM_LABEL: &[u8] = b"PSEUDONYM";
16const GLOBAL_LABEL: &[u8] = b"SOCIAL_LOGIN";
17
18/// Parameters for the protocol.
19#[derive(Clone, Debug)]
20pub struct Params<H: Hasher> {
21    h: G1Affine,
22    _hasher: PhantomData<H>,
23}
24
25impl<H: Hasher> Params<H> {
26    pub fn default() -> Self {
27        let mut hasher = H::default();
28        hasher.update(b"a very special string");
29        let mut rng = ChaCha20Rng::from_seed(hasher.finalize());
30        Params {
31            h: G1Projective::random(&mut rng).into(),
32            _hasher: PhantomData::default(),
33        }
34    }
35}
36
37struct FiatShamir<H: Hasher> {
38    hasher: H,
39}
40
41impl<H: Hasher> FiatShamir<H> {
42    fn new(label: &[u8], nonce: &[u8]) -> Self {
43        let mut hasher = H::default();
44        hasher.update(GLOBAL_LABEL);
45        hasher.update(G1Affine::label());
46        hasher.update(label);
47        hasher.update(nonce);
48        FiatShamir { hasher }
49    }
50
51    fn update(&mut self, bytes: &[u8]) {
52        self.hasher.update(bytes);
53    }
54
55    fn rng(&self) -> impl CryptoRngCore {
56        ChaCha20Rng::from_seed(self.hasher.finalize())
57    }
58}
59
60trait HasLabel {
61    fn label() -> &'static [u8];
62}
63
64impl HasLabel for G1Affine {
65    fn label() -> &'static [u8] {
66        b"BLS12_381"
67    }
68}
69
70/// The private key of the issuer.
71#[derive(Debug, Clone)]
72pub struct IssuerPrivateKey {
73    x: Scalar,
74}
75
76/// The public key of the issuer.
77#[derive(Debug, Clone)]
78pub struct IssuerPublicKey {
79    w: G2Affine,
80}
81
82/// The private PRF key held by the client as they request credential issuance.
83#[derive(Debug, Clone)]
84pub struct ClientPrivateKey {
85    k: Scalar,
86}
87
88/// The request sent to the server by the client, proving that they know their private key.
89#[derive(Debug, Clone)]
90pub struct CredentialRequest {
91    big_k: G1Affine,
92    // This zkp is not currently necessary (the commitment still is though), however if other
93    // fields were to be included in this proof you'd need this, so it is useful to keep around.
94    gamma: Scalar,
95    k_bar: Scalar,
96}
97
98impl ClientPrivateKey {
99    /// Generate a new client private key.
100    pub fn random(mut rng: impl CryptoRngCore) -> Self {
101        ClientPrivateKey {
102            k: Scalar::random(&mut rng),
103        }
104    }
105
106    /// Recover a client private key from a backup.
107    pub fn recover(k: Scalar) -> Self {
108        ClientPrivateKey { k }
109    }
110
111    /// Create a request for a new credential issuance associated to the given private key.
112    pub fn request<H: Hasher>(&self, params: &Params<H>, mut rng: impl CryptoRngCore) -> CredentialRequest {
113        let big_k = params.h * self.k;
114        let k_prime = Scalar::random(&mut rng);
115        let big_k_1 = params.h * k_prime;
116
117        let gamma = {
118            let mut fiat_shamir = FiatShamir::<H>::new(CLIENT_ISSUANCE_LABEL, b"");
119            fiat_shamir.update(GroupEncoding::to_bytes(&big_k).as_ref());
120            fiat_shamir.update(GroupEncoding::to_bytes(&big_k_1).as_ref());
121            let mut fiat_shamir_rng = fiat_shamir.rng();
122
123            Scalar::random(&mut fiat_shamir_rng)
124        };
125
126        let k_bar = gamma * self.k + k_prime;
127
128        CredentialRequest {
129            big_k: big_k.into(),
130            gamma,
131            k_bar,
132        }
133    }
134}
135
136/// The response the server sends back upon a request for issuance.
137#[derive(Debug, Clone)]
138pub struct CredentialResponse {
139    a: G1Affine,
140    e: Scalar,
141}
142
143impl CredentialRequest {
144    /// Responds to the given credential request with the data needed for the client to construct a
145    /// new credential.
146    pub fn respond<H: Hasher>(
147        &self,
148        issuer_private_key: &IssuerPrivateKey,
149        params: &Params<H>,
150        mut rng: impl CryptoRngCore,
151    ) -> Option<CredentialResponse> {
152        let big_k_1 = params.h * self.k_bar + self.big_k * self.gamma.neg();
153        let client_gamma = {
154            let mut fiat_shamir = FiatShamir::<H>::new(CLIENT_ISSUANCE_LABEL, b"");
155            fiat_shamir.update(G1Affine::from(self.big_k).to_compressed().as_ref());
156            fiat_shamir.update(G1Affine::from(big_k_1).to_compressed().as_ref());
157            let mut fiat_shamir_rng = fiat_shamir.rng();
158            Scalar::random(&mut fiat_shamir_rng)
159        };
160
161        if client_gamma != self.gamma {
162            return None;
163        }
164
165        let e = Scalar::random(&mut rng);
166        let a = (G1Affine::generator() + G1Projective::from(&self.big_k))
167            * (e + issuer_private_key.x).invert().unwrap();
168        Some(CredentialResponse { a: a.into(), e })
169    }
170}
171
172impl IssuerPrivateKey {
173    /// Generate random private key for the issuer.
174    pub fn random(mut rng: impl CryptoRngCore) -> Self {
175        IssuerPrivateKey {
176            x: Scalar::random(&mut rng),
177        }
178    }
179
180    /// Computes the public key of the issuer given the private one.
181    pub fn public(&self) -> IssuerPublicKey {
182        IssuerPublicKey {
183            w: (G2Affine::generator() * self.x).into(),
184        }
185    }
186}
187
188/// A credential which the client holds privately.
189#[derive(Debug, Clone)]
190pub struct Credential {
191    a: G1Affine,
192    e: Scalar,
193    k: Scalar,
194}
195
196impl ClientPrivateKey {
197    /// Creates a new credential using the original request, response from the server, and the
198    /// client's private PRF key.
199    pub fn create_credential(
200        &self,
201        request: &CredentialRequest,
202        response: &CredentialResponse,
203        issuer_public_key: &IssuerPublicKey,
204    ) -> Option<Credential> {
205        if Bls12::pairing(&response.a, &issuer_public_key.w)
206            != Bls12::pairing(
207                &(response.a * response.e.neg() + G1Affine::generator() + request.big_k).into(),
208                &G2Affine::generator(),
209            )
210        {
211            return None;
212        }
213
214        Some(Credential {
215            a: response.a,
216            e: response.e,
217            k: self.k,
218        })
219    }
220}
221
222impl Credential {
223    /// Verify the validity of the given credential.
224    pub fn verify<H: Hasher>(&self, params: &Params<H>, issuer_public_key: &IssuerPublicKey) -> Choice {
225        Bls12::pairing(&self.a, &issuer_public_key.w).ct_eq(&Bls12::pairing(
226            &G1Affine::from(&self.a * self.e.neg() + G1Affine::generator() + params.h * self.k),
227            &G2Affine::generator(),
228        ))
229    }
230
231    pub fn vrf_key(&self) -> &Scalar {
232        &self.k
233    }
234}
235
236/// A local pseudonym derived for a particular context. This pseudonym is unlinkable, such that
237/// even if the relying parties collude with each other and the issuer, they cannot link two
238/// pseudonyms derived from the same underlying credential.
239#[derive(Debug, Clone)]
240pub struct Pseudonym {
241    relying_party_id: Scalar,
242    a_prime: G1Affine,
243    b_bar: G1Affine,
244    a_bar: G1Affine,
245    y: G1Affine,
246    gamma: Scalar,
247    z_e: Scalar,
248    z_r2: Scalar,
249    z_r3: Scalar,
250    z_k: Scalar,
251}
252
253impl Credential {
254    /// Compute a pseudonym for the given context.
255    pub fn pseudonym_for<H: Hasher>(
256        &self,
257        params: &Params<H>,
258        relying_party_id: Scalar,
259        nonce: &[u8],
260        mut rng: impl CryptoRngCore,
261    ) -> CtOption<Pseudonym> {
262        let mut fiat_shamir = FiatShamir::<H>::new(PSEUDONYM_LABEL, nonce);
263
264        let r1 = Scalar::random(&mut rng);
265        let r2 = Scalar::random(&mut rng);
266        let e_prime = Scalar::random(&mut rng);
267        let r2_prime = Scalar::random(&mut rng);
268        let r3_prime = Scalar::random(&mut rng);
269        let k_prime = Scalar::random(&mut rng);
270
271        let b = G1Affine::generator() + params.h * self.k;
272        let a_prime = self.a * (r1 * r2);
273        let b_bar = b * r1;
274        let a_bar = a_prime * self.e.neg() + b_bar * r2;
275        let r3 = r1.invert();
276
277        fiat_shamir.update(GroupEncoding::to_bytes(&a_prime).as_ref());
278        fiat_shamir.update(GroupEncoding::to_bytes(&b_bar).as_ref());
279        fiat_shamir.update(GroupEncoding::to_bytes(&a_bar).as_ref());
280
281        let a1 = a_prime * e_prime + b_bar * r2_prime;
282        let a2 = b_bar * r3_prime + params.h * k_prime;
283
284        fiat_shamir.update(GroupEncoding::to_bytes(&a1).as_ref());
285        fiat_shamir.update(GroupEncoding::to_bytes(&a2).as_ref());
286
287        let y = G1Affine::generator() * (self.k + relying_party_id).invert().unwrap();
288
289        fiat_shamir.update(GroupEncoding::to_bytes(&y).as_ref());
290
291        let y1 = y * k_prime.neg();
292
293        fiat_shamir.update(GroupEncoding::to_bytes(&y1).as_ref());
294
295        let gamma = Scalar::random(fiat_shamir.rng());
296
297        let z_e = gamma.neg() * self.e + e_prime;
298        let z_r2 = gamma * r2 + r2_prime;
299        let z_r3 = r3.map(|r3| gamma * r3 + r3_prime);
300        let z_k = gamma.neg() * self.k + k_prime;
301
302        z_r3.map(|z_r3| Pseudonym {
303            relying_party_id,
304            a_prime: a_prime.into(),
305            b_bar: b_bar.into(),
306            a_bar: a_bar.into(),
307            y: y.into(),
308            gamma,
309            z_e,
310            z_r2,
311            z_r3,
312            z_k,
313        })
314    }
315}
316
317impl Pseudonym {
318    /// Verify the pseudonym's correctness.
319    pub fn verify<H: Hasher>(
320        &self,
321        params: &Params<H>,
322        issuer_public_key: &IssuerPublicKey,
323        nonce: &[u8],
324    ) -> Choice {
325        let mut choice = Choice::from(1);
326
327        choice &= !self.a_prime.ct_eq(&G1Affine::identity());
328        choice &= Bls12::pairing(&self.a_prime, &issuer_public_key.w)
329            .ct_eq(&Bls12::pairing(&self.a_bar, &G2Affine::generator()));
330
331        let mut fiat_shamir = FiatShamir::<H>::new(PSEUDONYM_LABEL, nonce);
332
333        fiat_shamir.update(GroupEncoding::to_bytes(&self.a_prime).as_ref());
334        fiat_shamir.update(GroupEncoding::to_bytes(&self.b_bar).as_ref());
335        fiat_shamir.update(GroupEncoding::to_bytes(&self.a_bar).as_ref());
336
337        let a1 = self.a_prime * self.z_e + self.b_bar * self.z_r2 + self.a_bar * self.gamma.neg();
338        let a2 =
339            self.b_bar * self.z_r3 + params.h * self.z_k + G1Affine::generator() * self.gamma.neg();
340
341        fiat_shamir.update(GroupEncoding::to_bytes(&a1).as_ref());
342        fiat_shamir.update(GroupEncoding::to_bytes(&a2).as_ref());
343
344        fiat_shamir.update(GroupEncoding::to_bytes(&self.y).as_ref());
345
346        let y1 = self.y * self.z_k.neg()
347            + (G1Affine::generator() - self.y * self.relying_party_id) * self.gamma.neg();
348
349        fiat_shamir.update(GroupEncoding::to_bytes(&y1).as_ref());
350
351        let gamma = Scalar::random(fiat_shamir.rng());
352
353        choice &= gamma.ct_eq(&self.gamma);
354
355        choice
356    }
357
358    /// Return which relying party this pseudonym is meant for.
359    pub fn relying_party_id(&self) -> &Scalar {
360        &self.relying_party_id
361    }
362
363    /// Return the pseudonym ID which can be compared to other pseudonym IDs.
364    pub fn pseudonym_id(&self) -> &G1Affine {
365        &self.y
366    }
367}
368
369#[test]
370fn test() {
371    for _ in 0..10 {
372        use rand_core::OsRng;
373        use sha2::Sha256;
374        let params = Params::<Sha256>::default();
375        let issuer_private_key = IssuerPrivateKey::random(OsRng);
376        let client_private_key = ClientPrivateKey::random(OsRng);
377        let credreq = client_private_key.request(&params, OsRng);
378        let credresp = credreq
379            .respond(&issuer_private_key, &params, OsRng)
380            .unwrap();
381        let cred1 = client_private_key
382            .create_credential(&credreq, &credresp, &issuer_private_key.public())
383            .unwrap();
384        assert!(bool::from(
385            cred1.verify(&params, &issuer_private_key.public())
386        ));
387        let relying_party_id = Scalar::random(OsRng);
388        let pseudonym1 = cred1
389            .pseudonym_for(&params, relying_party_id, b"nonce", OsRng)
390            .unwrap();
391        assert!(bool::from(pseudonym1.verify(
392            &params,
393            &issuer_private_key.public(),
394            b"nonce"
395        )));
396        assert_eq!(pseudonym1.relying_party_id(), &relying_party_id);
397    }
398}