Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Address to optimized QR string #581

Merged
merged 8 commits into from May 6, 2021
55 changes: 50 additions & 5 deletions src/util/address.rs
Expand Up @@ -36,7 +36,7 @@
//! let address = Address::p2pkh(&public_key, Network::Bitcoin);
//! ```

use std::fmt::{self, Display, Formatter};
use std::fmt;
use std::str::FromStr;
use std::error;

Expand Down Expand Up @@ -352,10 +352,23 @@ impl Address {
pub fn script_pubkey(&self) -> script::Script {
self.payload.script_pubkey()
}

/// Creates a string optimized to be encoded in QR codes, meaning it becomes uppercase if bech32.
///
/// Quoting BIP 173 "inside QR codes uppercase SHOULD be used, as those permit the use of
/// alphanumeric mode, which is 45% more compact than the normal byte mode."
/// Even inside Bitcoin URI may be more efficient to use the uppercase address since in QR codes
/// encoding modes can be mixed as needed within a QR symbol.
///
/// This `fn` is a shorthand of the alternate formatting `{:#}` which should be preferred in most
/// cases because it avoids the [String] allocation.
pub fn to_qr_string(&self) -> String {
sgeisler marked this conversation as resolved.
Show resolved Hide resolved
format!("{:#}", self)
}
}

impl Display for Address {
fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
impl fmt::Display for Address {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
match self.payload {
Payload::PubkeyHash(ref hash) => {
let mut prefixed = [0; 21];
Expand All @@ -381,17 +394,35 @@ impl Display for Address {
} => {
let hrp = match self.network {
Network::Bitcoin => "bc",
Network::Testnet | Network::Signet => "tb",
Network::Testnet | Network::Signet => "tb",
Network::Regtest => "bcrt",
};
sgeisler marked this conversation as resolved.
Show resolved Hide resolved
let mut bech32_writer = bech32::Bech32Writer::new(hrp, fmt)?;
let mut upper_writer;
let writer = if fmt.alternate() {
upper_writer = UpperWriter(fmt);
&mut upper_writer as &mut dyn fmt::Write
} else {
fmt as &mut dyn fmt::Write
};
let mut bech32_writer = bech32::Bech32Writer::new(hrp, writer)?;
bech32::WriteBase32::write_u5(&mut bech32_writer, ver)?;
bech32::ToBase32::write_base32(&prog, &mut bech32_writer)
}
}
}
}

struct UpperWriter<W: fmt::Write>(W);

impl<W: fmt::Write> fmt::Write for UpperWriter<W> {
fn write_str(&mut self, s: &str) -> fmt::Result {
for c in s.chars() {
self.0.write_char(c.to_ascii_uppercase())?;
}
Ok(())
}
}

/// Extract the bech32 prefix.
/// Returns the same slice when no prefix is found.
fn find_bech32_prefix(bech32: &str) -> &str {
Expand Down Expand Up @@ -743,4 +774,18 @@ mod tests {
hex_script!("001454d26dddb59c7073c6a197946ea1841951fa7a74")
);
}

#[test]
fn test_qr_string() {
for el in ["132F25rTsvBdp9JzLLBHP5mvGY66i1xdiM", "33iFwdLuRpW1uK1RTRqsoi8rR4NpDzk66k"].iter() {
let addr = Address::from_str(el).unwrap();
assert_eq!(addr.to_qr_string(), *el);
}

for el in ["bcrt1q2nfxmhd4n3c8834pj72xagvyr9gl57n5r94fsl", "bc1qwqdg6squsna38e46795at95yu9atm8azzmyvckulcc7kytlcckxswvvzej"].iter() {
let addr = Address::from_str(el).unwrap();
assert_eq!(addr.to_qr_string(), el.to_ascii_uppercase());
}
}

}