Skip to content

Commit 20737a5

Browse files
committed
Introduce Payjoin version enum
1 parent 73fafa8 commit 20737a5

File tree

9 files changed

+81
-20
lines changed

9 files changed

+81
-20
lines changed

payjoin/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,3 +70,7 @@ pub use uri::{PjParseError, PjUri, Uri, UriExt};
7070
pub use url::{ParseError, Url};
7171
#[cfg(feature = "_core")]
7272
pub(crate) mod error_codes;
73+
#[cfg(feature = "_core")]
74+
pub(crate) mod version;
75+
#[cfg(feature = "_core")]
76+
pub use version::Version;

payjoin/src/receive/mod.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ use optional_parameters::Params;
2121

2222
pub use crate::psbt::PsbtInputError;
2323
use crate::psbt::{InternalInputPair, InternalPsbtInputError, PsbtExt};
24+
use crate::Version;
2425

2526
mod error;
2627
pub(crate) mod optional_parameters;
@@ -73,7 +74,7 @@ impl<'a> From<&'a InputPair> for InternalInputPair<'a> {
7374
pub(crate) fn parse_payload(
7475
base64: String,
7576
query: &str,
76-
supported_versions: &'static [usize],
77+
supported_versions: &'static [Version],
7778
) -> Result<(Psbt, Params), PayloadError> {
7879
let unchecked_psbt = Psbt::from_str(&base64).map_err(InternalPayloadError::ParsePsbt)?;
7980

payjoin/src/receive/multiparty/error.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use core::fmt;
22
use std::error;
33

44
use crate::uri::ShortId;
5+
use crate::Version;
56

67
#[derive(Debug)]
78
pub struct MultipartyError(InternalMultipartyError);
@@ -13,7 +14,7 @@ pub(crate) enum InternalMultipartyError {
1314
/// Duplicate proposals
1415
IdenticalProposals(IdenticalProposalError),
1516
/// Proposal version not supported
16-
ProposalVersionNotSupported(usize),
17+
ProposalVersionNotSupported(Version),
1718
/// Optimistic merge not supported
1819
OptimisticMergeNotSupported,
1920
/// Bitcoin Internal Error

payjoin/src/receive/multiparty/mod.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,11 @@ use super::{v1, v2, Error, ImplementationError, InputPair};
66
use crate::psbt::merge::merge_unsigned_tx;
77
use crate::receive::multiparty::error::{InternalMultipartyError, MultipartyError};
88
use crate::receive::v2::SessionContext;
9+
use crate::Version;
910

1011
pub(crate) mod error;
1112

12-
const SUPPORTED_VERSIONS: &[usize] = &[2];
13+
const SUPPORTED_VERSIONS: &[Version] = &[Version::Two];
1314

1415
#[derive(Default)]
1516
pub struct UncheckedProposalBuilder {

payjoin/src/receive/optional_parameters.rs

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,12 @@ use bitcoin::FeeRate;
55
use log::warn;
66

77
use crate::output_substitution::OutputSubstitution;
8+
use crate::Version;
89

910
#[derive(Debug, Clone)]
1011
pub(crate) struct Params {
1112
// version
12-
pub v: usize,
13+
pub v: Version,
1314
// disableoutputsubstitution
1415
pub output_substitution: OutputSubstitution,
1516
// maxadditionalfeecontribution, additionalfeeoutputindex
@@ -24,7 +25,7 @@ pub(crate) struct Params {
2425
impl Default for Params {
2526
fn default() -> Self {
2627
Params {
27-
v: 1,
28+
v: Version::One,
2829
output_substitution: OutputSubstitution::Enabled,
2930
additional_fee_contribution: None,
3031
min_fee_rate: FeeRate::BROADCAST_MIN,
@@ -37,7 +38,7 @@ impl Default for Params {
3738
impl Params {
3839
pub fn from_query_pairs<K, V, I>(
3940
pairs: I,
40-
supported_versions: &'static [usize],
41+
supported_versions: &'static [Version],
4142
) -> Result<Self, Error>
4243
where
4344
I: Iterator<Item = (K, V)>,
@@ -52,8 +53,9 @@ impl Params {
5253
for (key, v) in pairs {
5354
match (key.borrow(), v.borrow()) {
5455
("v", version) =>
55-
params.v = match version.parse::<usize>() {
56-
Ok(version) if supported_versions.contains(&version) => version,
56+
params.v = match version {
57+
"1" => Version::One,
58+
"2" => Version::Two,
5759
_ => return Err(Error::UnknownVersion { supported_versions }),
5860
},
5961
("additionalfeeoutputindex", index) =>
@@ -111,9 +113,9 @@ impl Params {
111113
}
112114
}
113115

114-
#[derive(Debug)]
116+
#[derive(Debug, PartialEq, Eq)]
115117
pub(crate) enum Error {
116-
UnknownVersion { supported_versions: &'static [usize] },
118+
UnknownVersion { supported_versions: &'static [Version] },
117119
FeeRate,
118120
}
119121

@@ -135,19 +137,30 @@ pub(crate) mod test {
135137
use bitcoin::Amount;
136138

137139
use super::*;
140+
use crate::receive::optional_parameters::Params;
141+
use crate::Version;
138142

139143
#[test]
140144
fn test_parse_params() {
141145
use bitcoin::FeeRate;
142146

143147
let pairs = url::form_urlencoded::parse(b"maxadditionalfeecontribution=182&additionalfeeoutputindex=0&minfeerate=1&disableoutputsubstitution=true&optimisticmerge=true");
144-
let params =
145-
Params::from_query_pairs(pairs, &[1]).expect("Could not parse params from query pairs");
146-
assert_eq!(params.v, 1);
148+
let params = Params::from_query_pairs(pairs, &[Version::One])
149+
.expect("Could not parse params from query pairs");
150+
assert_eq!(params.v, Version::One);
147151
assert_eq!(params.output_substitution, OutputSubstitution::Disabled);
148152
assert_eq!(params.additional_fee_contribution, Some((Amount::from_sat(182), 0)));
149153
assert_eq!(params.min_fee_rate, FeeRate::BROADCAST_MIN);
150154
#[cfg(feature = "_multiparty")]
151155
assert!(params.optimistic_merge)
152156
}
157+
158+
#[test]
159+
fn from_query_pairs_unsupported_versions() {
160+
let invalid_pair: Vec<(&str, &str)> = vec![("v", "888")];
161+
let supported_versions = &[Version::One, Version::Two];
162+
let params = Params::from_query_pairs(invalid_pair.into_iter(), supported_versions);
163+
assert!(params.is_err());
164+
assert_eq!(params.err().unwrap(), Error::UnknownVersion { supported_versions });
165+
}
153166
}

payjoin/src/receive/v1/exclusive/mod.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@ pub use error::RequestError;
44

55
use super::*;
66
use crate::into_url::IntoUrl;
7+
use crate::Version;
78

89
/// 4_000_000 * 4 / 3 fits in u32
910
const MAX_CONTENT_LENGTH: usize = 4_000_000 * 4 / 3;
10-
const SUPPORTED_VERSIONS: &[usize] = &[1];
11+
const SUPPORTED_VERSIONS: &[Version] = &[Version::One];
1112

1213
pub trait Headers {
1314
fn get_header(&self, key: &str) -> Option<&str>;
@@ -131,7 +132,7 @@ mod tests {
131132
Address::from_script(&witness_utxo.script_pubkey, bitcoin::params::Params::MAINNET)?;
132133
assert_eq!(address.address_type(), Some(AddressType::P2sh));
133134

134-
assert_eq!(proposal.params.v, 1);
135+
assert_eq!(proposal.params.v, Version::One);
135136
assert_eq!(proposal.params.additional_fee_contribution, Some((Amount::from_sat(182), 0)));
136137
Ok(())
137138
}

payjoin/src/receive/v1/mod.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -783,11 +783,12 @@ pub(crate) mod test {
783783

784784
use super::*;
785785
use crate::receive::PayloadError;
786+
use crate::Version;
786787

787788
pub(crate) fn unchecked_proposal_from_test_vector() -> UncheckedProposal {
788789
let pairs = url::form_urlencoded::parse(QUERY_PARAMS.as_bytes());
789-
let params =
790-
Params::from_query_pairs(pairs, &[1]).expect("Could not parse params from query pairs");
790+
let params = Params::from_query_pairs(pairs, &[Version::One])
791+
.expect("Could not parse params from query pairs");
791792
UncheckedProposal { psbt: PARSED_ORIGINAL_PSBT.clone(), params }
792793
}
793794

payjoin/src/receive/v2/mod.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,12 @@ use crate::output_substitution::OutputSubstitution;
2323
use crate::persist::Persister;
2424
use crate::receive::{parse_payload, InputPair};
2525
use crate::uri::ShortId;
26-
use crate::{IntoUrl, IntoUrlError, Request};
26+
use crate::{IntoUrl, IntoUrlError, Request, Version};
2727

2828
mod error;
2929
mod persist;
3030

31-
const SUPPORTED_VERSIONS: &[usize] = &[1, 2];
31+
const SUPPORTED_VERSIONS: &[Version] = &[Version::One, Version::Two];
3232

3333
static TWENTY_FOUR_HOURS_DEFAULT_EXPIRY: Duration = Duration::from_secs(60 * 60 * 24);
3434

@@ -220,7 +220,7 @@ impl Receiver {
220220
// V2 proposals are authenticated and encrypted to prevent such attacks.
221221
//
222222
// see: https://github.com/bitcoin/bips/blob/master/bip-0078.mediawiki#unsecured-payjoin-server
223-
if params.v == 1 {
223+
if params.v == Version::One {
224224
params.output_substitution = OutputSubstitution::Disabled;
225225

226226
// Additionally V1 sessions never have an optimistic merge opportunity

payjoin/src/version.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
use core::fmt;
2+
3+
use serde::{Serialize, Serializer};
4+
5+
/// The Payjoin version
6+
#[repr(u8)]
7+
#[derive(Clone, Copy, PartialEq, Eq)]
8+
pub enum Version {
9+
/// BIP 78 Payjoin
10+
One = 1,
11+
/// BIP 77 Async Payjoin
12+
Two = 2,
13+
}
14+
15+
/// From [BIP 78](https://github.com/bitcoin/bips/blob/master/bip-0078.mediawiki#optional-parameters),
16+
/// the supported versions should be output as numbers:
17+
/// ```json
18+
/// {
19+
/// "errorCode": "version-unsupported",
20+
/// "supported" : [ 2, 3, 4 ],
21+
/// "message": "The version is not supported anymore"
22+
/// }
23+
/// ```
24+
impl fmt::Display for Version {
25+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { (*self as u8).fmt(f) }
26+
}
27+
28+
impl fmt::Debug for Version {
29+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Display::fmt(self, f) }
30+
}
31+
32+
impl Serialize for Version {
33+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
34+
where
35+
S: Serializer,
36+
{
37+
(*self as u8).serialize(serializer)
38+
}
39+
}

0 commit comments

Comments
 (0)