diff --git a/.gitignore b/.gitignore index 45aee9aa..5c9b44a4 100755 --- a/.gitignore +++ b/.gitignore @@ -15,4 +15,5 @@ tests/operations/index.mjs **/*.DS_Store tests/browser/output/* .node-version +build.sh chrome diff --git a/Dockerfile b/Dockerfile index e7076f42..4e303765 100644 --- a/Dockerfile +++ b/Dockerfile @@ -29,6 +29,4 @@ RUN npm run build ######################################### FROM nginxinc/nginx-unprivileged:stable-alpine@sha256:44e36330f74d4f3a1d4e222acca9e23b401fb87811a7597024502bb759c4dd49 AS cyberchef -LABEL maintainer="GCHQ " - COPY --from=builder /app/build/prod /usr/share/nginx/html/ diff --git a/LICENSE b/LICENSE index 7a4a3ea2..f49a4e16 100755 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,3 @@ - Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ diff --git a/package-lock.json b/package-lock.json index eabf953c..da2990dc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "dependencies": { "@alexaltea/capstone-js": "^3.0.5", "@astronautlabs/amf": "^0.0.6", + "@bitcoinerlab/secp256k1": "^1.2.0", "@blu3r4y/lzma": "^2.3.3", "@noble/hashes": "2.2.0", "@wavesenterprise/crypto-gost-js": "^2.1.0-RC1", @@ -22,6 +23,7 @@ "avsc": "^5.7.9", "bcryptjs": "^3.0.3", "bignumber.js": "^11.1.5", + "bip32": "^5.0.1", "blakejs": "^1.2.1", "bootstrap": "4.6.2", "bootstrap-colorpicker": "^3.4.0", @@ -1841,6 +1843,15 @@ "dev": true, "license": "Apache-2.0" }, + "node_modules/@bitcoinerlab/secp256k1": { + "version": "1.2.0", + "resolved": "https://artifacts.mitre.org:443/artifactory/api/npm/node-npm/@bitcoinerlab/secp256k1/-/secp256k1-1.2.0.tgz", + "integrity": "sha512-jeujZSzb3JOZfmJYI0ph1PVpCRV5oaexCgy+RvCXV8XlY+XFB/2n3WOcvBsKLsOw78KYgnQrQWb2HrKE4be88Q==", + "license": "MIT", + "dependencies": { + "@noble/curves": "^1.7.0" + } + }, "node_modules/@blu3r4y/lzma": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/@blu3r4y/lzma/-/lzma-2.3.3.tgz", @@ -4264,6 +4275,33 @@ "archiver": "^5.3.1" } }, + "node_modules/@noble/curves": { + "version": "1.9.7", + "resolved": "https://artifacts.mitre.org:443/artifactory/api/npm/node-npm/@noble/curves/-/curves-1.9.7.tgz", + "integrity": "sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.8.0" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/curves/node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://artifacts.mitre.org:443/artifactory/api/npm/node-npm/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@noble/hashes": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.2.0.tgz", @@ -4599,6 +4637,15 @@ "node": "^20.19.0 || ^22.12.0 || >=23" } }, + "node_modules/@scure/base": { + "version": "1.2.6", + "resolved": "https://artifacts.mitre.org:443/artifactory/api/npm/node-npm/@scure/base/-/base-1.2.6.tgz", + "integrity": "sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg==", + "license": "MIT", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@testim/chrome-version": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/@testim/chrome-version/-/chrome-version-1.1.4.tgz", @@ -5848,6 +5895,12 @@ "dev": true, "license": "MIT" }, + "node_modules/base-x": { + "version": "5.0.1", + "resolved": "https://artifacts.mitre.org:443/artifactory/api/npm/node-npm/base-x/-/base-x-5.0.1.tgz", + "integrity": "sha512-M7uio8Zt++eg3jPj+rHMfCC+IuygQHHCOU+IYsVtik6FWjuYpVt/+MRKcgsAMHh8mMFAwnB+Bs+mTrFiXjMzKg==", + "license": "MIT" + }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -5963,6 +6016,34 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/bip32": { + "version": "5.0.1", + "resolved": "https://artifacts.mitre.org:443/artifactory/api/npm/node-npm/bip32/-/bip32-5.0.1.tgz", + "integrity": "sha512-PWlHIAgYCfVhwqNpZyeakHXuLAGyN6rEQZnhxHxKI3BoFJRVWLl26455fhRlHsmbYcV986HqtPnt33Edu5sTCw==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "^1.2.0", + "@scure/base": "^1.1.1", + "uint8array-tools": "^0.0.8", + "valibot": "^1.2.0", + "wif": "^5.0.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/bip32/node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://artifacts.mitre.org:443/artifactory/api/npm/node-npm/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/bl": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", @@ -6443,6 +6524,37 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/bs58": { + "version": "6.0.0", + "resolved": "https://artifacts.mitre.org:443/artifactory/api/npm/node-npm/bs58/-/bs58-6.0.0.tgz", + "integrity": "sha512-PD0wEnEYg6ijszw/u8s+iI3H17cTymlrwkKhDhPZq+Sokl3AU4htyBFTjAeNAlCCmg0f53g6ih3jATyCKftTfw==", + "license": "MIT", + "dependencies": { + "base-x": "^5.0.0" + } + }, + "node_modules/bs58check": { + "version": "4.0.0", + "resolved": "https://artifacts.mitre.org:443/artifactory/api/npm/node-npm/bs58check/-/bs58check-4.0.0.tgz", + "integrity": "sha512-FsGDOnFg9aVI9erdriULkd/JjEWONV/lQE5aYziB5PoBsXRind56lh8doIZIc9X4HoxT5x4bLjMWN1/NB8Zp5g==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "^1.2.0", + "bs58": "^6.0.0" + } + }, + "node_modules/bs58check/node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://artifacts.mitre.org:443/artifactory/api/npm/node-npm/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/bson": { "version": "7.3.1", "resolved": "https://registry.npmjs.org/bson/-/bson-7.3.1.tgz", @@ -18002,6 +18114,15 @@ "integrity": "sha512-w+VZSp8hSZ/xWZfZNMppWNF6iqY+dcMYtG5CpwRDgxi94HIE6ematSdkzHGzVC4SDEaTsG65zrajN+oKoWG6ew==", "license": "MIT" }, + "node_modules/uint8array-tools": { + "version": "0.0.8", + "resolved": "https://artifacts.mitre.org:443/artifactory/api/npm/node-npm/uint8array-tools/-/uint8array-tools-0.0.8.tgz", + "integrity": "sha512-xS6+s8e0Xbx++5/0L+yyexukU7pz//Yg6IHg3BKhXotg1JcYtgxVcUctQ0HxLByiJzpAkNFawz1Nz5Xadzo82g==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/unc-path-regex": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", @@ -18269,6 +18390,20 @@ "node": ">= 0.10" } }, + "node_modules/valibot": { + "version": "1.4.2", + "resolved": "https://artifacts.mitre.org:443/artifactory/api/npm/node-npm/valibot/-/valibot-1.4.2.tgz", + "integrity": "sha512-gjdCvJ6d3RyHAneqxMYMW9QMCwYMb3jpOO0IyHZV1bnRHFBHrX3VkIILt5XYR0WhwHiH7Mty8ovuPZ/O3gamrg==", + "license": "MIT", + "peerDependencies": { + "typescript": ">=5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -18803,6 +18938,15 @@ "node": ">=8" } }, + "node_modules/wif": { + "version": "5.0.0", + "resolved": "https://artifacts.mitre.org:443/artifactory/api/npm/node-npm/wif/-/wif-5.0.0.tgz", + "integrity": "sha512-iFzrC/9ne740qFbNjTZ2FciSRJlHIXoxqk/Y5EnE08QOXu1WjJyCCswwDTYbohAOEnlCtLaAAQBhyaLRFh2hMA==", + "license": "MIT", + "dependencies": { + "bs58check": "^4.0.0" + } + }, "node_modules/wildcard": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", diff --git a/package.json b/package.json index 8cd81c9e..db041c56 100644 --- a/package.json +++ b/package.json @@ -97,6 +97,7 @@ "dependencies": { "@alexaltea/capstone-js": "^3.0.5", "@astronautlabs/amf": "^0.0.6", + "@bitcoinerlab/secp256k1": "^1.2.0", "@blu3r4y/lzma": "^2.3.3", "@noble/hashes": "2.2.0", "@wavesenterprise/crypto-gost-js": "^2.1.0-RC1", @@ -107,6 +108,7 @@ "avsc": "^5.7.9", "bcryptjs": "^3.0.3", "bignumber.js": "^11.1.5", + "bip32": "^5.0.1", "blakejs": "^1.2.1", "bootstrap": "4.6.2", "bootstrap-colorpicker": "^3.4.0", diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json index 6ad3adb7..e49223e5 100644 --- a/src/core/config/Categories.json +++ b/src/core/config/Categories.json @@ -387,6 +387,32 @@ "Sleep" ] }, + { + "name": "Cryptocurrency", + "ops": [ + "Extract Double SHA Artifacts", + "Extract Segwit Addresses", + "Extract Seedphrases", + "Deserialize Extended Key", + "Public Key To Bitcoin-Like 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", + "Public Key To TRX Style Address", + "ETH / TRX Conversion", + "Key To Extended Key", + "Private Extended Key To Public", + "From Base58Check", + "To Base58Check" + ] + }, { "name": "Extractors", "ops": [ @@ -619,4 +645,4 @@ "Comment" ] } -] \ No newline at end of file +] diff --git a/src/core/lib/Bech32.mjs b/src/core/lib/Bech32.mjs index 6b87a142..cce4737a 100644 --- a/src/core/lib/Bech32.mjs +++ b/src/core/lib/Bech32.mjs @@ -369,3 +369,31 @@ export function decode(str, encoding = "Auto-detect") { witnessVersion: witnessVersion }; } + + +/** + * Exported function to test if a string is a valid Bech32 encoded item. + * @param {*} test_string + * @returns + */ +export function segwitChecksum(testString) { + try { + decode(testString); + return true; + } catch (error) { + return false; + } + +} + +/** + * Exported function to create an address given the specific data. + * @param {*} hrp + * @param {*} version + * @param {*} program + */ +export function encodeProgramToSegwit(hrp, version, program) { + const encoding = version > 0 ? "Bech32m": "Bech32"; + return encode(hrp, program, encoding, true); + +} diff --git a/src/core/lib/Bitcoin.mjs b/src/core/lib/Bitcoin.mjs new file mode 100644 index 00000000..1914ee4b --- /dev/null +++ b/src/core/lib/Bitcoin.mjs @@ -0,0 +1,608 @@ +/** + * 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"; +import BigNumber from "bignumber.js"; + + +/** + * 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 + */ +export function isHex(input) { + const re = /^[0-9A-Fa-f]{2,}$/g; + return re.test(input) && input.length %2 === 0; +} + +/** + * 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) { + if (!validateLengths(input, [32, 64])) { + return "Invalid length. We want either 32 or 64 but we got: " + input.length; + } + if (input.length === 64 && !isHex(input)) { + return "We have a string of length 64, but not valid hex. Cannot be interpreted as a private key."; + } + if (input.length === 32 && !isValidBytes(input)) { + 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) { + if (!validateLengths(input, [33, 65, 66, 130])) { + return "Invalid length. We want either 33, 65 (if bytes) or 66, 130 (if hex) but we got: " + input.length; + } + if (isHex(input)) { + if (!validateLengths(input, [66, 130])) { + return "We have a hex string, but its length is wrong. We want 66, 130 but we got: " + input.length; + } + if (input.length === 66 && (input.slice(0, 2) !== "02" && input.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: " + input.slice(0, 2); + } + if (input.length === 130 && input.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: " + input.slice(0, 2); + } + return ""; + } + if (isValidBytes(input)) { + if (!validateLengths(input, [33, 65])) { + return "We have a byte string, but its length is wrong. We want 33 or 65 but we got: " + input.length; + } + if (input.length === 33 && toHex(input[0]) !== "02" && toHex(input[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(input[0]) ; + } + if (input.length === 65 && toHex(input[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(input[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(); +} + +// Tag Hash defined in https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki +/** + * Tag Hash defined in BIP340 https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki + * Hash is defined as SHA256(SHA256(tag) || SHA256(tag) || x) + * @param {*} input + * @returns + */ +export function tweakHash(input) { + const sha256Hasher = CryptoApi.getHasher("sha256"); + sha256Hasher.update("TapTweak"); + const tagHash = sha256Hasher.finalize(); + const sha256Hasher2 = CryptoApi.getHasher("sha256"); + sha256Hasher2.update(tagHash); + sha256Hasher2.update(tagHash); + sha256Hasher2.update(input); + const result = sha256Hasher2.finalize(); + return result; +} + +/** + * Given x, returns the point P(x) where the y-coordinate is even. Fails if x is greater than p-1 or if the point does not exist. + * Since this is mostly going to be used for analysis and not key derivation, failure should be rare but we check anyway. + * @param {*} input + * @returns + */ +export function liftX(input) { + const three = BigNumber(3); + const seven = BigNumber(7); + const one = BigNumber(1); + const four = BigNumber(4); + const two = BigNumber(2); + + const pHex ="FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F"; + const p = BigNumber(pHex, 16); + let x; + try { + x = BigNumber(makeSureIsHex(input), 16); + } catch (TypeError) { + return -1; + } + if (x.comparedTo(p) === 1) { + return -1; + } else { + const temp = x.pow(three, p).plus(seven); + const ySQ = temp.mod(p); + const tempExp = (p.plus(one)).idiv(four); + const y = ySQ.pow(tempExp, p); + if (y.pow(two, p).comparedTo(ySQ) !== 0) { + return -1; + } else { + return input; + } + + } +} + +// ################################################ 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" +}; + +// Lookup matching private extended key versions to public +const privateToPublicExtendedKeys = { + "xprv": "xpub", + "yprv": "ypub", + "zprv": "zpub", + "Zprv": "Zpub", + "Yprv": "Ypub", + "Ltpv": "Ltub", + "Mtpv": "Mtub", + "ttpv": "ttub", + "tprv": "tpub", + "uprv": "upub", + "vprv": "vprv", + "Uprv": "Upub", + "Vprv": "Vpub" +}; + +/** + * For a given private extended key version string, returns the appropriate public version string. + * @param {*} input + * @returns + */ +export function privateVersionToPublicVersion(input) { + return privateToPublicExtendedKeys[input]; +} + +/** + * 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]; +} + +/** + * Returns valid versions as an array. + */ +export function getVersions() { + return Object.keys(versionBytes); +} + +/** + * 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 ######################################################### + diff --git a/src/core/lib/Extract.mjs b/src/core/lib/Extract.mjs index 8828671e..059f912d 100644 --- a/src/core/lib/Extract.mjs +++ b/src/core/lib/Extract.mjs @@ -46,8 +46,38 @@ export function search(input, searchRegex, removeRegex=null, sortBy=null, unique } /** - * Email regular expression + * 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; +} + export const EMAIL_REGEX = /(?:[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9](?:[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9-]*[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9])?\.)+[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9](?:[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9-]*[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\])/ig; diff --git a/src/core/lib/Seedphrase.mjs b/src/core/lib/Seedphrase.mjs new file mode 100644 index 00000000..c2e69e00 --- /dev/null +++ b/src/core/lib/Seedphrase.mjs @@ -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 and Daniel Cousens 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")); +} diff --git a/src/core/lib/SeedphraseWordLists.mjs b/src/core/lib/SeedphraseWordLists.mjs new file mode 100644 index 00000000..60380b67 --- /dev/null +++ b/src/core/lib/SeedphraseWordLists.mjs @@ -0,0 +1,4109 @@ +/** + * Wordlists for different seedphrase standards. + * + * @author dgoldenberg [virtualcurrency@mitre.org] + * @copyright MITRE 2023 + * @license Apache-2.0 + */ + +export const bip39English = [ + "abandon", + "ability", + "able", + "about", + "above", + "absent", + "absorb", + "abstract", + "absurd", + "abuse", + "access", + "accident", + "account", + "accuse", + "achieve", + "acid", + "acoustic", + "acquire", + "across", + "act", + "action", + "actor", + "actress", + "actual", + "adapt", + "add", + "addict", + "address", + "adjust", + "admit", + "adult", + "advance", + "advice", + "aerobic", + "affair", + "afford", + "afraid", + "again", + "age", + "agent", + "agree", + "ahead", + "aim", + "air", + "airport", + "aisle", + "alarm", + "album", + "alcohol", + "alert", + "alien", + "all", + "alley", + "allow", + "almost", + "alone", + "alpha", + "already", + "also", + "alter", + "always", + "amateur", + "amazing", + "among", + "amount", + "amused", + "analyst", + "anchor", + "ancient", + "anger", + "angle", + "angry", + "animal", + "ankle", + "announce", + "annual", + "another", + "answer", + "antenna", + "antique", + "anxiety", + "any", + "apart", + "apology", + "appear", + "apple", + "approve", + "april", + "arch", + "arctic", + "area", + "arena", + "argue", + "arm", + "armed", + "armor", + "army", + "around", + "arrange", + "arrest", + "arrive", + "arrow", + "art", + "artefact", + "artist", + "artwork", + "ask", + "aspect", + "assault", + "asset", + "assist", + "assume", + "asthma", + "athlete", + "atom", + "attack", + "attend", + "attitude", + "attract", + "auction", + "audit", + "august", + "aunt", + "author", + "auto", + "autumn", + "average", + "avocado", + "avoid", + "awake", + "aware", + "away", + "awesome", + "awful", + "awkward", + "axis", + "baby", + "bachelor", + "bacon", + "badge", + "bag", + "balance", + "balcony", + "ball", + "bamboo", + "banana", + "banner", + "bar", + "barely", + "bargain", + "barrel", + "base", + "basic", + "basket", + "battle", + "beach", + "bean", + "beauty", + "because", + "become", + "beef", + "before", + "begin", + "behave", + "behind", + "believe", + "below", + "belt", + "bench", + "benefit", + "best", + "betray", + "better", + "between", + "beyond", + "bicycle", + "bid", + "bike", + "bind", + "biology", + "bird", + "birth", + "bitter", + "black", + "blade", + "blame", + "blanket", + "blast", + "bleak", + "bless", + "blind", + "blood", + "blossom", + "blouse", + "blue", + "blur", + "blush", + "board", + "boat", + "body", + "boil", + "bomb", + "bone", + "bonus", + "book", + "boost", + "border", + "boring", + "borrow", + "boss", + "bottom", + "bounce", + "box", + "boy", + "bracket", + "brain", + "brand", + "brass", + "brave", + "bread", + "breeze", + "brick", + "bridge", + "brief", + "bright", + "bring", + "brisk", + "broccoli", + "broken", + "bronze", + "broom", + "brother", + "brown", + "brush", + "bubble", + "buddy", + "budget", + "buffalo", + "build", + "bulb", + "bulk", + "bullet", + "bundle", + "bunker", + "burden", + "burger", + "burst", + "bus", + "business", + "busy", + "butter", + "buyer", + "buzz", + "cabbage", + "cabin", + "cable", + "cactus", + "cage", + "cake", + "call", + "calm", + "camera", + "camp", + "can", + "canal", + "cancel", + "candy", + "cannon", + "canoe", + "canvas", + "canyon", + "capable", + "capital", + "captain", + "car", + "carbon", + "card", + "cargo", + "carpet", + "carry", + "cart", + "case", + "cash", + "casino", + "castle", + "casual", + "cat", + "catalog", + "catch", + "category", + "cattle", + "caught", + "cause", + "caution", + "cave", + "ceiling", + "celery", + "cement", + "census", + "century", + "cereal", + "certain", + "chair", + "chalk", + "champion", + "change", + "chaos", + "chapter", + "charge", + "chase", + "chat", + "cheap", + "check", + "cheese", + "chef", + "cherry", + "chest", + "chicken", + "chief", + "child", + "chimney", + "choice", + "choose", + "chronic", + "chuckle", + "chunk", + "churn", + "cigar", + "cinnamon", + "circle", + "citizen", + "city", + "civil", + "claim", + "clap", + "clarify", + "claw", + "clay", + "clean", + "clerk", + "clever", + "click", + "client", + "cliff", + "climb", + "clinic", + "clip", + "clock", + "clog", + "close", + "cloth", + "cloud", + "clown", + "club", + "clump", + "cluster", + "clutch", + "coach", + "coast", + "coconut", + "code", + "coffee", + "coil", + "coin", + "collect", + "color", + "column", + "combine", + "come", + "comfort", + "comic", + "common", + "company", + "concert", + "conduct", + "confirm", + "congress", + "connect", + "consider", + "control", + "convince", + "cook", + "cool", + "copper", + "copy", + "coral", + "core", + "corn", + "correct", + "cost", + "cotton", + "couch", + "country", + "couple", + "course", + "cousin", + "cover", + "coyote", + "crack", + "cradle", + "craft", + "cram", + "crane", + "crash", + "crater", + "crawl", + "crazy", + "cream", + "credit", + "creek", + "crew", + "cricket", + "crime", + "crisp", + "critic", + "crop", + "cross", + "crouch", + "crowd", + "crucial", + "cruel", + "cruise", + "crumble", + "crunch", + "crush", + "cry", + "crystal", + "cube", + "culture", + "cup", + "cupboard", + "curious", + "current", + "curtain", + "curve", + "cushion", + "custom", + "cute", + "cycle", + "dad", + "damage", + "damp", + "dance", + "danger", + "daring", + "dash", + "daughter", + "dawn", + "day", + "deal", + "debate", + "debris", + "decade", + "december", + "decide", + "decline", + "decorate", + "decrease", + "deer", + "defense", + "define", + "defy", + "degree", + "delay", + "deliver", + "demand", + "demise", + "denial", + "dentist", + "deny", + "depart", + "depend", + "deposit", + "depth", + "deputy", + "derive", + "describe", + "desert", + "design", + "desk", + "despair", + "destroy", + "detail", + "detect", + "develop", + "device", + "devote", + "diagram", + "dial", + "diamond", + "diary", + "dice", + "diesel", + "diet", + "differ", + "digital", + "dignity", + "dilemma", + "dinner", + "dinosaur", + "direct", + "dirt", + "disagree", + "discover", + "disease", + "dish", + "dismiss", + "disorder", + "display", + "distance", + "divert", + "divide", + "divorce", + "dizzy", + "doctor", + "document", + "dog", + "doll", + "dolphin", + "domain", + "donate", + "donkey", + "donor", + "door", + "dose", + "double", + "dove", + "draft", + "dragon", + "drama", + "drastic", + "draw", + "dream", + "dress", + "drift", + "drill", + "drink", + "drip", + "drive", + "drop", + "drum", + "dry", + "duck", + "dumb", + "dune", + "during", + "dust", + "dutch", + "duty", + "dwarf", + "dynamic", + "eager", + "eagle", + "early", + "earn", + "earth", + "easily", + "east", + "easy", + "echo", + "ecology", + "economy", + "edge", + "edit", + "educate", + "effort", + "egg", + "eight", + "either", + "elbow", + "elder", + "electric", + "elegant", + "element", + "elephant", + "elevator", + "elite", + "else", + "embark", + "embody", + "embrace", + "emerge", + "emotion", + "employ", + "empower", + "empty", + "enable", + "enact", + "end", + "endless", + "endorse", + "enemy", + "energy", + "enforce", + "engage", + "engine", + "enhance", + "enjoy", + "enlist", + "enough", + "enrich", + "enroll", + "ensure", + "enter", + "entire", + "entry", + "envelope", + "episode", + "equal", + "equip", + "era", + "erase", + "erode", + "erosion", + "error", + "erupt", + "escape", + "essay", + "essence", + "estate", + "eternal", + "ethics", + "evidence", + "evil", + "evoke", + "evolve", + "exact", + "example", + "excess", + "exchange", + "excite", + "exclude", + "excuse", + "execute", + "exercise", + "exhaust", + "exhibit", + "exile", + "exist", + "exit", + "exotic", + "expand", + "expect", + "expire", + "explain", + "expose", + "express", + "extend", + "extra", + "eye", + "eyebrow", + "fabric", + "face", + "faculty", + "fade", + "faint", + "faith", + "fall", + "false", + "fame", + "family", + "famous", + "fan", + "fancy", + "fantasy", + "farm", + "fashion", + "fat", + "fatal", + "father", + "fatigue", + "fault", + "favorite", + "feature", + "february", + "federal", + "fee", + "feed", + "feel", + "female", + "fence", + "festival", + "fetch", + "fever", + "few", + "fiber", + "fiction", + "field", + "figure", + "file", + "film", + "filter", + "final", + "find", + "fine", + "finger", + "finish", + "fire", + "firm", + "first", + "fiscal", + "fish", + "fit", + "fitness", + "fix", + "flag", + "flame", + "flash", + "flat", + "flavor", + "flee", + "flight", + "flip", + "float", + "flock", + "floor", + "flower", + "fluid", + "flush", + "fly", + "foam", + "focus", + "fog", + "foil", + "fold", + "follow", + "food", + "foot", + "force", + "forest", + "forget", + "fork", + "fortune", + "forum", + "forward", + "fossil", + "foster", + "found", + "fox", + "fragile", + "frame", + "frequent", + "fresh", + "friend", + "fringe", + "frog", + "front", + "frost", + "frown", + "frozen", + "fruit", + "fuel", + "fun", + "funny", + "furnace", + "fury", + "future", + "gadget", + "gain", + "galaxy", + "gallery", + "game", + "gap", + "garage", + "garbage", + "garden", + "garlic", + "garment", + "gas", + "gasp", + "gate", + "gather", + "gauge", + "gaze", + "general", + "genius", + "genre", + "gentle", + "genuine", + "gesture", + "ghost", + "giant", + "gift", + "giggle", + "ginger", + "giraffe", + "girl", + "give", + "glad", + "glance", + "glare", + "glass", + "glide", + "glimpse", + "globe", + "gloom", + "glory", + "glove", + "glow", + "glue", + "goat", + "goddess", + "gold", + "good", + "goose", + "gorilla", + "gospel", + "gossip", + "govern", + "gown", + "grab", + "grace", + "grain", + "grant", + "grape", + "grass", + "gravity", + "great", + "green", + "grid", + "grief", + "grit", + "grocery", + "group", + "grow", + "grunt", + "guard", + "guess", + "guide", + "guilt", + "guitar", + "gun", + "gym", + "habit", + "hair", + "half", + "hammer", + "hamster", + "hand", + "happy", + "harbor", + "hard", + "harsh", + "harvest", + "hat", + "have", + "hawk", + "hazard", + "head", + "health", + "heart", + "heavy", + "hedgehog", + "height", + "hello", + "helmet", + "help", + "hen", + "hero", + "hidden", + "high", + "hill", + "hint", + "hip", + "hire", + "history", + "hobby", + "hockey", + "hold", + "hole", + "holiday", + "hollow", + "home", + "honey", + "hood", + "hope", + "horn", + "horror", + "horse", + "hospital", + "host", + "hotel", + "hour", + "hover", + "hub", + "huge", + "human", + "humble", + "humor", + "hundred", + "hungry", + "hunt", + "hurdle", + "hurry", + "hurt", + "husband", + "hybrid", + "ice", + "icon", + "idea", + "identify", + "idle", + "ignore", + "ill", + "illegal", + "illness", + "image", + "imitate", + "immense", + "immune", + "impact", + "impose", + "improve", + "impulse", + "inch", + "include", + "income", + "increase", + "index", + "indicate", + "indoor", + "industry", + "infant", + "inflict", + "inform", + "inhale", + "inherit", + "initial", + "inject", + "injury", + "inmate", + "inner", + "innocent", + "input", + "inquiry", + "insane", + "insect", + "inside", + "inspire", + "install", + "intact", + "interest", + "into", + "invest", + "invite", + "involve", + "iron", + "island", + "isolate", + "issue", + "item", + "ivory", + "jacket", + "jaguar", + "jar", + "jazz", + "jealous", + "jeans", + "jelly", + "jewel", + "job", + "join", + "joke", + "journey", + "joy", + "judge", + "juice", + "jump", + "jungle", + "junior", + "junk", + "just", + "kangaroo", + "keen", + "keep", + "ketchup", + "key", + "kick", + "kid", + "kidney", + "kind", + "kingdom", + "kiss", + "kit", + "kitchen", + "kite", + "kitten", + "kiwi", + "knee", + "knife", + "knock", + "know", + "lab", + "label", + "labor", + "ladder", + "lady", + "lake", + "lamp", + "language", + "laptop", + "large", + "later", + "latin", + "laugh", + "laundry", + "lava", + "law", + "lawn", + "lawsuit", + "layer", + "lazy", + "leader", + "leaf", + "learn", + "leave", + "lecture", + "left", + "leg", + "legal", + "legend", + "leisure", + "lemon", + "lend", + "length", + "lens", + "leopard", + "lesson", + "letter", + "level", + "liar", + "liberty", + "library", + "license", + "life", + "lift", + "light", + "like", + "limb", + "limit", + "link", + "lion", + "liquid", + "list", + "little", + "live", + "lizard", + "load", + "loan", + "lobster", + "local", + "lock", + "logic", + "lonely", + "long", + "loop", + "lottery", + "loud", + "lounge", + "love", + "loyal", + "lucky", + "luggage", + "lumber", + "lunar", + "lunch", + "luxury", + "lyrics", + "machine", + "mad", + "magic", + "magnet", + "maid", + "mail", + "main", + "major", + "make", + "mammal", + "man", + "manage", + "mandate", + "mango", + "mansion", + "manual", + "maple", + "marble", + "march", + "margin", + "marine", + "market", + "marriage", + "mask", + "mass", + "master", + "match", + "material", + "math", + "matrix", + "matter", + "maximum", + "maze", + "meadow", + "mean", + "measure", + "meat", + "mechanic", + "medal", + "media", + "melody", + "melt", + "member", + "memory", + "mention", + "menu", + "mercy", + "merge", + "merit", + "merry", + "mesh", + "message", + "metal", + "method", + "middle", + "midnight", + "milk", + "million", + "mimic", + "mind", + "minimum", + "minor", + "minute", + "miracle", + "mirror", + "misery", + "miss", + "mistake", + "mix", + "mixed", + "mixture", + "mobile", + "model", + "modify", + "mom", + "moment", + "monitor", + "monkey", + "monster", + "month", + "moon", + "moral", + "more", + "morning", + "mosquito", + "mother", + "motion", + "motor", + "mountain", + "mouse", + "move", + "movie", + "much", + "muffin", + "mule", + "multiply", + "muscle", + "museum", + "mushroom", + "music", + "must", + "mutual", + "myself", + "mystery", + "myth", + "naive", + "name", + "napkin", + "narrow", + "nasty", + "nation", + "nature", + "near", + "neck", + "need", + "negative", + "neglect", + "neither", + "nephew", + "nerve", + "nest", + "net", + "network", + "neutral", + "never", + "news", + "next", + "nice", + "night", + "noble", + "noise", + "nominee", + "noodle", + "normal", + "north", + "nose", + "notable", + "note", + "nothing", + "notice", + "novel", + "now", + "nuclear", + "number", + "nurse", + "nut", + "oak", + "obey", + "object", + "oblige", + "obscure", + "observe", + "obtain", + "obvious", + "occur", + "ocean", + "october", + "odor", + "off", + "offer", + "office", + "often", + "oil", + "okay", + "old", + "olive", + "olympic", + "omit", + "once", + "one", + "onion", + "online", + "only", + "open", + "opera", + "opinion", + "oppose", + "option", + "orange", + "orbit", + "orchard", + "order", + "ordinary", + "organ", + "orient", + "original", + "orphan", + "ostrich", + "other", + "outdoor", + "outer", + "output", + "outside", + "oval", + "oven", + "over", + "own", + "owner", + "oxygen", + "oyster", + "ozone", + "pact", + "paddle", + "page", + "pair", + "palace", + "palm", + "panda", + "panel", + "panic", + "panther", + "paper", + "parade", + "parent", + "park", + "parrot", + "party", + "pass", + "patch", + "path", + "patient", + "patrol", + "pattern", + "pause", + "pave", + "payment", + "peace", + "peanut", + "pear", + "peasant", + "pelican", + "pen", + "penalty", + "pencil", + "people", + "pepper", + "perfect", + "permit", + "person", + "pet", + "phone", + "photo", + "phrase", + "physical", + "piano", + "picnic", + "picture", + "piece", + "pig", + "pigeon", + "pill", + "pilot", + "pink", + "pioneer", + "pipe", + "pistol", + "pitch", + "pizza", + "place", + "planet", + "plastic", + "plate", + "play", + "please", + "pledge", + "pluck", + "plug", + "plunge", + "poem", + "poet", + "point", + "polar", + "pole", + "police", + "pond", + "pony", + "pool", + "popular", + "portion", + "position", + "possible", + "post", + "potato", + "pottery", + "poverty", + "powder", + "power", + "practice", + "praise", + "predict", + "prefer", + "prepare", + "present", + "pretty", + "prevent", + "price", + "pride", + "primary", + "print", + "priority", + "prison", + "private", + "prize", + "problem", + "process", + "produce", + "profit", + "program", + "project", + "promote", + "proof", + "property", + "prosper", + "protect", + "proud", + "provide", + "public", + "pudding", + "pull", + "pulp", + "pulse", + "pumpkin", + "punch", + "pupil", + "puppy", + "purchase", + "purity", + "purpose", + "purse", + "push", + "put", + "puzzle", + "pyramid", + "quality", + "quantum", + "quarter", + "question", + "quick", + "quit", + "quiz", + "quote", + "rabbit", + "raccoon", + "race", + "rack", + "radar", + "radio", + "rail", + "rain", + "raise", + "rally", + "ramp", + "ranch", + "random", + "range", + "rapid", + "rare", + "rate", + "rather", + "raven", + "raw", + "razor", + "ready", + "real", + "reason", + "rebel", + "rebuild", + "recall", + "receive", + "recipe", + "record", + "recycle", + "reduce", + "reflect", + "reform", + "refuse", + "region", + "regret", + "regular", + "reject", + "relax", + "release", + "relief", + "rely", + "remain", + "remember", + "remind", + "remove", + "render", + "renew", + "rent", + "reopen", + "repair", + "repeat", + "replace", + "report", + "require", + "rescue", + "resemble", + "resist", + "resource", + "response", + "result", + "retire", + "retreat", + "return", + "reunion", + "reveal", + "review", + "reward", + "rhythm", + "rib", + "ribbon", + "rice", + "rich", + "ride", + "ridge", + "rifle", + "right", + "rigid", + "ring", + "riot", + "ripple", + "risk", + "ritual", + "rival", + "river", + "road", + "roast", + "robot", + "robust", + "rocket", + "romance", + "roof", + "rookie", + "room", + "rose", + "rotate", + "rough", + "round", + "route", + "royal", + "rubber", + "rude", + "rug", + "rule", + "run", + "runway", + "rural", + "sad", + "saddle", + "sadness", + "safe", + "sail", + "salad", + "salmon", + "salon", + "salt", + "salute", + "same", + "sample", + "sand", + "satisfy", + "satoshi", + "sauce", + "sausage", + "save", + "say", + "scale", + "scan", + "scare", + "scatter", + "scene", + "scheme", + "school", + "science", + "scissors", + "scorpion", + "scout", + "scrap", + "screen", + "script", + "scrub", + "sea", + "search", + "season", + "seat", + "second", + "secret", + "section", + "security", + "seed", + "seek", + "segment", + "select", + "sell", + "seminar", + "senior", + "sense", + "sentence", + "series", + "service", + "session", + "settle", + "setup", + "seven", + "shadow", + "shaft", + "shallow", + "share", + "shed", + "shell", + "sheriff", + "shield", + "shift", + "shine", + "ship", + "shiver", + "shock", + "shoe", + "shoot", + "shop", + "short", + "shoulder", + "shove", + "shrimp", + "shrug", + "shuffle", + "shy", + "sibling", + "sick", + "side", + "siege", + "sight", + "sign", + "silent", + "silk", + "silly", + "silver", + "similar", + "simple", + "since", + "sing", + "siren", + "sister", + "situate", + "six", + "size", + "skate", + "sketch", + "ski", + "skill", + "skin", + "skirt", + "skull", + "slab", + "slam", + "sleep", + "slender", + "slice", + "slide", + "slight", + "slim", + "slogan", + "slot", + "slow", + "slush", + "small", + "smart", + "smile", + "smoke", + "smooth", + "snack", + "snake", + "snap", + "sniff", + "snow", + "soap", + "soccer", + "social", + "sock", + "soda", + "soft", + "solar", + "soldier", + "solid", + "solution", + "solve", + "someone", + "song", + "soon", + "sorry", + "sort", + "soul", + "sound", + "soup", + "source", + "south", + "space", + "spare", + "spatial", + "spawn", + "speak", + "special", + "speed", + "spell", + "spend", + "sphere", + "spice", + "spider", + "spike", + "spin", + "spirit", + "split", + "spoil", + "sponsor", + "spoon", + "sport", + "spot", + "spray", + "spread", + "spring", + "spy", + "square", + "squeeze", + "squirrel", + "stable", + "stadium", + "staff", + "stage", + "stairs", + "stamp", + "stand", + "start", + "state", + "stay", + "steak", + "steel", + "stem", + "step", + "stereo", + "stick", + "still", + "sting", + "stock", + "stomach", + "stone", + "stool", + "story", + "stove", + "strategy", + "street", + "strike", + "strong", + "struggle", + "student", + "stuff", + "stumble", + "style", + "subject", + "submit", + "subway", + "success", + "such", + "sudden", + "suffer", + "sugar", + "suggest", + "suit", + "summer", + "sun", + "sunny", + "sunset", + "super", + "supply", + "supreme", + "sure", + "surface", + "surge", + "surprise", + "surround", + "survey", + "suspect", + "sustain", + "swallow", + "swamp", + "swap", + "swarm", + "swear", + "sweet", + "swift", + "swim", + "swing", + "switch", + "sword", + "symbol", + "symptom", + "syrup", + "system", + "table", + "tackle", + "tag", + "tail", + "talent", + "talk", + "tank", + "tape", + "target", + "task", + "taste", + "tattoo", + "taxi", + "teach", + "team", + "tell", + "ten", + "tenant", + "tennis", + "tent", + "term", + "test", + "text", + "thank", + "that", + "theme", + "then", + "theory", + "there", + "they", + "thing", + "this", + "thought", + "three", + "thrive", + "throw", + "thumb", + "thunder", + "ticket", + "tide", + "tiger", + "tilt", + "timber", + "time", + "tiny", + "tip", + "tired", + "tissue", + "title", + "toast", + "tobacco", + "today", + "toddler", + "toe", + "together", + "toilet", + "token", + "tomato", + "tomorrow", + "tone", + "tongue", + "tonight", + "tool", + "tooth", + "top", + "topic", + "topple", + "torch", + "tornado", + "tortoise", + "toss", + "total", + "tourist", + "toward", + "tower", + "town", + "toy", + "track", + "trade", + "traffic", + "tragic", + "train", + "transfer", + "trap", + "trash", + "travel", + "tray", + "treat", + "tree", + "trend", + "trial", + "tribe", + "trick", + "trigger", + "trim", + "trip", + "trophy", + "trouble", + "truck", + "true", + "truly", + "trumpet", + "trust", + "truth", + "try", + "tube", + "tuition", + "tumble", + "tuna", + "tunnel", + "turkey", + "turn", + "turtle", + "twelve", + "twenty", + "twice", + "twin", + "twist", + "two", + "type", + "typical", + "ugly", + "umbrella", + "unable", + "unaware", + "uncle", + "uncover", + "under", + "undo", + "unfair", + "unfold", + "unhappy", + "uniform", + "unique", + "unit", + "universe", + "unknown", + "unlock", + "until", + "unusual", + "unveil", + "update", + "upgrade", + "uphold", + "upon", + "upper", + "upset", + "urban", + "urge", + "usage", + "use", + "used", + "useful", + "useless", + "usual", + "utility", + "vacant", + "vacuum", + "vague", + "valid", + "valley", + "valve", + "van", + "vanish", + "vapor", + "various", + "vast", + "vault", + "vehicle", + "velvet", + "vendor", + "venture", + "venue", + "verb", + "verify", + "version", + "very", + "vessel", + "veteran", + "viable", + "vibrant", + "vicious", + "victory", + "video", + "view", + "village", + "vintage", + "violin", + "virtual", + "virus", + "visa", + "visit", + "visual", + "vital", + "vivid", + "vocal", + "voice", + "void", + "volcano", + "volume", + "vote", + "voyage", + "wage", + "wagon", + "wait", + "walk", + "wall", + "walnut", + "want", + "warfare", + "warm", + "warrior", + "wash", + "wasp", + "waste", + "water", + "wave", + "way", + "wealth", + "weapon", + "wear", + "weasel", + "weather", + "web", + "wedding", + "weekend", + "weird", + "welcome", + "west", + "wet", + "whale", + "what", + "wheat", + "wheel", + "when", + "where", + "whip", + "whisper", + "wide", + "width", + "wife", + "wild", + "will", + "win", + "window", + "wine", + "wing", + "wink", + "winner", + "winter", + "wire", + "wisdom", + "wise", + "wish", + "witness", + "wolf", + "woman", + "wonder", + "wood", + "wool", + "word", + "work", + "world", + "worry", + "worth", + "wrap", + "wreck", + "wrestle", + "wrist", + "write", + "wrong", + "yard", + "year", + "yellow", + "you", + "young", + "youth", + "zebra", + "zero", + "zone", + "zoo" +]; + +export const electrum2English = [ + "abandon", + "ability", + "able", + "about", + "above", + "absent", + "absorb", + "abstract", + "absurd", + "abuse", + "access", + "accident", + "account", + "accuse", + "achieve", + "acid", + "acoustic", + "acquire", + "across", + "act", + "action", + "actor", + "actress", + "actual", + "adapt", + "add", + "addict", + "address", + "adjust", + "admit", + "adult", + "advance", + "advice", + "aerobic", + "affair", + "afford", + "afraid", + "again", + "age", + "agent", + "agree", + "ahead", + "aim", + "air", + "airport", + "aisle", + "alarm", + "album", + "alcohol", + "alert", + "alien", + "all", + "alley", + "allow", + "almost", + "alone", + "alpha", + "already", + "also", + "alter", + "always", + "amateur", + "amazing", + "among", + "amount", + "amused", + "analyst", + "anchor", + "ancient", + "anger", + "angle", + "angry", + "animal", + "ankle", + "announce", + "annual", + "another", + "answer", + "antenna", + "antique", + "anxiety", + "any", + "apart", + "apology", + "appear", + "apple", + "approve", + "april", + "arch", + "arctic", + "area", + "arena", + "argue", + "arm", + "armed", + "armor", + "army", + "around", + "arrange", + "arrest", + "arrive", + "arrow", + "art", + "artefact", + "artist", + "artwork", + "ask", + "aspect", + "assault", + "asset", + "assist", + "assume", + "asthma", + "athlete", + "atom", + "attack", + "attend", + "attitude", + "attract", + "auction", + "audit", + "august", + "aunt", + "author", + "auto", + "autumn", + "average", + "avocado", + "avoid", + "awake", + "aware", + "away", + "awesome", + "awful", + "awkward", + "axis", + "baby", + "bachelor", + "bacon", + "badge", + "bag", + "balance", + "balcony", + "ball", + "bamboo", + "banana", + "banner", + "bar", + "barely", + "bargain", + "barrel", + "base", + "basic", + "basket", + "battle", + "beach", + "bean", + "beauty", + "because", + "become", + "beef", + "before", + "begin", + "behave", + "behind", + "believe", + "below", + "belt", + "bench", + "benefit", + "best", + "betray", + "better", + "between", + "beyond", + "bicycle", + "bid", + "bike", + "bind", + "biology", + "bird", + "birth", + "bitter", + "black", + "blade", + "blame", + "blanket", + "blast", + "bleak", + "bless", + "blind", + "blood", + "blossom", + "blouse", + "blue", + "blur", + "blush", + "board", + "boat", + "body", + "boil", + "bomb", + "bone", + "bonus", + "book", + "boost", + "border", + "boring", + "borrow", + "boss", + "bottom", + "bounce", + "box", + "boy", + "bracket", + "brain", + "brand", + "brass", + "brave", + "bread", + "breeze", + "brick", + "bridge", + "brief", + "bright", + "bring", + "brisk", + "broccoli", + "broken", + "bronze", + "broom", + "brother", + "brown", + "brush", + "bubble", + "buddy", + "budget", + "buffalo", + "build", + "bulb", + "bulk", + "bullet", + "bundle", + "bunker", + "burden", + "burger", + "burst", + "bus", + "business", + "busy", + "butter", + "buyer", + "buzz", + "cabbage", + "cabin", + "cable", + "cactus", + "cage", + "cake", + "call", + "calm", + "camera", + "camp", + "can", + "canal", + "cancel", + "candy", + "cannon", + "canoe", + "canvas", + "canyon", + "capable", + "capital", + "captain", + "car", + "carbon", + "card", + "cargo", + "carpet", + "carry", + "cart", + "case", + "cash", + "casino", + "castle", + "casual", + "cat", + "catalog", + "catch", + "category", + "cattle", + "caught", + "cause", + "caution", + "cave", + "ceiling", + "celery", + "cement", + "census", + "century", + "cereal", + "certain", + "chair", + "chalk", + "champion", + "change", + "chaos", + "chapter", + "charge", + "chase", + "chat", + "cheap", + "check", + "cheese", + "chef", + "cherry", + "chest", + "chicken", + "chief", + "child", + "chimney", + "choice", + "choose", + "chronic", + "chuckle", + "chunk", + "churn", + "cigar", + "cinnamon", + "circle", + "citizen", + "city", + "civil", + "claim", + "clap", + "clarify", + "claw", + "clay", + "clean", + "clerk", + "clever", + "click", + "client", + "cliff", + "climb", + "clinic", + "clip", + "clock", + "clog", + "close", + "cloth", + "cloud", + "clown", + "club", + "clump", + "cluster", + "clutch", + "coach", + "coast", + "coconut", + "code", + "coffee", + "coil", + "coin", + "collect", + "color", + "column", + "combine", + "come", + "comfort", + "comic", + "common", + "company", + "concert", + "conduct", + "confirm", + "congress", + "connect", + "consider", + "control", + "convince", + "cook", + "cool", + "copper", + "copy", + "coral", + "core", + "corn", + "correct", + "cost", + "cotton", + "couch", + "country", + "couple", + "course", + "cousin", + "cover", + "coyote", + "crack", + "cradle", + "craft", + "cram", + "crane", + "crash", + "crater", + "crawl", + "crazy", + "cream", + "credit", + "creek", + "crew", + "cricket", + "crime", + "crisp", + "critic", + "crop", + "cross", + "crouch", + "crowd", + "crucial", + "cruel", + "cruise", + "crumble", + "crunch", + "crush", + "cry", + "crystal", + "cube", + "culture", + "cup", + "cupboard", + "curious", + "current", + "curtain", + "curve", + "cushion", + "custom", + "cute", + "cycle", + "dad", + "damage", + "damp", + "dance", + "danger", + "daring", + "dash", + "daughter", + "dawn", + "day", + "deal", + "debate", + "debris", + "decade", + "december", + "decide", + "decline", + "decorate", + "decrease", + "deer", + "defense", + "define", + "defy", + "degree", + "delay", + "deliver", + "demand", + "demise", + "denial", + "dentist", + "deny", + "depart", + "depend", + "deposit", + "depth", + "deputy", + "derive", + "describe", + "desert", + "design", + "desk", + "despair", + "destroy", + "detail", + "detect", + "develop", + "device", + "devote", + "diagram", + "dial", + "diamond", + "diary", + "dice", + "diesel", + "diet", + "differ", + "digital", + "dignity", + "dilemma", + "dinner", + "dinosaur", + "direct", + "dirt", + "disagree", + "discover", + "disease", + "dish", + "dismiss", + "disorder", + "display", + "distance", + "divert", + "divide", + "divorce", + "dizzy", + "doctor", + "document", + "dog", + "doll", + "dolphin", + "domain", + "donate", + "donkey", + "donor", + "door", + "dose", + "double", + "dove", + "draft", + "dragon", + "drama", + "drastic", + "draw", + "dream", + "dress", + "drift", + "drill", + "drink", + "drip", + "drive", + "drop", + "drum", + "dry", + "duck", + "dumb", + "dune", + "during", + "dust", + "dutch", + "duty", + "dwarf", + "dynamic", + "eager", + "eagle", + "early", + "earn", + "earth", + "easily", + "east", + "easy", + "echo", + "ecology", + "economy", + "edge", + "edit", + "educate", + "effort", + "egg", + "eight", + "either", + "elbow", + "elder", + "electric", + "elegant", + "element", + "elephant", + "elevator", + "elite", + "else", + "embark", + "embody", + "embrace", + "emerge", + "emotion", + "employ", + "empower", + "empty", + "enable", + "enact", + "end", + "endless", + "endorse", + "enemy", + "energy", + "enforce", + "engage", + "engine", + "enhance", + "enjoy", + "enlist", + "enough", + "enrich", + "enroll", + "ensure", + "enter", + "entire", + "entry", + "envelope", + "episode", + "equal", + "equip", + "era", + "erase", + "erode", + "erosion", + "error", + "erupt", + "escape", + "essay", + "essence", + "estate", + "eternal", + "ethics", + "evidence", + "evil", + "evoke", + "evolve", + "exact", + "example", + "excess", + "exchange", + "excite", + "exclude", + "excuse", + "execute", + "exercise", + "exhaust", + "exhibit", + "exile", + "exist", + "exit", + "exotic", + "expand", + "expect", + "expire", + "explain", + "expose", + "express", + "extend", + "extra", + "eye", + "eyebrow", + "fabric", + "face", + "faculty", + "fade", + "faint", + "faith", + "fall", + "false", + "fame", + "family", + "famous", + "fan", + "fancy", + "fantasy", + "farm", + "fashion", + "fat", + "fatal", + "father", + "fatigue", + "fault", + "favorite", + "feature", + "february", + "federal", + "fee", + "feed", + "feel", + "female", + "fence", + "festival", + "fetch", + "fever", + "few", + "fiber", + "fiction", + "field", + "figure", + "file", + "film", + "filter", + "final", + "find", + "fine", + "finger", + "finish", + "fire", + "firm", + "first", + "fiscal", + "fish", + "fit", + "fitness", + "fix", + "flag", + "flame", + "flash", + "flat", + "flavor", + "flee", + "flight", + "flip", + "float", + "flock", + "floor", + "flower", + "fluid", + "flush", + "fly", + "foam", + "focus", + "fog", + "foil", + "fold", + "follow", + "food", + "foot", + "force", + "forest", + "forget", + "fork", + "fortune", + "forum", + "forward", + "fossil", + "foster", + "found", + "fox", + "fragile", + "frame", + "frequent", + "fresh", + "friend", + "fringe", + "frog", + "front", + "frost", + "frown", + "frozen", + "fruit", + "fuel", + "fun", + "funny", + "furnace", + "fury", + "future", + "gadget", + "gain", + "galaxy", + "gallery", + "game", + "gap", + "garage", + "garbage", + "garden", + "garlic", + "garment", + "gas", + "gasp", + "gate", + "gather", + "gauge", + "gaze", + "general", + "genius", + "genre", + "gentle", + "genuine", + "gesture", + "ghost", + "giant", + "gift", + "giggle", + "ginger", + "giraffe", + "girl", + "give", + "glad", + "glance", + "glare", + "glass", + "glide", + "glimpse", + "globe", + "gloom", + "glory", + "glove", + "glow", + "glue", + "goat", + "goddess", + "gold", + "good", + "goose", + "gorilla", + "gospel", + "gossip", + "govern", + "gown", + "grab", + "grace", + "grain", + "grant", + "grape", + "grass", + "gravity", + "great", + "green", + "grid", + "grief", + "grit", + "grocery", + "group", + "grow", + "grunt", + "guard", + "guess", + "guide", + "guilt", + "guitar", + "gun", + "gym", + "habit", + "hair", + "half", + "hammer", + "hamster", + "hand", + "happy", + "harbor", + "hard", + "harsh", + "harvest", + "hat", + "have", + "hawk", + "hazard", + "head", + "health", + "heart", + "heavy", + "hedgehog", + "height", + "hello", + "helmet", + "help", + "hen", + "hero", + "hidden", + "high", + "hill", + "hint", + "hip", + "hire", + "history", + "hobby", + "hockey", + "hold", + "hole", + "holiday", + "hollow", + "home", + "honey", + "hood", + "hope", + "horn", + "horror", + "horse", + "hospital", + "host", + "hotel", + "hour", + "hover", + "hub", + "huge", + "human", + "humble", + "humor", + "hundred", + "hungry", + "hunt", + "hurdle", + "hurry", + "hurt", + "husband", + "hybrid", + "ice", + "icon", + "idea", + "identify", + "idle", + "ignore", + "ill", + "illegal", + "illness", + "image", + "imitate", + "immense", + "immune", + "impact", + "impose", + "improve", + "impulse", + "inch", + "include", + "income", + "increase", + "index", + "indicate", + "indoor", + "industry", + "infant", + "inflict", + "inform", + "inhale", + "inherit", + "initial", + "inject", + "injury", + "inmate", + "inner", + "innocent", + "input", + "inquiry", + "insane", + "insect", + "inside", + "inspire", + "install", + "intact", + "interest", + "into", + "invest", + "invite", + "involve", + "iron", + "island", + "isolate", + "issue", + "item", + "ivory", + "jacket", + "jaguar", + "jar", + "jazz", + "jealous", + "jeans", + "jelly", + "jewel", + "job", + "join", + "joke", + "journey", + "joy", + "judge", + "juice", + "jump", + "jungle", + "junior", + "junk", + "just", + "kangaroo", + "keen", + "keep", + "ketchup", + "key", + "kick", + "kid", + "kidney", + "kind", + "kingdom", + "kiss", + "kit", + "kitchen", + "kite", + "kitten", + "kiwi", + "knee", + "knife", + "knock", + "know", + "lab", + "label", + "labor", + "ladder", + "lady", + "lake", + "lamp", + "language", + "laptop", + "large", + "later", + "latin", + "laugh", + "laundry", + "lava", + "law", + "lawn", + "lawsuit", + "layer", + "lazy", + "leader", + "leaf", + "learn", + "leave", + "lecture", + "left", + "leg", + "legal", + "legend", + "leisure", + "lemon", + "lend", + "length", + "lens", + "leopard", + "lesson", + "letter", + "level", + "liar", + "liberty", + "library", + "license", + "life", + "lift", + "light", + "like", + "limb", + "limit", + "link", + "lion", + "liquid", + "list", + "little", + "live", + "lizard", + "load", + "loan", + "lobster", + "local", + "lock", + "logic", + "lonely", + "long", + "loop", + "lottery", + "loud", + "lounge", + "love", + "loyal", + "lucky", + "luggage", + "lumber", + "lunar", + "lunch", + "luxury", + "lyrics", + "machine", + "mad", + "magic", + "magnet", + "maid", + "mail", + "main", + "major", + "make", + "mammal", + "man", + "manage", + "mandate", + "mango", + "mansion", + "manual", + "maple", + "marble", + "march", + "margin", + "marine", + "market", + "marriage", + "mask", + "mass", + "master", + "match", + "material", + "math", + "matrix", + "matter", + "maximum", + "maze", + "meadow", + "mean", + "measure", + "meat", + "mechanic", + "medal", + "media", + "melody", + "melt", + "member", + "memory", + "mention", + "menu", + "mercy", + "merge", + "merit", + "merry", + "mesh", + "message", + "metal", + "method", + "middle", + "midnight", + "milk", + "million", + "mimic", + "mind", + "minimum", + "minor", + "minute", + "miracle", + "mirror", + "misery", + "miss", + "mistake", + "mix", + "mixed", + "mixture", + "mobile", + "model", + "modify", + "mom", + "moment", + "monitor", + "monkey", + "monster", + "month", + "moon", + "moral", + "more", + "morning", + "mosquito", + "mother", + "motion", + "motor", + "mountain", + "mouse", + "move", + "movie", + "much", + "muffin", + "mule", + "multiply", + "muscle", + "museum", + "mushroom", + "music", + "must", + "mutual", + "myself", + "mystery", + "myth", + "naive", + "name", + "napkin", + "narrow", + "nasty", + "nation", + "nature", + "near", + "neck", + "need", + "negative", + "neglect", + "neither", + "nephew", + "nerve", + "nest", + "net", + "network", + "neutral", + "never", + "news", + "next", + "nice", + "night", + "noble", + "noise", + "nominee", + "noodle", + "normal", + "north", + "nose", + "notable", + "note", + "nothing", + "notice", + "novel", + "now", + "nuclear", + "number", + "nurse", + "nut", + "oak", + "obey", + "object", + "oblige", + "obscure", + "observe", + "obtain", + "obvious", + "occur", + "ocean", + "october", + "odor", + "off", + "offer", + "office", + "often", + "oil", + "okay", + "old", + "olive", + "olympic", + "omit", + "once", + "one", + "onion", + "online", + "only", + "open", + "opera", + "opinion", + "oppose", + "option", + "orange", + "orbit", + "orchard", + "order", + "ordinary", + "organ", + "orient", + "original", + "orphan", + "ostrich", + "other", + "outdoor", + "outer", + "output", + "outside", + "oval", + "oven", + "over", + "own", + "owner", + "oxygen", + "oyster", + "ozone", + "pact", + "paddle", + "page", + "pair", + "palace", + "palm", + "panda", + "panel", + "panic", + "panther", + "paper", + "parade", + "parent", + "park", + "parrot", + "party", + "pass", + "patch", + "path", + "patient", + "patrol", + "pattern", + "pause", + "pave", + "payment", + "peace", + "peanut", + "pear", + "peasant", + "pelican", + "pen", + "penalty", + "pencil", + "people", + "pepper", + "perfect", + "permit", + "person", + "pet", + "phone", + "photo", + "phrase", + "physical", + "piano", + "picnic", + "picture", + "piece", + "pig", + "pigeon", + "pill", + "pilot", + "pink", + "pioneer", + "pipe", + "pistol", + "pitch", + "pizza", + "place", + "planet", + "plastic", + "plate", + "play", + "please", + "pledge", + "pluck", + "plug", + "plunge", + "poem", + "poet", + "point", + "polar", + "pole", + "police", + "pond", + "pony", + "pool", + "popular", + "portion", + "position", + "possible", + "post", + "potato", + "pottery", + "poverty", + "powder", + "power", + "practice", + "praise", + "predict", + "prefer", + "prepare", + "present", + "pretty", + "prevent", + "price", + "pride", + "primary", + "print", + "priority", + "prison", + "private", + "prize", + "problem", + "process", + "produce", + "profit", + "program", + "project", + "promote", + "proof", + "property", + "prosper", + "protect", + "proud", + "provide", + "public", + "pudding", + "pull", + "pulp", + "pulse", + "pumpkin", + "punch", + "pupil", + "puppy", + "purchase", + "purity", + "purpose", + "purse", + "push", + "put", + "puzzle", + "pyramid", + "quality", + "quantum", + "quarter", + "question", + "quick", + "quit", + "quiz", + "quote", + "rabbit", + "raccoon", + "race", + "rack", + "radar", + "radio", + "rail", + "rain", + "raise", + "rally", + "ramp", + "ranch", + "random", + "range", + "rapid", + "rare", + "rate", + "rather", + "raven", + "raw", + "razor", + "ready", + "real", + "reason", + "rebel", + "rebuild", + "recall", + "receive", + "recipe", + "record", + "recycle", + "reduce", + "reflect", + "reform", + "refuse", + "region", + "regret", + "regular", + "reject", + "relax", + "release", + "relief", + "rely", + "remain", + "remember", + "remind", + "remove", + "render", + "renew", + "rent", + "reopen", + "repair", + "repeat", + "replace", + "report", + "require", + "rescue", + "resemble", + "resist", + "resource", + "response", + "result", + "retire", + "retreat", + "return", + "reunion", + "reveal", + "review", + "reward", + "rhythm", + "rib", + "ribbon", + "rice", + "rich", + "ride", + "ridge", + "rifle", + "right", + "rigid", + "ring", + "riot", + "ripple", + "risk", + "ritual", + "rival", + "river", + "road", + "roast", + "robot", + "robust", + "rocket", + "romance", + "roof", + "rookie", + "room", + "rose", + "rotate", + "rough", + "round", + "route", + "royal", + "rubber", + "rude", + "rug", + "rule", + "run", + "runway", + "rural", + "sad", + "saddle", + "sadness", + "safe", + "sail", + "salad", + "salmon", + "salon", + "salt", + "salute", + "same", + "sample", + "sand", + "satisfy", + "satoshi", + "sauce", + "sausage", + "save", + "say", + "scale", + "scan", + "scare", + "scatter", + "scene", + "scheme", + "school", + "science", + "scissors", + "scorpion", + "scout", + "scrap", + "screen", + "script", + "scrub", + "sea", + "search", + "season", + "seat", + "second", + "secret", + "section", + "security", + "seed", + "seek", + "segment", + "select", + "sell", + "seminar", + "senior", + "sense", + "sentence", + "series", + "service", + "session", + "settle", + "setup", + "seven", + "shadow", + "shaft", + "shallow", + "share", + "shed", + "shell", + "sheriff", + "shield", + "shift", + "shine", + "ship", + "shiver", + "shock", + "shoe", + "shoot", + "shop", + "short", + "shoulder", + "shove", + "shrimp", + "shrug", + "shuffle", + "shy", + "sibling", + "sick", + "side", + "siege", + "sight", + "sign", + "silent", + "silk", + "silly", + "silver", + "similar", + "simple", + "since", + "sing", + "siren", + "sister", + "situate", + "six", + "size", + "skate", + "sketch", + "ski", + "skill", + "skin", + "skirt", + "skull", + "slab", + "slam", + "sleep", + "slender", + "slice", + "slide", + "slight", + "slim", + "slogan", + "slot", + "slow", + "slush", + "small", + "smart", + "smile", + "smoke", + "smooth", + "snack", + "snake", + "snap", + "sniff", + "snow", + "soap", + "soccer", + "social", + "sock", + "soda", + "soft", + "solar", + "soldier", + "solid", + "solution", + "solve", + "someone", + "song", + "soon", + "sorry", + "sort", + "soul", + "sound", + "soup", + "source", + "south", + "space", + "spare", + "spatial", + "spawn", + "speak", + "special", + "speed", + "spell", + "spend", + "sphere", + "spice", + "spider", + "spike", + "spin", + "spirit", + "split", + "spoil", + "sponsor", + "spoon", + "sport", + "spot", + "spray", + "spread", + "spring", + "spy", + "square", + "squeeze", + "squirrel", + "stable", + "stadium", + "staff", + "stage", + "stairs", + "stamp", + "stand", + "start", + "state", + "stay", + "steak", + "steel", + "stem", + "step", + "stereo", + "stick", + "still", + "sting", + "stock", + "stomach", + "stone", + "stool", + "story", + "stove", + "strategy", + "street", + "strike", + "strong", + "struggle", + "student", + "stuff", + "stumble", + "style", + "subject", + "submit", + "subway", + "success", + "such", + "sudden", + "suffer", + "sugar", + "suggest", + "suit", + "summer", + "sun", + "sunny", + "sunset", + "super", + "supply", + "supreme", + "sure", + "surface", + "surge", + "surprise", + "surround", + "survey", + "suspect", + "sustain", + "swallow", + "swamp", + "swap", + "swarm", + "swear", + "sweet", + "swift", + "swim", + "swing", + "switch", + "sword", + "symbol", + "symptom", + "syrup", + "system", + "table", + "tackle", + "tag", + "tail", + "talent", + "talk", + "tank", + "tape", + "target", + "task", + "taste", + "tattoo", + "taxi", + "teach", + "team", + "tell", + "ten", + "tenant", + "tennis", + "tent", + "term", + "test", + "text", + "thank", + "that", + "theme", + "then", + "theory", + "there", + "they", + "thing", + "this", + "thought", + "three", + "thrive", + "throw", + "thumb", + "thunder", + "ticket", + "tide", + "tiger", + "tilt", + "timber", + "time", + "tiny", + "tip", + "tired", + "tissue", + "title", + "toast", + "tobacco", + "today", + "toddler", + "toe", + "together", + "toilet", + "token", + "tomato", + "tomorrow", + "tone", + "tongue", + "tonight", + "tool", + "tooth", + "top", + "topic", + "topple", + "torch", + "tornado", + "tortoise", + "toss", + "total", + "tourist", + "toward", + "tower", + "town", + "toy", + "track", + "trade", + "traffic", + "tragic", + "train", + "transfer", + "trap", + "trash", + "travel", + "tray", + "treat", + "tree", + "trend", + "trial", + "tribe", + "trick", + "trigger", + "trim", + "trip", + "trophy", + "trouble", + "truck", + "true", + "truly", + "trumpet", + "trust", + "truth", + "try", + "tube", + "tuition", + "tumble", + "tuna", + "tunnel", + "turkey", + "turn", + "turtle", + "twelve", + "twenty", + "twice", + "twin", + "twist", + "two", + "type", + "typical", + "ugly", + "umbrella", + "unable", + "unaware", + "uncle", + "uncover", + "under", + "undo", + "unfair", + "unfold", + "unhappy", + "uniform", + "unique", + "unit", + "universe", + "unknown", + "unlock", + "until", + "unusual", + "unveil", + "update", + "upgrade", + "uphold", + "upon", + "upper", + "upset", + "urban", + "urge", + "usage", + "use", + "used", + "useful", + "useless", + "usual", + "utility", + "vacant", + "vacuum", + "vague", + "valid", + "valley", + "valve", + "van", + "vanish", + "vapor", + "various", + "vast", + "vault", + "vehicle", + "velvet", + "vendor", + "venture", + "venue", + "verb", + "verify", + "version", + "very", + "vessel", + "veteran", + "viable", + "vibrant", + "vicious", + "victory", + "video", + "view", + "village", + "vintage", + "violin", + "virtual", + "virus", + "visa", + "visit", + "visual", + "vital", + "vivid", + "vocal", + "voice", + "void", + "volcano", + "volume", + "vote", + "voyage", + "wage", + "wagon", + "wait", + "walk", + "wall", + "walnut", + "want", + "warfare", + "warm", + "warrior", + "wash", + "wasp", + "waste", + "water", + "wave", + "way", + "wealth", + "weapon", + "wear", + "weasel", + "weather", + "web", + "wedding", + "weekend", + "weird", + "welcome", + "west", + "wet", + "whale", + "what", + "wheat", + "wheel", + "when", + "where", + "whip", + "whisper", + "wide", + "width", + "wife", + "wild", + "will", + "win", + "window", + "wine", + "wing", + "wink", + "winner", + "winter", + "wire", + "wisdom", + "wise", + "wish", + "witness", + "wolf", + "woman", + "wonder", + "wood", + "wool", + "word", + "work", + "world", + "worry", + "worth", + "wrap", + "wreck", + "wrestle", + "wrist", + "write", + "wrong", + "yard", + "year", + "yellow", + "you", + "young", + "youth", + "zebra", + "zero", + "zone", + "zoo", +]; diff --git a/src/core/operations/BIP32Derive.mjs b/src/core/operations/BIP32Derive.mjs new file mode 100644 index 00000000..91d11719 --- /dev/null +++ b/src/core/operations/BIP32Derive.mjs @@ -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])) { + throw new OperationError("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)) { + throw new OperationError("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; diff --git a/src/core/operations/ChangeExtendedKeyVersion.mjs b/src/core/operations/ChangeExtendedKeyVersion.mjs new file mode 100644 index 00000000..05bce91b --- /dev/null +++ b/src/core/operations/ChangeExtendedKeyVersion.mjs @@ -0,0 +1,66 @@ +/** + * 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 OperationError from "../errors/OperationError.mjs"; +import { deserializeExtendedKeyFunc, serializeExtendedKeyFunc, getExtendedKeyVersion, getVersions } 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": getVersions() + } + ]; + 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); + if ("error" in result) { + throw new OperationError("Error in deserializing key. Error is: " + result.error); + } + const newVersion = getExtendedKeyVersion(args[0]); + return serializeExtendedKeyFunc(newVersion, result.level, result.fingerprint, result.i, result.chaincode, result.masterkey); + } + +} + +export default ChangeExtendedKeyVersion; diff --git a/src/core/operations/DecryptKeystoreFile.mjs b/src/core/operations/DecryptKeystoreFile.mjs new file mode 100644 index 00000000..bbba836e --- /dev/null +++ b/src/core/operations/DecryptKeystoreFile.mjs @@ -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; diff --git a/src/core/operations/DeserializeExtendedKey.mjs b/src/core/operations/DeserializeExtendedKey.mjs new file mode 100644 index 00000000..a2c51f2f --- /dev/null +++ b/src/core/operations/DeserializeExtendedKey.mjs @@ -0,0 +1,90 @@ +/** + * 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 OperationError from "../errors/OperationError.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) { + throw new OperationError(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; diff --git a/src/core/operations/ETHTRXConversion.mjs b/src/core/operations/ETHTRXConversion.mjs new file mode 100644 index 00000000..6caaaf63 --- /dev/null +++ b/src/core/operations/ETHTRXConversion.mjs @@ -0,0 +1,118 @@ +/** + * @author dgoldenberg [virtualcurrency@mitre.org] + * @copyright Crown Copyright 2024 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import {base58Decode, base58Encode, doubleSHA, b58DoubleSHAChecksum} from "../lib/Bitcoin.mjs"; +import Utils from "../Utils.mjs"; +import {toHex} from "crypto-api/src/encoder/hex.mjs"; +import { fromArrayBuffer } from "crypto-api/src/encoder/array-buffer.mjs"; +import JSSHA3 from "js-sha3"; + +/** + * Turns an address to a checksummed address. + * @param {*} address ETH style address. + */ +function ethCheckSum(address) { + const lowerCaseAddress = address.toLowerCase(); + const trimmedAddress = lowerCaseAddress.slice(2,); + const algo = JSSHA3.keccak256; + const hashResult = algo(Utils.convertToByteArray(trimmedAddress, "utf8")); + let finalResult = ""; + for (let i = 0; i < trimmedAddress.length; i++) { + if (trimmedAddress.charAt(i).toLowerCase() >= "a" && trimmedAddress.charAt(i).toLowerCase() <= "f") { + if (hashResult.charAt(i)>= "8") { + finalResult += trimmedAddress.charAt(i).toUpperCase(); + } else { + finalResult += trimmedAddress.charAt(i); + } + } else { + finalResult += trimmedAddress.charAt(i); + } + } + return "0x" + finalResult; +} + +/** + * Checks if past in address could be a valid ETH address based off of regex. + * @param {*} input + * @returns + */ +function validateETHAddress(input) { + const regex = /^0x[a-f,A-F,0-9]{40}$/g; + return regex.test(input); +} + +/** + * Runs the checksum on the potential TRX address. Returns false is checksum fails. + * @param {*} input + * @returns + */ +function validateTRXAddress(input) { + return b58DoubleSHAChecksum(input); +} +/** + * ETH / TRX Conversion operation + */ +class ETHTRXConversion extends Operation { + + /** + * ETHTRXConversion constructor + */ + constructor() { + super(); + + this.name = "ETH / TRX Conversion"; + this.module = "Default"; + this.description = "Converts between ETH and TRX addresses."; + this.infoURL = "https://tronscan.org/#/tools/code-converter/tron-ethereum-address"; // Usually a Wikipedia link. Remember to remove localisation (i.e. https://wikipedia.org/etc rather than https://en.wikipedia.org/etc) + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "Direction", + type: "option", + value: ["ETH->TRX", "TRX->ETH"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [direction] = 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 ""; + } + switch (direction) { + case "ETH->TRX":{ + if (!validateETHAddress(input)) { + throw new OperationError("Invalid ETH address. ETH addresses should have 20 bytes (40 characters) prefaced by 0x."); + } + const unencodedAddress = input.slice(2,); + const checksumHash = toHex(doubleSHA(fromArrayBuffer(Utils.convertToByteArray("41" + unencodedAddress, "hex")))); + const finalString = "41" + unencodedAddress + checksumHash.slice(0, 8); + const address = base58Encode(Utils.convertToByteArray(finalString, "hex")); + return address; + } + case "TRX->ETH":{ + if (!validateTRXAddress(input)) { + throw new OperationError("Invalid TRX Address. Checksum failed."); + } + return ethCheckSum("0x" + toHex(fromArrayBuffer(base58Decode(input).slice(1, -4)))); + } + + } + } + +} + +export default ETHTRXConversion; diff --git a/src/core/operations/ExtractDoubleShaArtifacts.mjs b/src/core/operations/ExtractDoubleShaArtifacts.mjs new file mode 100644 index 00000000..4cb8348e --- /dev/null +++ b/src/core/operations/ExtractDoubleShaArtifacts.mjs @@ -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; + diff --git a/src/core/operations/ExtractSeedPhrases.mjs b/src/core/operations/ExtractSeedPhrases.mjs new file mode 100644 index 00000000..64377db2 --- /dev/null +++ b/src/core/operations/ExtractSeedPhrases.mjs @@ -0,0 +1,87 @@ +/** + * Extracts seedphrases. Right now, extracts BIP39 and Electrum2 seedphrases. + * + * @author dgoldenberg [virtualcurrency@mitre.org] + * @copyright MITRE 2023 Wei Lu and Daniel Cousens 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; diff --git a/src/core/operations/ExtractSegwitAddresses.mjs b/src/core/operations/ExtractSegwitAddresses.mjs new file mode 100644 index 00000000..d1c0b9dc --- /dev/null +++ b/src/core/operations/ExtractSegwitAddresses.mjs @@ -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; + diff --git a/src/core/operations/FromBase58Check.mjs b/src/core/operations/FromBase58Check.mjs new file mode 100644 index 00000000..61006c7d --- /dev/null +++ b/src/core/operations/FromBase58Check.mjs @@ -0,0 +1,71 @@ +/** + * @author dgoldenberg [dgoldenberg@mitre.org] + * @copyright Crown Copyright 2026 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.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"; + + +/** + * FromBase58Check operation + */ +class FromBase58Check extends Operation { + + /** + * FromBase58Check constructor + */ + constructor() { + super(); + + this.name = "From Base58Check"; + this.module = "Default"; + this.description = "Decodes Base58 check encoded data. This is a version byte, data and a 4 byte checksum at the end. Many addresses, private keys and other cryptocurrency artifacts are encoded in this format."; + this.infoURL = "https://en.bitcoin.it/Base58Check_encoding"; // Usually a Wikipedia link. Remember to remove localisation (i.e. https://wikipedia.org/etc rather than https://en.wikipedia.org/etc) + this.inputType = "string"; + this.outputType = "JSON"; + this.args = [ + { + name: "Version Byte Length", + type: "number", + value: 1 + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {JSON} + */ + 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 versionHex = toHex(fromArrayBuffer(decoded.slice(0, args[0]))); + const checksum = toHex(fromArrayBuffer(decoded.slice(-4,))); + const data = toHex(fromArrayBuffer(decoded.slice(args[0], -4))); + return { + "version": versionHex, + "checksum": checksum, + "data": data + }; + + } else { + throw new OperationError("Invalid Checksum."); + } + } + +} + +export default FromBase58Check; diff --git a/src/core/operations/KeyToExtendedKey.mjs b/src/core/operations/KeyToExtendedKey.mjs new file mode 100644 index 00000000..0c0ba3f1 --- /dev/null +++ b/src/core/operations/KeyToExtendedKey.mjs @@ -0,0 +1,103 @@ +/** + * @author dcgoldenberg [dgoldenberg@mitre.org] + * @copyright Crown Copyright 2025 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import { makeSureIsHex, serializeExtendedKeyFunc, getExtendedKeyVersion, getVersions } from "../lib/Bitcoin.mjs"; + + +/** + * Key To Extended Key operation + */ +class KeyToExtendedKey extends Operation { + + /** + * KeyToExtendedKey constructor + */ + constructor() { + super(); + + this.name = "Key To Extended Key"; + this.module = "Default"; + this.description = "Turns a key, with chaincode and version as parameters, into an extended key. We assume the key is meant to be a master key, so depth and child number are set to 0, and fingerprint is set to 00000000."; + this.infoURL = "https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki"; // Usually a Wikipedia link. Remember to remove localisation (i.e. https://wikipedia.org/etc rather than https://en.wikipedia.org/etc) + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Chaincode", + "type": "toggleString", + "value": "", + "toggleValues": ["Hex"] + }, + { + "name": "Fingerprint", + "type": "toggleString", + "value": "00000000", + "toggleValues": ["Hex"] + }, + { + "name": "Level", + "type": "toggleString", + "value": 0, + "toggleValues": ["Hex", "Number"] + }, + { + "name": "Version Type", + "type": "option", + "value": getVersions() + }, + { + "name": "i", + "type": "toggleString", + "value": 0, + "toggleValues": ["Hex", "Number"] + } + + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + // const [firstArg, secondArg] = args; + if (input.trim().length === 0) { + return ""; + } + input = input.trim(); + + const inputAsHex = makeSureIsHex(input); + const isPublic = inputAsHex.length === 66 && (inputAsHex.startsWith("03") || inputAsHex.startsWith("02")); + const isPrivate = inputAsHex.length === 64; + const privateVersions = ["xprv", "yprv", "zprv", "Zprv", "Yprv", "Ltpv", "Mtpv", "ttpv", "tprv", "uprv", "vprv", "Uprv", "Vprv"]; + if (!isPublic && !isPrivate) { + throw new OperationError("Error: String " + inputAsHex + " is not a valid public or private key."); + } + if (isPublic && privateVersions.indexOf(args[3]) !== -1) { + throw new OperationError("Error: Mis-Match between version and key type. Public Key is entered, but a private version is selected."); + } + if (isPrivate && privateVersions.indexOf(args[3]) === -1) { + throw new OperationError("Error: Mis-Match between version and key type. Private Key is entered, but a public version is selected."); + } + const key = isPrivate ? "00" + inputAsHex : inputAsHex; + const radix = (args[2].option === "Hex") ? 16 : 10; + const level = parseInt(args[2].string, radix); + const iRadix = (args[4].option === "Hex") ? 16: 10; + const i = parseInt(args[4].string, iRadix); + const fingerprint = args[1]; + const newVersion = getExtendedKeyVersion(args[3]); + + const newExtendedKey = serializeExtendedKeyFunc(newVersion, level, fingerprint.string, i, args[0].string, key); + return newExtendedKey; + + } + +} + +export default KeyToExtendedKey; diff --git a/src/core/operations/PrivateECKeyToPublic.mjs b/src/core/operations/PrivateECKeyToPublic.mjs new file mode 100644 index 00000000..e1a72083 --- /dev/null +++ b/src/core/operations/PrivateECKeyToPublic.mjs @@ -0,0 +1,130 @@ +/** + * 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 OperationError from "../errors/OperationError.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"]; +const OUTPUT_OPTIONS = ["Public Key Only", "Private,Public", "Public,Private"]; + +/** + * Format the output based on the passed in option from OUTPUT_OPTIONS + * @param {*} public_key + * @param {*} private_key + * @param {*} option + * @returns + */ +function formatOutput(privateKey, publicKey, option) { + switch (option) { + case "Public Key Only": + return publicKey; + case "Private,Public": + return privateKey+","+publicKey; + case "Public,Private": + return publicKey+","+privateKey; + default: + throw OperationError("Invalid output format option: " + option + "."); + + } +} +/** + * 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. Covers secp256k1 and ed25519 curves. For secp256k1 curves we can output either compressed or uncompressed public keys. ED25519 keys are slightly different, in this case we differentiate between clamped scalar multiplication (used most often) or unclamped (less frequently used, though used in Monero addresses)."; + this.inputType = "string"; + this.outputType = "string"; + this.infoURL = "https://en.bitcoin.it/wiki/Secp256k1"; + this.args = [ + { + "name": "Compressed/Clamped", + "type": "boolean", + "value": true + }, + { + "name": "Curve", + "type": "option", + "value": ["secp256k1", "ed25519"] + }, + { + "name": "Output Option", + "type": "option", + "value": OUTPUT_OPTIONS + } + ]; + 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) { + throw new OperationError("Error with the input as private key. Error is:\n\t" + privKeyCheck); + } + const processedInput = makeSureIsHex(input); + + if (args[1] === "secp256k1") { + const ecContext = ec.ec("secp256k1"); + const key = ecContext.keyFromPrivate(processedInput); + const pubkey = key.getPublic(args[0], "hex"); + return formatOutput(processedInput, pubkey, args[2]); + } else if (args[1] === "ed25519") { + if (args[0]) { + const ed = new ec.eddsa("ed25519"); + const publicKeyHex = ed.keyFromSecret(processedInput).getPublic("hex"); + return formatOutput(processedInput, publicKeyHex, args[2]); + } else { + const ed = new ec.eddsa("ed25519"); + const scalarBytes = Buffer.from(processedInput, "hex"); + const scalar = ed.decodeInt([...scalarBytes]); + const point = ed.g.mul(scalar); + const encodedPoint = Buffer.from(ed.encodePoint(point)); + return encodedPoint.toString("hex"); + } + } else { + throw OperationError("Unexpected curve selection: " + args[1] + "."); + } + + + } + +} + +export default PrivateECKeyToPublic; diff --git a/src/core/operations/PrivateExtendedKeyToPublic.mjs b/src/core/operations/PrivateExtendedKeyToPublic.mjs new file mode 100644 index 00000000..4b22bfca --- /dev/null +++ b/src/core/operations/PrivateExtendedKeyToPublic.mjs @@ -0,0 +1,68 @@ +/** + * @author dgoldenberg [virtualcurrency@mitre.org] + * @copyright Crown Copyright 2025 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import ec from "elliptic"; +import { deserializeExtendedKeyFunc, serializeExtendedKeyFunc, getExtendedKeyVersion, privateVersionToPublicVersion, getExtendedKeyString +} from "../lib/Bitcoin.mjs"; + +/** + * PrivateExtendedKeyToPublic operation + */ +class PrivateExtendedKeyToPublic extends Operation { + + /** + * PrivateExtendedKeyToPublic constructor + */ + constructor() { + super(); + + this.name = "Private Extended Key To Public"; + this.module = "Default"; + this.description = "Convert a private extended key to its associated public key. This may be useful post a BIP32Derivation operation if you need the extended public key for a different toolset."; + this.infoURL = "https://en.bitcoin.it/wiki/BIP_0032"; // Usually a Wikipedia link. Remember to remove localisation (i.e. https://wikipedia.org/etc rather than https://en.wikipedia.org/etc) + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + // const [firstArg, secondArg] = args; + // const [firstArg, secondArg] = args; + if (input.trim().length === 0) { + return ""; + } + input = input.trim(); + + const result = deserializeExtendedKeyFunc(input); + if ("error" in result) { + throw new OperationError("Error in deserializing key. Error is: " + result.error); + } + const versionString = getExtendedKeyString(result.version); + const privateVersions = ["xprv", "yprv", "zprv", "Zprv", "Yprv", "Ltpv", "Mtpv", "ttpv", "tprv", "uprv", "vprv", "Uprv", "Vprv"]; + if (privateVersions.indexOf(versionString) === -1) { + throw new OperationError("Not an extended private key."); + } + const newVersion = privateVersionToPublicVersion(versionString); + + const processedInput = result.masterkey.slice(2,); + const ecContext = ec.ec("secp256k1"); + const key = ecContext.keyFromPrivate(processedInput); + const pubkey = key.getPublic(true, "hex"); + + return serializeExtendedKeyFunc(getExtendedKeyVersion(newVersion), result.level, result.fingerprint, result.i, result.chaincode, pubkey); + } + +} + +export default PrivateExtendedKeyToPublic; diff --git a/src/core/operations/PrivateKeyToWIF.mjs b/src/core/operations/PrivateKeyToWIF.mjs new file mode 100644 index 00000000..14d27253 --- /dev/null +++ b/src/core/operations/PrivateKeyToWIF.mjs @@ -0,0 +1,90 @@ +/** + * 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 OperationError from "../errors/OperationError.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) { + throw new OperationError("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; diff --git a/src/core/operations/PublicKeyToETHStyleAddress.mjs b/src/core/operations/PublicKeyToETHStyleAddress.mjs new file mode 100644 index 00000000..d5505c67 --- /dev/null +++ b/src/core/operations/PublicKeyToETHStyleAddress.mjs @@ -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; diff --git a/src/core/operations/PublicKeyToP2PKHAddress.mjs b/src/core/operations/PublicKeyToP2PKHAddress.mjs new file mode 100644 index 00000000..5ee25e3f --- /dev/null +++ b/src/core/operations/PublicKeyToP2PKHAddress.mjs @@ -0,0 +1,140 @@ +/** + * 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 OperationError from "../errors/OperationError.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, tweakHash, liftX, makeSureIsHex} from "../lib/Bitcoin.mjs"; +import {encodeProgramToSegwit} from "../lib/Bech32.mjs"; +import Utils from "../Utils.mjs"; +import ec from "elliptic"; + +/** + * Tweaks the key in compliance with BIP340. Needed for creating P2TR addresses. + * @param {*} input + */ +function tweakKey(input) { + // First EC Context. + const ecContext = ec.ec("secp256k1"); + + // We lift the passed in, input, dropping the first byte. + const liftedKey = liftX(makeSureIsHex(input).slice(2,)); + if (liftedKey === -1) + return -1; + // We then run the input through the tweakHash, getting the first tweaked Private Key; + const tweakedKey = tweakHash(makeSureIsBytes(liftedKey)); + // We turn the first private key, into a SECP256k1 Key. + const key = ecContext.keyFromPrivate(makeSureIsHex(tweakedKey)); + + // We take the lifted key, cast it back to a public key + const newKey = "02".concat(makeSureIsHex(liftedKey)); + const ecContext1 = ec.ec("secp256k1"); + const otherKey = ecContext1.keyFromPublic(newKey, "hex"); + + // We add the public keys together and return the result as compressed. + const final = otherKey.getPublic().add(key.getPublic()); + return final.encodeCompressed("hex"); +} + /** + * 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 Bitcoin-Like Address"; + this.module = "Default"; + this.description = "Turns a public key into a Bitcoin-Like cryptocurrency address. Can select P2PKH, P2SH-P2WPKH, P2WPKH and P2TR 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)", "Taproot (P2TR bc1p 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) !== "") { + throw new OperationError(validatePublicKey(input)); + } + // P2TR are their own separate case. We handle those first. + if (args[1] === "Taproot (P2TR bc1p Addresses)") { + const hrp = getHumanReadablePart(args[0]); + const resultKey = tweakKey(input); + if (resultKey === -1) { + throw new OperationError("Error: Bad Public Key to turn into P2TR Address."); + } + return encodeProgramToSegwit(hrp, 1, Utils.convertToByteArray("01" + resultKey.slice(2,), "hex")); + } else { + // 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("00" + redeemScript, "hex")); + } else { + throw new OperationError(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; diff --git a/src/core/operations/PublicKeyToTRXStyleAddress.mjs b/src/core/operations/PublicKeyToTRXStyleAddress.mjs new file mode 100644 index 00000000..3fcbc005 --- /dev/null +++ b/src/core/operations/PublicKeyToTRXStyleAddress.mjs @@ -0,0 +1,82 @@ +/** + * @author dgoldenberg [virtualcurrency@mitre.org] + * @copyright Crown Copyright 2024 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import {makeSureIsBytes, validatePublicKey, base58Encode, doubleSHA} from "../lib/Bitcoin.mjs"; +import { fromArrayBuffer } from "crypto-api/src/encoder/array-buffer.mjs"; +import {toHex} from "crypto-api/src/encoder/hex.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 pubKeyToTRXAddress(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,)); + const unencodedAddress = result.slice(-40); + const checksumHash = toHex(doubleSHA(fromArrayBuffer(Utils.convertToByteArray("41" + unencodedAddress, "hex")))); + const finalString = "41" + unencodedAddress + checksumHash.slice(0, 8); + const address = base58Encode(Utils.convertToByteArray(finalString, "hex")); + return address; + +} + +/** + * Public Key To TRX Style Address operation + */ +class PublicKeyToTRXStyleAddress extends Operation { + + /** + * PublicKeyToTRXStyleAddress constructor + */ + constructor() { + super(); + + this.name = "Public Key To TRX Style Address"; + this.module = "Default"; + this.description = "Converts a public key, (33 bytes beginning with 02 or 03 for compressed or 65 bytes beginning with 04) to a TRX style address. This involves hashing the public key using keccack-256 like Ethereum, but encoding the result using base58 encoding. "; + this.infoURL = "https://developers.tron.network/docs/account"; + this.inputType = "string"; + this.outputType = "string"; + this.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) !== "") { + throw new OperationError(validatePublicKey(input)); + } + return pubKeyToTRXAddress(input); + } + +} + +export default PublicKeyToTRXStyleAddress; diff --git a/src/core/operations/SeedToMPK.mjs b/src/core/operations/SeedToMPK.mjs new file mode 100644 index 00000000..16b62b7c --- /dev/null +++ b/src/core/operations/SeedToMPK.mjs @@ -0,0 +1,71 @@ +/** + * 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, getVersions, isHex } 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": getVersions() + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + if (input.trim().length === 0) { + return ""; + } + input = input.trim(); + + + const isItHex = isHex(input); + // Create the hmac. + const hmac = forge.hmac.create(); + hmac.start("sha512", Utils.convertToByteString("Bitcoin seed", "UTF8")); + if (isItHex) { + 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; diff --git a/src/core/operations/SeedphraseToSeed.mjs b/src/core/operations/SeedphraseToSeed.mjs new file mode 100644 index 00000000..d82b4551 --- /dev/null +++ b/src/core/operations/SeedphraseToSeed.mjs @@ -0,0 +1,68 @@ +/** + * Turns a seedphrase into the inital seed given PBKDF2. + * + * @author dgoldenberg [virtualcurrency@mitre.org] + * @copyright MITRE 2023 Wei Lu and Daniel Cousens 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; diff --git a/src/core/operations/ToBase58Check.mjs b/src/core/operations/ToBase58Check.mjs new file mode 100644 index 00000000..74709cfe --- /dev/null +++ b/src/core/operations/ToBase58Check.mjs @@ -0,0 +1,64 @@ +/** + * @author dgoldenberg [dgoldenberg@mitre.org] + * @copyright Crown Copyright 2026 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +// import OperationError from "../errors/OperationError.mjs"; +import { base58Encode, doubleSHA, 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 Utils from "../Utils.mjs"; +/** + * To Base58Check operation + */ +class ToBase58Check extends Operation { + + /** + * ToBase58Check constructor + */ + constructor() { + super(); + + this.name = "To Base58Check"; + this.module = "Default"; + this.description = "Converts passed in string to Base58 Check Encoding. Version Bytes (as hex) are pre-pended, and a checksum is created."; + this.infoURL = "https://en.bitcoin.it/Base58Check_encoding"; // Usually a Wikipedia link. Remember to remove localisation (i.e. https://wikipedia.org/etc rather than https://en.wikipedia.org/etc) + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Version Bytes", + "type": "toggleString", + "value": "", + "toggleValues": ["Hex"] + }, + ]; + } + + /** + * @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 processedInput = makeSureIsHex(input); + const processedVersion = makeSureIsHex(args[0].string); + const extendedInput = processedVersion + processedInput; + const checksumHash = toHex(doubleSHA(fromArrayBuffer(Utils.convertToByteArray(extendedInput, "hex")))); + const finalString = extendedInput + checksumHash.slice(0, 8); + const encodedOutput = base58Encode(Utils.convertToByteArray(finalString, "hex")); + return encodedOutput; + } + +} + +export default ToBase58Check; diff --git a/src/core/operations/TypeCryptoArtifact.mjs b/src/core/operations/TypeCryptoArtifact.mjs new file mode 100644 index 00000000..7b69d7e3 --- /dev/null +++ b/src/core/operations/TypeCryptoArtifact.mjs @@ -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; diff --git a/src/core/operations/WIFToPrivateKey.mjs b/src/core/operations/WIFToPrivateKey.mjs new file mode 100644 index 00000000..8f8ab28a --- /dev/null +++ b/src/core/operations/WIFToPrivateKey.mjs @@ -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; diff --git a/tests/operations/tests/Base58Check.mjs b/tests/operations/tests/Base58Check.mjs new file mode 100644 index 00000000..694d63a1 --- /dev/null +++ b/tests/operations/tests/Base58Check.mjs @@ -0,0 +1,144 @@ +/** + * 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: "Basic Base58 Check Decoding", + input: "1111111111111111111114oLvT2", + expectedOutput: "{\n \"version\": \"\",\n \"checksum\": \"94a00911\",\n \"data\": \"000000000000000000000000000000000000000000\"\n}", + recipeConfig: [ + { + "op": "From Base58Check", + "args": [0] + } + ], + }, + { + name: "Basic Base58 Check Decoding (Invalid Checksum)", + input: "1111111111111111111114oLvT3", + expectedOutput: "Invalid Checksum.", + recipeConfig: [ + { + "op": "From Base58Check", + "args": [0] + } + ], + }, + { + name: "Basic Base58 Check Decoding (Invalid Checksum Leading Zeros 1)", + input: "111111111111111111114oLvT2", + expectedOutput: "Invalid Checksum.", + recipeConfig: [ + { + "op": "From Base58Check", + "args": [0] + } + ], + }, + { + name: "Basic Base58 Check Decoding (Invalid Checksum Leading Zeros 2)", + input: "11111111111111111111114oLvT2", + expectedOutput: "Invalid Checksum.", + recipeConfig: [ + { + "op": "From Base58Check", + "args": [0] + } + ], + }, + { + name: "Basic Base58 Check Decoding (Invalid Checksum Leading Zeros 2 With Single Version Byte)", + input: "11111111111111111111114oLvT2", + expectedOutput: "Invalid Checksum.", + recipeConfig: [ + { + "op": "From Base58Check", + "args": [1] + } + ], + }, + { + name: "Basic Base58 Check Decoding Gibberish", + input: "A9CfWPWwyc4JC4ATfv4ajp9aGunQ375hLHQ8gcY", + expectedOutput: "Invalid Checksum.", + recipeConfig: [ + { + "op": "From Base58Check", + "args": [1] + } + ], + }, + { + name: "Basic Base58 Check Decoding (Invalid Checksum With one Byte Version)", + input: "1111111111111111111114oLvT3", + expectedOutput: "Invalid Checksum.", + recipeConfig: [ + { + "op": "From Base58Check", + "args": [1] + } + ], + }, + { + name: "Basic Base58 Check Decoding (Version Length 1)", + input: "1111111111111111111114oLvT2", + expectedOutput: "{\n \"version\": \"00\",\n \"checksum\": \"94a00911\",\n \"data\": \"0000000000000000000000000000000000000000\"\n}", + recipeConfig: [ + { + "op": "From Base58Check", + "args": [1] + } + ], + }, + { + name: "To Base58 Check Basic", + input: "0000000000000000000000000000000000000000", + expectedOutput: "1111111111111111111114oLvT2", + recipeConfig: [ + { + "op": "To Base58Check", + "args": [{ "option": "Hex", "string": "00" }] + } + ] + }, + { + name: "To and From Base58 Check", + input: "0000000000000000000000000000000000000000", + expectedOutput: "{\n \"version\": \"00\",\n \"checksum\": \"94a00911\",\n \"data\": \"0000000000000000000000000000000000000000\"\n}", + recipeConfig: [ + { + "op": "To Base58Check", + "args": [{ "option": "Hex", "string": "00" }] + }, + { + "op": "From Base58Check", + "args": [1] + } + ] + + }, + { + name: "To and From Base58 Check (Technically wrong version byte length", + input: "0000000000000000000000000000000000000000", + expectedOutput: "{\n \"version\": \"0000000000\",\n \"checksum\": \"94a00911\",\n \"data\": \"00000000000000000000000000000000\"\n}", + recipeConfig: [ + { + "op": "To Base58Check", + "args": [{ "option": "Hex", "string": "00" }] + }, + { + "op": "From Base58Check", + "args": [5] + } + ] + + } + +]); diff --git a/tests/operations/tests/ChangeExtendedKeyVersion.mjs b/tests/operations/tests/ChangeExtendedKeyVersion.mjs new file mode 100644 index 00000000..761b8067 --- /dev/null +++ b/tests/operations/tests/ChangeExtendedKeyVersion.mjs @@ -0,0 +1,67 @@ +/** + * 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"], + }, + ], + }, + { + name: "Change Extended Key Version (bad cheksums))", + input: "zprvAWgYBBk7JR8GkFBvcbtyjkviqhbXpwsiQwVVHmmePfBQH6MC2gna9L64DcoQZAS2tsGhZkTg9xTbAd7LdaACX5J9kyPM2qTC8kE9XXGNzuz", + expectedOutput: "Error in deserializing key. Error is: Invalid checksum.", + recipeConfig: [ + { + "op": "Change Extended Key Version", + "args": ["xprv"], + }, + ], + } +]); diff --git a/tests/operations/tests/DecryptKeyStoreFile.mjs b/tests/operations/tests/DecryptKeyStoreFile.mjs new file mode 100644 index 00000000..6876aa4b --- /dev/null +++ b/tests/operations/tests/DecryptKeyStoreFile.mjs @@ -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, "secp256k1", "Public Key Only"] + }, + { + "op": "Public Key To ETH Style Address", + "args": [] + } + ] + + } + +]); diff --git a/tests/operations/tests/DeserializeExtendedKey.mjs b/tests/operations/tests/DeserializeExtendedKey.mjs new file mode 100644 index 00000000..1b678338 --- /dev/null +++ b/tests/operations/tests/DeserializeExtendedKey.mjs @@ -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] + }, + ], + }, + +]); diff --git a/tests/operations/tests/ETHTRXConversion.mjs b/tests/operations/tests/ETHTRXConversion.mjs new file mode 100644 index 00000000..de9e9cdf --- /dev/null +++ b/tests/operations/tests/ETHTRXConversion.mjs @@ -0,0 +1,96 @@ +/** + * Public Key to TRX 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: "ETH To TRX", + input: "0xAb495f468B0bF116860E76be9f26339b2CbC33c3", + expectedOutput: "TRatSrEqUKLK4eNWL8NSrvEbpeht5takWd", + recipeConfig: [ + { + "op": "ETH / TRX Conversion", + "args": ["ETH->TRX"] + }, + ], + + }, + { + name: "ETH To TRX (Invalid ETH)", + input: "0xAb495f468B0bF116860E76be9f26339b2CbC33c3x", + expectedOutput: "Invalid ETH address. ETH addresses should have 20 bytes (40 characters) prefaced by 0x.", + recipeConfig: [ + { + "op": "ETH / TRX Conversion", + "args": ["ETH->TRX"] + }, + ], + + }, + { + name: "ETH To TRX (Blank Input)", + input: "", + expectedOutput: "", + recipeConfig: [ + { + "op": "ETH / TRX Conversion", + "args": ["ETH->TRX"] + }, + ], + + }, + { + name: "TRX to ETH", + input: "TRatSrEqUKLK4eNWL8NSrvEbpeht5takWd", + expectedOutput: "0xAb495f468B0bF116860E76be9f26339b2CbC33c3", + recipeConfig: [ + { + "op": "ETH / TRX Conversion", + "args": ["TRX->ETH"] + }, + ], + + }, + { + name: "TRX to ETH (Invalid TRX by Length)", + input: "TRatSrEqUKLK4eNWL8NSrvEbpeht5takWde", + expectedOutput: "Invalid TRX Address. Checksum failed.", + recipeConfig: [ + { + "op": "ETH / TRX Conversion", + "args": ["TRX->ETH"] + }, + ], + + }, + { + name: "TRX to ETH (Invalid TRX by Character Substitution)", + input: "TRatSrEqUKLK4eNLL8NSrvEbpeht5takWd", + expectedOutput: "Invalid TRX Address. Checksum failed.", + recipeConfig: [ + { + "op": "ETH / TRX Conversion", + "args": ["TRX->ETH"] + }, + ], + + }, + { + name: "TRX to ETH With Checksum", + input: "TF8xufByTvQmf5gssBzosfmCFoJyJpzZtj", + expectedOutput: "0x38b10632a1F3B4363cF1852D12ff492808aA3675", + recipeConfig: [ + { + "op": "ETH / TRX Conversion", + "args": ["TRX->ETH"] + }, + ] + + }, +]); diff --git a/tests/operations/tests/ExtractDoubleSHA.mjs b/tests/operations/tests/ExtractDoubleSHA.mjs new file mode 100644 index 00000000..ab708a4d --- /dev/null +++ b/tests/operations/tests/ExtractDoubleSHA.mjs @@ -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": [] + }, + ], + } +]); diff --git a/tests/operations/tests/ExtractSeedphrases.mjs b/tests/operations/tests/ExtractSeedphrases.mjs new file mode 100644 index 00000000..ac52da79 --- /dev/null +++ b/tests/operations/tests/ExtractSeedphrases.mjs @@ -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"] + }, + ], + } + +]); diff --git a/tests/operations/tests/ExtractSegwitArtifacts.mjs b/tests/operations/tests/ExtractSegwitArtifacts.mjs new file mode 100644 index 00000000..f6a9829d --- /dev/null +++ b/tests/operations/tests/ExtractSegwitArtifacts.mjs @@ -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": [] + }, + ], + }, +]); diff --git a/tests/operations/tests/KeyToExtendedKey.mjs b/tests/operations/tests/KeyToExtendedKey.mjs new file mode 100644 index 00000000..7f50ece9 --- /dev/null +++ b/tests/operations/tests/KeyToExtendedKey.mjs @@ -0,0 +1,131 @@ +/** + * Key To Extended Key Tests. + * + * @author dgoldenberg [virtualcurrency@mitre.org] + * @copyright MITRE 2023 + * @license Apache-2.0 + */ + +import TestRegister from "../../lib/TestRegister.mjs"; + + +TestRegister.addTests([ + { + name: "Key To Extended Key (Basic, XPRV)", + input: "f55acd736ff0f80f2cdc56ab5ac1e4d4ce92e1f46d137a282a61e706681df9c5", + expectedOutput: "xprv9s21ZrQH143K4bXtdSLbsWGSfAok775A1bF3YwPeHe8ePa7QwD5V4kK1RmdZV2M6TKVfszKt19UaHfyqdQ2MZ8dv2t7G2Tvtxnef7Pxu8Qu", + recipeConfig: [ + { + "op": "Key To Extended Key", + "args": [ + { + "option": "Hex", + "string": "fedf6c5ebcc2fc7b66291e55501a005886128bf97aeced3a91478a0c44f54dbe" + }, + { + "option": "Hex", + "string": "00000000" + }, + { + "option": "Number", + "string": "0" + }, + "xprv", + { + "option": "Number", + "string": "0" + }, + + ] + } + ], + }, + { + name: "Key To Extended Key (YPRV Same Data)", + input: "f55acd736ff0f80f2cdc56ab5ac1e4d4ce92e1f46d137a282a61e706681df9c5", + expectedOutput: "yprvABrGsX5C9janutj1To8E5bMwq8xC3j4evhmGLLHXfeWXSfveBsF3goy9Syb9Uw11rxcUdTvSToq8AxbQM6SNMNKWuDogcNkPEWiJVuszXFR", + recipeConfig: [ + { + "op": "Key To Extended Key", + "args": [ + { + "option": "Hex", + "string": "fedf6c5ebcc2fc7b66291e55501a005886128bf97aeced3a91478a0c44f54dbe" + }, + { + "option": "Hex", + "string": "00000000" + }, + { + "option": "Number", + "string": "0" + }, + "yprv", + { + "option": "Number", + "string": "0" + } + ] + } + ], + }, + { + name: "Key To Extended Key (Public Key With Private Version), Should Throw Error.", + input: "02233f618dad285dc8b03eb7bf9a59dec039b7c1b2433dabdca636e17e10890e85", + expectedOutput: "Error: Mis-Match between version and key type. Public Key is entered, but a private version is selected.", + recipeConfig: [ + { + "op": "Key To Extended Key", + "args": [ + { + "option": "Hex", + "string": "cda6685894f356bdf1bd6af6cb7e3961e550fb9c9b2c82d9ebfd89f48c2afea6" + }, + { + "option": "Hex", + "string": "00000000" + }, + { + "option": "Number", + "string": "0" + }, + "xprv", + { + "option": "Number", + "string": "0" + } + ] + } + ], + }, + { + name: "Key To Extended Key (Private Key With Public Version), Should Throw Error.", + input: "f55acd736ff0f80f2cdc56ab5ac1e4d4ce92e1f46d137a282a61e706681df9c5", + expectedOutput: "Error: Mis-Match between version and key type. Private Key is entered, but a public version is selected.", + recipeConfig: [ + { + "op": "Key To Extended Key", + "args": [ + { + "option": "Hex", + "string": "fedf6c5ebcc2fc7b66291e55501a005886128bf97aeced3a91478a0c44f54dbe" + }, + { + "option": "Hex", + "string": "00000000" + }, + { + "option": "Number", + "string": "0" + }, + "xpub", + { + "option": "Number", + "string": "0" + } + ] + } + ], + }, + +]); diff --git a/tests/operations/tests/PrivateECKeyToPublic.mjs b/tests/operations/tests/PrivateECKeyToPublic.mjs new file mode 100644 index 00000000..37ea88ae --- /dev/null +++ b/tests/operations/tests/PrivateECKeyToPublic.mjs @@ -0,0 +1,124 @@ +/** + * 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, "secp256k1", "Public Key Only"] + }, + ], + }, + { + name: "Private EC Key to Public (Uncompressed)", + input: "5E2A8FDE9F861056607208F512287CFBD634E124044EE23EBF7289E8E7B3822E", + expectedOutput: "046cf115be1fc5f54585965817c735d74bdae03d9665a43704a9bdbf8a1c6b1e401f8a5888434ba6abc1967c036cc28903283d0f43b53fee63a3fb0b416019892b", + recipeConfig: [ + { + "op": "Private EC Key to Public Key", + "args": [false, "secp256k1", "Public Key Only"] + }, + ], + }, + { + 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, "secp256k1", "Public Key Only"] + }, + ], + }, + { + 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, "secp256k1", "Public Key Only"] + }, + ], + }, + { + 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": [false, "secp256k1", "Public Key Only"] + }, + ], + }, + { + name: "Exodus Solana Private Key To Address Example (Full 64 Private Key)", + input: "S3Y5GEsCMcU15pAGb6fPAb6nG1nk6SA4nSuVLxEH9xVQdtdnDBDxE8kYkAvdnrF7xK5UTVTDeN3zKT3ZLeeBygc", + expectedOutput: "3LzenrN4cveQZbodWxgepAHRyr7Xs53FwMQsuiD13jMS", + recipeConfig: [ + { "op": "From Base58", "args": ["123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz", true] }, + { "op": "Drop bytes", "args": [-32, 32, false] }, + { "op": "To Hex", "args": ["None", 32] }, + { "op": "Private EC Key to Public Key", "args": [true, "ed25519", "Public Key Only"] }, + { "op": "From Hex", "args": ["Auto"] }, + { "op": "To Base58", "args": ["123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"] } + ] + }, + { + name: "Exodus Solana Private Key To Address Example Private Key to Full Solana Private Key", + input: "1598c4d3c92419359a1901eb9ee4f7836e999959737d002c3b2128aa592bd364", + expectedOutput: "S3Y5GEsCMcU15pAGb6fPAb6nG1nk6SA4nSuVLxEH9xVQdtdnDBDxE8kYkAvdnrF7xK5UTVTDeN3zKT3ZLeeBygc", + // To regenerate in Exodus use seedphrase: announce mesh cake need drink better dwarf profit cute lady want foster + // Should be first Solana address in Exodus. + recipeConfig: [ + { "op": "Private EC Key to Public Key", "args": [true, "ed25519", "Private,Public"] }, + { "op": "Find / Replace", "args": [{ "option": "Regex", "string": "," }, "", true, false, true, false] }, + { "op": "From Hex", "args": ["Auto"] }, + { "op": "To Base58", "args": ["123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"] } + ] + }, + { + name: "Private EC Key to Public Wrong Curve", + input: "5E2A8FDE9F861056607208F512287CFBD634E124044EE23EBF7289E8E7B3822E", + expectedOutput: "Curve must be one of the following: secp256k1, ed25519.", + recipeConfig: [ + { + "op": "Private EC Key to Public Key", + "args": [false, "secp256k2", "Public Key Only"] + }, + ], + }, + { + name: "Private EC Key to Public Wrong Option", + input: "5E2A8FDE9F861056607208F512287CFBD634E124044EE23EBF7289E8E7B3822E", + expectedOutput: "Output Option must be one of the following: Public Key Only, Private,Public, Public,Private.", + recipeConfig: [ + { + "op": "Private EC Key to Public Key", + "args": [false, "secp256k1", "Public Key Onl"] + }, + ], + }, +]); diff --git a/tests/operations/tests/PrivateExtendedKeyToPublic.mjs b/tests/operations/tests/PrivateExtendedKeyToPublic.mjs new file mode 100644 index 00000000..203cb669 --- /dev/null +++ b/tests/operations/tests/PrivateExtendedKeyToPublic.mjs @@ -0,0 +1,69 @@ +/** + * Private Extended Key to Public Version Extended Key Tests. + * + * @author dgoldenberg [virtualcurrency@mitre.org] + * @copyright MITRE 2023 + * @license Apache-2.0 + */ + +import TestRegister from "../../lib/TestRegister.mjs"; + + +TestRegister.addTests([ + { + name: "Private Extended Key To Public (xprv)", + input: "xprv9yoUuSwPzquxGY9ZsqizskpbKBbthhTpj5w4k2o8cxoFKigDAhUV3GCHtKL4CFuUCHvwGt9bo1DRnavubpiiS2wJ9AN9bbnipT95qKyda6r", + expectedOutput: "xpub6CnqJxUHqDUFV2E2ysG1EtmKsDSP7ABg6JrfYRCkBJLECX1MiEnjb4WmjYnUvYjzvnrygmcVMH2vSuZMNUWozhrDpqr8fpEZfxAune2FYPX", + recipeConfig: [ + { + "op": "Private Extended Key To Public", + "args": [] + } + ] + }, + { + name: "Private Extended Key To Public (yprv)", + input: "yprvAJEHhwPWiVEbr18CDkhzPRm3ywbJCCuVV7Q8UzLtE4t75hajmN7DvjeS3BWbQGPdAANzyPmhUZitHEqyLX8KoBBnRqitj2Kvisw6dgmVwBH", + expectedOutput: "ypub6XDe7SvQYrnu4VCfKnEzkZhnXyRnbfdLrLKjHNkVnQR5xVutJuRUUXxutSgRgjRaJKHKHW1WXLqCmbgQgapdwsx98NnRDXwhZFkavU6ojct", + recipeConfig: [ + { + "op": "Private Extended Key To Public", + "args": [] + } + ] + }, + { + name: "Private Extended Key To Public (zprv)", + input: "zprvAdjAKuQajRu4ptst29FzkcB18fyZuHCKJhe51m38ejMcjW5P4frUffXMYfwLiTs4UrFD5m41So3VhAWGyrb4YV4gFRwYuLom8psLoW252Xd", + expectedOutput: "zpub6riWjQwUZoTN3NxM8Ao17k7jghp4JjvAfvZfp9SkD4tbcJQXcDAjDTqqPxywFFvD1BHiMMKvW3EjwMvuc7k2YcQbF1v2R42gxac4k3qq3sc", + recipeConfig: [ + { + "op": "Private Extended Key To Public", + "args": [] + } + ] + }, + { + name: "Private Extended Key To Public (invalid checksum)", + input: "zprvAdjAKuQajRu4ptst29FzkcB18fyZuHCKJhe51m38ejMcjW5P4frUffXMYfwLiTs4UrFD5m41So3VhAWGyrb4YV4gFRwYuLom8psLoW252Xe", + expectedOutput: "Error in deserializing key. Error is: Invalid checksum.", + recipeConfig: [ + { + "op": "Private Extended Key To Public", + "args": [] + } + ] + }, + { + name: "Private Extended Key To Public (blank)", + input: "", + expectedOutput: "", + recipeConfig: [ + { + "op": "Private Extended Key To Public", + "args": [] + } + ] + } + +]); diff --git a/tests/operations/tests/PrivateKeyToWIF.mjs b/tests/operations/tests/PrivateKeyToWIF.mjs new file mode 100644 index 00000000..3e78dbbd --- /dev/null +++ b/tests/operations/tests/PrivateKeyToWIF.mjs @@ -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] + } + ], + } + +]); diff --git a/tests/operations/tests/PublicKeyToETHStyleAddress.mjs b/tests/operations/tests/PublicKeyToETHStyleAddress.mjs new file mode 100644 index 00000000..f0397831 --- /dev/null +++ b/tests/operations/tests/PublicKeyToETHStyleAddress.mjs @@ -0,0 +1,146 @@ +/** + * 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 Invalid Uncompressed Key", + input: "027d3e5107b46421c3ddf7292fcbe1f5805952279926c325d119f6ddfed1e5e7ea9a9f5dceadc57b897cb286479450b66a6422f8f270bcd2a61ab4eea800911956", + 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: 02", + 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: Blank Input", + input: "", + expectedOutput: "", + recipeConfig: [ + { + "op": "Public Key To ETH Style Address", + "args": [] + }, + ], + + }, + { + name: "Public Key to ETH Style Address: Invalid Compressed Key", + input: "059d491d3f5a2a79b422b76b8115b175b38e42c6103b1a84c88fb6221fe9072bbe", + 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 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": [] + }, + ], + + }, + +]); diff --git a/tests/operations/tests/PublicKeyToP2PKHAddress.mjs b/tests/operations/tests/PublicKeyToP2PKHAddress.mjs new file mode 100644 index 00000000..17be46bd --- /dev/null +++ b/tests/operations/tests/PublicKeyToP2PKHAddress.mjs @@ -0,0 +1,282 @@ +/** + * Public Key To Bitcoin-Like 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 (Empty String)", + input: "", + expectedOutput: "", + recipeConfig: [ + { + "op": "Public Key To Bitcoin-Like Address", + "args": ["BTC", "P2PKH (V1 BTC Addresses)"] + }, + ], + }, + { + name: "Public Key To Address: P2PKH (1)", + input: "03ebf60a619da2fbc6239089ca0a93878ea53baa3d22188cacad4033b103237ae9", + expectedOutput: "1MwwHqDj1FAyABeqPeiTTvJQCoCorcuFyP", + recipeConfig: [ + { + "op": "Public Key To Bitcoin-Like Address", + "args": ["BTC", "P2PKH (V1 BTC Addresses)"] + }, + ], + }, + { + name: "Public Key To Address: P2PKH (2)", + input: "021dc4eb14b93dfbbbe4578293d07b6ee443ca025d89fd43a657ee3fd8c81d03f6", + expectedOutput: "18wUwr4Jvor6LG1mvQcfEp1Lx51dYAXZX1", + recipeConfig: [ + { + "op": "Public Key To Bitcoin-Like Address", + "args": ["BTC", "P2PKH (V1 BTC Addresses)"] + }, + ], + }, + { + name: "Public Key To Address: P2PKH LTC", + input: "037b39f764a10f31bfd47038738ca27bffeefce1fe4ccbfb9343fcb69d9363b27b", + expectedOutput: "LPTR2TBuF8vbwWaJdNeCAQemW4SC7q7zJP", + recipeConfig: [ + { + "op": "Public Key To Bitcoin-Like Address", + "args": ["LTC", "P2PKH (V1 BTC Addresses)"] + }, + ], + }, + { + name: "Public Key To Address: P2PKH (Long)", + input: "04219A19E157B5FEDDF7EBDD3C7A58D7AB4F6565E84226691B6A5F80BBCE8E0100B49D6AB503CA4B701626E941EB8D2460F154992D7AD4EC671CF1CFB8C1DE8164", + expectedOutput: "1BgRqTW8RMmcTRXHymTCVJsn5NVk9U8L9q", + recipeConfig: [ + { + "op": "Public Key To Bitcoin-Like Address", + "args": ["BTC", "P2PKH (V1 BTC Addresses)"] + }, + ], + }, + { + name: "Public Key To Address: P2SH-P2WPKH (1)", + input: "03a24ca9f13f6bcbc15615f71504be75566120b9bc7072e171756233162c726432", + expectedOutput: "31vhdy8RGhSYZRRGZfqvZHGzVtpcua4cQW", + recipeConfig: [ + { + "op": "Public Key To Bitcoin-Like 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 Bitcoin-Like 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 Bitcoin-Like 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 Bitcoin-Like Address", + "args": ["BTC", "Segwit (P2WPKH bc1 Addresses)"] + }, + ], + }, + { + name: "Public Key To Address: P2WPKH (2)", + input: "03bc32bdc5dc96c9fb56e2481fefd321ebe9e17a807bbb337dea1df5e68b1f0756", + expectedOutput: "bc1qrjluhfu5qr2780zlvcx3kquckpvuamwqp2sjle", + recipeConfig: [ + { + "op": "Public Key To Bitcoin-Like Address", + "args": ["BTC", "Segwit (P2WPKH bc1 Addresses)"] + }, + ], + }, + { + name: "Public Key To Address: P2WPKH LTC", + input: "026a532c31184b94edf540ad60c3cc208342be4f51cc764a373a420731dd198a59", + expectedOutput: "ltc1qj587punda8h0r4m83k794xseqlnl3az4ktu2zp", + recipeConfig: [ + { + "op": "Public Key To Bitcoin-Like Address", + "args": ["LTC", "Segwit (P2WPKH bc1 Addresses)"] + }, + ], + }, + { + name: "Public Key To Address: (Testnet)", + input: "02aa6438e78b18a503f4466672fd04e31aaeec3ed1c3e0e1f19654776f0f0dc1b2", + expectedOutput: "mmuoeJDuuzeuaii1V6tPK3L5YjaJwjPqUM", + recipeConfig: [ + { + "op": "Public Key To Bitcoin-Like 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 Bitcoin-Like 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 Bitcoin-Like 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 Bitcoin-Like 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 Bitcoin-Like Address", + "args": ["BTC", "P2PKH (V1 BTC Addresses)"] + } + ], + }, + { + name: "Public Key To Address: P2TR (From WIF Key)", + input: "L5R7GAGwrBLcpK4jK1CLDL7VjPifYZZeS1NcixKvrPxXySJWEK9h", + expectedOutput: "bc1ph6py5lduje5urxkqewpaxj8cxlmmc9uxr386e0jgvp9vzsup54dqxpxsn7", + recipeConfig: [ + { + "op": "From WIF Format", + "args": [] + }, + { + "op": "Private EC Key to Public Key", + "args": [true, "secp256k1", "Public Key Only"] + }, + { + "op": "Public Key To Bitcoin-Like Address", + "args": ["BTC", "Taproot (P2TR bc1p Addresses)"] + } + ] + }, + { + name: "Public Key To Address: P2TR (BIP86 Test Vectors 1)", + input: "xpub6H3W6JmYJXN49h5TfcVjLC3onS6uPeUTTJoVvRC8oG9vsTn2J8LwigLzq5tHbrwAzH9DGo6ThGUdWsqce8dGfwHVBxSbixjDADGGdzF7t2B", + expectedOutput: "bc1p5cyxnuxmeuwuvkwfem96lqzszd02n6xdcjrs20cac6yqjjwudpxqkedrcr", + recipeConfig: [ + { + "op": "Deserialize Extended Key", + "args": [true] + }, + { + "op": "JPath expression", + "args": ["$['masterkey']", "\\n"] + }, + { + "op": "Find / Replace", + "args": [{ "option": "Regex", "string": "\"" }, "", true, false, true, false] + }, + { + "op": "Public Key To Bitcoin-Like Address", + "args": ["BTC", "Taproot (P2TR bc1p Addresses)"] + } + ] + }, + { + name: "Public Key To Address: P2TR (BIP86 Test Vectors 2)", + input: "xpub6H3W6JmYJXN4CCKUSnriaiQRCZmG6aq4sCMDqTu1ACyngw7HShf59hAxYjXgKDuuHThVEUzdHrc3aXCr9kfvQvZPit5dnD3K9xVRBzjK3rX", + expectedOutput: "bc1p4qhjn9zdvkux4e44uhx8tc55attvtyu358kutcqkudyccelu0was9fqzwh", + recipeConfig: [ + { + "op": "Deserialize Extended Key", + "args": [true] + }, + { + "op": "JPath expression", + "args": ["$['masterkey']", "\\n"] + }, + { + "op": "Find / Replace", + "args": [{ "option": "Regex", "string": "\"" }, "", true, false, true, false] + }, + { + "op": "Public Key To Bitcoin-Like Address", + "args": ["BTC", "Taproot (P2TR bc1p Addresses)"] + } + ] + }, + { + name: "Public Key To Address: P2TR (BIP86 Test Vectors 3)", + input: "xpub6GL8SnQwRCGDhB59LEz9HMyM6sRYoByXBzXK3iEKWgCz8XrZNHUzd9L3AUBELW5NzA7dEFvMas1F84TuPH3xqdUA5tumaGWFgihJzWytXe3", + expectedOutput: "bc1p3qkhfews2uk44qtvauqyr2ttdsw7svhkl9nkm9s9c3x4ax5h60wqwruhk7", + recipeConfig: [ + { + "op": "Deserialize Extended Key", + "args": [true] + }, + { + "op": "JPath expression", + "args": ["$['masterkey']", "\\n"] + }, + { + "op": "Find / Replace", + "args": [{ "option": "Regex", "string": "\"" }, "", true, false, true, false] + }, + { + "op": "Public Key To Bitcoin-Like Address", + "args": ["BTC", "Taproot (P2TR bc1p Addresses)"] + } + ] + }, +]); diff --git a/tests/operations/tests/PublicKeyToTRXStyleAddress.mjs b/tests/operations/tests/PublicKeyToTRXStyleAddress.mjs new file mode 100644 index 00000000..334e62fd --- /dev/null +++ b/tests/operations/tests/PublicKeyToTRXStyleAddress.mjs @@ -0,0 +1,144 @@ +/** + * Public Key to TRX 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 TRX Style Address, Invalid Uncompressed.", + input: "05187ac6bc2723630c936e363b826de17dac62382e3bbfabf306ad5f55cc79538783889fe32946b52092dad24c56893d522413d67e62b28f6c54f14821367a9edc", + 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: 05", + recipeConfig: [ + { + "op": "Public Key To TRX Style Address", + "args": [] + }, + ], + }, + { + name: "Public Key To TRX Style Address", + input: "04187ac6bc2723630c936e363b826de17dac62382e3bbfabf306ad5f55cc79538783889fe32946b52092dad24c56893d522413d67e62b28f6c54f14821367a9edc", + expectedOutput: "THV2shRZn4cam7aQreAg9aixfk2sTcho6r", + recipeConfig: [ + { + "op": "Public Key To TRX Style Address", + "args": [] + }, + ], + + }, + { + name: "Public Key To TRX Style Address Compressed Key", + input: "02d1b5855d3f99c4449eb7af576bec1b9bc0bf0769446820686d2de5c47c13b1a0", + expectedOutput: "TQS4NjvDN4TxcZd8LwD1eAKv9y4vSVkDW2", + recipeConfig: [ + { + "op": "Public Key To TRX Style Address", + "args": [] + }, + ], + + }, + { + name: "Public Key To TRX Style Address Invalid Compressed Key (Length)", + input: "02d1b5855d3f99c4449eb7af576bec1b9bc0bf0769446820686d2de5c47c13b1a0ff", + expectedOutput: "Invalid length. We want either 33, 65 (if bytes) or 66, 130 (if hex) but we got: 68", + recipeConfig: [ + { + "op": "Public Key To TRX Style Address", + "args": [] + }, + ], + + }, + { + name: "Public Key To TRX Style Address (Empty String)", + input: "", + expectedOutput: "", + recipeConfig: [ + { + "op": "Public Key To TRX Style Address", + "args": [] + }, + ], + + }, + { + name: "Public Key To TRX Style Address Invalid Compressed Key", + input: "06d1b5855d3f99c4449eb7af576bec1b9bc0bf0769446820686d2de5c47c13b1a0", + 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: 06", + recipeConfig: [ + { + "op": "Public Key To TRX Style Address", + "args": [] + }, + ], + + }, + { + name: "Public Key to ETH Style Address: Compressed Key 2", + input: "03a85e8f6fc71898b5c3347decd2c0bba8abb99393c8358fcf0bca72e4c7d68514", + expectedOutput: "TXBP2ebjZsnDEL9X5xCZSZZ6FC3Vppccv4", + recipeConfig: [ + { + "op": "Public Key To TRX Style Address", + "args": [] + }, + ], + + }, + { + name: "Public Key to ETH Style Address: Compressed Key 2 (From Hex)", + input: "03a85e8f6fc71898b5c3347decd2c0bba8abb99393c8358fcf0bca72e4c7d68514", + expectedOutput: "TXBP2ebjZsnDEL9X5xCZSZZ6FC3Vppccv4", + recipeConfig: [ + { + "op": "From Hex", + "args": ["Auto"] + }, + { + "op": "Public Key To TRX Style Address", + "args": [], + }, + ], + + }, + { + name: "Public Key to TRX Style Address: Compressed Key (From Hex)", + input: "02d1b5855d3f99c4449eb7af576bec1b9bc0bf0769446820686d2de5c47c13b1a0", + expectedOutput: "TQS4NjvDN4TxcZd8LwD1eAKv9y4vSVkDW2", + recipeConfig: [ + { + "op": "From Hex", + "args": ["Auto"] + }, + { + "op": "Public Key To TRX Style Address", + "args": [] + }, + ], + + }, + { + name: "Public Key To TRX Style Address (From Hex)", + input: "04187ac6bc2723630c936e363b826de17dac62382e3bbfabf306ad5f55cc79538783889fe32946b52092dad24c56893d522413d67e62b28f6c54f14821367a9edc", + expectedOutput: "THV2shRZn4cam7aQreAg9aixfk2sTcho6r", + recipeConfig: [ + { + "op": "From Hex", + "args": ["Auto"] + }, + { + "op": "Public Key To TRX Style Address", + "args": [] + }, + ], + + }, +]); diff --git a/tests/operations/tests/SeedToAddress.mjs b/tests/operations/tests/SeedToAddress.mjs new file mode 100644 index 00000000..61e09be6 --- /dev/null +++ b/tests/operations/tests/SeedToAddress.mjs @@ -0,0 +1,411 @@ +/** + * Key To Extended 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 BIP44 Bitcoin", + input: "monkey envelope scatter sword confirm hidden tool scale stay local guilt number", + expectedOutput: "1GViQ3SAjSy6bPfYy95ovP5KgXcBBnxVpm", + recipeConfig: [ + { + "op": "Seedphrase To Seed", + "args": + [ + "bip39", + { + "option": "UTF8", + "string": "" + } + ] + }, + { + "op": "Seed To Master Key", + "args": + [ + "xprv" + ] + }, + { + "op": "BIP32Derive", + "args": + [ + "44'/0'/0'/0/0" + ] + }, + { + "op": "Deserialize Extended Key", + "args": [true] + }, + { + "op": "JPath expression", + "args": ["$['masterkey']", "\\n"] + }, + { + "op": "Find / Replace", + "args": + [ + { + "option": "Simple string", "string": "\"" + }, + "", + true, + false, + true, + false + ] + }, + { + "op": "Drop bytes", + "args": [0, 2, false] + }, + { + "op": "Private EC Key to Public Key", + "args": [true, "secp256k1", "Public Key Only"] + }, + { + "op": "Public Key To Bitcoin-Like Address", + "args": ["BTC", "P2PKH (V1 BTC Addresses)"] + } + ], + }, + { + name: "Seed To BIP49 Bitcoin", + input: "monkey envelope scatter sword confirm hidden tool scale stay local guilt number", + expectedOutput: "34pwQXUVjMcbC4DFVeAopUiCMuZzrbnmbK", + recipeConfig: [ + { + "op": "Seedphrase To Seed", + "args": + [ + "bip39", + { + "option": "UTF8", + "string": "" + } + ] + }, + { + "op": "Seed To Master Key", + "args": + [ + "xprv" + ] + }, + { + "op": "BIP32Derive", + "args": + [ + "49'/0'/0'/0/0" + ] + }, + { + "op": "Deserialize Extended Key", + "args": [true] + }, + { + "op": "JPath expression", + "args": ["$['masterkey']", "\\n"] + }, + { + "op": "Find / Replace", + "args": + [ + { + "option": "Simple string", "string": "\"" + }, + "", + true, + false, + true, + false + ] + }, + { + "op": "Drop bytes", + "args": [0, 2, false] + }, + { + "op": "Private EC Key to Public Key", + "args": [true, "secp256k1", "Public Key Only"] + }, + { + "op": "Public Key To Bitcoin-Like Address", + "args": ["BTC", "P2SH-P2PWPKH (Segwit Compatible V3 Addresses)"] + } + ], + }, + { + name: "Seed To BIP84 Bitcoin", + input: "monkey envelope scatter sword confirm hidden tool scale stay local guilt number", + expectedOutput: "bc1q3t8jjjxne03j5g6chg74wwgqeadaeyeess0u94", + recipeConfig: [ + { + "op": "Seedphrase To Seed", + "args": + [ + "bip39", + { + "option": "UTF8", + "string": "" + } + ] + }, + { + "op": "Seed To Master Key", + "args": + [ + "xprv" + ] + }, + { + "op": "BIP32Derive", + "args": + [ + "84'/0'/0'/0/0" + ] + }, + { + "op": "Deserialize Extended Key", + "args": [true] + }, + { + "op": "JPath expression", + "args": ["$['masterkey']", "\\n"] + }, + { + "op": "Find / Replace", + "args": + [ + { + "option": "Simple string", "string": "\"" + }, + "", + true, + false, + true, + false + ] + }, + { + "op": "Drop bytes", + "args": [0, 2, false] + }, + { + "op": "Private EC Key to Public Key", + "args": [true, "secp256k1", "Public Key Only"] + }, + { + "op": "Public Key To Bitcoin-Like Address", + "args": ["BTC", "Segwit (P2WPKH bc1 Addresses)"] + } + ], + }, + { + name: "Seed To BIP86 Bitcoin", + input: "monkey envelope scatter sword confirm hidden tool scale stay local guilt number", + expectedOutput: "bc1p6fr48xprdq45qjq6s3ymnt8rllrjz0jgysn46rxwudl5rhfgtqaq7255nd", + recipeConfig: [ + { + "op": "Seedphrase To Seed", + "args": + [ + "bip39", + { + "option": "UTF8", + "string": "" + } + ] + }, + { + "op": "Seed To Master Key", + "args": + [ + "xprv" + ] + }, + { + "op": "BIP32Derive", + "args": + [ + "86'/0'/0'/0/0" + ] + }, + { + "op": "Deserialize Extended Key", + "args": [true] + }, + { + "op": "JPath expression", + "args": ["$['masterkey']", "\\n"] + }, + { + "op": "Find / Replace", + "args": + [ + { + "option": "Simple string", "string": "\"" + }, + "", + true, + false, + true, + false + ] + }, + { + "op": "Drop bytes", + "args": [0, 2, false] + }, + { + "op": "Private EC Key to Public Key", + "args": [true, "secp256k1", "Public Key Only"] + }, + { + "op": "Public Key To Bitcoin-Like Address", + "args": ["BTC", "Taproot (P2TR bc1p Addresses)"] + } + ], + }, + { + name: "Seed To BIP44 ETH", + input: "monkey envelope scatter sword confirm hidden tool scale stay local guilt number", + expectedOutput: "0x370b9b86dbde4fd8587c27ce28d7ee39ed9ea4dc", + recipeConfig: [ + { + "op": "Seedphrase To Seed", + "args": + [ + "bip39", + { + "option": "UTF8", + "string": "" + } + ] + }, + { + "op": "Seed To Master Key", + "args": + [ + "xprv" + ] + }, + { + "op": "BIP32Derive", + "args": + [ + "44'/60'/0'/0/0" + ] + }, + { + "op": "Deserialize Extended Key", + "args": [true] + }, + { + "op": "JPath expression", + "args": ["$['masterkey']", "\\n"] + }, + { + "op": "Find / Replace", + "args": + [ + { + "option": "Simple string", "string": "\"" + }, + "", + true, + false, + true, + false + ] + }, + { + "op": "Drop bytes", + "args": [0, 2, false] + }, + { + "op": "Private EC Key to Public Key", + "args": [true, "secp256k1", "Public Key Only"] + }, + { + "op": "Public Key To ETH Style Address", + "args": [] + } + ], + }, + { + name: "Seed To BIP44 TRX", + input: "monkey envelope scatter sword confirm hidden tool scale stay local guilt number", + expectedOutput: "TK3N2jjVUArH2Gy7iM6UkArsUou4vHQMhz", + recipeConfig: [ + { + "op": "Seedphrase To Seed", + "args": + [ + "bip39", + { + "option": "UTF8", + "string": "" + } + ] + }, + { + "op": "Seed To Master Key", + "args": + [ + "xprv" + ] + }, + { + "op": "BIP32Derive", + "args": + [ + "44'/195'/0'/0/0" + ] + }, + { + "op": "Deserialize Extended Key", + "args": [true] + }, + { + "op": "JPath expression", + "args": ["$['masterkey']", "\\n"] + }, + { + "op": "Find / Replace", + "args": + [ + { + "option": "Simple string", "string": "\"" + }, + "", + true, + false, + true, + false + ] + }, + { + "op": "Drop bytes", + "args": [0, 2, false] + }, + { + "op": "Private EC Key to Public Key", + "args": [true, "secp256k1", "Public Key Only"] + }, + { + "op": "Public Key To TRX Style Address", + "args": [] + } + ], + } + + +]); diff --git a/tests/operations/tests/SeedToMPK.mjs b/tests/operations/tests/SeedToMPK.mjs new file mode 100644 index 00000000..9bf84c36 --- /dev/null +++ b/tests/operations/tests/SeedToMPK.mjs @@ -0,0 +1,105 @@ +/** + * 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"] + } + ], + }, + { + name: "Seed To Master Private Key (XPrv, short seed)", + input: "1ed5b6f0dcf88085add90fbb138d5e16f661d5b738f842232fce5980fc4592", + expectedOutput: "xprv9s21ZrQH143K3mvkwf3aiiCQ6mpTnAh5ZPvhHFLXSaSRxAg5uAuHVvaFVSGoyE7U3UJ3knZM12AvNcF9a9xoARWCkCF9MYXjyN1ZJUC6ssa", + recipeConfig: [ + { + "op": "Seed To Master Key", + "args": ["xprv"] + } + ], + }, + { + name: "Seed to Master Private Key (XPrv, decoded Chinese)", + input: "霍 里 攻 繁 混 注 杀 侦 具 涤 涤 手", + expectedOutput: "xprv9s21ZrQH143K3NGAQKJovUKk9ty8HFDxoK2A8kseKUEQnQA2xuS1UiXj67d7jPS153aBSeqbJpGC4etDSmAJrD2MuN3pncdjFNh85PEPwS2", + recipeConfig: [ + { + "op": "Normalise Unicode", + "args": ["NFKD"] + }, + { + "op": "To Hex", + "args": ["None", 0] + }, + { + "op": "Seed To Master Key", + "args": ["xprv"] + } + + ] + }, + { + name: "Seed to Master Private Key (XPrv, Non-Hex Dealing With Raw Bytes)", + input: "abc=_*~$%^$^*&^$%@#$^^%&*^&(YIU%^$#@", + expectedOutput: "xprv9s21ZrQH143K2zVX3HhaQJZPeunqrxitrCQh5ZaUzyv9uHtZu86AVigehGy2ZCfGcAmp7wJUSb4GMGsbH8qqJawZEz2mdUVV8q7VH2AqgH6", + recipeConfig: [ + { + "op": "Seed To Master Key", + "args": ["xprv"] + } + + ] + } + +]); diff --git a/tests/operations/tests/SeedphraseToSeed.mjs b/tests/operations/tests/SeedphraseToSeed.mjs new file mode 100644 index 00000000..3e768798 --- /dev/null +++ b/tests/operations/tests/SeedphraseToSeed.mjs @@ -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" }] + } + ], + } +]); diff --git a/tests/operations/tests/TypeCryptoArtifacts.mjs b/tests/operations/tests/TypeCryptoArtifacts.mjs new file mode 100644 index 00000000..9506fef4 --- /dev/null +++ b/tests/operations/tests/TypeCryptoArtifacts.mjs @@ -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": [] + }, + ], + } +]); diff --git a/tests/operations/tests/WIFToPrivateKey.mjs b/tests/operations/tests/WIFToPrivateKey.mjs new file mode 100644 index 00000000..b8abf4d8 --- /dev/null +++ b/tests/operations/tests/WIFToPrivateKey.mjs @@ -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": [] + }, + ], + } +]);