From 81b4656b8bc6e91d283b81a5048cde2958d17cf9 Mon Sep 17 00:00:00 2001 From: Michael Muck Date: Wed, 11 Oct 2017 17:19:21 +0100 Subject: [PATCH 1/2] Refactoring to be able to programmatically create a full headless wallet configuration --- conf.js | 3 + start.js | 202 ++++++++++++++++++++++++++++++++++--------------------- 2 files changed, 130 insertions(+), 75 deletions(-) diff --git a/conf.js b/conf.js index ab2195a..6e96718 100644 --- a/conf.js +++ b/conf.js @@ -20,6 +20,9 @@ exports.KEYS_FILENAME = 'keys.json'; // where logs are written to (absolute path). Default is log.txt in app data directory //exports.LOG_FILENAME = '/dev/null'; +// auto start headless wallet +exports.bAutoStart = true; + // consolidate unspent outputs when there are too many of them. Value of 0 means do not try to consolidate exports.MAX_UNSPENT_OUTPUTS = 0; exports.CONSOLIDATION_INTERVAL = 3600*1000; diff --git a/start.js b/start.js index 3c35bad..dca5eb4 100644 --- a/start.js +++ b/start.js @@ -15,7 +15,8 @@ var Bitcore = require('bitcore-lib'); var readline = require('readline'); var appDataDir = desktopApp.getAppDataDir(); -var KEYS_FILENAME = appDataDir + '/' + (conf.KEYS_FILENAME || 'keys.json'); +var KEYS_FILENAME = (conf.KEYS_FILENAME || 'keys.json'); +var CONF_FILENAME = 'conf.json'; var wallet_id; var xPrivKey; @@ -33,6 +34,30 @@ function replaceConsoleLog(){ console.info = console.log; } +function writeUserConfFile(deviceName, onDone) { + var userConfFile = appDataDir + '/' + CONF_FILENAME; + fs.writeFile(userConfFile, JSON.stringify({deviceName: deviceName}, null, '\t'), 'utf8', function(err){ + onDone(userConfFile, err); + }); +} + +function writeKeysAndCreateWallet(passphrase, onDone) { + var deviceTempPrivKey = crypto.randomBytes(32); + var devicePrevTempPrivKey = crypto.randomBytes(32); + + var mnemonic = new Mnemonic(); // generates new mnemonic + while (!Mnemonic.isValid(mnemonic.toString())) + mnemonic = new Mnemonic(); + + writeKeys(mnemonic.phrase, deviceTempPrivKey, devicePrevTempPrivKey, function(){ + console.log('keys created'); + var xPrivKey = mnemonic.toHDPrivateKey(passphrase); + createWallet(xPrivKey, function(){ + onDone(mnemonic.phrase, passphrase, deviceTempPrivKey, devicePrevTempPrivKey); + }); + }); +} + function readKeys(onDone){ console.log('-----------------------'); if (conf.control_addresses) @@ -40,45 +65,33 @@ function readKeys(onDone){ if (conf.payout_address) console.log("payouts allowed to address: "+conf.payout_address); console.log('-----------------------'); - fs.readFile(KEYS_FILENAME, 'utf8', function(err, data){ + var keysConfFile = appDataDir + "/" + KEYS_FILENAME; + fs.readFile(keysConfFile, 'utf8', function(err, data){ var rl = readline.createInterface({ input: process.stdin, output: process.stdout, //terminal: true }); if (err){ // first start - console.log('failed to read keys, will gen'); + console.log('failed to read keys from ' + keysConfFile + ', will gen'); var suggestedDeviceName = require('os').hostname() || 'Headless'; rl.question("Please name this device ["+suggestedDeviceName+"]: ", function(deviceName){ if (!deviceName) deviceName = suggestedDeviceName; - var userConfFile = appDataDir + '/conf.json'; - fs.writeFile(userConfFile, JSON.stringify({deviceName: deviceName}, null, '\t'), 'utf8', function(err){ + writeUserConfFile(deviceName, function(userConfFile, err){ if (err) - throw Error('failed to write conf.json: '+err); + throw Error('failed to write to ' + userConfFile + ' - error: ' + err); rl.question( - 'Device name saved to '+userConfFile+', you can edit it later if you like.\n\nPassphrase for your private keys: ', + 'Device name saved to ' + userConfFile + ', you can edit it later if you like.\n\nPassphrase for your private keys: ', function(passphrase){ rl.close(); if (process.stdout.moveCursor) process.stdout.moveCursor(0, -1); if (process.stdout.clearLine) process.stdout.clearLine(); - var deviceTempPrivKey = crypto.randomBytes(32); - var devicePrevTempPrivKey = crypto.randomBytes(32); - - var mnemonic = new Mnemonic(); // generates new mnemonic - while (!Mnemonic.isValid(mnemonic.toString())) - mnemonic = new Mnemonic(); - - writeKeys(mnemonic.phrase, deviceTempPrivKey, devicePrevTempPrivKey, function(){ - console.log('keys created'); - var xPrivKey = mnemonic.toHDPrivateKey(passphrase); - createWallet(xPrivKey, function(){ - onDone(mnemonic.phrase, passphrase, deviceTempPrivKey, devicePrevTempPrivKey); - }); - }); + writeKeysAndCreateWallet(passphrase, onDone); } ); - }); + }) + }); } else{ // 2nd or later start @@ -111,9 +124,10 @@ function writeKeys(mnemonic_phrase, deviceTempPrivKey, devicePrevTempPrivKey, on temp_priv_key: deviceTempPrivKey.toString('base64'), prev_temp_priv_key: devicePrevTempPrivKey.toString('base64') }; - fs.writeFile(KEYS_FILENAME, JSON.stringify(keys, null, '\t'), 'utf8', function(err){ + var keysConfFile = appDataDir + "/" + KEYS_FILENAME; + fs.writeFile(keysConfFile, JSON.stringify(keys, null, '\t'), 'utf8', function(err){ if (err) - throw Error("failed to write keys file"); + throw Error("failed to write keys file: " + keysConfFile + " - error: " + err); if (onDone) onDone(); }); @@ -146,6 +160,16 @@ function readSingleAddress(handleAddress){ }); } +function readWalletAdressAndDefinition(handleAddress){ + db.query("SELECT address, definition FROM my_addresses WHERE wallet=?", [wallet_id], function(rows){ + if (rows.length === 0) + throw Error("no addresses"); + if (rows.length > 1) + throw Error("more than 1 address"); + handleAddress(rows[0].address, JSON.parse(rows[0].definition)); + }); +} + function prepareBalanceText(handleBalanceText){ var Wallet = require('byteballcore/wallet.js'); Wallet.readBalance(wallet_id, function(assocBalances){ @@ -224,60 +248,87 @@ if (conf.permanent_pairing_secret) [conf.permanent_pairing_secret] ); -setTimeout(function(){ - readKeys(function(mnemonic_phrase, passphrase, deviceTempPrivKey, devicePrevTempPrivKey){ - var saveTempKeys = function(new_temp_key, new_prev_temp_key, onDone){ - writeKeys(mnemonic_phrase, new_temp_key, new_prev_temp_key, onDone); - }; - var mnemonic = new Mnemonic(mnemonic_phrase); - // global - xPrivKey = mnemonic.toHDPrivateKey(passphrase); - var devicePrivKey = xPrivKey.derive("m/1'").privateKey.bn.toBuffer({size:32}); - // read the id of the only wallet - readSingleWallet(function(wallet){ - // global - wallet_id = wallet; - var device = require('byteballcore/device.js'); - device.setDevicePrivateKey(devicePrivKey); - let my_device_address = device.getMyDeviceAddress(); - db.query("SELECT 1 FROM extended_pubkeys WHERE device_address=?", [my_device_address], function(rows){ - if (rows.length > 1) - throw Error("more than 1 extended_pubkey?"); - if (rows.length === 0) - return setTimeout(function(){ - console.log('passphrase is incorrect'); - process.exit(0); - }, 1000); - require('byteballcore/wallet.js'); // we don't need any of its functions but it listens for hub/* messages - device.setTempKeys(deviceTempPrivKey, devicePrevTempPrivKey, saveTempKeys); - device.setDeviceName(conf.deviceName); - device.setDeviceHub(conf.hub); - let my_device_pubkey = device.getMyDevicePubKey(); - console.log("====== my device address: "+my_device_address); - console.log("====== my device pubkey: "+my_device_pubkey); - if (conf.permanent_pairing_secret) - console.log("====== my pairing code: "+my_device_pubkey+"@"+conf.hub+"#"+conf.permanent_pairing_secret); - if (conf.bLight){ - var light_wallet = require('byteballcore/light_wallet.js'); - light_wallet.setLightVendorHost(conf.hub); - } - eventBus.emit('headless_wallet_ready'); - setTimeout(replaceConsoleLog, 1000); - if (conf.MAX_UNSPENT_OUTPUTS && conf.CONSOLIDATION_INTERVAL){ - var consolidation = require('./consolidation.js'); - var network = require('byteballcore/network.js'); - function consolidate(){ - if (!network.isCatchingUp()) - consolidation.consolidate(wallet_id, signer); - } - setInterval(consolidate, conf.CONSOLIDATION_INTERVAL); - setTimeout(consolidate, 300*1000); - } +function generateHeadlessWalletConfig(deviceName, passphrase, onDone) { + setTimeout(function(){ + console.log("generating headless wallet configuration ..."); + writeUserConfFile(deviceName, function(userConfFile, err){ + if (err) + throw Error('failed to write to ' + userConfFile + ' - error: ' + err); + + writeKeysAndCreateWallet(passphrase, function(mnemonic_phrase, passphrase, deviceTempPrivKey, devicePrevTempPrivKey){ + initializeDeviceAndWallet(mnemonic_phrase, passphrase, deviceTempPrivKey, devicePrevTempPrivKey, function(){ + readWalletAdressAndDefinition(function(address, definition){ + onDone(mnemonic_phrase, passphrase, definition, address, appDataDir); + }); + }); }); + }) + }, 1000); +} + +function initializeDeviceAndWallet(mnemonic_phrase, passphrase, deviceTempPrivKey, devicePrevTempPrivKey, onInitialized){ + var saveTempKeys = function(new_temp_key, new_prev_temp_key, onDone){ + writeKeys(mnemonic_phrase, new_temp_key, new_prev_temp_key, onDone); + }; + var mnemonic = new Mnemonic(mnemonic_phrase); + // global + xPrivKey = mnemonic.toHDPrivateKey(passphrase); + var devicePrivKey = xPrivKey.derive("m/1'").privateKey.bn.toBuffer({size:32}); + // read the id of the only wallet + readSingleWallet(function(wallet){ + // global + wallet_id = wallet; + var device = require('byteballcore/device.js'); + device.setDevicePrivateKey(devicePrivKey); + let my_device_address = device.getMyDeviceAddress(); + db.query("SELECT 1 FROM extended_pubkeys WHERE device_address=?", [my_device_address], function(rows){ + if (rows.length > 1) + throw Error("more than 1 extended_pubkey?"); + if (rows.length === 0) + return setTimeout(function(){ + console.log('passphrase is incorrect'); + process.exit(0); + }, 1000); + require('byteballcore/wallet.js'); // we don't need any of its functions but it listens for hub/* messages + device.setTempKeys(deviceTempPrivKey, devicePrevTempPrivKey, saveTempKeys); + device.setDeviceName(conf.deviceName); + device.setDeviceHub(conf.hub); + let my_device_pubkey = device.getMyDevicePubKey(); + console.log("====== my device address: "+my_device_address); + console.log("====== my device pubkey: "+my_device_pubkey); + if (conf.permanent_pairing_secret) + console.log("====== my pairing code: "+my_device_pubkey+"@"+conf.hub+"#"+conf.permanent_pairing_secret); + if (conf.bLight){ + var light_wallet = require('byteballcore/light_wallet.js'); + light_wallet.setLightVendorHost(conf.hub); + } + if(onInitialized) + onInitialized(); }); - }); -}, 1000); + }); +} +if (conf.bAutoStart){ + setTimeout(function(){ + readKeys(function(mnemonic_phrase, passphrase, deviceTempPrivKey, devicePrevTempPrivKey){ + initializeDeviceAndWallet(mnemonic_phrase, passphrase, deviceTempPrivKey, devicePrevTempPrivKey, + function(){ + eventBus.emit('headless_wallet_ready'); + setTimeout(replaceConsoleLog, 1000); + if (conf.MAX_UNSPENT_OUTPUTS && conf.CONSOLIDATION_INTERVAL){ + var consolidation = require('./consolidation.js'); + var network = require('byteballcore/network.js'); + function consolidate(){ + if (!network.isCatchingUp()) + consolidation.consolidate(wallet_id, signer); + } + setInterval(consolidate, conf.CONSOLIDATION_INTERVAL); + setTimeout(consolidate, 300*1000); + } + }) + }); + }, 1000); +} function handlePairing(from_address){ var device = require('byteballcore/device.js'); @@ -527,6 +578,7 @@ exports.handlePairing = handlePairing; exports.handleText = handleText; exports.sendAllBytesFromAddress = sendAllBytesFromAddress; exports.sendAssetFromAddress = sendAssetFromAddress; +exports.generateHeadlessWalletConfig = generateHeadlessWalletConfig; if (require.main === module) setupChatEventHandlers(); From a8c18e98620a5c618967e88f7358b7cc25deafb2 Mon Sep 17 00:00:00 2001 From: Michael Muck Date: Thu, 12 Oct 2017 18:42:45 +0100 Subject: [PATCH 2/2] Added readme section --- README.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/README.md b/README.md index febf976..7a8870e 100644 --- a/README.md +++ b/README.md @@ -63,3 +63,33 @@ Payments are the central but not the only type of data that Byteball stores. In ## RPC service By default, no RPC service is enabled. If you want to manage your headless wallet via JSON-RPC API, e.g. you run an exchange, run [play/rpc_service.js](play/rpc_service.js) instead. See the [documentation about running RPC service](../../wiki/Running-RPC-service). + + +## Generate headless wallet configurations + +To be able to programmatically generate headless wallet configurations use the function `generateHeadlessWalletConfig`. + +The function will use the supplied configuration parameters and generate a new headless wallet configuration and an empty database in `dataDir`. + +Make sure that the `dataDir` directory is empty and does not contain a previous configuration (e.g. on MacOS `/Users//Library/Application Support/headless-byteball`). + +See the usage example below: +``` +// disable headless wallet autostart +conf.bAutoStart = false; +// dont connect to any hubs +conf.hub = ''; + +var headlessWallet = require('headless-wallet'); +headlessWallet.generateHeadlessWalletConfig("", "", function (mnemonicPhrase, passphrase, definition, address, dataDir) { + var finishedConfig = { + mnemonicPhrase: mnemonicPhrase, + passphrase: passphrase, + definition: definition, + address: address, + deviceName: deviceName, + appDataDir: dataDir + }; + console.log("wallet configuration: %j", finishedConfig); +}); +``` \ No newline at end of file