-
Notifications
You must be signed in to change notification settings - Fork 50
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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
Showing
2 changed files
with
173 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |