alice_det_math/semantics.rs
1//! Reading arithmetic identifiers back from stored data.
2//!
3//! [`SEMANTICS_ID`] names the arithmetic of this
4//! release. Data written by an earlier release carries that release's
5//! identifier; [`check_semantics`] says whether this release reproduces it.
6//!
7//! Nothing here computes a floating-point value: the functions compare
8//! 32-byte identifiers, so this module is outside the numeric surface the
9//! golden pins cover (and `tests/golden.rs` checks that it stays so).
10
11use crate::SEMANTICS_ID;
12
13/// Identifiers of earlier releases whose functions return **the same bits** as
14/// this release on every input, but which covered fewer functions.
15///
16/// Empty: this release changed the bits of `sin` / `cos` / `sin_cos` (the
17/// reduction near their zeros and the sign of zero), `round` / `round64` /
18/// `cbrt` / `smoothstep` and the other metric functions (the canonical NaN),
19/// `MetricWeights::lipschitz` / `new` (exact for tiny and huge weights) and
20/// both precisions of `powf` (`pow(1, y) = 1`), so neither 0.4.0's identifier
21/// (`d2209b30…398e`) nor the one recorded on the main branch before this
22/// change and never published (`a6fe7dc8…d6af`) is reproduced, and both read as
23/// [`SemanticsCheck::Mismatch`]. `tests/golden.rs` keeps 0.4.0's pin table
24/// and proves that it differs from this release in exactly the pins of those
25/// functions.
26///
27/// An entry is added only together with that release's pin table in
28/// `tests/golden.rs`, and the test proves two things about it: the table
29/// folds to the entry, and every pin in it is reproduced by this release.
30/// So data stamped with an entry here was computed with arithmetic this
31/// release still performs, bit for bit. An identifier whose release changed
32/// any function's output never appears here.
33///
34/// Writers always stamp [`SEMANTICS_ID`]. Readers use [`check_semantics`].
35pub const PREVIOUS_SEMANTICS_IDS: &[[u8; 32]] = &[];
36
37/// What a stored arithmetic identifier says about the data stamped with it.
38///
39/// Returned by [`check_semantics`]. The previous case is its own variant so
40/// a reader never treats "computed by an earlier release with the same bits"
41/// as silently equal to "computed by this release".
42#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
43#[must_use]
44pub enum SemanticsCheck {
45 /// The identifier is [`SEMANTICS_ID`].
46 Verified,
47 /// The identifier is one of [`PREVIOUS_SEMANTICS_IDS`]: an earlier
48 /// release, same bits on every function it covered.
49 VerifiedPrevious([u8; 32]),
50 /// Unknown: the data was computed with arithmetic this release does not
51 /// promise to reproduce.
52 Mismatch,
53}
54
55impl SemanticsCheck {
56 /// Accepting: [`Verified`](Self::Verified) or
57 /// [`VerifiedPrevious`](Self::VerifiedPrevious).
58 ///
59 /// For readers of stored data (residuals, law identifiers, snapshots):
60 /// the stored values are reproduced exactly by this release.
61 #[must_use]
62 pub const fn is_reproducible(self) -> bool {
63 !matches!(self, Self::Mismatch)
64 }
65
66 /// Strict: [`Verified`](Self::Verified) only.
67 ///
68 /// For comparing identifiers as keys (a cache keyed on the identifier, a
69 /// peer that must run exactly this release, a writer checking what it is
70 /// about to stamp), where an earlier identifier is a different key even
71 /// though the bits agree.
72 #[must_use]
73 pub const fn is_current(self) -> bool {
74 matches!(self, Self::Verified)
75 }
76}
77
78/// Classify an identifier read back from stored data.
79///
80/// ```
81/// use alice_det_math::{check_semantics, SemanticsCheck, SEMANTICS_ID};
82/// assert_eq!(check_semantics(&SEMANTICS_ID), SemanticsCheck::Verified);
83/// assert_eq!(check_semantics(&[0; 32]), SemanticsCheck::Mismatch);
84/// ```
85pub const fn check_semantics(id: &[u8; 32]) -> SemanticsCheck {
86 if bytes_eq(id, &SEMANTICS_ID) {
87 return SemanticsCheck::Verified;
88 }
89 let mut i = 0;
90 while i < PREVIOUS_SEMANTICS_IDS.len() {
91 if bytes_eq(id, &PREVIOUS_SEMANTICS_IDS[i]) {
92 return SemanticsCheck::VerifiedPrevious(PREVIOUS_SEMANTICS_IDS[i]);
93 }
94 i += 1;
95 }
96 SemanticsCheck::Mismatch
97}
98
99const fn bytes_eq(a: &[u8; 32], b: &[u8; 32]) -> bool {
100 let mut i = 0;
101 while i < 32 {
102 if a[i] != b[i] {
103 return false;
104 }
105 i += 1;
106 }
107 true
108}