From da1a87b0b3dec7d9202f64d46ae394a0aa0593ea Mon Sep 17 00:00:00 2001 From: Martin Habovstiak Date: Mon, 27 Feb 2023 13:11:34 +0100 Subject: [PATCH] Update `bitcoin` to 0.30 --- Cargo.toml | 2 +- src/de.rs | 19 +++++++++-------- src/lib.rs | 60 +++++++++++++++++++++++++++++++++++++++++++----------- src/ser.rs | 4 ++-- 4 files changed, 61 insertions(+), 24 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 56922f3..e8d2655 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,4 +23,4 @@ non-compliant-bytes = ["either"] [dependencies] either = { version = "1.6.1", optional = true } percent-encoding-rfc3986 = "0.1.3" -bitcoin = "0.29.0" +bitcoin = "0.30.0" diff --git a/src/de.rs b/src/de.rs index 37fe076..1b8278f 100644 --- a/src/de.rs +++ b/src/de.rs @@ -10,13 +10,14 @@ use alloc::borrow::ToOwned; use alloc::borrow::Cow; use alloc::string::String; use core::convert::{TryFrom, TryInto}; -use bitcoin::util::amount::{Denomination, ParseAmountError}; -use bitcoin::util::address::Error as AddressError; +use bitcoin::amount::{Denomination, ParseAmountError}; +use bitcoin::address::Error as AddressError; +use bitcoin::address::NetworkValidation; use core::fmt; use super::{Uri, Param}; use percent_encoding_rfc3986::PercentDecodeError; -impl<'a, T: DeserializeParams<'a>> Uri<'a, T> { +impl<'a, T: DeserializeParams<'a>> Uri<'a, bitcoin::address::NetworkUnchecked, T> { /// Implements deserialization. fn deserialize_raw(string: &'a str) -> Result> { const SCHEME: &str = "bitcoin:"; @@ -82,11 +83,11 @@ impl<'a, T: DeserializeParams<'a>> Uri<'a, T> { } } -impl<'a, T> Uri<'a, T> { +impl<'a, NetVal: NetworkValidation, T> Uri<'a, NetVal, T> { /// Makes the lifetime `'static` by converting all fields to owned. /// /// Note that this does **not** affect `extras`! - fn into_static(self) -> Uri<'static, T> { + fn into_static(self) -> Uri<'static, NetVal, T> { Uri { address: self.address, amount: self.amount, @@ -282,7 +283,7 @@ impl std::error::Error for UriError { } /// **Warning**: this implementation may needlessly allocate, consider using `TryFrom<&str>` instead. -impl<'a, T: for<'de> DeserializeParams<'de>> core::str::FromStr for Uri<'a, T> { +impl<'a, T: for<'de> DeserializeParams<'de>> core::str::FromStr for Uri<'a, bitcoin::address::NetworkUnchecked, T> { type Err = Error; fn from_str(s: &str) -> Result { @@ -290,7 +291,7 @@ impl<'a, T: for<'de> DeserializeParams<'de>> core::str::FromStr for Uri<'a, T> { } } -impl<'a, T: DeserializeParams<'a>> TryFrom<&'a str> for Uri<'a, T> { +impl<'a, T: DeserializeParams<'a>> TryFrom<&'a str> for Uri<'a, bitcoin::address::NetworkUnchecked, T> { type Error = Error; fn try_from(s: &'a str) -> Result { @@ -299,7 +300,7 @@ impl<'a, T: DeserializeParams<'a>> TryFrom<&'a str> for Uri<'a, T> { } /// **Warning**: this implementation may needlessly allocate, consider using `TryFrom<&str>` instead. -impl<'a, T: for<'de> DeserializeParams<'de>> TryFrom for Uri<'a, T> { +impl<'a, T: for<'de> DeserializeParams<'de>> TryFrom for Uri<'a, bitcoin::address::NetworkUnchecked, T> { type Error = Error; fn try_from(s: String) -> Result { @@ -308,7 +309,7 @@ impl<'a, T: for<'de> DeserializeParams<'de>> TryFrom for Uri<'a, T> { } /// **Warning**: this implementation may needlessly allocate, consider using `TryFrom<&str>` instead. -impl<'a, T: for<'de> DeserializeParams<'de>> TryFrom> for Uri<'a, T> { +impl<'a, T: for<'de> DeserializeParams<'de>> TryFrom> for Uri<'a, bitcoin::address::NetworkUnchecked, T> { type Error = Error; fn try_from(s: Cow<'a, str>) -> Result { diff --git a/src/lib.rs b/src/lib.rs index 656752f..e82131f 100755 --- a/src/lib.rs +++ b/src/lib.rs @@ -53,6 +53,7 @@ use percent_encoding_rfc3986::{PercentDecode, PercentDecodeError}; #[cfg(feature = "non-compliant-bytes")] use either::Either; use core::convert::{TryFrom, TryInto}; +use bitcoin::address::NetworkValidation; pub use de::{DeserializeParams, DeserializationState, DeserializationError}; pub use ser::{SerializeParams}; @@ -78,11 +79,14 @@ pub use ser::{SerializeParams}; /// [See compatibility table.](https://github.com/btcpayserver/btcpayserver/issues/2110) #[non_exhaustive] #[derive(Debug, Clone)] -pub struct Uri<'a, Extras = NoExtras> { +pub struct Uri<'a, NetVal = bitcoin::address::NetworkChecked, Extras = NoExtras> +where + NetVal: NetworkValidation, +{ /// The address provided in the URI. /// /// This field is mandatory because the address is mandatory in BIP21. - pub address: bitcoin::Address, + pub address: bitcoin::Address, /// Number of satoshis requested as payment. pub amount: Option, @@ -97,12 +101,12 @@ pub struct Uri<'a, Extras = NoExtras> { pub extras: Extras, } -impl<'a, T: Default> Uri<'a, T> { +impl<'a, NetVal: NetworkValidation, T: Default> Uri<'a, NetVal, T> { /// Creates an URI with defaults. /// /// This sets all fields except `address` to default values. /// They can be overwritten in subsequent assignments before displaying the URI. - pub fn new(address: bitcoin::Address) -> Self { + pub fn new(address: bitcoin::Address) -> Self { Uri { address, amount: None, @@ -113,12 +117,12 @@ impl<'a, T: Default> Uri<'a, T> { } } -impl<'a, T> Uri<'a, T> { +impl<'a, NetVal: NetworkValidation, T> Uri<'a, NetVal, T> { /// Creates an URI with defaults. /// /// This sets all fields except `address` and `extras` to default values. /// They can be overwritten in subsequent assignments before displaying the URI. - pub fn with_extras(address: bitcoin::Address, extras: T) -> Self { + pub fn with_extras(address: bitcoin::Address, extras: T) -> Self { Uri { address, amount: None, @@ -129,6 +133,38 @@ impl<'a, T> Uri<'a, T> { } } +impl<'a, T> Uri<'a, bitcoin::address::NetworkUnchecked, T> { + /// Checks that the bitcoin network in the URI is `network`. + pub fn require_network(self, network: bitcoin::Network) -> Result, InvalidNetworkError> { + if self.address.is_valid_for_network(network) { + Ok(self.assume_checked()) + } else { + Err(InvalidNetworkError { + required: network, + found: self.address.network, + }) + } + } + + /// Marks URI validated without checks. + pub fn assume_checked(self) -> Uri<'a, bitcoin::address::NetworkChecked, T> { + Uri { + address: self.address.assume_checked(), + amount: self.amount, + label: self.label, + message: self.message, + extras: self.extras, + } + } +} + +/// An error returned when network validation fails. +#[derive(Debug, Clone)] +pub struct InvalidNetworkError { + required: bitcoin::Network, + found: bitcoin::Network, +} + /// Abstracted stringly parameter in the URI. /// /// This type abstracts the parameter that may be encoded allowing lazy decoding, possibly even @@ -355,7 +391,7 @@ mod tests { #[test] fn just_address() { let input = "bitcoin:1andreas3batLhQa2FawWjeyjCqyBzypd"; - let uri = input.parse::>().unwrap(); + let uri = input.parse::>().unwrap().require_network(bitcoin::Network::Bitcoin).unwrap(); assert_eq!(uri.address.to_string(), "1andreas3batLhQa2FawWjeyjCqyBzypd"); assert!(uri.amount.is_none()); assert!(uri.label.is_none()); @@ -367,7 +403,7 @@ mod tests { #[test] fn address_with_name() { let input = "bitcoin:1andreas3batLhQa2FawWjeyjCqyBzypd?label=Luke-Jr"; - let uri = input.parse::>().unwrap(); + let uri = input.parse::>().unwrap().require_network(bitcoin::Network::Bitcoin).unwrap(); let label: Cow<'_, str> = uri.label.clone().unwrap().try_into().unwrap(); assert_eq!(uri.address.to_string(), "1andreas3batLhQa2FawWjeyjCqyBzypd"); assert_eq!(label, "Luke-Jr"); @@ -381,7 +417,7 @@ mod tests { #[test] fn request_20_point_30_btc_to_luke_dash_jr() { let input = "bitcoin:1andreas3batLhQa2FawWjeyjCqyBzypd?amount=20.3&label=Luke-Jr"; - let uri = input.parse::>().unwrap(); + let uri = input.parse::>().unwrap().require_network(bitcoin::Network::Bitcoin).unwrap(); let label: Cow<'_, str> = uri.label.clone().unwrap().try_into().unwrap(); assert_eq!(uri.address.to_string(), "1andreas3batLhQa2FawWjeyjCqyBzypd"); assert_eq!(label, "Luke-Jr"); @@ -395,7 +431,7 @@ mod tests { #[test] fn request_50_btc_with_message() { let input = "bitcoin:1andreas3batLhQa2FawWjeyjCqyBzypd?amount=50&label=Luke-Jr&message=Donation%20for%20project%20xyz"; - let uri = input.parse::>().unwrap(); + let uri = input.parse::>().unwrap().require_network(bitcoin::Network::Bitcoin).unwrap(); let label: Cow<'_, str> = uri.label.clone().unwrap().try_into().unwrap(); let message: Cow<'_, str> = uri.message.clone().unwrap().try_into().unwrap(); assert_eq!(uri.address.to_string(), "1andreas3batLhQa2FawWjeyjCqyBzypd"); @@ -409,14 +445,14 @@ mod tests { #[test] fn required_not_understood() { let input = "bitcoin:1andreas3batLhQa2FawWjeyjCqyBzypd?req-somethingyoudontunderstand=50&req-somethingelseyoudontget=999"; - let uri = input.parse::>(); + let uri = input.parse::>(); assert!(uri.is_err()); } #[test] fn required_understood() { let input = "bitcoin:1andreas3batLhQa2FawWjeyjCqyBzypd?somethingyoudontunderstand=50&somethingelseyoudontget=999"; - let uri = input.parse::>().unwrap(); + let uri = input.parse::>().unwrap().require_network(bitcoin::Network::Bitcoin).unwrap(); assert_eq!(uri.address.to_string(), "1andreas3batLhQa2FawWjeyjCqyBzypd"); assert!(uri.amount.is_none()); assert!(uri.label.is_none()); diff --git a/src/ser.rs b/src/ser.rs index b82919d..a8edffd 100644 --- a/src/ser.rs +++ b/src/ser.rs @@ -5,7 +5,7 @@ //! Check [`SerializeParams`] to get started. use alloc::borrow::Cow; -use bitcoin::util::amount::Denomination; +use bitcoin::amount::Denomination; use core::fmt; use super::{Uri, Param, ParamInner}; @@ -121,7 +121,7 @@ fn maybe_display_param(writer: &mut impl fmt::Write, key: impl fmt::Display, val /// Formats QR-code-optimized URI if alternate form (`{:#}`) is used. #[rustfmt::skip] -impl<'a, T> fmt::Display for Uri<'a, T> where for<'b> &'b T: SerializeParams { +impl<'a, T> fmt::Display for Uri<'a, bitcoin::address::NetworkChecked, T> where for<'b> &'b T: SerializeParams { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { if f.alternate() { write!(f, "BITCOIN:{:#}", self.address)?;