Skip to content

Commit

Permalink
checksum: introduce PrintImpl object to print impl Checksum blocks
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
apoelstra committed Mar 25, 2024
1 parent 4444f62 commit eea45d6
Show file tree
Hide file tree
Showing 4 changed files with 216 additions and 2 deletions.
11 changes: 11 additions & 0 deletions api/all-features.txt
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,13 @@ impl core::panic::unwind_safe::UnwindSafe for bech32::segwit::DecodeError
impl core::panic::unwind_safe::UnwindSafe for bech32::segwit::EncodeError
impl<'a, I, Ck> core::iter::traits::iterator::Iterator for bech32::primitives::encode::ByteIter<'a, I, Ck> where I: core::iter::traits::iterator::Iterator<Item = bech32::primitives::gf32::Fe32>, 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<Item = bech32::primitives::gf32::Fe32>, Ck: bech32::primitives::checksum::Checksum
impl<'a, T: bech32::primitives::checksum::Checksum> bech32::primitives::checksum::PrintImpl<'a, T>
impl<'a, T: bech32::primitives::checksum::Checksum> core::fmt::Display for bech32::primitives::checksum::PrintImpl<'a, T>
impl<'a, T> core::marker::Send for bech32::primitives::checksum::PrintImpl<'a, T> where T: core::marker::Send
impl<'a, T> core::marker::Sync for bech32::primitives::checksum::PrintImpl<'a, T> where T: core::marker::Sync
impl<'a, T> core::marker::Unpin for bech32::primitives::checksum::PrintImpl<'a, T> where T: core::marker::Unpin
impl<'a, T> core::panic::unwind_safe::RefUnwindSafe for bech32::primitives::checksum::PrintImpl<'a, T> where T: core::panic::unwind_safe::RefUnwindSafe
impl<'a, T> core::panic::unwind_safe::UnwindSafe for bech32::primitives::checksum::PrintImpl<'a, T> where T: core::panic::unwind_safe::UnwindSafe
impl<'b> core::iter::traits::double_ended::DoubleEndedIterator for bech32::primitives::hrp::ByteIter<'b>
impl<'b> core::iter::traits::double_ended::DoubleEndedIterator for bech32::primitives::hrp::CharIter<'b>
impl<'b> core::iter::traits::double_ended::DoubleEndedIterator for bech32::primitives::hrp::LowercaseByteIter<'b>
Expand Down Expand Up @@ -820,6 +827,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<I: core::iter::traits::iterator::Iterator<Item = u8>>(iter: I) -> Self
pub fn bech32::primitives::checksum::PackedNull::unpack(&self, usize) -> u8
pub fn bech32::primitives::checksum::PrintImpl<'a, T>::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result
pub fn bech32::primitives::checksum::PrintImpl<'a, T>::new(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<bech32::primitives::gf32::Fe32>
pub fn bech32::primitives::decode::AsciiToFe32Iter<'s>::size_hint(&self) -> (usize, core::option::Option<usize>)
Expand Down Expand Up @@ -1110,10 +1119,12 @@ pub mod bech32::primitives::iter
pub mod bech32::primitives::segwit
pub mod bech32::segwit
pub struct bech32::Hrp
pub struct bech32::PrintImpl<'a, T>
pub struct bech32::hrp::Hrp
pub struct bech32::primitives::checksum::Engine<Ck: bech32::primitives::checksum::Checksum>
pub struct bech32::primitives::checksum::HrpFe32Iter<'hrp>
pub struct bech32::primitives::checksum::PackedNull
pub struct bech32::primitives::checksum::PrintImpl<'a, T>
pub struct bech32::primitives::decode::AsciiToFe32Iter<'s>
pub struct bech32::primitives::decode::ByteIter<'s>
pub struct bech32::primitives::decode::CheckedHrpstring<'s>
Expand Down
11 changes: 11 additions & 0 deletions api/alloc-only.txt
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,13 @@ impl core::panic::unwind_safe::UnwindSafe for bech32::segwit::DecodeError
impl core::panic::unwind_safe::UnwindSafe for bech32::segwit::EncodeError
impl<'a, I, Ck> core::iter::traits::iterator::Iterator for bech32::primitives::encode::ByteIter<'a, I, Ck> where I: core::iter::traits::iterator::Iterator<Item = bech32::primitives::gf32::Fe32>, 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<Item = bech32::primitives::gf32::Fe32>, Ck: bech32::primitives::checksum::Checksum
impl<'a, T: bech32::primitives::checksum::Checksum> bech32::primitives::checksum::PrintImpl<'a, T>
impl<'a, T: bech32::primitives::checksum::Checksum> core::fmt::Display for bech32::primitives::checksum::PrintImpl<'a, T>
impl<'a, T> core::marker::Send for bech32::primitives::checksum::PrintImpl<'a, T> where T: core::marker::Send
impl<'a, T> core::marker::Sync for bech32::primitives::checksum::PrintImpl<'a, T> where T: core::marker::Sync
impl<'a, T> core::marker::Unpin for bech32::primitives::checksum::PrintImpl<'a, T> where T: core::marker::Unpin
impl<'a, T> core::panic::unwind_safe::RefUnwindSafe for bech32::primitives::checksum::PrintImpl<'a, T> where T: core::panic::unwind_safe::RefUnwindSafe
impl<'a, T> core::panic::unwind_safe::UnwindSafe for bech32::primitives::checksum::PrintImpl<'a, T> where T: core::panic::unwind_safe::UnwindSafe
impl<'b> core::iter::traits::double_ended::DoubleEndedIterator for bech32::primitives::hrp::ByteIter<'b>
impl<'b> core::iter::traits::double_ended::DoubleEndedIterator for bech32::primitives::hrp::CharIter<'b>
impl<'b> core::iter::traits::double_ended::DoubleEndedIterator for bech32::primitives::hrp::LowercaseByteIter<'b>
Expand Down Expand Up @@ -781,6 +788,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<I: core::iter::traits::iterator::Iterator<Item = u8>>(iter: I) -> Self
pub fn bech32::primitives::checksum::PackedNull::unpack(&self, usize) -> u8
pub fn bech32::primitives::checksum::PrintImpl<'a, T>::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result
pub fn bech32::primitives::checksum::PrintImpl<'a, T>::new(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<bech32::primitives::gf32::Fe32>
pub fn bech32::primitives::decode::AsciiToFe32Iter<'s>::size_hint(&self) -> (usize, core::option::Option<usize>)
Expand Down Expand Up @@ -1053,10 +1062,12 @@ pub mod bech32::primitives::iter
pub mod bech32::primitives::segwit
pub mod bech32::segwit
pub struct bech32::Hrp
pub struct bech32::PrintImpl<'a, T>
pub struct bech32::hrp::Hrp
pub struct bech32::primitives::checksum::Engine<Ck: bech32::primitives::checksum::Checksum>
pub struct bech32::primitives::checksum::HrpFe32Iter<'hrp>
pub struct bech32::primitives::checksum::PackedNull
pub struct bech32::primitives::checksum::PrintImpl<'a, T>
pub struct bech32::primitives::decode::AsciiToFe32Iter<'s>
pub struct bech32::primitives::decode::ByteIter<'s>
pub struct bech32::primitives::decode::CheckedHrpstring<'s>
Expand Down
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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))]
Expand Down
194 changes: 192 additions & 2 deletions src/primitives/checksum.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,20 @@
//!
//! [BCH]: <https://en.wikipedia.org/wiki/BCH_code>

