Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion Cargo-minimal.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2648,7 +2648,6 @@ dependencies = [
name = "payjoin-fuzz"
version = "0.0.1"
dependencies = [
"bitcoin_uri",
"home",
"libfuzzer-sys",
"payjoin",
Expand Down
1 change: 0 additions & 1 deletion Cargo-recent.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2779,7 +2779,6 @@ dependencies = [
name = "payjoin-fuzz"
version = "0.0.1"
dependencies = [
"bitcoin_uri",
"home",
"libfuzzer-sys",
"payjoin",
Expand Down
1 change: 0 additions & 1 deletion fuzz/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ cargo-fuzz = true
default = []

[dependencies]
bitcoin_uri = { version = "0.1.0" }
home = "=0.5.11"
libfuzzer-sys = { version = "0.4.10" }
payjoin = { path = "../payjoin", default-features = false, features = [
Expand Down
23 changes: 11 additions & 12 deletions fuzz/fuzz_targets/uri/deserialize_pjuri.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,39 +2,38 @@

use std::any::{Any, TypeId};

use bitcoin_uri::Param;
use libfuzzer_sys::fuzz_target;
use payjoin::{Uri, UriExt};
use payjoin::Uri;

fn do_test(data: &[u8]) {
if let Ok(uri_str) = std::str::from_utf8(data) {
let pj_uri = match Uri::try_from(uri_str.to_string()) {
Ok(uri) => uri.assume_checked(),
Err(_) => return,
};
let address = pj_uri.address.is_spend_standard();
if !address {
if !pj_uri.address().is_spend_standard() {
return;
}

if let Some(label) = pj_uri.clone().label {
if TypeId::of::<Param>() != label.type_id() {
if let Some(label) = pj_uri.label() {
if TypeId::of::<String>() != label.type_id() {
return;
}
};
if let Some(message) = pj_uri.clone().message {
if TypeId::of::<Param>() != message.type_id() {
if let Some(message) = pj_uri.message() {
if TypeId::of::<String>() != message.type_id() {
return;
}
};
let extras = match pj_uri.clone().check_pj_supported() {
Ok(res) => res.extras,
let extras = match pj_uri.check_pj_supported() {
Ok(res) => res,
Err(_) => return,
};
assert!(
TypeId::of::<payjoin::OutputSubstitution>() == extras.output_substitution().type_id()
TypeId::of::<payjoin::OutputSubstitution>()
== extras.extras().output_substitution().type_id()
);
assert!(TypeId::of::<String>() == extras.endpoint().type_id())
assert!(TypeId::of::<String>() == extras.extras().endpoint().type_id())
}
}

Expand Down
8 changes: 4 additions & 4 deletions payjoin-cli/src/app/v1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use payjoin::bitcoin::{Amount, FeeRate};
use payjoin::receive::v1::{PayjoinProposal, UncheckedOriginalPayload};
use payjoin::receive::Error;
use payjoin::send::v1::SenderBuilder;
use payjoin::{ImplementationError, IntoUrl, Uri, UriExt};
use payjoin::{ImplementationError, IntoUrl, Uri};
use tokio::net::TcpListener;
use tokio::sync::watch;

Expand Down Expand Up @@ -61,8 +61,8 @@ impl AppTrait for App {
Uri::try_from(bip21).map_err(|e| anyhow!("Failed to create URI from BIP21: {}", e))?;
let uri = uri.assume_checked();
let uri = uri.check_pj_supported().map_err(|_| anyhow!("URI does not support Payjoin"))?;
let amount = uri.amount.ok_or_else(|| anyhow!("please specify the amount in the Uri"))?;
let psbt = self.create_original_psbt(&uri.address, amount, fee_rate)?;
let amount = uri.amount().ok_or_else(|| anyhow!("please specify the amount in the Uri"))?;
let psbt = self.create_original_psbt(uri.address(), amount, fee_rate)?;
let fallback_tx = psbt.clone().extract_tx()?;
let (req, ctx) = SenderBuilder::new(psbt, uri.clone())
.build_recommended(fee_rate)
Expand Down Expand Up @@ -148,7 +148,7 @@ impl App {
endpoint,
payjoin::OutputSubstitution::Enabled,
)?;
pj_uri.amount = Some(amount);
pj_uri.set_amount(amount);

Ok(pj_uri.to_string())
}
Expand Down
7 changes: 3 additions & 4 deletions payjoin-cli/src/app/v2/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -210,15 +210,14 @@ impl AppTrait for App {
fn wallet(&self) -> BitcoindWallet { self.wallet.clone() }

async fn send_payjoin(&self, bip21: &str, fee_rate: FeeRate) -> Result<()> {
use payjoin::UriExt;
let uri = Uri::try_from(bip21)
.map_err(|e| anyhow!("Failed to create URI from BIP21: {}", e))?
.assume_checked()
.check_pj_supported()
.map_err(|_| anyhow!("URI does not support Payjoin"))?;
let address = uri.address;
let amount = uri.amount.ok_or_else(|| anyhow!("please specify the amount in the Uri"))?;
match uri.extras.pj_param() {
let address = uri.address().clone();
let amount = uri.amount().ok_or_else(|| anyhow!("please specify the amount in the Uri"))?;
match uri.extras().pj_param() {
#[cfg(feature = "v1")]
PjParam::V1(pj_param) => {
let psbt = self.create_original_psbt(&address, amount, fee_rate)?;
Expand Down
14 changes: 4 additions & 10 deletions payjoin-ffi/src/uri/error.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,7 @@
#[derive(Debug, PartialEq, Eq, thiserror::Error, uniffi::Object)]
#[uniffi::export(Debug, Display, Eq)]
#[error("Error parsing the payjoin URI: {msg}")]
pub struct PjParseError {
msg: String,
}

impl PjParseError {
pub(crate) fn from_err(err: impl std::fmt::Display) -> Self { Self { msg: err.to_string() } }
}
#[derive(Debug, thiserror::Error, uniffi::Object)]
#[uniffi::export(Debug, Display)]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any rationale for dropping Eq from this list?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, added the rationale to the commit message: Eq cannot be derived anymore because the Bip21 variant of payjoin::UriParseError holds a bitcoin_uri::de::UriError, which doesn't implement Eq.

#[error(transparent)]
pub struct UriParseError(#[from] payjoin::UriParseError);

#[derive(Debug, PartialEq, Eq, thiserror::Error, uniffi::Object)]
#[uniffi::export(Debug, Display, Eq)]
Expand Down
46 changes: 20 additions & 26 deletions payjoin-ffi/src/uri/mod.rs
Original file line number Diff line number Diff line change
@@ -1,41 +1,35 @@
use std::str::FromStr;
use std::sync::Arc;

pub use error::{PjNotSupported, PjParseError, UrlParseError};
pub use error::{PjNotSupported, UriParseError, UrlParseError};
use payjoin::bitcoin::address::NetworkChecked;
use payjoin::UriExt;

use crate::error::FfiValidationError;
use crate::validation::validate_amount_sat;

pub mod error;
#[derive(Clone, uniffi::Object)]
pub struct Uri(payjoin::Uri<'static, NetworkChecked>);
impl From<Uri> for payjoin::Uri<'static, NetworkChecked> {
pub struct Uri(payjoin::Uri<NetworkChecked>);
impl From<Uri> for payjoin::Uri<NetworkChecked> {
fn from(value: Uri) -> Self { value.0 }
}

impl From<payjoin::Uri<'static, NetworkChecked>> for Uri {
fn from(value: payjoin::Uri<'static, NetworkChecked>) -> Self { Uri(value) }
impl From<payjoin::Uri<NetworkChecked>> for Uri {
fn from(value: payjoin::Uri<NetworkChecked>) -> Self { Uri(value) }
}

#[uniffi::export]
impl Uri {
#[uniffi::constructor]
pub fn parse(uri: String) -> Result<Self, PjParseError> {
payjoin::Uri::from_str(uri.as_str())
.map(|e| e.assume_checked().into())
.map_err(PjParseError::from_err)
pub fn parse(uri: String) -> Result<Self, UriParseError> {
let uri = payjoin::Uri::from_str(uri.as_str())?;
Ok(uri.assume_checked().into())
}
pub fn address(&self) -> String { self.clone().0.address.to_string() }
pub fn address(&self) -> String { self.0.address().to_string() }
/// Gets the amount in satoshis.
pub fn amount_sats(&self) -> Option<u64> { self.0.amount.map(|x| x.to_sat()) }
pub fn label(&self) -> Option<String> {
self.0.label.clone().and_then(|x| String::try_from(x).ok())
}
pub fn message(&self) -> Option<String> {
self.0.message.clone().and_then(|x| String::try_from(x).ok())
}
pub fn amount_sats(&self) -> Option<u64> { self.0.amount().map(|x| x.to_sat()) }
pub fn label(&self) -> Option<String> { self.0.label() }
pub fn message(&self) -> Option<String> { self.0.message() }

pub fn check_pj_supported(&self) -> Result<Arc<PjUri>, PjNotSupported> {
self.0
Expand All @@ -47,32 +41,32 @@ impl Uri {
pub fn as_string(&self) -> String { self.0.clone().to_string() }
}

impl From<payjoin::PjUri<'static>> for PjUri {
fn from(value: payjoin::PjUri<'static>) -> Self { Self(value) }
impl From<payjoin::PjUri> for PjUri {
fn from(value: payjoin::PjUri) -> Self { Self(value) }
}

impl From<PjUri> for payjoin::PjUri<'_> {
impl From<PjUri> for payjoin::PjUri {
fn from(value: PjUri) -> Self { value.0 }
}

#[derive(Clone, uniffi::Object)]
pub struct PjUri(pub payjoin::PjUri<'static>);
pub struct PjUri(pub payjoin::PjUri);

#[uniffi::export]
impl PjUri {
pub fn address(&self) -> String { self.0.clone().address.to_string() }
pub fn address(&self) -> String { self.0.address().to_string() }
/// Number of sats requested as payment
pub fn amount_sats(&self) -> Option<u64> { self.0.clone().amount.map(|e| e.to_sat()) }
pub fn amount_sats(&self) -> Option<u64> { self.0.amount().map(|e| e.to_sat()) }

/// Sets the amount in sats and returns a new PjUri
pub fn set_amount_sats(&self, amount_sats: u64) -> Result<Self, FfiValidationError> {
let mut uri = self.0.clone();
let amount = validate_amount_sat(amount_sats)?;
uri.amount = Some(amount);
uri.set_amount(amount);
Ok(uri.into())
}

pub fn pj_endpoint(&self) -> String { self.0.extras.endpoint().to_string() }
pub fn pj_endpoint(&self) -> String { self.0.extras().endpoint().to_string() }

pub fn as_string(&self) -> String { self.0.clone().to_string() }
}
Expand Down
1 change: 1 addition & 0 deletions payjoin/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ _core = [
"serde_json",
"dep:percent-encoding-rfc3986",
"bitcoin_uri",
"bitcoin_uri/std",
"serde",
"bitcoin/serde",
]
Expand Down
2 changes: 1 addition & 1 deletion payjoin/src/core/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ pub use url::{ParseError as UrlParseError, Url};
#[cfg(feature = "v2")]
pub mod time;
pub mod uri;
pub use uri::{PjParam, PjParseError, PjUri, Uri, UriExt};
pub use uri::{PjParam, PjParseError, PjUri, Uri, UriParseError};
pub(crate) mod error_codes;

pub(crate) mod output_substitution;
Expand Down
6 changes: 3 additions & 3 deletions payjoin/src/core/receive/v1/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,14 @@ pub trait Headers {
fn get_header(&self, key: &str) -> Option<&str>;
}

pub fn build_v1_pj_uri<'a>(
pub fn build_v1_pj_uri(
address: &bitcoin::Address,
endpoint: impl IntoUrl,
output_substitution: OutputSubstitution,
) -> Result<crate::uri::PjUri<'a>, PjParseError> {
) -> Result<crate::uri::PjUri, PjParseError> {
let pj_param = PjParam::parse(endpoint)?;
let extras = crate::uri::PayjoinExtras { pj_param, output_substitution };
Ok(bitcoin_uri::Uri::with_extras(address.clone(), extras))
Ok(crate::uri::PjUri::from_extras(address.clone(), extras))
}

impl UncheckedOriginalPayload {
Expand Down
16 changes: 9 additions & 7 deletions payjoin/src/core/receive/v2/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -719,7 +719,7 @@ impl Receiver<Initialized> {
}

/// Build a V2 Payjoin URI from the receiver's context
pub fn pj_uri<'a>(&self) -> crate::PjUri<'a> {
pub fn pj_uri(&self) -> crate::PjUri {
pj_uri(&self.session_context, OutputSubstitution::Disabled)
}

Expand Down Expand Up @@ -1629,10 +1629,10 @@ fn mailbox_endpoint(directory: &Url, id: &ShortId) -> Url {
}

/// Gets the Payjoin URI from a session context
pub(crate) fn pj_uri<'a>(
pub(crate) fn pj_uri(
session_context: &SessionContext,
output_substitution: OutputSubstitution,
) -> crate::PjUri<'a> {
) -> crate::PjUri {
use crate::uri::PayjoinExtras;
let pj_param = crate::uri::PjParam::V2(crate::uri::v2::PjParam::new(
session_context.directory.clone(),
Expand All @@ -1642,8 +1642,10 @@ pub(crate) fn pj_uri<'a>(
session_context.receiver_key.public_key().clone(),
));
let extras = PayjoinExtras { pj_param, output_substitution };
let mut uri = bitcoin_uri::Uri::with_extras(session_context.address.clone(), extras);
uri.amount = session_context.amount;
let mut uri = crate::uri::PjUri::from_extras(session_context.address.clone(), extras);
if let Some(amount) = session_context.amount {
uri.set_amount(amount);
}

uri
}
Expand Down Expand Up @@ -2504,8 +2506,8 @@ pub mod test {
fn test_v2_pj_uri() {
let uri =
Receiver { state: Initialized {}, session_context: SHARED_CONTEXT.clone() }.pj_uri();
assert_ne!(uri.extras.pj_param.endpoint().as_str(), EXAMPLE_URL);
assert_eq!(uri.extras.output_substitution, OutputSubstitution::Disabled);
assert_ne!(uri.extras().pj_param().endpoint().as_str(), EXAMPLE_URL);
assert_eq!(uri.extras().output_substitution(), OutputSubstitution::Disabled);
}

#[test]
Expand Down
6 changes: 3 additions & 3 deletions payjoin/src/core/receive/v2/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ impl SessionHistory {
}

/// Receiver session Payjoin URI
pub fn pj_uri<'a>(&self) -> PjUri<'a> {
pub fn pj_uri(&self) -> PjUri {
self.events
.iter()
.find_map(|event| match event {
Expand Down Expand Up @@ -1207,8 +1207,8 @@ mod tests {

let uri = SessionHistory { events }.pj_uri();

assert_ne!(uri.extras.pj_param.endpoint().as_str(), EXAMPLE_URL);
assert_eq!(uri.extras.output_substitution, OutputSubstitution::Disabled);
assert_ne!(uri.extras().pj_param().endpoint().as_str(), EXAMPLE_URL);
assert_eq!(uri.extras().output_substitution(), OutputSubstitution::Disabled);

Ok(())
}
Expand Down
Loading
Loading