UNPKG

@xmr-core/xmr-b58

Version:

Functions to encode and decode base58 strings for monero operations

262 lines (243 loc) 7.52 kB
// Copyright (c) 2014-2018, MyMonero.com // // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are // permitted provided that the following conditions are met: // // 1. Redistributions of source code must retain the above copyright notice, this list of // conditions and the following disclaimer. // // 2. Redistributions in binary form must reproduce the above copyright notice, this list // of conditions and the following disclaimer in the documentation and/or other // materials provided with the distribution. // // 3. Neither the name of the copyright holder nor the names of its contributors may be // used to endorse or promote products derived from this software without specific // prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL // THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // v--- These should maybe be injected into context const JSBigInt = require("@xmr-core/biginteger").BigInt; var cnBase58 = (function() { var b58 = {}; // var alphabet_str = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; var alphabet = []; for (var i = 0; i < alphabet_str.length; i++) { alphabet.push(alphabet_str.charCodeAt(i)); } var encoded_block_sizes = [0, 2, 3, 5, 6, 7, 9, 10, 11]; var alphabet_size = alphabet.length; var full_block_size = 8; var full_encoded_block_size = 11; var UINT64_MAX = new JSBigInt(2).pow(64); function hextobin(hex) { if (hex.length % 2 !== 0) throw Error("Hex string has invalid length!"); var res = new Uint8Array(hex.length / 2); for (var i = 0; i < hex.length / 2; ++i) { res[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16); } return res; } function bintohex(bin) { var out = []; for (var i = 0; i < bin.length; ++i) { out.push(("0" + bin[i].toString(16)).slice(-2)); } return out.join(""); } function strtobin(str) { var res = new Uint8Array(str.length); for (var i = 0; i < str.length; i++) { res[i] = str.charCodeAt(i); } return res; } function bintostr(bin) { var out = []; for (var i = 0; i < bin.length; i++) { out.push(String.fromCharCode(bin[i])); } return out.join(""); } function uint8_be_to_64(data) { if (data.length < 1 || data.length > 8) { throw Error("Invalid input length"); } var res = JSBigInt.ZERO; var twopow8 = new JSBigInt(2).pow(8); var i = 0; switch (9 - data.length) { case 1: res = res.add(data[i++]); case 2: res = res.multiply(twopow8).add(data[i++]); case 3: res = res.multiply(twopow8).add(data[i++]); case 4: res = res.multiply(twopow8).add(data[i++]); case 5: res = res.multiply(twopow8).add(data[i++]); case 6: res = res.multiply(twopow8).add(data[i++]); case 7: res = res.multiply(twopow8).add(data[i++]); case 8: res = res.multiply(twopow8).add(data[i++]); break; default: throw Error("Impossible condition"); } return res; } function uint64_to_8be(num, size) { var res = new Uint8Array(size); if (size < 1 || size > 8) { throw Error("Invalid input length"); } var twopow8 = new JSBigInt(2).pow(8); for (var i = size - 1; i >= 0; i--) { res[i] = num.remainder(twopow8).toJSValue(); num = num.divide(twopow8); } return res; } b58.encode_block = function(data, buf, index) { if (data.length < 1 || data.length > full_encoded_block_size) { throw Error("Invalid block length: " + data.length); } var num = uint8_be_to_64(data); var i = encoded_block_sizes[data.length] - 1; // while num > 0 while (num.compare(0) === 1) { var div = num.divRem(alphabet_size); // remainder = num % alphabet_size var remainder = div[1]; // num = num / alphabet_size num = div[0]; buf[index + i] = alphabet[remainder.toJSValue()]; i--; } return buf; }; b58.encode = function(hex) { var data = hextobin(hex); if (data.length === 0) { return ""; } var full_block_count = Math.floor(data.length / full_block_size); var last_block_size = data.length % full_block_size; var res_size = full_block_count * full_encoded_block_size + encoded_block_sizes[last_block_size]; var res = new Uint8Array(res_size); var i; for (i = 0; i < res_size; ++i) { res[i] = alphabet[0]; } for (i = 0; i < full_block_count; i++) { res = b58.encode_block( data.subarray( i * full_block_size, i * full_block_size + full_block_size, ), res, i * full_encoded_block_size, ); } if (last_block_size > 0) { res = b58.encode_block( data.subarray( full_block_count * full_block_size, full_block_count * full_block_size + last_block_size, ), res, full_block_count * full_encoded_block_size, ); } return bintostr(res); }; b58.decode_block = function(data, buf, index) { if (data.length < 1 || data.length > full_encoded_block_size) { throw Error("Invalid block length: " + data.length); } var res_size = encoded_block_sizes.indexOf(data.length); if (res_size <= 0) { throw Error("Invalid block size"); } var res_num = new JSBigInt(0); var order = new JSBigInt(1); for (var i = data.length - 1; i >= 0; i--) { var digit = alphabet.indexOf(data[i]); if (digit < 0) { throw Error("Invalid symbol"); } var product = order.multiply(digit).add(res_num); // if product > UINT64_MAX if (product.compare(UINT64_MAX) === 1) { throw Error("Overflow"); } res_num = product; order = order.multiply(alphabet_size); } if ( res_size < full_block_size && new JSBigInt(2).pow(8 * res_size).compare(res_num) <= 0 ) { throw Error("Overflow 2"); } buf.set(uint64_to_8be(res_num, res_size), index); return buf; }; b58.decode = function(enc) { enc = strtobin(enc); if (enc.length === 0) { return ""; } var full_block_count = Math.floor(enc.length / full_encoded_block_size); var last_block_size = enc.length % full_encoded_block_size; var last_block_decoded_size = encoded_block_sizes.indexOf( last_block_size, ); if (last_block_decoded_size < 0) { throw Error("Invalid encoded length"); } var data_size = full_block_count * full_block_size + last_block_decoded_size; var data = new Uint8Array(data_size); for (var i = 0; i < full_block_count; i++) { data = b58.decode_block( enc.subarray( i * full_encoded_block_size, i * full_encoded_block_size + full_encoded_block_size, ), data, i * full_block_size, ); } if (last_block_size > 0) { data = b58.decode_block( enc.subarray( full_block_count * full_encoded_block_size, full_block_count * full_encoded_block_size + last_block_size, ), data, full_block_count * full_block_size, ); } return bintohex(data); }; return b58; })(); exports.cnBase58 = cnBase58;