Skip to content

Falcon: native implementation replacing liboqs, with crypto callbacks and ARM acceleration. Deprecate liboqs support.#10827

Open
danielinux wants to merge 38 commits into
wolfSSL:masterfrom
danielinux:falcon-native
Open

Falcon: native implementation replacing liboqs, with crypto callbacks and ARM acceleration. Deprecate liboqs support.#10827
danielinux wants to merge 38 commits into
wolfSSL:masterfrom
danielinux:falcon-native

Conversation

@danielinux

Copy link
Copy Markdown
Member

Description

Replaces wolfSSL's liboqs-based Falcon wrapper with a complete native
wolfCrypt implementation
of Falcon (key generation, signing and
verification, levels 1 and 5), removes the liboqs dependency entirely, adds the
standard crypto-callback interface, and adds optional per-architecture
acceleration for x86-64 and ARM.

What's in it

Native implementation (falcon.c + wc_falcon*.c, no liboqs):

  • Public API wc_falcon_* / falcon_key (single surface; the redundant
    wc_FnDsa_* layer is gone). Verify uses an integer NTT mod q=12289 with
    cached twiddle tables; signing uses the FFT / ffLDL tree + discrete Gaussian
    sampler; keygen uses the RNS/big-integer NTRU solver.
  • Floating-point fpr seam with selectable backends:
    • portable, constant-time integer-emulated FP — the default;
    • --enable-falcon-double — inline native double (fastest with a
      constant-time HW FPU);
    • --enable-falcon-asm — x86-64 SSE2 out-of-line fpr backend
      (generated; see companion wolfssl-scripts PR);
    • --enable-falcon-avx2 — x86-64 AVX2 (4-wide) FFT.
  • Division-free (Barrett) verify NTT, so no hardware divide is required on
    Cortex-M / embedded targets.

Remove liboqs: Falcon was the last liboqs consumer. Drops --with-liboqs
(and -loqs), the wolfcrypt/src/port/liboqs layer, the CMake WOLFSSL_OQS
option / FindOQS.cmake, and the VS/Zephyr/INTIME/Docker references. The
library links no liboqs. HAVE_FALCON is added to the experimental-settings
gate so the requirement holds across every build system.

Crypto callbacks (WOLF_CRYPTO_CB): keygen/sign/verify dispatch to
wc_CryptoCb_MakePqcSignatureKey / PqcSign / PqcVerify
(WC_PQC_SIG_TYPE_FALCON), with software fall-through. Adds
WOLF_CRYPTO_CB_ONLY_FALCON (first PQC signature to have it, mirroring
_RSA/_ECC): the callback becomes authoritative and the native core is
compiled out.

ARM acceleration (portable-C + intrinsics, no hand-asm):

  • Cortex-M DSP verify NTT — auto-enabled on __ARM_FEATURE_DSP
    (Cortex-M4/M7/M33). Packed 2-lane butterflies (SMLABB/SMLATB +
    SADD16/SSUB16 + USUB16/SEL) and an SMUAD squared-norm; bit-identical
    to the scalar path.
  • AArch64 NEON FFT--enable-falcon-neon, the 2-wide float64x2_t + FMA
    counterpart of the AVX2 FFT for the signing/keygen path.

Testing

  • wolfcrypt/test falcon_test: KAT verify + native keygen/sign/verify
    round-trip; the crypto-callback test drives every Falcon op through the
    callback and asserts the cb path was taken (incl. a CB_ONLY check).
  • Native ↔ liboqs interop harness + CI workflow (both directions,
    keygen+sign vs verify).
  • Invariant fuzzer: ~79k random seeds per level, checking the exact NTRU
    equation f·G − g·F = q on every keygen, h·f ≡ g (mod q), the norm bound,
    sign→native+liboqs verify, and forgery rejection — zero failures observed with this set.
  • Emulator tests for ARM acceleration (both under IDE/): m33mu-falcon-verify runs the DSP
    NTT on an STM32H563/Cortex-M33 (m33mu) — genuine sig accepted, tampered
    rejected; qemu-falcon-neon runs a full keygen→sign→verify at levels 1 and 5
    on qemu-system-aarch64 with the NEON FFT.
  • Builds green (testwolfcrypt + full testsuite) across default / cryptocb /
    CB_ONLY / verify-only and each fpr backend.

Build options

--enable-falcon native Falcon (requires --enable-experimental)
--enable-falcon-double inline native-double fpr backend
--enable-falcon-asm x86-64 SSE2 fpr asm backend
--enable-falcon-avx2 x86-64 AVX2 FFT
--enable-falcon-neon AArch64 NEON FFT

Checklist

  • added tests
  • updated/added doxygen
  • updated appropriate READMEs
  • Updated manual and documentation

Copilot AI review requested due to automatic review settings July 1, 2026 08:47
@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown

MemBrowse Memory Report

gcc-arm-cortex-m0plus

  • FLASH: .text +28 B (+0.0%, 64,183 B / 262,144 B, total: 24% used)

gcc-arm-cortex-m3

  • FLASH: .text +24 B (+0.0%, 122,505 B / 262,144 B, total: 47% used)

gcc-arm-cortex-m4

  • FLASH: .text +64 B (+0.0%, 200,384 B / 262,144 B, total: 76% used)

gcc-arm-cortex-m4-dtls13

  • FLASH: .text +64 B (+0.0%, 180,952 B / 1,048,576 B, total: 17% used)

