diff --git a/package.json b/package.json index 65834fe482..07d0508a34 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/packages/signing-key/package.json b/packages/signing-key/package.json index 372f89f4b5..99764e0315 100644 --- a/packages/signing-key/package.json +++ b/packages/signing-key/package.json @@ -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", diff --git a/packages/signing-key/src.ts/index.ts b/packages/signing-key/src.ts/index.ts index 7c4cca8389..f033c06cfa 100644 --- a/packages/signing-key/src.ts/index.ts +++ b/packages/signing-key/src.ts/index.ts @@ -1,7 +1,6 @@ "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"; @@ -9,63 +8,51 @@ 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 { @@ -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 { @@ -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]");