ElementsProject / ElementsProject/libwally-core

Add a PSA Crypto hashing backend (ESP-IDF 6 / Mbed TLS 4 breaks `--enable-mbed-tls`)

Open
#544 5 comments 0 reactions 0 assignees View on GitHub
Dominant language
C
Stars
307
Forks
159
Avg merge
2h 56m
Merged PRs (30d)
4

Description

## Summary

libwally's optional `--enable-mbed-tls` backend (`HAVE_MBEDTLS_SHA256_H` / `HAVE_MBEDTLS_SHA512_H`) uses the legacy `mbedtls_sha256_*` / `mbedtls_sha512_*` APIs and reaches into the private fields of `mbedtls_sha256_context` / `mbedtls_sha512_context`. Mbed TLS 4.0 (with cryptography split out into TF-PSA-Crypto 1.0) removes those headers from the public API, and ESP-IDF 6.0 ships Mbed TLS 4.0 with PSA Crypto as the primary cryptography interface. Consumers such as Jade (currently on ESP-IDF v5.5.4) will not be able to build libwally with hardware-accelerated hashing once they move to ESP-IDF 6.x.

This issue proposes adding a PSA Crypto hashing backend alongside the existing mbedtls one, and describes the scope so it can be discussed before a PR.

## What breaks

From the TF-PSA-Crypto 1.0 migration guide:

- `mbedtls/sha256.h` and `mbedtls/sha512.h` "are no longer available" as public headers. They now live under `mbedtls/private/`, and the guide "strongly recommend[s] against defining `MBEDTLS_DECLARE_PRIVATE_IDENTIFIERS` or `MBEDTLS_ALLOW_PRIVATE_ACCESS` in your own application."
- `mbedtls/md.h` remains with reduced functionality, but `mbedtls_md_hmac_xxx()` is gone.
- "PSA is now the preferred interface for computing hashes and HMAC."

From the ESP-IDF 6.0 security migration guide:

- "In ESP-IDF v6.0, multiple ESP-IDF components have been migrated from using legacy Mbed TLS cryptography APIs (for example, `mbedtls_sha*_*()`, `mbedtls_md*_*()`, etc.) to using the PSA Crypto API."
- Hardware acceleration is now exposed "through PSA drivers where available". ESP-IDF master carries PSA drivers for SHA-1/256/512 (both the DMA engines and the ESP32 "parallel engine" with software fallback), including a `hash_clone` entry point.
- `psa_crypto_init()` must be called before any cryptographic operation; ESP-IDF does this automatically at startup (`esp_psa_crypto_init.c`, secondary init priority 104).

Note also that the current libwally mbedtls branch is already ESP-IDF specific rather than generic mbedtls: `sha256.h` / `sha512.h` include ``, `` and `` / ``, and `hmac.c` / `internal.c` test `CONFIG_MBEDTLS_HARDWARE_SHA`, `MBEDTLS_SHA256_ALT` and `SOC_SHA_SUPPORT_PARALLEL_ENG`.

## Where libwally touches mbedtls today

| File | What it does | PSA impact |
|---|---|---|
| `src/ccan/ccan/crypto/sha256/sha256.h`, `sha256.c` | `struct sha256_ctx` embeds `mbedtls_sha256_context`; `sha256_init/update/done` wrap `mbedtls_sha256_*` | Replace with `psa_hash_operation_t` + `psa_hash_setup/update/finish/abort` |
| `src/ccan/ccan/crypto/sha512/sha512.h`, `sha512.c` | Same for SHA-512 | Same |
| `src/hmac.c` + `src/hmac.inl` | HMAC uses `sizeof(ctx.SHA_CTX_BUFF)` to get the block size, reading `c.MBEDTLS_PRIVATE(buffer)` | PSA operations are opaque; use the block size directly (64 / 128, `PSA_HASH_BLOCK_LENGTH()`) |
| `src/internal.c` (`sha256_midstate`, `wally_sha256_midstate`) | Reads `c.MBEDTLS_PRIVATE(state)` to export the SHA-256 midstate; byte-order special case for HW engines | PSA cannot export internal state. Keep the built-in software compression function for this one call (used by Elements asset/token issuance in `transaction.c`) |
| `src/tx_io.c` | `TXIO_CTX_CACHEABLE 0` for external hash backends because contexts cannot be copied | With PSA, `psa_hash_clone()` makes context caching possible again (optional improvement) |
| `src/ccan_config.h` | Maps `HAVE_MBEDTLS_SHA*_H` to `CCAN_CRYPTO_SHA*_USE_MBEDTLS` | Add `HAVE_PSA_CRYPTO_H` -> `CCAN_CRYPTO_SHA*_USE_PSA` |
| `configure.ac`, `README.md` | `--enable-mbed-tls` option and docs | Add `--enable-psa-crypto`; document `psa_crypto_init()` requirement |
| `src/amalgamation/combined.c` | Nothing extra needed; the ccan sources are already included | None |

Jade's ESP-IDF component (`components/libwally-core/CMakeLists.txt`) enables this path with `-DHAVE_MBEDTLS_SHA256_H -DHAVE_MBEDTLS_SHA512_H` and `PRIV_REQUIRES mbedtls`, and post-build strips the `sha256_init/done` / `sha512_init/done` symbols. Consumers would switch to a single `-DHAVE_PSA_CRYPTO_H`.

