Skip to content

Commit 4a50ff2

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 4a50ff2

File tree

2 files changed

+171
-0
lines changed

2 files changed

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

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

0 commit comments

Comments
 (0)