From db7144e9d028e8e19df89890ccd74cd8923acb63 Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Wed, 2 Aug 2023 10:58:28 +1000 Subject: [PATCH] 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. --- src/lib.rs | 1 + src/segwit.rs | 172 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 173 insertions(+) create mode 100644 src/segwit.rs diff --git a/src/lib.rs b/src/lib.rs index 4d775015f..605823199 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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}; diff --git a/src/segwit.rs b/src/segwit.rs new file mode 100644 index 000000000..e54274f3f --- /dev/null +++ b/src/segwit.rs @@ -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]: +//! [BIP-350]: +//! [`bip_173_test_vectors.rs`]: +//! [`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), SegwitHrpstringError> { + let segwit = SegwitHrpstring::new(s)?; + let version = segwit.witness_version(); + let data = segwit.byte_iter().collect::>(); + 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]: +#[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::(hrp) + .with_witness_version(Fe32::Q) + .chars() + { + fmt.write_char(c)?; + } + } + witver => { + for c in program + .iter() + .copied() + .bytes_to_fes() + .with_checksum::(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); + } +}