Skip to content

Commit

Permalink
Extract public key from fragment of pj URI
Browse files Browse the repository at this point in the history
  • Loading branch information
nothingmuch committed Nov 17, 2024
1 parent db2b2e8 commit 3db8512
Show file tree
Hide file tree
Showing 4 changed files with 64 additions and 56 deletions.
50 changes: 9 additions & 41 deletions payjoin/src/send/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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")]
Expand Down Expand Up @@ -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")]
Expand Down Expand Up @@ -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")]
Expand All @@ -278,44 +281,9 @@ impl From<crate::psbt::AddressTypeError> for CreateRequestError {
}

#[cfg(feature = "v2")]
impl From<ParseSubdirectoryError> 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<ParseReceiverPubkeyError> for CreateRequestError {
fn from(value: ParseReceiverPubkeyError) -> Self {
CreateRequestError(InternalCreateRequestError::ParseReceiverPubkey(value))
}
}

Expand Down
20 changes: 5 additions & 15 deletions payjoin/src/send/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -322,21 +322,11 @@ impl Sender {
}

#[cfg(feature = "v2")]
fn extract_rs_pubkey(&self) -> Result<HpkePublicKey, error::ParseSubdirectoryError> {
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<HpkePublicKey, crate::uri::error::ParseReceiverPubkeyError> {
use crate::uri::UrlExt;
self.endpoint.receiver_pubkey()
}

pub fn endpoint(&self) -> &Url { &self.endpoint }
Expand Down
35 changes: 35 additions & 0 deletions payjoin/src/uri/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<InternalPjParseError> for PjParseError {
fn from(value: InternalPjParseError) -> Self { PjParseError(value) }
}
Expand Down
15 changes: 15 additions & 0 deletions payjoin/src/uri/url_ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<HpkePublicKey, ParseReceiverPubkeyError>;
fn set_receiver_pubkey(&mut self, exp: Option<HpkePublicKey>);
fn ohttp(&self) -> Option<OhttpKeys>;
fn set_ohttp(&mut self, ohttp: Option<OhttpKeys>);
Expand All @@ -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<HpkePublicKey, ParseReceiverPubkeyError> {
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<HpkePublicKey>) {
set_param(
Expand Down

0 comments on commit 3db8512

Please sign in to comment.