Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
UR encode HPKE pubkey, OhttpKeys
Browse files Browse the repository at this point in the history
ur::bytewords encoding creates smaller QR codes
DanGould committed Mar 27, 2024
1 parent 7168b2f commit ac7538d
Showing 9 changed files with 152 additions and 54 deletions.
107 changes: 107 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions payjoin-directory/Cargo.toml
Original file line number Diff line number Diff line change
@@ -29,3 +29,4 @@ rustls = { version = "0.21", optional = true }
tokio = { version = "1.12.0", features = ["full"] }
tracing = "0.1.37"
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
ur = "0.4.1"
18 changes: 7 additions & 11 deletions payjoin-directory/src/lib.rs
Original file line number Diff line number Diff line change
@@ -3,7 +3,6 @@ use std::sync::Arc;
use std::time::Duration;

use anyhow::Result;
use bitcoin::{self, base64};
use hyper::header::{HeaderValue, ACCESS_CONTROL_ALLOW_ORIGIN, CONTENT_TYPE};
use hyper::server::conn::AddrIncoming;
use hyper::server::Builder;
@@ -101,12 +100,9 @@ fn init_ohttp() -> Result<ohttp::Server> {

// create or read from file
let server_config = ohttp::KeyConfig::new(KEY_ID, KEM, Vec::from(SYMMETRIC))?;
let encoded_config = server_config.encode()?;
let b64_config = base64::encode_config(
encoded_config,
base64::Config::new(base64::CharacterSet::UrlSafe, false),
);
info!("ohttp-keys server config base64 UrlSafe: {:?}", b64_config);
let encoded_config =
ur::bytewords::encode(&server_config.encode()?, ur::bytewords::Style::Minimal);
info!("ohttp-keys server config encoded: {:?}", encoded_config);
Ok(ohttp::Server::new(server_config)?)
}

@@ -242,13 +238,13 @@ impl From<hyper::http::Error> for HandlerError {
}

