Skip to content

Drop signing-key.verifying-key; generate-key returns the key pair - #59

Merged
lann merged 1 commit into
mainfrom
signing-key-pair
Jul 28, 2026
Merged

Drop signing-key.verifying-key; generate-key returns the key pair#59
lann merged 1 commit into
mainfrom
signing-key-pair

Conversation

@lann

@lann lann commented Jul 28, 2026

Copy link
Copy Markdown
Collaborator

A signing-key resource promised material a provider may not hold.

Deriving the public half from a private key is not a WebCrypto operation.
Recovering the point from a private-only import is a recognized spec gap
(w3c/webcrypto#356) that engines answer differently, and a keystore-resident
non-extractable key can sign while yielding nothing else. Because
signing-key.verifying-key was infallible by contract, both of those
legitimate key shapes were unservable — the jco host had to fail the whole
import when the derive failed. This is the same reasoning that keeps class D
algorithms out of the in-guest provider and mandated non-approved behavior out
of the WIT: the package should not require behavior a legitimate provider
cannot supply.

The change

  • WIT: remove signature.signing-key.verifying-key. ed25519-sign and
    ecdsa-sign generate-key now return
    result<tuple<signing-key, verifying-key>, error> — every platform hands
    over both halves at generation, so the public key is available exactly where
    it is guaranteed to exist. Importers mint it from the public bytes they hold
    via import-verifying-key. Evolved in 0.1.0 in place, per the precedent of
    the digest.compute return-type change and the ChaCha interface split.
  • A fallible per-algorithm derive remains possible additively (semver-minor)
    if a seed-only-import need ever materializes; removal does not foreclose it,
    while the infallible method did foreclose keystore-resident keys.

Cascading effect on the jco host

Signature private keys were imported and generated platform-extractable: true
solely so derivePublicKey could do its JWK round-trip, which demoted the WIT
extractability gate to a JS boolean. With no derive, the WIT flag is passed
straight to importKey/generateKey, so the platform CryptoKey enforces
non-extractability for signature keys exactly as it already did for HMAC/AES —
and README.md's claim about that becomes true rather than aspirational
(#28's second checklist item, resolved structurally rather than by weakening
the docs).

derivePublicKey and its unattributable error.other lift are deleted
outright. The ECDSA private-only PKCS#8 import is now documented as the
best-effort path it is: it works where the platform cooperates and declines
with invalid-key where it does not, which the WIT permits.

Suite coverage without the derive

  • The Ed25519 RFC 8032 §7.1 TEST 2 probe verifies its signature under the
    vector's public key, which pins that the imported seed and that key are one
    pair — the property the derived-point comparison was there for.
  • Round-trip and metadata probes use the pair returned by generation;
    verifying-key-export-roundtrip exports the generated public half.
  • Case names and feature tags are unchanged, so the suite lockfiles do not move.

Verification

Gate Result
just check (fmt, clippy, WIT, Rust tests) pass
wasmtime conformance (shared + signing) 6226 + 4 cases, 0 failed
composed in-guest conformance 6226 cases, 0 failed
jco-node conformance (shared + signing) 6226 + 4 cases, 0 failed
runner aggregate 18686 results, 0 failed, 0 transport problems
crypto-demo under wasmtime / jco / composed 22 checks passed on each
just transpile, just test-node pass

Two gates could not run locally and are left to CI: jco-browser (no Chrome on
this machine) and test-webcrypto-componentize-wpt — the WPT runner's input
lock hashes wit/*.wit, so any WIT change invalidates it and the
wpt-component job builds a fresh one. The componentize-sdk surface
(HMAC-SHA-256 + AES-256-GCM) is untouched by this change.

Issue tracking

Addresses the second checklist item of #28 (README overclaims platform
enforcement of extractability) at its root. Deliberately does not close
#28: the third item (JWK d-string exposure in SigningKey.exportKey) remains
open. Note that the mint-time half of that exposure is gone with
derivePublicKey — what remains is the per-export d string, addressable
separately by exporting via pkcs8 and parsing the DER.

A `signing-key` promised material a provider may not hold. Deriving the
public half from a private key is not a WebCrypto operation: recovering
the point from a private-only import is an unspecified spec gap
(w3c/webcrypto#356) that engines answer differently, and a
keystore-resident non-extractable key can sign while yielding nothing
else. An infallible derive therefore makes an entire class of legitimate
keys unservable — the same reasoning that keeps class D out of the
in-guest provider and mandated non-approved behavior out of the WIT.

Remove the method. `ed25519-sign`/`ecdsa-sign` `generate-key` now return
`tuple<signing-key, verifying-key>`: every platform hands over both
halves at generation, so the public key is available exactly where it is
guaranteed to exist. Importers mint it from the public bytes they hold
via `import-verifying-key`. A fallible per-algorithm derive stays
possible additively if a seed-only-import need ever materializes.

On the jco host this removes the reason signature private keys were
imported platform-extractable: the WIT flag is now the `CryptoKey`'s own,
so the platform enforces non-extractability and the README's claim about
it becomes true for signature keys, not just HMAC/AES. `derivePublicKey`
and its unattributable `error.other` lift go with it; the ECDSA
private-only PKCS#8 import is documented as the best-effort path it is.

The suites keep their coverage without the derive: the Ed25519 RFC 8032
known answer verifies its signature under the vector's public key
(pinning that seed and key are one pair), and the round-trip probes use
the pair returned by generation.
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.

1 participant