mirror of
https://github.com/gchq/CyberChef.git
synced 2025-05-11 16:51:31 -04:00
First Cryptochef branch commit.
This commit is contained in:
parent
415c59d74c
commit
f79ca6cf02
36 changed files with 7134 additions and 4 deletions
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
Loading…
Add table
Add a link
Reference in a new issue