UNPKG

banani

Version:

JS/TS library for the Banano cryptocurrency in the style of bananopie

273 lines (272 loc) 13.1 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.verify_signed_message = exports.sign_message = exports.construct_message_block_and_hash = exports.verify_block_hash = exports.sign_block_hash = exports.hash_block = exports.get_public_key_from_address = exports.get_address_from_public_key = exports.get_public_key_from_private_key = exports.get_private_key_from_seed = exports.raw_to_whole = exports.whole_to_raw = exports.NANO_DECIMALS = exports.utf8_to_uint8array = exports.base32_to_uint8array = exports.uint8array_to_base32 = exports.int_to_uint8array = exports.hex_to_uint8array = exports.uint8array_to_hex = void 0; // @ts-ignore const nacl = __importStar(require("./tweetnacl_mod")); const blake2b_1 = __importDefault(require("blake2b")); const PREAMBLE = "0000000000000000000000000000000000000000000000000000000000000006"; const MESSAGE_PREAMBLE = "62616E616E6F6D73672D"; //bananomsg- //random fun fact! signatures do not need to be deterministic, they can have a bit of random in them. learned this from flutter_nano_ffi // byte related const HEX_CHARS = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"]; //sigh... https://www.prussiafan.club/posts/hex-to-bytes-and-back/ function uint8array_to_hex(uint8array) { let hex = ""; for (let i = 0; i < uint8array.length; i++) { hex += HEX_CHARS[Math.floor(uint8array[i] / 16)] + HEX_CHARS[uint8array[i] % 16]; } return hex; } exports.uint8array_to_hex = uint8array_to_hex; //does not assume the hex length is multiple of 2 function hex_to_uint8array(hex) { hex = hex.toUpperCase(); let uint8array = new Uint8Array(Math.ceil(hex.length / 2)); for (let i = 0; i < Math.floor(hex.length / 2); i++) { uint8array[i] = HEX_CHARS.indexOf(hex[i * 2]) * 16 + HEX_CHARS.indexOf(hex[i * 2 + 1]); } if ((hex.length / 2) % 1 !== 0) { uint8array[uint8array.length - 1] = HEX_CHARS.indexOf(hex[hex.length - 1]) * 16; } return uint8array; } exports.hex_to_uint8array = hex_to_uint8array; function int_to_uint8array(int, len) { let uint8array = new Uint8Array(len); for (let i = 1; i <= len; i++) { if (i === 1) { uint8array[len - i] = int % 16 ** 2; } else { let subbed_int = int; for (let j = i - 1; j > 0; j--) { subbed_int -= uint8array[len - j] * 16 ** (2 * (j - 1)); } uint8array[len - i] = Math.floor(subbed_int / 16 ** (2 * (i - 1))); } } return uint8array; } exports.int_to_uint8array = int_to_uint8array; const BASE32_CHARS = ["1", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "m", "n", "o", "p", "q", "r", "s", "t", "u", "w", "x", "y", "z"]; //const BASE32_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ01234567".split(""); //ok, so in addition to the different character set,** you need to pad 4 bits at the front (with 0)** so no left over bits exist. we will pad before this function is called //now officially a bitwise operator enthusiast function uint8array_to_base32(uint8array) { let base32 = ""; for (let i = 0; i < Math.floor((uint8array.length * 8) / 5); i++) { const bitn = i * 5; //bit # const bytn = Math.floor(bitn / 8); //byte # const bits = bitn % 8; //bit start (in the byte) let b32in; //base32 chars array index (5 bit integer) const r = 8 - bits; if (r >= 5) { b32in = (uint8array[bytn] >> (r - 5)) & 31; //rightshift to get rid of extra bits on the right, then & 31 to get it down to 5 bits } else { const n = 5 - r; //amount of bits to get from the next byte b32in = ((uint8array[bytn] << n) & 31) + ((uint8array[bytn + 1] >> (8 - n)) & (2 ** (8 - n) - 1)); //first part: left shift to get the bits from the current byte in the right position, then & 31 to get it down to 5 bits. second part: get remaining bits from front of the next byte by rightshifting (get rid of extra bits on the right) and then again doing & to get it down to the appropriate amount of bits } base32 += BASE32_CHARS[b32in]; } //leftover if applicable (this is wrong), but for address generation, there shouldn't be any //let lo = uint8array.length * 8 % 5; //leftover //if (lo > 0) base32 += BASE32_CHARS[uint8array[uint8array.length - 1] << (5 - lo) & 31]; return base32; } exports.uint8array_to_base32 = uint8array_to_base32; function int_to_binary(int, bits) { let binary = ""; let r = int; for (let i = 0; i < bits; i++) { if (r >= 2 ** (bits - 1 - i)) { binary += "1"; r -= 2 ** (bits - 1 - i); } else { binary += "0"; } } return binary; } function binary_to_int(binary) { let int = 0; for (let i = 0; i < binary.length; i++) { int += binary[i] === "1" ? 2 ** (binary.length - 1 - i) : 0; } return int; } //I don't feel like using bitwise operators for this. might need to use up to 3 bytes, too much work //expects length * 5 to be multiple of 8 function base32_to_uint8array(base32) { const binary = base32 .split("") .map((c) => int_to_binary(BASE32_CHARS.indexOf(c), 5)) .join(""); let uint8array = new Uint8Array(Math.ceil((base32.length * 5) / 8)); for (let i = 0; i < uint8array.length; i++) { uint8array[i] = binary_to_int(binary.slice(i * 8, i * 8 + 8)); } return uint8array; } exports.base32_to_uint8array = base32_to_uint8array; function utf8_to_uint8array(utf8) { return new TextEncoder().encode(utf8); } exports.utf8_to_uint8array = utf8_to_uint8array; // // whole and raw related const BANANO_DECIMALS = 29; /** Do `rpc.DECIMALS = banani.NANO_DECIMALS` if using Nano. Putting the wrong amount of decimals in may result in LOSS OF FUNDS. */ exports.NANO_DECIMALS = 30; /** Turn whole Bananos (string) into raw Bananos (bigint) */ function whole_to_raw(whole, decimals = BANANO_DECIMALS) { let raw; if (whole.includes(".")) { const parts = whole.split("."); if (0 > decimals - parts[1].length) throw Error(`Too many decimals, cannot exceed ${decimals}`); raw = BigInt(parts[0]) * BigInt(10) ** BigInt(decimals) + BigInt(parts[1]) * BigInt(10) ** BigInt(decimals - parts[1].length); } else { raw = BigInt(whole) * BigInt(10) ** BigInt(decimals); } return raw; } exports.whole_to_raw = whole_to_raw; /** Turn raw Bananos (bigint) into whole Bananos (string) */ function raw_to_whole(raw, decimals = BANANO_DECIMALS) { const raw_string = raw.toString(); let whole_string; if (raw_string.length > decimals) { whole_string = raw_string.slice(0, -decimals) + "." + raw_string.slice(-decimals); } else { const r = decimals - raw_string.length; whole_string = "0." + "0".repeat(r > 0 ? r : 0) + raw_string; } //truncate any extra zeroes const cl = whole_string.length; for (let c = 0; c < cl; c++) { let dot = whole_string.slice(-1) === "."; if (whole_string.slice(-1) === "0" || dot) { whole_string = whole_string.slice(0, -1); if (dot) break; } else { break; } } return whole_string; } exports.raw_to_whole = raw_to_whole; // crypto related function get_private_key_from_seed(seed, index) { //index is 4 bytes return (0, blake2b_1.default)(32).update(hex_to_uint8array(seed)).update(int_to_uint8array(index, 4)).digest("hex").toUpperCase(); } exports.get_private_key_from_seed = get_private_key_from_seed; function get_public_key_from_private_key(private_key) { return uint8array_to_hex(nacl.sign.keyPair.fromSecretKey(hex_to_uint8array(private_key)).publicKey); } exports.get_public_key_from_private_key = get_public_key_from_private_key; function get_address_from_public_key(public_key, prefix = "ban_") { //the previously mentioned padding the front with 4 bits const encoded = uint8array_to_base32(hex_to_uint8array(`0${public_key}`)); //skip byte length assertions const hashed = uint8array_to_base32((0, blake2b_1.default)(5, undefined, undefined, undefined, true).update(hex_to_uint8array(public_key)).digest().reverse()); return `ban_${encoded}${hashed}`; //fix for old versions of typescript or something } exports.get_address_from_public_key = get_address_from_public_key; function get_public_key_from_address(address) { //extract just the public key portion const b = base32_to_uint8array(address.split("_")[1].slice(0, 52)); b[b.length - 1] = b[b.length - 1] * 16; //this is a bug fix //remove padding 0 added when encoding to address, remove trailing zero added by the code return uint8array_to_hex(b).slice(1, -1); } exports.get_public_key_from_address = get_public_key_from_address; function hash_block(block) { let padded_balance = BigInt(block.balance).toString(16).toUpperCase(); //balance needs to be 16 bytes while (padded_balance.length < 32) { padded_balance = "0" + padded_balance; } return (0, blake2b_1.default)(32) .update(hex_to_uint8array(PREAMBLE)) .update(hex_to_uint8array(get_public_key_from_address(block.account))) .update(hex_to_uint8array(block.previous)) .update(hex_to_uint8array(get_public_key_from_address(block.representative))) .update(hex_to_uint8array(padded_balance)) .update(hex_to_uint8array(block.link)) .digest("hex") .toUpperCase(); } exports.hash_block = hash_block; function sign_block_hash(private_key, block_hash) { return uint8array_to_hex(nacl.sign.detached(hex_to_uint8array(block_hash), hex_to_uint8array(private_key))); } exports.sign_block_hash = sign_block_hash; /** Make sure the alleged signature for a block hash is valid */ function verify_block_hash(public_key, signature, block_hash) { return nacl.sign.detached.verify(hex_to_uint8array(block_hash), hex_to_uint8array(signature), hex_to_uint8array(public_key)); } exports.verify_block_hash = verify_block_hash; /** For use in `sign_message` and `verify_signed_message` */ function construct_message_block_and_hash(address, message, preamble = MESSAGE_PREAMBLE) { //construct the dummy block const dummy32 = "0".repeat(64); const dummy_block = { type: "state", account: address, previous: dummy32, //utf8_to_uint8array not implemented representative: get_address_from_public_key(uint8array_to_hex((0, blake2b_1.default)(32).update(hex_to_uint8array(preamble)).update(utf8_to_uint8array(message)).digest())), balance: "0", link: dummy32, }; //return the hash return hash_block(dummy_block); } exports.construct_message_block_and_hash = construct_message_block_and_hash; /** Sign message by constructing a dummy block with the message (why not just sign the message itself instead of putting it in a dummy block? ledger support). This is already the standard across Banano services and wallets which support signing so please don't invent your own scheme * @return {string} The signature in hex */ function sign_message(private_key, message, preamble = MESSAGE_PREAMBLE) { return sign_block_hash(private_key, construct_message_block_and_hash(get_address_from_public_key(get_public_key_from_private_key(private_key)), message, preamble)); } exports.sign_message = sign_message; /** Use to verify message signatures. A wrapper for `verify_block_hash` * @return {boolean} Whether the message signature was actually signed by that address */ function verify_signed_message(address, message, signature, preamble = MESSAGE_PREAMBLE) { return verify_block_hash(get_public_key_from_address(address), signature, construct_message_block_and_hash(address, message, preamble)); } exports.verify_signed_message = verify_signed_message;