Skip to content

Commit 1c83012

Browse files
committed
Introduce Payjoin version enum
1 parent 518d8d2 commit 1c83012

File tree

10 files changed

+91
-22
lines changed

10 files changed

+91
-22
lines changed

payjoin/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ exclude = ["tests"]
1818
[features]
1919
default = ["v2"]
2020
#[doc = "Core features for payjoin state machines"]
21-
_core = ["bitcoin/rand-std", "serde_json", "url", "bitcoin_uri"]
21+
_core = ["bitcoin/rand-std", "serde_json", "url", "bitcoin_uri", "serde"]
2222
directory = []
2323
v1 = ["_core"]
2424
v2 = ["_core", "bitcoin/serde", "hpke", "dep:http", "bhttp", "ohttp", "serde", "url/serde", "directory"]

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: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
use core::fmt;
22
use std::error;
33

4+
use crate::Version;
5+
46
#[derive(Debug)]
57
pub struct MultipartyError(InternalMultipartyError);
68

@@ -9,7 +11,7 @@ pub(crate) enum InternalMultipartyError {
911
/// Not enough proposals
1012
NotEnoughProposals,
1113
/// Proposal version not supported
12-
ProposalVersionNotSupported(usize),
14+
ProposalVersionNotSupported(Version),
1315
/// Optimistic merge not supported
1416
OptimisticMergeNotSupported,
1517
/// Bitcoin Internal Error

payjoin/src/receive/multiparty/mod.rs

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

910
pub(crate) mod error;
1011

11-
const SUPPORTED_VERSIONS: &[usize] = &[2];
12+
const SUPPORTED_VERSIONS: &[Version] = &[Version::Two];
1213

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

payjoin/src/receive/optional_parameters.rs

Lines changed: 24 additions & 11 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

@@ -135,23 +137,34 @@ impl std::error::Error for Error {
135137
}
136138

137139
#[cfg(test)]
138-
pub(crate) mod test {
140+
mod test {
139141
use bitcoin::Amount;
140142

141143
use super::*;
144+
use crate::receive::optional_parameters::Params;
145+
use crate::Version;
142146

143147
#[test]
144148
fn test_parse_params() {
145149
use bitcoin::FeeRate;
146150

147151
let pairs = url::form_urlencoded::parse(b"maxadditionalfeecontribution=182&additionalfeeoutputindex=0&minfeerate=1&disableoutputsubstitution=true&optimisticmerge=true");
148-
let params =
149-
Params::from_query_pairs(pairs, &[1]).expect("Could not parse params from query pairs");
150-
assert_eq!(params.v, 1);
152+
let params = Params::from_query_pairs(pairs, &[Version::One])
153+
.expect("Could not parse params from query pairs");
154+
assert_eq!(params.v, Version::One);
151155
assert_eq!(params.output_substitution, OutputSubstitution::Disabled);
152156
assert_eq!(params.additional_fee_contribution, Some((Amount::from_sat(182), 0)));
153157
assert_eq!(params.min_fee_rate, FeeRate::BROADCAST_MIN);
154158
#[cfg(feature = "_multiparty")]
155159
assert!(params.optimistic_merge)
156160
}
161+
162+
#[test]
163+
fn from_query_pairs_unsupported_versions() {
164+
let invalid_pair: Vec<(&str, &str)> = vec![("v", "888")];
165+
let supported_versions = &[Version::One, Version::Two];
166+
let params = Params::from_query_pairs(invalid_pair.into_iter(), supported_versions);
167+
assert!(params.is_err());
168+
assert_eq!(params.err().unwrap(), Error::UnknownVersion { supported_versions });
169+
}
157170
}

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
mod error;
2+
23
pub(crate) use error::InternalRequestError;
34
pub use error::RequestError;
45

56
use super::*;
67
use crate::into_url::IntoUrl;
8+
use crate::Version;
79

810
/// 4_000_000 * 4 / 3 fits in u32
911
const MAX_CONTENT_LENGTH: usize = 4_000_000 * 4 / 3;
10-
const SUPPORTED_VERSIONS: &[usize] = &[1];
12+
const SUPPORTED_VERSIONS: &[Version] = &[Version::One];
1113

1214
pub trait Headers {
1315
fn get_header(&self, key: &str) -> Option<&str>;
@@ -131,7 +133,7 @@ mod tests {
131133
Address::from_script(&witness_utxo.script_pubkey, bitcoin::params::Params::MAINNET)?;
132134
assert_eq!(address.address_type(), Some(AddressType::P2sh));
133135

134-
assert_eq!(proposal.params.v, 1);
136+
assert_eq!(proposal.params.v, Version::One);
135137
assert_eq!(proposal.params.additional_fee_contribution, Some((Amount::from_sat(182), 0)));
136138
Ok(())
137139
}

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: 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)