biot-core
Version:
```sh $ npm install $ cd examples $ node balance.js ```
627 lines (565 loc) • 20.2 kB
JavaScript
const util = require('util');
const conf = require('ocore/conf');
const Wallet = require('ocore/wallet');
const db = require('ocore/db');
const eventBus = require('ocore/event_bus');
const walletDefinedByKeys = require('ocore/wallet_defined_by_keys');
const ecdsaSig = require('ocore/signature.js');
const ecdsa = require('secp256k1');
const Mnemonic = require('bitcore-mnemonic');
const toEs6 = require('./lib/toEs6');
const libKeys = require('./lib/keys');
const libWallet = require('./lib/wallet');
const libAddress = require('./lib/address');
const libTransactions = require('./lib/transactions');
const libCorrespondents = require('./lib/correspondents');
const libSqliteMigrations = require('./lib/sqlite_migrations');
const libWalletTransactions = require('./lib/walletTransactions');
const protocolVersion = '0.1';
let xPrivKey;
process.on('unhandledRejection', up => {
throw up;
});
function replaceConsoleLog() {
if (global.window && window.cordova) return;
const fs = require('fs' + '');
if (!fs) return;
const desktopApp = require('ocore/desktop_app' + '');
const appDataDir = desktopApp.getAppDataDir();
let log_filename = conf.LOG_FILENAME || (appDataDir + '/log.txt');
let writeStream = fs.createWriteStream(log_filename);
console.log('---------------');
console.log('From this point, output will be redirected to ' + log_filename);
console.log("To release the terminal, type Ctrl-Z, then 'bg'");
console.log = function () {
writeStream.write(Date().toString() + ': ');
writeStream.write(util.format.apply(null, arguments) + '\n');
};
console.warn = console.log;
console.info = console.log;
}
/**
@async
@description Core initializing
@param {string} passphrase Passphrase to unlock your account
@return {string} Status
@example
await core.init('test')
*/
exports.init = async (passphrase) => {
let deviceName;
if (global.window && window.cordova) {
deviceName = window.localStorage.getItem("device_name");
if (!deviceName) {
return 'Please set device name';
}
} else {
if (!conf.deviceName) {
return 'Please set device name';
} else {
deviceName = conf.deviceName;
}
}
let keys = await libKeys.readKeys(passphrase);
let saveTempKeys = (new_temp_key, new_prev_temp_key, onDone) => {
libKeys.writeKeys(keys.mnemonic_phrase, new_temp_key, new_prev_temp_key, onDone).catch(Promise.reject);
};
let mnemonic = new Mnemonic(keys.mnemonic_phrase);
xPrivKey = mnemonic.toHDPrivateKey(passphrase);
libKeys.setXPrivKey(xPrivKey);
let devicePrivKey = xPrivKey.derive("m/1'").privateKey.bn.toBuffer({size: 32});
let device = require('ocore/device');
device.setDevicePrivateKey(devicePrivKey);
let my_device_address = device.getMyDeviceAddress();
let rows = await toEs6.dbQuery("SELECT 1 FROM extended_pubkeys WHERE device_address=?", [my_device_address]);
if (rows.length === 0) {
console.log('passphrase is incorrect');
return false;
}
if (conf.permanent_pairing_secret)
db.query(
"INSERT " + db.getIgnore() + " INTO pairing_secrets (pairing_secret, is_permanent, expiry_date) VALUES (?, 1, '2038-01-01')",
[conf.permanent_pairing_secret]
);
device.setTempKeys(keys.deviceTempPrivKey, keys.devicePrevTempPrivKey, saveTempKeys);
device.setDeviceName(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) {
let light_wallet = require('ocore/light_wallet');
light_wallet.setLightVendorHost(conf.hub);
}
await libSqliteMigrations.migrateDb();
eventBus.emit('headless_wallet_ready');
if (!process.env.DEBUG) {
replaceConsoleLog();
}
return keys.mnemonic_phrase;
};
async function updateConfFile(obj) {
const desktopApp = require('ocore/desktop_app' + '');
const appDataDir = desktopApp.getAppDataDir();
const userConfFile = appDataDir + '/conf.json';
let json = require(userConfFile);
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
json[key] = obj[key];
}
}
await new Promise(resolve => {
fs.writeFile(userConfFile, JSON.stringify(json, null, '\t'), 'utf8', (err) => {
if (err)
throw Error('failed to write conf.json: ' + err);
resolve();
});
});
}
async function setDeviceName(name) {
if (global.window && window.cordova) {
window.localStorage.setItem('device_name', name);
} else {
await updateConfFile({deviceName: name});
}
}
/**
@async
@description Getting list of wallets
@return {string[]} Wallets list
@example
await core.getWallets()
*/
async function getWallets() {
let rows = await toEs6.dbQuery("SELECT wallet FROM wallets");
return rows.map(row => row.wallet);
}
async function getMyDeviceWallets() {
let device = require('ocore/device')
let rows = await toEs6.dbQuery("SELECT wallet FROM wallets JOIN wallet_signing_paths USING(wallet) WHERE device_address = ?",
[device.getMyDeviceAddress()]);
return rows.map(row => row.wallet);
}
/**
@async
@description Getting list of addresses
@param {string} walletId Wallet id
@return {string[]} Addresses list
@example
await core.getAddressesInWallet('yXSWvqast2rrmwcR/f5QfUAXZLwaaiRvwE+N9whoZLc=')
*/
async function getAddressesInWallet(walletId) {
let rows = await toEs6.dbQuery("SELECT address FROM my_addresses WHERE wallet = ?", [walletId]);
return rows.map(row => row.address);
}
/**
@async
@description Creating new wallet
@return {string} walletId
@example
await core.createNewWallet()
*/
async function createNewWallet() {
let rows = await toEs6.dbQuery("SELECT wallet FROM wallets");
let walletId = await libWallet.createWallet(xPrivKey, rows.length);
return walletId;
}
/**
@description Creating new address
@param {string} walletId Wallet id
@return {Promise.<string>} Address
@example
await core.createNewAddress('yXSWvqast2rrmwcR/f5QfUAXZLwaaiRvwE+N9whoZLc=')
*/
function createNewAddress(walletId) {
return new Promise(resolve => {
walletDefinedByKeys.issueNextAddress(walletId, 0, function (addressInfo) {
return resolve(addressInfo.address);
});
});
}
/**
@description Getting balance of wallet
@param {string} walletId Wallet id
@return {Object} Balance
@example
await core.getWalletBalance('yXSWvqast2rrmwcR/f5QfUAXZLwaaiRvwE+N9whoZLc=')
*/
function getWalletBalance(walletId) {
return new Promise(resolve => {
Wallet.readBalance(walletId, (balance) => {
return resolve(balance);
})
});
}
/**
@async
@description Getting balance of address
@param {string} address Byteball address
@return {Object} Balance
@example
await core.getAddressBalance('VY52VTHNX27WKRGFUGJY7KNTGXP3Z6YU')
*/
async function getAddressBalance(address) {
return libAddress.getAddressBalance(address);
}
/**
@description Sending text message to device address
@param {string} device_address Device address
@param {string} text Message text
@example
core.sendTextMessageToDevice('0PZT5VOY5AINZKW2SJ3Z7O4IDQNKPV364', 'Hello!')
*/
function sendTextMessageToDevice(device_address, text) {
let device = require('ocore/device');
device.sendMessageToDevice(device_address, 'text', text);
}
/**
@description Sending tech message to device address
@param {string} device_address Device address
@param {object} object Object
@param {function} callback callback
@example
core.sendTechMessageToDevice('0PZT5VOY5AINZKW2SJ3Z7O4IDQNKPV364', {version: '0.1'})
*/
function sendTechMessageToDevice(device_address, object, callback) {
let device = require('ocore/device');
object.version = protocolVersion;
object.app = 'BIoT';
device.sendMessageToDevice(device_address, 'object', object, callback);
}
/**
@description Sending payment from wallet
@param {Object} options Payment options
@param {string} options.asset Payment asset
@param {string} options.wallet Wallet from which goes payment
@param {string} options.toAddress Address on which you send the payment
@param {number} options.amount Payment amount
@param {string} options.changeAddress Your new address after the payment
@param {string|null} options.deviceAddress Device address on what will be sent notification
@return {Promise.<string>} unit
@example
core.sendTransaction({
asset: 'base',
wallet: 'yXSWvqast2rrmwcR/f5QfUAXZLwaaiRvwE+N9whoZLc=',
toAddress: 'VY52VTHNX27WKRGFUGJY7KNTGXP3Z6YU',
amount: 10,
changeAddress: 'IZHG7LKW2FJ2KAUHL4RRPY3JG2HNVNPD',
deviceAddress: null
});
*/
function sendPaymentFromWallet(options) {
return libTransactions.sendPaymentFromWallet(options);
}
function sendMultiPayment(options) {
return libTransactions.sendMultiPayment(options);
}
/**
@description Sending payment from wallet use unstable units
@param {Object} options Payment options
@param {string} options.asset Payment asset
@param {string} options.wallet Wallet from which goes payment
@param {string} options.toAddress Address on which you send the payment
@param {number} options.amount Payment amount
@param {string} options.changeAddress Your new address after the payment
@param {string|null} options.deviceAddress Device address on what will be sent notification
@return {Promise.<string>} unit
@example
core.sendPaymentFromWalletUseUnstableUnits({
asset: 'base',
wallet: 'yXSWvqast2rrmwcR/f5QfUAXZLwaaiRvwE+N9whoZLc=',
toAddress: 'VY52VTHNX27WKRGFUGJY7KNTGXP3Z6YU',
amount: 10,
changeAddress: 'IZHG7LKW2FJ2KAUHL4RRPY3JG2HNVNPD',
deviceAddress: null
});
*/
function sendPaymentUseUnstableUnits(to_address, amount, my_addresses, asset) {
return libTransactions.sendPaymentUseUnstableUnits(to_address, amount, my_addresses, asset);
}
/**
@description Getting list of transactions by wallet.
@param {string} walletId Wallet id
@return {Promise.<Object>} history
@example
core.getListTransactionsForWallet('yXSWvqast2rrmwcR/f5QfUAXZLwaaiRvwE+N9whoZLc=');
*/
function getListTransactionsForWallet(walletId) {
return new Promise(resolve => {
Wallet.readTransactionHistory({wallet: walletId}, resolve);
})
}
/**
@description Getting list of transactions by address.
@param {string} address Byteball address
@return {Promise.<Object>} history
@example
core.getListTransactionsForAddress('IZHG7LKW2FJ2KAUHL4RRPY3JG2HNVNPD');
*/
function getListTransactionsForAddress(address) {
return new Promise(resolve => {
Wallet.readTransactionHistory({address}, resolve);
})
}
/**
* @typedef {Object} AddressInfo
* @property {number} account
* @property {number} is_change
* @property {number} address_index
*/
/**
@async
@description Getting address info
@param {string} address Byteball address
@return {AddressInfo} address info
@example
await core.myAddressInfo('IZHG7LKW2FJ2KAUHL4RRPY3JG2HNVNPD');
*/
function myAddressInfo(address) {
return libAddress.myAddressInfo(address);
}
/**
* @typedef {Object} Sign
* @property {string} sign
* @property {string} pub_b64
*/
/**
@description Signing device private key
@param {string} hash Hash string
@return {Sign} sign object
@example
core.signDevicePrivateKey('IZHG7LKW2FJ2KAUHL4RRPY3JG2HNVNPD');
*/
function signDevicePrivateKey(hash) {
let buffer = Buffer.from(hash);
let device = require('ocore/device');
let devicePrivKey = xPrivKey.derive("m/1'").privateKey.bn.toBuffer({size: 32});
return {sign: ecdsaSig.sign(buffer, devicePrivKey), pub_b64: device.getMyDevicePubKey()};
}
/**
@description Signing with address (watch: myAddressInfo)
@param {number} account
@param {number} is_change
@param {number} address_index
@param {string} hash Hash string
@return {Sign} sign object
@example
core.signWithAddress(0, 0, 0, 'IZHG7LKW2FJ2KAUHL4RRPY3JG2HNVNPD');
*/
function signWithAddress(account, is_change, address_index, hash) {
let buffer = Buffer.from(hash);
let path = "m/44'/0'/" + account + "'/" + is_change + "/" + address_index;
let privateKey = xPrivKey.derive(path).privateKey;
let privKeyBuf = privateKey.bn.toBuffer({size: 32}); // https://github.com/bitpay/bitcore-lib/issues/47
let pubKeyb64 = ecdsa.publicKeyCreate(privKeyBuf, true).toString('base64')
return {sign: ecdsaSig.sign(buffer, privKeyBuf), pub_b64: pubKeyb64};
}
/**
@description Sign verification
@param {string} hash Hash string
@param {string} b64_sig Sign.sign
@param {string} b64_pub_key Sign.pub_b64
@return {boolean} Verification result
@example
core.verifySign('IZHG7LKW2FJ2KAUHL4RRPY3JG2HNVNPD', '/J6Gv9aT8KSgEP2TwmNoQ2W/JmZXYaXBLt4zBE8Po5Vm8TOX+fu53Y7DSYtuH/61EgR7WP5Spk76J8gFTCmPpg==', 'A4QdpqFIqVbyCXgbuzlHEMl+1osh2hGC3oVRzHU1V5V0');
*/
function verifySign(hash, b64_sig, b64_pub_key) {
let buffer = Buffer.from(hash);
return ecdsaSig.verify(buffer, b64_sig, b64_pub_key);
}
/**
@description Add a correspondent
@param {string} code Pairing code
@example
await core.addCorrespondent('ApM6ZzpMhnK87Qqz4LhkIHTxGA79VVTVqb1PmtrAzOzo@byteball.org/bb-test#O3IZDFeH4SR0');
*/
function addCorrespondent(code) {
return libCorrespondents.add(code);
}
/**
@description Remove a correspondent
@param {string} device_address Device address
@example
await core.removeCorrespondent('0WI73XY6WPR46D4ZKEQEFFQSSPBZMUOVD');
*/
function removeCorrespondent(device_address) {
return libCorrespondents.remove(device_address);
}
/**
@description List of correspondents
@return {Array.<Object>} list
@example
await core.listCorrespondents()
*/
function listCorrespondents() {
return libCorrespondents.list();
}
function getMyParingCode() {
const device = require('ocore/device');
return device.getMyDevicePubKey() + "@" + conf.hub + "#";
}
function postDataFeed(objDataFeed) {
return new Promise(resolve => {
(async () => {
const network = require('ocore/network.js');
const composer = require('ocore/composer.js');
const objectHash = require('ocore/object_hash.js');
const wallet = require('ocore/wallet.js');
const wallets = await getWallets();
const addresses = await getAddressesInWallet(wallets[0]);
const my_address = addresses[0];
let params = {
paying_addresses: [my_address],
outputs: [{address: my_address, amount: 0}],
signer: wallet.getSigner({}, addresses, libKeys.signWithLocalPrivateKey),
spend_unconfirmed: 'all',
callbacks: composer.getSavingCallbacks({
ifNotEnoughFunds: console.error,
ifError: console.error,
ifOk: function (objJoint) {
network.broadcastJoint(objJoint);
return resolve(objJoint);
}
})
};
let objMessage = {
app: "data_feed",
payload_location: "inline",
payload_hash: objectHash.getBase64Hash(objDataFeed),
payload: objDataFeed
};
params.messages = [objMessage];
composer.composeJoint(params);
})();
});
}
function postPrivateProfile(user_address, profile) {
return new Promise(resolve => {
(async () => {
const network = require('ocore/network.js');
const composer = require('ocore/composer.js');
const objectHash = require('ocore/object_hash.js');
const wallet = require('ocore/wallet.js');
const wallets = await getWallets();
const addresses = await getAddressesInWallet(wallets[0]);
const my_address = addresses[0];
let src_profile = {};
let hidden_profile = {};
for (let field in profile) {
let value = profile[field];
let blinding = composer.generateBlinding();
hidden_profile[field] = objectHash.getBase64Hash([value, blinding]);
src_profile[field] = [value, blinding];
}
let profile_hash = objectHash.getBase64Hash(hidden_profile);
let privProfile = {
profile_hash: profile_hash,
user_id: objectHash.getBase64Hash([profile, conf.salt || ''])
};
let payload = {
address: user_address,
profile: privProfile
};
let params = {
paying_addresses: [my_address],
outputs: [{address: my_address, amount: 0}],
signer: wallet.getSigner({}, addresses, libKeys.signWithLocalPrivateKey),
spend_unconfirmed: 'all',
callbacks: composer.getSavingCallbacks({
ifNotEnoughFunds: console.error,
ifError: console.error,
ifOk: function (objJoint) {
network.broadcastJoint(objJoint);
return resolve({objJoint, src_profile, address: my_address});
}
})
};
console.error('payload', payload);
let objMessage = {
app: "attestation",
payload_location: "inline",
payload_hash: objectHash.getBase64Hash(payload),
payload: payload
};
params.messages = [objMessage];
composer.composeJoint(params);
})();
});
}
function postPublicProfile(user_address, profile) {
return new Promise(resolve => {
(async () => {
const network = require('ocore/network.js');
const composer = require('ocore/composer.js');
const objectHash = require('ocore/object_hash.js');
const wallet = require('ocore/wallet.js');
const wallets = await getWallets();
const addresses = await getAddressesInWallet(wallets[0]);
const my_address = addresses[0];
let payload = {
address: user_address,
profile: profile
};
let params = {
paying_addresses: [my_address],
outputs: [{address: my_address, amount: 0}],
signer: wallet.getSigner({}, addresses, libKeys.signWithLocalPrivateKey),
spend_unconfirmed: 'all',
callbacks: composer.getSavingCallbacks({
ifNotEnoughFunds: console.error,
ifError: console.error,
ifOk: function (objJoint) {
network.broadcastJoint(objJoint);
return resolve({objJoint, address: my_address});
}
})
};
console.error('payload', payload);
let objMessage = {
app: "attestation",
payload_location: "inline",
payload_hash: objectHash.getBase64Hash(payload),
payload: payload
};
params.messages = [objMessage];
composer.composeJoint(params);
})();
});
}
function saveProfile(attester, my_address, unit, src_profile) {
return toEs6.dbQuery("INSERT INTO profiles (attester, address, unit, object) VALUES(?,?,?,?)", [attester, my_address, unit, src_profile]);
}
function getProfiles() {
return toEs6.dbQuery("SELECT * FROM profiles");
}
exports.createNewWallet = createNewWallet;
exports.getWallets = getWallets;
exports.getMyDeviceWallets = getMyDeviceWallets;
exports.getAddressesInWallet = getAddressesInWallet;
exports.createNewAddress = createNewAddress;
exports.getWalletBalance = getWalletBalance;
exports.getAddressBalance = getAddressBalance;
exports.sendTextMessageToDevice = sendTextMessageToDevice;
exports.sendTechMessageToDevice = sendTechMessageToDevice;
exports.sendPaymentFromWallet = sendPaymentFromWallet;
exports.sendPaymentUseUnstableUnits = sendPaymentUseUnstableUnits;
exports.getListTransactionsForAddress = getListTransactionsForAddress;
exports.getListTransactionsForWallet = getListTransactionsForWallet;
exports.myAddressInfo = myAddressInfo;
exports.signDevicePrivateKey = signDevicePrivateKey;
exports.signWithAddress = signWithAddress;
exports.verifySign = verifySign;
exports.addCorrespondent = addCorrespondent;
exports.removeCorrespondent = removeCorrespondent;
exports.listCorrespondents = listCorrespondents;
exports.getMyParingCode = getMyParingCode;
exports.postDataFeed = postDataFeed;
exports.postPrivateProfile = postPrivateProfile;
exports.postPublicProfile = postPublicProfile;
exports.saveProfile = saveProfile;
exports.getProfiles = getProfiles;
exports.setDeviceName = setDeviceName;
exports.sendMultiPayment = sendMultiPayment;
exports.getWalletTransactions = libWalletTransactions.getWalletTransactions;