Skip to content

crypto: add C906 ChaCha20 primitive to OpenSSL - #506

Merged
dormancygrace merged 12 commits into
mainfrom
codex/openssl-chacha20
Oct 27, 2026
Merged

dormancygrace merged 12 commits into
mainfrom
codex/openssl-chacha20

Conversation

@dormancygrace

@dormancygrace dormancygrace commented Oct 26, 2026 •

Copy link
Copy Markdown
Owner

Add the shared C906 five-block ChaCha20 kernel to OpenSSL 3.5.9's existing primitive dispatch. The default provider retains AEAD, Poly1305, TLS and counter-limit handling. Other algorithms remain separate patches/PRs.

Calls of at least 640 bytes can use the kernel only with explicit NANOKVM_OPENSSL_CHACHA20=auto, startup hwprobe confirmation of XTheadVector across online CPUs, and vector permission for the calling thread. The download service sets this opt-in for its curl child and preserves explicit operator overrides. Default/unknown/generic settings stay on upstream software without a per-cipher prctl. Direct libc secure_getenv avoids musl's lazy UID/GID syscalls inside OpenSSH preauth seccomp. No cipher call reads cpuinfo or invokes hwprobe; denied/unknown hardware or thread gates fall back.

Final physical C906 results (six rotated trials, current policy build):

  • 16 KiB AEAD opening: 48.14 vs 29.27 MiB per CPU-second for the Build OpenSSL 3.5 LTS with C906 -O3 flags #504 baseline.
  • Real 16 MiB HTTPS range download with production Go SHA-256 and SD output: median curl CPU 1.252 vs 1.478 s, 15.3% less. CA, HTTP status, length and independent SHA-256 checks pass.
  • Median peak RSS 8204 vs 8228 KiB, with overlapping ranges; anonymous memory is essentially identical. No RAM reduction is claimed.
  • Small-input full-build results do not show a gain. The 640-byte minimum is conservative: a same-layout 320-byte variant loses in full AEAD despite winning in the raw primitive. The precise remaining caller-context cause is unresolved.

Validation: 1680 native differential cases and 408 AEAD known-answer/rejected-tag cases; actual assembly entry counters (instrumented build only) across threshold, generic and vector-off/on threads; first-cipher seccomp tests killing file/probe/UID/GID calls and default-path prctl; six actual OpenSSH preauth cipher/request-size combinations; host ASan/UBSan dispatcher tests with three negative controls; Go download race tests and vet. Production retains 5808 dynamic definitions. Assembly regeneration and encoding checks pass.

Raw component, fixed-layout, syscall-overhead, HTTPS and memory data plus source/binary identities are in firmware/crypto/experimental/openssl-chacha20/. The per-call prctl costs about 0.61 us in the isolated native measurement. Kernel Poly1305 alternatives were evaluated but are not included. Current main is integrated; the already-merged network fixture fix is absent from this diff.

@github-actions

github-actions Bot commented Oct 26, 2026 •

Copy link
Copy Markdown

Build outputs

Compared with the build of main at 6c78048.

4 changed, 1 added, 0 removed of 595 outputs.

directory files
changed server server/NanoKVM-Server
changed tools tools/openssl/usr/lib/libcrypto.so.3
tools/openssl/usr/lib/ossl-modules/legacy.so
tools/openssl/usr/share/nanokvm/openssl/source.json
added tools tools/openssl/usr/share/licenses/nanokvm-openssl/C906-BSD-3-Clause.txt
Hashes: outputs-sha256 artifact of this run.

@dormancygrace dormancygrace left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

I read the generated assembly against the generator, and it is consistent:

  • the a0–a4 ABI and the callee-saved registers;
  • the stack layout;
  • the counter handling (addiw, sw 48(a0));
  • byte-wise th.vle/th.vse, so alignment and in-place calls are fine.

The provider's split at the 32-bit counter wrap matches the tail call. verify.c passes its 1680 cases on x86.