gcc-arm-cortex-m4-openssl-compat

  • FLASH: .text +64 B (+0.0%, 771,212 B / 1,048,576 B, total: 74% used)

gcc-arm-cortex-m4-pkcs7

  • FLASH: .text +128 B (+0.1%, 212,716 B / 262,144 B, total: 81% used)

gcc-arm-cortex-m4-pq

  • FLASH: .text +64 B (+0.0%, 279,460 B / 1,048,576 B, total: 27% used)

gcc-arm-cortex-m4-rsa-only

  • FLASH: .text +128 B (+0.0%, 325,120 B / 1,048,576 B, total: 31% used)

gcc-arm-cortex-m4-tls12

  • FLASH: .text +64 B (+0.1%, 123,277 B / 262,144 B, total: 47% used)

gcc-arm-cortex-m4-tls13

  • FLASH: .text +64 B (+0.0%, 236,146 B / 262,144 B, total: 90% used)

gcc-arm-cortex-m7-pq

  • FLASH: .text +64 B (+0.0%, 280,036 B / 1,048,576 B, total: 27% used)

gcc-arm-cortex-m7-tls13

  • FLASH: .text +64 B (+0.0%, 236,210 B / 262,144 B, total: 90% used)

linuxkm-pie

  • Data: __patchable_function_entries +8 B (+0.0%, 25,560 B)

linuxkm-standard

  • Data: __patchable_function_entries +32 B (+0.1%, 48,248 B)

stm32-sim-stm32h753

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR replaces the prior liboqs-backed Falcon integration with a native wolfCrypt Falcon implementation (keygen/sign/verify for levels 1 & 5), adds crypto-callback dispatch for Falcon operations, and removes the liboqs dependency across build systems and integrations. It also introduces optional architecture-specific acceleration paths (x86-64 and ARM) and adds CI/harnesses to validate native↔liboqs interoperability.

Changes:

  • Replaces Falcon’s liboqs wrapper with native wolfCrypt Falcon core + new internal modules (FFT/fpr seam/sampler/poly/codec/bigint/keygen/sign).
  • Removes liboqs build/config/install references across autotools/CMake/IDE/Zephyr/VS/Docker, and updates docs accordingly.
  • Adds Falcon crypto-callback integration and optional x86-64/ARM acceleration plus new CI workflow + interop harness and emulator projects.

Reviewed changes

Copilot reviewed 67 out of 68 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
zephyr/CMakeLists.txt Removes liboqs port source from Zephyr build.
wrapper/CSharp/wolfssl.vcxproj Removes liboqs port source from C# wrapper project.
wolfssl/wolfcrypt/wc_falcon_sign.h New internal signing orchestration API for native Falcon.
wolfssl/wolfcrypt/wc_falcon_sampler.h New internal sampler API for signing-side Gaussian sampling.
wolfssl/wolfcrypt/wc_falcon_poly.h New internal FFT-domain polynomial ops API.
wolfssl/wolfcrypt/wc_falcon_keygen.h New internal key generation API for native Falcon.
wolfssl/wolfcrypt/wc_falcon_fpr.h New fpr seam API + backend selection surface.
wolfssl/wolfcrypt/wc_falcon_fft.h New internal FFT API + twiddle table declaration.
wolfssl/wolfcrypt/wc_falcon_codec.h New internal encode/decode routines for sign/keygen paths.
wolfssl/wolfcrypt/wc_falcon_bigint.h New internal bigint/RNS arithmetic API for keygen.
wolfssl/wolfcrypt/settings.h Drops liboqs/Falcon coupling; makes HAVE_FALCON the gate.
wolfssl/wolfcrypt/port/liboqs/liboqs.h Removes liboqs port header.
wolfssl/wolfcrypt/include.am Removes liboqs header install listing.
wolfssl/wolfcrypt/falcon.h Updates public Falcon API/macros to native implementation and adds CB_ONLY hooks.
wolfssl.vcxproj Removes liboqs port source from VS project.
wolfssl.vcproj Removes liboqs port source from legacy VS project.
wolfssl-VS2022.vcxproj Removes liboqs port source from VS2022 project.
wolfcrypt/src/wc_port.c Removes liboqs init/cleanup hooks from wolfCrypt init lifecycle.
wolfcrypt/src/wc_falcon_sampler.c New SHAKE256-based PRNG + Gaussian sampler implementation.
wolfcrypt/src/wc_falcon_poly.c New FFT-domain polynomial primitives implementation.
wolfcrypt/src/wc_falcon_fpr_x86_64_asm.S New x86-64 asm fpr backend (generated).
wolfcrypt/src/wc_falcon_fft_neon.c New AArch64 NEON FFT backend for signing/keygen.
wolfcrypt/src/wc_falcon_codec.c New native Falcon codec implementation for sign/keygen paths.
wolfcrypt/src/port/liboqs/liboqs.c Removes liboqs port implementation source.
wolfcrypt/src/include.am Removes liboqs port source from dist list.
wolfcrypt/src/falcon.c Reworks public wc_falcon_* wrapper to dispatch to native core and crypto callbacks.
src/tls.c Removes liboqs mention; updates ECC extension error text to ML-KEM wording.
src/include.am Adds native Falcon sources and optional asm/AVX2/NEON sources under new conditionals.
scripts/falcon-interop.c New native↔liboqs Falcon interop harness program.
INSTALL Updates Falcon enablement instructions (native Falcon + experimental gate).
IDE/qemu-falcon-neon/user_settings.h New bare-metal AArch64 NEON test settings.
IDE/qemu-falcon-neon/start.S New AArch64 minimal reset/FP+MMU bring-up for QEMU test.
IDE/qemu-falcon-neon/README.md New documentation for AArch64 NEON FFT QEMU test.
IDE/qemu-falcon-neon/Makefile New build recipe for the QEMU AArch64 NEON test firmware.
IDE/qemu-falcon-neon/main.c New AArch64 firmware exercising native Falcon sign/verify with NEON FFT.
IDE/qemu-falcon-neon/link.ld New linker script for QEMU AArch64 test firmware.
IDE/qemu-falcon-neon/.gitignore New ignore rules for generated QEMU test artifacts.
IDE/m33mu-falcon-verify/user_settings.h New Cortex-M33 verify-only test settings.
IDE/m33mu-falcon-verify/target.ld New linker script for STM32H563 (m33mu) verify test.
IDE/m33mu-falcon-verify/syscalls.c New minimal syscall stubs for bare-metal test.
IDE/m33mu-falcon-verify/README.md New documentation for Cortex-M DSP verify acceleration test.
IDE/m33mu-falcon-verify/Makefile New build recipe for Cortex-M33 verify-only firmware.
IDE/m33mu-falcon-verify/main.c New firmware exercising Falcon-512 verify and tamper rejection.
IDE/m33mu-falcon-verify/kat.h New embedded Falcon-512 KAT vectors for the verify test.
IDE/m33mu-falcon-verify/ivt.c New interrupt vector table for bare-metal Cortex-M test.
IDE/m33mu-falcon-verify/.gitignore New ignore rules for generated m33mu test artifacts.
IDE/INTIME-RTOS/Makefile Removes liboqs header directory handling in INTIME packaging.
IDE/INTIME-RTOS/libwolfssl.vcxproj Removes liboqs port source from INTIME VS project.
Docker/Dockerfile Removes liboqs install step from Docker build environment.
doc/dox_comments/header_files/falcon.h New doxygen documentation for Falcon public API.
doc/dox_comments/header_files/doxygen_groups.h Adds Falcon doxygen group definition.
configure.ac Removes liboqs option; adds Falcon native options + feature conditionals and flags.
CMakeLists.txt Removes liboqs options and adds native Falcon option + required SHA-3/SHAKE256 defs.
cmake/options.h.in Removes HAVE_LIBOQS config define emission.
cmake/modules/FindOQS.cmake Removes liboqs CMake find-module.
cmake/include.am Removes FindOQS.cmake from distributed files.
cmake/functions.cmake Removes liboqs helper source inclusion and adds Falcon source list plumbing.
.github/workflows/falcon-interop.yml New CI workflow building liboqs baseline and running native↔liboqs interop matrix.
.github/workflows/cmake.yml Removes explicit WOLFSSL_OQS CMake option usage in CI workflow.
Comments suppressed due to low confidence (1)

wolfcrypt/src/falcon.c:130

  • wc_falcon_sign_msg() can call the crypto-callback and/or native signer with rng==NULL, which can lead to a NULL dereference or use of an uninitialized RNG. Also, in verify-only builds the function should not attempt a signing crypto-callback before returning NOT_COMPILED_IN (per the verify-only contract described in falcon.h).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread cmake/functions.cmake Outdated
Comment thread wolfcrypt/src/wc_falcon_sampler.c Outdated
Comment thread wolfcrypt/src/wc_falcon_sampler.c Outdated
Comment thread wolfcrypt/src/wc_falcon_fft_neon.c
Comment thread wolfcrypt/src/wc_falcon_fft_neon.c
Comment thread wolfssl/wolfcrypt/include.am
@danielinux danielinux changed the title Falcon: native implementation replacing liboqs, with crypto callbacks and ARM acceleration. Deprecate libOQS support. Falcon: native implementation replacing liboqs, with crypto callbacks and ARM acceleration. Deprecate liboqs support. Jul 1, 2026
@julek-wolfssl julek-wolfssl requested a review from Copilot July 1, 2026 10:53

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 72 out of 73 changed files in this pull request and generated 7 comments.

Comment thread wolfssl/wolfcrypt/wc_falcon_codec.h Outdated
Comment thread wolfcrypt/src/falcon.c
Comment thread wolfcrypt/src/falcon.c
Comment thread wolfcrypt/src/wc_falcon_sign.c Outdated
Comment thread wolfcrypt/src/wc_falcon_sign.c Outdated
Comment thread wolfcrypt/src/wc_falcon.c Outdated
Comment thread INSTALL

@wolfSSL-Fenrir-bot wolfSSL-Fenrir-bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fenrir Automated Review — PR #10827

Scan targets checked: wolfcrypt-bugs, wolfcrypt-port-bugs, wolfcrypt-rs-bugs, wolfcrypt-src, wolfssl-bugs, wolfssl-src

Findings: 2
2 finding(s) posted as inline comments (see file-level comments below)

This review was generated automatically by Fenrir. Findings are non-blocking.

Comment thread wolfcrypt/src/falcon.c
Comment thread wolfcrypt/src/wc_falcon.c Outdated

@Frauschi Frauschi left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some smaller initial things regarding the infrastructure. I haven't reviewed the heavy math stuff, this is probably better handled by @SparkiDev.

