Skip to content

Commit 047a29e

Browse files
committed
Add segwit API
Add a segwit API with the aim that "typical" modern bitcoin usage is easy and correct.
1 parent d998236 commit 047a29e

File tree

8 files changed

+383
-60
lines changed

8 files changed

+383
-60
lines changed

examples/qr_codes.rs

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
//! Demonstrate encoding with uppercase characters suitable for QR codes.
2+
3+
use std::fmt;
4+
5+
use bech32::primitives::hrp;
6+
use bech32::Fe32;
7+
8+
fn main() {
9+
let hrp = hrp::BC;
10+
let witness_version = Fe32::Q; // Segwit Version 0.
11+
let witness_program = [
12+
0x75, 0x1e, 0x76, 0xe8, 0x19, 0x91, 0x96, 0xd4, 0x54, 0x94, 0x1c, 0x45, 0xd1, 0xb3, 0xa3,
13+
0x23, 0xf1, 0x43, 0x3b, 0xd6,
14+
];
15+
16+
let mut address = String::new();
17+
let mut writer = UpperWriter(&mut address);
18+
bech32::segwit::encode_to_fmt_unchecked(&mut writer, &hrp, witness_version, &witness_program)
19+
.expect("failed to encode address to QR code");
20+
21+
println!("\n\nExample Bitcoin QR code URI: bitcoin:{}", address);
22+
}
23+
24+
struct UpperWriter<W: fmt::Write>(W);
25+
26+
impl<W: fmt::Write> fmt::Write for UpperWriter<W> {
27+
fn write_str(&mut self, s: &str) -> fmt::Result {
28+
for c in s.chars() {
29+
self.0.write_char(c.to_ascii_uppercase())?;
30+
}
31+
Ok(())
32+
}
33+
}

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+
}

0 commit comments

Comments
 (0)