Merge pull request #599 from status-im/remove-node-crypto

This commit is contained in:
Franck R 2022-05-10 09:57:29 +10:00 committed by GitHub
commit 0e42cf6d7f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 282 additions and 350 deletions

View File

@ -16,6 +16,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
- Prefer the use of `BigInt` over integer literal (`n` postfix) to facilitate the use of a polyfill.
- Replaced `secp256k1` and hence `elliptic` dependencies with `@noble/secp256k1`,
reducing package size, number of dependency and removing need for `crypto-browserify` polyfill.
### Fixed

View File

@ -8,7 +8,7 @@ module.exports = {
Object.assign(config.resolve.fallback, {
assert: require.resolve("assert"),
buffer: require.resolve("buffer"),
crypto: require.resolve("crypto-browserify"),
crypto: false,
http: require.resolve("http-browserify"),
https: require.resolve("https-browserify"),
stream: require.resolve("stream-browserify"),
@ -41,7 +41,7 @@ module.exports = {
Object.assign(config.resolve.fallback, {
assert: require.resolve("assert"),
buffer: require.resolve("buffer"),
crypto: require.resolve("crypto-browserify"),
crypto: false,
http: require.resolve("http-browserify"),
https: require.resolve("https-browserify"),
stream: require.resolve("stream-browserify"),

82
package-lock.json generated
View File

@ -11,6 +11,7 @@
"dependencies": {
"@chainsafe/libp2p-noise": "^5.0.0",
"@ethersproject/rlp": "^5.5.0",
"@noble/secp256k1": "^1.3.4",
"debug": "^4.3.1",
"dns-query": "^0.8.0",
"hi-base32": "^0.5.1",
@ -25,7 +26,6 @@
"multiaddr": "^10.0.1",
"multihashes": "^4.0.3",
"protobufjs": "^6.8.8",
"secp256k1": "^4.0.2",
"uuid": "^8.3.2"
},
"devDependencies": {
@ -45,7 +45,6 @@
"assert": "^2.0.0",
"buffer": "^6.0.3",
"chai": "^4.3.4",
"crypto-browserify": "^3.12.0",
"cspell": "^5.14.0",
"eslint": "^8.6.0",
"eslint-config-prettier": "^8.3.0",
@ -2642,7 +2641,8 @@
"node_modules/bn.js": {
"version": "4.12.0",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
"integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA=="
"integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==",
"dev": true
},
"node_modules/body-parser": {
"version": "1.19.0",
@ -2756,7 +2756,8 @@
"node_modules/brorand": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz",
"integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8="
"integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=",
"dev": true
},
"node_modules/browser-process-hrtime": {
"version": "1.0.0",
@ -4282,6 +4283,7 @@
"version": "6.5.4",
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz",
"integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==",
"dev": true,
"dependencies": {
"bn.js": "^4.11.9",
"brorand": "^1.1.0",
@ -6000,6 +6002,7 @@
"version": "1.1.7",
"resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz",
"integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==",
"dev": true,
"dependencies": {
"inherits": "^2.0.3",
"minimalistic-assert": "^1.0.1"
@ -6053,6 +6056,7 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
"integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=",
"dev": true,
"dependencies": {
"hash.js": "^1.0.3",
"minimalistic-assert": "^1.0.0",
@ -8317,12 +8321,14 @@
"node_modules/minimalistic-assert": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
"integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A=="
"integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==",
"dev": true
},
"node_modules/minimalistic-crypto-utils": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz",
"integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo="
"integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=",
"dev": true
},
"node_modules/minimatch": {
"version": "3.0.4",
@ -8703,11 +8709,6 @@
"integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==",
"dev": true
},
"node_modules/node-addon-api": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.2.tgz",
"integrity": "sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA=="
},
"node_modules/node-fetch": {
"name": "@achingbrain/node-fetch",
"version": "2.6.7",
@ -8725,16 +8726,6 @@
"node": ">= 6.13.0"
}
},
"node_modules/node-gyp-build": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.2.3.tgz",
"integrity": "sha512-MN6ZpzmfNCRM+3t57PTJHgHyw/h4OWnZ6mR8P5j/uZtqQr46RRuDE/P+g3n0YR/AiYXeWixZZzaip77gdICfRg==",
"bin": {
"node-gyp-build": "bin.js",
"node-gyp-build-optional": "optional.js",
"node-gyp-build-test": "build-test.js"
}
},
"node_modules/node-preload": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz",
@ -10669,20 +10660,6 @@
"url": "https://opencollective.com/webpack"
}
},
"node_modules/secp256k1": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-4.0.3.tgz",
"integrity": "sha512-NLZVf+ROMxwtEj3Xa562qgv2BK5e2WNmXPiOdVIPLgs6lyTzMvBq0aWTYMI5XCP9jZMVKOcqZLw/Wc4vDkuxhA==",
"hasInstallScript": true,
"dependencies": {
"elliptic": "^6.5.4",
"node-addon-api": "^2.0.0",
"node-gyp-build": "^4.2.0"
},
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/semver": {
"version": "7.3.5",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
@ -14803,7 +14780,8 @@
"bn.js": {
"version": "4.12.0",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
"integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA=="
"integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==",
"dev": true
},
"body-parser": {
"version": "1.19.0",
@ -14901,7 +14879,8 @@
"brorand": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz",
"integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8="
"integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=",
"dev": true
},
"browser-process-hrtime": {
"version": "1.0.0",
@ -16124,6 +16103,7 @@
"version": "6.5.4",
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz",
"integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==",
"dev": true,
"requires": {
"bn.js": "^4.11.9",
"brorand": "^1.1.0",
@ -17396,6 +17376,7 @@
"version": "1.1.7",
"resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz",
"integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==",
"dev": true,
"requires": {
"inherits": "^2.0.3",
"minimalistic-assert": "^1.0.1"
@ -17439,6 +17420,7 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
"integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=",
"dev": true,
"requires": {
"hash.js": "^1.0.3",
"minimalistic-assert": "^1.0.0",
@ -19209,12 +19191,14 @@
"minimalistic-assert": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
"integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A=="
"integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==",
"dev": true
},
"minimalistic-crypto-utils": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz",
"integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo="
"integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=",
"dev": true
},
"minimatch": {
"version": "3.0.4",
@ -19519,11 +19503,6 @@
"integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==",
"dev": true
},
"node-addon-api": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.2.tgz",
"integrity": "sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA=="
},
"node-fetch": {
"version": "npm:@achingbrain/node-fetch@2.6.7",
"resolved": "https://registry.npmjs.org/@achingbrain/node-fetch/-/node-fetch-2.6.7.tgz",
@ -19534,11 +19513,6 @@
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz",
"integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA=="
},
"node-gyp-build": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.2.3.tgz",
"integrity": "sha512-MN6ZpzmfNCRM+3t57PTJHgHyw/h4OWnZ6mR8P5j/uZtqQr46RRuDE/P+g3n0YR/AiYXeWixZZzaip77gdICfRg=="
},
"node-preload": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz",
@ -20990,16 +20964,6 @@
"ajv-keywords": "^3.5.2"
}
},
"secp256k1": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-4.0.3.tgz",
"integrity": "sha512-NLZVf+ROMxwtEj3Xa562qgv2BK5e2WNmXPiOdVIPLgs6lyTzMvBq0aWTYMI5XCP9jZMVKOcqZLw/Wc4vDkuxhA==",
"requires": {
"elliptic": "^6.5.4",
"node-addon-api": "^2.0.0",
"node-gyp-build": "^4.2.0"
}
},
"semver": {
"version": "7.3.5",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",

View File

@ -61,12 +61,16 @@
"deploy": "node ci/deploy.js",
"reset-hard": "git clean -dfx && git reset --hard && npm i && npm run build && for d in examples/*/; do (cd $d; npm i); done"
},
"browser": {
"crypto": false
},
"engines": {
"node": ">=16"
},
"dependencies": {
"@chainsafe/libp2p-noise": "^5.0.0",
"@ethersproject/rlp": "^5.5.0",
"@noble/secp256k1": "^1.3.4",
"debug": "^4.3.1",
"dns-query": "^0.8.0",
"hi-base32": "^0.5.1",
@ -81,7 +85,6 @@
"multiaddr": "^10.0.1",
"multihashes": "^4.0.3",
"protobufjs": "^6.8.8",
"secp256k1": "^4.0.2",
"uuid": "^8.3.2"
},
"devDependencies": {
@ -101,7 +104,6 @@
"assert": "^2.0.0",
"buffer": "^6.0.3",
"chai": "^4.3.4",
"crypto-browserify": "^3.12.0",
"cspell": "^5.14.0",
"eslint": "^8.6.0",
"eslint-config-prettier": "^8.3.0",

View File

@ -1,33 +1,48 @@
import nodeCrypto from "crypto";
// IE 11
declare global {
interface Window {
msCrypto?: Crypto;
}
import { concat } from "uint8arrays/concat";
interface Crypto {
webkitSubtle?: SubtleCrypto;
declare const self: Record<string, any> | undefined;
const crypto: { node?: any; web?: any } = {
node: nodeCrypto,
web: typeof self === "object" && "crypto" in self ? self.crypto : undefined,
};
export function getSubtle(): SubtleCrypto {
if (crypto.web) {
return crypto.web.subtle;
} else if (crypto.node) {
return crypto.node.webcrypto.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)"
);
}
}
const crypto =
(typeof window !== "undefined" &&
(window as Window) &&
(window.crypto || window.msCrypto)) ||
(nodeCrypto.webcrypto as unknown as Crypto);
const subtle: SubtleCrypto = crypto.subtle || crypto.webkitSubtle;
if (subtle === undefined) {
throw new Error("crypto and/or subtle api unavailable");
export function randomBytes(bytesLength = 32): Uint8Array {
if (crypto.web) {
return crypto.web.getRandomValues(new Uint8Array(bytesLength));
} else if (crypto.node) {
const { randomBytes } = crypto.node;
return Uint8Array.from(randomBytes(bytesLength));
} else {
throw new Error(
"The environment doesn't have randomBytes function (if in the browser, be sure to use to be in a secure context, ie, https)"
);
}
}
export { crypto, subtle };
export function randomBytes(size: number): Uint8Array {
return crypto.getRandomValues(new Uint8Array(size));
}
export function sha256(msg: ArrayBufferLike): Promise<ArrayBuffer> {
return subtle.digest({ name: "SHA-256" }, msg);
export async function sha256(...messages: Uint8Array[]): Promise<Uint8Array> {
if (crypto.web) {
const buffer = await crypto.web.subtle.digest("SHA-256", concat(messages));
return new Uint8Array(buffer);
} else if (crypto.node) {
const { createHash } = crypto.node;
const hash = createHash("sha256");
messages.forEach((m) => hash.update(m));
return Uint8Array.from(hash.digest());
} else {
throw new Error("The environment doesn't have sha256 function");
}
}

View File

@ -1,7 +1,7 @@
import assert from "assert";
import * as secp from "@noble/secp256k1";
import * as base32 from "hi-base32";
import { ecdsaVerify } from "secp256k1";
import { fromString } from "uint8arrays/from-string";
import { ENR } from "../enr";
@ -48,11 +48,17 @@ export class ENRTree {
64
);
const isVerified = ecdsaVerify(
signatureBuffer,
keccak256Buf(signedComponentBuffer),
new Uint8Array(decodedPublicKey)
);
let isVerified;
try {
const _sig = secp.Signature.fromCompact(signatureBuffer.slice(0, 64));
isVerified = secp.verify(
_sig,
keccak256Buf(signedComponentBuffer),
new Uint8Array(decodedPublicKey)
);
} catch {
isVerified = false;
}
assert(isVerified, "Unable to verify ENRTree root signature");

View File

@ -37,7 +37,7 @@ describe("ENR", function () {
lightPush: false,
};
const txt = enr.encodeTxt(keypair.privateKey);
const txt = await enr.encodeTxt(keypair.privateKey);
const enr2 = ENR.decodeTxt(txt);
if (!enr.signature) throw "enr.signature is undefined";
@ -116,7 +116,7 @@ describe("ENR", function () {
enr.setLocationMultiaddr(new Multiaddr("/ip4/18.223.219.100/udp/9000"));
enr.set("id", new Uint8Array([0]));
const txt = enr.encodeTxt(keypair.privateKey);
const txt = await enr.encodeTxt(keypair.privateKey);
ENR.decodeTxt(txt);
assert.fail("Expect error here");
@ -190,11 +190,11 @@ describe("ENR", function () {
});
});
describe("Static tests", () => {
describe("Static tests", function () {
let privateKey: Uint8Array;
let record: ENR;
beforeEach(() => {
beforeEach(async function () {
const seq = BigInt(1);
privateKey = hexToBytes(
"b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291"
@ -202,8 +202,7 @@ describe("ENR", function () {
record = ENR.createV4(v4.publicKey(privateKey));
record.setLocationMultiaddr(new Multiaddr("/ip4/127.0.0.1/udp/30303"));
record.seq = seq;
// To set signature
record.encode(privateKey);
await record.encodeTxt(privateKey);
});
it("should properly compute the node id", () => {
@ -212,21 +211,24 @@ describe("ENR", function () {
);
});
it("should encode/decode to RLP encoding", () => {
const decoded = ENR.decode(record.encode(privateKey));
it("should encode/decode to RLP encoding", async function () {
const decoded = ENR.decode(await record.encode(privateKey));
expect(decoded).to.deep.equal(record);
});
it("should encode/decode to text encoding", () => {
it("should encode/decode to text encoding", async function () {
// spec enr https://eips.ethereum.org/EIPS/eip-778
const testTxt =
"enr:-IS4QHCYrYZbAKWCBRlAy5zzaDZXJBGkcnh4MHcBFZntXNFrdvJjX04jRzjzCBOonrkTfj499SZuOh8R33Ls8RRcy5wBgmlkgnY0gmlwhH8AAAGJc2VjcDI1NmsxoQPKY0yuDUmstAHYpMa2_oxVtw0RW_QAdpzBQA8yWM0xOIN1ZHCCdl8";
const decoded = ENR.decodeTxt(testTxt);
expect(decoded.udp).to.be.equal(30303);
expect(decoded.ip).to.be.equal("127.0.0.1");
expect(decoded).to.deep.equal(record);
const recordTxt = record.encodeTxt(privateKey);
expect(recordTxt).to.equal(testTxt);
// Note: Signatures are different due to the extra entropy added
// by @noble/secp256k1:
// https://github.com/paulmillr/noble-secp256k1#signmsghash-privatekey
expect(decoded.udp).to.deep.equal(record.udp);
expect(decoded.ip).to.deep.equal(record.ip);
expect(decoded.id).to.deep.equal(record.id);
expect(decoded.seq).to.equal(record.seq);
expect(decoded.get("secp256k1")).to.deep.equal(record.get("secp256k1"));
});
});
@ -397,10 +399,10 @@ describe("ENR", function () {
};
});
it("should set field with all protocols disabled", () => {
it("should set field with all protocols disabled", async () => {
enr.waku2 = waku2Protocols;
const txt = enr.encodeTxt(keypair.privateKey);
const txt = await enr.encodeTxt(keypair.privateKey);
const decoded = ENR.decodeTxt(txt).waku2!;
expect(decoded.relay).to.equal(false);
@ -409,14 +411,14 @@ describe("ENR", function () {
expect(decoded.lightPush).to.equal(false);
});
it("should set field with all protocols enabled", () => {
it("should set field with all protocols enabled", async () => {
waku2Protocols.relay = true;
waku2Protocols.store = true;
waku2Protocols.filter = true;
waku2Protocols.lightPush = true;
enr.waku2 = waku2Protocols;
const txt = enr.encodeTxt(keypair.privateKey);
const txt = await enr.encodeTxt(keypair.privateKey);
const decoded = ENR.decodeTxt(txt).waku2!;
expect(decoded.relay).to.equal(true);
@ -425,11 +427,11 @@ describe("ENR", function () {
expect(decoded.lightPush).to.equal(true);
});
it("should set field with only RELAY enabled", () => {
it("should set field with only RELAY enabled", async () => {
waku2Protocols.relay = true;
enr.waku2 = waku2Protocols;
const txt = enr.encodeTxt(keypair.privateKey);
const txt = await enr.encodeTxt(keypair.privateKey);
const decoded = ENR.decodeTxt(txt).waku2!;
expect(decoded.relay).to.equal(true);
@ -438,11 +440,11 @@ describe("ENR", function () {
expect(decoded.lightPush).to.equal(false);
});
it("should set field with only STORE enabled", () => {
it("should set field with only STORE enabled", async () => {
waku2Protocols.store = true;
enr.waku2 = waku2Protocols;
const txt = enr.encodeTxt(keypair.privateKey);
const txt = await enr.encodeTxt(keypair.privateKey);
const decoded = ENR.decodeTxt(txt).waku2!;
expect(decoded.relay).to.equal(false);
@ -451,11 +453,11 @@ describe("ENR", function () {
expect(decoded.lightPush).to.equal(false);
});
it("should set field with only FILTER enabled", () => {
it("should set field with only FILTER enabled", async () => {
waku2Protocols.filter = true;
enr.waku2 = waku2Protocols;
const txt = enr.encodeTxt(keypair.privateKey);
const txt = await enr.encodeTxt(keypair.privateKey);
const decoded = ENR.decodeTxt(txt).waku2!;
expect(decoded.relay).to.equal(false);
@ -464,11 +466,11 @@ describe("ENR", function () {
expect(decoded.lightPush).to.equal(false);
});
it("should set field with only LIGHTPUSH enabled", () => {
it("should set field with only LIGHTPUSH enabled", async () => {
waku2Protocols.lightPush = true;
enr.waku2 = waku2Protocols;
const txt = enr.encodeTxt(keypair.privateKey);
const txt = await enr.encodeTxt(keypair.privateKey);
const decoded = ENR.decodeTxt(txt).waku2!;
expect(decoded.relay).to.equal(false);

View File

@ -22,6 +22,7 @@ import {
import { decodeMultiaddrs, encodeMultiaddrs } from "./multiaddrs_codec";
import { ENRKey, ENRValue, NodeId, SequenceNumber } from "./types";
import * as v4 from "./v4";
import { compressPublicKey } from "./v4";
import { decodeWaku2, encodeWaku2, Waku2 } from "./waku2_codec";
const dbg = debug("waku:enr");
@ -45,6 +46,10 @@ export class ENR extends Map<ENRKey, ENRValue> {
publicKey: Uint8Array,
kvs: Record<ENRKey, ENRValue> = {}
): ENR {
// EIP-778 specifies that the key must be in compressed format, 33 bytes
if (publicKey.length !== 33) {
publicKey = compressPublicKey(publicKey);
}
return new ENR({
...kvs,
id: utf8ToBytes("v4"),
@ -453,10 +458,10 @@ export class ENR extends Map<ENRKey, ENRValue> {
return v4.verify(this.publicKey, data, signature);
}
sign(data: Uint8Array, privateKey: Uint8Array): Uint8Array {
async sign(data: Uint8Array, privateKey: Uint8Array): Promise<Uint8Array> {
switch (this.id) {
case "v4":
this.signature = v4.sign(privateKey, data);
this.signature = await v4.sign(privateKey, data);
break;
default:
throw new Error(ERR_INVALID_ID);
@ -464,7 +469,9 @@ export class ENR extends Map<ENRKey, ENRValue> {
return this.signature;
}
encodeToValues(privateKey?: Uint8Array): (ENRKey | ENRValue | number[])[] {
async encodeToValues(
privateKey?: Uint8Array
): Promise<(ENRKey | ENRValue | number[])[]> {
// sort keys and flatten into [k, v, k, v, ...]
const content: Array<ENRKey | ENRValue | number[]> = Array.from(this.keys())
.sort((a, b) => a.localeCompare(b))
@ -473,7 +480,9 @@ export class ENR extends Map<ENRKey, ENRValue> {
.flat();
content.unshift(new Uint8Array([Number(this.seq)]));
if (privateKey) {
content.unshift(this.sign(hexToBytes(RLP.encode(content)), privateKey));
content.unshift(
await this.sign(hexToBytes(RLP.encode(content)), privateKey)
);
} else {
if (!this.signature) {
throw new Error(ERR_NO_SIGNATURE);
@ -483,15 +492,19 @@ export class ENR extends Map<ENRKey, ENRValue> {
return content;
}
encode(privateKey?: Uint8Array): Uint8Array {
const encoded = hexToBytes(RLP.encode(this.encodeToValues(privateKey)));
async encode(privateKey?: Uint8Array): Promise<Uint8Array> {
const encoded = hexToBytes(
RLP.encode(await this.encodeToValues(privateKey))
);
if (encoded.length >= MAX_RECORD_SIZE) {
throw new Error("ENR must be less than 300 bytes");
}
return encoded;
}
encodeTxt(privateKey?: Uint8Array): string {
return ENR.RECORD_PREFIX + toString(this.encode(privateKey), "base64url");
async encodeTxt(privateKey?: Uint8Array): Promise<string> {
return (
ENR.RECORD_PREFIX + toString(await this.encode(privateKey), "base64url")
);
}
}

View File

@ -1,8 +1,8 @@
import crypto from "crypto";
import * as secp256k1 from "secp256k1";
import * as secp from "@noble/secp256k1";
import { concat } from "uint8arrays/concat";
import { randomBytes } from "../../crypto";
import { AbstractKeypair, IKeypair, IKeypairClass, KeypairType } from "./types";
export function secp256k1PublicKeyToCompressed(
@ -11,18 +11,22 @@ export function secp256k1PublicKeyToCompressed(
if (publicKey.length === 64) {
publicKey = concat([[4], publicKey], 65);
}
return secp256k1.publicKeyConvert(publicKey, true);
const point = secp.Point.fromHex(publicKey);
return point.toRawBytes(true);
}
export function secp256k1PublicKeyToFull(publicKey: Uint8Array): Uint8Array {
if (publicKey.length === 64) {
publicKey = concat([[4], publicKey], 65);
}
return secp256k1.publicKeyConvert(publicKey, false);
const point = secp.Point.fromHex(publicKey);
return point.toRawBytes(false);
}
export function secp256k1PublicKeyToRaw(publicKey: Uint8Array): Uint8Array {
return secp256k1.publicKeyConvert(publicKey, false).slice(1);
const point = secp.Point.fromHex(publicKey);
return point.toRawBytes(false).slice(1);
}
export const Secp256k1Keypair: IKeypairClass = class Secp256k1Keypair
@ -41,41 +45,44 @@ export const Secp256k1Keypair: IKeypairClass = class Secp256k1Keypair
}
static async generate(): Promise<Secp256k1Keypair> {
const privateKey = await randomBytes(32);
const publicKey = secp256k1.publicKeyCreate(privateKey);
const privateKey = randomBytes(32);
const publicKey = secp.getPublicKey(privateKey);
return new Secp256k1Keypair(privateKey, publicKey);
}
privateKeyVerify(key = this._privateKey): boolean {
if (key) {
return secp256k1.privateKeyVerify(key);
return secp.utils.isValidPrivateKey(key);
}
return true;
}
publicKeyVerify(key = this._publicKey): boolean {
if (key) {
return secp256k1.publicKeyVerify(key);
try {
secp.Point.fromHex(key);
return true;
} catch {
return false;
}
}
return true;
}
sign(msg: Uint8Array): Uint8Array {
const { signature, recid } = secp256k1.ecdsaSign(msg, this.privateKey);
async sign(msg: Uint8Array): Promise<Uint8Array> {
const [signature, recid] = await secp.sign(msg, this.privateKey, {
recovered: true,
der: false,
});
return concat([signature, [recid]], signature.length + 1);
}
verify(msg: Uint8Array, sig: Uint8Array): boolean {
return secp256k1.ecdsaVerify(sig, msg, this.publicKey);
try {
const _sig = secp.Signature.fromCompact(sig.slice(0, 64));
return secp.verify(_sig, msg, this.publicKey);
} catch {
return false;
}
}
};
function randomBytes(length: number): Uint8Array {
if (typeof window !== "undefined" && window && window.crypto) {
const array = new Uint8Array(length);
window.crypto.getRandomValues(array);
return array;
} else {
return crypto.randomBytes(length);
}
}

View File

@ -10,7 +10,7 @@ export interface IKeypair {
publicKey: Uint8Array;
privateKeyVerify(): boolean;
publicKeyVerify(): boolean;
sign(msg: Uint8Array): Uint8Array;
sign(msg: Uint8Array): Promise<Uint8Array>;
verify(msg: Uint8Array, sig: Uint8Array): boolean;
hasPrivateKey(): boolean;
}
@ -29,7 +29,7 @@ export abstract class AbstractKeypair {
throw new Error("Invalid private key");
}
if ((this._publicKey = publicKey) && !this.publicKeyVerify()) {
throw new Error("Invalid private key");
throw new Error("Invalid public key");
}
}

View File

@ -1,7 +1,8 @@
import crypto from "crypto";
import * as secp from "@noble/secp256k1";
import { keccak256 } from "js-sha3";
import * as secp256k1 from "secp256k1";
import { randomBytes } from "../crypto";
import { bytesToHex } from "../utils";
import { createNodeId } from "./create";
import { NodeId } from "./types";
@ -10,17 +11,26 @@ export function hash(input: Uint8Array): Uint8Array {
return new Uint8Array(keccak256.arrayBuffer(input));
}
export async function createPrivateKey(): Promise<Uint8Array> {
export function createPrivateKey(): Uint8Array {
return randomBytes(32);
}
export function publicKey(privKey: Uint8Array): Uint8Array {
return secp256k1.publicKeyCreate(privKey);
return secp.getPublicKey(privKey, true);
}
export function sign(privKey: Uint8Array, msg: Uint8Array): Uint8Array {
const { signature } = secp256k1.ecdsaSign(hash(msg), privKey);
return signature;
export function compressPublicKey(publicKey: Uint8Array): Uint8Array {
const point = secp.Point.fromHex(bytesToHex(publicKey));
return point.toRawBytes(true);
}
export async function sign(
privKey: Uint8Array,
msg: Uint8Array
): Promise<Uint8Array> {
return secp.sign(hash(msg), privKey, {
der: false,
});
}
export function verify(
@ -28,12 +38,17 @@ export function verify(
msg: Uint8Array,
sig: Uint8Array
): boolean {
// Remove the recovery id if present (byte #65)
return secp256k1.ecdsaVerify(sig.slice(0, 64), hash(msg), pubKey);
try {
const _sig = secp.Signature.fromCompact(sig.slice(0, 64));
return secp.verify(_sig, hash(msg), pubKey);
} catch {
return false;
}
}
export function nodeId(pubKey: Uint8Array): NodeId {
const uncompressedPubkey = secp256k1.publicKeyConvert(pubKey, false);
const publicKey = secp.Point.fromHex(pubKey);
const uncompressedPubkey = publicKey.toRawBytes(false);
return createNodeId(hash(uncompressedPubkey.slice(1)));
}
@ -45,20 +60,20 @@ export class ENRKeyPair {
public readonly publicKey: Uint8Array
) {}
public static async create(privateKey?: Uint8Array): Promise<ENRKeyPair> {
public static create(privateKey?: Uint8Array): ENRKeyPair {
if (privateKey) {
if (!secp256k1.privateKeyVerify(privateKey)) {
if (!secp.utils.isValidPrivateKey(privateKey)) {
throw new Error("Invalid private key");
}
}
const _privateKey = privateKey || (await createPrivateKey());
const _privateKey = privateKey || createPrivateKey();
const _publicKey = publicKey(_privateKey);
const _nodeId = nodeId(_publicKey);
return new ENRKeyPair(_nodeId, _privateKey, _publicKey);
}
public sign(msg: Uint8Array): Uint8Array {
public async sign(msg: Uint8Array): Promise<Uint8Array> {
return sign(this.privateKey, msg);
}
@ -66,13 +81,3 @@ export class ENRKeyPair {
return verify(this.publicKey, msg, sig);
}
}
function randomBytes(length: number): Uint8Array {
if (typeof window !== "undefined" && window && window.crypto) {
const array = new Uint8Array(length);
window.crypto.getRandomValues(array);
return array;
} else {
return crypto.randomBytes(length);
}
}

View File

@ -1,7 +1,7 @@
import * as secp from "@noble/secp256k1";
import { concat } from "uint8arrays/concat";
import { randomBytes, sha256, subtle } from "../crypto";
import { getSubtle, randomBytes, sha256 } from "../crypto";
import { hexToBytes } from "../utils";
/**
* HKDF as implemented in go-ethereum.
@ -37,10 +37,10 @@ function aesCtrEncrypt(
key: ArrayBufferLike,
data: ArrayBufferLike
): Promise<Uint8Array> {
return subtle
return getSubtle()
.importKey("raw", key, "AES-CTR", false, ["encrypt"])
.then((cryptoKey) =>
subtle.encrypt(
getSubtle().encrypt(
{ name: "AES-CTR", counter: counter, length: 128 },
cryptoKey,
data
@ -54,10 +54,10 @@ function aesCtrDecrypt(
key: ArrayBufferLike,
data: ArrayBufferLike
): Promise<Uint8Array> {
return subtle
return getSubtle()
.importKey("raw", key, "AES-CTR", false, ["decrypt"])
.then((cryptoKey) =>
subtle.decrypt(
getSubtle().decrypt(
{ name: "AES-CTR", counter: counter, length: 128 },
cryptoKey,
data
@ -71,9 +71,9 @@ function hmacSha256Sign(
msg: ArrayBufferLike
): PromiseLike<Uint8Array> {
const algorithm = { name: "HMAC", hash: { name: "SHA-256" } };
return subtle
return getSubtle()
.importKey("raw", key, algorithm, false, ["sign"])
.then((cryptoKey) => subtle.sign(algorithm, cryptoKey, msg))
.then((cryptoKey) => getSubtle().sign(algorithm, cryptoKey, msg))
.then((bytes) => new Uint8Array(bytes));
}
@ -83,9 +83,9 @@ function hmacSha256Verify(
sig: ArrayBufferLike
): Promise<boolean> {
const algorithm = { name: "HMAC", hash: { name: "SHA-256" } };
const _key = subtle.importKey("raw", key, algorithm, false, ["verify"]);
const _key = getSubtle().importKey("raw", key, algorithm, false, ["verify"]);
return _key.then((cryptoKey) =>
subtle.verify(algorithm, cryptoKey, sig, msg)
getSubtle().verify(algorithm, cryptoKey, sig, msg)
);
}

View File

@ -35,7 +35,7 @@ describe("Waku Message: Browser & Node", function () {
await fc.assert(
fc.asyncProperty(
fc.uint8Array({ minLength: 1 }),
fc.uint8Array({ minLength: 32, maxLength: 32 }),
fc.uint8Array({ min: 1, minLength: 32, maxLength: 32 }),
async (payload, key) => {
const publicKey = getPublicKey(key);
@ -56,8 +56,8 @@ describe("Waku Message: Browser & Node", function () {
await fc.assert(
fc.asyncProperty(
fc.uint8Array({ minLength: 1 }),
fc.uint8Array({ minLength: 32, maxLength: 32 }),
fc.uint8Array({ minLength: 32, maxLength: 32 }),
fc.uint8Array({ min: 1, minLength: 32, maxLength: 32 }),
fc.uint8Array({ min: 1, minLength: 32, maxLength: 32 }),
async (payload, sigPrivKey, encPrivKey) => {
const sigPubKey = getPublicKey(sigPrivKey);
const encPubKey = getPublicKey(encPrivKey);

View File

@ -89,12 +89,12 @@ export class WakuMessage {
}
if (encPublicKey) {
const enc = version_1.clearEncode(_payload, sigPrivKey);
const enc = await version_1.clearEncode(_payload, sigPrivKey);
_payload = await version_1.encryptAsymmetric(enc.payload, encPublicKey);
sig = enc.sig;
version = 1;
} else if (symKey) {
const enc = version_1.clearEncode(_payload, sigPrivKey);
const enc = await version_1.clearEncode(_payload, sigPrivKey);
_payload = await version_1.encryptSymmetric(enc.payload, symKey);
sig = enc.sig;
version = 1;

View File

@ -0,0 +1,37 @@
import { getSubtle, randomBytes } from "../crypto";
export const KeySize = 32;
export const IvSize = 12;
export const TagSize = 16;
const Algorithm = { name: "AES-GCM", length: 128 };
export async function encrypt(
iv: Buffer | Uint8Array,
key: Buffer,
clearText: Buffer
): Promise<Buffer> {
return getSubtle()
.importKey("raw", key, Algorithm, false, ["encrypt"])
.then((cryptoKey) =>
getSubtle().encrypt({ iv, ...Algorithm }, cryptoKey, clearText)
)
.then(Buffer.from);
}
export async function decrypt(
iv: Buffer,
key: Buffer,
cipherText: Buffer
): Promise<Buffer> {
return getSubtle()
.importKey("raw", key, Algorithm, false, ["decrypt"])
.then((cryptoKey) =>
getSubtle().decrypt({ iv, ...Algorithm }, cryptoKey, cipherText)
)
.then(Buffer.from);
}
export function generateIv(): Uint8Array {
return randomBytes(IvSize);
}

View File

@ -1,51 +0,0 @@
import { IvSize } from "./index";
declare global {
interface Window {
msCrypto?: Crypto;
}
interface Crypto {
webkitSubtle?: SubtleCrypto;
}
}
const crypto = window.crypto || window.msCrypto;
const subtle: SubtleCrypto = crypto.subtle || crypto.webkitSubtle;
const Algorithm = { name: "AES-GCM", length: 128 };
if (subtle === undefined) {
throw new Error("Failed to load Subtle CryptoAPI");
}
export async function encrypt(
iv: Buffer | Uint8Array,
key: Buffer,
clearText: Buffer
): Promise<Buffer> {
return subtle
.importKey("raw", key, Algorithm, false, ["encrypt"])
.then((cryptoKey) =>
subtle.encrypt({ iv, ...Algorithm }, cryptoKey, clearText)
)
.then(Buffer.from);
}
export async function decrypt(
iv: Buffer,
key: Buffer,
cipherText: Buffer
): Promise<Buffer> {
return subtle
.importKey("raw", key, Algorithm, false, ["decrypt"])
.then((cryptoKey) =>
subtle.decrypt({ iv, ...Algorithm }, cryptoKey, cipherText)
)
.then(Buffer.from);
}
export function generateIv(): Uint8Array {
const iv = new Uint8Array(IvSize);
crypto.getRandomValues(iv);
return iv;
}

View File

@ -1,38 +0,0 @@
export const SymmetricKeySize = 32;
export const IvSize = 12;
export const TagSize = 16;
export interface Symmetric {
/**
* Proceed with symmetric encryption of `clearText` value.
*/
encrypt: (
iv: Buffer | Uint8Array,
key: Buffer,
clearText: Buffer
) => Promise<Buffer>;
/**
* Proceed with symmetric decryption of `cipherText` value.
*/
decrypt: (iv: Buffer, key: Buffer, cipherText: Buffer) => Promise<Buffer>;
/**
* Generate an Initialization Vector (iv) for for Symmetric encryption purposes.
*/
generateIv: () => Uint8Array;
}
export let symmetric: Symmetric = {} as unknown as Symmetric;
import("./browser")
.then((mod) => {
symmetric = mod;
})
.catch((eBrowser) => {
import("./node")
.then((mod) => {
symmetric = mod;
})
.catch((eNode) => {
throw `Could not load any symmetric crypto modules: ${eBrowser}, ${eNode}`;
});
});

View File

@ -1,36 +0,0 @@
import { createCipheriv, createDecipheriv, randomBytes } from "crypto";
import { IvSize, TagSize } from "./index";
const Algorithm = "aes-256-gcm";
export async function encrypt(
iv: Buffer | Uint8Array,
key: Buffer,
clearText: Buffer
): Promise<Buffer> {
const cipher = createCipheriv(Algorithm, key, iv);
const a = cipher.update(clearText);
const b = cipher.final();
const tag = cipher.getAuthTag();
return Buffer.concat([a, b, tag]);
}
export async function decrypt(
iv: Buffer,
key: Buffer,
data: Buffer
): Promise<Buffer> {
const tagStart = data.length - TagSize;
const cipherText = data.slice(0, tagStart);
const tag = data.slice(tagStart);
const decipher = createDecipheriv(Algorithm, key, iv);
decipher.setAuthTag(tag);
const a = decipher.update(cipherText);
const b = decipher.final();
return Buffer.concat([a, b]);
}
export function generateIv(): Buffer {
return randomBytes(IvSize);
}

View File

@ -14,11 +14,11 @@ import {
describe("Waku Message Version 1", function () {
it("Sign & Recover", function () {
fc.assert(
fc.property(
fc.asyncProperty(
fc.uint8Array(),
fc.uint8Array({ minLength: 32, maxLength: 32 }),
(message, privKey) => {
const enc = clearEncode(message, privKey);
async (message, privKey) => {
const enc = await clearEncode(message, privKey);
const res = clearDecode(enc.payload);
const pubKey = getPublicKey(privKey);
@ -44,7 +44,7 @@ describe("Waku Message Version 1", function () {
await fc.assert(
fc.asyncProperty(
fc.uint8Array({ minLength: 1 }),
fc.uint8Array({ minLength: 32, maxLength: 32 }),
fc.uint8Array({ min: 1, minLength: 32, maxLength: 32 }),
async (message, privKey) => {
const publicKey = getPublicKey(privKey);

View File

@ -1,13 +1,13 @@
import { Buffer } from "buffer";
import * as crypto from "crypto";
import * as secp from "@noble/secp256k1";
import { keccak256 } from "js-sha3";
import * as secp256k1 from "secp256k1";
import { randomBytes } from "../crypto";
import { hexToBytes } from "../utils";
import * as ecies from "./ecies";
import { IvSize, symmetric, SymmetricKeySize } from "./symmetric";
import * as symmetric from "./symmetric";
const FlagsLength = 1;
const FlagMask = 3; // 0011
@ -26,10 +26,10 @@ export const PrivateKeySize = 32;
* @returns The encoded payload, ready for encryption using {@link encryptAsymmetric}
* or {@link encryptSymmetric}.
*/
export function clearEncode(
export async function clearEncode(
messagePayload: Uint8Array,
sigPrivKey?: Uint8Array
): { payload: Uint8Array; sig?: Signature } {
): Promise<{ payload: Uint8Array; sig?: Signature }> {
let envelope = Buffer.from([0]); // No flags
envelope = addPayloadSizeField(envelope, messagePayload);
envelope = Buffer.concat([envelope, Buffer.from(messagePayload)]);
@ -58,10 +58,17 @@ export function clearEncode(
if (sigPrivKey) {
envelope[0] |= IsSignedMask;
const hash = keccak256(envelope);
const s = secp256k1.ecdsaSign(hexToBytes(hash), sigPrivKey);
envelope = Buffer.concat([envelope, s.signature, Buffer.from([s.recid])]);
const [signature, recid] = await secp.sign(hash, sigPrivKey, {
recovered: true,
der: false,
});
envelope = Buffer.concat([
envelope,
hexToBytes(signature),
Buffer.from([recid]),
]);
sig = {
signature: Buffer.from(s.signature),
signature: Buffer.from(signature),
publicKey: getPublicKey(sigPrivKey),
};
}
@ -71,7 +78,7 @@ export function clearEncode(
export type Signature = {
signature: Uint8Array;
publicKey: Uint8Array;
publicKey: Uint8Array | undefined;
};
/**
@ -170,7 +177,7 @@ export async function decryptSymmetric(
key: Uint8Array | Buffer | string
): Promise<Uint8Array> {
const data = Buffer.from(payload);
const ivStart = data.length - IvSize;
const ivStart = data.length - symmetric.IvSize;
const cipher = data.slice(0, ivStart);
const iv = data.slice(ivStart);
@ -190,7 +197,7 @@ export function generatePrivateKey(): Uint8Array {
* Generate a new symmetric key to be used for symmetric encryption.
*/
export function generateSymmetricKey(): Uint8Array {
return randomBytes(SymmetricKeySize);
return randomBytes(symmetric.KeySize);
}
/**
@ -198,7 +205,7 @@ export function generateSymmetricKey(): Uint8Array {
* encryption.
*/
export function getPublicKey(privateKey: Uint8Array | Buffer): Uint8Array {
return secp256k1.publicKeyCreate(privateKey, false);
return secp.getPublicKey(privateKey, false);
}
/**
@ -249,22 +256,19 @@ function getHash(message: Buffer, isSigned: boolean): string {
return keccak256(message);
}
function ecRecoverPubKey(messageHash: string, signature: Buffer): Uint8Array {
function ecRecoverPubKey(
messageHash: string,
signature: Buffer
): Uint8Array | undefined {
const recovery = signature.slice(64).readIntBE(0, 1);
return secp256k1.ecdsaRecover(
signature.slice(0, 64),
recovery,
const _signature = secp.Signature.fromCompact(signature.slice(0, 64));
return secp.recoverPublicKey(
hexToBytes(messageHash),
_signature,
recovery,
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: compressed: false
false
);
}
function randomBytes(length: number): Uint8Array {
if (typeof window !== "undefined" && window && window.crypto) {
const array = new Uint8Array(length);
window.crypto.getRandomValues(array);
return array;
} else {
return crypto.randomBytes(length);
}
}

View File

@ -24,7 +24,7 @@ module.exports = {
extensions: ['.ts', '.js'],
fallback: {
buffer: require.resolve('buffer/'),
crypto: require.resolve('crypto-browserify'),
crypto: false,
stream: require.resolve('stream-browserify'),
assert: require.resolve('assert'),
},

View File

@ -24,7 +24,7 @@ module.exports = {
extensions: ['.ts', '.js'],
fallback: {
buffer: require.resolve('buffer/'),
crypto: require.resolve('crypto-browserify'),
crypto: false,
stream: require.resolve('stream-browserify'),
assert: require.resolve('assert'),
},

View File

@ -24,7 +24,7 @@ module.exports = {
extensions: ['.ts', '.js'],
fallback: {
buffer: require.resolve('buffer/'),
crypto: require.resolve('crypto-browserify'),
crypto: false,
stream: require.resolve('stream-browserify'),
assert: require.resolve('assert'),
},

View File

@ -24,7 +24,7 @@ module.exports = {
extensions: ['.ts', '.js'],
fallback: {
buffer: require.resolve('buffer/'),
crypto: require.resolve('crypto-browserify'),
crypto: false,
stream: require.resolve('stream-browserify'),
assert: require.resolve('assert'),
},