mirror of
https://github.com/gchq/CyberChef.git
synced 2025-06-14 10:14:53 -04:00
Added support for public key -> P2TR addresses. Also renamed Public Key to Cryptocurrency Address Operation to Public Key to Bitcoin-Like Address Operation.
This commit is contained in:
parent
d745a516cb
commit
5605807778
4 changed files with 155 additions and 48 deletions
|
@ -345,7 +345,7 @@
|
|||
"Extract Segwit Addresses",
|
||||
"Extract Seedphrases",
|
||||
"Deserialize Extended Key",
|
||||
"Public Key To Cryptocurrency Address",
|
||||
"Public Key To Bitcoin-Like Address",
|
||||
"To WIF Format",
|
||||
"From WIF Format",
|
||||
"Type Cryptocurrency Artifact",
|
||||
|
|
|
@ -7,10 +7,12 @@
|
|||
*/
|
||||
|
||||
import CryptoApi from "crypto-api/src/crypto-api.mjs";
|
||||
import { fromArrayBuffer } from "crypto-api/src/encoder/array-buffer.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";
|
||||
import BigNumber from "bignumber.js";
|
||||
|
||||
|
||||
/**
|
||||
* Validates the length of the passed in input as one of the allowable lengths.
|
||||
|
@ -162,6 +164,57 @@ export function hash160Func(input) {
|
|||
return ripemdHasher.finalize();
|
||||
}
|
||||
|
||||
// Tag Hash defined in https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki
|
||||
/**
|
||||
* Tag Hash defined in BIP340 https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki
|
||||
* Hash is defined as SHA256(SHA256(tag) || SHA256(tag) || x)
|
||||
* @param {*} input
|
||||
* @returns
|
||||
*/
|
||||
export function tweakHash(input) {
|
||||
const sha256Hasher = CryptoApi.getHasher("sha256");
|
||||
sha256Hasher.update("TapTweak");
|
||||
const tagHash = sha256Hasher.finalize();
|
||||
const sha256Hasher2 = CryptoApi.getHasher("sha256");
|
||||
sha256Hasher2.update(tagHash);
|
||||
sha256Hasher2.update(tagHash);
|
||||
sha256Hasher2.update(input);
|
||||
const result = sha256Hasher2.finalize();
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given x, returns the point P(x) where the y-coordinate is even. Fails if x is greater than p-1 or if the point does not exist.
|
||||
* Since this is mostly going to be used for analysis and not key derivation, failure should be rare but we check anyway.
|
||||
* @param {*} input
|
||||
* @returns
|
||||
*/
|
||||
export function liftX(input) {
|
||||
const three = BigNumber(3);
|
||||
const seven = BigNumber(7);
|
||||
const one = BigNumber(1);
|
||||
const four = BigNumber(4);
|
||||
const two = BigNumber(2);
|
||||
|
||||
const pHex ="0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F";
|
||||
const p = BigNumber(pHex, 16);
|
||||
const x = BigNumber("0x" + makeSureIsHex(input), 16);
|
||||
if (x.comparedTo(p) === 1) {
|
||||
return -1;
|
||||
} else {
|
||||
const temp = x.pow(three, p).plus(seven);
|
||||
const ySQ = temp.mod(p);
|
||||
const tempExp = (p.plus(one)).idiv(four);
|
||||
const y = ySQ.pow(tempExp, p);
|
||||
if (y.pow(two, p).comparedTo(ySQ) !== 0) {
|
||||
return -1;
|
||||
} else {
|
||||
return input;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// ################################################ END HELPER HASH FUNCTIONS ###################################################
|
||||
|
||||
|
||||
|
|
|
@ -9,11 +9,37 @@
|
|||
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, makeSureIsBytes, validatePublicKey} from "../lib/Bitcoin.mjs";
|
||||
import { base58Encode, getP2PKHVersionByte, getP2SHVersionByte, hash160Func, doubleSHA, getHumanReadablePart, makeSureIsBytes, validatePublicKey, tweakHash, liftX, makeSureIsHex} from "../lib/Bitcoin.mjs";
|
||||
import {encodeProgramToSegwit} from "../lib/Bech32.mjs";
|
||||
import Utils from "../Utils.mjs";
|
||||
import ec from "elliptic";
|
||||
|
||||
/**
|
||||
* Tweaks the key in compliance with BIP340. Needed for creating P2TR addresses.
|
||||
* @param {*} input
|
||||
*/
|
||||
function tweakKey(input) {
|
||||
// First EC Context.
|
||||
const ecContext = ec.ec("secp256k1");
|
||||
|
||||
// We lift the passed in, input, dropping the first byte.
|
||||
const liftedKey = liftX(makeSureIsHex(input).slice(2,));
|
||||
if (liftedKey === -1)
|
||||
return -1;
|
||||
// We then run the input through the tweakHash, getting the first tweaked Private Key;
|
||||
const tweakedKey = tweakHash(makeSureIsBytes(liftedKey));
|
||||
// We turn the first private key, into a SECP256k1 Key.
|
||||
const key = ecContext.keyFromPrivate(makeSureIsHex(tweakedKey));
|
||||
|
||||
// We take the lifted key, cast it back to a public key
|
||||
const newKey = "02".concat(makeSureIsHex(liftedKey));
|
||||
const ecContext1 = ec.ec("secp256k1");
|
||||
const otherKey = ecContext1.keyFromPublic(newKey, "hex");
|
||||
|
||||
// We add the public keys together and return the result as compressed.
|
||||
const final = otherKey.getPublic().add(key.getPublic());
|
||||
return final.encodeCompressed("hex");
|
||||
}
|
||||
/**
|
||||
* Converts a Public Key to a P2PKH Address of the given type.
|
||||
*/
|
||||
|
@ -25,9 +51,9 @@ class PublicKeyToP2PKHAddress extends Operation {
|
|||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Public Key To Cryptocurrency Address";
|
||||
this.name = "Public Key To Bitcoin-Like Address";
|
||||
this.module = "Default";
|
||||
this.description = "Turns a public key into a cryptocurrency address. Can select P2PKH, P2SH-P2WPKH and P2WPKH addresses for Bitcoin and Testnet.";
|
||||
this.description = "Turns a public key into a Bitcoin-Like cryptocurrency address. Can select P2PKH, P2SH-P2WPKH, P2WPKH and P2TR addresses for Bitcoin and Testnet.";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
|
@ -39,7 +65,7 @@ class PublicKeyToP2PKHAddress extends Operation {
|
|||
{
|
||||
"name": "Address Type",
|
||||
"type": "option",
|
||||
"value": ["P2PKH (V1 BTC Addresses)", "P2SH-P2PWPKH (Segwit Compatible V3 Addresses)", "Segwit (P2WPKH bc1 Addresses)"]
|
||||
"value": ["P2PKH (V1 BTC Addresses)", "P2SH-P2PWPKH (Segwit Compatible V3 Addresses)", "Segwit (P2WPKH bc1 Addresses)", "Taproot (P2TR bc1p Addresses)"]
|
||||
}
|
||||
];
|
||||
this.checks = [
|
||||
|
@ -66,36 +92,45 @@ class PublicKeyToP2PKHAddress extends Operation {
|
|||
if (validatePublicKey(input) !== "") {
|
||||
return validatePublicKey(input);
|
||||
}
|
||||
|
||||
// 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;
|
||||
// P2TR are their own separate case. We handle those first.
|
||||
if (args[1] === "Taproot (P2TR bc1p Addresses)") {
|
||||
const hrp = getHumanReadablePart(args[0]);
|
||||
if (hrp !== "") {
|
||||
return encodeProgramToSegwit(hrp, 0, Utils.convertToByteArray(redeemScript, "hex"));
|
||||
} else {
|
||||
return args[0] + " does not support Segwit Addresses.";
|
||||
const resultKey = tweakKey(input);
|
||||
if (resultKey === -1) {
|
||||
return "Error: Bad Public Key to turn into P2TR 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"))));
|
||||
return encodeProgramToSegwit(hrp, 1, Utils.convertToByteArray(resultKey.slice(2,), "hex"));
|
||||
} else {
|
||||
hashRedeemedScript = versionByte + hash160;
|
||||
}
|
||||
// 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"));
|
||||
} else {
|
||||
return args[0] + " does not support Segwit Addresses.";
|
||||
}
|
||||
}
|
||||
// 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;
|
||||
// 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/**
|
||||
* Public Key to cryptocurrency address tests.
|
||||
* Public Key To Bitcoin-Like Address tests.
|
||||
*
|
||||
* @author dgoldenberg [virtualcurrency@mitre.org]
|
||||
* @copyright MITRE 2023
|
||||
|
@ -16,7 +16,7 @@ TestRegister.addTests([
|
|||
expectedOutput: "1MwwHqDj1FAyABeqPeiTTvJQCoCorcuFyP",
|
||||
recipeConfig: [
|
||||
{
|
||||
"op": "Public Key To Cryptocurrency Address",
|
||||
"op": "Public Key To Bitcoin-Like Address",
|
||||
"args": ["BTC", "P2PKH (V1 BTC Addresses)"]
|
||||
},
|
||||
],
|
||||
|
@ -27,7 +27,7 @@ TestRegister.addTests([
|
|||
expectedOutput: "18wUwr4Jvor6LG1mvQcfEp1Lx51dYAXZX1",
|
||||
recipeConfig: [
|
||||
{
|
||||
"op": "Public Key To Cryptocurrency Address",
|
||||
"op": "Public Key To Bitcoin-Like Address",
|
||||
"args": ["BTC", "P2PKH (V1 BTC Addresses)"]
|
||||
},
|
||||
],
|
||||
|
@ -38,7 +38,7 @@ TestRegister.addTests([
|
|||
expectedOutput: "LPTR2TBuF8vbwWaJdNeCAQemW4SC7q7zJP",
|
||||
recipeConfig: [
|
||||
{
|
||||
"op": "Public Key To Cryptocurrency Address",
|
||||
"op": "Public Key To Bitcoin-Like Address",
|
||||
"args": ["LTC", "P2PKH (V1 BTC Addresses)"]
|
||||
},
|
||||
],
|
||||
|
@ -49,7 +49,7 @@ TestRegister.addTests([
|
|||
expectedOutput: "1BgRqTW8RMmcTRXHymTCVJsn5NVk9U8L9q",
|
||||
recipeConfig: [
|
||||
{
|
||||
"op": "Public Key To Cryptocurrency Address",
|
||||
"op": "Public Key To Bitcoin-Like Address",
|
||||
"args": ["BTC", "P2PKH (V1 BTC Addresses)"]
|
||||
},
|
||||
],
|
||||
|
@ -60,7 +60,7 @@ TestRegister.addTests([
|
|||
expectedOutput: "31vhdy8RGhSYZRRGZfqvZHGzVtpcua4cQW",
|
||||
recipeConfig: [
|
||||
{
|
||||
"op": "Public Key To Cryptocurrency Address",
|
||||
"op": "Public Key To Bitcoin-Like Address",
|
||||
"args": ["BTC", "P2SH-P2PWPKH (Segwit Compatible V3 Addresses)"]
|
||||
},
|
||||
],
|
||||
|
@ -71,7 +71,7 @@ TestRegister.addTests([
|
|||
expectedOutput: "3C9wCFwcd36MHVpontDF7zQfKPfRTNg4Fe",
|
||||
recipeConfig: [
|
||||
{
|
||||
"op": "Public Key To Cryptocurrency Address",
|
||||
"op": "Public Key To Bitcoin-Like Address",
|
||||
"args": ["BTC", "P2SH-P2PWPKH (Segwit Compatible V3 Addresses)"]
|
||||
},
|
||||
],
|
||||
|
@ -82,7 +82,7 @@ TestRegister.addTests([
|
|||
expectedOutput: "MMwYiJmkxDKqiP2WWAHMMgkeRt2nLxGqih",
|
||||
recipeConfig: [
|
||||
{
|
||||
"op": "Public Key To Cryptocurrency Address",
|
||||
"op": "Public Key To Bitcoin-Like Address",
|
||||
"args": ["LTC", "P2SH-P2PWPKH (Segwit Compatible V3 Addresses)"]
|
||||
},
|
||||
],
|
||||
|
@ -93,7 +93,7 @@ TestRegister.addTests([
|
|||
expectedOutput: "bc1qu37uvwyzj23a2dd3x5nd8s77nfskzu3lzkuqfm",
|
||||
recipeConfig: [
|
||||
{
|
||||
"op": "Public Key To Cryptocurrency Address",
|
||||
"op": "Public Key To Bitcoin-Like Address",
|
||||
"args": ["BTC", "Segwit (P2WPKH bc1 Addresses)"]
|
||||
},
|
||||
],
|
||||
|
@ -104,7 +104,7 @@ TestRegister.addTests([
|
|||
expectedOutput: "bc1qrjluhfu5qr2780zlvcx3kquckpvuamwqp2sjle",
|
||||
recipeConfig: [
|
||||
{
|
||||
"op": "Public Key To Cryptocurrency Address",
|
||||
"op": "Public Key To Bitcoin-Like Address",
|
||||
"args": ["BTC", "Segwit (P2WPKH bc1 Addresses)"]
|
||||
},
|
||||
],
|
||||
|
@ -115,7 +115,7 @@ TestRegister.addTests([
|
|||
expectedOutput: "ltc1qj587punda8h0r4m83k794xseqlnl3az4ktu2zp",
|
||||
recipeConfig: [
|
||||
{
|
||||
"op": "Public Key To Cryptocurrency Address",
|
||||
"op": "Public Key To Bitcoin-Like Address",
|
||||
"args": ["LTC", "Segwit (P2WPKH bc1 Addresses)"]
|
||||
},
|
||||
],
|
||||
|
@ -126,7 +126,7 @@ TestRegister.addTests([
|
|||
expectedOutput: "mmuoeJDuuzeuaii1V6tPK3L5YjaJwjPqUM",
|
||||
recipeConfig: [
|
||||
{
|
||||
"op": "Public Key To Cryptocurrency Address",
|
||||
"op": "Public Key To Bitcoin-Like Address",
|
||||
"args": ["Testnet", "P2PKH (V1 BTC Addresses)"]
|
||||
},
|
||||
],
|
||||
|
@ -138,7 +138,7 @@ TestRegister.addTests([
|
|||
expectedOutput: "Invalid length. We want either 33, 65 (if bytes) or 66, 130 (if hex) but we got: 68",
|
||||
recipeConfig: [
|
||||
{
|
||||
"op": "Public Key To Cryptocurrency Address",
|
||||
"op": "Public Key To Bitcoin-Like Address",
|
||||
"args": ["BTC", "Segwit (P2WPKH bc1 Addresses)"]
|
||||
},
|
||||
],
|
||||
|
@ -149,7 +149,7 @@ TestRegister.addTests([
|
|||
expectedOutput: "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: 05",
|
||||
recipeConfig: [
|
||||
{
|
||||
"op": "Public Key To Cryptocurrency Address",
|
||||
"op": "Public Key To Bitcoin-Like Address",
|
||||
"args": ["BTC", "Segwit (P2WPKH bc1 Addresses)"]
|
||||
},
|
||||
],
|
||||
|
@ -160,7 +160,7 @@ TestRegister.addTests([
|
|||
expectedOutput: "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: 06",
|
||||
recipeConfig: [
|
||||
{
|
||||
"op": "Public Key To Cryptocurrency Address",
|
||||
"op": "Public Key To Bitcoin-Like Address",
|
||||
"args": ["BTC", "P2PKH (V1 BTC Addresses)"]
|
||||
},
|
||||
],
|
||||
|
@ -175,9 +175,28 @@ TestRegister.addTests([
|
|||
"args": ["Auto"]
|
||||
},
|
||||
{
|
||||
"op": "Public Key To Cryptocurrency Address",
|
||||
"op": "Public Key To Bitcoin-Like Address",
|
||||
"args": ["BTC", "P2PKH (V1 BTC Addresses)"]
|
||||
}
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Public Key To Address: P2TR (From WIF Key)",
|
||||
input: "L5R7GAGwrBLcpK4jK1CLDL7VjPifYZZeS1NcixKvrPxXySJWEK9h",
|
||||
expectedOutput: "bc1ph6py5lduje5urxkqewpaxj8cxlmmc9uxr386e0jgvp9vzsup54dqxpxsn7",
|
||||
recipeConfig: [
|
||||
{
|
||||
"op": "From WIF Format",
|
||||
"args": []
|
||||
},
|
||||
{
|
||||
"op": "Private EC Key to Public Key",
|
||||
"args": [true]
|
||||
},
|
||||
{
|
||||
"op": "Public Key To Bitcoin-Like Address",
|
||||
"args": ["BTC", "Taproot (P2TR bc1p Addresses)"]
|
||||
}
|
||||
]
|
||||
},
|
||||
]);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue