raffle 0.0.1

A non-cryptographic 'vouching' system
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
//! A non-cryptographic "vouching" system.
//!
//! The `raffle` library offers functionality similar to public key
//! signatures, except without any pretense of cryptographic strength.
//! It lets us pass [`CheckingParameters`] to modules so that they can
//! check whether a value looks like it was generated by code with
//! access to the corresponding [`VouchingParameters`], while making
//! it implausibly hard for these checking modules to *accidentally*
//! generate valid [`Voucher`]s for arbitrary values.
//!
//! Internally, the implementation relies on modular multiplicative
//! inverses, with added asymmetry to catch some misuse (e.g., swapped
//! vouching and checking parameters).  Both the vouching function and
//! the checking function are add-multiply (mod 2**64) permutations;
//! for a matching pair of vouching and checking functions,
//! `check(vouch(x)) = K - x`, where `K` is
//! `u64::from_le_bytes(*b"Vouch!OK")`.
//!
//! It's easy to write code that'll find the vouching parameters from
//! a set of checking parameters, but the library doesn't have that
//! functionality, not even in the vouching-to-checking direction.
//! The internal parameter generation code in `generate.rs` accepts
//! the multiplier for the vouching function and the addend for the
//! checking function, and fills in the blanks for both the vouching
//! and the checking parameters.  Any code to extract vouching
//! parameters from checking parameters will hopefully stick out in
//! review, if only because of the large integer constants involved.
//!
//! The internal modular add-multiply transformation gives us decent
//! bounds on collisions: each instance of the [`VouchingParameters`]
//! function is a permutation over the [`u64`]s, so we'll never accept a
//! [`Voucher`] generated from the correct [`VouchingParameters`]
//! (i.e., whence our [`CheckingParameters`] came) but for the wrong
//! [`u64`] value.  Otherwise, if two [`VouchingParameters`] differ,
//! they're both affine functions, and thus match for at most one
//! input-output pair... or we could have accidentally generated the
//! same parameters independently, but the parameter space is pretty
//! large (more than 125 bits).  All in all, the probability that we'll
//! accept a [`Voucher`] generated from the wrong
//! [`VouchingParameters`] is less than `2**-60`, assuming the
//! parameters were generated randomly and independently of the
//! vouched value.
//!
//! That's far from cryptographic levels of difficulty, but rare
//! enough that any erroneous match is much more likely a sign of
//! hardware failure or deliberate action than just bad luck.
//!
//! # Sample usage
//!
//! First, generate a [`Voucher`]
//!
//! ```rust, ignore
//! const VOUCHING_PARAMETERS : VouchingParameters = VouchingParameters::parse_or_die("VOUCH-...");
//! let value = ...;
//! let voucher = VOUCHING_PARAMETERS.vouch(digest(&value));
//! // pass around (voucher, value)
//! ```
//!
//! and, on the read side, validate a [`Voucher`] with
//!
//! ```rust, ignore
//! const CHECKING_PARAMETERS : CheckingParameters = CheckingParameters::parse_or_die("CHECK-...");
//! let (voucher, value) = ...;
//! if CHECKING_PARAMETERS.check(digest(&value), voucher) {
//!     // work with a validated `value`.
//! }
//! ```
//!
//! Validation only tells us that the value was most likely generated
//! by a module with access to the `VOUCHING_PARAMETERS` that
//! correspond to `CHECKING_PARAMETERS`.  The [`Voucher`] could well
//! have been generated for a different message or recipient.
//!
//! We're not looking for cryptographic guarantees here, and only want
//! to make it hard for code to accidentally generate valid vouchers
//! when that code should only be able to check them for validity.  In
//! most cases, it's probably good enough to generate fresh parameters
//! at runtime, or to hardcode the parameters in the source, with
//! strings generated by `examples/generate_raffle_parameters`.
//!
//! If you need more than that... it's always possible to load
//! parameters at runtime, but maybe you want real signatures.
//!
//! # Generating parameters
//!
//! When everything stays in the same process, it's probably good
//! enough to generate [`VouchingParameters`] by passing a (P)RNG to
//! [`VouchingParameters::generate`].
//!
//! ```
//! # use raffle::VouchingParameters;
//! use rand::Rng;
//! #[derive(Debug)]
//! enum Never {}
//!
//! let mut rng = rand::rngs::OsRng {};
//! VouchingParameters::generate(|| Ok::<u64, Never>(rng.gen())).unwrap();
//! ```
//!
//! Otherwise, you can generate parameter strings with the `generate_raffle_parameters` binary:
//!
//! ```sh
//! $ cargo build --examples
//!     Finished dev [unoptimized + debuginfo] target(s) in 0.00s
//! $ target/debug/examples/generate_raffle_parameters
//! VOUCH-ecf8c191680e5394-a0474d8e2618d059-9bf723a6b538fe4a-1dddb95caa81d852
//! CHECK-9bf723a6b538fe4a-1dddb95caa81d852
//! ```
//!
//! The first line is the string representation for a set of
//! [`VouchingParameters`], suitable for
//! [`VouchingParameters::parse`], or, more ergonomically for `const`
//! values, [`VouchingParameters::parse_or_die`].  The second line is
//! the string representation for the corresponding
//! [`CheckingParameters`], suitable for [`CheckingParameters::parse`]
//! or [`CheckingParameters::parse_or_die`].
//!
//! Each invocation of `generate_raffle_parameters` with no argument
//! gets fresh random bits from the operating system, and is thus
//! expected to generate different parameters.  When there are command
//! line arguments, the parameters are instead derived
//! deterministically from these arguments, with [BLAKE3](https://docs.rs/blake3/latest/blake3/):
//!
//! ```sh
//! $ target/debug/examples/generate_raffle_parameters test seed
//! VOUCH-13df39ed9cd4e2c9-97b5007485c16f9b-76d12fb42cb03d2d-2952336c44217bb8
//! CHECK-76d12fb42cb03d2d-2952336c44217bb8
//! $ target/debug/examples/generate_raffle_parameters test seed
//! VOUCH-13df39ed9cd4e2c9-97b5007485c16f9b-76d12fb42cb03d2d-2952336c44217bb8
//! CHECK-76d12fb42cb03d2d-2952336c44217bb8
//! ```
//!
//! The parameter strings always have the same fixed-width format, so should
//! be easy to `grep` for.  The `VOUCH`ing parameters also include the `CHECK`ing
//! parameters as a suffix, so we can `grep` for the hex digits to find matching pairs.
mod check;
mod constparse;
mod generate;
mod vouch;