Comment thread zephyr/CMakeLists.txt
Comment thread configure.ac Outdated
Comment thread configure.ac Outdated
Comment thread wolfcrypt/src/falcon.c Outdated
@danielinux danielinux self-assigned this Jul 6, 2026
@danielinux danielinux requested a review from Frauschi July 6, 2026 08:09
@danielinux danielinux assigned Frauschi and unassigned danielinux Jul 8, 2026

@Frauschi Frauschi left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🐺 Skoll Code Review

Overall recommendation: REQUEST_CHANGES
Findings: 8 total — 8 posted, 0 skipped

Posted findings

  • [Medium] Private-key NTT(f) left in freed heap in falcon_compute_public (no ForceZero)wolfcrypt/src/wc_falcon_keygen.c:310
  • [Medium] Sampler rejection loop can hang on a mid-signature PRNG failurewolfcrypt/src/wc_falcon_sampler.c:321
  • [Medium] Recursion used in signing path violates wolfSSL no-recursion conventionwolfcrypt/src/wc_falcon_sign.c:367
  • [Low] SHAKE256 context leaked when sampler init fails after wc_InitShake256 succeedswolfcrypt/src/wc_falcon_sampler.c:133
  • [Low] wc_falcon_check_key doxygen overstates behavior (no cryptographic cross-check)wolfcrypt/src/falcon.c:657
  • [Low] Undefined shift in trim_i8 encode/decode if bits is 0 or >= 32wolfcrypt/src/wc_falcon_codec.c:189
  • [Info] Internally allocated h scratch freed without ForceZerowolfcrypt/src/wc_falcon_keygen.c:1892
  • [Info] PRNG re-squeezes on every call once error is latchedwolfcrypt/src/wc_falcon_sampler.c:158

Review generated by Skoll via Claude/Codex

Comment thread wolfcrypt/src/wc_falcon_keygen.c Outdated
Comment thread wolfcrypt/src/wc_falcon_sampler.c Outdated
Comment thread wolfcrypt/src/wc_falcon_sign.c Outdated
Comment thread wolfcrypt/src/wc_falcon_sampler.c Outdated
Comment thread wolfcrypt/src/falcon.c
Comment thread wolfcrypt/src/wc_falcon_codec.c Outdated
Comment thread wolfcrypt/src/wc_falcon_keygen.c Outdated
Comment thread wolfcrypt/src/wc_falcon_sampler.c Outdated
@danielinux danielinux requested a review from Frauschi July 9, 2026 13:25
@danielinux

Copy link
Copy Markdown
Member Author

405db5c ran in fuzzer with 60,000,144 sign/verify round-trips, zero failures.

Add a complete native Falcon post-quantum lattice signature implementation to
wolfCrypt, replacing the liboqs wrapper. Full key generation, signing and
verification for Falcon-512 (level 1) and Falcon-1024 (level 5).

  - Public API wc_falcon_* / falcon_key in falcon.c wraps the native core
    (falcon_native_* in wc_falcon.c) plus wc_falcon_{fpr,fft,poly,sampler,
    codec,keygen,sign,bigint}.c. No liboqs dependency.
  - Portable, constant-time integer-emulated floating-point (fpr) backend is
    the default; opt-in per-architecture acceleration:
      --enable-falcon-double  inline native double
      --enable-falcon-asm     x86-64 SSE2 out-of-line fpr asm
      --enable-falcon-avx2    x86-64 AVX2 (4-wide) FFT
  - Division-free (Barrett) integer NTT on the verify path, so no hardware
    divide is required on Cortex-M / embedded targets.
  - Verify uses a cached twiddle-factor NTT; signing uses the FFT / ffLDL tree
    and discrete Gaussian sampler over the abstract fpr seam.
  - test.c falcon_test (KAT verify + native keygen/sign/verify roundtrip);
    scripts/falcon-interop.c and a CI workflow cross-check native<->liboqs in
    both directions.
Falcon was the last algorithm backed by liboqs; now that wolfCrypt has a
native Falcon implementation, liboqs is no longer needed. Remove the
integration entirely so liboqs does not appear as a build or SBOM dependency:

  - configure: drop --with-liboqs (and the -loqs link), the BUILD_LIBOQS
    conditional and the summary line.
  - CMake: drop WOLFSSL_OQS, the duplicate liboqs-backed WOLFSSL_FALCON
    option, the OQS cross-validation / find_package(OQS) block, the
    FindOQS.cmake module, BUILD_OQS_HELPER, and HAVE_LIBOQS from options.h.in.
  - Remove the wolfcrypt/src/port/liboqs port layer (liboqs.c/.h) and its
    wolfSSL_liboqsInit/Close calls in wc_port.c.
  - settings.h: drop HAVE_LIBOQS from the asym key import/export aggregates
    (HAVE_FALCON already covers them) and from the experimental gate; add
    HAVE_FALCON to the experimental gate so the unstandardized Falcon requires
    WOLFSSL_EXPERIMENTAL_SETTINGS in every build system.
  - Drop liboqs.c from the VS/Zephyr/INTIME project files, remove the liboqs
    install from Docker, and update INSTALL/tls.c text (Falcon is native now).

No functional change to non-Falcon builds; the library links no liboqs.
Wire Falcon into the crypto callback framework like the other algorithms:

  - wc_falcon_make_key now dispatches to wc_CryptoCb_MakePqcSignatureKey
    (WC_PQC_SIG_TYPE_FALCON); wc_falcon_sign_msg / wc_falcon_verify_msg already
    dispatched to wc_CryptoCb_PqcSign / PqcVerify. All three fall through to the
    software implementation when the callback is unavailable.

  - Add WOLF_CRYPTO_CB_ONLY_FALCON (mirrors WOLF_CRYPTO_CB_ONLY_RSA/ECC): the
    callback becomes authoritative (no software fallback; returns NO_VALID_DEVID
    when no device is registered) and the native core (wc_falcon*.c) is compiled
    out entirely. WC_FALCON_HAVE_NATIVE_SIGN and the falcon_native_* prototypes
    are gated off in that build.

Tests (test.c):
  - myCryptoDevCb gains a Falcon branch for PQC keygen/sign/verify.
  - falcon_test / falcon_verify_kat now use the global test devId, so
    cryptocb_test drives every Falcon operation through the callback and asserts
    (via the exampleVar hit counter) that the cb path was actually taken.
  - Under WOLF_CRYPTO_CB_ONLY_FALCON, falcon_test instead confirms the API
    returns NO_VALID_DEVID with no device registered, and the KAT data/verifier
    (software-only) are compiled out.

Verified: default (no cryptocb), --enable-cryptocb, and
-DWOLF_CRYPTO_CB_ONLY_FALCON all build and pass testwolfcrypt (falcon_test +
crypto callback test); the CB_ONLY library contains no falcon_native_* symbols.
Add an optional Cortex-M (ARMv7E-M / ARMv8-M) DSP-accelerated verify path,
auto-enabled on cores with the DSP extension (__ARM_FEATURE_DSP; Cortex-M4/M7/
M33). It is bit-identical to the scalar Barrett path (validated over 40k random
polynomials per level) and controlled by WOLFSSL_FALCON_NTT_DSP /
WOLFSSL_FALCON_NO_NTT_DSP.

  - NTT/iNTT butterflies process two packed 16-bit coefficients per iteration:
    SMLABB/SMLATB 16x16 twiddle multiplies (values are < q < 2^14), SADD16/
    SSUB16 packed adds, and a USUB16+SEL packed conditional subtract of q.
  - The pointwise multiply packs two coefficients per iteration (SMLABB/SMLATT).
  - The squared l2-norm accumulates two coefficients per SMUAD (a.lo^2+a.hi^2).

Tested on the STM32H563 (Cortex-M33) emulator m33mu via the new
IDE/m33mu-falcon-verify harness: the DSP path accepts a genuine Falcon-512 KAT
signature and rejects a tampered one (BKPT 0x7f), with SMUAD/USUB16/SEL/SMLABB
confirmed present in the image.

Also fixes three latent verify-only (WOLFSSL_FALCON_VERIFY_ONLY) build issues
surfaced by the embedded target:
  - falcon.h: include random.h unconditionally so WC_RNG is visible for the
    always-declared wc_falcon_sign_msg prototype.
  - falcon.c: define falcon_store_pub_behind_priv unconditionally
    (wc_falcon_import_public, a verify-only op, calls it).
  - falcon.c: silence unused inLen/rng in the verify-only sign stub.
Add wc_falcon_fft_neon.c, the 2-wide-double counterpart of the AVX2 FFT
backend: it processes two doubles per 128-bit vector with fused multiply-add
for the complex butterflies (falcon_FFT / falcon_iFFT), over the inline-double
fpr backend. Enabled with --enable-falcon-neon (AArch64; implies
--enable-falcon-double). Advanced SIMD is part of the ARMv8-A baseline, so no
special -march is needed; the scalar FFT/iFFT in wc_falcon_fft.c are excluded
under WOLFSSL_FALCON_FFT_NEON, and only the tail level (ht/t == 1) falls back to
scalar. Like the AVX2 backend, it does not promise bit-identical (no-FMA)
results; that is safe for the signing FFT (the sampler's determinism and
verification are unaffected).

Tested under qemu-system-aarch64 -machine virt (cortex-a53) via the new
IDE/qemu-falcon-neon bare-metal harness: a full keygen -> sign -> verify
round-trip at levels 1 and 5 accepts genuine signatures and rejects tampered
ones (NEON_FFT_PASS), with vector fmla v.2d confirmed in the FFT.
Add doc/dox_comments/header_files/falcon.h covering every public wc_falcon_* /
wc_Falcon_* function (init/init_ex/init_id/init_label, set/get level, make_key,
sign/verify, import/export public+private, check_key, sizes, and the DER
encode/decode helpers), plus a Falcon \defgroup in doxygen_groups.h. List
Falcon in the INSTALL algorithm summary.

Docs refer to the algorithm only as "Falcon" (it is not standardized yet); the
temporary-name note and the configure --help / summary text are reworded to not
name a specific future standard.
Scrub the temporary "FN-DSA" name and the "FIPS 206" designation from all
in-tree comments, build text, and message strings, leaving the algorithm
named only as "Falcon". The eventual standardized name is not announced.

