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, 安全插件, 密码学, 手动系统调用, 数据可视化, 椭圆曲线, 自动化攻击