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 ### Changed
- Prefer the use of `BigInt` over integer literal (`n` postfix) to facilitate the use of a polyfill. - 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 ### Fixed

View File

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

82
package-lock.json generated
View File

@ -11,6 +11,7 @@
"dependencies": { "dependencies": {
"@chainsafe/libp2p-noise": "^5.0.0", "@chainsafe/libp2p-noise": "^5.0.0",
"@ethersproject/rlp": "^5.5.0", "@ethersproject/rlp": "^5.5.0",
"@noble/secp256k1": "^1.3.4",
"debug": "^4.3.1", "debug": "^4.3.1",
"dns-query": "^0.8.0", "dns-query": "^0.8.0",
"hi-base32": "^0.5.1", "hi-base32": "^0.5.1",
@ -25,7 +26,6 @@
"multiaddr": "^10.0.1", "multiaddr": "^10.0.1",
"multihashes": "^4.0.3", "multihashes": "^4.0.3",
"protobufjs": "^6.8.8", "protobufjs": "^6.8.8",
"secp256k1": "^4.0.2",
"uuid": "^8.3.2" "uuid": "^8.3.2"
}, },
"devDependencies": { "devDependencies": {
@ -45,7 +45,6 @@
"assert": "^2.0.0", "assert": "^2.0.0",
"buffer": "^6.0.3", "buffer": "^6.0.3",
"chai": "^4.3.4", "chai": "^4.3.4",
"crypto-browserify": "^3.12.0",
"cspell": "^5.14.0", "cspell": "^5.14.0",
"eslint": "^8.6.0", "eslint": "^8.6.0",
"eslint-config-prettier": "^8.3.0", "eslint-config-prettier": "^8.3.0",
@ -2642,7 +2641,8 @@
"node_modules/bn.js": { "node_modules/bn.js": {
"version": "4.12.0", "version": "4.12.0",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", "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": { "node_modules/body-parser": {
"version": "1.19.0", "version": "1.19.0",
@ -2756,7 +2756,8 @@
"node_modules/brorand": { "node_modules/brorand": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", "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": { "node_modules/browser-process-hrtime": {
"version": "1.0.0", "version": "1.0.0",
@ -4282,6 +4283,7 @@
"version": "6.5.4", "version": "6.5.4",
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz",
"integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==",
"dev": true,
"dependencies": { "dependencies": {
"bn.js": "^4.11.9", "bn.js": "^4.11.9",
"brorand": "^1.1.0", "brorand": "^1.1.0",
@ -6000,6 +6002,7 @@
"version": "1.1.7", "version": "1.1.7",
"resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz",
"integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==",
"dev": true,
"dependencies": { "dependencies": {
"inherits": "^2.0.3", "inherits": "^2.0.3",
"minimalistic-assert": "^1.0.1" "minimalistic-assert": "^1.0.1"
@ -6053,6 +6056,7 @@
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
"integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=",
"dev": true,
"dependencies": { "dependencies": {
"hash.js": "^1.0.3", "hash.js": "^1.0.3",
"minimalistic-assert": "^1.0.0", "minimalistic-assert": "^1.0.0",
@ -8317,12 +8321,14 @@
"node_modules/minimalistic-assert": { "node_modules/minimalistic-assert": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", "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": { "node_modules/minimalistic-crypto-utils": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", "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": { "node_modules/minimatch": {
"version": "3.0.4", "version": "3.0.4",
@ -8703,11 +8709,6 @@
"integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==",
"dev": true "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": { "node_modules/node-fetch": {
"name": "@achingbrain/node-fetch", "name": "@achingbrain/node-fetch",
"version": "2.6.7", "version": "2.6.7",
@ -8725,16 +8726,6 @@
"node": ">= 6.13.0" "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": { "node_modules/node-preload": {
"version": "0.2.1", "version": "0.2.1",
"resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz", "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz",
@ -10669,20 +10660,6 @@
"url": "https://opencollective.com/webpack" "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": { "node_modules/semver": {
"version": "7.3.5", "version": "7.3.5",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
@ -14803,7 +14780,8 @@
"bn.js": { "bn.js": {
"version": "4.12.0", "version": "4.12.0",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", "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": { "body-parser": {
"version": "1.19.0", "version": "1.19.0",
@ -14901,7 +14879,8 @@
"brorand": { "brorand": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz",
"integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=" "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=",
"dev": true
}, },
"browser-process-hrtime": { "browser-process-hrtime": {
"version": "1.0.0", "version": "1.0.0",
@ -16124,6 +16103,7 @@
"version": "6.5.4", "version": "6.5.4",
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz",
"integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==",
"dev": true,
"requires": { "requires": {
"bn.js": "^4.11.9", "bn.js": "^4.11.9",
"brorand": "^1.1.0", "brorand": "^1.1.0",
@ -17396,6 +17376,7 @@
"version": "1.1.7", "version": "1.1.7",
"resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz",
"integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==",
"dev": true,
"requires": { "requires": {
"inherits": "^2.0.3", "inherits": "^2.0.3",
"minimalistic-assert": "^1.0.1" "minimalistic-assert": "^1.0.1"
@ -17439,6 +17420,7 @@
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
"integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=",
"dev": true,
"requires": { "requires": {
"hash.js": "^1.0.3", "hash.js": "^1.0.3",
"minimalistic-assert": "^1.0.0", "minimalistic-assert": "^1.0.0",
@ -19209,12 +19191,14 @@
"minimalistic-assert": { "minimalistic-assert": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", "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": { "minimalistic-crypto-utils": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", "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": { "minimatch": {
"version": "3.0.4", "version": "3.0.4",
@ -19519,11 +19503,6 @@
"integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==",
"dev": true "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": { "node-fetch": {
"version": "npm:@achingbrain/node-fetch@2.6.7", "version": "npm:@achingbrain/node-fetch@2.6.7",
"resolved": "https://registry.npmjs.org/@achingbrain/node-fetch/-/node-fetch-2.6.7.tgz", "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", "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz",
"integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==" "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": { "node-preload": {
"version": "0.2.1", "version": "0.2.1",
"resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz", "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz",
@ -20990,16 +20964,6 @@
"ajv-keywords": "^3.5.2" "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": { "semver": {
"version": "7.3.5", "version": "7.3.5",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",

View File

@ -61,12 +61,16 @@
"deploy": "node ci/deploy.js", "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" "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": { "engines": {
"node": ">=16" "node": ">=16"
}, },
"dependencies": { "dependencies": {
"@chainsafe/libp2p-noise": "^5.0.0", "@chainsafe/libp2p-noise": "^5.0.0",
"@ethersproject/rlp": "^5.5.0", "@ethersproject/rlp": "^5.5.0",
"@noble/secp256k1": "^1.3.4",
"debug": "^4.3.1", "debug": "^4.3.1",
"dns-query": "^0.8.0", "dns-query": "^0.8.0",
"hi-base32": "^0.5.1", "hi-base32": "^0.5.1",
@ -81,7 +85,6 @@
"multiaddr": "^10.0.1", "multiaddr": "^10.0.1",
"multihashes": "^4.0.3", "multihashes": "^4.0.3",
"protobufjs": "^6.8.8", "protobufjs": "^6.8.8",
"secp256k1": "^4.0.2",
"uuid": "^8.3.2" "uuid": "^8.3.2"
}, },
"devDependencies": { "devDependencies": {
@ -101,7 +104,6 @@
"assert": "^2.0.0", "assert": "^2.0.0",
"buffer": "^6.0.3", "buffer": "^6.0.3",
"chai": "^4.3.4", "chai": "^4.3.4",
"crypto-browserify": "^3.12.0",
"cspell": "^5.14.0", "cspell": "^5.14.0",
"eslint": "^8.6.0", "eslint": "^8.6.0",
"eslint-config-prettier": "^8.3.0", "eslint-config-prettier": "^8.3.0",

View File

@ -1,33 +1,48 @@
import nodeCrypto from "crypto"; import nodeCrypto from "crypto";
// IE 11 import { concat } from "uint8arrays/concat";
declare global {
interface Window {
msCrypto?: Crypto;
}
interface Crypto { declare const self: Record<string, any> | undefined;
webkitSubtle?: SubtleCrypto; 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 = export function randomBytes(bytesLength = 32): Uint8Array {
(typeof window !== "undefined" && if (crypto.web) {
(window as Window) && return crypto.web.getRandomValues(new Uint8Array(bytesLength));
(window.crypto || window.msCrypto)) || } else if (crypto.node) {
(nodeCrypto.webcrypto as unknown as Crypto); const { randomBytes } = crypto.node;
const subtle: SubtleCrypto = crypto.subtle || crypto.webkitSubtle; return Uint8Array.from(randomBytes(bytesLength));
} else {
if (subtle === undefined) { throw new Error(
throw new Error("crypto and/or subtle api unavailable"); "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 async function sha256(...messages: Uint8Array[]): Promise<Uint8Array> {
if (crypto.web) {
export function randomBytes(size: number): Uint8Array { const buffer = await crypto.web.subtle.digest("SHA-256", concat(messages));
return crypto.getRandomValues(new Uint8Array(size)); return new Uint8Array(buffer);
} } else if (crypto.node) {
const { createHash } = crypto.node;
export function sha256(msg: ArrayBufferLike): Promise<ArrayBuffer> { const hash = createHash("sha256");
return subtle.digest({ name: "SHA-256" }, msg); 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 assert from "assert";
import * as secp from "@noble/secp256k1";
import * as base32 from "hi-base32"; import * as base32 from "hi-base32";
import { ecdsaVerify } from "secp256k1";
import { fromString } from "uint8arrays/from-string"; import { fromString } from "uint8arrays/from-string";
import { ENR } from "../enr"; import { ENR } from "../enr";
@ -48,11 +48,17 @@ export class ENRTree {
64 64
); );
const isVerified = ecdsaVerify( let isVerified;
signatureBuffer, try {
keccak256Buf(signedComponentBuffer), const _sig = secp.Signature.fromCompact(signatureBuffer.slice(0, 64));
new Uint8Array(decodedPublicKey) isVerified = secp.verify(
); _sig,
keccak256Buf(signedComponentBuffer),
new Uint8Array(decodedPublicKey)
);
} catch {
isVerified = false;
}
assert(isVerified, "Unable to verify ENRTree root signature"); assert(isVerified, "Unable to verify ENRTree root signature");

View File

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

View File

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

View File

@ -1,8 +1,8 @@
import crypto from "crypto"; import * as secp from "@noble/secp256k1";
import * as secp256k1 from "secp256k1";
import { concat } from "uint8arrays/concat"; import { concat } from "uint8arrays/concat";
import { randomBytes } from "../../crypto";
import { AbstractKeypair, IKeypair, IKeypairClass, KeypairType } from "./types"; import { AbstractKeypair, IKeypair, IKeypairClass, KeypairType } from "./types";
export function secp256k1PublicKeyToCompressed( export function secp256k1PublicKeyToCompressed(
@ -11,18 +11,22 @@ export function secp256k1PublicKeyToCompressed(
if (publicKey.length === 64) { if (publicKey.length === 64) {
publicKey = concat([[4], publicKey], 65); 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 { export function secp256k1PublicKeyToFull(publicKey: Uint8Array): Uint8Array {
if (publicKey.length === 64) { if (publicKey.length === 64) {
publicKey = concat([[4], publicKey], 65); 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 { 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 export const Secp256k1Keypair: IKeypairClass = class Secp256k1Keypair
@ -41,41 +45,44 @@ export const Secp256k1Keypair: IKeypairClass = class Secp256k1Keypair
} }
static async generate(): Promise<Secp256k1Keypair> { static async generate(): Promise<Secp256k1Keypair> {
const privateKey = await randomBytes(32); const privateKey = randomBytes(32);
const publicKey = secp256k1.publicKeyCreate(privateKey); const publicKey = secp.getPublicKey(privateKey);
return new Secp256k1Keypair(privateKey, publicKey); return new Secp256k1Keypair(privateKey, publicKey);
} }
privateKeyVerify(key = this._privateKey): boolean { privateKeyVerify(key = this._privateKey): boolean {
if (key) { if (key) {
return secp256k1.privateKeyVerify(key); return secp.utils.isValidPrivateKey(key);
} }
return true; return true;
} }
publicKeyVerify(key = this._publicKey): boolean { publicKeyVerify(key = this._publicKey): boolean {
if (key) { if (key) {
return secp256k1.publicKeyVerify(key); try {
secp.Point.fromHex(key);
return true;
} catch {
return false;
}
} }
return true; return true;
} }
sign(msg: Uint8Array): Uint8Array { async sign(msg: Uint8Array): Promise<Uint8Array> {
const { signature, recid } = secp256k1.ecdsaSign(msg, this.privateKey); const [signature, recid] = await secp.sign(msg, this.privateKey, {
recovered: true,
der: false,
});
return concat([signature, [recid]], signature.length + 1); return concat([signature, [recid]], signature.length + 1);
} }
verify(msg: Uint8Array, sig: Uint8Array): boolean { 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; publicKey: Uint8Array;
privateKeyVerify(): boolean; privateKeyVerify(): boolean;
publicKeyVerify(): boolean; publicKeyVerify(): boolean;
sign(msg: Uint8Array): Uint8Array; sign(msg: Uint8Array): Promise<Uint8Array>;
verify(msg: Uint8Array, sig: Uint8Array): boolean; verify(msg: Uint8Array, sig: Uint8Array): boolean;
hasPrivateKey(): boolean; hasPrivateKey(): boolean;
} }
@ -29,7 +29,7 @@ export abstract class AbstractKeypair {
throw new Error("Invalid private key"); throw new Error("Invalid private key");
} }
if ((this._publicKey = publicKey) && !this.publicKeyVerify()) { 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 { keccak256 } from "js-sha3";
import * as secp256k1 from "secp256k1";
import { randomBytes } from "../crypto";
import { bytesToHex } from "../utils";
import { createNodeId } from "./create"; import { createNodeId } from "./create";
import { NodeId } from "./types"; import { NodeId } from "./types";
@ -10,17 +11,26 @@ export function hash(input: Uint8Array): Uint8Array {
return new Uint8Array(keccak256.arrayBuffer(input)); return new Uint8Array(keccak256.arrayBuffer(input));
} }
export async function createPrivateKey(): Promise<Uint8Array> { export function createPrivateKey(): Uint8Array {
return randomBytes(32); return randomBytes(32);
} }
export function publicKey(privKey: Uint8Array): Uint8Array { export function publicKey(privKey: Uint8Array): Uint8Array {
return secp256k1.publicKeyCreate(privKey); return secp.getPublicKey(privKey, true);
} }
export function sign(privKey: Uint8Array, msg: Uint8Array): Uint8Array { export function compressPublicKey(publicKey: Uint8Array): Uint8Array {
const { signature } = secp256k1.ecdsaSign(hash(msg), privKey); const point = secp.Point.fromHex(bytesToHex(publicKey));
return signature; 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( export function verify(
@ -28,12 +38,17 @@ export function verify(
msg: Uint8Array, msg: Uint8Array,
sig: Uint8Array sig: Uint8Array
): boolean { ): boolean {
// Remove the recovery id if present (byte #65) try {
return secp256k1.ecdsaVerify(sig.slice(0, 64), hash(msg), pubKey); 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 { 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))); return createNodeId(hash(uncompressedPubkey.slice(1)));
} }
@ -45,20 +60,20 @@ export class ENRKeyPair {
public readonly publicKey: Uint8Array public readonly publicKey: Uint8Array
) {} ) {}
public static async create(privateKey?: Uint8Array): Promise<ENRKeyPair> { public static create(privateKey?: Uint8Array): ENRKeyPair {
if (privateKey) { if (privateKey) {
if (!secp256k1.privateKeyVerify(privateKey)) { if (!secp.utils.isValidPrivateKey(privateKey)) {
throw new Error("Invalid private key"); throw new Error("Invalid private key");
} }
} }
const _privateKey = privateKey || (await createPrivateKey()); const _privateKey = privateKey || createPrivateKey();
const _publicKey = publicKey(_privateKey); const _publicKey = publicKey(_privateKey);
const _nodeId = nodeId(_publicKey); const _nodeId = nodeId(_publicKey);
return new ENRKeyPair(_nodeId, _privateKey, _publicKey); return new ENRKeyPair(_nodeId, _privateKey, _publicKey);
} }
public sign(msg: Uint8Array): Uint8Array { public async sign(msg: Uint8Array): Promise<Uint8Array> {
return sign(this.privateKey, msg); return sign(this.privateKey, msg);
} }
@ -66,13 +81,3 @@ export class ENRKeyPair {
return verify(this.publicKey, msg, sig); 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 * as secp from "@noble/secp256k1";
import { concat } from "uint8arrays/concat"; import { concat } from "uint8arrays/concat";
import { randomBytes, sha256, subtle } from "../crypto"; import { getSubtle, randomBytes, sha256 } from "../crypto";
import { hexToBytes } from "../utils"; import { hexToBytes } from "../utils";
/** /**
* HKDF as implemented in go-ethereum. * HKDF as implemented in go-ethereum.
@ -37,10 +37,10 @@ function aesCtrEncrypt(
key: ArrayBufferLike, key: ArrayBufferLike,
data: ArrayBufferLike data: ArrayBufferLike
): Promise<Uint8Array> { ): Promise<Uint8Array> {
return subtle return getSubtle()
.importKey("raw", key, "AES-CTR", false, ["encrypt"]) .importKey("raw", key, "AES-CTR", false, ["encrypt"])
.then((cryptoKey) => .then((cryptoKey) =>
subtle.encrypt( getSubtle().encrypt(
{ name: "AES-CTR", counter: counter, length: 128 }, { name: "AES-CTR", counter: counter, length: 128 },
cryptoKey, cryptoKey,
data data
@ -54,10 +54,10 @@ function aesCtrDecrypt(
key: ArrayBufferLike, key: ArrayBufferLike,
data: ArrayBufferLike data: ArrayBufferLike
): Promise<Uint8Array> { ): Promise<Uint8Array> {
return subtle return getSubtle()
.importKey("raw", key, "AES-CTR", false, ["decrypt"]) .importKey("raw", key, "AES-CTR", false, ["decrypt"])
.then((cryptoKey) => .then((cryptoKey) =>
subtle.decrypt( getSubtle().decrypt(
{ name: "AES-CTR", counter: counter, length: 128 }, { name: "AES-CTR", counter: counter, length: 128 },
cryptoKey, cryptoKey,
data data
@ -71,9 +71,9 @@ function hmacSha256Sign(
msg: ArrayBufferLike msg: ArrayBufferLike
): PromiseLike<Uint8Array> { ): PromiseLike<Uint8Array> {
const algorithm = { name: "HMAC", hash: { name: "SHA-256" } }; const algorithm = { name: "HMAC", hash: { name: "SHA-256" } };
return subtle return getSubtle()
.importKey("raw", key, algorithm, false, ["sign"]) .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)); .then((bytes) => new Uint8Array(bytes));
} }
@ -83,9 +83,9 @@ function hmacSha256Verify(
sig: ArrayBufferLike sig: ArrayBufferLike
): Promise<boolean> { ): Promise<boolean> {
const algorithm = { name: "HMAC", hash: { name: "SHA-256" } }; 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) => 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( await fc.assert(
fc.asyncProperty( fc.asyncProperty(
fc.uint8Array({ minLength: 1 }), fc.uint8Array({ minLength: 1 }),
fc.uint8Array({ minLength: 32, maxLength: 32 }), fc.uint8Array({ min: 1, minLength: 32, maxLength: 32 }),
async (payload, key) => { async (payload, key) => {
const publicKey = getPublicKey(key); const publicKey = getPublicKey(key);
@ -56,8 +56,8 @@ describe("Waku Message: Browser & Node", function () {
await fc.assert( await fc.assert(
fc.asyncProperty( fc.asyncProperty(
fc.uint8Array({ minLength: 1 }), fc.uint8Array({ minLength: 1 }),
fc.uint8Array({ minLength: 32, maxLength: 32 }), fc.uint8Array({ min: 1, minLength: 32, maxLength: 32 }),
fc.uint8Array({ minLength: 32, maxLength: 32 }), fc.uint8Array({ min: 1, minLength: 32, maxLength: 32 }),
async (payload, sigPrivKey, encPrivKey) => { async (payload, sigPrivKey, encPrivKey) => {
const sigPubKey = getPublicKey(sigPrivKey); const sigPubKey = getPublicKey(sigPrivKey);
const encPubKey = getPublicKey(encPrivKey); const encPubKey = getPublicKey(encPrivKey);

View File

@ -89,12 +89,12 @@ export class WakuMessage {
} }
if (encPublicKey) { 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); _payload = await version_1.encryptAsymmetric(enc.payload, encPublicKey);
sig = enc.sig; sig = enc.sig;
version = 1; version = 1;
} else if (symKey) { } 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); _payload = await version_1.encryptSymmetric(enc.payload, symKey);
sig = enc.sig; sig = enc.sig;
version = 1; 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 () { describe("Waku Message Version 1", function () {
it("Sign & Recover", function () { it("Sign & Recover", function () {
fc.assert( fc.assert(
fc.property( fc.asyncProperty(
fc.uint8Array(), fc.uint8Array(),
fc.uint8Array({ minLength: 32, maxLength: 32 }), fc.uint8Array({ minLength: 32, maxLength: 32 }),
(message, privKey) => { async (message, privKey) => {
const enc = clearEncode(message, privKey); const enc = await clearEncode(message, privKey);
const res = clearDecode(enc.payload); const res = clearDecode(enc.payload);
const pubKey = getPublicKey(privKey); const pubKey = getPublicKey(privKey);
@ -44,7 +44,7 @@ describe("Waku Message Version 1", function () {
await fc.assert( await fc.assert(
fc.asyncProperty( fc.asyncProperty(
fc.uint8Array({ minLength: 1 }), fc.uint8Array({ minLength: 1 }),
fc.uint8Array({ minLength: 32, maxLength: 32 }), fc.uint8Array({ min: 1, minLength: 32, maxLength: 32 }),
async (message, privKey) => { async (message, privKey) => {
const publicKey = getPublicKey(privKey); const publicKey = getPublicKey(privKey);

View File

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

View File

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

View File

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

View File

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