Deno ext/crypto 深度解析:cppgc 对象化与种子随机数

发布时间:2026/9/8 17:24:15

Deno ext/crypto 深度解析:cppgc 对象化与种子随机数 Deno ext/crypto 深度解析:cppgc 对象化与种子随机数【免费下载链接】denoA modern runtime for JavaScript and TypeScript.项目地址: https://gitcode.com/GitHub_Trending/de/deno一、从一个不可复现的测试说起给 Deno 写集成测试时很容易撞上这个痛点:代码里调了crypto.getRandomValues()或crypto.randomUUID(),快照对比第二天必然失败——熵来自操作系统,天然不可复现。更隐蔽的问题是错误断言:await crypto.subtle.sign(...)抛出来的到底是TypeError还是DOMException NotSupportedError,在 WPT(标准兼容性测试)里有硬性要求,写错一个名字整组用例全红。要回答随机数熵到底从哪来、密钥字节在内存的哪个位置、错误名称由谁决定,只能读源码。读完本文,你可以把任意一次crypto.subtle.*调用逐层追踪到具体的 Rust 函数,并说清每个设计点为什么不用另一种实现。二、心智地图:先看 ext/crypto 的组件拓扑deno_crypto这个 crate(见 ext/crypto/Cargo.toml 的description Web Cryptography API implementation for Deno)内部不是一个大文件结构,而是按接口层—操作层—算法层三层切分:各文件职责一句话概括:文件职责ext/crypto/lib.rsdeno_core::extension!声明、CryptoError错误枚举、KeyData、三个同步分派内核sign_key_sync/verify_key_sync/derive_bits_sync、fast_uuid_v4_bytesext/crypto/00_crypto.js刻意做薄的 JS shim:单例惰性铸造、privateCustomInspect、structured-clone 回调、async 转发器ext/crypto/crypto.rsCryptocppgc 类:getRandomValues、randomUUID、批量 UUID opext/crypto/subtle_crypto.rsSubtleCryptocppgc 类,16 个 API 全部落在这里ext/crypto/subtle_sign.rs 等subtle_*.rs每个操作的WebIdlConverter 操作级校验 run()ext/crypto/key_store.rsCryptoKeyHandle,密钥字节的 GC 托管容器ext/crypto/shared.rsRawKeyData枚举:密钥素材的类型表达ext/crypto/digest.rs、ext/crypto/ed25519.rs、ext/crypto/mlkem.rs、ext/crypto/mldsa.rs 等原子算法实现,含 XOF/KMAC 与后量子算法算法能力面由依赖声明直接体现:对称侧aes/aes-gcm/aes-kw/cbc/ctr/ocb3/hmac,非对称侧rsa/p256/p384/p521/curve25519-dalek/x25519-dalek/ecdsa,派生侧aws-lc-rs(HKDF/PBKDF2)/argon2,后量子侧fips203(启用ml-kem-512/ml-kem-768/ml-kem-1024)与fips205(ML-DSA),XOF 侧tiny-keccak(启用k12/kmacfeature)。三、一次 sign() 调用的完整旅程选crypto.subtle.sign(RSA-PSS, key, data)追踪,它穿过每一层设施。第 1 层:JS async 转发器。用户拿到的sign并不是 Rust 方法本身,而是 ext/crypto/00_crypto.js 里makeAsyncForwarder(sign, sign, 3)生成的包装。文件头注释解释了动机:a sync error path would propagate to JS as a synchronousthrow. That breaksassertRejectscallers ... WPTspromise_rejects_domwraps the call infn.call(undefined), which then surfaces the throw asTypeError: Failed to execute call...— a wrong shape compared to the specs rejected promise.也就是说,参数转换错误如果以同步throw冒出,错误形状就不符合规范必须 reject 一个 Promise的约定;转发器把成功与失败路径统一压进 Promise。第 2 层:cppgc 方法。真实实现是 ext/crypto/subtle_crypto.rs 中impl SubtleCrypto上的async fn sign,三个参数分别由SubtleSignParams、SubtleKey、BufferSource这三个WebIdlConverter在主线程、v8 栈上完成转换:算法字典解析、BufferSource字节拷贝(满足规范get a copy of the bytes)、CryptoKey槽位快照。第 3 层:converter 的推迟。ext/crypto/subtle_sign.rs 的WebIdlConverter for SubtleSignParams用canonical_sign_name做不区分大小写的名字归一;查不到的名字不抛错,而是塞进SubtleSignParams::Unknown(name)原样带下去。extract_name_and_obj处理字符串或{ name, ... }两种AlgorithmIdentifier形态;read_required_u32手动实现[EnforceRange] unsigned long语义(拒绝 NaN/负数/超u32::MAX),注释里明确说这不是 ECMAScript 的ToUint32截断。第 4 层:离开 v8 栈。方法体只有一行:spawn_blocking(move || run_sign(algorithm, key, data.0)).await?。签名计算可能很重,直接在事件循环线程算会阻塞所有 I/O;而子线程不能碰 V8 isolate,所以上一层的快照(SubtleKey携带从 handle 读出的RawKeyData副本)是必须的前置条件。第 5 层:操作级校验。ext/crypto/subtle_sign.rs 的run()先做跨算法一致性检查:params.canonical_name() ! key.algorithm_name报InvalidAccessError,key.has_usage(sign)不满足同样InvalidAccessError。RsaPss分支取出 key 上铸造时记录的hash,组好SignArg后进入同步内核。第 6 层:同步分派。ext/crypto/lib.rs 的sign_key_sync按Algorithm变体分派。RsaPss分支:Algorithm::RsaPss { let private_key RsaPrivateKey::from_pkcs1_der(key.data)?; let salt_len args.salt_length .ok_or_else(|| CryptoError::MissingArgumentSaltLength)? as usize; let mut rng OsRng; match args.hash.ok_or_else(|| CryptoError::MissingArgumentHash)? { CryptoHash::Sha256 { let signing_key Pss::new_with_salt::Sha256(salt_len); let hashed Sha256::digest(data); signing_key.sign(Some(mut rng), private_key, hashed)? } ... } .to_vec() }注意let mut rng OsRng:PSS 每次签名随机生成盐,熵源就是操作系统。返回值Vecu8经#[arraybuffer]变成ArrayBuffer,Promise resolve;任何Err沿CryptoError的#[class(...)]属性变成对应名称的 DOMException 被 reject。四、设计决策拆解4.1 密钥字节:为什么从 JS WeakMap 搬到 Rust 侧 GC 对象问题。每个CryptoKey背后是敏感字节(HMAC 密钥、PKCS#8 私钥)。历史实现把字节存在00_crypto.js的 JSWeakMap里,每次加密操作都要把字节序列化过 JS/Rust 边界。现有实现。ext/crypto/key_store.rs 的文档注释完整记录了演进:Historically the key material for everyCryptoKeylived in a JavaScriptWeakMap(KEY_STOREin00_crypto.js) and was serialized and passed to every crypto op. Instead, the key material now lives in Rust inside this cppgc object ... the key material is freed automatically by V8s garbage collector once the handle is collected ... NoFinalizationRegistryor manual bookkeeping is required.CryptoKeyHandle是unsafe impl GarbageCollected的 V8 GC 对象,内部只有data: RawKeyData(见 ext/crypto/shared.rs)。ext/crypto/lib.rs 中KeyData的注释同样留了痕迹:Previously the key bytes were serialized and passed from JavaScript on every operation.代价与取舍。换来三件事:免序列化(每次操作按 handle 查快照)、生命周期免簿记(GC 回收即释放)、JS 层再也接触不到原始字节(不可读、不可改)。代价是 converter 必须把SubtleKey连同密钥快照一起搬进spawn_blocking闭包,密钥在阻塞线程上会短暂多存一份Box[u8](见KeyData { r#type, data: Box[u8] })。另外FromRawKeyData for KeyData里对SeededPrivate直接unreachable!(),注释说明复合密钥(ML-KEM/ML-DSA)不会走到 sign/verify/derive 这条路。4.2 randomUUID:为什么保留两条随机数路径问题。randomUUID()是热点 API,但测试/快照场景又需要可复现的随机流——这两件事的优化方向相反。现有实现。扩展声明里种下开关(ext/crypto/lib.rs):options { maybe_seed: Optionu64 }, state |state, options| { if let Some(seed) options.maybe_seed { state.put(StdRng::seed_from_u64(seed)); } },有 seed 时OpState里放一个确定性StdRng;JS 侧 ext/crypto/00_crypto.js 在铸造Crypto单例时调op_crypto_is_seeded()记下usesSeededRng,随后randomUUID分两条路:批量路(无 seed):op_crypto_random_uuid_batch()一次向thread_rng()要 128×16 字节熵,经 ext/crypto/lib.rs 的fast_uuid_v4_bytes就地置位版本/变体位(bytes[6] (bytes[6] 0x0f) | 0x40)再用HEX_CHARS查表拼出 4608 字节的连续串,JS 端用StringPrototypeSlice按 36 字节切片消费。同文件里的test_fast_uuid_v4_correctness用uuidcrate 对拍,保证手工格式化与标准库逐字节一致。原生路(有 seed):直接走 cppgc 方法random_uuid,每次单发消费 seededStdRng。代价与取舍。批量路省掉 127 次 op 往返和 V8 字符串构造;但 seed 场景必须保留精确的 RNG 调用顺序——文件头注释原话是seeded runtimes use the native method to preserve exact RNG call order——否则确定性流的消费时序一变,所有快照全漂移。另外批量路还有this ! cryptoSingleton的前置判断:别的对象借用该原型方法时退回原生方法,防止误用别的this却消费了单例的缓存。4.3 错误命名:为什么 Unknown 算法名不能在 converter 层报错问题。未注册的算法名(如sign(MyAlgo, ...))规范上要求NotSupportedError,而WebIdlError这个类型硬编码为#[class(type)],在 converter 层抛出必然变成TypeError。现有实现。ext/crypto/subtle_sign.rs 中read_required_hash的注释把这个约束钉得很死:Does NOT validate the name against the known SHA list -- callers must do that at run-time and surface aDOMException NotSupportedError. (TheWebIdlErrortype is hardcoded#[class(type)], so anyNotSupportedErrorraised here would surface as aTypeError, breaking WPTECDSA verification failure due to bad hash namewhich assertserr.name NotSupportedError.)因此 converter 把陌生名字包装成SubtleSignParams::Unknown(name)下传,run()末尾统一not_supported(format!(Algorithm {name} is not supported))。文件尾部的invalid_access/op_error/not_supported三个 helper 用JsErrorBox::new(DOMException*, msg)精确指定异常类名。代价与取舍。每个subtle_*.rs都带一份自己的错误 helper,且名字合法性被推迟到分派期才知道,converter 层看起来不彻底。但换来的是错误名称与 WPT 逐条对齐——在 WebIDL 转换器和规范错误模型之间,这个 crate 选择了让转换器退让。五、边界与兼容工程这个 crate 里边界不是异常分支,而是被标准测试逐条钉住的契约。错误类映射总表(摘自 ext/crypto/lib.rs 的CryptoError):变体JS 侧异常类触发点MissingArgumentHash/MissingArgumentSaltLengthTypeErrorhash/saltLength缺失HKDFLengthTooLargeDOMExceptionOperationErrorHKDFexpand超限DecryptionErrorDOMExceptionOperationErrorAEAD 认证标签失败,消息 decryption error - integrity check failedArrayBufferViewLengthExceededDOMExceptionQuotaExceededErrorgetRandomValues输入超 65536 字节TypedArrayNotIntegerDOMExceptionTypeMismatchError浮点 TypedArray 等,而非 WebIDL 默认的TypeErrorUnsupportedDigestAlgorithmDOMExceptionNotSupportedError未登记哈希名注意get_random_values(ext/crypto/crypto.rs)的注释专门解释了为什么报TypeMismatchError而不是 WebIDL 默认的TypeError:规范把所有非整型参数(含null、DataView、浮点数组)都归到TypeMismatchError。P-521 的左补零。这是最容易漏掉的曲线相关细节。ext/crypto/lib.rs 的 ECDSA 分支:// P-521 field size is 66 bytes; bits2field requires at least // half that (33 bytes). Left-pad shorter hashes to meet the // minimum. let prehash if prehash.len() 33 { let mut padded vec![0u8; 33 - prehash.len()]; padded.extend_from_slice(prehash); padded } else { prehash };用 SHA-1(20 字节)对 P-521 签名时,若不补零,bits2field会直接失败。sign_key_sync与verify_key_sync各有一份相同处理——签名与验签必须走完全一致的规范化,否则会出现自己签的验不过。常量时间的空判定。ECDH 分支(ext/crypto/lib.rsderive_bits_sync)解码对端公钥时:let pk p256::PublicKey::from_encoded_point(point); // pk is a constant time Option. if pk.is_some().into() { pk.unwrap() }from_encoded_point返回的是常量时间Option,若直接match可能通过分支时序泄露点是否有效。这对非正规编码的公钥输入是有意的防御。XOF 参数防回绕。ext/crypto/digest.rs 的read_optional_u8注释:0x101does not wrap to0x01and slip past the callers range check即domainSeparation这类必须是 u8 且落在[0x01, 0x7F]的字段,先按完整数值读出再拒绝 0xFF的输入,堵住先取低 8 位再查范围的绕过。违规统一报InvalidXofParameters(OperationError)。JS 层的形状契约。ext/crypto/00_crypto.js 的applyWebIdlInterfaceShape把三个构造器的length钉成 0、prototype钉成不可写,注释里写明原因:op2 宏生成的 new-target 信号会让length变成 1,而 Web IDL 要求无暴露构造器的接口对象length为 0。makeAsyncForwarder的第三个参数(如unwrapKey为 7、deriveKey为 5)同理,是喂给 idlharness 的必需参数个数。这些看起来无意义的属性赋值,每一个背后都是一条 WebIDL 断言。六、README 与源码的三处差异ext/crypto/README.md 是较早的文档,引用时注意以下偏差。There are no standalone ops 不成立。当前扩展仍注册了两个 ops:ext/crypto/lib.rs 的扩展宏列了op_crypto_random_uuid_batch与op_crypto_is_seeded,紧随其后的注释解释了留任理由:前者给 JS 批量 UUID 快路径补水,后者在铸造单例时一次性确定 seeded 路径。init(Optionu64)入口已分化为三种。README 只写了init(Optionu64),当前仓库有四处真实调用:deno_crypto::deno_crypto::args(options.seed)(runtime/worker.rs 第 636 行)、init(options.seed)(runtime/web_worker.rs 第 569 行)、init(None)(runtime/snapshot_info.rs 第 33 行)与lazy_init()(runtime/snapshot.rs 第 70 行、runtime/worker.rs 第 1222 行)。种子语义未变,只是注入方式按调用方分路。README 的Object.defineProperty示例是嵌入方视角。Deno 本体运行时通过扩展系统注入该 crate,00_crypto.js的return块导出的除了Crypto/CryptoKey/SubtleCrypto和 gettercrypto,还有两个 Node.jsKeyObject互用函数cryptoKeyExportNodeKeyMaterial/importCryptoKeySync(分别转发到CryptoKey.exportNodeMaterial/CryptoKey.importSync),README 完全没有提及。七、代码阅读路径按依赖顺序推进,每步都能独立编译理解:ext/crypto/lib.rs——扩展宏(3 个 objects、2 个 ops、maybe_seed)与CryptoError全枚举,先建立谁是什么异常类的字典;ext/crypto/00_crypto.js——334 行读完 JS 侧全部簿记,重点看getSubtleSingleton/getCryptoSingleton与makeAsyncForwarder的注册表;ext/crypto/subtle_sign.rs 的run()——一个完整操作的converter → 校验 → 分派样板,其余subtle_*.rs结构相同;ext/crypto/key_store.rs ext/crypto/shared.rs——密钥素材的类型系统与 GC 生命周期。两个可以继续深挖的方向:一是RawKeyData::SeededPrivate { seed, private_key }复合素材在 ext/crypto/mlkem.rs / ext/crypto/mldsa.rs 里如何支撑 FIPS 203/204 的派生与导出限制;二是 ext/crypto/digest.rs 底部手写的TurboShakeNode136sponge 如何用 8192 字节树形归约补齐tiny-keccak只有k12feature 留下的 KangarooTwelve-256 缺口。行为边界以 tests/unit/ 下的 webcrypto 系列单测为锚点,读代码时遇到拿不准的语义,先搜对应 WPT 用例名再下结论。【免费下载链接】denoA modern runtime for JavaScript and TypeScript.项目地址: https://gitcode.com/GitHub_Trending/de/deno创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
延伸阅读

