Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 6c1379b

Browse files
committedAug 8, 2023
Add segwit API
Add a segwit API with the aim that "typical" modern bitcoin usage is easy and correct.
1 parent d998236 commit 6c1379b

File tree

7 files changed

+390
-60
lines changed

7 files changed

+390
-60
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
#[cfg(feature = "arrayvec")]
4849
use arrayvec::{ArrayVec, CapacityError};

‎src/primitives/decode.rs

+7-51
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ use crate::primitives::checksum::{self, Checksum};
7878
use crate::primitives::gf32::Fe32;
7979
use crate::primitives::hrp::{self, Hrp};
8080
use crate::primitives::iter::{Fe32IterExt, FesToBytes};
81+
use crate::primitives::segwit::{self, WitnessLengthError};
8182
use crate::{write_err, Bech32, Bech32m};
8283

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

266267
self.validate_padding()?;
267-
self.validate_witness_length(witness_version)?;
268+
self.validate_witness_program_length(witness_version)?;
268269

269270
Ok(SegwitHrpstring { hrp: self.hrp(), witness_version, data: self.data })
270271
}
@@ -309,21 +310,11 @@ impl<'s> CheckedHrpstring<'s> {
309310
/// Validates the segwit witness length rules.
310311
///
311312
/// Must be called after the witness version byte is removed from the data.
312-
#[allow(clippy::manual_range_contains)] // For witness length range check.
313-
fn validate_witness_length(&self, witness_version: Fe32) -> Result<(), WitnessLengthError> {
314-
use WitnessLengthError::*;
315-
316-
let witness_len = self.byte_iter().len();
317-
if witness_len < 2 {
318-
return Err(TooShort);
319-
}
320-
if witness_len > 40 {
321-
return Err(TooLong);
322-
}
323-
if witness_version == Fe32::Q && witness_len != 20 && witness_len != 32 {
324-
return Err(InvalidSegwitV0);
325-
}
326-
Ok(())
313+
fn validate_witness_program_length(
314+
&self,
315+
witness_version: Fe32,
316+
) -> Result<(), WitnessLengthError> {
317+
segwit::validate_witness_program_length(self.byte_iter().len(), witness_version)
327318
}
328319
}
329320

@@ -746,41 +737,6 @@ impl std::error::Error for ChecksumError {
746737
}
747738
}
748739

749-
/// Witness program invalid because of incorrect length.
750-
#[derive(Debug, Clone, PartialEq, Eq)]
751-
#[non_exhaustive]
752-
pub enum WitnessLengthError {
753-
/// The witness data is too short.
754-
TooShort,
755-
/// The witness data is too long.
756-
TooLong,
757-
/// The segwit v0 witness is not 20 or 32 bytes long.
758-
InvalidSegwitV0,
759-
}
760-
761-
impl fmt::Display for WitnessLengthError {
762-
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
763-
use WitnessLengthError::*;
764-
765-
match *self {
766-
TooShort => write!(f, "witness program is less than 2 bytes long"),
767-
TooLong => write!(f, "witness program is more than 40 bytes long"),
768-
InvalidSegwitV0 => write!(f, "the segwit v0 witness is not 20 or 32 bytes long"),
769-
}
770-
}
771-
}
772-
773-
#[cfg(feature = "std")]
774-
impl std::error::Error for WitnessLengthError {
775-
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
776-
use WitnessLengthError::*;
777-
778-
match *self {
779-
TooShort | TooLong | InvalidSegwitV0 => None,
780-
}
781-
}
782-
}
783-
784740
/// Error validating the padding bits on the witness data.
785741
#[derive(Debug, Clone, PartialEq, Eq)]
786742
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

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

‎src/segwit.rs