The hardware qualification is pending as stated. My comments are about making that qualification conclusive.

Comment thread firmware/crypto/experimental/openssl-chacha20/verify.c
Comment thread firmware/crypto/openssl-c906/0001-chacha20-c906.patch Outdated
Comment thread firmware/crypto/openssl-c906/0001-chacha20-c906.patch

@dormancygrace dormancygrace left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Follow-up at the current head.

  • The generated assembly still checks out: s0–s11 are saved in a 416-byte frame, and the keystream is wiped.
  • Patches 0001–0004 apply to 3.5.9 with zero fuzz, and the host tests pass.
  • The length check before the helper is a good change.

The earlier comments on verify.c and on the /proc/cpuinfo parse still apply. I added a concrete option to each thread.

Comment thread firmware/crypto/openssl-c906/0001-chacha20-c906.patch Outdated
Comment thread firmware/crypto/experimental/openssl-chacha20/README.md Outdated
Comment thread server/service/network/trial_test.go
@dormancygrace

Copy link
Copy Markdown
Owner Author

Poly1305 is now the larger half of this AEAD; OpenSSL has no RISC-V assembly for it

With the C906 ChaCha20 in place, the remaining ChaCha20-Poly1305 time in OpenSSL is mostly Poly1305. OpenSSL 3.5.9 has no POLY1305ASM for riscv64 (crypto/poly1305/build.info lists x86, x86_64, ia64, sparcv9, mips64, s390x, armv4, aarch64 and ppc), so this build uses the generic C in poly1305.c.

Rough comparison on the C906 at 1 GHz. The three numbers come from different harnesses, so treat them as an order-of-magnitude comparison:

Implementation Poly1305 throughput Source
Kernel: CRYPTOGAMS lib/crypto/riscv/poly1305-riscv.pl (CRYPTO_LIB_POLY1305_ARCH=y) 211–249 MB/s firmware/cpu/go-crypto-c906/README.md, in-kernel module
Go with #489 (ten MUL/MULHU, C906 scheduling) ~192 MB/s at 16 KiB #489 medians.csv, with video active
OpenSSL generic C, this stack not measured on its own; about 100–140 MB/s by my estimate #506's 16 KiB AEAD figure (47.35 MiB/CPU-s) minus ChaCha time at the kernel ChaCha kernel's 81–93 MB/s

The kernel file is Andy Polyakov's CRYPTOGAMS perlasm. Its header says it was "initially for use with OpenSSL", and the license is GPL-1.0+ OR BSD-3-Clause. Its own notes quote more than 100% over compiler-generated code on U74 and about 75% on C910. That suggests bringing it into this stack as a separate patch next to 0001:

  1. Patch. Add crypto/poly1305/asm/poly1305-riscv.pl from the kernel tree and set $POLY1305ASM_riscv64=poly1305-riscv.S plus the POLY1305_ASM define in crypto/poly1305/build.info. Check the glue in poly1305.c (poly1305_init and poly1305_blocks/poly1305_emit symbol names, the func[2] table) against what the perlasm emits. The perlasm takes the flavour argument the same way as OpenSSL's own riscv64 scripts. CHERI paths can stay disabled.
  2. Correctness. Run upstream test/evp_test with the Poly1305 and ChaCha20-Poly1305 vectors. Extend verify.c with Poly1305 alone over lengths 0–1100 and offsets 0–15, compared against host OpenSSL. As with ChaCha, a test-only counter should prove the assembly path ran.
  3. Measurement. Add Poly1305-only and ChaCha20-only rows to bench.c (1400 B and 16 KiB, at least five rotated trials, jsonl), so the AEAD time can be split. If the table above holds, ChaCha20-Poly1305 at 16 KiB should rise from about 47 to about 60 MiB/CPU-s; that is an estimate to confirm or reject. That is a bigger effect on HTTPS (curl, apk) than any of the SHA-1/DES DMA patches.
  4. Follow-up, separate. Measure the kernel CRYPTOGAMS code and the perf: optimize C906 Go Poly1305 #489 Go arithmetic in one harness, on the same message and with the same background load. Whichever wins can then be shared across all three stacks (kernel, Go, OpenSSL) instead of keeping three different Poly1305 cores.

