From 581a472eea2c2254a40b3c753dd35e12d916f9b7 Mon Sep 17 00:00:00 2001 From: Ben Westgate Date: Thu, 20 Aug 2026 03:01:38 -0500 Subject: [PATCH] rust: select checksum by expanded codeword length MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Account for the HRP expansion when selecting and slicing regular and long checksums. Enforce the invalid 94–95 value gap and update the affected test vectors. Aligns the reference implementation with bitcoin/bips#2258. --- reference/rust-codex32/src/lib.rs | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/reference/rust-codex32/src/lib.rs b/reference/rust-codex32/src/lib.rs index f42fae4..50e761b 100644 --- a/reference/rust-codex32/src/lib.rs +++ b/reference/rust-codex32/src/lib.rs @@ -14,12 +14,12 @@ //! codex32 Reference Implementation //! -//! This project is a reference implementation of BIP-XXX "codex32", a project +//! This project is a reference implementation of BIP-0093 "codex32", a project //! by Leon Olson Curr and Pearlwort Snead to produce checksummed and secret-shared //! BIP32 master seeds. //! //! References: -//! * BIP-XXX +//! * BIP-0093 //! * The codex32 website //! * BIP-0173 "bech32" //! * BIP-0032 "BIP 32" @@ -97,6 +97,13 @@ pub enum Case { Upper, } +fn expanded_length(s: &str) -> usize { + match s.rsplit_once('1') { + Some((hrp, data)) => 2 * hrp.len() + 1 + data.len(), + None => 1 + s.len(), + } +} + /// A codex32 string, containing a valid checksum #[derive(Clone, PartialEq, Eq, Hash, Debug)] pub struct Codex32String(String); @@ -120,7 +127,7 @@ impl Codex32String { /// Construct a codex32 string from a not-yet-checksummed string pub fn from_unchecksummed_string(mut s: String) -> Result { // Determine what checksum to use and extend the string - let (len, mut checksum) = if s.len() < 81 { + let (len, mut checksum) = if expanded_length(&s) < 81 { (13, checksum::Engine::new_codex32_short()) } else { (15, checksum::Engine::new_codex32_long()) @@ -146,9 +153,10 @@ impl Codex32String { /// Construct a codex32 string from an already-checksummed string pub fn from_string(s: String) -> Result { - let (name, mut checksum) = if s.len() >= 48 && s.len() < 94 { + let codeword_length = expanded_length(&s); + let (name, mut checksum) = if s.len() >= 48 && codeword_length < 94 { ("short", checksum::Engine::new_codex32_short()) - } else if s.len() >= 125 && s.len() < 128 { + } else if codeword_length >= 96 && s.len() < 128 { ("long", checksum::Engine::new_codex32_long()) } else { return Err(Error::InvalidLength(s.len())); @@ -179,7 +187,7 @@ impl Codex32String { Some((s1, s2)) => (s1, s2), None => ("", &self.0[..]), }; - let checksum_len = if self.0.len() > 93 { 15 } else { 13 }; + let checksum_len = if expanded_length(&self.0) > 93 { 15 } else { 13 }; let ret = Parts { hrp, threshold: match s.as_bytes()[0] { @@ -361,7 +369,7 @@ impl Codex32String { } // Initialize checksum engine with HRP and header - let mut checksum = if data.len() < 51 { + let mut checksum = if expanded_length(&ret) < 81 { checksum::Engine::new_codex32_short() } else { checksum::Engine::new_codex32_long() @@ -605,14 +613,13 @@ mod tests { let wrong_checksums = [ "ms10fauxsxxxxxxxxxxxxxxxxxxxxxxxxurfvwmdcmymdufv", "ms10fauxsxxxxxxxxxxxxxxxxxxxxxxxxxxcsyppjkd8lz4hx3", - "ms10fauxsxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxu6hwvl5p0l9xf3c", - "ms10fauxsxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxwqey9rfs6smenxa", - "ms10fauxsxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxv70wkzrjr4ntqet", "ms10fauxsxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx3hmlrmpa4zl0v", "ms10fauxsxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxrfggf88znkaup", "ms10fauxsxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxpt7l4aycv9qzj", "ms10fauxsxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxus27z9xtyxyw3", "ms10fauxsxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxcwm4re8fs78vn", + "ms10fauxsxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxr335l5tv88js3", + "ms12fauxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxky0ua3ha84qk8", ]; for chk in wrong_checksums { let err = Codex32String::from_string(chk.into()); @@ -632,17 +639,16 @@ mod tests { #[test] fn bip_invalid_improper_length() { let bad_length = [ + "ms10fauxsxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxwqey9rfs6smenxa", "ms10fauxsxxxxxxxxxxxxxxxxxxxxxxxxw0a4c70rfefn4", "ms10fauxsxxxxxxxxxxxxxxxxxxxxxxxxxk4pavy5n46nea", "ms10fauxsxxxxxxxxxxxxxxxxxxxxxxxxxxx9lrwar5zwng4w", - "ms10fauxsxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxr335l5tv88js3", "ms10fauxsxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxvu7q9nz8p7dj68v", "ms10fauxsxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxpq6k542scdxndq3", "ms10fauxsxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxkmfw6jm270mz6ej", "ms12fauxxxxxxxxxxxxxxxxxxxxxxxxxxzhddxw99w7xws", "ms12fauxxxxxxxxxxxxxxxxxxxxxxxxxxxx42cux6um92rz", "ms12fauxxxxxxxxxxxxxxxxxxxxxxxxxxxxxarja5kqukdhy9", - "ms12fauxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxky0ua3ha84qk8", "ms12fauxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx9eheesxadh2n2n9", "ms12fauxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx9llwmgesfulcj2z", "ms12fauxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx02ev7caq6n9fgkf",