Skip to content

Commit 0c5e0ab

Browse files
authored
Merge pull request #13 from dgarske/imx95_pqc_demo
Add NXP i.MX95 post-quantum demo for the A55 cluster and Cortex-M7
2 parents 238aef9 + 5b067ac commit 0c5e0ab

43 files changed

Lines changed: 4404 additions & 0 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎README.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ This repository contains example applications using [wolfBoot](https://github.co
1212
* wolfSSH SCP transfer firmware update mechanism, with [freeRTOS on Freescale K64F](freeRTOS-Freescale-K64F-scp)
1313
* BLE-GATT FOTA service using [RIOT-OS and Nimble on Nordic nRF52](riotOS-nrf52840dk-ble)
1414
* Measured boot demo using [wolfTPM on STM32F4](test-app-STM32F4-measured-boot)
15+
* Post-quantum demo on both clusters of the [NXP i.MX95](imx95-pqc-demo): ML-DSA-87 verified boot of the Cortex-M7 alongside wolfCrypt ML-KEM/ML-DSA benchmarks on the Cortex-A55 cluster
1516

1617
## License
1718

‎imx95-pqc-demo/.gitignore‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Zephyr build output (m7/build.sh writes here)
2+
m7/build/
3+
# wolfSSL source export and cross-build trees (container/build-aarch64.sh)
4+
container/wolfssl-src/
5+
container/build/
6+
# Python bytecode from the demo renderer
7+
demo/__pycache__/

‎imx95-pqc-demo/README.md‎

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
# Post-quantum on both clusters of the NXP i.MX95
2+
3+
A two-pane demo on a single screen, running entirely on a Toradex SMARC iMX95:
4+
5+
| pane | what it shows |
6+
|---|---|
7+
| **left** | wolfCrypt ML-KEM / ML-DSA benchmarks in a container on the six Cortex-A55 cores, under Torizon OS |
8+
| **right** | wolfBoot performing **ML-DSA-87 verified boot** of the Cortex-M7, with its console relayed to Linux |
9+
10+
The point of the pairing: the same post-quantum algorithms, at the same security
11+
level, on two very different cores of one SoC - a Linux application cluster and
12+
a bare-metal real-time core.
13+
14+
## Measured on hardware
15+
16+
Cortex-M7 at 800 MHz, DWT cycle counter, caches enabled:
17+
18+
| | wolfBoot text | verify + boot | at 800 MHz |
19+
|---|---|---|---|
20+
| ECDSA P-256 | 22,548 B | 1,741,288 cycles | 2.177 ms |
21+
| **ML-DSA-87** | **21,608 B** | **4,201,179 cycles** | **5.251 ms** |
22+
| ML-DSA-87, Zephyr payload | 21,608 B | 5,916,540 cycles | 7.396 ms |
23+
24+
Two results worth calling out. **Post-quantum verified boot costs 5.25 ms** -
25+
2.4x the cycles of ECDSA P-256 and irrelevant against any real boot time. And
26+
the **ML-DSA-87 bootloader is 940 bytes smaller than the ECDSA one**: ML-DSA
27+
verification is SHAKE plus polynomial arithmetic and never pulls in the
28+
big-integer math P-256 needs, so for a verify-only workload post-quantum can
29+
cost *less* flash.
30+
31+
Verification also scales far better than payload size suggests - 1,004 B to
32+
54,080 B is **54x the payload for 1.41x the verify**, because the lattice
33+
signature check is a fixed cost and only the hash grows.
34+
35+
> The M7's I- and D-caches are **disabled out of reset**. Any i.MX95 M7
36+
> benchmark taken without enabling them is wrong by one to two orders of
37+
> magnitude - we measured SHA-256 at 1.1 MiB/s before enabling them and
38+
> 29.2 MiB/s after.
39+
40+
## Layout
41+
42+
```
43+
container/ wolfCrypt PQC benchmark container for the A55 cluster (left pane)
44+
m7/ Zephyr RPMsg payload wolfBoot verifies and boots (right pane)
45+
demo/ board-side orchestration and the two-pane renderer
46+
gallery/ Torizon Demo Gallery submission: compose file and partner metadata
47+
tools/ memtool - mmap-based /dev/mem access for the M7 console
48+
charts/ the measured figures, as images
49+
```
50+
51+
## Requirements
52+
53+
- Toradex SMARC iMX95 (or another i.MX95 board) running Torizon OS
54+
- wolfBoot with the `imx95_m7` target
55+
- A Zephyr workspace (4.4.0 or newer) and an `arm-none-eabi` toolchain
56+
- Docker on the board (Torizon ships it)
57+
58+
## Building
59+
60+
**1. wolfBoot for the M7**, signing with ML-DSA-87:
61+
62+
```sh
63+
cp config/examples/imx95-m7.config .config
64+
make SIGN=ML_DSA ML_DSA_LEVEL=5 IMAGE_SIGNATURE_SIZE=4627 DEBUG_UART=1
65+
```
66+
67+
`DEBUG_UART=1` is what makes wolfBoot write its verification log to the
68+
shared-memory console the right pane reads.
69+
70+
**2. The Zephyr payload:**
71+
72+
```sh
73+
cd m7 && ZEPHYR_BASE=~/zephyrproject/zephyr ./build.sh
74+
```
75+
76+
Then sign it with the same key wolfBoot was built with:
77+
78+
```sh
79+
IMAGE_HEADER_SIZE=12288 ML_DSA_LEVEL=5 ./tools/keytools/sign --ml_dsa --sha256 \
80+
m7/build/zephyr/payload.bin wolfboot_signing_private_key.der 1
81+
```
82+
83+
**3. The benchmark container:**
84+
85+
```sh
86+
cd container && WOLFSSL_REPO=/path/to/wolfssl ./build-aarch64.sh all && ./build-image.sh
87+
```
88+
89+
## Running
90+
91+
```sh
92+
BOARD=torizon@<board-ip> WOLFBOOT=/path/to/wolfboot ./demo/stage.sh
93+
```
94+
95+
then on the board:
96+
97+
```sh
98+
sudo bash ~/demo/demo-run.sh /dev/tty1 # /dev/tty1 is the HDMI console
99+
```
100+
101+
Omit the argument to render on the current terminal instead.
102+
103+
### Starting it automatically
104+
105+
Because replaying the demo means power-cycling the board (see below), running it
106+
by hand also means logging back in afterwards, over a network link that does not
107+
always come up. Installing the unit makes cutting and restoring power the entire
108+
replay:
109+
110+
```sh
111+
sudo bash ~/demo/install-autostart.sh # enable
112+
sudo bash ~/demo/install-autostart.sh --off # disable, restore the getty
113+
```
114+
115+
It takes tty1 from `getty@tty1`, so the HDMI console is the demo rather than a
116+
login prompt.
117+
118+
### Reading the verify log over RPMsg
119+
120+
The right pane does not need this, but the same log can be read as an ordinary
121+
Linux tty:
122+
123+
```sh
124+
sudo bash ~/demo/m7-rpmsg-log.sh
125+
```
126+
127+
Order matters. The payload relays the whole console ring as soon as its endpoint
128+
has a destination address, which the host supplies while binding the channel -
129+
strictly before `/dev/ttyRPMSG*` exists. No reader can be attached for that first
130+
pass, and the log it sends is discarded by a tty nobody has open. The payload
131+
therefore treats any byte written to the tty as a request to rewind and send the
132+
log again, so the script attaches a reader first and then pokes.
133+
134+
## Notes that will save you time
135+
136+
**The M7 starts once per Linux boot.** `echo stop > .../state` fails on this BSP
137+
with "Interrupted system call" and the core stays running, so replaying the boot
138+
means a real power cycle of the board - not a restart. Plan the demo around
139+
that.
140+
141+
**The payload is not the upstream `openamp_rsc_table` sample, deliberately.**
142+
That sample assumes Linux loaded *its* ELF: the remote declares vrings with
143+
`da = FW_RSC_ADDR_ANY` and Linux writes the resolved addresses and the virtio
144+
status back into the resource table it loaded. Here Linux loads **wolfBoot**, so
145+
those values land in wolfBoot's table while the sample polls its own - and it
146+
waits forever in `rproc_virtio_wait_remote_ready()`. This payload uses fixed
147+
vring addresses matching wolfBoot's table and skips the wait, because there is
148+
nothing to wait for: Linux registers virtio0 before it releases the core.
149+
150+
**The right pane reads the console ring directly, not `rpmsg_tty`.** wolfBoot
151+
writes its log before any RPMsg endpoint exists, so the ring is the only source
152+
that contains the verification output - and reading it needs no driver, no
153+
endpoint binding and no module load.
154+
155+
**Nothing may sit unflushed in the page cache.** The replay beat is a hard power
156+
cut, so a file written seconds earlier is simply gone after the next cycle - a
157+
staged payload, or the autostart unit, silently reverts to what was there
158+
before. `stage.sh` and `install-autostart.sh` both `sync`; verify a hand-copied
159+
payload with `md5sum` rather than a timestamp.
160+
161+
**Torizon has no tmux**, and its rootfs is read-only OSTree, so there is nothing
162+
to install. `demo/twopane.py` renders two fixed columns with a full redraw
163+
instead.
164+
165+
**Do not quote the benchmark's "Cycles per byte" or "Cycles/op" columns on an
166+
A55.** They derive from the 24 MHz generic timer rather than the 1.8 GHz core
167+
clock and are wrong by roughly 75x. Use ops/sec and MB/s, or pass
168+
`-freq 1800000000`. The demo renderer strips those columns for this reason.
169+
170+
**The AHAB containers are unsigned, and the module is an open part.** The boot
171+
ROM checks each image against the SHA-384 held in the container, which is an
172+
integrity check, not an authenticity one. The signature block is a 16-byte stub
173+
with its SRK-table, certificate and signature offsets all zero, and the ROM does
174+
not check it. What is cryptographically verified here is what **wolfBoot**
175+
verifies: the Linux FIT and the Cortex-M7's Zephyr payload, both with ML-DSA-87.
176+
Anchoring the chain below wolfBoot means signing the containers - which needs no
177+
fuses - and then burning the OEM SRK hash and closing the part, which does and is
178+
irreversible. See *Signing the containers* in [docs/BUILD.md](docs/BUILD.md) and
179+
*Where the root of trust sits* in
180+
[docs/BOOT-WALKTHROUGH.md](docs/BOOT-WALKTHROUGH.md).
181+
182+
## Documentation
183+
184+
- [docs/BUILD.md](docs/BUILD.md) - building the demo from a stock module, end to end
185+
- [docs/BOOT-WALKTHROUGH.md](docs/BOOT-WALKTHROUGH.md) - the boot explained stage by stage, and how to switch between the stock and wolfBoot chains
186+
187+
## Tools
188+
189+
- `tools/ahab-extract.py` - list or extract the images in an AHAB container set
190+
- `tools/ahab-pack-container2.sh` - build container 2 (BL31, BL33, OP-TEE) into a boot image
191+
- `tools/ahab-replace-spl.py` - put a wolfBoot stage 1 in the U-Boot SPL slot
192+
- `tools/emmc-write-boot.sh` - write and verify an eMMC boot partition over ssh
193+
- `tools/emmc-select-boot.sh` - choose which boot partition the boot ROM loads
194+
195+
## Support
196+
197+
wolfSSL is dual licensed under GPLv3 or a commercial license. Questions:
198+
support@wolfssl.com
29 KB
Loading
22.8 KB
Loading
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# wolfCrypt PQC benchmark container for Toradex SMARC i.MX95 / Torizon OS.
2+
#
3+
# The builder stage runs on the NATIVE build platform and cross-compiles for
4+
# aarch64, rather than compiling arm64 under qemu emulation. Same artifacts,
5+
# minutes instead of the better part of an hour.
6+
#
7+
# Both wolfSSL configurations are shipped in the image so the SHA-3 A/B can be
8+
# run on the board without a rebuild, and so the entrypoint can pick the right
9+
# one at runtime from /proc/cpuinfo.
10+
11+
# ---------------------------------------------------------------- builder ---
12+
FROM --platform=$BUILDPLATFORM debian:bookworm AS builder
13+
14+
RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
15+
autoconf automake libtool make gcc \
16+
gcc-aarch64-linux-gnu binutils-aarch64-linux-gnu \
17+
libc6-dev-arm64-cross \
18+
ca-certificates \
19+
&& rm -rf /var/lib/apt/lists/*
20+
21+
COPY wolfssl-src /src/wolfssl
22+
WORKDIR /src/wolfssl
23+
24+
# --enable-mldsa is REQUIRED: it is off by default, and without it the
25+
# benchmark silently emits no ML-DSA rows rather than failing.
26+
ENV WOLF_COMMON="--host=aarch64-linux-gnu --enable-mlkem --enable-mldsa \
27+
--enable-sp --enable-sp-asm --enable-keygen --enable-sha3 \
28+
--enable-curve25519 --enable-ed25519 --disable-shared --enable-static"
29+
30+
# baseline: NEON only, runs on any Cortex-A55.
31+
RUN mkdir -p /build/baseline && cd /build/baseline \
32+
&& /src/wolfssl/configure $WOLF_COMMON --enable-armasm \
33+
&& make -j"$(nproc)"
34+
35+
# sha3-crypto: ARMv8.2 SHA3 instructions. Requires FEAT_SHA3 on the silicon;
36+
# the entrypoint checks /proc/cpuinfo before selecting this one.
37+
RUN mkdir -p /build/sha3-crypto && cd /build/sha3-crypto \
38+
&& /src/wolfssl/configure $WOLF_COMMON --enable-armasm=sha3-crypto \
39+
&& make -j"$(nproc)"
40+
41+
RUN set -eux; \
42+
for b in baseline sha3-crypto; do \
43+
mkdir -p /out/$b/bin; \
44+
cp /build/$b/wolfcrypt/benchmark/benchmark /out/$b/bin/; \
45+
cp /build/$b/wolfcrypt/test/testwolfcrypt /out/$b/bin/; \
46+
cp /build/$b/examples/client/client /out/$b/bin/ 2>/dev/null || true; \
47+
cp /build/$b/examples/server/server /out/$b/bin/ 2>/dev/null || true; \
48+
cp /build/$b/wolfssl/options.h /out/$b/; \
49+
done; \
50+
cp /src/wolfssl/README.md /out/ 2>/dev/null || true
51+
52+
# ---------------------------------------------------------------- runtime ---
53+
FROM debian:bookworm-slim
54+
55+
LABEL org.opencontainers.image.title="wolfCrypt PQC benchmark (i.MX95)" \
56+
org.opencontainers.image.description="Live ML-KEM / ML-DSA benchmarks on the Cortex-A55 cluster" \
57+
org.opencontainers.image.vendor="wolfSSL Inc."
58+
59+
RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
60+
util-linux procps ca-certificates \
61+
&& rm -rf /var/lib/apt/lists/*
62+
63+
COPY --from=builder /out/ /opt/wolfssl/
64+
COPY entrypoint.sh /opt/wolfssl/
65+
RUN chmod +x /opt/wolfssl/entrypoint.sh
66+
67+
WORKDIR /opt/wolfssl
68+
ENTRYPOINT ["/opt/wolfssl/entrypoint.sh"]
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
#!/usr/bin/env bash
2+
# Cross-build wolfSSL for the Toradex SMARC i.MX95 (6x Cortex-A55, aarch64).
3+
#
4+
# Builds two configurations out-of-tree so the wolfSSL source checkout stays clean:
5+
# baseline -- --enable-armasm (NEON; works on any A55)
6+
# sha3-crypto -- --enable-armasm=sha3-crypto (ARMv8.2 SHA3 instructions, FEAT_SHA3)
7+
#
8+
# The pair exists because ML-DSA has NO ARM assembly in wolfSSL: on aarch64 its
9+
# speed comes entirely from the SHA-3/SHAKE backend, so this is the only lever
10+
# that moves ML-DSA numbers. FEAT_SHA3 is optional on Cortex-A55, so the
11+
# sha3-crypto build may not be runnable on the actual silicon -- check
12+
# "grep Features /proc/cpuinfo" for 'sha3' on the board before shipping it.
13+
#
14+
# Static libraries only, so the binaries run under qemu-aarch64 with no
15+
# LD_LIBRARY_PATH juggling.
16+
#
17+
# Usage: ./build-aarch64.sh [baseline|sha3-crypto|all]
18+
19+
set -euo pipefail
20+
21+
# Point this at your wolfSSL checkout:
22+
# WOLFSSL_REPO=/path/to/wolfssl ./build-aarch64.sh all
23+
WOLFSSL_REPO="${WOLFSSL_REPO:?set WOLFSSL_REPO to your wolfSSL checkout}"
24+
WOLFSSL_REF="${WOLFSSL_REF:-HEAD}"
25+
DEMO_DIR="$(cd "${BASH_SOURCE[0]%/*}" && pwd)"
26+
# Pristine export of the repo. The developer's checkout is already configured
27+
# in-tree, and autotools refuses an out-of-tree build against a configured
28+
# source dir. Exporting rather than running "make distclean" over there keeps
29+
# their working tree and build state untouched.
30+
WOLFSSL_SRC="${DEMO_DIR}/wolfssl-src"
31+
HOST_TRIPLE=aarch64-linux-gnu
32+
JOBS="$(nproc)"
33+
34+
export_src() {
35+
if [ -x "${WOLFSSL_SRC}/configure" ]; then
36+
echo "=== source export already present: ${WOLFSSL_SRC} ==="
37+
return
38+
fi
39+
echo "=== exporting ${WOLFSSL_REPO} @ ${WOLFSSL_REF} -> ${WOLFSSL_SRC} ==="
40+
rm -rf "${WOLFSSL_SRC}"
41+
mkdir -p "${WOLFSSL_SRC}"
42+
git -C "${WOLFSSL_REPO}" archive "${WOLFSSL_REF}" | tar -x -C "${WOLFSSL_SRC}"
43+
echo "=== autogen.sh ==="
44+
(cd "${WOLFSSL_SRC}" && ./autogen.sh > autogen.log 2>&1) \
45+
|| { tail -40 "${WOLFSSL_SRC}/autogen.log"; exit 1; }
46+
}
47+
48+
# Shared across both configurations. --enable-mldsa is REQUIRED: it is off by
49+
# default, and without it the benchmark silently emits no ML-DSA rows at all
50+
# rather than failing.
51+
COMMON_OPTS=(
52+
"--host=${HOST_TRIPLE}"
53+
--enable-mlkem
54+
--enable-mldsa
55+
--enable-sp
56+
--enable-sp-asm
57+
--enable-keygen
58+
--enable-sha3
59+
--enable-curve25519
60+
--enable-ed25519
61+
--disable-shared
62+
--enable-static
63+
)
64+
65+
build_one() {
66+
local name="$1" armasm="$2"
67+
local builddir="${DEMO_DIR}/build/${name}"
68+
69+
echo "=== [${name}] configure (${armasm}) ==="
70+
rm -rf "${builddir}"
71+
mkdir -p "${builddir}"
72+
(
73+
cd "${builddir}"
74+
"${WOLFSSL_SRC}/configure" \
75+
"${COMMON_OPTS[@]}" \
76+
"${armasm}" \
77+
> configure.log 2>&1 || { tail -40 configure.log; exit 1; }
78+
)
79+
80+
echo "=== [${name}] make -j${JOBS} ==="
81+
make -C "${builddir}" -j"${JOBS}" > "${builddir}/build.log" 2>&1 \
82+
|| { tail -60 "${builddir}/build.log"; exit 1; }
83+
84+
echo "=== [${name}] OK ==="
85+
file "${builddir}/wolfcrypt/benchmark/benchmark" || true
86+
}
87+
88+
target="${1:-all}"
89+
export_src
90+
case "${target}" in
91+
baseline) build_one baseline "--enable-armasm" ;;
92+
sha3-crypto) build_one sha3-crypto "--enable-armasm=sha3-crypto" ;;
93+
all)
94+
build_one baseline "--enable-armasm"
95+
build_one sha3-crypto "--enable-armasm=sha3-crypto"
96+
;;
97+
*) echo "usage: $0 [baseline|sha3-crypto|all]" >&2; exit 2 ;;
98+
esac

0 commit comments

Comments
 (0)