Skip to content

Commit

Permalink
feat: replace elliptic with @noble/curves (#1601)
Browse files Browse the repository at this point in the history
  • Loading branch information
nedsalk authored Jan 5, 2024
1 parent f698256 commit 6c82640
Show file tree
Hide file tree
Showing 6 changed files with 56 additions and 65 deletions.
6 changes: 6 additions & 0 deletions .changeset/warm-poems-swim.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@fuel-ts/signer": minor
---

- Stopped exporting `getCurve()` / secp256k1
- Replaced `elliptic` with `@noble/curves`
7 changes: 2 additions & 5 deletions packages/signer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}
1 change: 0 additions & 1 deletion packages/signer/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
export { default as Signer } from './signer';
export * from './signer';
2 changes: 1 addition & 1 deletion packages/signer/src/signer.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { getBytesCopy, sha256 } from 'ethers';

import Signer from './signer';
import { Signer } from './signer';

/**
* @group node
Expand Down
67 changes: 24 additions & 43 deletions packages/signer/src/signer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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);
}

Expand All @@ -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]);
}
Expand All @@ -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)}`;
}

/**
Expand All @@ -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);
}

/**
Expand Down Expand Up @@ -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;
38 changes: 23 additions & 15 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 6c82640

Please sign in to comment.