mirror of
https://github.com/gchq/CyberChef.git
synced 2025-06-15 10:44:50 -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 Segwit Addresses",
|
||||||
"Extract Seedphrases",
|
"Extract Seedphrases",
|
||||||
"Deserialize Extended Key",
|
"Deserialize Extended Key",
|
||||||
"Public Key To Cryptocurrency Address",
|
"Public Key To Bitcoin-Like Address",
|
||||||
"To WIF Format",
|
"To WIF Format",
|
||||||
"From WIF Format",
|
"From WIF Format",
|
||||||
"Type Cryptocurrency Artifact",
|
"Type Cryptocurrency Artifact",
|
||||||
|
|
|
@ -7,10 +7,12 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import CryptoApi from "crypto-api/src/crypto-api.mjs";
|
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 {toHex} from "crypto-api/src/encoder/hex.mjs";
|
||||||
import Utils from "../Utils.mjs";
|
import Utils from "../Utils.mjs";
|
||||||
import OperationError from "../errors/OperationError.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.
|
* 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();
|
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 ###################################################
|
// ################################################ END HELPER HASH FUNCTIONS ###################################################
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -9,11 +9,37 @@
|
||||||
import Operation from "../Operation.mjs";
|
import Operation from "../Operation.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 {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 {encodeProgramToSegwit} from "../lib/Bech32.mjs";
|
||||||
import Utils from "../Utils.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.
|
* Converts a Public Key to a P2PKH Address of the given type.
|
||||||
*/
|
*/
|
||||||
|
@ -25,9 +51,9 @@ class PublicKeyToP2PKHAddress extends Operation {
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.name = "Public Key To Cryptocurrency Address";
|
this.name = "Public Key To Bitcoin-Like Address";
|
||||||
this.module = "Default";
|
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.inputType = "string";
|
||||||
this.outputType = "string";
|
this.outputType = "string";
|
||||||
this.args = [
|
this.args = [
|
||||||
|
@ -39,7 +65,7 @@ class PublicKeyToP2PKHAddress extends Operation {
|
||||||
{
|
{
|
||||||
"name": "Address Type",
|
"name": "Address Type",
|
||||||
"type": "option",
|
"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 = [
|
this.checks = [
|
||||||
|
@ -66,7 +92,15 @@ class PublicKeyToP2PKHAddress extends Operation {
|
||||||
if (validatePublicKey(input) !== "") {
|
if (validatePublicKey(input) !== "") {
|
||||||
return validatePublicKey(input);
|
return validatePublicKey(input);
|
||||||
}
|
}
|
||||||
|
// P2TR are their own separate case. We handle those first.
|
||||||
|
if (args[1] === "Taproot (P2TR bc1p Addresses)") {
|
||||||
|
const hrp = getHumanReadablePart(args[0]);
|
||||||
|
const resultKey = tweakKey(input);
|
||||||
|
if (resultKey === -1) {
|
||||||
|
return "Error: Bad Public Key to turn into P2TR Address.";
|
||||||
|
}
|
||||||
|
return encodeProgramToSegwit(hrp, 1, Utils.convertToByteArray(resultKey.slice(2,), "hex"));
|
||||||
|
} else {
|
||||||
// We hash the input
|
// We hash the input
|
||||||
const curInput = makeSureIsBytes(input);
|
const curInput = makeSureIsBytes(input);
|
||||||
const hash160 = toHex(hash160Func(curInput));
|
const hash160 = toHex(hash160Func(curInput));
|
||||||
|
@ -96,6 +130,7 @@ class PublicKeyToP2PKHAddress extends Operation {
|
||||||
const finalString = hashRedeemedScript + checksumHash.slice(0, 8);
|
const finalString = hashRedeemedScript + checksumHash.slice(0, 8);
|
||||||
const address = base58Encode(Utils.convertToByteArray(finalString, "hex"));
|
const address = base58Encode(Utils.convertToByteArray(finalString, "hex"));
|
||||||
return address;
|
return address;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/**
|
/**
|
||||||
* Public Key to cryptocurrency address tests.
|
* Public Key To Bitcoin-Like Address tests.
|
||||||
*
|
*
|
||||||
* @author dgoldenberg [virtualcurrency@mitre.org]
|
* @author dgoldenberg [virtualcurrency@mitre.org]
|
||||||
* @copyright MITRE 2023
|
* @copyright MITRE 2023
|
||||||
|
@ -16,7 +16,7 @@ TestRegister.addTests([
|
||||||
expectedOutput: "1MwwHqDj1FAyABeqPeiTTvJQCoCorcuFyP",
|
expectedOutput: "1MwwHqDj1FAyABeqPeiTTvJQCoCorcuFyP",
|
||||||
recipeConfig: [
|
recipeConfig: [
|
||||||
{
|
{
|
||||||
"op": "Public Key To Cryptocurrency Address",
|
"op": "Public Key To Bitcoin-Like Address",
|
||||||
"args": ["BTC", "P2PKH (V1 BTC Addresses)"]
|
"args": ["BTC", "P2PKH (V1 BTC Addresses)"]
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -27,7 +27,7 @@ TestRegister.addTests([
|
||||||
expectedOutput: "18wUwr4Jvor6LG1mvQcfEp1Lx51dYAXZX1",
|
expectedOutput: "18wUwr4Jvor6LG1mvQcfEp1Lx51dYAXZX1",
|
||||||
recipeConfig: [
|
recipeConfig: [
|
||||||
{
|
{
|
||||||
"op": "Public Key To Cryptocurrency Address",
|
"op": "Public Key To Bitcoin-Like Address",
|
||||||
"args": ["BTC", "P2PKH (V1 BTC Addresses)"]
|
"args": ["BTC", "P2PKH (V1 BTC Addresses)"]
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -38,7 +38,7 @@ TestRegister.addTests([
|
||||||
expectedOutput: "LPTR2TBuF8vbwWaJdNeCAQemW4SC7q7zJP",
|
expectedOutput: "LPTR2TBuF8vbwWaJdNeCAQemW4SC7q7zJP",
|
||||||
recipeConfig: [
|
recipeConfig: [
|
||||||
{
|
{
|
||||||
"op": "Public Key To Cryptocurrency Address",
|
"op": "Public Key To Bitcoin-Like Address",
|
||||||
"args": ["LTC", "P2PKH (V1 BTC Addresses)"]
|
"args": ["LTC", "P2PKH (V1 BTC Addresses)"]
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -49,7 +49,7 @@ TestRegister.addTests([
|
||||||
expectedOutput: "1BgRqTW8RMmcTRXHymTCVJsn5NVk9U8L9q",
|
expectedOutput: "1BgRqTW8RMmcTRXHymTCVJsn5NVk9U8L9q",
|
||||||
recipeConfig: [
|
recipeConfig: [
|
||||||
{
|
{
|
||||||
"op": "Public Key To Cryptocurrency Address",
|
"op": "Public Key To Bitcoin-Like Address",
|
||||||
"args": ["BTC", "P2PKH (V1 BTC Addresses)"]
|
"args": ["BTC", "P2PKH (V1 BTC Addresses)"]
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -60,7 +60,7 @@ TestRegister.addTests([
|
||||||
expectedOutput: "31vhdy8RGhSYZRRGZfqvZHGzVtpcua4cQW",
|
expectedOutput: "31vhdy8RGhSYZRRGZfqvZHGzVtpcua4cQW",
|
||||||
recipeConfig: [
|
recipeConfig: [
|
||||||
{
|
{
|
||||||
"op": "Public Key To Cryptocurrency Address",
|
"op": "Public Key To Bitcoin-Like Address",
|
||||||
"args": ["BTC", "P2SH-P2PWPKH (Segwit Compatible V3 Addresses)"]
|
"args": ["BTC", "P2SH-P2PWPKH (Segwit Compatible V3 Addresses)"]
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -71,7 +71,7 @@ TestRegister.addTests([
|
||||||
expectedOutput: "3C9wCFwcd36MHVpontDF7zQfKPfRTNg4Fe",
|
expectedOutput: "3C9wCFwcd36MHVpontDF7zQfKPfRTNg4Fe",
|
||||||
recipeConfig: [
|
recipeConfig: [
|
||||||
{
|
{
|
||||||
"op": "Public Key To Cryptocurrency Address",
|
"op": "Public Key To Bitcoin-Like Address",
|
||||||
"args": ["BTC", "P2SH-P2PWPKH (Segwit Compatible V3 Addresses)"]
|
"args": ["BTC", "P2SH-P2PWPKH (Segwit Compatible V3 Addresses)"]
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -82,7 +82,7 @@ TestRegister.addTests([
|
||||||
expectedOutput: "MMwYiJmkxDKqiP2WWAHMMgkeRt2nLxGqih",
|
expectedOutput: "MMwYiJmkxDKqiP2WWAHMMgkeRt2nLxGqih",
|
||||||
recipeConfig: [
|
recipeConfig: [
|
||||||
{
|
{
|
||||||
"op": "Public Key To Cryptocurrency Address",
|
"op": "Public Key To Bitcoin-Like Address",
|
||||||
"args": ["LTC", "P2SH-P2PWPKH (Segwit Compatible V3 Addresses)"]
|
"args": ["LTC", "P2SH-P2PWPKH (Segwit Compatible V3 Addresses)"]
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -93,7 +93,7 @@ TestRegister.addTests([
|
||||||
expectedOutput: "bc1qu37uvwyzj23a2dd3x5nd8s77nfskzu3lzkuqfm",
|
expectedOutput: "bc1qu37uvwyzj23a2dd3x5nd8s77nfskzu3lzkuqfm",
|
||||||
recipeConfig: [
|
recipeConfig: [
|
||||||
{
|
{
|
||||||
"op": "Public Key To Cryptocurrency Address",
|
"op": "Public Key To Bitcoin-Like Address",
|
||||||
"args": ["BTC", "Segwit (P2WPKH bc1 Addresses)"]
|
"args": ["BTC", "Segwit (P2WPKH bc1 Addresses)"]
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -104,7 +104,7 @@ TestRegister.addTests([
|
||||||
expectedOutput: "bc1qrjluhfu5qr2780zlvcx3kquckpvuamwqp2sjle",
|
expectedOutput: "bc1qrjluhfu5qr2780zlvcx3kquckpvuamwqp2sjle",
|
||||||
recipeConfig: [
|
recipeConfig: [
|
||||||
{
|
{
|
||||||
"op": "Public Key To Cryptocurrency Address",
|
"op": "Public Key To Bitcoin-Like Address",
|
||||||
"args": ["BTC", "Segwit (P2WPKH bc1 Addresses)"]
|
"args": ["BTC", "Segwit (P2WPKH bc1 Addresses)"]
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -115,7 +115,7 @@ TestRegister.addTests([
|
||||||
expectedOutput: "ltc1qj587punda8h0r4m83k794xseqlnl3az4ktu2zp",
|
expectedOutput: "ltc1qj587punda8h0r4m83k794xseqlnl3az4ktu2zp",
|
||||||
recipeConfig: [
|
recipeConfig: [
|
||||||
{
|
{
|
||||||
"op": "Public Key To Cryptocurrency Address",
|
"op": "Public Key To Bitcoin-Like Address",
|
||||||
"args": ["LTC", "Segwit (P2WPKH bc1 Addresses)"]
|
"args": ["LTC", "Segwit (P2WPKH bc1 Addresses)"]
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -126,7 +126,7 @@ TestRegister.addTests([
|
||||||
expectedOutput: "mmuoeJDuuzeuaii1V6tPK3L5YjaJwjPqUM",
|
expectedOutput: "mmuoeJDuuzeuaii1V6tPK3L5YjaJwjPqUM",
|
||||||
recipeConfig: [
|
recipeConfig: [
|
||||||
{
|
{
|
||||||
"op": "Public Key To Cryptocurrency Address",
|
"op": "Public Key To Bitcoin-Like Address",
|
||||||
"args": ["Testnet", "P2PKH (V1 BTC Addresses)"]
|
"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",
|
expectedOutput: "Invalid length. We want either 33, 65 (if bytes) or 66, 130 (if hex) but we got: 68",
|
||||||
recipeConfig: [
|
recipeConfig: [
|
||||||
{
|
{
|
||||||
"op": "Public Key To Cryptocurrency Address",
|
"op": "Public Key To Bitcoin-Like Address",
|
||||||
"args": ["BTC", "Segwit (P2WPKH bc1 Addresses)"]
|
"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",
|
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: [
|
recipeConfig: [
|
||||||
{
|
{
|
||||||
"op": "Public Key To Cryptocurrency Address",
|
"op": "Public Key To Bitcoin-Like Address",
|
||||||
"args": ["BTC", "Segwit (P2WPKH bc1 Addresses)"]
|
"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",
|
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: [
|
recipeConfig: [
|
||||||
{
|
{
|
||||||
"op": "Public Key To Cryptocurrency Address",
|
"op": "Public Key To Bitcoin-Like Address",
|
||||||
"args": ["BTC", "P2PKH (V1 BTC Addresses)"]
|
"args": ["BTC", "P2PKH (V1 BTC Addresses)"]
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -175,9 +175,28 @@ TestRegister.addTests([
|
||||||
"args": ["Auto"]
|
"args": ["Auto"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"op": "Public Key To Cryptocurrency Address",
|
"op": "Public Key To Bitcoin-Like Address",
|
||||||
"args": ["BTC", "P2PKH (V1 BTC Addresses)"]
|
"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