Skip to content

Commit d78e51f

Browse files
committed
Introduce Payjoin version enum
1 parent 4357f3b commit d78e51f

File tree

6 files changed

+93
-13
lines changed

6 files changed

+93
-13
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/optional_parameters.rs

Lines changed: 36 additions & 7 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) =>
@@ -115,9 +117,9 @@ impl Params {
115117
}
116118
}
117119

118-
#[derive(Debug)]
120+
#[derive(Debug, PartialEq, Eq)]
119121
pub(crate) enum Error {
120-
UnknownVersion { supported_versions: &'static [usize] },
122+
UnknownVersion { supported_versions: &'static [Version] },
121123
FeeRate,
122124
}
123125

@@ -133,3 +135,30 @@ impl fmt::Display for Error {
133135
impl std::error::Error for Error {
134136
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None }
135137
}
138+
139+
#[cfg(test)]
140+
mod test {
141+
use super::*;
142+
use crate::receive::optional_parameters::Params;
143+
use crate::Version;
144+
145+
#[test]
146+
fn from_query_pairs_matches_correct_versions() {
147+
let v1_pair: Vec<(&str, &str)> = vec![("v", "1")];
148+
let params = Params::from_query_pairs(v1_pair.into_iter(), &[Version::One]).unwrap();
149+
assert_eq!(params.v, Version::One);
150+
151+
let v2_pair: Vec<(&str, &str)> = vec![("v", "2")];
152+
let params = Params::from_query_pairs(v2_pair.into_iter(), &[Version::Two]).unwrap();
153+
assert_eq!(params.v, Version::Two);
154+
}
155+
156+
#[test]
157+
fn from_query_pairs_unsupported_versions() {
158+
let v1_pair: Vec<(&str, &str)> = vec![("v", "888")];
159+
let supported_versions = &[Version::One, Version::Two];
160+
let params = Params::from_query_pairs(v1_pair.into_iter(), supported_versions);
161+
assert!(params.is_err());
162+
assert_eq!(params.err().unwrap(), Error::UnknownVersion { supported_versions });
163+
}
164+
}

payjoin/src/receive/v1/mod.rs

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

782782
use super::*;
783783
use crate::receive::PayloadError;
784+
use crate::Version;
784785

785786
pub(crate) fn unchecked_proposal_from_test_vector() -> UncheckedProposal {
786787
let pairs = url::form_urlencoded::parse(QUERY_PARAMS.as_bytes());
787-
let params =
788-
Params::from_query_pairs(pairs, &[1]).expect("Could not parse params from query pairs");
788+
let params = Params::from_query_pairs(pairs, &[Version::One])
789+
.expect("Could not parse params from query pairs");
789790
UncheckedProposal { psbt: PARSED_ORIGINAL_PSBT.clone(), params }
790791
}
791792

payjoin/src/receive/v2/mod.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,11 @@ use crate::output_substitution::OutputSubstitution;
2323
use crate::persist::{self, 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
pub(crate) mod error;
2929

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

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

@@ -238,7 +238,7 @@ impl Receiver {
238238
// V2 proposals are authenticated and encrypted to prevent such attacks.
239239
//
240240
// see: https://github.com/bitcoin/bips/blob/master/bip-0078.mediawiki#unsecured-payjoin-server
241-
if params.v == 1 {
241+
if params.v == Version::One {
242242
params.output_substitution = OutputSubstitution::Disabled;
243243

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

payjoin/src/version.rs

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

0 commit comments

Comments
 (0)