paulmillr/noble-secp256k1

GitHub: paulmillr/noble-secp256k1

一个极速、零依赖且仅 5KB 的 JavaScript secp256k1 签名与 ECDH 密钥交换密码学库。

Stars: 877 | Forks: 124

# noble-secp256k1 最快速的 5KB JS secp256k1 签名与 ECDH 实现。 - ✍️ [ECDSA](https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm) 签名,兼容 [RFC6979](https://www.rfc-editor.org/rfc/rfc6979) - ➰ Schnorr 签名,兼容 [BIP340](https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki) - 🤝 椭圆曲线 Diffie-Hellman [ECDH](https://en.wikipedia.org/wiki/Elliptic-curve_Diffie–Hellman) - 🔒 支持[对冲签名](https://paulmillr.com/posts/deterministic-signatures/),防范故障攻击 - 🪶 4.94KB (gzipped) - 比同类库小 10 到 25 倍 该模块是 [noble-curves](https://github.com/paulmillr/noble-curves) 的姊妹项目。 如果你需要更小的攻击面与更好的可审计性,请使用 noble-secp256k1。 如果你需要诸如 MSM、DER 编码、自定义点预计算等功能,请切换到 noble-curves(直接替换)。 为了学习目的,我们在 [`test/misc/1kb.min.js`](https://github.com/paulmillr/noble-secp256k1/blob/c38e57d17a2ecfdb9b8a80890a8e1a2cc140aa04/test/misc/1kb.min.js) 中提供了一个 898 字节的库版本, 它是为了文章[学习快速椭圆曲线密码学](https://paulmillr.com/posts/noble-secp256k1-fast-ecc/)而创建的。 ### 该库属于 _noble_ 密码学 - 零依赖或极简依赖 - 高度可读的 TypeScript / JS 代码 - PGP 签名的版本发布和透明的 NPM 构建 - 所有库: [ciphers](https://github.com/paulmillr/noble-ciphers)、 [curves](https://github.com/paulmillr/noble-curves)、 [hashes](https://github.com/paulmillr/noble-hashes)、 [post-quantum](https://github.com/paulmillr/noble-post-quantum)、 5kb [secp256k1](https://github.com/paulmillr/noble-secp256k1) / [ed25519](https://github.com/paulmillr/noble-ed25519) - WASM 版本:[awasm-noble](https://github.com/paulmillr/awasm-noble) - [查看主页](https://paulmillr.com/noble/) 获取阅读资源、文档以及使用 noble 构建的应用 ## 用法 我们支持所有主要平台和运行时。对于 React Native,需要额外的 polyfill:见下文。 ``` import * as secp from '@noble/secp256k1'; (async () => { const { secretKey, publicKey } = secp.keygen(); // const publicKey = secp.getPublicKey(secretKey); const msg = new TextEncoder().encode('hello noble'); const sig = await secp.signAsync(msg, secretKey); const isValid = await secp.verifyAsync(sig, msg, publicKey); })(); // ECDH, key recovery (async () => { const alice = secp.keygen(); const bob = secp.keygen(); const shared = secp.getSharedSecret(alice.secretKey, bob.publicKey); const msg = new TextEncoder().encode('hello noble'); // recovery const sigr = await secp.signAsync(msg, alice.secretKey, { format: 'recovered' }); const publicKey2 = await secp.recoverPublicKeyAsync(sigr, msg); })(); // Schnorr signatures from BIP340 (async () => { const schnorr = secp.schnorr; const { secretKey, publicKey } = schnorr.keygen(); const msg = new TextEncoder().encode('hello noble'); const sig = await schnorr.signAsync(msg, secretKey); const isValid = await schnorr.verifyAsync(sig, msg, publicKey); })(); ``` ### 启用同步方法 默认情况下仅提供异步方法,以保持库零依赖。 要启用同步方法: ``` import * as secp from '@noble/secp256k1'; import { hmac } from '@noble/hashes/hmac.js'; import { sha256 } from '@noble/hashes/sha2.js'; secp.hashes.hmacSha256 = (key, msg) => hmac(sha256, key, msg); secp.hashes.sha256 = sha256; ``` ### React Native:polyfill getRandomValues 和 sha256 React Native 默认不提供安全的 getRandomValues。 这无法从我们这端安全地进行 polyfill,因此需要特定于 RN 的编译时依赖。 ``` import 'react-native-get-random-values'; import * as secp from '@noble/secp256k1'; import { hmac } from '@noble/hashes/hmac.js'; import { sha256 } from '@noble/hashes/sha2.js'; secp.hashes.hmacSha256 = (key, msg) => hmac(sha256, key, msg); secp.hashes.sha256 = sha256; secp.hashes.hmacSha256Async = async (key, msg) => hmac(sha256, key, msg); secp.hashes.sha256Async = async (msg) => sha256(msg); ``` ## API 有 4 个主要方法,它们接受 Uint8Array 类型: * `keygen()` * `getPublicKey(secretKey)` * `sign(messageHash, secretKey)` 和 `signAsync(messageHash, secretKey)` * `verify(signature, messageHash, publicKey)` 和 `verifyAsync(signature, messageHash, publicKey)` ### keygen ``` import * as secp from '@noble/secp256k1'; (async () => { const keys = secp.keygen(); const { secretKey, publicKey } = keys; })(); ``` ### getPublicKey ``` import * as secp from '@noble/secp256k1'; const secretKey = secp.utils.randomSecretKey(); const pubKey33b = secp.getPublicKey(secretKey); // Variants const pubKey65b = secp.getPublicKey(secretKey, false); const pubKeyPoint = secp.Point.fromBytes(pubKey65b); const samePoint = pubKeyPoint.toBytes(); ``` 从 32 字节的私钥生成 33 字节的压缩公钥(默认)或 65 字节的公钥。 ### sign ``` import * as secp from '@noble/secp256k1'; import { hmac } from '@noble/hashes/hmac.js'; import { sha256 } from '@noble/hashes/sha2.js'; import { keccak_256 } from '@noble/hashes/sha3.js'; secp.hashes.hmacSha256 = (key, msg) => hmac(sha256, key, msg); secp.hashes.sha256 = sha256; const { secretKey } = secp.keygen(); const msg = new TextEncoder().encode('hello noble'); const sig = secp.sign(msg, secretKey); // async const sigB = await secp.signAsync(msg, secretKey); // recovered, allows `recoverPublicKey(sigR, msg)` const sigR = secp.sign(msg, secretKey, { format: 'recovered' }); const sigH = secp.sign(keccak_256(msg), secretKey, { prehash: false }); // hedged sig const sigC = secp.sign(msg, secretKey, { extraEntropy: true }); const sigC2 = secp.sign(msg, secretKey, { extraEntropy: Uint8Array.from([0xca, 0xfe]) }); // malleable sig const sigD = secp.sign(msg, secretKey, { lowS: false }); ``` 生成 low-s 确定性 k RFC6979 ECDSA 签名。 - 消息将使用 sha256 进行哈希处理。如果你想使用不同的哈希函数, 请确保使用 `{ prehash: false }`。 - `extraEntropy: true` 启用对冲签名。它们在 RFC6979 中引入了 额外的随机性(在第 3.6 节中描述), 以提供针对故障攻击的额外保护。 查看博客文章[确定性签名不是你的朋友](https://paulmillr.com/posts/deterministic-signatures/)。 即使它们的 RNG 发生故障,它们也会回退到确定性模式。 - 默认行为 `lowS: true` 禁止具有 (sig.s >= CURVE.n/2n) 的签名,并且兼容 BTC/ETH。设置 `lowS: false` 允许创建可塑性签名,这是默认的 openssl 行为。非可塑性签名仍然可以在 openssl 中成功验证。 ### verify ``` import * as secp from '@noble/secp256k1'; import { hmac } from '@noble/hashes/hmac.js'; import { sha256 } from '@noble/hashes/sha2.js'; import { keccak_256 } from '@noble/hashes/sha3.js'; secp.hashes.hmacSha256 = (key, msg) => hmac(sha256, key, msg); secp.hashes.sha256 = sha256; const { secretKey, publicKey } = secp.keygen(); const msg = new TextEncoder().encode('hello noble'); const sig = secp.sign(msg, secretKey); const isValid = secp.verify(sig, msg, publicKey); const sigH = secp.sign(keccak_256(msg), secretKey, { prehash: false }); ``` 验证 ECDSA 签名。 - 消息将使用 sha256 进行哈希处理。如果你想使用不同的哈希函数, 请确保使用 `{ prehash: false }`。 - 默认行为 `lowS: true` 禁止具有 (`sig.s >= CURVE.n/2n`) 的可塑性签名,并且 兼容 BTC / ETH。 设置 `lowS: false` 允许创建签名,这是默认的 openssl 行为。 ### getSharedSecret ``` import * as secp from '@noble/secp256k1'; const alice = secp.keygen(); const bob = secp.keygen(); const shared33b = secp.getSharedSecret(alice.secretKey, bob.publicKey); const shared65b = secp.getSharedSecret(bob.secretKey, alice.publicKey, false); const sharedPoint = secp.Point.fromBytes(bob.publicKey).multiply( secp.etc.secretKeyToScalar(alice.secretKey) ); ``` 计算密钥 A 与不同密钥 B 之间的 ECDH (椭圆曲线 Diffie-Hellman) 共享密钥。 ### recoverPublicKey ``` import * as secp from '@noble/secp256k1'; import { hmac } from '@noble/hashes/hmac.js'; import { sha256 } from '@noble/hashes/sha2.js'; import { keccak_256 } from '@noble/hashes/sha3.js'; secp.hashes.hmacSha256 = (key, msg) => hmac(sha256, key, msg); secp.hashes.sha256 = sha256; const { secretKey, publicKey } = secp.keygen(); const msg = new TextEncoder().encode('hello noble'); const sigR = secp.sign(msg, secretKey, { format: 'recovered' }); const publicKey2 = secp.recoverPublicKey(sigR, msg); const sigRH = secp.sign(keccak_256(msg), secretKey, { format: 'recovered', prehash: false }); const publicKeyH = secp.recoverPublicKey(sigRH, keccak_256(msg), { prehash: false }); ``` 从设置了 `recovery` 位的 Signature 实例中恢复公钥。 ### schnorr ``` import * as secp from '@noble/secp256k1'; import { schnorr } from '@noble/secp256k1'; import { hmac } from '@noble/hashes/hmac.js'; import { sha256 } from '@noble/hashes/sha2.js'; secp.hashes.hmacSha256 = (key, msg) => hmac(sha256, key, msg); secp.hashes.sha256 = sha256; const { secretKey, publicKey } = schnorr.keygen(); const msg = new TextEncoder().encode('hello noble'); const sig = schnorr.sign(msg, secretKey); const isValid = schnorr.verify(sig, msg, publicKey); ``` 异步方法无需额外设置即可工作: ``` import { schnorr } from '@noble/secp256k1'; const { secretKey, publicKey } = schnorr.keygen(); const msg = new TextEncoder().encode('hello noble'); const sigA = await schnorr.signAsync(msg, secretKey); const isValidA = await schnorr.verifyAsync(sigA, msg, publicKey); ``` 支持 兼容 [BIP340](https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki) 的 Schnorr 签名。 ### utils 同时还暴露了一堆有用的**实用工具**: ``` import * as secp from '@noble/secp256k1'; const { bytesToHex, hexToBytes, concatBytes, mod, invert, randomBytes } = secp.etc; const { isValidSecretKey, isValidPublicKey, randomSecretKey } = secp.utils; const { Point } = secp; console.log(Point.CURVE(), Point.BASE); /* class Point { static BASE: Point; static ZERO: Point; readonly X: bigint; readonly Y: bigint; readonly Z: bigint; constructor(X: bigint, Y: bigint, Z: bigint); static CURVE(): WeierstrassOpts; static fromAffine(ap: AffinePoint): Point; static fromBytes(bytes: Bytes): Point; static fromHex(hex: string): Point; get x(): bigint; get y(): bigint; equals(other: Point): boolean; is0(): boolean; negate(): Point; double(): Point; add(other: Point): Point; subtract(other: Point): Point; multiply(n: bigint): Point; multiplyUnsafe(scalar: bigint): Point; toAffine(): AffinePoint; assertValidity(): Point; toBytes(isCompressed?: boolean): Bytes; toHex(isCompressed?: boolean): string; } */ ``` ## 安全性 该模块已达到生产就绪状态。 我们与姊妹项目 [noble-curves](https://github.com/paulmillr/noble-curves) 进行了交叉测试,该项目已经过审计并提供了更高的安全性。 - 当前版本尚未经过独立审计。它是 v1 的重写版,v1 曾在 2021 年 4 月由 cure53 进行过审计: [PDF](https://cure53.de/pentest-report_noble-lib.pdf)(由 [Umbra.cash](https://umbra.cash) 和社区资助)。 - 它正在[单独的存储库](https://github.com/paulmillr/fuzzing)中进行模糊测试 ### 恒定时间 我们的目标是算法层面的恒定时间。_JIT 编译器_ 和 _垃圾回收器_ 使得在脚本语言中 实现能够抵抗[时序攻击](https://en.wikipedia.org/wiki/Timing_attack)的“恒定时间” 变得极其困难。这意味着 _任何其他 JS 库都无法保证 恒定时间_。即使是静态类型的 Rust,一种没有 GC 的语言, [在某些情况下也很难实现恒定时间](https://www.chosenplaintext.ca/open-source/rust-timing-shield/security)。 如果你的目标是绝对安全,请不要使用任何 JS 库——包括对原生库的绑定。 请使用底层库和语言。 ### 供应链安全 - **提交** 使用 PGP 密钥签名以防伪造。请务必验证提交签名 - **发布** 通过无 token 的 GitHub CI 和 Trusted Publishing 透明地进行。请务必验证[出处日志](https://docs.npmjs.com/generating-provenance-statements)以确保真实性。 - 实行**低频发布**,以最大程度减少最终用户重新审计的需要。 - **依赖** 被最小化并严格锁定,以降低供应链风险。 - 我们使用尽可能少的依赖。 - 锁定版本范围,并使用 npm-diff 检查更改。 - **开发依赖** 被排除在最终用户安装之外;它们仅用于开发和构建步骤。 对于这个包,有 0 个依赖;以及一些开发依赖: - [noble-hashes](https://github.com/paulmillr/noble-hashes) 提供加密哈希功能 - jsbt 用于基准测试/测试/构建工具,由同一作者开发 - prettier、fast-check 和 typescript 用于代码质量/测试生成/ts 编译 ### 随机性 我们依赖于内置的 [`crypto.getRandomValues`](https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues), 这被认为是加密安全的 PRNG。 浏览器过去曾出现过弱点——并且有可能再次出现——但在用户空间实现 CSPRNG 会更糟,因为用户空间没有可靠的高质量熵源。 ### 量子计算机 如果建造出具有密码学相关性的量子计算机,将允许使用 Shor 算法 破解椭圆曲线密码学(ECDSA / EdDSA 和 ECDH)。 考虑切换到更新/混合的算法,例如 SPHINCS+。它们可在 [noble-post-quantum](https://github.com/paulmillr/noble-post-quantum) 中找到。 NIST 禁止在[2035年之后](https://nvlpubs.nist.gov/nistpubs/ir/2024/NIST.IR.8547.ipd.pdf)使用经典密码学(RSA、DSA、ECDSA、ECDH)。澳大利亚 ASD 禁止在[2030年之后](https://www.cyber.gov.au/resources-business-and-government/essential-cyber-security/ism/cyber-security-guidelines/guidelines-cryptography)使用。 ## 升级 ### v2 到 v3 v3 使该软件包更接近 noble-curves v2。 - 添加了 Schnorr 签名 - 大多数方法现在期望 Uint8Array,禁止输入字符串十六进制 - 添加了 `keygen`、`keygenAsync` 方法 - sign、verify:切换为**预哈希消息**。这些方法现在期望未哈希的 message,而不是 messageHash。 要恢复旧行为,请使用选项 `{prehash: false}` - sign、verify:默认切换为 **Uint8Array 签名**(格式:'compact')。 - verify:**必须在 `{format: 'der'}` 中显式指定 der 格式**。 这减少了可塑性 - verify:**禁止 Signature 实例**签名。用户现在必须始终执行 `signature.toBytes()` - Node v20.19 现在是最低要求版本 - 类型方面的各种小改动 - 等:哈希现在设置在 `hashes` 对象中。此外,对于 `prehash: true`,现在也需要设置 sha256: ``` import { etc, hashes } from '@noble/secp256k1'; import { hmac } from '@noble/hashes/hmac.js'; import { sha256 } from '@noble/hashes/sha2.js'; // before etc.hmacSha256Sync = (key, ...messages) => hmac(sha256, key, etc.concatBytes(...messages)); etc.hmacSha256Async = (key, ...messages) => Promise.resolve(etc.hmacSha256Sync(key, ...messages)); // after hashes.hmacSha256 = (key, msg) => hmac(sha256, key, msg); hashes.sha256 = sha256; hashes.hmacSha256Async = async (key, msg) => hmac(sha256, key, msg); hashes.sha256Async = async (msg) => sha256(msg); ``` ### v1 到 v2 v2 提高了安全性并减少了攻击面。 v2 的目标是提供尽可能最小、安全且快速的 JS 库。 - 禁用某些功能以确保比 v1 小 4 倍,达到 5KB 的包大小: - 这些功能现在是 [noble-curves](https://github.com/paulmillr/noble-curves) 的一部分, **如果你需要它们,请切换到 curves**。Curves 是直接替换的替代品。 - DER 编码:toDERHex、toDERRawBytes、DER 签名的签名/验证 - Schnorr 签名 - 支持不支持 bigint 字面量的环境 - Common.js 支持 - 支持没有 [shim](#usage) 的 node.js 18 及更早版本 - 对非基点使用 `utils.precompute()` - `getPublicKey` - 现在默认生成 33 字节的压缩签名 - 要使用以前生成 65 字节未压缩密钥的行为,请将 参数 `isCompressed` 设置为 `false`:`getPublicKey(priv, false)` - `sign` - 现在是同步的;使用 `signAsync` 获取异步版本 - 现在返回具有 `{ r, s, recovery }` 属性的 `Signature` 实例 - `canonical` 选项被重命名为 `lowS` - `recovered` 选项已被移除,因为现在总是返回恢复位 - `der` 选项已被移除。有两个选项: 1. 使用紧凑编码:`fromCompact`、`toBytes`、`toCompactHex`。 紧凑编码仅仅是 32 字节的 r 和 32 字节的 s 的拼接。 2. 如果你必须使用 DER 编码,请切换到 noble-curves(见上文)。 - `verify` - `strict` 选项被重命名为 `lowS` - `getSharedSecret` - 现在默认生成 33 字节的压缩签名 - 要使用以前生成 65 字节未压缩密钥的行为,请将 参数 `isCompressed` 设置为 `false`:`getSharedSecret(a, b, false)` - `recoverPublicKey(msg, sig, rec)` 更改为 `sig.recoverPublicKey(msg)` - 私钥的 `number` 类型已被移除:改用 `bigint` - `Point` (2d xy) 更改为 `ProjectivePoint` (3d xyz) - `utils` 被拆分为 `utils`(与 noble-curves 中的 API 相同)和 `etc`(`hmacSha256Sync` 及其他) ## 速度 ``` npm run bench ``` 基准测试使用 Apple M4 进行测量。[noble-curves](https://github.com/paulmillr/noble-curves) 可实现更快的性能。 ``` keygen x 7,267 ops/sec @ 137μs/op sign x 6,888 ops/sec @ 145μs/op verify x 788 ops/sec @ 1ms/op getSharedSecret x 654 ops/sec @ 1ms/op recoverPublicKey x 766 ops/sec @ 1ms/op signAsync x 4,353 ops/sec @ 229μs/op verifyAsync x 773 ops/sec @ 1ms/op Point.fromBytes x 13,322 ops/sec @ 75μs/op ``` ## 许可证 The MIT License (MIT) Copyright (c) 2019 Paul Miller [(https://paulmillr.com)](https://paulmillr.com) 请参阅 LICENSE 文件。
标签:CMS安全, ECDH, ECDSA, JavaScript, secp256k1, TypeScript, 安全插件, 密码学, 手动系统调用, 数据可视化, 椭圆曲线, 自动化攻击