authenticated_pseudonyms/
unlinkable_pseudonym.rs1use 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#[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#[derive(Debug, Clone)]
72pub struct IssuerPrivateKey {
73 x: Scalar,
74}
75
76#[derive(Debug, Clone)]
78pub struct IssuerPublicKey {
79 w: G2Affine,
80}
81
82#[derive(Debug, Clone)]
84pub struct ClientPrivateKey {
85 k: Scalar,
86}
87
88#[derive(Debug, Clone)]
90pub struct CredentialRequest {
91 big_k: G1Affine,
92 gamma: Scalar,
95 k_bar: Scalar,
96}
97
98impl ClientPrivateKey {
99 pub fn random(mut rng: impl CryptoRngCore) -> Self {
101 ClientPrivateKey {
102 k: Scalar::random(&mut rng),
103 }
104 }
105
106 pub fn recover(k: Scalar) -> Self {
108 ClientPrivateKey { k }
109 }
110
111 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#[derive(Debug, Clone)]
138pub struct CredentialResponse {
139 a: G1Affine,
140 e: Scalar,
141}
142
143impl CredentialRequest {
144 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 pub fn random(mut rng: impl CryptoRngCore) -> Self {
175 IssuerPrivateKey {
176 x: Scalar::random(&mut rng),
177 }
178 }
179
180 pub fn public(&self) -> IssuerPublicKey {
182 IssuerPublicKey {
183 w: (G2Affine::generator() * self.x).into(),
184 }
185 }
186}
187
188#[derive(Debug, Clone)]
190pub struct Credential {
191 a: G1Affine,
192 e: Scalar,
193 k: Scalar,
194}
195
196impl ClientPrivateKey {
197 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 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#[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 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 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 pub fn relying_party_id(&self) -> &Scalar {
360 &self.relying_party_id
361 }
362
363 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(¶ms, OsRng);
378 let credresp = credreq
379 .respond(&issuer_private_key, ¶ms, 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(¶ms, &issuer_private_key.public())
386 ));
387 let relying_party_id = Scalar::random(OsRng);
388 let pseudonym1 = cred1
389 .pseudonym_for(¶ms, relying_party_id, b"nonce", OsRng)
390 .unwrap();
391 assert!(bool::from(pseudonym1.verify(
392 ¶ms,
393 &issuer_private_key.public(),
394 b"nonce"
395 )));
396 assert_eq!(pseudonym1.relying_party_id(), &relying_party_id);
397 }
398}