mirror of
https://github.com/gchq/CyberChef.git
synced 2025-04-25 01:06:16 -04:00
Added BIP32Derive, which uses bip32 from bitcoinjs. Modified a few more operations. Code compiles with 20.3.0
This commit is contained in:
parent
810c94803d
commit
f474ffaf72
17 changed files with 764 additions and 170 deletions
|
@ -320,7 +320,9 @@
|
|||
"Seedphrase To Seed",
|
||||
"Change Extended Key Version",
|
||||
"Seed To Master Key",
|
||||
"Decrypt Keystore File"
|
||||
"Decrypt Keystore File",
|
||||
"BIP32Derive",
|
||||
"Public Key To ETH Style Address"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
|
@ -13,9 +13,9 @@
|
|||
* Javascript code below taken from:
|
||||
* https://github.com/geco/bech32-js/blob/master/bech32-js.js
|
||||
* Implements various segwit encoding / decoding functions.
|
||||
*
|
||||
*
|
||||
* MIT License
|
||||
*
|
||||
*
|
||||
* Copyright (c) 2019 geco
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -23,10 +23,10 @@
|
|||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
|
|
|
@ -12,6 +12,126 @@ import {toHex} from "crypto-api/src/encoder/hex.mjs";
|
|||
import Utils from "../Utils.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
|
||||
/**
|
||||
* Validates the length of the passed in input as one of the allowable lengths.
|
||||
* @param {*} input
|
||||
* @param {*} allowableLengths
|
||||
* @returns
|
||||
*/
|
||||
function validateLengths(input, allowableLengths) {
|
||||
return allowableLengths.includes(input.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if input is a valid hex string, false otherwise.
|
||||
* @param {*} input
|
||||
*/
|
||||
function isHex(input) {
|
||||
const re = /^[0-9A-Fa-f]{2,}$/g;
|
||||
return re.test(input);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if input could be interpreted as a byte string, false otherwise.
|
||||
*/
|
||||
function isValidBytes(input) {
|
||||
for (let i=0; i < input.length; i ++) {
|
||||
if (input.charCodeAt(i) > 255) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* We validate a passed in input to see if it could be a valid private key.
|
||||
* A valid private key is string of length 64 that is valid hex, or of length 32 that could be valid bytes.
|
||||
* @param {*} input
|
||||
*/
|
||||
export function validatePrivateKey(input) {
|
||||
const curInput = input.trim();
|
||||
if (!validateLengths(curInput, [32, 64])) {
|
||||
return "Invalid length. We want either 32 or 64 but we got: " + curInput.length;
|
||||
}
|
||||
if (curInput.length === 64 && !isHex(curInput)) {
|
||||
return "We have a string of length 64, but not valid hex. Cannot be interpreted as a private key.";
|
||||
}
|
||||
if (curInput.length === 32 && !isValidBytes(curInput)) {
|
||||
return "We have a string of length 32 but cannot cannot be interpreted as valid bytes.";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* We validate a passed in input to see if it could be a valid public key.
|
||||
* A valid public key (in bytes) is either:
|
||||
* 65 bytes beginning with 04
|
||||
* 33 bytes beginning with 02 or 03
|
||||
* @param {*} input
|
||||
*/
|
||||
export function validatePublicKey(input) {
|
||||
const curInput = input.trim();
|
||||
if (!validateLengths(curInput, [33, 65, 66, 130])) {
|
||||
return "Invalid length. We want either 33, 65 (if bytes) or 66, 130 (if hex) but we got: " + curInput.length;
|
||||
}
|
||||
if (isHex(curInput)) {
|
||||
if (!validateLengths(curInput, [66, 130])) {
|
||||
return "We have a hex string, but its length is wrong. We want 66, 130 but we got: " + curInput.length;
|
||||
}
|
||||
if (curInput.length === 66 && (curInput.slice(0, 2) !== "02" && curInput.slice(0, 2) !== "03")) {
|
||||
return "We have a valid hex string, of reasonable length, (66) but doesn't start with the right value. Correct values are 02, or 03 but we have: " + curInput.slice(0, 2);
|
||||
}
|
||||
if (curInput.length === 130 && curInput.slice(0, 2) !== "04") {
|
||||
return "We have a valid hex string of reasonable length, (130) but doesn't start with the right value. Correct values are 04 but we have: " + curInput.slice(0, 2);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
if (isValidBytes(curInput)) {
|
||||
if (!validateLengths(curInput, [33, 65])) {
|
||||
return "We have a byte string, but its length is wrong. We want 33 or 65 but we got: " + curInput.length;
|
||||
}
|
||||
if (curInput.length === 33 && toHex(curInput[0]) !== "02" && toHex(curInput[0]) !== "03") {
|
||||
return "We have a valid byte string, of reasonable length, (33) but doesn't start with the right value. Correct values are 02, or 03 but we have: " + toHex(curInput[0]) ;
|
||||
}
|
||||
if (curInput.length === 65 && toHex(curInput[0]) !== "04") {
|
||||
return "We have a valid byte string, of reasonable length, (65) but doesn't start with the right value. Correct value is 04 but we have: " + toHex(curInput[0]);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* We make sure the input is a valid hex string, regardless of if its hex or bytes.
|
||||
* If not valid bytes or hex, we throw TypeError.
|
||||
* @param {*} input
|
||||
* @returns
|
||||
*/
|
||||
export function makeSureIsHex(input) {
|
||||
if (!(isValidBytes(input)) && !(isHex(input))) {
|
||||
throw TypeError("Input: " + input + " is not valid bytes or hex.");
|
||||
}
|
||||
if (isValidBytes(input) && !isHex(input)) {
|
||||
return toHex(input);
|
||||
}
|
||||
return input;
|
||||
}
|
||||
|
||||
/**
|
||||
* We make sure the input is valid bytes, regardless of if its hex or bytes.
|
||||
* If not valid bytes or hex, we throw TypeError.
|
||||
* @param {*} input
|
||||
*/
|
||||
export function makeSureIsBytes(input) {
|
||||
if (!(isValidBytes(input)) && !(isHex(input))) {
|
||||
throw TypeError("Input: " + input + " is not valid bytes or hex.");
|
||||
}
|
||||
if (isHex(input)) {
|
||||
return fromArrayBuffer(Utils.convertToByteArray(input, "hex"));
|
||||
}
|
||||
return input;
|
||||
}
|
||||
|
||||
// ################################################ BEGIN HELPER HASH FUNCTIONS #################################################
|
||||
|
||||
// SHA256(SHA256(input))
|
||||
|
@ -212,6 +332,35 @@ export function deserializeExtendedKeyFunc (input) {
|
|||
}
|
||||
}
|
||||
|
||||
// Reverse lookup for version bytes
|
||||
const versionString = {
|
||||
"043587cf": "tpub",
|
||||
"04358394": "tprv",
|
||||
"044a5262": "upub",
|
||||
"044a4e28": "uprv",
|
||||
"045f1cf6": "vpub",
|
||||
"045f18bc": "vprv",
|
||||
"024289ef": "Upub",
|
||||
"024285b5": "Uprv",
|
||||
"02575483": "Vpub",
|
||||
"02575048": "Vprv",
|
||||
"0488b21e": "xpub",
|
||||
"0488ade4": "xprv",
|
||||
"049d7cb2": "ypub",
|
||||
"049d7878": "yprv",
|
||||
"04b24746": "zpub",
|
||||
"04b2430c": "zprv",
|
||||
"02aa7ed3": "Zpub",
|
||||
"02aa7a99": "Zprv",
|
||||
"0295b43f": "Ypub",
|
||||
"0295b005": "Yprv",
|
||||
"019da462": "Ltub",
|
||||
"019d9cfe": "Ltpv",
|
||||
"01b26ef6": "Mtub",
|
||||
"01b26792": "Mtpv",
|
||||
"0436f6e1": "ttub",
|
||||
"0436ef7d": "ttpv"
|
||||
};
|
||||
|
||||
// Version byte dictionary.
|
||||
const versionBytes = {
|
||||
|
@ -253,6 +402,16 @@ export function getExtendedKeyVersion(input) {
|
|||
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse lookup for version string. We take in bytes, output string.
|
||||
* @param {*} input
|
||||
* @returns
|
||||
*/
|
||||
export function getExtendedKeyString(input) {
|
||||
return versionString[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.
|
||||
|
@ -296,6 +455,12 @@ const versionByteInfo = {
|
|||
"P2SH": "C4",
|
||||
"WIF": "EF",
|
||||
"hrp": "tb"
|
||||
},
|
||||
"LTC": {
|
||||
"hrp": "ltc",
|
||||
"P2PKH": "30",
|
||||
"P2SH": "32",
|
||||
"WIF": "B0"
|
||||
}
|
||||
};
|
||||
|
||||
|
|
93
src/core/operations/BIP32Derive.mjs
Normal file
93
src/core/operations/BIP32Derive.mjs
Normal file
|
@ -0,0 +1,93 @@
|
|||
/**
|
||||
* @author dgoldenberg [virtualcurrency@mitre.org]
|
||||
* @copyright Crown Copyright 2023
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
// import OperationError from "../errors/OperationError.mjs";
|
||||
import { b58DoubleSHAChecksum} from "../lib/Bitcoin.mjs";
|
||||
import { BIP32Factory} from "bip32";
|
||||
import ecc from "@bitcoinerlab/secp256k1";
|
||||
|
||||
/**
|
||||
* Sanity checks a derivation path.
|
||||
* @param {*} input
|
||||
*/
|
||||
function verifyDerivationPath(input) {
|
||||
const splitResults = input.split("/");
|
||||
let startIndex = 0;
|
||||
// We skip the first index if its m, as that's common.
|
||||
if (splitResults[0] === "m") {
|
||||
startIndex = 1;
|
||||
}
|
||||
for (let i =startIndex; i < splitResults.length; i++) {
|
||||
const re = /^[0-9]{1,}[']{0,1}$/g;
|
||||
if (!re.test(splitResults[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* BIP32Derive operation
|
||||
*/
|
||||
class BIP32Derive extends Operation {
|
||||
|
||||
/**
|
||||
* BIP32Derive constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "BIP32Derive";
|
||||
this.module = "Default";
|
||||
this.description = "Takes in an extended key, performs BIP32 key derivation on the extended key, and returns the result as an extended key.";
|
||||
this.infoURL = "https://en.bitcoin.it/wiki/BIP_0032";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
"name": "Derivation Path",
|
||||
"type": "string",
|
||||
"value": ""
|
||||
},
|
||||
];
|
||||
this.checks = [
|
||||
{
|
||||
"pattern": "^(X|x|Y|y|Z|z|L|l|T|t)[pub|prv|tbv|tub][A-HJ-NP-Za-km-z1-9]{2,}$",
|
||||
"flags": "",
|
||||
"args": []
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
// We check if input is blank.
|
||||
// If its blank or just whitespace, we don't need to bother dealing with it.
|
||||
if (input.trim().length === 0) {
|
||||
return "";
|
||||
}
|
||||
input = input.trim();
|
||||
if (!verifyDerivationPath(args[0])) {
|
||||
return "Invalid derivation path: " + args[0] + "\n";
|
||||
}
|
||||
const xkeyRe = /^(X|x|Y|y|Z|z|L|l|T|t)[pub|prv|tbv|tub][A-HJ-NP-Za-km-z1-9]{2,}$/g;
|
||||
if (!b58DoubleSHAChecksum(input) || !xkeyRe.test(input)) {
|
||||
return "Possibly invalid Extended Key: " + input + "\n";
|
||||
}
|
||||
const bip32 = BIP32Factory(ecc);
|
||||
const node = bip32.fromBase58(input);
|
||||
const child = node.derivePath(args[0]);
|
||||
return child.toBase58();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default BIP32Derive;
|
|
@ -7,9 +7,11 @@
|
|||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import {toHex} from "../lib/Hex.mjs";
|
||||
import ec from "elliptic";
|
||||
import { validatePrivateKey, makeSureIsHex} from "../lib/Bitcoin.mjs";
|
||||
// import { toHex } from "crypto-api/src/encoder/hex.mjs";
|
||||
|
||||
// const curves = ["secp256k1", "ed25519", "curve25519", "p521", "p384", "p256", "p224", "p192"];
|
||||
/**
|
||||
* Class that takes in a private key, and returns the public key, either in compressed or uncompressed form(s).
|
||||
*/
|
||||
|
@ -56,24 +58,15 @@ class PrivateECKeyToPublic extends Operation {
|
|||
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 privKeyCheck = validatePrivateKey(input);
|
||||
|
||||
if (privKeyCheck.trim().length !== 0) {
|
||||
return "Error with the input as private key. Error is:\n\t" + privKeyCheck;
|
||||
}
|
||||
const processedInput = makeSureIsHex(input);
|
||||
const ecContext = ec.ec("secp256k1");
|
||||
const key = ecContext.keyFromPrivate(input);
|
||||
const key = ecContext.keyFromPrivate(processedInput);
|
||||
const pubkey = key.getPublic(args[0], "hex");
|
||||
|
||||
return pubkey;
|
||||
|
|
|
@ -7,10 +7,10 @@
|
|||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import { base58Encode, getWIFVersionByte, doubleSHA} from "../lib/Bitcoin.mjs";
|
||||
import { base58Encode, getWIFVersionByte, doubleSHA, validatePrivateKey, makeSureIsHex} from "../lib/Bitcoin.mjs";
|
||||
import { fromArrayBuffer } from "crypto-api/src/encoder/array-buffer.mjs";
|
||||
import {toHex} from "crypto-api/src/encoder/hex.mjs";
|
||||
import {toHex as toHexOther} from "../lib/Hex.mjs";
|
||||
// import {toHex as toHexOther} from "../lib/Hex.mjs";
|
||||
import Utils from "../Utils.mjs";
|
||||
|
||||
|
||||
|
@ -65,26 +65,13 @@ class PrivateKeyToWIF extends Operation {
|
|||
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;
|
||||
const privateKeyCheck = validatePrivateKey(input);
|
||||
if (privateKeyCheck.trim().length !== 0) {
|
||||
return "Error parsing private key. Error is:\n\t" + privateKeyCheck;
|
||||
}
|
||||
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 processedKey = makeSureIsHex(input);
|
||||
const versionByte = getWIFVersionByte(args[0]);
|
||||
let extendedPrivateKey = versionByte + input;
|
||||
let extendedPrivateKey = versionByte + processedKey;
|
||||
if (args[1]) {
|
||||
extendedPrivateKey += "01";
|
||||
}
|
||||
|
|
87
src/core/operations/PublicKeyToETHStyleAddress.mjs
Normal file
87
src/core/operations/PublicKeyToETHStyleAddress.mjs
Normal file
|
@ -0,0 +1,87 @@
|
|||
/**
|
||||
* @author dgoldenberg [virtualcurrency@mitre.org]
|
||||
* @copyright Crown Copyright 2023
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import { makeSureIsBytes, validatePublicKey} from "../lib/Bitcoin.mjs";
|
||||
import JSSHA3 from "js-sha3";
|
||||
import Utils from "../Utils.mjs";
|
||||
import ec from "elliptic";
|
||||
|
||||
/**
|
||||
* Turns a public key into an ETH address.
|
||||
* @param {*} input Input, a public key in hex or bytes.
|
||||
*/
|
||||
function pubKeyToETHAddress(input) {
|
||||
// Ethereum addresses require uncompressed public keys.
|
||||
// We convert if the public key is compressed.
|
||||
let curKey = makeSureIsBytes(input);
|
||||
if (curKey[0] !== 0x04 || curKey.length !== 65) {
|
||||
const ecContext = ec.ec("secp256k1");
|
||||
const thisKey = ecContext.keyFromPublic(curKey);
|
||||
curKey = thisKey.getPublic(false, "hex");
|
||||
}
|
||||
const algo = JSSHA3.keccak256;
|
||||
// We need to redo the hex-> bytes transformation here because Javascript is silly.
|
||||
// sometimes what is desired is an array of ints.
|
||||
// Other times a string
|
||||
// Here, the Keccak algorithm seems to want an array of ints. (sigh)
|
||||
const result = algo(Utils.convertToByteArray(curKey, "hex").slice(1,));
|
||||
return "0x" + result.slice(-40);
|
||||
}
|
||||
|
||||
/**
|
||||
* PublicKeyToETHStyleAddress operation
|
||||
*/
|
||||
class PublicKeyToETHStyleAddress extends Operation {
|
||||
|
||||
/**
|
||||
* PublicKeyToETHStyleAddress constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Public Key To ETH Style Address";
|
||||
this.module = "Default";
|
||||
this.description = "Converts a public key (compressed or uncompressed) to an Ethereum style address.";
|
||||
this.infoURL = "https://www.freecodecamp.org/news/how-to-create-an-ethereum-wallet-address-from-a-private-key-ae72b0eee27b/";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [];
|
||||
this.checks = [
|
||||
{
|
||||
pattern: "^0[3|2][a-fA-F0-9]{64}$",
|
||||
flags: "",
|
||||
args: []
|
||||
},
|
||||
{
|
||||
pattern: "^04[a-fA-F0-9]{128}$",
|
||||
flags: "",
|
||||
args: []
|
||||
}
|
||||
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
// We check if input is blank.
|
||||
// If its blank or just whitespace, we don't need to bother dealing with it.
|
||||
if (input.trim().length === 0) {
|
||||
return "";
|
||||
}
|
||||
if (validatePublicKey(input) !== "") {
|
||||
return validatePublicKey(input);
|
||||
}
|
||||
return pubKeyToETHAddress(input);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default PublicKeyToETHStyleAddress;
|
|
@ -9,11 +9,11 @@
|
|||
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 { base58Encode, getP2PKHVersionByte, getP2SHVersionByte, hash160Func, doubleSHA, getHumanReadablePart, makeSureIsBytes, validatePublicKey} 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.
|
||||
*/
|
||||
|
@ -27,19 +27,19 @@ class PublicKeyToP2PKHAddress extends Operation {
|
|||
|
||||
this.name = "Public Key To Cryptocurrency Address";
|
||||
this.module = "Default";
|
||||
this.description = "Turns a public key into a cryptocurrency address.";
|
||||
this.description = "Turns a public key into a cryptocurrency address. Can select P2PKH, P2SH-P2WPKH and P2WPKH addresses for Bitcoin and Testnet.";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
"name": "Currency Type",
|
||||
"type": "option",
|
||||
"value": ["BTC", "Testnet", "Ethereum"]
|
||||
"value": ["BTC", "Testnet", "LTC"]
|
||||
},
|
||||
{
|
||||
"name": "Address Type",
|
||||
"type": "option",
|
||||
"value": ["P2PKH (V1 BTC Addresses)", "P2SH-P2PWPKH (Segwit Compatible)", "Segwit (P2WPKH)"]
|
||||
"value": ["P2PKH (V1 BTC Addresses)", "P2SH-P2PWPKH (Segwit Compatible V3 Addresses)", "Segwit (P2WPKH bc1 Addresses)"]
|
||||
}
|
||||
];
|
||||
this.checks = [
|
||||
|
@ -48,11 +48,6 @@ class PublicKeyToP2PKHAddress extends Operation {
|
|||
flags: "",
|
||||
args: ["BTC", "P2PKH (V1 BTC Addresses)"]
|
||||
},
|
||||
{
|
||||
pattern: "^04[a-fA-F0-9]{128}$",
|
||||
flags: "",
|
||||
args: ["Ethereum", "P2PKH (V1 BTC Addresses)"]
|
||||
}
|
||||
|
||||
];
|
||||
}
|
||||
|
@ -68,75 +63,39 @@ class PublicKeyToP2PKHAddress extends Operation {
|
|||
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"));
|
||||
if (validatePublicKey(input) !== "") {
|
||||
return validatePublicKey(input);
|
||||
}
|
||||
|
||||
// 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]);
|
||||
// We hash the input
|
||||
const curInput = makeSureIsBytes(input);
|
||||
const hash160 = toHex(hash160Func(curInput));
|
||||
// We do segwit addresses first.
|
||||
if (args[1] === "Segwit (P2WPKH bc1 Addresses)") {
|
||||
const redeemScript = hash160;
|
||||
const hrp = getHumanReadablePart(args[0]);
|
||||
if (hrp !== "") {
|
||||
return encodeProgramToSegwit(hrp, 0, Utils.convertToByteArray(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;
|
||||
return args[0] + " does not support Segwit Addresses.";
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
// It its not segwit, we create the redeemScript either for P2PKH or P2SH-P2WPKH addresses.
|
||||
const versionByte = "P2PKH (V1 BTC Addresses)" === args[1] ? getP2PKHVersionByte(args[0]) : getP2SHVersionByte(args[0]);
|
||||
// If its a P2SH-P2WPKH address, we have to prepend some extra bytes and hash again. Either way we prepend the version byte.
|
||||
let hashRedeemedScript;
|
||||
if (args[1] === "P2SH-P2PWPKH (Segwit Compatible V3 Addresses)") {
|
||||
const redeemScript = "0014" + hash160;
|
||||
hashRedeemedScript = versionByte + toHex(hash160Func(fromArrayBuffer(Utils.convertToByteArray(redeemScript, "hex"))));
|
||||
} else {
|
||||
hashRedeemedScript = versionByte + hash160;
|
||||
}
|
||||
|
||||
// We calculate the checksum, convert to Base58 and then we're done!
|
||||
const checksumHash = toHex(doubleSHA(fromArrayBuffer(Utils.convertToByteArray(hashRedeemedScript, "hex"))));
|
||||
const finalString = hashRedeemedScript + checksumHash.slice(0, 8);
|
||||
const address = base58Encode(Utils.convertToByteArray(finalString, "hex"));
|
||||
return address;
|
||||
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue