biot-core
Version:
```sh $ npm install $ cd examples $ node balance.js ```
258 lines (235 loc) • 9.68 kB
JavaScript
const _fs = require('./fs');
const _ = require('lodash');
const crypto = require('crypto');
const conf = require('ocore/conf');
const Mnemonic = require('bitcore-mnemonic');
const ecdsaSig = require('ocore/signature');
const Wallet = require('ocore/wallet');
const objectHash = require('ocore/object_hash');
const eventBus = require('ocore/event_bus');
const walletGeneral = require('ocore/wallet_general');
const db = require('ocore/db');
const isCordova = global.window && window.cordova;
let desktopApp;
const toEs6 = require('./toEs6');
const wallet = require('./wallet');
const utils = require('./utils');
let KEYS_FILENAME;
let appDataDir;
if (!isCordova) {
desktopApp = require('ocore/desktop_app' + '');
appDataDir = desktopApp.getAppDataDir();
KEYS_FILENAME = appDataDir + '/' + (conf.KEYS_FILENAME || 'keys.json');
} else {
KEYS_FILENAME = _fs.getDatabaseDirPath() + '/keys';
}
let xPrivKey;
function setXPrivKey(_xPrivKey) {
xPrivKey = _xPrivKey
}
function signWithLocalPrivateKey(wallet_id, account, is_change, address_index, text_to_sign, handleSig) {
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
handleSig(ecdsaSig.sign(text_to_sign, privKeyBuf));
}
async function keysFileExists() {
return await toEs6.fsAccess(KEYS_FILENAME);
}
function generateKey() {
let deviceTempPrivKey = crypto.randomBytes(32);
let devicePrevTempPrivKey = crypto.randomBytes(32);
let mnemonic = new Mnemonic(); // generates new mnemonic
while (!Mnemonic.isValid(mnemonic.toString())) {
mnemonic = new Mnemonic();
}
return {mnemonic, deviceTempPrivKey, devicePrevTempPrivKey};
}
async function readKeys(passphrase) {
let isAccess = await keysFileExists();
if (!isAccess) {
let keys = generateKey(passphrase);
await writeKeys(keys.mnemonic.phrase, keys.deviceTempPrivKey, keys.devicePrevTempPrivKey).catch(Promise.reject);
console.log('keys created');
let xPrivKey = keys.mnemonic.toHDPrivateKey(passphrase);
await wallet.createWallet(xPrivKey, 0);
return Promise.resolve({
mnemonic_phrase: keys.mnemonic.phrase,
passphrase: passphrase,
deviceTempPrivKey: keys.deviceTempPrivKey,
devicePrevTempPrivKey: keys.devicePrevTempPrivKey
});
} else {
return new Promise((resolve, reject) => {
_fs.readFile(KEYS_FILENAME, async (err, data) => {
if (err) {
return reject(err);
}
let keys = JSON.parse(data);
let deviceTempPrivKey = Buffer.from(keys.temp_priv_key, 'base64');
let devicePrevTempPrivKey = Buffer.from(keys.prev_temp_priv_key, 'base64');
console.log('keys read');
let rows = await toEs6.dbQuery("SELECT wallet FROM wallets");
if (!rows.length) {
let mnemonic = new Mnemonic(keys.mnemonic_phrase);
let xPrivKey = mnemonic.toHDPrivateKey(passphrase);
let walletId = await wallet.createWallet(xPrivKey, 0);
if (global.window && window.cordova) {
let assocWalletToName = {};
assocWalletToName[walletId] = 'Main wallet';
window.localStorage.setItem('assocWalletToName', JSON.stringify(assocWalletToName));
}
}
return resolve({
mnemonic_phrase: keys.mnemonic_phrase,
passphrase: passphrase,
deviceTempPrivKey,
devicePrevTempPrivKey
});
});
});
}
}
async function writeKeys(mnemonic_phrase, deviceTempPrivKey, devicePrevTempPrivKey) {
let keys = {
mnemonic_phrase: mnemonic_phrase,
temp_priv_key: deviceTempPrivKey.toString('base64'),
prev_temp_priv_key: devicePrevTempPrivKey.toString('base64')
};
if (isCordova) {
await new Promise(resolve => {
_fs.cordovaWriteFile(_fs.getParentDirPath(), _fs.getDatabaseDirName(), 'keys', JSON.stringify(keys, null, '\t'), resolve);
})
} else {
await utils.createFolderIfNotExists(appDataDir);
await toEs6.fsWriteFile(KEYS_FILENAME, JSON.stringify(keys, null, '\t'), 'utf8').catch(Promise.reject);
}
return true;
}
function findAddress(address, signing_path, callbacks, fallback_remote_device_address) {
const device = require('ocore/device');
db.query(
"SELECT wallet, account, is_change, address_index, full_approval_date, device_address \n\
FROM my_addresses JOIN wallets USING(wallet) JOIN wallet_signing_paths USING(wallet) \n\
WHERE address=? AND signing_path=?",
[address, signing_path],
rows => {
if (rows.length > 1)
throw Error("more than 1 address found");
if (rows.length === 1) {
let row = rows[0];
if (!row.full_approval_date)
return callbacks.ifError("wallet of address " + address + " not approved");
if (row.device_address !== device.getMyDeviceAddress())
return callbacks.ifRemote(row.device_address);
let objAddress = {
address: address,
wallet: row.wallet,
account: row.account,
is_change: row.is_change,
address_index: row.address_index
};
callbacks.ifLocal(objAddress);
return;
}
db.query("SELECT device_address FROM biot_peer_addresses WHERE address = ?", [address], rows2 => {
if (rows2.length === 1) {
return callbacks.ifRemote(rows2[0].device_address);
}
db.query(
// "SELECT address, device_address, member_signing_path FROM shared_address_signing_paths WHERE shared_address=? AND signing_path=?",
// look for a prefix of the requested signing_path
"SELECT address, device_address, signing_path FROM shared_address_signing_paths \n\
WHERE shared_address=? AND signing_path=SUBSTR(?, 1, LENGTH(signing_path))",
[address, signing_path],
sa_rows => {
if (rows.length > 1)
throw Error("more than 1 member address found for shared address " + address + " and signing path " + signing_path);
if (sa_rows.length === 0) {
if (fallback_remote_device_address)
return callbacks.ifRemote(fallback_remote_device_address);
return callbacks.ifUnknownAddress();
}
let objSharedAddress = sa_rows[0];
let relative_signing_path = 'r' + signing_path.substr(objSharedAddress.signing_path.length);
let bLocal = (objSharedAddress.device_address === device.getMyDeviceAddress()); // local keys
if (objSharedAddress.address === '') {
return callbacks.ifMerkle(bLocal);
} else if (objSharedAddress.address === 'secret') {
return callbacks.ifSecret();
}
findAddress(objSharedAddress.address, relative_signing_path, callbacks, bLocal ? null : objSharedAddress.device_address);
}
);
});
}
);
}
function getLocalSigner(opts, arrSigningDeviceAddresses, signWithLocalPrivateKey) {
let bRequestedConfirmation = false;
let signer = Wallet.getSigner(opts, arrSigningDeviceAddresses, signWithLocalPrivateKey);
signer.readDefinition = function (conn, address, handleDefinition) {
conn.query(
"SELECT definition FROM my_addresses WHERE address=? \n\
UNION SELECT definition FROM shared_addresses WHERE shared_address=?\n\
UNION SELECT definition FROM biot_peer_addresses WHERE address=?",
[address, address, address],
function (rows) {
if (rows.length !== 1)
throw Error("definition not found");
handleDefinition(null, JSON.parse(rows[0].definition));
}
);
};
signer.sign = (objUnsignedUnit, assocPrivatePayloads, address, signing_path, handleSignature) => {
const authors = _.cloneDeep(objUnsignedUnit.authors);
let 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);
});
let newObjUnsignedUnit = _.cloneDeep(objUnsignedUnit);
newObjUnsignedUnit.authors = authors;
walletGeneral.sendOfferToSign(device_address, address, signing_path, newObjUnsignedUnit, 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 (!opts.merkle_proof)
throw Error("merkle proof at path " + signing_path + " not provided");
handleSignature(null, opts.merkle_proof);
},
ifSecret: function () {
if (!opts.secrets || !opts.secrets[signing_path])
throw Error("secret " + signing_path + " not found");
handleSignature(null, opts.secrets[signing_path])
}
});
};
return signer;
}
exports.keysFileExists = keysFileExists;
exports.readKeys = readKeys;
exports.writeKeys = writeKeys;
exports.signWithLocalPrivateKey = signWithLocalPrivateKey;
exports.setXPrivKey = setXPrivKey;
exports.getLocalSigner = getLocalSigner;