Wired in PQC
Juniper Beatitudes [email protected]
Sat, 18 Jul 2026 16:31:41 -0500
7 files changed,
369 insertions(+),
3 deletions(-)
M
.gitignore
→
.gitignore
@@ -56,4 +56,5 @@ # Build Artifacts
.cache/ build/ flake.lock -compile_commands.json+compile_commands.json +.vscode/
A
common/memory.c
@@ -0,0 +1,48 @@
+#include "memory.h" +#include <stdbool.h> +#include <stddef.h> +#include <stdint.h> + +bool +circe_memory_equal (const uint8_t *a, const uint8_t *b, size_t len) +{ + uint8_t acc = 0x0; + for (size_t i = 0; i < len; ++i) + { + acc |= a[i] ^ b[i]; + } + return acc == 0x0; +} + +void +circe_memory_copy (const uint8_t *restrict src, uint8_t *restrict dst, + size_t len) +{ + for (size_t i = 0; i < len; ++i) + { + dst[i] = src[i]; + } +} + +void +circe_memory_set (uint8_t *dst, uint8_t value, size_t n) +{ + for (size_t i = 0; i < n; ++i) + { + dst[i] = value; + } +} + +void * +circe_memset (void *dest, int value, size_t count) +{ + circe_memory_set ((uint8_t *)dest, (uint8_t)value, count); + return dest; +} + +void * +circe_memcpy (void *restrict dest, const void *restrict src, size_t n) +{ + circe_memory_copy ((const uint8_t *restrict)src, (uint8_t *restrict)dest, n); + return dest; +}
A
common/memory.h
@@ -0,0 +1,21 @@
+#ifndef CIRCE_COMMON_MEMORY_H +#define CIRCE_COMMON_MEMORY_H + +#include <stdbool.h> +#include <stddef.h> +#include <stdint.h> + +bool circe_memory_equal (const uint8_t *a, const uint8_t *b, size_t len); + +void circe_memory_copy (const uint8_t *restrict src, uint8_t *restrict dst, + size_t len); + +void circe_memory_set (uint8_t *dst, uint8_t value, size_t n); + +/* COMPATIBILITY FUNCTIONS */ + +void *circe_memset (void *dest, int value, size_t count); + +void *circe_memcpy (void *restrict dest, const void *restrict src, size_t n); + +#endif
A
contrib/config/circe_mldsa_config.h
@@ -0,0 +1,93 @@
+/* + * Circe configuration for mldsa-native. + * + * Selected instead of contrib/mldsa-native/mldsa/mldsa_native_config.h by + * building with -DMLD_CONFIG_FILE="circe_mldsa_config.h" (see + * contrib/meson.build). + * + * Circe only needs ML-DSA-44 with the randomized (randombytes) API. + */ + +#ifndef MLD_CONFIG_H +#define MLD_CONFIG_H + +/* ML-DSA-44. */ +#define MLD_CONFIG_PARAMETER_SET 44 + +/* Namespace prefix for exported symbols (matches the upstream default for 44). + */ +#define MLD_CONFIG_NAMESPACE_PREFIX PQCP_MLDSA_NATIVE_MLDSA44 + +/* + * Randomized API. + * + * We deliberately leave MLD_CONFIG_NO_RANDOMIZED_API and + * MLD_CONFIG_CUSTOM_RANDOMBYTES *unset*: mldsa-native keeps the randomized + * signing API and expects the consumer to provide + * + * int randombytes(uint8_t *out, size_t outlen); // 0 on success + * + * No deterministic/derand-only build. + */ + +/* + * Implementation-only options. + * + * MLD_BUILD_INTERNAL is defined by the library's src/common.h before this file + * is included, so these only affect the library build, not consumers that + * merely include mldsa_native.h. + */ +#if defined(MLD_BUILD_INTERNAL) + +/* + * Use the optimized native backends on AArch64 and x86-64, and the portable C + * backends everywhere else. The architecture is detected from the compiler's + * predefined macros, matching mldsa-native's own src/sys.h; the actual backend + * (NEON / AVX2, and the SHA3/Keccak accelerator) is then selected + * automatically from the -march flags passed by contrib/meson.build. + */ +#if defined(__aarch64__) || defined(__x86_64__) +#define MLD_CONFIG_USE_NATIVE_BACKEND_ARITH +#define MLD_CONFIG_ARITH_BACKEND_FILE "native/meta.h" +#define MLD_CONFIG_USE_NATIVE_BACKEND_FIPS202 +#define MLD_CONFIG_FIPS202_BACKEND_FILE "fips202/native/auto.h" +#endif + +/* + * Freestanding build: no libc is linked, so route mldsa-native's memcpy / + * memset / stack-zeroization through Circe's portable implementations in + * common/memory.c. This drops the library's <string.h> includes and leaves no + * unresolved libc symbols. + */ +#define MLD_CONFIG_CUSTOM_MEMCPY +#define MLD_CONFIG_CUSTOM_MEMSET +#define MLD_CONFIG_CUSTOM_ZEROIZE +#if !defined(__ASSEMBLER__) +#include "src/sys.h" /* for MLD_INLINE */ +#include <stddef.h> +#include <stdint.h> + +/* Defined in common/memory.c */ +void *circe_memcpy (void *restrict dest, const void *restrict src, size_t n); +void *circe_memset (void *dest, int value, size_t count); + +static MLD_INLINE void * +mld_memcpy (void *dest, const void *src, size_t n) +{ + return circe_memcpy (dest, src, n); +} +static MLD_INLINE void * +mld_memset (void *s, int c, size_t n) +{ + return circe_memset (s, c, n); +} +static MLD_INLINE void +mld_zeroize (void *ptr, size_t len) +{ + circe_memset (ptr, 0, len); +} +#endif /* !__ASSEMBLER__ */ + +#endif /* MLD_BUILD_INTERNAL */ + +#endif /* MLD_CONFIG_H */
A
contrib/config/circe_mlkem_config.h
@@ -0,0 +1,94 @@
+/* + * Circe configuration for mlkem-native. + * + * Selected instead of contrib/mlkem-native/mlkem/mlkem_native_config.h by + * building with -DMLK_CONFIG_FILE="circe_mlkem_config.h" (see + * contrib/meson.build). + * + * Circe only needs ML-KEM-768 with the randomized (randombytes) API. + */ + +#ifndef MLK_CONFIG_H +#define MLK_CONFIG_H + +/* ML-KEM-768. */ +#define MLK_CONFIG_PARAMETER_SET 768 + +/* Namespace prefix for exported symbols (matches the upstream default for + * 768). */ +#define MLK_CONFIG_NAMESPACE_PREFIX PQCP_MLKEM_NATIVE_MLKEM768 + +/* + * Randomized API. + * + * We deliberately leave MLK_CONFIG_NO_RANDOMIZED_API and + * MLK_CONFIG_CUSTOM_RANDOMBYTES *unset*: mlkem-native keeps + * crypto_kem_keypair() and crypto_kem_enc() and expects the consumer to + * provide + * + * int randombytes(uint8_t *out, size_t outlen); // 0 on success + * + * No deterministic/derand-only build. + */ + +/* + * Implementation-only options. + * + * MLK_BUILD_INTERNAL is defined by the library's src/common.h before this file + * is included, so these only affect the library build, not consumers that + * merely include mlkem_native.h. + */ +#if defined(MLK_BUILD_INTERNAL) + +/* + * Use the optimized native backends on AArch64 and x86-64, and the portable C + * backends everywhere else. The architecture is detected from the compiler's + * predefined macros, matching mlkem-native's own src/sys.h; the actual backend + * (NEON / AVX2, and the SHA3/Keccak accelerator) is then selected + * automatically from the -march flags passed by contrib/meson.build. + */ +#if defined(__aarch64__) || defined(__x86_64__) +#define MLK_CONFIG_USE_NATIVE_BACKEND_ARITH +#define MLK_CONFIG_ARITH_BACKEND_FILE "native/meta.h" +#define MLK_CONFIG_USE_NATIVE_BACKEND_FIPS202 +#define MLK_CONFIG_FIPS202_BACKEND_FILE "fips202/native/auto.h" +#endif + +/* + * Freestanding build: no libc is linked, so route mlkem-native's memcpy / + * memset / stack-zeroization through Circe's portable implementations in + * common/memory.c. This drops the library's <string.h> includes and leaves no + * unresolved libc symbols. + */ +#define MLK_CONFIG_CUSTOM_MEMCPY +#define MLK_CONFIG_CUSTOM_MEMSET +#define MLK_CONFIG_CUSTOM_ZEROIZE +#if !defined(__ASSEMBLER__) +#include "src/sys.h" /* for MLK_INLINE */ +#include <stddef.h> +#include <stdint.h> + +/* Defined in common/memory.c */ +void *circe_memcpy (void *restrict dest, const void *restrict src, size_t n); +void *circe_memset (void *dest, int value, size_t count); + +static MLK_INLINE void * +mlk_memcpy (void *dest, const void *src, size_t n) +{ + return circe_memcpy (dest, src, n); +} +static MLK_INLINE void * +mlk_memset (void *s, int c, size_t n) +{ + return circe_memset (s, c, n); +} +static MLK_INLINE void +mlk_zeroize (void *ptr, size_t len) +{ + circe_memset (ptr, 0, len); +} +#endif /* !__ASSEMBLER__ */ + +#endif /* MLK_BUILD_INTERNAL */ + +#endif /* MLK_CONFIG_H */
A
contrib/meson.build
@@ -0,0 +1,100 @@
+# Builds the external post-quantum primitives Circe depends on: +# - mlkem-native -> ML-KEM-768 +# - mldsa-native -> ML-DSA-44 +# +# Both are compiled as single-compilation-unit (monolithic) builds: one C unit +# plus one assembly unit per library. The parameter set, namespace and native +# backend selection live in the config headers under contrib/config/, which are +# pulled in via -DML{K,D}_CONFIG_FILE. +# +# Each consumer of these libraries must still provide a randombytes() +# implementation: int randombytes(uint8_t *out, size_t outlen); + +cc = meson.get_compiler('c') + +# --------------------------------------------------------------------------- +# Native-backend architecture flags +# +# The config headers enable the native backends whenever the compiler targets +# AArch64 or x86-64. Here we hand the compiler the ISA flags those backends +# need; on any other architecture we add nothing and the portable C backends +# are used instead. +# --------------------------------------------------------------------------- +native_arch_args = [] +cpu = host_machine.cpu_family() + +if cpu == 'x86_64' + # AVX2 arithmetic backend + BMI2 for the Keccak/rejection-sampling paths. + foreach arg : ['-mavx2', '-mbmi2'] + if cc.has_argument(arg) + native_arch_args += arg + endif + endforeach +elif cpu == 'aarch64' + # NEON arithmetic backend + the Armv8.4 SHA3 extension for native Keccak. + if cc.has_argument('-march=armv8.4-a+sha3') + native_arch_args += '-march=armv8.4-a+sha3' + endif +endif + +contrib_config_inc = include_directories('config') + +# Freestanding, matching the rest of Circe: no libc is linked, and the config +# headers route the libraries' memcpy/memset/zeroize through common's +# circe_memcpy/circe_memset (hence the common_dep below). +pqc_c_args = [ + '-O3', + '-ffreestanding', + '-fno-stack-protector', # no libc: don't emit __stack_chk_fail references + '-fomit-frame-pointer', +] + native_arch_args + +# --------------------------------------------------------------------------- +# mlkem-native -> ML-KEM-768 +# --------------------------------------------------------------------------- +mlkem_inc = include_directories('mlkem-native/mlkem') +mlkem_config_arg = '-DMLK_CONFIG_FILE="circe_mlkem_config.h"' + +mlkem_lib = static_library( + 'circe-mlkem', + [ + 'mlkem-native/mlkem/mlkem_native.c', + 'mlkem-native/mlkem/mlkem_native_asm.S', + ], + c_args: pqc_c_args + [mlkem_config_arg], + include_directories: [mlkem_inc, contrib_config_inc], + dependencies: [common_dep], +) + +mlkem_dep = declare_dependency( + link_with: mlkem_lib, + include_directories: [mlkem_inc, contrib_config_inc], + compile_args: [mlkem_config_arg], + dependencies: [common_dep], +) +set_variable(meson.project_name() + '_mlkem_dep', mlkem_dep) + +# --------------------------------------------------------------------------- +# mldsa-native -> ML-DSA-44 +# --------------------------------------------------------------------------- +mldsa_inc = include_directories('mldsa-native/mldsa') +mldsa_config_arg = '-DMLD_CONFIG_FILE="circe_mldsa_config.h"' + +mldsa_lib = static_library( + 'circe-mldsa', + [ + 'mldsa-native/mldsa/mldsa_native.c', + 'mldsa-native/mldsa/mldsa_native_asm.S', + ], + c_args: pqc_c_args + [mldsa_config_arg], + include_directories: [mldsa_inc, contrib_config_inc], + dependencies: [common_dep], +) + +mldsa_dep = declare_dependency( + link_with: mldsa_lib, + include_directories: [mldsa_inc, contrib_config_inc], + compile_args: [mldsa_config_arg], + dependencies: [common_dep], +) +set_variable(meson.project_name() + '_mldsa_dep', mldsa_dep)
M
meson.build
→
meson.build
@@ -16,6 +16,7 @@ ]
common_sources = [ 'common/cbor.c', + 'common/memory.c', ] common_incdir = include_directories('common/')@@ -30,6 +31,14 @@
common_dep = declare_dependency(include_directories: common_incdir, link_with: common_target) set_variable(meson.project_name() + '_common_dep', common_dep) +# Post-quantum primitives from contrib/ (ML-KEM-768, ML-DSA-44). +# Built freestanding; they route memcpy/memset/zeroize through common's +# circe_memcpy/circe_memset, so they depend on common_dep. +# Exposes <project>_mlkem_dep and <project>_mldsa_dep. +subdir('contrib') +mlkem_dependency = get_variable(meson.project_name() + '_mlkem_dep') +mldsa_dependency = get_variable(meson.project_name() + '_mldsa_dep') + ## LIBCIRCE-CORE core_build_args = [@@ -50,7 +59,7 @@ 'circe-core',
core_sources, c_args: core_build_args, include_directories: core_incdir, - dependencies: [common_dep, tct_dependency], + dependencies: [common_dep, tct_dependency, mlkem_dependency, mldsa_dependency], ) core_dep = declare_dependency(include_directories: core_incdir, link_with: core_target)@@ -58,4 +67,4 @@ set_variable(meson.project_name() + '_core_dep', core_dep)
if not meson.is_subproject() and not meson.is_cross_build() # Tests go here -endif+endif