Skip to content

Commit e839f6f

Browse files
committed
bech32: replace blech32 implementation
Essentially, we copied the src/primitives/decode.rs file from the bech32 crate, which provides some convenience types for parsing HRP strings that have witness versions and where the checksum is chosen selectively based on which witness version. This is stupid BIP-173-specific behavior so I don't think it makes sense to try to generalize rust-bech32 to enable this. Copying the file is fine.
1 parent a110476 commit e839f6f

7 files changed

Lines changed: 1084 additions & 391 deletions

File tree

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ json-contract = [ "serde_json" ]
2222
base64 = ["bitcoin/base64"]
2323

2424
[dependencies]
25+
bech32 = { version = "0.10.0-beta" }
2526
bitcoin = "0.30.0"
2627
secp256k1-zkp = { version = "0.9.2", features = [ "global-context", "bitcoin_hashes" ] }
2728

src/address.rs

Lines changed: 80 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -15,22 +15,23 @@
1515
//! # Addresses
1616
//!
1717
18+
use std::convert::TryFrom as _;
1819
use std::error;
1920
use std::fmt;
21+
use std::fmt::Write as _;
2022
use std::str::FromStr;
2123

2224
use bitcoin::base58;
2325
use bitcoin::PublicKey;
24-
use crate::bech32::{self, u5, FromBase32, ToBase32};
26+
use crate::bech32::{Bech32, Bech32m, ByteIterExt, Fe32, Hrp, Fe32IterExt};
27+
use crate::blech32::{Blech32, Blech32m};
2528
use crate::hashes::Hash;
2629
use secp256k1_zkp;
2730
use secp256k1_zkp::Secp256k1;
2831
use secp256k1_zkp::Verification;
2932
#[cfg(feature = "serde")]
3033
use serde;
3134

32-
use crate::blech32;
33-
3435
use crate::schnorr::{TapTweak, TweakedPublicKey, UntweakedPublicKey};
3536
use crate::taproot::TapBranchHash;
3637

