From e1070a8bb521f5dc490c055916c3a8a645e5ec16 Mon Sep 17 00:00:00 2001 From: DanGould Date: Sat, 7 Sep 2024 11:05:57 -0400 Subject: [PATCH] Serialize hpke pubkeys as compressed This reduces the size of the subdirectory pj= string. --- payjoin/src/receive/v2/mod.rs | 11 ++++------- payjoin/src/send/error.rs | 2 +- payjoin/src/send/mod.rs | 2 +- payjoin/src/v2.rs | 20 ++++++++++++++++++-- 4 files changed, 24 insertions(+), 11 deletions(-) diff --git a/payjoin/src/receive/v2/mod.rs b/payjoin/src/receive/v2/mod.rs index 47dd79f8..242ee111 100644 --- a/payjoin/src/receive/v2/mod.rs +++ b/payjoin/src/receive/v2/mod.rs @@ -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; @@ -127,10 +126,8 @@ impl SessionInitializer { } } -fn subdir_path_from_pubkey( - pubkey: &::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 @@ -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(); { @@ -253,7 +250,7 @@ impl ActiveSession { } /// The per-session public key to use as an identifier - pub fn id(&self) -> Vec { 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 diff --git a/payjoin/src/send/error.rs b/payjoin/src/send/error.rs index dfff8005..0c955199 100644 --- a/payjoin/src/send/error.rs +++ b/payjoin/src/send/error.rs @@ -282,7 +282,7 @@ impl From for CreateRequestError { pub(crate) enum ParseSubdirectoryError { MissingSubdirectory, SubdirectoryNotBase64(bitcoin::base64::DecodeError), - SubdirectoryInvalidPubkey(hpke::HpkeError), + SubdirectoryInvalidPubkey(crate::v2::HpkeError), } #[cfg(feature = "v2")] diff --git a/payjoin/src/send/mod.rs b/payjoin/src/send/mod.rs index 62f4169c..74d37611 100644 --- a/payjoin/src/send/mod.rs +++ b/payjoin/src/send/mod.rs @@ -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) } diff --git a/payjoin/src/v2.rs b/payjoin/src/v2.rs index 773affd2..ebba5737 100644 --- a/payjoin/src/v2.rs +++ b/payjoin/src/v2.rs @@ -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 { - 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 { + let compressed_key = bitcoin::secp256k1::PublicKey::from_slice(bytes)?; + Ok(HpkePublicKey(PublicKey::from_bytes( + compressed_key.serialize_uncompressed().as_slice(), + )?)) } } @@ -199,6 +208,7 @@ fn pad_plaintext(msg: &mut Vec) -> 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, @@ -209,6 +219,10 @@ impl From for HpkeError { fn from(value: hpke::HpkeError) -> Self { Self::Hpke(value) } } +impl From 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::*; @@ -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), } } } @@ -230,6 +245,7 @@ impl error::Error for HpkeError { match &self { Hpke(e) => Some(e), InvalidKeyLength | PayloadTooLarge | PayloadTooShort => None, + Secp256k1(e) => Some(e), } } }