UNPKG

xpeed-wallet-npm

Version:

Forked from rai-wallet. Creates ciphered RaiBlocks wallets for client-side and offline use

543 lines (457 loc) 16.6 kB
"use strict"; var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; var _functions = require("./functions"); var MAGIC_NUMBER = "5243"; // 0x52 0x43 var VERSION_MAX = "01"; // 0x01 var VERSION_MIN = "01"; // 0x01 var VERSION_USING = "01"; // 0x01 var EXTENSIONS = "0002"; // 0x00 0x02 var RAI_TO_RAW = "000000000000000000000000"; var MAIN_NET_WORK_THRESHOLD = "ffffffc000000000"; var blake = require('blakejs'); var bigInt = require('big-integer'); var blockID = { invalid: 0, not_a_block: 1, send: 2, receive: 3, open: 4, change: 5 }; module.exports = function () { var api = {}; // public methods var lowLevel = {}; // private methods var data = ""; // raw block to be relayed to the network directly var type; // block type var hash; // block hash var signed = false; // if block has signature var worked = false; // if block has work var signature = ""; // signature var work = ""; // work var blockAmount = bigInt(0); // amount transferred var blockAccount; // account owner of this block var origin; // account sending money in case of receive or open block var immutable = false; // if true means block has already been confirmed and cannot be changed, some checks are ignored var previous; // send, receive and change var destination; // send var balance; // send var decBalance; var source; // receive and open var representative; // open and change var account; // open var version = 1; // to make updates compatible with previous versions of the wallet var BLOCK_MAX_VERSION = 1; /** * Builds the block and calculates the hash * * @throws An exception on invalid type * @returns {Array} The block hash */ api.build = function () { switch (type) { case 'send': data = ""; data += MAGIC_NUMBER + VERSION_MAX + VERSION_USING + VERSION_MIN + (0, _functions.uint8_hex)(blockID[type]) + EXTENSIONS; data += previous; data += destination; data += balance; var context = blake.blake2bInit(32, null); blake.blake2bUpdate(context, (0, _functions.hex_uint8)(previous)); blake.blake2bUpdate(context, (0, _functions.hex_uint8)(destination)); blake.blake2bUpdate(context, (0, _functions.hex_uint8)(balance)); hash = (0, _functions.uint8_hex)(blake.blake2bFinal(context)); break; case 'receive': data = ""; data += MAGIC_NUMBER + VERSION_MAX + VERSION_USING + VERSION_MIN + (0, _functions.uint8_hex)(blockID[type]) + EXTENSIONS; data += previous; data += source; var context = blake.blake2bInit(32, null); blake.blake2bUpdate(context, (0, _functions.hex_uint8)(previous)); blake.blake2bUpdate(context, (0, _functions.hex_uint8)(source)); hash = (0, _functions.uint8_hex)(blake.blake2bFinal(context)); break; case 'open': data = ""; data += MAGIC_NUMBER + VERSION_MAX + VERSION_USING + VERSION_MIN + (0, _functions.uint8_hex)(blockID[type]) + EXTENSIONS; data += source; data += representative; data += account; var context = blake.blake2bInit(32, null); blake.blake2bUpdate(context, (0, _functions.hex_uint8)(source)); blake.blake2bUpdate(context, (0, _functions.hex_uint8)(representative)); blake.blake2bUpdate(context, (0, _functions.hex_uint8)(account)); hash = (0, _functions.uint8_hex)(blake.blake2bFinal(context)); break; case 'change': data = ""; data += MAGIC_NUMBER + VERSION_MAX + VERSION_USING + VERSION_MIN + (0, _functions.uint8_hex)(blockID[type]) + EXTENSIONS; data += previous; data += representative; var context = blake.blake2bInit(32, null); blake.blake2bUpdate(context, (0, _functions.hex_uint8)(previous)); blake.blake2bUpdate(context, (0, _functions.hex_uint8)(representative)); hash = (0, _functions.uint8_hex)(blake.blake2bFinal(context)); break; default: throw "Block parameters need to be set first."; } return hash; }; /** * Sets the send parameters and builds the block * * @param {string} previousBlockHash - The previous block 32 byte hash hex encoded * @param {string} destinationAccount - The XRB account receiving the money * @param {string} balanceRemaining - Remaining balance after sending this block (Raw) * @throws An exception on invalid block hash * @throws An exception on invalid destination account * @throws An exception on invalid balance */ api.setSendParameters = function (previousBlockHash, destinationAccount, balanceRemaining) { if (!/[0-9A-F]{64}/i.test(previousBlockHash)) throw "Invalid previous block hash."; try { var pk = (0, _functions.keyFromAccount)(destinationAccount); } catch (err) { throw "Invalid destination account."; } previous = previousBlockHash; destination = pk; decBalance = balanceRemaining; balance = (0, _functions.dec2hex)(balanceRemaining, 16); type = 'send'; }; /** * Sets the receive parameters and builds the block * * @param {string} previousBlockHash - The previous block 32 byte hash hex encoded * @param {string} sourceBlockHash - The hash of the send block which is going to be received, 32 byte hex encoded * @throws An exception on invalid previousBlockHash * @throws An exception on invalid sourceBlockHash */ api.setReceiveParameters = function (previousBlockHash, sourceBlockHash) { if (!/[0-9A-F]{64}/i.test(previousBlockHash)) throw "Invalid previous block hash."; if (!/[0-9A-F]{64}/i.test(sourceBlockHash)) throw "Invalid source block hash."; previous = previousBlockHash; source = sourceBlockHash; type = 'receive'; }; /** * Sets the open parameters and builds the block * * @param {string} sourceBlockHash - The hash of the send block which is going to be received, 32 byte hex encoded * @param {string} newAccount - The XRB account which is being created * @param {string} representativeAccount - The account to be set as representative, if none, its self assigned * @throws An exception on invalid sourceBlockHash * @throws An exception on invalid account * @throws An exception on invalid representative account */ api.setOpenParameters = function (sourceBlockHash, newAccount) { var representativeAccount = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null; if (!/[0-9A-F]{64}/i.test(sourceBlockHash)) throw "Invalid source block hash."; try { account = (0, _functions.keyFromAccount)(newAccount); } catch (err) { throw "Invalid XPD account."; } if (representativeAccount) { try { representative = (0, _functions.keyFromAccount)(representativeAccount); } catch (err) { throw "Invalid representative account."; } } else representative = account; source = sourceBlockHash; type = 'open'; }; /** * Sets the change parameters and builds the block * * @param {string} previousBlockHash - The previous block 32 byte hash hex encoded * @param {string} representativeAccount - The account to be set as representative * @throws An exception on invalid previousBlockHash * @throws An exception on invalid representative account */ api.setChangeParameters = function (previousBlockHash, representativeAccount) { if (!/[0-9A-F]{64}/i.test(previousBlockHash)) throw "Invalid previous block hash."; try { representative = (0, _functions.keyFromAccount)(representativeAccount); } catch (err) { throw "Invalid representative account."; } previous = previousBlockHash; type = "change"; }; /** * Sets the block signature * * @param {string} hex - The hex encoded 64 byte block hash signature */ api.setSignature = function (hex) { signature = hex; signed = true; }; /** * Sets the block work * * @param {string} hex - The hex encoded 8 byte block hash PoW * @throws An exception if work is not enough */ api.setWork = function (hex) { if (!api.checkWork(hex)) throw "Work not valid for block"; work = hex; worked = true; }; /** * Sets block amount, to be retrieved from it directly instead of calculating it quering the chain * * @param {number} am - The amount */ api.setAmount = function (am) { blockAmount = bigInt(am); }; /** * * @returns blockAmount - The amount transferred in raw */ api.getAmount = function () { return blockAmount; }; /** * Sets the account owner of the block * * @param {string} acc - The xrb account */ api.setAccount = function (acc) { blockAccount = acc; if (type == 'send') origin = acc; }; /** * * @returns blockAccount */ api.getAccount = function () { return blockAccount; }; /** * Sets the account which sent the block * @param {string} acc - The xrb account */ api.setOrigin = function (acc) { if (type == 'receive' || type == 'open') origin = acc; }; /** * * @returns originAccount */ api.getOrigin = function () { if (type == 'receive' || type == 'open') return origin; if (type == 'send') return blockAccount; return false; }; /** * * @returns destinationAccount */ api.getDestination = function () { if (type == 'send') return (0, _functions.accountFromHexKey)(destination); if (type == 'receive' || type == 'open') return blockAccount; }; /** * * @param {boolean} hex - To get the hash hex encoded * @returns {string} The block hash */ api.getHash = function () { var hex = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false; return hex ? hash : (0, _functions.hex_uint8)(hash); }; api.getSignature = function () { return signature; }; api.getType = function () { return type; }; api.getBalance = function () { var format = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'dec'; if (format == 'dec') { var dec = bigInt((0, _functions.hex2dec)(balance)); return dec; } return balance; }; /** * Returns the previous block hash if its not an open block, the public key if it is * * @returns {string} The previous block hash */ api.getPrevious = function () { if (type == 'open') return account; return previous; }; api.getSource = function () { return source; }; api.getRepresentative = function () { if (type == 'change' || type == 'open') return (0, _functions.accountFromHexKey)(representative);else return false; }; api.ready = function () { return signed && worked; }; api.setImmutable = function (bool) { immutable = bool; }; api.isImmutable = function () { return immutable; }; /** * Changes the previous block hash and rebuilds the block * * @param {string} newPrevious - The previous block hash hex encoded * @throws An exception if its an open block * @throws An exception if block is not built */ api.changePrevious = function (newPrevious) { switch (type) { case 'open': throw 'Open has no previous block.'; break; case 'receive': api.setReceiveParameters(newPrevious, source); api.build(); break; case 'send': api.setSendParameters(newPrevious, destination, (0, _functions.stringFromHex)(balance).replace(RAI_TO_RAW, '')); api.build(); break; case 'change': api.setChangeParameters(newPrevious, representative); api.build(); break; default: throw "Invalid block type"; } }; /** * * @returns {string} The raw block hex encoded ready to be sent to the network */ api.getRawBlock = function () { if (!signed || !worked) throw "Incomplete block"; return data; }; /** * * @returns {string} The block JSON encoded to be broadcasted with RPC */ api.getJSONBlock = function () { var pretty = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false; if (!signed) throw "Block lacks signature"; var obj = {}; obj.type = type; switch (type) { case 'send': obj.previous = previous; obj.destination = (0, _functions.accountFromHexKey)(destination); obj.balance = balance; break; case 'receive': obj.previous = previous; obj.source = source; break; case 'open': obj.source = source; obj.representative = (0, _functions.accountFromHexKey)(representative ? representative : account); obj.account = (0, _functions.accountFromHexKey)(account); break; case 'change': obj.previous = previous; obj.representative = (0, _functions.accountFromHexKey)(representative); break; default: throw "Invalid block type."; } obj.work = work; obj.signature = signature; if (pretty) return JSON.stringify(obj, null, 2); return JSON.stringify(obj); }; api.getEntireJSON = function () { var obj = JSON.parse(api.getJSONBlock()); var extras = {}; extras.blockAccount = blockAccount; if (blockAmount) extras.blockAmount = blockAmount.toString();else extras.blockAmount = 0; extras.origin = origin; obj.extras = extras; obj.version = version; return JSON.stringify(obj); }; api.buildFromJSON = function (json) { var v = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; if ((typeof json === "undefined" ? "undefined" : _typeof(json)) != 'object') var obj = JSON.parse(json);else var obj = json; switch (obj.type) { case 'send': type = 'send'; previous = obj.previous; destination = (0, _functions.keyFromAccount)(obj.destination); balance = obj.balance; break; case 'receive': type = 'receive'; previous = obj.previous; source = obj.source; break; case 'open': type = 'open'; source = obj.source; representative = (0, _functions.keyFromAccount)(obj.representative); account = (0, _functions.keyFromAccount)(obj.account); break; case 'change': type = 'change'; previous = obj.previous; representative = (0, _functions.keyFromAccount)(obj.representative); break; default: throw "Invalid block type."; } signature = obj.signature; work = obj.work; if (work) worked = true; if (signature) signed = true; if (obj.extras !== undefined) { api.setAccount(obj.extras.blockAccount); api.setAmount(obj.extras.blockAmount ? obj.extras.blockAmount : 0); api.setOrigin(obj.extras.origin); if (api.getAmount().greater("1000000000000000000000000000000000000000000000000")) // too big, glitch from the units change a couple of commits ago :P api.setAmount(api.getAmount().over("1000000000000000000000000")); } if (!v) version = obj.version ? obj.version : 0;else version = v; if (version == 0) { // update block data to new version and then update block version if (type != 'change') { if (blockAmount) { api.setAmount(blockAmount.multiply("1000000000000000000000000")); // rai to raw } } api.setVersion(1); } api.build(); }; api.checkWork = function (work) { var blockHash = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; if (blockHash === false) { blockHash = api.getPrevious(); } var t = (0, _functions.hex_uint8)(MAIN_NET_WORK_THRESHOLD); var context = blake.blake2bInit(8, null); blake.blake2bUpdate(context, (0, _functions.hex_uint8)(work).reverse()); blake.blake2bUpdate(context, (0, _functions.hex_uint8)(blockHash)); var threshold = blake.blake2bFinal(context).reverse(); if (threshold[0] == t[0]) if (threshold[1] == t[1]) if (threshold[2] == t[2]) if (threshold[3] >= t[3]) return true; return false; }; api.getVersion = function () { return version; }; api.setVersion = function (v) { version = v; }; api.getMaxVersion = function () { return BLOCK_MAX_VERSION; }; return api; };