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}