From f5ec03283b6d0d70f0bb1f93d3b9302171903401 Mon Sep 17 00:00:00 2001 From: zhongjiewang Date: Wed, 6 Jun 2018 12:51:02 +0800 Subject: [PATCH 1/2] add network database --- Gruntfile.js | 85 - index.js | 1 - lib.js | 223 -- LICENSE => lib/LICENSE | 0 lib/config/constants.js | 37 + chash.js => lib/crypto/chash.js | 3 - object_hash.js => lib/crypto/object_hash.js | 0 .../crypto/object_length.js | 0 lib/crypto/signature.js | 20 + lib/db/database.js | 127 + lib/db/mutex.js | 126 + lib/db/save_joint_helper.js | 506 ++++ lib/db/sqlite_pool.js | 332 +++ lib/db/storage.js | 11 + lib/network_manager.js | 254 ++ .../back_up/initial.trustnote-light.sqlite | Bin 0 -> 215040 bytes lib/offline/back_up/initial.trustnote.sqlite | Bin 0 -> 218112 bytes lib/offline/initial.trustnote-light.sqlite | Bin 0 -> 215040 bytes lib/test/database_test.js | 40 + lib/test/light_history_test.js | 2378 +++++++++++++++++ lib/test/network_manager_test.js | 62 + lib/test/wallet_test.js | 119 + lib/tools/event_bus.js | 9 + lib/tools/objLogin.js | 59 + lib/wallet.js | 231 ++ package.json | 4 +- validation.js | 24 - 27 files changed, 4314 insertions(+), 337 deletions(-) delete mode 100644 Gruntfile.js delete mode 100644 index.js delete mode 100644 lib.js rename LICENSE => lib/LICENSE (100%) create mode 100644 lib/config/constants.js rename chash.js => lib/crypto/chash.js (97%) rename object_hash.js => lib/crypto/object_hash.js (100%) rename object_length.js => lib/crypto/object_length.js (100%) create mode 100644 lib/crypto/signature.js create mode 100644 lib/db/database.js create mode 100644 lib/db/mutex.js create mode 100644 lib/db/save_joint_helper.js create mode 100644 lib/db/sqlite_pool.js create mode 100644 lib/db/storage.js create mode 100644 lib/network_manager.js create mode 100644 lib/offline/back_up/initial.trustnote-light.sqlite create mode 100644 lib/offline/back_up/initial.trustnote.sqlite create mode 100644 lib/offline/initial.trustnote-light.sqlite create mode 100644 lib/test/database_test.js create mode 100644 lib/test/light_history_test.js create mode 100644 lib/test/network_manager_test.js create mode 100644 lib/test/wallet_test.js create mode 100644 lib/tools/event_bus.js create mode 100644 lib/tools/objLogin.js create mode 100644 lib/wallet.js delete mode 100644 validation.js diff --git a/Gruntfile.js b/Gruntfile.js deleted file mode 100644 index 816e3b5..0000000 --- a/Gruntfile.js +++ /dev/null @@ -1,85 +0,0 @@ -/*global module:false*/ -module.exports = function(grunt) { - - // Project configuration. - grunt.initConfig({ - // Metadata. - meta: { - version: '0.1.0' - }, - browserify: { - dist: { - src: 'index.js', - dest: 'public/core.js' - } - }, - concat: { - options: { - banner: '<%= banner %>', - stripBanners: true - }, - dist: { - src: ['lib/FILE_NAME.js'], - dest: 'dist/FILE_NAME.js' - } - }, - uglify: { - options: { - mangle: false - }, - dist: { - src: 'public/core.js', - dest: 'public/core.js' - } - }, - jshint: { - options: { - curly: true, - eqeqeq: true, - immed: true, - latedef: true, - newcap: true, - noarg: true, - sub: true, - undef: true, - unused: true, - boss: true, - eqnull: true, - browser: true, - globals: { - jQuery: true - } - }, - gruntfile: { - src: 'Gruntfile.js' - }, - lib_test: { - src: ['lib/**/*.js', 'test/**/*.js'] - } - }, - qunit: { - files: ['test/**/*.html'] - }, - watch: { - gruntfile: { - files: '<%= jshint.gruntfile.src %>', - tasks: ['jshint:gruntfile'] - }, - lib_test: { - files: '<%= jshint.lib_test.src %>', - tasks: ['jshint:lib_test', 'qunit'] - } - } - }); - - // These plugins provide necessary tasks. - //grunt.loadNpmTasks('grunt-contrib-concat'); - //grunt.loadNpmTasks('grunt-contrib-qunit'); - //grunt.loadNpmTasks('grunt-contrib-jshint'); - //grunt.loadNpmTasks('grunt-contrib-watch'); - grunt.loadNpmTasks('grunt-contrib-uglify'); - grunt.loadNpmTasks('grunt-browserify'); - - // Default task. - grunt.registerTask('default', ['browserify:dist', 'uglify']); -}; diff --git a/index.js b/index.js deleted file mode 100644 index 1db48d6..0000000 --- a/index.js +++ /dev/null @@ -1 +0,0 @@ -window.Client = require('./lib'); \ No newline at end of file diff --git a/lib.js b/lib.js deleted file mode 100644 index 3e4d1eb..0000000 --- a/lib.js +++ /dev/null @@ -1,223 +0,0 @@ -"use strict" -var Bitcore = require("bitcore-lib"); -var crypto = require("crypto"); -var ecdsa = require('secp256k1'); -var Mnemonic = require("bitcore-mnemonic"); -var objectHash = require("./object_hash"); -var objectLength = require("./object_length"); -var validation = require("./validation"); - -var Base = {}; - -Base.getDeviceMessageHashToSign = objectHash.getDeviceMessageHashToSign; -Base.getUnitHashToSign = objectHash.getUnitHashToSign; -Base.getBase64Hash = objectHash.getBase64Hash; -Base.getUnitHash = objectHash.getUnitHash; - -Base.getHeadersSize = objectLength.getHeadersSize; -Base.getTotalPayloadSize = objectLength.getTotalPayloadSize; - -Base.isValidAddress = validation.isValidAddress; - -//生成随机数 -Base.randomBytes = function (num) { - try { - var random_base64 = crypto.randomBytes(num).toString("base64"); - return random_base64; - } catch (error) { - return 0; - } -} - -//生成助记词 -Base.mnemonic = function () { - try { - var mnemonic = new Mnemonic(); - return mnemonic.phrase; - } catch (error) { - return 0; - } -} - -//根据助记词生成根私钥 -Base.xPrivKey = function (mnemonic) { - try { - var xPrivKey = new Mnemonic(mnemonic).toHDPrivateKey(); - return xPrivKey.toString(); - } catch (error) { - return 0; - } -} - -//根据私钥生成私钥对象结构 -Base.objPrivKey = function (xPrivKey) { - try { - var objPrivKey = new Bitcore.HDPrivateKey.fromString(xPrivKey); - return objPrivKey; - } catch (error) { - return 0; - } -} - -//根公钥 -Base.xPubKey = function (xPrivKey) { - try { - var xPubKey = Bitcore.HDPublicKey(Bitcore.HDPrivateKey.fromString(xPrivKey)); - return xPubKey.toString(); - } catch (error) { - return 0; - } -} - -//钱包公钥 -Base.walletPubKey = function (xPrivKey, num) { - try { - xPrivKey = Bitcore.HDPrivateKey.fromString(xPrivKey); - var wallet_xPubKey = Bitcore.HDPublicKey(xPrivKey.derive("m/44'/0'/" + num + "'")); - return wallet_xPubKey.toString(); - } catch (error) { - return 0; - } -} - -//钱包ID -Base.walletID = function (walletPubKey) { - try { - var wallet_Id = crypto.createHash("sha256").update(walletPubKey, "utf8").digest("base64"); - return wallet_Id; - } catch (error) { - return 0; - } -} - -//生成设备地址 -Base.deviceAddress = function (xPrivKey) { - try { - xPrivKey = Bitcore.HDPrivateKey.fromString(xPrivKey); - var priv_key = xPrivKey.derive("m/1'").privateKey.bn.toBuffer({ - size: 32 - }); - var pub_b64 = ecdsa.publicKeyCreate(priv_key, true).toString('base64'); - var device_address = objectHash.getDeviceAddress(pub_b64); - return device_address; - } catch (error) { - return 0; - } -} - -//生成钱包的地址 -Base.walletAddress = function (wallet_xPubKey, change, num) { - try { - wallet_xPubKey = Bitcore.HDPublicKey.fromString(wallet_xPubKey); - var wallet_xPubKey_base64 = wallet_xPubKey.derive("m/" + change + "/" + num).publicKey.toBuffer().toString("base64"); - var address = objectHash.getChash160(["sig", { - "pubkey": wallet_xPubKey_base64 - }]); - return address; - } catch (error) { - return 0; - } -} - -//生成钱包地址对应的公钥 -Base.walletAddressPubkey = function (wallet_xPubKey, change, num) { - try { - wallet_xPubKey = Bitcore.HDPublicKey.fromString(wallet_xPubKey); - var wallet_xPubKey_base64 = wallet_xPubKey.derive("m/" + change + "/" + num).publicKey.toBuffer().toString("base64"); - return wallet_xPubKey_base64; - } catch (error) { - return 0; - } -} - -//签名 -Base.sign = function (b64_hash, xPrivKey, path) { - try { - var buf_to_sign = new Buffer(b64_hash, "base64"); - if (path != "null") { - var xPrivKey = new Bitcore.HDPrivateKey.fromString(xPrivKey); - var privateKey = xPrivKey.derive(path).privateKey; - var privKeyBuf = privateKey.bn.toBuffer({ - size: 32 - }); - } else { - var privKeyBuf = new Buffer(xPrivKey, "base64"); - } - var res = ecdsa.sign(buf_to_sign, privKeyBuf); - return res.signature.toString("base64"); - } catch (error) { - return 0; - } -} - -//验证签名 -Base.verify = function (b64_hash, sig, pub_key) { - try { - var buf_to_verify = new Buffer(b64_hash, "base64"); - var signature = new Buffer(sig, "base64"); // 64 bytes (32+32) - if (ecdsa.verify(buf_to_verify, signature, new Buffer(pub_key, "base64"))) - return 1; - else - return 0; - } catch (errer) { - return 0; - } -} - -//生成ecdsa签名公钥 -Base.ecdsaPubkey = function (xPrivKey, path) { - try { - if (path != "null") { - xPrivKey = Bitcore.HDPrivateKey.fromString(xPrivKey); - var priv_key = xPrivKey.derive(path).privateKey.bn.toBuffer({ - size: 32 - }); - } else { - var priv_key = new Buffer(xPrivKey, "base64"); - } - var pub_b64 = ecdsa.publicKeyCreate(priv_key, true).toString('base64'); - return pub_b64; - } catch (error) { - return 0; - } -} - -//生成m/1'私钥 -Base.m1PrivKey = function (xPrivKey) { - try { - var xPrivKey = new Bitcore.HDPrivateKey.fromString(xPrivKey); - var privateKey = xPrivKey.derive("m/1'").privateKey; - var privKeyBuf = privateKey.bn.toBuffer({ - size: 32 - }); - return privKeyBuf.toString("base64"); - } catch (error) { - return 0; - } -} - -//生成临时私钥 -Base.genPrivKey = function () { - var privKey; - try { - do { - privKey = crypto.randomBytes(32); - } - while (!ecdsa.privateKeyVerify(privKey)); - return privKey.toString("base64"); - } catch (error) { - return 0; - } -} - -//根据临时私钥生成公钥 -Base.genPubKey = function (privKey) { - try { - var pubKey = ecdsa.publicKeyCreate(new Buffer(privKey, "base64"), true).toString('base64'); - return pubKey; - } catch (error) { - return 0; - } -} - -module.exports = Base; \ No newline at end of file diff --git a/LICENSE b/lib/LICENSE similarity index 100% rename from LICENSE rename to lib/LICENSE diff --git a/lib/config/constants.js b/lib/config/constants.js new file mode 100644 index 0000000..958dc78 --- /dev/null +++ b/lib/config/constants.js @@ -0,0 +1,37 @@ +"use strict"; + +exports.COUNT_WITNESSES = 12; + + +/* +exports.MAX_WITNESS_LIST_MUTATIONS = 1; +exports.TOTAL_WHITEBYTES = 5e14; +exports.MAJORITY_OF_WITNESSES = (exports.COUNT_WITNESSES%2===0) ? (exports.COUNT_WITNESSES/2+1) : Math.ceil(exports.COUNT_WITNESSES/2); +exports.COUNT_MC_BALLS_FOR_PAID_WITNESSING = 100; + +exports.version = '1.0'; +exports.alt = '1'; + +exports.GENESIS_UNIT = 'rg1RzwKwnfRHjBojGol3gZaC5w7kR++rOR6O61JRsrQ='; +exports.BLACKBYTES_ASSET = '9qQId3BlWRQHvVy+STWyLKFb3lUd0xfQhX6mPVEHC2c='; + +exports.HASH_LENGTH = 44; +exports.PUBKEY_LENGTH = 44; +exports.SIG_LENGTH = 88; + +// anti-spam limits +exports.MAX_AUTHORS_PER_UNIT = 16; +exports.MAX_PARENTS_PER_UNIT = 16; +exports.MAX_MESSAGES_PER_UNIT = 128; +exports.MAX_SPEND_PROOFS_PER_MESSAGE = 128; +exports.MAX_INPUTS_PER_PAYMENT_MESSAGE = 128; +exports.MAX_OUTPUTS_PER_PAYMENT_MESSAGE = 128; +exports.MAX_CHOICES_PER_POLL = 128; +exports.MAX_DENOMINATIONS_PER_ASSET_DEFINITION = 64; +exports.MAX_ATTESTORS_PER_ASSET = 64; +exports.MAX_DATA_FEED_NAME_LENGTH = 64; +exports.MAX_DATA_FEED_VALUE_LENGTH = 64; +exports.MAX_AUTHENTIFIER_LENGTH = 4096; +exports.MAX_CAP = 9e15; +exports.MAX_COMPLEXITY = 100; +*/ \ No newline at end of file diff --git a/chash.js b/lib/crypto/chash.js similarity index 97% rename from chash.js rename to lib/crypto/chash.js index d5416c3..d882cd7 100644 --- a/chash.js +++ b/lib/crypto/chash.js @@ -157,10 +157,7 @@ function isChashValid(encoded) { var binChash = buffer2bin(chash); var separated = separateIntoCleanDataAndChecksum(binChash); var clean_data = bin2buffer(separated.clean_data); - //console.log("clean data", clean_data); var checksum = bin2buffer(separated.checksum); - //console.log(checksum); - //console.log(getChecksum(clean_data)); return checksum.equals(getChecksum(clean_data)); } diff --git a/object_hash.js b/lib/crypto/object_hash.js similarity index 100% rename from object_hash.js rename to lib/crypto/object_hash.js diff --git a/object_length.js b/lib/crypto/object_length.js similarity index 100% rename from object_length.js rename to lib/crypto/object_length.js diff --git a/lib/crypto/signature.js b/lib/crypto/signature.js new file mode 100644 index 0000000..beda037 --- /dev/null +++ b/lib/crypto/signature.js @@ -0,0 +1,20 @@ +/*jslint node: true */ +"use strict"; +var ecdsa = require('secp256k1'); + +exports.sign = function(hash, priv_key){ + var res = ecdsa.sign(hash, priv_key); + return res.signature.toString("base64"); +}; + +exports.verify = function(hash, b64_sig, b64_pub_key){ + try{ + var signature = new Buffer(b64_sig, "base64"); // 64 bytes (32+32) + return ecdsa.verify(hash, signature, new Buffer(b64_pub_key, "base64")); + } + catch(e){ + console.log('signature verification exception: '+e.toString()); + return false; + } +}; + diff --git a/lib/db/database.js b/lib/db/database.js new file mode 100644 index 0000000..87c29d2 --- /dev/null +++ b/lib/db/database.js @@ -0,0 +1,127 @@ +'use strict' + +var sqlitePool = require('./sqlite_pool.js'); +var path = '../offline/initial.trustnote-light.sqlite' +const constants = require('../config/constants'); +var saveJointHelper = require('./save_joint_helper'); + + +class DataBase { + + constructor() { + this.db = sqlitePool(path,1, false, this.errorHandler); + } + + errorHandler(err) { + console.log('catch error: ',err); + } + + query (){ + this.db.query.apply(null,arguments); + } + + insertWitnesses (arrWitnesses) { + return new Promise((resolve, reject) => { + if (arrWitnesses.length !== constants.COUNT_WITNESSES){ + throw Error("attempting to insert wrong number of witnesses: "+arrWitnesses.length); + } + var placeholders = Array.apply(null, Array(arrWitnesses.length)).map(function(){ return '(?)'; }).join(','); + this.query("INSERT INTO my_witnesses (address) VALUES "+placeholders, arrWitnesses, function(){ + console.log('inserted witnesses'); + resolve(); + }); + }); + } + + readMyWitnesses () { + return new Promise((resolve, reject) => { + this.query("SELECT address FROM my_witnesses ORDER BY address", function(rows){ + var arrWitnesses = rows.map(function(row){ return row.address; }); + resolve(arrWitnesses); + }) + }); + } + + insertMyAddresses (wallet, is_change, address_index, address, arrDefinition){ + return new Promise((resolve, reject) => { + let db = this.db; + if (typeof address_index === 'string' && is_change) + throw Error("address with string index cannot be change address"); + var address_index_column_name = (typeof address_index === 'string') ? 'app' : 'address_index'; + this.query( // IGNORE in case the address was already generated + "INSERT "+db.getIgnore()+" INTO my_addresses (wallet, is_change, "+address_index_column_name+", address, definition) VALUES (?,?,?,?,?)", + [wallet, is_change, address_index, address, JSON.stringify(arrDefinition)], + function(){ + resolve(); + } + ); + }); + } + + readMyAddresses () { + return new Promise((resolve, reject) => { + this.query("SELECT address FROM my_addresses UNION SELECT shared_address AS address FROM shared_addresses", function(rows){ + var arrAddresses = rows.map(function(row){ return row.address; }); + resolve(arrAddresses); + }) + }); + } + + readListOfUnstableUnits(){ + return new Promise((resolve, reject) => { + this.query("SELECT unit FROM units WHERE is_stable=0", function(rows){ + var arrUnits = rows.map(function(row){ return row.unit; }); + resolve(arrUnits); + }) + }) + } + + readKnownStableUnits (arrAddresses) { + return new Promise ((resolve, reject) => { + var strAddressList = arrAddresses.map(this.db.escape).join(', '); + this.query( + "SELECT unit FROM unit_authors JOIN units USING(unit) WHERE is_stable=1 AND address IN("+strAddressList+") \n\ + UNION \n\ + SELECT unit FROM outputs JOIN units USING(unit) WHERE is_stable=1 AND address IN("+strAddressList+")", + function(rows){ + let known_stable_units = rows.map(function(row){ return row.unit; }); + resolve(known_stable_units); + } + ); + }) + } + + __saveJoint (joint) { + let sequence = 'good'; + let objValidationState = { + sequence: sequence, + arrDoubleSpendInputs: [], + arrAdditionalQueries: [] + }; + return new Promise((resolve, reject )=>{ + saveJointHelper(this.db, joint, objValidationState , null, function(err, objUnit, objJoint){ + if(err) errorHandler(err); + // console.log('err: ',err, '\nobjUnit', JSON.stringify(objUnit, null, 3)); + resolve(); + }); + }) + } + + async saveLightHistory(lightHistoryResponse) { + var arrProvenUnits = [];// is stable units + for (let joint of lightHistoryResponse.joints) { + await this.__saveJoint(joint); + } + for ( let ball of lightHistoryResponse.proofchain_balls) { + arrProvenUnits.push(ball.unit); + } + return new Promise((resolve,reject) => { + this.query("UPDATE units SET is_stable=1, is_free=0 WHERE unit IN(?)", [arrProvenUnits], function(){ + console.log('update units is_stable successfully'); + resolve(); + }); + }) + } +} + +module.exports = DataBase; diff --git a/lib/db/mutex.js b/lib/db/mutex.js new file mode 100644 index 0000000..ab190a6 --- /dev/null +++ b/lib/db/mutex.js @@ -0,0 +1,126 @@ +/*jslint node: true */ +"use strict"; +var _ = require('lodash'); +// require('./enforce_singleton.js'); + +var arrQueuedJobs = []; +var arrLockedKeyArrays = []; + +function getCountOfQueuedJobs(){ + return arrQueuedJobs.length; +} + +function getCountOfLocks(){ + return arrLockedKeyArrays.length; +} + +function isAnyOfKeysLocked(arrKeys){ + for (var i=0; i 30*1000) + throw Error("possible deadlock on job "+require('util').inspect(job)+",\nproc:"+job.proc.toString()+" \nall jobs: "+require('util').inspect(arrQueuedJobs, {depth: null})); + } +} + +// long running locks are normal in multisig scenarios +//setInterval(checkForDeadlocks, 1000); + +setInterval(function(){ + console.log("queued jobs: "+JSON.stringify(arrQueuedJobs.map(function(job){ return job.arrKeys; }))+", locked keys: "+JSON.stringify(arrLockedKeyArrays)); +}, 10000); + +exports.lock = lock; +exports.lockOrSkip = lockOrSkip; +exports.getCountOfQueuedJobs = getCountOfQueuedJobs; +exports.getCountOfLocks = getCountOfLocks; + +/* +function test(key){ + var loc = "localvar"+key; + lock( + [key], + function(cb){ + console.log("doing "+key); + setTimeout(function(){ + console.log("done "+key); + cb("arg1", "arg2"); + }, 1000) + }, + function(arg1, arg2){ + console.log("got "+arg1+", "+arg2+", loc="+loc); + } + ); +} + +test("key1"); +test("key2"); +*/ diff --git a/lib/db/save_joint_helper.js b/lib/db/save_joint_helper.js new file mode 100644 index 0000000..6f41ef5 --- /dev/null +++ b/lib/db/save_joint_helper.js @@ -0,0 +1,506 @@ +'use strict' + +const constants = require('../config/constants'); +var storage = require('./storage'); +var mutex = require('./mutex'); +var async = require('async'); +var objectHash = require('../crypto/object_hash'); +var Definition = {}; +Definition.hasReferences = hasReferences; +var conf = {}; +conf.bLight = true; + +function saveJoint(db, objJoint, objValidationState, preCommitCallback, onDone) { + var objUnit = objJoint.unit; + console.log("\nsaving unit "+objUnit.unit); + + db.takeConnectionFromPool(function(conn){ + var arrQueries = []; + conn.addQuery(arrQueries, "BEGIN"); + + // additional queries generated by the validator, used only when received a doublespend + for (var i=0; i 1) + throw Error("multiple src outputs found"); + if (rows.length === 0){ + if (conf.bLight) // it's normal that a light client doesn't store the previous output + return handleAddress(null); + else + throw Error("src output not found"); + } + var row = rows[0]; + if (!(!asset && !row.asset || asset === row.asset)) + throw Error("asset doesn't match"); + if (denomination !== row.denomination) + throw Error("denomination doesn't match"); + var address = row.address; + if (arrAuthorAddresses.indexOf(address) === -1) + throw Error("src output address not among authors"); + handleAddress(address); + } + ); + } + + function addInlinePaymentQueries(cb){ + async.forEachOfSeries( + objUnit.messages, + function(message, i, cb2){ + if (message.payload_location !== 'inline') + return cb2(); + var payload = message.payload; + if (message.app !== 'payment') + return cb2(); + + var denomination = payload.denomination || 1; + + async.forEachOfSeries( + payload.inputs, + function(input, j, cb3){ + var type = input.type || "transfer"; + var src_unit = (type === "transfer") ? input.unit : null; + var src_message_index = (type === "transfer") ? input.message_index : null; + var src_output_index = (type === "transfer") ? input.output_index : null; + var from_main_chain_index = (type === "witnessing" || type === "headers_commission") ? input.from_main_chain_index : null; + var to_main_chain_index = (type === "witnessing" || type === "headers_commission") ? input.to_main_chain_index : null; + + var determineInputAddress = function(handleAddress){ + if (type === "headers_commission" || type === "witnessing" || type === "issue") + return handleAddress((arrAuthorAddresses.length === 1) ? arrAuthorAddresses[0] : input.address); + // hereafter, transfer + if (arrAuthorAddresses.length === 1) + return handleAddress(arrAuthorAddresses[0]); + determineInputAddressFromSrcOutput(payload.asset, denomination, input, handleAddress); + }; + + determineInputAddress(function(address){ + var is_unique = + objValidationState.arrDoubleSpendInputs.some(function(ds){ return (ds.message_index === i && ds.input_index === j); }) + ? null : 1; + conn.addQuery(arrQueries, "INSERT INTO inputs \n\ + (unit, message_index, input_index, type, \n\ + src_unit, src_message_index, src_output_index, \ + from_main_chain_index, to_main_chain_index, \n\ + denomination, amount, serial_number, \n\ + asset, is_unique, address) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)", + [objUnit.unit, i, j, type, + src_unit, src_message_index, src_output_index, + from_main_chain_index, to_main_chain_index, + denomination, input.amount, input.serial_number, + payload.asset, is_unique, address]); + switch (type){ + case "transfer": + conn.addQuery(arrQueries, + "UPDATE outputs SET is_spent=1 WHERE unit=? AND message_index=? AND output_index=?", + [src_unit, src_message_index, src_output_index]); + break; + case "headers_commission": + case "witnessing": + var table = type + "_outputs"; + conn.addQuery(arrQueries, "UPDATE "+table+" SET is_spent=1 \n\ + WHERE main_chain_index>=? AND main_chain_index<=? AND address=?", + [from_main_chain_index, to_main_chain_index, address]); + break; + } + cb3(); + }); + }, + function(){ + for (var j=0; j=?) \n\ + ORDER BY witnessed_level DESC, \n\ + level-witnessed_level ASC, \n\ + unit ASC \n\ + LIMIT 1", + [objUnit.parent_units, objUnit.witness_list_unit, + objUnit.unit, objUnit.witness_list_unit, constants.COUNT_WITNESSES - constants.MAX_WITNESS_LIST_MUTATIONS], + function(rows){ + if (rows.length !== 1) + throw Error("zero or more than one best parent unit?"); + my_best_parent_unit = rows[0].unit; + conn.query("UPDATE units SET best_parent_unit=? WHERE unit=?", [my_best_parent_unit, objUnit.unit], function(){ cb(); }); + } + ); + } + + function updateLevel(cb){ + conn.query("SELECT MAX(level) AS max_level FROM units WHERE unit IN(?)", [objUnit.parent_units], function(rows){ + if (rows.length !== 1) + throw Error("not a single max level?"); + conn.query("UPDATE units SET level=? WHERE unit=?", [rows[0].max_level + 1, objUnit.unit], function(){ + cb(); + }); + }); + } + + + function updateWitnessedLevel(cb){ + if (objUnit.witnesses) + updateWitnessedLevelByWitnesslist(objUnit.witnesses, cb); + else + storage.readWitnessList(conn, objUnit.witness_list_unit, function(arrWitnesses){ + updateWitnessedLevelByWitnesslist(arrWitnesses, cb); + }); + } + + // The level at which we collect at least 7 distinct witnesses while walking up the main chain from our unit. + // The unit itself is not counted even if it is authored by a witness + function updateWitnessedLevelByWitnesslist(arrWitnesses, cb){ + var arrCollectedWitnesses = []; + + function setWitnessedLevel(witnessed_level){ + conn.query("UPDATE units SET witnessed_level=? WHERE unit=?", [witnessed_level, objUnit.unit], function(){ + cb(); + }); + } + + function addWitnessesAndGoUp(start_unit){ + storage.readStaticUnitProps(conn, start_unit, function(props){ + var best_parent_unit = props.best_parent_unit; + var level = props.level; + if (level === null) + throw Error("null level in updateWitnessedLevel"); + if (level === 0) // genesis + return setWitnessedLevel(0); + storage.readUnitAuthors(conn, start_unit, function(arrAuthors){ + for (var i=0; i { + var options = {}; + let _this = this; + this.ws = new WebSocket(url); + let ws = this.ws; + ws.peer = url; + ws.setMaxListeners(20); //avoid warning + ws.once('open', function onWsOpen() { + _this.ws.assocPendingRequests = {}; + console.log('connected to ' + url ); + console.log('connectToHub done'); + resolve(); + }); + ws.on('close', function onWsClose() { + console.log('close event, removing '+ url); + }); + ws.on('error', function onWsError(e){ + console.log("error from server "+url+": "+e); + }); + ws.on('message', this.onWebsocketMessage.bind(this)); + }); + } + + onWebsocketMessage(message) { + let ws = this.ws; + if (ws.readyState !== ws.OPEN) + return; + + console.log('\nRECEIVED '+(message.length > 1000 ? message.substr(0,1000)+'...' : message)+' from '+ws.peer); + ws.last_ts = Date.now(); + + try{ + var arrMessage = JSON.parse(message); + } + catch(e){ + return console.log('failed to json.parse message '+message); + } + // console.log(JSON.stringify(arrMessage, null, 3)); + var message_type = arrMessage[0]; + var content = arrMessage[1]; + + switch (message_type){ + case 'justsaying': + return this.handleJustsaying(content.subject, content.body); + + // case 'request': + // return handleRequest(ws, content.tag, content.command, content.params); + + case 'response': + return this.handleResponse(content.tag, content.response); + + default: + console.log("unknown type: "+message_type); + } + } + + handleJustsaying (subject, body){ + let ws = this.ws; + switch (subject){ + case 'refresh': + return; + case 'version': + + break; + + case 'new_version': // a new version is available + break; + + case 'hub/push_project_number': + + break; + + case 'bugreport': + + break; + + case 'joint': + + return; + case 'free_joints_end': + case 'result': + case 'info': + case 'error': + break; + + case 'private_payment': + break; + + case 'my_url': + break; + + case 'want_echo': + break; + + case 'your_echo': // comes on the same ws as my_url, claimed_url is already set + break; + + // I'm a hub, the peer wants to authenticate + case 'hub/login': + break; + + // I'm a hub, the peer wants to download new messages + case 'hub/refresh': + break; + + // I'm a hub, the peer wants to remove a message that he's just handled + case 'hub/delete': + break; + + // I'm connected to a hub + case 'hub/challenge': + case 'hub/message': + case 'hub/message_box_status': + if (!body) { + return; + } + this.challenge = body; + // eventBus.emit("message_from_hub", ws, subject, body); + break; + + // I'm light client + case 'light/have_updates': + break; + + // I'm light vendor + case 'light/new_address_to_watch': + break; + } + } + + + handleResponse(tag, response){ + let ws = this.ws; + var pendingRequest = ws.assocPendingRequests[tag]; + if (!pendingRequest) // was canceled due to timeout or rerouted and answered by another peer + //throw "no req by tag "+tag; + return console.log("no req by tag "+tag); + pendingRequest.responseHandlers.forEach(function(responseHandler){ + process.nextTick(function(){ + responseHandler(ws, pendingRequest.request, response); + }); + }); + + clearTimeout(pendingRequest.reroute_timer); + clearTimeout(pendingRequest.cancel_timer); + delete ws.assocPendingRequests[tag]; + + // if the request was rerouted, cancel all other pending requests + if (assocReroutedConnectionsByTag[tag]){ + assocReroutedConnectionsByTag[tag].forEach(function(client){ + if (client.assocPendingRequests[tag]){ + clearTimeout(client.assocPendingRequests[tag].reroute_timer); + clearTimeout(client.assocPendingRequests[tag].cancel_timer); + delete client.assocPendingRequests[tag]; + } + }); + delete assocReroutedConnectionsByTag[tag]; + } + } + + sendMessage(type, content) { + let ws = this.ws; + var message = JSON.stringify([type, content]); + if (ws.readyState !== ws.OPEN) + return console.log("readyState="+ws.readyState+' on peer '+ws.peer+', will not send '+message); + console.log("\nSENDING "+message+" to "+ws.peer); + ws.send(message); + } + + sendJustsaying(subject, body){ + this.sendMessage('justsaying', {subject: subject, body: body}); + } + + + sendVersion(version){ + this.sendJustsaying('version', version); + } + + + // if a 2nd identical request is issued before we receive a response to the 1st request, then: + // 1. its responseHandler will be called too but no second request will be sent to the wire + // 2. bReroutable flag must be the same + _sendRequest(command, params, bReroutable, responseHandler){ + let ws = this.ws; + var request = {command: command}; + if (params) + request.params = params; + var content = _.clone(request); + var tag = objectHash.getBase64Hash(request); + //if (ws.assocPendingRequests[tag]) // ignore duplicate requests while still waiting for response from the same peer + // return console.log("will not send identical "+command+" request"); + if (ws.assocPendingRequests[tag]){ + console.log('already sent a '+command+' request to '+ws.peer+', will add one more response handler rather than sending a duplicate request to the wire'); + ws.assocPendingRequests[tag].responseHandlers.push(responseHandler); + } + else{ + content.tag = tag; + var cancel_timer = bReroutable ? null : setTimeout(function(){ + ws.assocPendingRequests[tag].responseHandlers.forEach(function(rh){ + rh(ws, request, {error: "[internal] response timeout"}); + }); + delete ws.assocPendingRequests[tag]; + }, RESPONSE_TIMEOUT); + ws.assocPendingRequests[tag] = { + request: request, + responseHandlers: [responseHandler], + cancel_timer: cancel_timer + }; + this.sendMessage('request', content); + } + } + sendRequest (command, params, bReroutable) { + let _this = this; + let resolve; + const promise = new Promise(r => resolve = r); + _this._sendRequest(command,params,bReroutable, function(ws, request, response){ + resolve({ws:ws, request:request, response: response}); + }); + return promise; + } + + fetchChallenge() { + if (this.challenge) { + return this.challenge; + } + console.log('now, we have not receive hub/challenge from a hub, challenge is null'); + return null; + } + +} + +module.exports = NetworkManager; +/* +*/ \ No newline at end of file diff --git a/lib/offline/back_up/initial.trustnote-light.sqlite b/lib/offline/back_up/initial.trustnote-light.sqlite new file mode 100644 index 0000000000000000000000000000000000000000..6a661ce003a5bb164f7f6b44928cd5bffed87d40 GIT binary patch literal 215040 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 a7E=csfe8F$5Ww~SPX>3a6%l9$1pYrjE__V@ literal 0 HcmV?d00001 diff --git a/lib/offline/initial.trustnote-light.sqlite b/lib/offline/initial.trustnote-light.sqlite new file mode 100644 index 0000000000000000000000000000000000000000..6a661ce003a5bb164f7f6b44928cd5bffed87d40 GIT binary patch literal 215040 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 私钥 ==> 公钥 ==> 钱包地址 ==> 钱包ID + let info = {}; + info.mnemonic = wallet.mnemonic(); + info.xPrivKey = wallet.xPrivKey(info.mnemonic); + info.xPubKey = wallet.xPubKey(info.xPrivKey); + + info.walletPubKey = wallet.walletPubKey(info.xPrivKey, 0); + info.walletAddress = wallet.walletAddress(info.walletPubKey, 0, 0); + info.walletID = wallet.walletID(info.walletAddress); + info.walletAddressPubkey = wallet.walletAddressPubkey(info.walletPubKey,0, 0) + + let walletID = info.walletID; + let is_change = 0; + let address_index = 0; + let address = info.walletAddress; + var arrDefinition = ['sig', {pubkey: info.walletAddressPubkey }]; + + // await db.insertMyAddresses(walletID, is_change, address_index, address, arrDefinition ); + // let addresses = ['WP2JOUJQ24YEECSEXQIZWKYJ5HTBTE4K', 'QWVH56KBONKG7BIOZ7LJMYLLQJHQAWWU']; + // let known_stable_units = await db.readKnownStableUnits(addresses); + // console.log(known_stable_units); + + const lightHistoryResponse = require('./light_history_test'); + console.log('joints units count: ', lightHistoryResponse.joints.length); + await db.saveLightHistory(lightHistoryResponse); +} + +test (); \ No newline at end of file diff --git a/lib/test/light_history_test.js b/lib/test/light_history_test.js new file mode 100644 index 0000000..466b0e8 --- /dev/null +++ b/lib/test/light_history_test.js @@ -0,0 +1,2378 @@ +'use strict' + +module.exports = +{ + "unstable_mc_joints": [ + { + "unit": { + "unit": "xNDly7PY9uEZt2dts2hSlydpTN+3sK43p7IUkO/RaU4=", + "version": "1.0", + "alt": "1", + "witness_list_unit": "rg1RzwKwnfRHjBojGol3gZaC5w7kR++rOR6O61JRsrQ=", + "last_ball_unit": "O8leIlxqYA0K0CXy7rA5lkZ2nrQhRShq0FZV9WQLuxo=", + "last_ball": "QFAwPXjwzRqXV5wHfXCMFB7xCUa7TI9ozZJI8Ofqp6E=", + "headers_commission": 344, + "payload_commission": 157, + "main_chain_index": 263557, + "timestamp": 1528190149, + "parent_units": [ + "r1RZ30ObaZsqu825JG4yvppoPokHuMEAlmPhhV+1Zck=", + "s0eKzg6zLMd8iCwxvcACzL/uIGrR2SqKdH4/AS+rcqE=" + ], + "authors": [ + { + "address": "KM5FZPIP264YRRWRQPXF7F7Y6ETDEW5Y", + "authentifiers": { + "r": "5N0/GboEhHbDQYrBeXMstab2n9mEtzrPOiyepiC/QQ9xR94JA5i6lxokEo9e4nYimXtq2zU91GYyRXWSS8/eEA==" + } + } + ], + "messages": [ + { + "app": "payment", + "payload_hash": "RDEJLVQs7b2owoKchPq2+Lv0OCXDD/RGSoMRtEEh4uU=", + "payload_location": "inline", + "payload": { + "inputs": [ + { + "unit": "Dx6spULXck+9PppTHCu4vn2haZGtGxTMCCO9AKO1ISo=", + "message_index": 0, + "output_index": 0 + } + ], + "outputs": [ + { + "address": "KM5FZPIP264YRRWRQPXF7F7Y6ETDEW5Y", + "amount": 1010442 + } + ] + } + } + ] + } + }, + { + "unit": { + "unit": "r1RZ30ObaZsqu825JG4yvppoPokHuMEAlmPhhV+1Zck=", + "version": "1.0", + "alt": "1", + "witness_list_unit": "rg1RzwKwnfRHjBojGol3gZaC5w7kR++rOR6O61JRsrQ=", + "last_ball_unit": "sWBqfe9vUQPZe30euItbkPqXkaMUhmQ2X439FGnsDKA=", + "last_ball": "G5s488McsnuNBFeTV6XaqXF8U89iOxOJvPHOpQrEQJY=", + "headers_commission": 344, + "payload_commission": 157, + "main_chain_index": 263556, + "timestamp": 1528190145, + "parent_units": [ + "xTC3U0+006TlWZjnL3ml/aYE4qZZyG6hOCwbFd/22bY=" + ], + "authors": [ + { + "address": "XIM76DRNUNFWPXPI5AGOCYNMA3IOXL7V", + "authentifiers": { + "r": "Ms7eR9+M4vvdKDBl0PR0DNw1Mw9LCXHiDeQRF6UDHRka4QTiA111bIQ0QLR2q6SrL+Kn65CW8pidj+isaac7rQ==" + } + } + ], + "messages": [ + { + "app": "payment", + "payload_hash": "fFEEG5ORcjixY3z6i7XPuKhFHgAVYtypIjr+CKqVXKI=", + "payload_location": "inline", + "payload": { + "inputs": [ + { + "unit": "qlJQamfg+keQNST12uD8Kr/KKxvsN00QpTabYwJESJw=", + "message_index": 0, + "output_index": 0 + } + ], + "outputs": [ + { + "address": "XIM76DRNUNFWPXPI5AGOCYNMA3IOXL7V", + "amount": 1121163 + } + ] + } + } + ] + } + }, + { + "unit": { + "unit": "xTC3U0+006TlWZjnL3ml/aYE4qZZyG6hOCwbFd/22bY=", + "version": "1.0", + "alt": "1", + "witness_list_unit": "rg1RzwKwnfRHjBojGol3gZaC5w7kR++rOR6O61JRsrQ=", + "last_ball_unit": "A3h3WfU2WB/YBxzr8hiDaamYjVk9iY598ES/5a7QQGU=", + "last_ball": "8FZBy31ld+Lj8Cy4bJGupQQgunzObgTWfF6G9o6NdSU=", + "headers_commission": 344, + "payload_commission": 157, + "main_chain_index": 263555, + "timestamp": 1528190143, + "parent_units": [ + "ohxQSdMnY8HhTGqgqhkY/YHw84QobornsoAbk6DXppM=" + ], + "authors": [ + { + "address": "33RVJX3WBNZXJOSFCU6KK7O7TVEXLXGR", + "authentifiers": { + "r": "3WWHN0CbrkTYTV6nouy+bi2jw0AGrI8Co7bSMe57YDZsz3d6VHUxLOhPqKEpJYfgROohgSBNNsNASLuM3LwPpg==" + } + } + ], + "messages": [ + { + "app": "payment", + "payload_hash": "jIE2E+e19k839M1DimToyMxGbEIIws4ZpESlP+G4Q28=", + "payload_location": "inline", + "payload": { + "inputs": [ + { + "unit": "8fwf8+xRBsmV5RS0ZsaIpYT1pb02PpbN7s1lxZ0XGlA=", + "message_index": 0, + "output_index": 0 + } + ], + "outputs": [ + { + "address": "33RVJX3WBNZXJOSFCU6KK7O7TVEXLXGR", + "amount": 1043508 + } + ] + } + } + ] + } + }, + { + "unit": { + "unit": "ohxQSdMnY8HhTGqgqhkY/YHw84QobornsoAbk6DXppM=", + "version": "1.0", + "alt": "1", + "witness_list_unit": "rg1RzwKwnfRHjBojGol3gZaC5w7kR++rOR6O61JRsrQ=", + "last_ball_unit": "Dx6spULXck+9PppTHCu4vn2haZGtGxTMCCO9AKO1ISo=", + "last_ball": "Cay5Ih0rwn9Kwl4HGSj54Jz22uDAvCW5cbXSXBSnzTA=", + "headers_commission": 344, + "payload_commission": 157, + "main_chain_index": 263554, + "timestamp": 1528190141, + "parent_units": [ + "Lnxwth2BdPp7mOx2rfCr5xWbGtL2rnESSk9rIlL7fzc=" + ], + "authors": [ + { + "address": "J3XIKRBU4BV2PX2BP4PSGIXDVND2XRIF", + "authentifiers": { + "r": "TRqx+FzTSmNa5Uu1XAQuwFcMdzBHP962DPDyt8EiMtAYGxEzkpF1AMIrgBuS3zRdLNSaLoDXaaFy61DGblwikA==" + } + } + ], + "messages": [ + { + "app": "payment", + "payload_hash": "a15ueTRyV567sLqZd9kb9BFLY4peibWtiqE3PD7O144=", + "payload_location": "inline", + "payload": { + "inputs": [ + { + "unit": "PAIAzax7zlet/ig3hfjLUbiBQf5OXOZHM1VdcibUY9Q=", + "message_index": 0, + "output_index": 0 + } + ], + "outputs": [ + { + "address": "J3XIKRBU4BV2PX2BP4PSGIXDVND2XRIF", + "amount": 1053027 + } + ] + } + } + ] + } + }, + { + "unit": { + "unit": "Lnxwth2BdPp7mOx2rfCr5xWbGtL2rnESSk9rIlL7fzc=", + "version": "1.0", + "alt": "1", + "witness_list_unit": "rg1RzwKwnfRHjBojGol3gZaC5w7kR++rOR6O61JRsrQ=", + "last_ball_unit": "fYRzHsMCnhW9Hzc9SmU5/EPmCBCmCGwyRn7s0KK3Be4=", + "last_ball": "3vMKagn7bztNTQoPpuYyzRId7vy8GLDIO3gCJ4auKxc=", + "headers_commission": 344, + "payload_commission": 157, + "main_chain_index": 263553, + "timestamp": 1528190139, + "parent_units": [ + "83f8ZJYTLWMxaUgxqnVtxbGqnNi5dPyx3DZJvHVZujY=" + ], + "authors": [ + { + "address": "FYQXBPQWBPXWMJGCHWJ52AK2QMEOICR5", + "authentifiers": { + "r": "tQ+mBcgQDM82KD7pDzXpsu0V5gbO5GDKg3e5JWK1YMpRsFCb6ELcg35y6hC/iOd8eZszlynA0JqsqAFRlfgEPA==" + } + } + ], + "messages": [ + { + "app": "payment", + "payload_hash": "TvHbaicd6rw/ciEpXGUT51jv6jx4R4yFJn+ruaOhiw4=", + "payload_location": "inline", + "payload": { + "inputs": [ + { + "unit": "0bJeNAg1F2cfwiMIhZv7uQ0+p4dkmmUa39jxmXSX7Yc=", + "message_index": 0, + "output_index": 0 + } + ], + "outputs": [ + { + "address": "FYQXBPQWBPXWMJGCHWJ52AK2QMEOICR5", + "amount": 1058037 + } + ] + } + } + ] + } + }, + { + "unit": { + "unit": "83f8ZJYTLWMxaUgxqnVtxbGqnNi5dPyx3DZJvHVZujY=", + "version": "1.0", + "alt": "1", + "witness_list_unit": "rg1RzwKwnfRHjBojGol3gZaC5w7kR++rOR6O61JRsrQ=", + "last_ball_unit": "fYRzHsMCnhW9Hzc9SmU5/EPmCBCmCGwyRn7s0KK3Be4=", + "last_ball": "3vMKagn7bztNTQoPpuYyzRId7vy8GLDIO3gCJ4auKxc=", + "headers_commission": 344, + "payload_commission": 157, + "main_chain_index": 263552, + "timestamp": 1528190137, + "parent_units": [ + "Cj3vU479XQrazYzzEeIE3Oc1yZir+a1pJH1cjbhwYB8=" + ], + "authors": [ + { + "address": "2SATGZDFDXNNJRVZ52O4J6VYTTMO2EZR", + "authentifiers": { + "r": "IYBMl7dRXsA+2d0K8m6gO8ztF8KUOZVSwkV5Uc3+KG4Gw5urjQnlcjoN7ud4g3t/mbh/auSfwGcYgC/3zPxlQA==" + } + } + ], + "messages": [ + { + "app": "payment", + "payload_hash": "FkBwzQEfYZUlVaakoNvCDcxMeXIMT4jgYNq+9pbJS2g=", + "payload_location": "inline", + "payload": { + "inputs": [ + { + "unit": "6rUWs2Ei1L2IaG2u+VuYZrNgsQTVm/hUuLE8psLDcLU=", + "message_index": 0, + "output_index": 0 + } + ], + "outputs": [ + { + "address": "2SATGZDFDXNNJRVZ52O4J6VYTTMO2EZR", + "amount": 1036995 + } + ] + } + } + ] + } + }, + { + "unit": { + "unit": "Cj3vU479XQrazYzzEeIE3Oc1yZir+a1pJH1cjbhwYB8=", + "version": "1.0", + "alt": "1", + "witness_list_unit": "rg1RzwKwnfRHjBojGol3gZaC5w7kR++rOR6O61JRsrQ=", + "last_ball_unit": "PAIAzax7zlet/ig3hfjLUbiBQf5OXOZHM1VdcibUY9Q=", + "last_ball": "2hWmDCAmFEX7Owe6Gm5S3Pa84U1NNA9PiyadeR26K0U=", + "headers_commission": 344, + "payload_commission": 157, + "main_chain_index": 263551, + "timestamp": 1528190136, + "parent_units": [ + "reBlGiN5pjxLdID06Vm08P2cnWVrHhqH8eQNrZ05C2E=", + "xWiFHVJdWt/xYDmtGFBCkSo1t4j7pffHMwqF4vF4KlY=" + ], + "authors": [ + { + "address": "X27CW2UWU5SGE647LK5SBTIPOOIQ7GJT", + "authentifiers": { + "r": "O084aJXKnBfvl1b7C8g1wOvK4eE48avXRp/aRq0Pol5jnMzT2JGcKUsCPSlE8CZQIsYpYKAAfxvrh/AE+2/yBQ==" + } + } + ], + "messages": [ + { + "app": "payment", + "payload_hash": "7yXIZ1D41T4ohWKj4MDdRfItHs7yideULMykeV3TsJ0=", + "payload_location": "inline", + "payload": { + "inputs": [ + { + "unit": "CwnACjidzbWGtCovfTARTHw/6EwTBgKiqPChsC/fhJ4=", + "message_index": 0, + "output_index": 0 + } + ], + "outputs": [ + { + "address": "X27CW2UWU5SGE647LK5SBTIPOOIQ7GJT", + "amount": 1053027 + } + ] + } + } + ] + } + }, + { + "unit": { + "unit": "reBlGiN5pjxLdID06Vm08P2cnWVrHhqH8eQNrZ05C2E=", + "version": "1.0", + "alt": "1", + "witness_list_unit": "rg1RzwKwnfRHjBojGol3gZaC5w7kR++rOR6O61JRsrQ=", + "last_ball_unit": "PAIAzax7zlet/ig3hfjLUbiBQf5OXOZHM1VdcibUY9Q=", + "last_ball": "2hWmDCAmFEX7Owe6Gm5S3Pa84U1NNA9PiyadeR26K0U=", + "headers_commission": 344, + "payload_commission": 157, + "main_chain_index": 263550, + "timestamp": 1528190133, + "parent_units": [ + "PJaR/ECoRThd+Qc0h03SgAqyBSZ34sDpX8/cixC3mUY=", + "unEras0EmHkaOk6GSo9ZGbxbMbHaGMkC6ohTnr5iaxQ=" + ], + "authors": [ + { + "address": "NBEFJ3LKG2SBSBK7D7GCFREOAFMS7QTQ", + "authentifiers": { + "r": "IEJzfw8+L8Z9xIG6IJ7o3SWbFpZfgq5juhKTTH0SoVdQT0cT6QCIFMetjeD6+xFSH3ltsPxHWozufDP81EDNsQ==" + } + } + ], + "messages": [ + { + "app": "payment", + "payload_hash": "N8mnlOtJEl0BwWVEwouzVzYouho1zgf4aemIROQ8qTY=", + "payload_location": "inline", + "payload": { + "inputs": [ + { + "unit": "jwHBa8no6o+ri9eQch2QiImzQ0CIz+YZlCBZ+1lGkV8=", + "message_index": 0, + "output_index": 0 + } + ], + "outputs": [ + { + "address": "NBEFJ3LKG2SBSBK7D7GCFREOAFMS7QTQ", + "amount": 992406 + } + ] + } + } + ] + } + }, + { + "unit": { + "unit": "PJaR/ECoRThd+Qc0h03SgAqyBSZ34sDpX8/cixC3mUY=", + "version": "1.0", + "alt": "1", + "witness_list_unit": "rg1RzwKwnfRHjBojGol3gZaC5w7kR++rOR6O61JRsrQ=", + "last_ball_unit": "PAIAzax7zlet/ig3hfjLUbiBQf5OXOZHM1VdcibUY9Q=", + "last_ball": "2hWmDCAmFEX7Owe6Gm5S3Pa84U1NNA9PiyadeR26K0U=", + "headers_commission": 391, + "payload_commission": 197, + "main_chain_index": 263549, + "timestamp": 1528190131, + "parent_units": [ + "KAc7F75Z+7abh5wSkK2yHRTltY2Fsp5+tc3QHXamkpI=", + "TZBu1Lv1c8UEvQ3KNZxIjdwSkUEvXuyeNX/Dv0+f9S4=" + ], + "authors": [ + { + "address": "VFA6EHDW467JVAEKQZSVVOAHRZMHMFWR", + "authentifiers": { + "r": "3y/Q95DJGFaM5IEICWJBO5gtzQPZDi0WRRwWqcQ9nF4YMb9/3HKzSTZaR3Xauhx4r6ULBTuA833VLVuPzMiuqw==" + }, + "definition": [ + "sig", + { + "pubkey": "Ayee0EG3VmLy7Rd8tq13tTnqvw9HTDXuU+cqLDo2I3VD" + } + ] + } + ], + "messages": [ + { + "app": "payment", + "payload_hash": "u/sURNg8XEM2slY3pXJsJIMqjoWNtiJwAdjU5qx2v20=", + "payload_location": "inline", + "payload": { + "inputs": [ + { + "unit": "WdIIt/OpMnCn0TYN0G/XMja7CKmlvlw/xp8vhjXpTy8=", + "message_index": 0, + "output_index": 1 + } + ], + "outputs": [ + { + "address": "R2CJ353CPFT6ZB372H324A5VYOGPVOKI", + "amount": 100 + }, + { + "address": "RZFOZ6MCZIVDX4JHQVVYUFZ3A6AFZA2O", + "amount": 8036 + } + ] + } + } + ] + } + }, + { + "unit": { + "unit": "KAc7F75Z+7abh5wSkK2yHRTltY2Fsp5+tc3QHXamkpI=", + "version": "1.0", + "alt": "1", + "witness_list_unit": "rg1RzwKwnfRHjBojGol3gZaC5w7kR++rOR6O61JRsrQ=", + "last_ball_unit": "CwnACjidzbWGtCovfTARTHw/6EwTBgKiqPChsC/fhJ4=", + "last_ball": "W+pkP0Li4UFqjqxhKvjZJ0BCX/Vbig6f64AUgzGXq7c=", + "headers_commission": 344, + "payload_commission": 157, + "main_chain_index": 263548, + "timestamp": 1528190130, + "parent_units": [ + "tL4YHK4J9DrV9mgoFqZwUHuIJo8Yy2rAY4d9R4wV0Do=" + ], + "authors": [ + { + "address": "KM5FZPIP264YRRWRQPXF7F7Y6ETDEW5Y", + "authentifiers": { + "r": "IeY/Mqba/SrM9f7Tcdt+i31T1L6xPNbzXMklK+bUJicuA/gHp8/acHW6jy6vAnr5A2wxPdPY6DhgmoJUC6uOpA==" + } + } + ], + "messages": [ + { + "app": "payment", + "payload_hash": "PbPK+X2X2nBvF2B/aZttMtY25tDT+5Hsd79uwE9grBk=", + "payload_location": "inline", + "payload": { + "inputs": [ + { + "unit": "/a6yUprmtD+ohMoNh0m/oZzSNrzX650zms32BwZ5Cpg=", + "message_index": 0, + "output_index": 0 + } + ], + "outputs": [ + { + "address": "KM5FZPIP264YRRWRQPXF7F7Y6ETDEW5Y", + "amount": 1058538 + } + ] + } + } + ] + } + }, + { + "unit": { + "unit": "tL4YHK4J9DrV9mgoFqZwUHuIJo8Yy2rAY4d9R4wV0Do=", + "version": "1.0", + "alt": "1", + "witness_list_unit": "rg1RzwKwnfRHjBojGol3gZaC5w7kR++rOR6O61JRsrQ=", + "last_ball_unit": "CwnACjidzbWGtCovfTARTHw/6EwTBgKiqPChsC/fhJ4=", + "last_ball": "W+pkP0Li4UFqjqxhKvjZJ0BCX/Vbig6f64AUgzGXq7c=", + "headers_commission": 344, + "payload_commission": 157, + "main_chain_index": 263547, + "timestamp": 1528190128, + "parent_units": [ + "IMPr4IwWcZNGFnU2Da7fBbh6jr5+AUY2pdAqvTkhI3s=" + ], + "authors": [ + { + "address": "XIM76DRNUNFWPXPI5AGOCYNMA3IOXL7V", + "authentifiers": { + "r": "2yIUEfjRHiwPnPQWiVFQaClM4XMGufILkyvxAixQ9lEJkvWokPj8+zD1PXcP93gJXviK+8GLkcxZc3Udidxyug==" + } + } + ], + "messages": [ + { + "app": "payment", + "payload_hash": "yH/As1F8kbftRiyLzQ6UMUr5HrhNH+mkys1CVhJ1Nxs=", + "payload_location": "inline", + "payload": { + "inputs": [ + { + "unit": "M+XPxdTHnpUQ68UJxyu8rh3uONlPmjJVplzlf6+1Ork=", + "message_index": 0, + "output_index": 0 + } + ], + "outputs": [ + { + "address": "XIM76DRNUNFWPXPI5AGOCYNMA3IOXL7V", + "amount": 1085592 + } + ] + } + } + ] + } + }, + { + "unit": { + "unit": "IMPr4IwWcZNGFnU2Da7fBbh6jr5+AUY2pdAqvTkhI3s=", + "version": "1.0", + "alt": "1", + "witness_list_unit": "rg1RzwKwnfRHjBojGol3gZaC5w7kR++rOR6O61JRsrQ=", + "last_ball_unit": "i9G3LRJGA6cdC+3RSpb3JJu2tz8xfScgzfRYDUPTLN4=", + "last_ball": "zUkY1MU+sWEeH5xbmnDhWIUVScmyuSncso61XZdq25Y=", + "headers_commission": 344, + "payload_commission": 157, + "main_chain_index": 263546, + "timestamp": 1528190126, + "parent_units": [ + "wfw8+2K48qlUF8p+i9DorT2qLOSKSR2Sh2e5bly7mjI=" + ], + "authors": [ + { + "address": "RIHZR7AHPVKZWTTDWI6UTKC7L73BJJQW", + "authentifiers": { + "r": "Tpb2E06kjDDQXGRDHFq/AIgT87lJJ3LEkvRErgZVNUwmRKFpmOhWohuBIC4Iy9qFXMDn2DI8+uQ3mMQXqq0/gQ==" + } + } + ], + "messages": [ + { + "app": "payment", + "payload_hash": "/KeWQjJ293O/ycA6AejQPrgCDqkJsG7c+KeFKlpIJSs=", + "payload_location": "inline", + "payload": { + "inputs": [ + { + "unit": "qmOU4D7Dnf8uWhgVXZR34fVz+jySAgDV3D83ihvdBEs=", + "message_index": 0, + "output_index": 0 + } + ], + "outputs": [ + { + "address": "RIHZR7AHPVKZWTTDWI6UTKC7L73BJJQW", + "amount": 1056534 + } + ] + } + } + ] + } + }, + { + "unit": { + "unit": "wfw8+2K48qlUF8p+i9DorT2qLOSKSR2Sh2e5bly7mjI=", + "version": "1.0", + "alt": "1", + "witness_list_unit": "rg1RzwKwnfRHjBojGol3gZaC5w7kR++rOR6O61JRsrQ=", + "last_ball_unit": "/a6yUprmtD+ohMoNh0m/oZzSNrzX650zms32BwZ5Cpg=", + "last_ball": "Ki3lprr2sGZ0lMeC4cYRjmrif0Hy6BT+VkNe7IHn+U4=", + "headers_commission": 344, + "payload_commission": 157, + "main_chain_index": 263545, + "timestamp": 1528190125, + "parent_units": [ + "1cBBkIyfxt7mLlAbVbyuj9msGNC8x8pBg2yu6SwF/zw=" + ], + "authors": [ + { + "address": "33RVJX3WBNZXJOSFCU6KK7O7TVEXLXGR", + "authentifiers": { + "r": "YDJMknl7371TKOoe44appuyxUr0Vkzaor6e9QqcGSspuMwy2dItGtGmicOtVq6dacHX5kT5LeJlGFOWIvIOa/A==" + } + } + ], + "messages": [ + { + "app": "payment", + "payload_hash": "CANPnXaAA/aNMWusFlkTQZvabwOnbvnpyDql6in7Vic=", + "payload_location": "inline", + "payload": { + "inputs": [ + { + "unit": "oH1nStynF8ZWE/MtdHm5xmK+49Yjvyz3o5DmiTvWwtw=", + "message_index": 0, + "output_index": 0 + } + ], + "outputs": [ + { + "address": "33RVJX3WBNZXJOSFCU6KK7O7TVEXLXGR", + "amount": 1091103 + } + ] + } + } + ] + } + }, + { + "unit": { + "unit": "1cBBkIyfxt7mLlAbVbyuj9msGNC8x8pBg2yu6SwF/zw=", + "version": "1.0", + "alt": "1", + "witness_list_unit": "rg1RzwKwnfRHjBojGol3gZaC5w7kR++rOR6O61JRsrQ=", + "last_ball_unit": "Hkgi3rxjQfpiZeXtSLbf7ehdRCLXcIo1ZTltEKVREKs=", + "last_ball": "6AJ0HBQ3NMaiqNevOGVrGkVzjLw3brFkl/QD0YfUbTI=", + "headers_commission": 344, + "payload_commission": 157, + "main_chain_index": 263544, + "timestamp": 1528190123, + "parent_units": [ + "zliCCIEBpMStILSjnlaeMeuSfVVNhwwL2C0zi4vjQt8=" + ], + "authors": [ + { + "address": "J3XIKRBU4BV2PX2BP4PSGIXDVND2XRIF", + "authentifiers": { + "r": "IrIaGf9U8HtFnSs0umDWeoNy9j0Lg1TNZ7qERK+uFdlRCKHFuzKpeIwAaD9gviSdcHSI3HscyhwqKGUIi00k2A==" + } + } + ], + "messages": [ + { + "app": "payment", + "payload_hash": "NsZ6bPyvvaSjREz9wUow8PZxErIIJUcgr25aWNGPwb4=", + "payload_location": "inline", + "payload": { + "inputs": [ + { + "unit": "ksDoVmhhcVEwch1TTLvgklPUKs7sL9MWwthhLOpu2MI=", + "message_index": 0, + "output_index": 0 + } + ], + "outputs": [ + { + "address": "J3XIKRBU4BV2PX2BP4PSGIXDVND2XRIF", + "amount": 1009941 + } + ] + } + } + ] + } + }, + { + "unit": { + "unit": "zliCCIEBpMStILSjnlaeMeuSfVVNhwwL2C0zi4vjQt8=", + "version": "1.0", + "alt": "1", + "witness_list_unit": "rg1RzwKwnfRHjBojGol3gZaC5w7kR++rOR6O61JRsrQ=", + "last_ball_unit": "jwHBa8no6o+ri9eQch2QiImzQ0CIz+YZlCBZ+1lGkV8=", + "last_ball": "+P/O6Ie5MvkguNKIdlAkUKeFv87JTwJFuzeslaD4apQ=", + "headers_commission": 344, + "payload_commission": 157, + "main_chain_index": 263543, + "timestamp": 1528190121, + "parent_units": [ + "dvwWU2kYWbIytynkNL5SQzxxCmR+/Ym/iXF3wNxmcwE=" + ], + "authors": [ + { + "address": "FYQXBPQWBPXWMJGCHWJ52AK2QMEOICR5", + "authentifiers": { + "r": "zFvhUxDVqdcliDsIKdi7oVJ6X3cXjEB7D6w4jf/h83QJuNpOJXr2Dba6S7R+8FC+JIxFff1WghGmGi3Xhj7O+Q==" + } + } + ], + "messages": [ + { + "app": "payment", + "payload_hash": "e6qFfHTDMvbn2mdPT7lCvRLxezZ05nA+42VkH8hXSh8=", + "payload_location": "inline", + "payload": { + "inputs": [ + { + "unit": "ubHgZDOSlMrNw/20/CF+VvLW5pvYOcsp2JCjOooTrXg=", + "message_index": 0, + "output_index": 0 + } + ], + "outputs": [ + { + "address": "FYQXBPQWBPXWMJGCHWJ52AK2QMEOICR5", + "amount": 1006935 + } + ] + } + } + ] + } + } + ], + "witness_change_and_definition_joints": [ + { + "unit": { + "unit": "rg1RzwKwnfRHjBojGol3gZaC5w7kR++rOR6O61JRsrQ=", + "version": "1.0", + "alt": "1", + "headers_commission": 2520, + "payload_commission": 5061, + "main_chain_index": 0, + "timestamp": 1516166118, + "witnesses": [ + "2SATGZDFDXNNJRVZ52O4J6VYTTMO2EZR", + "33RVJX3WBNZXJOSFCU6KK7O7TVEXLXGR", + "FYQXBPQWBPXWMJGCHWJ52AK2QMEOICR5", + "J3XIKRBU4BV2PX2BP4PSGIXDVND2XRIF", + "K5JWBZBADITKZAZDTHAPCU5FLYVSM752", + "KM5FZPIP264YRRWRQPXF7F7Y6ETDEW5Y", + "NBEFJ3LKG2SBSBK7D7GCFREOAFMS7QTQ", + "RIHZR7AHPVKZWTTDWI6UTKC7L73BJJQW", + "TIPXQ4CAO7G4C4P2P4PEN2KQK4MY73WD", + "X27CW2UWU5SGE647LK5SBTIPOOIQ7GJT", + "X6DWZUEW4IBFR77I46CAKTJVK4DBPOHE", + "XIM76DRNUNFWPXPI5AGOCYNMA3IOXL7V" + ], + "earned_headers_commission_recipients": [ + { + "address": "WP2JOUJQ24YEECSEXQIZWKYJ5HTBTE4K", + "earned_headers_commission_share": 100 + } + ], + "authors": [ + { + "address": "2SATGZDFDXNNJRVZ52O4J6VYTTMO2EZR", + "authentifiers": { + "r": "geRT7D69AMrXtBQvnAY3gOztGVXTy3CjCCkjOQ4uUM18NtJLnb6VYq7JxJW+lAtVdsW06OVvuTessH/fB7IzIA==" + }, + "definition": [ + "sig", + { + "pubkey": "A4wr97yeg43RvVxX0iuPkE42tZm0gBLzJ7YB5BTbtqj1" + } + ] + }, + { + "address": "33RVJX3WBNZXJOSFCU6KK7O7TVEXLXGR", + "authentifiers": { + "r": "8UkGVvbSDSvWZVsUFyZznJBz4R1Ag/vNyTHdKpTZzeZufHQ6MS5FXIgBpYbyWBa247+VDinItfJE4LT52+vJbw==" + }, + "definition": [ + "sig", + { + "pubkey": "A7RdNYIh9ZSA+0qY7FqOtLPGPMtZRxB4L1fCqXslX6TL" + } + ] + }, + { + "address": "FYQXBPQWBPXWMJGCHWJ52AK2QMEOICR5", + "authentifiers": { + "r": "8O/d03bTEL0Tz/DM+kcPE5x8gEU5Op3ytM+U/RkwPfkmGYVNU/BFoVmA85/mjczh4OFosfo87+8BWP8XR0h4gg==" + }, + "definition": [ + "sig", + { + "pubkey": "AvouAhhkmecy9ZtWaQudeZY8oXz44GW5abFVd/tPPrxh" + } + ] + }, + { + "address": "J3XIKRBU4BV2PX2BP4PSGIXDVND2XRIF", + "authentifiers": { + "r": "OeNhlmHWgc2n0gpobTz0jj6A6Y2YywV8R4etjHSq2g4DiGHEVa3eyhvm35nKs7wAivbwbhs+G7mMf3107v1WYA==" + }, + "definition": [ + "sig", + { + "pubkey": "A5j/mZ1XAYOLmUhisKJjHXQH+qe3QA3+5nqnnNi3KeBc" + } + ] + }, + { + "address": "K5JWBZBADITKZAZDTHAPCU5FLYVSM752", + "authentifiers": { + "r": "Gg2g3R9TjLuyUqmsHJtatKAzvm0LXgldPWHNH7IUzdcUAbvPK7NHRi2CTc/TueDxV8xOLBBJ0LUbw3ngyVr+1A==" + }, + "definition": [ + "sig", + { + "pubkey": "A6cyboJzXCoZokt1DvYs02NloDm5tny8W9U//LBYqk9L" + } + ] + }, + { + "address": "KM5FZPIP264YRRWRQPXF7F7Y6ETDEW5Y", + "authentifiers": { + "r": "9bFv8v+JJPLFmaqt2Vv2QiALhUd291MMB1UZETiippc2+Q2JIcsajF58sRhqJhJgp3xkwDQ87hVb/zq624XFFQ==" + }, + "definition": [ + "sig", + { + "pubkey": "ApkVW7NdqDyz9msVIN/H1rQx4/LfMt6go1k4mwiaj+Jm" + } + ] + }, + { + "address": "NBEFJ3LKG2SBSBK7D7GCFREOAFMS7QTQ", + "authentifiers": { + "r": "b830wTlgRvlQUk4V/3rKewJXwGNsRmGoEPmX53KVO+U3SE6J+4fG2EnIuisO4lBZYt0V63XCFr7F4MOLQgq0ng==" + }, + "definition": [ + "sig", + { + "pubkey": "AtWYab4O/Ja2qPIDsNGfREV6mYlc9k+uv9eJXxV8enbg" + } + ] + }, + { + "address": "RIHZR7AHPVKZWTTDWI6UTKC7L73BJJQW", + "authentifiers": { + "r": "t6eDt3Wfv6G2H7m/xYgRB1fnaOwDIwmHJeAckPgn1V4MfRwSZKmhSTX5Jb7P+Q7/vBfHAZnxlZS80kNW6o5FQA==" + }, + "definition": [ + "sig", + { + "pubkey": "Aw5VMgBi2xNo5RE3iXvxYV9kL6WzN4q2V+MxDfHMbSR0" + } + ] + }, + { + "address": "TIPXQ4CAO7G4C4P2P4PEN2KQK4MY73WD", + "authentifiers": { + "r": "mmtLS+jtFifWOLd5W4zzh7S743EvVVmPq3vm8hR6/8UndFWhwSxZuRWcN9Gcfn2wlRWnkBezmxzXxtpE9F3AVQ==" + }, + "definition": [ + "sig", + { + "pubkey": "AtNMePYXPTVXD744hnoMkLgQgRwPVO9Dvsp5L8T7Wrhs" + } + ] + }, + { + "address": "X27CW2UWU5SGE647LK5SBTIPOOIQ7GJT", + "authentifiers": { + "r": "Tvmk6NKRjsDRCcwGrx5wpx9buEYTAlqDsELKjJVoH097g+XQRnexYZ+tglMelOU0z58RHD3a/ivFajLRKOvAww==" + }, + "definition": [ + "sig", + { + "pubkey": "AvnoRT9yHJ2/fZWPUk2L26V10CW755zQOfl5De/OIeRo" + } + ] + }, + { + "address": "X6DWZUEW4IBFR77I46CAKTJVK4DBPOHE", + "authentifiers": { + "r": "CUvWoqPI7XK9RJf0APlOX5TnyS+mkQHo7eHZhSyhcJlXLSwXZmQOjRj43SHx4kVrBzRtBQcjwePT2kIR5oLE2A==" + }, + "definition": [ + "sig", + { + "pubkey": "An4P+f7cnAH3GNTZqKSws/mkrPsxtLGB3Tm+RxDBgony" + } + ] + }, + { + "address": "XIM76DRNUNFWPXPI5AGOCYNMA3IOXL7V", + "authentifiers": { + "r": "xubkkFkyOa+VFgKzcRtnyKUlmNldxyC4oWglmn3H/Uh34sfvVJ85QM/GWaTfQwRi/kWrihOXRB93NmXNwNScuQ==" + }, + "definition": [ + "sig", + { + "pubkey": "A7wH+w3b+nn1URPcL4LKFgxYsAHMBHgzkTLj1GQkZbrX" + } + ] + } + ], + "messages": [ + { + "app": "text", + "payload_hash": "XFO/L9rdUb2ZMUGpjjl6/oo90/q9HuRgAOGyQmvTFH8=", + "payload_location": "inline", + "payload": "Make payment fun!" + }, + { + "app": "payment", + "payload_hash": "GbIslagzpcVNbglTv3TMAVWs3OdZe4pVekVQ4dcrhYw=", + "payload_location": "inline", + "payload": { + "inputs": [ + { + "type": "issue", + "serial_number": 1, + "amount": 500000000000000, + "address": "2SATGZDFDXNNJRVZ52O4J6VYTTMO2EZR" + } + ], + "outputs": [ + { + "address": "2SATGZDFDXNNJRVZ52O4J6VYTTMO2EZR", + "amount": 10000000 + }, + { + "address": "2SATGZDFDXNNJRVZ52O4J6VYTTMO2EZR", + "amount": 10000000 + }, + { + "address": "2SATGZDFDXNNJRVZ52O4J6VYTTMO2EZR", + "amount": 10000000 + }, + { + "address": "2SATGZDFDXNNJRVZ52O4J6VYTTMO2EZR", + "amount": 10000000 + }, + { + "address": "2SATGZDFDXNNJRVZ52O4J6VYTTMO2EZR", + "amount": 10000000 + }, + { + "address": "2SATGZDFDXNNJRVZ52O4J6VYTTMO2EZR", + "amount": 10000000 + }, + { + "address": "2SATGZDFDXNNJRVZ52O4J6VYTTMO2EZR", + "amount": 10000000 + }, + { + "address": "2SATGZDFDXNNJRVZ52O4J6VYTTMO2EZR", + "amount": 10000000 + }, + { + "address": "2SATGZDFDXNNJRVZ52O4J6VYTTMO2EZR", + "amount": 10000000 + }, + { + "address": "2SATGZDFDXNNJRVZ52O4J6VYTTMO2EZR", + "amount": 10000000 + }, + { + "address": "33RVJX3WBNZXJOSFCU6KK7O7TVEXLXGR", + "amount": 10000000 + }, + { + "address": "33RVJX3WBNZXJOSFCU6KK7O7TVEXLXGR", + "amount": 10000000 + }, + { + "address": "33RVJX3WBNZXJOSFCU6KK7O7TVEXLXGR", + "amount": 10000000 + }, + { + "address": "33RVJX3WBNZXJOSFCU6KK7O7TVEXLXGR", + "amount": 10000000 + }, + { + "address": "33RVJX3WBNZXJOSFCU6KK7O7TVEXLXGR", + "amount": 10000000 + }, + { + "address": "33RVJX3WBNZXJOSFCU6KK7O7TVEXLXGR", + "amount": 10000000 + }, + { + "address": "33RVJX3WBNZXJOSFCU6KK7O7TVEXLXGR", + "amount": 10000000 + }, + { + "address": "33RVJX3WBNZXJOSFCU6KK7O7TVEXLXGR", + "amount": 10000000 + }, + { + "address": "33RVJX3WBNZXJOSFCU6KK7O7TVEXLXGR", + "amount": 10000000 + }, + { + "address": "33RVJX3WBNZXJOSFCU6KK7O7TVEXLXGR", + "amount": 10000000 + }, + { + "address": "FYQXBPQWBPXWMJGCHWJ52AK2QMEOICR5", + "amount": 10000000 + }, + { + "address": "FYQXBPQWBPXWMJGCHWJ52AK2QMEOICR5", + "amount": 10000000 + }, + { + "address": "FYQXBPQWBPXWMJGCHWJ52AK2QMEOICR5", + "amount": 10000000 + }, + { + "address": "FYQXBPQWBPXWMJGCHWJ52AK2QMEOICR5", + "amount": 10000000 + }, + { + "address": "FYQXBPQWBPXWMJGCHWJ52AK2QMEOICR5", + "amount": 10000000 + }, + { + "address": "FYQXBPQWBPXWMJGCHWJ52AK2QMEOICR5", + "amount": 10000000 + }, + { + "address": "FYQXBPQWBPXWMJGCHWJ52AK2QMEOICR5", + "amount": 10000000 + }, + { + "address": "FYQXBPQWBPXWMJGCHWJ52AK2QMEOICR5", + "amount": 10000000 + }, + { + "address": "FYQXBPQWBPXWMJGCHWJ52AK2QMEOICR5", + "amount": 10000000 + }, + { + "address": "FYQXBPQWBPXWMJGCHWJ52AK2QMEOICR5", + "amount": 10000000 + }, + { + "address": "J3XIKRBU4BV2PX2BP4PSGIXDVND2XRIF", + "amount": 10000000 + }, + { + "address": "J3XIKRBU4BV2PX2BP4PSGIXDVND2XRIF", + "amount": 10000000 + }, + { + "address": "J3XIKRBU4BV2PX2BP4PSGIXDVND2XRIF", + "amount": 10000000 + }, + { + "address": "J3XIKRBU4BV2PX2BP4PSGIXDVND2XRIF", + "amount": 10000000 + }, + { + "address": "J3XIKRBU4BV2PX2BP4PSGIXDVND2XRIF", + "amount": 10000000 + }, + { + "address": "J3XIKRBU4BV2PX2BP4PSGIXDVND2XRIF", + "amount": 10000000 + }, + { + "address": "J3XIKRBU4BV2PX2BP4PSGIXDVND2XRIF", + "amount": 10000000 + }, + { + "address": "J3XIKRBU4BV2PX2BP4PSGIXDVND2XRIF", + "amount": 10000000 + }, + { + "address": "J3XIKRBU4BV2PX2BP4PSGIXDVND2XRIF", + "amount": 10000000 + }, + { + "address": "J3XIKRBU4BV2PX2BP4PSGIXDVND2XRIF", + "amount": 10000000 + }, + { + "address": "K5JWBZBADITKZAZDTHAPCU5FLYVSM752", + "amount": 10000000 + }, + { + "address": "K5JWBZBADITKZAZDTHAPCU5FLYVSM752", + "amount": 10000000 + }, + { + "address": "K5JWBZBADITKZAZDTHAPCU5FLYVSM752", + "amount": 10000000 + }, + { + "address": "K5JWBZBADITKZAZDTHAPCU5FLYVSM752", + "amount": 10000000 + }, + { + "address": "K5JWBZBADITKZAZDTHAPCU5FLYVSM752", + "amount": 10000000 + }, + { + "address": "K5JWBZBADITKZAZDTHAPCU5FLYVSM752", + "amount": 10000000 + }, + { + "address": "K5JWBZBADITKZAZDTHAPCU5FLYVSM752", + "amount": 10000000 + }, + { + "address": "K5JWBZBADITKZAZDTHAPCU5FLYVSM752", + "amount": 10000000 + }, + { + "address": "K5JWBZBADITKZAZDTHAPCU5FLYVSM752", + "amount": 10000000 + }, + { + "address": "K5JWBZBADITKZAZDTHAPCU5FLYVSM752", + "amount": 10000000 + }, + { + "address": "KM5FZPIP264YRRWRQPXF7F7Y6ETDEW5Y", + "amount": 10000000 + }, + { + "address": "KM5FZPIP264YRRWRQPXF7F7Y6ETDEW5Y", + "amount": 10000000 + }, + { + "address": "KM5FZPIP264YRRWRQPXF7F7Y6ETDEW5Y", + "amount": 10000000 + }, + { + "address": "KM5FZPIP264YRRWRQPXF7F7Y6ETDEW5Y", + "amount": 10000000 + }, + { + "address": "KM5FZPIP264YRRWRQPXF7F7Y6ETDEW5Y", + "amount": 10000000 + }, + { + "address": "KM5FZPIP264YRRWRQPXF7F7Y6ETDEW5Y", + "amount": 10000000 + }, + { + "address": "KM5FZPIP264YRRWRQPXF7F7Y6ETDEW5Y", + "amount": 10000000 + }, + { + "address": "KM5FZPIP264YRRWRQPXF7F7Y6ETDEW5Y", + "amount": 10000000 + }, + { + "address": "KM5FZPIP264YRRWRQPXF7F7Y6ETDEW5Y", + "amount": 10000000 + }, + { + "address": "KM5FZPIP264YRRWRQPXF7F7Y6ETDEW5Y", + "amount": 10000000 + }, + { + "address": "NBEFJ3LKG2SBSBK7D7GCFREOAFMS7QTQ", + "amount": 10000000 + }, + { + "address": "NBEFJ3LKG2SBSBK7D7GCFREOAFMS7QTQ", + "amount": 10000000 + }, + { + "address": "NBEFJ3LKG2SBSBK7D7GCFREOAFMS7QTQ", + "amount": 10000000 + }, + { + "address": "NBEFJ3LKG2SBSBK7D7GCFREOAFMS7QTQ", + "amount": 10000000 + }, + { + "address": "NBEFJ3LKG2SBSBK7D7GCFREOAFMS7QTQ", + "amount": 10000000 + }, + { + "address": "NBEFJ3LKG2SBSBK7D7GCFREOAFMS7QTQ", + "amount": 10000000 + }, + { + "address": "NBEFJ3LKG2SBSBK7D7GCFREOAFMS7QTQ", + "amount": 10000000 + }, + { + "address": "NBEFJ3LKG2SBSBK7D7GCFREOAFMS7QTQ", + "amount": 10000000 + }, + { + "address": "NBEFJ3LKG2SBSBK7D7GCFREOAFMS7QTQ", + "amount": 10000000 + }, + { + "address": "NBEFJ3LKG2SBSBK7D7GCFREOAFMS7QTQ", + "amount": 10000000 + }, + { + "address": "RIHZR7AHPVKZWTTDWI6UTKC7L73BJJQW", + "amount": 10000000 + }, + { + "address": "RIHZR7AHPVKZWTTDWI6UTKC7L73BJJQW", + "amount": 10000000 + }, + { + "address": "RIHZR7AHPVKZWTTDWI6UTKC7L73BJJQW", + "amount": 10000000 + }, + { + "address": "RIHZR7AHPVKZWTTDWI6UTKC7L73BJJQW", + "amount": 10000000 + }, + { + "address": "RIHZR7AHPVKZWTTDWI6UTKC7L73BJJQW", + "amount": 10000000 + }, + { + "address": "RIHZR7AHPVKZWTTDWI6UTKC7L73BJJQW", + "amount": 10000000 + }, + { + "address": "RIHZR7AHPVKZWTTDWI6UTKC7L73BJJQW", + "amount": 10000000 + }, + { + "address": "RIHZR7AHPVKZWTTDWI6UTKC7L73BJJQW", + "amount": 10000000 + }, + { + "address": "RIHZR7AHPVKZWTTDWI6UTKC7L73BJJQW", + "amount": 10000000 + }, + { + "address": "RIHZR7AHPVKZWTTDWI6UTKC7L73BJJQW", + "amount": 10000000 + }, + { + "address": "TIPXQ4CAO7G4C4P2P4PEN2KQK4MY73WD", + "amount": 10000000 + }, + { + "address": "TIPXQ4CAO7G4C4P2P4PEN2KQK4MY73WD", + "amount": 10000000 + }, + { + "address": "TIPXQ4CAO7G4C4P2P4PEN2KQK4MY73WD", + "amount": 10000000 + }, + { + "address": "TIPXQ4CAO7G4C4P2P4PEN2KQK4MY73WD", + "amount": 10000000 + }, + { + "address": "TIPXQ4CAO7G4C4P2P4PEN2KQK4MY73WD", + "amount": 10000000 + }, + { + "address": "TIPXQ4CAO7G4C4P2P4PEN2KQK4MY73WD", + "amount": 10000000 + }, + { + "address": "TIPXQ4CAO7G4C4P2P4PEN2KQK4MY73WD", + "amount": 10000000 + }, + { + "address": "TIPXQ4CAO7G4C4P2P4PEN2KQK4MY73WD", + "amount": 10000000 + }, + { + "address": "TIPXQ4CAO7G4C4P2P4PEN2KQK4MY73WD", + "amount": 10000000 + }, + { + "address": "TIPXQ4CAO7G4C4P2P4PEN2KQK4MY73WD", + "amount": 10000000 + }, + { + "address": "WP2JOUJQ24YEECSEXQIZWKYJ5HTBTE4K", + "amount": 2419 + }, + { + "address": "WP2JOUJQ24YEECSEXQIZWKYJ5HTBTE4K", + "amount": 499998799990000 + }, + { + "address": "X27CW2UWU5SGE647LK5SBTIPOOIQ7GJT", + "amount": 10000000 + }, + { + "address": "X27CW2UWU5SGE647LK5SBTIPOOIQ7GJT", + "amount": 10000000 + }, + { + "address": "X27CW2UWU5SGE647LK5SBTIPOOIQ7GJT", + "amount": 10000000 + }, + { + "address": "X27CW2UWU5SGE647LK5SBTIPOOIQ7GJT", + "amount": 10000000 + }, + { + "address": "X27CW2UWU5SGE647LK5SBTIPOOIQ7GJT", + "amount": 10000000 + }, + { + "address": "X27CW2UWU5SGE647LK5SBTIPOOIQ7GJT", + "amount": 10000000 + }, + { + "address": "X27CW2UWU5SGE647LK5SBTIPOOIQ7GJT", + "amount": 10000000 + }, + { + "address": "X27CW2UWU5SGE647LK5SBTIPOOIQ7GJT", + "amount": 10000000 + }, + { + "address": "X27CW2UWU5SGE647LK5SBTIPOOIQ7GJT", + "amount": 10000000 + }, + { + "address": "X27CW2UWU5SGE647LK5SBTIPOOIQ7GJT", + "amount": 10000000 + }, + { + "address": "X6DWZUEW4IBFR77I46CAKTJVK4DBPOHE", + "amount": 10000000 + }, + { + "address": "X6DWZUEW4IBFR77I46CAKTJVK4DBPOHE", + "amount": 10000000 + }, + { + "address": "X6DWZUEW4IBFR77I46CAKTJVK4DBPOHE", + "amount": 10000000 + }, + { + "address": "X6DWZUEW4IBFR77I46CAKTJVK4DBPOHE", + "amount": 10000000 + }, + { + "address": "X6DWZUEW4IBFR77I46CAKTJVK4DBPOHE", + "amount": 10000000 + }, + { + "address": "X6DWZUEW4IBFR77I46CAKTJVK4DBPOHE", + "amount": 10000000 + }, + { + "address": "X6DWZUEW4IBFR77I46CAKTJVK4DBPOHE", + "amount": 10000000 + }, + { + "address": "X6DWZUEW4IBFR77I46CAKTJVK4DBPOHE", + "amount": 10000000 + }, + { + "address": "X6DWZUEW4IBFR77I46CAKTJVK4DBPOHE", + "amount": 10000000 + }, + { + "address": "X6DWZUEW4IBFR77I46CAKTJVK4DBPOHE", + "amount": 10000000 + }, + { + "address": "XIM76DRNUNFWPXPI5AGOCYNMA3IOXL7V", + "amount": 10000000 + }, + { + "address": "XIM76DRNUNFWPXPI5AGOCYNMA3IOXL7V", + "amount": 10000000 + }, + { + "address": "XIM76DRNUNFWPXPI5AGOCYNMA3IOXL7V", + "amount": 10000000 + }, + { + "address": "XIM76DRNUNFWPXPI5AGOCYNMA3IOXL7V", + "amount": 10000000 + }, + { + "address": "XIM76DRNUNFWPXPI5AGOCYNMA3IOXL7V", + "amount": 10000000 + }, + { + "address": "XIM76DRNUNFWPXPI5AGOCYNMA3IOXL7V", + "amount": 10000000 + }, + { + "address": "XIM76DRNUNFWPXPI5AGOCYNMA3IOXL7V", + "amount": 10000000 + }, + { + "address": "XIM76DRNUNFWPXPI5AGOCYNMA3IOXL7V", + "amount": 10000000 + }, + { + "address": "XIM76DRNUNFWPXPI5AGOCYNMA3IOXL7V", + "amount": 10000000 + }, + { + "address": "XIM76DRNUNFWPXPI5AGOCYNMA3IOXL7V", + "amount": 10000000 + } + ] + } + } + ] + }, + "ball": "gYmO1Qvlp4bTL//qT6ZGxQrOm9ZPb5oGdlV2o4LwjIk=" + } + ], + "joints": [ + { + "unit": { + "unit": "PJaR/ECoRThd+Qc0h03SgAqyBSZ34sDpX8/cixC3mUY=", + "version": "1.0", + "alt": "1", + "witness_list_unit": "rg1RzwKwnfRHjBojGol3gZaC5w7kR++rOR6O61JRsrQ=", + "last_ball_unit": "PAIAzax7zlet/ig3hfjLUbiBQf5OXOZHM1VdcibUY9Q=", + "last_ball": "2hWmDCAmFEX7Owe6Gm5S3Pa84U1NNA9PiyadeR26K0U=", + "headers_commission": 391, + "payload_commission": 197, + "main_chain_index": 263549, + "timestamp": 1528190131, + "parent_units": [ + "KAc7F75Z+7abh5wSkK2yHRTltY2Fsp5+tc3QHXamkpI=", + "TZBu1Lv1c8UEvQ3KNZxIjdwSkUEvXuyeNX/Dv0+f9S4=" + ], + "authors": [ + { + "address": "VFA6EHDW467JVAEKQZSVVOAHRZMHMFWR", + "authentifiers": { + "r": "3y/Q95DJGFaM5IEICWJBO5gtzQPZDi0WRRwWqcQ9nF4YMb9/3HKzSTZaR3Xauhx4r6ULBTuA833VLVuPzMiuqw==" + }, + "definition": [ + "sig", + { + "pubkey": "Ayee0EG3VmLy7Rd8tq13tTnqvw9HTDXuU+cqLDo2I3VD" + } + ] + } + ], + "messages": [ + { + "app": "payment", + "payload_hash": "u/sURNg8XEM2slY3pXJsJIMqjoWNtiJwAdjU5qx2v20=", + "payload_location": "inline", + "payload": { + "inputs": [ + { + "unit": "WdIIt/OpMnCn0TYN0G/XMja7CKmlvlw/xp8vhjXpTy8=", + "message_index": 0, + "output_index": 1 + } + ], + "outputs": [ + { + "address": "R2CJ353CPFT6ZB372H324A5VYOGPVOKI", + "amount": 100 + }, + { + "address": "RZFOZ6MCZIVDX4JHQVVYUFZ3A6AFZA2O", + "amount": 8036 + } + ] + } + } + ] + } + }, + { + "unit": { + "unit": "TEPdlog5xAyBAxaS9eIdV3eDkG75WCO7rUVfB87X4RY=", + "version": "1.0", + "alt": "1", + "witness_list_unit": "rg1RzwKwnfRHjBojGol3gZaC5w7kR++rOR6O61JRsrQ=", + "last_ball_unit": "QCt6kR7ROvfAEz9Ad6kGWWiqIIh5MfnTFcpsa9Z3qpc=", + "last_ball": "PhokrfBVGJbCcB61FrQ4WA7kXDptJmzrPP/b9/IeG7M=", + "headers_commission": 344, + "payload_commission": 4157, + "main_chain_index": 151949, + "timestamp": 1525341594, + "parent_units": [ + "fDByuQ8PIO4+xjgwuckvLdokaTqifAjY4eWpcY57MVk=" + ], + "authors": [ + { + "address": "WP2JOUJQ24YEECSEXQIZWKYJ5HTBTE4K", + "authentifiers": { + "r": "spjnn3vX6ZJ6Ohw5bqS4ImkiJ6hvUnJ+mM7xUDmCtZ9GQ3NJhUVXWZqTnct1vOobKYcEEp5OZH8XpTTZMQMxMw==" + } + } + ], + "messages": [ + { + "app": "payment", + "payload_hash": "yUDfMbbDOMbAABJ9sITrC8GJVCxvBFa1eSfrD7noQVM=", + "payload_location": "inline", + "payload": { + "inputs": [ + { + "unit": "sJ9xSDlKXd+ZjwFUVVrB1ZLpV2mcMo2cO6/Yccfnf/I=", + "message_index": 0, + "output_index": 0 + } + ], + "outputs": [ + { + "address": "7AI2ZP2F676CDRV3RGG3ERKQJKV5SOMD", + "amount": 5000000 + }, + { + "address": "PDYCXF7RVHNZK5FDEE54QSA3DXZZXSQK", + "amount": 5000000 + }, + { + "address": "PESP3ACM3P66T7SCV6YX6RFRPLTHTYEJ", + "amount": 5000000 + }, + { + "address": "PETCT2RAHQBPFCNAZW27VZW3WSON4CSW", + "amount": 5000000 + }, + { + "address": "PEXOOX4S4X5TFBNH5ZSHDYPXVFV4IB4S", + "amount": 5000000 + }, + { + "address": "PFLHR2GY5TDDU3VW472XNOFVCA5PHSFY", + "amount": 5000000 + }, + { + "address": "PGCIUQU53USWXX7OTMJMN66NWNUAOJHD", + "amount": 5000000 + }, + { + "address": "PJFLIYNZOVXXQ2X3HHHXOUIVCPXKBJ35", + "amount": 5000000 + }, + { + "address": "PPEGTSRP4KMLJ4WL4LVUBJEB7PNY66NH", + "amount": 5000000 + }, + { + "address": "PQIP33MNQWKDPA4RLH7LGYBET2G3VVSM", + "amount": 5000000 + }, + { + "address": "PT4VXJO2EBMQUXWHV7T3TUK56KYFQESH", + "amount": 5000000 + }, + { + "address": "PTEYI7JSI6WLF4ZAFNPRB3PPVHZYAOMS", + "amount": 5000000 + }, + { + "address": "Q4UN546D2NKDOR3YV4JT3VFIUJWGMEKN", + "amount": 5000000 + }, + { + "address": "QBOIUDLU2WQUXJJZ32TLR4JHA6OCFUKG", + "amount": 5000000 + }, + { + "address": "QHGFYCR3CK5OJSXSYDXH46VJ7UR2AOSC", + "amount": 5000000 + }, + { + "address": "QMCLVZOLXBITNDR74SZ4ZACR2B43XFFR", + "amount": 5000000 + }, + { + "address": "QOCEPR36SQW2USG4SZUXA53GOC3KM2XP", + "amount": 5000000 + }, + { + "address": "QQD3B56FYIKLOVAP7QSA27JVMHR6MHYE", + "amount": 5000000 + }, + { + "address": "QRFHNVHNPOQXQ4E5G25O6OR62FGR7CFL", + "amount": 5000000 + }, + { + "address": "QWVH56KBONKG7BIOZ7LJMYLLQJHQAWWU", + "amount": 5000000 + }, + { + "address": "R2CJ353CPFT6ZB372H324A5VYOGPVOKI", + "amount": 5000000 + }, + { + "address": "R346SFL3XBIMPZQPVXFKK2KEKHVBGMAN", + "amount": 5000000 + }, + { + "address": "R4PYZYGUOPBCQCZHA24KDIEHBY56DD2H", + "amount": 5000000 + }, + { + "address": "R6RG2GK62KZ2IS74EZM45ET2PL6X4DSN", + "amount": 5000000 + }, + { + "address": "RFERH45MUH7ZZ4ON2BNAFFESPPPQ7SKP", + "amount": 5000000 + }, + { + "address": "RGFXOGLCXGWF6K5YXILJVIYJSV3UCN4S", + "amount": 5000000 + }, + { + "address": "RHAIMFWBC7JKMVKNEXA3NWV2VACAOE7P", + "amount": 5000000 + }, + { + "address": "RP5WMZ5N4PTZQKYFWDIL5K4MDYCO4NLH", + "amount": 5000000 + }, + { + "address": "RY7W5HCEWOQIKL45BMRXFTBQSGOD3EXW", + "amount": 5000000 + }, + { + "address": "S6DJ6YP5OZFHW3WUZBZS2BP7IHWDRVDT", + "amount": 5000000 + }, + { + "address": "S7F6BUQ2RRX3FLKCVSKVYWAJDAESGKII", + "amount": 5000000 + }, + { + "address": "SHXXSWVRK5FNLUX2NWKKVZQ3ZKA3AQJ3", + "amount": 5000000 + }, + { + "address": "SKQ2R72F6QFNULTDH4EQ3VHGUT3J565J", + "amount": 5000000 + }, + { + "address": "SOUP3OEIW2WZPTO4QUTBI6QZOSH7V63A", + "amount": 5000000 + }, + { + "address": "SQS2WJHV4P6DP4D3RI6GW5OOO7QRUSGW", + "amount": 5000000 + }, + { + "address": "SW47RBMISZEB7D2SZAM6XMT22CBWCF7P", + "amount": 5000000 + }, + { + "address": "SWAGETFVIFOJ5CESWT3IJTRGB6ANBUUR", + "amount": 5000000 + }, + { + "address": "SZR4I4ON6HKBLBDBQZZ2FWOR6IZ6ZUOA", + "amount": 5000000 + }, + { + "address": "T27HYHVGJNZ5JXBWVS4GS4A64EMPTZUR", + "amount": 5000000 + }, + { + "address": "T4XZEA4A3NRVFGLNSMQIGHYEKFSYFNAT", + "amount": 5000000 + }, + { + "address": "TAWYKDRNXXSZ6CML6K4HK7BVWGWSJS6U", + "amount": 5000000 + }, + { + "address": "TB6UTKE5TWNKAPTTL5TXMIE3I3DT6BXN", + "amount": 5000000 + }, + { + "address": "TH7HPTFGKYTOPC3SPFPR3KHUVJHS2OKN", + "amount": 5000000 + }, + { + "address": "TIJVZWUSTPEX5KEQTPEFWU4QMTZBCPOB", + "amount": 5000000 + }, + { + "address": "TL5EN436JIJMOGFVNLXO5K54XTGGRLAT", + "amount": 5000000 + }, + { + "address": "TLV5NEY3JVMO6NZYVRFO7CVF3ZRYYG2S", + "amount": 5000000 + }, + { + "address": "TMU3NABLRHH2R64QWUI3FWWTXA5J5AGR", + "amount": 5000000 + }, + { + "address": "TU4I4TDWY43F3JCBEEJQLD7TIO5DOBNF", + "amount": 5000000 + }, + { + "address": "TZWNIKKGRX65JZELYPRN2DTXSCK3PHDY", + "amount": 5000000 + }, + { + "address": "UGL4JDM4FE7CG5IBEQ6VLHPF4AFMIQ5Z", + "amount": 5000000 + }, + { + "address": "UHT2FIB46A7SE3NS7YF7U3ALDZV45SW7", + "amount": 5000000 + }, + { + "address": "UHYJU5NUYP5YJACO7VWMQLSU45SDFDBZ", + "amount": 5000000 + }, + { + "address": "UKWZCIPVVLD2BCHNRAMVXBIOP35J5OI6", + "amount": 5000000 + }, + { + "address": "UQP6WXNOORG75VCJILUSCBWMY3K6MC56", + "amount": 5000000 + }, + { + "address": "UUBAXZ7H72FIYRGXKGTBJZJ2XCFRTYQS", + "amount": 5000000 + }, + { + "address": "V3PKUHWBXASFO3JZAS3N72OG7CKAUWFP", + "amount": 5000000 + }, + { + "address": "V5F457HNTJR2D4GA7W47D22GZQ6ITLPP", + "amount": 5000000 + }, + { + "address": "VCZJYKFBDEJYWTQSUAXRKND6OJT3IBEU", + "amount": 5000000 + }, + { + "address": "VI7GLTC4DZWQO236VLED7MQFFG6TB5YX", + "amount": 5000000 + }, + { + "address": "VJSWT6KXGU7XYRT2DM77PSBJKD6HV4NM", + "amount": 5000000 + }, + { + "address": "VQZTX6HQ4VJVMYVJKKZGAAKFOURWT3JE", + "amount": 5000000 + }, + { + "address": "VU6K5D5LTWZY7AS5BWGCR5FDKGQMUWS2", + "amount": 5000000 + }, + { + "address": "VV2ENBPLQMGUWHRPB36O7BFVHXAHNFZN", + "amount": 5000000 + }, + { + "address": "VZ7WZOPXTPOSP6GXFMJ2FRPX7IJWAWIS", + "amount": 5000000 + }, + { + "address": "W3G5XJDLLSU4MNJBBI3Q3JF6M4P2WKNW", + "amount": 5000000 + }, + { + "address": "W7KZXASCMGEUJBZTO24A53D2FZX5RMUB", + "amount": 5000000 + }, + { + "address": "WBQMCHF5ZATAZUQMZ4DORPKT24JDSC3A", + "amount": 5000000 + }, + { + "address": "WGNHSBMNQTGQW2P2DHCWTFTI3TVUW7ST", + "amount": 5000000 + }, + { + "address": "WGYL4A76W3NBE4HP4JQH4PFYPP6CYU55", + "amount": 5000000 + }, + { + "address": "WHGJR2KYQQNLORE25PYQNPHJW2UQTJJ2", + "amount": 5000000 + }, + { + "address": "WN6LBGK7XRLUGRN2YMNLQV6W5EDOD7CZ", + "amount": 5000000 + }, + { + "address": "WP2JOUJQ24YEECSEXQIZWKYJ5HTBTE4K", + "amount": 999995499 + }, + { + "address": "WP7N2J7HKMZFXWLWGXV4CRZEEEGHY2AY", + "amount": 5000000 + }, + { + "address": "WQAU765M66TKPA4XINGVNUU7UWFVV2DW", + "amount": 5000000 + }, + { + "address": "WZMDCZWPELRJZSKZ4255OREGOGZDLFAB", + "amount": 5000000 + }, + { + "address": "X2F42NWXKMCQEF2LGWOI7L3HTMIBZZMC", + "amount": 5000000 + }, + { + "address": "X2IARJ7CRK5DKA4OAXBHZQ6TWMFI5TL6", + "amount": 5000000 + }, + { + "address": "X6X4E7J257CXA4SVTMT336Z7AIENX2OO", + "amount": 5000000 + }, + { + "address": "XDCXEBPVWBVY67XFGVRFYFTEUO66IEJ5", + "amount": 5000000 + }, + { + "address": "XGUA547ZA4URWMG3RWEAGABPUB4EZHOL", + "amount": 5000000 + }, + { + "address": "XLXZPJ62DC7D52BHNH6II3UGSHA7WSFJ", + "amount": 5000000 + }, + { + "address": "XQGBFFW7JA6DSPZ4V5DXHTLKSHX4P2TZ", + "amount": 5000000 + }, + { + "address": "XR5DA6FBENCS6KNWJE7KPPY4I6HBAJ7Q", + "amount": 5000000 + }, + { + "address": "XTHTSCI55EMXRMIMUKESZQCS3EGOO2BD", + "amount": 5000000 + }, + { + "address": "XU3SGGXGUVR3CCZX6H6JGIMMCXQHHWKX", + "amount": 5000000 + }, + { + "address": "XXNTZOG5NUQ5PTLNVEDXPXGNNHE6C3Z6", + "amount": 5000000 + }, + { + "address": "Y4I25H7WUGUPA7ZEMS4GZPCSTRT5JMQA", + "amount": 5000000 + }, + { + "address": "Y7IDRTIAWY2KZIXWI7HBBR6RMXXP3NDP", + "amount": 5000000 + }, + { + "address": "YHFDPT7GPJG4MWJSJF222OSSO3K5CX65", + "amount": 5000000 + }, + { + "address": "YIHMRO47SO53GWL26EW2ZLTAJAUFQS5Z", + "amount": 5000000 + }, + { + "address": "YIZPRRJS4Q7KQLSJDF7ASHW77JF6LPR6", + "amount": 5000000 + }, + { + "address": "YRIWLVK6KBAYBI5GHIUTGAPY5SHSHTC7", + "amount": 5000000 + }, + { + "address": "YVOTXHHBC2AFDO7C7BZ44FB7Z5XLB53X", + "amount": 5000000 + }, + { + "address": "Z4SVYEPVMAXB25WRAXR2YJNVTSV7FQCC", + "amount": 5000000 + }, + { + "address": "ZK6JVGEV7B5NMBT243E5F4543YGHIBK4", + "amount": 5000000 + }, + { + "address": "ZQ2LDH4ZK6OITM5G6KUUN73RPGU4U7DM", + "amount": 5000000 + }, + { + "address": "ZR256VWSD5AFXHQFV2JL5RRCSF6UGQF5", + "amount": 5000000 + }, + { + "address": "ZR6TJIIYYAN5U7MH7D4WBPTFTO53UQFR", + "amount": 5000000 + }, + { + "address": "ZR7GDGLYJXBB7A43TBHLKT5EIVQLT5HJ", + "amount": 5000000 + }, + { + "address": "ZSSDLLALCUUKHLOFVTT56SOJVONPHPMW", + "amount": 5000000 + }, + { + "address": "ZVFMXQTPJERGYV3KCJPZ5G4LLFUG7WTH", + "amount": 5000000 + } + ] + } + } + ] + }, + "ball": "kcLuTF8oq+EoMrRvBOT36bOZ170kSCk34CDMadQFUMw=" + } + ], + "proofchain_balls": [ + { + "unit": "PAIAzax7zlet/ig3hfjLUbiBQf5OXOZHM1VdcibUY9Q=", + "ball": "2hWmDCAmFEX7Owe6Gm5S3Pa84U1NNA9PiyadeR26K0U=", + "parent_balls": [ + "1wc9jU9j+6fk9ZHKQ8P8KpxVgmIjaqAcKhOv4O08M3A=" + ] + }, + { + "unit": "y8q1SBZRB2Gqi0rvKW6J20WcK8ZTS/9YDijwdjUjinQ=", + "ball": "1wc9jU9j+6fk9ZHKQ8P8KpxVgmIjaqAcKhOv4O08M3A=", + "parent_balls": [ + "0RxrLcZBmAFYwQ6DzjOEh9B04TddtRMFhXSsYbcejVk=", + "WeEMxYZc2ewIyazgI1drE2BGraEA345IvDfhMK5ieaI=" + ] + }, + { + "unit": "0bJeNAg1F2cfwiMIhZv7uQ0+p4dkmmUa39jxmXSX7Yc=", + "ball": "WeEMxYZc2ewIyazgI1drE2BGraEA345IvDfhMK5ieaI=", + "parent_balls": [ + "W+pkP0Li4UFqjqxhKvjZJ0BCX/Vbig6f64AUgzGXq7c=" + ] + }, + { + "unit": "CwnACjidzbWGtCovfTARTHw/6EwTBgKiqPChsC/fhJ4=", + "ball": "W+pkP0Li4UFqjqxhKvjZJ0BCX/Vbig6f64AUgzGXq7c=", + "parent_balls": [ + "zUkY1MU+sWEeH5xbmnDhWIUVScmyuSncso61XZdq25Y=" + ] + }, + { + "unit": "i9G3LRJGA6cdC+3RSpb3JJu2tz8xfScgzfRYDUPTLN4=", + "ball": "zUkY1MU+sWEeH5xbmnDhWIUVScmyuSncso61XZdq25Y=", + "parent_balls": [ + "Ki3lprr2sGZ0lMeC4cYRjmrif0Hy6BT+VkNe7IHn+U4=" + ], + "skiplist_balls": [ + "KphyY8dVNaQRs9shdfeKuyYkVMk7SIsIAArKrXauah0=" + ] + }, + { + "unit": "edhQrJwXAq7lNDbxDBlweF3gc24sdbXFNXHinoVDfXI=", + "ball": "KphyY8dVNaQRs9shdfeKuyYkVMk7SIsIAArKrXauah0=", + "parent_balls": [ + "gYlpnkII84WEmdaZMCflfkldQFhkAjmgK5j7r4Lw5n0=" + ], + "skiplist_balls": [ + "EMFvlAdxVDXETrtspE4B2wewaFH5MG6SlFitVr14kZw=" + ] + }, + { + "unit": "2w62j2PLedLp6/7HZ/SOXi+92vOVwke1qb4OIuijHfE=", + "ball": "EMFvlAdxVDXETrtspE4B2wewaFH5MG6SlFitVr14kZw=", + "parent_balls": [ + "g5coIkIc1IrbWkHIweX1g+tbtVj2sBPKwocv5o1gffU=", + "zEjF+kkuBF1CHLKnPJCuJcpo+EIDCAj5akZTIWJLSpw=" + ], + "skiplist_balls": [ + "pjsUfxOAB4f7s+aU9XMFk6tf8FSQ5aVP70DE+KoYzEQ=" + ] + }, + { + "unit": "VzjAbCGeaaHFrVDjrX8R1sbqOAiWYSw7uzTGVmWnQYc=", + "ball": "pjsUfxOAB4f7s+aU9XMFk6tf8FSQ5aVP70DE+KoYzEQ=", + "parent_balls": [ + "DQLpcrv4RiXakqYAoR4YuE/uHenPPsFmtGJPtjid9ME=" + ], + "skiplist_balls": [ + "WKT/k0KFpq3I4+rRH6iWZIeZY4Ef5fTcMD38BWTprDk=", + "psxvGpDVn9K8ybKv2+9CenEOzNJrzMx0tsSgiCoMCSE=" + ] + }, + { + "unit": "/sP7KIvHMO3rD5BUpJCJfh52TYf7qSHJG/+zb/qD+GI=", + "ball": "psxvGpDVn9K8ybKv2+9CenEOzNJrzMx0tsSgiCoMCSE=", + "parent_balls": [ + "CcNjGrnApsAP6rASa+qyOz02Fc0AO4g9p72QQIjbLlU=" + ], + "skiplist_balls": [ + "INEVjZKyQrnYuGzT09SYJpnWiWAPXuJuAfeFTG5kUJc=", + "jlF3/jEsOoDJ9Ce/qLGCgCayPqXe8ZRw2fTE0czVm1o=" + ] + }, + { + "unit": "QobU4fMXsw5cxdXSjax0KCISrghjA/bDXNQx+nqhYns=", + "ball": "INEVjZKyQrnYuGzT09SYJpnWiWAPXuJuAfeFTG5kUJc=", + "parent_balls": [ + "o7VbQD7QF38pO9Tj80g/ovFALDF6nZ0NRC8k2y1v4vA=" + ], + "skiplist_balls": [ + "06y8jUgbhK6k/6LvNHRHNKvTx6y0YAIWe4t7Zk70M3s=", + "Nc9+fQXj9lcaUNznsYh92EZJqfk/IeVrbp34d8bOuFQ=" + ] + }, + { + "unit": "LSbpe3/jOO/THsg8bvjitgjMuaon5810Gpn4gylP0kQ=", + "ball": "06y8jUgbhK6k/6LvNHRHNKvTx6y0YAIWe4t7Zk70M3s=", + "parent_balls": [ + "PsdAjPCHNyWAhmoYE2oBc6R8L0AaRwo4jt/qhjOaC6s=" + ], + "skiplist_balls": [ + "6yGE2g0WAIqYcclr92E3dFVebW3aflNwWqHTFdQb/+Q=", + "u+PHUXP4Dh27Z/eAY8CGr2OdRqYhhn/ghxi3UMBmF6o=" + ] + }, + { + "unit": "UheFqcoBdMEjy/5Q/SxpYHjzi/NKM1SiHlzuanF+O5g=", + "ball": "6yGE2g0WAIqYcclr92E3dFVebW3aflNwWqHTFdQb/+Q=", + "parent_balls": [ + "9MgZODv6DXOpHDRErTeBKqfvVkpQyB9y32uG/uKzbnU=" + ], + "skiplist_balls": [ + "QfnPCLiqcXWN86tkCs4xq6j+YWMx17tcmJM0XCNogxA=", + "ZWmIVaXGmH/EwSHx+KWp9riZBKjbGVy8c9+oca/d/Jc=" + ] + }, + { + "unit": "sxTkoFL59o0WT5bo/3vgPutPTAZ0MCpHlpOpDB9uf3E=", + "ball": "QfnPCLiqcXWN86tkCs4xq6j+YWMx17tcmJM0XCNogxA=", + "parent_balls": [ + "B4JqcvKfTDJX9TGtllC1lBPIhFsxbwo7sPcHu4gOXX0=" + ], + "skiplist_balls": [ + "0cB9rz1fEQC/ONr2YZExw3mXKPpz7fxBiakcsQt0eQ8=", + "VUeIleTR34BAYdkOFOkgV3bIdCCW3OyfuN1qFS6zKBI=", + "jtovlmon9ELUbYF3lF+jT4bgslMe6DEzOx+oeX1xNBg=" + ] + }, + { + "unit": "8x7Czinwm/JlBeccx3BhsETkJSfoiMFRop/BHnb6zd4=", + "ball": "jtovlmon9ELUbYF3lF+jT4bgslMe6DEzOx+oeX1xNBg=", + "parent_balls": [ + "Md3DzK2x7FeII1CP4/CbDUP/r+nrK1yTVc2WcAuCs3A=" + ], + "skiplist_balls": [ + "FdwNS2fNzVEWVW0aSfzfUBgdedyQjpc8wVSurQinOPE=", + "puc9xj1EPUdLFpvAOiybkkjruEtc4GOpalml/N+OttM=", + "yN+o8Oi5nvyLQb7msaI5VhBy89Sswk6ajiShUh0EBco=" + ] + }, + { + "unit": "PE4PGnnatHMxJbJjKiqdrpf/31ZpBpINW/31TdT9M5E=", + "ball": "puc9xj1EPUdLFpvAOiybkkjruEtc4GOpalml/N+OttM=", + "parent_balls": [ + "zSoihbuUjbeATxOXBX7qOtNZy1YBKV95hZaCLDD0yOA=" + ], + "skiplist_balls": [ + "k24Ftws3mTcbc0C6VQw7BvGARMZn4StkyftNmPKX9iI=", + "sSQTHmV7ebwGvmxM6Q0w4zbdzbUiKSjIv70qsJyz/WM=", + "zPycrFnb2lOV6YAgSFZX8cFT0JKrheALBR2kwLgLmy0=" + ] + }, + { + "unit": "LhfJ76NH6aSbNPKsgWeq714HkQqj2JzMp84zCvTd5ao=", + "ball": "sSQTHmV7ebwGvmxM6Q0w4zbdzbUiKSjIv70qsJyz/WM=", + "parent_balls": [ + "GEturyYZU3vuUcq5MbZGTTk+8YrlHL9tVNZIuGKF72Q=" + ], + "skiplist_balls": [ + "F8u8t9cXx+UO8YK+fLTwF+4CNrpgjHNGSiYonKd8d8A=", + "HVRWruQNNZ5/A7n2AaFaYghf2kdmMGEKX0e3/RIaQlA=", + "grXaAKyY1GuWTLfWyFazceKlpW5o/sYVpsN55L4m/cg=", + "inXXrXJ7AcKFImgro9nMW452swvyPGrDK5slBHS6GEU=" + ] + }, + { + "unit": "yyQMAasliEJncM9tLeizKmkWjT3IjLfn57uMRrsHaMw=", + "ball": "inXXrXJ7AcKFImgro9nMW452swvyPGrDK5slBHS6GEU=", + "parent_balls": [ + "+OEZPBDgD5AcQL/z59M3RfBSR7eadM/IW2EiipaUnQg=", + "Q3gZ2GrhuoQgIM8JlKv4/QCKRrvoXDtqBWlDzg4jFEs=" + ], + "skiplist_balls": [ + "UPyXf0HPiVb+fnaqJWTyw9nt8YGlBS2p0+QLLV/WWPg=", + "avaQ1OVgTs29qsqWegFHi4QESuba/kueKxQq7i0zius=", + "ee6yObzPN+3MmNAHK4e/xJY2fYg9aeOj7EYR/5EPrqg=", + "m7lBs+QyNiVyCxrzPzw4qOVMhRG70tYKgG8UJWL++Ug=" + ] + }, + { + "unit": "2cSVqL7MswAoKYYM0eWlz+Jhp747MZveIQTURXQKFxs=", + "ball": "ee6yObzPN+3MmNAHK4e/xJY2fYg9aeOj7EYR/5EPrqg=", + "parent_balls": [ + "l8eJd0W4OKALOYEzKbGExI4rqgLr44xlVTNhAEjbXKI=" + ], + "skiplist_balls": [ + "+ONY5aLDJ9i9Ug5meuMnwC4zKY/j+VVjFHmpadMi2J4=", + "ZI12faNKjb7Dtz3UdXbFuvgQo4uullSRwoiu9F1BXV8=", + "mDjPhZfxrx9uKKZn3CVcO5uESzkhDYqo171GaWcEqgk=", + "tXqLiKiPqSD2kVLpzeFsZVgAQAQzJAB6iqL9KGzCEA8=" + ] + }, + { + "unit": "+P2w4AreSVbCwsvdzBB6tdYwMokLXNQvTIpp9xt1hog=", + "ball": "ZI12faNKjb7Dtz3UdXbFuvgQo4uullSRwoiu9F1BXV8=", + "parent_balls": [ + "1hyxtWLjBMLS8A4K3HO71unpww4lUIvJo3i+R+MHqNQ=" + ], + "skiplist_balls": [ + "DamUbYlzKH5Bmmr4Dp/4VpJN6CY2Yptt5oawSCYQT8c=", + "M/AuNd93lG2vrRSvf2g4YK/RYVjs88o7kE/GzOoX5gQ=", + "jygwN/VYoqob9Pdks6S51l/M4ynRdBjzpC0jOX4vq04=", + "psl1tN+612kC4MPvHU3zA5mSzbk+EWa1mP8WaH2/Rac=" + ] + }, + { + "unit": "upaM45jwjX8LT16FqFJ8cjG9JOv2YXYLNRZt76vci3A=", + "ball": "jygwN/VYoqob9Pdks6S51l/M4ynRdBjzpC0jOX4vq04=", + "parent_balls": [ + "wLaBTSiobBl86TXQhXqVZxzjNbCgNhR99+Xjpmvm2G0=" + ], + "skiplist_balls": [ + "9Ju4CY7G+odKZZflAN7j9J2zhCMdy5f4iO2C6KmuRhw=", + "DrDDo1HBM9zicT740Qmy1YdSdpB/rU07SModf+hgfYE=", + "FdGv2apA0Wf6SuJOFLFXAbpAwBVa9YfYdmATpn/ftpE=", + "ukRWyYRs8Y2DPvcXBy81ebpP0/9vy8b/mu6X5G+98Ho=" + ] + }, + { + "unit": "NoNmmUbTRvnRWxEHxl41MKnWq2HMICvH7Z1crK8xlis=", + "ball": "FdGv2apA0Wf6SuJOFLFXAbpAwBVa9YfYdmATpn/ftpE=", + "parent_balls": [ + "f6gQTwuXPkGAneYgOAXfzTZmQmjRJBChfdu95i2J/Sw=" + ], + "skiplist_balls": [ + "5RHoidhEm37C0LUiR8l2wEx/8erjuunxnBS6mzbNyio=", + "F+RR80H1OfKFRzh4CCQ2EC7hbubg+K+7FWUTPAQkyEw=", + "jozjfblgOcX60VFbykvIvFF2DXxj0lPirU+3Ap0LVYc=", + "ytEHTtuO2oHvy7+E+m423j4+xYnUjxdVLnJ1uUhsr+8=" + ] + }, + { + "unit": "g63W/gF+2YLlSaySbHYy2Wxp3pvjn8rsAyRgt9/pNus=", + "ball": "ytEHTtuO2oHvy7+E+m423j4+xYnUjxdVLnJ1uUhsr+8=", + "parent_balls": [ + "r0iWlXZzF3lCa0gpGWKSj5DsqCeLjawjeY2VnVOGQbs=" + ], + "skiplist_balls": [ + "/gKDRm9pzxkN+ZQ02xL1d/a2Zk+OLxZNRwJ05euzhNs=", + "3WX5irM5KAHVg3RwgQvyg4H6qC9bNPBXwS93iSpjR/Y=", + "cTiIweV5np2jIBVvP1y5vQ9cJ5az5eT/uMihTQw6i54=", + "oJx3GOjmBuKX+DKTMpJ+hmbvq1WLWb/Ok8iQdb9+f7s=", + "zVPvLjpWEmsKdxzSdH3eJjVfS0hRnXZgSJZIGzIBKwM=" + ] + }, + { + "unit": "i13zSvF3cuZBfgXJYyU+VrfVl6AlDorVNnltpqVM8FE=", + "ball": "cTiIweV5np2jIBVvP1y5vQ9cJ5az5eT/uMihTQw6i54=", + "parent_balls": [ + "lPlk97mzZbbfwe75czPfhR48AgRgYCgmBoHWAEHmONI=" + ], + "skiplist_balls": [ + "dNcpQUwlFcooNrUZIKAEuMnfIqtUisNZwFst0fl+SZ8=", + "r5n+OLk+lRQCWZjXWsPbZN5/P6rTlOT2SzDjyd0DYIA=", + "u6HoutWWTuvjCEPCSKXh1I4kJ76FAqdwjxP8RyahkJU=", + "z6mWM/R/15xoibYctnGz/mKwqwQo5YQ3jvmyjcslM0I=" + ] + }, + { + "unit": "2YtNWVwRKjHQh8icBWVK0c2HDPVy1cVIjvDiDTMSr4U=", + "ball": "z6mWM/R/15xoibYctnGz/mKwqwQo5YQ3jvmyjcslM0I=", + "parent_balls": [ + "C8tOxIQJ8knbZulGVU/m00s8q9kEkCik2TvLz9G+KlM=" + ], + "skiplist_balls": [ + "2C89w4HloxCJ4/OSOyxYm6BYjBqPLwaOWHRZekirz8A=", + "86BZyeUTPFQGwWUJT4YDvbiPWtwBZiJ6IOQsOkHoDVI=", + "L6+J73oVITXBeX2fEN3728d7SJscgLOGF7o62lpdXII=", + "OF3uFaSWSdHhjdrQ2tjKxc7LR5sbm8klTDcPfn1nyys=" + ] + }, + { + "unit": "cUjtYkFjE0Dn5MCP3jCAGiy33aGp8Nq1ZbXcu3B1EPY=", + "ball": "L6+J73oVITXBeX2fEN3728d7SJscgLOGF7o62lpdXII=", + "parent_balls": [ + "TpHMpH7XiJ1GZOI34kn6uQiH8Iz7Zq5oPOJoPnuHfSg=" + ], + "skiplist_balls": [ + "2Ig+/EIVls3ue+QW45CpbyaRZ63F6PWb9SpjXb5G6tw=", + "A0eZ49HsY3YpUx46DOfCmDCz49Z9cjCSRYe8bcyi37M=", + "eB4QSWko13rJ1qBKMsbA+4Fult0nte6W9c3xYJ+EmNo=", + "g5u0YYEU7i1qwCO/7qMmVAiTNoJqYD+YARP8R156qpI=" + ] + }, + { + "unit": "jom28J0xIq1MFHmhOqpsmaxp4DAAu07yW/L+PN25xIs=", + "ball": "2Ig+/EIVls3ue+QW45CpbyaRZ63F6PWb9SpjXb5G6tw=", + "parent_balls": [ + "fd5xJ7shbifre5u+bNB64GmAylsbG+i0fWLyOC9573M=" + ], + "skiplist_balls": [ + "BDVB5aPBc2qH6/aaSGrmED9QmEq4E/0ji7UonPQVUr8=", + "EOxBgCKWHZ0EtBJc4cU+i7zMX62erUnhYqSuThEmaCQ=", + "tHGyJQgxPZkEZCXYZNH7msgzUV/eNwNq4NO/uR/5dtw=", + "xewG6QcBkOaAebA+SMciNoa2ZSZyhKbKnEjwLdLN5gQ=" + ] + }, + { + "unit": "6wmWPe17y7OxdosjIPghsS6F0lwdL1f3WKysE8IXsQU=", + "ball": "tHGyJQgxPZkEZCXYZNH7msgzUV/eNwNq4NO/uR/5dtw=", + "parent_balls": [ + "Olph7FbU2leNd9YvgzApKxlkbWVhdDAmA1+F6YzVsbQ=" + ], + "skiplist_balls": [ + "9cYLaPHOiRCQWPpPF+CRClaNgn+BlTsvmTy/TN1tAEQ=", + "R0Koc2T3P4qnSehg7HnP5LYYaTu5fBglv0sEAmBEyao=", + "cHNTw/gWMV2ksMUnfRmI9g6N+J1lZIeMufDqXRP1w4c=" + ] + }, + { + "unit": "MblTeUhJUVusbBawhe/2RalQ9XMQwWePJsZwPoL7T0o=", + "ball": "R0Koc2T3P4qnSehg7HnP5LYYaTu5fBglv0sEAmBEyao=", + "parent_balls": [ + "64OgtC8qQGyhM0MaIYWTj0pu+r5zHw/EbEc4HqaEUww=" + ], + "skiplist_balls": [ + "DLDjCm4Y78DtPqIJOX9SFbT1zo1yfqQlN7pZtniKkLs=", + "eTcp5XqmO2tTbX8CkIqvp7Va1+g9YGRTBSHu8dr9Pfo=", + "n7+9T1UYWkArhiVqXVIEGDg7KAX/s9O9t7HR10INbrg=" + ] + }, + { + "unit": "svQc0m744sYwhK+OAoaBEDmDjA+rGHYqeDMFdLOU0yw=", + "ball": "eTcp5XqmO2tTbX8CkIqvp7Va1+g9YGRTBSHu8dr9Pfo=", + "parent_balls": [ + "bNkJoJsmw6EV2SIPkOsT7q7p6n5v4j0c1MxkgmRmz+8=" + ], + "skiplist_balls": [ + "K2W/a4Rfbd2l3Hr/WLS03FRWdx5fdE5xJg1t6RCq6oU=", + "b5G9Wyvojog5YrGhpjtdmu3aCUiQihcDW+fKe16t2Ig=", + "yPecnoDQZl8AhQrMgJTXcwxQ5X4fTheCIv+C6a5oFxM=" + ] + }, + { + "unit": "pIr8JufA40MVrGPPnSAwtdVdnz+k1jErOkp9ogxe5jY=", + "ball": "b5G9Wyvojog5YrGhpjtdmu3aCUiQihcDW+fKe16t2Ig=", + "parent_balls": [ + "7RMdMG//u89uPsegPZ0lkUszHK0lAaBV2QBLYhb+FVs=" + ], + "skiplist_balls": [ + "Ut9D+fl4I5lZBqWS34Q8rPGyT6Eg8fVZOrGTEXIhzBg=", + "ZAv8bdmn8ymzxROaFXbq0Bu0U1nI/+1WdNIgL86UzQw=", + "kTKJvnuGBLcW6IsluxplcpTF8gAdzEW32mH8BXhCjlA=" + ] + }, + { + "unit": "8GthmU97uwOdp4OQagJh/2EHaikHg5hoZdJG5/kL42U=", + "ball": "kTKJvnuGBLcW6IsluxplcpTF8gAdzEW32mH8BXhCjlA=", + "parent_balls": [ + "mOaXgY2FuCON82LyMf2s5S/bXtmA/NsDGldluySVwl4=" + ], + "skiplist_balls": [ + "1tFhWQDV9Qq6QBJmM1+4dLTkxF5ekuEdf45dmF5WozE=", + "A3skX+NUeMlVEY3J1LgKR+epqbVktdi0FXDdBSrQPg8=", + "hbH/qWAigwq+W/561XRvFer+Tox4+j0bKVR5fsSC4Tg=" + ] + }, + { + "unit": "dE0QUy/dWZIvrgdhkZsI20svVBPMdrt0t7q9LM+NOCo=", + "ball": "hbH/qWAigwq+W/561XRvFer+Tox4+j0bKVR5fsSC4Tg=", + "parent_balls": [ + "X0A4aTcLjHbL1pg4fKs14BsJFQu6rBwTZAGCgESj5DU=" + ], + "skiplist_balls": [ + "RLPGK01OVO5gWyRNdnD6sRnvLRCGICsenaLuZr98giA=", + "SV285zmXxzOO6FIzw/64lKlGDCToodrq/CvKtLThZB0=", + "TCuT9qNcdqcAqjbBAHugvt1tBCyUCNbN0ZfgjGUdhao=" + ] + }, + { + "unit": "mBz1pAJvrbm8XnBcjp/cOP1aulpHgAq6PnaPySXJ+2Y=", + "ball": "RLPGK01OVO5gWyRNdnD6sRnvLRCGICsenaLuZr98giA=", + "parent_balls": [ + "iZuCzdJ7451da8hCTLcpHTHPsgw01+IjKLRvZn/LtV0=" + ], + "skiplist_balls": [ + "L0K8HyDpEyQ2I9KUOl+BoUQ7PkYVyeWfuJ5hFn4DKs4=", + "owsUp/3uoFgjt0DFVKQPi52nkweYLpZJ5KuEMVqv9Uw=", + "xkemm+tfIQ3ICdAf/WXwrkY4UArpKNqb72UUZ8lcnF4=" + ] + }, + { + "unit": "RoSXBSoosio8u8rMLaMcBTqaaBzNO3DhCT5OofoHkJU=", + "ball": "xkemm+tfIQ3ICdAf/WXwrkY4UArpKNqb72UUZ8lcnF4=", + "parent_balls": [ + "ge6RJR83a0/zGRucRfa/4gL0MWMKQgiH+kk6hwnZIzU=" + ], + "skiplist_balls": [ + "V6mRlSBV1ILwFURyekytfHQCCVxDv9C/H6lkCwmi+Vo=", + "boHNw1V8hqZRVwUoKNg11e0UboG8SJ0PLphNqVvD2AA=", + "xuSryWhik8N/m7wrm9CLF5MMhvo4VXjphDgXPzVG/TI=" + ] + }, + { + "unit": "4RQxfV/oi4F742igVKLgrzgFvn/KiyIT4ts6BB2sSIA=", + "ball": "boHNw1V8hqZRVwUoKNg11e0UboG8SJ0PLphNqVvD2AA=", + "parent_balls": [ + "1w767TURAhXiqy/NcI/6Q6QvLrXBz3EAai+jAekJxUA=" + ], + "skiplist_balls": [ + "FWTqw9Vp1WZMu8pu/vLV2/rixdo0sV6fWyDuV7j5qOI=" + ] + }, + { + "unit": "iixaKx5qOJ+LeYIRUXI8G63IZlLFZcje+SPJwW7DbNA=", + "ball": "FWTqw9Vp1WZMu8pu/vLV2/rixdo0sV6fWyDuV7j5qOI=", + "parent_balls": [ + "asb8gaV6NTfgUrZ93D2XF5OZZyomB4VwEU1PHCI5WUs=" + ], + "skiplist_balls": [ + "sWbe7QhksACZMWrTjdqQX+zcu2EL6gLFOPgx5d5LvRo=" + ] + }, + { + "unit": "bXW5ISLBy+m8ZHQh4mYYH39rl/6dWly9aS5rXzBtHt0=", + "ball": "sWbe7QhksACZMWrTjdqQX+zcu2EL6gLFOPgx5d5LvRo=", + "parent_balls": [ + "iRGeSTt13/9oHgkssNSv9MWoSFLbHeLpcUX3EdL2hNk=" + ], + "skiplist_balls": [ + "p84gFZpGLhTjJ0muLLEOAU6g8LIs1fXU4wIPHlH6Dog=" + ] + }, + { + "unit": "PPKi8g+6Y0fvcrzUq8t9QkSYcDEYKp+eJK2DWoPkNuw=", + "ball": "p84gFZpGLhTjJ0muLLEOAU6g8LIs1fXU4wIPHlH6Dog=", + "parent_balls": [ + "4zRDeSlVee1ZJdiV78zEzYCOY1Cqvg3KtvO+m7xyR8o=" + ], + "skiplist_balls": [ + "6A0gxMlHlqxmos0ZXJVsF2DRT05T8HAjQr6xGLrzkQk=" + ] + }, + { + "unit": "F7gjmkenhIBY8VJi6cSDPwOaZV2EUYLsJxH0rQgiO4c=", + "ball": "6A0gxMlHlqxmos0ZXJVsF2DRT05T8HAjQr6xGLrzkQk=", + "parent_balls": [ + "kcLuTF8oq+EoMrRvBOT36bOZ170kSCk34CDMadQFUMw=" + ], + "skiplist_balls": [ + "JWYHKR4eWBJ2BPpis3Rys0GAalY0x9Xe7woYoRTSXzQ=" + ] + }, + { + "unit": "TEPdlog5xAyBAxaS9eIdV3eDkG75WCO7rUVfB87X4RY=", + "ball": "kcLuTF8oq+EoMrRvBOT36bOZ170kSCk34CDMadQFUMw=", + "parent_balls": [ + "qPH/BtktAO/Q0N5vMRjGTX1yEf0V1JQaAMTcWghBT5M=" + ] + } + ] +} \ No newline at end of file diff --git a/lib/test/network_manager_test.js b/lib/test/network_manager_test.js new file mode 100644 index 0000000..d0e9eb8 --- /dev/null +++ b/lib/test/network_manager_test.js @@ -0,0 +1,62 @@ +'use strict' + +var NetworkManager = require('../network_manager'); +const url = 'https://victor.trustnote.org/tn'; +const ObjLogin = require('../tools/objLogin'); +var DataBase = require('../db/database'); + + +const networkManager = new NetworkManager() +const db = new DataBase(); + +async function test (){ + await networkManager.connectToHub(url); + let version = { + "protocol_version": "1.0", + "alt": "1", + "library": "trustnote-common", + "library_version": "0.1.1111111111111", + "program": "TTT", + "program_version": "1.1.1" + } + await networkManager.sendVersion(version); + let ret1 = await networkManager.sendRequest('get_witnesses', null, false); + await db.insertWitnesses(ret1.response); + + let arrWitnesses = await db.readMyWitnesses(); + console.log('arrWitnesses: ', arrWitnesses); + + + let challenge = networkManager.fetchChallenge(); + console.log('challenge: ', challenge ); + + var objLogin = ObjLogin.getObjLogin(challenge); + await networkManager.sendJustsaying('hub/login', objLogin); + + let ret3 = await networkManager.sendRequest('hub/temp_pubkey', ObjLogin.createTempPubkeyPackage(), false); + console.log('hub/temp_pubkey: ',ret3.response) + + + let addresses = ['R2CJ353CPFT6ZB372H324A5VYOGPVOKI'] + // 'QWVH56KBONKG7BIOZ7LJMYLLQJHQAWWU']; + + let known_stable_units = ['33cs5A6tioSR94Pw6EAi0eRwdqA9XFnC5ZdCfyngAI0='] + var objRequest = { + witnesses: arrWitnesses, + addresses: addresses, + last_stable_mci: 0, + known_stable_units: known_stable_units + } + let ret4 = await networkManager.sendRequest('light/get_history', objRequest, false); + console.log('light/get_history-------',JSON.stringify(ret4.response, null, 3)); + + await db.saveLightHistory(ret4.response); + + + var obj = {}; + obj.witnesses = arrWitnesses; + let ret5 = await networkManager.sendRequest('light/get_parents_and_last_ball_and_witness_list_unit', obj, false); + console.log('light/get_parents_and_last_ball_and_witness_list_unit-------',JSON.stringify(ret5.response, null, 3)); +} + +test (); diff --git a/lib/test/wallet_test.js b/lib/test/wallet_test.js new file mode 100644 index 0000000..7841db8 --- /dev/null +++ b/lib/test/wallet_test.js @@ -0,0 +1,119 @@ +'use strict' + +var Bitcore = require("bitcore-lib"); +var crypto = require("crypto"); +var ecdsa = require('secp256k1'); +var ecdsaSig = require('../crypto/signature.js'); +var Mnemonic = require("bitcore-mnemonic"); +var objectHash = require("../crypto/object_hash"); +var objectLength = require("../crypto/object_length"); + + + +var window = {}; +var wallet = require('../wallet'); +window.Client = new wallet(); + +var obj1 = { + "version": "1.0", + "alt": "1", + "messages": [ + { + "app": "payment", + "payload_location": "inline", + "payload_hash": "P46nXMzKXC2LDzXyMUYBDCXetrjchlcP3l0MLo4WORo=", + "payload": { + "outputs": [ + { + "address": "YDKTOQ7VCBQ336VGH3S5RLIWRRAUTB5O", + "amount": 1000 + }, + { + "address": "ZXBUYS27ZS7QPISUGH3OBWFEPPYFLNHN", + "amount": 232957 + } + ], + "inputs": [ + { + "unit": "0qP+mIYs767MWotyHLtNmOSGSH6ISWGESC1+N1buaPs=", + "message_index": 0, + "output_index": 0 + } + ] + } + } + ], + "authors": [ + { + "address": "7LA5PM2WUGONMSFLYRXFE3DY7X6ORKJW", + "authentifiers": { + "r": "cMKJdsCjSCg1iP9VLq6QFDlv3S6tRhKaXcmJhGTMWtxlKDg6tYn7Q7LqUamjRz7JMbSmAZCP/K1LM1vA1p+/wQ==" + }, + "definition": [ + "sig", + { + "pubkey": "A5YLk2BEKnOXjXINYIBWPkdYx67lHmsTYso4R+2OygDV" + } + ] + } + ], + "parent_units": [ + "EYmSD9jUPMLidEXFIIuI6m/Wj9te3bHE8DouYheGzqQ=" + ], + "last_ball": "bYY1fmND7WSE6zTSw0l0rs/queoaF83/y+OY4tuMhBs=", + "last_ball_unit": "sf6F/Rjb7K/5j/GMa3XtLu6JrPCDraeOGBEX/9+FQG8=", + "witness_list_unit": "rg1RzwKwnfRHjBojGol3gZaC5w7kR++rOR6O61JRsrQ=", + "headers_commission": 391, + "payload_commission": 197 +} + +var info = {}; + +info.unitHash = window.Client.getUnitHash(obj1); +info.totalPayloadSize = window.Client.getTotalPayloadSize(obj1); + +// 助记词 ==> 私钥 ==> 公钥 ==> 钱包地址 ==> 钱包ID +info.mnemonic = window.Client.mnemonic(); +info.xPrivKey = window.Client.xPrivKey(info.mnemonic); +info.xPubKey = window.Client.xPubKey(info.xPrivKey); + +info.walletPubKey = window.Client.walletPubKey(info.xPrivKey, 0); +info.walletAddress = window.Client.walletAddress(info.walletPubKey, 0, 0); +info.walletID = window.Client.walletID(info.walletAddress); + +info.m1PrivKey = window.Client.m1PrivKey(info.xPrivKey); +info.genPrivKey = window.Client.genPrivKey(); +info.genPubKey = window.Client.genPubKey(info.genPrivKey); + +info.getUnitHashToSign = window.Client.getUnitHashToSign(obj1); + + +info.ecdsaPubkey = window.Client.ecdsaPubkey(info.xPrivKey, "m/44/0'/0'/1/13"); +//签名 +info.sign = window.Client.sign(info.getUnitHashToSign, info.xPrivKey, "m/44/0'/0'/1/13"); +//验证签名 +info.ret = window.Client.verify(info.getUnitHashToSign, info.sign, info.ecdsaPubkey); + +console.log('--------------------------->') +console.log(JSON.stringify(info,null,3)); + + +function getObjLogin (challenge) { + var mnemonic = new Mnemonic(); // generates new mnemonic + var passphrase = ''; + var xPrivKey = mnemonic.toHDPrivateKey(); + var devicePrivKey = xPrivKey.derive("m/1'").privateKey.bn.toBuffer({size:32}); + + + var objMyPermanentDeviceKey = { + priv: devicePrivKey, + pub_b64: ecdsa.publicKeyCreate(devicePrivKey, true).toString('base64') + }; + + var objLogin = {challenge: challenge, pubkey: objMyPermanentDeviceKey.pub_b64}; + var buf_to_sign = new Buffer(objectHash.getDeviceMessageHashToSign(objLogin), "base64"); + objLogin.signature = ecdsaSig.sign(buf_to_sign, objMyPermanentDeviceKey.priv); + + return objLogin +} + diff --git a/lib/tools/event_bus.js b/lib/tools/event_bus.js new file mode 100644 index 0000000..ab41629 --- /dev/null +++ b/lib/tools/event_bus.js @@ -0,0 +1,9 @@ +/*jslint node: true */ +"use strict"; + +// require('./enforce_singleton.js'); +var EventEmitter = require('events').EventEmitter; +var eventEmitter = new EventEmitter(); +eventEmitter.setMaxListeners(25); + +module.exports = eventEmitter; diff --git a/lib/tools/objLogin.js b/lib/tools/objLogin.js new file mode 100644 index 0000000..571578f --- /dev/null +++ b/lib/tools/objLogin.js @@ -0,0 +1,59 @@ +'use strict' + +var Bitcore = require("bitcore-lib"); +var crypto = require("crypto"); +var ecdsa = require('secp256k1'); +var ecdsaSig = require('../crypto/signature.js'); +var Mnemonic = require("bitcore-mnemonic"); +var objectHash = require("../crypto/object_hash"); +var objectLength = require("../crypto/object_length"); + +var objMyPermanentDeviceKey; + +function getObjLogin (challenge) { + var mnemonic = new Mnemonic(); // generates new mnemonic + var passphrase = ''; + var xPrivKey = mnemonic.toHDPrivateKey(); + var devicePrivKey = xPrivKey.derive("m/1'").privateKey.bn.toBuffer({size:32}); + + + objMyPermanentDeviceKey = { + priv: devicePrivKey, + pub_b64: ecdsa.publicKeyCreate(devicePrivKey, true).toString('base64') + }; + + var objLogin = {challenge: challenge, pubkey: objMyPermanentDeviceKey.pub_b64}; + var buf_to_sign = new Buffer(objectHash.getDeviceMessageHashToSign(objLogin), "base64"); + objLogin.signature = ecdsaSig.sign(buf_to_sign, objMyPermanentDeviceKey.priv); + + return objLogin +} + +var wallet = require('../wallet'); +var Client = new wallet(); +var info = {}; +info.genPrivKey = Client.genPrivKey(); +info.genPubKey = Client.genPubKey(info.genPrivKey); + +var objMyTempDeviceKey = { + pub_b64: info.genPubKey + }; + +// objMyTempDeviceKey.pub_b64 +function createTempPubkeyPackage(){ + var objTempPubkey = { + temp_pubkey: objMyTempDeviceKey.pub_b64, + pubkey: objMyPermanentDeviceKey.pub_b64 + }; + var buf_to_sign = new Buffer(objectHash.getDeviceMessageHashToSign(objTempPubkey), 'base64'); + objTempPubkey.signature = ecdsaSig.sign(buf_to_sign, objMyPermanentDeviceKey.priv); + return objTempPubkey; +} + + +exports.getObjLogin = getObjLogin; +exports.createTempPubkeyPackage = createTempPubkeyPackage; + + + + diff --git a/lib/wallet.js b/lib/wallet.js new file mode 100644 index 0000000..386ccec --- /dev/null +++ b/lib/wallet.js @@ -0,0 +1,231 @@ +"use strict" + +var Bitcore = require("bitcore-lib"); +var crypto = require("crypto"); +var ecdsa = require('secp256k1'); +var Mnemonic = require("bitcore-mnemonic"); +var objectHash = require("./crypto/object_hash"); +var objectLength = require("./crypto/object_length"); + +class Wallet { + constructor () { + this.networkManager = null + } + + //生成助记词 + mnemonic () { + try { + var mnemonic = new Mnemonic(); + return mnemonic.phrase; + } catch (error) { + return 0; + } + } + + //根据助记词生成根私钥 + xPrivKey (mnemonic) { + try { + var xPrivKey = new Mnemonic(mnemonic).toHDPrivateKey(); + return xPrivKey.toString(); + } catch (error) { + return 0; + } + } + + //根据私钥生成私钥对象结构 + objPrivKey (xPrivKey) { + try { + var objPrivKey = new Bitcore.HDPrivateKey.fromString(xPrivKey); + return objPrivKey; + } catch (error) { + return 0; + } + } + + //根公钥 + xPubKey (xPrivKey) { + try { + var xPubKey = Bitcore.HDPublicKey(Bitcore.HDPrivateKey.fromString(xPrivKey)); + return xPubKey.toString(); + } catch (error) { + return 0; + } + } + + //钱包公钥 + walletPubKey (xPrivKey, num) { + try { + xPrivKey = Bitcore.HDPrivateKey.fromString(xPrivKey); + var wallet_xPubKey = Bitcore.HDPublicKey(xPrivKey.derive("m/44'/0'/" + num + "'")); + return wallet_xPubKey.toString(); + } catch (error) { + return 0; + } + } + + //钱包ID + walletID (walletPubKey) { + try { + var wallet_Id = crypto.createHash("sha256").update(walletPubKey, "utf8").digest("base64"); + return wallet_Id; + } catch (error) { + return 0; + } + } + + //生成设备地址 + deviceAddress (xPrivKey) { + try { + xPrivKey = Bitcore.HDPrivateKey.fromString(xPrivKey); + var priv_key = xPrivKey.derive("m/1'").privateKey.bn.toBuffer({ + size: 32 + }); + var pub_b64 = ecdsa.publicKeyCreate(priv_key, true).toString('base64'); + var device_address = objectHash.getDeviceAddress(pub_b64); + return device_address; + } catch (error) { + return 0; + } + } + + //生成钱包的地址 + walletAddress (wallet_xPubKey, change, num) { + try { + wallet_xPubKey = Bitcore.HDPublicKey.fromString(wallet_xPubKey); + var wallet_xPubKey_base64 = wallet_xPubKey.derive("m/" + change + "/" + num).publicKey.toBuffer().toString("base64"); + var address = objectHash.getChash160(["sig", { + "pubkey": wallet_xPubKey_base64 + }]); + return address; + } catch (error) { + return 0; + } + } + + //生成钱包地址对应的公钥 + walletAddressPubkey (wallet_xPubKey, change, num) { + try { + wallet_xPubKey = Bitcore.HDPublicKey.fromString(wallet_xPubKey); + var wallet_xPubKey_base64 = wallet_xPubKey.derive("m/" + change + "/" + num).publicKey.toBuffer().toString("base64"); + return wallet_xPubKey_base64; + } catch (error) { + return 0; + } + } + + //签名 + sign (b64_hash, xPrivKey, path) { + try { + var buf_to_sign = new Buffer(b64_hash, "base64"); + if (path != "null") { + var xPrivKey = new Bitcore.HDPrivateKey.fromString(xPrivKey); + var privateKey = xPrivKey.derive(path).privateKey; + var privKeyBuf = privateKey.bn.toBuffer({ + size: 32 + }); + } else { + var privKeyBuf = new Buffer(xPrivKey, "base64"); + } + var res = ecdsa.sign(buf_to_sign, privKeyBuf); + return res.signature.toString("base64"); + } catch (error) { + return 0; + } + } + + //验证签名 + verify (b64_hash, sig, pub_key) { + try { + var buf_to_verify = new Buffer(b64_hash, "base64"); + var signature = new Buffer(sig, "base64"); // 64 bytes (32+32) + if (ecdsa.verify(buf_to_verify, signature, new Buffer(pub_key, "base64"))) + return 1; + else + return 0; + } catch (errer) { + return 0; + } + } + + //生成ecdsa签名公钥 + ecdsaPubkey (xPrivKey, path) { + try { + if (path != "null") { + xPrivKey = Bitcore.HDPrivateKey.fromString(xPrivKey); + var priv_key = xPrivKey.derive(path).privateKey.bn.toBuffer({ + size: 32 + }); + } else { + var priv_key = new Buffer(xPrivKey, "base64"); + } + var pub_b64 = ecdsa.publicKeyCreate(priv_key, true).toString('base64'); + return pub_b64; + } catch (error) { + return 0; + } + } + + //生成m/1'私钥 + m1PrivKey (xPrivKey) { + try { + var xPrivKey = new Bitcore.HDPrivateKey.fromString(xPrivKey); + var privateKey = xPrivKey.derive("m/1'").privateKey; + var privKeyBuf = privateKey.bn.toBuffer({ + size: 32 + }); + return privKeyBuf.toString("base64"); + } catch (error) { + return 0; + } + } + + //生成临时私钥 + genPrivKey () { + var privKey; + try { + do { + privKey = crypto.randomBytes(32); + } + while (!ecdsa.privateKeyVerify(privKey)); + return privKey.toString("base64"); + } catch (error) { + return 0; + } + } + + //根据临时私钥生成公钥 + genPubKey (privKey) { + try { + var pubKey = ecdsa.publicKeyCreate(new Buffer(privKey, "base64"), true).toString('base64'); + return pubKey; + } catch (error) { + return 0; + } + } + + getDeviceMessageHashToSign (objDeviceMessage) { + return objectHash.getDeviceMessageHashToSign(objDeviceMessage); + } + + getUnitHashToSign (objUnit) { + return objectHash.getUnitHashToSign(objUnit); + } + + getBase64Hash (objUnit) { + return objectHash.getBase64Hash(objUnit); + } + + getUnitHash (objUnit) { + return objectHash.getUnitHash(objUnit); + } + + getHeadersSize (objUnit) { + return objectLength.getHeadersSize(objUnit); + } + + getTotalPayloadSize (objUnit) { + return objectLength.getTotalPayloadSize(objUnit); + } +}; + +module.exports = Wallet; \ No newline at end of file diff --git a/package.json b/package.json index c06e154..5aca513 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,9 @@ "bitcore-lib": "^0.15.0", "bitcore-mnemonic": "^1.5.0", "secp256k1": "^3.5.0", - "thirty-two": "^1.0.2" + "thirty-two": "^1.0.2", + "ws": "^1.0.1", + "sqlite3": "^3.1.4" }, "devDependencies": { "grunt": "^1.0.2", diff --git a/validation.js b/validation.js deleted file mode 100644 index 7c7cb2a..0000000 --- a/validation.js +++ /dev/null @@ -1,24 +0,0 @@ -"use strict"; -var chash = require('./chash.js'); - -function isStringOfLength(str, len) { - return (typeof str === "string" && str.length === len); -} - -function isValidChash(str, len) { - return (isStringOfLength(str, len) && chash.isChashValid(str)); -} - -function isValidAddress(address) { - try { - if ((typeof address === "string" && address === address.toUpperCase() && isValidChash(address, 32))) { - return 1 - } else { - return 0; - } - } catch (error) { - return 0; - } -} - -exports.isValidAddress = isValidAddress; \ No newline at end of file From 28c1efb3bc5778fd59588cbf7f35bf7821651a42 Mon Sep 17 00:00:00 2001 From: zhongjiewang Date: Tue, 19 Jun 2018 09:51:22 +0800 Subject: [PATCH 2/2] temp --- lib/common/async.js | 34 + lib/common/composer.js | 721 +++++++++++++++++++ lib/common/device.js | 792 +++++++++++++++++++++ lib/common/headers_commission.js | 11 + lib/common/mc_outputs.js | 151 ++++ lib/common/paid_witnessing.js | 14 + lib/common/validation.js | 224 ++++++ lib/common/wallet.js | 242 +++++++ lib/config/constants.js | 22 +- lib/db/save_joint_helper.js | 2 +- lib/db/storage.js | 32 + lib/headless/headlessWallet.js | 42 ++ lib/headless/temp.js | 83 +++ lib/offline/initial.trustnote-light.sqlite | Bin 215040 -> 215040 bytes lib/test/create_payment_test.js | 75 ++ lib/test/network_manager_test.js | 5 +- lib/{db => tools}/mutex.js | 0 17 files changed, 2443 insertions(+), 7 deletions(-) create mode 100644 lib/common/async.js create mode 100644 lib/common/composer.js create mode 100644 lib/common/device.js create mode 100644 lib/common/headers_commission.js create mode 100644 lib/common/mc_outputs.js create mode 100644 lib/common/paid_witnessing.js create mode 100644 lib/common/validation.js create mode 100644 lib/common/wallet.js create mode 100644 lib/headless/headlessWallet.js create mode 100644 lib/headless/temp.js create mode 100644 lib/test/create_payment_test.js rename lib/{db => tools}/mutex.js (100%) diff --git a/lib/common/async.js b/lib/common/async.js new file mode 100644 index 0000000..7a17332 --- /dev/null +++ b/lib/common/async.js @@ -0,0 +1,34 @@ +'use strict' + + +var async = require('async'); +var openFiles = ['1', '2', '3']; + +async.eachSeries(openFiles, function(file, callback) { + + // Perform operation on file here. + console.log('Processing file ' + file); + + if( file.length > 32 ) { + console.log('This file name is too long'); + callback('File name too long'); + } else { + // Do work to process file here + console.log('File processed', file); + // callback(file); + let err = null; + if (file == '2') { + err = file; + } + callback(err); + } +}, function(err) { + // if any of the file processing produced an error, err would equal that error + if( err ) { + // One of the iterations produced an error. + // All processing will now stop. + console.log('A file failed to process',err); + } else { + console.log('All files have been processed successfully'); + } +}); \ No newline at end of file diff --git a/lib/common/composer.js b/lib/common/composer.js new file mode 100644 index 0000000..419de9b --- /dev/null +++ b/lib/common/composer.js @@ -0,0 +1,721 @@ +/*jslint node: true */ +"use strict"; + +var _ = require('lodash'); +var constants = require('../config/constants'); +var mutex = require('../tools/mutex'); +var async = require('async'); +var storage = require('../db/storage.js'); +var objectLength = require('../crypto/object_length.js'); +var headers_commission = require("./headers_commission.js"); +var mc_outputs = require("./mc_outputs.js"); +var paid_witnessing = require("./paid_witnessing.js"); + + + +var sqlitePool = require('../db/sqlite_pool.js'); +var path = '../offline/initial.trustnote-light.sqlite'; +var db = sqlitePool(path,1, false, function(err){ + console.log('err---------', err ); +}); + + +var TRANSFER_INPUT_SIZE = 0 // type: "transfer" omitted + + 44 // unit + + 8 // message_index + + 8; // output_index + +var HEADERS_COMMISSION_INPUT_SIZE = 18 // type: "headers_commission" + + 8 // from_main_chain_index + + 8; // to_main_chain_index + +var WITNESSING_INPUT_SIZE = 10 // type: "witnessing" + + 8 // from_main_chain_index + + 8; // to_main_chain_index + +var ADDRESS_SIZE = 32; + + +var conf = {}; +conf.bLight = true; + +var bGenesis = false; + + +var hash_placeholder = "--------------------------------------------"; // 256 bits (32 bytes) base64: 44 bytes +var sig_placeholder = "----------------------------------------------------------------------------------------"; // 88 bytes + + +function repeatString(str, times){ + if (str.repeat) + return str.repeat(times); + return (new Array(times+1)).join(str); +} + +/* + params.signing_addresses must sign the message but they do not necessarily pay + params.paying_addresses pay for byte outputs and commissions +*/ +function composeJoint(params){ + + console.log('composeJoint---params-:\n', JSON.stringify(params, null,3)); + + var arrWitnesses = params.witnesses; + /* + if (conf.bLight && !params.lightProps){ + var network = require('./network.js'); + network.requestFromLightVendor( + 'light/get_parents_and_last_ball_and_witness_list_unit', + {witnesses: arrWitnesses}, + function(ws, request, response){ + if (response.error) + return params.callbacks.ifError(response.error); + if (!response.parent_units || !response.last_stable_mc_ball || !response.last_stable_mc_ball_unit || typeof response.last_stable_mc_ball_mci !== 'number') + return params.callbacks.ifError("invalid parents from light vendor"); + params.lightProps = response; + composeJoint(params); + } + ); + return; + } + */ + + var arrSigningAddresses = params.signing_addresses || []; + var arrPayingAddresses = params.paying_addresses || []; + var arrOutputs = params.outputs || []; + var arrMessages = _.clone(params.messages || []); + var assocPrivatePayloads = params.private_payloads || {}; // those that correspond to a subset of params.messages + var fnRetrieveMessages = params.retrieveMessages; + var lightProps = params.lightProps; + var signer = params.signer; + var callbacks = params.callbacks; + + if (conf.bLight && !lightProps) + throw Error("no parent props for light"); + + + var arrChangeOutputs = arrOutputs.filter(function(output) { return (output.amount === 0); }); + //额外的 + var arrExternalOutputs = arrOutputs.filter(function(output) { return (output.amount > 0); }); + if (arrChangeOutputs.length > 1) + throw Error("more than one change output"); + if (arrChangeOutputs.length === 0) + throw Error("no change outputs"); + + if (arrPayingAddresses.length === 0) + throw Error("no payers?"); + var arrFromAddresses = _.union(arrSigningAddresses, arrPayingAddresses).sort(); + + var objPaymentMessage = { + app: "payment", + payload_location: "inline", + payload_hash: hash_placeholder, + payload: { + // first output is the change, it has 0 amount (placeholder) that we'll modify later. + // Then we'll sort outputs, so the change is not necessarity the first in the final transaction + outputs: arrChangeOutputs + // we'll add more outputs below + } + }; + var total_amount = 0; + arrExternalOutputs.forEach(function(output){ + objPaymentMessage.payload.outputs.push(output); + total_amount += output.amount; + }); + arrMessages.push(objPaymentMessage); + + var bMultiAuthored = (arrFromAddresses.length > 1); + var objUnit = { + version: constants.version, + alt: constants.alt, + //timestamp: Date.now(), + messages: arrMessages, + authors: [] + }; + + console.log('---objUnit: \n', JSON.stringify(objUnit, null, 3)); + + var objJoint = {unit: objUnit}; + if (params.earned_headers_commission_recipients) // it needn't be already sorted by address, we'll sort it now + objUnit.earned_headers_commission_recipients = params.earned_headers_commission_recipients.concat().sort(function(a,b){ + return ((a.address < b.address) ? -1 : 1); + }); + else if (bMultiAuthored) // by default, the entire earned hc goes to the change address + objUnit.earned_headers_commission_recipients = [{address: arrChangeOutputs[0].address, earned_headers_commission_share: 100}]; + + var total_input; + var last_ball_mci; + var assocSigningPaths = {}; + var unlock_callback; + var conn; + + var handleError = function(err){ + //profiler.stop('compose'); + unlock_callback(); + if (typeof err === "object"){ + if (err.error_code === "NOT_ENOUGH_FUNDS") + return callbacks.ifNotEnoughFunds(err.error); + throw Error("unknown error code in: "+JSON.stringify(err)); + } + callbacks.ifError(err); + }; + + async.series([ + function(cb){ // lock + mutex.lock(arrFromAddresses.map(function(from_address){ return 'c-'+from_address; }), function(unlock){ + unlock_callback = unlock; + cb(); + }); + }, + function(cb){ // start transaction + console.log('-----------------start transaction--------------------'); + db.takeConnectionFromPool(function(new_conn){ + conn = new_conn; + conn.query("BEGIN", function(){cb();}); + }); + // cb(); + }, + + function(cb){ // parent units + console.log('-----------------parent units-------------------------'); + // if (bGenesis){ + // return cb(); + // } + + function checkForUnstablePredecessors(){ + conn.query( + // is_stable=0 condition is redundant given that last_ball_mci is stable + "SELECT 1 FROM units CROSS JOIN unit_authors USING(unit) \n\ + WHERE (main_chain_index>? OR main_chain_index IS NULL) AND address IN(?) AND definition_chash IS NOT NULL \n\ + UNION \n\ + SELECT 1 FROM units JOIN address_definition_changes USING(unit) \n\ + WHERE (main_chain_index>? OR main_chain_index IS NULL) AND address IN(?) \n\ + UNION \n\ + SELECT 1 FROM units CROSS JOIN unit_authors USING(unit) \n\ + WHERE (main_chain_index>? OR main_chain_index IS NULL) AND address IN(?) AND sequence!='good'", + [last_ball_mci, arrFromAddresses, last_ball_mci, arrFromAddresses, last_ball_mci, arrFromAddresses], + function(rows){ + if (rows.length > 0) + return cb("some definition changes or definitions or nonserials are not stable yet"); + cb(); + } + ); + } + + if (conf.bLight){ + objUnit.parent_units = lightProps.parent_units; + objUnit.last_ball = lightProps.last_stable_mc_ball; + objUnit.last_ball_unit = lightProps.last_stable_mc_ball_unit; + last_ball_mci = lightProps.last_stable_mc_ball_mci; + // return checkForUnstablePredecessors(); + return cb(); + } + }, + function(cb){ // authors + console.log('-----------------authors--------------------'); + async.eachSeries(arrFromAddresses, function(from_address, cb2){ + + function setDefinition(){ + signer.readDefinition(conn, from_address, function(err, arrDefinition){ + if (err) + return cb2(err); + objAuthor.definition = arrDefinition; + cb2(); + }); + } + + var objAuthor = { + address: from_address, + authentifiers: {} + }; + //assocLengthsBySigningPaths ==> {r: constants.SIG_LENGTH} + signer.readSigningPaths(conn, from_address, function(assocLengthsBySigningPaths){ + var arrSigningPaths = Object.keys(assocLengthsBySigningPaths); + assocSigningPaths[from_address] = arrSigningPaths; + for (var j=0; j 0) + // for (var payload_hash in assocMorePrivatePayloads) + // assocPrivatePayloads[payload_hash] = assocMorePrivatePayloads[payload_hash]; + // cb(); + // }); + }, + function(cb){ // input coins + console.log('------------input coins------------------'); + objUnit.headers_commission = objectLength.getHeadersSize(objUnit); //子单元的佣金 + var naked_payload_commission = objectLength.getTotalPayloadSize(objUnit); // without input coins + + if (bGenesis){ + objPaymentMessage.payload.inputs = [{type: "issue", serial_number: 1, amount: constants.TOTAL_WHITEBYTES, address: arrWitnesses[0]}]; + objUnit.payload_commission = objectLength.getTotalPayloadSize(objUnit); + total_input = constants.TOTAL_WHITEBYTES; + return cb(); + } + if (params.inputs){ // input coins already selected + if (!params.input_amount) + throw Error('inputs but no input_amount'); + total_input = params.input_amount; + objPaymentMessage.payload.inputs = params.inputs; + objUnit.payload_commission = objectLength.getTotalPayloadSize(objUnit); + return cb(); + } + + // all inputs must appear before last_ball + var target_amount = params.send_all ? Infinity : (total_amount + objUnit.headers_commission + naked_payload_commission); + pickDivisibleCoinsForAmount( + conn, null, arrPayingAddresses, last_ball_mci, target_amount, bMultiAuthored, + function(arrInputsWithProofs, _total_input){ + console.log('-----pickDivisibleCoinsForAmount-------onDone----arrInputsWithProofs:',arrInputsWithProofs); + if (!arrInputsWithProofs) + return cb({ + error_code: "NOT_ENOUGH_FUNDS", + error: "not enough spendable funds from "+arrPayingAddresses+" for "+target_amount + }); + total_input = _total_input; + objPaymentMessage.payload.inputs = arrInputsWithProofs.map(function(objInputWithProof){ return objInputWithProof.input; }); + objUnit.payload_commission = objectLength.getTotalPayloadSize(objUnit); + console.log("inputs increased payload by", objUnit.payload_commission - naked_payload_commission); + cb(); + } + ); + } + ], function(err){ + console.log('---------composer end -',err); + // we close the transaction and release the connection before signing as multisig signing may take very very long + // however we still keep c-ADDRESS lock to avoid creating accidental doublespends + + // conn.query(err ? "ROLLBACK" : "COMMIT", function(){ + // conn.release(); + // if (err) + // return handleError(err); + + // // change, payload hash, signature, and unit hash + // var change = total_input - total_amount - objUnit.headers_commission - objUnit.payload_commission; + // if (change <= 0){ + // if (!params.send_all) + // throw Error("change="+change+", params="+JSON.stringify(params)); + // return handleError({ + // error_code: "NOT_ENOUGH_FUNDS", + // error: "not enough spendable funds from "+arrPayingAddresses+" for fees" + // }); + // } + // objPaymentMessage.payload.outputs[0].amount = change; + // objPaymentMessage.payload.outputs.sort(sortOutputs); + // objPaymentMessage.payload_hash = objectHash.getBase64Hash(objPaymentMessage.payload); + // var text_to_sign = objectHash.getUnitHashToSign(objUnit); + // async.each( + // objUnit.authors, + // function(author, cb2){ + // var address = author.address; + // async.each( // different keys sign in parallel (if multisig) + // assocSigningPaths[address], + // function(path, cb3){ + // if (signer.sign){ + // signer.sign(objUnit, assocPrivatePayloads, address, path, function(err, signature){ + // if (err) + // return cb3(err); + // // it can't be accidentally confused with real signature as there are no [ and ] in base64 alphabet + // if (signature === '[refused]') + // return cb3('one of the cosigners refused to sign'); + // author.authentifiers[path] = signature; + // cb3(); + // }); + // } + // else{ + // signer.readPrivateKey(address, path, function(err, privKey){ + // if (err) + // return cb3(err); + // author.authentifiers[path] = ecdsaSig.sign(text_to_sign, privKey); + // cb3(); + // }); + // } + // }, + // function(err){ + // cb2(err); + // } + // ); + // }, + // function(err){ + // if (err) + // return handleError(err); + // objUnit.unit = objectHash.getUnitHash(objUnit); + // if (bGenesis) + // objJoint.ball = objectHash.getBallHash(objUnit.unit); + // console.log(require('util').inspect(objJoint, {depth:null})); + // objJoint.unit.timestamp = Math.round(Date.now()/1000); // light clients need timestamp + // if (Object.keys(assocPrivatePayloads).length === 0) + // assocPrivatePayloads = null; + // //profiler.stop('compose'); + // callbacks.ifOk(objJoint, assocPrivatePayloads, unlock_callback); + // } + // ); + // }); + }); + +} + +function getSavingCallbacks(callbacks){ + return {}; + /* + return { + ifError: callbacks.ifError, + ifNotEnoughFunds: callbacks.ifNotEnoughFunds, + ifOk: function(objJoint, assocPrivatePayloads, composer_unlock){ + var objUnit = objJoint.unit; + var unit = objUnit.unit; + validation.validate(objJoint, { + ifUnitError: function(err){ + composer_unlock(); + callbacks.ifError("Validation error: "+err); + // throw Error("unexpected validation error: "+err); + }, + ifJointError: function(err){ + throw Error("unexpected validation joint error: "+err); + }, + ifTransientError: function(err){ + throw Error("unexpected validation transient error: "+err); + }, + ifNeedHashTree: function(){ + throw Error("unexpected need hash tree"); + }, + ifNeedParentUnits: function(arrMissingUnits){ + throw Error("unexpected dependencies: "+arrMissingUnits.join(", ")); + }, + ifOk: function(objValidationState, validation_unlock){ + console.log("base asset OK "+objValidationState.sequence); + if (objValidationState.sequence !== 'good'){ + validation_unlock(); + composer_unlock(); + return callbacks.ifError("Bad sequence "+objValidationState.sequence); + } + postJointToLightVendorIfNecessaryAndSave( + objJoint, + function onLightError(err){ // light only + console.log("failed to post base payment "+unit); + var eventBus = require('./event_bus.js'); + if (err.match(/signature/)) + eventBus.emit('nonfatal_error', "failed to post unit "+unit+": "+err+"; "+JSON.stringify(objUnit), new Error()); + validation_unlock(); + composer_unlock(); + callbacks.ifError(err); + }, + function save(){ + writer.saveJoint( + objJoint, objValidationState, + null, + function onDone(){ + console.log("saved unit "+unit); + validation_unlock(); + composer_unlock(); + callbacks.ifOk(objJoint, assocPrivatePayloads); + } + ); + } + ); + } // ifOk validation + }); // validate + } + }; + */ +} + + +// bMultiAuthored includes all addresses, not just those that pay +// arrAddresses is paying addresses +function pickDivisibleCoinsForAmount(conn, objAsset, arrAddresses, last_ball_mci, amount, bMultiAuthored, onDone){ + var asset = objAsset ? objAsset.asset : null; + console.log("pick coins "+asset+" amount "+amount); + var is_base = objAsset ? 0 : 1; + var arrInputsWithProofs = []; + var total_amount = 0; + var required_amount = amount; + + // adds element to arrInputsWithProofs + function addInput(input){ + total_amount += input.amount; + var objInputWithProof = {input: input}; + if (objAsset && objAsset.is_private){ // for type=payment only + var spend_proof = objectHash.getBase64Hash({ + asset: asset, + amount: input.amount, + address: input.address, + unit: input.unit, + message_index: input.message_index, + output_index: input.output_index, + blinding: input.blinding + }); + var objSpendProof = {spend_proof: spend_proof}; + if (bMultiAuthored) + objSpendProof.address = input.address; + objInputWithProof.spend_proof = objSpendProof; + } + if (!bMultiAuthored || !input.type) + delete input.address; + delete input.amount; + delete input.blinding; + arrInputsWithProofs.push(objInputWithProof); + } + + // first, try to find a coin just bigger than the required amount + function pickOneCoinJustBiggerAndContinue(){ + if (amount === Infinity) + return pickMultipleCoinsAndContinue(); + var more = is_base ? '>' : '>='; + conn.query( + "SELECT unit, message_index, output_index, amount, blinding, address \n\ + FROM outputs \n\ + CROSS JOIN units USING(unit) \n\ + WHERE address IN(?) AND asset"+(asset ? "="+conn.escape(asset) : " IS NULL")+" AND is_spent=0 AND amount "+more+" ? \n\ + AND is_stable=1 AND sequence='good' AND main_chain_index<=? \n\ + ORDER BY amount LIMIT 1", + [arrSpendableAddresses, amount+is_base*TRANSFER_INPUT_SIZE, last_ball_mci], + function(rows){ + if (rows.length === 1){ + var input = rows[0]; + // default type is "transfer" + addInput(input); + onDone(arrInputsWithProofs, total_amount); + } + else + pickMultipleCoinsAndContinue(); + } + ); + } + + // then, try to add smaller coins until we accumulate the target amount + function pickMultipleCoinsAndContinue(){ + conn.query( + "SELECT unit, message_index, output_index, amount, address, blinding \n\ + FROM outputs \n\ + CROSS JOIN units USING(unit) \n\ + WHERE address IN(?) AND asset"+(asset ? "="+conn.escape(asset) : " IS NULL")+" AND is_spent=0 \n\ + AND is_stable=1 AND sequence='good' AND main_chain_index<=? \n\ + ORDER BY amount DESC", + [arrSpendableAddresses, last_ball_mci], + function(rows){ + async.eachSeries( + rows, + function(row, cb){ + var input = row; + objectHash.cleanNulls(input); + required_amount += is_base*TRANSFER_INPUT_SIZE; + addInput(input); + // if we allow equality, we might get 0 amount for change which is invalid + var bFound = is_base ? (total_amount > required_amount) : (total_amount >= required_amount); + bFound ? cb('found') : cb(); + }, + function(err){ + if (err === 'found') + onDone(arrInputsWithProofs, total_amount); + else if (asset) + issueAsset(); + else + addHeadersCommissionInputs(); + } + ); + } + ); + } + + function addHeadersCommissionInputs(){ + addMcInputs("headers_commission", HEADERS_COMMISSION_INPUT_SIZE, + headers_commission.getMaxSpendableMciForLastBallMci(last_ball_mci), addWitnessingInputs); + } + + function addWitnessingInputs(){ + addMcInputs("witnessing", WITNESSING_INPUT_SIZE, paid_witnessing.getMaxSpendableMciForLastBallMci(last_ball_mci), issueAsset); + } + + function addMcInputs(type, input_size, max_mci, onStillNotEnough){ + async.eachSeries( + arrAddresses, + function(address, cb){ + var target_amount = required_amount + input_size + (bMultiAuthored ? ADDRESS_SIZE : 0) - total_amount; + mc_outputs.findMcIndexIntervalToTargetAmount(conn, type, address, max_mci, target_amount, { + ifNothing: cb, + ifFound: function(from_mc_index, to_mc_index, earnings, bSufficient){ + if (earnings === 0) + throw Error("earnings === 0"); + total_amount += earnings; + var input = { + type: type, + from_main_chain_index: from_mc_index, + to_main_chain_index: to_mc_index + }; + var full_input_size = input_size; + if (bMultiAuthored){ + full_input_size += ADDRESS_SIZE; // address length + input.address = address; + } + required_amount += full_input_size; + arrInputsWithProofs.push({input: input}); + (total_amount > required_amount) + ? cb("found") // break eachSeries + : cb(); // try next address + } + }); + }, + function(err){ + if (!err) + console.log(arrAddresses+" "+type+": got only "+total_amount+" out of required "+required_amount); + (err === "found") ? onDone(arrInputsWithProofs, total_amount) : onStillNotEnough(); + } + ); + } + + function issueAsset(){ + if (!asset) + return finish(); + else{ + if (amount === Infinity && !objAsset.cap) // don't try to create infinite issue + return onDone(null); + } + console.log("will try to issue asset "+asset); + // for issue, we use full list of addresses rather than spendable addresses + if (objAsset.issued_by_definer_only && arrAddresses.indexOf(objAsset.definer_address) === -1) + return finish(); + var issuer_address = objAsset.issued_by_definer_only ? objAsset.definer_address : arrAddresses[0]; + var issue_amount = objAsset.cap || (required_amount - total_amount) || 1; // 1 currency unit in case required_amount = total_amount + + function addIssueInput(serial_number){ + total_amount += issue_amount; + var input = { + type: "issue", + amount: issue_amount, + serial_number: serial_number + }; + if (bMultiAuthored) + input.address = issuer_address; + var objInputWithProof = {input: input}; + if (objAsset && objAsset.is_private){ + var spend_proof = objectHash.getBase64Hash({ + asset: asset, + amount: issue_amount, + denomination: 1, + address: issuer_address, + serial_number: serial_number + }); + var objSpendProof = {spend_proof: spend_proof}; + if (bMultiAuthored) + objSpendProof.address = input.address; + objInputWithProof.spend_proof = objSpendProof; + } + arrInputsWithProofs.push(objInputWithProof); + var bFound = is_base ? (total_amount > required_amount) : (total_amount >= required_amount); + bFound ? onDone(arrInputsWithProofs, total_amount) : finish(); + } + + if (objAsset.cap){ + conn.query("SELECT 1 FROM inputs WHERE type='issue' AND asset=?", [asset], function(rows){ + if (rows.length > 0) // already issued + return finish(); + addIssueInput(1); + }); + } + else{ + conn.query( + "SELECT MAX(serial_number) AS max_serial_number FROM inputs WHERE type='issue' AND asset=? AND address=?", + [asset, issuer_address], + function(rows){ + var max_serial_number = (rows.length === 0) ? 0 : rows[0].max_serial_number; + addIssueInput(max_serial_number+1); + } + ); + } + } + + function finish(){ + if (amount === Infinity && arrInputsWithProofs.length > 0) + onDone(arrInputsWithProofs, total_amount); + else + onDone(null); + } + + var arrSpendableAddresses = arrAddresses.concat(); // cloning + if (objAsset && objAsset.auto_destroy){ + var i = arrAddresses.indexOf(objAsset.definer_address); + if (i>=0) + arrSpendableAddresses.splice(i, 1); + } + if (arrSpendableAddresses.length > 0) { + pickOneCoinJustBiggerAndContinue(); + } else { + issueAsset(); + } +} + + + + +// function composeJoint () { }; +exports.composeJoint = composeJoint; +exports.getSavingCallbacks = getSavingCallbacks; \ No newline at end of file diff --git a/lib/common/device.js b/lib/common/device.js new file mode 100644 index 0000000..3860a44 --- /dev/null +++ b/lib/common/device.js @@ -0,0 +1,792 @@ +/*jslint node: true */ +"use strict"; +var crypto = require('crypto'); +var async = require('async'); +var ecdsa = require('secp256k1'); +var db = require('./db.js'); +var mutex = require('./mutex.js'); +var objectHash = require('./object_hash.js'); +var ecdsaSig = require('./signature.js'); +var network = require('./network.js'); +var eventBus = require('./event_bus.js'); +var ValidationUtils = require("./validation_utils.js"); +var conf = require('./conf.js'); +var breadcrumbs = require('./breadcrumbs.js'); + + +var SEND_RETRY_PERIOD = 60*1000; +var RECONNECT_TO_HUB_PERIOD = 60*1000; +var TEMP_DEVICE_KEY_ROTATION_PERIOD = 3600*1000; + +var my_device_hub; +var my_device_name; +var my_device_address; +var my_cold_device_address; + +var objMyPermanentDeviceKey; +var objMyTempDeviceKey; +var objMyPrevTempDeviceKey; +var saveTempKeys; // function that saves temp keys +var bScheduledTempDeviceKeyRotation = false; +var loginHubTimeoutCount = 0; +var loginHubTimeoutDoneCount = 0; +var stableHub = "stable.trustnote.org/tn"; + + +function getMyDevicePubKey(){ + if (!objMyPermanentDeviceKey || !objMyPermanentDeviceKey.pub_b64) + throw Error('my device pubkey not defined'); + return objMyPermanentDeviceKey.pub_b64; +} + +function getMyDeviceAddress(){ + if (!my_device_address) + throw Error('my_device_address not defined'); + if (my_cold_device_address && (my_cold_device_address != my_device_address)) + return my_cold_device_address; + if (typeof window !== 'undefined' && window && window.cordova) + checkDeviceAddress(); + return my_device_address; +} + +function uPMyColdDeviceAddress(walletId) { + db.query("SELECT device_address FROM extended_pubkeys WHERE wallet=?",[walletId], function(rows) { + setMyColdDeviceAddress(rows[0].device_address); + }) +} + +function setMyColdDeviceAddress(addr) { + my_cold_device_address = addr; +} + +function getMyColdDeviceAddress() { + if(!my_cold_device_address) + throw Error('My_device_address not defined'); + return my_cold_device_address; +} + +function setDevicePrivateKey(priv_key){ + breadcrumbs.add("setDevicePrivateKey"); + var bChanged = (!objMyPermanentDeviceKey || priv_key !== objMyPermanentDeviceKey.priv); + objMyPermanentDeviceKey = { + priv: priv_key, + pub_b64: ecdsa.publicKeyCreate(priv_key, true).toString('base64') + }; + var new_my_device_address = objectHash.getDeviceAddress(objMyPermanentDeviceKey.pub_b64); + if (my_device_address && my_device_address !== new_my_device_address){ + breadcrumbs.add('different device address: old '+my_device_address+', new '+new_my_device_address); + throw Error('different device address: old '+my_device_address+', new '+new_my_device_address); + } + breadcrumbs.add("same device addresses: "+new_my_device_address); + my_device_address = new_my_device_address; + // this temp pubkey package signs my permanent key and is actually used only if I'm my own hub. + // In this case, there are no intermediaries and TLS already provides perfect forward security + network.setMyDeviceProps(my_device_address, createTempPubkeyPackage(objMyPermanentDeviceKey.pub_b64)); + if (bChanged) + loginToHub(); +} + +function checkDeviceAddress(){ + if (!objMyPermanentDeviceKey) + return; + var derived_my_device_address = objectHash.getDeviceAddress(objMyPermanentDeviceKey.pub_b64); + if (my_device_address !== derived_my_device_address){ + breadcrumbs.add('different device address: old '+my_device_address+', derived '+derived_my_device_address); + throw Error('different device address: old '+my_device_address+', derived '+derived_my_device_address); + } +} + +function setTempKeys(temp_priv_key, prev_temp_priv_key, fnSaveTempKeys){ +// console.log("setTempKeys", temp_priv_key, prev_temp_priv_key); + objMyTempDeviceKey = { + use_count: null, // unknown + priv: temp_priv_key, + pub_b64: ecdsa.publicKeyCreate(temp_priv_key, true).toString('base64') + }; + if (prev_temp_priv_key) // may be null + objMyPrevTempDeviceKey = { + priv: prev_temp_priv_key, + pub_b64: ecdsa.publicKeyCreate(prev_temp_priv_key, true).toString('base64') + }; + saveTempKeys = fnSaveTempKeys; + loginToHub(); +} + +function setDeviceAddress(device_address){ + breadcrumbs.add("setDeviceAddress: " + device_address); + if (my_device_address && my_device_address !== device_address) + throw Error('different device address'); + my_device_address = device_address; +} + +function setNewDeviceAddress(device_address){ + breadcrumbs.add("setNewDeviceAddress: " + device_address); + my_device_address = device_address; +} + +function setDeviceName(device_name){ + console.log("setDeviceName", device_name); + my_device_name = device_name; +} + +function setDeviceHub(device_hub){ + console.log("setDeviceHub", device_hub); + var bChanged = (device_hub !== my_device_hub); + my_device_hub = device_hub; + if (bChanged){ + network.addPeer(conf.WS_PROTOCOL+device_hub); + loginToHub(); + } +} + +function isValidPubKey(b64_pubkey){ + return ecdsa.publicKeyVerify(new Buffer(b64_pubkey, 'base64')); +} + +// ------------------------- +// logging in to hub + + +function handleChallenge(ws, challenge){ + console.log('handleChallenge'); + if (ws.bLoggingIn) + sendLoginCommand(ws, challenge); + else // save for future login + ws.received_challenge = challenge; +} + +function loginToHub(){ + if (!objMyPermanentDeviceKey) + return console.log("objMyPermanentDeviceKey not set yet, can't log in"); + if (!objMyTempDeviceKey) + return console.log("objMyTempDeviceKey not set yet, can't log in"); + if (!my_device_hub) + return console.log("my_device_hub not set yet, can't log in"); + console.log("logging in to hub "+my_device_hub); + network.findOutboundPeerOrConnect(conf.WS_PROTOCOL+my_device_hub, function onLocatedHubForLogin(err, ws){ + if (err) { + if(loginHubTimeoutDoneCount > 2) + return; + loginHubTimeoutCount++; + if(loginHubTimeoutCount > 3) { + loginHubTimeoutCount = 0; + loginHubTimeoutDoneCount++; + setDeviceHub(stableHub); + return; + } + loginToHub(); + return; + } + if (ws.bLoggedIn) + return; + if (ws.received_challenge) + sendLoginCommand(ws, ws.received_challenge); + else { + ws.bLoggingIn = true; + loginHubTimeoutCount = 0; + } + console.log('done loginToHub'); + }); +} + + +setInterval(loginToHub, RECONNECT_TO_HUB_PERIOD); +eventBus.on('connected', loginToHub); + +function sendLoginCommand(ws, challenge){ + var objLogin = {challenge: challenge, pubkey: objMyPermanentDeviceKey.pub_b64}; + objLogin.signature = ecdsaSig.sign(objectHash.getDeviceMessageHashToSign(objLogin), objMyPermanentDeviceKey.priv); + network.sendJustsaying(ws, 'hub/login', objLogin); + ws.bLoggedIn = true; + sendTempPubkey(ws, objMyTempDeviceKey.pub_b64); + network.initWitnessesIfNecessary(ws); + resendStalledMessages(); +} + +function sendTempPubkey(ws, temp_pubkey, callbacks){ + if (!callbacks) + callbacks = {ifOk: function(){}, ifError: function(){}}; + network.sendRequest(ws, 'hub/temp_pubkey', createTempPubkeyPackage(temp_pubkey), false, function(ws, request, response){ + if (response === 'updated') + return callbacks.ifOk(); + var error = response.error || ("unrecognized response: "+JSON.stringify(response)); + callbacks.ifError(error); + }); +} + +function createTempPubkeyPackage(temp_pubkey){ + var objTempPubkey = { + temp_pubkey: temp_pubkey, + pubkey: objMyPermanentDeviceKey.pub_b64 + }; + objTempPubkey.signature = ecdsaSig.sign(objectHash.getDeviceMessageHashToSign(objTempPubkey), objMyPermanentDeviceKey.priv); + return objTempPubkey; +} + + +// ------------------------------ +// rotation of temp keys + + +function genPrivKey(){ + var privKey; + do { + console.log("generating new priv key"); + privKey = crypto.randomBytes(32); + } + while (!ecdsa.privateKeyVerify(privKey)); + return privKey; +} + +var last_rotate_wake_ts = Date.now(); + +function rotateTempDeviceKeyIfCouldBeAlreadyUsed(){ + var actual_interval = Date.now() - last_rotate_wake_ts; + last_rotate_wake_ts = Date.now(); + if (actual_interval > TEMP_DEVICE_KEY_ROTATION_PERIOD + 1000) + return console.log("woke up after sleep or high load, will skip rotation"); + if (objMyTempDeviceKey.use_count === 0) // new key that was never used yet + return console.log("the current temp key was not used yet, will not rotate"); + // if use_count === null, the key was set at start up, it could've been used before + rotateTempDeviceKey(); +} + +function rotateTempDeviceKey(){ + if (!saveTempKeys) + return console.log("no saving function"); + console.log("will rotate temp device key"); + network.findOutboundPeerOrConnect(conf.WS_PROTOCOL+my_device_hub, function onLocatedHubForRotation(err, ws){ + if (err) + return console.log('will not rotate because: '+err); + if (ws.readyState !== ws.OPEN) + return console.log('will not rotate because connection is not open'); + if (!ws.bLoggedIn) + return console.log('will not rotate because not logged in'); // reconnected and not logged in yet + var new_priv_key = genPrivKey(); + var objNewMyTempDeviceKey = { + use_count: 0, + priv: new_priv_key, + pub_b64: ecdsa.publicKeyCreate(new_priv_key, true).toString('base64') + }; + saveTempKeys(new_priv_key, objMyTempDeviceKey.priv, function(err){ + if (err){ + console.log('failed to save new temp keys, canceling: '+err); + return; + } + objMyPrevTempDeviceKey = objMyTempDeviceKey; + objMyTempDeviceKey = objNewMyTempDeviceKey; + breadcrumbs.add('rotated temp device key'); + sendTempPubkey(ws, objMyTempDeviceKey.pub_b64); + }); + }); +} + +function scheduleTempDeviceKeyRotation(){ + if (bScheduledTempDeviceKeyRotation) + return; + bScheduledTempDeviceKeyRotation = true; + console.log('will schedule rotation in 1 minute'); + setTimeout(function(){ + // due to timeout, we are probably last to request (and receive) this lock + mutex.lock(["from_hub"], function(unlock){ + console.log("will schedule rotation"); + rotateTempDeviceKeyIfCouldBeAlreadyUsed(); + last_rotate_wake_ts = Date.now(); + setInterval(rotateTempDeviceKeyIfCouldBeAlreadyUsed, TEMP_DEVICE_KEY_ROTATION_PERIOD); + unlock(); + }); + }, 60*1000); +} + + +// --------------------------- +// sending/receiving messages + +function deriveSharedSecret(ecdh, peer_b64_pubkey){ + var shared_secret_src = ecdh.computeSecret(peer_b64_pubkey, "base64"); + var shared_secret = crypto.createHash("sha256").update(shared_secret_src).digest().slice(0, 16); + return shared_secret; +} + +function decryptPackage(objEncryptedPackage){ + var priv_key; + if (objEncryptedPackage.dh.recipient_ephemeral_pubkey === objMyTempDeviceKey.pub_b64){ + priv_key = objMyTempDeviceKey.priv; + if (objMyTempDeviceKey.use_count) + objMyTempDeviceKey.use_count++; + else + objMyTempDeviceKey.use_count = 1; + console.log("message encrypted to temp key"); + } + else if (objMyPrevTempDeviceKey && objEncryptedPackage.dh.recipient_ephemeral_pubkey === objMyPrevTempDeviceKey.pub_b64){ + priv_key = objMyPrevTempDeviceKey.priv; + console.log("message encrypted to prev temp key"); + //console.log("objMyPrevTempDeviceKey: "+JSON.stringify(objMyPrevTempDeviceKey)); + //console.log("prev temp private key buf: ", priv_key); + //console.log("prev temp private key b64: "+priv_key.toString('base64')); + } + else if (objEncryptedPackage.dh.recipient_ephemeral_pubkey === objMyPermanentDeviceKey.pub_b64){ + priv_key = objMyPermanentDeviceKey.priv; + console.log("message encrypted to permanent key"); + } + else{ + console.log("message encrypted to unknown key"); + setTimeout(function(){ + throw Error("message encrypted to unknown key, device "+my_device_address+", len="+objEncryptedPackage.encrypted_message.length+". The error might be caused by restoring from an old backup or using the same keys on another device."); + }, 100); + // eventBus.emit('nonfatal_error', "message encrypted to unknown key, device "+my_device_address+", len="+objEncryptedPackage.encrypted_message.length, new Error('unknown key')); + return null; + } + + var ecdh = crypto.createECDH('secp256k1'); + if (process.browser) // workaround bug in crypto-browserify https://github.com/crypto-browserify/createECDH/issues/9 + ecdh.generateKeys("base64", "compressed"); + ecdh.setPrivateKey(priv_key); + var shared_secret = deriveSharedSecret(ecdh, objEncryptedPackage.dh.sender_ephemeral_pubkey); + var iv = new Buffer(objEncryptedPackage.iv, 'base64'); + var decipher = crypto.createDecipheriv('aes-128-gcm', shared_secret, iv); + var authtag = new Buffer(objEncryptedPackage.authtag, 'base64'); + decipher.setAuthTag(authtag); + var enc_buf = Buffer(objEncryptedPackage.encrypted_message, "base64"); +// var decrypted1 = decipher.update(enc_buf); + // under browserify, decryption of long buffers fails with Array buffer allocation errors, have to split the buffer into chunks + var arrChunks = []; + var CHUNK_LENGTH = 4096; + for (var offset = 0; offset < enc_buf.length; offset += CHUNK_LENGTH){ + // console.log('offset '+offset); + arrChunks.push(decipher.update(enc_buf.slice(offset, Math.min(offset+CHUNK_LENGTH, enc_buf.length)))); + } + var decrypted1 = Buffer.concat(arrChunks); + arrChunks = null; + var decrypted2 = decipher.final(); + breadcrumbs.add("decrypted lengths: "+decrypted1.length+" + "+decrypted2.length); + var decrypted_message_buf = Buffer.concat([decrypted1, decrypted2]); + var decrypted_message = decrypted_message_buf.toString("utf8"); + console.log("decrypted: "+decrypted_message); + var json = JSON.parse(decrypted_message); + if (json.encrypted_package){ // strip another layer of encryption + console.log("inner encryption"); + return decryptPackage(json.encrypted_package); + } + else + return json; +} + +// a hack to read large text from cordova sqlite +function readMessageInChunksFromOutbox(message_hash, len, handleMessage){ + var CHUNK_LEN = 1000000; + var start = 1; + var message = ''; + function readChunk(){ + db.query("SELECT SUBSTR(message, ?, ?) AS chunk FROM outbox WHERE message_hash=?", [start, CHUNK_LEN, message_hash], function(rows){ + if (rows.length !== 1) + throw Error(rows.length+' msgs by hash in outbox'); + message += rows[0].chunk; + start += CHUNK_LEN; + (start > len) ? handleMessage(message) : readChunk(); + }); + } + readChunk(); +} + +function resendStalledMessages(){ + console.log("resending stalled messages"); + if (!objMyPermanentDeviceKey) + return console.log("objMyPermanentDeviceKey not set yet, can't resend stalled messages"); + mutex.lockOrSkip(['stalled'], function(unlock){ + var bCordova = (typeof window !== 'undefined' && window && window.cordova); + db.query( + "SELECT "+(bCordova ? "LENGTH(message) AS len" : "message")+", message_hash, `to`, pubkey, hub \n\ + FROM outbox JOIN correspondent_devices ON `to`=device_address \n\ + WHERE outbox.creation_date<"+db.addTime("-1 MINUTE")+" ORDER BY outbox.creation_date", + function(rows){ + console.log(rows.length+" stalled messages"); + async.eachSeries( + rows, + function(row, cb){ + if (!row.hub){ // weird error + eventBus.emit('nonfatal_error', "no hub in resendStalledMessages: "+JSON.stringify(row)+", l="+rows.length, new Error('no hub')); + return cb(); + } + // throw Error("no hub in resendStalledMessages: "+JSON.stringify(row)); + var send = function(message){ + var objDeviceMessage = JSON.parse(message); + //if (objDeviceMessage.to !== row.to) + // throw "to mismatch"; + console.log('sending stalled '+row.message_hash); + sendPreparedMessageToHub(row.hub, row.pubkey, row.message_hash, objDeviceMessage, {ifOk: cb, ifError: function(err){ cb(); }}); + }; + bCordova ? readMessageInChunksFromOutbox(row.message_hash, row.len, send) : send(row.message); + }, + unlock + ); + } + ); + }); +} + +setInterval(resendStalledMessages, SEND_RETRY_PERIOD); + +// reliable delivery +// first param is either WebSocket or hostname of the hub +function reliablySendPreparedMessageToHub(ws, recipient_device_pubkey, json, callbacks, conn){ + var recipient_device_address = objectHash.getDeviceAddress(recipient_device_pubkey); + console.log('will encrypt and send to '+recipient_device_address+': '+JSON.stringify(json)); + // encrypt to recipient's permanent pubkey before storing the message into outbox + var objEncryptedPackage = createEncryptedPackage(json, recipient_device_pubkey); + // if the first attempt fails, this will be the inner message + var objDeviceMessage = { + encrypted_package: objEncryptedPackage + }; + var message_hash = objectHash.getBase64Hash(objDeviceMessage); + conn = conn || db; + conn.query( + "INSERT INTO outbox (message_hash, `to`, message) VALUES (?,?,?)", + [message_hash, recipient_device_address, JSON.stringify(objDeviceMessage)], + function(){ + if (callbacks && callbacks.onSaved) + callbacks.onSaved(); + sendPreparedMessageToHub(ws, recipient_device_pubkey, message_hash, json, callbacks); + } + ); +} + +// first param is either WebSocket or hostname of the hub +function sendPreparedMessageToHub(ws, recipient_device_pubkey, message_hash, json, callbacks){ + if (!callbacks) + callbacks = {ifOk: function(){}, ifError: function(){}}; + if (typeof ws === "string"){ + var hub_host = ws; + network.findOutboundPeerOrConnect(conf.WS_PROTOCOL+hub_host, function onLocatedHubForSend(err, ws){ + if (err) + return callbacks.ifError(err); + sendPreparedMessageToConnectedHub(ws, recipient_device_pubkey, message_hash, json, callbacks); + }); + } + else + sendPreparedMessageToConnectedHub(ws, recipient_device_pubkey, message_hash, json, callbacks); +} + +// first param is WebSocket only +function sendPreparedMessageToConnectedHub(ws, recipient_device_pubkey, message_hash, json, callbacks){ + network.sendRequest(ws, 'hub/get_temp_pubkey', recipient_device_pubkey, false, function(ws, request, response){ + function handleError(error){ + callbacks.ifError(error); + db.query("UPDATE outbox SET last_error=? WHERE message_hash=?", [error, message_hash], function(){}); + } + if (response.error) + return handleError(response.error); + var objTempPubkey = response; + if (!objTempPubkey.temp_pubkey || !objTempPubkey.pubkey || !objTempPubkey.signature) + return handleError("missing fields in hub response"); + if (objTempPubkey.pubkey !== recipient_device_pubkey) + return handleError("temp pubkey signed by wrong permanent pubkey"); + if (!ecdsaSig.verify(objectHash.getDeviceMessageHashToSign(objTempPubkey), objTempPubkey.signature, objTempPubkey.pubkey)) + return handleError("wrong sig under temp pubkey"); + var objEncryptedPackage = createEncryptedPackage(json, objTempPubkey.temp_pubkey); + var recipient_device_address = objectHash.getDeviceAddress(recipient_device_pubkey); + var objDeviceMessage = { + encrypted_package: objEncryptedPackage, + to: recipient_device_address, + pubkey: objMyPermanentDeviceKey.pub_b64 // who signs. Essentially, the from again. + }; + objDeviceMessage.signature = ecdsaSig.sign(objectHash.getDeviceMessageHashToSign(objDeviceMessage), objMyPermanentDeviceKey.priv); + network.sendRequest(ws, 'hub/deliver', objDeviceMessage, false, function(ws, request, response){ + if (response === "accepted"){ + db.query("DELETE FROM outbox WHERE message_hash=?", [message_hash], function(){ + callbacks.ifOk(); + }); + } + else + handleError( response.error || ("unrecognized response: "+JSON.stringify(response)) ); + }); + }); +} + +function createEncryptedPackage(json, recipient_device_pubkey){ + var text = JSON.stringify(json); +// console.log("will encrypt and send: "+text); + var ecdh = crypto.createECDH('secp256k1'); + var sender_ephemeral_pubkey = ecdh.generateKeys("base64", "compressed"); + var shared_secret = deriveSharedSecret(ecdh, recipient_device_pubkey); // Buffer + console.log(shared_secret.length); + // we could also derive iv from the unused bits of ecdh.computeSecret() and save some bandwidth + var iv = crypto.randomBytes(12); // 128 bits (16 bytes) total, we take 12 bytes for random iv and leave 4 bytes for the counter + var cipher = crypto.createCipheriv("aes-128-gcm", shared_secret, iv); + // under browserify, encryption of long strings fails with Array buffer allocation errors, have to split the string into chunks + var arrChunks = []; + var CHUNK_LENGTH = 2003; + for (var offset = 0; offset < text.length; offset += CHUNK_LENGTH){ + // console.log('offset '+offset); + arrChunks.push(cipher.update(text.slice(offset, Math.min(offset+CHUNK_LENGTH, text.length)), 'utf8')); + } + arrChunks.push(cipher.final()); + var encrypted_message_buf = Buffer.concat(arrChunks); + arrChunks = null; +// var encrypted_message_buf = Buffer.concat([cipher.update(text, "utf8"), cipher.final()]); + //console.log(encrypted_message_buf); + var encrypted_message = encrypted_message_buf.toString("base64"); + //console.log(encrypted_message); + var authtag = cipher.getAuthTag(); + // this is visible and verifiable by the hub + var encrypted_package = { + encrypted_message: encrypted_message, + iv: iv.toString('base64'), + authtag: authtag.toString('base64'), + dh: { + sender_ephemeral_pubkey: sender_ephemeral_pubkey, + recipient_ephemeral_pubkey: recipient_device_pubkey + } + }; + return encrypted_package; +} + +// first param is either WebSocket or hostname of the hub or null +function sendMessageToHub(ws, recipient_device_pubkey, subject, body, callbacks, conn){ + // this content is hidden from the hub by encryption + var json = { + from: my_device_address, // presence of this field guarantees that you cannot strip off the signature and add your own signature instead + device_hub: my_device_hub, + subject: subject, + body: body + }; + conn = conn || db; + if (ws) + return reliablySendPreparedMessageToHub(ws, recipient_device_pubkey, json, callbacks, conn); + var recipient_device_address = objectHash.getDeviceAddress(recipient_device_pubkey); + conn.query("SELECT hub FROM correspondent_devices WHERE device_address=?", [recipient_device_address], function(rows){ + if (rows.length !== 1) + throw Error("no hub in correspondents"); + reliablySendPreparedMessageToHub(rows[0].hub, recipient_device_pubkey, json, callbacks, conn); + }); +} + +function sendMessageToDevice(device_address, subject, body, callbacks, conn){ + conn = conn || db; + conn.query("SELECT hub, pubkey FROM correspondent_devices WHERE device_address=?", [device_address], function(rows){ + if (rows.length !== 1) + throw Error("correspondent not found"); + sendMessageToHub(rows[0].hub, rows[0].pubkey, subject, body, callbacks, conn); + }); +} + + + +// ------------------------- +// pairing + + +function sendPairingMessage(hub_host, recipient_device_pubkey, pairing_secret, reverse_pairing_secret, callbacks){ + var body = {pairing_secret: pairing_secret, device_name: my_device_name}; + if (reverse_pairing_secret) + body.reverse_pairing_secret = reverse_pairing_secret; + sendMessageToHub(hub_host, recipient_device_pubkey, "pairing", body, callbacks); +} + +function startWaitingForPairing(handlePairingInfo){ + var pairing_secret = crypto.randomBytes(9).toString("base64"); + var pairingInfo = { + pairing_secret: pairing_secret, + device_pubkey: objMyPermanentDeviceKey.pub_b64, + device_address: my_device_address, + hub: my_device_hub + }; + db.query("INSERT INTO pairing_secrets (pairing_secret, expiry_date) VALUES(?, "+db.addTime("+1 MONTH")+")", [pairing_secret], function(){ + handlePairingInfo(pairingInfo); + }); +} + +// {pairing_secret: "random string", device_name: "Bob's MacBook Pro", reverse_pairing_secret: "random string"} +function handlePairingMessage(json, device_pubkey, callbacks){ + var body = json.body; + var from_address = objectHash.getDeviceAddress(device_pubkey); + if (!ValidationUtils.isNonemptyString(body.pairing_secret)) + return callbacks.ifError("correspondent not known and no pairing secret"); + if (!ValidationUtils.isNonemptyString(json.device_hub)) // home hub of the sender + return callbacks.ifError("no device_hub when pairing"); + if (!ValidationUtils.isNonemptyString(body.device_name)) + return callbacks.ifError("no device_name when pairing"); + if ("reverse_pairing_secret" in body && !ValidationUtils.isNonemptyString(body.reverse_pairing_secret)) + return callbacks.ifError("bad reverse pairing secret"); + db.query( + "SELECT is_permanent FROM pairing_secrets WHERE pairing_secret=? AND expiry_date > "+db.getNow(), + [body.pairing_secret], + function(pairing_rows){ + if (pairing_rows.length === 0) + return callbacks.ifError("pairing secret not found or expired"); + // add new correspondent and delete pending pairing + db.query( + "INSERT "+db.getIgnore()+" INTO correspondent_devices (device_address, pubkey, hub, name, is_confirmed) VALUES (?,?,?,?,1)", + [from_address, device_pubkey, json.device_hub, body.device_name], + function(){ + db.query( // don't update name if already confirmed + "UPDATE correspondent_devices SET is_confirmed=1, name=? WHERE device_address=? AND is_confirmed=0", + [body.device_name, from_address], + function(){ + eventBus.emit("paired", from_address); + if (pairing_rows[0].is_permanent === 0){ // multiple peers can pair through permanent secret + db.query("DELETE FROM pairing_secrets WHERE pairing_secret=?", [body.pairing_secret], function(){}); + eventBus.emit('paired_by_secret-'+body.pairing_secret, from_address); + } + if (body.reverse_pairing_secret) + sendPairingMessage(json.device_hub, device_pubkey, body.reverse_pairing_secret, null); + callbacks.ifOk(); + } + ); + } + ); + } + ); +} + + + +// ------------------------------- +// correspondents + +function addUnconfirmedCorrespondent(device_pubkey, device_hub, device_name, onDone){ + console.log("addUnconfirmedCorrespondent"); + var device_address = objectHash.getDeviceAddress(device_pubkey); + db.query( + "INSERT "+db.getIgnore()+" INTO correspondent_devices (device_address, pubkey, hub, name, is_confirmed) VALUES (?,?,?,?,0)", + [device_address, device_pubkey, device_hub, device_name], + function(){ + if (onDone) + onDone(device_address); + } + ); +} + +function readCorrespondents(handleCorrespondents){ + db.query("SELECT device_address, hub, name, my_record_pref, peer_record_pref FROM correspondent_devices ORDER BY name", function(rows){ + handleCorrespondents(rows); + }); +} + +function readCorrespondent(device_address, handleCorrespondent){ + db.query("SELECT device_address, hub, name, my_record_pref, peer_record_pref FROM correspondent_devices WHERE device_address=?", [device_address], function(rows){ + handleCorrespondent(rows.length ? rows[0] : null); + }); +} + +function readCorrespondentsByDeviceAddresses(arrDeviceAddresses, handleCorrespondents){ + db.query( + "SELECT device_address, hub, name, pubkey, my_record_pref, peer_record_pref FROM correspondent_devices WHERE device_address IN(?) ORDER BY name", + [arrDeviceAddresses], + function(rows){ + handleCorrespondents(rows); + } + ); +} + +function updateCorrespondentProps(correspondent, onDone){ + db.query( + "UPDATE correspondent_devices SET hub=?, name=?, my_record_pref=?, peer_record_pref=? WHERE device_address=?", + [correspondent.hub, correspondent.name, correspondent.my_record_pref, correspondent.peer_record_pref, correspondent.device_address], + function(){ + if (onDone) onDone(); + } + ); +} + +function addIndirectCorrespondents(arrOtherCosigners, onDone){ + async.eachSeries(arrOtherCosigners, function(correspondent, cb){ + if (correspondent.device_address === my_device_address) + return cb(); + db.query( + "INSERT "+db.getIgnore()+" INTO correspondent_devices (device_address, hub, name, pubkey, is_indirect) VALUES(?,?,?,?,1)", + [correspondent.device_address, correspondent.hub, correspondent.name, correspondent.pubkey], + function(){ + cb(); + } + ); + }, onDone); +} + +function removeCorrespondentDevice(device_address, onDone){ + breadcrumbs.add('correspondent removed: '+device_address); + var arrQueries = []; + db.addQuery(arrQueries, "DELETE FROM outbox WHERE `to`=?", [device_address]); + db.addQuery(arrQueries, "DELETE FROM correspondent_devices WHERE device_address=?", [device_address]); + async.series(arrQueries, onDone); +} + +// ------------------------------- +// witnesses + + +function getWitnessesFromHub(cb){ + console.log('getWitnessesFromHub'); + if (!my_device_hub){ + console.log('getWitnessesFromHub: no hub yet'); + return setTimeout(function(){ + getWitnessesFromHub(cb); + }, 2000); + } + network.findOutboundPeerOrConnect(conf.WS_PROTOCOL+my_device_hub, function(err, ws){ + if (err) + return cb(err); + network.sendRequest(ws, 'get_witnesses', null, false, function(ws, request, response){ + if (response.error) + return cb(response.error); + var arrWitnessesFromHub = response; + cb(null, arrWitnessesFromHub); + }); + }); +} + +// responseHandler(error, response) callback +function requestFromHub(command, params, responseHandler){ + if (!my_device_hub) + return setTimeout(function(){ requestFromHub(command, params, responseHandler); }, 2000); + network.findOutboundPeerOrConnect(conf.WS_PROTOCOL+my_device_hub, function(err, ws){ + if (err) + return responseHandler(err); + network.sendRequest(ws, command, params, false, function(ws, request, response){ + if (response.error) + return responseHandler(response.error); + responseHandler(null, response); + }); + }); +} + + +exports.getMyDevicePubKey = getMyDevicePubKey; +exports.getMyDeviceAddress = getMyDeviceAddress; +exports.uPMyColdDeviceAddress = uPMyColdDeviceAddress; +exports.setMyColdDeviceAddress = setMyColdDeviceAddress; +exports.getMyColdDeviceAddress = getMyColdDeviceAddress; +exports.isValidPubKey = isValidPubKey; + +exports.genPrivKey = genPrivKey; + +exports.setDevicePrivateKey = setDevicePrivateKey; +exports.setTempKeys = setTempKeys; +exports.setDeviceAddress = setDeviceAddress; +exports.setNewDeviceAddress = setNewDeviceAddress; +exports.setDeviceName = setDeviceName; +exports.setDeviceHub = setDeviceHub; + +exports.scheduleTempDeviceKeyRotation = scheduleTempDeviceKeyRotation; + +exports.decryptPackage = decryptPackage; + +exports.handleChallenge = handleChallenge; +exports.loginToHub = loginToHub; + +exports.sendMessageToHub = sendMessageToHub; +exports.sendMessageToDevice = sendMessageToDevice; + +exports.sendPairingMessage = sendPairingMessage; +exports.startWaitingForPairing = startWaitingForPairing; +exports.handlePairingMessage = handlePairingMessage; + +exports.addUnconfirmedCorrespondent = addUnconfirmedCorrespondent; +exports.readCorrespondents = readCorrespondents; +exports.readCorrespondent = readCorrespondent; +exports.readCorrespondentsByDeviceAddresses = readCorrespondentsByDeviceAddresses; +exports.updateCorrespondentProps = updateCorrespondentProps; +exports.removeCorrespondentDevice = removeCorrespondentDevice; +exports.addIndirectCorrespondents = addIndirectCorrespondents; +exports.getWitnessesFromHub = getWitnessesFromHub; +exports.requestFromHub = requestFromHub; diff --git a/lib/common/headers_commission.js b/lib/common/headers_commission.js new file mode 100644 index 0000000..aece8f0 --- /dev/null +++ b/lib/common/headers_commission.js @@ -0,0 +1,11 @@ +/*jslint node: true */ +"use strict"; +var crypto = require('crypto'); +var async = require('async'); + + +function getMaxSpendableMciForLastBallMci(last_ball_mci){ + return last_ball_mci - 1; +} + +exports.getMaxSpendableMciForLastBallMci = getMaxSpendableMciForLastBallMci; diff --git a/lib/common/mc_outputs.js b/lib/common/mc_outputs.js new file mode 100644 index 0000000..3b8989d --- /dev/null +++ b/lib/common/mc_outputs.js @@ -0,0 +1,151 @@ +/*jslint node: true */ +"use strict"; +var _ = require('lodash'); +var async = require('async'); + +var sqlitePool = require('../db/sqlite_pool.js'); +var path = '../offline/initial.trustnote-light.sqlite'; +var db = sqlitePool(path,1, false, function(err){ + console.log('err---------', err ); +}); + + +var conf = {}; + + +// Functions for reading headers commissions and witnessing outputs. +// In all functions below, type=(headers_commission|witnessing) + + +function readNextSpendableMcIndex(conn, type, address, arrConflictingUnits, handleNextSpendableMcIndex){ + conn.query( + "SELECT to_main_chain_index FROM inputs CROSS JOIN units USING(unit) \n\ + WHERE type=? AND address=? AND sequence='good' "+( + (arrConflictingUnits && arrConflictingUnits.length > 0) + ? " AND unit NOT IN("+arrConflictingUnits.map(function(unit){ return db.escape(unit); }).join(", ")+") " + : "" + )+" \n\ + ORDER BY to_main_chain_index DESC LIMIT 1", + [type, address], + function(rows){ + var mci = (rows.length > 0) ? (rows[0].to_main_chain_index+1) : 0; + // readNextUnspentMcIndex(conn, type, address, function(next_unspent_mci){ + // if (next_unspent_mci !== mci) + // throw Error("next unspent mci !== next spendable mci: "+next_unspent_mci+" !== "+mci+", address "+address); + handleNextSpendableMcIndex(mci); + // }); + } + ); +} + +/* +function readNextUnspentMcIndex(conn, type, address, handleNextUnspentMcIndex){ + var table = type + '_outputs'; + conn.query( + "SELECT main_chain_index FROM "+table+" WHERE address=? AND is_spent=1 \n\ + ORDER BY main_chain_index DESC LIMIT 1", [address], + function(rows){ + handleNextUnspentMcIndex((rows.length > 0) ? (rows[0].main_chain_index+1) : 0); + } + ); +} +*/ + +function readMaxSpendableMcIndex(conn, type, handleMaxSpendableMcIndex){ + var table = type + '_outputs'; + conn.query("SELECT MAX(main_chain_index) AS max_mc_index FROM "+table, function(rows){ + var max_mc_index = rows[0].max_mc_index || 0; + handleMaxSpendableMcIndex(max_mc_index); + }); +} + + + +function findMcIndexIntervalToTargetAmount(conn, type, address, max_mci, target_amount, callbacks){ + var table = type + '_outputs'; + readNextSpendableMcIndex(conn, type, address, null, function(from_mci){ + if (from_mci > max_mci) + return callbacks.ifNothing(); + readMaxSpendableMcIndex(conn, type, function(max_spendable_mci){ + if (max_spendable_mci <= 0) + return callbacks.ifNothing(); + if (max_spendable_mci > max_mci) + max_spendable_mci = max_mci; + if (target_amount === Infinity) + target_amount = 1e15; + if (conf.storage === 'mysql'){ + conn.query( + "SELECT main_chain_index, accumulated, has_sufficient \n\ + FROM ( \n\ + SELECT main_chain_index, @sum:=@sum+amount AS accumulated, @has_sufficient:=(@sum>?) AS has_sufficient \n\ + FROM "+table+", (SELECT @sum:=0, @has_sufficient:=0) AS unused \n\ + WHERE is_spent=0 AND address=? AND main_chain_index>=? AND main_chain_index<=? \n\ + ORDER BY main_chain_index \n\ + ) AS t \n\ + WHERE IF(@has_sufficient, has_sufficient, 1) \n\ + ORDER BY IF(@has_sufficient, accumulated, -accumulated) LIMIT 1", + [target_amount, address, from_mci, max_spendable_mci], + function(rows){ + if (rows.length === 0) + return callbacks.ifNothing(); + var bHasSufficient = rows[0].has_sufficient; + var accumulated = rows[0].accumulated; + var to_mci = rows[0].main_chain_index; + callbacks.ifFound(from_mci, to_mci, accumulated, bHasSufficient); + } + ); + } + else{ + var MIN_MC_OUTPUT = (type === 'witnessing') ? 11 : 344; + var max_count_outputs = Math.ceil(target_amount/MIN_MC_OUTPUT); + conn.query( + "SELECT main_chain_index, amount \n\ + FROM "+table+" \n\ + WHERE is_spent=0 AND address=? AND main_chain_index>=? AND main_chain_index<=? \n\ + ORDER BY main_chain_index LIMIT ?", + [address, from_mci, max_spendable_mci, max_count_outputs], + function(rows){ + if (rows.length === 0) + return callbacks.ifNothing(); + var accumulated = 0; + var to_mci; + var bHasSufficient = false; + for (var i=0; i target_amount){ + bHasSufficient = true; + break; + } + } + callbacks.ifFound(from_mci, to_mci, accumulated, bHasSufficient); + } + ); + } + }); + }); +} + +function calcEarnings(conn, type, from_main_chain_index, to_main_chain_index, address, callbacks){ + var table = type + '_outputs'; + conn.query( + "SELECT SUM(amount) AS total \n\ + FROM "+table+" \n\ + WHERE main_chain_index>=? AND main_chain_index<=? AND address=?", + [from_main_chain_index, to_main_chain_index, address], + function(rows){ + var total = rows[0].total; + if (total === null) + total = 0; + if (typeof total !== 'number') + throw Error("mc outputs total is not a number"); + callbacks.ifOk(total); + } + ); +} + + +exports.readNextSpendableMcIndex = readNextSpendableMcIndex; +exports.readMaxSpendableMcIndex = readMaxSpendableMcIndex; +exports.findMcIndexIntervalToTargetAmount = findMcIndexIntervalToTargetAmount; +exports.calcEarnings = calcEarnings; diff --git a/lib/common/paid_witnessing.js b/lib/common/paid_witnessing.js new file mode 100644 index 0000000..b656571 --- /dev/null +++ b/lib/common/paid_witnessing.js @@ -0,0 +1,14 @@ +/*jslint node: true */ +"use strict"; +var _ = require('lodash'); +var async = require('async'); +var constants = require("../config/constants.js"); + +function getMaxSpendableMciForLastBallMci(last_ball_mci){ + return last_ball_mci - 1 - constants.COUNT_MC_BALLS_FOR_PAID_WITNESSING; +} + + +// exports.updatePaidWitnesses = updatePaidWitnesses; +// exports.calcWitnessEarnings = calcWitnessEarnings; +exports.getMaxSpendableMciForLastBallMci = getMaxSpendableMciForLastBallMci; diff --git a/lib/common/validation.js b/lib/common/validation.js new file mode 100644 index 0000000..d0f520d --- /dev/null +++ b/lib/common/validation.js @@ -0,0 +1,224 @@ +/*jslint node: true */ +"use strict"; +var async = require('async'); + + +function validate(objJoint, callbacks) { + + var objUnit = objJoint.unit; + if (typeof objUnit !== "object" || objUnit === null) + throw Error("no unit object"); + if (!objUnit.unit) + throw Error("no unit"); + + console.log("\nvalidating joint identified by unit "+objJoint.unit.unit); + + if (!isStringOfLength(objUnit.unit, constants.HASH_LENGTH)) + return callbacks.ifJointError("wrong unit length"); + + try{ + // UnitError is linked to objUnit.unit, so we need to ensure objUnit.unit is true before we throw any UnitErrors + if (objectHash.getUnitHash(objUnit) !== objUnit.unit) + return callbacks.ifJointError("wrong unit hash: "+objectHash.getUnitHash(objUnit)+" != "+objUnit.unit); + } + catch(e){ + return callbacks.ifJointError("failed to calc unit hash: "+e); + } + + if (objJoint.unsigned){ + if (hasFieldsExcept(objJoint, ["unit", "unsigned"])) + return callbacks.ifJointError("unknown fields in unsigned unit-joint"); + } + else if ("ball" in objJoint){ + if (!isStringOfLength(objJoint.ball, constants.HASH_LENGTH)) + return callbacks.ifJointError("wrong ball length"); + if (hasFieldsExcept(objJoint, ["unit", "ball", "skiplist_units"])) + return callbacks.ifJointError("unknown fields in ball-joint"); + if ("skiplist_units" in objJoint){ + if (!isNonemptyArray(objJoint.skiplist_units)) + return callbacks.ifJointError("missing or empty skiplist array"); + //if (objUnit.unit.charAt(0) !== "0") + // return callbacks.ifJointError("found skiplist while unit doesn't start with 0"); + } + } + else{ + if (hasFieldsExcept(objJoint, ["unit"])) + return callbacks.ifJointError("unknown fields in unit-joint"); + } + + if ("content_hash" in objUnit){ // nonserial and stripped off content + if (!isStringOfLength(objUnit.content_hash, constants.HASH_LENGTH)) + return callbacks.ifUnitError("wrong content_hash length"); + if (hasFieldsExcept(objUnit, ["unit", "version", "alt", "timestamp", "authors", "witness_list_unit", "witnesses", "content_hash", "parent_units", "last_ball", "last_ball_unit"])) + return callbacks.ifUnitError("unknown fields in nonserial unit"); + if (!objJoint.ball) + return callbacks.ifJointError("content_hash allowed only in finished ball"); + } + else{ // serial + if (hasFieldsExcept(objUnit, ["unit", "version", "alt", "timestamp", "authors", "messages", "witness_list_unit", "witnesses", "earned_headers_commission_recipients", "last_ball", "last_ball_unit", "parent_units", "headers_commission", "payload_commission"])) + return callbacks.ifUnitError("unknown fields in unit"); + + if (typeof objUnit.headers_commission !== "number") + return callbacks.ifJointError("no headers_commission"); + if (typeof objUnit.payload_commission !== "number") + return callbacks.ifJointError("no payload_commission"); + + if (!isNonemptyArray(objUnit.messages)) + return callbacks.ifUnitError("missing or empty messages array"); + if (objUnit.messages.length > constants.MAX_MESSAGES_PER_UNIT) + return callbacks.ifUnitError("too many messages"); + + if (objectLength.getHeadersSize(objUnit) !== objUnit.headers_commission) + return callbacks.ifJointError("wrong headers commission, expected "+objectLength.getHeadersSize(objUnit)); + if (objectLength.getTotalPayloadSize(objUnit) !== objUnit.payload_commission) + return callbacks.ifJointError("wrong payload commission, unit "+objUnit.unit+", calculated "+objectLength.getTotalPayloadSize(objUnit)+", expected "+objUnit.payload_commission); + } + + if (!isNonemptyArray(objUnit.authors)) + return callbacks.ifUnitError("missing or empty authors array"); + + + if (objUnit.version !== constants.version) + return callbacks.ifUnitError("wrong version"); + if (objUnit.alt !== constants.alt) + return callbacks.ifUnitError("wrong alt"); + + + if (!storage.isGenesisUnit(objUnit.unit)){ + if (!isNonemptyArray(objUnit.parent_units)) + return callbacks.ifUnitError("missing or empty parent units array"); + + if (!isStringOfLength(objUnit.last_ball, constants.HASH_LENGTH)) + return callbacks.ifUnitError("wrong length of last ball"); + if (!isStringOfLength(objUnit.last_ball_unit, constants.HASH_LENGTH)) + return callbacks.ifUnitError("wrong length of last ball unit"); + } + + + if ("witness_list_unit" in objUnit && "witnesses" in objUnit) + return callbacks.ifUnitError("ambiguous witnesses"); + + var arrAuthorAddresses = objUnit.authors ? objUnit.authors.map(function(author) { return author.address; } ) : []; + + var objValidationState = { + arrAdditionalQueries: [], + arrDoubleSpendInputs: [], + arrInputKeys: [] + }; + if (objJoint.unsigned) + objValidationState.bUnsigned = true; + + if (conf.bLight){ + if (!isPositiveInteger(objUnit.timestamp) && !objJoint.unsigned) + return callbacks.ifJointError("bad timestamp"); + if (objJoint.ball) + return callbacks.ifJointError("I'm light, can't accept stable unit "+objUnit.unit+" without proof"); + return objJoint.unsigned + ? callbacks.ifOkUnsigned(true) + : callbacks.ifOk({sequence: 'good', arrDoubleSpendInputs: [], arrAdditionalQueries: []}, function(){}); + } + else{ + if ("timestamp" in objUnit && !isPositiveInteger(objUnit.timestamp)) + return callbacks.ifJointError("bad timestamp"); + } + + mutex.lock(arrAuthorAddresses, function(unlock){ + + var conn = null; + + async.series( + [ + function(cb){ + db.takeConnectionFromPool(function(new_conn){ + conn = new_conn; + conn.query("BEGIN", function(){cb();}); + }); + }, + function(cb){ + profiler.start(); + checkDuplicate(conn, objUnit.unit, cb); + }, + function(cb){ + profiler.stop('validation-checkDuplicate'); + profiler.start(); + objUnit.content_hash ? cb() : validateHeadersCommissionRecipients(objUnit, cb); + }, + function(cb){ + profiler.stop('validation-hc-recipients'); + profiler.start(); + !objUnit.parent_units + ? cb() + : validateHashTree(conn, objJoint, objValidationState, cb); + }, + function(cb){ + profiler.stop('validation-hash-tree'); + profiler.start(); + !objUnit.parent_units + ? cb() + : validateParents(conn, objJoint, objValidationState, cb); + }, + function(cb){ + profiler.stop('validation-parents'); + profiler.start(); + !objJoint.skiplist_units + ? cb() + : validateSkiplist(conn, objJoint.skiplist_units, cb); + }, + function(cb){ + profiler.stop('validation-skiplist'); + validateWitnesses(conn, objUnit, objValidationState, cb); + }, + function(cb){ + profiler.start(); + validateAuthors(conn, objUnit.authors, objUnit, objValidationState, cb); + }, + function(cb){ + profiler.stop('validation-authors'); + profiler.start(); + objUnit.content_hash ? cb() : validateMessages(conn, objUnit.messages, objUnit, objValidationState, cb); + } + ], + function(err){ + profiler.stop('validation-messages'); + if(err){ + conn.query("ROLLBACK", function(){ + conn.release(); + unlock(); + if (typeof err === "object"){ + if (err.error_code === "unresolved_dependency") + callbacks.ifNeedParentUnits(err.arrMissingUnits); + else if (err.error_code === "need_hash_tree") // need to download hash tree to catch up + callbacks.ifNeedHashTree(); + else if (err.error_code === "invalid_joint") // ball found in hash tree but with another unit + callbacks.ifJointError(err.message); + else if (err.error_code === "transient") + callbacks.ifTransientError(err.message); + else + throw Error("unknown error code"); + } + else + callbacks.ifUnitError(err); + }); + } + else{ + profiler.start(); + conn.query("COMMIT", function(){ + conn.release(); + profiler.stop('validation-commit'); + if (objJoint.unsigned){ + unlock(); + callbacks.ifOkUnsigned(objValidationState.sequence === 'good'); + } + else + callbacks.ifOk(objValidationState, unlock); + }); + } + } + ); // async.series + + }); + +} + + +exports.validate = validate; diff --git a/lib/common/wallet.js b/lib/common/wallet.js new file mode 100644 index 0000000..72ee5ac --- /dev/null +++ b/lib/common/wallet.js @@ -0,0 +1,242 @@ +"use strict"; + +// function composePaymentJoint(arrFromAddresses, arrOutputs, signer, callbacks){ +// composeJoint({paying_addresses: arrFromAddresses, outputs: arrOutputs, signer: signer, callbacks: callbacks}); +// } + //asset, wallet_id, to_address, amount, change_address, [], device_address, signWithLocalPrivateKey, +function sendPaymentFromWallet( + asset, wallet, to_address, amount, change_address, arrSigningDeviceAddresses, recipient_device_address, signWithLocalPrivateKey, handleResult) +{ + sendMultiPayment({ + asset: asset, + wallet: wallet, + to_address: to_address, + amount: amount, + change_address: change_address, + arrSigningDeviceAddresses: arrSigningDeviceAddresses, //[] + recipient_device_address: recipient_device_address,//device_address + signWithLocalPrivateKey: signWithLocalPrivateKey // a function + }, handleResult); +} + +function sendMultiPayment(opts, handleResult) +{ + var asset = opts.asset; + if (asset === 'base') + asset = null; + var wallet = opts.wallet; //wallet_id; + var arrPayingAddresses = opts.paying_addresses; //undefined + var fee_paying_wallet = opts.fee_paying_wallet; //undefined + var arrSigningAddresses = opts.signing_addresses || []; //[] + var to_address = opts.to_address; // 'xxxxxxxxxxxxxx' + var amount = opts.amount; // '10000' + var bSendAll = opts.send_all; // undefined + var change_address = opts.change_address; //'xxxxxxxxxxxxx' + var arrSigningDeviceAddresses = opts.arrSigningDeviceAddresses; // [] + var recipient_device_address = opts.recipient_device_address; // null + var signWithLocalPrivateKey = opts.signWithLocalPrivateKey; // a function + var merkle_proof = opts.merkle_proof; //undefined + + var base_outputs = opts.base_outputs; //undefined + var asset_outputs = opts.asset_outputs; //undefined + var messages = opts.messages; //undefined + + if (!wallet && !arrPayingAddresses) + throw Error("neither wallet id nor paying addresses"); + if (wallet && arrPayingAddresses) + throw Error("both wallet id and paying addresses"); + if ((to_address || amount) && (base_outputs || asset_outputs)) + throw Error('to_address and outputs at the same time'); + if (!asset && asset_outputs) + throw Error('base asset and asset outputs'); + if (amount){ + if (typeof amount !== 'number') + throw Error('amount must be a number'); + if (amount < 0) + throw Error('amount must be positive'); + } + + var estimated_amount = amount; + if (!estimated_amount && asset_outputs) + estimated_amount = asset_outputs.reduce(function(acc, output){ return acc+output.amount; }, 0); + if (estimated_amount && !asset) + estimated_amount += TYPICAL_FEE; + + // readFundedAndSigningAddresses( + // asset, wallet || arrPayingAddresses, estimated_amount, fee_paying_wallet, arrSigningAddresses, arrSigningDeviceAddresses, + // function(arrFundedAddresses, arrBaseFundedAddresses, arrAllSigningAddresses){ + + // if (arrFundedAddresses.length === 0) + // return handleResult("There are no funded addresses"); + // if (asset && arrBaseFundedAddresses.length === 0) + // return handleResult("No notes to pay fees"); + + // var bRequestedConfirmation = false; + // var signer = { + // readSigningPaths: function(conn, address, handleLengthsBySigningPaths){ // returns assoc array signing_path => length + // readFullSigningPaths(conn, address, arrSigningDeviceAddresses, function(assocTypesBySigningPaths){ + // var assocLengthsBySigningPaths = {}; + // for (var signing_path in assocTypesBySigningPaths){ + // var type = assocTypesBySigningPaths[signing_path]; + // if (type === 'key') + // assocLengthsBySigningPaths[signing_path] = constants.SIG_LENGTH; + // else if (type === 'merkle'){ + // if (merkle_proof) + // assocLengthsBySigningPaths[signing_path] = merkle_proof.length; + // } + // else + // throw Error("unknown type "+type+" at "+signing_path); + // } + // handleLengthsBySigningPaths(assocLengthsBySigningPaths); + // }); + // }, + // readDefinition: function(conn, address, handleDefinition){ + // conn.query( + // "SELECT definition FROM my_addresses WHERE address=? UNION SELECT definition FROM shared_addresses WHERE shared_address=?", + // [address, address], + // function(rows){ + // if (rows.length !== 1) + // throw Error("definition not found"); + // handleDefinition(null, JSON.parse(rows[0].definition)); + // } + // ); + // }, + // sign: function(objUnsignedUnit, assocPrivatePayloads, address, signing_path, handleSignature){ + // var buf_to_sign = objectHash.getUnitHashToSign(objUnsignedUnit); + // findAddress(address, signing_path, { + // ifError: function(err){ + // throw Error(err); + // }, + // ifUnknownAddress: function(err){ + // throw Error("unknown address "+address+" at "+signing_path); + // }, + // ifLocal: function(objAddress){ + // signWithLocalPrivateKey(objAddress.wallet, objAddress.account, objAddress.is_change, objAddress.address_index, buf_to_sign, function(sig){ + // handleSignature(null, sig); + // }); + // }, + // ifRemote: function(device_address){ + // // we'll receive this event after the peer signs + // eventBus.once("signature-"+device_address+"-"+address+"-"+signing_path+"-"+buf_to_sign.toString("base64"), function(sig){ + // handleSignature(null, sig); + // if (sig === '[refused]') + // eventBus.emit('refused_to_sign', device_address); + // }); + // walletGeneral.sendOfferToSign(device_address, address, signing_path, objUnsignedUnit, assocPrivatePayloads); + // if (!bRequestedConfirmation){ + // eventBus.emit("confirm_on_other_devices"); + // bRequestedConfirmation = true; + // } + // }, + // ifMerkle: function(bLocal){ + // if (!bLocal) + // throw Error("merkle proof at path "+signing_path+" should be provided by another device"); + // if (!merkle_proof) + // throw Error("merkle proof at path "+signing_path+" not provided"); + // handleSignature(null, merkle_proof); + // } + // }); + // } + // }; + + // var params = { + // available_paying_addresses: arrFundedAddresses, // forces 'minimal' for payments from shared addresses too, it doesn't hurt + // signing_addresses: arrAllSigningAddresses, + // messages: messages, + // signer: signer, + // callbacks: { + // ifNotEnoughFunds: function(err){ + // handleResult(err); + // }, + // ifError: function(err){ + // handleResult(err); + // }, + // // for asset payments, 2nd argument is array of chains of private elements + // // for base asset, 2nd argument is assocPrivatePayloads which is null + // ifOk: function(objJoint, arrChainsOfRecipientPrivateElements, arrChainsOfCosignerPrivateElements){ + // network.broadcastJoint(objJoint); + // if (!arrChainsOfRecipientPrivateElements && recipient_device_address) // send notification about public payment + // walletGeneral.sendPaymentNotification(recipient_device_address, objJoint.unit.unit); + // handleResult(null, objJoint.unit.unit); + // } + // } + // }; + + // if (asset){ + // if (bSendAll) + // throw Error('send_all with asset'); + // params.asset = asset; + // params.available_fee_paying_addresses = arrBaseFundedAddresses; + // if (to_address){ + // params.to_address = to_address; + // params.amount = amount; // in asset units + // } + // else{ + // params.asset_outputs = asset_outputs; + // params.base_outputs = base_outputs; // only destinations, without the change + // } + // params.change_address = change_address; + // storage.readAsset(db, asset, null, function(err, objAsset){ + // if (err) + // throw Error(err); + // // if (objAsset.is_private && !recipient_device_address) + // // return handleResult("for private asset, need recipient's device address to send private payload to"); + // if (objAsset.is_private){ + // // save messages in outbox before committing + // params.callbacks.preCommitCb = function(conn, arrChainsOfRecipientPrivateElements, arrChainsOfCosignerPrivateElements, cb){ + // if (!arrChainsOfRecipientPrivateElements || !arrChainsOfCosignerPrivateElements) + // throw Error('no private elements'); + // var sendToRecipients = function(cb2){ + // if (recipient_device_address) + // walletGeneral.sendPrivatePayments(recipient_device_address, arrChainsOfRecipientPrivateElements, false, conn, cb2); + // else // paying to another wallet on the same device + // forwardPrivateChainsToOtherMembersOfOutputAddresses(arrChainsOfRecipientPrivateElements, conn, cb2); + // }; + // var sendToCosigners = function(cb2){ + // if (wallet) + // walletDefinedByKeys.forwardPrivateChainsToOtherMembersOfWallets(arrChainsOfCosignerPrivateElements, [wallet], conn, cb2); + // else // arrPayingAddresses can be only shared addresses + // walletDefinedByAddresses.forwardPrivateChainsToOtherMembersOfAddresses(arrChainsOfCosignerPrivateElements, arrPayingAddresses, conn, cb2); + // }; + // async.series([sendToRecipients, sendToCosigners], cb); + // }; + // } + // if (objAsset.fixed_denominations){ // indivisible + // params.tolerance_plus = 0; + // params.tolerance_minus = 0; + // indivisibleAsset.composeAndSaveMinimalIndivisibleAssetPaymentJoint(params); + // } + // else{ // divisible + // divisibleAsset.composeAndSaveMinimalDivisibleAssetPaymentJoint(params); + // } + // }); + // } + // else{ // base asset + // if (bSendAll){ + // params.send_all = bSendAll; + // params.outputs = [{address: to_address, amount: 0}]; + // } + // else{ + // params.outputs = to_address ? [{address: to_address, amount: amount}] : (base_outputs || []); + // if(opts.candyOutput && opts.candyOutput.length > 1) { + // params.outputs = opts.candyOutput; + // } + // params.outputs.push({address: change_address, amount: 0}); + // } + // composer.composeAndSaveMinimalJoint(params); + // } + + // } + // ); +} + + +/* +walletGeneral.readMyAddresses(function(arrAddresses){ + network.setWatchedAddresses(arrAddresses); +}) +*/ + + +exports.sendPaymentFromWallet = sendPaymentFromWallet; +exports.sendMultiPayment = sendMultiPayment; \ No newline at end of file diff --git a/lib/config/constants.js b/lib/config/constants.js index 958dc78..38e260a 100644 --- a/lib/config/constants.js +++ b/lib/config/constants.js @@ -1,15 +1,26 @@ "use strict"; -exports.COUNT_WITNESSES = 12; - -/* +exports.COUNT_WITNESSES = 12; exports.MAX_WITNESS_LIST_MUTATIONS = 1; exports.TOTAL_WHITEBYTES = 5e14; exports.MAJORITY_OF_WITNESSES = (exports.COUNT_WITNESSES%2===0) ? (exports.COUNT_WITNESSES/2+1) : Math.ceil(exports.COUNT_WITNESSES/2); exports.COUNT_MC_BALLS_FOR_PAID_WITNESSING = 100; + + +exports.SIG_LENGTH = 88; exports.version = '1.0'; + + + + + + + + + +/* exports.alt = '1'; exports.GENESIS_UNIT = 'rg1RzwKwnfRHjBojGol3gZaC5w7kR++rOR6O61JRsrQ='; @@ -34,4 +45,7 @@ exports.MAX_DATA_FEED_VALUE_LENGTH = 64; exports.MAX_AUTHENTIFIER_LENGTH = 4096; exports.MAX_CAP = 9e15; exports.MAX_COMPLEXITY = 100; -*/ \ No newline at end of file +*/ + + + diff --git a/lib/db/save_joint_helper.js b/lib/db/save_joint_helper.js index 6f41ef5..0723675 100644 --- a/lib/db/save_joint_helper.js +++ b/lib/db/save_joint_helper.js @@ -2,7 +2,7 @@ const constants = require('../config/constants'); var storage = require('./storage'); -var mutex = require('./mutex'); +var mutex = require('../tools/mutex'); var async = require('async'); var objectHash = require('../crypto/object_hash'); var Definition = {}; diff --git a/lib/db/storage.js b/lib/db/storage.js index 6905ec1..e12b735 100644 --- a/lib/db/storage.js +++ b/lib/db/storage.js @@ -8,4 +8,36 @@ function isGenesisUnit(unit){ return (unit === constants.GENESIS_UNIT); } + +function determineIfWitnessAddressDefinitionsHaveReferences(conn, arrWitnesses, handleResult){ + conn.query( + "SELECT 1 FROM address_definition_changes JOIN definitions USING(definition_chash) \n\ + WHERE address IN(?) AND has_references=1 \n\ + UNION \n\ + SELECT 1 FROM definitions WHERE definition_chash IN(?) AND has_references=1 \n\ + LIMIT 1", + [arrWitnesses, arrWitnesses], + function(rows){ + handleResult(rows.length > 0); + } + ); +} + +function findWitnessListUnit(conn, arrWitnesses, last_ball_mci, handleWitnessListUnit){ + conn.query( + "SELECT witness_list_hashes.witness_list_unit \n\ + FROM witness_list_hashes CROSS JOIN units ON witness_list_hashes.witness_list_unit=unit \n\ + WHERE witness_list_hash=? AND sequence='good' AND is_stable=1 AND main_chain_index<=?", + [objectHash.getBase64Hash(arrWitnesses), last_ball_mci], + function(rows){ + handleWitnessListUnit((rows.length === 0) ? null : rows[0].witness_list_unit); + } + ); +} + + + + +exports.findWitnessListUnit = findWitnessListUnit; +exports.determineIfWitnessAddressDefinitionsHaveReferences = determineIfWitnessAddressDefinitionsHaveReferences; exports.isGenesisUnit = isGenesisUnit; diff --git a/lib/headless/headlessWallet.js b/lib/headless/headlessWallet.js new file mode 100644 index 0000000..1118b05 --- /dev/null +++ b/lib/headless/headlessWallet.js @@ -0,0 +1,42 @@ +'use strict' + +var constants = require('../config/constants'); + +var signer = { + readSigningPaths: function(conn, address, handleLengthsBySigningPaths){ + handleLengthsBySigningPaths({r: constants.SIG_LENGTH}); + }, + readDefinition: function(conn, address, handleDefinition){ + // conn.query("SELECT definition FROM my_addresses WHERE address=?", [address], function(rows){ + // if (rows.length !== 1) + // throw "definition not found"; + // handleDefinition(null, JSON.parse(rows[0].definition)); + // }); + }, + sign: function(objUnsignedUnit, assocPrivatePayloads, address, signing_path, handleSignature){ + // var buf_to_sign = objectHash.getUnitHashToSign(objUnsignedUnit); + // db.query( + // "SELECT wallet, account, is_change, address_index \n\ + // FROM my_addresses JOIN wallets USING(wallet) JOIN wallet_signing_paths USING(wallet) \n\ + // WHERE address=? AND signing_path=?", + // [address, signing_path], + // function(rows){ + // if (rows.length !== 1) + // throw Error(rows.length+" indexes for address "+address+" and signing path "+signing_path); + // var row = rows[0]; + // signWithLocalPrivateKey(row.wallet, row.account, row.is_change, row.address_index, buf_to_sign, function(sig){ + // handleSignature(null, sig); + // }); + // } + // ); + } +}; + + +exports.signer = signer; + + + + + + diff --git a/lib/headless/temp.js b/lib/headless/temp.js new file mode 100644 index 0000000..1476b01 --- /dev/null +++ b/lib/headless/temp.js @@ -0,0 +1,83 @@ + +/* + +async.series([ +// function(callback) { +// // do some stuff ... +// console.log('+++++++++++++++++++++one++++++++'); +// // callback('adsfdsafsdfsd', 'one'); +// // throw new Error("more"); +// }, +// function(callback) { +// console.log('+++++++++++++++++++++two++++++++'); +// // do some more stuff ... +// callback(null, 'two'); +// } +// ], +// // optional callback +// function(err, results) { +// // results is now equal to ['one', 'two'] +// console.log('-------------err', err); +// console.log('++++++++++++++results:',results); +// }); + +*/ + +/* +let asset = null; +let amount = 1000; +let to_address = ''; +let change_address = ''; +let device_address = null; + +function onDone (err, unit) { + console.log('err: ',err, '\nunit: ', unit); +} + +sendPayment(asset, amount, to_address, change_address, device_address, onDone); + +// headlessWallet.issueChangeAddressAndSendPayment(null, amount, toAddress, null, function(err, unit) { + +function signWithLocalPrivateKey(wallet_id, account, is_change, address_index, text_to_sign, handleSig){ + var path = "m/44'/0'/" + account + "'/"+is_change+"/"+address_index; + var privateKey = xPrivKey.derive(path).privateKey; + var privKeyBuf = privateKey.bn.toBuffer({size:32}); // https://github.com/bitpay/bitcore-lib/issues/47 + handleSig(ecdsaSig.sign(text_to_sign, privKeyBuf)); +} + +let wallet_id = ''; + +function sendPayment(asset, amount, to_address, change_address, device_address, onDone){ + // var device = require('./common/device.js'); + var Wallet = require('./common/wallet.js'); + Wallet.sendPaymentFromWallet( + asset, wallet_id, to_address, amount, change_address, + [], device_address, + signWithLocalPrivateKey, + function(err, unit){ + console.log('wallet sendPayment successfully !') + console.log('err---------',err); + console.log('unit--------',unit); + // if (device_address) { + // if (err) + // device.sendMessageToDevice(device_address, 'text', "Failed to pay: " + err); + // else + // // if successful, the peer will also receive a payment notification + // device.sendMessageToDevice(device_address, 'text', "paid"); + // } + // if (onDone) + // onDone(err, unit); + } + ); +} + + + + +// function signWithLocalPrivateKey(wallet_id, account, is_change, address_index, text_to_sign, handleSig){ +// var path = "m/44'/0'/" + account + "'/"+is_change+"/"+address_index; +// var privateKey = xPrivKey.derive(path).privateKey; +// var privKeyBuf = privateKey.bn.toBuffer({size:32}); // https://github.com/bitpay/bitcore-lib/issues/47 +// handleSig(ecdsaSig.sign(text_to_sign, privKeyBuf)); +// } +*/ diff --git a/lib/offline/initial.trustnote-light.sqlite b/lib/offline/initial.trustnote-light.sqlite index 6a661ce003a5bb164f7f6b44928cd5bffed87d40..f17399ac7e8f565db6749912de551210dfeff825 100644 GIT binary patch delta 4164 zcmb_fU2NOd6~5$=9Wim51x*JBNwz^vu>wvUiNBUH7Z86WkrYLWqDV?(l$>SmWRD=1Ga}gv;~S`msWzf zo?Avk^+xgB?|$d|&N=t;`quI5TgSJ~5YPPhtTgfrnk4+U>pzE40%Qd8?UUov(7%x; zo(-61PXqxX$#@(PLez6eSn4dTEf=_})mGpbdeH50O=j*|kDkAxmFLqbGg(^hT=h4k z(YD9TIywK?ZsZ)*pb4KZ5-f!KZ`WMgb@+#i7xUNgM$Ag1;YQe}do>jQuZ&ABjX^H`%>2yWpQKZAwK1O&k_Doc5}#d(HzsiZN5<&8OcsafReX?1xb#aK>3<~KWZ zzMBjby*e{U<;i9*$AW+w24EOshq*M&`r~`$w~b&MD{~Y2^Tdd8<3!x1C5_=2$;vu> zO=JW*&(Lg!bE{^~a!p;moLF62NL=_{Vr6Zv8Eht|6PeDUYfjDP8 zv9prI_P)E3v^)Ni*V4_^)m)HEeE*dJ?}rE;VggTox4$v|9!x$D1{K9%K>QAZJDB+I zK;#K9n5aej`&cxAc%DxXqk(ba#>`i#XqV47ma=Un-4wc+ z^)g@YnU`i;YgToF4K~se*Ict!+O=zqN+aE@r#*97Yor=!m(KRohvtc z8K;fKoTL`u=uOC9c zO;ADn%;^AE%rL^P%x*3R?cP$mwbTxTiK|wH({ycK?XDE1#YJ8ylv8<2tMc`^x)5-s zpy$~IuH9-r(&c>w@9*{Z?XQpj9#Y9z5FbjLox8DZoJI>l3#T!MtK%f`wICix6m>3$ z6Cw0ao!9HmwpZtauA^A>R%e0R$ZU$4joETKQ0p!e$jzLOK5E_W2J!hxk$jr^3yZZMqiiRkgguF}x@7=+4VT2#(9f!1u#L!Si2u zJ>)&F%2f((K{50ys|kwBX-tx3bn-B?qkDLGV|22fzL$crV0^I1!~5c^*emQCSbb zKgRb)_!xu#6JNCe5oCCG+}$Q(56+H}7Ct-RH}F$ffdae;G4gNZZ^>=4M_PD6lI$nO zNW)hRftT%KrI__B-%`0u&XlS}Bg3esUl82HF&x9oUag{dtSV-0K~Py<%IJ>f>Z~kU zW?o4g#WA!Xd343AaOIrBvqC}V%A%uMrm2>MoaQ7b(!h^?iIp;@kYgp*>eCi$DMeZ@ z>8w!|7*9^bNyAq2HCxE!EmyC3jw5?2UvYFvCw+mbuHqN`oSh&@ zgDz*BTusi(ezB<8ZjGZ&R^#2O;}|Bb)a=mpF%q}!L-;lP1ePHW&y)Wl|3dzXe2r|7 zVJp8t_RoVsL>W7_7Zl;_vAv)OYmbIO`@QXBbN|O%DvXLUHy#E>xqBoG`Z9a_p}(Vh zQ4#)*gh5dT$HJf}han7lWbsQ+JU(DC8II;Ck9Rl7*sJ)^eHX#I*w442B*%>CD|ys8 zaoE71!hu0iyK&e%LiT;T!E;pjeY`Usi(@MH5!}aA?!e^55#t<+Jd+-udA~Rd7yb(z Cn`aXM delta 165 zcmZpez}ql^cY-t{_e2?IR&EB}k2^M|EMVTua^oNSW+omcmc=X%KrF(tndQPC{zX@K zHZ5ThSj^%gv6*GXU*YYH7L3e5ZUazy!C&Fcj5C;ZwzIG>f%KkXngP*c!!j{|WjRX) zBg;}2MxNy?9gINLV9g5{nJ;W-5n%kszMZLpiKzp|=3r)G+0G)v_*Z;8qYCqXWdPy; BHHQEI diff --git a/lib/test/create_payment_test.js b/lib/test/create_payment_test.js new file mode 100644 index 0000000..fe0804f --- /dev/null +++ b/lib/test/create_payment_test.js @@ -0,0 +1,75 @@ +"use strict"; + +var headlessWallet = require('../headless/headlessWallet.js'); +var composer = require('../common/composer.js'); + + +function onError(err){ + throw Error(err); +} + +function createPayment(){ + + // var callbacks = composer.getSavingCallbacks({ + // ifNotEnoughFunds: onError, + // ifError: onError, + // ifOk: function(objJoint){ + // network.broadcastJoint(objJoint); + // } + // }); + + var callbacks = function () { + console.log('------------------------------------'); + } + + var from_address = '6WWY5ULVLEMKY4J7EV5JTU2UER4GNZDO'; + + var outputs = [ + {address: from_address, amount: 0}, // the change + {address: 'EEQS6IBV7SCLB6ROROVP2WYJHP536WC6', amount: 8} // the receiver + ]; + + var witnesses = + ['2SATGZDFDXNNJRVZ52O4J6VYTTMO2EZR', + '33RVJX3WBNZXJOSFCU6KK7O7TVEXLXGR', + 'FYQXBPQWBPXWMJGCHWJ52AK2QMEOICR5', + 'J3XIKRBU4BV2PX2BP4PSGIXDVND2XRIF', + 'K5JWBZBADITKZAZDTHAPCU5FLYVSM752', + 'KM5FZPIP264YRRWRQPXF7F7Y6ETDEW5Y', + 'NBEFJ3LKG2SBSBK7D7GCFREOAFMS7QTQ', + 'RIHZR7AHPVKZWTTDWI6UTKC7L73BJJQW', + 'TIPXQ4CAO7G4C4P2P4PEN2KQK4MY73WD', + 'X27CW2UWU5SGE647LK5SBTIPOOIQ7GJT', + 'X6DWZUEW4IBFR77I46CAKTJVK4DBPOHE', + 'XIM76DRNUNFWPXPI5AGOCYNMA3IOXL7V'] + + var lightProps = + { + "parent_units": [ + "QQgPbBDvhM83hopAstmIbOw16MOKxPTBtpprUPEr0AQ=", + "z8MbR1cbBFjTUmHQuUSMOO4dwEWxd+/Dm6Ds6FeHdb4=" + ], + "last_stable_mc_ball": "jEDL9JXuRdleroHRY7l5jSlf+b1hMZr1PTq6CPWox4w=", + "last_stable_mc_ball_unit": "NIoTU+pwysAW0cpeYWN80Mma9V0cNJaYf46qLzk1MAU=", + "last_stable_mc_ball_mci": 266423, + "witness_list_unit": "rg1RzwKwnfRHjBojGol3gZaC5w7kR++rOR6O61JRsrQ=" + } + + let params = {}; + params.paying_addresses = [ from_address ]; + params.outputs = outputs; + params.witnesses = witnesses; + params.signer = headlessWallet.signer; + params.lightProps = lightProps; + + params.callbacks = callbacks; + + composer.composeJoint(params); +} + + + +createPayment(); + + + diff --git a/lib/test/network_manager_test.js b/lib/test/network_manager_test.js index d0e9eb8..268f7e5 100644 --- a/lib/test/network_manager_test.js +++ b/lib/test/network_manager_test.js @@ -24,6 +24,7 @@ async function test (){ await db.insertWitnesses(ret1.response); let arrWitnesses = await db.readMyWitnesses(); + // let arrWitnesses = ret1.response; console.log('arrWitnesses: ', arrWitnesses); @@ -37,7 +38,7 @@ async function test (){ console.log('hub/temp_pubkey: ',ret3.response) - let addresses = ['R2CJ353CPFT6ZB372H324A5VYOGPVOKI'] + let addresses = ['6WWY5ULVLEMKY4J7EV5JTU2UER4GNZDO'] // 'QWVH56KBONKG7BIOZ7LJMYLLQJHQAWWU']; let known_stable_units = ['33cs5A6tioSR94Pw6EAi0eRwdqA9XFnC5ZdCfyngAI0='] @@ -59,4 +60,4 @@ async function test (){ console.log('light/get_parents_and_last_ball_and_witness_list_unit-------',JSON.stringify(ret5.response, null, 3)); } -test (); +// test (); diff --git a/lib/db/mutex.js b/lib/tools/mutex.js similarity index 100% rename from lib/db/mutex.js rename to lib/tools/mutex.js