mirror of
https://github.com/gchq/CyberChef.git
synced 2025-05-07 15:07:11 -04:00
355 lines
10 KiB
JavaScript
355 lines
10 KiB
JavaScript
/**
|
|
* 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 #########################################################
|
|
|