|
| 1 | +//! Bech32 and Bech32m encoding/decoding for Bitcoin witness addresses. |
| 2 | +//! |
| 3 | +//! Implements BIP 173 (Bech32) and BIP 350 (Bech32m) encoding schemes using the bitcoin crate. |
| 4 | +//! - Bech32 is used for witness version 0 (P2WPKH, P2WSH) |
| 5 | +//! - Bech32m is used for witness version 1+ (P2TR) |
| 6 | +
|
| 7 | +use super::{AddressCodec, AddressError, Result}; |
| 8 | +use crate::bitcoin::{Script, ScriptBuf, WitnessVersion}; |
| 9 | + |
| 10 | +/// Bech32/Bech32m codec for witness addresses |
| 11 | +#[derive(Debug, Clone, Copy, PartialEq, Eq)] |
| 12 | +pub struct Bech32Codec { |
| 13 | + /// Bech32 Human Readable Part (HRP) |
| 14 | + pub hrp: &'static str, |
| 15 | +} |
| 16 | + |
| 17 | +impl Bech32Codec { |
| 18 | + /// Create a new Bech32 codec with the specified HRP |
| 19 | + pub const fn new(hrp: &'static str) -> Self { |
| 20 | + Self { hrp } |
| 21 | + } |
| 22 | +} |
| 23 | + |
| 24 | +/// Encode witness program with custom HRP |
| 25 | +fn encode_witness_with_custom_hrp( |
| 26 | + program: &[u8], |
| 27 | + version: WitnessVersion, |
| 28 | + hrp_str: &str, |
| 29 | +) -> Result<String> { |
| 30 | + // Try using the bech32 functionality from bitcoin crate |
| 31 | + // The bitcoin crate includes bech32 encoding via its dependencies |
| 32 | + use bech32::{self, Hrp}; |
| 33 | + |
| 34 | + // Parse the HRP |
| 35 | + let hrp = Hrp::parse(hrp_str) |
| 36 | + .map_err(|e| AddressError::Bech32Error(format!("Invalid HRP '{}': {}", hrp_str, e)))?; |
| 37 | + |
| 38 | + // Encode based on witness version |
| 39 | + let address = if version == WitnessVersion::V0 { |
| 40 | + // Use Bech32 for witness version 0 |
| 41 | + bech32::segwit::encode_v0(hrp, program) |
| 42 | + .map_err(|e| AddressError::Bech32Error(format!("Bech32 encoding failed: {}", e)))? |
| 43 | + } else { |
| 44 | + // Use Bech32m for witness version 1+ |
| 45 | + bech32::segwit::encode_v1(hrp, program) |
| 46 | + .map_err(|e| AddressError::Bech32Error(format!("Bech32m encoding failed: {}", e)))? |
| 47 | + }; |
| 48 | + |
| 49 | + Ok(address) |
| 50 | +} |
| 51 | + |
| 52 | +/// Decode witness program with custom HRP |
| 53 | +fn decode_witness_with_custom_hrp(address: &str, expected_hrp: &str) -> Result<Vec<u8>> { |
| 54 | + use bech32::{self, Hrp}; |
| 55 | + |
| 56 | + // Parse the expected HRP |
| 57 | + let expected_hrp_parsed = Hrp::parse(expected_hrp) |
| 58 | + .map_err(|e| AddressError::Bech32Error(format!("Invalid HRP '{}': {}", expected_hrp, e)))?; |
| 59 | + |
| 60 | + // Decode the address |
| 61 | + let (decoded_hrp, witness_version, witness_program) = bech32::segwit::decode(address) |
| 62 | + .map_err(|e| AddressError::Bech32Error(format!("Failed to decode address: {}", e)))?; |
| 63 | + |
| 64 | + // Verify HRP matches |
| 65 | + if decoded_hrp != expected_hrp_parsed { |
| 66 | + return Err(AddressError::Bech32Error(format!( |
| 67 | + "HRP mismatch: expected '{}', got '{}'", |
| 68 | + expected_hrp, decoded_hrp |
| 69 | + ))); |
| 70 | + } |
| 71 | + |
| 72 | + // Convert witness version (Fe32) to OP code |
| 73 | + // Fe32 can be 0-31, but for segwit, we only care about 0-16 |
| 74 | + // OP_0 = 0x00, OP_1 = 0x51, OP_2 = 0x52, ... OP_16 = 0x60 |
| 75 | + let version_byte: u8 = witness_version.to_u8(); |
| 76 | + let version_opcode = if version_byte == 0 { |
| 77 | + 0x00 // OP_0 |
| 78 | + } else if version_byte <= 16 { |
| 79 | + 0x50 + version_byte // OP_1 through OP_16 |
| 80 | + } else { |
| 81 | + return Err(AddressError::Bech32Error(format!( |
| 82 | + "Invalid witness version: {}", |
| 83 | + version_byte |
| 84 | + ))); |
| 85 | + }; |
| 86 | + |
| 87 | + // Construct the script pubkey: <version> <length> <program> |
| 88 | + let mut script = vec![version_opcode, witness_program.len() as u8]; |
| 89 | + script.extend_from_slice(&witness_program); |
| 90 | + Ok(script) |
| 91 | +} |
| 92 | + |
| 93 | +impl AddressCodec for Bech32Codec { |
| 94 | + fn encode(&self, script: &Script) -> Result<String> { |
| 95 | + let (witness_version, program) = if script.is_p2wpkh() { |
| 96 | + if script.len() != 22 { |
| 97 | + return Err(AddressError::InvalidScript( |
| 98 | + "Invalid P2WPKH script length".to_string(), |
| 99 | + )); |
| 100 | + } |
| 101 | + (WitnessVersion::V0, &script.as_bytes()[2..22]) |
| 102 | + } else if script.is_p2wsh() { |
| 103 | + if script.len() != 34 { |
| 104 | + return Err(AddressError::InvalidScript( |
| 105 | + "Invalid P2WSH script length".to_string(), |
| 106 | + )); |
| 107 | + } |
| 108 | + (WitnessVersion::V0, &script.as_bytes()[2..34]) |
| 109 | + } else if script.is_p2tr() { |
| 110 | + if script.len() != 34 { |
| 111 | + return Err(AddressError::InvalidScript( |
| 112 | + "Invalid P2TR script length".to_string(), |
| 113 | + )); |
| 114 | + } |
| 115 | + (WitnessVersion::V1, &script.as_bytes()[2..34]) |
| 116 | + } else { |
| 117 | + return Err(AddressError::UnsupportedScriptType( |
| 118 | + "Bech32 only supports witness programs (P2WPKH, P2WSH, P2TR)".to_string(), |
| 119 | + )); |
| 120 | + }; |
| 121 | + |
| 122 | + // Use custom HRP encoding for all networks |
| 123 | + encode_witness_with_custom_hrp(program, witness_version, self.hrp) |
| 124 | + } |
| 125 | + |
| 126 | + fn decode(&self, address: &str) -> Result<ScriptBuf> { |
| 127 | + // Use custom HRP decoding for all networks |
| 128 | + let script_bytes = decode_witness_with_custom_hrp(address, self.hrp)?; |
| 129 | + Ok(ScriptBuf::from(script_bytes)) |
| 130 | + } |
| 131 | +} |
0 commit comments