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

chore: bump @noble/secp256k1 #1486

Closed
wants to merge 10 commits into from
8,732 changes: 2,138 additions & 6,594 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion packages/enr/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
"@libp2p/crypto": "^1.0.17",
"@libp2p/peer-id": "^3.0.2",
"@multiformats/multiaddr": "^12.0.0",
"@noble/secp256k1": "^1.7.1",
"@noble/secp256k1": "^2.0.0",
"@waku/utils": "0.0.10",
"debug": "^4.3.4",
"js-sha3": "^0.8.0"
Expand Down
29 changes: 14 additions & 15 deletions packages/enr/src/crypto.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import * as secp from "@noble/secp256k1";
import {
sign as nobleSign,
ProjectivePoint as Point,
Signature,
verify
} from "@noble/secp256k1";
import { concat } from "@waku/utils/bytes";
import sha3 from "js-sha3";

Expand All @@ -14,14 +19,11 @@ export async function sign(
message: Uint8Array,
privateKey: Uint8Array
): Promise<Uint8Array> {
const [signature, recoveryId] = await secp.sign(message, privateKey, {
recovered: true,
der: false
});
return concat(
[signature, new Uint8Array([recoveryId])],
signature.length + 1
);
const signature = nobleSign(message, privateKey);
return concat([
signature.toCompactRawBytes(),
new Uint8Array([signature.recovery || 0])
]);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we sure that an undefined signature.recovery means the value should be 0?

}

