Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 31 additions & 14 deletions payjoin/src/hpke.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use std::ops::Deref;
use std::{error, fmt};

use bitcoin::key::constants::{ELLSWIFT_ENCODING_SIZE, UNCOMPRESSED_PUBLIC_KEY_SIZE};
use bitcoin::key::constants::{ELLSWIFT_ENCODING_SIZE, PUBLIC_KEY_SIZE};
use bitcoin::secp256k1;
use bitcoin::secp256k1::ellswift::ElligatorSwift;
use hpke::aead::ChaCha20Poly1305;
use hpke::kdf::HkdfSha256;
Expand All @@ -11,8 +12,8 @@ use hpke::{Deserializable, OpModeR, OpModeS, Serializable};
use serde::{Deserialize, Serialize};

pub const PADDED_MESSAGE_BYTES: usize = 7168;
pub const PADDED_PLAINTEXT_A_LENGTH: usize = PADDED_MESSAGE_BYTES
- (ELLSWIFT_ENCODING_SIZE + UNCOMPRESSED_PUBLIC_KEY_SIZE + POLY1305_TAG_SIZE);
pub const PADDED_PLAINTEXT_A_LENGTH: usize =
PADDED_MESSAGE_BYTES - (ELLSWIFT_ENCODING_SIZE + PUBLIC_KEY_SIZE + POLY1305_TAG_SIZE);
pub const PADDED_PLAINTEXT_B_LENGTH: usize =
PADDED_MESSAGE_BYTES - (ELLSWIFT_ENCODING_SIZE + POLY1305_TAG_SIZE);
pub const POLY1305_TAG_SIZE: usize = 16; // FIXME there is a U16 defined for poly1305, should bitcoin hpke re-export it?
Expand Down Expand Up @@ -44,19 +45,36 @@ impl HpkeKeyPair {
pub fn public_key(&self) -> &HpkePublicKey { &self.1 }
}

fn pubkey_from_compressed_bytes(pk_bytes: &[u8]) -> Result<HpkePublicKey, HpkeError> {
let uncompressed_pk_bytes =
secp256k1::PublicKey::from_slice(pk_bytes)?.serialize_uncompressed();

Ok(HpkePublicKey(
PublicKey::from_bytes(&uncompressed_pk_bytes)
.expect("conversion to uncompressed pubkey must not fail"),
))
}

fn compressed_bytes_from_pubkey(pk: &HpkePublicKey) -> [u8; PUBLIC_KEY_SIZE] {
let reply_pk_uncompressed = pk.to_bytes();
secp256k1::PublicKey::from_slice(&reply_pk_uncompressed[..])
.expect("parsing a pubkey immediately after serializing it must not fail")
.serialize()
}

fn encapped_key_from_ellswift_bytes(encoded: &[u8]) -> Result<EncappedKey, HpkeError> {
let mut buf = [0u8; ELLSWIFT_ENCODING_SIZE];
buf.copy_from_slice(encoded);
let ellswift = ElligatorSwift::from_array(buf);
let pk = bitcoin::secp256k1::PublicKey::from_ellswift(ellswift);
let pk = secp256k1::PublicKey::from_ellswift(ellswift);
Ok(EncappedKey::from_bytes(pk.serialize_uncompressed().as_slice())?)
}

fn ellswift_bytes_from_encapped_key(
enc: &EncappedKey,
) -> Result<[u8; ELLSWIFT_ENCODING_SIZE], HpkeError> {
let uncompressed = enc.to_bytes();
let pk = bitcoin::secp256k1::PublicKey::from_slice(&uncompressed)?;
let pk = secp256k1::PublicKey::from_slice(&uncompressed)?;
let ellswift = ElligatorSwift::from_pubkey(pk);
Ok(ellswift.to_array())
}
Expand Down Expand Up @@ -103,13 +121,13 @@ pub struct HpkePublicKey(pub PublicKey);

impl HpkePublicKey {
pub fn to_compressed_bytes(&self) -> [u8; 33] {
let compressed_key = bitcoin::secp256k1::PublicKey::from_slice(&self.0.to_bytes())
let compressed_key = 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)?;
let compressed_key = secp256k1::PublicKey::from_slice(bytes)?;
Ok(HpkePublicKey(PublicKey::from_bytes(
compressed_key.serialize_uncompressed().as_slice(),
)?))
Expand Down Expand Up @@ -166,7 +184,7 @@ pub fn encrypt_message_a(
)?;
let mut body = body;
pad_plaintext(&mut body, PADDED_PLAINTEXT_A_LENGTH)?;
let mut plaintext = reply_pk.to_bytes().to_vec();
let mut plaintext = compressed_bytes_from_pubkey(reply_pk).to_vec();
plaintext.extend(body);
let ciphertext = encryption_context.seal(&plaintext, &[])?;
let mut message_a = ellswift_bytes_from_encapped_key(&encapsulated_key)?.to_vec();
Expand Down Expand Up @@ -197,10 +215,9 @@ pub fn decrypt_message_a(
cursor.read_to_end(&mut ciphertext).map_err(|_| HpkeError::PayloadTooShort)?;
let plaintext = decryption_ctx.open(&ciphertext, &[])?;

let reply_pk_bytes = &plaintext[..UNCOMPRESSED_PUBLIC_KEY_SIZE];
let reply_pk = HpkePublicKey(PublicKey::from_bytes(reply_pk_bytes)?);
let reply_pk = pubkey_from_compressed_bytes(&plaintext[..PUBLIC_KEY_SIZE])?;

let body = &plaintext[UNCOMPRESSED_PUBLIC_KEY_SIZE..];
let body = &plaintext[PUBLIC_KEY_SIZE..];

Ok((body.to_vec(), reply_pk))
}
Expand Down Expand Up @@ -258,7 +275,7 @@ fn pad_plaintext(msg: &mut Vec<u8>, padded_length: usize) -> Result<&[u8], HpkeE
/// Error from de/encrypting a v2 Hybrid Public Key Encryption payload.
#[derive(Debug, PartialEq)]
pub enum HpkeError {
Secp256k1(bitcoin::secp256k1::Error),
Secp256k1(secp256k1::Error),
Hpke(hpke::HpkeError),
InvalidKeyLength,
PayloadTooLarge { actual: usize, max: usize },
Expand All @@ -269,8 +286,8 @@ 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 From<secp256k1::Error> for HpkeError {
fn from(value: secp256k1::Error) -> Self { Self::Secp256k1(value) }
}

impl fmt::Display for HpkeError {
Expand Down
Loading