Skip to content

Commit db7144e

Browse files
committed
Add segwit API
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.
1 parent d998236 commit db7144e

File tree

2 files changed

+173
-0
lines changed

2 files changed

+173
-0
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/segwit.rs

+172
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
// SPDX-License-Identifier: MIT
2+
3+
//! Segregated Witness API - enabling typical usage for encoding and decoding segwit addresses.
4+
//!
5+
//! The bips contain some complexity, this module should allow you to create modern bitcoin
6+
//! addresses, and parse existing addresses from the Bitcoin blockchain, correctly and easily.
7+
//!
8+
//! You should not need to intimately know [BIP-173] and [BIP-350] in order to correctly use this
9+
//! module. If you are doing unusual things, consider using the `primitives` submodules directly.
10+
//!
11+
//! Note, we do implement the bips to spec, however this is done in the `primitives` submodules, to
12+
//! convince yourself, and to see the nitty gritty, you can look at the test vector code in
13+
//! [`bip_173_test_vectors.rs`] and [`bip_350_test_vectors.rs`].
14+
//!
15+
//! [BIP-173]: <https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki>
16+
//! [BIP-350]: <https://github.com/bitcoin/bips/blob/master/bip-0350.mediawiki>
17+
//! [`bip_173_test_vectors.rs`]: <https://github.com/rust-bitcoin/rust-bech32/blob/master/tests/bip_173_test_vectors.rs>
18+
//! [`bip_350_test_vectors.rs`]: <https://github.com/rust-bitcoin/rust-bech32/blob/master/tests/bip_350_test_vectors.rs>
19+
20+
#[cfg(all(feature = "alloc", not(feature = "std"), not(test)))]
21+
use alloc::{string::String, vec::Vec};
22+
use core::fmt;
23+
24+
use crate::primitives::decode::{SegwitHrpstring, SegwitHrpstringError};
25+
use crate::primitives::gf32::Fe32;
26+
// TODO: Add ergonomic re-exports of types used in this modules public API, ether to `lib.rs` or here.
27+
use crate::primitives::hrp::{self, Hrp};
28+
use crate::primitives::iter::{ByteIterExt, Fe32IterExt};
29+
use crate::primitives::{Bech32, Bech32m};
30+
31+
/// Decodes a bech32 address returning the witness version and encode data.
32+
///
33+
/// Handles segwit v0 and v1 addresses with the respective checksum algorithm.
34+
#[cfg(feature = "alloc")]
35+
pub fn decode(s: &str) -> Result<(Fe32, Vec<u8>), SegwitHrpstringError> {
36+
let segwit = SegwitHrpstring::new(s)?;
37+
let version = segwit.witness_version();
38+
let data = segwit.byte_iter().collect::<Vec<u8>>();
39+
Ok((version, data))
40+
}
41+
42+
/// Encodes a witness program as a bech32 address.
43+
///
44+
/// Uses segwit v1 and the bech32m checksum algorithm in line with [BIP-350]
45+
///
46+
/// [BIP-350]: <https://github.com/bitcoin/bips/blob/master/bip-0350.mediawiki>
47+
#[cfg(feature = "alloc")]
48+
pub fn encode(program: &[u8]) -> String { encode_program_as_segwit_v1_mainnet_address(program) }
49+
50+
/// Encodes a witness program as a segwit version 0 address suitable for use on mainnet.
51+
#[cfg(feature = "alloc")]
52+
pub fn encode_program_as_segwit_v0_mainnet_address(program: &[u8]) -> String {
53+
let hrp = hrp::BC;
54+
let version = Fe32::Q;
55+
encode_program(&hrp, version, program)
56+
}
57+
58+
/// Encodes a witness program as a segwit version 1 address suitable for use on mainnet.
59+
#[cfg(feature = "alloc")]
60+
pub fn encode_program_as_segwit_v1_mainnet_address(program: &[u8]) -> String {
61+
let hrp = hrp::BC;
62+
let version = Fe32::P;
63+
encode_program(&hrp, version, program)
64+
}
65+
66+
/// Encodes a witness program as a segwit version 0 address suitable for use on testnet.
67+
#[cfg(feature = "alloc")]
68+
pub fn encode_program_as_segwit_v0_testnet_address(program: &[u8]) -> String {
69+
let hrp = hrp::TB;
70+
let version = Fe32::Q;
71+
encode_program(&hrp, version, program)
72+
}
73+
74+
/// Encodes a witness program as a segwit version 1 address suitable for use on testnet.
75+
#[cfg(feature = "alloc")]
76+
pub fn encode_program_as_segwit_v1_testnet_address(program: &[u8]) -> String {
77+
let hrp = hrp::TB;
78+
let version = Fe32::P;
79+
encode_program(&hrp, version, program)
80+
}
81+
82+
/// Encodes a witness program as a bech32 address string using the given `hrp` and `version`.
83+
#[cfg(feature = "alloc")]
84+
pub fn encode_program(hrp: &Hrp, version: Fe32, program: &[u8]) -> String {
85+
// Possibly faster to use `collect` instead of writing char by char?
86+
// (Counter argument: "Early optimization is the root of all evil.")
87+
let mut buf = String::new();
88+
encode_program_to_fmt(&mut buf, hrp, version, program).expect("TODO: Handle errors");
89+
buf
90+
}
91+
92+
/// Encodes a witness program to a writer ([`fmt::Write`]).
93+
///
94+
/// Prefixes with the appropriate witness version byte and appends the appropriate checksum.
95+
/// Currently no checks done on the validity of the `hrp`, `version`, or `program`.
96+
pub fn encode_program_to_fmt(
97+
fmt: &mut dyn fmt::Write,
98+
hrp: &Hrp,
99+
version: Fe32,
100+
program: &[u8],
101+
) -> fmt::Result {
102+
match version {
103+
Fe32::Q => {
104+
for c in program
105+
.iter()
106+
.copied()
107+
.bytes_to_fes()
108+
.with_checksum::<Bech32>(hrp)
109+
.with_witness_version(Fe32::Q)
110+
.chars()
111+
{
112+
fmt.write_char(c)?;
113+
}
114+
}
115+
witver => {
116+
for c in program
117+
.iter()
118+
.copied()
119+
.bytes_to_fes()
120+
.with_checksum::<Bech32m>(hrp)
121+
.with_witness_version(witver)
122+
.chars()
123+
{
124+
fmt.write_char(c)?;
125+
}
126+
}
127+
}
128+
Ok(())
129+
}
130+
131+
#[cfg(all(test, feature = "alloc"))]
132+
mod tests {
133+
use super::*;
134+
135+
#[test]
136+
fn parse_valid_mainnet_addresses() {
137+
// A few recent addresses from mainnet (Block 801266).
138+
let addresses = vec![
139+
"bc1q2s3rjwvam9dt2ftt4sqxqjf3twav0gdx0k0q2etxflx38c3x8tnssdmnjq", // Segwit v0
140+
"bc1py3m7vwnghyne9gnvcjw82j7gqt2rafgdmlmwmqnn3hvcmdm09rjqcgrtxs", // Segwit v1
141+
];
142+
143+
for address in addresses {
144+
// Just shows we handle both v0 and v1 addresses, for complete test
145+
// coverage see primitives submodules and test vectors.
146+
assert!(decode(address).is_ok());
147+
}
148+
}
149+
150+
#[test]
151+
fn roundtrip_valid_v0_mainnet_address() {
152+
let address = "bc1qwqdg6squsna38e46795at95yu9atm8azzmyvckulcc7kytlcckxswvvzej";
153+
let (_version, data) = decode(address).expect("failed to decode address");
154+
let encoded = encode_program_as_segwit_v0_mainnet_address(&data);
155+
assert_eq!(encoded, address);
156+
}
157+
158+
#[test]
159+
fn roundtrip_valid_v1_mainnet_address() {
160+
let address = "bc1pguzvu9c7d6qc9pwgu93vqmwzsr6e3kpxewjk93wkprn4suhz8w3qp3yxsd";
161+
let (_version, data) = decode(address).expect("failed to decode address");
162+
163+
let explicit = encode_program_as_segwit_v1_mainnet_address(&data);
164+
let implicit = encode(&data);
165+
166+
// Ensure `encode` implicitly uses v1.
167+
assert_eq!(implicit, explicit);
168+
169+
// Ensure it roundtrips.
170+
assert_eq!(implicit, address);
171+
}
172+
}

0 commit comments

Comments
 (0)