The differential known-answer test message ("wolfSSL FN-DSA differential
KAT") is a signed input, so the Falcon-512/1024 public keys and signatures
in wolfcrypt/test/test.c (and the mirrored Falcon-512 vector in
IDE/m33mu-falcon-verify/kat.h) were regenerated with liboqs over the new
message "wolfSSL Falcon differential KAT", preserving the differential
property (liboqs-produced signatures verified by the native verifier).

Verified: testwolfcrypt Falcon test passes; the m33mu verify-only harness
passes (BKPT 0x7f) with the regenerated vector.
The DSP-NTT gate in wc_falcon.c keys off the compiler-predefined
__ARM_FEATURE_DSP, and the FPR_DOUBLE soft-double guard in wc_falcon_fpr.h
keys off __ARM_FP; neither was listed in .wolfssl_known_macro_extras, so
the source-text unknown-macro check flags them. Add both in sorted order.
wolfSSL C style forbids recursion. The reference implementation expresses
the ffLDL tree construction, tree normalization and Fast Fourier sampling
as self-recursions on logn; flatten all three into iterations over small
fixed-size frame stacks (depth bounded by logn <= 10):

- ffLDL_fft_inner and ffLDL_binary_normalize do all their work before
  their two half-degree sub-invocations, so a LIFO work list of pending
  jobs suffices (at most logn + 1 <= 11 frames).

- falcon_ffSampling_fft has work interleaved between its sub-invocations,
  so it becomes a three-stage state machine over at most 8 frames. The
  reference's inlined base cases are split out into WC_INLINE helpers
  (ffSampling_fft_deg4 / ffSampling_fft_deg2), and a bounds check on logn
  keeps the frame stack safe even for invalid callers.

Sub-invocations run in exactly the reference order; for ffSampling this is
a hard requirement since the discrete Gaussian sampler consumes a PRNG
stream, and any reordering would change the produced signatures.

Verified with a differential harness (old recursive code from git vs the
new build): expanded keys and ffSampling outputs are bit-identical for
logn 1..10, with identical sampler invocation counts and an identical
order-sensitive hash of every (mu, isigma) sampler argument pair.
Performance is unchanged (within noise at logn 9 and 10).
Remaining fixes from the second review round:

- keygen: falcon_compute_public's scratch buffer holds NTT(f) (private-key
  material) in its tail; wc_ForceZero it before both frees (the
  f-not-invertible reject path and the success path). Also zeroize the
  internally allocated hwork for consistency with the tmpbuf hardening.

- sampler: falcon_sampler_z's rejection loop never consulted the sticky
  PRNG error flag, so a mid-signature SHAKE256 squeeze failure could make
  berexp deterministically reject and the loop spin forever. Check p.err
  each iteration and bail out; the returned value is discarded since
  falcon_sign_core rejects the whole signature once p.err is set.
  falcon_prng_init now frees the SHAKE256 context when a later init step
  fails (plugs a device-context leak in WOLFSSL_ASYNC_CRYPT builds), and
  falcon_prng_refill early-returns once the error is latched instead of
  re-issuing failing squeezes.

- codec: guard the bits-dependent shifts in falcon_trim_i8_encode/decode
  against out-of-range widths (defense in depth; callers only pass 5..8).

- check_key: implement the cryptographic private/public cross-check that
  91ebd89 documented as a follow-up. New falcon_native_check_key decodes
  (f, g) from the private key and h from the public key and verifies the
  defining relation h*f == g (mod q, mod X^n + 1) slot-wise in the NTT
  domain (falcon_ntt keeps values canonical in [0, q)); a slot with
  NTT(f) == 0 is rejected too, as keygen only emits invertible f.
  wc_falcon_check_key dispatches to it whenever the native signing core is
  compiled in, and falls back to the presence check in verify-only /
  callback-only builds. Doxygen updated to the actual contract, and a unit
  test added: a mismatched pair (public half from a different key) must
  fail with PUBLIC_KEY_E. This also strengthens the keypair validation
  done via wc_falcon_check_key in asn.c.
Regenerated from wolfssl-scripts falcon/x86_64/falcon.rb (commit
b680440), which documents in the file header that the backend is
SysV AMD64 ABI only and requires the default MXCSR state.  No code
change, comment only.
test_wc_falcon_sign_verify in tests/api/test_signature.c was merged to
master gated on HAVE_FALCON && HAVE_LIBOQS.  With liboqs removed the
macro is defined nowhere, so the test was dead code and check-source-text
failed with 'unrecognized macros used: HAVE_LIBOQS'.

Gate it on WC_FALCON_HAVE_NATIVE_SIGN like the rest of the native
signing tests, and replace the obsolete liboqs-RNG comment.  The tree
now has zero HAVE_LIBOQS references, so no .wolfssl_known_macro_extras
entry is needed.

@Frauschi Frauschi left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🐺 Skoll Code Review

Overall recommendation: APPROVE
Findings: 5 total — 4 posted, 1 skipped

Posted findings

  • [Low] Stale "Phase 1: verification only" file header commentwolfcrypt/src/wc_falcon.c:22-26
  • [Low] wc_falcon_verify_msg doc comment references non-existent contextLenwolfcrypt/src/falcon.c:160-162
  • [Medium] Sign compression-fit retry silently caps at 32 attemptswolfcrypt/src/wc_falcon.c:824-855
  • [Medium] No unit test exercises the concat(priv,pub) legacy import size path directlywolfcrypt/src/falcon.c:448-484
Skipped findings
  • [Low] Bare-scope { } blocks used to introduce mid-function declarations

Review generated by Skoll via Claude/Codex

