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}