From f79ca6cf02b3be68f99a0a94e41e4ec059417824 Mon Sep 17 00:00:00 2001 From: David C Goldenberg Date: Wed, 26 Apr 2023 10:24:39 -0400 Subject: [PATCH] First Cryptochef branch commit. --- LICENSE | 1 - package-lock.json | 4 +- package.json | 2 +- src/core/config/Categories.json | 18 + src/core/lib/Bech32.mjs | 286 ++ src/core/lib/Bitcoin.mjs | 355 ++ src/core/lib/Extract.mjs | 32 + src/core/lib/Seedphrase.mjs | 147 + src/core/lib/SeedphraseWordLists.mjs | 4109 +++++++++++++++++ .../operations/ChangeExtendedKeyVersion.mjs | 62 + src/core/operations/DecryptKeystoreFile.mjs | 140 + .../operations/DeserializeExtendedKey.mjs | 89 + .../operations/ExtractDoubleShaArtifacts.mjs | 53 + src/core/operations/ExtractSeedPhrases.mjs | 87 + .../operations/ExtractSegwitAddresses.mjs | 54 + src/core/operations/PrivateECKeyToPublic.mjs | 84 + src/core/operations/PrivateKeyToWIF.mjs | 101 + .../operations/PublicKeyToP2PKHAddress.mjs | 145 + src/core/operations/SeedToMPK.mjs | 74 + src/core/operations/SeedphraseToSeed.mjs | 70 + src/core/operations/TypeCryptoArtifact.mjs | 224 + src/core/operations/WIFToPrivateKey.mjs | 77 + tests/operations/index.mjs | 13 + .../tests/ChangeExtendedKeyVersion.mjs | 57 + .../operations/tests/DecryptKeyStoreFile.mjs | 44 + .../tests/DeserializeExtendedKey.mjs | 35 + tests/operations/tests/ExtractDoubleSHA.mjs | 61 + tests/operations/tests/ExtractSeedphrases.mjs | 57 + .../tests/ExtractSegwitArtifacts.mjs | 27 + .../operations/tests/PrivateECKeyToPublic.mjs | 76 + tests/operations/tests/PrivateKeyToWIF.mjs | 88 + .../tests/PublicKeyToP2PKHAddress.mjs | 174 + tests/operations/tests/SeedToMPK.mjs | 47 + tests/operations/tests/SeedphraseToSeed.mjs | 56 + .../operations/tests/TypeCryptoArtifacts.mjs | 144 + tests/operations/tests/WIFToPrivateKey.mjs | 45 + 36 files changed, 7134 insertions(+), 4 deletions(-) create mode 100644 src/core/lib/Bech32.mjs create mode 100644 src/core/lib/Bitcoin.mjs create mode 100644 src/core/lib/Seedphrase.mjs create mode 100644 src/core/lib/SeedphraseWordLists.mjs create mode 100644 src/core/operations/ChangeExtendedKeyVersion.mjs create mode 100644 src/core/operations/DecryptKeystoreFile.mjs create mode 100644 src/core/operations/DeserializeExtendedKey.mjs create mode 100644 src/core/operations/ExtractDoubleShaArtifacts.mjs create mode 100644 src/core/operations/ExtractSeedPhrases.mjs create mode 100644 src/core/operations/ExtractSegwitAddresses.mjs create mode 100644 src/core/operations/PrivateECKeyToPublic.mjs create mode 100644 src/core/operations/PrivateKeyToWIF.mjs create mode 100644 src/core/operations/PublicKeyToP2PKHAddress.mjs create mode 100644 src/core/operations/SeedToMPK.mjs create mode 100644 src/core/operations/SeedphraseToSeed.mjs create mode 100644 src/core/operations/TypeCryptoArtifact.mjs create mode 100644 src/core/operations/WIFToPrivateKey.mjs create mode 100644 tests/operations/tests/ChangeExtendedKeyVersion.mjs create mode 100644 tests/operations/tests/DecryptKeyStoreFile.mjs create mode 100644 tests/operations/tests/DeserializeExtendedKey.mjs create mode 100644 tests/operations/tests/ExtractDoubleSHA.mjs create mode 100644 tests/operations/tests/ExtractSeedphrases.mjs create mode 100644 tests/operations/tests/ExtractSegwitArtifacts.mjs create mode 100644 tests/operations/tests/PrivateECKeyToPublic.mjs create mode 100644 tests/operations/tests/PrivateKeyToWIF.mjs create mode 100644 tests/operations/tests/PublicKeyToP2PKHAddress.mjs create mode 100644 tests/operations/tests/SeedToMPK.mjs create mode 100644 tests/operations/tests/SeedphraseToSeed.mjs create mode 100644 tests/operations/tests/TypeCryptoArtifacts.mjs create mode 100644 tests/operations/tests/WIFToPrivateKey.mjs 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 2a7fa973..3e851ebe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "cyberchef", - "version": "9.47.4", + "version": "9.47.5", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "cyberchef", - "version": "9.47.4", + "version": "9.47.5", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { diff --git a/package.json b/package.json index d02827cd..f15eae76 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cyberchef", - "version": "9.47.4", + "version": "9.47.5", "description": "The Cyber Swiss Army Knife for encryption, encoding, compression and data analysis.", "author": "n1474335 ", "homepage": "https://gchq.github.io/CyberChef", diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json index 7869893a..5898824d 100644 --- a/src/core/config/Categories.json +++ b/src/core/config/Categories.json @@ -295,6 +295,24 @@ "Sleep" ] }, + { + "name": "Cryptocurrency", + "ops": [ + "Extract Double SHA Artifacts", + "Extract Segwit Addresses", + "Extract Seedphrases", + "Deserialize Extended Key", + "Public Key To Cryptocurrency Address", + "To WIF Format", + "From WIF Format", + "Type Cryptocurrency Artifact", + "Private EC Key to Public Key", + "Seedphrase To Seed", + "Change Extended Key Version", + "Seed To Master Key", + "Decrypt Keystore File" + ] + }, { "name": "Extractors", "ops": [ diff --git a/src/core/lib/Bech32.mjs b/src/core/lib/Bech32.mjs new file mode 100644 index 00000000..9e4f9be5 --- /dev/null +++ b/src/core/lib/Bech32.mjs @@ -0,0 +1,286 @@ +/** + * Bech32 Encoding and Decoding resources (BIP0173 and BIP0350) + * + * @author dgoldenberg [virtualcurrency@mitre.org] + * @copyright MITRE 2023, geco 2019 + * @license MIT + */ + + +// ################################################ BEGIN SEGWIT DECODING FUNCTIONS ################################################# + +/** + * Javascript code below taken from: + * https://github.com/geco/bech32-js/blob/master/bech32-js.js + * Implements various segwit encoding / decoding functions. +*/ + +// Segwit alphabet +const ALPHABET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"; + +const ALPHABET_MAP = {}; +for (let z = 0; z < ALPHABET.length; z++) { + const x = ALPHABET.charAt(z); + ALPHABET_MAP[x] = z; +} + +/** + * Polynomial multiply step. + * Input value is viewed as 32 bit int. + * Constants taken from the BIP0173 wiki: https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki + * They are part of the BCH code generator polynomial. + * @param {string} pre + * @returns + */ +function polymodStep (pre) { + const b = pre >> 25; + return ((pre & 0x1FFFFFF) << 5) ^ + (-((b >> 0) & 1) & 0x3b6a57b2) ^ + (-((b >> 1) & 1) & 0x26508e6d) ^ + (-((b >> 2) & 1) & 0x1ea119fa) ^ + (-((b >> 3) & 1) & 0x3d4233dd) ^ + (-((b >> 4) & 1) & 0x2a1462b3); +} + +/** + * Checks the prefix of a string. + * @param {*} prefix + * @returns + */ +function prefixChk (prefix) { + let chk = 1; + for (let i = 0; i < prefix.length; ++i) { + const c = prefix.charCodeAt(i); + if (c < 33 || c > 126) return "KO"; + chk = polymodStep(chk) ^ (c >> 5); + } + chk = polymodStep(chk); + + for (let i = 0; i < prefix.length; ++i) { + const v = prefix.charCodeAt(i); + chk = polymodStep(chk) ^ (v & 0x1f); + } + return chk; +} + +/** + * Bech32 Checksum + * We check the entire string to see if its segwit encoded. + * Lengths and other constants taken from BIP 0173: https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki + * + * @param {*} str + * @returns + */ +function checkbech32 (str) { + const LIMIT = 90; + if (str.length < 8) return "KO"; + if (str.length > LIMIT) return "KO"; + + + const split = str.lastIndexOf("1"); + if (split === -1) return "KO"; + if (split === 0) return "KO"; + + const prefix = str.slice(0, split); + const wordChars = str.slice(split + 1); + if (wordChars.length < 6) return "KO"; + + let chk = prefixChk(prefix); + if (typeof chk === "string") return "KO"; + + const words = []; + for (let i = 0; i < wordChars.length; ++i) { + const c = wordChars.charAt(i); + const v = ALPHABET_MAP[c]; + if (v === undefined) return "KO"; + chk = polymodStep(chk) ^ v; + if (i + 6 >= wordChars.length) continue; + words.push(v); + } + // Second number is decimal representation of 0x2bc830a3 + // Useful as P2TR addresses are segwit encoded, with different final checksum. + // Taken from https://github.com/bitcoin/bips/blob/master/bip-0350.mediawiki + if (chk === 1 || chk === 734539939) { + return "OK"; + } else { + return "KO"; + } +} + +// ################################################ END SEGWIT DECODING FUNCTIONS ################################################### + +// ################################################ BEGIN MAIN CHECKSUM FUNCTIONS ################################################### + +// Segwit Checksum +/** + * Segwit Checksum + * @param {*} str + * @returns + */ +export function segwitChecksum(str) { + return (checkbech32(str) === "OK"); +} + +// ################################################ END MAIN CHECKSUM FUNCTIONS ##################################################### + + +// ################################################ BEGIN SEGWIT ENCODING FUNCTIONS ################################################# + +// Taken from https://github.com/sipa/bech32/blob/master/ref/javascript/bech32.js +// We use this to encode values into segwit encoding. +/** + * Expands the human readable part. + * @param {string} hrp + * @returns + */ +function hrpExpand (hrp) { + const ret = []; + let p; + for (p = 0; p < hrp.length; ++p) { + ret.push(hrp.charCodeAt(p) >> 5); + } + ret.push(0); + for (p = 0; p < hrp.length; ++p) { + ret.push(hrp.charCodeAt(p) & 31); + } + return ret; +} + +const encodings = { + BECH32: "bech32", + BECH32M: "bech32m", +}; + + +/** + * We get the encoding constant. + * Differentiates between Segwit and P2TR. + * Constants found in BIP0173: https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki + * Also BIP0350: https://github.com/bitcoin/bips/blob/master/bip-0350.mediawiki + * @param {string} enc + * @returns + */ +function getEncodingConst (enc) { + if (enc === encodings.BECH32) { + return 1; + } else if (enc === encodings.BECH32M) { + return 0x2bc830a3; + } else { + return null; + } +} + +// Constants for the BIP0173 BCH Generator polynomial. +const GENERATOR = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3]; + +/** + * Separate version of the polymod step. Taken from https://github.com/sipa/bech32/blob/master/ref/javascript/bech32.js + * Here its an array of values. + * @param {} values + * @returns + */ +function polymod (values) { + let chk = 1; + for (let p = 0; p < values.length; ++p) { + const top = chk >> 25; + chk = (chk & 0x1ffffff) << 5 ^ values[p]; + for (let i = 0; i < 5; ++i) { + if ((top >> i) & 1) { + chk ^= GENERATOR[i]; + } + } + } + return chk; +} + +/** + * Creates the Segwit checksum + * @param {string} hrp + * @param {string} data + * @param {string} enc + * @returns + */ +function createChecksum (hrp, data, enc) { + const values = hrpExpand(hrp).concat(data).concat([0, 0, 0, 0, 0, 0]); + const mod = polymod(values) ^ getEncodingConst(enc); + const ret = []; + for (let p = 0; p < 6; ++p) { + ret.push((mod >> 5 * (5 - p)) & 31); + } + return ret; +} + +/** + * Converts bits from base 5 to base 8 or back again as appropriate. + * @param {*} data + * @param {*} frombits + * @param {*} tobits + * @param {*} pad + * @returns + */ +function convertbits (data, frombits, tobits, pad) { + let acc = 0; + let bits = 0; + const ret = []; + const maxv = (1 << tobits) - 1; + for (let p = 0; p < data.length; ++p) { + const value = data[p]; + if (value < 0 || (value >> frombits) !== 0) { + return null; + } + acc = (acc << frombits) | value; + bits += frombits; + while (bits >= tobits) { + bits -= tobits; + ret.push((acc >> bits) & maxv); + } + } + if (pad) { + if (bits > 0) { + ret.push((acc << (tobits - bits)) & maxv); + } + } else if (bits >= frombits || ((acc << (tobits - bits)) & maxv)) { + return null; + } + return ret; +} + +/** + * Function to encode data into a segwit address. + * We take in the human readable part, the data, and whether its P2TR or Segwit. + * @param {string} hrp + * @param {string} data + * @param {string} enc + * @returns + */ +function segwitEncode (hrp, data, enc) { + const combined = data.concat(createChecksum(hrp, data, enc)); + let ret = hrp + "1"; + for (let p = 0; p < combined.length; ++p) { + ret += ALPHABET.charAt(combined[p]); + } + return ret; +} + +/** + * Turns the public key (as 'program') into the address. + * @param {*} hrp + * @param {*} version + * @param {*} program + * @returns + */ +export function encodeProgramToSegwit (hrp, version, program) { + let enc; + if (version > 0) { + enc = encodings.BECH32M; + } else { + enc = encodings.BECH32; + } + const convertedbits = convertbits(program, 8, 5, true); + const intermediate = [version].concat(convertedbits); + const ret = segwitEncode(hrp, intermediate, enc); + return ret; +} + + +// ################################################ END SEGWIT ENCODING FUNCTIONS ################################################### diff --git a/src/core/lib/Bitcoin.mjs b/src/core/lib/Bitcoin.mjs new file mode 100644 index 00000000..3a5ac538 --- /dev/null +++ b/src/core/lib/Bitcoin.mjs @@ -0,0 +1,355 @@ +/** + * 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"; + +// ################################################ BEGIN HELPER HASH FUNCTIONS ################################################# + +// SHA256(SHA256(input)) +/** + * Double SHA256 hash the passed in string. + * @param {string} input + * @returns + */ +export function doubleSHA(input) { + const hasher= CryptoApi.getHasher("sha256"); + hasher.update(input); + const result = hasher.finalize(); + const hasher2 = CryptoApi.getHasher("sha256"); + hasher2.update(result); + return hasher2.finalize(); +} + +// RIPEMD160(SHA256(input)) +/** + * Performs the RIPEMD_160(SHA256(input)) hash. This is a common hash pattern in cryptocurrency. + * @param {string} input + * @returns + */ +export function hash160Func(input) { + const sha256Hasher= CryptoApi.getHasher("sha256"); + sha256Hasher.update(input); + const sha256hash = sha256Hasher.finalize(); + const ripemdHasher=CryptoApi.getHasher("ripemd160"); + ripemdHasher.update(sha256hash); + return ripemdHasher.finalize(); +} + +// ################################################ END HELPER HASH FUNCTIONS ################################################### + + +// ################################################ BEGIN BASE58 FUNCTIONS ###################################################### + +/** + * Taken and modified from the ToBase58 op. + * We need this code as the operation code isn't exportable / easily available to other functions. + * We don't remove non Base58 characters, (we assume this must be done earlier) and we stick to only the Bitcoin alphabet here. + * @param {*} input + * @returns + */ +export function base58Encode (input) { + let alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; + input = new Uint8Array(input); + + alphabet = Utils.expandAlphRange(alphabet).join(""); + let result = [0]; + if (alphabet.length !== 58 || + [].unique.call(alphabet).length !== 58) { + throw new OperationError("Error: alphabet must be of length 58"); + } + + if (input.length === 0) { + return ""; + } + + let zeroPrefix = 0; + for (let i = 0; i < input.length && input[i] === 0; i++) { + zeroPrefix++; + } + + input.forEach(function(b) { + let carry = (result[0] << 8) + b; + result[0] = carry % 58; + carry = (carry / 58) | 0; + + for (let i = 1; i < result.length; i++) { + carry += result[i] << 8; + result[i] = carry % 58; + carry = (carry / 58) | 0; + } + + while (carry > 0) { + result.push(carry % 58); + carry = (carry / 58) | 0; + } + }); + result = result.map(function(b) { + return alphabet[b]; + }).reverse().join(""); + + while (zeroPrefix--) { + result = alphabet[0] + result; + } + + return result; +} + +/** + * Taken and modified from the FromBase58 op. + * We need this code as the operation code isn't exportable / easily available to other functions. + * We don't remove non Base58 characters, (we assume this must be done earlier) and we stick to only the Bitcoin alphabet here. + * @param {*} input + * @returns +*/ +export function base58Decode (input) { + let alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; + const result = [0]; + + alphabet = Utils.expandAlphRange(alphabet).join(""); + + if (alphabet.length !== 58 || + [].unique.call(alphabet).length !== 58) { + throw new OperationError("Alphabet must be of length 58"); + } + + if (input.length === 0) return []; + + let zeroPrefix = 0; + for (let i = 0; i < input.length && input[i] === alphabet[0]; i++) { + zeroPrefix++; + } + + [].forEach.call(input, function(c, charIndex) { + const index = alphabet.indexOf(c); + + if (index === -1) { + throw new OperationError(`Char '${c}' at position ${charIndex} not in alphabet`); + } + + let carry = result[0] * 58 + index; + result[0] = carry & 0xFF; + carry = carry >> 8; + + for (let i = 1; i < result.length; i++) { + carry += result[i] * 58; + result[i] = carry & 0xFF; + carry = carry >> 8; + } + + while (carry > 0) { + result.push(carry & 0xFF); + carry = carry >> 8; + } + }); + + while (zeroPrefix--) { + result.push(0); + } + + return result.reverse(); +} + + +// Base58 Checksum +/** + * Base58 Checksum + * @param {*} input + * @returns + */ +export function b58DoubleSHAChecksum(input) { + let byteResult; + try { + byteResult = fromArrayBuffer(base58Decode(input)); + } catch (oe) { + if (oe instanceof OperationError) { + return false; + } else { + throw oe; + } + } + const data = byteResult.slice(0, -4); + const checksum = byteResult.slice(byteResult.length-4,); + const hashedData = doubleSHA(data); + return hashedData.slice(0, 4) === checksum; +} + +// ################################################ END BASE58 FUNCTIONS ######################################################## + +// ################################################ BEGIN EXTRA FUNCTIONS ####################################################### +// Function for Deserializing Extended Keys (XPUBs/XPRVs) +/** + * Function for deserializing an extended key (xpub/xprv). + * We break down an extended key into its constituent parts, and return the results as JSON. + * @param {*} input + * @returns + */ +export function deserializeExtendedKeyFunc (input) { + if (! b58DoubleSHAChecksum(input)) { + const output = {"error": "Invalid checksum."}; + return output; + } else { + const byteResult = fromArrayBuffer(base58Decode(input)); + const checksum = byteResult.slice(-4); + const xprv = byteResult.slice(0, -4); + const version = xprv.slice(0, 4); + const level = parseInt(toHex(xprv.slice(4, 5)), 16); + const fingerprint = xprv.slice(5, 9); + const i = parseInt(toHex(xprv.slice(9, 13)), 16); + const chaincode = xprv.slice(13, 45); + const masterkey = xprv.slice(45, 78); + + return {"version": toHex(version), "level": level, "checksum": toHex(checksum), "key": input, + "fingerprint": toHex(fingerprint), "chaincode": toHex(chaincode), "masterkey": toHex(masterkey), "i": i}; + } +} + + +// Version byte dictionary. +const versionBytes = { + "tpub": "043587cf", + "tprv": "04358394", + "upub": "044a5262", + "uprv": "044a4e28", + "vpub": "045f1cf6", + "vprv": "045f18bc", + "Upub": "024289ef", + "Uprv": "024285b5", + "Vpub": "02575483", + "Vprv": "02575048", + "xpub": "0488b21e", + "xprv": "0488ade4", + "ypub": "049d7cb2", + "yprv": "049d7878", + "zpub": "04b24746", + "zprv": "04b2430c", + "Zpub": "02aa7ed3", + "Zprv": "02aa7a99", + "Ypub": "0295b43f", + "Yprv": "0295b005", + "Ltub": "019da462", + "Ltpv": "019d9cfe", + "Mtub": "01b26ef6", + "Mtpv": "01b26792", + "ttub": "0436f6e1", + "ttpv": "0436ef7d" +}; + +/** + * We return the correct version bytes from the versionBytes map, given input string. + * @param {*} input + * @returns + */ +export function getExtendedKeyVersion(input) { + return versionBytes[input]; + +} + +/** + * 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" + } +}; + +/** + * 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 18fec28c..86b8e5fd 100644 --- a/src/core/lib/Extract.mjs +++ b/src/core/lib/Extract.mjs @@ -45,6 +45,38 @@ export function search(input, searchRegex, removeRegex=null, sortBy=null, unique return results; } +/** + * Runs search operation across the input data using the regular expressions. Filters using the filterFunc. + * @param {string} input + * @param {RegExp} searchRegex + * @param {RegExp} removeRegex + * @param {func} filterFunc + * @param {boolean} includeTotal + * @returns {string} + */ +export function searchAndFilter(input, searchRegex, removeRegex, filterFunc, includeTotal) { + let output = "", + total = 0, + match; + + while ((match = searchRegex.exec(input))) { + // Moves pointer when an empty string is matched (prevents infinite loop) + if (match.index === searchRegex.lastIndex) { + searchRegex.lastIndex++; + } + if (removeRegex && removeRegex.test(match[0])) + continue; + if (filterFunc && !filterFunc(match[0])) + continue; + total++; + output += match[0] + "\n"; + } + + if (includeTotal) + output = "Total found: " + total + "\n\n" + output; + + return output; +} /** * URL regular expression 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/ChangeExtendedKeyVersion.mjs b/src/core/operations/ChangeExtendedKeyVersion.mjs new file mode 100644 index 00000000..7f367f5d --- /dev/null +++ b/src/core/operations/ChangeExtendedKeyVersion.mjs @@ -0,0 +1,62 @@ +/** + * Changes the extended key from one version to another. + * + * @author dgoldenberg [virtualcurrency@mitre.org] + * @copyright MITRE 2023 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import { deserializeExtendedKeyFunc, serializeExtendedKeyFunc, getExtendedKeyVersion } from "../lib/Bitcoin.mjs"; + + +/** + * Changes the version of an extended key. This can help to see if two keys are equal. + */ +class ChangeExtendedKeyVersion extends Operation { + + /** + * Extract Seedphrases Constructor. + */ + constructor() { + super(); + + this.name = "Change Extended Key Version"; + this.module = "Serialize"; + this.description = "Changes the version of an Extended Key (xpub to ypub, ypub to xpub and so on) and returns the new extended key with the different version. All other data is kept the same. This can be useful in comparing extended keys written in different standards, or changing an extended key to a needed standard for importation into a wallet. Note that changing a public key to a private key (XPUB->XPRV) or vice versa will make the resulting key invalid as this operation does not change the key data itself as of now."; + this.infoURL = "https://github.com/satoshilabs/slips/blob/master/slip-0132.md"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Version Type", + "type": "option", + "value": ["xpub", "xprv", "ypub", "yprv", "zpub", "zprv", "Zpub", "Zprv", "Ypub", "Yprv", "Ltub", "Ltpv", "Mtub", "Mtpv", "ttub", "ttpv", "tpub", "tprv", "upub", "uprv", "vpub", "vprv", "Upub", "Uprv", "Vpub", "Vprv"] + } + ]; + this.checks = [ + { + "pattern": "^(X|x|Y|y|Z|z|L|l|T|t)[pub|prv|tbv|tub][A-HJ-NP-Za-km-z1-9]{2,}$", + "flags": "", + "args": [] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + if (input.trim().length === 0) { + return ""; + } + const result = deserializeExtendedKeyFunc(input); + const newVersion = getExtendedKeyVersion(args[0]); + return serializeExtendedKeyFunc(newVersion, result.level, result.fingerprint, result.i, result.chaincode, result.masterkey); + } + +} + +export default ChangeExtendedKeyVersion; 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..262de700 --- /dev/null +++ b/src/core/operations/DeserializeExtendedKey.mjs @@ -0,0 +1,89 @@ +/** + * Deserializes the passed in Extended Key to its component parts. + * + * @author dgoldenberg [virtualcurrency@mitre.org] + * @copyright MITRE 2023 + * @license Apache-2.0 + */ +import Operation from "../Operation.mjs"; +import { deserializeExtendedKeyFunc } from "../lib/Bitcoin.mjs"; + + +/** + * Deserializes an extended key (XPUB/XPRV). Returns available information. + */ +class DeserializeExtendedKey extends Operation { + + /** + * Extract Seedphrases Constructor. + */ + constructor() { + super(); + + this.name = "Deserialize Extended Key"; + this.module = "Serialize"; + this.description = "Deserializes a passed in extended key. Can return all the deserialized information, or just the master key part (useful for chaining this call to others in a recipe)."; + this.inputType = "string"; + this.outputType = "JSON"; + this.presentType = "string"; + this.infoURL = "https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#Serialization_format"; + this.args = [ + { + name: "Key Only", + type: "boolean", + value: true + } + ]; + this.checks = [ + { + "pattern": "^(X|x|Y|y|Z|z|L|l|T|t)[pub|prv|tbv|tub][A-HJ-NP-Za-km-z1-9]{2,}$", + "flags": "", + "args": [] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [keyOnly] = args; + const result = deserializeExtendedKeyFunc(input); + if (!keyOnly) { + return result; + } else { + return {"masterkey": result.masterkey}; + } + } + + /** + * Displays the result of deserializing the extended key. + * + * @param {Object} output + * @returns {final_output} + */ + present(output) { + if ("error" in output) { + return output.error; + } else { + if (Object.prototype.hasOwnProperty.call(output, "masterkey") && Object.prototype.hasOwnProperty.call(output, "checksum")) { + let finalOutput = "Key Analyzed: " + output.key + "\n"; + finalOutput += "\tChecksum: " + output.checksum + "\n"; + finalOutput += "\tVersion: " + output.version + "\n"; + finalOutput += "\tLevel: " + output.level + "\n"; + finalOutput += "\tFingerprint: " + output.fingerprint + "\n"; + finalOutput += "\tChaincode: " + output.chaincode + "\n"; + finalOutput += "\tMasterKey: " + output.masterkey + "\n"; + finalOutput += "\n"; + return finalOutput; + } else { + return output.masterkey; + } + } + } + +} + +export default DeserializeExtendedKey; 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/PrivateECKeyToPublic.mjs b/src/core/operations/PrivateECKeyToPublic.mjs new file mode 100644 index 00000000..8a19d0d7 --- /dev/null +++ b/src/core/operations/PrivateECKeyToPublic.mjs @@ -0,0 +1,84 @@ +/** + * 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 {toHex} from "../lib/Hex.mjs"; +import ec from "elliptic"; + +/** + * Class that takes in a private key, and returns the public key, either in compressed or uncompressed form(s). + */ +class PrivateECKeyToPublic extends Operation { + /** + * Constructor. + */ + constructor() { + super(); + + this.name = "Private EC Key to Public Key"; + this.module = "Default"; + this.description = "Turns a private key to the appropriate ECC public key. Right now assumes the private key is a private key to the Secp256k1 curve."; + this.inputType = "string"; + this.outputType = "string"; + this.infoURL = "https://en.bitcoin.it/wiki/Secp256k1"; + this.args = [ + { + "name": "Compressed", + "type": "boolean", + "value": true + } + ]; + this.checks = [ + { + "pattern": "^[0-9A-Fa-f]{64}$", + "flags": "", + "args": [true] + } + ]; + + } + + /** + * Takes in a string, and an array of args. Interpolates the string as a private ECC Key, and attempts to construct the public key. + * @param {string} input + * @param {*} args + * @returns + */ + run(input, args) { + // We check if input is blank. + // If its blank or just whitespace, we don't need to bother dealing with it. + if (input.trim().length === 0) { + return ""; + } + input = input.trim(); + const re = /^[0-9A-Fa-f]{2,}$/g; + if (!(input.length === 64 && re.test(input)) && !(input.length === 32)) { + return "Must pass a hex string of length 64, or a byte string of length 32. Got length " + input.length; + } + // If we have bytes, we need to turn the bytes to hex. + if (input.length !== undefined && input.length === 32) { + const buf = new Uint8Array(new ArrayBuffer(32)); + + for (let i= 0; i < 32; i ++) { + if (input.charCodeAt(i) > 255) { + return "Cannot interpret this 32 character string as bytes."; + } + buf[i] = input.charCodeAt(i); + } + input = toHex(buf, "", 2, "", 0); + } + const ecContext = ec.ec("secp256k1"); + const key = ecContext.keyFromPrivate(input); + const pubkey = key.getPublic(args[0], "hex"); + + return pubkey; + } + +} + +export default PrivateECKeyToPublic; diff --git a/src/core/operations/PrivateKeyToWIF.mjs b/src/core/operations/PrivateKeyToWIF.mjs new file mode 100644 index 00000000..18544b18 --- /dev/null +++ b/src/core/operations/PrivateKeyToWIF.mjs @@ -0,0 +1,101 @@ +/** + * Turns a 32 byte private key into Wallet-Import-Format + * + * @author dgoldenberg [virtualcurrency@mitre.org] + * @copyright MITRE 2023 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import { base58Encode, getWIFVersionByte, doubleSHA} 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(); + // We check to see if the input is hex or not. + // If it is not, we convert it back to hex + const re = /[0-9A-Fa-f]{2,}/g; + if (!(input.length === 64 && re.test(input)) && !(input.length === 32)) { + return "Must pass a hex string of length 64, or a byte string of length 32. Got length: " + input.length; + } + if (input.length === 32) { + const buf = new Uint8Array(new ArrayBuffer(32)); + + for (let i= 0; i < 32; i ++) { + if (input.charCodeAt(i) > 255) { + return "Cannot interpret this 32 character string as bytes."; + } + buf[i] = input.charCodeAt(i); + } + input = toHexOther(buf, "", 2, "", 0); + } + + const versionByte = getWIFVersionByte(args[0]); + let extendedPrivateKey = versionByte + input; + 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/PublicKeyToP2PKHAddress.mjs b/src/core/operations/PublicKeyToP2PKHAddress.mjs new file mode 100644 index 00000000..87dc09af --- /dev/null +++ b/src/core/operations/PublicKeyToP2PKHAddress.mjs @@ -0,0 +1,145 @@ +/** + * Turns a public key into a cryptocurrency address. Supports Bitcoin (P2PKH, P2SH-P2WPKH, Segwit) as well as Ethereum. + * + * @author dgoldenberg [virtualcurrency@mitre.org] + * @copyright MITRE 2023, geco 2019 + * @license MIT + */ + +import Operation from "../Operation.mjs"; +import { fromArrayBuffer } from "crypto-api/src/encoder/array-buffer.mjs"; +import {toHex} from "crypto-api/src/encoder/hex.mjs"; +import { base58Encode, getP2PKHVersionByte, getP2SHVersionByte, hash160Func, doubleSHA, getHumanReadablePart} from "../lib/Bitcoin.mjs"; +import {encodeProgramToSegwit} from "../lib/Bech32.mjs"; +import JSSHA3 from "js-sha3"; +import Utils from "../Utils.mjs"; + + /** + * Converts a Public Key to a P2PKH Address of the given type. + */ +class PublicKeyToP2PKHAddress extends Operation { + + /** + * Converts a public key to a P2PKH Address. + */ + constructor() { + super(); + + this.name = "Public Key To Cryptocurrency Address"; + this.module = "Default"; + this.description = "Turns a public key into a cryptocurrency address."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Currency Type", + "type": "option", + "value": ["BTC", "Testnet", "Ethereum"] + }, + { + "name": "Address Type", + "type": "option", + "value": ["P2PKH (V1 BTC Addresses)", "P2SH-P2PWPKH (Segwit Compatible)", "Segwit (P2WPKH)"] + } + ]; + this.checks = [ + { + pattern: "^0[3|2][a-fA-F0-9]{64}$", + flags: "", + args: ["BTC", "P2PKH (V1 BTC Addresses)"] + }, + { + pattern: "^04[a-fA-F0-9]{128}$", + flags: "", + args: ["Ethereum", "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 ""; + } + // We check to see if the input is hex or not. + // If it is, we convert back to bytes. + const re = /([0-9A-Fa-f]{2,})/g; + let inputIsHex = false; + let curInput = input; + if (re.test(input)) { + inputIsHex = true; + } + if (inputIsHex) { + curInput = fromArrayBuffer(Utils.convertToByteArray(input, "hex")); + } + + // We sanity check the input + const startByte = toHex(curInput[0]); + if (curInput.length !== 33 && curInput.length !== 65) { + return "Input is wrong length. Should be either 33 or 65 bytes, but is: " + curInput.length; + } + if (curInput.length === 33 && startByte !== "03" && startByte !== "02") { + return "Input is 33 bytes, but begins with invalid byte: " + startByte; + } + + if (curInput.length === 65 && startByte !== "04") { + return "Input is 65 bytes, but begins with invalid byte: " + startByte; + } + + if (args[0] === "Ethereum") { + // Ethereum addresses require uncompressed public keys. + if (startByte !== "04" || curInput.length !== 65) { + return "Ethereum addresses require uncompressed public keys."; + } + 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) + let result; + if (inputIsHex) { + result = algo(Utils.convertToByteArray(input, "hex").slice(1,)); + } else { + result = algo(Utils.convertToByteArray(toHex(input), "hex").slice(1,)); + } + return "0x" + result.slice(-40); + + } else { + // We hash the input + const hash160 = toHex(hash160Func(curInput)); + // We do segwit addresses first. + if (args[1] === "Segwit (P2WPKH)") { + const redeemScript = hash160; + const hrp = getHumanReadablePart(args[0]); + return encodeProgramToSegwit(hrp, 0, Utils.convertToByteArray(redeemScript, "hex")); + } + // 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)") { + 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/SeedToMPK.mjs b/src/core/operations/SeedToMPK.mjs new file mode 100644 index 00000000..fc7aafb0 --- /dev/null +++ b/src/core/operations/SeedToMPK.mjs @@ -0,0 +1,74 @@ +/** + * Turns a seed into the initial master private key. + * + * @author dgoldenberg [virtualcurrency@mitre.org] + * @copyright MITRE 2023 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import { serializeExtendedKeyFunc, getExtendedKeyVersion } from "../lib/Bitcoin.mjs"; +import forge from "node-forge"; +import Utils from "../Utils.mjs"; + + +/** + * Changes the version of an extended key. This can help to see if two keys are equal. + */ +class SeedToMPK extends Operation { + + /** + * Extract Seedphrases Constructor. + */ + constructor() { + super(); + + this.name = "Seed To Master Key"; + this.module = "Serialize"; + this.description = "Given a 64 byte seed, we change the seed into the extended given master private key, with selected version. To produce the seed from a seedphrase, you can use the Seedphrase To Seed Op."; + this.infoURL = "https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#Serialization_format"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Version Type", + "type": "option", + "value": ["xprv", "yprv", "zprv", "Zprv", "Yprv", "Ltpv", "Mtpv", "ttpv", "tprv", "uprv", "vprv", "Uprv", "Vprv"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + if (input.trim().length === 0) { + return ""; + } + input = input.trim(); + // We check to see if the input is hex or not. + // If it is not, we convert it back to hex + const re = /[0-9A-Fa-f]{2,}/g; + if (!(input.length === 128 && re.test(input)) && !(input.length === 64)) { + return "Must pass a hex string of length 128, or a byte string of length 64. Got length: " + input.length; + } + + const hmac = forge.hmac.create(); + hmac.start("sha512", Utils.convertToByteString("Bitcoin seed", "UTF8")); + if (input.length === 128) { + hmac.update(Utils.convertToByteString(input, "hex")); + } else { + hmac.update(input); + } + const hexValue = hmac.digest().toHex(); + + const newVersion = getExtendedKeyVersion(args[0]); + + return serializeExtendedKeyFunc(newVersion, 0, "00000000", 0, hexValue.slice(64,), "00" + hexValue.slice(0, 64)); + } + +} + +export default SeedToMPK; diff --git a/src/core/operations/SeedphraseToSeed.mjs b/src/core/operations/SeedphraseToSeed.mjs new file mode 100644 index 00000000..d425022e --- /dev/null +++ b/src/core/operations/SeedphraseToSeed.mjs @@ -0,0 +1,70 @@ +/** + * 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/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/index.mjs b/tests/operations/index.mjs index cb300b34..ce8b14c9 100644 --- a/tests/operations/index.mjs +++ b/tests/operations/index.mjs @@ -115,6 +115,19 @@ import "./tests/CBORDecode.mjs"; import "./tests/JA3Fingerprint.mjs"; import "./tests/JA3SFingerprint.mjs"; import "./tests/HASSH.mjs"; +import "./tests/ExtractDoubleSHA.mjs"; +import "./tests/ExtractSegwitArtifacts.mjs"; +import "./tests/TypeCryptoArtifacts.mjs"; +import "./tests/ExtractSeedphrases.mjs"; +import "./tests/PublicKeyToP2PKHAddress.mjs"; +import "./tests/PrivateECKeyToPublic.mjs"; +import "./tests/PrivateKeyToWIF.mjs"; +import "./tests/ChangeExtendedKeyVersion.mjs"; +import "./tests/SeedToMPK.mjs"; +import "./tests/DecryptKeyStoreFile.mjs"; +import "./tests/WIFToPrivateKey.mjs"; +import "./tests/SeedphraseToSeed.mjs"; +import "./tests/DeserializeExtendedKey.mjs"; import "./tests/GetAllCasings.mjs"; import "./tests/SIGABA.mjs"; import "./tests/ELFInfo.mjs"; diff --git a/tests/operations/tests/ChangeExtendedKeyVersion.mjs b/tests/operations/tests/ChangeExtendedKeyVersion.mjs new file mode 100644 index 00000000..c0da00e7 --- /dev/null +++ b/tests/operations/tests/ChangeExtendedKeyVersion.mjs @@ -0,0 +1,57 @@ +/** + * 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"], + }, + ], + } + +]); diff --git a/tests/operations/tests/DecryptKeyStoreFile.mjs b/tests/operations/tests/DecryptKeyStoreFile.mjs new file mode 100644 index 00000000..9c642b98 --- /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] + }, + { + "op": "Public Key To Cryptocurrency Address", + "args": ["Ethereum", "P2PKH (V1 BTC Addresses)"] + } + ] + + } + +]); 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/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/PrivateECKeyToPublic.mjs b/tests/operations/tests/PrivateECKeyToPublic.mjs new file mode 100644 index 00000000..b60f89e5 --- /dev/null +++ b/tests/operations/tests/PrivateECKeyToPublic.mjs @@ -0,0 +1,76 @@ +/** + * Private key to secp256k1 public key tests. + * + * @author dgoldenberg [virtualcurrency@mitre.org] + * @copyright MITRE 2023 + * @license Apache-2.0 + */ + +import TestRegister from "../../lib/TestRegister.mjs"; + + +TestRegister.addTests([ + { + name: "Private EC Key to Public (Compressed)", + input: "5E2A8FDE9F861056607208F512287CFBD634E124044EE23EBF7289E8E7B3822E", + expectedOutput: "036cf115be1fc5f54585965817c735d74bdae03d9665a43704a9bdbf8a1c6b1e40", + recipeConfig: [ + { + "op": "Private EC Key to Public Key", + "args": [true], + }, + ], + }, + { + name: "Private EC Key to Public (Uncompressed)", + input: "5E2A8FDE9F861056607208F512287CFBD634E124044EE23EBF7289E8E7B3822E", + expectedOutput: "046cf115be1fc5f54585965817c735d74bdae03d9665a43704a9bdbf8a1c6b1e401f8a5888434ba6abc1967c036cc28903283d0f43b53fee63a3fb0b416019892b", + recipeConfig: [ + { + "op": "Private EC Key to Public Key", + "args": [false], + }, + ], + }, + { + name: "Private EC Key to Public (From Bytes Uncompressed)", + input: "5E2A8FDE9F861056607208F512287CFBD634E124044EE23EBF7289E8E7B3822E", + expectedOutput: "036cf115be1fc5f54585965817c735d74bdae03d9665a43704a9bdbf8a1c6b1e40", + recipeConfig: [ + { + "op": "From Hex", + "args": ["Auto"] + }, + { + "op": "Private EC Key to Public Key", + "args": [true], + }, + ], + }, + { + name: "Private EC Key to Public (Wrong Length)", + input: "5E2A8FDE9F861056607208F512287CFBD634E124044EE23EBF7289E8E7B3822E08", + expectedOutput: "Must pass a hex string of length 64, or a byte string of length 32. Got length 66", + recipeConfig: [ + { + "op": "Private EC Key to Public Key", + "args": [false], + }, + ], + }, + { + name: "Private EC Key to Public (From Bytes Uncompressed Wrong Length)", + input: "5E2A8FDE9F861056607208F512287CFBD634E124044EE23EBF7289E8E7B3822E08", + expectedOutput: "Must pass a hex string of length 64, or a byte string of length 32. Got length 33", + recipeConfig: [ + { + "op": "From Hex", + "args": ["Auto"] + }, + { + "op": "Private EC Key to Public Key", + "args": [true], + }, + ], + } +]); diff --git a/tests/operations/tests/PrivateKeyToWIF.mjs b/tests/operations/tests/PrivateKeyToWIF.mjs new file mode 100644 index 00000000..4d6832f2 --- /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: "Must pass a hex string of length 64, or a byte string of length 32. Got length: 66", + recipeConfig: [ + { + "op": "To WIF Format", + "args": ["BTC", true] + } + ], + }, + { + name: "Private Key To WIF (BTC, Compressed, Wrong Number of Bytes)", + input: "5E2A8FDE9F861056607208F512287CFBD634E124044EE23EBF7289E8E7B3822E08", + expectedOutput: "Must pass a hex string of length 64, or a byte string of length 32. Got length: 33", + recipeConfig: [ + { + "op": "From Hex", + "args": ["Auto"] + }, + { + "op": "To WIF Format", + "args": ["BTC", true] + } + ], + } + +]); diff --git a/tests/operations/tests/PublicKeyToP2PKHAddress.mjs b/tests/operations/tests/PublicKeyToP2PKHAddress.mjs new file mode 100644 index 00000000..d06614fa --- /dev/null +++ b/tests/operations/tests/PublicKeyToP2PKHAddress.mjs @@ -0,0 +1,174 @@ +/** + * Public Key to cryptocurrency address tests. + * + * @author dgoldenberg [virtualcurrency@mitre.org] + * @copyright MITRE 2023 + * @license Apache-2.0 + */ + +import TestRegister from "../../lib/TestRegister.mjs"; + + +TestRegister.addTests([ + { + name: "Public Key To Address: P2PKH (1)", + input: "03ebf60a619da2fbc6239089ca0a93878ea53baa3d22188cacad4033b103237ae9", + expectedOutput: "1MwwHqDj1FAyABeqPeiTTvJQCoCorcuFyP", + recipeConfig: [ + { + "op": "Public Key To Cryptocurrency Address", + "args": ["BTC", "P2PKH (V1 BTC Addresses)"] + }, + ], + }, + { + name: "Public Key To Address: P2PKH (2)", + input: "021dc4eb14b93dfbbbe4578293d07b6ee443ca025d89fd43a657ee3fd8c81d03f6", + expectedOutput: "18wUwr4Jvor6LG1mvQcfEp1Lx51dYAXZX1", + recipeConfig: [ + { + "op": "Public Key To Cryptocurrency Address", + "args": ["BTC", "P2PKH (V1 BTC Addresses)"] + }, + ], + }, + { + name: "Public Key To Address: P2PKH (Long)", + input: "04219A19E157B5FEDDF7EBDD3C7A58D7AB4F6565E84226691B6A5F80BBCE8E0100B49D6AB503CA4B701626E941EB8D2460F154992D7AD4EC671CF1CFB8C1DE8164", + expectedOutput: "1BgRqTW8RMmcTRXHymTCVJsn5NVk9U8L9q", + recipeConfig: [ + { + "op": "Public Key To Cryptocurrency Address", + "args": ["BTC", "P2PKH (V1 BTC Addresses)"] + }, + ], + }, + { + name: "Public Key To Address: P2SH-P2WPKH (1)", + input: "03a24ca9f13f6bcbc15615f71504be75566120b9bc7072e171756233162c726432", + expectedOutput: "31vhdy8RGhSYZRRGZfqvZHGzVtpcua4cQW", + recipeConfig: [ + { + "op": "Public Key To Cryptocurrency Address", + "args": ["BTC", "P2SH-P2PWPKH (Segwit Compatible)"] + }, + ], + }, + { + name: "Public Key To Address: P2SH-P2WPKH (2)", + input: "021a4310db1211939e20c88e9b90be354a145ec323a045de47ff0ea3145f99c8c9", + expectedOutput: "3C9wCFwcd36MHVpontDF7zQfKPfRTNg4Fe", + recipeConfig: [ + { + "op": "Public Key To Cryptocurrency Address", + "args": ["BTC", "P2SH-P2PWPKH (Segwit Compatible)"] + }, + ], + }, + { + name: "Public Key To Address: P2WPKH (1)", + input: "02530f0512d544344a04777be5477a2ffef813a110ac0705fafa012f5b61b56380", + expectedOutput: "bc1qu37uvwyzj23a2dd3x5nd8s77nfskzu3lzkuqfm", + recipeConfig: [ + { + "op": "Public Key To Cryptocurrency Address", + "args": ["BTC", "Segwit (P2WPKH)"] + }, + ], + }, + { + name: "Public Key To Address: P2WPKH (2)", + input: "03bc32bdc5dc96c9fb56e2481fefd321ebe9e17a807bbb337dea1df5e68b1f0756", + expectedOutput: "bc1qrjluhfu5qr2780zlvcx3kquckpvuamwqp2sjle", + recipeConfig: [ + { + "op": "Public Key To Cryptocurrency Address", + "args": ["BTC", "Segwit (P2WPKH)"] + }, + ], + }, + { + name: "Public Key To Address: (ETH)", + input: "04d26bcecd763bdf6bdb89ba929d2485429fbda73bae723d525ef55554ef45350582085bd24055079f6deebad5b6af612c14587c6862391d330484afe750fbf144", + expectedOutput: "0x63e8b85679d29235791a0f558d6485c7ed51c9e6", + recipeConfig: [ + { + "op": "Public Key To Cryptocurrency Address", + "args": ["Ethereum", "Segwit (P2WPKH)"] + }, + ], + + }, + { + name: "Public Key To Address: (Testnet)", + input: "02aa6438e78b18a503f4466672fd04e31aaeec3ed1c3e0e1f19654776f0f0dc1b2", + expectedOutput: "mmuoeJDuuzeuaii1V6tPK3L5YjaJwjPqUM", + recipeConfig: [ + { + "op": "Public Key To Cryptocurrency Address", + "args": ["Testnet", "P2PKH (V1 BTC Addresses)"] + }, + ], + + }, + { + name: "Public Key To Address: P2WPKH (Wrong Length)", + input: "03bc32bdc5dc96c9fb56e2481fefd321ebe9e17a807bbb337dea1df5e68b1f075642", + expectedOutput: "Input is wrong length. Should be either 33 or 65 bytes, but is: 34", + recipeConfig: [ + { + "op": "Public Key To Cryptocurrency Address", + "args": ["BTC", "Segwit (P2WPKH)"] + }, + ], + }, + { + name: "Public Key To Address: P2WPKH (Wrong Start)", + input: "05bc32bdc5dc96c9fb56e2481fefd321ebe9e17a807bbb337dea1df5e68b1f0756", + expectedOutput: "Input is 33 bytes, but begins with invalid byte: 05", + recipeConfig: [ + { + "op": "Public Key To Cryptocurrency Address", + "args": ["BTC", "Segwit (P2WPKH)"] + }, + ], + }, + { + name: "Public Key To Address: P2PKH (Long With Error)", + input: "06219A19E157B5FEDDF7EBDD3C7A58D7AB4F6565E84226691B6A5F80BBCE8E0100B49D6AB503CA4B701626E941EB8D2460F154992D7AD4EC671CF1CFB8C1DE8164", + expectedOutput: "Input is 65 bytes, but begins with invalid byte: 06", + recipeConfig: [ + { + "op": "Public Key To Cryptocurrency Address", + "args": ["BTC", "P2PKH (V1 BTC Addresses)"] + }, + ], + }, + { + name: "Public Key To Address: P2PKH (From Bytes)", + input: "03ebf60a619da2fbc6239089ca0a93878ea53baa3d22188cacad4033b103237ae9", + expectedOutput: "1MwwHqDj1FAyABeqPeiTTvJQCoCorcuFyP", + recipeConfig: [ + { + "op": "From Hex", + "args": ["Auto"] + }, + { + "op": "Public Key To Cryptocurrency Address", + "args": ["BTC", "P2PKH (V1 BTC Addresses)"] + } + ], + }, + { + name: "Public Key To Address: (ETH Compressed Key)", + input: "03ebf60a619da2fbc6239089ca0a93878ea53baa3d22188cacad4033b103237ae9", + expectedOutput: "Ethereum addresses require uncompressed public keys.", + recipeConfig: [ + { + "op": "Public Key To Cryptocurrency Address", + "args": ["Ethereum", "Segwit (P2WPKH)"] + }, + ], + + } +]); diff --git a/tests/operations/tests/SeedToMPK.mjs b/tests/operations/tests/SeedToMPK.mjs new file mode 100644 index 00000000..e1dfc246 --- /dev/null +++ b/tests/operations/tests/SeedToMPK.mjs @@ -0,0 +1,47 @@ +/** + * 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 (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"] + } + ], + } + +]); 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": [] + }, + ], + } +]);