ECDH deriveBits: step 5's OperationError precedes the curve-mismatch check, but no browser does that
Nobody has claimed this yet.
- Dominant language
- HTML
- Stars
- 294
- Forks
- 78
- PR merge metrics
- No merged PRs in 30d
Description
The prose
ECDH "Derive Bits" orders
its checks like this:
- Let publicKey be the
publicmember of normalizedAlgorithm. - If the
[[type]]of publicKey is not"public"→ InvalidAccessError. - If the
nameof publicKey's[[algorithm]]≠ normalizedAlgorithm'sname→ InvalidAccessError. - Let maximumLength be the length in bits of the output of the field element to octet
string conversion … for the EC domain parameters associated with publicKey. - If length is not null and is greater than maximumLength → OperationError.
- If the
[[type]]of key is not"private"→ InvalidAccessError. - If the
nameof publicKey's[[algorithm]]≠ thenameof key's[[algorithm]]→ InvalidAccessError. - If the
namedCurveof publicKey's[[algorithm]]≠ thenamedCurveof key's[[algorithm]]→ InvalidAccessError.
9.–11. the ECDH primitive, and the truncation of secret.
Steps 4 and 5 measure the ceiling off the public key alone and raise before steps 7 and
8 have established that the two keys are a pair at all. So for a mismatched pair where the
public key is on the narrower curve, the request is refused for its length rather than
for the mismatch — a P-521 base key, a P-256 public key and length: 528 is an
OperationError under a literal reading, not an InvalidAccessError.
The browsers
All of them answer InvalidAccessError. Chromium
(components/webcrypto/algorithms/ecdh.cc), Gecko (dom/crypto/WebCryptoTask.cpp), WebKit
(CryptoAlgorithmECDH::deriveBits) and Node (lib/internal/crypto/diffiehellman.js) all
validate the key roles and the curve equality first and only look at length once secret
exists — several of them have no maximumLength step at all and rely on step 11's
"if the length in bits of secret is less than length".
The WPT rows that pin it
WebCryptoAPI/derive_bits_keys/ecdh_bits.https.any.js builds its mismatched-curve cases in
derive_bits_keys/ecdh.js, pairing each curve with P-256 (or, for P-256 itself, with
P-384), and derive.js's derive() defaults length to 8 * size where size is the
base key's field width. So:
| row | base key | public key | length | step 5 says | asserted |
|---|---|---|---|---|---|
P-384 mismatched curves |
P-384 | P-256 | 384 | OperationError (384 > 256) | InvalidAccessError |
P-521 mismatched curves |
P-521 | P-256 | 528 | OperationError (528 > 256) | InvalidAccessError |
P-256 mismatched curves |
P-256 | P-384 | 256 | passes (256 ≤ 384) | InvalidAccessError |
The first two are unsatisfiable by an implementation that runs step 5 where it is written.
The third is green either way, which is why this is easy to miss. ecdh_bits.https.any.html
is 40/40 in Chrome 152, Firefox 154 and Safari 26.6 on wpt.fyi (master/stable,
2026-08-21), so the tests are not aspirational — the spec is simply describing a different
algorithm from the one that shipped.
Suggested fix
Move steps 4 and 5 to after step 8, so the order becomes 1, 2, 3, 6, 7, 8, then the
domain-parameter/length steps, then the primitive. Nothing else needs to change:
- Every one of steps 2, 3, 6, 7 and 8 is a comparison of already-validated key metadata, so
moving the ceiling past them costs nothing and cannot make a previously-succeeding call fail. - Once step 8 has run, "the EC domain parameters associated with publicKey" and those
associated with key are the same parameters, so the wording of step 4 need not change at
all — only its position. (If you would rather make that explicit, step 4 could equally read
"associated with key" after the move.) - The only observable difference is exactly the one the WPT rows assert: a mismatched pair
now reports the mismatch whatever length asked for, instead of reporting a length that
was never going to be derivable.
A smaller alternative — leaving the steps where they are but deriving maximumLength from
key rather than publicKey — would make the two rows above pass, but it leaves an
OperationError reachable for a pair that step 8 is about to reject anyway (e.g. a P-256
base key, a P-384 public key and length: 264), which is still not what browsers do.
Reordering is the change that matches shipped behaviour in every case.
Found while running the WebCryptoAPI corpus against
Jint's Web Crypto implementation
(sebastienros/jint#3180); Jint has taken the browsers' order and documented the divergence
rather than keep two red WPT rows.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the ECDH “Derive Bits” algorithm linked in the issue, then inspect WebCryptoAPI/derive_bits_keys/ecdh_bits.https.any.js, derive_bits_keys/ecdh.js, and derive.js. Move the domain-parameter and length checks after the key-role and curve-equality checks, and verify that the P-384 and P-521 mismatched-curve rows report InvalidAccessError while the WPT test remains green.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript
- Domain
- cryptography
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 76/100