#[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::{Field, Polynomial};
use crate::primitives::hrp::Hrp;
#[cfg(feature = "alloc")]
use crate::Fe1024;
use crate::Fe32;

/// Trait defining a particular checksum.
///
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -83,6 +93,144 @@ pub trait Checksum {
}
}

/// Given a polynomial representation for your generator polynomial and your
/// target residue, outputs a `impl Checksum` block.
///
/// Used like
///
/// ```
/// # #[cfg(feature = "alloc")] {
/// use bech32::{Bech32, Fe32, PrintImpl};
/// println!(
/// "{}",
/// PrintImpl::<Bech32>::new(
/// &[Fe32::A, Fe32::K, Fe32::_5, Fe32::_4, Fe32::A, Fe32::J],
/// &[Fe32::Q, Fe32::Q, Fe32::Q, Fe32::Q, Fe32::Q, Fe32::P],
/// ),
/// );
/// # }
/// ```
///
/// 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, T> {
generator: &'a [Fe32],
target: &'a [Fe32],
bit_len: usize,
hex_width: usize,
midstate_repr: &'static str,
phantom: PhantomData<T>,
}

#[cfg(feature = "alloc")]
impl<'a, T: Checksum> PrintImpl<'a, T> {
/// 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(generator: &'a [Fe32], target: &'a [Fe32]) -> Self {
// Sanity checks.
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 { generator, target, bit_len, hex_width, midstate_repr, phantom: PhantomData }
}
}

#[cfg(feature = "alloc")]
impl<'a, T: Checksum> fmt::Display for PrintImpl<'a, T> {
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(Fe1024::ONE);
v.extend(self.generator.iter().cloned().map(Fe1024::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("")?;
writeln!(f, "impl Checksum for {} {{", core::any::type_name::<T>())?;
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
Expand Down Expand Up @@ -311,7 +459,12 @@ impl<'hrp> Iterator for HrpFe32Iter<'hrp> {

#[cfg(test)]
mod tests {
#[cfg(feature = "alloc")]
use core::convert::TryFrom;

use super::*;
#[cfg(feature = "alloc")]
use crate::Bech32;

#[test]
fn pack_unpack() {
Expand All @@ -327,4 +480,41 @@ 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::<Vec<_>>();
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::<Bech32>::new(
&[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);
}
}

0 comments on commit eea45d6

Please sign in to comment.