Expand description
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
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
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 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:
$ 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:
$ 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.
Structs§
- Checking
Parameters CheckingParameters
carry enough information to confirm whether aVoucher
was generated from a givenu64
value using the unknownVouchingParameters
associated with theCheckingParameters
.- Voucher
- A
Voucher
is a very weakly one-way-transformed value for an arbitraryu64
. - Vouching
Parameters VouchingParameters
let us convert an arbitraryu64
value to aVoucher
that can later be checked as matching the initialu64
value, given only theCheckingParameters
associated with theVouchingParameters
.