-
Notifications
You must be signed in to change notification settings - Fork 190
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add SimpleKEM and FullKEM traits to kem #1559
base: master
Are you sure you want to change the base?
Conversation
This is also very similar to pub trait KemCore {
type SharedKeySize: ArraySize;
type CiphertextSize: ArraySize;
type DecapsulationKey: Decapsulate<Ciphertext<Self>, SharedKey<Self>> + EncodedSizeUser + Debug + PartialEq;
#[cfg(not(feature = "deterministic"))]
type EncapsulationKey: Encapsulate<Ciphertext<Self>, SharedKey<Self>> + EncodedSizeUser + Debug + PartialEq;
#[cfg(feature = "deterministic")]
type EncapsulationKey: Encapsulate<Ciphertext<Self>, SharedKey<Self>> + EncapsulateDeterministic<Ciphertext<Self>, SharedKey<Self>> + EncodedSizeUser + Debug + PartialEq;
fn generate(rng: &mut impl CryptoRngCore) -> (Self::DecapsulationKey, Self::EncapsulationKey);
#[cfg(feature = "deterministic")]
fn generate_deterministic(d: &B32, z: &B32) -> (Self::DecapsulationKey, Self::EncapsulationKey);
} In particular let dk_bytes = Encoded::<K::DecapsulationKey>::from_slice(self.dk);
assert_eq!(dk, K::DecapsulationKey::from_bytes(dk_bytes));
let ek_bytes = Encoded::<K::EncapsulationKey>::from_slice(self.ek);
assert_eq!(ek, K::EncapsulationKey::from_bytes(ek_bytes)); In the general model, this requires an adapter trait to be written for tests and included as an additional trait bound for generic tests. pub trait SecretBytes {
fn as_slice(&self) -> &[u8];
}
impl SecretBytes for Secret {
fn as_slice(&self) -> &[u8] {
self.0.as_bytes().as_slice()
}
}
// use a generic SimpleKEM function to ensure correctness
fn test_kemtrait_basic<K: SimpleKEM>()
where
<K as SimpleKEM>::SharedSecret: SecretBytes,
{
let mut rng = rand::thread_rng();
let (sk, pk) = K::random_keypair(&mut rng);
let (ek, ss1) = K::encapsulate(&pk, &mut rng).expect("never fails");
let ss2 = K::decapsulate(&sk, &ek).expect("never fails");
assert_eq!(ss1.as_slice(), ss2.as_slice());
} |
I agree ml-kem probably shouldn't be defining its own traits. I guess I'm not sure what functionality is needed by users. The primary addition I see in Thinking through alternatives: suppose a function is generic over a KEM and needs to be able to generate a fresh ephemeral keypair. Then perhaps it should be of the form fn foo<EK, SS, E, F>(gen: F)
where
E: Encapsulate<EK, SS>,
F: Fn(&mut impl CryptoRngCore) -> (E, [u8; 32])
{
let rng = rand::thread_rng();
let (ek, dk) = gen(&mut rng);
// ...
} This is nice because Thoughts? |
I don't have an opinion per se, but the goal of the trait is so that one can precisely express what a specific KEM model does. i.e.
As for how these types are specified, it is ultimately up to the user. The X3DH test includes an example of how impl FullKEM for X3Dh {
type PrivateKey = X3DhPrivkeyBundle;
type PublicKey = X3DhPubkeyBundle;
type DecapsulatingKey = DecapContext;
type EncapsulatingKey = EncapContext;
type EncapsulatedKey = EphemeralKey;
type SharedSecret = SharedSecret;
fn random_keypair(_: &mut impl CryptoRngCore) -> (Self::PrivateKey, Self::PublicKey) {
let sk = Self::PrivateKey::gen();
let pk = sk.as_pubkeys();
(sk, pk)
}
}
fn test_kemtrait_x3dh() {
let mut rng = rand::thread_rng();
let sk_ident_a = IdentityKey::default();
let pk_ident_a = sk_ident_a.strip();
let (sk_bundle_b, pk_bundle_b) = X3Dh::random_keypair(&mut rng);
let encap_context = EncapContext(pk_bundle_b, sk_ident_a);
let decap_context = DecapContext(sk_bundle_b, pk_ident_a);
// Now do an authenticated encap
let (encapped_key, ss1) = X3Dh::encapsulate(&encap_context, &mut rng).unwrap();
let ss2 = X3Dh::decapsulate(&decap_context, &encapped_key).unwrap();
assert_eq!(ss1, ss2);
} The primary motivation for these traits is to develop the general TLS KEM combiner so we can ultimately do |
I see what you mean. Though I might be missing the point on the example API and impl for X3DH you give. If you defined a function that generically created a keypair and did some operations, it's very unlikely that it would generate an identity key AND a prekey AND an ephemeral key (more likely: keep the identity key, pick a prekey from a set, and generate a fresh ephemeral key). For that reason, it seems like there's no concrete use case for a |
I do agree that key generation can probably be moved outside of the trait. I think the main reason I included it is due to it being in |
I think having a trait for capturing these details is good. I'm unclear why we need two traits and why they can't build on each other. It feels like a lot of duplication. The names should follow RFC430, i.e. |
My initial impression is that this seems like a regression over the simplification we did in #1509. It looks to me like the only details this interface exposes is key generation, which has not been exposed in, e.g., the traits in the trait KemKeyGenerate {
type EncapKey: Encapsulator<EK>,
type DecapKey: Decapsulator<EK>,
fn generate(rng: &mut CryptoRng) -> Result<(Self::DecapKey, Self::EncapKey), Error>;
} Having If we're copying patterns from |
For KEMs in particular it seemed very weird to me why the original traits also split Encapsulator and Decapsulator. In particular, for KEMs, these actions are closely tied together and it would make sense to bundle these into a single trait.
I think the point here is similar to above. In practice, KEMs are essentially specified by their Perhaps the best design here is to unify |
Why is this any different from a signature algorithm that specifies both sign and verify? These are reflected in Signer and Verifier traits in this repo. Just because they're in different traits doesn't mean they can't be implemented together, say in the same file. To put it differently, think of what application code needs to have in order to do something. With the |
@incertia having a ZST to hang the overall scheme off of seems fine to me, but I would think it would only have associated types for the |
@tarcieri what value would that add over say |
Alternatively, |
@bifurcation being able to write generic code that can locate the type which performs decapsulation, similar to Edit: whoops, it would probably make more sense to be able to look up the associated encapsulator for a given decapsulator, but hopefully you get the idea |
This also makes sense |
|
As a more concrete example of a (sidebar: it would be nice to support RSA-KEM eventually) |
@bifurcation |
Ok, to sketch out at bit: we have as a starting point pub trait signature::Keypair {
type VerifyingKey: Clone;
fn verifying_key(&self) -> Self::VerifyingKey;
} Replacing everything with the appropriate types, we get pub trait kem::Keypair<EK, SS> {
type EncapsulationKey: Encapsulation<EK, SS>;
fn encapsulation_key(&self) -> Self::EncapsulationKey;
} Did we want to support getting the decap key from this? If so, do we know why the |
@rozbb in The reason As I mentioned in this earlier, since that complication doesn't exist here, |
I see. So iteration 2: pub trait kem::Keypair<EK, SS> {
type EncapsulationKey: Encapsulation<EK, SS>;
type DecapsulationKey: Decapsulation<EK, SS>;
fn encapsulation_key(&self) -> Self::EncapsulationKey;
fn decapsulation_key(&self) -> Self::DecapsulationKey;
} Actually, by this logic, why can't we just do: struct kem::Keypair<EK, SS, E, D>
where
E: Encapsulation<EK, SS>,
D: Decapsulation<EK, SS>,
{
pub encap_key: E,
pub decap_key: D,
} |
@rozbb of those, the struct looks better to me. Either are a bit different from |
following in this vein, perhaps |
This adds a
SimpleKEM
trait, representing KEM models where the public and private keys from key generation are equivalent to the encapsulating and decapsulating keys. We also add aFullKEM
trait, which is more general, which allows for KEM models where this is not the case, such as when trying to model authenticated X3DH as a KEM.Motivation for this PR is mostly just promoting the
DhKem
trait from RustCrypto/KEMs#16 into the kem crate itself.I think it is also good to have some code that forwards the calls from
SimpleKEM::encapsulate
andFullKEM::encapsulate
to the actualEncapsulate<EK, SS>
implementation. That way, a user can directly writeKemModel::encapsulate
and ensure that the types are correct, in the case that there are separate models that use very similar setups. e.g. unauthenticated vs authenticated modes of key exchange.