You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
201 lines
5.1 KiB
201 lines
5.1 KiB
'use strict';
|
|
|
|
var _ = require('lodash');
|
|
var $ = require('preconditions').singleton();
|
|
var sjcl = require('sjcl');
|
|
|
|
var Bitcore = require('bitcore');
|
|
var Address = Bitcore.Address;
|
|
var PrivateKey = Bitcore.PrivateKey;
|
|
var PublicKey = Bitcore.PublicKey;
|
|
var crypto = Bitcore.crypto;
|
|
var encoding = Bitcore.encoding;
|
|
var Utils = require('./utils');
|
|
|
|
function WalletUtils() {};
|
|
|
|
/* TODO: It would be nice to be compatible with bitcoind signmessage. How
|
|
* the hash is calculated there? */
|
|
WalletUtils.hashMessage = function(text) {
|
|
$.checkArgument(text);
|
|
var buf = new Buffer(text);
|
|
var ret = crypto.Hash.sha256sha256(buf);
|
|
ret = new Bitcore.encoding.BufferReader(ret).readReverse();
|
|
return ret;
|
|
};
|
|
|
|
|
|
WalletUtils.signMessage = function(text, privKey) {
|
|
$.checkArgument(text);
|
|
var priv = new PrivateKey(privKey);
|
|
var hash = WalletUtils.hashMessage(text);
|
|
return crypto.ECDSA.sign(hash, priv, 'little').toString();
|
|
};
|
|
|
|
|
|
WalletUtils.accessFromData = function(data) {
|
|
if (data.xPrivKey)
|
|
return 'full';
|
|
|
|
if (data.rwPrivKey)
|
|
return 'readwrite';
|
|
|
|
if (data.roPrivKey)
|
|
return 'readonly';
|
|
|
|
return 'none';
|
|
};
|
|
|
|
WalletUtils.accessNameToLevel = function(name) {
|
|
|
|
if (name === 'full')
|
|
return 30;
|
|
if (name === 'readwrite')
|
|
return 20;
|
|
if (name === 'readonly')
|
|
return 10;
|
|
if (name === 'none')
|
|
return 0;
|
|
throw new Error('Bad access name:' + name);
|
|
};
|
|
|
|
|
|
WalletUtils.verifyMessage = function(text, signature, pubKey) {
|
|
$.checkArgument(text);
|
|
$.checkArgument(pubKey);
|
|
|
|
if (!signature)
|
|
return false;
|
|
|
|
var pub = new PublicKey(pubKey);
|
|
var hash = WalletUtils.hashMessage(text);
|
|
|
|
try {
|
|
var sig = new crypto.Signature.fromString(signature);
|
|
return crypto.ECDSA.verify(hash, sig, pub, 'little');
|
|
} catch (e) {
|
|
return false;
|
|
}
|
|
};
|
|
|
|
WalletUtils.deriveAddress = function(publicKeyRing, path, m, network) {
|
|
var publicKeys = _.map(publicKeyRing, function(xPubKey) {
|
|
var xpub = new Bitcore.HDPublicKey(xPubKey);
|
|
return xpub.derive(path).publicKey;
|
|
});
|
|
|
|
var bitcoreAddress = Address.createMultisig(publicKeys, m, network);
|
|
|
|
return {
|
|
address: bitcoreAddress.toString(),
|
|
path: path,
|
|
publicKeys: _.invoke(publicKeys, 'toString'),
|
|
};
|
|
};
|
|
|
|
WalletUtils.getProposalHash = function(toAddress, amount, message) {
|
|
return toAddress + '|' + amount + '|' + (message || '');
|
|
};
|
|
|
|
WalletUtils.xPubToCopayerId = function(xpub) {
|
|
var hash = sjcl.hash.sha256.hash(xpub);
|
|
return sjcl.codec.hex.fromBits(hash);
|
|
};
|
|
|
|
WalletUtils.toSecret = function(walletId, walletPrivKey, network) {
|
|
var widHex = new Buffer(walletId.replace(/-/g, ''), 'hex');
|
|
var widBase58 = new encoding.Base58(widHex).toString();
|
|
return _.padRight(widBase58, 22, '0') + walletPrivKey.toWIF() + (network == 'testnet' ? 'T' : 'L');
|
|
};
|
|
|
|
WalletUtils.fromSecret = function(secret) {
|
|
$.checkArgument(secret);
|
|
|
|
function split(str, indexes) {
|
|
var parts = [];
|
|
indexes.push(str.length);
|
|
var i = 0;
|
|
while (i < indexes.length) {
|
|
parts.push(str.substring(i == 0 ? 0 : indexes[i - 1], indexes[i]));
|
|
i++;
|
|
};
|
|
return parts;
|
|
};
|
|
|
|
try {
|
|
var secretSplit = split(secret, [22, 74]);
|
|
var widBase58 = secretSplit[0].replace(/0/g, '');
|
|
var widHex = encoding.Base58.decode(widBase58).toString('hex');
|
|
var walletId = split(widHex, [8, 12, 16, 20]).join('-');
|
|
|
|
var walletPrivKey = Bitcore.PrivateKey.fromString(secretSplit[1]);
|
|
var networkChar = secretSplit[2];
|
|
|
|
return {
|
|
walletId: walletId,
|
|
walletPrivKey: walletPrivKey,
|
|
network: networkChar == 'T' ? 'testnet' : 'livenet',
|
|
};
|
|
} catch (ex) {
|
|
throw new Error('Invalid secret');
|
|
}
|
|
};
|
|
|
|
|
|
WalletUtils.encryptMessage = function(message, encryptingKey) {
|
|
var key = sjcl.codec.base64.toBits(encryptingKey);
|
|
return sjcl.encrypt(key, message, {
|
|
ks: 128,
|
|
iter: 1
|
|
});
|
|
};
|
|
|
|
WalletUtils.decryptMessage = function(cyphertextJson, encryptingKey) {
|
|
var key = sjcl.codec.base64.toBits(encryptingKey);
|
|
return sjcl.decrypt(key, cyphertextJson);
|
|
};
|
|
|
|
WalletUtils.privateKeyToAESKey = function(privKey) {
|
|
var pk = Bitcore.PrivateKey.fromString(privKey);
|
|
return Bitcore.crypto.Hash.sha256(pk.toBuffer()).slice(0, 16).toString('base64');
|
|
};
|
|
|
|
WalletUtils.decryptWallet = function(data, password) {
|
|
$.checkArgument(data.enc);
|
|
var extraFields = JSON.parse(sjcl.decrypt(password, data.enc));
|
|
delete data.enc;
|
|
return _.extend(data, extraFields);
|
|
};
|
|
|
|
|
|
WalletUtils.sjclOpts = {
|
|
iter: 5000,
|
|
};
|
|
|
|
WalletUtils.encryptWallet = function(data, accessWithoutEncrytion, password) {
|
|
|
|
// Fields to encrypt, given the NOPASSWD access level
|
|
var fieldsEncryptByLevel = {
|
|
none: _.keys(data),
|
|
readonly: ['xPrivKey', 'rwPrivKey', 'publicKeyRing' ],
|
|
readwrite: ['xPrivKey', ],
|
|
full: [],
|
|
};
|
|
|
|
var fieldsEncrypt = fieldsEncryptByLevel[accessWithoutEncrytion];
|
|
$.checkState(!_.isUndefined(fieldsEncrypt));
|
|
|
|
if (!_.every(fieldsEncrypt, function(k) {
|
|
return data[k];
|
|
})) throw new Error('Wallet does not contain necesary info to encrypt');
|
|
|
|
var toEncrypt = _.pick(data, fieldsEncrypt);
|
|
var enc = sjcl.encrypt(password, JSON.stringify(toEncrypt), WalletUtils.sjclOpts);
|
|
|
|
var ret = _.omit(data, fieldsEncrypt);
|
|
ret.enc = enc;
|
|
return ret;
|
|
};
|
|
|
|
|
|
module.exports = WalletUtils;
|
|
|