Skip to content

Commit 97c5adf

Browse files
committed
Impl traits for session persistence
1 parent b065521 commit 97c5adf

File tree

3 files changed

+140
-2
lines changed

3 files changed

+140
-2
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

payjoin/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ exclude = ["tests"]
1818
send = []
1919
receive = ["rand"]
2020
base64 = ["bitcoin/base64"]
21-
v2 = ["bitcoin/rand-std", "chacha20poly1305", "ohttp", "bhttp"]
21+
v2 = ["bitcoin/rand-std", "chacha20poly1305", "ohttp", "bhttp", "serde"]
2222

2323
[dependencies]
2424
bitcoin = { version = "0.30.0", features = ["base64"] }
@@ -28,6 +28,7 @@ log = { version = "0.4.14"}
2828
ohttp = { version = "0.4.0", optional = true }
2929
bhttp = { version = "0.4.0", optional = true }
3030
rand = { version = "0.8.4", optional = true }
31+
serde = { version = "1.0.186", default-features = false, optional = true }
3132
url = "2.2.2"
3233

3334
[dev-dependencies]

payjoin/src/receive/v2.rs

Lines changed: 137 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ use std::collections::HashMap;
22

33
use bitcoin::psbt::Psbt;
44
use bitcoin::{base64, Amount, FeeRate, OutPoint, Script, TxOut};
5+
use serde::ser::SerializeStruct;
6+
use serde::{Deserialize, Serialize, Serializer};
57

68
use super::{Error, InternalRequestError, RequestError, SelectionError};
79
use crate::psbt::PsbtExt;
@@ -105,14 +107,146 @@ fn subdirectory(pubkey: &bitcoin::secp256k1::PublicKey) -> String {
105107
base64::encode_config(pubkey, b64_config)
106108
}
107109

108-
#[derive(Debug)]
110+
#[derive(Debug, Clone, PartialEq)]
109111
pub struct Enrolled {
110112
relay_url: url::Url,
111113
ohttp_config: Vec<u8>,
112114
ohttp_proxy: url::Url,
113115
s: bitcoin::secp256k1::KeyPair,
114116
}
115117

118+
impl Serialize for Enrolled {
119+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
120+
where
121+
S: Serializer,
122+
{
123+
let mut state = serializer.serialize_struct("Enrolled", 4)?;
124+
state.serialize_field("relay_url", &self.relay_url.to_string())?;
125+
state.serialize_field("ohttp_config", &self.ohttp_config)?;
126+
state.serialize_field("ohttp_proxy", &self.ohttp_proxy.to_string())?;
127+
state.serialize_field("s", &self.s.secret_key().secret_bytes())?;
128+
129+
state.end()
130+
}
131+
}
132+
133+
use std::fmt;
134+
use std::str::FromStr;
135+
136+
use serde::de::{self, Deserializer, MapAccess, Visitor};
137+
138+
impl<'de> Deserialize<'de> for Enrolled {
139+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
140+
where
141+
D: Deserializer<'de>,
142+
{
143+
enum Field {
144+
RelayUrl,
145+
OhttpConfig,
146+
OhttpProxy,
147+
S,
148+
}
149+
150+
impl<'de> Deserialize<'de> for Field {
151+
fn deserialize<D>(deserializer: D) -> Result<Field, D::Error>
152+
where
153+
D: Deserializer<'de>,
154+
{
155+
struct FieldVisitor;
156+
157+
impl<'de> Visitor<'de> for FieldVisitor {
158+
type Value = Field;
159+
160+
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
161+
formatter.write_str("`relay_url`, `ohttp_config`, `ohttp_proxy`, or `s`")
162+
}
163+
164+
fn visit_str<E>(self, value: &str) -> Result<Field, E>
165+
where
166+
E: de::Error,
167+
{
168+
match value {
169+
"relay_url" => Ok(Field::RelayUrl),
170+
"ohttp_config" => Ok(Field::OhttpConfig),
171+
"ohttp_proxy" => Ok(Field::OhttpProxy),
172+
"s" => Ok(Field::S),
173+
_ => Err(de::Error::unknown_field(value, FIELDS)),
174+
}
175+
}
176+
}
177+
178+
deserializer.deserialize_identifier(FieldVisitor)
179+
}
180+
}
181+
182+
struct EnrolledVisitor;
183+
184+
impl<'de> Visitor<'de> for EnrolledVisitor {
185+
type Value = Enrolled;
186+
187+
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
188+
formatter.write_str("struct Enrolled")
189+
}
190+
191+
fn visit_map<V>(self, mut map: V) -> Result<Enrolled, V::Error>
192+
where
193+
V: MapAccess<'de>,
194+
{
195+
let mut relay_url = None;
196+
let mut ohttp_config = None;
197+
let mut ohttp_proxy = None;
198+
let mut s = None;
199+
while let Some(key) = map.next_key()? {
200+
match key {
201+
Field::RelayUrl => {
202+
if relay_url.is_some() {
203+
return Err(de::Error::duplicate_field("relay_url"));
204+
}
205+
let url_str: String = map.next_value()?;
206+
relay_url = Some(url::Url::parse(&url_str).map_err(de::Error::custom)?);
207+
}
208+
Field::OhttpConfig => {
209+
if ohttp_config.is_some() {
210+
return Err(de::Error::duplicate_field("ohttp_config"));
211+
}
212+
ohttp_config = Some(map.next_value()?);
213+
}
214+
Field::OhttpProxy => {
215+
if ohttp_proxy.is_some() {
216+
return Err(de::Error::duplicate_field("ohttp_proxy"));
217+
}
218+
let proxy_str: String = map.next_value()?;
219+
ohttp_proxy =
220+
Some(url::Url::parse(&proxy_str).map_err(de::Error::custom)?);
221+
}
222+
Field::S => {
223+
if s.is_some() {
224+
return Err(de::Error::duplicate_field("s"));
225+
}
226+
let s_bytes: Vec<u8> = map.next_value()?;
227+
let secp = bitcoin::secp256k1::Secp256k1::new();
228+
s = Some(
229+
bitcoin::secp256k1::KeyPair::from_seckey_slice(&secp, &s_bytes)
230+
.map_err(de::Error::custom)?,
231+
);
232+
}
233+
}
234+
}
235+
let relay_url = relay_url.ok_or_else(|| de::Error::missing_field("relay_url"))?;
236+
let ohttp_config =
237+
ohttp_config.ok_or_else(|| de::Error::missing_field("ohttp_config"))?;
238+
let ohttp_proxy =
239+
ohttp_proxy.ok_or_else(|| de::Error::missing_field("ohttp_proxy"))?;
240+
let s = s.ok_or_else(|| de::Error::missing_field("s"))?;
241+
Ok(Enrolled { relay_url, ohttp_config, ohttp_proxy, s })
242+
}
243+
}
244+
245+
const FIELDS: &[& str] = &["relay_url", "ohttp_config", "ohttp_proxy", "s"];
246+
deserializer.deserialize_struct("Enrolled", FIELDS, EnrolledVisitor)
247+
}
248+
}
249+
116250
impl Enrolled {
117251
pub fn extract_req(&self) -> Result<(Request, ohttp::ClientResponse), Error> {
118252
let (body, ohttp_ctx) = self.fallback_req_body()?;
@@ -172,6 +306,8 @@ impl Enrolled {
172306
crate::v2::ohttp_encapsulate(&self.ohttp_config, "GET", &self.fallback_target(), None)
173307
}
174308

309+
pub fn pubkey(&self) -> [u8; 33] { self.s.public_key().serialize() }
310+
175311
pub fn fallback_target(&self) -> String {
176312
let pubkey = &self.s.public_key().serialize();
177313
let b64_config = base64::Config::new(base64::CharacterSet::UrlSafe, false);

0 commit comments

Comments
 (0)