mirror of
https://github.com/gchq/CyberChef.git
synced 2025-04-21 15:26:16 -04:00
Merge e96e31cd52
into d635cca210
This commit is contained in:
commit
cd5bd8114a
41 changed files with 24099 additions and 16405 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -13,3 +13,4 @@ src/node/index.mjs
|
||||||
**/*.DS_Store
|
**/*.DS_Store
|
||||||
tests/browser/output/*
|
tests/browser/output/*
|
||||||
.node-version
|
.node-version
|
||||||
|
build.sh
|
||||||
|
|
|
@ -6,4 +6,4 @@ RUN npm run build
|
||||||
|
|
||||||
FROM nginx:1.25-alpine3.18 AS cyberchef
|
FROM nginx:1.25-alpine3.18 AS cyberchef
|
||||||
|
|
||||||
COPY --from=build ./build/prod /usr/share/nginx/html/
|
COPY --from=build ./build/prod /usr/share/nginx/html/
|
1
LICENSE
1
LICENSE
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
Apache License
|
Apache License
|
||||||
Version 2.0, January 2004
|
Version 2.0, January 2004
|
||||||
http://www.apache.org/licenses/
|
http://www.apache.org/licenses/
|
||||||
|
|
32484
package-lock.json
generated
32484
package-lock.json
generated
File diff suppressed because it is too large
Load diff
400
package.json
400
package.json
|
@ -1,201 +1,203 @@
|
||||||
{
|
{
|
||||||
"name": "cyberchef",
|
"name": "cyberchef",
|
||||||
"version": "10.19.2",
|
"version": "10.19.2",
|
||||||
"description": "The Cyber Swiss Army Knife for encryption, encoding, compression and data analysis.",
|
"description": "The Cyber Swiss Army Knife for encryption, encoding, compression and data analysis.",
|
||||||
"author": "n1474335 <n1474335@gmail.com>",
|
"author": "n1474335 <n1474335@gmail.com>",
|
||||||
"homepage": "https://gchq.github.io/CyberChef",
|
"homepage": "https://gchq.github.io/CyberChef",
|
||||||
"copyright": "Crown copyright 2016",
|
"copyright": "Crown copyright 2016",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"cipher",
|
"cipher",
|
||||||
"cypher",
|
"cypher",
|
||||||
"encode",
|
"encode",
|
||||||
"decode",
|
"decode",
|
||||||
"encrypt",
|
"encrypt",
|
||||||
"decrypt",
|
"decrypt",
|
||||||
"base64",
|
"base64",
|
||||||
"xor",
|
"xor",
|
||||||
"charset",
|
"charset",
|
||||||
"hex",
|
"hex",
|
||||||
"encoding",
|
"encoding",
|
||||||
"format",
|
"format",
|
||||||
"cybersecurity",
|
"cybersecurity",
|
||||||
"data manipulation",
|
"data manipulation",
|
||||||
"data analysis"
|
"data analysis"
|
||||||
],
|
],
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/gchq/CyberChef/"
|
"url": "https://github.com/gchq/CyberChef/"
|
||||||
},
|
},
|
||||||
"main": "src/node/wrapper.js",
|
"main": "src/node/wrapper.js",
|
||||||
"exports": {
|
"exports": {
|
||||||
"import": "./src/node/index.mjs",
|
"import": "./src/node/index.mjs",
|
||||||
"require": "./src/node/wrapper.js"
|
"require": "./src/node/wrapper.js"
|
||||||
},
|
},
|
||||||
"bugs": "https://github.com/gchq/CyberChef/issues",
|
"bugs": "https://github.com/gchq/CyberChef/issues",
|
||||||
"browserslist": [
|
"browserslist": [
|
||||||
"Chrome >= 50",
|
"Chrome >= 50",
|
||||||
"Firefox >= 38",
|
"Firefox >= 38",
|
||||||
"node >= 16"
|
"node >= 16"
|
||||||
],
|
],
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.24.7",
|
"@babel/core": "^7.24.7",
|
||||||
"@babel/eslint-parser": "^7.24.7",
|
"@babel/eslint-parser": "^7.24.7",
|
||||||
"@babel/plugin-syntax-import-assertions": "^7.24.7",
|
"@babel/plugin-syntax-import-assertions": "^7.24.7",
|
||||||
"@babel/plugin-transform-runtime": "^7.24.7",
|
"@babel/plugin-transform-runtime": "^7.24.7",
|
||||||
"@babel/preset-env": "^7.24.7",
|
"@babel/preset-env": "^7.24.7",
|
||||||
"@babel/runtime": "^7.24.7",
|
"@babel/runtime": "^7.24.7",
|
||||||
"@codemirror/commands": "^6.6.0",
|
"@codemirror/commands": "^6.6.0",
|
||||||
"@codemirror/language": "^6.10.2",
|
"@codemirror/language": "^6.10.2",
|
||||||
"@codemirror/search": "^6.5.6",
|
"@codemirror/search": "^6.5.6",
|
||||||
"@codemirror/state": "^6.4.1",
|
"@codemirror/state": "^6.4.1",
|
||||||
"@codemirror/view": "^6.28.0",
|
"@codemirror/view": "^6.28.0",
|
||||||
"autoprefixer": "^10.4.19",
|
"autoprefixer": "^10.4.19",
|
||||||
"babel-loader": "^9.1.3",
|
"babel-loader": "^9.1.3",
|
||||||
"babel-plugin-dynamic-import-node": "^2.3.3",
|
"babel-plugin-dynamic-import-node": "^2.3.3",
|
||||||
"babel-plugin-transform-builtin-extend": "1.1.2",
|
"babel-plugin-transform-builtin-extend": "1.1.2",
|
||||||
"base64-loader": "^1.0.0",
|
"base64-loader": "^1.0.0",
|
||||||
"chromedriver": "^127.0.2",
|
"cli-progress": "^3.12.0",
|
||||||
"cli-progress": "^3.12.0",
|
"colors": "^1.4.0",
|
||||||
"colors": "^1.4.0",
|
"copy-webpack-plugin": "^12.0.2",
|
||||||
"copy-webpack-plugin": "^12.0.2",
|
"core-js": "^3.37.1",
|
||||||
"core-js": "^3.37.1",
|
"css-loader": "7.1.2",
|
||||||
"css-loader": "7.1.2",
|
"eslint": "^9.4.0",
|
||||||
"eslint": "^9.4.0",
|
"eslint-plugin-jsdoc": "^48.2.9",
|
||||||
"eslint-plugin-jsdoc": "^48.2.9",
|
"globals": "^15.4.0",
|
||||||
"globals": "^15.4.0",
|
"grunt": "^1.6.1",
|
||||||
"grunt": "^1.6.1",
|
"grunt-chmod": "~1.1.1",
|
||||||
"grunt-chmod": "~1.1.1",
|
"grunt-concurrent": "^3.0.0",
|
||||||
"grunt-concurrent": "^3.0.0",
|
"grunt-contrib-clean": "~2.0.1",
|
||||||
"grunt-contrib-clean": "~2.0.1",
|
"grunt-contrib-connect": "^4.0.0",
|
||||||
"grunt-contrib-connect": "^4.0.0",
|
"grunt-contrib-copy": "~1.0.0",
|
||||||
"grunt-contrib-copy": "~1.0.0",
|
"grunt-contrib-watch": "^1.1.0",
|
||||||
"grunt-contrib-watch": "^1.1.0",
|
"grunt-eslint": "^25.0.0",
|
||||||
"grunt-eslint": "^25.0.0",
|
"grunt-exec": "~3.0.0",
|
||||||
"grunt-exec": "~3.0.0",
|
"grunt-webpack": "^6.0.0",
|
||||||
"grunt-webpack": "^6.0.0",
|
"grunt-zip": "^1.0.0",
|
||||||
"grunt-zip": "^1.0.0",
|
"html-webpack-plugin": "^5.6.0",
|
||||||
"html-webpack-plugin": "^5.6.0",
|
"imports-loader": "^5.0.0",
|
||||||
"imports-loader": "^5.0.0",
|
"mini-css-extract-plugin": "2.9.0",
|
||||||
"mini-css-extract-plugin": "2.9.0",
|
"modify-source-webpack-plugin": "^4.1.0",
|
||||||
"modify-source-webpack-plugin": "^4.1.0",
|
"nightwatch": "^3.6.3",
|
||||||
"nightwatch": "^3.6.3",
|
"postcss": "^8.4.38",
|
||||||
"postcss": "^8.4.38",
|
"postcss-css-variables": "^0.19.0",
|
||||||
"postcss-css-variables": "^0.19.0",
|
"postcss-import": "^16.1.0",
|
||||||
"postcss-import": "^16.1.0",
|
"postcss-loader": "^8.1.1",
|
||||||
"postcss-loader": "^8.1.1",
|
"prompt": "^1.3.0",
|
||||||
"prompt": "^1.3.0",
|
"sitemap": "^8.0.0",
|
||||||
"sitemap": "^8.0.0",
|
"terser": "^5.31.1",
|
||||||
"terser": "^5.31.1",
|
"webpack": "^5.91.0",
|
||||||
"webpack": "^5.91.0",
|
"webpack-bundle-analyzer": "^4.10.2",
|
||||||
"webpack-bundle-analyzer": "^4.10.2",
|
"webpack-dev-server": "5.0.4",
|
||||||
"webpack-dev-server": "5.0.4",
|
"webpack-node-externals": "^3.0.0",
|
||||||
"webpack-node-externals": "^3.0.0",
|
"worker-loader": "^3.0.8"
|
||||||
"worker-loader": "^3.0.8"
|
},
|
||||||
},
|
"dependencies": {
|
||||||
"dependencies": {
|
"@astronautlabs/amf": "^0.0.6",
|
||||||
"@astronautlabs/amf": "^0.0.6",
|
"@babel/polyfill": "^7.12.1",
|
||||||
"@babel/polyfill": "^7.12.1",
|
"@bitcoinerlab/secp256k1": "^1.1.1",
|
||||||
"@blu3r4y/lzma": "^2.3.3",
|
"@blu3r4y/lzma": "^2.3.3",
|
||||||
"@wavesenterprise/crypto-gost-js": "^2.1.0-RC1",
|
"@wavesenterprise/crypto-gost-js": "^2.1.0-RC1",
|
||||||
"@xmldom/xmldom": "^0.8.10",
|
"@xmldom/xmldom": "^0.8.10",
|
||||||
"argon2-browser": "^1.18.0",
|
"argon2-browser": "^1.18.0",
|
||||||
"arrive": "^2.4.1",
|
"arrive": "^2.4.1",
|
||||||
"avsc": "^5.7.7",
|
"avsc": "^5.7.7",
|
||||||
"bcryptjs": "^2.4.3",
|
"bcryptjs": "^2.4.3",
|
||||||
"bignumber.js": "^9.1.2",
|
"bignumber.js": "^9.1.2",
|
||||||
"blakejs": "^1.2.1",
|
"bip32": "^5.0.0-rc.0",
|
||||||
"bootstrap": "4.6.2",
|
"blakejs": "^1.2.1",
|
||||||
"bootstrap-colorpicker": "^3.4.0",
|
"bootstrap": "4.6.2",
|
||||||
"bootstrap-material-design": "^4.1.3",
|
"bootstrap-colorpicker": "^3.4.0",
|
||||||
"browserify-zlib": "^0.2.0",
|
"bootstrap-material-design": "^4.1.3",
|
||||||
"bson": "^4.7.2",
|
"browserify-zlib": "^0.2.0",
|
||||||
"buffer": "^6.0.3",
|
"bson": "^4.7.2",
|
||||||
"cbor": "9.0.2",
|
"buffer": "^6.0.3",
|
||||||
"chi-squared": "^1.1.0",
|
"cbor": "9.0.2",
|
||||||
"codepage": "^1.15.0",
|
"chi-squared": "^1.1.0",
|
||||||
"crypto-api": "^0.8.5",
|
"chromedriver": "^129.0.0",
|
||||||
"crypto-browserify": "^3.12.0",
|
"codepage": "^1.15.0",
|
||||||
"crypto-js": "^4.2.0",
|
"crypto-api": "^0.8.5",
|
||||||
"ctph.js": "0.0.5",
|
"crypto-browserify": "^3.12.0",
|
||||||
"d3": "7.9.0",
|
"crypto-js": "^4.2.0",
|
||||||
"d3-hexbin": "^0.2.2",
|
"ctph.js": "0.0.5",
|
||||||
"diff": "^5.2.0",
|
"d3": "7.9.0",
|
||||||
"es6-promisify": "^7.0.0",
|
"d3-hexbin": "^0.2.2",
|
||||||
"escodegen": "^2.1.0",
|
"diff": "^5.2.0",
|
||||||
"esprima": "^4.0.1",
|
"es6-promisify": "^7.0.0",
|
||||||
"exif-parser": "^0.1.12",
|
"escodegen": "^2.1.0",
|
||||||
"fernet": "^0.4.0",
|
"esprima": "^4.0.1",
|
||||||
"file-saver": "^2.0.5",
|
"exif-parser": "^0.1.12",
|
||||||
"flat": "^6.0.1",
|
"fernet": "^0.4.0",
|
||||||
"geodesy": "1.1.3",
|
"file-saver": "^2.0.5",
|
||||||
"highlight.js": "^11.9.0",
|
"flat": "^6.0.1",
|
||||||
"ieee754": "^1.2.1",
|
"geodesy": "1.1.3",
|
||||||
"jimp": "^0.22.12",
|
"highlight.js": "^11.9.0",
|
||||||
"jquery": "3.7.1",
|
"ieee754": "^1.2.1",
|
||||||
"js-crc": "^0.2.0",
|
"jimp": "^0.22.12",
|
||||||
"js-sha3": "^0.9.3",
|
"jquery": "3.7.1",
|
||||||
"jsesc": "^3.0.2",
|
"js-crc": "^0.2.0",
|
||||||
"json5": "^2.2.3",
|
"js-sha3": "^0.9.3",
|
||||||
"jsonpath-plus": "^9.0.0",
|
"jsesc": "^3.0.2",
|
||||||
"jsonwebtoken": "8.5.1",
|
"json5": "^2.2.3",
|
||||||
"jsqr": "^1.4.0",
|
"jsonpath-plus": "^9.0.0",
|
||||||
"jsrsasign": "^11.1.0",
|
"jsonwebtoken": "8.5.1",
|
||||||
"kbpgp": "2.1.15",
|
"jsqr": "^1.4.0",
|
||||||
"libbzip2-wasm": "0.0.4",
|
"jsrsasign": "^11.1.0",
|
||||||
"libyara-wasm": "^1.2.1",
|
"kbpgp": "2.1.15",
|
||||||
"lodash": "^4.17.21",
|
"libbzip2-wasm": "0.0.4",
|
||||||
"loglevel": "^1.9.1",
|
"libyara-wasm": "^1.2.1",
|
||||||
"loglevel-message-prefix": "^3.0.0",
|
"lodash": "^4.17.21",
|
||||||
"lz-string": "^1.5.0",
|
"loglevel": "^1.9.1",
|
||||||
"lz4js": "^0.2.0",
|
"loglevel-message-prefix": "^3.0.0",
|
||||||
"markdown-it": "^14.1.0",
|
"lz-string": "^1.5.0",
|
||||||
"moment": "^2.30.1",
|
"lz4js": "^0.2.0",
|
||||||
"moment-timezone": "^0.5.45",
|
"markdown-it": "^14.1.0",
|
||||||
"ngeohash": "^0.6.3",
|
"moment": "^2.30.1",
|
||||||
"node-forge": "^1.3.1",
|
"moment-timezone": "^0.5.45",
|
||||||
"node-md6": "^0.1.0",
|
"ngeohash": "^0.6.3",
|
||||||
"nodom": "^2.4.0",
|
"node-forge": "^1.3.1",
|
||||||
"notepack.io": "^3.0.1",
|
"node-md6": "^0.1.0",
|
||||||
"ntlm": "^0.1.3",
|
"nodom": "^2.4.0",
|
||||||
"nwmatcher": "^1.4.4",
|
"notepack.io": "^3.0.1",
|
||||||
"otp": "0.1.3",
|
"ntlm": "^0.1.3",
|
||||||
"path": "^0.12.7",
|
"nwmatcher": "^1.4.4",
|
||||||
"popper.js": "^1.16.1",
|
"otp": "0.1.3",
|
||||||
"process": "^0.11.10",
|
"path": "^0.12.7",
|
||||||
"protobufjs": "^7.3.1",
|
"popper.js": "^1.16.1",
|
||||||
"qr-image": "^3.2.0",
|
"process": "^0.11.10",
|
||||||
"reflect-metadata": "^0.2.2",
|
"protobufjs": "^7.3.1",
|
||||||
"rison": "^0.1.1",
|
"qr-image": "^3.2.0",
|
||||||
"scryptsy": "^2.1.0",
|
"reflect-metadata": "^0.2.2",
|
||||||
"snackbarjs": "^1.1.0",
|
"rison": "^0.1.1",
|
||||||
"sortablejs": "^1.15.2",
|
"scryptsy": "^2.1.0",
|
||||||
"split.js": "^1.6.5",
|
"snackbarjs": "^1.1.0",
|
||||||
"ssdeep.js": "0.0.3",
|
"sortablejs": "^1.15.2",
|
||||||
"stream-browserify": "^3.0.0",
|
"split.js": "^1.6.5",
|
||||||
"tesseract.js": "5.1.0",
|
"ssdeep.js": "0.0.3",
|
||||||
"ua-parser-js": "^1.0.38",
|
"stream-browserify": "^3.0.0",
|
||||||
"unorm": "^1.6.0",
|
"tesseract.js": "5.1.0",
|
||||||
"utf8": "^3.0.0",
|
"ua-parser-js": "^1.0.38",
|
||||||
"vkbeautify": "^0.99.3",
|
"unorm": "^1.6.0",
|
||||||
"xpath": "0.0.34",
|
"utf8": "^3.0.0",
|
||||||
"xregexp": "^5.1.1",
|
"vkbeautify": "^0.99.3",
|
||||||
"zlibjs": "^0.3.1"
|
"xpath": "0.0.34",
|
||||||
},
|
"xregexp": "^5.1.1",
|
||||||
"scripts": {
|
"zlibjs": "^0.3.1"
|
||||||
"start": "npx grunt dev",
|
},
|
||||||
"build": "npx grunt prod",
|
"scripts": {
|
||||||
"node": "npx grunt node",
|
"start": "npx grunt dev",
|
||||||
"repl": "node --experimental-modules --experimental-json-modules --experimental-specifier-resolution=node --no-experimental-fetch --no-warnings src/node/repl.mjs",
|
"build": "npx grunt prod",
|
||||||
"test": "npx grunt configTests && node --experimental-modules --experimental-json-modules --no-warnings --no-deprecation --openssl-legacy-provider --no-experimental-fetch tests/node/index.mjs && node --experimental-modules --experimental-json-modules --no-warnings --no-deprecation --openssl-legacy-provider --no-experimental-fetch --trace-uncaught tests/operations/index.mjs",
|
"node": "npx grunt node",
|
||||||
"testnodeconsumer": "npx grunt testnodeconsumer",
|
"repl": "node --experimental-modules --experimental-json-modules --experimental-specifier-resolution=node --no-experimental-fetch --no-warnings src/node/repl.mjs",
|
||||||
"testui": "npx grunt testui",
|
"test": "npx grunt configTests && node --experimental-modules --experimental-json-modules --no-warnings --no-deprecation --openssl-legacy-provider --no-experimental-fetch tests/node/index.mjs && node --experimental-modules --experimental-json-modules --no-warnings --no-deprecation --openssl-legacy-provider --no-experimental-fetch --trace-uncaught tests/operations/index.mjs",
|
||||||
"testuidev": "npx nightwatch --env=dev",
|
"testnodeconsumer": "npx grunt testnodeconsumer",
|
||||||
"lint": "npx grunt lint",
|
"testui": "npx grunt testui",
|
||||||
"postinstall": "npx grunt exec:fixCryptoApiImports && npx grunt exec:fixSnackbarMarkup && npx grunt exec:fixJimpModule",
|
"testuidev": "npx nightwatch --env=dev",
|
||||||
"newop": "node --experimental-modules --experimental-json-modules src/core/config/scripts/newOperation.mjs",
|
"lint": "npx grunt lint",
|
||||||
"minor": "node --experimental-modules --experimental-json-modules src/core/config/scripts/newMinorVersion.mjs",
|
"postinstall": "npx grunt exec:fixCryptoApiImports && npx grunt exec:fixSnackbarMarkup && npx grunt exec:fixJimpModule",
|
||||||
"getheapsize": "node -e 'console.log(`node heap limit = ${require(\"v8\").getHeapStatistics().heap_size_limit / (1024 * 1024)} Mb`)'",
|
"newop": "node --experimental-modules --experimental-json-modules src/core/config/scripts/newOperation.mjs",
|
||||||
"setheapsize": "export NODE_OPTIONS=--max_old_space_size=2048"
|
"minor": "node --experimental-modules --experimental-json-modules src/core/config/scripts/newMinorVersion.mjs",
|
||||||
}
|
"getheapsize": "node -e 'console.log(`node heap limit = ${require(\"v8\").getHeapStatistics().heap_size_limit / (1024 * 1024)} Mb`)'",
|
||||||
|
"setheapsize": "export NODE_OPTIONS=--max_old_space_size=2048"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -338,6 +338,26 @@
|
||||||
"Sleep"
|
"Sleep"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "Cryptocurrency",
|
||||||
|
"ops": [
|
||||||
|
"Extract Double SHA Artifacts",
|
||||||
|
"Extract Segwit Addresses",
|
||||||
|
"Extract Seedphrases",
|
||||||
|
"Deserialize Extended Key",
|
||||||
|
"Public Key To Cryptocurrency Address",
|
||||||
|
"To WIF Format",
|
||||||
|
"From WIF Format",
|
||||||
|
"Type Cryptocurrency Artifact",
|
||||||
|
"Private EC Key to Public Key",
|
||||||
|
"Seedphrase To Seed",
|
||||||
|
"Change Extended Key Version",
|
||||||
|
"Seed To Master Key",
|
||||||
|
"Decrypt Keystore File",
|
||||||
|
"BIP32Derive",
|
||||||
|
"Public Key To ETH Style Address"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "Extractors",
|
"name": "Extractors",
|
||||||
"ops": [
|
"ops": [
|
||||||
|
|
328
src/core/lib/Bech32.mjs
Normal file
328
src/core/lib/Bech32.mjs
Normal file
|
@ -0,0 +1,328 @@
|
||||||
|
/**
|
||||||
|
* Bech32 Encoding and Decoding resources (BIP0173 and BIP0350)
|
||||||
|
*
|
||||||
|
* @author dgoldenberg [virtualcurrency@mitre.org]
|
||||||
|
* @copyright MITRE 2023, geco 2019
|
||||||
|
* @license MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
// ################################################ BEGIN SEGWIT DECODING FUNCTIONS #################################################
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Javascript code below taken from:
|
||||||
|
* https://github.com/geco/bech32-js/blob/master/bech32-js.js
|
||||||
|
* Implements various segwit encoding / decoding functions.
|
||||||
|
*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2019 geco
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Segwit alphabet
|
||||||
|
const ALPHABET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l";
|
||||||
|
|
||||||
|
const ALPHABET_MAP = {};
|
||||||
|
for (let z = 0; z < ALPHABET.length; z++) {
|
||||||
|
const x = ALPHABET.charAt(z);
|
||||||
|
ALPHABET_MAP[x] = z;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Polynomial multiply step.
|
||||||
|
* Input value is viewed as 32 bit int.
|
||||||
|
* Constants taken from the BIP0173 wiki: https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki
|
||||||
|
* They are part of the BCH code generator polynomial.
|
||||||
|
* @param {string} pre
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
function polymodStep (pre) {
|
||||||
|
const b = pre >> 25;
|
||||||
|
return ((pre & 0x1FFFFFF) << 5) ^
|
||||||
|
(-((b >> 0) & 1) & 0x3b6a57b2) ^
|
||||||
|
(-((b >> 1) & 1) & 0x26508e6d) ^
|
||||||
|
(-((b >> 2) & 1) & 0x1ea119fa) ^
|
||||||
|
(-((b >> 3) & 1) & 0x3d4233dd) ^
|
||||||
|
(-((b >> 4) & 1) & 0x2a1462b3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks the prefix of a string.
|
||||||
|
* @param {*} prefix
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
function prefixChk (prefix) {
|
||||||
|
let chk = 1;
|
||||||
|
for (let i = 0; i < prefix.length; ++i) {
|
||||||
|
const c = prefix.charCodeAt(i);
|
||||||
|
if (c < 33 || c > 126) return "KO";
|
||||||
|
chk = polymodStep(chk) ^ (c >> 5);
|
||||||
|
}
|
||||||
|
chk = polymodStep(chk);
|
||||||
|
|
||||||
|
for (let i = 0; i < prefix.length; ++i) {
|
||||||
|
const v = prefix.charCodeAt(i);
|
||||||
|
chk = polymodStep(chk) ^ (v & 0x1f);
|
||||||
|
}
|
||||||
|
return chk;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bech32 Checksum
|
||||||
|
* We check the entire string to see if its segwit encoded.
|
||||||
|
* Lengths and other constants taken from BIP 0173: https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki
|
||||||
|
*
|
||||||
|
* @param {*} str
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
function checkbech32 (str) {
|
||||||
|
const LIMIT = 90;
|
||||||
|
if (str.length < 8) return "KO";
|
||||||
|
if (str.length > LIMIT) return "KO";
|
||||||
|
|
||||||
|
|
||||||
|
const split = str.lastIndexOf("1");
|
||||||
|
if (split === -1) return "KO";
|
||||||
|
if (split === 0) return "KO";
|
||||||
|
|
||||||
|
const prefix = str.slice(0, split);
|
||||||
|
const wordChars = str.slice(split + 1);
|
||||||
|
if (wordChars.length < 6) return "KO";
|
||||||
|
|
||||||
|
let chk = prefixChk(prefix);
|
||||||
|
if (typeof chk === "string") return "KO";
|
||||||
|
|
||||||
|
const words = [];
|
||||||
|
for (let i = 0; i < wordChars.length; ++i) {
|
||||||
|
const c = wordChars.charAt(i);
|
||||||
|
const v = ALPHABET_MAP[c];
|
||||||
|
if (v === undefined) return "KO";
|
||||||
|
chk = polymodStep(chk) ^ v;
|
||||||
|
if (i + 6 >= wordChars.length) continue;
|
||||||
|
words.push(v);
|
||||||
|
}
|
||||||
|
// Second number is decimal representation of 0x2bc830a3
|
||||||
|
// Useful as P2TR addresses are segwit encoded, with different final checksum.
|
||||||
|
// Taken from https://github.com/bitcoin/bips/blob/master/bip-0350.mediawiki
|
||||||
|
if (chk === 1 || chk === 734539939) {
|
||||||
|
return "OK";
|
||||||
|
} else {
|
||||||
|
return "KO";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ################################################ END SEGWIT DECODING FUNCTIONS ###################################################
|
||||||
|
|
||||||
|
// ################################################ BEGIN MAIN CHECKSUM FUNCTIONS ###################################################
|
||||||
|
|
||||||
|
// Segwit Checksum
|
||||||
|
/**
|
||||||
|
* Segwit Checksum
|
||||||
|
* @param {*} str
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function segwitChecksum(str) {
|
||||||
|
return (checkbech32(str) === "OK");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ################################################ END MAIN CHECKSUM FUNCTIONS #####################################################
|
||||||
|
|
||||||
|
|
||||||
|
// ################################################ BEGIN SEGWIT ENCODING FUNCTIONS #################################################
|
||||||
|
|
||||||
|
// We use this to encode values into segwit encoding.
|
||||||
|
// Taken from https://github.com/sipa/bech32/blob/master/ref/javascript/bech32.js
|
||||||
|
|
||||||
|
// Copyright (c) 2017, 2021 Pieter Wuille
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expands the human readable part.
|
||||||
|
* @param {string} hrp
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
function hrpExpand (hrp) {
|
||||||
|
const ret = [];
|
||||||
|
let p;
|
||||||
|
for (p = 0; p < hrp.length; ++p) {
|
||||||
|
ret.push(hrp.charCodeAt(p) >> 5);
|
||||||
|
}
|
||||||
|
ret.push(0);
|
||||||
|
for (p = 0; p < hrp.length; ++p) {
|
||||||
|
ret.push(hrp.charCodeAt(p) & 31);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
const encodings = {
|
||||||
|
BECH32: "bech32",
|
||||||
|
BECH32M: "bech32m",
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We get the encoding constant.
|
||||||
|
* Differentiates between Segwit and P2TR.
|
||||||
|
* Constants found in BIP0173: https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki
|
||||||
|
* Also BIP0350: https://github.com/bitcoin/bips/blob/master/bip-0350.mediawiki
|
||||||
|
* @param {string} enc
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
function getEncodingConst (enc) {
|
||||||
|
if (enc === encodings.BECH32) {
|
||||||
|
return 1;
|
||||||
|
} else if (enc === encodings.BECH32M) {
|
||||||
|
return 0x2bc830a3;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Constants for the BIP0173 BCH Generator polynomial.
|
||||||
|
const GENERATOR = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Separate version of the polymod step. Taken from https://github.com/sipa/bech32/blob/master/ref/javascript/bech32.js
|
||||||
|
* Here its an array of values.
|
||||||
|
* @param {} values
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
function polymod (values) {
|
||||||
|
let chk = 1;
|
||||||
|
for (let p = 0; p < values.length; ++p) {
|
||||||
|
const top = chk >> 25;
|
||||||
|
chk = (chk & 0x1ffffff) << 5 ^ values[p];
|
||||||
|
for (let i = 0; i < 5; ++i) {
|
||||||
|
if ((top >> i) & 1) {
|
||||||
|
chk ^= GENERATOR[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return chk;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the Segwit checksum
|
||||||
|
* @param {string} hrp
|
||||||
|
* @param {string} data
|
||||||
|
* @param {string} enc
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
function createChecksum (hrp, data, enc) {
|
||||||
|
const values = hrpExpand(hrp).concat(data).concat([0, 0, 0, 0, 0, 0]);
|
||||||
|
const mod = polymod(values) ^ getEncodingConst(enc);
|
||||||
|
const ret = [];
|
||||||
|
for (let p = 0; p < 6; ++p) {
|
||||||
|
ret.push((mod >> 5 * (5 - p)) & 31);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts bits from base 5 to base 8 or back again as appropriate.
|
||||||
|
* @param {*} data
|
||||||
|
* @param {*} frombits
|
||||||
|
* @param {*} tobits
|
||||||
|
* @param {*} pad
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
function convertbits (data, frombits, tobits, pad) {
|
||||||
|
let acc = 0;
|
||||||
|
let bits = 0;
|
||||||
|
const ret = [];
|
||||||
|
const maxv = (1 << tobits) - 1;
|
||||||
|
for (let p = 0; p < data.length; ++p) {
|
||||||
|
const value = data[p];
|
||||||
|
if (value < 0 || (value >> frombits) !== 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
acc = (acc << frombits) | value;
|
||||||
|
bits += frombits;
|
||||||
|
while (bits >= tobits) {
|
||||||
|
bits -= tobits;
|
||||||
|
ret.push((acc >> bits) & maxv);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (pad) {
|
||||||
|
if (bits > 0) {
|
||||||
|
ret.push((acc << (tobits - bits)) & maxv);
|
||||||
|
}
|
||||||
|
} else if (bits >= frombits || ((acc << (tobits - bits)) & maxv)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function to encode data into a segwit address.
|
||||||
|
* We take in the human readable part, the data, and whether its P2TR or Segwit.
|
||||||
|
* @param {string} hrp
|
||||||
|
* @param {string} data
|
||||||
|
* @param {string} enc
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
function segwitEncode (hrp, data, enc) {
|
||||||
|
const combined = data.concat(createChecksum(hrp, data, enc));
|
||||||
|
let ret = hrp + "1";
|
||||||
|
for (let p = 0; p < combined.length; ++p) {
|
||||||
|
ret += ALPHABET.charAt(combined[p]);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Turns the public key (as 'program') into the address.
|
||||||
|
* @param {*} hrp
|
||||||
|
* @param {*} version
|
||||||
|
* @param {*} program
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function encodeProgramToSegwit (hrp, version, program) {
|
||||||
|
let enc;
|
||||||
|
if (version > 0) {
|
||||||
|
enc = encodings.BECH32M;
|
||||||
|
} else {
|
||||||
|
enc = encodings.BECH32;
|
||||||
|
}
|
||||||
|
const convertedbits = convertbits(program, 8, 5, true);
|
||||||
|
const intermediate = [version].concat(convertedbits);
|
||||||
|
const ret = segwitEncode(hrp, intermediate, enc);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ################################################ END SEGWIT ENCODING FUNCTIONS ###################################################
|
520
src/core/lib/Bitcoin.mjs
Normal file
520
src/core/lib/Bitcoin.mjs
Normal file
|
@ -0,0 +1,520 @@
|
||||||
|
/**
|
||||||
|
* Many Bitcoin specific function. Base58, Extended Key functions and other utility functions
|
||||||
|
*
|
||||||
|
* @author dgoldenberg [virtualcurrency@mitre.org]
|
||||||
|
* @copyright MITRE 2023
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import CryptoApi from "crypto-api/src/crypto-api.mjs";
|
||||||
|
import { fromArrayBuffer } from "crypto-api/src/encoder/array-buffer.mjs";
|
||||||
|
import {toHex} from "crypto-api/src/encoder/hex.mjs";
|
||||||
|
import Utils from "../Utils.mjs";
|
||||||
|
import OperationError from "../errors/OperationError.mjs";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates the length of the passed in input as one of the allowable lengths.
|
||||||
|
* @param {*} input
|
||||||
|
* @param {*} allowableLengths
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
function validateLengths(input, allowableLengths) {
|
||||||
|
return allowableLengths.includes(input.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if input is a valid hex string, false otherwise.
|
||||||
|
* @param {*} input
|
||||||
|
*/
|
||||||
|
function isHex(input) {
|
||||||
|
const re = /^[0-9A-Fa-f]{2,}$/g;
|
||||||
|
return re.test(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if input could be interpreted as a byte string, false otherwise.
|
||||||
|
*/
|
||||||
|
function isValidBytes(input) {
|
||||||
|
for (let i=0; i < input.length; i ++) {
|
||||||
|
if (input.charCodeAt(i) > 255) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We validate a passed in input to see if it could be a valid private key.
|
||||||
|
* A valid private key is string of length 64 that is valid hex, or of length 32 that could be valid bytes.
|
||||||
|
* @param {*} input
|
||||||
|
*/
|
||||||
|
export function validatePrivateKey(input) {
|
||||||
|
const curInput = input.trim();
|
||||||
|
if (!validateLengths(curInput, [32, 64])) {
|
||||||
|
return "Invalid length. We want either 32 or 64 but we got: " + curInput.length;
|
||||||
|
}
|
||||||
|
if (curInput.length === 64 && !isHex(curInput)) {
|
||||||
|
return "We have a string of length 64, but not valid hex. Cannot be interpreted as a private key.";
|
||||||
|
}
|
||||||
|
if (curInput.length === 32 && !isValidBytes(curInput)) {
|
||||||
|
return "We have a string of length 32 but cannot cannot be interpreted as valid bytes.";
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We validate a passed in input to see if it could be a valid public key.
|
||||||
|
* A valid public key (in bytes) is either:
|
||||||
|
* 65 bytes beginning with 04
|
||||||
|
* 33 bytes beginning with 02 or 03
|
||||||
|
* @param {*} input
|
||||||
|
*/
|
||||||
|
export function validatePublicKey(input) {
|
||||||
|
const curInput = input.trim();
|
||||||
|
if (!validateLengths(curInput, [33, 65, 66, 130])) {
|
||||||
|
return "Invalid length. We want either 33, 65 (if bytes) or 66, 130 (if hex) but we got: " + curInput.length;
|
||||||
|
}
|
||||||
|
if (isHex(curInput)) {
|
||||||
|
if (!validateLengths(curInput, [66, 130])) {
|
||||||
|
return "We have a hex string, but its length is wrong. We want 66, 130 but we got: " + curInput.length;
|
||||||
|
}
|
||||||
|
if (curInput.length === 66 && (curInput.slice(0, 2) !== "02" && curInput.slice(0, 2) !== "03")) {
|
||||||
|
return "We have a valid hex string, of reasonable length, (66) but doesn't start with the right value. Correct values are 02, or 03 but we have: " + curInput.slice(0, 2);
|
||||||
|
}
|
||||||
|
if (curInput.length === 130 && curInput.slice(0, 2) !== "04") {
|
||||||
|
return "We have a valid hex string of reasonable length, (130) but doesn't start with the right value. Correct values are 04 but we have: " + curInput.slice(0, 2);
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
if (isValidBytes(curInput)) {
|
||||||
|
if (!validateLengths(curInput, [33, 65])) {
|
||||||
|
return "We have a byte string, but its length is wrong. We want 33 or 65 but we got: " + curInput.length;
|
||||||
|
}
|
||||||
|
if (curInput.length === 33 && toHex(curInput[0]) !== "02" && toHex(curInput[0]) !== "03") {
|
||||||
|
return "We have a valid byte string, of reasonable length, (33) but doesn't start with the right value. Correct values are 02, or 03 but we have: " + toHex(curInput[0]) ;
|
||||||
|
}
|
||||||
|
if (curInput.length === 65 && toHex(curInput[0]) !== "04") {
|
||||||
|
return "We have a valid byte string, of reasonable length, (65) but doesn't start with the right value. Correct value is 04 but we have: " + toHex(curInput[0]);
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We make sure the input is a valid hex string, regardless of if its hex or bytes.
|
||||||
|
* If not valid bytes or hex, we throw TypeError.
|
||||||
|
* @param {*} input
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function makeSureIsHex(input) {
|
||||||
|
if (!(isValidBytes(input)) && !(isHex(input))) {
|
||||||
|
throw TypeError("Input: " + input + " is not valid bytes or hex.");
|
||||||
|
}
|
||||||
|
if (isValidBytes(input) && !isHex(input)) {
|
||||||
|
return toHex(input);
|
||||||
|
}
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We make sure the input is valid bytes, regardless of if its hex or bytes.
|
||||||
|
* If not valid bytes or hex, we throw TypeError.
|
||||||
|
* @param {*} input
|
||||||
|
*/
|
||||||
|
export function makeSureIsBytes(input) {
|
||||||
|
if (!(isValidBytes(input)) && !(isHex(input))) {
|
||||||
|
throw TypeError("Input: " + input + " is not valid bytes or hex.");
|
||||||
|
}
|
||||||
|
if (isHex(input)) {
|
||||||
|
return fromArrayBuffer(Utils.convertToByteArray(input, "hex"));
|
||||||
|
}
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ################################################ BEGIN HELPER HASH FUNCTIONS #################################################
|
||||||
|
|
||||||
|
// SHA256(SHA256(input))
|
||||||
|
/**
|
||||||
|
* Double SHA256 hash the passed in string.
|
||||||
|
* @param {string} input
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function doubleSHA(input) {
|
||||||
|
const hasher= CryptoApi.getHasher("sha256");
|
||||||
|
hasher.update(input);
|
||||||
|
const result = hasher.finalize();
|
||||||
|
const hasher2 = CryptoApi.getHasher("sha256");
|
||||||
|
hasher2.update(result);
|
||||||
|
return hasher2.finalize();
|
||||||
|
}
|
||||||
|
|
||||||
|
// RIPEMD160(SHA256(input))
|
||||||
|
/**
|
||||||
|
* Performs the RIPEMD_160(SHA256(input)) hash. This is a common hash pattern in cryptocurrency.
|
||||||
|
* @param {string} input
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function hash160Func(input) {
|
||||||
|
const sha256Hasher= CryptoApi.getHasher("sha256");
|
||||||
|
sha256Hasher.update(input);
|
||||||
|
const sha256hash = sha256Hasher.finalize();
|
||||||
|
const ripemdHasher=CryptoApi.getHasher("ripemd160");
|
||||||
|
ripemdHasher.update(sha256hash);
|
||||||
|
return ripemdHasher.finalize();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ################################################ END HELPER HASH FUNCTIONS ###################################################
|
||||||
|
|
||||||
|
|
||||||
|
// ################################################ BEGIN BASE58 FUNCTIONS ######################################################
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Taken and modified from the ToBase58 op.
|
||||||
|
* We need this code as the operation code isn't exportable / easily available to other functions.
|
||||||
|
* We don't remove non Base58 characters, (we assume this must be done earlier) and we stick to only the Bitcoin alphabet here.
|
||||||
|
* @param {*} input
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function base58Encode (input) {
|
||||||
|
let alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
|
||||||
|
input = new Uint8Array(input);
|
||||||
|
|
||||||
|
alphabet = Utils.expandAlphRange(alphabet).join("");
|
||||||
|
let result = [0];
|
||||||
|
if (alphabet.length !== 58 ||
|
||||||
|
[].unique.call(alphabet).length !== 58) {
|
||||||
|
throw new OperationError("Error: alphabet must be of length 58");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input.length === 0) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
let zeroPrefix = 0;
|
||||||
|
for (let i = 0; i < input.length && input[i] === 0; i++) {
|
||||||
|
zeroPrefix++;
|
||||||
|
}
|
||||||
|
|
||||||
|
input.forEach(function(b) {
|
||||||
|
let carry = (result[0] << 8) + b;
|
||||||
|
result[0] = carry % 58;
|
||||||
|
carry = (carry / 58) | 0;
|
||||||
|
|
||||||
|
for (let i = 1; i < result.length; i++) {
|
||||||
|
carry += result[i] << 8;
|
||||||
|
result[i] = carry % 58;
|
||||||
|
carry = (carry / 58) | 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (carry > 0) {
|
||||||
|
result.push(carry % 58);
|
||||||
|
carry = (carry / 58) | 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
result = result.map(function(b) {
|
||||||
|
return alphabet[b];
|
||||||
|
}).reverse().join("");
|
||||||
|
|
||||||
|
while (zeroPrefix--) {
|
||||||
|
result = alphabet[0] + result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Taken and modified from the FromBase58 op.
|
||||||
|
* We need this code as the operation code isn't exportable / easily available to other functions.
|
||||||
|
* We don't remove non Base58 characters, (we assume this must be done earlier) and we stick to only the Bitcoin alphabet here.
|
||||||
|
* @param {*} input
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function base58Decode (input) {
|
||||||
|
let alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
|
||||||
|
const result = [0];
|
||||||
|
|
||||||
|
alphabet = Utils.expandAlphRange(alphabet).join("");
|
||||||
|
|
||||||
|
if (alphabet.length !== 58 ||
|
||||||
|
[].unique.call(alphabet).length !== 58) {
|
||||||
|
throw new OperationError("Alphabet must be of length 58");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input.length === 0) return [];
|
||||||
|
|
||||||
|
let zeroPrefix = 0;
|
||||||
|
for (let i = 0; i < input.length && input[i] === alphabet[0]; i++) {
|
||||||
|
zeroPrefix++;
|
||||||
|
}
|
||||||
|
|
||||||
|
[].forEach.call(input, function(c, charIndex) {
|
||||||
|
const index = alphabet.indexOf(c);
|
||||||
|
|
||||||
|
if (index === -1) {
|
||||||
|
throw new OperationError(`Char '${c}' at position ${charIndex} not in alphabet`);
|
||||||
|
}
|
||||||
|
|
||||||
|
let carry = result[0] * 58 + index;
|
||||||
|
result[0] = carry & 0xFF;
|
||||||
|
carry = carry >> 8;
|
||||||
|
|
||||||
|
for (let i = 1; i < result.length; i++) {
|
||||||
|
carry += result[i] * 58;
|
||||||
|
result[i] = carry & 0xFF;
|
||||||
|
carry = carry >> 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (carry > 0) {
|
||||||
|
result.push(carry & 0xFF);
|
||||||
|
carry = carry >> 8;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
while (zeroPrefix--) {
|
||||||
|
result.push(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.reverse();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Base58 Checksum
|
||||||
|
/**
|
||||||
|
* Base58 Checksum
|
||||||
|
* @param {*} input
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function b58DoubleSHAChecksum(input) {
|
||||||
|
let byteResult;
|
||||||
|
try {
|
||||||
|
byteResult = fromArrayBuffer(base58Decode(input));
|
||||||
|
} catch (oe) {
|
||||||
|
if (oe instanceof OperationError) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
throw oe;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const data = byteResult.slice(0, -4);
|
||||||
|
const checksum = byteResult.slice(byteResult.length-4,);
|
||||||
|
const hashedData = doubleSHA(data);
|
||||||
|
return hashedData.slice(0, 4) === checksum;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ################################################ END BASE58 FUNCTIONS ########################################################
|
||||||
|
|
||||||
|
// ################################################ BEGIN EXTRA FUNCTIONS #######################################################
|
||||||
|
// Function for Deserializing Extended Keys (XPUBs/XPRVs)
|
||||||
|
/**
|
||||||
|
* Function for deserializing an extended key (xpub/xprv).
|
||||||
|
* We break down an extended key into its constituent parts, and return the results as JSON.
|
||||||
|
* @param {*} input
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function deserializeExtendedKeyFunc (input) {
|
||||||
|
if (! b58DoubleSHAChecksum(input)) {
|
||||||
|
const output = {"error": "Invalid checksum."};
|
||||||
|
return output;
|
||||||
|
} else {
|
||||||
|
const byteResult = fromArrayBuffer(base58Decode(input));
|
||||||
|
const checksum = byteResult.slice(-4);
|
||||||
|
const xprv = byteResult.slice(0, -4);
|
||||||
|
const version = xprv.slice(0, 4);
|
||||||
|
const level = parseInt(toHex(xprv.slice(4, 5)), 16);
|
||||||
|
const fingerprint = xprv.slice(5, 9);
|
||||||
|
const i = parseInt(toHex(xprv.slice(9, 13)), 16);
|
||||||
|
const chaincode = xprv.slice(13, 45);
|
||||||
|
const masterkey = xprv.slice(45, 78);
|
||||||
|
|
||||||
|
return {"version": toHex(version), "level": level, "checksum": toHex(checksum), "key": input,
|
||||||
|
"fingerprint": toHex(fingerprint), "chaincode": toHex(chaincode), "masterkey": toHex(masterkey), "i": i};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reverse lookup for version bytes
|
||||||
|
const versionString = {
|
||||||
|
"043587cf": "tpub",
|
||||||
|
"04358394": "tprv",
|
||||||
|
"044a5262": "upub",
|
||||||
|
"044a4e28": "uprv",
|
||||||
|
"045f1cf6": "vpub",
|
||||||
|
"045f18bc": "vprv",
|
||||||
|
"024289ef": "Upub",
|
||||||
|
"024285b5": "Uprv",
|
||||||
|
"02575483": "Vpub",
|
||||||
|
"02575048": "Vprv",
|
||||||
|
"0488b21e": "xpub",
|
||||||
|
"0488ade4": "xprv",
|
||||||
|
"049d7cb2": "ypub",
|
||||||
|
"049d7878": "yprv",
|
||||||
|
"04b24746": "zpub",
|
||||||
|
"04b2430c": "zprv",
|
||||||
|
"02aa7ed3": "Zpub",
|
||||||
|
"02aa7a99": "Zprv",
|
||||||
|
"0295b43f": "Ypub",
|
||||||
|
"0295b005": "Yprv",
|
||||||
|
"019da462": "Ltub",
|
||||||
|
"019d9cfe": "Ltpv",
|
||||||
|
"01b26ef6": "Mtub",
|
||||||
|
"01b26792": "Mtpv",
|
||||||
|
"0436f6e1": "ttub",
|
||||||
|
"0436ef7d": "ttpv"
|
||||||
|
};
|
||||||
|
|
||||||
|
// Version byte dictionary.
|
||||||
|
const versionBytes = {
|
||||||
|
"tpub": "043587cf",
|
||||||
|
"tprv": "04358394",
|
||||||
|
"upub": "044a5262",
|
||||||
|
"uprv": "044a4e28",
|
||||||
|
"vpub": "045f1cf6",
|
||||||
|
"vprv": "045f18bc",
|
||||||
|
"Upub": "024289ef",
|
||||||
|
"Uprv": "024285b5",
|
||||||
|
"Vpub": "02575483",
|
||||||
|
"Vprv": "02575048",
|
||||||
|
"xpub": "0488b21e",
|
||||||
|
"xprv": "0488ade4",
|
||||||
|
"ypub": "049d7cb2",
|
||||||
|
"yprv": "049d7878",
|
||||||
|
"zpub": "04b24746",
|
||||||
|
"zprv": "04b2430c",
|
||||||
|
"Zpub": "02aa7ed3",
|
||||||
|
"Zprv": "02aa7a99",
|
||||||
|
"Ypub": "0295b43f",
|
||||||
|
"Yprv": "0295b005",
|
||||||
|
"Ltub": "019da462",
|
||||||
|
"Ltpv": "019d9cfe",
|
||||||
|
"Mtub": "01b26ef6",
|
||||||
|
"Mtpv": "01b26792",
|
||||||
|
"ttub": "0436f6e1",
|
||||||
|
"ttpv": "0436ef7d"
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We return the correct version bytes from the versionBytes map, given input string.
|
||||||
|
* @param {*} input
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function getExtendedKeyVersion(input) {
|
||||||
|
return versionBytes[input];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse lookup for version string. We take in bytes, output string.
|
||||||
|
* @param {*} input
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function getExtendedKeyString(input) {
|
||||||
|
return versionString[input];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We serialize the extended key based off of the passed in data.
|
||||||
|
* We assume that the i value should be interpreted as a Uint32 LE.
|
||||||
|
* We assume the level is a number that should be interpreted as a byte.
|
||||||
|
* All other arguments are hex.
|
||||||
|
* @param {*} version
|
||||||
|
* @param {*} level
|
||||||
|
* @param {*} fingerprint
|
||||||
|
* @param {*} i
|
||||||
|
* @param {*} chaincode
|
||||||
|
* @param {*} masterkey
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function serializeExtendedKeyFunc (version, level, fingerprint, i, chaincode, masterkey) {
|
||||||
|
const iArr = new ArrayBuffer(4);
|
||||||
|
const iView = new DataView(iArr);
|
||||||
|
iView.setUint32(0, i, false);
|
||||||
|
const iAsHex = toHex(fromArrayBuffer(iArr));
|
||||||
|
|
||||||
|
const levelArr = new ArrayBuffer(1);
|
||||||
|
const levelView = new DataView(levelArr);
|
||||||
|
levelView.setUint8(0, level);
|
||||||
|
const levelAsHex = toHex(fromArrayBuffer(levelArr));
|
||||||
|
|
||||||
|
let s = version + levelAsHex + fingerprint + iAsHex + chaincode + masterkey;
|
||||||
|
const checksumHash = toHex(doubleSHA(fromArrayBuffer(Utils.convertToByteArray(s, "hex"))));
|
||||||
|
s += checksumHash.slice(0, 8);
|
||||||
|
return base58Encode(Utils.convertToByteArray(s, "hex"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Version Byte Info
|
||||||
|
const versionByteInfo = {
|
||||||
|
"BTC": {
|
||||||
|
"P2PKH": "00",
|
||||||
|
"P2SH": "05",
|
||||||
|
"WIF": "80",
|
||||||
|
"hrp": "bc"
|
||||||
|
},
|
||||||
|
"Testnet": {
|
||||||
|
"P2PKH": "6F",
|
||||||
|
"P2SH": "C4",
|
||||||
|
"WIF": "EF",
|
||||||
|
"hrp": "tb"
|
||||||
|
},
|
||||||
|
"LTC": {
|
||||||
|
"hrp": "ltc",
|
||||||
|
"P2PKH": "30",
|
||||||
|
"P2SH": "32",
|
||||||
|
"WIF": "B0"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We get the P2PKH byte for the given cryptocurrency type.
|
||||||
|
* @param {string} type
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function getP2PKHVersionByte(type) {
|
||||||
|
if (type in versionByteInfo) {
|
||||||
|
return versionByteInfo[type].P2PKH;
|
||||||
|
} else {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We get the P2SH byte from the given cryptocurrency type.
|
||||||
|
* @param {string} type
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function getP2SHVersionByte(type) {
|
||||||
|
if (type in versionByteInfo) {
|
||||||
|
return versionByteInfo[type].P2SH;
|
||||||
|
} else {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We get the private key WIF version byte for the given cryptocurrency type.
|
||||||
|
* @param {string} type
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function getWIFVersionByte(type) {
|
||||||
|
if (type in versionByteInfo) {
|
||||||
|
return versionByteInfo[type].WIF;
|
||||||
|
} else {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the human readable part (hrp) for segwit addresses.
|
||||||
|
* @param {*} type
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function getHumanReadablePart(type) {
|
||||||
|
if (type in versionByteInfo) {
|
||||||
|
return versionByteInfo[type].hrp;
|
||||||
|
} else {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ################################################ END EXTRA FUNCTIONS #########################################################
|
||||||
|
|
|
@ -45,6 +45,38 @@ export function search(input, searchRegex, removeRegex=null, sortBy=null, unique
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs search operation across the input data using the regular expressions. Filters using the filterFunc.
|
||||||
|
* @param {string} input
|
||||||
|
* @param {RegExp} searchRegex
|
||||||
|
* @param {RegExp} removeRegex
|
||||||
|
* @param {func} filterFunc
|
||||||
|
* @param {boolean} includeTotal
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
export function searchAndFilter(input, searchRegex, removeRegex, filterFunc, includeTotal) {
|
||||||
|
let output = "",
|
||||||
|
total = 0,
|
||||||
|
match;
|
||||||
|
|
||||||
|
while ((match = searchRegex.exec(input))) {
|
||||||
|
// Moves pointer when an empty string is matched (prevents infinite loop)
|
||||||
|
if (match.index === searchRegex.lastIndex) {
|
||||||
|
searchRegex.lastIndex++;
|
||||||
|
}
|
||||||
|
if (removeRegex && removeRegex.test(match[0]))
|
||||||
|
continue;
|
||||||
|
if (filterFunc && !filterFunc(match[0]))
|
||||||
|
continue;
|
||||||
|
total++;
|
||||||
|
output += match[0] + "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (includeTotal)
|
||||||
|
output = "Total found: " + total + "\n\n" + output;
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* URL regular expression
|
* URL regular expression
|
||||||
|
|
147
src/core/lib/Seedphrase.mjs
Normal file
147
src/core/lib/Seedphrase.mjs
Normal file
|
@ -0,0 +1,147 @@
|
||||||
|
/**
|
||||||
|
* Many Seedphrase specific functions. We cover BIP39 and Electrum2 checksums so far.
|
||||||
|
*
|
||||||
|
* @author dgoldenberg [virtualcurrency@mitre.org]
|
||||||
|
* @copyright MITRE 2023 Wei Lu <luwei.here@gmail.com> and Daniel Cousens <email@dcousens.com> 2014
|
||||||
|
* @license ISC
|
||||||
|
*/
|
||||||
|
|
||||||
|
import CryptoApi from "crypto-api/src/crypto-api.mjs";
|
||||||
|
import {toHex} from "crypto-api/src/encoder/hex.mjs";
|
||||||
|
import Hmac from "crypto-api/src/mac/hmac.mjs";
|
||||||
|
import { fromArrayBuffer } from "crypto-api/src/encoder/array-buffer.mjs";
|
||||||
|
import {bip39English, electrum2English} from "./SeedphraseWordLists.mjs";
|
||||||
|
|
||||||
|
// Dictionary for BIP39.
|
||||||
|
export const bip39 = {
|
||||||
|
"acceptable_lengths": [12, 15, 18, 21, 24],
|
||||||
|
"english": bip39English,
|
||||||
|
"checksum": validateMnemonic
|
||||||
|
};
|
||||||
|
|
||||||
|
// Dictionary for Electrum2
|
||||||
|
export const electrum2 = {
|
||||||
|
"acceptable_lengths": [12, 14],
|
||||||
|
"english": electrum2English,
|
||||||
|
"checksum": validateElectrum2Mnemonic
|
||||||
|
};
|
||||||
|
|
||||||
|
// BIP39 Verification code taken from https://github.com/vacuumlabs/bip39-light/blob/master/index.js
|
||||||
|
const INVALIDMNEMONIC = "Invalid mnemonic";
|
||||||
|
const INVALIDENTROPY = "Invalid entropy";
|
||||||
|
const INVALIDCHECKSUM = "Invalid mnemonic checksum";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Left pad data.
|
||||||
|
* @param {string} str
|
||||||
|
* @param {string} padString
|
||||||
|
* @param {int} length
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
function lpad (str, padString, length) {
|
||||||
|
while (str.length < length) str = padString + str;
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Turns a string of 0 and 1 to bytes.
|
||||||
|
* @param {string} bin
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
function binaryToByte (bin) {
|
||||||
|
return parseInt(bin, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Turns a string of bytes to a binary array
|
||||||
|
* @param {string} bytes
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
function bytesToBinary (bytes) {
|
||||||
|
return bytes.map(function (x) {
|
||||||
|
return lpad(x.toString(2), "0", 8);
|
||||||
|
}).join("");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Derive the checksum bits for a BIP39 seedphrase.
|
||||||
|
* @param {bytes} entropyBuffer
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
function deriveChecksumBits (entropyBuffer) {
|
||||||
|
const ENT = entropyBuffer.length * 8;
|
||||||
|
const CS = ENT / 32;
|
||||||
|
const hasher= CryptoApi.getHasher("sha256");
|
||||||
|
hasher.update(fromArrayBuffer(entropyBuffer));
|
||||||
|
const result = hasher.finalize();
|
||||||
|
const hexResult = toHex(result);
|
||||||
|
const temp = bytesToBinary([parseInt(hexResult.slice(0, 2), 16)]);
|
||||||
|
const final = temp.slice(0, CS);
|
||||||
|
return final;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Turns a mnemonic string to the underlying bytes.
|
||||||
|
* @param {str} mnemonic
|
||||||
|
* @param {list} wordlist
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
function mnemonicToEntropy (mnemonic, wordlist) {
|
||||||
|
const words = mnemonic.split(" ");
|
||||||
|
if (words.length % 3 !== 0) throw new Error(INVALIDMNEMONIC);
|
||||||
|
|
||||||
|
// convert word indices to 11 bit binary strings
|
||||||
|
const bits = words.map(function (word) {
|
||||||
|
const index = wordlist.indexOf(word);
|
||||||
|
if (index === -1) throw new Error(INVALIDMNEMONIC);
|
||||||
|
|
||||||
|
return lpad(index.toString(2), "0", 11);
|
||||||
|
}).join("");
|
||||||
|
|
||||||
|
|
||||||
|
// split the binary string into ENT/CS
|
||||||
|
const dividerIndex = Math.floor(bits.length / 33) * 32;
|
||||||
|
const entropyBits = bits.slice(0, dividerIndex);
|
||||||
|
const checksumBits = bits.slice(dividerIndex);
|
||||||
|
|
||||||
|
// calculate the checksum and compare
|
||||||
|
const entropyBytes = entropyBits.match(/(.{1,8})/g).map(binaryToByte);
|
||||||
|
if (entropyBytes.length < 16) throw new Error(INVALIDENTROPY);
|
||||||
|
if (entropyBytes.length > 32) throw new Error(INVALIDENTROPY);
|
||||||
|
if (entropyBytes.length % 4 !== 0) throw new Error(INVALIDENTROPY);
|
||||||
|
|
||||||
|
const entropy = Buffer.from(entropyBytes);
|
||||||
|
const newChecksum = deriveChecksumBits(entropy);
|
||||||
|
if (newChecksum !== checksumBits) throw new Error(INVALIDCHECKSUM);
|
||||||
|
return entropy.toString("hex");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates the BIP39 mnemonic string.
|
||||||
|
* @param {str} mnemonic
|
||||||
|
* @param {list} wordlist
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
function validateMnemonic (mnemonic, wordlist) {
|
||||||
|
try {
|
||||||
|
mnemonicToEntropy(mnemonic, wordlist);
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// My own code for Electrum2
|
||||||
|
/**
|
||||||
|
* Validates an Electrum2 Mnemonic
|
||||||
|
* @param {string} mnemonic
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
function validateElectrum2Mnemonic(mnemonic) {
|
||||||
|
const hasher = CryptoApi.getHasher("sha512");
|
||||||
|
const hmac = new Hmac("Seed version", hasher);
|
||||||
|
hmac.update(Buffer.from(mnemonic, "utf-8").toString());
|
||||||
|
const result = toHex(hmac.finalize());
|
||||||
|
return (result.startsWith("01") || result.startsWith("100") || result.startsWith("101") || result.startsWith("102"));
|
||||||
|
}
|
4109
src/core/lib/SeedphraseWordLists.mjs
Normal file
4109
src/core/lib/SeedphraseWordLists.mjs
Normal file
File diff suppressed because it is too large
Load diff
93
src/core/operations/BIP32Derive.mjs
Normal file
93
src/core/operations/BIP32Derive.mjs
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
/**
|
||||||
|
* @author dgoldenberg [virtualcurrency@mitre.org]
|
||||||
|
* @copyright Crown Copyright 2023
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Operation from "../Operation.mjs";
|
||||||
|
// import OperationError from "../errors/OperationError.mjs";
|
||||||
|
import { b58DoubleSHAChecksum} from "../lib/Bitcoin.mjs";
|
||||||
|
import { BIP32Factory} from "bip32";
|
||||||
|
import ecc from "@bitcoinerlab/secp256k1";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sanity checks a derivation path.
|
||||||
|
* @param {*} input
|
||||||
|
*/
|
||||||
|
function verifyDerivationPath(input) {
|
||||||
|
const splitResults = input.split("/");
|
||||||
|
let startIndex = 0;
|
||||||
|
// We skip the first index if its m, as that's common.
|
||||||
|
if (splitResults[0] === "m") {
|
||||||
|
startIndex = 1;
|
||||||
|
}
|
||||||
|
for (let i =startIndex; i < splitResults.length; i++) {
|
||||||
|
const re = /^[0-9]{1,}[']{0,1}$/g;
|
||||||
|
if (!re.test(splitResults[i])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BIP32Derive operation
|
||||||
|
*/
|
||||||
|
class BIP32Derive extends Operation {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BIP32Derive constructor
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.name = "BIP32Derive";
|
||||||
|
this.module = "Default";
|
||||||
|
this.description = "Takes in an extended key, performs BIP32 key derivation on the extended key, and returns the result as an extended key.";
|
||||||
|
this.infoURL = "https://en.bitcoin.it/wiki/BIP_0032";
|
||||||
|
this.inputType = "string";
|
||||||
|
this.outputType = "string";
|
||||||
|
this.args = [
|
||||||
|
{
|
||||||
|
"name": "Derivation Path",
|
||||||
|
"type": "string",
|
||||||
|
"value": ""
|
||||||
|
},
|
||||||
|
];
|
||||||
|
this.checks = [
|
||||||
|
{
|
||||||
|
"pattern": "^(X|x|Y|y|Z|z|L|l|T|t)[pub|prv|tbv|tub][A-HJ-NP-Za-km-z1-9]{2,}$",
|
||||||
|
"flags": "",
|
||||||
|
"args": []
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} input
|
||||||
|
* @param {Object[]} args
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
run(input, args) {
|
||||||
|
// We check if input is blank.
|
||||||
|
// If its blank or just whitespace, we don't need to bother dealing with it.
|
||||||
|
if (input.trim().length === 0) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
input = input.trim();
|
||||||
|
if (!verifyDerivationPath(args[0])) {
|
||||||
|
return "Invalid derivation path: " + args[0] + "\n";
|
||||||
|
}
|
||||||
|
const xkeyRe = /^(X|x|Y|y|Z|z|L|l|T|t)[pub|prv|tbv|tub][A-HJ-NP-Za-km-z1-9]{2,}$/g;
|
||||||
|
if (!b58DoubleSHAChecksum(input) || !xkeyRe.test(input)) {
|
||||||
|
return "Possibly invalid Extended Key: " + input + "\n";
|
||||||
|
}
|
||||||
|
const bip32 = BIP32Factory(ecc);
|
||||||
|
const node = bip32.fromBase58(input);
|
||||||
|
const child = node.derivePath(args[0]);
|
||||||
|
return child.toBase58();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default BIP32Derive;
|
62
src/core/operations/ChangeExtendedKeyVersion.mjs
Normal file
62
src/core/operations/ChangeExtendedKeyVersion.mjs
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
/**
|
||||||
|
* Changes the extended key from one version to another.
|
||||||
|
*
|
||||||
|
* @author dgoldenberg [virtualcurrency@mitre.org]
|
||||||
|
* @copyright MITRE 2023
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Operation from "../Operation.mjs";
|
||||||
|
import { deserializeExtendedKeyFunc, serializeExtendedKeyFunc, getExtendedKeyVersion } from "../lib/Bitcoin.mjs";
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Changes the version of an extended key. This can help to see if two keys are equal.
|
||||||
|
*/
|
||||||
|
class ChangeExtendedKeyVersion extends Operation {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract Seedphrases Constructor.
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.name = "Change Extended Key Version";
|
||||||
|
this.module = "Serialize";
|
||||||
|
this.description = "Changes the version of an Extended Key (xpub to ypub, ypub to xpub and so on) and returns the new extended key with the different version. All other data is kept the same. This can be useful in comparing extended keys written in different standards, or changing an extended key to a needed standard for importation into a wallet. Note that changing a public key to a private key (XPUB->XPRV) or vice versa will make the resulting key invalid as this operation does not change the key data itself as of now.";
|
||||||
|
this.infoURL = "https://github.com/satoshilabs/slips/blob/master/slip-0132.md";
|
||||||
|
this.inputType = "string";
|
||||||
|
this.outputType = "string";
|
||||||
|
this.args = [
|
||||||
|
{
|
||||||
|
"name": "Version Type",
|
||||||
|
"type": "option",
|
||||||
|
"value": ["xpub", "xprv", "ypub", "yprv", "zpub", "zprv", "Zpub", "Zprv", "Ypub", "Yprv", "Ltub", "Ltpv", "Mtub", "Mtpv", "ttub", "ttpv", "tpub", "tprv", "upub", "uprv", "vpub", "vprv", "Upub", "Uprv", "Vpub", "Vprv"]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
this.checks = [
|
||||||
|
{
|
||||||
|
"pattern": "^(X|x|Y|y|Z|z|L|l|T|t)[pub|prv|tbv|tub][A-HJ-NP-Za-km-z1-9]{2,}$",
|
||||||
|
"flags": "",
|
||||||
|
"args": []
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} input
|
||||||
|
* @param {Object[]} args
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
run(input, args) {
|
||||||
|
if (input.trim().length === 0) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
const result = deserializeExtendedKeyFunc(input);
|
||||||
|
const newVersion = getExtendedKeyVersion(args[0]);
|
||||||
|
return serializeExtendedKeyFunc(newVersion, result.level, result.fingerprint, result.i, result.chaincode, result.masterkey);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ChangeExtendedKeyVersion;
|
140
src/core/operations/DecryptKeystoreFile.mjs
Normal file
140
src/core/operations/DecryptKeystoreFile.mjs
Normal file
|
@ -0,0 +1,140 @@
|
||||||
|
/**
|
||||||
|
* Decrypts ETH keystore files, given the password.
|
||||||
|
*
|
||||||
|
* @author dgoldenberg [virtualcurrency@mitre.org]
|
||||||
|
* @copyright MITRE 2023
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Operation from "../Operation.mjs";
|
||||||
|
import OperationError from "../errors/OperationError.mjs";
|
||||||
|
import JSSHA3 from "js-sha3";
|
||||||
|
import Utils from "../Utils.mjs";
|
||||||
|
import {fromHex} from "../lib/Hex.mjs";
|
||||||
|
import scryptsy from "scryptsy";
|
||||||
|
import { isWorkerEnvironment } from "../Utils.mjs";
|
||||||
|
import forge from "node-forge";
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JPath expression operation
|
||||||
|
*/
|
||||||
|
class DecryptKeystoreFile extends Operation {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decrypt Keystore constructor
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.name = "Decrypt Keystore File";
|
||||||
|
this.module = "Crypto";
|
||||||
|
this.description = "Attempts to decrypt the given ETH keystore file, with the passed in password. Will return the private key if successful, error out if not.";
|
||||||
|
this.inputType = "string";
|
||||||
|
this.outputType = "string";
|
||||||
|
this.infoURL = "https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition";
|
||||||
|
this.args = [
|
||||||
|
{
|
||||||
|
name: "password",
|
||||||
|
type: "string",
|
||||||
|
value: ""
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} input
|
||||||
|
* @param {Object[]} args
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
run(input, args) {
|
||||||
|
const password = args[0];
|
||||||
|
let jsonObj, dkey;
|
||||||
|
// We parse the JSON object first and throw an error if its not JSON.
|
||||||
|
try {
|
||||||
|
jsonObj = JSON.parse(input);
|
||||||
|
} catch (err) {
|
||||||
|
throw new OperationError(`Invalid input, not JSON. Data: ${err.message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// We then check for a crypto property, and that crypto should have a kdf and kdfparams property.
|
||||||
|
if (!Object.prototype.hasOwnProperty.call(jsonObj, "crypto") || !Object.prototype.hasOwnProperty.call(jsonObj.crypto, "kdfparams") || !Object.prototype.hasOwnProperty.call(jsonObj.crypto, "kdf")) {
|
||||||
|
throw new OperationError(`Error. Invalid JSON blob, missing either a crypto, crypto.kdf or crypto.kdfparams object.`);
|
||||||
|
}
|
||||||
|
const kdfParams = jsonObj.crypto.kdfparams;
|
||||||
|
const kdfType = jsonObj.crypto.kdf;
|
||||||
|
|
||||||
|
// We compute the kdf.
|
||||||
|
if (kdfType === "scrypt") {
|
||||||
|
try {
|
||||||
|
// We compute the salt, and the compute the scrypt.
|
||||||
|
const salt = Buffer.from(Utils.convertToByteArray(kdfParams.salt, "hex"));
|
||||||
|
const data = scryptsy(password, salt, kdfParams.n, kdfParams.r, kdfParams.p, kdfParams.dklen,
|
||||||
|
p =>{
|
||||||
|
if (isWorkerEnvironment()) self.sendStatusMessage(`Progress: ${p.percent.toFixed(0)}%`);
|
||||||
|
});
|
||||||
|
// Result of the SCRYPT in hex.
|
||||||
|
dkey = data.toString("hex");
|
||||||
|
} catch (err) {
|
||||||
|
throw new OperationError("Error: " + err.toString());
|
||||||
|
}
|
||||||
|
} else if (kdfType === "pbkdf2") {
|
||||||
|
// If the kdf is PBKDF2, we check to make sure it has a prf property, and that property is hmac-sha256
|
||||||
|
if (!Object.prototype.hasOwnProperty.call(kdfParams, "prf") || kdfParams.prf !== "hmac-sha256") {
|
||||||
|
throw new OperationError(`Error with PBKDF2. Either HMAC function not present, or is not hmac-sha256. It is: ` + JSON.stringify(kdfParams));
|
||||||
|
}
|
||||||
|
// We compute the pbkdf2 and cast to hex.
|
||||||
|
const iterations = kdfParams.c;
|
||||||
|
const keyLength = kdfParams.dklen;
|
||||||
|
const salt = Utils.convertToByteString(kdfParams.salt, "hex");
|
||||||
|
dkey = forge.util.bytesToHex(forge.pkcs5.pbkdf2(password, salt, iterations, keyLength, "sha256"));
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// If there's a different KDF, we err out.
|
||||||
|
throw new OperationError("We don't support KDF type " + kdfType + " ... yet.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const mackey = dkey.slice(-32,);
|
||||||
|
const decryptionkey = dkey.slice(0, 32);
|
||||||
|
const ciphertext = jsonObj.crypto.ciphertext;
|
||||||
|
const hmac = jsonObj.crypto.mac;
|
||||||
|
const algo = JSSHA3.keccak256;
|
||||||
|
|
||||||
|
const testmac = algo(fromHex(mackey + ciphertext, "hex"));
|
||||||
|
if (testmac === hmac) {
|
||||||
|
// If the MAC passes, we can decrypt.
|
||||||
|
// We check for the right data to decrypt.
|
||||||
|
if (!Object.prototype.hasOwnProperty.call(jsonObj.crypto, "cipherparams") || !Object.prototype.hasOwnProperty.call(jsonObj.crypto.cipherparams, "iv")) {
|
||||||
|
throw new OperationError("We are missing needed cipherparams and IV.");
|
||||||
|
}
|
||||||
|
if (!Object.prototype.hasOwnProperty.call(jsonObj.crypto, "ciphertext") || !Object.prototype.hasOwnProperty.call(jsonObj.crypto.cipherparams, "iv")) {
|
||||||
|
throw new OperationError("We are the ciphertext");
|
||||||
|
}
|
||||||
|
// We grab the key, and IV, and ciphertext
|
||||||
|
const key = Utils.convertToByteString(decryptionkey, "hex");
|
||||||
|
const iv = Utils.convertToByteString(jsonObj.crypto.cipherparams.iv, "hex");
|
||||||
|
const cipherinput = Utils.convertToByteString(jsonObj.crypto.ciphertext, "hex");
|
||||||
|
// We create the decryptor.
|
||||||
|
const decipher = forge.cipher.createDecipher("AES-CTR", key);
|
||||||
|
// We do the decryption.
|
||||||
|
decipher.start({
|
||||||
|
iv: iv.length === 0 ? "" : iv,
|
||||||
|
tag: undefined,
|
||||||
|
additionalData: undefined
|
||||||
|
});
|
||||||
|
decipher.update(forge.util.createBuffer(cipherinput));
|
||||||
|
const result = decipher.finish();
|
||||||
|
if (result) {
|
||||||
|
return decipher.output.toHex();
|
||||||
|
} else {
|
||||||
|
throw new OperationError("Unable to decrypt keystore with these parameters.");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// In this case the MAC failed so we error out.
|
||||||
|
throw new OperationError("MAC error. We got: " + testmac + " , but we wanted. " + hmac);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DecryptKeystoreFile;
|
89
src/core/operations/DeserializeExtendedKey.mjs
Normal file
89
src/core/operations/DeserializeExtendedKey.mjs
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
/**
|
||||||
|
* Deserializes the passed in Extended Key to its component parts.
|
||||||
|
*
|
||||||
|
* @author dgoldenberg [virtualcurrency@mitre.org]
|
||||||
|
* @copyright MITRE 2023
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
import Operation from "../Operation.mjs";
|
||||||
|
import { deserializeExtendedKeyFunc } from "../lib/Bitcoin.mjs";
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deserializes an extended key (XPUB/XPRV). Returns available information.
|
||||||
|
*/
|
||||||
|
class DeserializeExtendedKey extends Operation {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract Seedphrases Constructor.
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.name = "Deserialize Extended Key";
|
||||||
|
this.module = "Serialize";
|
||||||
|
this.description = "Deserializes a passed in extended key. Can return all the deserialized information, or just the master key part (useful for chaining this call to others in a recipe).";
|
||||||
|
this.inputType = "string";
|
||||||
|
this.outputType = "JSON";
|
||||||
|
this.presentType = "string";
|
||||||
|
this.infoURL = "https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#Serialization_format";
|
||||||
|
this.args = [
|
||||||
|
{
|
||||||
|
name: "Key Only",
|
||||||
|
type: "boolean",
|
||||||
|
value: true
|
||||||
|
}
|
||||||
|
];
|
||||||
|
this.checks = [
|
||||||
|
{
|
||||||
|
"pattern": "^(X|x|Y|y|Z|z|L|l|T|t)[pub|prv|tbv|tub][A-HJ-NP-Za-km-z1-9]{2,}$",
|
||||||
|
"flags": "",
|
||||||
|
"args": []
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} input
|
||||||
|
* @param {Object[]} args
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
run(input, args) {
|
||||||
|
const [keyOnly] = args;
|
||||||
|
const result = deserializeExtendedKeyFunc(input);
|
||||||
|
if (!keyOnly) {
|
||||||
|
return result;
|
||||||
|
} else {
|
||||||
|
return {"masterkey": result.masterkey};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays the result of deserializing the extended key.
|
||||||
|
*
|
||||||
|
* @param {Object} output
|
||||||
|
* @returns {final_output}
|
||||||
|
*/
|
||||||
|
present(output) {
|
||||||
|
if ("error" in output) {
|
||||||
|
return output.error;
|
||||||
|
} else {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(output, "masterkey") && Object.prototype.hasOwnProperty.call(output, "checksum")) {
|
||||||
|
let finalOutput = "Key Analyzed: " + output.key + "\n";
|
||||||
|
finalOutput += "\tChecksum: " + output.checksum + "\n";
|
||||||
|
finalOutput += "\tVersion: " + output.version + "\n";
|
||||||
|
finalOutput += "\tLevel: " + output.level + "\n";
|
||||||
|
finalOutput += "\tFingerprint: " + output.fingerprint + "\n";
|
||||||
|
finalOutput += "\tChaincode: " + output.chaincode + "\n";
|
||||||
|
finalOutput += "\tMasterKey: " + output.masterkey + "\n";
|
||||||
|
finalOutput += "\n";
|
||||||
|
return finalOutput;
|
||||||
|
} else {
|
||||||
|
return output.masterkey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DeserializeExtendedKey;
|
53
src/core/operations/ExtractDoubleShaArtifacts.mjs
Normal file
53
src/core/operations/ExtractDoubleShaArtifacts.mjs
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
/**
|
||||||
|
* Extracts most Double SHA256 encoded strings from text. These are strings which are valid Base58 encoded, with the last 4 bytes being the double sha256 of the previous bytes.
|
||||||
|
*
|
||||||
|
* @author dgoldenberg [virtualcurrency@mitre.org]
|
||||||
|
* @copyright MITRE 2023
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
import Operation from "../Operation.mjs";
|
||||||
|
import { searchAndFilter } from "../lib/Extract.mjs";
|
||||||
|
import { b58DoubleSHAChecksum } from "../lib/Bitcoin.mjs";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract Cryptocurrency addresses that are Base58 Encoded, with Double SHA256 Checksum.
|
||||||
|
*/
|
||||||
|
class ExtractDoubleShaArtifacts extends Operation {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ExtractDoubleShaArtifacts Constructor.
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.name = "Extract Double SHA Artifacts";
|
||||||
|
this.module = "Regex";
|
||||||
|
this.description = "Extracts many cryptocurrency artifact strings that are Base58 encoded where here we define that as: 28-150 characters that are Base-58 encoded with a Double SHA256 checksum. For example, this will extract Bitcoin/Litecoin/Dogecoin addreses, WIF keys, extended keys amongst other artifacts from ASCII text.";
|
||||||
|
this.infoURL = "https://en.bitcoin.it/wiki/Base58Check_encoding";
|
||||||
|
this.inputType = "string";
|
||||||
|
this.outputType = "string";
|
||||||
|
this.args = [
|
||||||
|
{
|
||||||
|
"name": "Display total",
|
||||||
|
"type": "boolean",
|
||||||
|
"value": false
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} input
|
||||||
|
* @param {Object[]} args
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
run(input, args) {
|
||||||
|
const displayTotal = args[0],
|
||||||
|
regex = /[A-HJ-NP-Za-km-z1-9]{28,150}/g;
|
||||||
|
|
||||||
|
return searchAndFilter(input, regex, null, b58DoubleSHAChecksum, displayTotal);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ExtractDoubleShaArtifacts;
|
||||||
|
|
87
src/core/operations/ExtractSeedPhrases.mjs
Normal file
87
src/core/operations/ExtractSeedPhrases.mjs
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
/**
|
||||||
|
* Extracts seedphrases. Right now, extracts BIP39 and Electrum2 seedphrases.
|
||||||
|
*
|
||||||
|
* @author dgoldenberg [virtualcurrency@mitre.org]
|
||||||
|
* @copyright MITRE 2023 Wei Lu <luwei.here@gmail.com> and Daniel Cousens <email@dcousens.com> 2014
|
||||||
|
* @license ISC
|
||||||
|
*/
|
||||||
|
import Operation from "../Operation.mjs";
|
||||||
|
import { search} from "../lib/Extract.mjs";
|
||||||
|
import {bip39, electrum2} from "../lib/Seedphrase.mjs";
|
||||||
|
import OperationError from "../errors/OperationError.mjs";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract Cryptocurrency addresses that are Base58 Encoded, with Double SHA256 Checksum.
|
||||||
|
*/
|
||||||
|
class ExtractSeedPhrases extends Operation {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract Seedphrases Constructor.
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.name = "Extract Seedphrases";
|
||||||
|
this.module = "Regex";
|
||||||
|
this.description = "Attempts to extract seedphrases from text. Right now, only BIP39 and Electrum2 standards and the English language supported.";
|
||||||
|
this.infoURL = "https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki";
|
||||||
|
this.inputType = "string";
|
||||||
|
this.outputType = "string";
|
||||||
|
this.args = [{
|
||||||
|
"name": "Seedphrase Type",
|
||||||
|
"type": "option",
|
||||||
|
"value": ["bip39", "electrum2"]
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} input
|
||||||
|
* @param {Object[]} args
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
run(input, args) {
|
||||||
|
|
||||||
|
const regex = /[a-z]{3,20}/g;
|
||||||
|
|
||||||
|
const type = args[0];
|
||||||
|
let seedphraseInfo = {};
|
||||||
|
if (type === "bip39") {
|
||||||
|
seedphraseInfo = bip39;
|
||||||
|
} else if (type === "electrum2") {
|
||||||
|
seedphraseInfo = electrum2;
|
||||||
|
} else {
|
||||||
|
throw new OperationError("Unknown seedphrase type value: " + type);
|
||||||
|
}
|
||||||
|
|
||||||
|
let output = "";
|
||||||
|
const wordArray = search(input, regex, null, false);
|
||||||
|
|
||||||
|
// Start scanning through the list
|
||||||
|
for (let i = 0; i < wordArray.length; i++) {
|
||||||
|
// If we find a starting element in the wordlist, we expand.
|
||||||
|
if (seedphraseInfo.english.includes(wordArray[i])) {
|
||||||
|
for (let j = 0; j < seedphraseInfo.acceptable_lengths.length; j++) {
|
||||||
|
// For each possible length, we scan through the list.
|
||||||
|
const curPhraseLength = seedphraseInfo.acceptable_lengths[j];
|
||||||
|
let correctPhrase = true;
|
||||||
|
for (let w =0; w < curPhraseLength; w++) {
|
||||||
|
if (!seedphraseInfo.english.includes(wordArray[i + w])) {
|
||||||
|
correctPhrase = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If all words in that slice belong in the word list, and the checksum holds we assume the phrase is a valid one.
|
||||||
|
if (correctPhrase) {
|
||||||
|
if (seedphraseInfo.checksum(wordArray.slice(i, i + curPhraseLength).join(" "), seedphraseInfo.english)) {
|
||||||
|
output += wordArray.slice(i, i + curPhraseLength).join(" ") + "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ExtractSeedPhrases;
|
54
src/core/operations/ExtractSegwitAddresses.mjs
Normal file
54
src/core/operations/ExtractSegwitAddresses.mjs
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
/**
|
||||||
|
* Extracts segwit encoded seedphrases. These can be Bech32 encoded or P2TR style addresses.
|
||||||
|
*
|
||||||
|
* @author dgoldenberg [virtualcurrency@mitre.org]
|
||||||
|
* @copyright MITRE 2023, geco 2019
|
||||||
|
* @license MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Operation from "../Operation.mjs";
|
||||||
|
import { searchAndFilter } from "../lib/Extract.mjs";
|
||||||
|
import { segwitChecksum} from "../lib/Bech32.mjs";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract Cryptocurrency addresses that are Segwit Formatted
|
||||||
|
*/
|
||||||
|
class ExtractSegwitAddresses extends Operation {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract Segwit Constructor.
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.name = "Extract Segwit Addresses";
|
||||||
|
this.module = "Regex";
|
||||||
|
this.description = "Extracts Segwit formatted cryptocurrency addresses. Compliant with BIP173, and BIP350, given normal addresses. Not compatible as of now with Lightning Network invoices.";
|
||||||
|
this.infoURL = "https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki";
|
||||||
|
this.inputType = "string";
|
||||||
|
this.outputType = "string";
|
||||||
|
this.args = [
|
||||||
|
{
|
||||||
|
"name": "Display total",
|
||||||
|
"type": "boolean",
|
||||||
|
"value": false
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} input
|
||||||
|
* @param {Object[]} args
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
run(input, args) {
|
||||||
|
const displayTotal = args[0],
|
||||||
|
regex = /(bc|ltc|tb)1[023456789ac-hj-np-z]{38,60}/g;
|
||||||
|
|
||||||
|
return searchAndFilter(input, regex, null, segwitChecksum, displayTotal);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ExtractSegwitAddresses;
|
||||||
|
|
77
src/core/operations/PrivateECKeyToPublic.mjs
Normal file
77
src/core/operations/PrivateECKeyToPublic.mjs
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
/**
|
||||||
|
* Turns a 32 byte private key into into a secp256k1 public key.
|
||||||
|
*
|
||||||
|
* @author dgoldenberg [virtualcurrency@mitre.org]
|
||||||
|
* @copyright MITRE 2023
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Operation from "../Operation.mjs";
|
||||||
|
import ec from "elliptic";
|
||||||
|
import { validatePrivateKey, makeSureIsHex} from "../lib/Bitcoin.mjs";
|
||||||
|
// import { toHex } from "crypto-api/src/encoder/hex.mjs";
|
||||||
|
|
||||||
|
// const curves = ["secp256k1", "ed25519", "curve25519", "p521", "p384", "p256", "p224", "p192"];
|
||||||
|
/**
|
||||||
|
* Class that takes in a private key, and returns the public key, either in compressed or uncompressed form(s).
|
||||||
|
*/
|
||||||
|
class PrivateECKeyToPublic extends Operation {
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.name = "Private EC Key to Public Key";
|
||||||
|
this.module = "Default";
|
||||||
|
this.description = "Turns a private key to the appropriate ECC public key. Right now assumes the private key is a private key to the Secp256k1 curve.";
|
||||||
|
this.inputType = "string";
|
||||||
|
this.outputType = "string";
|
||||||
|
this.infoURL = "https://en.bitcoin.it/wiki/Secp256k1";
|
||||||
|
this.args = [
|
||||||
|
{
|
||||||
|
"name": "Compressed",
|
||||||
|
"type": "boolean",
|
||||||
|
"value": true
|
||||||
|
}
|
||||||
|
];
|
||||||
|
this.checks = [
|
||||||
|
{
|
||||||
|
"pattern": "^[0-9A-Fa-f]{64}$",
|
||||||
|
"flags": "",
|
||||||
|
"args": [true]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes in a string, and an array of args. Interpolates the string as a private ECC Key, and attempts to construct the public key.
|
||||||
|
* @param {string} input
|
||||||
|
* @param {*} args
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
run(input, args) {
|
||||||
|
// We check if input is blank.
|
||||||
|
// If its blank or just whitespace, we don't need to bother dealing with it.
|
||||||
|
if (input.trim().length === 0) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
input = input.trim();
|
||||||
|
|
||||||
|
const privKeyCheck = validatePrivateKey(input);
|
||||||
|
|
||||||
|
if (privKeyCheck.trim().length !== 0) {
|
||||||
|
return "Error with the input as private key. Error is:\n\t" + privKeyCheck;
|
||||||
|
}
|
||||||
|
const processedInput = makeSureIsHex(input);
|
||||||
|
const ecContext = ec.ec("secp256k1");
|
||||||
|
const key = ecContext.keyFromPrivate(processedInput);
|
||||||
|
const pubkey = key.getPublic(args[0], "hex");
|
||||||
|
|
||||||
|
return pubkey;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PrivateECKeyToPublic;
|
88
src/core/operations/PrivateKeyToWIF.mjs
Normal file
88
src/core/operations/PrivateKeyToWIF.mjs
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
/**
|
||||||
|
* Turns a 32 byte private key into Wallet-Import-Format
|
||||||
|
*
|
||||||
|
* @author dgoldenberg [virtualcurrency@mitre.org]
|
||||||
|
* @copyright MITRE 2023
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Operation from "../Operation.mjs";
|
||||||
|
import { base58Encode, getWIFVersionByte, doubleSHA, validatePrivateKey, makeSureIsHex} from "../lib/Bitcoin.mjs";
|
||||||
|
import { fromArrayBuffer } from "crypto-api/src/encoder/array-buffer.mjs";
|
||||||
|
import {toHex} from "crypto-api/src/encoder/hex.mjs";
|
||||||
|
// import {toHex as toHexOther} from "../lib/Hex.mjs";
|
||||||
|
import Utils from "../Utils.mjs";
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a private key to the WIF format.
|
||||||
|
*/
|
||||||
|
class PrivateKeyToWIF extends Operation {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a private key to the WIF format.
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.name = "To WIF Format";
|
||||||
|
this.module = "Default";
|
||||||
|
this.description = "Turns a 32 bye private key into a WIF format key. Options include if the key should produce a compressed or uncompressed public key";
|
||||||
|
this.inputType = "string";
|
||||||
|
this.outputType = "string";
|
||||||
|
this.infoURL = "https://en.bitcoin.it/wiki/Wallet_import_format";
|
||||||
|
this.args = [
|
||||||
|
{
|
||||||
|
"name": "Currency Type",
|
||||||
|
"type": "option",
|
||||||
|
"value": ["BTC", "Testnet"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Compressed",
|
||||||
|
"type": "boolean",
|
||||||
|
"value": true
|
||||||
|
}
|
||||||
|
];
|
||||||
|
this.checks = [
|
||||||
|
{
|
||||||
|
"pattern": "^[0-9a-fA-F]{64}$",
|
||||||
|
"flags": "",
|
||||||
|
"args": ["BTC", true]
|
||||||
|
}
|
||||||
|
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} input
|
||||||
|
* @param {Object[]} args
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
run(input, args) {
|
||||||
|
// We check if input is blank.
|
||||||
|
// If its blank or just whitespace, we don't need to bother dealing with it.
|
||||||
|
if (input.trim().length === 0) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
input = input.trim();
|
||||||
|
const privateKeyCheck = validatePrivateKey(input);
|
||||||
|
if (privateKeyCheck.trim().length !== 0) {
|
||||||
|
return "Error parsing private key. Error is:\n\t" + privateKeyCheck;
|
||||||
|
}
|
||||||
|
const processedKey = makeSureIsHex(input);
|
||||||
|
const versionByte = getWIFVersionByte(args[0]);
|
||||||
|
let extendedPrivateKey = versionByte + processedKey;
|
||||||
|
if (args[1]) {
|
||||||
|
extendedPrivateKey += "01";
|
||||||
|
}
|
||||||
|
|
||||||
|
const checksumHash = toHex(doubleSHA(fromArrayBuffer(Utils.convertToByteArray(extendedPrivateKey, "hex"))));
|
||||||
|
const finalString = extendedPrivateKey + checksumHash.slice(0, 8);
|
||||||
|
const wifKey = base58Encode(Utils.convertToByteArray(finalString, "hex"));
|
||||||
|
return wifKey;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PrivateKeyToWIF;
|
87
src/core/operations/PublicKeyToETHStyleAddress.mjs
Normal file
87
src/core/operations/PublicKeyToETHStyleAddress.mjs
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
/**
|
||||||
|
* @author dgoldenberg [virtualcurrency@mitre.org]
|
||||||
|
* @copyright Crown Copyright 2023
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Operation from "../Operation.mjs";
|
||||||
|
import { makeSureIsBytes, validatePublicKey} from "../lib/Bitcoin.mjs";
|
||||||
|
import JSSHA3 from "js-sha3";
|
||||||
|
import Utils from "../Utils.mjs";
|
||||||
|
import ec from "elliptic";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Turns a public key into an ETH address.
|
||||||
|
* @param {*} input Input, a public key in hex or bytes.
|
||||||
|
*/
|
||||||
|
function pubKeyToETHAddress(input) {
|
||||||
|
// Ethereum addresses require uncompressed public keys.
|
||||||
|
// We convert if the public key is compressed.
|
||||||
|
let curKey = makeSureIsBytes(input);
|
||||||
|
if (curKey[0] !== 0x04 || curKey.length !== 65) {
|
||||||
|
const ecContext = ec.ec("secp256k1");
|
||||||
|
const thisKey = ecContext.keyFromPublic(curKey);
|
||||||
|
curKey = thisKey.getPublic(false, "hex");
|
||||||
|
}
|
||||||
|
const algo = JSSHA3.keccak256;
|
||||||
|
// We need to redo the hex-> bytes transformation here because Javascript is silly.
|
||||||
|
// sometimes what is desired is an array of ints.
|
||||||
|
// Other times a string
|
||||||
|
// Here, the Keccak algorithm seems to want an array of ints. (sigh)
|
||||||
|
const result = algo(Utils.convertToByteArray(curKey, "hex").slice(1,));
|
||||||
|
return "0x" + result.slice(-40);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PublicKeyToETHStyleAddress operation
|
||||||
|
*/
|
||||||
|
class PublicKeyToETHStyleAddress extends Operation {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PublicKeyToETHStyleAddress constructor
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.name = "Public Key To ETH Style Address";
|
||||||
|
this.module = "Default";
|
||||||
|
this.description = "Converts a public key (compressed or uncompressed) to an Ethereum style address.";
|
||||||
|
this.infoURL = "https://www.freecodecamp.org/news/how-to-create-an-ethereum-wallet-address-from-a-private-key-ae72b0eee27b/";
|
||||||
|
this.inputType = "string";
|
||||||
|
this.outputType = "string";
|
||||||
|
this.args = [];
|
||||||
|
this.checks = [
|
||||||
|
{
|
||||||
|
pattern: "^0[3|2][a-fA-F0-9]{64}$",
|
||||||
|
flags: "",
|
||||||
|
args: []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern: "^04[a-fA-F0-9]{128}$",
|
||||||
|
flags: "",
|
||||||
|
args: []
|
||||||
|
}
|
||||||
|
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} input
|
||||||
|
* @param {Object[]} args
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
run(input, args) {
|
||||||
|
// We check if input is blank.
|
||||||
|
// If its blank or just whitespace, we don't need to bother dealing with it.
|
||||||
|
if (input.trim().length === 0) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
if (validatePublicKey(input) !== "") {
|
||||||
|
return validatePublicKey(input);
|
||||||
|
}
|
||||||
|
return pubKeyToETHAddress(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PublicKeyToETHStyleAddress;
|
104
src/core/operations/PublicKeyToP2PKHAddress.mjs
Normal file
104
src/core/operations/PublicKeyToP2PKHAddress.mjs
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
/**
|
||||||
|
* Turns a public key into a cryptocurrency address. Supports Bitcoin (P2PKH, P2SH-P2WPKH, Segwit) as well as Ethereum.
|
||||||
|
*
|
||||||
|
* @author dgoldenberg [virtualcurrency@mitre.org]
|
||||||
|
* @copyright MITRE 2023, geco 2019
|
||||||
|
* @license MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Operation from "../Operation.mjs";
|
||||||
|
import { fromArrayBuffer } from "crypto-api/src/encoder/array-buffer.mjs";
|
||||||
|
import {toHex} from "crypto-api/src/encoder/hex.mjs";
|
||||||
|
import { base58Encode, getP2PKHVersionByte, getP2SHVersionByte, hash160Func, doubleSHA, getHumanReadablePart, makeSureIsBytes, validatePublicKey} from "../lib/Bitcoin.mjs";
|
||||||
|
import {encodeProgramToSegwit} from "../lib/Bech32.mjs";
|
||||||
|
import Utils from "../Utils.mjs";
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a Public Key to a P2PKH Address of the given type.
|
||||||
|
*/
|
||||||
|
class PublicKeyToP2PKHAddress extends Operation {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a public key to a P2PKH Address.
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.name = "Public Key To Cryptocurrency Address";
|
||||||
|
this.module = "Default";
|
||||||
|
this.description = "Turns a public key into a cryptocurrency address. Can select P2PKH, P2SH-P2WPKH and P2WPKH addresses for Bitcoin and Testnet.";
|
||||||
|
this.inputType = "string";
|
||||||
|
this.outputType = "string";
|
||||||
|
this.args = [
|
||||||
|
{
|
||||||
|
"name": "Currency Type",
|
||||||
|
"type": "option",
|
||||||
|
"value": ["BTC", "Testnet", "LTC"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Address Type",
|
||||||
|
"type": "option",
|
||||||
|
"value": ["P2PKH (V1 BTC Addresses)", "P2SH-P2PWPKH (Segwit Compatible V3 Addresses)", "Segwit (P2WPKH bc1 Addresses)"]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
this.checks = [
|
||||||
|
{
|
||||||
|
pattern: "^0[3|2][a-fA-F0-9]{64}$",
|
||||||
|
flags: "",
|
||||||
|
args: ["BTC", "P2PKH (V1 BTC Addresses)"]
|
||||||
|
},
|
||||||
|
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} input
|
||||||
|
* @param {Object[]} args
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
run(input, args) {
|
||||||
|
// We check if input is blank.
|
||||||
|
// If its blank or just whitespace, we don't need to bother dealing with it.
|
||||||
|
if (input.trim().length === 0) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
if (validatePublicKey(input) !== "") {
|
||||||
|
return validatePublicKey(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
// We hash the input
|
||||||
|
const curInput = makeSureIsBytes(input);
|
||||||
|
const hash160 = toHex(hash160Func(curInput));
|
||||||
|
// We do segwit addresses first.
|
||||||
|
if (args[1] === "Segwit (P2WPKH bc1 Addresses)") {
|
||||||
|
const redeemScript = hash160;
|
||||||
|
const hrp = getHumanReadablePart(args[0]);
|
||||||
|
if (hrp !== "") {
|
||||||
|
return encodeProgramToSegwit(hrp, 0, Utils.convertToByteArray(redeemScript, "hex"));
|
||||||
|
} else {
|
||||||
|
return args[0] + " does not support Segwit Addresses.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// It its not segwit, we create the redeemScript either for P2PKH or P2SH-P2WPKH addresses.
|
||||||
|
const versionByte = "P2PKH (V1 BTC Addresses)" === args[1] ? getP2PKHVersionByte(args[0]) : getP2SHVersionByte(args[0]);
|
||||||
|
// If its a P2SH-P2WPKH address, we have to prepend some extra bytes and hash again. Either way we prepend the version byte.
|
||||||
|
let hashRedeemedScript;
|
||||||
|
if (args[1] === "P2SH-P2PWPKH (Segwit Compatible V3 Addresses)") {
|
||||||
|
const redeemScript = "0014" + hash160;
|
||||||
|
hashRedeemedScript = versionByte + toHex(hash160Func(fromArrayBuffer(Utils.convertToByteArray(redeemScript, "hex"))));
|
||||||
|
} else {
|
||||||
|
hashRedeemedScript = versionByte + hash160;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We calculate the checksum, convert to Base58 and then we're done!
|
||||||
|
const checksumHash = toHex(doubleSHA(fromArrayBuffer(Utils.convertToByteArray(hashRedeemedScript, "hex"))));
|
||||||
|
const finalString = hashRedeemedScript + checksumHash.slice(0, 8);
|
||||||
|
const address = base58Encode(Utils.convertToByteArray(finalString, "hex"));
|
||||||
|
return address;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PublicKeyToP2PKHAddress;
|
74
src/core/operations/SeedToMPK.mjs
Normal file
74
src/core/operations/SeedToMPK.mjs
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
/**
|
||||||
|
* Turns a seed into the initial master private key.
|
||||||
|
*
|
||||||
|
* @author dgoldenberg [virtualcurrency@mitre.org]
|
||||||
|
* @copyright MITRE 2023
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Operation from "../Operation.mjs";
|
||||||
|
import { serializeExtendedKeyFunc, getExtendedKeyVersion } from "../lib/Bitcoin.mjs";
|
||||||
|
import forge from "node-forge";
|
||||||
|
import Utils from "../Utils.mjs";
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Changes the version of an extended key. This can help to see if two keys are equal.
|
||||||
|
*/
|
||||||
|
class SeedToMPK extends Operation {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract Seedphrases Constructor.
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.name = "Seed To Master Key";
|
||||||
|
this.module = "Serialize";
|
||||||
|
this.description = "Given a 64 byte seed, we change the seed into the extended given master private key, with selected version. To produce the seed from a seedphrase, you can use the Seedphrase To Seed Op.";
|
||||||
|
this.infoURL = "https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#Serialization_format";
|
||||||
|
this.inputType = "string";
|
||||||
|
this.outputType = "string";
|
||||||
|
this.args = [
|
||||||
|
{
|
||||||
|
"name": "Version Type",
|
||||||
|
"type": "option",
|
||||||
|
"value": ["xprv", "yprv", "zprv", "Zprv", "Yprv", "Ltpv", "Mtpv", "ttpv", "tprv", "uprv", "vprv", "Uprv", "Vprv"]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} input
|
||||||
|
* @param {Object[]} args
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
run(input, args) {
|
||||||
|
if (input.trim().length === 0) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
input = input.trim();
|
||||||
|
// We check to see if the input is hex or not.
|
||||||
|
// If it is not, we convert it back to hex
|
||||||
|
const re = /[0-9A-Fa-f]{2,}/g;
|
||||||
|
if (!(input.length === 128 && re.test(input)) && !(input.length === 64)) {
|
||||||
|
return "Must pass a hex string of length 128, or a byte string of length 64. Got length: " + input.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
const hmac = forge.hmac.create();
|
||||||
|
hmac.start("sha512", Utils.convertToByteString("Bitcoin seed", "UTF8"));
|
||||||
|
if (input.length === 128) {
|
||||||
|
hmac.update(Utils.convertToByteString(input, "hex"));
|
||||||
|
} else {
|
||||||
|
hmac.update(input);
|
||||||
|
}
|
||||||
|
const hexValue = hmac.digest().toHex();
|
||||||
|
|
||||||
|
const newVersion = getExtendedKeyVersion(args[0]);
|
||||||
|
|
||||||
|
return serializeExtendedKeyFunc(newVersion, 0, "00000000", 0, hexValue.slice(64,), "00" + hexValue.slice(0, 64));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SeedToMPK;
|
68
src/core/operations/SeedphraseToSeed.mjs
Normal file
68
src/core/operations/SeedphraseToSeed.mjs
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
/**
|
||||||
|
* Turns a seedphrase into the inital seed given PBKDF2.
|
||||||
|
*
|
||||||
|
* @author dgoldenberg [virtualcurrency@mitre.org]
|
||||||
|
* @copyright MITRE 2023 Wei Lu <luwei.here@gmail.com> and Daniel Cousens <email@dcousens.com> 2014
|
||||||
|
* @license ISC
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Operation from "../Operation.mjs";
|
||||||
|
import Utils from "../Utils.mjs";
|
||||||
|
import forge from "node-forge";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Seedphrase to Seed Class.
|
||||||
|
*/
|
||||||
|
class SeedphraseToSeed extends Operation {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SeedphraseToSeed Constructor.
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.name = "Seedphrase To Seed";
|
||||||
|
this.module = "Serialize";
|
||||||
|
this.description = "Turns a seedphrase (with possible seed passphrase) into a seed. Note, does not type or validate the input, it is assumed to be a valid seedphrase. The Extract Seedphrases Op can extract valid seedphrases from text. Supports BIP39 and Electrum2 standards.";
|
||||||
|
this.infoURL = "https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki#from-mnemonic-to-seed";
|
||||||
|
this.inputType = "string";
|
||||||
|
this.outputType = "string";
|
||||||
|
this.args = [
|
||||||
|
{
|
||||||
|
"name": "Seedphrase Type",
|
||||||
|
"type": "option",
|
||||||
|
"value": ["bip39", "electrum2"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Seed Passphrase",
|
||||||
|
"type": "toggleString",
|
||||||
|
"value": "",
|
||||||
|
"toggleValues": ["UTF8"]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} input
|
||||||
|
* @param {Object[]} args
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
run(input, args) {
|
||||||
|
const convertedSeedPhrase = Utils.convertToByteString(input.trim(), "UTF8");
|
||||||
|
const iterations = 2048;
|
||||||
|
const keySize = 512;
|
||||||
|
const hasher = "SHA512";
|
||||||
|
let salt;
|
||||||
|
if (args[0] === "bip39") {
|
||||||
|
salt = Utils.convertToByteString("mnemonic" + args[1].string, args[1].option) ||
|
||||||
|
forge.random.getBytesSync(keySize);
|
||||||
|
} else {
|
||||||
|
salt = Utils.convertToByteString("electrum" + args[1].string, args[1].option) ||
|
||||||
|
forge.random.getBytesSync(keySize);
|
||||||
|
}
|
||||||
|
const derivedKey = forge.pkcs5.pbkdf2(convertedSeedPhrase, salt, iterations, keySize / 8, hasher.toLowerCase());
|
||||||
|
return forge.util.bytesToHex(derivedKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SeedphraseToSeed;
|
224
src/core/operations/TypeCryptoArtifact.mjs
Normal file
224
src/core/operations/TypeCryptoArtifact.mjs
Normal file
|
@ -0,0 +1,224 @@
|
||||||
|
/**
|
||||||
|
* Attempts to determine the type of cryptocurrency artifact passed in.
|
||||||
|
*
|
||||||
|
* @author dgoldenberg [virtualcurrency@mitre.org]
|
||||||
|
* @copyright MITRE 2023, geco 2019
|
||||||
|
* @license MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {b58DoubleSHAChecksum} from "../lib/Bitcoin.mjs";
|
||||||
|
import { segwitChecksum } from "../lib/Bech32.mjs";
|
||||||
|
import Operation from "../Operation.mjs";
|
||||||
|
/**
|
||||||
|
* Converts a Public Key to a P2PKH Address of the given type.
|
||||||
|
*/
|
||||||
|
class TypeCryptoArtifact extends Operation {
|
||||||
|
/**
|
||||||
|
* Attempts to Type a Cryptocurrency artifact.
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.name = "Type Cryptocurrency Artifact";
|
||||||
|
this.module = "Default";
|
||||||
|
this.description = "Attempts to type and return information about a cryptocurrency artifact. ";
|
||||||
|
this.inputType = "string";
|
||||||
|
this.outputType = "JSON";
|
||||||
|
this.presentType = "string";
|
||||||
|
this.args = [];
|
||||||
|
this.checks = [
|
||||||
|
{
|
||||||
|
pattern: "^0(3|2)[0-9A-Fa-f]{64}$",
|
||||||
|
flags: "",
|
||||||
|
args: []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern: "^04[0-9A-Fa-f]{128}$",
|
||||||
|
flags: "",
|
||||||
|
args: []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern: "^[0-9A-Fa-f]{64}$",
|
||||||
|
flags: "",
|
||||||
|
args: []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern: "^0x[0-9A-Fa-f]{40}$",
|
||||||
|
flags: "",
|
||||||
|
args: []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern: "^(X|x|Y|y|Z|z|L|l|T|t)[pub|prv|tbv|tub][A-HJ-NP-Za-km-z1-9]{2,}$",
|
||||||
|
flags: "",
|
||||||
|
args: []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern: "^(1|3)[a-km-zA-HJ-NP-Z1-9]{25,34}$",
|
||||||
|
flags: "",
|
||||||
|
args: []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern: "^5[HJK][a-km-zA-HJ-NP-Z1-9]{49}$",
|
||||||
|
flags: "",
|
||||||
|
args: []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern: "^5[HJK][a-km-zA-HJ-NP-Z1-9]{49}$",
|
||||||
|
flags: "",
|
||||||
|
args: []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern: "^[KL][a-km-zA-HJ-NP-Z1-9]{51}$",
|
||||||
|
flags: "",
|
||||||
|
args: []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern: "^6P[a-km-zA-HJ-NP-Z1-9]{56}$",
|
||||||
|
flags: "",
|
||||||
|
args: []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern: "^(bc|tb)1q[023456789ac-hj-np-z]{36,}$",
|
||||||
|
flags: "",
|
||||||
|
args: []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern: "^(bc|tb)1p[023456789ac-hj-np-z]{36,}$",
|
||||||
|
flags: "",
|
||||||
|
args: []
|
||||||
|
}
|
||||||
|
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats the return data to be easier to read.
|
||||||
|
* @param {Object} output
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
present(output) {
|
||||||
|
switch (output.type) {
|
||||||
|
case "P2PKH":
|
||||||
|
return "Input " + output.value + " is possibly a Bitcoin P2PKH address.\n";
|
||||||
|
case "P2SH":
|
||||||
|
return "Input " + output.value + " is possibly a P2SH, or P2SH-P2WPKH address.\n";
|
||||||
|
case "XPUB":
|
||||||
|
return "Input " + output.value + " is possibly an extended public key.\n";
|
||||||
|
case "XPRV":
|
||||||
|
return "Input " + output.value + " is possibly an extended private key.\n";
|
||||||
|
case "compressedWIF":
|
||||||
|
return "Input " + output.value + " is possibly a compressed WIF (Wallet-Input-Format) Key.\n";
|
||||||
|
case "uncompressedWIF":
|
||||||
|
return "Input " + output.value + " is possibly an uncompressed WIF (Wallet-Input-Format) Key.\n";
|
||||||
|
case "eth":
|
||||||
|
return "Input " + output.value + " is possibly an ethereum address.\n";
|
||||||
|
case "bip38":
|
||||||
|
return "Input " + output.value + " is possibly a BIP38 encrypted key.\n";
|
||||||
|
case "segwit":
|
||||||
|
return "Input " + output.value + " is possibly a Segwit address (P2WPKH/P2WSH).\n";
|
||||||
|
case "taproot":
|
||||||
|
return "Input " + output.value + " is possibly a taproot.\n";
|
||||||
|
case "compressed_public":
|
||||||
|
return "Input " + output.value + " is possibly a compressed public key in raw bytes.\n";
|
||||||
|
case "uncompressed_public":
|
||||||
|
return "Input " + output.value + " is possibly an uncompressed public key in raw bytes.\n";
|
||||||
|
case "private":
|
||||||
|
return "Input " + output.value + " is possibly a private key in raw bytes.\n";
|
||||||
|
case "unknownB58":
|
||||||
|
return "Input " + output.value + " is a B58 encoded string, but of unknown type.\n";
|
||||||
|
case "unknownSegwit":
|
||||||
|
return "Input " + output.value + " is a Segwit encoded string, but of unknown type.\n";
|
||||||
|
case "error":
|
||||||
|
return "Error with input " + output.value + " \n";
|
||||||
|
case "blank":
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} input
|
||||||
|
* @param {Object[]} args
|
||||||
|
* @returns {Object}
|
||||||
|
*/
|
||||||
|
run(input, args) {
|
||||||
|
// We check if input is blank.
|
||||||
|
// If its blank or just whitespace, we don't need to bother dealing with it.
|
||||||
|
if (input.trim() === "") {
|
||||||
|
return {"value": input, "type": "blank"};
|
||||||
|
}
|
||||||
|
input = input.trim();
|
||||||
|
// We check to see if the input is hex or not.
|
||||||
|
const hexRe = /^[0-9A-Fa-f]{4,}$/g;
|
||||||
|
const inputIsHex = hexRe.test(input);
|
||||||
|
if (inputIsHex) {
|
||||||
|
const compressedPubkeyRe = /^0(3|2)[0-9A-Fa-f]{64}$/g;
|
||||||
|
const uncompressedPubKeyRe = /^04[0-9A-Fa-f]{128}$/g;
|
||||||
|
const privateKeyRe = /^[0-9A-Fa-f]{64}$/g;
|
||||||
|
if (compressedPubkeyRe.test(input)) {
|
||||||
|
return {"value": input, "type": "compressed_public"};
|
||||||
|
}
|
||||||
|
if (uncompressedPubKeyRe.test(input)) {
|
||||||
|
return {"value": input, "type": "uncompressed_public"};
|
||||||
|
}
|
||||||
|
if (privateKeyRe.test(input)) {
|
||||||
|
return {"value": input, "type": "private"};
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
const ethereumRe = /^0x[0-9A-Fa-f]{40}$/g;
|
||||||
|
if (ethereumRe.test(input)) {
|
||||||
|
return {"value": input, "type": "eth"};
|
||||||
|
}
|
||||||
|
const b58Checksum = b58DoubleSHAChecksum(input);
|
||||||
|
|
||||||
|
const sChecksum = segwitChecksum(input);
|
||||||
|
if (b58Checksum) {
|
||||||
|
const xRe = /^(X|x|Y|y|Z|z|L|l|T|t)[pub|prv|tbv|tub][A-HJ-NP-Za-km-z1-9]{2,}$/g;
|
||||||
|
const btcRe = /^(1|3)[a-km-zA-HJ-NP-Z1-9]{25,34}$/g;
|
||||||
|
const wifUncompressedRe = /^5[HJK][a-km-zA-HJ-NP-Z1-9]{49}$/g;
|
||||||
|
const wifCompressedRe = /^[KL][a-km-zA-HJ-NP-Z1-9]{51}$/g;
|
||||||
|
const bip38Re = /^6P[a-km-zA-HJ-NP-Z1-9]{56}$/g;
|
||||||
|
|
||||||
|
if (xRe.test(input)) {
|
||||||
|
if (input.slice(0, 4).includes("tbv") || input.slice(0, 4).includes("prv")) {
|
||||||
|
return {"value": input, "type": "XPRV"};
|
||||||
|
} else {
|
||||||
|
return {"value": input, "type": "XPUB"};
|
||||||
|
}
|
||||||
|
} else if (btcRe.test(input)) {
|
||||||
|
if (input.startsWith("1")) {
|
||||||
|
return {"value": input, "type": "P2PKH"};
|
||||||
|
} else {
|
||||||
|
return {"value": input, "type": "P2SH"};
|
||||||
|
}
|
||||||
|
} else if (wifCompressedRe.test(input)) {
|
||||||
|
return {"value": input, "type": "compressedWIF"};
|
||||||
|
} else if (wifUncompressedRe.test(input)) {
|
||||||
|
return {"value": input, "type": "uncompressedWIF"};
|
||||||
|
} else if (bip38Re.test(input)) {
|
||||||
|
return {"value": input, "type": "bip38"};
|
||||||
|
} else {
|
||||||
|
return {"value": input, "type": "unknownB58"};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (sChecksum) {
|
||||||
|
const oldSegwitRe = /^(bc|tb)1q[023456789ac-hj-np-z]{36,}$/;
|
||||||
|
const newSegwitRe = /^(bc|tb)1p[023456789ac-hj-np-z]{36,}$/;
|
||||||
|
|
||||||
|
if (oldSegwitRe.test(input)) {
|
||||||
|
return {"value": input, "type": "segwit"};
|
||||||
|
} else if (newSegwitRe.test(input)) {
|
||||||
|
return {"value": input, "type": "taproot"};
|
||||||
|
} else {
|
||||||
|
return {"value": input, "type": "unknownSegwit"};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {"value": input, "type": "error"};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TypeCryptoArtifact;
|
77
src/core/operations/WIFToPrivateKey.mjs
Normal file
77
src/core/operations/WIFToPrivateKey.mjs
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
/**
|
||||||
|
* Extracts the private key from a WIF format key.
|
||||||
|
*
|
||||||
|
* @author dgoldenberg [virtualcurrency@mitre.org]
|
||||||
|
* @copyright MITRE 2023
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Operation from "../Operation.mjs";
|
||||||
|
import { base58Decode, b58DoubleSHAChecksum} from "../lib/Bitcoin.mjs";
|
||||||
|
import { fromArrayBuffer } from "crypto-api/src/encoder/array-buffer.mjs";
|
||||||
|
import {toHex} from "crypto-api/src/encoder/hex.mjs";
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a private key to the WIF format.
|
||||||
|
*/
|
||||||
|
class WIFToPrivateKey extends Operation {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a private key to the WIF format.
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.name = "From WIF Format";
|
||||||
|
this.module = "Default";
|
||||||
|
this.description = "Turns a WIF format cryptocurrency key into the 32 byte private key. ";
|
||||||
|
this.inputType = "string";
|
||||||
|
this.outputType = "string";
|
||||||
|
this.infoURL = "https://en.bitcoin.it/wiki/Wallet_import_format";
|
||||||
|
this.args = [
|
||||||
|
];
|
||||||
|
this.checks = [
|
||||||
|
{
|
||||||
|
"pattern": "^5[HJK][a-km-zA-HJ-NP-Z1-9]{49}$",
|
||||||
|
"flags": "",
|
||||||
|
"args": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pattern": "^[KL][a-km-zA-HJ-NP-Z1-9]{51}$",
|
||||||
|
"flags": "",
|
||||||
|
"args": []
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} input
|
||||||
|
* @param {Object[]} args
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
run(input, args) {
|
||||||
|
// We check if input is blank.
|
||||||
|
// If its blank or just whitespace, we don't need to bother dealing with it.
|
||||||
|
|
||||||
|
if (input.trim().length === 0) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
input = input.trim();
|
||||||
|
if (b58DoubleSHAChecksum(input)) {
|
||||||
|
const decoded = base58Decode(input);
|
||||||
|
const trimmed = toHex(fromArrayBuffer(decoded.slice(1, -4)));
|
||||||
|
if (trimmed.endsWith("01") && trimmed.length === 66) {
|
||||||
|
return trimmed.slice(0, -2);
|
||||||
|
} else {
|
||||||
|
return trimmed;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return "Invalid Checksum. May not be a private Key. ";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default WIFToPrivateKey;
|
|
@ -149,6 +149,45 @@ import "./tests/TextEncodingBruteForce.mjs";
|
||||||
import "./tests/ToFromInsensitiveRegex.mjs";
|
import "./tests/ToFromInsensitiveRegex.mjs";
|
||||||
import "./tests/TranslateDateTimeFormat.mjs";
|
import "./tests/TranslateDateTimeFormat.mjs";
|
||||||
import "./tests/Typex.mjs";
|
import "./tests/Typex.mjs";
|
||||||
|
import "./tests/BLAKE2b.mjs";
|
||||||
|
import "./tests/BLAKE2s.mjs";
|
||||||
|
import "./tests/Protobuf.mjs";
|
||||||
|
import "./tests/ParseSSHHostKey.mjs";
|
||||||
|
import "./tests/DefangIP.mjs";
|
||||||
|
import "./tests/ParseUDP.mjs";
|
||||||
|
import "./tests/ParseTCP.mjs";
|
||||||
|
import "./tests/AvroToJSON.mjs";
|
||||||
|
import "./tests/Lorenz.mjs";
|
||||||
|
import "./tests/LuhnChecksum.mjs";
|
||||||
|
import "./tests/CipherSaber2.mjs";
|
||||||
|
import "./tests/Colossus.mjs";
|
||||||
|
import "./tests/ParseObjectIDTimestamp.mjs";
|
||||||
|
import "./tests/Unicode.mjs";
|
||||||
|
import "./tests/RSA.mjs";
|
||||||
|
import "./tests/CBOREncode.mjs";
|
||||||
|
import "./tests/CBORDecode.mjs";
|
||||||
|
import "./tests/JA3Fingerprint.mjs";
|
||||||
|
import "./tests/JA3SFingerprint.mjs";
|
||||||
|
import "./tests/HASSH.mjs";
|
||||||
|
import "./tests/ExtractDoubleSHA.mjs";
|
||||||
|
import "./tests/ExtractSegwitArtifacts.mjs";
|
||||||
|
import "./tests/TypeCryptoArtifacts.mjs";
|
||||||
|
import "./tests/ExtractSeedphrases.mjs";
|
||||||
|
import "./tests/PublicKeyToP2PKHAddress.mjs";
|
||||||
|
import "./tests/PrivateECKeyToPublic.mjs";
|
||||||
|
import "./tests/PrivateKeyToWIF.mjs";
|
||||||
|
import "./tests/ChangeExtendedKeyVersion.mjs";
|
||||||
|
import "./tests/SeedToMPK.mjs";
|
||||||
|
import "./tests/DecryptKeyStoreFile.mjs";
|
||||||
|
import "./tests/WIFToPrivateKey.mjs";
|
||||||
|
import "./tests/SeedphraseToSeed.mjs";
|
||||||
|
import "./tests/DeserializeExtendedKey.mjs";
|
||||||
|
import "./tests/PublicKeyToETHStyleAddress.mjs";
|
||||||
|
import "./tests/GetAllCasings.mjs";
|
||||||
|
import "./tests/SIGABA.mjs";
|
||||||
|
import "./tests/ELFInfo.mjs";
|
||||||
|
import "./tests/Subsection.mjs";
|
||||||
|
import "./tests/CaesarBoxCipher.mjs";
|
||||||
import "./tests/UnescapeString.mjs";
|
import "./tests/UnescapeString.mjs";
|
||||||
import "./tests/Unicode.mjs";
|
import "./tests/Unicode.mjs";
|
||||||
import "./tests/YARA.mjs";
|
import "./tests/YARA.mjs";
|
||||||
|
|
56
tests/operations/tests/ChangeExtendedKeyVersion.mjs
Normal file
56
tests/operations/tests/ChangeExtendedKeyVersion.mjs
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
/**
|
||||||
|
* Extended Key tests.
|
||||||
|
*
|
||||||
|
* @author dgoldenberg [virtualcurrency@mitre.org]
|
||||||
|
* @copyright MITRE 2023
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import TestRegister from "../../lib/TestRegister.mjs";
|
||||||
|
|
||||||
|
TestRegister.addTests([
|
||||||
|
{
|
||||||
|
name: "Change Extended Key Version (Test Idempotence XPRV)",
|
||||||
|
input: "xprv9s21ZrQH143K3LSmZ6frRpWYucY5KoUzD4xSU6Dj64nXtcBoPUE5cq3oBNyQVwBKDCimMN3k4gUUZ6eRMRFmt7HrrLdi2eZXBpAFXy4gx2c",
|
||||||
|
expectedOutput: "xprv9s21ZrQH143K3LSmZ6frRpWYucY5KoUzD4xSU6Dj64nXtcBoPUE5cq3oBNyQVwBKDCimMN3k4gUUZ6eRMRFmt7HrrLdi2eZXBpAFXy4gx2c",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "Change Extended Key Version",
|
||||||
|
"args": ["xprv"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Change Extended Key Version (xprv to yprv))",
|
||||||
|
input: "xprv9s21ZrQH143K3eogwtKjKajiVmJdwhtiaiT3iyysdeReAtijXNTSuCmnBCtEZM8C5b364oGZEdkVQ3tDCBLAvbvx2HzVs1pDbJ6rkR9xJMb",
|
||||||
|
expectedOutput: "yprvABrGsX5C9jantwzonF7MXfqDfjT5tKtDVpyGWNsm1eoXDzXxn2d1XGRvCQqpZFn7VE9tpGs7hJ73HLVmuskBiqcYtdgvSvdhs2AW8yT3J9a",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "Change Extended Key Version",
|
||||||
|
"args": ["yprv"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Change Extended Key Version (xprv to zprv))",
|
||||||
|
input: "xprv9s21ZrQH143K3eogwtKjKajiVmJdwhtiaiT3iyysdeReAtijXNTSuCmnBCtEZM8C5b364oGZEdkVQ3tDCBLAvbvx2HzVs1pDbJ6rkR9xJMb",
|
||||||
|
expectedOutput: "zprvAWgYBBk7JR8GkFBvcbtyjkviqhbXpwsiQwVVHmmePfBQH6MC2gna9L64DcoQZAS2tsGhZkTg9xTbAd7LdaACX5J9kyPM2qTC8kE9XXGNzuP",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "Change Extended Key Version",
|
||||||
|
"args": ["zprv"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Change Extended Key Version (zprv to xprv))",
|
||||||
|
input: "zprvAWgYBBk7JR8GkFBvcbtyjkviqhbXpwsiQwVVHmmePfBQH6MC2gna9L64DcoQZAS2tsGhZkTg9xTbAd7LdaACX5J9kyPM2qTC8kE9XXGNzuP",
|
||||||
|
expectedOutput: "xprv9s21ZrQH143K3eogwtKjKajiVmJdwhtiaiT3iyysdeReAtijXNTSuCmnBCtEZM8C5b364oGZEdkVQ3tDCBLAvbvx2HzVs1pDbJ6rkR9xJMb",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "Change Extended Key Version",
|
||||||
|
"args": ["xprv"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
]);
|
44
tests/operations/tests/DecryptKeyStoreFile.mjs
Normal file
44
tests/operations/tests/DecryptKeyStoreFile.mjs
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
/**
|
||||||
|
* Keystore tests
|
||||||
|
*
|
||||||
|
* @author dgoldenberg [virtualcurrency@mitre.org]
|
||||||
|
* @copyright MITRE 2023
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import TestRegister from "../../lib/TestRegister.mjs";
|
||||||
|
|
||||||
|
TestRegister.addTests([
|
||||||
|
{
|
||||||
|
name: "Decrypt Keystore File: With Address",
|
||||||
|
input: '{"address": "e6ff69353a16c9a5b139c79f6499d8ad74bfeceb", "crypto": {"cipher": "aes-128-ctr", "cipherparams": {"iv": "8cab4ff1613bb10cec166c69226e3ce5"}, "ciphertext": "78490968f75a8698660da5993395d2f561f41cd008498b349fd1bb4124eb3b72", "kdf": "scrypt", "kdfparams": {"dklen": 32, "n": 262144, "r": 1, "p": 8, "salt": "198fed004cf1d8cfd3d0ef8b2f99e058"}, "mac": "511ca8716f86a1853370d868478f57bdd19058a9464558750ff304e12a2cfd56"}, "id": "e130c4be-f3cf-4a22-bb80-4d3e948eb827", "version": 3}',
|
||||||
|
expectedOutput: "128538fd0b0e53d52ac4c4dcd66badaec5c83eacd4d31709979dca31f5e761c7",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "Decrypt Keystore File",
|
||||||
|
"args": ["password1234"]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Decrypt Keystore File: Cast to Address",
|
||||||
|
input: '{"address": "e6ff69353a16c9a5b139c79f6499d8ad74bfeceb", "crypto": {"cipher": "aes-128-ctr", "cipherparams": {"iv": "8cab4ff1613bb10cec166c69226e3ce5"}, "ciphertext": "78490968f75a8698660da5993395d2f561f41cd008498b349fd1bb4124eb3b72", "kdf": "scrypt", "kdfparams": {"dklen": 32, "n": 262144, "r": 1, "p": 8, "salt": "198fed004cf1d8cfd3d0ef8b2f99e058"}, "mac": "511ca8716f86a1853370d868478f57bdd19058a9464558750ff304e12a2cfd56"}, "id": "e130c4be-f3cf-4a22-bb80-4d3e948eb827", "version": 3}',
|
||||||
|
expectedOutput: "0xe6ff69353a16c9a5b139c79f6499d8ad74bfeceb",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "Decrypt Keystore File",
|
||||||
|
"args": ["password1234"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "Private EC Key to Public Key",
|
||||||
|
"args": [false]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "Public Key To ETH Style Address",
|
||||||
|
"args": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
]);
|
35
tests/operations/tests/DeserializeExtendedKey.mjs
Normal file
35
tests/operations/tests/DeserializeExtendedKey.mjs
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
/**
|
||||||
|
* Deserialize Key tests.
|
||||||
|
*
|
||||||
|
* @author dgoldenberg [virtualcurrency@mitre.org]
|
||||||
|
* @copyright MITRE 2023
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import TestRegister from "../../lib/TestRegister.mjs";
|
||||||
|
|
||||||
|
TestRegister.addTests([
|
||||||
|
{
|
||||||
|
name: "Deserialize Extended Key",
|
||||||
|
input: "xprv9s21ZrQH143K2cGpWQPQuyZYEMwror2UbE2oWX7BfanEBvVay5SZAbF5795VUXesFgkHXAw1eDzijWA1QMG76cxqehM7zZurQuCrJsZnPFi",
|
||||||
|
expectedOutput: "Key Analyzed: xprv9s21ZrQH143K2cGpWQPQuyZYEMwror2UbE2oWX7BfanEBvVay5SZAbF5795VUXesFgkHXAw1eDzijWA1QMG76cxqehM7zZurQuCrJsZnPFi\n\tChecksum: 663cfc75\n\tVersion: 0488ade4\n\tLevel: 0\n\tFingerprint: 00000000\n\tChaincode: 374570a7ea4028600ce87e2769b4be7e4d90aa0b417cde33bc6c896f046b2c9f\n\tMasterKey: 00692cd2a168f6ef0d1b857b5f0ce89f14cc9fae5888a3a822c22e31b85b442059\n\n",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "Deserialize Extended Key",
|
||||||
|
"args": [false]
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Deserialize Extended Key - Key Only",
|
||||||
|
input: "xprv9s21ZrQH143K2cGpWQPQuyZYEMwror2UbE2oWX7BfanEBvVay5SZAbF5795VUXesFgkHXAw1eDzijWA1QMG76cxqehM7zZurQuCrJsZnPFi",
|
||||||
|
expectedOutput: "00692cd2a168f6ef0d1b857b5f0ce89f14cc9fae5888a3a822c22e31b85b442059",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "Deserialize Extended Key",
|
||||||
|
"args": [true]
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
]);
|
61
tests/operations/tests/ExtractDoubleSHA.mjs
Normal file
61
tests/operations/tests/ExtractDoubleSHA.mjs
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
/**
|
||||||
|
* Double SHA extraction tests.
|
||||||
|
*
|
||||||
|
* @author dgoldenberg [virtualcurrency@mitre.org]
|
||||||
|
* @copyright MITRE 2023
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import TestRegister from "../../lib/TestRegister.mjs";
|
||||||
|
|
||||||
|
TestRegister.addTests([
|
||||||
|
{
|
||||||
|
name: "Extract Double SHA Artifacts - Basic - Addresses.",
|
||||||
|
input: "1CK6KHY6MHgYvmRQ4PAafKYDrg1ejbH1cE\n"+ "138EKMWwCNeScgkBs7ujW4JAUkx5PJqE5c\n" + "138EKMWwCNeScgkBs7ujW4JAUkx5PJqE5d\n" +
|
||||||
|
"37TKx6FKj3P7fAeVoVwKsy39DzFhyHnvnr\n" + "37TKx6FKj3P7fAeVoVwKsy39DzFhyHnvnq\n",
|
||||||
|
expectedOutput: "1CK6KHY6MHgYvmRQ4PAafKYDrg1ejbH1cE\n" + "138EKMWwCNeScgkBs7ujW4JAUkx5PJqE5c\n" + "37TKx6FKj3P7fAeVoVwKsy39DzFhyHnvnr\n",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "Extract Double SHA Artifacts",
|
||||||
|
"args": []
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Extract Double SHA Artifacts - Basic - Extended Public Keys.",
|
||||||
|
input: "xprv9s21ZrQH143K31GyMWKbByidYkgWYp6w8jv66FXRAF2LQPyDvFKNUz57Rdq9zw4avf11d1GQ71rtH1fvWxo8iHq5J3LwqxUjYGVhk7Tf9Z2\n" +
|
||||||
|
"xprv9s21ZrQH143K31GyMWKbByidYkgWYp6w8jv66FXRAF2LQPyDvFKNUz57Rdq9zw4avf11d1GQ71rtH1fvWxo8iHq5J3LwqxUjYGVhk7Tf9Z3\n" +
|
||||||
|
"xpub6CVS2FsjnyE9kTXw1jxaXdDwgGduEfCtF4JLHGbiL5GqamiaRnzAjKYCCcg2BTMgpqcudPsZX6WtaiJXNbwdWfSU657nduXSiFFgbGD1q7t\n" +
|
||||||
|
"xpub6CVS2FsjnyE9kTXw1jxaXdDwgGduEfCtF4JLHGbiL5GqamiaRnzAjKYCCcg2BTMgpqcudPsZX6WtaiJXNbwdWfSU657nduXSiFFgbGD1q7x\n" +
|
||||||
|
"Ltpv71G8qDifUiNesyvYoQNDLPwNk29NdonGYiwe3jur9tcRRCk6nt5vqpmmPQNAsehmqmaHhWuvG6ZYGWnzv5n7MQCRFcJ89uXmLf7boarssUN\n" +
|
||||||
|
"Ltpv71G8qDifUiNesyvYoQNDLPwNk29NdonGYiwe3jur9tcRRCk6nt5vqpmmPQNAsehmqmaHhWuvG6ZYGWnzv5n7MQCRFcJ89uXmLf7boarssUQ\n" +
|
||||||
|
"Ltub2b1CMTSWhX5jGKUYqyXJMpko827CQ6ixymV2KnvQ7xg3Egd1aSPYF2dm4Abdx2jsH3MgmfWFzPkC131ed8LH4814j9HT9SnwfU5fqZcg3UD\n" +
|
||||||
|
"Ltub2b1CMTSWhX5jGKUYqyXJMpko827CQ6ixymV2KnvQ7xg3Egd1aSPYF2dm4Abdx2jsH3MgmfWFzPkC131ed8LH4814j9HT9SnwfU5fqZcg3Us\n",
|
||||||
|
expectedOutput: "xprv9s21ZrQH143K31GyMWKbByidYkgWYp6w8jv66FXRAF2LQPyDvFKNUz57Rdq9zw4avf11d1GQ71rtH1fvWxo8iHq5J3LwqxUjYGVhk7Tf9Z2\n" +
|
||||||
|
"xpub6CVS2FsjnyE9kTXw1jxaXdDwgGduEfCtF4JLHGbiL5GqamiaRnzAjKYCCcg2BTMgpqcudPsZX6WtaiJXNbwdWfSU657nduXSiFFgbGD1q7t\n" +
|
||||||
|
"Ltpv71G8qDifUiNesyvYoQNDLPwNk29NdonGYiwe3jur9tcRRCk6nt5vqpmmPQNAsehmqmaHhWuvG6ZYGWnzv5n7MQCRFcJ89uXmLf7boarssUN\n" +
|
||||||
|
"Ltub2b1CMTSWhX5jGKUYqyXJMpko827CQ6ixymV2KnvQ7xg3Egd1aSPYF2dm4Abdx2jsH3MgmfWFzPkC131ed8LH4814j9HT9SnwfU5fqZcg3UD\n",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "Extract Double SHA Artifacts",
|
||||||
|
"args": []
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Extract Double SHA Artifacts - Basic - Private Keys.",
|
||||||
|
input: "KycSyYzEQvDEh2mBxVwj8igqsK5P1maqpRhmyZZZjC5vA443NfZP\n" +
|
||||||
|
"KycSyYzEQvDEh2mBxVwj8igqsK5P1maqpRhmyZZZjC5vA443NfZQ\n" +
|
||||||
|
"L4ecqwNqxgCUGdtL8Czup7eU71uxVNjqNsXfa3cCXSBvFoDRb2nf\n" +
|
||||||
|
"L4ecqwNqxgCUGdtL8Czup7eU71uxVNjqNsXfa3cCXSBvFoDRb2nx\n" +
|
||||||
|
"TBDvJMKM359XnzFUePjbJGmpDvDDZNTdTLzVJgbYBBK5hQSKikcZ\n" +
|
||||||
|
"TBDvJMKM359XnzFUePjbJGmpDvDDZNTdTLzVJgbYBBK5hQSKikcs\n",
|
||||||
|
expectedOutput: "KycSyYzEQvDEh2mBxVwj8igqsK5P1maqpRhmyZZZjC5vA443NfZP\n" + "L4ecqwNqxgCUGdtL8Czup7eU71uxVNjqNsXfa3cCXSBvFoDRb2nf\n" + "TBDvJMKM359XnzFUePjbJGmpDvDDZNTdTLzVJgbYBBK5hQSKikcZ\n",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "Extract Double SHA Artifacts",
|
||||||
|
"args": []
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
]);
|
57
tests/operations/tests/ExtractSeedphrases.mjs
Normal file
57
tests/operations/tests/ExtractSeedphrases.mjs
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
/**
|
||||||
|
* Seedphrase Extract tests.
|
||||||
|
*
|
||||||
|
* @author dgoldenberg [virtualcurrency@mitre.org]
|
||||||
|
* @copyright MITRE 2023
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import TestRegister from "../../lib/TestRegister.mjs";
|
||||||
|
|
||||||
|
TestRegister.addTests([
|
||||||
|
{
|
||||||
|
name: "Extract Seedphrases - BIP39 - Basic With Extra Newlines",
|
||||||
|
input: "Test BIP39 Seedphrase:\n\nfitness shed \ntape chef fiber behave dad again glass number please panic\n\nThis has been a test.",
|
||||||
|
expectedOutput: "fitness shed tape chef fiber behave dad again glass number please panic\n",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "Extract Seedphrases",
|
||||||
|
"args": ["bip39"]
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Extract Seedphrases - BIP39 - Basic With Extra Newlines and Tabs",
|
||||||
|
input: "Test BIP39 Seedphrase:\n\nfitness \tshed \ntape chef fiber behave dad again \t\nglass number \n\tplease panic\n\nThis has been a test.",
|
||||||
|
expectedOutput: "fitness shed tape chef fiber behave dad again glass number please panic\n",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "Extract Seedphrases",
|
||||||
|
"args": ["bip39"]
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Extract Seedphrases - BIP39 - Basic With Extra Newlines and not actual words.",
|
||||||
|
input: "Test BIP39 Seedphrase:\n\nfitness shed \ntape chef 1: fiber behave 2: dad again glass 4:number please panic\n\nThis has been a test.",
|
||||||
|
expectedOutput: "fitness shed tape chef fiber behave dad again glass number please panic\n",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "Extract Seedphrases",
|
||||||
|
"args": ["bip39"]
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Extract Seedphrases - Electrum2 - Basic with Extra Newlines",
|
||||||
|
input: "Test Electrum2 Seedphrase:\n\nventure enter \nribbon belt anger razor problem believe swap silk bike blur\n\nThis has been a test.",
|
||||||
|
expectedOutput: "venture enter ribbon belt anger razor problem believe swap silk bike blur\n",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "Extract Seedphrases",
|
||||||
|
"args": ["electrum2"]
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
]);
|
27
tests/operations/tests/ExtractSegwitArtifacts.mjs
Normal file
27
tests/operations/tests/ExtractSegwitArtifacts.mjs
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
/**
|
||||||
|
* Segwit extract tests.
|
||||||
|
*
|
||||||
|
* @author dgoldenberg [virtualcurrency@mitre.org]
|
||||||
|
* @copyright MITRE 2023
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import TestRegister from "../../lib/TestRegister.mjs";
|
||||||
|
|
||||||
|
TestRegister.addTests([
|
||||||
|
{
|
||||||
|
name: "Extract Segwit Artifacts - Basic",
|
||||||
|
input: "bc1qglsjlj8m2yzxvlwvhclz0kdpy96q2vtn5cmre6\n" + "bc1qglsjlj8m2yzxvlwvhclz0kdpy96q2vtn5cmre7\n" +
|
||||||
|
"ltc1qnral26ktht5e5yr7fkzcpzwm7vta4ykjm4ksp0\n" + "ltc1qnral26ktht5e5yr7fkzcpzwm7vta4ykjm4ksp1\n" +
|
||||||
|
"tb1qp09q604zgltxrqsu5s24qnyv6ycm3g6lzw45ta\n" + "tb1qp09q604zgltxrqsu5s24qnyv6ycm3g6lzw45ts\n" +
|
||||||
|
"bc1pr28rdctaptapvyumjqxushmht57qccj2y7r0hszm5ldc0ua9tlxsch4nge\nbc1pr28rdctaptapvyumjqxushmht57qccj2y7r0hszm5ldc0ua9tlxsch4ngf\n",
|
||||||
|
expectedOutput: "bc1qglsjlj8m2yzxvlwvhclz0kdpy96q2vtn5cmre6\n" + "ltc1qnral26ktht5e5yr7fkzcpzwm7vta4ykjm4ksp0\n" +
|
||||||
|
"tb1qp09q604zgltxrqsu5s24qnyv6ycm3g6lzw45ta\nbc1pr28rdctaptapvyumjqxushmht57qccj2y7r0hszm5ldc0ua9tlxsch4nge\n",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "Extract Segwit Addresses",
|
||||||
|
"args": []
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]);
|
76
tests/operations/tests/PrivateECKeyToPublic.mjs
Normal file
76
tests/operations/tests/PrivateECKeyToPublic.mjs
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
/**
|
||||||
|
* Private key to secp256k1 public key tests.
|
||||||
|
*
|
||||||
|
* @author dgoldenberg [virtualcurrency@mitre.org]
|
||||||
|
* @copyright MITRE 2023
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import TestRegister from "../../lib/TestRegister.mjs";
|
||||||
|
|
||||||
|
|
||||||
|
TestRegister.addTests([
|
||||||
|
{
|
||||||
|
name: "Private EC Key to Public (Compressed)",
|
||||||
|
input: "5E2A8FDE9F861056607208F512287CFBD634E124044EE23EBF7289E8E7B3822E",
|
||||||
|
expectedOutput: "036cf115be1fc5f54585965817c735d74bdae03d9665a43704a9bdbf8a1c6b1e40",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "Private EC Key to Public Key",
|
||||||
|
"args": [true],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Private EC Key to Public (Uncompressed)",
|
||||||
|
input: "5E2A8FDE9F861056607208F512287CFBD634E124044EE23EBF7289E8E7B3822E",
|
||||||
|
expectedOutput: "046cf115be1fc5f54585965817c735d74bdae03d9665a43704a9bdbf8a1c6b1e401f8a5888434ba6abc1967c036cc28903283d0f43b53fee63a3fb0b416019892b",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "Private EC Key to Public Key",
|
||||||
|
"args": [false],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Private EC Key to Public (From Bytes Uncompressed)",
|
||||||
|
input: "5E2A8FDE9F861056607208F512287CFBD634E124044EE23EBF7289E8E7B3822E",
|
||||||
|
expectedOutput: "036cf115be1fc5f54585965817c735d74bdae03d9665a43704a9bdbf8a1c6b1e40",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "From Hex",
|
||||||
|
"args": ["Auto"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "Private EC Key to Public Key",
|
||||||
|
"args": [true],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Private EC Key to Public (Wrong Length)",
|
||||||
|
input: "5E2A8FDE9F861056607208F512287CFBD634E124044EE23EBF7289E8E7B3822E08",
|
||||||
|
expectedOutput: "Error with the input as private key. Error is:\n\tInvalid length. We want either 32 or 64 but we got: 66",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "Private EC Key to Public Key",
|
||||||
|
"args": [false],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Private EC Key to Public (From Bytes Uncompressed Wrong Length)",
|
||||||
|
input: "5E2A8FDE9F861056607208F512287CFBD634E124044EE23EBF7289E8E7B3822E08",
|
||||||
|
expectedOutput: "Error with the input as private key. Error is:\n\tInvalid length. We want either 32 or 64 but we got: 33",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "From Hex",
|
||||||
|
"args": ["Auto"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "Private EC Key to Public Key",
|
||||||
|
"args": [true],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
]);
|
88
tests/operations/tests/PrivateKeyToWIF.mjs
Normal file
88
tests/operations/tests/PrivateKeyToWIF.mjs
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
/**
|
||||||
|
* Private Key to Wallet-Import-Format tests.
|
||||||
|
*
|
||||||
|
* @author dgoldenberg [virtualcurrency@mitre.org]
|
||||||
|
* @copyright MITRE 2023
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import TestRegister from "../../lib/TestRegister.mjs";
|
||||||
|
|
||||||
|
|
||||||
|
TestRegister.addTests([
|
||||||
|
{
|
||||||
|
name: "Private Key To WIF (BTC, Compressed)",
|
||||||
|
input: "5E2A8FDE9F861056607208F512287CFBD634E124044EE23EBF7289E8E7B3822E",
|
||||||
|
expectedOutput: "KzNkuRstcjZGuwYu5wWbLcwXzNngvH2oWPDymKxL7tdU48SSb5uX",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "To WIF Format",
|
||||||
|
"args": ["BTC", true]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Private Key To WIF (BTC, Compressed, From Bytes)",
|
||||||
|
input: "5E2A8FDE9F861056607208F512287CFBD634E124044EE23EBF7289E8E7B3822E",
|
||||||
|
expectedOutput: "KzNkuRstcjZGuwYu5wWbLcwXzNngvH2oWPDymKxL7tdU48SSb5uX",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "From Hex",
|
||||||
|
"args": ["Auto"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "To WIF Format",
|
||||||
|
"args": ["BTC", true]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Private Key To WIF (BTC, Uncompressed)",
|
||||||
|
input: "5E2A8FDE9F861056607208F512287CFBD634E124044EE23EBF7289E8E7B3822E",
|
||||||
|
expectedOutput: "5JXkw276MLA8EMWtDuE3o8gzmie5y2NFcf7ZoHywdTCSAPQWJtG",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "To WIF Format",
|
||||||
|
"args": ["BTC", false]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Private Key To WIF (Testnet, Compressed)",
|
||||||
|
input: "6ADA95C3D8BC2E06E818EC4816A0EB4455448A3FD70DA26EF218F7FC5ED1A2C0",
|
||||||
|
expectedOutput: "cRAQpjSDxVVHdMHQbhyUjE8XytUm8nB47NHJqg9dpJqkhuetPesF",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "To WIF Format",
|
||||||
|
"args": ["Testnet", true]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Private Key To WIF (BTC, Compressed, Wrong Number of Bytes)",
|
||||||
|
input: "5E2A8FDE9F861056607208F512287CFBD634E124044EE23EBF7289E8E7B3822E08",
|
||||||
|
expectedOutput: "Error parsing private key. Error is:\n\tInvalid length. We want either 32 or 64 but we got: 66",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "To WIF Format",
|
||||||
|
"args": ["BTC", true]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Private Key To WIF (BTC, Compressed, Wrong Number of Bytes)",
|
||||||
|
input: "5E2A8FDE9F861056607208F512287CFBD634E124044EE23EBF7289E8E7B3822E08",
|
||||||
|
expectedOutput: "Error parsing private key. Error is:\n\tInvalid length. We want either 32 or 64 but we got: 33",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "From Hex",
|
||||||
|
"args": ["Auto"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "To WIF Format",
|
||||||
|
"args": ["BTC", true]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
]);
|
110
tests/operations/tests/PublicKeyToETHStyleAddress.mjs
Normal file
110
tests/operations/tests/PublicKeyToETHStyleAddress.mjs
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
/**
|
||||||
|
* Public Key to ETH Style Address Cryptocurrency Address tests.
|
||||||
|
*
|
||||||
|
* @author dgoldenberg [virtualcurrency@mitre.org]
|
||||||
|
* @copyright MITRE 2023
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import TestRegister from "../../lib/TestRegister.mjs";
|
||||||
|
|
||||||
|
|
||||||
|
TestRegister.addTests([
|
||||||
|
{
|
||||||
|
name: "Public Key To ETH Style Address",
|
||||||
|
input: "04d26bcecd763bdf6bdb89ba929d2485429fbda73bae723d525ef55554ef45350582085bd24055079f6deebad5b6af612c14587c6862391d330484afe750fbf144",
|
||||||
|
expectedOutput: "0x63e8b85679d29235791a0f558d6485c7ed51c9e6",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "Public Key To ETH Style Address",
|
||||||
|
"args": []
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Public Key To ETH Style Address 2",
|
||||||
|
input: "047d3e5107b46421c3ddf7292fcbe1f5805952279926c325d119f6ddfed1e5e7ea9a9f5dceadc57b897cb286479450b66a6422f8f270bcd2a61ab4eea800911956",
|
||||||
|
expectedOutput: "0x099f75d5bc069026531394d5c6d6c37a41158d31",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "Public Key To ETH Style Address",
|
||||||
|
"args": []
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Public Key to ETH Style Address: Compressed Key",
|
||||||
|
input: "027d3e5107b46421c3ddf7292fcbe1f5805952279926c325d119f6ddfed1e5e7ea",
|
||||||
|
expectedOutput: "0x099f75d5bc069026531394d5c6d6c37a41158d31",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "Public Key To ETH Style Address",
|
||||||
|
"args": []
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Public Key to ETH Style Address: Compressed Key 2",
|
||||||
|
input: "039d491d3f5a2a79b422b76b8115b175b38e42c6103b1a84c88fb6221fe9072bbe",
|
||||||
|
expectedOutput: "0x345029d286159d516ca1169edf0a80ff6c855ac0",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "Public Key To ETH Style Address",
|
||||||
|
"args": [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Public Key to ETH Style Address: Compressed Key (From Hex)",
|
||||||
|
input: "027d3e5107b46421c3ddf7292fcbe1f5805952279926c325d119f6ddfed1e5e7ea",
|
||||||
|
expectedOutput: "0x099f75d5bc069026531394d5c6d6c37a41158d31",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "From Hex",
|
||||||
|
"args": ["Auto"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "Public Key To ETH Style Address",
|
||||||
|
"args": []
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Public Key to ETH Style Address: Compressed Key 2 (From Hex)",
|
||||||
|
input: "039d491d3f5a2a79b422b76b8115b175b38e42c6103b1a84c88fb6221fe9072bbe",
|
||||||
|
expectedOutput: "0x345029d286159d516ca1169edf0a80ff6c855ac0",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "From Hex",
|
||||||
|
"args": ["Auto"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "Public Key To ETH Style Address",
|
||||||
|
"args": [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Public Key To ETH Style Address (From Hex)",
|
||||||
|
input: "04d26bcecd763bdf6bdb89ba929d2485429fbda73bae723d525ef55554ef45350582085bd24055079f6deebad5b6af612c14587c6862391d330484afe750fbf144",
|
||||||
|
expectedOutput: "0x63e8b85679d29235791a0f558d6485c7ed51c9e6",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "From Hex",
|
||||||
|
"args": ["Auto"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "Public Key To ETH Style Address",
|
||||||
|
"args": []
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
]);
|
183
tests/operations/tests/PublicKeyToP2PKHAddress.mjs
Normal file
183
tests/operations/tests/PublicKeyToP2PKHAddress.mjs
Normal file
|
@ -0,0 +1,183 @@
|
||||||
|
/**
|
||||||
|
* Public Key to cryptocurrency address tests.
|
||||||
|
*
|
||||||
|
* @author dgoldenberg [virtualcurrency@mitre.org]
|
||||||
|
* @copyright MITRE 2023
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import TestRegister from "../../lib/TestRegister.mjs";
|
||||||
|
|
||||||
|
|
||||||
|
TestRegister.addTests([
|
||||||
|
{
|
||||||
|
name: "Public Key To Address: P2PKH (1)",
|
||||||
|
input: "03ebf60a619da2fbc6239089ca0a93878ea53baa3d22188cacad4033b103237ae9",
|
||||||
|
expectedOutput: "1MwwHqDj1FAyABeqPeiTTvJQCoCorcuFyP",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "Public Key To Cryptocurrency Address",
|
||||||
|
"args": ["BTC", "P2PKH (V1 BTC Addresses)"]
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Public Key To Address: P2PKH (2)",
|
||||||
|
input: "021dc4eb14b93dfbbbe4578293d07b6ee443ca025d89fd43a657ee3fd8c81d03f6",
|
||||||
|
expectedOutput: "18wUwr4Jvor6LG1mvQcfEp1Lx51dYAXZX1",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "Public Key To Cryptocurrency Address",
|
||||||
|
"args": ["BTC", "P2PKH (V1 BTC Addresses)"]
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Public Key To Address: P2PKH LTC",
|
||||||
|
input: "037b39f764a10f31bfd47038738ca27bffeefce1fe4ccbfb9343fcb69d9363b27b",
|
||||||
|
expectedOutput: "LPTR2TBuF8vbwWaJdNeCAQemW4SC7q7zJP",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "Public Key To Cryptocurrency Address",
|
||||||
|
"args": ["LTC", "P2PKH (V1 BTC Addresses)"]
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Public Key To Address: P2PKH (Long)",
|
||||||
|
input: "04219A19E157B5FEDDF7EBDD3C7A58D7AB4F6565E84226691B6A5F80BBCE8E0100B49D6AB503CA4B701626E941EB8D2460F154992D7AD4EC671CF1CFB8C1DE8164",
|
||||||
|
expectedOutput: "1BgRqTW8RMmcTRXHymTCVJsn5NVk9U8L9q",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "Public Key To Cryptocurrency Address",
|
||||||
|
"args": ["BTC", "P2PKH (V1 BTC Addresses)"]
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Public Key To Address: P2SH-P2WPKH (1)",
|
||||||
|
input: "03a24ca9f13f6bcbc15615f71504be75566120b9bc7072e171756233162c726432",
|
||||||
|
expectedOutput: "31vhdy8RGhSYZRRGZfqvZHGzVtpcua4cQW",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "Public Key To Cryptocurrency Address",
|
||||||
|
"args": ["BTC", "P2SH-P2PWPKH (Segwit Compatible V3 Addresses)"]
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Public Key To Address: P2SH-P2WPKH (2)",
|
||||||
|
input: "021a4310db1211939e20c88e9b90be354a145ec323a045de47ff0ea3145f99c8c9",
|
||||||
|
expectedOutput: "3C9wCFwcd36MHVpontDF7zQfKPfRTNg4Fe",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "Public Key To Cryptocurrency Address",
|
||||||
|
"args": ["BTC", "P2SH-P2PWPKH (Segwit Compatible V3 Addresses)"]
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Public Key To Address: P2SH-P2WPKH LTC",
|
||||||
|
input: "02f442a169ca36702bbcbb268319295bece8fe1cbc6ca095b2669d13ef56c759de",
|
||||||
|
expectedOutput: "MMwYiJmkxDKqiP2WWAHMMgkeRt2nLxGqih",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "Public Key To Cryptocurrency Address",
|
||||||
|
"args": ["LTC", "P2SH-P2PWPKH (Segwit Compatible V3 Addresses)"]
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Public Key To Address: P2WPKH (1)",
|
||||||
|
input: "02530f0512d544344a04777be5477a2ffef813a110ac0705fafa012f5b61b56380",
|
||||||
|
expectedOutput: "bc1qu37uvwyzj23a2dd3x5nd8s77nfskzu3lzkuqfm",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "Public Key To Cryptocurrency Address",
|
||||||
|
"args": ["BTC", "Segwit (P2WPKH bc1 Addresses)"]
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Public Key To Address: P2WPKH (2)",
|
||||||
|
input: "03bc32bdc5dc96c9fb56e2481fefd321ebe9e17a807bbb337dea1df5e68b1f0756",
|
||||||
|
expectedOutput: "bc1qrjluhfu5qr2780zlvcx3kquckpvuamwqp2sjle",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "Public Key To Cryptocurrency Address",
|
||||||
|
"args": ["BTC", "Segwit (P2WPKH bc1 Addresses)"]
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Public Key To Address: P2WPKH LTC",
|
||||||
|
input: "026a532c31184b94edf540ad60c3cc208342be4f51cc764a373a420731dd198a59",
|
||||||
|
expectedOutput: "ltc1qj587punda8h0r4m83k794xseqlnl3az4ktu2zp",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "Public Key To Cryptocurrency Address",
|
||||||
|
"args": ["LTC", "Segwit (P2WPKH bc1 Addresses)"]
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Public Key To Address: (Testnet)",
|
||||||
|
input: "02aa6438e78b18a503f4466672fd04e31aaeec3ed1c3e0e1f19654776f0f0dc1b2",
|
||||||
|
expectedOutput: "mmuoeJDuuzeuaii1V6tPK3L5YjaJwjPqUM",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "Public Key To Cryptocurrency Address",
|
||||||
|
"args": ["Testnet", "P2PKH (V1 BTC Addresses)"]
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Public Key To Address: P2WPKH (Wrong Length)",
|
||||||
|
input: "03bc32bdc5dc96c9fb56e2481fefd321ebe9e17a807bbb337dea1df5e68b1f075642",
|
||||||
|
expectedOutput: "Invalid length. We want either 33, 65 (if bytes) or 66, 130 (if hex) but we got: 68",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "Public Key To Cryptocurrency Address",
|
||||||
|
"args": ["BTC", "Segwit (P2WPKH bc1 Addresses)"]
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Public Key To Address: P2WPKH (Wrong Start)",
|
||||||
|
input: "05bc32bdc5dc96c9fb56e2481fefd321ebe9e17a807bbb337dea1df5e68b1f0756",
|
||||||
|
expectedOutput: "We have a valid hex string, of reasonable length, (66) but doesn't start with the right value. Correct values are 02, or 03 but we have: 05",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "Public Key To Cryptocurrency Address",
|
||||||
|
"args": ["BTC", "Segwit (P2WPKH bc1 Addresses)"]
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Public Key To Address: P2PKH (Long With Error)",
|
||||||
|
input: "06219A19E157B5FEDDF7EBDD3C7A58D7AB4F6565E84226691B6A5F80BBCE8E0100B49D6AB503CA4B701626E941EB8D2460F154992D7AD4EC671CF1CFB8C1DE8164",
|
||||||
|
expectedOutput: "We have a valid hex string of reasonable length, (130) but doesn't start with the right value. Correct values are 04 but we have: 06",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "Public Key To Cryptocurrency Address",
|
||||||
|
"args": ["BTC", "P2PKH (V1 BTC Addresses)"]
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Public Key To Address: P2PKH (From Bytes)",
|
||||||
|
input: "03ebf60a619da2fbc6239089ca0a93878ea53baa3d22188cacad4033b103237ae9",
|
||||||
|
expectedOutput: "1MwwHqDj1FAyABeqPeiTTvJQCoCorcuFyP",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "From Hex",
|
||||||
|
"args": ["Auto"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "Public Key To Cryptocurrency Address",
|
||||||
|
"args": ["BTC", "P2PKH (V1 BTC Addresses)"]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]);
|
62
tests/operations/tests/SeedToMPK.mjs
Normal file
62
tests/operations/tests/SeedToMPK.mjs
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
/**
|
||||||
|
* Seed to master private key tests.
|
||||||
|
*
|
||||||
|
* @author dgoldenberg [virtualcurrency@mitre.org]
|
||||||
|
* @copyright MITRE 2023
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import TestRegister from "../../lib/TestRegister.mjs";
|
||||||
|
|
||||||
|
|
||||||
|
TestRegister.addTests([
|
||||||
|
{
|
||||||
|
name: "Seed To Master Private Key (xprv)",
|
||||||
|
input: "c766f48d3729a16249b5d0171c678d458d31454b2bb7791b61169b5541a130719714ebd41f22a2515246d013e9a4e978aee48dd5140b73a540108d58008c4aa9",
|
||||||
|
expectedOutput: "xprv9s21ZrQH143K2nwujwREGif1wyBwt5Jh9BdFVSgSeYdrUp1qPxKsHrmnpJ8xKpKPDvXJMmBRpsZ3X64MeafyURs8Xoj53kGu7hb48Yg7unj",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "Seed To Master Key",
|
||||||
|
"args": ["xprv"]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Seed To Master Private Key (xprv) From Hex",
|
||||||
|
input: "c766f48d3729a16249b5d0171c678d458d31454b2bb7791b61169b5541a130719714ebd41f22a2515246d013e9a4e978aee48dd5140b73a540108d58008c4aa9",
|
||||||
|
expectedOutput: "xprv9s21ZrQH143K2nwujwREGif1wyBwt5Jh9BdFVSgSeYdrUp1qPxKsHrmnpJ8xKpKPDvXJMmBRpsZ3X64MeafyURs8Xoj53kGu7hb48Yg7unj",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "From Hex",
|
||||||
|
"args": ["Auto"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "Seed To Master Key",
|
||||||
|
"args": ["xprv"]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Seed To Master Private Key (tprv)",
|
||||||
|
input: "c766f48d3729a16249b5d0171c678d458d31454b2bb7791b61169b5541a130719714ebd41f22a2515246d013e9a4e978aee48dd5140b73a540108d58008c4aa9",
|
||||||
|
expectedOutput: "tprv8ZgxMBicQKsPdcBSQWGjSNH1G6cA7bLhUjYNMs6u8X8LGQkvPKfcoc9EjUJcLBhhbN45MroBzE8qywc6mo1vHV8j4SwNi6zx2oLUaEMVJqo",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "Seed To Master Key",
|
||||||
|
"args": ["tprv"]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Seed To Master Private Key (Ltpv)",
|
||||||
|
input: "c766f48d3729a16249b5d0171c678d458d31454b2bb7791b61169b5541a130719714ebd41f22a2515246d013e9a4e978aee48dd5140b73a540108d58008c4aa9",
|
||||||
|
expectedOutput: "Ltpv71G8qDifUiNesmbVBqTrR8sm9Eeoy4z2ZAeoSw4seCDwVcniGb6RehUSn4fyCXxa936aSGpwyxFhWbBS3hex7YEUVNgFMhKvv6CxBzBoSoz",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "Seed To Master Key",
|
||||||
|
"args": ["Ltpv"]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
]);
|
56
tests/operations/tests/SeedphraseToSeed.mjs
Normal file
56
tests/operations/tests/SeedphraseToSeed.mjs
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
/**
|
||||||
|
* Seedphrase to seed tests.
|
||||||
|
*
|
||||||
|
* @author dgoldenberg [virtualcurrency@mitre.org]
|
||||||
|
* @copyright MITRE 2023
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import TestRegister from "../../lib/TestRegister.mjs";
|
||||||
|
|
||||||
|
TestRegister.addTests([
|
||||||
|
{
|
||||||
|
name: "Seedphrase to Seed - BIP39 - 12",
|
||||||
|
input: "regret snack luxury tornado orient end bind video caution syrup minimum tree",
|
||||||
|
expectedOutput: "f2c6a948086e43745837e3aafb5fad31086da2bc83d76da4d029a38037f4e032eb75e30560921e75fafb791c53888d0eed7006ce29a9552d9888c9e7be58b461",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "Seedphrase To Seed",
|
||||||
|
"args": ["bip39", { "option": "UTF8", "string": "" }]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Seedphrase to Seed - BIP39 - 12 - Passphrase",
|
||||||
|
input: "regret snack luxury tornado orient end bind video caution syrup minimum tree",
|
||||||
|
expectedOutput: "60fa03fa8de256a794c04828051578b76ac7c3e130a034b27f47017eae8d52a37fe2a0f8e71e364ec81af9109ea100b4a16fef730628c9be4c4cc8d7360599a4",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "Seedphrase To Seed",
|
||||||
|
"args": ["bip39", { "option": "UTF8", "string": "password1234" }]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Seedphrase to Seed - Electrum - 12",
|
||||||
|
input: "hover engine either unknown hospital pole idea settle advice parent bundle solid",
|
||||||
|
expectedOutput: "0e66066dcd50ce43cd64dd42adae6f6fe2d121622677255cadcbf9695c73eb5a1dc44e763793f858c3421089c0e623c55309262527665f1bbb0d26bac080108b",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "Seedphrase To Seed",
|
||||||
|
"args": ["electrum2", { "option": "UTF8", "string": "" }]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Seedphrase to Seed - Electrum - 12 - Passphrase",
|
||||||
|
input: "hover engine either unknown hospital pole idea settle advice parent bundle solid",
|
||||||
|
expectedOutput: "90a4fff5335f9023dc75c7872c1207e90cadc7b4d8dc3b7c217d4d3be9428a7987154ab21c5c9a4abe6d038fbf68bb0400eaa461f41b9838f346bb79b8852f98",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "Seedphrase To Seed",
|
||||||
|
"args": ["electrum2", { "option": "UTF8", "string": "password1234" }]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
]);
|
144
tests/operations/tests/TypeCryptoArtifacts.mjs
Normal file
144
tests/operations/tests/TypeCryptoArtifacts.mjs
Normal file
|
@ -0,0 +1,144 @@
|
||||||
|
/**
|
||||||
|
* Typing cryptocurrency artifact tests.
|
||||||
|
*
|
||||||
|
* @author dgoldenberg [virtualcurrency@mitre.org]
|
||||||
|
* @copyright MITRE 2023
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import TestRegister from "../../lib/TestRegister.mjs";
|
||||||
|
|
||||||
|
TestRegister.addTests([
|
||||||
|
{
|
||||||
|
name: "Type Cryptocurrency Artifacts - P2SH",
|
||||||
|
input: "37TKx6FKj3P7fAeVoVwKsy39DzFhyHnvnr",
|
||||||
|
expectedOutput: "Input 37TKx6FKj3P7fAeVoVwKsy39DzFhyHnvnr is possibly a P2SH, or P2SH-P2WPKH address.\n",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "Type Cryptocurrency Artifact",
|
||||||
|
"args": []
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Type Cryptocurrency Artifacts - Compressed WIF",
|
||||||
|
input: "KwaCxiHcCGgD2b2MopWhoX218C1e5KZ8uu5G5ijGSCZDReuuxZcB",
|
||||||
|
expectedOutput: "Input KwaCxiHcCGgD2b2MopWhoX218C1e5KZ8uu5G5ijGSCZDReuuxZcB is possibly a compressed WIF (Wallet-Input-Format) Key.\n",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "Type Cryptocurrency Artifact",
|
||||||
|
"args": []
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Type Cryptocurrency Artifacts - Compressed WIF",
|
||||||
|
input: "L3vjDdHwHzw2QnPkMRnsnwtfd3zpWHcmxq5LVBkBnGy7mU5iVJJg",
|
||||||
|
expectedOutput: "Input L3vjDdHwHzw2QnPkMRnsnwtfd3zpWHcmxq5LVBkBnGy7mU5iVJJg is possibly a compressed WIF (Wallet-Input-Format) Key.\n",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "Type Cryptocurrency Artifact",
|
||||||
|
"args": []
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Type Cryptocurrency Artifacts - P2PKH",
|
||||||
|
input: "15bhKCjC8C3jKgTCexe15vX62ahhsujoLx",
|
||||||
|
expectedOutput: "Input 15bhKCjC8C3jKgTCexe15vX62ahhsujoLx is possibly a Bitcoin P2PKH address.\n",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "Type Cryptocurrency Artifact",
|
||||||
|
"args": []
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Type Cryptocurrency Artifacts - Compressed Public",
|
||||||
|
input: "02fc2dcc9f0b88922e32eb3ced89eecaea7eb8d9c0adf05b8b6c8cc0e4504aa91d",
|
||||||
|
expectedOutput: "Input 02fc2dcc9f0b88922e32eb3ced89eecaea7eb8d9c0adf05b8b6c8cc0e4504aa91d is possibly a compressed public key in raw bytes.\n",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "Type Cryptocurrency Artifact",
|
||||||
|
"args": []
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Type Cryptocurrency Artifacts - Compressed Public",
|
||||||
|
input: "03ebbd632f1f1b885ffddde739e0a285dd337f3d80759a119fd6699d740a8cefc9",
|
||||||
|
expectedOutput: "Input 03ebbd632f1f1b885ffddde739e0a285dd337f3d80759a119fd6699d740a8cefc9 is possibly a compressed public key in raw bytes.\n",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "Type Cryptocurrency Artifact",
|
||||||
|
"args": []
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Type Cryptocurrency Artifacts - Uncompressed Public",
|
||||||
|
input: "04BED04F74E8CB11C0A0FFC214429F7803174100660385F739A246F5D42231D7F44357E672B06EC1891DFA8BBC2129DC54384EA20B360C63EFA4E6CC6E4260D306",
|
||||||
|
expectedOutput: "Input 04BED04F74E8CB11C0A0FFC214429F7803174100660385F739A246F5D42231D7F44357E672B06EC1891DFA8BBC2129DC54384EA20B360C63EFA4E6CC6E4260D306 is possibly an uncompressed public key in raw bytes.\n",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "Type Cryptocurrency Artifact",
|
||||||
|
"args": []
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Type Cryptocurrency Artifacts - Uncompressed WIF",
|
||||||
|
input: "5KbFn3aqNgxotDZA4S28xaDcm7GhLt9K3LydqUtKfJbkAjarLnt",
|
||||||
|
expectedOutput: "Input 5KbFn3aqNgxotDZA4S28xaDcm7GhLt9K3LydqUtKfJbkAjarLnt is possibly an uncompressed WIF (Wallet-Input-Format) Key.\n",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "Type Cryptocurrency Artifact",
|
||||||
|
"args": []
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Type Cryptocurrency Artifacts - Segwit",
|
||||||
|
input: "bc1qxqy2d8mfl6gvk3fp5k9y3umv7h3fcgxpfs7w02",
|
||||||
|
expectedOutput: "Input bc1qxqy2d8mfl6gvk3fp5k9y3umv7h3fcgxpfs7w02 is possibly a Segwit address (P2WPKH/P2WSH).\n",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "Type Cryptocurrency Artifact",
|
||||||
|
"args": []
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Type Cryptocurrency Artifacts - Taproot",
|
||||||
|
input: "bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kt5nd6y",
|
||||||
|
expectedOutput: "Input bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kt5nd6y is possibly a taproot.\n",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "Type Cryptocurrency Artifact",
|
||||||
|
"args": []
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Type Cryptocurrency Artifacts - Private",
|
||||||
|
input: "E9CDB0734EC8CD727F58B5FD16E4277AB87FA5E74ED288779E908F4EF433BD2E",
|
||||||
|
expectedOutput: "Input E9CDB0734EC8CD727F58B5FD16E4277AB87FA5E74ED288779E908F4EF433BD2E is possibly a private key in raw bytes.\n",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "Type Cryptocurrency Artifact",
|
||||||
|
"args": []
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Type Cryptocurrency Artifacts - BIP38",
|
||||||
|
input: "6PYLLdPvL6tu2te7d4EaPpjZuzmuv2KWq2EkxBukPyknrf36gQW6a6qhXx",
|
||||||
|
expectedOutput: "Input 6PYLLdPvL6tu2te7d4EaPpjZuzmuv2KWq2EkxBukPyknrf36gQW6a6qhXx is possibly a BIP38 encrypted key.\n",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "Type Cryptocurrency Artifact",
|
||||||
|
"args": []
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
]);
|
45
tests/operations/tests/WIFToPrivateKey.mjs
Normal file
45
tests/operations/tests/WIFToPrivateKey.mjs
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
/**
|
||||||
|
* Wallet Import Format to Private key tests.
|
||||||
|
*
|
||||||
|
* @author dgoldenberg [virtualcurrency@mitre.org]
|
||||||
|
* @copyright MITRE 2023
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import TestRegister from "../../lib/TestRegister.mjs";
|
||||||
|
|
||||||
|
TestRegister.addTests([
|
||||||
|
{
|
||||||
|
name: "WIF to Private Key - Compressed",
|
||||||
|
input: "L3uC2epVmApXLbnCFSiS3hK4UkVLMnyrCGpPRzbxvRB3tKWRJPUF",
|
||||||
|
expectedOutput: "c7559d1ffd85dc07e8e86413f99fdb42201257f97765c2dee8a632451b680a78",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "From WIF Format",
|
||||||
|
"args": []
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "WIF to Private Key - Uncompressed 2",
|
||||||
|
input: "5Jqv2HL2tEHEeg5hGdSTKkVaAdFtXfi1jKXkJLVAr6rU3DdASSy",
|
||||||
|
expectedOutput: "8764b291f07ca7dd0cf939c65a4127f95d16b9412e90829d2b2a5085ad5ffb5b",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "From WIF Format",
|
||||||
|
"args": []
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "WIF to Private Key - Unompressed",
|
||||||
|
input: "5KL5K3gJqybnPZoVNrKuM6WU52F8755GgFusrARgriGsVrkJ9Hm",
|
||||||
|
expectedOutput: "c7559d1ffd85dc07e8e86413f99fdb42201257f97765c2dee8a632451b680a78",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "From WIF Format",
|
||||||
|
"args": []
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
]);
|
Loading…
Add table
Add a link
Reference in a new issue