Skip to content

Commit

Permalink
secp256k1 DHKEM (#59)
Browse files Browse the repository at this point in the history
* Impld secp256k1 DHKEX

* Filled in k256 KEM and most automated tests

* Removed serde from CI

* Add DHKEM(secp256k1, HKDF-SHA256) to README

KEM name listed at iana.org
see: https://www.iana.org/assignments/hpke/hpke.xhtml
  • Loading branch information
DanGould authored Jul 5, 2024
1 parent 8c67667 commit 81a7782
Show file tree
Hide file tree
Showing 9 changed files with 146 additions and 9 deletions.
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ x25519 = ["dep:x25519-dalek"]
p384 = ["dep:p384"]
p256 = ["dep:p256"]
p521 = ["dep:p521"]
k256 = ["dep:k256"]
# Include allocating methods like open() and seal()
alloc = []
# Includes an implementation of `std::error::Error` for `HpkeError`. Also does what `alloc` does.
Expand All @@ -34,6 +35,7 @@ digest = "0.10"
hkdf = "0.12"
hmac = "0.12"
rand_core = { version = "0.6", default-features = false }
k256 = { version = "0.13", default-features = false, features = ["arithmetic", "ecdh"], optional = true}
p256 = { version = "0.13", default-features = false, features = ["arithmetic", "ecdh"], optional = true}
p384 = { version = "0.13", default-features = false, features = ["arithmetic", "ecdh"], optional = true}
p521 = { version = "0.13", default-features = false, features = ["arithmetic", "ecdh"], optional = true}
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ Here are all the primitives listed in the spec. The primitives with checked boxe
- [X] DHKEM(P-256, HKDF-SHA256)
- [X] DHKEM(P-384, HKDF-SHA384)
- [X] DHKEM(P-521, HKDF-SHA512)
- [X] DHKEM(secp256k1, HKDF-SHA256)
* KDFs
- [X] HKDF-SHA256
- [X] HKDF-SHA384
Expand Down
29 changes: 29 additions & 0 deletions src/aead.rs
Original file line number Diff line number Diff line change
Expand Up @@ -764,6 +764,35 @@ mod test {
);
}

#[cfg(all(feature = "k256", any(feature = "alloc", feature = "std")))]
mod k256_tests {
use super::*;

test_export_idempotence!(test_export_idempotence_k256, crate::kem::DhP256HkdfSha256);
test_exportonly_panics!(
test_exportonly_panics_k256_seal,
test_exportonly_panics_k256_open,
crate::kem::DhK256HkdfSha256
);
test_overflow!(test_overflow_k256, crate::kem::DhK256HkdfSha256);

test_ctx_correctness!(
test_ctx_correctness_aes128_k256,
AesGcm128,
crate::kem::DhK256HkdfSha256
);
test_ctx_correctness!(
test_ctx_correctness_aes256_k256,
AesGcm256,
crate::kem::DhK256HkdfSha256
);
test_ctx_correctness!(
test_ctx_correctness_chacha_k256,
ChaCha20Poly1305,
crate::kem::DhK256HkdfSha256
);
}

/// Tests that Serialize::write_exact() panics when given a buffer of incorrect length
#[should_panic]
#[test]
Expand Down
4 changes: 2 additions & 2 deletions src/dhkex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ pub trait DhKeyExchange {
) -> (Self::PrivateKey, Self::PublicKey);
}

#[cfg(any(feature = "p256", feature = "p384", feature = "p521"))]
pub(crate) mod ecdh_nistp;
#[cfg(any(feature = "p256", feature = "p384", feature = "k256", feature = "k256"))]
pub(crate) mod ecdh_nist;

#[cfg(feature = "x25519")]
pub(crate) mod x25519;
68 changes: 64 additions & 4 deletions src/dhkex/ecdh_nistp.rs → src/dhkex/ecdh_nist.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// We define all the NIST P- curve ECDH functionalities in one macro
macro_rules! nistp_dhkex {
macro_rules! nist_dhkex {
(
$curve_name:expr,
$dh_name:ident,
Expand Down Expand Up @@ -238,7 +238,7 @@ macro_rules! nistp_dhkex {
use generic_array::typenum;

#[cfg(feature = "p256")]
nistp_dhkex!(
nist_dhkex!(
"P-256",
DhP256,
p256,
Expand All @@ -249,7 +249,7 @@ nistp_dhkex!(
);

#[cfg(feature = "p384")]
nistp_dhkex!(
nist_dhkex!(
"P-384",
DhP384,
p384,
Expand All @@ -260,7 +260,7 @@ nistp_dhkex!(
);

#[cfg(feature = "p521")]
nistp_dhkex!(
nist_dhkex!(
"P-521",
DhP521,
p521,
Expand All @@ -270,10 +270,23 @@ nistp_dhkex!(
0x01 // RFC 9180 §7.1.3: The `bitmask` in DeriveKeyPair to be 0x01 for P-521
);

#[cfg(feature = "k256")]
nist_dhkex!(
"K-256",
DhK256,
k256,
typenum::U65,
typenum::U32,
typenum::U32,
0xFF
);

#[cfg(test)]
mod tests {
use crate::{dhkex::DhKeyExchange, test_util::dhkex_gen_keypair, Deserializable, Serializable};

#[cfg(feature = "k256")]
use super::k256::DhK256;
#[cfg(feature = "p256")]
use super::p256::DhP256;
#[cfg(feature = "p384")]
Expand Down Expand Up @@ -398,6 +411,32 @@ mod tests {
"DDEA"
);

#[cfg(feature = "k256")]
const K256_PRIVKEYS: &[&[u8]] = &[
&hex!("30FBC0D4 1CD01885 333211FF 53B9ED29 BCBDCCC3 FF13625A 82DB61A7 BB8EAE19"),
&hex!("A795C287 C132154A 8B96DC81 DC8B4E2F 02BBBAD7 8DAB0567 B59DB1D1 540751F6"),
];

// The public keys corresponding to the above private keys, in order
#[cfg(feature = "k256")]
const K256_PUBKEYS: &[&[u8]] = &[
&hex!(
"04" // Uncompressed
"59177516 8F328A2A DBCB887A CD287D55 A1025D7D 2B15E193 7278A5EF D1D48B19" // x-coordinate
"C00CF075 59320E6D 278A71C9 E58BAE5D 9AB041D7 905C6629 1F4D0845 9C946E18" // y-coordinate
),
&hex!(
"04" // Uncompressed
"3EE73144 07753D1B A296DE29 F07B2CD5 505CA94B 614F127E 71F3C19F C7845DAF" // x-coordinate
"49C9BB4B F4D00D3B 5411C8EB 86D59A2D CADC5A13 115FA9FE F44D1E0B 7EF11CAB" // y-coordinate
),
];

// The result of DH(privkey0, pubkey1) or equivalently, DH(privkey1, pubkey0)
#[cfg(feature = "k256")]
const K256_DH_RES_XCOORD: &[u8] =
&hex!("3ADDFBC2 B30E3D1B 1DF262A4 D6CECF73 A11DF8BD 93E0EB21 FC11847C 6F3DDBE2");

//
// Some helper functions for tests
//
Expand Down Expand Up @@ -485,6 +524,11 @@ mod tests {
fn test_vector_ecdh_p384() {
test_vector_ecdh::<DhP384>(&P384_PRIVKEYS[0], &P384_PUBKEYS[1], &P384_DH_RES_XCOORD);
}
#[cfg(feature = "k256")]
#[test]
fn test_vector_ecdh_k256() {
test_vector_ecdh::<DhK256>(&K256_PRIVKEYS[0], &K256_PUBKEYS[1], &K256_DH_RES_XCOORD);
}

#[cfg(feature = "p521")]
#[test]
Expand All @@ -503,6 +547,11 @@ mod tests {
fn test_vector_corresponding_pubkey_p384() {
test_vector_corresponding_pubkey::<DhP384>(P384_PRIVKEYS, P384_PUBKEYS);
}
#[cfg(feature = "k256")]
#[test]
fn test_vector_corresponding_pubkey_k256() {
test_vector_corresponding_pubkey::<DhK256>(K256_PRIVKEYS, K256_PUBKEYS);
}

#[cfg(feature = "p521")]
#[test]
Expand All @@ -521,6 +570,11 @@ mod tests {
fn test_pubkey_serialize_correctness_p384() {
test_pubkey_serialize_correctness::<DhP384>();
}
#[cfg(feature = "k256")]
#[test]
fn test_pubkey_serialize_correctness_k256() {
test_pubkey_serialize_correctness::<DhK256>();
}

#[cfg(feature = "p521")]
#[test]
Expand All @@ -545,4 +599,10 @@ mod tests {
fn test_dh_serialize_correctness_p521() {
test_dh_serialize_correctness::<DhP521>();
}

#[cfg(feature = "k256")]
#[test]
fn test_dh_serialize_correctness_k256() {
test_dh_serialize_correctness::<DhK256>();
}
}
8 changes: 8 additions & 0 deletions src/kem.rs
Original file line number Diff line number Diff line change
Expand Up @@ -223,4 +223,12 @@ mod tests {
test_encap_correctness!(test_encap_correctness_p521, crate::kem::DhP521HkdfSha512);
test_encapped_serialize!(test_encapped_serialize_p521, crate::kem::DhP521HkdfSha512);
}

#[cfg(feature = "k256")]
mod k256_tests {
use super::*;

test_encap_correctness!(test_encap_correctness_k256, crate::kem::DhK256HkdfSha256);
test_encapped_serialize!(test_encapped_serialize_k256, crate::kem::DhK256HkdfSha256);
}
}
17 changes: 14 additions & 3 deletions src/kem/dhkem.rs
Original file line number Diff line number Diff line change
Expand Up @@ -364,7 +364,7 @@ impl_dhkem!(
impl_dhkem!(
dhp256_hkdfsha256,
DhP256HkdfSha256,
crate::dhkex::ecdh_nistp::p256::DhP256,
crate::dhkex::ecdh_nist::p256::DhP256,
crate::kdf::HkdfSha256,
0x0010,
"Represents DHKEM(P-256, HKDF-SHA256)"
Expand All @@ -375,7 +375,7 @@ impl_dhkem!(
impl_dhkem!(
dhp384_hkdfsha384,
DhP384HkdfSha384,
crate::dhkex::ecdh_nistp::p384::DhP384,
crate::dhkex::ecdh_nist::p384::DhP384,
crate::kdf::HkdfSha384,
0x0011,
"Represents DHKEM(P-384, HKDF-SHA384)"
Expand All @@ -386,8 +386,19 @@ impl_dhkem!(
impl_dhkem!(
dhp521_hkdfsha512,
DhP521HkdfSha512,
crate::dhkex::ecdh_nistp::p521::DhP521,
crate::dhkex::ecdh_nist::p521::DhP521,
crate::kdf::HkdfSha512,
0x0012,
"Represents DHKEM(P-521, HKDF-SHA512)"
);

// Implement DHKEM(K-256, HKDF-SHA256)
#[cfg(feature = "k256")]
impl_dhkem!(
dhk256_hkdfsha256,
DhK256HkdfSha256,
crate::dhkex::ecdh_nist::k256::DhK256,
crate::kdf::HkdfSha256,
0x0016,
"Represents DHKEM(K-256, HKDF-SHA256)"
);
18 changes: 18 additions & 0 deletions src/setup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -410,4 +410,22 @@ mod test {
crate::kem::dhp521_hkdfsha512::DhP521HkdfSha512
);
}

#[cfg(feature = "k256")]
mod k256_tests {
use super::*;

test_setup_correctness!(
test_setup_correctness_k256,
ChaCha20Poly1305,
HkdfSha256,
crate::kem::dhk256_hkdfsha256::DhK256HkdfSha256
);
test_setup_soundness!(
test_setup_soundness_k256,
ChaCha20Poly1305,
HkdfSha256,
crate::kem::dhk256_hkdfsha256::DhK256HkdfSha256
);
}
}
8 changes: 8 additions & 0 deletions src/single_shot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -255,4 +255,12 @@ mod test {
crate::kdf::HkdfSha512,
crate::kem::dhp521_hkdfsha512::DhP521HkdfSha512
);

#[cfg(feature = "k256")]
test_single_shot_correctness!(
test_single_shot_correctness_k256,
ChaCha20Poly1305,
crate::kdf::HkdfSha256,
crate::kem::dhk256_hkdfsha256::DhK256HkdfSha256
);
}

0 comments on commit 81a7782

Please sign in to comment.