Skip to content

Commit f52be3d

Browse files
committed
Merge #120: Add segwit API
024ed01 Add segwit version field element consts (Tobin C. Harding) 6c1379b Add segwit API (Tobin C. Harding) Pull request description: Add a `segwit` API with the aim that "typical" modern bitcoin usage is easy and correct. Done in a separate module so as not to impact the main crate API. ACKs for top commit: apoelstra: ACK 024ed01 Tree-SHA512: 169e1a836f122fa3344857eec5945034afc2c727d1d6df57d5f3c5cde7a994d79398060cca5561a3706af0c835efafebaaa619df7b49f5c64acee01587259832
2 parents 72e7dd8 + 024ed01 commit f52be3d

File tree

7 files changed

+397
-62
lines changed

7 files changed

+397
-62
lines changed

src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ pub use crate::primitives::{Bech32, Bech32m};
4343

4444
mod error;
4545
pub mod primitives;
46+
pub mod segwit;
4647

4748
pub use primitives::gf32::Fe32 as u5;
4849

src/primitives/decode.rs

+10-53
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
//! ```
3636
//! use bech32::{Bech32, Bech32m, Fe32, Hrp};
3737
//! use bech32::primitives::decode::{CheckedHrpstring, SegwitHrpstring, UncheckedHrpstring};
38+
//! use bech32::segwit::VERSION_1;
3839
//!
3940
//! // An arbitrary HRP and a string of valid bech32 characters.
4041
//! let s = "abcd143hj65vxw49rts6kcw35u6r6tgzguyr03vvveeewjqpn05efzq444444";
@@ -66,7 +67,7 @@
6667
//! let segwit = SegwitHrpstring::new(address).expect("valid segwit address");
6768
//! let _encoded_data = segwit.byte_iter();
6869
//! assert_eq!(segwit.hrp(), Hrp::parse("bc").unwrap());
69-
//! assert_eq!(segwit.witness_version(), Fe32::P);
70+
//! assert_eq!(segwit.witness_version(), VERSION_1);
7071
//! ```
7172
//!
7273
//! [BIP-173]: <https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki>
@@ -78,6 +79,7 @@ use crate::primitives::checksum::{self, Checksum};
7879
use crate::primitives::gf32::Fe32;
7980
use crate::primitives::hrp::{self, Hrp};
8081
use crate::primitives::iter::{Fe32IterExt, FesToBytes};
82+
use crate::primitives::segwit::{self, WitnessLengthError, VERSION_0};
8183
use crate::{write_err, Bech32, Bech32m};
8284

8385
/// Separator between the hrp and payload (as defined by BIP-173).
@@ -274,7 +276,7 @@ impl<'s> CheckedHrpstring<'s> {
274276
self.data = &self.data[1..]; // Remove the witness version byte from data.
275277

276278
self.validate_padding()?;
277-
self.validate_witness_length(witness_version)?;
279+
self.validate_witness_program_length(witness_version)?;
278280

279281
Ok(SegwitHrpstring { hrp: self.hrp(), witness_version, data: self.data })
280282
}
@@ -319,21 +321,11 @@ impl<'s> CheckedHrpstring<'s> {
319321
/// Validates the segwit witness length rules.
320322
///
321323
/// Must be called after the witness version byte is removed from the data.
322-
#[allow(clippy::manual_range_contains)] // For witness length range check.
323-
fn validate_witness_length(&self, witness_version: Fe32) -> Result<(), WitnessLengthError> {
324-
use WitnessLengthError::*;
325-
326-
let witness_len = self.byte_iter().len();
327-
if witness_len < 2 {
328-
return Err(TooShort);
329-
}
330-
if witness_len > 40 {
331-
return Err(TooLong);
332-
}
333-
if witness_version == Fe32::Q && witness_len != 20 && witness_len != 32 {
334-
return Err(InvalidSegwitV0);
335-
}
336-
Ok(())
324+
fn validate_witness_program_length(
325+
&self,
326+
witness_version: Fe32,
327+
) -> Result<(), WitnessLengthError> {
328+
segwit::validate_witness_program_length(self.byte_iter().len(), witness_version)
337329
}
338330
}
339331

@@ -383,7 +375,7 @@ impl<'s> SegwitHrpstring<'s> {
383375
}
384376

385377
let checked: CheckedHrpstring<'s> = match witness_version {
386-
Fe32::Q => unchecked.validate_and_remove_checksum::<Bech32>()?,
378+
VERSION_0 => unchecked.validate_and_remove_checksum::<Bech32>()?,
387379
_ => unchecked.validate_and_remove_checksum::<Bech32m>()?,
388380
};
389381

@@ -770,41 +762,6 @@ impl std::error::Error for ChecksumError {
770762
}
771763
}
772764

