Skip to content

Commit 43ea73b

Browse files
ernestognwAmxx
andauthored
Add and update @noble/curves (#6320)
Co-authored-by: Hadrien Croubois <hadrien.croubois@gmail.com>
1 parent dde766b commit 43ea73b

5 files changed

Lines changed: 88 additions & 30 deletions

File tree

package-lock.json

Lines changed: 67 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
"@changesets/read": "^0.6.0",
5959
"@eslint/compat": "^1.2.1",
6060
"@ethereumjs/mpt": "^10.1.0",
61+
"@noble/curves": "^2.0.1",
6162
"@nomicfoundation/hardhat-chai-matchers": "^2.0.6",
6263
"@nomicfoundation/hardhat-ethers": "^3.0.9",
6364
"@nomicfoundation/hardhat-network-helpers": "^1.0.13",

test/helpers/signers.js

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
const { ethers } = require('ethers');
2-
const { secp256r1 } = require('@noble/curves/p256');
2+
const { p256 } = require('@noble/curves/nist.js');
33
const { generateKeyPairSync, privateEncrypt } = require('crypto');
44

55
// Lightweight version of BaseWallet
@@ -68,15 +68,15 @@ class P256SigningKey {
6868
}
6969

7070
static random() {
71-
return new this(secp256r1.utils.randomPrivateKey());
71+
return new this(p256.utils.randomSecretKey());
7272
}
7373

7474
get privateKey() {
7575
return ethers.hexlify(this.#privateKey);
7676
}
7777

7878
get publicKey() {
79-
const publicKeyBytes = secp256r1.getPublicKey(this.#privateKey, false);
79+
const publicKeyBytes = p256.getPublicKey(this.#privateKey, false);
8080
return {
8181
qx: ethers.hexlify(publicKeyBytes.slice(0x01, 0x21)),
8282
qy: ethers.hexlify(publicKeyBytes.slice(0x21, 0x41)),
@@ -86,12 +86,15 @@ class P256SigningKey {
8686
sign(digest /*: BytesLike*/) /*: ethers.Signature*/ {
8787
ethers.assertArgument(ethers.dataLength(digest) === 32, 'invalid digest length', 'digest', digest);
8888

89-
const sig = secp256r1.sign(ethers.getBytesCopy(digest), ethers.getBytesCopy(this.#privateKey), { lowS: true });
89+
const rawSignature = p256.sign(ethers.getBytes(digest), ethers.getBytes(this.#privateKey), {
90+
prehash: false,
91+
format: 'recovered',
92+
});
9093

9194
return ethers.Signature.from({
92-
r: ethers.toBeHex(sig.r, 32),
93-
s: ethers.toBeHex(sig.s, 32),
94-
v: sig.recovery ? 0x1c : 0x1b,
95+
r: ethers.hexlify(rawSignature.slice(0x01, 0x21)),
96+
s: ethers.hexlify(rawSignature.slice(0x21, 0x41)),
97+
v: rawSignature[0] ? 0x1c : 0x1b,
9598
});
9699
}
97100
}

test/utils/cryptography/ECDSA.test.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
const { ethers } = require('hardhat');
22
const { expect } = require('chai');
33
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
4-
const { secp256k1 } = require('@noble/curves/secp256k1');
4+
const { secp256k1 } = require('@noble/curves/secp256k1.js');
55

66
const TEST_MESSAGE = ethers.id('OpenZeppelin');
77
const WRONG_MESSAGE = ethers.id('Nope');
@@ -245,7 +245,7 @@ describe('ECDSA', function () {
245245

246246
// In ethers v6.15.0+, the library no longer throws 'non-canonical s' error for high-s signatures. This
247247
// assertion verifies we are in fact dealing with a high-s value that the ECDSA library should reject.
248-
expect(ethers.toBigInt(s)).to.be.gt(secp256k1.CURVE.n / 2n);
248+
expect(ethers.toBigInt(s)).to.be.gt(secp256k1.Point.Fn.ORDER / 2n);
249249

250250
await expect(this.mock.$recover(message, highSSignature))
251251
.to.be.revertedWithCustomError(this.mock, 'ECDSAInvalidSignatureS')

test/utils/cryptography/P256.test.js

Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,22 @@
11
const { ethers } = require('hardhat');
22
const { expect } = require('chai');
3-
const { secp256r1 } = require('@noble/curves/p256');
3+
const { p256 } = require('@noble/curves/nist.js');
44
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
55

66
const N = 0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551n;
77

8-
// As in ECDSA, signatures are malleable and the tooling produce both high and low S values.
9-
// We need to ensure that the s value is in the lower half of the order of the curve.
10-
const ensureLowerOrderS = ({ s, recovery, ...rest }) => {
11-
if (s > N / 2n) {
12-
s = N - s;
13-
recovery = 1 - recovery;
14-
}
15-
return { s, recovery, ...rest };
16-
};
17-
188
const prepareSignature = (
19-
privateKey = secp256r1.utils.randomPrivateKey(),
9+
privateKey = p256.utils.randomSecretKey(),
2010
messageHash = ethers.hexlify(ethers.randomBytes(0x20)),
2111
) => {
2212
const publicKey = [
23-
secp256r1.getPublicKey(privateKey, false).slice(0x01, 0x21),
24-
secp256r1.getPublicKey(privateKey, false).slice(0x21, 0x41),
13+
p256.getPublicKey(privateKey, false).slice(0x01, 0x21),
14+
p256.getPublicKey(privateKey, false).slice(0x21, 0x41),
2515
].map(ethers.hexlify);
26-
const { r, s, recovery } = ensureLowerOrderS(secp256r1.sign(messageHash.replace(/0x/, ''), privateKey));
27-
const signature = [r, s].map(v => ethers.toBeHex(v, 0x20));
16+
17+
const rawSignature = p256.sign(ethers.getBytes(messageHash), privateKey, { prehash: false, format: 'recovered' });
18+
const signature = [ethers.hexlify(rawSignature.slice(0x01, 0x21)), ethers.hexlify(rawSignature.slice(0x21, 0x41))];
19+
const recovery = rawSignature[0];
2820

2921
return { privateKey, publicKey, signature, recovery, messageHash };
3022
};

0 commit comments

Comments
 (0)