Comment thread wolfcrypt/src/wc_falcon.c Outdated
Comment thread wolfcrypt/src/falcon.c
Comment thread wolfcrypt/src/wc_falcon.c Outdated
Comment thread wolfcrypt/src/falcon.c
@Frauschi Frauschi assigned danielinux and unassigned wolfSSL-Bot Jul 13, 2026
- wc_falcon.c: replace the stale "Phase 1: verification only" file banner
  (the file now holds keygen/sign/verify cores).
- falcon.c: fix the garbled wc_falcon_verify_msg doc comment (removed a
  non-existent contextLen parameter; state the level-dependent BUFFER_E
  bound and the *res convention).
- wc_falcon.c: name the sign compression-fit retry bound
  FALCON_SIGN_MAX_ENCODE_RETRIES (was a bare 32) and document why the
  bound is safe, mirroring FALCON_SIGN_MAX_RESTARTS in wc_falcon_sign.c.
- test_falcon.c: add a direct wc_falcon_import_private_only concat(priv,pub)
  test that recovers the public key and signs+verifies from that single
  import, covering the recover-pub-from-concat path end to end.
@Frauschi

Copy link
Copy Markdown
Contributor

🟡 [Medium] Alternate fpr/FFT backends (asm, double, avx2, neon) not exercised by CI
💡 SUGGEST test

The added CI job builds only ./configure --enable-falcon --enable-experimental (.github/workflows/falcon-interop.yml:126) — i.e. just the default portable-integer fpr backend on x86-64. The four selectable backends (asm, double, avx2, neon) are the highest-risk new code (SIMD intrinsics, generated asm), yet they're currently validated only out-of-band via the IDE/ emulator harnesses.

Good news: all four can run on GitHub-hosted runners — none need self-hosted hardware or a mandatory emulator. Backend dispatch is compile-time with no runtime CPUID fallback, so the runner CPU is the deciding factor. What each needs to execute:

Backend ISA needed to run Runner
double none (hardware double) any (ubuntu-latest)
asm SSE2 scalar (movsd/addsd/mulsd) — baseline on all x86-64 ubuntu-latest
avx2 AVX2 + FMA3 — present on all GitHub x86 runners (Xeon 8272CL/8370C, EPYC 7763) ubuntu-latest
neon ARM64 NEON + FMA ubuntu-24.04-arm (free ARM64 runners for public repos) or macos-14 (Apple Silicon)

The avx2 TU uses per-function __attribute__((target("avx2,fma"))), so it compiles cleanly without a global -mavx2 — only the runtime CPU needs the features, and GitHub's standard runners have had AVX2+FMA3 for years.

Suggested matrix (respecting the configure constraints — double XOR asm, and avx2/neon imply double):

  • ubuntu-latest (x86-64): matrix --enable-falcon={asm,double,avx2} → build + run testwolfcrypt. Covers 3 of 4 natively.
  • ubuntu-24.04-arm (ARM64, free for public repos): --enable-falcon=neon → build + run. Covers NEON natively.

One caveat: since backend selection is compile-time with no CPUID guard, an avx2 CI job is implicitly pinned to AVX2-capable runners. That's effectively always true on GitHub, but a one-line grep -q avx2 /proc/cpuinfo guard (or runtime dispatch) would make it robust if a runner class ever changes.

@danielinux

Copy link
Copy Markdown
Member Author

🟡 [Medium] Alternate fpr/FFT backends (asm, double, avx2, neon) not exercised by CI 💡 SUGGEST test

The added CI job builds only ./configure --enable-falcon --enable-experimental (.github/workflows/falcon-interop.yml:126) — i.e. just the default portable-integer fpr backend on x86-64. The four selectable backends (asm, double, avx2, neon) are the highest-risk new code (SIMD intrinsics, generated asm), yet they're currently validated only out-of-band via the IDE/ emulator harnesses.

Good news: all four can run on GitHub-hosted runners — none need self-hosted hardware or a mandatory emulator. Backend dispatch is compile-time with no runtime CPUID fallback, so the runner CPU is the deciding factor. What each needs to execute:
Backend ISA needed to run Runner
double none (hardware double) any (ubuntu-latest)
asm SSE2 scalar (movsd/addsd/mulsd) — baseline on all x86-64 ubuntu-latest
avx2 AVX2 + FMA3 — present on all GitHub x86 runners (Xeon 8272CL/8370C, EPYC 7763) ubuntu-latest
neon ARM64 NEON + FMA ubuntu-24.04-arm (free ARM64 runners for public repos) or macos-14 (Apple Silicon)

The avx2 TU uses per-function __attribute__((target("avx2,fma"))), so it compiles cleanly without a global -mavx2 — only the runtime CPU needs the features, and GitHub's standard runners have had AVX2+FMA3 for years.

Suggested matrix (respecting the configure constraints — double XOR asm, and avx2/neon imply double):

* **`ubuntu-latest` (x86-64):** matrix `--enable-falcon={asm,double,avx2}` → build + run `testwolfcrypt`. Covers 3 of 4 natively.

* **`ubuntu-24.04-arm` (ARM64, free for public repos):** `--enable-falcon=neon` → build + run. Covers NEON natively.

One caveat: since backend selection is compile-time with no CPUID guard, an avx2 CI job is implicitly pinned to AVX2-capable runners. That's effectively always true on GitHub, but a one-line grep -q avx2 /proc/cpuinfo guard (or runtime dispatch) would make it robust if a runner class ever changes.