## Proposed design

1. **New backend, not a replacement.** Add `CCAN_CRYPTO_SHA256_USE_PSA` / `CCAN_CRYPTO_SHA512_USE_PSA` next to the OpenSSL and mbedtls branches. Keep `--enable-mbed-tls` working for ESP-IDF 5.x users for now, and consider deprecating it once Jade and other consumers move to ESP-IDF 6.

2. **Context type.** `struct sha256_ctx { psa_hash_operation_t op; }`.
- `sha256_init`: `op = PSA_HASH_OPERATION_INIT; psa_hash_setup(&op, PSA_ALG_SHA_256)`.
- `sha256_update`: `psa_hash_update`.
- `sha256_done`: `psa_hash_finish(&op, res->u.u8, 32, &len)`, `psa_hash_abort` on failure.
- Same for SHA-512 with `PSA_ALG_SHA_512`.
- Keep the `sha256()` / `sha512()` one-shot helpers on `psa_hash_compute()` so the ESP driver's one-shot path (DMA) is used where available.

3. **HMAC.** Stop deriving the block size from the context layout. Define the pad size as 64 / 128 for the PSA backend (and ideally for all backends; it is a constant of the hash, not the context). No functional change. Moving HMAC/PBKDF2 to `psa_mac_*` / `PSA_ALG_PBKDF2_HMAC` is possible but out of scope; it needs key import per call and offers no benefit unless a platform has hardware HMAC.

4. **Midstate.** `wally_sha256_midstate()` only needs the compression function over whole blocks. With the PSA backend, compile the built-in ccan SHA-256 transform in as a private helper for this call only. Cost is a couple of KB of flash; it removes the byte-order special-casing currently keyed on `MBEDTLS_SHA256_ALT` / `SOC_SHA_SUPPORT_PARALLEL_ENG`.

5. **Initialisation.** Call `psa_crypto_init()` from `wally_init()` when the PSA backend is compiled in. It is idempotent, and ESP-IDF calls it itself at boot, so this only matters for non-ESP builds against TF-PSA-Crypto. Document that consumers who hash before `wally_init()` must call it themselves.

6. **Context copying.** `psa_hash_clone()` is part of the PSA API and is implemented by the ESP SHA drivers. `tx_io.c` could set `TXIO_CTX_CACHEABLE 1` for PSA via a small `sha256_ctx_clone()` helper. Optional follow-up; the initial PR can keep caching off as it is for mbedtls today.

7. **Remove ESP-IDF specifics from ccan headers.** The PSA branch should not need ``, `` or ``; the driver selection happens inside TF-PSA-Crypto.

## Estimated size

- Roughly 150 to 250 lines changed across about 9 files; no public API or ABI change (the context structs are internal).
- Mostly mechanical: the mbedtls branches being replaced are about 20 lines per hash, plus the HMAC and midstate cleanups.
- Bindings, wasm and Python are unaffected.

## Things to watch

- **Stack usage.** `psa_hash_operation_t` is a union over all enabled hash contexts (the ESP driver's SHA-512 context alone is about 230 bytes) and is larger than `mbedtls_sha256_context`. `struct sha256_ctx` is used on the stack in `tx_io.c`, `elements.c` and the HMAC path. See Mbed-TLS/mbedtls#10537 ("Increased Stack Usage After Migrating to PSA Crypto"). Worth measuring on Jade.
- **Flash footprint.** Espressif reports a 3 to 6 percent increase for their own examples after the PSA move; this is a platform cost, not a libwally one.
- **Testing.** Nothing in libwally's CI exercises `--enable-mbed-tls` today. A CI job building against TF-PSA-Crypto on Linux (built from source, since distro packages still ship Mbed TLS 2.28 / 3.6) or inside the `espressif/idf:v6.0` container would give the new backend real coverage and catch future PSA API churn.
- **Error handling.** The ccan hash API returns `void`; PSA functions return `psa_status_t`. Failures (for example the hardware engine unavailable) would have to be surfaced by leaving the context in an aborted state so `sha256_done` produces an error rather than silently wrong output. This needs a small design decision.

## References

- ESP-IDF 6.0 security migration guide: https://docs.espressif.com/projects/esp-idf/en/latest/esp32/migration-guides/release-6.x/6.0/security.html
- TF-PSA-Crypto 1.0 migration guide: https://github.com/Mbed-TLS/TF-PSA-Crypto/blob/development/docs/1.0-migration-guide.md
- Mbed TLS 4.0 migration guide: https://github.com/Mbed-TLS/mbedtls/blob/development/docs/4.0-migration-guide.md
- ESP-IDF PSA SHA drivers: https://github.com/espressif/esp-idf/tree/master/components/mbedtls/port/psa_driver/esp_sha
- ESPHome's equivalent migration for reference: https://github.com/esphome/esphome/pull/14809 (SHA-256), https://github.com/esphome/esphome/pull/14814 (HMAC)
- Jade's libwally component: https://github.com/Blockstream/Jade/blob/master/components/libwally-core/CMakeLists.txt

I am happy to open a PR along these lines if the approach is acceptable.

Contributor guide

No contributing guide indexed for this repository

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.