From b9d5200448b3c090ca89ba51ab387e1a18e3d577 Mon Sep 17 00:00:00 2001 From: Riccardo Casatta Date: Mon, 22 Mar 2021 13:42:32 +0100 Subject: [PATCH 1/8] Access Display and Formatter with fmt:: like in other places --- src/util/address.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/util/address.rs b/src/util/address.rs index 2c26e7cf..1a1f83f7 100644 --- a/src/util/address.rs +++ b/src/util/address.rs @@ -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; @@ -354,8 +354,8 @@ impl Address { } } -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]; From d18554e756cea42586a782e44a4623163dc65599 Mon Sep 17 00:00:00 2001 From: Riccardo Casatta Date: Mon, 22 Mar 2021 13:58:59 +0100 Subject: [PATCH 2/8] Address to string conversion optimized for qr codes --- src/util/address.rs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/util/address.rs b/src/util/address.rs index 1a1f83f7..d0192507 100644 --- a/src/util/address.rs +++ b/src/util/address.rs @@ -352,6 +352,15 @@ 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 + pub fn to_qr_string(&self) -> String { + let address_string = self.to_string(); + match self.payload { + Payload::WitnessProgram { .. } => address_string.to_ascii_uppercase(), + _ => address_string, + } + } } impl fmt::Display for Address { @@ -743,4 +752,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()); + } + } + } From cac3f460a265d5c50a813358d02fa881beab5272 Mon Sep 17 00:00:00 2001 From: Riccardo Casatta Date: Tue, 23 Mar 2021 09:40:19 +0100 Subject: [PATCH 3/8] improve to_qr_string doc --- src/util/address.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/util/address.rs b/src/util/address.rs index d0192507..cd8979ce 100644 --- a/src/util/address.rs +++ b/src/util/address.rs @@ -353,7 +353,11 @@ impl Address { self.payload.script_pubkey() } - /// Creates a string optimized to be encoded in QR codes, meaning it becomes uppercase if bech32 + /// 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. pub fn to_qr_string(&self) -> String { let address_string = self.to_string(); match self.payload { From 104836a042f81573065fe447639919d05e283f37 Mon Sep 17 00:00:00 2001 From: Riccardo Casatta Date: Wed, 7 Apr 2021 15:49:58 +0200 Subject: [PATCH 4/8] implements alternate formatting for address --- src/util/address.rs | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/src/util/address.rs b/src/util/address.rs index cd8979ce..1598a8df 100644 --- a/src/util/address.rs +++ b/src/util/address.rs @@ -359,11 +359,7 @@ impl Address { /// 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. pub fn to_qr_string(&self) -> String { - let address_string = self.to_string(); - match self.payload { - Payload::WitnessProgram { .. } => address_string.to_ascii_uppercase(), - _ => address_string, - } + format!("{:#}", self) } } @@ -392,14 +388,20 @@ impl fmt::Display for Address { version: ver, program: ref prog, } => { - let hrp = match self.network { - Network::Bitcoin => "bc", - Network::Testnet | Network::Signet => "tb", - Network::Regtest => "bcrt", - }; - let mut bech32_writer = bech32::Bech32Writer::new(hrp, fmt)?; - bech32::WriteBase32::write_u5(&mut bech32_writer, ver)?; - bech32::ToBase32::write_base32(&prog, &mut bech32_writer) + if fmt.alternate() { + //TODO format without allocation when alternate uppercase is in bech32 + let lower = self.to_string(); + write!(fmt, "{}", lower.to_ascii_uppercase()) + } else { + let hrp = match self.network { + Network::Bitcoin => "bc", + Network::Testnet | Network::Signet => "tb", + Network::Regtest => "bcrt", + }; + let mut bech32_writer = bech32::Bech32Writer::new(hrp, fmt)?; + bech32::WriteBase32::write_u5(&mut bech32_writer, ver)?; + bech32::ToBase32::write_base32(&prog, &mut bech32_writer) + } } } } From 85ae82febb20e3f7330ef1d6e34d69a59aebad34 Mon Sep 17 00:00:00 2001 From: Riccardo Casatta Date: Wed, 7 Apr 2021 16:56:54 +0200 Subject: [PATCH 5/8] use the char trick to avoid allocation --- src/util/address.rs | 39 +++++++++++++++++++++++++-------------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/src/util/address.rs b/src/util/address.rs index 1598a8df..896a4fb3 100644 --- a/src/util/address.rs +++ b/src/util/address.rs @@ -388,25 +388,36 @@ impl fmt::Display for Address { version: ver, program: ref prog, } => { - if fmt.alternate() { - //TODO format without allocation when alternate uppercase is in bech32 - let lower = self.to_string(); - write!(fmt, "{}", lower.to_ascii_uppercase()) - } else { - let hrp = match self.network { - Network::Bitcoin => "bc", - Network::Testnet | Network::Signet => "tb", - Network::Regtest => "bcrt", - }; - let mut bech32_writer = bech32::Bech32Writer::new(hrp, fmt)?; - bech32::WriteBase32::write_u5(&mut bech32_writer, ver)?; - bech32::ToBase32::write_base32(&prog, &mut bech32_writer) - } + let hrp = match self.network { + Network::Bitcoin => "bc", + Network::Testnet | Network::Signet => "tb", + Network::Regtest => "bcrt", + }; + let is_alternate = fmt.alternate(); + let mut opt_up_writer = OptionallyUpperWriter(fmt, is_alternate); + let mut bech32_writer = bech32::Bech32Writer::new(hrp, &mut opt_up_writer)?; + bech32::WriteBase32::write_u5(&mut bech32_writer, ver)?; + bech32::ToBase32::write_base32(&prog, &mut bech32_writer) } } } } +struct OptionallyUpperWriter(W, bool); + +impl fmt::Write for OptionallyUpperWriter { + fn write_str(&mut self, s: &str) -> fmt::Result { + if self.1 { + for c in s.chars() { + self.0.write_char(c.to_ascii_uppercase())?; + } + } else { + self.0.write_str(s)?; + } + Ok(()) + } +} + /// Extract the bech32 prefix. /// Returns the same slice when no prefix is found. fn find_bech32_prefix(bech32: &str) -> &str { From bc406bfdd6c579794387494094e19e0361c59ad4 Mon Sep 17 00:00:00 2001 From: Martin Habovstiak Date: Fri, 9 Apr 2021 17:58:35 +0200 Subject: [PATCH 6/8] Use &mut dyn fmt::Write instead of bool This replaces manually-written dynamic dispatch with `&mut dyn fmt::Write` which is hopefully more readable. --- src/util/address.rs | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/util/address.rs b/src/util/address.rs index 896a4fb3..dd664d6a 100644 --- a/src/util/address.rs +++ b/src/util/address.rs @@ -393,9 +393,14 @@ impl fmt::Display for Address { Network::Testnet | Network::Signet => "tb", Network::Regtest => "bcrt", }; - let is_alternate = fmt.alternate(); - let mut opt_up_writer = OptionallyUpperWriter(fmt, is_alternate); - let mut bech32_writer = bech32::Bech32Writer::new(hrp, &mut opt_up_writer)?; + 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) } @@ -403,16 +408,12 @@ impl fmt::Display for Address { } } -struct OptionallyUpperWriter(W, bool); +struct UpperWriter(W); -impl fmt::Write for OptionallyUpperWriter { +impl fmt::Write for UpperWriter { fn write_str(&mut self, s: &str) -> fmt::Result { - if self.1 { - for c in s.chars() { - self.0.write_char(c.to_ascii_uppercase())?; - } - } else { - self.0.write_str(s)?; + for c in s.chars() { + self.0.write_char(c.to_ascii_uppercase())?; } Ok(()) } From 3158cedea0539005375fc484dc82a423cb1c9925 Mon Sep 17 00:00:00 2001 From: Riccardo Casatta Date: Fri, 9 Apr 2021 18:24:27 +0200 Subject: [PATCH 7/8] document alternate formatting --- src/util/address.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/util/address.rs b/src/util/address.rs index dd664d6a..580acc28 100644 --- a/src/util/address.rs +++ b/src/util/address.rs @@ -354,10 +354,14 @@ impl Address { } /// 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 { format!("{:#}", self) } From 0a9149657030f25642959a35b31c4a4738175520 Mon Sep 17 00:00:00 2001 From: Riccardo Casatta Date: Thu, 15 Apr 2021 10:40:57 +0200 Subject: [PATCH 8/8] rename to_qr_string into to_qr_uri returning also the schema --- src/util/address.rs | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/util/address.rs b/src/util/address.rs index 580acc28..788a10d4 100644 --- a/src/util/address.rs +++ b/src/util/address.rs @@ -353,20 +353,24 @@ impl Address { self.payload.script_pubkey() } - /// Creates a string optimized to be encoded in QR codes, meaning it becomes uppercase if bech32. + /// Creates a URI string *bitcoin:address* optimized to be encoded in QR codes. + /// + /// If the address is bech32, both the schema and the address become uppercase. + /// If the address is base58, the schema is lowercase and the address is left mixed case. /// /// 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 { - format!("{:#}", self) + pub fn to_qr_uri(&self) -> String { + let schema = match self.payload { + Payload::WitnessProgram { .. } => "BITCOIN", + _ => "bitcoin", + }; + format!("{}:{:#}", schema, self) } } +// Alternate formatting `{:#}` is used to return uppercase version of bech32 addresses which should +// be used in QR codes, see [Address::to_qr_uri] impl fmt::Display for Address { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { match self.payload { @@ -779,12 +783,12 @@ mod tests { fn test_qr_string() { for el in ["132F25rTsvBdp9JzLLBHP5mvGY66i1xdiM", "33iFwdLuRpW1uK1RTRqsoi8rR4NpDzk66k"].iter() { let addr = Address::from_str(el).unwrap(); - assert_eq!(addr.to_qr_string(), *el); + assert_eq!(addr.to_qr_uri(), format!("bitcoin:{}", el)); } for el in ["bcrt1q2nfxmhd4n3c8834pj72xagvyr9gl57n5r94fsl", "bc1qwqdg6squsna38e46795at95yu9atm8azzmyvckulcc7kytlcckxswvvzej"].iter() { let addr = Address::from_str(el).unwrap(); - assert_eq!(addr.to_qr_string(), el.to_ascii_uppercase()); + assert_eq!(addr.to_qr_uri(), format!("BITCOIN:{}", el.to_ascii_uppercase()) ); } }