Skip to content

Commit 17db396

Browse files
authored
Merge pull request #74 from ethereum/noble-crypto
Switch from noble-secp256k1 to noble-curves
2 parents 112e545 + f83552f commit 17db396

File tree

8 files changed

+69
-83
lines changed

8 files changed

+69
-83
lines changed

.github/workflows/nodejs.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ jobs:
99
runs-on: ${{ matrix.os }}
1010
strategy:
1111
matrix:
12-
node: [14, 16, 18]
12+
node: [16, 18, 19]
1313
os: [ubuntu-latest]
1414
steps:
1515
- uses: actions/checkout@v3

README.md

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -167,31 +167,29 @@ console.log(getRandomBytesSync(32));
167167
## secp256k1 curve
168168

169169
```ts
170-
function getPublicKey(privateKey: Uint8Array, isCompressed?: false): Uint8Array;
171-
function getSharedSecret(privateKeyA: Uint8Array, publicKeyB: Uint8Array): Uint8Array;
172-
function sign(msgHash: Uint8Array, privateKey: Uint8Array, opts?: Options): Promise<Uint8Array>;
173-
function signSync(msgHash: Uint8Array, privateKey: Uint8Array, opts?: Options): Uint8Array;
170+
function getPublicKey(privateKey: Uint8Array, isCompressed = true): Uint8Array;
171+
function sign(msgHash: Uint8Array, privateKey: Uint8Array): { r: bigint; s: bigint; recovery: number };
174172
function verify(signature: Uint8Array, msgHash: Uint8Array, publicKey: Uint8Array): boolean
175-
function recoverPublicKey(msgHash: Uint8Array, signature: Uint8Array, recovery: number): Uint8Array | undefined;
173+
function getSharedSecret(privateKeyA: Uint8Array, publicKeyB: Uint8Array): Uint8Array;
176174
function utils.randomPrivateKey(): Uint8Array;
177175
```
178176

