1use core::fmt::Debug;
2
3use curve25519_dalek::constants;
4use curve25519_dalek::ristretto::{CompressedRistretto, RistrettoPoint};
5use curve25519_dalek::scalar::Scalar;
6use digest::generic_array::typenum::U64;
7use digest::{Digest, KeyInit};
8use hmac::digest::generic_array::GenericArray;
9use hmac::Mac;
10use rand::{CryptoRng, Rng};
11use subtle::{Choice, ConstantTimeEq};
12use zeroize::Zeroize;
13
14use crate::errors::{InternalError, TokenError};
15
16pub const TOKEN_PREIMAGE_LENGTH: usize = 64;
18pub const TOKEN_LENGTH: usize = 96;
20pub const BLINDED_TOKEN_LENGTH: usize = 32;
22pub const PUBLIC_KEY_LENGTH: usize = 32;
24pub const SIGNING_KEY_LENGTH: usize = 32;
26pub const SIGNED_TOKEN_LENGTH: usize = 32;
28pub const UNBLINDED_TOKEN_LENGTH: usize = 96;
30pub const VERIFICATION_SIGNATURE_LENGTH: usize = 64;
32
33#[cfg_attr(not(feature = "cbindgen"), repr(C))]
38#[derive(Copy, Clone)]
39pub struct TokenPreimage([u8; TOKEN_PREIMAGE_LENGTH]);
40
41impl PartialEq for TokenPreimage {
42 fn eq(&self, other: &TokenPreimage) -> bool {
43 self.0[..] == other.0[..]
44 }
45}
46
47#[cfg(any(test, feature = "base64"))]
48impl_base64!(TokenPreimage);
49
50#[cfg(feature = "serde")]
51impl_serde!(TokenPreimage);
52
53impl Debug for TokenPreimage {
54 fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
55 write!(f, "TokenPreimage: {:?}", &self.0[..])
56 }
57}
58
59#[allow(non_snake_case)]
60impl TokenPreimage {
61 pub(crate) fn T(&self) -> RistrettoPoint {
62 RistrettoPoint::from_uniform_bytes(&self.0)
63 }
64
65 pub fn to_bytes(&self) -> [u8; TOKEN_PREIMAGE_LENGTH] {
67 self.0
68 }
69
70 fn bytes_length_error() -> TokenError {
71 TokenError(InternalError::BytesLengthError {
72 name: "TokenPreimage",
73 length: TOKEN_PREIMAGE_LENGTH,
74 })
75 }
76
77 pub fn from_bytes(bytes: &[u8]) -> Result<TokenPreimage, TokenError> {
79 if bytes.len() != TOKEN_PREIMAGE_LENGTH {
80 return Err(TokenPreimage::bytes_length_error());
81 }
82
83 let mut bits: [u8; TOKEN_PREIMAGE_LENGTH] = [0u8; TOKEN_PREIMAGE_LENGTH];
84 bits.copy_from_slice(bytes);
85 Ok(TokenPreimage(bits))
86 }
87}
88
89#[cfg_attr(not(feature = "cbindgen"), repr(C))]
94#[derive(Debug, Clone)]
95pub struct Token {
96 pub(crate) t: TokenPreimage,
98 r: Scalar,
100}
101
102impl Drop for Token {
104 fn drop(&mut self) {
105 self.r.zeroize();
106 }
107}
108
109#[cfg(any(test, feature = "base64"))]
110impl_base64!(Token);
111
112#[cfg(feature = "serde")]
113impl_serde!(Token);
114
115#[allow(non_snake_case)]
116impl Token {
117 pub fn random<D, T>(rng: &mut T) -> Self
119 where
120 D: Digest<OutputSize = U64> + Default,
121 T: Rng + CryptoRng,
122 {
123 let mut seed = [0u8; 64];
124 rng.fill(&mut seed);
125 let blinding_scalar = Scalar::random(rng);
126 Self::hash_from_bytes_with_blind::<D>(&seed, blinding_scalar)
127 }
128
129 pub(crate) fn hash_from_bytes_with_blind<D>(bytes: &[u8], blinding_scalar: Scalar) -> Self
131 where
132 D: Digest<OutputSize = U64> + Default,
133 {
134 let mut hash = D::default();
135 let mut seed = [0u8; 64];
136 hash.update(bytes);
137 seed.copy_from_slice(hash.finalize().as_slice());
138
139 Token {
140 t: TokenPreimage(seed),
141 r: blinding_scalar,
142 }
143 }
144
145 pub fn hash_from_bytes<D, T>(rng: &mut T, bytes: &[u8]) -> Self
147 where
148 D: Digest<OutputSize = U64> + Default,
149 T: Rng + CryptoRng,
150 {
151 let blinding_scalar = Scalar::random(rng);
152 Self::hash_from_bytes_with_blind::<D>(bytes, blinding_scalar)
153 }
154
155 pub fn blind(&self) -> BlindedToken {
157 BlindedToken((self.r * self.t.T()).compress())
158 }
159
160 pub(crate) fn unblind(&self, Q: &SignedToken) -> Result<UnblindedToken, TokenError> {
165 Ok(UnblindedToken {
166 t: self.t,
167 W: (self.r.invert()
168 * Q.0
169 .decompress()
170 .ok_or(TokenError(InternalError::PointDecompressionError))?)
171 .compress(),
172 })
173 }
174
175 pub fn to_bytes(&self) -> [u8; TOKEN_LENGTH] {
177 let mut token_bytes: [u8; TOKEN_LENGTH] = [0u8; TOKEN_LENGTH];
178
179 token_bytes[..TOKEN_PREIMAGE_LENGTH].copy_from_slice(&self.t.to_bytes());
180 token_bytes[TOKEN_PREIMAGE_LENGTH..].copy_from_slice(&self.r.to_bytes());
181 token_bytes
182 }
183
184 fn bytes_length_error() -> TokenError {
185 TokenError(InternalError::BytesLengthError {
186 name: "Token",
187 length: TOKEN_LENGTH,
188 })
189 }
190
191 pub fn from_bytes(bytes: &[u8]) -> Result<Token, TokenError> {
193 if bytes.len() != TOKEN_LENGTH {
194 return Err(Token::bytes_length_error());
195 }
196
197 let preimage = TokenPreimage::from_bytes(&bytes[..TOKEN_PREIMAGE_LENGTH])?;
198
199 let mut blinding_factor_bits: [u8; 32] = [0u8; 32];
200 blinding_factor_bits.copy_from_slice(&bytes[TOKEN_PREIMAGE_LENGTH..]);
201 let blinding_factor = Option::from(Scalar::from_canonical_bytes(blinding_factor_bits))
202 .ok_or(TokenError(InternalError::ScalarFormatError))?;
203
204 Ok(Token {
205 t: preimage,
206 r: blinding_factor,
207 })
208 }
209}
210
211#[cfg_attr(not(feature = "cbindgen"), repr(C))]
218#[derive(Copy, Clone, Debug)]
219pub struct BlindedToken(pub(crate) CompressedRistretto);
220
221#[cfg(any(test, feature = "base64"))]
222impl_base64!(BlindedToken);
223
224#[cfg(feature = "serde")]
225impl_serde!(BlindedToken);
226
227impl BlindedToken {
228 pub fn to_bytes(&self) -> [u8; BLINDED_TOKEN_LENGTH] {
230 self.0.to_bytes()
231 }
232
233 fn bytes_length_error() -> TokenError {
234 TokenError(InternalError::BytesLengthError {
235 name: "BlindedToken",
236 length: BLINDED_TOKEN_LENGTH,
237 })
238 }
239
240 pub fn from_bytes(bytes: &[u8]) -> Result<BlindedToken, TokenError> {
242 if bytes.len() != BLINDED_TOKEN_LENGTH {
243 return Err(BlindedToken::bytes_length_error());
244 }
245
246 let mut bits: [u8; 32] = [0u8; 32];
247 bits.copy_from_slice(&bytes[..32]);
248 Ok(BlindedToken(CompressedRistretto(bits)))
249 }
250}
251
252#[cfg_attr(not(feature = "cbindgen"), repr(C))]
256#[derive(Copy, Clone, Debug)]
257#[allow(non_snake_case)]
258pub struct PublicKey(pub(crate) CompressedRistretto);
259
260#[cfg(any(test, feature = "base64"))]
261impl_base64!(PublicKey);
262
263#[cfg(feature = "serde")]
264impl_serde!(PublicKey);
265
266impl PublicKey {
267 pub fn to_bytes(&self) -> [u8; PUBLIC_KEY_LENGTH] {
269 self.0.to_bytes()
270 }
271
272 fn bytes_length_error() -> TokenError {
273 TokenError(InternalError::BytesLengthError {
274 name: "PublicKey",
275 length: PUBLIC_KEY_LENGTH,
276 })
277 }
278
279 pub fn from_bytes(bytes: &[u8]) -> Result<PublicKey, TokenError> {
281 if bytes.len() != PUBLIC_KEY_LENGTH {
282 return Err(PublicKey::bytes_length_error());
283 }
284
285 let mut bits: [u8; 32] = [0u8; 32];
286 bits.copy_from_slice(&bytes[..32]);
287
288 Ok(PublicKey(CompressedRistretto(bits)))
289 }
290}
291
292#[cfg_attr(not(feature = "cbindgen"), repr(C))]
296#[derive(Debug, Clone)]
297pub struct SigningKey {
298 pub public_key: PublicKey,
300 pub(crate) k: Scalar,
302}
303
304#[cfg(any(test, feature = "base64"))]
305impl_base64!(SigningKey);
306
307#[cfg(feature = "serde")]
308impl_serde!(SigningKey);
309
310impl Drop for SigningKey {
312 fn drop(&mut self) {
313 self.k.zeroize();
314 }
315}
316
317#[allow(non_snake_case)]
318impl SigningKey {
319 pub fn random<T: Rng + CryptoRng>(rng: &mut T) -> Self {
321 let k = Scalar::random(rng);
322 let Y = k * constants::RISTRETTO_BASEPOINT_POINT;
323 SigningKey {
324 k,
325 public_key: PublicKey(Y.compress()),
326 }
327 }
328
329 pub fn sign(&self, P: &BlindedToken) -> Result<SignedToken, TokenError> {
333 Ok(SignedToken(
334 (self.k
335 * P.0
336 .decompress()
337 .ok_or(TokenError(InternalError::PointDecompressionError))?)
338 .compress(),
339 ))
340 }
341
342 pub fn rederive_unblinded_token(&self, t: &TokenPreimage) -> UnblindedToken {
346 UnblindedToken {
347 t: *t,
348 W: (self.k * t.T()).compress(),
349 }
350 }
351
352 pub fn to_bytes(&self) -> [u8; SIGNING_KEY_LENGTH] {
354 self.k.to_bytes()
355 }
356
357 fn bytes_length_error() -> TokenError {
358 TokenError(InternalError::BytesLengthError {
359 name: "SigningKey",
360 length: SIGNING_KEY_LENGTH,
361 })
362 }
363
364 pub fn from_bytes(bytes: &[u8]) -> Result<SigningKey, TokenError> {
366 if bytes.len() != SIGNING_KEY_LENGTH {
367 return Err(SigningKey::bytes_length_error());
368 }
369
370 let mut bits: [u8; 32] = [0u8; 32];
371 bits.copy_from_slice(&bytes[..32]);
372 let k = Option::from(Scalar::from_canonical_bytes(bits))
373 .ok_or(TokenError(InternalError::ScalarFormatError))?;
374
375 let Y: RistrettoPoint = k * constants::RISTRETTO_BASEPOINT_POINT;
376
377 Ok(SigningKey {
378 public_key: PublicKey(Y.compress()),
379 k,
380 })
381 }
382}
383
384#[cfg_attr(not(feature = "cbindgen"), repr(C))]
388#[derive(Copy, Clone, Debug)]
389pub struct SignedToken(pub(crate) CompressedRistretto);
390
391#[cfg(any(test, feature = "base64"))]
392impl_base64!(SignedToken);
393
394#[cfg(feature = "serde")]
395impl_serde!(SignedToken);
396
397impl SignedToken {
398 pub fn to_bytes(&self) -> [u8; SIGNED_TOKEN_LENGTH] {
400 self.0.to_bytes()
401 }
402
403 fn bytes_length_error() -> TokenError {
404 TokenError(InternalError::BytesLengthError {
405 name: "SignedToken",
406 length: SIGNED_TOKEN_LENGTH,
407 })
408 }
409
410 pub fn from_bytes(bytes: &[u8]) -> Result<SignedToken, TokenError> {
412 if bytes.len() != SIGNED_TOKEN_LENGTH {
413 return Err(SignedToken::bytes_length_error());
414 }
415
416 let mut bits: [u8; 32] = [0u8; 32];
417 bits.copy_from_slice(&bytes[..32]);
418 Ok(SignedToken(CompressedRistretto(bits)))
419 }
420}
421
422#[cfg_attr(not(feature = "cbindgen"), repr(C))]
427#[allow(non_snake_case)]
428#[derive(Debug, Clone)]
429pub struct UnblindedToken {
430 pub t: TokenPreimage,
432 W: CompressedRistretto,
436}
437
438#[cfg(any(test, feature = "base64"))]
439impl_base64!(UnblindedToken);
440
441#[cfg(feature = "serde")]
442impl_serde!(UnblindedToken);
443
444impl UnblindedToken {
445 pub fn derive_verification_key<D>(&self) -> VerificationKey
447 where
448 D: Digest<OutputSize = U64> + Default,
449 {
450 let mut hash = D::default();
451 hash.update(b"hash_derive_key");
452
453 hash.update(self.t.0.as_ref());
454 hash.update(self.W.as_bytes());
455
456 let output = hash.finalize();
457 let mut output_bytes = [0u8; 64];
458 output_bytes.copy_from_slice(output.as_slice());
459
460 VerificationKey(output_bytes)
461 }
462
463 pub fn to_bytes(&self) -> [u8; UNBLINDED_TOKEN_LENGTH] {
465 let mut unblinded_token_bytes: [u8; UNBLINDED_TOKEN_LENGTH] = [0u8; UNBLINDED_TOKEN_LENGTH];
466
467 unblinded_token_bytes[..TOKEN_PREIMAGE_LENGTH].copy_from_slice(&self.t.to_bytes());
468 unblinded_token_bytes[TOKEN_PREIMAGE_LENGTH..].copy_from_slice(&self.W.to_bytes());
469 unblinded_token_bytes
470 }
471
472 fn bytes_length_error() -> TokenError {
473 TokenError(InternalError::BytesLengthError {
474 name: "UnblindedToken",
475 length: UNBLINDED_TOKEN_LENGTH,
476 })
477 }
478
479 pub fn from_bytes(bytes: &[u8]) -> Result<UnblindedToken, TokenError> {
481 if bytes.len() != UNBLINDED_TOKEN_LENGTH {
482 return Err(UnblindedToken::bytes_length_error());
483 }
484
485 let preimage = TokenPreimage::from_bytes(&bytes[..TOKEN_PREIMAGE_LENGTH])?;
486
487 let mut w_bits: [u8; 32] = [0u8; 32];
488 w_bits.copy_from_slice(&bytes[TOKEN_PREIMAGE_LENGTH..]);
489 Ok(UnblindedToken {
490 t: preimage,
491 W: CompressedRistretto(w_bits),
492 })
493 }
494}
495
496#[cfg_attr(not(feature = "cbindgen"), repr(C))]
500#[derive(Clone)]
501pub struct VerificationKey([u8; 64]);
502
503impl Debug for VerificationKey {
504 fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
505 write!(f, "VerificationKey: {:?}", &self.0[..])
506 }
507}
508
509impl VerificationKey {
510 pub fn sign<D>(&self, message: &[u8]) -> VerificationSignature
512 where
513 D: Mac<OutputSize = U64> + KeyInit,
514 {
515 let mut mac = <D as Mac>::new_from_slice(self.0.as_ref()).unwrap();
516 mac.update(message);
517
518 VerificationSignature(mac.finalize().into_bytes())
519 }
520
521 pub fn verify<D>(&self, sig: &VerificationSignature, message: &[u8]) -> bool
524 where
525 D: Mac<OutputSize = U64> + KeyInit,
526 {
527 &self.sign::<D>(message) == sig
528 }
529}
530
531#[cfg_attr(not(feature = "cbindgen"), repr(C))]
533#[derive(Clone)]
534pub struct VerificationSignature(GenericArray<u8, U64>);
535
536#[cfg(any(test, feature = "base64"))]
537impl_base64!(VerificationSignature);
538
539#[cfg(feature = "serde")]
540impl_serde!(VerificationSignature);
541
542impl ConstantTimeEq for VerificationSignature {
543 fn ct_eq(&self, other: &VerificationSignature) -> Choice {
544 self.0.ct_eq(&other.0)
545 }
546}
547impl PartialEq for VerificationSignature {
548 fn eq(&self, other: &VerificationSignature) -> bool {
549 self.ct_eq(other).unwrap_u8() == 1
550 }
551}
552
553#[cfg(any(test, feature = "serde", feature = "base64"))]
554impl VerificationSignature {
555 fn to_bytes(&self) -> [u8; VERIFICATION_SIGNATURE_LENGTH] {
558 let mut bytes: [u8; VERIFICATION_SIGNATURE_LENGTH] = [0u8; VERIFICATION_SIGNATURE_LENGTH];
559 bytes.copy_from_slice(self.0.as_slice());
560 bytes
561 }
562
563 fn bytes_length_error() -> TokenError {
564 TokenError(InternalError::BytesLengthError {
565 name: "VerificationSignature",
566 length: VERIFICATION_SIGNATURE_LENGTH,
567 })
568 }
569
570 fn from_bytes(bytes: &[u8]) -> Result<VerificationSignature, TokenError> {
572 if bytes.len() != VERIFICATION_SIGNATURE_LENGTH {
573 return Err(VerificationSignature::bytes_length_error());
574 }
575
576 let arr: &GenericArray<u8, U64> = GenericArray::from_slice(bytes);
577 Ok(VerificationSignature(*arr))
578 }
579}
580
581#[cfg(test)]
582mod tests {
583 use base64::{engine::Engine as _, prelude::BASE64_STANDARD};
584 use hmac::Hmac;
585 use rand::rngs::OsRng;
586 use sha2::Sha512;
587
588 use super::*;
589 type HmacSha512 = Hmac<Sha512>;
590
591 #[allow(non_snake_case)]
592 #[test]
593 fn vector_tests() {
594 let vectors = [
596 ("SlPD+7xZlw7l+Fr4E4dd/8E6kEouU65+ZfoN6m5iyQE=", "nGajOcg0T5IvwyBstdroFKWUwBd90yNcJU2cQJpluAg=", "nwfnvlVROHqYupd8cy0IDcsPKaBI42VpEsZTPjLueu0ptyF2nOZOQ9VxM7B02DnVMe0fKFEK+0Ws4QofS3lNbw==", "rBKvQjzCywrH+WAHjvVpB4P59cy1A0CCcYjeUioWdA0=", "iKt8hXS7Zyqy5/xbbknh/CuCmQM+Cti6uOibdKZBlEM=", "OFccZ1mrx9SSrRSoj95nEVmkbMAdggfpj6haKO0BrQQ=", "JFJyI4tUdjjtud9a4qZalp5i9QY4I0x/VhChVu4P714="),
597 ("7oD3U1ZwWQN/2eZhiXfHtnwmhR+yl3P7Gta+T123awI=", "vtiIh6vgqE9kaR/gvfo9rxps1pehPweuB1iJEM45ySc=", "5aaIdCtHxa37WdTfdv0dseUe4Dscqfgqyhc+24tyk0dOvpgPkE0QyRZEK0eDoOmEhgy2yVeznDjtj1HP+qaKTQ==", "yhpWSFSxFQRlZH9QtcmCrL1p27dYMEKs+sub7hfVbA8=", "qDUfb1GhqEsJg2MEo0jI5fUDsKitwSSkV6kaF3wWHBU=", "goTV+GGlPyIodeEfRu62nWVJFpj3lXMjZY6w4ABaolc=", "sgJfuuExkd+VoIXOr9gv+M7VlRnjnUtveVzWcOY6YzM="),
598 ("tviSLm/W8oFds67y9lMs990fjh08hQNV17/4V2bmOQY=", "5ufRlCvVKvXp1yuxxS7Jvw9LSwQUl6Q/MlT6HY2l1Hc=", "7aq3TaFBD8BV6gaMmekmCsvjN89dPgDlsyMP/tsLmQeH0McOC/5BmOpnWN1aYftf7C35gfMb7+FT+B0XFheE2w==", "Ge3prZ2jJSoh1A3ZvrSfaSA1kDziGW2I+Gmh6jniaAs=", "pOTANELrS8oor67hIYyCvrbmlMrn6Fr+04nBmFgrvxU=", "JjbfZ+UifRtHLdxcvVdI6C2SXYls9aWS0UyS6vyfCAY=", "Ki8Vb+Fm7qqeeL63/Oco95UaMOO62bRGq2fMTz5xPU8="),
599 ("2srhyAUoqF+s2y+NfSXluDXVxC7JBgiD2ttOSXBYPww=", "HhdUX1s812RxwznoreV7i/BHvj2Xy9tJo24GxNDmtF8=", "bQqtuvgVfQYqyyQYwXakdVEbNcP2IYpWaOpbxvVLh4L4s0DwCsG+ul5izWMqfOeAnHJWCKyl7798QfI3ZD8GwA==", "gfes2hjQSpt6QOBJnz4t/N/utBkdDS+W4GRQIYjb/wQ=", "ZnaqI3kpS5nh9B3jw6uOeUld//Q2+olaAlimWRFvcDE=", "0pISjRRLsiYWLzqiukHe1xkIEuDmibUcg9m/5zcPwSo=", "2lSAwKDv4mdzZuMSEEngBXSQBJRJoprzqKMtX9Bi/zs="),
600 ("pfwD6XL8PnQfJPuzg/LKKVf3QRLc4ZclvC+6GBBETgo=", "Vv9rfOewgYx7jHMyfAZfBFEBt5IuwIKwz2NbDpra+UM=", "FtP6gVFikwP+l0FWLtBl2068AFDvVYNkroESSij1wBMTIy+8SW39iiVoZXtobXIUOCoaAJAwF92paYeEQrpa/w==", "xrF8ZzEtEsGbRfJGjULVkNisgpaAO69AHZKYhyQmng4=", "rHUztBaoMDDVwOTnOxFQgEOEeG6lKBL7Tb+fluztCxo=", "dhQv3WoCFW+EcV1dpCARarugjhU/enn0UlamXDPoFxc=", "GgWeq8r+ZRsPJa50bP7y3kVAq7yBSSN8eM0oOn/U2CM="),
601 ("xNWxYjBW6Sty2Yy33S38IPkX6v4zAwK10Ge/WPxVVwU=", "GrqZp9KBIAi1mExq2VUhG8lIuNO/J9Ap4xATdJ5TfmM=", "xP/wuGwC7WYtLSUiZHofCaey+e6lbn4gsfBszdnOw347LSCBLfNpl4Y2wiZGYDZ1gEEEdF0pl+KN9dgkKq0ZyA==", "RclUsYJkWG7Uw0tM/rn+nyvDey/Ibwl7WkmmvkIPWwc=", "fiD5jlU2Bhu+chVrhWKvZTaVJnbmBTpDcfEAH9Kcjg4=", "mCFMTFdnxZ/gvTnVNGMmZkXWqlnnZH3JnwDKhTBT3x0=", "krmSP8LTAA0O35g7uk+o7MMYTl2qACiWu+CDZXtQJSo="),
602 ("lE2Tu2LzhNgU77KnsEFbqVYOc5wsbMYYzBQOcpi32Ag=", "EuJk2I4y6ZrbIn04deR+lzJS1xrBIpN+RthbPknv+gA=", "DPw4xN363pkKIT0gj794mDNPTe7X5YMP0mZ1ExVDWuGbuU9NPckmUvJD3R+W81latHPSNW2PqPbWTMT2SxLKmQ==", "Dbpt5WypybRRaQiInYndWLf/O2ewT3IEYOOdxKywNg8=", "QJH62wtVRKX0Eq1GTWVyAVuML0mpEl18VvxFn3TvTDI=", "TMRELsL1kWbyRNLQhWuIyU2j7M2FJk0trp+uR1w4hHw=", "nDiCPlbQ6HfR3MX9jRqY3id4DWo3GaZ1FvUfkmAjWyg="),
603 ("Tmfvkm6Kvi/BWAvqNsGsdQzJTI9tkGa4Sr0dNPTMCwc=", "iPXJcLCmT9SYYVYVfNx6br3VG1rHHF2hIrD2cVx8YGE=", "BdOrSDfezktj/f1d0HordqjIJWbfGiFn2uXhJbaqDna52ZyoRmT1Du6lTkSfDlhwORN/V1Q9iSBUgxQckzFmtg==", "0web3hpMwnqhIhu9mjAKNLfmFdJUfY3pu4clcs0G8AQ=", "Qll/1fI1hlcc3Dm6xtfo3LlJwu6fgoffCZ3VzzQvCAs=", "KNbnK4jL8SThHByYWWrzdpZHxvrTVid7aBYHnZD2BW0=", "Lti4tRDBQzwNIiTbGpPVVViHMvCEfC7ov2Ne3LrQdRg="),
604 ("N8oRiMuSrYdp9TMKp++AP8ridXqdX6BoPOucx2eRCQE=", "mnikks9ySHzZGMgoPZ0SRA8/JJkMh5aA+m3eqeMfqTE=", "9sNH3G618rH0vy3TKBMNRQDKOb66LUKBo9jOtMsezeN4sgAp+2pMVDMS5BATkVxXAW5dpoGUTMJ3+cfnX0plSg==", "f44zH9r/YnCyaHZnKtEc/68diotEo1GjQ5MWepNEXAk=", "EEH0FTbmxN5XoXnAHmIH0y4VjcixJ5U9T8WqXgP2IAg=", "Km0KASMeIqj0s5vswz+WEYptTx2Y0fOb9cVjb+UKexw=", "lNDdKND+R/JmDrM08Q7w7ePoXT7/hgzGU6xVBU5RFig="),
605 ("Nye8fMOQJv1HjCY6qxG0Br661wjd8OwNI1O0ZbkmGAc=", "5szoRS3/9jdVTmhswiS9yyaLeC2I0CfBAUzfe0zGjz8=", "OkOqxU+boJmNIhmzusoRGUDVJLfPlGd9bFV3UPpNueEHfu21um4zwQSuJUQ8hr8VgzU63fb93Rmk/0kRiOPUhw==", "ZBztTnJvQKmPkxfgzGzufhRa6o4oUPublpOIhODHKA4=", "lD1eLLmRw7ebLOd51OQSps51cZGTIg2DM+GL38bQQww=", "qA27hu9S60UX0jfnWJQgUBllQvfOPu+jQVkphi6Sv24=", "HhPZFQiNAYzG+niNmUiWut2g/YMhox86h1XyZypQfVk="),
606 ];
607 for (k, Y, seed, r, P, Q, W) in vectors {
608 let server_key = SigningKey::decode_base64(k).unwrap();
609 let seed = BASE64_STANDARD.decode(seed).unwrap();
610
611 assert!(server_key.public_key.encode_base64() == Y);
612
613 let r_bytes = BASE64_STANDARD.decode(r).unwrap();
614 let mut r_bits: [u8; 32] = [0u8; 32];
615 r_bits.copy_from_slice(&r_bytes);
616 let r = Scalar::from_canonical_bytes(r_bits).unwrap();
617
618 let token = Token::hash_from_bytes_with_blind::<Sha512>(&seed, r);
619
620 let blinded_token = token.blind();
621
622 assert!(blinded_token.encode_base64() == P);
623
624 let signed_token = server_key.sign(&blinded_token).unwrap();
625
626 assert!(signed_token.encode_base64() == Q);
627
628 let unblinded_token = token.unblind(&signed_token).unwrap();
629
630 let W_bytes = BASE64_STANDARD.decode(W).unwrap();
631 let mut W_bits: [u8; 32] = [0u8; 32];
632 W_bits.copy_from_slice(&W_bytes[..32]);
633 let W = CompressedRistretto(W_bits);
634
635 let unblinded_token_expected = UnblindedToken { W, t: token.t };
636 assert!(unblinded_token.encode_base64() == unblinded_token_expected.encode_base64());
637 }
638 }
639
640 #[test]
641 fn works() {
642 let mut rng = OsRng;
643
644 let server_key = SigningKey::random(&mut rng);
647
648 let token = Token::random::<Sha512, _>(&mut rng);
652 let blinded_token = token.blind();
654
655 let signed_token = server_key.sign(&blinded_token).unwrap();
657
658 let unblinded_token = token.unblind(&signed_token).unwrap();
660
661 let client_verification_key = unblinded_token.derive_verification_key::<Sha512>();
665 let client_sig = client_verification_key.sign::<HmacSha512>(b"test message");
667
668 let server_unblinded_token = server_key.rederive_unblinded_token(&unblinded_token.t);
672 let server_verification_key = server_unblinded_token.derive_verification_key::<Sha512>();
674 let server_sig = server_verification_key.sign::<HmacSha512>(b"test message");
676
677 assert!(client_sig == server_sig);
679
680 let server_sig_fail = server_verification_key.sign::<HmacSha512>(b"failing test message");
682 assert!(!(client_sig == server_sig_fail));
683 }
684}