Latency
Latency, measured and estimated
If you’ve seen 42 µs or 62 µs next to our name: neither was ever measured inside an enclave. Here is every number we have, what each one measures, and our best estimate for the full trip through one.
The short answer
- Signing takes about 40 µs. The median was 39.1 µs on an Apple M2 laptop and 41.2 µs on a shared Linux runner. Measured
- The band is narrow. On the laptop, the middle half of 200,000 signatures fell within 0.13 µs of each other, and 97.7% within 5 µs of the median. Measured
- Through a Nitro Enclave, expect about 120 µs, most of it the trip in and out. We haven’t measured that yet. Estimate
- So, 42 or 62? Neither is an enclave number. 62 was the 99th percentile of one benchmark of signing alone, on a shared CI machine in May; that run’s median was 41.2. We have no recorded run behind 42.
Two questions in one
“Is it 42 or 62?” mixes two questions. Where does a typical signature land? And how far do the slow ones stray from it? The first depends on the machine and on everything around the signature, and for the enclave we can only estimate it today. The second is about the work itself, and that we can measure now. The answer is that the band is narrow, because every signature does the same fixed amount of work.
What we measured
Each run times one call to SigningKey::sign in RustCrypto k256 0.13.4: it hashes a 256-byte message with SHA-256, then signs it with ECDSA on the secp256k1 curve, using deterministic nonces (RFC 6979). Nothing else is inside the timer: no enclave, no vsock, no network, no policy check. We ran it 200,000 times on an Apple M2 laptop that was doing its usual work, and kept every sample.
Figure 1. 200,000 signatures, one narrow spike
Share of signatures in each half-microsecond bin. Apple M2 laptop, 8 November 2026. Signing alone.
Show the numbers
| Microseconds | Signatures | Share |
|---|---|---|
| 38.5 to 39.0 | 7,615 | 3.807% |
| 39.0 to 39.5 | 167,010 | 83.505% |
| 39.5 to 40.0 | 1,766 | 0.883% |
| 40.0 to 40.5 | 11,655 | 5.828% |
| 40.5 to 41.0 | 523 | 0.262% |
| 41.0 to 41.5 | 463 | 0.231% |
| 41.5 to 42.0 | 2,160 | 1.080% |
| 42.0 to 42.5 | 1,906 | 0.953% |
| 42.5 to 43.0 | 1,192 | 0.596% |
| 43.0 to 43.5 | 617 | 0.309% |
| 43.5 to 44.0 | 473 | 0.237% |
| 44.0 to 44.5 | 575 | 0.288% |
| 44.5 to 45.0 | 615 | 0.308% |
| 45.0 to 45.5 | 321 | 0.161% |
| 45.5 to 46.0 | 305 | 0.153% |
| 46.0 to 46.5 | 318 | 0.159% |
| 46.5 to 47.0 | 283 | 0.142% |
| 47.0 to 47.5 | 257 | 0.129% |
| 47.5 to 48.0 | 206 | 0.103% |
| 48.0 to 48.5 | 172 | 0.086% |
| 48.5 to 49.0 | 180 | 0.090% |
| 49.0 to 49.5 | 155 | 0.078% |
| 49.5 to 50.0 | 129 | 0.065% |
| 50.0 to 50.5 | 140 | 0.070% |
| 50.5 to 51.0 | 162 | 0.081% |
| 51.0 to 51.5 | 173 | 0.087% |
| 51.5 to 52.0 | 177 | 0.089% |
| 52.0 to 52.5 | 71 | 0.036% |
| 52.5 to 53.0 | 58 | 0.029% |
| 53.0 to 53.5 | 53 | 0.027% |
| 53.5 to 54.0 | 67 | 0.034% |
| 54.0 to 54.5 | 58 | 0.029% |
| 54.5 to 55.0 | 41 | 0.021% |
| 55.0 to 55.5 | 12 | 0.006% |
| 55.5 to 56.0 | 10 | 0.005% |
| 56.0 to 56.5 | 3 | 0.002% |
| 56.5 to 57.0 | 5 | 0.003% |
| 57.0 to 57.5 | 3 | 0.002% |
| 57.5 to 58.0 | 3 | 0.002% |
| 58.0 to 58.5 | 4 | 0.002% |
| 58.5 to 59.0 | 3 | 0.002% |
| 59.0 to 59.5 | 2 | 0.001% |
| 59.5 to 60.0 | 1 | 0.001% |
| 60.0 to 60.5 | 1 | 0.001% |
| 60.5 to 61.0 | 3 | 0.002% |
| 61.5 to 62.0 | 3 | 0.002% |
| 62.0 to 62.5 | 2 | 0.001% |
| 62.5 to 63.0 | 1 | 0.001% |
| 63.0 to 63.5 | 2 | 0.001% |
| 63.5 to 64.0 | 1 | 0.001% |
| 64.0 or more | 45 | 0.023% |
That spike is the point. Nearly all the signatures took the same time, to within a few ticks of the laptop’s clock. The smaller bumps to the right are the same work, interrupted.
Why the band is narrow
Three things keep it tight.
- The work never changes. One hash of a fixed-size message and one signature. There’s no branch that does more work for some orders than for others.
- The library takes the same time whatever the key. k256 is designed so that operations on secret values run in constant time, to resist timing attacks; NCC Group reviewed it in 2023 (their report). A side effect we like: the time doesn’t wander with the input either.
- There’s nothing to wait for. No network, no disk, no lock, no queue.
What does wander is the machine around the code: an interrupt, another process, the scheduler moving the thread, the CPU changing speed. That is the tail in Figure 2, and it’s why the shared cloud runner, where other jobs compete for the CPU, has a longer tail than the laptop. On a dedicated core with nothing else on it, the tail should shrink. We’ll show that when we’ve measured it, not before.
Figure 2. Same median, different tails
Signing time, in microseconds, that a given share of signatures stayed within. Higher on the right means a longer tail.
Show the numbers
| Share of signatures | Apple M2 laptop | Shared Linux runner (x86_64) |
|---|---|---|
| Half of them | 39.1 | 41.2 |
| 9 in 10 | 40.1 | not reported |
| 99 in 100 | 47.4 | 61.7 |
| 999 in 1,000 | 54.0 | 81.6 |
| 9,999 in 10,000 | 100.4 | not reported |
Through an enclave: our estimate
A Nitro Enclave has no network. Requests reach it over vsock, a socket between the instance and its enclave, and that hop costs more than the signature does. Lutsch, Franck, El-Hindi, István and Binnig measured it for their DaMoN 2025 paper, “An Analysis of AWS Nitro Enclaves for Database Workloads”: on a c6in.8xlarge, a small request and reply took 77.4 µs at the median and 85.5 µs for 99 in 100, with a long tail at 1 in 1,000 (665.4 µs). Their raw data is in their repository. Published
Figure 3. Where the time would go inside an enclave
One signature, from the trading process on the host into the enclave and back. Typical case.
So our estimate is about 120 µs for a typical signature through the enclave, and under about 150 µs for 99 in 100. Two caveats, both honest. Their benchmark wasn’t tuned for latency the way a signing path will be, with pinned cores and polling instead of sleeping, so the hop may come in lower. And their 1-in-1,000 tail means the far tail through an enclave is an open question. It’s the first thing we’ll measure.
Figure 4. Where these numbers sit
Round trips on AWS from Lutsch, Franck, El-Hindi, István and Binnig (DaMoN 2025, c6in.8xlarge, small messages, medians), next to ours.
Show the numbers
| Path | Microseconds | Source |
|---|---|---|
| TCP, loopback on one instance | 19.6 | DaMoN 2025 |
| Signing alone | 39.1 | Measured |
| TCP between two instances, one zone | 49.0 | DaMoN 2025 |
| vsock, instance to its enclave | 77.4 | DaMoN 2025 |
| Through the enclave, our estimate | 120.0 | Estimate |
| From another instance, through socat | 1,125.6 | DaMoN 2025 |
Every run
| When and where | Signatures | Median | 99 in 100 | 999 in 1,000 | Raw data |
|---|---|---|---|---|---|
| 15 May 2026, GitHub-hosted ubuntu-latest runner, x86_64. Signing alone; commit 512cd6fc. | 50,000 | 41.2 | 61.7 | 81.6 | latest.json |
| 8 November 2026, Apple M2 laptop, the same benchmark five times. Signing alone; commit c707f9c0. | 5 × 50,000 | 39.1 | 47.5 to 50.2 | 53.7 to 79.4 | m2-2026-11-08.json |
| 8 November 2026, the same laptop, every sample kept. Signing alone. | 200,000 | 39.1 | 47.4 | 54.0 | samples, harness |
| DaMoN 2025, c6in.8xlarge, published. vsock round trip, instance to enclave, small messages. | n/a | 77.4 | 85.5 | 665.4 | their repository |
All figures in microseconds. A 99th percentile from a single run of 50,000 rests on its slowest 500 samples, which is why the laptop’s five runs spread from 47.5 to 50.2 µs while the median never moved.
How it was measured
The timed loop is short enough to read in full:
let signing_key = SigningKey::from_bytes(&[42u8; 32].into())?;
let message = [0xABu8; 256];
for _ in 0..1_000 { let _: Signature = signing_key.sign(&message); } // warm up
for _ in 0..n {
let t0 = Instant::now();
let sig: Signature = signing_key.sign(&message); // SHA-256, then ECDSA (RFC 6979)
samples.push(t0.elapsed().as_nanos() as u64);
black_box(sig);
}- The clock is Rust’s monotonic
Instant; on this Mac it ticks every 41.7 ns. - Percentiles are nearest-rank: the 99th is the sample that 99% of samples are at or below.
- The build matches the workspace’s release profile: fat LTO, one codegen unit, overflow checks on. This harness’s median matches the repo’s own benchmark to within a tenth of a microsecond.
- To repeat it, take the harness (its
Cargo.tomlis in the comment at the top), runcargo build --release, thensign-dist 200000 > samples.txt.
What we measure next
The full round trip from the host process into the enclave and back, on a Nitro-capable instance with pinned cores, a million samples, raw data published. We call it EXP-01. When it’s done, its numbers replace the estimate on this page, whatever they turn out to be.
If you’d like it run on your instances and your workload, that’s what a pilot is for.
A correction
What this page replaces
Until 8 November 2026 this site said “62 µs P99 signing inside AWS Nitro Enclaves”, and drew charts modeled from that one figure. The 62 µs was the 99th percentile of the signing step alone, on a shared CI runner. It was never measured inside an enclave, and the full trip through one will be slower, as the estimate above shows. Earlier versions quoted 42 µs, a figure with no recorded run behind it.