Falcon: native implementation replacing liboqs, with crypto callbacks and ARM acceleration. Deprecate liboqs support.#10827
Falcon: native implementation replacing liboqs, with crypto callbacks and ARM acceleration. Deprecate liboqs support.#10827danielinux wants to merge 27 commits into
Conversation
There was a problem hiding this comment.
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.
wolfSSL-Fenrir-bot
left a comment
There was a problem hiding this comment.
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.
Frauschi
left a comment
There was a problem hiding this comment.
Some smaller initial things regarding the infrastructure. I haven't reviewed the heavy math stuff, this is probably better handled by @SparkiDev.
Frauschi
left a comment
There was a problem hiding this comment.
🐺 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 failure —
wolfcrypt/src/wc_falcon_sampler.c:321 - [Medium] Recursion used in signing path violates wolfSSL no-recursion convention —
wolfcrypt/src/wc_falcon_sign.c:367 - [Low] SHAKE256 context leaked when sampler init fails after wc_InitShake256 succeeds —
wolfcrypt/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 >= 32 —
wolfcrypt/src/wc_falcon_codec.c:189 - [Info] Internally allocated h scratch freed without ForceZero —
wolfcrypt/src/wc_falcon_keygen.c:1892 - [Info] PRNG re-squeezes on every call once error is latched —
wolfcrypt/src/wc_falcon_sampler.c:158
Review generated by Skoll via Claude/Codex
|
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.
- codespell.yml: add "fpr"/"FPR" to ignore_words_list. "fpr" is the Falcon reference's canonical name for the floating-point primitive seam and appears hundreds of times across the sources; it is not a typo. - wc_falcon.c: rename local "clen" -> "compLen" (codespell flagged clen) and replace a non-ASCII em-dash in a comment with "--" (check-source-text 8-bit byte violation). - wc_falcon_bigint.c: fix typos "morever" -> "moreover", "Mutiply" -> "Multiply". - wc_falcon_codec.c: reword "are statics in" -> "are static functions in" (codespell flagged "statics").
- cmake/functions.cmake: remove duplicated BUILD_FALCON conditional block; BUILD_FALCON is now set in one place. - wolfssl/wolfcrypt/include.am: list the internal wc_falcon_*.h headers under an "if BUILD_FALCON" block so they ship in "make dist" tarballs and install when Falcon is enabled (the public falcon.h was already listed). Automake distributes both branches of the conditional, so a Falcon-disabled dist still contains them. - wc_falcon_fft_neon.c: fix strict-aliasing UB in falcon_FFT/falcon_iFFT. fpr is a word64 bit pattern; casting fpr* to double* and using vld1q_f64/vst1q_f64 accesses the store through the wrong type and can miscompile at -O2/-O3. Load/store through uint64_t (the real object type) and reinterpret the vector register to/from f64 via new FALCON_VLD/FALCON_VST macros. Verified under qemu (NEON_FFT_PASS) with fmla v.2d still emitted. - wc_falcon_sampler.c / wc_falcon_sampler.h: falcon_prng_get_u8/get_u64 have no error return, so a SHAKE256 squeeze failure previously went unnoticed and stale buffer bytes could feed the signer. Add a sticky "err" field latched on the first refill failure. - wc_falcon_sign.c: falcon_sign_core now rejects the signature when the PRNG latched an error, and falcon_do_sign_tree bounds its restart loop (FALCON_SIGN_MAX_RESTARTS) so a wedged sampler terminates instead of spinning forever. The bound is far beyond any legitimate restart count.
The earlier codespell fix reworded the same phrasing in wc_falcon_codec.c but missed the mirrored comment in the header; codespell flagged "statics" (==> statistics) in both. Reword to "static functions".
The falcon_interop job restored/untarred liboqs into the workspace and only then ran actions/checkout, whose "git clean -ffdx" deleted the untracked oqs-install/ directory. The interop harness build then failed with "oqs/oqs.h: No such file or directory". Move the wolfSSL checkout ahead of the cache-restore / artifact-download / untar steps so the liboqs install lands after the clean and survives. The harness itself is fine: built locally against liboqs it passes all eight interop cells (liboqs<->native, both directions, levels 1 and 5).
Falcon had crypto-level coverage (KAT + native round-trip in
wolfcrypt/test/test.c) but, unlike ML-DSA and SLH-DSA, no dedicated
tests/api/ unit test exercising the public wc_falcon_* / wc_Falcon_* API
surface. This adds one, wired into the unit test runner as the "falcon"
group.
Coverage (both Falcon-512 / L1 and Falcon-1024 / L5, which are always
compiled together):
- sizes: size/priv_size/pub_size/sig_size vs the spec constants,
get_level round-trip, and NULL / unset-level rejection.
- make_key: NULL and unset-level rejection; real keygen -> check_key.
- sign_vfy: sign -> verify; wrong-message and one-byte tamper rejected;
too-small buffer -> BUFFER_E with the required length set;
verify with no public key -> BAD_FUNC_ARG.
- import_export: public / private-only (raw) / private (concat) / export_key
round-trips, each re-signed or verified, plus too-small
(BUFFER_E) and wrong-size (BAD_FUNC_ARG) paths.
- check_key: valid pass; corrupted public copy, public-only and
private-only keys all fail (PUBLIC_KEY_E); NULL rejected.
- der: KeyToDer / PrivateKeyToDer / PublicKeyToDer round-trips via
PrivateKeyDecode / PublicKeyDecode, size-query (NULL output),
and the SetAsymKeyDer too-small contract (BAD_FUNC_ARG).
- error_paths: exhaustive NULL / bad-level / wrong-size / no-key-set
argument sanitising for every public entry point.
Tests requiring key generation or signing are gated on
WC_FALCON_HAVE_NATIVE_SIGN so the file also builds in
WOLFSSL_FALCON_VERIFY_ONLY and WOLF_CRYPTO_CB_ONLY_FALCON configurations;
size and argument-sanitising tests run in every HAVE_FALCON build.
Verified: 7/7 pass under both --enable-falcon-avx2 and the default
constant-time build; compiles clean with WOLFSSL_FALCON_VERIFY_ONLY.
The new tests/api/test_falcon.c was wired into the automake unit test (tests/api/include.am) but not the CMake one, so CMake-based CI builds failed to link the unit test with undefined references to test_wc_falcon_*. Add it to the CMake unit_test source list alongside the other tests/api/test_*.c entries.
- wc_falcon.c: replace the lazily-initialised, mutable NTT twiddle-table cache with precomputed read-only const tables (falcon_zetas/izetas_l1/l5). This removes the data race on the shared cache (a reader could see the init flag set before all table entries were visible) and also drops the now-unused falcon_brv / falcon_build_tables helpers. Verify stays fast (const reads, no per-call rebuild); KAT confirms the values. - falcon.c: correct the wc_falcon_sign_msg doc comment (signing needs the *private* key; required buffer size is the active level's signature size, not always FALCON_LEVEL1_SIG_SIZE) and validate rng != NULL in the software path so the error is reported at the API boundary. - wc_falcon_sign.c / wc_falcon_sign.h: route key->heap into falcon_complete_private and falcon_expand_privkey (their large fpr scratch allocations previously used a NULL heap hint, bypassing custom heap routing); and fail fast in falcon_do_sign_tree on the sampler's sticky PRNG error (passed in as samplerErr) instead of running to the restart bound. - wc_falcon_codec.h: use the project-standard WOLF_CRYPT_WC_FALCON_CODEC_H include guard instead of the collision-prone generic FALCON_CODEC_H. - doc/dox_comments (ssl.h + -ja): drop the stale "or HAVE_LIBOQS" from the ML-KEM hybrid group requirements; liboqs is no longer supported.
- falcon.c (wc_falcon_import_private_only): call falcon_store_pub_behind_priv unconditionally after setting prvKeySet. The raw-size branch previously only synced the behind-private public copy in the concat layout, so importing a public key first and then a raw private key left key->k + KEY_SIZE zero and made wc_falcon_check_key return a false PUBLIC_KEY_E. Added a regression case to test_wc_falcon_check_key covering the public-then-raw-private ordering. - wc_falcon.c (native sign cleanup): free the sampler's SHAKE256 context with wc_Shake256_Free(&spc.p.shake) when it was initialized, before ForceZero. Without it, WOLFSSL_ASYNC_CRYPT + WC_ASYNC_ENABLE_SHA3 builds leaked the async device context allocated by wc_InitShake256 on every sign, unlike the keygen and hash-to-point paths which already free their SHAKE contexts.
configure.ac sets enable_option_checking=fatal, so removing the --with-liboqs option turned it into a hard "unrecognized options" error. Test matrices / CI (e.g. the Jenkins PRB liboqs config branch) and user scripts that still pass --with-liboqs then fail at the configuration phase before any build runs. Re-add --with-liboqs as a recognised, deprecated no-op that warns it has no effect and points at --enable-falcon. It links no liboqs and does not change the build (SBOM unaffected); it only prevents the fatal unknown-option abort. Mirrors the existing --enable-cryptodev deprecation shim.
The check-source-text unknown-macro check (subtest K) flags preprocessor symbols used in the sources that are neither #defined in a header nor listed in .wolfssl_known_macro_extras. The native Falcon backends add several configure/-D build gates that are not #defined anywhere, so a build that scans the Falcon sources without those options set (e.g. --enable-all without the sub-backends) reported them as unknown. Register the nine new gates (LC_ALL=C sorted): WOLF_CRYPTO_CB_ONLY_FALCON WOLFSSL_FALCON_FFT_AVX2, WOLFSSL_FALCON_FFT_NEON WOLFSSL_FALCON_FPR_ASM, WOLFSSL_FALCON_FPR_DOUBLE WOLFSSL_FALCON_NTT_DSP, WOLFSSL_FALCON_NO_NTT_DSP WOLFSSL_FALCON_SIGN_STATS, WOLFSSL_FALCON_VERIFY_ONLY HAVE_FALCON and WC_FALCON_HAVE_NATIVE_SIGN are already known (configure / falcon.h), so they are not added.
The sign retry loop capped the compressed signature at the caller-supplied *outLen instead of the level's fixed size, so a buffer larger than the max let an over-length (e.g. 667-byte Falcon-512) signature escape without a restart, and verification then rejected it. Cap at sigMax instead.
tests/falcon/ hammers make_key -> sign -> verify across many keys and messages to catch intermittent non-verifying signatures, dumping a replayable artifact on any mismatch. Includes Makefile and run.sh.
The keygen/sign/verify fuzzer now lives in the testing repo (fuzzers/falcon); it is not part of the library or its CI. Removing it here also drops tests/falcon/run.sh from the PR's changed-shell set.
On 32-bit ARM targets lacking a double-precision FPU (Cortex-M0/M3/M4/ M23/M33), C double maps onto slow libgcc soft-double, making the opt-in WOLFSSL_FALCON_FPR_DOUBLE backend a pessimization versus the default integer fpr backend. Emit a #warning to steer users away. Detect via __ARM_FP bit 3; AArch64 does not define __arm__ so it is unaffected.
…s, footprint)
Four fixes from PR review:
- zephyr/CMakeLists.txt: the native port split falcon.c into wc_falcon_*.c
translation units; add the portable sources so a Zephyr build with Falcon
links. x86-64 asm/AVX2 and the NEON backend are left out (not selected by any
Zephyr config).
- configure.ac: fold the standalone --enable-falcon-{asm,double,avx2,neon}
switches into comma-separated sub-options of --enable-falcon
(e.g. --enable-falcon=avx2), matching the common wolfSSL idiom. avx2/neon
imply the double backend after arch-gating so ignoring an unsupported vector
backend does not clobber an explicit 'double'. Sweep the qemu-falcon-neon doc
to the new spelling.
- configure.ac: align the Falcon line in the two feature summaries.
- falcon.c/falcon.h: drop the duplicate public-key copy kept behind the private
key. Its only remaining reader was wc_falcon_check_key, whose compare was
against a copy of the same bytes and so could never detect a real mismatch;
wc_falcon_export_private already rebuilds the concat layout on demand. Shrink
key->k from FALCON_MAX_PRV_KEY_SIZE to FALCON_MAX_KEY_SIZE (saves 1793 bytes
per key at level 5). check_key now verifies both halves are present and
documents a full cryptographic cross-check as a follow-up. Update the unit
test that relied on the old in-memory-copy compare.
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.
465db5c to
3876465
Compare
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.
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):wc_falcon_*/falcon_key(single surface; the redundantwc_FnDsa_*layer is gone). Verify uses an integer NTT mod q=12289 withcached twiddle tables; signing uses the FFT / ffLDL tree + discrete Gaussian
sampler; keygen uses the RNS/big-integer NTRU solver.
fprseam with selectable backends:--enable-falcon-double— inline nativedouble(fastest with aconstant-time HW FPU);
--enable-falcon-asm— x86-64 SSE2 out-of-linefprbackend(generated; see companion wolfssl-scripts PR);
--enable-falcon-avx2— x86-64 AVX2 (4-wide) FFT.Cortex-M / embedded targets.
Remove liboqs: Falcon was the last liboqs consumer. Drops
--with-liboqs(and
-loqs), thewolfcrypt/src/port/liboqslayer, the CMakeWOLFSSL_OQSoption /
FindOQS.cmake, and the VS/Zephyr/INTIME/Docker references. Thelibrary links no liboqs.
HAVE_FALCONis added to the experimental-settingsgate so the requirement holds across every build system.
Crypto callbacks (
WOLF_CRYPTO_CB): keygen/sign/verify dispatch towc_CryptoCb_MakePqcSignatureKey/PqcSign/PqcVerify(
WC_PQC_SIG_TYPE_FALCON), with software fall-through. AddsWOLF_CRYPTO_CB_ONLY_FALCON(first PQC signature to have it, mirroring_RSA/_ECC): the callback becomes authoritative and the native core iscompiled out.
ARM acceleration (portable-C + intrinsics, no hand-asm):
__ARM_FEATURE_DSP(Cortex-M4/M7/M33). Packed 2-lane butterflies (
SMLABB/SMLATB+SADD16/SSUB16+USUB16/SEL) and anSMUADsquared-norm; bit-identicalto the scalar path.
--enable-falcon-neon, the 2-widefloat64x2_t+ FMAcounterpart of the AVX2 FFT for the signing/keygen path.
Testing
wolfcrypt/testfalcon_test: KAT verify + native keygen/sign/verifyround-trip; the crypto-callback test drives every Falcon op through the
callback and asserts the cb path was taken (incl. a CB_ONLY check).
keygen+sign vs verify).
equation
f·G − g·F = qon every keygen,h·f ≡ g (mod q), the norm bound,sign→native+liboqs verify, and forgery rejection — zero failures observed with this set.
IDE/):m33mu-falcon-verifyruns the DSPNTT on an STM32H563/Cortex-M33 (m33mu) — genuine sig accepted, tampered
rejected;
qemu-falcon-neonruns a full keygen→sign→verify at levels 1 and 5on qemu-system-aarch64 with the NEON FFT.
testwolfcrypt+ full testsuite) across default / cryptocb /CB_ONLY / verify-only and each fpr backend.
Build options
--enable-falconnative Falcon (requires --enable-experimental)--enable-falcon-doubleinline native-double fpr backend--enable-falcon-asmx86-64 SSE2 fpr asm backend--enable-falcon-avx2x86-64 AVX2 FFT--enable-falcon-neonAArch64 NEON FFTChecklist