diff --git a/payjoin/src/send/error.rs b/payjoin/src/send/error.rs index 94c13737..6a377b78 100644 --- a/payjoin/src/send/error.rs +++ b/payjoin/src/send/error.rs @@ -4,6 +4,9 @@ use bitcoin::locktime::absolute::LockTime; use bitcoin::transaction::Version; use bitcoin::{AddressType, Sequence}; +#[cfg(feature = "v2")] +use crate::uri::error::ParseReceiverPubkeyError; + /// Error that may occur when the response from receiver is malformed. /// /// This is currently opaque type because we aren't sure which variants will stay. @@ -194,7 +197,7 @@ pub(crate) enum InternalCreateRequestError { #[cfg(feature = "v2")] OhttpEncapsulation(crate::ohttp::OhttpEncapsulationError), #[cfg(feature = "v2")] - ParseSubdirectory(ParseSubdirectoryError), + ParseReceiverPubkey(ParseReceiverPubkeyError), #[cfg(feature = "v2")] MissingOhttpConfig, #[cfg(feature = "v2")] @@ -225,7 +228,7 @@ impl fmt::Display for CreateRequestError { #[cfg(feature = "v2")] OhttpEncapsulation(e) => write!(f, "v2 error: {}", e), #[cfg(feature = "v2")] - ParseSubdirectory(e) => write!(f, "cannot parse subdirectory: {}", e), + ParseReceiverPubkey(e) => write!(f, "cannot parse receiver public key: {}", e), #[cfg(feature = "v2")] MissingOhttpConfig => write!(f, "no ohttp configuration with which to make a v2 request available"), #[cfg(feature = "v2")] @@ -258,7 +261,7 @@ impl std::error::Error for CreateRequestError { #[cfg(feature = "v2")] OhttpEncapsulation(error) => Some(error), #[cfg(feature = "v2")] - ParseSubdirectory(error) => Some(error), + ParseReceiverPubkey(error) => Some(error), #[cfg(feature = "v2")] MissingOhttpConfig => None, #[cfg(feature = "v2")] @@ -278,44 +281,9 @@ impl From for CreateRequestError { } #[cfg(feature = "v2")] -impl From for CreateRequestError { - fn from(value: ParseSubdirectoryError) -> Self { - CreateRequestError(InternalCreateRequestError::ParseSubdirectory(value)) - } -} - -#[cfg(feature = "v2")] -#[derive(Debug)] -pub(crate) enum ParseSubdirectoryError { - MissingSubdirectory, - SubdirectoryNotBase64(bitcoin::base64::DecodeError), - SubdirectoryInvalidPubkey(crate::hpke::HpkeError), -} - -#[cfg(feature = "v2")] -impl std::fmt::Display for ParseSubdirectoryError { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - use ParseSubdirectoryError::*; - - match &self { - MissingSubdirectory => write!(f, "subdirectory is missing"), - SubdirectoryNotBase64(e) => write!(f, "subdirectory is not valid base64: {}", e), - SubdirectoryInvalidPubkey(e) => - write!(f, "subdirectory does not represent a valid pubkey: {}", e), - } - } -} - -#[cfg(feature = "v2")] -impl std::error::Error for ParseSubdirectoryError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - use ParseSubdirectoryError::*; - - match &self { - MissingSubdirectory => None, - SubdirectoryNotBase64(error) => Some(error), - SubdirectoryInvalidPubkey(error) => Some(error), - } +impl From for CreateRequestError { + fn from(value: ParseReceiverPubkeyError) -> Self { + CreateRequestError(InternalCreateRequestError::ParseReceiverPubkey(value)) } } diff --git a/payjoin/src/send/mod.rs b/payjoin/src/send/mod.rs index 494192e9..a0823a05 100644 --- a/payjoin/src/send/mod.rs +++ b/payjoin/src/send/mod.rs @@ -322,21 +322,11 @@ impl Sender { } #[cfg(feature = "v2")] - fn extract_rs_pubkey(&self) -> Result { - use error::ParseSubdirectoryError; - - let subdirectory = self - .endpoint - .path_segments() - .and_then(|mut segments| segments.next()) - .ok_or(ParseSubdirectoryError::MissingSubdirectory)?; - - let pubkey_bytes = BASE64_URL_SAFE_NO_PAD - .decode(subdirectory) - .map_err(ParseSubdirectoryError::SubdirectoryNotBase64)?; - - HpkePublicKey::from_compressed_bytes(&pubkey_bytes) - .map_err(ParseSubdirectoryError::SubdirectoryInvalidPubkey) + fn extract_rs_pubkey( + &self, + ) -> Result { + use crate::uri::UrlExt; + self.endpoint.receiver_pubkey() } pub fn endpoint(&self) -> &Url { &self.endpoint } diff --git a/payjoin/src/uri/error.rs b/payjoin/src/uri/error.rs index f1f6ed9c..7bd94281 100644 --- a/payjoin/src/uri/error.rs +++ b/payjoin/src/uri/error.rs @@ -11,6 +11,41 @@ pub(crate) enum InternalPjParseError { UnsecureEndpoint, } +#[cfg(feature = "v2")] +#[derive(Debug)] +pub(crate) enum ParseReceiverPubkeyError { + MissingPubkey, + PubkeyNotBase64(bitcoin::base64::DecodeError), + InvalidPubkey(crate::hpke::HpkeError), +} + +#[cfg(feature = "v2")] +impl std::fmt::Display for ParseReceiverPubkeyError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + use ParseReceiverPubkeyError::*; + + match &self { + MissingPubkey => write!(f, "receiver public key is missing"), + PubkeyNotBase64(e) => write!(f, "receiver public is not valid base64: {}", e), + InvalidPubkey(e) => + write!(f, "receiver public key does not represent a valid pubkey: {}", e), + } + } +} + +#[cfg(feature = "v2")] +impl std::error::Error for ParseReceiverPubkeyError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + use ParseReceiverPubkeyError::*; + + match &self { + MissingPubkey => None, + PubkeyNotBase64(error) => Some(error), + InvalidPubkey(error) => Some(error), + } + } +} + impl From for PjParseError { fn from(value: InternalPjParseError) -> Self { PjParseError(value) } } diff --git a/payjoin/src/uri/url_ext.rs b/payjoin/src/uri/url_ext.rs index 73d191ef..824be1a2 100644 --- a/payjoin/src/uri/url_ext.rs +++ b/payjoin/src/uri/url_ext.rs @@ -4,11 +4,13 @@ use bitcoin::base64::prelude::BASE64_URL_SAFE_NO_PAD; use bitcoin::base64::Engine; use url::Url; +use super::error::ParseReceiverPubkeyError; use crate::hpke::HpkePublicKey; use crate::OhttpKeys; /// Parse and set fragment parameters from `&pj=` URI parameter URLs pub(crate) trait UrlExt { + fn receiver_pubkey(&self) -> Result; fn set_receiver_pubkey(&mut self, exp: Option); fn ohttp(&self) -> Option; fn set_ohttp(&mut self, ohttp: Option); @@ -17,6 +19,19 @@ pub(crate) trait UrlExt { } impl UrlExt for Url { + /// Retrieve the receiver's public key from the URL fragment + fn receiver_pubkey(&self) -> Result { + let value = get_param(self, "rk=", |v| Some(v.to_owned())) + .ok_or(ParseReceiverPubkeyError::MissingPubkey)?; + + let decoded = BASE64_URL_SAFE_NO_PAD + .decode(&value) + .map_err(ParseReceiverPubkeyError::PubkeyNotBase64)?; + + HpkePublicKey::from_compressed_bytes(&decoded) + .map_err(ParseReceiverPubkeyError::InvalidPubkey) + } + /// Set the receiver's public key in the URL fragment fn set_receiver_pubkey(&mut self, pubkey: Option) { set_param(