vercel-labs / vercel-labs/scriptc
--dynamic island crypto lacks X25519 / AES / HKDF / SHA-512, which blocks protocol libraries
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 4.9k
- Forks
- 125
- Avg merge
- 2h 14m
- Merged PRs (30d)
- 95
Description
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:
- X25519 (
createPrivateKey+diffieHellman, or WebCryptoderiveBits) — blocks the handshake outright - AES-GCM via
createCipheriv— blocks all post-handshake transport - HKDF (
hkdfSync) - SHA-512 and HMAC-SHA-512
- Ed25519
sign/verify - 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.
Contributor guide
No contributing guide indexed for this repository
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 packages/runtime/src/scr_island.c and the makeCrypto implementation, then inspect how static crypto lowering and coverage --dynamic classify shimmed APIs. Verify the requested X25519, AES-GCM, and HKDF paths against the Node baseline, and ensure coverage distinguishes implemented APIs from present-but-stubbed ones.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- c, node.js, typescript
- Domain
- compilers, cryptography
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100