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 38 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.
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.
Frauschi
left a comment
There was a problem hiding this comment.
🐺 Skoll Code Review
Overall recommendation: APPROVE
Findings: 5 total — 4 posted, 1 skipped
Posted findings
- [Low] Stale "Phase 1: verification only" file header comment —
wolfcrypt/src/wc_falcon.c:22-26 - [Low] wc_falcon_verify_msg doc comment references non-existent contextLen —
wolfcrypt/src/falcon.c:160-162 - [Medium] Sign compression-fit retry silently caps at 32 attempts —
wolfcrypt/src/wc_falcon.c:824-855 - [Medium] No unit test exercises the concat(priv,pub) legacy import size path directly —
wolfcrypt/src/falcon.c:448-484
Skipped findings
- [Low] Bare-scope { } blocks used to introduce mid-function declarations
Review generated by Skoll via Claude/Codex
- 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.
|
🟡 [Medium] Alternate fpr/FFT backends (asm, double, avx2, neon) not exercised by CI The added CI job builds only 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:
The Suggested matrix (respecting the configure constraints —
One caveat: since backend selection is compile-time with no CPUID guard, an |
I'll get there in the mc-dc coverage campaign |
Frauschi
left a comment
There was a problem hiding this comment.
LGTM, over to @SparkiDev.
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
left a comment
There was a problem hiding this comment.
🐺 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-option —
configure.ac:1840 - [Info] Native check_key uses hardware modulo despite division-free NTT elsewhere —
wolfcrypt/src/falcon.c:8221-8227
Skipped findings
- [Low] Pervasive bare-scope
{ ... }blocks in new Falcon C code
Review generated by Skoll via Claude/Codex
…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.
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