/// A [`Voucher`] is a very weakly one-way-transformed value for an arbitrary [`u64`].
///
/// [`CheckingParameters`] let us confirm whether the voucher came
/// from an expected u64 value after transformation by the
/// [`VouchingParameters`] associated with the [`CheckingParameters`],
/// while making it hard to accidentally generate a valid [`Voucher`]
/// with only [`CheckingParameters`] (i.e., it's hard to accidentally
/// back out the [`VouchingParameters`] from [`CheckingParameters`]).
///
/// The type wrapper exists to prevent confusion between what's
/// a [`Voucher`] value what's an expected [`u64`] value.
///
/// Generate [`Voucher`]s with [`VouchingParameters::vouch`] or
/// [`VouchingParameters::vouch_many`], by deserialising directly into
/// [`Voucher`] objects, or with [`std::mem::transmute`].  The latter is
/// `unsafe`, and that's on purpose: code that takes arbitrary [`u64`]s
/// and stamps them as [`Voucher`] values should be scrutinised.
///
/// Confirm that a [`Voucher`] is valid for a given set of [`CheckingParameters`]
/// and initial [`u64`] (i.e., was generated from the initial [`u64`] using
/// the [`VouchingParameters`] associated with the [`CheckingParameters`])
/// by calling [`CheckingParameters::check`] or [`CheckingParameters::check_many`].
#[derive(Clone, Copy, Eq, PartialEq, Hash)]
#[cfg_attr(not(feature = "prost"), derive(Debug))] // prost::Message derives `Debug`
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "prost", derive(prost::Message))]
#[repr(transparent)]
pub struct Voucher(#[cfg_attr(feature = "prost", prost(fixed64, tag = "1"))] u64);

/// [`CheckingParameters`] carry enough information to confirm whether a
/// [`Voucher`] was generated from a given [`u64`] value using the unknown
/// [`VouchingParameters`] associated with the [`CheckingParameters`].
///
/// [`CheckingParameters`] should be obtained either by calling
/// [`VouchingParameters::checking_parameters`] for transient
/// in-memory parameters, by parsing a string representation with
/// [`CheckingParameters::parse`] or
/// [`CheckingParameters::parse_bytes`], or, when initialising `const`
/// variables with hardcoded strings, with
/// [`CheckingParameters::parse_or_die`].
///
/// After that, the result of [`VouchingParameters::vouch`] can be checked
/// with [`CheckingParameters::check`], and that of [`VouchingParameters::vouch_many`]
/// with [`CheckingParameters::check_many`].
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
pub struct CheckingParameters {
    unoffset: u64,
    unscale: u64,
}

/// [`VouchingParameters`] let us convert an arbitrary [`u64`] value
/// to a [`Voucher`] that can later be checked as matching the initial
/// [`u64`] value, given only the [`CheckingParameters`] associated
/// with the [`VouchingParameters`].
///
/// [`VouchingParameters`] should be constructed with
/// [`VouchingParameters::generate`] for transient in-memory
/// parameters, by parsing a string representation, with
/// [`VouchingParameters::parse`] or
/// [`VouchingParameters::parse_bytes`], or, when initialising `const`
/// variables with hardocded strings, with
/// [`VouchingParameters::parse_or_die`].
///
/// After that, we can convert a [`u64`] to a [`Voucher`] with
/// [`VouchingParameters::vouch`].  Readers can confirm that the [`Voucher`]
/// matches the initial `[u64`] once they have the corresponding
/// [`VouchingParameters::checking_parameters`], by calling [`CheckingParameters::check`]
/// with the expected [`u64`] and the [`Voucher`].
///
/// Vouching for a batch of [`u64`] values should instead use
/// [`VouchingParameters::vouch_many`] and
/// [`CheckingParameters::check_many`]: the vouching transformation
/// varies for each index, making it harder to accidentally accept
/// permuted [`u64`] values and [`Voucher`]s.
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
pub struct VouchingParameters {
    offset: u64,
    scale: u64,
    checking: CheckingParameters,
}

impl CheckingParameters {
    /// Attempts to parse the string representation of a [`CheckingParameters`] instance.
    ///
    /// This representation can be generated by the [`std::fmt::Display`] trait, e.g.,
    /// with `format!("{}", checking_params) => "CHECK-9bf723a6b538fe4a-1dddb95caa81d852"`.
    #[inline(always)]
    pub const fn parse(string: &str) -> Result<CheckingParameters, &'static str> {
        Self::parse_bytes(string.as_bytes())
    }

    /// Parses the string representation of a [`CheckingParameters`] object
    /// or panics.
    ///
    /// This function is mostly useful to initialise `const` literals.
    #[inline(never)]
    pub const fn parse_or_die(string: &str) -> CheckingParameters {
        match Self::parse(string) {
            Ok(ret) => ret,
            Err(_) => panic!("failed to parse checking parameter string."),
        }
    }

    /// Returns whether the `expected` value matches the `voucher`,
    /// assuming the voucher was generated with the [`VouchingParameters`] from
    /// which the self [`CheckingParameters`] came.
    ///
    /// If the `voucher` was generated from different parameters
    /// (generated independently and uniformly at random), the
    /// probability of a match is less than `2**-60`.
    #[must_use]
    #[inline(always)]
    pub const fn check(self, expected: u64, voucher: Voucher) -> bool {
        check::check(self.unoffset, self.unscale, expected, voucher.0)
    }

    /// Returns whether the `expected` values match all the
    /// `voucher`s, assuming the vouchers were generated with the
    /// [`VouchingParameters`] from which the self
    /// [`CheckingParameters`] came.
    ///
    /// If the `voucher`s were generated from different parameters
    /// (generated independently and uniformly at random), the
    /// probability of a match is less than `2**-60`.
    #[must_use]
    pub fn check_many(self, expected: &[u64], vouchers: &[Voucher]) -> bool {
        if expected.len() != vouchers.len() {
            return false;
        }

        std::iter::zip(expected.iter(), vouchers.iter())
            .enumerate()
            .all(|(idx, (expected, voucher))| {
                let input_rot = (idx % 64) as u32;
                let voucher_rot = (idx % 63) as u32;

                self.check(
                    expected.rotate_right(input_rot),
                    Voucher(voucher.0.rotate_right(voucher_rot)),
                )
            })
    }

    /// Number of ASCII characters in the string representation for
    /// one [`CheckingParameters`] instance.
    pub const REPRESENTATION_BYTE_COUNT: usize = 39;

    /// Attempts to parse `bytes`, which must be the utf-8 (it's all
    /// ASCII) representation of a serialised [`CheckingParameters`],
    /// with a length of exactly `REPRESENTATION_BYTE_COUNT` bytes.
    ///
    /// Returns the [`CheckingParameters`] on success, and an error
    /// reason on failure.
    #[inline(never)]
    pub const fn parse_bytes(bytes: &[u8]) -> Result<CheckingParameters, &'static str> {
        #[allow(clippy::assertions_on_constants)]
        const _: () = assert!(
            CheckingParameters::REPRESENTATION_BYTE_COUNT == check::REPRESENTATION_BYTE_COUNT
        );

        match check::parse_bytes(bytes) {
            Err(e) => Err(e),
            Ok((unoffset, unscale)) => Ok(CheckingParameters { unoffset, unscale }),
        }
    }
}

