diff --git a/Cargo.toml b/Cargo.toml index d57cefa..322d548 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,7 @@ p384 = ["dep:p384"] p256 = ["dep:p256"] p521 = ["dep:p521"] k256 = ["dep:k256"] +secp = ["bitcoin", "secp256k1/global-context", "secp256k1/rand-std"] # Include allocating methods like open() and seal() alloc = [] # Includes an implementation of `std::error::Error` for `HpkeError`. Also does what `alloc` does. @@ -29,6 +30,9 @@ std = [] [dependencies] aead = "0.5" aes-gcm = "0.10" +bitcoin = { version = "0.32.0", optional = true } +secp256k1 = { version = "0.29", optional = true } +# bitcoin = { git = "https://github.com/rust-bitcoin/rust-bitcoin", commit = "0d1cab68eee59f79c3ec76cf393438471b68fe69", optional = true } byteorder = { version = "1.4", default-features = false } chacha20poly1305 = "0.10" generic-array = { version = "0.14", default-features = false } diff --git a/src/dhkex.rs b/src/dhkex.rs index bf27779..95a9535 100644 --- a/src/dhkex.rs +++ b/src/dhkex.rs @@ -49,3 +49,6 @@ pub(crate) mod ecdh_nist; #[cfg(feature = "x25519")] pub(crate) mod x25519; + +#[cfg(feature = "secp")] +pub(crate) mod secp256k1; \ No newline at end of file diff --git a/src/dhkex/secp256k1.rs b/src/dhkex/secp256k1.rs new file mode 100644 index 0000000..1a6701b --- /dev/null +++ b/src/dhkex/secp256k1.rs @@ -0,0 +1,363 @@ +use crate::{ + dhkex::{DhError, DhKeyExchange}, + kdf::{labeled_extract, Kdf as KdfTrait, LabeledExpand}, + util::{enforce_equal_len, enforce_outbuf_len, KemSuiteId}, + Deserializable, HpkeError, Serializable, +}; + +use generic_array::typenum::{self, Unsigned}; +use subtle::{Choice, ConstantTimeEq}; + +// We wrap the types in order to abstract away the dalek dep + +/// A Secp256k1 public key +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct PublicKey(bitcoin::secp256k1::PublicKey); + +// The underlying type is zeroize-on-drop +/// An X25519 private key +#[derive(Clone)] +pub struct PrivateKey(bitcoin::secp256k1::SecretKey); + +impl ConstantTimeEq for PrivateKey { + fn ct_eq(&self, other: &Self) -> Choice { + self.0.secret_bytes().ct_eq(&other.0.secret_bytes()) + } +} + +impl PartialEq for PrivateKey { + fn eq(&self, other: &Self) -> bool { + self.ct_eq(other).into() + } +} +impl Eq for PrivateKey {} + +// The underlying type is zeroize-on-drop +/// A bare DH computation result +pub struct KexResult(bitcoin::secp256k1::ecdh::SharedSecret); + +// Oh I love an excuse to break out type-level integers +impl Serializable for PublicKey { + // RFC 9180 §7.1 Table 2: Npk of DHKEM(Secp256k1, HKDF-SHA256) is 65 + type OutputSize = typenum::U65; + + // secp256k1 lets us serialize uncompressed pubkeys to [u8; 65] + fn write_exact(&self, buf: &mut [u8]) { + // Check the length is correct and panic if not + enforce_outbuf_len::(buf); + + buf.copy_from_slice(&self.0.serialize_uncompressed()); + } +} + +impl Deserializable for PublicKey { + // secp256k1 lets us convert [u8; 65] to pubkeys. Assuming the input length is correct, this // TODO IS THIS RIGHT? + // conversion is infallible, so no ValidationErrors are raised. + fn from_bytes(encoded: &[u8]) -> Result { + // TODO can I get rid of equal len since bitcoin::secp256k1 already does this? + // Pubkeys must be 65 bytes + enforce_equal_len(Self::OutputSize::to_usize(), encoded.len())?; + + // Copy to a fixed-size array + let mut arr = [0u8; 65]; + arr.copy_from_slice(encoded); + Ok(PublicKey(bitcoin::secp256k1::PublicKey::from_slice(&arr).map_err(|_| HpkeError::ValidationError)?)) + } +} + +impl Serializable for PrivateKey { + // RFC 9180 §7.1 Table 2: Nsk of DHKEM(Secp256k1, HKDF-SHA256) is 32 + type OutputSize = typenum::U32; + + // secp256k1 lets us convert scalars to [u8; 32] // TODO CHECK! + fn write_exact(&self, buf: &mut [u8]) { + // Check the length is correct and panic if not + enforce_outbuf_len::(buf); + + buf.copy_from_slice(&self.0.secret_bytes()); + } +} +impl Deserializable for PrivateKey { + // Secp256k1 lets us convert [u8; 32] to scalars. Assuming the input length is correct, this + // conversion is infallible, so no ValidationErrors are raised. + fn from_bytes(encoded: &[u8]) -> Result { + // Privkeys must be 32 bytes + enforce_equal_len(Self::OutputSize::to_usize(), encoded.len())?; + + // Copy to a fixed-size array + let mut arr = [0u8; 32]; + arr.copy_from_slice(encoded); + // We don't have to do a zero-check for X25519 private keys. We clamp all private keys upon + // deserialization, and clamped private keys cannot ever be 0 mod curve_order. In fact, + // they can't even be 0 mod q where q is the order of the prime subgroup generated by the + // canonical generator. + // Why? + // A clamped key k is of the form 2^254 + 8j where j is in [0, 2^251-1]. If k = 0 (mod q) + // then k = nq for some n > 0. And since k is a multiple of 8 and q is prime, n must be a + // multiple of 8. However, 8q > 2^257 which is already out of representable range! So k + // cannot be 0 (mod q). + Ok(PrivateKey(bitcoin::secp256k1::SecretKey::from_slice(&arr).map_err(|_| HpkeError::ValidationError)?)) + } +} + +impl Serializable for KexResult { + // RFC 9180 §4.1: For Secp256k1, the size Ndh is equal to 32 + type OutputSize = typenum::U32; + + // curve25519's point representation is our DH result. We don't have to do anything special. + fn write_exact(&self, buf: &mut [u8]) { + // Check the length is correct and panic if not + enforce_outbuf_len::(buf); + + // Dalek lets us convert shared secrets to to [u8; 32] + buf.copy_from_slice(&self.0.secret_bytes()); + } +} + +/// Represents ECDH functionality over the Secp256k1 group +pub struct Secp256k1 {} + +impl DhKeyExchange for Secp256k1 { + #[doc(hidden)] + type PublicKey = PublicKey; + #[doc(hidden)] + type PrivateKey = PrivateKey; + #[doc(hidden)] + type KexResult = KexResult; + + /// Converts an Secp256k1 private key to a public key + #[doc(hidden)] + fn sk_to_pk(sk: &PrivateKey) -> PublicKey { + PublicKey(bitcoin::secp256k1::PublicKey::from_secret_key_global(&sk.0)) + } + + /// Does the DH operation. Returns an error if and only if the DH result was all zeros. This is + /// required by the HPKE spec. The error is converted into the appropriate higher-level error + /// by the caller, i.e., `HpkeError::EncapError` or `HpkeError::DecapError`. + #[doc(hidden)] + fn dh(sk: &PrivateKey, pk: &PublicKey) -> Result { + // let res = bitcoin::secp256k1::ecdh::shared_secret_point(&pk.0, &sk.0); + let res = bitcoin::secp256k1::ecdh::SharedSecret::new( &pk.0, &sk.0); + // "Senders and recipients MUST check whether the shared secret is the all-zero value + // and abort if so" + if res.secret_bytes().ct_eq(&[0u8; 32]).into() { + Err(DhError) + } else { + Ok(KexResult(res)) + } + } + + // RFC 9180 §7.1.3 + // def DeriveKeyPair(ikm): + // dkp_prk = LabeledExtract("", "dkp_prk", ikm) + // sk = LabeledExpand(dkp_prk, "sk", "", Nsk) + // return (sk, pk(sk)) + + /// Deterministically derives a keypair from the given input keying material and ciphersuite + /// ID. The keying material SHOULD have as many bits of entropy as the bit length of a secret + /// key, i.e., 256. + #[doc(hidden)] + fn derive_keypair(suite_id: &KemSuiteId, ikm: &[u8]) -> (PrivateKey, PublicKey) { + // Write the label into a byte buffer and extract from the IKM + let (_, hkdf_ctx) = labeled_extract::(&[], suite_id, b"dkp_prk", ikm); + // The buffer we hold the candidate scalar bytes in. This is the size of a private key. + let mut buf = [0u8; 32]; + hkdf_ctx + .labeled_expand(suite_id, b"sk", &[], &mut buf) + .unwrap(); + + let sk = bitcoin::secp256k1::SecretKey::from_slice(&buf).expect("clamped private key"); + let pk = bitcoin::secp256k1::PublicKey::from_secret_key_global(&sk); + (PrivateKey(sk), PublicKey(pk)) + } +} + +#[cfg(test)] +mod tests { + use crate::dhkex::{secp256k1::Secp256k1, Deserializable, DhKeyExchange, Serializable}; + use generic_array::typenum::Unsigned; + use rand::{rngs::StdRng, RngCore, SeedableRng}; + + // /// Tests that an serialize-deserialize round-trip ends up at the same pubkey + // #[test] + // fn test_pubkey_serialize_correctness() { + // type Kex = Secp256k1; + + // let mut csprng = StdRng::from_entropy(); + + // let (_sk, pk) = secp256k1::generate_keypair(&mut csprng); + + // // Make a pubkey with those random bytes. Note, that from_bytes() does not clamp the input + // // bytes. This is why this test passes. + // let pk = crate::dhkex::secp256k1::PublicKey(pk); + // let pk_bytes = pk.to_bytes(); + // let re_serialized = ::PublicKey::from_bytes(&pk_bytes.as_slice()).unwrap(); + + // // See if the re-serialized bytes are the same as the input + // assert_eq!(pk_bytes,re_serialized.to_bytes()); + // } + + // /// Tests that an deserialize-serialize round trip on a DH keypair ends up at the same values + // #[test] + // fn test_dh_serialize_correctness() { + // type Kex = Secp256k1; + + // let mut csprng = StdRng::from_entropy(); + + // // Make a random keypair and serialize it + // let (sk, pk) = secp256k1::generate_keypair(&mut csprng); + // let (sk_bytes, pk_bytes) = (sk.secret_bytes(), pk.serialize_uncompressed()); + + + // // Now deserialize those bytes + // let new_sk = ::PrivateKey::from_bytes(&sk_bytes.as_slice()).unwrap(); + // let new_pk = ::PublicKey::from_bytes(&pk_bytes.as_slice()).unwrap(); + + // // See if the deserialized values are the same as the initial ones + // assert!(new_sk == crate::dhkex::secp256k1::PrivateKey(sk), "private key doesn't serialize correctly"); + // assert!(new_pk == crate::dhkex::secp256k1::PublicKey(pk), "public key doesn't serialize correctly"); + // } + + #[test] + fn just_test_secp_roundtrip() { + use secp256k1::{PublicKey, SecretKey, generate_keypair}; + let (sk1, pk1) = generate_keypair(&mut rand_core::OsRng); + assert_eq!(SecretKey::from_slice(&sk1[..]), Ok(sk1)); + assert_eq!(PublicKey::from_slice(&pk1.serialize()[..]), Ok(pk1)); + assert_eq!(PublicKey::from_slice(&pk1.serialize_uncompressed()[..]), Ok(pk1)); + } + + use crate::{test_util::dhkex_gen_keypair}; + + use hex_literal::hex; + + // + // Test vectors come from the draft's ChaCha20-Poly1305 first base test case. + // https://github.com/kwantam/draft-wahby-cfrg-hpke-kem-secp256k1/blob/main/draft-wahby-cfrg-hpke-kem-secp256k1.md#dhkemsecp256k1-hkdf-sha256-hkdf-sha256-chacha20-poly1305 + // + + #[cfg(feature = "secp")] + 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 = "secp")] + 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 = "secp")] + const K256_DH_RES_XCOORD: &[u8] = + &hex!("3ADDFBC2 B30E3D1B 1DF262A4 D6CECF73 A11DF8BD 93E0EB21 FC11847C 6F3DDBE2"); + + /// Tests the ECDH op against a known answer + #[allow(dead_code)] + fn test_vector_ecdh( + sk_recip_bytes: &[u8], + pk_sender_bytes: &[u8], + dh_res_xcoord_bytes: &[u8], + ) { + // Deserialize the pubkey and privkey and do a DH operation + let sk_recip = Kex::PrivateKey::from_bytes(&sk_recip_bytes).unwrap(); + let pk_sender = Kex::PublicKey::from_bytes(&pk_sender_bytes).unwrap(); + let derived_dh = Kex::dh(&sk_recip, &pk_sender).unwrap(); + + // Assert that the derived DH result matches the test vector. Recall that the HPKE DH + // result is just the x-coordinate, so that's all we can compare + assert_eq!(derived_dh.to_bytes().as_slice(), dh_res_xcoord_bytes,); + } + + /// Tests that an deserialize-serialize round-trip ends up at the same pubkey + #[allow(dead_code)] + fn test_pubkey_serialize_correctness() { + let mut csprng = StdRng::from_entropy(); + + // We can't do the same thing as in the X25519 tests, since a completely random point + // is not likely to lie on the curve. Instead, we just generate a random point, + // serialize it, deserialize it, and test whether it's the same using impl Eq for + // AffinePoint + + let (_, pubkey) = dhkex_gen_keypair::(&mut csprng); + let pubkey_bytes = pubkey.to_bytes(); + let rederived_pubkey = + ::PublicKey::from_bytes(&pubkey_bytes).unwrap(); + + // See if the re-serialized bytes are the same as the input + assert_eq!(pubkey, rederived_pubkey); + } + + /// Tests the `sk_to_pk` function against known answers + #[allow(dead_code)] + fn test_vector_corresponding_pubkey(sks: &[&[u8]], pks: &[&[u8]]) { + for (sk_bytes, pk_bytes) in sks.iter().zip(pks.iter()) { + // Deserialize the hex values + let sk = Kex::PrivateKey::from_bytes(sk_bytes).unwrap(); + let pk = Kex::PublicKey::from_bytes(pk_bytes).unwrap(); + + // Derive the secret key's corresponding pubkey and check that it matches the given + // pubkey + let derived_pk = Kex::sk_to_pk(&sk); + assert_eq!(derived_pk, pk); + } + } + + /// Tests that an deserialize-serialize round-trip on a DH keypair ends up at the same values + #[allow(dead_code)] + fn test_dh_serialize_correctness() + where + Kex::PrivateKey: PartialEq, + { + let mut csprng = StdRng::from_entropy(); + + // Make a random keypair and serialize it + let (sk, pk) = dhkex_gen_keypair::(&mut csprng); + let (sk_bytes, pk_bytes) = (sk.to_bytes(), pk.to_bytes()); + + // Now deserialize those bytes + let new_sk = Kex::PrivateKey::from_bytes(&sk_bytes).unwrap(); + let new_pk = Kex::PublicKey::from_bytes(&pk_bytes).unwrap(); + + // See if the deserialized values are the same as the initial ones + assert!(new_sk == sk, "private key doesn't serialize correctly"); + assert!(new_pk == pk, "public key doesn't serialize correctly"); + } + + #[cfg(feature = "secp")] + #[test] + fn test_vector_ecdh_k256() { + test_vector_ecdh::(&K256_PRIVKEYS[0], &K256_PUBKEYS[1], &K256_DH_RES_XCOORD); + } + + #[cfg(feature = "secp")] + #[test] + fn test_vector_corresponding_pubkey_k256() { + test_vector_corresponding_pubkey::(K256_PRIVKEYS, K256_PUBKEYS); + } + + #[cfg(feature = "secp")] + #[test] + fn test_pubkey_serialize_correctness_k256() { + test_pubkey_serialize_correctness::(); + } + + #[cfg(feature = "secp")] + #[test] + fn test_dh_serialize_correctness_k256() { + use super::Secp256k1; + + test_dh_serialize_correctness::(); + } +} + diff --git a/src/kem/dhkem.rs b/src/kem/dhkem.rs index 44b7c8b..f21ed2c 100644 --- a/src/kem/dhkem.rs +++ b/src/kem/dhkem.rs @@ -402,3 +402,15 @@ impl_dhkem!( 0x0016, "Represents DHKEM(K-256, HKDF-SHA256)" ); + +// Implement DHKEM(Secp256k1, HKDF-SHA256) +#[cfg(feature = "secp")] +impl_dhkem!( + secpk256_hkdfsha256, + SecpK256HkdfSha256, + crate::dhkex::secp256k1::Secp256k1, + crate::kdf::HkdfSha256, + 0x0016, + "Represents DHKEM(Secp256k1, HKDF-SHA256)" +); + diff --git a/src/kem/secp-dhkem.rs b/src/kem/secp-dhkem.rs new file mode 100644 index 0000000..01bb7de --- /dev/null +++ b/src/kem/secp-dhkem.rs @@ -0,0 +1,351 @@ +// /// Defines DHKEM(G, K) given a Diffie-Hellman group G and KDF K +// // pub use $mod_name::$kem_name; + +// pub(crate) mod dhsecp256k1_hkdfsha256 { +// use crate::{ +// dhkex::{DhKeyExchange, MAX_PUBKEY_SIZE}, +// kdf::{extract_and_expand, Kdf as KdfTrait}, +// kem::{Kem as KemTrait, SharedSecret}, +// util::{enforce_outbuf_len, kem_suite_id}, +// Deserializable, HpkeError, Serializable, +// }; + +// use digest::OutputSizeUser; +// use rand_core::{CryptoRng, RngCore}; + +// // Define convenience types +// type PublicKey = bitcoin::secp256k1::PublicKey; +// type PrivateKey = bitcoin::secp256k1::PrivateKey; + +// // RFC 9180 §4.1 +// // The function parameters pkR and pkS are deserialized public keys, and enc is a +// // serialized public key. Since encapsulated keys are Diffie-Hellman public keys in +// // this KEM algorithm, we use SerializePublicKey() and DeserializePublicKey() to +// // encode and decode them, respectively. Npk equals Nenc. + +// /// Holds the content of an encapsulated secret. This is what the receiver uses to +// /// derive the shared secret. This just wraps a pubkey, because that's all an +// /// encapsulated key is in a DHKEM. +// #[doc(hidden)] +// #[derive(Clone)] +// pub struct EncappedKey(pub(crate) bitcoin::secp256k1::PublicKey); + +// // EncappedKeys need to be serializable, since they're gonna be sent over the wire. +// // Underlyingly, they're just DH pubkeys, so we just serialize them the same way +// impl Serializable for EncappedKey { +// type OutputSize = ::OutputSize; + +// // Pass to underlying to_bytes() impl +// fn write_exact(&self, buf: &mut [u8]) { +// // Check the length is correct and panic if not +// enforce_outbuf_len::(buf); + +// buf.copy_from_slice(&self.0.to_bytes()); +// } +// } + +// impl Deserializable for EncappedKey { +// // Pass to underlying from_bytes() impl +// fn from_bytes(encoded: &[u8]) -> Result { +// let pubkey = +// ::from_bytes(encoded)?; +// Ok(EncappedKey(pubkey)) +// } +// } + +// // Define the KEM struct +// #[doc = $doc_str] +// pub struct Secp256k1KEM; + +// // RFC 9180 §4.1 +// // def Encap(pkR): +// // skE, pkE = GenerateKeyPair() +// // dh = DH(skE, pkR) +// // enc = SerializePublicKey(pkE) +// // +// // pkRm = SerializePublicKey(pkR) +// // kem_context = concat(enc, pkRm) +// // +// // def AuthEncap(pkR, skS): +// // skE, pkE = GenerateKeyPair() +// // dh = concat(DH(skE, pkR), DH(skS, pkR)) +// // enc = SerializePublicKey(pkE) +// // +// // pkRm = SerializePublicKey(pkR) +// // pkSm = SerializePublicKey(pk(skS)) +// // kem_context = concat(enc, pkRm, pkSm) +// // +// // shared_secret = ExtractAndExpand(dh, kem_context) +// // return shared_secret, enc + +// // The reason we define encap_with_eph() rather than just encap() is because we need to +// // use deterministic ephemeral keys in the known-answer tests. So we define a function +// // here, then use it to impl kem::Kem and kat_tests::TestableKem. + +// /// Derives a shared secret that the owner of the recipient's pubkey can use to derive +// /// the same shared secret. If `sk_sender_id` is given, the sender's identity will be +// /// tied to the shared secret. +// /// +// /// Return Value +// /// ============ +// /// Returns a shared secret and encapped key on success. If an error happened during +// /// key exchange, returns `Err(HpkeError::EncapError)`. +// #[doc(hidden)] +// pub(crate) fn encap_with_eph( +// pk_recip: &PublicKey, +// sender_id_keypair: Option<(&PrivateKey, &PublicKey)>, +// sk_eph: PrivateKey, +// ) -> Result<(bitcoin::secp256k1::ecdh::SharedSecret, EncappedKey), HpkeError> { +// // Put together the binding context used for all KDF operations +// let suite_id = kem_suite_id::<$kem_name>(); + +// // Compute the shared secret from the ephemeral inputs +// let kex_res_eph = SharedSecret::new(&sk_eph, pk_recip) +// .map_err(|_| HpkeError::EncapError)?; + +// // The encapped key is the ephemeral pubkey +// let encapped_key = { +// let pk_eph = PublicKey::from_secret_key(&sk_eph); +// EncappedKey(pk_eph) +// }; + +// // The shared secret is either gonna be kex_res_eph, or that along with another +// // shared secret that's tied to the sender's identity. +// let shared_secret = if let Some((sk_sender_id, pk_sender_id)) = sender_id_keypair { +// // kem_context = encapped_key || pk_recip || pk_sender_id +// // We concat without allocation by making a buffer of the maximum possible +// // size, then taking the appropriately sized slice. +// let (kem_context_buf, kem_context_size) = concat_with_known_maxlen!( +// bitcoin::secp256k1::constants::UNCOMPRESSED_PUBLIC_KEY_SIZE, +// &encapped_key.to_bytes(), +// &pk_recip.to_bytes(), +// &pk_sender_id.to_bytes() +// ); +// let kem_context = &kem_context_buf[..kem_context_size]; + +// // We want to do an authed encap. Do a DH exchange between the sender identity +// // secret key and the recipient's pubkey +// let kex_res_identity = SharedSecret::new(sk_sender_id, pk_recip) +// .map_err(|_| HpkeError::EncapError)?; + +// // concatted_secrets = kex_res_eph || kex_res_identity +// // Same no-alloc concat trick as above +// let (concatted_secrets_buf, concatted_secret_size) = concat_with_known_maxlen!( +// bitcoin::secp256k1::constants::UNCOMPRESSED_PUBLIC_KEY_SIZE, +// &kex_res_eph.to_bytes(), +// &kex_res_identity.to_bytes() +// ); +// let concatted_secrets = &concatted_secrets_buf[..concatted_secret_size]; + +// // The "authed shared secret" is derived from the KEX of the ephemeral input +// // with the recipient pubkey, and the KEX of the identity input with the +// // recipient pubkey. The HKDF-Expand call only errors if the output values are +// // 255x the digest size of the hash function. Since these values are fixed at +// // compile time, we don't worry about it. +// let mut buf = as Default>::default(); +// // TODO +// extract_and_expand::<$kdf>(concatted_secrets, &suite_id, kem_context, &mut buf.0) +// .expect("shared secret is way too big"); +// buf +// } else { +// // kem_context = encapped_key || pk_recip +// // We concat without allocation by making a buffer of the maximum possible +// // size, then taking the appropriately sized slice. +// let (kem_context_buf, kem_context_size) = concat_with_known_maxlen!( +// bitcoin::secp256k1::constants::UNCOMPRESSED_PUBLIC_KEY_SIZE, +// &encapped_key.to_bytes(), +// &pk_recip.to_bytes() +// ); +// let kem_context = &kem_context_buf[..kem_context_size]; + +// // The "unauthed shared secret" is derived from just the KEX of the ephemeral +// // input with the recipient pubkey. The HKDF-Expand call only errors if the +// // output values are 255x the digest size of the hash function. Since these +// // values are fixed at compile time, we don't worry about it. +// let mut buf = as Default>::default(); +// extract_and_expand::<$kdf>( +// &kex_res_eph.to_bytes(), +// &suite_id, +// kem_context, +// &mut buf.0, +// ) +// .expect("shared secret is way too big"); +// buf +// }; + +// Ok((shared_secret, encapped_key)) +// } + +// impl KemTrait for $kem_name { +// // RFC 9180 §4.1 +// // For the variants of DHKEM defined in this document, the size Nsecret of the +// // KEM shared secret is equal to the output length of the hash function underlying +// // the KDF. + +// /// The size of the shared secret at the end of the key exchange process +// #[doc(hidden)] +// type NSecret = <<$kdf as KdfTrait>::HashImpl as OutputSizeUser>::OutputSize; + +// type PublicKey = PublicKey; +// type PrivateKey = PrivateKey; +// type EncappedKey = EncappedKey; + +// const KEM_ID: u16 = $kem_id; + +// /// Deterministically derives a keypair from the given input keying material +// /// +// /// Requirements +// /// ============ +// /// This keying material SHOULD have as many bits of entropy as the bit length of a +// /// secret key, i.e., `8 * Self::PrivateKey::size()`. For X25519 and P-256, this is +// /// 256 bits of entropy. +// fn derive_keypair(ikm: &[u8]) -> (Self::PrivateKey, Self::PublicKey) { +// let suite_id = kem_suite_id::(); +// let secp = bitcoin::secp256k1::Secp256k1::new(); +// secp.generate_keypair(/*(randomness) */) +// } + +// /// Computes the public key of a given private key +// fn sk_to_pk(sk: &PrivateKey) -> PublicKey { +// sk.public_key(&secp) +// } + +// // Runs encap_with_eph using a random ephemeral key +// fn encap( +// pk_recip: &Self::PublicKey, +// sender_id_keypair: Option<(&Self::PrivateKey, &Self::PublicKey)>, +// csprng: &mut R, +// ) -> Result<(SharedSecret, Self::EncappedKey), HpkeError> { +// // Generate a new ephemeral key +// let (sk_eph, _) = Self::gen_keypair(csprng); +// // Now pass to encap_with_eph() +// encap_with_eph(pk_recip, sender_id_keypair, sk_eph) +// } + +// // RFC 9180 §4.1 +// // def Decap(enc, skR): +// // pkE = DeserializePublicKey(enc) +// // dh = DH(skR, pkE) +// // +// // pkRm = SerializePublicKey(pk(skR)) +// // kem_context = concat(enc, pkRm) +// // +// // shared_secret = ExtractAndExpand(dh, kem_context) +// // return shared_secret +// // +// // def AuthDecap(enc, skR, pkS): +// // pkE = DeserializePublicKey(enc) +// // dh = concat(DH(skR, pkE), DH(skR, pkS)) +// // +// // pkRm = SerializePublicKey(pk(skR)) +// // pkSm = SerializePublicKey(pkS) +// // kem_context = concat(enc, pkRm, pkSm) +// // +// // shared_secret = ExtractAndExpand(dh, kem_context) +// // return shared_secret + +// /// Derives a shared secret given the encapsulated key and the recipients secret key. +// /// If `pk_sender_id` is given, the sender's identity will be tied to the shared +// /// secret. +// /// +// /// Return Value +// /// ============ +// /// Returns a shared secret on success. If an error happened during key exchange, +// /// returns `Err(HpkeError::DecapError)`. +// #[doc(hidden)] +// fn decap( +// sk_recip: &Self::PrivateKey, +// pk_sender_id: Option<&Self::PublicKey>, +// encapped_key: &Self::EncappedKey, +// ) -> Result, HpkeError> { +// // Put together the binding context used for all KDF operations +// let suite_id = kem_suite_id::(); + +// // Compute the shared secret from the ephemeral inputs +// let kex_res_eph = SharedSecret::new(sk_recip, &encapped_key.0) +// .map_err(|_| HpkeError::DecapError)?; + +// // Compute the sender's pubkey from their privkey +// let pk_recip = SecretKey::sk_to_pk(sk_recip); + +// // The shared secret is either gonna be kex_res_eph, or that along with another +// // shared secret that's tied to the sender's identity. +// if let Some(pk_sender_id) = pk_sender_id { +// // kem_context = encapped_key || pk_recip || pk_sender_id We concat without +// // allocation by making a buffer of the maximum possible size, then taking the +// // appropriately sized slice. +// let (kem_context_buf, kem_context_size) = concat_with_known_maxlen!( +// bitcoin::secp256k1::constants::UNCOMPRESSED_PUBLIC_KEY_SIZE, +// &encapped_key.to_bytes(), +// &pk_recip.to_bytes(), +// &pk_sender_id.to_bytes() +// ); +// let kem_context = &kem_context_buf[..kem_context_size]; + +// // We want to do an authed encap. Do a DH exchange between the sender identity +// // secret key and the recipient's pubkey +// let kex_res_identity = <$dhkex as DhKeyExchange>::dh(sk_recip, pk_sender_id) +// .map_err(|_| HpkeError::DecapError)?; + +// // concatted_secrets = kex_res_eph || kex_res_identity +// // Same no-alloc concat trick as above +// let (concatted_secrets_buf, concatted_secret_size) = concat_with_known_maxlen!( +// bitcoin::secp256k1::constants::UNCOMPRESSED_PUBLIC_KEY_SIZE, +// &kex_res_eph.to_bytes(), +// &kex_res_identity.to_bytes() +// ); +// let concatted_secrets = &concatted_secrets_buf[..concatted_secret_size]; + +// // The "authed shared secret" is derived from the KEX of the ephemeral input +// // with the recipient pubkey, and the kex of the identity input with the +// // recipient pubkey. The HKDF-Expand call only errors if the output values are +// // 255x the digest size of the hash function. Since these values are fixed at +// // compile time, we don't worry about it. +// let mut shared_secret = as Default>::default(); +// extract_and_expand::<$kdf>( +// concatted_secrets, +// &suite_id, +// kem_context, +// &mut shared_secret.0, +// ) +// .expect("shared secret is way too big"); +// Ok(shared_secret) +// } else { +// // kem_context = encapped_key || pk_recip || pk_sender_id +// // We concat without allocation by making a buffer of the maximum possible +// // size, then taking the appropriately sized slice. +// let (kem_context_buf, kem_context_size) = concat_with_known_maxlen!( +// bitcoin::secp256k1::constants::UNCOMPRESSED_PUBLIC_KEY_SIZE, +// &encapped_key.to_bytes(), +// &pk_recip.to_bytes() +// ); +// let kem_context = &kem_context_buf[..kem_context_size]; + +// // The "unauthed shared secret" is derived from just the KEX of the ephemeral +// // input with the recipient pubkey. The HKDF-Expand call only errors if the +// // output values are 255x the digest size of the hash function. Since these +// // values are fixed at compile time, we don't worry about it. +// let mut shared_secret = as Default>::default(); +// extract_and_expand::<$kdf>( +// &kex_res_eph.to_bytes(), +// &suite_id, +// kem_context, +// &mut shared_secret.0, +// ) +// .expect("shared secret is way too big"); +// Ok(shared_secret) +// } +// } +// } +// } + +// // Implement DHKEM(K-256, HKDF-SHA256) +// #[cfg(feature = "k256")] +// impl_dhkem!( +// dhk256_hkdfsha256, // mod +// DhK256HkdfSha256, // struct +// crate::dhkex::ecdh_nist::k256::DhK256, // nist_dhkex defined ECDH functionalities +// crate::kdf::HkdfSha256, // HKDF +// 0x0016, // IANA KEM ID value https://www.iana.org/assignments/hpke/hpke.xhtml +// "Represents DHKEM(K-256, HKDF-SHA256)" // Description +// );