mirror of
https://github.com/odin-lang/Odin.git
synced 2026-05-25 13:18:14 +00:00
305 lines
9.0 KiB
Odin
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
|
|
}
|