From b44431fae7b6a634b0a81ddcfe08da1434cb3e8c Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Sun, 24 Mar 2024 15:15:04 +0000 Subject: [PATCH] checksum: introduce `PrintImpl` object to print `impl Checksum` blocks For the most part, BCH codes used in the ecosystem are defined as generator polynomials over GF32 and a target residue polynomial, and the remaining code parameters are not specified, or are only specified indirectly. It is difficult for non-experts to compute or even validate parameters such as the length of the code or the shifted+packed generator polynomials. It will be even more difficult in the sequel when we extend the Checksum trait to also cover error-correction parameters (some of which will depend on our particular choice of representation for GF1024 elements, which is not canonical and differs between different documents). So we introduce an object `PrintImpl` and a unit test and doctest demonstrating its use, which will generate all of the needed parameters and output a block of Rust code. --- api/all-features.txt | 12 ++ api/alloc-only.txt | 12 ++ src/lib.rs | 2 + src/primitives/checksum.rs | 233 ++++++++++++++++++++++++++++++++++++- 4 files changed, 257 insertions(+), 2 deletions(-) diff --git a/api/all-features.txt b/api/all-features.txt index 34562bf4e..5b282444e 100644 --- a/api/all-features.txt +++ b/api/all-features.txt @@ -438,6 +438,14 @@ impl core::panic::unwind_safe::UnwindSafe for bech32::primitives::segwit::Invali impl core::panic::unwind_safe::UnwindSafe for bech32::primitives::segwit::WitnessLengthError impl core::panic::unwind_safe::UnwindSafe for bech32::segwit::DecodeError impl core::panic::unwind_safe::UnwindSafe for bech32::segwit::EncodeError +impl<'a, ExtField> bech32::primitives::checksum::PrintImpl<'a, ExtField> +impl<'a, ExtField> core::fmt::Display for bech32::primitives::checksum::PrintImpl<'a, ExtField> where ExtField: bech32::primitives::ExtensionField + core::convert::From +impl<'a, ExtField> core::marker::Freeze for bech32::primitives::checksum::PrintImpl<'a, ExtField> +impl<'a, ExtField> core::marker::Send for bech32::primitives::checksum::PrintImpl<'a, ExtField> where ExtField: core::marker::Send +impl<'a, ExtField> core::marker::Sync for bech32::primitives::checksum::PrintImpl<'a, ExtField> where ExtField: core::marker::Sync +impl<'a, ExtField> core::marker::Unpin for bech32::primitives::checksum::PrintImpl<'a, ExtField> where ExtField: core::marker::Unpin +impl<'a, ExtField> core::panic::unwind_safe::RefUnwindSafe for bech32::primitives::checksum::PrintImpl<'a, ExtField> where ExtField: core::panic::unwind_safe::RefUnwindSafe +impl<'a, ExtField> core::panic::unwind_safe::UnwindSafe for bech32::primitives::checksum::PrintImpl<'a, ExtField> where ExtField: core::panic::unwind_safe::UnwindSafe impl<'a, I, Ck> core::iter::traits::iterator::Iterator for bech32::primitives::encode::ByteIter<'a, I, Ck> where I: core::iter::traits::iterator::Iterator, Ck: bech32::primitives::checksum::Checksum impl<'a, I, Ck> core::iter::traits::iterator::Iterator for bech32::primitives::encode::CharIter<'a, I, Ck> where I: core::iter::traits::iterator::Iterator, Ck: bech32::primitives::checksum::Checksum impl<'b> core::iter::traits::double_ended::DoubleEndedIterator for bech32::primitives::hrp::ByteIter<'b> @@ -893,6 +901,8 @@ pub fn bech32::primitives::checksum::PackedNull::fmt(&self, f: &mut core::fmt::F pub fn bech32::primitives::checksum::PackedNull::mul_by_x_then_add(&mut self, usize, u8) -> u8 pub fn bech32::primitives::checksum::PackedNull::pack>(iter: I) -> Self pub fn bech32::primitives::checksum::PackedNull::unpack(&self, usize) -> u8 +pub fn bech32::primitives::checksum::PrintImpl<'a, ExtField>::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result +pub fn bech32::primitives::checksum::PrintImpl<'a, ExtField>::new(name: &'a str, generator: &'a [bech32::primitives::gf32::Fe32], target: &'a [bech32::primitives::gf32::Fe32]) -> Self pub fn bech32::primitives::decode::AsciiToFe32Iter<'s>::len(&self) -> usize pub fn bech32::primitives::decode::AsciiToFe32Iter<'s>::next(&mut self) -> core::option::Option pub fn bech32::primitives::decode::AsciiToFe32Iter<'s>::size_hint(&self) -> (usize, core::option::Option) @@ -1194,10 +1204,12 @@ pub mod bech32::primitives::iter pub mod bech32::primitives::segwit pub mod bech32::segwit pub struct bech32::Hrp +pub struct bech32::PrintImpl<'a, ExtField> pub struct bech32::hrp::Hrp pub struct bech32::primitives::checksum::Engine pub struct bech32::primitives::checksum::HrpFe32Iter<'hrp> pub struct bech32::primitives::checksum::PackedNull +pub struct bech32::primitives::checksum::PrintImpl<'a, ExtField> pub struct bech32::primitives::decode::AsciiToFe32Iter<'s> pub struct bech32::primitives::decode::ByteIter<'s> pub struct bech32::primitives::decode::CheckedHrpstring<'s> diff --git a/api/alloc-only.txt b/api/alloc-only.txt index a13fc1f5f..ddf1cb3b3 100644 --- a/api/alloc-only.txt +++ b/api/alloc-only.txt @@ -409,6 +409,14 @@ impl core::panic::unwind_safe::UnwindSafe for bech32::primitives::segwit::Invali impl core::panic::unwind_safe::UnwindSafe for bech32::primitives::segwit::WitnessLengthError impl core::panic::unwind_safe::UnwindSafe for bech32::segwit::DecodeError impl core::panic::unwind_safe::UnwindSafe for bech32::segwit::EncodeError +impl<'a, ExtField> bech32::primitives::checksum::PrintImpl<'a, ExtField> +impl<'a, ExtField> core::fmt::Display for bech32::primitives::checksum::PrintImpl<'a, ExtField> where ExtField: bech32::primitives::ExtensionField + core::convert::From +impl<'a, ExtField> core::marker::Freeze for bech32::primitives::checksum::PrintImpl<'a, ExtField> +impl<'a, ExtField> core::marker::Send for bech32::primitives::checksum::PrintImpl<'a, ExtField> where ExtField: core::marker::Send +impl<'a, ExtField> core::marker::Sync for bech32::primitives::checksum::PrintImpl<'a, ExtField> where ExtField: core::marker::Sync +impl<'a, ExtField> core::marker::Unpin for bech32::primitives::checksum::PrintImpl<'a, ExtField> where ExtField: core::marker::Unpin +impl<'a, ExtField> core::panic::unwind_safe::RefUnwindSafe for bech32::primitives::checksum::PrintImpl<'a, ExtField> where ExtField: core::panic::unwind_safe::RefUnwindSafe +impl<'a, ExtField> core::panic::unwind_safe::UnwindSafe for bech32::primitives::checksum::PrintImpl<'a, ExtField> where ExtField: core::panic::unwind_safe::UnwindSafe impl<'a, I, Ck> core::iter::traits::iterator::Iterator for bech32::primitives::encode::ByteIter<'a, I, Ck> where I: core::iter::traits::iterator::Iterator, Ck: bech32::primitives::checksum::Checksum impl<'a, I, Ck> core::iter::traits::iterator::Iterator for bech32::primitives::encode::CharIter<'a, I, Ck> where I: core::iter::traits::iterator::Iterator, Ck: bech32::primitives::checksum::Checksum impl<'b> core::iter::traits::double_ended::DoubleEndedIterator for bech32::primitives::hrp::ByteIter<'b> @@ -853,6 +861,8 @@ pub fn bech32::primitives::checksum::PackedNull::fmt(&self, f: &mut core::fmt::F pub fn bech32::primitives::checksum::PackedNull::mul_by_x_then_add(&mut self, usize, u8) -> u8 pub fn bech32::primitives::checksum::PackedNull::pack>(iter: I) -> Self pub fn bech32::primitives::checksum::PackedNull::unpack(&self, usize) -> u8 +pub fn bech32::primitives::checksum::PrintImpl<'a, ExtField>::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result +pub fn bech32::primitives::checksum::PrintImpl<'a, ExtField>::new(name: &'a str, generator: &'a [bech32::primitives::gf32::Fe32], target: &'a [bech32::primitives::gf32::Fe32]) -> Self pub fn bech32::primitives::decode::AsciiToFe32Iter<'s>::len(&self) -> usize pub fn bech32::primitives::decode::AsciiToFe32Iter<'s>::next(&mut self) -> core::option::Option pub fn bech32::primitives::decode::AsciiToFe32Iter<'s>::size_hint(&self) -> (usize, core::option::Option) @@ -1136,10 +1146,12 @@ pub mod bech32::primitives::iter pub mod bech32::primitives::segwit pub mod bech32::segwit pub struct bech32::Hrp +pub struct bech32::PrintImpl<'a, ExtField> pub struct bech32::hrp::Hrp pub struct bech32::primitives::checksum::Engine pub struct bech32::primitives::checksum::HrpFe32Iter<'hrp> pub struct bech32::primitives::checksum::PackedNull +pub struct bech32::primitives::checksum::PrintImpl<'a, ExtField> pub struct bech32::primitives::decode::AsciiToFe32Iter<'s> pub struct bech32::primitives::decode::ByteIter<'s> pub struct bech32::primitives::decode::CheckedHrpstring<'s> diff --git a/src/lib.rs b/src/lib.rs index 4ca1381b1..41e474af4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -166,6 +166,8 @@ pub use { crate::primitives::iter::{ByteIterExt, Fe32IterExt}, crate::primitives::{Bech32, Bech32m, NoChecksum}, }; +#[cfg(feature = "alloc")] +pub use crate::primitives::checksum::PrintImpl; // Write to fmt buffer, small during testing to exercise full code path. #[cfg(not(test))] diff --git a/src/primitives/checksum.rs b/src/primitives/checksum.rs index e3724d816..d9ee80d4f 100644 --- a/src/primitives/checksum.rs +++ b/src/primitives/checksum.rs @@ -4,10 +4,20 @@ //! //! [BCH]: +#[cfg(all(feature = "alloc", not(feature = "std"), not(test)))] +use alloc::vec::Vec; +#[cfg(feature = "alloc")] +use core::fmt; +#[cfg(feature = "alloc")] +use core::marker::PhantomData; use core::{mem, ops}; -use crate::primitives::gf32::Fe32; +#[cfg(feature = "alloc")] +use super::Polynomial; use crate::primitives::hrp::Hrp; +#[cfg(feature = "alloc")] +use crate::Fe1024; +use crate::Fe32; /// Trait defining a particular checksum. /// @@ -49,7 +59,7 @@ pub trait Checksum { /// /// These cannot be usefully pre-computed because of Rust's limited constfn support /// as of 1.67, so they must be specified manually for each checksum. To check the - /// values for consistency, run `Self::sanity_check()`. + /// values for consistency, run [`Self::sanity_check`]. const GENERATOR_SH: [Self::MidstateRepr; 5]; /// The residue, modulo the generator polynomial, that a valid codeword will have. @@ -83,6 +93,184 @@ pub trait Checksum { } } +/// Given a polynomial representation for your generator polynomial and your +/// target residue, outputs a `impl Checksum` block. +/// +/// You must specify an extension field. You should try [`crate::Fe1024`], and if +/// you get an error about a polynomial not splitting, try [`crate::Fe32768`]. +/// +/// Used like +/// +/// ``` +/// # #[cfg(feature = "alloc")] { +/// use core::convert::TryFrom; +/// +/// use bech32::{Fe32, Fe1024, PrintImpl}; +/// use bech32::primitives::checksum::PackedFe32; +/// +/// // In codes specified in BIPs, the code generator polynomial and residue +/// // are often only given indirectly, in the reference code which encodes +/// // it in a packed form (shifted multiple times). +/// // +/// // For example in the BIP173 Python reference code you will see an array +/// // called `generator` whose first entry is 0x3b6a57b2. This first entry +/// // is the generator polynomial in packed form. +/// // +/// // To get the expanded polynomial form you can use `u128::unpack` like so: +/// let unpacked_poly = (0..6) +/// .rev() // Note .rev() to convert from BE integer literal to LE polynomial! +/// .map(|i| 0x3b6a57b2u128.unpack(i)) +/// .map(|u| Fe32::try_from(u).unwrap()) +/// .collect::>(); +/// let unpacked_residue = (0..6) +/// .rev() +/// .map(|i| 0x1u128.unpack(i)) +/// .map(|u| Fe32::try_from(u).unwrap()) +/// .collect::>(); +/// println!( +/// "{}", +/// PrintImpl::::new( +/// "Bech32", +/// &unpacked_poly, +/// &unpacked_residue, +/// ), +/// ); +/// # } +/// ``` +/// +/// The awkward API is to allow this type to be used in the widest set of +/// circumstances, including in nostd settings. (However, the underlying +/// polynomial math requires the `alloc` feature.) +/// +/// Both polynomial representations should be in little-endian order, so that +/// the coefficient of x^i appears in the ith slot. The generator polynomial +/// should be a monic polynomial but you should not include the monic term, +/// so that both `generator` and `target` are arrays of the same length. +/// +/// **This function should never need to be called by users, but will be helpful +/// for developers.** +/// +/// In general, when defining a checksum, it is best to call this method (and +/// to add a unit test that calls [`Checksum::sanity_check`] rather than trying +/// to compute the values yourself. The reason is that the specific values +/// used depend on the representation of extension fields, which may differ +/// between implementations (and between specifications) of your BCH code. +#[cfg(feature = "alloc")] +pub struct PrintImpl<'a, ExtField = Fe1024> { + name: &'a str, + generator: &'a [Fe32], + target: &'a [Fe32], + bit_len: usize, + hex_width: usize, + midstate_repr: &'static str, + phantom: PhantomData, +} + +#[cfg(feature = "alloc")] +impl<'a, ExtField> PrintImpl<'a, ExtField> { + /// Constructor for an object to print an impl-block for the [`Checksum`] trait. + /// + /// # Panics + /// + /// Panics if any of the input values fail various sanity checks. + pub fn new(name: &'a str, generator: &'a [Fe32], target: &'a [Fe32]) -> Self { + // Sanity checks. + assert_ne!(name.len(), 0, "type name cannot be the empty string",); + assert_ne!( + generator.len(), + 0, + "generator polynomial cannot be the empty string (constant 1)" + ); + assert_ne!(target.len(), 0, "target residue cannot be the empty string"); + if generator.len() != target.len() { + let hint = if generator.len() == target.len() + 1 { + " (you should not include the monic term of the generator polynomial" + } else if generator.len() > target.len() { + " (you may need to zero-pad your target residue)" + } else { + "" + }; + panic!( + "Generator length {} does not match target residue length {}{}", + generator.len(), + target.len(), + hint + ); + } + + let bit_len = 5 * target.len(); + let (hex_width, midstate_repr) = if bit_len <= 32 { + (8, "u32") + } else if bit_len <= 64 { + (16, "u64") + } else if bit_len <= 128 { + (32, "u128") + } else { + panic!("Generator length {} cannot exceed 25, as we cannot represent it by packing bits into a Rust numeric type", generator.len()); + }; + // End sanity checks. + PrintImpl { + name, + generator, + target, + bit_len, + hex_width, + midstate_repr, + phantom: PhantomData, + } + } +} + +#[cfg(feature = "alloc")] +impl<'a, ExtField> fmt::Display for PrintImpl<'a, ExtField> +where + ExtField: super::ExtensionField + From, +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + // Generator polynomial as a polynomial over GF1024 + let gen_poly = { + let mut v = Vec::with_capacity(self.generator.len() + 1); + v.push(ExtField::ONE); + v.extend(self.generator.iter().cloned().map(ExtField::from)); + Polynomial::new(v) + }; + let (_gen, length, _exponents) = gen_poly.bch_generator_primitive_element(); + + write!(f, "// Code block generated by Checksum::print_impl polynomial ")?; + for fe in self.generator { + write!(f, "{}", fe)?; + } + write!(f, " target ")?; + for fe in self.target { + write!(f, "{}", fe)?; + } + f.write_str("\n")?; + writeln!(f, "impl Checksum for {} {{", self.name)?; + writeln!( + f, + " type MidstateRepr = {}; // checksum packs into {} bits", + self.midstate_repr, self.bit_len + )?; + writeln!(f, " const CODE_LENGTH: usize = {};", length)?; + writeln!(f, " const CHECKSUM_LENGTH: usize = {};", gen_poly.degree())?; + writeln!(f, " const GENERATOR_SH: [{}; 5] = [", self.midstate_repr)?; + let mut gen5 = self.generator.to_vec(); + for _ in 0..5 { + let gen_packed = u128::pack(gen5.iter().copied().map(From::from)); + writeln!(f, " 0x{:0width$x},", gen_packed, width = self.hex_width)?; + gen5.iter_mut().for_each(|x| *x *= Fe32::Z); + } + writeln!(f, " ];")?; + writeln!( + f, + " const TARGET_RESIDUE: {} = {:?};", + self.midstate_repr, + u128::pack(self.target.iter().copied().map(From::from)) + )?; + f.write_str("}") + } +} + /// A checksum engine, which can be used to compute or verify a checksum. /// /// Use this to verify a checksum, feed it the data to be checksummed using @@ -311,6 +499,9 @@ impl<'hrp> Iterator for HrpFe32Iter<'hrp> { #[cfg(test)] mod tests { + #[cfg(feature = "alloc")] + use core::convert::TryFrom; + use super::*; #[test] @@ -327,4 +518,42 @@ mod tests { assert_eq!(packed.unpack(2), 2); assert_eq!(packed.unpack(3), 1); } + + #[test] + #[cfg(feature = "alloc")] + fn bech32() { + // In codes that Pieter specifies typically the generator polynomial is + // only given indirectly, in the reference code which encodes it in a + // packed form (shifted multiple times). + // + // For example in the BIP173 Python reference code you will see an array + // called `generator` whose first entry is 0x3b6a57b2. This first entry + // is the generator polynomial in packed form. + // + // To get the expanded polynomial form you can use `u128::unpack` like so: + let unpacked_poly = (0..6) + .rev() // Note .rev() to convert from BE integer literal to LE polynomial! + .map(|i| 0x3b6a57b2u128.unpack(i)) + .map(|u| Fe32::try_from(u).unwrap()) + .collect::>(); + assert_eq!(unpacked_poly, [Fe32::A, Fe32::K, Fe32::_5, Fe32::_4, Fe32::A, Fe32::J],); + // To get a version of the above with bech32 chars instead of Fe32s, which + // can be a bit hard to print, just stick a `.map(Fe32::to_char)` into the + // above iterator chain. + + // Ok, exposition over. The actual unit test follows. + + // Run with -- --nocapture to see the output of this. This unit test + // does not check the exact output because it is not deterministic, + // and cannot check the code semantics because Rust does not have + // any sort of `eval`, but you can manually check the output works. + let _s = PrintImpl::::new( + "Bech32", + &[Fe32::A, Fe32::K, Fe32::_5, Fe32::_4, Fe32::A, Fe32::J], + &[Fe32::Q, Fe32::Q, Fe32::Q, Fe32::Q, Fe32::Q, Fe32::P], + ) + .to_string(); + #[cfg(feature = "std")] + println!("{}", _s); + } }