From 9b420e8668b613bfa714cc5ce0b10b293bd4a940 Mon Sep 17 00:00:00 2001 From: "Matthias J. Kannwischer" Date: Wed, 14 Oct 2026 00:45:45 +0800 Subject: [PATCH 1/5] CI: replace pqcp-arm64 with ubuntu-24.04-arm Signed-off-by: Matthias J. Kannwischer --- .github/workflows/base.yml | 20 ++------------------ .github/workflows/ci.yml | 2 +- 2 files changed, 3 insertions(+), 19 deletions(-) diff --git a/.github/workflows/base.yml b/.github/workflows/base.yml index 09409bc..4fe74c7 100644 --- a/.github/workflows/base.yml +++ b/.github/workflows/base.yml @@ -14,10 +14,8 @@ jobs: strategy: fail-fast: false matrix: - external: - - ${{ github.repository_owner != 'pq-code-package' }} target: - - runner: pqcp-arm64 + - runner: ubuntu-24.04-arm name: 'aarch64' - runner: ubuntu-latest name: 'x86_64' @@ -26,12 +24,6 @@ jobs: - runner: macos-15-intel name: 'macos (x86_64)' acvp-version: [v1.1.0.40, v1.1.0.39] - exclude: - - {external: true, - target: { - runner: pqcp-arm64, - name: 'aarch64' - }} name: Quickcheck (${{ matrix.target.name }}, ACVP ${{ matrix.acvp-version }}) runs-on: ${{ matrix.target.runner }} steps: @@ -44,19 +36,11 @@ jobs: strategy: fail-fast: false matrix: - external: - - ${{ github.repository_owner != 'pq-code-package' }} target: - - runner: pqcp-arm64 + - runner: ubuntu-24.04-arm name: 'aarch64' - runner: ubuntu-latest name: 'x86_64' - exclude: - - {external: true, - target: { - runner: pqcp-arm64, - name: 'aarch64' - }} name: Quickcheck C90 (${{ matrix.target.name }}) runs-on: ${{ matrix.target.runner }} steps: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5b44cb9..4519502 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,7 +15,7 @@ jobs: matrix: cflags: [ "-Os", "-O3" ] target: - - runner: pqcp-arm64 + - runner: ubuntu-24.04-arm name: 'aarch64' archflags: '' - runner: ubuntu-latest From 3644efca8568617f98be69a21a631f8ca7b5dea2 Mon Sep 17 00:00:00 2001 From: "Matthias J. Kannwischer" Date: Tue, 13 Oct 2026 23:20:58 +0800 Subject: [PATCH 2/5] Add central endianness detection Port SLH_SYS_{LITTLE,BIG}_ENDIAN from mlkem-native's sys.h. Signed-off-by: Matthias J. Kannwischer --- slh_sys.h | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/slh_sys.h b/slh_sys.h index 48eabf2..6169061 100644 --- a/slh_sys.h +++ b/slh_sys.h @@ -7,6 +7,28 @@ #ifndef SLH_SYS_H #define SLH_SYS_H +/* Try to find endianness, if not forced through CFLAGS already */ +#if !defined(SLH_SYS_LITTLE_ENDIAN) && !defined(SLH_SYS_BIG_ENDIAN) +#if defined(__BYTE_ORDER__) +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ +#define SLH_SYS_LITTLE_ENDIAN +#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ +#define SLH_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 SLH_SYS_LITTLE_ENDIAN +#endif + +#endif /* !SLH_SYS_LITTLE_ENDIAN && !SLH_SYS_BIG_ENDIAN */ + /* * C90 does not have the inline compiler directive yet. * We don't use it in C90 builds. From eea6666f552b743586f504737f0b63ac0f5ea688 Mon Sep 17 00:00:00 2001 From: "Matthias J. Kannwischer" Date: Tue, 13 Oct 2026 23:20:58 +0800 Subject: [PATCH 3/5] SHAKE: fix big-endian support Port keccakf1600_{xor,extract}_bytes from mlkem-native and use them for all byte access to the Keccak state. Signed-off-by: Matthias J. Kannwischer --- sha3_api.c | 57 +++++++++++++++++++++++++++++++--------------------- sha3_api.h | 6 ++++++ sha3_f1600.c | 39 +++++++++++++++++++++++++++++++++++ slh_shake.c | 18 ++++++++++++----- 4 files changed, 92 insertions(+), 28 deletions(-) diff --git a/sha3_api.c b/sha3_api.c index 22d7d9e..682ad63 100644 --- a/sha3_api.c +++ b/sha3_api.c @@ -25,35 +25,39 @@ void sha3_init(sha3_var_t *c, size_t md_sz) void sha3_update(sha3_var_t *c, const void *data, size_t len) { - size_t i, j; + const uint8_t *in = data; + size_t l; - j = c->pt; - for (i = 0; i < len; i++) + while (len > 0) { - c->st.b[j++] ^= ((const uint8_t *)data)[i]; - if (j >= c->r_sz) + l = c->r_sz - c->pt; + if (l > len) + { + l = len; + } + keccak_f1600_xor_bytes(c->st.d, in, c->pt, l); + c->pt += l; + in += l; + len -= l; + if (c->pt >= c->r_sz) { keccak_f1600(c->st.d); - j = 0; + c->pt = 0; } } - c->pt = j; } /* finalize and output a hash */ void sha3_final(sha3_var_t *c, uint8_t *md) { - size_t i; + const uint8_t pad = 0x06, pad_end = 0x80; - c->st.b[c->pt] ^= 0x06; - c->st.b[c->r_sz - 1] ^= 0x80; + keccak_f1600_xor_bytes(c->st.d, &pad, c->pt, 1); + keccak_f1600_xor_bytes(c->st.d, &pad_end, c->r_sz - 1, 1); keccak_f1600(c->st.d); - for (i = 0; i < c->md_sz; i++) - { - md[i] = c->st.b[i]; - } + keccak_f1600_extract_bytes(c->st.d, md, 0, c->md_sz); } /* compute a SHA-3 hash "md" of "md_sz" bytes from data in "in" */ @@ -72,29 +76,36 @@ void sha3(uint8_t *md, size_t md_sz, const void *in, size_t in_sz) void shake_out(sha3_var_t *c, uint8_t *out, size_t out_sz) { - size_t i, j; + const uint8_t pad = 0x1F, pad_end = 0x80; + size_t l; /* add padding on the first call */ if (c->md_sz != 0) { - c->st.b[c->pt] ^= 0x1F; - c->st.b[c->r_sz - 1] ^= 0x80; + keccak_f1600_xor_bytes(c->st.d, &pad, c->pt, 1); + keccak_f1600_xor_bytes(c->st.d, &pad_end, c->r_sz - 1, 1); keccak_f1600(c->st.d); c->pt = 0; c->md_sz = 0; } - j = c->pt; - for (i = 0; i < out_sz; i++) + while (out_sz > 0) { - if (j >= c->r_sz) + if (c->pt >= c->r_sz) { keccak_f1600(c->st.d); - j = 0; + c->pt = 0; + } + l = c->r_sz - c->pt; + if (l > out_sz) + { + l = out_sz; } - out[i] = c->st.b[j++]; + keccak_f1600_extract_bytes(c->st.d, out, c->pt, l); + c->pt += l; + out += l; + out_sz -= l; } - c->pt = j; } /* compute a SHAKE hash "md" of "md_sz" bytes from data in "in" */ diff --git a/sha3_api.h b/sha3_api.h index 6c1a4ec..3c533af 100644 --- a/sha3_api.h +++ b/sha3_api.h @@ -59,6 +59,12 @@ extern "C" /* core permutation */ void keccak_f1600(uint64_t x[25]); + /* byte access to the (native-endian) state */ + void keccak_f1600_xor_bytes(uint64_t *state, const uint8_t *data, + size_t offset, size_t length); + void keccak_f1600_extract_bytes(const uint64_t *state, uint8_t *data, + size_t offset, size_t length); + #ifdef __cplusplus } #endif diff --git a/sha3_f1600.c b/sha3_f1600.c index 0cf2f00..20b1c1a 100644 --- a/sha3_f1600.c +++ b/sha3_f1600.c @@ -12,6 +12,45 @@ uint64_t keccak_f1600_count = 0; /* instrumentation */ #endif +/* byte access to the state; adapted from mlkem-native */ + +void keccak_f1600_xor_bytes(uint64_t *state, const uint8_t *data, size_t offset, + size_t length) +{ + size_t i; +#if defined(SLH_SYS_LITTLE_ENDIAN) + uint8_t *state_ptr = (uint8_t *)state + offset; + for (i = 0; i < length; i++) + { + state_ptr[i] ^= data[i]; + } +#else + for (i = 0; i < length; i++) + { + state[(offset + i) >> 3] ^= (uint64_t)data[i] + << (8 * ((offset + i) & 0x07)); + } +#endif +} + +void keccak_f1600_extract_bytes(const uint64_t *state, uint8_t *data, + size_t offset, size_t length) +{ + size_t i; +#if defined(SLH_SYS_LITTLE_ENDIAN) + const uint8_t *state_ptr = (const uint8_t *)state + offset; + for (i = 0; i < length; i++) + { + data[i] = state_ptr[i]; + } +#else + for (i = 0; i < length; i++) + { + data[i] = (state[(offset + i) >> 3] >> (8 * ((offset + i) & 0x07))) & 0xFF; + } +#endif +} + /* forward permutation */ void keccak_f1600(uint64_t x[25]) diff --git a/slh_shake.c b/slh_shake.c index 3598213..a644b11 100644 --- a/slh_shake.c +++ b/slh_shake.c @@ -169,16 +169,24 @@ static void shake_chain(slh_var_t *var, uint8_t *tmp, const uint8_t *x, return; } - memcpy(ks + h, x, n); /* start node */ + for (k = h; k < l; k++) + { + ks[k] = 0; + } + keccak_f1600_xor_bytes(ks, x, 8 * h, n); /* start node */ for (j = 0; j < s; j++) { if (j > 0) { memcpy(ks + h, ks, n); /* chaining */ } - memcpy(ks, var->pk_seed, n); /* PK.seed */ - adrs_set_hash_address(var, i + j); /* address */ - memcpy(ks + n8, (const uint8_t *)var->adrs->u8, 32); + for (k = 0; k < h; k++) + { + ks[k] = 0; + } + keccak_f1600_xor_bytes(ks, var->pk_seed, 0, n); /* PK.seed */ + adrs_set_hash_address(var, i + j); /* address */ + keccak_f1600_xor_bytes(ks, var->adrs->u8, 8 * n8, 32); /* padding */ ks[l] = 0x1F; /* shake padding */ @@ -194,7 +202,7 @@ static void shake_chain(slh_var_t *var, uint8_t *tmp, const uint8_t *x, keccak_f1600(ks); /* permutation */ } - memcpy(tmp, ks, n); + keccak_f1600_extract_bytes(ks, tmp, 0, n); } /* Combination WOTS PRF + Chain */ From b08936f26689f8067e43eb5a31ca2b7fe931cfef Mon Sep 17 00:00:00 2001 From: "Matthias J. Kannwischer" Date: Tue, 13 Oct 2026 23:20:58 +0800 Subject: [PATCH 4/5] CI: add aarch64_be cross tests Port the aarch64_be toolchain from mlkem-native and run the ACVP tests under qemu-aarch64_be. Signed-off-by: Matthias J. Kannwischer --- .github/actions/functest/action.yml | 12 ++++++++- .github/workflows/ci.yml | 24 +++++++++++++++++ Makefile | 4 ++- flake.nix | 4 +++ nix/aarch64_be-none-linux-gnu-gcc.nix | 37 +++++++++++++++++++++++++++ nix/util.nix | 7 +++++ test/acvp_client.py | 5 +++- 7 files changed, 90 insertions(+), 3 deletions(-) create mode 100644 nix/aarch64_be-none-linux-gnu-gcc.nix diff --git a/.github/actions/functest/action.yml b/.github/actions/functest/action.yml index a59ac0b..6f9d296 100644 --- a/.github/actions/functest/action.yml +++ b/.github/actions/functest/action.yml @@ -22,6 +22,12 @@ inputs: cflags: description: CFLAGS to pass to compilation default: "" + cross_prefix: + description: Binary prefix for cross compilation + default: "" + exec_wrapper: + description: Binary execution wrapper (e.g. QEMU) + default: "" runs: using: composite @@ -34,7 +40,11 @@ runs: gh_token: ${{ inputs.gh_token }} - name: Functional tests shell: ${{ env.SHELL }} + env: + CFLAGS: ${{ inputs.cflags }} + CROSS_PREFIX: ${{ inputs.cross_prefix }} + EXEC_WRAPPER: ${{ inputs.exec_wrapper }} run: | make clean - CFLAGS="${{ inputs.cflags }}" make test + make test diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4519502..e1d9199 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -98,3 +98,27 @@ jobs: gh_token: ${{ secrets.GITHUB_TOKEN }} nix-shell: ${{ matrix.compiler.shell }} cflags: "-std=c99 ${{ matrix.cflags }} ${{ matrix.target.archflags }}" + cross_tests: + name: Cross tests (${{ matrix.target.name }}) + strategy: + fail-fast: false + matrix: + target: + - runner: ubuntu-latest + name: 'aarch64_be' + nix_shell: ci-cross-aarch64_be + cross_prefix: aarch64_be-none-linux-gnu- + exec_wrapper: qemu-aarch64_be + cflags: '-static' + runs-on: ${{ matrix.target.runner }} + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - name: build+functest (cross) + uses: ./.github/actions/functest + with: + gh_token: ${{ secrets.GITHUB_TOKEN }} + nix-shell: ${{ matrix.target.nix_shell }} + nix-cache: 'true' + cflags: ${{ matrix.target.cflags }} + cross_prefix: ${{ matrix.target.cross_prefix }} + exec_wrapper: ${{ matrix.target.exec_wrapper }} diff --git a/Makefile b/Makefile index 38dd957..04b9648 100644 --- a/Makefile +++ b/Makefile @@ -9,7 +9,9 @@ OBJS = $(CSRC:.c=.o) XTEST ?= xfips205 XTESTC ?= test/xfips205.c +CROSS_PREFIX ?= CC ?= gcc +CC := $(CROSS_PREFIX)$(CC) CFLAGS := -Wall \ -Wextra \ -Werror=unused-result \ @@ -36,7 +38,7 @@ $(XTEST): $(OBJS) $(CC) $(CFLAGS) -c $^ -o $@ test: $(XTEST) - python3 test/acvp_client.py + EXEC_WRAPPER="$(EXEC_WRAPPER)" python3 test/acvp_client.py clean: $(RM) -rf $(XTEST) $(OBJS) *.rsp *.req *.log diff --git a/flake.nix b/flake.nix index 3a45118..f4b1964 100644 --- a/flake.nix +++ b/flake.nix @@ -67,6 +67,7 @@ packages.toolchain_riscv64 = util.toolchain_riscv64; packages.toolchain_riscv32 = util.toolchain_riscv32; packages.toolchain_ppc64le = util.toolchain_ppc64le; + packages.toolchain_aarch64_be = util.toolchain_aarch64_be; devShells.default = util.mkShell { packages = builtins.attrValues @@ -106,6 +107,9 @@ devShells.ci-cross-ppc64le = util.mkShell { packages = builtins.attrValues { inherit (config.packages) linters toolchain_ppc64le; }; }; + devShells.ci-cross-aarch64_be = util.mkShell { + packages = builtins.attrValues { inherit (config.packages) linters toolchain_aarch64_be; }; + }; devShells.ci-linter = util.mkShellNoCC { packages = builtins.attrValues { inherit (config.packages) linters; }; }; diff --git a/nix/aarch64_be-none-linux-gnu-gcc.nix b/nix/aarch64_be-none-linux-gnu-gcc.nix new file mode 100644 index 0000000..09e6a69 --- /dev/null +++ b/nix/aarch64_be-none-linux-gnu-gcc.nix @@ -0,0 +1,37 @@ +# Copyright (c) The mlkem-native project authors +# Copyright (c) The slhdsa-c project authors +# SPDX-License-Identifier: Apache-2.0 OR ISC OR MIT + +{ stdenvNoCC +, fetchurl +}: + +stdenvNoCC.mkDerivation rec { + pname = "aarch64_be-none-linux-gnu"; + version = "10.3.2021.07"; + + platform = { + x86_64-linux = "x86_64"; + }.${stdenvNoCC.hostPlatform.system} or (throw "Unsupported system: ${stdenvNoCC.hostPlatform.system}"); + + platform_suffix = { + x86_64-linux = "linux-gnu"; + }.${stdenvNoCC.hostPlatform.system} or (throw "Unsupported system: ${stdenvNoCC.hostPlatform.system}"); + + src = fetchurl { + url = "https://developer.arm.com/-/media/Files/downloads/gnu-a/10.3-2021.07/binrel/gcc-arm-10.3-2021.07-x86_64-aarch64_be-none-linux-gnu.tar.xz"; + sha256 = { + x86_64-linux = "sha256-Y8NMrAfOrddGIOqH8nrxqmpvVcIKW8EWryGlndtramo"; + }.${stdenvNoCC.hostPlatform.system} or (throw "Unsupported system: ${stdenvNoCC.hostPlatform.system}"); + }; + + dontConfigure = true; + dontBuild = true; + dontPatchELF = true; + dontStrip = true; + + installPhase = '' + mkdir -p $out + cp -r * $out + ''; +} diff --git a/nix/util.nix b/nix/util.nix index a3f427c..2f4db0b 100644 --- a/nix/util.nix +++ b/nix/util.nix @@ -30,6 +30,7 @@ rec { riscv64-gcc = wrap-gcc pkgs.pkgsCross.riscv64; riscv32-gcc = wrap-gcc pkgs.pkgsCross.riscv32; ppc64le-gcc = wrap-gcc pkgs.pkgsCross.powernv; + aarch64_be-gcc = (pkgs.callPackage ./aarch64_be-none-linux-gnu-gcc.nix { }); in # NOTE: # - native toolchain should be equipped in the shell via `mkShellWithCC` (see `mkShell`) @@ -40,6 +41,7 @@ rec { # - On some machines, `native-gcc` needed to be evaluated lastly (placed as the last element of the toolchain list), or else would result in environment variables (CC, AR, ...) overriding issue. # pkgs.lib.optionals cross [ pkgs.qemu x86_64-gcc aarch64-gcc riscv64-gcc riscv32-gcc ppc64le-gcc ] pkgs.lib.optionals cross [ pkgs.qemu ] + ++ pkgs.lib.optionals (cross && pkgs.stdenv.isLinux && pkgs.stdenv.isx86_64) [ aarch64_be-gcc ] ++ pkgs.lib.optionals cross [ native-gcc ] # git is not available in the nix shell on Darwin. As a workaround we add git as a dependency here. # Initially, we expected this to be fixed by https://github.com/NixOS/nixpkgs/pull/353893, but that does not seem to be the case. @@ -135,6 +137,11 @@ rec { cross_compilers = [ (wrap-gcc pkgs.pkgsCross.powernv) ]; }; + toolchain_aarch64_be = _individual_toolchain { + name = "aarch64_be"; + cross_compilers = pkgs.lib.optionals (pkgs.stdenv.isLinux && pkgs.stdenv.isx86_64) [ (pkgs.callPackage ./aarch64_be-none-linux-gnu-gcc.nix { }) ]; + }; + toolchains = pkgs.symlinkJoin { name = "toolchains"; paths = _toolchains { }; diff --git a/test/acvp_client.py b/test/acvp_client.py index 0e3adac..717b8d3 100755 --- a/test/acvp_client.py +++ b/test/acvp_client.py @@ -13,6 +13,9 @@ from concurrent.futures import ThreadPoolExecutor, as_completed from pathlib import Path +# Check if we need to use a wrapper for execution (e.g. QEMU) +exec_prefix = os.environ.get("EXEC_WRAPPER", "") + # === ACVP file downloading === def download_acvp_files(version="v1.1.0.40"): @@ -189,7 +192,7 @@ def run_test_kat(test_type, kat, jobs, xbin='./xfips205'): failed = 0 def build_command(x): - s = xbin + s = f'{exec_prefix} {xbin}' if exec_prefix else xbin for t in x: if x[t] != "": s += f' -{t} "{x[t]}"' From 554ef19c5252d0241fd3c320ccdb7713587a69ed Mon Sep 17 00:00:00 2001 From: "Matthias J. Kannwischer" Date: Wed, 14 Oct 2026 01:12:39 +0800 Subject: [PATCH 5/5] CI: skip gcc on macOS The nix gcc builds fail to run on current macOS runners (duplicate LC_RPATH). Signed-off-by: Matthias J. Kannwischer --- .github/workflows/ci.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e1d9199..3294515 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,13 +36,16 @@ jobs: darwin: False - name: gcc-11 shell: ci_gcc11 - darwin: True + # TODO: re-enable on macOS (nix gcc fails with duplicate LC_RPATH) + darwin: False - name: gcc-13 shell: ci_gcc13 - darwin: True + # TODO: re-enable on macOS (nix gcc fails with duplicate LC_RPATH) + darwin: False - name: gcc-14 shell: ci_gcc14 - darwin: True + # TODO: re-enable on macOS (nix gcc fails with duplicate LC_RPATH) + darwin: False - name: clang-18 shell: ci_clang18 darwin: True