// 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 _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 ;