Skip to main content

ms_codec/codex32/
checksum.rs

1// Vendored from `codex32` v0.1.0 (crates.io checksum
2// d230935faa4d0521349d228f39aba4ff489cf2a8bcab4d84e31f4cbd6fe918e9), CC0-1.0,
3// by Andrew Poelstra. Inlined into ms-codec (Cycle-B, shape A) to own the
4// Zeroize/Drop/redacting-Debug secret-hygiene fixes (FOLLOWUP
5// codex32-upstream-dormant-vendor-vs-accept-decision).
6//
7// Copied from the upstream runtime modules (lib.rs / field.rs / checksum.rs).
8// The ONLY substantive edits are: (1) Zeroize/ZeroizeOnDrop + a redacting Debug
9// on `Codex32String` (Phase 2); (2) module-routing of `use` paths to fit the
10// inlined submodule (`crate::field` -> `super::field` in checksum.rs, since the
11// codex32 crate root is now the `codex32` submodule); (3) crate-local lint
12// `#![allow(..)]`s (the crate denies missing_docs / runs -D-warnings clippy; the
13// upstream copy predates both). rustfmt also normalized a few cosmetic spots
14// (import order, one array literal, one fn-signature wrap) — NONE touch encoding
15// logic. The ENCODING (from_seed/from_string/interpolate_at/checksum/field) is
16// behaviorally UNCHANGED; the wire-byte-identity invariant is proven by
17// tests/codex32_vendor_parity.rs. The upstream CC0 LICENSE is retained verbatim
18// alongside as src/codex32/LICENSE.
19//
20// Rust Codex32 Library and Reference Implementation
21// Written in 2023 by
22//   Andrew Poelstra <apoelstra@wpsoftware.net>
23//
24// To the extent possible under law, the author(s) have dedicated all
25// copyright and related and neighboring rights to this software to
26// the public domain worldwide. This software is distributed without
27// any warranty.
28//
29// You should have received a copy of the CC0 Public Domain Dedication
30// along with this software.
31// If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.
32//
33
34//! Checksums
35//!
36//! Validates specific checksums
37//!
38
39use super::field::Fe; // vendored: was `crate::field::Fe` upstream (crate root = the codex32 module here)
40use super::{Case, Error};
41
42/// An engine which consumes one GF32 character at a time, and produces
43/// a residue modulo some generator
44#[derive(Clone, PartialEq, Eq, Debug)]
45pub struct Engine {
46    case: Option<Case>,
47    generator: Vec<Fe>,
48    residue: Vec<Fe>,
49    target: Vec<Fe>,
50}
51
52impl Engine {
53    // An engine which computes the normal codex32 checksum
54    pub fn new_codex32_short() -> Engine {
55        Engine {
56            case: None,
57            #[rustfmt::skip]
58            generator: vec![
59                Fe::E, Fe::M, Fe::_3, Fe::G, Fe::Q, Fe::E,
60                Fe::E, Fe::E, Fe::L, Fe::M, Fe::C, Fe::S,
61                Fe::S,
62            ],
63            #[rustfmt::skip]
64            residue: vec![
65                Fe::Q, Fe::Q, Fe::Q, Fe::Q, Fe::Q, Fe::Q,
66                Fe::Q, Fe::Q, Fe::Q, Fe::Q, Fe::Q, Fe::Q,
67                Fe::P,
68            ],
69            #[rustfmt::skip]
70            target: vec![
71                Fe::S, Fe::E, Fe::C, Fe::R, Fe::E, Fe::T,
72                Fe::S, Fe::H, Fe::A, Fe::R, Fe::E, Fe::_3,
73                Fe::_2,
74            ],
75        }
76    }
77
78    // An engine which computes the "long" codex32 checksum
79    pub fn new_codex32_long() -> Engine {
80        // hyk9x4hx4ef6e20p
81        Engine {
82            case: None,
83            #[rustfmt::skip]
84            generator: vec![
85                Fe::_0, Fe::_2, Fe::E, Fe::_6, Fe::F, Fe::E,
86                Fe::_4, Fe::X, Fe::H, Fe::_4, Fe::X, Fe::_9,
87                Fe::K,  Fe::Y, Fe::H,
88            ],
89            #[rustfmt::skip]
90            residue: vec![
91                Fe::Q, Fe::Q, Fe::Q, Fe::Q, Fe::Q, Fe::Q,
92                Fe::Q, Fe::Q, Fe::Q, Fe::Q, Fe::Q, Fe::Q,
93                Fe::Q, Fe::Q, Fe::P,
94            ],
95            #[rustfmt::skip]
96            target: vec![
97                Fe::S, Fe::E, Fe::C, Fe::R, Fe::E, Fe::T,
98                Fe::S, Fe::H, Fe::A, Fe::R, Fe::E, Fe::_3,
99                Fe::_2, Fe::E, Fe::X,
100            ],
101        }
102    }
103
104    /// When computing checksums of "diffs" you do may want to set
105    /// the highest-degree coefficient of the polynomial to 1.
106    ///
107    /// If you do not know exactly why you are using this function,
108    /// you should not use it.
109    pub fn force_residue_to_zero(&mut self) {
110        self.residue = vec![Fe::Q; self.residue.len()];
111    }
112
113    /// Extracts the residue from a checksum engine
114    pub fn into_residue(self) -> Vec<Fe> {
115        self.residue
116    }
117
118    /// Determines whether the residue matches the target value
119    /// for the checksum
120    ///
121    /// If you need the actual residue, e.g. for error correction,
122    /// call the `into_residue` function (which will consume the
123    /// engine).
124    pub fn is_valid(&self) -> bool {
125        self.residue == self.target
126    }
127
128    /// Initializes the checksum engine by loading an HRP into it
129    pub fn input_hrp(&mut self, hrp: &str) -> Result<(), Error> {
130        for ch in hrp.chars() {
131            self.set_check_case(ch)?;
132            self.input_fe(Fe::from_int(u32::from(ch.to_ascii_lowercase()) >> 5)?);
133        }
134        self.input_fe(Fe::Q);
135        for ch in hrp.chars() {
136            self.input_fe(Fe::from_int(u32::from(ch.to_ascii_lowercase()) & 0x1f)?);
137        }
138        Ok(())
139    }
140
141    /// Adds a single character to the checksum engine
142    pub fn input_char(&mut self, c: char) -> Result<(), Error> {
143        self.set_check_case(c)?;
144        self.input_fe(Fe::from_char(c)?);
145        Ok(())
146    }
147
148    /// Adds an entire string to the engine, counting each character as a data character
149    /// (not an HRP).
150    pub fn input_data_str(&mut self, s: &str) -> Result<(), Error> {
151        for ch in s.chars() {
152            self.input_char(ch)?;
153        }
154        Ok(())
155    }
156
157    /// Adds the target residue to the end of the input string
158    pub fn input_own_target(&mut self) {
159        let fuck_the_borrow_checker = self.target.clone();
160        for u in fuck_the_borrow_checker {
161            self.input_fe(u);
162        }
163    }
164
165    /// Helper function to check that the whole input has consistent case
166    fn set_check_case(&mut self, c: char) -> Result<(), Error> {
167        if !c.is_ascii() {
168            Err(Error::InvalidChar(c))
169        } else if c.is_numeric() {
170            // numbers don't affect case, nor are they affected by case
171            Ok(())
172        } else {
173            let is_lower = c.is_ascii_lowercase();
174            match (self.case, is_lower) {
175                (Some(Case::Lower), true) | (Some(Case::Upper), false) => Ok(()),
176                (Some(case @ Case::Lower), false) | (Some(case @ Case::Upper), true) => {
177                    Err(Error::InvalidCase(case, c))
178                }
179                (None, true) => {
180                    self.case = Some(Case::Lower);
181                    Ok(())
182                }
183                (None, false) => {
184                    self.case = Some(Case::Upper);
185                    Ok(())
186                }
187            }
188        }
189    }
190
191    /// Adds a single field element to the checksum engine
192    ///
193    /// This is where the real magic happens.
194    #[rustfmt::skip]
195    pub fn input_fe(&mut self, e: Fe) {
196        let res_len = self.residue.len(); // needed for borrowck
197        // Store current coefficient of x^{n-1}, which will become
198        // x^n (and get reduced)
199        let xn = self.residue[0];
200        // Simply shift x^0 through x^{n-1} up one, and set x^0 to the new input
201        for i in 1..res_len {
202            self.residue[i - 1] = self.residue[i];
203        }
204        self.residue[res_len - 1] = e;
205        // Then reduce x^n mod the generator.
206        for i in 0..res_len {
207            self.residue[i] += self.generator[i] * xn;
208        }
209    }
210}