// 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.
// 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 monero _config = require ( './monero_config' ) ;
const currency _amount _format _utils = require ( "../cryptonote_utils/money_format_utils" ) ( monero _config ) ;
//
function ret _val _boolstring _to _bool ( boolstring )
{
if ( typeof boolstring !== "string" ) {
throw "ret_val_boolstring_to_bool expected string input"
}
if ( boolstring === "true" ) {
return true
} else if ( boolstring === "false" ) {
return false
}
throw "ret_val_boolstring_to_bool given illegal input"
}
function api _safe _wordset _name ( wordset _name )
{
return wordset _name . charAt ( 0 ) . toUpperCase ( ) + wordset _name . substr ( 1 ) // capitalizes first letter
}
//
class MyMoneroCoreBridge
{
constructor ( Module )
{
this . Module = Module ;
}
//
//
is _subaddress ( addr , nettype ) {
const args =
{
address : addr ,
nettype _string : nettype _utils . nettype _to _API _string ( nettype )
} ;
const args _str = JSON . stringify ( args ) ;
const ret _string = this . Module . is _subaddress ( args _str ) ;
const ret = JSON . parse ( ret _string ) ;
if ( typeof ret . err _msg !== 'undefined' && ret . err _msg ) {
return { err _msg : ret . err _msg }
}
return ret _val _boolstring _to _bool ( ret . retVal ) ;
}
is _integrated _address ( addr , nettype ) {
const args =
{
address : addr ,
nettype _string : nettype _utils . nettype _to _API _string ( nettype )
} ;
const args _str = JSON . stringify ( args ) ;
const ret _string = this . Module . is _integrated _address ( args _str ) ;
const ret = JSON . parse ( ret _string ) ;
if ( typeof ret . err _msg !== 'undefined' && ret . err _msg ) {
return { err _msg : ret . err _msg }
}
return ret _val _boolstring _to _bool ( ret . retVal ) ;
}
new _payment _id ( ) {
const args = { } ;
const args _str = JSON . stringify ( args ) ;
const ret _string = this . Module . new _payment _id ( args _str ) ;
const ret = JSON . parse ( ret _string ) ;
if ( typeof ret . err _msg !== 'undefined' && ret . err _msg ) {
return { err _msg : ret . err _msg }
}
return ret . retVal ;
}
new _ _int _addr _from _addr _and _short _pid (
address ,
short _pid ,
nettype
) {
if ( ! short _pid || short _pid . length != 16 ) {
return { err _msg : "expected valid short_pid" } ;
}
const args =
{
address : address ,
short _pid : short _pid ,
nettype _string : nettype _utils . nettype _to _API _string ( nettype )
} ;
const args _str = JSON . stringify ( args ) ;
const ret _string = this . Module . new _integrated _address ( args _str ) ;
const ret = JSON . parse ( ret _string ) ;
if ( typeof ret . err _msg !== 'undefined' && ret . err _msg ) {
return { err _msg : ret . err _msg }
}
return ret . retVal ;
}
new _fake _address _for _rct _tx ( nettype )
{ // TODO: possibly support sending random_scalar from JS to emscripten to avoid emscripten random
const args = { nettype _string : nettype _utils . nettype _to _API _string ( nettype ) } ;
const args _str = JSON . stringify ( args ) ;
const ret _string = this . Module . new _fake _address _for _rct _tx ( args _str ) ;
const ret = JSON . parse ( ret _string ) ;
if ( typeof ret . err _msg !== 'undefined' && ret . err _msg ) {
return { err _msg : ret . err _msg }
}
return ret . retVal ;
}
decode _address ( address , nettype )
{
const args =
{
address : address ,
nettype _string : nettype _utils . nettype _to _API _string ( nettype )
} ;
const args _str = JSON . stringify ( args ) ;
const ret _string = this . Module . decode _address ( args _str ) ;
const ret = JSON . parse ( ret _string ) ;
if ( typeof ret . err _msg !== 'undefined' && ret . err _msg ) {
return { err _msg : ret . err _msg }
}
return {
spend : ret . pub _spendKey _string ,
view : ret . pub _viewKey _string ,
intPaymentId : ret . paymentID _string , // may be undefined
isSubaddress : ret _val _boolstring _to _bool ( ret . isSubaddress )
}
}
newly _created _wallet (
locale _language _code ,
nettype
) {
const args =
{
locale _language _code : locale _language _code , // NOTE: this function takes the locale, not the wordset name
nettype _string : nettype _utils . nettype _to _API _string ( nettype )
} ;
const args _str = JSON . stringify ( args ) ;
const ret _string = this . Module . newly _created _wallet ( args _str ) ;
const ret = JSON . parse ( ret _string ) ;
if ( typeof ret . err _msg !== 'undefined' && ret . err _msg ) {
return { err _msg : ret . err _msg }
}
return { // calling these out so as to provide a stable ret val interface
mnemonic _string : ret . mnemonic _string ,
mnemonic _language : ret . mnemonic _language ,
sec _seed _string : ret . sec _seed _string ,
address _string : ret . address _string ,
pub _viewKey _string : ret . pub _viewKey _string ,
sec _viewKey _string : ret . sec _viewKey _string ,
pub _spendKey _string : ret . pub _spendKey _string ,
sec _spendKey _string : ret . sec _spendKey _string
} ;
}
are _equal _mnemonics ( a , b ) {
const args =
{
a : a ,
b : b
} ;
const args _str = JSON . stringify ( args ) ;
const ret _string = this . Module . are _equal _mnemonics ( args _str ) ;
const ret = JSON . parse ( ret _string ) ;
if ( typeof ret . err _msg !== 'undefined' && ret . err _msg ) {
return { err _msg : ret . err _msg }
}
return ret _val _boolstring _to _bool ( ret . retVal ) ;
}
mnemonic _from _seed (
seed _string ,
wordset _name
) {
const args =
{
seed _string : seed _string ,
wordset _name : api _safe _wordset _name ( wordset _name )
} ;
const args _str = JSON . stringify ( args ) ;
const ret _string = this . Module . mnemonic _from _seed ( args _str ) ;
const ret = JSON . parse ( ret _string ) ;
if ( typeof ret . err _msg !== 'undefined' && ret . err _msg ) {
return { err _msg : ret . err _msg } // TODO: maybe return this somehow
}
return ret . retVal ;
}
seed _and _keys _from _mnemonic (
mnemonic _string ,
nettype
) {
const args =
{
mnemonic _string : mnemonic _string ,
nettype _string : nettype _utils . nettype _to _API _string ( nettype )
} ;
const args _str = JSON . stringify ( args ) ;
const ret _string = this . Module . seed _and _keys _from _mnemonic ( args _str ) ;
const ret = JSON . parse ( ret _string ) ;
if ( typeof ret . err _msg !== 'undefined' && ret . err _msg ) {
return { err _msg : ret . err _msg }
}
return { // calling these out so as to provide a stable ret val interface
sec _seed _string : ret . sec _seed _string ,
mnemonic _language : ret . mnemonic _language ,
address _string : ret . address _string ,
pub _viewKey _string : ret . pub _viewKey _string ,
sec _viewKey _string : ret . sec _viewKey _string ,
pub _spendKey _string : ret . pub _spendKey _string ,
sec _spendKey _string : ret . sec _spendKey _string
} ;
}
validate _components _for _login (
address _string ,
sec _viewKey _string ,
sec _spendKey _string ,
seed _string ,
nettype
) {
const args =
{
address _string : address _string ,
sec _viewKey _string : sec _viewKey _string ,
sec _spendKey _string : sec _spendKey _string ,
seed _string : seed _string ,
nettype _string : nettype _utils . nettype _to _API _string ( nettype )
} ;
const args _str = JSON . stringify ( args ) ;
const ret _string = this . Module . validate _components _for _login ( args _str ) ;
const ret = JSON . parse ( ret _string ) ;
if ( typeof ret . err _msg !== 'undefined' && ret . err _msg ) {
return { err _msg : ret . err _msg }
}
return { // calling these out so as to provide a stable ret val interface
isValid : ret _val _boolstring _to _bool ( ret . isValid ) ,
isInViewOnlyMode : ret _val _boolstring _to _bool ( ret . isInViewOnlyMode ) ,
pub _viewKey _string : ret . pub _viewKey _string ,
pub _spendKey _string : ret . pub _spendKey _string
} ;
}
generate _key _image (
tx _pub ,
view _sec ,
spend _pub ,
spend _sec ,
output _index
) {
if ( tx _pub . length !== 64 ) {
return { err _msg : "Invalid tx_pub length" } ;
}
if ( view _sec . length !== 64 ) {
return { err _msg : "Invalid view_sec length" } ;
}
if ( spend _pub . length !== 64 ) {
return { err _msg : "Invalid spend_pub length" } ;
}
if ( spend _sec . length !== 64 ) {
return { err _msg : "Invalid spend_sec length" } ;
}
const args =
{
sec _viewKey _string : view _sec ,
sec _spendKey _string : spend _sec ,
pub _spendKey _string : spend _pub ,
tx _pub _key : tx _pub ,
out _index : "" + output _index
} ;
const args _str = JSON . stringify ( args ) ;
const ret _string = this . Module . generate _key _image ( args _str ) ;
const ret = JSON . parse ( ret _string ) ;
if ( typeof ret . err _msg !== 'undefined' && ret . err _msg ) {
return { err _msg : ret . err _msg } ;
}
return ret . retVal ;
}
create _signed _transaction (
from _address _string ,
sec _keys ,
to _address _string ,
outputs ,
mix _outs ,
fake _outputs _count ,
serialized _ _sending _amount ,
serialized _ _change _amount ,
serialized _ _fee _amount , // string amount
payment _id ,
unlock _time ,
rct ,
nettype
) {
return this . create _signed _transaction _ _nonIPCsafe (
from _address _string ,
sec _keys ,
to _address _string ,
outputs ,
mix _outs ,
fake _outputs _count ,
new JSBigInt ( serialized _ _sending _amount ) ,
new JSBigInt ( serialized _ _change _amount ) ,
new JSBigInt ( serialized _ _fee _amount ) , // only to be deserialized again is a bit silly but this at least exposes a JSBigInt API for others
payment _id ,
unlock _time ,
rct ,
nettype
) ;
}
create _signed _transaction _ _nonIPCsafe ( // you can use this function to pass JSBigInts
from _address _string ,
sec _keys ,
to _address _string ,
outputs ,
mix _outs ,
fake _outputs _count ,
sending _amount ,
change _amount ,
fee _amount ,
payment _id ,
unlock _time ,
rct ,
nettype
) {
unlock _time = unlock _time || 0 ;
mix _outs = mix _outs || [ ] ;
if ( rct != true ) {
return { err _msg : "Expected rct=true" }
}
if ( mix _outs . length !== outputs . length && fake _outputs _count !== 0 ) {
return { err _msg : "Wrong number of mix outs provided (" +
outputs . length +
" outputs, " +
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
var sanitary _ _outputs = [ ] ;
for ( let i in outputs ) {
const sanitary _ _output =
{
amount : outputs [ i ] . amount . toString ( ) ,
public _key : outputs [ i ] . public _key ,
global _index : "" + outputs [ i ] . global _index ,
index : "" + outputs [ i ] . index ,
tx _pub _key : outputs [ i ] . tx _pub _key
} ;
if ( outputs [ i ] . rct && typeof outputs [ i ] . rct !== 'undefined' ) {
sanitary _ _output . rct = outputs [ i ] . rct ;
}
sanitary _ _outputs . push ( sanitary _ _output ) ;
}
var sanitary _ _mix _outs = [ ] ;
for ( let i in mix _outs ) {
const sanitary _ _mix _outs _and _amount =
{
amount : "" + mix _outs [ i ] . amount ,
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 ,
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 ,
sending _amount : sending _amount . toString ( ) ,
change _amount : change _amount . toString ( ) ,
fee _amount : fee _amount . toString ( ) ,
outputs : sanitary _ _outputs ,
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 ;
}
const args _str = JSON . stringify ( args ) ;
const ret _string = this . Module . 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 } ;
}
return { // calling these out to set an interface
signed _serialized _tx : ret . serialized _signed _tx ,
tx _hash : ret . tx _hash ,
tx _key : ret . tx _key
} ;
}
}
//
module . exports = function ( options )
{
options = options || { }
//
return new Promise ( function ( resolve ) {
const ENVIRONMENT _IS _WEB = typeof window === "object" ;
const ENVIRONMENT _IS _WORKER = typeof importScripts === "function" ;
const ENVIRONMENT _IS _NODE = typeof process === "object" && process . browser !== true && typeof require === "function" && ENVIRONMENT _IS _WORKER == false ; // we want this to be true for Electron but not for a WebView
const ENVIRONMENT _IS _SHELL = ! ENVIRONMENT _IS _WEB && ! ENVIRONMENT _IS _NODE && ! ENVIRONMENT _IS _WORKER ;
var _Module _template =
{
locateFile : function ( filename , scriptDirectory )
{
// if (options["locateFile"]) {
// return options["locateFile"](filename, scriptDirectory)
// }
var this _scriptDirectory = scriptDirectory
const lastChar = this _scriptDirectory . charAt ( this _scriptDirectory . length - 1 )
if ( lastChar == "/" ) {
this _scriptDirectory = this _scriptDirectory . substring ( 0 , this _scriptDirectory . length - 1 ) // remove trailing "/"
}
const scriptDirectory _pathComponents = this _scriptDirectory . split ( "/" )
const lastPathComponent = scriptDirectory _pathComponents [ scriptDirectory _pathComponents . length - 1 ]
var pathTo _cryptonoteUtilsDir ; // add trailing slash to this
if ( lastPathComponent == "monero_utils" ) { // typical node or electron-main process
pathTo _cryptonoteUtilsDir = scriptDirectory _pathComponents . join ( "/" ) + "/"
} else if ( ENVIRONMENT _IS _WEB ) { // this will still match on electron-renderer, so the path must be patched up…
if ( typeof _ _dirname !== undefined && _ _dirname !== "/" ) { // looks like node running in browser.. assuming Electron-renderer
// have to check != "/" b/c webpack (I think) replaces __dirname
pathTo _cryptonoteUtilsDir = "file://" + _ _dirname + "/" // prepending "file://" because it's going to try to stream it
} else { // actual web browser
pathTo _cryptonoteUtilsDir = this _scriptDirectory + "/mymonero_core_js/monero_utils/" // this works for the MyMonero browser build, and is quite general, at least
}
} else {
throw "Undefined pathTo_cryptonoteUtilsDir. Please pass locateFile() to cryptonote_utils init."
}
const fullPath = pathTo _cryptonoteUtilsDir + filename
//
return fullPath
}
}
// if (ENVIRONMENT_IS_WEB && ENVIRONMENT_IS_NODE) { // that means it's probably electron-renderer
// const fs = require("fs");
// const path = require("path");
// const filepath = path.normalize(path.join(__dirname, "MyMoneroCoreCpp.wasm"));
// const wasmBinary = fs.readFileSync(filepath)
// console.log("wasmBinary", wasmBinary)
// _Module_template["wasmBinary"] = wasmBinary
// }
require ( "./MyMoneroCoreCpp" ) ( _Module _template ) . ready . then ( function ( thisModule )
{
const instance = new MyMoneroCoreBridge ( thisModule ) ;
resolve ( instance ) ;
} ) . catch ( function ( e ) {
console . error ( "Error loading MyMoneroCoreCpp:" , e ) ;
reject ( e ) ;
} ) ;
} ) ;
} ;