constant_time: fix the -Os inlining loss, and add an Xtensa assembly path #873
Open
sweetlilmre wants to merge 7 commits from
sweetlilmre/constant-time-embedded-perf into development
pull from: sweetlilmre/constant-time-embedded-perf
merge into: gh-Mbed-TLS:development
gh-Mbed-TLS:development
gh-Mbed-TLS:tf-psa-crypto-1.1
gh-Mbed-TLS:archive/20261005T153337Z-force/pull/927
gh-Mbed-TLS:archive/20261001T141055Z-force/pull/927
gh-Mbed-TLS:archive/20261005T153337Z-force/pull/931
gh-Mbed-TLS:archive/20261001T083616Z-force/pull/927
gh-Mbed-TLS:archive/20260930T232327Z-force/pull/927
gh-Mbed-TLS:archive/20260930T154618Z-force/pull/927
gh-Mbed-TLS:archive/20260929T231919Z-rescue/rohankl-arm/mldsa-pubkey-899
gh-Mbed-TLS:archive/20260929T231919Z-rescue/sigvartmh/pr/spake2p-9342-public-key-import-export
gh-Mbed-TLS:archive/20260929T231919Z-rescue/ManoharPaturi/fix-pem-ct-padding
gh-Mbed-TLS:archive/20260929T231919Z-rescue/ManoharPaturi/fix-asn1write-null-guard
gh-Mbed-TLS:archive/20260929T231919Z-rescue/ManoharPaturi/fix-pbes2-params-validation
gh-Mbed-TLS:archive/20260929T231919Z-rescue/valeriosetti/blake2
gh-Mbed-TLS:archive/20260929T231919Z-rescue/jfdev001/v1.1.1_bump-cmake-minimum-version
gh-Mbed-TLS:archive/20260929T231919Z-rescue/jfdev001/bump-cmake-minimum-version
gh-Mbed-TLS:archive/20260929T231919Z-rescue/ronald-cron-arm/sm3-base
gh-Mbed-TLS:archive/20260929T231919Z-rescue/valeriosetti/blake2-hmac
gh-Mbed-TLS:archive/20260929T231919Z-rescue/v1rtu3x/enforce-hash-length-sign-verify
gh-Mbed-TLS:archive/20260929T231919Z-rescue/bjwtaylor/file-backed-keystore-doc
gh-Mbed-TLS:archive/20260929T231919Z-rescue/sigvartmh/pr/spake2p-9344-set-context
gh-Mbed-TLS:archive/20260929T231919Z-rescue/sigvartmh/pr/spake2p-9343-key-pair-import-export-depends-on-9342
gh-Mbed-TLS:archive/20260929T231919Z-rescue/romart/development
gh-Mbed-TLS:archive/20260929T231919Z-rescue/sweetlilmre/constant-time-embedded-perf
gh-Mbed-TLS:archive/20260929T231919Z-rescue/JulioLoayzaM/add-wycheproof-rsa-pss
gh-Mbed-TLS:archive/20260929T231919Z-rescue/JulioLoayzaM/add-wycheproof-ecdsa
gh-Mbed-TLS:archive/20260929T231919Z-rescue/JulioLoayzaM/add-wycheproof-tests
gh-Mbed-TLS:archive/20260929T231919Z-rescue/oubaid-a/dev_psa_driver_wrapper
gh-Mbed-TLS:archive/20260929T231919Z-rescue/gilles-peskine-arm/keystoreless-prototype-1
gh-Mbed-TLS:archive/20260929T231919Z-rescue/valeriosetti/issue9580
gh-Mbed-TLS:archive/20260929T231919Z-rescue/LeonieReichertABB/hpke-functionality
gh-Mbed-TLS:archive/20260929T231919Z-rescue/gilles-peskine-arm/psa-hash-no-buffer-copy-1.1
gh-Mbed-TLS:archive/20260929T231919Z-rescue/vanhauser-thc/development
gh-Mbed-TLS:archive/20260929T231919Z-rescue/tpambor/key-wrap
gh-Mbed-TLS:archive/20260929T231919Z-rescue/ruiliio/psa_xts
gh-Mbed-TLS:archive/20261011T063515Z-force/pull/538
gh-Mbed-TLS:archive/20260929T231919Z-rescue/gilles-peskine-arm/fix-broken-links-1.1
gh-Mbed-TLS:archive/20260929T231919Z-rescue/valeriosetti/issue669
gh-Mbed-TLS:archive/20260929T231919Z-rescue/gilles-peskine-arm/prepare-release-test
gh-Mbed-TLS:archive/20260929T231919Z-rescue/krish2718/wpa3-sae-design-note
gh-Mbed-TLS:archive/20260929T231919Z-rescue/nzzlinh/fix_iar_endianness_1.1
gh-Mbed-TLS:archive/20260929T231919Z-rescue/nzzlinh/fix-iar-endianness
gh-Mbed-TLS:archive/20260929T231919Z-rescue/abtom87/fix-pbkdf2-iter-count
gh-Mbed-TLS:archive/20260929T231919Z-rescue/minosgalanakis/tools/prepare_release_lite
gh-Mbed-TLS:archive/20260929T231919Z-rescue/Harshal5/fix/aes-xts-in-place-cts
gh-Mbed-TLS:archive/20260929T231919Z-rescue/sigvartmh/pr/spake2p-9347-prover-depends-on-9342-9343-9344
gh-Mbed-TLS:archive/20260929T231919Z-rescue/crabel99/async-hardware-support-c35df962
gh-Mbed-TLS:tf-psa-crypto-1.2.0_mergeback
gh-Mbed-TLS:tf-psa-crypto-1.1.1_mergeback
gh-Mbed-TLS:archive/20260929T231919Z-rescue/asmellby/feature/dispatch-library
gh-Mbed-TLS:tf-psa-crypto-release-sync
gh-Mbed-TLS:archive/20260929T231919Z-rescue/ChakshuGupta13/fix/ecdsa-zeroize-stack-key
gh-Mbed-TLS:archive/20260929T231919Z-rescue/gilles-peskine-arm/security-md-option-names
gh-Mbed-TLS:archive/20260929T231919Z-rescue/parmi93/patch-2
gh-Mbed-TLS:archive/20260929T231919Z-rescue/bjwtaylor/remove-mldsa-sign-from-build
gh-Mbed-TLS:archive/20260929T231919Z-rescue/bjwtaylor/tf-m-build-issues
gh-Mbed-TLS:archive/20260929T231919Z-rescue/bjwtaylor/test-context-population
gh-Mbed-TLS:archive/20260929T231919Z-rescue/gilles-peskine-arm/iar-1.1.0
gh-Mbed-TLS:archive/20260929T231919Z-rescue/gilles-peskine-arm/fork-helper-unit-tests-create-crypto
gh-Mbed-TLS:archive/20260929T231919Z-rescue/bjwtaylor/redundant-declarations
gh-Mbed-TLS:archive/20260929T231919Z-rescue/hasheddan/docs/fix-broken-links
gh-Mbed-TLS:main
gh-Mbed-TLS:archive/20260929T231919Z-rescue/bjwtaylor/reintroduce-dh
gh-Mbed-TLS:archive/20260929T231919Z-rescue/bjwtaylor/cipher-update-incorrect-error
gh-Mbed-TLS:archive/20260929T231919Z-rescue/Marandil/fix/mbedtls-4314-parse-explicit-curve
gh-Mbed-TLS:archive/20260929T231919Z-rescue/schneider-electric/dev/plaj-se/parse-rsassa-pss
gh-Mbed-TLS:archive/20260929T231919Z-rescue/minosgalanakis/bugfix/program-fuzz-mscv
gh-Mbed-TLS:archive/20260929T231919Z-rescue/daverodgman/aesgcm
gh-Mbed-TLS:archive/20260929T231919Z-rescue/gilles-peskine-arm/sha3-coverity-20260224
gh-Mbed-TLS:archive/20260929T231919Z-rescue/bensze01/undef
gh-Mbed-TLS:archive/20260929T231919Z-rescue/valeriosetti/issue523
gh-Mbed-TLS:archive/20260929T231919Z-rescue/dannytsen/ppc-dev
gh-Mbed-TLS:archive/20260929T231919Z-rescue/daverodgman/aes12
gh-Mbed-TLS:archive/20260929T231919Z-rescue/gilles-peskine-arm/ascon-prototype
gh-Mbed-TLS:archive/20260929T231919Z-rescue/daverodgman/chacha10
gh-Mbed-TLS:archive/20260929T231919Z-rescue/kusumitg/implement_psa_pake_set_context_for_spake2p
gh-Mbed-TLS:archive/20260929T231919Z-rescue/davidhorstmann-arm/add-code-size-target
gh-Mbed-TLS:archive/20260929T231919Z-rescue/bjwtaylor/simplify-sign_func
gh-Mbed-TLS:archive/20260929T231919Z-rescue/mfil/feature/aes_ni_parallel
gh-Mbed-TLS:archive/20260929T231919Z-rescue/ivv19041994/patch-1
gh-Mbed-TLS:archive/20260929T231919Z-rescue/gilles-peskine-arm/psa-verify-policy-relax
gh-Mbed-TLS:public_2/main
gh-Mbed-TLS:archive/20260929T231919Z-rescue/mpg/pk-sign-proto
gh-Mbed-TLS:release/tf-psa-crypto1.0.0-documentation
gh-Mbed-TLS:archive/20260929T231919Z-rescue/gilles-peskine-arm/0e-plans-july-updates
gh-Mbed-TLS:archive/20260929T231919Z-rescue/davidhorstmann-arm/guard-context-types-in-client-only
gh-Mbed-TLS:archive/20260929T231919Z-rescue/gilles-peskine-arm/psa-driver-interface-spec-remove
gh-Mbed-TLS:archive/20260929T231919Z-rescue/gilles-peskine-arm/ecp-self-test-race-crypto
gh-Mbed-TLS:archive/20260929T231919Z-rescue/KiruthikPrakash/feature/9342_add-support-for-spake2p-public-key-import-export
gh-Mbed-TLS:archive/20260929T231919Z-rescue/ruiliio/chacha20_16
gh-Mbed-TLS:archive/20260929T231919Z-rescue/irwir/winsock2
gh-Mbed-TLS:archive/20260929T231919Z-rescue/aphroteus/development
gh-Mbed-TLS:archive/20260929T231919Z-rescue/amjoul01/amjoul01/psa-mac-out-of-bound-fix
gh-Mbed-TLS:archive/20260929T231919Z-rescue/gilles-peskine-arm/everest-submodule-crypto
gh-Mbed-TLS:archive/20260929T231919Z-rescue/yamt/xtensa-ccount
gh-Mbed-TLS:archive/20260929T231919Z-rescue/gilles-peskine-arm/all.sh-generate-only-before-checks-crypto
gh-Mbed-TLS:archive/20260929T231919Z-rescue/Harshal5/feat/builtin_cmac_driver_use_psa_crypto_interface
gh-Mbed-TLS:archive/20260929T231919Z-rescue/gilles-peskine-arm/test-data_t-empty_is_empty-crypto
gh-Mbed-TLS:archive/20260929T231919Z-rescue/jean-baptisteboric-eaton/oids-pkcs7-authenticated-attributes
gh-Mbed-TLS:dev/bensze01/public-only-commit
gh-Mbed-TLS:archive/20260929T231919Z-rescue/waleed-elmelegy-arm/psa-key-wrap-aes-kw
gh-Mbed-TLS:archive/20260929T231919Z-rescue/tfloch/psa_add_xts_algorithm
gh-Mbed-TLS:archive/20260929T231919Z-rescue/gilles-peskine-arm/msancf-clang16-crypto
gh-Mbed-TLS:archive/20260929T231919Z-rescue/DemiMarie/document-unexpected-tags
gh-Mbed-TLS:archive/20260929T231919Z-rescue/DemiMarie/strict-len
gh-Mbed-TLS:archive/20260929T231919Z-rescue/valeriosetti/issue9255-tfpsacrypto
gh-Mbed-TLS:archive/20260929T231919Z-rescue/daantimmer/use-compiler-frontend
gh-Mbed-TLS:archive/20260929T231919Z-rescue/benmcollins/enable-rsassa-pss-pkparse
gh-Mbed-TLS:dev/paul-elliott-arm/add_iop_key_agreement_negative_tests
gh-Mbed-TLS:add-iop-export-pub-key-get-num-ops
gh-Mbed-TLS:dev/rc/new-2-d635ea1
gh-Mbed-TLS:dev/bensze01/jenkins
No reviewers
Labels
Clear labels
api-break
This issue/PR breaks the API and must wait for a new major version
approved
Design and code approved - may be waiting for CI or backports
bug
Something isn't working
component-docs
Docs / website issues filed here for tracking
component-platform
Portability layer and build scripts
component-test
Test framework and CI scripts
dependencies
Pull requests that update a dependency file
deprecation
needs deprecation announcement
DO-NOT-MERGE
enhancement
New feature or request
good first issue
Good for newcomers
help wanted
This issue is not being actively worked on, but PRs welcome.
needs-adoption
stalled PR that someone should pick up and complete
needs-backports
Backports are missing or are pending review and approval.
needs-ci
Needs to pass CI tests
needs-design-approval
Needs design discussion / approval
needs-info
An issue or PR which needs further info from the reporter / author
needs-preceding-pr
Requires another PR to be merged first
needs-review
Every commit must be reviewed by at least two team members
needs-reviewer
This PR needs someone to pick it up for review
needs-work
priority-high
High priority - will be reviewed soon
priority-low
Low priority - this may not receive review soon
priority-medium
Medium priority - this can be reviewed as time permits
priority-scheduled
This PR is big - it will require time to be scheduled for review
priority-very-high
Highest priority - prioritise this over other review work
resolved-after-1.1
The issue is resolved in development, but still present in 1.1 LTS.
single-reviewer
This PR qualifies for having only one reviewer
size-l
Estimated task size: large (2w+)
size-m
Estimated task size: medium (~1w)
size-optimisation
size-s
Estimated task size: small (~2d)
size-xs
Estimated task size: extra small (a few hours at most)
No labels
api-break
approved
bug
component-docs
component-platform
component-test
dependencies
deprecation
DO-NOT-MERGE
enhancement
good first issue
help wanted
needs-adoption
needs-backports
needs-ci
needs-design-approval
needs-info
needs-preceding-pr
needs-review
needs-reviewer
needs-work
priority-high
priority-low
priority-medium
priority-scheduled
priority-very-high
resolved-after-1.1
single-reviewer
size-l
size-m
size-optimisation
size-s
size-xs
Milestone
Clear milestone
No items
No milestone
Assignees
Clear assignees
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".
No due date set.
Dependencies
No dependencies set
Reference
gh-Mbed-TLS/TF-PSA-Crypto!873
Loading…
Reference in a new issue
No description provided.
Delete branch "sweetlilmre/constant-time-embedded-perf"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Intro
While building a firmware for an esp32 device and migrating from Arduino / IDF to pure IDF I noticed a failure during TLS negotiation. This was due to the TLS handshake taking longer than the watchdog timeout and causing a reboot during mid fetch. In investigating this behaviour, I found a regression in performance due to constant time hardening.
AI was used in both the development and analysis of this patch, specifically Claude Opus 5 on Medium level thinking. The patch was adversarially reviewed by myself through several iterations, constant-time testing was done exposing a coverage gap in the current code and purpose test harnesses built to validate the approach.
Summary
Two independent problems make the constant-time primitives expensive on embedded targets, both on the bignum hot path since
mbedtls_mpi_core_sub()andmbedtls_mpi_core_mla()were made constant time (mbedtls4b4869a15, TF-PSA-Crypto77bd4798). On an ESP32 this costs 15-41% per bignum primitive against mbedTLS 3.6.6, at identical call counts.-Osthese functions are not inlined, so every use pays a call. This affects targets that already have an assembly path, including Arm.mbedtls_ct_compiler_opaque()barriers.The third commit is an unrelated small cleanup at two call sites, separated deliberately so it can be dropped without affecting the rest.
Why
-Osmatters herembedtls_mpi_core_sub()callsmbedtls_ct_uint_lt()twice per limb. Whether that call survives:-Os-O2arm-none-eabi-gcc -mthumb -mcpu=cortex-m4arm-none-eabi-gcc -marm -mcpu=arm7tdmixtensa-esp-elf-gccx86-64 inlines regardless, and does so for the generic C body too, so the difference is the target rather than the code. That is presumably why this has gone unnoticed. Arm Cortex-M
-Osbuilds pay it today.MBEDTLS_CT_INLINEis applied only where an assembly path is in use. Cost on Arm thumb-Os, whole translation unit: +8 bytes of.text. Applying it to the generic C fallback instead costs +396 bytes, which is a size/speed trade-off rather than a clear win, so that has been left alone.Measurements
ESP32-D0WD-V3 (Xtensa LX6) and ESP32-S3 (Xtensa LX7), both at 240 MHz, GCC 14.2,
-Os, RSA/MPI accelerator off so this is pure library C. Offline benchmark: no WiFi, no TLS, fixed operands, one task pinned to a core, batches sized to ~200 ms, min/mean/max over 5 batches. Reference is mbedTLS 3.6.6 from ESP-IDF v5.5.5 on the same device.ESP32 (LX6):
ecdsa_verifyP-256ecp_mulP-256mpi_inv_modP-256mpi_mul256-bitESP32-S3 (LX7):
ecdsa_verifyP-256ecp_mulP-256mpi_inv_modP-256The regression is worse on the newer core, and the series recovers 94% of it there against 100% on LX6.
Accelerator on (ESP32 LX6). The tables above are the accelerator-off configuration, where the bignum hot path is pure library C. This is the other one — the configuration in which the end-to-end result further down is a regression. Run A-B-A in one session, patched / unpatched / patched, with the two patched runs agreeing to 0.8 µs:
ecdsa_verifyP-256ecdsa_verifyP-384ecp_mulP-256ecp_mulP-384mpi_inv_modP-256So the series reaches parity with 3.6.6 in both accelerator configurations and on both curves. The 3.6.6 column is from an earlier session on the same device and harness; the 4.1.0 unpatched figure taken in that same earlier session was 416.92 ms against this session's 416.99 ms, so the harness is stable across sessions to about 0.02%. Every patched image was disassembled before flashing:
mbedtls_mpi_core_sub()with zero calls and one branch, against two calls unpatched.Attribution is exact: reverting only the constant-time lines in
mbedtls_mpi_core_sub()andmbedtls_mpi_core_mla()to the 3.6 plain-C form recovers the whole gap on its own, which is what pins the cause to them rather than to anything else that changed between 3.6 and 4.x.Call counts have since been confirmed in running firmware as well as in the offline harness, using
-Wl,--wrap=mbedtls_mpi_mul_mpi, which works against a precompiled 3.6.6 because it acts at link time: both versions make exactly 5817 calls per P-256 verify and exactly 8569 per P-384 verify, on both curves and in both accelerator configurations.The regression is O(n) at roughly 44 cycles per limb, flat from 256 to 4096 bits.
mbedtls_mpi_core_mul()callsmlaonce per limb of B withexcess_len == 1, so an n×n multiply runs the changed line exactly n times, and eachmbedtls_mpi_core_montmul()performs 6n such operations. Because the multiply itself is O(n²) the relative cost shrinks as operands grow (+15.4% at 256 bits, +2.0% at 4096), so it is worst exactly where ECC lives.P-384 bears that out across curve size. Measured in a different cell from the tables above — fixed-point ECP enabled, accelerator still off — so read these rows against their own P-256 figure and not against the rows above:
ecdsa_verifyP-256ecdsa_verifyP-384ecp_mulP-256ecp_mulP-384The cost is spread across curve size rather than concentrated on one curve, which is what a per-limb effect should look like — and P-384 is the curve a real certificate chain uses for its root. There is no "with this series" column for these two cells; the series was not rebuilt for them.
End to end, in real firmware
The tables above are primitives. The effect on a real TLS 1.2 ECDHE-ECDSA handshake to
api.github.com, from the ESP32 firmware, timed from the host, six reps each:About 830 ms, or 19.8%. It was run as A-B-A because a single before/after against a live third-party host proves nothing: the two unpatched runs bracket the patched one and agree with each other to within 55 ms of mean. The firmware is also 144 bytes smaller.
That 19.8% is conditional, and in one configuration the series is a regression. It is measured with the ESP32 RSA/MPI accelerator disabled (
MBEDTLS_HARDWARE_MPIoff), which is this firmware's shipped setting for reasons unrelated to this patch. With the accelerator enabled, the same series makes the same handshake roughly 875 ms slower, measured as A-B-A on one device in one session, four reps each, timed inside the firmware:The two unpatched runs bracket the patched one and agree with each other to within 13 ms of total, so this is not drift.
Since first posting this, the flash-cache hypothesis has been measured, and it holds. Instruction fetch dominates this workload on this part, and it dominates far more with the accelerator enabled:
--wraphook with a cycle counter inside it puts the identicalmbedtls_mpi_mul_mpicall at 22.9 µs in a tight loop and 49.0 µs inside a real ECDSA verify — same binary, same core, same hook. Only what runs between the calls differs.With the accelerator on, every multiply also calls into ESP-IDF's own bignum port, the peripheral clock control, a crypto mutex and the ESP32 DPORT access path — a second body of code that trades cache lines with the elliptic-curve code 5817 times per P-256 verify. That is why the same peripheral costs ~25% in an offline benchmark and ~210% in a real firmware.
That same finding means the 875 ms should be treated with caution as a property of this series. On this part, moving mbedTLS 1424 bytes with no source change at all — by linking unrelated code ahead of it — costs 20–23% on an ECDSA verify, measured on both curves, with the multiply itself unchanged. This series changes code size and therefore placement. The 875 ms is a real measurement of those two binaries, but a 14% swing sits well inside the range that placement alone produces here, and no attempt has been made to separate the two. Anyone reproducing it on a different image, or a different IDF version, should not expect the same magnitude or necessarily the same sign.
And the primitives say the 875 ms is not the arithmetic. In the accelerator-on table above, the series is at parity with 3.6.6 on every bench — P-256 −0.4%, P-384 −0.6% — measured A-B-A in one session in exactly the configuration this handshake used. So whatever costs 875 ms in that firmware, it is not this series computing more slowly. It is a memory-system effect: either the code growth originally guessed at here, or the placement effect above. Those two are still unseparated, but "the patch made the crypto slower" is ruled out.
One correction to the original wording here: with the accelerator on, ESP-IDF substitutes its own
mbedtls_mpi_mul_mpi, so the multiply is off this series' code — but measured, that multiply is only 37–41% of an ECDSA verify. The remaining ~60% is the modular reduction and the add/subtract paths, which do run throughmbedtls_mpi_core_sub(). Saying the hot path is "largely off" the code this series touches overstated it, and the parity result above is what you would expect if the series is in fact reaching that 60%.So the honest summary is: a clear win where the constant-time helpers are on the hot path, parity in the accelerator-on configuration at the primitive level, and a measured end-to-end slowdown on the one accelerator-enabled firmware image tested that the primitives say is not the crypto. If that trade is judged unacceptable, patch 1/3 (the forced inlining) is the piece to reconsider, since it is the one that grows code; but on the placement evidence above, dropping it is not guaranteed to recover the 875 ms either.
Constant-time testing, including a rejected approach
A pure-C fix was tried first: rewriting the generic
mbedtls_ct_uint_lt()as the same branchless six-operation sequence the Arm path uses, with no barriers. It is faster, and it is not constant time — clang 22 at-O2recognises the idiom, re-derivesx < yand emits a conditional branch. Keeping barriers on only the two inputs does not help either, because a barrier hides a value and not the structure of an expression.Both were caught with
MBEDTLS_TEST_CONSTANT_FLOW_MEMSANand a negative control:_if_else_0change in commit 3That is what led to assembly, which is opaque to the optimiser by construction. It also says the existing design is right and the assembly paths are load-bearing.
What has not been verified
mbedtls_mpi_core_sub()contains one conditional branch, the loop back-edge on the public limb count, identical to stock, and no calls at all. The project's own constant-flow suite should be run before accepting..textsize were measured on Arm; the timings above are Xtensa.-Os/-O2; no other compilers or optimisation levels were tested..textfigure for the series on Xtensa. The Arm size cost is measured (+8 bytes on the thumb-Ostranslation unit); the ESP32 image came out 144 bytes smaller overall, but that is the whole firmware and not the library, so it does not settle how much code growth the accelerator-on build actually saw.The commits
constant_time: force inlining where an assembly path is in use— independent, and the one that helps existing Arm users.constant_time: add an Xtensa assembly path— additive#elifbranches; cannot affect an architecture that already has a path. Applies on top of 1, which it extends by one line.bignum_core: use _if_else_0 where the zero branch is unused— independent and architecture-neutral. Worth about 1.2% in the one configuration where it was isolated, so if it is contentious it should be dropped rather than hold up the other two.These can be split into separate PRs, commit 3 dropped, or a RISC-V path added, if any of that would help. The harness and the raw captures behind every number above can be shared if useful.
LGTM except for the missing changelog entry and an inconsistency about 64-bit support.
Note that I didn't review the Xtensa assembly. We consider this a user-supported platform, so as long as it doesn't break other platforms, we trust you that it works.
I haven't run any benchmarks. But since
mbedtls_ct_mpi_uint_if_else_0exists, I'm happy to use it where it's relevant.@ -452,10 +452,10 @@ mbedtls_mpi_uint mbedtls_mpi_core_sub(mbedtls_mpi_uint *X,mbedtls_mpi_uint c = 0;We generally don't mention optimizations in the changelog unless they're huge (more than 2×, I'd say). But please add a changelog entry under “Features” for the Xtensa assembly.
@ -47,2 +47,4 @@#elif defined(__XTENSA__)#define MBEDTLS_CT_XTENSA_ASM#endif#endifI don't understand this part. I'm not sure what the first paragraph is about. Both test functions
mpi_core_subandmpi_core_mladeclare the inputs as secret, so the carry is also secret (but that doesn't really matter since the only thing we do with it is to pass it toTEST_EQUAL, which declassifies the values before comparing them). This seems all seems correct to me, so I don't understand where there is an issue. Who is “they” in “under MemSan they report”? What “reports” are you referring to?Regarding the second paragraph, in our CI, we do constant-flow testing with both MSan and Valgrind. With Valgrind, we test both the x86_64 assembly and the generic C path (only on x86_64). There are definitely a lot of gaps in our constant-time testing (effect of different compilers and compiler options, different architectures, etc.), but not this one.
Is this coming from you (human) or some unfiltered LLM output?
@ -50,0 +68,4 @@* Deliberately NOT applied to the generic C fallback, which is much larger:* there the same change costs +396 bytes on the same measurement, which is a* size/speed trade-off rather than a clear win. Either always_inline branch* implies __GNUC__, because the MBEDTLS_CT_*_ASM macros above require it.There's Xtensa assembly below only when
MBEDTLS_CT_SIZE_32is defined. Should the functions be inlined on 64-bit Xtensa (is that even a thing or not yet?)?@ -50,0 +68,4 @@* Deliberately NOT applied to the generic C fallback, which is much larger:* there the same change costs +396 bytes on the same measurement, which is a* size/speed trade-off rather than a clear win. Either always_inline branch* implies __GNUC__, because the MBEDTLS_CT_*_ASM macros above require it.This needs to be fixed. I am looking at it now.
@ -47,2 +47,4 @@#elif defined(__XTENSA__)#define MBEDTLS_CT_XTENSA_ASM#endif#endifFirstly apologies, this goes to prove that even when you think you've checked thoroughly these clankers can be full of nonsense. This claim is retracted and I will remove the section from the PR. I ran further analysis on the claim on my side to fully disprove it.
@ -452,10 +452,10 @@ mbedtls_mpi_uint mbedtls_mpi_core_sub(mbedtls_mpi_uint *X,mbedtls_mpi_uint c = 0;I will add this in the next commit, along with the 32/64 fixes
@ -50,0 +68,4 @@* Deliberately NOT applied to the generic C fallback, which is much larger:* there the same change costs +396 bytes on the same measurement, which is a* size/speed trade-off rather than a clear win. Either always_inline branch* implies __GNUC__, because the MBEDTLS_CT_*_ASM macros above require it.There is no 64-bit extensa, but the macros need to be tightened. This has been done in the new commit with the changelog as requested
I've really hammered this patch and I think it's good to go now.
LGTM except for this passage in a comment.
@ -50,0 +64,4 @@* x86-64 inlines regardless, which is presumably why this has gone unnoticed.* Cost on the Arm thumb -Os measurement: +8 bytes of .text for the whole* translation unit.*I think something's missing in this sentence.
MBEDTLS_HAVE_INT64andMBEDTLS_HAVE_ASMare compatible, it's the usual arrangement on x86_64 or aarch64 with a gcc-like compiler. I'm not sure whyMBEDTLS_HAVE_INT64is relevant here: did you mean some other macro?@ -50,0 +64,4 @@* x86-64 inlines regardless, which is presumably why this has gone unnoticed.* Cost on the Arm thumb -Os measurement: +8 bytes of .text for the whole* translation unit.*You're right, and thank you — that sentence conflated the auto-detected case with the explicit one. bignum.h auto-defines MBEDTLS_HAVE_INT64 alongside MBEDTLS_HAVE_ASM on 64-bit platforms, which is indeed the usual arrangement; the exclusion I half-remembered is check_config rejecting an explicitly configured MBEDTLS_HAVE_INT64 in combination with MBEDTLS_HAVE_ASM. Fixed in
40e3f4d82: the paragraph now states the two facts that actually close the gap (no auto-selection of 64-bit limbs on the 32-bit-only architectures, and the explicit override cannot coexist with the assembly paths). No code change.@ -50,0 +64,4 @@* x86-64 inlines regardless, which is presumably why this has gone unnoticed.* Cost on the Arm thumb -Os measurement: +8 bytes of .text for the whole* translation unit.*Was the guard that caused the confusion, the comment now reads as below which is much clearer.
LGTM
View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.Merge
Merge the changes and update on Forgejo.Warning: The "Autodetect manual merge" setting is not enabled for this repository, you will have to mark this pull request as manually merged afterwards.