Skip to content

--dynamic island crypto lacks X25519 / AES / HKDF / SHA-512, which blocks protocol libraries #36

Description

@vinikjkkj

Summary

The island's node:crypto shim carries a hashing-and-random slice only. Packages that do real cryptography import and initialize fine, then throw on the first crypto call — and scriptc coverage --dynamic reports no blockers for such a program, so the failure only surfaces at runtime.

I hit this trying to compile a WhatsApp protocol client (Noise handshake, Signal protocol, AES-GCM transport). It is not a performance question — the handshake cannot start.

Environment

scriptc 0.0.17, Ubuntu 24.04 x86_64, clang 18.1.3, cmake 3.28.3, Node 22.22.2 as differential baseline. Package under test: zapo-js@1.6.2, zero runtime dependencies, Uint8Array throughout (no Buffer).

What works

Measured against Node, byte-identical output:

primitive result
SHA-256 ✅ identical
SHA-1 ✅ identical
MD5 ✅ identical
HMAC-SHA-256 ✅ identical

What throws

primitive error
SHA-512 Digest method not supported
HMAC-SHA-512 Digest method not supported
HKDF crypto.hkdfSync is not available in the scriptc island (the embedded runtime carries the hashing/random slice only)
AES-GCM crypto.createCipheriv is not available in the scriptc island
AES-CBC crypto.createCipheriv is not available in the scriptc island
X25519 ECDH crypto.createPrivateKey is not available in the scriptc island

From packages/runtime/src/scr_island.c, makeCrypto: getHashes() returns ["md5", "sha1", "sha256"]; getCiphers() and getCurves() both return []; 32 APIs are die() stubs, including createCipheriv, createDecipheriv, createPrivateKey, createPublicKey, createSecretKey, diffieHellman, createECDH, sign, verify, hkdf, hkdfSync, scrypt, generateKeyPair.

crypto.subtle is assigned from globalThis.crypto, but the embedded engine does not expose a WebCrypto implementation that covers these, so it is not an alternative route.

Coverage reports no blockers

For a program importing the client:

statements analyzed   4
compile statically    0  (0%)
compile dynamically   4  (100%) (island sites)

embedded npm code imports Node builtins and unresolved specifiers:
  node:crypto           shimmed     (zapo-js)
  node:events           shimmed     (zapo-js)
  ... 11 more builtins ...

builds with --dynamic — no remaining blockers

It builds, the client object constructs correctly, output matches Node — and then it throws on the first createPrivateKey. Suggestion: shimmed currently covers both "fully implemented" and "present but stubbed". A separate marker for the latter would make coverage predictive of what actually runs.

The static surface is narrower still

Probing each API in a plain (non---dynamic) build:

expression static
createHash('sha256').update(x).digest('hex')
createHash('sha1').update(x).digest('hex')
randomBytes(n)
randomUUID()
createHash('sha512') / ('md5') ❌ SC2020
createHmac(...) ❌ SC2020
createCipheriv(...) ❌ SC2020
hkdfSync(...) ❌ SC2020
createPrivateKey ❌ SC2020
generateKeyPairSync('x25519') ❌ SC2020
timingSafeEqual(...) ❌ SC2020
webcrypto ❌ SC2020 — "the WebCrypto object has no lowering — the lowered crypto surface is randomUUID, randomBytes, and the createHash chain"

So the static path has less crypto than the island (no HMAC, no MD5, no SHA-512), which is surprising given static is the default and preferred lane. Also note createHash only lowers as a complete chain — binding the intermediate (const h = createHash('sha256')) fails with 'Hash' is typed by @types/node but has no scriptc lowering yet.

Request

Rough priority order for making protocol clients viable:

  1. X25519 (createPrivateKey + diffieHellman, or WebCrypto deriveBits) — blocks the handshake outright
  2. AES-GCM via createCipheriv — blocks all post-handshake transport
  3. HKDF (hkdfSync)
  4. SHA-512 and HMAC-SHA-512
  5. Ed25519 sign / verify
  6. AES-CBC / AES-CTR

Items 1–3 alone would take this from "cannot connect" to "usable".

The payoff is real for this class of workload. Measured here on pure TS: ~2 ms startup vs Node's ~27 ms, and 2.1 MB RSS vs 47 MB for an equivalent HTTP server. For multi-tenant connection handling that difference is the whole point.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions