Skip to content

Multikey f-form decoder accepts odd-Y (03) while the JWK path rejects it — inconsistent parity handling #571

Description

@melvincarvalho

Two code paths that resolve "is this Nostr pubkey present as a verification method?" disagree on Y-parity.

Lenient (Multikey f-form). decodeFFormSecp256k1 (src/auth/nostr-keys.js:75-77) accepts both 02 and 03 parity prefixes, then discards the parity byte and returns the bare x-only key:

const parity = rest.slice(0, 2);
if (parity !== '02' && parity !== '03') return null;
return rest.slice(2);   // parity discarded

So a VM publicKeyMultibase: "fe70103<x>" resolves to the same identity as "fe70102<x>" (via findNostrVmInProfile, src/auth/nostr.js:612-614, and extractNostrPubkeysFromProfile, src/auth/nostr-keys.js:98-99).

Strict (JWK). jwkMatchesNostrPubkey (src/auth/nostr.js:646-668) / pubkeyFromValidatedJwk (src/auth/nostr-keys.js:44-55) reconstruct the canonical even-Y point and reject a JWK whose y is odd, with an explicit comment that this is deliberate.

So the same logical key, expressed two ways, has two different validation semantics: the Multikey form of an odd-Y point is accepted (mapped to the even-Y key), but the JWK form of it is rejected.

Not an active auth issue. Every builder emits 02 only (src/keys/provision.js:41,90; src/idp/well-known-did-nostr.js:498), so 03 is not produced today. And NIP-98 verifies the Schnorr signature against the real even-Y pubkey before this lookup, while LWS-CID verifies against the JWK. This is a latent consistency defect, not a bypass.

Resolution is a parity-design call (not picking one here):

  • (a) Make the Multikey decoder strict (reject 03), matching the JWK path. Correct for NIP-98, where a signed Nostr key is BIP-340 x-only (even-Y) by definition.
  • (b) Keep accepting both, and reconcile the JWK path instead — appropriate if decodeFFormSecp256k1 is also meant to parse tweaked / taproot-derived keys, which can land on either parity (cf. the did:nostr spec note that both 02/03 occur because keys may be tweaked).

The right answer depends on whether decodeFFormSecp256k1 is only the NIP-98 matcher (even-Y) or also a general did:nostr-document parser, and should track the did:nostr spec's parity stance.

Metadata

Metadata

Assignees

No one assigned

    Labels

    diddid:nostr method spec + implementation alignment

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions