Skip to content

Commit ac74415

Browse files
committed
Merge #142: Enforce maximum length
12d7b38 Enforce maximum string length (Tobin C. Harding) f5bd09b Add encoded_length functions (Tobin C. Harding) Pull request description: Enforce maximum string length when encoding. Fix: #140 ACKs for top commit: apoelstra: ACK 12d7b38 Tree-SHA512: cbfa1507ae37cc24b26001399e1ebb908c2a7086c18149e1061d3b14d3590ca8f007fbe80948736de326cceb59320df4b4dcce886a4790b25d5ba4b3ed802996
2 parents 2e06b36 + 12d7b38 commit ac74415

File tree

8 files changed

+406
-34
lines changed

8 files changed

+406
-34
lines changed

src/lib.rs

Lines changed: 178 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,12 @@
2323
//! The original description in [BIP-0173](https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki)
2424
//! has more details. See also [BIP-0350](https://github.com/bitcoin/bips/blob/master/bip-0350.mediawiki).
2525
//!
26+
//! # Deviation from spec
27+
//!
28+
//! We do not enforce the 90 character limit specified by [BIP-173], instead we enforce the code
29+
//! length for the respective checksum algorithm (see [`Checksum::CODE_LENGTH`]). We do however
30+
//! enforce the 90 character limit within the `segwit` modules and types.
31+
//!
2632
//! # Examples
2733
//!
2834
//! ## Encoding
@@ -100,6 +106,7 @@
100106
//! impl Checksum for Codex32 {
101107
//! type MidstateRepr = u128;
102108
//! const CHECKSUM_LENGTH: usize = 13;
109+
//! const CODE_LENGTH: usize = 93;
103110
//! // Copied from BIP-93
104111
//! const GENERATOR_SH: [u128; 5] = [
105112
//! 0x19dc500ce73fde210,
@@ -113,6 +120,8 @@
113120
//!
114121
//! # }
115122
//! ```
123+
//!
124+
//! [`Checksum::CODE_LENGTH`]: crate::primitives::checksum::Checksum::CODE_LENGTH
116125
117126
#![cfg_attr(all(not(feature = "std"), not(test)), no_std)]
118127
// Experimental features we need.
@@ -142,14 +151,12 @@ pub mod segwit;
142151
use alloc::{string::String, vec::Vec};
143152
use core::fmt;
144153

145-
#[cfg(feature = "alloc")]
146154
use crate::error::write_err;
147155
#[cfg(doc)]
148156
use crate::primitives::decode::CheckedHrpstring;
157+
use crate::primitives::decode::CodeLengthError;
149158
#[cfg(feature = "alloc")]
150-
use crate::primitives::decode::UncheckedHrpstringError;
151-
#[cfg(feature = "alloc")]
152-
use crate::primitives::decode::{ChecksumError, UncheckedHrpstring};
159+
use crate::primitives::decode::{ChecksumError, UncheckedHrpstring, UncheckedHrpstringError};
153160

154161
#[rustfmt::skip] // Keep public re-exports separate.
155162
#[doc(inline)]
@@ -216,7 +223,7 @@ pub fn decode(s: &str) -> Result<(Hrp, Vec<u8>), DecodeError> {
216223
/// `Ck` algorithm (`NoChecksum` to exclude checksum all together).
217224
#[cfg(feature = "alloc")]
218225
#[inline]
219-
pub fn encode<Ck: Checksum>(hrp: Hrp, data: &[u8]) -> Result<String, fmt::Error> {
226+
pub fn encode<Ck: Checksum>(hrp: Hrp, data: &[u8]) -> Result<String, EncodeError> {
220227
encode_lower::<Ck>(hrp, data)
221228
}
222229

@@ -226,7 +233,7 @@ pub fn encode<Ck: Checksum>(hrp: Hrp, data: &[u8]) -> Result<String, fmt::Error>
226233
/// `Ck` algorithm (`NoChecksum` to exclude checksum all together).
227234
#[cfg(feature = "alloc")]
228235
#[inline]
229-
pub fn encode_lower<Ck: Checksum>(hrp: Hrp, data: &[u8]) -> Result<String, fmt::Error> {
236+
pub fn encode_lower<Ck: Checksum>(hrp: Hrp, data: &[u8]) -> Result<String, EncodeError> {
230237
let mut buf = String::new();
231238
encode_lower_to_fmt::<Ck, String>(&mut buf, hrp, data)?;
232239
Ok(buf)
@@ -238,7 +245,7 @@ pub fn encode_lower<Ck: Checksum>(hrp: Hrp, data: &[u8]) -> Result<String, fmt::
238245
/// `Ck` algorithm (`NoChecksum` to exclude checksum all together).
239246
#[cfg(feature = "alloc")]
240247
#[inline]
241-
pub fn encode_upper<Ck: Checksum>(hrp: Hrp, data: &[u8]) -> Result<String, fmt::Error> {
248+
pub fn encode_upper<Ck: Checksum>(hrp: Hrp, data: &[u8]) -> Result<String, EncodeError> {
242249
let mut buf = String::new();
243250
encode_upper_to_fmt::<Ck, String>(&mut buf, hrp, data)?;
244251
Ok(buf)
@@ -253,7 +260,7 @@ pub fn encode_to_fmt<Ck: Checksum, W: fmt::Write>(
253260
fmt: &mut W,
254261
hrp: Hrp,
255262
data: &[u8],
256-
) -> Result<(), fmt::Error> {
263+
) -> Result<(), EncodeError> {
257264
encode_lower_to_fmt::<Ck, W>(fmt, hrp, data)
258265
}
259266

@@ -266,7 +273,9 @@ pub fn encode_lower_to_fmt<Ck: Checksum, W: fmt::Write>(
266273
fmt: &mut W,
267274
hrp: Hrp,
268275
data: &[u8],
269-
) -> Result<(), fmt::Error> {
276+
) -> Result<(), EncodeError> {
277+
let _ = encoded_length::<Ck>(hrp, data)?;
278+
270279
let iter = data.iter().copied().bytes_to_fes();
271280
let chars = iter.with_checksum::<Ck>(&hrp).chars();
272281
for c in chars {
@@ -284,7 +293,9 @@ pub fn encode_upper_to_fmt<Ck: Checksum, W: fmt::Write>(
284293
fmt: &mut W,
285294
hrp: Hrp,
286295
data: &[u8],
287-
) -> Result<(), fmt::Error> {
296+
) -> Result<(), EncodeError> {
297+
let _ = encoded_length::<Ck>(hrp, data)?;
298+
288299
let iter = data.iter().copied().bytes_to_fes();
289300
let chars = iter.with_checksum::<Ck>(&hrp).chars();
290301
for c in chars {
@@ -303,7 +314,7 @@ pub fn encode_to_writer<Ck: Checksum, W: std::io::Write>(
303314
w: &mut W,
304315
hrp: Hrp,
305316
data: &[u8],
306-
) -> Result<(), std::io::Error> {
317+
) -> Result<(), EncodeIoError> {
307318
encode_lower_to_writer::<Ck, W>(w, hrp, data)
308319
}
309320

@@ -317,7 +328,9 @@ pub fn encode_lower_to_writer<Ck: Checksum, W: std::io::Write>(
317328
w: &mut W,
318329
hrp: Hrp,
319330
data: &[u8],
320-
) -> Result<(), std::io::Error> {
331+
) -> Result<(), EncodeIoError> {
332+
let _ = encoded_length::<Ck>(hrp, data)?;
333+
321334
let iter = data.iter().copied().bytes_to_fes();
322335
let chars = iter.with_checksum::<Ck>(&hrp).chars();
323336
for c in chars {
@@ -336,7 +349,9 @@ pub fn encode_upper_to_writer<Ck: Checksum, W: std::io::Write>(
336349
w: &mut W,
337350
hrp: Hrp,
338351
data: &[u8],
339-
) -> Result<(), std::io::Error> {
352+
) -> Result<(), EncodeIoError> {
353+
let _ = encoded_length::<Ck>(hrp, data)?;
354+
340355
let iter = data.iter().copied().bytes_to_fes();
341356
let chars = iter.with_checksum::<Ck>(&hrp).chars();
342357
for c in chars {
@@ -345,6 +360,25 @@ pub fn encode_upper_to_writer<Ck: Checksum, W: std::io::Write>(
345360
Ok(())
346361
}
347362

363+
/// Checks that encoding `hrp` and `data` creates a code that is less than the code length for `Ck`.
364+
///
365+
/// The length of the code is how long a coded message can be (including the checksum!) for the code
366+
/// to retain its error-correcting properties.
367+
///
368+
/// # Returns
369+
///
370+
/// `Ok(encoded_string_length)` if the encoded length is less than or equal to `Ck::CODE_LENGTH`
371+
/// otherwise a [`CodeLengthError`] containing the encoded length and the maximum allowed.
372+
pub fn encoded_length<Ck: Checksum>(hrp: Hrp, data: &[u8]) -> Result<usize, CodeLengthError> {
373+
let iter = data.iter().copied().bytes_to_fes();
374+
let len = hrp.len() + 1 + iter.len() + Ck::CHECKSUM_LENGTH; // +1 for separator
375+
if len > Ck::CODE_LENGTH {
376+
Err(CodeLengthError { encoded_length: len, code_length: Ck::CODE_LENGTH })
377+
} else {
378+
Ok(len)
379+
}
380+
}
381+
348382
/// An error while decoding a bech32 string.
349383
#[cfg(feature = "alloc")]
350384
#[derive(Debug, Clone, PartialEq, Eq)]
@@ -386,11 +420,101 @@ impl From<UncheckedHrpstringError> for DecodeError {
386420
fn from(e: UncheckedHrpstringError) -> Self { Self::Parse(e) }
387421
}
388422

423+
/// An error while encoding a bech32 string.
424+
#[derive(Debug, Clone, PartialEq, Eq)]
425+
#[non_exhaustive]
426+
pub enum EncodeError {
427+
/// Encoding HRP and data into a bech32 string exceeds maximum allowed.
428+
TooLong(CodeLengthError),
429+
/// Encode to formatter failed.
430+
Fmt(fmt::Error),
431+
}
432+
433+
impl fmt::Display for EncodeError {
434+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
435+
use EncodeError::*;
436+
437+
match *self {
438+
TooLong(ref e) => write_err!(f, "encode error"; e),
439+
Fmt(ref e) => write_err!(f, "encode to formatter failed"; e),
440+
}
441+
}
442+
}
443+
444+
#[cfg(feature = "std")]
445+
impl std::error::Error for EncodeError {
446+
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
447+
use EncodeError::*;
448+
449+
match *self {
450+
TooLong(ref e) => Some(e),
451+
Fmt(ref e) => Some(e),
452+
}
453+
}
454+
}
455+
456+
impl From<CodeLengthError> for EncodeError {
457+
#[inline]
458+
fn from(e: CodeLengthError) -> Self { Self::TooLong(e) }
459+
}
460+
461+
impl From<fmt::Error> for EncodeError {
462+
#[inline]
463+
fn from(e: fmt::Error) -> Self { Self::Fmt(e) }
464+
}
465+
466+
/// An error while encoding a bech32 string.
467+
#[cfg(feature = "std")]
468+
#[derive(Debug)]
469+
#[non_exhaustive]
470+
pub enum EncodeIoError {
471+
/// Encoding HRP and data into a bech32 string exceeds maximum allowed.
472+
TooLong(CodeLengthError),
473+
/// Encode to writer failed.
474+
Write(std::io::Error),
475+
}
476+
477+
#[cfg(feature = "std")]
478+
impl fmt::Display for EncodeIoError {
479+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
480+
use EncodeIoError::*;
481+
482+
match *self {
483+
TooLong(ref e) => write_err!(f, "encode error"; e),
484+
Write(ref e) => write_err!(f, "encode to writer failed"; e),
485+
}
486+
}
487+
}
488+
489+
#[cfg(feature = "std")]
490+
impl std::error::Error for EncodeIoError {
491+
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
492+
use EncodeIoError::*;
493+
494+
match *self {
495+
TooLong(ref e) => Some(e),
496+
Write(ref e) => Some(e),
497+
}
498+
}
499+
}
500+
501+
#[cfg(feature = "std")]
502+
impl From<CodeLengthError> for EncodeIoError {
503+
#[inline]
504+
fn from(e: CodeLengthError) -> Self { Self::TooLong(e) }
505+
}
506+
507+
#[cfg(feature = "std")]
508+
impl From<std::io::Error> for EncodeIoError {
509+
#[inline]
510+
fn from(e: std::io::Error) -> Self { Self::Write(e) }
511+
}
512+
389513
#[cfg(test)]
390514
#[cfg(feature = "alloc")]
391515
mod tests {
392516
use super::*;
393-
use crate::Bech32;
517+
use crate::{Bech32, Bech32m};
394518

395519
// Tests below using this data, are based on the test vector (from BIP-173):
396520
// BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4: 0014751e76e8199196d454941c45d1b3a323f1433bd6
@@ -475,4 +599,44 @@ mod tests {
475599
assert_eq!(hrp, Hrp::parse_unchecked("TEST"));
476600
assert_eq!(data, DATA);
477601
}
602+
603+
#[test]
604+
fn encoded_length_works() {
605+
let s = "test1lu08d6qejxtdg4y5r3zarvary0c5xw7kmz4lky";
606+
let (hrp, data) = decode(s).expect("valid string");
607+
608+
let encoded = encode::<Bech32m>(hrp, &data).expect("valid data");
609+
let want = encoded.len();
610+
let got = encoded_length::<Bech32m>(hrp, &data).expect("encoded length");
611+
612+
assert_eq!(got, want);
613+
}
614+
615+
#[test]
616+
fn can_encode_maximum_length_string() {
617+
let data = [0_u8; 632];
618+
let hrp = Hrp::parse_unchecked("abcd");
619+
let s = encode::<Bech32m>(hrp, &data).expect("valid data");
620+
assert_eq!(s.len(), 1023);
621+
}
622+
623+
#[test]
624+
fn can_not_encode_string_too_long() {
625+
let data = [0_u8; 632];
626+
let hrp = Hrp::parse_unchecked("abcde");
627+
628+
match encode::<Bech32m>(hrp, &data) {
629+
Ok(_) => panic!("false positive"),
630+
Err(EncodeError::TooLong(CodeLengthError { encoded_length, code_length: _ })) =>
631+
assert_eq!(encoded_length, 1024),
632+
_ => panic!("false negative"),
633+
}
634+
}
635+
636+
#[test]
637+
fn can_decode_segwit_too_long_string() {
638+
// A 91 character long string, greater than the segwit enforced maximum of 90.
639+
let s = "abcd1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqrw9z3s";
640+
assert!(decode(s).is_ok());
641+
}
478642
}

src/primitives/checksum.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,16 @@ pub trait Checksum {
2727
/// be pretty efficient no matter what.
2828
type MidstateRepr: PackedFe32;
2929

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+
3036
/// The number of characters in the checksum.
3137
///
3238
/// Alternately, the degree of the generator polynomial. This is **not** the same
33-
/// as the "length of the code", which is the maximum number of characters that
34-
/// the checksum can usefully cover.
39+
/// as `Self::CODE_LENGTH`.
3540
const CHECKSUM_LENGTH: usize;
3641

3742
/// The coefficients of the generator polynomial, except the leading monic term,

0 commit comments

Comments
 (0)