export function keccak256(input: Uint8Array): Uint8Array {
Expand All @@ -32,21 +34,18 @@ export function compressPublicKey(publicKey: Uint8Array): Uint8Array {
if (publicKey.length === 64) {
publicKey = concat([new Uint8Array([4]), publicKey], 65);
}
const point = secp.Point.fromHex(publicKey);
const point = Point.fromHex(publicKey);
return point.toRawBytes(true);
}

/**
* Verify an ECDSA signature.
*/
export function verifySignature(
signature: Uint8Array,
message: Uint8Array | string,
publicKey: Uint8Array
): boolean {
try {
const _signature = secp.Signature.fromCompact(signature.slice(0, 64));
return secp.verify(_signature, message, publicKey);
const _signature = Signature.fromCompact(signature.slice(0, 64));
return verify(_signature, message, publicKey);
} catch {
return false;
}
Expand Down
9 changes: 4 additions & 5 deletions packages/enr/src/v4.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
import * as secp from "@noble/secp256k1";
import { sign as nobleSign, ProjectivePoint as Point } from "@noble/secp256k1";
import type { NodeId } from "@waku/interfaces";
import { bytesToHex } from "@waku/utils/bytes";

import { keccak256 } from "./crypto.js";

export async function sign(
privKey: Uint8Array,
msg: Uint8Array
): Promise<Uint8Array> {
return secp.sign(keccak256(msg), privKey, {
der: false
});
return nobleSign(keccak256(msg), privKey).toCompactRawBytes();
}

export function nodeId(pubKey: Uint8Array): NodeId {
const publicKey = secp.Point.fromHex(pubKey);
const publicKey = Point.fromHex(pubKey);
const uncompressedPubkey = publicKey.toRawBytes(false);

return bytesToHex(keccak256(uncompressedPubkey.slice(1)));
Expand Down
2 changes: 1 addition & 1 deletion packages/message-encryption/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@
"crypto": false
},
"dependencies": {
"@noble/secp256k1": "^1.7.1",
"@noble/secp256k1": "^2.0.0",
"@waku/core": "0.0.22",
"@waku/interfaces": "0.0.17",
"@waku/proto": "0.0.5",
Expand Down
96 changes: 50 additions & 46 deletions packages/message-encryption/src/crypto/ecies.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import * as secp from "@noble/secp256k1";
import { concat, hexToBytes } from "@waku/utils/bytes";

import { getSubtle, randomBytes, sha256 } from "./index.js";
import {
getPublicKey,
getSharedSecret,
etc as secpUtils
} from "@noble/secp256k1";
import { concat } from "@waku/utils/bytes";

import { getSubtle } from "./index.js";
/**
* HKDF as implemented in go-ethereum.
*/
Expand All @@ -15,7 +19,7 @@ function kdf(secret: Uint8Array, outputLength: number): Promise<Uint8Array> {
[counters, secret],
counters.length + secret.length
);
const willBeHashResult = sha256(countersSecret);
const willBeHashResult = secpUtils.hmacSha256Async(countersSecret);
willBeResult = willBeResult.then((result) =>
willBeHashResult.then((hashResult) => {
const _hashResult = new Uint8Array(hashResult);
Expand All @@ -31,61 +35,59 @@ function kdf(secret: Uint8Array, outputLength: number): Promise<Uint8Array> {
return willBeResult;
}

function aesCtrEncrypt(
async function aesCtrEncrypt(
counter: Uint8Array,
key: ArrayBufferLike,
data: ArrayBufferLike
): Promise<Uint8Array> {
return getSubtle()
.importKey("raw", key, "AES-CTR", false, ["encrypt"])
.then((cryptoKey) =>
getSubtle().encrypt(
{ name: "AES-CTR", counter: counter, length: 128 },
cryptoKey,
data
)
)
.then((bytes) => new Uint8Array(bytes));
const cryptoKey = await getSubtle().importKey("raw", key, "AES-CTR", false, [
"encrypt"
]);
const bytes = await getSubtle().encrypt(
{ name: "AES-CTR", counter: counter, length: 128 },
cryptoKey,
data
);
return new Uint8Array(bytes);
}

function aesCtrDecrypt(
async function aesCtrDecrypt(
counter: Uint8Array,
key: ArrayBufferLike,
data: ArrayBufferLike
): Promise<Uint8Array> {
return getSubtle()
.importKey("raw", key, "AES-CTR", false, ["decrypt"])
.then((cryptoKey) =>
getSubtle().decrypt(
{ name: "AES-CTR", counter: counter, length: 128 },
cryptoKey,
data
)
)
.then((bytes) => new Uint8Array(bytes));
const cryptoKey = await getSubtle().importKey("raw", key, "AES-CTR", false, [
"decrypt"
]);
const bytes = await getSubtle().decrypt(
{ name: "AES-CTR", counter: counter, length: 128 },
cryptoKey,
data
);
return new Uint8Array(bytes);
}

function hmacSha256Sign(
async function hmacSha256Sign(
key: ArrayBufferLike,
msg: ArrayBufferLike
): PromiseLike<Uint8Array> {
): Promise<Uint8Array> {
const algorithm = { name: "HMAC", hash: { name: "SHA-256" } };
return getSubtle()
.importKey("raw", key, algorithm, false, ["sign"])
.then((cryptoKey) => getSubtle().sign(algorithm, cryptoKey, msg))
.then((bytes) => new Uint8Array(bytes));
const cryptoKey = await getSubtle().importKey("raw", key, algorithm, false, [
"sign"
]);
const bytes = await getSubtle().sign(algorithm, cryptoKey, msg);
return new Uint8Array(bytes);
}

function hmacSha256Verify(
async function hmacSha256Verify(
key: ArrayBufferLike,
msg: ArrayBufferLike,
sig: ArrayBufferLike
): Promise<boolean> {
const algorithm = { name: "HMAC", hash: { name: "SHA-256" } };
const _key = getSubtle().importKey("raw", key, algorithm, false, ["verify"]);
return _key.then((cryptoKey) =>
getSubtle().verify(algorithm, cryptoKey, sig, msg)
);
const cryptoKey = await _key;
return await getSubtle().verify(algorithm, cryptoKey, sig, msg);
}

/**
Expand All @@ -108,9 +110,11 @@ function derive(privateKeyA: Uint8Array, publicKeyB: Uint8Array): Uint8Array {
} else if (publicKeyB[0] !== 4) {
throw new Error("Bad public key, a valid public key would begin with 4");
} else {
const px = secp.getSharedSecret(privateKeyA, publicKeyB, true);
const privateKeyAHex = secpUtils.bytesToHex(privateKeyA);
const publicKeyBHex = secpUtils.bytesToHex(publicKeyB);
const px = getSharedSecret(privateKeyAHex, publicKeyBHex, true);
// Remove the compression prefix
return new Uint8Array(hexToBytes(px).slice(1));
return new Uint8Array(px.slice(1));
}
}

Expand All @@ -125,21 +129,21 @@ export async function encrypt(
publicKeyTo: Uint8Array,
msg: Uint8Array
): Promise<Uint8Array> {
const ephemPrivateKey = randomBytes(32);
const ephemPrivateKey = secpUtils.randomBytes(32);

const sharedPx = derive(ephemPrivateKey, publicKeyTo);

const hash = await kdf(sharedPx, 32);

const iv = randomBytes(16);
const iv = secpUtils.randomBytes(16);
const encryptionKey = hash.slice(0, 16);
const cipherText = await aesCtrEncrypt(iv, encryptionKey, msg);

const ivCipherText = concat([iv, cipherText], iv.length + cipherText.length);

const macKey = await sha256(hash.slice(16));
const macKey = await secpUtils.hmacSha256Async(hash.slice(16));
const hmac = await hmacSha256Sign(macKey, ivCipherText);
const ephemPublicKey = secp.getPublicKey(ephemPrivateKey, false);
const ephemPublicKey = getPublicKey(ephemPrivateKey, false);

return concat(
[ephemPublicKey, ivCipherText, hmac],
Expand Down Expand Up @@ -181,9 +185,9 @@ export async function decrypt(
// check HMAC
const px = derive(privateKey, ephemPublicKey);
const hash = await kdf(px, 32);
const [encryptionKey, macKey] = await sha256(hash.slice(16)).then(
(macKey) => [hash.slice(0, 16), macKey]
);
const [encryptionKey, macKey] = await secpUtils
.hmacSha256Async(hash.slice(16))
.then((macKey) => [hash.slice(0, 16), macKey]);

if (!(await hmacSha256Verify(macKey, cipherAndIv, msgMac))) {
throw new Error("Incorrect MAC");
Expand Down
51 changes: 27 additions & 24 deletions packages/message-encryption/src/crypto/index.ts
Original file line number Diff line number Diff line change
@@ -1,53 +1,55 @@
import nodeCrypto from "crypto";

import * as secp from "@noble/secp256k1";
import { concat } from "@waku/utils/bytes";
import {
getPublicKey as secpGetPublicKey,
sign as secpSign,
etc as secpUtils
} from "@noble/secp256k1";
import sha3 from "js-sha3";

import { Asymmetric, Symmetric } from "../constants.js";

declare const self: Record<string, any> | undefined;

Check warning on line 12 in packages/message-encryption/src/crypto/index.ts

View workflow job for this annotation

GitHub Actions / check

Unexpected any. Specify a different type

Check warning on line 12 in packages/message-encryption/src/crypto/index.ts

View workflow job for this annotation

GitHub Actions / proto

Unexpected any. Specify a different type
const crypto: { node?: any; web?: any } = {
node: nodeCrypto,
web: typeof self === "object" && "crypto" in self ? self.crypto : undefined
};

// Determine the correct crypto object for the environment
if (typeof self === "object" && "crypto" in self) {
globalThis.crypto = self.crypto; // Browser environment
} else {
globalThis.crypto = nodeCrypto.webcrypto as unknown as Crypto; // Node.js environment
}

export function getSubtle(): SubtleCrypto {
if (crypto.web) {
return crypto.web.subtle;
} else if (crypto.node) {
return crypto.node.webcrypto.subtle;
if (globalThis.crypto && globalThis.crypto.subtle) {
return globalThis.crypto.subtle;
} else {
throw new Error(
"The environment doesn't have Crypto Subtle API (if in the browser, be sure to use to be in a secure context, ie, https)"
);
}
}

export const randomBytes = secp.utils.randomBytes;
export const sha256 = secp.utils.sha256;

/**
* Generate a new private key to be used for asymmetric encryption.
*
* Use {@link getPublicKey} to get the corresponding Public Key.
*/
export function generatePrivateKey(): Uint8Array {
return randomBytes(Asymmetric.keySize);
return secpUtils.randomBytes(Asymmetric.keySize);
}

/**
* Generate a new symmetric key to be used for symmetric encryption.
*/

export function generateSymmetricKey(): Uint8Array {
return randomBytes(Symmetric.keySize);
return secpUtils.randomBytes(Symmetric.keySize);
}

/**
* Return the public key for the given private key, to be used for asymmetric
* encryption.
*/
export const getPublicKey = secp.getPublicKey;
export const getPublicKey = secpGetPublicKey;

/**
* ECDSA Sign a message with the given private key.
Expand All @@ -61,14 +63,15 @@
message: Uint8Array,
privateKey: Uint8Array
): Promise<Uint8Array> {
const [signature, recoveryId] = await secp.sign(message, privateKey, {
recovered: true,
der: false
});
return concat(
[signature, new Uint8Array([recoveryId])],
signature.length + 1
);
const signatureObj = secpSign(message, privateKey);
const signature = signatureObj.toCompactRawBytes();
const recoveryId = signatureObj.recovery;

if (recoveryId === undefined) {
throw new Error("Recovery ID is undefined");
}

return new Uint8Array([...signature, recoveryId]);
}

export function keccak256(input: Uint8Array): Uint8Array {
Expand Down
6 changes: 4 additions & 2 deletions packages/message-encryption/src/crypto/symmetric.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { etc as secpUtils } from "@noble/secp256k1";

import { Symmetric } from "../constants.js";

import { getSubtle, randomBytes } from "./index.js";
import { getSubtle } from "./index.js";

export async function encrypt(
iv: Uint8Array,
Expand Down Expand Up @@ -29,5 +31,5 @@ export async function decrypt(
}

export function generateIv(): Uint8Array {
return randomBytes(Symmetric.ivSize);
return secpUtils.randomBytes(Symmetric.ivSize);
}
Loading
Loading