impl std::fmt::Display for CheckingParameters {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "CHECK-{:016x}-{:016x}", self.unoffset, self.unscale)
    }
}

impl VouchingParameters {
    /// Attempts to generate a fresh set of [`VouchingParameters`] by
    /// repeatedly calling `generator` to get [`u64`] values.
    ///
    /// The `generator` should yield (pseudo)random [`u64`] values
    /// sampled uniformly from the full 64-bit range.  This function
    /// may loop forever when the `generator` is of very low quality.
    ///
    /// Returns a fresh [`VouchingParameters`] instance on success,
    /// and bubbles any error from `generator` on failure.
    pub fn generate<Err>(
        mut generator: impl FnMut() -> Result<u64, Err>,
    ) -> Result<VouchingParameters, Err> {
        fn gen64<Err>(mut generator: impl FnMut() -> Result<u64, Err>) -> Result<u64, Err> {
            loop {
                let ret = generator()?;
                // Avoid trivial values.
                if ret > 10 && !ret > 10 && ret.count_ones() > 2 && ret.count_zeros() > 2 {
                    return Ok(ret);
                }
            }
        }

        // `generate:;derive_parameters` has an internal `assert!` check for validity.
        let (offset, scale, (unoffset, unscale)) =
            generate::derive_parameters(gen64(&mut generator)?, gen64(&mut generator)?);
        Ok(VouchingParameters {
            offset,
            scale,
            checking: CheckingParameters { unoffset, unscale },
        })
    }

