Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Switch from indutny/elliptic to noble-secp256k1. #727

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@
"elliptic": "6.5.4",
"hash.js": "1.1.7",
"js-sha3": "0.5.7",
"noble-secp256k1": "^1.2.8",
"scrypt-js": "3.0.1",
"solc": "0.7.1",
"tiny-inflate": "1.0.3",
Expand Down
4 changes: 3 additions & 1 deletion packages/signing-key/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@
"@ethersproject/bytes": "^5.4.0",
"@ethersproject/logger": "^5.4.0",
"@ethersproject/properties": "^5.4.0",
"@ethersproject/sha2": "^5.4.0",
"bn.js": "^4.11.9",
"elliptic": "6.5.4",
"hash.js": "1.1.7"
"hash.js": "1.1.7",
"noble-secp256k1": "^1.2.8"
},
"description": "Elliptic curve library functions for the secp256k1 curve.",
"ethereum": "donations.ethers.eth",
Expand Down
63 changes: 24 additions & 39 deletions packages/signing-key/src.ts/index.ts
Original file line number Diff line number Diff line change
@@ -1,71 +1,58 @@
"use strict";

import { EC } from "./elliptic";

import * as secp256k1 from "noble-secp256k1";
import { computeHmac, SupportedAlgorithm } from "@ethersproject/sha2";
import { arrayify, BytesLike, hexlify, hexZeroPad, Signature, SignatureLike, splitSignature } from "@ethersproject/bytes";
import { defineReadOnly } from "@ethersproject/properties";

import { Logger } from "@ethersproject/logger";
import { version } from "./_version";
const logger = new Logger(version);

let _curve: EC = null
function getCurve() {
if (!_curve) {
_curve = new EC("secp256k1");
}
return _curve;
// @ts-ignore
secp256k1.utils.hmacSha256 = function(key: Uint8Array, ...messages: Uint8Array[]): Uint8Array {
return arrayify(computeHmac(SupportedAlgorithm.sha256, key, Buffer.concat(messages)));
}

export class SigningKey {

readonly curve: string;

readonly privateKey: string;
readonly publicKey: string;
readonly compressedPublicKey: string;

//readonly address: string;

readonly _isSigningKey: boolean;

constructor(privateKey: BytesLike) {
defineReadOnly(this, "curve", "secp256k1");

defineReadOnly(this, "privateKey", hexlify(privateKey));

const keyPair = getCurve().keyFromPrivate(arrayify(this.privateKey));

defineReadOnly(this, "publicKey", "0x" + keyPair.getPublic(false, "hex"));
defineReadOnly(this, "compressedPublicKey", "0x" + keyPair.getPublic(true, "hex"));

const bytes = arrayify(this.privateKey);
defineReadOnly(this, "publicKey", "0x" + secp256k1.getPublicKey(bytes, false));
defineReadOnly(this, "compressedPublicKey", "0x" + secp256k1.getPublicKey(bytes, true));
defineReadOnly(this, "_isSigningKey", true);
}

_addPoint(other: BytesLike): string {
const p0 = getCurve().keyFromPublic(arrayify(this.publicKey));
const p1 = getCurve().keyFromPublic(arrayify(other));
return "0x" + p0.pub.add(p1.pub).encodeCompressed("hex");
const p0 = secp256k1.Point.fromHex(arrayify(this.publicKey));
const p1 = secp256k1.Point.fromHex(arrayify(other));
return "0x" + p0.add(p1).toHex(true);
}

signDigest(digest: BytesLike): Signature {
const keyPair = getCurve().keyFromPrivate(arrayify(this.privateKey));
const digestBytes = arrayify(digest);
if (digestBytes.length !== 32) {
logger.throwArgumentError("bad digest length", "digest", digest);
}
const signature = keyPair.sign(digestBytes, { canonical: true });
const [sigBytes, recoveryParam] = secp256k1._syncSign(
digestBytes, arrayify(this.privateKey), {canonical: true, recovered: true}
);
const {r, s} = secp256k1.Signature.fromHex(sigBytes);
return splitSignature({
recoveryParam: signature.recoveryParam,
r: hexZeroPad("0x" + signature.r.toString(16), 32),
s: hexZeroPad("0x" + signature.s.toString(16), 32),
recoveryParam: recoveryParam,
r: hexZeroPad("0x" + r.toString(16), 32),
s: hexZeroPad("0x" + s.toString(16), 32),
})
}

computeSharedSecret(otherKey: BytesLike): string {
const keyPair = getCurve().keyFromPrivate(arrayify(this.privateKey));
const otherKeyPair = getCurve().keyFromPublic(arrayify(computePublicKey(otherKey)));
return hexZeroPad("0x" + keyPair.derive(otherKeyPair.getPublic()).toString(16), 32);
return "0x" + secp256k1.getSharedSecret(arrayify(this.privateKey), arrayify(computePublicKey(otherKey)));
}

static isSigningKey(value: any): value is SigningKey {
Expand All @@ -75,8 +62,8 @@ export class SigningKey {

export function recoverPublicKey(digest: BytesLike, signature: SignatureLike): string {
const sig = splitSignature(signature);
const rs = { r: arrayify(sig.r), s: arrayify(sig.s) };
return "0x" + getCurve().recoverPubKey(arrayify(digest), rs, sig.recoveryParam).encode("hex", false);
const rs = new secp256k1.Signature(BigInt(sig.r), BigInt(sig.s));
return "0x" + secp256k1.recoverPublicKey(hexlify(digest), rs.toHex(), sig.recoveryParam);
}

export function computePublicKey(key: BytesLike, compressed?: boolean): string {
Expand All @@ -85,17 +72,15 @@ export function computePublicKey(key: BytesLike, compressed?: boolean): string {
if (bytes.length === 32) {
const signingKey = new SigningKey(bytes);
if (compressed) {
return "0x" + getCurve().keyFromPrivate(bytes).getPublic(true, "hex");
return "0x" + secp256k1.getPublicKey(bytes, true);
}
return signingKey.publicKey;

} else if (bytes.length === 33) {
if (compressed) { return hexlify(bytes); }
return "0x" + getCurve().keyFromPublic(bytes).getPublic(false, "hex");

return "0x" + secp256k1.getPublicKey(bytes, false);
} else if (bytes.length === 65) {
if (!compressed) { return hexlify(bytes); }
return "0x" + getCurve().keyFromPublic(bytes).getPublic(true, "hex");
return "0x" + secp256k1.getPublicKey(bytes, true);
}

return logger.throwArgumentError("invalid public or private key", "key", "[REDACTED]");
Expand Down