I'll get there in the mc-dc coverage campaign
see #10845 and #10876 -- I would keep those out for now, there will be new api tests added as soon as we get rid of liboqs.

Frauschi
Frauschi previously approved these changes Jul 14, 2026

@Frauschi Frauschi left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, over to @SparkiDev.

@Frauschi Frauschi removed their assignment Jul 14, 2026
Merge the nine wc_falcon_*.c cores into wolfcrypt/src/falcon.c, matching the
one-file-per-algorithm convention (ed25519.c, wc_mldsa.c). Sign and keygen are
gated internally on WOLFSSL_FALCON_VERIFY_ONLY and the internal helpers become
static. The fpr/fft/poly primitives stay external (shared with the separate
AVX2/NEON FFT backends) so their three headers are retained; the other five
internal headers are removed.
The max bit width for encoding F and G is a constant 8 for every Falcon
degree, so the per-logn lookup table carried no information. Replace it with
a FALCON_MAX_FG_BITS define. falcon_max_fg_bits stays a table (it genuinely
varies with logn).
Reflow the REV10 bit-reversal table into an aligned 12-per-row grid (values
right-justified) and collapse the one-value-per-line falcon_max_fg_bits into a
single row with a logn index legend. No functional change.
falcon_hash_to_point malloc'd a buffer solely to concatenate nonce and message
for a single wc_Shake256_Absorb. Since Absorb is an Update plus the SHAKE
finalize on the same sponge, feed the nonce with wc_Shake256_Update first and
the message with Absorb: same absorbed bytes, same state, but no allocation or
copy on every sign and verify.
Sign (8), keygen (5) and check_key (6) each carved their temporaries from that
many separate XMALLOCs; combine each into one alignment-ordered arena with a
single free and a single ForceZero over the secrets. Verify's ~8KB working set
(h/c/t/s2) now lives on the stack by default and only allocates under
WOLFSSL_SMALL_STACK. Cuts allocation churn to one call per operation.
Port the Falcon reference dynamic signer (sign_dyn / ffSampling_fft_dyntree),
which rebuilds the ffLDL tree inside the sampler instead of precomputing the
whole expanded key -- roughly halving the sign working set (Falcon-1024 ~168KB
to ~78KB) at the cost of redoing the tree work per attempt. The reference
recursion is flattened to an explicit stack per the no-recursion rule, and the
signer is byte-for-byte identical to the default path (verified with a
deterministic-RNG differential over both levels). Selected with
--enable-falcon=small-mem (WOLFSSL_FALCON_SIGN_SMALL_MEM); the eager tree signer
is compiled out in that mode.
falcon_prng_get_u64 and get_rng_u64 assembled a little-endian word64 from eight
explicit byte shifts. Replace both with a falcon_load_le64 helper: a single word
load on a little-endian CPU and a load plus byte-swap on a big-endian one, using
readUnalignedWord64/ByteReverseWord64. The spec-defined MSB-first bitstream codec
reads and the big-endian hash-to-point read are unchanged (their byte order is
fixed by the Falcon spec, not the CPU).
A review flagged zint_bezout as looking inefficient. It is in fact the reference
constant-time binary GCD: x and y derive from the secret key during keygen, so
its fixed-count loops and mask/sign-bit conditionals (rather than data-dependent
branches or early exits) are what keep the private key off the timing side
channel. It already reduces the operands ~31 bits per iteration word-wise. Add a
comment explaining this so the structure is not mistaken for a missed
optimization; no code change.

@Frauschi Frauschi left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🐺 Skoll Code Review

Overall recommendation: APPROVE
Findings: 4 total — 3 posted, 1 skipped

Posted findings

  • [Medium] WOLFSSL_FALCON_SIGN_STATS references an undefined symbol (link error when enabled)wolfcrypt/src/falcon.c:6947-6951
  • [Low] configure.ac --enable-falcon help omits the accepted 'small-mem' sub-optionconfigure.ac:1840
  • [Info] Native check_key uses hardware modulo despite division-free NTT elsewherewolfcrypt/src/falcon.c:8221-8227
Skipped findings
  • [Low] Pervasive bare-scope { ... } blocks in new Falcon C code

Review generated by Skoll via Claude/Codex

Comment thread wolfcrypt/src/falcon.c Outdated
Comment thread configure.ac Outdated
Comment thread wolfcrypt/src/falcon.c
…rrett)

- configure.ac: list small-mem in the --enable-falcon help string and the
  sub-option doc block (the parser already accepted it).
- WOLFSSL_FALCON_SIGN_STATS: define falcon_sign_restart_count at file scope so
  the debug knob links, and drop the mid-function extern (C89 decl-at-top).
- falcon_native_check_key: reduce h*ft with falcon_barrett instead of a hardware
  '%', matching the verify path -- division-free and constant-time on ft (the NTT
  image of secret f).
The Falcon workflow only built --enable-falcon (default integer fpr). Add a
falcon_backends matrix that builds and runs testwolfcrypt for asm, double, avx2,
small-mem and avx2+small-mem on ubuntu-latest, and neon on the free ubuntu-24.04-arm
runner -- the SIMD/asm backends are the highest-risk code and now execute in CI.
verify-only is a build/link-only leg (its self-test needs signing). An AVX2 leg
guards on /proc/cpuinfo since backend dispatch is compile-time.

@Frauschi Frauschi left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants