mirror of https://github.com/waku-org/js-waku.git
Merge pull request #599 from status-im/remove-node-crypto
This commit is contained in:
commit
0e42cf6d7f
|
@ -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
|
||||
|
||||
|
|
|
@ -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"),
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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}`;
|
||||
});
|
||||
});
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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'),
|
||||
},
|
||||
|
|
|
@ -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'),
|
||||
},
|
||||
|
|
|
@ -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'),
|
||||
},
|
||||
|
|
|
@ -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'),
|
||||
},
|
||||
|
|
Loading…
Reference in New Issue