bech32/primitives/
checksum.rs

1// SPDX-License-Identifier: MIT
2
3//! Degree-2 [BCH] code checksum.
4//!
5//! [BCH]: <https://en.wikipedia.org/wiki/BCH_code>
6
7use core::{mem, ops};
8
9use crate::primitives::gf32::Fe32;
10use crate::primitives::hrp::Hrp;
11
12/// Trait defining a particular checksum.
13///
14/// For users, this can be treated as a marker trait; none of the associated data
15/// are end-user relevant.
16pub trait Checksum {
17    /// An unsigned integer type capable of holding a packed version of the generator
18    /// polynomial (without its leading 1) and target residue (which will have the
19    /// same width).
20    ///
21    /// Generally, this is the number of characters in the checksum times 5. So e.g.
22    /// for bech32, which has a 6-character checksum, we need 30 bits, so we can use
23    /// u32 here.
24    ///
25    /// The smallest type possible should be used, for efficiency reasons, but the
26    /// only operations we do on these types are bitwise xor and shifts, so it should
27    /// be pretty efficient no matter what.
28    type MidstateRepr: PackedFe32;
29
30    /// The length of the code.
31    ///
32    /// The length of the code is how long a coded message can be (including the
33    /// checksum!) for the code to retain its error-correcting properties.
34    const CODE_LENGTH: usize;
35
36    /// The number of characters in the checksum.
37    ///
38    /// Alternately, the degree of the generator polynomial. This is **not** the same
39    /// as `Self::CODE_LENGTH`.
40    const CHECKSUM_LENGTH: usize;
41
42    /// The coefficients of the generator polynomial, except the leading monic term,
43    /// in "big-endian" (highest-degree coefficients get leftmost bits) order, along
44    /// with the 4 shifts of the generator.
45    ///
46    /// The shifts are literally the generator polynomial left-shifted (i.e. multiplied
47    /// by the appropriate power of 2) in the field. That is, the 5 entries in this
48    /// array are the generator times { P, Z, Y, G, S } in that order.
49    ///
50    /// These cannot be usefully pre-computed because of Rust's limited constfn support
51    /// as of 1.67, so they must be specified manually for each checksum. To check the
52    /// values for consistency, run `Self::sanity_check()`.
53    const GENERATOR_SH: [Self::MidstateRepr; 5];
54
55    /// The residue, modulo the generator polynomial, that a valid codeword will have.
56    const TARGET_RESIDUE: Self::MidstateRepr;
57
58    /// Sanity checks that the various constants of the trait are set in a way that they
59    /// are consistent with each other.
60    ///
61    /// This function never needs to be called by users, but anyone defining a checksum
62    /// should add a unit test to their codebase which calls this.
63    fn sanity_check() {
64        // Check that the declared midstate type can actually hold the whole checksum.
65        assert!(Self::CHECKSUM_LENGTH <= Self::MidstateRepr::WIDTH);
66
67        // Check that the provided generator polynomials are, indeed, the same polynomial just shifted.
68        for i in 1..5 {
69            for j in 0..Self::MidstateRepr::WIDTH {
70                let last = Self::GENERATOR_SH[i - 1].unpack(j);
71                let curr = Self::GENERATOR_SH[i].unpack(j);
72                // GF32 is defined by extending GF2 with a root of x^5 + x^3 + 1 = 0
73                // which when written as bit coefficients is 41 = 0. Hence xoring
74                // (adding, in GF32) by 41 is the way to reduce x^5.
75                assert_eq!(
76                    curr,
77                    (last << 1) ^ if last & 0x10 == 0x10 { 41 } else { 0 },
78                    "Element {} of generator << 2^{} was incorrectly computed. (Should have been {} << 1)",
79                    j, i, last,
80                );
81            }
82        }
83    }
84}
85
86/// A checksum engine, which can be used to compute or verify a checksum.
87///
88/// Use this to verify a checksum, feed it the data to be checksummed using
89/// the `Self::input_*` methods.
90#[derive(Debug, Copy, Clone, PartialEq, Eq)]
91pub struct Engine<Ck: Checksum> {
92    residue: Ck::MidstateRepr,
93}
94
95impl<Ck: Checksum> Default for Engine<Ck> {
96    fn default() -> Self { Self::new() }
97}
98
99impl<Ck: Checksum> Engine<Ck> {
100    /// Constructs a new checksum engine with no data input.
101    #[inline]
102    pub fn new() -> Self { Engine { residue: Ck::MidstateRepr::ONE } }
103
104    /// Feeds `hrp` into the checksum engine.
105    #[inline]
106    pub fn input_hrp(&mut self, hrp: Hrp) {
107        for fe in HrpFe32Iter::new(&hrp) {
108            self.input_fe(fe)
109        }
110    }
111
112    /// Adds a single gf32 element to the checksum engine.
113    ///
114    /// This is where the actual checksum computation magic happens.
115    #[inline]
116    pub fn input_fe(&mut self, e: Fe32) {
117        let xn = self.residue.mul_by_x_then_add(Ck::CHECKSUM_LENGTH, e.into());
118        for i in 0..5 {
119            if xn & (1 << i) != 0 {
120                self.residue = self.residue ^ Ck::GENERATOR_SH[i];
121            }
122        }
123    }
124
125    /// Inputs the target residue of the checksum.
126    ///
127    /// Checksums are generated by appending the target residue to the input
128    /// string, then computing the actual residue, and then replacing the
129    /// target with the actual. This method lets us compute the actual residue
130    /// without doing any string concatenations.
131    #[inline]
132    pub fn input_target_residue(&mut self) {
133        for i in 0..Ck::CHECKSUM_LENGTH {
134            self.input_fe(Fe32(Ck::TARGET_RESIDUE.unpack(Ck::CHECKSUM_LENGTH - i - 1)));
135        }
136    }
137
138    /// Returns for the current checksum residue.
139    #[inline]
140    pub fn residue(&self) -> &Ck::MidstateRepr { &self.residue }
141}
142
143/// Trait describing an integer type which can be used as a "packed" sequence of Fe32s.
144///
145/// This is implemented for u32, u64 and u128, as a way to treat these primitive types as
146/// packed coefficients of polynomials over GF32 (up to some maximal degree, of course).
147///
148/// This is useful because then multiplication by x reduces to simply left-shifting by 5,
149/// and addition of entire polynomials can be done by xor.
150pub trait PackedFe32: Copy + PartialEq + Eq + ops::BitXor<Self, Output = Self> {
151    /// The one constant, for which stdlib provides no existing trait.
152    const ONE: Self;
153
154    /// The number of fe32s that can fit into the type; computed as floor(bitwidth / 5).
155    const WIDTH: usize = mem::size_of::<Self>() * 8 / 5;
156
157    /// Extracts the coefficient of the x^n from the packed polynomial.
158    fn unpack(&self, n: usize) -> u8;
159
160    /// Multiply the polynomial by x, drop its highest coefficient (and return it), and
161    /// add a new field element to the now-0 constant coefficient.
162    ///
163    /// Takes the degree of the polynomial as an input; for checksum applications
164    /// this should basically always be `Checksum::CHECKSUM_WIDTH`.
165    fn mul_by_x_then_add(&mut self, degree: usize, add: u8) -> u8;
166}
167
168/// A placeholder type used as part of the [`NoChecksum`] "checksum".
169///
170/// [`NoChecksum`]: crate::primitives::NoChecksum
171#[derive(Debug, Copy, Clone, PartialEq, Eq)]
172pub struct PackedNull;
173
174impl ops::BitXor<PackedNull> for PackedNull {
175    type Output = PackedNull;
176    #[inline]
177    fn bitxor(self, _: PackedNull) -> PackedNull { PackedNull }
178}
179
180impl PackedFe32 for PackedNull {
181    const ONE: Self = PackedNull;
182    #[inline]
183    fn unpack(&self, _: usize) -> u8 { 0 }
184    #[inline]
185    fn mul_by_x_then_add(&mut self, _: usize, _: u8) -> u8 { 0 }
186}
187
188macro_rules! impl_packed_fe32 {
189    ($ty:ident) => {
190        impl PackedFe32 for $ty {
191            const ONE: Self = 1;
192
193            #[inline]
194            fn unpack(&self, n: usize) -> u8 {
195                debug_assert!(n < Self::WIDTH);
196                (*self >> (n * 5)) as u8 & 0x1f
197            }
198
199            #[inline]
200            fn mul_by_x_then_add(&mut self, degree: usize, add: u8) -> u8 {
201                debug_assert!(degree > 0);
202                debug_assert!(degree <= Self::WIDTH);
203                debug_assert!(add < 32);
204                let ret = self.unpack(degree - 1);
205                *self &= !(0x1f << ((degree - 1) * 5));
206                *self <<= 5;
207                *self |= Self::from(add);
208                ret
209            }
210        }
211    };
212}
213impl_packed_fe32!(u32);
214impl_packed_fe32!(u64);
215impl_packed_fe32!(u128);
216
217/// Iterator that yields the field elements that are input into a checksum algorithm for an [`Hrp`].
218pub struct HrpFe32Iter<'hrp> {
219    /// `None` once the hrp high fes have been yielded.
220    high_iter: Option<crate::primitives::hrp::LowercaseByteIter<'hrp>>,
221    /// `None` once the hrp low fes have been yielded.
222    low_iter: Option<crate::primitives::hrp::LowercaseByteIter<'hrp>>,
223}
224
225impl<'hrp> HrpFe32Iter<'hrp> {
226    /// Creates an iterator that yields the field elements of `hrp` as they are input into the
227    /// checksum algorithm.
228    #[inline]
229    pub fn new(hrp: &'hrp Hrp) -> Self {
230        let high_iter = hrp.lowercase_byte_iter();
231        let low_iter = hrp.lowercase_byte_iter();
232
233        Self { high_iter: Some(high_iter), low_iter: Some(low_iter) }
234    }
235}
236
237impl<'hrp> Iterator for HrpFe32Iter<'hrp> {
238    type Item = Fe32;
239    #[inline]
240    fn next(&mut self) -> Option<Fe32> {
241        if let Some(ref mut high_iter) = &mut self.high_iter {
242            match high_iter.next() {
243                Some(high) => return Some(Fe32(high >> 5)),
244                None => {
245                    self.high_iter = None;
246                    return Some(Fe32::Q);
247                }
248            }
249        }
250        if let Some(ref mut low_iter) = &mut self.low_iter {
251            match low_iter.next() {
252                Some(low) => return Some(Fe32(low & 0x1f)),
253                None => self.low_iter = None,
254            }
255        }
256        None
257    }
258
259    #[inline]
260    fn size_hint(&self) -> (usize, Option<usize>) {
261        let high = match &self.high_iter {
262            Some(high_iter) => {
263                let (min, max) = high_iter.size_hint();
264                (min + 1, max.map(|max| max + 1)) // +1 for the extra Q
265            }
266            None => (0, Some(0)),
267        };
268        let low = match &self.low_iter {
269            Some(low_iter) => low_iter.size_hint(),
270            None => (0, Some(0)),
271        };
272
273        let min = high.0 + 1 + low.0;
274        let max = high.1.zip(low.1).map(|(high, low)| high + 1 + low);
275
276        (min, max)
277    }
278}