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

发布时间:2026/10/29 19:14:40

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/10/29 13:20:06

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/10/21 16:24:02

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

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

2026/10/21 16:24:07

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

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

2026/10/29 23:20:39

一阶与二阶平顺性:悬架模态设计的核心物理逻辑

1. 为什么“一阶”“二阶”不是数学考试,而是悬架设计的生死线你有没有过这种体验:坐进一辆新车,刚起步就感觉底盘“咯噔”一下,像轮胎碾过井盖边缘;高速过弯时车身明明没侧倾多少,但座椅传来一阵持续的、让…

2026/10/29 23:20:39

VC++直连USB打印机:ZPL RAW通道标签打印实践

简介:面向需要在Windows环境下直接控制斑马GT800打印条形码的VC开发者,这份工程型示例围绕USB连接与ZPL指令展开,适合希望绕开.NET依赖、借助Winspool和设备接口完成底层打印的初中级人员参考,也适合需要理解底层通信细节的开发者…

2026/10/29 23:20:39

YOLO11-Seg在工业管道泄漏检测中的实践与优化

1. 项目概述:当计算机视觉遇上管道安全管道泄漏检测一直是工业安全领域的痛点问题。传统人工巡检方式效率低下且存在盲区,而基于传感器的监测方案又面临高成本和复杂部署的挑战。我们团队基于最新的YOLO11-Seg模型开发的这套检测系统,在多个化…

2026/10/29 23:20:39

8款AI论文工具深度测评:提升科研效率的智能解决方案

1. 项目概述:AI论文工具测评的价值与意义作为一名科研工作者,我深知在继续教育过程中查阅学术论文的痛苦。传统文献检索方式效率低下,而新兴的AI论文工具正在改变这一现状。这次我精选了8款主流AI论文网站,通过为期两周的实际测试…

2026/10/29 23:20:39

NarratoAI全自动影视解说工具:原理与应用指南

1. 项目概述:NarratoAI影视解说全自动工具最近在影视解说领域出现了一款名为NarratoAI的全自动工具,它号称能让零基础用户在10分钟内制作出专业级别的解说视频。作为一个长期从事视频制作的从业者,我对这类工具总是保持高度关注,因…

2026/10/29 23:15:39

2024智能驾驶技术:机器视觉与系统架构演进

1. 智能驾驶技术发展现状与核心挑战2024年智能驾驶领域已经进入技术深水区,各大车企和科技公司的自动驾驶系统正在从L2级辅助驾驶向L3级有条件自动驾驶快速演进。根据最新行业数据显示,全球前装ADAS系统装配率在2024年Q2已达到78.3%,其中自动…

2026/10/29 17:05:52

全国计算机二级C语言真题PDF高效刷题指南

简介:这份PDF是全国计算机二级C语言考试的试题与答案解析,专为备考二级C语言的考生准备。内容涵盖C语言特点与设计理念、程序基本组成单位、main函数执行流程、函数嵌套调用规则、标识符命名规范、整型实型字符型数据类型、八进制十六进制数合法形式、运…

2026/10/29 17:06:02

requests+BeautifulSoup:爬虫抓取h1标题的入门实战指南

简介:一份以实战为导向的Python爬虫入门资源,面向具备基本编程基础、希望快速掌握网络数据抓取的初学者,可解决从零编写简单爬虫的常见困惑。实例以requests库发送HTTP请求、BeautifulSoup解析HTML文档,完整演示从网页中提取 标题…

2026/10/29 17:06:13

递推最小二乘参数辨识MATLAB编程:二阶系统与渐消记忆实现

简介:这是一篇面向控制系统、信号处理与机器学习等领域学习者的最小二乘参数辨识程序说明文档。资源共包含一个docx格式文档,压缩包大小七十八KB,内容围绕递推最小二乘算法和渐消记忆最小二乘算法展开:先构造一个极点位于单位圆内…

2026/10/29 0:03:42

学生选课系统DFD图绘制指南:边界划分与分层校验实战

简介:文档以顶层与第一层DFD图为核心,完整刻画学生选课系统的逻辑模型,面向软件工程课程设计、系统分析学习及毕设前期建模人群。顶层图界定系统边界,外部实体包括管理员、教师和学生三类角色;第一层图进一步细化用户登…

2026/10/29 0:03:42

JIRA敏捷落地:Scrum角色权限、Sprint与燃尽图避坑指南

简介:围绕“基于JIRA的敏捷开发项目管理”这一主题,面向产品负责人、Scrum Master及研发测试成员,梳理Scrum角色分工、迭代流程与JIRA工具配置的对应关系,适合初、中级团队导入敏捷管理时参考。压缩包共1个docx文件,大…

2026/10/29 0:03:42

SEMI S8人机工程验收实战:从条款拆解到现场实测的完整指南

简介:面向半导体设备设计、制造与运维工程师及安全合规人员,这份资料聚焦SEMI标准体系下的设备安全规范,重点收录SEMI S8-0308《半导体制造设备人体工程学工程安全指南》。内容围绕设备设计原则、错误与事故预防、人体工程学适配三大主线展开…

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

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

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