Files
Odin/core/crypto/mlkem/api.odin
2026-05-19 19:32:50 +09:00

305 lines
9.0 KiB
Odin

package mlkem
import "core:crypto"
import "core:crypto/_mlkem"
// Parameters are the supported ML-KEM parameter sets.
Parameters :: enum {
Invalid,
ML_KEM_512,
ML_KEM_768,
ML_KEM_1024,
}
// DECAPSULATION_KEY_SEED_SIZE is the size of a Decapsulation key in bytes.
DECAPSULATION_KEY_SEED_SIZE :: 64 // (d, z) in NIST terms.
// DECAPSULATION_KEY_EXPANDED_SIZES are the per-parameter sizes of the
// decapsulation key in bytes.
DECAPSULATION_KEY_EXPANDED_SIZES := [Parameters]int {
.Invalid = 0,
.ML_KEM_512 = _mlkem.DECAPSKEYBYTES_512, // 1632-bytes
.ML_KEM_768 = _mlkem.DECAPSKEYBYTES_768, // 2400-bytes
.ML_KEM_1024 = _mlkem.DECAPSKEYBYTES_1024, // 3168-bytes
}
// ENCAPSULATION_KEY_SIZES are the per-parameter sizes of the encapsulation
// key in bytes.
ENCAPSULATION_KEY_SIZES := [Parameters]int {
.Invalid = 0,
.ML_KEM_512 = _mlkem.ENCAPSKEYBYTES_512, // 800-bytes
.ML_KEM_768 = _mlkem.ENCAPSKEYBYTES_768, // 1184-bytes
.ML_KEM_1024 = _mlkem.ENCAPSKEYBYTES_1024, // 1568-bytes
}
// CIPHERTEXT_SIZES are the per-parameter set sizes of the ciphertext
// in bytes.
CIPHERTEXT_SIZES := [Parameters]int {
.Invalid = 0,
.ML_KEM_512 = _mlkem.CIPHERTEXTBYTES_512, // 768-bytes
.ML_KEM_768 = _mlkem.CIPHERTEXTBYTES_768, // 1088-bytes
.ML_KEM_1024 = _mlkem.CIPHERTEXTBYTES_1024, // 1568-bytes
}
// SHARED_SECRET_SIZE is the size of the final shared secret in bytes.
SHARED_SECRET_SIZE :: 32
// Decapsulation_Key is a ML-KEM decapsulation (aka "private") key.
// This implementation opts to include the encapsulation (aka "public")
// key as well for cases where the decapsulation key is reused (eg: HPKE
// with X-Wing).
Decapsulation_Key :: _mlkem.Decapsulation_Key
// Encapsulation_Key is a ML-KEM encapsulation (aka "public") key.
Encapsulation_Key :: _mlkem.Encapsulation_Key
// decapsulation_key_generate uses the system entropy source to generate
// a decapsulation key. This will only fail if and only if (⟺) the system
// entropy source is missing or broken.
@(require_results)
decapsulation_key_generate :: proc(dk: ^Decapsulation_Key, params: Parameters) -> bool {
decapsulation_key_clear(dk)
if !crypto.HAS_RAND_BYTES {
return false
}
k := params_to_k(params)
if k == 0 {
panic("crypto/mlkem: invalid parameter set")
}
seed: [DECAPSULATION_KEY_SEED_SIZE]byte = ---
defer crypto.zero_explicit(&seed, size_of(seed))
crypto.rand_bytes(seed[:])
_mlkem.kem_keygen_internal(dk, seed[:], k)
return true
}
// decapsulation_key_set_bytes decodes a byte-encoded decapsulation key
// in (d, z) "seed" format, and returns true if and only if (⟺) the
// operation was successful.
@(require_results)
decapsulation_key_set_bytes :: proc(dk: ^Decapsulation_Key, params: Parameters, seed: []byte) -> bool {
k := params_to_k(params)
if k == 0 {
return false
}
if len(seed) != DECAPSULATION_KEY_SEED_SIZE {
return false
}
_mlkem.kem_keygen_internal(dk, seed, k)
return true
}
// decapsulation_key_bytes sets dst to byte-encoding of dk in the (d, z)
// "seed" format.
decapsulation_key_bytes :: proc(dk: ^Decapsulation_Key, dst: []byte) {
ensure(dk.pke_dk.k != 0, "crypto/mlkem: uninitialized Decapsulation_Key")
ensure(len(dst) == DECAPSULATION_KEY_SEED_SIZE, "crypto/mlkem: invalid destination size")
copy(dst, dk.seed[:])
}
// decapsulation_key_expanded_bytes sets dst to the byte-encoding of dk.
// in the expanded FIPS 203 format. This primarily exists for export
// purposes.
decapsulation_key_expanded_bytes :: proc(dk: ^Decapsulation_Key, dst: []byte) {
dk_len: int
switch dk.pke_dk.k {
case _mlkem.K_512:
dk_len = DECAPSULATION_KEY_EXPANDED_SIZES[.ML_KEM_512]
case _mlkem.K_768:
dk_len = DECAPSULATION_KEY_EXPANDED_SIZES[.ML_KEM_768]
case _mlkem.K_1024:
dk_len = DECAPSULATION_KEY_EXPANDED_SIZES[.ML_KEM_1024]
case:
panic("crypto/mlkem: uninitialized Decapsulation_Key")
}
ensure(len(dst) == dk_len, "crypto/mlkem: invalid destination size")
_mlkem.decapsulation_key_expanded_bytes(dk, dst)
}
// decapsulation_key_encaps_bytes sets dst to the byte-encoding of the
// encasulation key corresponding to dk.
decapsulation_key_encaps_bytes :: proc(dk: ^Decapsulation_Key, dst: []byte) {
encapsulation_key_bytes(&dk.ek, dst)
}
// decapsulation_key_clear clears dk to the uninitialized state.
decapsulation_key_clear :: proc(dk: ^Decapsulation_Key) {
crypto.zero_explicit(dk, size_of(Decapsulation_Key))
}
// encapsulation_key_set_bytes decodes a byte-encoded encapsulation key,
// and returns true if and only if (⟺) the operation was successful.
@(require_results)
encapsulation_key_set_bytes :: proc(ek: ^Encapsulation_Key, params: Parameters, b: []byte) -> bool {
k := params_to_k(params)
if k == 0 {
return false
}
if len(b) != ENCAPSULATION_KEY_SIZES[params] {
return false
}
return _mlkem.encapsulation_key_set_bytes(ek, k, b)
}
// encapsulation_key_set_decaps sets ek to the encapsulation key corresponding
// to dk.
encapsulation_key_set_decaps :: proc(ek: ^Encapsulation_Key, dk: ^Decapsulation_Key) {
ensure(dk.pke_dk.k != 0, "crypto/mlkem: uninitialized Decapsulation_Key")
_mlkem.encapsulation_key_set_decaps(ek, dk)
}
// encapsulation_key_encaps_bytes sets dst to the byte-encoding of ek.
encapsulation_key_bytes :: proc(ek: ^Encapsulation_Key, dst: []byte) {
ensure(ek.pke_ek.k != 0, "crypto/mlkem: uninitialized Encapsulation_Key")
k_len: int
switch ek.pke_ek.k {
case _mlkem.K_512:
k_len = ENCAPSULATION_KEY_SIZES[.ML_KEM_512]
case _mlkem.K_768:
k_len = ENCAPSULATION_KEY_SIZES[.ML_KEM_768]
case _mlkem.K_1024:
k_len = ENCAPSULATION_KEY_SIZES[.ML_KEM_1024]
case:
panic("crypto/mlkem: invalid destination size")
}
copy(dst, ek.raw_bytes[:k_len])
}
// encapsulation_key_clear clears ek to the uninitialized state.
encapsulation_key_clear :: proc(ek: ^Encapsulation_Key) {
crypto.zero_explicit(ek, size_of(Encapsulation_Key))
}
// encaps_raw_ek_bytes uses the byte encoded encapsulation key to generate
// a shared secret and an associated ciphertext. This routine will fail
// if the system entropy source is unavailable, or of the encapsulation key
// is invalid.
@(require_results)
encaps_ek_raw_bytes :: proc(params: Parameters, raw_ek, shared_secret, ciphertext: []byte) -> bool {
ek: Encapsulation_Key = ---
if !encapsulation_key_set_bytes(&ek, params, raw_ek) {
return false
}
defer encapsulation_key_clear(&ek)
return encaps_ek(&ek, shared_secret, ciphertext)
}
// encaps_ek uses the encapsulation key to generate a shared secret and an
// associated ciphertext. This routine will fail if the system entropy source
// is unavailable.
@(require_results)
encaps_ek :: proc(ek: ^Encapsulation_Key, shared_secret, ciphertext: []byte) -> bool {
ensure(len(shared_secret) == SHARED_SECRET_SIZE, "crypto/mlkem: invalid shared_seret size")
if !crypto.HAS_RAND_BYTES {
return false
}
m: [_mlkem.SYMBYTES]byte = ---
defer crypto.zero_explicit(&m, size_of(m))
crypto.rand_bytes(m[:])
_mlkem.kem_encaps_internal(shared_secret, ciphertext, ek, m[:])
return true
}
encaps :: proc {
encaps_ek,
encaps_ek_raw_bytes,
}
// decaps uses the decapsulation key to generate a shared secret from a
// ciphertext. Due to ML-KEM's implicit rejection mechanism, this function
// will only return false if and only if (⟺) the lengths of the inputs
// are invalid or the decapsulation key is uninitialized.
//
// This routine returning true does not guarantee that the shared secret
// matches that generated by the peer.
@(require_results)
decaps :: proc(dk: ^Decapsulation_Key, ciphertext, shared_secret: []byte) -> bool {
ensure(len(shared_secret) == SHARED_SECRET_SIZE, "crypto/mlkem: invalid shared_seret size")
ct_len: int
switch dk.pke_dk.k {
case _mlkem.K_512:
ct_len = CIPHERTEXT_SIZES[.ML_KEM_512]
case _mlkem.K_768:
ct_len = CIPHERTEXT_SIZES[.ML_KEM_768]
case _mlkem.K_1024:
ct_len = CIPHERTEXT_SIZES[.ML_KEM_1024]
case:
return false
}
if len(ciphertext) != ct_len {
return false
}
_mlkem.kem_decaps_internal(shared_secret, dk, ciphertext)
return true
}
// params returns the Parameters used by a Decapsulation_Key or
// Encapsulation_Key instance.
@(require_results)
params :: proc(k: ^$T) -> Parameters where (T == Encapsulation_Key || T == Decapsulation_Key) {
when T == Encapsulation_Key {
return k_to_params(k.pke_ek.k)
} else {
return k_to_params(k.pke_dk.k)
}
}
// key_size returns the key size of a Decapsulation_Key or Encapsulation_Key
// in bytes.
@(require_results)
key_size :: proc(k: ^$T) -> int where (T == Encapsulation_Key || T == Decapsulation_Key) {
when T == Encapsulation_Key {
return ENCAPSULATION_KEY_SIZES[k.pke_ek.k]
} else {
return DECAPSULATION_KEY_SEED_SIZE
}
}
@(private="file")
params_to_k :: #force_inline proc "contextless" (params: Parameters) -> int {
#partial switch params {
case .ML_KEM_512:
return _mlkem.K_512
case .ML_KEM_768:
return _mlkem.K_768
case .ML_KEM_1024:
return _mlkem.K_1024
}
return 0
}
@(private="file")
k_to_params :: #force_inline proc "contextless" (k: int) -> Parameters {
switch k {
case _mlkem.K_512:
return .ML_KEM_512
case _mlkem.K_768:
return .ML_KEM_768
case _mlkem.K_1024:
return .ML_KEM_1024
}
return .Invalid
}