Skip to content

spec(10.1.6): the curve is part of the identity, not just an algorithm detail - #13

Open
maoryeh wants to merge 1 commit into
masterfrom
maoryeh/crv-is-part-of-the-identity
Open

spec(10.1.6): the curve is part of the identity, not just an algorithm detail#13
maoryeh wants to merge 1 commit into
masterfrom
maoryeh/crv-is-part-of-the-identity

Conversation

@maoryeh

@maoryeh maoryeh commented Aug 24, 2026

Copy link
Copy Markdown
Collaborator

§10.1.6 makes the RFC 7638 thumbprint the platform identifier, but never requires a verifier to pin crv — and verifying the signature does not imply it. That gap is reachable with an off-the-shelf JOSE library, so the thumbprint is not actually canonical today.

The mechanism

The thumbprint's canonical input for an OKP key is {crv, kty, x}, so crv is inside the digest. Libraries in wide use accept an OKP JWK whose crv is any of Ed25519, Ed448, X25519 or X448 without validating x's length against it, then verify an EdDSA signature against those bytes regardless.

So one key's own bytes, merely re-labelled, yield a different jkt that still verifies against that same header JWK. Reproduced against a real verifier before writing any of this — the same Ed25519 key produced four verifying thumbprints, one per curve label. The new vector carries two of them: 004-proof-issuance-no-ath publishes K1 as UDDReOZl…, and 011-crv-substitution presents the identical x as LSW0kh4c….

Why it matters here specifically

Because the thumbprint is the identifier, this hands one key several identities, and it lands hardest on the trust-on-first-use binding this section leans on:

  • A principal already rejected for claiming a second profile re-presents under another crv and binds cleanly as a first contact.
  • The "MAY flag or rate-limit" signal the section recommends is escaped by editing one header member.
  • X25519/X448 are key-agreement keys besides, which [RFC 8037] §3.1 requires be rejected for signing — so this is key-type confusion as well as an identity break.

What this is not: the cross-key replay defence still holds. An attacker cannot produce a variant whose thumbprint matches a victim's recorded cnf.jkt, so 006 still passes either way. What breaks is identity canonicalization — which is what everything downstream keys off.

Changes

  • A MUST in the Algorithms paragraph: reject a crv other than the one the accepted alg requires, and never treat thumbprints differing only in crv as distinct principals.
  • Folded the curve into step 5 of the normative order rather than adding a step — alg is already checked there, and the two are one decision about the same header.
  • Vector 011-crv-substitution, cited in specification.md. It follows the 006 model the vectors README asks for — a rejection case a plausible implementation would accept: K1's own published bytes labelled crv: X448, with a genuine signature. A library strict enough to refuse at parse passes it too, since the outcome is the same rejection.
  • Defined [RFC 8037] in the reference block, now that §3.1 is cited.

Checks

vectors, refs and schemas all pass, with cryptography installed so signatures are verified for real rather than taken on trust:

vectors: ok (0 known issue(s))
refs:    ok (0 known issue(s))
schemas: ok (0 known issue(s))

authority reports 45 failures, but identically on a clean checkout — it wants a built site/, which a fresh clone doesn't have. Not touched by this change.

One incidental note for whoever owns the tooling: usp_check.py verifies signatures with Ed25519PublicKey.from_public_bytes(jwk["x"]) and ignores crv, which is why this vector's signature checks out. That's defensible for a vector validator — it isn't a conformance verifier — but it does mean the checker cannot catch a curve-confused vector, so the property has to be asserted in the spec text rather than relied on from tooling.

Found while implementing §10.1.6 in usp-impl; the check is in place there.

…m detail

§10.1.6 makes the RFC 7638 thumbprint the platform identifier but never requires verifiers to pin `crv`,
and verifying the signature does not imply it. That gap is reachable with an off-the-shelf JOSE library.

The thumbprint's canonical input for an OKP key is {crv, kty, x}, so `crv` is inside the digest. Libraries in
wide use accept an OKP JWK whose `crv` is any of Ed25519, Ed448, X25519 or X448 without validating `x`'s
length against it, and then verify an EdDSA signature against those bytes regardless. So one key's own bytes,
merely re-labelled, yield a different `jkt` that still verifies against that same header JWK.

Because the thumbprint *is* the identifier, that hands one key several identities. It lands hardest on the
trust-on-first-use binding this section leans on: a principal already rejected for claiming a second profile
re-presents under another `crv` and binds cleanly as a first contact, and the "MAY flag or rate-limit" signal
the section recommends is escaped by editing one header member. X25519 and X448 are key-agreement keys
besides, which RFC 8037 §3.1 requires be rejected for signing.

What this is *not*: the cross-key replay defence still holds, since an attacker cannot produce a variant
whose thumbprint matches a victim's recorded `cnf.jkt`. What breaks is identity canonicalization — which is
what everything downstream keys off.

Changes:

- Added a MUST to the Algorithms paragraph: reject a `crv` other than the one the accepted `alg` requires,
  and never treat thumbprints differing only in `crv` as distinct principals.
- Folded the curve into step 5 of the normative order rather than adding a step, since `alg` is already
  checked there and the two are one decision about the same header.
- Added vector 011-crv-substitution and cited it in specification.md. It follows the 006 model — a rejection
  case a plausible implementation would accept: K1's own published bytes labelled crv: X448, with a genuine
  signature. A library strict enough to refuse at parse passes it too, since the outcome is the same
  rejection.
- Defined [RFC 8037] in the reference block, now that §3.1 is cited.

Found while implementing this section in usp-impl, and reproduced against a real verifier before writing any
of the above: the same Ed25519 key produced four verifying thumbprints, one per curve label.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants