Summary
Node exposes WebCrypto both as a global browser-compatible surface and through node:crypto.webcrypto. Perry has substantial node:crypto / crypto.subtle.<method>() lowering support, but the native global object and module namespace shape appear incomplete: globalThis.crypto, Crypto, SubtleCrypto, CryptoKey, and crypto.webcrypto are not exposed as real WebCrypto objects/classes.
This breaks browser-compatible libraries that do feature detection or identity checks before calling WebCrypto, e.g. globalThis.crypto instanceof Crypto, crypto.subtle instanceof SubtleCrypto, key instanceof CryptoKey, or require("node:crypto").webcrypto === globalThis.crypto.
Expected Node behavior
Current Node docs list crypto, Crypto, CryptoKey, and SubtleCrypto as globals, and the Web Crypto docs state that globalThis.crypto is a Crypto singleton. The node:crypto docs also document crypto.webcrypto as a Crypto object.
Docs:
Local Node v25.9.0 probe:
crypto object Crypto
Crypto function Function
Crypto proto constructor,getRandomValues,randomUUID,subtle
SubtleCrypto function Function
SubtleCrypto proto constructor,decapsulateBits,decapsulateKey,decrypt,deriveBits,deriveKey,digest,encapsulateBits,encapsulateKey,encrypt,exportKey,generateKey,getPublicKey,importKey,sign,unwrapKey,verify,wrapKey
CryptoKey function Function
CryptoKey proto algorithm,constructor,extractable,type,usages
crypto instanceof Crypto true
crypto.subtle instanceof SubtleCrypto true
key instanceof CryptoKey true secret true HMAC sign,verify
module.webcrypto type object Crypto
module.webcrypto === globalThis.crypto true
module.webcrypto.subtle instanceof SubtleCrypto true
Current Perry evidence
From origin/main source inspection:
crates/perry-runtime/src/object/global_this.rs populates constructor globals such as TextEncoder, URL, AbortSignal, Blob, Request, and Response, but not Crypto, SubtleCrypto, or CryptoKey. It also creates a special performance global, but no crypto global.
crates/perry-codegen/src/expr/helpers.rs::is_global_this_builtin_name similarly has no crypto, Crypto, SubtleCrypto, or CryptoKey routing for globalThis.<name> reads.
crates/perry-hir/src/lower/lower_expr.rs treats bare crypto as a known global identifier to avoid warnings, but bare global values still lower through the generic GlobalGet(0) sentinel unless a parent call/member pattern recognizes them.
crates/perry-runtime/src/object/native_module.rs materializes crypto.subtle as a sub-namespace for import-style code, but there is no webcrypto property in the crypto module property handling.
crates/perry-api-manifest/src/entries.rs lists only property("crypto", "subtle") for the WebCrypto namespace; there is no property("crypto", "webcrypto"), class("crypto", "Crypto"), class("crypto", "SubtleCrypto"), or global constructor inventory.
docs/api/perry.d.ts declares export const subtle: any in crypto, but no webcrypto, Crypto, SubtleCrypto, or CryptoKey class declarations.
- Existing WebCrypto key material is represented through internal Buffer-backed registry objects, so
instanceof CryptoKey / prototype identity is not currently represented as a Node-compatible class surface.
I could not run a fresh Perry binary locally in this checkout, so this is based on origin/main source, current Node docs, and local Node probes.
Suggested test surface
Add parity cases that check both shape and identity before algorithm details:
import cryptoModule from "node:crypto";
console.log(typeof globalThis.crypto, globalThis.crypto.constructor.name);
console.log(typeof Crypto, typeof SubtleCrypto, typeof CryptoKey);
console.log(globalThis.crypto instanceof Crypto);
console.log(globalThis.crypto.subtle instanceof SubtleCrypto);
console.log(cryptoModule.webcrypto === globalThis.crypto);
console.log(cryptoModule.webcrypto.subtle === globalThis.crypto.subtle);
const key = await crypto.subtle.generateKey({ name: "HMAC", hash: "SHA-256" }, true, ["sign", "verify"]);
console.log(key instanceof CryptoKey, key.type, key.extractable, key.algorithm.name, key.usages.join(","));
const bytes = new Uint8Array(4);
console.log(globalThis.crypto.getRandomValues(bytes) === bytes, bytes.length);
console.log(typeof globalThis.crypto.randomUUID());
Scope / non-goals
This issue is about WebCrypto namespace/class shape, global exposure, and identity/prototype behavior. It should not duplicate algorithm-support work in #2518, KeyObject conversion/class work in #2565, or util.types.isCryptoKey predicate work in #2552. Unsupported or experimental algorithms can still throw Node-shaped errors after the objects/classes themselves exist.
Summary
Node exposes WebCrypto both as a global browser-compatible surface and through
node:crypto.webcrypto. Perry has substantialnode:crypto/crypto.subtle.<method>()lowering support, but the native global object and module namespace shape appear incomplete:globalThis.crypto,Crypto,SubtleCrypto,CryptoKey, andcrypto.webcryptoare not exposed as real WebCrypto objects/classes.This breaks browser-compatible libraries that do feature detection or identity checks before calling WebCrypto, e.g.
globalThis.crypto instanceof Crypto,crypto.subtle instanceof SubtleCrypto,key instanceof CryptoKey, orrequire("node:crypto").webcrypto === globalThis.crypto.Expected Node behavior
Current Node docs list
crypto,Crypto,CryptoKey, andSubtleCryptoas globals, and the Web Crypto docs state thatglobalThis.cryptois aCryptosingleton. Thenode:cryptodocs also documentcrypto.webcryptoas aCryptoobject.Docs:
Local Node v25.9.0 probe:
Current Perry evidence
From
origin/mainsource inspection:crates/perry-runtime/src/object/global_this.rspopulates constructor globals such asTextEncoder,URL,AbortSignal,Blob,Request, andResponse, but notCrypto,SubtleCrypto, orCryptoKey. It also creates a specialperformanceglobal, but nocryptoglobal.crates/perry-codegen/src/expr/helpers.rs::is_global_this_builtin_namesimilarly has nocrypto,Crypto,SubtleCrypto, orCryptoKeyrouting forglobalThis.<name>reads.crates/perry-hir/src/lower/lower_expr.rstreats barecryptoas a known global identifier to avoid warnings, but bare global values still lower through the genericGlobalGet(0)sentinel unless a parent call/member pattern recognizes them.crates/perry-runtime/src/object/native_module.rsmaterializescrypto.subtleas a sub-namespace for import-style code, but there is nowebcryptoproperty in the crypto module property handling.crates/perry-api-manifest/src/entries.rslists onlyproperty("crypto", "subtle")for the WebCrypto namespace; there is noproperty("crypto", "webcrypto"),class("crypto", "Crypto"),class("crypto", "SubtleCrypto"), or global constructor inventory.docs/api/perry.d.tsdeclaresexport const subtle: anyincrypto, but nowebcrypto,Crypto,SubtleCrypto, orCryptoKeyclass declarations.instanceof CryptoKey/ prototype identity is not currently represented as a Node-compatible class surface.I could not run a fresh Perry binary locally in this checkout, so this is based on
origin/mainsource, current Node docs, and local Node probes.Suggested test surface
Add parity cases that check both shape and identity before algorithm details:
Scope / non-goals
This issue is about WebCrypto namespace/class shape, global exposure, and identity/prototype behavior. It should not duplicate algorithm-support work in #2518,
KeyObjectconversion/class work in #2565, orutil.types.isCryptoKeypredicate work in #2552. Unsupported or experimental algorithms can still throw Node-shaped errors after the objects/classes themselves exist.