    /// Attempts to parse the string representation of [`VouchingParameters`].
    ///
    /// This representation can be generated by the [`std::fmt::Display`] trait,
    /// e.g., with `format!("{}", vouching_params) => "VOUCH-13df39ed9cd4e2c9-97b5007485c16f9b-76d12fb42cb03d2d-2952336c44217bb8"`.
    #[inline(always)]
    pub const fn parse(string: &str) -> Result<VouchingParameters, &'static str> {
        Self::parse_bytes(string.as_bytes())
    }

    /// Parses the string representation of a [`VouchingParameters`] object
    /// or panics.
    ///
    /// This function is mostly useful to initialise `const` literals.
    #[inline(never)]
    pub const fn parse_or_die(string: &str) -> VouchingParameters {
        match Self::parse(string) {
            Ok(ret) => ret,
            Err(_) => panic!("failed to parse vouching parameter string."),
        }
    }

    /// Computes a [`Voucher`] for `value`.  The match can be
    /// confirmed by [`CheckingParameters::check`]ing it against
    /// `value`, with [`Self::checking_parameters`] as the checking
    /// parameters.
    ///
    /// As an internal correctness check, this method `assert`s
    /// that the returned [`Voucher`] matches `value`, according to the
    /// [`CheckingParameters`] returned by [`Self::checking_parameters`].
    ///
    /// This assertion should only fail when the [`VouchingParameters`] instance
    /// is invalid... and all constructors ([`VouchingParameters::generate`],
    /// [`VouchingParameters::parse_bytes`], and the latter's convenience wrappers)
    /// check for validity before returning an instance of [`VouchingParameters`].
    #[must_use]
    #[inline(always)]
    pub const fn vouch(&self, value: u64) -> Voucher {
        Voucher(vouch::vouch(
            self.offset,
            self.scale,
            (self.checking.unoffset, self.checking.unscale),
            value,
        ))
    }

    /// Returns an iterator with a [`Voucher`]s for each [`u64`] value  in the input iterator.
    pub fn vouch_many<'scope>(
        &'scope self,
        values: impl IntoIterator<Item = u64> + 'scope,
    ) -> impl Iterator<Item = Voucher> + 'scope {
        values.into_iter().enumerate().map(|(idx, value)| {
            // Rotate the input and the voucher on different periods:
            // this lets us process 64 * 63 = 4032 values before
            // repeating the same rotation counts.
            let input_rot = (idx % 64) as u32;
            let voucher_rot = (idx % 63) as u32;

            let voucher = self.vouch(value.rotate_right(input_rot));

            Voucher(voucher.0.rotate_left(voucher_rot))
        })
    }

    /// Returns the [`CheckingParameters`] that will accept the
    /// [`Voucher`]s generated with this [`VouchingParameters`].
    #[must_use]
    #[inline(always)]
    pub const fn checking_parameters(&self) -> CheckingParameters {
        self.checking
    }

    /// Number of ASCII characters in the string representation for
    /// one [`VouchingParameters`] instance.
    pub const REPRESENTATION_BYTE_COUNT: usize = 73;

    /// Attempts to parse `bytes`, which must be the utf-8 (it's all
    /// ASCII) representation of a serialised [`VouchingParameters`],
    /// with a length of exactly `REPRESENTATION_BYTE_COUNT` bytes.
    ///
    /// Returns the [`VouchingParameters`] on success, and an error
    /// reason on failure.
    #[inline(never)]
    pub const fn parse_bytes(bytes: &[u8]) -> Result<VouchingParameters, &'static str> {
        #[allow(clippy::assertions_on_constants)]
        const _: () = assert!(
            VouchingParameters::REPRESENTATION_BYTE_COUNT == vouch::REPRESENTATION_BYTE_COUNT
        );

        match vouch::parse_bytes(bytes) {
            Err(e) => Err(e),
            Ok((offset, scale, (unoffset, unscale))) => {
                // `generate:;derive_parameters` has an internal `assert!` check for validity,
                // and we make sure the return value matches the parameters derived from
                // `scale` and `unoffset`.
                let expected = generate::derive_parameters(scale ^ vouch::VOUCHING_TAG, unoffset);
                if (expected.0 == offset)
                    & (expected.1 == scale)
                    & (expected.2 .0 == unoffset)
                    & (expected.2 .1 == unscale)
                {
                    Ok(VouchingParameters {
                        offset,
                        scale,
                        checking: CheckingParameters { unoffset, unscale },
                    })
                } else {
                    Err("Invalid VouchingParameters values")
                }
            }
        }
    }
}

