Skip to content

Commit

Permalink
Add segwit API
Browse files Browse the repository at this point in the history
Add a segwit API with the aim that "typical" modern bitcoin usage is
easy and correct.
  • Loading branch information
tcharding committed Aug 4, 2023
1 parent d998236 commit 047a29e
Show file tree
Hide file tree
Showing 8 changed files with 383 additions and 60 deletions.
33 changes: 33 additions & 0 deletions examples/qr_codes.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//! Demonstrate encoding with uppercase characters suitable for QR codes.

use std::fmt;

use bech32::primitives::hrp;
use bech32::Fe32;

fn main() {
let hrp = hrp::BC;
let witness_version = Fe32::Q; // Segwit Version 0.
let witness_program = [
0x75, 0x1e, 0x76, 0xe8, 0x19, 0x91, 0x96, 0xd4, 0x54, 0x94, 0x1c, 0x45, 0xd1, 0xb3, 0xa3,
0x23, 0xf1, 0x43, 0x3b, 0xd6,
];

let mut address = String::new();
let mut writer = UpperWriter(&mut address);
bech32::segwit::encode_to_fmt_unchecked(&mut writer, &hrp, witness_version, &witness_program)
.expect("failed to encode address to QR code");

println!("\n\nExample Bitcoin QR code URI: bitcoin:{}", address);
}

struct UpperWriter<W: fmt::Write>(W);

impl<W: fmt::Write> fmt::Write for UpperWriter<W> {
fn write_str(&mut self, s: &str) -> fmt::Result {
for c in s.chars() {
self.0.write_char(c.to_ascii_uppercase())?;
}
Ok(())
}
}
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ pub use crate::primitives::{Bech32, Bech32m};

mod error;
pub mod primitives;
pub mod segwit;

#[cfg(feature = "arrayvec")]
use arrayvec::{ArrayVec, CapacityError};
Expand Down
58 changes: 7 additions & 51 deletions src/primitives/decode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ use crate::primitives::checksum::{self, Checksum};
use crate::primitives::gf32::Fe32;
use crate::primitives::hrp::{self, Hrp};
use crate::primitives::iter::{Fe32IterExt, FesToBytes};
use crate::primitives::segwit::{self, WitnessLengthError};
use crate::{write_err, Bech32, Bech32m};

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

self.validate_padding()?;
self.validate_witness_length(witness_version)?;
self.validate_witness_program_length(witness_version)?;

Ok(SegwitHrpstring { hrp: self.hrp(), witness_version, data: self.data })
}
Expand Down Expand Up @@ -309,21 +310,11 @@ impl<'s> CheckedHrpstring<'s> {
/// Validates the segwit witness length rules.
///
/// Must be called after the witness version byte is removed from the data.
#[allow(clippy::manual_range_contains)] // For witness length range check.
fn validate_witness_length(&self, witness_version: Fe32) -> Result<(), WitnessLengthError> {
use WitnessLengthError::*;

let witness_len = self.byte_iter().len();
if witness_len < 2 {
return Err(TooShort);
}
if witness_len > 40 {
return Err(TooLong);
}
if witness_version == Fe32::Q && witness_len != 20 && witness_len != 32 {
return Err(InvalidSegwitV0);
}
Ok(())
fn validate_witness_program_length(
&self,
witness_version: Fe32,
) -> Result<(), WitnessLengthError> {
segwit::validate_witness_program_length(self.byte_iter().len(), witness_version)
}
}

Expand Down Expand Up @@ -746,41 +737,6 @@ impl std::error::Error for ChecksumError {
}
}

/// Witness program invalid because of incorrect length.
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum WitnessLengthError {
/// The witness data is too short.
TooShort,
/// The witness data is too long.
TooLong,
/// The segwit v0 witness is not 20 or 32 bytes long.
InvalidSegwitV0,
}

impl fmt::Display for WitnessLengthError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use WitnessLengthError::*;

match *self {
TooShort => write!(f, "witness program is less than 2 bytes long"),
TooLong => write!(f, "witness program is more than 40 bytes long"),
InvalidSegwitV0 => write!(f, "the segwit v0 witness is not 20 or 32 bytes long"),
}
}
}

#[cfg(feature = "std")]
impl std::error::Error for WitnessLengthError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
use WitnessLengthError::*;

match *self {
TooShort | TooLong | InvalidSegwitV0 => None,
}
}
}

/// Error validating the padding bits on the witness data.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PaddingError {
Expand Down
1 change: 1 addition & 0 deletions src/primitives/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ pub mod encode;
pub mod gf32;
pub mod hrp;
pub mod iter;
pub mod segwit;

use checksum::{Checksum, PackedNull};

Expand Down
98 changes: 98 additions & 0 deletions src/primitives/segwit.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// SPDX-License-Identifier: MIT

//! Segregated Witness functionality - useful for enforcing parts of [`BIP-173`] and [`BIP-350`].
//!
//! [BIP-173]: <https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki>
//! [BIP-350]: <https://github.com/bitcoin/bips/blob/master/bip-0350.mediawiki>

use core::fmt;

use crate::primitives::gf32::Fe32;

/// Returns true if given field element represents a valid segwit version.
pub fn is_valid_witness_version(witness_version: Fe32) -> bool {
validate_witness_version(witness_version).is_ok()
}

/// Returns true if `length` represents a valid witness program length for `witness_version`.
pub fn is_valid_witness_program_length(length: usize, witness_version: Fe32) -> bool {
validate_witness_program_length(length, witness_version).is_ok()
}

/// Checks that the given field element represents a valid segwit witness version.
pub fn validate_witness_version(witness_version: Fe32) -> Result<(), InvalidWitnessVersionError> {
if witness_version.to_u8() > 16 {
Err(InvalidWitnessVersionError(witness_version))
} else {
Ok(())
}
}

/// Validates the segwit witness program `length` rules for witness `version`.
pub fn validate_witness_program_length(
length: usize,
version: Fe32,
) -> Result<(), WitnessLengthError> {
use WitnessLengthError::*;

if length < 2 {
return Err(TooShort);
}
if length > 40 {
return Err(TooLong);
}
if version == Fe32::Q && length != 20 && length != 32 {
return Err(InvalidSegwitV0);
}
Ok(())
}

/// Field element does not represent a valid witness version.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct InvalidWitnessVersionError(Fe32);

impl fmt::Display for InvalidWitnessVersionError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "field element does not represent a valid witness version")
}
}

#[cfg(feature = "std")]
impl std::error::Error for InvalidWitnessVersionError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None }
}

/// Witness program invalid because of incorrect length.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum WitnessLengthError {
/// The witness data is too short.
TooShort,
/// The witness data is too long.
TooLong,
/// The segwit v0 witness is not 20 or 32 bytes long.
InvalidSegwitV0,
}

impl fmt::Display for WitnessLengthError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use WitnessLengthError::*;

match *self {
TooShort => write!(f, "witness program is less than 2 bytes long"),
TooLong => write!(f, "witness program is more than 40 bytes long"),
InvalidSegwitV0 => write!(f, "the segwit v0 witness is not 20 or 32 bytes long"),
}
}
}

#[cfg(feature = "std")]
impl std::error::Error for WitnessLengthError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
use WitnessLengthError::*;

match *self {
TooShort | TooLong | InvalidSegwitV0 => None,
}
}
}
Loading

0 comments on commit 047a29e

Please sign in to comment.