+267
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,267 @@
1+
// SPDX-License-Identifier: MIT
2+
3+
//! Segregated Witness API - enables typical usage for encoding and decoding segwit addresses.
4+
//!
5+
//! [BIP-173] and [BIP-350] contain some complexity. This module aims to allow you to create modern
6+
//! Bitcoin addresses correctly and easily without intimate knowledge of the BIPs. However, if you
7+
//! do posses such knowledge and are doing unusual things you may prefer to use the `primitives`
8+
//! submodules directly.
9+
//!
10+
//! # Examples
11+
//!
12+
//! ```
13+
//! # #[cfg(feature = "alloc")] {
14+
//! use bech32::primitives::hrp::{self, Hrp};
15+
//! use bech32::{Fe32, segwit};
16+
//!
17+
//! let witness_prog = [
18+
//! 0x75, 0x1e, 0x76, 0xe8, 0x19, 0x91, 0x96, 0xd4,
19+
//! 0x54, 0x94, 0x1c, 0x45, 0xd1, 0xb3, 0xa3, 0x23,
20+
//! 0xf1, 0x43, 0x3b, 0xd6,
21+
//! ];
22+
//!
23+
//! // Encode a taproot address suitable for use on mainnet.
24+
//! let _ = segwit::encode_v1(&hrp::BC, &witness_prog);
25+
//!
26+
//! // Encode a segwit v0 address suitable for use on testnet.
27+
//! let _ = segwit::encode_v0(&hrp::TB, &witness_prog);
28+
//!
29+
//! // If you have the witness version already you can use:
30+
//! # let witness_version = Fe32::Q;
31+
//! let _ = segwit::encode(&hrp::BC, witness_version, &witness_prog);
32+
//!
33+
//! // Decode a Bitcoin bech32 segwit address.
34+
//! let address = "bc1q2s3rjwvam9dt2ftt4sqxqjf3twav0gdx0k0q2etxflx38c3x8tnssdmnjq";
35+
//! let (hrp, witness_version, witness_program) = segwit::decode(address).expect("failed to decode address");
36+
//! # }
37+
//! ```
38+
//!
39+
//! [BIP-173]: <https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki>
40+
//! [BIP-350]: <https://github.com/bitcoin/bips/blob/master/bip-0350.mediawiki>
41+
//! [`bip_173_test_vectors.rs`]: <https://github.com/rust-bitcoin/rust-bech32/blob/master/tests/bip_173_test_vectors.rs>
42+
//! [`bip_350_test_vectors.rs`]: <https://github.com/rust-bitcoin/rust-bech32/blob/master/tests/bip_350_test_vectors.rs>
43+
44+
#[cfg(all(feature = "alloc", not(feature = "std"), not(test)))]
45+
use alloc::{string::String, vec::Vec};
46+
use core::fmt;
47+
48+
#[cfg(feature = "alloc")]
49+
use crate::primitives::decode::{SegwitHrpstring, SegwitHrpstringError};
50+
use crate::primitives::gf32::Fe32;
51+
use crate::primitives::hrp::Hrp;
52+
use crate::primitives::iter::{ByteIterExt, Fe32IterExt};
53+
#[cfg(feature = "alloc")]
54+
use crate::primitives::segwit;
55+
use crate::primitives::segwit::{InvalidWitnessVersionError, WitnessLengthError};
56+
use crate::primitives::{Bech32, Bech32m};
57+
use crate::write_err;
58+
59+
/// Decodes a segwit address.
60+
///
61+
/// # Examples
62+
///
63+
/// ```
64+
/// use bech32::segwit;
65+
/// let address = "bc1py3m7vwnghyne9gnvcjw82j7gqt2rafgdmlmwmqnn3hvcmdm09rjqcgrtxs";
66+
/// let (_hrp, _witness_version, _witness_program) = segwit::decode(address).expect("failed to decode address");
67+
/// ```
68+
#[cfg(feature = "alloc")]
69+
pub fn decode(s: &str) -> Result<(Hrp, Fe32, Vec<u8>), SegwitHrpstringError> {
70+
let segwit = SegwitHrpstring::new(s)?;
71+
Ok((segwit.hrp(), segwit.witness_version(), segwit.byte_iter().collect::<Vec<u8>>()))
72+
}
73+
74+
/// Encodes a segwit address.
75+
///
76+
/// Does validity checks on the `witness_version` and length checks on the `witness_program`.
77+
///
78+
/// As specified by [`BIP-350`] we use the [`Bech32m`] checksum algorithm for witness versions 1 and
79+
/// above, and for witness version 0 we use the original ([`BIP-173`]) [`Bech32`] checksum
80+
/// algorithm.
81+
///
82+
/// See also [`encode_v0`] or [`encode_v1`].
83+
///
84+
/// [`Bech32`]: crate::primitives::Bech32
85+
/// [`Bech32m`]: crate::primitives::Bech32m
86+
/// [BIP-173]: <https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki>
87+
/// [BIP-350]: <https://github.com/bitcoin/bips/blob/master/bip-0350.mediawiki>
88+
#[cfg(feature = "alloc")]
89+
pub fn encode(
90+
hrp: &Hrp,
91+
witness_version: Fe32,
92+
witness_program: &[u8],
93+
) -> Result<String, EncodeError> {
94+
segwit::validate_witness_version(witness_version)?;
95+
segwit::validate_witness_program_length(witness_program.len(), witness_version)?;
96+
97+
let mut buf = String::new();
98+
encode_to_fmt_unchecked(&mut buf, hrp, witness_version, witness_program)?;
99+
Ok(buf)
100+
}
101+
102+
/// Encodes a segwit version 0 address.
103+
#[cfg(feature = "alloc")]
104+
pub fn encode_v0(hrp: &Hrp, witness_program: &[u8]) -> Result<String, EncodeError> {
105+
encode(hrp, Fe32::Q, witness_program)
106+
}
107+
108+
/// Encodes a segwit version 1 address.
109+
#[cfg(feature = "alloc")]
110+
pub fn encode_v1(hrp: &Hrp, witness_program: &[u8]) -> Result<String, EncodeError> {
111+
encode(hrp, Fe32::P, witness_program)
112+
}
113+
114+
/// Encodes a segwit address to a writer ([`fmt::Write`]) using lowercase characters.
115+
///
116+
/// Does not check the validity of the witness version and witness program lengths (see
117+
/// the [`crate::primitives::segwit`] module for validation functions).
118+
pub fn encode_to_fmt_unchecked<W: fmt::Write>(
119+
fmt: &mut W,
120+
hrp: &Hrp,
121+
witness_version: Fe32,
122+
witness_program: &[u8],
123+
) -> fmt::Result {
124+
let iter = witness_program.iter().copied().bytes_to_fes();
125+
match witness_version {
126+
Fe32::Q => {
127+
for c in iter.with_checksum::<Bech32>(hrp).with_witness_version(Fe32::Q).chars() {
128+
fmt.write_char(c)?;
129+
}
130+
}
131+
version => {
132+
for c in iter.with_checksum::<Bech32m>(hrp).with_witness_version(version).chars() {
133+
fmt.write_char(c)?;
134+
}
135+
}
136+
}
137+
Ok(())
138+
}
139+
140+
/// Encodes a segwit address to a writer ([`fmt::Write`]) using uppercase characters.
141+
///
142+
/// This is provided for use when creating QR codes.
143+
///
144+
/// Does not check the validity of the witness version and witness program lengths (see
145+
/// the [`crate::primitives::segwit`] module for validation functions).
146+
pub fn encode_to_fmt_unchecked_uppercase<W: fmt::Write>(
147+
fmt: &mut W,
148+
hrp: &Hrp,
149+
witness_version: Fe32,
150+
witness_program: &[u8],
151+
) -> fmt::Result {
152+
let iter = witness_program.iter().copied().bytes_to_fes();
153+
match witness_version {
154+
Fe32::Q => {
155+
for c in iter.with_checksum::<Bech32>(hrp).with_witness_version(Fe32::Q).chars() {
156+
fmt.write_char(c.to_ascii_uppercase())?;
157+
}
158+
}
159+
version => {
160+
for c in iter.with_checksum::<Bech32m>(hrp).with_witness_version(version).chars() {
161+
fmt.write_char(c.to_ascii_uppercase())?;
162+
}
163+
}
164+
}
165+
166+
Ok(())
167+
}
168+
169+
/// An error while constructing a [`SegwitHrpstring`] type.
170+
#[derive(Debug, Clone, PartialEq, Eq)]
171+
pub enum EncodeError {
172+
/// Invalid witness version (must be 0-16 inclusive).
173+
WitnessVersion(InvalidWitnessVersionError),
174+
/// Invalid witness length.
175+
WitnessLength(WitnessLengthError),
176+
/// Writing to formatter failed.
177+
Write(fmt::Error),
178+
}
179+
180+
impl fmt::Display for EncodeError {
181+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
182+
use EncodeError::*;
183+
184+
match *self {
185+
WitnessVersion(ref e) => write_err!(f, "witness version"; e),
186+
WitnessLength(ref e) => write_err!(f, "witness length"; e),
187+
Write(ref e) => write_err!(f, "writing to formatter failed"; e),
188+
}
189+
}
190+
}
191+
192+
#[cfg(feature = "std")]
193+
impl std::error::Error for EncodeError {
194+
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
195+
use EncodeError::*;
196+
197+
match *self {
198+
WitnessVersion(ref e) => Some(e),
199+
WitnessLength(ref e) => Some(e),
200+
Write(ref e) => Some(e),
201+
}
202+
}
203+
}
204+
205+
impl From<InvalidWitnessVersionError> for EncodeError {
206+
fn from(e: InvalidWitnessVersionError) -> Self { Self::WitnessVersion(e) }
207+
}
208+
209+
impl From<WitnessLengthError> for EncodeError {
210+
fn from(e: WitnessLengthError) -> Self { Self::WitnessLength(e) }
211+
}
212+
213+
impl From<fmt::Error> for EncodeError {
214+
fn from(e: fmt::Error) -> Self { Self::Write(e) }
215+
}
216+
217+
#[cfg(all(test, feature = "alloc"))]
218+
mod tests {
219+
use super::*;
220+
use crate::primitives::hrp;
221+
222+
#[test]
223+
// Just shows we handle both v0 and v1 addresses, for complete test
224+
// coverage see primitives submodules and test vectors.
225+
fn roundtrip_valid_mainnet_addresses() {
226+
// A few recent addresses from mainnet (Block 801266).
227+
let addresses = vec![
228+
"bc1q2s3rjwvam9dt2ftt4sqxqjf3twav0gdx0k0q2etxflx38c3x8tnssdmnjq", // Segwit v0
229+
"bc1py3m7vwnghyne9gnvcjw82j7gqt2rafgdmlmwmqnn3hvcmdm09rjqcgrtxs", // Segwit v1
230+
];
231+
232+
for address in addresses {
233+
let (hrp, version, program) = decode(address).expect("failed to decode valid address");
234+
let encoded = encode(&hrp, version, &program).expect("failed to encode address");
235+
assert_eq!(encoded, address);
236+
}
237+
}
238+
239+
fn witness_program() -> [u8; 20] {
240+
[
241+
0x75, 0x1e, 0x76, 0xe8, 0x19, 0x91, 0x96, 0xd4, 0x54, 0x94, 0x1c, 0x45, 0xd1, 0xb3,
242+
0xa3, 0x23, 0xf1, 0x43, 0x3b, 0xd6,
243+
]
244+
}
245+
246+
#[test]
247+
fn encode_to_fmt_lowercase() {
248+
let program = witness_program();
249+
let mut address = String::new();
250+
encode_to_fmt_unchecked(&mut address, &hrp::BC, Fe32::Q, &program)
251+
.expect("failed to encode address to QR code");
252+
253+
let want = "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4";
254+
assert_eq!(address, want);
255+
}
256+
257+
#[test]
258+
fn encode_to_fmt_uppercase() {
259+
let program = witness_program();
260+
let mut address = String::new();
261+
encode_to_fmt_unchecked_uppercase(&mut address, &hrp::BC, Fe32::Q, &program)
262+
.expect("failed to encode address to QR code");
263+
264+
let want = "BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4";
265+
assert_eq!(address, want);
266+
}
267+
}

