raffle/lib.rs
1//! A non-cryptographic "vouching" system.
2//!
3//! The `raffle` library offers functionality similar to public key
4//! signatures, except without any pretense of cryptographic strength.
5//! It lets us pass [`CheckingParameters`] to modules so that they can
6//! check whether a value looks like it was generated by code with
7//! access to the corresponding [`VouchingParameters`], while making
8//! it implausibly hard for these checking modules to *accidentally*
9//! generate valid [`Voucher`]s for arbitrary values.
10//!
11//! Internally, the implementation relies on modular multiplicative
12//! inverses, with added asymmetry to catch some misuse (e.g., swapped
13//! vouching and checking parameters). Both the vouching function and
14//! the checking function are add-multiply (mod 2**64) permutations;
15//! for a matching pair of vouching and checking functions,
16//! `check(vouch(x)) = K - x`, where `K` is
17//! `u64::from_le_bytes(*b"Vouch!OK")`.
18//!
19//! It's easy to write code that'll find the vouching parameters from
20//! a set of checking parameters, but the library doesn't have that
21//! functionality, not even in the vouching-to-checking direction.
22//! The internal parameter generation code in `generate.rs` accepts
23//! the multiplier for the vouching function and the addend for the
24//! checking function, and fills in the blanks for both the vouching
25//! and the checking parameters. Any code to extract vouching
26//! parameters from checking parameters will hopefully stick out in
27//! review, if only because of the large integer constants involved.
28//!
29//! The internal modular add-multiply transformation gives us decent
30//! bounds on collisions: each instance of the [`VouchingParameters`]
31//! function is a permutation over the [`u64`]s, so we'll never accept a
32//! [`Voucher`] generated from the correct [`VouchingParameters`]
33//! (i.e., whence our [`CheckingParameters`] came) but for the wrong
34//! [`u64`] value. Otherwise, if two [`VouchingParameters`] differ,
35//! they're both affine functions, and thus match for at most one
36//! input-output pair... or we could have accidentally generated the
37//! same parameters independently, but the parameter space is pretty
38//! large (more than 125 bits). All in all, the probability that we'll
39//! accept a [`Voucher`] generated from the wrong
40//! [`VouchingParameters`] is less than `2**-60`, assuming the
41//! parameters were generated randomly and independently of the
42//! vouched value.
43//!
44//! That's far from cryptographic levels of difficulty, but rare
45//! enough that any erroneous match is much more likely a sign of
46//! hardware failure or deliberate action than just bad luck.
47//!
48//! # Sample usage
49//!
50//! First, generate a [`Voucher`]
51//!
52//! ```rust, ignore
53//! const VOUCHING_PARAMETERS : VouchingParameters = VouchingParameters::parse_or_die("VOUCH-...");
54//! let value = ...;
55//! let voucher = VOUCHING_PARAMETERS.vouch(digest(&value));
56//! // pass around (voucher, value)
57//! ```
58//!
59//! and, on the read side, validate a [`Voucher`] with
60//!
61//! ```rust, ignore
62//! const CHECKING_PARAMETERS : CheckingParameters = CheckingParameters::parse_or_die("CHECK-...");
63//! let (voucher, value) = ...;
64//! if CHECKING_PARAMETERS.check(digest(&value), voucher) {
65//! // work with a validated `value`.
66//! }
67//! ```
68//!
69//! Validation only tells us that the value was most likely generated
70//! by a module with access to the `VOUCHING_PARAMETERS` that
71//! correspond to `CHECKING_PARAMETERS`. The [`Voucher`] could well
72//! have been generated for a different message or recipient.
73//!
74//! We're not looking for cryptographic guarantees here, and only want
75//! to make it hard for code to accidentally generate valid vouchers
76//! when that code should only be able to check them for validity. In
77//! most cases, it's probably good enough to generate fresh parameters
78//! at runtime, or to hardcode the parameters in the source, with
79//! strings generated by `examples/generate_raffle_parameters`.
80//!
81//! If you need more than that... it's always possible to load
82//! parameters at runtime, but maybe you want real signatures.
83//!
84//! # Generating parameters
85//!
86//! When everything stays in the same process, it's probably good
87//! enough to generate [`VouchingParameters`] by passing a (P)RNG to
88//! [`VouchingParameters::generate`].
89//!
90//! ```
91//! # use raffle::VouchingParameters;
92//! use rand::Rng;
93//! #[derive(Debug)]
94//! enum Never {}
95//!
96//! let mut rng = rand::rngs::OsRng {};
97//! VouchingParameters::generate(|| Ok::<u64, Never>(rng.gen())).unwrap();
98//! ```
99//!
100//! Otherwise, you can generate parameter strings with the `generate_raffle_parameters` binary:
101//!
102//! ```sh
103//! $ cargo build --examples
104//! Finished dev [unoptimized + debuginfo] target(s) in 0.00s
105//! $ target/debug/examples/generate_raffle_parameters
106//! VOUCH-ecf8c191680e5394-a0474d8e2618d059-9bf723a6b538fe4a-1dddb95caa81d852
107//! CHECK-9bf723a6b538fe4a-1dddb95caa81d852
108//! ```
109//!
110//! The first line is the string representation for a set of
111//! [`VouchingParameters`], suitable for
112//! [`VouchingParameters::parse`], or, more ergonomically for `const`
113//! values, [`VouchingParameters::parse_or_die`]. The second line is
114//! the string representation for the corresponding
115//! [`CheckingParameters`], suitable for [`CheckingParameters::parse`]
116//! or [`CheckingParameters::parse_or_die`].
117//!
118//! Each invocation of `generate_raffle_parameters` with no argument
119//! gets fresh random bits from the operating system, and is thus
120//! expected to generate different parameters. When there are command
121//! line arguments, the parameters are instead derived
122//! deterministically from these arguments, with [BLAKE3](https://docs.rs/blake3/latest/blake3/):
123//!
124//! ```sh
125//! $ target/debug/examples/generate_raffle_parameters test seed
126//! VOUCH-13df39ed9cd4e2c9-97b5007485c16f9b-76d12fb42cb03d2d-2952336c44217bb8
127//! CHECK-76d12fb42cb03d2d-2952336c44217bb8
128//! $ target/debug/examples/generate_raffle_parameters test seed
129//! VOUCH-13df39ed9cd4e2c9-97b5007485c16f9b-76d12fb42cb03d2d-2952336c44217bb8
130//! CHECK-76d12fb42cb03d2d-2952336c44217bb8
131//! ```
132//!
133//! The parameter strings always have the same fixed-width format, so should
134//! be easy to `grep` for. The `VOUCH`ing parameters also include the `CHECK`ing
135//! parameters as a suffix, so we can `grep` for the hex digits to find matching pairs.
136mod check;
137mod constparse;
138mod generate;
139mod vouch;
140
141/// A [`Voucher`] is a very weakly one-way-transformed value for an arbitrary [`u64`].
142///
143/// [`CheckingParameters`] let us confirm whether the voucher came
144/// from an expected u64 value after transformation by the
145/// [`VouchingParameters`] associated with the [`CheckingParameters`],
146/// while making it hard to accidentally generate a valid [`Voucher`]
147/// with only [`CheckingParameters`] (i.e., it's hard to accidentally
148/// back out the [`VouchingParameters`] from [`CheckingParameters`]).
149///
150/// The type wrapper exists to prevent confusion between what's
151/// a [`Voucher`] value what's an expected [`u64`] value.
152///
153/// Generate [`Voucher`]s with [`VouchingParameters::vouch`] or
154/// [`VouchingParameters::vouch_many`], by deserialising directly into
155/// [`Voucher`] objects, or with [`std::mem::transmute`]. The latter is
156/// `unsafe`, and that's on purpose: code that takes arbitrary [`u64`]s
157/// and stamps them as [`Voucher`] values should be scrutinised.
158///
159/// Confirm that a [`Voucher`] is valid for a given set of [`CheckingParameters`]
160/// and initial [`u64`] (i.e., was generated from the initial [`u64`] using
161/// the [`VouchingParameters`] associated with the [`CheckingParameters`])
162/// by calling [`CheckingParameters::check`] or [`CheckingParameters::check_many`].
163#[derive(Clone, Copy, Eq, PartialEq, Hash)]
164#[cfg_attr(not(feature = "prost"), derive(Debug))] // prost::Message derives `Debug`
165#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
166#[cfg_attr(feature = "prost", derive(prost::Message))]
167#[repr(transparent)]
168pub struct Voucher(#[cfg_attr(feature = "prost", prost(fixed64, tag = "1"))] u64);
169
170/// [`CheckingParameters`] carry enough information to confirm whether a
171/// [`Voucher`] was generated from a given [`u64`] value using the unknown
172/// [`VouchingParameters`] associated with the [`CheckingParameters`].
173///
174/// [`CheckingParameters`] should be obtained either by calling
175/// [`VouchingParameters::checking_parameters`] for transient
176/// in-memory parameters, by parsing a string representation with
177/// [`CheckingParameters::parse`] or
178/// [`CheckingParameters::parse_bytes`], or, when initialising `const`
179/// variables with hardcoded strings, with
180/// [`CheckingParameters::parse_or_die`].
181///
182/// After that, the result of [`VouchingParameters::vouch`] can be checked
183/// with [`CheckingParameters::check`], and that of [`VouchingParameters::vouch_many`]
184/// with [`CheckingParameters::check_many`].
185#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
186pub struct CheckingParameters {
187 unoffset: u64,
188 unscale: u64,
189}
190
191/// [`VouchingParameters`] let us convert an arbitrary [`u64`] value
192/// to a [`Voucher`] that can later be checked as matching the initial
193/// [`u64`] value, given only the [`CheckingParameters`] associated
194/// with the [`VouchingParameters`].
195///
196/// [`VouchingParameters`] should be constructed with
197/// [`VouchingParameters::generate`] for transient in-memory
198/// parameters, by parsing a string representation, with
199/// [`VouchingParameters::parse`] or
200/// [`VouchingParameters::parse_bytes`], or, when initialising `const`
201/// variables with hardocded strings, with
202/// [`VouchingParameters::parse_or_die`].
203///
204/// After that, we can convert a [`u64`] to a [`Voucher`] with
205/// [`VouchingParameters::vouch`]. Readers can confirm that the [`Voucher`]
206/// matches the initial `[u64`] once they have the corresponding
207/// [`VouchingParameters::checking_parameters`], by calling [`CheckingParameters::check`]
208/// with the expected [`u64`] and the [`Voucher`].
209///
210/// Vouching for a batch of [`u64`] values should instead use
211/// [`VouchingParameters::vouch_many`] and
212/// [`CheckingParameters::check_many`]: the vouching transformation
213/// varies for each index, making it harder to accidentally accept
214/// permuted [`u64`] values and [`Voucher`]s.
215#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
216pub struct VouchingParameters {
217 offset: u64,
218 scale: u64,
219 checking: CheckingParameters,
220}
221
222impl CheckingParameters {
223 /// Attempts to parse the string representation of a [`CheckingParameters`] instance.
224 ///
225 /// This representation can be generated by the [`std::fmt::Display`] trait, e.g.,
226 /// with `format!("{}", checking_params) => "CHECK-9bf723a6b538fe4a-1dddb95caa81d852"`.
227 #[inline(always)]
228 pub const fn parse(string: &str) -> Result<CheckingParameters, &'static str> {
229 Self::parse_bytes(string.as_bytes())
230 }
231
232 /// Parses the string representation of a [`CheckingParameters`] object
233 /// or panics.
234 ///
235 /// This function is mostly useful to initialise `const` literals.
236 #[inline(never)]
237 pub const fn parse_or_die(string: &str) -> CheckingParameters {
238 match Self::parse(string) {
239 Ok(ret) => ret,
240 Err(_) => panic!("failed to parse checking parameter string."),
241 }
242 }
243
244 /// Returns whether the `expected` value matches the `voucher`,
245 /// assuming the voucher was generated with the [`VouchingParameters`] from
246 /// which the self [`CheckingParameters`] came.
247 ///
248 /// If the `voucher` was generated from different parameters
249 /// (generated independently and uniformly at random), the
250 /// probability of a match is less than `2**-60`.
251 #[must_use]
252 #[inline(always)]
253 pub const fn check(self, expected: u64, voucher: Voucher) -> bool {
254 check::check(self.unoffset, self.unscale, expected, voucher.0)
255 }
256
257 /// Returns whether the `expected` values match all the
258 /// `voucher`s, assuming the vouchers were generated with the
259 /// [`VouchingParameters`] from which the self
260 /// [`CheckingParameters`] came.
261 ///
262 /// If the `voucher`s were generated from different parameters
263 /// (generated independently and uniformly at random), the
264 /// probability of a match is less than `2**-60`.
265 #[must_use]
266 pub fn check_many(self, expected: &[u64], vouchers: &[Voucher]) -> bool {
267 if expected.len() != vouchers.len() {
268 return false;
269 }
270
271 std::iter::zip(expected.iter(), vouchers.iter())
272 .enumerate()
273 .all(|(idx, (expected, voucher))| {
274 let input_rot = (idx % 64) as u32;
275 let voucher_rot = (idx % 63) as u32;
276
277 self.check(
278 expected.rotate_right(input_rot),
279 Voucher(voucher.0.rotate_right(voucher_rot)),
280 )
281 })
282 }
283
284 /// Number of ASCII characters in the string representation for
285 /// one [`CheckingParameters`] instance.
286 pub const REPRESENTATION_BYTE_COUNT: usize = 39;
287
288 /// Attempts to parse `bytes`, which must be the utf-8 (it's all
289 /// ASCII) representation of a serialised [`CheckingParameters`],
290 /// with a length of exactly `REPRESENTATION_BYTE_COUNT` bytes.
291 ///
292 /// Returns the [`CheckingParameters`] on success, and an error
293 /// reason on failure.
294 #[inline(never)]
295 pub const fn parse_bytes(bytes: &[u8]) -> Result<CheckingParameters, &'static str> {
296 #[allow(clippy::assertions_on_constants)]
297 const _: () = assert!(
298 CheckingParameters::REPRESENTATION_BYTE_COUNT == check::REPRESENTATION_BYTE_COUNT
299 );
300
301 match check::parse_bytes(bytes) {
302 Err(e) => Err(e),
303 Ok((unoffset, unscale)) => Ok(CheckingParameters { unoffset, unscale }),
304 }
305 }
306}
307
308impl std::fmt::Display for CheckingParameters {
309 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
310 write!(f, "CHECK-{:016x}-{:016x}", self.unoffset, self.unscale)
311 }
312}
313
314impl VouchingParameters {
315 /// Attempts to generate a fresh set of [`VouchingParameters`] by
316 /// repeatedly calling `generator` to get [`u64`] values.
317 ///
318 /// The `generator` should yield (pseudo)random [`u64`] values
319 /// sampled uniformly from the full 64-bit range. This function
320 /// may loop forever when the `generator` is of very low quality.
321 ///
322 /// Returns a fresh [`VouchingParameters`] instance on success,
323 /// and bubbles any error from `generator` on failure.
324 pub fn generate<Err>(
325 mut generator: impl FnMut() -> Result<u64, Err>,
326 ) -> Result<VouchingParameters, Err> {
327 fn gen64<Err>(mut generator: impl FnMut() -> Result<u64, Err>) -> Result<u64, Err> {
328 loop {
329 let ret = generator()?;
330 // Avoid trivial values.
331 if ret > 10 && !ret > 10 && ret.count_ones() > 2 && ret.count_zeros() > 2 {
332 return Ok(ret);
333 }
334 }
335 }
336
337 // `generate:;derive_parameters` has an internal `assert!` check for validity.
338 let (offset, scale, (unoffset, unscale)) =
339 generate::derive_parameters(gen64(&mut generator)?, gen64(&mut generator)?);
340 Ok(VouchingParameters {
341 offset,
342 scale,
343 checking: CheckingParameters { unoffset, unscale },
344 })
345 }
346
347 /// Attempts to parse the string representation of [`VouchingParameters`].
348 ///
349 /// This representation can be generated by the [`std::fmt::Display`] trait,
350 /// e.g., with `format!("{}", vouching_params) => "VOUCH-13df39ed9cd4e2c9-97b5007485c16f9b-76d12fb42cb03d2d-2952336c44217bb8"`.
351 #[inline(always)]
352 pub const fn parse(string: &str) -> Result<VouchingParameters, &'static str> {
353 Self::parse_bytes(string.as_bytes())
354 }
355
356 /// Parses the string representation of a [`VouchingParameters`] object
357 /// or panics.
358 ///
359 /// This function is mostly useful to initialise `const` literals.
360 #[inline(never)]
361 pub const fn parse_or_die(string: &str) -> VouchingParameters {
362 match Self::parse(string) {
363 Ok(ret) => ret,
364 Err(_) => panic!("failed to parse vouching parameter string."),
365 }
366 }
367
368 /// Computes a [`Voucher`] for `value`. The match can be
369 /// confirmed by [`CheckingParameters::check`]ing it against
370 /// `value`, with [`Self::checking_parameters`] as the checking
371 /// parameters.
372 ///
373 /// As an internal correctness check, this method `assert`s
374 /// that the returned [`Voucher`] matches `value`, according to the
375 /// [`CheckingParameters`] returned by [`Self::checking_parameters`].
376 ///
377 /// This assertion should only fail when the [`VouchingParameters`] instance
378 /// is invalid... and all constructors ([`VouchingParameters::generate`],
379 /// [`VouchingParameters::parse_bytes`], and the latter's convenience wrappers)
380 /// check for validity before returning an instance of [`VouchingParameters`].
381 #[must_use]
382 #[inline(always)]
383 pub const fn vouch(&self, value: u64) -> Voucher {
384 Voucher(vouch::vouch(
385 self.offset,
386 self.scale,
387 (self.checking.unoffset, self.checking.unscale),
388 value,
389 ))
390 }
391
392 /// Returns an iterator with a [`Voucher`]s for each [`u64`] value in the input iterator.
393 pub fn vouch_many<'scope>(
394 &'scope self,
395 values: impl IntoIterator<Item = u64> + 'scope,
396 ) -> impl Iterator<Item = Voucher> + 'scope {
397 values.into_iter().enumerate().map(|(idx, value)| {
398 // Rotate the input and the voucher on different periods:
399 // this lets us process 64 * 63 = 4032 values before
400 // repeating the same rotation counts.
401 let input_rot = (idx % 64) as u32;
402 let voucher_rot = (idx % 63) as u32;
403
404 let voucher = self.vouch(value.rotate_right(input_rot));
405
406 Voucher(voucher.0.rotate_left(voucher_rot))
407 })
408 }
409
410 /// Returns the [`CheckingParameters`] that will accept the
411 /// [`Voucher`]s generated with this [`VouchingParameters`].
412 #[must_use]
413 #[inline(always)]
414 pub const fn checking_parameters(&self) -> CheckingParameters {
415 self.checking
416 }
417
418 /// Number of ASCII characters in the string representation for
419 /// one [`VouchingParameters`] instance.
420 pub const REPRESENTATION_BYTE_COUNT: usize = 73;
421
422 /// Attempts to parse `bytes`, which must be the utf-8 (it's all
423 /// ASCII) representation of a serialised [`VouchingParameters`],
424 /// with a length of exactly `REPRESENTATION_BYTE_COUNT` bytes.
425 ///
426 /// Returns the [`VouchingParameters`] on success, and an error
427 /// reason on failure.
428 #[inline(never)]
429 pub const fn parse_bytes(bytes: &[u8]) -> Result<VouchingParameters, &'static str> {
430 #[allow(clippy::assertions_on_constants)]
431 const _: () = assert!(
432 VouchingParameters::REPRESENTATION_BYTE_COUNT == vouch::REPRESENTATION_BYTE_COUNT
433 );
434
435 match vouch::parse_bytes(bytes) {
436 Err(e) => Err(e),
437 Ok((offset, scale, (unoffset, unscale))) => {
438 // `generate:;derive_parameters` has an internal `assert!` check for validity,
439 // and we make sure the return value matches the parameters derived from
440 // `scale` and `unoffset`.
441 let expected = generate::derive_parameters(scale ^ vouch::VOUCHING_TAG, unoffset);
442 if (expected.0 == offset)
443 & (expected.1 == scale)
444 & (expected.2 .0 == unoffset)
445 & (expected.2 .1 == unscale)
446 {
447 Ok(VouchingParameters {
448 offset,
449 scale,
450 checking: CheckingParameters { unoffset, unscale },
451 })
452 } else {
453 Err("Invalid VouchingParameters values")
454 }
455 }
456 }
457 }
458}
459
460impl std::fmt::Display for VouchingParameters {
461 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
462 write!(
463 f,
464 "VOUCH-{:016x}-{:016x}-{:016x}-{:016x}",
465 self.offset, self.scale, self.checking.unoffset, self.checking.unscale
466 )
467 }
468}
469
470#[cfg(test)]
471fn make_generator(values: &[u64]) -> impl FnMut() -> Result<u64, &'static str> + '_ {
472 let mut idx = 0;
473 move || {
474 if idx < values.len() {
475 let ret = values[idx];
476 idx += 1;
477 Ok(ret)
478 } else {
479 Err("ran out of indices")
480 }
481 }
482}
483
484#[test]
485fn test_round_trip() {
486 let params = VouchingParameters::generate(make_generator(&[131, 131])).expect("must succeed");
487
488 let voucher = params.vouch(42);
489 assert!(params.checking.check(42, voucher));
490
491 // This should fail even if we circumvent the type system.
492 assert!(!params.checking.check(voucher.0, Voucher(42)));
493
494 // Incorrect values should fail.
495 assert!(!params.checking.check(43, voucher));
496 assert!(!params.checking.check(41, voucher));
497 assert!(!params.checking.check(42, Voucher(voucher.0 - 1)));
498 assert!(!params.checking.check(42, Voucher(voucher.0 + 1)));
499 assert!(!params.checking.check(41, Voucher(voucher.0 - 1)));
500 assert!(!params.checking.check(41, Voucher(voucher.0 + 1)));
501 assert!(!params.checking.check(43, Voucher(voucher.0 - 1)));
502 assert!(!params.checking.check(43, Voucher(voucher.0 + 1)));
503}
504
505#[test]
506fn test_round_trip_many() {
507 let params = VouchingParameters::generate(make_generator(&[131, 131])).expect("must succeed");
508
509 let vouchers: Vec<Voucher> = params.vouch_many([42u64, 101u64]).collect();
510 assert!(params.checking.check_many(&[42, 101], &vouchers));
511
512 // Make sure we can call this usefully.
513 let arr = [42u64, 101u64];
514 let vouchers2: Vec<Voucher> = params.vouch_many(arr.iter().copied()).collect();
515 assert_eq!(vouchers, vouchers2);
516 let vouchers3: Vec<Voucher> = params
517 .vouch_many([41u64, 100u64].iter().map(|x| x + 1))
518 .collect();
519 assert_eq!(vouchers, vouchers3);
520
521 // The first element is the same as a regular scalar voucher
522 assert!(params.checking.check(42, vouchers[0]));
523 // But not the second
524 assert!(!params.checking.check(101, vouchers[1]));
525
526 // And any difference causes a failure.
527 assert!(!params.checking.check_many(&[41, 101], &vouchers));
528 assert!(!params.checking.check_many(&[42, 100], &vouchers));
529 assert!(!params.checking.check_many(&[42], &vouchers));
530 assert!(!params.checking.check_many(&[42, 101, 10], &vouchers));
531}
532
533#[test]
534fn test_round_trip_many_long() {
535 let params = VouchingParameters::generate(make_generator(&[131, 131])).expect("must succeed");
536 let values: Vec<u64> = (0..5000u64).collect();
537
538 let vouchers: Vec<Voucher> = params.vouch_many(values.iter().copied()).collect();
539 assert!(params.checking_parameters().check_many(&values, &vouchers));
540}
541#[test]
542fn test_parse_check() {
543 let params = VouchingParameters::generate(make_generator(&[131, 131]))
544 .expect("must succeed")
545 .checking_parameters();
546
547 eprintln!("{}", params);
548 const SERIAL: &str = "CHECK-0000000000000083-9b791a2755d2d996";
549 assert_eq!(format!("{}", params), SERIAL);
550
551 const COPY: CheckingParameters = CheckingParameters::parse_or_die(SERIAL);
552 assert_eq!(params, COPY);
553
554 // Check for weirdness in non-consteval context.
555 assert_eq!(
556 params,
557 CheckingParameters::parse(SERIAL).expect("Should succeed")
558 );
559 assert_eq!(params, CheckingParameters::parse_or_die(SERIAL));
560}
561
562#[test]
563#[should_panic(expected = "failed to parse checking parameter string.")]
564fn test_parse_check_fail() {
565 CheckingParameters::parse_or_die("CHECK-0000000000000083-9b791a2755d2d99");
566}
567
568#[test]
569fn test_generate() {
570 VouchingParameters::generate(make_generator(&[131, 131])).expect("must succeed");
571}
572
573#[test]
574fn test_generate_eventually_accept() {
575 let (offset, scale, (unoffset, unscale)) = generate::derive_parameters(13, 142);
576
577 let values = [0u64, 1u64, u64::MAX, 3u64, !17u64, 13, 142];
578 assert_eq!(
579 VouchingParameters::generate(make_generator(&values)),
580 Ok(VouchingParameters {
581 offset,
582 scale,
583 checking: CheckingParameters { unoffset, unscale }
584 })
585 );
586}
587
588#[test]
589fn test_generate_fail() {
590 let values = [0u64, 1u64, u64::MAX, 3u64, 17, !17u64, 13];
591
592 assert_eq!(
593 VouchingParameters::generate(make_generator(&values)),
594 Err("ran out of indices")
595 );
596}
597
598#[test]
599fn test_generate_fail_early() {
600 assert_eq!(
601 VouchingParameters::generate(make_generator(&[13])),
602 Err("ran out of indices")
603 );
604 assert_eq!(
605 VouchingParameters::generate(make_generator(&[])),
606 Err("ran out of indices")
607 );
608}
609
610#[test]
611fn test_parse_vouch() {
612 let params = VouchingParameters::generate(make_generator(&[131, 131])).expect("must succeed");
613
614 eprintln!("{}", params);
615 const SERIAL: &str =
616 "VOUCH-b4b0de979c8a90a9-676e696863756fd5-0000000000000083-9b791a2755d2d996";
617 assert_eq!(format!("{}", params), SERIAL);
618
619 const COPY: VouchingParameters = VouchingParameters::parse_or_die(SERIAL);
620 assert_eq!(params, COPY);
621
622 // Double check for weirdness around const eval.
623 assert_eq!(params, VouchingParameters::parse(SERIAL).unwrap());
624 assert_eq!(params, VouchingParameters::parse_or_die(SERIAL));
625}
626
627#[test]
628#[should_panic(expected = "failed to parse vouching parameter string.")]
629fn test_parse_vouch_fail_params() {
630 // Bad parameters
631 let bad_serial = "VOUCH-b4b0de979c8a90a9-676e696863756fd5-0000000000000083-9b791a2755d2d995";
632 // This should fail validate.
633 VouchingParameters::parse_or_die(bad_serial);
634}
635
636#[test]
637#[should_panic(expected = "failed to parse vouching parameter string.")]
638fn test_parse_vouch_fail_early() {
639 // Bad format.
640 let bad_serial = "VOUCH-b4b0de979c8a90a9-676e696863756fd5-0000000000000083-9b791a2755d2d99";
641 // This should fail validate.
642 VouchingParameters::parse_or_die(bad_serial);
643}