@@ -43,9 +44,9 @@ pub enum AddressError {
4344
/// Base58 encoding error
4445
Base58(base58::Error),
4546
/// Bech32 encoding error
46-
Bech32(bech32::Error),
47+
Bech32(crate::bech32::primitives::decode::SegwitHrpstringError),
4748
/// Blech32 encoding error
48-
Blech32(bech32::Error),
49+
Blech32(crate::blech32::decode::SegwitHrpstringError),
4950
/// Was unable to parse the address.
5051
InvalidAddress(String),
5152
/// Script version must be 0 to 16 inclusive
@@ -63,6 +64,18 @@ pub enum AddressError {
6364
InvalidBlindingPubKey(secp256k1_zkp::UpstreamError),
6465
}
6566

67+
impl From<crate::bech32::primitives::decode::SegwitHrpstringError> for AddressError {
68+
fn from(e: crate::bech32::primitives::decode::SegwitHrpstringError) -> Self {
69+
AddressError::Bech32(e)
70+
}
71+
}
72+
73+
impl From<crate::blech32::decode::SegwitHrpstringError> for AddressError {
74+
fn from(e: crate::blech32::decode::SegwitHrpstringError) -> Self {
75+
AddressError::Blech32(e)
76+
}
77+
}
78+
6679
impl fmt::Display for AddressError {
6780
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
6881
match *self {
@@ -123,9 +136,9 @@ pub struct AddressParams {
123136
/// The base58 prefix for blinded addresses.
124137
pub blinded_prefix: u8,
125138
/// The bech32 HRP for unblinded segwit addresses.
126-
pub bech_hrp: &'static str,
139+
pub bech_hrp: Hrp,
127140
/// The bech32 HRP for blinded segwit addresses.
128-
pub blech_hrp: &'static str,
141+
pub blech_hrp: Hrp,
129142
}
130143

131144
impl AddressParams {
@@ -134,31 +147,31 @@ impl AddressParams {
134147
p2pkh_prefix: 57,
135148
p2sh_prefix: 39,
136149
blinded_prefix: 12,
137-
bech_hrp: "ex",
138-
blech_hrp: "lq",
150+
bech_hrp: Hrp::parse_unchecked("ex"),
151+
blech_hrp: Hrp::parse_unchecked("lq"),
139152
};
140153

141154
/// The default Elements network address parameters.
142155
pub const ELEMENTS: AddressParams = AddressParams {
143156
p2pkh_prefix: 235,
144157
p2sh_prefix: 75,
145158
blinded_prefix: 4,
146-
bech_hrp: "ert",
147-
blech_hrp: "el",
159+
bech_hrp: Hrp::parse_unchecked("ert"),
160+
blech_hrp: Hrp::parse_unchecked("el"),
148161
};
149162

150163
/// The default liquid testnet network address parameters.
151164
pub const LIQUID_TESTNET: AddressParams = AddressParams {
152165
p2pkh_prefix: 36,
153166
p2sh_prefix: 19,
154167
blinded_prefix: 23,
155-
bech_hrp: "tex",
156-
blech_hrp: "tlq",
168+
bech_hrp: Hrp::parse_unchecked("tex"),
169+
blech_hrp: Hrp::parse_unchecked("tlq"),
157170
};
158171
}
159172

160173
/// The method used to produce an address
161-
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
174+
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
162175
pub enum Payload {
163176
/// pay-to-pkhash address
164177
PubkeyHash(PubkeyHash),
@@ -167,14 +180,14 @@ pub enum Payload {
167180
/// Segwit address
168181
WitnessProgram {
169182
/// The segwit version.
170-
version: u5,
183+
version: Fe32,
171184
/// The segwit program.
172185
program: Vec<u8>,
173186
},
174187
}
175188

176189
/// An Elements address.
177-
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
190+
#[derive(Clone, PartialEq, Eq, Hash)]
178191
pub struct Address {
179192
/// the network
180193
pub params: &'static AddressParams,
@@ -236,7 +249,7 @@ impl Address {
236249
Address {
237250
params,
238251
payload: Payload::WitnessProgram {
239-
version: u5::try_from_u8(0).expect("0<32"),
252+
version: Fe32::Q,
240253
program: WPubkeyHash::from_engine(hash_engine)[..].to_vec(),
241254
},
242255
blinding_pubkey: blinder,
@@ -273,7 +286,7 @@ impl Address {
273286
Address {
274287
params,
275288
payload: Payload::WitnessProgram {
276-
version: u5::try_from_u8(0).expect("0<32"),
289+
version: Fe32::Q,
277290
program: WScriptHash::hash(&script[..])[..].to_vec(),
278291
},
279292
blinding_pubkey: blinder,
@@ -312,7 +325,7 @@ impl Address {
312325
payload: {
313326
let (output_key, _parity) = internal_key.tap_tweak(secp, merkle_root);
314327
Payload::WitnessProgram {
315-
version: u5::try_from_u8(1).expect("0<32"),
328+
version: Fe32::P,
316329
program: output_key.into_inner().serialize().to_vec(),
317330
}
318331
},
@@ -331,7 +344,7 @@ impl Address {
331344
Address {
332345
params,
333346
payload: Payload::WitnessProgram {
334-
version: u5::try_from_u8(1).expect("0<32"),
347+
version: Fe32::P,
335348
program: output_key.into_inner().serialize().to_vec(),
336349
},
337350
blinding_pubkey: blinder,
@@ -351,17 +364,17 @@ impl Address {
351364
Payload::ScriptHash(Hash::from_slice(&script.as_bytes()[2..22]).unwrap())
352365
} else if script.is_v0_p2wpkh() {
353366
Payload::WitnessProgram {
354-
version: u5::try_from_u8(0).expect("0<32"),
367+
version: Fe32::Q,
355368
program: script.as_bytes()[2..22].to_vec(),
356369
}
357370
} else if script.is_v0_p2wsh() {
358371
Payload::WitnessProgram {
359-
version: u5::try_from_u8(0).expect("0<32"),
372+
version: Fe32::Q,
360373
program: script.as_bytes()[2..34].to_vec(),
361374
}
362375
} else if script.is_v1plus_p2witprog() {
363376
Payload::WitnessProgram {
364-
version: u5::try_from_u8(script.as_bytes()[0] - 0x50).expect("0<32"),
377+
version: Fe32::try_from(script.as_bytes()[0] - 0x50).expect("0<32"),
365378
program: script.as_bytes()[2..].to_vec(),
366379
}
367380
} else {
@@ -416,53 +429,14 @@ impl Address {
416429
blinded: bool,
417430
params: &'static AddressParams,
418431
) -> Result<Address, AddressError> {
419-
let (payload, is_bech32m) = if !blinded {
420-
let (_, payload, variant) = bech32::decode(s).map_err(AddressError::Bech32)?;
421-
(payload, variant == bech32::Variant::Bech32m)
432+
let (version, data): (Fe32, Vec<u8>) = if blinded {
433+
let hs = crate::blech32::decode::SegwitHrpstring::new(s)?;
434+
(hs.witness_version(), hs.byte_iter().collect())
422435
} else {
423-
let (_, payload, variant) = blech32::decode(s).map_err(AddressError::Blech32)?;
424-
(payload, variant == blech32::Variant::Blech32m)
436+
let hs = crate::bech32::primitives::decode::SegwitHrpstring::new(s)?;
437+
(hs.witness_version(), hs.byte_iter().collect())
425438
};
426439

427-
if payload.is_empty() {
428-
return Err(AddressError::InvalidAddress(s.to_owned()));
429-
}
430-
431-
// Get the script version and program (converted from 5-bit to 8-bit)
432-
let (version, data) = {
433-
let (v, p5) = payload.split_at(1);
434-
let data_res = Vec::from_base32(p5);
435-
if let Err(e) = data_res {
436-
return Err(match blinded {
437-
true => AddressError::Blech32(e),
438-
false => AddressError::Bech32(e),
439-
});
440-
}
441-
(v[0], data_res.unwrap())
442-
};
443-
444-
// Generic segwit checks.
445-
if version.to_u8() > 16 {
446-
return Err(AddressError::InvalidWitnessVersion(version.to_u8()));
447-
}
448-
if data.len() < 2 || data.len() > 40 + if blinded { 33 } else { 0 } {
449-
return Err(AddressError::InvalidWitnessProgramLength(data.len() - if blinded { 33 } else { 0 }));
450-
}
451-
452-
// Specific segwit v0 check.
453-
if !blinded && version.to_u8() == 0 && data.len() != 20 && data.len() != 32 {
454-
return Err(AddressError::InvalidSegwitV0ProgramLength(data.len()));
455-
}
456-
if blinded && version.to_u8() == 0 && data.len() != 53 && data.len() != 65 {
457-
return Err(AddressError::InvalidSegwitV0ProgramLength(data.len() - 33));
458-
}
459-
460-
if version.to_u8() == 0 && is_bech32m {
461-
return Err(AddressError::InvalidSegwitV0Encoding);
462-
} else if version.to_u8() > 0 && !is_bech32m {
463-
return Err(AddressError::InvalidWitnessEncoding);
464-
}
465-
466440
let (blinding_pubkey, program) = match blinded {
467441
true => (
468442
Some(
@@ -597,25 +571,36 @@ impl fmt::Display for Address {
597571
false => self.params.bech_hrp,
598572
};
599573

574+
// FIXME: surely we can fix this logic to not be so repetitive.
600575
if self.is_blinded() {
601-
let mut data = Vec::with_capacity(53);
602576
if let Some(ref blinder) = self.blinding_pubkey {
603-
data.extend_from_slice(&blinder.serialize());
577+
let byte_iter = IntoIterator::into_iter(blinder.serialize()).chain(witprog.iter().copied());
578+
let fe_iter = byte_iter.bytes_to_fes();
579+
if witver.to_u8() == 0 {
580+
for c in fe_iter.with_checksum::<Blech32>(&hrp).with_witness_version(witver).chars() {
581+
fmt.write_char(c)?;
582+
}
583+
} else {
584+
for c in fe_iter.with_checksum::<Blech32m>(&hrp).with_witness_version(witver).chars() {
585+
fmt.write_char(c)?;
586+
}
587+
}
588+
return Ok(());
604589
}
605-
data.extend_from_slice(witprog);
606-
let mut b32_data = vec![witver];
607-
b32_data.extend_from_slice(&data.to_base32());
608-
if witver.to_u8() == 0 {
609-
blech32::encode_to_fmt(fmt, hrp, &b32_data, blech32::Variant::Blech32)
610-
} else {
611-
blech32::encode_to_fmt(fmt, hrp, &b32_data, blech32::Variant::Blech32m)
590+
}
591+
592+
let byte_iter = witprog.iter().copied();
593+
let fe_iter = byte_iter.bytes_to_fes();
594+
if witver.to_u8() == 0 {
595+
for c in fe_iter.with_checksum::<Bech32>(&hrp).with_witness_version(witver).chars() {
596+
fmt.write_char(c)?;
612597
}
613598
} else {
614-
let var = if witver.to_u8() == 0 { bech32::Variant::Bech32 } else { bech32::Variant::Bech32m };
615-
let mut bech32_writer = bech32::Bech32Writer::new(hrp, var, fmt)?;
616-
bech32::WriteBase32::write_u5(&mut bech32_writer, witver)?;
617-
bech32::ToBase32::write_base32(&witprog, &mut bech32_writer)
599+
for c in fe_iter.with_checksum::<Bech32m>(&hrp).with_witness_version(witver).chars() {
600+
fmt.write_char(c)?;
601+
}
618602
}
603+
Ok(())
619604
}
620605
}
621606
}
@@ -640,12 +625,12 @@ fn find_prefix(bech32: &str) -> &str {
640625
/// Checks if both prefixes match, regardless of case.
641626
/// The first prefix can be mixed case, but the second one is expected in
642627
/// lower case.
643-
fn match_prefix(prefix_mixed: &str, prefix_lower: &str) -> bool {
644-
if prefix_lower.len() != prefix_mixed.len() {
628+
fn match_prefix(prefix_mixed: &str, target: Hrp) -> bool {
629+
if target.len() != prefix_mixed.len() {
645630
false
646631
} else {
647-
prefix_lower
648-
.chars()
632+
target
633+
.lowercase_char_iter()
649634
.zip(prefix_mixed.chars())
650635
.all(|(char_lower, char_mixed)| char_lower == char_mixed.to_ascii_lowercase())
651636
}
@@ -775,6 +760,13 @@ mod test {
775760
);
776761
}
777762

763+
#[test]
764+
fn regression_188() {
765+
// Tests that the `tlq` prefix was not accidentally changed, e.g. to `tlg` :).
766+
let addr = Address::from_str("tlq1qq2xvpcvfup5j8zscjq05u2wxxjcyewk7979f3mmz5l7uw5pqmx6xf5xy50hsn6vhkm5euwt72x878eq6zxx2z58hd7zrsg9qn").unwrap();
767+
roundtrips(&addr);
768+
}
769+
778770
#[test]
779771
fn exhaustive() {
780772
let blinder_hex = "0218845781f631c48f1c9709e23092067d06837f30aa0cd0544ac887fe91ddd166";
@@ -857,25 +849,25 @@ mod test {
857849
let address: Result<Address, _> = "el1pq0umk3pez693jrrlxz9ndlkuwne93gdu9g83mhhzuyf46e3mdzfpva0w48gqgzgrklncnm0k5zeyw8my2ypfsxguu9nrdg2pc".parse();
858850
assert_eq!(
859851
address.err().unwrap().to_string(),
860-
"v1+ witness program must use b(l)ech32m not b(l)ech32",
852+
"blech32 error: invalid checksum", // is valid blech32, but should be blech32m
861853
);
862854

863855
let address: Result<Address, _> = "el1qq0umk3pez693jrrlxz9ndlkuwne93gdu9g83mhhzuyf46e3mdzfpva0w48gqgzgrklncnm0k5zeyw8my2ypfsnnmzrstzt7de".parse();
864856
assert_eq!(
865857
address.err().unwrap().to_string(),
866-
"v0 witness program must use b(l)ech32 not b(l)ech32m",
858+
"blech32 error: invalid checksum", // is valid blech32m, but should be blech32
867859
);
868860

869861
let address: Result<Address, _> = "ert130xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqqu2tys".parse();
870862
assert_eq!(
871863
address.err().unwrap().to_string(),
872-
"invalid witness script version: 17",
864+
"bech32 error: invalid segwit witness version: 3", // FIXME https://github.com/rust-bitcoin/rust-bech32/issues/162 should be 17
873865
);
874866

875867
let address: Result<Address, _> = "el1pq0umk3pez693jrrlxz9ndlkuwne93gdu9g83mhhzuyf46e3mdzfpva0w48gqgzgrklncnm0k5zeyw8my2ypfsqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqpe9jfn0gypaj".parse();
876868
assert_eq!(
877869
address.err().unwrap().to_string(),
878-
"the witness program must be between 2 and 40 bytes in length, not 41",
870+
"blech32 error: invalid witness length",
879871
);
880872

881873
// "invalid prefix" gives a weird error message because we do

0 commit comments

Comments
 (0)