‎tests/bip_173_test_vectors.rs

+13
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,19 @@ macro_rules! check_valid_address_roundtrip {
5555
$(
5656
#[test]
5757
fn $test_name() {
58+
// We cannot use encode/decode for all test vectors because according to BIP-173 the
59+
// bech32 checksum algorithm can be used with any witness version, and this is
60+
// tested by the test vectors. However when BIP-350 came into effect only witness
61+
// version 0 uses bech32 (and this is enforced by encode/decode).
62+
if let Ok((hrp, bech32::Fe32::Q, program)) = bech32::segwit::decode($addr) {
63+
let encoded = bech32::segwit::encode_v0(&hrp, &program).expect("failed to encode address");
64+
// The bips specifically say that encoder should output lowercase characters so we uppercase manually.
65+
if encoded != $addr {
66+
let got = encoded.to_uppercase();
67+
assert_eq!(got, $addr)
68+
}
69+
}
70+
5871
let hrpstring = SegwitHrpstring::new_bech32($addr).expect("valid address");
5972
let hrp = hrpstring.hrp();
6073
let witness_version = hrpstring.witness_version();

‎tests/bip_350_test_vectors.rs

+3-9
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use bech32::primitives::decode::{
66
CheckedHrpstring, CheckedHrpstringError, ChecksumError, SegwitHrpstring, SegwitHrpstringError,
77
UncheckedHrpstring,
88
};
9-
use bech32::{Bech32, Bech32m, ByteIterExt, Fe32, Fe32IterExt};
9+
use bech32::{Bech32, Bech32m};
1010

1111
// This is a separate test because we correctly identify this string as invalid but not for the
1212
// reason given in the bip.
@@ -55,14 +55,8 @@ macro_rules! check_valid_address_roundtrip {
5555
#[test]
5656
#[cfg(feature = "alloc")]
5757
fn $test_name() {
58-
let hrpstring = SegwitHrpstring::new($addr).expect("valid address");
59-
let hrp = hrpstring.hrp();
60-
let witness_version = hrpstring.witness_version();
61-
62-
let encoded = match witness_version {
63-
Fe32::Q => hrpstring.byte_iter().bytes_to_fes().with_checksum::<Bech32>(&hrp.into()).with_witness_version(witness_version).chars().collect::<String>(),
64-
_ => hrpstring.byte_iter().bytes_to_fes().with_checksum::<Bech32m>(&hrp.into()).with_witness_version(witness_version).chars().collect::<String>(),
65-
};
58+
let (hrp, version, program) = bech32::segwit::decode($addr).expect("failed to decode valid address");
59+
let encoded = bech32::segwit::encode(&hrp, version, &program).expect("failed to encode address");
6660

6761
// The bips specifically say that encoder should output lowercase characters so we uppercase manually.
6862
if encoded != $addr {

0 commit comments

Comments
 (0)
Please sign in to comment.