Skip to content

Commit

Permalink
Add segwit API
Browse files Browse the repository at this point in the history
The bips are a little bit hard to use correctly, add a `segwit` API with
the aim that "typical" modern bitcoin usage is easy and correct.
  • Loading branch information
tcharding committed Aug 2, 2023
1 parent d998236 commit db7144e
Show file tree
Hide file tree
Showing 2 changed files with 173 additions and 0 deletions.
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
172 changes: 172 additions & 0 deletions src/segwit.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
// SPDX-License-Identifier: MIT

//! Segregated Witness API - enabling typical usage for encoding and decoding segwit addresses.
//!
//! The bips contain some complexity, this module should allow you to create modern bitcoin
//! addresses, and parse existing addresses from the Bitcoin blockchain, correctly and easily.
//!
//! You should not need to intimately know [BIP-173] and [BIP-350] in order to correctly use this
//! module. If you are doing unusual things, consider using the `primitives` submodules directly.
//!
//! Note, we do implement the bips to spec, however this is done in the `primitives` submodules, to
//! convince yourself, and to see the nitty gritty, you can look at the test vector code in
//! [`bip_173_test_vectors.rs`] and [`bip_350_test_vectors.rs`].
//!
//! [BIP-173]: <https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki>
//! [BIP-350]: <https://github.com/bitcoin/bips/blob/master/bip-0350.mediawiki>
//! [`bip_173_test_vectors.rs`]: <https://github.com/rust-bitcoin/rust-bech32/blob/master/tests/bip_173_test_vectors.rs>
//! [`bip_350_test_vectors.rs`]: <https://github.com/rust-bitcoin/rust-bech32/blob/master/tests/bip_350_test_vectors.rs>

#[cfg(all(feature = "alloc", not(feature = "std"), not(test)))]
use alloc::{string::String, vec::Vec};
use core::fmt;

use crate::primitives::decode::{SegwitHrpstring, SegwitHrpstringError};
use crate::primitives::gf32::Fe32;
// TODO: Add ergonomic re-exports of types used in this modules public API, ether to `lib.rs` or here.
use crate::primitives::hrp::{self, Hrp};
use crate::primitives::iter::{ByteIterExt, Fe32IterExt};
use crate::primitives::{Bech32, Bech32m};

/// Decodes a bech32 address returning the witness version and encode data.
///
/// Handles segwit v0 and v1 addresses with the respective checksum algorithm.
#[cfg(feature = "alloc")]
pub fn decode(s: &str) -> Result<(Fe32, Vec<u8>), SegwitHrpstringError> {
let segwit = SegwitHrpstring::new(s)?;
let version = segwit.witness_version();
let data = segwit.byte_iter().collect::<Vec<u8>>();
Ok((version, data))
}

/// Encodes a witness program as a bech32 address.
///
/// Uses segwit v1 and the bech32m checksum algorithm in line with [BIP-350]
///
/// [BIP-350]: <https://github.com/bitcoin/bips/blob/master/bip-0350.mediawiki>
#[cfg(feature = "alloc")]
pub fn encode(program: &[u8]) -> String { encode_program_as_segwit_v1_mainnet_address(program) }

/// Encodes a witness program as a segwit version 0 address suitable for use on mainnet.
#[cfg(feature = "alloc")]
pub fn encode_program_as_segwit_v0_mainnet_address(program: &[u8]) -> String {
let hrp = hrp::BC;
let version = Fe32::Q;
encode_program(&hrp, version, program)
}

/// Encodes a witness program as a segwit version 1 address suitable for use on mainnet.
#[cfg(feature = "alloc")]
pub fn encode_program_as_segwit_v1_mainnet_address(program: &[u8]) -> String {
let hrp = hrp::BC;
let version = Fe32::P;
encode_program(&hrp, version, program)
}

/// Encodes a witness program as a segwit version 0 address suitable for use on testnet.
#[cfg(feature = "alloc")]
pub fn encode_program_as_segwit_v0_testnet_address(program: &[u8]) -> String {
let hrp = hrp::TB;
let version = Fe32::Q;
encode_program(&hrp, version, program)
}

/// Encodes a witness program as a segwit version 1 address suitable for use on testnet.
#[cfg(feature = "alloc")]
pub fn encode_program_as_segwit_v1_testnet_address(program: &[u8]) -> String {
let hrp = hrp::TB;
let version = Fe32::P;
encode_program(&hrp, version, program)
}

/// Encodes a witness program as a bech32 address string using the given `hrp` and `version`.
#[cfg(feature = "alloc")]
pub fn encode_program(hrp: &Hrp, version: Fe32, program: &[u8]) -> String {
// Possibly faster to use `collect` instead of writing char by char?
// (Counter argument: "Early optimization is the root of all evil.")
let mut buf = String::new();
encode_program_to_fmt(&mut buf, hrp, version, program).expect("TODO: Handle errors");
buf
}

/// Encodes a witness program to a writer ([`fmt::Write`]).
///
/// Prefixes with the appropriate witness version byte and appends the appropriate checksum.
/// Currently no checks done on the validity of the `hrp`, `version`, or `program`.
pub fn encode_program_to_fmt(
fmt: &mut dyn fmt::Write,
hrp: &Hrp,
version: Fe32,
program: &[u8],
) -> fmt::Result {
match version {
Fe32::Q => {
for c in program
.iter()
.copied()
.bytes_to_fes()
.with_checksum::<Bech32>(hrp)
.with_witness_version(Fe32::Q)
.chars()
{
fmt.write_char(c)?;
}
}
witver => {
for c in program
.iter()
.copied()
.bytes_to_fes()
.with_checksum::<Bech32m>(hrp)
.with_witness_version(witver)
.chars()
{
fmt.write_char(c)?;
}
}
}
Ok(())
}

#[cfg(all(test, feature = "alloc"))]
mod tests {
use super::*;

#[test]
fn parse_valid_mainnet_addresses() {
// A few recent addresses from mainnet (Block 801266).
let addresses = vec![
"bc1q2s3rjwvam9dt2ftt4sqxqjf3twav0gdx0k0q2etxflx38c3x8tnssdmnjq", // Segwit v0
"bc1py3m7vwnghyne9gnvcjw82j7gqt2rafgdmlmwmqnn3hvcmdm09rjqcgrtxs", // Segwit v1
];

for address in addresses {
// Just shows we handle both v0 and v1 addresses, for complete test
// coverage see primitives submodules and test vectors.
assert!(decode(address).is_ok());
}
}

#[test]
fn roundtrip_valid_v0_mainnet_address() {
let address = "bc1qwqdg6squsna38e46795at95yu9atm8azzmyvckulcc7kytlcckxswvvzej";
let (_version, data) = decode(address).expect("failed to decode address");
let encoded = encode_program_as_segwit_v0_mainnet_address(&data);
assert_eq!(encoded, address);
}

#[test]
fn roundtrip_valid_v1_mainnet_address() {
let address = "bc1pguzvu9c7d6qc9pwgu93vqmwzsr6e3kpxewjk93wkprn4suhz8w3qp3yxsd";
let (_version, data) = decode(address).expect("failed to decode address");

let explicit = encode_program_as_segwit_v1_mainnet_address(&data);
let implicit = encode(&data);

// Ensure `encode` implicitly uses v1.
assert_eq!(implicit, explicit);

// Ensure it roundtrips.
assert_eq!(implicit, address);
}
}

0 comments on commit db7144e

Please sign in to comment.