diff --git a/.changeset/warm-poems-swim.md b/.changeset/warm-poems-swim.md new file mode 100644 index 00000000000..c4462671a88 --- /dev/null +++ b/.changeset/warm-poems-swim.md @@ -0,0 +1,6 @@ +--- +"@fuel-ts/signer": minor +--- + +- Stopped exporting `getCurve()` / secp256k1 +- Replaced `elliptic` with `@noble/curves` diff --git a/packages/signer/package.json b/packages/signer/package.json index 2a9a4c06d1d..13febfad510 100644 --- a/packages/signer/package.json +++ b/packages/signer/package.json @@ -29,10 +29,7 @@ "@fuel-ts/crypto": "workspace:*", "@fuel-ts/hasher": "workspace:*", "@fuel-ts/math": "workspace:*", - "elliptic": "^6.5.4", - "ethers": "^6.7.1" - }, - "devDependencies": { - "@types/elliptic": "^6.4.14" + "ethers": "^6.7.1", + "@noble/curves": "^1.3.0" } } diff --git a/packages/signer/src/index.ts b/packages/signer/src/index.ts index 3eface77c7f..af5d8964d66 100644 --- a/packages/signer/src/index.ts +++ b/packages/signer/src/index.ts @@ -1,2 +1 @@ -export { default as Signer } from './signer'; export * from './signer'; diff --git a/packages/signer/src/signer.test.ts b/packages/signer/src/signer.test.ts index 3ee0da2e312..83f192a240e 100644 --- a/packages/signer/src/signer.test.ts +++ b/packages/signer/src/signer.test.ts @@ -1,6 +1,6 @@ import { getBytesCopy, sha256 } from 'ethers'; -import Signer from './signer'; +import { Signer } from './signer'; /** * @group node diff --git a/packages/signer/src/signer.ts b/packages/signer/src/signer.ts index 364cd10397a..0444a4c1ece 100644 --- a/packages/signer/src/signer.ts +++ b/packages/signer/src/signer.ts @@ -2,24 +2,11 @@ import { Address } from '@fuel-ts/address'; import { randomBytes } from '@fuel-ts/crypto'; import { hash } from '@fuel-ts/hasher'; import { toBytes } from '@fuel-ts/math'; -import * as elliptic from 'elliptic'; -import { hexlify, concat, getBytesCopy } from 'ethers'; +import { secp256k1 } from '@noble/curves/secp256k1'; import type { BytesLike } from 'ethers'; +import { hexlify, concat, getBytesCopy } from 'ethers'; -/* Importing `ec` like this to avoid the 'Requested module is a CommonJS module, - * which may not support all module.exports as named exports' error - * @see https://github.com/FuelLabs/fuels-ts/issues/841 - */ -const { ec: EC } = elliptic; - -/** - * Return elliptic instance with curve secp256k1 - */ -export function getCurve() { - return new EC('secp256k1'); -} - -class Signer { +export class Signer { readonly address: Address; readonly publicKey: string; @@ -42,16 +29,15 @@ class Signer { privateKey = `0x${privateKey}`; } } - // Convert to byte array, normalize private key input allowing it to be BytesLike // like remove 0x prefix and accept array of bytes - const privateKeyBytes = getBytesCopy(privateKey); - const keyPair = getCurve().keyFromPrivate(privateKeyBytes, 'hex'); + const privateKeyBytes = toBytes(privateKey, 32); - // Slice(1) removes the encoding scheme from the public key - this.compressedPublicKey = hexlify(Uint8Array.from(keyPair.getPublic(true, 'array'))); - this.publicKey = hexlify(Uint8Array.from(keyPair.getPublic(false, 'array').slice(1))); this.privateKey = hexlify(privateKeyBytes); + + // Slice(1) removes the encoding scheme from the public key + this.publicKey = hexlify(secp256k1.getPublicKey(privateKeyBytes, false).slice(1)); + this.compressedPublicKey = hexlify(secp256k1.getPublicKey(privateKeyBytes, true)); this.address = Address.fromPublicKey(this.publicKey); } @@ -64,15 +50,13 @@ class Signer { * @returns hashed signature */ sign(data: BytesLike) { - const keyPair = getCurve().keyFromPrivate(getBytesCopy(this.privateKey), 'hex'); - const signature = keyPair.sign(getBytesCopy(data), { - canonical: true, - }); - const r = toBytes(signature.r, 32); - const s = toBytes(signature.s, 32); + const signature = secp256k1.sign(getBytesCopy(data), getBytesCopy(this.privateKey)); + + const r = toBytes(`0x${signature.r.toString(16)}`, 32); + const s = toBytes(`0x${signature.s.toString(16)}`, 32); // add recoveryParam to first s byte - s[0] |= (signature.recoveryParam || 0) << 7; + s[0] |= (signature.recovery || 0) << 7; return concat([r, s]); } @@ -84,11 +68,10 @@ class Signer { * @returns compressed point on the curve */ addPoint(point: BytesLike) { - const p0 = getCurve().keyFromPublic(getBytesCopy(this.compressedPublicKey)); - const p1 = getCurve().keyFromPublic(getBytesCopy(point)); - const result = p0.getPublic().add(p1.getPublic()); - - return hexlify(Uint8Array.from(result.encode('array', true))); + const p0 = secp256k1.ProjectivePoint.fromHex(getBytesCopy(this.compressedPublicKey)); + const p1 = secp256k1.ProjectivePoint.fromHex(getBytesCopy(point)); + const result = p0.add(p1); + return `0x${result.toHex(true)}`; } /** @@ -107,12 +90,12 @@ class Signer { // remove recoveryParam from s first byte s[0] &= 0x7f; - const publicKey = getCurve() - .recoverPubKey(getBytesCopy(data), { r, s }, recoveryParam) - .encode('array', false) - .slice(1); + const sig = new secp256k1.Signature(BigInt(hexlify(r)), BigInt(hexlify(s))).addRecoveryBit( + recoveryParam + ); - return hexlify(Uint8Array.from(publicKey)); + const publicKey = sig.recoverPublicKey(getBytesCopy(data)).toRawBytes(false).slice(1); + return hexlify(publicKey); } /** @@ -143,9 +126,7 @@ class Signer { * @returns extended publicKey */ static extendPublicKey(publicKey: BytesLike) { - const keyPair = getCurve().keyFromPublic(getBytesCopy(publicKey)); - return hexlify(Uint8Array.from(keyPair.getPublic(false, 'array').slice(1))); + const point = secp256k1.ProjectivePoint.fromHex(getBytesCopy(publicKey)); + return hexlify(point.toRawBytes(false).slice(1)); } } - -export default Signer; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 37343747a9a..0677cc27c59 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1104,16 +1104,12 @@ importers: '@fuel-ts/math': specifier: workspace:* version: link:../math - elliptic: - specifier: ^6.5.4 - version: 6.5.4 + '@noble/curves': + specifier: ^1.3.0 + version: 1.3.0 ethers: specifier: ^6.7.1 version: 6.7.1 - devDependencies: - '@types/elliptic': - specifier: ^6.4.14 - version: 6.4.14 packages/transactions: dependencies: @@ -5962,6 +5958,12 @@ packages: '@noble/hashes': 1.3.1 dev: false + /@noble/curves@1.3.0: + resolution: {integrity: sha512-t01iSXPuN+Eqzb4eBX0S5oubSqXbK/xXa1Ne18Hj8f9pStxztHCE2gfboSp/dZRLSqfuLpRK2nDXDK+W9puocA==} + dependencies: + '@noble/hashes': 1.3.3 + dev: false + /@noble/hashes@1.1.2: resolution: {integrity: sha512-KYRCASVTv6aeUi1tsF8/vpyR7zpfs3FUzy2Jqm+MU+LmUKhQ0y2FpfwqkCcxSg2ua4GALJd8k2R76WxwZGbQpA==} @@ -5970,6 +5972,11 @@ packages: engines: {node: '>= 16'} dev: false + /@noble/hashes@1.3.3: + resolution: {integrity: sha512-V7/fPHgl+jsVPXqqeOzT8egNj2iBIVt+ECeMMG8TdcnTikP3oaBtUVqpT/gYCR68aEBJSF+XbYUxStjbFMqIIA==} + engines: {node: '>= 16'} + dev: false + /@noble/secp256k1@1.7.1: resolution: {integrity: sha512-hOUk6AyBFmqVrv7k5WAw/LpszxVbj9gGN4JRkIX52fdFAj1UA61KXmZDvqVEm+pOyec3+fIeZB02LYa/pWOArw==} @@ -8303,14 +8310,14 @@ packages: resolution: {integrity: sha512-osvveYtyzdEVbt3OfwwXFr4P2iVBL5u1Q3q4ONBfDY/UpOuXmOlbgwc1xECEboY8wIays8Yt6onaWMUdUbfl0A==} dependencies: '@noble/curves': 1.1.0 - '@noble/hashes': 1.3.1 + '@noble/hashes': 1.3.3 '@scure/base': 1.1.1 dev: false /@scure/bip39@1.2.1: resolution: {integrity: sha512-Z3/Fsz1yr904dduJD0NpiyRHhRYHdcnyh73FZWiV+/qhWi83wNJ3NWolYqCEN+ZWsUz2TWwajJggcRE9r1zUYg==} dependencies: - '@noble/hashes': 1.3.1 + '@noble/hashes': 1.3.3 '@scure/base': 1.1.1 dev: false @@ -8781,6 +8788,7 @@ packages: resolution: {integrity: sha512-qNrYbZqMx0uJAfKnKclPh+dTwK33KfLHYqtyODwd5HnXOjnkhc4qgn3BrK6RWyGZm5+sIFE7Q7Vz6QQtJB7w7g==} dependencies: '@types/node': 16.18.34 + dev: false /@types/body-parser@1.19.2: resolution: {integrity: sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==} @@ -8812,12 +8820,6 @@ packages: '@types/node': 20.10.5 dev: false - /@types/elliptic@6.4.14: - resolution: {integrity: sha512-z4OBcDAU0GVwDTuwJzQCiL6188QvZMkvoERgcVjq0/mPM8jCfdwZ3x5zQEVoL9WCAru3aG5wl3Z5Ww5wBWn7ZQ==} - dependencies: - '@types/bn.js': 5.1.1 - dev: true - /@types/eslint-scope@3.7.4: resolution: {integrity: sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==} dependencies: @@ -10864,6 +10866,7 @@ packages: /bn.js@4.12.0: resolution: {integrity: sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==} + dev: true /bn.js@5.2.1: resolution: {integrity: sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==} @@ -10941,6 +10944,7 @@ packages: /brorand@1.1.0: resolution: {integrity: sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==} + dev: true /browser-process-hrtime@1.0.0: resolution: {integrity: sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==} @@ -12604,6 +12608,7 @@ packages: inherits: 2.0.4 minimalistic-assert: 1.0.1 minimalistic-crypto-utils: 1.0.1 + dev: true /emittery@0.10.2: resolution: {integrity: sha512-aITqOwnLanpHLNXZJENbOgjUBeHocD+xsSJmNrjovKBW5HbSpW3d1pEls7GFQPUWXiwG9+0P4GtHfEqC/4M0Iw==} @@ -14845,6 +14850,7 @@ packages: dependencies: inherits: 2.0.4 minimalistic-assert: 1.0.1 + dev: true /hasha@5.2.2: resolution: {integrity: sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==} @@ -14878,6 +14884,7 @@ packages: hash.js: 1.1.7 minimalistic-assert: 1.0.1 minimalistic-crypto-utils: 1.0.1 + dev: true /hoopy@0.1.4: resolution: {integrity: sha512-HRcs+2mr52W0K+x8RzcLzuPPmVIKMSv97RGHy0Ea9y/mpcaK+xTrjICA04KAHi4GRzxliNqNJEFYWHghy3rSfQ==} @@ -17234,6 +17241,7 @@ packages: /minimalistic-crypto-utils@1.0.1: resolution: {integrity: sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==} + dev: true /minimatch@3.0.5: resolution: {integrity: sha512-tUpxzX0VAzJHjLu0xUfFv1gwVp9ba3IOuRAVH2EGuRW8a5emA2FlACLqiT/lDVtS1W+TGNwqz3sWaNyLgDJWuw==}