773-
/// Witness program invalid because of incorrect length.
774-
#[derive(Debug, Clone, PartialEq, Eq)]
775-
#[non_exhaustive]
776-
pub enum WitnessLengthError {
777-
/// The witness data is too short.
778-
TooShort,
779-
/// The witness data is too long.
780-
TooLong,
781-
/// The segwit v0 witness is not 20 or 32 bytes long.
782-
InvalidSegwitV0,
783-
}
784-
785-
impl fmt::Display for WitnessLengthError {
786-
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
787-
use WitnessLengthError::*;
788-
789-
match *self {
790-
TooShort => write!(f, "witness program is less than 2 bytes long"),
791-
TooLong => write!(f, "witness program is more than 40 bytes long"),
792-
InvalidSegwitV0 => write!(f, "the segwit v0 witness is not 20 or 32 bytes long"),
793-
}
794-
}
795-
}
796-
797-
#[cfg(feature = "std")]
798-
impl std::error::Error for WitnessLengthError {
799-
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
800-
use WitnessLengthError::*;
801-
802-
match *self {
803-
TooShort | TooLong | InvalidSegwitV0 => None,
804-
}
805-
}
806-
}
807-
808765
/// Error validating the padding bits on the witness data.
809766
#[derive(Debug, Clone, PartialEq, Eq)]
810767
pub enum PaddingError {

src/primitives/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ pub mod encode;
88
pub mod gf32;
99
pub mod hrp;
1010
pub mod iter;
11+
pub mod segwit;
1112

1213
use checksum::{Checksum, PackedNull};
1314

src/primitives/segwit.rs

+103
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
// SPDX-License-Identifier: MIT
2+
3+
//! Segregated Witness functionality - useful for enforcing parts of [`BIP-173`] and [`BIP-350`].
4+
//!
5+
//! [BIP-173]: <https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki>
6+
//! [BIP-350]: <https://github.com/bitcoin/bips/blob/master/bip-0350.mediawiki>
7+
8+
use core::fmt;
9+
10+
use crate::primitives::gf32::Fe32;
11+
12+
/// The field element representing segwit version 0.
13+
pub const VERSION_0: Fe32 = Fe32::Q;
14+
/// The field element representing segwit version 1 (taproot).
15+
pub const VERSION_1: Fe32 = Fe32::P;
16+
17+
/// Returns true if given field element represents a valid segwit version.
18+
pub fn is_valid_witness_version(witness_version: Fe32) -> bool {
19+
validate_witness_version(witness_version).is_ok()
20+
}
21+
22+
/// Returns true if `length` represents a valid witness program length for `witness_version`.
23+
pub fn is_valid_witness_program_length(length: usize, witness_version: Fe32) -> bool {
24+
validate_witness_program_length(length, witness_version).is_ok()
25+
}
26+
27+
/// Checks that the given field element represents a valid segwit witness version.
28+
pub fn validate_witness_version(witness_version: Fe32) -> Result<(), InvalidWitnessVersionError> {
29+
if witness_version.to_u8() > 16 {
30+
Err(InvalidWitnessVersionError(witness_version))
31+
} else {
32+
Ok(())
33+
}
34+
}
35+
36+
/// Validates the segwit witness program `length` rules for witness `version`.
37+
pub fn validate_witness_program_length(
38+
length: usize,
39+
version: Fe32,
40+
) -> Result<(), WitnessLengthError> {
41+
use WitnessLengthError::*;
42+
43+
if length < 2 {
44+
return Err(TooShort);
45+
}
46+
if length > 40 {
47+
return Err(TooLong);
48+
}
49+
if version == VERSION_0 && length != 20 && length != 32 {
50+
return Err(InvalidSegwitV0);
51+
}
52+
Ok(())
53+
}
54+
55+
/// Field element does not represent a valid witness version.
56+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
57+
pub struct InvalidWitnessVersionError(Fe32);
58+
59+
impl fmt::Display for InvalidWitnessVersionError {
60+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
61+
write!(f, "field element does not represent a valid witness version")
62+
}
63+
}
64+
65+
#[cfg(feature = "std")]
66+
impl std::error::Error for InvalidWitnessVersionError {
67+
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None }
68+
}
69+
70+
/// Witness program invalid because of incorrect length.
71+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
72+
#[non_exhaustive]
73+
pub enum WitnessLengthError {
74+
/// The witness data is too short.
75+
TooShort,
76+
/// The witness data is too long.
77+
TooLong,
78+
/// The segwit v0 witness is not 20 or 32 bytes long.
79+
InvalidSegwitV0,
80+
}
81+
82+
impl fmt::Display for WitnessLengthError {
83+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
84+
use WitnessLengthError::*;
85+
86+
match *self {
87+
TooShort => write!(f, "witness program is less than 2 bytes long"),
88+
TooLong => write!(f, "witness program is more than 40 bytes long"),
89+
InvalidSegwitV0 => write!(f, "the segwit v0 witness is not 20 or 32 bytes long"),
90+
}
91+
}
92+
}
93+
94+
#[cfg(feature = "std")]
95+
impl std::error::Error for WitnessLengthError {
96+
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
97+
use WitnessLengthError::*;
98+
99+
match *self {
100+
TooShort | TooLong | InvalidSegwitV0 => None,
101+
}
102+
}
103+
}

0 commit comments

Comments
 (0)