diff --git a/packages/enr/src/crypto.ts b/packages/enr/src/crypto.ts index b0846f8401..95cde87dbb 100644 --- a/packages/enr/src/crypto.ts +++ b/packages/enr/src/crypto.ts @@ -1,27 +1,21 @@ -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"; -/** - * ECDSA Sign a message with the given private key. - * - * @param message The message to sign, usually a hash. - * @param privateKey The ECDSA private key to use to sign the message. - * - * @returns The signature and the recovery id concatenated. - */ export async function sign( message: Uint8Array, privateKey: Uint8Array ): Promise { - 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]) + ]); } export function keccak256(input: Uint8Array): Uint8Array { @@ -32,21 +26,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; } diff --git a/packages/enr/src/v4.ts b/packages/enr/src/v4.ts index 4d13bf9b9e..b1d52c7c0b 100644 --- a/packages/enr/src/v4.ts +++ b/packages/enr/src/v4.ts @@ -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 { - 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))); diff --git a/packages/message-encryption/src/crypto/ecies.ts b/packages/message-encryption/src/crypto/ecies.ts index d8f16557b2..4da26edb92 100644 --- a/packages/message-encryption/src/crypto/ecies.ts +++ b/packages/message-encryption/src/crypto/ecies.ts @@ -1,7 +1,11 @@ -import * as secp from "@noble/secp256k1"; -import { concat, hexToBytes } from "@waku/utils/bytes"; +import { + getPublicKey, + getSharedSecret, + etc as secpUtils +} from "@noble/secp256k1"; +import { concat } from "@waku/utils/bytes"; -import { getSubtle, randomBytes, sha256 } from "./index.js"; +import { getSubtle } from "./index.js"; /** * HKDF as implemented in go-ethereum. */ @@ -15,7 +19,7 @@ function kdf(secret: Uint8Array, outputLength: number): Promise { [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); @@ -31,61 +35,59 @@ function kdf(secret: Uint8Array, outputLength: number): Promise { return willBeResult; } -function aesCtrEncrypt( +async function aesCtrEncrypt( counter: Uint8Array, key: ArrayBufferLike, data: ArrayBufferLike ): Promise { - 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 { - 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 { +): Promise { 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 { 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); } /** @@ -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)); } } @@ -125,21 +129,21 @@ export async function encrypt( publicKeyTo: Uint8Array, msg: Uint8Array ): Promise { - 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], @@ -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"); diff --git a/packages/message-encryption/src/crypto/index.ts b/packages/message-encryption/src/crypto/index.ts index 78e23d7774..e65d62be80 100644 --- a/packages/message-encryption/src/crypto/index.ts +++ b/packages/message-encryption/src/crypto/index.ts @@ -1,7 +1,10 @@ import nodeCrypto from "crypto"; -import * as secp from "@noble/secp256k1"; -import { concat } from "@waku/utils/bytes"; +import { + getPublicKey, + sign as secpSign, + etc as secpUtils +} from "@noble/secp256k1"; import sha3 from "js-sha3"; import { Asymmetric, Symmetric } from "../constants.js"; @@ -24,30 +27,28 @@ export function getSubtle(): SubtleCrypto { } } -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 { getPublicKey }; /** * ECDSA Sign a message with the given private key. @@ -61,14 +62,15 @@ export async function sign( message: Uint8Array, privateKey: Uint8Array ): Promise { - 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 { diff --git a/packages/message-encryption/src/crypto/symmetric.ts b/packages/message-encryption/src/crypto/symmetric.ts index 35f69f4558..0d7107dec1 100644 --- a/packages/message-encryption/src/crypto/symmetric.ts +++ b/packages/message-encryption/src/crypto/symmetric.ts @@ -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, @@ -29,5 +31,5 @@ export async function decrypt( } export function generateIv(): Uint8Array { - return randomBytes(Symmetric.ivSize); + return secpUtils.randomBytes(Symmetric.ivSize); } diff --git a/packages/message-encryption/src/waku_payload.ts b/packages/message-encryption/src/waku_payload.ts index d68ec2a8d2..d5a0fc247d 100644 --- a/packages/message-encryption/src/waku_payload.ts +++ b/packages/message-encryption/src/waku_payload.ts @@ -1,9 +1,10 @@ import { sign, Signature } from "@noble/secp256k1"; +import { etc as secpUtils } from "@noble/secp256k1"; import { concat, hexToBytes } from "@waku/utils/bytes"; import { Symmetric } from "./constants.js"; import * as ecies from "./crypto/ecies.js"; -import { keccak256, randomBytes } from "./crypto/index.js"; +import { keccak256 } from "./crypto/index.js"; import * as symmetric from "./crypto/symmetric.js"; import { Signature as WakuSignature } from "./index.js"; @@ -190,7 +191,7 @@ export async function preCipher( const remainder = rawSize % PaddingTarget; const paddingSize = PaddingTarget - remainder; - const pad = randomBytes(paddingSize); + const pad = secpUtils.randomBytes(paddingSize); if (!validateDataIntegrity(pad, paddingSize)) { throw new Error("failed to generate random padding of size " + paddingSize);