diff --git a/mldsa_native/README.md b/mldsa_native/README.md new file mode 100644 index 0000000..146c655 --- /dev/null +++ b/mldsa_native/README.md @@ -0,0 +1,23 @@ +[//]: # (SPDX-License-Identifier: CC-BY-4.0) + +# mldsa-native source tree + +This is the main source tree of mldsa-native. + +## Building + +To build mldsa-native for a fixed parameter set (ML-DSA-44/65/87), build the compilation units in `src/*` separately, and link to an RNG and your application. See [examples/basic](../examples/basic) for a simple example. + +Alternatively, you can use the auto-generated helper files [mldsa_native.c](mldsa_native.c) and [mldsa_native_asm.S](mldsa_native_asm.S), which bundle all *.c and *.S files together. See [examples/monolithic_build](../examples/monolithic_build) and [examples/monolithic_build_native](../examples/monolithic_build_native) for examples with and without native code. + +## Configuration + +The build is configured by [mldsa_native_config.h](mldsa_native_config.h), or by the file pointed to by `MLD_CONFIG_FILE`. Note in particular `MLD_CONFIG_PARAMETER_SET` and `MLD_CONFIG_NAMESPACE_PREFIX`, which set the parameter set and namespace prefix, respectively. + +## API + +The public API is defined in [mldsa_native.h](mldsa_native.h). + +## Supporting multiple parameter sets + +If you want to support multiple parameter sets, build the library once per parameter set you want to support. Set `MLD_CONFIG_MULTILEVEL_WITH_SHARED` for one of the builds, and `MLD_CONFIG_MULTILEVEL_NO_SHARED` for the others, to avoid duplicating shared functionality. Finally, link with RNG and your application as before. This is demonstrated in the examples [examples/multilevel_build](../examples/multilevel_build), [examples/multilevel_build_native](../examples/multilevel_build_native), [examples/monolithic_build_multilevel](../examples/monolithic_build_multilevel) and [examples/monolithic_build_multilevel_native](../examples/monolithic_build_multilevel_native). diff --git a/mldsa_native/mldsa_native.c b/mldsa_native/mldsa_native.c new file mode 100644 index 0000000..5dbe925 --- /dev/null +++ b/mldsa_native/mldsa_native.c @@ -0,0 +1,792 @@ +/* + * Copyright (c) The mldsa-native project authors + * SPDX-License-Identifier: Apache-2.0 OR ISC OR MIT + */ + +/* + * WARNING: This file is auto-generated from scripts/autogen + * in the mldsa-native repository. + * Do not modify it directly. + */ + +/****************************************************************************** + * + * Single compilation unit (SCU) for fixed-level build of mldsa-native + * + * This compilation unit bundles together all source files for a build + * of mldsa-native for a fixed security level (MLDSA-44/65/87). + * + * # API + * + * The API exposed by this file is described in mldsa_native.h. + * + * # Multi-level build + * + * If you want an SCU build of mldsa-native with support for multiple security + * levels, you need to include this file multiple times, and set + * MLD_CONFIG_MULTILEVEL_WITH_SHARED and MLD_CONFIG_MULTILEVEL_NO_SHARED + * appropriately. This is exemplified in examples/monolithic_build_multilevel + * and examples/monolithic_build_multilevel_native. + * + * # Configuration + * + * The following options from the mldsa-native configuration are relevant: + * + * - MLD_CONFIG_FIPS202_CUSTOM_HEADER + * Set this option if you use a custom FIPS202 implementation. + * + * - MLD_CONFIG_USE_NATIVE_BACKEND_ARITH + * Set this option if you want to include the native arithmetic backends + * in your build. + * + * - MLD_CONFIG_USE_NATIVE_BACKEND_FIPS202 + * Set this option if you want to include the native FIPS202 backends + * in your build. + * + * - MLD_CONFIG_MONOBUILD_KEEP_SHARED_HEADERS + * Set this option if you want to keep the directives defined in + * level-independent headers. This is needed for a multi-level build. + */ + +/* If parts of the mldsa-native source tree are not used, + * consider reducing this header via `unifdef`. + * + * Example: + * ```bash + * unifdef -UMLD_CONFIG_USE_NATIVE_BACKEND_ARITH mldsa_native.c + * ``` + */ + +#include "src/common.h" + +#include "src/ct.c" +#include "src/debug.c" +#include "src/packing.c" +#include "src/poly.c" +#include "src/poly_kl.c" +#include "src/polyvec.c" +#include "src/polyvec_lazy.c" +#include "src/sign.c" + +#if !defined(MLD_CONFIG_FIPS202_CUSTOM_HEADER) +#include "src/fips202/fips202.c" +#include "src/fips202/fips202x4.c" +#include "src/fips202/keccakf1600.c" +#endif + +#if defined(MLD_CONFIG_USE_NATIVE_BACKEND_ARITH) +#if defined(MLD_SYS_AARCH64) +#include "src/native/aarch64/src/aarch64_zetas.c" +#include "src/native/aarch64/src/polyz_unpack_table.c" +#include "src/native/aarch64/src/rej_uniform_eta_table.c" +#include "src/native/aarch64/src/rej_uniform_table.c" +#endif /* MLD_SYS_AARCH64 */ +#if defined(MLD_SYS_X86_64) +#include "src/native/x86_64/src/consts.c" +#include "src/native/x86_64/src/rej_uniform_avx2.c" +#include "src/native/x86_64/src/rej_uniform_eta2_avx2.c" +#include "src/native/x86_64/src/rej_uniform_eta4_avx2.c" +#include "src/native/x86_64/src/rej_uniform_table.c" +#endif /* MLD_SYS_X86_64 */ +#endif /* MLD_CONFIG_USE_NATIVE_BACKEND_ARITH */ + +#if defined(MLD_CONFIG_USE_NATIVE_BACKEND_FIPS202) +#if defined(MLD_SYS_AARCH64) +#include "src/fips202/native/aarch64/src/keccakf1600_round_constants.c" +#endif +#if defined(MLD_SYS_X86_64) +#include "src/fips202/native/x86_64/src/keccakf1600_constants.c" +#endif +#if defined(MLD_SYS_ARMV81M_MVE) +#include "src/fips202/native/armv81m/src/keccak_f1600_x4_mve.c" +#include "src/fips202/native/armv81m/src/keccakf1600_round_constants.c" +#endif +#endif /* MLD_CONFIG_USE_NATIVE_BACKEND_FIPS202 */ + +/* Macro #undef's + * + * The following undefines macros from headers + * included by the source files imported above. + * + * This is to allow building and linking multiple builds + * of mldsa-native for varying parameter sets through concatenation + * of this file, as if the files had been compiled separately. + * If this is not relevant to you, you may remove the following. + */ + +/* + * Undefine macros from MLD_CONFIG_PARAMETER_SET-specific files + */ +/* mldsa/mldsa_native.h */ +#undef CRYPTO_BYTES +#undef CRYPTO_PUBLICKEYBYTES +#undef CRYPTO_SECRETKEYBYTES +#undef MLDSA44_BYTES +#undef MLDSA44_CRHBYTES +#undef MLDSA44_PUBLICKEYBYTES +#undef MLDSA44_RNDBYTES +#undef MLDSA44_SECRETKEYBYTES +#undef MLDSA44_SEEDBYTES +#undef MLDSA65_BYTES +#undef MLDSA65_CRHBYTES +#undef MLDSA65_PUBLICKEYBYTES +#undef MLDSA65_RNDBYTES +#undef MLDSA65_SECRETKEYBYTES +#undef MLDSA65_SEEDBYTES +#undef MLDSA87_BYTES +#undef MLDSA87_CRHBYTES +#undef MLDSA87_PUBLICKEYBYTES +#undef MLDSA87_RNDBYTES +#undef MLDSA87_SECRETKEYBYTES +#undef MLDSA87_SEEDBYTES +#undef MLDSA_BYTES +#undef MLDSA_BYTES_ +#undef MLDSA_CRHBYTES +#undef MLDSA_PUBLICKEYBYTES +#undef MLDSA_PUBLICKEYBYTES_ +#undef MLDSA_RNDBYTES +#undef MLDSA_SECRETKEYBYTES +#undef MLDSA_SECRETKEYBYTES_ +#undef MLDSA_SEEDBYTES +#undef MLD_API_CONCAT +#undef MLD_API_CONCAT_ +#undef MLD_API_CONCAT_UNDERSCORE +#undef MLD_API_MUST_CHECK_RETURN_VALUE +#undef MLD_API_NAMESPACE +#undef MLD_API_NAMESPACE_PREFIX +#undef MLD_API_QUALIFIER +#undef MLD_DOMAIN_SEPARATION_MAX_BYTES +#undef MLD_ERR_FAIL +#undef MLD_ERR_OUT_OF_MEMORY +#undef MLD_ERR_RNG_FAIL +#undef MLD_ERR_SIGN_ATTEMPTS_EXHAUSTED +#undef MLD_H +#undef MLD_MAX3_ +#undef MLD_MAX4_ +#undef MLD_PREHASH_NONE +#undef MLD_PREHASH_SHA2_224 +#undef MLD_PREHASH_SHA2_256 +#undef MLD_PREHASH_SHA2_384 +#undef MLD_PREHASH_SHA2_512 +#undef MLD_PREHASH_SHA2_512_224 +#undef MLD_PREHASH_SHA2_512_256 +#undef MLD_PREHASH_SHA3_224 +#undef MLD_PREHASH_SHA3_256 +#undef MLD_PREHASH_SHA3_384 +#undef MLD_PREHASH_SHA3_512 +#undef MLD_PREHASH_SHAKE_128 +#undef MLD_PREHASH_SHAKE_256 +#undef MLD_TOTAL_ALLOC_44 +#undef MLD_TOTAL_ALLOC_44_KEYPAIR +#undef MLD_TOTAL_ALLOC_44_KEYPAIR_NO_PCT +#undef MLD_TOTAL_ALLOC_44_KEYPAIR_PCT +#undef MLD_TOTAL_ALLOC_44_PK_FROM_SK +#undef MLD_TOTAL_ALLOC_44_SIGN +#undef MLD_TOTAL_ALLOC_44_VERIFY +#undef MLD_TOTAL_ALLOC_65 +#undef MLD_TOTAL_ALLOC_65_KEYPAIR +#undef MLD_TOTAL_ALLOC_65_KEYPAIR_NO_PCT +#undef MLD_TOTAL_ALLOC_65_KEYPAIR_PCT +#undef MLD_TOTAL_ALLOC_65_PK_FROM_SK +#undef MLD_TOTAL_ALLOC_65_SIGN +#undef MLD_TOTAL_ALLOC_65_VERIFY +#undef MLD_TOTAL_ALLOC_87 +#undef MLD_TOTAL_ALLOC_87_KEYPAIR +#undef MLD_TOTAL_ALLOC_87_KEYPAIR_NO_PCT +#undef MLD_TOTAL_ALLOC_87_KEYPAIR_PCT +#undef MLD_TOTAL_ALLOC_87_PK_FROM_SK +#undef MLD_TOTAL_ALLOC_87_SIGN +#undef MLD_TOTAL_ALLOC_87_VERIFY +#undef crypto_sign_keypair +#undef crypto_sign_signature +#undef crypto_sign_verify +/* mldsa/src/common.h */ +#undef MLD_ADD_PARAM_SET +#undef MLD_ALLOC +#undef MLD_ANY_ERROR +#undef MLD_APPLY +#undef MLD_ASM_FN_SIZE +#undef MLD_ASM_FN_SYMBOL +#undef MLD_ASM_NAMESPACE +#undef MLD_BUILD_INTERNAL +#undef MLD_COMMON_H +#undef MLD_CONCAT +#undef MLD_CONCAT_ +#undef MLD_EMPTY_CU +#undef MLD_ERR_FAIL +#undef MLD_ERR_OUT_OF_MEMORY +#undef MLD_ERR_RNG_FAIL +#undef MLD_ERR_SIGN_ATTEMPTS_EXHAUSTED +#undef MLD_EXTERNAL_API +#undef MLD_FIPS202X4_HEADER_FILE +#undef MLD_FIPS202_HEADER_FILE +#undef MLD_FREE +#undef MLD_INTERNAL_API +#undef MLD_INTERNAL_DATA_DECLARATION +#undef MLD_INTERNAL_DATA_DEFINITION +#undef MLD_MULTILEVEL_BUILD +#undef MLD_NAMESPACE +#undef MLD_NAMESPACE_KL +#undef MLD_NAMESPACE_PREFIX +#undef MLD_NAMESPACE_PREFIX_KL +#undef mld_memcpy +#undef mld_memset +/* mldsa/src/packing.h */ +#undef MLD_PACKING_H +#undef mld_pack_sig_c +#undef mld_pack_sig_h +#undef mld_pack_sig_z +#undef mld_pack_sk_rho_key_tr_s2 +#undef mld_pack_sk_s1 +#undef mld_sig_unpack_hints +#undef mld_unpack_pk_t1 +#undef mld_unpack_sk +/* mldsa/src/params.h */ +#undef MLDSA_BETA +#undef MLDSA_CRHBYTES +#undef MLDSA_CRYPTO_BYTES +#undef MLDSA_CRYPTO_PUBLICKEYBYTES +#undef MLDSA_CRYPTO_SECRETKEYBYTES +#undef MLDSA_CTILDEBYTES +#undef MLDSA_D +#undef MLDSA_ETA +#undef MLDSA_GAMMA1 +#undef MLDSA_GAMMA2 +#undef MLDSA_GAMMA2_32 +#undef MLDSA_GAMMA2_88 +#undef MLDSA_K +#undef MLDSA_L +#undef MLDSA_N +#undef MLDSA_OMEGA +#undef MLDSA_PK_END +#undef MLDSA_PK_RHO_BYTES +#undef MLDSA_PK_RHO_OFFSET +#undef MLDSA_PK_T1_BYTES +#undef MLDSA_PK_T1_OFFSET +#undef MLDSA_POLYETA_PACKEDBYTES +#undef MLDSA_POLYT0_PACKEDBYTES +#undef MLDSA_POLYT1_PACKEDBYTES +#undef MLDSA_POLYVECH_PACKEDBYTES +#undef MLDSA_POLYW1_PACKEDBYTES +#undef MLDSA_POLYW1_PACKEDBYTES_32 +#undef MLDSA_POLYW1_PACKEDBYTES_88 +#undef MLDSA_POLYZ_PACKEDBYTES +#undef MLDSA_Q +#undef MLDSA_Q_HALF +#undef MLDSA_RNDBYTES +#undef MLDSA_SEEDBYTES +#undef MLDSA_SIG_C_BYTES +#undef MLDSA_SIG_C_OFFSET +#undef MLDSA_SIG_END +#undef MLDSA_SIG_H_BYTES +#undef MLDSA_SIG_H_OFFSET +#undef MLDSA_SIG_Z_BYTES +#undef MLDSA_SIG_Z_OFFSET +#undef MLDSA_SK_END +#undef MLDSA_SK_KEY_BYTES +#undef MLDSA_SK_KEY_OFFSET +#undef MLDSA_SK_RHO_BYTES +#undef MLDSA_SK_RHO_OFFSET +#undef MLDSA_SK_S1_BYTES +#undef MLDSA_SK_S1_OFFSET +#undef MLDSA_SK_S2_BYTES +#undef MLDSA_SK_S2_OFFSET +#undef MLDSA_SK_T0_BYTES +#undef MLDSA_SK_T0_OFFSET +#undef MLDSA_SK_TR_BYTES +#undef MLDSA_SK_TR_OFFSET +#undef MLDSA_TAU +#undef MLDSA_TRBYTES +#undef MLD_PARAMS_H +/* mldsa/src/poly_kl.h */ +#undef MLD_POLYETA_UNPACK_LOWER_BOUND +#undef MLD_POLY_KL_H +#undef mld_poly_challenge +#undef mld_poly_decompose +#undef mld_poly_uniform_eta +#undef mld_poly_uniform_eta_4x +#undef mld_poly_uniform_gamma1 +#undef mld_poly_uniform_gamma1_4x +#undef mld_poly_use_hint +#undef mld_polyeta_pack +#undef mld_polyeta_unpack +#undef mld_polyw1_pack +#undef mld_polyz_pack +#undef mld_polyz_unpack +/* mldsa/src/polyvec.h */ +#undef MLD_POLYVEC_H +#undef mld_polyveck +#undef mld_polyveck_caddq +#undef mld_polyveck_chknorm +#undef mld_polyveck_decompose +#undef mld_polyveck_invntt_tomont +#undef mld_polyveck_ntt +#undef mld_polyveck_pack_eta +#undef mld_polyveck_pack_w1 +#undef mld_polyveck_reduce +#undef mld_polyveck_unpack_eta +#undef mld_polyvecl +#undef mld_polyvecl_chknorm +#undef mld_polyvecl_ntt +#undef mld_polyvecl_pack_eta +#undef mld_polyvecl_pointwise_acc_montgomery +#undef mld_polyvecl_uniform_gamma1 +#undef mld_polyvecl_unpack_eta +#undef mld_polyvecl_unpack_z +/* mldsa/src/polyvec_lazy.h */ +#undef MLD_POLYVEC_LAZY_H +#undef mld_poly_permute_bitrev_to_custom_optional +#undef mld_polymat +#undef mld_polymat_eager +#undef mld_polymat_lazy +#undef mld_polyvec_matrix_expand +#undef mld_polyvec_matrix_expand_eager +#undef mld_polyvec_matrix_expand_lazy +#undef mld_polyvec_matrix_pointwise_montgomery +#undef mld_polyvec_matrix_pointwise_montgomery_row +#undef mld_polyvec_matrix_pointwise_montgomery_row_eager +#undef mld_polyvec_matrix_pointwise_montgomery_row_lazy +#undef mld_polyvec_matrix_pointwise_montgomery_yvec +#undef mld_polyvec_matrix_pointwise_montgomery_yvec_eager +#undef mld_polyvec_matrix_pointwise_montgomery_yvec_lazy +#undef mld_sk_s1hat +#undef mld_sk_s1hat_eager +#undef mld_sk_s1hat_get_poly +#undef mld_sk_s1hat_get_poly_eager +#undef mld_sk_s1hat_get_poly_lazy +#undef mld_sk_s1hat_lazy +#undef mld_sk_s2hat +#undef mld_sk_s2hat_eager +#undef mld_sk_s2hat_get_poly +#undef mld_sk_s2hat_get_poly_eager +#undef mld_sk_s2hat_get_poly_lazy +#undef mld_sk_s2hat_lazy +#undef mld_sk_t0hat +#undef mld_sk_t0hat_eager +#undef mld_sk_t0hat_get_poly +#undef mld_sk_t0hat_get_poly_eager +#undef mld_sk_t0hat_get_poly_lazy +#undef mld_sk_t0hat_lazy +#undef mld_unpack_sk_s1hat +#undef mld_unpack_sk_s1hat_eager +#undef mld_unpack_sk_s1hat_lazy +#undef mld_unpack_sk_s2hat +#undef mld_unpack_sk_s2hat_eager +#undef mld_unpack_sk_s2hat_lazy +#undef mld_unpack_sk_t0hat +#undef mld_unpack_sk_t0hat_eager +#undef mld_unpack_sk_t0hat_lazy +#undef mld_yvec +#undef mld_yvec_eager +#undef mld_yvec_get_poly +#undef mld_yvec_get_poly_eager +#undef mld_yvec_get_poly_lazy +#undef mld_yvec_init +#undef mld_yvec_init_eager +#undef mld_yvec_init_lazy +#undef mld_yvec_lazy +/* mldsa/src/rounding.h */ +#undef MLD_2_POW_D +#undef MLD_ROUNDING_H +#undef mld_decompose +#undef mld_make_hint +#undef mld_power2round +#undef mld_use_hint +/* mldsa/src/sign.h */ +#undef MLD_DOMAIN_SEPARATION_MAX_BYTES +#undef MLD_PREHASH_NONE +#undef MLD_PREHASH_SHA2_224 +#undef MLD_PREHASH_SHA2_256 +#undef MLD_PREHASH_SHA2_384 +#undef MLD_PREHASH_SHA2_512 +#undef MLD_PREHASH_SHA2_512_224 +#undef MLD_PREHASH_SHA2_512_256 +#undef MLD_PREHASH_SHA3_224 +#undef MLD_PREHASH_SHA3_256 +#undef MLD_PREHASH_SHA3_384 +#undef MLD_PREHASH_SHA3_512 +#undef MLD_PREHASH_SHAKE_128 +#undef MLD_PREHASH_SHAKE_256 +#undef MLD_SIGN_H +#undef mld_prepare_domain_separation_prefix +#undef mld_sign_keypair +#undef mld_sign_keypair_internal +#undef mld_sign_pk_from_sk +#undef mld_sign_signature +#undef mld_sign_signature_extmu +#undef mld_sign_signature_internal +#undef mld_sign_signature_pre_hash_internal +#undef mld_sign_signature_pre_hash_shake256 +#undef mld_sign_verify +#undef mld_sign_verify_extmu +#undef mld_sign_verify_internal +#undef mld_sign_verify_pre_hash_internal +#undef mld_sign_verify_pre_hash_shake256 + +#if !defined(MLD_CONFIG_MONOBUILD_KEEP_SHARED_HEADERS) +/* + * Undefine macros from MLD_CONFIG_PARAMETER_SET-generic files + */ +/* mldsa/src/context.h */ +#undef MLD_CONTEXT_H +#undef MLD_CONTEXT_PARAMETERS_0 +#undef MLD_CONTEXT_PARAMETERS_1 +#undef MLD_CONTEXT_PARAMETERS_2 +#undef MLD_CONTEXT_PARAMETERS_3 +#undef MLD_CONTEXT_PARAMETERS_4 +#undef MLD_CONTEXT_PARAMETERS_5 +#undef MLD_CONTEXT_PARAMETERS_6 +#undef MLD_CONTEXT_PARAMETERS_7 +#undef MLD_CONTEXT_PARAMETERS_8 +#undef MLD_CONTEXT_PARAMETERS_9 +/* mldsa/src/ct.h */ +#undef MLD_CT_H +#undef MLD_USE_ASM_VALUE_BARRIER +#undef mld_ct_opt_blocker_u64 +/* mldsa/src/debug.h */ +#undef MLD_DEBUG_H +#undef mld_assert +#undef mld_assert_abs_bound +#undef mld_assert_abs_bound_2d +#undef mld_assert_bound +#undef mld_assert_bound_2d +#undef mld_debug_check_assert +#undef mld_debug_check_bounds +/* mldsa/src/poly.h */ +#undef MLD_FQMUL_BOUND +#undef MLD_INTT_BOUND +#undef MLD_NTT_BOUND +#undef MLD_POLY_H +#undef mld_poly_add +#undef mld_poly_caddq +#undef mld_poly_chknorm +#undef mld_poly_invntt_tomont +#undef mld_poly_ntt +#undef mld_poly_pointwise_montgomery +#undef mld_poly_power2round +#undef mld_poly_reduce +#undef mld_poly_shiftl +#undef mld_poly_sub +#undef mld_poly_uniform +#undef mld_poly_uniform_4x +#undef mld_polyt0_pack +#undef mld_polyt0_unpack +#undef mld_polyt1_pack +#undef mld_polyt1_unpack +#undef mld_polyw1_pack_32 +#undef mld_polyw1_pack_88 +/* mldsa/src/randombytes.h */ +#undef MLD_RANDOMBYTES_H +/* mldsa/src/reduce.h */ +#undef MLD_MONT +#undef MLD_REDUCE32_DOMAIN_MAX +#undef MLD_REDUCE32_RANGE_MAX +#undef MLD_REDUCE_H +/* mldsa/src/symmetric.h */ +#undef MLD_STREAM128_BLOCKBYTES +#undef MLD_STREAM256_BLOCKBYTES +#undef MLD_SYMMETRIC_H +#undef mld_xof128_absorb_once +#undef mld_xof128_ctx +#undef mld_xof128_init +#undef mld_xof128_release +#undef mld_xof128_squeezeblocks +#undef mld_xof128_x4_absorb +#undef mld_xof128_x4_ctx +#undef mld_xof128_x4_init +#undef mld_xof128_x4_release +#undef mld_xof128_x4_squeezeblocks +#undef mld_xof256_absorb_once +#undef mld_xof256_ctx +#undef mld_xof256_init +#undef mld_xof256_release +#undef mld_xof256_squeezeblocks +#undef mld_xof256_x4_absorb +#undef mld_xof256_x4_ctx +#undef mld_xof256_x4_init +#undef mld_xof256_x4_release +#undef mld_xof256_x4_squeezeblocks +/* mldsa/src/sys.h */ +#undef MLD_ALIGN +#undef MLD_ALIGN_UP +#undef MLD_ALWAYS_INLINE +#undef MLD_CET_ENDBR +#undef MLD_CT_TESTING_DECLASSIFY +#undef MLD_CT_TESTING_SECRET +#undef MLD_DEFAULT_ALIGN +#undef MLD_HAVE_INLINE_ASM +#undef MLD_INLINE +#undef MLD_MUST_CHECK_RETURN_VALUE +#undef MLD_RESTRICT +#undef MLD_STATIC_TESTABLE +#undef MLD_SYSV_ABI +#undef MLD_SYSV_ABI_SUPPORTED +#undef MLD_SYS_AARCH64 +#undef MLD_SYS_AARCH64_EB +#undef MLD_SYS_APPLE +#undef MLD_SYS_ARMV81M_MVE +#undef MLD_SYS_BIG_ENDIAN +#undef MLD_SYS_H +#undef MLD_SYS_LINUX +#undef MLD_SYS_LITTLE_ENDIAN +#undef MLD_SYS_PPC64LE +#undef MLD_SYS_RISCV32 +#undef MLD_SYS_RISCV64 +#undef MLD_SYS_RISCV64_RVV +#undef MLD_SYS_WINDOWS +#undef MLD_SYS_X86_64 +#undef MLD_SYS_X86_64_AVX2 +/* mldsa/src/cbmc.h */ +#undef MLD_CBMC_H +#undef __contract__ +#undef __loop__ + +#if !defined(MLD_CONFIG_FIPS202_CUSTOM_HEADER) +/* + * Undefine macros from FIPS-202 files + */ +/* mldsa/src/fips202/fips202.h */ +#undef MLD_FIPS202_FIPS202_H +#undef MLD_KECCAK_LANES +#undef SHA3_256_HASHBYTES +#undef SHA3_256_RATE +#undef SHA3_512_HASHBYTES +#undef SHA3_512_RATE +#undef SHAKE128_RATE +#undef SHAKE256_RATE +#undef mld_shake128_absorb +#undef mld_shake128_finalize +#undef mld_shake128_init +#undef mld_shake128_release +#undef mld_shake128_squeeze +#undef mld_shake256 +#undef mld_shake256_absorb +#undef mld_shake256_finalize +#undef mld_shake256_init +#undef mld_shake256_release +#undef mld_shake256_squeeze +/* mldsa/src/fips202/fips202x4.h */ +#undef MLD_FIPS202_FIPS202X4_H +#undef mld_shake128x4_absorb_once +#undef mld_shake128x4_init +#undef mld_shake128x4_release +#undef mld_shake128x4_squeezeblocks +#undef mld_shake256x4_absorb_once +#undef mld_shake256x4_init +#undef mld_shake256x4_release +#undef mld_shake256x4_squeezeblocks +/* mldsa/src/fips202/keccakf1600.h */ +#undef MLD_FIPS202_KECCAKF1600_H +#undef MLD_KECCAK_LANES +#undef MLD_KECCAK_WAY +#undef mld_keccakf1600_extract_bytes +#undef mld_keccakf1600_permute +#undef mld_keccakf1600_xor_bytes +#undef mld_keccakf1600x4_extract_bytes +#undef mld_keccakf1600x4_permute +#undef mld_keccakf1600x4_xor_bytes +#endif /* !MLD_CONFIG_FIPS202_CUSTOM_HEADER */ + +#if defined(MLD_CONFIG_USE_NATIVE_BACKEND_FIPS202) +/* mldsa/src/fips202/native/api.h */ +#undef MLD_FIPS202_NATIVE_API_H +#undef MLD_NATIVE_FUNC_FALLBACK +#undef MLD_NATIVE_FUNC_SUCCESS +/* mldsa/src/fips202/native/auto.h */ +#undef MLD_FIPS202_NATIVE_AUTO_H +#if defined(MLD_SYS_AARCH64) +/* + * Undefine macros from native code (FIPS202, AArch64) + */ +/* mldsa/src/fips202/native/aarch64/auto.h */ +#undef MLD_FIPS202_NATIVE_AARCH64_AUTO_H +/* mldsa/src/fips202/native/aarch64/src/fips202_native_aarch64.h */ +#undef MLD_FIPS202_NATIVE_AARCH64_SRC_FIPS202_NATIVE_AARCH64_H +#undef mld_keccak_f1600_x1_scalar_aarch64_asm +#undef mld_keccak_f1600_x1_v84a_aarch64_asm +#undef mld_keccak_f1600_x2_v84a_aarch64_asm +#undef mld_keccak_f1600_x4_v8a_scalar_hybrid_aarch64_asm +#undef mld_keccak_f1600_x4_v8a_v84a_scalar_hybrid_aarch64_asm +#undef mld_keccakf1600_round_constants +/* mldsa/src/fips202/native/aarch64/x1_scalar.h */ +#undef MLD_FIPS202_AARCH64_NEED_X1_SCALAR +#undef MLD_FIPS202_NATIVE_AARCH64_X1_SCALAR_H +#undef MLD_USE_NATIVE_FIPS202_X1 +/* mldsa/src/fips202/native/aarch64/x1_v84a.h */ +#undef MLD_FIPS202_AARCH64_NEED_X1_V84A +#undef MLD_FIPS202_NATIVE_AARCH64_X1_V84A_H +#undef MLD_USE_NATIVE_FIPS202_X1 +/* mldsa/src/fips202/native/aarch64/x2_v84a.h */ +#undef MLD_FIPS202_AARCH64_NEED_X2_V84A +#undef MLD_FIPS202_NATIVE_AARCH64_X2_V84A_H +#undef MLD_USE_NATIVE_FIPS202_X4 +/* mldsa/src/fips202/native/aarch64/x4_v8a_scalar.h */ +#undef MLD_FIPS202_AARCH64_NEED_X4_V8A_SCALAR_HYBRID +#undef MLD_FIPS202_NATIVE_AARCH64_X4_V8A_SCALAR_H +#undef MLD_USE_NATIVE_FIPS202_X4 +/* mldsa/src/fips202/native/aarch64/x4_v8a_v84a_scalar.h */ +#undef MLD_FIPS202_AARCH64_NEED_X4_V8A_V84A_SCALAR_HYBRID +#undef MLD_FIPS202_NATIVE_AARCH64_X4_V8A_V84A_SCALAR_H +#undef MLD_USE_NATIVE_FIPS202_X4 +#endif /* MLD_SYS_AARCH64 */ +#if defined(MLD_SYS_X86_64) +/* + * Undefine macros from native code (FIPS202, x86_64) + */ +/* mldsa/src/fips202/native/x86_64/keccak_f1600_x4_avx2.h */ +#undef MLD_FIPS202_NATIVE_X86_64_KECCAK_F1600_X4_AVX2_H +#undef MLD_FIPS202_X86_64_NEED_X4_AVX2 +#undef MLD_USE_NATIVE_FIPS202_X4 +/* mldsa/src/fips202/native/x86_64/src/fips202_native_x86_64.h */ +#undef MLD_FIPS202_NATIVE_X86_64_SRC_FIPS202_NATIVE_X86_64_H +#undef mld_keccak_f1600_x4_avx2_asm +#undef mld_keccak_rho56 +#undef mld_keccak_rho8 +#undef mld_keccakf1600_round_constants +#endif /* MLD_SYS_X86_64 */ +#if defined(MLD_SYS_ARMV81M_MVE) +/* + * Undefine macros from native code (FIPS202, Armv8.1-M) + */ +/* mldsa/src/fips202/native/armv81m/mve.h */ +#undef MLD_FIPS202_ARMV81M_NEED_X4 +#undef MLD_FIPS202_NATIVE_ARMV81M +#undef MLD_FIPS202_NATIVE_ARMV81M_MVE_H +#undef MLD_USE_NATIVE_FIPS202_X4 +#undef MLD_USE_NATIVE_FIPS202_X4_EXTRACT_BYTES +#undef MLD_USE_NATIVE_FIPS202_X4_XOR_BYTES +#undef mld_keccak_f1600_x4_native_impl +/* mldsa/src/fips202/native/armv81m/src/fips202_native_armv81m.h */ +#undef MLD_FIPS202_NATIVE_ARMV81M_SRC_FIPS202_NATIVE_ARMV81M_H +#undef mld_keccak_f1600_x4_mve_asm +#undef mld_keccak_f1600_x4_state_extract_bytes_asm +#undef mld_keccak_f1600_x4_state_xor_bytes_asm +#undef mld_keccakf1600_round_constants +#endif /* MLD_SYS_ARMV81M_MVE */ +#endif /* MLD_CONFIG_USE_NATIVE_BACKEND_FIPS202 */ +#if defined(MLD_CONFIG_USE_NATIVE_BACKEND_ARITH) +/* mldsa/src/native/api.h */ +#undef MLD_FQMUL_BOUND +#undef MLD_INTT_BOUND +#undef MLD_NATIVE_API_H +#undef MLD_NATIVE_FUNC_FALLBACK +#undef MLD_NATIVE_FUNC_SUCCESS +#undef MLD_NTT_BOUND +#undef MLD_REDUCE32_RANGE_MAX +/* mldsa/src/native/meta.h */ +#undef MLD_NATIVE_META_H +#if defined(MLD_SYS_AARCH64) +/* + * Undefine macros from native code (Arith, AArch64) + */ +/* mldsa/src/native/aarch64/meta.h */ +#undef MLD_ARITH_BACKEND_AARCH64 +#undef MLD_NATIVE_AARCH64_META_H +#undef MLD_USE_NATIVE_INTT +#undef MLD_USE_NATIVE_NTT +#undef MLD_USE_NATIVE_POINTWISE_MONTGOMERY +#undef MLD_USE_NATIVE_POLYVECL_POINTWISE_ACC_MONTGOMERY_L4 +#undef MLD_USE_NATIVE_POLYVECL_POINTWISE_ACC_MONTGOMERY_L5 +#undef MLD_USE_NATIVE_POLYVECL_POINTWISE_ACC_MONTGOMERY_L7 +#undef MLD_USE_NATIVE_POLYZ_UNPACK_17 +#undef MLD_USE_NATIVE_POLYZ_UNPACK_19 +#undef MLD_USE_NATIVE_POLY_CADDQ +#undef MLD_USE_NATIVE_POLY_CHKNORM +#undef MLD_USE_NATIVE_POLY_DECOMPOSE_32 +#undef MLD_USE_NATIVE_POLY_DECOMPOSE_88 +#undef MLD_USE_NATIVE_POLY_USE_HINT_32 +#undef MLD_USE_NATIVE_POLY_USE_HINT_88 +#undef MLD_USE_NATIVE_REJ_UNIFORM +#undef MLD_USE_NATIVE_REJ_UNIFORM_ETA2 +#undef MLD_USE_NATIVE_REJ_UNIFORM_ETA4 +/* mldsa/src/native/aarch64/src/arith_native_aarch64.h */ +#undef MLD_AARCH64_REJ_UNIFORM_ETA2_BUFLEN +#undef MLD_AARCH64_REJ_UNIFORM_ETA4_BUFLEN +#undef MLD_NATIVE_AARCH64_SRC_ARITH_NATIVE_AARCH64_H +#undef mld_aarch64_intt_zetas_layer123456 +#undef mld_aarch64_intt_zetas_layer78 +#undef mld_aarch64_ntt_zetas_layer123456 +#undef mld_aarch64_ntt_zetas_layer78 +#undef mld_intt_aarch64_asm +#undef mld_ntt_aarch64_asm +#undef mld_poly_caddq_aarch64_asm +#undef mld_poly_chknorm_aarch64_asm +#undef mld_poly_decompose_32_aarch64_asm +#undef mld_poly_decompose_88_aarch64_asm +#undef mld_poly_pointwise_montgomery_aarch64_asm +#undef mld_poly_use_hint_32_aarch64_asm +#undef mld_poly_use_hint_88_aarch64_asm +#undef mld_polyvecl_pointwise_acc_montgomery_l4_aarch64_asm +#undef mld_polyvecl_pointwise_acc_montgomery_l5_aarch64_asm +#undef mld_polyvecl_pointwise_acc_montgomery_l7_aarch64_asm +#undef mld_polyz_unpack_17_aarch64_asm +#undef mld_polyz_unpack_17_indices +#undef mld_polyz_unpack_19_aarch64_asm +#undef mld_polyz_unpack_19_indices +#undef mld_rej_uniform_aarch64_asm +#undef mld_rej_uniform_eta2_aarch64_asm +#undef mld_rej_uniform_eta4_aarch64_asm +#undef mld_rej_uniform_eta_table +#undef mld_rej_uniform_table +#endif /* MLD_SYS_AARCH64 */ +#if defined(MLD_SYS_X86_64) +/* + * Undefine macros from native code (Arith, X86_64) + */ +/* mldsa/src/native/x86_64/meta.h */ +#undef MLD_ARITH_BACKEND_X86_64_DEFAULT +#undef MLD_NATIVE_X86_64_META_H +#undef MLD_USE_NATIVE_INTT +#undef MLD_USE_NATIVE_NTT +#undef MLD_USE_NATIVE_NTT_CUSTOM_ORDER +#undef MLD_USE_NATIVE_POINTWISE_MONTGOMERY +#undef MLD_USE_NATIVE_POLYVECL_POINTWISE_ACC_MONTGOMERY_L4 +#undef MLD_USE_NATIVE_POLYVECL_POINTWISE_ACC_MONTGOMERY_L5 +#undef MLD_USE_NATIVE_POLYVECL_POINTWISE_ACC_MONTGOMERY_L7 +#undef MLD_USE_NATIVE_POLYZ_UNPACK_17 +#undef MLD_USE_NATIVE_POLYZ_UNPACK_19 +#undef MLD_USE_NATIVE_POLY_CADDQ +#undef MLD_USE_NATIVE_POLY_CHKNORM +#undef MLD_USE_NATIVE_POLY_DECOMPOSE_32 +#undef MLD_USE_NATIVE_POLY_DECOMPOSE_88 +#undef MLD_USE_NATIVE_POLY_USE_HINT_32 +#undef MLD_USE_NATIVE_POLY_USE_HINT_88 +#undef MLD_USE_NATIVE_REJ_UNIFORM +#undef MLD_USE_NATIVE_REJ_UNIFORM_ETA2 +#undef MLD_USE_NATIVE_REJ_UNIFORM_ETA4 +/* mldsa/src/native/x86_64/src/arith_native_x86_64.h */ +#undef MLD_AVX2_REJ_UNIFORM_BUFLEN +#undef MLD_AVX2_REJ_UNIFORM_ETA2_BUFLEN +#undef MLD_AVX2_REJ_UNIFORM_ETA4_BUFLEN +#undef MLD_NATIVE_X86_64_SRC_ARITH_NATIVE_X86_64_H +#undef mld_invntt_avx2_asm +#undef mld_ntt_avx2_asm +#undef mld_nttunpack_avx2_asm +#undef mld_pointwise_acc_l4_avx2_asm +#undef mld_pointwise_acc_l5_avx2_asm +#undef mld_pointwise_acc_l7_avx2_asm +#undef mld_pointwise_avx2_asm +#undef mld_poly_caddq_avx2_asm +#undef mld_poly_chknorm_avx2_asm +#undef mld_poly_decompose_32_avx2_asm +#undef mld_poly_decompose_88_avx2_asm +#undef mld_poly_use_hint_32_avx2_asm +#undef mld_poly_use_hint_88_avx2_asm +#undef mld_polyz_unpack_17_avx2_asm +#undef mld_polyz_unpack_19_avx2_asm +#undef mld_rej_uniform_avx2 +#undef mld_rej_uniform_eta2_avx2 +#undef mld_rej_uniform_eta4_avx2 +#undef mld_rej_uniform_table +/* mldsa/src/native/x86_64/src/consts.h */ +#undef MLD_AVX2_BACKEND_DATA_OFFSET_8XDIV +#undef MLD_AVX2_BACKEND_DATA_OFFSET_8XDIV_QINV +#undef MLD_AVX2_BACKEND_DATA_OFFSET_8XQ +#undef MLD_AVX2_BACKEND_DATA_OFFSET_8XQINV +#undef MLD_AVX2_BACKEND_DATA_OFFSET_ZETAS +#undef MLD_AVX2_BACKEND_DATA_OFFSET_ZETAS_QINV +#undef MLD_NATIVE_X86_64_SRC_CONSTS_H +#undef mld_qdata +#endif /* MLD_SYS_X86_64 */ +#endif /* MLD_CONFIG_USE_NATIVE_BACKEND_ARITH */ +#endif /* !MLD_CONFIG_MONOBUILD_KEEP_SHARED_HEADERS */ diff --git a/mldsa_native/mldsa_native.h b/mldsa_native/mldsa_native.h new file mode 100644 index 0000000..3f64217 --- /dev/null +++ b/mldsa_native/mldsa_native.h @@ -0,0 +1,867 @@ +/* + * Copyright (c) The mldsa-native project authors + * SPDX-License-Identifier: Apache-2.0 OR ISC OR MIT + */ + +/* References + * ========== + * + * - [FIPS204] + * FIPS 204 Module-Lattice-Based Digital Signature Standard + * National Institute of Standards and Technology + * https://csrc.nist.gov/pubs/fips/204/final + */ + +#ifndef MLD_H +#define MLD_H + +/* + * Public API for mldsa-native + * + * This header defines the public API of a single build of mldsa-native. + * + * Make sure the configuration file is in the include path + * (this is "mldsa_native_config.h" by default, or MLD_CONFIG_FILE if defined). + * + * # Multi-level builds + * + * This header specifies a build of mldsa-native for a fixed security level. + * If you need multiple security levels, leave the security level unspecified + * in the configuration file and include this header multiple times, setting + * MLD_CONFIG_PARAMETER_SET accordingly for each, and #undef'ing the MLD_H + * guard to allow multiple inclusions. + */ + +/******************************* Key sizes ************************************/ + +/* Sizes of cryptographic material, per parameter set */ +/* See mldsa/src/params.h for the arithmetic expressions giving rise to these */ +/* check-magic: off */ +#define MLDSA44_SECRETKEYBYTES 2560 +#define MLDSA44_PUBLICKEYBYTES 1312 +#define MLDSA44_BYTES 2420 + +#define MLDSA65_SECRETKEYBYTES 4032 +#define MLDSA65_PUBLICKEYBYTES 1952 +#define MLDSA65_BYTES 3309 + +#define MLDSA87_SECRETKEYBYTES 4896 +#define MLDSA87_PUBLICKEYBYTES 2592 +#define MLDSA87_BYTES 4627 +/* check-magic: on */ + +/* Size of seed and randomness in bytes (level-independent) */ +#define MLDSA_SEEDBYTES 32 +#define MLDSA44_SEEDBYTES MLDSA_SEEDBYTES +#define MLDSA65_SEEDBYTES MLDSA_SEEDBYTES +#define MLDSA87_SEEDBYTES MLDSA_SEEDBYTES + +/* Size of CRH output in bytes (level-independent) */ +#define MLDSA_CRHBYTES 64 +#define MLDSA44_CRHBYTES MLDSA_CRHBYTES +#define MLDSA65_CRHBYTES MLDSA_CRHBYTES +#define MLDSA87_CRHBYTES MLDSA_CRHBYTES + +/* Size of randomness for signing in bytes (level-independent) */ +#define MLDSA_RNDBYTES 32 +#define MLDSA44_RNDBYTES MLDSA_RNDBYTES +#define MLDSA65_RNDBYTES MLDSA_RNDBYTES +#define MLDSA87_RNDBYTES MLDSA_RNDBYTES + +/* Sizes of cryptographic material, as a function of LVL=44,65,87 */ +#define MLDSA_SECRETKEYBYTES_(LVL) MLDSA##LVL##_SECRETKEYBYTES +#define MLDSA_PUBLICKEYBYTES_(LVL) MLDSA##LVL##_PUBLICKEYBYTES +#define MLDSA_BYTES_(LVL) MLDSA##LVL##_BYTES +#define MLDSA_SECRETKEYBYTES(LVL) MLDSA_SECRETKEYBYTES_(LVL) +#define MLDSA_PUBLICKEYBYTES(LVL) MLDSA_PUBLICKEYBYTES_(LVL) +#define MLDSA_BYTES(LVL) MLDSA_BYTES_(LVL) + +/****************************** Error codes ***********************************/ + +/* Generic failure condition */ +#define MLD_ERR_FAIL (-1) +/* An allocation failed. This can only happen if MLD_CONFIG_CUSTOM_ALLOC_FREE + * is defined and the provided MLD_CUSTOM_ALLOC can fail. */ +#define MLD_ERR_OUT_OF_MEMORY (-2) +/* An rng failure occured. Might be due to insufficient entropy or + * system misconfiguration. */ +#define MLD_ERR_RNG_FAIL (-3) +/* The signing rejection-sampling loop exceeded + * MLD_CONFIG_MAX_SIGNING_ATTEMPTS iterations without producing a valid + * signature. With a FIPS 204 Appendix C compliant bound (>= 814) this + * has probability < 2^-256. */ +#define MLD_ERR_SIGN_ATTEMPTS_EXHAUSTED (-4) + +/********************* Namespacing and Qualifiers *****************************/ + +#define MLD_API_CONCAT_(x, y) x##y +#define MLD_API_CONCAT(x, y) MLD_API_CONCAT_(x, y) +#define MLD_API_CONCAT_UNDERSCORE(x, y) MLD_API_CONCAT(MLD_API_CONCAT(x, _), y) + +/* You need to make sure the config file is in the include path. */ +#if defined(MLD_CONFIG_FILE) +#include MLD_CONFIG_FILE +#else +#include "mldsa_native_config.h" +#endif + +/* Namespace prefix for the public API symbols. For multi-level builds, the + * parameter set is appended to disambiguate the security levels. */ +#if defined(MLD_CONFIG_MULTILEVEL_BUILD) +#define MLD_API_NAMESPACE_PREFIX \ + MLD_API_CONCAT(MLD_CONFIG_NAMESPACE_PREFIX, MLD_CONFIG_PARAMETER_SET) +#else +#define MLD_API_NAMESPACE_PREFIX MLD_CONFIG_NAMESPACE_PREFIX +#endif + +#define MLD_API_NAMESPACE(sym) \ + MLD_API_CONCAT_UNDERSCORE(MLD_API_NAMESPACE_PREFIX, sym) + +#if defined(__GNUC__) || defined(__clang__) +#define MLD_API_MUST_CHECK_RETURN_VALUE __attribute__((warn_unused_result)) +#else +#define MLD_API_MUST_CHECK_RETURN_VALUE +#endif + +#if defined(MLD_CONFIG_EXTERNAL_API_QUALIFIER) +#define MLD_API_QUALIFIER MLD_CONFIG_EXTERNAL_API_QUALIFIER +#else +#define MLD_API_QUALIFIER +#endif + +/****************************** Function API **********************************/ + +#if !defined(MLD_CONFIG_CONSTANTS_ONLY) + +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +#if !defined(MLD_CONFIG_NO_KEYPAIR_API) +/** + * Generate a public-private key pair from a seed. + * + * When MLD_CONFIG_KEYGEN_PCT is set, performs a Pairwise Consistency Test + * (PCT) as required by FIPS 140-3 IG. + * + * @warning The seed must be generated by a cryptographically secure random + * number generator. + * + * @spec{Implements @[FIPS204, Algorithm 6, ML-DSA.KeyGen_internal].} + * + * @param[out] pk Output public key. + * @param[out] sk Output private key. + * @param[in] seed Input random seed. + * @param context Application context. Only present when + * MLD_CONFIG_CONTEXT_PARAMETER is defined; type set by + * MLD_CONFIG_CONTEXT_PARAMETER_TYPE. + * + * @retval 0 Success. + * @retval MLD_ERR_OUT_OF_MEMORY MLD_CONFIG_CUSTOM_ALLOC_FREE was + * used and an allocation via + * MLD_CUSTOM_ALLOC returned NULL. + * @retval MLD_ERR_RNG_FAIL Random number generation failed. + * @retval MLD_ERR_SIGN_ATTEMPTS_EXHAUSTED The PCT's signing step exhausted + * MLD_CONFIG_MAX_SIGNING_ATTEMPTS + * iterations. Only possible when + * MLD_CONFIG_KEYGEN_PCT is enabled. + * @retval MLD_ERR_FAIL Other kinds of failure, including + * PCT failure if + * MLD_CONFIG_KEYGEN_PCT is enabled. + */ +MLD_API_QUALIFIER +MLD_API_MUST_CHECK_RETURN_VALUE +int MLD_API_NAMESPACE(keypair_internal)( + uint8_t pk[MLDSA_PUBLICKEYBYTES(MLD_CONFIG_PARAMETER_SET)], + uint8_t sk[MLDSA_SECRETKEYBYTES(MLD_CONFIG_PARAMETER_SET)], + const uint8_t seed[MLDSA_SEEDBYTES] +#ifdef MLD_CONFIG_CONTEXT_PARAMETER + , + MLD_CONFIG_CONTEXT_PARAMETER_TYPE context +#endif +); + +#if !defined(MLD_CONFIG_CORE_API_ONLY) +/** + * Generate a public-private key pair. + * + * When MLD_CONFIG_KEYGEN_PCT is set, performs a Pairwise Consistency Test + * (PCT) as required by FIPS 140-3 IG. + * + * @spec{Implements @[FIPS204, Algorithm 1, ML-DSA.KeyGen].} + * + * @param[out] pk Output public key. + * @param[out] sk Output private key. + * @param context Application context. Only present when + * MLD_CONFIG_CONTEXT_PARAMETER is defined; type set by + * MLD_CONFIG_CONTEXT_PARAMETER_TYPE. + * + * @retval 0 Success. + * @retval MLD_ERR_OUT_OF_MEMORY MLD_CONFIG_CUSTOM_ALLOC_FREE was + * used and an allocation via + * MLD_CUSTOM_ALLOC returned NULL. + * @retval MLD_ERR_RNG_FAIL Random number generation failed. + * @retval MLD_ERR_SIGN_ATTEMPTS_EXHAUSTED The PCT's signing step exhausted + * MLD_CONFIG_MAX_SIGNING_ATTEMPTS + * iterations. Only possible when + * MLD_CONFIG_KEYGEN_PCT is enabled. + * @retval MLD_ERR_FAIL MLD_CONFIG_KEYGEN_PCT is enabled and + * the PCT check failed. + */ +MLD_API_QUALIFIER +MLD_API_MUST_CHECK_RETURN_VALUE +int MLD_API_NAMESPACE(keypair)( + uint8_t pk[MLDSA_PUBLICKEYBYTES(MLD_CONFIG_PARAMETER_SET)], + uint8_t sk[MLDSA_SECRETKEYBYTES(MLD_CONFIG_PARAMETER_SET)] +#ifdef MLD_CONFIG_CONTEXT_PARAMETER + , + MLD_CONFIG_CONTEXT_PARAMETER_TYPE context +#endif +); +#endif /* !MLD_CONFIG_CORE_API_ONLY */ +#endif /* !MLD_CONFIG_NO_KEYPAIR_API */ + +#if !defined(MLD_CONFIG_NO_SIGN_API) +/** + * Compute signature using a caller-supplied random seed and prefix. + * + * If the returned value is non-zero, then the values of *sig and *siglen + * should not be referenced. + * + * @spec{Implements @[FIPS204, Algorithm 7, ML-DSA.Sign_internal].} + * + * @param[out] sig Output signature. + * @param[out] siglen Pointer to output length of signature. + * @param[in] m Pointer to message to be signed (when + * externalmu == 0), or to a precomputed + * message representative mu (when externalmu != 0). + * @param mlen Length of m. Must equal MLDSA_CRHBYTES when + * externalmu != 0. + * @param[in] pre Pointer to prefix string. Ignored when + * externalmu != 0. + * @param prelen Length of prefix string. Ignored when + * externalmu != 0. + * @param[in] rnd Random seed. + * @param[in] sk Bit-packed secret key. + * @param externalmu 0: m/mlen is the raw message; mu = H(tr, pre, m) is + * computed internally. + * non-zero: m points to a precomputed mu of + * MLDSA_CRHBYTES bytes; pre/prelen unused. + * @param context Application context. Only present when + * MLD_CONFIG_CONTEXT_PARAMETER is defined; type set by + * MLD_CONFIG_CONTEXT_PARAMETER_TYPE. + * + * @retval 0 Success. + * @retval MLD_ERR_OUT_OF_MEMORY MLD_CONFIG_CUSTOM_ALLOC_FREE was + * used and an allocation via + * MLD_CUSTOM_ALLOC returned NULL. + * @retval MLD_ERR_SIGN_ATTEMPTS_EXHAUSTED The rejection-sampling loop exceeded + * MLD_CONFIG_MAX_SIGNING_ATTEMPTS + * iterations. + * @retval MLD_ERR_FAIL Other kinds of failure. + */ +MLD_API_QUALIFIER +MLD_API_MUST_CHECK_RETURN_VALUE +int MLD_API_NAMESPACE(signature_internal)( + uint8_t sig[MLDSA_BYTES(MLD_CONFIG_PARAMETER_SET)], size_t *siglen, + const uint8_t *m, size_t mlen, const uint8_t *pre, size_t prelen, + const uint8_t rnd[MLDSA_RNDBYTES], + const uint8_t sk[MLDSA_SECRETKEYBYTES(MLD_CONFIG_PARAMETER_SET)], + int externalmu +#ifdef MLD_CONFIG_CONTEXT_PARAMETER + , + MLD_CONFIG_CONTEXT_PARAMETER_TYPE context +#endif +); + +#if !defined(MLD_CONFIG_CORE_API_ONLY) +/** + * Compute signature. This function implements the randomized variant of + * ML-DSA. If you require the deterministic variant, use + * crypto_sign_signature_internal directly. + * + * @spec{Implements @[FIPS204, Algorithm 2, ML-DSA.Sign].} + * + * @param[out] sig Output signature. + * @param[out] siglen Pointer to output length of signature. + * @param[in] m Pointer to message to be signed. + * @param mlen Length of message. + * @param[in] ctx Pointer to context string. May be NULL if ctxlen == 0. + * @param ctxlen Length of context string. Should be <= 255. + * @param[in] sk Bit-packed secret key. + * @param context Application context. Only present when + * MLD_CONFIG_CONTEXT_PARAMETER is defined; type set by + * MLD_CONFIG_CONTEXT_PARAMETER_TYPE. + * + * @retval 0 Success. + * @retval MLD_ERR_OUT_OF_MEMORY MLD_CONFIG_CUSTOM_ALLOC_FREE was + * used and an allocation via + * MLD_CUSTOM_ALLOC returned NULL. + * @retval MLD_ERR_RNG_FAIL Random number generation failed. + * @retval MLD_ERR_SIGN_ATTEMPTS_EXHAUSTED The rejection-sampling loop exceeded + * MLD_CONFIG_MAX_SIGNING_ATTEMPTS + * iterations. + * @retval MLD_ERR_FAIL Other kinds of failure. + */ +MLD_API_QUALIFIER +MLD_API_MUST_CHECK_RETURN_VALUE +int MLD_API_NAMESPACE(signature)( + uint8_t sig[MLDSA_BYTES(MLD_CONFIG_PARAMETER_SET)], size_t *siglen, + const uint8_t *m, size_t mlen, const uint8_t *ctx, size_t ctxlen, + const uint8_t sk[MLDSA_SECRETKEYBYTES(MLD_CONFIG_PARAMETER_SET)] +#ifdef MLD_CONFIG_CONTEXT_PARAMETER + , + MLD_CONFIG_CONTEXT_PARAMETER_TYPE context +#endif +); + +/** + * Compute signature in "external mu" mode: the caller has already computed + * the message representative mu = SHAKE256(tr || M', 64), where + * tr = SHAKE256(pk, 64) and M' is the FIPS 204 formatted message (e.g. + * 0x00 || ctxlen || ctx || msg for pure ML-DSA). This is useful when the + * message is large or streamed and cannot be held in memory. + * + * @spec{Implements @[FIPS204, Algorithm 2, ML-DSA.Sign external mu variant].} + * + * @param[out] sig Output signature. + * @param[out] siglen Pointer to output length of signature. + * @param[in] mu Precomputed message representative. + * @param[in] sk Bit-packed secret key. + * @param context Application context. Only present when + * MLD_CONFIG_CONTEXT_PARAMETER is defined; type set by + * MLD_CONFIG_CONTEXT_PARAMETER_TYPE. + * + * @retval 0 Success. + * @retval MLD_ERR_OUT_OF_MEMORY MLD_CONFIG_CUSTOM_ALLOC_FREE was + * used and an allocation via + * MLD_CUSTOM_ALLOC returned NULL. + * @retval MLD_ERR_RNG_FAIL Random number generation failed. + * @retval MLD_ERR_SIGN_ATTEMPTS_EXHAUSTED The rejection-sampling loop exceeded + * MLD_CONFIG_MAX_SIGNING_ATTEMPTS + * iterations. + * @retval MLD_ERR_FAIL Other kinds of failure. + */ +MLD_API_QUALIFIER +MLD_API_MUST_CHECK_RETURN_VALUE +int MLD_API_NAMESPACE(signature_extmu)( + uint8_t sig[MLDSA_BYTES(MLD_CONFIG_PARAMETER_SET)], size_t *siglen, + const uint8_t mu[MLDSA_CRHBYTES], + const uint8_t sk[MLDSA_SECRETKEYBYTES(MLD_CONFIG_PARAMETER_SET)] +#ifdef MLD_CONFIG_CONTEXT_PARAMETER + , + MLD_CONFIG_CONTEXT_PARAMETER_TYPE context +#endif +); + +#endif /* !MLD_CONFIG_CORE_API_ONLY */ +#endif /* !MLD_CONFIG_NO_SIGN_API */ + +#if !defined(MLD_CONFIG_NO_VERIFY_API) +/** + * Verify signature. Internal API. + * + * @spec{Implements @[FIPS204, Algorithm 8, ML-DSA.Verify_internal].} + * + * @param[in] sig Pointer to input signature. + * @param siglen Length of signature. + * @param[in] m Pointer to message (when externalmu == 0), or to a + * precomputed message representative mu (when + * externalmu != 0). + * @param mlen Length of m. Must equal MLDSA_CRHBYTES when + * externalmu != 0. + * @param[in] pre Pointer to prefix string. Ignored when externalmu != 0. + * @param prelen Length of prefix string. Ignored when externalmu != 0. + * @param[in] pk Bit-packed public key. + * @param externalmu 0: m/mlen is the raw message; mu = H(H(pk), pre, m) is + * computed internally. + * non-zero: m points to a precomputed mu of + * MLDSA_CRHBYTES bytes; pre/prelen unused. + * @param context Application context. Only present when + * MLD_CONFIG_CONTEXT_PARAMETER is defined; type set by + * MLD_CONFIG_CONTEXT_PARAMETER_TYPE. + * + * @retval 0 Success. + * @retval MLD_ERR_OUT_OF_MEMORY MLD_CONFIG_CUSTOM_ALLOC_FREE was used and an + * allocation via MLD_CUSTOM_ALLOC returned NULL. + * @retval MLD_ERR_FAIL Signature verification failed. + */ +MLD_API_QUALIFIER +MLD_API_MUST_CHECK_RETURN_VALUE +int MLD_API_NAMESPACE(verify_internal)( + const uint8_t *sig, size_t siglen, const uint8_t *m, size_t mlen, + const uint8_t *pre, size_t prelen, + const uint8_t pk[MLDSA_PUBLICKEYBYTES(MLD_CONFIG_PARAMETER_SET)], + int externalmu +#ifdef MLD_CONFIG_CONTEXT_PARAMETER + , + MLD_CONFIG_CONTEXT_PARAMETER_TYPE context +#endif +); + +#if !defined(MLD_CONFIG_CORE_API_ONLY) +/** + * Verify signature. + * + * @spec{Implements @[FIPS204, Algorithm 3, ML-DSA.Verify].} + * + * @param[in] sig Pointer to input signature. + * @param siglen Length of signature. + * @param[in] m Pointer to message. + * @param mlen Length of message. + * @param[in] ctx Pointer to context string. May be NULL if ctxlen == 0. + * @param ctxlen Length of context string. + * @param[in] pk Bit-packed public key. + * @param context Application context. Only present when + * MLD_CONFIG_CONTEXT_PARAMETER is defined; type set by + * MLD_CONFIG_CONTEXT_PARAMETER_TYPE. + * + * @retval 0 Success. + * @retval MLD_ERR_OUT_OF_MEMORY MLD_CONFIG_CUSTOM_ALLOC_FREE was used and an + * allocation via MLD_CUSTOM_ALLOC returned NULL. + * @retval MLD_ERR_FAIL Signature verification failed. + */ +MLD_API_QUALIFIER +MLD_API_MUST_CHECK_RETURN_VALUE +int MLD_API_NAMESPACE(verify)( + const uint8_t *sig, size_t siglen, const uint8_t *m, size_t mlen, + const uint8_t *ctx, size_t ctxlen, + const uint8_t pk[MLDSA_PUBLICKEYBYTES(MLD_CONFIG_PARAMETER_SET)] +#ifdef MLD_CONFIG_CONTEXT_PARAMETER + , + MLD_CONFIG_CONTEXT_PARAMETER_TYPE context +#endif +); + +/** + * Verify signature in "external mu" mode: the caller has already computed + * the message representative mu = SHAKE256(tr || M', 64), where + * tr = SHAKE256(pk, 64) and M' is the FIPS 204 formatted message (e.g. + * 0x00 || ctxlen || ctx || msg for pure ML-DSA). The same mu must have + * been used at signing time. + * + * @spec{Implements @[FIPS204, Algorithm 3, ML-DSA.Verify external mu variant].} + * + * @param[in] sig Pointer to input signature. + * @param siglen Length of signature. + * @param[in] mu Precomputed message representative. + * @param[in] pk Bit-packed public key. + * @param context Application context. Only present when + * MLD_CONFIG_CONTEXT_PARAMETER is defined; type set by + * MLD_CONFIG_CONTEXT_PARAMETER_TYPE. + * + * @retval 0 Success. + * @retval MLD_ERR_OUT_OF_MEMORY MLD_CONFIG_CUSTOM_ALLOC_FREE was used and an + * allocation via MLD_CUSTOM_ALLOC returned NULL. + * @retval MLD_ERR_FAIL Signature verification failed. + */ +MLD_API_QUALIFIER +MLD_API_MUST_CHECK_RETURN_VALUE +int MLD_API_NAMESPACE(verify_extmu)( + const uint8_t *sig, size_t siglen, const uint8_t mu[MLDSA_CRHBYTES], + const uint8_t pk[MLDSA_PUBLICKEYBYTES(MLD_CONFIG_PARAMETER_SET)] +#ifdef MLD_CONFIG_CONTEXT_PARAMETER + , + MLD_CONFIG_CONTEXT_PARAMETER_TYPE context +#endif +); + +#endif /* !MLD_CONFIG_CORE_API_ONLY */ +#endif /* !MLD_CONFIG_NO_VERIFY_API */ + +/* Hash algorithm constants for domain separation */ +#define MLD_PREHASH_NONE 0 +#define MLD_PREHASH_SHA2_224 1 +#define MLD_PREHASH_SHA2_256 2 +#define MLD_PREHASH_SHA2_384 3 +#define MLD_PREHASH_SHA2_512 4 +#define MLD_PREHASH_SHA2_512_224 5 +#define MLD_PREHASH_SHA2_512_256 6 +#define MLD_PREHASH_SHA3_224 7 +#define MLD_PREHASH_SHA3_256 8 +#define MLD_PREHASH_SHA3_384 9 +#define MLD_PREHASH_SHA3_512 10 +#define MLD_PREHASH_SHAKE_128 11 +#define MLD_PREHASH_SHAKE_256 12 + +#if !defined(MLD_CONFIG_CORE_API_ONLY) +#if !defined(MLD_CONFIG_NO_SIGN_API) +/** + * Compute signature with pre-hashed message. + * + * @spec{Implements @[FIPS204, Algorithm 4, HashML-DSA.Sign].} + * + * Supported hash algorithm constants: + * MLD_PREHASH_SHA2_224, MLD_PREHASH_SHA2_256, MLD_PREHASH_SHA2_384, + * MLD_PREHASH_SHA2_512, MLD_PREHASH_SHA2_512_224, MLD_PREHASH_SHA2_512_256, + * MLD_PREHASH_SHA3_224, MLD_PREHASH_SHA3_256, MLD_PREHASH_SHA3_384, + * MLD_PREHASH_SHA3_512, MLD_PREHASH_SHAKE_128, MLD_PREHASH_SHAKE_256. + * + * @warning This is an unstable API that may change in the future. If you need + * a stable API use crypto_sign_signature_pre_hash_shake256. + * + * @param[out] sig Output signature. + * @param[out] siglen Pointer to output length of signature. + * @param[in] ph Pointer to pre-hashed message. + * @param phlen Length of pre-hashed message. + * @param[in] ctx Pointer to context string. + * @param ctxlen Length of context string. + * @param[in] rnd Random seed. + * @param[in] sk Bit-packed secret key. + * @param hashalg Hash algorithm constant (one of MLD_PREHASH_*). + * @param context Application context. Only present when + * MLD_CONFIG_CONTEXT_PARAMETER is defined; type set by + * MLD_CONFIG_CONTEXT_PARAMETER_TYPE. + * + * @retval 0 Success. + * @retval MLD_ERR_OUT_OF_MEMORY MLD_CONFIG_CUSTOM_ALLOC_FREE was + * used and an allocation via + * MLD_CUSTOM_ALLOC returned NULL. + * @retval MLD_ERR_SIGN_ATTEMPTS_EXHAUSTED The rejection-sampling loop exceeded + * MLD_CONFIG_MAX_SIGNING_ATTEMPTS + * iterations. + * @retval MLD_ERR_FAIL Other kinds of failure. + */ +MLD_API_QUALIFIER +MLD_API_MUST_CHECK_RETURN_VALUE +int MLD_API_NAMESPACE(signature_pre_hash_internal)( + uint8_t sig[MLDSA_BYTES(MLD_CONFIG_PARAMETER_SET)], size_t *siglen, + const uint8_t *ph, size_t phlen, const uint8_t *ctx, size_t ctxlen, + const uint8_t rnd[MLDSA_RNDBYTES], + const uint8_t sk[MLDSA_SECRETKEYBYTES(MLD_CONFIG_PARAMETER_SET)], + int hashalg +#ifdef MLD_CONFIG_CONTEXT_PARAMETER + , + MLD_CONFIG_CONTEXT_PARAMETER_TYPE context +#endif +); +#endif /* !MLD_CONFIG_NO_SIGN_API */ + +#if !defined(MLD_CONFIG_NO_VERIFY_API) +/** + * Verifies signature with pre-hashed message. + * + * @spec{Implements @[FIPS204, Algorithm 5, HashML-DSA.Verify].} + * + * Supported hash algorithm constants: + * MLD_PREHASH_SHA2_224, MLD_PREHASH_SHA2_256, MLD_PREHASH_SHA2_384, + * MLD_PREHASH_SHA2_512, MLD_PREHASH_SHA2_512_224, MLD_PREHASH_SHA2_512_256, + * MLD_PREHASH_SHA3_224, MLD_PREHASH_SHA3_256, MLD_PREHASH_SHA3_384, + * MLD_PREHASH_SHA3_512, MLD_PREHASH_SHAKE_128, MLD_PREHASH_SHAKE_256. + * + * @warning This is an unstable API that may change in the future. If you need + * a stable API use crypto_sign_verify_pre_hash_shake256. + * + * @param[in] sig Pointer to input signature. + * @param siglen Length of signature. + * @param[in] ph Pointer to pre-hashed message. + * @param phlen Length of pre-hashed message. + * @param[in] ctx Pointer to context string. + * @param ctxlen Length of context string. + * @param[in] pk Bit-packed public key. + * @param hashalg Hash algorithm constant (one of MLD_PREHASH_*). + * @param context Application context. Only present when + * MLD_CONFIG_CONTEXT_PARAMETER is defined; type set by + * MLD_CONFIG_CONTEXT_PARAMETER_TYPE. + * + * @retval 0 Success. + * @retval MLD_ERR_OUT_OF_MEMORY MLD_CONFIG_CUSTOM_ALLOC_FREE was used and an + * allocation via MLD_CUSTOM_ALLOC returned NULL. + * @retval MLD_ERR_FAIL Signature verification failed. + */ +MLD_API_QUALIFIER +MLD_API_MUST_CHECK_RETURN_VALUE +int MLD_API_NAMESPACE(verify_pre_hash_internal)( + const uint8_t *sig, size_t siglen, const uint8_t *ph, size_t phlen, + const uint8_t *ctx, size_t ctxlen, + const uint8_t pk[MLDSA_PUBLICKEYBYTES(MLD_CONFIG_PARAMETER_SET)], + int hashalg +#ifdef MLD_CONFIG_CONTEXT_PARAMETER + , + MLD_CONFIG_CONTEXT_PARAMETER_TYPE context +#endif +); +#endif /* !MLD_CONFIG_NO_VERIFY_API */ + +#if !defined(MLD_CONFIG_NO_SIGN_API) +/** + * Compute signature with pre-hashed message using SHAKE256. This function + * computes the SHAKE256 hash of the message internally. + * + * @spec{Implements @[FIPS204, Algorithm 4, HashML-DSA.Sign] with SHAKE256 as + * the pre-hash.} + * + * @param[out] sig Output signature. + * @param[out] siglen Pointer to output length of signature. + * @param[in] m Pointer to message to be hashed and signed. + * @param mlen Length of message. + * @param[in] ctx Pointer to context string. + * @param ctxlen Length of context string. + * @param[in] rnd Random seed. + * @param[in] sk Bit-packed secret key. + * @param context Application context. Only present when + * MLD_CONFIG_CONTEXT_PARAMETER is defined; type set by + * MLD_CONFIG_CONTEXT_PARAMETER_TYPE. + * + * @retval 0 Success. + * @retval MLD_ERR_OUT_OF_MEMORY MLD_CONFIG_CUSTOM_ALLOC_FREE was + * used and an allocation via + * MLD_CUSTOM_ALLOC returned NULL. + * @retval MLD_ERR_SIGN_ATTEMPTS_EXHAUSTED The rejection-sampling loop exceeded + * MLD_CONFIG_MAX_SIGNING_ATTEMPTS + * iterations. + * @retval MLD_ERR_FAIL Other kinds of failure. + */ +MLD_API_QUALIFIER +MLD_API_MUST_CHECK_RETURN_VALUE +int MLD_API_NAMESPACE(signature_pre_hash_shake256)( + uint8_t sig[MLDSA_BYTES(MLD_CONFIG_PARAMETER_SET)], size_t *siglen, + const uint8_t *m, size_t mlen, const uint8_t *ctx, size_t ctxlen, + const uint8_t rnd[MLDSA_RNDBYTES], + const uint8_t sk[MLDSA_SECRETKEYBYTES(MLD_CONFIG_PARAMETER_SET)] +#ifdef MLD_CONFIG_CONTEXT_PARAMETER + , + MLD_CONFIG_CONTEXT_PARAMETER_TYPE context +#endif +); +#endif /* !MLD_CONFIG_NO_SIGN_API */ + +#if !defined(MLD_CONFIG_NO_VERIFY_API) +/** + * Verify signature with pre-hashed message using SHAKE256. This function + * computes the SHAKE256 hash of the message internally. + * + * @spec{Implements @[FIPS204, Algorithm 5, HashML-DSA.Verify] with SHAKE256 as + * the pre-hash.} + * + * @param[in] sig Pointer to input signature. + * @param siglen Length of signature. + * @param[in] m Pointer to message to be hashed and verified. + * @param mlen Length of message. + * @param[in] ctx Pointer to context string. + * @param ctxlen Length of context string. + * @param[in] pk Bit-packed public key. + * @param context Application context. Only present when + * MLD_CONFIG_CONTEXT_PARAMETER is defined; type set by + * MLD_CONFIG_CONTEXT_PARAMETER_TYPE. + * + * @retval 0 Success. + * @retval MLD_ERR_OUT_OF_MEMORY MLD_CONFIG_CUSTOM_ALLOC_FREE was used and an + * allocation via MLD_CUSTOM_ALLOC returned NULL. + * @retval MLD_ERR_FAIL Signature verification failed. + */ +MLD_API_QUALIFIER +MLD_API_MUST_CHECK_RETURN_VALUE +int MLD_API_NAMESPACE(verify_pre_hash_shake256)( + const uint8_t *sig, size_t siglen, const uint8_t *m, size_t mlen, + const uint8_t *ctx, size_t ctxlen, + const uint8_t pk[MLDSA_PUBLICKEYBYTES(MLD_CONFIG_PARAMETER_SET)] +#ifdef MLD_CONFIG_CONTEXT_PARAMETER + , + MLD_CONFIG_CONTEXT_PARAMETER_TYPE context +#endif +); +#endif /* !MLD_CONFIG_NO_VERIFY_API */ +#endif /* !MLD_CONFIG_CORE_API_ONLY */ + +/* Maximum formatted domain separation message length */ +#define MLD_DOMAIN_SEPARATION_MAX_BYTES (2 + 255 + 11 + 64) + +#if !defined(MLD_CONFIG_CORE_API_ONLY) +/** + * Prepare domain separation prefix for ML-DSA signing. + * + * For pure ML-DSA (hashalg == MLD_PREHASH_NONE): + * Format: 0x00 || ctxlen (1 byte) || ctx. + * + * For HashML-DSA (hashalg != MLD_PREHASH_NONE): + * Format: 0x01 || ctxlen (1 byte) || ctx || oid (11 bytes) || ph. + * + * This function is useful for building incremental signing APIs. + * + * @spec{For HashML-DSA (hashalg != MLD_PREHASH_NONE), implements + * @[FIPS204, Algorithm 4, line 23]. For Pure ML-DSA + * (hashalg == MLD_PREHASH_NONE), implements + * ``` + * M' <- BytesToBits(IntegerToBytes(0, 1) + * || IntegerToBytes(|ctx|, 1) + * || ctx + * ``` + * which is part of @[FIPS204, Algorithm 2, ML-DSA.Sign, line 10] and + * @[FIPS204, Algorithm 3, ML-DSA.Verify, line 5].} + * + * @param[out] prefix Output domain separation prefix buffer. + * @param[in] ph Pointer to pre-hashed message (ignored for pure + * ML-DSA). + * @param phlen Length of pre-hashed message (ignored for pure ML-DSA). + * @param[in] ctx Pointer to context string (may be NULL). + * @param ctxlen Length of context string. + * @param hashalg Hash algorithm constant (MLD_PREHASH_NONE for pure + * ML-DSA, or MLD_PREHASH_* for HashML-DSA). + * + * @return The total length of the formatted prefix, or 0 on error. + */ +MLD_API_QUALIFIER +MLD_API_MUST_CHECK_RETURN_VALUE +size_t MLD_API_NAMESPACE(prepare_domain_separation_prefix)( + uint8_t prefix[MLD_DOMAIN_SEPARATION_MAX_BYTES], const uint8_t *ph, + size_t phlen, const uint8_t *ctx, size_t ctxlen, int hashalg); + +#if !defined(MLD_CONFIG_NO_KEYPAIR_API) +/** + * Perform basic validity checks on secret key, and derive public key. + * + * Referring to the decoding of the secret key `sk=(rho, K, tr, s1, s2, t0)` + * (cf. @[FIPS204, Algorithm 25, skDecode]), the following checks are + * performed: + * - Check that s1 and s2 have coefficients in [-MLDSA_ETA, MLDSA_ETA]. + * - Check that t0 and tr stored in sk match recomputed values. + * + * @note This function leaks whether the secret key is valid or invalid + * through its return value and timing. + * + * @param[out] pk Output public key. + * @param[in] sk Input secret key. + * @param context Application context. Only present when + * MLD_CONFIG_CONTEXT_PARAMETER is defined; type set by + * MLD_CONFIG_CONTEXT_PARAMETER_TYPE. + * + * @retval 0 Success. + * @retval MLD_ERR_OUT_OF_MEMORY MLD_CONFIG_CUSTOM_ALLOC_FREE was used and an + * allocation via MLD_CUSTOM_ALLOC returned NULL. + * @retval MLD_ERR_FAIL Secret key validation failed. + */ +MLD_API_QUALIFIER +MLD_API_MUST_CHECK_RETURN_VALUE +int MLD_API_NAMESPACE(pk_from_sk)( + uint8_t pk[MLDSA_PUBLICKEYBYTES(MLD_CONFIG_PARAMETER_SET)], + const uint8_t sk[MLDSA_SECRETKEYBYTES(MLD_CONFIG_PARAMETER_SET)] +#ifdef MLD_CONFIG_CONTEXT_PARAMETER + , + MLD_CONFIG_CONTEXT_PARAMETER_TYPE context +#endif +); +#endif /* !MLD_CONFIG_NO_KEYPAIR_API */ +#endif /* !MLD_CONFIG_CORE_API_ONLY */ + +#ifdef __cplusplus +} +#endif + +/****************************** SUPERCOP API *********************************/ + +#if !defined(MLD_CONFIG_NO_SUPERCOP) +/* Export API in SUPERCOP naming scheme CRYPTO_xxx / crypto_sign_xxx */ +#define CRYPTO_SECRETKEYBYTES MLDSA_SECRETKEYBYTES(MLD_CONFIG_PARAMETER_SET) +#define CRYPTO_PUBLICKEYBYTES MLDSA_PUBLICKEYBYTES(MLD_CONFIG_PARAMETER_SET) +#define CRYPTO_BYTES MLDSA_BYTES(MLD_CONFIG_PARAMETER_SET) + +#define crypto_sign_keypair MLD_API_NAMESPACE(keypair) +#define crypto_sign_signature MLD_API_NAMESPACE(signature) +#define crypto_sign_verify MLD_API_NAMESPACE(verify) + +#else /* !MLD_CONFIG_NO_SUPERCOP */ + +/* If the SUPERCOP API is not needed, we can undefine the various helper macros + * above. Otherwise, they are needed for lazy evaluation of crypto_sign_xxx. */ +#undef MLD_API_NAMESPACE_PREFIX +#undef MLD_API_CONCAT +#undef MLD_API_CONCAT_ +#undef MLD_API_CONCAT_UNDERSCORE +#undef MLD_API_NAMESPACE +#undef MLD_API_MUST_CHECK_RETURN_VALUE +#undef MLD_API_QUALIFIER + +#endif /* MLD_CONFIG_NO_SUPERCOP */ +#endif /* !MLD_CONFIG_CONSTANTS_ONLY */ + + +/***************************** Memory Usage **********************************/ + +/* + * By default mldsa-native performs all memory allocations on the stack. + * Alternatively, mldsa-native supports custom allocation of large structures + * through the `MLD_CONFIG_CUSTOM_ALLOC_FREE` configuration option. + * See mldsa_native_config.h for details. + * + * `MLD_TOTAL_ALLOC_{44,65,87}_{KEYPAIR,SIGN,VERIFY}` indicates the maximum + * (accumulative) allocation via MLD_ALLOC for each parameter set and operation. + * Note that some stack allocation remains even + * when using custom allocators, so these values are lower than total stack + * usage with the default stack-only allocation. + * + * These constants may be used to implement custom allocations using a + * fixed-sized buffer and a simple allocator (e.g., bump allocator). + */ +/* check-magic: off */ +#if !defined(MLD_CONFIG_REDUCE_RAM) +#define MLD_TOTAL_ALLOC_44_KEYPAIR_NO_PCT 26912 +#define MLD_TOTAL_ALLOC_44_KEYPAIR_PCT 48480 +#define MLD_TOTAL_ALLOC_44_PK_FROM_SK 28480 +#define MLD_TOTAL_ALLOC_44_SIGN 44704 +#define MLD_TOTAL_ALLOC_44_VERIFY 24448 +#define MLD_TOTAL_ALLOC_65_KEYPAIR_NO_PCT 44320 +#define MLD_TOTAL_ALLOC_65_KEYPAIR_PCT 74624 +#define MLD_TOTAL_ALLOC_65_PK_FROM_SK 46720 +#define MLD_TOTAL_ALLOC_65_SIGN 69312 +#define MLD_TOTAL_ALLOC_65_VERIFY 39872 +#define MLD_TOTAL_ALLOC_87_KEYPAIR_NO_PCT 75040 +#define MLD_TOTAL_ALLOC_87_KEYPAIR_PCT 115488 +#define MLD_TOTAL_ALLOC_87_PK_FROM_SK 78272 +#define MLD_TOTAL_ALLOC_87_SIGN 108224 +#define MLD_TOTAL_ALLOC_87_VERIFY 68800 +#else /* !MLD_CONFIG_REDUCE_RAM */ +#define MLD_TOTAL_ALLOC_44_KEYPAIR_NO_PCT 11584 +#define MLD_TOTAL_ALLOC_44_KEYPAIR_PCT 16896 +#define MLD_TOTAL_ALLOC_44_PK_FROM_SK 13152 +#define MLD_TOTAL_ALLOC_44_SIGN 13120 +#define MLD_TOTAL_ALLOC_44_VERIFY 9120 +#define MLD_TOTAL_ALLOC_65_KEYPAIR_NO_PCT 14656 +#define MLD_TOTAL_ALLOC_65_KEYPAIR_PCT 22560 +#define MLD_TOTAL_ALLOC_65_PK_FROM_SK 17056 +#define MLD_TOTAL_ALLOC_65_SIGN 17248 +#define MLD_TOTAL_ALLOC_65_VERIFY 10208 +#define MLD_TOTAL_ALLOC_87_KEYPAIR_NO_PCT 18752 +#define MLD_TOTAL_ALLOC_87_KEYPAIR_PCT 28608 +#define MLD_TOTAL_ALLOC_87_PK_FROM_SK 21984 +#define MLD_TOTAL_ALLOC_87_SIGN 21344 +#define MLD_TOTAL_ALLOC_87_VERIFY 12512 +#endif /* MLD_CONFIG_REDUCE_RAM */ +/* check-magic: on */ + +/* + * MLD_TOTAL_ALLOC_*_KEYPAIR adapts based on MLD_CONFIG_KEYGEN_PCT. + */ +#if defined(MLD_CONFIG_KEYGEN_PCT) +#define MLD_TOTAL_ALLOC_44_KEYPAIR MLD_TOTAL_ALLOC_44_KEYPAIR_PCT +#define MLD_TOTAL_ALLOC_65_KEYPAIR MLD_TOTAL_ALLOC_65_KEYPAIR_PCT +#define MLD_TOTAL_ALLOC_87_KEYPAIR MLD_TOTAL_ALLOC_87_KEYPAIR_PCT +#else +#define MLD_TOTAL_ALLOC_44_KEYPAIR MLD_TOTAL_ALLOC_44_KEYPAIR_NO_PCT +#define MLD_TOTAL_ALLOC_65_KEYPAIR MLD_TOTAL_ALLOC_65_KEYPAIR_NO_PCT +#define MLD_TOTAL_ALLOC_87_KEYPAIR MLD_TOTAL_ALLOC_87_KEYPAIR_NO_PCT +#endif + +#define MLD_MAX3_(a, b, c) \ + ((a) > (b) ? ((a) > (c) ? (a) : (c)) : ((b) > (c) ? (b) : (c))) +#define MLD_MAX4_(a, b, c, d) MLD_MAX3_((a), (b), MLD_MAX3_((c), (d), (d))) + +/* + * `MLD_TOTAL_ALLOC_{44,65,87}` is the maximum across standard API operations + * (keygen, sign, verify) for each parameter set. + */ +#define MLD_TOTAL_ALLOC_44 \ + MLD_MAX4_(MLD_TOTAL_ALLOC_44_KEYPAIR, MLD_TOTAL_ALLOC_44_PK_FROM_SK, \ + MLD_TOTAL_ALLOC_44_SIGN, MLD_TOTAL_ALLOC_44_VERIFY) +#define MLD_TOTAL_ALLOC_65 \ + MLD_MAX4_(MLD_TOTAL_ALLOC_65_KEYPAIR, MLD_TOTAL_ALLOC_65_PK_FROM_SK, \ + MLD_TOTAL_ALLOC_65_SIGN, MLD_TOTAL_ALLOC_65_VERIFY) +#define MLD_TOTAL_ALLOC_87 \ + MLD_MAX4_(MLD_TOTAL_ALLOC_87_KEYPAIR, MLD_TOTAL_ALLOC_87_PK_FROM_SK, \ + MLD_TOTAL_ALLOC_87_SIGN, MLD_TOTAL_ALLOC_87_VERIFY) + +#endif /* !MLD_H */ diff --git a/mldsa_native/mldsa_native_config.h b/mldsa_native/mldsa_native_config.h new file mode 100644 index 0000000..223dc40 --- /dev/null +++ b/mldsa_native/mldsa_native_config.h @@ -0,0 +1,756 @@ +/* + * Copyright (c) The mldsa-native project authors + * SPDX-License-Identifier: Apache-2.0 OR ISC OR MIT + */ + +/* References + * ========== + * + * - [FIPS140_3_IG] + * Implementation Guidance for FIPS 140-3 and the Cryptographic Module + * Validation Program + * National Institute of Standards and Technology + * https://csrc.nist.gov/projects/cryptographic-module-validation-program/fips-140-3-ig-announcements + * + * - [FIPS204] + * FIPS 204 Module-Lattice-Based Digital Signature Standard + * National Institute of Standards and Technology + * https://csrc.nist.gov/pubs/fips/204/final + */ + +#ifndef MLD_CONFIG_H +#define MLD_CONFIG_H + +/** + * MLD_CONFIG_PARAMETER_SET + * + * Specifies the parameter set for ML-DSA + * - MLD_CONFIG_PARAMETER_SET=44 corresponds to ML-DSA-44 + * - MLD_CONFIG_PARAMETER_SET=65 corresponds to ML-DSA-65 + * - MLD_CONFIG_PARAMETER_SET=87 corresponds to ML-DSA-87 + * + * If you want to support multiple parameter sets, build the + * library multiple times and set MLD_CONFIG_MULTILEVEL_BUILD. + * See MLD_CONFIG_MULTILEVEL_BUILD for how to do this while + * minimizing code duplication. + * + * This can also be set using CFLAGS. + */ +#ifndef MLD_CONFIG_PARAMETER_SET +#define MLD_CONFIG_PARAMETER_SET \ + 65 /* OnlyKey: ML-DSA-65 */ +#endif + +/** + * MLD_CONFIG_FILE + * + * If defined, this is a header that will be included instead + * of the default configuration file mldsa/mldsa_native_config.h. + * + * When you need to build mldsa-native in multiple configurations, + * using varying MLD_CONFIG_FILE can be more convenient + * then configuring everything through CFLAGS. + * + * To use, MLD_CONFIG_FILE _must_ be defined prior + * to the inclusion of any mldsa-native headers. For example, + * it can be set by passing `-DMLD_CONFIG_FILE="..."` + * on the command line. + */ +/* #define MLD_CONFIG_FILE "mldsa_native_config.h" */ + +/** + * MLD_CONFIG_NAMESPACE_PREFIX + * + * The prefix to use to namespace global symbols from mldsa/. + * + * In a multi-level build, level-dependent symbols will + * additionally be prefixed with the parameter set (44/65/87). + * + * This can also be set using CFLAGS. + */ +#if !defined(MLD_CONFIG_NAMESPACE_PREFIX) +#define MLD_CONFIG_NAMESPACE_PREFIX MLD_DEFAULT_NAMESPACE_PREFIX +#endif + +/** + * MLD_CONFIG_MULTILEVEL_BUILD + * + * Set this if the build is part of a multi-level build supporting + * multiple parameter sets. + * + * If you need only a single parameter set, keep this unset. + * + * To build mldsa-native with support for all parameter sets, + * build it three times -- once per parameter set -- and set the + * option MLD_CONFIG_MULTILEVEL_WITH_SHARED for exactly one of + * them, and MLD_CONFIG_MULTILEVEL_NO_SHARED for the others. + * MLD_CONFIG_MULTILEVEL_BUILD should be set for all of them. + * + * See examples/multilevel_build for an example. + * + * This can also be set using CFLAGS. + */ +/* #define MLD_CONFIG_MULTILEVEL_BUILD */ + +/** + * MLD_CONFIG_EXTERNAL_API_QUALIFIER + * + * If set, this option provides an additional function + * qualifier to be added to declarations of mldsa-native's + * public API. + * + * The primary use case for this option are single-CU builds + * where the public API exposed by mldsa-native is wrapped by + * another API in the consuming application. In this case, + * even mldsa-native's public API can be marked `static`. + */ +/* #define MLD_CONFIG_EXTERNAL_API_QUALIFIER */ + +/** + * MLD_CONFIG_NO_KEYPAIR_API + * + * By default, mldsa-native includes support for generating key + * pairs. If you don't need this, set MLD_CONFIG_NO_KEYPAIR_API + * to exclude crypto_sign_keypair, crypto_sign_keypair_internal, + * crypto_sign_pk_from_sk, and all internal APIs only needed by + * those functions. + */ +/* #define MLD_CONFIG_NO_KEYPAIR_API */ + +/** + * MLD_CONFIG_NO_SIGN_API + * + * By default, mldsa-native includes support for creating + * signatures. If you don't need this, set MLD_CONFIG_NO_SIGN_API + * to exclude crypto_sign_signature, + * crypto_sign_signature_extmu, crypto_sign_signature_internal, + * crypto_sign_signature_pre_hash_internal, + * crypto_sign_signature_pre_hash_shake256, and all internal APIs + * only needed by those functions. + */ +/* #define MLD_CONFIG_NO_SIGN_API */ + +/** + * MLD_CONFIG_NO_VERIFY_API + * + * By default, mldsa-native includes support for verifying + * signatures. If you don't need this, set + * MLD_CONFIG_NO_VERIFY_API to exclude crypto_sign_verify, + * crypto_sign_verify_extmu, crypto_sign_verify_internal, + * crypto_sign_verify_pre_hash_internal, + * crypto_sign_verify_pre_hash_shake256, and all internal APIs + * only needed by those functions. + */ +#define MLD_CONFIG_NO_VERIFY_API /* OnlyKey: device never verifies (openpgp.js does) */ + +#define MLD_CONFIG_SERIAL_FIPS202_ONLY /* OnlyKey: scalar SHAKE (no SIMD on M4) */ + +/** + * MLD_CONFIG_CORE_API_ONLY + * + * Set this to remove all public APIs except + * crypto_sign_keypair_internal, crypto_sign_signature_internal, + * and crypto_sign_verify_internal. + */ +/* #define MLD_CONFIG_CORE_API_ONLY */ + +/** + * MLD_CONFIG_NO_RANDOMIZED_API + * + * If this option is set, mldsa-native will be built without the + * randomized API functions (crypto_sign_keypair, + * crypto_sign_signature, and crypto_sign_signature_extmu). + * This allows users to build mldsa-native without providing a + * randombytes() implementation if they only need the + * internal deterministic API + * (crypto_sign_keypair_internal, crypto_sign_signature_internal). + * + * @note This option is incompatible with MLD_CONFIG_KEYGEN_PCT + * as the current PCT implementation requires + * crypto_sign_signature(). + */ +/* #define MLD_CONFIG_NO_RANDOMIZED_API */ + +/** + * MLD_CONFIG_NO_SUPERCOP + * + * By default, mldsa_native.h exposes the mldsa-native API in the + * SUPERCOP naming convention (crypto_sign_xxx). If you don't need + * this, set MLD_CONFIG_NO_SUPERCOP. + * + * @note You must set this for a multi-level build as the SUPERCOP + * naming does not disambiguate between the parameter sets. + */ +/* #define MLD_CONFIG_NO_SUPERCOP */ + +/** + * MLD_CONFIG_CONSTANTS_ONLY + * + * If you only need the size constants (MLDSA_PUBLICKEYBYTES, etc.) + * but no function declarations, set MLD_CONFIG_CONSTANTS_ONLY. + * + * This only affects the public header mldsa_native.h, not + * the implementation. + */ +/* #define MLD_CONFIG_CONSTANTS_ONLY */ +/****************************************************************************** + * + * Build-only configuration options + * + * The remaining configurations are build-options only. + * They do not affect the API described in mldsa_native.h. + * + *****************************************************************************/ +#if defined(MLD_BUILD_INTERNAL) + +/** + * MLD_CONFIG_MULTILEVEL_WITH_SHARED + * + * This is for multi-level builds of mldsa-native only. If you + * need only a single parameter set, keep this unset. + * + * If this is set, all MLD_CONFIG_PARAMETER_SET-independent + * code will be included in the build, including code needed only + * for other parameter sets. + * + * Example: TODO: add example + * + * To build mldsa-native with support for all parameter sets, + * build it three times -- once per parameter set -- and set the + * option MLD_CONFIG_MULTILEVEL_WITH_SHARED for exactly one of + * them, and MLD_CONFIG_MULTILEVEL_NO_SHARED for the others. + * + * See examples/multilevel_build_mldsa for an example. + * + * This can also be set using CFLAGS. + */ +/* #define MLD_CONFIG_MULTILEVEL_WITH_SHARED */ + +/** + * MLD_CONFIG_MULTILEVEL_NO_SHARED + * + * This is for multi-level builds of mldsa-native only. If you + * need only a single parameter set, keep this unset. + * + * If this is set, no MLD_CONFIG_PARAMETER_SET-independent code + * will be included in the build. + * + * To build mldsa-native with support for all parameter sets, + * build it three times -- once per parameter set -- and set the + * option MLD_CONFIG_MULTILEVEL_WITH_SHARED for exactly one of + * them, and MLD_CONFIG_MULTILEVEL_NO_SHARED for the others. + * + * See examples/multilevel_build_mldsa for an example. + * + * This can also be set using CFLAGS. + */ +/* #define MLD_CONFIG_MULTILEVEL_NO_SHARED */ + +/** + * MLD_CONFIG_MONOBUILD_KEEP_SHARED_HEADERS + * + * This is only relevant for single compilation unit (SCU) + * builds of mldsa-native. In this case, it determines whether + * directives defined in parameter-set-independent headers should + * be #undef'ined or not at the of the SCU file. This is needed + * in multilevel builds. + * + * See examples/multilevel_build_native for an example. + * + * This can also be set using CFLAGS. + */ +/* #define MLD_CONFIG_MONOBUILD_KEEP_SHARED_HEADERS */ + +/** + * MLD_CONFIG_USE_NATIVE_BACKEND_ARITH + * + * Determines whether an native arithmetic backend should be used. + * + * The arithmetic backend covers performance-critical functions + * such as the number-theoretic transform (NTT). + * + * If this option is unset, the C backend will be used. + * + * If this option is set, the arithmetic backend to be use is + * determined by MLD_CONFIG_ARITH_BACKEND_FILE: If the latter is + * unset, the default backend for your the target architecture + * will be used. If set, it must be the name of a backend metadata + * file. + * + * This can also be set using CFLAGS. + */ +#if !defined(MLD_CONFIG_USE_NATIVE_BACKEND_ARITH) +/* #define MLD_CONFIG_USE_NATIVE_BACKEND_ARITH */ +#endif + +/** + * MLD_CONFIG_ARITH_BACKEND_FILE + * + * The arithmetic backend to use. + * + * If MLD_CONFIG_USE_NATIVE_BACKEND_ARITH is unset, this option + * is ignored. + * + * If MLD_CONFIG_USE_NATIVE_BACKEND_ARITH is set, this option must + * either be undefined or the filename of an arithmetic backend. + * If unset, the default backend will be used. + * + * This can be set using CFLAGS. + */ +#if defined(MLD_CONFIG_USE_NATIVE_BACKEND_ARITH) && \ + !defined(MLD_CONFIG_ARITH_BACKEND_FILE) +#define MLD_CONFIG_ARITH_BACKEND_FILE "native/meta.h" +#endif + +/** + * MLD_CONFIG_USE_NATIVE_BACKEND_FIPS202 + * + * Determines whether an native FIPS202 backend should be used. + * + * The FIPS202 backend covers 1x/2x/4x-fold Keccak-f1600, which is + * the performance bottleneck of SHA3 and SHAKE. + * + * If this option is unset, the C backend will be used. + * + * If this option is set, the FIPS202 backend to be use is + * determined by MLD_CONFIG_FIPS202_BACKEND_FILE: If the latter is + * unset, the default backend for your the target architecture + * will be used. If set, it must be the name of a backend metadata + * file. + * + * This can also be set using CFLAGS. + */ +#if !defined(MLD_CONFIG_USE_NATIVE_BACKEND_FIPS202) +/* #define MLD_CONFIG_USE_NATIVE_BACKEND_FIPS202 */ +#endif + +/** + * MLD_CONFIG_FIPS202_BACKEND_FILE + * + * The FIPS-202 backend to use. + * + * If MLD_CONFIG_USE_NATIVE_BACKEND_FIPS202 is set, this option + * must either be undefined or the filename of a FIPS202 backend. + * If unset, the default backend will be used. + * + * This can be set using CFLAGS. + */ +#if defined(MLD_CONFIG_USE_NATIVE_BACKEND_FIPS202) && \ + !defined(MLD_CONFIG_FIPS202_BACKEND_FILE) +#define MLD_CONFIG_FIPS202_BACKEND_FILE "fips202/native/auto.h" +#endif + +/** + * MLD_CONFIG_FIPS202_CUSTOM_HEADER + * + * Custom header to use for FIPS-202 + * + * This should only be set if you intend to use a custom + * FIPS-202 implementation, different from the one shipped + * with mldsa-native. + * + * If set, it must be the name of a file serving as the + * replacement for mldsa/src/fips202/fips202.h, and exposing + * the same API (see FIPS202.md). + */ +/* #define MLD_CONFIG_FIPS202_CUSTOM_HEADER "SOME_FILE.h" */ + +/** + * MLD_CONFIG_FIPS202X4_CUSTOM_HEADER + * + * Custom header to use for FIPS-202-X4 + * + * This should only be set if you intend to use a custom + * FIPS-202 implementation, different from the one shipped + * with mldsa-native. + * + * If set, it must be the name of a file serving as the + * replacement for mldsa/src/fips202/fips202x4.h, and exposing + * the same API (see FIPS202.md). + */ +/* #define MLD_CONFIG_FIPS202X4_CUSTOM_HEADER "SOME_FILE.h" */ + +/** + * MLD_CONFIG_CUSTOM_ZEROIZE + * + * In compliance with @[FIPS204, Section 3.6.3], mldsa-native, + * zeroizes intermediate stack buffers before returning from + * function calls. + * + * Set this option and define `mld_zeroize` if you want to + * use a custom method to zeroize intermediate stack buffers. + * The default implementation uses SecureZeroMemory on Windows + * and a memset + compiler barrier otherwise. If neither of those + * is available on the target platform, compilation will fail, + * and you will need to use MLD_CONFIG_CUSTOM_ZEROIZE to provide + * a custom implementation of `mld_zeroize()`. + * + * @warning The explicit stack zeroization conducted by mldsa-native reduces + * the likelihood of data leaking on the stack, but does not + * eliminate it. The C standard makes no guarantee about where a + * compiler allocates structures and whether/where it makes copies + * of them. Also, in addition to entire structures, there may also + * be potentially exploitable leakage of individual values on the + * stack. + * + * If you need bullet-proof zeroization of the stack, you need to + * consider additional measures instead of what this feature + * provides. In this case, you can set mld_zeroize to a no-op. + */ +/* #define MLD_CONFIG_CUSTOM_ZEROIZE + #if !defined(__ASSEMBLER__) + #include + #include "src/src.h" + static MLD_INLINE void mld_zeroize(void *ptr, size_t len) + { + ... your implementation ... + } + #endif +*/ + +/** + * MLD_CONFIG_CUSTOM_RANDOMBYTES + * + * mldsa-native does not provide a secure randombytes + * implementation. Such an implementation has to provided by the + * consumer. + * + * If this option is not set, mldsa-native expects a function + * int randombytes(uint8_t *out, size_t outlen). + * + * Set this option and define `mld_randombytes` if you want to + * use a custom method to sample randombytes with a different name + * or signature. + */ +#define MLD_CONFIG_CUSTOM_RANDOMBYTES +#if !defined(__ASSEMBLER__) +#include +#include +extern int onlykey_mldsa_randombytes(uint8_t *out, size_t outlen); +static inline int mld_randombytes(uint8_t *ptr, size_t len) +{ + return onlykey_mldsa_randombytes(ptr, len); +} +#endif + +/** + * MLD_CONFIG_CUSTOM_CAPABILITY_FUNC + * + * mldsa-native backends may rely on specific hardware features. + * Those backends will only be included in an mldsa-native build + * if support for the respective features is enabled at + * compile-time. However, when building for a heteroneous set + * of CPUs to run the resulting binary/library on, feature + * detection at _runtime_ is needed to decided whether a backend + * can be used or not. + * + * Set this option and define `mld_sys_check_capability` if you + * want to use a custom method to dispatch between implementations. + * + * Return value 1 indicates that a capability is supported. + * Return value 0 indicates that a capability is not supported. + * + * If this option is not set, mldsa-native uses compile-time + * feature detection only to decide which backend to use. + * + * If you compile mldsa-native on a system with different + * capabilities than the system that the resulting binary/library + * will be run on, you must use this option. + */ +/* #define MLD_CONFIG_CUSTOM_CAPABILITY_FUNC + static MLD_INLINE int mld_sys_check_capability(mld_sys_cap cap) + { + ... your implementation ... + } +*/ + +/** + * MLD_CONFIG_CUSTOM_ALLOC_FREE + * + * Set this option and define `MLD_CUSTOM_ALLOC` and + * `MLD_CUSTOM_FREE` if you want to use custom allocation for + * large local structures or buffers. + * + * By default, all buffers/structures are allocated on the stack. + * If this option is set, most of them will be allocated via + * MLD_CUSTOM_ALLOC. + * + * Parameters to MLD_CUSTOM_ALLOC: + * - T* v: Target pointer to declare. + * - T: Type of structure to be allocated + * - N: Number of elements to be allocated. + * + * Parameters to MLD_CUSTOM_FREE: + * - T* v: Target pointer to free. May be NULL. + * - T: Type of structure to be freed. + * - N: Number of elements to be freed. + * + * @warning This option is experimental. Its scope, configuration and + * function/macro signatures may change at any time. We expect a + * stable API for v2. + * + * @note Even if this option is set, some allocations further down + * the call stack will still be made from the stack. Those will + * likely be added to the scope of this option in the future. + * + * @note MLD_CUSTOM_ALLOC need not guarantee a successful + * allocation nor include error handling. Upon failure, the + * target pointer should simply be set to NULL. The calling + * code will handle this case and invoke MLD_CUSTOM_FREE. + */ +/* #define MLD_CONFIG_CUSTOM_ALLOC_FREE + #if !defined(__ASSEMBLER__) + #include + #define MLD_CUSTOM_ALLOC(v, T, N) \ + T* (v) = (T *)aligned_alloc(MLD_DEFAULT_ALIGN, \ + MLD_ALIGN_UP(sizeof(T) * (N))) + #define MLD_CUSTOM_FREE(v, T, N) free(v) + #endif +*/ + +/** + * MLD_CONFIG_CUSTOM_MEMCPY + * + * Set this option and define `mld_memcpy` if you want to + * use a custom method to copy memory instead of the standard + * library memcpy function. + * + * The custom implementation must have the same signature and + * behavior as the standard memcpy function: + * void *mld_memcpy(void *dest, const void *src, size_t n) + */ +/* #define MLD_CONFIG_CUSTOM_MEMCPY + #if !defined(__ASSEMBLER__) + #include + #include "src/src.h" + static MLD_INLINE void *mld_memcpy(void *dest, const void *src, size_t n) + { + ... your implementation ... + } + #endif +*/ + +/** + * MLD_CONFIG_CUSTOM_MEMSET + * + * Set this option and define `mld_memset` if you want to + * use a custom method to set memory instead of the standard + * library memset function. + * + * The custom implementation must have the same signature and + * behavior as the standard memset function: + * void *mld_memset(void *s, int c, size_t n) + */ +/* #define MLD_CONFIG_CUSTOM_MEMSET + #if !defined(__ASSEMBLER__) + #include + #include "src/src.h" + static MLD_INLINE void *mld_memset(void *s, int c, size_t n) + { + ... your implementation ... + } + #endif +*/ + +/** + * MLD_CONFIG_INTERNAL_API_QUALIFIER + * + * If set, this option provides an additional qualifier + * to be added to declarations of internal API functions and data. + * + * The primary use case for this option are single-CU builds, + * in which case this option can be set to `static`. + */ +/* #define MLD_CONFIG_INTERNAL_API_QUALIFIER */ + +/** + * MLD_CONFIG_CT_TESTING_ENABLED + * + * If set, mldsa-native annotates data as secret / public using + * valgrind's annotations VALGRIND_MAKE_MEM_UNDEFINED and + * VALGRIND_MAKE_MEM_DEFINED, enabling various checks for secret- + * dependent control flow of variable time execution (depending + * on the exact version of valgrind installed). + */ +/* #define MLD_CONFIG_CT_TESTING_ENABLED */ + +/** + * MLD_CONFIG_NO_ASM + * + * If this option is set, mldsa-native will be built without + * use of native code or inline assembly. + * + * By default, inline assembly is used to implement value barriers. + * Without inline assembly, mldsa-native will use a global volatile + * 'opt blocker' instead; see ct.h. + * + * Inline assembly is also used to implement a secure zeroization + * function on non-Windows platforms. If this option is set and + * the target platform is not Windows, you MUST set + * MLD_CONFIG_CUSTOM_ZEROIZE and provide a custom zeroization + * function. + * + * If this option is set, MLD_CONFIG_USE_NATIVE_BACKEND_FIPS202 and + * and MLD_CONFIG_USE_NATIVE_BACKEND_ARITH will be ignored, and no + * native backends will be used. + */ +/* #define MLD_CONFIG_NO_ASM */ + +/** + * MLD_CONFIG_NO_ASM_VALUE_BARRIER + * + * If this option is set, mldsa-native will be built without + * use of native code or inline assembly for value barriers. + * + * By default, inline assembly (if available) is used to implement + * value barriers. + * Without inline assembly, mldsa-native will use a global volatile + * 'opt blocker' instead; see ct.h. + */ +/* #define MLD_CONFIG_NO_ASM_VALUE_BARRIER */ + +/** + * MLD_CONFIG_KEYGEN_PCT + * + * Compliance with @[FIPS140_3_IG, p.87] requires a + * Pairwise Consistency Test (PCT) to be carried out on a freshly + * generated keypair before it can be exported. + * + * Set this option if such a check should be implemented. + * In this case, crypto_sign_keypair_internal and + * crypto_sign_keypair will return a non-zero error code if the + * PCT failed. + * + * @note This feature will drastically lower the performance of + * key generation. + * + * @note This option is incompatible with MLD_CONFIG_NO_SIGN_API + * and MLD_CONFIG_NO_VERIFY_API as the current PCT implementation + * requires crypto_sign_signature() and crypto_sign_verify(). + */ +/* #define MLD_CONFIG_KEYGEN_PCT */ + +/** + * MLD_CONFIG_KEYGEN_PCT_BREAKAGE_TEST + * + * If this option is set, the user must provide a runtime + * function `static inline int mld_break_pct() { ... }` to + * indicate whether the PCT should be made fail. + * + * This option only has an effect if MLD_CONFIG_KEYGEN_PCT is set. + */ +/* #define MLD_CONFIG_KEYGEN_PCT_BREAKAGE_TEST + #if !defined(__ASSEMBLER__) + #include "src/src.h" + static MLD_INLINE int mld_break_pct(void) + { + ... return 0/1 depending on whether PCT should be broken ... + } + #endif +*/ + +/** + * MLD_CONFIG_MAX_SIGNING_ATTEMPTS + * + * Upper bound on the number of rejection-sampling iterations + * performed by ML-DSA signing (@[FIPS204, Algorithm 7]). + * + * If a valid signature is not produced within this many + * attempts, signing returns MLD_ERR_SIGN_ATTEMPTS_EXHAUSTED. + * This is useful in timing-sensitive environments that + * require a deterministic worst-case bound on signing time. + * + * For FIPS 204 compliance, this value MUST be at least 814, + * cf. @[FIPS204, Appendix C], which is chosen so that the + * signing failure rate is < 2^{-256}. + * + * Default: Largest possible value before internal counters + * would overflow. This is larger than the FIPS204 bound. + * + * In particular, in the default configuration, the signing + * failure rate is < 2^{-256}. + */ +/* #define MLD_CONFIG_MAX_SIGNING_ATTEMPTS 814 */ + +/** + * MLD_CONFIG_SERIAL_FIPS202_ONLY + * + * Set this to use a FIPS202 implementation with global state + * that supports only one active Keccak computation at a time + * (e.g. some hardware accelerators). + * + * If this option is set, ML-DSA will use FIPS202 operations + * serially, ensuring that only one SHAKE context is active + * at any given time. + * + * This allows offloading Keccak computations to a hardware + * accelerator that holds only a single Keccak state locally, + * rather than requiring support for multiple concurrent + * Keccak states. + * + * @note Depending on the target CPU, this may reduce + * performance when using software FIPS202 implementations. + * Only enable this when you have to. + */ +/* #define MLD_CONFIG_SERIAL_FIPS202_ONLY */ + +/** + * MLD_CONFIG_CONTEXT_PARAMETER + * + * Set this to add a context parameter that is provided to public + * API functions and is then available in custom callbacks. + * + * The type of the context parameter is configured via + * MLD_CONFIG_CONTEXT_PARAMETER_TYPE. + */ +/* #define MLD_CONFIG_CONTEXT_PARAMETER */ + +/** + * MLD_CONFIG_CONTEXT_PARAMETER_TYPE + * + * Set this to define the type for the context parameter used by + * MLD_CONFIG_CONTEXT_PARAMETER. + * + * This is only relevant if MLD_CONFIG_CONTEXT_PARAMETER is set. + */ +/* #define MLD_CONFIG_CONTEXT_PARAMETER_TYPE void* */ + +/** + * MLD_CONFIG_REDUCE_RAM + * + * Set this to reduce RAM usage. This trades memory for performance. + * + * For expected memory usage, see the MLD_TOTAL_ALLOC_* constants defined in + * mldsa_native.h. + * + * This option is useful for embedded systems with tight RAM constraints but + * relaxed performance requirements. + * + */ +#define MLD_CONFIG_REDUCE_RAM /* OnlyKey: ~17KB sign vs ~69KB; fits 64KB RAM */ + +/************************* Config internals ********************************/ + +#endif /* MLD_BUILD_INTERNAL */ + +/* Default namespace + * + * Don't change this. If you need a different namespace, re-define + * MLD_CONFIG_NAMESPACE_PREFIX above instead, and remove the following. + * + * The default MLDSA namespace is + * + * PQCP_MLDSA_NATIVE_MLDSA_ + * + * e.g., PQCP_MLDSA_NATIVE_MLDSA44_ + */ + +#if MLD_CONFIG_PARAMETER_SET == 44 +#define MLD_DEFAULT_NAMESPACE_PREFIX PQCP_MLDSA_NATIVE_MLDSA44 +#elif MLD_CONFIG_PARAMETER_SET == 65 +#define MLD_DEFAULT_NAMESPACE_PREFIX PQCP_MLDSA_NATIVE_MLDSA65 +#elif MLD_CONFIG_PARAMETER_SET == 87 +#define MLD_DEFAULT_NAMESPACE_PREFIX PQCP_MLDSA_NATIVE_MLDSA87 +#endif + +#endif /* !MLD_CONFIG_H */ diff --git a/mldsa_native/src/cbmc.h b/mldsa_native/src/cbmc.h new file mode 100644 index 0000000..0fd2045 --- /dev/null +++ b/mldsa_native/src/cbmc.h @@ -0,0 +1,199 @@ +/* + * Copyright (c) The mldsa-native project authors + * SPDX-License-Identifier: Apache-2.0 OR ISC OR MIT + */ + +#ifndef MLD_CBMC_H +#define MLD_CBMC_H + +/*************************************************** + * Basic replacements for __CPROVER_XXX contracts + ***************************************************/ +/* + * The `__contract__` / `__loop__` annotation macros use a + * leading-double-underscore spelling in line with other CBMC macros. + * clang-tidy flags these as reserved identifiers; we suppress the diagnostic + * at each definition site (NOLINT) rather than disabling the check globally, + * so it stays active for the rest of the tree. + */ +#ifndef CBMC + +/* clang-format off */ +#define __contract__(x) /* NOLINT(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp) */ +#define __loop__(x) /* NOLINT(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp) */ +/* clang-format on */ +#define cassert(x) + +#else /* !CBMC */ + + +/* clang-format off */ +#define __contract__(x) x /* NOLINT(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp) */ +#define __loop__(x) x /* NOLINT(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp) */ +/* clang-format on */ + +/* Conditionally expand to __VA_ARGS__ depending on MLD_CONFIG_REDUCE_RAM. */ +#if defined(MLD_CONFIG_REDUCE_RAM) +#define MLD_IF_REDUCE_RAM(...) __VA_ARGS__ +#define MLD_IF_NOT_REDUCE_RAM(...) +#else +#define MLD_IF_REDUCE_RAM(...) +#define MLD_IF_NOT_REDUCE_RAM(...) __VA_ARGS__ +#endif + +/* https://diffblue.github.io/cbmc/contracts-assigns.html */ +#define assigns(...) __CPROVER_assigns(__VA_ARGS__) + +/* https://diffblue.github.io/cbmc/contracts-requires-ensures.html */ +#define requires(...) __CPROVER_requires(__VA_ARGS__) +#define ensures(...) __CPROVER_ensures(__VA_ARGS__) +/* https://diffblue.github.io/cbmc/contracts-loops.html */ +#define invariant(...) __CPROVER_loop_invariant(__VA_ARGS__) +#define decreases(...) __CPROVER_decreases(__VA_ARGS__) +/* cassert to avoid confusion with in-built assert */ +#define cassert(x) __CPROVER_assert(x, "cbmc assertion failed") +#define assume(...) __CPROVER_assume(__VA_ARGS__) + +/*************************************************** + * Macros for "expression" forms that may appear + * _inside_ top-level contracts. + ***************************************************/ + +/* + * function return value - useful inside ensures + * https://diffblue.github.io/cbmc/contracts-functions.html + */ +#define return_value (__CPROVER_return_value) + +/* + * assigns l-value targets + * https://diffblue.github.io/cbmc/contracts-assigns.html + */ +#define object_whole(...) __CPROVER_object_whole(__VA_ARGS__) +#define memory_slice(...) __CPROVER_object_upto(__VA_ARGS__) +#define same_object(...) __CPROVER_same_object(__VA_ARGS__) + +/* + * Pointer-related predicates + * https://diffblue.github.io/cbmc/contracts-memory-predicates.html + */ +#define memory_no_alias(...) __CPROVER_is_fresh(__VA_ARGS__) +#define readable(...) __CPROVER_r_ok(__VA_ARGS__) +#define writeable(...) __CPROVER_w_ok(__VA_ARGS__) + +/* Maximum supported buffer size + * + * Larger buffers may be supported, but due to internal modeling constraints + * in CBMC, the proofs of memory- and type-safety won't be able to run. + * + * If you find yourself in need for a buffer size larger than this, + * please contact the maintainers, so we can prioritize work to relax + * this somewhat artificial bound. + */ +#define MLD_MAX_BUFFER_SIZE (SIZE_MAX >> 12) + + +/* + * History variables + * https://diffblue.github.io/cbmc/contracts-history-variables.html + */ +#define old(...) __CPROVER_old(__VA_ARGS__) +#define loop_entry(...) __CPROVER_loop_entry(__VA_ARGS__) + +/* + * Quantifiers + * Note that the range on qvar is _exclusive_ between qvar_lb .. qvar_ub + * https://diffblue.github.io/cbmc/contracts-quantifiers.html + * + * The quantified variable is declared as uint32_t, so these macros + * quantify only over indices in [0, UINT32_MAX). Bounds larger than + * UINT32_MAX (4 GiB) are NOT supported: the explicit (uint32_t) casts + * on the bounds will trigger CBMC's conversion check if a wider bound + * (e.g. a size_t > UINT32_MAX) is passed. + * + * Quantifying over size_t (64-bit) was found to blow up SMT proof + * times, so we deliberately keep the index width at 32 bits. Callers + * dealing with size_t-typed buffers must add an explicit + * requires(len <= UINT32_MAX) + * precondition. + */ + +/* + * Prevent clang-format from corrupting CBMC's special ==> operator + */ +/* clang-format off */ +#define forall(qvar, qvar_lb, qvar_ub, predicate) \ + __CPROVER_forall \ + { \ + uint32_t qvar; \ + ((uint32_t) (qvar_lb) <= (qvar) && (qvar) < (uint32_t) (qvar_ub)) ==> (predicate) \ + } + +#define exists(qvar, qvar_lb, qvar_ub, predicate) \ + __CPROVER_exists \ + { \ + uint32_t qvar; \ + ((uint32_t) (qvar_lb) <= (qvar) && (qvar) < (uint32_t) (qvar_ub)) && (predicate) \ + } +/* clang-format on */ + +/*************************************************** + * Convenience macros for common contract patterns + ***************************************************/ +/* + * Prevent clang-format from corrupting CBMC's special ==> operator + */ +/* clang-format off */ +#define CBMC_CONCAT_(left, right) left##right +#define CBMC_CONCAT(left, right) CBMC_CONCAT_(left, right) + +#define array_bound_core(qvar, qvar_lb, qvar_ub, array_var, \ + value_lb, value_ub) \ + __CPROVER_forall \ + { \ + uint32_t qvar; \ + ((uint32_t) (qvar_lb) <= (qvar) && (qvar) < (uint32_t) (qvar_ub)) ==> \ + (((int)(value_lb) <= ((array_var)[(qvar)])) && \ + (((array_var)[(qvar)]) < (int)(value_ub))) \ + } + +#define array_bound(array_var, qvar_lb, qvar_ub, value_lb, value_ub) \ + array_bound_core(CBMC_CONCAT(_cbmc_idx, __COUNTER__), (qvar_lb), \ + (qvar_ub), (array_var), (value_lb), (value_ub)) + +#define array_unchanged_core(qvar, qvar_lb, qvar_ub, array_var) \ + __CPROVER_forall \ + { \ + uint32_t qvar; \ + ((uint32_t) (qvar_lb) <= (qvar) && (qvar) < (uint32_t) (qvar_ub)) ==> \ + ((array_var)[(qvar)]) == (old(* (int32_t (*)[(qvar_ub)])(array_var)))[(qvar)] \ + } + +#define array_unchanged(array_var, N) \ + array_unchanged_core(CBMC_CONCAT(_cbmc_idx, __COUNTER__), 0, (N), (array_var)) + +#define array_unchanged_u64_core(qvar, qvar_lb, qvar_ub, array_var) \ + __CPROVER_forall \ + { \ + uint32_t qvar; \ + ((uint32_t) (qvar_lb) <= (qvar) && (qvar) < (uint32_t) (qvar_ub)) ==> \ + ((array_var)[(qvar)]) == (old(* (uint64_t (*)[(qvar_ub)])(array_var)))[(qvar)] \ + } + +#define array_unchanged_u64(array_var, N) \ + array_unchanged_u64_core(CBMC_CONCAT(_cbmc_idx, __COUNTER__), 0, (N), (array_var)) +/* clang-format on */ + +/* Wrapper around array_bound operating on absolute values. + * + * The absolute value bound `k` is exclusive. + * + * Note that since the lower bound in array_bound is inclusive, we have to + * raise it by 1 here. + */ +#define array_abs_bound(arr, lb, ub, k) \ + array_bound((arr), (lb), (ub), -((int)(k)) + 1, (k)) + +#endif /* CBMC */ + +#endif /* !MLD_CBMC_H */ diff --git a/mldsa_native/src/common.h b/mldsa_native/src/common.h new file mode 100644 index 0000000..95a88b3 --- /dev/null +++ b/mldsa_native/src/common.h @@ -0,0 +1,286 @@ +/* + * Copyright (c) The mldsa-native project authors + * Copyright (c) The mlkem-native project authors + * SPDX-License-Identifier: Apache-2.0 OR ISC OR MIT + */ +#ifndef MLD_COMMON_H +#define MLD_COMMON_H + +#ifndef __ASSEMBLER__ +#include +#endif + + +#define MLD_BUILD_INTERNAL + +#if defined(MLD_CONFIG_FILE) +#include MLD_CONFIG_FILE +#else +#include "mldsa_native_config.h" +#endif + +#include "params.h" +#include "sys.h" + +/* Internal and public API have external linkage by default, but + * this can be overwritten by the user, e.g. for single-CU builds. */ +#if !defined(MLD_CONFIG_INTERNAL_API_QUALIFIER) +#define MLD_INTERNAL_API +#define MLD_INTERNAL_DATA_DECLARATION extern +#define MLD_INTERNAL_DATA_DEFINITION +#else +#define MLD_INTERNAL_API MLD_CONFIG_INTERNAL_API_QUALIFIER +#define MLD_INTERNAL_DATA_DECLARATION MLD_CONFIG_INTERNAL_API_QUALIFIER +#define MLD_INTERNAL_DATA_DEFINITION MLD_CONFIG_INTERNAL_API_QUALIFIER +#endif + +#if !defined(MLD_CONFIG_EXTERNAL_API_QUALIFIER) +#define MLD_EXTERNAL_API +#else +#define MLD_EXTERNAL_API MLD_CONFIG_EXTERNAL_API_QUALIFIER +#endif + +#if defined(MLD_CONFIG_MULTILEVEL_NO_SHARED) || \ + defined(MLD_CONFIG_MULTILEVEL_WITH_SHARED) +#define MLD_MULTILEVEL_BUILD +#endif + +#define MLD_CONCAT_(x1, x2) x1##x2 +#define MLD_CONCAT(x1, x2) MLD_CONCAT_(x1, x2) + +#if defined(MLD_MULTILEVEL_BUILD) +#define MLD_ADD_PARAM_SET(s) MLD_CONCAT(s, MLD_CONFIG_PARAMETER_SET) +#else +#define MLD_ADD_PARAM_SET(s) s +#endif + +#define MLD_NAMESPACE_PREFIX MLD_CONCAT(MLD_CONFIG_NAMESPACE_PREFIX, _) +#define MLD_NAMESPACE_PREFIX_KL \ + MLD_CONCAT(MLD_ADD_PARAM_SET(MLD_CONFIG_NAMESPACE_PREFIX), _) + +/* Functions are prefixed by MLD_CONFIG_NAMESPACE_PREFIX. + * + * If multiple parameter sets are used, functions depending on the parameter + * set are additionally prefixed with 44/65/87. See mldsa_native_config.h. + * + * Example: If MLD_CONFIG_NAMESPACE_PREFIX is PQCP_MLDSA_NATIVE, then + * MLD_NAMESPACE_KL(keypair) becomes PQCP_MLDSA_NATIVE44_keypair/ + * PQCP_MLDSA_NATIVE65_keypair/PQCP_MLDSA_NATIVE87_keypair. + */ +#define MLD_NAMESPACE(s) MLD_CONCAT(MLD_NAMESPACE_PREFIX, s) +#define MLD_NAMESPACE_KL(s) MLD_CONCAT(MLD_NAMESPACE_PREFIX_KL, s) + +/* On Apple platforms, we need to emit leading underscore + * in front of assembly symbols. We thus introducee a separate + * namespace wrapper for ASM symbols. */ +#if !defined(__APPLE__) +#define MLD_ASM_NAMESPACE(sym) MLD_NAMESPACE(sym) +#else +#define MLD_ASM_NAMESPACE(sym) MLD_CONCAT(_, MLD_NAMESPACE(sym)) +#endif + +/* + * On X86_64 if control-flow protections (CET) are enabled (through + * -fcf-protection=), we add an endbr64 instruction at every global function + * label. See sys.h for more details + */ +#if defined(MLD_SYS_X86_64) +#define MLD_ASM_FN_SYMBOL(sym) MLD_ASM_NAMESPACE(sym) : MLD_CET_ENDBR +#elif defined(MLD_SYS_ARMV81M_MVE) +/* clang-format off */ +#define MLD_ASM_FN_SYMBOL(sym) \ + .type MLD_ASM_NAMESPACE(sym), %function; \ + MLD_ASM_NAMESPACE(sym) : +/* clang-format on */ +#else /* !MLD_SYS_X86_64 && MLD_SYS_ARMV81M_MVE */ +#define MLD_ASM_FN_SYMBOL(sym) MLD_ASM_NAMESPACE(sym) : +#endif /* !MLD_SYS_X86_64 && !MLD_SYS_ARMV81M_MVE */ + +/* + * Output the size of an assembly function. + */ +#if defined(__ELF__) +#define MLD_ASM_FN_SIZE(sym) \ + .size MLD_ASM_NAMESPACE(sym), .- MLD_ASM_NAMESPACE(sym) +#else +#define MLD_ASM_FN_SIZE(sym) +#endif + +/* We aim to simplify the user's life by supporting builds where + * all source files are included, even those that are not needed. + * Those files are appropriately guarded and will be empty when unneeded. + * The following is to avoid compilers complaining about this. */ +#define MLD_EMPTY_CU(s) extern int MLD_NAMESPACE_KL(empty_cu_##s); + +/* MLD_CONFIG_NO_ASM takes precedence over MLD_USE_NATIVE_XXX */ +#if defined(MLD_CONFIG_NO_ASM) +#undef MLD_CONFIG_USE_NATIVE_BACKEND_ARITH +#undef MLD_CONFIG_USE_NATIVE_BACKEND_FIPS202 +#endif + +#if defined(MLD_CONFIG_USE_NATIVE_BACKEND_ARITH) && \ + !defined(MLD_CONFIG_ARITH_BACKEND_FILE) +#error Bad configuration: MLD_CONFIG_USE_NATIVE_BACKEND_ARITH is set, but MLD_CONFIG_ARITH_BACKEND_FILE is not. +#endif + +#if defined(MLD_CONFIG_USE_NATIVE_BACKEND_FIPS202) && \ + !defined(MLD_CONFIG_FIPS202_BACKEND_FILE) +#error Bad configuration: MLD_CONFIG_USE_NATIVE_BACKEND_FIPS202 is set, but MLD_CONFIG_FIPS202_BACKEND_FILE is not. +#endif + +#if defined(MLD_CONFIG_NO_RANDOMIZED_API) && defined(MLD_CONFIG_KEYGEN_PCT) +#error Bad configuration: MLD_CONFIG_NO_RANDOMIZED_API is incompatible with MLD_CONFIG_KEYGEN_PCT as the current PCT implementation requires crypto_sign_signature() +#endif + +#if defined(MLD_CONFIG_NO_SIGN_API) && defined(MLD_CONFIG_KEYGEN_PCT) +#error Bad configuration: MLD_CONFIG_NO_SIGN_API is incompatible with MLD_CONFIG_KEYGEN_PCT as the current PCT implementation requires crypto_sign_signature() +#endif + +#if defined(MLD_CONFIG_NO_VERIFY_API) && defined(MLD_CONFIG_KEYGEN_PCT) +#error Bad configuration: MLD_CONFIG_NO_VERIFY_API is incompatible with MLD_CONFIG_KEYGEN_PCT as the current PCT implementation requires crypto_sign_verify() +#endif + +#if defined(MLD_CONFIG_USE_NATIVE_BACKEND_ARITH) +#include MLD_CONFIG_ARITH_BACKEND_FILE +/* Include to enforce consistency of API and implementation, + * and conduct sanity checks on the backend. + * + * Keep this _after_ the inclusion of the backend; otherwise, + * the sanity checks won't have an effect. */ +#if defined(MLD_CHECK_APIS) && !defined(__ASSEMBLER__) +#include "native/api.h" +#endif +#endif /* MLD_CONFIG_USE_NATIVE_BACKEND_ARITH */ + +#if defined(MLD_CONFIG_USE_NATIVE_BACKEND_FIPS202) +#include MLD_CONFIG_FIPS202_BACKEND_FILE +/* Include to enforce consistency of API and implementation, + * and conduct sanity checks on the backend. + * + * Keep this _after_ the inclusion of the backend; otherwise, + * the sanity checks won't have an effect. */ +#if defined(MLD_CHECK_APIS) && !defined(__ASSEMBLER__) +#include "fips202/native/api.h" +#endif +#endif /* MLD_CONFIG_USE_NATIVE_BACKEND_FIPS202 */ + +#if !defined(MLD_CONFIG_FIPS202_CUSTOM_HEADER) +#define MLD_FIPS202_HEADER_FILE "fips202/fips202.h" +#else +#define MLD_FIPS202_HEADER_FILE MLD_CONFIG_FIPS202_CUSTOM_HEADER +#endif + +#if !defined(MLD_CONFIG_FIPS202X4_CUSTOM_HEADER) +#define MLD_FIPS202X4_HEADER_FILE "fips202/fips202x4.h" +#else +#define MLD_FIPS202X4_HEADER_FILE MLD_CONFIG_FIPS202X4_CUSTOM_HEADER +#endif + +/* Standard library function replacements */ +#if !defined(__ASSEMBLER__) +#if !defined(MLD_CONFIG_CUSTOM_MEMCPY) +#include +#define mld_memcpy memcpy +#endif + +#if !defined(MLD_CONFIG_CUSTOM_MEMSET) +#include +#define mld_memset memset +#endif + +/* Allocation macros for large local structures + * + * MLD_ALLOC(v, T, N) declares T *v and attempts to point it to an T[N] + * MLD_FREE(v, T, N) zeroizes and frees the allocation + * + * Default implementation uses stack allocation. + * Can be overridden by setting the config option MLD_CONFIG_CUSTOM_ALLOC_FREE + * and defining MLD_CUSTOM_ALLOC and MLD_CUSTOM_FREE. + */ +#if defined(MLD_CONFIG_CUSTOM_ALLOC_FREE) != \ + (defined(MLD_CUSTOM_ALLOC) && defined(MLD_CUSTOM_FREE)) +#error Bad configuration: MLD_CONFIG_CUSTOM_ALLOC_FREE must be set together with MLD_CUSTOM_ALLOC and MLD_CUSTOM_FREE +#endif + +/* Context-parameter machinery (MLD_CONTEXT_PARAMETERS_n and related config + * checks). Kept in a separate, level-generic header for readability; included + * here so it is available to the allocation macros below and to all consumers + * of common.h. */ +#include "context.h" + +#if !defined(MLD_CONFIG_CUSTOM_ALLOC_FREE) +/* Default: stack allocation */ + +/* This is a declaration macro, not an expression macro: T is a type and v is + * a declarator, neither of which can be wrapped in parentheses. The + * bugprone-macro-parentheses diagnostic is therefore a false positive here. */ +#define MLD_ALLOC(v, T, N, context) \ + MLD_ALIGN T mld_alloc_##v[N]; \ + T *v = mld_alloc_##v /* NOLINT(bugprone-macro-parentheses) */ + +/* The MLD_FREE macro body references mld_zeroize(), which is declared in + * ct.h. We deliberately do NOT include ct.h here: doing so would create a + * circular dependency (ct.h includes common.h), and common.h itself never + * calls mld_zeroize() -- only the macro expansion does. Each translation + * unit that uses MLD_FREE therefore includes ct.h directly. */ +#define MLD_FREE(v, T, N, context) \ + do \ + { \ + mld_zeroize(mld_alloc_##v, sizeof(mld_alloc_##v)); \ + (v) = NULL; \ + } while (0) + +#else /* !MLD_CONFIG_CUSTOM_ALLOC_FREE */ + +/* Custom allocation */ + +/* + * The indirection here is necessary to use MLD_CONTEXT_PARAMETERS_3 here. + */ +#define MLD_APPLY(f, args) f args + +#define MLD_ALLOC(v, T, N, context) \ + MLD_APPLY(MLD_CUSTOM_ALLOC, MLD_CONTEXT_PARAMETERS_3(v, T, N, context)) + +#define MLD_FREE(v, T, N, context) \ + do \ + { \ + if (v != NULL) \ + { \ + mld_zeroize(v, sizeof(T) * (N)); \ + MLD_APPLY(MLD_CUSTOM_FREE, MLD_CONTEXT_PARAMETERS_3(v, T, N, context)); \ + v = NULL; \ + } \ + } while (0) + +#endif /* MLD_CONFIG_CUSTOM_ALLOC_FREE */ + +/****************************** Error codes ***********************************/ + +/* Generic failure condition */ +#define MLD_ERR_FAIL (-1) +/* An allocation failed. This can only happen if MLD_CONFIG_CUSTOM_ALLOC_FREE + * is defined and the provided MLD_CUSTOM_ALLOC can fail. */ +#define MLD_ERR_OUT_OF_MEMORY (-2) +/* An rng failure occured. Might be due to insufficient entropy or + * system misconfiguration. */ +#define MLD_ERR_RNG_FAIL (-3) +/* The signing rejection-sampling loop exceeded + * MLD_CONFIG_MAX_SIGNING_ATTEMPTS iterations without producing a valid + * signature. With a FIPS 204 Appendix C compliant bound (>= 814) this + * has probability < 2^-256. */ +#define MLD_ERR_SIGN_ATTEMPTS_EXHAUSTED (-4) + +/* Disjunction over the full set of MLD_ERR_XXX failure codes. + * + * Intended for use in top-level `ensures` clauses that admit every + * possible error. Narrower contracts should enumerate only the + * specific errors they can actually return. */ +#define MLD_ANY_ERROR(err) \ + ((err) == MLD_ERR_FAIL || (err) == MLD_ERR_OUT_OF_MEMORY || \ + (err) == MLD_ERR_RNG_FAIL || (err) == MLD_ERR_SIGN_ATTEMPTS_EXHAUSTED) + + +#endif /* !__ASSEMBLER__ */ + +#endif /* !MLD_COMMON_H */ diff --git a/mldsa_native/src/context.h b/mldsa_native/src/context.h new file mode 100644 index 0000000..901fc63 --- /dev/null +++ b/mldsa_native/src/context.h @@ -0,0 +1,68 @@ +/* + * Copyright (c) The mldsa-native project authors + * SPDX-License-Identifier: Apache-2.0 OR ISC OR MIT + */ +#ifndef MLD_CONTEXT_H +#define MLD_CONTEXT_H + +/* This header is included by common.h once the configuration has been pulled + * in; it is not meant to be included directly. */ + +/* + * If the integration wants to provide a context parameter for use in + * platform-specific hooks, then it should define this parameter. + * + * The MLD_CONTEXT_PARAMETERS_n macros are intended to be used with macros + * defining the function names and expand to either pass or discard the context + * argument as required by the current build. If there is no context parameter + * requested then these are removed from the prototypes and from all calls. + */ +#ifdef MLD_CONFIG_CONTEXT_PARAMETER +#define MLD_CONTEXT_PARAMETERS_0(context) (context) +#define MLD_CONTEXT_PARAMETERS_1(arg0, context) (arg0, context) +#define MLD_CONTEXT_PARAMETERS_2(arg0, arg1, context) (arg0, arg1, context) +#define MLD_CONTEXT_PARAMETERS_3(arg0, arg1, arg2, context) \ + (arg0, arg1, arg2, context) +#define MLD_CONTEXT_PARAMETERS_4(arg0, arg1, arg2, arg3, context) \ + (arg0, arg1, arg2, arg3, context) +#define MLD_CONTEXT_PARAMETERS_5(arg0, arg1, arg2, arg3, arg4, context) \ + (arg0, arg1, arg2, arg3, arg4, context) +#define MLD_CONTEXT_PARAMETERS_6(arg0, arg1, arg2, arg3, arg4, arg5, context) \ + (arg0, arg1, arg2, arg3, arg4, arg5, context) +#define MLD_CONTEXT_PARAMETERS_7(arg0, arg1, arg2, arg3, arg4, arg5, arg6, \ + context) \ + (arg0, arg1, arg2, arg3, arg4, arg5, arg6, context) +#define MLD_CONTEXT_PARAMETERS_8(arg0, arg1, arg2, arg3, arg4, arg5, arg6, \ + arg7, context) \ + (arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, context) +#define MLD_CONTEXT_PARAMETERS_9(arg0, arg1, arg2, arg3, arg4, arg5, arg6, \ + arg7, arg8, context) \ + (arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, context) +#else /* MLD_CONFIG_CONTEXT_PARAMETER */ +#define MLD_CONTEXT_PARAMETERS_0(context) () +#define MLD_CONTEXT_PARAMETERS_1(arg0, context) (arg0) +#define MLD_CONTEXT_PARAMETERS_2(arg0, arg1, context) (arg0, arg1) +#define MLD_CONTEXT_PARAMETERS_3(arg0, arg1, arg2, context) (arg0, arg1, arg2) +#define MLD_CONTEXT_PARAMETERS_4(arg0, arg1, arg2, arg3, context) \ + (arg0, arg1, arg2, arg3) +#define MLD_CONTEXT_PARAMETERS_5(arg0, arg1, arg2, arg3, arg4, context) \ + (arg0, arg1, arg2, arg3, arg4) +#define MLD_CONTEXT_PARAMETERS_6(arg0, arg1, arg2, arg3, arg4, arg5, context) \ + (arg0, arg1, arg2, arg3, arg4, arg5) +#define MLD_CONTEXT_PARAMETERS_7(arg0, arg1, arg2, arg3, arg4, arg5, arg6, \ + context) \ + (arg0, arg1, arg2, arg3, arg4, arg5, arg6) +#define MLD_CONTEXT_PARAMETERS_8(arg0, arg1, arg2, arg3, arg4, arg5, arg6, \ + arg7, context) \ + (arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7) +#define MLD_CONTEXT_PARAMETERS_9(arg0, arg1, arg2, arg3, arg4, arg5, arg6, \ + arg7, arg8, context) \ + (arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8) +#endif /* !MLD_CONFIG_CONTEXT_PARAMETER */ + +#if defined(MLD_CONFIG_CONTEXT_PARAMETER_TYPE) != \ + defined(MLD_CONFIG_CONTEXT_PARAMETER) +#error MLD_CONFIG_CONTEXT_PARAMETER_TYPE must be defined if and only if MLD_CONFIG_CONTEXT_PARAMETER is defined +#endif + +#endif /* !MLD_CONTEXT_H */ diff --git a/mldsa_native/src/ct.c b/mldsa_native/src/ct.c new file mode 100644 index 0000000..010b1a5 --- /dev/null +++ b/mldsa_native/src/ct.c @@ -0,0 +1,21 @@ +/* + * Copyright (c) The mlkem-native project authors + * Copyright (c) The mldsa-native project authors + * SPDX-License-Identifier: Apache-2.0 OR ISC OR MIT + */ +#include "ct.h" + +#if !defined(MLD_USE_ASM_VALUE_BARRIER) && \ + !defined(MLD_CONFIG_MULTILEVEL_NO_SHARED) +/* + * Masking value used in constant-time functions from + * ct.h to block the compiler's range analysis and + * thereby reduce the risk of compiler-introduced branches. + */ +volatile uint64_t mld_ct_opt_blocker_u64 = 0; + +#else /* !MLD_USE_ASM_VALUE_BARRIER && !MLD_CONFIG_MULTILEVEL_NO_SHARED */ + +MLD_EMPTY_CU(ct) + +#endif /* !(!MLD_USE_ASM_VALUE_BARRIER && !MLD_CONFIG_MULTILEVEL_NO_SHARED) */ diff --git a/mldsa_native/src/ct.h b/mldsa_native/src/ct.h new file mode 100644 index 0000000..1a08a83 --- /dev/null +++ b/mldsa_native/src/ct.h @@ -0,0 +1,372 @@ +/* + * Copyright (c) The mlkem-native project authors + * Copyright (c) The mldsa-native project authors + * SPDX-License-Identifier: Apache-2.0 OR ISC OR MIT + */ + +/* References + * ========== + * + * - [FIPS204] + * FIPS 204 Module-Lattice-Based Digital Signature Standard + * National Institute of Standards and Technology + * https://csrc.nist.gov/pubs/fips/204/final + * + * - [libmceliece] + * libmceliece implementation of Classic McEliece + * Bernstein, Chou + * https://lib.mceliece.org/ + * + * - [optblocker] + * PQC forum post on opt-blockers using volatile globals + * Daniel J. Bernstein + * https://groups.google.com/a/list.nist.gov/g/pqc-forum/c/hqbtIGFKIpU/m/H14H0wOlBgAJ + */ + +#ifndef MLD_CT_H +#define MLD_CT_H + +#include "cbmc.h" +#include "common.h" + +/* Constant-time comparisons and conditional operations + + We reduce the risk for compilation into variable-time code + through the use of 'value barriers'. + + Functionally, a value barrier is a no-op. To the compiler, however, + it constitutes an arbitrary modification of its input, and therefore + harden's value propagation and range analysis. + + We consider two approaches to implement a value barrier: + - An empty inline asm block which marks the target value as clobbered. + - XOR'ing with the value of a volatile global that's set to 0; + see @[optblocker] for a discussion of this idea, and + @[libmceliece, inttypes/crypto_intN.h] for an implementation. + + The first approach is cheap because it only prevents the compiler + from reasoning about the value of the variable past the barrier, + but does not directly generate additional instructions. + + The second approach generates redundant loads and XOR operations + and therefore comes at a higher runtime cost. However, it appears + more robust towards optimization, as compilers should never drop + a volatile load. + + We use the empty-ASM value barrier for GCC and clang, and fall + back to the global volatile barrier otherwise. + + The global value barrier can be forced by setting + MLD_CONFIG_NO_ASM_VALUE_BARRIER. + +*/ + +#if defined(MLD_HAVE_INLINE_ASM) && !defined(MLD_CONFIG_NO_ASM_VALUE_BARRIER) +#define MLD_USE_ASM_VALUE_BARRIER +#endif + + +#if !defined(MLD_USE_ASM_VALUE_BARRIER) +/* + * Declaration of global volatile that the global value barrier + * is loading from and masking with. + */ +#define mld_ct_opt_blocker_u64 MLD_NAMESPACE(ct_opt_blocker_u64) +extern volatile uint64_t mld_ct_opt_blocker_u64; + + +/* Helper functions for obtaining global masks of various sizes */ + +/* This contract is not proved but treated as an axiom. + * + * Its validity relies on the assumption that the global opt-blocker + * constant mld_ct_opt_blocker_u64 is not modified. + */ +MLD_MUST_CHECK_RETURN_VALUE +static MLD_INLINE uint64_t mld_ct_get_optblocker_u64(void) +__contract__(ensures(return_value == 0)) { return mld_ct_opt_blocker_u64; } + +MLD_MUST_CHECK_RETURN_VALUE +static MLD_INLINE int64_t mld_ct_get_optblocker_i64(void) +__contract__(ensures(return_value == 0)) { return (int64_t)mld_ct_get_optblocker_u64(); } + +MLD_MUST_CHECK_RETURN_VALUE +static MLD_INLINE uint32_t mld_ct_get_optblocker_u32(void) +__contract__(ensures(return_value == 0)) { return (uint32_t)mld_ct_get_optblocker_u64(); } + +MLD_MUST_CHECK_RETURN_VALUE +static MLD_INLINE uint8_t mld_ct_get_optblocker_u8(void) +__contract__(ensures(return_value == 0)) { return (uint8_t)mld_ct_get_optblocker_u64(); } + +/* Opt-blocker based implementation of value barriers */ +MLD_MUST_CHECK_RETURN_VALUE +static MLD_INLINE int64_t mld_value_barrier_i64(int64_t b) +__contract__(ensures(return_value == b)) { return (b ^ mld_ct_get_optblocker_i64()); } + +MLD_MUST_CHECK_RETURN_VALUE +static MLD_INLINE uint32_t mld_value_barrier_u32(uint32_t b) +__contract__(ensures(return_value == b)) { return (b ^ mld_ct_get_optblocker_u32()); } + +MLD_MUST_CHECK_RETURN_VALUE +static MLD_INLINE uint8_t mld_value_barrier_u8(uint8_t b) +__contract__(ensures(return_value == b)) { return (b ^ mld_ct_get_optblocker_u8()); } + + +#else /* !MLD_USE_ASM_VALUE_BARRIER */ +MLD_MUST_CHECK_RETURN_VALUE +static MLD_INLINE int64_t mld_value_barrier_i64(int64_t b) +__contract__(ensures(return_value == b)) +{ + __asm__ volatile("" : "+r"(b)); + return b; +} + +MLD_MUST_CHECK_RETURN_VALUE +static MLD_INLINE uint32_t mld_value_barrier_u32(uint32_t b) +__contract__(ensures(return_value == b)) +{ + __asm__ volatile("" : "+r"(b)); + return b; +} + +MLD_MUST_CHECK_RETURN_VALUE +static MLD_INLINE uint8_t mld_value_barrier_u8(uint8_t b) +__contract__(ensures(return_value == b)) +{ + __asm__ volatile("" : "+r"(b)); + return b; +} +#endif /* MLD_USE_ASM_VALUE_BARRIER */ + +#ifdef CBMC +#pragma CPROVER check push +#pragma CPROVER check disable "conversion" +#endif + +/** + * Cast uint32 value to int32. + * + * @param x Input value. + * + * @return For uint32_t x, the unique y in int32_t so that x == y mod 2^32. + * Concretely: + * - x < 2^31: returns x + * - x >= 2^31: returns x - 2^31 + */ +MLD_MUST_CHECK_RETURN_VALUE +static MLD_ALWAYS_INLINE int32_t mld_cast_uint32_to_int32(uint32_t x) +{ + /* + * PORTABILITY: This relies on uint32_t -> int32_t + * being implemented as the inverse of int32_t -> uint32_t, + * which is implementation-defined (C99 6.3.1.3 (3)) + * CBMC (correctly) fails to prove this conversion is OK, + * so we have to suppress that check here + */ + return (int32_t)x; +} + +#ifdef CBMC +#pragma CPROVER check pop +#endif + + +/** + * Cast int64 value to uint32 as per C standard. + * + * @param x Input value. + * + * @return For int64_t x, the unique y in uint32_t so that x == y mod 2^32. + */ +MLD_MUST_CHECK_RETURN_VALUE +static MLD_ALWAYS_INLINE uint32_t mld_cast_int64_to_uint32(int64_t x) +{ + return (uint32_t)(x & (int64_t)UINT32_MAX); +} + +/** + * Cast int32 value to uint32 as per C standard. + * + * @param x Input value. + * + * @return For int32_t x, the unique y in uint32_t so that x == y mod 2^32. + */ +MLD_MUST_CHECK_RETURN_VALUE +static MLD_ALWAYS_INLINE uint32_t mld_cast_int32_to_uint32(int32_t x) +{ + return mld_cast_int64_to_uint32((int64_t)x); +} + +/** + * Functionally equivalent to cond ? a : b, but implemented with guards against + * compiler-introduced branches. + * + * @param a First alternative. + * @param b Second alternative. + * @param cond Condition variable. + * + * @return a if cond is 0xFFFFFFFF, b if cond is 0. + */ +MLD_MUST_CHECK_RETURN_VALUE +static MLD_INLINE int32_t mld_ct_sel_int32(int32_t a, int32_t b, uint32_t cond) +__contract__( + requires(cond == 0x0 || cond == 0xFFFFFFFF) + ensures(return_value == (cond ? a : b)) +) +{ + uint32_t au = mld_cast_int32_to_uint32(a); + uint32_t bu = mld_cast_int32_to_uint32(b); + uint32_t res = bu ^ (mld_value_barrier_u32(cond) & (au ^ bu)); + return mld_cast_uint32_to_int32(res); +} + +/** + * Return 0 if input is zero, and -1 otherwise. + * + * @param x Value to be converted into a mask. + */ +MLD_MUST_CHECK_RETURN_VALUE +static MLD_INLINE uint32_t mld_ct_cmask_nonzero_u32(uint32_t x) +__contract__(ensures(return_value == ((x == 0) ? 0 : 0xFFFFFFFF))) +{ + int64_t tmp = mld_value_barrier_i64(-((int64_t)x)); + /* + * PORTABILITY: Right-shift on a signed integer is + * implementation-defined for negative left argument. + * Here, we assume it's sign-preserving "arithmetic" shift right. + * See (C99 6.5.7 (5)) + */ + tmp >>= 32; + return mld_cast_int64_to_uint32(tmp); +} + +/** + * Return 0 if input is zero, and -1 otherwise. + * + * @param x Value to be converted into a mask. + */ +MLD_MUST_CHECK_RETURN_VALUE +static MLD_INLINE uint8_t mld_ct_cmask_nonzero_u8(uint8_t x) +__contract__(ensures(return_value == ((x == 0) ? 0 : 0xFF))) +{ + uint32_t mask = mld_ct_cmask_nonzero_u32((uint32_t)x); + return (uint8_t)(mask & 0xFF); +} + +/** + * Return 0 if input is non-negative, and -1 otherwise. + * + * @param x Value to be converted into a mask. + */ +MLD_MUST_CHECK_RETURN_VALUE +static MLD_INLINE uint32_t mld_ct_cmask_neg_i32(int32_t x) +__contract__( + ensures(return_value == ((x < 0) ? 0xFFFFFFFF : 0)) +) +{ + int64_t tmp = mld_value_barrier_i64((int64_t)x); + /* + * PORTABILITY: Right-shift on a signed integer is + * implementation-defined for negative left argument. + * Here, we assume it's sign-preserving "arithmetic" shift right. + * See (C99 6.5.7 (5)) + */ + tmp >>= 31; + return mld_cast_int64_to_uint32(tmp); +} + +/** + * Return -x if x<0, x otherwise. + * + * @param x Input value. + */ +MLD_MUST_CHECK_RETURN_VALUE +static MLD_INLINE int32_t mld_ct_abs_i32(int32_t x) +__contract__( + requires(x >= -INT32_MAX) + ensures(return_value == ((x < 0) ? -x : x)) +) +{ + return mld_ct_sel_int32(-x, x, mld_ct_cmask_neg_i32(x)); +} + +/** + * Compare two arrays for equality in constant time. + * + * @param[in] a Pointer to first byte array. + * @param[in] b Pointer to second byte array. + * @param len Length of the byte arrays, upper-bounded to UINT16_MAX to + * control proof complexity only. + * + * @return 0 if the byte arrays are equal, 0xFF otherwise. + */ +MLD_MUST_CHECK_RETURN_VALUE +static MLD_INLINE uint8_t mld_ct_memcmp(const uint8_t *a, const uint8_t *b, + const size_t len) +__contract__( + requires(len <= UINT16_MAX) + requires(memory_no_alias(a, len)) + requires(memory_no_alias(b, len)) + ensures((return_value == 0) || (return_value == 0xFF)) + ensures((return_value == 0) == forall(i, 0, len, (a[i] == b[i])))) +{ + uint8_t r = 0, s = 0; + unsigned i; + + for (i = 0; i < len; i++) + __loop__( + invariant(i <= len) + invariant((r == 0) == (forall(k, 0, i, (a[k] == b[k])))) + decreases(len - i)) + { + r |= a[i] ^ b[i]; + /* s is useless, but prevents the loop from being aborted once r=0xff. */ + s ^= a[i] ^ b[i]; + } + + /* + * - Convert r into a mask; this may not be necessary, but is an additional + * safeguard + * towards leaking information about a and b. + * - XOR twice with s, separated by a value barrier, to prevent the compile + * from dropping the s computation in the loop. + */ + return (mld_value_barrier_u8(mld_ct_cmask_nonzero_u8(r) ^ s) ^ s); +} + +/** + * Force-zeroize a buffer. + * + * @[FIPS204, Section 3.6.3] Destruction of intermediate values. + * + * @param[out] ptr Pointer to buffer to be zeroed. + * @param len Amount of bytes to be zeroed. + */ +#if !defined(MLD_CONFIG_CUSTOM_ZEROIZE) +#if defined(MLD_SYS_WINDOWS) +#include +static MLD_INLINE void mld_zeroize(void *ptr, size_t len) +__contract__( + requires(memory_no_alias(ptr, len)) + assigns(memory_slice(ptr, len))) { SecureZeroMemory(ptr, len); } +#elif defined(MLD_HAVE_INLINE_ASM) +#include +static MLD_INLINE void mld_zeroize(void *ptr, size_t len) +__contract__( + requires(memory_no_alias(ptr, len)) + assigns(memory_slice(ptr, len))) +{ + memset(ptr, 0, len); + /* This follows OpenSSL and seems sufficient to prevent the compiler + * from optimizing away the memset. + * + * If there was a reliable way to detect availability of memset_s(), + * that would be preferred. */ + __asm__ __volatile__("" : : "r"(ptr) : "memory"); +} +#else /* !MLD_SYS_WINDOWS && MLD_HAVE_INLINE_ASM */ +#error No plausibly-secure implementation of mld_zeroize available. Please provide your own using MLD_CONFIG_CUSTOM_ZEROIZE. +#endif /* !MLD_SYS_WINDOWS && !MLD_HAVE_INLINE_ASM */ +#endif /* !MLD_CONFIG_CUSTOM_ZEROIZE */ + +#endif /* !MLD_CT_H */ diff --git a/mldsa_native/src/debug.c b/mldsa_native/src/debug.c new file mode 100644 index 0000000..a55d51e --- /dev/null +++ b/mldsa_native/src/debug.c @@ -0,0 +1,73 @@ +/* + * Copyright (c) The mlkem-native project authors + * Copyright (c) The mldsa-native project authors + * SPDX-License-Identifier: Apache-2.0 OR ISC OR MIT + */ + +/* NOTE: You can remove this file unless you compile with MLDSA_DEBUG. */ + +#include "common.h" + +#if !defined(MLD_CONFIG_MULTILEVEL_NO_SHARED) + +#if defined(MLDSA_DEBUG) + +#include +#include +#include +#include "debug.h" + +#define MLD_DEBUG_ERROR_HEADER "[ERROR:%s:%04d] " + +void mld_debug_check_assert(const char *file, int line, const int val) +{ + if (val == 0) + { + fprintf(stderr, MLD_DEBUG_ERROR_HEADER "Assertion failed (value %d)\n", + file, line, val); + exit(1); + } +} + +void mld_debug_check_bounds(const char *file, int line, const int32_t *ptr, + unsigned len, int64_t lower_bound_exclusive, + int64_t upper_bound_exclusive) +{ + int err = 0; + unsigned i; + for (i = 0; i < len; i++) + { + int32_t val = ptr[i]; + if (!(val > lower_bound_exclusive && val < upper_bound_exclusive)) + { + fprintf(stderr, + MLD_DEBUG_ERROR_HEADER + "Bounds assertion failed: Index %u, value %d out of bounds " + "(%" PRId64 ",%" PRId64 ")\n", + file, line, i, (int)val, lower_bound_exclusive, + upper_bound_exclusive); + err = 1; + } + } + + if (err == 1) + { + exit(1); + } +} + +#else /* MLDSA_DEBUG */ + +MLD_EMPTY_CU(debug) + +#endif /* !MLDSA_DEBUG */ + +#else /* !MLD_CONFIG_MULTILEVEL_NO_SHARED */ + +MLD_EMPTY_CU(debug) + +#endif /* MLD_CONFIG_MULTILEVEL_NO_SHARED */ + +/* To facilitate single-compilation-unit (SCU) builds, undefine all macros. + * Don't modify by hand -- this is auto-generated by scripts/autogen. */ +#undef MLD_DEBUG_ERROR_HEADER diff --git a/mldsa_native/src/debug.h b/mldsa_native/src/debug.h new file mode 100644 index 0000000..b6b4689 --- /dev/null +++ b/mldsa_native/src/debug.h @@ -0,0 +1,123 @@ +/* + * Copyright (c) The mlkem-native project authors + * Copyright (c) The mldsa-native project authors + * SPDX-License-Identifier: Apache-2.0 OR ISC OR MIT + */ +#ifndef MLD_DEBUG_H +#define MLD_DEBUG_H +#include "common.h" + +#if defined(MLDSA_DEBUG) + +/** + * Check debug assertion. + * + * Prints an error message to stderr and calls exit(1) if not. + * + * @param file Filename. + * @param line Line number. + * @param val Value asserted to be non-zero. + */ +#define mld_debug_check_assert MLD_NAMESPACE(mldsa_debug_assert) +void mld_debug_check_assert(const char *file, int line, const int val); + +/** + * Check whether values in an array of int32_t are within specified bounds. + * + * Prints an error message to stderr and calls exit(1) if not. + * + * @param file Filename. + * @param line Line number. + * @param[in] ptr Base of array to be checked. + * @param len Number of int32_t in ptr. + * @param lower_bound_exclusive Exclusive lower bound. + * @param upper_bound_exclusive Exclusive upper bound. + */ +#define mld_debug_check_bounds MLD_NAMESPACE(mldsa_debug_check_bounds) +void mld_debug_check_bounds(const char *file, int line, const int32_t *ptr, + unsigned len, int64_t lower_bound_exclusive, + int64_t upper_bound_exclusive); + +/* Check assertion, calling exit() upon failure + * + * val: Value that's asserted to be non-zero + */ +#define mld_assert(val) mld_debug_check_assert(__FILE__, __LINE__, (val)) + +/* Check bounds in array of int32_t's + * ptr: Base of int32_t array; will be explicitly cast to int32_t*, + * so you may pass a byte-compatible type such as mld_poly or mld_polyvec. + * len: Number of int32_t in array + * value_lb: Inclusive lower value bound + * value_ub: Exclusive upper value bound */ +#define mld_assert_bound(ptr, len, value_lb, value_ub) \ + mld_debug_check_bounds(__FILE__, __LINE__, (const int32_t *)(ptr), (len), \ + ((int64_t)(value_lb)) - 1, (value_ub)) + +/* Check absolute bounds in array of int32_t's + * ptr: Base of array, expression of type int32_t* + * len: Number of int32_t in array + * value_abs_bd: Exclusive absolute upper bound */ +#define mld_assert_abs_bound(ptr, len, value_abs_bd) \ + mld_assert_bound((ptr), (len), (-((int64_t)(value_abs_bd)) + 1), \ + (value_abs_bd)) + +/* Version of bounds assertions for 2-dimensional arrays */ +#define mld_assert_bound_2d(ptr, len0, len1, value_lb, value_ub) \ + mld_assert_bound((ptr), ((len0) * (len1)), (value_lb), (value_ub)) + +#define mld_assert_abs_bound_2d(ptr, len0, len1, value_abs_bd) \ + mld_assert_abs_bound((ptr), ((len0) * (len1)), (value_abs_bd)) + +/* When running CBMC, convert debug assertions into proof obligations */ +#elif defined(CBMC) +#include "cbmc.h" + +#define mld_assert(val) cassert(val) + +#define mld_assert_bound(ptr, len, value_lb, value_ub) \ + cassert(array_bound(((int32_t *)(ptr)), 0, (len), (value_lb), (value_ub))) + +#define mld_assert_abs_bound(ptr, len, value_abs_bd) \ + cassert(array_abs_bound(((int32_t *)(ptr)), 0, (len), (value_abs_bd))) + +/* Because of https://github.com/diffblue/cbmc/issues/8570, we can't + * just use a single flattened array_bound(...) here. */ +#define mld_assert_bound_2d(ptr, M, N, value_lb, value_ub) \ + cassert(forall(kN, 0, (M), \ + array_bound(&((int32_t (*)[(N)])(ptr))[kN][0], 0, (N), \ + (value_lb), (value_ub)))) + +#define mld_assert_abs_bound_2d(ptr, M, N, value_abs_bd) \ + cassert(forall(kN, 0, (M), \ + array_abs_bound(&((int32_t (*)[(N)])(ptr))[kN][0], 0, (N), \ + (value_abs_bd)))) + +#else /* !MLDSA_DEBUG && CBMC */ + +#define mld_assert(val) \ + do \ + { \ + } while (0) +#define mld_assert_bound(ptr, len, value_lb, value_ub) \ + do \ + { \ + } while (0) +#define mld_assert_abs_bound(ptr, len, value_abs_bd) \ + do \ + { \ + } while (0) + +#define mld_assert_bound_2d(ptr, len0, len1, value_lb, value_ub) \ + do \ + { \ + } while (0) + +#define mld_assert_abs_bound_2d(ptr, len0, len1, value_abs_bd) \ + do \ + { \ + } while (0) + + +#endif /* !MLDSA_DEBUG && !CBMC */ +#endif /* !MLD_DEBUG_H */ diff --git a/mldsa_native/src/fips202/fips202.c b/mldsa_native/src/fips202/fips202.c new file mode 100644 index 0000000..fa7f682 --- /dev/null +++ b/mldsa_native/src/fips202/fips202.c @@ -0,0 +1,270 @@ +/* + * Copyright (c) The mldsa-native project authors + * SPDX-License-Identifier: Apache-2.0 OR ISC OR MIT + */ + +/* References + * ========== + * + * - [FIPS204] + * FIPS 204 Module-Lattice-Based Digital Signature Standard + * National Institute of Standards and Technology + * https://csrc.nist.gov/pubs/fips/204/final + * + * - [mupq] + * Common files for pqm4, pqm3, pqriscv + * Kannwischer, Petri, Rijneveld, Schwabe, Stoffelen + * https://github.com/mupq/mupq + * + * - [supercop] + * SUPERCOP benchmarking framework + * Daniel J. Bernstein + * http://bench.cr.yp.to/supercop.html + * + * - [tweetfips] + * 'tweetfips202' FIPS202 implementation + * Van Assche, Bernstein, Schwabe + * https://keccak.team/2015/tweetfips202.html + */ + +/* Based on the CC0 implementation from @[mupq] and the public domain + * implementation @[supercop, crypto_hash/keccakc512/simple/] + * by Ronny Van Keer, and the public domain @[tweetfips] implementation. */ + +#include + +#include "../common.h" +#include "../ct.h" +#include "fips202.h" +#include "keccakf1600.h" +#if !defined(MLD_CONFIG_MULTILEVEL_NO_SHARED) + +/** + * Initializes the Keccak state. + * + * @param[out] s Pointer to Keccak state. + */ +static void keccak_init(uint64_t s[MLD_KECCAK_LANES]) +__contract__( + requires(memory_no_alias(s, sizeof(uint64_t) * MLD_KECCAK_LANES)) + assigns(memory_slice(s, sizeof(uint64_t) * MLD_KECCAK_LANES)) +) +{ + mld_memset(s, 0, sizeof(uint64_t) * MLD_KECCAK_LANES); +} + +/** + * Absorb step of Keccak; incremental. + * + * @param[in,out] s Pointer to Keccak state. + * @param pos Position in current block to be absorbed. + * @param r Rate in bytes (e.g., 168 for SHAKE128). + * @param[in] in Pointer to input to be absorbed into s. + * @param inlen Length of input in bytes. + * + * @return New position pos in current block. + */ +static unsigned int keccak_absorb(uint64_t s[MLD_KECCAK_LANES], + unsigned int pos, unsigned int r, + const uint8_t *in, size_t inlen) +__contract__( + requires(inlen <= MLD_MAX_BUFFER_SIZE) + requires(r > 0) + requires(r < sizeof(uint64_t) * MLD_KECCAK_LANES) + requires(pos <= r) + requires(memory_no_alias(s, sizeof(uint64_t) * MLD_KECCAK_LANES)) + requires(memory_no_alias(in, inlen)) + assigns(memory_slice(s, sizeof(uint64_t) * MLD_KECCAK_LANES)) + ensures(return_value < r)) +{ + while (inlen >= r - pos) + __loop__( + assigns(pos, in, inlen, + memory_slice(s, sizeof(uint64_t) * MLD_KECCAK_LANES)) + invariant(inlen <= loop_entry(inlen)) + invariant(pos <= r) + invariant(in == loop_entry(in) + (loop_entry(inlen) - inlen)) + decreases(inlen + pos)) + { + mld_keccakf1600_xor_bytes(s, in, pos, r - pos); + inlen -= r - pos; + in += r - pos; + mld_keccakf1600_permute(s); + pos = 0; + } + /* Safety: At this point, inlen < r, so the truncation to unsigned is safe. */ + mld_keccakf1600_xor_bytes(s, in, pos, (unsigned)inlen); + + /* Safety: At this point, inlen < r and pos <= r so the truncation to unsigned + * is safe. */ + return (unsigned)(pos + inlen); +} + +/** + * Finalize absorb step. + * + * @param[in,out] s Pointer to Keccak state. + * @param pos Position in current block to be absorbed. + * @param r Rate in bytes (e.g., 168 for SHAKE128). + * @param p Domain separation byte. + */ +static void keccak_finalize(uint64_t s[MLD_KECCAK_LANES], unsigned int pos, + unsigned int r, uint8_t p) +__contract__( + requires(pos <= r && r < sizeof(uint64_t) * MLD_KECCAK_LANES) + requires((r / 8) >= 1) + requires(memory_no_alias(s, sizeof(uint64_t) * MLD_KECCAK_LANES)) + assigns(memory_slice(s, sizeof(uint64_t) * MLD_KECCAK_LANES)) +) +{ + uint8_t b = 0x80; + mld_keccakf1600_xor_bytes(s, &p, pos, 1); + mld_keccakf1600_xor_bytes(s, &b, r - 1, 1); +} + +/** + * Squeeze step of Keccak. Squeezes arbitrarily many bytes. Modifies the + * state. Can be called multiple times to keep squeezing, i.e., is + * incremental. + * + * @param[out] out Pointer to output data. + * @param outlen Number of bytes to be squeezed (written to out). + * @param[in,out] s Pointer to input/output Keccak state. + * @param pos Number of bytes in current block already squeezed. + * @param r Rate in bytes (e.g., 168 for SHAKE128). + * + * @return New position pos in current block. + */ +static unsigned int keccak_squeeze(uint8_t *out, size_t outlen, + uint64_t s[MLD_KECCAK_LANES], + unsigned int pos, unsigned int r) +__contract__( + requires((r == SHAKE128_RATE && pos <= SHAKE128_RATE) || + (r == SHAKE256_RATE && pos <= SHAKE256_RATE) || + (r == SHA3_512_RATE && pos <= SHA3_512_RATE)) + requires(outlen <= 8 * r /* somewhat arbitrary bound */) + requires(memory_no_alias(s, sizeof(uint64_t) * MLD_KECCAK_LANES)) + requires(memory_no_alias(out, outlen)) + assigns(memory_slice(s, sizeof(uint64_t) * MLD_KECCAK_LANES)) + assigns(memory_slice(out, outlen)) + ensures(return_value <= r)) +{ + unsigned int i; + size_t out_offset = 0; + + /* Reference: This code is re-factored from the reference implementation + * to facilitate proof with CBMC and to improve readability. + * + * Take a mutable copy of outlen to count down the number of bytes + * still to squeeze. The initial value of outlen is needed for the CBMC + * assigns() clauses. */ + size_t bytes_to_go = outlen; + + while (bytes_to_go > 0) + __loop__( + assigns(i, bytes_to_go, pos, out_offset, memory_slice(s, sizeof(uint64_t) * MLD_KECCAK_LANES), memory_slice(out, outlen)) + invariant(bytes_to_go <= outlen) + invariant(out_offset == outlen - bytes_to_go) + invariant(pos <= r) + decreases(bytes_to_go) + ) + { + if (pos == r) + { + mld_keccakf1600_permute(s); + pos = 0; + } + /* Safety: If bytes_to_go < r - pos, truncation to unsigned is safe. */ + i = bytes_to_go < r - pos ? (unsigned)bytes_to_go : r - pos; + mld_keccakf1600_extract_bytes(s, out + out_offset, pos, i); + bytes_to_go -= i; + pos += i; + out_offset += i; + } + + return pos; +} + +MLD_INTERNAL_API +void mld_shake128_init(mld_shake128ctx *state) +{ + keccak_init(state->s); + state->pos = 0; +} + +MLD_INTERNAL_API +void mld_shake128_absorb(mld_shake128ctx *state, const uint8_t *in, + size_t inlen) +{ + state->pos = keccak_absorb(state->s, state->pos, SHAKE128_RATE, in, inlen); +} + +MLD_INTERNAL_API +void mld_shake128_finalize(mld_shake128ctx *state) +{ + keccak_finalize(state->s, state->pos, SHAKE128_RATE, 0x1F); + state->pos = SHAKE128_RATE; +} + +MLD_INTERNAL_API +void mld_shake128_squeeze(uint8_t *out, size_t outlen, mld_shake128ctx *state) +{ + state->pos = keccak_squeeze(out, outlen, state->s, state->pos, SHAKE128_RATE); +} + +MLD_INTERNAL_API +void mld_shake128_release(mld_shake128ctx *state) +{ + /* @[FIPS204, Section 3.6.3] Destruction of intermediate values. */ + mld_zeroize(state, sizeof(mld_shake128ctx)); +} + +MLD_INTERNAL_API +void mld_shake256_init(mld_shake256ctx *state) +{ + keccak_init(state->s); + state->pos = 0; +} + +MLD_INTERNAL_API +void mld_shake256_absorb(mld_shake256ctx *state, const uint8_t *in, + size_t inlen) +{ + state->pos = keccak_absorb(state->s, state->pos, SHAKE256_RATE, in, inlen); +} + +MLD_INTERNAL_API +void mld_shake256_finalize(mld_shake256ctx *state) +{ + keccak_finalize(state->s, state->pos, SHAKE256_RATE, 0x1F); + state->pos = SHAKE256_RATE; +} + +MLD_INTERNAL_API +void mld_shake256_squeeze(uint8_t *out, size_t outlen, mld_shake256ctx *state) +{ + state->pos = keccak_squeeze(out, outlen, state->s, state->pos, SHAKE256_RATE); +} + +MLD_INTERNAL_API +void mld_shake256_release(mld_shake256ctx *state) +{ + /* @[FIPS204, Section 3.6.3] Destruction of intermediate values. */ + mld_zeroize(state, sizeof(mld_shake256ctx)); +} + +#if !defined(MLD_CONFIG_NO_KEYPAIR_API) || !defined(MLD_CONFIG_CORE_API_ONLY) +MLD_INTERNAL_API +void mld_shake256(uint8_t *out, size_t outlen, const uint8_t *in, size_t inlen) +{ + mld_shake256ctx state; + + mld_shake256_init(&state); + mld_shake256_absorb(&state, in, inlen); + mld_shake256_finalize(&state); + mld_shake256_squeeze(out, outlen, &state); + mld_shake256_release(&state); +} +#endif /* !MLD_CONFIG_NO_KEYPAIR_API || !MLD_CONFIG_CORE_API_ONLY */ + +#endif /* !MLD_CONFIG_MULTILEVEL_NO_SHARED */ diff --git a/mldsa_native/src/fips202/fips202.h b/mldsa_native/src/fips202/fips202.h new file mode 100644 index 0000000..c97628e --- /dev/null +++ b/mldsa_native/src/fips202/fips202.h @@ -0,0 +1,224 @@ +/* + * Copyright (c) The mldsa-native project authors + * SPDX-License-Identifier: Apache-2.0 OR ISC OR MIT + */ +#ifndef MLD_FIPS202_FIPS202_H +#define MLD_FIPS202_FIPS202_H + +#include +#include "../cbmc.h" +#include "../common.h" + +#define SHAKE128_RATE 168 +#define SHAKE256_RATE 136 +#define SHA3_256_RATE 136 +#define SHA3_512_RATE 72 +#define MLD_KECCAK_LANES 25 +#define SHA3_256_HASHBYTES 32 +#define SHA3_512_HASHBYTES 64 + + +/** Context for the incremental SHAKE128 XOF. */ +typedef struct +{ + uint64_t s[MLD_KECCAK_LANES]; /**< Keccak state. */ + unsigned int pos; /**< Byte position within the current Keccak block. */ +} mld_shake128ctx; + +/** Context for the incremental SHAKE256 XOF. */ +typedef struct +{ + uint64_t s[MLD_KECCAK_LANES]; /**< Keccak state. */ + unsigned int pos; /**< Byte position within the current Keccak block. */ +} mld_shake256ctx; + +#define mld_shake128_init MLD_NAMESPACE(shake128_init) +/** + * Initializes state for use as SHAKE128 XOF. + * + * @param[out] state Pointer to (uninitialized) state. + */ +MLD_INTERNAL_API +void mld_shake128_init(mld_shake128ctx *state) +__contract__( + requires(memory_no_alias(state, sizeof(mld_shake128ctx))) + assigns(memory_slice(state, sizeof(mld_shake128ctx))) + ensures(state->pos == 0) +); + +#define mld_shake128_absorb MLD_NAMESPACE(shake128_absorb) +/** + * Absorb step of the SHAKE128 XOF. Absorbs arbitrarily many bytes. Can be + * called multiple times to absorb multiple chunks of data. + * + * @param[in,out] state Pointer to (initialized) output state. + * @param[in] in Pointer to input to be absorbed into s. + * @param inlen Length of input in bytes. + */ +MLD_INTERNAL_API +void mld_shake128_absorb(mld_shake128ctx *state, const uint8_t *in, + size_t inlen) +__contract__( + requires(inlen <= MLD_MAX_BUFFER_SIZE) + requires(memory_no_alias(state, sizeof(mld_shake128ctx))) + requires(memory_no_alias(in, inlen)) + requires(state->pos <= SHAKE128_RATE) + assigns(memory_slice(state, sizeof(mld_shake128ctx))) + ensures(state->pos <= SHAKE128_RATE) +); + +#define mld_shake128_finalize MLD_NAMESPACE(shake128_finalize) +/** + * Concludes the absorb phase of the SHAKE128 XOF. + * + * @param[in,out] state Pointer to state. + */ +MLD_INTERNAL_API +void mld_shake128_finalize(mld_shake128ctx *state) +__contract__( + requires(memory_no_alias(state, sizeof(mld_shake128ctx))) + requires(state->pos <= SHAKE128_RATE) + assigns(memory_slice(state, sizeof(mld_shake128ctx))) + ensures(state->pos <= SHAKE128_RATE) +); + +#define mld_shake128_squeeze MLD_NAMESPACE(shake128_squeeze) +/** + * Squeeze step of SHAKE128 XOF. Squeezes arbitrarily many bytes. Can be + * called multiple times to keep squeezing. + * + * @param[out] out Pointer to output blocks. + * @param outlen Number of bytes to be squeezed (written to output). + * @param[in,out] state Pointer to input/output state. + */ +MLD_INTERNAL_API +void mld_shake128_squeeze(uint8_t *out, size_t outlen, mld_shake128ctx *state) +__contract__( + requires(outlen <= 8 * SHAKE128_RATE /* somewhat arbitrary bound */) + requires(memory_no_alias(state, sizeof(mld_shake128ctx))) + requires(memory_no_alias(out, outlen)) + requires(state->pos <= SHAKE128_RATE) + assigns(memory_slice(state, sizeof(mld_shake128ctx))) + assigns(memory_slice(out, outlen)) + ensures(state->pos <= SHAKE128_RATE) +); + +#define mld_shake128_release MLD_NAMESPACE(shake128_release) +/** + * Release and securely zero the SHAKE128 state. + * + * @param[in,out] state Pointer to state. + */ +MLD_INTERNAL_API +void mld_shake128_release(mld_shake128ctx *state) +__contract__( + requires(memory_no_alias(state, sizeof(mld_shake128ctx))) + assigns(memory_slice(state, sizeof(mld_shake128ctx))) +); + +#define mld_shake256_init MLD_NAMESPACE(shake256_init) +/** + * Initializes state for use as SHAKE256 XOF. + * + * @param[out] state Pointer to (uninitialized) state. + */ +MLD_INTERNAL_API +void mld_shake256_init(mld_shake256ctx *state) +__contract__( + requires(memory_no_alias(state, sizeof(mld_shake256ctx))) + assigns(memory_slice(state, sizeof(mld_shake256ctx))) + ensures(state->pos == 0) +); + +#define mld_shake256_absorb MLD_NAMESPACE(shake256_absorb) +/** + * Absorb step of the SHAKE256 XOF. Absorbs arbitrarily many bytes. Can be + * called multiple times to absorb multiple chunks of data. + * + * @param[in,out] state Pointer to (initialized) output state. + * @param[in] in Pointer to input to be absorbed into s. + * @param inlen Length of input in bytes. + */ +MLD_INTERNAL_API +void mld_shake256_absorb(mld_shake256ctx *state, const uint8_t *in, + size_t inlen) +__contract__( + requires(inlen <= MLD_MAX_BUFFER_SIZE) + requires(memory_no_alias(state, sizeof(mld_shake256ctx))) + requires(memory_no_alias(in, inlen)) + requires(state->pos <= SHAKE256_RATE) + assigns(memory_slice(state, sizeof(mld_shake256ctx))) + ensures(state->pos <= SHAKE256_RATE) +); + +#define mld_shake256_finalize MLD_NAMESPACE(shake256_finalize) +/** + * Concludes the absorb phase of the SHAKE256 XOF. + * + * @param[in,out] state Pointer to state. + */ +MLD_INTERNAL_API +void mld_shake256_finalize(mld_shake256ctx *state) +__contract__( + requires(memory_no_alias(state, sizeof(mld_shake256ctx))) + requires(state->pos <= SHAKE256_RATE) + assigns(memory_slice(state, sizeof(mld_shake256ctx))) + ensures(state->pos <= SHAKE256_RATE) +); + +#define mld_shake256_squeeze MLD_NAMESPACE(shake256_squeeze) +/** + * Squeeze step of SHAKE256 XOF. Squeezes arbitrarily many bytes. Can be + * called multiple times to keep squeezing. + * + * @param[out] out Pointer to output blocks. + * @param outlen Number of bytes to be squeezed (written to output). + * @param[in,out] state Pointer to input/output state. + */ +MLD_INTERNAL_API +void mld_shake256_squeeze(uint8_t *out, size_t outlen, mld_shake256ctx *state) +__contract__( + requires(outlen <= 8 * SHAKE256_RATE /* somewhat arbitrary bound */) + requires(memory_no_alias(state, sizeof(mld_shake256ctx))) + requires(memory_no_alias(out, outlen)) + requires(state->pos <= SHAKE256_RATE) + assigns(memory_slice(state, sizeof(mld_shake256ctx))) + assigns(memory_slice(out, outlen)) + ensures(state->pos <= SHAKE256_RATE) +); + +#define mld_shake256_release MLD_NAMESPACE(shake256_release) +/** + * Release and securely zero the SHAKE256 state. + * + * @param[in,out] state Pointer to state. + */ +MLD_INTERNAL_API +void mld_shake256_release(mld_shake256ctx *state) +__contract__( + requires(memory_no_alias(state, sizeof(mld_shake256ctx))) + assigns(memory_slice(state, sizeof(mld_shake256ctx))) +); + +#if !defined(MLD_CONFIG_NO_KEYPAIR_API) || !defined(MLD_CONFIG_CORE_API_ONLY) +#define mld_shake256 MLD_NAMESPACE(shake256) +/** + * SHAKE256 XOF with non-incremental API. + * + * @param[out] out Pointer to output. + * @param outlen Requested output length in bytes. + * @param[in] in Pointer to input. + * @param inlen Length of input in bytes. + */ +MLD_INTERNAL_API +void mld_shake256(uint8_t *out, size_t outlen, const uint8_t *in, size_t inlen) +__contract__( + requires(inlen <= MLD_MAX_BUFFER_SIZE) + requires(outlen <= 8 * SHAKE256_RATE /* somewhat arbitrary bound */) + requires(memory_no_alias(in, inlen)) + requires(memory_no_alias(out, outlen)) + assigns(memory_slice(out, outlen)) +); +#endif /* !MLD_CONFIG_NO_KEYPAIR_API || !MLD_CONFIG_CORE_API_ONLY */ + +#endif /* !MLD_FIPS202_FIPS202_H */ diff --git a/mldsa_native/src/fips202/fips202x4.c b/mldsa_native/src/fips202/fips202x4.c new file mode 100644 index 0000000..23da9ac --- /dev/null +++ b/mldsa_native/src/fips202/fips202x4.c @@ -0,0 +1,187 @@ +/* + * Copyright (c) The mlkem-native project authors + * Copyright (c) The mldsa-native project authors + * SPDX-License-Identifier: Apache-2.0 OR ISC OR MIT + */ + +/* References + * ========== + * + * - [FIPS204] + * FIPS 204 Module-Lattice-Based Digital Signature Standard + * National Institute of Standards and Technology + * https://csrc.nist.gov/pubs/fips/204/final + */ + +#include "../common.h" +#if !defined(MLD_CONFIG_MULTILEVEL_NO_SHARED) && \ + !defined(MLD_CONFIG_SERIAL_FIPS202_ONLY) + +#include "../ct.h" +#include "fips202.h" +#include "fips202x4.h" +#include "keccakf1600.h" + +#if !defined(MLD_CONFIG_NO_KEYPAIR_API) || !defined(MLD_CONFIG_REDUCE_RAM) || \ + defined(MLD_UNIT_TEST) +static void mld_keccak_absorb_once_x4(uint64_t *s, unsigned r, + const uint8_t *in0, const uint8_t *in1, + const uint8_t *in2, const uint8_t *in3, + size_t inlen, uint8_t p) +__contract__( + requires(inlen <= MLD_MAX_BUFFER_SIZE) + requires(memory_no_alias(s, sizeof(uint64_t) * MLD_KECCAK_LANES * MLD_KECCAK_WAY)) + requires(r > 0) + requires(r <= sizeof(uint64_t) * MLD_KECCAK_LANES) + requires(memory_no_alias(in0, inlen)) + requires(memory_no_alias(in1, inlen)) + requires(memory_no_alias(in2, inlen)) + requires(memory_no_alias(in3, inlen)) + assigns(memory_slice(s, sizeof(uint64_t) * MLD_KECCAK_LANES * MLD_KECCAK_WAY))) +{ + while (inlen >= r) + __loop__( + assigns(inlen, in0, in1, in2, in3, memory_slice(s, sizeof(uint64_t) * MLD_KECCAK_LANES * MLD_KECCAK_WAY)) + invariant(inlen <= loop_entry(inlen)) + invariant(in0 == loop_entry(in0) + (loop_entry(inlen) - inlen)) + invariant(in1 == loop_entry(in1) + (loop_entry(inlen) - inlen)) + invariant(in2 == loop_entry(in2) + (loop_entry(inlen) - inlen)) + invariant(in3 == loop_entry(in3) + (loop_entry(inlen) - inlen)) + decreases(inlen)) + { + mld_keccakf1600x4_xor_bytes(s, in0, in1, in2, in3, 0, r); + mld_keccakf1600x4_permute(s); + + in0 += r; + in1 += r; + in2 += r; + in3 += r; + inlen -= r; + } + + /* Safety: At this point, inlen < r, so the truncations to unsigned are safe + * below. */ + if (inlen > 0) + { + mld_keccakf1600x4_xor_bytes(s, in0, in1, in2, in3, 0, (unsigned)inlen); + } + + if (inlen == r - 1) + { + p |= 128; + mld_keccakf1600x4_xor_bytes(s, &p, &p, &p, &p, (unsigned)inlen, 1); + } + else + { + mld_keccakf1600x4_xor_bytes(s, &p, &p, &p, &p, (unsigned)inlen, 1); + p = 128; + mld_keccakf1600x4_xor_bytes(s, &p, &p, &p, &p, r - 1, 1); + } +} + +static void mld_keccak_squeezeblocks_x4(uint8_t *out0, uint8_t *out1, + uint8_t *out2, uint8_t *out3, + size_t nblocks, uint64_t *s, unsigned r) +__contract__( + requires(r <= sizeof(uint64_t) * MLD_KECCAK_LANES) + requires(nblocks <= 8 /* somewhat arbitrary bound */) + requires(memory_no_alias(s, sizeof(uint64_t) * MLD_KECCAK_LANES * MLD_KECCAK_WAY)) + requires(memory_no_alias(out0, nblocks * r)) + requires(memory_no_alias(out1, nblocks * r)) + requires(memory_no_alias(out2, nblocks * r)) + requires(memory_no_alias(out3, nblocks * r)) + assigns(memory_slice(s, sizeof(uint64_t) * MLD_KECCAK_LANES * MLD_KECCAK_WAY)) + assigns(memory_slice(out0, nblocks * r)) + assigns(memory_slice(out1, nblocks * r)) + assigns(memory_slice(out2, nblocks * r)) + assigns(memory_slice(out3, nblocks * r))) +{ + size_t current_offset = 0; + while (nblocks > 0) + __loop__( + assigns(nblocks, current_offset, + memory_slice(s, sizeof(uint64_t) * MLD_KECCAK_LANES * MLD_KECCAK_WAY), + memory_slice(out0, nblocks * r), + memory_slice(out1, nblocks * r), + memory_slice(out2, nblocks * r), + memory_slice(out3, nblocks * r)) + invariant(nblocks <= loop_entry(nblocks)) + invariant(current_offset == (loop_entry(nblocks) - nblocks) * r) + decreases(nblocks)) + { + mld_keccakf1600x4_permute(s); + mld_keccakf1600x4_extract_bytes( + s, &out0[current_offset], &out1[current_offset], &out2[current_offset], + &out3[current_offset], 0, r); + current_offset += r; + nblocks--; + } +} +#endif /* !MLD_CONFIG_NO_KEYPAIR_API || !MLD_CONFIG_REDUCE_RAM || \ + MLD_UNIT_TEST */ + +#if !defined(MLD_CONFIG_REDUCE_RAM) || defined(MLD_UNIT_TEST) +MLD_INTERNAL_API +void mld_shake128x4_absorb_once(mld_shake128x4ctx *state, const uint8_t *in0, + const uint8_t *in1, const uint8_t *in2, + const uint8_t *in3, size_t inlen) +{ + mld_memset(state, 0, sizeof(mld_shake128x4ctx)); + mld_keccak_absorb_once_x4(state->ctx, SHAKE128_RATE, in0, in1, in2, in3, + inlen, 0x1F); +} + +MLD_INTERNAL_API +void mld_shake128x4_squeezeblocks(uint8_t *out0, uint8_t *out1, uint8_t *out2, + uint8_t *out3, size_t nblocks, + mld_shake128x4ctx *state) +{ + mld_keccak_squeezeblocks_x4(out0, out1, out2, out3, nblocks, state->ctx, + SHAKE128_RATE); +} + +MLD_INTERNAL_API +void mld_shake128x4_init(mld_shake128x4ctx *state) { (void)state; } +MLD_INTERNAL_API +void mld_shake128x4_release(mld_shake128x4ctx *state) +{ + /* @[FIPS204, Section 3.6.3] Destruction of intermediate values. */ + mld_zeroize(state, sizeof(mld_shake128x4ctx)); +} +#endif /* !MLD_CONFIG_REDUCE_RAM || MLD_UNIT_TEST */ + +#if !defined(MLD_CONFIG_NO_KEYPAIR_API) || \ + (!defined(MLD_CONFIG_NO_SIGN_API) && \ + (!defined(MLD_CONFIG_REDUCE_RAM) || defined(MLD_UNIT_TEST))) +MLD_INTERNAL_API +void mld_shake256x4_absorb_once(mld_shake256x4ctx *state, const uint8_t *in0, + const uint8_t *in1, const uint8_t *in2, + const uint8_t *in3, size_t inlen) +{ + mld_memset(state, 0, sizeof(mld_shake256x4ctx)); + mld_keccak_absorb_once_x4(state->ctx, SHAKE256_RATE, in0, in1, in2, in3, + inlen, 0x1F); +} + +MLD_INTERNAL_API +void mld_shake256x4_squeezeblocks(uint8_t *out0, uint8_t *out1, uint8_t *out2, + uint8_t *out3, size_t nblocks, + mld_shake256x4ctx *state) +{ + mld_keccak_squeezeblocks_x4(out0, out1, out2, out3, nblocks, state->ctx, + SHAKE256_RATE); +} + +MLD_INTERNAL_API +void mld_shake256x4_init(mld_shake256x4ctx *state) { (void)state; } +MLD_INTERNAL_API +void mld_shake256x4_release(mld_shake256x4ctx *state) +{ + /* @[FIPS204, Section 3.6.3] Destruction of intermediate values. */ + mld_zeroize(state, sizeof(mld_shake256x4ctx)); +} +#endif /* !MLD_CONFIG_NO_KEYPAIR_API || (!MLD_CONFIG_NO_SIGN_API && \ + (!MLD_CONFIG_REDUCE_RAM || MLD_UNIT_TEST)) */ + +#endif /* !MLD_CONFIG_MULTILEVEL_NO_SHARED && !MLD_CONFIG_SERIAL_FIPS202_ONLY \ + */ diff --git a/mldsa_native/src/fips202/fips202x4.h b/mldsa_native/src/fips202/fips202x4.h new file mode 100644 index 0000000..2f5db18 --- /dev/null +++ b/mldsa_native/src/fips202/fips202x4.h @@ -0,0 +1,125 @@ +/* + * Copyright (c) The mlkem-native project authors + * Copyright (c) The mldsa-native project authors + * SPDX-License-Identifier: Apache-2.0 OR ISC OR MIT + */ +#ifndef MLD_FIPS202_FIPS202X4_H +#define MLD_FIPS202_FIPS202X4_H + +#include "../common.h" + +#if !defined(MLD_CONFIG_SERIAL_FIPS202_ONLY) + +#include + +#include "../cbmc.h" +#include "fips202.h" +#include "keccakf1600.h" + +/** Context for the non-incremental 4-way SHAKE128 API. */ +typedef struct +{ + uint64_t ctx[MLD_KECCAK_LANES * + MLD_KECCAK_WAY]; /**< 4-way Keccak state, stored sequentially. */ +} mld_shake128x4ctx; + +/** Context for the 4-way batched SHAKE256 XOF. */ +typedef struct +{ + uint64_t ctx[MLD_KECCAK_LANES * + MLD_KECCAK_WAY]; /**< Interleaved 4-way Keccak state. */ +} mld_shake256x4ctx; + +#if !defined(MLD_CONFIG_REDUCE_RAM) || defined(MLD_UNIT_TEST) +#define mld_shake128x4_absorb_once MLD_NAMESPACE(shake128x4_absorb_once) +MLD_INTERNAL_API +void mld_shake128x4_absorb_once(mld_shake128x4ctx *state, const uint8_t *in0, + const uint8_t *in1, const uint8_t *in2, + const uint8_t *in3, size_t inlen) +__contract__( + requires(inlen <= MLD_MAX_BUFFER_SIZE) + requires(memory_no_alias(state, sizeof(mld_shake128x4ctx))) + requires(memory_no_alias(in0, inlen)) + requires(memory_no_alias(in1, inlen)) + requires(memory_no_alias(in2, inlen)) + requires(memory_no_alias(in3, inlen)) + assigns(memory_slice(state, sizeof(mld_shake128x4ctx))) +); + +#define mld_shake128x4_squeezeblocks MLD_NAMESPACE(shake128x4_squeezeblocks) +MLD_INTERNAL_API +void mld_shake128x4_squeezeblocks(uint8_t *out0, uint8_t *out1, uint8_t *out2, + uint8_t *out3, size_t nblocks, + mld_shake128x4ctx *state) +__contract__( + requires(nblocks <= 8 /* somewhat arbitrary bound */) + requires(memory_no_alias(state, sizeof(mld_shake128x4ctx))) + requires(memory_no_alias(out0, nblocks * SHAKE128_RATE)) + requires(memory_no_alias(out1, nblocks * SHAKE128_RATE)) + requires(memory_no_alias(out2, nblocks * SHAKE128_RATE)) + requires(memory_no_alias(out3, nblocks * SHAKE128_RATE)) + assigns(memory_slice(out0, nblocks * SHAKE128_RATE), + memory_slice(out1, nblocks * SHAKE128_RATE), + memory_slice(out2, nblocks * SHAKE128_RATE), + memory_slice(out3, nblocks * SHAKE128_RATE), + memory_slice(state, sizeof(mld_shake128x4ctx))) +); + +#define mld_shake128x4_init MLD_NAMESPACE(shake128x4_init) +MLD_INTERNAL_API +void mld_shake128x4_init(mld_shake128x4ctx *state); + +#define mld_shake128x4_release MLD_NAMESPACE(shake128x4_release) +MLD_INTERNAL_API +void mld_shake128x4_release(mld_shake128x4ctx *state); +#endif /* !MLD_CONFIG_REDUCE_RAM || MLD_UNIT_TEST */ + +#if !defined(MLD_CONFIG_NO_KEYPAIR_API) || \ + (!defined(MLD_CONFIG_NO_SIGN_API) && \ + (!defined(MLD_CONFIG_REDUCE_RAM) || defined(MLD_UNIT_TEST))) +#define mld_shake256x4_absorb_once MLD_NAMESPACE(shake256x4_absorb_once) +MLD_INTERNAL_API +void mld_shake256x4_absorb_once(mld_shake256x4ctx *state, const uint8_t *in0, + const uint8_t *in1, const uint8_t *in2, + const uint8_t *in3, size_t inlen) +__contract__( + requires(inlen <= MLD_MAX_BUFFER_SIZE) + requires(memory_no_alias(state, sizeof(mld_shake256x4ctx))) + requires(memory_no_alias(in0, inlen)) + requires(memory_no_alias(in1, inlen)) + requires(memory_no_alias(in2, inlen)) + requires(memory_no_alias(in3, inlen)) + assigns(memory_slice(state, sizeof(mld_shake256x4ctx))) +); + +#define mld_shake256x4_squeezeblocks MLD_NAMESPACE(shake256x4_squeezeblocks) +MLD_INTERNAL_API +void mld_shake256x4_squeezeblocks(uint8_t *out0, uint8_t *out1, uint8_t *out2, + uint8_t *out3, size_t nblocks, + mld_shake256x4ctx *state) +__contract__( + requires(nblocks <= 8 /* somewhat arbitrary bound */) + requires(memory_no_alias(state, sizeof(mld_shake256x4ctx))) + requires(memory_no_alias(out0, nblocks * SHAKE256_RATE)) + requires(memory_no_alias(out1, nblocks * SHAKE256_RATE)) + requires(memory_no_alias(out2, nblocks * SHAKE256_RATE)) + requires(memory_no_alias(out3, nblocks * SHAKE256_RATE)) + assigns(memory_slice(out0, nblocks * SHAKE256_RATE), + memory_slice(out1, nblocks * SHAKE256_RATE), + memory_slice(out2, nblocks * SHAKE256_RATE), + memory_slice(out3, nblocks * SHAKE256_RATE), + memory_slice(state, sizeof(mld_shake256x4ctx))) +); + +#define mld_shake256x4_init MLD_NAMESPACE(shake256x4_init) +MLD_INTERNAL_API +void mld_shake256x4_init(mld_shake256x4ctx *state); + +#define mld_shake256x4_release MLD_NAMESPACE(shake256x4_release) +MLD_INTERNAL_API +void mld_shake256x4_release(mld_shake256x4ctx *state); +#endif /* !MLD_CONFIG_NO_KEYPAIR_API || (!MLD_CONFIG_NO_SIGN_API && \ + (!MLD_CONFIG_REDUCE_RAM || MLD_UNIT_TEST)) */ + +#endif /* !MLD_CONFIG_SERIAL_FIPS202_ONLY */ +#endif /* !MLD_FIPS202_FIPS202X4_H */ diff --git a/mldsa_native/src/fips202/keccakf1600.c b/mldsa_native/src/fips202/keccakf1600.c new file mode 100644 index 0000000..ffc994a --- /dev/null +++ b/mldsa_native/src/fips202/keccakf1600.c @@ -0,0 +1,22 @@ +/* + * Shared Keccak: forward ML-DSA's Keccak-f[1600] to ML-KEM-native's identical + * implementation, removing the duplicate permutation from flash. The (xor, + * permute, extract) triple is internally consistent, so this is layout-safe. + * OnlyKey size optimization. + */ +#include +#include "keccakf1600.h" + +extern void PQCP_MLKEM_NATIVE_MLKEM768_keccakf1600_permute(uint64_t *s); +extern void PQCP_MLKEM_NATIVE_MLKEM768_keccakf1600_xor_bytes(uint64_t *s, const unsigned char *d, unsigned o, unsigned l); +extern void PQCP_MLKEM_NATIVE_MLKEM768_keccakf1600_extract_bytes(uint64_t *s, unsigned char *d, unsigned o, unsigned l); +extern void PQCP_MLKEM_NATIVE_MLKEM768_keccakf1600x4_permute(uint64_t *s); +extern void PQCP_MLKEM_NATIVE_MLKEM768_keccakf1600x4_xor_bytes(uint64_t *s, const unsigned char *d0, const unsigned char *d1, const unsigned char *d2, const unsigned char *d3, unsigned o, unsigned l); +extern void PQCP_MLKEM_NATIVE_MLKEM768_keccakf1600x4_extract_bytes(uint64_t *s, unsigned char *d0, unsigned char *d1, unsigned char *d2, unsigned char *d3, unsigned o, unsigned l); + +void mld_keccakf1600_permute(uint64_t *s) { PQCP_MLKEM_NATIVE_MLKEM768_keccakf1600_permute(s); } +void mld_keccakf1600_xor_bytes(uint64_t *s, const unsigned char *d, unsigned o, unsigned l) { PQCP_MLKEM_NATIVE_MLKEM768_keccakf1600_xor_bytes(s, d, o, l); } +void mld_keccakf1600_extract_bytes(uint64_t *s, unsigned char *d, unsigned o, unsigned l) { PQCP_MLKEM_NATIVE_MLKEM768_keccakf1600_extract_bytes(s, d, o, l); } +void mld_keccakf1600x4_permute(uint64_t *s) { PQCP_MLKEM_NATIVE_MLKEM768_keccakf1600x4_permute(s); } +void mld_keccakf1600x4_xor_bytes(uint64_t *s, const unsigned char *d0, const unsigned char *d1, const unsigned char *d2, const unsigned char *d3, unsigned o, unsigned l) { PQCP_MLKEM_NATIVE_MLKEM768_keccakf1600x4_xor_bytes(s, d0,d1,d2,d3, o, l); } +void mld_keccakf1600x4_extract_bytes(uint64_t *s, unsigned char *d0, unsigned char *d1, unsigned char *d2, unsigned char *d3, unsigned o, unsigned l) { PQCP_MLKEM_NATIVE_MLKEM768_keccakf1600x4_extract_bytes(s, d0,d1,d2,d3, o, l); } diff --git a/mldsa_native/src/fips202/keccakf1600.h b/mldsa_native/src/fips202/keccakf1600.h new file mode 100644 index 0000000..c0875f4 --- /dev/null +++ b/mldsa_native/src/fips202/keccakf1600.h @@ -0,0 +1,110 @@ +/* + * Copyright (c) The mlkem-native project authors + * Copyright (c) The mldsa-native project authors + * SPDX-License-Identifier: Apache-2.0 OR ISC OR MIT + */ +#ifndef MLD_FIPS202_KECCAKF1600_H +#define MLD_FIPS202_KECCAKF1600_H +#include "../cbmc.h" +#include "../common.h" +#include "fips202.h" + +#define MLD_KECCAK_LANES 25 +#define MLD_KECCAK_WAY 4 + +/* + * WARNING: + * The contents of this structure, including the placement + * and interleaving of Keccak lanes, are IMPLEMENTATION-DEFINED. + * The struct is only exposed here to allow its construction on the stack. + */ + +#define mld_keccakf1600_extract_bytes MLD_NAMESPACE(keccakf1600_extract_bytes) +MLD_INTERNAL_API +void mld_keccakf1600_extract_bytes(uint64_t *state, unsigned char *data, + unsigned offset, unsigned length) +__contract__( + requires(0 <= offset && offset <= MLD_KECCAK_LANES * sizeof(uint64_t) && + 0 <= length && length <= MLD_KECCAK_LANES * sizeof(uint64_t) - offset) + requires(memory_no_alias(state, sizeof(uint64_t) * MLD_KECCAK_LANES)) + requires(memory_no_alias(data, length)) + assigns(memory_slice(data, length)) +); + +#define mld_keccakf1600_xor_bytes MLD_NAMESPACE(keccakf1600_xor_bytes) +MLD_INTERNAL_API +void mld_keccakf1600_xor_bytes(uint64_t *state, const unsigned char *data, + unsigned offset, unsigned length) +__contract__( + requires(0 <= offset && offset <= MLD_KECCAK_LANES * sizeof(uint64_t) && + 0 <= length && length <= MLD_KECCAK_LANES * sizeof(uint64_t) - offset) + requires(memory_no_alias(state, sizeof(uint64_t) * MLD_KECCAK_LANES)) + requires(memory_no_alias(data, length)) + assigns(memory_slice(state, sizeof(uint64_t) * MLD_KECCAK_LANES)) +); + +#if (!defined(MLD_CONFIG_NO_KEYPAIR_API) || !defined(MLD_CONFIG_REDUCE_RAM) || \ + defined(MLD_UNIT_TEST)) && \ + !defined(MLD_CONFIG_SERIAL_FIPS202_ONLY) +#define mld_keccakf1600x4_extract_bytes \ + MLD_NAMESPACE(keccakf1600x4_extract_bytes) +MLD_INTERNAL_API +void mld_keccakf1600x4_extract_bytes(uint64_t *state, unsigned char *data0, + unsigned char *data1, unsigned char *data2, + unsigned char *data3, unsigned offset, + unsigned length) +__contract__( + requires(0 <= offset && offset <= MLD_KECCAK_LANES * sizeof(uint64_t) && + 0 <= length && length <= MLD_KECCAK_LANES * sizeof(uint64_t) - offset) + requires(memory_no_alias(state, sizeof(uint64_t) * MLD_KECCAK_LANES * MLD_KECCAK_WAY)) + requires(memory_no_alias(data0, length)) + requires(memory_no_alias(data1, length)) + requires(memory_no_alias(data2, length)) + requires(memory_no_alias(data3, length)) + assigns(memory_slice(data0, length)) + assigns(memory_slice(data1, length)) + assigns(memory_slice(data2, length)) + assigns(memory_slice(data3, length)) +); + +#define mld_keccakf1600x4_xor_bytes MLD_NAMESPACE(keccakf1600x4_xor_bytes) +MLD_INTERNAL_API +void mld_keccakf1600x4_xor_bytes(uint64_t *state, const unsigned char *data0, + const unsigned char *data1, + const unsigned char *data2, + const unsigned char *data3, unsigned offset, + unsigned length) +__contract__( + requires(0 <= offset && offset <= MLD_KECCAK_LANES * sizeof(uint64_t) && + 0 <= length && length <= MLD_KECCAK_LANES * sizeof(uint64_t) - offset) + requires(memory_no_alias(state, sizeof(uint64_t) * MLD_KECCAK_LANES * MLD_KECCAK_WAY)) + requires(memory_no_alias(data0, length)) + /* Case 1: all input buffers are distinct; Case 2: All input buffers are the same */ + requires((data0 == data1 && + data0 == data2 && + data0 == data3) || + (memory_no_alias(data1, length) && + memory_no_alias(data2, length) && + memory_no_alias(data3, length))) + assigns(memory_slice(state, sizeof(uint64_t) * MLD_KECCAK_LANES * MLD_KECCAK_WAY)) +); + +#define mld_keccakf1600x4_permute MLD_NAMESPACE(keccakf1600x4_permute) +MLD_INTERNAL_API +void mld_keccakf1600x4_permute(uint64_t *state) +__contract__( + requires(memory_no_alias(state, sizeof(uint64_t) * MLD_KECCAK_LANES * MLD_KECCAK_WAY)) + assigns(memory_slice(state, sizeof(uint64_t) * MLD_KECCAK_LANES * MLD_KECCAK_WAY)) +); +#endif /* (!MLD_CONFIG_NO_KEYPAIR_API || !MLD_CONFIG_REDUCE_RAM || \ + MLD_UNIT_TEST) && !MLD_CONFIG_SERIAL_FIPS202_ONLY */ + +#define mld_keccakf1600_permute MLD_NAMESPACE(keccakf1600_permute) +MLD_INTERNAL_API +void mld_keccakf1600_permute(uint64_t *state) +__contract__( + requires(memory_no_alias(state, sizeof(uint64_t) * MLD_KECCAK_LANES)) + assigns(memory_slice(state, sizeof(uint64_t) * MLD_KECCAK_LANES)) +); + +#endif /* !MLD_FIPS202_KECCAKF1600_H */ diff --git a/mldsa_native/src/packing.c b/mldsa_native/src/packing.c new file mode 100644 index 0000000..3107b7d --- /dev/null +++ b/mldsa_native/src/packing.c @@ -0,0 +1,201 @@ +/* + * Copyright (c) The mldsa-native project authors + * SPDX-License-Identifier: Apache-2.0 OR ISC OR MIT + */ +#include + +#include "common.h" +#include "packing.h" +#include "poly.h" +#include "polyvec.h" +#include "rounding.h" + +/* Parameter set namespacing + * This is to facilitate building multiple instances + * of mldsa-native (e.g. with varying parameter sets) + * within a single compilation unit. */ +/* End of parameter set namespacing */ + +#if !defined(MLD_CONFIG_NO_VERIFY_API) +MLD_INTERNAL_API +void mld_unpack_pk_t1(mld_poly *t1, + const uint8_t pk[MLDSA_CRYPTO_PUBLICKEYBYTES], + unsigned int i) +{ + mld_polyt1_unpack(t1, pk + MLDSA_PK_T1_OFFSET + i * MLDSA_POLYT1_PACKEDBYTES); +} +#endif /* !MLD_CONFIG_NO_VERIFY_API */ + +#if !defined(MLD_CONFIG_NO_KEYPAIR_API) +MLD_INTERNAL_API +void mld_pack_sk_s1(uint8_t sk[MLDSA_CRYPTO_SECRETKEYBYTES], + const mld_polyvecl *s1) +{ + mld_polyvecl_pack_eta(sk + MLDSA_SK_S1_OFFSET, s1); +} + +MLD_INTERNAL_API +void mld_pack_sk_rho_key_tr_s2(uint8_t sk[MLDSA_CRYPTO_SECRETKEYBYTES], + const uint8_t rho[MLDSA_SEEDBYTES], + const uint8_t tr[MLDSA_TRBYTES], + const uint8_t key[MLDSA_SEEDBYTES], + const mld_polyveck *s2) +{ + mld_memcpy(sk + MLDSA_SK_RHO_OFFSET, rho, MLDSA_SEEDBYTES); + mld_memcpy(sk + MLDSA_SK_KEY_OFFSET, key, MLDSA_SEEDBYTES); + mld_memcpy(sk + MLDSA_SK_TR_OFFSET, tr, MLDSA_TRBYTES); + /* s1 already packed via mld_pack_sk_s1 */ + mld_polyveck_pack_eta(sk + MLDSA_SK_S2_OFFSET, s2); + /* t0 already packed via mld_compute_pack_t0_t1 */ +} +#endif /* !MLD_CONFIG_NO_KEYPAIR_API */ + +#if !defined(MLD_CONFIG_NO_SIGN_API) +MLD_INTERNAL_API +void mld_unpack_sk(uint8_t rho[MLDSA_SEEDBYTES], uint8_t tr[MLDSA_TRBYTES], + uint8_t key[MLDSA_SEEDBYTES], mld_sk_t0hat *t0, + mld_sk_s1hat *s1, mld_sk_s2hat *s2, + const uint8_t sk[MLDSA_CRYPTO_SECRETKEYBYTES]) +{ + mld_memcpy(rho, sk + MLDSA_SK_RHO_OFFSET, MLDSA_SEEDBYTES); + mld_memcpy(key, sk + MLDSA_SK_KEY_OFFSET, MLDSA_SEEDBYTES); + mld_memcpy(tr, sk + MLDSA_SK_TR_OFFSET, MLDSA_TRBYTES); + mld_unpack_sk_s1hat(s1, sk + MLDSA_SK_S1_OFFSET); + mld_unpack_sk_s2hat(s2, sk + MLDSA_SK_S2_OFFSET); + mld_unpack_sk_t0hat(t0, sk + MLDSA_SK_T0_OFFSET); +} + +MLD_INTERNAL_API +void mld_pack_sig_c(uint8_t sig[MLDSA_CRYPTO_BYTES], + const uint8_t c[MLDSA_CTILDEBYTES]) +{ + mld_memcpy(sig, c, MLDSA_CTILDEBYTES); +} + +MLD_INTERNAL_API +int mld_pack_sig_h(uint8_t sig[MLDSA_CRYPTO_BYTES], const mld_polyveck *w0, + const mld_polyveck *w1) +{ + unsigned int j, k, n; + + /* The hint section of sig[] is MLDSA_POLYVECH_PACKEDBYTES long, where + * MLDSA_POLYVECH_PACKEDBYTES = MLDSA_OMEGA + MLDSA_K. + * + * The first OMEGA bytes record the index numbers of the coefficients + * that are not equal to 0. + * + * The final K bytes record a running tally of the number of hints + * coming from each of the K polynomials. */ + uint8_t *sig_h = sig + MLDSA_SIG_H_OFFSET; + + mld_memset(sig_h, 0, MLDSA_POLYVECH_PACKEDBYTES); + n = 0; + + /* For each coefficient of each polynomial, compute its hint bit and, if + * non-zero, record the index in the hint section of sig. If recording the + * hint would overflow the OMEGA-sized index array, abort early and return + * MLD_ERR_FAIL. The caller is expected to reject the signature in that case. + * + * Constant time: At this point w0/w1 are public (see comment in sign.c + * before the call), so a data-dependent early return is fine. */ + for (k = 0; k < MLDSA_K; k++) + __loop__( + assigns(k, j, n, memory_slice(sig_h, MLDSA_POLYVECH_PACKEDBYTES)) + invariant(k <= MLDSA_K && n <= MLDSA_OMEGA) + decreases(MLDSA_K - k) + ) + { + for (j = 0; j < MLDSA_N; j++) + __loop__( + assigns(j, n, memory_slice(sig_h, MLDSA_POLYVECH_PACKEDBYTES)) + invariant(j <= MLDSA_N && n <= MLDSA_OMEGA) + decreases(MLDSA_N - j) + ) + { + const unsigned int hint_bit = + mld_make_hint(w0->vec[k].coeffs[j], w1->vec[k].coeffs[j]); + if (hint_bit) + { + if (n == MLDSA_OMEGA) + { + return MLD_ERR_FAIL; + } + /* Safety: branch above ensures n < MLDSA_OMEGA so n is a valid index + * into the OMEGA-sized index array; j < MLDSA_N <= 256 fits in + * uint8_t. */ + sig_h[n] = (uint8_t)j; + n++; + } + } + /* Record the running tally into the correct slot for this polynomial. + * Safety: k < MLDSA_K, so MLDSA_OMEGA + k is a valid index into the + * K-byte tally tail; n <= MLDSA_OMEGA fits in uint8_t. */ + sig_h[MLDSA_OMEGA + k] = (uint8_t)n; + } + return 0; +} + +MLD_INTERNAL_API +void mld_pack_sig_z(uint8_t sig[MLDSA_CRYPTO_BYTES], const mld_poly *zi, + unsigned i) +{ + mld_polyz_pack(sig + MLDSA_SIG_Z_OFFSET + i * MLDSA_POLYZ_PACKEDBYTES, zi); +} +#endif /* !MLD_CONFIG_NO_SIGN_API */ + +#if !defined(MLD_CONFIG_NO_VERIFY_API) +MLD_INTERNAL_API +int mld_sig_unpack_hints(mld_poly *h, const uint8_t sig[MLDSA_CRYPTO_BYTES], + unsigned int i) +{ + const uint8_t *packed_hints = sig + MLDSA_SIG_H_OFFSET; + const unsigned int old_hint_count = + (i == 0) ? 0 : packed_hints[MLDSA_OMEGA + i - 1]; + const unsigned int new_hint_count = packed_hints[MLDSA_OMEGA + i]; + unsigned int j; + + if (new_hint_count < old_hint_count || new_hint_count > MLDSA_OMEGA) + { + return MLD_ERR_FAIL; + } + + mld_memset(h, 0, sizeof(mld_poly)); + + for (j = old_hint_count; j < new_hint_count; ++j) + __loop__( + invariant(j >= old_hint_count && j <= new_hint_count && + new_hint_count <= MLDSA_OMEGA) + invariant(array_bound(h->coeffs, 0, MLDSA_N, 0, 2)) + decreases(new_hint_count - j) + ) + { + if (j > old_hint_count && packed_hints[j] <= packed_hints[j - 1]) + { + return MLD_ERR_FAIL; + } + /* Safety: packed_hints[j] is uint8_t (<= 255) and MLDSA_N == 256. */ + h->coeffs[packed_hints[j]] = 1; + } + + /* On the last row, also verify that the trailing index slots are zero. */ + if (i == MLDSA_K - 1) + { + for (j = new_hint_count; j < MLDSA_OMEGA; ++j) + __loop__( + invariant(j <= MLDSA_OMEGA) + decreases(MLDSA_OMEGA - j) + ) + { + if (packed_hints[j] != 0) + { + return MLD_ERR_FAIL; + } + } + } + + return 0; +} +#endif /* !MLD_CONFIG_NO_VERIFY_API */ + +/* To facilitate single-compilation-unit (SCU) builds, undefine all macros. + * Don't modify by hand -- this is auto-generated by scripts/autogen. */ diff --git a/mldsa_native/src/packing.h b/mldsa_native/src/packing.h new file mode 100644 index 0000000..ec57554 --- /dev/null +++ b/mldsa_native/src/packing.h @@ -0,0 +1,277 @@ +/* + * Copyright (c) The mldsa-native project authors + * SPDX-License-Identifier: Apache-2.0 OR ISC OR MIT + */ + +/* References + * ========== + * + * - [FIPS204] + * FIPS 204 Module-Lattice-Based Digital Signature Standard + * National Institute of Standards and Technology + * https://csrc.nist.gov/pubs/fips/204/final + */ + +#ifndef MLD_PACKING_H +#define MLD_PACKING_H + +#include "polyvec.h" +#include "polyvec_lazy.h" + +#if !defined(MLD_CONFIG_NO_KEYPAIR_API) +#define mld_pack_sk_s1 MLD_NAMESPACE_KL(pack_sk_s1) +/** + * Bit-pack the s1 component into the secret key. + * + * @spec{Partially implements @[FIPS204, Algorithm 24, skEncode] (s1 + * component).} + * + * @param[out] sk Output byte array. + * @param[in] s1 Pointer to vector s1. + */ +MLD_INTERNAL_API +void mld_pack_sk_s1(uint8_t sk[MLDSA_CRYPTO_SECRETKEYBYTES], + const mld_polyvecl *s1) +__contract__( + requires(memory_no_alias(sk, MLDSA_CRYPTO_SECRETKEYBYTES)) + requires(memory_no_alias(s1, sizeof(mld_polyvecl))) + requires(forall(k1, 0, MLDSA_L, + array_abs_bound(s1->vec[k1].coeffs, 0, MLDSA_N, MLDSA_ETA + 1))) + assigns(memory_slice(sk, MLDSA_CRYPTO_SECRETKEYBYTES)) +); + +#define mld_pack_sk_rho_key_tr_s2 MLD_NAMESPACE_KL(pack_sk_rho_key_tr_s2) +/** + * Bit-pack rho, key, tr, s2 into the secret key. + * + * s1 must already be packed via mld_pack_sk_s1, and t0 via + * mld_compute_pack_t0_t1. + * + * @spec{Partially implements @[FIPS204, Algorithm 24, skEncode] (rho, key, tr, + * s2 components).} + * + * @param[out] sk Output byte array. + * @param[in] rho Byte array containing rho. + * @param[in] tr Byte array containing tr. + * @param[in] key Byte array containing key. + * @param[in] s2 Pointer to vector s2. + */ +MLD_INTERNAL_API +void mld_pack_sk_rho_key_tr_s2(uint8_t sk[MLDSA_CRYPTO_SECRETKEYBYTES], + const uint8_t rho[MLDSA_SEEDBYTES], + const uint8_t tr[MLDSA_TRBYTES], + const uint8_t key[MLDSA_SEEDBYTES], + const mld_polyveck *s2) +__contract__( + requires(memory_no_alias(sk, MLDSA_CRYPTO_SECRETKEYBYTES)) + requires(memory_no_alias(rho, MLDSA_SEEDBYTES)) + requires(memory_no_alias(tr, MLDSA_TRBYTES)) + requires(memory_no_alias(key, MLDSA_SEEDBYTES)) + requires(memory_no_alias(s2, sizeof(mld_polyveck))) + requires(forall(k2, 0, MLDSA_K, + array_abs_bound(s2->vec[k2].coeffs, 0, MLDSA_N, MLDSA_ETA + 1))) + assigns(memory_slice(sk, MLDSA_CRYPTO_SECRETKEYBYTES)) +); +#endif /* !MLD_CONFIG_NO_KEYPAIR_API */ + + +#if !defined(MLD_CONFIG_NO_SIGN_API) +#define mld_pack_sig_c MLD_NAMESPACE_KL(pack_sig_c) +/** + * Bit-pack challenge c into sig = (c, z, h). + * + * @spec{Partially implements @[FIPS204, Algorithm 26, sigEncode] (c + * component).} + * + * @param[out] sig Output byte array. + * @param[in] c Pointer to challenge hash. + */ +MLD_INTERNAL_API +void mld_pack_sig_c(uint8_t sig[MLDSA_CRYPTO_BYTES], + const uint8_t c[MLDSA_CTILDEBYTES]) +__contract__( + requires(memory_no_alias(sig, MLDSA_CRYPTO_BYTES)) + requires(memory_no_alias(c, MLDSA_CTILDEBYTES)) + assigns(memory_slice(sig, MLDSA_CRYPTO_BYTES)) +); + +#define mld_pack_sig_h MLD_NAMESPACE_KL(pack_sig_h) +/** + * Compute hints from (w0, w1) and pack them into the hint section of sig. + * + * @spec{Combines the hint computation @[FIPS204, Algorithm 39, MakeHint] with + * the packing @[FIPS204, Algorithm 20, HintBitPack] (the h component of + * @[FIPS204, Algorithm 26, sigEncode]): it computes the hint vector h from + * (w0, w1) and packs it, rather than receiving a ready-made h as HintBitPack + * does. The hints are computed via mld_make_hint (rounding.h), a specialized + * MakeHint valid only for the values arising during signing; see the block + * comment in mld_attempt_signature_generation (sign.c).} + * + * @param[in,out] sig Byte array containing signature. + * @param[in] w0 Pointer to low part of input vector. + * @param[in] w1 Pointer to high part of input vector. + * + * @retval 0 Success. + * @retval MLD_ERR_FAIL The total number of hints exceeds MLDSA_OMEGA. In this + * case the hint section of sig is left in a + * partially-written state and the caller must reject the + * signature. + */ +MLD_INTERNAL_API +MLD_MUST_CHECK_RETURN_VALUE +int mld_pack_sig_h(uint8_t sig[MLDSA_CRYPTO_BYTES], const mld_polyveck *w0, + const mld_polyveck *w1) +__contract__( + requires(memory_no_alias(sig, MLDSA_CRYPTO_BYTES)) + requires(memory_no_alias(w0, sizeof(mld_polyveck))) + requires(memory_no_alias(w1, sizeof(mld_polyveck))) + assigns(memory_slice(sig + MLDSA_SIG_H_OFFSET, MLDSA_POLYVECH_PACKEDBYTES)) + ensures(return_value == 0 || return_value == MLD_ERR_FAIL) +); + +#define mld_pack_sig_z MLD_NAMESPACE_KL(pack_sig_z) +/** + * Bit-pack single polynomial of z component of sig = (c, z, h). + * + * The c and h components are packed separately using mld_pack_sig_c and + * mld_pack_sig_h. + * + * @spec{Partially implements @[FIPS204, Algorithm 26, sigEncode] (one + * polynomial of the z component).} + * + * @param[in,out] sig Output byte array. + * @param[in] zi Pointer to a single polynomial in z. + * @param i Index of zi in vector z. + */ +MLD_INTERNAL_API +void mld_pack_sig_z(uint8_t sig[MLDSA_CRYPTO_BYTES], const mld_poly *zi, + unsigned i) +__contract__( + requires(memory_no_alias(sig, MLDSA_CRYPTO_BYTES)) + requires(memory_no_alias(zi, sizeof(mld_poly))) + requires(i < MLDSA_L) + requires(array_bound(zi->coeffs, 0, MLDSA_N, -(MLDSA_GAMMA1 - 1), MLDSA_GAMMA1 + 1)) + assigns(memory_slice(sig, MLDSA_CRYPTO_BYTES)) +); +#endif /* !MLD_CONFIG_NO_SIGN_API */ + +#if !defined(MLD_CONFIG_NO_VERIFY_API) +#define mld_unpack_pk_t1 MLD_NAMESPACE_KL(unpack_pk_t1) +/** + * Unpack a single polynomial of the t1 component of a public key + * pk = (rho, t1). + * + * @spec{Partially implements @[FIPS204, Algorithm 23, pkDecode] (one polynomial + * of t1).} + * + * @param[out] t1 Pointer to output polynomial t1[i]. + * @param[in] pk Byte array containing bit-packed pk. + * @param i Row index, must be < MLDSA_K. + */ +MLD_INTERNAL_API +void mld_unpack_pk_t1(mld_poly *t1, + const uint8_t pk[MLDSA_CRYPTO_PUBLICKEYBYTES], + unsigned int i) +__contract__( + requires(memory_no_alias(pk, MLDSA_CRYPTO_PUBLICKEYBYTES)) + requires(memory_no_alias(t1, sizeof(mld_poly))) + requires(i < MLDSA_K) + assigns(memory_slice(t1, sizeof(mld_poly))) + ensures(array_bound(t1->coeffs, 0, MLDSA_N, 0, 1 << 10)) +); +#endif /* !MLD_CONFIG_NO_VERIFY_API */ + +#if !defined(MLD_CONFIG_NO_SIGN_API) +#define mld_unpack_sk MLD_NAMESPACE_KL(unpack_sk) +/** + * Unpack secret key sk = (rho, tr, key, t0, s1, s2). + * + * NOTE: In REDUCE_RAM mode, s1/s2/t0 borrow from sk rather than copying. + * + * @spec{Implements @[FIPS204, Algorithm 25, skDecode].} + * + * @param[out] rho Output byte array for rho. + * @param[out] tr Output byte array for tr. + * @param[out] key Output byte array for key. + * @param[out] t0 Pointer to output vector t0. + * @param[out] s1 Pointer to output vector s1. + * @param[out] s2 Pointer to output vector s2. + * @param[in] sk Byte array containing bit-packed sk. + */ +MLD_INTERNAL_API +void mld_unpack_sk(uint8_t rho[MLDSA_SEEDBYTES], uint8_t tr[MLDSA_TRBYTES], + uint8_t key[MLDSA_SEEDBYTES], mld_sk_t0hat *t0, + mld_sk_s1hat *s1, mld_sk_s2hat *s2, + const uint8_t sk[MLDSA_CRYPTO_SECRETKEYBYTES]) +__contract__( + requires(memory_no_alias(rho, MLDSA_SEEDBYTES)) + requires(memory_no_alias(tr, MLDSA_TRBYTES)) + requires(memory_no_alias(key, MLDSA_SEEDBYTES)) + requires(memory_no_alias(t0, sizeof(mld_sk_t0hat))) + requires(memory_no_alias(s1, sizeof(mld_sk_s1hat))) + requires(memory_no_alias(s2, sizeof(mld_sk_s2hat))) + requires(memory_no_alias(sk, MLDSA_CRYPTO_SECRETKEYBYTES)) + assigns(memory_slice(rho, MLDSA_SEEDBYTES)) + assigns(memory_slice(tr, MLDSA_TRBYTES)) + assigns(memory_slice(key, MLDSA_SEEDBYTES)) + assigns(memory_slice(t0, sizeof(mld_sk_t0hat))) + assigns(memory_slice(s1, sizeof(mld_sk_s1hat))) + assigns(memory_slice(s2, sizeof(mld_sk_s2hat))) + MLD_IF_NOT_REDUCE_RAM( + ensures(forall(k0, 0, MLDSA_K, + array_abs_bound(t0->vec.vec[k0].coeffs, 0, MLDSA_N, MLD_NTT_BOUND))) + ensures(forall(k1, 0, MLDSA_L, + array_abs_bound(s1->vec.vec[k1].coeffs, 0, MLDSA_N, MLD_NTT_BOUND))) + ensures(forall(k2, 0, MLDSA_K, + array_abs_bound(s2->vec.vec[k2].coeffs, 0, MLDSA_N, MLD_NTT_BOUND))) + ) + MLD_IF_REDUCE_RAM( + ensures(s1->packed == old(sk) + MLDSA_SK_S1_OFFSET) + ensures(s2->packed == old(sk) + MLDSA_SK_S2_OFFSET) + ensures(t0->packed == old(sk) + MLDSA_SK_T0_OFFSET) + ) +); +#endif /* !MLD_CONFIG_NO_SIGN_API */ + +#if !defined(MLD_CONFIG_NO_VERIFY_API) +#define mld_sig_unpack_hints MLD_NAMESPACE_KL(sig_unpack_hints) +/** + * Decode and validate a single row of the hint vector h from a signature + * buffer. + * + * The hint encoding is shared across all rows (a count array followed by a + * single index list), so this function performs the validation relevant to + * row i: + * - the i'th hint count is non-decreasing and bounded by MLDSA_OMEGA; + * - the indices for row i are strictly ascending; + * - on i == MLDSA_K - 1, the trailing index slots are zero. + * + * Callers must invoke this for every i in [0, 1, .., MLDSA_K - 1]; if any + * call returns MLD_ERR_FAIL the encoding is malformed and the signature must + * be rejected. + * + * @spec{Implements @[FIPS204, Algorithm 21, HintBitUnpack] (one row; part of + * @[FIPS204, Algorithm 27, sigDecode]).} + * + * @param[out] h Pointer to output polynomial h[i]. + * @param[in] sig Signature buffer. + * @param i Row index, must be < MLDSA_K. + * + * @retval 0 Hints were decoded successfully. + * @retval MLD_ERR_FAIL Hints are malformed. + */ +MLD_INTERNAL_API +MLD_MUST_CHECK_RETURN_VALUE +int mld_sig_unpack_hints(mld_poly *h, const uint8_t sig[MLDSA_CRYPTO_BYTES], + unsigned int i) +__contract__( + requires(memory_no_alias(sig, MLDSA_CRYPTO_BYTES)) + requires(memory_no_alias(h, sizeof(mld_poly))) + requires(i < MLDSA_K) + assigns(memory_slice(h, sizeof(mld_poly))) + ensures(return_value == 0 || return_value == MLD_ERR_FAIL) + ensures(return_value == 0 ==> array_bound(h->coeffs, 0, MLDSA_N, 0, 2)) +); +#endif /* !MLD_CONFIG_NO_VERIFY_API */ + +#endif /* !MLD_PACKING_H */ diff --git a/mldsa_native/src/params.h b/mldsa_native/src/params.h new file mode 100644 index 0000000..5a63f77 --- /dev/null +++ b/mldsa_native/src/params.h @@ -0,0 +1,147 @@ +/* + * Copyright (c) The mldsa-native project authors + * SPDX-License-Identifier: Apache-2.0 OR ISC OR MIT + */ +#ifndef MLD_PARAMS_H +#define MLD_PARAMS_H + +#define MLDSA_SEEDBYTES 32 +#define MLDSA_CRHBYTES 64 +#define MLDSA_TRBYTES 64 +#define MLDSA_RNDBYTES 32 +#define MLDSA_N 256 +#define MLDSA_Q 8380417 +#define MLDSA_Q_HALF ((MLDSA_Q + 1) / 2) +#define MLDSA_D 13 + +#define MLDSA_GAMMA2_88 ((MLDSA_Q - 1) / 88) +#define MLDSA_GAMMA2_32 ((MLDSA_Q - 1) / 32) +#define MLDSA_POLYW1_PACKEDBYTES_88 192 +#define MLDSA_POLYW1_PACKEDBYTES_32 128 + +#if MLD_CONFIG_PARAMETER_SET == 44 + +#define MLDSA_K 4 +#define MLDSA_L 4 +#define MLDSA_ETA 2 +#define MLDSA_TAU 39 +#define MLDSA_BETA 78 +#define MLDSA_GAMMA1 ((int32_t)1 << 17) +#define MLDSA_GAMMA2 MLDSA_GAMMA2_88 +#define MLDSA_OMEGA 80 +#define MLDSA_CTILDEBYTES 32 +#define MLDSA_POLYZ_PACKEDBYTES 576 +#define MLDSA_POLYW1_PACKEDBYTES MLDSA_POLYW1_PACKEDBYTES_88 +#define MLDSA_POLYETA_PACKEDBYTES 96 + +#elif MLD_CONFIG_PARAMETER_SET == 65 + +#define MLDSA_K 6 +#define MLDSA_L 5 +#define MLDSA_ETA 4 +#define MLDSA_TAU 49 +#define MLDSA_BETA 196 +#define MLDSA_GAMMA1 ((int32_t)1 << 19) +#define MLDSA_GAMMA2 MLDSA_GAMMA2_32 +#define MLDSA_OMEGA 55 +#define MLDSA_CTILDEBYTES 48 +#define MLDSA_POLYZ_PACKEDBYTES 640 +#define MLDSA_POLYW1_PACKEDBYTES MLDSA_POLYW1_PACKEDBYTES_32 +#define MLDSA_POLYETA_PACKEDBYTES 128 + +#elif MLD_CONFIG_PARAMETER_SET == 87 + +#define MLDSA_K 8 +#define MLDSA_L 7 +#define MLDSA_ETA 2 +#define MLDSA_TAU 60 +#define MLDSA_BETA 120 +#define MLDSA_GAMMA1 ((int32_t)1 << 19) +#define MLDSA_GAMMA2 MLDSA_GAMMA2_32 +#define MLDSA_OMEGA 75 +#define MLDSA_CTILDEBYTES 64 +#define MLDSA_POLYZ_PACKEDBYTES 640 +#define MLDSA_POLYW1_PACKEDBYTES MLDSA_POLYW1_PACKEDBYTES_32 +#define MLDSA_POLYETA_PACKEDBYTES 96 + +#endif /* MLD_CONFIG_PARAMETER_SET == 87 */ + +#define MLDSA_POLYT1_PACKEDBYTES 320 +#define MLDSA_POLYT0_PACKEDBYTES 416 +#define MLDSA_POLYVECH_PACKEDBYTES (MLDSA_OMEGA + MLDSA_K) + +/* Layout of the packed public key pk[MLDSA_CRYPTO_PUBLICKEYBYTES] = (rho, t1): + * + * +-------------+--------------------------+ + * | rho | t1 | + * +-------------+--------------------------+ + * | SEEDBYTES | K * POLYT1_PACKEDBYTES | + * +-------------+--------------------------+ + */ +#define MLDSA_PK_RHO_OFFSET 0 +#define MLDSA_PK_RHO_BYTES MLDSA_SEEDBYTES + +#define MLDSA_PK_T1_OFFSET (MLDSA_PK_RHO_OFFSET + MLDSA_PK_RHO_BYTES) +#define MLDSA_PK_T1_BYTES (MLDSA_K * MLDSA_POLYT1_PACKEDBYTES) + +#define MLDSA_PK_END (MLDSA_PK_T1_OFFSET + MLDSA_PK_T1_BYTES) + +#define MLDSA_CRYPTO_PUBLICKEYBYTES MLDSA_PK_END + +/* Layout of the packed secret key + * sk[MLDSA_CRYPTO_SECRETKEYBYTES] = (rho, key, tr, s1, s2, t0): + * + * +-----------+-----------+-----------+-----------+-----------+-----------+ + * | rho | key | tr | s1 | s2 | t0 | + * +-----------+-----------+-----------+-----------+-----------+-----------+ + * | SEEDBYTES | SEEDBYTES | TRBYTES | L * | K * | K * | + * | | | | POLYETA_ | POLYETA_ | POLYT0_ | + * | | | | PACKED- | PACKED- | PACKED- | + * | | | | BYTES | BYTES | BYTES | + * +-----------+-----------+-----------+-----------+-----------+-----------+ + */ +#define MLDSA_SK_RHO_OFFSET 0 +#define MLDSA_SK_RHO_BYTES MLDSA_SEEDBYTES + +#define MLDSA_SK_KEY_OFFSET (MLDSA_SK_RHO_OFFSET + MLDSA_SK_RHO_BYTES) +#define MLDSA_SK_KEY_BYTES MLDSA_SEEDBYTES + +#define MLDSA_SK_TR_OFFSET (MLDSA_SK_KEY_OFFSET + MLDSA_SK_KEY_BYTES) +#define MLDSA_SK_TR_BYTES MLDSA_TRBYTES + +#define MLDSA_SK_S1_OFFSET (MLDSA_SK_TR_OFFSET + MLDSA_SK_TR_BYTES) +#define MLDSA_SK_S1_BYTES (MLDSA_L * MLDSA_POLYETA_PACKEDBYTES) + +#define MLDSA_SK_S2_OFFSET (MLDSA_SK_S1_OFFSET + MLDSA_SK_S1_BYTES) +#define MLDSA_SK_S2_BYTES (MLDSA_K * MLDSA_POLYETA_PACKEDBYTES) + +#define MLDSA_SK_T0_OFFSET (MLDSA_SK_S2_OFFSET + MLDSA_SK_S2_BYTES) +#define MLDSA_SK_T0_BYTES (MLDSA_K * MLDSA_POLYT0_PACKEDBYTES) + +#define MLDSA_SK_END (MLDSA_SK_T0_OFFSET + MLDSA_SK_T0_BYTES) + +#define MLDSA_CRYPTO_SECRETKEYBYTES MLDSA_SK_END + +/* Layout of the packed signature sig[MLDSA_CRYPTO_BYTES] = (c, z, h): + * + * +----------------+-------------------+----------------------+ + * | c (challenge) | z | h (hints) | + * +----------------+-------------------+----------------------+ + * | CTILDEBYTES | L * | POLYVECH_PACKEDBYTES | + * | | POLYZ_PACKEDBYTES | (= OMEGA + K) | + * +----------------+-------------------+----------------------+ + */ +#define MLDSA_SIG_C_OFFSET 0 +#define MLDSA_SIG_C_BYTES MLDSA_CTILDEBYTES + +#define MLDSA_SIG_Z_OFFSET (MLDSA_SIG_C_OFFSET + MLDSA_SIG_C_BYTES) +#define MLDSA_SIG_Z_BYTES (MLDSA_L * MLDSA_POLYZ_PACKEDBYTES) + +#define MLDSA_SIG_H_OFFSET (MLDSA_SIG_Z_OFFSET + MLDSA_SIG_Z_BYTES) +#define MLDSA_SIG_H_BYTES MLDSA_POLYVECH_PACKEDBYTES + +#define MLDSA_SIG_END (MLDSA_SIG_H_OFFSET + MLDSA_SIG_H_BYTES) + +#define MLDSA_CRYPTO_BYTES MLDSA_SIG_END + +#endif /* !MLD_PARAMS_H */ diff --git a/mldsa_native/src/poly.c b/mldsa_native/src/poly.c new file mode 100644 index 0000000..4d74306 --- /dev/null +++ b/mldsa_native/src/poly.c @@ -0,0 +1,1066 @@ +/* + * Copyright (c) The mldsa-native project authors + * Copyright (c) The mlkem-native project authors + * SPDX-License-Identifier: Apache-2.0 OR ISC OR MIT + */ + +/* References + * ========== + * + * - [FIPS204] + * FIPS 204 Module-Lattice-Based Digital Signature Standard + * National Institute of Standards and Technology + * https://csrc.nist.gov/pubs/fips/204/final + * + * - [REF] + * CRYSTALS-Dilithium reference implementation + * Bai, Ducas, Kiltz, Lepoint, Lyubashevsky, Schwabe, Seiler, StehlΓ© + * https://github.com/pq-crystals/dilithium/tree/master/ref + */ + +#include "poly.h" + +#include "common.h" +#include "ct.h" +#include "debug.h" +#include "reduce.h" +#include "rounding.h" +#include "symmetric.h" + +#if !defined(MLD_CONFIG_MULTILEVEL_NO_SHARED) +#include "zetas.inc" + +MLD_INTERNAL_API +void mld_poly_reduce(mld_poly *a) +{ + unsigned int i; + mld_assert_bound(a->coeffs, MLDSA_N, INT32_MIN, MLD_REDUCE32_DOMAIN_MAX); + + for (i = 0; i < MLDSA_N; ++i) + __loop__( + invariant(i <= MLDSA_N) + invariant(forall(k0, i, MLDSA_N, a->coeffs[k0] == loop_entry(*a).coeffs[k0])) + invariant(array_bound(a->coeffs, 0, i, -MLD_REDUCE32_RANGE_MAX, MLD_REDUCE32_RANGE_MAX)) + decreases(MLDSA_N - i)) + { + a->coeffs[i] = mld_reduce32(a->coeffs[i]); + } + + mld_assert_bound(a->coeffs, MLDSA_N, -MLD_REDUCE32_RANGE_MAX, + MLD_REDUCE32_RANGE_MAX); +} + +MLD_STATIC_TESTABLE void mld_poly_caddq_c(mld_poly *a) +__contract__( + requires(memory_no_alias(a, sizeof(mld_poly))) + requires(array_abs_bound(a->coeffs, 0, MLDSA_N, MLDSA_Q)) + assigns(memory_slice(a, sizeof(mld_poly))) + ensures(array_bound(a->coeffs, 0, MLDSA_N, 0, MLDSA_Q)) +) +{ + unsigned int i; + mld_assert_abs_bound(a->coeffs, MLDSA_N, MLDSA_Q); + + for (i = 0; i < MLDSA_N; ++i) + __loop__( + invariant(i <= MLDSA_N) + invariant(forall(k0, i, MLDSA_N, a->coeffs[k0] == loop_entry(*a).coeffs[k0])) + invariant(array_bound(a->coeffs, 0, i, 0, MLDSA_Q)) + decreases(MLDSA_N - i) + ) + { + a->coeffs[i] = mld_caddq(a->coeffs[i]); + } + + mld_assert_bound(a->coeffs, MLDSA_N, 0, MLDSA_Q); +} + +MLD_INTERNAL_API +void mld_poly_caddq(mld_poly *a) +{ +#if defined(MLD_USE_NATIVE_POLY_CADDQ) + int ret; + mld_assert_abs_bound(a->coeffs, MLDSA_N, MLDSA_Q); + ret = mld_poly_caddq_native(a->coeffs); + if (ret == MLD_NATIVE_FUNC_SUCCESS) + { + mld_assert_bound(a->coeffs, MLDSA_N, 0, MLDSA_Q); + return; + } +#endif /* MLD_USE_NATIVE_POLY_CADDQ */ + mld_poly_caddq_c(a); +} + +#if !defined(MLD_CONFIG_NO_KEYPAIR_API) || !defined(MLD_CONFIG_NO_SIGN_API) || \ + defined(MLD_CONFIG_REDUCE_RAM) || defined(MLD_UNIT_TEST) +/* Reference: We use destructive version (output=first input) to avoid + * reasoning about aliasing in the CBMC specification */ +MLD_INTERNAL_API +void mld_poly_add(mld_poly *r, const mld_poly *b) +{ + unsigned int i; + for (i = 0; i < MLDSA_N; ++i) + __loop__( + assigns(i, memory_slice(r, sizeof(mld_poly))) + invariant(i <= MLDSA_N) + invariant(forall(k0, i, MLDSA_N, r->coeffs[k0] == loop_entry(*r).coeffs[k0])) + invariant(forall(k1, 0, i, r->coeffs[k1] == loop_entry(*r).coeffs[k1] + b->coeffs[k1])) + invariant(forall(k2, 0, i, r->coeffs[k2] < MLD_REDUCE32_DOMAIN_MAX)) + invariant(forall(k2, 0, i, r->coeffs[k2] >= INT32_MIN)) + decreases(MLDSA_N - i) + ) + { + r->coeffs[i] = r->coeffs[i] + b->coeffs[i]; + } +} +#endif /* !MLD_CONFIG_NO_KEYPAIR_API || !MLD_CONFIG_NO_SIGN_API || \ + MLD_CONFIG_REDUCE_RAM || MLD_UNIT_TEST */ + +#if !defined(MLD_CONFIG_NO_SIGN_API) || !defined(MLD_CONFIG_NO_VERIFY_API) +/* Reference: We use destructive version (output=first input) to avoid + * reasoning about aliasing in the CBMC specification */ +MLD_INTERNAL_API +void mld_poly_sub(mld_poly *r, const mld_poly *b) +{ + unsigned int i; + mld_assert_abs_bound(b->coeffs, MLDSA_N, MLDSA_Q); + mld_assert_abs_bound(r->coeffs, MLDSA_N, MLDSA_Q); + + for (i = 0; i < MLDSA_N; ++i) + __loop__( + invariant(i <= MLDSA_N) + invariant(array_bound(r->coeffs, 0, i, INT32_MIN, MLD_REDUCE32_DOMAIN_MAX)) + invariant(forall(k0, i, MLDSA_N, r->coeffs[k0] == loop_entry(*r).coeffs[k0])) + decreases(MLDSA_N - i) + ) + { + r->coeffs[i] = r->coeffs[i] - b->coeffs[i]; + } + + mld_assert_bound(r->coeffs, MLDSA_N, INT32_MIN, MLD_REDUCE32_DOMAIN_MAX); +} +#endif /* !MLD_CONFIG_NO_SIGN_API || !MLD_CONFIG_NO_VERIFY_API */ + +#if !defined(MLD_CONFIG_NO_VERIFY_API) +MLD_INTERNAL_API +void mld_poly_shiftl(mld_poly *a) +{ + unsigned int i; + mld_assert_bound(a->coeffs, MLDSA_N, 0, 1 << 10); + + for (i = 0; i < MLDSA_N; i++) + __loop__( + invariant(i <= MLDSA_N) + invariant(array_bound(a->coeffs, 0, i, 0, MLDSA_Q)) + invariant(forall(k0, i, MLDSA_N, a->coeffs[k0] == loop_entry(*a).coeffs[k0])) + decreases(MLDSA_N - i)) + { + /* Reference: uses a left shift by MLDSA_D which is undefined behaviour in + * C90/C99 + */ + a->coeffs[i] *= (1 << MLDSA_D); + } + mld_assert_bound(a->coeffs, MLDSA_N, 0, MLDSA_Q); +} +#endif /* !MLD_CONFIG_NO_VERIFY_API */ + +static MLD_INLINE int32_t mld_fqmul(int32_t a, int32_t b) +__contract__( + requires(b > -MLDSA_Q_HALF && b < MLDSA_Q_HALF) + ensures(return_value > -MLD_FQMUL_BOUND && return_value < MLD_FQMUL_BOUND) +) +{ + /* Bounds: We argue in mld_montgomery_reduce() that the result + * of Montgomery reduction is < MLDSA_Q if the input is smaller + * than 2^31 * MLDSA_Q in absolute value. Indeed, we have: + * + * |a * b| = |a| * |b| + * < 2^31 * MLDSA_Q_HALF + * < 2^31 * MLDSA_Q + * + * So the output is < MLDSA_Q < MLD_FQMUL_BOUND. + */ + return mld_montgomery_reduce((int64_t)a * (int64_t)b); +} + +/* mld_ntt_butterfly_block() + * + * Computes a block CT butterflies with a fixed twiddle factor, + * using Montgomery multiplication. + * + * Parameters: + * - r: Pointer to base of polynomial (_not_ the base of butterfly block) + * - zeta: Twiddle factor to use for the butterfly. This must be in + * Montgomery form and signed canonical. + * - start: Offset to the beginning of the butterfly block + * - len: Index difference between coefficients subject to a butterfly + * - bound: Ghost variable describing coefficient bound: Prior to `start`, + * coefficients must be bound by `bound + MLDSA_Q`. Post `start`, + * they must be bound by `bound`. + * When this function returns, output coefficients in the index range + * [start, start+2*len) have bound bumped to `bound + MLDSA_Q`. + * Example: + * - start=8, len=4 + * This would compute the following four butterflies + * 8 -- 12 + * 9 -- 13 + * 10 -- 14 + * 11 -- 15 + * - start=4, len=2 + * This would compute the following two butterflies + * 4 -- 6 + * 5 -- 7 + */ + +/* Reference: Embedded in `ntt()` in the reference implementation @[REF]. */ +static MLD_INLINE void mld_ntt_butterfly_block(int32_t r[MLDSA_N], + const int32_t zeta, + const unsigned start, + const unsigned len, + const uint32_t bound) +__contract__( + requires(start < MLDSA_N) + requires(1 <= len && len <= MLDSA_N / 2 && start + 2 * len <= MLDSA_N) + requires(0 <= bound && bound < INT32_MAX - MLD_FQMUL_BOUND) + requires(-MLDSA_Q_HALF < zeta && zeta < MLDSA_Q_HALF) + requires(memory_no_alias(r, sizeof(int32_t) * MLDSA_N)) + requires(array_abs_bound(r, 0, start, bound + MLD_FQMUL_BOUND)) + requires(array_abs_bound(r, start, MLDSA_N, bound)) + assigns(memory_slice(r, sizeof(int32_t) * MLDSA_N)) + ensures(array_abs_bound(r, 0, start + 2*len, bound + MLD_FQMUL_BOUND)) + ensures(array_abs_bound(r, start + 2 * len, MLDSA_N, bound))) +{ + /* `bound` is a ghost variable only needed in the CBMC specification */ + unsigned j; + ((void)bound); + for (j = start; j < start + len; j++) + __loop__( + invariant(start <= j && j <= start + len) + /* + * Coefficients are updated in strided pairs, so the bounds for the + * intermediate states alternate twice between the old and new bound + */ + invariant(array_abs_bound(r, 0, j, bound + MLD_FQMUL_BOUND)) + invariant(array_abs_bound(r, j, start + len, bound)) + invariant(array_abs_bound(r, start + len, j + len, bound + MLD_FQMUL_BOUND)) + invariant(array_abs_bound(r, j + len, MLDSA_N, bound)) + decreases(start + len - j)) + { + int32_t t; + t = mld_fqmul(r[j + len], zeta); + r[j + len] = r[j] - t; + r[j] = r[j] + t; + } +} + +/* mld_ntt_layer() + * + * Compute one layer of forward NTT + * + * Parameters: + * - r: Pointer to base of polynomial + * - layer: Indicates which layer is being applied. + */ + +/* Reference: Embedded in `ntt()` in the reference implementation @[REF]. */ +static MLD_INLINE void mld_ntt_layer(int32_t r[MLDSA_N], const unsigned layer) +__contract__( + requires(memory_no_alias(r, sizeof(int32_t) * MLDSA_N)) + requires(1 <= layer && layer <= 8) + requires(array_abs_bound(r, 0, MLDSA_N, layer * MLD_FQMUL_BOUND)) + assigns(memory_slice(r, sizeof(int32_t) * MLDSA_N)) + ensures(array_abs_bound(r, 0, MLDSA_N, (layer + 1) * MLD_FQMUL_BOUND))) +{ + unsigned start, k, len; + /* Twiddle factors for layer n are at indices 2^(n-1)..2^n-1. */ + k = 1u << (layer - 1); + len = (unsigned)MLDSA_N >> layer; + for (start = 0; start < MLDSA_N; start += 2 * len) + __loop__( + invariant(start < MLDSA_N + 2 * len) + invariant(k <= MLDSA_N) + invariant(2 * len * k == start + MLDSA_N) + invariant(array_abs_bound(r, 0, start, layer * MLD_FQMUL_BOUND + MLD_FQMUL_BOUND)) + invariant(array_abs_bound(r, start, MLDSA_N, layer * MLD_FQMUL_BOUND)) + decreases(MLDSA_N - start)) + { + int32_t zeta = mld_zetas[k++]; + mld_ntt_butterfly_block(r, zeta, start, len, layer * MLD_FQMUL_BOUND); + } +} + +MLD_STATIC_TESTABLE void mld_poly_ntt_c(mld_poly *a) +__contract__( + requires(memory_no_alias(a, sizeof(mld_poly))) + requires(array_abs_bound(a->coeffs, 0, MLDSA_N, MLDSA_Q)) + assigns(memory_slice(a, sizeof(mld_poly))) + ensures(array_abs_bound(a->coeffs, 0, MLDSA_N, MLD_NTT_BOUND)) +) +{ + unsigned int layer; + int32_t *r; + + + mld_assert_abs_bound(a->coeffs, MLDSA_N, MLDSA_Q); + r = a->coeffs; + + for (layer = 1; layer < 9; layer++) + __loop__( + invariant(1 <= layer && layer <= 9) + invariant(array_abs_bound(r, 0, MLDSA_N, layer * MLD_FQMUL_BOUND)) + decreases(9 - layer) + ) + { + mld_ntt_layer(r, layer); + } + + mld_assert_abs_bound(a->coeffs, MLDSA_N, MLD_NTT_BOUND); +} + +MLD_INTERNAL_API +void mld_poly_ntt(mld_poly *a) +{ +#if defined(MLD_USE_NATIVE_NTT) + int ret; + mld_assert_abs_bound(a->coeffs, MLDSA_N, MLDSA_Q); + ret = mld_ntt_native(a->coeffs); + if (ret == MLD_NATIVE_FUNC_SUCCESS) + { + mld_assert_abs_bound(a->coeffs, MLDSA_N, MLD_NTT_BOUND); + return; + } +#endif /* MLD_USE_NATIVE_NTT */ + mld_poly_ntt_c(a); +} + +/** + * Scale a field element by mont/256, i.e., perform Montgomery multiplication + * by mont^2/256. + * + * Input is expected to have absolute value smaller than 256 * MLDSA_Q. Output + * has absolute value smaller than MLD_INTT_BOUND. + * + * @param a Field element to be scaled. + */ +static MLD_INLINE int32_t mld_fqscale(int32_t a) +__contract__( + requires(a > -256*MLDSA_Q && a < 256*MLDSA_Q) + ensures(return_value > -MLD_INTT_BOUND && return_value < MLD_INTT_BOUND) +) +{ + /* check-magic: 41978 == pow(2,64-8,MLDSA_Q) */ + const int32_t f = 41978; + /* Bounds: MLD_INTT_BOUND is MLDSA_Q, so the bounds reasoning is just + * a special case of that in mld_fqmul(). */ + return mld_montgomery_reduce((int64_t)a * f); +} + +/* Reference: Embedded into `invntt_tomont()` in the reference implementation + * @[REF] */ +static MLD_INLINE void mld_invntt_layer(int32_t r[MLDSA_N], unsigned layer) +__contract__( + requires(memory_no_alias(r, sizeof(int32_t) * MLDSA_N)) + requires(1 <= layer && layer <= 8) + requires(array_abs_bound(r, 0, MLDSA_N, (MLDSA_N >> layer) * MLDSA_Q)) + assigns(memory_slice(r, sizeof(int32_t) * MLDSA_N)) + ensures(array_abs_bound(r, 0, MLDSA_N, (MLDSA_N >> (layer - 1)) * MLDSA_Q))) +{ + unsigned start, k, len; + len = (unsigned)MLDSA_N >> layer; + k = (1u << layer) - 1; + for (start = 0; start < MLDSA_N; start += 2 * len) + __loop__( + invariant(start <= MLDSA_N && k <= 255) + invariant(2 * len * k + start == 2 * MLDSA_N - 2 * len) + invariant(array_abs_bound(r, 0, start, (MLDSA_N >> (layer - 1)) * MLDSA_Q)) + invariant(array_abs_bound(r, start, MLDSA_N, (MLDSA_N >> layer) * MLDSA_Q)) + decreases(MLDSA_N - start)) + { + unsigned j; + int32_t zeta = -mld_zetas[k--]; + + /* The bound `(MLDSA_N >> (layer - 1)) * MLDSA_Q` is loose enough to + * cover both the input bound `(MLDSA_N >> layer) * MLDSA_Q` + * (for layers >= 1) and the fqmul output bound `MLD_FQMUL_BOUND` + * (which is < 2 * MLDSA_Q <= (MLDSA_N >> (layer - 1)) * MLDSA_Q). */ + for (j = start; j < start + len; j++) + __loop__( + invariant(start <= j && j <= start + len) + invariant(array_abs_bound(r, 0, start, (MLDSA_N >> (layer - 1)) * MLDSA_Q)) + invariant(array_abs_bound(r, start, j, (MLDSA_N >> (layer - 1)) * MLDSA_Q)) + invariant(array_abs_bound(r, j, start + len, (MLDSA_N >> layer) * MLDSA_Q)) + invariant(array_abs_bound(r, start + len, j + len, (MLDSA_N >> (layer - 1)) * MLDSA_Q)) + invariant(array_abs_bound(r, j + len, MLDSA_N, (MLDSA_N >> layer) * MLDSA_Q)) + decreases(start + len - j)) + { + int32_t t = r[j]; + r[j] = t + r[j + len]; + r[j + len] = t - r[j + len]; + r[j + len] = mld_fqmul(r[j + len], zeta); + } + } +} + +MLD_STATIC_TESTABLE void mld_poly_invntt_tomont_c(mld_poly *a) +__contract__( + requires(memory_no_alias(a, sizeof(mld_poly))) + requires(array_abs_bound(a->coeffs, 0, MLDSA_N, MLDSA_Q)) + assigns(memory_slice(a, sizeof(mld_poly))) + ensures(array_abs_bound(a->coeffs, 0, MLDSA_N, MLD_INTT_BOUND)) +) +{ + unsigned int layer, j; + int32_t *r; + + mld_assert_abs_bound(a->coeffs, MLDSA_N, MLDSA_Q); + + r = a->coeffs; + for (layer = 8; layer >= 1; layer--) + __loop__( + invariant(layer <= 8) + /* Absolute bounds increase from 1Q before layer 8 */ + /* up to 256Q after layer 1 */ + invariant(array_abs_bound(r, 0, MLDSA_N, (MLDSA_N >> layer) * MLDSA_Q)) + decreases(layer)) + { + mld_invntt_layer(r, layer); + } + + /* Coefficient bounds are now at 256Q. We now scale by mont / 256, + * i.e., compute the Montgomery multiplication by mont^2 / 256. + * mont corrects the mont^-1 factor introduced in the basemul. + * 1/256 performs that scaling of the inverse NTT. + * The reduced value is bounded by MLD_INTT_BOUND in absolute + * value.*/ + for (j = 0; j < MLDSA_N; ++j) + __loop__( + invariant(j <= MLDSA_N) + invariant(array_abs_bound(r, 0, j, MLD_INTT_BOUND)) + invariant(array_abs_bound(r, j, MLDSA_N, MLDSA_N * MLDSA_Q)) + decreases(MLDSA_N - j) + ) + { + r[j] = mld_fqscale(r[j]); + } + + mld_assert_abs_bound(a->coeffs, MLDSA_N, MLD_INTT_BOUND); +} + + +MLD_INTERNAL_API +void mld_poly_invntt_tomont(mld_poly *a) +{ +#if defined(MLD_USE_NATIVE_INTT) + int ret; + mld_assert_abs_bound(a->coeffs, MLDSA_N, MLDSA_Q); + ret = mld_intt_native(a->coeffs); + if (ret == MLD_NATIVE_FUNC_SUCCESS) + { + mld_assert_abs_bound(a->coeffs, MLDSA_N, MLD_INTT_BOUND); + return; + } +#endif /* MLD_USE_NATIVE_INTT */ + mld_poly_invntt_tomont_c(a); +} + +#if !defined(MLD_CONFIG_NO_SIGN_API) || !defined(MLD_CONFIG_NO_VERIFY_API) || \ + defined(MLD_CONFIG_REDUCE_RAM) || defined(MLD_UNIT_TEST) +MLD_STATIC_TESTABLE void mld_poly_pointwise_montgomery_c(mld_poly *a, + const mld_poly *b) +__contract__( + requires(memory_no_alias(a, sizeof(mld_poly))) + requires(memory_no_alias(b, sizeof(mld_poly))) + requires(array_abs_bound(a->coeffs, 0, MLDSA_N, MLD_NTT_BOUND)) + requires(array_abs_bound(b->coeffs, 0, MLDSA_N, MLD_NTT_BOUND)) + assigns(memory_slice(a, sizeof(mld_poly))) + ensures(array_abs_bound(a->coeffs, 0, MLDSA_N, MLDSA_Q)) +) +{ + unsigned int i; + mld_assert_abs_bound(a->coeffs, MLDSA_N, MLD_NTT_BOUND); + mld_assert_abs_bound(b->coeffs, MLDSA_N, MLD_NTT_BOUND); + + for (i = 0; i < MLDSA_N; ++i) + __loop__( + invariant(i <= MLDSA_N) + invariant(array_abs_bound(a->coeffs, 0, i, MLDSA_Q)) + invariant(array_abs_bound(a->coeffs, i, MLDSA_N, MLD_NTT_BOUND)) + decreases(MLDSA_N - i) + ) + { + a->coeffs[i] = mld_montgomery_reduce((int64_t)a->coeffs[i] * b->coeffs[i]); + } + mld_assert_abs_bound(a->coeffs, MLDSA_N, MLDSA_Q); +} + +MLD_INTERNAL_API +void mld_poly_pointwise_montgomery(mld_poly *a, const mld_poly *b) +{ +#if defined(MLD_USE_NATIVE_POINTWISE_MONTGOMERY) + int ret; + mld_assert_abs_bound(a->coeffs, MLDSA_N, MLD_NTT_BOUND); + mld_assert_abs_bound(b->coeffs, MLDSA_N, MLD_NTT_BOUND); + ret = mld_poly_pointwise_montgomery_native(a->coeffs, b->coeffs); + if (ret == MLD_NATIVE_FUNC_SUCCESS) + { + mld_assert_abs_bound(a->coeffs, MLDSA_N, MLDSA_Q); + return; + } +#endif /* MLD_USE_NATIVE_POINTWISE_MONTGOMERY */ + mld_poly_pointwise_montgomery_c(a, b); +} +#endif /* !MLD_CONFIG_NO_SIGN_API || !MLD_CONFIG_NO_VERIFY_API || \ + MLD_CONFIG_REDUCE_RAM || MLD_UNIT_TEST */ + +#if !defined(MLD_CONFIG_NO_KEYPAIR_API) +MLD_INTERNAL_API +void mld_poly_power2round(mld_poly *a1, mld_poly *a0, const mld_poly *a) +{ + unsigned int i; + mld_assert_bound(a->coeffs, MLDSA_N, 0, MLDSA_Q); + + for (i = 0; i < MLDSA_N; ++i) + __loop__( + assigns(i, memory_slice(a0, sizeof(mld_poly)), memory_slice(a1, sizeof(mld_poly))) + invariant(i <= MLDSA_N) + invariant(forall(k0, i, MLDSA_N, a->coeffs[k0] == loop_entry(*a).coeffs[k0])) + invariant(array_bound(a0->coeffs, 0, i, -(MLD_2_POW_D/2)+1, (MLD_2_POW_D/2)+1)) + invariant(array_bound(a1->coeffs, 0, i, 0, ((MLDSA_Q - 1) / MLD_2_POW_D) + 1)) + decreases(MLDSA_N - i) + ) + { + mld_power2round(&a0->coeffs[i], &a1->coeffs[i], a->coeffs[i]); + } + + mld_assert_bound(a0->coeffs, MLDSA_N, -(MLD_2_POW_D / 2) + 1, + (MLD_2_POW_D / 2) + 1); + mld_assert_bound(a1->coeffs, MLDSA_N, 0, ((MLDSA_Q - 1) / MLD_2_POW_D) + 1); +} +#endif /* !MLD_CONFIG_NO_KEYPAIR_API */ + +#ifndef MLD_POLY_UNIFORM_NBLOCKS +#define MLD_POLY_UNIFORM_NBLOCKS \ + ((768 + MLD_STREAM128_BLOCKBYTES - 1) / MLD_STREAM128_BLOCKBYTES) +#endif +/* Reference: `mld_rej_uniform()` in the reference implementation @[REF]. + * - Our signature differs from the reference implementation + * in that it adds the offset and always expects the base of the + * target buffer. This avoids shifting the buffer base in the + * caller, which appears tricky to reason about. */ +MLD_STATIC_TESTABLE unsigned int mld_rej_uniform_c(int32_t *a, + unsigned int target, + unsigned int offset, + const uint8_t *buf, + unsigned int buflen) +__contract__( + requires(offset <= target && target <= MLDSA_N) + requires(buflen <= (MLD_POLY_UNIFORM_NBLOCKS * MLD_STREAM128_BLOCKBYTES) && buflen % 3 == 0) + requires(memory_no_alias(a, sizeof(int32_t) * target)) + requires(memory_no_alias(buf, buflen)) + requires(array_bound(a, 0, offset, 0, MLDSA_Q)) + assigns(memory_slice(a, sizeof(int32_t) * target)) + ensures(offset <= return_value && return_value <= target) + ensures(array_bound(a, 0, return_value, 0, MLDSA_Q)) +) +{ + unsigned int ctr, pos; + uint32_t t; + mld_assert_bound(a, offset, 0, MLDSA_Q); + + ctr = offset; + pos = 0; + /* pos + 3 cannot overflow due to the assumption + buflen <= (MLD_POLY_UNIFORM_NBLOCKS * MLD_STREAM128_BLOCKBYTES) */ + while (ctr < target && pos + 3 <= buflen) + __loop__( + invariant(offset <= ctr && ctr <= target && pos <= buflen) + invariant(array_bound(a, 0, ctr, 0, MLDSA_Q)) + decreases(buflen - pos)) + { + t = buf[pos++]; + t |= (uint32_t)buf[pos++] << 8; + t |= (uint32_t)buf[pos++] << 16; + t &= 0x7FFFFF; + + if (t < MLDSA_Q) + { + a[ctr++] = (int32_t)t; + } + } + + mld_assert_bound(a, ctr, 0, MLDSA_Q); + + return ctr; +} +/** + * Sample uniformly random coefficients in [0, MLDSA_Q-1] by performing + * rejection sampling on an array of random bytes. + * + * @param[out] a Pointer to output array (allocated). + * @param target Requested number of coefficients to sample. + * @param offset Number of coefficients already sampled. + * @param[in] buf Array of random bytes to sample from. + * @param buflen Length of array of random bytes (must be multiple of 3). + * + * @return Number of sampled coefficients. Can be smaller than len if not + * enough random bytes were given. + */ + +/* Reference: `mld_rej_uniform()` in the reference implementation @[REF]. + * - Our signature differs from the reference implementation + * in that it adds the offset and always expects the base of the + * target buffer. This avoids shifting the buffer base in the + * caller, which appears tricky to reason about. */ +static unsigned int mld_rej_uniform(int32_t *a, unsigned int target, + unsigned int offset, const uint8_t *buf, + unsigned int buflen) +__contract__( + requires(offset <= target && target <= MLDSA_N) + requires(buflen <= (MLD_POLY_UNIFORM_NBLOCKS * MLD_STREAM128_BLOCKBYTES) && buflen % 3 == 0) + requires(memory_no_alias(a, sizeof(int32_t) * target)) + requires(memory_no_alias(buf, buflen)) + requires(array_bound(a, 0, offset, 0, MLDSA_Q)) + assigns(memory_slice(a, sizeof(int32_t) * target)) + ensures(offset <= return_value && return_value <= target) + ensures(array_bound(a, 0, return_value, 0, MLDSA_Q)) +) +{ +#if defined(MLD_USE_NATIVE_REJ_UNIFORM) + int ret; + mld_assert_bound(a, offset, 0, MLDSA_Q); + if (offset == 0) + { + ret = mld_rej_uniform_native(a, target, buf, buflen); + if (ret != MLD_NATIVE_FUNC_FALLBACK) + { + unsigned res = (unsigned)ret; + mld_assert_bound(a, res, 0, MLDSA_Q); + return res; + } + } +#endif /* MLD_USE_NATIVE_REJ_UNIFORM */ + + return mld_rej_uniform_c(a, target, offset, buf, buflen); +} + +/* Reference: poly_uniform() in the reference implementation @[REF]. + * - Simplified from reference by removing buffer tail handling + * since buflen % 3 = 0 always holds true (MLD_STREAM128_BLOCKBYTES + * = 168). + * - Modified rej_uniform interface to track offset directly. + * - Pass nonce packed in the extended seed array instead of a third + * argument. + * */ +MLD_INTERNAL_API +void mld_poly_uniform(mld_poly *a, const uint8_t seed[MLDSA_SEEDBYTES + 2]) +{ + unsigned int ctr; + unsigned int buflen = MLD_POLY_UNIFORM_NBLOCKS * MLD_STREAM128_BLOCKBYTES; + MLD_ALIGN uint8_t buf[MLD_POLY_UNIFORM_NBLOCKS * MLD_STREAM128_BLOCKBYTES]; + mld_xof128_ctx state; + + mld_xof128_init(&state); + mld_xof128_absorb_once(&state, seed, MLDSA_SEEDBYTES + 2); + mld_xof128_squeezeblocks(buf, MLD_POLY_UNIFORM_NBLOCKS, &state); + + ctr = mld_rej_uniform(a->coeffs, MLDSA_N, 0, buf, buflen); + buflen = MLD_STREAM128_BLOCKBYTES; + while (ctr < MLDSA_N) + __loop__( + assigns(ctr, state, memory_slice(a, sizeof(mld_poly)), object_whole(buf)) + invariant(ctr <= MLDSA_N) + invariant(array_bound(a->coeffs, 0, ctr, 0, MLDSA_Q)) + invariant(state.pos <= SHAKE128_RATE) + ) + { + mld_xof128_squeezeblocks(buf, 1, &state); + ctr = mld_rej_uniform(a->coeffs, MLDSA_N, ctr, buf, buflen); + } + mld_xof128_release(&state); + mld_assert_bound(a->coeffs, MLDSA_N, 0, MLDSA_Q); + + /* @[FIPS204, Section 3.6.3] Destruction of intermediate values. */ + mld_zeroize(buf, sizeof(buf)); +} + +#if !defined(MLD_CONFIG_SERIAL_FIPS202_ONLY) && \ + (!defined(MLD_CONFIG_REDUCE_RAM) || defined(MLD_UNIT_TEST)) +MLD_INTERNAL_API +void mld_poly_uniform_4x(mld_poly *vec0, mld_poly *vec1, mld_poly *vec2, + mld_poly *vec3, + uint8_t seed[4][MLD_ALIGN_UP(MLDSA_SEEDBYTES + 2)]) +{ + /* Temporary buffers for XOF output before rejection sampling */ + MLD_ALIGN uint8_t + buf[4][MLD_ALIGN_UP(MLD_POLY_UNIFORM_NBLOCKS * MLD_STREAM128_BLOCKBYTES)]; + + /* Tracks the number of coefficients we have already sampled */ + unsigned ctr[4]; + mld_xof128_x4_ctx state; + unsigned buflen; + + mld_xof128_x4_init(&state); + mld_xof128_x4_absorb(&state, seed, MLDSA_SEEDBYTES + 2); + + /* + * Initially, squeeze heuristic number of MLD_POLY_UNIFORM_NBLOCKS. + * This should generate the matrix entries with high probability. + */ + + mld_xof128_x4_squeezeblocks(buf, MLD_POLY_UNIFORM_NBLOCKS, &state); + buflen = MLD_POLY_UNIFORM_NBLOCKS * MLD_STREAM128_BLOCKBYTES; + ctr[0] = mld_rej_uniform(vec0->coeffs, MLDSA_N, 0, buf[0], buflen); + ctr[1] = mld_rej_uniform(vec1->coeffs, MLDSA_N, 0, buf[1], buflen); + ctr[2] = mld_rej_uniform(vec2->coeffs, MLDSA_N, 0, buf[2], buflen); + ctr[3] = mld_rej_uniform(vec3->coeffs, MLDSA_N, 0, buf[3], buflen); + + /* + * So long as not all matrix entries have been generated, squeeze + * one more block a time until we're done. + */ + buflen = MLD_STREAM128_BLOCKBYTES; + while (ctr[0] < MLDSA_N || ctr[1] < MLDSA_N || ctr[2] < MLDSA_N || + ctr[3] < MLDSA_N) + __loop__( + assigns(ctr, state, object_whole(buf), + memory_slice(vec0, sizeof(mld_poly)), memory_slice(vec1, sizeof(mld_poly)), + memory_slice(vec2, sizeof(mld_poly)), memory_slice(vec3, sizeof(mld_poly))) + invariant(ctr[0] <= MLDSA_N && ctr[1] <= MLDSA_N) + invariant(ctr[2] <= MLDSA_N && ctr[3] <= MLDSA_N) + invariant(array_bound(vec0->coeffs, 0, ctr[0], 0, MLDSA_Q)) + invariant(array_bound(vec1->coeffs, 0, ctr[1], 0, MLDSA_Q)) + invariant(array_bound(vec2->coeffs, 0, ctr[2], 0, MLDSA_Q)) + invariant(array_bound(vec3->coeffs, 0, ctr[3], 0, MLDSA_Q))) + { + mld_xof128_x4_squeezeblocks(buf, 1, &state); + ctr[0] = mld_rej_uniform(vec0->coeffs, MLDSA_N, ctr[0], buf[0], buflen); + ctr[1] = mld_rej_uniform(vec1->coeffs, MLDSA_N, ctr[1], buf[1], buflen); + ctr[2] = mld_rej_uniform(vec2->coeffs, MLDSA_N, ctr[2], buf[2], buflen); + ctr[3] = mld_rej_uniform(vec3->coeffs, MLDSA_N, ctr[3], buf[3], buflen); + } + mld_xof128_x4_release(&state); + + mld_assert_bound(vec0->coeffs, MLDSA_N, 0, MLDSA_Q); + mld_assert_bound(vec1->coeffs, MLDSA_N, 0, MLDSA_Q); + mld_assert_bound(vec2->coeffs, MLDSA_N, 0, MLDSA_Q); + mld_assert_bound(vec3->coeffs, MLDSA_N, 0, MLDSA_Q); + + /* @[FIPS204, Section 3.6.3] Destruction of intermediate values. */ + mld_zeroize(buf, sizeof(buf)); +} + +#endif /* !MLD_CONFIG_SERIAL_FIPS202_ONLY && (!MLD_CONFIG_REDUCE_RAM || \ + MLD_UNIT_TEST) */ + +#if !defined(MLD_CONFIG_NO_KEYPAIR_API) +MLD_INTERNAL_API +void mld_polyt1_pack(uint8_t r[MLDSA_POLYT1_PACKEDBYTES], const mld_poly *a) +{ + unsigned int i; + mld_assert_bound(a->coeffs, MLDSA_N, 0, 1 << 10); + + for (i = 0; i < MLDSA_N / 4; ++i) + __loop__( + invariant(i <= MLDSA_N/4) + decreases(MLDSA_N / 4 - i)) + { + r[5 * i + 0] = (uint8_t)((a->coeffs[4 * i + 0] >> 0) & 0xFF); + r[5 * i + 1] = + (uint8_t)(((a->coeffs[4 * i + 0] >> 8) | (a->coeffs[4 * i + 1] << 2)) & + 0xFF); + r[5 * i + 2] = + (uint8_t)(((a->coeffs[4 * i + 1] >> 6) | (a->coeffs[4 * i + 2] << 4)) & + 0xFF); + r[5 * i + 3] = + (uint8_t)(((a->coeffs[4 * i + 2] >> 4) | (a->coeffs[4 * i + 3] << 6)) & + 0xFF); + r[5 * i + 4] = (uint8_t)((a->coeffs[4 * i + 3] >> 2) & 0xFF); + } +} +#endif /* !MLD_CONFIG_NO_KEYPAIR_API */ + +#if !defined(MLD_CONFIG_NO_VERIFY_API) +MLD_INTERNAL_API +void mld_polyt1_unpack(mld_poly *r, const uint8_t a[MLDSA_POLYT1_PACKEDBYTES]) +{ + unsigned int i; + + for (i = 0; i < MLDSA_N / 4; ++i) + __loop__( + invariant(i <= MLDSA_N/4) + invariant(array_bound(r->coeffs, 0, i*4, 0, 1 << 10)) + decreases(MLDSA_N / 4 - i)) + { + r->coeffs[4 * i + 0] = + ((a[5 * i + 0] >> 0) | ((int32_t)a[5 * i + 1] << 8)) & 0x3FF; + r->coeffs[4 * i + 1] = + ((a[5 * i + 1] >> 2) | ((int32_t)a[5 * i + 2] << 6)) & 0x3FF; + r->coeffs[4 * i + 2] = + ((a[5 * i + 2] >> 4) | ((int32_t)a[5 * i + 3] << 4)) & 0x3FF; + r->coeffs[4 * i + 3] = + ((a[5 * i + 3] >> 6) | ((int32_t)a[5 * i + 4] << 2)) & 0x3FF; + } + + mld_assert_bound(r->coeffs, MLDSA_N, 0, 1 << 10); +} +#endif /* !MLD_CONFIG_NO_VERIFY_API */ + +#if !defined(MLD_CONFIG_NO_KEYPAIR_API) +MLD_INTERNAL_API +void mld_polyt0_pack(uint8_t r[MLDSA_POLYT0_PACKEDBYTES], const mld_poly *a) +{ + unsigned int i; + uint32_t t[8]; + + mld_assert_bound(a->coeffs, MLDSA_N, -(1 << (MLDSA_D - 1)) + 1, + (1 << (MLDSA_D - 1)) + 1); + + for (i = 0; i < MLDSA_N / 8; ++i) + __loop__( + invariant(i <= MLDSA_N/8) + decreases(MLDSA_N / 8 - i)) + { + /* Safety: a->coeffs[i] <= (1 << (MLDSA_D - 1) as they are output of + * power2round, hence, these casts are safe. */ + t[0] = (uint32_t)((1 << (MLDSA_D - 1)) - a->coeffs[8 * i + 0]); + t[1] = (uint32_t)((1 << (MLDSA_D - 1)) - a->coeffs[8 * i + 1]); + t[2] = (uint32_t)((1 << (MLDSA_D - 1)) - a->coeffs[8 * i + 2]); + t[3] = (uint32_t)((1 << (MLDSA_D - 1)) - a->coeffs[8 * i + 3]); + t[4] = (uint32_t)((1 << (MLDSA_D - 1)) - a->coeffs[8 * i + 4]); + t[5] = (uint32_t)((1 << (MLDSA_D - 1)) - a->coeffs[8 * i + 5]); + t[6] = (uint32_t)((1 << (MLDSA_D - 1)) - a->coeffs[8 * i + 6]); + t[7] = (uint32_t)((1 << (MLDSA_D - 1)) - a->coeffs[8 * i + 7]); + + r[13 * i + 0] = (uint8_t)((t[0]) & 0xFF); + r[13 * i + 1] = (uint8_t)((t[0] >> 8) & 0xFF); + r[13 * i + 1] |= (uint8_t)((t[1] << 5) & 0xFF); + r[13 * i + 2] = (uint8_t)((t[1] >> 3) & 0xFF); + r[13 * i + 3] = (uint8_t)((t[1] >> 11) & 0xFF); + r[13 * i + 3] |= (uint8_t)((t[2] << 2) & 0xFF); + r[13 * i + 4] = (uint8_t)((t[2] >> 6) & 0xFF); + r[13 * i + 4] |= (uint8_t)((t[3] << 7) & 0xFF); + r[13 * i + 5] = (uint8_t)((t[3] >> 1) & 0xFF); + r[13 * i + 6] = (uint8_t)((t[3] >> 9) & 0xFF); + r[13 * i + 6] |= (uint8_t)((t[4] << 4) & 0xFF); + r[13 * i + 7] = (uint8_t)((t[4] >> 4) & 0xFF); + r[13 * i + 8] = (uint8_t)((t[4] >> 12) & 0xFF); + r[13 * i + 8] |= (uint8_t)((t[5] << 1) & 0xFF); + r[13 * i + 9] = (uint8_t)((t[5] >> 7) & 0xFF); + r[13 * i + 9] |= (uint8_t)((t[6] << 6) & 0xFF); + r[13 * i + 10] = (uint8_t)((t[6] >> 2) & 0xFF); + r[13 * i + 11] = (uint8_t)((t[6] >> 10) & 0xFF); + r[13 * i + 11] |= (uint8_t)((t[7] << 3) & 0xFF); + r[13 * i + 12] = (uint8_t)((t[7] >> 5) & 0xFF); + } +} +#endif /* !MLD_CONFIG_NO_KEYPAIR_API */ + +#if !defined(MLD_CONFIG_NO_SIGN_API) || defined(MLD_UNIT_TEST) +MLD_INTERNAL_API +void mld_polyt0_unpack(mld_poly *r, const uint8_t a[MLDSA_POLYT0_PACKEDBYTES]) +{ + unsigned int i; + + for (i = 0; i < MLDSA_N / 8; ++i) + __loop__( + invariant(i <= MLDSA_N/8) + invariant(array_bound(r->coeffs, 0, i*8, -(1<<(MLDSA_D-1)) + 1, (1<<(MLDSA_D-1)) + 1)) + decreases(MLDSA_N / 8 - i)) + { + r->coeffs[8 * i + 0] = a[13 * i + 0]; + r->coeffs[8 * i + 0] |= (int32_t)a[13 * i + 1] << 8; + r->coeffs[8 * i + 0] &= 0x1FFF; + + r->coeffs[8 * i + 1] = a[13 * i + 1] >> 5; + r->coeffs[8 * i + 1] |= (int32_t)a[13 * i + 2] << 3; + r->coeffs[8 * i + 1] |= (int32_t)a[13 * i + 3] << 11; + r->coeffs[8 * i + 1] &= 0x1FFF; + + r->coeffs[8 * i + 2] = a[13 * i + 3] >> 2; + r->coeffs[8 * i + 2] |= (int32_t)a[13 * i + 4] << 6; + r->coeffs[8 * i + 2] &= 0x1FFF; + + r->coeffs[8 * i + 3] = a[13 * i + 4] >> 7; + r->coeffs[8 * i + 3] |= (int32_t)a[13 * i + 5] << 1; + r->coeffs[8 * i + 3] |= (int32_t)a[13 * i + 6] << 9; + r->coeffs[8 * i + 3] &= 0x1FFF; + + r->coeffs[8 * i + 4] = a[13 * i + 6] >> 4; + r->coeffs[8 * i + 4] |= (int32_t)a[13 * i + 7] << 4; + r->coeffs[8 * i + 4] |= (int32_t)a[13 * i + 8] << 12; + r->coeffs[8 * i + 4] &= 0x1FFF; + + r->coeffs[8 * i + 5] = a[13 * i + 8] >> 1; + r->coeffs[8 * i + 5] |= (int32_t)a[13 * i + 9] << 7; + r->coeffs[8 * i + 5] &= 0x1FFF; + + r->coeffs[8 * i + 6] = a[13 * i + 9] >> 6; + r->coeffs[8 * i + 6] |= (int32_t)a[13 * i + 10] << 2; + r->coeffs[8 * i + 6] |= (int32_t)a[13 * i + 11] << 10; + r->coeffs[8 * i + 6] &= 0x1FFF; + + r->coeffs[8 * i + 7] = a[13 * i + 11] >> 3; + r->coeffs[8 * i + 7] |= (int32_t)a[13 * i + 12] << 5; + r->coeffs[8 * i + 7] &= 0x1FFF; + + r->coeffs[8 * i + 0] = (1 << (MLDSA_D - 1)) - r->coeffs[8 * i + 0]; + r->coeffs[8 * i + 1] = (1 << (MLDSA_D - 1)) - r->coeffs[8 * i + 1]; + r->coeffs[8 * i + 2] = (1 << (MLDSA_D - 1)) - r->coeffs[8 * i + 2]; + r->coeffs[8 * i + 3] = (1 << (MLDSA_D - 1)) - r->coeffs[8 * i + 3]; + r->coeffs[8 * i + 4] = (1 << (MLDSA_D - 1)) - r->coeffs[8 * i + 4]; + r->coeffs[8 * i + 5] = (1 << (MLDSA_D - 1)) - r->coeffs[8 * i + 5]; + r->coeffs[8 * i + 6] = (1 << (MLDSA_D - 1)) - r->coeffs[8 * i + 6]; + r->coeffs[8 * i + 7] = (1 << (MLDSA_D - 1)) - r->coeffs[8 * i + 7]; + } + + mld_assert_bound(r->coeffs, MLDSA_N, -(1 << (MLDSA_D - 1)) + 1, + (1 << (MLDSA_D - 1)) + 1); +} +#endif /* !MLD_CONFIG_NO_SIGN_API || MLD_UNIT_TEST */ + +MLD_STATIC_TESTABLE uint32_t mld_poly_chknorm_c(const mld_poly *a, int32_t B) +__contract__( + requires(memory_no_alias(a, sizeof(mld_poly))) + requires(0 <= B && B <= MLDSA_Q - MLD_REDUCE32_RANGE_MAX) + requires(array_bound(a->coeffs, 0, MLDSA_N, -MLD_REDUCE32_RANGE_MAX, MLD_REDUCE32_RANGE_MAX)) + ensures(return_value == 0 || return_value == 0xFFFFFFFF) + ensures((return_value == 0) == array_abs_bound(a->coeffs, 0, MLDSA_N, B)) +) +{ + unsigned int i; + uint32_t t = 0; + mld_assert_bound(a->coeffs, MLDSA_N, -MLD_REDUCE32_RANGE_MAX, + MLD_REDUCE32_RANGE_MAX); + for (i = 0; i < MLDSA_N; ++i) + __loop__( + invariant(i <= MLDSA_N) + invariant(t == 0 || t == 0xFFFFFFFF) + invariant((t == 0) == array_abs_bound(a->coeffs, 0, i, B)) + decreases(MLDSA_N - i) + ) + { + /* + * Since we know that -MLD_REDUCE32_RANGE_MAX <= a < MLD_REDUCE32_RANGE_MAX, + * and B <= MLDSA_Q - MLD_REDUCE32_RANGE_MAX, to check if + * -B < (a modΒ± MLDSA_Q) < B, it suffices to check if -B < a < B. + * + * We prove this to be true using the following CBMC assertions. + * a ==> b expressed as !a || b to also allow run-time assertion. + */ + mld_assert(a->coeffs[i] < B || a->coeffs[i] - MLDSA_Q <= -B); + mld_assert(a->coeffs[i] > -B || a->coeffs[i] + MLDSA_Q >= B); + + /* Reference: Leaks which coefficient violates the bound via a conditional. + * We are more conservative to reduce the number of declassifications in + * constant-time testing. + */ + + /* if (abs(a[i]) >= B) */ + t |= mld_ct_cmask_neg_i32(B - 1 - mld_ct_abs_i32(a->coeffs[i])); + } + + return t; +} + +/* Reference: explicitly checks the bound B to be <= (MLDSA_Q - 1) / 8). + * This is unnecessary as it's always a compile-time constant. + * We instead model it as a precondition. + * Checking the bound is performed using a conditional arguing + * that it is okay to leak which coefficient violates the bound (while the + * coefficient itself must remain secret). + * We instead perform everything in constant-time. + * Also it is sufficient to check that it is smaller than + * MLDSA_Q - MLD_REDUCE32_RANGE_MAX > (MLDSA_Q - 1) / 8). + */ +MLD_INTERNAL_API +uint32_t mld_poly_chknorm(const mld_poly *a, int32_t B) +{ +#if defined(MLD_USE_NATIVE_POLY_CHKNORM) + int ret; + int success; + mld_assert_bound(a->coeffs, MLDSA_N, -MLD_REDUCE32_RANGE_MAX, + MLD_REDUCE32_RANGE_MAX); + /* The native backend returns 0 if all coefficients are within the bound, + * 1 if at least one coefficient exceeds the bound, and + * -1 (MLD_NATIVE_FUNC_FALLBACK) if the platform does not have the + * required capabilities to run the native function. + */ + ret = mld_poly_chknorm_native(a->coeffs, B); + + success = (ret != MLD_NATIVE_FUNC_FALLBACK); + /* Constant-time: It would be fine to leak the return value of chknorm + * entirely (as it is fine to leak if any coefficient exceeded the bound or + * not). However, it is cleaner to perform declassification in sign.c. + * Hence, here we only declassify if the native function returned + * MLD_NATIVE_FUNC_FALLBACK or not (which solely depends on system + * capabilities). + */ + MLD_CT_TESTING_DECLASSIFY(&success, sizeof(int)); + if (success) + { + /* Convert 0 / 1 to 0 / 0xFFFFFFFF here */ + return mld_ct_cmask_nonzero_u32((uint32_t)ret); + } +#endif /* MLD_USE_NATIVE_POLY_CHKNORM */ + return mld_poly_chknorm_c(a, B); +} + +#if !defined(MLD_CONFIG_NO_SIGN_API) || !defined(MLD_CONFIG_NO_VERIFY_API) +#if defined(MLD_CONFIG_MULTILEVEL_WITH_SHARED) || \ + MLD_CONFIG_PARAMETER_SET == 44 +MLD_INTERNAL_API +void mld_polyw1_pack_88(uint8_t r[MLDSA_POLYW1_PACKEDBYTES_88], + const mld_poly *a) +{ + unsigned int i; + + mld_assert_bound(a->coeffs, MLDSA_N, 0, + (MLDSA_Q - 1) / (2 * MLDSA_GAMMA2_88)); + + for (i = 0; i < MLDSA_N / 4; ++i) + __loop__( + invariant(i <= MLDSA_N/4) + decreases(MLDSA_N / 4 - i)) + { + r[3 * i + 0] = (uint8_t)((a->coeffs[4 * i + 0]) & 0xFF); + r[3 * i + 0] |= (uint8_t)((a->coeffs[4 * i + 1] << 6) & 0xFF); + r[3 * i + 1] = (uint8_t)((a->coeffs[4 * i + 1] >> 2) & 0xFF); + r[3 * i + 1] |= (uint8_t)((a->coeffs[4 * i + 2] << 4) & 0xFF); + r[3 * i + 2] = (uint8_t)((a->coeffs[4 * i + 2] >> 4) & 0xFF); + r[3 * i + 2] |= (uint8_t)((a->coeffs[4 * i + 3] << 2) & 0xFF); + } +} +#endif /* MLD_CONFIG_MULTILEVEL_WITH_SHARED || MLD_CONFIG_PARAMETER_SET == 44 \ + */ + +#if defined(MLD_CONFIG_MULTILEVEL_WITH_SHARED) || \ + (MLD_CONFIG_PARAMETER_SET == 65 || MLD_CONFIG_PARAMETER_SET == 87) +MLD_INTERNAL_API +void mld_polyw1_pack_32(uint8_t r[MLDSA_POLYW1_PACKEDBYTES_32], + const mld_poly *a) +{ + unsigned int i; + + mld_assert_bound(a->coeffs, MLDSA_N, 0, + (MLDSA_Q - 1) / (2 * MLDSA_GAMMA2_32)); + + for (i = 0; i < MLDSA_N / 2; ++i) + __loop__( + invariant(i <= MLDSA_N/2) + decreases(MLDSA_N / 2 - i)) + { + r[i] = + (uint8_t)((a->coeffs[2 * i + 0] | (a->coeffs[2 * i + 1] << 4)) & 0xFF); + } +} +#endif /* MLD_CONFIG_MULTILEVEL_WITH_SHARED || MLD_CONFIG_PARAMETER_SET == 65 \ + || MLD_CONFIG_PARAMETER_SET == 87 */ +#endif /* !MLD_CONFIG_NO_SIGN_API || !MLD_CONFIG_NO_VERIFY_API */ + +#else /* !MLD_CONFIG_MULTILEVEL_NO_SHARED */ +MLD_EMPTY_CU(mld_poly) +#endif /* MLD_CONFIG_MULTILEVEL_NO_SHARED */ + +/* To facilitate single-compilation-unit (SCU) builds, undefine all macros. + * Don't modify by hand -- this is auto-generated by scripts/autogen. */ +#undef MLD_POLY_UNIFORM_NBLOCKS diff --git a/mldsa_native/src/poly.h b/mldsa_native/src/poly.h new file mode 100644 index 0000000..2467f23 --- /dev/null +++ b/mldsa_native/src/poly.h @@ -0,0 +1,464 @@ +/* + * Copyright (c) The mldsa-native project authors + * SPDX-License-Identifier: Apache-2.0 OR ISC OR MIT + */ + +/* References + * ========== + * + * - [FIPS204] + * FIPS 204 Module-Lattice-Based Digital Signature Standard + * National Institute of Standards and Technology + * https://csrc.nist.gov/pubs/fips/204/final + */ + +#ifndef MLD_POLY_H +#define MLD_POLY_H + +#include "cbmc.h" +#include "common.h" +#include "reduce.h" +#include "rounding.h" + +/* Absolute exclusive upper bound for the output of fqmul */ +#define MLD_FQMUL_BOUND ((5 * MLDSA_Q + 3) / 4) +/* Absolute exclusive upper bound for the output of the forward NTT */ +#define MLD_NTT_BOUND (9 * MLD_FQMUL_BOUND) +/* Absolute exclusive upper bound for the output of the inverse NTT*/ +#define MLD_INTT_BOUND MLDSA_Q + +/** + * Element of R_q = Z_q[X]/(X^n + 1). Represents polynomial + * coeffs[0] + X*coeffs[1] + X^2*coeffs[2] + ... + X^{n-1}*coeffs[n-1]. + */ +typedef struct +{ + int32_t coeffs[MLDSA_N]; /**< Polynomial coefficients. */ +} MLD_ALIGN mld_poly; + +#define mld_poly_reduce MLD_NAMESPACE(poly_reduce) +/** + * In-place reduction of all coefficients of polynomial to representative in + * [-MLD_REDUCE32_RANGE_MAX, MLD_REDUCE32_RANGE_MAX]. + * + * @param[in,out] a Pointer to input/output polynomial. + */ +MLD_INTERNAL_API +void mld_poly_reduce(mld_poly *a) +__contract__( + requires(memory_no_alias(a, sizeof(mld_poly))) + requires(array_bound(a->coeffs, 0, MLDSA_N, INT32_MIN, MLD_REDUCE32_DOMAIN_MAX)) + assigns(memory_slice(a, sizeof(mld_poly))) + ensures(array_bound(a->coeffs, 0, MLDSA_N, -MLD_REDUCE32_RANGE_MAX, MLD_REDUCE32_RANGE_MAX)) +); + +#define mld_poly_caddq MLD_NAMESPACE(poly_caddq) +/** + * For all coefficients of in/out polynomial add MLDSA_Q if coefficient is + * negative. + * + * @param[in,out] a Pointer to input/output polynomial. + */ +MLD_INTERNAL_API +void mld_poly_caddq(mld_poly *a) +__contract__( + requires(memory_no_alias(a, sizeof(mld_poly))) + requires(array_abs_bound(a->coeffs, 0, MLDSA_N, MLDSA_Q)) + assigns(memory_slice(a, sizeof(mld_poly))) + ensures(array_bound(a->coeffs, 0, MLDSA_N, 0, MLDSA_Q)) +); + +#if !defined(MLD_CONFIG_NO_KEYPAIR_API) || !defined(MLD_CONFIG_NO_SIGN_API) || \ + defined(MLD_CONFIG_REDUCE_RAM) || defined(MLD_UNIT_TEST) +#define mld_poly_add MLD_NAMESPACE(poly_add) +/** + * Add polynomials. No modular reduction is performed. + * + * @spec{Implements @[FIPS204, Algorithm 44, AddNTT] (coefficientwise + * polynomial addition; also used for addition in the normal domain).} + * + * @param[in,out] r Pointer to input-output polynomial to be added to. + * @param[in] b Pointer to input polynomial that should be added to r. + * Must be disjoint from r. + */ + +/* + * NOTE: The reference implementation uses a 3-argument poly_add. + * We specialize to the accumulator form to avoid reasoning about aliasing. + */ +MLD_INTERNAL_API +void mld_poly_add(mld_poly *r, const mld_poly *b) +__contract__( + requires(memory_no_alias(b, sizeof(mld_poly))) + requires(memory_no_alias(r, sizeof(mld_poly))) + requires(forall(k0, 0, MLDSA_N, (int64_t) r->coeffs[k0] + b->coeffs[k0] < MLD_REDUCE32_DOMAIN_MAX)) + requires(forall(k1, 0, MLDSA_N, (int64_t) r->coeffs[k1] + b->coeffs[k1] >= INT32_MIN)) + assigns(memory_slice(r, sizeof(mld_poly))) + ensures(forall(k2, 0, MLDSA_N, r->coeffs[k2] == old(*r).coeffs[k2] + b->coeffs[k2])) + ensures(forall(k3, 0, MLDSA_N, r->coeffs[k3] < MLD_REDUCE32_DOMAIN_MAX)) + ensures(forall(k4, 0, MLDSA_N, r->coeffs[k4] >= INT32_MIN)) +); +#endif /* !MLD_CONFIG_NO_KEYPAIR_API || !MLD_CONFIG_NO_SIGN_API || \ + MLD_CONFIG_REDUCE_RAM || MLD_UNIT_TEST */ + +#if !defined(MLD_CONFIG_NO_SIGN_API) || !defined(MLD_CONFIG_NO_VERIFY_API) +#define mld_poly_sub MLD_NAMESPACE(poly_sub) +/** + * Subtract polynomials. No modular reduction is performed. + * + * @param[in,out] r Pointer to input-output polynomial. + * @param[in] b Pointer to input polynomial that should be subtracted from + * r. Must be disjoint from r. + */ +/* + * NOTE: The reference implementation uses a 3-argument poly_sub. + * We specialize to the accumulator form to avoid reasoning about aliasing. + */ +MLD_INTERNAL_API +void mld_poly_sub(mld_poly *r, const mld_poly *b) +__contract__( + requires(memory_no_alias(b, sizeof(mld_poly))) + requires(memory_no_alias(r, sizeof(mld_poly))) + requires(array_abs_bound(r->coeffs, 0, MLDSA_N, MLDSA_Q)) + requires(array_abs_bound(b->coeffs, 0, MLDSA_N, MLDSA_Q)) + assigns(memory_slice(r, sizeof(mld_poly))) + ensures(array_bound(r->coeffs, 0, MLDSA_N, INT32_MIN, MLD_REDUCE32_DOMAIN_MAX)) +); +#endif /* !MLD_CONFIG_NO_SIGN_API || !MLD_CONFIG_NO_VERIFY_API */ + +#if !defined(MLD_CONFIG_NO_VERIFY_API) +#define mld_poly_shiftl MLD_NAMESPACE(poly_shiftl) +/** + * Multiply polynomial by 2^MLDSA_D without modular reduction. Assumes input + * coefficients to be less than 2^{31-MLDSA_D} in absolute value. + * + * @param[in,out] a Pointer to input/output polynomial. + */ +MLD_INTERNAL_API +void mld_poly_shiftl(mld_poly *a) +__contract__( + requires(memory_no_alias(a, sizeof(mld_poly))) + requires(array_bound(a->coeffs, 0, MLDSA_N, 0, 1 << 10)) + assigns(memory_slice(a, sizeof(mld_poly))) + ensures(array_bound(a->coeffs, 0, MLDSA_N, 0, MLDSA_Q)) +); +#endif /* !MLD_CONFIG_NO_VERIFY_API */ + +#define mld_poly_ntt MLD_NAMESPACE(poly_ntt) +/** + * In-place forward NTT. Output coefficients are bounded by MLD_NTT_BOUND in + * absolute value. + * + * @spec{Implements @[FIPS204, Algorithm 41, NTT].} + * + * @param[in,out] a Pointer to input/output polynomial. + */ +MLD_INTERNAL_API +void mld_poly_ntt(mld_poly *a) +__contract__( + requires(memory_no_alias(a, sizeof(mld_poly))) + requires(array_abs_bound(a->coeffs, 0, MLDSA_N, MLDSA_Q)) + assigns(memory_slice(a, sizeof(mld_poly))) + ensures(array_abs_bound(a->coeffs, 0, MLDSA_N, MLD_NTT_BOUND)) +); + + +#define mld_poly_invntt_tomont MLD_NAMESPACE(poly_invntt_tomont) +/** + * In-place inverse NTT. + * + * Input coefficients need to be less than MLDSA_Q in absolute value and + * output coefficients are bounded by MLD_INTT_BOUND. + * + * @spec{Implements @[FIPS204, Algorithm 42, NTT^{-1}] up to scaling: + * The input is scaled by 2^{-32} as a result of the Montgomery base + * multiplication. The output is in normal domain. In other words, this + * function implements `NTT^{-1} o mult(2^32)`.} + * + * @param[in,out] a Pointer to input/output polynomial. + */ +MLD_INTERNAL_API +void mld_poly_invntt_tomont(mld_poly *a) +__contract__( + requires(memory_no_alias(a, sizeof(mld_poly))) + requires(array_abs_bound(a->coeffs, 0, MLDSA_N, MLDSA_Q)) + assigns(memory_slice(a, sizeof(mld_poly))) + ensures(array_abs_bound(a->coeffs, 0, MLDSA_N, MLD_INTT_BOUND)) +); + +#if !defined(MLD_CONFIG_NO_SIGN_API) || !defined(MLD_CONFIG_NO_VERIFY_API) || \ + defined(MLD_CONFIG_REDUCE_RAM) || defined(MLD_UNIT_TEST) +#define mld_poly_pointwise_montgomery MLD_NAMESPACE(poly_pointwise_montgomery) +/** + * Pointwise multiplication of polynomials. Destructive in the first argument. + * + * @spec{Implements @[FIPS204, Algorithm 45, MultiplyNTT], up to scaling: The + * input is in normal domain, the output is scaled by 2^{-32} as a result of + * the use of Montgomery multiplication. In other words, this function + * implements `mult(2^{-32}) o MultiplyNTT`.} + * + * @param[in,out] a Pointer to first input/output polynomial. On entry, holds + * the first multiplicand; on exit, holds the product + * a * b * 2^{-32}. + * @param[in] b Pointer to second input polynomial. + */ +MLD_INTERNAL_API +void mld_poly_pointwise_montgomery(mld_poly *a, const mld_poly *b) +__contract__( + requires(memory_no_alias(a, sizeof(mld_poly))) + requires(memory_no_alias(b, sizeof(mld_poly))) + requires(array_abs_bound(a->coeffs, 0, MLDSA_N, MLD_NTT_BOUND)) + requires(array_abs_bound(b->coeffs, 0, MLDSA_N, MLD_NTT_BOUND)) + assigns(memory_slice(a, sizeof(mld_poly))) + ensures(array_abs_bound(a->coeffs, 0, MLDSA_N, MLDSA_Q)) +); +#endif /* !MLD_CONFIG_NO_SIGN_API || !MLD_CONFIG_NO_VERIFY_API || \ + MLD_CONFIG_REDUCE_RAM || MLD_UNIT_TEST */ + +#if !defined(MLD_CONFIG_NO_KEYPAIR_API) +#define mld_poly_power2round MLD_NAMESPACE(poly_power2round) +/** + * For all coefficients c of the input polynomial, compute c0, c1 such that + * c mod MLDSA_Q = c1*2^MLDSA_D + c0 with -2^{MLDSA_D-1} < c0 <= 2^{MLDSA_D-1}. + * Assumes coefficients to be standard representatives. + * + * @param[out] a1 Pointer to output polynomial with coefficients c1. + * @param[out] a0 Pointer to output polynomial with coefficients c0; may alias + * the input polynomial a. + * @param[in] a Pointer to input polynomial. + */ +MLD_INTERNAL_API +void mld_poly_power2round(mld_poly *a1, mld_poly *a0, const mld_poly *a) +__contract__( + requires(memory_no_alias(a0, sizeof(mld_poly))) + requires(memory_no_alias(a1, sizeof(mld_poly))) + /* The implementation does not require a0 == a, but the single call site + * aliases them and asserting equality simplifies the proof. */ + requires(a0 == a) + requires(array_bound(a->coeffs, 0, MLDSA_N, 0, MLDSA_Q)) + assigns(memory_slice(a1, sizeof(mld_poly))) + assigns(memory_slice(a0, sizeof(mld_poly))) + ensures(array_bound(a0->coeffs, 0, MLDSA_N, -(MLD_2_POW_D/2)+1, (MLD_2_POW_D/2)+1)) + ensures(array_bound(a1->coeffs, 0, MLDSA_N, 0, ((MLDSA_Q - 1) / MLD_2_POW_D) + 1)) +); +#endif /* !MLD_CONFIG_NO_KEYPAIR_API */ + +#define mld_poly_uniform MLD_NAMESPACE(poly_uniform) +/** + * Sample polynomial with uniformly random coefficients in [0, MLDSA_Q-1] by + * performing rejection sampling on the output stream of SHAKE128(seed|nonce). + * + * @spec{Implements @[FIPS204, Algorithm 30, RejNTTPoly].} + * + * @param[out] a Pointer to output polynomial. + * @param[in] seed Byte array with seed of length MLDSA_SEEDBYTES and the + * packed 2-byte nonce. + */ +MLD_INTERNAL_API +void mld_poly_uniform(mld_poly *a, const uint8_t seed[MLDSA_SEEDBYTES + 2]) +__contract__( + requires(memory_no_alias(a, sizeof(mld_poly))) + requires(memory_no_alias(seed, MLDSA_SEEDBYTES + 2)) + assigns(memory_slice(a, sizeof(mld_poly))) + ensures(array_bound(a->coeffs, 0, MLDSA_N, 0, MLDSA_Q)) +); + +#if !defined(MLD_CONFIG_SERIAL_FIPS202_ONLY) && \ + (!defined(MLD_CONFIG_REDUCE_RAM) || defined(MLD_UNIT_TEST)) +#define mld_poly_uniform_4x MLD_NAMESPACE(poly_uniform_4x) +/** + * Generate four polynomials using rejection sampling on (pseudo-)uniformly + * random bytes sampled from a seed. + * + * @spec{Implements @[FIPS204, Algorithm 30, RejNTTPoly] (four-way batched).} + * + * @param[out] vec0 Pointer to first polynomial to be sampled. + * @param[out] vec1 Pointer to second polynomial to be sampled. + * @param[out] vec2 Pointer to third polynomial to be sampled. + * @param[out] vec3 Pointer to fourth polynomial to be sampled. + * @param[in] seed Pointer to consecutive array of seed buffers of size + * MLDSA_SEEDBYTES + 2 each, plus padding for alignment. + */ +MLD_INTERNAL_API +void mld_poly_uniform_4x(mld_poly *vec0, mld_poly *vec1, mld_poly *vec2, + mld_poly *vec3, + uint8_t seed[4][MLD_ALIGN_UP(MLDSA_SEEDBYTES + 2)]) +__contract__( + requires(memory_no_alias(vec0, sizeof(mld_poly))) + requires(memory_no_alias(vec1, sizeof(mld_poly))) + requires(memory_no_alias(vec2, sizeof(mld_poly))) + requires(memory_no_alias(vec3, sizeof(mld_poly))) + requires(memory_no_alias(seed, 4 * MLD_ALIGN_UP(MLDSA_SEEDBYTES + 2))) + assigns(memory_slice(vec0, sizeof(mld_poly))) + assigns(memory_slice(vec1, sizeof(mld_poly))) + assigns(memory_slice(vec2, sizeof(mld_poly))) + assigns(memory_slice(vec3, sizeof(mld_poly))) + ensures(array_bound(vec0->coeffs, 0, MLDSA_N, 0, MLDSA_Q)) + ensures(array_bound(vec1->coeffs, 0, MLDSA_N, 0, MLDSA_Q)) + ensures(array_bound(vec2->coeffs, 0, MLDSA_N, 0, MLDSA_Q)) + ensures(array_bound(vec3->coeffs, 0, MLDSA_N, 0, MLDSA_Q)) +); +#endif /* !MLD_CONFIG_SERIAL_FIPS202_ONLY && (!MLD_CONFIG_REDUCE_RAM || \ + MLD_UNIT_TEST) */ + +#if !defined(MLD_CONFIG_NO_KEYPAIR_API) +#define mld_polyt1_pack MLD_NAMESPACE(polyt1_pack) +/** + * Bit-pack polynomial t1 with coefficients fitting in 10 bits. Input + * coefficients are assumed to be standard representatives. + * + * @spec{Implements @[FIPS204, Algorithm 16, SimpleBitPack].} + * + * @param[out] r Pointer to output byte array with at least + * MLDSA_POLYT1_PACKEDBYTES bytes. + * @param[in] a Pointer to input polynomial. + */ +MLD_INTERNAL_API +void mld_polyt1_pack(uint8_t r[MLDSA_POLYT1_PACKEDBYTES], const mld_poly *a) +__contract__( + requires(memory_no_alias(r, MLDSA_POLYT1_PACKEDBYTES)) + requires(memory_no_alias(a, sizeof(mld_poly))) + requires(array_bound(a->coeffs, 0, MLDSA_N, 0, 1 << 10)) + assigns(memory_slice(r, MLDSA_POLYT1_PACKEDBYTES)) +); +#endif /* !MLD_CONFIG_NO_KEYPAIR_API */ + +#if !defined(MLD_CONFIG_NO_VERIFY_API) +#define mld_polyt1_unpack MLD_NAMESPACE(polyt1_unpack) +/** + * Unpack polynomial t1 with 10-bit coefficients. Output coefficients are + * standard representatives. + * + * @spec{Implements @[FIPS204, Algorithm 18, SimpleBitUnpack].} + * + * @param[out] r Pointer to output polynomial. + * @param[in] a Byte array with bit-packed polynomial. + */ +MLD_INTERNAL_API +void mld_polyt1_unpack(mld_poly *r, const uint8_t a[MLDSA_POLYT1_PACKEDBYTES]) +__contract__( + requires(memory_no_alias(r, sizeof(mld_poly))) + requires(memory_no_alias(a, MLDSA_POLYT1_PACKEDBYTES)) + assigns(memory_slice(r, sizeof(mld_poly))) + ensures(array_bound(r->coeffs, 0, MLDSA_N, 0, 1 << 10)) +); +#endif /* !MLD_CONFIG_NO_VERIFY_API */ + +#if !defined(MLD_CONFIG_NO_KEYPAIR_API) +#define mld_polyt0_pack MLD_NAMESPACE(polyt0_pack) +/** + * Bit-pack polynomial t0 with coefficients in ]-2^{MLDSA_D-1}, 2^{MLDSA_D-1}]. + * + * @spec{Implements @[FIPS204, Algorithm 17, BitPack].} + * + * @param[out] r Pointer to output byte array with at least + * MLDSA_POLYT0_PACKEDBYTES bytes. + * @param[in] a Pointer to input polynomial. + */ +MLD_INTERNAL_API +void mld_polyt0_pack(uint8_t r[MLDSA_POLYT0_PACKEDBYTES], const mld_poly *a) +__contract__( + requires(memory_no_alias(r, MLDSA_POLYT0_PACKEDBYTES)) + requires(memory_no_alias(a, sizeof(mld_poly))) + requires(array_bound(a->coeffs, 0, MLDSA_N, -(1<<(MLDSA_D-1)) + 1, (1<<(MLDSA_D-1)) + 1)) + assigns(memory_slice(r, MLDSA_POLYT0_PACKEDBYTES)) +); +#endif /* !MLD_CONFIG_NO_KEYPAIR_API */ + +#if !defined(MLD_CONFIG_NO_SIGN_API) || defined(MLD_UNIT_TEST) +#define mld_polyt0_unpack MLD_NAMESPACE(polyt0_unpack) +/** + * Unpack polynomial t0 with coefficients in ]-2^{MLDSA_D-1}, 2^{MLDSA_D-1}]. + * + * @spec{Implements @[FIPS204, Algorithm 19, BitUnpack].} + * + * @param[out] r Pointer to output polynomial. + * @param[in] a Byte array with bit-packed polynomial. + */ +MLD_INTERNAL_API +void mld_polyt0_unpack(mld_poly *r, const uint8_t a[MLDSA_POLYT0_PACKEDBYTES]) +__contract__( + requires(memory_no_alias(r, sizeof(mld_poly))) + requires(memory_no_alias(a, MLDSA_POLYT0_PACKEDBYTES)) + assigns(memory_slice(r, sizeof(mld_poly))) + ensures(array_bound(r->coeffs, 0, MLDSA_N, -(1<<(MLDSA_D-1)) + 1, (1<<(MLDSA_D-1)) + 1)) +); +#endif /* !MLD_CONFIG_NO_SIGN_API || MLD_UNIT_TEST */ + +#define mld_poly_chknorm MLD_NAMESPACE(poly_chknorm) +/** + * Check infinity norm of polynomial against given bound. Assumes input + * coefficients were reduced by mld_reduce32(). + * + * @spec{@[FIPS204] defines the infinity norm via signed canonical reduction + * (modΒ± MLDSA_Q) prior to applying the bounds check. However, + * `-B < (a modΒ± MLDSA_Q) < B` is equivalent to `-B < a < B` under the + * assumption that `B <= MLDSA_Q - MLD_REDUCE32_RANGE_MAX` (cf. the assertion in + * the code). Hence, this contract and implementation are correct without + * reduction.} + * + * @param[in] a Pointer to polynomial. + * @param B Norm bound. + * + * @return 0 if norm is strictly smaller than + * B <= (MLDSA_Q - MLD_REDUCE32_RANGE_MAX) and 0xFFFFFFFF otherwise. + */ +MLD_INTERNAL_API +MLD_MUST_CHECK_RETURN_VALUE +uint32_t mld_poly_chknorm(const mld_poly *a, int32_t B) +__contract__( + requires(memory_no_alias(a, sizeof(mld_poly))) + requires(0 <= B && B <= MLDSA_Q - MLD_REDUCE32_RANGE_MAX) + requires(array_bound(a->coeffs, 0, MLDSA_N, -MLD_REDUCE32_RANGE_MAX, MLD_REDUCE32_RANGE_MAX)) + ensures(return_value == 0 || return_value == 0xFFFFFFFF) + ensures((return_value == 0) == array_abs_bound(a->coeffs, 0, MLDSA_N, B)) +); + +#if !defined(MLD_CONFIG_NO_SIGN_API) || !defined(MLD_CONFIG_NO_VERIFY_API) +#if defined(MLD_CONFIG_MULTILEVEL_WITH_SHARED) || MLD_CONFIG_PARAMETER_SET == 44 +#define mld_polyw1_pack_88 MLD_NAMESPACE(polyw1_pack_88) +/** + * Bit-pack polynomial w1, using 6 bits per coefficient. + * This is the variant for parameter sets with MLDSA_GAMMA2 = (MLDSA_Q-1)/88 + * (ML-DSA-44), for which w1 coefficients lie in [0, 43]. + * + * @param[out] r Pointer to output byte array (MLDSA_POLYW1_PACKEDBYTES_88). + * @param[in] a Pointer to input polynomial. + */ +MLD_INTERNAL_API +void mld_polyw1_pack_88(uint8_t r[MLDSA_POLYW1_PACKEDBYTES_88], + const mld_poly *a) +__contract__( + requires(memory_no_alias(r, MLDSA_POLYW1_PACKEDBYTES_88)) + requires(memory_no_alias(a, sizeof(mld_poly))) + requires(array_bound(a->coeffs, 0, MLDSA_N, 0, (MLDSA_Q-1)/(2*MLDSA_GAMMA2_88))) + assigns(memory_slice(r, MLDSA_POLYW1_PACKEDBYTES_88)) +); +#endif /* MLD_CONFIG_MULTILEVEL_WITH_SHARED || MLD_CONFIG_PARAMETER_SET == 44 \ + */ + +#if defined(MLD_CONFIG_MULTILEVEL_WITH_SHARED) || \ + (MLD_CONFIG_PARAMETER_SET == 65 || MLD_CONFIG_PARAMETER_SET == 87) +#define mld_polyw1_pack_32 MLD_NAMESPACE(polyw1_pack_32) +/** + * Bit-pack polynomial w1, using 4 bits per coefficient. + * This is the variant for parameter sets with MLDSA_GAMMA2 = (MLDSA_Q-1)/32 + * (ML-DSA-65 and ML-DSA-87), for which w1 coefficients lie in [0, 15]. + * + * @param[out] r Pointer to output byte array (MLDSA_POLYW1_PACKEDBYTES_32). + * @param[in] a Pointer to input polynomial. + */ +MLD_INTERNAL_API +void mld_polyw1_pack_32(uint8_t r[MLDSA_POLYW1_PACKEDBYTES_32], + const mld_poly *a) +__contract__( + requires(memory_no_alias(r, MLDSA_POLYW1_PACKEDBYTES_32)) + requires(memory_no_alias(a, sizeof(mld_poly))) + requires(array_bound(a->coeffs, 0, MLDSA_N, 0, (MLDSA_Q-1)/(2*MLDSA_GAMMA2_32))) + assigns(memory_slice(r, MLDSA_POLYW1_PACKEDBYTES_32)) +); +#endif /* MLD_CONFIG_MULTILEVEL_WITH_SHARED || MLD_CONFIG_PARAMETER_SET == 65 \ + || MLD_CONFIG_PARAMETER_SET == 87 */ +#endif /* !MLD_CONFIG_NO_SIGN_API || !MLD_CONFIG_NO_VERIFY_API */ + +#endif /* !MLD_POLY_H */ diff --git a/mldsa_native/src/poly_kl.c b/mldsa_native/src/poly_kl.c new file mode 100644 index 0000000..5be537d --- /dev/null +++ b/mldsa_native/src/poly_kl.c @@ -0,0 +1,909 @@ +/* + * Copyright (c) The mldsa-native project authors + * Copyright (c) The mlkem-native project authors + * SPDX-License-Identifier: Apache-2.0 OR ISC OR MIT + */ + +/* References + * ========== + * + * - [FIPS204] + * FIPS 204 Module-Lattice-Based Digital Signature Standard + * National Institute of Standards and Technology + * https://csrc.nist.gov/pubs/fips/204/final + * + * - [REF] + * CRYSTALS-Dilithium reference implementation + * Bai, Ducas, Kiltz, Lepoint, Lyubashevsky, Schwabe, Seiler, StehlΓ© + * https://github.com/pq-crystals/dilithium/tree/master/ref + */ + +#include "poly_kl.h" + +#include "ct.h" +#include "debug.h" +#include "rounding.h" +#include "symmetric.h" + +/* Parameter set namespacing + * This is to facilitate building multiple instances + * of mldsa-native (e.g. with varying parameter sets) + * within a single compilation unit. */ +#define mld_rej_eta MLD_ADD_PARAM_SET(mld_rej_eta) +#define mld_rej_eta_c MLD_ADD_PARAM_SET(mld_rej_eta_c) +#define mld_poly_decompose_c MLD_ADD_PARAM_SET(mld_poly_decompose_c) +#define mld_poly_use_hint_c MLD_ADD_PARAM_SET(mld_poly_use_hint_c) +#define mld_polyz_unpack_c MLD_ADD_PARAM_SET(mld_polyz_unpack_c) +/* End of parameter set namespacing */ + + +#if !defined(MLD_CONFIG_NO_SIGN_API) +MLD_STATIC_TESTABLE +void mld_poly_decompose_c(mld_poly *a1, mld_poly *a0) +__contract__( + requires(memory_no_alias(a1, sizeof(mld_poly))) + requires(memory_no_alias(a0, sizeof(mld_poly))) + requires(array_bound(a0->coeffs, 0, MLDSA_N, 0, MLDSA_Q)) + assigns(memory_slice(a1, sizeof(mld_poly))) + assigns(memory_slice(a0, sizeof(mld_poly))) + ensures(array_bound(a1->coeffs, 0, MLDSA_N, 0, (MLDSA_Q-1)/(2*MLDSA_GAMMA2))) + ensures(array_abs_bound(a0->coeffs, 0, MLDSA_N, MLDSA_GAMMA2+1)) +) +{ + unsigned int i; + mld_assert_bound(a0->coeffs, MLDSA_N, 0, MLDSA_Q); + for (i = 0; i < MLDSA_N; ++i) + __loop__( + assigns(i, memory_slice(a0, sizeof(mld_poly)), memory_slice(a1, sizeof(mld_poly))) + invariant(i <= MLDSA_N) + invariant(array_bound(a0->coeffs, i, MLDSA_N, 0, MLDSA_Q)) + invariant(array_bound(a1->coeffs, 0, i, 0, (MLDSA_Q-1)/(2*MLDSA_GAMMA2))) + invariant(array_abs_bound(a0->coeffs, 0, i, MLDSA_GAMMA2+1)) + decreases(MLDSA_N - i) + ) + { + mld_decompose(&a0->coeffs[i], &a1->coeffs[i], a0->coeffs[i]); + } + + mld_assert_abs_bound(a0->coeffs, MLDSA_N, MLDSA_GAMMA2 + 1); + mld_assert_bound(a1->coeffs, MLDSA_N, 0, (MLDSA_Q - 1) / (2 * MLDSA_GAMMA2)); +} + +MLD_INTERNAL_API +void mld_poly_decompose(mld_poly *a1, mld_poly *a0) +{ +#if defined(MLD_USE_NATIVE_POLY_DECOMPOSE_88) && MLD_CONFIG_PARAMETER_SET == 44 + int ret; + mld_assert_bound(a0->coeffs, MLDSA_N, 0, MLDSA_Q); + ret = mld_poly_decompose_88_native(a1->coeffs, a0->coeffs); + if (ret == MLD_NATIVE_FUNC_SUCCESS) + { + mld_assert_abs_bound(a0->coeffs, MLDSA_N, MLDSA_GAMMA2 + 1); + mld_assert_bound(a1->coeffs, MLDSA_N, 0, + (MLDSA_Q - 1) / (2 * MLDSA_GAMMA2)); + return; + } +#elif defined(MLD_USE_NATIVE_POLY_DECOMPOSE_32) && \ + (MLD_CONFIG_PARAMETER_SET == 65 || MLD_CONFIG_PARAMETER_SET == 87) + int ret; + mld_assert_bound(a0->coeffs, MLDSA_N, 0, MLDSA_Q); + ret = mld_poly_decompose_32_native(a1->coeffs, a0->coeffs); + if (ret == MLD_NATIVE_FUNC_SUCCESS) + { + mld_assert_abs_bound(a0->coeffs, MLDSA_N, MLDSA_GAMMA2 + 1); + mld_assert_bound(a1->coeffs, MLDSA_N, 0, + (MLDSA_Q - 1) / (2 * MLDSA_GAMMA2)); + return; + } +#endif /* !(MLD_USE_NATIVE_POLY_DECOMPOSE_88 && MLD_CONFIG_PARAMETER_SET == \ + 44) && MLD_USE_NATIVE_POLY_DECOMPOSE_32 && (MLD_CONFIG_PARAMETER_SET \ + == 65 || MLD_CONFIG_PARAMETER_SET == 87) */ + mld_poly_decompose_c(a1, a0); +} + +#endif /* !MLD_CONFIG_NO_SIGN_API */ + +#if !defined(MLD_CONFIG_NO_VERIFY_API) +MLD_STATIC_TESTABLE void mld_poly_use_hint_c(mld_poly *a, const mld_poly *h) +__contract__( + requires(memory_no_alias(a, sizeof(mld_poly))) + requires(memory_no_alias(h, sizeof(mld_poly))) + requires(array_bound(a->coeffs, 0, MLDSA_N, 0, MLDSA_Q)) + requires(array_bound(h->coeffs, 0, MLDSA_N, 0, 2)) + assigns(memory_slice(a, sizeof(mld_poly))) + ensures(array_bound(a->coeffs, 0, MLDSA_N, 0, (MLDSA_Q-1)/(2*MLDSA_GAMMA2))) +) +{ + unsigned int i; + mld_assert_bound(a->coeffs, MLDSA_N, 0, MLDSA_Q); + mld_assert_bound(h->coeffs, MLDSA_N, 0, 2); + + for (i = 0; i < MLDSA_N; ++i) + __loop__( + invariant(i <= MLDSA_N) + invariant(array_bound(a->coeffs, 0, i, 0, (MLDSA_Q-1)/(2*MLDSA_GAMMA2))) + invariant(array_bound(a->coeffs, i, MLDSA_N, 0, MLDSA_Q)) + decreases(MLDSA_N - i) + ) + { + a->coeffs[i] = mld_use_hint(a->coeffs[i], h->coeffs[i]); + } + mld_assert_bound(a->coeffs, MLDSA_N, 0, (MLDSA_Q - 1) / (2 * MLDSA_GAMMA2)); +} + +MLD_INTERNAL_API +void mld_poly_use_hint(mld_poly *a, const mld_poly *h) +{ +#if defined(MLD_USE_NATIVE_POLY_USE_HINT_88) && MLD_CONFIG_PARAMETER_SET == 44 + int ret; + mld_assert_bound(a->coeffs, MLDSA_N, 0, MLDSA_Q); + mld_assert_bound(h->coeffs, MLDSA_N, 0, 2); + ret = mld_poly_use_hint_88_native(a->coeffs, h->coeffs); + if (ret == MLD_NATIVE_FUNC_SUCCESS) + { + mld_assert_bound(a->coeffs, MLDSA_N, 0, (MLDSA_Q - 1) / (2 * MLDSA_GAMMA2)); + return; + } +#elif defined(MLD_USE_NATIVE_POLY_USE_HINT_32) && \ + (MLD_CONFIG_PARAMETER_SET == 65 || MLD_CONFIG_PARAMETER_SET == 87) + int ret; + mld_assert_bound(a->coeffs, MLDSA_N, 0, MLDSA_Q); + mld_assert_bound(h->coeffs, MLDSA_N, 0, 2); + ret = mld_poly_use_hint_32_native(a->coeffs, h->coeffs); + if (ret == MLD_NATIVE_FUNC_SUCCESS) + { + mld_assert_bound(a->coeffs, MLDSA_N, 0, (MLDSA_Q - 1) / (2 * MLDSA_GAMMA2)); + return; + } +#endif /* !(MLD_USE_NATIVE_POLY_USE_HINT_88 && MLD_CONFIG_PARAMETER_SET == 44) \ + && MLD_USE_NATIVE_POLY_USE_HINT_32 && (MLD_CONFIG_PARAMETER_SET == \ + 65 || MLD_CONFIG_PARAMETER_SET == 87) */ + mld_poly_use_hint_c(a, h); +} +#endif /* !MLD_CONFIG_NO_VERIFY_API */ + +#if !defined(MLD_CONFIG_NO_KEYPAIR_API) +/** + * Sample uniformly random coefficients in [-MLDSA_ETA, MLDSA_ETA] by + * performing rejection sampling on an array of random bytes. + * + * @param[out] a Pointer to output array (allocated). + * @param target Requested number of coefficients to sample. + * @param offset Number of coefficients already sampled. + * @param[in] buf Array of random bytes to sample from. + * @param buflen Length of array of random bytes. + * + * @return Number of sampled coefficients. Can be smaller than target if not + * enough random bytes were given. + */ + +/* Reference: `mld_rej_eta()` in the reference implementation @[REF]. + * - Our signature differs from the reference implementation + * in that it adds the offset and always expects the base of the + * target buffer. This avoids shifting the buffer base in the + * caller, which appears tricky to reason about. */ +#if MLDSA_ETA == 2 +/* + * Sampling 256 coefficients mod 15 using rejection sampling from 4 bits. + * Expected number of required bytes: (256 * (16/15))/2 = 136.5 bytes. + * We sample 1 block (=136 bytes) of SHAKE256_RATE output initially. + * Sampling 2 blocks initially results in slightly worse performance. + */ +#define MLD_POLY_UNIFORM_ETA_NBLOCKS 1 +#elif MLDSA_ETA == 4 +/* + * Sampling 256 coefficients mod 9 using rejection sampling from 4 bits. + * Expected number of required bytes: (256 * (16/9))/2 = 227.5 bytes. + * We sample 2 blocks (=272 bytes) of SHAKE256_RATE output initially. + */ +#define MLD_POLY_UNIFORM_ETA_NBLOCKS 2 +#else /* MLDSA_ETA == 4 */ +#error "Invalid value of MLDSA_ETA" +#endif /* MLDSA_ETA != 2 && MLDSA_ETA != 4 */ + +MLD_STATIC_TESTABLE unsigned int mld_rej_eta_c(int32_t *a, unsigned int target, + unsigned int offset, + const uint8_t *buf, + unsigned int buflen) +__contract__( + requires(offset <= target && target <= MLDSA_N) + requires(buflen <= (MLD_POLY_UNIFORM_ETA_NBLOCKS * MLD_STREAM256_BLOCKBYTES)) + requires(memory_no_alias(a, sizeof(int32_t) * target)) + requires(memory_no_alias(buf, buflen)) + requires(array_abs_bound(a, 0, offset, MLDSA_ETA + 1)) + assigns(memory_slice(a, sizeof(int32_t) * target)) + ensures(offset <= return_value && return_value <= target) + ensures(array_abs_bound(a, 0, return_value, MLDSA_ETA + 1)) +) +{ + unsigned int ctr, pos; + int t_valid; + uint32_t t0, t1; + mld_assert_abs_bound(a, offset, MLDSA_ETA + 1); + ctr = offset; + pos = 0; + while (ctr < target && pos < buflen) + __loop__( + invariant(offset <= ctr && ctr <= target && pos <= buflen) + invariant(array_abs_bound(a, 0, ctr, MLDSA_ETA + 1)) + decreases(buflen - pos) + ) + { + t0 = buf[pos] & 0x0F; + t1 = buf[pos++] >> 4; + + /* Constant time: The inputs and outputs to the rejection sampling are + * secret. However, it is fine to leak which coefficients have been + * rejected. For constant-time testing, we declassify the result of + * the comparison. + */ +#if MLDSA_ETA == 2 + t_valid = t0 < 15; + MLD_CT_TESTING_DECLASSIFY(&t_valid, sizeof(int)); + if (t_valid) /* t0 < 15 */ + { + t0 = t0 - (205 * t0 >> 10) * 5; + a[ctr++] = 2 - (int32_t)t0; + } + t_valid = t1 < 15; + MLD_CT_TESTING_DECLASSIFY(&t_valid, sizeof(int)); + if (t_valid && ctr < target) /* t1 < 15 */ + { + t1 = t1 - (205 * t1 >> 10) * 5; + a[ctr++] = 2 - (int32_t)t1; + } +#elif MLDSA_ETA == 4 + t_valid = t0 < 9; + MLD_CT_TESTING_DECLASSIFY(&t_valid, sizeof(int)); + if (t_valid) /* t0 < 9 */ + { + a[ctr++] = 4 - (int32_t)t0; + } + t_valid = t1 < 9; /* t1 < 9 */ + MLD_CT_TESTING_DECLASSIFY(&t_valid, sizeof(int)); + if (t_valid && ctr < target) + { + a[ctr++] = 4 - (int32_t)t1; + } +#else /* MLDSA_ETA == 4 */ +#error "Invalid value of MLDSA_ETA" +#endif /* MLDSA_ETA != 2 && MLDSA_ETA != 4 */ + } + + mld_assert_abs_bound(a, ctr, MLDSA_ETA + 1); + + return ctr; +} + +static unsigned int mld_rej_eta(int32_t *a, unsigned int target, + unsigned int offset, const uint8_t *buf, + unsigned int buflen) +__contract__( + requires(offset <= target && target <= MLDSA_N) + requires(buflen <= (MLD_POLY_UNIFORM_ETA_NBLOCKS * MLD_STREAM256_BLOCKBYTES)) + requires(memory_no_alias(a, sizeof(int32_t) * target)) + requires(memory_no_alias(buf, buflen)) + requires(array_abs_bound(a, 0, offset, MLDSA_ETA + 1)) + assigns(memory_slice(a, sizeof(int32_t) * target)) + ensures(offset <= return_value && return_value <= target) + ensures(array_abs_bound(a, 0, return_value, MLDSA_ETA + 1)) +) +{ +#if MLDSA_ETA == 2 && defined(MLD_USE_NATIVE_REJ_UNIFORM_ETA2) + int ret; + mld_assert_abs_bound(a, offset, MLDSA_ETA + 1); + if (offset == 0) + { + ret = mld_rej_uniform_eta2_native(a, target, buf, buflen); + if (ret != MLD_NATIVE_FUNC_FALLBACK) + { + unsigned res = (unsigned)ret; + mld_assert_abs_bound(a, res, MLDSA_ETA + 1); + return res; + } + } +#elif MLDSA_ETA == 4 && defined(MLD_USE_NATIVE_REJ_UNIFORM_ETA4) + int ret; + mld_assert_abs_bound(a, offset, MLDSA_ETA + 1); + if (offset == 0) + { + ret = mld_rej_uniform_eta4_native(a, target, buf, buflen); + if (ret != MLD_NATIVE_FUNC_FALLBACK) + { + unsigned res = (unsigned)ret; + mld_assert_abs_bound(a, res, MLDSA_ETA + 1); + return res; + } + } +#endif /* !(MLDSA_ETA == 2 && MLD_USE_NATIVE_REJ_UNIFORM_ETA2) && MLDSA_ETA == \ + 4 && MLD_USE_NATIVE_REJ_UNIFORM_ETA4 */ + + return mld_rej_eta_c(a, target, offset, buf, buflen); +} + +#if !defined(MLD_CONFIG_SERIAL_FIPS202_ONLY) +MLD_INTERNAL_API +void mld_poly_uniform_eta_4x(mld_poly *r0, mld_poly *r1, mld_poly *r2, + mld_poly *r3, const uint8_t seed[MLDSA_CRHBYTES], + uint8_t nonce0, uint8_t nonce1, uint8_t nonce2, + uint8_t nonce3) +{ + /* Temporary buffers for XOF output before rejection sampling */ + MLD_ALIGN uint8_t buf[4][MLD_ALIGN_UP(MLD_POLY_UNIFORM_ETA_NBLOCKS * + MLD_STREAM256_BLOCKBYTES)]; + + MLD_ALIGN uint8_t extseed[4][MLD_ALIGN_UP(MLDSA_CRHBYTES + 2)]; + + /* Tracks the number of coefficients we have already sampled */ + unsigned ctr[4]; + mld_xof256_x4_ctx state; + unsigned buflen; + + mld_memcpy(extseed[0], seed, MLDSA_CRHBYTES); + mld_memcpy(extseed[1], seed, MLDSA_CRHBYTES); + mld_memcpy(extseed[2], seed, MLDSA_CRHBYTES); + mld_memcpy(extseed[3], seed, MLDSA_CRHBYTES); + extseed[0][MLDSA_CRHBYTES] = nonce0; + extseed[1][MLDSA_CRHBYTES] = nonce1; + extseed[2][MLDSA_CRHBYTES] = nonce2; + extseed[3][MLDSA_CRHBYTES] = nonce3; + extseed[0][MLDSA_CRHBYTES + 1] = 0; + extseed[1][MLDSA_CRHBYTES + 1] = 0; + extseed[2][MLDSA_CRHBYTES + 1] = 0; + extseed[3][MLDSA_CRHBYTES + 1] = 0; + + mld_xof256_x4_init(&state); + mld_xof256_x4_absorb(&state, extseed, MLDSA_CRHBYTES + 2); + + /* + * Initially, squeeze heuristic number of MLD_POLY_UNIFORM_ETA_NBLOCKS. + * This should generate the coefficients with high probability. + */ + mld_xof256_x4_squeezeblocks(buf, MLD_POLY_UNIFORM_ETA_NBLOCKS, &state); + buflen = MLD_POLY_UNIFORM_ETA_NBLOCKS * MLD_STREAM256_BLOCKBYTES; + + ctr[0] = mld_rej_eta(r0->coeffs, MLDSA_N, 0, buf[0], buflen); + ctr[1] = mld_rej_eta(r1->coeffs, MLDSA_N, 0, buf[1], buflen); + ctr[2] = mld_rej_eta(r2->coeffs, MLDSA_N, 0, buf[2], buflen); + ctr[3] = mld_rej_eta(r3->coeffs, MLDSA_N, 0, buf[3], buflen); + + /* + * So long as not all entries have been generated, squeeze + * one more block at a time until we're done. + */ + buflen = MLD_STREAM256_BLOCKBYTES; + while (ctr[0] < MLDSA_N || ctr[1] < MLDSA_N || ctr[2] < MLDSA_N || + ctr[3] < MLDSA_N) + __loop__( + assigns(ctr, state, memory_slice(r0, sizeof(mld_poly)), + memory_slice(r1, sizeof(mld_poly)), memory_slice(r2, sizeof(mld_poly)), + memory_slice(r3, sizeof(mld_poly)), object_whole(buf[0]), + object_whole(buf[1]), object_whole(buf[2]), + object_whole(buf[3])) + invariant(ctr[0] <= MLDSA_N && ctr[1] <= MLDSA_N) + invariant(ctr[2] <= MLDSA_N && ctr[3] <= MLDSA_N) + invariant(array_abs_bound(r0->coeffs, 0, ctr[0], MLDSA_ETA + 1)) + invariant(array_abs_bound(r1->coeffs, 0, ctr[1], MLDSA_ETA + 1)) + invariant(array_abs_bound(r2->coeffs, 0, ctr[2], MLDSA_ETA + 1)) + invariant(array_abs_bound(r3->coeffs, 0, ctr[3], MLDSA_ETA + 1))) + { + mld_xof256_x4_squeezeblocks(buf, 1, &state); + ctr[0] = mld_rej_eta(r0->coeffs, MLDSA_N, ctr[0], buf[0], buflen); + ctr[1] = mld_rej_eta(r1->coeffs, MLDSA_N, ctr[1], buf[1], buflen); + ctr[2] = mld_rej_eta(r2->coeffs, MLDSA_N, ctr[2], buf[2], buflen); + ctr[3] = mld_rej_eta(r3->coeffs, MLDSA_N, ctr[3], buf[3], buflen); + } + + mld_xof256_x4_release(&state); + + mld_assert_abs_bound(r0->coeffs, MLDSA_N, MLDSA_ETA + 1); + mld_assert_abs_bound(r1->coeffs, MLDSA_N, MLDSA_ETA + 1); + mld_assert_abs_bound(r2->coeffs, MLDSA_N, MLDSA_ETA + 1); + mld_assert_abs_bound(r3->coeffs, MLDSA_N, MLDSA_ETA + 1); + + /* @[FIPS204, Section 3.6.3] Destruction of intermediate values. */ + mld_zeroize(buf, sizeof(buf)); + mld_zeroize(extseed, sizeof(extseed)); +} +#else /* !MLD_CONFIG_SERIAL_FIPS202_ONLY */ + +MLD_INTERNAL_API +void mld_poly_uniform_eta(mld_poly *r, const uint8_t seed[MLDSA_CRHBYTES], + uint8_t nonce) +{ + /* Temporary buffer for XOF output before rejection sampling */ + MLD_ALIGN uint8_t + buf[MLD_POLY_UNIFORM_ETA_NBLOCKS * MLD_STREAM256_BLOCKBYTES]; + MLD_ALIGN uint8_t extseed[MLDSA_CRHBYTES + 2]; + + /* Tracks the number of coefficients we have already sampled */ + unsigned ctr; + mld_xof256_ctx state; + unsigned buflen; + + mld_memcpy(extseed, seed, MLDSA_CRHBYTES); + extseed[MLDSA_CRHBYTES] = nonce; + extseed[MLDSA_CRHBYTES + 1] = 0; + + mld_xof256_init(&state); + mld_xof256_absorb_once(&state, extseed, MLDSA_CRHBYTES + 2); + + /* + * Initially, squeeze heuristic number of MLD_POLY_UNIFORM_ETA_NBLOCKS. + * This should generate the coefficients with high probability. + */ + mld_xof256_squeezeblocks(buf, MLD_POLY_UNIFORM_ETA_NBLOCKS, &state); + buflen = MLD_POLY_UNIFORM_ETA_NBLOCKS * MLD_STREAM256_BLOCKBYTES; + + ctr = mld_rej_eta(r->coeffs, MLDSA_N, 0, buf, buflen); + + /* + * So long as not all entries have been generated, squeeze + * one more block at a time until we're done. + */ + buflen = MLD_STREAM256_BLOCKBYTES; + while (ctr < MLDSA_N) + __loop__( + assigns(ctr, object_whole(&state), + object_whole(buf), memory_slice(r, sizeof(mld_poly))) + invariant(ctr <= MLDSA_N) + invariant(state.pos <= SHAKE256_RATE) + invariant(array_abs_bound(r->coeffs, 0, ctr, MLDSA_ETA + 1))) + { + mld_xof256_squeezeblocks(buf, 1, &state); + ctr = mld_rej_eta(r->coeffs, MLDSA_N, ctr, buf, buflen); + } + + mld_xof256_release(&state); + + mld_assert_abs_bound(r->coeffs, MLDSA_N, MLDSA_ETA + 1); + + /* @[FIPS204, Section 3.6.3] Destruction of intermediate values. */ + mld_zeroize(buf, sizeof(buf)); + mld_zeroize(extseed, sizeof(extseed)); +} +#endif /* MLD_CONFIG_SERIAL_FIPS202_ONLY */ +#endif /* !MLD_CONFIG_NO_KEYPAIR_API */ + +#if !defined(MLD_CONFIG_NO_SIGN_API) +#define MLD_POLY_UNIFORM_GAMMA1_NBLOCKS \ + ((MLDSA_POLYZ_PACKEDBYTES + MLD_STREAM256_BLOCKBYTES - 1) / \ + MLD_STREAM256_BLOCKBYTES) + +#if MLD_CONFIG_PARAMETER_SET == 65 || \ + defined(MLD_CONFIG_SERIAL_FIPS202_ONLY) || \ + defined(MLD_CONFIG_REDUCE_RAM) || defined(MLD_UNIT_TEST) +MLD_INTERNAL_API +void mld_poly_uniform_gamma1(mld_poly *a, const uint8_t seed[MLDSA_CRHBYTES], + uint16_t nonce) +{ + MLD_ALIGN uint8_t + buf[MLD_POLY_UNIFORM_GAMMA1_NBLOCKS * MLD_STREAM256_BLOCKBYTES]; + MLD_ALIGN uint8_t extseed[MLDSA_CRHBYTES + 2]; + mld_xof256_ctx state; + + mld_memcpy(extseed, seed, MLDSA_CRHBYTES); + extseed[MLDSA_CRHBYTES] = (uint8_t)(nonce & 0xFF); + extseed[MLDSA_CRHBYTES + 1] = (uint8_t)(nonce >> 8); + + mld_xof256_init(&state); + mld_xof256_absorb_once(&state, extseed, MLDSA_CRHBYTES + 2); + + mld_xof256_squeezeblocks(buf, MLD_POLY_UNIFORM_GAMMA1_NBLOCKS, &state); + mld_polyz_unpack(a, buf); + + mld_xof256_release(&state); + + mld_assert_bound(a->coeffs, MLDSA_N, -(MLDSA_GAMMA1 - 1), MLDSA_GAMMA1 + 1); + + /* @[FIPS204, Section 3.6.3] Destruction of intermediate values. */ + mld_zeroize(buf, sizeof(buf)); + mld_zeroize(extseed, sizeof(extseed)); +} +#endif /* MLD_CONFIG_PARAMETER_SET == 65 || MLD_CONFIG_SERIAL_FIPS202_ONLY || \ + MLD_CONFIG_REDUCE_RAM || MLD_UNIT_TEST */ + + +#if !defined(MLD_CONFIG_SERIAL_FIPS202_ONLY) && \ + (!defined(MLD_CONFIG_REDUCE_RAM) || defined(MLD_UNIT_TEST)) +MLD_INTERNAL_API +void mld_poly_uniform_gamma1_4x(mld_poly *r0, mld_poly *r1, mld_poly *r2, + mld_poly *r3, + const uint8_t seed[MLDSA_CRHBYTES], + uint16_t nonce0, uint16_t nonce1, + uint16_t nonce2, uint16_t nonce3) +{ + /* Temporary buffers for XOF output before rejection sampling */ + MLD_ALIGN uint8_t buf[4][MLD_ALIGN_UP(MLD_POLY_UNIFORM_GAMMA1_NBLOCKS * + MLD_STREAM256_BLOCKBYTES)]; + + MLD_ALIGN uint8_t extseed[4][MLD_ALIGN_UP(MLDSA_CRHBYTES + 2)]; + + /* Tracks the number of coefficients we have already sampled */ + mld_xof256_x4_ctx state; + + mld_memcpy(extseed[0], seed, MLDSA_CRHBYTES); + mld_memcpy(extseed[1], seed, MLDSA_CRHBYTES); + mld_memcpy(extseed[2], seed, MLDSA_CRHBYTES); + mld_memcpy(extseed[3], seed, MLDSA_CRHBYTES); + extseed[0][MLDSA_CRHBYTES] = (uint8_t)(nonce0 & 0xFF); + extseed[1][MLDSA_CRHBYTES] = (uint8_t)(nonce1 & 0xFF); + extseed[2][MLDSA_CRHBYTES] = (uint8_t)(nonce2 & 0xFF); + extseed[3][MLDSA_CRHBYTES] = (uint8_t)(nonce3 & 0xFF); + extseed[0][MLDSA_CRHBYTES + 1] = (uint8_t)(nonce0 >> 8); + extseed[1][MLDSA_CRHBYTES + 1] = (uint8_t)(nonce1 >> 8); + extseed[2][MLDSA_CRHBYTES + 1] = (uint8_t)(nonce2 >> 8); + extseed[3][MLDSA_CRHBYTES + 1] = (uint8_t)(nonce3 >> 8); + + mld_xof256_x4_init(&state); + mld_xof256_x4_absorb(&state, extseed, MLDSA_CRHBYTES + 2); + mld_xof256_x4_squeezeblocks(buf, MLD_POLY_UNIFORM_GAMMA1_NBLOCKS, &state); + + mld_polyz_unpack(r0, buf[0]); + mld_polyz_unpack(r1, buf[1]); + mld_polyz_unpack(r2, buf[2]); + mld_polyz_unpack(r3, buf[3]); + mld_xof256_x4_release(&state); + + mld_assert_bound(r0->coeffs, MLDSA_N, -(MLDSA_GAMMA1 - 1), MLDSA_GAMMA1 + 1); + mld_assert_bound(r1->coeffs, MLDSA_N, -(MLDSA_GAMMA1 - 1), MLDSA_GAMMA1 + 1); + mld_assert_bound(r2->coeffs, MLDSA_N, -(MLDSA_GAMMA1 - 1), MLDSA_GAMMA1 + 1); + mld_assert_bound(r3->coeffs, MLDSA_N, -(MLDSA_GAMMA1 - 1), MLDSA_GAMMA1 + 1); + + /* @[FIPS204, Section 3.6.3] Destruction of intermediate values. */ + mld_zeroize(buf, sizeof(buf)); + mld_zeroize(extseed, sizeof(extseed)); +} +#endif /* !MLD_CONFIG_SERIAL_FIPS202_ONLY && (!MLD_CONFIG_REDUCE_RAM || \ + MLD_UNIT_TEST) */ +#endif /* !MLD_CONFIG_NO_SIGN_API */ + +#if !defined(MLD_CONFIG_NO_SIGN_API) || !defined(MLD_CONFIG_NO_VERIFY_API) +MLD_INTERNAL_API +void mld_poly_challenge(mld_poly *c, const uint8_t seed[MLDSA_CTILDEBYTES]) +{ + unsigned int i, j, pos; + uint64_t signs; + uint64_t offset; + MLD_ALIGN uint8_t buf[SHAKE256_RATE]; + mld_shake256ctx state; + + mld_shake256_init(&state); + mld_shake256_absorb(&state, seed, MLDSA_CTILDEBYTES); + mld_shake256_finalize(&state); + mld_shake256_squeeze(buf, SHAKE256_RATE, &state); + + /* Convert the first 8 bytes of buf[] into an unsigned 64-bit value. */ + /* Each bit of that dictates the sign of the resulting challenge value */ + signs = 0; + for (i = 0; i < 8; ++i) + __loop__( + assigns(i, signs) + invariant(i <= 8) + decreases(8 - i) + ) + { + signs |= (uint64_t)buf[i] << 8 * i; + } + pos = 8; + + mld_memset(c, 0, sizeof(mld_poly)); + + for (i = MLDSA_N - MLDSA_TAU; i < MLDSA_N; ++i) + __loop__( + assigns(i, j, object_whole(buf), state, pos, memory_slice(c, sizeof(mld_poly)), signs) + invariant(i >= MLDSA_N - MLDSA_TAU) + invariant(i <= MLDSA_N) + invariant(pos >= 1) + invariant(pos <= SHAKE256_RATE) + invariant(array_bound(c->coeffs, 0, MLDSA_N, -1, 2)) + invariant(state.pos <= SHAKE256_RATE) + decreases(MLDSA_N - i) + ) + { + /* This loop teminates only probabilistically, hence no decreases clause. */ + do + __loop__( + assigns(j, object_whole(buf), state, pos) + invariant(state.pos <= SHAKE256_RATE) + ) + { + if (pos >= SHAKE256_RATE) + { + mld_shake256_squeeze(buf, SHAKE256_RATE, &state); + pos = 0; + } + j = buf[pos++]; + } while (j > i); + + c->coeffs[i] = c->coeffs[j]; + + /* Reference: Compute coefficent value here in two steps to */ + /* mixinf unsigned and signed arithmetic with implicit */ + /* conversions, and so that CBMC can keep track of ranges */ + /* to complete type-safety proof here. */ + + /* The least-significant bit of signs tells us if we want -1 or +1 */ + offset = 2 * (signs & 1); + + /* offset has value 0 or 2 here, so (1 - (int32_t) offset) has + * value -1 or +1 */ + c->coeffs[j] = 1 - (int32_t)offset; + + /* Move to the next bit of signs for next time */ + signs >>= 1; + } + + mld_assert_bound(c->coeffs, MLDSA_N, -1, 2); + mld_shake256_release(&state); + + /* @[FIPS204, Section 3.6.3] Destruction of intermediate values. */ + mld_zeroize(buf, sizeof(buf)); + mld_zeroize(&signs, sizeof(signs)); +} +#endif /* !MLD_CONFIG_NO_SIGN_API || !MLD_CONFIG_NO_VERIFY_API */ + +#if !defined(MLD_CONFIG_NO_KEYPAIR_API) +MLD_INTERNAL_API +void mld_polyeta_pack(uint8_t r[MLDSA_POLYETA_PACKEDBYTES], const mld_poly *a) +{ + unsigned int i; + uint8_t t[8]; + + mld_assert_abs_bound(a->coeffs, MLDSA_N, MLDSA_ETA + 1); + +#if MLDSA_ETA == 2 + for (i = 0; i < MLDSA_N / 8; ++i) + __loop__( + invariant(i <= MLDSA_N/8) + decreases(MLDSA_N / 8 - i)) + { + /* The casts are safe since we assume that the coefficients + * of a are <= MLDSA_ETA in absolute value. */ + t[0] = (uint8_t)(MLDSA_ETA - a->coeffs[8 * i + 0]); + t[1] = (uint8_t)(MLDSA_ETA - a->coeffs[8 * i + 1]); + t[2] = (uint8_t)(MLDSA_ETA - a->coeffs[8 * i + 2]); + t[3] = (uint8_t)(MLDSA_ETA - a->coeffs[8 * i + 3]); + t[4] = (uint8_t)(MLDSA_ETA - a->coeffs[8 * i + 4]); + t[5] = (uint8_t)(MLDSA_ETA - a->coeffs[8 * i + 5]); + t[6] = (uint8_t)(MLDSA_ETA - a->coeffs[8 * i + 6]); + t[7] = (uint8_t)(MLDSA_ETA - a->coeffs[8 * i + 7]); + + r[3 * i + 0] = (uint8_t)(((t[0] >> 0) | (t[1] << 3) | (t[2] << 6)) & 0xFF); + r[3 * i + 1] = + (uint8_t)(((t[2] >> 2) | (t[3] << 1) | (t[4] << 4) | (t[5] << 7)) & + 0xFF); + r[3 * i + 2] = (uint8_t)(((t[5] >> 1) | (t[6] << 2) | (t[7] << 5)) & 0xFF); + } +#elif MLDSA_ETA == 4 + for (i = 0; i < MLDSA_N / 2; ++i) + __loop__( + invariant(i <= MLDSA_N/2) + decreases(MLDSA_N / 2 - i)) + { + /* The casts are safe since we assume that the coefficients + * of a are <= MLDSA_ETA in absolute value. */ + t[0] = (uint8_t)(MLDSA_ETA - a->coeffs[2 * i + 0]); + t[1] = (uint8_t)(MLDSA_ETA - a->coeffs[2 * i + 1]); + r[i] = (uint8_t)(t[0] | (t[1] << 4)); + } +#else /* MLDSA_ETA == 4 */ +#error "Invalid value of MLDSA_ETA" +#endif /* MLDSA_ETA != 2 && MLDSA_ETA != 4 */ +} +#endif /* !MLD_CONFIG_NO_KEYPAIR_API */ + +#if !defined(MLD_CONFIG_NO_KEYPAIR_API) || !defined(MLD_CONFIG_NO_SIGN_API) +void mld_polyeta_unpack(mld_poly *r, const uint8_t a[MLDSA_POLYETA_PACKEDBYTES]) +{ + unsigned int i; + +#if MLDSA_ETA == 2 + for (i = 0; i < MLDSA_N / 8; ++i) + __loop__( + invariant(i <= MLDSA_N/8) + invariant(array_bound(r->coeffs, 0, i*8, -5, MLDSA_ETA + 1)) + decreases(MLDSA_N / 8 - i)) + { + r->coeffs[8 * i + 0] = (a[3 * i + 0] >> 0) & 7; + r->coeffs[8 * i + 1] = (a[3 * i + 0] >> 3) & 7; + r->coeffs[8 * i + 2] = ((a[3 * i + 0] >> 6) | (a[3 * i + 1] << 2)) & 7; + r->coeffs[8 * i + 3] = (a[3 * i + 1] >> 1) & 7; + r->coeffs[8 * i + 4] = (a[3 * i + 1] >> 4) & 7; + r->coeffs[8 * i + 5] = ((a[3 * i + 1] >> 7) | (a[3 * i + 2] << 1)) & 7; + r->coeffs[8 * i + 6] = (a[3 * i + 2] >> 2) & 7; + r->coeffs[8 * i + 7] = (a[3 * i + 2] >> 5) & 7; + + r->coeffs[8 * i + 0] = MLDSA_ETA - r->coeffs[8 * i + 0]; + r->coeffs[8 * i + 1] = MLDSA_ETA - r->coeffs[8 * i + 1]; + r->coeffs[8 * i + 2] = MLDSA_ETA - r->coeffs[8 * i + 2]; + r->coeffs[8 * i + 3] = MLDSA_ETA - r->coeffs[8 * i + 3]; + r->coeffs[8 * i + 4] = MLDSA_ETA - r->coeffs[8 * i + 4]; + r->coeffs[8 * i + 5] = MLDSA_ETA - r->coeffs[8 * i + 5]; + r->coeffs[8 * i + 6] = MLDSA_ETA - r->coeffs[8 * i + 6]; + r->coeffs[8 * i + 7] = MLDSA_ETA - r->coeffs[8 * i + 7]; + } +#elif MLDSA_ETA == 4 + for (i = 0; i < MLDSA_N / 2; ++i) + __loop__( + invariant(i <= MLDSA_N/2) + invariant(array_bound(r->coeffs, 0, i*2, -11, MLDSA_ETA + 1)) + decreases(MLDSA_N / 2 - i)) + { + r->coeffs[2 * i + 0] = a[i] & 0x0F; + r->coeffs[2 * i + 1] = a[i] >> 4; + r->coeffs[2 * i + 0] = MLDSA_ETA - r->coeffs[2 * i + 0]; + r->coeffs[2 * i + 1] = MLDSA_ETA - r->coeffs[2 * i + 1]; + } +#else /* MLDSA_ETA == 4 */ +#error "Invalid value of MLDSA_ETA" +#endif /* MLDSA_ETA != 2 && MLDSA_ETA != 4 */ + + mld_assert_bound(r->coeffs, MLDSA_N, MLD_POLYETA_UNPACK_LOWER_BOUND, + MLDSA_ETA + 1); +} +#endif /* !MLD_CONFIG_NO_KEYPAIR_API || !MLD_CONFIG_NO_SIGN_API */ + +#if !defined(MLD_CONFIG_NO_SIGN_API) +MLD_INTERNAL_API +void mld_polyz_pack(uint8_t r[MLDSA_POLYZ_PACKEDBYTES], const mld_poly *a) +{ + unsigned int i; + uint32_t t[4]; + + mld_assert_bound(a->coeffs, MLDSA_N, -(MLDSA_GAMMA1 - 1), MLDSA_GAMMA1 + 1); + +#if MLD_CONFIG_PARAMETER_SET == 44 + for (i = 0; i < MLDSA_N / 4; ++i) + __loop__( + invariant(i <= MLDSA_N/4) + decreases(MLDSA_N / 4 - i)) + { + /* Safety: a->coeffs[i] <= MLDSA_GAMMA1, hence, these casts are safe. */ + t[0] = (uint32_t)(MLDSA_GAMMA1 - a->coeffs[4 * i + 0]); + t[1] = (uint32_t)(MLDSA_GAMMA1 - a->coeffs[4 * i + 1]); + t[2] = (uint32_t)(MLDSA_GAMMA1 - a->coeffs[4 * i + 2]); + t[3] = (uint32_t)(MLDSA_GAMMA1 - a->coeffs[4 * i + 3]); + + r[9 * i + 0] = (uint8_t)((t[0]) & 0xFF); + r[9 * i + 1] = (uint8_t)((t[0] >> 8) & 0xFF); + r[9 * i + 2] = (uint8_t)((t[0] >> 16) & 0xFF); + r[9 * i + 2] |= (uint8_t)((t[1] << 2) & 0xFF); + r[9 * i + 3] = (uint8_t)((t[1] >> 6) & 0xFF); + r[9 * i + 4] = (uint8_t)((t[1] >> 14) & 0xFF); + r[9 * i + 4] |= (uint8_t)((t[2] << 4) & 0xFF); + r[9 * i + 5] = (uint8_t)((t[2] >> 4) & 0xFF); + r[9 * i + 6] = (uint8_t)((t[2] >> 12) & 0xFF); + r[9 * i + 6] |= (uint8_t)((t[3] << 6) & 0xFF); + r[9 * i + 7] = (uint8_t)((t[3] >> 2) & 0xFF); + r[9 * i + 8] = (uint8_t)((t[3] >> 10) & 0xFF); + } +#else /* MLD_CONFIG_PARAMETER_SET == 44 */ + for (i = 0; i < MLDSA_N / 2; ++i) + __loop__( + invariant(i <= MLDSA_N/2) + decreases(MLDSA_N / 2 - i)) + { + /* Safety: a->coeffs[i] <= MLDSA_GAMMA1, hence, these casts are safe. */ + t[0] = (uint32_t)(MLDSA_GAMMA1 - a->coeffs[2 * i + 0]); + t[1] = (uint32_t)(MLDSA_GAMMA1 - a->coeffs[2 * i + 1]); + + r[5 * i + 0] = (uint8_t)((t[0]) & 0xFF); + r[5 * i + 1] = (uint8_t)((t[0] >> 8) & 0xFF); + r[5 * i + 2] = (uint8_t)((t[0] >> 16) & 0xFF); + r[5 * i + 2] |= (uint8_t)((t[1] << 4) & 0xFF); + r[5 * i + 3] = (uint8_t)((t[1] >> 4) & 0xFF); + r[5 * i + 4] = (uint8_t)((t[1] >> 12) & 0xFF); + } +#endif /* MLD_CONFIG_PARAMETER_SET != 44 */ +} +#endif /* !MLD_CONFIG_NO_SIGN_API */ + +#if !defined(MLD_CONFIG_NO_SIGN_API) || !defined(MLD_CONFIG_NO_VERIFY_API) +MLD_STATIC_TESTABLE void mld_polyz_unpack_c( + mld_poly *r, const uint8_t a[MLDSA_POLYZ_PACKEDBYTES]) +__contract__( + requires(memory_no_alias(r, sizeof(mld_poly))) + requires(memory_no_alias(a, MLDSA_POLYZ_PACKEDBYTES)) + assigns(memory_slice(r, sizeof(mld_poly))) + ensures(array_bound(r->coeffs, 0, MLDSA_N, -(MLDSA_GAMMA1 - 1), MLDSA_GAMMA1 + 1)) +) +{ + unsigned int i; +#if MLD_CONFIG_PARAMETER_SET == 44 + for (i = 0; i < MLDSA_N / 4; ++i) + __loop__( + invariant(i <= MLDSA_N/4) + invariant(array_bound(r->coeffs, 0, i*4, -(MLDSA_GAMMA1 - 1), MLDSA_GAMMA1 + 1)) + decreases(MLDSA_N / 4 - i)) + { + r->coeffs[4 * i + 0] = a[9 * i + 0]; + r->coeffs[4 * i + 0] |= (int32_t)a[9 * i + 1] << 8; + r->coeffs[4 * i + 0] |= (int32_t)a[9 * i + 2] << 16; + r->coeffs[4 * i + 0] &= 0x3FFFF; + + r->coeffs[4 * i + 1] = a[9 * i + 2] >> 2; + r->coeffs[4 * i + 1] |= (int32_t)a[9 * i + 3] << 6; + r->coeffs[4 * i + 1] |= (int32_t)a[9 * i + 4] << 14; + r->coeffs[4 * i + 1] &= 0x3FFFF; + + r->coeffs[4 * i + 2] = a[9 * i + 4] >> 4; + r->coeffs[4 * i + 2] |= (int32_t)a[9 * i + 5] << 4; + r->coeffs[4 * i + 2] |= (int32_t)a[9 * i + 6] << 12; + r->coeffs[4 * i + 2] &= 0x3FFFF; + + r->coeffs[4 * i + 3] = a[9 * i + 6] >> 6; + r->coeffs[4 * i + 3] |= (int32_t)a[9 * i + 7] << 2; + r->coeffs[4 * i + 3] |= (int32_t)a[9 * i + 8] << 10; + r->coeffs[4 * i + 3] &= 0x3FFFF; + + r->coeffs[4 * i + 0] = MLDSA_GAMMA1 - r->coeffs[4 * i + 0]; + r->coeffs[4 * i + 1] = MLDSA_GAMMA1 - r->coeffs[4 * i + 1]; + r->coeffs[4 * i + 2] = MLDSA_GAMMA1 - r->coeffs[4 * i + 2]; + r->coeffs[4 * i + 3] = MLDSA_GAMMA1 - r->coeffs[4 * i + 3]; + } +#else /* MLD_CONFIG_PARAMETER_SET == 44 */ + for (i = 0; i < MLDSA_N / 2; ++i) + __loop__( + invariant(i <= MLDSA_N/2) + invariant(array_bound(r->coeffs, 0, i*2, -(MLDSA_GAMMA1 - 1), MLDSA_GAMMA1 + 1)) + decreases(MLDSA_N / 2 - i)) + { + r->coeffs[2 * i + 0] = a[5 * i + 0]; + r->coeffs[2 * i + 0] |= (int32_t)a[5 * i + 1] << 8; + r->coeffs[2 * i + 0] |= (int32_t)a[5 * i + 2] << 16; + r->coeffs[2 * i + 0] &= 0xFFFFF; + + r->coeffs[2 * i + 1] = a[5 * i + 2] >> 4; + r->coeffs[2 * i + 1] |= (int32_t)a[5 * i + 3] << 4; + r->coeffs[2 * i + 1] |= (int32_t)a[5 * i + 4] << 12; + /* r->coeffs[2*i+1] &= 0xFFFFF; */ /* No effect, since we're anyway at 20 + bits */ + + r->coeffs[2 * i + 0] = MLDSA_GAMMA1 - r->coeffs[2 * i + 0]; + r->coeffs[2 * i + 1] = MLDSA_GAMMA1 - r->coeffs[2 * i + 1]; + } +#endif /* MLD_CONFIG_PARAMETER_SET != 44 */ + mld_assert_bound(r->coeffs, MLDSA_N, -(MLDSA_GAMMA1 - 1), MLDSA_GAMMA1 + 1); +} + +MLD_INTERNAL_API +void mld_polyz_unpack(mld_poly *r, const uint8_t a[MLDSA_POLYZ_PACKEDBYTES]) +{ +#if defined(MLD_USE_NATIVE_POLYZ_UNPACK_17) && MLD_CONFIG_PARAMETER_SET == 44 + int ret; + ret = mld_polyz_unpack_17_native(r->coeffs, a); + if (ret == MLD_NATIVE_FUNC_SUCCESS) + { + mld_assert_bound(r->coeffs, MLDSA_N, -(MLDSA_GAMMA1 - 1), MLDSA_GAMMA1 + 1); + return; + } +#elif defined(MLD_USE_NATIVE_POLYZ_UNPACK_19) && \ + (MLD_CONFIG_PARAMETER_SET == 65 || MLD_CONFIG_PARAMETER_SET == 87) + int ret; + ret = mld_polyz_unpack_19_native(r->coeffs, a); + if (ret == MLD_NATIVE_FUNC_SUCCESS) + { + mld_assert_bound(r->coeffs, MLDSA_N, -(MLDSA_GAMMA1 - 1), MLDSA_GAMMA1 + 1); + return; + } +#endif /* !(MLD_USE_NATIVE_POLYZ_UNPACK_17 && MLD_CONFIG_PARAMETER_SET == 44) \ + && MLD_USE_NATIVE_POLYZ_UNPACK_19 && (MLD_CONFIG_PARAMETER_SET == 65 \ + || MLD_CONFIG_PARAMETER_SET == 87) */ + + mld_polyz_unpack_c(r, a); +} +#endif /* !MLD_CONFIG_NO_SIGN_API || !MLD_CONFIG_NO_VERIFY_API */ + +/* To facilitate single-compilation-unit (SCU) builds, undefine all macros. */ + +/* To facilitate single-compilation-unit (SCU) builds, undefine all macros. + * Don't modify by hand -- this is auto-generated by scripts/autogen. */ +#undef mld_rej_eta +#undef mld_rej_eta_c +#undef mld_poly_decompose_c +#undef mld_poly_use_hint_c +#undef mld_polyz_unpack_c +#undef MLD_POLY_UNIFORM_ETA_NBLOCKS +#undef MLD_POLY_UNIFORM_ETA_NBLOCKS +#undef MLD_POLY_UNIFORM_GAMMA1_NBLOCKS diff --git a/mldsa_native/src/poly_kl.h b/mldsa_native/src/poly_kl.h new file mode 100644 index 0000000..2dca8f1 --- /dev/null +++ b/mldsa_native/src/poly_kl.h @@ -0,0 +1,367 @@ +/* + * Copyright (c) The mldsa-native project authors + * SPDX-License-Identifier: Apache-2.0 OR ISC OR MIT + */ + +/* References + * ========== + * + * - [FIPS204] + * FIPS 204 Module-Lattice-Based Digital Signature Standard + * National Institute of Standards and Technology + * https://csrc.nist.gov/pubs/fips/204/final + */ + +#ifndef MLD_POLY_KL_H +#define MLD_POLY_KL_H + +#include "cbmc.h" +#include "common.h" +#include "poly.h" + +#if !defined(MLD_CONFIG_NO_SIGN_API) +#define mld_poly_decompose MLD_NAMESPACE_KL(poly_decompose) +/** + * For all coefficients c of the input polynomial, compute high and low bits + * c0, c1 such c mod MLDSA_Q = c1*ALPHA + c0 with -ALPHA/2 < c0 <= ALPHA/2 + * except c1 = (MLDSA_Q-1)/ALPHA where we set c1 = 0 and + * -ALPHA/2 <= c0 = c mod MLDSA_Q - MLDSA_Q < 0. Assumes coefficients to be + * standard representatives. + * + * @reference{The reference implementation has the input polynomial as a + * separate argument that may be aliased with either of the outputs. Removing + * the aliasing eases CBMC proofs.} + * + * @param[out] a1 Pointer to output polynomial with coefficients c1. + * @param[in,out] a0 Pointer to input/output polynomial. Output polynomial has + * coefficients c0. + */ +MLD_INTERNAL_API +void mld_poly_decompose(mld_poly *a1, mld_poly *a0) +__contract__( + requires(memory_no_alias(a1, sizeof(mld_poly))) + requires(memory_no_alias(a0, sizeof(mld_poly))) + requires(array_bound(a0->coeffs, 0, MLDSA_N, 0, MLDSA_Q)) + assigns(memory_slice(a1, sizeof(mld_poly))) + assigns(memory_slice(a0, sizeof(mld_poly))) + ensures(array_bound(a1->coeffs, 0, MLDSA_N, 0, (MLDSA_Q-1)/(2*MLDSA_GAMMA2))) + ensures(array_abs_bound(a0->coeffs, 0, MLDSA_N, MLDSA_GAMMA2+1)) +); + +#endif /* !MLD_CONFIG_NO_SIGN_API */ + +#if !defined(MLD_CONFIG_NO_VERIFY_API) +#define mld_poly_use_hint MLD_NAMESPACE_KL(poly_use_hint) +/** + * Use hint polynomial h to correct the high bits of a in-place. + * + * @param[in,out] a Input/output polynomial. + * @param[in] h Hint polynomial. + */ +MLD_INTERNAL_API +void mld_poly_use_hint(mld_poly *a, const mld_poly *h) +__contract__( + requires(memory_no_alias(a, sizeof(mld_poly))) + requires(memory_no_alias(h, sizeof(mld_poly))) + requires(array_bound(a->coeffs, 0, MLDSA_N, 0, MLDSA_Q)) + requires(array_bound(h->coeffs, 0, MLDSA_N, 0, 2)) + assigns(memory_slice(a, sizeof(mld_poly))) + ensures(array_bound(a->coeffs, 0, MLDSA_N, 0, (MLDSA_Q-1)/(2*MLDSA_GAMMA2))) +); +#endif /* !MLD_CONFIG_NO_VERIFY_API */ + +#if !defined(MLD_CONFIG_NO_KEYPAIR_API) +#if !defined(MLD_CONFIG_SERIAL_FIPS202_ONLY) +#define mld_poly_uniform_eta_4x MLD_NAMESPACE_KL(poly_uniform_eta_4x) +/** + * Sample four polynomials with uniformly random coefficients in + * [-MLDSA_ETA, MLDSA_ETA] by performing rejection sampling on the output + * stream from SHAKE256(seed|nonce_i). + * + * @spec{Implements @[FIPS204, Algorithm 31, RejBoundedPoly] (four-way + * batched).} + * + * @param[out] r0 Pointer to first output polynomial. + * @param[out] r1 Pointer to second output polynomial. + * @param[out] r2 Pointer to third output polynomial. + * @param[out] r3 Pointer to fourth output polynomial. + * @param[in] seed Byte array with seed of length MLDSA_CRHBYTES. + * @param nonce0 First nonce. + * @param nonce1 Second nonce. + * @param nonce2 Third nonce. + * @param nonce3 Fourth nonce. + */ +MLD_INTERNAL_API +void mld_poly_uniform_eta_4x(mld_poly *r0, mld_poly *r1, mld_poly *r2, + mld_poly *r3, const uint8_t seed[MLDSA_CRHBYTES], + uint8_t nonce0, uint8_t nonce1, uint8_t nonce2, + uint8_t nonce3) +__contract__( + requires(memory_no_alias(r0, sizeof(mld_poly))) + requires(memory_no_alias(r1, sizeof(mld_poly))) + requires(memory_no_alias(r2, sizeof(mld_poly))) + requires(memory_no_alias(r3, sizeof(mld_poly))) + requires(memory_no_alias(seed, MLDSA_CRHBYTES)) + assigns(memory_slice(r0, sizeof(mld_poly))) + assigns(memory_slice(r1, sizeof(mld_poly))) + assigns(memory_slice(r2, sizeof(mld_poly))) + assigns(memory_slice(r3, sizeof(mld_poly))) + ensures(array_abs_bound(r0->coeffs, 0, MLDSA_N, MLDSA_ETA + 1)) + ensures(array_abs_bound(r1->coeffs, 0, MLDSA_N, MLDSA_ETA + 1)) + ensures(array_abs_bound(r2->coeffs, 0, MLDSA_N, MLDSA_ETA + 1)) + ensures(array_abs_bound(r3->coeffs, 0, MLDSA_N, MLDSA_ETA + 1)) +); +#endif /* !MLD_CONFIG_SERIAL_FIPS202_ONLY */ + +#if defined(MLD_CONFIG_SERIAL_FIPS202_ONLY) +#define mld_poly_uniform_eta MLD_NAMESPACE_KL(poly_uniform_eta) +/** + * Sample polynomial with uniformly random coefficients in + * [-MLDSA_ETA, MLDSA_ETA] by performing rejection sampling on the output + * stream from SHAKE256(seed|nonce). + * + * @spec{Implements @[FIPS204, Algorithm 31, RejBoundedPoly].} + * + * @param[out] r Pointer to output polynomial. + * @param[in] seed Byte array with seed of length MLDSA_CRHBYTES. + * @param nonce Nonce. + */ +MLD_INTERNAL_API +void mld_poly_uniform_eta(mld_poly *r, const uint8_t seed[MLDSA_CRHBYTES], + uint8_t nonce) +__contract__( + requires(memory_no_alias(r, sizeof(mld_poly))) + requires(memory_no_alias(seed, MLDSA_CRHBYTES)) + assigns(memory_slice(r, sizeof(mld_poly))) + ensures(array_abs_bound(r->coeffs, 0, MLDSA_N, MLDSA_ETA + 1)) +); +#endif /* MLD_CONFIG_SERIAL_FIPS202_ONLY */ +#endif /* !MLD_CONFIG_NO_KEYPAIR_API */ + +#if !defined(MLD_CONFIG_NO_SIGN_API) +#if MLD_CONFIG_PARAMETER_SET == 65 || \ + defined(MLD_CONFIG_SERIAL_FIPS202_ONLY) || \ + defined(MLD_CONFIG_REDUCE_RAM) || defined(MLD_UNIT_TEST) +#define mld_poly_uniform_gamma1 MLD_NAMESPACE_KL(poly_uniform_gamma1) +/** + * Sample polynomial with uniformly random coefficients in + * [-(MLDSA_GAMMA1 - 1), MLDSA_GAMMA1] by unpacking output stream of + * SHAKE256(seed|nonce). + * + * @spec{Partially implements @[FIPS204, Algorithm 34, ExpandMask] (one + * polynomial, i.e. the loop body of lines 3-5).} + * + * @param[out] a Pointer to output polynomial. + * @param[in] seed Byte array with seed of length MLDSA_CRHBYTES. + * @param nonce 16-bit nonce. + */ +MLD_INTERNAL_API +void mld_poly_uniform_gamma1(mld_poly *a, const uint8_t seed[MLDSA_CRHBYTES], + uint16_t nonce) +__contract__( + requires(memory_no_alias(a, sizeof(mld_poly))) + requires(memory_no_alias(seed, MLDSA_CRHBYTES)) + assigns(memory_slice(a, sizeof(mld_poly))) + ensures(array_bound(a->coeffs, 0, MLDSA_N, -(MLDSA_GAMMA1 - 1), MLDSA_GAMMA1 + 1)) +); +#endif /* MLD_CONFIG_PARAMETER_SET == 65 || MLD_CONFIG_SERIAL_FIPS202_ONLY || \ + MLD_CONFIG_REDUCE_RAM || MLD_UNIT_TEST */ + +#if !defined(MLD_CONFIG_SERIAL_FIPS202_ONLY) && \ + (!defined(MLD_CONFIG_REDUCE_RAM) || defined(MLD_UNIT_TEST)) +#define mld_poly_uniform_gamma1_4x MLD_NAMESPACE_KL(poly_uniform_gamma1_4x) +/** + * Sample four polynomials with uniformly random coefficients in + * [-(MLDSA_GAMMA1 - 1), MLDSA_GAMMA1] by unpacking output streams of + * SHAKE256(seed|nonce_i). + * + * @spec{Partially implements @[FIPS204, Algorithm 34, ExpandMask] (four-way + * batched, i.e. four iterations of the loop body of lines 3-5).} + * + * @param[out] r0 Pointer to first output polynomial. + * @param[out] r1 Pointer to second output polynomial. + * @param[out] r2 Pointer to third output polynomial. + * @param[out] r3 Pointer to fourth output polynomial. + * @param[in] seed Byte array with seed of length MLDSA_CRHBYTES. + * @param nonce0 First 16-bit nonce. + * @param nonce1 Second 16-bit nonce. + * @param nonce2 Third 16-bit nonce. + * @param nonce3 Fourth 16-bit nonce. + */ +MLD_INTERNAL_API +void mld_poly_uniform_gamma1_4x(mld_poly *r0, mld_poly *r1, mld_poly *r2, + mld_poly *r3, + const uint8_t seed[MLDSA_CRHBYTES], + uint16_t nonce0, uint16_t nonce1, + uint16_t nonce2, uint16_t nonce3) +__contract__( + requires(memory_no_alias(r0, sizeof(mld_poly))) + requires(memory_no_alias(r1, sizeof(mld_poly))) + requires(memory_no_alias(r2, sizeof(mld_poly))) + requires(memory_no_alias(r3, sizeof(mld_poly))) + requires(memory_no_alias(seed, MLDSA_CRHBYTES)) + assigns(memory_slice(r0, sizeof(mld_poly))) + assigns(memory_slice(r1, sizeof(mld_poly))) + assigns(memory_slice(r2, sizeof(mld_poly))) + assigns(memory_slice(r3, sizeof(mld_poly))) + ensures(array_bound(r0->coeffs, 0, MLDSA_N, -(MLDSA_GAMMA1 - 1), MLDSA_GAMMA1 + 1)) + ensures(array_bound(r1->coeffs, 0, MLDSA_N, -(MLDSA_GAMMA1 - 1), MLDSA_GAMMA1 + 1)) + ensures(array_bound(r2->coeffs, 0, MLDSA_N, -(MLDSA_GAMMA1 - 1), MLDSA_GAMMA1 + 1)) + ensures(array_bound(r3->coeffs, 0, MLDSA_N, -(MLDSA_GAMMA1 - 1), MLDSA_GAMMA1 + 1)) +); +#endif /* !MLD_CONFIG_SERIAL_FIPS202_ONLY && (!MLD_CONFIG_REDUCE_RAM || \ + MLD_UNIT_TEST) */ +#endif /* !MLD_CONFIG_NO_SIGN_API */ + +#if !defined(MLD_CONFIG_NO_SIGN_API) || !defined(MLD_CONFIG_NO_VERIFY_API) +#define mld_poly_challenge MLD_NAMESPACE_KL(poly_challenge) +/** + * Samples polynomial with MLDSA_TAU nonzero coefficients in {-1, 1} using the + * output stream of SHAKE256(seed). + * + * @spec{Implements @[FIPS204, Algorithm 29, SampleInBall].} + * + * @param[out] c Pointer to output polynomial. + * @param[in] seed Byte array containing seed of length MLDSA_CTILDEBYTES. + */ +MLD_INTERNAL_API +void mld_poly_challenge(mld_poly *c, const uint8_t seed[MLDSA_CTILDEBYTES]) +__contract__( + requires(memory_no_alias(c, sizeof(mld_poly))) + requires(memory_no_alias(seed, MLDSA_CTILDEBYTES)) + assigns(memory_slice(c, sizeof(mld_poly))) + /* All coefficients of c are -1, 0 or +1 */ + ensures(array_bound(c->coeffs, 0, MLDSA_N, -1, 2)) +); +#endif /* !MLD_CONFIG_NO_SIGN_API || !MLD_CONFIG_NO_VERIFY_API */ + +#if !defined(MLD_CONFIG_NO_KEYPAIR_API) +#define mld_polyeta_pack MLD_NAMESPACE_KL(polyeta_pack) +/** + * Bit-pack polynomial with coefficients in [-MLDSA_ETA, MLDSA_ETA]. + * + * @spec{Implements @[FIPS204, Algorithm 17, BitPack].} + * + * @param[out] r Pointer to output byte array with at least + * MLDSA_POLYETA_PACKEDBYTES bytes. + * @param[in] a Pointer to input polynomial. + */ +MLD_INTERNAL_API +void mld_polyeta_pack(uint8_t r[MLDSA_POLYETA_PACKEDBYTES], const mld_poly *a) +__contract__( + requires(memory_no_alias(r, MLDSA_POLYETA_PACKEDBYTES)) + requires(memory_no_alias(a, sizeof(mld_poly))) + requires(array_abs_bound(a->coeffs, 0, MLDSA_N, MLDSA_ETA + 1)) + assigns(memory_slice(r, MLDSA_POLYETA_PACKEDBYTES)) +); +#endif /* !MLD_CONFIG_NO_KEYPAIR_API */ + +#if !defined(MLD_CONFIG_NO_KEYPAIR_API) || !defined(MLD_CONFIG_NO_SIGN_API) +/* + * polyeta_unpack produces coefficients in [-MLDSA_ETA, MLDSA_ETA] for + * well-formed inputs (i.e., those produced by polyeta_pack). + * However, when passed an arbitrary byte array, it may produce smaller values, + * i.e., values in [MLD_POLYETA_UNPACK_LOWER_BOUND, MLDSA_ETA]. + * Even though this should never happen, we use use the bound for arbitrary + * inputs in the CBMC proofs. + */ +#if MLDSA_ETA == 2 +#define MLD_POLYETA_UNPACK_LOWER_BOUND (-5) +#elif MLDSA_ETA == 4 +#define MLD_POLYETA_UNPACK_LOWER_BOUND (-11) +#else +#error "Invalid value of MLDSA_ETA" +#endif + +#define mld_polyeta_unpack MLD_NAMESPACE_KL(polyeta_unpack) +/** + * Unpack polynomial with coefficients in [-MLDSA_ETA, MLDSA_ETA]. + * + * @spec{Implements @[FIPS204, Algorithm 19, BitUnpack].} + * + * @param[out] r Pointer to output polynomial. + * @param[in] a Byte array with bit-packed polynomial. + */ +MLD_INTERNAL_API +void mld_polyeta_unpack(mld_poly *r, const uint8_t a[MLDSA_POLYETA_PACKEDBYTES]) +__contract__( + requires(memory_no_alias(r, sizeof(mld_poly))) + requires(memory_no_alias(a, MLDSA_POLYETA_PACKEDBYTES)) + assigns(memory_slice(r, sizeof(mld_poly))) + ensures(array_bound(r->coeffs, 0, MLDSA_N, MLD_POLYETA_UNPACK_LOWER_BOUND, MLDSA_ETA + 1)) +); +#endif /* !MLD_CONFIG_NO_KEYPAIR_API || !MLD_CONFIG_NO_SIGN_API */ + +#if !defined(MLD_CONFIG_NO_SIGN_API) +#define mld_polyz_pack MLD_NAMESPACE_KL(polyz_pack) +/** + * Bit-pack polynomial with coefficients in + * [-(MLDSA_GAMMA1 - 1), MLDSA_GAMMA1]. + * + * @spec{Implements @[FIPS204, Algorithm 17, BitPack].} + * + * @param[out] r Pointer to output byte array with at least + * MLDSA_POLYZ_PACKEDBYTES bytes. + * @param[in] a Pointer to input polynomial. + */ +MLD_INTERNAL_API +void mld_polyz_pack(uint8_t r[MLDSA_POLYZ_PACKEDBYTES], const mld_poly *a) +__contract__( + requires(memory_no_alias(r, MLDSA_POLYZ_PACKEDBYTES)) + requires(memory_no_alias(a, sizeof(mld_poly))) + requires(array_bound(a->coeffs, 0, MLDSA_N, -(MLDSA_GAMMA1 - 1), MLDSA_GAMMA1 + 1)) + assigns(memory_slice(r, MLDSA_POLYZ_PACKEDBYTES)) +); +#endif /* !MLD_CONFIG_NO_SIGN_API */ + +#if !defined(MLD_CONFIG_NO_SIGN_API) || !defined(MLD_CONFIG_NO_VERIFY_API) +#define mld_polyz_unpack MLD_NAMESPACE_KL(polyz_unpack) +/** + * Unpack polynomial z with coefficients in + * [-(MLDSA_GAMMA1 - 1), MLDSA_GAMMA1]. + * + * @spec{Implements @[FIPS204, Algorithm 19, BitUnpack].} + * + * @param[out] r Pointer to output polynomial. + * @param[in] a Byte array with bit-packed polynomial. + */ +MLD_INTERNAL_API +void mld_polyz_unpack(mld_poly *r, const uint8_t a[MLDSA_POLYZ_PACKEDBYTES]) +__contract__( + requires(memory_no_alias(r, sizeof(mld_poly))) + requires(memory_no_alias(a, MLDSA_POLYZ_PACKEDBYTES)) + assigns(memory_slice(r, sizeof(mld_poly))) + ensures(array_bound(r->coeffs, 0, MLDSA_N, -(MLDSA_GAMMA1 - 1), MLDSA_GAMMA1 + 1)) +); + +#define mld_polyw1_pack MLD_NAMESPACE_KL(polyw1_pack) +/** + * Bit-pack polynomial w1. Input coefficients must be in + * [0, (MLDSA_Q-1)/(2*MLDSA_GAMMA2)), i.e. [0, 43] for ML-DSA-44 and [0, 15] + * for ML-DSA-65/87. Dispatches to the value-specialized variant for the + * selected parameter set. + * + * @spec{Implements @[FIPS204, Algorithm 16, SimpleBitPack].} + * + * @param[out] r Pointer to output byte array with at least + * MLDSA_POLYW1_PACKEDBYTES bytes. + * @param[in] a Pointer to input polynomial. + */ +static MLD_INLINE void mld_polyw1_pack(uint8_t r[MLDSA_POLYW1_PACKEDBYTES], + const mld_poly *a) +__contract__( + requires(memory_no_alias(r, MLDSA_POLYW1_PACKEDBYTES)) + requires(memory_no_alias(a, sizeof(mld_poly))) + requires(array_bound(a->coeffs, 0, MLDSA_N, 0, (MLDSA_Q-1)/(2*MLDSA_GAMMA2))) + assigns(memory_slice(r, MLDSA_POLYW1_PACKEDBYTES)) +) +{ +#if MLD_CONFIG_PARAMETER_SET == 44 + mld_polyw1_pack_88(r, a); +#else + mld_polyw1_pack_32(r, a); +#endif +} +#endif /* !MLD_CONFIG_NO_SIGN_API || !MLD_CONFIG_NO_VERIFY_API */ + +#endif /* !MLD_POLY_KL_H */ diff --git a/mldsa_native/src/polyvec.c b/mldsa_native/src/polyvec.c new file mode 100644 index 0000000..b27868f --- /dev/null +++ b/mldsa_native/src/polyvec.c @@ -0,0 +1,509 @@ +/* + * Copyright (c) The mldsa-native project authors + * SPDX-License-Identifier: Apache-2.0 OR ISC OR MIT + */ + +/* References + * ========== + * + * - [FIPS204] + * FIPS 204 Module-Lattice-Based Digital Signature Standard + * National Institute of Standards and Technology + * https://csrc.nist.gov/pubs/fips/204/final + */ + +#include "polyvec.h" + +#include "debug.h" +#include "polyvec_lazy.h" + +/* This namespacing is not done at the top to avoid a naming conflict + * with native backends, which are currently not yet namespaced. */ +#define mld_polyvecl_pointwise_acc_montgomery_c \ + MLD_ADD_PARAM_SET(mld_polyvecl_pointwise_acc_montgomery_c) + +/**************************************************************/ +/************ Vectors of polynomials of length MLDSA_L **************/ +/**************************************************************/ +#if !defined(MLD_CONFIG_NO_SIGN_API) && \ + (!defined(MLD_CONFIG_REDUCE_RAM) || defined(MLD_UNIT_TEST)) +MLD_INTERNAL_API +void mld_polyvecl_uniform_gamma1(mld_polyvecl *v, + const uint8_t seed[MLDSA_CRHBYTES], + uint16_t kappa) +{ +#if defined(MLD_CONFIG_SERIAL_FIPS202_ONLY) + int i; +#endif + + /* The caller passes the base counter kappa; component i is sampled from + * kappa + i. Safety: kappa <= UINT16_MAX - MLDSA_L and i < MLDSA_L, so the + * casts below are safe. See MLD_MAX_KAPPA comment in sign.c. */ +#if defined(MLD_CONFIG_SERIAL_FIPS202_ONLY) + for (i = 0; i < MLDSA_L; i++) + { + mld_poly_uniform_gamma1(&v->vec[i], seed, (uint16_t)(kappa + i)); + } +#else /* MLD_CONFIG_SERIAL_FIPS202_ONLY */ +#if MLDSA_L == 4 + mld_poly_uniform_gamma1_4x(&v->vec[0], &v->vec[1], &v->vec[2], &v->vec[3], + seed, kappa, (uint16_t)(kappa + 1), + (uint16_t)(kappa + 2), (uint16_t)(kappa + 3)); +#elif MLDSA_L == 5 + mld_poly_uniform_gamma1_4x(&v->vec[0], &v->vec[1], &v->vec[2], &v->vec[3], + seed, kappa, (uint16_t)(kappa + 1), + (uint16_t)(kappa + 2), (uint16_t)(kappa + 3)); + mld_poly_uniform_gamma1(&v->vec[4], seed, (uint16_t)(kappa + 4)); +#elif MLDSA_L == 7 + mld_poly_uniform_gamma1_4x(&v->vec[0], &v->vec[1], &v->vec[2], + &v->vec[3 /* irrelevant */], seed, kappa, + (uint16_t)(kappa + 1), (uint16_t)(kappa + 2), + 0xFF /* irrelevant */); + mld_poly_uniform_gamma1_4x(&v->vec[3], &v->vec[4], &v->vec[5], &v->vec[6], + seed, (uint16_t)(kappa + 3), (uint16_t)(kappa + 4), + (uint16_t)(kappa + 5), (uint16_t)(kappa + 6)); +#endif /* MLDSA_L == 7 */ +#endif /* !MLD_CONFIG_SERIAL_FIPS202_ONLY */ + + mld_assert_bound_2d(v->vec, MLDSA_L, MLDSA_N, -(MLDSA_GAMMA1 - 1), + MLDSA_GAMMA1 + 1); +} +#endif /* !MLD_CONFIG_NO_SIGN_API && (!MLD_CONFIG_REDUCE_RAM || MLD_UNIT_TEST) \ + */ + +#if !defined(MLD_CONFIG_NO_KEYPAIR_API) || \ + !defined(MLD_CONFIG_NO_VERIFY_API) || \ + (!defined(MLD_CONFIG_NO_SIGN_API) && \ + (!defined(MLD_CONFIG_REDUCE_RAM) || defined(MLD_UNIT_TEST))) +MLD_INTERNAL_API +void mld_polyvecl_ntt(mld_polyvecl *v) +{ + unsigned int i; + mld_assert_abs_bound_2d(v->vec, MLDSA_L, MLDSA_N, MLDSA_Q); + + for (i = 0; i < MLDSA_L; ++i) + __loop__( + assigns(i, memory_slice(v, sizeof(mld_polyvecl))) + invariant(i <= MLDSA_L) + invariant(forall(k0, i, MLDSA_L, forall(k1, 0, MLDSA_N, v->vec[k0].coeffs[k1] == loop_entry(*v).vec[k0].coeffs[k1]))) + invariant(forall(k1, 0, i, array_abs_bound(v->vec[k1].coeffs, 0, MLDSA_N, MLD_NTT_BOUND))) + decreases(MLDSA_L - i)) + { + mld_poly_ntt(&v->vec[i]); + } + + mld_assert_abs_bound_2d(v->vec, MLDSA_L, MLDSA_N, MLD_NTT_BOUND); +} +#endif /* !MLD_CONFIG_NO_KEYPAIR_API || !MLD_CONFIG_NO_VERIFY_API || \ + (!MLD_CONFIG_NO_SIGN_API && (!MLD_CONFIG_REDUCE_RAM || \ + MLD_UNIT_TEST)) */ + +#if !defined(MLD_CONFIG_REDUCE_RAM) || defined(MLD_UNIT_TEST) +MLD_STATIC_TESTABLE void mld_polyvecl_pointwise_acc_montgomery_c( + mld_poly *w, const mld_polyvecl *u, const mld_polyvecl *v) +__contract__( + requires(memory_no_alias(w, sizeof(mld_poly))) + requires(memory_no_alias(u, sizeof(mld_polyvecl))) + requires(memory_no_alias(v, sizeof(mld_polyvecl))) + requires(forall(l0, 0, MLDSA_L, + array_bound(u->vec[l0].coeffs, 0, MLDSA_N, 0, MLDSA_Q))) + requires(forall(l1, 0, MLDSA_L, + array_abs_bound(v->vec[l1].coeffs, 0, MLDSA_N, MLD_NTT_BOUND))) + assigns(memory_slice(w, sizeof(mld_poly))) + ensures(array_abs_bound(w->coeffs, 0, MLDSA_N, MLDSA_Q)) +) +{ + unsigned int i, j; + mld_assert_bound_2d(u->vec, MLDSA_L, MLDSA_N, 0, MLDSA_Q); + mld_assert_abs_bound_2d(v->vec, MLDSA_L, MLDSA_N, MLD_NTT_BOUND); + for (i = 0; i < MLDSA_N; i++) + __loop__( + assigns(i, j, memory_slice(w, sizeof(mld_poly))) + invariant(i <= MLDSA_N) + invariant(array_abs_bound(w->coeffs, 0, i, MLDSA_Q)) + decreases(MLDSA_N - i) + ) + { + int64_t t = 0; + int32_t r; + for (j = 0; j < MLDSA_L; j++) + __loop__( + assigns(j, t) + invariant(j <= MLDSA_L) + invariant(t >= -(int64_t)j*(MLDSA_Q - 1)*(MLD_NTT_BOUND - 1)) + invariant(t <= (int64_t)j*(MLDSA_Q - 1)*(MLD_NTT_BOUND - 1)) + decreases(MLDSA_L - j) + ) + { + t += (int64_t)u->vec[j].coeffs[i] * v->vec[j].coeffs[i]; + } + + r = mld_montgomery_reduce(t); + w->coeffs[i] = r; + } + + mld_assert_abs_bound(w->coeffs, MLDSA_N, MLDSA_Q); +} + +MLD_INTERNAL_API +void mld_polyvecl_pointwise_acc_montgomery(mld_poly *w, const mld_polyvecl *u, + const mld_polyvecl *v) +{ +#if defined(MLD_USE_NATIVE_POLYVECL_POINTWISE_ACC_MONTGOMERY_L4) && \ + MLD_CONFIG_PARAMETER_SET == 44 + int ret; + mld_assert_bound_2d(u->vec, MLDSA_L, MLDSA_N, 0, MLDSA_Q); + mld_assert_abs_bound_2d(v->vec, MLDSA_L, MLDSA_N, MLD_NTT_BOUND); + ret = mld_polyvecl_pointwise_acc_montgomery_l4_native( + w->coeffs, (const int32_t (*)[MLDSA_N])u->vec, + (const int32_t (*)[MLDSA_N])v->vec); + if (ret == MLD_NATIVE_FUNC_SUCCESS) + { + mld_assert_abs_bound(w->coeffs, MLDSA_N, MLDSA_Q); + return; + } +#elif defined(MLD_USE_NATIVE_POLYVECL_POINTWISE_ACC_MONTGOMERY_L5) && \ + MLD_CONFIG_PARAMETER_SET == 65 + int ret; + mld_assert_bound_2d(u->vec, MLDSA_L, MLDSA_N, 0, MLDSA_Q); + mld_assert_abs_bound_2d(v->vec, MLDSA_L, MLDSA_N, MLD_NTT_BOUND); + ret = mld_polyvecl_pointwise_acc_montgomery_l5_native( + w->coeffs, (const int32_t (*)[MLDSA_N])u->vec, + (const int32_t (*)[MLDSA_N])v->vec); + if (ret == MLD_NATIVE_FUNC_SUCCESS) + { + mld_assert_abs_bound(w->coeffs, MLDSA_N, MLDSA_Q); + return; + } +#elif defined(MLD_USE_NATIVE_POLYVECL_POINTWISE_ACC_MONTGOMERY_L7) && \ + MLD_CONFIG_PARAMETER_SET == 87 + int ret; + mld_assert_bound_2d(u->vec, MLDSA_L, MLDSA_N, 0, MLDSA_Q); + mld_assert_abs_bound_2d(v->vec, MLDSA_L, MLDSA_N, MLD_NTT_BOUND); + ret = mld_polyvecl_pointwise_acc_montgomery_l7_native( + w->coeffs, (const int32_t (*)[MLDSA_N])u->vec, + (const int32_t (*)[MLDSA_N])v->vec); + if (ret == MLD_NATIVE_FUNC_SUCCESS) + { + mld_assert_abs_bound(w->coeffs, MLDSA_N, MLDSA_Q); + return; + } +#endif /* !(MLD_USE_NATIVE_POLYVECL_POINTWISE_ACC_MONTGOMERY_L4 && \ + MLD_CONFIG_PARAMETER_SET == 44) && \ + !(MLD_USE_NATIVE_POLYVECL_POINTWISE_ACC_MONTGOMERY_L5 && \ + MLD_CONFIG_PARAMETER_SET == 65) && \ + MLD_USE_NATIVE_POLYVECL_POINTWISE_ACC_MONTGOMERY_L7 && \ + MLD_CONFIG_PARAMETER_SET == 87 */ + /* The first input is bounded by [0, MLDSA_Q-1] inclusive. + * The second input is bounded by [-(MLD_NTT_BOUND-1), MLD_NTT_BOUND-1]. + * Hence, we can safely accumulate in 64-bits without intermediate reductions + * as MLDSA_L * (MLD_NTT_BOUND-1) * (MLDSA_Q-1) < INT64_MAX. + * + * The worst case is ML-DSA-87: 7 * (MLD_NTT_BOUND-1) * (MLDSA_Q-1) < 2**53 + * (and likewise for negative values). + */ + mld_polyvecl_pointwise_acc_montgomery_c(w, u, v); +} +#endif /* !MLD_CONFIG_REDUCE_RAM || MLD_UNIT_TEST */ + +#if !defined(MLD_CONFIG_NO_KEYPAIR_API) || !defined(MLD_CONFIG_NO_VERIFY_API) || \ + defined(MLD_UNIT_TEST) +MLD_INTERNAL_API +uint32_t mld_polyvecl_chknorm(const mld_polyvecl *v, int32_t bound) +{ + unsigned int i; + uint32_t t = 0; + mld_assert_bound_2d(v->vec, MLDSA_L, MLDSA_N, -MLD_REDUCE32_RANGE_MAX, + MLD_REDUCE32_RANGE_MAX); + + for (i = 0; i < MLDSA_L; ++i) + __loop__( + invariant(i <= MLDSA_L) + invariant(t == 0 || t == 0xFFFFFFFF) + invariant((t == 0) == forall(k1, 0, i, array_abs_bound(v->vec[k1].coeffs, 0, MLDSA_N, bound))) + decreases(MLDSA_L - i) + ) + { + /* Reference: Leaks which polynomial violates the bound via a conditional. + * We are more conservative to reduce the number of declassifications in + * constant-time testing. + */ + t |= mld_poly_chknorm(&v->vec[i], bound); + } + return t; +} +#endif /* !MLD_CONFIG_NO_KEYPAIR_API || !MLD_CONFIG_NO_VERIFY_API || \ + MLD_UNIT_TEST */ + +/**************************************************************/ +/************ Vectors of polynomials of length MLDSA_K **************/ +/**************************************************************/ +#if (!defined(MLD_CONFIG_NO_SIGN_API) && \ + defined(MLD_CONFIG_REDUCE_RAM)) || \ + defined(MLD_UNIT_TEST) +MLD_INTERNAL_API +void mld_polyveck_reduce(mld_polyveck *v) +{ + unsigned int i; + mld_assert_bound_2d(v->vec, MLDSA_K, MLDSA_N, INT32_MIN, + MLD_REDUCE32_DOMAIN_MAX); + + for (i = 0; i < MLDSA_K; ++i) + __loop__( + assigns(i, memory_slice(v, sizeof(mld_polyveck))) + invariant(i <= MLDSA_K) + invariant(forall(k0, i, MLDSA_K, forall(k1, 0, MLDSA_N, v->vec[k0].coeffs[k1] == loop_entry(*v).vec[k0].coeffs[k1]))) + invariant(forall(k2, 0, i, + array_bound(v->vec[k2].coeffs, 0, MLDSA_N, -MLD_REDUCE32_RANGE_MAX, MLD_REDUCE32_RANGE_MAX))) + decreases(MLDSA_K - i) + ) + { + mld_poly_reduce(&v->vec[i]); + } + + mld_assert_bound_2d(v->vec, MLDSA_K, MLDSA_N, -MLD_REDUCE32_RANGE_MAX, + MLD_REDUCE32_RANGE_MAX); +} +#endif /* (!MLD_CONFIG_NO_SIGN_API && MLD_CONFIG_REDUCE_RAM) || MLD_UNIT_TEST \ + */ + +#if !defined(MLD_CONFIG_NO_SIGN_API) || defined(MLD_UNIT_TEST) +MLD_INTERNAL_API +void mld_polyveck_caddq(mld_polyveck *v) +{ + unsigned int i; + mld_assert_abs_bound_2d(v->vec, MLDSA_K, MLDSA_N, MLDSA_Q); + + for (i = 0; i < MLDSA_K; ++i) + __loop__( + assigns(i, memory_slice(v, sizeof(mld_polyveck))) + invariant(i <= MLDSA_K) + invariant(forall(k0, i, MLDSA_K, forall(k1, 0, MLDSA_N, v->vec[k0].coeffs[k1] == loop_entry(*v).vec[k0].coeffs[k1]))) + invariant(forall(k1, 0, i, array_bound(v->vec[k1].coeffs, 0, MLDSA_N, 0, MLDSA_Q))) + decreases(MLDSA_K - i)) + { + mld_poly_caddq(&v->vec[i]); + } + + mld_assert_bound_2d(v->vec, MLDSA_K, MLDSA_N, 0, MLDSA_Q); +} +#endif /* !MLD_CONFIG_NO_SIGN_API || MLD_UNIT_TEST */ + +#if (!defined(MLD_CONFIG_NO_SIGN_API) || defined(MLD_UNIT_TEST)) && \ + (!defined(MLD_CONFIG_REDUCE_RAM) || defined(MLD_UNIT_TEST)) +MLD_INTERNAL_API +void mld_polyveck_ntt(mld_polyveck *v) +{ + unsigned int i; + mld_assert_abs_bound_2d(v->vec, MLDSA_K, MLDSA_N, MLDSA_Q); + + for (i = 0; i < MLDSA_K; ++i) + __loop__( + assigns(i, memory_slice(v, sizeof(mld_polyveck))) + invariant(i <= MLDSA_K) + invariant(forall(k0, i, MLDSA_K, forall(k1, 0, MLDSA_N, v->vec[k0].coeffs[k1] == loop_entry(*v).vec[k0].coeffs[k1]))) + invariant(forall(k1, 0, i, array_abs_bound(v->vec[k1].coeffs, 0, MLDSA_N, MLD_NTT_BOUND))) + decreases(MLDSA_K - i)) + { + mld_poly_ntt(&v->vec[i]); + } + mld_assert_abs_bound_2d(v->vec, MLDSA_K, MLDSA_N, MLD_NTT_BOUND); +} +#endif /* (!MLD_CONFIG_NO_SIGN_API || MLD_UNIT_TEST) && \ + (!MLD_CONFIG_REDUCE_RAM || MLD_UNIT_TEST) */ + +#if !defined(MLD_CONFIG_NO_SIGN_API) || defined(MLD_UNIT_TEST) +MLD_INTERNAL_API +void mld_polyveck_invntt_tomont(mld_polyveck *v) +{ + unsigned int i; + mld_assert_abs_bound_2d(v->vec, MLDSA_K, MLDSA_N, MLDSA_Q); + + for (i = 0; i < MLDSA_K; ++i) + __loop__( + assigns(i, memory_slice(v, sizeof(mld_polyveck))) + invariant(i <= MLDSA_K) + invariant(forall(k0, i, MLDSA_K, forall(k1, 0, MLDSA_N, v->vec[k0].coeffs[k1] == loop_entry(*v).vec[k0].coeffs[k1]))) + invariant(forall(k1, 0, i, array_abs_bound(v->vec[k1].coeffs, 0, MLDSA_N, MLD_INTT_BOUND))) + decreases(MLDSA_K - i)) + { + mld_poly_invntt_tomont(&v->vec[i]); + } + + mld_assert_abs_bound_2d(v->vec, MLDSA_K, MLDSA_N, MLD_INTT_BOUND); +} +#endif /* !MLD_CONFIG_NO_SIGN_API || MLD_UNIT_TEST */ + +#if !defined(MLD_CONFIG_NO_KEYPAIR_API) +MLD_INTERNAL_API +uint32_t mld_polyveck_chknorm(const mld_polyveck *v, int32_t bound) +{ + unsigned int i; + uint32_t t = 0; + mld_assert_bound_2d(v->vec, MLDSA_K, MLDSA_N, -MLD_REDUCE32_RANGE_MAX, + MLD_REDUCE32_RANGE_MAX); + + for (i = 0; i < MLDSA_K; ++i) + __loop__( + invariant(i <= MLDSA_K) + invariant(t == 0 || t == 0xFFFFFFFF) + invariant((t == 0) == forall(k1, 0, i, array_abs_bound(v->vec[k1].coeffs, 0, MLDSA_N, bound))) + decreases(MLDSA_K - i) + ) + { + /* Reference: Leaks which polynomial violates the bound via a conditional. + * We are more conservative to reduce the number of declassifications in + * constant-time testing. + */ + t |= mld_poly_chknorm(&v->vec[i], bound); + } + + return t; +} + +#endif /* !MLD_CONFIG_NO_KEYPAIR_API */ + +#if !defined(MLD_CONFIG_NO_SIGN_API) +MLD_INTERNAL_API +void mld_polyveck_decompose(mld_polyveck *v1, mld_polyveck *v0) +{ + unsigned int i; + mld_assert_bound_2d(v0->vec, MLDSA_K, MLDSA_N, 0, MLDSA_Q); + + for (i = 0; i < MLDSA_K; ++i) + __loop__( + assigns(i, memory_slice(v0, sizeof(mld_polyveck)), memory_slice(v1, sizeof(mld_polyveck))) + invariant(i <= MLDSA_K) + invariant(forall(k1, 0, i, + array_bound(v1->vec[k1].coeffs, 0, MLDSA_N, 0, (MLDSA_Q-1)/(2*MLDSA_GAMMA2)))) + invariant(forall(k2, 0, i, + array_abs_bound(v0->vec[k2].coeffs, 0, MLDSA_N, MLDSA_GAMMA2+1))) + invariant(forall(k3, i, MLDSA_K, + array_bound(v0->vec[k3].coeffs, 0, MLDSA_N, 0, MLDSA_Q))) + decreases(MLDSA_K - i) + ) + { + mld_poly_decompose(&v1->vec[i], &v0->vec[i]); + } + + mld_assert_bound_2d(v1->vec, MLDSA_K, MLDSA_N, 0, + (MLDSA_Q - 1) / (2 * MLDSA_GAMMA2)); + mld_assert_abs_bound_2d(v0->vec, MLDSA_K, MLDSA_N, MLDSA_GAMMA2 + 1); +} +#endif /* !MLD_CONFIG_NO_SIGN_API */ + +#if !defined(MLD_CONFIG_NO_SIGN_API) +MLD_INTERNAL_API +void mld_polyveck_pack_w1(uint8_t r[MLDSA_K * MLDSA_POLYW1_PACKEDBYTES], + const mld_polyveck *w1) +{ + unsigned int i; + mld_assert_bound_2d(w1->vec, MLDSA_K, MLDSA_N, 0, + (MLDSA_Q - 1) / (2 * MLDSA_GAMMA2)); + + for (i = 0; i < MLDSA_K; ++i) + __loop__( + assigns(i, memory_slice(r, MLDSA_K * MLDSA_POLYW1_PACKEDBYTES)) + invariant(i <= MLDSA_K) + decreases(MLDSA_K - i) + ) + { + mld_polyw1_pack(&r[i * MLDSA_POLYW1_PACKEDBYTES], &w1->vec[i]); + } +} +#endif /* !MLD_CONFIG_NO_SIGN_API */ + +#if !defined(MLD_CONFIG_NO_KEYPAIR_API) +MLD_INTERNAL_API +void mld_polyveck_pack_eta(uint8_t r[MLDSA_K * MLDSA_POLYETA_PACKEDBYTES], + const mld_polyveck *p) +{ + unsigned int i; + mld_assert_abs_bound_2d(p->vec, MLDSA_K, MLDSA_N, MLDSA_ETA + 1); + for (i = 0; i < MLDSA_K; ++i) + __loop__( + assigns(i, memory_slice(r, MLDSA_K * MLDSA_POLYETA_PACKEDBYTES)) + invariant(i <= MLDSA_K) + decreases(MLDSA_K - i) + ) + { + mld_polyeta_pack(&r[i * MLDSA_POLYETA_PACKEDBYTES], &p->vec[i]); + } +} + +MLD_INTERNAL_API +void mld_polyvecl_pack_eta(uint8_t r[MLDSA_L * MLDSA_POLYETA_PACKEDBYTES], + const mld_polyvecl *p) +{ + unsigned int i; + mld_assert_abs_bound_2d(p->vec, MLDSA_L, MLDSA_N, MLDSA_ETA + 1); + for (i = 0; i < MLDSA_L; ++i) + __loop__( + assigns(i, memory_slice(r, MLDSA_L * MLDSA_POLYETA_PACKEDBYTES)) + invariant(i <= MLDSA_L) + decreases(MLDSA_L - i) + ) + { + mld_polyeta_pack(&r[i * MLDSA_POLYETA_PACKEDBYTES], &p->vec[i]); + } +} + +#endif /* !MLD_CONFIG_NO_KEYPAIR_API */ + +#if !defined(MLD_CONFIG_NO_KEYPAIR_API) || \ + (!defined(MLD_CONFIG_NO_SIGN_API) && \ + (!defined(MLD_CONFIG_REDUCE_RAM) || defined(MLD_UNIT_TEST))) +MLD_INTERNAL_API +void mld_polyvecl_unpack_eta( + mld_polyvecl *p, const uint8_t r[MLDSA_L * MLDSA_POLYETA_PACKEDBYTES]) +{ + unsigned int i; + for (i = 0; i < MLDSA_L; ++i) + { + mld_polyeta_unpack(&p->vec[i], r + i * MLDSA_POLYETA_PACKEDBYTES); + } + + mld_assert_bound_2d(p->vec, MLDSA_L, MLDSA_N, MLD_POLYETA_UNPACK_LOWER_BOUND, + MLDSA_ETA + 1); +} +#endif /* !MLD_CONFIG_NO_KEYPAIR_API || (!MLD_CONFIG_NO_SIGN_API && \ + (!MLD_CONFIG_REDUCE_RAM || MLD_UNIT_TEST)) */ + +#if !defined(MLD_CONFIG_NO_VERIFY_API) +MLD_INTERNAL_API +void mld_polyvecl_unpack_z(mld_polyvecl *z, + const uint8_t r[MLDSA_L * MLDSA_POLYZ_PACKEDBYTES]) +{ + unsigned int i; + for (i = 0; i < MLDSA_L; ++i) + { + mld_polyz_unpack(&z->vec[i], r + i * MLDSA_POLYZ_PACKEDBYTES); + } + + mld_assert_bound_2d(z->vec, MLDSA_L, MLDSA_N, -(MLDSA_GAMMA1 - 1), + MLDSA_GAMMA1 + 1); +} +#endif /* !MLD_CONFIG_NO_VERIFY_API */ + +#if !defined(MLD_CONFIG_NO_KEYPAIR_API) || \ + (!defined(MLD_CONFIG_NO_SIGN_API) && \ + (!defined(MLD_CONFIG_REDUCE_RAM) || defined(MLD_UNIT_TEST))) +MLD_INTERNAL_API +void mld_polyveck_unpack_eta( + mld_polyveck *p, const uint8_t r[MLDSA_K * MLDSA_POLYETA_PACKEDBYTES]) +{ + unsigned int i; + for (i = 0; i < MLDSA_K; ++i) + { + mld_polyeta_unpack(&p->vec[i], r + i * MLDSA_POLYETA_PACKEDBYTES); + } + + mld_assert_bound_2d(p->vec, MLDSA_K, MLDSA_N, MLD_POLYETA_UNPACK_LOWER_BOUND, + MLDSA_ETA + 1); +} +#endif /* !MLD_CONFIG_NO_KEYPAIR_API || (!MLD_CONFIG_NO_SIGN_API && \ + (!MLD_CONFIG_REDUCE_RAM || MLD_UNIT_TEST)) */ + +/* To facilitate single-compilation-unit (SCU) builds, undefine all macros. + * Don't modify by hand -- this is auto-generated by scripts/autogen. */ +#undef mld_polyvecl_pointwise_acc_montgomery_c diff --git a/mldsa_native/src/polyvec.h b/mldsa_native/src/polyvec.h new file mode 100644 index 0000000..610ed7b --- /dev/null +++ b/mldsa_native/src/polyvec.h @@ -0,0 +1,435 @@ +/* + * Copyright (c) The mldsa-native project authors + * SPDX-License-Identifier: Apache-2.0 OR ISC OR MIT + */ + +/* References + * ========== + * + * - [FIPS204] + * FIPS 204 Module-Lattice-Based Digital Signature Standard + * National Institute of Standards and Technology + * https://csrc.nist.gov/pubs/fips/204/final + */ + +#ifndef MLD_POLYVEC_H +#define MLD_POLYVEC_H + +#include "cbmc.h" +#include "common.h" +#include "poly.h" +#include "poly_kl.h" + +/* Parameter set namespacing + * This is to facilitate building multiple instances + * of mldsa-native (e.g. with varying parameter sets) + * within a single compilation unit. */ +#define mld_polyvecl MLD_ADD_PARAM_SET(mld_polyvecl) +#define mld_polyveck MLD_ADD_PARAM_SET(mld_polyveck) +/* End of parameter set namespacing */ + +/** Vector of MLDSA_L polynomials. */ +typedef struct +{ + mld_poly vec[MLDSA_L]; /**< Component polynomials. */ +} mld_polyvecl; + + +#if !defined(MLD_CONFIG_NO_SIGN_API) && \ + (!defined(MLD_CONFIG_REDUCE_RAM) || defined(MLD_UNIT_TEST)) +#define mld_polyvecl_uniform_gamma1 MLD_NAMESPACE_KL(polyvecl_uniform_gamma1) +/** + * Sample vector of polynomials with uniformly random coefficients in + * [-(MLDSA_GAMMA1 - 1), MLDSA_GAMMA1] by unpacking output stream of + * SHAKE256(seed|kappa+i) for component i. + * + * @spec{Implements @[FIPS204, Algorithm 34, ExpandMask].} + * + * @param[out] v Pointer to output vector. + * @param[in] seed Byte array with seed of length MLDSA_CRHBYTES. + * @param kappa Base counter; component i uses kappa + i. + */ +MLD_INTERNAL_API +void mld_polyvecl_uniform_gamma1(mld_polyvecl *v, + const uint8_t seed[MLDSA_CRHBYTES], + uint16_t kappa) +__contract__( + requires(memory_no_alias(v, sizeof(mld_polyvecl))) + requires(memory_no_alias(seed, MLDSA_CRHBYTES)) + requires(kappa <= UINT16_MAX - MLDSA_L) + assigns(memory_slice(v, sizeof(mld_polyvecl))) + ensures(forall(k0, 0, MLDSA_L, + array_bound(v->vec[k0].coeffs, 0, MLDSA_N, -(MLDSA_GAMMA1 - 1), MLDSA_GAMMA1 + 1))) +); +#endif /* !MLD_CONFIG_NO_SIGN_API && (!MLD_CONFIG_REDUCE_RAM || MLD_UNIT_TEST) \ + */ + +#if !defined(MLD_CONFIG_NO_KEYPAIR_API) || \ + !defined(MLD_CONFIG_NO_VERIFY_API) || \ + (!defined(MLD_CONFIG_NO_SIGN_API) && \ + (!defined(MLD_CONFIG_REDUCE_RAM) || defined(MLD_UNIT_TEST))) +#define mld_polyvecl_ntt MLD_NAMESPACE_KL(polyvecl_ntt) +/** + * Forward NTT of all polynomials in vector of length MLDSA_L. Output + * coefficients are bounded by MLD_NTT_BOUND in absolute value. + * + * @param[in,out] v Pointer to input/output vector. + */ +MLD_INTERNAL_API +void mld_polyvecl_ntt(mld_polyvecl *v) +__contract__( + requires(memory_no_alias(v, sizeof(mld_polyvecl))) + requires(forall(k0, 0, MLDSA_L, array_abs_bound(v->vec[k0].coeffs, 0, MLDSA_N, MLDSA_Q))) + assigns(memory_slice(v, sizeof(mld_polyvecl))) + ensures(forall(k1, 0, MLDSA_L, array_abs_bound(v->vec[k1].coeffs, 0, MLDSA_N, MLD_NTT_BOUND))) +); +#endif /* !MLD_CONFIG_NO_KEYPAIR_API || !MLD_CONFIG_NO_VERIFY_API || \ + (!MLD_CONFIG_NO_SIGN_API && (!MLD_CONFIG_REDUCE_RAM || \ + MLD_UNIT_TEST)) */ + +#if !defined(MLD_CONFIG_REDUCE_RAM) || defined(MLD_UNIT_TEST) +#define mld_polyvecl_pointwise_acc_montgomery \ + MLD_NAMESPACE_KL(polyvecl_pointwise_acc_montgomery) +/** + * Pointwise multiply vectors of polynomials of length MLDSA_L, multiply + * resulting vector by 2^{-32} and add (accumulate) polynomials in it. + * Input/output vectors are in NTT domain representation. + * + * The first input "u" must be the output of polyvec_matrix_expand() and so + * have coefficients in [0, MLDSA_Q-1] inclusive. + * + * The second input "v" is assumed to be output of an NTT, and hence must have + * coefficients bounded by [-(MLD_NTT_BOUND-1), MLD_NTT_BOUND-1] inclusive. + * + * @spec{Partially implements @[FIPS204, Algorithm 48, MatrixVectorNTT] + * (one output polynomial; multiply-accumulate of two NTT-domain vectors).} + * + * @param[out] w Output polynomial. + * @param[in] u Pointer to first input vector. + * @param[in] v Pointer to second input vector. + */ +MLD_INTERNAL_API +void mld_polyvecl_pointwise_acc_montgomery(mld_poly *w, const mld_polyvecl *u, + const mld_polyvecl *v) +__contract__( + requires(memory_no_alias(w, sizeof(mld_poly))) + requires(memory_no_alias(u, sizeof(mld_polyvecl))) + requires(memory_no_alias(v, sizeof(mld_polyvecl))) + requires(forall(l0, 0, MLDSA_L, + array_bound(u->vec[l0].coeffs, 0, MLDSA_N, 0, MLDSA_Q))) + requires(forall(l1, 0, MLDSA_L, + array_abs_bound(v->vec[l1].coeffs, 0, MLDSA_N, MLD_NTT_BOUND))) + assigns(memory_slice(w, sizeof(mld_poly))) + ensures(array_abs_bound(w->coeffs, 0, MLDSA_N, MLDSA_Q)) +); +#endif /* !MLD_CONFIG_REDUCE_RAM || MLD_UNIT_TEST */ + +#if !defined(MLD_CONFIG_NO_KEYPAIR_API) || !defined(MLD_CONFIG_NO_VERIFY_API) +#define mld_polyvecl_chknorm MLD_NAMESPACE_KL(polyvecl_chknorm) +/** + * Check infinity norm of polynomials in vector of length MLDSA_L. Assumes + * input mld_polyvecl to be reduced by polyvecl_reduce(). + * + * @param[in] v Pointer to vector. + * @param B Norm bound. + * + * @return 0 if norm of all polynomials is strictly smaller than + * B <= (MLDSA_Q-1)/8 and 0xFFFFFFFF otherwise. + */ +MLD_INTERNAL_API +MLD_MUST_CHECK_RETURN_VALUE +uint32_t mld_polyvecl_chknorm(const mld_polyvecl *v, int32_t B) +__contract__( + requires(memory_no_alias(v, sizeof(mld_polyvecl))) + requires(0 <= B && B <= (MLDSA_Q - 1) / 8) + requires(forall(k0, 0, MLDSA_L, + array_bound(v->vec[k0].coeffs, 0, MLDSA_N, -MLD_REDUCE32_RANGE_MAX, MLD_REDUCE32_RANGE_MAX))) + ensures(return_value == 0 || return_value == 0xFFFFFFFF) + ensures((return_value == 0) == forall(k1, 0, MLDSA_L, array_abs_bound(v->vec[k1].coeffs, 0, MLDSA_N, B))) +); +#endif /* !MLD_CONFIG_NO_KEYPAIR_API || !MLD_CONFIG_NO_VERIFY_API */ + +/** Vector of MLDSA_K polynomials. */ +typedef struct +{ + mld_poly vec[MLDSA_K]; /**< Component polynomials. */ +} mld_polyveck; + +#if (!defined(MLD_CONFIG_NO_SIGN_API) && defined(MLD_CONFIG_REDUCE_RAM)) || \ + defined(MLD_UNIT_TEST) +#define mld_polyveck_reduce MLD_NAMESPACE_KL(polyveck_reduce) +/** + * Reduce coefficients of polynomials in vector of length MLDSA_K to + * representatives in [-MLD_REDUCE32_RANGE_MAX, MLD_REDUCE32_RANGE_MAX]. + * + * @param[in,out] v Pointer to input/output vector. + */ +MLD_INTERNAL_API +void mld_polyveck_reduce(mld_polyveck *v) +__contract__( + requires(memory_no_alias(v, sizeof(mld_polyveck))) + requires(forall(k0, 0, MLDSA_K, + array_bound(v->vec[k0].coeffs, 0, MLDSA_N, INT32_MIN, MLD_REDUCE32_DOMAIN_MAX))) + assigns(memory_slice(v, sizeof(mld_polyveck))) + ensures(forall(k1, 0, MLDSA_K, + array_bound(v->vec[k1].coeffs, 0, MLDSA_N, -MLD_REDUCE32_RANGE_MAX, MLD_REDUCE32_RANGE_MAX))) +); +#endif /* (!MLD_CONFIG_NO_SIGN_API && MLD_CONFIG_REDUCE_RAM) || MLD_UNIT_TEST \ + */ + +#if !defined(MLD_CONFIG_NO_SIGN_API) || defined(MLD_UNIT_TEST) +#define mld_polyveck_caddq MLD_NAMESPACE_KL(polyveck_caddq) +/** + * For all coefficients of polynomials in vector of length MLDSA_K add MLDSA_Q + * if coefficient is negative. + * + * @param[in,out] v Pointer to input/output vector. + */ +MLD_INTERNAL_API +void mld_polyveck_caddq(mld_polyveck *v) +__contract__( + requires(memory_no_alias(v, sizeof(mld_polyveck))) + requires(forall(k0, 0, MLDSA_K, + array_abs_bound(v->vec[k0].coeffs, 0, MLDSA_N, MLDSA_Q))) + assigns(memory_slice(v, sizeof(mld_polyveck))) + ensures(forall(k1, 0, MLDSA_K, + array_bound(v->vec[k1].coeffs, 0, MLDSA_N, 0, MLDSA_Q))) +); +#endif /* !MLD_CONFIG_NO_SIGN_API || MLD_UNIT_TEST */ + +#if (!defined(MLD_CONFIG_NO_SIGN_API) || defined(MLD_UNIT_TEST)) && \ + (!defined(MLD_CONFIG_REDUCE_RAM) || defined(MLD_UNIT_TEST)) +#define mld_polyveck_ntt MLD_NAMESPACE_KL(polyveck_ntt) +/** + * Forward NTT of all polynomials in vector of length MLDSA_K. Output + * coefficients are bounded by MLD_NTT_BOUND in absolute value. + * + * @param[in,out] v Pointer to input/output vector. + */ +MLD_INTERNAL_API +void mld_polyveck_ntt(mld_polyveck *v) +__contract__( + requires(memory_no_alias(v, sizeof(mld_polyveck))) + requires(forall(k0, 0, MLDSA_K, array_abs_bound(v->vec[k0].coeffs, 0, MLDSA_N, MLDSA_Q))) + assigns(memory_slice(v, sizeof(mld_polyveck))) + ensures(forall(k1, 0, MLDSA_K, array_abs_bound(v->vec[k1].coeffs, 0, MLDSA_N, MLD_NTT_BOUND))) +); +#endif /* (!MLD_CONFIG_NO_SIGN_API || MLD_UNIT_TEST) && \ + (!MLD_CONFIG_REDUCE_RAM || MLD_UNIT_TEST) */ + +#if !defined(MLD_CONFIG_NO_SIGN_API) || defined(MLD_UNIT_TEST) +#define mld_polyveck_invntt_tomont MLD_NAMESPACE_KL(polyveck_invntt_tomont) +/** + * Inverse NTT and multiplication by 2^{32} of polynomials in vector of + * length MLDSA_K. + * + * Input coefficients need to be less than MLDSA_Q, and output coefficients + * are bounded by MLD_INTT_BOUND. + * + * @param[in,out] v Pointer to input/output vector. + */ +MLD_INTERNAL_API +void mld_polyveck_invntt_tomont(mld_polyveck *v) +__contract__( + requires(memory_no_alias(v, sizeof(mld_polyveck))) + requires(forall(k0, 0, MLDSA_K, array_abs_bound(v->vec[k0].coeffs, 0, MLDSA_N, MLDSA_Q))) + assigns(memory_slice(v, sizeof(mld_polyveck))) + ensures(forall(k1, 0, MLDSA_K, array_abs_bound(v->vec[k1].coeffs, 0, MLDSA_N, MLD_INTT_BOUND))) +); +#endif /* !MLD_CONFIG_NO_SIGN_API || MLD_UNIT_TEST */ + +#if !defined(MLD_CONFIG_NO_KEYPAIR_API) +#define mld_polyveck_chknorm MLD_NAMESPACE_KL(polyveck_chknorm) +/** + * Check infinity norm of polynomials in vector of length MLDSA_K. Assumes + * input mld_polyveck to be reduced by polyveck_reduce(). + * + * @param[in] v Pointer to vector. + * @param B Norm bound. + * + * @return 0 if norm of all polynomials are strictly smaller than + * B <= (MLDSA_Q-1)/8 and 0xFFFFFFFF otherwise. + */ +MLD_INTERNAL_API +MLD_MUST_CHECK_RETURN_VALUE +uint32_t mld_polyveck_chknorm(const mld_polyveck *v, int32_t B) +__contract__( + requires(memory_no_alias(v, sizeof(mld_polyveck))) + requires(0 <= B && B <= (MLDSA_Q - 1) / 8) + requires(forall(k0, 0, MLDSA_K, + array_bound(v->vec[k0].coeffs, 0, MLDSA_N, + -MLD_REDUCE32_RANGE_MAX, MLD_REDUCE32_RANGE_MAX))) + ensures(return_value == 0 || return_value == 0xFFFFFFFF) + ensures((return_value == 0) == forall(k1, 0, MLDSA_K, array_abs_bound(v->vec[k1].coeffs, 0, MLDSA_N, B))) +); + +#endif /* !MLD_CONFIG_NO_KEYPAIR_API */ + +#if !defined(MLD_CONFIG_NO_SIGN_API) +#define mld_polyveck_decompose MLD_NAMESPACE_KL(polyveck_decompose) +/** + * For all coefficients a of polynomials in vector of length MLDSA_K, compute + * high and low bits a0, a1 such a mod^+ MLDSA_Q = a1*ALPHA + a0 with + * -ALPHA/2 < a0 <= ALPHA/2 except a1 = (MLDSA_Q-1)/ALPHA where we set + * a1 = 0 and -ALPHA/2 <= a0 = a mod MLDSA_Q - MLDSA_Q < 0. Assumes + * coefficients to be standard representatives. + * + * @reference{The reference implementation has the input polynomial as a + * separate argument that may be aliased with either of the outputs. Removing + * the aliasing eases CBMC proofs.} + * + * @param[out] v1 Pointer to output vector of polynomials with + * coefficients a1. + * @param[in,out] v0 Pointer to input/output vector of polynomials. Output + * polynomial has coefficients a0. + */ +MLD_INTERNAL_API +void mld_polyveck_decompose(mld_polyveck *v1, mld_polyveck *v0) +__contract__( + requires(memory_no_alias(v1, sizeof(mld_polyveck))) + requires(memory_no_alias(v0, sizeof(mld_polyveck))) + requires(forall(k0, 0, MLDSA_K, + array_bound(v0->vec[k0].coeffs, 0, MLDSA_N, 0, MLDSA_Q))) + assigns(memory_slice(v1, sizeof(mld_polyveck))) + assigns(memory_slice(v0, sizeof(mld_polyveck))) + ensures(forall(k1, 0, MLDSA_K, + array_bound(v1->vec[k1].coeffs, 0, MLDSA_N, 0, (MLDSA_Q-1)/(2*MLDSA_GAMMA2)))) + ensures(forall(k2, 0, MLDSA_K, + array_abs_bound(v0->vec[k2].coeffs, 0, MLDSA_N, MLDSA_GAMMA2+1))) +); +#endif /* !MLD_CONFIG_NO_SIGN_API */ + +#if !defined(MLD_CONFIG_NO_SIGN_API) +#define mld_polyveck_pack_w1 MLD_NAMESPACE_KL(polyveck_pack_w1) +/** + * Bit-pack polynomial vector w1 with coefficients in [0, 15] or [0, 43]. Input + * coefficients are assumed to be standard representatives. + * + * @spec{Implements @[FIPS204, Algorithm 28, w1Encode].} + * + * @param[out] r Pointer to output byte array with at least + * MLDSA_K * MLDSA_POLYW1_PACKEDBYTES bytes. + * @param[in] w1 Pointer to input polynomial vector. + */ +MLD_INTERNAL_API +void mld_polyveck_pack_w1(uint8_t r[MLDSA_K * MLDSA_POLYW1_PACKEDBYTES], + const mld_polyveck *w1) +__contract__( + requires(memory_no_alias(r, MLDSA_K * MLDSA_POLYW1_PACKEDBYTES)) + requires(memory_no_alias(w1, sizeof(mld_polyveck))) + requires(forall(k1, 0, MLDSA_K, + array_bound(w1->vec[k1].coeffs, 0, MLDSA_N, 0, (MLDSA_Q-1)/(2*MLDSA_GAMMA2)))) + assigns(memory_slice(r, MLDSA_K * MLDSA_POLYW1_PACKEDBYTES)) +); +#endif /* !MLD_CONFIG_NO_SIGN_API */ + +#if !defined(MLD_CONFIG_NO_KEYPAIR_API) +#define mld_polyveck_pack_eta MLD_NAMESPACE_KL(polyveck_pack_eta) +/** + * Bit-pack polynomial vector with coefficients in [-MLDSA_ETA, MLDSA_ETA]. + * + * @param[out] r Pointer to output byte array with + * MLDSA_K * MLDSA_POLYETA_PACKEDBYTES bytes. + * @param[in] p Pointer to input polynomial vector. + */ +MLD_INTERNAL_API +void mld_polyveck_pack_eta(uint8_t r[MLDSA_K * MLDSA_POLYETA_PACKEDBYTES], + const mld_polyveck *p) +__contract__( + requires(memory_no_alias(r, MLDSA_K * MLDSA_POLYETA_PACKEDBYTES)) + requires(memory_no_alias(p, sizeof(mld_polyveck))) + requires(forall(k1, 0, MLDSA_K, + array_abs_bound(p->vec[k1].coeffs, 0, MLDSA_N, MLDSA_ETA + 1))) + assigns(memory_slice(r, MLDSA_K * MLDSA_POLYETA_PACKEDBYTES)) +); + +#define mld_polyvecl_pack_eta MLD_NAMESPACE_KL(polyvecl_pack_eta) +/** + * Bit-pack polynomial vector with coefficients in [-MLDSA_ETA, MLDSA_ETA]. + * + * @param[out] r Pointer to output byte array with + * MLDSA_L * MLDSA_POLYETA_PACKEDBYTES bytes. + * @param[in] p Pointer to input polynomial vector. + */ +MLD_INTERNAL_API +void mld_polyvecl_pack_eta(uint8_t r[MLDSA_L * MLDSA_POLYETA_PACKEDBYTES], + const mld_polyvecl *p) +__contract__( + requires(memory_no_alias(r, MLDSA_L * MLDSA_POLYETA_PACKEDBYTES)) + requires(memory_no_alias(p, sizeof(mld_polyvecl))) + requires(forall(k1, 0, MLDSA_L, + array_abs_bound(p->vec[k1].coeffs, 0, MLDSA_N, MLDSA_ETA + 1))) + assigns(memory_slice(r, MLDSA_L * MLDSA_POLYETA_PACKEDBYTES)) +); + +#endif /* !MLD_CONFIG_NO_KEYPAIR_API */ + +#if !defined(MLD_CONFIG_NO_KEYPAIR_API) || \ + (!defined(MLD_CONFIG_NO_SIGN_API) && \ + (!defined(MLD_CONFIG_REDUCE_RAM) || defined(MLD_UNIT_TEST))) +#define mld_polyvecl_unpack_eta MLD_NAMESPACE_KL(polyvecl_unpack_eta) +/** + * Unpack polynomial vector with coefficients in [-MLDSA_ETA, MLDSA_ETA]. + * + * @param[out] p Pointer to output polynomial vector. + * @param[in] r Input byte array with bit-packed polynomial vector. + */ +MLD_INTERNAL_API +void mld_polyvecl_unpack_eta( + mld_polyvecl *p, const uint8_t r[MLDSA_L * MLDSA_POLYETA_PACKEDBYTES]) +__contract__( + requires(memory_no_alias(r, MLDSA_L * MLDSA_POLYETA_PACKEDBYTES)) + requires(memory_no_alias(p, sizeof(mld_polyvecl))) + assigns(memory_slice(p, sizeof(mld_polyvecl))) + ensures(forall(k1, 0, MLDSA_L, + array_bound(p->vec[k1].coeffs, 0, MLDSA_N, MLD_POLYETA_UNPACK_LOWER_BOUND, MLDSA_ETA + 1))) +); +#endif /* !MLD_CONFIG_NO_KEYPAIR_API || (!MLD_CONFIG_NO_SIGN_API && \ + (!MLD_CONFIG_REDUCE_RAM || MLD_UNIT_TEST)) */ + +#if !defined(MLD_CONFIG_NO_VERIFY_API) +#define mld_polyvecl_unpack_z MLD_NAMESPACE_KL(polyvecl_unpack_z) +/** + * Unpack polynomial vector with coefficients in + * [-(MLDSA_GAMMA1 - 1), MLDSA_GAMMA1]. + * + * @param[out] z Pointer to output polynomial vector. + * @param[in] r Input byte array with bit-packed polynomial vector. + */ +MLD_INTERNAL_API +void mld_polyvecl_unpack_z(mld_polyvecl *z, + const uint8_t r[MLDSA_L * MLDSA_POLYZ_PACKEDBYTES]) +__contract__( + requires(memory_no_alias(r, MLDSA_L * MLDSA_POLYZ_PACKEDBYTES)) + requires(memory_no_alias(z, sizeof(mld_polyvecl))) + assigns(memory_slice(z, sizeof(mld_polyvecl))) + ensures(forall(k1, 0, MLDSA_L, + array_bound(z->vec[k1].coeffs, 0, MLDSA_N, -(MLDSA_GAMMA1 - 1), MLDSA_GAMMA1 + 1))) +); +#endif /* !MLD_CONFIG_NO_VERIFY_API */ + +#if !defined(MLD_CONFIG_NO_KEYPAIR_API) || \ + (!defined(MLD_CONFIG_NO_SIGN_API) && \ + (!defined(MLD_CONFIG_REDUCE_RAM) || defined(MLD_UNIT_TEST))) +#define mld_polyveck_unpack_eta MLD_NAMESPACE_KL(polyveck_unpack_eta) +/** + * Unpack polynomial vector with coefficients in [-MLDSA_ETA, MLDSA_ETA]. + * + * @param[out] p Pointer to output polynomial vector. + * @param[in] r Input byte array with bit-packed polynomial vector. + */ +MLD_INTERNAL_API +void mld_polyveck_unpack_eta( + mld_polyveck *p, const uint8_t r[MLDSA_K * MLDSA_POLYETA_PACKEDBYTES]) +__contract__( + requires(memory_no_alias(r, MLDSA_K * MLDSA_POLYETA_PACKEDBYTES)) + requires(memory_no_alias(p, sizeof(mld_polyveck))) + assigns(memory_slice(p, sizeof(mld_polyveck))) + ensures(forall(k1, 0, MLDSA_K, + array_bound(p->vec[k1].coeffs, 0, MLDSA_N, MLD_POLYETA_UNPACK_LOWER_BOUND, MLDSA_ETA + 1))) +); +#endif /* !MLD_CONFIG_NO_KEYPAIR_API || (!MLD_CONFIG_NO_SIGN_API && \ + (!MLD_CONFIG_REDUCE_RAM || MLD_UNIT_TEST)) */ + + +#endif /* !MLD_POLYVEC_H */ diff --git a/mldsa_native/src/polyvec_lazy.c b/mldsa_native/src/polyvec_lazy.c new file mode 100644 index 0000000..d85761e --- /dev/null +++ b/mldsa_native/src/polyvec_lazy.c @@ -0,0 +1,311 @@ +/* + * Copyright (c) The mldsa-native project authors + * SPDX-License-Identifier: Apache-2.0 OR ISC OR MIT + */ + +/* References + * ========== + * + * - [FIPS204] + * FIPS 204 Module-Lattice-Based Digital Signature Standard + * National Institute of Standards and Technology + * https://csrc.nist.gov/pubs/fips/204/final + */ + +#include "polyvec_lazy.h" + +#include "debug.h" + +/* This namespacing is not done at the top to avoid a naming conflict + * with native backends, which are currently not yet namespaced. */ +#define mld_polymat_expand_entry MLD_ADD_PARAM_SET(mld_polymat_expand_entry) + +/** + * Sample a single matrix entry A[k][l] of ExpandA(rho) by rejection sampling + * from SHAKE128(rho|l|k), and apply the custom-order permutation when a + * native NTT backend is in use. + * + * The caller is expected to have copied rho into the first MLDSA_SEEDBYTES + * of seed_ext. This function writes the domain-separation bytes + * seed_ext[SEEDBYTES..+2] = {l, k} before sampling. + * + * @spec{Partially implements @[FIPS204, Algorithm 32, ExpandA] (samples one + * matrix entry via @[FIPS204, Algorithm 30, RejNTTPoly]).} + * + * @param[out] p Pointer to output polynomial. + * @param[in,out] seed_ext Seed buffer pre-filled with rho in the first + * MLDSA_SEEDBYTES; the final two bytes are + * overwritten. + * @param l Column index (inner, aka nonce low byte). + * @param k Row index (outer, aka nonce high byte). + */ +static MLD_INLINE void mld_polymat_expand_entry( + mld_poly *p, uint8_t seed_ext[MLD_ALIGN_UP(MLDSA_SEEDBYTES + 2)], uint8_t l, + uint8_t k) +__contract__( + requires(memory_no_alias(p, sizeof(mld_poly))) + requires(memory_no_alias(seed_ext, MLD_ALIGN_UP(MLDSA_SEEDBYTES + 2))) + assigns(memory_slice(p, sizeof(mld_poly))) + assigns(memory_slice(seed_ext, MLD_ALIGN_UP(MLDSA_SEEDBYTES + 2))) + ensures(array_bound(p->coeffs, 0, MLDSA_N, 0, MLDSA_Q)) +) +{ + seed_ext[MLDSA_SEEDBYTES + 0] = l; + seed_ext[MLDSA_SEEDBYTES + 1] = k; + mld_poly_uniform(p, seed_ext); + mld_poly_permute_bitrev_to_custom_optional(p); +} + +#if !defined(MLD_CONFIG_REDUCE_RAM) || defined(MLD_UNIT_TEST) + +MLD_INTERNAL_API +void mld_polyvec_matrix_expand_eager(mld_polymat_eager *mat, + const uint8_t rho[MLDSA_SEEDBYTES]) +{ + unsigned int i, j; + MLD_ALIGN uint8_t seed_ext[4][MLD_ALIGN_UP(MLDSA_SEEDBYTES + 2)]; + + for (j = 0; j < 4; j++) + __loop__( + assigns(j, object_whole(seed_ext)) + invariant(j <= 4) + decreases(4 - j) + ) + { + mld_memcpy(seed_ext[j], rho, MLDSA_SEEDBYTES); + } + +#if !defined(MLD_CONFIG_SERIAL_FIPS202_ONLY) + /* Sample 4 matrix entries a time. */ + for (i = 0; i < (MLDSA_K * MLDSA_L / 4) * 4; i += 4) + __loop__( + assigns(i, j, object_whole(seed_ext), memory_slice(mat, sizeof(mld_polymat_eager))) + invariant(i <= (MLDSA_K * MLDSA_L / 4) * 4 && i % 4 == 0) + /* vectors 0 .. i / MLDSA_L are completely sampled */ + invariant(forall(k1, 0, i / MLDSA_L, forall(l1, 0, MLDSA_L, + array_bound(mat->vec[k1].vec[l1].coeffs, 0, MLDSA_N, 0, MLDSA_Q)))) + /* last vector is sampled up to i % MLDSA_L */ + invariant(forall(k2, i / MLDSA_L, i / MLDSA_L + 1, forall(l2, 0, i % MLDSA_L, + array_bound(mat->vec[k2].vec[l2].coeffs, 0, MLDSA_N, 0, MLDSA_Q)))) + decreases((MLDSA_K * MLDSA_L / 4) * 4 - i) + ) + { + for (j = 0; j < 4; j++) + __loop__( + assigns(j, object_whole(seed_ext)) + invariant(j <= 4) + decreases(4 - j) + ) + { + uint8_t x = (uint8_t)((i + j) / MLDSA_L); + uint8_t y = (uint8_t)((i + j) % MLDSA_L); + + seed_ext[j][MLDSA_SEEDBYTES + 0] = y; + seed_ext[j][MLDSA_SEEDBYTES + 1] = x; + } + + mld_poly_uniform_4x(&mat->vec[i / MLDSA_L].vec[i % MLDSA_L], + &mat->vec[(i + 1) / MLDSA_L].vec[(i + 1) % MLDSA_L], + &mat->vec[(i + 2) / MLDSA_L].vec[(i + 2) % MLDSA_L], + &mat->vec[(i + 3) / MLDSA_L].vec[(i + 3) % MLDSA_L], + seed_ext); + mld_poly_permute_bitrev_to_custom_optional( + &mat->vec[i / MLDSA_L].vec[i % MLDSA_L]); + mld_poly_permute_bitrev_to_custom_optional( + &mat->vec[(i + 1) / MLDSA_L].vec[(i + 1) % MLDSA_L]); + mld_poly_permute_bitrev_to_custom_optional( + &mat->vec[(i + 2) / MLDSA_L].vec[(i + 2) % MLDSA_L]); + mld_poly_permute_bitrev_to_custom_optional( + &mat->vec[(i + 3) / MLDSA_L].vec[(i + 3) % MLDSA_L]); + } +#else /* !MLD_CONFIG_SERIAL_FIPS202_ONLY */ + i = 0; +#endif /* MLD_CONFIG_SERIAL_FIPS202_ONLY */ + + /* Entries omitted by the batch-sampling are sampled individually. */ + while (i < MLDSA_K * MLDSA_L) + __loop__( + assigns(i, object_whole(seed_ext), memory_slice(mat, sizeof(mld_polymat_eager))) + invariant(i <= MLDSA_K * MLDSA_L) + /* vectors 0 .. i / MLDSA_L are completely sampled */ + invariant(forall(k1, 0, i / MLDSA_L, forall(l1, 0, MLDSA_L, + array_bound(mat->vec[k1].vec[l1].coeffs, 0, MLDSA_N, 0, MLDSA_Q)))) + /* last vector is sampled up to i % MLDSA_L */ + invariant(forall(k2, i / MLDSA_L, i / MLDSA_L + 1, forall(l2, 0, i % MLDSA_L, + array_bound(mat->vec[k2].vec[l2].coeffs, 0, MLDSA_N, 0, MLDSA_Q)))) + decreases(MLDSA_K * MLDSA_L - i) + ) + { + uint8_t x = (uint8_t)(i / MLDSA_L); + uint8_t y = (uint8_t)(i % MLDSA_L); + mld_polymat_expand_entry(&mat->vec[x].vec[y], seed_ext[0], y, x); + i++; + } + + /* @[FIPS204, Section 3.6.3] Destruction of intermediate values. */ + mld_zeroize(seed_ext, sizeof(seed_ext)); +} + +MLD_INTERNAL_API +void mld_polyvec_matrix_pointwise_montgomery_row_eager(mld_poly *t_row, + mld_polymat_eager *mat, + const mld_polyvecl *v, + unsigned int i) +{ + mld_polyvecl_pointwise_acc_montgomery(t_row, &mat->vec[i], v); +} + +#if !defined(MLD_CONFIG_NO_SIGN_API) +MLD_INTERNAL_API +void mld_polyvec_matrix_pointwise_montgomery_yvec_eager(mld_polyveck *w, + mld_polymat_eager *mat, + const mld_yvec_eager *y, + mld_polyvecl *scratch) +{ + unsigned int i; + *scratch = y->vec; + mld_polyvecl_ntt(scratch); + + for (i = 0; i < MLDSA_K; ++i) + __loop__( + assigns(i, memory_slice(w, sizeof(mld_polyveck))) + invariant(i <= MLDSA_K) + invariant(forall(k0, 0, i, + array_abs_bound(w->vec[k0].coeffs, 0, MLDSA_N, MLDSA_Q))) + decreases(MLDSA_K - i) + ) + { + mld_polyvec_matrix_pointwise_montgomery_row_eager(&w->vec[i], mat, scratch, + i); + } + + mld_polyveck_invntt_tomont(w); +} +#endif /* !MLD_CONFIG_NO_SIGN_API */ + +#endif /* !MLD_CONFIG_REDUCE_RAM || MLD_UNIT_TEST */ + +#if defined(MLD_CONFIG_REDUCE_RAM) || defined(MLD_UNIT_TEST) + +MLD_INTERNAL_API +void mld_polyvec_matrix_expand_lazy(mld_polymat_lazy *mat, + const uint8_t rho[MLDSA_SEEDBYTES]) +{ + mld_memcpy(mat->rho, rho, MLDSA_SEEDBYTES); +} + +#if !defined(MLD_CONFIG_NO_KEYPAIR_API) || !defined(MLD_CONFIG_NO_VERIFY_API) +MLD_INTERNAL_API +void mld_polyvec_matrix_pointwise_montgomery_row_lazy(mld_poly *t_row, + mld_polymat_lazy *mat, + const mld_polyvecl *v, + unsigned int i) +{ + unsigned int l; + MLD_ALIGN uint8_t seed_ext[MLD_ALIGN_UP(MLDSA_SEEDBYTES + 2)]; + mld_memcpy(seed_ext, mat->rho, MLDSA_SEEDBYTES); + + mld_polymat_expand_entry(t_row, seed_ext, 0, (uint8_t)i); + mld_poly_pointwise_montgomery(t_row, &v->vec[0]); + + for (l = 1; l < MLDSA_L; ++l) + __loop__( + assigns(l, object_whole(seed_ext), + memory_slice(t_row, sizeof(mld_poly)), + memory_slice(mat, sizeof(mld_polymat_lazy))) + invariant(l >= 1 && l <= MLDSA_L) + invariant(array_abs_bound(t_row->coeffs, 0, MLDSA_N, l * MLDSA_Q)) + decreases(MLDSA_L - l) + ) + { + mld_polymat_expand_entry(&mat->cur, seed_ext, (uint8_t)l, (uint8_t)i); + mld_poly_pointwise_montgomery(&mat->cur, &v->vec[l]); + mld_poly_add(t_row, &mat->cur); + } + mld_poly_reduce(t_row); + + /* @[FIPS204, Section 3.6.3] Destruction of intermediate values. */ + mld_zeroize(seed_ext, sizeof(seed_ext)); +} +#endif /* !MLD_CONFIG_NO_KEYPAIR_API || !MLD_CONFIG_NO_VERIFY_API */ + +#if !defined(MLD_CONFIG_NO_SIGN_API) +MLD_INTERNAL_API +void mld_polyvec_matrix_pointwise_montgomery_yvec_lazy(mld_polyveck *w, + mld_polymat_lazy *mat, + const mld_yvec_lazy *y, + mld_polyvecl *scratch) +{ + unsigned int k, l; + MLD_ALIGN uint8_t seed_ext[MLD_ALIGN_UP(MLDSA_SEEDBYTES + 2)]; + /* Only the first poly of the polyvecl scratch is used. The polyvecl type + * matches the eager variant for API uniformity; in REDUCE_RAM mode the + * polyvecl storage is provided "for free" by the caller's polyveck/polyvecl + * union. */ + mld_poly *y_ntt = &scratch->vec[0]; + + mld_memcpy(seed_ext, mat->rho, MLDSA_SEEDBYTES); + + /* Column-by-column: sample y[l], NTT, accumulate column l of A into w. */ + for (l = 0; l < MLDSA_L; l++) + __loop__( + assigns(k, l, object_whole(seed_ext), + memory_slice(w, sizeof(mld_polyveck)), + memory_slice(mat, sizeof(mld_polymat_lazy)), + memory_slice(scratch, sizeof(mld_polyvecl))) + invariant(l <= MLDSA_L) + invariant(l == 0 || + forall(k0, 0, MLDSA_K, + array_abs_bound(w->vec[k0].coeffs, 0, MLDSA_N, + (int)l * MLDSA_Q))) + decreases(MLDSA_L - l) + ) + { + mld_yvec_get_poly_lazy(y_ntt, y, l); + mld_poly_ntt(y_ntt); + for (k = 0; k < MLDSA_K; k++) + __loop__( + assigns(k, object_whole(seed_ext), + memory_slice(w, sizeof(mld_polyveck)), + memory_slice(mat, sizeof(mld_polymat_lazy))) + invariant(k <= MLDSA_K) + invariant(l != 0 || + forall(k1, 0, k, + array_abs_bound(w->vec[k1].coeffs, 0, MLDSA_N, MLDSA_Q))) + invariant(l == 0 || + forall(k2, 0, k, + array_abs_bound(w->vec[k2].coeffs, 0, MLDSA_N, + ((int)l + 1) * MLDSA_Q))) + invariant(l == 0 || + forall(k3, k, MLDSA_K, + array_abs_bound(w->vec[k3].coeffs, 0, MLDSA_N, + (int)l * MLDSA_Q))) + decreases(MLDSA_K - k) + ) + { + if (l == 0) + { + mld_polymat_expand_entry(&w->vec[k], seed_ext, 0, (uint8_t)k); + mld_poly_pointwise_montgomery(&w->vec[k], y_ntt); + } + else + { + mld_polymat_expand_entry(&mat->cur, seed_ext, (uint8_t)l, (uint8_t)k); + mld_poly_pointwise_montgomery(&mat->cur, y_ntt); + mld_poly_add(&w->vec[k], &mat->cur); + } + } + } + + /* @[FIPS204, Section 3.6.3] Destruction of intermediate values. */ + mld_zeroize(seed_ext, sizeof(seed_ext)); + mld_polyveck_reduce(w); + mld_polyveck_invntt_tomont(w); +} +#endif /* !MLD_CONFIG_NO_SIGN_API */ + +#endif /* MLD_CONFIG_REDUCE_RAM || MLD_UNIT_TEST */ + +/* To facilitate single-compilation-unit (SCU) builds, undefine all macros. + * Don't modify by hand -- this is auto-generated by scripts/autogen. */ +#undef mld_polymat_expand_entry diff --git a/mldsa_native/src/polyvec_lazy.h b/mldsa_native/src/polyvec_lazy.h new file mode 100644 index 0000000..501d44c --- /dev/null +++ b/mldsa_native/src/polyvec_lazy.h @@ -0,0 +1,652 @@ +/* + * Copyright (c) The mldsa-native project authors + * SPDX-License-Identifier: Apache-2.0 OR ISC OR MIT + */ + +/* References + * ========== + * + * - [FIPS204] + * FIPS 204 Module-Lattice-Based Digital Signature Standard + * National Institute of Standards and Technology + * https://csrc.nist.gov/pubs/fips/204/final + */ + +/* + * Eager and lazy variants of polynomial vector types. + * + * In eager mode, full vectors are precomputed and stored in memory. + * In lazy mode, data is stored in packed form and expanded on demand, + * trading computation for reduced memory usage. + * + * MLD_CONFIG_REDUCE_RAM selects which variant is used. + */ + +#ifndef MLD_POLYVEC_LAZY_H +#define MLD_POLYVEC_LAZY_H + +#if !defined(MLD_CONFIG_NO_KEYPAIR_API) || !defined(MLD_CONFIG_NO_SIGN_API) || \ + !defined(MLD_CONFIG_NO_VERIFY_API) + +#include "poly.h" +#include "poly_kl.h" +#include "polyvec.h" + +/* Parameter set namespacing */ +#define mld_sk_s1hat_eager MLD_ADD_PARAM_SET(mld_sk_s1hat_eager) +#define mld_sk_s1hat_lazy MLD_ADD_PARAM_SET(mld_sk_s1hat_lazy) +#define mld_sk_s1hat MLD_ADD_PARAM_SET(mld_sk_s1hat) +#define mld_unpack_sk_s1hat_eager MLD_ADD_PARAM_SET(mld_unpack_sk_s1hat_eager) +#define mld_unpack_sk_s1hat_lazy MLD_ADD_PARAM_SET(mld_unpack_sk_s1hat_lazy) +#define mld_sk_s1hat_get_poly_eager \ + MLD_ADD_PARAM_SET(mld_sk_s1hat_get_poly_eager) +#define mld_sk_s1hat_get_poly_lazy MLD_ADD_PARAM_SET(mld_sk_s1hat_get_poly_lazy) +#define mld_sk_s2hat_eager MLD_ADD_PARAM_SET(mld_sk_s2hat_eager) +#define mld_sk_s2hat_lazy MLD_ADD_PARAM_SET(mld_sk_s2hat_lazy) +#define mld_sk_s2hat MLD_ADD_PARAM_SET(mld_sk_s2hat) +#define mld_unpack_sk_s2hat_eager MLD_ADD_PARAM_SET(mld_unpack_sk_s2hat_eager) +#define mld_unpack_sk_s2hat_lazy MLD_ADD_PARAM_SET(mld_unpack_sk_s2hat_lazy) +#define mld_sk_s2hat_get_poly_eager \ + MLD_ADD_PARAM_SET(mld_sk_s2hat_get_poly_eager) +#define mld_sk_s2hat_get_poly_lazy MLD_ADD_PARAM_SET(mld_sk_s2hat_get_poly_lazy) +#define mld_sk_t0hat_eager MLD_ADD_PARAM_SET(mld_sk_t0hat_eager) +#define mld_sk_t0hat_lazy MLD_ADD_PARAM_SET(mld_sk_t0hat_lazy) +#define mld_sk_t0hat MLD_ADD_PARAM_SET(mld_sk_t0hat) +#define mld_unpack_sk_t0hat_eager MLD_ADD_PARAM_SET(mld_unpack_sk_t0hat_eager) +#define mld_unpack_sk_t0hat_lazy MLD_ADD_PARAM_SET(mld_unpack_sk_t0hat_lazy) +#define mld_sk_t0hat_get_poly_eager \ + MLD_ADD_PARAM_SET(mld_sk_t0hat_get_poly_eager) +#define mld_sk_t0hat_get_poly_lazy MLD_ADD_PARAM_SET(mld_sk_t0hat_get_poly_lazy) +#define mld_polymat MLD_ADD_PARAM_SET(mld_polymat) +#define mld_polymat_eager MLD_ADD_PARAM_SET(mld_polymat_eager) +#define mld_polymat_lazy MLD_ADD_PARAM_SET(mld_polymat_lazy) +#define mld_poly_permute_bitrev_to_custom_optional \ + MLD_ADD_PARAM_SET(mld_poly_permute_bitrev_to_custom_optional) +#define mld_polyvec_matrix_expand_eager \ + MLD_NAMESPACE_KL(polyvec_matrix_expand_eager) +#define mld_polyvec_matrix_expand_lazy \ + MLD_NAMESPACE_KL(polyvec_matrix_expand_lazy) +#define mld_polyvec_matrix_pointwise_montgomery \ + MLD_NAMESPACE_KL(polyvec_matrix_pointwise_montgomery) +#define mld_polyvec_matrix_pointwise_montgomery_row_eager \ + MLD_NAMESPACE_KL(polyvec_matrix_pointwise_montgomery_row_eager) +#define mld_polyvec_matrix_pointwise_montgomery_row_lazy \ + MLD_NAMESPACE_KL(polyvec_matrix_pointwise_montgomery_row_lazy) +#define mld_polyvec_matrix_pointwise_montgomery_yvec_eager \ + MLD_NAMESPACE_KL(polyvec_matrix_pointwise_montgomery_yvec_eager) +#define mld_polyvec_matrix_pointwise_montgomery_yvec_lazy \ + MLD_NAMESPACE_KL(polyvec_matrix_pointwise_montgomery_yvec_lazy) +#define mld_yvec_eager MLD_ADD_PARAM_SET(mld_yvec_eager) +#define mld_yvec_lazy MLD_ADD_PARAM_SET(mld_yvec_lazy) +#define mld_yvec MLD_ADD_PARAM_SET(mld_yvec) +#define mld_yvec_init_eager MLD_ADD_PARAM_SET(mld_yvec_init_eager) +#define mld_yvec_init_lazy MLD_ADD_PARAM_SET(mld_yvec_init_lazy) +#define mld_yvec_get_poly_eager MLD_ADD_PARAM_SET(mld_yvec_get_poly_eager) +#define mld_yvec_get_poly_lazy MLD_ADD_PARAM_SET(mld_yvec_get_poly_lazy) +/* End of parameter set namespacing */ + +/** Eager s1hat: precomputed s1 vector in NTT domain. */ +typedef struct +{ + mld_polyvecl vec; /**< s1 vector in NTT domain. */ +} mld_sk_s1hat_eager; + +/** Eager s2hat: precomputed s2 vector in NTT domain. */ +typedef struct +{ + mld_polyveck vec; /**< s2 vector in NTT domain. */ +} mld_sk_s2hat_eager; + +/** Eager t0hat: precomputed t0 vector in NTT domain. */ +typedef struct +{ + mld_polyveck vec; /**< t0 vector in NTT domain. */ +} mld_sk_t0hat_eager; + +/** Lazy s1hat: borrow packed s1, unpack and convert to NTT domain on demand. */ +typedef struct +{ + const uint8_t *packed; /**< Pointer to packed s1 in the secret key. */ +} mld_sk_s1hat_lazy; + +/** Lazy s2hat: borrow packed s2, unpack and convert to NTT domain on demand. */ +typedef struct +{ + const uint8_t *packed; /**< Pointer to packed s2 in the secret key. */ +} mld_sk_s2hat_lazy; + +/** Lazy t0hat: borrow packed t0, unpack and convert to NTT domain on demand. */ +typedef struct +{ + const uint8_t *packed; /**< Pointer to packed t0 in the secret key. */ +} mld_sk_t0hat_lazy; + +/** Eager yvec: precomputed and stored full signing masking vector y. */ +typedef struct +{ + mld_polyvecl vec; /**< Masking vector y. */ +} mld_yvec_eager; + +/** Lazy yvec: store seed and base counter kappa, regenerate y[i] on demand. */ +typedef struct +{ + const uint8_t *rhoprime; /**< Pointer to seed used to derive y. */ + uint16_t kappa; /**< Base counter; component i uses kappa + i. */ +} mld_yvec_lazy; + +#if !defined(MLD_CONFIG_NO_KEYPAIR_API) || !defined(MLD_CONFIG_NO_SIGN_API) +/* s1vec */ + +#if !defined(MLD_CONFIG_REDUCE_RAM) || defined(MLD_UNIT_TEST) +static MLD_INLINE void mld_unpack_sk_s1hat_eager( + mld_sk_s1hat_eager *s1, + const uint8_t packed_s1[MLDSA_L * MLDSA_POLYETA_PACKEDBYTES]) +__contract__( + requires(memory_no_alias(s1, sizeof(mld_sk_s1hat_eager))) + requires(memory_no_alias(packed_s1, MLDSA_L * MLDSA_POLYETA_PACKEDBYTES)) + assigns(memory_slice(s1, sizeof(mld_sk_s1hat_eager))) + ensures(forall(k1, 0, MLDSA_L, + array_abs_bound(s1->vec.vec[k1].coeffs, 0, MLDSA_N, MLD_NTT_BOUND))) +) +{ + mld_polyvecl_unpack_eta(&s1->vec, packed_s1); + mld_polyvecl_ntt(&s1->vec); +} + +#if !defined(MLD_CONFIG_NO_SIGN_API) +static MLD_INLINE void mld_sk_s1hat_get_poly_eager(mld_poly *buf, + const mld_sk_s1hat_eager *s1, + unsigned int i) +__contract__( + requires(memory_no_alias(buf, sizeof(mld_poly))) + requires(memory_no_alias(s1, sizeof(mld_sk_s1hat_eager))) + requires(i < MLDSA_L) + requires(array_abs_bound(s1->vec.vec[i].coeffs, 0, MLDSA_N, MLD_NTT_BOUND)) + assigns(memory_slice(buf, sizeof(mld_poly))) + ensures(array_abs_bound(buf->coeffs, 0, MLDSA_N, MLD_NTT_BOUND)) +) { *buf = s1->vec.vec[i]; } +#endif /* !MLD_CONFIG_NO_SIGN_API */ +#endif /* !MLD_CONFIG_REDUCE_RAM || MLD_UNIT_TEST */ +#if defined(MLD_CONFIG_REDUCE_RAM) || defined(MLD_UNIT_TEST) +static MLD_INLINE void mld_unpack_sk_s1hat_lazy( + mld_sk_s1hat_lazy *s1, + const uint8_t packed_s1[MLDSA_L * MLDSA_POLYETA_PACKEDBYTES]) +__contract__( + requires(memory_no_alias(s1, sizeof(mld_sk_s1hat_lazy))) + assigns(memory_slice(s1, sizeof(mld_sk_s1hat_lazy))) + ensures(s1->packed == old(packed_s1)) +) { s1->packed = packed_s1; } + +#if !defined(MLD_CONFIG_NO_SIGN_API) +static MLD_INLINE void mld_sk_s1hat_get_poly_lazy(mld_poly *buf, + const mld_sk_s1hat_lazy *s1, + unsigned int i) +__contract__( + requires(memory_no_alias(buf, sizeof(mld_poly))) + requires(memory_no_alias(s1, sizeof(mld_sk_s1hat_lazy))) + requires(i < MLDSA_L) + requires(memory_no_alias(s1->packed, MLDSA_L * MLDSA_POLYETA_PACKEDBYTES)) + assigns(memory_slice(buf, sizeof(mld_poly))) + ensures(array_abs_bound(buf->coeffs, 0, MLDSA_N, MLD_NTT_BOUND)) +) +{ + mld_polyeta_unpack(buf, s1->packed + i * MLDSA_POLYETA_PACKEDBYTES); + mld_poly_ntt(buf); +} +#endif /* !MLD_CONFIG_NO_SIGN_API */ +#endif /* MLD_CONFIG_REDUCE_RAM || MLD_UNIT_TEST */ + +/* s2vec */ + +#if (!defined(MLD_CONFIG_NO_SIGN_API) || defined(MLD_UNIT_TEST)) && \ + (!defined(MLD_CONFIG_REDUCE_RAM) || defined(MLD_UNIT_TEST)) +static MLD_INLINE void mld_unpack_sk_s2hat_eager( + mld_sk_s2hat_eager *s2, + const uint8_t packed_s2[MLDSA_K * MLDSA_POLYETA_PACKEDBYTES]) +__contract__( + requires(memory_no_alias(s2, sizeof(mld_sk_s2hat_eager))) + requires(memory_no_alias(packed_s2, MLDSA_K * MLDSA_POLYETA_PACKEDBYTES)) + assigns(memory_slice(s2, sizeof(mld_sk_s2hat_eager))) + ensures(forall(k1, 0, MLDSA_K, + array_abs_bound(s2->vec.vec[k1].coeffs, 0, MLDSA_N, MLD_NTT_BOUND))) +) +{ + mld_polyveck_unpack_eta(&s2->vec, packed_s2); + mld_polyveck_ntt(&s2->vec); +} + +#if !defined(MLD_CONFIG_NO_SIGN_API) +static MLD_INLINE void mld_sk_s2hat_get_poly_eager(mld_poly *buf, + const mld_sk_s2hat_eager *s2, + unsigned int i) +__contract__( + requires(memory_no_alias(buf, sizeof(mld_poly))) + requires(memory_no_alias(s2, sizeof(mld_sk_s2hat_eager))) + requires(i < MLDSA_K) + requires(array_abs_bound(s2->vec.vec[i].coeffs, 0, MLDSA_N, MLD_NTT_BOUND)) + assigns(memory_slice(buf, sizeof(mld_poly))) + ensures(array_abs_bound(buf->coeffs, 0, MLDSA_N, MLD_NTT_BOUND)) +) { *buf = s2->vec.vec[i]; } +#endif /* !MLD_CONFIG_NO_SIGN_API */ +#endif /* (!MLD_CONFIG_NO_SIGN_API || MLD_UNIT_TEST) && \ + (!MLD_CONFIG_REDUCE_RAM || MLD_UNIT_TEST) */ +#if defined(MLD_CONFIG_REDUCE_RAM) || defined(MLD_UNIT_TEST) +static MLD_INLINE void mld_unpack_sk_s2hat_lazy( + mld_sk_s2hat_lazy *s2, + const uint8_t packed_s2[MLDSA_K * MLDSA_POLYETA_PACKEDBYTES]) +__contract__( + requires(memory_no_alias(s2, sizeof(mld_sk_s2hat_lazy))) + assigns(memory_slice(s2, sizeof(mld_sk_s2hat_lazy))) + ensures(s2->packed == old(packed_s2)) +) { s2->packed = packed_s2; } + +#if !defined(MLD_CONFIG_NO_SIGN_API) +static MLD_INLINE void mld_sk_s2hat_get_poly_lazy(mld_poly *buf, + const mld_sk_s2hat_lazy *s2, + unsigned int i) +__contract__( + requires(memory_no_alias(buf, sizeof(mld_poly))) + requires(memory_no_alias(s2, sizeof(mld_sk_s2hat_lazy))) + requires(i < MLDSA_K) + requires(memory_no_alias(s2->packed, MLDSA_K * MLDSA_POLYETA_PACKEDBYTES)) + assigns(memory_slice(buf, sizeof(mld_poly))) + ensures(array_abs_bound(buf->coeffs, 0, MLDSA_N, MLD_NTT_BOUND)) +) +{ + mld_polyeta_unpack(buf, s2->packed + i * MLDSA_POLYETA_PACKEDBYTES); + mld_poly_ntt(buf); +} +#endif /* !MLD_CONFIG_NO_SIGN_API */ +#endif /* MLD_CONFIG_REDUCE_RAM || MLD_UNIT_TEST */ + +/* t0vec */ + +#if (!defined(MLD_CONFIG_NO_SIGN_API) || defined(MLD_UNIT_TEST)) && \ + (!defined(MLD_CONFIG_REDUCE_RAM) || defined(MLD_UNIT_TEST)) +static MLD_INLINE void mld_unpack_sk_t0hat_eager( + mld_sk_t0hat_eager *t0, + const uint8_t packed_t0[MLDSA_K * MLDSA_POLYT0_PACKEDBYTES]) +__contract__( + requires(memory_no_alias(t0, sizeof(mld_sk_t0hat_eager))) + requires(memory_no_alias(packed_t0, MLDSA_K * MLDSA_POLYT0_PACKEDBYTES)) + assigns(memory_slice(t0, sizeof(mld_sk_t0hat_eager))) + ensures(forall(k1, 0, MLDSA_K, + array_abs_bound(t0->vec.vec[k1].coeffs, 0, MLDSA_N, MLD_NTT_BOUND))) +) +{ + unsigned int i; + for (i = 0; i < MLDSA_K; ++i) + __loop__( + assigns(i, memory_slice(t0, sizeof(mld_sk_t0hat_eager))) + invariant(i <= MLDSA_K) + invariant(forall(k0, 0, i, + array_bound(t0->vec.vec[k0].coeffs, 0, MLDSA_N, + -(1 << (MLDSA_D - 1)) + 1, (1 << (MLDSA_D - 1)) + 1))) + decreases(MLDSA_K - i) + ) + { + mld_polyt0_unpack(&t0->vec.vec[i], + packed_t0 + i * MLDSA_POLYT0_PACKEDBYTES); + } + mld_polyveck_ntt(&t0->vec); +} + +#if !defined(MLD_CONFIG_NO_SIGN_API) +static MLD_INLINE void mld_sk_t0hat_get_poly_eager(mld_poly *buf, + const mld_sk_t0hat_eager *t0, + unsigned int i) +__contract__( + requires(memory_no_alias(buf, sizeof(mld_poly))) + requires(memory_no_alias(t0, sizeof(mld_sk_t0hat_eager))) + requires(i < MLDSA_K) + requires(array_abs_bound(t0->vec.vec[i].coeffs, 0, MLDSA_N, MLD_NTT_BOUND)) + assigns(memory_slice(buf, sizeof(mld_poly))) + ensures(array_abs_bound(buf->coeffs, 0, MLDSA_N, MLD_NTT_BOUND)) +) { *buf = t0->vec.vec[i]; } +#endif /* !MLD_CONFIG_NO_SIGN_API */ +#endif /* (!MLD_CONFIG_NO_SIGN_API || MLD_UNIT_TEST) && \ + (!MLD_CONFIG_REDUCE_RAM || MLD_UNIT_TEST) */ +#if defined(MLD_CONFIG_REDUCE_RAM) || defined(MLD_UNIT_TEST) +static MLD_INLINE void mld_unpack_sk_t0hat_lazy( + mld_sk_t0hat_lazy *t0, + const uint8_t packed_t0[MLDSA_K * MLDSA_POLYT0_PACKEDBYTES]) +__contract__( + requires(memory_no_alias(t0, sizeof(mld_sk_t0hat_lazy))) + assigns(memory_slice(t0, sizeof(mld_sk_t0hat_lazy))) + ensures(t0->packed == old(packed_t0)) +) { t0->packed = packed_t0; } + +#if !defined(MLD_CONFIG_NO_SIGN_API) +static MLD_INLINE void mld_sk_t0hat_get_poly_lazy(mld_poly *buf, + const mld_sk_t0hat_lazy *t0, + unsigned int i) +__contract__( + requires(memory_no_alias(buf, sizeof(mld_poly))) + requires(memory_no_alias(t0, sizeof(mld_sk_t0hat_lazy))) + requires(i < MLDSA_K) + requires(memory_no_alias(t0->packed, MLDSA_K * MLDSA_POLYT0_PACKEDBYTES)) + assigns(memory_slice(buf, sizeof(mld_poly))) + ensures(array_abs_bound(buf->coeffs, 0, MLDSA_N, MLD_NTT_BOUND)) +) +{ + mld_polyt0_unpack(buf, t0->packed + i * MLDSA_POLYT0_PACKEDBYTES); + mld_poly_ntt(buf); +} +#endif /* !MLD_CONFIG_NO_SIGN_API */ +#endif /* MLD_CONFIG_REDUCE_RAM || MLD_UNIT_TEST */ +#endif /* !MLD_CONFIG_NO_KEYPAIR_API || !MLD_CONFIG_NO_SIGN_API */ + +/* yvec */ + +#if !defined(MLD_CONFIG_NO_SIGN_API) && \ + (!defined(MLD_CONFIG_REDUCE_RAM) || defined(MLD_UNIT_TEST)) +static MLD_INLINE void mld_yvec_init_eager( + mld_yvec_eager *y, const uint8_t rhoprime[MLDSA_CRHBYTES], uint16_t kappa) +__contract__( + requires(memory_no_alias(y, sizeof(mld_yvec_eager))) + requires(memory_no_alias(rhoprime, MLDSA_CRHBYTES)) + requires(kappa <= UINT16_MAX - MLDSA_L) + assigns(memory_slice(y, sizeof(mld_yvec_eager))) + ensures(forall(k1, 0, MLDSA_L, + array_bound(y->vec.vec[k1].coeffs, 0, MLDSA_N, -(MLDSA_GAMMA1 - 1), MLDSA_GAMMA1 + 1))) +) +{ + mld_polyvecl_uniform_gamma1(&y->vec, rhoprime, kappa); +} + +static MLD_INLINE void mld_yvec_get_poly_eager(mld_poly *buf, + const mld_yvec_eager *y, + unsigned int i) +__contract__( + requires(memory_no_alias(buf, sizeof(mld_poly))) + requires(memory_no_alias(y, sizeof(mld_yvec_eager))) + requires(i < MLDSA_L) + requires(array_bound(y->vec.vec[i].coeffs, 0, MLDSA_N, -(MLDSA_GAMMA1 - 1), MLDSA_GAMMA1 + 1)) + assigns(memory_slice(buf, sizeof(mld_poly))) + ensures(array_bound(buf->coeffs, 0, MLDSA_N, -(MLDSA_GAMMA1 - 1), MLDSA_GAMMA1 + 1)) +) { *buf = y->vec.vec[i]; } +#endif /* !MLD_CONFIG_NO_SIGN_API && (!MLD_CONFIG_REDUCE_RAM || MLD_UNIT_TEST) \ + */ +#if !defined(MLD_CONFIG_NO_SIGN_API) && \ + (defined(MLD_CONFIG_REDUCE_RAM) || defined(MLD_UNIT_TEST)) +static MLD_INLINE void mld_yvec_init_lazy( + mld_yvec_lazy *y, const uint8_t rhoprime[MLDSA_CRHBYTES], uint16_t kappa) +__contract__( + requires(memory_no_alias(y, sizeof(mld_yvec_lazy))) + assigns(memory_slice(y, sizeof(mld_yvec_lazy))) + ensures(y->rhoprime == old(rhoprime)) + ensures(y->kappa == old(kappa)) +) +{ + y->rhoprime = rhoprime; + y->kappa = kappa; +} + +static MLD_INLINE void mld_yvec_get_poly_lazy(mld_poly *buf, + const mld_yvec_lazy *y, + unsigned int i) +__contract__( + requires(memory_no_alias(buf, sizeof(mld_poly))) + requires(memory_no_alias(y, sizeof(mld_yvec_lazy))) + requires(i < MLDSA_L) + requires(memory_no_alias(y->rhoprime, MLDSA_CRHBYTES)) + requires(y->kappa <= UINT16_MAX - MLDSA_L) + assigns(memory_slice(buf, sizeof(mld_poly))) + ensures(array_bound(buf->coeffs, 0, MLDSA_N, -(MLDSA_GAMMA1 - 1), MLDSA_GAMMA1 + 1)) +) +{ + /* Safety: y->kappa <= UINT16_MAX - MLDSA_L and i < MLDSA_L, so y->kappa + i + * fits in uint16_t. See MLD_MAX_KAPPA comment in sign.c. */ + mld_poly_uniform_gamma1(buf, y->rhoprime, (uint16_t)(y->kappa + i)); +} +#endif /* !MLD_CONFIG_NO_SIGN_API && (MLD_CONFIG_REDUCE_RAM || MLD_UNIT_TEST) \ + */ + +/* polymat */ + +#if !defined(MLD_CONFIG_REDUCE_RAM) || defined(MLD_UNIT_TEST) +/** Eager polymat: precomputed and stored full MLDSA_K x MLDSA_L matrix. */ +typedef struct +{ + mld_polyvecl vec[MLDSA_K]; /**< Rows of the matrix. */ +} mld_polymat_eager; +#endif /* !MLD_CONFIG_REDUCE_RAM || MLD_UNIT_TEST */ + +/** Lazy polymat: store seed, sample elements A[k][l] on demand. */ +typedef struct +{ + mld_poly cur; /**< On-demand sampled matrix element A[k][l]. */ + uint8_t rho[MLDSA_SEEDBYTES]; /**< Public seed used to expand A. */ +} mld_polymat_lazy; + +static MLD_INLINE void mld_poly_permute_bitrev_to_custom_optional(mld_poly *p) +__contract__( + /* We don't specify that this is a permutation, only that it preserves + * the bounds. + * When the native NTT backend does not use the custom order, this is a no-op. */ + requires(memory_no_alias(p, sizeof(mld_poly))) + requires(array_bound(p->coeffs, 0, MLDSA_N, 0, MLDSA_Q)) + assigns(memory_slice(p, sizeof(mld_poly))) + ensures(array_bound(p->coeffs, 0, MLDSA_N, 0, MLDSA_Q)) +) +{ +#if defined(MLD_USE_NATIVE_NTT_CUSTOM_ORDER) + mld_poly_permute_bitrev_to_custom(p->coeffs); +#else + (void)p; +#endif +} + +#if !defined(MLD_CONFIG_REDUCE_RAM) || defined(MLD_UNIT_TEST) +/** + * Generates matrix A with uniformly random coefficients a_{i,j} by performing + * rejection sampling on the output stream of SHAKE128(rho|j|i). + * + * @spec{Implements @[FIPS204, Algorithm 32, ExpandA].} + * + * @param[out] mat Pointer to output matrix. + * @param[in] rho Byte array containing seed rho. + */ +MLD_INTERNAL_API +void mld_polyvec_matrix_expand_eager(mld_polymat_eager *mat, + const uint8_t rho[MLDSA_SEEDBYTES]) +__contract__( + requires(memory_no_alias(mat, sizeof(mld_polymat_eager))) + requires(memory_no_alias(rho, MLDSA_SEEDBYTES)) + assigns(memory_slice(mat, sizeof(mld_polymat_eager))) + ensures(forall(k1, 0, MLDSA_K, forall(l1, 0, MLDSA_L, + array_bound(mat->vec[k1].vec[l1].coeffs, 0, MLDSA_N, 0, MLDSA_Q)))) +); + +/** + * Compute row i of matrix-vector multiplication in NTT domain with pointwise + * multiplication and multiplication by 2^{-32}. + * + * Input matrix and vector must be in NTT domain representation. Output + * coefficients are bounded by MLDSA_Q in absolute value. + * + * @param[out] t_row Pointer to output row polynomial. + * @param[in] mat Pointer to input matrix. + * @param[in] v Pointer to input vector v. + * @param i Row index, 0 <= i < MLDSA_K. + */ +MLD_INTERNAL_API +void mld_polyvec_matrix_pointwise_montgomery_row_eager(mld_poly *t_row, + mld_polymat_eager *mat, + const mld_polyvecl *v, + unsigned int i) +__contract__( + requires(memory_no_alias(t_row, sizeof(mld_poly))) + requires(memory_no_alias(mat, sizeof(mld_polymat_eager))) + requires(memory_no_alias(v, sizeof(mld_polyvecl))) + requires(i < MLDSA_K) + requires(forall(l1, 0, MLDSA_L, + array_bound(mat->vec[i].vec[l1].coeffs, 0, MLDSA_N, 0, MLDSA_Q))) + requires(forall(l2, 0, MLDSA_L, + array_abs_bound(v->vec[l2].coeffs, 0, MLDSA_N, MLD_NTT_BOUND))) + assigns(memory_slice(t_row, sizeof(mld_poly))) + ensures(array_abs_bound(t_row->coeffs, 0, MLDSA_N, MLDSA_Q)) +); + +#if !defined(MLD_CONFIG_NO_SIGN_API) +/** + * Compute w = invNTT(A * NTT(y)) for the signing y vector. + * + * The eager variant copies y into the scratch polyvecl, NTTs it in place, + * calls the standard matrix-vector multiply, and finally inverse-NTTs the + * result into w. + * + * @param[out] w Pointer to output vector. + * @param[in] mat Pointer to input matrix. + * @param[in] y Pointer to (non-NTT) y vector. + * @param[out] scratch Scratch polyvecl for NTT'd copy of y. + */ +MLD_INTERNAL_API +void mld_polyvec_matrix_pointwise_montgomery_yvec_eager(mld_polyveck *w, + mld_polymat_eager *mat, + const mld_yvec_eager *y, + mld_polyvecl *scratch) +__contract__( + requires(memory_no_alias(w, sizeof(mld_polyveck))) + requires(memory_no_alias(mat, sizeof(mld_polymat_eager))) + requires(memory_no_alias(y, sizeof(mld_yvec_eager))) + requires(memory_no_alias(scratch, sizeof(mld_polyvecl))) + requires(forall(k1, 0, MLDSA_K, forall(l1, 0, MLDSA_L, + array_bound(mat->vec[k1].vec[l1].coeffs, 0, MLDSA_N, 0, MLDSA_Q)))) + requires(forall(l2, 0, MLDSA_L, + array_bound(y->vec.vec[l2].coeffs, 0, MLDSA_N, -(MLDSA_GAMMA1 - 1), MLDSA_GAMMA1 + 1))) + assigns(memory_slice(w, sizeof(mld_polyveck))) + assigns(memory_slice(scratch, sizeof(mld_polyvecl))) + ensures(forall(k0, 0, MLDSA_K, + array_abs_bound(w->vec[k0].coeffs, 0, MLDSA_N, MLD_INTT_BOUND))) +); +#endif /* !MLD_CONFIG_NO_SIGN_API */ +#endif /* !MLD_CONFIG_REDUCE_RAM || MLD_UNIT_TEST */ + +#if defined(MLD_CONFIG_REDUCE_RAM) || defined(MLD_UNIT_TEST) +MLD_INTERNAL_API +void mld_polyvec_matrix_expand_lazy(mld_polymat_lazy *mat, + const uint8_t rho[MLDSA_SEEDBYTES]) +__contract__( + requires(memory_no_alias(mat, sizeof(mld_polymat_lazy))) + requires(memory_no_alias(rho, MLDSA_SEEDBYTES)) + assigns(memory_slice(mat, sizeof(mld_polymat_lazy))) +); + +#if !defined(MLD_CONFIG_NO_KEYPAIR_API) || !defined(MLD_CONFIG_NO_VERIFY_API) +/** + * Compute row i of matrix-vector multiplication in NTT domain with pointwise + * multiplication and multiplication by 2^{-32}. + * + * Input vector must be in NTT domain representation; the matrix entries are + * sampled on demand from the seed stored in mat->rho, using mat->cur as + * scratch. Output coefficients are bounded by MLDSA_Q in absolute value. + * + * @param[out] t_row Pointer to output row polynomial. + * @param[in,out] mat Pointer to input matrix (seed + scratch). + * @param[in] v Pointer to input vector v. + * @param i Row index, 0 <= i < MLDSA_K. + */ +MLD_INTERNAL_API +void mld_polyvec_matrix_pointwise_montgomery_row_lazy(mld_poly *t_row, + mld_polymat_lazy *mat, + const mld_polyvecl *v, + unsigned int i) +__contract__( + requires(memory_no_alias(t_row, sizeof(mld_poly))) + requires(memory_no_alias(mat, sizeof(mld_polymat_lazy))) + requires(memory_no_alias(v, sizeof(mld_polyvecl))) + requires(i < MLDSA_K) + requires(forall(l1, 0, MLDSA_L, + array_abs_bound(v->vec[l1].coeffs, 0, MLDSA_N, MLD_NTT_BOUND))) + assigns(memory_slice(t_row, sizeof(mld_poly))) + assigns(memory_slice(mat, sizeof(mld_polymat_lazy))) + ensures(array_abs_bound(t_row->coeffs, 0, MLDSA_N, MLDSA_Q)) +); +#endif /* !MLD_CONFIG_NO_KEYPAIR_API || !MLD_CONFIG_NO_VERIFY_API */ + +#if !defined(MLD_CONFIG_NO_SIGN_API) +/** + * Compute w = invNTT(A * NTT(y)) for the signing y vector. + * + * The lazy variant samples one column of y at a time, NTTs it into + * &scratch->vec[0], and accumulates the matrix-vector product + * column-by-column with on-demand sampling of A[k][l]. Only the first poly of + * the polyvecl scratch is used; the polyvecl type is shared with the eager + * variant for API uniformity (the storage is provided "for free" by the + * caller's polyveck/polyvecl union in REDUCE_RAM mode). + * + * @param[out] w Pointer to output vector. + * @param[in,out] mat Pointer to input matrix. + * @param[in] y Pointer to y seed/kappa. + * @param[out] scratch Scratch (only &scratch->vec[0] used). + */ +MLD_INTERNAL_API +void mld_polyvec_matrix_pointwise_montgomery_yvec_lazy(mld_polyveck *w, + mld_polymat_lazy *mat, + const mld_yvec_lazy *y, + mld_polyvecl *scratch) +__contract__( + requires(memory_no_alias(w, sizeof(mld_polyveck))) + requires(memory_no_alias(mat, sizeof(mld_polymat_lazy))) + requires(memory_no_alias(y, sizeof(mld_yvec_lazy))) + requires(memory_no_alias(scratch, sizeof(mld_polyvecl))) + requires(memory_no_alias(y->rhoprime, MLDSA_CRHBYTES)) + requires(y->kappa <= UINT16_MAX - MLDSA_L) + assigns(memory_slice(w, sizeof(mld_polyveck))) + assigns(memory_slice(mat, sizeof(mld_polymat_lazy))) + assigns(memory_slice(scratch, sizeof(mld_polyvecl))) + ensures(forall(k0, 0, MLDSA_K, + array_abs_bound(w->vec[k0].coeffs, 0, MLDSA_N, MLD_INTT_BOUND))) +); +#endif /* !MLD_CONFIG_NO_SIGN_API */ +#endif /* MLD_CONFIG_REDUCE_RAM || MLD_UNIT_TEST */ + +/* Dispatch: typedef and define based on MLD_CONFIG_REDUCE_RAM */ +#if defined(MLD_CONFIG_REDUCE_RAM) +typedef mld_sk_s1hat_lazy mld_sk_s1hat; +typedef mld_sk_s2hat_lazy mld_sk_s2hat; +typedef mld_sk_t0hat_lazy mld_sk_t0hat; +typedef mld_polymat_lazy mld_polymat; +typedef mld_yvec_lazy mld_yvec; +#define mld_unpack_sk_s1hat mld_unpack_sk_s1hat_lazy +#define mld_unpack_sk_s2hat mld_unpack_sk_s2hat_lazy +#define mld_unpack_sk_t0hat mld_unpack_sk_t0hat_lazy +#if !defined(MLD_CONFIG_NO_SIGN_API) +#define mld_sk_s1hat_get_poly mld_sk_s1hat_get_poly_lazy +#define mld_sk_s2hat_get_poly mld_sk_s2hat_get_poly_lazy +#define mld_sk_t0hat_get_poly mld_sk_t0hat_get_poly_lazy +#endif +#define mld_polyvec_matrix_expand mld_polyvec_matrix_expand_lazy +#define mld_polyvec_matrix_pointwise_montgomery_row \ + mld_polyvec_matrix_pointwise_montgomery_row_lazy +#define mld_yvec_init mld_yvec_init_lazy +#define mld_yvec_get_poly mld_yvec_get_poly_lazy +#define mld_polyvec_matrix_pointwise_montgomery_yvec \ + mld_polyvec_matrix_pointwise_montgomery_yvec_lazy +#else /* MLD_CONFIG_REDUCE_RAM */ +typedef mld_sk_s1hat_eager mld_sk_s1hat; +typedef mld_sk_s2hat_eager mld_sk_s2hat; +typedef mld_sk_t0hat_eager mld_sk_t0hat; +typedef mld_polymat_eager mld_polymat; +typedef mld_yvec_eager mld_yvec; +#define mld_unpack_sk_s1hat mld_unpack_sk_s1hat_eager +#define mld_unpack_sk_s2hat mld_unpack_sk_s2hat_eager +#define mld_unpack_sk_t0hat mld_unpack_sk_t0hat_eager +#if !defined(MLD_CONFIG_NO_SIGN_API) +#define mld_sk_s2hat_get_poly mld_sk_s2hat_get_poly_eager +#define mld_sk_s1hat_get_poly mld_sk_s1hat_get_poly_eager +#define mld_sk_t0hat_get_poly mld_sk_t0hat_get_poly_eager +#endif +#define mld_polyvec_matrix_expand mld_polyvec_matrix_expand_eager +#define mld_polyvec_matrix_pointwise_montgomery_row \ + mld_polyvec_matrix_pointwise_montgomery_row_eager +#define mld_yvec_init mld_yvec_init_eager +#define mld_yvec_get_poly mld_yvec_get_poly_eager +#define mld_polyvec_matrix_pointwise_montgomery_yvec \ + mld_polyvec_matrix_pointwise_montgomery_yvec_eager +#endif /* !MLD_CONFIG_REDUCE_RAM */ + +#endif /* !MLD_CONFIG_NO_KEYPAIR_API || !MLD_CONFIG_NO_SIGN_API || \ + !MLD_CONFIG_NO_VERIFY_API */ +#endif /* !MLD_POLYVEC_LAZY_H */ diff --git a/mldsa_native/src/randombytes.h b/mldsa_native/src/randombytes.h new file mode 100644 index 0000000..ee34dc9 --- /dev/null +++ b/mldsa_native/src/randombytes.h @@ -0,0 +1,26 @@ +/* + * Copyright (c) The mldsa-native project authors + * SPDX-License-Identifier: Apache-2.0 OR ISC OR MIT + */ +#ifndef MLD_RANDOMBYTES_H +#define MLD_RANDOMBYTES_H + +#include + +#include "cbmc.h" +#include "common.h" + +#if !defined(MLD_CONFIG_NO_RANDOMIZED_API) +#if !defined(MLD_CONFIG_CUSTOM_RANDOMBYTES) +MLD_MUST_CHECK_RETURN_VALUE +int randombytes(uint8_t *out, size_t outlen); + +MLD_MUST_CHECK_RETURN_VALUE +static MLD_INLINE int mld_randombytes(uint8_t *out, size_t outlen) +__contract__( + requires(memory_no_alias(out, outlen)) + assigns(memory_slice(out, outlen)) +) { return randombytes(out, outlen); } +#endif /* !MLD_CONFIG_CUSTOM_RANDOMBYTES */ +#endif /* !MLD_CONFIG_NO_RANDOMIZED_API */ +#endif /* !MLD_RANDOMBYTES_H */ diff --git a/mldsa_native/src/reduce.h b/mldsa_native/src/reduce.h new file mode 100644 index 0000000..199fe22 --- /dev/null +++ b/mldsa_native/src/reduce.h @@ -0,0 +1,144 @@ +/* + * Copyright (c) The mldsa-native project authors + * SPDX-License-Identifier: Apache-2.0 OR ISC OR MIT + */ + +/* References + * ========== + * + * - [FIPS204] + * FIPS 204 Module-Lattice-Based Digital Signature Standard + * National Institute of Standards and Technology + * https://csrc.nist.gov/pubs/fips/204/final + */ + +#ifndef MLD_REDUCE_H +#define MLD_REDUCE_H + +#include "cbmc.h" +#include "common.h" +#include "ct.h" +#include "debug.h" + +/* check-magic: -4186625 == pow(2,32,MLDSA_Q) */ +#define MLD_MONT (-4186625) + +/* Upper bound for domain of mld_reduce32() */ +#define MLD_REDUCE32_DOMAIN_MAX (INT32_MAX - ((int32_t)1 << 22)) + +/* Absolute bound for range of mld_reduce32() */ +/* check-magic: 6283009 == (MLD_REDUCE32_DOMAIN_MAX - 255 * MLDSA_Q + 1) */ +#define MLD_REDUCE32_RANGE_MAX 6283009 + +/** + * Generic Montgomery reduction; given a 64-bit integer a, computes a 32-bit + * integer congruent to a * R^-1 mod MLDSA_Q, where R=2^32. + * + * @spec{Implements @[FIPS204, Algorithm 49, MontgomeryReduce].} + * + * @param a Input integer to be reduced, of absolute value smaller or equal + * to INT64_MAX - 2^31 * MLDSA_Q. + * + * @return Integer congruent to a * R^-1 modulo MLDSA_Q, with absolute value + * <= |a| / 2^32 + MLDSA_Q / 2. + * In particular, if |a| < 2^31 * MLDSA_Q, the absolute value of the + * return value is < MLDSA_Q. + */ +MLD_MUST_CHECK_RETURN_VALUE +static MLD_INLINE int32_t mld_montgomery_reduce(int64_t a) +__contract__( + /* We don't attempt to express an input-dependent output bound + * as the post-condition here, as all call-sites satisfy the + * absolute input bound 2^31 * MLDSA_Q and higher-level + * reasoning can be conducted using |return_value| < MLDSA_Q. */ + requires(a > -(((int64_t)1 << 31) * MLDSA_Q) && + a < (((int64_t)1 << 31) * MLDSA_Q)) + ensures(return_value > -MLDSA_Q && return_value < MLDSA_Q) +) +{ + /* check-magic: 58728449 == unsigned_mod(pow(MLDSA_Q, -1, 2^32), 2^32) */ + const uint64_t QINV = 58728449; + + /* Compute a*q^{-1} mod 2^32 in unsigned representatives */ + const uint32_t a_reduced = mld_cast_int64_to_uint32(a); + const uint32_t a_inverted = (a_reduced * QINV) & UINT32_MAX; + + /* Lift to signed canonical representative mod 2^32. */ + const int32_t t = mld_cast_uint32_to_int32(a_inverted); + + int64_t r; + + mld_assert(a < +(INT64_MAX - (((int64_t)1 << 31) * MLDSA_Q)) && + a > -(INT64_MAX - (((int64_t)1 << 31) * MLDSA_Q))); + + r = a - (int64_t)t * MLDSA_Q; + + /* + * PORTABILITY: Right-shift on a signed integer is, strictly-speaking, + * implementation-defined for negative left argument. Here, + * we assume it's sign-preserving "arithmetic" shift right. (C99 6.5.7 (5)) + */ + r = r >> 32; + + /* Bounds: + * + * By construction of the Montgomery multiplication, by the time we + * compute r >> 32, r is divisible by 2^32, and hence + * + * |r >> 32| = |r| / 2^32 + * <= |a| / 2^32 + MLDSA_Q / 2 + * + * (In general, we would only have |x >> n| <= ceil(|x| / 2^n)). + * + * In particular, if |a| < 2^31 * MLDSA_Q, then |return_value| < MLDSA_Q. + */ + return (int32_t)r; +} + +/** + * For finite field element a with a <= 2^{31} - 2^{22} - 1, compute + * r congruent to a (mod MLDSA_Q) such that + * -MLD_REDUCE32_RANGE_MAX <= r < MLD_REDUCE32_RANGE_MAX. + * + * @param a Finite field element. + * + * @return r. + */ +MLD_MUST_CHECK_RETURN_VALUE +static MLD_INLINE int32_t mld_reduce32(int32_t a) +__contract__( + requires(a <= MLD_REDUCE32_DOMAIN_MAX) + ensures(return_value >= -MLD_REDUCE32_RANGE_MAX) + ensures(return_value < MLD_REDUCE32_RANGE_MAX) +) +{ + int32_t t; + + t = (a + ((int32_t)1 << 22)) >> 23; + t = a - t * MLDSA_Q; + mld_assert((t - a) % MLDSA_Q == 0); + return t; +} + +/** + * Add MLDSA_Q if input coefficient is negative. + * + * @param a Finite field element. + * + * @return r. + */ +MLD_MUST_CHECK_RETURN_VALUE +static MLD_INLINE int32_t mld_caddq(int32_t a) +__contract__( + requires(a > -MLDSA_Q) + requires(a < MLDSA_Q) + ensures(return_value >= 0) + ensures(return_value < MLDSA_Q) + ensures(return_value == ((a >= 0) ? a : (a + MLDSA_Q))) +) +{ + return mld_ct_sel_int32(a + MLDSA_Q, a, mld_ct_cmask_neg_i32(a)); +} + + +#endif /* !MLD_REDUCE_H */ diff --git a/mldsa_native/src/rounding.h b/mldsa_native/src/rounding.h new file mode 100644 index 0000000..97f70e2 --- /dev/null +++ b/mldsa_native/src/rounding.h @@ -0,0 +1,263 @@ +/* + * Copyright (c) The mldsa-native project authors + * SPDX-License-Identifier: Apache-2.0 OR ISC OR MIT + */ + +/* References + * ========== + * + * - [FIPS204] + * FIPS 204 Module-Lattice-Based Digital Signature Standard + * National Institute of Standards and Technology + * https://csrc.nist.gov/pubs/fips/204/final + */ + +#ifndef MLD_ROUNDING_H +#define MLD_ROUNDING_H + +#include "cbmc.h" +#include "common.h" +#include "ct.h" +#include "debug.h" + +/* Parameter set namespacing + * This is to facilitate building multiple instances + * of mldsa-native (e.g. with varying parameter sets) + * within a single compilation unit. */ +#define mld_power2round MLD_ADD_PARAM_SET(mld_power2round) +#define mld_decompose MLD_ADD_PARAM_SET(mld_decompose) +#define mld_make_hint MLD_ADD_PARAM_SET(mld_make_hint) +#define mld_use_hint MLD_ADD_PARAM_SET(mld_use_hint) +/* End of parameter set namespacing */ + +#define MLD_2_POW_D (1 << MLDSA_D) + +/** + * For finite field element a, compute a0, a1 such that + * a mod^+ MLDSA_Q = a1*2^MLDSA_D + a0 with + * -2^{MLDSA_D-1} < a0 <= 2^{MLDSA_D-1}. Assumes a to be standard + * representative. + * + * @spec{Implements @[FIPS204, Algorithm 35, Power2Round].} + * + * @reference{In the reference implementation, a1 is passed as a return value + * instead.} + * + * @param[out] a0 Pointer to output element a0. + * @param[out] a1 Pointer to output element a1. + * @param a Input element. + */ +static MLD_INLINE void mld_power2round(int32_t *a0, int32_t *a1, int32_t a) +__contract__( + requires(memory_no_alias(a0, sizeof(int32_t))) + requires(memory_no_alias(a1, sizeof(int32_t))) + requires(a >= 0 && a < MLDSA_Q) + assigns(memory_slice(a0, sizeof(int32_t))) + assigns(memory_slice(a1, sizeof(int32_t))) + ensures(*a0 > -(MLD_2_POW_D/2) && *a0 <= (MLD_2_POW_D/2)) + ensures(*a1 >= 0 && *a1 <= (MLDSA_Q - 1) / MLD_2_POW_D) + ensures((*a1 * MLD_2_POW_D + *a0 - a) % MLDSA_Q == 0) +) +{ + *a1 = (a + (1 << (MLDSA_D - 1)) - 1) >> MLDSA_D; + *a0 = a - (*a1 << MLDSA_D); +} + +/** + * For finite field element a, compute high and low bits a0, a1 such that + * a mod^+ MLDSA_Q = a1 * 2 * MLDSA_GAMMA2 + a0 with + * -MLDSA_GAMMA2 < a0 <= MLDSA_GAMMA2 except if + * a1 = (MLDSA_Q-1)/(MLDSA_GAMMA2*2) where we set a1 = 0 and + * -MLDSA_GAMMA2 <= a0 = a mod^+ MLDSA_Q - MLDSA_Q < 0. Assumes a to be + * standard representative. + * + * @spec{Implements @[FIPS204, Algorithm 36, Decompose].} + * + * @reference{In the reference implementation, a1 is passed as a return value + * instead.} + * + * @param[out] a0 Pointer to output element a0. + * @param[out] a1 Pointer to output element a1. + * @param a Input element. + */ +static MLD_INLINE void mld_decompose(int32_t *a0, int32_t *a1, int32_t a) +__contract__( + requires(memory_no_alias(a0, sizeof(int32_t))) + requires(memory_no_alias(a1, sizeof(int32_t))) + requires(a >= 0 && a < MLDSA_Q) + assigns(memory_slice(a0, sizeof(int32_t))) + assigns(memory_slice(a1, sizeof(int32_t))) + /* a0 = -MLDSA_GAMMA2 occurs exactly when a = MLDSA_Q - MLDSA_GAMMA2: the + * border case of Decompose where a1 = (MLDSA_Q-1)/(2*MLDSA_GAMMA2) is + * wrapped to 0 and a0 = a - MLDSA_Q (@[FIPS204, Algorithm 36, Decompose]) */ + ensures(*a0 >= -MLDSA_GAMMA2 && *a0 <= MLDSA_GAMMA2) + ensures(*a1 >= 0 && *a1 < (MLDSA_Q-1)/(2*MLDSA_GAMMA2)) + ensures((*a1 * 2 * MLDSA_GAMMA2 + *a0 - a) % MLDSA_Q == 0) +) +{ + /* + * The goal is to compute f1 = round-(f / (2*GAMMA2)), which can be computed + * alternatively as round-(f / (128B)) = round-(ceil(f / 128) / B) where + * B = 2*GAMMA2 / 128. Here round-() denotes "round half down". + * + * The equality round-(f / (128B)) = round-(ceil(f / 128) / B) can deduced + * as follows. Since changing f to align-up(f, 128) can move f onto but not + * across a rounding boundary for division by 128*B (note that we need B to be + * even for this to work), and round- rounds down on the boundary, we have + * + * round-(f / (128B)) = round-(align-up(f, 128) / (128B)) + * = round-((align-up(f, 128) / 128) / B) + * = round-(ceil(f / 128) / B). + */ + *a1 = (a + 127) >> 7; + /* We know a >= 0 and a < MLDSA_Q, so... */ + /* check-magic: 65472 == round((MLDSA_Q-1)/128) */ + mld_assert(*a1 >= 0 && *a1 <= 65472); + +#if MLD_CONFIG_PARAMETER_SET == 44 + /* check-magic: 1488 == 2 * intdiv(intdiv(MLDSA_Q - 1, 88), 128) */ + /* check-magic: 11275 == floor(2**24 / 1488) */ + /* check-magic: 1560281088 == 1 / (1 / 1488 - 11275 / 2**24) */ + /* + * Compute f1 = round-(f1' / B) β‰ˆ round(f1' * 11275 / 2^24). This is exact for + * 0 <= f1' < 2^16. + * + * To see this, consider the (signed) error f1' * (1 / B - 11275 / 2^24) + * between f1' / B and the (under-)approximation f1' * 11275 / 2^24. Because + * eps := 1 / B - 11275 / 2^24 is 1 / 1560281088 β‰ˆ 2^(-30.54) < 2^(-30), we + * have 0 <= f1' * eps < 2^16 * 2^(-30) = 1 / 2^14 < 1 / 2^11 < 1 / B (note + * that f1' is non-negative). + * + * On the other hand, 1 / B is the spacing between the integral multiples + * of 1 / B, which includes all rounding boundaries n + 0.5 (since B is even). + * Hence, if f1' / B is not of the form n + 0.5, then it is at least 1 / B + * away from the nearest rounding boundary, so moving from f1' / B to + * f1' * 11275 / 2^24 does not affect the rounding result, no matter the type + * of rounding used in either side. In particular, we have round-(f1' / B) = + * round(f1' * 11275 / 2^24) as claimed. + * + * As for the remaining case where f1' / B _is_ of the form n + 0.5, because + * f1' * 11275 / 2^24 is slightly but strictly below f1' / B = n + 0.5 (note + * that f1' and thus the error f1' * eps cannot be 0 here), it is always + * rounded down to n. More precisely, we have round-(f1' / B) = + * round(f1' * 11275 / 2^24), where the round-down on the LHS is essential, + * and on the RHS the type of rounding again does not matter. This concludes + * the proof. + * + * See proofs/isabelle/compress for a formalization of the above argument. + */ + *a1 = (*a1 * 11275 + ((int32_t)1 << 23)) >> 24; + mld_assert(*a1 >= 0 && *a1 <= 44); + + *a1 = mld_ct_sel_int32(0, *a1, mld_ct_cmask_neg_i32(43 - *a1)); + mld_assert(*a1 >= 0 && *a1 <= 43); +#else /* MLD_CONFIG_PARAMETER_SET == 44 */ + /* check-magic: 4092 == 2 * intdiv(intdiv(MLDSA_Q - 1, 32), 128) */ + /* check-magic: 1025 == floor(2**22 / 4092) */ + /* check-magic: 4290772992 == 1 / (1 / 4092 - 1025 / 2**22) */ + /* + * Compute f1 = round-(f1' / B) β‰ˆ round(f1' * 1025 / 2^22). This is exact for + * 0 <= f1' < 2^16. Following the same argument above, it suffices to show + * that f1' * eps < 1 / B, where eps := 1 / B - 1025 / 2^22. Indeed, we have + * eps = 1 / 4290772992 β‰ˆ 2^(-31.99) < 2^(-31), therefore f1' * eps < + * 2^16 * 2^(-31) = 1 / 2^15 < 1 / 2^12 < 1 / B. + */ + *a1 = (*a1 * 1025 + ((int32_t)1 << 21)) >> 22; + mld_assert(*a1 >= 0 && *a1 <= 16); + + *a1 &= 15; + mld_assert(*a1 >= 0 && *a1 <= 15); + +#endif /* MLD_CONFIG_PARAMETER_SET != 44 */ + + *a0 = a - *a1 * 2 * MLDSA_GAMMA2; + *a0 = mld_ct_sel_int32(*a0 - MLDSA_Q, *a0, + mld_ct_cmask_neg_i32((MLDSA_Q - 1) / 2 - *a0)); +} + +/** + * Decide a single hint bit from the low part a0 and high part a1 of a + * coefficient: return 1 unless a0 lies in the range (-GAMMA2, GAMMA2] that + * LowBits would produce, with the boundary value -GAMMA2 also admitted when + * a1 == 0 (the Decompose border case). + * + * @note This is not a line-for-line implementation of FIPS 204's MakeHint(z, r) + * (@[FIPS204, Algorithm 39, MakeHint]), which takes two ring elements and + * returns [[HighBits(r) != HighBits(r + z)]]. Instead, it takes the already + * decomposed low/high parts (a0, a1) of a coefficient and decides the hint bit + * from them directly. As explained in the block comment of + * mld_attempt_signature_generation (sign.c), for the specific values that arise + * during signing -- a0 = w0 - cs2 + ct0 and a1 = w1 = HighBits(w) -- this is + * equivalent to the spec's MakeHint(-ct0, w - cs2 + ct0) coefficient-wise. + * Because it consumes (a0, a1) rather than (z, r), it relies on the caller + * having computed a compatible decomposition. + * + * @param a0 Low bits of input element. + * @param a1 High bits of input element. + * + * @return 1 if overflow, 0 otherwise. + */ +MLD_MUST_CHECK_RETURN_VALUE +static MLD_INLINE unsigned int mld_make_hint(int32_t a0, int32_t a1) +__contract__( + ensures(return_value >= 0 && return_value <= 1) +) +{ + if (a0 > MLDSA_GAMMA2 || a0 < -MLDSA_GAMMA2 || + (a0 == -MLDSA_GAMMA2 && a1 != 0)) + { + return 1; + } + + return 0; +} + +/** + * Correct high bits according to hint. + * + * @spec{Implements @[FIPS204, Algorithm 40, UseHint].} + * + * @param a Input element. + * @param hint Hint bit. + * + * @return Corrected high bits. + */ +MLD_MUST_CHECK_RETURN_VALUE +static MLD_INLINE int32_t mld_use_hint(int32_t a, int32_t hint) +__contract__( + requires(hint >= 0 && hint <= 1) + requires(a >= 0 && a < MLDSA_Q) + ensures(return_value >= 0 && return_value < (MLDSA_Q-1)/(2*MLDSA_GAMMA2)) +) +{ + int32_t a0, a1; + + mld_decompose(&a0, &a1, a); + if (hint == 0) + { + return a1; + } + +#if MLD_CONFIG_PARAMETER_SET == 44 + if (a0 > 0) + { + return (a1 == 43) ? 0 : a1 + 1; + } + else + { + return (a1 == 0) ? 43 : a1 - 1; + } +#else /* MLD_CONFIG_PARAMETER_SET == 44 */ + if (a0 > 0) + { + return (a1 + 1) & 15; + } + else + { + return (a1 - 1) & 15; + } +#endif /* MLD_CONFIG_PARAMETER_SET != 44 */ +} + + +#endif /* !MLD_ROUNDING_H */ diff --git a/mldsa_native/src/sign.c b/mldsa_native/src/sign.c new file mode 100644 index 0000000..0b08e5f --- /dev/null +++ b/mldsa_native/src/sign.c @@ -0,0 +1,1670 @@ +/* + * Copyright (c) The mldsa-native project authors + * SPDX-License-Identifier: Apache-2.0 OR ISC OR MIT + */ + +/* References + * ========== + * + * - [FIPS140_3_IG] + * Implementation Guidance for FIPS 140-3 and the Cryptographic Module + * Validation Program + * National Institute of Standards and Technology + * https://csrc.nist.gov/projects/cryptographic-module-validation-program/fips-140-3-ig-announcements + * + * - [FIPS204] + * FIPS 204 Module-Lattice-Based Digital Signature Standard + * National Institute of Standards and Technology + * https://csrc.nist.gov/pubs/fips/204/final + * + * - [Round3_Spec] + * CRYSTALS-Dilithium Algorithm Specifications and Supporting Documentation + * (Version 3.1) + * Bai, Ducas, Kiltz, Lepoint, Lyubashevsky, Schwabe, Seiler, StehlΓ© + * https://pq-crystals.org/dilithium/data/dilithium-specification-round3-20210208.pdf + */ + +#include "sign.h" + +#include "cbmc.h" +#include "ct.h" +#include "debug.h" +#include "packing.h" +#include "poly.h" +#include "poly_kl.h" +#include "polyvec.h" +#include "randombytes.h" +#include "symmetric.h" + +/* Parameter set namespacing + * This is to facilitate building multiple instances + * of mldsa-native (e.g. with varying parameter sets) + * within a single compilation unit. */ +#define mld_check_pct MLD_ADD_PARAM_SET(mld_check_pct) MLD_CONTEXT_PARAMETERS_2 +#define mld_sample_s1_s2 MLD_ADD_PARAM_SET(mld_sample_s1_s2) +#define mld_validate_hash_length MLD_ADD_PARAM_SET(mld_validate_hash_length) +#define mld_get_hash_oid MLD_ADD_PARAM_SET(mld_get_hash_oid) +#define mld_H MLD_ADD_PARAM_SET(mld_H) +#define mld_compute_pack_z MLD_ADD_PARAM_SET(mld_compute_pack_z) +#define mld_attempt_signature_generation \ + MLD_ADD_PARAM_SET(mld_attempt_signature_generation) MLD_CONTEXT_PARAMETERS_8 +#define mld_compute_pack_t0_t1 \ + MLD_ADD_PARAM_SET(mld_compute_pack_t0_t1) MLD_CONTEXT_PARAMETERS_5 +#define mld_get_max_signing_attempts \ + MLD_ADD_PARAM_SET(mld_get_max_signing_attempts) + +#if !defined(MLD_CONFIG_NO_KEYPAIR_API) +static int mld_check_pct(uint8_t const pk[MLDSA_CRYPTO_PUBLICKEYBYTES], + uint8_t const sk[MLDSA_CRYPTO_SECRETKEYBYTES], + MLD_CONFIG_CONTEXT_PARAMETER_TYPE context) +__contract__( + requires(memory_no_alias(pk, MLDSA_CRYPTO_PUBLICKEYBYTES)) + requires(memory_no_alias(sk, MLDSA_CRYPTO_SECRETKEYBYTES)) + ensures(return_value == 0 || MLD_ANY_ERROR(return_value)) +); + +#if defined(MLD_CONFIG_KEYGEN_PCT) +/** + * Pair-wise Consistency Test (PCT) for DSA keypairs. + * + * @[FIPS140_3_IG] TE10.35.02 + * (https://csrc.nist.gov/csrc/media/Projects/cryptographic-module-validation-program/documents/fips%20140-3/FIPS%20140-3%20IG.pdf). + * + * Validates that a generated public/private key pair can correctly sign and + * verify data. Performs signature generation using the private key (sk), + * followed by signature verification using the public key (pk). + * + * @note @[FIPS204] requires that public/private key pairs are to be used + * only for the calculation and/or verification of digital signatures. + * + * @param[in] pk Public key. + * @param[in] sk Secret key. + * @param context Application context. Only present when + * MLD_CONFIG_CONTEXT_PARAMETER is defined; type set by + * MLD_CONFIG_CONTEXT_PARAMETER_TYPE. + * + * @return 0 if the signature was successfully verified, non-zero otherwise. + */ +static int mld_check_pct(uint8_t const pk[MLDSA_CRYPTO_PUBLICKEYBYTES], + uint8_t const sk[MLDSA_CRYPTO_SECRETKEYBYTES], + MLD_CONFIG_CONTEXT_PARAMETER_TYPE context) +{ + MLD_ALIGN uint8_t message[1] = {0}; + size_t siglen; + int ret; + MLD_ALLOC(signature, uint8_t, MLDSA_CRYPTO_BYTES, context); + MLD_ALLOC(pk_test, uint8_t, MLDSA_CRYPTO_PUBLICKEYBYTES, context); + + if (signature == NULL || pk_test == NULL) + { + ret = MLD_ERR_OUT_OF_MEMORY; + goto cleanup; + } + + /* Copy public key for testing */ + mld_memcpy(pk_test, pk, MLDSA_CRYPTO_PUBLICKEYBYTES); + + /* Sign a test message using the original secret key */ + ret = mld_sign_signature(signature, &siglen, message, sizeof(message), NULL, + 0, sk, context); + if (ret != 0) + { + goto cleanup; + } + +#if defined(MLD_CONFIG_KEYGEN_PCT_BREAKAGE_TEST) + /* Deliberately break public key for testing purposes */ + if (mld_break_pct()) + { + pk_test[0] = ~pk_test[0]; + } +#endif /* MLD_CONFIG_KEYGEN_PCT_BREAKAGE_TEST */ + + /* Verify the signature using the (potentially corrupted) public key */ + ret = mld_sign_verify(signature, siglen, message, sizeof(message), NULL, 0, + pk_test, context); + +cleanup: + /* @[FIPS204, Section 3.6.3] Destruction of intermediate values. */ + MLD_FREE(pk_test, uint8_t, MLDSA_CRYPTO_PUBLICKEYBYTES, context); + MLD_FREE(signature, uint8_t, MLDSA_CRYPTO_BYTES, context); + + return ret; +} +#else /* MLD_CONFIG_KEYGEN_PCT */ +static int mld_check_pct(uint8_t const pk[MLDSA_CRYPTO_PUBLICKEYBYTES], + uint8_t const sk[MLDSA_CRYPTO_SECRETKEYBYTES], + MLD_CONFIG_CONTEXT_PARAMETER_TYPE context) +{ + /* Skip PCT */ + ((void)pk); + ((void)sk); +#if defined(MLD_CONFIG_CONTEXT_PARAMETER) + ((void)context); +#endif + return 0; +} +#endif /* !MLD_CONFIG_KEYGEN_PCT */ + +/** + * Sample the short secret vectors s1 (length MLDSA_L) and s2 (length MLDSA_K) + * with coefficients in [-MLDSA_ETA, MLDSA_ETA] from the seed. + * + * @spec{Implements @[FIPS204, Algorithm 33, ExpandS].} + * + * @param[out] s1 Output vector s1. + * @param[out] s2 Output vector s2. + * @param[in] seed Byte array with seed of length MLDSA_CRHBYTES. + */ +static void mld_sample_s1_s2(mld_polyvecl *s1, mld_polyveck *s2, + const uint8_t seed[MLDSA_CRHBYTES]) +__contract__( + requires(memory_no_alias(s1, sizeof(mld_polyvecl))) + requires(memory_no_alias(s2, sizeof(mld_polyveck))) + requires(memory_no_alias(seed, MLDSA_CRHBYTES)) + assigns(object_whole(s1), object_whole(s2)) + ensures(forall(l0, 0, MLDSA_L, array_abs_bound(s1->vec[l0].coeffs, 0, MLDSA_N, MLDSA_ETA + 1))) + ensures(forall(k0, 0, MLDSA_K, array_abs_bound(s2->vec[k0].coeffs, 0, MLDSA_N, MLDSA_ETA + 1))) +) +{ +/* Sample short vectors s1 and s2 */ +#if defined(MLD_CONFIG_SERIAL_FIPS202_ONLY) + int i; + uint16_t nonce = 0; + /* Safety: The nonces are at most 14 (MLDSA_L + MLDSA_K - 1), and, hence, the + * casts are safe. */ + for (i = 0; i < MLDSA_L; i++) + { + mld_poly_uniform_eta(&s1->vec[i], seed, (uint8_t)(nonce + i)); + } + for (i = 0; i < MLDSA_K; i++) + { + mld_poly_uniform_eta(&s2->vec[i], seed, (uint8_t)(nonce + MLDSA_L + i)); + } +#else /* MLD_CONFIG_SERIAL_FIPS202_ONLY */ +#if MLD_CONFIG_PARAMETER_SET == 44 + mld_poly_uniform_eta_4x(&s1->vec[0], &s1->vec[1], &s1->vec[2], &s1->vec[3], + seed, 0, 1, 2, 3); + mld_poly_uniform_eta_4x(&s2->vec[0], &s2->vec[1], &s2->vec[2], &s2->vec[3], + seed, 4, 5, 6, 7); +#elif MLD_CONFIG_PARAMETER_SET == 65 + mld_poly_uniform_eta_4x(&s1->vec[0], &s1->vec[1], &s1->vec[2], &s1->vec[3], + seed, 0, 1, 2, 3); + mld_poly_uniform_eta_4x(&s1->vec[4], &s2->vec[0], &s2->vec[1], + &s2->vec[2] /* irrelevant */, seed, 4, 5, 6, + 0xFF /* irrelevant */); + mld_poly_uniform_eta_4x(&s2->vec[2], &s2->vec[3], &s2->vec[4], &s2->vec[5], + seed, 7, 8, 9, 10); +#elif MLD_CONFIG_PARAMETER_SET == 87 + mld_poly_uniform_eta_4x(&s1->vec[0], &s1->vec[1], &s1->vec[2], &s1->vec[3], + seed, 0, 1, 2, 3); + mld_poly_uniform_eta_4x(&s1->vec[4], &s1->vec[5], &s1->vec[6], + &s2->vec[0] /* irrelevant */, seed, 4, 5, 6, + 0xFF /* irrelevant */); + mld_poly_uniform_eta_4x(&s2->vec[0], &s2->vec[1], &s2->vec[2], &s2->vec[3], + seed, 7, 8, 9, 10); + mld_poly_uniform_eta_4x(&s2->vec[4], &s2->vec[5], &s2->vec[6], &s2->vec[7], + seed, 11, 12, 13, 14); +#endif /* MLD_CONFIG_PARAMETER_SET == 87 */ +#endif /* !MLD_CONFIG_SERIAL_FIPS202_ONLY */ +} + +/** + * Compute t = A*s1hat + s2 row by row, decompose each row into t0[k] and + * t1[k] via power2round, and bit-pack t1[k] into pk_t1 and t0[k] into the + * t0_packed buffer. Used by both keygen and pk_from_sk. + * + * @spec{Partially implements @[FIPS204, Algorithm 22, pkEncode] (t1) and + * @[FIPS204, Algorithm 24, skEncode] (t0).} + * + * @param[out] pk_t1 Output buffer for packed t1 (size + * MLDSA_K * MLDSA_POLYT1_PACKEDBYTES; i.e. the t1 + * region of pk). + * @param[out] t0_packed Output buffer for packed t0 (size + * MLDSA_K * MLDSA_POLYT0_PACKEDBYTES). + * @param[in] s1hat s1 in NTT domain. + * @param[in] s2 s2. + * @param[in] rho Byte array containing seed rho. + * @param context Application context. Only present when + * MLD_CONFIG_CONTEXT_PARAMETER is defined; type set by + * MLD_CONFIG_CONTEXT_PARAMETER_TYPE. + * + * @return - 0: Success. + * - MLD_ERR_OUT_OF_MEMORY: If MLD_CONFIG_CUSTOM_ALLOC_FREE is used and + * an allocation via MLD_CUSTOM_ALLOC returned NULL. + */ +MLD_MUST_CHECK_RETURN_VALUE +static int mld_compute_pack_t0_t1( + uint8_t pk_t1[MLDSA_K * MLDSA_POLYT1_PACKEDBYTES], + uint8_t t0_packed[MLDSA_K * MLDSA_POLYT0_PACKEDBYTES], + const mld_polyvecl *s1hat, const mld_polyveck *s2, + const uint8_t rho[MLDSA_SEEDBYTES], + MLD_CONFIG_CONTEXT_PARAMETER_TYPE context) +__contract__( + requires(memory_no_alias(pk_t1, MLDSA_K * MLDSA_POLYT1_PACKEDBYTES)) + requires(memory_no_alias(t0_packed, MLDSA_K * MLDSA_POLYT0_PACKEDBYTES)) + requires(memory_no_alias(s1hat, sizeof(mld_polyvecl))) + requires(memory_no_alias(s2, sizeof(mld_polyveck))) + requires(memory_no_alias(rho, MLDSA_SEEDBYTES)) + requires(forall(l1, 0, MLDSA_L, + array_abs_bound(s1hat->vec[l1].coeffs, 0, MLDSA_N, MLD_NTT_BOUND))) + requires(forall(k2, 0, MLDSA_K, + array_bound(s2->vec[k2].coeffs, 0, MLDSA_N, + MLD_POLYETA_UNPACK_LOWER_BOUND, MLDSA_ETA + 1))) + assigns(memory_slice(pk_t1, MLDSA_K * MLDSA_POLYT1_PACKEDBYTES)) + assigns(memory_slice(t0_packed, MLDSA_K * MLDSA_POLYT0_PACKEDBYTES)) + ensures(return_value == 0 || return_value == MLD_ERR_OUT_OF_MEMORY)) +{ + unsigned int k; + int ret; + MLD_ALLOC(mat, mld_polymat, 1, context); + MLD_ALLOC(t0k, mld_poly, 1, context); + MLD_ALLOC(t1k, mld_poly, 1, context); + + if (mat == NULL || t0k == NULL || t1k == NULL) + { + ret = MLD_ERR_OUT_OF_MEMORY; + goto cleanup; + } + + /* Expand matrix */ + mld_polyvec_matrix_expand(mat, rho); + + for (k = 0; k < MLDSA_K; k++) + __loop__( + assigns(k, memory_slice(pk_t1, MLDSA_K * MLDSA_POLYT1_PACKEDBYTES), + memory_slice(t0_packed, MLDSA_K * MLDSA_POLYT0_PACKEDBYTES), + memory_slice(t0k, sizeof(mld_poly)), + memory_slice(t1k, sizeof(mld_poly)) + MLD_IF_REDUCE_RAM(, memory_slice(mat, sizeof(mld_polymat)))) + invariant(k <= MLDSA_K) + decreases(MLDSA_K - k) + ) + { + /* t0k = (A * s1hat)_k in NTT domain */ + mld_polyvec_matrix_pointwise_montgomery_row(t0k, mat, s1hat, k); + + /* t0k = invNTT(t0k) */ + mld_poly_invntt_tomont(t0k); + + /* t0k += s2[k] */ + mld_poly_add(t0k, &s2->vec[k]); + + /* Reference: The following reduction is not present in the reference + * implementation. Omitting this reduction requires the output + * of the invntt to be small enough such that the addition of + * s2 does not result in absolute values >= MLDSA_Q. While our + * C, x86_64, and AArch64 invntt implementations produce small + * enough values for this to work out, it complicates the + * bounds reasoning. We instead add an additional reduction, + * and can consequently, relax the bounds requirements for the + * invntt. + */ + mld_poly_reduce(t0k); + + /* Decompose into t1[k] and t0[k] (in place into t0k). */ + mld_poly_caddq(t0k); + mld_poly_power2round(t1k, t0k, t0k); + + /* Pack t1[k] into pk and t0[k] into the t0 output buffer. */ + mld_polyt1_pack(pk_t1 + k * MLDSA_POLYT1_PACKEDBYTES, t1k); + mld_polyt0_pack(t0_packed + k * MLDSA_POLYT0_PACKEDBYTES, t0k); + } + + ret = 0; +cleanup: + /* @[FIPS204, Section 3.6.3] Destruction of intermediate values. */ + MLD_FREE(t1k, mld_poly, 1, context); + MLD_FREE(t0k, mld_poly, 1, context); + MLD_FREE(mat, mld_polymat, 1, context); + return ret; +} + +MLD_MUST_CHECK_RETURN_VALUE +MLD_EXTERNAL_API +int mld_sign_keypair_internal(uint8_t pk[MLDSA_CRYPTO_PUBLICKEYBYTES], + uint8_t sk[MLDSA_CRYPTO_SECRETKEYBYTES], + const uint8_t seed[MLDSA_SEEDBYTES], + MLD_CONFIG_CONTEXT_PARAMETER_TYPE context) +{ + int ret; + const uint8_t *rho, *rhoprime, *key; + + MLD_ALLOC(seedbuf, uint8_t, 2 * MLDSA_SEEDBYTES + MLDSA_CRHBYTES, context); + MLD_ALLOC(inbuf, uint8_t, MLDSA_SEEDBYTES + 2, context); + MLD_ALLOC(tr, uint8_t, MLDSA_TRBYTES, context); + MLD_ALLOC(s1, mld_polyvecl, 1, context); + MLD_ALLOC(s2, mld_polyveck, 1, context); + + if (seedbuf == NULL || inbuf == NULL || tr == NULL || s1 == NULL || + s2 == NULL) + { + ret = MLD_ERR_OUT_OF_MEMORY; + goto cleanup; + } + + /* Get randomness for rho, rhoprime and key */ + mld_memcpy(inbuf, seed, MLDSA_SEEDBYTES); + inbuf[MLDSA_SEEDBYTES + 0] = MLDSA_K; + inbuf[MLDSA_SEEDBYTES + 1] = MLDSA_L; + mld_shake256(seedbuf, 2 * MLDSA_SEEDBYTES + MLDSA_CRHBYTES, inbuf, + MLDSA_SEEDBYTES + 2); + rho = seedbuf; + rhoprime = rho + MLDSA_SEEDBYTES; + key = rhoprime + MLDSA_CRHBYTES; + + /* Constant time: rho is part of the public key and, hence, public. */ + MLD_CT_TESTING_DECLASSIFY(rho, MLDSA_SEEDBYTES); + + /* Sample s1 and s2 */ + mld_sample_s1_s2(s1, s2, rhoprime); + + /* Pack s1 into sk before NTT */ + mld_pack_sk_s1(sk, s1); + + /* NTT s1 in place to use as s1hat */ + mld_polyvecl_ntt(s1); + + /* Pack rho into pk */ + mld_memcpy(pk + MLDSA_PK_RHO_OFFSET, rho, MLDSA_SEEDBYTES); + + /* Compute t = A*s1hat + s2 row by row, decompose into t1/t0, and pack + * t1 into pk and t0 directly into the t0 region of sk. */ + ret = mld_compute_pack_t0_t1(pk + MLDSA_PK_T1_OFFSET, sk + MLDSA_SK_T0_OFFSET, + s1, s2, rho, context); + if (ret != 0) + { + goto cleanup; + } + + /* Compute tr = H(pk) */ + mld_shake256(tr, MLDSA_TRBYTES, pk, MLDSA_CRYPTO_PUBLICKEYBYTES); + + /* Pack remaining secret key components (s1 and t0 already packed) */ + mld_pack_sk_rho_key_tr_s2(sk, rho, tr, key, s2); + + /* Constant time: pk is the public key, inherently public data */ + MLD_CT_TESTING_DECLASSIFY(pk, MLDSA_CRYPTO_PUBLICKEYBYTES); + +cleanup: + /* @[FIPS204, Section 3.6.3] Destruction of intermediate values. */ + MLD_FREE(s2, mld_polyveck, 1, context); + MLD_FREE(s1, mld_polyvecl, 1, context); + MLD_FREE(tr, uint8_t, MLDSA_TRBYTES, context); + MLD_FREE(inbuf, uint8_t, MLDSA_SEEDBYTES + 2, context); + MLD_FREE(seedbuf, uint8_t, 2 * MLDSA_SEEDBYTES + MLDSA_CRHBYTES, context); + + if (ret != 0) + { + return ret; + } + + /* Pairwise Consistency Test (PCT) @[FIPS140_3_IG, p.87] */ + /* Do this after freeing all temporaries. */ + return mld_check_pct(pk, sk, context); +} + +#if !defined(MLD_CONFIG_CORE_API_ONLY) +#if !defined(MLD_CONFIG_NO_RANDOMIZED_API) +MLD_MUST_CHECK_RETURN_VALUE +MLD_EXTERNAL_API +int mld_sign_keypair(uint8_t pk[MLDSA_CRYPTO_PUBLICKEYBYTES], + uint8_t sk[MLDSA_CRYPTO_SECRETKEYBYTES], + MLD_CONFIG_CONTEXT_PARAMETER_TYPE context) +{ + int ret; + MLD_ALLOC(seed, uint8_t, MLDSA_SEEDBYTES, context); + + if (seed == NULL) + { + ret = MLD_ERR_OUT_OF_MEMORY; + goto cleanup; + } + + if (mld_randombytes(seed, MLDSA_SEEDBYTES) != 0) + { + ret = MLD_ERR_RNG_FAIL; + goto cleanup; + } + MLD_CT_TESTING_SECRET(seed, MLDSA_SEEDBYTES); + ret = mld_sign_keypair_internal(pk, sk, seed, context); + +cleanup: + /* @[FIPS204, Section 3.6.3] Destruction of intermediate values. */ + MLD_FREE(seed, uint8_t, MLDSA_SEEDBYTES, context); + return ret; +} +#endif /* !MLD_CONFIG_NO_RANDOMIZED_API */ +#endif /* !MLD_CONFIG_CORE_API_ONLY */ +#endif /* !MLD_CONFIG_NO_KEYPAIR_API */ + +#if !defined(MLD_CONFIG_NO_SIGN_API) || !defined(MLD_CONFIG_NO_VERIFY_API) +/** + * Abstracts application of SHAKE256 to one, two or three blocks of data, + * yielding a user-requested size of output. + * + * @param[out] out Pointer to output. + * @param outlen Requested output length in bytes. + * @param[in] in1 Pointer to input block 1. Must NOT be NULL. + * @param in1len Length of input in1 in bytes. + * @param[in] in2 Pointer to input block 2. May be NULL if in2len == 0, + * in which case this block is ignored. + * @param in2len Length of input in2 in bytes. + * @param[in] in3 Pointer to input block 3. May be NULL if in3len == 0, + * in which case this block is ignored. + * @param in3len Length of input in3 in bytes. + */ +static void mld_H(uint8_t *out, size_t outlen, const uint8_t *in1, + size_t in1len, const uint8_t *in2, size_t in2len, + const uint8_t *in3, size_t in3len) +__contract__( + requires(in1len <= MLD_MAX_BUFFER_SIZE) + requires(in2len <= MLD_MAX_BUFFER_SIZE) + requires(in3len <= MLD_MAX_BUFFER_SIZE) + requires(outlen <= 8 * SHAKE256_RATE /* somewhat arbitrary bound */) + requires(memory_no_alias(in1, in1len)) + requires(in2len == 0 || memory_no_alias(in2, in2len)) + requires(in3len == 0 || memory_no_alias(in3, in3len)) + requires(memory_no_alias(out, outlen)) + assigns(memory_slice(out, outlen)) +) +{ + mld_shake256ctx state; + mld_shake256_init(&state); + mld_shake256_absorb(&state, in1, in1len); + if (in2len != 0) + { + mld_shake256_absorb(&state, in2, in2len); + } + if (in3len != 0) + { + mld_shake256_absorb(&state, in3, in3len); + } + mld_shake256_finalize(&state); + mld_shake256_squeeze(out, outlen, &state); + mld_shake256_release(&state); + + /* @[FIPS204, Section 3.6.3] Destruction of intermediate values. */ + mld_zeroize(&state, sizeof(state)); +} +#endif /* !MLD_CONFIG_NO_SIGN_API || !MLD_CONFIG_NO_VERIFY_API */ + +#if !defined(MLD_CONFIG_NO_SIGN_API) +/* Sampling y from counter kappa uses nonces kappa, ..., kappa+L-1, which fit in + * uint16_t iff kappa <= UINT16_MAX - MLDSA_L. */ +#define MLD_MAX_KAPPA (UINT16_MAX - MLDSA_L) + +/* With kappa = attempt*MLDSA_L, the kappa bound becomes a bound on attempts. + * Bounding attempts (the reference loops indefinitely) gives predictable + * termination and provable type-safety. */ +#define MLD_MAX_SIGNING_ATTEMPTS (MLD_MAX_KAPPA / MLDSA_L) + +/** + * Compute z = y + s1*c, check that z has coefficients smaller than + * MLDSA_GAMMA1 - MLDSA_BETA, and pack z into the signature buffer. + * + * @reference{This function is inlined into mld_sign_signature in the + * reference implementation.} + * + * @param[in,out] sig Output signature. + * @param[in] cp Challenge polynomial. + * @param[in] s1hat Secret vector s1 in NTT domain. + * @param[in] y Masking vector y (or seed in REDUCE_RAM mode). + * @param[out] z Scratch polynomial for z computation. + * @param[out] tmp Scratch polynomial. + * + * @return - 0: Success (z has coefficients smaller than + * MLDSA_GAMMA1 - MLDSA_BETA). + * - MLD_ERR_FAIL: z rejected (norm check failed). + * - MLD_ERR_OUT_OF_MEMORY: If MLD_CONFIG_CUSTOM_ALLOC_FREE is used and + * an allocation via MLD_CUSTOM_ALLOC returned NULL. + */ +MLD_MUST_CHECK_RETURN_VALUE +static int mld_compute_pack_z(uint8_t sig[MLDSA_CRYPTO_BYTES], + const mld_poly *cp, const mld_sk_s1hat *s1hat, + const mld_yvec *y, mld_poly *z, mld_poly *tmp) +__contract__( + requires(memory_no_alias(sig, MLDSA_CRYPTO_BYTES)) + requires(memory_no_alias(cp, sizeof(mld_poly))) + requires(memory_no_alias(s1hat, sizeof(mld_sk_s1hat))) + requires(memory_no_alias(y, sizeof(mld_yvec))) + requires(memory_no_alias(z, sizeof(mld_poly))) + requires(memory_no_alias(tmp, sizeof(mld_poly))) + requires(array_abs_bound(cp->coeffs, 0, MLDSA_N, MLD_NTT_BOUND)) + MLD_IF_NOT_REDUCE_RAM( + requires(forall(k0, 0, MLDSA_L, + array_bound(y->vec.vec[k0].coeffs, 0, MLDSA_N, -(MLDSA_GAMMA1 - 1), MLDSA_GAMMA1 + 1))) + requires(forall(k1, 0, MLDSA_L, array_abs_bound(s1hat->vec.vec[k1].coeffs, 0, MLDSA_N, MLD_NTT_BOUND))) + ) + MLD_IF_REDUCE_RAM( + requires(memory_no_alias(s1hat->packed, MLDSA_L * MLDSA_POLYETA_PACKEDBYTES)) + requires(memory_no_alias(y->rhoprime, MLDSA_CRHBYTES)) + requires(y->kappa <= MLD_MAX_KAPPA) + ) + assigns(memory_slice(sig, MLDSA_CRYPTO_BYTES)) + assigns(memory_slice(z, sizeof(mld_poly))) + assigns(memory_slice(tmp, sizeof(mld_poly))) + ensures(return_value == 0 || return_value == MLD_ERR_FAIL || + return_value == MLD_ERR_OUT_OF_MEMORY) +) +{ + unsigned int i; + uint32_t z_invalid; + for (i = 0; i < MLDSA_L; i++) + __loop__( + assigns(i, memory_slice(z, sizeof(mld_poly)), + memory_slice(tmp, sizeof(mld_poly)), + memory_slice(sig, MLDSA_CRYPTO_BYTES)) + invariant(i <= MLDSA_L) + decreases(MLDSA_L - i) + ) + { + mld_sk_s1hat_get_poly(z, s1hat, i); + mld_poly_pointwise_montgomery(z, cp); + mld_poly_invntt_tomont(z); + mld_yvec_get_poly(tmp, y, i); + mld_poly_add(z, tmp); + mld_poly_reduce(z); + + z_invalid = mld_poly_chknorm(z, MLDSA_GAMMA1 - MLDSA_BETA); + /* Constant time: It is fine (and prohibitively expensive to avoid) + * to leak the result of the norm check and which polynomial in z caused a + * rejection. It would even be okay to leak which coefficient led to + * rejection as the candidate signature will be discarded anyway. + * See Section 5.5 of @[Round3_Spec]. */ + MLD_CT_TESTING_DECLASSIFY(&z_invalid, sizeof(uint32_t)); + if (z_invalid) + { + return MLD_ERR_FAIL; /* reject */ + } + /* If z is valid, then its coefficients are bounded by + * MLDSA_GAMMA1 - MLDSA_BETA. This will be needed below + * to prove the pre-condition of pack_sig_z() */ + mld_assert_abs_bound(z, MLDSA_N, (MLDSA_GAMMA1 - MLDSA_BETA)); + + /* After the norm check, the distribution of each coefficient of z is + * independent of the secret key and it can, hence, be considered + * public. It is, hence, okay to immediately pack it into the user-provided + * signature buffer. */ + mld_pack_sig_z(sig, z, i); + } + return 0; +} + +/* User-facing bound on signing attempts. See MLD_CONFIG_MAX_SIGNING_ATTEMPTS + * in mldsa_native_config.h. Default is chosen so that failure probability + * is < 2^{-256}, that is, signatures will practically always succeed. */ +#if defined(MLD_CONFIG_MAX_SIGNING_ATTEMPTS) + +#if !defined(MLD_ALLOW_NONCOMPLIANT_SIGNING_BOUND) && \ + MLD_CONFIG_MAX_SIGNING_ATTEMPTS < 814 +#error Bad configuration: MLD_CONFIG_MAX_SIGNING_ATTEMPTS must be >= 814 for FIPS 204 compliance @[FIPS204, Appendix C] +#endif + +#if MLD_CONFIG_MAX_SIGNING_ATTEMPTS < 1 +#error Bad configuration: MLD_CONFIG_MAX_SIGNING_ATTEMPTS must be >= 1 +#endif + +#if MLD_CONFIG_MAX_SIGNING_ATTEMPTS > MLD_MAX_SIGNING_ATTEMPTS +#error Bad configuration: MLD_CONFIG_MAX_SIGNING_ATTEMPTS exceeds the maximum allowed value. +#endif + +#endif /* MLD_CONFIG_MAX_SIGNING_ATTEMPTS */ + +MLD_MUST_CHECK_RETURN_VALUE +static MLD_INLINE uint16_t mld_get_max_signing_attempts(void) +__contract__( + ensures(return_value >= 1) + ensures(return_value <= MLD_MAX_SIGNING_ATTEMPTS) +) +{ + /* cassert(0) ensures CBMC uses the contract rather than inlining the body, + * keeping proofs agnostic of the configured value. */ + cassert(0); +#if defined(MLD_CONFIG_MAX_SIGNING_ATTEMPTS) + return MLD_CONFIG_MAX_SIGNING_ATTEMPTS; +#else + return MLD_MAX_SIGNING_ATTEMPTS; +#endif +} + +/** + * Attempt to generate a single signature: one iteration of the + * ML-DSA.Sign_internal rejection-sampling loop. + * + * @spec{Implements one iteration of the rejection-sampling loop body of + * @[FIPS204, Algorithm 7, ML-DSA.Sign_internal] (lines 11-30) plus, on success, + * the sigEncode step (line 33). The per-signature setup (Algorithm 7 lines 1-7: + * skDecode, NTT of s1/s2/t0, ExpandA, and computation of mu and rhoprime) and + * the loop itself (lines 8-10, 31-32) live in the caller + * mld_sign_signature_internal; kappa is this iteration's counter, used to + * sample y.} + * + * @reference{This code differs from the reference implementation in that it + * factors out the core signature generation step into a distinct function + * here in order to improve efficiency of CBMC proof.} + * + * @param[out] sig Pointer to output signature. + * @param[in] mu Pointer to message or hash of exactly MLDSA_CRHBYTES + * bytes. + * @param[in] rhoprime Pointer to randomness seed. + * @param kappa Counter for this iteration (= attempt*MLDSA_L). + * @param[in] mat Expanded matrix. + * @param[in] s1hat Secret vector s1 in NTT domain. + * @param[in] s2hat Secret vector s2 in NTT domain. + * @param[in] t0hat Vector t0 in NTT domain. + * @param context Application context. Only present when + * MLD_CONFIG_CONTEXT_PARAMETER is defined; type set by + * MLD_CONFIG_CONTEXT_PARAMETER_TYPE. + * + * @return - 0: Signature generation succeeded. + * - MLD_ERR_FAIL: Signature rejected (norm check failed). + * - MLD_ERR_OUT_OF_MEMORY: If MLD_CONFIG_CUSTOM_ALLOC_FREE is used and + * an allocation via MLD_CUSTOM_ALLOC returned NULL. + */ +MLD_MUST_CHECK_RETURN_VALUE +/* NOLINTNEXTLINE(readability-function-cognitive-complexity) */ +static int mld_attempt_signature_generation( + uint8_t sig[MLDSA_CRYPTO_BYTES], const uint8_t *mu, + const uint8_t rhoprime[MLDSA_CRHBYTES], uint16_t kappa, mld_polymat *mat, + const mld_sk_s1hat *s1hat, const mld_sk_s2hat *s2hat, + const mld_sk_t0hat *t0hat, MLD_CONFIG_CONTEXT_PARAMETER_TYPE context) +__contract__( + requires(memory_no_alias(sig, MLDSA_CRYPTO_BYTES)) + requires(memory_no_alias(mu, MLDSA_CRHBYTES)) + requires(memory_no_alias(rhoprime, MLDSA_CRHBYTES)) + requires(memory_no_alias(mat, sizeof(mld_polymat))) + requires(memory_no_alias(s1hat, sizeof(mld_sk_s1hat))) + requires(memory_no_alias(s2hat, sizeof(mld_sk_s2hat))) + requires(memory_no_alias(t0hat, sizeof(mld_sk_t0hat))) + requires(kappa <= MLD_MAX_KAPPA) + MLD_IF_NOT_REDUCE_RAM( + requires(forall(k1, 0, MLDSA_K, forall(l1, 0, MLDSA_L, + array_bound(mat->vec[k1].vec[l1].coeffs, 0, MLDSA_N, 0, MLDSA_Q)))) + requires(forall(k2, 0, MLDSA_K, array_abs_bound(t0hat->vec.vec[k2].coeffs, 0, MLDSA_N, MLD_NTT_BOUND))) + requires(forall(k3, 0, MLDSA_L, array_abs_bound(s1hat->vec.vec[k3].coeffs, 0, MLDSA_N, MLD_NTT_BOUND))) + requires(forall(k4, 0, MLDSA_K, array_abs_bound(s2hat->vec.vec[k4].coeffs, 0, MLDSA_N, MLD_NTT_BOUND))) + ) + MLD_IF_REDUCE_RAM( + requires(memory_no_alias(s1hat->packed, MLDSA_L * MLDSA_POLYETA_PACKEDBYTES)) + requires(memory_no_alias(s2hat->packed, MLDSA_K * MLDSA_POLYETA_PACKEDBYTES)) + requires(memory_no_alias(t0hat->packed, MLDSA_K * MLDSA_POLYT0_PACKEDBYTES)) + ) + assigns(memory_slice(sig, MLDSA_CRYPTO_BYTES)) + MLD_IF_REDUCE_RAM( + assigns(memory_slice(mat, sizeof(mld_polymat))) + ) + ensures(return_value == 0 || return_value == MLD_ERR_FAIL || + return_value == MLD_ERR_OUT_OF_MEMORY) +) +{ + unsigned int k; + uint32_t w0_invalid, h_invalid; + int ret; + + typedef union + { + mld_polyveck w1; + mld_polyvecl tmp; + } w1tmp_u; + mld_polyveck *w1; + mld_polyvecl *tmp; + + MLD_ALLOC(challenge_bytes, uint8_t, MLDSA_CTILDEBYTES, context); + MLD_ALLOC(y, mld_yvec, 1, context); + MLD_ALLOC(z, mld_poly, 1, context); + MLD_ALLOC(w1tmp, w1tmp_u, 1, context); + MLD_ALLOC(w0, mld_polyveck, 1, context); + MLD_ALLOC(cp, mld_poly, 1, context); + MLD_ALLOC(t, mld_poly, 1, context); + + if (challenge_bytes == NULL || y == NULL || z == NULL || w1tmp == NULL || + w0 == NULL || cp == NULL || t == NULL) + { + ret = MLD_ERR_OUT_OF_MEMORY; + goto cleanup; + } + w1 = &w1tmp->w1; + tmp = &w1tmp->tmp; + + /* @[FIPS204, Algorithm 7, line 11] y <- ExpandMask(rhoprime, kappa). */ + mld_yvec_init(y, rhoprime, kappa); + + /* @[FIPS204, Algorithm 7, line 12] w <- invNTT(A_hat o NTT(y)). This call + * performs the whole line: it NTTs y, accumulates the pointwise product with + * A_hat, and applies the inverse NTT. In REDUCE_RAM mode the y sampling is + * fused into the same pass. */ + mld_polyvec_matrix_pointwise_montgomery_yvec(w0, mat, y, tmp); + + /* @[FIPS204, Algorithm 7, line 13] w1 <- HighBits(w), here together with the + * low part: Decompose yields w = 2*GAMMA2*w1 + w0, keeping both w1 and w0 + * (w0 is reused below in the line-21/26 alternative, see further down). */ + mld_polyveck_caddq(w0); + mld_polyveck_decompose(w1, w0); + + /* @[FIPS204, Algorithm 7, line 15] ctilde <- H(mu || w1Encode(w1), lambda/4). + * w1Encode(w1) is packed into the w1 region of sig (mld_polyveck_pack_w1), + * then absorbed by H together with mu. */ + mld_polyveck_pack_w1(sig, w1); + + mld_H(challenge_bytes, MLDSA_CTILDEBYTES, mu, MLDSA_CRHBYTES, sig, + MLDSA_K * MLDSA_POLYW1_PACKEDBYTES, NULL, 0); + /* Constant time: Leaking challenge_bytes does not reveal any information + * about the secret key as H() is modelled as random oracle. + * This also applies to challenges for rejected signatures. + * See Section 5.5 of @[Round3_Spec]. */ + MLD_CT_TESTING_DECLASSIFY(challenge_bytes, MLDSA_CTILDEBYTES); + /* @[FIPS204, Algorithm 7, line 16] c <- SampleInBall(ctilde) and + * @[FIPS204, Algorithm 7, line 17] c_hat <- NTT(c). */ + mld_poly_challenge(cp, challenge_bytes); + mld_poly_ntt(cp); + + /* @[FIPS204, Algorithm 7, lines 18+20] cs1 <- invNTT(c_hat o s1_hat) and + * z <- y + cs1, followed by the line-23 norm check ||z||_inf >= GAMMA1 - + * BETA. mld_compute_pack_z fuses all three per polynomial and, on success, + * packs z into sig; it returns MLD_ERR_FAIL if the norm check rejects z. */ + ret = mld_compute_pack_z(sig, cp, s1hat, y, t, z); + if (ret != 0) + { + goto cleanup; + } + + /* The remaining steps realize @[FIPS204, Algorithm 7, lines 21-28] (the + * low-bits norm check and the hint h) via the faster alternative formulation + * of @[Round3_Spec, Section 5.1]. @[FIPS204] explicitly permits this: the + * note accompanying Algorithm 7 states that the validity checks on z and the + * computation of h may instead be implemented "as described in Section 5.1 of + * [6]", and that reference is @[Round3_Spec, Section 5.1]. + * + * The loop below builds w0 - cs2 + ct0 in place in w0; w1 is unmodified, and + * is HighBits(w) from line 13. Those are the inputs to the streamlined + * computation of MakeHint explained below. + * + * Low-bits norm check: + * @[FIPS204, Algorithm 7, line 21] computes r0 = LowBits(w - cs2) and line + * 23 rejects when ||r0||_inf >= GAMMA2 - BETA. By @[Round3_Spec, Section + * 5.1] (Lemma 3), this line-23 check on r0 = LowBits(w - cs2) is implied by + * ||w0 - cs2||_inf < GAMMA2 - BETA, where w0 is the low part of w. In our + * context, w0 already holds the low part of w from the line-13 Decompose; + * after subtracting cs2 from it in place, the mld_poly_chknorm(w0, GAMMA2 - + * BETA) call below is exactly that check. + * + * Hint: + * @[FIPS204, Algorithm 7, line 26] sets h = MakeHint(-ct0, w - cs2 + ct0), + * and line 28 rejects when ||ct0||_inf >= GAMMA2 or h has more than OMEGA + * nonzero coefficients. @[Round3_Spec, Section 5.1] provides the following + * alternative description for MakeHint(-ct0, w - cs2 + ct0): a hint bit is + * zero exactly when the coefficient of w0 - cs2 + ct0 lies in + * (-GAMMA2, GAMMA2], or equals -GAMMA2 while the matching w1 coefficient is + * zero (the Decompose border case), and is set otherwise. This equivalence + * is precisely what mld_pack_sig_h -> mld_make_hint compute from w0 + * (= w0 - cs2 + ct0) and w1. The line-28 ||ct0||_inf >= GAMMA2 check is the + * mld_poly_chknorm(z, GAMMA2) call on ct0 below; the weight bound is + * enforced by mld_pack_sig_h. + * + * Building w0 per-component and checking norms incrementally also avoids + * allocating a full polyveck for h. */ + for (k = 0; k < MLDSA_K; k++) + __loop__( + assigns(k, + object_whole(z), + object_whole(w0)) + invariant(k <= MLDSA_K) + invariant(forall(k0, k, MLDSA_K, + array_abs_bound(w0->vec[k0].coeffs, 0, MLDSA_N, MLDSA_GAMMA2 + 1))) + decreases(MLDSA_K - k) + ) + { + /* @[FIPS204, Algorithm 7, line 19] cs2[k] <- invNTT(c_hat o s2_hat)[k], + * then subtract from w0[k] to form (w0 - cs2)[k]. */ + mld_sk_s2hat_get_poly(z, s2hat, k); + mld_poly_pointwise_montgomery(z, cp); + mld_poly_invntt_tomont(z); + + mld_poly_sub(&w0->vec[k], z); + mld_poly_reduce(&w0->vec[k]); + + /* Low-bits norm check (see block comment above): the line-23 check on + * r0 = LowBits(w - cs2) holds via ||w0 - cs2||_inf < GAMMA2 - BETA. */ + w0_invalid = mld_poly_chknorm(&w0->vec[k], MLDSA_GAMMA2 - MLDSA_BETA); + /* Constant time: w0_invalid may be leaked - see comment for z_invalid. */ + MLD_CT_TESTING_DECLASSIFY(&w0_invalid, sizeof(uint32_t)); + if (w0_invalid) + { + ret = MLD_ERR_FAIL; /* reject */ + goto cleanup; + } + + /* @[FIPS204, Algorithm 7, line 25] ct0[k] <- invNTT(c_hat o t0_hat)[k]. */ + mld_sk_t0hat_get_poly(z, t0hat, k); + mld_poly_pointwise_montgomery(z, cp); + mld_poly_invntt_tomont(z); + mld_poly_reduce(z); + + /* @[FIPS204, Algorithm 7, line 28] reject when ||ct0||_inf >= GAMMA2 (the + * second part, the OMEGA weight bound, is enforced by mld_pack_sig_h). */ + h_invalid = mld_poly_chknorm(z, MLDSA_GAMMA2); + /* Constant time: h_invalid may be leaked - see comment for z_invalid. */ + MLD_CT_TESTING_DECLASSIFY(&h_invalid, sizeof(uint32_t)); + if (h_invalid) + { + ret = MLD_ERR_FAIL; /* reject */ + goto cleanup; + } + + /* Add ct0[k] to (w0 - cs2)[k], leaving (w0 - cs2 + ct0)[k] in w0[k] -- the + * MakeHint input prepared for mld_pack_sig_h (see block comment above). */ + mld_poly_add(&w0->vec[k], z); + } + + /* Constant time: At this point all norm checks have passed and we, hence, + * know that the signature does not leak any secret information. + * Consequently, any value that can be computed from the signature and public + * key is considered public. + * w0 and w1 are public as they can be computed from Az - ct = \alpha w1 + w0. + * h=c*t0 is public as both c and t0 are considered public. + * While t0 is not part of the public key, it can be reconstructed from + * a small number of signatures and need not be regarded as secret + * (see @[FIPS204, Section 6.1]). + */ + MLD_CT_TESTING_DECLASSIFY(w0, sizeof(*w0)); + MLD_CT_TESTING_DECLASSIFY(w1, sizeof(*w1)); + + /* @[FIPS204, Algorithm 7, line 33] sigEncode(ctilde, z mod+/- q, h) is split + * across three calls: z was already packed by mld_compute_pack_z, this call + * packs ctilde, and mld_pack_sig_h below packs the hint h. */ + mld_pack_sig_c(sig, challenge_bytes); + + /* @[FIPS204, Algorithm 7, line 26] h <- MakeHint(-ct0, w - cs2 + ct0), + * computed from (w0 = w0 - cs2 + ct0, w1) as described in the block comment + * above, and packed as the h component of the line-33 sigEncode. Returns + * MLD_ERR_FAIL if h would exceed OMEGA nonzero coefficients (the remaining + * part of the line-28 check), in which case we reject. */ + ret = mld_pack_sig_h(sig, w0, w1); + if (ret != 0) + { + goto cleanup; + } + + /* Constant time: At this point it is clear that the signature is valid - it + * can, hence, be considered public. */ + MLD_CT_TESTING_DECLASSIFY(sig, MLDSA_CRYPTO_BYTES); + ret = 0; /* success */ + +cleanup: + /* @[FIPS204, Section 3.6.3] Destruction of intermediate values. */ + MLD_FREE(t, mld_poly, 1, context); + MLD_FREE(cp, mld_poly, 1, context); + MLD_FREE(w0, mld_polyveck, 1, context); + MLD_FREE(w1tmp, w1tmp_u, 1, context); + MLD_FREE(z, mld_poly, 1, context); + MLD_FREE(y, mld_yvec, 1, context); + MLD_FREE(challenge_bytes, uint8_t, MLDSA_CTILDEBYTES, context); + + return ret; +} +MLD_MUST_CHECK_RETURN_VALUE +MLD_EXTERNAL_API +int mld_sign_signature_internal(uint8_t sig[MLDSA_CRYPTO_BYTES], size_t *siglen, + const uint8_t *m, size_t mlen, + const uint8_t *pre, size_t prelen, + const uint8_t rnd[MLDSA_RNDBYTES], + const uint8_t sk[MLDSA_CRYPTO_SECRETKEYBYTES], + int externalmu, + MLD_CONFIG_CONTEXT_PARAMETER_TYPE context) +{ + int ret; + uint8_t *rho, *tr, *key, *mu, *rhoprime; + const uint16_t max_signing_attempts = mld_get_max_signing_attempts(); + uint16_t attempt = 0; + MLD_ALLOC(seedbuf, uint8_t, + 2 * MLDSA_SEEDBYTES + MLDSA_TRBYTES + 2 * MLDSA_CRHBYTES, context); + MLD_ALLOC(mat, mld_polymat, 1, context); + MLD_ALLOC(s1hat, mld_sk_s1hat, 1, context); + MLD_ALLOC(t0hat, mld_sk_t0hat, 1, context); + MLD_ALLOC(s2hat, mld_sk_s2hat, 1, context); + + if (seedbuf == NULL || mat == NULL || s1hat == NULL || t0hat == NULL || + s2hat == NULL) + { + ret = MLD_ERR_OUT_OF_MEMORY; + goto cleanup; + } + + rho = seedbuf; + tr = rho + MLDSA_SEEDBYTES; + key = tr + MLDSA_TRBYTES; + mu = key + MLDSA_SEEDBYTES; + rhoprime = mu + MLDSA_CRHBYTES; + /* @[FIPS204, Algorithm 7, line 1] (rho, K, tr, s1, s2, t0) <- skDecode(sk) + * and @[FIPS204, Algorithm 7, lines 2-4] s1_hat/s2_hat/t0_hat <- NTT(...): + * mld_unpack_sk returns s1hat, s2hat, t0hat already in NTT domain. The spec's + * private random seed K is held in the local variable key. */ + mld_unpack_sk(rho, tr, key, t0hat, s1hat, s2hat, sk); + + if (!externalmu) + { + /* @[FIPS204, Algorithm 7, line 6] mu <- H(BytesToBits(tr) || M', 64). */ + mld_H(mu, MLDSA_CRHBYTES, tr, MLDSA_TRBYTES, pre, prelen, m, mlen); + } + else + { + /* mu has been provided directly (external-mu variant; line 6 done by the + * caller in a separate cryptographic module). */ + mld_memcpy(mu, m, MLDSA_CRHBYTES); + } + + /* @[FIPS204, Algorithm 7, line 7] rhoprime <- H(K || rnd || mu, 64). */ + mld_H(rhoprime, MLDSA_CRHBYTES, key, MLDSA_SEEDBYTES, rnd, MLDSA_RNDBYTES, mu, + MLDSA_CRHBYTES); + + /* Constant time: rho is part of the public key and, hence, public. */ + MLD_CT_TESTING_DECLASSIFY(rho, MLDSA_SEEDBYTES); + /* @[FIPS204, Algorithm 7, line 5] A_hat <- ExpandA(rho). */ + mld_polyvec_matrix_expand(mat, rho); + + /* @[FIPS204, Algorithm 7, lines 8-10 and 31-32] the rejection-sampling loop, + * tracked by attempt (kappa = attempt*MLDSA_L). Each iteration's body (lines + * 11-30) plus, on success, the line-33 sigEncode are performed by + * mld_attempt_signature_generation. */ + + /* Reference: the reference loops unboundedly; we instead iterate over the + * bounded range [0, max_signing_attempts) for predictable termination. + * A success or fatal error exits via goto cleanup; running to completion + * means every attempt was rejected; with a FIPS compliant choice of + * MLD_CONFIG_MAX_SIGNING_ATTEMPTS, this should never happen. */ + for (; attempt < max_signing_attempts; attempt++) + __loop__( + MLD_IF_NOT_REDUCE_RAM( + assigns(attempt, ret, object_whole(siglen), memory_slice(sig, MLDSA_CRYPTO_BYTES)) + ) + MLD_IF_REDUCE_RAM( + assigns(attempt, ret, object_whole(siglen), memory_slice(sig, MLDSA_CRYPTO_BYTES), + memory_slice(mat, sizeof(mld_polymat))) + ) + invariant(attempt <= max_signing_attempts) + + /* t0, s1, s2, and mat are initialized above and are NOT changed by this */ + /* loop. We can therefore re-assert their bounds here as part of the */ + /* loop invariant. This makes proof noticeably faster with CBMC */ + MLD_IF_NOT_REDUCE_RAM( + invariant(forall(k1, 0, MLDSA_K, forall(l1, 0, MLDSA_L, + array_bound(mat->vec[k1].vec[l1].coeffs, 0, MLDSA_N, 0, MLDSA_Q)))) + invariant(forall(k2, 0, MLDSA_K, array_abs_bound(t0hat->vec.vec[k2].coeffs, 0, MLDSA_N, MLD_NTT_BOUND))) + invariant(forall(k3, 0, MLDSA_L, array_abs_bound(s1hat->vec.vec[k3].coeffs, 0, MLDSA_N, MLD_NTT_BOUND))) + invariant(forall(k4, 0, MLDSA_K, array_abs_bound(s2hat->vec.vec[k4].coeffs, 0, MLDSA_N, MLD_NTT_BOUND))) + ) + decreases(max_signing_attempts - attempt) + ) + { + /* Safety: attempt < max_signing_attempts <= MLD_MAX_SIGNING_ATTEMPTS, so + * kappa <= MLD_MAX_KAPPA and the cast is safe. */ + const uint16_t kappa = (uint16_t)(attempt * MLDSA_L); + ret = mld_attempt_signature_generation(sig, mu, rhoprime, kappa, mat, s1hat, + s2hat, t0hat, context); + if (ret == 0) + { + *siglen = MLDSA_CRYPTO_BYTES; + goto cleanup; + } + else if (ret != MLD_ERR_FAIL) + { + /* For failures such as out-of-memory, propagate and exit immediately. */ + goto cleanup; + } + + /* Otherwise, the signature was rejected; try the next attempt. */ + } + + /* Loop ran to completion: all attempts rejected, budget exhausted. + * This should never happen with a FIPS compliant choice of + * MLD_CONFIG_MAX_SIGNING_ATTEMPTS. */ + ret = MLD_ERR_SIGN_ATTEMPTS_EXHAUSTED; + +cleanup: + + if (ret != 0) + { + /* To be on the safe-side, we zeroize the signature buffer. */ + *siglen = 0; + mld_memset(sig, 0, MLDSA_CRYPTO_BYTES); + } + + /* @[FIPS204, Section 3.6.3] Destruction of intermediate values. */ + MLD_FREE(s2hat, mld_sk_s2hat, 1, context); + MLD_FREE(t0hat, mld_sk_t0hat, 1, context); + MLD_FREE(s1hat, mld_sk_s1hat, 1, context); + MLD_FREE(mat, mld_polymat, 1, context); + MLD_FREE(seedbuf, uint8_t, + 2 * MLDSA_SEEDBYTES + MLDSA_TRBYTES + 2 * MLDSA_CRHBYTES, context); + return ret; +} + +#if !defined(MLD_CONFIG_CORE_API_ONLY) +#if !defined(MLD_CONFIG_NO_RANDOMIZED_API) +MLD_MUST_CHECK_RETURN_VALUE +MLD_EXTERNAL_API +int mld_sign_signature(uint8_t sig[MLDSA_CRYPTO_BYTES], size_t *siglen, + const uint8_t *m, size_t mlen, const uint8_t *ctx, + size_t ctxlen, + const uint8_t sk[MLDSA_CRYPTO_SECRETKEYBYTES], + MLD_CONFIG_CONTEXT_PARAMETER_TYPE context) +{ + size_t pre_len; + int ret; + MLD_ALLOC(pre, uint8_t, MLD_DOMAIN_SEPARATION_MAX_BYTES, context); + MLD_ALLOC(rnd, uint8_t, MLDSA_RNDBYTES, context); + + if (pre == NULL || rnd == NULL) + { + ret = MLD_ERR_OUT_OF_MEMORY; + goto cleanup; + } + + /* Prepare domain separation prefix for pure ML-DSA */ + pre_len = mld_prepare_domain_separation_prefix(pre, NULL, 0, ctx, ctxlen, + MLD_PREHASH_NONE); + if (pre_len == 0) + { + ret = MLD_ERR_FAIL; + goto cleanup; + } + + /* Randomized variant of ML-DSA. If you need the deterministic variant, + * call mld_sign_signature_internal directly with all-zero rnd. */ + if (mld_randombytes(rnd, MLDSA_RNDBYTES) != 0) + { + ret = MLD_ERR_RNG_FAIL; + goto cleanup; + } + MLD_CT_TESTING_SECRET(rnd, MLDSA_RNDBYTES); + + ret = mld_sign_signature_internal(sig, siglen, m, mlen, pre, pre_len, rnd, sk, + 0, context); + +cleanup: + if (ret != 0) + { + /* To be on the safe-side, make sure *siglen and sig have a well-defined + * value, even in the case of error. + * + * If we come from mld_sign_signature_internal, both are redundant, + * but the error case should not be the norm, and the added cost of the + * memset insignificant. */ + *siglen = 0; + mld_memset(sig, 0, MLDSA_CRYPTO_BYTES); + } + + /* @[FIPS204, Section 3.6.3] Destruction of intermediate values. */ + MLD_FREE(rnd, uint8_t, MLDSA_RNDBYTES, context); + MLD_FREE(pre, uint8_t, MLD_DOMAIN_SEPARATION_MAX_BYTES, context); + + return ret; +} +#endif /* !MLD_CONFIG_NO_RANDOMIZED_API */ + +#if !defined(MLD_CONFIG_NO_RANDOMIZED_API) +MLD_MUST_CHECK_RETURN_VALUE +MLD_EXTERNAL_API +int mld_sign_signature_extmu(uint8_t sig[MLDSA_CRYPTO_BYTES], size_t *siglen, + const uint8_t mu[MLDSA_CRHBYTES], + const uint8_t sk[MLDSA_CRYPTO_SECRETKEYBYTES], + MLD_CONFIG_CONTEXT_PARAMETER_TYPE context) +{ + int ret; + MLD_ALLOC(rnd, uint8_t, MLDSA_RNDBYTES, context); + + if (rnd == NULL) + { + *siglen = 0; + ret = MLD_ERR_OUT_OF_MEMORY; + goto cleanup; + } + + /* Randomized variant of ML-DSA. If you need the deterministic variant, + * call mld_sign_signature_internal directly with all-zero rnd. */ + if (mld_randombytes(rnd, MLDSA_RNDBYTES) != 0) + { + *siglen = 0; + ret = MLD_ERR_RNG_FAIL; + goto cleanup; + } + MLD_CT_TESTING_SECRET(rnd, MLDSA_RNDBYTES); + + ret = mld_sign_signature_internal(sig, siglen, mu, MLDSA_CRHBYTES, NULL, 0, + rnd, sk, 1, context); + +cleanup: + /* @[FIPS204, Section 3.6.3] Destruction of intermediate values. */ + MLD_FREE(rnd, uint8_t, MLDSA_RNDBYTES, context); + + return ret; +} +#endif /* !MLD_CONFIG_NO_RANDOMIZED_API */ +#endif /* !MLD_CONFIG_CORE_API_ONLY */ +#endif /* !MLD_CONFIG_NO_SIGN_API */ + +#if !defined(MLD_CONFIG_NO_VERIFY_API) +MLD_MUST_CHECK_RETURN_VALUE +MLD_EXTERNAL_API +int mld_sign_verify_internal(const uint8_t *sig, size_t siglen, + const uint8_t *m, size_t mlen, const uint8_t *pre, + size_t prelen, + const uint8_t pk[MLDSA_CRYPTO_PUBLICKEYBYTES], + int externalmu, + MLD_CONFIG_CONTEXT_PARAMETER_TYPE context) +{ + int ret, cmp; + unsigned int i; + + MLD_ALLOC(buf, uint8_t, (MLDSA_K * MLDSA_POLYW1_PACKEDBYTES), context); + MLD_ALLOC(mu, uint8_t, MLDSA_CRHBYTES, context); + MLD_ALLOC(c, uint8_t, MLDSA_CTILDEBYTES, context); + MLD_ALLOC(c2, uint8_t, MLDSA_CTILDEBYTES, context); + MLD_ALLOC(z, mld_polyvecl, 1, context); + MLD_ALLOC(cp, mld_poly, 1, context); + MLD_ALLOC(mat, mld_polymat, 1, context); + MLD_ALLOC(w1, mld_poly, 1, context); + MLD_ALLOC(tmp, mld_poly, 1, context); + + if (buf == NULL || mu == NULL || c == NULL || c2 == NULL || z == NULL || + cp == NULL || mat == NULL || w1 == NULL || tmp == NULL) + { + ret = MLD_ERR_OUT_OF_MEMORY; + goto cleanup; + } + + if (siglen != MLDSA_CRYPTO_BYTES) + { + ret = MLD_ERR_FAIL; + goto cleanup; + } + + mld_memcpy(c, sig, MLDSA_CTILDEBYTES); + mld_polyvecl_unpack_z(z, sig + MLDSA_SIG_Z_OFFSET); + + /* mld_polyvecl_chknorm signals failure through a single non-zero error code + * that's not yet aligned with MLD_ERR_XXX. Map it to MLD_ERR_FAIL. */ + if (mld_polyvecl_chknorm(z, MLDSA_GAMMA1 - MLDSA_BETA)) + { + ret = MLD_ERR_FAIL; + goto cleanup; + } + + if (!externalmu) + { + /* Compute CRH(H(rho, t1), pre, msg) */ + MLD_ALIGN uint8_t hpk[MLDSA_CRHBYTES]; + mld_H(hpk, MLDSA_TRBYTES, pk, MLDSA_CRYPTO_PUBLICKEYBYTES, NULL, 0, NULL, + 0); + mld_H(mu, MLDSA_CRHBYTES, hpk, MLDSA_TRBYTES, pre, prelen, m, mlen); + + /* @[FIPS204, Section 3.6.3] Destruction of intermediate values. */ + mld_zeroize(hpk, sizeof(hpk)); + } + else + { + /* mu has been provided directly */ + mld_memcpy(mu, m, MLDSA_CRHBYTES); + } + + /* Matrix-vector multiplication and per-row reconstruction of w1. */ + mld_polyvecl_ntt(z); + mld_polyvec_matrix_expand(mat, pk); + mld_poly_challenge(cp, c); + mld_poly_ntt(cp); + + for (i = 0; i < MLDSA_K; ++i) + __loop__( + assigns(MLD_IF_REDUCE_RAM(memory_slice(mat, sizeof(mld_polymat)),) + i, ret, + memory_slice(w1, sizeof(mld_poly)), + memory_slice(tmp, sizeof(mld_poly)), + memory_slice(buf, MLDSA_K * MLDSA_POLYW1_PACKEDBYTES) + ) + invariant(i <= MLDSA_K) + decreases(MLDSA_K - i) + ) + { + /* w1 = (A * z)_i in NTT domain */ + mld_polyvec_matrix_pointwise_montgomery_row(w1, mat, z, i); + + /* tmp = c * t1_i * 2^d in NTT domain */ + mld_unpack_pk_t1(tmp, pk, i); + mld_poly_shiftl(tmp); + mld_poly_ntt(tmp); + mld_poly_pointwise_montgomery(tmp, cp); + + /* w1 = invNTT(w1 - c * t1_i * 2^d) */ + mld_poly_sub(w1, tmp); + mld_poly_reduce(w1); + mld_poly_invntt_tomont(w1); + mld_poly_caddq(w1); + + /* tmp = h_i (decoded and validated from signature) */ + ret = mld_sig_unpack_hints(tmp, sig, i); + if (ret != 0) + { + goto cleanup; + } + + /* w1 = use_hint(w1, tmp), then pack into buf[i] */ + mld_poly_use_hint(w1, tmp); + mld_polyw1_pack(buf + i * MLDSA_POLYW1_PACKEDBYTES, w1); + } + + /* Call random oracle and verify challenge */ + mld_H(c2, MLDSA_CTILDEBYTES, mu, MLDSA_CRHBYTES, buf, + MLDSA_K * MLDSA_POLYW1_PACKEDBYTES, NULL, 0); + + cmp = mld_ct_memcmp(c, c2, MLDSA_CTILDEBYTES); + + /* Declassify the result of the verification. */ + MLD_CT_TESTING_DECLASSIFY(&cmp, sizeof(cmp)); + + ret = cmp == 0 ? 0 : MLD_ERR_FAIL; + +cleanup: + /* @[FIPS204, Section 3.6.3] Destruction of intermediate values. */ + MLD_FREE(tmp, mld_poly, 1, context); + MLD_FREE(w1, mld_poly, 1, context); + MLD_FREE(mat, mld_polymat, 1, context); + MLD_FREE(cp, mld_poly, 1, context); + MLD_FREE(z, mld_polyvecl, 1, context); + MLD_FREE(c2, uint8_t, MLDSA_CTILDEBYTES, context); + MLD_FREE(c, uint8_t, MLDSA_CTILDEBYTES, context); + MLD_FREE(mu, uint8_t, MLDSA_CRHBYTES, context); + MLD_FREE(buf, uint8_t, (MLDSA_K * MLDSA_POLYW1_PACKEDBYTES), context); + return ret; +} + +#if !defined(MLD_CONFIG_CORE_API_ONLY) +MLD_MUST_CHECK_RETURN_VALUE +MLD_EXTERNAL_API +int mld_sign_verify(const uint8_t *sig, size_t siglen, const uint8_t *m, + size_t mlen, const uint8_t *ctx, size_t ctxlen, + const uint8_t pk[MLDSA_CRYPTO_PUBLICKEYBYTES], + MLD_CONFIG_CONTEXT_PARAMETER_TYPE context) +{ + MLD_ALIGN uint8_t pre[MLD_DOMAIN_SEPARATION_MAX_BYTES]; + size_t pre_len; + int ret; + + pre_len = mld_prepare_domain_separation_prefix(pre, NULL, 0, ctx, ctxlen, + MLD_PREHASH_NONE); + if (pre_len == 0) + { + ret = MLD_ERR_FAIL; + goto cleanup; + } + + ret = mld_sign_verify_internal(sig, siglen, m, mlen, pre, pre_len, pk, 0, + context); + +cleanup: + /* @[FIPS204, Section 3.6.3] Destruction of intermediate values. */ + mld_zeroize(pre, sizeof(pre)); + + return ret; +} + +MLD_MUST_CHECK_RETURN_VALUE +MLD_EXTERNAL_API +int mld_sign_verify_extmu(const uint8_t *sig, size_t siglen, + const uint8_t mu[MLDSA_CRHBYTES], + const uint8_t pk[MLDSA_CRYPTO_PUBLICKEYBYTES], + MLD_CONFIG_CONTEXT_PARAMETER_TYPE context) +{ + return mld_sign_verify_internal(sig, siglen, mu, MLDSA_CRHBYTES, NULL, 0, pk, + 1, context); +} +#endif /* !MLD_CONFIG_CORE_API_ONLY */ +#endif /* !MLD_CONFIG_NO_VERIFY_API */ + +#if !defined(MLD_CONFIG_CORE_API_ONLY) +#if !defined(MLD_CONFIG_NO_SIGN_API) +MLD_MUST_CHECK_RETURN_VALUE +MLD_EXTERNAL_API +int mld_sign_signature_pre_hash_internal( + uint8_t sig[MLDSA_CRYPTO_BYTES], size_t *siglen, const uint8_t *ph, + size_t phlen, const uint8_t *ctx, size_t ctxlen, + const uint8_t rnd[MLDSA_RNDBYTES], + const uint8_t sk[MLDSA_CRYPTO_SECRETKEYBYTES], int hashalg, + MLD_CONFIG_CONTEXT_PARAMETER_TYPE context) +{ + MLD_ALIGN uint8_t pre[MLD_DOMAIN_SEPARATION_MAX_BYTES]; + size_t pre_len; + int ret; + + pre_len = mld_prepare_domain_separation_prefix(pre, ph, phlen, ctx, ctxlen, + hashalg); + if (pre_len == 0) + { + ret = MLD_ERR_FAIL; + goto cleanup; + } + + ret = mld_sign_signature_internal(sig, siglen, pre, pre_len, NULL, 0, rnd, sk, + 0, context); +cleanup: + if (ret != 0) + { + /* To be on the safe-side, make sure *siglen and sig have a well-defined + * value, even in the case of error. + * + * If we come from mld_sign_signature_internal, both are redundant, + * but the error case should not be the norm, and the added cost of the + * memset insignificant. */ + *siglen = 0; + mld_memset(sig, 0, MLDSA_CRYPTO_BYTES); + } + + /* @[FIPS204, Section 3.6.3] Destruction of intermediate values. */ + mld_zeroize(pre, sizeof(pre)); + return ret; +} +#endif /* !MLD_CONFIG_NO_SIGN_API */ + +#if !defined(MLD_CONFIG_NO_VERIFY_API) +MLD_MUST_CHECK_RETURN_VALUE +MLD_EXTERNAL_API +int mld_sign_verify_pre_hash_internal( + const uint8_t *sig, size_t siglen, const uint8_t *ph, size_t phlen, + const uint8_t *ctx, size_t ctxlen, + const uint8_t pk[MLDSA_CRYPTO_PUBLICKEYBYTES], int hashalg, + MLD_CONFIG_CONTEXT_PARAMETER_TYPE context) +{ + MLD_ALIGN uint8_t pre[MLD_DOMAIN_SEPARATION_MAX_BYTES]; + size_t pre_len; + int ret; + + pre_len = mld_prepare_domain_separation_prefix(pre, ph, phlen, ctx, ctxlen, + hashalg); + if (pre_len == 0) + { + ret = MLD_ERR_FAIL; + goto cleanup; + } + + ret = mld_sign_verify_internal(sig, siglen, pre, pre_len, NULL, 0, pk, 0, + context); + +cleanup: + /* @[FIPS204, Section 3.6.3] Destruction of intermediate values. */ + mld_zeroize(pre, sizeof(pre)); + return ret; +} +#endif /* !MLD_CONFIG_NO_VERIFY_API */ + +#if !defined(MLD_CONFIG_NO_SIGN_API) +MLD_MUST_CHECK_RETURN_VALUE +MLD_EXTERNAL_API +int mld_sign_signature_pre_hash_shake256( + uint8_t sig[MLDSA_CRYPTO_BYTES], size_t *siglen, const uint8_t *m, + size_t mlen, const uint8_t *ctx, size_t ctxlen, + const uint8_t rnd[MLDSA_RNDBYTES], + const uint8_t sk[MLDSA_CRYPTO_SECRETKEYBYTES], + MLD_CONFIG_CONTEXT_PARAMETER_TYPE context) +{ + MLD_ALIGN uint8_t ph[64]; + int ret; + mld_shake256(ph, sizeof(ph), m, mlen); + ret = mld_sign_signature_pre_hash_internal(sig, siglen, ph, sizeof(ph), ctx, + ctxlen, rnd, sk, + MLD_PREHASH_SHAKE_256, context); + /* @[FIPS204, Section 3.6.3] Destruction of intermediate values. */ + mld_zeroize(ph, sizeof(ph)); + return ret; +} +#endif /* !MLD_CONFIG_NO_SIGN_API */ + +#if !defined(MLD_CONFIG_NO_VERIFY_API) +MLD_MUST_CHECK_RETURN_VALUE +MLD_EXTERNAL_API +int mld_sign_verify_pre_hash_shake256( + const uint8_t *sig, size_t siglen, const uint8_t *m, size_t mlen, + const uint8_t *ctx, size_t ctxlen, + const uint8_t pk[MLDSA_CRYPTO_PUBLICKEYBYTES], + MLD_CONFIG_CONTEXT_PARAMETER_TYPE context) +{ + MLD_ALIGN uint8_t ph[64]; + int ret; + mld_shake256(ph, sizeof(ph), m, mlen); + ret = mld_sign_verify_pre_hash_internal(sig, siglen, ph, sizeof(ph), ctx, + ctxlen, pk, MLD_PREHASH_SHAKE_256, + context); + /* @[FIPS204, Section 3.6.3] Destruction of intermediate values. */ + mld_zeroize(ph, sizeof(ph)); + return ret; +} +#endif /* !MLD_CONFIG_NO_VERIFY_API */ + +#if !defined(MLD_CONFIG_NO_SIGN_API) || !defined(MLD_CONFIG_NO_VERIFY_API) +#define MLD_PRE_HASH_OID_LEN 11 + +/** + * Return the OID of a given SHA-2/SHA-3 hash function. + * + * @param[out] oid Pointer to output OID. + * @param hashalg Hash algorithm constant (MLD_PREHASH_*). + */ +static void mld_get_hash_oid(uint8_t oid[MLD_PRE_HASH_OID_LEN], int hashalg) +{ + unsigned int i; + static const struct + { + int alg; + uint8_t oid[MLD_PRE_HASH_OID_LEN]; + } oid_map[] = { + {MLD_PREHASH_SHA2_224, + {0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x04}}, + {MLD_PREHASH_SHA2_256, + {0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01}}, + {MLD_PREHASH_SHA2_384, + {0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02}}, + {MLD_PREHASH_SHA2_512, + {0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03}}, + {MLD_PREHASH_SHA2_512_224, + {0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x05}}, + {MLD_PREHASH_SHA2_512_256, + {0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x06}}, + {MLD_PREHASH_SHA3_224, + {0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x07}}, + {MLD_PREHASH_SHA3_256, + {0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x08}}, + {MLD_PREHASH_SHA3_384, + {0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x09}}, + {MLD_PREHASH_SHA3_512, + {0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x0A}}, + {MLD_PREHASH_SHAKE_128, + {0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x0B}}, + {MLD_PREHASH_SHAKE_256, + {0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x0C}}}; + + for (i = 0; i < sizeof(oid_map) / sizeof(oid_map[0]); i++) + __loop__( + invariant(i <= sizeof(oid_map) / sizeof(oid_map[0])) + decreases(sizeof(oid_map) / sizeof(oid_map[0]) - i) + ) + { + if (oid_map[i].alg == hashalg) + { + mld_memcpy(oid, oid_map[i].oid, MLD_PRE_HASH_OID_LEN); + return; + } + } +} + +static int mld_validate_hash_length(int hashalg, size_t len) +{ + switch (hashalg) + { + case MLD_PREHASH_SHA2_224: + return (len == 224 / 8) ? 0 : -1; + case MLD_PREHASH_SHA2_256: + return (len == 256 / 8) ? 0 : -1; + case MLD_PREHASH_SHA2_384: + return (len == 384 / 8) ? 0 : -1; + case MLD_PREHASH_SHA2_512: + return (len == 512 / 8) ? 0 : -1; + case MLD_PREHASH_SHA2_512_224: + return (len == 224 / 8) ? 0 : -1; + case MLD_PREHASH_SHA2_512_256: + return (len == 256 / 8) ? 0 : -1; + case MLD_PREHASH_SHA3_224: + return (len == 224 / 8) ? 0 : -1; + case MLD_PREHASH_SHA3_256: + return (len == 256 / 8) ? 0 : -1; + case MLD_PREHASH_SHA3_384: + return (len == 384 / 8) ? 0 : -1; + case MLD_PREHASH_SHA3_512: + return (len == 512 / 8) ? 0 : -1; + case MLD_PREHASH_SHAKE_128: + return (len == 256 / 8) ? 0 : -1; + case MLD_PREHASH_SHAKE_256: + return (len == 512 / 8) ? 0 : -1; + default: + return -1; + } +} + +size_t mld_prepare_domain_separation_prefix( + uint8_t prefix[MLD_DOMAIN_SEPARATION_MAX_BYTES], const uint8_t *ph, + size_t phlen, const uint8_t *ctx, size_t ctxlen, int hashalg) +{ + if (ctxlen > 255) + { + return 0; + } + + if (hashalg != MLD_PREHASH_NONE) + { + if (ph == NULL || mld_validate_hash_length(hashalg, phlen) != 0) + { + return 0; + } + } + + /* Common prefix: 0x00/0x01 || ctxlen || ctx */ + prefix[0] = (hashalg == MLD_PREHASH_NONE) ? 0 : 1; + prefix[1] = (uint8_t)ctxlen; + if (ctxlen > 0) + { + mld_memcpy(prefix + 2, ctx, ctxlen); + } + + if (hashalg == MLD_PREHASH_NONE) + { + return 2 + ctxlen; + } + + /* HashML-DSA: append oid || ph */ + mld_get_hash_oid(prefix + 2 + ctxlen, hashalg); + mld_memcpy(prefix + 2 + ctxlen + MLD_PRE_HASH_OID_LEN, ph, phlen); + return 2 + ctxlen + MLD_PRE_HASH_OID_LEN + phlen; +} +#endif /* !MLD_CONFIG_NO_SIGN_API || !MLD_CONFIG_NO_VERIFY_API */ + +#if !defined(MLD_CONFIG_NO_KEYPAIR_API) +MLD_EXTERNAL_API +int mld_sign_pk_from_sk(uint8_t pk[MLDSA_CRYPTO_PUBLICKEYBYTES], + const uint8_t sk[MLDSA_CRYPTO_SECRETKEYBYTES], + MLD_CONFIG_CONTEXT_PARAMETER_TYPE context) +{ + uint8_t check, cmp0, cmp1, chk1, chk2; + int ret; + MLD_ALLOC(rho, uint8_t, MLDSA_SEEDBYTES, context); + MLD_ALLOC(tr, uint8_t, MLDSA_TRBYTES, context); + MLD_ALLOC(tr_computed, uint8_t, MLDSA_TRBYTES, context); + MLD_ALLOC(key, uint8_t, MLDSA_SEEDBYTES, context); + MLD_ALLOC(s1, mld_polyvecl, 1, context); + MLD_ALLOC(s2, mld_polyveck, 1, context); + MLD_ALLOC(t0_packed, uint8_t, MLDSA_K *MLDSA_POLYT0_PACKEDBYTES, context); + + if (rho == NULL || tr == NULL || tr_computed == NULL || key == NULL || + s1 == NULL || s2 == NULL || t0_packed == NULL) + { + ret = MLD_ERR_OUT_OF_MEMORY; + goto cleanup; + } + + /* Inline unpack_sk: mld_unpack_sk uses lazy types for s1/s2/t0 which + * we cannot use here. t0 stays in packed form -- we compare it against + * the recomputed value below. */ + mld_memcpy(rho, sk + MLDSA_SK_RHO_OFFSET, MLDSA_SEEDBYTES); + mld_memcpy(key, sk + MLDSA_SK_KEY_OFFSET, MLDSA_SEEDBYTES); + mld_memcpy(tr, sk + MLDSA_SK_TR_OFFSET, MLDSA_TRBYTES); + mld_polyvecl_unpack_eta(s1, sk + MLDSA_SK_S1_OFFSET); + mld_polyveck_unpack_eta(s2, sk + MLDSA_SK_S2_OFFSET); + + /* Validate s1 and s2 coefficients are within [-MLDSA_ETA, MLDSA_ETA] */ + chk1 = mld_polyvecl_chknorm(s1, MLDSA_ETA + 1) & 0xFF; + chk2 = mld_polyveck_chknorm(s2, MLDSA_ETA + 1) & 0xFF; + + /* NTT s1 in place to use as s1hat */ + mld_polyvecl_ntt(s1); + + /* Pack rho into pk */ + mld_memcpy(pk + MLDSA_PK_RHO_OFFSET, rho, MLDSA_SEEDBYTES); + + /* Recompute t row by row, decompose, and pack t1 into pk and t0 into + * t0_packed. */ + ret = mld_compute_pack_t0_t1(pk + MLDSA_PK_T1_OFFSET, t0_packed, s1, s2, rho, + context); + if (ret != 0) + { + goto cleanup; + } + + /* Compare recomputed packed t0 against the t0 region of sk. */ + cmp0 = mld_ct_memcmp(t0_packed, sk + MLDSA_SK_T0_OFFSET, + MLDSA_K * MLDSA_POLYT0_PACKEDBYTES); + + /* Compute tr_computed = H(pk) and compare to the stored tr */ + mld_shake256(tr_computed, MLDSA_TRBYTES, pk, MLDSA_CRYPTO_PUBLICKEYBYTES); + cmp1 = mld_ct_memcmp((const uint8_t *)tr, (const uint8_t *)tr_computed, + MLDSA_TRBYTES); + check = mld_value_barrier_u8(cmp0 | cmp1 | chk1 | chk2); + + /* Declassify the final result of the validity check. */ + MLD_CT_TESTING_DECLASSIFY(&check, sizeof(check)); + ret = (check != 0) ? MLD_ERR_FAIL : 0; + +cleanup: + + if (ret != 0) + { + mld_zeroize(pk, MLDSA_CRYPTO_PUBLICKEYBYTES); + } + + /* Constant time: pk is either the valid public key or zeroed on error */ + MLD_CT_TESTING_DECLASSIFY(pk, MLDSA_CRYPTO_PUBLICKEYBYTES); + + /* @[FIPS204, Section 3.6.3] Destruction of intermediate values. */ + MLD_FREE(t0_packed, uint8_t, MLDSA_K *MLDSA_POLYT0_PACKEDBYTES, context); + MLD_FREE(s2, mld_polyveck, 1, context); + MLD_FREE(s1, mld_polyvecl, 1, context); + MLD_FREE(key, uint8_t, MLDSA_SEEDBYTES, context); + MLD_FREE(tr_computed, uint8_t, MLDSA_TRBYTES, context); + MLD_FREE(tr, uint8_t, MLDSA_TRBYTES, context); + MLD_FREE(rho, uint8_t, MLDSA_SEEDBYTES, context); + + return ret; +} +#endif /* !MLD_CONFIG_NO_KEYPAIR_API */ +#endif /* !MLD_CONFIG_CORE_API_ONLY */ + +/* To facilitate single-compilation-unit (SCU) builds, undefine all macros. + * Don't modify by hand -- this is auto-generated by scripts/autogen. */ +#undef mld_check_pct +#undef mld_sample_s1_s2 +#undef mld_validate_hash_length +#undef mld_get_hash_oid +#undef mld_H +#undef mld_compute_pack_z +#undef mld_attempt_signature_generation +#undef mld_compute_pack_t0_t1 +#undef mld_get_max_signing_attempts +#undef MLD_MAX_KAPPA +#undef MLD_MAX_SIGNING_ATTEMPTS +#undef MLD_PRE_HASH_OID_LEN diff --git a/mldsa_native/src/sign.h b/mldsa_native/src/sign.h new file mode 100644 index 0000000..d7e65fb --- /dev/null +++ b/mldsa_native/src/sign.h @@ -0,0 +1,768 @@ +/* + * Copyright (c) The mldsa-native project authors + * SPDX-License-Identifier: Apache-2.0 OR ISC OR MIT + */ + +/* References + * ========== + * + * - [FIPS204] + * FIPS 204 Module-Lattice-Based Digital Signature Standard + * National Institute of Standards and Technology + * https://csrc.nist.gov/pubs/fips/204/final + */ + +#ifndef MLD_SIGN_H +#define MLD_SIGN_H + +#include +#include "cbmc.h" +#include "common.h" +#include "poly.h" +#include "polyvec.h" +#include "sys.h" + +#if defined(MLD_CHECK_APIS) +/* Include to ensure consistency between internal sign.h + * and external mldsa_native.h. */ +#include "mldsa_native.h" + +#if MLDSA_CRYPTO_SECRETKEYBYTES != \ + MLDSA_SECRETKEYBYTES(MLD_CONFIG_PARAMETER_SET) +#error Mismatch for SECRETKEYBYTES between sign.h and mldsa_native.h +#endif + +#if MLDSA_CRYPTO_PUBLICKEYBYTES != \ + MLDSA_PUBLICKEYBYTES(MLD_CONFIG_PARAMETER_SET) +#error Mismatch for PUBLICKEYBYTES between sign.h and mldsa_native.h +#endif + +#if MLDSA_CRYPTO_BYTES != MLDSA_BYTES(MLD_CONFIG_PARAMETER_SET) +#error Mismatch for CRYPTO_BYTES between sign.h and mldsa_native.h +#endif + +#endif /* MLD_CHECK_APIS */ + +#define mld_sign_keypair_internal \ + MLD_NAMESPACE_KL(keypair_internal) MLD_CONTEXT_PARAMETERS_3 +#define mld_sign_keypair MLD_NAMESPACE_KL(keypair) MLD_CONTEXT_PARAMETERS_2 +#define mld_sign_signature_internal \ + MLD_NAMESPACE_KL(signature_internal) MLD_CONTEXT_PARAMETERS_9 +#define mld_sign_signature MLD_NAMESPACE_KL(signature) MLD_CONTEXT_PARAMETERS_7 +#define mld_sign_signature_extmu \ + MLD_NAMESPACE_KL(signature_extmu) MLD_CONTEXT_PARAMETERS_4 +#define mld_sign_verify_internal \ + MLD_NAMESPACE_KL(verify_internal) MLD_CONTEXT_PARAMETERS_8 +#define mld_sign_verify MLD_NAMESPACE_KL(verify) MLD_CONTEXT_PARAMETERS_7 +#define mld_sign_verify_extmu \ + MLD_NAMESPACE_KL(verify_extmu) MLD_CONTEXT_PARAMETERS_4 +#define mld_sign_signature_pre_hash_internal \ + MLD_NAMESPACE_KL(signature_pre_hash_internal) MLD_CONTEXT_PARAMETERS_9 +#define mld_sign_verify_pre_hash_internal \ + MLD_NAMESPACE_KL(verify_pre_hash_internal) MLD_CONTEXT_PARAMETERS_8 +#define mld_sign_signature_pre_hash_shake256 \ + MLD_NAMESPACE_KL(signature_pre_hash_shake256) MLD_CONTEXT_PARAMETERS_8 +#define mld_sign_verify_pre_hash_shake256 \ + MLD_NAMESPACE_KL(verify_pre_hash_shake256) MLD_CONTEXT_PARAMETERS_7 +#define mld_prepare_domain_separation_prefix \ + MLD_NAMESPACE_KL(prepare_domain_separation_prefix) +#define mld_sign_pk_from_sk \ + MLD_NAMESPACE_KL(pk_from_sk) MLD_CONTEXT_PARAMETERS_2 + +/* Hash algorithm constants for domain separation */ +#define MLD_PREHASH_NONE 0 +#define MLD_PREHASH_SHA2_224 1 +#define MLD_PREHASH_SHA2_256 2 +#define MLD_PREHASH_SHA2_384 3 +#define MLD_PREHASH_SHA2_512 4 +#define MLD_PREHASH_SHA2_512_224 5 +#define MLD_PREHASH_SHA2_512_256 6 +#define MLD_PREHASH_SHA3_224 7 +#define MLD_PREHASH_SHA3_256 8 +#define MLD_PREHASH_SHA3_384 9 +#define MLD_PREHASH_SHA3_512 10 +#define MLD_PREHASH_SHAKE_128 11 +#define MLD_PREHASH_SHAKE_256 12 + +#if !defined(MLD_CONFIG_NO_KEYPAIR_API) +/** + * Generate a public-private key pair from a seed. + * + * When MLD_CONFIG_KEYGEN_PCT is set, performs a Pairwise Consistency Test + * (PCT) as required by FIPS 140-3 IG. + * + * @spec{Implements @[FIPS204, Algorithm 6, ML-DSA.KeyGen_internal].} + * + * @param[out] pk Output public key. + * @param[out] sk Output private key. + * @param[in] seed Input random seed. + * @param context Application context. Only present when + * MLD_CONFIG_CONTEXT_PARAMETER is defined; type set by + * MLD_CONFIG_CONTEXT_PARAMETER_TYPE. + * + * @retval 0 Success. + * @retval MLD_ERR_OUT_OF_MEMORY MLD_CONFIG_CUSTOM_ALLOC_FREE was + * used and an allocation via + * MLD_CUSTOM_ALLOC returned NULL. + * @retval MLD_ERR_RNG_FAIL Random number generation failed. + * @retval MLD_ERR_SIGN_ATTEMPTS_EXHAUSTED The PCT's signing step exhausted + * MLD_CONFIG_MAX_SIGNING_ATTEMPTS + * iterations. Only possible when + * MLD_CONFIG_KEYGEN_PCT is enabled. + * @retval MLD_ERR_FAIL Other kinds of failure, including + * PCT failure if + * MLD_CONFIG_KEYGEN_PCT is enabled. + */ +MLD_MUST_CHECK_RETURN_VALUE +MLD_EXTERNAL_API +int mld_sign_keypair_internal(uint8_t pk[MLDSA_CRYPTO_PUBLICKEYBYTES], + uint8_t sk[MLDSA_CRYPTO_SECRETKEYBYTES], + const uint8_t seed[MLDSA_SEEDBYTES], + MLD_CONFIG_CONTEXT_PARAMETER_TYPE context) +__contract__( + requires(memory_no_alias(pk, MLDSA_CRYPTO_PUBLICKEYBYTES)) + requires(memory_no_alias(sk, MLDSA_CRYPTO_SECRETKEYBYTES)) + requires(memory_no_alias(seed, MLDSA_SEEDBYTES)) + assigns(object_whole(pk)) + assigns(object_whole(sk)) + ensures(return_value == 0 || MLD_ANY_ERROR(return_value)) +); + +#if !defined(MLD_CONFIG_CORE_API_ONLY) +/** + * Generate a public-private key pair. + * + * When MLD_CONFIG_KEYGEN_PCT is set, performs a Pairwise Consistency Test + * (PCT) as required by FIPS 140-3 IG. + * + * @spec{Implements @[FIPS204, Algorithm 1, ML-DSA.KeyGen].} + * + * @param[out] pk Output public key. + * @param[out] sk Output private key. + * @param context Application context. Only present when + * MLD_CONFIG_CONTEXT_PARAMETER is defined; type set by + * MLD_CONFIG_CONTEXT_PARAMETER_TYPE. + * + * @retval 0 Success. + * @retval MLD_ERR_OUT_OF_MEMORY MLD_CONFIG_CUSTOM_ALLOC_FREE was + * used and an allocation via + * MLD_CUSTOM_ALLOC returned NULL. + * @retval MLD_ERR_RNG_FAIL Random number generation failed. + * @retval MLD_ERR_SIGN_ATTEMPTS_EXHAUSTED The PCT's signing step exhausted + * MLD_CONFIG_MAX_SIGNING_ATTEMPTS + * iterations. Only possible when + * MLD_CONFIG_KEYGEN_PCT is enabled. + * @retval MLD_ERR_FAIL Other kinds of failure, including + * PCT failure if + * MLD_CONFIG_KEYGEN_PCT is enabled. + */ +MLD_MUST_CHECK_RETURN_VALUE +MLD_EXTERNAL_API +int mld_sign_keypair(uint8_t pk[MLDSA_CRYPTO_PUBLICKEYBYTES], + uint8_t sk[MLDSA_CRYPTO_SECRETKEYBYTES], + MLD_CONFIG_CONTEXT_PARAMETER_TYPE context) +__contract__( + requires(memory_no_alias(pk, MLDSA_CRYPTO_PUBLICKEYBYTES)) + requires(memory_no_alias(sk, MLDSA_CRYPTO_SECRETKEYBYTES)) + assigns(object_whole(pk)) + assigns(object_whole(sk)) + ensures(return_value == 0 || MLD_ANY_ERROR(return_value)) +); +#endif /* !MLD_CONFIG_CORE_API_ONLY */ +#endif /* !MLD_CONFIG_NO_KEYPAIR_API */ + +#if !defined(MLD_CONFIG_NO_SIGN_API) +/** + * Compute signature using internal randomness. + * + * If the returned value is non-zero, then the values of *sig and *siglen + * should not be referenced. + * + * @spec{Implements @[FIPS204, Algorithm 7, ML-DSA.Sign_internal].} + * + * @param[out] sig Output signature. + * @param[out] siglen Pointer to output length of signature. + * @param[in] m Pointer to message to be signed (when + * externalmu == 0), or to a precomputed + * message representative mu (when externalmu != 0). + * @param mlen Length of m. Must equal MLDSA_CRHBYTES when + * externalmu != 0. + * @param[in] pre Pointer to prefix string. Ignored when + * externalmu != 0. + * @param prelen Length of prefix string. Ignored when + * externalmu != 0. + * @param[in] rnd Random seed. + * @param[in] sk Bit-packed secret key. + * @param externalmu 0: m/mlen is the raw message; mu = H(tr, pre, m) is + * computed internally. + * non-zero: m points to a precomputed mu of + * MLDSA_CRHBYTES bytes; pre/prelen unused. + * @param context Application context. Only present when + * MLD_CONFIG_CONTEXT_PARAMETER is defined; type set by + * MLD_CONFIG_CONTEXT_PARAMETER_TYPE. + * + * @retval 0 Success. + * @retval MLD_ERR_OUT_OF_MEMORY MLD_CONFIG_CUSTOM_ALLOC_FREE was + * used and an allocation via + * MLD_CUSTOM_ALLOC returned NULL. + * @retval MLD_ERR_SIGN_ATTEMPTS_EXHAUSTED The rejection-sampling loop exceeded + * MLD_CONFIG_MAX_SIGNING_ATTEMPTS + * iterations. + * @retval MLD_ERR_FAIL Other kinds of failure. + */ +MLD_MUST_CHECK_RETURN_VALUE +MLD_EXTERNAL_API +int mld_sign_signature_internal(uint8_t sig[MLDSA_CRYPTO_BYTES], size_t *siglen, + const uint8_t *m, size_t mlen, + const uint8_t *pre, size_t prelen, + const uint8_t rnd[MLDSA_RNDBYTES], + const uint8_t sk[MLDSA_CRYPTO_SECRETKEYBYTES], + int externalmu, + MLD_CONFIG_CONTEXT_PARAMETER_TYPE context) +__contract__( + requires(mlen <= MLD_MAX_BUFFER_SIZE) + requires(prelen <= MLD_MAX_BUFFER_SIZE) + requires(memory_no_alias(sig, MLDSA_CRYPTO_BYTES)) + requires(memory_no_alias(siglen, sizeof(size_t))) + requires(memory_no_alias(m, mlen)) + requires(memory_no_alias(rnd, MLDSA_RNDBYTES)) + requires(memory_no_alias(sk, MLDSA_CRYPTO_SECRETKEYBYTES)) + requires((externalmu == 0) ==> ((prelen == 0) || memory_no_alias(pre, prelen))) + requires((externalmu != 0) ==> (mlen == MLDSA_CRHBYTES)) + assigns(memory_slice(sig, MLDSA_CRYPTO_BYTES)) + assigns(object_whole(siglen)) + ensures(return_value == 0 || return_value == MLD_ERR_FAIL || + return_value == MLD_ERR_OUT_OF_MEMORY || + return_value == MLD_ERR_SIGN_ATTEMPTS_EXHAUSTED) + ensures(return_value == 0 ==> *siglen == MLDSA_CRYPTO_BYTES) + ensures(return_value != 0 ==> *siglen == 0) +); + +#if !defined(MLD_CONFIG_CORE_API_ONLY) +/** + * Compute signature. This function implements the randomized variant of + * ML-DSA. If you require the deterministic variant, use + * mld_sign_signature_internal directly. + * + * @spec{Implements @[FIPS204, Algorithm 2, ML-DSA.Sign].} + * + * @param[out] sig Output signature. + * @param[out] siglen Pointer to output length of signature. + * @param[in] m Pointer to message to be signed. + * @param mlen Length of message. + * @param[in] ctx Pointer to context string. May be NULL if ctxlen == 0. + * @param ctxlen Length of context string. Should be <= 255. + * @param[in] sk Bit-packed secret key. + * @param context Application context. Only present when + * MLD_CONFIG_CONTEXT_PARAMETER is defined; type set by + * MLD_CONFIG_CONTEXT_PARAMETER_TYPE. + * + * @retval 0 Success. + * @retval MLD_ERR_OUT_OF_MEMORY MLD_CONFIG_CUSTOM_ALLOC_FREE was + * used and an allocation via + * MLD_CUSTOM_ALLOC returned NULL. + * @retval MLD_ERR_RNG_FAIL Random number generation failed. + * @retval MLD_ERR_SIGN_ATTEMPTS_EXHAUSTED The rejection-sampling loop exceeded + * MLD_CONFIG_MAX_SIGNING_ATTEMPTS + * iterations. + * @retval MLD_ERR_FAIL Other kinds of failure. + */ +MLD_MUST_CHECK_RETURN_VALUE +MLD_EXTERNAL_API +int mld_sign_signature(uint8_t sig[MLDSA_CRYPTO_BYTES], size_t *siglen, + const uint8_t *m, size_t mlen, const uint8_t *ctx, + size_t ctxlen, + const uint8_t sk[MLDSA_CRYPTO_SECRETKEYBYTES], + MLD_CONFIG_CONTEXT_PARAMETER_TYPE context) +__contract__( + requires(mlen <= MLD_MAX_BUFFER_SIZE) + requires(memory_no_alias(sig, MLDSA_CRYPTO_BYTES)) + requires(memory_no_alias(siglen, sizeof(size_t))) + requires(memory_no_alias(m, mlen)) + requires(ctxlen <= MLD_MAX_BUFFER_SIZE) + requires(ctxlen == 0 || memory_no_alias(ctx, ctxlen)) + requires(memory_no_alias(sk, MLDSA_CRYPTO_SECRETKEYBYTES)) + assigns(memory_slice(sig, MLDSA_CRYPTO_BYTES)) + assigns(object_whole(siglen)) + ensures((return_value == 0 && *siglen == MLDSA_CRYPTO_BYTES) || + (MLD_ANY_ERROR(return_value) && *siglen == 0)) +); + +/** + * Compute signature in "external mu" mode: the caller has already computed + * the message representative mu = SHAKE256(tr || M', 64), where + * tr = SHAKE256(pk, 64) and M' is the FIPS 204 formatted message (e.g. + * 0x00 || ctxlen || ctx || msg for pure ML-DSA). This is the randomized + * variant; for the deterministic variant, use mld_sign_signature_internal + * directly with externalmu set to non-zero and an all-zero rnd. + * + * @spec{Implements @[FIPS204, Algorithm 2, ML-DSA.Sign external mu variant].} + * + * @param[out] sig Output signature. + * @param[out] siglen Pointer to output length of signature. + * @param[in] mu Precomputed message representative. + * @param[in] sk Bit-packed secret key. + * @param context Application context. Only present when + * MLD_CONFIG_CONTEXT_PARAMETER is defined; type set by + * MLD_CONFIG_CONTEXT_PARAMETER_TYPE. + * + * @retval 0 Success. + * @retval MLD_ERR_OUT_OF_MEMORY MLD_CONFIG_CUSTOM_ALLOC_FREE was + * used and an allocation via + * MLD_CUSTOM_ALLOC returned NULL. + * @retval MLD_ERR_RNG_FAIL Random number generation failed. + * @retval MLD_ERR_SIGN_ATTEMPTS_EXHAUSTED The rejection-sampling loop exceeded + * MLD_CONFIG_MAX_SIGNING_ATTEMPTS + * iterations. + * @retval MLD_ERR_FAIL Other kinds of failure. + */ +MLD_MUST_CHECK_RETURN_VALUE +MLD_EXTERNAL_API +int mld_sign_signature_extmu(uint8_t sig[MLDSA_CRYPTO_BYTES], size_t *siglen, + const uint8_t mu[MLDSA_CRHBYTES], + const uint8_t sk[MLDSA_CRYPTO_SECRETKEYBYTES], + MLD_CONFIG_CONTEXT_PARAMETER_TYPE context) +__contract__( + requires(memory_no_alias(sig, MLDSA_CRYPTO_BYTES)) + requires(memory_no_alias(siglen, sizeof(size_t))) + requires(memory_no_alias(mu, MLDSA_CRHBYTES)) + requires(memory_no_alias(sk, MLDSA_CRYPTO_SECRETKEYBYTES)) + assigns(memory_slice(sig, MLDSA_CRYPTO_BYTES)) + assigns(object_whole(siglen)) + ensures((return_value == 0 && *siglen == MLDSA_CRYPTO_BYTES) || + (MLD_ANY_ERROR(return_value) && *siglen == 0)) +); + +#endif /* !MLD_CONFIG_CORE_API_ONLY */ +#endif /* !MLD_CONFIG_NO_SIGN_API */ + +#if !defined(MLD_CONFIG_NO_VERIFY_API) +/** + * Verify signature. + * + * @spec{Implements @[FIPS204, Algorithm 8, ML-DSA.Verify_internal].} + * + * @param[in] sig Pointer to input signature. + * @param siglen Length of signature. + * @param[in] m Pointer to message (when externalmu == 0), or to a + * precomputed message representative mu (when + * externalmu != 0). + * @param mlen Length of m. Must equal MLDSA_CRHBYTES when + * externalmu != 0. + * @param[in] pre Pointer to prefix string. Ignored when externalmu != 0. + * @param prelen Length of prefix string. Ignored when externalmu != 0. + * @param[in] pk Bit-packed public key. + * @param externalmu 0: m/mlen is the raw message; mu = H(H(pk), pre, m) is + * computed internally. + * non-zero: m points to a precomputed mu of + * MLDSA_CRHBYTES bytes; pre/prelen unused. + * @param context Application context. Only present when + * MLD_CONFIG_CONTEXT_PARAMETER is defined; type set by + * MLD_CONFIG_CONTEXT_PARAMETER_TYPE. + * + * @retval 0 Success. + * @retval MLD_ERR_OUT_OF_MEMORY MLD_CONFIG_CUSTOM_ALLOC_FREE was used and an + * allocation via MLD_CUSTOM_ALLOC returned NULL. + * @retval MLD_ERR_FAIL Signature verification failed. + */ +MLD_MUST_CHECK_RETURN_VALUE +MLD_EXTERNAL_API +int mld_sign_verify_internal(const uint8_t *sig, size_t siglen, + const uint8_t *m, size_t mlen, const uint8_t *pre, + size_t prelen, + const uint8_t pk[MLDSA_CRYPTO_PUBLICKEYBYTES], + int externalmu, + MLD_CONFIG_CONTEXT_PARAMETER_TYPE context) +__contract__( + requires(prelen <= MLD_MAX_BUFFER_SIZE) + requires(mlen <= MLD_MAX_BUFFER_SIZE) + requires(siglen <= MLD_MAX_BUFFER_SIZE) + requires(memory_no_alias(sig, siglen)) + requires(memory_no_alias(m, mlen)) + requires((externalmu == 0) ==> ((prelen == 0) || memory_no_alias(pre, prelen))) + requires((externalmu != 0) ==> (mlen == MLDSA_CRHBYTES)) + requires(memory_no_alias(pk, MLDSA_CRYPTO_PUBLICKEYBYTES)) + ensures(return_value == 0 || return_value == MLD_ERR_FAIL || return_value == MLD_ERR_OUT_OF_MEMORY) +); + +#if !defined(MLD_CONFIG_CORE_API_ONLY) +/** + * Verify signature. + * + * @spec{Implements @[FIPS204, Algorithm 3, ML-DSA.Verify].} + * + * @param[in] sig Pointer to input signature. + * @param siglen Length of signature. + * @param[in] m Pointer to message. + * @param mlen Length of message. + * @param[in] ctx Pointer to context string. May be NULL if ctxlen == 0. + * @param ctxlen Length of context string. + * @param[in] pk Bit-packed public key. + * @param context Application context. Only present when + * MLD_CONFIG_CONTEXT_PARAMETER is defined; type set by + * MLD_CONFIG_CONTEXT_PARAMETER_TYPE. + * + * @retval 0 Success. + * @retval MLD_ERR_OUT_OF_MEMORY MLD_CONFIG_CUSTOM_ALLOC_FREE was used and an + * allocation via MLD_CUSTOM_ALLOC returned NULL. + * @retval MLD_ERR_FAIL Signature verification failed. + */ +MLD_MUST_CHECK_RETURN_VALUE +MLD_EXTERNAL_API +int mld_sign_verify(const uint8_t *sig, size_t siglen, const uint8_t *m, + size_t mlen, const uint8_t *ctx, size_t ctxlen, + const uint8_t pk[MLDSA_CRYPTO_PUBLICKEYBYTES], + MLD_CONFIG_CONTEXT_PARAMETER_TYPE context) +__contract__( + requires(mlen <= MLD_MAX_BUFFER_SIZE) + requires(siglen <= MLD_MAX_BUFFER_SIZE) + requires(ctxlen <= MLD_MAX_BUFFER_SIZE) + requires(memory_no_alias(sig, siglen)) + requires(memory_no_alias(m, mlen)) + requires(ctxlen == 0 || memory_no_alias(ctx, ctxlen)) + requires(memory_no_alias(pk, MLDSA_CRYPTO_PUBLICKEYBYTES)) + ensures(return_value == 0 || return_value == MLD_ERR_FAIL || return_value == MLD_ERR_OUT_OF_MEMORY) +); + +/** + * Verify signature in "external mu" mode: the caller has already computed + * the message representative mu = SHAKE256(tr || M', 64), where + * tr = SHAKE256(pk, 64) and M' is the FIPS 204 formatted message (e.g. + * 0x00 || ctxlen || ctx || msg for pure ML-DSA). The same mu must have been + * used at signing time. + * + * @spec{Implements @[FIPS204, Algorithm 3, ML-DSA.Verify external mu variant].} + * + * @param[in] sig Pointer to input signature. + * @param siglen Length of signature. + * @param[in] mu Precomputed message representative. + * @param[in] pk Bit-packed public key. + * @param context Application context. Only present when + * MLD_CONFIG_CONTEXT_PARAMETER is defined; type set by + * MLD_CONFIG_CONTEXT_PARAMETER_TYPE. + * + * @retval 0 Success. + * @retval MLD_ERR_OUT_OF_MEMORY MLD_CONFIG_CUSTOM_ALLOC_FREE was used and an + * allocation via MLD_CUSTOM_ALLOC returned NULL. + * @retval MLD_ERR_FAIL Signature verification failed. + */ +MLD_MUST_CHECK_RETURN_VALUE +MLD_EXTERNAL_API +int mld_sign_verify_extmu(const uint8_t *sig, size_t siglen, + const uint8_t mu[MLDSA_CRHBYTES], + const uint8_t pk[MLDSA_CRYPTO_PUBLICKEYBYTES], + MLD_CONFIG_CONTEXT_PARAMETER_TYPE context) +__contract__( + requires(siglen <= MLD_MAX_BUFFER_SIZE) + requires(memory_no_alias(sig, siglen)) + requires(memory_no_alias(mu, MLDSA_CRHBYTES)) + requires(memory_no_alias(pk, MLDSA_CRYPTO_PUBLICKEYBYTES)) + ensures(return_value == 0 || return_value == MLD_ERR_FAIL || return_value == MLD_ERR_OUT_OF_MEMORY) +); + +#endif /* !MLD_CONFIG_CORE_API_ONLY */ +#endif /* !MLD_CONFIG_NO_VERIFY_API */ + +#if !defined(MLD_CONFIG_CORE_API_ONLY) +#if !defined(MLD_CONFIG_NO_SIGN_API) +/** + * Compute signature with pre-hashed message. + * + * @spec{Implements @[FIPS204, Algorithm 4, HashML-DSA.Sign].} + * + * Supported hash algorithm constants: + * MLD_PREHASH_SHA2_224, MLD_PREHASH_SHA2_256, MLD_PREHASH_SHA2_384, + * MLD_PREHASH_SHA2_512, MLD_PREHASH_SHA2_512_224, MLD_PREHASH_SHA2_512_256, + * MLD_PREHASH_SHA3_224, MLD_PREHASH_SHA3_256, MLD_PREHASH_SHA3_384, + * MLD_PREHASH_SHA3_512, MLD_PREHASH_SHAKE_128, MLD_PREHASH_SHAKE_256. + * + * @warning This is an unstable API that may change in the future. If you need + * a stable API use mld_sign_signature_pre_hash_shake256. + * + * @param[out] sig Output signature. + * @param[out] siglen Pointer to output length of signature. + * @param[in] ph Pointer to pre-hashed message. + * @param phlen Length of pre-hashed message. + * @param[in] ctx Pointer to context string. + * @param ctxlen Length of context string. + * @param[in] rnd Random seed. + * @param[in] sk Bit-packed secret key. + * @param hashalg Hash algorithm constant (one of MLD_PREHASH_*). + * @param context Application context. Only present when + * MLD_CONFIG_CONTEXT_PARAMETER is defined; type set by + * MLD_CONFIG_CONTEXT_PARAMETER_TYPE. + * + * @retval 0 Success. + * @retval MLD_ERR_OUT_OF_MEMORY MLD_CONFIG_CUSTOM_ALLOC_FREE was + * used and an allocation via + * MLD_CUSTOM_ALLOC returned NULL. + * @retval MLD_ERR_SIGN_ATTEMPTS_EXHAUSTED The rejection-sampling loop exceeded + * MLD_CONFIG_MAX_SIGNING_ATTEMPTS + * iterations. + * @retval MLD_ERR_FAIL Other kinds of failure. + */ +MLD_MUST_CHECK_RETURN_VALUE +MLD_EXTERNAL_API +int mld_sign_signature_pre_hash_internal( + uint8_t sig[MLDSA_CRYPTO_BYTES], size_t *siglen, const uint8_t *ph, + size_t phlen, const uint8_t *ctx, size_t ctxlen, + const uint8_t rnd[MLDSA_RNDBYTES], + const uint8_t sk[MLDSA_CRYPTO_SECRETKEYBYTES], int hashalg, + MLD_CONFIG_CONTEXT_PARAMETER_TYPE context) +__contract__( + requires(ctxlen <= MLD_MAX_BUFFER_SIZE) + requires(phlen <= MLD_MAX_BUFFER_SIZE) + requires(memory_no_alias(sig, MLDSA_CRYPTO_BYTES)) + requires(memory_no_alias(siglen, sizeof(size_t))) + requires(memory_no_alias(ph, phlen)) + requires(ctxlen == 0 || memory_no_alias(ctx, ctxlen)) + requires(memory_no_alias(rnd, MLDSA_RNDBYTES)) + requires(memory_no_alias(sk, MLDSA_CRYPTO_SECRETKEYBYTES)) + assigns(memory_slice(sig, MLDSA_CRYPTO_BYTES)) + assigns(object_whole(siglen)) + ensures((return_value == 0 && *siglen == MLDSA_CRYPTO_BYTES) || + ((return_value == MLD_ERR_FAIL || return_value == MLD_ERR_OUT_OF_MEMORY || return_value == MLD_ERR_SIGN_ATTEMPTS_EXHAUSTED) && *siglen == 0)) +); +#endif /* !MLD_CONFIG_NO_SIGN_API */ + +#if !defined(MLD_CONFIG_NO_VERIFY_API) +/** + * Verify signature with pre-hashed message. + * + * @spec{Implements @[FIPS204, Algorithm 5, HashML-DSA.Verify].} + * + * Supported hash algorithm constants: + * MLD_PREHASH_SHA2_224, MLD_PREHASH_SHA2_256, MLD_PREHASH_SHA2_384, + * MLD_PREHASH_SHA2_512, MLD_PREHASH_SHA2_512_224, MLD_PREHASH_SHA2_512_256, + * MLD_PREHASH_SHA3_224, MLD_PREHASH_SHA3_256, MLD_PREHASH_SHA3_384, + * MLD_PREHASH_SHA3_512, MLD_PREHASH_SHAKE_128, MLD_PREHASH_SHAKE_256. + * + * @warning This is an unstable API that may change in the future. If you need + * a stable API use mld_sign_verify_pre_hash_shake256. + * + * @param[in] sig Pointer to input signature. + * @param siglen Length of signature. + * @param[in] ph Pointer to pre-hashed message. + * @param phlen Length of pre-hashed message. + * @param[in] ctx Pointer to context string. + * @param ctxlen Length of context string. + * @param[in] pk Bit-packed public key. + * @param hashalg Hash algorithm constant (one of MLD_PREHASH_*). + * @param context Application context. Only present when + * MLD_CONFIG_CONTEXT_PARAMETER is defined; type set by + * MLD_CONFIG_CONTEXT_PARAMETER_TYPE. + * + * @retval 0 Success. + * @retval MLD_ERR_OUT_OF_MEMORY MLD_CONFIG_CUSTOM_ALLOC_FREE was used and an + * allocation via MLD_CUSTOM_ALLOC returned NULL. + * @retval MLD_ERR_FAIL Signature verification failed. + */ +MLD_MUST_CHECK_RETURN_VALUE +MLD_EXTERNAL_API +int mld_sign_verify_pre_hash_internal( + const uint8_t *sig, size_t siglen, const uint8_t *ph, size_t phlen, + const uint8_t *ctx, size_t ctxlen, + const uint8_t pk[MLDSA_CRYPTO_PUBLICKEYBYTES], int hashalg, + MLD_CONFIG_CONTEXT_PARAMETER_TYPE context) +__contract__( + requires(phlen <= MLD_MAX_BUFFER_SIZE) + requires(ctxlen <= MLD_MAX_BUFFER_SIZE - 77) + requires(siglen <= MLD_MAX_BUFFER_SIZE) + requires(memory_no_alias(sig, siglen)) + requires(memory_no_alias(ph, phlen)) + requires(ctxlen == 0 || memory_no_alias(ctx, ctxlen)) + requires(memory_no_alias(pk, MLDSA_CRYPTO_PUBLICKEYBYTES)) + ensures(return_value == 0 || return_value == MLD_ERR_FAIL || return_value == MLD_ERR_OUT_OF_MEMORY) +); +#endif /* !MLD_CONFIG_NO_VERIFY_API */ + +#if !defined(MLD_CONFIG_NO_SIGN_API) +/** + * Compute signature with pre-hashed message using SHAKE256. This function + * computes the SHAKE256 hash of the message internally. + * + * @spec{Implements @[FIPS204, Algorithm 4, HashML-DSA.Sign] with SHAKE256 as + * the pre-hash.} + * + * @param[out] sig Output signature. + * @param[out] siglen Pointer to output length of signature. + * @param[in] m Pointer to message to be hashed and signed. + * @param mlen Length of message. + * @param[in] ctx Pointer to context string. + * @param ctxlen Length of context string. + * @param[in] rnd Random seed. + * @param[in] sk Bit-packed secret key. + * @param context Application context. Only present when + * MLD_CONFIG_CONTEXT_PARAMETER is defined; type set by + * MLD_CONFIG_CONTEXT_PARAMETER_TYPE. + * + * @retval 0 Success. + * @retval MLD_ERR_OUT_OF_MEMORY MLD_CONFIG_CUSTOM_ALLOC_FREE was + * used and an allocation via + * MLD_CUSTOM_ALLOC returned NULL. + * @retval MLD_ERR_SIGN_ATTEMPTS_EXHAUSTED The rejection-sampling loop exceeded + * MLD_CONFIG_MAX_SIGNING_ATTEMPTS + * iterations. + * @retval MLD_ERR_FAIL Other kinds of failure. + */ +MLD_MUST_CHECK_RETURN_VALUE +MLD_EXTERNAL_API +int mld_sign_signature_pre_hash_shake256( + uint8_t sig[MLDSA_CRYPTO_BYTES], size_t *siglen, const uint8_t *m, + size_t mlen, const uint8_t *ctx, size_t ctxlen, + const uint8_t rnd[MLDSA_RNDBYTES], + const uint8_t sk[MLDSA_CRYPTO_SECRETKEYBYTES], + MLD_CONFIG_CONTEXT_PARAMETER_TYPE context) +__contract__( + requires(mlen <= MLD_MAX_BUFFER_SIZE) + requires(ctxlen <= MLD_MAX_BUFFER_SIZE) + requires(memory_no_alias(sig, MLDSA_CRYPTO_BYTES)) + requires(memory_no_alias(siglen, sizeof(size_t))) + requires(memory_no_alias(m, mlen)) + requires(ctxlen == 0 || memory_no_alias(ctx, ctxlen)) + requires(memory_no_alias(rnd, MLDSA_RNDBYTES)) + requires(memory_no_alias(sk, MLDSA_CRYPTO_SECRETKEYBYTES)) + assigns(memory_slice(sig, MLDSA_CRYPTO_BYTES)) + assigns(object_whole(siglen)) + ensures((return_value == 0 && *siglen == MLDSA_CRYPTO_BYTES) || + ((return_value == MLD_ERR_FAIL || return_value == MLD_ERR_OUT_OF_MEMORY || return_value == MLD_ERR_SIGN_ATTEMPTS_EXHAUSTED) && *siglen == 0)) +); +#endif /* !MLD_CONFIG_NO_SIGN_API */ + +#if !defined(MLD_CONFIG_NO_VERIFY_API) +/** + * Verify signature with pre-hashed message using SHAKE256. This function + * computes the SHAKE256 hash of the message internally. + * + * @spec{Implements @[FIPS204, Algorithm 5, HashML-DSA.Verify] with SHAKE256 as + * the pre-hash.} + * + * @param[in] sig Pointer to input signature. + * @param siglen Length of signature. + * @param[in] m Pointer to message to be hashed and verified. + * @param mlen Length of message. + * @param[in] ctx Pointer to context string. + * @param ctxlen Length of context string. + * @param[in] pk Bit-packed public key. + * @param context Application context. Only present when + * MLD_CONFIG_CONTEXT_PARAMETER is defined; type set by + * MLD_CONFIG_CONTEXT_PARAMETER_TYPE. + * + * @retval 0 Success. + * @retval MLD_ERR_OUT_OF_MEMORY MLD_CONFIG_CUSTOM_ALLOC_FREE was used and an + * allocation via MLD_CUSTOM_ALLOC returned NULL. + * @retval MLD_ERR_FAIL Signature verification failed. + */ +MLD_MUST_CHECK_RETURN_VALUE +MLD_EXTERNAL_API +int mld_sign_verify_pre_hash_shake256( + const uint8_t *sig, size_t siglen, const uint8_t *m, size_t mlen, + const uint8_t *ctx, size_t ctxlen, + const uint8_t pk[MLDSA_CRYPTO_PUBLICKEYBYTES], + MLD_CONFIG_CONTEXT_PARAMETER_TYPE context) +__contract__( + requires(mlen <= MLD_MAX_BUFFER_SIZE) + requires(ctxlen <= MLD_MAX_BUFFER_SIZE - 77) + requires(siglen <= MLD_MAX_BUFFER_SIZE) + requires(memory_no_alias(sig, siglen)) + requires(memory_no_alias(m, mlen)) + requires(ctxlen == 0 || memory_no_alias(ctx, ctxlen)) + requires(memory_no_alias(pk, MLDSA_CRYPTO_PUBLICKEYBYTES)) + ensures(return_value == 0 || return_value == MLD_ERR_FAIL || return_value == MLD_ERR_OUT_OF_MEMORY) +); +#endif /* !MLD_CONFIG_NO_VERIFY_API */ + +#if !defined(MLD_CONFIG_NO_SIGN_API) || !defined(MLD_CONFIG_NO_VERIFY_API) +/* Maximum formatted domain separation message length: + * - Pure ML-DSA: 0x00 || ctxlen || ctx (max 255) + * - HashML-DSA: 0x01 || ctxlen || ctx (max 255) || oid (11) || ph (max 64) */ +#define MLD_DOMAIN_SEPARATION_MAX_BYTES (2 + 255 + 11 + 64) + +/** + * Prepare domain separation prefix for ML-DSA signing. + * + * For pure ML-DSA (hashalg == MLD_PREHASH_NONE): + * Format: 0x00 || ctxlen (1 byte) || ctx. + * + * For HashML-DSA (hashalg != MLD_PREHASH_NONE): + * Format: 0x01 || ctxlen (1 byte) || ctx || oid (11 bytes) || ph. + * + * This function is useful for building incremental signing APIs. + * + * @spec{For HashML-DSA (hashalg != MLD_PREHASH_NONE), implements + * @[FIPS204, Algorithm 4, line 23]. For Pure ML-DSA + * (hashalg == MLD_PREHASH_NONE), implements + * ``` + * M' <- BytesToBits(IntegerToBytes(0, 1) + * || IntegerToBytes(|ctx|, 1) + * || ctx + * ``` + * which is part of @[FIPS204, Algorithm 2, ML-DSA.Sign, line 10] and + * @[FIPS204, Algorithm 3, ML-DSA.Verify, line 5].} + * + * @param[out] prefix Output domain separation prefix buffer. + * @param[in] ph Pointer to pre-hashed message (ignored for pure + * ML-DSA). + * @param phlen Length of pre-hashed message (ignored for pure ML-DSA). + * @param[in] ctx Pointer to context string (may be NULL). + * @param ctxlen Length of context string. + * @param hashalg Hash algorithm constant (MLD_PREHASH_NONE for pure + * ML-DSA, or MLD_PREHASH_* for HashML-DSA). + * + * @return The total length of the formatted prefix, or 0 on error. + */ +MLD_MUST_CHECK_RETURN_VALUE +MLD_EXTERNAL_API +size_t mld_prepare_domain_separation_prefix( + uint8_t prefix[MLD_DOMAIN_SEPARATION_MAX_BYTES], const uint8_t *ph, + size_t phlen, const uint8_t *ctx, size_t ctxlen, int hashalg) +__contract__( + requires(ctxlen <= 255) + requires(phlen <= MLD_MAX_BUFFER_SIZE) + requires(ctxlen == 0 || memory_no_alias(ctx, ctxlen)) + requires(hashalg == MLD_PREHASH_NONE || memory_no_alias(ph, phlen)) + requires(memory_no_alias(prefix, MLD_DOMAIN_SEPARATION_MAX_BYTES)) + assigns(memory_slice(prefix, MLD_DOMAIN_SEPARATION_MAX_BYTES)) + ensures(return_value <= MLD_DOMAIN_SEPARATION_MAX_BYTES) +); +#endif /* !MLD_CONFIG_NO_SIGN_API || !MLD_CONFIG_NO_VERIFY_API */ + +#if !defined(MLD_CONFIG_NO_KEYPAIR_API) +/** + * Perform basic validity checks on secret key, and derive public key. + * + * Referring to the decoding of the secret key `sk=(rho, K, tr, s1, s2, t0)` + * (cf. @[FIPS204, Algorithm 25, skDecode]), the following checks are + * performed: + * - Check that s1 and s2 have coefficients in [-MLDSA_ETA, MLDSA_ETA]. + * - Check that t0 and tr stored in sk match recomputed values. + * + * @note This function leaks whether the secret key is valid or invalid + * through its return value and timing. + * + * @param[out] pk Output public key. + * @param[in] sk Input secret key. + * @param context Application context. Only present when + * MLD_CONFIG_CONTEXT_PARAMETER is defined; type set by + * MLD_CONFIG_CONTEXT_PARAMETER_TYPE. + * + * @retval 0 Success. + * @retval MLD_ERR_OUT_OF_MEMORY MLD_CONFIG_CUSTOM_ALLOC_FREE was used and an + * allocation via MLD_CUSTOM_ALLOC returned NULL. + * @retval MLD_ERR_FAIL Secret key validation failed. + */ +MLD_MUST_CHECK_RETURN_VALUE +MLD_EXTERNAL_API +int mld_sign_pk_from_sk(uint8_t pk[MLDSA_CRYPTO_PUBLICKEYBYTES], + const uint8_t sk[MLDSA_CRYPTO_SECRETKEYBYTES], + MLD_CONFIG_CONTEXT_PARAMETER_TYPE context) +__contract__( + requires(memory_no_alias(pk, MLDSA_CRYPTO_PUBLICKEYBYTES)) + requires(memory_no_alias(sk, MLDSA_CRYPTO_SECRETKEYBYTES)) + assigns(memory_slice(pk, MLDSA_CRYPTO_PUBLICKEYBYTES)) + ensures(return_value == 0 || return_value == MLD_ERR_FAIL || return_value == MLD_ERR_OUT_OF_MEMORY) +); +#endif /* !MLD_CONFIG_NO_KEYPAIR_API */ +#endif /* !MLD_CONFIG_CORE_API_ONLY */ + +#endif /* !MLD_SIGN_H */ diff --git a/mldsa_native/src/symmetric.h b/mldsa_native/src/symmetric.h new file mode 100644 index 0000000..4bbd4f1 --- /dev/null +++ b/mldsa_native/src/symmetric.h @@ -0,0 +1,68 @@ +/* + * Copyright (c) The mldsa-native project authors + * SPDX-License-Identifier: Apache-2.0 OR ISC OR MIT + */ +#ifndef MLD_SYMMETRIC_H +#define MLD_SYMMETRIC_H + +#include "cbmc.h" +#include "common.h" + +#include MLD_FIPS202_HEADER_FILE +#if !defined(MLD_CONFIG_SERIAL_FIPS202_ONLY) +#include MLD_FIPS202X4_HEADER_FILE +#endif + +#define MLD_STREAM128_BLOCKBYTES SHAKE128_RATE +#define MLD_STREAM256_BLOCKBYTES SHAKE256_RATE + +#define mld_xof256_ctx mld_shake256ctx +#define mld_xof256_init(CTX) mld_shake256_init(CTX) + +#define mld_xof256_absorb_once(CTX, IN, INBYTES) \ + do \ + { \ + mld_shake256_absorb(CTX, IN, INBYTES); \ + mld_shake256_finalize(CTX); \ + } while (0) + + +#define mld_xof256_release(CTX) mld_shake256_release(CTX) +#define mld_xof256_squeezeblocks(OUT, OUTBLOCKS, STATE) \ + mld_shake256_squeeze(OUT, (OUTBLOCKS) * SHAKE256_RATE, STATE) + +#define mld_xof128_ctx mld_shake128ctx +#define mld_xof128_init(CTX) mld_shake128_init(CTX) + +#define mld_xof128_absorb_once(CTX, IN, INBYTES) \ + do \ + { \ + mld_shake128_absorb(CTX, IN, INBYTES); \ + mld_shake128_finalize(CTX); \ + } while (0) + +#define mld_xof128_release(CTX) mld_shake128_release(CTX) +#define mld_xof128_squeezeblocks(OUT, OUTBLOCKS, STATE) \ + mld_shake128_squeeze(OUT, (OUTBLOCKS) * SHAKE128_RATE, STATE) + +#define mld_xof256_x4_ctx mld_shake256x4ctx +#define mld_xof256_x4_init(CTX) mld_shake256x4_init((CTX)) +#define mld_xof256_x4_absorb(CTX, IN, INBYTES) \ + mld_shake256x4_absorb_once((CTX), (IN)[0], (IN)[1], (IN)[2], (IN)[3], \ + (INBYTES)) +#define mld_xof256_x4_squeezeblocks(BUF, NBLOCKS, CTX) \ + mld_shake256x4_squeezeblocks((BUF)[0], (BUF)[1], (BUF)[2], (BUF)[3], \ + (NBLOCKS), (CTX)) +#define mld_xof256_x4_release(CTX) mld_shake256x4_release((CTX)) + +#define mld_xof128_x4_ctx mld_shake128x4ctx +#define mld_xof128_x4_init(CTX) mld_shake128x4_init((CTX)) +#define mld_xof128_x4_absorb(CTX, IN, INBYTES) \ + mld_shake128x4_absorb_once((CTX), (IN)[0], (IN)[1], (IN)[2], (IN)[3], \ + (INBYTES)) +#define mld_xof128_x4_squeezeblocks(BUF, NBLOCKS, CTX) \ + mld_shake128x4_squeezeblocks((BUF)[0], (BUF)[1], (BUF)[2], (BUF)[3], \ + (NBLOCKS), (CTX)) +#define mld_xof128_x4_release(CTX) mld_shake128x4_release((CTX)) + +#endif /* !MLD_SYMMETRIC_H */ diff --git a/mldsa_native/src/sys.h b/mldsa_native/src/sys.h new file mode 100644 index 0000000..a96ba75 --- /dev/null +++ b/mldsa_native/src/sys.h @@ -0,0 +1,295 @@ +/* + * Copyright (c) The mlkem-native project authors + * Copyright (c) The mldsa-native project authors + * SPDX-License-Identifier: Apache-2.0 OR ISC OR MIT + */ + +#ifndef MLD_SYS_H +#define MLD_SYS_H + +#if !defined(MLD_CONFIG_NO_ASM) && (defined(__GNUC__) || defined(__clang__)) +#define MLD_HAVE_INLINE_ASM +#endif + +/* Try to find endianness, if not forced through CFLAGS already */ +#if !defined(MLD_SYS_LITTLE_ENDIAN) && !defined(MLD_SYS_BIG_ENDIAN) +#if defined(__BYTE_ORDER__) +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ +#define MLD_SYS_LITTLE_ENDIAN +#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ +#define MLD_SYS_BIG_ENDIAN +#else +#error "__BYTE_ORDER__ defined, but don't recognize value." +#endif +#endif /* __BYTE_ORDER__ */ + +/* MSVC does not define __BYTE_ORDER__. However, MSVC only supports + * little endian x86, x86_64, and AArch64. It is, hence, safe to assume + * little endian. */ +#if defined(_MSC_VER) && (defined(_M_X64) || defined(_M_AMD64) || \ + defined(_M_IX86) || defined(_M_ARM64)) +#define MLD_SYS_LITTLE_ENDIAN +#endif + +#endif /* !MLD_SYS_LITTLE_ENDIAN && !MLD_SYS_BIG_ENDIAN */ + +/* Check if we're running on an AArch64 little endian system. _M_ARM64 is set by + * MSVC. */ +#if defined(__AARCH64EL__) || defined(_M_ARM64) +#define MLD_SYS_AARCH64 +#endif + +/* Check if we're running on an AArch64 big endian system. */ +#if defined(__AARCH64EB__) +#define MLD_SYS_AARCH64_EB +#endif + +/* Check if we're running on an Armv8.1-M system with MVE */ +#if defined(__ARM_ARCH_8_1M_MAIN__) || defined(__ARM_FEATURE_MVE) +#define MLD_SYS_ARMV81M_MVE +#endif + +/* Check if we're running on an x86_64 system. */ +#if defined(__x86_64__) || defined(_M_X64) || defined(_M_AMD64) +#define MLD_SYS_X86_64 +#if defined(__AVX2__) +#define MLD_SYS_X86_64_AVX2 +#endif +#endif /* __x86_64__ || _M_X64 || _M_AMD64 */ + +#if defined(MLD_SYS_LITTLE_ENDIAN) && defined(__powerpc64__) +#define MLD_SYS_PPC64LE +#endif + +#if defined(__riscv) && defined(__riscv_xlen) && __riscv_xlen == 64 +#define MLD_SYS_RISCV64 +#endif + +#if defined(MLD_SYS_RISCV64) && defined(__riscv_vector) && \ + defined(__riscv_v_intrinsic) +#define MLD_SYS_RISCV64_RVV +#endif + +#if defined(__riscv) && defined(__riscv_xlen) && __riscv_xlen == 32 +#define MLD_SYS_RISCV32 +#endif + +#if defined(_WIN64) || defined(_WIN32) +#define MLD_SYS_WINDOWS +#endif + +#if defined(__linux__) +#define MLD_SYS_LINUX +#endif + +#if defined(__APPLE__) +#define MLD_SYS_APPLE +#endif + +/* If MLD_FORCE_AARCH64 is set, assert that we're indeed on an AArch64 system. + */ +#if defined(MLD_FORCE_AARCH64) && !defined(MLD_SYS_AARCH64) +#error "MLD_FORCE_AARCH64 is set, but we don't seem to be on an AArch64 system." +#endif + +/* If MLD_FORCE_AARCH64_EB is set, assert that we're indeed on a big endian + * AArch64 system. */ +#if defined(MLD_FORCE_AARCH64_EB) && !defined(MLD_SYS_AARCH64_EB) +#error \ + "MLD_FORCE_AARCH64_EB is set, but we don't seem to be on an AArch64 system." +#endif + +/* If MLD_FORCE_X86_64 is set, assert that we're indeed on an X86_64 system. */ +#if defined(MLD_FORCE_X86_64) && !defined(MLD_SYS_X86_64) +#error "MLD_FORCE_X86_64 is set, but we don't seem to be on an X86_64 system." +#endif + +#if defined(MLD_FORCE_PPC64LE) && !defined(MLD_SYS_PPC64LE) +#error "MLD_FORCE_PPC64LE is set, but we don't seem to be on a PPC64LE system." +#endif + +#if defined(MLD_FORCE_RISCV64) && !defined(MLD_SYS_RISCV64) +#error "MLD_FORCE_RISCV64 is set, but we don't seem to be on a RISCV64 system." +#endif + +#if defined(MLD_FORCE_RISCV32) && !defined(MLD_SYS_RISCV32) +#error "MLD_FORCE_RISCV32 is set, but we don't seem to be on a RISCV32 system." +#endif + +/* + * MLD_INLINE: Hint for inlining. + * - MSVC: __inline + * - C99+: inline + * - GCC/Clang C90: __attribute__((unused)) to silence warnings + * - Other C90: empty + */ +#if !defined(MLD_INLINE) +#if defined(_MSC_VER) +#define MLD_INLINE __inline +#elif defined(inline) || \ + (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) +#define MLD_INLINE inline +#elif defined(__GNUC__) || defined(__clang__) +#define MLD_INLINE __attribute__((unused)) +#else +#define MLD_INLINE +#endif +#endif /* !MLD_INLINE */ + +/* + * MLD_ALWAYS_INLINE: Force inlining. + * - MSVC: __forceinline + * - GCC/Clang C99+: MLD_INLINE __attribute__((always_inline)) + * - Other: MLD_INLINE (no forced inlining) + */ +#if !defined(MLD_ALWAYS_INLINE) +#if defined(_MSC_VER) +#define MLD_ALWAYS_INLINE __forceinline +#elif (defined(__GNUC__) || defined(__clang__)) && \ + (defined(inline) || \ + (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L)) +#define MLD_ALWAYS_INLINE MLD_INLINE __attribute__((always_inline)) +#else +#define MLD_ALWAYS_INLINE MLD_INLINE +#endif +#endif /* !MLD_ALWAYS_INLINE */ + +#ifndef MLD_STATIC_TESTABLE +#define MLD_STATIC_TESTABLE static +#endif + +/* + * C90 does not have the restrict compiler directive yet. + * We don't use it in C90 builds. + */ +#if !defined(restrict) +#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L +#define MLD_RESTRICT restrict +#else +#define MLD_RESTRICT +#endif + +#else /* !restrict */ + +#define MLD_RESTRICT restrict +#endif /* restrict */ + +#define MLD_DEFAULT_ALIGN 32 +#define MLD_ALIGN_UP(N) \ + ((((N) + (MLD_DEFAULT_ALIGN - 1)) / MLD_DEFAULT_ALIGN) * MLD_DEFAULT_ALIGN) +#if defined(__GNUC__) +#define MLD_ALIGN __attribute__((aligned(MLD_DEFAULT_ALIGN))) +#elif defined(_MSC_VER) +#define MLD_ALIGN __declspec(align(MLD_DEFAULT_ALIGN)) +#else +#define MLD_ALIGN /* No known support for alignment constraints */ +#endif + + +/* New X86_64 CPUs support Conflow-flow protection using the CET instructions. + * When enabled (through -fcf-protection=), all compilation units (including + * empty ones) need to support CET for this to work. + * For assembly, this means that source files need to signal support for + * CET by setting the appropriate note.gnu.property section. + * This can be achieved by including the header in all assembly file. + * This file also provides the _CET_ENDBR macro which needs to be placed at + * every potential target of an indirect branch. + * If CET is enabled _CET_ENDBR maps to the endbr64 instruction, otherwise + * it is empty. + * In case the compiler does not support CET (e.g., +#define MLD_CET_ENDBR _CET_ENDBR +#else +#define MLD_CET_ENDBR +#endif +#endif /* MLD_SYS_X86_64 */ + +#if defined(MLD_CONFIG_CT_TESTING_ENABLED) && !defined(__ASSEMBLER__) +#include +#define MLD_CT_TESTING_SECRET(ptr, len) \ + VALGRIND_MAKE_MEM_UNDEFINED((ptr), (len)) +#define MLD_CT_TESTING_DECLASSIFY(ptr, len) \ + VALGRIND_MAKE_MEM_DEFINED((ptr), (len)) +#else /* MLD_CONFIG_CT_TESTING_ENABLED && !__ASSEMBLER__ */ +#define MLD_CT_TESTING_SECRET(ptr, len) \ + do \ + { \ + } while (0) +#define MLD_CT_TESTING_DECLASSIFY(ptr, len) \ + do \ + { \ + } while (0) +#endif /* !(MLD_CONFIG_CT_TESTING_ENABLED && !__ASSEMBLER__) */ + +#if defined(__GNUC__) || defined(__clang__) +#define MLD_MUST_CHECK_RETURN_VALUE __attribute__((warn_unused_result)) +#else +#define MLD_MUST_CHECK_RETURN_VALUE +#endif + +/* The x86_64 assembly backend uses the SysV calling convention. On Windows, + * where the Microsoft x64 calling convention is the default, it can still be + * used with compilers that allow choosing the calling convention per + * function: GCC and Clang support __attribute__((sysv_abi)), which makes + * calls to the annotated function follow the SysV calling convention. + * + * MLD_SYSV_ABI_SUPPORTED signals that the toolchain can call SysV assembly + * routines; the x86_64 assembly backend is only enabled if it is defined. + * MLD_SYSV_ABI is the attribute carried by declarations of x86_64 assembly + * routines. Both macros can be set externally for toolchains offering an + * equivalent mechanism that is not recognized here. */ +#if defined(MLD_SYS_X86_64) && !defined(MLD_SYSV_ABI_SUPPORTED) +#if !defined(MLD_SYS_WINDOWS) || defined(__GNUC__) || defined(__clang__) +#define MLD_SYSV_ABI_SUPPORTED +#endif +#endif + +#if !defined(MLD_SYSV_ABI) +#if defined(MLD_SYS_WINDOWS) && defined(MLD_SYSV_ABI_SUPPORTED) +#define MLD_SYSV_ABI __attribute__((sysv_abi)) +#else +#define MLD_SYSV_ABI +#endif +#endif /* !MLD_SYSV_ABI */ + +#if !defined(__ASSEMBLER__) +/* System capability enumeration */ +typedef enum +{ + /* x86_64 */ + MLD_SYS_CAP_X86_64_AVX2, + /* AArch64 */ + MLD_SYS_CAP_AARCH64_SHA3, + /* Armv8.1-M */ + MLD_SYS_CAP_ARMV81M_MVE +} mld_sys_cap; + +#if !defined(MLD_CONFIG_CUSTOM_CAPABILITY_FUNC) +#include "cbmc.h" + +MLD_MUST_CHECK_RETURN_VALUE +static MLD_INLINE int mld_sys_check_capability(mld_sys_cap cap) +__contract__( + ensures(return_value == 0 || return_value == 1) +) +{ + /* By default, we rely on compile-time feature detection/specification: + * If a feature is enabled at compile-time, we assume it is supported by + * the host that the resulting library/binary will be built on. + * If this assumption is not true, you MUST overwrite this function. + * See the documentation of MLD_CONFIG_CUSTOM_CAPABILITY_FUNC in + * mldsa_native_config.h for more information. */ + (void)cap; + return 1; +} +#endif /* !MLD_CONFIG_CUSTOM_CAPABILITY_FUNC */ +#endif /* !__ASSEMBLER__ */ + +#endif /* !MLD_SYS_H */ diff --git a/mldsa_native/src/zetas.inc b/mldsa_native/src/zetas.inc new file mode 100644 index 0000000..fa30344 --- /dev/null +++ b/mldsa_native/src/zetas.inc @@ -0,0 +1,55 @@ +/* + * Copyright (c) The mldsa-native project authors + * SPDX-License-Identifier: Apache-2.0 OR ISC OR MIT + */ + +/* + * WARNING: This file is auto-generated from scripts/autogen + * in the mldsa-native repository. + * Do not modify it directly. + */ + + +/* + * Table of zeta values used in the reference NTT and inverse NTT. + * See autogen for details. + */ +static const int32_t mld_zetas[MLDSA_N] = { + 0, 25847, -2608894, -518909, 237124, -777960, -876248, + 466468, 1826347, 2353451, -359251, -2091905, 3119733, -2884855, + 3111497, 2680103, 2725464, 1024112, -1079900, 3585928, -549488, + -1119584, 2619752, -2108549, -2118186, -3859737, -1399561, -3277672, + 1757237, -19422, 4010497, 280005, 2706023, 95776, 3077325, + 3530437, -1661693, -3592148, -2537516, 3915439, -3861115, -3043716, + 3574422, -2867647, 3539968, -300467, 2348700, -539299, -1699267, + -1643818, 3505694, -3821735, 3507263, -2140649, -1600420, 3699596, + 811944, 531354, 954230, 3881043, 3900724, -2556880, 2071892, + -2797779, -3930395, -1528703, -3677745, -3041255, -1452451, 3475950, + 2176455, -1585221, -1257611, 1939314, -4083598, -1000202, -3190144, + -3157330, -3632928, 126922, 3412210, -983419, 2147896, 2715295, + -2967645, -3693493, -411027, -2477047, -671102, -1228525, -22981, + -1308169, -381987, 1349076, 1852771, -1430430, -3343383, 264944, + 508951, 3097992, 44288, -1100098, 904516, 3958618, -3724342, + -8578, 1653064, -3249728, 2389356, -210977, 759969, -1316856, + 189548, -3553272, 3159746, -1851402, -2409325, -177440, 1315589, + 1341330, 1285669, -1584928, -812732, -1439742, -3019102, -3881060, + -3628969, 3839961, 2091667, 3407706, 2316500, 3817976, -3342478, + 2244091, -2446433, -3562462, 266997, 2434439, -1235728, 3513181, + -3520352, -3759364, -1197226, -3193378, 900702, 1859098, 909542, + 819034, 495491, -1613174, -43260, -522500, -655327, -3122442, + 2031748, 3207046, -3556995, -525098, -768622, -3595838, 342297, + 286988, -2437823, 4108315, 3437287, -3342277, 1735879, 203044, + 2842341, 2691481, -2590150, 1265009, 4055324, 1247620, 2486353, + 1595974, -3767016, 1250494, 2635921, -3548272, -2994039, 1869119, + 1903435, -1050970, -1333058, 1237275, -3318210, -1430225, -451100, + 1312455, 3306115, -1962642, -1279661, 1917081, -2546312, -1374803, + 1500165, 777191, 2235880, 3406031, -542412, -2831860, -1671176, + -1846953, -2584293, -3724270, 594136, -3776993, -2013608, 2432395, + 2454455, -164721, 1957272, 3369112, 185531, -1207385, -3183426, + 162844, 1616392, 3014001, 810149, 1652634, -3694233, -1799107, + -3038916, 3523897, 3866901, 269760, 2213111, -975884, 1717735, + 472078, -426683, 1723600, -1803090, 1910376, -1667432, -1104333, + -260646, -3833893, -2939036, -2235985, -420899, -2286327, 183443, + -976891, 1612842, -3545687, -554416, 3919660, -48306, -1362209, + 3937738, 1400424, -846154, 1976782, +}; diff --git a/mlkem_native/mlkem_native.c b/mlkem_native/mlkem_native.c new file mode 100644 index 0000000..a00697e --- /dev/null +++ b/mlkem_native/mlkem_native.c @@ -0,0 +1,660 @@ +/* + * Copyright (c) The mlkem-native project authors + * SPDX-License-Identifier: Apache-2.0 OR ISC OR MIT + */ + +/* + * WARNING: This file is auto-generated from scripts/autogen + * in the mlkem-native repository. + * Do not modify it directly. + */ + +/****************************************************************************** + * + * Single compilation unit (SCU) for fixed-level build of mlkem-native + * + * This compilation unit bundles together all source files for a build + * of mlkem-native for a fixed security level (MLKEM-512/768/1024). + * + * # API + * + * The API exposed by this file is described in mlkem_native.h. + * + * # Multi-level build + * + * If you want an SCU build of mlkem-native with support for multiple security + * levels, you need to include this file multiple times, and set + * MLK_CONFIG_MULTILEVEL_WITH_SHARED and MLK_CONFIG_MULTILEVEL_NO_SHARED + * appropriately. This is exemplified in examples/monolithic_build_multilevel + * and examples/monolithic_build_multilevel_native. + * + * # Configuration + * + * The following options from the mlkem-native configuration are relevant: + * + * - MLK_CONFIG_FIPS202_CUSTOM_HEADER + * Set this option if you use a custom FIPS202 implementation. + * + * - MLK_CONFIG_USE_NATIVE_BACKEND_ARITH + * Set this option if you want to include the native arithmetic backends + * in your build. + * + * - MLK_CONFIG_USE_NATIVE_BACKEND_FIPS202 + * Set this option if you want to include the native FIPS202 backends + * in your build. + * + * - MLK_CONFIG_MONOBUILD_KEEP_SHARED_HEADERS + * Set this option if you want to keep the directives defined in + * level-independent headers. This is needed for a multi-level build. + */ + +/* If parts of the mlkem-native source tree are not used, + * consider reducing this header via `unifdef`. + * + * Example: + * ```bash + * unifdef -UMLK_CONFIG_USE_NATIVE_BACKEND_ARITH mlkem_native.c + * ``` + */ + +#include "src/common.h" + +#include "src/compress.c" +#include "src/debug.c" +#include "src/indcpa.c" +#include "src/kem.c" +#include "src/poly.c" +#include "src/poly_k.c" +#include "src/sampling.c" +#include "src/verify.c" + +#if !defined(MLK_CONFIG_FIPS202_CUSTOM_HEADER) +#include "src/fips202/fips202.c" +#include "src/fips202/fips202x4.c" +#include "src/fips202/keccakf1600.c" +#endif + +#if defined(MLK_CONFIG_USE_NATIVE_BACKEND_ARITH) +#if defined(MLK_SYS_AARCH64) +#include "src/native/aarch64/src/aarch64_zetas.c" +#include "src/native/aarch64/src/rej_uniform_table.c" +#endif +#if defined(MLK_SYS_X86_64) +#include "src/native/x86_64/src/compress_consts.c" +#include "src/native/x86_64/src/consts.c" +#include "src/native/x86_64/src/rej_uniform_table.c" +#endif +#if defined(MLK_SYS_RISCV64) +#include "src/native/riscv64/src/rv64v_debug.c" +#include "src/native/riscv64/src/rv64v_poly.c" +#endif +#endif /* MLK_CONFIG_USE_NATIVE_BACKEND_ARITH */ + +#if defined(MLK_CONFIG_USE_NATIVE_BACKEND_FIPS202) +#if defined(MLK_SYS_AARCH64) +#include "src/fips202/native/aarch64/src/keccakf1600_round_constants.c" +#endif +#if defined(MLK_SYS_X86_64) +#include "src/fips202/native/x86_64/src/keccakf1600_constants.c" +#endif +#if defined(MLK_SYS_ARMV81M_MVE) +#include "src/fips202/native/armv81m/src/keccak_f1600_x4_mve.c" +#include "src/fips202/native/armv81m/src/keccakf1600_round_constants.c" +#endif +#endif /* MLK_CONFIG_USE_NATIVE_BACKEND_FIPS202 */ + +/* Macro #undef's + * + * The following undefines macros from headers + * included by the source files imported above. + * + * This is to allow building and linking multiple builds + * of mlkem-native for varying parameter sets through concatenation + * of this file, as if the files had been compiled separately. + * If this is not relevant to you, you may remove the following. + */ + +/* + * Undefine macros from MLK_CONFIG_PARAMETER_SET-specific files + */ +/* mlkem/mlkem_native.h */ +#undef CRYPTO_BYTES +#undef CRYPTO_CIPHERTEXTBYTES +#undef CRYPTO_PUBLICKEYBYTES +#undef CRYPTO_SECRETKEYBYTES +#undef CRYPTO_SYMBYTES +#undef MLKEM1024_BYTES +#undef MLKEM1024_CIPHERTEXTBYTES +#undef MLKEM1024_PUBLICKEYBYTES +#undef MLKEM1024_SECRETKEYBYTES +#undef MLKEM1024_SYMBYTES +#undef MLKEM512_BYTES +#undef MLKEM512_CIPHERTEXTBYTES +#undef MLKEM512_PUBLICKEYBYTES +#undef MLKEM512_SECRETKEYBYTES +#undef MLKEM512_SYMBYTES +#undef MLKEM768_BYTES +#undef MLKEM768_CIPHERTEXTBYTES +#undef MLKEM768_PUBLICKEYBYTES +#undef MLKEM768_SECRETKEYBYTES +#undef MLKEM768_SYMBYTES +#undef MLKEM_BYTES +#undef MLKEM_CIPHERTEXTBYTES +#undef MLKEM_CIPHERTEXTBYTES_ +#undef MLKEM_PUBLICKEYBYTES +#undef MLKEM_PUBLICKEYBYTES_ +#undef MLKEM_SECRETKEYBYTES +#undef MLKEM_SECRETKEYBYTES_ +#undef MLKEM_SYMBYTES +#undef MLK_API_CONCAT +#undef MLK_API_CONCAT_ +#undef MLK_API_CONCAT_UNDERSCORE +#undef MLK_API_LEGACY_CONFIG +#undef MLK_API_MUST_CHECK_RETURN_VALUE +#undef MLK_API_NAMESPACE +#undef MLK_API_QUALIFIER +#undef MLK_CONFIG_API_CONSTANTS_ONLY +#undef MLK_CONFIG_API_NAMESPACE_PREFIX +#undef MLK_CONFIG_API_NO_SUPERCOP +#undef MLK_CONFIG_API_PARAMETER_SET +#undef MLK_CONFIG_API_QUALIFIER +#undef MLK_ERR_FAIL +#undef MLK_ERR_OUT_OF_MEMORY +#undef MLK_ERR_RNG_FAIL +#undef MLK_H +#undef MLK_MAX3_ +#undef MLK_TOTAL_ALLOC_1024 +#undef MLK_TOTAL_ALLOC_1024_DECAPS +#undef MLK_TOTAL_ALLOC_1024_ENCAPS +#undef MLK_TOTAL_ALLOC_1024_KEYPAIR +#undef MLK_TOTAL_ALLOC_1024_KEYPAIR_NO_PCT +#undef MLK_TOTAL_ALLOC_1024_KEYPAIR_PCT +#undef MLK_TOTAL_ALLOC_512 +#undef MLK_TOTAL_ALLOC_512_DECAPS +#undef MLK_TOTAL_ALLOC_512_ENCAPS +#undef MLK_TOTAL_ALLOC_512_KEYPAIR +#undef MLK_TOTAL_ALLOC_512_KEYPAIR_NO_PCT +#undef MLK_TOTAL_ALLOC_512_KEYPAIR_PCT +#undef MLK_TOTAL_ALLOC_768 +#undef MLK_TOTAL_ALLOC_768_DECAPS +#undef MLK_TOTAL_ALLOC_768_ENCAPS +#undef MLK_TOTAL_ALLOC_768_KEYPAIR +#undef MLK_TOTAL_ALLOC_768_KEYPAIR_NO_PCT +#undef MLK_TOTAL_ALLOC_768_KEYPAIR_PCT +#undef crypto_kem_check_pk +#undef crypto_kem_check_sk +#undef crypto_kem_dec +#undef crypto_kem_enc +#undef crypto_kem_enc_derand +#undef crypto_kem_keypair +#undef crypto_kem_keypair_derand +/* mlkem/src/common.h */ +#undef MLK_ADD_PARAM_SET +#undef MLK_ALLOC +#undef MLK_APPLY +#undef MLK_ASM_FN_SIZE +#undef MLK_ASM_FN_SYMBOL +#undef MLK_ASM_NAMESPACE +#undef MLK_BUILD_INTERNAL +#undef MLK_COMMON_H +#undef MLK_CONCAT +#undef MLK_CONCAT_ +#undef MLK_CONTEXT_PARAMETERS_0 +#undef MLK_CONTEXT_PARAMETERS_1 +#undef MLK_CONTEXT_PARAMETERS_2 +#undef MLK_CONTEXT_PARAMETERS_3 +#undef MLK_CONTEXT_PARAMETERS_4 +#undef MLK_EMPTY_CU +#undef MLK_ERR_FAIL +#undef MLK_ERR_OUT_OF_MEMORY +#undef MLK_ERR_RNG_FAIL +#undef MLK_EXTERNAL_API +#undef MLK_FIPS202X4_HEADER_FILE +#undef MLK_FIPS202_HEADER_FILE +#undef MLK_FREE +#undef MLK_INTERNAL_API +#undef MLK_NAMESPACE +#undef MLK_NAMESPACE_K +#undef MLK_NAMESPACE_PREFIX +#undef MLK_NAMESPACE_PREFIX_K +#undef mlk_memcpy +#undef mlk_memset +/* mlkem/src/indcpa.h */ +#undef MLK_INDCPA_H +#undef mlk_gen_matrix +#undef mlk_indcpa_dec +#undef mlk_indcpa_enc +#undef mlk_indcpa_keypair_derand +/* mlkem/src/kem.h */ +#undef MLK_KEM_H +#undef mlk_kem_check_pk +#undef mlk_kem_check_sk +#undef mlk_kem_dec +#undef mlk_kem_enc +#undef mlk_kem_enc_derand +#undef mlk_kem_keypair +#undef mlk_kem_keypair_derand +/* mlkem/src/params.h */ +#undef MLKEM_DU +#undef MLKEM_DV +#undef MLKEM_ETA1 +#undef MLKEM_ETA2 +#undef MLKEM_INDCCA_CIPHERTEXTBYTES +#undef MLKEM_INDCCA_PUBLICKEYBYTES +#undef MLKEM_INDCCA_SECRETKEYBYTES +#undef MLKEM_INDCPA_BYTES +#undef MLKEM_INDCPA_MSGBYTES +#undef MLKEM_INDCPA_PUBLICKEYBYTES +#undef MLKEM_INDCPA_SECRETKEYBYTES +#undef MLKEM_K +#undef MLKEM_N +#undef MLKEM_POLYBYTES +#undef MLKEM_POLYCOMPRESSEDBYTES_D10 +#undef MLKEM_POLYCOMPRESSEDBYTES_D11 +#undef MLKEM_POLYCOMPRESSEDBYTES_D4 +#undef MLKEM_POLYCOMPRESSEDBYTES_D5 +#undef MLKEM_POLYCOMPRESSEDBYTES_DU +#undef MLKEM_POLYCOMPRESSEDBYTES_DV +#undef MLKEM_POLYVECBYTES +#undef MLKEM_POLYVECCOMPRESSEDBYTES_DU +#undef MLKEM_Q +#undef MLKEM_Q_HALF +#undef MLKEM_SSBYTES +#undef MLKEM_SYMBYTES +#undef MLKEM_UINT12_LIMIT +#undef MLK_PARAMS_H +/* mlkem/src/poly_k.h */ +#undef MLK_POLY_K_H +#undef mlk_poly_compress_du +#undef mlk_poly_compress_dv +#undef mlk_poly_decompress_du +#undef mlk_poly_decompress_dv +#undef mlk_poly_getnoise_eta1122_4x +#undef mlk_poly_getnoise_eta1_4x +#undef mlk_poly_getnoise_eta2 +#undef mlk_poly_getnoise_eta2_4x +#undef mlk_polymat +#undef mlk_polyvec +#undef mlk_polyvec_add +#undef mlk_polyvec_basemul_acc_montgomery_cached +#undef mlk_polyvec_compress_du +#undef mlk_polyvec_decompress_du +#undef mlk_polyvec_frombytes +#undef mlk_polyvec_invntt_tomont +#undef mlk_polyvec_mulcache +#undef mlk_polyvec_mulcache_compute +#undef mlk_polyvec_ntt +#undef mlk_polyvec_reduce +#undef mlk_polyvec_tobytes +#undef mlk_polyvec_tomont + +#if !defined(MLK_CONFIG_MONOBUILD_KEEP_SHARED_HEADERS) +/* + * Undefine macros from MLK_CONFIG_PARAMETER_SET-generic files + */ +/* mlkem/src/compress.h */ +#undef MLK_COMPRESS_H +#undef mlk_poly_compress_d10 +#undef mlk_poly_compress_d11 +#undef mlk_poly_compress_d4 +#undef mlk_poly_compress_d5 +#undef mlk_poly_decompress_d10 +#undef mlk_poly_decompress_d11 +#undef mlk_poly_decompress_d4 +#undef mlk_poly_decompress_d5 +#undef mlk_poly_frombytes +#undef mlk_poly_frommsg +#undef mlk_poly_tobytes +#undef mlk_poly_tomsg +/* mlkem/src/debug.h */ +#undef MLK_DEBUG_H +#undef mlk_assert +#undef mlk_assert_abs_bound +#undef mlk_assert_abs_bound_2d +#undef mlk_assert_bound +#undef mlk_assert_bound_2d +#undef mlk_debug_check_assert +#undef mlk_debug_check_bounds +/* mlkem/src/poly.h */ +#undef MLK_INVNTT_BOUND +#undef MLK_NTT_BOUND +#undef MLK_POLY_H +#undef mlk_poly_add +#undef mlk_poly_invntt_tomont +#undef mlk_poly_mulcache_compute +#undef mlk_poly_ntt +#undef mlk_poly_reduce +#undef mlk_poly_sub +#undef mlk_poly_tomont +/* mlkem/src/randombytes.h */ +#undef MLK_RANDOMBYTES_H +/* mlkem/src/sampling.h */ +#undef MLK_SAMPLING_H +#undef mlk_poly_cbd2 +#undef mlk_poly_cbd3 +#undef mlk_poly_rej_uniform +#undef mlk_poly_rej_uniform_x4 +/* mlkem/src/symmetric.h */ +#undef MLK_SYMMETRIC_H +#undef MLK_XOF_RATE +#undef mlk_hash_g +#undef mlk_hash_h +#undef mlk_hash_j +#undef mlk_prf_eta +#undef mlk_prf_eta1 +#undef mlk_prf_eta1_x4 +#undef mlk_prf_eta2 +#undef mlk_xof_absorb +#undef mlk_xof_ctx +#undef mlk_xof_init +#undef mlk_xof_release +#undef mlk_xof_squeezeblocks +#undef mlk_xof_x4_absorb +#undef mlk_xof_x4_ctx +#undef mlk_xof_x4_init +#undef mlk_xof_x4_release +#undef mlk_xof_x4_squeezeblocks +/* mlkem/src/sys.h */ +#undef MLK_ALIGN +#undef MLK_ALIGN_UP +#undef MLK_ALWAYS_INLINE +#undef MLK_CET_ENDBR +#undef MLK_CT_TESTING_DECLASSIFY +#undef MLK_CT_TESTING_SECRET +#undef MLK_DEFAULT_ALIGN +#undef MLK_HAVE_INLINE_ASM +#undef MLK_INLINE +#undef MLK_MUST_CHECK_RETURN_VALUE +#undef MLK_RESTRICT +#undef MLK_STATIC_TESTABLE +#undef MLK_SYS_AARCH64 +#undef MLK_SYS_AARCH64_EB +#undef MLK_SYS_APPLE +#undef MLK_SYS_ARMV81M_MVE +#undef MLK_SYS_BIG_ENDIAN +#undef MLK_SYS_H +#undef MLK_SYS_LINUX +#undef MLK_SYS_LITTLE_ENDIAN +#undef MLK_SYS_PPC64LE +#undef MLK_SYS_RISCV32 +#undef MLK_SYS_RISCV64 +#undef MLK_SYS_RISCV64_RVV +#undef MLK_SYS_WINDOWS +#undef MLK_SYS_X86_64 +#undef MLK_SYS_X86_64_AVX2 +/* mlkem/src/verify.h */ +#undef MLK_USE_ASM_VALUE_BARRIER +#undef MLK_VERIFY_H +#undef mlk_ct_opt_blocker_u64 +/* mlkem/src/cbmc.h */ +#undef MLK_CBMC_H +#undef __contract__ +#undef __loop__ + +#if !defined(MLK_CONFIG_FIPS202_CUSTOM_HEADER) +/* + * Undefine macros from FIPS-202 files + */ +/* mlkem/src/fips202/fips202.h */ +#undef FIPS202_X4_DEFAULT_IMPLEMENTATION +#undef MLK_FIPS202_FIPS202_H +#undef SHA3_256_HASHBYTES +#undef SHA3_256_RATE +#undef SHA3_384_RATE +#undef SHA3_512_HASHBYTES +#undef SHA3_512_RATE +#undef SHAKE128_RATE +#undef SHAKE256_RATE +#undef mlk_sha3_256 +#undef mlk_sha3_512 +#undef mlk_shake128_absorb_once +#undef mlk_shake128_init +#undef mlk_shake128_release +#undef mlk_shake128_squeezeblocks +#undef mlk_shake256 +/* mlkem/src/fips202/fips202x4.h */ +#undef MLK_FIPS202_FIPS202X4_H +#undef mlk_shake128x4_absorb_once +#undef mlk_shake128x4_init +#undef mlk_shake128x4_release +#undef mlk_shake128x4_squeezeblocks +#undef mlk_shake256x4 +/* mlkem/src/fips202/keccakf1600.h */ +#undef MLK_FIPS202_KECCAKF1600_H +#undef MLK_KECCAK_LANES +#undef MLK_KECCAK_WAY +#undef mlk_keccakf1600_extract_bytes +#undef mlk_keccakf1600_permute +#undef mlk_keccakf1600_xor_bytes +#undef mlk_keccakf1600x4_extract_bytes +#undef mlk_keccakf1600x4_permute +#undef mlk_keccakf1600x4_xor_bytes +#endif /* !MLK_CONFIG_FIPS202_CUSTOM_HEADER */ + +#if defined(MLK_CONFIG_USE_NATIVE_BACKEND_FIPS202) +/* mlkem/src/fips202/native/api.h */ +#undef MLK_FIPS202_NATIVE_API_H +#undef MLK_NATIVE_FUNC_FALLBACK +#undef MLK_NATIVE_FUNC_SUCCESS +/* mlkem/src/fips202/native/auto.h */ +#undef MLK_FIPS202_NATIVE_AUTO_H +#if defined(MLK_SYS_AARCH64) +/* + * Undefine macros from native code (FIPS202, AArch64) + */ +/* mlkem/src/fips202/native/aarch64/auto.h */ +#undef MLK_FIPS202_NATIVE_AARCH64_AUTO_H +/* mlkem/src/fips202/native/aarch64/src/fips202_native_aarch64.h */ +#undef MLK_FIPS202_NATIVE_AARCH64_SRC_FIPS202_NATIVE_AARCH64_H +#undef mlk_keccak_f1600_x1_scalar_asm +#undef mlk_keccak_f1600_x1_v84a_asm +#undef mlk_keccak_f1600_x2_v84a_asm +#undef mlk_keccak_f1600_x4_v8a_scalar_hybrid_asm +#undef mlk_keccak_f1600_x4_v8a_v84a_scalar_hybrid_asm +#undef mlk_keccakf1600_round_constants +/* mlkem/src/fips202/native/aarch64/x1_scalar.h */ +#undef MLK_FIPS202_AARCH64_NEED_X1_SCALAR +#undef MLK_FIPS202_NATIVE_AARCH64_X1_SCALAR_H +#undef MLK_USE_FIPS202_X1_NATIVE +/* mlkem/src/fips202/native/aarch64/x1_v84a.h */ +#undef MLK_FIPS202_AARCH64_NEED_X1_V84A +#undef MLK_FIPS202_NATIVE_AARCH64_X1_V84A_H +#undef MLK_USE_FIPS202_X1_NATIVE +/* mlkem/src/fips202/native/aarch64/x2_v84a.h */ +#undef MLK_FIPS202_AARCH64_NEED_X2_V84A +#undef MLK_FIPS202_NATIVE_AARCH64_X2_V84A_H +#undef MLK_USE_FIPS202_X4_NATIVE +/* mlkem/src/fips202/native/aarch64/x4_v8a_scalar.h */ +#undef MLK_FIPS202_AARCH64_NEED_X4_V8A_SCALAR_HYBRID +#undef MLK_FIPS202_NATIVE_AARCH64_X4_V8A_SCALAR_H +#undef MLK_USE_FIPS202_X4_NATIVE +/* mlkem/src/fips202/native/aarch64/x4_v8a_v84a_scalar.h */ +#undef MLK_FIPS202_AARCH64_NEED_X4_V8A_V84A_SCALAR_HYBRID +#undef MLK_FIPS202_NATIVE_AARCH64_X4_V8A_V84A_SCALAR_H +#undef MLK_USE_FIPS202_X4_NATIVE +#endif /* MLK_SYS_AARCH64 */ +#if defined(MLK_SYS_X86_64) +/* + * Undefine macros from native code (FIPS202, x86_64) + */ +/* mlkem/src/fips202/native/x86_64/keccak_f1600_x4_avx2.h */ +#undef MLK_FIPS202_NATIVE_X86_64_KECCAK_F1600_X4_AVX2_H +#undef MLK_FIPS202_X86_64_NEED_X4_AVX2 +#undef MLK_USE_FIPS202_X4_NATIVE +/* mlkem/src/fips202/native/x86_64/src/fips202_native_x86_64.h */ +#undef MLK_FIPS202_NATIVE_X86_64_SRC_FIPS202_NATIVE_X86_64_H +#undef mlk_keccak_f1600_x4_avx2 +#undef mlk_keccak_rho56 +#undef mlk_keccak_rho8 +#undef mlk_keccakf1600_round_constants +#endif /* MLK_SYS_X86_64 */ +#if defined(MLK_SYS_ARMV81M_MVE) +/* + * Undefine macros from native code (FIPS202, Armv8.1-M) + */ +/* mlkem/src/fips202/native/armv81m/mve.h */ +#undef MLK_FIPS202_ARMV81M_NEED_X4 +#undef MLK_FIPS202_NATIVE_ARMV81M +#undef MLK_FIPS202_NATIVE_ARMV81M_MVE_H +#undef MLK_USE_FIPS202_X4_EXTRACT_BYTES_NATIVE +#undef MLK_USE_FIPS202_X4_NATIVE +#undef MLK_USE_FIPS202_X4_XOR_BYTES_NATIVE +#undef mlk_keccak_f1600_x4_native_impl +#undef mlk_keccak_f1600_x4_state_extract_bytes +#undef mlk_keccak_f1600_x4_state_xor_bytes +/* mlkem/src/fips202/native/armv81m/src/fips202_native_armv81m.h */ +#undef MLK_FIPS202_NATIVE_ARMV81M_SRC_FIPS202_NATIVE_ARMV81M_H +#undef mlk_keccak_f1600_x4_mve_asm +#undef mlk_keccak_f1600_x4_state_extract_bytes_asm +#undef mlk_keccak_f1600_x4_state_xor_bytes_asm +#undef mlk_keccakf1600_round_constants +#endif /* MLK_SYS_ARMV81M_MVE */ +#endif /* MLK_CONFIG_USE_NATIVE_BACKEND_FIPS202 */ +#if defined(MLK_CONFIG_USE_NATIVE_BACKEND_ARITH) +/* mlkem/src/native/api.h */ +#undef MLK_INVNTT_BOUND +#undef MLK_NATIVE_API_H +#undef MLK_NATIVE_FUNC_FALLBACK +#undef MLK_NATIVE_FUNC_SUCCESS +#undef MLK_NTT_BOUND +/* mlkem/src/native/meta.h */ +#undef MLK_NATIVE_META_H +#if defined(MLK_SYS_AARCH64) +/* + * Undefine macros from native code (Arith, AArch64) + */ +/* mlkem/src/native/aarch64/meta.h */ +#undef MLK_ARITH_BACKEND_AARCH64 +#undef MLK_NATIVE_AARCH64_META_H +#undef MLK_USE_NATIVE_INTT +#undef MLK_USE_NATIVE_NTT +#undef MLK_USE_NATIVE_POLYVEC_BASEMUL_ACC_MONTGOMERY_CACHED +#undef MLK_USE_NATIVE_POLY_MULCACHE_COMPUTE +#undef MLK_USE_NATIVE_POLY_REDUCE +#undef MLK_USE_NATIVE_POLY_TOBYTES +#undef MLK_USE_NATIVE_POLY_TOMONT +#undef MLK_USE_NATIVE_REJ_UNIFORM +/* mlkem/src/native/aarch64/src/arith_native_aarch64.h */ +#undef MLK_NATIVE_AARCH64_SRC_ARITH_NATIVE_AARCH64_H +#undef mlk_aarch64_invntt_zetas_layer12345 +#undef mlk_aarch64_invntt_zetas_layer67 +#undef mlk_aarch64_ntt_zetas_layer12345 +#undef mlk_aarch64_ntt_zetas_layer67 +#undef mlk_aarch64_zetas_mulcache_native +#undef mlk_aarch64_zetas_mulcache_twisted_native +#undef mlk_intt_asm +#undef mlk_ntt_asm +#undef mlk_poly_mulcache_compute_asm +#undef mlk_poly_reduce_asm +#undef mlk_poly_tobytes_asm +#undef mlk_poly_tomont_asm +#undef mlk_polyvec_basemul_acc_montgomery_cached_asm_k2 +#undef mlk_polyvec_basemul_acc_montgomery_cached_asm_k3 +#undef mlk_polyvec_basemul_acc_montgomery_cached_asm_k4 +#undef mlk_rej_uniform_asm +#undef mlk_rej_uniform_table +#endif /* MLK_SYS_AARCH64 */ +#if defined(MLK_SYS_X86_64) +/* + * Undefine macros from native code (Arith, X86_64) + */ +/* mlkem/src/native/x86_64/meta.h */ +#undef MLK_ARITH_BACKEND_X86_64_DEFAULT +#undef MLK_NATIVE_X86_64_META_H +#undef MLK_USE_NATIVE_INTT +#undef MLK_USE_NATIVE_NTT +#undef MLK_USE_NATIVE_NTT_CUSTOM_ORDER +#undef MLK_USE_NATIVE_POLYVEC_BASEMUL_ACC_MONTGOMERY_CACHED +#undef MLK_USE_NATIVE_POLY_COMPRESS_D10 +#undef MLK_USE_NATIVE_POLY_COMPRESS_D11 +#undef MLK_USE_NATIVE_POLY_COMPRESS_D4 +#undef MLK_USE_NATIVE_POLY_COMPRESS_D5 +#undef MLK_USE_NATIVE_POLY_DECOMPRESS_D10 +#undef MLK_USE_NATIVE_POLY_DECOMPRESS_D11 +#undef MLK_USE_NATIVE_POLY_DECOMPRESS_D4 +#undef MLK_USE_NATIVE_POLY_DECOMPRESS_D5 +#undef MLK_USE_NATIVE_POLY_FROMBYTES +#undef MLK_USE_NATIVE_POLY_MULCACHE_COMPUTE +#undef MLK_USE_NATIVE_POLY_REDUCE +#undef MLK_USE_NATIVE_POLY_TOBYTES +#undef MLK_USE_NATIVE_POLY_TOMONT +#undef MLK_USE_NATIVE_REJ_UNIFORM +/* mlkem/src/native/x86_64/src/arith_native_x86_64.h */ +#undef MLK_AVX2_REJ_UNIFORM_BUFLEN +#undef MLK_NATIVE_X86_64_SRC_ARITH_NATIVE_X86_64_H +#undef mlk_invntt_avx2 +#undef mlk_ntt_avx2 +#undef mlk_nttfrombytes_avx2 +#undef mlk_ntttobytes_avx2 +#undef mlk_nttunpack_avx2 +#undef mlk_poly_compress_d10_avx2 +#undef mlk_poly_compress_d11_avx2 +#undef mlk_poly_compress_d4_avx2 +#undef mlk_poly_compress_d5_avx2 +#undef mlk_poly_decompress_d10_avx2 +#undef mlk_poly_decompress_d11_avx2 +#undef mlk_poly_decompress_d4_avx2 +#undef mlk_poly_decompress_d5_avx2 +#undef mlk_poly_mulcache_compute_avx2 +#undef mlk_polyvec_basemul_acc_montgomery_cached_asm_k2 +#undef mlk_polyvec_basemul_acc_montgomery_cached_asm_k3 +#undef mlk_polyvec_basemul_acc_montgomery_cached_asm_k4 +#undef mlk_reduce_avx2 +#undef mlk_rej_uniform_asm +#undef mlk_rej_uniform_table +#undef mlk_tomont_avx2 +/* mlkem/src/native/x86_64/src/compress_consts.h */ +#undef MLK_NATIVE_X86_64_SRC_COMPRESS_CONSTS_H +#undef mlk_compress_d10_data +#undef mlk_compress_d11_data +#undef mlk_compress_d4_data +#undef mlk_compress_d5_data +#undef mlk_decompress_d10_data +#undef mlk_decompress_d11_data +#undef mlk_decompress_d4_data +#undef mlk_decompress_d5_data +/* mlkem/src/native/x86_64/src/consts.h */ +#undef MLK_AVX2_BACKEND_DATA_OFFSET_MULCACHE_TWIDDLES +#undef MLK_AVX2_BACKEND_DATA_OFFSET_REVIDXB +#undef MLK_AVX2_BACKEND_DATA_OFFSET_REVIDXD +#undef MLK_AVX2_BACKEND_DATA_OFFSET_ZETAS_EXP +#undef MLK_NATIVE_X86_64_SRC_CONSTS_H +#undef mlk_qdata +#endif /* MLK_SYS_X86_64 */ +#if defined(MLK_SYS_RISCV64) +/* + * Undefine macros from native code (Arith, RISC-V 64) + */ +/* mlkem/src/native/riscv64/meta.h */ +#undef MLK_ARITH_BACKEND_RISCV64 +#undef MLK_NATIVE_RISCV64_META_H +#undef MLK_USE_NATIVE_INTT +#undef MLK_USE_NATIVE_NTT +#undef MLK_USE_NATIVE_POLYVEC_BASEMUL_ACC_MONTGOMERY_CACHED +#undef MLK_USE_NATIVE_POLY_MULCACHE_COMPUTE +#undef MLK_USE_NATIVE_POLY_REDUCE +#undef MLK_USE_NATIVE_POLY_TOMONT +#undef MLK_USE_NATIVE_REJ_UNIFORM +/* mlkem/src/native/riscv64/src/arith_native_riscv64.h */ +#undef MLK_NATIVE_RISCV64_SRC_ARITH_NATIVE_RISCV64_H +#undef mlk_rv64v_poly_add +#undef mlk_rv64v_poly_basemul_mont_add_k2 +#undef mlk_rv64v_poly_basemul_mont_add_k3 +#undef mlk_rv64v_poly_basemul_mont_add_k4 +#undef mlk_rv64v_poly_invntt_tomont +#undef mlk_rv64v_poly_ntt +#undef mlk_rv64v_poly_reduce +#undef mlk_rv64v_poly_sub +#undef mlk_rv64v_poly_tomont +#undef mlk_rv64v_rej_uniform +/* mlkem/src/native/riscv64/src/rv64v_debug.h */ +#undef MLK_NATIVE_RISCV64_SRC_RV64V_DEBUG_H +#undef mlk_assert_abs_bound_int16m1 +#undef mlk_assert_abs_bound_int16m2 +#undef mlk_assert_bound_int16m1 +#undef mlk_assert_bound_int16m2 +#undef mlk_debug_check_bounds_int16m1 +#undef mlk_debug_check_bounds_int16m2 +#endif /* MLK_SYS_RISCV64 */ +#endif /* MLK_CONFIG_USE_NATIVE_BACKEND_ARITH */ +#endif /* !MLK_CONFIG_MONOBUILD_KEEP_SHARED_HEADERS */ diff --git a/mlkem_native/mlkem_native.h b/mlkem_native/mlkem_native.h new file mode 100644 index 0000000..302ca3f --- /dev/null +++ b/mlkem_native/mlkem_native.h @@ -0,0 +1,538 @@ +/* + * Copyright (c) The mlkem-native project authors + * SPDX-License-Identifier: Apache-2.0 OR ISC OR MIT + */ + +/* References + * ========== + * + * - [FIPS203] + * FIPS 203 Module-Lattice-Based Key-Encapsulation Mechanism Standard + * National Institute of Standards and Technology + * https://csrc.nist.gov/pubs/fips/203/final + */ + +#ifndef MLK_H +#define MLK_H + +/* + * Public API for mlkem-native. + * + * This header defines the public API of a single build of mlkem-native. + * + * Make sure the configuration file is in the include path + * (this is "mlkem_native_config.h" by default, or MLK_CONFIG_FILE if defined). + * + * # Multi-level builds + * + * This header specifies a build of mlkem-native for a fixed security level. + * If you need multiple security levels, leave the security level unspecified + * in the configuration file and include this header multiple times, setting + * MLK_CONFIG_PARAMETER_SET accordingly for each, and #undef'ing the MLK_H + * guard to allow multiple inclusions. + * + * # Legacy configuration (deprecated) + * + * Instead of providing the config file used for the build, you can + * alternatively set the following configuration options prior to + * including this header. + * + * This method of configuration is deprecated. + * It will be removed in mlkem-native-v2. + * + * - MLK_CONFIG_API_PARAMETER_SET [required] + * + * The parameter set used for the build; 512, 768, or 1024. + * + * - MLK_CONFIG_API_NAMESPACE_PREFIX [required] + * + * The namespace prefix used for the build. + * + * NOTE: + * For a multi-level build, you must include the 512/768/1024 suffixes + * in MLK_CONFIG_API_NAMESPACE_PREFIX. + * + * - MLK_CONFIG_API_NO_SUPERCOP [optional] + * + * By default, this header will also expose the mlkem-native API in the + * SUPERCOP naming convention crypto_kem_xxx. If you don't want/need this, + * set MLK_CONFIG_API_NO_SUPERCOP. You must set this for a multi-level build. + * + * - MLK_CONFIG_API_CONSTANTS_ONLY [optional] + * + * If you don't want this header to expose any function declarations, + * but only constants for the sizes of key material, set + * MLK_CONFIG_API_CONSTANTS_ONLY. In this case, you don't need to set + * MLK_CONFIG_API_PARAMETER_SET or MLK_CONFIG_API_NAMESPACE_PREFIX, + * nor include a configuration. + * + * - MLK_CONFIG_API_QUALIFIER [optional] + * + * Qualifier to apply to external API. + * + ******************************************************************************/ + +/******************************* Key sizes ************************************/ + +/* Sizes of cryptographic material, per parameter set */ +/* See mlkem/common.h for the arithmetic expressions giving rise to these */ +/* check-magic: off */ +#define MLKEM512_SECRETKEYBYTES 1632 +#define MLKEM512_PUBLICKEYBYTES 800 +#define MLKEM512_CIPHERTEXTBYTES 768 + +#define MLKEM768_SECRETKEYBYTES 2400 +#define MLKEM768_PUBLICKEYBYTES 1184 +#define MLKEM768_CIPHERTEXTBYTES 1088 + +#define MLKEM1024_SECRETKEYBYTES 3168 +#define MLKEM1024_PUBLICKEYBYTES 1568 +#define MLKEM1024_CIPHERTEXTBYTES 1568 +/* check-magic: on */ + +/* Size of randomness coins in bytes (level-independent) */ +#define MLKEM_SYMBYTES 32 +#define MLKEM512_SYMBYTES MLKEM_SYMBYTES +#define MLKEM768_SYMBYTES MLKEM_SYMBYTES +#define MLKEM1024_SYMBYTES MLKEM_SYMBYTES +/* Size of shared secret in bytes (level-independent) */ +#define MLKEM_BYTES 32 +#define MLKEM512_BYTES MLKEM_BYTES +#define MLKEM768_BYTES MLKEM_BYTES +#define MLKEM1024_BYTES MLKEM_BYTES + +/* Sizes of cryptographic material, as a function of LVL=512,768,1024 */ +#define MLKEM_SECRETKEYBYTES_(LVL) MLKEM##LVL##_SECRETKEYBYTES +#define MLKEM_PUBLICKEYBYTES_(LVL) MLKEM##LVL##_PUBLICKEYBYTES +#define MLKEM_CIPHERTEXTBYTES_(LVL) MLKEM##LVL##_CIPHERTEXTBYTES +#define MLKEM_SECRETKEYBYTES(LVL) MLKEM_SECRETKEYBYTES_(LVL) +#define MLKEM_PUBLICKEYBYTES(LVL) MLKEM_PUBLICKEYBYTES_(LVL) +#define MLKEM_CIPHERTEXTBYTES(LVL) MLKEM_CIPHERTEXTBYTES_(LVL) + +/****************************** Error codes ***********************************/ + +/* Generic failure condition */ +#define MLK_ERR_FAIL -1 +/* An allocation failed. This can only happen if MLK_CONFIG_CUSTOM_ALLOC_FREE + * is defined and the provided MLK_CUSTOM_ALLOC can fail. */ +#define MLK_ERR_OUT_OF_MEMORY -2 +/* An rng failure occured. Might be due to insufficient entropy or + * system misconfiguration. */ +#define MLK_ERR_RNG_FAIL -3 + +/****************************** Function API **********************************/ + +#define MLK_API_CONCAT_(x, y) x##y +#define MLK_API_CONCAT(x, y) MLK_API_CONCAT_(x, y) +#define MLK_API_CONCAT_UNDERSCORE(x, y) MLK_API_CONCAT(MLK_API_CONCAT(x, _), y) + +#if !defined(MLK_CONFIG_API_PARAMETER_SET) +/* Recommended configuration via same config file as used for the build. */ + +/* For now, we derive the legacy API configuration MLK_CONFIG_API_XXX from + * the config file. In mlkem-native-v2, this will be removed and we will + * exclusively work with MLK_CONFIG_XXX. */ + +/* You need to make sure the config file is in the include path. */ +#if defined(MLK_CONFIG_FILE) +#include MLK_CONFIG_FILE +#else +#include "mlkem_native_config.h" +#endif + +#define MLK_CONFIG_API_PARAMETER_SET MLK_CONFIG_PARAMETER_SET + +#if defined(MLK_CONFIG_MULTILEVEL_BUILD) +#define MLK_CONFIG_API_NAMESPACE_PREFIX \ + MLK_API_CONCAT(MLK_CONFIG_NAMESPACE_PREFIX, MLK_CONFIG_PARAMETER_SET) +#else +#define MLK_CONFIG_API_NAMESPACE_PREFIX MLK_CONFIG_NAMESPACE_PREFIX +#endif + +#if defined(MLK_CONFIG_NO_SUPERCOP) +#define MLK_CONFIG_API_NO_SUPERCOP +#endif + +#if defined(MLK_CONFIG_CONSTANTS_ONLY) +#define MLK_CONFIG_API_CONSTANTS_ONLY +#endif + +#if defined(MLK_CONFIG_EXTERNAL_API_QUALIFIER) +#define MLK_CONFIG_API_QUALIFIER MLK_CONFIG_EXTERNAL_API_QUALIFIER +#endif + +#else /* !MLK_CONFIG_API_PARAMETER_SET */ + +#define MLK_API_LEGACY_CONFIG + +#endif /* MLK_CONFIG_API_PARAMETER_SET */ + +#define MLK_API_NAMESPACE(sym) \ + MLK_API_CONCAT_UNDERSCORE(MLK_CONFIG_API_NAMESPACE_PREFIX, sym) + +#if defined(__GNUC__) || defined(clang) +#define MLK_API_MUST_CHECK_RETURN_VALUE __attribute__((warn_unused_result)) +#else +#define MLK_API_MUST_CHECK_RETURN_VALUE +#endif + +#if defined(MLK_CONFIG_API_QUALIFIER) +#define MLK_API_QUALIFIER MLK_CONFIG_API_QUALIFIER +#else +#define MLK_API_QUALIFIER +#endif + +#if !defined(MLK_CONFIG_API_CONSTANTS_ONLY) + +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/************************************************* + * Name: crypto_kem_keypair_derand + * + * Description: Generates public and private key + * for CCA-secure ML-KEM key encapsulation mechanism + * + * Arguments: - uint8_t pk[]: pointer to output public key, an array of + * length MLKEM{512,768,1024}_PUBLICKEYBYTES bytes. + * - uint8_t sk[]: pointer to output private key, an array of + * of MLKEM{512,768,1024}_SECRETKEYBYTES bytes. + * - uint8_t *coins: pointer to input randomness, an array of + * 2*MLKEM_SYMBYTES uniformly random bytes. + * + * Returns: - 0: On success + * - MLK_ERR_FAIL: If MLK_CONFIG_KEYGEN_PCT is enabled and the + * PCT failed. + * - MLK_ERR_OUT_OF_MEMORY: If MLK_CONFIG_CUSTOM_ALLOC_FREE is + * used and an allocation via MLK_CUSTOM_ALLOC returned NULL. + * + * Specification: Implements @[FIPS203, Algorithm 16, ML-KEM.KeyGen_Internal] + * + **************************************************/ +MLK_API_QUALIFIER +MLK_API_MUST_CHECK_RETURN_VALUE +int MLK_API_NAMESPACE(keypair_derand)( + uint8_t pk[MLKEM_PUBLICKEYBYTES(MLK_CONFIG_API_PARAMETER_SET)], + uint8_t sk[MLKEM_SECRETKEYBYTES(MLK_CONFIG_API_PARAMETER_SET)], + const uint8_t coins[2 * MLKEM_SYMBYTES] +#ifdef MLK_CONFIG_CONTEXT_PARAMETER + , + MLK_CONFIG_CONTEXT_PARAMETER_TYPE context +#endif +); + + +#if !defined(MLK_CONFIG_NO_RANDOMIZED_API) +/************************************************* + * Name: crypto_kem_keypair + * + * Description: Generates public and private key + * for CCA-secure ML-KEM key encapsulation mechanism + * + * Arguments: - uint8_t *pk: pointer to output public key, an array of + * MLKEM{512,768,1024}_PUBLICKEYBYTES bytes. + * - uint8_t *sk: pointer to output private key, an array of + * MLKEM{512,768,1024}_SECRETKEYBYTES bytes. + * + * Returns: - 0: On success + * - MLK_ERR_FAIL: If MLK_CONFIG_KEYGEN_PCT is enabled and the + * PCT failed. + * - MLK_ERR_OUT_OF_MEMORY: If MLK_CONFIG_CUSTOM_ALLOC_FREE is + * used and an allocation via MLK_CUSTOM_ALLOC returned NULL. + * - MLK_ERR_RNG_FAIL: Random number generation failed. + * + * Specification: Implements @[FIPS203, Algorithm 19, ML-KEM.KeyGen] + * + **************************************************/ +MLK_API_QUALIFIER +MLK_API_MUST_CHECK_RETURN_VALUE +int MLK_API_NAMESPACE(keypair)( + uint8_t pk[MLKEM_PUBLICKEYBYTES(MLK_CONFIG_API_PARAMETER_SET)], + uint8_t sk[MLKEM_SECRETKEYBYTES(MLK_CONFIG_API_PARAMETER_SET)] +#ifdef MLK_CONFIG_CONTEXT_PARAMETER + , + MLK_CONFIG_CONTEXT_PARAMETER_TYPE context +#endif +); +#endif /* !MLK_CONFIG_NO_RANDOMIZED_API */ + +/************************************************* + * Name: crypto_kem_enc_derand + * + * Description: Generates cipher text and shared + * secret for given public key + * + * Arguments: - uint8_t *ct: pointer to output cipher text, an array of + * MLKEM{512,768,1024}_CIPHERTEXTBYTES bytes. + * - uint8_t *ss: pointer to output shared secret, an array of + * MLKEM_BYTES bytes. + * - const uint8_t *pk: pointer to input public key, an array of + * MLKEM{512,768,1024}_PUBLICKEYBYTES bytes. + * - const uint8_t *coins: pointer to input randomness, an array of + * MLKEM_SYMBYTES bytes. + * + * Returns: - 0 on success + * - MLK_ERR_FAIL: If the 'modulus check' @[FIPS203, Section 7.2] + * for the public key fails. + * - MLK_ERR_OUT_OF_MEMORY: If MLK_CONFIG_CUSTOM_ALLOC_FREE is + * used and an allocation via MLK_CUSTOM_ALLOC returned NULL. + * + * Specification: Implements @[FIPS203, Algorithm 17, ML-KEM.Encaps_Internal] + * + **************************************************/ +MLK_API_QUALIFIER +MLK_API_MUST_CHECK_RETURN_VALUE +int MLK_API_NAMESPACE(enc_derand)( + uint8_t ct[MLKEM_CIPHERTEXTBYTES(MLK_CONFIG_API_PARAMETER_SET)], + uint8_t ss[MLKEM_BYTES], + const uint8_t pk[MLKEM_PUBLICKEYBYTES(MLK_CONFIG_API_PARAMETER_SET)], + const uint8_t coins[MLKEM_SYMBYTES] +#ifdef MLK_CONFIG_CONTEXT_PARAMETER + , + MLK_CONFIG_CONTEXT_PARAMETER_TYPE context +#endif +); + +#if !defined(MLK_CONFIG_NO_RANDOMIZED_API) +/************************************************* + * Name: crypto_kem_enc + * + * Description: Generates cipher text and shared + * secret for given public key + * + * Arguments: - uint8_t *ct: pointer to output cipher text, an array of + * MLKEM{512,768,1024}_CIPHERTEXTBYTES bytes. + * - uint8_t *ss: pointer to output shared secret, an array of + * MLKEM_BYTES bytes. + * - const uint8_t *pk: pointer to input public key, an array of + * MLKEM{512,768,1024}_PUBLICKEYBYTES bytes. + * + * Returns: - 0 on success + * - MLK_ERR_FAIL: If the 'modulus check' @[FIPS203, Section 7.2] + * for the public key fails. + * - MLK_ERR_OUT_OF_MEMORY: If MLK_CONFIG_CUSTOM_ALLOC_FREE is + * used and an allocation via MLK_CUSTOM_ALLOC returned NULL. + * - MLK_ERR_RNG_FAIL: Random number generation failed. + * + * Specification: Implements @[FIPS203, Algorithm 20, ML-KEM.Encaps] + * + **************************************************/ +MLK_API_QUALIFIER +MLK_API_MUST_CHECK_RETURN_VALUE +int MLK_API_NAMESPACE(enc)( + uint8_t ct[MLKEM_CIPHERTEXTBYTES(MLK_CONFIG_API_PARAMETER_SET)], + uint8_t ss[MLKEM_BYTES], + const uint8_t pk[MLKEM_PUBLICKEYBYTES(MLK_CONFIG_API_PARAMETER_SET)] +#ifdef MLK_CONFIG_CONTEXT_PARAMETER + , + MLK_CONFIG_CONTEXT_PARAMETER_TYPE context +#endif +); +#endif /* !MLK_CONFIG_NO_RANDOMIZED_API */ + +/************************************************* + * Name: crypto_kem_dec + * + * Description: Generates shared secret for given + * cipher text and private key + * + * Arguments: - uint8_t *ss: pointer to output shared secret, an array of + * MLKEM_BYTES bytes. + * - const uint8_t *ct: pointer to input cipher text, an array of + * MLKEM{512,768,1024}_CIPHERTEXTBYTES bytes. + * - const uint8_t *sk: pointer to input private key, an array of + * MLKEM{512,768,1024}_SECRETKEYBYTES bytes. + * + * Returns: - 0 on success + * - MLK_ERR_FAIL: If the 'hash check' @[FIPS203, Section 7.3] + * for the secret key fails. + * - MLK_ERR_OUT_OF_MEMORY: If MLK_CONFIG_CUSTOM_ALLOC_FREE is + * used and an allocation via MLK_CUSTOM_ALLOC returned NULL. + * + * Specification: Implements @[FIPS203, Algorithm 21, ML-KEM.Decaps] + * + **************************************************/ +MLK_API_QUALIFIER +MLK_API_MUST_CHECK_RETURN_VALUE +int MLK_API_NAMESPACE(dec)( + uint8_t ss[MLKEM_BYTES], + const uint8_t ct[MLKEM_CIPHERTEXTBYTES(MLK_CONFIG_API_PARAMETER_SET)], + const uint8_t sk[MLKEM_SECRETKEYBYTES(MLK_CONFIG_API_PARAMETER_SET)] +#ifdef MLK_CONFIG_CONTEXT_PARAMETER + , + MLK_CONFIG_CONTEXT_PARAMETER_TYPE context +#endif +); + + +/************************************************* + * Name: crypto_kem_check_pk + * + * Description: Implements modulus check mandated by FIPS 203, + * i.e., ensures that coefficients are in [0,q-1]. + * + * Arguments: - const uint8_t *pk: pointer to input public key, an array of + * MLKEM{512,768,1024}_PUBLICKEYBYTES bytes. + * + * Returns: - 0 on success + * - MLK_ERR_FAIL: If the modulus check failed. + * - MLK_ERR_OUT_OF_MEMORY: If MLK_CONFIG_CUSTOM_ALLOC_FREE is + * used and an allocation via MLK_CUSTOM_ALLOC returned NULL. + * + * Specification: Implements @[FIPS203, Section 7.2, 'modulus check'] + * + **************************************************/ +MLK_API_QUALIFIER +MLK_API_MUST_CHECK_RETURN_VALUE +int MLK_API_NAMESPACE(check_pk)( + const uint8_t pk[MLKEM_PUBLICKEYBYTES(MLK_CONFIG_API_PARAMETER_SET)] +#ifdef MLK_CONFIG_CONTEXT_PARAMETER + , + MLK_CONFIG_CONTEXT_PARAMETER_TYPE context +#endif +); + +/************************************************* + * Name: crypto_kem_check_sk + * + * Description: Implements public key hash check mandated by FIPS 203, + * i.e., ensures that + * sk[768π‘˜+32 ∢ 768π‘˜+64] = H(pk)= H(sk[384π‘˜ : 768π‘˜+32]) + * + * Arguments: - const uint8_t *sk: pointer to input private key, an array of + * MLKEM{512,768,1024}_SECRETKEYBYTES bytes. + * + * Returns: - 0 on success + * - MLK_ERR_FAIL: If the public key hash check failed. + * - MLK_ERR_OUT_OF_MEMORY: If MLK_CONFIG_CUSTOM_ALLOC_FREE is + * used and an allocation via MLK_CUSTOM_ALLOC returned NULL. + * + * Specification: Implements @[FIPS203, Section 7.3, 'hash check'] + * + **************************************************/ +MLK_API_QUALIFIER +MLK_API_MUST_CHECK_RETURN_VALUE +int MLK_API_NAMESPACE(check_sk)( + const uint8_t sk[MLKEM_SECRETKEYBYTES(MLK_CONFIG_API_PARAMETER_SET)] +#ifdef MLK_CONFIG_CONTEXT_PARAMETER + , + MLK_CONFIG_CONTEXT_PARAMETER_TYPE context +#endif +); + +#ifdef __cplusplus +} +#endif + +/****************************** SUPERCOP API *********************************/ + +#if !defined(MLK_CONFIG_API_NO_SUPERCOP) +/* Export API in SUPERCOP naming scheme CRYPTO_xxx / crypto_kem_xxx */ +#define CRYPTO_SECRETKEYBYTES MLKEM_SECRETKEYBYTES(MLK_CONFIG_API_PARAMETER_SET) +#define CRYPTO_PUBLICKEYBYTES MLKEM_PUBLICKEYBYTES(MLK_CONFIG_API_PARAMETER_SET) +#define CRYPTO_CIPHERTEXTBYTES \ + MLKEM_CIPHERTEXTBYTES(MLK_CONFIG_API_PARAMETER_SET) +#define CRYPTO_SYMBYTES MLKEM_SYMBYTES +#define CRYPTO_BYTES MLKEM_BYTES + +#define crypto_kem_keypair_derand MLK_API_NAMESPACE(keypair_derand) +#define crypto_kem_keypair MLK_API_NAMESPACE(keypair) +#define crypto_kem_enc_derand MLK_API_NAMESPACE(enc_derand) +#define crypto_kem_enc MLK_API_NAMESPACE(enc) +#define crypto_kem_dec MLK_API_NAMESPACE(dec) +#define crypto_kem_check_pk MLK_API_NAMESPACE(check_pk) +#define crypto_kem_check_sk MLK_API_NAMESPACE(check_sk) + +#else /* !MLK_CONFIG_API_NO_SUPERCOP */ + +/* If the SUPERCOP API is not needed, we can undefine the various helper macros + * above. Otherwise, they are needed for lazy evaluation of crypto_kem_xxx. */ +#if !defined(MLK_API_LEGACY_CONFIG) +#undef MLK_CONFIG_API_PARAMETER_SET +#undef MLK_CONFIG_API_NAMESPACE_PREFIX +#undef MLK_CONFIG_API_NO_SUPERCOP +#undef MLK_CONFIG_API_CONSTANTS_ONLY +#undef MLK_CONFIG_API_QUALIFIER +#endif /* !MLK_API_LEGACY_CONFIG */ + +#undef MLK_API_CONCAT +#undef MLK_API_CONCAT_ +#undef MLK_API_CONCAT_UNDERSCORE +#undef MLK_API_NAMESPACE +#undef MLK_API_MUST_CHECK_RETURN_VALUE +#undef MLK_API_QUALIFIER +#undef MLK_API_LEGACY_CONFIG + +#endif /* MLK_CONFIG_API_NO_SUPERCOP */ +#endif /* !MLK_CONFIG_API_CONSTANTS_ONLY */ + + +/***************************** Memory Usage **********************************/ + +/* + * By default mlkem-native performs all memory allocations on the stack. + * Alternatively, mlkem-native supports custom allocation of large structures + * through the `MLK_CONFIG_CUSTOM_ALLOC_FREE` configuration option. + * See mlkem_native_config.h for details. + * + * `MLK_TOTAL_ALLOC_{512,768,1024}_{KEYPAIR,ENCAPS,DECAPS}` indicates the + * maximum (accumulative) allocation via MLK_ALLOC for each parameter set and + * operation. Note that some stack allocation remains even when using custom + * allocators, so these values are lower than total stack usage with the default + * stack-only allocation. + * + * These constants may be used to implement custom allocations using a + * fixed-sized buffer and a simple allocator (e.g., bump allocator). + */ +/* check-magic: off */ +#define MLK_TOTAL_ALLOC_512_KEYPAIR_NO_PCT 5824 +#define MLK_TOTAL_ALLOC_512_KEYPAIR_PCT 10048 +#define MLK_TOTAL_ALLOC_512_ENCAPS 8384 +#define MLK_TOTAL_ALLOC_512_DECAPS 9152 +#define MLK_TOTAL_ALLOC_768_KEYPAIR_NO_PCT 10176 +#define MLK_TOTAL_ALLOC_768_KEYPAIR_PCT 15552 +#define MLK_TOTAL_ALLOC_768_ENCAPS 13248 +#define MLK_TOTAL_ALLOC_768_DECAPS 14336 +#define MLK_TOTAL_ALLOC_1024_KEYPAIR_NO_PCT 15552 +#define MLK_TOTAL_ALLOC_1024_KEYPAIR_PCT 22400 +#define MLK_TOTAL_ALLOC_1024_ENCAPS 19136 +#define MLK_TOTAL_ALLOC_1024_DECAPS 20704 +/* check-magic: on */ + +/* + * MLK_TOTAL_ALLOC_*_KEYPAIR adapts based on MLK_CONFIG_KEYGEN_PCT. + * For legacy config, we don't know which options are used, so assume + * the worst case (PCT enabled). + */ +#if defined(MLK_API_LEGACY_CONFIG) || defined(MLK_CONFIG_KEYGEN_PCT) +#define MLK_TOTAL_ALLOC_512_KEYPAIR MLK_TOTAL_ALLOC_512_KEYPAIR_PCT +#define MLK_TOTAL_ALLOC_768_KEYPAIR MLK_TOTAL_ALLOC_768_KEYPAIR_PCT +#define MLK_TOTAL_ALLOC_1024_KEYPAIR MLK_TOTAL_ALLOC_1024_KEYPAIR_PCT +#else +#define MLK_TOTAL_ALLOC_512_KEYPAIR MLK_TOTAL_ALLOC_512_KEYPAIR_NO_PCT +#define MLK_TOTAL_ALLOC_768_KEYPAIR MLK_TOTAL_ALLOC_768_KEYPAIR_NO_PCT +#define MLK_TOTAL_ALLOC_1024_KEYPAIR MLK_TOTAL_ALLOC_1024_KEYPAIR_NO_PCT +#endif + +#define MLK_MAX3_(a, b, c) \ + ((a) > (b) ? ((a) > (c) ? (a) : (c)) : ((b) > (c) ? (b) : (c))) + +/* + * `MLK_TOTAL_ALLOC_{512,768,1024}` is the maximum across all operations for + * each parameter set. + */ +#define MLK_TOTAL_ALLOC_512 \ + MLK_MAX3_(MLK_TOTAL_ALLOC_512_KEYPAIR, MLK_TOTAL_ALLOC_512_ENCAPS, \ + MLK_TOTAL_ALLOC_512_DECAPS) +#define MLK_TOTAL_ALLOC_768 \ + MLK_MAX3_(MLK_TOTAL_ALLOC_768_KEYPAIR, MLK_TOTAL_ALLOC_768_ENCAPS, \ + MLK_TOTAL_ALLOC_768_DECAPS) +#define MLK_TOTAL_ALLOC_1024 \ + MLK_MAX3_(MLK_TOTAL_ALLOC_1024_KEYPAIR, MLK_TOTAL_ALLOC_1024_ENCAPS, \ + MLK_TOTAL_ALLOC_1024_DECAPS) + +#endif /* !MLK_H */ diff --git a/mlkem_native/mlkem_native_config.h b/mlkem_native/mlkem_native_config.h new file mode 100644 index 0000000..19450f3 --- /dev/null +++ b/mlkem_native/mlkem_native_config.h @@ -0,0 +1,64 @@ +/* + * mlkem-native configuration for OnlyKey (NXP MK20DX256, Cortex-M4) + * ML-KEM-768 (FIPS 203), C-only portable backend + * + * SPDX-License-Identifier: Apache-2.0 OR ISC OR MIT + */ + +#ifndef MLK_CONFIG_H +#define MLK_CONFIG_H + +/* ML-KEM-768 (NIST Level 3) */ +#ifndef MLK_CONFIG_PARAMETER_SET +#define MLK_CONFIG_PARAMETER_SET 768 +#endif + +/* Namespace prefix for symbols */ +#if !defined(MLK_CONFIG_NAMESPACE_PREFIX) +#define MLK_CONFIG_NAMESPACE_PREFIX MLK_DEFAULT_NAMESPACE_PREFIX +#endif + +/* No native assembly backends β€” Cortex-M4 not supported by existing + * AArch64/x86_64/RVV/Helium backends. Pure portable C. */ + +/* Build-only options */ +#if defined(MLK_BUILD_INTERNAL) + +/* + * Custom randombytes wrapper + * + * OnlyKey's existing randombytes has signature: + * void randombytes(unsigned char *x, unsigned long long xlen) + * + * mlkem-native expects: + * int mlk_randombytes(uint8_t *out, size_t outlen) β€” returns 0 on success + * + * We bridge with a custom wrapper. Implement onlykey_mlkem_randombytes() + * in okcrypto.cpp using your preferred entropy source. + */ +#define MLK_CONFIG_CUSTOM_RANDOMBYTES +#if !defined(__ASSEMBLER__) +#include +#include +#include "src/sys.h" + +extern int onlykey_mlkem_randombytes(uint8_t *out, size_t outlen); + +static MLK_INLINE int mlk_randombytes(uint8_t *out, size_t outlen) +{ + return onlykey_mlkem_randombytes(out, outlen); +} +#endif /* !__ASSEMBLER__ */ + +#endif /* MLK_BUILD_INTERNAL */ + +/* Default namespace */ +#if MLK_CONFIG_PARAMETER_SET == 512 +#define MLK_DEFAULT_NAMESPACE_PREFIX PQCP_MLKEM_NATIVE_MLKEM512 +#elif MLK_CONFIG_PARAMETER_SET == 768 +#define MLK_DEFAULT_NAMESPACE_PREFIX PQCP_MLKEM_NATIVE_MLKEM768 +#elif MLK_CONFIG_PARAMETER_SET == 1024 +#define MLK_DEFAULT_NAMESPACE_PREFIX PQCP_MLKEM_NATIVE_MLKEM1024 +#endif + +#endif /* !MLK_CONFIG_H */ diff --git a/mlkem_native/src/cbmc.h b/mlkem_native/src/cbmc.h new file mode 100644 index 0000000..80e1a36 --- /dev/null +++ b/mlkem_native/src/cbmc.h @@ -0,0 +1,174 @@ +/* + * Copyright (c) The mlkem-native project authors + * SPDX-License-Identifier: Apache-2.0 OR ISC OR MIT + */ + +#ifndef MLK_CBMC_H +#define MLK_CBMC_H +/*************************************************** + * Basic replacements for __CPROVER_XXX contracts + ***************************************************/ +#ifndef CBMC + +#define __contract__(x) +#define __loop__(x) + +#else /* !CBMC */ + + +#define __contract__(x) x +#define __loop__(x) x + +/* https://diffblue.github.io/cbmc/contracts-assigns.html */ +#define assigns(...) __CPROVER_assigns(__VA_ARGS__) + +/* https://diffblue.github.io/cbmc/contracts-requires-ensures.html */ +#define requires(...) __CPROVER_requires(__VA_ARGS__) +#define ensures(...) __CPROVER_ensures(__VA_ARGS__) +/* https://diffblue.github.io/cbmc/contracts-loops.html */ +#define invariant(...) __CPROVER_loop_invariant(__VA_ARGS__) +#define decreases(...) __CPROVER_decreases(__VA_ARGS__) +/* cassert to avoid confusion with in-built assert */ +#define cassert(x) __CPROVER_assert(x, "cbmc assertion failed") +#define assume(...) __CPROVER_assume(__VA_ARGS__) + +/*************************************************** + * Macros for "expression" forms that may appear + * _inside_ top-level contracts. + ***************************************************/ + +/* + * function return value - useful inside ensures + * https://diffblue.github.io/cbmc/contracts-functions.html + */ +#define return_value (__CPROVER_return_value) + +/* + * assigns l-value targets + * https://diffblue.github.io/cbmc/contracts-assigns.html + */ +#define object_whole(...) __CPROVER_object_whole(__VA_ARGS__) +#define memory_slice(...) __CPROVER_object_upto(__VA_ARGS__) + +/* + * Pointer-related predicates + * https://diffblue.github.io/cbmc/contracts-memory-predicates.html + */ +#define memory_no_alias(...) __CPROVER_is_fresh(__VA_ARGS__) +#define readable(...) __CPROVER_r_ok(__VA_ARGS__) +#define writeable(...) __CPROVER_w_ok(__VA_ARGS__) + +/* Maximum supported buffer size + * + * Larger buffers may be supported, but due to internal modeling constraints + * in CBMC, the proofs of memory- and type-safety won't be able to run. + * + * If you find yourself in need for a buffer size larger than this, + * please contact the maintainers, so we can prioritize work to relax + * this somewhat artificial bound. + */ +#define MLK_MAX_BUFFER_SIZE (SIZE_MAX >> 12) + +/* + * History variables + * https://diffblue.github.io/cbmc/contracts-history-variables.html + */ +#define old(...) __CPROVER_old(__VA_ARGS__) +#define loop_entry(...) __CPROVER_loop_entry(__VA_ARGS__) + +/* + * Quantifiers + * Note that the range on qvar is _exclusive_ between qvar_lb .. qvar_ub + * https://diffblue.github.io/cbmc/contracts-quantifiers.html + */ + +/* + * Prevent clang-format from corrupting CBMC's special ==> operator + */ +/* clang-format off */ +#define forall(qvar, qvar_lb, qvar_ub, predicate) \ + __CPROVER_forall \ + { \ + unsigned qvar; \ + ((qvar_lb) <= (qvar) && (qvar) < (qvar_ub)) ==> (predicate) \ + } + +#define exists(qvar, qvar_lb, qvar_ub, predicate) \ + __CPROVER_exists \ + { \ + unsigned qvar; \ + ((qvar_lb) <= (qvar) && (qvar) < (qvar_ub)) && (predicate) \ + } +/* clang-format on */ + +/*************************************************** + * Convenience macros for common contract patterns + ***************************************************/ + +/* + * Boolean-value predidate that asserts that "all values of array_var are in + * range value_lb (inclusive) .. value_ub (exclusive)" + * Example: + * array_bound(a->coeffs, 0, MLKEM_N, 0, MLKEM_Q) + * expands to + * __CPROVER_forall { int k; (0 <= k && k <= MLKEM_N-1) ==> ( + * 0 <= a->coeffs[k]) && a->coeffs[k] < MLKEM_Q)) } + */ + +/* + * Prevent clang-format from corrupting CBMC's special ==> operator + */ +/* clang-format off */ +#define CBMC_CONCAT_(left, right) left##right +#define CBMC_CONCAT(left, right) CBMC_CONCAT_(left, right) + +#define array_bound_core(qvar, qvar_lb, qvar_ub, array_var, \ + value_lb, value_ub) \ + __CPROVER_forall \ + { \ + unsigned qvar; \ + ((qvar_lb) <= (qvar) && (qvar) < (qvar_ub)) ==> \ + (((int)(value_lb) <= ((array_var)[(qvar)])) && \ + (((array_var)[(qvar)]) < (int)(value_ub))) \ + } + +#define array_bound(array_var, qvar_lb, qvar_ub, value_lb, value_ub) \ + array_bound_core(CBMC_CONCAT(_cbmc_idx, __COUNTER__), (qvar_lb), \ + (qvar_ub), (array_var), (value_lb), (value_ub)) + +#define array_unchanged_core(qvar, qvar_lb, qvar_ub, array_var) \ + __CPROVER_forall \ + { \ + unsigned qvar; \ + ((qvar_lb) <= (qvar) && (qvar) < (qvar_ub)) ==> \ + ((array_var)[(qvar)]) == (old(* (int16_t (*)[(qvar_ub)])(array_var)))[(qvar)] \ + } + +#define array_unchanged(array_var, N) \ + array_unchanged_core(CBMC_CONCAT(_cbmc_idx, __COUNTER__), 0, (N), (array_var)) + +#define array_unchanged_u64_core(qvar, qvar_lb, qvar_ub, array_var) \ + __CPROVER_forall \ + { \ + unsigned qvar; \ + ((qvar_lb) <= (qvar) && (qvar) < (qvar_ub)) ==> \ + ((array_var)[(qvar)]) == (old(* (uint64_t (*)[(qvar_ub)])(array_var)))[(qvar)] \ + } + +#define array_unchanged_u64(array_var, N) \ + array_unchanged_u64_core(CBMC_CONCAT(_cbmc_idx, __COUNTER__), 0, (N), (array_var)) +/* clang-format on */ + +/* Wrapper around array_bound operating on absolute values. + * + * The absolute value bound `k` is exclusive. + * + * Note that since the lower bound in array_bound is inclusive, we have to + * raise it by 1 here. + */ +#define array_abs_bound(arr, lb, ub, k) \ + array_bound((arr), (lb), (ub), -((int)(k)) + 1, (k)) + +#endif /* CBMC */ + +#endif /* !MLK_CBMC_H */ diff --git a/mlkem_native/src/common.h b/mlkem_native/src/common.h new file mode 100644 index 0000000..bc4e9ed --- /dev/null +++ b/mlkem_native/src/common.h @@ -0,0 +1,274 @@ +/* + * Copyright (c) The mlkem-native project authors + * SPDX-License-Identifier: Apache-2.0 OR ISC OR MIT + */ +#ifndef MLK_COMMON_H +#define MLK_COMMON_H + +#ifndef __ASSEMBLER__ +#include +#endif + +#define MLK_BUILD_INTERNAL + +#if defined(MLK_CONFIG_FILE) +#include MLK_CONFIG_FILE +#else +#include "mlkem_native_config.h" +#endif + +#include "params.h" +#include "sys.h" + +/* Internal and public API have external linkage by default, but + * this can be overwritten by the user, e.g. for single-CU builds. */ +#if !defined(MLK_CONFIG_INTERNAL_API_QUALIFIER) +#define MLK_INTERNAL_API +#else +#define MLK_INTERNAL_API MLK_CONFIG_INTERNAL_API_QUALIFIER +#endif + +#if !defined(MLK_CONFIG_EXTERNAL_API_QUALIFIER) +#define MLK_EXTERNAL_API +#else +#define MLK_EXTERNAL_API MLK_CONFIG_EXTERNAL_API_QUALIFIER +#endif + +#define MLK_CONCAT_(x1, x2) x1##x2 +#define MLK_CONCAT(x1, x2) MLK_CONCAT_(x1, x2) + +#if (defined(MLK_CONFIG_MULTILEVEL_WITH_SHARED) || \ + defined(MLK_CONFIG_MULTILEVEL_NO_SHARED)) +#define MLK_ADD_PARAM_SET(s) MLK_CONCAT(s, MLK_CONFIG_PARAMETER_SET) +#else +#define MLK_ADD_PARAM_SET(s) s +#endif + +#define MLK_NAMESPACE_PREFIX MLK_CONCAT(MLK_CONFIG_NAMESPACE_PREFIX, _) +#define MLK_NAMESPACE_PREFIX_K \ + MLK_CONCAT(MLK_ADD_PARAM_SET(MLK_CONFIG_NAMESPACE_PREFIX), _) + +/* Functions are prefixed by MLK_CONFIG_NAMESPACE_PREFIX. + * + * If multiple parameter sets are used, functions depending on the parameter + * set are additionally prefixed with 512/768/1024. See mlkem_native_config.h. + * + * Example: If MLK_CONFIG_NAMESPACE_PREFIX is mlkem, then + * MLK_NAMESPACE_K(enc) becomes mlkem512_enc/mlkem768_enc/mlkem1024_enc. + */ +#define MLK_NAMESPACE(s) MLK_CONCAT(MLK_NAMESPACE_PREFIX, s) +#define MLK_NAMESPACE_K(s) MLK_CONCAT(MLK_NAMESPACE_PREFIX_K, s) + +/* On Apple platforms, we need to emit leading underscore + * in front of assembly symbols. We thus introducee a separate + * namespace wrapper for ASM symbols. */ +#if !defined(__APPLE__) +#define MLK_ASM_NAMESPACE(sym) MLK_NAMESPACE(sym) +#else +#define MLK_ASM_NAMESPACE(sym) MLK_CONCAT(_, MLK_NAMESPACE(sym)) +#endif + +/* + * On X86_64 if control-flow protections (CET) are enabled (through + * -fcf-protection=), we add an endbr64 instruction at every global function + * label. See sys.h for more details + */ +#if defined(MLK_SYS_X86_64) +#define MLK_ASM_FN_SYMBOL(sym) MLK_ASM_NAMESPACE(sym) : MLK_CET_ENDBR +#elif defined(MLK_SYS_ARMV81M_MVE) +/* clang-format off */ +#define MLK_ASM_FN_SYMBOL(sym) \ + .type MLK_ASM_NAMESPACE(sym), %function; \ + MLK_ASM_NAMESPACE(sym) : +/* clang-format on */ +#else /* !MLK_SYS_X86_64 && MLK_SYS_ARMV81M_MVE */ +#define MLK_ASM_FN_SYMBOL(sym) MLK_ASM_NAMESPACE(sym) : +#endif /* !MLK_SYS_X86_64 && !MLK_SYS_ARMV81M_MVE */ + +/* + * Output the size of an assembly function. + */ +#if defined(__ELF__) +#define MLK_ASM_FN_SIZE(sym) \ + .size MLK_ASM_NAMESPACE(sym), .- MLK_ASM_NAMESPACE(sym) +#else +#define MLK_ASM_FN_SIZE(sym) +#endif + +/* We aim to simplify the user's life by supporting builds where + * all source files are included, even those that are not needed. + * Those files are appropriately guarded and will be empty when unneeded. + * The following is to avoid compilers complaining about this. */ +#define MLK_EMPTY_CU(s) extern int MLK_NAMESPACE_K(empty_cu_##s); + +/* MLK_CONFIG_NO_ASM takes precedence over MLK_USE_NATIVE_XXX */ +#if defined(MLK_CONFIG_NO_ASM) +#undef MLK_CONFIG_USE_NATIVE_BACKEND_ARITH +#undef MLK_CONFIG_USE_NATIVE_BACKEND_FIPS202 +#endif + +#if defined(MLK_CONFIG_USE_NATIVE_BACKEND_ARITH) && \ + !defined(MLK_CONFIG_ARITH_BACKEND_FILE) +#error Bad configuration: MLK_CONFIG_USE_NATIVE_BACKEND_ARITH is set, but MLK_CONFIG_ARITH_BACKEND_FILE is not. +#endif + +#if defined(MLK_CONFIG_USE_NATIVE_BACKEND_FIPS202) && \ + !defined(MLK_CONFIG_FIPS202_BACKEND_FILE) +#error Bad configuration: MLK_CONFIG_USE_NATIVE_BACKEND_FIPS202 is set, but MLK_CONFIG_FIPS202_BACKEND_FILE is not. +#endif + +#if defined(MLK_CONFIG_NO_RANDOMIZED_API) && defined(MLK_CONFIG_KEYGEN_PCT) +#error Bad configuration: MLK_CONFIG_NO_RANDOMIZED_API is incompatible with MLK_CONFIG_KEYGEN_PCT as the current PCT implementation requires crypto_kem_enc() +#endif + +#if defined(MLK_CONFIG_USE_NATIVE_BACKEND_ARITH) +#include MLK_CONFIG_ARITH_BACKEND_FILE +/* Include to enforce consistency of API and implementation, + * and conduct sanity checks on the backend. + * + * Keep this _after_ the inclusion of the backend; otherwise, + * the sanity checks won't have an effect. */ +#if defined(MLK_CHECK_APIS) && !defined(__ASSEMBLER__) +#include "native/api.h" +#endif +#endif /* MLK_CONFIG_USE_NATIVE_BACKEND_ARITH */ + +#if defined(MLK_CONFIG_USE_NATIVE_BACKEND_FIPS202) +#include MLK_CONFIG_FIPS202_BACKEND_FILE +/* Include to enforce consistency of API and implementation, + * and conduct sanity checks on the backend. + * + * Keep this _after_ the inclusion of the backend; otherwise, + * the sanity checks won't have an effect. */ +#if defined(MLK_CHECK_APIS) && !defined(__ASSEMBLER__) +#include "fips202/native/api.h" +#endif +#endif /* MLK_CONFIG_USE_NATIVE_BACKEND_FIPS202 */ + +#if !defined(MLK_CONFIG_FIPS202_CUSTOM_HEADER) +#define MLK_FIPS202_HEADER_FILE "fips202/fips202.h" +#else +#define MLK_FIPS202_HEADER_FILE MLK_CONFIG_FIPS202_CUSTOM_HEADER +#endif + +#if !defined(MLK_CONFIG_FIPS202X4_CUSTOM_HEADER) +#define MLK_FIPS202X4_HEADER_FILE "fips202/fips202x4.h" +#else +#define MLK_FIPS202X4_HEADER_FILE MLK_CONFIG_FIPS202X4_CUSTOM_HEADER +#endif + +/* Standard library function replacements */ +#if !defined(__ASSEMBLER__) +#if !defined(MLK_CONFIG_CUSTOM_MEMCPY) +#include +#define mlk_memcpy memcpy +#endif + +#if !defined(MLK_CONFIG_CUSTOM_MEMSET) +#include +#define mlk_memset memset +#endif + + +/* Allocation macros for large local structures + * + * MLK_ALLOC(v, T, N) declares T *v and attempts to point it to an T[N] + * MLK_FREE(v, T, N) zeroizes and frees the allocation + * + * Default implementation uses stack allocation. + * Can be overridden by setting the config option MLK_CONFIG_CUSTOM_ALLOC_FREE + * and defining MLK_CUSTOM_ALLOC and MLK_CUSTOM_FREE. + */ +#if defined(MLK_CONFIG_CUSTOM_ALLOC_FREE) != \ + (defined(MLK_CUSTOM_ALLOC) && defined(MLK_CUSTOM_FREE)) +#error Bad configuration: MLK_CONFIG_CUSTOM_ALLOC_FREE must be set together with MLK_CUSTOM_ALLOC and MLK_CUSTOM_FREE +#endif + +/* + * If the integration wants to provide a context parameter for use in + * platform-specific hooks, then it should define this parameter. + * + * The MLK_CONTEXT_PARAMETERS_n macros are intended to be used with macros + * defining the function names and expand to either pass or discard the context + * argument as required by the current build. If there is no context parameter + * requested then these are removed from the prototypes and from all calls. + */ +#ifdef MLK_CONFIG_CONTEXT_PARAMETER +#define MLK_CONTEXT_PARAMETERS_0(context) (context) +#define MLK_CONTEXT_PARAMETERS_1(arg0, context) (arg0, context) +#define MLK_CONTEXT_PARAMETERS_2(arg0, arg1, context) (arg0, arg1, context) +#define MLK_CONTEXT_PARAMETERS_3(arg0, arg1, arg2, context) \ + (arg0, arg1, arg2, context) +#define MLK_CONTEXT_PARAMETERS_4(arg0, arg1, arg2, arg3, context) \ + (arg0, arg1, arg2, arg3, context) +#else /* MLK_CONFIG_CONTEXT_PARAMETER */ +#define MLK_CONTEXT_PARAMETERS_0(context) () +#define MLK_CONTEXT_PARAMETERS_1(arg0, context) (arg0) +#define MLK_CONTEXT_PARAMETERS_2(arg0, arg1, context) (arg0, arg1) +#define MLK_CONTEXT_PARAMETERS_3(arg0, arg1, arg2, context) (arg0, arg1, arg2) +#define MLK_CONTEXT_PARAMETERS_4(arg0, arg1, arg2, arg3, context) \ + (arg0, arg1, arg2, arg3) +#endif /* !MLK_CONFIG_CONTEXT_PARAMETER */ + +#if defined(MLK_CONFIG_CONTEXT_PARAMETER_TYPE) != \ + defined(MLK_CONFIG_CONTEXT_PARAMETER) +#error MLK_CONFIG_CONTEXT_PARAMETER_TYPE must be defined if and only if MLK_CONFIG_CONTEXT_PARAMETER is defined +#endif + +#if !defined(MLK_CONFIG_CUSTOM_ALLOC_FREE) +/* Default: stack allocation */ + +#define MLK_ALLOC(v, T, N, context) \ + MLK_ALIGN T mlk_alloc_##v[N]; \ + T *v = mlk_alloc_##v + +/* TODO: This leads to a circular dependency between common and verify.h + * It just works out before we're at the end of the file, but it's still + * prone to issues in the future. */ +#include "verify.h" +#define MLK_FREE(v, T, N, context) \ + do \ + { \ + mlk_zeroize(mlk_alloc_##v, sizeof(mlk_alloc_##v)); \ + (v) = NULL; \ + } while (0) + +#else /* !MLK_CONFIG_CUSTOM_ALLOC_FREE */ + +/* Custom allocation */ + +/* + * The indirection here is necessary to use MLK_CONTEXT_PARAMETERS_3 here. + */ +#define MLK_APPLY(f, args) f args + +#define MLK_ALLOC(v, T, N, context) \ + MLK_APPLY(MLK_CUSTOM_ALLOC, MLK_CONTEXT_PARAMETERS_3(v, T, N, context)) + +#define MLK_FREE(v, T, N, context) \ + do \ + { \ + if (v != NULL) \ + { \ + mlk_zeroize(v, sizeof(T) * (N)); \ + MLK_APPLY(MLK_CUSTOM_FREE, MLK_CONTEXT_PARAMETERS_3(v, T, N, context)); \ + v = NULL; \ + } \ + } while (0) + +#endif /* MLK_CONFIG_CUSTOM_ALLOC_FREE */ + +/****************************** Error codes ***********************************/ + +/* Generic failure condition */ +#define MLK_ERR_FAIL -1 +/* An allocation failed. This can only happen if MLK_CONFIG_CUSTOM_ALLOC_FREE + * is defined and the provided MLK_CUSTOM_ALLOC can fail. */ +#define MLK_ERR_OUT_OF_MEMORY -2 +/* An rng failure occured. Might be due to insufficient entropy or + * system misconfiguration. */ +#define MLK_ERR_RNG_FAIL -3 + +#endif /* !__ASSEMBLER__ */ + +#endif /* !MLK_COMMON_H */ diff --git a/mlkem_native/src/compress.c b/mlkem_native/src/compress.c new file mode 100644 index 0000000..50da36d --- /dev/null +++ b/mlkem_native/src/compress.c @@ -0,0 +1,717 @@ +/* + * Copyright (c) The mlkem-native project authors + * SPDX-License-Identifier: Apache-2.0 OR ISC OR MIT + */ + +/* References + * ========== + * + * - [FIPS203] + * FIPS 203 Module-Lattice-Based Key-Encapsulation Mechanism Standard + * National Institute of Standards and Technology + * https://csrc.nist.gov/pubs/fips/203/final + * + * - [REF] + * CRYSTALS-Kyber C reference implementation + * Bos, Ducas, Kiltz, Lepoint, Lyubashevsky, Schanck, Schwabe, Seiler, StehlΓ© + * https://github.com/pq-crystals/kyber/tree/main/ref + */ + +#include "common.h" +#if !defined(MLK_CONFIG_MULTILEVEL_NO_SHARED) + + +#include "cbmc.h" +#include "compress.h" +#include "debug.h" +#include "verify.h" + +#if defined(MLK_CONFIG_MULTILEVEL_WITH_SHARED) || (MLKEM_K == 2 || MLKEM_K == 3) +/* Reference: `poly_compress()` in the reference implementation @[REF], + * for ML-KEM-{512,768}. + * - In contrast to the reference implementation, we assume + * unsigned canonical coefficients here. + * The reference implementation works with coefficients + * in the range (-MLKEM_Q+1,...,MLKEM_Q-1). */ +MLK_STATIC_TESTABLE void mlk_poly_compress_d4_c( + uint8_t r[MLKEM_POLYCOMPRESSEDBYTES_D4], const mlk_poly *a) +__contract__( + requires(memory_no_alias(r, MLKEM_POLYCOMPRESSEDBYTES_D4)) + requires(memory_no_alias(a, sizeof(mlk_poly))) + requires(array_bound(a->coeffs, 0, MLKEM_N, 0, MLKEM_Q)) + assigns(memory_slice(r, MLKEM_POLYCOMPRESSEDBYTES_D4)) +) +{ + unsigned i; + mlk_assert_bound(a, MLKEM_N, 0, MLKEM_Q); + + for (i = 0; i < MLKEM_N / 8; i++) + __loop__(invariant(i <= MLKEM_N / 8)) + { + unsigned j; + uint8_t t[8] = {0}; + for (j = 0; j < 8; j++) + __loop__( + invariant(i <= MLKEM_N / 8 && j <= 8) + invariant(array_bound(t, 0, j, 0, 16))) + { + t[j] = mlk_scalar_compress_d4(a->coeffs[8 * i + j]); + } + + /* All t[i] are 4-bit wide, so the truncations don't alter the value. */ + r[i * 4] = (uint8_t)(t[0] | (t[1] << 4)); + r[i * 4 + 1] = (uint8_t)(t[2] | (t[3] << 4)); + r[i * 4 + 2] = (uint8_t)(t[4] | (t[5] << 4)); + r[i * 4 + 3] = (uint8_t)(t[6] | (t[7] << 4)); + } +} + +MLK_INTERNAL_API +void mlk_poly_compress_d4(uint8_t r[MLKEM_POLYCOMPRESSEDBYTES_D4], + const mlk_poly *a) +__contract__( + requires(memory_no_alias(r, MLKEM_POLYCOMPRESSEDBYTES_D4)) + requires(memory_no_alias(a, sizeof(mlk_poly))) + requires(array_bound(a->coeffs, 0, MLKEM_N, 0, MLKEM_Q)) + assigns(memory_slice(r, MLKEM_POLYCOMPRESSEDBYTES_D4)) +) +{ +#if defined(MLK_USE_NATIVE_POLY_COMPRESS_D4) + int ret; + mlk_assert_bound(a, MLKEM_N, 0, MLKEM_Q); + ret = mlk_poly_compress_d4_native(r, a->coeffs); + if (ret == MLK_NATIVE_FUNC_SUCCESS) + { + return; + } +#endif /* MLK_USE_NATIVE_POLY_COMPRESS_D4 */ + + mlk_poly_compress_d4_c(r, a); +} + +/* Reference: Embedded into `polyvec_compress()` in the + * reference implementation, for ML-KEM-{512,768}. + * - In contrast to the reference implementation, we assume + * unsigned canonical coefficients here. + * The reference implementation works with coefficients + * in the range (-MLKEM_Q+1,...,MLKEM_Q-1). */ +MLK_STATIC_TESTABLE void mlk_poly_compress_d10_c( + uint8_t r[MLKEM_POLYCOMPRESSEDBYTES_D10], const mlk_poly *a) +__contract__( + requires(memory_no_alias(r, MLKEM_POLYCOMPRESSEDBYTES_D10)) + requires(memory_no_alias(a, sizeof(mlk_poly))) + requires(array_bound(a->coeffs, 0, MLKEM_N, 0, MLKEM_Q)) + assigns(memory_slice(r, MLKEM_POLYCOMPRESSEDBYTES_D10)) +) +{ + unsigned j; + mlk_assert_bound(a, MLKEM_N, 0, MLKEM_Q); + for (j = 0; j < MLKEM_N / 4; j++) + __loop__(invariant(j <= MLKEM_N / 4)) + { + unsigned k; + uint16_t t[4]; + for (k = 0; k < 4; k++) + __loop__( + invariant(k <= 4) + invariant(forall(r, 0, k, t[r] < (1u << 10)))) + { + t[k] = mlk_scalar_compress_d10(a->coeffs[4 * j + k]); + } + + /* + * Make all implicit truncation explicit. No data is being + * truncated for the LHS's since each t[i] is 10-bit in size. + */ + r[5 * j + 0] = (uint8_t)((t[0] >> 0) & 0xFF); + r[5 * j + 1] = (uint8_t)((t[0] >> 8) | ((t[1] << 2) & 0xFF)); + r[5 * j + 2] = (uint8_t)((t[1] >> 6) | ((t[2] << 4) & 0xFF)); + r[5 * j + 3] = (uint8_t)((t[2] >> 4) | ((t[3] << 6) & 0xFF)); + r[5 * j + 4] = (uint8_t)(t[3] >> 2); + } +} + +MLK_INTERNAL_API +void mlk_poly_compress_d10(uint8_t r[MLKEM_POLYCOMPRESSEDBYTES_D10], + const mlk_poly *a) +__contract__( + requires(memory_no_alias(r, MLKEM_POLYCOMPRESSEDBYTES_D10)) + requires(memory_no_alias(a, sizeof(mlk_poly))) + requires(array_bound(a->coeffs, 0, MLKEM_N, 0, MLKEM_Q)) + assigns(memory_slice(r, MLKEM_POLYCOMPRESSEDBYTES_D10)) +) +{ +#if defined(MLK_USE_NATIVE_POLY_COMPRESS_D10) + int ret; + mlk_assert_bound(a, MLKEM_N, 0, MLKEM_Q); + ret = mlk_poly_compress_d10_native(r, a->coeffs); + if (ret == MLK_NATIVE_FUNC_SUCCESS) + { + return; + } +#endif /* MLK_USE_NATIVE_POLY_COMPRESS_D10 */ + + mlk_poly_compress_d10_c(r, a); +} + +/* Reference: `poly_decompress()` in the reference implementation @[REF], + * for ML-KEM-{512,768}. */ +MLK_STATIC_TESTABLE void mlk_poly_decompress_d4_c( + mlk_poly *r, const uint8_t a[MLKEM_POLYCOMPRESSEDBYTES_D4]) +__contract__( + requires(memory_no_alias(r, sizeof(mlk_poly))) + requires(memory_no_alias(a, MLKEM_POLYCOMPRESSEDBYTES_D4)) + assigns(memory_slice(r, sizeof(mlk_poly))) + ensures(array_bound(r->coeffs, 0, MLKEM_N, 0, MLKEM_Q)) +) +{ + unsigned i; + for (i = 0; i < MLKEM_N / 2; i++) + __loop__( + invariant(i <= MLKEM_N / 2) + invariant(array_bound(r->coeffs, 0, 2 * i, 0, MLKEM_Q))) + { + r->coeffs[2 * i + 0] = mlk_scalar_decompress_d4((a[i] >> 0) & 0xF); + r->coeffs[2 * i + 1] = mlk_scalar_decompress_d4((a[i] >> 4) & 0xF); + } + + mlk_assert_bound(r, MLKEM_N, 0, MLKEM_Q); +} + +MLK_INTERNAL_API +void mlk_poly_decompress_d4(mlk_poly *r, + const uint8_t a[MLKEM_POLYCOMPRESSEDBYTES_D4]) +__contract__( + requires(memory_no_alias(r, sizeof(mlk_poly))) + requires(memory_no_alias(a, MLKEM_POLYCOMPRESSEDBYTES_D4)) + assigns(memory_slice(r, sizeof(mlk_poly))) + ensures(array_bound(r->coeffs, 0, MLKEM_N, 0, MLKEM_Q)) +) +{ +#if defined(MLK_USE_NATIVE_POLY_DECOMPRESS_D4) + int ret; + ret = mlk_poly_decompress_d4_native(r->coeffs, a); + if (ret == MLK_NATIVE_FUNC_SUCCESS) + { + mlk_assert_bound(r, MLKEM_N, 0, MLKEM_Q); + return; + } +#endif /* MLK_USE_NATIVE_POLY_DECOMPRESS_D4 */ + + mlk_poly_decompress_d4_c(r, a); +} + +/* Reference: Embedded into `polyvec_decompress()` in the + * reference implementation, for ML-KEM-{512,768}. */ +MLK_STATIC_TESTABLE void mlk_poly_decompress_d10_c( + mlk_poly *r, const uint8_t a[MLKEM_POLYCOMPRESSEDBYTES_D10]) +__contract__( + requires(memory_no_alias(r, sizeof(mlk_poly))) + requires(memory_no_alias(a, MLKEM_POLYCOMPRESSEDBYTES_D10)) + assigns(memory_slice(r, sizeof(mlk_poly))) + ensures(array_bound(r->coeffs, 0, MLKEM_N, 0, MLKEM_Q)) +) +{ + unsigned j; + for (j = 0; j < MLKEM_N / 4; j++) + __loop__( + invariant(j <= MLKEM_N / 4) + invariant(array_bound(r->coeffs, 0, 4 * j, 0, MLKEM_Q))) + { + unsigned k; + uint16_t t[4]; + uint8_t const *base = &a[5 * j]; + + t[0] = 0x3FF & ((base[0] >> 0) | ((uint16_t)base[1] << 8)); + t[1] = 0x3FF & ((base[1] >> 2) | ((uint16_t)base[2] << 6)); + t[2] = 0x3FF & ((base[2] >> 4) | ((uint16_t)base[3] << 4)); + t[3] = 0x3FF & ((base[3] >> 6) | ((uint16_t)base[4] << 2)); + + for (k = 0; k < 4; k++) + __loop__( + invariant(k <= 4) + invariant(array_bound(r->coeffs, 0, 4 * j + k, 0, MLKEM_Q))) + { + r->coeffs[4 * j + k] = mlk_scalar_decompress_d10(t[k]); + } + } + + mlk_assert_bound(r, MLKEM_N, 0, MLKEM_Q); +} + +MLK_INTERNAL_API +void mlk_poly_decompress_d10(mlk_poly *r, + const uint8_t a[MLKEM_POLYCOMPRESSEDBYTES_D10]) +__contract__( + requires(memory_no_alias(r, sizeof(mlk_poly))) + requires(memory_no_alias(a, MLKEM_POLYCOMPRESSEDBYTES_D10)) + assigns(memory_slice(r, sizeof(mlk_poly))) + ensures(array_bound(r->coeffs, 0, MLKEM_N, 0, MLKEM_Q)) +) +{ +#if defined(MLK_USE_NATIVE_POLY_DECOMPRESS_D10) + int ret; + ret = mlk_poly_decompress_d10_native(r->coeffs, a); + if (ret == MLK_NATIVE_FUNC_SUCCESS) + { + mlk_assert_bound(r, MLKEM_N, 0, MLKEM_Q); + return; + } +#endif /* MLK_USE_NATIVE_POLY_DECOMPRESS_D10 */ + + mlk_poly_decompress_d10_c(r, a); +} +#endif /* MLK_CONFIG_MULTILEVEL_WITH_SHARED || MLKEM_K == 2 || MLKEM_K == 3 */ + +#if defined(MLK_CONFIG_MULTILEVEL_WITH_SHARED) || MLKEM_K == 4 +/* Reference: `poly_compress()` in the reference implementation @[REF], + * for ML-KEM-1024. + * - In contrast to the reference implementation, we assume + * unsigned canonical coefficients here. + * The reference implementation works with coefficients + * in the range (-MLKEM_Q+1,...,MLKEM_Q-1). */ +MLK_STATIC_TESTABLE void mlk_poly_compress_d5_c( + uint8_t r[MLKEM_POLYCOMPRESSEDBYTES_D5], const mlk_poly *a) +__contract__( + requires(memory_no_alias(r, MLKEM_POLYCOMPRESSEDBYTES_D5)) + requires(memory_no_alias(a, sizeof(mlk_poly))) + requires(array_bound(a->coeffs, 0, MLKEM_N, 0, MLKEM_Q)) + assigns(memory_slice(r, MLKEM_POLYCOMPRESSEDBYTES_D5)) +) +{ + unsigned i; + mlk_assert_bound(a, MLKEM_N, 0, MLKEM_Q); + + for (i = 0; i < MLKEM_N / 8; i++) + __loop__(invariant(i <= MLKEM_N / 8)) + { + unsigned j; + uint8_t t[8] = {0}; + for (j = 0; j < 8; j++) + __loop__( + invariant(i <= MLKEM_N / 8 && j <= 8) + invariant(array_bound(t, 0, j, 0, 32))) + { + t[j] = mlk_scalar_compress_d5(a->coeffs[8 * i + j]); + } + + r[i * 5] = (uint8_t)(0xFF & ((t[0] >> 0) | (t[1] << 5))); + r[i * 5 + 1] = (uint8_t)(0xFF & ((t[1] >> 3) | (t[2] << 2) | (t[3] << 7))); + r[i * 5 + 2] = (uint8_t)(0xFF & ((t[3] >> 1) | (t[4] << 4))); + r[i * 5 + 3] = (uint8_t)(0xFF & ((t[4] >> 4) | (t[5] << 1) | (t[6] << 6))); + r[i * 5 + 4] = (uint8_t)(0xFF & ((t[6] >> 2) | (t[7] << 3))); + } +} + +MLK_INTERNAL_API +void mlk_poly_compress_d5(uint8_t r[MLKEM_POLYCOMPRESSEDBYTES_D5], + const mlk_poly *a) +__contract__( + requires(memory_no_alias(r, MLKEM_POLYCOMPRESSEDBYTES_D5)) + requires(memory_no_alias(a, sizeof(mlk_poly))) + requires(array_bound(a->coeffs, 0, MLKEM_N, 0, MLKEM_Q)) + assigns(memory_slice(r, MLKEM_POLYCOMPRESSEDBYTES_D5)) +) +{ +#if defined(MLK_USE_NATIVE_POLY_COMPRESS_D5) + int ret; + mlk_assert_bound(a, MLKEM_N, 0, MLKEM_Q); + ret = mlk_poly_compress_d5_native(r, a->coeffs); + if (ret == MLK_NATIVE_FUNC_SUCCESS) + { + return; + } +#endif /* MLK_USE_NATIVE_POLY_COMPRESS_D5 */ + + mlk_poly_compress_d5_c(r, a); +} + +/* Reference: Embedded into `polyvec_compress()` in the + * reference implementation, for ML-KEM-1024. + * - In contrast to the reference implementation, we assume + * unsigned canonical coefficients here. + * The reference implementation works with coefficients + * in the range (-MLKEM_Q+1,...,MLKEM_Q-1). */ +MLK_STATIC_TESTABLE void mlk_poly_compress_d11_c( + uint8_t r[MLKEM_POLYCOMPRESSEDBYTES_D11], const mlk_poly *a) +__contract__( + requires(memory_no_alias(r, MLKEM_POLYCOMPRESSEDBYTES_D11)) + requires(memory_no_alias(a, sizeof(mlk_poly))) + requires(array_bound(a->coeffs, 0, MLKEM_N, 0, MLKEM_Q)) + assigns(memory_slice(r, MLKEM_POLYCOMPRESSEDBYTES_D11)) +) +{ + unsigned j; + mlk_assert_bound(a, MLKEM_N, 0, MLKEM_Q); + + for (j = 0; j < MLKEM_N / 8; j++) + __loop__(invariant(j <= MLKEM_N / 8)) + { + unsigned k; + uint16_t t[8]; + for (k = 0; k < 8; k++) + __loop__( + invariant(k <= 8) + invariant(forall(r, 0, k, t[r] < (1u << 11)))) + { + t[k] = mlk_scalar_compress_d11(a->coeffs[8 * j + k]); + } + + /* + * Make all implicit truncation explicit. No data is being + * truncated for the LHS's since each t[i] is 11-bit in size. + */ + r[11 * j + 0] = (uint8_t)((t[0] >> 0) & 0xFF); + r[11 * j + 1] = (uint8_t)((t[0] >> 8) | ((t[1] << 3) & 0xFF)); + r[11 * j + 2] = (uint8_t)((t[1] >> 5) | ((t[2] << 6) & 0xFF)); + r[11 * j + 3] = (uint8_t)((t[2] >> 2) & 0xFF); + r[11 * j + 4] = (uint8_t)((t[2] >> 10) | ((t[3] << 1) & 0xFF)); + r[11 * j + 5] = (uint8_t)((t[3] >> 7) | ((t[4] << 4) & 0xFF)); + r[11 * j + 6] = (uint8_t)((t[4] >> 4) | ((t[5] << 7) & 0xFF)); + r[11 * j + 7] = (uint8_t)((t[5] >> 1) & 0xFF); + r[11 * j + 8] = (uint8_t)((t[5] >> 9) | ((t[6] << 2) & 0xFF)); + r[11 * j + 9] = (uint8_t)((t[6] >> 6) | ((t[7] << 5) & 0xFF)); + r[11 * j + 10] = (uint8_t)(t[7] >> 3); + } +} + +MLK_INTERNAL_API +void mlk_poly_compress_d11(uint8_t r[MLKEM_POLYCOMPRESSEDBYTES_D11], + const mlk_poly *a) +__contract__( + requires(memory_no_alias(r, MLKEM_POLYCOMPRESSEDBYTES_D11)) + requires(memory_no_alias(a, sizeof(mlk_poly))) + requires(array_bound(a->coeffs, 0, MLKEM_N, 0, MLKEM_Q)) + assigns(memory_slice(r, MLKEM_POLYCOMPRESSEDBYTES_D11)) +) +{ +#if defined(MLK_USE_NATIVE_POLY_COMPRESS_D11) + int ret; + mlk_assert_bound(a, MLKEM_N, 0, MLKEM_Q); + ret = mlk_poly_compress_d11_native(r, a->coeffs); + if (ret == MLK_NATIVE_FUNC_SUCCESS) + { + return; + } +#endif /* MLK_USE_NATIVE_POLY_COMPRESS_D11 */ + + mlk_poly_compress_d11_c(r, a); +} + +/* Reference: `poly_decompress()` in the reference implementation @[REF], + * for ML-KEM-1024. */ +MLK_STATIC_TESTABLE void mlk_poly_decompress_d5_c( + mlk_poly *r, const uint8_t a[MLKEM_POLYCOMPRESSEDBYTES_D5]) +__contract__( + requires(memory_no_alias(r, sizeof(mlk_poly))) + requires(memory_no_alias(a, MLKEM_POLYCOMPRESSEDBYTES_D5)) + assigns(memory_slice(r, sizeof(mlk_poly))) + ensures(array_bound(r->coeffs, 0, MLKEM_N, 0, MLKEM_Q)) +) +{ + unsigned i; + for (i = 0; i < MLKEM_N / 8; i++) + __loop__( + invariant(i <= MLKEM_N / 8) + invariant(array_bound(r->coeffs, 0, 8 * i, 0, MLKEM_Q))) + { + unsigned j; + uint8_t t[8]; + const unsigned offset = i * 5; + /* + * Explicitly truncate to avoid warning about + * implicit truncation in CBMC and unwind loop for ease + * of proof. + */ + + /* + * Decompress 5 8-bit bytes (so 40 bits) into + * 8 5-bit values stored in t[] + */ + t[0] = 0x1F & (a[offset + 0] >> 0); + t[1] = 0x1F & ((a[offset + 0] >> 5) | (a[offset + 1] << 3)); + t[2] = 0x1F & (a[offset + 1] >> 2); + t[3] = 0x1F & ((a[offset + 1] >> 7) | (a[offset + 2] << 1)); + t[4] = 0x1F & ((a[offset + 2] >> 4) | (a[offset + 3] << 4)); + t[5] = 0x1F & (a[offset + 3] >> 1); + t[6] = 0x1F & ((a[offset + 3] >> 6) | (a[offset + 4] << 2)); + t[7] = 0x1F & (a[offset + 4] >> 3); + + /* and copy to the correct slice in r[] */ + for (j = 0; j < 8; j++) + __loop__( + invariant(j <= 8 && i <= MLKEM_N / 8) + invariant(array_bound(r->coeffs, 0, 8 * i + j, 0, MLKEM_Q))) + { + r->coeffs[8 * i + j] = mlk_scalar_decompress_d5(t[j]); + } + } + + mlk_assert_bound(r, MLKEM_N, 0, MLKEM_Q); +} + +MLK_INTERNAL_API +void mlk_poly_decompress_d5(mlk_poly *r, + const uint8_t a[MLKEM_POLYCOMPRESSEDBYTES_D5]) +__contract__( + requires(memory_no_alias(r, sizeof(mlk_poly))) + requires(memory_no_alias(a, MLKEM_POLYCOMPRESSEDBYTES_D5)) + assigns(memory_slice(r, sizeof(mlk_poly))) + ensures(array_bound(r->coeffs, 0, MLKEM_N, 0, MLKEM_Q)) +) +{ +#if defined(MLK_USE_NATIVE_POLY_DECOMPRESS_D5) + int ret; + ret = mlk_poly_decompress_d5_native(r->coeffs, a); + if (ret == MLK_NATIVE_FUNC_SUCCESS) + { + mlk_assert_bound(r, MLKEM_N, 0, MLKEM_Q); + return; + } +#endif /* MLK_USE_NATIVE_POLY_DECOMPRESS_D5 */ + + mlk_poly_decompress_d5_c(r, a); +} + +/* Reference: Embedded into `polyvec_decompress()` in the + * reference implementation, for ML-KEM-1024. */ +MLK_STATIC_TESTABLE void mlk_poly_decompress_d11_c( + mlk_poly *r, const uint8_t a[MLKEM_POLYCOMPRESSEDBYTES_D11]) +__contract__( + requires(memory_no_alias(r, sizeof(mlk_poly))) + requires(memory_no_alias(a, MLKEM_POLYCOMPRESSEDBYTES_D11)) + assigns(memory_slice(r, sizeof(mlk_poly))) + ensures(array_bound(r->coeffs, 0, MLKEM_N, 0, MLKEM_Q)) +) +{ + unsigned j; + for (j = 0; j < MLKEM_N / 8; j++) + __loop__( + invariant(j <= MLKEM_N / 8) + invariant(array_bound(r->coeffs, 0, 8 * j, 0, MLKEM_Q))) + { + unsigned k; + uint16_t t[8]; + uint8_t const *base = &a[11 * j]; + t[0] = 0x7FF & ((base[0] >> 0) | ((uint16_t)base[1] << 8)); + t[1] = 0x7FF & ((base[1] >> 3) | ((uint16_t)base[2] << 5)); + t[2] = 0x7FF & ((base[2] >> 6) | ((uint16_t)base[3] << 2) | + ((uint16_t)base[4] << 10)); + t[3] = 0x7FF & ((base[4] >> 1) | ((uint16_t)base[5] << 7)); + t[4] = 0x7FF & ((base[5] >> 4) | ((uint16_t)base[6] << 4)); + t[5] = 0x7FF & ((base[6] >> 7) | ((uint16_t)base[7] << 1) | + ((uint16_t)base[8] << 9)); + t[6] = 0x7FF & ((base[8] >> 2) | ((uint16_t)base[9] << 6)); + t[7] = 0x7FF & ((base[9] >> 5) | ((uint16_t)base[10] << 3)); + + for (k = 0; k < 8; k++) + __loop__( + invariant(k <= 8) + invariant(array_bound(r->coeffs, 0, 8 * j + k, 0, MLKEM_Q))) + { + r->coeffs[8 * j + k] = mlk_scalar_decompress_d11(t[k]); + } + } + + mlk_assert_bound(r, MLKEM_N, 0, MLKEM_Q); +} + +MLK_INTERNAL_API +void mlk_poly_decompress_d11(mlk_poly *r, + const uint8_t a[MLKEM_POLYCOMPRESSEDBYTES_D11]) +__contract__( + requires(memory_no_alias(r, sizeof(mlk_poly))) + requires(memory_no_alias(a, MLKEM_POLYCOMPRESSEDBYTES_D11)) + assigns(memory_slice(r, sizeof(mlk_poly))) + ensures(array_bound(r->coeffs, 0, MLKEM_N, 0, MLKEM_Q)) +) +{ +#if defined(MLK_USE_NATIVE_POLY_DECOMPRESS_D11) + int ret; + ret = mlk_poly_decompress_d11_native(r->coeffs, a); + if (ret == MLK_NATIVE_FUNC_SUCCESS) + { + mlk_assert_bound(r, MLKEM_N, 0, MLKEM_Q); + return; + } +#endif /* MLK_USE_NATIVE_POLY_DECOMPRESS_D11 */ + + mlk_poly_decompress_d11_c(r, a); +} + +#endif /* MLK_CONFIG_MULTILEVEL_WITH_SHARED || MLKEM_K == 4 */ + +/* Reference: `poly_tobytes()` in the reference implementation @[REF]. + * - In contrast to the reference implementation, we assume + * unsigned canonical coefficients here. + * The reference implementation works with coefficients + * in the range (-MLKEM_Q+1,...,MLKEM_Q-1). */ +MLK_STATIC_TESTABLE void mlk_poly_tobytes_c(uint8_t r[MLKEM_POLYBYTES], + const mlk_poly *a) +__contract__( + requires(memory_no_alias(r, MLKEM_POLYBYTES)) + requires(memory_no_alias(a, sizeof(mlk_poly))) + requires(array_bound(a->coeffs, 0, MLKEM_N, 0, MLKEM_Q)) + assigns(memory_slice(r, MLKEM_POLYBYTES)) +) +{ + unsigned i; + mlk_assert_bound(a, MLKEM_N, 0, MLKEM_Q); + + for (i = 0; i < MLKEM_N / 2; i++) + __loop__(invariant(i <= MLKEM_N / 2)) + { + /* The conversion to uint16_t is safe since we assume that + * the coefficients of `a` are non-negative. */ + const uint16_t t0 = (uint16_t)a->coeffs[2 * i]; + const uint16_t t1 = (uint16_t)a->coeffs[2 * i + 1]; + /* + * t0 and t1 are both < MLKEM_Q, so contain at most 12 bits each of + * significant data, so these can be packed into 24 bits or exactly + * 3 bytes, as follows. + */ + + /* Least significant bits 0 - 7 of t0. */ + r[3 * i + 0] = (uint8_t)(t0 & 0xFF); + + /* + * Most significant bits 8 - 11 of t0 become the least significant + * nibble of the second byte. The least significant 4 bits + * of t1 become the upper nibble of the second byte. + * + * The conversion to uint8_t does not alter the value. + */ + r[3 * i + 1] = (uint8_t)((t0 >> 8) | ((t1 << 4) & 0xF0)); + + /* Bits 4 - 11 of t1 become the third byte. The conversion to uint8_t + * does not alter the value because t1 is 12-bit wide. */ + r[3 * i + 2] = (uint8_t)(t1 >> 4); + } +} + +MLK_INTERNAL_API +void mlk_poly_tobytes(uint8_t r[MLKEM_POLYBYTES], const mlk_poly *a) +{ +#if defined(MLK_USE_NATIVE_POLY_TOBYTES) + int ret; + mlk_assert_bound(a, MLKEM_N, 0, MLKEM_Q); + ret = mlk_poly_tobytes_native(r, a->coeffs); + if (ret == MLK_NATIVE_FUNC_SUCCESS) + { + return; + } +#endif /* MLK_USE_NATIVE_POLY_TOBYTES */ + + mlk_poly_tobytes_c(r, a); +} + +/* Reference: `poly_frombytes()` in the reference implementation @[REF]. */ +MLK_STATIC_TESTABLE void mlk_poly_frombytes_c(mlk_poly *r, + const uint8_t a[MLKEM_POLYBYTES]) +__contract__( + requires(memory_no_alias(a, MLKEM_POLYBYTES)) + requires(memory_no_alias(r, sizeof(mlk_poly))) + assigns(memory_slice(r, sizeof(mlk_poly))) + ensures(array_bound(r->coeffs, 0, MLKEM_N, 0, MLKEM_UINT12_LIMIT)) +) +{ + unsigned i; + for (i = 0; i < MLKEM_N / 2; i++) + __loop__( + invariant(i <= MLKEM_N / 2) + invariant(array_bound(r->coeffs, 0, 2 * i, 0, MLKEM_UINT12_LIMIT))) + { + const uint8_t t0 = a[3 * i + 0]; + const uint8_t t1 = a[3 * i + 1]; + const uint8_t t2 = a[3 * i + 2]; + r->coeffs[2 * i + 0] = (int16_t)(t0 | ((t1 << 8) & 0xFFF)); + r->coeffs[2 * i + 1] = (int16_t)((t1 >> 4) | (t2 << 4)); + } + + /* Note that the coefficients are not canonical */ + mlk_assert_bound(r, MLKEM_N, 0, MLKEM_UINT12_LIMIT); +} + +MLK_INTERNAL_API +void mlk_poly_frombytes(mlk_poly *r, const uint8_t a[MLKEM_POLYBYTES]) +{ +#if defined(MLK_USE_NATIVE_POLY_FROMBYTES) + int ret; + ret = mlk_poly_frombytes_native(r->coeffs, a); + if (ret == MLK_NATIVE_FUNC_SUCCESS) + { + return; + } +#endif /* MLK_USE_NATIVE_POLY_FROMBYTES */ + + mlk_poly_frombytes_c(r, a); +} + +/* Reference: `poly_frommsg()` in the reference implementation @[REF]. + * - We use a value barrier around the bit-selection mask to + * reduce the risk of compiler-introduced branches. + * The reference implementation contains the expression + * `(msg[i] >> j) & 1` which the compiler can reason must + * be either 0 or 1. */ +MLK_INTERNAL_API +void mlk_poly_frommsg(mlk_poly *r, const uint8_t msg[MLKEM_INDCPA_MSGBYTES]) +{ + unsigned i; +#if (MLKEM_INDCPA_MSGBYTES != MLKEM_N / 8) +#error "MLKEM_INDCPA_MSGBYTES must be equal to MLKEM_N/8 bytes!" +#endif + + for (i = 0; i < MLKEM_N / 8; i++) + __loop__( + invariant(i <= MLKEM_N / 8) + invariant(array_bound(r->coeffs, 0, 8 * i, 0, MLKEM_Q))) + { + unsigned j; + for (j = 0; j < 8; j++) + __loop__( + invariant(i < MLKEM_N / 8 && j <= 8) + invariant(array_bound(r->coeffs, 0, 8 * i + j, 0, MLKEM_Q))) + { + /* mlk_ct_sel_int16(MLKEM_Q_HALF, 0, b) is `Decompress_1(b != 0)` + * as per @[FIPS203, Eq (4.8)]. */ + + /* Prevent the compiler from recognizing this as a bit selection */ + uint8_t mask = mlk_value_barrier_u8((uint8_t)(1u << j)); + r->coeffs[8 * i + j] = mlk_ct_sel_int16(MLKEM_Q_HALF, 0, msg[i] & mask); + } + } + mlk_assert_abs_bound(r, MLKEM_N, MLKEM_Q); +} + +/* Reference: `poly_tomsg()` in the reference implementation @[REF]. + * - In contrast to the reference implementation, we assume + * unsigned canonical coefficients here. + * The reference implementation works with coefficients + * in the range (-MLKEM_Q+1,...,MLKEM_Q-1). + */ +MLK_INTERNAL_API +void mlk_poly_tomsg(uint8_t msg[MLKEM_INDCPA_MSGBYTES], const mlk_poly *a) +{ + unsigned i; + mlk_assert_bound(a, MLKEM_N, 0, MLKEM_Q); + + for (i = 0; i < MLKEM_N / 8; i++) + __loop__(invariant(i <= MLKEM_N / 8)) + { + unsigned j; + msg[i] = 0; + for (j = 0; j < 8; j++) + __loop__( + invariant(i <= MLKEM_N / 8 && j <= 8)) + { + uint32_t t = mlk_scalar_compress_d1(a->coeffs[8 * i + j]); + msg[i] |= (uint8_t)(t << j); + } + } +} + +#else /* !MLK_CONFIG_MULTILEVEL_NO_SHARED */ + +MLK_EMPTY_CU(compress) + +#endif /* MLK_CONFIG_MULTILEVEL_NO_SHARED */ diff --git a/mlkem_native/src/compress.h b/mlkem_native/src/compress.h new file mode 100644 index 0000000..b16b088 --- /dev/null +++ b/mlkem_native/src/compress.h @@ -0,0 +1,688 @@ +/* + * Copyright (c) The mlkem-native project authors + * SPDX-License-Identifier: Apache-2.0 OR ISC OR MIT + */ + +/* References + * ========== + * + * - [FIPS203] + * FIPS 203 Module-Lattice-Based Key-Encapsulation Mechanism Standard + * National Institute of Standards and Technology + * https://csrc.nist.gov/pubs/fips/203/final + * + * - [REF] + * CRYSTALS-Kyber C reference implementation + * Bos, Ducas, Kiltz, Lepoint, Lyubashevsky, Schanck, Schwabe, Seiler, StehlΓ© + * https://github.com/pq-crystals/kyber/tree/main/ref + */ + +#ifndef MLK_COMPRESS_H +#define MLK_COMPRESS_H + + +#include "cbmc.h" +#include "common.h" +#include "debug.h" +#include "poly.h" +#include "verify.h" + +/************************************************************ + * Name: mlk_scalar_compress_d1 + * + * Description: Computes round(u * 2 / q) + * + * Arguments: - u: Unsigned canonical modulus modulo q + * to be compressed. + * + * Specification: Compress_1 from @[FIPS203, Eq (4.7)]. + * + ************************************************************/ + +/* + * The multiplication in this routine will exceed UINT32_MAX + * and wrap around for large values of u. This is expected and required. + */ +#ifdef CBMC +#pragma CPROVER check push +#pragma CPROVER check disable "unsigned-overflow" +#endif + +/* Reference: Part of poly_tomsg() in the reference implementation @[REF]. */ +static MLK_INLINE uint8_t mlk_scalar_compress_d1(int16_t u) +__contract__( + requires(0 <= u && u <= MLKEM_Q - 1) + ensures(return_value < 2) + ensures(return_value == (((uint32_t)u * 2 + MLKEM_Q / 2) / MLKEM_Q) % 2) ) +{ + /* Compute as follows: + * ``` + * round(u * 2 / MLKEM_Q) + * = round(u * 2 * (2^31 / MLKEM_Q) / 2^31) + * ~= round(u * 2 * round(2^31 / MLKEM_Q) / 2^31) + * ``` + */ + /* check-magic: 1290168 == 2*round(2^31 / MLKEM_Q) */ + uint32_t d0 = (uint32_t)u * 1290168; + /* Unsigned shifting by 31 positions leaves only the top bit. */ + return (uint8_t)((d0 + ((uint32_t)1u << 30)) >> 31); +} +#ifdef CBMC +#pragma CPROVER check pop +#endif + +/************************************************************ + * Name: mlk_scalar_compress_d4 + * + * Description: Computes round(u * 16 / q) % 16 + * + * Arguments: - u: Unsigned canonical modulus modulo q + * to be compressed. + * + * Specification: Compress_4 from @[FIPS203, Eq (4.7)]. + * + ************************************************************/ +/* + * The multiplication in this routine will exceed UINT32_MAX + * and wrap around for large values of u. This is expected and required. + */ +#ifdef CBMC +#pragma CPROVER check push +#pragma CPROVER check disable "unsigned-overflow" +#endif + +/* Reference: Embedded into `poly_compress()` in the + * reference implementation @[REF]. */ +static MLK_INLINE uint8_t mlk_scalar_compress_d4(int16_t u) +__contract__( + requires(0 <= u && u <= MLKEM_Q - 1) + ensures(return_value < 16) + ensures(return_value == (((uint32_t)u * 16 + MLKEM_Q / 2) / MLKEM_Q) % 16)) +{ + /* Compute as follows: + * ``` + * round(u * 16 / MLKEM_Q) + * = round(u * 16 * (2^28 / MLKEM_Q) / 2^28) + * ~= round(u * 16 * round(2^28 / MLKEM_Q) / 2^28) + * ``` + */ + /* check-magic: 1290160 == 16 * round(2^28 / MLKEM_Q) */ + uint32_t d0 = (uint32_t)u * 1290160; + /* The return value is < 16, so not altered by the conversion to uint8_t. */ + return (uint8_t)((d0 + ((uint32_t)1u << 27)) >> 28); /* round(d0/2^28) */ +} +#ifdef CBMC +#pragma CPROVER check pop +#endif + +/************************************************************ + * Name: mlk_scalar_decompress_d4 + * + * Description: Computes round(u * q / 16) + * + * Arguments: - u: Unsigned canonical modulus modulo 16 + * to be decompressed. + * + * Specification: Decompress_4 from @[FIPS203, Eq (4.8)]. + * + ************************************************************/ + +/* Reference: Embedded into `poly_decompress()` in the + * reference implementation @[REF]. */ +static MLK_INLINE int16_t mlk_scalar_decompress_d4(uint8_t u) +__contract__( + requires(0 <= u && u < 16) + ensures(return_value <= (MLKEM_Q - 1)) +) +{ + /* The return value is in 0..MLKEM_Q-1, hence not altered by the + * conversion to int16_t. */ + return (int16_t)((((uint32_t)u * MLKEM_Q) + 8) >> 4); +} + +/************************************************************ + * Name: mlk_scalar_compress_d5 + * + * Description: Computes round(u * 32 / q) % 32 + * + * Arguments: - u: Unsigned canonical modulus modulo q + * to be compressed. + * + * Specification: Compress_5 from @[FIPS203, Eq (4.7)]. + * + ************************************************************/ +/* + * The multiplication in this routine will exceed UINT32_MAX + * and wrap around for large values of u. This is expected and required. + */ +#ifdef CBMC +#pragma CPROVER check push +#pragma CPROVER check disable "unsigned-overflow" +#endif + +/* Reference: Embedded into `poly_compress()` in the + * reference implementation @[REF]. */ +static MLK_INLINE uint8_t mlk_scalar_compress_d5(int16_t u) +__contract__( + requires(0 <= u && u <= MLKEM_Q - 1) + ensures(return_value < 32) + ensures(return_value == (((uint32_t)u * 32 + MLKEM_Q / 2) / MLKEM_Q) % 32) ) +{ + /* Compute as follows: + * ``` + * round(u * 32 / MLKEM_Q) + * = round(u * 32 * (2^27 / MLKEM_Q) / 2^27) + * ~= round(u * 32 * round(2^27 / MLKEM_Q) / 2^27) + * ``` + */ + /* check-magic: 1290176 == 2^5 * round(2^27 / MLKEM_Q) */ + uint32_t d0 = (uint32_t)u * 1290176; + /* The return value is < 32, so not altered by the conversion to uint8_t. */ + return (uint8_t)((d0 + ((uint32_t)1u << 26)) >> 27); /* round(d0/2^27) */ +} +#ifdef CBMC +#pragma CPROVER check pop +#endif + +/************************************************************ + * Name: mlk_scalar_decompress_d5 + * + * Description: Computes round(u * q / 32) + * + * Arguments: - u: Unsigned canonical modulus modulo 32 + * to be decompressed. + * + * Specification: Decompress_5 from @[FIPS203, Eq (4.8)]. + * + ************************************************************/ + +/* Reference: Embedded into `poly_decompress()` in the + * reference implementation @[REF]. */ +static MLK_INLINE int16_t mlk_scalar_decompress_d5(uint8_t u) +__contract__( + requires(0 <= u && u < 32) + ensures(0 <= return_value && return_value <= MLKEM_Q - 1) +) +{ + /* The return value is in 0..MLKEM_Q-1, hence not altered by the + * conversion to int16_t. */ + return (int16_t)((((uint32_t)u * MLKEM_Q) + 16) >> 5); +} + +/************************************************************ + * Name: mlk_scalar_compress_d10 + * + * Description: Computes round(u * 2**10 / q) % 2**10 + * + * Arguments: - u: Unsigned canonical modulus modulo q + * to be compressed. + * + * Specification: Compress_10 from @[FIPS203, Eq (4.7)]. + * + ************************************************************/ +/* + * The multiplication in this routine will exceed UINT32_MAX + * and wrap around for large values of u. This is expected and required. + */ +#ifdef CBMC +#pragma CPROVER check push +#pragma CPROVER check disable "unsigned-overflow" +#endif + +/* Reference: Embedded into `polyvec_compress()` in the + * reference implementation @[REF]. */ +static MLK_INLINE uint16_t mlk_scalar_compress_d10(int16_t u) +__contract__( + requires(0 <= u && u <= MLKEM_Q - 1) + ensures(return_value < (1u << 10)) + ensures(return_value == (((uint32_t)u * (1u << 10) + MLKEM_Q / 2) / MLKEM_Q) % (1 << 10))) +{ + /* Compute as follows: + * ``` + * round(u * 1024 / MLKEM_Q) + * = round(u * 1024 * (2^33 / MLKEM_Q) / 2^33) + * ~= round(u * 1024 * round(2^33 / MLKEM_Q) / 2^33) + * ``` + */ + /* check-magic: 2642263040 == 2^10 * round(2^33 / MLKEM_Q) */ + uint64_t d0 = (uint64_t)u * 2642263040; + d0 = (d0 + ((uint64_t)1u << 32)) >> 33; /* round(d0/2^33) */ + return (d0 & 0x3FF); +} +#ifdef CBMC +#pragma CPROVER check pop +#endif + +/************************************************************ + * Name: mlk_scalar_decompress_d10 + * + * Description: Computes round(u * q / 1024) + * + * Arguments: - u: Unsigned canonical modulus modulo 1024 + * to be decompressed. + * + * Specification: Decompress_10 from @[FIPS203, Eq (4.8)]. + * + ************************************************************/ + +/* Reference: Embedded into `polyvec_decompress()` in the + * reference implementation @[REF]. */ +static MLK_INLINE int16_t mlk_scalar_decompress_d10(uint16_t u) +__contract__( + requires(0 <= u && u < 1024) + ensures(0 <= return_value && return_value <= (MLKEM_Q - 1)) +) +{ + /* The return value is in 0..MLKEM_Q-1, hence not altered by the + * conversion to int16_t. */ + return (int16_t)((((uint32_t)u * MLKEM_Q) + 512) >> 10); +} + +/************************************************************ + * Name: mlk_scalar_compress_d11 + * + * Description: Computes round(u * 2**11 / q) % 2**11 + * + * Arguments: - u: Unsigned canonical modulus modulo q + * to be compressed. + * + * Specification: Compress_11 from @[FIPS203, Eq (4.7)]. + * + ************************************************************/ +/* + * The multiplication in this routine will exceed UINT32_MAX + * and wrap around for large values of u. This is expected and required. + */ +#ifdef CBMC +#pragma CPROVER check push +#pragma CPROVER check disable "unsigned-overflow" +#endif + +/* Reference: Embedded into `polyvec_compress()` in the + * reference implementation @[REF]. */ +static MLK_INLINE uint16_t mlk_scalar_compress_d11(int16_t u) +__contract__( + requires(0 <= u && u <= MLKEM_Q - 1) + ensures(return_value < (1u << 11)) + ensures(return_value == (((uint32_t)u * (1u << 11) + MLKEM_Q / 2) / MLKEM_Q) % (1 << 11))) +{ + /* Compute as follows: + * ``` + * round(u * 2048 / MLKEM_Q) + * = round(u * 2048 * (2^33 / MLKEM_Q) / 2^33) + * ~= round(u * 2048 * round(2^33 / MLKEM_Q) / 2^33) + * ``` + */ + /* check-magic: 5284526080 == 2^11 * round(2^33 / MLKEM_Q) */ + uint64_t d0 = (uint64_t)u * 5284526080; + d0 = (d0 + ((uint64_t)1u << 32)) >> 33; /* round(d0/2^33) */ + return (d0 & 0x7FF); +} +#ifdef CBMC +#pragma CPROVER check pop +#endif + +/************************************************************ + * Name: mlk_scalar_decompress_d11 + * + * Description: Computes round(u * q / 2048) + * + * Arguments: - u: Unsigned canonical modulus modulo 2048 + * to be decompressed. + * + * Specification: Decompress_11 from @[FIPS203, Eq (4.8)]. + * + ************************************************************/ + +/* Reference: Embedded into `polyvec_decompress()` in the + * reference implementation @[REF]. */ +static MLK_INLINE int16_t mlk_scalar_decompress_d11(uint16_t u) +__contract__( + requires(0 <= u && u < 2048) + ensures(0 <= return_value && return_value <= (MLKEM_Q - 1)) +) +{ + /* The return value is in 0..MLKEM_Q-1, hence not altered by the + * conversion to int16_t. */ + return (int16_t)((((uint32_t)u * MLKEM_Q) + 1024) >> 11); +} + +#if defined(MLK_CONFIG_MULTILEVEL_WITH_SHARED) || (MLKEM_K == 2 || MLKEM_K == 3) +#define mlk_poly_compress_d4 MLK_NAMESPACE(poly_compress_d4) +/************************************************* + * Name: mlk_poly_compress_d4 + * + * Description: Compression (4 bits) and subsequent serialization of a + * polynomial + * + * Arguments: - uint8_t *r: pointer to output byte array + * (of length MLKEM_POLYCOMPRESSEDBYTES_D4 bytes) + * - const mlk_poly *a: pointer to input polynomial + * Coefficients must be unsigned canonical, + * i.e. in [0,1,..,MLKEM_Q-1]. + * + * Specification: Implements `ByteEncode_4 (Compress_4 (a))`: + * - ByteEncode_d: @[FIPS203, Algorithm 5], + * - Compress_d: @[FIPS203, Eq (4.7)] + * Extended to vectors as per + * @[FIPS203, 2.4.8 Applying Algorithms to Arrays] + * - `ByteEncode_{d_v} (Compress_{d_v} (v))` appears in + * @[FIPS203, Algorithm 14 (K-PKE.Encrypt), L23], + * where `d_v=4` for ML-KEM-{512,768} @[FIPS203, Table 2]. + * + **************************************************/ +MLK_INTERNAL_API +void mlk_poly_compress_d4(uint8_t r[MLKEM_POLYCOMPRESSEDBYTES_D4], + const mlk_poly *a); + +#define mlk_poly_compress_d10 MLK_NAMESPACE(poly_compress_d10) +/************************************************* + * Name: mlk_poly_compress_d10 + * + * Description: Compression (10 bits) and subsequent serialization of a + * polynomial + * + * Arguments: - uint8_t *r: pointer to output byte array + * (of length MLKEM_POLYCOMPRESSEDBYTES_D10 bytes) + * - const mlk_poly *a: pointer to input polynomial + * Coefficients must be unsigned canonical, + * i.e. in [0,1,..,MLKEM_Q-1]. + * + * Specification: Implements `ByteEncode_10 (Compress_10 (a))`: + * - ByteEncode_d: @[FIPS203, Algorithm 5], + * - Compress_d: @[FIPS203, Eq (4.7)] + * Extended to vectors as per + * @[FIPS203, 2.4.8 Applying Algorithms to Arrays] + * - `ByteEncode_{d_u} (Compress_{d_u} (u))` appears in + * @[FIPS203, Algorithm 14 (K-PKE.Encrypt), L22], + * where `d_u=10` for ML-KEM-{512,768} @[FIPS203, Table 2]. + * + **************************************************/ +MLK_INTERNAL_API +void mlk_poly_compress_d10(uint8_t r[MLKEM_POLYCOMPRESSEDBYTES_D10], + const mlk_poly *a); + +#define mlk_poly_decompress_d4 MLK_NAMESPACE(poly_decompress_d4) +/************************************************* + * Name: mlk_poly_decompress_d4 + * + * Description: De-serialization and subsequent decompression (dv bits) of a + * polynomial; approximate inverse of poly_compress + * + * Arguments: - mlk_poly *r: pointer to output polynomial + * - const uint8_t *a: pointer to input byte array + * (of length MLKEM_POLYCOMPRESSEDBYTES_D4 bytes) + * + * Upon return, the coefficients of the output polynomial are unsigned-canonical + * (non-negative and smaller than MLKEM_Q). + * + * Specification: Implements `Decompress_4 (ByteDecode_4 (a))`: + * - ByteDecode_d: @[FIPS203, Algorithm 6], + * - Decompress_d: @[FIPS203, Eq (4.8)] + * Extended to vectors as per + * @[FIPS203, 2.4.8 Applying Algorithms to Arrays] + * - `Decompress_{d_v} (ByteDecode_{d_v} (v))` appears in + * @[FIPS203, Algorithm 15 (K-PKE.Decrypt), L4], + * where `d_v=4` for ML-KEM-{512,768} @[FIPS203, Table 2]. + * + **************************************************/ +MLK_INTERNAL_API +void mlk_poly_decompress_d4(mlk_poly *r, + const uint8_t a[MLKEM_POLYCOMPRESSEDBYTES_D4]); + +#define mlk_poly_decompress_d10 MLK_NAMESPACE(poly_decompress_d10) +/************************************************* + * Name: mlk_poly_decompress_d10 + * + * Description: De-serialization and subsequent decompression (10 bits) of a + * polynomial; approximate inverse of mlk_poly_compress_d10 + * + * Arguments: - mlk_poly *r: pointer to output polynomial + * - const uint8_t *a: pointer to input byte array + * (of length MLKEM_POLYCOMPRESSEDBYTES_D10 bytes) + * + * Upon return, the coefficients of the output polynomial are unsigned-canonical + * (non-negative and smaller than MLKEM_Q). + * + * Specification: Implements `Decompress_10 (ByteDecode_10 (a))`: + * - ByteDecode_d: @[FIPS203, Algorithm 6], + * - Decompress_d: @[FIPS203, Eq (4.8)] + * Extended to vectors as per + * @[FIPS203, 2.4.8 Applying Algorithms to Arrays] + * - `Decompress_{d_u} (ByteDecode_{d_u} (u))` appears in + * @[FIPS203, Algorithm 15 (K-PKE.Decrypt), L3], + * where `d_u=10` for ML-KEM-{512,768} @[FIPS203, Table 2]. + * + **************************************************/ +MLK_INTERNAL_API +void mlk_poly_decompress_d10(mlk_poly *r, + const uint8_t a[MLKEM_POLYCOMPRESSEDBYTES_D10]); +#endif /* MLK_CONFIG_MULTILEVEL_WITH_SHARED || MLKEM_K == 2 || MLKEM_K == 3 */ + +#if defined(MLK_CONFIG_MULTILEVEL_WITH_SHARED) || MLKEM_K == 4 +#define mlk_poly_compress_d5 MLK_NAMESPACE(poly_compress_d5) +/************************************************* + * Name: mlk_poly_compress_d5 + * + * Description: Compression (5 bits) and subsequent serialization of a + * polynomial + * + * Arguments: - uint8_t *r: pointer to output byte array + * (of length MLKEM_POLYCOMPRESSEDBYTES_D5 bytes) + * - const mlk_poly *a: pointer to input polynomial + * Coefficients must be unsigned canonical, + * i.e. in [0,1,..,MLKEM_Q-1]. + * + * Specification: Implements `ByteEncode_5 (Compress_5 (a))`: + * - ByteEncode_d: @[FIPS203, Algorithm 5], + * - Compress_d: @[FIPS203, Eq (4.7)] + * Extended to vectors as per + * @[FIPS203, 2.4.8 Applying Algorithms to Arrays] + * - `ByteEncode_{d_v} (Compress_{d_v} (v))` appears in + * @[FIPS203, Algorithm 14 (K-PKE.Encrypt), L23], + * where `d_v=5` for ML-KEM-1024 @[FIPS203, Table 2]. + * + **************************************************/ +MLK_INTERNAL_API +void mlk_poly_compress_d5(uint8_t r[MLKEM_POLYCOMPRESSEDBYTES_D5], + const mlk_poly *a); + +#define mlk_poly_compress_d11 MLK_NAMESPACE(poly_compress_d11) +/************************************************* + * Name: mlk_poly_compress_d11 + * + * Description: Compression (11 bits) and subsequent serialization of a + * polynomial + * + * Arguments: - uint8_t *r: pointer to output byte array + * (of length MLKEM_POLYCOMPRESSEDBYTES_D11 bytes) + * - const mlk_poly *a: pointer to input polynomial + * Coefficients must be unsigned canonical, + * i.e. in [0,1,..,MLKEM_Q-1]. + * + * Specification: `ByteEncode_11 (Compress_11 (a))`: + * - ByteEncode_d: @[FIPS203, Algorithm 5], + * - Compress_d: @[FIPS203, Eq (4.7)] + * Extended to vectors as per + * @[FIPS203, 2.4.8 Applying Algorithms to Arrays] + * - `ByteEncode_{d_u} (Compress_{d_u} (u))` appears in + * @[FIPS203, Algorithm 14 (K-PKE.Encrypt), L22], + * where `d_u=11` for ML-KEM-1024 @[FIPS203, Table 2]. + * + **************************************************/ +MLK_INTERNAL_API +void mlk_poly_compress_d11(uint8_t r[MLKEM_POLYCOMPRESSEDBYTES_D11], + const mlk_poly *a); + +#define mlk_poly_decompress_d5 MLK_NAMESPACE(poly_decompress_d5) +/************************************************* + * Name: mlk_poly_decompress_d5 + * + * Description: De-serialization and subsequent decompression (dv bits) of a + * polynomial; approximate inverse of poly_compress + * + * Arguments: - mlk_poly *r: pointer to output polynomial + * - const uint8_t *a: pointer to input byte array + * (of length MLKEM_POLYCOMPRESSEDBYTES_D5 bytes) + * + * Upon return, the coefficients of the output polynomial are unsigned-canonical + * (non-negative and smaller than MLKEM_Q). + * + * Specification: Implements `Decompress_5 (ByteDecode_5 (a))`: + * - ByteDecode_d: @[FIPS203, Algorithm 6], + * - Decompress_d: @[FIPS203, Eq (4.8)] + * Extended to vectors as per + * @[FIPS203, 2.4.8 Applying Algorithms to Arrays] + * - `Decompress_{d_v} (ByteDecode_{d_v} (v))` appears in + * @[FIPS203, Algorithm 15 (K-PKE.Decrypt), L4], + * where `d_v=5` for ML-KEM-1024 @[FIPS203, Table 2]. + * + **************************************************/ +MLK_INTERNAL_API +void mlk_poly_decompress_d5(mlk_poly *r, + const uint8_t a[MLKEM_POLYCOMPRESSEDBYTES_D5]); + +#define mlk_poly_decompress_d11 MLK_NAMESPACE(poly_decompress_d11) +/************************************************* + * Name: mlk_poly_decompress_d11 + * + * Description: De-serialization and subsequent decompression (11 bits) of a + * polynomial; approximate inverse of mlk_poly_compress_d11 + * + * Arguments: - mlk_poly *r: pointer to output polynomial + * - const uint8_t *a: pointer to input byte array + * (of length MLKEM_POLYCOMPRESSEDBYTES_D11 bytes) + * + * Upon return, the coefficients of the output polynomial are unsigned-canonical + * (non-negative and smaller than MLKEM_Q). + * + * Specification: Implements `Decompress_11 (ByteDecode_11 (a))`: + * - ByteDecode_d: @[FIPS203, Algorithm 6], + * - Decompress_d: @[FIPS203, Eq (4.8)] + * Extended to vectors as per + * @[FIPS203, 2.4.8 Applying Algorithms to Arrays] + * - `Decompress_{d_u} (ByteDecode_{d_u} (u))` appears in + * @[FIPS203, Algorithm 15 (K-PKE.Decrypt), L3], + * where `d_u=11` for ML-KEM-1024 @[FIPS203, Table 2]. + * + **************************************************/ +MLK_INTERNAL_API +void mlk_poly_decompress_d11(mlk_poly *r, + const uint8_t a[MLKEM_POLYCOMPRESSEDBYTES_D11]); +#endif /* MLK_CONFIG_MULTILEVEL_WITH_SHARED || MLKEM_K == 4 */ + +#define mlk_poly_tobytes MLK_NAMESPACE(poly_tobytes) +/************************************************* + * Name: mlk_poly_tobytes + * + * Description: Serialization of a polynomial. + * Signed coefficients are converted to + * unsigned form before serialization. + * + * Arguments: INPUT: + * - a: const pointer to input polynomial, + * with each coefficient in the range [0,1,..,Q-1] + * OUTPUT + * - r: pointer to output byte array + * (of MLKEM_POLYBYTES bytes) + * + * Specification: Implements ByteEncode_12 @[FIPS203, Algorithm 5]. + * Extended to vectors as per + * @[FIPS203, 2.4.8 Applying Algorithms to Arrays] + * + **************************************************/ +MLK_INTERNAL_API +void mlk_poly_tobytes(uint8_t r[MLKEM_POLYBYTES], const mlk_poly *a) +__contract__( + requires(memory_no_alias(r, MLKEM_POLYBYTES)) + requires(memory_no_alias(a, sizeof(mlk_poly))) + requires(array_bound(a->coeffs, 0, MLKEM_N, 0, MLKEM_Q)) + assigns(memory_slice(r, MLKEM_POLYBYTES)) +); + + +#define mlk_poly_frombytes MLK_NAMESPACE(poly_frombytes) +/************************************************* + * Name: mlk_poly_frombytes + * + * Description: De-serialization of a polynomial. + * + * Arguments: INPUT + * - a: pointer to input byte array + * (of MLKEM_POLYBYTES bytes) + * OUTPUT + * - r: pointer to output polynomial, with + * each coefficient unsigned and in the range + * 0 .. 4095 + * + * Specification: Implements ByteDecode_12 @[FIPS203, Algorithm 6]. + * Extended to vectors as per + * @[FIPS203, 2.4.8 Applying Algorithms to Arrays] + * + **************************************************/ +MLK_INTERNAL_API +void mlk_poly_frombytes(mlk_poly *r, const uint8_t a[MLKEM_POLYBYTES]) +__contract__( + requires(memory_no_alias(a, MLKEM_POLYBYTES)) + requires(memory_no_alias(r, sizeof(mlk_poly))) + assigns(memory_slice(r, sizeof(mlk_poly))) + ensures(array_bound(r->coeffs, 0, MLKEM_N, 0, MLKEM_UINT12_LIMIT)) +); + + +#define mlk_poly_frommsg MLK_NAMESPACE(poly_frommsg) +/************************************************* + * Name: mlk_poly_frommsg + * + * Description: Convert 32-byte message to polynomial + * + * Arguments: - mlk_poly *r: pointer to output polynomial + * - const uint8_t *msg: pointer to input message + * + * Specification: Implements `Decompress_1 (ByteDecode_1 (a))`: + * - ByteDecode_d: @[FIPS203, Algorithm 6], + * - Decompress_d: @[FIPS203, Eq (4.8)] + * Extended to vectors as per + * @[FIPS203, 2.4.8 Applying Algorithms to Arrays] + * - `Decompress_1 (ByteDecode_1 (w))` appears in + * @[FIPS203, Algorithm 15 (K-PKE.Encrypt), L20]. + * + **************************************************/ +MLK_INTERNAL_API +void mlk_poly_frommsg(mlk_poly *r, const uint8_t msg[MLKEM_INDCPA_MSGBYTES]) +__contract__( + requires(memory_no_alias(msg, MLKEM_INDCPA_MSGBYTES)) + requires(memory_no_alias(r, sizeof(mlk_poly))) + assigns(memory_slice(r, sizeof(mlk_poly))) + ensures(array_bound(r->coeffs, 0, MLKEM_N, 0, MLKEM_Q)) +); + +#define mlk_poly_tomsg MLK_NAMESPACE(poly_tomsg) +/************************************************* + * Name: mlk_poly_tomsg + * + * Description: Convert polynomial to 32-byte message + * + * Arguments: - uint8_t *msg: pointer to output message + * - const mlk_poly *r: pointer to input polynomial + * Coefficients must be unsigned canonical + * + * Specification: Implements `ByteEncode_1 (Compress_1 (a))`: + * - ByteEncode_d: @[FIPS203, Algorithm 5], + * - Compress_d: @[FIPS203, Eq (4.7)] + * Extended to vectors as per + * @[FIPS203, 2.4.8 Applying Algorithms to Arrays] + * - `ByteEncode_1 (Compress_1 (w))` appears in + * @[FIPS203, Algorithm 14 (K-PKE.Decrypt), L7]. + * + **************************************************/ +MLK_INTERNAL_API +void mlk_poly_tomsg(uint8_t msg[MLKEM_INDCPA_MSGBYTES], const mlk_poly *r) +__contract__( + requires(memory_no_alias(msg, MLKEM_INDCPA_MSGBYTES)) + requires(memory_no_alias(r, sizeof(mlk_poly))) + requires(array_bound(r->coeffs, 0, MLKEM_N, 0, MLKEM_Q)) + assigns(memory_slice(msg, MLKEM_INDCPA_MSGBYTES)) +); + +#endif /* !MLK_COMPRESS_H */ diff --git a/mlkem_native/src/debug.c b/mlkem_native/src/debug.c new file mode 100644 index 0000000..386f526 --- /dev/null +++ b/mlkem_native/src/debug.c @@ -0,0 +1,64 @@ +/* + * Copyright (c) The mlkem-native project authors + * SPDX-License-Identifier: Apache-2.0 OR ISC OR MIT + */ + +/* NOTE: You can remove this file unless you compile with MLKEM_DEBUG. */ + +#include "common.h" + +#if !defined(MLK_CONFIG_MULTILEVEL_NO_SHARED) && defined(MLKEM_DEBUG) + + +#include +#include +#include "debug.h" + +#define MLK_DEBUG_ERROR_HEADER "[ERROR:%s:%04d] " + +void mlk_debug_check_assert(const char *file, int line, const int val) +{ + if (val == 0) + { + fprintf(stderr, MLK_DEBUG_ERROR_HEADER "Assertion failed (value %d)\n", + file, line, val); + exit(1); + } +} + +void mlk_debug_check_bounds(const char *file, int line, const int16_t *ptr, + unsigned len, int lower_bound_exclusive, + int upper_bound_exclusive) +{ + int err = 0; + unsigned i; + for (i = 0; i < len; i++) + { + int16_t val = ptr[i]; + if (!(val > lower_bound_exclusive && val < upper_bound_exclusive)) + { + fprintf( + stderr, + MLK_DEBUG_ERROR_HEADER + "Bounds assertion failed: Index %u, value %d out of bounds (%d,%d)\n", + file, line, i, (int)val, lower_bound_exclusive, + upper_bound_exclusive); + err = 1; + } + } + + if (err == 1) + { + exit(1); + } +} + +#else /* !MLK_CONFIG_MULTILEVEL_NO_SHARED && MLKEM_DEBUG */ + +MLK_EMPTY_CU(debug) + +#endif /* !(!MLK_CONFIG_MULTILEVEL_NO_SHARED && MLKEM_DEBUG) */ + +/* To facilitate single-compilation-unit (SCU) builds, undefine all macros. + * Don't modify by hand -- this is auto-generated by scripts/autogen. */ +#undef MLK_DEBUG_ERROR_HEADER diff --git a/mlkem_native/src/debug.h b/mlkem_native/src/debug.h new file mode 100644 index 0000000..47c864b --- /dev/null +++ b/mlkem_native/src/debug.h @@ -0,0 +1,128 @@ +/* + * Copyright (c) The mlkem-native project authors + * SPDX-License-Identifier: Apache-2.0 OR ISC OR MIT + */ +#ifndef MLK_DEBUG_H +#define MLK_DEBUG_H +#include "common.h" + +#if defined(MLKEM_DEBUG) + +/************************************************* + * Name: mlk_assert + * + * Description: Check debug assertion + * + * Prints an error message to stderr and calls + * exit(1) if not. + * + * Arguments: - file: filename + * - line: line number + * - val: Value asserted to be non-zero + **************************************************/ +#define mlk_debug_check_assert MLK_NAMESPACE(mlkem_debug_assert) +void mlk_debug_check_assert(const char *file, int line, const int val); + +/************************************************* + * Name: mlk_debug_check_bounds + * + * Description: Check whether values in an array of int16_t + * are within specified bounds. + * + * Prints an error message to stderr and calls + * exit(1) if not. + * + * Arguments: - file: filename + * - line: line number + * - ptr: Base of array to be checked + * - len: Number of int16_t in ptr + * - lower_bound_exclusive: Exclusive lower bound + * - upper_bound_exclusive: Exclusive upper bound + **************************************************/ +#define mlk_debug_check_bounds MLK_NAMESPACE(mlkem_debug_check_bounds) +void mlk_debug_check_bounds(const char *file, int line, const int16_t *ptr, + unsigned len, int lower_bound_exclusive, + int upper_bound_exclusive); + +/* Check assertion, calling exit() upon failure + * + * val: Value that's asserted to be non-zero + */ +#define mlk_assert(val) mlk_debug_check_assert(__FILE__, __LINE__, (val)) + +/* Check bounds in array of int16_t's + * ptr: Base of int16_t array; will be explicitly cast to int16_t*, + * so you may pass a byte-compatible type such as mlk_poly or mlk_polyvec. + * len: Number of int16_t in array + * value_lb: Inclusive lower value bound + * value_ub: Exclusive upper value bound */ +#define mlk_assert_bound(ptr, len, value_lb, value_ub) \ + mlk_debug_check_bounds(__FILE__, __LINE__, (const int16_t *)(ptr), (len), \ + (value_lb) - 1, (value_ub)) + +/* Check absolute bounds in array of int16_t's + * ptr: Base of array, expression of type int16_t* + * len: Number of int16_t in array + * value_abs_bd: Exclusive absolute upper bound */ +#define mlk_assert_abs_bound(ptr, len, value_abs_bd) \ + mlk_assert_bound((ptr), (len), (-(value_abs_bd) + 1), (value_abs_bd)) + +/* Version of bounds assertions for 2-dimensional arrays */ +#define mlk_assert_bound_2d(ptr, len0, len1, value_lb, value_ub) \ + mlk_assert_bound((ptr), ((len0) * (len1)), (value_lb), (value_ub)) + +#define mlk_assert_abs_bound_2d(ptr, len0, len1, value_abs_bd) \ + mlk_assert_abs_bound((ptr), ((len0) * (len1)), (value_abs_bd)) + +/* When running CBMC, convert debug assertions into proof obligations */ +#elif defined(CBMC) +#include "cbmc.h" + +#define mlk_assert(val) cassert(val) + +#define mlk_assert_bound(ptr, len, value_lb, value_ub) \ + cassert(array_bound(((int16_t *)(ptr)), 0, (len), (value_lb), (value_ub))) + +#define mlk_assert_abs_bound(ptr, len, value_abs_bd) \ + cassert(array_abs_bound(((int16_t *)(ptr)), 0, (len), (value_abs_bd))) + +/* Because of https://github.com/diffblue/cbmc/issues/8570, we can't + * just use a single flattened array_bound(...) here. */ +#define mlk_assert_bound_2d(ptr, M, N, value_lb, value_ub) \ + cassert(forall(kN, 0, (M), \ + array_bound(&((int16_t (*)[(N)])(ptr))[kN][0], 0, (N), \ + (value_lb), (value_ub)))) + +#define mlk_assert_abs_bound_2d(ptr, M, N, value_abs_bd) \ + cassert(forall(kN, 0, (M), \ + array_abs_bound(&((int16_t (*)[(N)])(ptr))[kN][0], 0, (N), \ + (value_abs_bd)))) + +#else /* !MLKEM_DEBUG && CBMC */ + +#define mlk_assert(val) \ + do \ + { \ + } while (0) +#define mlk_assert_bound(ptr, len, value_lb, value_ub) \ + do \ + { \ + } while (0) +#define mlk_assert_abs_bound(ptr, len, value_abs_bd) \ + do \ + { \ + } while (0) + +#define mlk_assert_bound_2d(ptr, len0, len1, value_lb, value_ub) \ + do \ + { \ + } while (0) + +#define mlk_assert_abs_bound_2d(ptr, len0, len1, value_abs_bd) \ + do \ + { \ + } while (0) + + +#endif /* !MLKEM_DEBUG && !CBMC */ +#endif /* !MLK_DEBUG_H */ diff --git a/mlkem_native/src/fips202/fips202.c b/mlkem_native/src/fips202/fips202.c new file mode 100644 index 0000000..4751efb --- /dev/null +++ b/mlkem_native/src/fips202/fips202.c @@ -0,0 +1,251 @@ +/* + * Copyright (c) The mlkem-native project authors + * SPDX-License-Identifier: Apache-2.0 OR ISC OR MIT + */ + +/* References + * ========== + * + * - [FIPS203] + * FIPS 203 Module-Lattice-Based Key-Encapsulation Mechanism Standard + * National Institute of Standards and Technology + * https://csrc.nist.gov/pubs/fips/203/final + * + * - [mupq] + * Common files for pqm4, pqm3, pqriscv + * Kannwischer, Petri, Rijneveld, Schwabe, Stoffelen + * https://github.com/mupq/mupq + * + * - [supercop] + * SUPERCOP benchmarking framework + * Daniel J. Bernstein + * http://bench.cr.yp.to/supercop.html + * + * - [tweetfips] + * 'tweetfips202' FIPS202 implementation + * Van Assche, Bernstein, Schwabe + * https://keccak.team/2015/tweetfips202.html + */ + +/* Based on the CC0 implementation from @[mupq] and the public domain + * implementation @[supercop, crypto_hash/keccakc512/simple/] + * by Ronny Van Keer, and the public domain @[tweetfips] implementation. */ + +#include "../common.h" +#if !defined(MLK_CONFIG_MULTILEVEL_NO_SHARED) + + +#include "../verify.h" +#include "fips202.h" +#include "keccakf1600.h" + +/************************************************* + * Name: mlk_keccak_absorb_once + * + * Description: Absorb step of Keccak; + * non-incremental, starts by zeroeing the state. + * + * WARNING: Must only be called once. + * + * Arguments: - uint64_t *s: pointer to (uninitialized) output Keccak + * state + * - unsigned r: rate in bytes (e.g., 168 for SHAKE128) + * - const uint8_t *m: pointer to input to be absorbed into s + * - size_t mlen: length of input in bytes + * - uint8_t p: domain-separation byte for different + * Keccak-derived functions + **************************************************/ +static void mlk_keccak_absorb_once(uint64_t *s, unsigned r, const uint8_t *m, + size_t mlen, uint8_t p) +__contract__( + requires(mlen <= MLK_MAX_BUFFER_SIZE) + requires(r <= sizeof(uint64_t) * MLK_KECCAK_LANES) + requires(memory_no_alias(s, sizeof(uint64_t) * MLK_KECCAK_LANES)) + requires(memory_no_alias(m, mlen)) + assigns(memory_slice(s, sizeof(uint64_t) * MLK_KECCAK_LANES))) +{ + /* Initialize state */ + size_t i; + for (i = 0; i < 25; ++i) + __loop__(invariant(i <= 25)) + { + s[i] = 0; + } + + while (mlen >= r) + __loop__( + assigns(mlen, m, memory_slice(s, sizeof(uint64_t) * MLK_KECCAK_LANES)) + invariant(mlen <= loop_entry(mlen)) + invariant(m == loop_entry(m) + (loop_entry(mlen) - mlen))) + { + mlk_keccakf1600_xor_bytes(s, m, 0, r); + mlk_keccakf1600_permute(s); + mlen -= r; + m += r; + } + + /* At this point, mlen < r, so the truncations to unsigned are safe below. */ + + if (mlen > 0) + { + mlk_keccakf1600_xor_bytes(s, m, 0, (unsigned int)mlen); + } + + if (mlen == r - 1) + { + p |= 128; + mlk_keccakf1600_xor_bytes(s, &p, (unsigned int)mlen, 1); + } + else + { + mlk_keccakf1600_xor_bytes(s, &p, (unsigned int)mlen, 1); + p = 128; + mlk_keccakf1600_xor_bytes(s, &p, r - 1, 1); + } +} + +/************************************************* + * Name: mlk_keccak_squeezeblocks + * + * Description: block-level Keccak squeeze + * + * Arguments: - uint8_t *h: pointer to output bytes + * - size_t nblocks: number of blocks to be squeezed + * - uint64_t *s_inc: pointer to input/output state + * - unsigned r: rate in bytes (e.g., 168 for SHAKE128) + **************************************************/ +static void mlk_keccak_squeezeblocks(uint8_t *h, size_t nblocks, uint64_t *s, + unsigned r) +__contract__( + requires(r <= sizeof(uint64_t) * MLK_KECCAK_LANES) + requires(nblocks <= 8 /* somewhat arbitrary bound */) + requires(memory_no_alias(s, sizeof(uint64_t) * MLK_KECCAK_LANES)) + requires(memory_no_alias(h, nblocks * r)) + assigns(memory_slice(s, sizeof(uint64_t) * MLK_KECCAK_LANES)) + assigns(memory_slice(h, nblocks * r))) +{ + while (nblocks > 0) + __loop__( + assigns(h, nblocks, + memory_slice(s, sizeof(uint64_t) * MLK_KECCAK_LANES), + memory_slice(h, nblocks * r)) + invariant(nblocks <= loop_entry(nblocks) && + h == loop_entry(h) + r * (loop_entry(nblocks) - nblocks))) + { + mlk_keccakf1600_permute(s); + mlk_keccakf1600_extract_bytes(s, h, 0, r); + h += r; + nblocks--; + } +} + +/************************************************* + * Name: mlk_keccak_squeeze_once + * + * Description: Keccak squeeze; can be called on byte-level + * + * WARNING: This must only be called once. + * + * Arguments: - uint8_t *h: pointer to output bytes + * - size_t outlen: number of bytes to be squeezed + * - uint64_t *s_inc: pointer to Keccak state + * - unsigned r: rate in bytes (e.g., 168 for SHAKE128) + **************************************************/ +static void mlk_keccak_squeeze_once(uint8_t *h, size_t outlen, uint64_t *s, + unsigned r) +__contract__( + requires(outlen <= MLK_MAX_BUFFER_SIZE) + requires(r <= sizeof(uint64_t) * MLK_KECCAK_LANES) + requires(memory_no_alias(s, sizeof(uint64_t) * MLK_KECCAK_LANES)) + requires(memory_no_alias(h, outlen)) + assigns(memory_slice(s, sizeof(uint64_t) * MLK_KECCAK_LANES)) + assigns(memory_slice(h, outlen))) +{ + size_t len; + while (outlen > 0) + __loop__( + assigns(len, h, outlen, + memory_slice(s, sizeof(uint64_t) * MLK_KECCAK_LANES), + memory_slice(h, outlen)) + invariant(outlen <= loop_entry(outlen) && + h == loop_entry(h) + (loop_entry(outlen) - outlen))) + { + mlk_keccakf1600_permute(s); + + if (outlen < r) + { + len = outlen; + } + else + { + len = r; + } + mlk_keccakf1600_extract_bytes(s, h, 0, (unsigned int)len); + h += len; + outlen -= len; + } +} + +void mlk_shake128_absorb_once(mlk_shake128ctx *state, const uint8_t *input, + size_t inlen) +{ + mlk_keccak_absorb_once(state->ctx, SHAKE128_RATE, input, inlen, 0x1F); +} + +void mlk_shake128_squeezeblocks(uint8_t *output, size_t nblocks, + mlk_shake128ctx *state) +{ + mlk_keccak_squeezeblocks(output, nblocks, state->ctx, SHAKE128_RATE); +} + +void mlk_shake128_init(mlk_shake128ctx *state) { (void)state; } +void mlk_shake128_release(mlk_shake128ctx *state) +{ + /* Specification: Partially implements + * @[FIPS203, Section 3.3, Destruction of intermediate values] */ + mlk_zeroize(state, sizeof(mlk_shake128ctx)); +} + +typedef mlk_shake128ctx mlk_shake256ctx; +void mlk_shake256(uint8_t *output, size_t outlen, const uint8_t *input, + size_t inlen) +{ + mlk_shake256ctx state; + /* Absorb input */ + mlk_keccak_absorb_once(state.ctx, SHAKE256_RATE, input, inlen, 0x1F); + /* Squeeze output */ + mlk_keccak_squeeze_once(output, outlen, state.ctx, SHAKE256_RATE); + /* Specification: Partially implements + * @[FIPS203, Section 3.3, Destruction of intermediate values] */ + mlk_zeroize(&state, sizeof(state)); +} + +void mlk_sha3_256(uint8_t *output, const uint8_t *input, size_t inlen) +{ + uint64_t ctx[25]; + /* Absorb input */ + mlk_keccak_absorb_once(ctx, SHA3_256_RATE, input, inlen, 0x06); + /* Squeeze output */ + mlk_keccak_squeeze_once(output, 32, ctx, SHA3_256_RATE); + /* Specification: Partially implements + * @[FIPS203, Section 3.3, Destruction of intermediate values] */ + mlk_zeroize(ctx, sizeof(ctx)); +} + +void mlk_sha3_512(uint8_t *output, const uint8_t *input, size_t inlen) +{ + uint64_t ctx[25]; + /* Absorb input */ + mlk_keccak_absorb_once(ctx, SHA3_512_RATE, input, inlen, 0x06); + /* Squeeze output */ + mlk_keccak_squeeze_once(output, 64, ctx, SHA3_512_RATE); + /* Specification: Partially implements + * @[FIPS203, Section 3.3, Destruction of intermediate values] */ + mlk_zeroize(ctx, sizeof(ctx)); +} + +#else /* !MLK_CONFIG_MULTILEVEL_NO_SHARED */ + +MLK_EMPTY_CU(fips202) + +#endif /* MLK_CONFIG_MULTILEVEL_NO_SHARED */ diff --git a/mlkem_native/src/fips202/fips202.h b/mlkem_native/src/fips202/fips202.h new file mode 100644 index 0000000..9ebc158 --- /dev/null +++ b/mlkem_native/src/fips202/fips202.h @@ -0,0 +1,158 @@ +/* + * Copyright (c) The mlkem-native project authors + * SPDX-License-Identifier: Apache-2.0 OR ISC OR MIT + */ +#ifndef MLK_FIPS202_FIPS202_H +#define MLK_FIPS202_FIPS202_H + +#include "../cbmc.h" +#include "../common.h" + +#define SHAKE128_RATE 168 +#define SHAKE256_RATE 136 +#define SHA3_256_RATE 136 +#define SHA3_384_RATE 104 +#define SHA3_512_RATE 72 + +/* Context for non-incremental API */ +typedef struct +{ + uint64_t ctx[25]; +} MLK_ALIGN mlk_shake128ctx; + +#define mlk_shake128_absorb_once MLK_NAMESPACE(shake128_absorb_once) +/************************************************* + * Name: mlk_shake128_absorb_once + * + * Description: One-shot absorb step of the SHAKE128 XOF. + * + * For call-sites (in mlkem-native): + * - This function MUST ONLY be called straight after + * mlk_shake128_init(). + * - This function MUST ONLY be called once. + * + * Consequently, for providers of custom FIPS202 code + * to be used with mlkem-native: + * - You may assume that the input context is + * freshly initialized via mlk_shake128_init(). + * - You may assume that this function is + * called exactly once. + * + * Arguments: - mlk_shake128ctx *state: pointer to SHAKE128 context + * - const uint8_t *input: pointer to input to be absorbed into + * the state + * - size_t inlen: length of input in bytes + **************************************************/ +void mlk_shake128_absorb_once(mlk_shake128ctx *state, const uint8_t *input, + size_t inlen) +__contract__( + requires(inlen <= MLK_MAX_BUFFER_SIZE) + requires(memory_no_alias(state, sizeof(mlk_shake128ctx))) + requires(memory_no_alias(input, inlen)) + assigns(memory_slice(state, sizeof(mlk_shake128ctx))) +); + +#define mlk_shake128_squeezeblocks MLK_NAMESPACE(shake128_squeezeblocks) +/************************************************* + * Name: mlk_shake128_squeezeblocks + * + * Description: Squeeze step of SHAKE128 XOF. Squeezes full blocks of + * SHAKE128_RATE bytes each. Modifies the state. Can be called + * multiple times to keep squeezing, i.e., is incremental. + * + * Arguments: - uint8_t *output: pointer to output blocks + * - size_t nblocks: number of blocks to be squeezed (written + * to output) + * - mlk_shake128ctx *state: pointer to in/output Keccak state + **************************************************/ +void mlk_shake128_squeezeblocks(uint8_t *output, size_t nblocks, + mlk_shake128ctx *state) +__contract__( + requires(nblocks <= 8 /* somewhat arbitrary bound */) + requires(memory_no_alias(state, sizeof(mlk_shake128ctx))) + requires(memory_no_alias(output, nblocks * SHAKE128_RATE)) + assigns(memory_slice(output, nblocks * SHAKE128_RATE), memory_slice(state, sizeof(mlk_shake128ctx))) +); + +#define mlk_shake128_init MLK_NAMESPACE(shake128_init) +void mlk_shake128_init(mlk_shake128ctx *state); + +#define mlk_shake128_release MLK_NAMESPACE(shake128_release) +void mlk_shake128_release(mlk_shake128ctx *state); + +/* One-stop SHAKE256 call. Aliasing between input and + * output is not permitted */ +#define mlk_shake256 MLK_NAMESPACE(shake256) +/************************************************* + * Name: mlk_shake256 + * + * Description: SHAKE256 XOF with non-incremental API + * + * Arguments: - uint8_t *output: pointer to output + * - size_t outlen: requested output length in bytes + * - const uint8_t *input: pointer to input + * - size_t inlen: length of input in bytes + **************************************************/ +void mlk_shake256(uint8_t *output, size_t outlen, const uint8_t *input, + size_t inlen) +__contract__( + requires(inlen <= MLK_MAX_BUFFER_SIZE) + requires(outlen <= MLK_MAX_BUFFER_SIZE) + requires(memory_no_alias(input, inlen)) + requires(memory_no_alias(output, outlen)) + assigns(memory_slice(output, outlen)) +); + +/* One-stop SHA3_256 call. Aliasing between input and + * output is not permitted */ +#define SHA3_256_HASHBYTES 32 +#define mlk_sha3_256 MLK_NAMESPACE(sha3_256) +/************************************************* + * Name: mlk_sha3_256 + * + * Description: SHA3-256 with non-incremental API + * + * Arguments: - uint8_t *output: pointer to output + * - const uint8_t *input: pointer to input + * - size_t inlen: length of input in bytes + **************************************************/ +void mlk_sha3_256(uint8_t *output, const uint8_t *input, size_t inlen) +__contract__( + requires(inlen <= MLK_MAX_BUFFER_SIZE) + requires(memory_no_alias(input, inlen)) + requires(memory_no_alias(output, SHA3_256_HASHBYTES)) + assigns(memory_slice(output, SHA3_256_HASHBYTES)) +); + +/* One-stop SHA3_512 call. Aliasing between input and + * output is not permitted */ +#define SHA3_512_HASHBYTES 64 +#define mlk_sha3_512 MLK_NAMESPACE(sha3_512) +/************************************************* + * Name: mlk_sha3_512 + * + * Description: SHA3-512 with non-incremental API + * + * Arguments: - uint8_t *output: pointer to output + * - const uint8_t *input: pointer to input + * - size_t inlen: length of input in bytes + **************************************************/ +void mlk_sha3_512(uint8_t *output, const uint8_t *input, size_t inlen) +__contract__( + requires(inlen <= MLK_MAX_BUFFER_SIZE) + requires(memory_no_alias(input, inlen)) + requires(memory_no_alias(output, SHA3_512_HASHBYTES)) + assigns(memory_slice(output, SHA3_512_HASHBYTES)) +); + +#if !defined(MLK_CONFIG_USE_NATIVE_BACKEND_FIPS202) || \ + !defined(MLK_USE_FIPS202_X4_NATIVE) +/* If you provide your own FIPS-202 implementation where the x4- + * Keccak-f1600-x4 implementation falls back to 4-fold Keccak-f1600, + * set this to gain a small speedup. */ +#define FIPS202_X4_DEFAULT_IMPLEMENTATION +#endif /* !MLK_CONFIG_USE_NATIVE_BACKEND_FIPS202 || !MLK_USE_FIPS202_X4_NATIVE \ + */ + + +#endif /* !MLK_FIPS202_FIPS202_H */ diff --git a/mlkem_native/src/fips202/fips202x4.c b/mlkem_native/src/fips202/fips202x4.c new file mode 100644 index 0000000..95f8848 --- /dev/null +++ b/mlkem_native/src/fips202/fips202x4.c @@ -0,0 +1,208 @@ +/* + * Copyright (c) The mlkem-native project authors + * SPDX-License-Identifier: Apache-2.0 OR ISC OR MIT + */ + +/* References + * ========== + * + * - [FIPS203] + * FIPS 203 Module-Lattice-Based Key-Encapsulation Mechanism Standard + * National Institute of Standards and Technology + * https://csrc.nist.gov/pubs/fips/203/final + */ + +#include "../common.h" +#if !defined(MLK_CONFIG_MULTILEVEL_NO_SHARED) + +#include "../verify.h" +#include "fips202.h" +#include "fips202x4.h" +#include "keccakf1600.h" + +typedef mlk_shake128x4ctx mlk_shake256x4_ctx; + +static void mlk_keccak_absorb_once_x4(uint64_t *s, unsigned r, + const uint8_t *in0, const uint8_t *in1, + const uint8_t *in2, const uint8_t *in3, + size_t inlen, uint8_t p) +__contract__( + requires(inlen <= MLK_MAX_BUFFER_SIZE) + requires(memory_no_alias(s, sizeof(uint64_t) * MLK_KECCAK_LANES * MLK_KECCAK_WAY)) + requires(r <= sizeof(uint64_t) * MLK_KECCAK_LANES) + requires(memory_no_alias(in0, inlen)) + requires(memory_no_alias(in1, inlen)) + requires(memory_no_alias(in2, inlen)) + requires(memory_no_alias(in3, inlen)) + assigns(memory_slice(s, sizeof(uint64_t) * MLK_KECCAK_LANES * MLK_KECCAK_WAY))) +{ + while (inlen >= r) + __loop__( + assigns(inlen, in0, in1, in2, in3, memory_slice(s, sizeof(uint64_t) * MLK_KECCAK_LANES * MLK_KECCAK_WAY)) + invariant(inlen <= loop_entry(inlen)) + invariant(in0 == loop_entry(in0) + (loop_entry(inlen) - inlen)) + invariant(in1 == loop_entry(in1) + (loop_entry(inlen) - inlen)) + invariant(in2 == loop_entry(in2) + (loop_entry(inlen) - inlen)) + invariant(in3 == loop_entry(in3) + (loop_entry(inlen) - inlen))) + { + mlk_keccakf1600x4_xor_bytes(s, in0, in1, in2, in3, 0, r); + mlk_keccakf1600x4_permute(s); + + in0 += r; + in1 += r; + in2 += r; + in3 += r; + inlen -= r; + } + + /* At this point, inlen < r, so the truncations to unsigned are safe below. */ + + if (inlen > 0) + { + mlk_keccakf1600x4_xor_bytes(s, in0, in1, in2, in3, 0, (unsigned int)inlen); + } + + if (inlen == r - 1) + { + p |= 128; + mlk_keccakf1600x4_xor_bytes(s, &p, &p, &p, &p, (unsigned int)inlen, 1); + } + else + { + mlk_keccakf1600x4_xor_bytes(s, &p, &p, &p, &p, (unsigned int)inlen, 1); + p = 128; + mlk_keccakf1600x4_xor_bytes(s, &p, &p, &p, &p, r - 1, 1); + } +} + +static void mlk_keccak_squeezeblocks_x4(uint8_t *out0, uint8_t *out1, + uint8_t *out2, uint8_t *out3, + size_t nblocks, uint64_t *s, unsigned r) +__contract__( + requires(r <= sizeof(uint64_t) * MLK_KECCAK_LANES) + requires(r == SHAKE128_RATE || r == SHAKE256_RATE) + requires(nblocks <= (MLK_MAX_BUFFER_SIZE / SHAKE256_RATE)) + requires(memory_no_alias(s, sizeof(uint64_t) * MLK_KECCAK_LANES * MLK_KECCAK_WAY)) + requires(memory_no_alias(out0, nblocks * r)) + requires(memory_no_alias(out1, nblocks * r)) + requires(memory_no_alias(out2, nblocks * r)) + requires(memory_no_alias(out3, nblocks * r)) + assigns(memory_slice(s, sizeof(uint64_t) * MLK_KECCAK_LANES * MLK_KECCAK_WAY)) + assigns(memory_slice(out0, nblocks * r)) + assigns(memory_slice(out1, nblocks * r)) + assigns(memory_slice(out2, nblocks * r)) + assigns(memory_slice(out3, nblocks * r))) +{ + while (nblocks > 0) + __loop__( + assigns(out0, out1, out2, out3, nblocks, + memory_slice(s, sizeof(uint64_t) * MLK_KECCAK_LANES * MLK_KECCAK_WAY), + memory_slice(out0, nblocks * r), + memory_slice(out1, nblocks * r), + memory_slice(out2, nblocks * r), + memory_slice(out3, nblocks * r)) + invariant(nblocks <= loop_entry(nblocks) && + out0 == loop_entry(out0) + r * (loop_entry(nblocks) - nblocks) && + out1 == loop_entry(out1) + r * (loop_entry(nblocks) - nblocks) && + out2 == loop_entry(out2) + r * (loop_entry(nblocks) - nblocks) && + out3 == loop_entry(out3) + r * (loop_entry(nblocks) - nblocks))) + { + mlk_keccakf1600x4_permute(s); + mlk_keccakf1600x4_extract_bytes(s, out0, out1, out2, out3, 0, r); + + out0 += r; + out1 += r; + out2 += r; + out3 += r; + nblocks--; + } +} + +void mlk_shake128x4_absorb_once(mlk_shake128x4ctx *state, const uint8_t *in0, + const uint8_t *in1, const uint8_t *in2, + const uint8_t *in3, size_t inlen) +{ + mlk_memset(state, 0, sizeof(mlk_shake128x4ctx)); + mlk_keccak_absorb_once_x4(state->ctx, SHAKE128_RATE, in0, in1, in2, in3, + inlen, 0x1F); +} + +void mlk_shake128x4_squeezeblocks(uint8_t *out0, uint8_t *out1, uint8_t *out2, + uint8_t *out3, size_t nblocks, + mlk_shake128x4ctx *state) +{ + mlk_keccak_squeezeblocks_x4(out0, out1, out2, out3, nblocks, state->ctx, + SHAKE128_RATE); +} + +void mlk_shake128x4_init(mlk_shake128x4ctx *state) { (void)state; } +void mlk_shake128x4_release(mlk_shake128x4ctx *state) +{ + /* Specification: Partially implements + * @[FIPS203, Section 3.3, Destruction of intermediate values] */ + mlk_zeroize(state, sizeof(mlk_shake128x4ctx)); +} + +static void mlk_shake256x4_absorb_once(mlk_shake256x4_ctx *state, + const uint8_t *in0, const uint8_t *in1, + const uint8_t *in2, const uint8_t *in3, + size_t inlen) +{ + mlk_memset(state, 0, sizeof(mlk_shake128x4ctx)); + mlk_keccak_absorb_once_x4(state->ctx, SHAKE256_RATE, in0, in1, in2, in3, + inlen, 0x1F); +} + +static void mlk_shake256x4_squeezeblocks(uint8_t *out0, uint8_t *out1, + uint8_t *out2, uint8_t *out3, + size_t nblocks, + mlk_shake256x4_ctx *state) +{ + mlk_keccak_squeezeblocks_x4(out0, out1, out2, out3, nblocks, state->ctx, + SHAKE256_RATE); +} + +void mlk_shake256x4(uint8_t *out0, uint8_t *out1, uint8_t *out2, uint8_t *out3, + size_t outlen, uint8_t *in0, uint8_t *in1, uint8_t *in2, + uint8_t *in3, size_t inlen) +{ + mlk_shake256x4_ctx statex; + size_t nblocks = outlen / SHAKE256_RATE; + uint8_t tmp0[SHAKE256_RATE]; + uint8_t tmp1[SHAKE256_RATE]; + uint8_t tmp2[SHAKE256_RATE]; + uint8_t tmp3[SHAKE256_RATE]; + + mlk_shake256x4_absorb_once(&statex, in0, in1, in2, in3, inlen); + mlk_shake256x4_squeezeblocks(out0, out1, out2, out3, nblocks, &statex); + + out0 += nblocks * SHAKE256_RATE; + out1 += nblocks * SHAKE256_RATE; + out2 += nblocks * SHAKE256_RATE; + out3 += nblocks * SHAKE256_RATE; + + outlen -= nblocks * SHAKE256_RATE; + + if (outlen) + { + mlk_shake256x4_squeezeblocks(tmp0, tmp1, tmp2, tmp3, 1, &statex); + mlk_memcpy(out0, tmp0, outlen); + mlk_memcpy(out1, tmp1, outlen); + mlk_memcpy(out2, tmp2, outlen); + mlk_memcpy(out3, tmp3, outlen); + } + + /* Specification: Partially implements + * @[FIPS203, Section 3.3, Destruction of intermediate values] */ + mlk_zeroize(&statex, sizeof(statex)); + mlk_zeroize(tmp0, sizeof(tmp0)); + mlk_zeroize(tmp1, sizeof(tmp1)); + mlk_zeroize(tmp2, sizeof(tmp2)); + mlk_zeroize(tmp3, sizeof(tmp3)); +} + +#else /* !MLK_CONFIG_MULTILEVEL_NO_SHARED */ + +MLK_EMPTY_CU(fips202x4) + +#endif /* MLK_CONFIG_MULTILEVEL_NO_SHARED */ diff --git a/mlkem_native/src/fips202/fips202x4.h b/mlkem_native/src/fips202/fips202x4.h new file mode 100644 index 0000000..1f6a8be --- /dev/null +++ b/mlkem_native/src/fips202/fips202x4.h @@ -0,0 +1,80 @@ +/* + * Copyright (c) The mlkem-native project authors + * SPDX-License-Identifier: Apache-2.0 OR ISC OR MIT + */ +#ifndef MLK_FIPS202_FIPS202X4_H +#define MLK_FIPS202_FIPS202X4_H + + +#include "../cbmc.h" +#include "../common.h" + +#include "fips202.h" +#include "keccakf1600.h" + +/* Context for non-incremental API */ +typedef struct +{ + uint64_t ctx[MLK_KECCAK_LANES * MLK_KECCAK_WAY]; +} MLK_ALIGN mlk_shake128x4ctx; + +#define mlk_shake128x4_absorb_once MLK_NAMESPACE(shake128x4_absorb_once) +void mlk_shake128x4_absorb_once(mlk_shake128x4ctx *state, const uint8_t *in0, + const uint8_t *in1, const uint8_t *in2, + const uint8_t *in3, size_t inlen) +__contract__( + requires(inlen <= MLK_MAX_BUFFER_SIZE) + requires(memory_no_alias(state, sizeof(mlk_shake128x4ctx))) + requires(memory_no_alias(in0, inlen)) + requires(memory_no_alias(in1, inlen)) + requires(memory_no_alias(in2, inlen)) + requires(memory_no_alias(in3, inlen)) + assigns(memory_slice(state, sizeof(mlk_shake128x4ctx))) +); + +#define mlk_shake128x4_squeezeblocks MLK_NAMESPACE(shake128x4_squeezeblocks) +void mlk_shake128x4_squeezeblocks(uint8_t *out0, uint8_t *out1, uint8_t *out2, + uint8_t *out3, size_t nblocks, + mlk_shake128x4ctx *state) +__contract__( + requires(nblocks <= 8 /* somewhat arbitrary bound */) + requires(memory_no_alias(state, sizeof(mlk_shake128x4ctx))) + requires(memory_no_alias(out0, nblocks * SHAKE128_RATE)) + requires(memory_no_alias(out1, nblocks * SHAKE128_RATE)) + requires(memory_no_alias(out2, nblocks * SHAKE128_RATE)) + requires(memory_no_alias(out3, nblocks * SHAKE128_RATE)) + assigns(memory_slice(out0, nblocks * SHAKE128_RATE), + memory_slice(out1, nblocks * SHAKE128_RATE), + memory_slice(out2, nblocks * SHAKE128_RATE), + memory_slice(out3, nblocks * SHAKE128_RATE), + memory_slice(state, sizeof(mlk_shake128x4ctx))) +); + +#define mlk_shake128x4_init MLK_NAMESPACE(shake128x4_init) +void mlk_shake128x4_init(mlk_shake128x4ctx *state); + +#define mlk_shake128x4_release MLK_NAMESPACE(shake128x4_release) +void mlk_shake128x4_release(mlk_shake128x4ctx *state); + +#define mlk_shake256x4 MLK_NAMESPACE(shake256x4) +void mlk_shake256x4(uint8_t *out0, uint8_t *out1, uint8_t *out2, uint8_t *out3, + size_t outlen, uint8_t *in0, uint8_t *in1, uint8_t *in2, + uint8_t *in3, size_t inlen) +__contract__( + requires(inlen <= MLK_MAX_BUFFER_SIZE) + requires(outlen <= MLK_MAX_BUFFER_SIZE) + requires(memory_no_alias(in0, inlen)) + requires(memory_no_alias(in1, inlen)) + requires(memory_no_alias(in2, inlen)) + requires(memory_no_alias(in3, inlen)) + requires(memory_no_alias(out0, outlen)) + requires(memory_no_alias(out1, outlen)) + requires(memory_no_alias(out2, outlen)) + requires(memory_no_alias(out3, outlen)) + assigns(memory_slice(out0, outlen)) + assigns(memory_slice(out1, outlen)) + assigns(memory_slice(out2, outlen)) + assigns(memory_slice(out3, outlen)) +); + +#endif /* !MLK_FIPS202_FIPS202X4_H */ diff --git a/mlkem_native/src/fips202/keccakf1600.c b/mlkem_native/src/fips202/keccakf1600.c new file mode 100644 index 0000000..cf423e3 --- /dev/null +++ b/mlkem_native/src/fips202/keccakf1600.c @@ -0,0 +1,463 @@ +/* + * Copyright (c) The mlkem-native project authors + * SPDX-License-Identifier: Apache-2.0 OR ISC OR MIT + */ + +/* References + * ========== + * + * - [mupq] + * Common files for pqm4, pqm3, pqriscv + * Kannwischer, Petri, Rijneveld, Schwabe, Stoffelen + * https://github.com/mupq/mupq + * + * - [supercop] + * SUPERCOP benchmarking framework + * Daniel J. Bernstein + * http://bench.cr.yp.to/supercop.html + * + * - [tweetfips] + * 'tweetfips202' FIPS202 implementation + * Van Assche, Bernstein, Schwabe + * https://keccak.team/2015/tweetfips202.html + */ + +/* Based on the CC0 implementation from @[mupq] and the public domain + * implementation @[supercop, crypto_hash/keccakc512/simple/] + * by Ronny Van Keer, and the public domain @[tweetfips] implementation. */ + + +#include "keccakf1600.h" +#if !defined(MLK_CONFIG_MULTILEVEL_NO_SHARED) + +#define MLK_KECCAK_NROUNDS 24 +#define MLK_KECCAK_ROL(a, offset) ((a << offset) ^ (a >> (64 - offset))) + +void mlk_keccakf1600_extract_bytes(uint64_t *state, unsigned char *data, + unsigned offset, unsigned length) +{ + unsigned i; +#if defined(MLK_SYS_LITTLE_ENDIAN) + uint8_t *state_ptr = (uint8_t *)state + offset; + for (i = 0; i < length; i++) + __loop__(invariant(i <= length)) + { + data[i] = state_ptr[i]; + } +#else /* MLK_SYS_LITTLE_ENDIAN */ + /* Portable version */ + for (i = 0; i < length; i++) + __loop__(invariant(i <= length)) + { + data[i] = (state[(offset + i) >> 3] >> (8 * ((offset + i) & 0x07))) & 0xFF; + } +#endif /* !MLK_SYS_LITTLE_ENDIAN */ +} + +void mlk_keccakf1600_xor_bytes(uint64_t *state, const unsigned char *data, + unsigned offset, unsigned length) +{ + unsigned i; +#if defined(MLK_SYS_LITTLE_ENDIAN) + uint8_t *state_ptr = (uint8_t *)state + offset; + for (i = 0; i < length; i++) + __loop__(invariant(i <= length)) + { + state_ptr[i] ^= data[i]; + } +#else /* MLK_SYS_LITTLE_ENDIAN */ + /* Portable version */ + for (i = 0; i < length; i++) + __loop__(invariant(i <= length)) + { + state[(offset + i) >> 3] ^= (uint64_t)data[i] + << (8 * ((offset + i) & 0x07)); + } +#endif /* !MLK_SYS_LITTLE_ENDIAN */ +} + +static void mlk_keccakf1600x4_extract_bytes_c(uint64_t *state, + unsigned char *data0, + unsigned char *data1, + unsigned char *data2, + unsigned char *data3, + unsigned offset, unsigned length) +{ + mlk_keccakf1600_extract_bytes(state + MLK_KECCAK_LANES * 0, data0, offset, + length); + mlk_keccakf1600_extract_bytes(state + MLK_KECCAK_LANES * 1, data1, offset, + length); + mlk_keccakf1600_extract_bytes(state + MLK_KECCAK_LANES * 2, data2, offset, + length); + mlk_keccakf1600_extract_bytes(state + MLK_KECCAK_LANES * 3, data3, offset, + length); +} + +void mlk_keccakf1600x4_extract_bytes(uint64_t *state, unsigned char *data0, + unsigned char *data1, unsigned char *data2, + unsigned char *data3, unsigned offset, + unsigned length) +{ +#if defined(MLK_USE_FIPS202_X4_EXTRACT_BYTES_NATIVE) + if (mlk_keccakf1600_extract_bytes_x4_native(state, data0, data1, data2, data3, + offset, length) == + MLK_NATIVE_FUNC_SUCCESS) + { + return; + } +#endif /* MLK_USE_FIPS202_X4_EXTRACT_BYTES_NATIVE */ + mlk_keccakf1600x4_extract_bytes_c(state, data0, data1, data2, data3, offset, + length); +} + +static void mlk_keccakf1600x4_xor_bytes_c(uint64_t *state, + const unsigned char *data0, + const unsigned char *data1, + const unsigned char *data2, + const unsigned char *data3, + unsigned offset, unsigned length) +{ + mlk_keccakf1600_xor_bytes(state + MLK_KECCAK_LANES * 0, data0, offset, + length); + mlk_keccakf1600_xor_bytes(state + MLK_KECCAK_LANES * 1, data1, offset, + length); + mlk_keccakf1600_xor_bytes(state + MLK_KECCAK_LANES * 2, data2, offset, + length); + mlk_keccakf1600_xor_bytes(state + MLK_KECCAK_LANES * 3, data3, offset, + length); +} + +void mlk_keccakf1600x4_xor_bytes(uint64_t *state, const unsigned char *data0, + const unsigned char *data1, + const unsigned char *data2, + const unsigned char *data3, unsigned offset, + unsigned length) +{ +#if defined(MLK_USE_FIPS202_X4_XOR_BYTES_NATIVE) + if (mlk_keccakf1600_xor_bytes_x4_native(state, data0, data1, data2, data3, + offset, + length) == MLK_NATIVE_FUNC_SUCCESS) + { + return; + } +#endif /* MLK_USE_FIPS202_X4_XOR_BYTES_NATIVE */ + mlk_keccakf1600x4_xor_bytes_c(state, data0, data1, data2, data3, offset, + length); +} + +void mlk_keccakf1600x4_permute(uint64_t *state) +{ +#if defined(MLK_USE_FIPS202_X4_NATIVE) + if (mlk_keccak_f1600_x4_native(state) == MLK_NATIVE_FUNC_SUCCESS) + { + return; + } +#endif /* MLK_USE_FIPS202_X4_NATIVE */ + mlk_keccakf1600_permute(state + MLK_KECCAK_LANES * 0); + mlk_keccakf1600_permute(state + MLK_KECCAK_LANES * 1); + mlk_keccakf1600_permute(state + MLK_KECCAK_LANES * 2); + mlk_keccakf1600_permute(state + MLK_KECCAK_LANES * 3); +} + +static const uint64_t mlk_KeccakF_RoundConstants[MLK_KECCAK_NROUNDS] = { + (uint64_t)0x0000000000000001ULL, (uint64_t)0x0000000000008082ULL, + (uint64_t)0x800000000000808aULL, (uint64_t)0x8000000080008000ULL, + (uint64_t)0x000000000000808bULL, (uint64_t)0x0000000080000001ULL, + (uint64_t)0x8000000080008081ULL, (uint64_t)0x8000000000008009ULL, + (uint64_t)0x000000000000008aULL, (uint64_t)0x0000000000000088ULL, + (uint64_t)0x0000000080008009ULL, (uint64_t)0x000000008000000aULL, + (uint64_t)0x000000008000808bULL, (uint64_t)0x800000000000008bULL, + (uint64_t)0x8000000000008089ULL, (uint64_t)0x8000000000008003ULL, + (uint64_t)0x8000000000008002ULL, (uint64_t)0x8000000000000080ULL, + (uint64_t)0x000000000000800aULL, (uint64_t)0x800000008000000aULL, + (uint64_t)0x8000000080008081ULL, (uint64_t)0x8000000000008080ULL, + (uint64_t)0x0000000080000001ULL, (uint64_t)0x8000000080008008ULL}; + +MLK_STATIC_TESTABLE +void mlk_keccakf1600_permute_c(uint64_t *state) +{ + unsigned round; + + uint64_t Aba, Abe, Abi, Abo, Abu; + uint64_t Aga, Age, Agi, Ago, Agu; + uint64_t Aka, Ake, Aki, Ako, Aku; + uint64_t Ama, Ame, Ami, Amo, Amu; + uint64_t Asa, Ase, Asi, Aso, Asu; + uint64_t BCa, BCe, BCi, BCo, BCu; + uint64_t Da, De, Di, Do, Du; + uint64_t Eba, Ebe, Ebi, Ebo, Ebu; + uint64_t Ega, Ege, Egi, Ego, Egu; + uint64_t Eka, Eke, Eki, Eko, Eku; + uint64_t Ema, Eme, Emi, Emo, Emu; + uint64_t Esa, Ese, Esi, Eso, Esu; + + /* copyFromState(A, state) */ + Aba = state[0]; + Abe = state[1]; + Abi = state[2]; + Abo = state[3]; + Abu = state[4]; + Aga = state[5]; + Age = state[6]; + Agi = state[7]; + Ago = state[8]; + Agu = state[9]; + Aka = state[10]; + Ake = state[11]; + Aki = state[12]; + Ako = state[13]; + Aku = state[14]; + Ama = state[15]; + Ame = state[16]; + Ami = state[17]; + Amo = state[18]; + Amu = state[19]; + Asa = state[20]; + Ase = state[21]; + Asi = state[22]; + Aso = state[23]; + Asu = state[24]; + + for (round = 0; round < MLK_KECCAK_NROUNDS; round += 2) + __loop__(invariant(round <= MLK_KECCAK_NROUNDS && round % 2 == 0)) + { + /* prepareTheta */ + BCa = Aba ^ Aga ^ Aka ^ Ama ^ Asa; + BCe = Abe ^ Age ^ Ake ^ Ame ^ Ase; + BCi = Abi ^ Agi ^ Aki ^ Ami ^ Asi; + BCo = Abo ^ Ago ^ Ako ^ Amo ^ Aso; + BCu = Abu ^ Agu ^ Aku ^ Amu ^ Asu; + + /* thetaRhoPiChiIotaPrepareTheta(round, A, E) */ + Da = BCu ^ MLK_KECCAK_ROL(BCe, 1); + De = BCa ^ MLK_KECCAK_ROL(BCi, 1); + Di = BCe ^ MLK_KECCAK_ROL(BCo, 1); + Do = BCi ^ MLK_KECCAK_ROL(BCu, 1); + Du = BCo ^ MLK_KECCAK_ROL(BCa, 1); + + Aba ^= Da; + BCa = Aba; + Age ^= De; + BCe = MLK_KECCAK_ROL(Age, 44); + Aki ^= Di; + BCi = MLK_KECCAK_ROL(Aki, 43); + Amo ^= Do; + BCo = MLK_KECCAK_ROL(Amo, 21); + Asu ^= Du; + BCu = MLK_KECCAK_ROL(Asu, 14); + Eba = BCa ^ ((~BCe) & BCi); + Eba ^= (uint64_t)mlk_KeccakF_RoundConstants[round]; + Ebe = BCe ^ ((~BCi) & BCo); + Ebi = BCi ^ ((~BCo) & BCu); + Ebo = BCo ^ ((~BCu) & BCa); + Ebu = BCu ^ ((~BCa) & BCe); + + Abo ^= Do; + BCa = MLK_KECCAK_ROL(Abo, 28); + Agu ^= Du; + BCe = MLK_KECCAK_ROL(Agu, 20); + Aka ^= Da; + BCi = MLK_KECCAK_ROL(Aka, 3); + Ame ^= De; + BCo = MLK_KECCAK_ROL(Ame, 45); + Asi ^= Di; + BCu = MLK_KECCAK_ROL(Asi, 61); + Ega = BCa ^ ((~BCe) & BCi); + Ege = BCe ^ ((~BCi) & BCo); + Egi = BCi ^ ((~BCo) & BCu); + Ego = BCo ^ ((~BCu) & BCa); + Egu = BCu ^ ((~BCa) & BCe); + + Abe ^= De; + BCa = MLK_KECCAK_ROL(Abe, 1); + Agi ^= Di; + BCe = MLK_KECCAK_ROL(Agi, 6); + Ako ^= Do; + BCi = MLK_KECCAK_ROL(Ako, 25); + Amu ^= Du; + BCo = MLK_KECCAK_ROL(Amu, 8); + Asa ^= Da; + BCu = MLK_KECCAK_ROL(Asa, 18); + Eka = BCa ^ ((~BCe) & BCi); + Eke = BCe ^ ((~BCi) & BCo); + Eki = BCi ^ ((~BCo) & BCu); + Eko = BCo ^ ((~BCu) & BCa); + Eku = BCu ^ ((~BCa) & BCe); + + Abu ^= Du; + BCa = MLK_KECCAK_ROL(Abu, 27); + Aga ^= Da; + BCe = MLK_KECCAK_ROL(Aga, 36); + Ake ^= De; + BCi = MLK_KECCAK_ROL(Ake, 10); + Ami ^= Di; + BCo = MLK_KECCAK_ROL(Ami, 15); + Aso ^= Do; + BCu = MLK_KECCAK_ROL(Aso, 56); + Ema = BCa ^ ((~BCe) & BCi); + Eme = BCe ^ ((~BCi) & BCo); + Emi = BCi ^ ((~BCo) & BCu); + Emo = BCo ^ ((~BCu) & BCa); + Emu = BCu ^ ((~BCa) & BCe); + + Abi ^= Di; + BCa = MLK_KECCAK_ROL(Abi, 62); + Ago ^= Do; + BCe = MLK_KECCAK_ROL(Ago, 55); + Aku ^= Du; + BCi = MLK_KECCAK_ROL(Aku, 39); + Ama ^= Da; + BCo = MLK_KECCAK_ROL(Ama, 41); + Ase ^= De; + BCu = MLK_KECCAK_ROL(Ase, 2); + Esa = BCa ^ ((~BCe) & BCi); + Ese = BCe ^ ((~BCi) & BCo); + Esi = BCi ^ ((~BCo) & BCu); + Eso = BCo ^ ((~BCu) & BCa); + Esu = BCu ^ ((~BCa) & BCe); + + /* prepareTheta */ + BCa = Eba ^ Ega ^ Eka ^ Ema ^ Esa; + BCe = Ebe ^ Ege ^ Eke ^ Eme ^ Ese; + BCi = Ebi ^ Egi ^ Eki ^ Emi ^ Esi; + BCo = Ebo ^ Ego ^ Eko ^ Emo ^ Eso; + BCu = Ebu ^ Egu ^ Eku ^ Emu ^ Esu; + + /* thetaRhoPiChiIotaPrepareTheta(round+1, E, A) */ + Da = BCu ^ MLK_KECCAK_ROL(BCe, 1); + De = BCa ^ MLK_KECCAK_ROL(BCi, 1); + Di = BCe ^ MLK_KECCAK_ROL(BCo, 1); + Do = BCi ^ MLK_KECCAK_ROL(BCu, 1); + Du = BCo ^ MLK_KECCAK_ROL(BCa, 1); + + Eba ^= Da; + BCa = Eba; + Ege ^= De; + BCe = MLK_KECCAK_ROL(Ege, 44); + Eki ^= Di; + BCi = MLK_KECCAK_ROL(Eki, 43); + Emo ^= Do; + BCo = MLK_KECCAK_ROL(Emo, 21); + Esu ^= Du; + BCu = MLK_KECCAK_ROL(Esu, 14); + Aba = BCa ^ ((~BCe) & BCi); + Aba ^= (uint64_t)mlk_KeccakF_RoundConstants[round + 1]; + Abe = BCe ^ ((~BCi) & BCo); + Abi = BCi ^ ((~BCo) & BCu); + Abo = BCo ^ ((~BCu) & BCa); + Abu = BCu ^ ((~BCa) & BCe); + + Ebo ^= Do; + BCa = MLK_KECCAK_ROL(Ebo, 28); + Egu ^= Du; + BCe = MLK_KECCAK_ROL(Egu, 20); + Eka ^= Da; + BCi = MLK_KECCAK_ROL(Eka, 3); + Eme ^= De; + BCo = MLK_KECCAK_ROL(Eme, 45); + Esi ^= Di; + BCu = MLK_KECCAK_ROL(Esi, 61); + Aga = BCa ^ ((~BCe) & BCi); + Age = BCe ^ ((~BCi) & BCo); + Agi = BCi ^ ((~BCo) & BCu); + Ago = BCo ^ ((~BCu) & BCa); + Agu = BCu ^ ((~BCa) & BCe); + + Ebe ^= De; + BCa = MLK_KECCAK_ROL(Ebe, 1); + Egi ^= Di; + BCe = MLK_KECCAK_ROL(Egi, 6); + Eko ^= Do; + BCi = MLK_KECCAK_ROL(Eko, 25); + Emu ^= Du; + BCo = MLK_KECCAK_ROL(Emu, 8); + Esa ^= Da; + BCu = MLK_KECCAK_ROL(Esa, 18); + Aka = BCa ^ ((~BCe) & BCi); + Ake = BCe ^ ((~BCi) & BCo); + Aki = BCi ^ ((~BCo) & BCu); + Ako = BCo ^ ((~BCu) & BCa); + Aku = BCu ^ ((~BCa) & BCe); + + Ebu ^= Du; + BCa = MLK_KECCAK_ROL(Ebu, 27); + Ega ^= Da; + BCe = MLK_KECCAK_ROL(Ega, 36); + Eke ^= De; + BCi = MLK_KECCAK_ROL(Eke, 10); + Emi ^= Di; + BCo = MLK_KECCAK_ROL(Emi, 15); + Eso ^= Do; + BCu = MLK_KECCAK_ROL(Eso, 56); + Ama = BCa ^ ((~BCe) & BCi); + Ame = BCe ^ ((~BCi) & BCo); + Ami = BCi ^ ((~BCo) & BCu); + Amo = BCo ^ ((~BCu) & BCa); + Amu = BCu ^ ((~BCa) & BCe); + + Ebi ^= Di; + BCa = MLK_KECCAK_ROL(Ebi, 62); + Ego ^= Do; + BCe = MLK_KECCAK_ROL(Ego, 55); + Eku ^= Du; + BCi = MLK_KECCAK_ROL(Eku, 39); + Ema ^= Da; + BCo = MLK_KECCAK_ROL(Ema, 41); + Ese ^= De; + BCu = MLK_KECCAK_ROL(Ese, 2); + Asa = BCa ^ ((~BCe) & BCi); + Ase = BCe ^ ((~BCi) & BCo); + Asi = BCi ^ ((~BCo) & BCu); + Aso = BCo ^ ((~BCu) & BCa); + Asu = BCu ^ ((~BCa) & BCe); + } + + /* copyToState(state, A) */ + state[0] = Aba; + state[1] = Abe; + state[2] = Abi; + state[3] = Abo; + state[4] = Abu; + state[5] = Aga; + state[6] = Age; + state[7] = Agi; + state[8] = Ago; + state[9] = Agu; + state[10] = Aka; + state[11] = Ake; + state[12] = Aki; + state[13] = Ako; + state[14] = Aku; + state[15] = Ama; + state[16] = Ame; + state[17] = Ami; + state[18] = Amo; + state[19] = Amu; + state[20] = Asa; + state[21] = Ase; + state[22] = Asi; + state[23] = Aso; + state[24] = Asu; +} + +void mlk_keccakf1600_permute(uint64_t *state) +{ +#if defined(MLK_USE_FIPS202_X1_NATIVE) + if (mlk_keccak_f1600_x1_native(state) == MLK_NATIVE_FUNC_SUCCESS) + { + return; + } +#endif /* MLK_USE_FIPS202_X1_NATIVE */ + mlk_keccakf1600_permute_c(state); +} + +#else /* !MLK_CONFIG_MULTILEVEL_NO_SHARED */ + +MLK_EMPTY_CU(keccakf1600) + +#endif /* MLK_CONFIG_MULTILEVEL_NO_SHARED */ + +/* To facilitate single-compilation-unit (SCU) builds, undefine all macros. + * Don't modify by hand -- this is auto-generated by scripts/autogen. */ +#undef MLK_KECCAK_NROUNDS +#undef MLK_KECCAK_ROL diff --git a/mlkem_native/src/fips202/keccakf1600.h b/mlkem_native/src/fips202/keccakf1600.h new file mode 100644 index 0000000..c26c36a --- /dev/null +++ b/mlkem_native/src/fips202/keccakf1600.h @@ -0,0 +1,98 @@ +/* + * Copyright (c) The mlkem-native project authors + * SPDX-License-Identifier: Apache-2.0 OR ISC OR MIT + */ +#ifndef MLK_FIPS202_KECCAKF1600_H +#define MLK_FIPS202_KECCAKF1600_H +#include "../cbmc.h" +#include "../common.h" + +#define MLK_KECCAK_LANES 25 +#define MLK_KECCAK_WAY 4 + +/* + * WARNING: + * The contents of this structure, including the placement + * and interleaving of Keccak lanes, are IMPLEMENTATION-DEFINED. + * The struct is only exposed here to allow its construction on the stack. + */ + +#define mlk_keccakf1600_extract_bytes MLK_NAMESPACE(keccakf1600_extract_bytes) +void mlk_keccakf1600_extract_bytes(uint64_t *state, unsigned char *data, + unsigned offset, unsigned length) +__contract__( + requires(0 <= offset && offset <= MLK_KECCAK_LANES * sizeof(uint64_t) && + 0 <= length && length <= MLK_KECCAK_LANES * sizeof(uint64_t) - offset) + requires(memory_no_alias(state, sizeof(uint64_t) * MLK_KECCAK_LANES)) + requires(memory_no_alias(data, length)) + assigns(memory_slice(data, length)) +); + +#define mlk_keccakf1600_xor_bytes MLK_NAMESPACE(keccakf1600_xor_bytes) +void mlk_keccakf1600_xor_bytes(uint64_t *state, const unsigned char *data, + unsigned offset, unsigned length) +__contract__( + requires(0 <= offset && offset <= MLK_KECCAK_LANES * sizeof(uint64_t) && + 0 <= length && length <= MLK_KECCAK_LANES * sizeof(uint64_t) - offset) + requires(memory_no_alias(state, sizeof(uint64_t) * MLK_KECCAK_LANES)) + requires(memory_no_alias(data, length)) + assigns(memory_slice(state, sizeof(uint64_t) * MLK_KECCAK_LANES)) +); + +#define mlk_keccakf1600x4_extract_bytes \ + MLK_NAMESPACE(keccakf1600x4_extract_bytes) +void mlk_keccakf1600x4_extract_bytes(uint64_t *state, unsigned char *data0, + unsigned char *data1, unsigned char *data2, + unsigned char *data3, unsigned offset, + unsigned length) +__contract__( + requires(0 <= offset && offset <= MLK_KECCAK_LANES * sizeof(uint64_t) && + 0 <= length && length <= MLK_KECCAK_LANES * sizeof(uint64_t) - offset) + requires(memory_no_alias(state, sizeof(uint64_t) * MLK_KECCAK_LANES * MLK_KECCAK_WAY)) + requires(memory_no_alias(data0, length)) + requires(memory_no_alias(data1, length)) + requires(memory_no_alias(data2, length)) + requires(memory_no_alias(data3, length)) + assigns(memory_slice(data0, length)) + assigns(memory_slice(data1, length)) + assigns(memory_slice(data2, length)) + assigns(memory_slice(data3, length)) +); + +#define mlk_keccakf1600x4_xor_bytes MLK_NAMESPACE(keccakf1600x4_xor_bytes) +void mlk_keccakf1600x4_xor_bytes(uint64_t *state, const unsigned char *data0, + const unsigned char *data1, + const unsigned char *data2, + const unsigned char *data3, unsigned offset, + unsigned length) +__contract__( + requires(0 <= offset && offset <= MLK_KECCAK_LANES * sizeof(uint64_t) && + 0 <= length && length <= MLK_KECCAK_LANES * sizeof(uint64_t) - offset) + requires(memory_no_alias(state, sizeof(uint64_t) * MLK_KECCAK_LANES * MLK_KECCAK_WAY)) + requires(memory_no_alias(data0, length)) + /* Case 1: all input buffers are distinct; Case 2: All input buffers are the same */ + requires((data0 == data1 && + data0 == data2 && + data0 == data3) || + (memory_no_alias(data1, length) && + memory_no_alias(data2, length) && + memory_no_alias(data3, length))) + assigns(memory_slice(state, sizeof(uint64_t) * MLK_KECCAK_LANES * MLK_KECCAK_WAY)) +); + + +#define mlk_keccakf1600x4_permute MLK_NAMESPACE(keccakf1600x4_permute) +void mlk_keccakf1600x4_permute(uint64_t *state) +__contract__( + requires(memory_no_alias(state, sizeof(uint64_t) * MLK_KECCAK_LANES * MLK_KECCAK_WAY)) + assigns(memory_slice(state, sizeof(uint64_t) * MLK_KECCAK_LANES * MLK_KECCAK_WAY)) +); + +#define mlk_keccakf1600_permute MLK_NAMESPACE(keccakf1600_permute) +void mlk_keccakf1600_permute(uint64_t *state) +__contract__( + requires(memory_no_alias(state, sizeof(uint64_t) * MLK_KECCAK_LANES)) + assigns(memory_slice(state, sizeof(uint64_t) * MLK_KECCAK_LANES)) +); + +#endif /* !MLK_FIPS202_KECCAKF1600_H */ diff --git a/mlkem_native/src/indcpa.c b/mlkem_native/src/indcpa.c new file mode 100644 index 0000000..d3dc364 --- /dev/null +++ b/mlkem_native/src/indcpa.c @@ -0,0 +1,652 @@ +/* + * Copyright (c) The mlkem-native project authors + * SPDX-License-Identifier: Apache-2.0 OR ISC OR MIT + */ + +/* References + * ========== + * + * - [FIPS203] + * FIPS 203 Module-Lattice-Based Key-Encapsulation Mechanism Standard + * National Institute of Standards and Technology + * https://csrc.nist.gov/pubs/fips/203/final + * + * - [REF] + * CRYSTALS-Kyber C reference implementation + * Bos, Ducas, Kiltz, Lepoint, Lyubashevsky, Schanck, Schwabe, Seiler, StehlΓ© + * https://github.com/pq-crystals/kyber/tree/main/ref + */ + +#include "indcpa.h" + +#include "debug.h" +#include "randombytes.h" +#include "sampling.h" +#include "symmetric.h" + +/* Parameter set namespacing + * This is to facilitate building multiple instances + * of mlkem-native (e.g. with varying parameter sets) + * within a single compilation unit. */ +#define mlk_pack_pk MLK_ADD_PARAM_SET(mlk_pack_pk) +#define mlk_unpack_pk MLK_ADD_PARAM_SET(mlk_unpack_pk) +#define mlk_pack_sk MLK_ADD_PARAM_SET(mlk_pack_sk) +#define mlk_unpack_sk MLK_ADD_PARAM_SET(mlk_unpack_sk) +#define mlk_pack_ciphertext MLK_ADD_PARAM_SET(mlk_pack_ciphertext) +#define mlk_unpack_ciphertext MLK_ADD_PARAM_SET(mlk_unpack_ciphertext) +#define mlk_matvec_mul MLK_ADD_PARAM_SET(mlk_matvec_mul) +#define mlk_polyvec_permute_bitrev_to_custom \ + MLK_ADD_PARAM_SET(mlk_polyvec_permute_bitrev_to_custom) +#define mlk_polymat_permute_bitrev_to_custom \ + MLK_ADD_PARAM_SET(mlk_polymat_permute_bitrev_to_custom) +#define mlk_keypair_getnoise MLK_ADD_PARAM_SET(mlk_keypair_getnoise) +/* End of parameter set namespacing */ + +/************************************************* + * Name: mlk_pack_pk + * + * Description: Serialize the public key as concatenation of the + * serialized vector of polynomials pk + * and the public seed used to generate the matrix A. + * + * Arguments: uint8_t *r: pointer to the output serialized public key + * mlk_polyvec pk: pointer to the input public-key mlk_polyvec. + * Must have coefficients within [0,..,q-1]. + * const uint8_t *seed: pointer to the input public seed + * + * Specification: + * Implements @[FIPS203, Algorithm 13 (K-PKE.KeyGen), L19] + * + **************************************************/ +static void mlk_pack_pk(uint8_t r[MLKEM_INDCPA_PUBLICKEYBYTES], + const mlk_polyvec *pk, + const uint8_t seed[MLKEM_SYMBYTES]) +{ + mlk_assert_bound_2d(pk->vec, MLKEM_K, MLKEM_N, 0, MLKEM_Q); + mlk_polyvec_tobytes(r, pk); + mlk_memcpy(r + MLKEM_POLYVECBYTES, seed, MLKEM_SYMBYTES); +} + +/************************************************* + * Name: mlk_unpack_pk + * + * Description: De-serialize public key from a byte array; + * approximate inverse of mlk_pack_pk + * + * Arguments: - mlk_polyvec pk: pointer to output public-key polynomial + * vector Coefficients will be normalized to [0,..,q-1]. + * - uint8_t *seed: pointer to output seed to generate matrix A + * - const uint8_t *packedpk: pointer to input serialized public + * key. + * + * Specification: + * Implements @[FIPS203, Algorithm 14 (K-PKE.Encrypt), L2-3] + * + **************************************************/ +static void mlk_unpack_pk(mlk_polyvec *pk, uint8_t seed[MLKEM_SYMBYTES], + const uint8_t packedpk[MLKEM_INDCPA_PUBLICKEYBYTES]) +{ + mlk_polyvec_frombytes(pk, packedpk); + mlk_memcpy(seed, packedpk + MLKEM_POLYVECBYTES, MLKEM_SYMBYTES); + + /* NOTE: If a modulus check was conducted on the PK, we know at this + * point that the coefficients of `pk` are unsigned canonical. The + * specifications and proofs, however, do _not_ assume this, and instead + * work with the easily provable bound by MLKEM_UINT12_LIMIT. */ +} + +/************************************************* + * Name: mlk_pack_sk + * + * Description: Serialize the secret key + * + * Arguments: - uint8_t *r: pointer to output serialized secret key + * - mlk_polyvec sk: pointer to input vector of polynomials + * (secret key) + * + * Specification: + * Implements @[FIPS203, Algorithm 13 (K-PKE.KeyGen), L20] + * + **************************************************/ +static void mlk_pack_sk(uint8_t r[MLKEM_INDCPA_SECRETKEYBYTES], + const mlk_polyvec *sk) +{ + mlk_assert_bound_2d(sk->vec, MLKEM_K, MLKEM_N, 0, MLKEM_Q); + mlk_polyvec_tobytes(r, sk); +} + +/************************************************* + * Name: mlk_unpack_sk + * + * Description: De-serialize the secret key; inverse of mlk_pack_sk + * + * Arguments: - mlk_polyvec sk: pointer to output vector of polynomials + * (secret key) + * - const uint8_t *packedsk: pointer to input serialized secret + * key + * + * Specification: + * Implements @[FIPS203, Algorithm 15 (K-PKE.Decrypt), L5] + * + **************************************************/ +static void mlk_unpack_sk(mlk_polyvec *sk, + const uint8_t packedsk[MLKEM_INDCPA_SECRETKEYBYTES]) +{ + mlk_polyvec_frombytes(sk, packedsk); +} + +/************************************************* + * Name: mlk_pack_ciphertext + * + * Description: Serialize the ciphertext as concatenation of the + * compressed and serialized vector of polynomials b + * and the compressed and serialized polynomial v + * + * Arguments: uint8_t *r: pointer to the output serialized ciphertext + * mlk_poly *pk: pointer to the input vector of polynomials b + * mlk_poly *v: pointer to the input polynomial v + * + * Specification: + * Implements @[FIPS203, Algorithm 14 (K-PKE.Encrypt), L22-23] + * + **************************************************/ +static void mlk_pack_ciphertext(uint8_t r[MLKEM_INDCPA_BYTES], + const mlk_polyvec *b, mlk_poly *v) +{ + mlk_polyvec_compress_du(r, b); + mlk_poly_compress_dv(r + MLKEM_POLYVECCOMPRESSEDBYTES_DU, v); +} + +/************************************************* + * Name: mlk_unpack_ciphertext + * + * Description: De-serialize and decompress ciphertext from a byte array; + * approximate inverse of mlk_pack_ciphertext + * + * Arguments: - mlk_polyvec b: pointer to the output vector of polynomials b + * - mlk_poly *v: pointer to the output polynomial v + * - const uint8_t *c: pointer to the input serialized ciphertext + * + * Specification: + * Implements @[FIPS203, Algorithm 15 (K-PKE.Decrypt), L1-4] + * + **************************************************/ +static void mlk_unpack_ciphertext(mlk_polyvec *b, mlk_poly *v, + const uint8_t c[MLKEM_INDCPA_BYTES]) +{ + mlk_polyvec_decompress_du(b, c); + mlk_poly_decompress_dv(v, c + MLKEM_POLYVECCOMPRESSEDBYTES_DU); +} + +/* Helper function to ensure that the polynomial entries in the output + * of gen_matrix use the standard (bitreversed) ordering of coefficients. + * No-op unless a native backend with a custom ordering is used. + * + * We don't inline this into gen_matrix to avoid having to split the CBMC + * proof for gen_matrix based on MLK_USE_NATIVE_NTT_CUSTOM_ORDER. */ +static void mlk_polyvec_permute_bitrev_to_custom(mlk_polyvec *v) +__contract__( + /* We don't specify that this should be a permutation, but only + * that it does not change the bound established at the end of mlk_gen_matrix. */ + requires(memory_no_alias(v, sizeof(mlk_polyvec))) + requires(forall(x, 0, MLKEM_K, + array_bound(v->vec[x].coeffs, 0, MLKEM_N, 0, MLKEM_Q))) + assigns(memory_slice(v, sizeof(mlk_polyvec))) + ensures(forall(x, 0, MLKEM_K, + array_bound(v->vec[x].coeffs, 0, MLKEM_N, 0, MLKEM_Q)))) +{ +#if defined(MLK_USE_NATIVE_NTT_CUSTOM_ORDER) + unsigned i; + for (i = 0; i < MLKEM_K; i++) + __loop__( + assigns(i, memory_slice(v, sizeof(mlk_polyvec))) + invariant(i <= MLKEM_K) + invariant(forall(x, 0, MLKEM_K, + array_bound(v->vec[x].coeffs, 0, MLKEM_N, 0, MLKEM_Q)))) + { + mlk_poly_permute_bitrev_to_custom(v->vec[i].coeffs); + } +#else /* MLK_USE_NATIVE_NTT_CUSTOM_ORDER */ + /* Nothing to do */ + (void)v; +#endif /* !MLK_USE_NATIVE_NTT_CUSTOM_ORDER */ +} + +static void mlk_polymat_permute_bitrev_to_custom(mlk_polymat *a) +__contract__( + /* We don't specify that this should be a permutation, but only + * that it does not change the bound established at the end of mlk_gen_matrix. */ + requires(memory_no_alias(a, sizeof(mlk_polymat))) + requires(forall(x, 0, MLKEM_K, forall(y, 0, MLKEM_K, + array_bound(a->vec[x].vec[y].coeffs, 0, MLKEM_N, 0, MLKEM_Q)))) + assigns(memory_slice(a, sizeof(mlk_polymat))) + ensures(forall(x, 0, MLKEM_K, forall(y, 0, MLKEM_K, + array_bound(a->vec[x].vec[y].coeffs, 0, MLKEM_N, 0, MLKEM_Q))))) +{ + unsigned i; + for (i = 0; i < MLKEM_K; i++) + __loop__( + assigns(i, memory_slice(a, sizeof(mlk_polymat))) + invariant(i <= MLKEM_K) + invariant(forall(x, 0, MLKEM_K, forall(y, 0, MLKEM_K, + array_bound(a->vec[x].vec[y].coeffs, 0, MLKEM_N, 0, MLKEM_Q))))) + { + mlk_polyvec_permute_bitrev_to_custom(&a->vec[i]); + } +} + +/* Reference: `gen_matrix()` in the reference implementation @[REF]. + * - We use a special subroutine to generate 4 polynomials + * at a time, to be able to leverage batched Keccak-f1600 + * implementations. The reference implementation generates + * one matrix entry a time. + * + * Not static for benchmarking */ +MLK_INTERNAL_API +void mlk_gen_matrix(mlk_polymat *a, const uint8_t seed[MLKEM_SYMBYTES], + int transposed) +{ + unsigned i, j; + MLK_ALIGN uint8_t seed_ext[4][MLK_ALIGN_UP(MLKEM_SYMBYTES + 2)]; + + for (j = 0; j < 4; j++) + { + mlk_memcpy(seed_ext[j], seed, MLKEM_SYMBYTES); + } + +#if !defined(MLK_CONFIG_SERIAL_FIPS202_ONLY) + /* Sample 4 matrix entries a time. */ + for (i = 0; i < (MLKEM_K * MLKEM_K / 4) * 4; i += 4) + { + for (j = 0; j < 4; j++) + { + uint8_t x, y; + /* MLKEM_K <= 4, so the values fit in uint8_t. */ + x = (uint8_t)((i + j) / MLKEM_K); + y = (uint8_t)((i + j) % MLKEM_K); + if (transposed) + { + seed_ext[j][MLKEM_SYMBYTES + 0] = x; + seed_ext[j][MLKEM_SYMBYTES + 1] = y; + } + else + { + seed_ext[j][MLKEM_SYMBYTES + 0] = y; + seed_ext[j][MLKEM_SYMBYTES + 1] = x; + } + } + + mlk_poly_rej_uniform_x4(&a->vec[i / MLKEM_K].vec[i % MLKEM_K], + &a->vec[(i + 1) / MLKEM_K].vec[(i + 1) % MLKEM_K], + &a->vec[(i + 2) / MLKEM_K].vec[(i + 2) % MLKEM_K], + &a->vec[(i + 3) / MLKEM_K].vec[(i + 3) % MLKEM_K], + seed_ext); + } +#else /* !MLK_CONFIG_SERIAL_FIPS202_ONLY */ + /* When using serial FIPS202, sample all entries individually. */ + i = 0; +#endif /* MLK_CONFIG_SERIAL_FIPS202_ONLY */ + + /* For MLKEM_K == 3, sample the last entry individually. + * When MLK_CONFIG_SERIAL_FIPS202_ONLY is set, sample all entries + * individually. */ + for (; i < MLKEM_K * MLKEM_K; i++) + { + uint8_t x, y; + /* MLKEM_K <= 4, so the values fit in uint8_t. */ + x = (uint8_t)(i / MLKEM_K); + y = (uint8_t)(i % MLKEM_K); + + if (transposed) + { + seed_ext[0][MLKEM_SYMBYTES + 0] = x; + seed_ext[0][MLKEM_SYMBYTES + 1] = y; + } + else + { + seed_ext[0][MLKEM_SYMBYTES + 0] = y; + seed_ext[0][MLKEM_SYMBYTES + 1] = x; + } + + mlk_poly_rej_uniform(&a->vec[i / MLKEM_K].vec[i % MLKEM_K], seed_ext[0]); + } + + mlk_assert(i == MLKEM_K * MLKEM_K); + + /* + * The public matrix is generated in NTT domain. If the native backend + * uses a custom order in NTT domain, permute A accordingly. + */ + mlk_polymat_permute_bitrev_to_custom(a); + + /* Specification: Partially implements + * @[FIPS203, Section 3.3, Destruction of intermediate values] */ + mlk_zeroize(seed_ext, sizeof(seed_ext)); +} + +/************************************************* + * Name: mlk_matvec_mul + * + * Description: Computes matrix-vector product in NTT domain, + * via Montgomery multiplication. + * + * Arguments: - mlk_polyvec out: Pointer to output polynomial vector + * - mlk_polymat a: Input matrix. Must be in NTT domain + * and have coefficients of absolute value < 4096. + * - mlk_polyvec v: Input polynomial vector. Must be in NTT + * domain. + * - mlk_polyvec vc: Mulcache for v, computed via + * mlk_polyvec_mulcache_compute(). + * + * Specification: Implements @[FIPS203, Section 2.4.7, Eq (2.12), (2.13)] + * + **************************************************/ +static void mlk_matvec_mul(mlk_polyvec *out, const mlk_polymat *a, + const mlk_polyvec *v, const mlk_polyvec_mulcache *vc) +__contract__( + requires(memory_no_alias(out, sizeof(mlk_polyvec))) + requires(memory_no_alias(a, sizeof(mlk_polymat))) + requires(memory_no_alias(v, sizeof(mlk_polyvec))) + requires(memory_no_alias(vc, sizeof(mlk_polyvec_mulcache))) + requires(forall(k0, 0, MLKEM_K, + forall(k1, 0, MLKEM_K, + array_bound(a->vec[k0].vec[k1].coeffs, 0, MLKEM_N, 0, MLKEM_UINT12_LIMIT)))) + assigns(memory_slice(out, sizeof(mlk_polyvec)))) +{ + unsigned i; + for (i = 0; i < MLKEM_K; i++) + __loop__( + assigns(i, memory_slice(out, sizeof(mlk_polyvec))) + invariant(i <= MLKEM_K)) + { + mlk_polyvec_basemul_acc_montgomery_cached(&out->vec[i], &a->vec[i], v, vc); + } +} + +/************************************************* + * Name: mlk_keypair_getnoise + * + * Description: Computes and fills the pv and e polyvec + * structures needed by mlk_keypair_derand() + * + * Arguments: - pv: Pointer to output polynomial vector + * - e: Pointer to output polynomial vector + * - seed: seed bytes for sampling + * + * Specification: Implements @[FIPS203, Algorithm 13 (K-PKE.KeyGen)]. + * steps 8 - 15 + **************************************************/ +static void mlk_keypair_getnoise(mlk_polyvec *pv, mlk_polyvec *e, + const uint8_t seed[MLKEM_SYMBYTES]) +__contract__( + requires(memory_no_alias(pv, sizeof(mlk_polyvec))) + requires(memory_no_alias(e, sizeof(mlk_polyvec))) + requires(memory_no_alias(seed, MLKEM_SYMBYTES)) + assigns(memory_slice(pv, sizeof(mlk_polyvec))) + assigns(memory_slice(e, sizeof(mlk_polyvec))) + ensures(forall(k0, 0, MLKEM_K, array_abs_bound(pv->vec[k0].coeffs, 0, MLKEM_N, MLKEM_ETA1 + 1))) + ensures(forall(k1, 0, MLKEM_K, array_abs_bound(e->vec[k1].coeffs, 0, MLKEM_N, MLKEM_ETA1 + 1))) +) +{ +#if MLKEM_K == 2 + mlk_poly_getnoise_eta1_4x(&pv->vec[0], &pv->vec[1], &e->vec[0], &e->vec[1], + seed, 0, 1, 2, 3); +#elif MLKEM_K == 3 + /* + * Only the first three output buffers are needed. + */ + mlk_poly_getnoise_eta1_4x(&pv->vec[0], &pv->vec[1], &pv->vec[2], NULL, seed, + 0, 1, 2, 0xFF /* irrelevant */); + /* Same here */ + mlk_poly_getnoise_eta1_4x(&e->vec[0], &e->vec[1], &e->vec[2], NULL, seed, 3, + 4, 5, 0xFF /* irrelevant */); +#elif MLKEM_K == 4 + mlk_poly_getnoise_eta1_4x(&pv->vec[0], &pv->vec[1], &pv->vec[2], &pv->vec[3], + seed, 0, 1, 2, 3); + mlk_poly_getnoise_eta1_4x(&e->vec[0], &e->vec[1], &e->vec[2], &e->vec[3], + seed, 4, 5, 6, 7); +#endif /* MLKEM_K == 4 */ +} + + +/* Reference: `indcpa_keypair_derand()` in the reference implementation @[REF]. + * - We use x4-batched versions of `poly_getnoise` to leverage + * batched x4-batched Keccak-f1600. + * - We use a different implementation of `gen_matrix()` which + * uses x4-batched Keccak-f1600 (see `mlk_gen_matrix()` above). + * - We use a mulcache to speed up matrix-vector multiplication. + * - We include buffer zeroization. + */ +MLK_INTERNAL_API +int mlk_indcpa_keypair_derand(uint8_t pk[MLKEM_INDCPA_PUBLICKEYBYTES], + uint8_t sk[MLKEM_INDCPA_SECRETKEYBYTES], + const uint8_t coins[MLKEM_SYMBYTES], + MLK_CONFIG_CONTEXT_PARAMETER_TYPE context) +{ + int ret = 0; + const uint8_t *publicseed; + const uint8_t *noiseseed; + MLK_ALLOC(buf, uint8_t, 2 * MLKEM_SYMBYTES, context); + MLK_ALLOC(coins_with_domain_separator, uint8_t, MLKEM_SYMBYTES + 1, context); + MLK_ALLOC(a, mlk_polymat, 1, context); + MLK_ALLOC(e, mlk_polyvec, 1, context); + MLK_ALLOC(pkpv, mlk_polyvec, 1, context); + MLK_ALLOC(skpv, mlk_polyvec, 1, context); + MLK_ALLOC(skpv_cache, mlk_polyvec_mulcache, 1, context); + + if (buf == NULL || coins_with_domain_separator == NULL || a == NULL || + e == NULL || pkpv == NULL || skpv == NULL || skpv_cache == NULL) + { + ret = MLK_ERR_OUT_OF_MEMORY; + goto cleanup; + } + + publicseed = buf; + noiseseed = buf + MLKEM_SYMBYTES; + + /* Concatenate coins with MLKEM_K for domain separation of security levels */ + mlk_memcpy(coins_with_domain_separator, coins, MLKEM_SYMBYTES); + coins_with_domain_separator[MLKEM_SYMBYTES] = MLKEM_K; + + mlk_hash_g(buf, coins_with_domain_separator, MLKEM_SYMBYTES + 1); + + /* + * Declassify the public seed. + * Required to use it in conditional-branches in rejection sampling. + * This is needed because all output of randombytes is marked as secret + * (=undefined) + */ + MLK_CT_TESTING_DECLASSIFY(publicseed, MLKEM_SYMBYTES); + + mlk_gen_matrix(a, publicseed, 0 /* no transpose */); + + mlk_keypair_getnoise(skpv, e, noiseseed); + + mlk_polyvec_ntt(skpv); + mlk_polyvec_ntt(e); + + mlk_polyvec_mulcache_compute(skpv_cache, skpv); + mlk_matvec_mul(pkpv, a, skpv, skpv_cache); + mlk_polyvec_tomont(pkpv); + + mlk_polyvec_add(pkpv, e); + mlk_polyvec_reduce(pkpv); + mlk_polyvec_reduce(skpv); + + mlk_pack_sk(sk, skpv); + mlk_pack_pk(pk, pkpv, publicseed); + +cleanup: + /* Specification: Partially implements + * @[FIPS203, Section 3.3, Destruction of intermediate values] */ + MLK_FREE(skpv_cache, mlk_polyvec_mulcache, 1, context); + MLK_FREE(skpv, mlk_polyvec, 1, context); + MLK_FREE(pkpv, mlk_polyvec, 1, context); + MLK_FREE(e, mlk_polyvec, 1, context); + MLK_FREE(a, mlk_polymat, 1, context); + MLK_FREE(coins_with_domain_separator, uint8_t, MLKEM_SYMBYTES + 1, context); + MLK_FREE(buf, uint8_t, 2 * MLKEM_SYMBYTES, context); + return ret; +} + +/* Reference: `indcpa_enc()` in the reference implementation @[REF]. + * - We use x4-batched versions of `poly_getnoise` to leverage + * batched x4-batched Keccak-f1600. + * - We use a different implementation of `gen_matrix()` which + * uses x4-batched Keccak-f1600 (see `mlk_gen_matrix()` above). + * - We use a mulcache to speed up matrix-vector multiplication. + * - We include buffer zeroization. + */ +MLK_INTERNAL_API +int mlk_indcpa_enc(uint8_t c[MLKEM_INDCPA_BYTES], + const uint8_t m[MLKEM_INDCPA_MSGBYTES], + const uint8_t pk[MLKEM_INDCPA_PUBLICKEYBYTES], + const uint8_t coins[MLKEM_SYMBYTES], + MLK_CONFIG_CONTEXT_PARAMETER_TYPE context) +{ + int ret = 0; + MLK_ALLOC(seed, uint8_t, MLKEM_SYMBYTES, context); + MLK_ALLOC(at, mlk_polymat, 1, context); + MLK_ALLOC(sp, mlk_polyvec, 1, context); + MLK_ALLOC(pkpv, mlk_polyvec, 1, context); + MLK_ALLOC(ep, mlk_polyvec, 1, context); + MLK_ALLOC(b, mlk_polyvec, 1, context); + MLK_ALLOC(v, mlk_poly, 1, context); + MLK_ALLOC(k, mlk_poly, 1, context); + MLK_ALLOC(epp, mlk_poly, 1, context); + MLK_ALLOC(sp_cache, mlk_polyvec_mulcache, 1, context); + + if (seed == NULL || at == NULL || sp == NULL || pkpv == NULL || ep == NULL || + b == NULL || v == NULL || k == NULL || epp == NULL || sp_cache == NULL) + { + ret = MLK_ERR_OUT_OF_MEMORY; + goto cleanup; + } + + mlk_unpack_pk(pkpv, seed, pk); + mlk_poly_frommsg(k, m); + + /* + * Declassify the public seed. + * Required to use it in conditional-branches in rejection sampling. + * This is needed because in re-encryption the publicseed originated from sk + * which is marked undefined. + */ + MLK_CT_TESTING_DECLASSIFY(seed, MLKEM_SYMBYTES); + + mlk_gen_matrix(at, seed, 1 /* transpose */); + +#if MLKEM_K == 2 + mlk_poly_getnoise_eta1122_4x(&sp->vec[0], &sp->vec[1], &ep->vec[0], + &ep->vec[1], coins, 0, 1, 2, 3); + mlk_poly_getnoise_eta2(epp, coins, 4); +#elif MLKEM_K == 3 + /* + * In this call, only the first three output buffers are needed. + * The last parameter is a dummy that's overwritten later. + */ + mlk_poly_getnoise_eta1_4x(&sp->vec[0], &sp->vec[1], &sp->vec[2], NULL, coins, + 0, 1, 2, 0xFF /* irrelevant */); + /* The fourth output buffer in this call _is_ used. */ + mlk_poly_getnoise_eta2_4x(&ep->vec[0], &ep->vec[1], &ep->vec[2], epp, coins, + 3, 4, 5, 6); +#elif MLKEM_K == 4 + mlk_poly_getnoise_eta1_4x(&sp->vec[0], &sp->vec[1], &sp->vec[2], &sp->vec[3], + coins, 0, 1, 2, 3); + mlk_poly_getnoise_eta2_4x(&ep->vec[0], &ep->vec[1], &ep->vec[2], &ep->vec[3], + coins, 4, 5, 6, 7); + mlk_poly_getnoise_eta2(epp, coins, 8); +#endif /* MLKEM_K == 4 */ + + mlk_polyvec_ntt(sp); + + mlk_polyvec_mulcache_compute(sp_cache, sp); + mlk_matvec_mul(b, at, sp, sp_cache); + mlk_polyvec_basemul_acc_montgomery_cached(v, pkpv, sp, sp_cache); + + mlk_polyvec_invntt_tomont(b); + mlk_poly_invntt_tomont(v); + + mlk_polyvec_add(b, ep); + mlk_poly_add(v, epp); + mlk_poly_add(v, k); + + mlk_polyvec_reduce(b); + mlk_poly_reduce(v); + + mlk_pack_ciphertext(c, b, v); + +cleanup: + /* Specification: Partially implements + * @[FIPS203, Section 3.3, Destruction of intermediate values] */ + MLK_FREE(sp_cache, mlk_polyvec_mulcache, 1, context); + MLK_FREE(epp, mlk_poly, 1, context); + MLK_FREE(k, mlk_poly, 1, context); + MLK_FREE(v, mlk_poly, 1, context); + MLK_FREE(b, mlk_polyvec, 1, context); + MLK_FREE(ep, mlk_polyvec, 1, context); + MLK_FREE(pkpv, mlk_polyvec, 1, context); + MLK_FREE(sp, mlk_polyvec, 1, context); + MLK_FREE(at, mlk_polymat, 1, context); + MLK_FREE(seed, uint8_t, MLKEM_SYMBYTES, context); + return ret; +} + +/* Reference: `indcpa_dec()` in the reference implementation @[REF]. + * - We use a mulcache for the scalar product. + * - We include buffer zeroization. */ +MLK_INTERNAL_API +int mlk_indcpa_dec(uint8_t m[MLKEM_INDCPA_MSGBYTES], + const uint8_t c[MLKEM_INDCPA_BYTES], + const uint8_t sk[MLKEM_INDCPA_SECRETKEYBYTES], + MLK_CONFIG_CONTEXT_PARAMETER_TYPE context) +{ + int ret = 0; + MLK_ALLOC(b, mlk_polyvec, 1, context); + MLK_ALLOC(skpv, mlk_polyvec, 1, context); + MLK_ALLOC(v, mlk_poly, 1, context); + MLK_ALLOC(sb, mlk_poly, 1, context); + MLK_ALLOC(b_cache, mlk_polyvec_mulcache, 1, context); + + if (b == NULL || skpv == NULL || v == NULL || sb == NULL || b_cache == NULL) + { + ret = MLK_ERR_OUT_OF_MEMORY; + goto cleanup; + } + + mlk_unpack_ciphertext(b, v, c); + mlk_unpack_sk(skpv, sk); + + mlk_polyvec_ntt(b); + mlk_polyvec_mulcache_compute(b_cache, b); + mlk_polyvec_basemul_acc_montgomery_cached(sb, skpv, b, b_cache); + mlk_poly_invntt_tomont(sb); + + mlk_poly_sub(v, sb); + mlk_poly_reduce(v); + + mlk_poly_tomsg(m, v); + +cleanup: + /* Specification: Partially implements + * @[FIPS203, Section 3.3, Destruction of intermediate values] */ + MLK_FREE(b_cache, mlk_polyvec_mulcache, 1, context); + MLK_FREE(sb, mlk_poly, 1, context); + MLK_FREE(v, mlk_poly, 1, context); + MLK_FREE(skpv, mlk_polyvec, 1, context); + MLK_FREE(b, mlk_polyvec, 1, context); + return ret; +} + +/* To facilitate single-compilation-unit (SCU) builds, undefine all macros. + * Don't modify by hand -- this is auto-generated by scripts/autogen. */ +#undef mlk_pack_pk +#undef mlk_unpack_pk +#undef mlk_pack_sk +#undef mlk_unpack_sk +#undef mlk_pack_ciphertext +#undef mlk_unpack_ciphertext +#undef mlk_matvec_mul +#undef mlk_polyvec_permute_bitrev_to_custom +#undef mlk_polymat_permute_bitrev_to_custom +#undef mlk_keypair_getnoise diff --git a/mlkem_native/src/indcpa.h b/mlkem_native/src/indcpa.h new file mode 100644 index 0000000..b31756d --- /dev/null +++ b/mlkem_native/src/indcpa.h @@ -0,0 +1,156 @@ +/* + * Copyright (c) The mlkem-native project authors + * SPDX-License-Identifier: Apache-2.0 OR ISC OR MIT + */ + +/* References + * ========== + * + * - [FIPS203] + * FIPS 203 Module-Lattice-Based Key-Encapsulation Mechanism Standard + * National Institute of Standards and Technology + * https://csrc.nist.gov/pubs/fips/203/final + */ + +#ifndef MLK_INDCPA_H +#define MLK_INDCPA_H + +#include "cbmc.h" +#include "common.h" +#include "poly_k.h" + +#define mlk_gen_matrix MLK_NAMESPACE_K(gen_matrix) +/************************************************* + * Name: mlk_gen_matrix + * + * Description: Deterministically generate matrix A (or the transpose of A) + * from a seed. Entries of the matrix are polynomials that look + * uniformly random. Performs rejection sampling on output of + * a XOF + * + * Arguments: - mlk_polymat a: pointer to output matrix A + * - const uint8_t *seed: pointer to input seed + * - int transposed: boolean deciding whether A or A^T is generated + * + * Specification: Implements @[FIPS203, Algorithm 13 (K-PKE.KeyGen), L3-7] + * and @[FIPS203, Algorithm 14 (K-PKE.Encrypt), L4-8]. + * The `transposed` parameter only affects internal presentation. + * + **************************************************/ +MLK_INTERNAL_API +void mlk_gen_matrix(mlk_polymat *a, const uint8_t seed[MLKEM_SYMBYTES], + int transposed) +__contract__( + requires(memory_no_alias(a, sizeof(mlk_polymat))) + requires(memory_no_alias(seed, MLKEM_SYMBYTES)) + requires(transposed == 0 || transposed == 1) + assigns(memory_slice(a, sizeof(mlk_polymat))) + ensures(forall(x, 0, MLKEM_K, forall(y, 0, MLKEM_K, + array_bound(a->vec[x].vec[y].coeffs, 0, MLKEM_N, 0, MLKEM_Q)))) +); + +#define mlk_indcpa_keypair_derand \ + MLK_NAMESPACE_K(indcpa_keypair_derand) MLK_CONTEXT_PARAMETERS_3 +/************************************************* + * Name: mlk_indcpa_keypair_derand + * + * Description: Generates public and private key for the CPA-secure + * public-key encryption scheme underlying ML-KEM + * + * Arguments: - uint8_t *pk: pointer to output public key + * (of length MLKEM_INDCPA_PUBLICKEYBYTES bytes) + * - uint8_t *sk: pointer to output private key + * (of length MLKEM_INDCPA_SECRETKEYBYTES bytes) + * - const uint8_t *coins: pointer to input randomness + * (of length MLKEM_SYMBYTES bytes) + * + * Specification: Implements @[FIPS203, Algorithm 13 (K-PKE.KeyGen)]. + * + **************************************************/ +MLK_INTERNAL_API +MLK_MUST_CHECK_RETURN_VALUE +int mlk_indcpa_keypair_derand(uint8_t pk[MLKEM_INDCPA_PUBLICKEYBYTES], + uint8_t sk[MLKEM_INDCPA_SECRETKEYBYTES], + const uint8_t coins[MLKEM_SYMBYTES], + MLK_CONFIG_CONTEXT_PARAMETER_TYPE context) +__contract__( + requires(memory_no_alias(pk, MLKEM_INDCPA_PUBLICKEYBYTES)) + requires(memory_no_alias(sk, MLKEM_INDCPA_SECRETKEYBYTES)) + requires(memory_no_alias(coins, MLKEM_SYMBYTES)) + assigns(memory_slice(pk, MLKEM_INDCPA_PUBLICKEYBYTES)) + assigns(memory_slice(sk, MLKEM_INDCPA_SECRETKEYBYTES)) + ensures(return_value == 0 || return_value == MLK_ERR_FAIL || + return_value == MLK_ERR_OUT_OF_MEMORY || + return_value == MLK_ERR_RNG_FAIL) +); + +#define mlk_indcpa_enc MLK_NAMESPACE_K(indcpa_enc) MLK_CONTEXT_PARAMETERS_4 +/************************************************* + * Name: mlk_indcpa_enc + * + * Description: Encryption function of the CPA-secure + * public-key encryption scheme underlying Kyber. + * + * Arguments: - uint8_t *c: pointer to output ciphertext + * (of length MLKEM_INDCPA_BYTES bytes) + * - const uint8_t *m: pointer to input message + * (of length MLKEM_INDCPA_MSGBYTES bytes) + * - const uint8_t *pk: pointer to input public key + * (of length MLKEM_INDCPA_PUBLICKEYBYTES) + * - const uint8_t *coins: pointer to input random coins used as + * seed (of length MLKEM_SYMBYTES) to deterministically generate + * all randomness + * + * Specification: Implements @[FIPS203, Algorithm 14 (K-PKE.Encrypt)]. + * + **************************************************/ +MLK_INTERNAL_API +MLK_MUST_CHECK_RETURN_VALUE +int mlk_indcpa_enc(uint8_t c[MLKEM_INDCPA_BYTES], + const uint8_t m[MLKEM_INDCPA_MSGBYTES], + const uint8_t pk[MLKEM_INDCPA_PUBLICKEYBYTES], + const uint8_t coins[MLKEM_SYMBYTES], + MLK_CONFIG_CONTEXT_PARAMETER_TYPE context) +__contract__( + requires(memory_no_alias(c, MLKEM_INDCPA_BYTES)) + requires(memory_no_alias(m, MLKEM_INDCPA_MSGBYTES)) + requires(memory_no_alias(pk, MLKEM_INDCPA_PUBLICKEYBYTES)) + requires(memory_no_alias(coins, MLKEM_SYMBYTES)) + assigns(memory_slice(c, MLKEM_INDCPA_BYTES)) + ensures(return_value == 0 || return_value == MLK_ERR_FAIL || + return_value == MLK_ERR_OUT_OF_MEMORY) +); + +#define mlk_indcpa_dec MLK_NAMESPACE_K(indcpa_dec) MLK_CONTEXT_PARAMETERS_3 +/************************************************* + * Name: mlk_indcpa_dec + * + * Description: Decryption function of the CPA-secure + * public-key encryption scheme underlying Kyber. + * + * Arguments: - uint8_t *m: pointer to output decrypted message + * (of length MLKEM_INDCPA_MSGBYTES) + * - const uint8_t *c: pointer to input ciphertext + * (of length MLKEM_INDCPA_BYTES) + * - const uint8_t *sk: pointer to input secret key + * (of length MLKEM_INDCPA_SECRETKEYBYTES) + * + * Specification: Implements @[FIPS203, Algorithm 15 (K-PKE.Decrypt)]. + * + **************************************************/ +MLK_INTERNAL_API +MLK_MUST_CHECK_RETURN_VALUE +int mlk_indcpa_dec(uint8_t m[MLKEM_INDCPA_MSGBYTES], + const uint8_t c[MLKEM_INDCPA_BYTES], + const uint8_t sk[MLKEM_INDCPA_SECRETKEYBYTES], + MLK_CONFIG_CONTEXT_PARAMETER_TYPE context) +__contract__( + requires(memory_no_alias(c, MLKEM_INDCPA_BYTES)) + requires(memory_no_alias(m, MLKEM_INDCPA_MSGBYTES)) + requires(memory_no_alias(sk, MLKEM_INDCPA_SECRETKEYBYTES)) + assigns(memory_slice(m, MLKEM_INDCPA_MSGBYTES)) + ensures(return_value == 0 || return_value == MLK_ERR_FAIL || + return_value == MLK_ERR_OUT_OF_MEMORY) +); + +#endif /* !MLK_INDCPA_H */ diff --git a/mlkem_native/src/kem.c b/mlkem_native/src/kem.c new file mode 100644 index 0000000..3c82d6d --- /dev/null +++ b/mlkem_native/src/kem.c @@ -0,0 +1,446 @@ +/* + * Copyright (c) The mlkem-native project authors + * SPDX-License-Identifier: Apache-2.0 OR ISC OR MIT + */ + +/* References + * ========== + * + * - [FIPS140_3_IG] + * Implementation Guidance for FIPS 140-3 and the Cryptographic Module + * Validation Program + * National Institute of Standards and Technology + * https://csrc.nist.gov/projects/cryptographic-module-validation-program/fips-140-3-ig-announcements + * + * - [FIPS203] + * FIPS 203 Module-Lattice-Based Key-Encapsulation Mechanism Standard + * National Institute of Standards and Technology + * https://csrc.nist.gov/pubs/fips/203/final + * + * - [REF] + * CRYSTALS-Kyber C reference implementation + * Bos, Ducas, Kiltz, Lepoint, Lyubashevsky, Schanck, Schwabe, Seiler, StehlΓ© + * https://github.com/pq-crystals/kyber/tree/main/ref + */ + +#include "kem.h" + +#include "indcpa.h" +#include "randombytes.h" +#include "symmetric.h" +#include "verify.h" + +/* Parameter set namespacing + * This is to facilitate building multiple instances + * of mlkem-native (e.g. with varying security levels) + * within a single compilation unit. */ +#define mlk_check_pct MLK_ADD_PARAM_SET(mlk_check_pct) MLK_CONTEXT_PARAMETERS_2 +/* End of parameter set namespacing */ + +/* Reference: Not implemented in the reference implementation @[REF]. */ +MLK_EXTERNAL_API +MLK_MUST_CHECK_RETURN_VALUE +int mlk_kem_check_pk(const uint8_t pk[MLKEM_INDCCA_PUBLICKEYBYTES], + MLK_CONFIG_CONTEXT_PARAMETER_TYPE context) +{ + int ret = 0; + MLK_ALLOC(p, mlk_polyvec, 1, context); + MLK_ALLOC(p_reencoded, uint8_t, MLKEM_POLYVECBYTES, context); + + if (p == NULL || p_reencoded == NULL) + { + ret = MLK_ERR_OUT_OF_MEMORY; + goto cleanup; + } + + mlk_polyvec_frombytes(p, pk); + mlk_polyvec_reduce(p); + mlk_polyvec_tobytes(p_reencoded, p); + + /* We use a constant-time memcmp here to avoid having to + * declassify the PK before the PCT has succeeded. */ + ret = mlk_ct_memcmp(pk, p_reencoded, MLKEM_POLYVECBYTES) ? MLK_ERR_FAIL : 0; + +cleanup: + /* Specification: Partially implements + * @[FIPS203, Section 3.3, Destruction of intermediate values] */ + MLK_FREE(p_reencoded, uint8_t, MLKEM_POLYVECBYTES, context); + MLK_FREE(p, mlk_polyvec, 1, context); + return ret; +} + + +/* Reference: Not implemented in the reference implementation @[REF]. */ +MLK_EXTERNAL_API +MLK_MUST_CHECK_RETURN_VALUE +int mlk_kem_check_sk(const uint8_t sk[MLKEM_INDCCA_SECRETKEYBYTES], + MLK_CONFIG_CONTEXT_PARAMETER_TYPE context) +{ + int ret = 0; + MLK_ALLOC(test, uint8_t, MLKEM_SYMBYTES, context); + + if (test == NULL) + { + ret = MLK_ERR_OUT_OF_MEMORY; + goto cleanup; + } + + /* + * The parts of `sk` being hashed and compared here are public, so + * no public information is leaked through the runtime or the return value + * of this function. + */ + + /* Declassify the public part of the secret key */ + MLK_CT_TESTING_DECLASSIFY(sk + MLKEM_INDCPA_SECRETKEYBYTES, + MLKEM_INDCCA_PUBLICKEYBYTES); + MLK_CT_TESTING_DECLASSIFY( + sk + MLKEM_INDCCA_SECRETKEYBYTES - 2 * MLKEM_SYMBYTES, MLKEM_SYMBYTES); + + mlk_hash_h(test, sk + MLKEM_INDCPA_SECRETKEYBYTES, + MLKEM_INDCCA_PUBLICKEYBYTES); + /* This doesn't have to be a constant-time memcmp, but it's the only place + * in the library where a normal memcmp would be used otherwise, so for sake + * of minimizing stdlib dependency, we use our constant-time one anyway. */ + ret = mlk_ct_memcmp(sk + MLKEM_INDCCA_SECRETKEYBYTES - 2 * MLKEM_SYMBYTES, + test, MLKEM_SYMBYTES) + ? MLK_ERR_FAIL + : 0; + +cleanup: + /* Specification: Partially implements + * @[FIPS203, Section 3.3, Destruction of intermediate values] */ + MLK_FREE(test, uint8_t, MLKEM_SYMBYTES, context); + return ret; +} + +MLK_MUST_CHECK_RETURN_VALUE +static int mlk_check_pct(uint8_t const pk[MLKEM_INDCCA_PUBLICKEYBYTES], + uint8_t const sk[MLKEM_INDCCA_SECRETKEYBYTES], + MLK_CONFIG_CONTEXT_PARAMETER_TYPE context) +__contract__( + requires(memory_no_alias(pk, MLKEM_INDCCA_PUBLICKEYBYTES)) + requires(memory_no_alias(sk, MLKEM_INDCCA_SECRETKEYBYTES)) + ensures(return_value == 0 || return_value == MLK_ERR_FAIL || + return_value == MLK_ERR_OUT_OF_MEMORY || + return_value == MLK_ERR_RNG_FAIL) +); + +#if defined(MLK_CONFIG_KEYGEN_PCT) +/* Specification: + * Partially implements 'Pairwise Consistency Test' @[FIPS140_3_IG, p.87] and + * @[FIPS203, Section 7.1, Pairwise Consistency]. */ + +/* Reference: Not implemented in the reference implementation @[REF]. */ +MLK_MUST_CHECK_RETURN_VALUE +static int mlk_check_pct(uint8_t const pk[MLKEM_INDCCA_PUBLICKEYBYTES], + uint8_t const sk[MLKEM_INDCCA_SECRETKEYBYTES], + MLK_CONFIG_CONTEXT_PARAMETER_TYPE context) +{ + int ret = 0; + MLK_ALLOC(ct, uint8_t, MLKEM_INDCCA_CIPHERTEXTBYTES, context); + MLK_ALLOC(ss_enc, uint8_t, MLKEM_SSBYTES, context); + MLK_ALLOC(ss_dec, uint8_t, MLKEM_SSBYTES, context); + + if (ct == NULL || ss_enc == NULL || ss_dec == NULL) + { + ret = MLK_ERR_OUT_OF_MEMORY; + goto cleanup; + } + + ret = mlk_kem_enc(ct, ss_enc, pk, context); + if (ret != 0) + { + goto cleanup; + } + + ret = mlk_kem_dec(ss_dec, ct, sk, context); + if (ret != 0) + { + goto cleanup; + } + +#if defined(MLK_CONFIG_KEYGEN_PCT_BREAKAGE_TEST) + /* Deliberately break PCT for testing purposes */ + if (mlk_break_pct()) + { + ss_enc[0] = ~ss_enc[0]; + } +#endif /* MLK_CONFIG_KEYGEN_PCT_BREAKAGE_TEST */ + + ret = mlk_ct_memcmp(ss_enc, ss_dec, MLKEM_SSBYTES); + /* The result of the PCT is public. */ + MLK_CT_TESTING_DECLASSIFY(&ret, sizeof(ret)); + + if (ret != 0) + { + ret = MLK_ERR_FAIL; + } + +cleanup: + + /* Specification: Partially implements + * @[FIPS203, Section 3.3, Destruction of intermediate values] */ + MLK_FREE(ss_dec, uint8_t, MLKEM_SSBYTES, context); + MLK_FREE(ss_enc, uint8_t, MLKEM_SSBYTES, context); + MLK_FREE(ct, uint8_t, MLKEM_INDCCA_CIPHERTEXTBYTES, context); + return ret; +} +#else /* MLK_CONFIG_KEYGEN_PCT */ +MLK_MUST_CHECK_RETURN_VALUE +static int mlk_check_pct(uint8_t const pk[MLKEM_INDCCA_PUBLICKEYBYTES], + uint8_t const sk[MLKEM_INDCCA_SECRETKEYBYTES], + MLK_CONFIG_CONTEXT_PARAMETER_TYPE context) +{ + /* Skip PCT */ + ((void)pk); + ((void)sk); +#if defined(MLK_CONFIG_CONTEXT_PARAMETER) + ((void)context); +#endif + return 0; +} +#endif /* !MLK_CONFIG_KEYGEN_PCT */ + +/* Reference: `crypto_kem_keypair_derand()` in the reference implementation + * @[REF]. + * - We optionally include PCT which is not present in + * the reference code. */ +MLK_EXTERNAL_API +int mlk_kem_keypair_derand(uint8_t pk[MLKEM_INDCCA_PUBLICKEYBYTES], + uint8_t sk[MLKEM_INDCCA_SECRETKEYBYTES], + const uint8_t coins[2 * MLKEM_SYMBYTES], + MLK_CONFIG_CONTEXT_PARAMETER_TYPE context) +{ + int ret; + + ret = mlk_indcpa_keypair_derand(pk, sk, coins, context); + if (ret != 0) + { + goto cleanup; + } + + mlk_memcpy(sk + MLKEM_INDCPA_SECRETKEYBYTES, pk, MLKEM_INDCCA_PUBLICKEYBYTES); + mlk_hash_h(sk + MLKEM_INDCCA_SECRETKEYBYTES - 2 * MLKEM_SYMBYTES, pk, + MLKEM_INDCCA_PUBLICKEYBYTES); + /* Value z for pseudo-random output on reject */ + mlk_memcpy(sk + MLKEM_INDCCA_SECRETKEYBYTES - MLKEM_SYMBYTES, + coins + MLKEM_SYMBYTES, MLKEM_SYMBYTES); + + /* Declassify public key */ + MLK_CT_TESTING_DECLASSIFY(pk, MLKEM_INDCCA_PUBLICKEYBYTES); + + /* Pairwise Consistency Test (PCT) @[FIPS140_3_IG, p.87] */ + ret = mlk_check_pct(pk, sk, context); + if (ret != 0) + { + goto cleanup; + } + +cleanup: + if (ret != 0) + { + mlk_zeroize(pk, MLKEM_INDCCA_PUBLICKEYBYTES); + mlk_zeroize(sk, MLKEM_INDCCA_SECRETKEYBYTES); + } + + return ret; +} + +#if !defined(MLK_CONFIG_NO_RANDOMIZED_API) +/* Reference: `crypto_kem_keypair()` in the reference implementation @[REF] + * - We zeroize the stack buffer */ +MLK_EXTERNAL_API +int mlk_kem_keypair(uint8_t pk[MLKEM_INDCCA_PUBLICKEYBYTES], + uint8_t sk[MLKEM_INDCCA_SECRETKEYBYTES], + MLK_CONFIG_CONTEXT_PARAMETER_TYPE context) +{ + int ret = 0; + MLK_ALLOC(coins, uint8_t, 2 * MLKEM_SYMBYTES, context); + + if (coins == NULL) + { + ret = MLK_ERR_OUT_OF_MEMORY; + goto cleanup; + } + + /* Acquire necessary randomness, and mark it as secret. */ + if (mlk_randombytes(coins, 2 * MLKEM_SYMBYTES) != 0) + { + ret = MLK_ERR_RNG_FAIL; + goto cleanup; + } + + MLK_CT_TESTING_SECRET(coins, 2 * MLKEM_SYMBYTES); + + ret = mlk_kem_keypair_derand(pk, sk, coins, context); + +cleanup: + /* Specification: Partially implements + * @[FIPS203, Section 3.3, Destruction of intermediate values] */ + MLK_FREE(coins, uint8_t, 2 * MLKEM_SYMBYTES, context); + return ret; +} +#endif /* !MLK_CONFIG_NO_RANDOMIZED_API */ + +/* Reference: `crypto_kem_enc_derand()` in the reference implementation @[REF] + * - We include public key check + * - We include stack buffer zeroization */ +MLK_EXTERNAL_API +int mlk_kem_enc_derand(uint8_t ct[MLKEM_INDCCA_CIPHERTEXTBYTES], + uint8_t ss[MLKEM_SSBYTES], + const uint8_t pk[MLKEM_INDCCA_PUBLICKEYBYTES], + const uint8_t coins[MLKEM_SYMBYTES], + MLK_CONFIG_CONTEXT_PARAMETER_TYPE context) +{ + int ret = 0; + MLK_ALLOC(buf, uint8_t, 2 * MLKEM_SYMBYTES, context); + MLK_ALLOC(kr, uint8_t, 2 * MLKEM_SYMBYTES, context); + + if (buf == NULL || kr == NULL) + { + ret = MLK_ERR_OUT_OF_MEMORY; + goto cleanup; + } + + /* Specification: Implements @[FIPS203, Section 7.2, Modulus check] */ + ret = mlk_kem_check_pk(pk, context); + if (ret != 0) + { + goto cleanup; + } + + mlk_memcpy(buf, coins, MLKEM_SYMBYTES); + + /* Multitarget countermeasure for coins + contributory KEM */ + mlk_hash_h(buf + MLKEM_SYMBYTES, pk, MLKEM_INDCCA_PUBLICKEYBYTES); + mlk_hash_g(kr, buf, 2 * MLKEM_SYMBYTES); + + /* coins are in kr+MLKEM_SYMBYTES */ + ret = mlk_indcpa_enc(ct, buf, pk, kr + MLKEM_SYMBYTES, context); + if (ret != 0) + { + goto cleanup; + } + + mlk_memcpy(ss, kr, MLKEM_SYMBYTES); + +cleanup: + /* Specification: Partially implements + * @[FIPS203, Section 3.3, Destruction of intermediate values] */ + MLK_FREE(kr, uint8_t, 2 * MLKEM_SYMBYTES, context); + MLK_FREE(buf, uint8_t, 2 * MLKEM_SYMBYTES, context); + return ret; +} + +#if !defined(MLK_CONFIG_NO_RANDOMIZED_API) +/* Reference: `crypto_kem_enc()` in the reference implementation @[REF] + * - We include stack buffer zeroization */ +MLK_EXTERNAL_API +int mlk_kem_enc(uint8_t ct[MLKEM_INDCCA_CIPHERTEXTBYTES], + uint8_t ss[MLKEM_SSBYTES], + const uint8_t pk[MLKEM_INDCCA_PUBLICKEYBYTES], + MLK_CONFIG_CONTEXT_PARAMETER_TYPE context) +{ + int ret = 0; + MLK_ALLOC(coins, uint8_t, MLKEM_SYMBYTES, context); + + if (coins == NULL) + { + ret = MLK_ERR_OUT_OF_MEMORY; + goto cleanup; + } + + if (mlk_randombytes(coins, MLKEM_SYMBYTES) != 0) + { + ret = MLK_ERR_RNG_FAIL; + goto cleanup; + } + + MLK_CT_TESTING_SECRET(coins, MLKEM_SYMBYTES); + + ret = mlk_kem_enc_derand(ct, ss, pk, coins, context); + +cleanup: + /* Specification: Partially implements + * @[FIPS203, Section 3.3, Destruction of intermediate values] */ + MLK_FREE(coins, uint8_t, MLKEM_SYMBYTES, context); + return ret; +} +#endif /* !MLK_CONFIG_NO_RANDOMIZED_API */ + +/* Reference: `crypto_kem_dec()` in the reference implementation @[REF] + * - We include secret key check + * - We include stack buffer zeroization */ +MLK_EXTERNAL_API +int mlk_kem_dec(uint8_t ss[MLKEM_SSBYTES], + const uint8_t ct[MLKEM_INDCCA_CIPHERTEXTBYTES], + const uint8_t sk[MLKEM_INDCCA_SECRETKEYBYTES], + MLK_CONFIG_CONTEXT_PARAMETER_TYPE context) +{ + int ret = 0; + uint8_t fail; + const uint8_t *pk = sk + MLKEM_INDCPA_SECRETKEYBYTES; + MLK_ALLOC(buf, uint8_t, 2 * MLKEM_SYMBYTES, context); + MLK_ALLOC(kr, uint8_t, 2 * MLKEM_SYMBYTES, context); + MLK_ALLOC(tmp, uint8_t, MLKEM_SYMBYTES + MLKEM_INDCCA_CIPHERTEXTBYTES, + context); + + if (buf == NULL || kr == NULL || tmp == NULL) + { + ret = MLK_ERR_OUT_OF_MEMORY; + goto cleanup; + } + + /* Specification: Implements @[FIPS203, Section 7.3, Hash check] */ + ret = mlk_kem_check_sk(sk, context); + if (ret != 0) + { + goto cleanup; + } + + ret = mlk_indcpa_dec(buf, ct, sk, context); + if (ret != 0) + { + goto cleanup; + } + + /* Multitarget countermeasure for coins + contributory KEM */ + mlk_memcpy(buf + MLKEM_SYMBYTES, + sk + MLKEM_INDCCA_SECRETKEYBYTES - 2 * MLKEM_SYMBYTES, + MLKEM_SYMBYTES); + mlk_hash_g(kr, buf, 2 * MLKEM_SYMBYTES); + + /* Recompute and compare ciphertext */ + /* coins are in kr+MLKEM_SYMBYTES */ + ret = mlk_indcpa_enc(tmp, buf, pk, kr + MLKEM_SYMBYTES, context); + if (ret != 0) + { + goto cleanup; + } + + fail = mlk_ct_memcmp(ct, tmp, MLKEM_INDCCA_CIPHERTEXTBYTES); + + /* Compute rejection key */ + mlk_memcpy(tmp, sk + MLKEM_INDCCA_SECRETKEYBYTES - MLKEM_SYMBYTES, + MLKEM_SYMBYTES); + mlk_memcpy(tmp + MLKEM_SYMBYTES, ct, MLKEM_INDCCA_CIPHERTEXTBYTES); + mlk_hash_j(ss, tmp, MLKEM_SYMBYTES + MLKEM_INDCCA_CIPHERTEXTBYTES); + + /* Copy true key to return buffer if fail is 0 */ + mlk_ct_cmov_zero(ss, kr, MLKEM_SYMBYTES, fail); + +cleanup: + /* Specification: Partially implements + * @[FIPS203, Section 3.3, Destruction of intermediate values] */ + MLK_FREE(tmp, uint8_t, MLKEM_SYMBYTES + MLKEM_INDCCA_CIPHERTEXTBYTES, + context); + MLK_FREE(kr, uint8_t, 2 * MLKEM_SYMBYTES, context); + MLK_FREE(buf, uint8_t, 2 * MLKEM_SYMBYTES, context); + + return ret; +} + +/* To facilitate single-compilation-unit (SCU) builds, undefine all macros. + * Don't modify by hand -- this is auto-generated by scripts/autogen. */ +#undef mlk_check_pct diff --git a/mlkem_native/src/kem.h b/mlkem_native/src/kem.h new file mode 100644 index 0000000..0502715 --- /dev/null +++ b/mlkem_native/src/kem.h @@ -0,0 +1,326 @@ +/* + * Copyright (c) The mlkem-native project authors + * SPDX-License-Identifier: Apache-2.0 OR ISC OR MIT + */ + +/* References + * ========== + * + * - [FIPS203] + * FIPS 203 Module-Lattice-Based Key-Encapsulation Mechanism Standard + * National Institute of Standards and Technology + * https://csrc.nist.gov/pubs/fips/203/final + * + * - [REF] + * CRYSTALS-Kyber C reference implementation + * Bos, Ducas, Kiltz, Lepoint, Lyubashevsky, Schanck, Schwabe, Seiler, StehlΓ© + * https://github.com/pq-crystals/kyber/tree/main/ref + */ + +#ifndef MLK_KEM_H +#define MLK_KEM_H + +#include "cbmc.h" +#include "common.h" +#include "sys.h" + +#if defined(MLK_CHECK_APIS) +/* Include to ensure consistency between internal kem.h + * and external mlkem_native.h. */ +#include "mlkem_native.h" + +#if MLKEM_INDCCA_SECRETKEYBYTES != \ + MLKEM_SECRETKEYBYTES(MLK_CONFIG_PARAMETER_SET) +#error Mismatch for SECRETKEYBYTES between kem.h and mlkem_native.h +#endif + +#if MLKEM_INDCCA_PUBLICKEYBYTES != \ + MLKEM_PUBLICKEYBYTES(MLK_CONFIG_PARAMETER_SET) +#error Mismatch for PUBLICKEYBYTES between kem.h and mlkem_native.h +#endif + +#if MLKEM_INDCCA_CIPHERTEXTBYTES != \ + MLKEM_CIPHERTEXTBYTES(MLK_CONFIG_PARAMETER_SET) +#error Mismatch for CIPHERTEXTBYTES between kem.h and mlkem_native.h +#endif + +#endif /* MLK_CHECK_APIS */ + +#define mlk_kem_keypair_derand \ + MLK_NAMESPACE_K(keypair_derand) MLK_CONTEXT_PARAMETERS_3 +#define mlk_kem_keypair MLK_NAMESPACE_K(keypair) MLK_CONTEXT_PARAMETERS_2 +#define mlk_kem_enc_derand MLK_NAMESPACE_K(enc_derand) MLK_CONTEXT_PARAMETERS_4 +#define mlk_kem_enc MLK_NAMESPACE_K(enc) MLK_CONTEXT_PARAMETERS_3 +#define mlk_kem_dec MLK_NAMESPACE_K(dec) MLK_CONTEXT_PARAMETERS_3 +#define mlk_kem_check_pk MLK_NAMESPACE_K(check_pk) MLK_CONTEXT_PARAMETERS_1 +#define mlk_kem_check_sk MLK_NAMESPACE_K(check_sk) MLK_CONTEXT_PARAMETERS_1 + +/************************************************* + * Name: mlk_kem_check_pk + * + * Description: Implements modulus check mandated by FIPS 203, + * i.e., ensures that coefficients are in [0,q-1]. + * + * Arguments: - const uint8_t *pk: pointer to input public key + * (an already allocated array of MLKEM_INDCCA_PUBLICKEYBYTES + * bytes) + * + * Returns: - 0 on success + * - MLK_ERR_FAIL: If the modulus check failed. + * - MLK_ERR_OUT_OF_MEMORY: If MLK_CONFIG_CUSTOM_ALLOC_FREE is + * used and an allocation via MLK_CUSTOM_ALLOC returned NULL. + * + * Specification: Implements @[FIPS203, Section 7.2, 'modulus check'] + * + **************************************************/ + +/* Reference: Not implemented in the reference implementation @[REF]. */ +MLK_EXTERNAL_API +MLK_MUST_CHECK_RETURN_VALUE +int mlk_kem_check_pk(const uint8_t pk[MLKEM_INDCCA_PUBLICKEYBYTES], + MLK_CONFIG_CONTEXT_PARAMETER_TYPE context) +__contract__( + requires(memory_no_alias(pk, MLKEM_INDCCA_PUBLICKEYBYTES)) + ensures(return_value == 0 || return_value == MLK_ERR_FAIL || + return_value == MLK_ERR_OUT_OF_MEMORY) +); + + +/************************************************* + * Name: mlk_kem_check_sk + * + * Description: Implements public key hash check mandated by FIPS 203, + * i.e., ensures that + * sk[768π‘˜+32 ∢ 768π‘˜+64] = H(pk)= H(sk[384π‘˜ : 768π‘˜+32]) + * + * Arguments: - const uint8_t *sk: pointer to input private key + * (an already allocated array of MLKEM_INDCCA_SECRETKEYBYTES + * bytes) + * + * Returns: - 0 on success + * - MLK_ERR_FAIL: If the public key hash check failed. + * - MLK_ERR_OUT_OF_MEMORY: If MLK_CONFIG_CUSTOM_ALLOC_FREE is + * used and an allocation via MLK_CUSTOM_ALLOC returned NULL. + * + * Specification: Implements @[FIPS203, Section 7.3, 'hash check'] + * + **************************************************/ + +/* Reference: Not implemented in the reference implementation @[REF]. */ +MLK_EXTERNAL_API +MLK_MUST_CHECK_RETURN_VALUE +int mlk_kem_check_sk(const uint8_t sk[MLKEM_INDCCA_SECRETKEYBYTES], + MLK_CONFIG_CONTEXT_PARAMETER_TYPE context) +__contract__( + requires(memory_no_alias(sk, MLKEM_INDCCA_SECRETKEYBYTES)) + ensures(return_value == 0 || return_value == MLK_ERR_FAIL || + return_value == MLK_ERR_OUT_OF_MEMORY) +); + +/************************************************* + * Name: mlk_kem_keypair_derand + * + * Description: Generates public and private key + * for CCA-secure ML-KEM key encapsulation mechanism + * + * Arguments: - uint8_t *pk: pointer to output public key + * (an already allocated array of MLKEM_INDCCA_PUBLICKEYBYTES + * bytes) + * - uint8_t *sk: pointer to output private key + * (an already allocated array of MLKEM_INDCCA_SECRETKEYBYTES + * bytes) + * - uint8_t *coins: pointer to input randomness + * (an already allocated array filled with 2*MLKEM_SYMBYTES + * random bytes) + * + * Returns: - 0: On success + * - MLK_ERR_FAIL: If MLK_CONFIG_KEYGEN_PCT is enabled and the + * PCT failed. + * - MLK_ERR_OUT_OF_MEMORY: If MLK_CONFIG_CUSTOM_ALLOC_FREE is + * used and an allocation via MLK_CUSTOM_ALLOC returned NULL. + * + * Specification: Implements @[FIPS203, Algorithm 16, ML-KEM.KeyGen_Internal] + * + **************************************************/ +MLK_EXTERNAL_API +MLK_MUST_CHECK_RETURN_VALUE +int mlk_kem_keypair_derand(uint8_t pk[MLKEM_INDCCA_PUBLICKEYBYTES], + uint8_t sk[MLKEM_INDCCA_SECRETKEYBYTES], + const uint8_t coins[2 * MLKEM_SYMBYTES], + MLK_CONFIG_CONTEXT_PARAMETER_TYPE context) +__contract__( + requires(memory_no_alias(pk, MLKEM_INDCCA_PUBLICKEYBYTES)) + requires(memory_no_alias(sk, MLKEM_INDCCA_SECRETKEYBYTES)) + requires(memory_no_alias(coins, 2 * MLKEM_SYMBYTES)) + assigns(memory_slice(pk, MLKEM_INDCCA_PUBLICKEYBYTES)) + assigns(memory_slice(sk, MLKEM_INDCCA_SECRETKEYBYTES)) + ensures(return_value == 0 || return_value == MLK_ERR_FAIL || + return_value == MLK_ERR_OUT_OF_MEMORY || + return_value == MLK_ERR_RNG_FAIL) +); + +/************************************************* + * Name: mlk_kem_keypair + * + * Description: Generates public and private key + * for CCA-secure ML-KEM key encapsulation mechanism + * + * Arguments: - uint8_t *pk: pointer to output public key + * (an already allocated array of MLKEM_INDCCA_PUBLICKEYBYTES + * bytes) + * - uint8_t *sk: pointer to output private key + * (an already allocated array of MLKEM_INDCCA_SECRETKEYBYTES + * bytes) + * + * Returns: - 0: On success + * - MLK_ERR_OUT_OF_MEMORY: If MLK_CONFIG_CUSTOM_ALLOC_FREE is + * used and an allocation via MLK_CUSTOM_ALLOC returned NULL. + * - MLK_ERR_RNG_FAIL: Random number generation failed. + * - MLK_ERR_FAIL: If MLK_CONFIG_KEYGEN_PCT is enabled and the + * PCT failed. + * + * Specification: Implements @[FIPS203, Algorithm 19, ML-KEM.KeyGen] + * + **************************************************/ +MLK_EXTERNAL_API +MLK_MUST_CHECK_RETURN_VALUE +int mlk_kem_keypair(uint8_t pk[MLKEM_INDCCA_PUBLICKEYBYTES], + uint8_t sk[MLKEM_INDCCA_SECRETKEYBYTES], + MLK_CONFIG_CONTEXT_PARAMETER_TYPE context) +__contract__( + requires(memory_no_alias(pk, MLKEM_INDCCA_PUBLICKEYBYTES)) + requires(memory_no_alias(sk, MLKEM_INDCCA_SECRETKEYBYTES)) + assigns(memory_slice(pk, MLKEM_INDCCA_PUBLICKEYBYTES)) + assigns(memory_slice(sk, MLKEM_INDCCA_SECRETKEYBYTES)) + ensures(return_value == 0 || return_value == MLK_ERR_FAIL || + return_value == MLK_ERR_OUT_OF_MEMORY || + return_value == MLK_ERR_RNG_FAIL) +); + +/************************************************* + * Name: mlk_kem_enc_derand + * + * Description: Generates cipher text and shared + * secret for given public key + * + * Arguments: - uint8_t *ct: pointer to output cipher text + * (an already allocated array of MLKEM_INDCCA_CIPHERTEXTBYTES + * bytes) + * - uint8_t *ss: pointer to output shared secret + * (an already allocated array of MLKEM_SSBYTES bytes) + * - const uint8_t *pk: pointer to input public key + * (an already allocated array of MLKEM_INDCCA_PUBLICKEYBYTES + * bytes) + * - const uint8_t *coins: pointer to input randomness + * (an already allocated array filled with MLKEM_SYMBYTES random + * bytes) + * + * Returns: - 0 on success + * - MLK_ERR_FAIL: If the 'modulus check' @[FIPS203, Section 7.2] + * for the public key fails. + * - MLK_ERR_OUT_OF_MEMORY: If MLK_CONFIG_CUSTOM_ALLOC_FREE is + * used and an allocation via MLK_CUSTOM_ALLOC returned NULL. + * + * Specification: Implements @[FIPS203, Algorithm 17, ML-KEM.Encaps_Internal] + * + **************************************************/ +MLK_EXTERNAL_API +MLK_MUST_CHECK_RETURN_VALUE +int mlk_kem_enc_derand(uint8_t ct[MLKEM_INDCCA_CIPHERTEXTBYTES], + uint8_t ss[MLKEM_SSBYTES], + const uint8_t pk[MLKEM_INDCCA_PUBLICKEYBYTES], + const uint8_t coins[MLKEM_SYMBYTES], + MLK_CONFIG_CONTEXT_PARAMETER_TYPE context) +__contract__( + requires(memory_no_alias(ct, MLKEM_INDCCA_CIPHERTEXTBYTES)) + requires(memory_no_alias(ss, MLKEM_SSBYTES)) + requires(memory_no_alias(pk, MLKEM_INDCCA_PUBLICKEYBYTES)) + requires(memory_no_alias(coins, MLKEM_SYMBYTES)) + assigns(memory_slice(ct, MLKEM_INDCCA_CIPHERTEXTBYTES)) + assigns(memory_slice(ss, MLKEM_SSBYTES)) + ensures(return_value == 0 || return_value == MLK_ERR_FAIL || + return_value == MLK_ERR_OUT_OF_MEMORY) +); + +/************************************************* + * Name: mlk_kem_enc + * + * Description: Generates cipher text and shared + * secret for given public key + * + * Arguments: - uint8_t *ct: pointer to output cipher text + * (an already allocated array of MLKEM_INDCCA_CIPHERTEXTBYTES + * bytes) + * - uint8_t *ss: pointer to output shared secret + * (an already allocated array of MLKEM_SSBYTES bytes) + * - const uint8_t *pk: pointer to input public key + * (an already allocated array of MLKEM_INDCCA_PUBLICKEYBYTES + * bytes) + * + * Returns: - 0 on success + * - MLK_ERR_OUT_OF_MEMORY: If MLK_CONFIG_CUSTOM_ALLOC_FREE is + * used and an allocation via MLK_CUSTOM_ALLOC returned NULL. + * - MLK_ERR_RNG_FAIL: Random number generation failed. + * - MLK_ERR_FAIL: If the 'modulus check' @[FIPS203, Section 7.2] + * for the public key fails. + * + * Specification: Implements @[FIPS203, Algorithm 20, ML-KEM.Encaps] + * + **************************************************/ +MLK_EXTERNAL_API +MLK_MUST_CHECK_RETURN_VALUE +int mlk_kem_enc(uint8_t ct[MLKEM_INDCCA_CIPHERTEXTBYTES], + uint8_t ss[MLKEM_SSBYTES], + const uint8_t pk[MLKEM_INDCCA_PUBLICKEYBYTES], + MLK_CONFIG_CONTEXT_PARAMETER_TYPE context) +__contract__( + requires(memory_no_alias(ct, MLKEM_INDCCA_CIPHERTEXTBYTES)) + requires(memory_no_alias(ss, MLKEM_SSBYTES)) + requires(memory_no_alias(pk, MLKEM_INDCCA_PUBLICKEYBYTES)) + assigns(memory_slice(ct, MLKEM_INDCCA_CIPHERTEXTBYTES)) + assigns(memory_slice(ss, MLKEM_SSBYTES)) + ensures(return_value == 0 || return_value == MLK_ERR_FAIL || + return_value == MLK_ERR_OUT_OF_MEMORY || + return_value == MLK_ERR_RNG_FAIL) +); + +/************************************************* + * Name: mlk_kem_dec + * + * Description: Generates shared secret for given + * cipher text and private key + * + * Arguments: - uint8_t *ss: pointer to output shared secret + * (an already allocated array of MLKEM_SSBYTES bytes) + * - const uint8_t *ct: pointer to input cipher text + * (an already allocated array of MLKEM_INDCCA_CIPHERTEXTBYTES + * bytes) + * - const uint8_t *sk: pointer to input private key + * (an already allocated array of MLKEM_INDCCA_SECRETKEYBYTES + * bytes) + * + * Returns: - 0 on success + * - MLK_ERR_FAIL: If the 'hash check' @[FIPS203, Section 7.3] + * for the secret key fails. + * - MLK_ERR_OUT_OF_MEMORY: If MLK_CONFIG_CUSTOM_ALLOC_FREE is + * used and an allocation via MLK_CUSTOM_ALLOC returned NULL. + * + * Specification: Implements @[FIPS203, Algorithm 21, ML-KEM.Decaps] + * + **************************************************/ +MLK_EXTERNAL_API +MLK_MUST_CHECK_RETURN_VALUE +int mlk_kem_dec(uint8_t ss[MLKEM_SSBYTES], + const uint8_t ct[MLKEM_INDCCA_CIPHERTEXTBYTES], + const uint8_t sk[MLKEM_INDCCA_SECRETKEYBYTES], + MLK_CONFIG_CONTEXT_PARAMETER_TYPE context) +__contract__( + requires(memory_no_alias(ss, MLKEM_SSBYTES)) + requires(memory_no_alias(ct, MLKEM_INDCCA_CIPHERTEXTBYTES)) + requires(memory_no_alias(sk, MLKEM_INDCCA_SECRETKEYBYTES)) + assigns(memory_slice(ss, MLKEM_SSBYTES)) + ensures(return_value == 0 || return_value == MLK_ERR_FAIL || + return_value == MLK_ERR_OUT_OF_MEMORY) +); + +#endif /* !MLK_KEM_H */ diff --git a/mlkem_native/src/params.h b/mlkem_native/src/params.h new file mode 100644 index 0000000..0459853 --- /dev/null +++ b/mlkem_native/src/params.h @@ -0,0 +1,76 @@ +/* + * Copyright (c) The mlkem-native project authors + * SPDX-License-Identifier: Apache-2.0 OR ISC OR MIT + */ +#ifndef MLK_PARAMS_H +#define MLK_PARAMS_H + +#if !defined(MLK_CONFIG_PARAMETER_SET) +#error MLK_CONFIG_PARAMETER_SET is not defined +#endif + +#if MLK_CONFIG_PARAMETER_SET == 512 +#define MLKEM_K 2 +#elif MLK_CONFIG_PARAMETER_SET == 768 +#define MLKEM_K 3 +#elif MLK_CONFIG_PARAMETER_SET == 1024 +#define MLKEM_K 4 +#else +#error Invalid value for MLK_CONFIG_PARAMETER_SET. Must be 512, 768, or 1024. +#endif + +#define MLKEM_N 256 +#define MLKEM_Q 3329 +#define MLKEM_Q_HALF ((MLKEM_Q + 1) / 2) /* 1665 */ +#define MLKEM_UINT12_LIMIT 4096 + +#define MLKEM_SYMBYTES 32 /* size in bytes of hashes, and seeds */ +#define MLKEM_SSBYTES 32 /* size in bytes of shared key */ + +#define MLKEM_POLYBYTES 384 +#define MLKEM_POLYVECBYTES (MLKEM_K * MLKEM_POLYBYTES) + +#define MLKEM_POLYCOMPRESSEDBYTES_D4 128 +#define MLKEM_POLYCOMPRESSEDBYTES_D5 160 +#define MLKEM_POLYCOMPRESSEDBYTES_D10 320 +#define MLKEM_POLYCOMPRESSEDBYTES_D11 352 + +#if MLKEM_K == 2 +#define MLKEM_ETA1 3 +#define MLKEM_DU 10 +#define MLKEM_DV 4 +#define MLKEM_POLYCOMPRESSEDBYTES_DV MLKEM_POLYCOMPRESSEDBYTES_D4 +#define MLKEM_POLYCOMPRESSEDBYTES_DU MLKEM_POLYCOMPRESSEDBYTES_D10 +#define MLKEM_POLYVECCOMPRESSEDBYTES_DU (MLKEM_K * MLKEM_POLYCOMPRESSEDBYTES_DU) +#elif MLKEM_K == 3 +#define MLKEM_ETA1 2 +#define MLKEM_DU 10 +#define MLKEM_DV 4 +#define MLKEM_POLYCOMPRESSEDBYTES_DV MLKEM_POLYCOMPRESSEDBYTES_D4 +#define MLKEM_POLYCOMPRESSEDBYTES_DU MLKEM_POLYCOMPRESSEDBYTES_D10 +#define MLKEM_POLYVECCOMPRESSEDBYTES_DU (MLKEM_K * MLKEM_POLYCOMPRESSEDBYTES_DU) +#elif MLKEM_K == 4 +#define MLKEM_ETA1 2 +#define MLKEM_DU 11 +#define MLKEM_DV 5 +#define MLKEM_POLYCOMPRESSEDBYTES_DV MLKEM_POLYCOMPRESSEDBYTES_D5 +#define MLKEM_POLYCOMPRESSEDBYTES_DU MLKEM_POLYCOMPRESSEDBYTES_D11 +#define MLKEM_POLYVECCOMPRESSEDBYTES_DU (MLKEM_K * MLKEM_POLYCOMPRESSEDBYTES_DU) +#endif /* MLKEM_K == 4 */ + +#define MLKEM_ETA2 2 + +#define MLKEM_INDCPA_MSGBYTES (MLKEM_SYMBYTES) +#define MLKEM_INDCPA_PUBLICKEYBYTES (MLKEM_POLYVECBYTES + MLKEM_SYMBYTES) +#define MLKEM_INDCPA_SECRETKEYBYTES (MLKEM_POLYVECBYTES) +#define MLKEM_INDCPA_BYTES \ + (MLKEM_POLYVECCOMPRESSEDBYTES_DU + MLKEM_POLYCOMPRESSEDBYTES_DV) + +#define MLKEM_INDCCA_PUBLICKEYBYTES (MLKEM_INDCPA_PUBLICKEYBYTES) +/* 32 bytes of additional space to save H(pk) */ +#define MLKEM_INDCCA_SECRETKEYBYTES \ + (MLKEM_INDCPA_SECRETKEYBYTES + MLKEM_INDCPA_PUBLICKEYBYTES + \ + 2 * MLKEM_SYMBYTES) +#define MLKEM_INDCCA_CIPHERTEXTBYTES (MLKEM_INDCPA_BYTES) + +#endif /* !MLK_PARAMS_H */ diff --git a/mlkem_native/src/poly.c b/mlkem_native/src/poly.c new file mode 100644 index 0000000..564d5d7 --- /dev/null +++ b/mlkem_native/src/poly.c @@ -0,0 +1,572 @@ +/* + * Copyright (c) The mlkem-native project authors + * SPDX-License-Identifier: Apache-2.0 OR ISC OR MIT + */ + +/* References + * ========== + * + * - [NeonNTT] + * Neon NTT: Faster Dilithium, Kyber, and Saber on Cortex-A72 and Apple M1 + * Becker, Hwang, Kannwischer, Yang, Yang + * https://eprint.iacr.org/2021/986 + * + * - [REF] + * CRYSTALS-Kyber C reference implementation + * Bos, Ducas, Kiltz, Lepoint, Lyubashevsky, Schanck, Schwabe, Seiler, StehlΓ© + * https://github.com/pq-crystals/kyber/tree/main/ref + */ + +#include "common.h" +#if !defined(MLK_CONFIG_MULTILEVEL_NO_SHARED) + + +#include "cbmc.h" +#include "debug.h" +#include "poly.h" +#include "sampling.h" +#include "symmetric.h" +#include "verify.h" + +/************************************************* + * Name: mlk_fqmul + * + * Description: Montgomery multiplication modulo MLKEM_Q + * + * Arguments: - int16_t a: first factor + * Can be any int16_t. + * - int16_t b: second factor. + * Must be signed canonical (abs value <(MLKEM_Q+1)/2) + * + * Returns 16-bit integer congruent to a*b*R^{-1} mod MLKEM_Q, and + * smaller than MLKEM_Q in absolute value. + * + **************************************************/ + +/* Reference: `fqmul()` in the reference implementation @[REF]. */ +static MLK_INLINE int16_t mlk_fqmul(int16_t a, int16_t b) +__contract__( + requires(b > -MLKEM_Q_HALF && b < MLKEM_Q_HALF) + ensures(return_value > -MLKEM_Q && return_value < MLKEM_Q) +) +{ + int16_t res; + mlk_assert_abs_bound(&b, 1, MLKEM_Q_HALF); + + res = mlk_montgomery_reduce((int32_t)a * (int32_t)b); + /* Bounds: + * |res| <= ceil(|a| * |b| / 2^16) + (MLKEM_Q + 1) / 2 + * <= ceil(2^15 * ((MLKEM_Q - 1)/2) / 2^16) + (MLKEM_Q + 1) / 2 + * <= ceil((MLKEM_Q - 1) / 4) + (MLKEM_Q + 1) / 2 + * < MLKEM_Q + */ + + mlk_assert_abs_bound(&res, 1, MLKEM_Q); + return res; +} + +/************************************************* + * Name: mlk_barrett_reduce + * + * Description: Barrett reduction; given a 16-bit integer a, computes + * centered representative congruent to a mod q in + * {-(q-1)/2,...,(q-1)/2} + * + * Arguments: - int16_t a: input integer to be reduced + * + * Returns: integer in {-(q-1)/2,...,(q-1)/2} congruent to a modulo q. + * + **************************************************/ + +/* Reference: `barrett_reduce()` in the reference implementation @[REF]. */ +static MLK_INLINE int16_t mlk_barrett_reduce(int16_t a) +__contract__( + ensures(return_value > -MLKEM_Q_HALF && return_value < MLKEM_Q_HALF) +) +{ + /* Barrett reduction approximates + * ``` + * round(a/MLKEM_Q) + * = round(a*(2^N/MLKEM_Q))/2^N) + * ~= round(a*round(2^N/MLKEM_Q)/2^N) + * ``` + * Here, we pick N=26. + */ + const int32_t magic = 20159; /* check-magic: 20159 == round(2^26 / MLKEM_Q) */ + + /* + * PORTABILITY: Right-shift on a signed integer is + * implementation-defined for negative left argument. + * Here, we assume it's sign-preserving "arithmetic" shift right. + * See (C99 6.5.7 (5)) + */ + const int32_t t = (magic * a + ((int32_t)1 << 25)) >> 26; + + /* + * t is in -10 .. +10, so we need 32-bit math to + * evaluate t * MLKEM_Q and the subsequent subtraction + */ + int16_t res = (int16_t)(a - t * MLKEM_Q); + + mlk_assert_abs_bound(&res, 1, MLKEM_Q_HALF); + return res; +} + +/* Reference: `poly_tomont()` in the reference implementation @[REF]. */ +MLK_STATIC_TESTABLE void mlk_poly_tomont_c(mlk_poly *r) +__contract__( + requires(memory_no_alias(r, sizeof(mlk_poly))) + assigns(memory_slice(r, sizeof(mlk_poly))) + ensures(array_abs_bound(r->coeffs, 0, MLKEM_N, MLKEM_Q)) +) +{ + unsigned i; + const int16_t f = 1353; /* check-magic: 1353 == signed_mod(2^32, MLKEM_Q) */ + for (i = 0; i < MLKEM_N; i++) + __loop__( + invariant(i <= MLKEM_N) + invariant(array_abs_bound(r->coeffs, 0, i, MLKEM_Q))) + { + r->coeffs[i] = mlk_fqmul(r->coeffs[i], f); + } + + mlk_assert_abs_bound(r, MLKEM_N, MLKEM_Q); +} + +MLK_INTERNAL_API +void mlk_poly_tomont(mlk_poly *r) +{ +#if defined(MLK_USE_NATIVE_POLY_TOMONT) + int ret; + ret = mlk_poly_tomont_native(r->coeffs); + if (ret == MLK_NATIVE_FUNC_SUCCESS) + { + mlk_assert_abs_bound(r, MLKEM_N, MLKEM_Q); + return; + } +#endif /* MLK_USE_NATIVE_POLY_TOMONT */ + + mlk_poly_tomont_c(r); +} + +/************************************************************ + * Name: mlk_scalar_signed_to_unsigned_q + * + * Description: Constant-time conversion of signed representatives + * modulo MLKEM_Q within range (-(MLKEM_Q-1) .. (MLKEM_Q-1)) + * into unsigned representatives within range (0..(MLKEM_Q-1)). + * + * Arguments: c: signed coefficient to be converted + * + ************************************************************/ + +/* Reference: Not present in the reference implementation @[REF]. + * - Used here to implement different semantics of `poly_reduce()`; + * see below. in the reference implementation @[REF], this logic is + * part of all compression functions (see `compress.c`). */ +static MLK_INLINE int16_t mlk_scalar_signed_to_unsigned_q(int16_t c) +__contract__( + requires(c > -MLKEM_Q && c < MLKEM_Q) + ensures(return_value >= 0 && return_value < MLKEM_Q) + ensures(return_value == (int32_t)c + (((int32_t)c < 0) * MLKEM_Q))) +{ + mlk_assert_abs_bound(&c, 1, MLKEM_Q); + + /* Add MLKEM_Q if c is negative, but in constant time. + * + * Note that c + MLKEM_Q does not overflow in int16_t, + * so the cast to uint16_t is safe. */ + c = mlk_ct_sel_int16((int16_t)(c + MLKEM_Q), c, mlk_ct_cmask_neg_i16(c)); + + mlk_assert_bound(&c, 1, 0, MLKEM_Q); + return c; +} + +/* Reference: `poly_reduce()` in the reference implementation @[REF] + * - We use _unsigned_ canonical outputs, while the reference + * implementation uses _signed_ canonical outputs. + * Accordingly, we need a conditional addition of MLKEM_Q + * here to go from signed to unsigned representatives. + * This conditional addition is then dropped from all + * polynomial compression functions instead (see `compress.c`). */ +MLK_STATIC_TESTABLE void mlk_poly_reduce_c(mlk_poly *r) +__contract__( + requires(memory_no_alias(r, sizeof(mlk_poly))) + assigns(memory_slice(r, sizeof(mlk_poly))) + ensures(array_bound(r->coeffs, 0, MLKEM_N, 0, MLKEM_Q)) +) +{ + unsigned i; + + for (i = 0; i < MLKEM_N; i++) + __loop__( + invariant(i <= MLKEM_N) + invariant(array_bound(r->coeffs, 0, i, 0, MLKEM_Q))) + { + /* Barrett reduction, giving signed canonical representative */ + int16_t t = mlk_barrett_reduce(r->coeffs[i]); + /* Conditional addition to get unsigned canonical representative */ + r->coeffs[i] = mlk_scalar_signed_to_unsigned_q(t); + } + + mlk_assert_bound(r, MLKEM_N, 0, MLKEM_Q); +} + +MLK_INTERNAL_API +void mlk_poly_reduce(mlk_poly *r) +{ +#if defined(MLK_USE_NATIVE_POLY_REDUCE) + int ret; + ret = mlk_poly_reduce_native(r->coeffs); + if (ret == MLK_NATIVE_FUNC_SUCCESS) + { + mlk_assert_bound(r, MLKEM_N, 0, MLKEM_Q); + return; + } +#endif /* MLK_USE_NATIVE_POLY_REDUCE */ + + mlk_poly_reduce_c(r); +} + +/* Reference: `poly_add()` in the reference implementation @[REF]. + * - We use destructive version (output=first input) to avoid + * reasoning about aliasing in the CBMC specification */ +MLK_INTERNAL_API +void mlk_poly_add(mlk_poly *r, const mlk_poly *b) +{ + unsigned i; + for (i = 0; i < MLKEM_N; i++) + __loop__( + invariant(i <= MLKEM_N) + invariant(forall(k0, i, MLKEM_N, r->coeffs[k0] == loop_entry(*r).coeffs[k0])) + invariant(forall(k1, 0, i, r->coeffs[k1] == loop_entry(*r).coeffs[k1] + b->coeffs[k1]))) + { + /* The preconditions imply that the addition stays within int16_t. */ + r->coeffs[i] = (int16_t)(r->coeffs[i] + b->coeffs[i]); + } +} + +/* Reference: `poly_sub()` in the reference implementation @[REF]. + * - We use destructive version (output=first input) to avoid + * reasoning about aliasing in the CBMC specification */ +MLK_INTERNAL_API +void mlk_poly_sub(mlk_poly *r, const mlk_poly *b) +{ + unsigned i; + for (i = 0; i < MLKEM_N; i++) + __loop__( + invariant(i <= MLKEM_N) + invariant(forall(k0, i, MLKEM_N, r->coeffs[k0] == loop_entry(*r).coeffs[k0])) + invariant(forall(k1, 0, i, r->coeffs[k1] == loop_entry(*r).coeffs[k1] - b->coeffs[k1]))) + { + /* The preconditions imply that the subtraction stays within int16_t. */ + r->coeffs[i] = (int16_t)(r->coeffs[i] - b->coeffs[i]); + } +} + +#include "zetas.inc" + +/* Reference: Does not exist in the reference implementation @[REF]. + * - The reference implementation does not use a + * multiplication cache ('mulcache'). This idea originates + * from @[NeonNTT] and is used at the C level here. */ +MLK_STATIC_TESTABLE void mlk_poly_mulcache_compute_c(mlk_poly_mulcache *x, + const mlk_poly *a) +__contract__( + requires(memory_no_alias(x, sizeof(mlk_poly_mulcache))) + requires(memory_no_alias(a, sizeof(mlk_poly))) + assigns(memory_slice(x, sizeof(mlk_poly_mulcache))) +) +{ + unsigned i; + for (i = 0; i < MLKEM_N / 4; i++) + __loop__( + invariant(i <= MLKEM_N / 4) + invariant(array_abs_bound(x->coeffs, 0, 2 * i, MLKEM_Q))) + { + x->coeffs[2 * i + 0] = mlk_fqmul(a->coeffs[4 * i + 1], mlk_zetas[64 + i]); + /* The values in zeta table are <= MLKEM_Q in absolute value, + * so the negation in int16_t is safe. */ + x->coeffs[2 * i + 1] = + mlk_fqmul(a->coeffs[4 * i + 3], (int16_t)(-mlk_zetas[64 + i])); + } + + /* + * This bound is true for the C implementation, but not needed + * in the higher level bounds reasoning. It is thus omitted + * from the spec to not unnecessarily constrain native + * implementations, but checked here nonetheless. + */ + mlk_assert_abs_bound(x, MLKEM_N / 2, MLKEM_Q); +} + +MLK_INTERNAL_API +void mlk_poly_mulcache_compute(mlk_poly_mulcache *x, const mlk_poly *a) +{ +#if defined(MLK_USE_NATIVE_POLY_MULCACHE_COMPUTE) + int ret; + ret = mlk_poly_mulcache_compute_native(x->coeffs, a->coeffs); + if (ret == MLK_NATIVE_FUNC_SUCCESS) + { + return; + } +#endif /* MLK_USE_NATIVE_POLY_MULCACHE_COMPUTE */ + + mlk_poly_mulcache_compute_c(x, a); +} + +/* + * Computes a block CT butterflies with a fixed twiddle factor, + * using Montgomery multiplication. + * Parameters: + * - r: Pointer to base of polynomial (_not_ the base of butterfly block) + * - root: Twiddle factor to use for the butterfly. This must be in + * Montgomery form and signed canonical. + * - start: Offset to the beginning of the butterfly block + * - len: Index difference between coefficients subject to a butterfly + * - bound: Ghost variable describing coefficient bound: Prior to `start`, + * coefficients must be bound by `bound + MLKEM_Q`. Post `start`, + * they must be bound by `bound`. + * When this function returns, output coefficients in the index range + * [start, start+2*len) have bound bumped to `bound + MLKEM_Q`. + * Example: + * - start=8, len=4 + * This would compute the following four butterflies + * 8 -- 12 + * 9 -- 13 + * 10 -- 14 + * 11 -- 15 + * - start=4, len=2 + * This would compute the following two butterflies + * 4 -- 6 + * 5 -- 7 + */ + +/* Reference: Embedded in `ntt()` in the reference implementation @[REF]. */ +static void mlk_ntt_butterfly_block(int16_t r[MLKEM_N], int16_t zeta, + unsigned start, unsigned len, + unsigned bound) +__contract__( + requires(start < MLKEM_N) + requires(1 <= len && len <= MLKEM_N / 2 && start + 2 * len <= MLKEM_N) + requires(0 <= bound && bound < INT16_MAX - MLKEM_Q) + requires(-MLKEM_Q_HALF < zeta && zeta < MLKEM_Q_HALF) + requires(memory_no_alias(r, sizeof(int16_t) * MLKEM_N)) + requires(array_abs_bound(r, 0, start, bound + MLKEM_Q)) + requires(array_abs_bound(r, start, MLKEM_N, bound)) + assigns(memory_slice(r, sizeof(int16_t) * MLKEM_N)) + ensures(array_abs_bound(r, 0, start + 2*len, bound + MLKEM_Q)) + ensures(array_abs_bound(r, start + 2 * len, MLKEM_N, bound))) +{ + /* `bound` is a ghost variable only needed in the CBMC specification */ + unsigned j; + ((void)bound); + for (j = start; j < start + len; j++) + __loop__( + invariant(start <= j && j <= start + len) + /* + * Coefficients are updated in strided pairs, so the bounds for the + * intermediate states alternate twice between the old and new bound + */ + invariant(array_abs_bound(r, 0, j, bound + MLKEM_Q)) + invariant(array_abs_bound(r, j, start + len, bound)) + invariant(array_abs_bound(r, start + len, j + len, bound + MLKEM_Q)) + invariant(array_abs_bound(r, j + len, MLKEM_N, bound))) + { + int16_t t; + t = mlk_fqmul(r[j + len], zeta); + /* The precondition implies that the arithmetic does not overflow. */ + r[j + len] = (int16_t)(r[j] - t); + r[j] = (int16_t)(r[j] + t); + } +} + +/* + * Compute one layer of forward NTT + * Parameters: + * - r: Pointer to base of polynomial + * - layer: Variable indicating which layer is being applied. + */ + +/* Reference: Embedded in `ntt()` in the reference implementation @[REF]. */ +static void mlk_ntt_layer(int16_t r[MLKEM_N], unsigned layer) +__contract__( + requires(memory_no_alias(r, sizeof(int16_t) * MLKEM_N)) + requires(1 <= layer && layer <= 7) + requires(array_abs_bound(r, 0, MLKEM_N, layer * MLKEM_Q)) + assigns(memory_slice(r, sizeof(int16_t) * MLKEM_N)) + ensures(array_abs_bound(r, 0, MLKEM_N, (layer + 1) * MLKEM_Q))) +{ + unsigned start, k, len; + /* Twiddle factors for layer n are at indices 2^(n-1)..2^n-1. */ + k = 1u << (layer - 1); + len = (unsigned)MLKEM_N >> layer; + for (start = 0; start < MLKEM_N; start += 2 * len) + __loop__( + invariant(start < MLKEM_N + 2 * len) + invariant(k <= MLKEM_N / 2 && 2 * len * k == start + MLKEM_N) + invariant(array_abs_bound(r, 0, start, layer * MLKEM_Q + MLKEM_Q)) + invariant(array_abs_bound(r, start, MLKEM_N, layer * MLKEM_Q))) + { + int16_t zeta = mlk_zetas[k++]; + mlk_ntt_butterfly_block(r, zeta, start, len, layer * MLKEM_Q); + } +} + +/* + * Compute full forward NTT + * NOTE: This particular implementation satisfies a much tighter + * bound on the output coefficients (5*q) than the contractual one (8*q), + * but this is not needed in the calling code. Should we change the + * base multiplication strategy to require smaller NTT output bounds, + * the proof may need strengthening. + */ + +/* Reference: `ntt()` in the reference implementation @[REF]. + * - Iterate over `layer` instead of `len` in the outer loop + * to simplify computation of zeta index. */ +MLK_STATIC_TESTABLE void mlk_poly_ntt_c(mlk_poly *p) +__contract__( + requires(memory_no_alias(p, sizeof(mlk_poly))) + requires(array_abs_bound(p->coeffs, 0, MLKEM_N, MLKEM_Q)) + assigns(memory_slice(p, sizeof(mlk_poly))) + ensures(array_abs_bound(p->coeffs, 0, MLKEM_N, MLK_NTT_BOUND)) +) +{ + unsigned layer; + int16_t *r; + + mlk_assert_abs_bound(p, MLKEM_N, MLKEM_Q); + + r = p->coeffs; + + for (layer = 1; layer <= 7; layer++) + __loop__( + invariant(1 <= layer && layer <= 8) + invariant(array_abs_bound(r, 0, MLKEM_N, layer * MLKEM_Q))) + { + mlk_ntt_layer(r, layer); + } + + /* Check the stronger bound */ + mlk_assert_abs_bound(p, MLKEM_N, MLK_NTT_BOUND); +} + +MLK_INTERNAL_API +void mlk_poly_ntt(mlk_poly *p) +{ +#if defined(MLK_USE_NATIVE_NTT) + int ret; + mlk_assert_abs_bound(p, MLKEM_N, MLKEM_Q); + ret = mlk_ntt_native(p->coeffs); + if (ret == MLK_NATIVE_FUNC_SUCCESS) + { + mlk_assert_abs_bound(p, MLKEM_N, MLK_NTT_BOUND); + return; + } +#endif /* MLK_USE_NATIVE_NTT */ + + mlk_poly_ntt_c(p); +} + + +/* Compute one layer of inverse NTT */ + +/* Reference: Embedded into `invntt()` in the reference implementation @[REF] */ +static void mlk_invntt_layer(int16_t *r, unsigned layer) +__contract__( + requires(memory_no_alias(r, sizeof(int16_t) * MLKEM_N)) + requires(1 <= layer && layer <= 7) + requires(array_abs_bound(r, 0, MLKEM_N, MLKEM_Q)) + assigns(memory_slice(r, sizeof(int16_t) * MLKEM_N)) + ensures(array_abs_bound(r, 0, MLKEM_N, MLKEM_Q))) +{ + unsigned start, k, len; + len = (unsigned)MLKEM_N >> layer; + k = (1u << layer) - 1; + for (start = 0; start < MLKEM_N; start += 2 * len) + __loop__( + invariant(array_abs_bound(r, 0, MLKEM_N, MLKEM_Q)) + invariant(start <= MLKEM_N && k <= 127) + /* Normalised form of k == MLKEM_N / len - 1 - start / (2 * len) */ + invariant(2 * len * k + start == 2 * MLKEM_N - 2 * len)) + { + unsigned j; + int16_t zeta = mlk_zetas[k--]; + for (j = start; j < start + len; j++) + __loop__( + invariant(start <= j && j <= start + len) + invariant(start <= MLKEM_N && k <= 127) + invariant(array_abs_bound(r, 0, MLKEM_N, MLKEM_Q))) + { + int16_t t = r[j]; + /* The preconditions imply that the arithmetic does not overflow. */ + r[j] = mlk_barrett_reduce((int16_t)(t + r[j + len])); + r[j + len] = (int16_t)(r[j + len] - t); + r[j + len] = mlk_fqmul(r[j + len], zeta); + } + } +} + +/* Reference: `invntt()` in the reference implementation @[REF] + * - We normalize at the beginning of the inverse NTT, + * while the reference implementation normalizes at + * the end. This allows us to drop a call to `poly_reduce()` + * from the base multiplication. */ +MLK_STATIC_TESTABLE void mlk_poly_invntt_tomont_c(mlk_poly *p) +__contract__( + requires(memory_no_alias(p, sizeof(mlk_poly))) + assigns(memory_slice(p, sizeof(mlk_poly))) + ensures(array_abs_bound(p->coeffs, 0, MLKEM_N, MLK_INVNTT_BOUND)) +) +{ + unsigned j, layer; + const int16_t f = 1441; /* check-magic: 1441 == pow(2,32 - 7,MLKEM_Q) */ + int16_t *r = p->coeffs; + + /* + * Scale input polynomial to account for Montgomery factor + * and NTT twist. This also brings coefficients down to + * absolute value < MLKEM_Q. + */ + for (j = 0; j < MLKEM_N; j++) + __loop__( + invariant(j <= MLKEM_N) + invariant(array_abs_bound(r, 0, j, MLKEM_Q))) + { + r[j] = mlk_fqmul(r[j], f); + } + + /* Run the invNTT layers */ + for (layer = 7; layer > 0; layer--) + __loop__( + invariant(0 <= layer && layer < 8) + invariant(array_abs_bound(r, 0, MLKEM_N, MLKEM_Q))) + { + mlk_invntt_layer(r, layer); + } + + mlk_assert_abs_bound(p, MLKEM_N, MLK_INVNTT_BOUND); +} + +MLK_INTERNAL_API +void mlk_poly_invntt_tomont(mlk_poly *p) +{ +#if defined(MLK_USE_NATIVE_INTT) + int ret; + ret = mlk_intt_native(p->coeffs); + if (ret == MLK_NATIVE_FUNC_SUCCESS) + { + mlk_assert_abs_bound(p, MLKEM_N, MLK_INVNTT_BOUND); + return; + } +#endif /* MLK_USE_NATIVE_INTT */ + + mlk_poly_invntt_tomont_c(p); +} + +#else /* !MLK_CONFIG_MULTILEVEL_NO_SHARED */ + +MLK_EMPTY_CU(mlk_poly) + +#endif /* MLK_CONFIG_MULTILEVEL_NO_SHARED */ diff --git a/mlkem_native/src/poly.h b/mlkem_native/src/poly.h new file mode 100644 index 0000000..587062c --- /dev/null +++ b/mlkem_native/src/poly.h @@ -0,0 +1,317 @@ +/* + * Copyright (c) The mlkem-native project authors + * SPDX-License-Identifier: Apache-2.0 OR ISC OR MIT + */ + +/* References + * ========== + * + * - [FIPS203] + * FIPS 203 Module-Lattice-Based Key-Encapsulation Mechanism Standard + * National Institute of Standards and Technology + * https://csrc.nist.gov/pubs/fips/203/final + */ + +#ifndef MLK_POLY_H +#define MLK_POLY_H + + +#include "cbmc.h" +#include "common.h" +#include "debug.h" +#include "verify.h" + +/* Absolute exclusive upper bound for the output of the inverse NTT */ +#define MLK_INVNTT_BOUND (8 * MLKEM_Q) + +/* Absolute exclusive upper bound for the output of the forward NTT */ +#define MLK_NTT_BOUND (8 * MLKEM_Q) + +/* + * Elements of R_q = Z_q[X]/(X^n + 1). Represents polynomial + * coeffs[0] + X*coeffs[1] + X^2*coeffs[2] + ... + X^{n-1}*coeffs[n-1] + */ +typedef struct +{ + int16_t coeffs[MLKEM_N]; +} MLK_ALIGN mlk_poly; + +/* + * INTERNAL presentation of precomputed data speeding up + * the base multiplication of two polynomials in NTT domain. + */ +typedef struct +{ + int16_t coeffs[MLKEM_N >> 1]; +} MLK_ALIGN mlk_poly_mulcache; + +/************************************************* + * Name: mlk_montgomery_reduce + * + * Description: Generic Montgomery reduction; given a 32-bit integer a, computes + * 16-bit integer congruent to a * R^-1 mod q, where R=2^16 + * + * Arguments: - int32_t a: input integer to be reduced, of absolute value + * smaller or equal to INT32_MAX - 2^15 * MLKEM_Q. + * + * Returns: integer congruent to a * R^-1 modulo q, with absolute value + * <= ceil(|a| / 2^16) + (MLKEM_Q + 1)/2 + * + **************************************************/ +static MLK_ALWAYS_INLINE int16_t mlk_montgomery_reduce(int32_t a) +__contract__( + requires(a < +(INT32_MAX - (((int32_t)1 << 15) * MLKEM_Q)) && + a > -(INT32_MAX - (((int32_t)1 << 15) * MLKEM_Q))) + /* We don't attempt to express an input-dependent output bound + * as the post-condition here. There are two call-sites for this + * function: + * - The base multiplication: Here, we need no output bound. + * - mlk_fqmul: Here, we inline this function and prove another spec + * for mlk_fqmul which does have a post-condition bound. */ +) +{ + /* check-magic: 62209 == unsigned_mod(pow(MLKEM_Q, -1, 2^16), 2^16) */ + const uint32_t QINV = 62209; + + /* Compute a*q^{-1} mod 2^16 in unsigned representatives. */ + const uint16_t a_reduced = mlk_cast_int32_to_uint16(a); + const uint16_t a_inverted = (a_reduced * QINV) & UINT16_MAX; + + /* Lift to signed canonical representative mod 2^16. */ + const int16_t t = mlk_cast_uint16_to_int16(a_inverted); + + int32_t r; + + mlk_assert(a < +(INT32_MAX - (((int32_t)1 << 15) * MLKEM_Q)) && + a > -(INT32_MAX - (((int32_t)1 << 15) * MLKEM_Q))); + + r = a - ((int32_t)t * MLKEM_Q); + + /* + * PORTABILITY: Right-shift on a signed integer is, strictly-speaking, + * implementation-defined for negative left argument. Here, + * we assume it's sign-preserving "arithmetic" shift right. (C99 6.5.7 (5)) + */ + r = r >> 16; + /* Bounds: |r >> 16| <= ceil(|r| / 2^16) + * <= ceil(|a| / 2^16 + MLKEM_Q / 2) + * <= ceil(|a| / 2^16) + (MLKEM_Q + 1) / 2 + * + * (Note that |a >> n| = ceil(|a| / 2^16) for negative a) + */ + return (int16_t)r; +} + +#define mlk_poly_tomont MLK_NAMESPACE(poly_tomont) +/************************************************* + * Name: mlk_poly_tomont + * + * Description: Inplace conversion of all coefficients of a polynomial + * from normal domain to Montgomery domain + * + * Bounds: Output < q in absolute value. + * + * Arguments: - mlk_poly *r: pointer to input/output polynomial + * + * Specification: Internal normalization required in `mlk_indcpa_keypair_derand` + * as part of matrix-vector multiplication + * @[FIPS203, Algorithm 13, K-PKE.KeyGen, L18]. + * + **************************************************/ +MLK_INTERNAL_API +void mlk_poly_tomont(mlk_poly *r) +__contract__( + requires(memory_no_alias(r, sizeof(mlk_poly))) + assigns(memory_slice(r, sizeof(mlk_poly))) + ensures(array_abs_bound(r->coeffs, 0, MLKEM_N, MLKEM_Q)) +); + +#define mlk_poly_mulcache_compute MLK_NAMESPACE(poly_mulcache_compute) +/************************************************************ + * Name: mlk_poly_mulcache_compute + * + * Description: Computes the mulcache for a polynomial in NTT domain + * + * The mulcache of a degree-2 polynomial b := b0 + b1*X + * in Fq[X]/(X^2-zeta) is the value b1*zeta, needed when + * computing products of b in Fq[X]/(X^2-zeta). + * + * The mulcache of a polynomial in NTT domain -- which is + * a 128-tuple of degree-2 polynomials in Fq[X]/(X^2-zeta), + * for varying zeta, is the 128-tuple of mulcaches of those + * polynomials. + * + * Arguments: - x: Pointer to mulcache to be populated + * - a: Pointer to input polynomial + * + * Specification: + * - Caches `b_1 * \gamma` in @[FIPS203, Algorithm 12, BaseCaseMultiply, L1] + * + ************************************************************/ +/* + * NOTE: The default C implementation of this function populates + * the mulcache with values in (-q,q), but this is not needed for the + * higher level safety proofs, and thus not part of the spec. + */ +MLK_INTERNAL_API +void mlk_poly_mulcache_compute(mlk_poly_mulcache *x, const mlk_poly *a) +__contract__( + requires(memory_no_alias(x, sizeof(mlk_poly_mulcache))) + requires(memory_no_alias(a, sizeof(mlk_poly))) + assigns(memory_slice(x, sizeof(mlk_poly_mulcache))) +); + +#define mlk_poly_reduce MLK_NAMESPACE(poly_reduce) +/************************************************* + * Name: mlk_poly_reduce + * + * Description: Converts polynomial to _unsigned canonical_ representatives. + * + * The input coefficients can be arbitrary integers in int16_t. + * The output coefficients are in [0,1,...,MLKEM_Q-1]. + * + * Arguments: - mlk_poly *r: pointer to input/output polynomial + * + * Specification: Normalizes on unsigned canoncial representatives + * ahead of calling @[FIPS203, Compress_d, Eq (4.7)]. + * This is not made explicit in FIPS 203. + * + **************************************************/ +/* + * NOTE: The semantics of mlk_poly_reduce() is different in + * the reference implementation, which requires + * signed canonical output data. Unsigned canonical + * outputs are better suited to the only remaining + * use of mlk_poly_reduce() in the context of (de)serialization. + */ +MLK_INTERNAL_API +void mlk_poly_reduce(mlk_poly *r) +__contract__( + requires(memory_no_alias(r, sizeof(mlk_poly))) + assigns(memory_slice(r, sizeof(mlk_poly))) + ensures(array_bound(r->coeffs, 0, MLKEM_N, 0, MLKEM_Q)) +); + +#define mlk_poly_add MLK_NAMESPACE(poly_add) +/************************************************************ + * Name: mlk_poly_add + * + * Description: Adds two polynomials in place + * + * Arguments: - r: Pointer to input-output polynomial to be added to. + * - b: Pointer to input polynomial that should be added + * to r. Must be disjoint from r. + * + * The coefficients of r and b must be so that the addition does + * not overflow. Otherwise, the behaviour of this function is undefined. + * + * Specification: + * - @[FIPS203, 2.4.5, Arithmetic With Polynomials and NTT Representations] + * - Used in @[FIPS203, Algorithm 14 (K-PKE.Encrypt), L21] + * + ************************************************************/ +/* + * NOTE: The reference implementation uses a 3-argument mlk_poly_add. + * We specialize to the accumulator form to avoid reasoning about aliasing. + */ +MLK_INTERNAL_API +void mlk_poly_add(mlk_poly *r, const mlk_poly *b) +__contract__( + requires(memory_no_alias(r, sizeof(mlk_poly))) + requires(memory_no_alias(b, sizeof(mlk_poly))) + requires(forall(k0, 0, MLKEM_N, (int32_t) r->coeffs[k0] + b->coeffs[k0] <= INT16_MAX)) + requires(forall(k1, 0, MLKEM_N, (int32_t) r->coeffs[k1] + b->coeffs[k1] >= INT16_MIN)) + ensures(forall(k, 0, MLKEM_N, r->coeffs[k] == old(*r).coeffs[k] + b->coeffs[k])) + assigns(memory_slice(r, sizeof(mlk_poly))) +); + +#define mlk_poly_sub MLK_NAMESPACE(poly_sub) +/************************************************* + * Name: mlk_poly_sub + * + * Description: Subtract two polynomials; no modular reduction is performed + * + * Arguments: - mlk_poly *r: Pointer to input-output polynomial to be added to. + * - const mlk_poly *b: Pointer to second input polynomial + * + * Specification: + * - @[FIPS203, 2.4.5, Arithmetic With Polynomials and NTT Representations] + * - Used in @[FIPS203, Algorithm 15, K-PKE.Decrypt, L6] + * + **************************************************/ +/* + * NOTE: The reference implementation uses a 3-argument mlk_poly_sub. + * We specialize to the accumulator form to avoid reasoning about aliasing. + */ +MLK_INTERNAL_API +void mlk_poly_sub(mlk_poly *r, const mlk_poly *b) +__contract__( + requires(memory_no_alias(r, sizeof(mlk_poly))) + requires(memory_no_alias(b, sizeof(mlk_poly))) + requires(forall(k0, 0, MLKEM_N, (int32_t) r->coeffs[k0] - b->coeffs[k0] <= INT16_MAX)) + requires(forall(k1, 0, MLKEM_N, (int32_t) r->coeffs[k1] - b->coeffs[k1] >= INT16_MIN)) + ensures(forall(k, 0, MLKEM_N, r->coeffs[k] == old(*r).coeffs[k] - b->coeffs[k])) + assigns(memory_slice(r, sizeof(mlk_poly))) +); + +#define mlk_poly_ntt MLK_NAMESPACE(poly_ntt) +/************************************************* + * Name: mlk_poly_ntt + * + * Description: Computes negacyclic number-theoretic transform (NTT) of + * a polynomial in place. + * + * The input is assumed to be in normal order and + * coefficient-wise bound by MLKEM_Q in absolute value. + * + * The output polynomial is in bitreversed order, and + * coefficient-wise bound by MLK_NTT_BOUND in absolute value. + * + * (NOTE: Sometimes the input to the NTT is actually smaller, + * which gives better bounds.) + * + * Arguments: - mlk_poly *p: pointer to in/output polynomial + * + * Specification: Implements @[FIPS203, Algorithm 9, NTT] + * + **************************************************/ +MLK_INTERNAL_API +void mlk_poly_ntt(mlk_poly *r) +__contract__( + requires(memory_no_alias(r, sizeof(mlk_poly))) + requires(array_abs_bound(r->coeffs, 0, MLKEM_N, MLKEM_Q)) + assigns(memory_slice(r, sizeof(mlk_poly))) + ensures(array_abs_bound(r->coeffs, 0, MLKEM_N, MLK_NTT_BOUND)) +); + +#define mlk_poly_invntt_tomont MLK_NAMESPACE(poly_invntt_tomont) +/************************************************* + * Name: mlk_poly_invntt_tomont + * + * Description: Computes inverse of negacyclic number-theoretic transform (NTT) + * of a polynomial in place; + * inputs assumed to be in bitreversed order, output in normal + * order + * + * The input is assumed to be in bitreversed order, and can + * have arbitrary coefficients in int16_t. + * + * The output polynomial is in normal order, and + * coefficient-wise bound by MLK_INVNTT_BOUND in absolute value. + * + * Arguments: - uint16_t *a: pointer to in/output polynomial + * + * Specification: Implements composition of @[FIPS203, Algorithm 10, NTT^{-1}] + * and elementwise modular multiplication with a suitable + * Montgomery factor introduced during the base multiplication. + * + **************************************************/ +MLK_INTERNAL_API +void mlk_poly_invntt_tomont(mlk_poly *r) +__contract__( + requires(memory_no_alias(r, sizeof(mlk_poly))) + assigns(memory_slice(r, sizeof(mlk_poly))) + ensures(array_abs_bound(r->coeffs, 0, MLKEM_N, MLK_INVNTT_BOUND)) +); + +#endif /* !MLK_POLY_H */ diff --git a/mlkem_native/src/poly_k.c b/mlkem_native/src/poly_k.c new file mode 100644 index 0000000..32b214e --- /dev/null +++ b/mlkem_native/src/poly_k.c @@ -0,0 +1,502 @@ +/* + * Copyright (c) The mlkem-native project authors + * SPDX-License-Identifier: Apache-2.0 OR ISC OR MIT + */ + +/* References + * ========== + * + * - [FIPS203] + * FIPS 203 Module-Lattice-Based Key-Encapsulation Mechanism Standard + * National Institute of Standards and Technology + * https://csrc.nist.gov/pubs/fips/203/final + * + * - [NeonNTT] + * Neon NTT: Faster Dilithium, Kyber, and Saber on Cortex-A72 and Apple M1 + * Becker, Hwang, Kannwischer, Yang, Yang + * https://eprint.iacr.org/2021/986 + * + * - [REF] + * CRYSTALS-Kyber C reference implementation + * Bos, Ducas, Kiltz, Lepoint, Lyubashevsky, Schanck, Schwabe, Seiler, StehlΓ© + * https://github.com/pq-crystals/kyber/tree/main/ref + */ + +#include "poly_k.h" + +#include "debug.h" +#include "sampling.h" +#include "symmetric.h" + +/* Parameter set namespacing + * This is to facilitate building multiple instances + * of mlkem-native (e.g. with varying parameter sets) + * within a single compilation unit. */ +#define mlk_poly_cbd_eta1 MLK_ADD_PARAM_SET(mlk_poly_cbd_eta1) +#define mlk_poly_cbd_eta2 MLK_ADD_PARAM_SET(mlk_poly_cbd_eta2) +#define mlk_polyvec_basemul_acc_montgomery_cached_c \ + MLK_ADD_PARAM_SET(mlk_polyvec_basemul_acc_montgomery_cached_c) +/* End of parameter set namespacing */ + +/* Reference: `polyvec_compress()` in the reference implementation @[REF] + * - In contrast to the reference implementation, we assume + * unsigned canonical coefficients here. + * The reference implementation works with coefficients + * in the range (-MLKEM_Q+1,...,MLKEM_Q-1). */ +MLK_INTERNAL_API +void mlk_polyvec_compress_du(uint8_t r[MLKEM_POLYVECCOMPRESSEDBYTES_DU], + const mlk_polyvec *a) +{ + unsigned i; + mlk_assert_bound_2d(a->vec, MLKEM_K, MLKEM_N, 0, MLKEM_Q); + + for (i = 0; i < MLKEM_K; i++) + { + mlk_poly_compress_du(r + i * MLKEM_POLYCOMPRESSEDBYTES_DU, &a->vec[i]); + } +} + +/* Reference: `polyvec_decompress()` in the reference implementation @[REF]. */ +MLK_INTERNAL_API +void mlk_polyvec_decompress_du(mlk_polyvec *r, + const uint8_t a[MLKEM_POLYVECCOMPRESSEDBYTES_DU]) +{ + unsigned i; + for (i = 0; i < MLKEM_K; i++) + { + mlk_poly_decompress_du(&r->vec[i], a + i * MLKEM_POLYCOMPRESSEDBYTES_DU); + } + + mlk_assert_bound_2d(r->vec, MLKEM_K, MLKEM_N, 0, MLKEM_Q); +} + +/* Reference: `polyvec_tobytes()` in the reference implementation @[REF]. + * - In contrast to the reference implementation, we assume + * unsigned canonical coefficients here. + * The reference implementation works with coefficients + * in the range (-MLKEM_Q+1,...,MLKEM_Q-1). */ +MLK_INTERNAL_API +void mlk_polyvec_tobytes(uint8_t r[MLKEM_POLYVECBYTES], const mlk_polyvec *a) +{ + unsigned i; + mlk_assert_bound_2d(a->vec, MLKEM_K, MLKEM_N, 0, MLKEM_Q); + + for (i = 0; i < MLKEM_K; i++) + __loop__( + assigns(i, memory_slice(r, MLKEM_POLYVECBYTES)) + invariant(i <= MLKEM_K) + ) + { + mlk_poly_tobytes(&r[i * MLKEM_POLYBYTES], &a->vec[i]); + } +} + +/* Reference: `polyvec_frombytes()` in the reference implementation @[REF]. */ +MLK_INTERNAL_API +void mlk_polyvec_frombytes(mlk_polyvec *r, const uint8_t a[MLKEM_POLYVECBYTES]) +{ + unsigned i; + for (i = 0; i < MLKEM_K; i++) + { + mlk_poly_frombytes(&r->vec[i], a + i * MLKEM_POLYBYTES); + } + + mlk_assert_bound_2d(r->vec, MLKEM_K, MLKEM_N, 0, MLKEM_UINT12_LIMIT); +} + +/* Reference: `polyvec_ntt()` in the reference implementation @[REF]. */ +MLK_INTERNAL_API +void mlk_polyvec_ntt(mlk_polyvec *r) +{ + unsigned i; + for (i = 0; i < MLKEM_K; i++) + { + mlk_poly_ntt(&r->vec[i]); + } + + mlk_assert_abs_bound_2d(r->vec, MLKEM_K, MLKEM_N, MLK_NTT_BOUND); +} + +/* Reference: `polyvec_invntt_tomont()` in the reference implementation @[REF]. + * - We normalize at the beginning of the inverse NTT, + * while the reference implementation normalizes at + * the end. This allows us to drop a call to `poly_reduce()` + * from the base multiplication. */ +MLK_INTERNAL_API +void mlk_polyvec_invntt_tomont(mlk_polyvec *r) +{ + unsigned i; + for (i = 0; i < MLKEM_K; i++) + { + mlk_poly_invntt_tomont(&r->vec[i]); + } + + mlk_assert_abs_bound_2d(r->vec, MLKEM_K, MLKEM_N, MLK_INVNTT_BOUND); +} + +/* Reference: `polyvec_basemul_acc_montgomery()` in the + * reference implementation @[REF]. + * - We use a multiplication cache ('mulcache') here + * which is not present in the reference implementation @[REF]. + * This idea originates from @[NeonNTT] and is used + * at the C level here. + * - We compute the coefficients of the scalar product in 32-bit + * coefficients and perform only a single modular reduction + * at the end. The reference implementation uses 2 * MLKEM_K + * more modular reductions since it reduces after every modular + * multiplication. */ +MLK_STATIC_TESTABLE void mlk_polyvec_basemul_acc_montgomery_cached_c( + mlk_poly *r, const mlk_polyvec *a, const mlk_polyvec *b, + const mlk_polyvec_mulcache *b_cache) +__contract__( + requires(memory_no_alias(r, sizeof(mlk_poly))) + requires(memory_no_alias(a, sizeof(mlk_polyvec))) + requires(memory_no_alias(b, sizeof(mlk_polyvec))) + requires(memory_no_alias(b_cache, sizeof(mlk_polyvec_mulcache))) + requires(forall(k1, 0, MLKEM_K, + array_bound(a->vec[k1].coeffs, 0, MLKEM_N, 0, MLKEM_UINT12_LIMIT))) + assigns(memory_slice(r, sizeof(mlk_poly))) +) +{ + unsigned i; + mlk_assert_bound_2d(a->vec, MLKEM_K, MLKEM_N, 0, MLKEM_UINT12_LIMIT); + + for (i = 0; i < MLKEM_N / 2; i++) + __loop__(invariant(i <= MLKEM_N / 2)) + { + unsigned k; + int32_t t[2] = {0}; + for (k = 0; k < MLKEM_K; k++) + __loop__( + invariant(k <= MLKEM_K && + t[0] <= (int32_t) k * 2 * MLKEM_UINT12_LIMIT * 32768 && + t[0] >= - ((int32_t) k * 2 * MLKEM_UINT12_LIMIT * 32768) && + t[1] <= ((int32_t) k * 2 * MLKEM_UINT12_LIMIT * 32768) && + t[1] >= - ((int32_t) k * 2 * MLKEM_UINT12_LIMIT * 32768))) + { + t[0] += (int32_t)a->vec[k].coeffs[2 * i + 1] * b_cache->vec[k].coeffs[i]; + t[0] += (int32_t)a->vec[k].coeffs[2 * i] * b->vec[k].coeffs[2 * i]; + t[1] += (int32_t)a->vec[k].coeffs[2 * i] * b->vec[k].coeffs[2 * i + 1]; + t[1] += (int32_t)a->vec[k].coeffs[2 * i + 1] * b->vec[k].coeffs[2 * i]; + } + r->coeffs[2 * i + 0] = mlk_montgomery_reduce(t[0]); + r->coeffs[2 * i + 1] = mlk_montgomery_reduce(t[1]); + } +} + +MLK_INTERNAL_API +void mlk_polyvec_basemul_acc_montgomery_cached( + mlk_poly *r, const mlk_polyvec *a, const mlk_polyvec *b, + const mlk_polyvec_mulcache *b_cache) +{ +#if defined(MLK_USE_NATIVE_POLYVEC_BASEMUL_ACC_MONTGOMERY_CACHED) + { + int ret; + mlk_assert_bound_2d(a->vec, MLKEM_K, MLKEM_N, 0, MLKEM_UINT12_LIMIT); +#if MLKEM_K == 2 + ret = mlk_polyvec_basemul_acc_montgomery_cached_k2_native( + r->coeffs, (const int16_t *)a, (const int16_t *)b, + (const int16_t *)b_cache); +#elif MLKEM_K == 3 + ret = mlk_polyvec_basemul_acc_montgomery_cached_k3_native( + r->coeffs, (const int16_t *)a, (const int16_t *)b, + (const int16_t *)b_cache); +#elif MLKEM_K == 4 + ret = mlk_polyvec_basemul_acc_montgomery_cached_k4_native( + r->coeffs, (const int16_t *)a, (const int16_t *)b, + (const int16_t *)b_cache); +#endif + if (ret == MLK_NATIVE_FUNC_SUCCESS) + { + return; + } + } +#endif /* MLK_USE_NATIVE_POLYVEC_BASEMUL_ACC_MONTGOMERY_CACHED */ + + mlk_polyvec_basemul_acc_montgomery_cached_c(r, a, b, b_cache); +} + +/* Reference: Does not exist in the reference implementation @[REF]. + * - The reference implementation does not use a + * multiplication cache ('mulcache'). This idea originates + * from @[NeonNTT] and is used at the C level here. */ +MLK_INTERNAL_API +void mlk_polyvec_mulcache_compute(mlk_polyvec_mulcache *x, const mlk_polyvec *a) +{ + unsigned i; + for (i = 0; i < MLKEM_K; i++) + { + mlk_poly_mulcache_compute(&x->vec[i], &a->vec[i]); + } +} + +/* Reference: `polyvec_reduce()` in the reference implementation @[REF]. + * - We use _unsigned_ canonical outputs, while the reference + * implementation uses _signed_ canonical outputs. + * Accordingly, we need a conditional addition of MLKEM_Q + * here to go from signed to unsigned representatives. + * This conditional addition is then dropped from all + * polynomial compression functions instead (see `compress.c`). */ +MLK_INTERNAL_API +void mlk_polyvec_reduce(mlk_polyvec *r) +{ + unsigned i; + for (i = 0; i < MLKEM_K; i++) + { + mlk_poly_reduce(&r->vec[i]); + } + + mlk_assert_bound_2d(r->vec, MLKEM_K, MLKEM_N, 0, MLKEM_Q); +} + +/* Reference: `polyvec_add()` in the reference implementation @[REF]. + * - We use destructive version (output=first input) to avoid + * reasoning about aliasing in the CBMC specification */ +MLK_INTERNAL_API +void mlk_polyvec_add(mlk_polyvec *r, const mlk_polyvec *b) +{ + unsigned i; + for (i = 0; i < MLKEM_K; i++) + __loop__( + assigns(i, memory_slice(r, sizeof(mlk_polyvec))) + invariant(i <= MLKEM_K) + invariant(forall(j0, i, MLKEM_K, + forall(k0, 0, MLKEM_N, + ((int32_t)r->vec[j0].coeffs[k0] + b->vec[j0].coeffs[k0] <= INT16_MAX) && + ((int32_t)r->vec[j0].coeffs[k0] + b->vec[j0].coeffs[k0] >= INT16_MIN)))) + invariant(forall(j2, 0, i, + forall(k2, 0, MLKEM_N, + (r->vec[j2].coeffs[k2] <= INT16_MAX) && + (r->vec[j2].coeffs[k2] >= INT16_MIN)))) + ) + { + mlk_poly_add(&r->vec[i], &b->vec[i]); + } +} + +/* Reference: `polyvec_tomont()` in the reference implementation @[REF]. */ +MLK_INTERNAL_API +void mlk_polyvec_tomont(mlk_polyvec *r) +{ + unsigned i; + for (i = 0; i < MLKEM_K; i++) + { + mlk_poly_tomont(&r->vec[i]); + } + + mlk_assert_abs_bound_2d(r->vec, MLKEM_K, MLKEM_N, MLKEM_Q); +} + + +/************************************************* + * Name: mlk_poly_cbd_eta1 + * + * Description: Given an array of uniformly random bytes, compute + * polynomial with coefficients distributed according to + * a centered binomial distribution with parameter MLKEM_ETA1. + * + * Arguments: - mlk_poly *r: pointer to output polynomial + * - const uint8_t *buf: pointer to input byte array + * + * Specification: Implements @[FIPS203, Algorithm 8, SamplePolyCBD_eta1], where + * eta1 is specified per parameter set in @[FIPS203, Table 2] + * and represented as MLKEM_ETA1 here. + * + **************************************************/ + +/* Reference: `poly_cbd_eta1` in the reference implementation @[REF]. */ +static MLK_INLINE void mlk_poly_cbd_eta1( + mlk_poly *r, const uint8_t buf[MLKEM_ETA1 * MLKEM_N / 4]) +__contract__( + requires(memory_no_alias(r, sizeof(mlk_poly))) + requires(memory_no_alias(buf, MLKEM_ETA1 * MLKEM_N / 4)) + assigns(memory_slice(r, sizeof(mlk_poly))) + ensures(array_abs_bound(r->coeffs, 0, MLKEM_N, MLKEM_ETA1 + 1)) +) +{ +#if MLKEM_ETA1 == 2 + mlk_poly_cbd2(r, buf); +#elif MLKEM_ETA1 == 3 + mlk_poly_cbd3(r, buf); +#else +#error "Invalid value of MLKEM_ETA1" +#endif +} + +/* Reference: Does not exist in the reference implementation @[REF]. + * - This implements a x4-batched version of `poly_getnoise_eta1()` + * from the reference implementation, to leverage + * batched Keccak-f1600.*/ +MLK_INTERNAL_API +void mlk_poly_getnoise_eta1_4x(mlk_poly *r0, mlk_poly *r1, mlk_poly *r2, + mlk_poly *r3, const uint8_t seed[MLKEM_SYMBYTES], + uint8_t nonce0, uint8_t nonce1, uint8_t nonce2, + uint8_t nonce3) +{ + MLK_ALIGN uint8_t buf[4][MLK_ALIGN_UP(MLKEM_ETA1 * MLKEM_N / 4)]; + MLK_ALIGN uint8_t extkey[4][MLK_ALIGN_UP(MLKEM_SYMBYTES + 1)]; + mlk_memcpy(extkey[0], seed, MLKEM_SYMBYTES); + mlk_memcpy(extkey[1], seed, MLKEM_SYMBYTES); + mlk_memcpy(extkey[2], seed, MLKEM_SYMBYTES); + mlk_memcpy(extkey[3], seed, MLKEM_SYMBYTES); + extkey[0][MLKEM_SYMBYTES] = nonce0; + extkey[1][MLKEM_SYMBYTES] = nonce1; + extkey[2][MLKEM_SYMBYTES] = nonce2; + extkey[3][MLKEM_SYMBYTES] = nonce3; + +#if !defined(FIPS202_X4_DEFAULT_IMPLEMENTATION) && \ + !defined(MLK_CONFIG_SERIAL_FIPS202_ONLY) + mlk_prf_eta1_x4(buf, extkey); +#else + mlk_prf_eta1(buf[0], extkey[0]); + mlk_prf_eta1(buf[1], extkey[1]); + mlk_prf_eta1(buf[2], extkey[2]); + if (r3 != NULL) + { + mlk_prf_eta1(buf[3], extkey[3]); + } +#endif /* !(!FIPS202_X4_DEFAULT_IMPLEMENTATION && \ + !MLK_CONFIG_SERIAL_FIPS202_ONLY) */ + + mlk_poly_cbd_eta1(r0, buf[0]); + mlk_poly_cbd_eta1(r1, buf[1]); + mlk_poly_cbd_eta1(r2, buf[2]); + if (r3 != NULL) + { + mlk_poly_cbd_eta1(r3, buf[3]); + mlk_assert_abs_bound(r3, MLKEM_N, MLKEM_ETA1 + 1); + } + + mlk_assert_abs_bound(r0, MLKEM_N, MLKEM_ETA1 + 1); + mlk_assert_abs_bound(r1, MLKEM_N, MLKEM_ETA1 + 1); + mlk_assert_abs_bound(r2, MLKEM_N, MLKEM_ETA1 + 1); + + /* Specification: Partially implements + * @[FIPS203, Section 3.3, Destruction of intermediate values] */ + mlk_zeroize(buf, sizeof(buf)); + mlk_zeroize(extkey, sizeof(extkey)); +} + +#if MLKEM_K == 2 || MLKEM_K == 4 +/************************************************* + * Name: mlk_poly_cbd_eta2 + * + * Description: Given an array of uniformly random bytes, compute + * polynomial with coefficients distributed according to + * a centered binomial distribution with parameter MLKEM_ETA2. + * + * Arguments: - mlk_poly *r: pointer to output polynomial + * - const uint8_t *buf: pointer to input byte array + * + * Specification: Implements @[FIPS203, Algorithm 8, SamplePolyCBD_eta2], where + * eta2 is specified per parameter set in @[FIPS203, Table 2] + * and represented as MLKEM_ETA2 here. + * + **************************************************/ + +/* Reference: `poly_cbd_eta2` in the reference implementation @[REF]. */ +static MLK_INLINE void mlk_poly_cbd_eta2( + mlk_poly *r, const uint8_t buf[MLKEM_ETA2 * MLKEM_N / 4]) +__contract__( + requires(memory_no_alias(r, sizeof(mlk_poly))) + requires(memory_no_alias(buf, MLKEM_ETA2 * MLKEM_N / 4)) + assigns(memory_slice(r, sizeof(mlk_poly))) + ensures(array_abs_bound(r->coeffs, 0, MLKEM_N, MLKEM_ETA2 + 1))) +{ +#if MLKEM_ETA2 == 2 + mlk_poly_cbd2(r, buf); +#else +#error "Invalid value of MLKEM_ETA2" +#endif +} + +/* Reference: `poly_getnoise_eta2()` in the reference implementation @[REF]. + * - We include buffer zeroization. */ +MLK_INTERNAL_API +void mlk_poly_getnoise_eta2(mlk_poly *r, const uint8_t seed[MLKEM_SYMBYTES], + uint8_t nonce) +{ + MLK_ALIGN uint8_t buf[MLKEM_ETA2 * MLKEM_N / 4]; + MLK_ALIGN uint8_t extkey[MLKEM_SYMBYTES + 1]; + + mlk_memcpy(extkey, seed, MLKEM_SYMBYTES); + extkey[MLKEM_SYMBYTES] = nonce; + mlk_prf_eta2(buf, extkey); + + mlk_poly_cbd_eta2(r, buf); + + mlk_assert_abs_bound(r, MLKEM_N, MLKEM_ETA2 + 1); + + /* Specification: Partially implements + * @[FIPS203, Section 3.3, Destruction of intermediate values] */ + mlk_zeroize(buf, sizeof(buf)); + mlk_zeroize(extkey, sizeof(extkey)); +} +#endif /* MLKEM_K == 2 || MLKEM_K == 4 */ + +#if MLKEM_K == 2 +/* Reference: Does not exist in the reference implementation @[REF]. + * - This implements a x4-batched version of `poly_getnoise_eta1()` + * and `poly_getnoise_eta2()` from the reference implementation, + * leveraging batched Keccak-f1600. + * - If a x4-batched Keccak-f1600 is available, we squeeze + * more random data than needed for the eta2 calls, to be + * be able to use a x4-batched Keccak-f1600. */ +MLK_INTERNAL_API +void mlk_poly_getnoise_eta1122_4x(mlk_poly *r0, mlk_poly *r1, mlk_poly *r2, + mlk_poly *r3, + const uint8_t seed[MLKEM_SYMBYTES], + uint8_t nonce0, uint8_t nonce1, + uint8_t nonce2, uint8_t nonce3) +{ +#if MLKEM_ETA2 >= MLKEM_ETA1 +#error mlk_poly_getnoise_eta1122_4x assumes MLKEM_ETA1 > MLKEM_ETA2 +#endif + MLK_ALIGN uint8_t buf[4][MLK_ALIGN_UP(MLKEM_ETA1 * MLKEM_N / 4)]; + MLK_ALIGN uint8_t extkey[4][MLK_ALIGN_UP(MLKEM_SYMBYTES + 1)]; + + mlk_memcpy(extkey[0], seed, MLKEM_SYMBYTES); + mlk_memcpy(extkey[1], seed, MLKEM_SYMBYTES); + mlk_memcpy(extkey[2], seed, MLKEM_SYMBYTES); + mlk_memcpy(extkey[3], seed, MLKEM_SYMBYTES); + extkey[0][MLKEM_SYMBYTES] = nonce0; + extkey[1][MLKEM_SYMBYTES] = nonce1; + extkey[2][MLKEM_SYMBYTES] = nonce2; + extkey[3][MLKEM_SYMBYTES] = nonce3; + + /* On systems with fast batched Keccak, we use 4-fold batched PRF, + * even though that means generating more random data in buf[2] and buf[3] + * than necessary. */ +#if !defined(FIPS202_X4_DEFAULT_IMPLEMENTATION) && \ + !defined(MLK_CONFIG_SERIAL_FIPS202_ONLY) + mlk_prf_eta1_x4(buf, extkey); +#else + mlk_prf_eta1(buf[0], extkey[0]); + mlk_prf_eta1(buf[1], extkey[1]); + mlk_prf_eta2(buf[2], extkey[2]); + mlk_prf_eta2(buf[3], extkey[3]); +#endif /* !(!FIPS202_X4_DEFAULT_IMPLEMENTATION && \ + !MLK_CONFIG_SERIAL_FIPS202_ONLY) */ + + mlk_poly_cbd_eta1(r0, buf[0]); + mlk_poly_cbd_eta1(r1, buf[1]); + mlk_poly_cbd_eta2(r2, buf[2]); + mlk_poly_cbd_eta2(r3, buf[3]); + + mlk_assert_abs_bound(r0, MLKEM_N, MLKEM_ETA1 + 1); + mlk_assert_abs_bound(r1, MLKEM_N, MLKEM_ETA1 + 1); + mlk_assert_abs_bound(r2, MLKEM_N, MLKEM_ETA2 + 1); + mlk_assert_abs_bound(r3, MLKEM_N, MLKEM_ETA2 + 1); + + /* Specification: Partially implements + * @[FIPS203, Section 3.3, Destruction of intermediate values] */ + mlk_zeroize(buf, sizeof(buf)); + mlk_zeroize(extkey, sizeof(extkey)); +} +#endif /* MLKEM_K == 2 */ + +/* To facilitate single-compilation-unit (SCU) builds, undefine all macros. + * Don't modify by hand -- this is auto-generated by scripts/autogen. */ +#undef mlk_poly_cbd_eta1 +#undef mlk_poly_cbd_eta2 +#undef mlk_polyvec_basemul_acc_montgomery_cached_c diff --git a/mlkem_native/src/poly_k.h b/mlkem_native/src/poly_k.h new file mode 100644 index 0000000..9089a8e --- /dev/null +++ b/mlkem_native/src/poly_k.h @@ -0,0 +1,668 @@ +/* + * Copyright (c) The mlkem-native project authors + * SPDX-License-Identifier: Apache-2.0 OR ISC OR MIT + */ + +/* References + * ========== + * + * - [FIPS203] + * FIPS 203 Module-Lattice-Based Key-Encapsulation Mechanism Standard + * National Institute of Standards and Technology + * https://csrc.nist.gov/pubs/fips/203/final + */ + +#ifndef MLK_POLY_K_H +#define MLK_POLY_K_H + +#include "common.h" +#include "compress.h" +#include "poly.h" + +/* Parameter set namespacing + * This is to facilitate building multiple instances + * of mlkem-native (e.g. with varying parameter sets) + * within a single compilation unit. */ +#define mlk_polyvec MLK_ADD_PARAM_SET(mlk_polyvec) +#define mlk_polymat MLK_ADD_PARAM_SET(mlk_polymat) +#define mlk_polyvec_mulcache MLK_ADD_PARAM_SET(mlk_polyvec_mulcache) +/* End of parameter set namespacing */ + +typedef struct +{ + mlk_poly vec[MLKEM_K]; +} MLK_ALIGN mlk_polyvec; + +typedef struct +{ + mlk_polyvec vec[MLKEM_K]; +} MLK_ALIGN mlk_polymat; + +typedef struct +{ + mlk_poly_mulcache vec[MLKEM_K]; +} MLK_ALIGN mlk_polyvec_mulcache; + +#define mlk_poly_compress_du MLK_NAMESPACE_K(poly_compress_du) +/************************************************* + * Name: mlk_poly_compress_du + * + * Description: Compression (du bits) and subsequent serialization of a + * polynomial + * + * Arguments: - uint8_t *r: pointer to output byte array + * (of length MLKEM_POLYCOMPRESSEDBYTES_DU bytes) + * - const mlk_poly *a: pointer to input polynomial + * Coefficients must be unsigned canonical, + * i.e. in [0,1,..,MLKEM_Q-1]. + * + * Specification: Implements `ByteEncode_{d_u} (Compress_{d_u} (u))` + * in @[FIPS203, Algorithm 14 (K-PKE.Encrypt), L22], + * with level-specific d_u defined in @[FIPS203, Table 2], + * and given by MLKEM_DU here. + * + **************************************************/ +static MLK_INLINE void mlk_poly_compress_du( + uint8_t r[MLKEM_POLYCOMPRESSEDBYTES_DU], const mlk_poly *a) +__contract__( + requires(memory_no_alias(r, MLKEM_POLYCOMPRESSEDBYTES_DU)) + requires(memory_no_alias(a, sizeof(mlk_poly))) + requires(array_bound(a->coeffs, 0, MLKEM_N, 0, MLKEM_Q)) + assigns(memory_slice(r, MLKEM_POLYCOMPRESSEDBYTES_DU))) +{ +#if MLKEM_DU == 10 + mlk_poly_compress_d10(r, a); +#elif MLKEM_DU == 11 + mlk_poly_compress_d11(r, a); +#else +#error "Invalid value of MLKEM_DU" +#endif +} + +#define mlk_poly_decompress_du MLK_NAMESPACE_K(poly_decompress_du) +/************************************************* + * Name: mlk_poly_decompress_du + * + * Description: De-serialization and subsequent decompression (du bits) of a + * polynomial; approximate inverse of mlk_poly_compress_du + * + * Arguments: - mlk_poly *r: pointer to output polynomial + * - const uint8_t *a: pointer to input byte array + * (of length MLKEM_POLYCOMPRESSEDBYTES_DU bytes) + * + * Upon return, the coefficients of the output polynomial are unsigned-canonical + * (non-negative and smaller than MLKEM_Q). + * + * Specification: Implements `Decompress_{d_u} (ByteDecode_{d_u} (u))` + * in @[FIPS203, Algorithm 15 (K-PKE.Decrypt), L3]. + * with level-specific d_u defined in @[FIPS203, Table 2], + * and given by MLKEM_DU here. + * + **************************************************/ +static MLK_INLINE void mlk_poly_decompress_du( + mlk_poly *r, const uint8_t a[MLKEM_POLYCOMPRESSEDBYTES_DU]) +__contract__( + requires(memory_no_alias(a, MLKEM_POLYCOMPRESSEDBYTES_DU)) + requires(memory_no_alias(r, sizeof(mlk_poly))) + assigns(memory_slice(r, sizeof(mlk_poly))) + ensures(array_bound(r->coeffs, 0, MLKEM_N, 0, MLKEM_Q))) +{ +#if MLKEM_DU == 10 + mlk_poly_decompress_d10(r, a); +#elif MLKEM_DU == 11 + mlk_poly_decompress_d11(r, a); +#else +#error "Invalid value of MLKEM_DU" +#endif +} + +#define mlk_poly_compress_dv MLK_NAMESPACE_K(poly_compress_dv) +/************************************************* + * Name: mlk_poly_compress_dv + * + * Description: Compression (dv bits) and subsequent serialization of a + * polynomial + * + * Arguments: - uint8_t *r: pointer to output byte array + * (of length MLKEM_POLYCOMPRESSEDBYTES_DV bytes) + * - const mlk_poly *a: pointer to input polynomial + * Coefficients must be unsigned canonical, + * i.e. in [0,1,..,MLKEM_Q-1]. + * + * Specification: Implements `ByteEncode_{d_v} (Compress_{d_v} (v))` + * in @[FIPS203, Algorithm 14 (K-PKE.Encrypt), L23]. + * with level-specific d_v defined in @[FIPS203, Table 2], + * and given by MLKEM_DV here. + * + **************************************************/ +static MLK_INLINE void mlk_poly_compress_dv( + uint8_t r[MLKEM_POLYCOMPRESSEDBYTES_DV], const mlk_poly *a) +__contract__( + requires(memory_no_alias(r, MLKEM_POLYCOMPRESSEDBYTES_DV)) + requires(memory_no_alias(a, sizeof(mlk_poly))) + requires(array_bound(a->coeffs, 0, MLKEM_N, 0, MLKEM_Q)) + assigns(memory_slice(r, MLKEM_POLYCOMPRESSEDBYTES_DV))) +{ +#if MLKEM_DV == 4 + mlk_poly_compress_d4(r, a); +#elif MLKEM_DV == 5 + mlk_poly_compress_d5(r, a); +#else +#error "Invalid value of MLKEM_DV" +#endif +} + + +#define mlk_poly_decompress_dv MLK_NAMESPACE_K(poly_decompress_dv) +/************************************************* + * Name: mlk_poly_decompress_dv + * + * Description: De-serialization and subsequent decompression (dv bits) of a + * polynomial; approximate inverse of poly_compress + * + * Arguments: - mlk_poly *r: pointer to output polynomial + * - const uint8_t *a: pointer to input byte array + * (of length MLKEM_POLYCOMPRESSEDBYTES_DV bytes) + * + * Upon return, the coefficients of the output polynomial are unsigned-canonical + * (non-negative and smaller than MLKEM_Q). + * + * Specification: Implements `Decompress_{d_v} (ByteDecode_{d_v} (v))` + * in @[FIPS203, Algorithm 15 (K-PKE.Decrypt), L4]. + * with level-specific d_v defined in @[FIPS203, Table 2], + * and given by MLKEM_DV here. + * + **************************************************/ +static MLK_INLINE void mlk_poly_decompress_dv( + mlk_poly *r, const uint8_t a[MLKEM_POLYCOMPRESSEDBYTES_DV]) +__contract__( + requires(memory_no_alias(a, MLKEM_POLYCOMPRESSEDBYTES_DV)) + requires(memory_no_alias(r, sizeof(mlk_poly))) + assigns(memory_slice(r, sizeof(mlk_poly))) + ensures(array_bound(r->coeffs, 0, MLKEM_N, 0, MLKEM_Q))) +{ +#if MLKEM_DV == 4 + mlk_poly_decompress_d4(r, a); +#elif MLKEM_DV == 5 + mlk_poly_decompress_d5(r, a); +#else +#error "Invalid value of MLKEM_DV" +#endif +} + +#define mlk_polyvec_compress_du MLK_NAMESPACE_K(polyvec_compress_du) +/************************************************* + * Name: mlk_polyvec_compress_du + * + * Description: Compress and serialize vector of polynomials + * + * Arguments: - uint8_t *r: pointer to output byte array + * (needs space for MLKEM_POLYVECCOMPRESSEDBYTES_DU) + * - const mlk_polyvec a: pointer to input vector of polynomials. + * Coefficients must be unsigned canonical, + * i.e. in [0,1,..,MLKEM_Q-1]. + * + * Specification: Implements `ByteEncode_{d_u} (Compress_{d_u} (u))` + * in @[FIPS203, Algorithm 14 (K-PKE.Encrypt), L22]. + * with level-specific d_u defined in @[FIPS203, Table 2], + * and given by MLKEM_DU here. + * + **************************************************/ +MLK_INTERNAL_API +void mlk_polyvec_compress_du(uint8_t r[MLKEM_POLYVECCOMPRESSEDBYTES_DU], + const mlk_polyvec *a) +__contract__( + requires(memory_no_alias(r, MLKEM_POLYVECCOMPRESSEDBYTES_DU)) + requires(memory_no_alias(a, sizeof(mlk_polyvec))) + requires(forall(k0, 0, MLKEM_K, + array_bound(a->vec[k0].coeffs, 0, MLKEM_N, 0, MLKEM_Q))) + assigns(memory_slice(r, MLKEM_POLYVECCOMPRESSEDBYTES_DU)) +); + +#define mlk_polyvec_decompress_du MLK_NAMESPACE_K(polyvec_decompress_du) +/************************************************* + * Name: mlk_polyvec_decompress_du + * + * Description: De-serialize and decompress vector of polynomials; + * approximate inverse of mlk_polyvec_compress_du + * + * Arguments: - mlk_polyvec r: pointer to output vector of polynomials. + * Output will have coefficients normalized to [0,..,q-1]. + * - const uint8_t *a: pointer to input byte array + * (of length MLKEM_POLYVECCOMPRESSEDBYTES_DU) + * + * Specification: Implements `Decompress_{d_u} (ByteDecode_{d_u} (u))` + * in @[FIPS203, Algorithm 15 (K-PKE.Decrypt), L3]. + * with level-specific d_u defined in @[FIPS203, Table 2], + * and given by MLKEM_DU here. + * + **************************************************/ +MLK_INTERNAL_API +void mlk_polyvec_decompress_du(mlk_polyvec *r, + const uint8_t a[MLKEM_POLYVECCOMPRESSEDBYTES_DU]) +__contract__( + requires(memory_no_alias(a, MLKEM_POLYVECCOMPRESSEDBYTES_DU)) + requires(memory_no_alias(r, sizeof(mlk_polyvec))) + assigns(memory_slice(r, sizeof(mlk_polyvec))) + ensures(forall(k0, 0, MLKEM_K, + array_bound(r->vec[k0].coeffs, 0, MLKEM_N, 0, MLKEM_Q))) +); + +#define mlk_polyvec_tobytes MLK_NAMESPACE_K(polyvec_tobytes) +/************************************************* + * Name: mlk_polyvec_tobytes + * + * Description: Serialize vector of polynomials + * + * Arguments: - uint8_t *r: pointer to output byte array + * (needs space for MLKEM_POLYVECBYTES) + * - const mlk_polyvec a: pointer to input vector of polynomials + * Each polynomial must have coefficients in [0,..,q-1]. + * + * Specification: Implements ByteEncode_12 @[FIPS203, Algorithm 5]. + * Extended to vectors as per + * @[FIPS203, 2.4.8 Applying Algorithms to Arrays] + * and @[FIPS203, 2.4.6, Matrices and Vectors] + * + **************************************************/ +MLK_INTERNAL_API +void mlk_polyvec_tobytes(uint8_t r[MLKEM_POLYVECBYTES], const mlk_polyvec *a) +__contract__( + requires(memory_no_alias(a, sizeof(mlk_polyvec))) + requires(memory_no_alias(r, MLKEM_POLYVECBYTES)) + requires(forall(k0, 0, MLKEM_K, + array_bound(a->vec[k0].coeffs, 0, MLKEM_N, 0, MLKEM_Q))) + assigns(memory_slice(r, MLKEM_POLYVECBYTES)) +); + +#define mlk_polyvec_frombytes MLK_NAMESPACE_K(polyvec_frombytes) +/************************************************* + * Name: mlk_polyvec_frombytes + * + * Description: De-serialize vector of polynomials; + * inverse of mlk_polyvec_tobytes + * + * Arguments: - const mlk_polyvec a: pointer to output vector of polynomials + * (of length MLKEM_POLYVECBYTES). Output will have coefficients + * normalized in [0..4095]. + * - uint8_t *r: pointer to input byte array + * + * Specification: Implements ByteDecode_12 @[FIPS203, Algorithm 6]. + * Extended to vectors as per + * @[FIPS203, 2.4.8 Applying Algorithms to Arrays] + * and @[FIPS203, 2.4.6, Matrices and Vectors] + * + **************************************************/ +MLK_INTERNAL_API +void mlk_polyvec_frombytes(mlk_polyvec *r, const uint8_t a[MLKEM_POLYVECBYTES]) +__contract__( + requires(memory_no_alias(r, sizeof(mlk_polyvec))) + requires(memory_no_alias(a, MLKEM_POLYVECBYTES)) + assigns(memory_slice(r, sizeof(mlk_polyvec))) + ensures(forall(k0, 0, MLKEM_K, + array_bound(r->vec[k0].coeffs, 0, MLKEM_N, 0, MLKEM_UINT12_LIMIT))) +); + +#define mlk_polyvec_ntt MLK_NAMESPACE_K(polyvec_ntt) +/************************************************* + * Name: mlk_polyvec_ntt + * + * Description: Apply forward NTT to all elements of a vector of polynomials. + * + * The input is assumed to be in normal order and + * coefficient-wise bound by MLKEM_Q in absolute value. + * + * The output polynomial is in bitreversed order, and + * coefficient-wise bound by MLK_NTT_BOUND in absolute value. + * + * Arguments: - mlk_polyvec r: pointer to in/output vector of polynomials + * + * Specification: + * - Implements @[FIPS203, Algorithm 9, NTT] + * - Extended to vectors as per @[FIPS203, 2.4.6, Matrices and Vectors] + * + **************************************************/ +MLK_INTERNAL_API +void mlk_polyvec_ntt(mlk_polyvec *r) +__contract__( + requires(memory_no_alias(r, sizeof(mlk_polyvec))) + requires(forall(j, 0, MLKEM_K, + array_abs_bound(r->vec[j].coeffs, 0, MLKEM_N, MLKEM_Q))) + assigns(memory_slice(r, sizeof(mlk_polyvec))) + ensures(forall(j, 0, MLKEM_K, + array_abs_bound(r->vec[j].coeffs, 0, MLKEM_N, MLK_NTT_BOUND))) +); + +#define mlk_polyvec_invntt_tomont MLK_NAMESPACE_K(polyvec_invntt_tomont) +/************************************************* + * Name: mlk_polyvec_invntt_tomont + * + * Description: Apply inverse NTT to all elements of a vector of polynomials + * and multiply by Montgomery factor 2^16 + * + * The input is assumed to be in bitreversed order, and can + * have arbitrary coefficients in int16_t. + * + * The output polynomial is in normal order, and + * coefficient-wise bound by MLK_INVNTT_BOUND in absolute value. + * + * Arguments: - mlk_polyvec r: pointer to in/output vector of polynomials + * + * Specification: + * - Implements @[FIPS203, Algorithm 10, NTT^{-1}] + * - Extended to vectors as per @[FIPS203, 2.4.6, Matrices and Vectors] + * + **************************************************/ +MLK_INTERNAL_API +void mlk_polyvec_invntt_tomont(mlk_polyvec *r) +__contract__( + requires(memory_no_alias(r, sizeof(mlk_polyvec))) + assigns(memory_slice(r, sizeof(mlk_polyvec))) + ensures(forall(j, 0, MLKEM_K, + array_abs_bound(r->vec[j].coeffs, 0, MLKEM_N, MLK_INVNTT_BOUND))) +); + +#define mlk_polyvec_basemul_acc_montgomery_cached \ + MLK_NAMESPACE_K(polyvec_basemul_acc_montgomery_cached) +/************************************************* + * Name: mlk_polyvec_basemul_acc_montgomery_cached + * + * Description: Scalar product of two vectors of polynomials in NTT domain, + * using mulcache for second operand. + * + * Bounds: + * - Every coefficient of a is assumed to be in [0..4095] + * - No bounds guarantees for the coefficients in the result. + * + * Arguments: - mlk_poly *r: pointer to output polynomial + * - const mlk_polyvec a: pointer to first input polynomial vector + * - const mlk_polyvec b: pointer to second input polynomial + * vector + * - const mlk_polyvec_mulcache b_cache: pointer to mulcache + * for second input polynomial vector. Can be computed + * via mlk_polyvec_mulcache_compute(). + * + * Specification: Implements + * - @[FIPS203, Section 2.4.7, Eq (2.14)] + * - @[FIPS203, Algorithm 11, MultiplyNTTs] + * - @[FIPS203, Algorithm 12, BaseCaseMultiply] + * + **************************************************/ +MLK_INTERNAL_API +void mlk_polyvec_basemul_acc_montgomery_cached( + mlk_poly *r, const mlk_polyvec *a, const mlk_polyvec *b, + const mlk_polyvec_mulcache *b_cache) +__contract__( + requires(memory_no_alias(r, sizeof(mlk_poly))) + requires(memory_no_alias(a, sizeof(mlk_polyvec))) + requires(memory_no_alias(b, sizeof(mlk_polyvec))) + requires(memory_no_alias(b_cache, sizeof(mlk_polyvec_mulcache))) + requires(forall(k1, 0, MLKEM_K, + array_bound(a->vec[k1].coeffs, 0, MLKEM_N, 0, MLKEM_UINT12_LIMIT))) + assigns(memory_slice(r, sizeof(mlk_poly))) +); + +#define mlk_polyvec_mulcache_compute MLK_NAMESPACE_K(polyvec_mulcache_compute) +/************************************************************ + * Name: mlk_polyvec_mulcache_compute + * + * Description: Computes the mulcache for a vector of polynomials in NTT domain + * + * The mulcache of a degree-2 polynomial b := b0 + b1*X + * in Fq[X]/(X^2-zeta) is the value b1*zeta, needed when + * computing products of b in Fq[X]/(X^2-zeta). + * + * The mulcache of a polynomial in NTT domain -- which is + * a 128-tuple of degree-2 polynomials in Fq[X]/(X^2-zeta), + * for varying zeta, is the 128-tuple of mulcaches of those + * polynomials. + * + * The mulcache of a vector of polynomials is the vector + * of mulcaches of its entries. + * + * Arguments: - x: Pointer to mulcache to be populated + * - a: Pointer to input polynomial vector + * + * Specification: + * - Caches `b_1 * \gamma` in @[FIPS203, Algorithm 12, BaseCaseMultiply, L1] + * + ************************************************************/ +/* + * NOTE: The default C implementation of this function populates + * the mulcache with values in (-q,q), but this is not needed for the + * higher level safety proofs, and thus not part of the spec. + */ +MLK_INTERNAL_API +void mlk_polyvec_mulcache_compute(mlk_polyvec_mulcache *x, const mlk_polyvec *a) +__contract__( + requires(memory_no_alias(x, sizeof(mlk_polyvec_mulcache))) + requires(memory_no_alias(a, sizeof(mlk_polyvec))) + assigns(memory_slice(x, sizeof(mlk_polyvec_mulcache))) +); + +#define mlk_polyvec_reduce MLK_NAMESPACE_K(polyvec_reduce) +/************************************************* + * Name: mlk_polyvec_reduce + * + * Description: Applies Barrett reduction to each coefficient + * of each element of a vector of polynomials; + * for details of the Barrett reduction see comments in poly.c + * + * Arguments: - mlk_polyvec r: pointer to input/output polynomial + * + * Specification: Normalizes on unsigned canoncial representatives + * ahead of calling @[FIPS203, Compress_d, Eq (4.7)]. + * This is not made explicit in FIPS 203. + * + **************************************************/ +/* + * NOTE: The semantics of mlk_polyvec_reduce() is different in + * the reference implementation, which requires + * signed canonical output data. Unsigned canonical + * outputs are better suited to the only remaining + * use of mlk_poly_reduce() in the context of (de)serialization. + */ +MLK_INTERNAL_API +void mlk_polyvec_reduce(mlk_polyvec *r) +__contract__( + requires(memory_no_alias(r, sizeof(mlk_polyvec))) + assigns(memory_slice(r, sizeof(mlk_polyvec))) + ensures(forall(k0, 0, MLKEM_K, + array_bound(r->vec[k0].coeffs, 0, MLKEM_N, 0, MLKEM_Q))) +); + +#define mlk_polyvec_add MLK_NAMESPACE_K(polyvec_add) +/************************************************* + * Name: mlk_polyvec_add + * + * Description: Add vectors of polynomials + * + * Arguments: - mlk_polyvec r: pointer to input-output vector of polynomials to + * be added to + * - const mlk_polyvec b: pointer to second input vector of + * polynomials + * + * The coefficients of r and b must be so that the addition does + * not overflow. Otherwise, the behaviour of this function is undefined. + * + * The coefficients returned in *r are in int16_t which is sufficient + * to prove type-safety of calling units. Therefore, no stronger + * ensures clause is required on this function. + * + * Specification: + * - @[FIPS203, 2.4.5, Arithmetic With Polynomials and NTT Representations] + * - Used in @[FIPS203, Algorithm 14 (K-PKE.Encrypt), L19] + * + **************************************************/ +MLK_INTERNAL_API +void mlk_polyvec_add(mlk_polyvec *r, const mlk_polyvec *b) +__contract__( + requires(memory_no_alias(r, sizeof(mlk_polyvec))) + requires(memory_no_alias(b, sizeof(mlk_polyvec))) + requires(forall(j0, 0, MLKEM_K, + forall(k0, 0, MLKEM_N, + (int32_t)r->vec[j0].coeffs[k0] + b->vec[j0].coeffs[k0] <= INT16_MAX))) + requires(forall(j1, 0, MLKEM_K, + forall(k1, 0, MLKEM_N, + (int32_t)r->vec[j1].coeffs[k1] + b->vec[j1].coeffs[k1] >= INT16_MIN))) + assigns(memory_slice(r, sizeof(mlk_polyvec))) +); + +#define mlk_polyvec_tomont MLK_NAMESPACE_K(polyvec_tomont) +/************************************************* + * Name: mlk_polyvec_tomont + * + * Description: Inplace conversion of all coefficients of a polynomial + * vector from normal domain to Montgomery domain + * + * Bounds: Output < q in absolute value. + * + * + * Specification: Internal normalization required in `mlk_indcpa_keypair_derand` + * as part of matrix-vector multiplication + * @[FIPS203, Algorithm 13, K-PKE.KeyGen, L18]. + * + **************************************************/ +MLK_INTERNAL_API +void mlk_polyvec_tomont(mlk_polyvec *r) +__contract__( + requires(memory_no_alias(r, sizeof(mlk_polyvec))) + assigns(memory_slice(r, sizeof(mlk_polyvec))) + ensures(forall(j, 0, MLKEM_K, + array_abs_bound(r->vec[j].coeffs, 0, MLKEM_N, MLKEM_Q))) +); + +#define mlk_poly_getnoise_eta1_4x MLK_NAMESPACE_K(poly_getnoise_eta1_4x) +/************************************************* + * Name: mlk_poly_getnoise_eta1_4x + * + * Description: Batch sample four polynomials deterministically from a seed + * and nonces, with output polynomials close to centered binomial + * distribution with parameter MLKEM_ETA1. + * + * Arguments: - mlk_poly *r{0,1,2,3}: pointer to output polynomial. The last + * polynomial pointer may be NULL. + * - const uint8_t *seed: pointer to input seed + * (of length MLKEM_SYMBYTES bytes) + * - uint8_t nonce{0,1,2,3}: one-byte input nonce + * + * Specification: + * Implements 4x `SamplePolyCBD_{eta1} (PRF_{eta1} (sigma, N))`: + * - @[FIPS203, Algorithm 8, SamplePolyCBD_eta] + * - @[FIPS203, Eq (4.3), PRF_eta] + * - `SamplePolyCBD_{eta1} (PRF_{eta1} (sigma, N))` appears in + * @[FIPS203, Algorithm 13, K-PKE.KeyGen, L{9, 13}] + * @[FIPS203, Algorithm 14, K-PKE.Encrypt, L10] + * + **************************************************/ +MLK_INTERNAL_API +void mlk_poly_getnoise_eta1_4x(mlk_poly *r0, mlk_poly *r1, mlk_poly *r2, + mlk_poly *r3, const uint8_t seed[MLKEM_SYMBYTES], + uint8_t nonce0, uint8_t nonce1, uint8_t nonce2, + uint8_t nonce3) +__contract__( + requires(memory_no_alias(seed, MLKEM_SYMBYTES)) + requires(memory_no_alias(r0, sizeof(mlk_poly))) + requires(memory_no_alias(r1, sizeof(mlk_poly))) + requires(memory_no_alias(r2, sizeof(mlk_poly))) + requires(r3 == NULL || memory_no_alias(r3, sizeof(mlk_poly))) + assigns(memory_slice(r0, sizeof(mlk_poly))) + assigns(memory_slice(r1, sizeof(mlk_poly))) + assigns(memory_slice(r2, sizeof(mlk_poly))) + assigns(r3 != NULL: memory_slice(r3, sizeof(mlk_poly))) + ensures(array_abs_bound(r0->coeffs,0, MLKEM_N, MLKEM_ETA1 + 1)) + ensures(array_abs_bound(r1->coeffs,0, MLKEM_N, MLKEM_ETA1 + 1)) + ensures(array_abs_bound(r2->coeffs,0, MLKEM_N, MLKEM_ETA1 + 1)) + ensures(r3 != NULL ==> array_abs_bound(r3->coeffs,0, MLKEM_N, MLKEM_ETA1 + 1)) +); + +#if MLKEM_ETA1 == MLKEM_ETA2 +/* + * We only require mlk_poly_getnoise_eta2_4x for ml-kem-768 and ml-kem-1024 + * where MLKEM_ETA2 = MLKEM_ETA1 = 2. + * For ml-kem-512, mlk_poly_getnoise_eta1122_4x is used instead. + */ +#define mlk_poly_getnoise_eta2_4x mlk_poly_getnoise_eta1_4x +#endif /* MLKEM_ETA1 == MLKEM_ETA2 */ + +#if MLKEM_K == 2 || MLKEM_K == 4 +#define mlk_poly_getnoise_eta2 MLK_NAMESPACE_K(poly_getnoise_eta2) +/************************************************* + * Name: mlk_poly_getnoise_eta2 + * + * Description: Sample a polynomial deterministically from a seed and a nonce, + * with output polynomial close to centered binomial distribution + * with parameter MLKEM_ETA2 + * + * Arguments: - mlk_poly *r: pointer to output polynomial + * - const uint8_t *seed: pointer to input seed + * (of length MLKEM_SYMBYTES bytes) + * - uint8_t nonce: one-byte input nonce + * + * Specification: + * Implements `SamplePolyCBD_{eta2} (PRF_{eta2} (sigma, N))`: + * - @[FIPS203, Algorithm 8, SamplePolyCBD_eta] + * - @[FIPS203, Eq (4.3), PRF_eta] + * - `SamplePolyCBD_{eta2} (PRF_{eta2} (sigma, N))` appears in + * @[FIPS203, Algorithm 14, K-PKE.Encrypt, L14] + * + **************************************************/ +MLK_INTERNAL_API +void mlk_poly_getnoise_eta2(mlk_poly *r, const uint8_t seed[MLKEM_SYMBYTES], + uint8_t nonce) +__contract__( + requires(memory_no_alias(r, sizeof(mlk_poly))) + requires(memory_no_alias(seed, MLKEM_SYMBYTES)) + assigns(memory_slice(r, sizeof(mlk_poly))) + ensures(array_abs_bound(r->coeffs, 0, MLKEM_N, MLKEM_ETA2 + 1)) +); +#endif /* MLKEM_K == 2 || MLKEM_K == 4 */ + +#if MLKEM_K == 2 +#define mlk_poly_getnoise_eta1122_4x MLK_NAMESPACE_K(poly_getnoise_eta1122_4x) +/************************************************* + * Name: mlk_poly_getnoise_eta1122_4x + * + * Description: Batch sample four polynomials deterministically from a seed + * and a nonces, with output polynomials close to centered binomial + * distribution with parameter MLKEM_ETA1 and MLKEM_ETA2 + * + * Arguments: - mlk_poly *r{0,1,2,3}: pointer to output polynomial + * - const uint8_t *seed: pointer to input seed + * (of length MLKEM_SYMBYTES bytes) + * - uint8_t nonce{0,1,2,3}: one-byte input nonce + * + * Specification: + * Implements two instances each of + * `SamplePolyCBD_{eta1} (PRF_{eta1} (sigma, N))` and + * `SamplePolyCBD_{eta2} (PRF_{eta2} (sigma, N))`: + * - @[FIPS203, Algorithm 8, SamplePolyCBD_eta] + * - @[FIPS203, Eq (4.3), PRF_eta] + * - `SamplePolyCBD_{eta2} (PRF_{eta2} (sigma, N))` appears in + * @[FIPS203, Algorithm 14, K-PKE.Encrypt, L14] + * + **************************************************/ +MLK_INTERNAL_API +void mlk_poly_getnoise_eta1122_4x(mlk_poly *r0, mlk_poly *r1, mlk_poly *r2, + mlk_poly *r3, + const uint8_t seed[MLKEM_SYMBYTES], + uint8_t nonce0, uint8_t nonce1, + uint8_t nonce2, uint8_t nonce3) +__contract__( + requires(memory_no_alias(r0, sizeof(mlk_poly))) + requires(memory_no_alias(r1, sizeof(mlk_poly))) + requires(memory_no_alias(r2, sizeof(mlk_poly))) + requires(memory_no_alias(r3, sizeof(mlk_poly))) + requires(memory_no_alias(seed, MLKEM_SYMBYTES)) + assigns(memory_slice(r0, sizeof(mlk_poly))) + assigns(memory_slice(r1, sizeof(mlk_poly))) + assigns(memory_slice(r2, sizeof(mlk_poly))) + assigns(memory_slice(r3, sizeof(mlk_poly))) + ensures(array_abs_bound(r0->coeffs,0, MLKEM_N, MLKEM_ETA1 + 1) + && array_abs_bound(r1->coeffs,0, MLKEM_N, MLKEM_ETA1 + 1) + && array_abs_bound(r2->coeffs,0, MLKEM_N, MLKEM_ETA2 + 1) + && array_abs_bound(r3->coeffs,0, MLKEM_N, MLKEM_ETA2 + 1)) +); +#endif /* MLKEM_K == 2 */ + +#endif /* !MLK_POLY_K_H */ diff --git a/mlkem_native/src/randombytes.h b/mlkem_native/src/randombytes.h new file mode 100644 index 0000000..3e841d2 --- /dev/null +++ b/mlkem_native/src/randombytes.h @@ -0,0 +1,60 @@ +/* + * Copyright (c) The mlkem-native project authors + * SPDX-License-Identifier: Apache-2.0 OR ISC OR MIT + */ +#ifndef MLK_RANDOMBYTES_H +#define MLK_RANDOMBYTES_H + + +#include "cbmc.h" +#include "common.h" + +#if !defined(MLK_CONFIG_NO_RANDOMIZED_API) +#if !defined(MLK_CONFIG_CUSTOM_RANDOMBYTES) +/************************************************* + * Name: randombytes + * + * Description: Fill a buffer with cryptographically secure random bytes. + * + * mlkem-native does not provide an implementation of this + * function. It must be provided by the consumer. + * + * To use a custom random byte source with a different name + * or signature, set MLK_CONFIG_CUSTOM_RANDOMBYTES and define + * mlk_randombytes directly. + * + * Arguments: - uint8_t *out: pointer to output buffer + * - size_t outlen: number of random bytes to write + * + * Returns: 0 on success, non-zero on failure. + * On failure, top-level APIs return MLK_ERR_RNG_FAIL. + * + **************************************************/ +int randombytes(uint8_t *out, size_t outlen); + +/************************************************* + * Name: mlk_randombytes + * + * Description: Internal wrapper around randombytes(). + * + * Fill a buffer with cryptographically secure random bytes. + * + * This function can be replaced by setting + * MLK_CONFIG_CUSTOM_RANDOMBYTES and defining mlk_randombytes + * directly. + * + * Arguments: - uint8_t *out: pointer to output buffer + * - size_t outlen: number of random bytes to write + * + * Returns: 0 on success, non-zero on failure. + * On failure, top-level APIs return MLK_ERR_RNG_FAIL. + * + **************************************************/ +MLK_MUST_CHECK_RETURN_VALUE +static MLK_INLINE int mlk_randombytes(uint8_t *out, size_t outlen) +__contract__( + requires(memory_no_alias(out, outlen)) + assigns(memory_slice(out, outlen))) { return randombytes(out, outlen); } +#endif /* !MLK_CONFIG_CUSTOM_RANDOMBYTES */ +#endif /* !MLK_CONFIG_NO_RANDOMIZED_API */ +#endif /* !MLK_RANDOMBYTES_H */ diff --git a/mlkem_native/src/sampling.c b/mlkem_native/src/sampling.c new file mode 100644 index 0000000..945d12e --- /dev/null +++ b/mlkem_native/src/sampling.c @@ -0,0 +1,362 @@ +/* + * Copyright (c) The mlkem-native project authors + * SPDX-License-Identifier: Apache-2.0 OR ISC OR MIT + */ + +/* References + * ========== + * + * - [FIPS203] + * FIPS 203 Module-Lattice-Based Key-Encapsulation Mechanism Standard + * National Institute of Standards and Technology + * https://csrc.nist.gov/pubs/fips/203/final + * + * - [REF] + * CRYSTALS-Kyber C reference implementation + * Bos, Ducas, Kiltz, Lepoint, Lyubashevsky, Schanck, Schwabe, Seiler, StehlΓ© + * https://github.com/pq-crystals/kyber/tree/main/ref + */ + +#include "common.h" +#if !defined(MLK_CONFIG_MULTILEVEL_NO_SHARED) + +#include "debug.h" +#include "sampling.h" +#include "symmetric.h" + +/* Reference: `rej_uniform()` in the reference implementation @[REF]. + * - Our signature differs from the reference implementation + * in that it adds the offset and always expects the base of the + * target buffer. This avoids shifting the buffer base in the + * caller, which appears tricky to reason about. */ +MLK_STATIC_TESTABLE unsigned mlk_rej_uniform_c(int16_t *r, unsigned target, + unsigned offset, + const uint8_t *buf, + unsigned buflen) +__contract__( + requires(offset <= target && target <= 4096 && buflen <= 4096 && buflen % 3 == 0) + requires(memory_no_alias(r, sizeof(int16_t) * target)) + requires(memory_no_alias(buf, buflen)) + requires(array_bound(r, 0, offset, 0, MLKEM_Q)) + assigns(memory_slice(r, sizeof(int16_t) * target)) + ensures(offset <= return_value && return_value <= target) + ensures(array_bound(r, 0, return_value, 0, MLKEM_Q))) +{ + unsigned ctr, pos; + int16_t val0, val1; + + mlk_assert_bound(r, offset, 0, MLKEM_Q); + + ctr = offset; + pos = 0; + /* pos + 3 cannot overflow due to the assumption buflen <= 4096 */ + while (ctr < target && pos + 3 <= buflen) + __loop__( + invariant(offset <= ctr && ctr <= target && pos <= buflen) + invariant(array_bound(r, 0, ctr, 0, MLKEM_Q))) + { + val0 = ((buf[pos + 0] >> 0) | (buf[pos + 1] << 8)) & 0xFFF; + val1 = ((buf[pos + 1] >> 4) | (buf[pos + 2] << 4)) & 0xFFF; + pos += 3; + + if (val0 < MLKEM_Q) + { + r[ctr++] = val0; + } + if (ctr < target && val1 < MLKEM_Q) + { + r[ctr++] = val1; + } + } + + mlk_assert_bound(r, ctr, 0, MLKEM_Q); + return ctr; +} + +/************************************************* + * Name: mlk_rej_uniform + * + * Description: Run rejection sampling on uniform random bytes to generate + * uniform random integers mod q + * + * Arguments: - int16_t *r: pointer to output buffer + * - unsigned target: requested number of 16-bit integers + * (uniform mod q). + * Must be <= 4096. + * - unsigned offset: number of 16-bit integers that have + * already been sampled. + * Must be <= target. + * - const uint8_t *buf: pointer to input buffer + * (assumed to be uniform random bytes) + * - unsigned buflen: length of input buffer in bytes + * Must be <= 4096. + * Must be a multiple of 3. + * + * Note: Strictly speaking, only a few values of buflen near UINT_MAX need + * excluding. The limit of 4096 is somewhat arbitrary but sufficient for all + * uses of this function. Similarly, the actual limit for target is UINT_MAX/2. + * + * Returns the new offset of sampled 16-bit integers, at most target, + * and at least the initial offset. + * If the new offset is strictly less than len, all of the input buffers + * is guaranteed to have been consumed. If it is equal to len, no information + * is provided on how many bytes of the input buffer have been consumed. + **************************************************/ + +/* Reference: `rej_uniform()` in the reference implementation @[REF]. + * - Our signature differs from the reference implementation + * in that it adds the offset and always expects the base of the + * target buffer. This avoids shifting the buffer base in the + * caller, which appears tricky to reason about. + * - Optional fallback to native implementation. */ +static unsigned mlk_rej_uniform(int16_t *r, unsigned target, unsigned offset, + const uint8_t *buf, unsigned buflen) +__contract__( + requires(offset <= target && target <= 4096 && buflen <= 4096 && buflen % 3 == 0) + requires(memory_no_alias(r, sizeof(int16_t) * target)) + requires(memory_no_alias(buf, buflen)) + requires(array_bound(r, 0, offset, 0, MLKEM_Q)) + assigns(memory_slice(r, sizeof(int16_t) * target)) + ensures(offset <= return_value && return_value <= target) + ensures(array_bound(r, 0, return_value, 0, MLKEM_Q)) +) +{ +#if defined(MLK_USE_NATIVE_REJ_UNIFORM) + if (offset == 0) + { + int ret; + ret = mlk_rej_uniform_native(r, target, buf, buflen); + if (ret != MLK_NATIVE_FUNC_FALLBACK) + { + unsigned res = (unsigned)ret; + mlk_assert_bound(r, res, 0, MLKEM_Q); + return res; + } + } +#endif /* MLK_USE_NATIVE_REJ_UNIFORM */ + + return mlk_rej_uniform_c(r, target, offset, buf, buflen); +} + +#ifndef MLKEM_GEN_MATRIX_NBLOCKS +#define MLKEM_GEN_MATRIX_NBLOCKS \ + ((12 * MLKEM_N / 8 * ((uint32_t)1 << 12) / MLKEM_Q + MLK_XOF_RATE) / \ + MLK_XOF_RATE) +#endif + +#if !defined(MLK_CONFIG_SERIAL_FIPS202_ONLY) +/* Reference: Does not exist in the reference implementation @[REF]. + * - x4-batched version of `rej_uniform()` from the + * reference implementation, leveraging x4-batched Keccak-f1600. */ +MLK_INTERNAL_API +void mlk_poly_rej_uniform_x4(mlk_poly *vec0, mlk_poly *vec1, mlk_poly *vec2, + mlk_poly *vec3, + uint8_t seed[4][MLK_ALIGN_UP(MLKEM_SYMBYTES + 2)]) +{ + /* Temporary buffers for XOF output before rejection sampling */ + MLK_ALIGN uint8_t + buf[4][MLK_ALIGN_UP(MLKEM_GEN_MATRIX_NBLOCKS * MLK_XOF_RATE)]; + + /* Tracks the number of coefficients we have already sampled */ + unsigned ctr[4]; + mlk_xof_x4_ctx statex; + unsigned buflen; + + mlk_xof_x4_init(&statex); + mlk_xof_x4_absorb(&statex, seed, MLKEM_SYMBYTES + 2); + + /* + * Initially, squeeze heuristic number of MLKEM_GEN_MATRIX_NBLOCKS. + * This should generate the matrix entries with high probability. + */ + mlk_xof_x4_squeezeblocks(buf, MLKEM_GEN_MATRIX_NBLOCKS, &statex); + buflen = MLKEM_GEN_MATRIX_NBLOCKS * MLK_XOF_RATE; + ctr[0] = mlk_rej_uniform(vec0->coeffs, MLKEM_N, 0, buf[0], buflen); + ctr[1] = mlk_rej_uniform(vec1->coeffs, MLKEM_N, 0, buf[1], buflen); + ctr[2] = mlk_rej_uniform(vec2->coeffs, MLKEM_N, 0, buf[2], buflen); + ctr[3] = mlk_rej_uniform(vec3->coeffs, MLKEM_N, 0, buf[3], buflen); + + /* + * So long as not all matrix entries have been generated, squeeze + * one more block a time until we're done. + */ + buflen = MLK_XOF_RATE; + while (ctr[0] < MLKEM_N || ctr[1] < MLKEM_N || ctr[2] < MLKEM_N || + ctr[3] < MLKEM_N) + __loop__( + assigns(ctr, statex, + memory_slice(vec0, sizeof(mlk_poly)), + memory_slice(vec1, sizeof(mlk_poly)), + memory_slice(vec2, sizeof(mlk_poly)), + memory_slice(vec3, sizeof(mlk_poly)), + object_whole(buf)) + invariant(ctr[0] <= MLKEM_N && ctr[1] <= MLKEM_N) + invariant(ctr[2] <= MLKEM_N && ctr[3] <= MLKEM_N) + invariant(array_bound(vec0->coeffs, 0, ctr[0], 0, MLKEM_Q)) + invariant(array_bound(vec1->coeffs, 0, ctr[1], 0, MLKEM_Q)) + invariant(array_bound(vec2->coeffs, 0, ctr[2], 0, MLKEM_Q)) + invariant(array_bound(vec3->coeffs, 0, ctr[3], 0, MLKEM_Q))) + { + mlk_xof_x4_squeezeblocks(buf, 1, &statex); + ctr[0] = mlk_rej_uniform(vec0->coeffs, MLKEM_N, ctr[0], buf[0], buflen); + ctr[1] = mlk_rej_uniform(vec1->coeffs, MLKEM_N, ctr[1], buf[1], buflen); + ctr[2] = mlk_rej_uniform(vec2->coeffs, MLKEM_N, ctr[2], buf[2], buflen); + ctr[3] = mlk_rej_uniform(vec3->coeffs, MLKEM_N, ctr[3], buf[3], buflen); + } + + mlk_xof_x4_release(&statex); + + /* Specification: Partially implements + * @[FIPS203, Section 3.3, Destruction of intermediate values] */ + mlk_zeroize(buf, sizeof(buf)); +} +#endif /* !MLK_CONFIG_SERIAL_FIPS202_ONLY */ + +MLK_INTERNAL_API +void mlk_poly_rej_uniform(mlk_poly *entry, uint8_t seed[MLKEM_SYMBYTES + 2]) +{ + mlk_xof_ctx state; + MLK_ALIGN uint8_t buf[MLKEM_GEN_MATRIX_NBLOCKS * MLK_XOF_RATE]; + unsigned ctr, buflen; + + mlk_xof_init(&state); + mlk_xof_absorb(&state, seed, MLKEM_SYMBYTES + 2); + + /* Initially, squeeze + sample heuristic number of MLKEM_GEN_MATRIX_NBLOCKS. + */ + /* This should generate the matrix entry with high probability. */ + mlk_xof_squeezeblocks(buf, MLKEM_GEN_MATRIX_NBLOCKS, &state); + buflen = MLKEM_GEN_MATRIX_NBLOCKS * MLK_XOF_RATE; + ctr = mlk_rej_uniform(entry->coeffs, MLKEM_N, 0, buf, buflen); + + /* Squeeze + sample one more block a time until we're done */ + buflen = MLK_XOF_RATE; + while (ctr < MLKEM_N) + __loop__( + assigns(ctr, state, memory_slice(entry, sizeof(mlk_poly)), object_whole(buf)) + invariant(ctr <= MLKEM_N) + invariant(array_bound(entry->coeffs, 0, ctr, 0, MLKEM_Q))) + { + mlk_xof_squeezeblocks(buf, 1, &state); + ctr = mlk_rej_uniform(entry->coeffs, MLKEM_N, ctr, buf, buflen); + } + + mlk_xof_release(&state); + + /* Specification: Partially implements + * @[FIPS203, Section 3.3, Destruction of intermediate values] */ + mlk_zeroize(buf, sizeof(buf)); +} + +/************************************************* + * Name: mlk_load32_littleendian + * + * Description: load 4 bytes into a 32-bit integer + * in little-endian order + * + * Arguments: - const uint8_t *x: pointer to input byte array + * + * Returns 32-bit unsigned integer loaded from x + * + **************************************************/ + +/* Reference: `load32_littleendian()` in the reference implementation @[REF]. */ +static uint32_t mlk_load32_littleendian(const uint8_t x[4]) +{ + uint32_t r; + r = (uint32_t)x[0]; + r |= (uint32_t)x[1] << 8; + r |= (uint32_t)x[2] << 16; + r |= (uint32_t)x[3] << 24; + return r; +} + +/* Reference: `cbd2()` in the reference implementation @[REF]. */ +MLK_INTERNAL_API +void mlk_poly_cbd2(mlk_poly *r, const uint8_t buf[2 * MLKEM_N / 4]) +{ + unsigned i; + for (i = 0; i < MLKEM_N / 8; i++) + __loop__( + invariant(i <= MLKEM_N / 8) + invariant(array_abs_bound(r->coeffs, 0, 8 * i, 3))) + { + unsigned j; + uint32_t t = mlk_load32_littleendian(buf + 4 * i); + uint32_t d = t & 0x55555555; + d += (t >> 1) & 0x55555555; + + for (j = 0; j < 8; j++) + __loop__( + invariant(i <= MLKEM_N / 8 && j <= 8) + invariant(array_abs_bound(r->coeffs, 0, 8 * i + j, 3))) + { + const int16_t a = (d >> (4 * j + 0)) & 0x3; + const int16_t b = (d >> (4 * j + 2)) & 0x3; + r->coeffs[8 * i + j] = (int16_t)(a - b); + } + } +} + +#if defined(MLK_CONFIG_MULTILEVEL_WITH_SHARED) || MLKEM_ETA1 == 3 +/************************************************* + * Name: mlk_load24_littleendian + * + * Description: load 3 bytes into a 32-bit integer + * in little-endian order. + * This function is only needed for ML-KEM-512 + * + * Arguments: - const uint8_t *x: pointer to input byte array + * + * Returns 32-bit unsigned integer loaded from x (most significant byte is zero) + * + **************************************************/ + +/* Reference: `load24_littleendian()` in the reference implementation @[REF]. */ +static uint32_t mlk_load24_littleendian(const uint8_t x[3]) +{ + uint32_t r; + r = (uint32_t)x[0]; + r |= (uint32_t)x[1] << 8; + r |= (uint32_t)x[2] << 16; + return r; +} + +/* Reference: `cbd3()` in the reference implementation @[REF]. */ +MLK_INTERNAL_API +void mlk_poly_cbd3(mlk_poly *r, const uint8_t buf[3 * MLKEM_N / 4]) +{ + unsigned i; + for (i = 0; i < MLKEM_N / 4; i++) + __loop__( + invariant(i <= MLKEM_N / 4) + invariant(array_abs_bound(r->coeffs, 0, 4 * i, 4))) + { + unsigned j; + const uint32_t t = mlk_load24_littleendian(buf + 3 * i); + uint32_t d = t & 0x00249249; + d += (t >> 1) & 0x00249249; + d += (t >> 2) & 0x00249249; + + for (j = 0; j < 4; j++) + __loop__( + invariant(i <= MLKEM_N / 4 && j <= 4) + invariant(array_abs_bound(r->coeffs, 0, 4 * i + j, 4))) + { + const int16_t a = (d >> (6 * j + 0)) & 0x7; + const int16_t b = (d >> (6 * j + 3)) & 0x7; + r->coeffs[4 * i + j] = (int16_t)(a - b); + } + } +} +#endif /* MLK_CONFIG_MULTILEVEL_WITH_SHARED || MLKEM_ETA1 == 3 */ + +#else /* !MLK_CONFIG_MULTILEVEL_NO_SHARED */ + +MLK_EMPTY_CU(sampling) + +#endif /* MLK_CONFIG_MULTILEVEL_NO_SHARED */ + +/* To facilitate single-compilation-unit (SCU) builds, undefine all macros. + * Don't modify by hand -- this is auto-generated by scripts/autogen. */ +#undef MLKEM_GEN_MATRIX_NBLOCKS diff --git a/mlkem_native/src/sampling.h b/mlkem_native/src/sampling.h new file mode 100644 index 0000000..24c26b3 --- /dev/null +++ b/mlkem_native/src/sampling.h @@ -0,0 +1,118 @@ +/* + * Copyright (c) The mlkem-native project authors + * SPDX-License-Identifier: Apache-2.0 OR ISC OR MIT + */ + +/* References + * ========== + * + * - [FIPS203] + * FIPS 203 Module-Lattice-Based Key-Encapsulation Mechanism Standard + * National Institute of Standards and Technology + * https://csrc.nist.gov/pubs/fips/203/final + */ + +#ifndef MLK_SAMPLING_H +#define MLK_SAMPLING_H + +#include "cbmc.h" +#include "common.h" +#include "poly.h" + +#define mlk_poly_cbd2 MLK_NAMESPACE(poly_cbd2) +/************************************************* + * Name: mlk_poly_cbd2 + * + * Description: Given an array of uniformly random bytes, compute + * polynomial with coefficients distributed according to + * a centered binomial distribution with parameter eta=2 + * + * Arguments: - mlk_poly *r: pointer to output polynomial + * - const uint8_t *buf: pointer to input byte array + * + * Specification: Implements @[FIPS203, Algorithm 8, SamplePolyCBD_2] + * + **************************************************/ +MLK_INTERNAL_API +void mlk_poly_cbd2(mlk_poly *r, const uint8_t buf[2 * MLKEM_N / 4]); + +#if defined(MLK_CONFIG_MULTILEVEL_WITH_SHARED) || MLKEM_ETA1 == 3 +#define mlk_poly_cbd3 MLK_NAMESPACE(poly_cbd3) +/************************************************* + * Name: mlk_poly_cbd3 + * + * Description: Given an array of uniformly random bytes, compute + * polynomial with coefficients distributed according to + * a centered binomial distribution with parameter eta=3. + * This function is only needed for ML-KEM-512 + * + * Arguments: - mlk_poly *r: pointer to output polynomial + * - const uint8_t *buf: pointer to input byte array + * + * Specification: Implements @[FIPS203, Algorithm 8, SamplePolyCBD_3] + * + **************************************************/ +MLK_INTERNAL_API +void mlk_poly_cbd3(mlk_poly *r, const uint8_t buf[3 * MLKEM_N / 4]); +#endif /* MLK_CONFIG_MULTILEVEL_WITH_SHARED || MLKEM_ETA1 == 3 */ + +#if !defined(MLK_CONFIG_SERIAL_FIPS202_ONLY) +#define mlk_poly_rej_uniform_x4 MLK_NAMESPACE(poly_rej_uniform_x4) +/************************************************* + * Name: mlk_poly_rej_uniform_x4 + * + * Description: Generate four polynomials using rejection sampling + * on (pseudo-)uniformly random bytes sampled from a seed. + * + * Arguments: - mlk_poly *vec0, *vec1, *vec2, *vec3: + * Pointers to 4 polynomials to be sampled. + * - uint8_t seed[4][MLK_ALIGN_UP(MLKEM_SYMBYTES + 2)]: + * Pointer consecutive array of seed buffers of size + * MLKEM_SYMBYTES + 2 each, plus padding for alignment. + * + * Specification: Implements @[FIPS203, Algorithm 7, SampleNTT] + * + **************************************************/ +MLK_INTERNAL_API +void mlk_poly_rej_uniform_x4(mlk_poly *vec0, mlk_poly *vec1, mlk_poly *vec2, + mlk_poly *vec3, + uint8_t seed[4][MLK_ALIGN_UP(MLKEM_SYMBYTES + 2)]) +__contract__( + requires(memory_no_alias(vec0, sizeof(mlk_poly))) + requires(memory_no_alias(vec1, sizeof(mlk_poly))) + requires(memory_no_alias(vec2, sizeof(mlk_poly))) + requires(memory_no_alias(vec3, sizeof(mlk_poly))) + requires(memory_no_alias(seed, 4 * MLK_ALIGN_UP(MLKEM_SYMBYTES + 2))) + assigns(memory_slice(vec0, sizeof(mlk_poly))) + assigns(memory_slice(vec1, sizeof(mlk_poly))) + assigns(memory_slice(vec2, sizeof(mlk_poly))) + assigns(memory_slice(vec3, sizeof(mlk_poly))) + ensures(array_bound(vec0->coeffs, 0, MLKEM_N, 0, MLKEM_Q)) + ensures(array_bound(vec1->coeffs, 0, MLKEM_N, 0, MLKEM_Q)) + ensures(array_bound(vec2->coeffs, 0, MLKEM_N, 0, MLKEM_Q)) + ensures(array_bound(vec3->coeffs, 0, MLKEM_N, 0, MLKEM_Q))); +#endif /* !MLK_CONFIG_SERIAL_FIPS202_ONLY */ + +#define mlk_poly_rej_uniform MLK_NAMESPACE(poly_rej_uniform) +/************************************************* + * Name: mlk_poly_rej_uniform + * + * Description: Generate polynomial using rejection sampling + * on (pseudo-)uniformly random bytes sampled from a seed. + * + * Arguments: - mlk_poly *vec: Pointer to polynomial to be sampled. + * - uint8_t *seed: Pointer to seed buffer of size + * MLKEM_SYMBYTES + 2 each. + * + * Specification: Implements @[FIPS203, Algorithm 7, SampleNTT] + * + **************************************************/ +MLK_INTERNAL_API +void mlk_poly_rej_uniform(mlk_poly *entry, uint8_t seed[MLKEM_SYMBYTES + 2]) +__contract__( + requires(memory_no_alias(entry, sizeof(mlk_poly))) + requires(memory_no_alias(seed, MLKEM_SYMBYTES + 2)) + assigns(memory_slice(entry, sizeof(mlk_poly))) + ensures(array_bound(entry->coeffs, 0, MLKEM_N, 0, MLKEM_Q))); + +#endif /* !MLK_SAMPLING_H */ diff --git a/mlkem_native/src/symmetric.h b/mlkem_native/src/symmetric.h new file mode 100644 index 0000000..68d7e1a --- /dev/null +++ b/mlkem_native/src/symmetric.h @@ -0,0 +1,70 @@ +/* + * Copyright (c) The mlkem-native project authors + * SPDX-License-Identifier: Apache-2.0 OR ISC OR MIT + */ + +/* References + * ========== + * + * - [FIPS203] + * FIPS 203 Module-Lattice-Based Key-Encapsulation Mechanism Standard + * National Institute of Standards and Technology + * https://csrc.nist.gov/pubs/fips/203/final + */ + +#ifndef MLK_SYMMETRIC_H +#define MLK_SYMMETRIC_H + + +#include "cbmc.h" +#include "common.h" +#include MLK_FIPS202_HEADER_FILE +#if !defined(MLK_CONFIG_SERIAL_FIPS202_ONLY) +#include MLK_FIPS202X4_HEADER_FILE +#endif + +/* Macros denoting FIPS 203 specific Hash functions */ + +/* Hash function H, @[FIPS203, Section 4.1, Eq (4.4)] */ +#define mlk_hash_h(OUT, IN, INBYTES) mlk_sha3_256(OUT, IN, INBYTES) + +/* Hash function G, @[FIPS203, Section 4.1, Eq (4.5)] */ +#define mlk_hash_g(OUT, IN, INBYTES) mlk_sha3_512(OUT, IN, INBYTES) + +/* Hash function J, @[FIPS203, Section 4.1, Eq (4.4)] */ +#define mlk_hash_j(OUT, IN, INBYTES) \ + mlk_shake256(OUT, MLKEM_SYMBYTES, IN, INBYTES) + +/* PRF function, @[FIPS203, Section 4.1, Eq (4.3)] + * Referring to (eq 4.3), `OUT` is assumed to contain `s || b`. */ +#define mlk_prf_eta(ETA, OUT, IN) \ + mlk_shake256(OUT, (ETA) * MLKEM_N / 4, IN, MLKEM_SYMBYTES + 1) +#define mlk_prf_eta1(OUT, IN) mlk_prf_eta(MLKEM_ETA1, OUT, IN) +#define mlk_prf_eta2(OUT, IN) mlk_prf_eta(MLKEM_ETA2, OUT, IN) +#define mlk_prf_eta1_x4(OUT, IN) \ + mlk_shake256x4((OUT)[0], (OUT)[1], (OUT)[2], (OUT)[3], \ + (MLKEM_ETA1 * MLKEM_N / 4), (IN)[0], (IN)[1], (IN)[2], \ + (IN)[3], MLKEM_SYMBYTES + 1) + +/* XOF function, FIPS 203 4.1 */ +#define mlk_xof_ctx mlk_shake128ctx +#define mlk_xof_x4_ctx mlk_shake128x4ctx +#define mlk_xof_init(CTX) mlk_shake128_init((CTX)) +#define mlk_xof_absorb(CTX, IN, INBYTES) \ + mlk_shake128_absorb_once((CTX), (IN), (INBYTES)) +#define mlk_xof_squeezeblocks(BUF, NBLOCKS, CTX) \ + mlk_shake128_squeezeblocks((BUF), (NBLOCKS), (CTX)) +#define mlk_xof_release(CTX) mlk_shake128_release((CTX)) + +#define mlk_xof_x4_init(CTX) mlk_shake128x4_init((CTX)) +#define mlk_xof_x4_absorb(CTX, IN, INBYTES) \ + mlk_shake128x4_absorb_once((CTX), (IN)[0], (IN)[1], (IN)[2], (IN)[3], \ + (INBYTES)) +#define mlk_xof_x4_squeezeblocks(BUF, NBLOCKS, CTX) \ + mlk_shake128x4_squeezeblocks((BUF)[0], (BUF)[1], (BUF)[2], (BUF)[3], \ + (NBLOCKS), (CTX)) +#define mlk_xof_x4_release(CTX) mlk_shake128x4_release((CTX)) + +#define MLK_XOF_RATE SHAKE128_RATE + +#endif /* !MLK_SYMMETRIC_H */ diff --git a/mlkem_native/src/sys.h b/mlkem_native/src/sys.h new file mode 100644 index 0000000..0ab8947 --- /dev/null +++ b/mlkem_native/src/sys.h @@ -0,0 +1,260 @@ +/* + * Copyright (c) The mlkem-native project authors + * SPDX-License-Identifier: Apache-2.0 OR ISC OR MIT + */ +#ifndef MLK_SYS_H +#define MLK_SYS_H + +#if !defined(MLK_CONFIG_NO_ASM) && (defined(__GNUC__) || defined(__clang__)) +#define MLK_HAVE_INLINE_ASM +#endif + +/* Try to find endianness, if not forced through CFLAGS already */ +#if !defined(MLK_SYS_LITTLE_ENDIAN) && !defined(MLK_SYS_BIG_ENDIAN) +#if defined(__BYTE_ORDER__) +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ +#define MLK_SYS_LITTLE_ENDIAN +#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ +#define MLK_SYS_BIG_ENDIAN +#else +#error "__BYTE_ORDER__ defined, but don't recognize value." +#endif +#endif /* __BYTE_ORDER__ */ + +/* MSVC does not define __BYTE_ORDER__. However, MSVC only supports + * little endian x86, x86_64, and AArch64. It is, hence, safe to assume + * little endian. */ +#if defined(_MSC_VER) && (defined(_M_X64) || defined(_M_AMD64) || \ + defined(_M_IX86) || defined(_M_ARM64)) +#define MLK_SYS_LITTLE_ENDIAN +#endif + +#endif /* !MLK_SYS_LITTLE_ENDIAN && !MLK_SYS_BIG_ENDIAN */ + +/* Check if we're running on an AArch64 little endian system. _M_ARM64 is set by + * MSVC. */ +#if defined(__AARCH64EL__) || defined(_M_ARM64) +#define MLK_SYS_AARCH64 +#endif + +/* Check if we're running on an AArch64 big endian system. */ +#if defined(__AARCH64EB__) +#define MLK_SYS_AARCH64_EB +#endif + +/* Check if we're running on an Armv8.1-M system with MVE */ +#if defined(__ARM_ARCH_8_1M_MAIN__) || defined(__ARM_FEATURE_MVE) +#define MLK_SYS_ARMV81M_MVE +#endif + +#if defined(__x86_64__) +#define MLK_SYS_X86_64 +#if defined(__AVX2__) +#define MLK_SYS_X86_64_AVX2 +#endif +#endif /* __x86_64__ */ + +#if defined(MLK_SYS_LITTLE_ENDIAN) && defined(__powerpc64__) +#define MLK_SYS_PPC64LE +#endif + +#if defined(__riscv) && defined(__riscv_xlen) && __riscv_xlen == 64 +#define MLK_SYS_RISCV64 +#endif + +#if defined(MLK_SYS_RISCV64) && defined(__riscv_vector) && \ + defined(__riscv_v_intrinsic) +#define MLK_SYS_RISCV64_RVV +#endif + +#if defined(__riscv) && defined(__riscv_xlen) && __riscv_xlen == 32 +#define MLK_SYS_RISCV32 +#endif + +#if defined(_WIN32) +#define MLK_SYS_WINDOWS +#endif + +#if defined(__linux__) +#define MLK_SYS_LINUX +#endif + +#if defined(__APPLE__) +#define MLK_SYS_APPLE +#endif + +#if defined(MLK_FORCE_AARCH64) && !defined(MLK_SYS_AARCH64) +#error "MLK_FORCE_AARCH64 is set, but we don't seem to be on an AArch64 system." +#endif + +#if defined(MLK_FORCE_AARCH64_EB) && !defined(MLK_SYS_AARCH64_EB) +#error \ + "MLK_FORCE_AARCH64_EB is set, but we don't seem to be on an AArch64 system." +#endif + +#if defined(MLK_FORCE_X86_64) && !defined(MLK_SYS_X86_64) +#error "MLK_FORCE_X86_64 is set, but we don't seem to be on an X86_64 system." +#endif + +#if defined(MLK_FORCE_PPC64LE) && !defined(MLK_SYS_PPC64LE) +#error "MLK_FORCE_PPC64LE is set, but we don't seem to be on a PPC64LE system." +#endif + +#if defined(MLK_FORCE_RISCV64) && !defined(MLK_SYS_RISCV64) +#error "MLK_FORCE_RISCV64 is set, but we don't seem to be on a RISCV64 system." +#endif + +#if defined(MLK_FORCE_RISCV32) && !defined(MLK_SYS_RISCV32) +#error "MLK_FORCE_RISCV32 is set, but we don't seem to be on a RISCV32 system." +#endif + +/* + * MLK_INLINE: Hint for inlining. + * - MSVC: __inline + * - C99+: inline + * - GCC/Clang C90: __attribute__((unused)) to silence warnings + * - Other C90: empty + */ +#if !defined(MLK_INLINE) +#if defined(_MSC_VER) +#define MLK_INLINE __inline +#elif defined(inline) || \ + (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) +#define MLK_INLINE inline +#elif defined(__GNUC__) || defined(__clang__) +#define MLK_INLINE __attribute__((unused)) +#else +#define MLK_INLINE +#endif +#endif /* !MLK_INLINE */ + +/* + * MLK_ALWAYS_INLINE: Force inlining. + * - MSVC: __forceinline + * - GCC/Clang C99+: MLK_INLINE __attribute__((always_inline)) + * - Other: MLK_INLINE (no forced inlining) + */ +#if !defined(MLK_ALWAYS_INLINE) +#if defined(_MSC_VER) +#define MLK_ALWAYS_INLINE __forceinline +#elif (defined(__GNUC__) || defined(__clang__)) && \ + (defined(inline) || \ + (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L)) +#define MLK_ALWAYS_INLINE MLK_INLINE __attribute__((always_inline)) +#else +#define MLK_ALWAYS_INLINE MLK_INLINE +#endif +#endif /* !MLK_ALWAYS_INLINE */ + +#ifndef MLK_STATIC_TESTABLE +#define MLK_STATIC_TESTABLE static +#endif + +/* + * C90 does not have the restrict compiler directive yet. + * We don't use it in C90 builds. + */ +#if !defined(restrict) +#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L +#define MLK_RESTRICT restrict +#else +#define MLK_RESTRICT +#endif + +#else /* !restrict */ + +#define MLK_RESTRICT restrict +#endif /* restrict */ + +#define MLK_DEFAULT_ALIGN 32 +#define MLK_ALIGN_UP(N) \ + ((((N) + (MLK_DEFAULT_ALIGN - 1)) / MLK_DEFAULT_ALIGN) * MLK_DEFAULT_ALIGN) +#if defined(__GNUC__) +#define MLK_ALIGN __attribute__((aligned(MLK_DEFAULT_ALIGN))) +#elif defined(_MSC_VER) +#define MLK_ALIGN __declspec(align(MLK_DEFAULT_ALIGN)) +#else +#define MLK_ALIGN /* No known support for alignment constraints */ +#endif + + +/* New X86_64 CPUs support Conflow-flow protection using the CET instructions. + * When enabled (through -fcf-protection=), all compilation units (including + * empty ones) need to support CET for this to work. + * For assembly, this means that source files need to signal support for + * CET by setting the appropriate note.gnu.property section. + * This can be achieved by including the header in all assembly file. + * This file also provides the _CET_ENDBR macro which needs to be placed at + * every potential target of an indirect branch. + * If CET is enabled _CET_ENDBR maps to the endbr64 instruction, otherwise + * it is empty. + * In case the compiler does not support CET (e.g., +#define MLK_CET_ENDBR _CET_ENDBR +#else +#define MLK_CET_ENDBR +#endif +#endif /* MLK_SYS_X86_64 */ + +#if defined(MLK_CONFIG_CT_TESTING_ENABLED) && !defined(__ASSEMBLER__) +#include +#define MLK_CT_TESTING_SECRET(ptr, len) \ + VALGRIND_MAKE_MEM_UNDEFINED((ptr), (len)) +#define MLK_CT_TESTING_DECLASSIFY(ptr, len) \ + VALGRIND_MAKE_MEM_DEFINED((ptr), (len)) +#else /* MLK_CONFIG_CT_TESTING_ENABLED && !__ASSEMBLER__ */ +#define MLK_CT_TESTING_SECRET(ptr, len) \ + do \ + { \ + } while (0) +#define MLK_CT_TESTING_DECLASSIFY(ptr, len) \ + do \ + { \ + } while (0) +#endif /* !(MLK_CONFIG_CT_TESTING_ENABLED && !__ASSEMBLER__) */ + +#if defined(__GNUC__) || defined(__clang__) +#define MLK_MUST_CHECK_RETURN_VALUE __attribute__((warn_unused_result)) +#else +#define MLK_MUST_CHECK_RETURN_VALUE +#endif + +#if !defined(__ASSEMBLER__) +/* System capability enumeration */ +typedef enum +{ + /* x86_64 */ + MLK_SYS_CAP_AVX2, + /* AArch64 */ + MLK_SYS_CAP_SHA3 +} mlk_sys_cap; + +#if !defined(MLK_CONFIG_CUSTOM_CAPABILITY_FUNC) +#include "cbmc.h" + +MLK_MUST_CHECK_RETURN_VALUE +static MLK_INLINE int mlk_sys_check_capability(mlk_sys_cap cap) +__contract__( + ensures(return_value == 0 || return_value == 1) +) +{ + /* By default, we rely on compile-time feature detection/specification: + * If a feature is enabled at compile-time, we assume it is supported by + * the host that the resulting library/binary will be built on. + * If this assumption is not true, you MUST overwrite this function. + * See the documentation of MLK_CONFIG_CUSTOM_CAPABILITY_FUNC in + * mlkem_native_config.h for more information. */ + (void)cap; + return 1; +} +#endif /* !MLK_CONFIG_CUSTOM_CAPABILITY_FUNC */ +#endif /* !__ASSEMBLER__ */ + +#endif /* !MLK_SYS_H */ diff --git a/mlkem_native/src/verify.c b/mlkem_native/src/verify.c new file mode 100644 index 0000000..db760f7 --- /dev/null +++ b/mlkem_native/src/verify.c @@ -0,0 +1,20 @@ +/* + * Copyright (c) The mlkem-native project authors + * SPDX-License-Identifier: Apache-2.0 OR ISC OR MIT + */ +#include "verify.h" + +#if !defined(MLK_USE_ASM_VALUE_BARRIER) && \ + !defined(MLK_CONFIG_MULTILEVEL_NO_SHARED) +/* + * Masking value used in constant-time functions from + * verify.h to block the compiler's range analysis and + * thereby reduce the risk of compiler-introduced branches. + */ +volatile uint64_t mlk_ct_opt_blocker_u64 = 0; + +#else /* !MLK_USE_ASM_VALUE_BARRIER && !MLK_CONFIG_MULTILEVEL_NO_SHARED */ + +MLK_EMPTY_CU(verify) + +#endif /* !(!MLK_USE_ASM_VALUE_BARRIER && !MLK_CONFIG_MULTILEVEL_NO_SHARED) */ diff --git a/mlkem_native/src/verify.h b/mlkem_native/src/verify.h new file mode 100644 index 0000000..a9bdeaa --- /dev/null +++ b/mlkem_native/src/verify.h @@ -0,0 +1,464 @@ +/* + * Copyright (c) The mlkem-native project authors + * SPDX-License-Identifier: Apache-2.0 OR ISC OR MIT + */ + +/* References + * ========== + * + * - [FIPS203] + * FIPS 203 Module-Lattice-Based Key-Encapsulation Mechanism Standard + * National Institute of Standards and Technology + * https://csrc.nist.gov/pubs/fips/203/final + * + * - [REF] + * CRYSTALS-Kyber C reference implementation + * Bos, Ducas, Kiltz, Lepoint, Lyubashevsky, Schanck, Schwabe, Seiler, StehlΓ© + * https://github.com/pq-crystals/kyber/tree/main/ref + * + * - [libmceliece] + * libmceliece implementation of Classic McEliece + * Bernstein, Chou + * https://lib.mceliece.org/ + * + * - [optblocker] + * PQC forum post on opt-blockers using volatile globals + * Daniel J. Bernstein + * https://groups.google.com/a/list.nist.gov/g/pqc-forum/c/hqbtIGFKIpU/m/H14H0wOlBgAJ + */ + +#ifndef MLK_VERIFY_H +#define MLK_VERIFY_H + + +#include "cbmc.h" +#include "common.h" + +/* Constant-time comparisons and conditional operations + + We reduce the risk for compilation into variable-time code + through the use of 'value barriers'. + + Functionally, a value barrier is a no-op. To the compiler, however, + it constitutes an arbitrary modification of its input, and therefore + harden's value propagation and range analysis. + + We consider two approaches to implement a value barrier: + - An empty inline asm block which marks the target value as clobbered. + - XOR'ing with the value of a volatile global that's set to 0; + see @[optblocker] for a discussion of this idea, and + @[libmceliece, inttypes/crypto_intN.h] for an implementation. + + The first approach is cheap because it only prevents the compiler + from reasoning about the value of the variable past the barrier, + but does not directly generate additional instructions. + + The second approach generates redundant loads and XOR operations + and therefore comes at a higher runtime cost. However, it appears + more robust towards optimization, as compilers should never drop + a volatile load. + + We use the empty-ASM value barrier for GCC and clang, and fall + back to the global volatile barrier otherwise. + + The global value barrier can be forced by setting + MLK_CONFIG_NO_ASM_VALUE_BARRIER. + +*/ + +#if defined(MLK_HAVE_INLINE_ASM) && !defined(MLK_CONFIG_NO_ASM_VALUE_BARRIER) +#define MLK_USE_ASM_VALUE_BARRIER +#endif + +#if !defined(MLK_USE_ASM_VALUE_BARRIER) + +/* + * Declaration of global volatile that the global value barrier + * is loading from and masking with. + */ +#define mlk_ct_opt_blocker_u64 MLK_NAMESPACE(ct_opt_blocker_u64) +extern volatile uint64_t mlk_ct_opt_blocker_u64; + +/* Helper functions for obtaining global masks of various sizes */ + +/* This contract is not proved but treated as an axiom. + * + * Its validity relies on the assumption that the global opt-blocker + * constant mlk_ct_opt_blocker_u64 is not modified. + */ +static MLK_INLINE uint64_t mlk_ct_get_optblocker_u64(void) +__contract__(ensures(return_value == 0)) { return mlk_ct_opt_blocker_u64; } + +static MLK_INLINE uint8_t mlk_ct_get_optblocker_u8(void) +__contract__(ensures(return_value == 0)) { return (uint8_t)mlk_ct_get_optblocker_u64(); } + +static MLK_INLINE uint32_t mlk_ct_get_optblocker_u32(void) +__contract__(ensures(return_value == 0)) { return (uint32_t)mlk_ct_get_optblocker_u64(); } + +static MLK_INLINE int32_t mlk_ct_get_optblocker_i32(void) +__contract__(ensures(return_value == 0)) { return (int32_t)mlk_ct_get_optblocker_u64(); } + +/* Opt-blocker based implementation of value barriers */ +static MLK_INLINE uint32_t mlk_value_barrier_u32(uint32_t b) +__contract__(ensures(return_value == b)) { return (b ^ mlk_ct_get_optblocker_u32()); } + +static MLK_INLINE int32_t mlk_value_barrier_i32(int32_t b) +__contract__(ensures(return_value == b)) { return (b ^ mlk_ct_get_optblocker_i32()); } + +static MLK_INLINE uint8_t mlk_value_barrier_u8(uint8_t b) +__contract__(ensures(return_value == b)) { return (b ^ mlk_ct_get_optblocker_u8()); } + +#else /* !MLK_USE_ASM_VALUE_BARRIER */ + +static MLK_INLINE uint32_t mlk_value_barrier_u32(uint32_t b) +__contract__(ensures(return_value == b)) +{ + __asm__ volatile("" : "+r"(b)); + return b; +} + +static MLK_INLINE int32_t mlk_value_barrier_i32(int32_t b) +__contract__(ensures(return_value == b)) +{ + __asm__ volatile("" : "+r"(b)); + return b; +} + +static MLK_INLINE uint8_t mlk_value_barrier_u8(uint8_t b) +__contract__(ensures(return_value == b)) +{ + __asm__ volatile("" : "+r"(b)); + return b; +} + +#endif /* MLK_USE_ASM_VALUE_BARRIER */ + +#ifdef CBMC +#pragma CPROVER check push +#pragma CPROVER check disable "conversion" +#endif +/************************************************* + * Name: mlk_cast_uint16_to_int16 + * + * Description: Cast uint16 value to int16 + * + * Returns: For uint16_t x, the unique y in int16_t + * so that x == y mod 2^16. + * + * Concretely: + * - x < 32768: returns x + * - x >= 32768: returns x - 65536 + * + **************************************************/ +static MLK_ALWAYS_INLINE int16_t mlk_cast_uint16_to_int16(uint16_t x) +{ + /* + * PORTABILITY: This relies on uint16_t -> int16_t + * being implemented as the inverse of int16_t -> uint16_t, + * which is implementation-defined (C99 6.3.1.3 (3)) + * CBMC (correctly) fails to prove this conversion is OK, + * so we have to suppress that check here + */ + return (int16_t)x; +} +#ifdef CBMC +#pragma CPROVER check pop +#endif + +/************************************************* + * Name: mlk_cast_int32_to_uint16 + * + * Description: Cast int32 value to uint16 as per C standard. + * + * Returns: For int32_t x, the unique y in uint16_t + * so that x == y mod 2^16. + **************************************************/ +static MLK_ALWAYS_INLINE uint16_t mlk_cast_int32_to_uint16(int32_t x) +{ + return (uint16_t)(x & (int32_t)UINT16_MAX); +} + +/************************************************* + * Name: mlk_cast_int16_to_uint16 + * + * Description: Cast int16 value to uint16 as per C standard. + * + * Returns: For int16_t x, the unique y in uint16_t + * so that x == y mod 2^16. + **************************************************/ +static MLK_ALWAYS_INLINE uint16_t mlk_cast_int16_to_uint16(int32_t x) +{ + return mlk_cast_int32_to_uint16((int32_t)x); +} + +/************************************************* + * Name: mlk_ct_cmask_neg_i16 + * + * Description: Return 0 if input is non-negative, and -1 otherwise. + * + * Arguments: uint16_t x: Value to be converted into a mask + * + **************************************************/ + +/* Reference: Embedded in polynomial compression function in the + * reference implementation @[REF]. + * - Used as part of signed->unsigned conversion for modular + * representatives to detect whether the input is negative. + * This happen in `mlk_poly_reduce()` here, and as part of + * polynomial compression functions in the reference + * implementation. See `mlk_poly_reduce()`. + * - We use value barriers to reduce the risk of + * compiler-introduced branches. */ +static MLK_INLINE uint16_t mlk_ct_cmask_neg_i16(int16_t x) +__contract__(ensures(return_value == ((x < 0) ? 0xFFFF : 0))) +{ + int32_t tmp = mlk_value_barrier_i32((int32_t)x); + tmp >>= 16; + return mlk_cast_int32_to_uint16(tmp); +} + +/************************************************* + * Name: mlk_ct_cmask_nonzero_u16 + * + * Description: Return 0 if input is zero, and -1 otherwise. + * + * Arguments: uint16_t x: Value to be converted into a mask + * + **************************************************/ + +/* Reference: Embedded in `cmov_int16()` in the reference implementation @[REF]. + * - Use value barrier and shift instead of `b = -b` to + * convert condition into mask. */ +static MLK_INLINE uint16_t mlk_ct_cmask_nonzero_u16(uint16_t x) +__contract__(ensures(return_value == ((x == 0) ? 0 : 0xFFFF))) +{ + int32_t tmp = mlk_value_barrier_i32(-((int32_t)x)); + tmp >>= 16; + return mlk_cast_int32_to_uint16(tmp); +} + +/************************************************* + * Name: mlk_ct_cmask_nonzero_u8 + * + * Description: Return 0 if input is zero, and -1 otherwise. + * + * Arguments: uint8_t x: Value to be converted into a mask + * + **************************************************/ + +/* Reference: Embedded in `verify()` and `cmov()` in the + * reference implementation @[REF]. + * - We include a value barrier not present in the + * reference implementation, to prevent the compiler + * from realizing that this function returns a mask. */ +static MLK_INLINE uint8_t mlk_ct_cmask_nonzero_u8(uint8_t x) +__contract__(ensures(return_value == ((x == 0) ? 0 : 0xFF))) +{ + uint16_t mask = mlk_ct_cmask_nonzero_u16((uint16_t)x); + return (uint8_t)(mask & 0xFF); +} + +/************************************************* + * Name: mlk_ct_sel_int16 + * + * Description: Functionally equivalent to cond ? a : b, + * but implemented with guards against + * compiler-introduced branches. + * + * Arguments: int16_t a: First alternative + * int16_t b: Second alternative + * uint16_t cond: Condition variable. + * + * Specification: + * - With `a = MLKEM_Q_HALF` and `b=0`, this essentially + * implements `Decompress_1` @[FIPS203, Eq (4.8)] in `mlk_poly_frommsg()`. + * - With `a = x + MLKEM_Q`, `b = x`, and `cond` indicating whether `x` + * is negative, implements signed->unsigned conversion of modular + * representatives. Questions of representation are not considered + * in the specification @[FIPS203, Section 2.4.1, "The pseudocode is + * agnostic regarding how an integer modulo π‘š is represented in + * actual implementations"]. + * + **************************************************/ + +/* Reference: Embedded in polynomial compression function in the + * reference implementation @[REF]. + * - Used as part of signed->unsigned conversion for modular + * representatives. This happen in `mlk_poly_reduce()` here, + * and as part of polynomial compression functions in @[REF]. + * See `mlk_poly_reduce()`. + * - Barrier to reduce the risk of compiler-introduced branches. + * For `a = MLKEM_Q_HALF` and `b=0`, also embedded in + * `poly_frommsg()` from the reference implementation, which uses + * `cmov_int16()` instead. */ +static MLK_INLINE int16_t mlk_ct_sel_int16(int16_t a, int16_t b, uint16_t cond) +__contract__(ensures(return_value == (cond ? a : b))) +{ + uint16_t au = mlk_cast_int16_to_uint16(a); + uint16_t bu = mlk_cast_int16_to_uint16(b); + uint16_t res = bu ^ (mlk_ct_cmask_nonzero_u16(cond) & (au ^ bu)); + return mlk_cast_uint16_to_int16(res); +} + +/************************************************* + * Name: mlk_ct_sel_uint8 + * + * Description: Functionally equivalent to cond ? a : b, + * but implemented with guards against + * compiler-introduced branches. + * + * Arguments: uint8_t a: First alternative + * uint8_t b: Second alternative + * uuint8_t cond: Condition variable. + * + **************************************************/ + +/* Reference: Embedded into `cmov()` in the reference implementation @[REF]. + * - Use value barrier to get mask from condition value. */ +static MLK_INLINE uint8_t mlk_ct_sel_uint8(uint8_t a, uint8_t b, uint8_t cond) +__contract__(ensures(return_value == (cond ? a : b))) +{ + return b ^ (mlk_ct_cmask_nonzero_u8(cond) & (a ^ b)); +} + +/************************************************* + * Name: mlk_ct_memcmp + * + * Description: Compare two arrays for equality in constant time. + * + * Arguments: const uint8_t *a: pointer to first byte array + * const uint8_t *b: pointer to second byte array + * size_t len: length of the byte arrays, upper-bounded + * to UINT16_MAX to control proof complexity + * only. + * + * Returns 0 if the byte arrays are equal, 0xFF otherwise. + * + * Specification: + * - Used to securely compute conditional move in + * @[FIPS203, Algorithm 18 (ML-KEM.Decaps_Internal, L9-11] + * + **************************************************/ + +/* Reference: `cmov()` in the reference implementation @[REF] + * - We return `uint8_t`, not `int`. + * - We use an additional XOR-accumulator in the comparison loop + * which prevents early abort if the OR-accumulator is 0xFF. + * - We use a value barrier to convert the OR-accumulator into + * a mask. The reference implementation uses a shift which the + * compiler can argue to result in either 0 of 0xFF..FF. */ +static MLK_INLINE uint8_t mlk_ct_memcmp(const uint8_t *a, const uint8_t *b, + const size_t len) +__contract__( + requires(len <= UINT16_MAX) + requires(memory_no_alias(a, len)) + requires(memory_no_alias(b, len)) + ensures((return_value == 0) || (return_value == 0xFF)) + ensures((return_value == 0) == forall(i, 0, len, (a[i] == b[i])))) +{ + uint8_t r = 0, s = 0; + unsigned i; + + for (i = 0; i < len; i++) + __loop__( + invariant(i <= len) + invariant((r == 0) == (forall(k, 0, i, (a[k] == b[k]))))) + { + r |= a[i] ^ b[i]; + /* s is useless, but prevents the loop from being aborted once r=0xff. */ + s ^= a[i] ^ b[i]; + } + + /* + * - Convert r into a mask; this may not be necessary, but is an additional + * safeguard + * towards leaking information about a and b. + * - XOR twice with s, separated by a value barrier, to prevent the compile + * from dropping the s computation in the loop. + */ + return (mlk_value_barrier_u8(mlk_ct_cmask_nonzero_u8(r) ^ s) ^ s); +} + +/************************************************* + * Name: mlk_ct_cmov_zero + * + * Description: Copy len bytes from x to r if b is zero; + * don't modify x if b is non-zero. + * assumes two's complement representation of negative integers. + * Runs in constant time. + * + * Arguments: uint8_t *r: pointer to output byte array + * const uint8_t *x: pointer to input byte array + * size_t len: Amount of bytes to be copied + * uint8_t b: Condition value. + * + * Specification: + * - Used to securely compute conditional move in + * @[FIPS203, Algorithm 18 (ML-KEM.Decaps_Internal, L9-11] + * + **************************************************/ + +/* Reference: `cmov()` in the reference implementation @[REF]. + * - We move if condition value is `0`, not `1`. + * - We use `mlk_ct_sel_uint8` for constant-time selection. */ +static MLK_INLINE void mlk_ct_cmov_zero(uint8_t *r, const uint8_t *x, + size_t len, uint8_t b) +__contract__( + requires(len <= MLK_MAX_BUFFER_SIZE) + requires(memory_no_alias(r, len)) + requires(memory_no_alias(x, len)) + assigns(memory_slice(r, len)) + ensures(forall(i, 0, len, (r[i] == (b == 0 ? x[i] : old(r)[i]))))) +{ + size_t i; + for (i = 0; i < len; i++) + __loop__( + invariant(i <= len) + invariant(forall(k, 0, i, r[k] == (b == 0 ? x[k] : loop_entry(r)[k])))) + { + r[i] = mlk_ct_sel_uint8(r[i], x[i], b); + } +} + +/************************************************* + * Name: mlk_zeroize + * + * Description: Force-zeroize a buffer. + * + * Arguments: uint8_t *r: pointer to byte array to be zeroed + * size_t len: Amount of bytes to be zeroed + * + * Specification: Used to implement + * @[FIPS203, Section 3.3, Destruction of intermediate values] + * + **************************************************/ + +/* Reference: Not present in the reference implementation @[REF]. */ +#if !defined(MLK_CONFIG_CUSTOM_ZEROIZE) +#if defined(MLK_SYS_WINDOWS) +#include +static MLK_INLINE void mlk_zeroize(void *ptr, size_t len) +__contract__( + requires(memory_no_alias(ptr, len)) + assigns(memory_slice(ptr, len))) { SecureZeroMemory(ptr, len); } +#elif defined(MLK_HAVE_INLINE_ASM) +#include +static MLK_INLINE void mlk_zeroize(void *ptr, size_t len) +__contract__( + requires(memory_no_alias(ptr, len)) + assigns(memory_slice(ptr, len))) +{ + mlk_memset(ptr, 0, len); + /* This follows OpenSSL and seems sufficient to prevent the compiler + * from optimizing away the memset. + * + * If there was a reliable way to detect availability of memset_s(), + * that would be preferred. */ + __asm__ volatile("" : : "r"(ptr) : "memory"); +} +#else /* !MLK_SYS_WINDOWS && MLK_HAVE_INLINE_ASM */ +#error No plausibly-secure implementation of mlk_zeroize available. Please provide your own using MLK_CONFIG_CUSTOM_ZEROIZE. +#endif /* !MLK_SYS_WINDOWS && !MLK_HAVE_INLINE_ASM */ +#endif /* !MLK_CONFIG_CUSTOM_ZEROIZE */ + +#endif /* !MLK_VERIFY_H */ diff --git a/mlkem_native/src/zetas.inc b/mlkem_native/src/zetas.inc new file mode 100644 index 0000000..00316da --- /dev/null +++ b/mlkem_native/src/zetas.inc @@ -0,0 +1,30 @@ +/* + * Copyright (c) The mlkem-native project authors + * SPDX-License-Identifier: Apache-2.0 OR ISC OR MIT + */ + +/* + * WARNING: This file is auto-generated from scripts/autogen + * in the mlkem-native repository. + * Do not modify it directly. + */ + + +/* + * Table of zeta values used in the reference NTT and inverse NTT. + * See autogen for details. + */ +static MLK_ALIGN const int16_t mlk_zetas[128] = { + -1044, -758, -359, -1517, 1493, 1422, 287, 202, -171, 622, 1577, + 182, 962, -1202, -1474, 1468, 573, -1325, 264, 383, -829, 1458, + -1602, -130, -681, 1017, 732, 608, -1542, 411, -205, -1571, 1223, + 652, -552, 1015, -1293, 1491, -282, -1544, 516, -8, -320, -666, + -1618, -1162, 126, 1469, -853, -90, -271, 830, 107, -1421, -247, + -951, -398, 961, -1508, -725, 448, -1065, 677, -1275, -1103, 430, + 555, 843, -1251, 871, 1550, 105, 422, 587, 177, -235, -291, + -460, 1574, 1653, -246, 778, 1159, -147, -777, 1483, -602, 1119, + -1590, 644, -872, 349, 418, 329, -156, -75, 817, 1097, 603, + 610, 1322, -1285, -1465, 384, -1215, -136, 1218, -1335, -874, 220, + -1187, -1659, -1185, -1530, -1278, 794, -1510, -854, -870, 478, -108, + -308, 996, 991, 958, -1460, 1522, 1628, +}; diff --git a/onlykey/BUILD.md b/onlykey/BUILD.md new file mode 100644 index 0000000..30aac45 --- /dev/null +++ b/onlykey/BUILD.md @@ -0,0 +1,57 @@ +# Building the PQC firmware (composite ML-KEM-768 + ML-DSA-65) + +Status: **compiles + links + runs on host** (crypto core verified). UNTESTED on the MK20DX256 β€” +needs a Teensyduino build + hardware pass. + +## What's in the tree +Vendored at the **repo root** (same as `mbedtls-2.4.0`, `mlkem_native`): +- `mlkem_native/` β€” ML-KEM-768 (reused from `feature/mlkem-768`). +- `mldsa_native/` β€” ML-DSA-65, configured for OnlyKey (see below). Native asm backends removed + (pure C for Cortex-M4). + +In `onlykey/`: +- `okpqc.h` / `okpqc.cpp` β€” the composite PQC ops (new). +- `okcore.h`, `okcore.cpp`, `okcrypto.cpp` β€” **edited** (18 lines total; see `firmware-edits.patch`). + +## mldsa_native configuration (already applied in the vendored copy) +`mldsa_native/mldsa_native_config.h`: +- `MLD_CONFIG_PARAMETER_SET = 65` (ML-DSA-65) +- `MLD_CONFIG_REDUCE_RAM` β€” **required**: signing workspace ~17 KB vs ~69 KB without (64 KB RAM) +- `MLD_CONFIG_CUSTOM_RANDOMBYTES` β†’ bridges to `onlykey_mldsa_randombytes` (defined in `okpqc.cpp`, + wraps the firmware `RNG`). `mlkem_native` bridges to `onlykey_mlkem_randombytes` likewise. + +## Build model (matches mlkem_native) +The OnlyKey build compiles each library's **root** monolithic `.c` (which `#include`s its `src/`); +the `src/` subfolder is not compiled separately. So the build compiles: +- `mlkem_native/mlkem_native.c` (define `MLK_CONFIG_PARAMETER_SET=768` if not set in a config) +- `mldsa_native/mldsa_native.c` +- `onlykey/okpqc.cpp` and the rest of `onlykey/`. + +`okpqc.cpp` needs the Arduino Crypto library headers already used by the firmware: +`Ed25519.h`, `Curve25519.h`, `RNG.h`. + +## Host compile-check performed +``` +gcc -O2 -std=c11 -I mlkem_native -DMLK_CONFIG_PARAMETER_SET=768 mlkem_native/mlkem_native.c -c +gcc -O2 -std=c11 -I mldsa_native mldsa_native/mldsa_native.c -c +g++ -O2 -std=gnu++11 -I onlykey -I onlykey/okpqc.cpp -c +# link with the firmware -> resolves; a KEM+signature round-trip passes: +# ML-KEM-768 decaps match: YES +# ML-DSA-65 siglen=3309 verify: OK +``` + +## Integration (in `firmware-edits.patch`, already applied to the tree) +- `okcore.h`: `#include "okpqc.h"` (brings `KEYTYPE_PQC_PGP`=7, `PQC_PGP_BLOB_LEN`=160, op decls). +- `okcore_flashget_RSA`: read the 160-byte blob for a type-7 slot (no `rsa_getpub`). +- `OKSETRSAPRIV`: accept the 160-byte blob (`type==7`). +- `okcrypto_sign` / `okcrypto_decrypt`: dispatch type-7 slots to `okpqc_sign` / `okpqc_decrypt`. + +## Flash budget β€” the one thing to watch on the real build +256 KB flash total; the firmware is already large and adds `mlkem_native` + `mldsa_native` +(tens of KB compiled). Check the `.map`/size output after the first Teensy build. RAM is fine +(sign ~17 KB, decaps ~14 KB; the 18 KB backup buffer isn't allocated during these ops). + +## Remaining hardware validation +- ML-DSA message framing (`[0,0]||digest`) vs openpgp.js `ml_dsa.js` (see note in `okpqc.cpp`). +- 3309-byte signature response fragmentation over CTAP. +- Full round-trip: import a 160-byte composite key β†’ sign/verify + encrypt/decrypt via openpgp.js. diff --git a/onlykey/DESIGN.md b/onlykey/DESIGN.md new file mode 100644 index 0000000..7c3ed51 --- /dev/null +++ b/onlykey/DESIGN.md @@ -0,0 +1,145 @@ +# OnlyKey firmware: composite PQC PGP keys in RSA slots (new branch) + +**Model:** one RSA slot (1–4) holds **one imported composite PQC PGP key** β€” all four private +seed components in a single ~160-byte blob. OnlyKey supports **up to 4 PQC keys** (slots 1–4), +each either RSA or a composite PQC key. + +Import-first: the host (python-onlykey / lib-agent) loads a PGP key's private seeds into a slot. +The device stores only the seeds (~160 B), expands them at use time, and performs sign/decrypt. +The composite **public key is never stored** (3200 B ≫ 512 B slot); the host holds it, and it can +be re-derived on demand from the seeds if ever needed. + +`feature/mlkem-768` is a **reference only** (reuse `mlkem_native` and the ML-KEM decaps structure); +this is a new branch that does not use that ECC-slot approach. + +--- + +## 1. Slot contents (160 bytes, fixed layout) + +A composite PGP key = ML-DSA+Ed25519 signing primary + ML-KEM+X25519 encryption subkey. OpenPGP +stores each private key as its **seed** (verified against draft-ietf-openpgp-pqc / openpgp.js): + +| offset | bytes | component | role | +|---|---|---|---| +| `[0:32]` | 32 | Ed25519 secret | sign, ecc half | +| `[32:64]` | 32 | ML-DSA-65 seed (ΞΎ) | sign, pqc half | +| `[64:96]` | 32 | X25519 secret | decrypt, ecc half | +| `[96:160]` | 64 | ML-KEM-768 seed (dβ€–z) | decrypt, pqc half | + +Total **160 B**, well within the 512-byte RSA slot. Stored AES-GCM-encrypted like an RSA key. + +> The ML-KEM seed is used **directly** as the keygen coins `(d,z)` β€” no SHAKE(32β†’64). (That SHAKE +> trick was only for `feature/mlkem-768`'s device-generated 32-byte seeds; imported keys carry the +> real 64-byte seed.) ML-DSA seed (32 B) and the two ECC secrets (32 B each) are used as-is. + +--- + +## 2. Key type + storage (mirror RSA) + +```c +// okcore.h +#define KEYTYPE_PQC_PGP 7 // slot 1-4 holds a composite PQC PGP key (160-byte seed blob) +#define PQC_PGP_BLOB_LEN 160 +// component offsets +#define PQC_OFF_ED25519 0 +#define PQC_OFF_MLDSA_SEED 32 +#define PQC_OFF_X25519 64 +#define PQC_OFF_MLKEM_SEED 96 +#define MLDSA65_SEED_LEN 32 +#define MLKEM768_SEED_LEN 64 +``` +Per-slot EEPROM type byte low nibble = 7. Feature bits: this key is both sign- and decrypt-capable +(bit 5 + bit 6), since the composite has both a signing and an encryption half. + +Load (mirror `OKSETRSAPRIV`): a `==7` branch, `keysize = 160`, accumulate the blob in 57-byte chunks +into `rsa_private_key`, then `okeeprom_eeset_rsakey` + `okcore_aes_gcm_encrypt(..., 160)` + slot write. +Flashget (mirror `okcore_flashget_RSA`): read/decrypt 160 B, leave `type = 7`, no `rsa_getpub`. + +--- + +## 3. Which half? β€” component selector + +The composite's halves are independent operations that openpgp.js already requests as **separate +hooks** (`ecdh`, `mlkemDecaps`, `sign_ecc`, ML-DSA `signer`). For sign, both halves take the same +digest, so input size can't disambiguate β†’ the request carries an explicit **component selector**: + +``` +buffer[6] (sub-op): 0 = ECC half (Ed25519 / X25519) 1 = PQC half (ML-DSA / ML-KEM) +``` +(For decrypt the halves are also distinguishable by input size β€” 32-B ephemeral vs 1088-B ct β€” but +the explicit selector keeps sign/decrypt uniform.) + +--- + +## 4. Sign dispatch (OKSIGN, slot 1–4, type 7) +```c +if ((type & 0x0F) == KEYTYPE_PQC_PGP) { okcrypto_pqc_pgp_sign(buffer); return; } // in okcrypto_sign +``` +`okcrypto_pqc_pgp_sign`: +- selector ECC β†’ `ed25519_sign(sig64, digest, rsa_private_key + PQC_OFF_ED25519)` β†’ 64 B. +- selector PQC β†’ expand `rsa_private_key + PQC_OFF_MLDSA_SEED` (32 B) β†’ SK 4032 β†’ `mldsa65_sign` β†’ + **3309 B** signature (returned chunked from the large response region). + +## 5. Decrypt dispatch (OKDECRYPT, slot 1–4, type 7) +```c +if ((type & 0x0F) == KEYTYPE_PQC_PGP) { okcrypto_pqc_pgp_decrypt(buffer); return; } // in okcrypto_decrypt +``` +`okcrypto_pqc_pgp_decrypt`: +- selector ECC β†’ `x25519(ss32, ephemeral, rsa_private_key + PQC_OFF_X25519)` β†’ 32-B shared secret. +- selector PQC β†’ expand `rsa_private_key + PQC_OFF_MLKEM_SEED` (64 B) β†’ DK 2400 β†’ `mlkem768_dec` on + the 1088-B ct β†’ 32-B shared secret. + +openpgp.js does the KMAC combine (decrypt) / signature concatenation (sign); the device returns raw +component outputs. No secret leaves the device. + +--- + +## 6. Implementation, primitives & memory (target: NXP MK20DX256) + +Target MCU: **NXP MK20DX256** (Teensy 3.2 class) β€” Cortex-M4 @ 72 MHz, **256 KB flash, 64 KB +RAM**, 2 KB EEPROM, pure portable C (no NEON/asm backend). The whole implementation is the +single consolidated pair **`okpqc.h` / `okpqc.cpp`** (supersedes the earlier +`okcrypto_pqc_pgp.*` + `mlkem768.h`/`mldsa65.h`/`pqc_shim.c` split). + +| primitive | library | on-device workspace | +|---|---|---| +| Ed25519 sign, X25519 | **existing firmware** (thin `okpqc_ed25519_sign` / `okpqc_x25519_shared` wrappers) | negligible | +| ML-KEM-768 keygen-from-seed + decaps | **`mlkem-native`** (reuse the tree from `feature/mlkem-768`) | decaps **~14 KB** | +| ML-DSA-65 keygen-from-seed + sign | **`mldsa-native`** (PQCA, portable C90, formally verified) | sign **~17 KB with `MLD_CONFIG_REDUCE_RAM`** | +| SHA3/SHAKE | bundled with mlkem-native / mldsa-native | β€” | + +**`MLD_CONFIG_REDUCE_RAM` is mandatory:** ML-DSA-65 signing is **69,312 B without it β€” larger +than the 64 KB RAM** β€” and **17,248 B with it.** Both crypto ops run when the 18 KB backup buffer +is not allocated, so ~40+ KB is free; comfortable. (Why not Mbed-TLS: it has no shipping PQC yet β€” +ML-DSA is roadmapped for 2026 Q2, ML-KEM later. Why not wolfCrypt: a whole second TLS stack for two +primitives; mlkem-native/mldsa-native are the minimal, verified, RAM-tunable fit and you already +use mlkem-native.) + +Exact symbols `okpqc.cpp` calls: +```c +// mlkem-native (same calls feature/mlkem-768 uses): +int crypto_kem_keypair_derand(uint8_t *pk, uint8_t *dk, const uint8_t *coins /*64B seed*/); +int crypto_kem_dec(uint8_t *ss, const uint8_t *ct, const uint8_t *dk); +// mldsa-native (MLD_CONFIG_PARAMETER_SET=65): +int PQCP_MLDSA_NATIVE_MLDSA65_keypair_internal(uint8_t *pk, uint8_t *sk, const uint8_t seed[32]); +int PQCP_MLDSA_NATIVE_MLDSA65_signature(uint8_t *sig, size_t *siglen, + const uint8_t *m, size_t mlen, const uint8_t *ctx, size_t ctxlen, const uint8_t *sk); +``` + +## 7. Response sizing +- ML-DSA signature is **3309 B** > `LARGE_RESP_BUFFER_SIZE` (1024). Return from a β‰₯3309-B region + (the 18000-B `large_temp`/`ctap_buffer` scratch); the transport already fragments long responses. +- ML-KEM ct in is 1088 B; reassembles via the RSA `large_buffer` path (bump `LARGE_BUFFER_SIZE` + 1024β†’1152 if needed). + +## 8. Host side (follow-on) +- **python-onlykey / lib-agent:** `OKSETPRIV type=7` loads the 160-B seed blob; the app extracts the + four seeds from an imported PGP secret key (openpgp secret packet: X25519 32, mlkemSeed 64, + Ed25519 32, mldsaSeed 32) and packs them in the fixed layout. No pubkey load (host has it). +- **openpgp.js hooks:** pass the component selector (ECC vs PQC) per hook; `mlkemDecaps` sends the + 1088-B ct β†’ 32-B share; add the ML-DSA `signer` branch (digest β†’ 3309-B sig). + +## 9. New branch +Base off `master`; copy `mlkem_native/` from `feature/mlkem-768`; add `mldsa-native/` (or dilithium); +add `okcrypto_pqc_pgp.{cpp,h}` + the dispatch/load/flashget edits + `KEYTYPE_PQC_PGP`. **UNTESTED β€” +by inspection**, like #30; validate on hardware. diff --git a/onlykey/INTEGRATION.md b/onlykey/INTEGRATION.md new file mode 100644 index 0000000..b22aa7e --- /dev/null +++ b/onlykey/INTEGRATION.md @@ -0,0 +1,71 @@ +# Composite PQC PGP β€” integration into a new branch + +New files: `okcrypto_pqc_pgp.{h,cpp}`, `mlkem768.h`, `mldsa65.h`, `pqc_shim.c`. +Vendored: copy `mlkem_native/` from `feature/mlkem-768`; add `mldsa-native/` (or pq-crystals +dilithium, `DILITHIUM_MODE=3`). Below are the edits to existing files. **UNTESTED β€” by inspection.** + +## okcore.h +```c +#include "okcrypto_pqc_pgp.h" // KEYTYPE_PQC_PGP(7), offsets, sizes +// If the 1088-byte ML-KEM ct exceeds the reassembly cap: +// #define LARGE_BUFFER_SIZE 1152 // was 1024 +``` + +## okcore.cpp β€” `okcore_flashget_RSA`: read the 160-byte blob +After the type read + `type==0` guard, before the RSA `type*128` read: +```c +if ((type & 0x0F) == KEYTYPE_PQC_PGP) { + adr = adr + ((slot * MAX_RSA_KEY_SIZE) - MAX_RSA_KEY_SIZE); + okcore_flashget_common((uint8_t *)rsa_private_key, (unsigned long *)adr, PQC_PGP_BLOB_LEN); + okcore_aes_gcm_decrypt(rsa_private_key, slot, features, profilekey, PQC_PGP_BLOB_LEN); + type = (type & 0x0F); // leave type = 7 for the dispatchers + return features; // no rsa_getpub() +} +``` + +## okcore.cpp β€” `OKSETRSAPRIV`: import the 160-byte blob +```c +else if ((buffer[6] & 0x0F) == KEYTYPE_PQC_PGP) { keysize = PQC_PGP_BLOB_LEN; // 160 + if (buffer[0] != 0xBA && packet_buffer_offset <= (PQC_PGP_BLOB_LEN - 57)) { + memcpy(rsa_private_key + packet_buffer_offset, buffer + 7, 57); packet_buffer_offset += 57; } } +``` +Completion (`okeeprom_eeset_rsakey`, `okcore_aes_gcm_encrypt(..., 160)`, slot write) is unchanged. +The type byte should carry both feature bits (decrypt bit 5 + sign bit 6) since the composite does both. + +## okcrypto.cpp β€” dispatch +`okcrypto_sign` (slot 1-4, after flashget + type!=0): +```c +if ((type & 0x0F) == KEYTYPE_PQC_PGP) { okcrypto_pqc_pgp_sign(buffer); return; } +``` +`okcrypto_decrypt` (slot 1-4, after flashget + type!=0): +```c +if ((type & 0x0F) == KEYTYPE_PQC_PGP) { okcrypto_pqc_pgp_decrypt(buffer); return; } +``` + +## Component selector +The host sets `buffer[6]` = `PQC_HALF_ECC (0)` or `PQC_HALF_PQC (1)` to pick which half of the +composite to run. The glue captures it on the first (user-action) phase. + +## ECC-half primitives +`okcrypto_pqc_pgp.cpp` calls `ok_ed25519_sign()` / `ok_x25519_shared()` β€” wire these to the +firmware's existing Ed25519/X25519 math (the same used by `okcrypto_ecdsa_eddsa` / `okcrypto_ecdh`), +operating on the 32-byte secret at the blob offset instead of an ECC-slot key. (Thin wrappers, a few +lines each.) + +## Build +- Compile `mlkem_native/**` (reused) + `mldsa-native/**` (new) + `pqc_shim.c` + `okcrypto_pqc_pgp.cpp`. +- Gate behind the board `#ifdef` used for RSA (`STD_VERSION`). + +## Response sizing +- ML-DSA signature is 3309 B > `LARGE_RESP_BUFFER_SIZE` (1024). The glue writes it from the + `ctap_buffer` scratch (>= 3309); the transport fragments long responses. + +## Host side +- python-onlykey / lib-agent: `OKSETPRIV type=7` packs the four seeds from an imported OpenPGP + secret key into the 160-byte layout and loads it. No pubkey load. +- openpgp.js hooks: pass the selector per hook; `mlkemDecaps` sends the 1088-B ct; add an ML-DSA + `signer` branch (digest -> 3309-B sig). `ecdh`/`sign_ecc` route to the ECC half. + +## Verify on hardware +- 160-B import round-trip; four component ops; 3309-B response fragmentation; ML-DSA message framing + ([0,0]||digest) matches openpgp.js verify. diff --git a/onlykey/firmware-edits.patch b/onlykey/firmware-edits.patch new file mode 100644 index 0000000..c718f79 --- /dev/null +++ b/onlykey/firmware-edits.patch @@ -0,0 +1,65 @@ +diff --git a/onlykey/okcore.cpp b/onlykey/okcore.cpp +index 827fd61..5baf267 100644 +--- a/onlykey/okcore.cpp ++++ b/onlykey/okcore.cpp +@@ -5061,6 +5061,12 @@ int okcore_flashget_RSA(uint8_t slot) + { + type = (type & 0x0F); + } ++ if (type == KEYTYPE_PQC_PGP) { // composite PQC PGP key: 160-byte seed blob ++ adr = adr + ((slot * MAX_RSA_KEY_SIZE) - MAX_RSA_KEY_SIZE); ++ okcore_flashget_common((uint8_t *)rsa_private_key, (unsigned long *)adr, PQC_PGP_BLOB_LEN); ++ okcore_aes_gcm_decrypt(rsa_private_key, slot, features, profilekey, PQC_PGP_BLOB_LEN); ++ return features; ++ } + #ifdef DEBUG + Serial.print("Type of RSA KEY is "); + Serial.println(type, HEX); +@@ -5159,6 +5165,15 @@ void rsa_priv_flash(uint8_t *buffer, bool wipe) + packet_buffer_offset = packet_buffer_offset + 57; + } + } ++ else if ((buffer[6] & 0x0F) == KEYTYPE_PQC_PGP) ++ { // composite PQC PGP key: 160-byte seed blob ++ keysize = PQC_PGP_BLOB_LEN; ++ if (buffer[0] != 0xBA && packet_buffer_offset < PQC_PGP_BLOB_LEN) ++ { ++ memcpy(rsa_private_key + packet_buffer_offset, buffer + 7, 57); ++ packet_buffer_offset = packet_buffer_offset + 57; ++ } ++ } + else + { + hidprint("Error invalid RSA type"); +diff --git a/onlykey/okcore.h b/onlykey/okcore.h +index 11ed80f..7b07402 100644 +--- a/onlykey/okcore.h ++++ b/onlykey/okcore.h +@@ -86,6 +86,7 @@ extern "C" + + #include + #include "base64.h" ++#include "okpqc.h" + + /*************************************/ + //Firmware Memory Locations +diff --git a/onlykey/okcrypto.cpp b/onlykey/okcrypto.cpp +index bb7577d..ec4397a 100644 +--- a/onlykey/okcrypto.cpp ++++ b/onlykey/okcrypto.cpp +@@ -152,6 +152,7 @@ void okcrypto_sign (uint8_t *buffer) { + #ifdef DEBUG + Serial.print(features, BIN); + #endif ++ if ((type & 0x0F) == KEYTYPE_PQC_PGP) { okpqc_sign(buffer); return; } + if (is_bit_set(features, 6)) { + okcrypto_rsasign(buffer); + } else { +@@ -219,6 +220,7 @@ void okcrypto_decrypt (uint8_t *buffer){ + fadeoff(0); + return; + } ++ if ((type & 0x0F) == KEYTYPE_PQC_PGP) { okpqc_decrypt(buffer); return; } + if (is_bit_set(features, 5)) { + okcrypto_rsadecrypt(buffer); + } else { diff --git a/onlykey/mldsa65.h b/onlykey/mldsa65.h new file mode 100644 index 0000000..c489686 --- /dev/null +++ b/onlykey/mldsa65.h @@ -0,0 +1,16 @@ +/* mldsa65.h β€” thin ML-DSA-65 API used by okcrypto_pqc.cpp (backed by pq-crystals). */ +#ifndef MLDSA65_H +#define MLDSA65_H +#include +#include +#ifdef __cplusplus +extern "C" { +#endif +/* Deterministically expand the 32-byte FIPS 204 seed to the keypair. */ +int mldsa65_keypair_from_seed(uint8_t pk[1952], uint8_t sk[4032], const uint8_t seed[32]); +/* Sign message m (already the value the verifier will check, e.g. [0,0]||digest). */ +int mldsa65_sign(uint8_t *sig, size_t *siglen, const uint8_t *m, size_t mlen, const uint8_t sk[4032]); +#ifdef __cplusplus +} +#endif +#endif diff --git a/onlykey/mlkem768.h b/onlykey/mlkem768.h new file mode 100644 index 0000000..53f050f --- /dev/null +++ b/onlykey/mlkem768.h @@ -0,0 +1,15 @@ +/* mlkem768.h β€” thin ML-KEM-768 API used by okcrypto_pqc.cpp (backed by pq-crystals). */ +#ifndef MLKEM768_H +#define MLKEM768_H +#include +#ifdef __cplusplus +extern "C" { +#endif +/* Deterministically expand the 64-byte FIPS 203 seed (d||z) to the keypair. */ +int mlkem768_keypair_from_seed(uint8_t pk[1184], uint8_t dk[2400], const uint8_t seed[64]); +/* Decapsulate: ss = Decaps(dk, ct). */ +int mlkem768_dec(uint8_t ss[32], const uint8_t ct[1088], const uint8_t dk[2400]); +#ifdef __cplusplus +} +#endif +#endif diff --git a/onlykey/okcore.cpp b/onlykey/okcore.cpp index 827fd61..5baf267 100644 --- a/onlykey/okcore.cpp +++ b/onlykey/okcore.cpp @@ -5061,6 +5061,12 @@ int okcore_flashget_RSA(uint8_t slot) { type = (type & 0x0F); } + if (type == KEYTYPE_PQC_PGP) { // composite PQC PGP key: 160-byte seed blob + adr = adr + ((slot * MAX_RSA_KEY_SIZE) - MAX_RSA_KEY_SIZE); + okcore_flashget_common((uint8_t *)rsa_private_key, (unsigned long *)adr, PQC_PGP_BLOB_LEN); + okcore_aes_gcm_decrypt(rsa_private_key, slot, features, profilekey, PQC_PGP_BLOB_LEN); + return features; + } #ifdef DEBUG Serial.print("Type of RSA KEY is "); Serial.println(type, HEX); @@ -5159,6 +5165,15 @@ void rsa_priv_flash(uint8_t *buffer, bool wipe) packet_buffer_offset = packet_buffer_offset + 57; } } + else if ((buffer[6] & 0x0F) == KEYTYPE_PQC_PGP) + { // composite PQC PGP key: 160-byte seed blob + keysize = PQC_PGP_BLOB_LEN; + if (buffer[0] != 0xBA && packet_buffer_offset < PQC_PGP_BLOB_LEN) + { + memcpy(rsa_private_key + packet_buffer_offset, buffer + 7, 57); + packet_buffer_offset = packet_buffer_offset + 57; + } + } else { hidprint("Error invalid RSA type"); diff --git a/onlykey/okcore.h b/onlykey/okcore.h index 11ed80f..7b07402 100644 --- a/onlykey/okcore.h +++ b/onlykey/okcore.h @@ -86,6 +86,7 @@ extern "C" #include #include "base64.h" +#include "okpqc.h" /*************************************/ //Firmware Memory Locations diff --git a/onlykey/okcrypto.cpp b/onlykey/okcrypto.cpp index bb7577d..ec4397a 100644 --- a/onlykey/okcrypto.cpp +++ b/onlykey/okcrypto.cpp @@ -152,6 +152,7 @@ void okcrypto_sign (uint8_t *buffer) { #ifdef DEBUG Serial.print(features, BIN); #endif + if ((type & 0x0F) == KEYTYPE_PQC_PGP) { okpqc_sign(buffer); return; } if (is_bit_set(features, 6)) { okcrypto_rsasign(buffer); } else { @@ -219,6 +220,7 @@ void okcrypto_decrypt (uint8_t *buffer){ fadeoff(0); return; } + if ((type & 0x0F) == KEYTYPE_PQC_PGP) { okpqc_decrypt(buffer); return; } if (is_bit_set(features, 5)) { okcrypto_rsadecrypt(buffer); } else { diff --git a/onlykey/okcrypto_pqc_pgp.cpp b/onlykey/okcrypto_pqc_pgp.cpp new file mode 100644 index 0000000..1b42662 --- /dev/null +++ b/onlykey/okcrypto_pqc_pgp.cpp @@ -0,0 +1,135 @@ +/* + * okcrypto_pqc_pgp.cpp β€” composite PQC PGP key ops (one RSA slot = one key). + * Mirrors the two-phase CRYPTO_AUTH flow of okcrypto_mlkem_decaps (feature/mlkem-768). + * The 160-byte seed blob is loaded into rsa_private_key[] by okcore_flashget_RSA in dispatch. + * + * UNTESTED β€” by inspection. Validate on hardware. + */ +#include "okcrypto_pqc_pgp.h" +#include "mlkem768.h" +#include "mldsa65.h" +#include + +/* ---- firmware globals/APIs (defined in okcore.cpp / okcrypto.cpp) ---- */ +extern uint8_t rsa_private_key[]; /* holds the 160-byte seed blob after flashget */ +extern uint8_t *large_buffer; /* request in (digest / ct) */ +extern uint8_t *large_resp_buffer; +extern int large_buffer_offset; +extern uint8_t CRYPTO_AUTH; +extern uint8_t pending_operation; +extern int outputmode; +extern uint32_t packet_buffer_details[]; +extern uint8_t profilekey[]; +extern uint8_t ctap_buffer[]; /* large scratch (>= MLDSA_SIG_SIZE) */ + +extern "C" { + void process_packets(uint8_t *buffer, uint8_t type, uint8_t contype); + void okcore_aes_gcm_decrypt(uint8_t *state, uint8_t slot, uint8_t features, uint8_t *key, int len); + void send_transport_response(uint8_t *data, int len, bool enc, bool storeread); + void hidprint(const char *s); + void fadeoff(int); + /* ECC halves β€” the firmware's existing Ed25519/X25519 primitives (nacl/tweetnacl): + * wire these to the same math okcrypto_ecdsa_eddsa / okcrypto_ecdh already use, + * called on the 32-byte secret from the blob rather than an ECC-slot key. */ + int ok_ed25519_sign (uint8_t sig[64], const uint8_t *m, unsigned long mlen, const uint8_t sk[32]); + int ok_x25519_shared (uint8_t out[32], const uint8_t scalar[32], const uint8_t point[32]); +} + +#ifndef OKDECRYPT_ERR_USER_ACTION_PENDING +#define OKDECRYPT_ERR_USER_ACTION_PENDING 0xF9 +#endif +#ifndef CTAP2_ERR_OPERATION_PENDING +#define CTAP2_ERR_OPERATION_PENDING 0xF2 +#endif +#ifndef CTAP2_ERR_DATA_READY +#define CTAP2_ERR_DATA_READY 0xF1 +#endif +#ifndef LARGE_BUFFER_SIZE +#define LARGE_BUFFER_SIZE 1024 +#endif + +/* component selector captured at request time (buffer[6]); persists across the two phases */ +static uint8_t pqc_component; + +/* ============================== SIGN ============================== */ +void okcrypto_pqc_pgp_sign(uint8_t *buffer) +{ + if (!CRYPTO_AUTH) { + pqc_component = buffer[6]; /* PQC_HALF_ECC | PQC_HALF_PQC */ + process_packets(buffer, 0, 0); + pending_operation = OKDECRYPT_ERR_USER_ACTION_PENDING; + return; + } + if (CRYPTO_AUTH != 4) return; + + /* large_buffer holds the message digest to sign (transit-encrypted) */ + okcore_aes_gcm_decrypt(large_buffer, (uint8_t)packet_buffer_details[0], + (uint8_t)packet_buffer_details[1], profilekey, large_buffer_offset); + pending_operation = CTAP2_ERR_OPERATION_PENDING; + outputmode = (int)packet_buffer_details[2]; + + if (pqc_component == PQC_HALF_ECC) { + uint8_t sig[ED25519_SIG_SIZE]; + int rc = ok_ed25519_sign(sig, large_buffer, (unsigned long)large_buffer_offset, + rsa_private_key + PQC_OFF_ED25519); + memset(large_buffer, 0, LARGE_BUFFER_SIZE); + if (rc != 0) { pending_operation = 0; hidprint("Error Ed25519 sign"); return; } + pending_operation = CTAP2_ERR_DATA_READY; + send_transport_response(sig, ED25519_SIG_SIZE, false, true); + memset(sig, 0, sizeof sig); + } else { /* PQC_HALF_PQC : ML-DSA-65 */ + static uint8_t sk[MLDSA_SK_SIZE]; uint8_t pk[MLDSA_PK_SIZE]; + uint8_t *sig = ctap_buffer; /* >= 3309 B scratch */ + size_t siglen = 0; + int rc = mldsa65_keypair_from_seed(pk, sk, rsa_private_key + PQC_OFF_MLDSA_SEED); /* expand */ + if (rc == 0) rc = mldsa65_sign(sig, &siglen, large_buffer, (size_t)large_buffer_offset, sk); + memset(sk, 0, sizeof sk); + memset(large_buffer, 0, LARGE_BUFFER_SIZE); + if (rc != 0 || siglen != MLDSA_SIG_SIZE) { pending_operation = 0; hidprint("Error ML-DSA sign"); return; } + pending_operation = CTAP2_ERR_DATA_READY; + send_transport_response(sig, MLDSA_SIG_SIZE, false, true); + memset(sig, 0, MLDSA_SIG_SIZE); + } + fadeoff(85); +} + +/* ============================= DECRYPT ============================ */ +void okcrypto_pqc_pgp_decrypt(uint8_t *buffer) +{ + if (!CRYPTO_AUTH) { + pqc_component = buffer[6]; + process_packets(buffer, 0, 0); + pending_operation = OKDECRYPT_ERR_USER_ACTION_PENDING; + return; + } + if (CRYPTO_AUTH != 4) return; + + okcore_aes_gcm_decrypt(large_buffer, (uint8_t)packet_buffer_details[0], + (uint8_t)packet_buffer_details[1], profilekey, large_buffer_offset); + pending_operation = CTAP2_ERR_OPERATION_PENDING; + outputmode = (int)packet_buffer_details[2]; + + if (pqc_component == PQC_HALF_ECC) { /* X25519 on the ephemeral point (32 B) */ + if (large_buffer_offset != X25519_SS_SIZE) { hidprint("Error X25519 point size"); + memset(large_buffer,0,LARGE_BUFFER_SIZE); return; } + uint8_t ss[X25519_SS_SIZE]; + int rc = ok_x25519_shared(ss, rsa_private_key + PQC_OFF_X25519, large_buffer); + memset(large_buffer, 0, LARGE_BUFFER_SIZE); + if (rc != 0) { pending_operation = 0; hidprint("Error X25519"); return; } + memcpy(large_resp_buffer, ss, X25519_SS_SIZE); memset(ss, 0, sizeof ss); + pending_operation = CTAP2_ERR_DATA_READY; + send_transport_response(large_resp_buffer, X25519_SS_SIZE, false, true); + } else { /* PQC_HALF_PQC : ML-KEM-768 decapsulate (1088-B ct) */ + if (large_buffer_offset != MLKEM_CT_SIZE) { hidprint("Error ML-KEM ct size"); + memset(large_buffer,0,LARGE_BUFFER_SIZE); return; } + static uint8_t dk[MLKEM_DK_SIZE]; uint8_t pk[MLKEM_PK_SIZE], ss[MLKEM_SS_SIZE]; + int rc = mlkem768_keypair_from_seed(pk, dk, rsa_private_key + PQC_OFF_MLKEM_SEED); /* seed used directly */ + if (rc == 0) rc = mlkem768_dec(ss, large_buffer, dk); + memset(dk, 0, sizeof dk); memset(large_buffer, 0, LARGE_BUFFER_SIZE); + if (rc != 0) { memset(ss,0,sizeof ss); pending_operation = 0; hidprint("Error ML-KEM decaps"); return; } + memcpy(large_resp_buffer, ss, MLKEM_SS_SIZE); memset(ss, 0, sizeof ss); + pending_operation = CTAP2_ERR_DATA_READY; + send_transport_response(large_resp_buffer, MLKEM_SS_SIZE, false, true); + } + fadeoff(85); +} diff --git a/onlykey/okcrypto_pqc_pgp.h b/onlykey/okcrypto_pqc_pgp.h new file mode 100644 index 0000000..b91a249 --- /dev/null +++ b/onlykey/okcrypto_pqc_pgp.h @@ -0,0 +1,47 @@ +/* + * okcrypto_pqc_pgp.h β€” OnlyKey composite PQC PGP key in an RSA slot (1-4). + * + * One RSA slot holds one imported composite key as a 160-byte seed blob: + * [0:32] Ed25519 sk | [32:64] ML-DSA-65 seed | [64:96] X25519 sk | [96:160] ML-KEM-768 seed + * The device expands the PQC seeds at use time and performs the requested half. + * Public key is never stored (host holds it). + * + * UNTESTED β€” by inspection; reuses mlkem_native (from feature/mlkem-768) + adds ML-DSA. + */ +#ifndef OKCRYPTO_PQC_PGP_H +#define OKCRYPTO_PQC_PGP_H +#include + +#define KEYTYPE_PQC_PGP 7 +#define PQC_PGP_BLOB_LEN 160 +#define PQC_OFF_ED25519 0 +#define PQC_OFF_MLDSA_SEED 32 +#define PQC_OFF_X25519 64 +#define PQC_OFF_MLKEM_SEED 96 +#define MLDSA65_SEED_LEN 32 +#define MLKEM768_SEED_LEN 64 + +/* component selector (buffer[6]) */ +#define PQC_HALF_ECC 0 /* Ed25519 / X25519 */ +#define PQC_HALF_PQC 1 /* ML-DSA / ML-KEM */ + +/* sizes */ +#define MLKEM_CT_SIZE 1088 +#define MLKEM_SS_SIZE 32 +#define MLKEM_DK_SIZE 2400 +#define MLKEM_PK_SIZE 1184 +#define MLDSA_SK_SIZE 4032 +#define MLDSA_PK_SIZE 1952 +#define MLDSA_SIG_SIZE 3309 +#define ED25519_SIG_SIZE 64 +#define X25519_SS_SIZE 32 + +#ifdef __cplusplus +extern "C" { +#endif +void okcrypto_pqc_pgp_sign (uint8_t *buffer); /* OKSIGN on a type-7 slot */ +void okcrypto_pqc_pgp_decrypt (uint8_t *buffer); /* OKDECRYPT on a type-7 slot */ +#ifdef __cplusplus +} +#endif +#endif diff --git a/onlykey/okpqc.cpp b/onlykey/okpqc.cpp new file mode 100644 index 0000000..e9d3a67 --- /dev/null +++ b/onlykey/okpqc.cpp @@ -0,0 +1,180 @@ +/* + * okpqc.cpp β€” OnlyKey composite PQC PGP key operations (ML-KEM-768 + ML-DSA-65). + * See okpqc.h for the 160-byte slot layout. Mirrors the two-phase CRYPTO_AUTH flow of + * okcrypto_mlkem_decaps. Target: NXP MK20DX256 (Cortex-M4, 64 KB RAM). + * + * Libraries (vendored at repo root, compiled via their monolithic .c): + * ML-KEM-768: mlkem_native (crypto_kem_keypair_derand / crypto_kem_dec) + * ML-DSA-65 : mldsa_native built with MLD_CONFIG_PARAMETER_SET=65 + MLD_CONFIG_REDUCE_RAM + * (~17 KB sign; 69 KB without -> exceeds 64 KB RAM) + * Ed25519 / Curve25519: Arduino Crypto library (already used by the firmware). + * + * UNTESTED on hardware; the crypto core (keygen-from-seed + op) is verified on host. + */ +#include "okpqc.h" +#include +#include +#include "Curve25519.h" +#include + +/* ---- vendored PQC primitives (declared here to avoid pulling the big headers) ---- */ +extern "C" int PQCP_MLKEM_NATIVE_MLKEM768_keypair_derand(uint8_t *pk, uint8_t *dk, const uint8_t *coins /*64B seed*/); +extern "C" int PQCP_MLKEM_NATIVE_MLKEM768_dec(uint8_t *ss, const uint8_t *ct, const uint8_t *dk); +extern "C" int PQCP_MLDSA_NATIVE_MLDSA65_keypair_internal(uint8_t *pk, uint8_t *sk, const uint8_t seed[32]); +extern "C" int PQCP_MLDSA_NATIVE_MLDSA65_signature(uint8_t *sig, size_t *siglen, + const uint8_t *m, size_t mlen, const uint8_t *ctx, size_t ctxlen, const uint8_t *sk); + +/* ---- firmware RNG bridges required by mlkem_native / mldsa_native configs ---- + * RNG is the Arduino Crypto library global (RNG.h), as used elsewhere in the firmware. */ +extern "C" int onlykey_mlkem_randombytes(uint8_t *out, size_t outlen) { RNG.rand(out, (size_t)outlen); return 0; } +extern "C" int onlykey_mldsa_randombytes(uint8_t *out, size_t outlen) { RNG.rand(out, (size_t)outlen); return 0; } + +/* ---- firmware globals/APIs (okcore.cpp / okcrypto.cpp) ---- */ +extern uint8_t rsa_private_key[]; /* 160-byte blob after okcore_flashget_RSA */ +extern uint8_t *large_buffer; /* request in (digest / ct) */ +extern uint8_t *large_resp_buffer; +extern int large_buffer_offset; +extern uint8_t CRYPTO_AUTH; +extern uint8_t pending_operation; +extern int outputmode; +extern uint32_t packet_buffer_details[]; +extern uint8_t profilekey[]; +extern uint8_t ctap_buffer[]; /* large scratch (>= MLDSA_SIG_SIZE 3309) */ + +extern "C" { + void process_packets(uint8_t *buffer, uint8_t type, uint8_t contype); + void okcore_aes_gcm_decrypt(uint8_t *state, uint8_t slot, uint8_t features, uint8_t *key, int len); + void send_transport_response(uint8_t *data, int len, bool enc, bool storeread); + void hidprint(const char *s); + void fadeoff(int); +} + +#ifndef OKDECRYPT_ERR_USER_ACTION_PENDING +#define OKDECRYPT_ERR_USER_ACTION_PENDING 0xF9 +#endif +#ifndef CTAP2_ERR_OPERATION_PENDING +#define CTAP2_ERR_OPERATION_PENDING 0xF2 +#endif +#ifndef CTAP2_ERR_DATA_READY +#define CTAP2_ERR_DATA_READY 0xF1 +#endif +#ifndef LARGE_BUFFER_SIZE +#define LARGE_BUFFER_SIZE 1024 +#endif + +/* Ed25519 sign over the 32-byte secret in the blob (derive pub, then sign). */ +static int okpqc_ed25519_sign(uint8_t sig[64], const uint8_t *m, size_t mlen, const uint8_t sk[32]) +{ + uint8_t priv[32], pub[32]; + memcpy(priv, sk, 32); + Ed25519::derivePublicKey(pub, priv); + Ed25519::sign(sig, priv, pub, m, mlen); + memset(priv, 0, sizeof priv); + return 0; +} + +/* X25519 shared secret = scalar * point (Curve25519::eval clamps + mults). */ +static int okpqc_x25519_shared(uint8_t out[32], const uint8_t scalar[32], const uint8_t point[32]) +{ + uint8_t s[32], p[32]; + memcpy(s, scalar, 32); memcpy(p, point, 32); + bool ok = Curve25519::eval(out, s, p); + memset(s, 0, sizeof s); + return ok ? 0 : -1; +} + +/* One scratch for the expanded secret key: ML-DSA sk(4032) covers ML-KEM dk(2400); + * never used simultaneously. In .bss so it isn't on the deep call stack. */ +static uint8_t pqc_expanded_sk[MLDSA_SK_SIZE]; + + +/* ============================== SIGN ============================== */ +void okpqc_sign(uint8_t *buffer) +{ + if (!CRYPTO_AUTH) { + process_packets(buffer, 0, 0); + pending_operation = OKDECRYPT_ERR_USER_ACTION_PENDING; + return; + } + if (CRYPTO_AUTH != 4) return; + + okcore_aes_gcm_decrypt(large_buffer, (uint8_t)packet_buffer_details[0], + (uint8_t)packet_buffer_details[1], profilekey, large_buffer_offset); + pending_operation = CTAP2_ERR_OPERATION_PENDING; + outputmode = (int)packet_buffer_details[2]; + + /* wire: large_buffer[0] = component selector, large_buffer[1..] = message digest */ + uint8_t sel = large_buffer[0]; + uint8_t *msg = large_buffer + 1; + size_t msglen = (large_buffer_offset > 0) ? (size_t)(large_buffer_offset - 1) : 0; + + if (sel == PQC_HALF_ECC) { /* Ed25519 */ + uint8_t sig[ED25519_SIG_SIZE]; + okpqc_ed25519_sign(sig, msg, msglen, rsa_private_key + PQC_OFF_ED25519); + memset(large_buffer, 0, LARGE_BUFFER_SIZE); + pending_operation = CTAP2_ERR_DATA_READY; + send_transport_response(sig, ED25519_SIG_SIZE, false, true); + memset(sig, 0, sizeof sig); + } else { /* ML-DSA-65 */ + uint8_t pk[MLDSA_PK_SIZE]; + uint8_t *sig = ctap_buffer; /* >= 3309 B scratch */ + size_t siglen = 0; + int rc = PQCP_MLDSA_NATIVE_MLDSA65_keypair_internal(pk, pqc_expanded_sk, + rsa_private_key + PQC_OFF_MLDSA_SEED); + /* openpgp.js ml_dsa.js signs [0x00,0x00]||digest with empty ML-DSA context; sending the + * RAW digest and signing with ctx="" yields the same FIPS 204 message representative. + * Verify against openpgp.js on hardware. */ + if (rc == 0) rc = PQCP_MLDSA_NATIVE_MLDSA65_signature(sig, &siglen, msg, msglen, + (const uint8_t*)0, 0, pqc_expanded_sk); + memset(pqc_expanded_sk, 0, sizeof pqc_expanded_sk); + memset(large_buffer, 0, LARGE_BUFFER_SIZE); + if (rc != 0 || siglen != MLDSA_SIG_SIZE) { pending_operation = 0; hidprint("Error ML-DSA sign"); return; } + pending_operation = CTAP2_ERR_DATA_READY; + send_transport_response(sig, MLDSA_SIG_SIZE, false, true); /* 3309 B, fragmented by transport */ + memset(sig, 0, MLDSA_SIG_SIZE); + } + fadeoff(85); +} + +/* ============================= DECRYPT ============================ */ +void okpqc_decrypt(uint8_t *buffer) +{ + if (!CRYPTO_AUTH) { + process_packets(buffer, 0, 0); + pending_operation = OKDECRYPT_ERR_USER_ACTION_PENDING; + return; + } + if (CRYPTO_AUTH != 4) return; + + okcore_aes_gcm_decrypt(large_buffer, (uint8_t)packet_buffer_details[0], + (uint8_t)packet_buffer_details[1], profilekey, large_buffer_offset); + pending_operation = CTAP2_ERR_OPERATION_PENDING; + outputmode = (int)packet_buffer_details[2]; + + /* component inferred from input size: 32 B ephemeral point -> X25519, 1088 B ct -> ML-KEM */ + if (large_buffer_offset == X25519_SS_SIZE) { /* X25519 on the 32-byte ephemeral point */ + uint8_t ss[X25519_SS_SIZE]; + int rc = okpqc_x25519_shared(ss, rsa_private_key + PQC_OFF_X25519, large_buffer); + memset(large_buffer, 0, LARGE_BUFFER_SIZE); + if (rc != 0) { pending_operation = 0; hidprint("Error X25519"); return; } + memcpy(large_resp_buffer, ss, X25519_SS_SIZE); memset(ss, 0, sizeof ss); + pending_operation = CTAP2_ERR_DATA_READY; + send_transport_response(large_resp_buffer, X25519_SS_SIZE, false, true); + } else if (large_buffer_offset == MLKEM_CT_SIZE) { /* ML-KEM-768 decapsulate 1088-B ct */ + uint8_t pk[MLKEM_PK_SIZE], ss[MLKEM_SS_SIZE]; + /* seed used DIRECTLY as coins (d||z) β€” no SHAKE(32->64); this is an imported key */ + int rc = PQCP_MLKEM_NATIVE_MLKEM768_keypair_derand(pk, pqc_expanded_sk, rsa_private_key + PQC_OFF_MLKEM_SEED); + if (rc == 0) rc = PQCP_MLKEM_NATIVE_MLKEM768_dec(ss, large_buffer, pqc_expanded_sk); + memset(pqc_expanded_sk, 0, MLKEM_DK_SIZE); + memset(large_buffer, 0, LARGE_BUFFER_SIZE); + if (rc != 0) { memset(ss, 0, sizeof ss); pending_operation = 0; hidprint("Error ML-KEM decaps"); return; } + memcpy(large_resp_buffer, ss, MLKEM_SS_SIZE); memset(ss, 0, sizeof ss); + pending_operation = CTAP2_ERR_DATA_READY; + send_transport_response(large_resp_buffer, MLKEM_SS_SIZE, false, true); + } else { + hidprint("Error PQC decrypt: bad input size"); + memset(large_buffer, 0, LARGE_BUFFER_SIZE); + pending_operation = 0; + } + fadeoff(85); +} diff --git a/onlykey/okpqc.h b/onlykey/okpqc.h new file mode 100644 index 0000000..b49eeea --- /dev/null +++ b/onlykey/okpqc.h @@ -0,0 +1,63 @@ +/* + * okpqc.h β€” OnlyKey post-quantum PGP keys (composite ML-KEM-768 + ML-DSA-65). + * + * One RSA slot (1-4) holds one imported composite PQC PGP key as a 160-byte seed blob: + * [0:32] Ed25519 secret (sign, ecc half) + * [32:64] ML-DSA-65 seed (sign, pqc half) FIPS 204 32-byte seed + * [64:96] X25519 secret (decrypt, ecc half) + * [96:160] ML-KEM-768 seed (decrypt, pqc half) FIPS 203 64-byte seed (d||z) + * + * The device expands the PQC seeds at use time (deterministic keygen) and performs the + * requested half. Nothing but the seeds is stored; the public key lives on the host. + * + * Primitives: + * ML-KEM-768: mlkem-native (already vendored) β€” crypto_kem_keypair_derand / crypto_kem_dec + * ML-DSA-65 : mldsa-native with MLD_CONFIG_REDUCE_RAM (~17 KB sign; 69 KB without -> won't fit 64 KB) + * Ed25519 / X25519: the firmware's existing primitives. + * + * Target: NXP MK20DX256 (Cortex-M4, 256 KB flash / 64 KB RAM). UNTESTED β€” by inspection. + */ +#ifndef OKPQC_H +#define OKPQC_H +#include + +/* slot key type (per-slot EEPROM nibble) */ +#define KEYTYPE_PQC_PGP 7 +#define PQC_PGP_BLOB_LEN 160 + +/* blob offsets */ +#define PQC_OFF_ED25519 0 +#define PQC_OFF_MLDSA_SEED 32 +#define PQC_OFF_X25519 64 +#define PQC_OFF_MLKEM_SEED 96 + +/* seed lengths */ +#define MLDSA65_SEED_LEN 32 +#define MLKEM768_SEED_LEN 64 + +/* component selector (buffer[6]): which half of the composite to run */ +#define PQC_HALF_ECC 0 /* Ed25519 / X25519 */ +#define PQC_HALF_PQC 1 /* ML-DSA-65 / ML-KEM-768 */ + +/* sizes (FIPS 203 / 204) */ +#define MLKEM_CT_SIZE 1088 +#define MLKEM_SS_SIZE 32 +#define MLKEM_DK_SIZE 2400 +#define MLKEM_PK_SIZE 1184 +#define MLDSA_SK_SIZE 4032 +#define MLDSA_PK_SIZE 1952 +#define MLDSA_SIG_SIZE 3309 +#define ED25519_SIG_SIZE 64 +#define X25519_SS_SIZE 32 + +#ifdef __cplusplus +extern "C" { +#endif +/* Called from okcrypto_sign / okcrypto_decrypt when the slot type is KEYTYPE_PQC_PGP. + * okcore_flashget_RSA() must have loaded the 160-byte blob into rsa_private_key[] and set type=7. */ +void okpqc_sign (uint8_t *buffer); /* OKSIGN: selector -> Ed25519 sig(64) | ML-DSA sig(3309) */ +void okpqc_decrypt (uint8_t *buffer); /* OKDECRYPT: selector -> X25519 ss(32) | ML-KEM ss(32) */ +#ifdef __cplusplus +} +#endif +#endif /* OKPQC_H */ diff --git a/onlykey/pqc_shim.c b/onlykey/pqc_shim.c new file mode 100644 index 0000000..fa3a91e --- /dev/null +++ b/onlykey/pqc_shim.c @@ -0,0 +1,42 @@ +/* + * pqc_shim.c β€” maps mlkem768.h / mldsa65.h onto the vendored primitives. + * ML-KEM: reuse `mlkem_native` from feature/mlkem-768 (crypto_kem_* API). + * ML-DSA: add `mldsa-native` (same pq-code-package family) or pq-crystals dilithium (mode 3). + * + * The ML-KEM seed is the 64-byte OpenPGP mlkemSeed used DIRECTLY as keygen coins (d||z) β€” no + * SHAKE(32->64). The ML-DSA seed is the 32-byte OpenPGP mldsaSeed used as the keygen seed. + * + * UNTESTED β€” by inspection. Confirm exact symbol names against the pinned vendored versions. + */ +#include "mlkem768.h" +#include "mldsa65.h" + +/* ---- mlkem_native (ML-KEM-768) β€” same API feature/mlkem-768 already calls ---- */ +extern int crypto_kem_keypair_derand(unsigned char *pk, unsigned char *sk, const unsigned char *coins); +extern int crypto_kem_dec(unsigned char *ss, const unsigned char *ct, const unsigned char *sk); + +/* ---- ML-DSA-65 (mldsa-native / dilithium mode 3) ---- */ +extern int mldsa_keypair_from_seed(unsigned char *pk, unsigned char *sk, const unsigned char seed[32]); +extern int mldsa_signature(unsigned char *sig, unsigned long *siglen, + const unsigned char *m, unsigned long mlen, + const unsigned char *ctx, unsigned long ctxlen, + const unsigned char *sk); + +int mlkem768_keypair_from_seed(uint8_t pk[1184], uint8_t dk[2400], const uint8_t seed[64]) { + return crypto_kem_keypair_derand(pk, dk, seed); /* coins = 64-byte (d||z) */ +} +int mlkem768_dec(uint8_t ss[32], const uint8_t ct[1088], const uint8_t dk[2400]) { + return crypto_kem_dec(ss, ct, dk); +} + +int mldsa65_keypair_from_seed(uint8_t pk[1952], uint8_t sk[4032], const uint8_t seed[32]) { + return mldsa_keypair_from_seed(pk, sk, seed); +} +int mldsa65_sign(uint8_t *sig, size_t *siglen, const uint8_t *m, size_t mlen, const uint8_t sk[4032]) { + unsigned long sl = 0; + /* openpgp.js signs [0x00,0x00] || digest with empty ML-DSA context; the host sends those + * exact bytes as `m`, so ctx is empty here. */ + int rc = mldsa_signature(sig, &sl, m, (unsigned long)mlen, (const unsigned char*)0, 0, sk); + if (siglen) *siglen = (size_t)sl; + return rc; +}