async fn post_enroll(body: Body) -> Result<Response<Body>, HandlerError> {
let b64_config = base64::Config::new(base64::CharacterSet::UrlSafe, false);
let bytes =
hyper::body::to_bytes(body).await.map_err(|e| HandlerError::BadRequest(e.into()))?;
let base64_id =
let encoded_pubkey =
String::from_utf8(bytes.to_vec()).map_err(|e| HandlerError::BadRequest(e.into()))?;
let pubkey_bytes: Vec<u8> = base64::decode_config(base64_id, b64_config)
.map_err(|e| HandlerError::BadRequest(e.into()))?;
let pubkey_bytes: Vec<u8> =
ur::bytewords::decode(&encoded_pubkey, ur::bytewords::Style::Minimal)
.map_err(|e| HandlerError::BadRequest(e.into()))?;
let pubkey = bitcoin::secp256k1::PublicKey::from_slice(&pubkey_bytes)
.map_err(|e| HandlerError::BadRequest(e.into()))?;
tracing::info!("Enrolled valid pubkey: {:?}", pubkey);
3 changes: 2 additions & 1 deletion payjoin/Cargo.toml
Original file line number Diff line number Diff line change
@@ -18,7 +18,7 @@ exclude = ["tests"]
send = []
receive = ["rand"]
base64 = ["bitcoin/base64"]
v2 = ["bitcoin/rand-std", "bitcoin/serde", "chacha20poly1305", "ohttp", "bhttp", "serde"]
v2 = ["bitcoin/rand-std", "bitcoin/serde", "chacha20poly1305", "ohttp", "bhttp", "ur", "serde"]

[dependencies]
bitcoin = { version = "0.30.0", features = ["base64"] }
@@ -28,6 +28,7 @@ log = { version = "0.4.14"}
ohttp = { version = "0.5.1", optional = true }
bhttp = { version = "0.5.1", optional = true }
rand = { version = "0.8.4", optional = true }
ur = { version = "0.4.1", optional = true }
serde = { version = "1.0.186", default-features = false, optional = true }
url = "2.2.2"
serde_json = "1.0.108"
12 changes: 4 additions & 8 deletions payjoin/src/receive/v2.rs
Original file line number Diff line number Diff line change
@@ -58,8 +58,7 @@ impl Enroller {

pub fn subdirectory(&self) -> String {
let pubkey = &self.s.public_key().serialize();
let b64_config = base64::Config::new(base64::CharacterSet::UrlSafe, false);
base64::encode_config(pubkey, b64_config)
ur::bytewords::encode(pubkey, ur::bytewords::Style::Minimal)
}

pub fn payjoin_subdir(&self) -> String { format!("{}/{}", self.subdirectory(), "payjoin") }
@@ -98,9 +97,7 @@ impl Enroller {
}

fn subdirectory(pubkey: &bitcoin::secp256k1::PublicKey) -> String {
let pubkey = pubkey.serialize();
let b64_config = base64::Config::new(base64::CharacterSet::UrlSafe, false);
base64::encode_config(pubkey, b64_config)
ur::bytewords::encode(&pubkey.serialize(), ur::bytewords::Style::Minimal)
}

#[derive(Debug, Clone, PartialEq, Eq)]
@@ -272,9 +269,8 @@ impl Enrolled {

pub fn fallback_target(&self) -> String {
let pubkey = &self.s.public_key().serialize();
let b64_config = base64::Config::new(base64::CharacterSet::UrlSafe, false);
let pubkey_base64 = base64::encode_config(pubkey, b64_config);
format!("{}{}", &self.directory, pubkey_base64)
let subdirectory = ur::bytewords::encode(pubkey, ur::bytewords::Style::Minimal);
format!("{}{}", &self.directory, subdirectory)
}
}

6 changes: 3 additions & 3 deletions payjoin/src/send/error.rs
Original file line number Diff line number Diff line change
@@ -179,7 +179,7 @@ pub(crate) enum InternalCreateRequestError {
#[cfg(feature = "v2")]
V2(crate::v2::Error),
#[cfg(feature = "v2")]
SubdirectoryNotBase64(bitcoin::base64::DecodeError),
PubkeyEncoding,
#[cfg(feature = "v2")]
SubdirectoryInvalidPubkey(bitcoin::secp256k1::Error),
#[cfg(feature = "v2")]
@@ -209,7 +209,7 @@ impl fmt::Display for CreateRequestError {
#[cfg(feature = "v2")]
V2(e) => write!(f, "v2 error: {}", e),
#[cfg(feature = "v2")]
SubdirectoryNotBase64(e) => write!(f, "subdirectory is not valid base64 error: {}", e),
PubkeyEncoding => write!(f, "Bad public key encoding"),
#[cfg(feature = "v2")]
SubdirectoryInvalidPubkey(e) => write!(f, "subdirectory does not represent a valid pubkey: {}", e),
#[cfg(feature = "v2")]
@@ -241,7 +241,7 @@ impl std::error::Error for CreateRequestError {
#[cfg(feature = "v2")]
V2(error) => Some(error),
#[cfg(feature = "v2")]
SubdirectoryNotBase64(error) => Some(error),
PubkeyEncoding => None,
#[cfg(feature = "v2")]
SubdirectoryInvalidPubkey(error) => Some(error),
#[cfg(feature = "v2")]
21 changes: 11 additions & 10 deletions payjoin/src/send/mod.rs
Original file line number Diff line number Diff line change
@@ -325,10 +325,8 @@ impl RequestContext {
) -> Result<(Request, ContextV2), CreateRequestError> {
let rs_base64 = crate::v2::subdir(self.endpoint.as_str()).to_string();
log::debug!("rs_base64: {:?}", rs_base64);
let b64_config =
bitcoin::base64::Config::new(bitcoin::base64::CharacterSet::UrlSafe, false);
let rs = bitcoin::base64::decode_config(rs_base64, b64_config)
.map_err(InternalCreateRequestError::SubdirectoryNotBase64)?;
let rs = ur::bytewords::decode(&rs_base64, ur::bytewords::Style::Minimal)
.map_err(|_| InternalCreateRequestError::PubkeyEncoding)?;
log::debug!("rs: {:?}", rs.len());
let rs = bitcoin::secp256k1::PublicKey::from_slice(&rs)
.map_err(InternalCreateRequestError::SubdirectoryInvalidPubkey)?;
@@ -383,7 +381,7 @@ impl Serialize for RequestContext {
config
.encode()
.map_err(|e| serde::ser::Error::custom(format!("ohttp-keys encoding error: {}", e)))
.map(bitcoin::base64::encode)
.map(|bytes| ur::bytewords::encode(&bytes, ur::bytewords::Style::Minimal))
})?;
state.serialize_field("ohttp_keys", &ohttp_string)?;
state.serialize_field("disable_output_substitution", &self.disable_output_substitution)?;
@@ -455,15 +453,18 @@ impl<'de> Deserialize<'de> for RequestContext {
.map_err(de::Error::custom)?,
),
"ohttp_keys" => {
let ohttp_base64: String = map.next_value()?;
ohttp_keys = if ohttp_base64.is_empty() {
let ohttp_encoded: String = map.next_value()?;
ohttp_keys = if ohttp_encoded.is_empty() {
None
} else {
Some(
crate::v2::OhttpKeys::decode(
bitcoin::base64::decode(&ohttp_base64)
.map_err(de::Error::custom)?
.as_slice(),
ur::bytewords::decode(
&ohttp_encoded,
ur::bytewords::Style::Minimal,
)
.map_err(de::Error::custom)?
.as_slice(),
)
.map_err(de::Error::custom)?,
)
26 changes: 13 additions & 13 deletions payjoin/src/uri.rs
Original file line number Diff line number Diff line change
@@ -239,10 +239,9 @@ impl<'a> bip21::SerializeParams for &'a PayjoinExtras {
];
#[cfg(feature = "v2")]
if let Some(ohttp_keys) = self.ohttp_keys.clone().and_then(|c| c.encode().ok()) {
let config =
bitcoin::base64::Config::new(bitcoin::base64::CharacterSet::UrlSafe, false);
let base64_ohttp_keys = bitcoin::base64::encode_config(ohttp_keys, config);
params.push(("ohttp", base64_ohttp_keys));
let encoded_ohttp_keys =
ur::bytewords::encode(&ohttp_keys, ur::bytewords::Style::Minimal);
params.push(("ohttp", encoded_ohttp_keys));
} else {
log::warn!("Failed to encode ohttp config, ignoring");
}
@@ -266,13 +265,13 @@ impl<'a> bip21::de::DeserializationState<'a> for DeserializationState {
match key {
#[cfg(feature = "v2")]
"ohttp" if self.ohttp.is_none() => {
let base64_config = Cow::try_from(value).map_err(InternalPjParseError::NotUtf8)?;
let config_bytes =
bitcoin::base64::decode_config(&*base64_config, bitcoin::base64::URL_SAFE)
.map_err(InternalPjParseError::NotBase64)?;
let config =
OhttpKeys::decode(&config_bytes).map_err(InternalPjParseError::BadOhttp)?;
self.ohttp = Some(config);
let ohttp_encoded = Cow::try_from(value).map_err(InternalPjParseError::NotUtf8)?;
let ohttp_bytes =
ur::bytewords::decode(&ohttp_encoded, ur::bytewords::Style::Minimal)
.map_err(|_| InternalPjParseError::BadOhttpEncoding)?;
let ohttp_keys =
OhttpKeys::decode(&ohttp_bytes).map_err(InternalPjParseError::BadOhttp)?;
self.ohttp = Some(ohttp_keys);
Ok(bip21::de::ParamKind::Known)
}
#[cfg(feature = "v2")]
@@ -333,7 +332,8 @@ impl std::fmt::Display for PjParseError {
InternalPjParseError::MissingEndpoint => write!(f, "Missing payjoin endpoint"),
InternalPjParseError::NotUtf8(_) => write!(f, "Endpoint is not valid UTF-8"),
#[cfg(feature = "v2")]
InternalPjParseError::NotBase64(_) => write!(f, "ohttp config is not valid base64"),
InternalPjParseError::BadOhttpEncoding =>
write!(f, "ohttp parameter is improperly encoded"),
InternalPjParseError::BadEndpoint(_) => write!(f, "Endpoint is not valid"),
#[cfg(feature = "v2")]
InternalPjParseError::BadOhttp(_) => write!(f, "ohttp config is not valid"),
@@ -351,7 +351,7 @@ enum InternalPjParseError {
MissingEndpoint,
NotUtf8(core::str::Utf8Error),
#[cfg(feature = "v2")]
NotBase64(bitcoin::base64::DecodeError),
BadOhttpEncoding,
BadEndpoint(url::ParseError),
#[cfg(feature = "v2")]
BadOhttp(crate::v2::Error),
12 changes: 4 additions & 8 deletions payjoin/src/v2.rs
Original file line number Diff line number Diff line change
@@ -263,10 +263,8 @@ impl<'de> serde::Deserialize<'de> for OhttpKeys {
where
D: serde::Deserializer<'de>,
{
use bitcoin::base64;

let base64_string = String::deserialize(deserializer)?;
let bytes = base64::decode_config(base64_string, base64::URL_SAFE)
let encoded = String::deserialize(deserializer)?;
let bytes = ur::bytewords::decode(&encoded, ur::bytewords::Style::Minimal)
.map_err(serde::de::Error::custom)?;
Ok(OhttpKeys(ohttp::KeyConfig::decode(&bytes).map_err(serde::de::Error::custom)?))
}
@@ -277,11 +275,9 @@ impl serde::Serialize for OhttpKeys {
where
S: serde::Serializer,
{
use bitcoin::base64;

let bytes = self.0.encode().map_err(serde::ser::Error::custom)?;
let base64_string = base64::encode_config(bytes, base64::URL_SAFE);
base64_string.serialize(serializer)
let encoded = ur::bytewords::encode(&bytes, ur::bytewords::Style::Minimal);
encoded.serialize(serializer)
}
}

0 comments on commit ac7538d

Please sign in to comment.