Hotfix: Fixed a bug where if a private or public key ended with a0 or other whitespace byte, the validatePrivate or Public Key function in Bitcoin.mjs would strip that byte. In addition added a Public Key To TRX Address Operation.

This commit is contained in:
David C Goldenberg 2024-10-06 21:25:48 -04:00
parent e96e31cd52
commit f7ebae4e88
5 changed files with 202 additions and 24 deletions

View file

@ -355,7 +355,8 @@
"Seed To Master Key",
"Decrypt Keystore File",
"BIP32Derive",
"Public Key To ETH Style Address"
"Public Key To ETH Style Address",
"Public Key To TRX Style Address"
]
},
{

View file

@ -28,7 +28,7 @@ function validateLengths(input, allowableLengths) {
*/
function isHex(input) {
const re = /^[0-9A-Fa-f]{2,}$/g;
return re.test(input);
return re.test(input) && input.length %2 === 0;
}
/**
@ -49,14 +49,13 @@ function isValidBytes(input) {
* @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 (!validateLengths(input, [32, 64])) {
return "Invalid length. We want either 32 or 64 but we got: " + input.length;
}
if (curInput.length === 64 && !isHex(curInput)) {
if (input.length === 64 && !isHex(input)) {
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)) {
if (input.length === 32 && !isValidBytes(input)) {
return "We have a string of length 32 but cannot cannot be interpreted as valid bytes.";
}
return "";
@ -70,31 +69,30 @@ export function validatePrivateKey(input) {
* @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 (!validateLengths(input, [33, 65, 66, 130])) {
return "Invalid length. We want either 33, 65 (if bytes) or 66, 130 (if hex) but we got: " + input.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 (isHex(input)) {
if (!validateLengths(input, [66, 130])) {
return "We have a hex string, but its length is wrong. We want 66, 130 but we got: " + input.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 (input.length === 66 && (input.slice(0, 2) !== "02" && input.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: " + input.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);
if (input.length === 130 && input.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: " + input.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 (isValidBytes(input)) {
if (!validateLengths(input, [33, 65])) {
return "We have a byte string, but its length is wrong. We want 33 or 65 but we got: " + input.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 (input.length === 33 && toHex(input[0]) !== "02" && toHex(input[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(input[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]);
if (input.length === 65 && toHex(input[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(input[0]);
}
return "";
}

View file

@ -0,0 +1,81 @@
/**
* @author dgoldenberg [virtualcurrency@mitre.org]
* @copyright Crown Copyright 2024
* @license Apache-2.0
*/
import Operation from "../Operation.mjs";
import {makeSureIsBytes, validatePublicKey, base58Encode, doubleSHA} from "../lib/Bitcoin.mjs";
import { fromArrayBuffer } from "crypto-api/src/encoder/array-buffer.mjs";
import {toHex} from "crypto-api/src/encoder/hex.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 pubKeyToTRXAddress(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,));
const unencodedAddress = result.slice(-40);
const checksumHash = toHex(doubleSHA(fromArrayBuffer(Utils.convertToByteArray("41" + unencodedAddress, "hex"))));
const finalString = "41" + unencodedAddress + checksumHash.slice(0, 8);
const address = base58Encode(Utils.convertToByteArray(finalString, "hex"));
return address;
}
/**
* Public Key To TRX Style Address operation
*/
class PublicKeyToTRXStyleAddress extends Operation {
/**
* PublicKeyToTRXStyleAddress constructor
*/
constructor() {
super();
this.name = "Public Key To TRX Style Address";
this.module = "Default";
this.description = "Converts a public key, (33 bytes beginning with 02 or 03 for compressed or 65 bytes beginning with 04) to a TRX style address. This involves hashing the public key using keccack-256 like Ethereum, but encoding the result using base58 encoding. ";
this.infoURL = "https://developers.tron.network/docs/account";
this.inputType = "string";
this.outputType = "string";
this.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 pubKeyToTRXAddress(input);
}
}
export default PublicKeyToTRXStyleAddress;

View file

@ -183,6 +183,7 @@ import "./tests/WIFToPrivateKey.mjs";
import "./tests/SeedphraseToSeed.mjs";
import "./tests/DeserializeExtendedKey.mjs";
import "./tests/PublicKeyToETHStyleAddress.mjs";
import "./tests/PublicKeyToTRXStyleAddress.mjs";
import "./tests/GetAllCasings.mjs";
import "./tests/SIGABA.mjs";
import "./tests/ELFInfo.mjs";

View file

@ -0,0 +1,97 @@
/**
* Public Key to TRX Style Address Cryptocurrency Address tests.
*
* @author dgoldenberg [virtualcurrency@mitre.org]
* @copyright MITRE 2023
* @license Apache-2.0
*/
import TestRegister from "../../lib/TestRegister.mjs";
TestRegister.addTests([
{
name: "Public Key To TRX Style Address",
input: "04187ac6bc2723630c936e363b826de17dac62382e3bbfabf306ad5f55cc79538783889fe32946b52092dad24c56893d522413d67e62b28f6c54f14821367a9edc",
expectedOutput: "THV2shRZn4cam7aQreAg9aixfk2sTcho6r",
recipeConfig: [
{
"op": "Public Key To TRX Style Address",
"args": []
},
],
},
{
name: "Public Key To TRX Style Address Compressed Key",
input: "02d1b5855d3f99c4449eb7af576bec1b9bc0bf0769446820686d2de5c47c13b1a0",
expectedOutput: "TQS4NjvDN4TxcZd8LwD1eAKv9y4vSVkDW2",
recipeConfig: [
{
"op": "Public Key To TRX Style Address",
"args": []
},
],
},
{
name: "Public Key to ETH Style Address: Compressed Key 2",
input: "03a85e8f6fc71898b5c3347decd2c0bba8abb99393c8358fcf0bca72e4c7d68514",
expectedOutput: "TXBP2ebjZsnDEL9X5xCZSZZ6FC3Vppccv4",
recipeConfig: [
{
"op": "Public Key To TRX Style Address",
"args": []
},
],
},
{
name: "Public Key to ETH Style Address: Compressed Key 2 (From Hex)",
input: "03a85e8f6fc71898b5c3347decd2c0bba8abb99393c8358fcf0bca72e4c7d68514",
expectedOutput: "TXBP2ebjZsnDEL9X5xCZSZZ6FC3Vppccv4",
recipeConfig: [
{
"op": "From Hex",
"args": ["Auto"]
},
{
"op": "Public Key To TRX Style Address",
"args": [],
},
],
},
{
name: "Public Key to TRX Style Address: Compressed Key (From Hex)",
input: "02d1b5855d3f99c4449eb7af576bec1b9bc0bf0769446820686d2de5c47c13b1a0",
expectedOutput: "TQS4NjvDN4TxcZd8LwD1eAKv9y4vSVkDW2",
recipeConfig: [
{
"op": "From Hex",
"args": ["Auto"]
},
{
"op": "Public Key To TRX Style Address",
"args": []
},
],
},
{
name: "Public Key To TRX Style Address (From Hex)",
input: "04187ac6bc2723630c936e363b826de17dac62382e3bbfabf306ad5f55cc79538783889fe32946b52092dad24c56893d522413d67e62b28f6c54f14821367a9edc",
expectedOutput: "THV2shRZn4cam7aQreAg9aixfk2sTcho6r",
recipeConfig: [
{
"op": "From Hex",
"args": ["Auto"]
},
{
"op": "Public Key To TRX Style Address",
"args": []
},
],
},
]);