impl std::fmt::Display for VouchingParameters {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "VOUCH-{:016x}-{:016x}-{:016x}-{:016x}",
            self.offset, self.scale, self.checking.unoffset, self.checking.unscale
        )
    }
}

#[cfg(test)]
fn make_generator(values: &[u64]) -> impl FnMut() -> Result<u64, &'static str> + '_ {
    let mut idx = 0;
    move || {
        if idx < values.len() {
            let ret = values[idx];
            idx += 1;
            Ok(ret)
        } else {
            Err("ran out of indices")
        }
    }
}

#[test]
fn test_round_trip() {
    let params = VouchingParameters::generate(make_generator(&[131, 131])).expect("must succeed");

    let voucher = params.vouch(42);
    assert!(params.checking.check(42, voucher));

    // This should fail even if we circumvent the type system.
    assert!(!params.checking.check(voucher.0, Voucher(42)));

    // Incorrect values should fail.
    assert!(!params.checking.check(43, voucher));
    assert!(!params.checking.check(41, voucher));
    assert!(!params.checking.check(42, Voucher(voucher.0 - 1)));
    assert!(!params.checking.check(42, Voucher(voucher.0 + 1)));
    assert!(!params.checking.check(41, Voucher(voucher.0 - 1)));
    assert!(!params.checking.check(41, Voucher(voucher.0 + 1)));
    assert!(!params.checking.check(43, Voucher(voucher.0 - 1)));
    assert!(!params.checking.check(43, Voucher(voucher.0 + 1)));
}

#[test]
fn test_round_trip_many() {
    let params = VouchingParameters::generate(make_generator(&[131, 131])).expect("must succeed");

    let vouchers: Vec<Voucher> = params.vouch_many([42u64, 101u64]).collect();
    assert!(params.checking.check_many(&[42, 101], &vouchers));

    // Make sure we can call this usefully.
    let arr = [42u64, 101u64];
    let vouchers2: Vec<Voucher> = params.vouch_many(arr.iter().copied()).collect();
    assert_eq!(vouchers, vouchers2);
    let vouchers3: Vec<Voucher> = params
        .vouch_many([41u64, 100u64].iter().map(|x| x + 1))
        .collect();
    assert_eq!(vouchers, vouchers3);

    // The first element is the same as a regular scalar voucher
    assert!(params.checking.check(42, vouchers[0]));
    // But not the second
    assert!(!params.checking.check(101, vouchers[1]));

    // And any difference causes a failure.
    assert!(!params.checking.check_many(&[41, 101], &vouchers));
    assert!(!params.checking.check_many(&[42, 100], &vouchers));
    assert!(!params.checking.check_many(&[42], &vouchers));
    assert!(!params.checking.check_many(&[42, 101, 10], &vouchers));
}

#[test]
fn test_round_trip_many_long() {
    let params = VouchingParameters::generate(make_generator(&[131, 131])).expect("must succeed");
    let values: Vec<u64> = (0..5000u64).collect();

    let vouchers: Vec<Voucher> = params.vouch_many(values.iter().copied()).collect();
    assert!(params.checking_parameters().check_many(&values, &vouchers));
}
#[test]
fn test_parse_check() {
    let params = VouchingParameters::generate(make_generator(&[131, 131]))
        .expect("must succeed")
        .checking_parameters();

    eprintln!("{}", params);
    const SERIAL: &str = "CHECK-0000000000000083-9b791a2755d2d996";
    assert_eq!(format!("{}", params), SERIAL);

    const COPY: CheckingParameters = CheckingParameters::parse_or_die(SERIAL);
    assert_eq!(params, COPY);

    // Check for weirdness in non-consteval context.
    assert_eq!(
        params,
        CheckingParameters::parse(SERIAL).expect("Should succeed")
    );
    assert_eq!(params, CheckingParameters::parse_or_die(SERIAL));
}

#[test]
#[should_panic(expected = "failed to parse checking parameter string.")]
fn test_parse_check_fail() {
    CheckingParameters::parse_or_die("CHECK-0000000000000083-9b791a2755d2d99");
}

#[test]
fn test_generate() {
    VouchingParameters::generate(make_generator(&[131, 131])).expect("must succeed");
}

#[test]
fn test_generate_eventually_accept() {
    let (offset, scale, (unoffset, unscale)) = generate::derive_parameters(13, 142);

    let values = [0u64, 1u64, u64::MAX, 3u64, !17u64, 13, 142];
    assert_eq!(
        VouchingParameters::generate(make_generator(&values)),
        Ok(VouchingParameters {
            offset,
            scale,
            checking: CheckingParameters { unoffset, unscale }
        })
    );
}

#[test]
fn test_generate_fail() {
    let values = [0u64, 1u64, u64::MAX, 3u64, 17, !17u64, 13];

    assert_eq!(
        VouchingParameters::generate(make_generator(&values)),
        Err("ran out of indices")
    );
}

#[test]
fn test_generate_fail_early() {
    assert_eq!(
        VouchingParameters::generate(make_generator(&[13])),
        Err("ran out of indices")
    );
    assert_eq!(
        VouchingParameters::generate(make_generator(&[])),
        Err("ran out of indices")
    );
}

#[test]
fn test_parse_vouch() {
    let params = VouchingParameters::generate(make_generator(&[131, 131])).expect("must succeed");

    eprintln!("{}", params);
    const SERIAL: &str =
        "VOUCH-b4b0de979c8a90a9-676e696863756fd5-0000000000000083-9b791a2755d2d996";
    assert_eq!(format!("{}", params), SERIAL);

    const COPY: VouchingParameters = VouchingParameters::parse_or_die(SERIAL);
    assert_eq!(params, COPY);

    // Double check for weirdness around const eval.
    assert_eq!(params, VouchingParameters::parse(SERIAL).unwrap());
    assert_eq!(params, VouchingParameters::parse_or_die(SERIAL));
}

#[test]
#[should_panic(expected = "failed to parse vouching parameter string.")]
fn test_parse_vouch_fail_params() {
    // Bad parameters
    let bad_serial = "VOUCH-b4b0de979c8a90a9-676e696863756fd5-0000000000000083-9b791a2755d2d995";
    // This should fail validate.
    VouchingParameters::parse_or_die(bad_serial);
}

#[test]
#[should_panic(expected = "failed to parse vouching parameter string.")]
fn test_parse_vouch_fail_early() {
    // Bad format.
    let bad_serial = "VOUCH-b4b0de979c8a90a9-676e696863756fd5-0000000000000083-9b791a2755d2d99";
    // This should fail validate.
    VouchingParameters::parse_or_die(bad_serial);
}