更多相关文章

2026/9/8 17:24:15

MediaMTX:一条命令接入多协议直播分发

MediaMTX:一条命令接入多协议直播分发 【免费下载链接】mediamtx Ready-to-use Media-over-QUIC / SRT / WebRTC / RTSP / RTMP / LL-HLS / MPEG-TS / RTP live media server and media proxy that allows to read, publish, proxy, record and playback real-time …

2026/9/8 17:24:15

MCP实操指南:为AI打造即插即用的工具接口

你手上有台AI,但它只会聊天不会干活。想让它查个数据库、调个接口、操作一下文件,它要么一脸茫然,要么就需要你写一堆胶水代码,把数据搬运来搬运去。MCP(Model Context Protocol,模型上下文协议&#xff09…

2026/9/8 17:24:15

端侧YOLO还是云端Flash?一张决策表终结视觉方案选型纠结

先说结论:这两条路根本不是二选一,而是看你手里项目的延迟、带宽、功耗、预算哪个更值钱。方向选错,后面所有优化都是在给错误买单。这篇文章把这套决策思路完整讲清楚,文末那张表可以直接抄。做了几年国产 AI 视觉 SoC 的方案落地…

2026/9/8 18:14:26

国产工业MCU替代避坑指南:从引脚兼容到平台迁移的实战经验

前阵子我帮客户做一颗老料 MCU 的替代,原厂已经放出停产通知,库存价涨到离谱,最后拿到一颗号称“引脚全兼容”的国产工业 MCU。PCB 完全不用改,封装和丝印位置都差不多,我当时真以为把程序烧进去就行。结果第一轮验证就…

2026/9/8 18:14:26

CMSIS-DSP源码审计:从FIR状态缓冲到滤波器实现细节

1. 先说结论:为什么这份源码值得掏出来重读一遍 很多人第一次接触 Arm-CMSIS-DSP,都是从厂商的例程里复制一个 arm_fir_f32 或者 arm_cfft_f32 调用,跑通了就算完。直到某天线上固件出现诡异现象:同一个数组,前一版…

2026/9/8 18:14:26

ESP8285飞控WiFi数传实战:从接线到调参的完整指南

做无人机和飞控的时间一长,你会发现一个特别现实的需求:不管是四旋翼、六旋翼还是固定翼,只要你想在地面站上实时看姿态、改航线、切换飞行模式,就绕不开“飞控数据怎么稳定传到电脑”这个问题。飞控板本身不能天天插着USB线悬在机…

2026/9/8 18:14:26

通过光谱响应函数进行光谱退化的详细教程

通过光谱响应函数进行光谱退化的详细教程 制作高光谱与多光谱融合模拟数据集教程 目录: 简介什么是光谱退化?准备工作 3.1 安装必要的工具3.2 下载和准备数据 具体实现步骤(Python) 4.1 加载数据4.2 裁剪高光谱图像并移除坏波段…

2026/9/8 18:09:26

技术管理者如何建立EMBA选择模型

面对众多项目,真正实用的 EMBA 推荐不是简单列出学校,而是帮助管理者把目标、课程、时间和长期投入放进同一个框架。下面这套方法适合希望稳妥建立候选清单的人,也适合企业在支持核心管理者深造前进行内部讨论。 技术管理者可以像做系统设计一…

2026/9/8 7:15:10

超人会飞不算本事:系统稳定依赖清晰规则与边界设计

开头先不绕弯子。“#斯坦李吐槽dc 所以超人是无缘无故会飞的嘛哈哈哈哈哈哈哈锤哥真是技术人才啊!#雷神 #复联”这类调侃式短标题,第一波冲击力在于它把两个宇宙的角色塞进同一个吐槽箱里,但细想一下就能发现,它真正碰到的根本不是…

2026/9/8 7:15:15

超人VS蜘蛛侠:拆解超级IP的影响力与传播方法论

把“蜘蛛侠 vs 超人”放在 CSDN 上聊,可能很多人第一反应是走错片场了。但如果把这两个角色看成“两个持续运营了 80 多年的文化产品”,你会发现,这场比较本质上是两个不同 IP 策略的长期结果对比:超人赢在定义了整个超级英雄题材…

2026/9/8 7:15:10

基于CNN的调制信号识别:MATLAB实现时频图分类实战

简介:本资源是一套面向通信工程与信号处理方向学习者、研究者的深度学习实践方案,聚焦调制信号自动检测与识别这一典型无线通信任务,解决传统方法依赖人工特征、低信噪比下性能下降等痛点。压缩包共12个文件(10.73MB)&…

2026/9/8 0:01:49

踩多轮坑才跑通|OpenClaw 3.1.0 双平台本地 AI 自动化搭建实操实录

🔹 工具简述 OpenClaw 是一款备受开发者与办公人群青睐的开源本地智能工具,凭借离线本地运行、可视化图形面板、全流程自主任务处理三大核心特点,积累了众多忠实用户。与普通对话类 AI 产品不同,它能够直接调用电脑的软硬件操作权…

2026/9/8 0:01:50

拒绝复杂命令行,Hermes Agent 一键包快速解锁智能办公能力

🔍前言 不少想要体验 Hermes Agent 办公能力的使用者,往往会被复杂的环境配置拦住使用脚步。手动下载匹配依赖、反复调整系统目录、处理命令行持续报错、修复权限异常、补全丢失核心文件等一系列操作,对普通使用者而言门槛较高,很…

2026/9/7 16:23:03

USB Type-C PCB布局分区设计:电源、高速信号与PD协议全攻略

做硬件这行,Type-C接口算是典型的“看着简单,做起来全坑”的东西。光引脚就24个,高低速信号、电源、控制线全部塞在一个小小的连接器里,如果PCB布局不做规划,打样回来基本就是“插上没反应”、“高速掉线”、“静电一打…

2026/9/7 22:46:00

系统编程学习原型如何补齐稳定性边界

系统编程学习原型如何补齐稳定性边界预算有限时&#xff0c;我先优化明显多余的复制&#xff0c;而不是猜测性地换容器。用借用传递只读数据通常就能减少分配&#xff1a; fn parse(line: &str) -> Result<Item, Error> { /* ... */ }用基准确认热点确实在分配&am…

2026/9/7 22:45:59

雨花区哪家财务公司代理记账比较好?

在雨花区&#xff0c;企业处理财税事务常常面临诸多挑战&#xff0c;选择一家靠谱的财务公司至关重要。湖南巨勤财务管理咨询有限公司就是本地正规实体财税服务机构&#xff0c;深耕本地工商财税行业多年&#xff0c;熟悉当地工商局、税务局最新政策与申报流程。主营公司注册、…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
咨询二维码