Skip to content

Commit

Permalink
De/Serialize v2 payload
Browse files Browse the repository at this point in the history
  • Loading branch information
DanGould committed Dec 11, 2023
1 parent 933fd5b commit a8c4d2b
Show file tree
Hide file tree
Showing 6 changed files with 385 additions and 317 deletions.
1 change: 0 additions & 1 deletion payjoin-relay/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,3 @@ sqlx = { version = "0.7.1", features = ["postgres", "runtime-tokio"] }
tokio = { version = "1.12.0", features = ["full"] }
tracing = "0.1.37"
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }

1 change: 1 addition & 0 deletions payjoin/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ exclude = ["tests"]
send = []
receive = ["rand"]
base64 = ["bitcoin/base64"]
v2 = []

[dependencies]
bitcoin = { version = "0.30.0", features = ["base64"] }
Expand Down
9 changes: 9 additions & 0 deletions payjoin/src/receive/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@ pub(crate) enum InternalRequestError {
/// Original PSBT input has been seen before. Only automatic receivers, aka "interactive" in the spec
/// look out for these to prevent probing attacks.
InputSeen(bitcoin::OutPoint),
/// Serde deserialization failed
#[cfg(feature = "v2")]
ParsePsbt(bitcoin::psbt::PsbtParseError),
#[cfg(feature = "v2")]
Utf8(std::string::FromUtf8Error),
}

impl From<InternalRequestError> for RequestError {
Expand Down Expand Up @@ -125,6 +130,10 @@ impl fmt::Display for RequestError {
write_error(f, "original-psbt-rejected", &format!("Input Type Error: {}.", e)),
InternalRequestError::InputSeen(_) =>
write_error(f, "original-psbt-rejected", "The receiver rejected the original PSBT."),
#[cfg(feature = "v2")]
InternalRequestError::ParsePsbt(e) => write_error(f, "Error parsing PSBT:", e),
#[cfg(feature = "v2")]
InternalRequestError::Utf8(e) => write_error(f, "Error parsing PSBT:", e),
}
}
}
Expand Down
23 changes: 11 additions & 12 deletions payjoin/src/receive/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -303,23 +303,22 @@ pub struct UncheckedProposal {
}

impl UncheckedProposal {
#[cfg(feature = "v2")]
pub fn from_relay_response(mut body: impl std::io::Read) -> Result<Self, RequestError> {
use std::str::FromStr;

let mut buf = Vec::new();
let _ = body.read_to_end(&mut buf);
let base64 = bitcoin::base64::decode(buf).map_err(InternalRequestError::Base64)?;
let unchecked_psbt = Psbt::deserialize(&base64).map_err(InternalRequestError::Psbt)?;

let buf_as_string = String::from_utf8(buf.to_vec()).map_err(InternalRequestError::Utf8)?;
log::debug!("{}", &buf_as_string);
let (query, base64) = buf_as_string.split_once('\n').unwrap_or_default();
let unchecked_psbt = Psbt::from_str(base64).map_err(InternalRequestError::ParsePsbt)?;
let psbt = unchecked_psbt.validate().map_err(InternalRequestError::InconsistentPsbt)?;
log::debug!("Received original psbt: {:?}", psbt);

// TODO accept parameters
// let pairs = url::form_urlencoded::parse(query.as_bytes());
// let params = Params::from_query_pairs(pairs).map_err(InternalRequestError::SenderParams)?;
// log::debug!("Received request with params: {:?}", params);

// TODO handle v1 and v2

Ok(UncheckedProposal { psbt, params: Params::default() })
let params = Params::from_query_pairs(url::form_urlencoded::parse(query.as_bytes()))
.map_err(InternalRequestError::SenderParams)?;
log::debug!("Received request with params: {:?}", params);
Ok(Self { psbt, params })
}

pub fn from_request(
Expand Down
62 changes: 48 additions & 14 deletions payjoin/src/send/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -326,16 +326,34 @@ impl<'a> RequestBuilder<'a> {
let sequence = zeroth_input.txin.sequence;
let txout = zeroth_input.previous_txout().expect("We already checked this above");
let input_type = InputType::from_spent_input(txout, zeroth_input.psbtin).unwrap();
let url = serialize_url(
self.uri.extras._endpoint.into(),
disable_output_substitution,
fee_contribution,
self.min_fee_rate,
)
.map_err(InternalCreateRequestError::Url)?;
let body = serialize_psbt(&psbt);

#[cfg(not(feature = "v2"))]
let request = {
let url = serialize_url(
self.uri.extras._endpoint.into(),
disable_output_substitution,
fee_contribution,
self.min_fee_rate,
)
.map_err(InternalCreateRequestError::Url)?;
let body = psbt.to_string().as_bytes().to_vec();
Request { url, body }
};

#[cfg(feature = "v2")]
let request = {
let url = self.uri.extras._endpoint;
let body = serialize_v2_body(
&psbt,
disable_output_substitution,
fee_contribution,
self.min_fee_rate,
)?;
Request { url, body }
};

Ok((
Request { url, body },
request,
Context {
original_psbt: psbt,
disable_output_substitution,
Expand Down Expand Up @@ -372,6 +390,7 @@ pub struct Request {
///
/// This type is used to process the response. Get it from [`RequestBuilder`](crate::send::RequestBuilder)'s build methods.
/// Then you only need to call [`.process_response()`](crate::send::Context::process_response()) on it to continue BIP78 flow.
#[derive(Debug)]
pub struct Context {
original_psbt: Psbt,
disable_output_substitution: bool,
Expand Down Expand Up @@ -769,6 +788,26 @@ fn determine_fee_contribution(
})
}

#[cfg(feature = "v2")]
fn serialize_v2_body(
psbt: &Psbt,
disable_output_substitution: bool,
fee_contribution: Option<(bitcoin::Amount, usize)>,
min_feerate: FeeRate,
) -> Result<Vec<u8>, CreateRequestError> {
// Grug say localhost base be discarded anyway. no big brain needed.
let placeholder_url = serialize_url(
"http:/localhost".to_string(),
disable_output_substitution,
fee_contribution,
min_feerate,
)
.map_err(InternalCreateRequestError::Url)?;
let query_params = placeholder_url.query().unwrap_or_default();
let body = psbt.to_string();
Ok(format!("{}\n{}", query_params, body).into_bytes())
}

fn serialize_url(
endpoint: String,
disable_output_substitution: bool,
Expand All @@ -793,11 +832,6 @@ fn serialize_url(
Ok(url)
}

fn serialize_psbt(psbt: &Psbt) -> Vec<u8> {
let bytes = psbt.serialize();
bitcoin::base64::encode(bytes).into_bytes()
}

#[cfg(test)]
mod tests {
#[test]
Expand Down
Loading

0 comments on commit a8c4d2b

Please sign in to comment.