179177
The `secp256k1` submodule provides a library for elliptic curve operations on
180-
the curve secp256k1. For detailed documentation, follow [README of `noble-secp256k1`](https://github.com/paulmillr/noble-secp256k1), which the module uses as a backend.
178+
the curve secp256k1. For detailed documentation, follow [README of `noble-curves`](https://github.com/paulmillr/noble-curves), which the module uses as a backend.
181179

182180
secp256k1 private keys need to be cryptographically secure random numbers with
183181
certain characteristics. If this is not the case, the security of secp256k1 is
184182
compromised. We strongly recommend using `utils.randomPrivateKey()` to generate them.
185183

186184
```js
187-
const secp = require("ethereum-cryptography/secp256k1");
185+
const {secp256k1} = require("ethereum-cryptography/secp256k1");
188186
(async () => {
189187
// You pass either a hex string, or Uint8Array
190188
const privateKey = "6b911fd37cdf5c81d4c0adb1ab7fa822ed253ab0ad9aa18d77257c88b29b718e";
191189
const messageHash = "a33321f98e4ff1c283c76998f14f57447545d339b3db534c6d886decb4209f28";
192-
const publicKey = secp.getPublicKey(privateKey);
193-
const signature = await secp.sign(messageHash, privateKey);
194-
const isSigned = secp.verify(signature, messageHash, publicKey);
190+
const publicKey = secp256k1.getPublicKey(privateKey);
191+
const signature = secp256k1.sign(messageHash, privateKey);
192+
const isSigned = secp256k1.verify(signature, messageHash, publicKey);
195193
})();
196194
```
197195

@@ -448,9 +446,15 @@ you found another primitive that is missing.
448446

449447
## Upgrading
450448

451-
Version 1.0 changes from 0.1:
449+
Upgrading from 1.0 to 2.0:
450+
451+
1. `secp256k1` module was changed massively:
452+
before, it was using [noble-secp256k1 1.7](https://github.com/paulmillr/noble-secp256k1);
453+
now it uses safer [noble-curves](https://github.com/paulmillr/noble-curves). Please refer
454+
to [upgrading section from curves README](https://github.com/paulmillr/noble-curves#upgrading).
455+
2. node.js 14 and older support was dropped. Upgrade to node.js 16 or later.
452456

453-
**Same functionality**, all old APIs remain the same except for the breaking changes:
457+
Upgrading from 0.1 to 1.0: **Same functionality**, all old APIs remain the same except for the breaking changes:
454458

455459
1. We return `Uint8Array` from all methods that worked with `Buffer` before.
456460
`Buffer` has never been supported in browsers, while `Uint8Array`s are supported natively in both

package.json

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,10 @@
2424
"*.d.ts"
2525
],
2626
"dependencies": {
27-
"@noble/hashes": "1.2.0",
28-
"@noble/secp256k1": "1.7.1",
29-
"@scure/bip32": "1.1.5",
30-
"@scure/bip39": "1.1.1"
27+
"@noble/curves": "1.0.0",
28+
"@noble/hashes": "1.3.0",
29+
"@scure/bip32": "1.3.0",
30+
"@scure/bip39": "1.2.0"
3131
},
3232
"browser": {
3333
"crypto": false
@@ -53,13 +53,13 @@
5353
"devDependencies": {
5454
"@rollup/plugin-commonjs": "22.0.1",
5555
"@rollup/plugin-node-resolve": "13.3.0",
56-
"@types/estree": "0.0.47",
56+
"@types/estree": "1.0.0",
5757
"@types/mocha": "9.1.1",
58-
"@types/node": "18.0.4",
58+
"@types/node": "18.15.11",
5959
"@typescript-eslint/eslint-plugin": "5.30.6",
6060
"@typescript-eslint/parser": "5.30.6",
6161
"browserify": "17.0.0",
62-
"eslint": "8.19.0",
62+
"eslint": "8.38.0",
6363
"eslint-plugin-prettier": "4.2.1",
6464
"karma": "6.4.0",
6565
"karma-chrome-launcher": "3.1.1",
@@ -72,8 +72,8 @@
7272
"rimraf": "~3.0.2",
7373
"rollup": "2.76.0",
7474
"ts-node": "10.9.1",
75-
"typescript": "4.7.3",
76-
"webpack": "5.73.0",
75+
"typescript": "5.0.2",
76+
"webpack": "5.76.0",
7777
"webpack-cli": "4.10"
7878
},
7979
"keywords": [

src/aes.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
import { crypto } from "@noble/hashes/crypto";
1+
import { crypto as cr } from "@noble/hashes/crypto";
22
import { concatBytes, equalsBytes } from "./utils";
33

4+
const crypto: any = { web: cr };
5+
46
function validateOpt(key: Uint8Array, iv: Uint8Array, mode: string) {
57
if (!mode.startsWith("aes-")) {
68
throw new Error(`AES submodule doesn't support mode ${mode}`);

src/secp256k1-compat.ts

Lines changed: 34 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
import { sha256 } from "@noble/hashes/sha256";
2-
import * as secp from "./secp256k1";
2+
import { mod } from "@noble/curves/abstract/modular";
3+
import { secp256k1 } from "./secp256k1";
34
import { assertBool, assertBytes, hexToBytes, toHex } from "./utils";
45

56
// Use `secp256k1` module directly.
67
// This is a legacy compatibility layer for the npm package `secp256k1` via noble-secp256k1
78

9+
const Point = secp256k1.ProjectivePoint;
10+
811
function hexToNumber(hex: string): bigint {
912
if (typeof hex !== "string") {
1013
throw new TypeError("hexToNumber: expected string, got " + typeof hex);
@@ -17,9 +20,8 @@ const bytesToNumber = (bytes: Uint8Array) => hexToNumber(toHex(bytes));
1720
const numberToHex = (num: number | bigint) =>
1821
num.toString(16).padStart(64, "0");
1922
const numberToBytes = (num: number | bigint) => hexToBytes(numberToHex(num));
20-
const { mod } = secp.utils;
2123

22-
const ORDER = secp.CURVE.n;
24+
const ORDER = secp256k1.CURVE.n;
2325

2426
type Output = Uint8Array | ((len: number) => Uint8Array);
2527
interface Signature {
@@ -44,11 +46,11 @@ function output(
4446

4547
function getSignature(signature: Uint8Array) {
4648
assertBytes(signature, 64);
47-
return secp.Signature.fromCompact(signature);
49+
return secp256k1.Signature.fromCompact(signature);
4850
}
4951

5052
export function createPrivateKeySync(): Uint8Array {
51-
return secp.utils.randomPrivateKey();
53+
return secp256k1.utils.randomPrivateKey();
5254
}
5355

5456
export async function createPrivateKey(): Promise<Uint8Array> {
@@ -57,7 +59,7 @@ export async function createPrivateKey(): Promise<Uint8Array> {
5759

5860
export function privateKeyVerify(privateKey: Uint8Array): boolean {
5961
assertBytes(privateKey, 32);
60-
return secp.utils.isValidPrivateKey(privateKey);
62+
return secp256k1.utils.isValidPrivateKey(privateKey);
6163
}
6264

6365
export function publicKeyCreate(
@@ -67,14 +69,14 @@ export function publicKeyCreate(
6769
): Uint8Array {
6870
assertBytes(privateKey, 32);
6971
assertBool(compressed);
70-
const res = secp.getPublicKey(privateKey, compressed);
72+
const res = secp256k1.getPublicKey(privateKey, compressed);
7173
return output(out, compressed ? 33 : 65, res);
7274
}
7375

7476
export function publicKeyVerify(publicKey: Uint8Array): boolean {
7577
assertBytes(publicKey, 33, 65);
7678
try {
77-
secp.Point.fromHex(publicKey);
79+
Point.fromHex(publicKey);
7880
return true;
7981
} catch (e) {
8082
return false;
@@ -88,7 +90,7 @@ export function publicKeyConvert(
8890
): Uint8Array {
8991
assertBytes(publicKey, 33, 65);
9092
assertBool(compressed);
91-
const res = secp.Point.fromHex(publicKey).toRawBytes(compressed);
93+
const res = Point.fromHex(publicKey).toRawBytes(compressed);
9294
return output(out, compressed ? 33 : 65, res);
9395
}
9496

@@ -110,11 +112,9 @@ export function ecdsaSign(
110112
) {
111113
throw new Error("Secp256k1: noncefn && data is unsupported");
112114
}
113-
const [signature, recid] = secp.signSync(msgHash, privateKey, {
114-
recovered: true,
115-
der: false,
116-
});
117-
return { signature: output(out, 64, signature), recid };
115+
const sig = secp256k1.sign(msgHash, privateKey);
116+
const recid = sig.recovery!;
117+
return { signature: output(out, 64, sig.toCompactRawBytes()), recid };
118118
}
119119

120120
export function ecdsaRecover(
@@ -126,8 +126,8 @@ export function ecdsaRecover(
126126
) {
127127
assertBytes(msgHash, 32);
128128
assertBool(compressed);
129-
const sign = getSignature(signature).toHex();
130-
const point = secp.Point.fromSignature(msgHash, sign, recid);
129+
const sign = getSignature(signature);
130+
const point = sign.addRecoveryBit(recid).recoverPublicKey(msgHash);
131131
return output(out, compressed ? 33 : 65, point.toRawBytes(compressed));
132132
}
133133

@@ -145,14 +145,15 @@ export function ecdsaVerify(
145145
if (r >= ORDER || s >= ORDER) {
146146
throw new Error("Cannot parse signature");
147147
}
148-
const pub = secp.Point.fromHex(publicKey); // should not throw error
148+
const pub = Point.fromHex(publicKey); // can throw error
149+
pub; // typescript
149150
let sig;
150151
try {
151152
sig = getSignature(signature);
152153
} catch (error) {
153154
return false;
154155
}
155-
return secp.verify(sig, msgHash, pub);
156+
return secp256k1.verify(sig, msgHash, publicKey);
156157
}
157158

158159
export function privateKeyTweakAdd(
@@ -195,7 +196,7 @@ export function publicKeyNegate(
195196
) {
196197
assertBytes(publicKey, 33, 65);
197198
assertBool(compressed);
198-
const point = secp.Point.fromHex(publicKey).negate();
199+
const point = Point.fromHex(publicKey).negate();
199200
return output(out, compressed ? 33 : 65, point.toRawBytes(compressed));
200201
}
201202

@@ -214,10 +215,10 @@ export function publicKeyCombine(
214215
}
215216
assertBool(compressed);
216217
const combined = publicKeys
217-
.map((pub) => secp.Point.fromHex(pub))
218-
.reduce((res, curr) => res.add(curr), secp.Point.ZERO);
218+
.map((pub) => Point.fromHex(pub))
219+
.reduce((res, curr) => res.add(curr), Point.ZERO);
219220
// Prohibit returning ZERO point
220-
if (combined.equals(secp.Point.ZERO)) {
221+
if (combined.equals(Point.ZERO)) {
221222
throw new Error("Combined result must not be zero");
222223
}
223224
return output(out, compressed ? 33 : 65, combined.toRawBytes(compressed));
@@ -232,10 +233,10 @@ export function publicKeyTweakAdd(
232233
assertBytes(publicKey, 33, 65);
233234
assertBytes(tweak, 32);
234235
assertBool(compressed);
235-
const p1 = secp.Point.fromHex(publicKey);
236-
const p2 = secp.Point.fromPrivateKey(tweak);
236+
const p1 = Point.fromHex(publicKey);
237+
const p2 = Point.fromPrivateKey(tweak);
237238
const point = p1.add(p2);
238-
if (p2.equals(secp.Point.ZERO) || point.equals(secp.Point.ZERO)) {
239+
if (p2.equals(Point.ZERO) || point.equals(Point.ZERO)) {
239240
throw new Error("Tweak must not be zero");
240241
}
241242
return output(out, compressed ? 33 : 65, point.toRawBytes(compressed));
@@ -257,7 +258,7 @@ export function publicKeyTweakMul(
257258
if (bn <= 1 || bn >= ORDER) {
258259
throw new Error("Tweak is zero or bigger than curve order");
259260
}
260-
const point = secp.Point.fromHex(publicKey).multiply(bn);
261+
const point = Point.fromHex(publicKey).multiply(bn);
261262
return output(out, compressed ? 33 : 65, point.toRawBytes(compressed));
262263
}
263264

@@ -285,8 +286,8 @@ export function signatureExport(
285286
signature: Uint8Array,
286287
out?: Output
287288
): Uint8Array {
288-
const res = getSignature(signature).toRawBytes();
289-
return output(out, 72, getSignature(signature).toRawBytes()).slice(
289+
const res = getSignature(signature).toDERRawBytes();
290+
return output(out, 72, res.slice()).slice(
290291
0,
291292
res.length
292293
);
@@ -297,7 +298,7 @@ export function signatureImport(
297298
out?: Output
298299
): Uint8Array {
299300
assertBytes(signature);
300-
const sig = secp.Signature.fromDER(signature);
301+
const sig = secp256k1.Signature.fromDER(signature);
301302
return output(out, 64, hexToBytes(sig.toCompactHex()));
302303
}
303304

@@ -328,7 +329,7 @@ export function ecdh(
328329
if (options.data !== undefined) {
329330
assertBytes(options.data);
330331
}
331-
const point = secp.Point.fromHex(secp.getSharedSecret(privateKey, publicKey));
332+
const point = Point.fromHex(secp256k1.getSharedSecret(privateKey, publicKey));
332333
if (options.hashfn === undefined) {
333334
return output(out, 32, sha256(point.toRawBytes(true)));
334335
}
@@ -342,10 +343,11 @@ export function ecdh(
342343
assertBytes(options.ybuf, 32);
343344
}
344345
assertBytes(out as Uint8Array, 32);
346+
const { x, y } = point.toAffine();
345347
const xbuf = options.xbuf || new Uint8Array(32);
346-
xbuf.set(numberToBytes(point.x));
348+
xbuf.set(numberToBytes(x));
347349
const ybuf = options.ybuf || new Uint8Array(32);
348-
ybuf.set(numberToBytes(point.y));
350+
ybuf.set(numberToBytes(y));
349351
const hash = options.hashfn(xbuf, ybuf, options.data!);
350352
if (!(hash instanceof Uint8Array) || hash.length !== 32) {
351353
throw new Error("secp256k1.ecdh: invalid options.hashfn output");

src/secp256k1.ts

Lines changed: 1 addition & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1 @@
1-
import { hmac } from "@noble/hashes/hmac";
2-
import { sha256 } from "@noble/hashes/sha256";
3-
import { utils as _utils } from "@noble/secp256k1";
4-
export {
5-
getPublicKey,
6-
sign,
7-
signSync,
8-
verify,
9-
recoverPublicKey,
10-
getSharedSecret,
11-
utils,
12-
CURVE,
13-
Point,
14-
Signature,
15-
schnorr
16-
} from "@noble/secp256k1";
17-
18-
// Enable sync API for noble-secp256k1
19-
_utils.hmacSha256Sync = (key: Uint8Array, ...messages: Uint8Array[]) => {
20-
const h = hmac.create(sha256, key);
21-
messages.forEach(msg => h.update(msg));
22-
return h.digest();
23-
};
1+
export { secp256k1 } from "@noble/curves/secp256k1";

test/test-vectors/hdkey.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import * as secp from "@noble/secp256k1";
1+
import { secp256k1 as secp } from "@noble/curves/secp256k1";
22
import { HARDENED_OFFSET, HDKey } from "../../src/hdkey";
33
import { hexToBytes, toHex } from "../../src/utils";
44
import { deepStrictEqual, throws } from "./assert";

test/test-vectors/secp256k1.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import * as secp from "../../src/secp256k1";
1+
import { secp256k1 } from "../../src/secp256k1";
22
import { deepStrictEqual } from "./assert";
33

44
describe("secp256k1", () => {
@@ -9,8 +9,8 @@ describe("secp256k1", () => {
99
const y = 17482644437196207387910659778872952193236850502325156318830589868678978890912n;
1010
const r = 432420386565659656852420866390673177323n;
1111
const s = 115792089237316195423570985008687907852837564279074904382605163141518161494334n;
12-
const pub = new secp.Point(x, y);
13-
const sig = new secp.Signature(r, s);
14-
deepStrictEqual(secp.verify(sig, msg, pub, { strict: false }), true);
12+
const pub = new secp256k1.ProjectivePoint(x, y, 1n);
13+
const sig = new secp256k1.Signature(r, s);
14+
deepStrictEqual(secp256k1.verify(sig, msg, pub.toRawBytes(), { lowS: false }), true);
1515
});
1616
});

0 commit comments

Comments
 (0)