Skip to content

Commit

Permalink
Serialize hpke pubkeys as compressed
Browse files Browse the repository at this point in the history
This reduces the size of the subdirectory pj= string.
  • Loading branch information
DanGould committed Sep 8, 2024
1 parent 827b719 commit e1070a8
Show file tree
Hide file tree
Showing 4 changed files with 24 additions and 11 deletions.
11 changes: 4 additions & 7 deletions payjoin/src/receive/v2/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ use bitcoin::base64::prelude::BASE64_URL_SAFE_NO_PAD;
use bitcoin::base64::Engine;
use bitcoin::psbt::Psbt;
use bitcoin::{Address, Amount, FeeRate, OutPoint, Script, TxOut};
use hpke::Serializable;
use serde::de::Deserializer;
use serde::{Deserialize, Serialize};
use url::Url;
Expand Down Expand Up @@ -127,10 +126,8 @@ impl SessionInitializer {
}
}

fn subdir_path_from_pubkey(
pubkey: &<hpke::kem::SecpK256HkdfSha256 as hpke::Kem>::PublicKey,
) -> String {
BASE64_URL_SAFE_NO_PAD.encode(pubkey.to_bytes())
fn subdir_path_from_pubkey(pubkey: &HpkePublicKey) -> String {
BASE64_URL_SAFE_NO_PAD.encode(pubkey.to_compressed_bytes())
}

/// An active payjoin V2 session, allowing for polled requests to the
Expand Down Expand Up @@ -241,7 +238,7 @@ impl ActiveSession {
// The contents of the `&pj=` query parameter including the base64url-encoded public key receiver subdirectory.
// This identifies a session at the payjoin directory server.
pub fn pj_url(&self) -> Url {
let pubkey = &self.context.s.1.to_bytes();
let pubkey = &self.id();
let pubkey_base64 = BASE64_URL_SAFE_NO_PAD.encode(pubkey);
let mut url = self.context.directory.clone();
{
Expand All @@ -253,7 +250,7 @@ impl ActiveSession {
}

/// The per-session public key to use as an identifier
pub fn id(&self) -> Vec<u8> { self.context.s.1.to_bytes().to_vec() }
pub fn id(&self) -> [u8; 33] { self.context.s.1.to_compressed_bytes() }
}

/// The sender's original PSBT and optional parameters
Expand Down
2 changes: 1 addition & 1 deletion payjoin/src/send/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,7 @@ impl From<ParseSubdirectoryError> for CreateRequestError {
pub(crate) enum ParseSubdirectoryError {
MissingSubdirectory,
SubdirectoryNotBase64(bitcoin::base64::DecodeError),
SubdirectoryInvalidPubkey(hpke::HpkeError),
SubdirectoryInvalidPubkey(crate::v2::HpkeError),
}

#[cfg(feature = "v2")]
Expand Down
2 changes: 1 addition & 1 deletion payjoin/src/send/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -377,7 +377,7 @@ impl RequestContext {
.decode(subdirectory)
.map_err(ParseSubdirectoryError::SubdirectoryNotBase64)?;

HpkePublicKey::from_bytes(&pubkey_bytes)
HpkePublicKey::from_compressed_bytes(&pubkey_bytes)
.map_err(ParseSubdirectoryError::SubdirectoryInvalidPubkey)
}

Expand Down
20 changes: 18 additions & 2 deletions payjoin/src/v2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,17 @@ impl<'de> serde::Deserialize<'de> for HpkeSecretKey {
pub struct HpkePublicKey(pub PublicKey);

impl HpkePublicKey {
pub fn from_bytes(bytes: &[u8]) -> Result<Self, hpke::HpkeError> {
Ok(HpkePublicKey(PublicKey::from_bytes(bytes)?))
pub fn to_compressed_bytes(&self) -> [u8; 33] {
let compressed_key = bitcoin::secp256k1::PublicKey::from_slice(&self.0.to_bytes())
.expect("Invalid public key from known valid bytes");
compressed_key.serialize()
}

pub fn from_compressed_bytes(bytes: &[u8]) -> Result<Self, HpkeError> {
let compressed_key = bitcoin::secp256k1::PublicKey::from_slice(bytes)?;
Ok(HpkePublicKey(PublicKey::from_bytes(
compressed_key.serialize_uncompressed().as_slice(),
)?))
}
}

Expand Down Expand Up @@ -199,6 +208,7 @@ fn pad_plaintext(msg: &mut Vec<u8>) -> Result<&[u8], HpkeError> {
/// Error from de/encrypting a v2 Hybrid Public Key Encryption payload.
#[derive(Debug)]
pub enum HpkeError {
Secp256k1(bitcoin::secp256k1::Error),
Hpke(hpke::HpkeError),
InvalidKeyLength,
PayloadTooLarge,
Expand All @@ -209,6 +219,10 @@ impl From<hpke::HpkeError> for HpkeError {
fn from(value: hpke::HpkeError) -> Self { Self::Hpke(value) }
}

impl From<bitcoin::secp256k1::Error> for HpkeError {
fn from(value: bitcoin::secp256k1::Error) -> Self { Self::Secp256k1(value) }
}

impl fmt::Display for HpkeError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use HpkeError::*;
Expand All @@ -219,6 +233,7 @@ impl fmt::Display for HpkeError {
PayloadTooLarge =>
write!(f, "Plaintext too large, max size is {} bytes", PADDED_PLAINTEXT_LENGTH),
PayloadTooShort => write!(f, "Payload too small"),
Secp256k1(e) => e.fmt(f),
}
}
}
Expand All @@ -230,6 +245,7 @@ impl error::Error for HpkeError {
match &self {
Hpke(e) => Some(e),
InvalidKeyLength | PayloadTooLarge | PayloadTooShort => None,
Secp256k1(e) => Some(e),
}
}
}
Expand Down

0 comments on commit e1070a8

Please sign in to comment.