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

291 lines
7.9 KiB
Odin

package mldsa
import "core:crypto"
import "core:crypto/_mldsa"
// Parameters are the supported ML-DSA parameter sets.
Parameters :: enum {
Invalid,
ML_DSA_44,
ML_DSA_65,
ML_DSA_87,
}
// PRIVATE_KEY_SEED_SIZE is the size of a private key in bytes.
PRIVATE_KEY_SEED_SIZE :: _mldsa.SEEDBYTES // 32-bytes
// MAX_CTX_SIZE is the maximum size of the signature context
// (domain separation tag) in bytes.
MAX_CTX_SIZE :: _mldsa.CTXBYTES_MAX // 255-bytes
// PUBLIC_KEY_SIZES are the per-parameter sizes of a public
// key in bytes.
PUBLIC_KEY_SIZES := [Parameters]int {
.Invalid = 0,
.ML_DSA_44 = 1312,
.ML_DSA_65 = 1952,
.ML_DSA_87 = 2592,
}
// SIGNATURE_SIZES are the per-parameter sizes of a signature
// in byte.
SIGNATURE_SIZES := [Parameters]int {
.Invalid = 0,
.ML_DSA_44 = 2420,
.ML_DSA_65 = 3309,
.ML_DSA_87 = 4627,
}
@(private="file")
_PARAMS_TO_INTERNAL := [Parameters]^_mldsa.Params {
.Invalid = nil,
.ML_DSA_44 = &_mldsa.Params_44,
.ML_DSA_65 = &_mldsa.Params_65,
.ML_DSA_87 = &_mldsa.Params_87,
}
// Private_Key is a ML-DSA private key.
Private_Key :: _mldsa.Private_Key
// Public_Key is a ML-DSA public key.
Public_Key :: _mldsa.Public_Key
// private_key_generate uses the system entropy source to generate a new
// Private_Key. This will only fail if and only if (⟺) the system entropy
// source is missing or broken.
@(require_results)
private_key_generate :: proc(priv_key: ^Private_Key, params: Parameters) -> bool {
private_key_clear(priv_key)
if !crypto.HAS_RAND_BYTES {
return false
}
params_ := _PARAMS_TO_INTERNAL[params]
if params_ == nil {
return false
}
seed: [PRIVATE_KEY_SEED_SIZE]byte = ---
defer crypto.zero_explicit(&seed, size_of(seed))
crypto.rand_bytes(seed[:])
_mldsa.dsa_keygen_internal(priv_key, seed[:], params_)
return true
}
// private_key_set_bytes decodes a byte-encoded private key in "seed" format,
// and returns true if and only if (⟺) the operation was successful.
@(require_results)
private_key_set_bytes :: proc(priv_key: ^Private_Key, params: Parameters, b: []byte) -> bool {
private_key_clear(priv_key)
params_ := _PARAMS_TO_INTERNAL[params]
if params_ == nil {
return false
}
if len(b) != PRIVATE_KEY_SEED_SIZE {
return false
}
_mldsa.dsa_keygen_internal(priv_key, b, params_)
return true
}
// private_key_bytes sets dst to byte-encoding of priv_key in the "seed"
// format.
private_key_bytes :: proc(priv_key: ^Private_Key, dst: []byte) {
ensure(priv_key.params != nil, "crypto/mldsa: uninitialized private key")
ensure(len(dst) == PRIVATE_KEY_SEED_SIZE, "crypto/mldsa: invalid destination size")
copy(dst, priv_key.seed[:])
}
// private_key_public_bytes sets dst to the byte-encoding of the public
// key corresponding to priv_key.
private_key_public_bytes :: proc(priv_key: ^Private_Key, dst: []byte) {
public_key_bytes(&priv_key.pub_key, dst)
}
// private_key_set sets priv_key to src.
private_key_set :: proc(priv_key, src: ^Private_Key) {
if src == nil || internal_to_params(src.params) == .Invalid {
private_key_clear(priv_key)
return
}
_mldsa.set_sk(priv_key, src)
}
// private_key_equal returns true if and only if (⟺) the private keys are
// equal, in constant time.
@(require_results)
private_key_equal :: proc(p, q: ^Private_Key) -> bool {
if p.params != q.params {
return false
}
if p.params == nil {
return true
}
// Just compare the seed that was passed to dsa_keygen_internal,
// since the process is completely deterministic.
return crypto.compare_constant_time(p.seed[:], q.seed[:]) == 1
}
// private_key_clear clears priv_key to the uninitialized state.
private_key_clear :: proc "contextless" (priv_key: ^Private_Key) {
_mldsa.clear_sk(priv_key)
}
// public_key_set_bytes decodes a byte-encoded public key, and returns
// true if and only if (⟺) the operation was successful.
@(require_results)
public_key_set_bytes :: proc(pub_key: ^Public_Key, params: Parameters, b: []byte) -> bool {
params_ := _PARAMS_TO_INTERNAL[params]
if params_ == nil {
return false
}
return _mldsa.unpack_pk(pub_key, b, params_)
}
// public_key_set sets pub_key to src.
public_key_set :: proc(pub_key, src: ^Public_Key) {
if src == nil || internal_to_params(src.params) == .Invalid {
public_key_clear(pub_key)
return
}
_mldsa.set_pk(pub_key, src)
}
// public_key_set_priv sets pub_key to the public component of priv_key.
public_key_set_priv :: proc(pub_key: ^Public_Key, priv_key: ^Private_Key) {
ensure(priv_key.params != nil, "crypto/mldsa: uninitialized private key")
public_key_set(pub_key, &priv_key.pub_key)
}
// public_key_bytes sets dst to byte-encoding of pub_key.
public_key_bytes :: proc(pub_key: ^Public_Key, dst: []byte) {
ensure(pub_key.params != nil, "crypto/mldsa: uninitialized public key")
params := internal_to_params(pub_key.params)
ensure(len(dst) == PUBLIC_KEY_SIZES[params], "crypto/mldsa: invalid destination size")
_ = _mldsa.pack_pk(dst, pub_key)
}
// public_key_equal returns true if and only if (⟺) the public keys are equal,
// in constant time.
@(require_results)
public_key_equal :: proc(p, q: ^Public_Key) -> bool {
if p.params != q.params {
return false
}
if p.params == nil {
return true
}
// Comparing the pre-computed hash should be enough, but pack
// both public keys and do the comparisons.
PUBLIC_KEY_SIZE_MAX :: 2592
l := PUBLIC_KEY_SIZES[internal_to_params(p.params)]
p_buf_, q_buf_: [PUBLIC_KEY_SIZE_MAX]byte = ---, ---
p_buf, q_buf := p_buf_[:l], q_buf_[:l]
_ = _mldsa.pack_pk(p_buf, p)
_ = _mldsa.pack_pk(q_buf, q)
return crypto.compare_constant_time(p_buf, q_buf) == 1
}
// public_key_clear clears pub_key to the uninitialized state.
public_key_clear :: proc "contextless" (pub_key: ^Public_Key) {
_mldsa.clear_pk(pub_key)
}
// sign writes the signature by priv_key over (ctx, msg) to sig and
// returns true if and only if (⟺) the signing succeeded.
//
// ctx is an optional domain separation tag and may be omitted (nil).
@(require_results)
sign :: proc(priv_key: ^Private_Key, ctx, msg, sig: []byte, deterministic := !crypto.HAS_RAND_BYTES) -> bool {
params := internal_to_params(priv_key.params)
ensure(params != .Invalid, "crypto/mldsa: invalid private key")
ensure(len(sig) == SIGNATURE_SIZES[params], "crypto/mldsa: invalid destination size")
if !deterministic && !crypto.HAS_RAND_BYTES {
return false
}
if len(ctx) > MAX_CTX_SIZE {
return false
}
rnd: [_mldsa.RNDBYTES]byte
defer crypto.zero_explicit(&rnd, size_of(rnd))
if !deterministic {
crypto.rand_bytes(rnd[:])
}
return _mldsa.dsa_sign_internal(sig, msg, ctx, rnd[:], priv_key)
}
// verify returns true if and only if (⟺) sig is a valid signature by pub_key
// over (ctx, msg).
@(require_results)
verify :: proc(pub_key: ^Public_Key, ctx, msg, sig: []byte) -> bool {
params := internal_to_params(pub_key.params)
ensure(params != .Invalid, "crypto/mldsa: invalid public key")
if len(sig) != SIGNATURE_SIZES[params] {
return false
}
if len(ctx) > MAX_CTX_SIZE {
return false
}
return _mldsa.dsa_verify_internal(sig, msg, ctx, pub_key)
}
// params returns the Parameters used by a Private_Key or Public_Key
// instance.
@(require_results)
params :: proc(k: ^$T) -> Parameters where (T == Private_Key || T == Public_Key) {
return internal_to_params(k.params)
}
// key_size returns the key size of a Private_Key or Public_Key in bytes.
@(require_results)
key_size :: proc(k: ^$T) -> int where (T == Private_Key || T == Public_Key) {
when T == Private_Key {
return PRIVATE_KEY_SEED_SIZE
} else {
return PUBLIC_KEY_SIZES[internal_to_params(k.params)]
}
}
// signature_size returns the key size of a signature in bytes.
@(require_results)
signature_size :: proc(k: ^$T) -> int where (T == Private_Key || T == Public_Key) {
return SIGNATURE_SIZES[internal_to_params(k.params)]
}
@(private="file",require_results)
internal_to_params :: proc "contextless" (params: ^_mldsa.Params) -> Parameters {
switch params {
case &_mldsa.Params_44:
return .ML_DSA_44
case &_mldsa.Params_65:
return .ML_DSA_65
case &_mldsa.Params_87:
return .ML_DSA_87
case:
return .Invalid
}
}