mirror of
https://github.com/gchq/CyberChef.git
synced 2025-04-25 01:06:16 -04:00
First Cryptochef branch commit.
This commit is contained in:
parent
415c59d74c
commit
f79ca6cf02
36 changed files with 7134 additions and 4 deletions
|
@ -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": [
|
||||
|
|
286
src/core/lib/Bech32.mjs
Normal file
286
src/core/lib/Bech32.mjs
Normal file
|
@ -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 ###################################################
|
355
src/core/lib/Bitcoin.mjs
Normal file
355
src/core/lib/Bitcoin.mjs
Normal file
|
@ -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 #########################################################
|
||||
|
|
@ -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
|
||||
|
|
147
src/core/lib/Seedphrase.mjs
Normal file
147
src/core/lib/Seedphrase.mjs
Normal file
|
@ -0,0 +1,147 @@
|
|||
/**
|
||||
* Many Seedphrase specific functions. We cover BIP39 and Electrum2 checksums so far.
|
||||
*
|
||||
* @author dgoldenberg [virtualcurrency@mitre.org]
|
||||
* @copyright MITRE 2023 Wei Lu <luwei.here@gmail.com> and Daniel Cousens <email@dcousens.com> 2014
|
||||
* @license ISC
|
||||
*/
|
||||
|
||||
import CryptoApi from "crypto-api/src/crypto-api.mjs";
|
||||
import {toHex} from "crypto-api/src/encoder/hex.mjs";
|
||||
import Hmac from "crypto-api/src/mac/hmac.mjs";
|
||||
import { fromArrayBuffer } from "crypto-api/src/encoder/array-buffer.mjs";
|
||||
import {bip39English, electrum2English} from "./SeedphraseWordLists.mjs";
|
||||
|
||||
// Dictionary for BIP39.
|
||||
export const bip39 = {
|
||||
"acceptable_lengths": [12, 15, 18, 21, 24],
|
||||
"english": bip39English,
|
||||
"checksum": validateMnemonic
|
||||
};
|
||||
|
||||
// Dictionary for Electrum2
|
||||
export const electrum2 = {
|
||||
"acceptable_lengths": [12, 14],
|
||||
"english": electrum2English,
|
||||
"checksum": validateElectrum2Mnemonic
|
||||
};
|
||||
|
||||
// BIP39 Verification code taken from https://github.com/vacuumlabs/bip39-light/blob/master/index.js
|
||||
const INVALIDMNEMONIC = "Invalid mnemonic";
|
||||
const INVALIDENTROPY = "Invalid entropy";
|
||||
const INVALIDCHECKSUM = "Invalid mnemonic checksum";
|
||||
|
||||
/**
|
||||
* Left pad data.
|
||||
* @param {string} str
|
||||
* @param {string} padString
|
||||
* @param {int} length
|
||||
* @returns
|
||||
*/
|
||||
function lpad (str, padString, length) {
|
||||
while (str.length < length) str = padString + str;
|
||||
return str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns a string of 0 and 1 to bytes.
|
||||
* @param {string} bin
|
||||
* @returns
|
||||
*/
|
||||
function binaryToByte (bin) {
|
||||
return parseInt(bin, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns a string of bytes to a binary array
|
||||
* @param {string} bytes
|
||||
* @returns
|
||||
*/
|
||||
function bytesToBinary (bytes) {
|
||||
return bytes.map(function (x) {
|
||||
return lpad(x.toString(2), "0", 8);
|
||||
}).join("");
|
||||
}
|
||||
|
||||
/**
|
||||
* Derive the checksum bits for a BIP39 seedphrase.
|
||||
* @param {bytes} entropyBuffer
|
||||
* @returns
|
||||
*/
|
||||
function deriveChecksumBits (entropyBuffer) {
|
||||
const ENT = entropyBuffer.length * 8;
|
||||
const CS = ENT / 32;
|
||||
const hasher= CryptoApi.getHasher("sha256");
|
||||
hasher.update(fromArrayBuffer(entropyBuffer));
|
||||
const result = hasher.finalize();
|
||||
const hexResult = toHex(result);
|
||||
const temp = bytesToBinary([parseInt(hexResult.slice(0, 2), 16)]);
|
||||
const final = temp.slice(0, CS);
|
||||
return final;
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns a mnemonic string to the underlying bytes.
|
||||
* @param {str} mnemonic
|
||||
* @param {list} wordlist
|
||||
* @returns
|
||||
*/
|
||||
function mnemonicToEntropy (mnemonic, wordlist) {
|
||||
const words = mnemonic.split(" ");
|
||||
if (words.length % 3 !== 0) throw new Error(INVALIDMNEMONIC);
|
||||
|
||||
// convert word indices to 11 bit binary strings
|
||||
const bits = words.map(function (word) {
|
||||
const index = wordlist.indexOf(word);
|
||||
if (index === -1) throw new Error(INVALIDMNEMONIC);
|
||||
|
||||
return lpad(index.toString(2), "0", 11);
|
||||
}).join("");
|
||||
|
||||
|
||||
// split the binary string into ENT/CS
|
||||
const dividerIndex = Math.floor(bits.length / 33) * 32;
|
||||
const entropyBits = bits.slice(0, dividerIndex);
|
||||
const checksumBits = bits.slice(dividerIndex);
|
||||
|
||||
// calculate the checksum and compare
|
||||
const entropyBytes = entropyBits.match(/(.{1,8})/g).map(binaryToByte);
|
||||
if (entropyBytes.length < 16) throw new Error(INVALIDENTROPY);
|
||||
if (entropyBytes.length > 32) throw new Error(INVALIDENTROPY);
|
||||
if (entropyBytes.length % 4 !== 0) throw new Error(INVALIDENTROPY);
|
||||
|
||||
const entropy = Buffer.from(entropyBytes);
|
||||
const newChecksum = deriveChecksumBits(entropy);
|
||||
if (newChecksum !== checksumBits) throw new Error(INVALIDCHECKSUM);
|
||||
return entropy.toString("hex");
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the BIP39 mnemonic string.
|
||||
* @param {str} mnemonic
|
||||
* @param {list} wordlist
|
||||
* @returns
|
||||
*/
|
||||
function validateMnemonic (mnemonic, wordlist) {
|
||||
try {
|
||||
mnemonicToEntropy(mnemonic, wordlist);
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// My own code for Electrum2
|
||||
/**
|
||||
* Validates an Electrum2 Mnemonic
|
||||
* @param {string} mnemonic
|
||||
* @returns
|
||||
*/
|
||||
function validateElectrum2Mnemonic(mnemonic) {
|
||||
const hasher = CryptoApi.getHasher("sha512");
|
||||
const hmac = new Hmac("Seed version", hasher);
|
||||
hmac.update(Buffer.from(mnemonic, "utf-8").toString());
|
||||
const result = toHex(hmac.finalize());
|
||||
return (result.startsWith("01") || result.startsWith("100") || result.startsWith("101") || result.startsWith("102"));
|
||||
}
|
4109
src/core/lib/SeedphraseWordLists.mjs
Normal file
4109
src/core/lib/SeedphraseWordLists.mjs
Normal file
File diff suppressed because it is too large
Load diff
62
src/core/operations/ChangeExtendedKeyVersion.mjs
Normal file
62
src/core/operations/ChangeExtendedKeyVersion.mjs
Normal file
|
@ -0,0 +1,62 @@
|
|||
/**
|
||||
* Changes the extended key from one version to another.
|
||||
*
|
||||
* @author dgoldenberg [virtualcurrency@mitre.org]
|
||||
* @copyright MITRE 2023
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import { deserializeExtendedKeyFunc, serializeExtendedKeyFunc, getExtendedKeyVersion } from "../lib/Bitcoin.mjs";
|
||||
|
||||
|
||||
/**
|
||||
* Changes the version of an extended key. This can help to see if two keys are equal.
|
||||
*/
|
||||
class ChangeExtendedKeyVersion extends Operation {
|
||||
|
||||
/**
|
||||
* Extract Seedphrases Constructor.
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Change Extended Key Version";
|
||||
this.module = "Serialize";
|
||||
this.description = "Changes the version of an Extended Key (xpub to ypub, ypub to xpub and so on) and returns the new extended key with the different version. All other data is kept the same. This can be useful in comparing extended keys written in different standards, or changing an extended key to a needed standard for importation into a wallet. Note that changing a public key to a private key (XPUB->XPRV) or vice versa will make the resulting key invalid as this operation does not change the key data itself as of now.";
|
||||
this.infoURL = "https://github.com/satoshilabs/slips/blob/master/slip-0132.md";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
"name": "Version Type",
|
||||
"type": "option",
|
||||
"value": ["xpub", "xprv", "ypub", "yprv", "zpub", "zprv", "Zpub", "Zprv", "Ypub", "Yprv", "Ltub", "Ltpv", "Mtub", "Mtpv", "ttub", "ttpv", "tpub", "tprv", "upub", "uprv", "vpub", "vprv", "Upub", "Uprv", "Vpub", "Vprv"]
|
||||
}
|
||||
];
|
||||
this.checks = [
|
||||
{
|
||||
"pattern": "^(X|x|Y|y|Z|z|L|l|T|t)[pub|prv|tbv|tub][A-HJ-NP-Za-km-z1-9]{2,}$",
|
||||
"flags": "",
|
||||
"args": []
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
if (input.trim().length === 0) {
|
||||
return "";
|
||||
}
|
||||
const result = deserializeExtendedKeyFunc(input);
|
||||
const newVersion = getExtendedKeyVersion(args[0]);
|
||||
return serializeExtendedKeyFunc(newVersion, result.level, result.fingerprint, result.i, result.chaincode, result.masterkey);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default ChangeExtendedKeyVersion;
|
140
src/core/operations/DecryptKeystoreFile.mjs
Normal file
140
src/core/operations/DecryptKeystoreFile.mjs
Normal file
|
@ -0,0 +1,140 @@
|
|||
/**
|
||||
* Decrypts ETH keystore files, given the password.
|
||||
*
|
||||
* @author dgoldenberg [virtualcurrency@mitre.org]
|
||||
* @copyright MITRE 2023
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
import JSSHA3 from "js-sha3";
|
||||
import Utils from "../Utils.mjs";
|
||||
import {fromHex} from "../lib/Hex.mjs";
|
||||
import scryptsy from "scryptsy";
|
||||
import { isWorkerEnvironment } from "../Utils.mjs";
|
||||
import forge from "node-forge";
|
||||
|
||||
|
||||
/**
|
||||
* JPath expression operation
|
||||
*/
|
||||
class DecryptKeystoreFile extends Operation {
|
||||
|
||||
/**
|
||||
* Decrypt Keystore constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Decrypt Keystore File";
|
||||
this.module = "Crypto";
|
||||
this.description = "Attempts to decrypt the given ETH keystore file, with the passed in password. Will return the private key if successful, error out if not.";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.infoURL = "https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition";
|
||||
this.args = [
|
||||
{
|
||||
name: "password",
|
||||
type: "string",
|
||||
value: ""
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const password = args[0];
|
||||
let jsonObj, dkey;
|
||||
// We parse the JSON object first and throw an error if its not JSON.
|
||||
try {
|
||||
jsonObj = JSON.parse(input);
|
||||
} catch (err) {
|
||||
throw new OperationError(`Invalid input, not JSON. Data: ${err.message}`);
|
||||
}
|
||||
|
||||
// We then check for a crypto property, and that crypto should have a kdf and kdfparams property.
|
||||
if (!Object.prototype.hasOwnProperty.call(jsonObj, "crypto") || !Object.prototype.hasOwnProperty.call(jsonObj.crypto, "kdfparams") || !Object.prototype.hasOwnProperty.call(jsonObj.crypto, "kdf")) {
|
||||
throw new OperationError(`Error. Invalid JSON blob, missing either a crypto, crypto.kdf or crypto.kdfparams object.`);
|
||||
}
|
||||
const kdfParams = jsonObj.crypto.kdfparams;
|
||||
const kdfType = jsonObj.crypto.kdf;
|
||||
|
||||
// We compute the kdf.
|
||||
if (kdfType === "scrypt") {
|
||||
try {
|
||||
// We compute the salt, and the compute the scrypt.
|
||||
const salt = Buffer.from(Utils.convertToByteArray(kdfParams.salt, "hex"));
|
||||
const data = scryptsy(password, salt, kdfParams.n, kdfParams.r, kdfParams.p, kdfParams.dklen,
|
||||
p =>{
|
||||
if (isWorkerEnvironment()) self.sendStatusMessage(`Progress: ${p.percent.toFixed(0)}%`);
|
||||
});
|
||||
// Result of the SCRYPT in hex.
|
||||
dkey = data.toString("hex");
|
||||
} catch (err) {
|
||||
throw new OperationError("Error: " + err.toString());
|
||||
}
|
||||
} else if (kdfType === "pbkdf2") {
|
||||
// If the kdf is PBKDF2, we check to make sure it has a prf property, and that property is hmac-sha256
|
||||
if (!Object.prototype.hasOwnProperty.call(kdfParams, "prf") || kdfParams.prf !== "hmac-sha256") {
|
||||
throw new OperationError(`Error with PBKDF2. Either HMAC function not present, or is not hmac-sha256. It is: ` + JSON.stringify(kdfParams));
|
||||
}
|
||||
// We compute the pbkdf2 and cast to hex.
|
||||
const iterations = kdfParams.c;
|
||||
const keyLength = kdfParams.dklen;
|
||||
const salt = Utils.convertToByteString(kdfParams.salt, "hex");
|
||||
dkey = forge.util.bytesToHex(forge.pkcs5.pbkdf2(password, salt, iterations, keyLength, "sha256"));
|
||||
|
||||
} else {
|
||||
// If there's a different KDF, we err out.
|
||||
throw new OperationError("We don't support KDF type " + kdfType + " ... yet.");
|
||||
}
|
||||
|
||||
const mackey = dkey.slice(-32,);
|
||||
const decryptionkey = dkey.slice(0, 32);
|
||||
const ciphertext = jsonObj.crypto.ciphertext;
|
||||
const hmac = jsonObj.crypto.mac;
|
||||
const algo = JSSHA3.keccak256;
|
||||
|
||||
const testmac = algo(fromHex(mackey + ciphertext, "hex"));
|
||||
if (testmac === hmac) {
|
||||
// If the MAC passes, we can decrypt.
|
||||
// We check for the right data to decrypt.
|
||||
if (!Object.prototype.hasOwnProperty.call(jsonObj.crypto, "cipherparams") || !Object.prototype.hasOwnProperty.call(jsonObj.crypto.cipherparams, "iv")) {
|
||||
throw new OperationError("We are missing needed cipherparams and IV.");
|
||||
}
|
||||
if (!Object.prototype.hasOwnProperty.call(jsonObj.crypto, "ciphertext") || !Object.prototype.hasOwnProperty.call(jsonObj.crypto.cipherparams, "iv")) {
|
||||
throw new OperationError("We are the ciphertext");
|
||||
}
|
||||
// We grab the key, and IV, and ciphertext
|
||||
const key = Utils.convertToByteString(decryptionkey, "hex");
|
||||
const iv = Utils.convertToByteString(jsonObj.crypto.cipherparams.iv, "hex");
|
||||
const cipherinput = Utils.convertToByteString(jsonObj.crypto.ciphertext, "hex");
|
||||
// We create the decryptor.
|
||||
const decipher = forge.cipher.createDecipher("AES-CTR", key);
|
||||
// We do the decryption.
|
||||
decipher.start({
|
||||
iv: iv.length === 0 ? "" : iv,
|
||||
tag: undefined,
|
||||
additionalData: undefined
|
||||
});
|
||||
decipher.update(forge.util.createBuffer(cipherinput));
|
||||
const result = decipher.finish();
|
||||
if (result) {
|
||||
return decipher.output.toHex();
|
||||
} else {
|
||||
throw new OperationError("Unable to decrypt keystore with these parameters.");
|
||||
}
|
||||
} else {
|
||||
// In this case the MAC failed so we error out.
|
||||
throw new OperationError("MAC error. We got: " + testmac + " , but we wanted. " + hmac);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default DecryptKeystoreFile;
|
89
src/core/operations/DeserializeExtendedKey.mjs
Normal file
89
src/core/operations/DeserializeExtendedKey.mjs
Normal file
|
@ -0,0 +1,89 @@
|
|||
/**
|
||||
* Deserializes the passed in Extended Key to its component parts.
|
||||
*
|
||||
* @author dgoldenberg [virtualcurrency@mitre.org]
|
||||
* @copyright MITRE 2023
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
import Operation from "../Operation.mjs";
|
||||
import { deserializeExtendedKeyFunc } from "../lib/Bitcoin.mjs";
|
||||
|
||||
|
||||
/**
|
||||
* Deserializes an extended key (XPUB/XPRV). Returns available information.
|
||||
*/
|
||||
class DeserializeExtendedKey extends Operation {
|
||||
|
||||
/**
|
||||
* Extract Seedphrases Constructor.
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Deserialize Extended Key";
|
||||
this.module = "Serialize";
|
||||
this.description = "Deserializes a passed in extended key. Can return all the deserialized information, or just the master key part (useful for chaining this call to others in a recipe).";
|
||||
this.inputType = "string";
|
||||
this.outputType = "JSON";
|
||||
this.presentType = "string";
|
||||
this.infoURL = "https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#Serialization_format";
|
||||
this.args = [
|
||||
{
|
||||
name: "Key Only",
|
||||
type: "boolean",
|
||||
value: true
|
||||
}
|
||||
];
|
||||
this.checks = [
|
||||
{
|
||||
"pattern": "^(X|x|Y|y|Z|z|L|l|T|t)[pub|prv|tbv|tub][A-HJ-NP-Za-km-z1-9]{2,}$",
|
||||
"flags": "",
|
||||
"args": []
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const [keyOnly] = args;
|
||||
const result = deserializeExtendedKeyFunc(input);
|
||||
if (!keyOnly) {
|
||||
return result;
|
||||
} else {
|
||||
return {"masterkey": result.masterkey};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the result of deserializing the extended key.
|
||||
*
|
||||
* @param {Object} output
|
||||
* @returns {final_output}
|
||||
*/
|
||||
present(output) {
|
||||
if ("error" in output) {
|
||||
return output.error;
|
||||
} else {
|
||||
if (Object.prototype.hasOwnProperty.call(output, "masterkey") && Object.prototype.hasOwnProperty.call(output, "checksum")) {
|
||||
let finalOutput = "Key Analyzed: " + output.key + "\n";
|
||||
finalOutput += "\tChecksum: " + output.checksum + "\n";
|
||||
finalOutput += "\tVersion: " + output.version + "\n";
|
||||
finalOutput += "\tLevel: " + output.level + "\n";
|
||||
finalOutput += "\tFingerprint: " + output.fingerprint + "\n";
|
||||
finalOutput += "\tChaincode: " + output.chaincode + "\n";
|
||||
finalOutput += "\tMasterKey: " + output.masterkey + "\n";
|
||||
finalOutput += "\n";
|
||||
return finalOutput;
|
||||
} else {
|
||||
return output.masterkey;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default DeserializeExtendedKey;
|
53
src/core/operations/ExtractDoubleShaArtifacts.mjs
Normal file
53
src/core/operations/ExtractDoubleShaArtifacts.mjs
Normal file
|
@ -0,0 +1,53 @@
|
|||
/**
|
||||
* Extracts most Double SHA256 encoded strings from text. These are strings which are valid Base58 encoded, with the last 4 bytes being the double sha256 of the previous bytes.
|
||||
*
|
||||
* @author dgoldenberg [virtualcurrency@mitre.org]
|
||||
* @copyright MITRE 2023
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
import Operation from "../Operation.mjs";
|
||||
import { searchAndFilter } from "../lib/Extract.mjs";
|
||||
import { b58DoubleSHAChecksum } from "../lib/Bitcoin.mjs";
|
||||
|
||||
/**
|
||||
* Extract Cryptocurrency addresses that are Base58 Encoded, with Double SHA256 Checksum.
|
||||
*/
|
||||
class ExtractDoubleShaArtifacts extends Operation {
|
||||
|
||||
/**
|
||||
* ExtractDoubleShaArtifacts Constructor.
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Extract Double SHA Artifacts";
|
||||
this.module = "Regex";
|
||||
this.description = "Extracts many cryptocurrency artifact strings that are Base58 encoded where here we define that as: 28-150 characters that are Base-58 encoded with a Double SHA256 checksum. For example, this will extract Bitcoin/Litecoin/Dogecoin addreses, WIF keys, extended keys amongst other artifacts from ASCII text.";
|
||||
this.infoURL = "https://en.bitcoin.it/wiki/Base58Check_encoding";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
"name": "Display total",
|
||||
"type": "boolean",
|
||||
"value": false
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const displayTotal = args[0],
|
||||
regex = /[A-HJ-NP-Za-km-z1-9]{28,150}/g;
|
||||
|
||||
return searchAndFilter(input, regex, null, b58DoubleSHAChecksum, displayTotal);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default ExtractDoubleShaArtifacts;
|
||||
|
87
src/core/operations/ExtractSeedPhrases.mjs
Normal file
87
src/core/operations/ExtractSeedPhrases.mjs
Normal file
|
@ -0,0 +1,87 @@
|
|||
/**
|
||||
* Extracts seedphrases. Right now, extracts BIP39 and Electrum2 seedphrases.
|
||||
*
|
||||
* @author dgoldenberg [virtualcurrency@mitre.org]
|
||||
* @copyright MITRE 2023 Wei Lu <luwei.here@gmail.com> and Daniel Cousens <email@dcousens.com> 2014
|
||||
* @license ISC
|
||||
*/
|
||||
import Operation from "../Operation.mjs";
|
||||
import { search} from "../lib/Extract.mjs";
|
||||
import {bip39, electrum2} from "../lib/Seedphrase.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
|
||||
/**
|
||||
* Extract Cryptocurrency addresses that are Base58 Encoded, with Double SHA256 Checksum.
|
||||
*/
|
||||
class ExtractSeedPhrases extends Operation {
|
||||
|
||||
/**
|
||||
* Extract Seedphrases Constructor.
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Extract Seedphrases";
|
||||
this.module = "Regex";
|
||||
this.description = "Attempts to extract seedphrases from text. Right now, only BIP39 and Electrum2 standards and the English language supported.";
|
||||
this.infoURL = "https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [{
|
||||
"name": "Seedphrase Type",
|
||||
"type": "option",
|
||||
"value": ["bip39", "electrum2"]
|
||||
}];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
|
||||
const regex = /[a-z]{3,20}/g;
|
||||
|
||||
const type = args[0];
|
||||
let seedphraseInfo = {};
|
||||
if (type === "bip39") {
|
||||
seedphraseInfo = bip39;
|
||||
} else if (type === "electrum2") {
|
||||
seedphraseInfo = electrum2;
|
||||
} else {
|
||||
throw new OperationError("Unknown seedphrase type value: " + type);
|
||||
}
|
||||
|
||||
let output = "";
|
||||
const wordArray = search(input, regex, null, false);
|
||||
|
||||
// Start scanning through the list
|
||||
for (let i = 0; i < wordArray.length; i++) {
|
||||
// If we find a starting element in the wordlist, we expand.
|
||||
if (seedphraseInfo.english.includes(wordArray[i])) {
|
||||
for (let j = 0; j < seedphraseInfo.acceptable_lengths.length; j++) {
|
||||
// For each possible length, we scan through the list.
|
||||
const curPhraseLength = seedphraseInfo.acceptable_lengths[j];
|
||||
let correctPhrase = true;
|
||||
for (let w =0; w < curPhraseLength; w++) {
|
||||
if (!seedphraseInfo.english.includes(wordArray[i + w])) {
|
||||
correctPhrase = false;
|
||||
}
|
||||
}
|
||||
// If all words in that slice belong in the word list, and the checksum holds we assume the phrase is a valid one.
|
||||
if (correctPhrase) {
|
||||
if (seedphraseInfo.checksum(wordArray.slice(i, i + curPhraseLength).join(" "), seedphraseInfo.english)) {
|
||||
output += wordArray.slice(i, i + curPhraseLength).join(" ") + "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default ExtractSeedPhrases;
|
54
src/core/operations/ExtractSegwitAddresses.mjs
Normal file
54
src/core/operations/ExtractSegwitAddresses.mjs
Normal file
|
@ -0,0 +1,54 @@
|
|||
/**
|
||||
* Extracts segwit encoded seedphrases. These can be Bech32 encoded or P2TR style addresses.
|
||||
*
|
||||
* @author dgoldenberg [virtualcurrency@mitre.org]
|
||||
* @copyright MITRE 2023, geco 2019
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import { searchAndFilter } from "../lib/Extract.mjs";
|
||||
import { segwitChecksum} from "../lib/Bech32.mjs";
|
||||
|
||||
/**
|
||||
* Extract Cryptocurrency addresses that are Segwit Formatted
|
||||
*/
|
||||
class ExtractSegwitAddresses extends Operation {
|
||||
|
||||
/**
|
||||
* Extract Segwit Constructor.
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Extract Segwit Addresses";
|
||||
this.module = "Regex";
|
||||
this.description = "Extracts Segwit formatted cryptocurrency addresses. Compliant with BIP173, and BIP350, given normal addresses. Not compatible as of now with Lightning Network invoices.";
|
||||
this.infoURL = "https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
"name": "Display total",
|
||||
"type": "boolean",
|
||||
"value": false
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const displayTotal = args[0],
|
||||
regex = /(bc|ltc|tb)1[023456789ac-hj-np-z]{38,60}/g;
|
||||
|
||||
return searchAndFilter(input, regex, null, segwitChecksum, displayTotal);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default ExtractSegwitAddresses;
|
||||
|
84
src/core/operations/PrivateECKeyToPublic.mjs
Normal file
84
src/core/operations/PrivateECKeyToPublic.mjs
Normal file
|
@ -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;
|
101
src/core/operations/PrivateKeyToWIF.mjs
Normal file
101
src/core/operations/PrivateKeyToWIF.mjs
Normal file
|
@ -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;
|
145
src/core/operations/PublicKeyToP2PKHAddress.mjs
Normal file
145
src/core/operations/PublicKeyToP2PKHAddress.mjs
Normal file
|
@ -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;
|
74
src/core/operations/SeedToMPK.mjs
Normal file
74
src/core/operations/SeedToMPK.mjs
Normal file
|
@ -0,0 +1,74 @@
|
|||
/**
|
||||
* Turns a seed into the initial master private key.
|
||||
*
|
||||
* @author dgoldenberg [virtualcurrency@mitre.org]
|
||||
* @copyright MITRE 2023
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import { serializeExtendedKeyFunc, getExtendedKeyVersion } from "../lib/Bitcoin.mjs";
|
||||
import forge from "node-forge";
|
||||
import Utils from "../Utils.mjs";
|
||||
|
||||
|
||||
/**
|
||||
* Changes the version of an extended key. This can help to see if two keys are equal.
|
||||
*/
|
||||
class SeedToMPK extends Operation {
|
||||
|
||||
/**
|
||||
* Extract Seedphrases Constructor.
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Seed To Master Key";
|
||||
this.module = "Serialize";
|
||||
this.description = "Given a 64 byte seed, we change the seed into the extended given master private key, with selected version. To produce the seed from a seedphrase, you can use the Seedphrase To Seed Op.";
|
||||
this.infoURL = "https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#Serialization_format";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
"name": "Version Type",
|
||||
"type": "option",
|
||||
"value": ["xprv", "yprv", "zprv", "Zprv", "Yprv", "Ltpv", "Mtpv", "ttpv", "tprv", "uprv", "vprv", "Uprv", "Vprv"]
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
if (input.trim().length === 0) {
|
||||
return "";
|
||||
}
|
||||
input = input.trim();
|
||||
// We check to see if the input is hex or not.
|
||||
// If it is not, we convert it back to hex
|
||||
const re = /[0-9A-Fa-f]{2,}/g;
|
||||
if (!(input.length === 128 && re.test(input)) && !(input.length === 64)) {
|
||||
return "Must pass a hex string of length 128, or a byte string of length 64. Got length: " + input.length;
|
||||
}
|
||||
|
||||
const hmac = forge.hmac.create();
|
||||
hmac.start("sha512", Utils.convertToByteString("Bitcoin seed", "UTF8"));
|
||||
if (input.length === 128) {
|
||||
hmac.update(Utils.convertToByteString(input, "hex"));
|
||||
} else {
|
||||
hmac.update(input);
|
||||
}
|
||||
const hexValue = hmac.digest().toHex();
|
||||
|
||||
const newVersion = getExtendedKeyVersion(args[0]);
|
||||
|
||||
return serializeExtendedKeyFunc(newVersion, 0, "00000000", 0, hexValue.slice(64,), "00" + hexValue.slice(0, 64));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default SeedToMPK;
|
70
src/core/operations/SeedphraseToSeed.mjs
Normal file
70
src/core/operations/SeedphraseToSeed.mjs
Normal file
|
@ -0,0 +1,70 @@
|
|||
/**
|
||||
* Turns a seedphrase into the inital seed given PBKDF2.
|
||||
*
|
||||
* @author dgoldenberg [virtualcurrency@mitre.org]
|
||||
* @copyright MITRE 2023 Wei Lu <luwei.here@gmail.com> and Daniel Cousens <email@dcousens.com> 2014
|
||||
* @license ISC
|
||||
*/
|
||||
|
||||
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import Utils from "../Utils.mjs";
|
||||
import forge from "node-forge";
|
||||
|
||||
/**
|
||||
* Seedphrase to Seed Class.
|
||||
*/
|
||||
class SeedphraseToSeed extends Operation {
|
||||
|
||||
/**
|
||||
* SeedphraseToSeed Constructor.
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Seedphrase To Seed";
|
||||
this.module = "Serialize";
|
||||
this.description = "Turns a seedphrase (with possible seed passphrase) into a seed. Note, does not type or validate the input, it is assumed to be a valid seedphrase. The Extract Seedphrases Op can extract valid seedphrases from text. Supports BIP39 and Electrum2 standards.";
|
||||
this.infoURL = "https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki#from-mnemonic-to-seed";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
"name": "Seedphrase Type",
|
||||
"type": "option",
|
||||
"value": ["bip39", "electrum2"]
|
||||
},
|
||||
{
|
||||
"name": "Seed Passphrase",
|
||||
"type": "toggleString",
|
||||
"value": "",
|
||||
"toggleValues": ["UTF8"]
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const convertedSeedPhrase = Utils.convertToByteString(input.trim(), "UTF8");
|
||||
const iterations = 2048;
|
||||
const keySize = 512;
|
||||
const hasher = "SHA512";
|
||||
let salt;
|
||||
if (args[0] === "bip39") {
|
||||
salt = Utils.convertToByteString("mnemonic" + args[1].string, args[1].option) ||
|
||||
forge.random.getBytesSync(keySize);
|
||||
} else {
|
||||
salt = Utils.convertToByteString("electrum" + args[1].string, args[1].option) ||
|
||||
forge.random.getBytesSync(keySize);
|
||||
}
|
||||
const derivedKey = forge.pkcs5.pbkdf2(convertedSeedPhrase, salt, iterations, keySize / 8, hasher.toLowerCase());
|
||||
return forge.util.bytesToHex(derivedKey);
|
||||
}
|
||||
}
|
||||
|
||||
export default SeedphraseToSeed;
|
224
src/core/operations/TypeCryptoArtifact.mjs
Normal file
224
src/core/operations/TypeCryptoArtifact.mjs
Normal file
|
@ -0,0 +1,224 @@
|
|||
/**
|
||||
* Attempts to determine the type of cryptocurrency artifact passed in.
|
||||
*
|
||||
* @author dgoldenberg [virtualcurrency@mitre.org]
|
||||
* @copyright MITRE 2023, geco 2019
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import {b58DoubleSHAChecksum} from "../lib/Bitcoin.mjs";
|
||||
import { segwitChecksum } from "../lib/Bech32.mjs";
|
||||
import Operation from "../Operation.mjs";
|
||||
/**
|
||||
* Converts a Public Key to a P2PKH Address of the given type.
|
||||
*/
|
||||
class TypeCryptoArtifact extends Operation {
|
||||
/**
|
||||
* Attempts to Type a Cryptocurrency artifact.
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Type Cryptocurrency Artifact";
|
||||
this.module = "Default";
|
||||
this.description = "Attempts to type and return information about a cryptocurrency artifact. ";
|
||||
this.inputType = "string";
|
||||
this.outputType = "JSON";
|
||||
this.presentType = "string";
|
||||
this.args = [];
|
||||
this.checks = [
|
||||
{
|
||||
pattern: "^0(3|2)[0-9A-Fa-f]{64}$",
|
||||
flags: "",
|
||||
args: []
|
||||
},
|
||||
{
|
||||
pattern: "^04[0-9A-Fa-f]{128}$",
|
||||
flags: "",
|
||||
args: []
|
||||
},
|
||||
{
|
||||
pattern: "^[0-9A-Fa-f]{64}$",
|
||||
flags: "",
|
||||
args: []
|
||||
},
|
||||
{
|
||||
pattern: "^0x[0-9A-Fa-f]{40}$",
|
||||
flags: "",
|
||||
args: []
|
||||
},
|
||||
{
|
||||
pattern: "^(X|x|Y|y|Z|z|L|l|T|t)[pub|prv|tbv|tub][A-HJ-NP-Za-km-z1-9]{2,}$",
|
||||
flags: "",
|
||||
args: []
|
||||
},
|
||||
{
|
||||
pattern: "^(1|3)[a-km-zA-HJ-NP-Z1-9]{25,34}$",
|
||||
flags: "",
|
||||
args: []
|
||||
},
|
||||
{
|
||||
pattern: "^5[HJK][a-km-zA-HJ-NP-Z1-9]{49}$",
|
||||
flags: "",
|
||||
args: []
|
||||
},
|
||||
{
|
||||
pattern: "^5[HJK][a-km-zA-HJ-NP-Z1-9]{49}$",
|
||||
flags: "",
|
||||
args: []
|
||||
},
|
||||
{
|
||||
pattern: "^[KL][a-km-zA-HJ-NP-Z1-9]{51}$",
|
||||
flags: "",
|
||||
args: []
|
||||
},
|
||||
{
|
||||
pattern: "^6P[a-km-zA-HJ-NP-Z1-9]{56}$",
|
||||
flags: "",
|
||||
args: []
|
||||
},
|
||||
{
|
||||
pattern: "^(bc|tb)1q[023456789ac-hj-np-z]{36,}$",
|
||||
flags: "",
|
||||
args: []
|
||||
},
|
||||
{
|
||||
pattern: "^(bc|tb)1p[023456789ac-hj-np-z]{36,}$",
|
||||
flags: "",
|
||||
args: []
|
||||
}
|
||||
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the return data to be easier to read.
|
||||
* @param {Object} output
|
||||
* @returns {string}
|
||||
*/
|
||||
present(output) {
|
||||
switch (output.type) {
|
||||
case "P2PKH":
|
||||
return "Input " + output.value + " is possibly a Bitcoin P2PKH address.\n";
|
||||
case "P2SH":
|
||||
return "Input " + output.value + " is possibly a P2SH, or P2SH-P2WPKH address.\n";
|
||||
case "XPUB":
|
||||
return "Input " + output.value + " is possibly an extended public key.\n";
|
||||
case "XPRV":
|
||||
return "Input " + output.value + " is possibly an extended private key.\n";
|
||||
case "compressedWIF":
|
||||
return "Input " + output.value + " is possibly a compressed WIF (Wallet-Input-Format) Key.\n";
|
||||
case "uncompressedWIF":
|
||||
return "Input " + output.value + " is possibly an uncompressed WIF (Wallet-Input-Format) Key.\n";
|
||||
case "eth":
|
||||
return "Input " + output.value + " is possibly an ethereum address.\n";
|
||||
case "bip38":
|
||||
return "Input " + output.value + " is possibly a BIP38 encrypted key.\n";
|
||||
case "segwit":
|
||||
return "Input " + output.value + " is possibly a Segwit address (P2WPKH/P2WSH).\n";
|
||||
case "taproot":
|
||||
return "Input " + output.value + " is possibly a taproot.\n";
|
||||
case "compressed_public":
|
||||
return "Input " + output.value + " is possibly a compressed public key in raw bytes.\n";
|
||||
case "uncompressed_public":
|
||||
return "Input " + output.value + " is possibly an uncompressed public key in raw bytes.\n";
|
||||
case "private":
|
||||
return "Input " + output.value + " is possibly a private key in raw bytes.\n";
|
||||
case "unknownB58":
|
||||
return "Input " + output.value + " is a B58 encoded string, but of unknown type.\n";
|
||||
case "unknownSegwit":
|
||||
return "Input " + output.value + " is a Segwit encoded string, but of unknown type.\n";
|
||||
case "error":
|
||||
return "Error with input " + output.value + " \n";
|
||||
case "blank":
|
||||
return "";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {Object}
|
||||
*/
|
||||
run(input, args) {
|
||||
// We check if input is blank.
|
||||
// If its blank or just whitespace, we don't need to bother dealing with it.
|
||||
if (input.trim() === "") {
|
||||
return {"value": input, "type": "blank"};
|
||||
}
|
||||
input = input.trim();
|
||||
// We check to see if the input is hex or not.
|
||||
const hexRe = /^[0-9A-Fa-f]{4,}$/g;
|
||||
const inputIsHex = hexRe.test(input);
|
||||
if (inputIsHex) {
|
||||
const compressedPubkeyRe = /^0(3|2)[0-9A-Fa-f]{64}$/g;
|
||||
const uncompressedPubKeyRe = /^04[0-9A-Fa-f]{128}$/g;
|
||||
const privateKeyRe = /^[0-9A-Fa-f]{64}$/g;
|
||||
if (compressedPubkeyRe.test(input)) {
|
||||
return {"value": input, "type": "compressed_public"};
|
||||
}
|
||||
if (uncompressedPubKeyRe.test(input)) {
|
||||
return {"value": input, "type": "uncompressed_public"};
|
||||
}
|
||||
if (privateKeyRe.test(input)) {
|
||||
return {"value": input, "type": "private"};
|
||||
}
|
||||
|
||||
} else {
|
||||
const ethereumRe = /^0x[0-9A-Fa-f]{40}$/g;
|
||||
if (ethereumRe.test(input)) {
|
||||
return {"value": input, "type": "eth"};
|
||||
}
|
||||
const b58Checksum = b58DoubleSHAChecksum(input);
|
||||
|
||||
const sChecksum = segwitChecksum(input);
|
||||
if (b58Checksum) {
|
||||
const xRe = /^(X|x|Y|y|Z|z|L|l|T|t)[pub|prv|tbv|tub][A-HJ-NP-Za-km-z1-9]{2,}$/g;
|
||||
const btcRe = /^(1|3)[a-km-zA-HJ-NP-Z1-9]{25,34}$/g;
|
||||
const wifUncompressedRe = /^5[HJK][a-km-zA-HJ-NP-Z1-9]{49}$/g;
|
||||
const wifCompressedRe = /^[KL][a-km-zA-HJ-NP-Z1-9]{51}$/g;
|
||||
const bip38Re = /^6P[a-km-zA-HJ-NP-Z1-9]{56}$/g;
|
||||
|
||||
if (xRe.test(input)) {
|
||||
if (input.slice(0, 4).includes("tbv") || input.slice(0, 4).includes("prv")) {
|
||||
return {"value": input, "type": "XPRV"};
|
||||
} else {
|
||||
return {"value": input, "type": "XPUB"};
|
||||
}
|
||||
} else if (btcRe.test(input)) {
|
||||
if (input.startsWith("1")) {
|
||||
return {"value": input, "type": "P2PKH"};
|
||||
} else {
|
||||
return {"value": input, "type": "P2SH"};
|
||||
}
|
||||
} else if (wifCompressedRe.test(input)) {
|
||||
return {"value": input, "type": "compressedWIF"};
|
||||
} else if (wifUncompressedRe.test(input)) {
|
||||
return {"value": input, "type": "uncompressedWIF"};
|
||||
} else if (bip38Re.test(input)) {
|
||||
return {"value": input, "type": "bip38"};
|
||||
} else {
|
||||
return {"value": input, "type": "unknownB58"};
|
||||
}
|
||||
}
|
||||
if (sChecksum) {
|
||||
const oldSegwitRe = /^(bc|tb)1q[023456789ac-hj-np-z]{36,}$/;
|
||||
const newSegwitRe = /^(bc|tb)1p[023456789ac-hj-np-z]{36,}$/;
|
||||
|
||||
if (oldSegwitRe.test(input)) {
|
||||
return {"value": input, "type": "segwit"};
|
||||
} else if (newSegwitRe.test(input)) {
|
||||
return {"value": input, "type": "taproot"};
|
||||
} else {
|
||||
return {"value": input, "type": "unknownSegwit"};
|
||||
}
|
||||
}
|
||||
return {"value": input, "type": "error"};
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
export default TypeCryptoArtifact;
|
77
src/core/operations/WIFToPrivateKey.mjs
Normal file
77
src/core/operations/WIFToPrivateKey.mjs
Normal file
|
@ -0,0 +1,77 @@
|
|||
/**
|
||||
* Extracts the private key from a WIF format key.
|
||||
*
|
||||
* @author dgoldenberg [virtualcurrency@mitre.org]
|
||||
* @copyright MITRE 2023
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import { base58Decode, b58DoubleSHAChecksum} from "../lib/Bitcoin.mjs";
|
||||
import { fromArrayBuffer } from "crypto-api/src/encoder/array-buffer.mjs";
|
||||
import {toHex} from "crypto-api/src/encoder/hex.mjs";
|
||||
|
||||
|
||||
/**
|
||||
* Converts a private key to the WIF format.
|
||||
*/
|
||||
class WIFToPrivateKey extends Operation {
|
||||
|
||||
/**
|
||||
* Converts a private key to the WIF format.
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "From WIF Format";
|
||||
this.module = "Default";
|
||||
this.description = "Turns a WIF format cryptocurrency key into the 32 byte private key. ";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.infoURL = "https://en.bitcoin.it/wiki/Wallet_import_format";
|
||||
this.args = [
|
||||
];
|
||||
this.checks = [
|
||||
{
|
||||
"pattern": "^5[HJK][a-km-zA-HJ-NP-Z1-9]{49}$",
|
||||
"flags": "",
|
||||
"args": []
|
||||
},
|
||||
{
|
||||
"pattern": "^[KL][a-km-zA-HJ-NP-Z1-9]{51}$",
|
||||
"flags": "",
|
||||
"args": []
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
// We check if input is blank.
|
||||
// If its blank or just whitespace, we don't need to bother dealing with it.
|
||||
|
||||
if (input.trim().length === 0) {
|
||||
return "";
|
||||
}
|
||||
input = input.trim();
|
||||
if (b58DoubleSHAChecksum(input)) {
|
||||
const decoded = base58Decode(input);
|
||||
const trimmed = toHex(fromArrayBuffer(decoded.slice(1, -4)));
|
||||
if (trimmed.endsWith("01") && trimmed.length === 66) {
|
||||
return trimmed.slice(0, -2);
|
||||
} else {
|
||||
return trimmed;
|
||||
}
|
||||
} else {
|
||||
return "Invalid Checksum. May not be a private Key. ";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default WIFToPrivateKey;
|
Loading…
Add table
Add a link
Reference in a new issue