From f7ebae4e88e26863b871b8431e5ad73b86549733 Mon Sep 17 00:00:00 2001 From: David C Goldenberg Date: Sun, 6 Oct 2024 21:25:48 -0400 Subject: [PATCH] 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. --- src/core/config/Categories.json | 3 +- src/core/lib/Bitcoin.mjs | 44 ++++----- .../operations/PublicKeyToTRXStyleAddress.mjs | 81 ++++++++++++++++ tests/operations/index.mjs | 1 + .../tests/PublicKeyToTRXStyleAddress.mjs | 97 +++++++++++++++++++ 5 files changed, 202 insertions(+), 24 deletions(-) create mode 100644 src/core/operations/PublicKeyToTRXStyleAddress.mjs create mode 100644 tests/operations/tests/PublicKeyToTRXStyleAddress.mjs diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json index 46643cbb..f5fe28e2 100644 --- a/src/core/config/Categories.json +++ b/src/core/config/Categories.json @@ -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" ] }, { diff --git a/src/core/lib/Bitcoin.mjs b/src/core/lib/Bitcoin.mjs index c456599c..45c50a10 100644 --- a/src/core/lib/Bitcoin.mjs +++ b/src/core/lib/Bitcoin.mjs @@ -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 ""; } diff --git a/src/core/operations/PublicKeyToTRXStyleAddress.mjs b/src/core/operations/PublicKeyToTRXStyleAddress.mjs new file mode 100644 index 00000000..10af3676 --- /dev/null +++ b/src/core/operations/PublicKeyToTRXStyleAddress.mjs @@ -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; diff --git a/tests/operations/index.mjs b/tests/operations/index.mjs index 08bed1bf..47f3c0f4 100644 --- a/tests/operations/index.mjs +++ b/tests/operations/index.mjs @@ -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"; diff --git a/tests/operations/tests/PublicKeyToTRXStyleAddress.mjs b/tests/operations/tests/PublicKeyToTRXStyleAddress.mjs new file mode 100644 index 00000000..47bf4dd8 --- /dev/null +++ b/tests/operations/tests/PublicKeyToTRXStyleAddress.mjs @@ -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": [] + }, + ], + + }, +]);