@dormancygrace

Copy link
Copy Markdown
Owner Author

The CRYPTOGAMS suggestion is distinct from the earlier Go-assembly experiment, so the earlier result does not reject it. I have built both the kernel perlasm's 64-bit path and its 32-bit arithmetic path for RV64 against the same OpenSSL profile. The existing poly1305_init / blocks / emit ABI fits OpenSSL and the opaque context does not grow. Independent host reference generation covers 17,616 length/alignment cases; the verifier adds split updates, duplication with the original freed, output guards and protected page boundaries. Native qualification is still pending.

There is a hardware-specific detail to include in the comparison: the C906 multiplier RTL chooses the short or iterative path from the operands' upper 32 bits. Thus wide mul/mulhu timing cannot simply be assumed data-independent. The perlasm's RV64-compatible 32-bit path is also being evaluated; no new throughput or constant-time claim is made yet.

For the ChaCha review, f5c23c6 uses the existing startup hwprobe, defines the 640-byte threshold once, adds a test-only counter at the actual assembly entry, and supplies component/overhead and seccomp tests. The production library still has 5808 dynamic definitions. Host dispatch sanitizer tests and negative controls pass; expanded native tests remain pending. The stack now includes current main, so the duplicate network fixture change is gone from #506.

Base automatically changed from codex/openssl-c906 to main October 27, 2026 01:29
@dormancygrace
dormancygrace marked this pull request as ready for review October 27, 2026 09:30

@dormancygrace dormancygrace left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Re-reviewed at c2d5592. No blocking issues.

  • Detection: hwprobe key 11 (RISCV_HWPROBE_KEY_VENDOR_EXT_THEAD_0), bit 0 (XTheadVector), matches the v7.3 uapi. With cpus=NULL it is the all-online-CPU intersection, and an unknown key comes back as -1, which leaves the path off. The flag is tested before the once/env check, so other CPUs pay one load.
  • Opt-in: secure_getenv("NANOKVM_OPENSSL_CHACHA20") == "auto", generic otherwise. imageCurlEnv() keeps explicit operator values, and curl_env_test.go covers unset/generic/unknown/empty/auto. The downloader already pins ChaCha on its first attempt (curlImageArgs(..., true)), so the path really runs: about 15–17% less curl CPU in native-https-results.jsonl.
  • The entry counter in the assembly exists only under NKOS_CHACHA_TEST.
  • 640-byte thread: accepted, see the reply there.

One stack-wide observation is in the #509 review: the patched libraries are slower than the base at small sizes even with every new path off. The 64-byte ChaCha row here (6.39 vs 6.84) fits the same pattern.

@dormancygrace dormancygrace left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Reviewed the final head f5f992db168f0995fce20af11f2ab1da18025709. No new blocking findings.

Rechecked the generated assembly, 32-bit counter-wrap handling, 416-byte aligned frame, saved registers and keystream wipe. Startup hwprobe uses the all-online-CPU intersection; unknown capability, missing opt-in and disabled vector threads retain the upstream path. The downloader preserves explicit operator overrides. The earlier native qualification and timing remain attributed to their recorded builds.

Generator/encoding checks, real dispatch sanitizer tests and all three gate-removal negative controls pass, as do the downloader race tests. Integration with current main preserves both log-vector and Keccak CI checks without changing the ChaCha patch. Existing resolved findings remain addressed.

All 19 final-head CI checks pass, including the firmware build.

@dormancygrace
dormancygrace merged commit 0cf9c70 into main Oct 27, 2026
19 checks passed
@dormancygrace
dormancygrace deleted the codex/openssl-chacha20 branch October 27, 2026 14:08
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.

1 participant