// Copyright (c) 2014-2019, 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. // Original Author: Lucas Jones // Modified to remove jQuery dep and support modular inclusion of deps by Paul Shapiro (2016) // Modified to add RingCT support by luigi1111 (2017) // // v--- These should maybe be injected into a context and supplied to currencyConfig for future platforms const JSBigInt = require("../cryptonote_utils/biginteger").BigInteger; const nettype_utils = require("../cryptonote_utils/nettype"); // const MyMoneroCoreBridgeEssentialsClass = require('./MyMoneroCoreBridgeEssentialsClass') const MyMoneroBridge_utils = require('./MyMoneroBridge_utils') // function bridge_sanitized__spendable_out(raw__out) { const sanitary__output = { amount: raw__out.amount.toString(), public_key: raw__out.public_key, global_index: "" + raw__out.global_index, index: "" + raw__out.index, tx_pub_key: raw__out.tx_pub_key }; if (raw__out.rct && typeof raw__out.rct !== 'undefined') { sanitary__output.rct = raw__out.rct; } return sanitary__output; } // class MyMoneroCoreBridgeClass extends MyMoneroCoreBridgeEssentialsClass { constructor(this_Module) { super(this_Module); // this._register_async_cb_fns__send_funds(); } // // generate_key_derivation( pub, sec ) { const args = { pub: pub, sec: sec, }; const args_str = JSON.stringify(args); const ret_string = this.Module.generate_key_derivation(args_str); const ret = JSON.parse(ret_string); if (typeof ret.err_msg !== 'undefined' && ret.err_msg) { throw ret.err_msg; } return ret.retVal; } derive_public_key(derivation, out_index, pub) // TODO: fix legacy interface here by moving out_index to last arg pos { if (typeof pub === 'undefined' || pub === "" || pub === null) { throw "Missing pub arg (arg pos idx 2)"; } if (typeof out_index === 'undefined' || out_index === "" || out_index === null) { throw "Missing out_index arg (arg pos idx 1)"; } const args = { pub: pub, derivation: derivation, out_index: ""+out_index, }; const args_str = JSON.stringify(args); const ret_string = this.Module.derive_public_key(args_str); const ret = JSON.parse(ret_string); if (typeof ret.err_msg !== 'undefined' && ret.err_msg) { throw ret.err_msg; } return ret.retVal; } derive_subaddress_public_key( output_key, derivation, out_index ) { if (typeof out_index === 'undefined' || out_index === "" || out_index === null) { throw "Missing out_index arg (arg pos idx 2)"; } const args = { output_key: output_key, derivation: derivation, out_index: "" + out_index, // must be passed as string }; const args_str = JSON.stringify(args); const ret_string = this.Module.derive_subaddress_public_key(args_str); const ret = JSON.parse(ret_string); if (typeof ret.err_msg !== 'undefined' && ret.err_msg) { throw ret.err_msg; } return ret.retVal; } derivation_to_scalar(derivation, output_index) { const args = { derivation: derivation, output_index: output_index, }; const args_str = JSON.stringify(args); const ret_string = this.Module.derivation_to_scalar(args_str); const ret = JSON.parse(ret_string); if (typeof ret.err_msg !== 'undefined' && ret.err_msg) { throw ret.err_msg; } return ret.retVal; } decodeRct(rv, sk, i) { const ecdhInfo = []; // should obvs be plural but just keeping exact names in-tact for (var j = 0 ; j < rv.outPk.length ; j++) { var this_ecdhInfo = rv.ecdhInfo[j]; ecdhInfo.push({ mask: this_ecdhInfo.mask, amount: this_ecdhInfo.amount }) } const outPk = []; for (var j = 0 ; j < rv.outPk.length ; j++) { var this_outPk_mask = null; var this_outPk = rv.outPk[j]; if (typeof this_outPk === 'string') { this_outPk_mask = this_outPk; } else if (typeof this_outPk === "object") { this_outPk_mask = this_outPk.mask; } if (this_outPk_mask == null) { throw "Couldn't locate outPk mask value"; } outPk.push({ mask: this_outPk_mask }) } const args = { i: "" + i, // must be passed as string sk: sk, rv: { type: "" + rv.type/*must be string*/, // e.g. 1, 3 ... corresponding to rct::RCTType* in rctSigs.cpp ecdhInfo: ecdhInfo, outPk: outPk } }; const args_str = JSON.stringify(args); const ret_string = this.Module.decodeRct(args_str); const ret = JSON.parse(ret_string); if (typeof ret.err_msg !== 'undefined' && ret.err_msg) { throw ret.err_msg } return { // calling these out so as to provide a stable ret val interface amount: ret.amount, // string mask: ret.mask, }; } decodeRctSimple(rv, sk, i) { const ecdhInfo = []; // should obvs be plural but just keeping exact names in-tact for (var j = 0 ; j < rv.outPk.length ; j++) { var this_ecdhInfo = rv.ecdhInfo[j]; ecdhInfo.push({ mask: this_ecdhInfo.mask, amount: this_ecdhInfo.amount }) } const outPk = []; for (var j = 0 ; j < rv.outPk.length ; j++) { var this_outPk_mask = null; var this_outPk = rv.outPk[j]; if (typeof this_outPk === 'string') { this_outPk_mask = this_outPk; } else if (typeof this_outPk === "object") { this_outPk_mask = this_outPk.mask; } if (this_outPk_mask == null) { throw "Couldn't locate outPk mask value"; } outPk.push({ mask: this_outPk_mask }) } const args = { i: "" + i, // must be passed as string sk: sk, rv: { type: "" + rv.type/*must be string*/, // e.g. 1, 3 ... corresponding to rct::RCTType* in rctSigs.cpp ecdhInfo: ecdhInfo, outPk: outPk } }; const args_str = JSON.stringify(args); const ret_string = this.Module.decodeRctSimple(args_str); const ret = JSON.parse(ret_string); if (typeof ret.err_msg !== 'undefined' && ret.err_msg) { throw ret.err_msg; } return { // calling these out so as to provide a stable ret val interface amount: ret.amount, // string mask: ret.mask, }; } estimate_fee(args) { const args_str = JSON.stringify(args); const ret_string = this.Module.estimate_fee(args_str); const ret = JSON.parse(ret_string); if (typeof ret.err_msg !== 'undefined' && ret.err_msg) { throw ret.err_msg; } return ret.retVal; } estimate_tx_weight(args) { const args_str = JSON.stringify(args); const ret_string = this.Module.estimate_tx_weight(args_str); const ret = JSON.parse(ret_string); if (typeof ret.err_msg !== 'undefined' && ret.err_msg) { throw ret.err_msg; } return parseInt(ret.retVal, 10); } estimate_rct_tx_size(n_inputs, mixin, n_outputs, extra_size, bulletproof) { const args = { n_inputs, mixin, n_outputs, extra_size, bulletproof }; const args_str = JSON.stringify(args); const ret_string = this.Module.estimate_rct_tx_size(args_str); const ret = JSON.parse(ret_string); if (typeof ret.err_msg !== 'undefined' && ret.err_msg) { throw ret.err_msg; } return parseInt(ret.retVal, 10); } // // Send __key_for_fromCpp__send_funds__get_unspent_outs(task_id) { return `fromCpp__send_funds__get_unspent_outs-${task_id}` } __key_for_fromCpp__send_funds__get_random_outs(task_id) { return `fromCpp__send_funds__get_random_outs-${task_id}` } __key_for_fromCpp__send_funds__submit_raw_tx(task_id) { return `fromCpp__send_funds__submit_raw_tx-${task_id}` } __key_for_fromCpp__send_funds__status_update(task_id) { return `fromCpp__send_funds__status_update-${task_id}` } __key_for_fromCpp__send_funds__error(task_id) { return `fromCpp__send_funds__error-${task_id}` } __key_for_fromCpp__send_funds__success(task_id) { return `fromCpp__send_funds__success-${task_id}` } _register_async_cb_fns__send_funds() { const self = this self.Module.fromCpp__send_funds__get_unspent_outs = function(task_id, req_params) { self._cb_handlers__send_funds[self.__key_for_fromCpp__send_funds__get_unspent_outs(task_id)](req_params); }; self.Module.fromCpp__send_funds__get_random_outs = function(task_id, req_params) { self._cb_handlers__send_funds[self.__key_for_fromCpp__send_funds__get_random_outs(task_id)](req_params); }; self.Module.fromCpp__send_funds__submit_raw_tx = function(task_id, req_params) { self._cb_handlers__send_funds[self.__key_for_fromCpp__send_funds__submit_raw_tx(task_id)](req_params); }; self.Module.fromCpp__send_funds__status_update = function(task_id, params) { self._cb_handlers__send_funds[self.__key_for_fromCpp__send_funds__status_update(task_id)](params); }; self.Module.fromCpp__send_funds__error = function(task_id, params) { self._cb_handlers__send_funds[self.__key_for_fromCpp__send_funds__error(task_id)](params); }; self.Module.fromCpp__send_funds__success = function(task_id, params) { self._cb_handlers__send_funds[self.__key_for_fromCpp__send_funds__success(task_id)](params); }; } __new_task_id() { return Math.random().toString(36).substr(2, 9); // doesn't have to be super random } async__send_funds(fn_args) { const self = this; const task_id = self.__new_task_id(); // register cb handler fns to wait for calls with thi task id if (typeof self._cb_handlers__send_funds == 'undefined' || !self._cb_handlers__send_funds) { self._cb_handlers__send_funds = {} } function freeAllCBHandlersForTask() { delete self._cb_handlers__send_funds[self.__key_for_fromCpp__send_funds__get_unspent_outs(task_id)] delete self._cb_handlers__send_funds[self.__key_for_fromCpp__send_funds__get_random_outs(task_id)] delete self._cb_handlers__send_funds[self.__key_for_fromCpp__send_funds__submit_raw_tx(task_id)] delete self._cb_handlers__send_funds[self.__key_for_fromCpp__send_funds__status_update(task_id)] delete self._cb_handlers__send_funds[self.__key_for_fromCpp__send_funds__error(task_id)] delete self._cb_handlers__send_funds[self.__key_for_fromCpp__send_funds__success(task_id)] } self._cb_handlers__send_funds[self.__key_for_fromCpp__send_funds__get_unspent_outs(task_id)] = function(req_params) { // convert bridge-strings to native primitive types req_params.use_dust = MyMoneroBridge_utils.ret_val_boolstring_to_bool(req_params.use_dust) req_params.mixin = parseInt(req_params.mixin) // fn_args.get_unspent_outs_fn(req_params, function(err_msg, res) { const args = self.__new_cb_args_with(task_id, err_msg, res); const ret_string = self.Module.send_cb_I__got_unspent_outs(JSON.stringify(args)) const ret = JSON.parse(ret_string); if (typeof ret.err_msg !== 'undefined' && ret.err_msg) { // this is actually an exception self._cb_handlers__send_funds[self.__key_for_fromCpp__send_funds__error(task_id)]({ err_msg: ret.err_msg }); // ^-- this will clean up cb handlers too return; } else { // TODO: assert Object.keys(ret).length == 0 } }); }; self._cb_handlers__send_funds[self.__key_for_fromCpp__send_funds__get_random_outs(task_id)] = function(req_params) { // convert bridge-strings to native primitive types req_params.count = parseInt(req_params.count) // fn_args.get_random_outs_fn(req_params, function(err_msg, res) { const args = self.__new_cb_args_with(task_id, err_msg, res); const ret_string = self.Module.send_cb_II__got_random_outs(JSON.stringify(args)) const ret = JSON.parse(ret_string); if (typeof ret.err_msg !== 'undefined' && ret.err_msg) { // this is actually an exception self._cb_handlers__send_funds[self.__key_for_fromCpp__send_funds__error(task_id)]({ err_msg: ret.err_msg }); // ^-- this will clean up cb handlers too return; } else { // TODO: assert Object.keys(ret).length == 0 } }); }; self._cb_handlers__send_funds[self.__key_for_fromCpp__send_funds__submit_raw_tx(task_id)] = function(req_params) { fn_args.submit_raw_tx_fn(req_params, function(err_msg, res) { const args = self.__new_cb_args_with(task_id, err_msg, res); const ret_string = self.Module.send_cb_III__submitted_tx(JSON.stringify(args)) const ret = JSON.parse(ret_string); if (typeof ret.err_msg !== 'undefined' && ret.err_msg) { // this is actually an exception self._cb_handlers__send_funds[self.__key_for_fromCpp__send_funds__error(task_id)]({ err_msg: ret.err_msg }); // ^-- this will clean up cb handlers too return; } else { // TODO: assert Object.keys(ret).length == 0 } }) }; self._cb_handlers__send_funds[self.__key_for_fromCpp__send_funds__status_update(task_id)] = function(params) { params.code = parseInt(params.code) // fn_args.status_update_fn(params); }; self._cb_handlers__send_funds[self.__key_for_fromCpp__send_funds__error(task_id)] = function(params) { fn_args.error_fn(params); freeAllCBHandlersForTask(); // since we're done with the task }; self._cb_handlers__send_funds[self.__key_for_fromCpp__send_funds__success(task_id)] = function(params) { params.mixin = parseInt(params.mixin) // fn_args.success_fn(params); freeAllCBHandlersForTask(); // since we're done with the task }; const args = { task_id: task_id, is_sweeping: fn_args.is_sweeping, sending_amount: "" + fn_args.sending_amount, from_address_string: fn_args.from_address_string, sec_viewKey_string: fn_args.sec_viewKey_string, sec_spendKey_string: fn_args.sec_spendKey_string, pub_spendKey_string: fn_args.pub_spendKey_string, to_address_string: fn_args.to_address_string, priority: "" + fn_args.priority, nettype_string: nettype_utils.nettype_to_API_string(fn_args.nettype) }; if (typeof fn_args.payment_id_string !== 'undefined' && fn_args.payment_id_string) { args.payment_id_string = fn_args.payment_id_string; } if (typeof fn_args.unlock_time !== 'undefined' && fn_args.unlock_time !== null) { args.unlock_time = "" + fn_args.unlock_time; // bridge is expecting a string } const args_str = JSON.stringify(args, null, '') const ret_string = this.Module.send_funds(args_str); const ret = JSON.parse(ret_string); if (typeof ret.err_msg !== 'undefined' && ret.err_msg) { // this is actually an exception self._cb_handlers__send_funds[self.__key_for_fromCpp__send_funds__error(task_id)]({ err_msg: ret.err_msg }); // ^-- this will clean up cb handlers too return; } else { // TODO: assert Object.keys(ret).length == 0 } } encrypt_payment_id(payment_id, public_key, secret_key) { const args = { payment_id: payment_id, public_key: public_key, secret_key: secret_key }; const args_str = JSON.stringify(args); const ret_string = this.Module.encrypt_payment_id(args_str); const ret = JSON.parse(ret_string); if (typeof ret.err_msg !== 'undefined' && ret.err_msg) { throw ret.err_msg; } return ret.retVal; } send_step2__try_create_transaction( // send only IPC-safe vals - no JSBigInts from_address_string, sec_keys, to_address_string, using_outs, mix_outs, fake_outputs_count, final_total_wo_fee, change_amount, fee_amount, payment_id, priority, fee_per_b, // not kib - if fee_per_kb, /= 1024 fee_mask, unlock_time, nettype, optl__fork_version ) { unlock_time = unlock_time || 0; mix_outs = mix_outs || []; // NOTE: we also do this check in the C++... may as well remove it from here if (mix_outs.length !== using_outs.length && fake_outputs_count !== 0) { return { err_msg: "Wrong number of mix outs provided (" + using_outs.length + " using_outs, " + mix_outs.length + " mix outs)" }; } for (var i = 0; i < mix_outs.length; i++) { if ((mix_outs[i].outputs || []).length < fake_outputs_count) { return { err_msg: "Not enough outputs to mix with" }; } } // // Now we need to convert all non-JSON-serializable objects such as JSBigInts to strings etc - not that there should be any! // - and all numbers to strings - especially those which may be uint64_t on the receiving side var sanitary__using_outs = []; for (let i in using_outs) { const sanitary__output = bridge_sanitized__spendable_out(using_outs[i]) sanitary__using_outs.push(sanitary__output); } var sanitary__mix_outs = []; for (let i in mix_outs) { const sanitary__mix_outs_and_amount = { amount: mix_outs[i].amount.toString(), // it should be a string, but in case it's not outputs: [] }; if (mix_outs[i].outputs && typeof mix_outs[i].outputs !== 'undefined') { for (let j in mix_outs[i].outputs) { const sanitary__mix_out = { global_index: "" + mix_outs[i].outputs[j].global_index, // number to string public_key: mix_outs[i].outputs[j].public_key }; if (mix_outs[i].outputs[j].rct && typeof mix_outs[i].outputs[j].rct !== 'undefined') { sanitary__mix_out.rct = mix_outs[i].outputs[j].rct; } sanitary__mix_outs_and_amount.outputs.push(sanitary__mix_out); } } sanitary__mix_outs.push(sanitary__mix_outs_and_amount); } const args = { from_address_string: from_address_string, sec_viewKey_string: sec_keys.view, sec_spendKey_string: sec_keys.spend, to_address_string: to_address_string, final_total_wo_fee: final_total_wo_fee.toString(), change_amount: change_amount.toString(), fee_amount: fee_amount.toString(), priority: "" + priority, fee_per_b: fee_per_b.toString(), fee_mask: fee_mask.toString(), using_outs: sanitary__using_outs, mix_outs: sanitary__mix_outs, unlock_time: "" + unlock_time, // bridge is expecting a string nettype_string: nettype_utils.nettype_to_API_string(nettype), }; if (typeof payment_id !== "undefined" && payment_id) { args.payment_id_string = payment_id; } if (typeof optl__fork_version !== "undefined" && optl__fork_version && optl__fork_version != "") { args.fork_version = optl__fork_version.toString(); } const args_str = JSON.stringify(args); const ret_string = this.Module.send_step2__try_create_transaction(args_str); const ret = JSON.parse(ret_string); // if (typeof ret.err_msg !== 'undefined' && ret.err_msg) { return { err_msg: ret.err_msg, tx_must_be_reconstructed: false }; } if (ret.tx_must_be_reconstructed == "true" || ret.tx_must_be_reconstructed == true) { if (typeof ret.fee_actually_needed == 'undefined' || !ret.fee_actually_needed) { throw "tx_must_be_reconstructed; expected non-nil fee_actually_needed" } return { tx_must_be_reconstructed: ret.tx_must_be_reconstructed, // if true, re-do procedure from step1 except for requesting UnspentOuts (that can be done oncet) fee_actually_needed: ret.fee_actually_needed // can be passed back to step1 } } return { // calling these out to set an interface tx_must_be_reconstructed: false, // in case caller is not checking for nil signed_serialized_tx: ret.serialized_signed_tx, // this name change should be fixed to serialized_signed_tx tx_hash: ret.tx_hash, tx_key: ret.tx_key }; } } // module.exports = MyMoneroCoreBridgeClass;