Vitamin C Normalize
Normalized equality terms for the plaintext data model of
vitaminc-aead. Part of the
Vitamin C framework.
An equality index derives a key from a value. Two values that are equal must derive the same key whichever width, scale or spelling wrote them, and two values that merely look alike (a date and the integer it is stored as, a uuid and the same sixteen bytes) must not. This crate decides both: each scalar kind that can be indexed has a frozen domain label and a normalization to bytes, and everything else has no term.
There is no cryptography here and no PAE: the crate's own dependencies
are the Unicode tables. (It builds on vitaminc-aead for the data model,
which brings that crate's dependencies with it.) The encodings are its
own, including the decimal one, which is ported from the frozen
orderable-bytes release and checked against it byte for byte. A PRF crate such as
vitaminc-prf takes a [Normalized]
and derives under its domain.
One scalar
use ;
use ;
use Controlled;
// Every integer width is one term.
let a = normalize?;
let b = normalize?;
assert_eq!;
assert_eq!;
// A date is not the integer it is stored as.
let day = normalize?;
assert_eq!;
assert_ne!;
# Ok::
Logical types opt in: a date, a timestamp, a decimal and a uuid have
domains of their own. A null, a boolean, an undefined and a logical type
this crate does not know have no term, and normalize refuses them with
[NormalizeError::NoDomain].
A whole value, beside encryption
[Terms] is a sink that collects one [Term] per indexable scalar, with
the path to it. Teed with the encrypting sink it sees the same walk, so
the terms are complete before any cipher call:
use BTreeMap;
use ;
# use ;
# use ;
# /// Keeps the plaintext in the clear, for the example. Any `Cipher` will do.
# ;
#
# let cipher = Clear;
let mut user = new;
user.insert;
user.insert;
let = encrypt_with_terms?;
assert_eq!;
assert_eq!;
assert_eq!;
# Ok::
encrypt_with_terms_async does the same through an async cipher and has
every term before its first await. plan_with_terms walks without
sealing at all, and terms collects the terms with no encryption in the
walk.
terms refuses every value encryption refuses, for the same reason and
at the same path, so an index never holds terms for a value that cannot
be stored. It keeps the same shape rules, through the same code: no
duplicate key, no value nested too deep, every scalar valid for its
logical type, every passthrough framable, and something sealed in every
container with children, even though it seals nothing itself. Read such a
refusal with TermsError::refusal; encryption reports the same one as
EncryptError::Shape.
A scalar whose kind has no domain (a null, a boolean, an undefined)
produces no term and does not stop the walk; passthrough subtrees get none
either. A scalar whose logical type has no domain, one from a newer
client, stops the walk: encrypting it with no term and no error would leave
an index silently incomplete. That refusal and an unassigned code point
arrive as EncryptError::Source carrying a TermError (from terms, as
TermsError::term), which holds the NormalizeError and the path to the
refused field. Downcast to TermError; the NormalizeError is also its
source(), so a reporter that walks the error chain reaches it. The
message names neither the field nor the value, because map keys can be
user data; read the path with TermError::path.
Encodings
These are protocol data. Changing any of them, or a domain label, means a new label.
| Kind | Domain | Bytes |
|---|---|---|
| integer, any width | vitaminc/prf/value/integer-orderable/v1 |
sign byte (0x00 negative, 0x01 otherwise), then the big-endian 128-bit two's-complement word |
| float, either width | vitaminc/prf/value/float-orderable/v1 |
the f64 value's orderable bytes; f32 widened exactly, zero signs and NaN payloads folded |
| date | vitaminc/prf/value/date-orderable/v1 |
orderable days from 0001-01-01 |
| timestamp, any unit | vitaminc/prf/value/timestamp-micros-orderable/v1 |
orderable seconds, then nanoseconds truncated to microseconds |
| decimal | vitaminc/prf/value/decimal-orderable/v1 |
the 14-byte orderable encoding of the value (see [decimal]), scale and signed zero normalized |
| text | vitaminc/prf/value/text-nfc/unicode-16/v1 |
Unicode 16 NFC; a code point unassigned in Unicode 16 is refused |
| bytes | vitaminc/prf/value/bytes/v1 |
as they are |
| uuid | vitaminc/prf/value/uuid/v1 |
the sixteen raw bytes |
Text normalization is variable-time. The integer and float paths use no floating-point arithmetic.