commit
4c6491795d
|
@ -1,27 +1,57 @@
|
||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
|
|
||||||
var program = require('commander');
|
var program = require('commander');
|
||||||
var qr = require('qr-image');
|
var qr = require('qr-image');
|
||||||
|
var fs = require('fs');
|
||||||
|
var _ = require('lodash');
|
||||||
|
|
||||||
var Client = require('../lib/client');
|
var Client = require('../lib/client');
|
||||||
var utils = require('./cli-utils');
|
var utils = require('./cli-utils');
|
||||||
program = utils.configureCommander(program);
|
program = utils.configureCommander(program);
|
||||||
|
|
||||||
program
|
program
|
||||||
.option('-q, --qr')
|
.option('-a, --access [level]', 'access privileges for exported data (full, readwrite, readonly)', 'full')
|
||||||
|
.option('-q, --qr', 'export a QR code')
|
||||||
|
.option('-o, --output [filename]', 'output file');
|
||||||
|
|
||||||
|
program.on('--help', function() {
|
||||||
|
console.log(' Access Levels:');
|
||||||
|
console.log('');
|
||||||
|
console.log(' readonly : allows to read wallet data: balance, tx proposals ');
|
||||||
|
console.log(' readwrite: + allows to create addresses and unsigned tx prposals ');
|
||||||
|
console.log(' full : + allows sign tx prposals ');
|
||||||
|
console.log('');
|
||||||
|
});
|
||||||
|
|
||||||
|
program
|
||||||
.parse(process.argv);
|
.parse(process.argv);
|
||||||
|
|
||||||
var args = program.args;
|
var args = program.args;
|
||||||
var client = utils.getClient(program);
|
var client = utils.getClient(program);
|
||||||
|
|
||||||
client.export(function(err, x) {
|
if (!_.contains(['full', 'readwrite', 'readonly'], program.access)) {
|
||||||
|
program.help();
|
||||||
|
}
|
||||||
|
|
||||||
|
var msg = ' Access Level: ' + program.access;
|
||||||
|
|
||||||
|
client.export({
|
||||||
|
access: program.access
|
||||||
|
}, function(err, x) {
|
||||||
utils.die(err);
|
utils.die(err);
|
||||||
if (program.qr) {
|
if (program.qr) {
|
||||||
var filename = program.config + '.svg';
|
var filename = program.file + '.svg';
|
||||||
var qr_svg = qr.image(x, { type: 'svg' });
|
var qr_svg = qr.image(x, {
|
||||||
qr_svg.pipe(require('fs').createWriteStream(filename));
|
type: 'svg'
|
||||||
console.log('Wallet Critical Data: exported to ' + filename);
|
});
|
||||||
|
qr_svg.pipe(fs.createWriteStream(filename));
|
||||||
|
console.log('Wallet Critical Data: exported to %s. %s\n',filename, msg);
|
||||||
} else {
|
} else {
|
||||||
console.log('Wallet Critical Data:\n', x);
|
if (program.output) {
|
||||||
|
fs.writeFileSync(program.output, x);
|
||||||
|
console.log('Wallet Critical Data saved at %s. %s\n', program.output, msg);
|
||||||
|
} else {
|
||||||
|
console.log('Wallet Critical Data (%s)\n%s', msg, x);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -22,5 +22,5 @@ var str = fs.readFileSync(args[0]);
|
||||||
|
|
||||||
client.import(str, function(err, x) {
|
client.import(str, function(err, x) {
|
||||||
utils.die(err);
|
utils.die(err);
|
||||||
console.log('Wallet Imported');
|
console.log('Wallet Imported. Access level:' + x);
|
||||||
});
|
});
|
||||||
|
|
|
@ -16,6 +16,7 @@ var ServerCompromisedError = require('./servercompromisederror')
|
||||||
var BASE_URL = 'http://localhost:3001/copay/api';
|
var BASE_URL = 'http://localhost:3001/copay/api';
|
||||||
|
|
||||||
var WALLET_CRITICAL_DATA = ['xPrivKey', 'm', 'publicKeyRing', 'sharedEncryptingKey'];
|
var WALLET_CRITICAL_DATA = ['xPrivKey', 'm', 'publicKeyRing', 'sharedEncryptingKey'];
|
||||||
|
var WALLET_EXTRA_DATA = ['copayerId', 'roPrivKey', 'rwPrivKey'];
|
||||||
|
|
||||||
function _encryptMessage(message, encryptingKey) {
|
function _encryptMessage(message, encryptingKey) {
|
||||||
if (!message) return null;
|
if (!message) return null;
|
||||||
|
@ -94,9 +95,12 @@ API.prototype._tryToComplete = function(data, cb) {
|
||||||
if (wallet.status != 'complete')
|
if (wallet.status != 'complete')
|
||||||
return cb('Wallet Incomplete');
|
return cb('Wallet Incomplete');
|
||||||
|
|
||||||
if (!Verifier.checkCopayers(wallet.copayers, data.walletPrivKey, data.xPrivKey, data.n))
|
if (!Verifier.checkCopayers(wallet.copayers, data.walletPrivKey,
|
||||||
|
data.xPrivKey, data.n)) {
|
||||||
|
|
||||||
return cb(new ServerCompromisedError(
|
return cb(new ServerCompromisedError(
|
||||||
'Copayers in the wallet could not be verified to have known the wallet secret'));
|
'Copayers in the wallet could not be verified to have known the wallet secret'));
|
||||||
|
}
|
||||||
|
|
||||||
data.publicKeyRing = _.pluck(wallet.copayers, 'xPubKey')
|
data.publicKeyRing = _.pluck(wallet.copayers, 'xPubKey')
|
||||||
|
|
||||||
|
@ -140,8 +144,13 @@ API.prototype._doRequest = function(method, url, args, data, cb) {
|
||||||
var reqSignature;
|
var reqSignature;
|
||||||
data = data || {};
|
data = data || {};
|
||||||
|
|
||||||
if (data.signingPrivKey)
|
if (method == 'get') {
|
||||||
reqSignature = _signRequest(method, url, args, data.signingPrivKey);
|
if (data.roPrivKey)
|
||||||
|
reqSignature = _signRequest(method, url, args, data.roPrivKey);
|
||||||
|
} else {
|
||||||
|
if (data.rwPrivKey)
|
||||||
|
reqSignature = _signRequest(method, url, args, data.rwPrivKey);
|
||||||
|
}
|
||||||
|
|
||||||
var absUrl = this.baseUrl + url;
|
var absUrl = this.baseUrl + url;
|
||||||
var args = {
|
var args = {
|
||||||
|
@ -186,7 +195,8 @@ API.prototype._doGetRequest = function(url, data, cb) {
|
||||||
API.prototype._initData = function(network, walletPrivKey, m, n) {
|
API.prototype._initData = function(network, walletPrivKey, m, n) {
|
||||||
var xPrivKey = new Bitcore.HDPrivateKey(network);
|
var xPrivKey = new Bitcore.HDPrivateKey(network);
|
||||||
var xPubKey = (new Bitcore.HDPublicKey(xPrivKey)).toString();
|
var xPubKey = (new Bitcore.HDPublicKey(xPrivKey)).toString();
|
||||||
var signingPrivKey = (new Bitcore.HDPrivateKey(xPrivKey)).derive('m/1/0').privateKey;
|
var roPrivKey = xPrivKey.derive('m/1/0').privateKey;
|
||||||
|
var rwPrivKey = xPrivKey.derive('m/1/1').privateKey;
|
||||||
var sharedEncryptingKey = Bitcore.crypto.Hash.sha256(walletPrivKey.toBuffer()).slice(0, 16).toString('base64');
|
var sharedEncryptingKey = Bitcore.crypto.Hash.sha256(walletPrivKey.toBuffer()).slice(0, 16).toString('base64');
|
||||||
var copayerId = WalletUtils.xPubToCopayerId(xPubKey);
|
var copayerId = WalletUtils.xPubToCopayerId(xPubKey);
|
||||||
|
|
||||||
|
@ -197,7 +207,8 @@ API.prototype._initData = function(network, walletPrivKey, m, n) {
|
||||||
network: network,
|
network: network,
|
||||||
m: m,
|
m: m,
|
||||||
n: n,
|
n: n,
|
||||||
signingPrivKey: signingPrivKey.toWIF(),
|
roPrivKey: roPrivKey.toWIF(),
|
||||||
|
rwPrivKey: rwPrivKey.toWIF(),
|
||||||
walletPrivKey: walletPrivKey.toWIF(),
|
walletPrivKey: walletPrivKey.toWIF(),
|
||||||
sharedEncryptingKey: sharedEncryptingKey,
|
sharedEncryptingKey: sharedEncryptingKey,
|
||||||
};
|
};
|
||||||
|
@ -339,13 +350,16 @@ API.prototype.sendTxProposal = function(opts, cb) {
|
||||||
this._loadAndCheck(function(err, data) {
|
this._loadAndCheck(function(err, data) {
|
||||||
if (err) return cb(err);
|
if (err) return cb(err);
|
||||||
|
|
||||||
|
if (!data.rwPrivKey)
|
||||||
|
return cb('No key to generate proposals');
|
||||||
|
|
||||||
var args = {
|
var args = {
|
||||||
toAddress: opts.toAddress,
|
toAddress: opts.toAddress,
|
||||||
amount: opts.amount,
|
amount: opts.amount,
|
||||||
message: _encryptMessage(opts.message, data.sharedEncryptingKey),
|
message: _encryptMessage(opts.message, data.sharedEncryptingKey),
|
||||||
};
|
};
|
||||||
var hash = WalletUtils.getProposalHash(args.toAddress, args.amount, args.message);
|
var hash = WalletUtils.getProposalHash(args.toAddress, args.amount, args.message);
|
||||||
args.proposalSignature = WalletUtils.signMessage(hash, data.signingPrivKey);
|
args.proposalSignature = WalletUtils.signMessage(hash, data.rwPrivKey);
|
||||||
log.debug('Generating & signing tx proposal hash -> Hash: ', hash, ' Signature: ', args.proposalSignature);
|
log.debug('Generating & signing tx proposal hash -> Hash: ', hash, ' Signature: ', args.proposalSignature);
|
||||||
|
|
||||||
var url = '/v1/txproposals/';
|
var url = '/v1/txproposals/';
|
||||||
|
@ -385,8 +399,16 @@ API.prototype.getBalance = function(cb) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
API.prototype.export = function(cb) {
|
/**
|
||||||
|
* export
|
||||||
|
*
|
||||||
|
* @param opts.access =['full', 'readonly', 'readwrite']
|
||||||
|
*/
|
||||||
|
API.prototype.export = function(opts, cb) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
$.shouldBeFunction(cb);
|
||||||
|
opts = opts || {};
|
||||||
|
var access = opts.access || 'full';
|
||||||
|
|
||||||
this._loadAndCheck(function(err, data) {
|
this._loadAndCheck(function(err, data) {
|
||||||
if (err) return cb(err);
|
if (err) return cb(err);
|
||||||
|
@ -396,13 +418,29 @@ API.prototype.export = function(cb) {
|
||||||
|
|
||||||
_.each(WALLET_CRITICAL_DATA, function(k) {
|
_.each(WALLET_CRITICAL_DATA, function(k) {
|
||||||
var d;
|
var d;
|
||||||
if (k === 'publicKeyRing') {
|
|
||||||
|
if (access != 'full' && k === 'xPrivKey') {
|
||||||
|
v.push(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skips own pub key IF priv key is exported
|
||||||
|
if (access == 'full' && k === 'publicKeyRing') {
|
||||||
d = _.without(data[k], myXPubKey);
|
d = _.without(data[k], myXPubKey);
|
||||||
} else {
|
} else {
|
||||||
d = data[k];
|
d = data[k];
|
||||||
}
|
}
|
||||||
v.push(d);
|
v.push(d);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (access != 'full') {
|
||||||
|
v.push(data.copayerId);
|
||||||
|
v.push(data.roPrivKey);
|
||||||
|
if (access == 'readwrite') {
|
||||||
|
v.push(data.rwPrivKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return cb(null, JSON.stringify(v));
|
return cb(null, JSON.stringify(v));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -420,20 +458,28 @@ API.prototype.import = function(str, cb) {
|
||||||
var inData = JSON.parse(str);
|
var inData = JSON.parse(str);
|
||||||
var i = 0;
|
var i = 0;
|
||||||
|
|
||||||
_.each(WALLET_CRITICAL_DATA, function(k) {
|
_.each(WALLET_CRITICAL_DATA.concat(WALLET_EXTRA_DATA), function(k) {
|
||||||
data[k] = inData[i++];
|
data[k] = inData[i++];
|
||||||
if (!data[k])
|
|
||||||
return cb('Invalid wallet data');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
var xPubKey = (new Bitcore.HDPublicKey(data.xPrivKey)).toString();
|
if (data.xPrivKey) {
|
||||||
|
var xpriv = new Bitcore.HDPrivateKey(data.xPrivKey);
|
||||||
|
var xPubKey = new Bitcore.HDPublicKey(xpriv).toString();
|
||||||
|
data.publicKeyRing.unshift(xPubKey);
|
||||||
|
data.copayerId = WalletUtils.xPubToCopayerId(xPubKey);
|
||||||
|
data.roPrivKey = xpriv.derive('m/1/0').privateKey.toWIF();
|
||||||
|
data.rwPrivKey = xpriv.derive('m/1/1').privateKey.toWIF();
|
||||||
|
}
|
||||||
|
|
||||||
data.publicKeyRing.unshift(xPubKey);
|
|
||||||
data.copayerId = WalletUtils.xPubToCopayerId(xPubKey);
|
|
||||||
data.n = data.publicKeyRing.length;
|
data.n = data.publicKeyRing.length;
|
||||||
data.signingPrivKey = (new Bitcore.HDPrivateKey(data.xPrivKey)).derive('m/1/0').privateKey.toWIF();
|
|
||||||
data.network = data.xPrivKey.substr(0, 4) === 'tprv' ? 'testnet' : 'livenet';
|
if (!data.copayerId || !data.n || !data.m)
|
||||||
self.storage.save(data, cb);
|
return cb('Invalid source data');
|
||||||
|
|
||||||
|
data.network = data.publicKeyRing[0].substr(0, 4) == 'tpub' ? 'testnet' : 'livenet';
|
||||||
|
self.storage.save(data, function(err) {
|
||||||
|
return cb(err, WalletUtils.accessFromData(data));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -60,7 +60,7 @@ Verifier.checkTxProposal = function(data, txp) {
|
||||||
if (WalletUtils.xPubToCopayerId(xPubKey) === txp.creatorId) return true;
|
if (WalletUtils.xPubToCopayerId(xPubKey) === txp.creatorId) return true;
|
||||||
});
|
});
|
||||||
if (!creatorXPubKey) return false;
|
if (!creatorXPubKey) return false;
|
||||||
var creatorSigningPubKey = (new Bitcore.HDPublicKey(creatorXPubKey)).derive('m/1/0').publicKey.toString();
|
var creatorSigningPubKey = (new Bitcore.HDPublicKey(creatorXPubKey)).derive('m/1/1').publicKey.toString();
|
||||||
|
|
||||||
var hash = WalletUtils.getProposalHash(txp.toAddress, txp.amount, txp.encryptedMessage || txp.message);
|
var hash = WalletUtils.getProposalHash(txp.toAddress, txp.amount, txp.encryptedMessage || txp.message);
|
||||||
log.debug('Regenerating & verifying tx proposal hash -> Hash: ', hash, ' Signature: ', txp.proposalSignature);
|
log.debug('Regenerating & verifying tx proposal hash -> Hash: ', hash, ' Signature: ', txp.proposalSignature);
|
||||||
|
|
|
@ -88,10 +88,13 @@ ExpressApp.start = function(opts) {
|
||||||
code: 'NOTAUTHORIZED'
|
code: 'NOTAUTHORIZED'
|
||||||
}), res, req);
|
}), res, req);
|
||||||
|
|
||||||
|
var readOnly = req.method == 'GET';
|
||||||
|
|
||||||
var auth = {
|
var auth = {
|
||||||
copayerId: credentials.copayerId,
|
copayerId: credentials.copayerId,
|
||||||
message: req.method.toLowerCase() + '|' + req.url + '|' + JSON.stringify(req.body),
|
message: req.method.toLowerCase() + '|' + req.url + '|' + JSON.stringify(req.body),
|
||||||
signature: credentials.signature,
|
signature: credentials.signature,
|
||||||
|
readOnly: readOnly,
|
||||||
};
|
};
|
||||||
WalletService.getInstanceWithAuth(auth, function(err, server) {
|
WalletService.getInstanceWithAuth(auth, function(err, server) {
|
||||||
if (err) return returnError(err, res, req);
|
if (err) return returnError(err, res, req);
|
||||||
|
|
|
@ -11,7 +11,8 @@ var AddressManager = require('./addressmanager');
|
||||||
var Utils = require('../walletutils');
|
var Utils = require('../walletutils');
|
||||||
|
|
||||||
|
|
||||||
var MESSAGE_SIGNING_PATH = "m/1/0";
|
var RO_SIGNING_PATH = "m/1/0";
|
||||||
|
var RW_SIGNING_PATH = "m/1/1";
|
||||||
|
|
||||||
function Copayer() {
|
function Copayer() {
|
||||||
this.version = '1.0.0';
|
this.version = '1.0.0';
|
||||||
|
@ -30,7 +31,8 @@ Copayer.create = function(opts) {
|
||||||
x.id = Utils.xPubToCopayerId(x.xPubKey);
|
x.id = Utils.xPubToCopayerId(x.xPubKey);
|
||||||
x.name = opts.name;
|
x.name = opts.name;
|
||||||
x.xPubKeySignature = opts.xPubKeySignature; // So third parties can check independently
|
x.xPubKeySignature = opts.xPubKeySignature; // So third parties can check independently
|
||||||
x.signingPubKey = x.getSigningPubKey();
|
x.roPubKey = x.getROPubKey();
|
||||||
|
x.rwPubKey = x.getRWPubKey();
|
||||||
x.addressManager = AddressManager.create({
|
x.addressManager = AddressManager.create({
|
||||||
copayerIndex: opts.copayerIndex
|
copayerIndex: opts.copayerIndex
|
||||||
});
|
});
|
||||||
|
@ -46,7 +48,8 @@ Copayer.fromObj = function(obj) {
|
||||||
x.name = obj.name;
|
x.name = obj.name;
|
||||||
x.xPubKey = obj.xPubKey;
|
x.xPubKey = obj.xPubKey;
|
||||||
x.xPubKeySignature = obj.xPubKeySignature;
|
x.xPubKeySignature = obj.xPubKeySignature;
|
||||||
x.signingPubKey = obj.signingPubKey;
|
x.roPubKey = obj.roPubKey;
|
||||||
|
x.rwPubKey = obj.rwPubKey;
|
||||||
x.addressManager = AddressManager.fromObj(obj.addressManager);
|
x.addressManager = AddressManager.fromObj(obj.addressManager);
|
||||||
|
|
||||||
return x;
|
return x;
|
||||||
|
@ -60,8 +63,14 @@ Copayer.prototype.getPublicKey = function(path) {
|
||||||
.toString();
|
.toString();
|
||||||
};
|
};
|
||||||
|
|
||||||
Copayer.prototype.getSigningPubKey = function() {
|
Copayer.prototype.getROPubKey = function() {
|
||||||
return this.getPublicKey(MESSAGE_SIGNING_PATH);
|
return this.getPublicKey(RO_SIGNING_PATH);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Copayer.prototype.getRWPubKey = function() {
|
||||||
|
return this.getPublicKey(RW_SIGNING_PATH);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
module.exports = Copayer;
|
module.exports = Copayer;
|
||||||
|
|
|
@ -66,7 +66,8 @@ WalletService.getInstance = function() {
|
||||||
* @param {Object} opts
|
* @param {Object} opts
|
||||||
* @param {string} opts.copayerId - The copayer id making the request.
|
* @param {string} opts.copayerId - The copayer id making the request.
|
||||||
* @param {string} opts.message - The contents of the request to be signed.
|
* @param {string} opts.message - The contents of the request to be signed.
|
||||||
* @param {string} opts.signature - Signature of message to be verified using the copayer's signingPubKey.
|
* @param {string} opts.signature - Signature of message to be verified using the copayer's roPubKey / rwPubKey
|
||||||
|
* @param {string} opts.readOnly - Signature of message to be verified using the copayer's roPubKey / rwPubKey
|
||||||
*/
|
*/
|
||||||
WalletService.getInstanceWithAuth = function(opts, cb) {
|
WalletService.getInstanceWithAuth = function(opts, cb) {
|
||||||
|
|
||||||
|
@ -78,8 +79,12 @@ WalletService.getInstanceWithAuth = function(opts, cb) {
|
||||||
if (err) return cb(err);
|
if (err) return cb(err);
|
||||||
if (!copayer) return cb(new ClientError('NOTAUTHORIZED', 'Copayer not found'));
|
if (!copayer) return cb(new ClientError('NOTAUTHORIZED', 'Copayer not found'));
|
||||||
|
|
||||||
var isValid = server._verifySignature(opts.message, opts.signature, copayer.signingPubKey);
|
var pubKey = opts.readOnly ? copayer.roPubKey : copayer.rwPubKey;
|
||||||
if (!isValid) return cb(new ClientError('NOTAUTHORIZED', 'Invalid signature'));
|
var isValid = server._verifySignature(opts.message, opts.signature,
|
||||||
|
pubKey);
|
||||||
|
|
||||||
|
if (!isValid)
|
||||||
|
return cb(new ClientError('NOTAUTHORIZED', 'Invalid signature'));
|
||||||
|
|
||||||
server.copayerId = opts.copayerId;
|
server.copayerId = opts.copayerId;
|
||||||
server.walletId = copayer.walletId;
|
server.walletId = copayer.walletId;
|
||||||
|
@ -307,7 +312,7 @@ WalletService.prototype.verifyMessageSignature = function(opts, cb) {
|
||||||
|
|
||||||
var copayer = wallet.getCopayer(self.copayerId);
|
var copayer = wallet.getCopayer(self.copayerId);
|
||||||
|
|
||||||
var isValid = self._verifySignature(opts.message, opts.signature, copayer.signingPubKey);
|
var isValid = self._verifySignature(opts.message, opts.signature, copayer.rwPubKey);
|
||||||
return cb(null, isValid);
|
return cb(null, isValid);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -477,7 +482,7 @@ WalletService.prototype.createTx = function(opts, cb) {
|
||||||
|
|
||||||
var copayer = wallet.getCopayer(self.copayerId);
|
var copayer = wallet.getCopayer(self.copayerId);
|
||||||
var hash = WalletUtils.getProposalHash(opts.toAddress, opts.amount, opts.message);
|
var hash = WalletUtils.getProposalHash(opts.toAddress, opts.amount, opts.message);
|
||||||
if (!self._verifySignature(hash, opts.proposalSignature, copayer.signingPubKey))
|
if (!self._verifySignature(hash, opts.proposalSignature, copayer.rwPubKey))
|
||||||
return cb(new ClientError('Invalid proposal signature'));
|
return cb(new ClientError('Invalid proposal signature'));
|
||||||
|
|
||||||
var toAddress;
|
var toAddress;
|
||||||
|
|
|
@ -84,7 +84,8 @@ Storage.prototype.storeWalletAndUpdateCopayersLookup = function(wallet, cb) {
|
||||||
_.each(wallet.copayers, function(copayer) {
|
_.each(wallet.copayers, function(copayer) {
|
||||||
var value = {
|
var value = {
|
||||||
walletId: wallet.id,
|
walletId: wallet.id,
|
||||||
signingPubKey: copayer.signingPubKey,
|
roPubKey: copayer.roPubKey,
|
||||||
|
rwPubKey: copayer.rwPubKey,
|
||||||
};
|
};
|
||||||
ops.push({
|
ops.push({
|
||||||
type: 'put',
|
type: 'put',
|
||||||
|
|
|
@ -31,8 +31,19 @@ WalletUtils.signMessage = function(text, privKey) {
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
WalletUtils.accessFromData = function(data) {
|
||||||
|
if (data.xPrivKey)
|
||||||
|
return 'full';
|
||||||
|
|
||||||
|
if (data.rwPrivKey)
|
||||||
|
return 'readwrite';
|
||||||
|
|
||||||
|
return 'readonly';
|
||||||
|
};
|
||||||
|
|
||||||
WalletUtils.verifyMessage = function(text, signature, pubKey) {
|
WalletUtils.verifyMessage = function(text, signature, pubKey) {
|
||||||
$.checkArgument(text, pubKey);
|
$.checkArgument(text);
|
||||||
|
$.checkArgument(pubKey);
|
||||||
|
|
||||||
if (!signature)
|
if (!signature)
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -81,6 +81,9 @@ fsmock.reset = function() {
|
||||||
fsmock._get = function(name) {
|
fsmock._get = function(name) {
|
||||||
return content[name];
|
return content[name];
|
||||||
};
|
};
|
||||||
|
fsmock._set = function(name, data) {
|
||||||
|
return content[name] = data;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
var blockExplorerMock = {};
|
var blockExplorerMock = {};
|
||||||
|
@ -274,6 +277,92 @@ describe('client API ', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('Access control & export', function() {
|
||||||
|
it('should not be able to create address if not rwPubKey', function(done) {
|
||||||
|
helpers.createAndJoinWallet(clients, 1, 1, function(err) {
|
||||||
|
should.not.exist(err);
|
||||||
|
|
||||||
|
var data = JSON.parse(fsmock._get('client0'));
|
||||||
|
delete data.rwPrivKey;
|
||||||
|
fsmock._set('client0', JSON.stringify(data));
|
||||||
|
data.rwPrivKey = null;
|
||||||
|
clients[0].createAddress(function(err, x0) {
|
||||||
|
err.code.should.equal('NOTAUTHORIZED');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('should not be able to create address from a ro export', function(done) {
|
||||||
|
helpers.createAndJoinWallet(clients, 1, 1, function(err) {
|
||||||
|
should.not.exist(err);
|
||||||
|
clients[0].export({
|
||||||
|
access: 'readonly'
|
||||||
|
}, function(err, str) {
|
||||||
|
should.not.exist(err);
|
||||||
|
clients[1].import(str, function(err, wallet) {
|
||||||
|
should.not.exist(err);
|
||||||
|
clients[1].createAddress(function(err, x0) {
|
||||||
|
err.code.should.equal('NOTAUTHORIZED');
|
||||||
|
clients[0].createAddress(function(err, x0) {
|
||||||
|
should.not.exist(err);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('should be able to create address from a rw export', function(done) {
|
||||||
|
helpers.createAndJoinWallet(clients, 1, 1, function(err) {
|
||||||
|
should.not.exist(err);
|
||||||
|
clients[0].export({
|
||||||
|
access: 'readwrite'
|
||||||
|
}, function(err, str) {
|
||||||
|
should.not.exist(err);
|
||||||
|
clients[1].import(str, function(err, wallet) {
|
||||||
|
should.not.exist(err);
|
||||||
|
clients[1].createAddress(function(err, x0) {
|
||||||
|
should.not.exist(err);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not be able to create tx proposals from a rw export', function(done) {
|
||||||
|
helpers.createAndJoinWallet(clients, 1, 1, function(err, w) {
|
||||||
|
should.not.exist(err);
|
||||||
|
clients[0].export({
|
||||||
|
access: 'readwrite'
|
||||||
|
}, function(err, str) {
|
||||||
|
clients[1].import(str, function(err, wallet) {
|
||||||
|
should.not.exist(err);
|
||||||
|
clients[1].createAddress(function(err, x0) {
|
||||||
|
should.not.exist(err);
|
||||||
|
blockExplorerMock.setUtxo(x0, 1, 1);
|
||||||
|
var opts = {
|
||||||
|
amount: 10000000,
|
||||||
|
toAddress: 'n2TBMPzPECGUfcT2EByiTJ12TPZkhN2mN5',
|
||||||
|
message: 'hello 1-1',
|
||||||
|
};
|
||||||
|
clients[1].sendTxProposal(opts, function(err, x) {
|
||||||
|
should.not.exist(err);
|
||||||
|
clients[1].signTxProposal(x, function(err, tx) {
|
||||||
|
err.code.should.be.equal('BADSIGNATURES');
|
||||||
|
clients[1].getTxProposals({}, function(err, txs) {
|
||||||
|
should.not.exist(err);
|
||||||
|
txs[0].status.should.equal('pending');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('Address Creation', function() {
|
describe('Address Creation', function() {
|
||||||
it('should be able to create address in all copayers in a 2-3 wallet', function(done) {
|
it('should be able to create address in all copayers in a 2-3 wallet', function(done) {
|
||||||
|
@ -348,7 +437,6 @@ describe('client API ', function() {
|
||||||
clients[0]._load(function(err, data) {
|
clients[0]._load(function(err, data) {
|
||||||
var url = '/v1/addresses/';
|
var url = '/v1/addresses/';
|
||||||
clients[0]._doPostRequest(url, {}, data, function(err, address) {
|
clients[0]._doPostRequest(url, {}, data, function(err, address) {
|
||||||
console.log('[clientApi.js.326:address:]', address); //TODO
|
|
||||||
|
|
||||||
// Tamper data
|
// Tamper data
|
||||||
address.publicKeys = ['0322defe0c3eb9fcd8bc01878e6dbca7a6846880908d214b50a752445040cc5c54',
|
address.publicKeys = ['0322defe0c3eb9fcd8bc01878e6dbca7a6846880908d214b50a752445040cc5c54',
|
||||||
|
@ -376,7 +464,7 @@ describe('client API ', function() {
|
||||||
it('round trip #import #export', function(done) {
|
it('round trip #import #export', function(done) {
|
||||||
helpers.createAndJoinWallet(clients, 2, 2, function(err, w) {
|
helpers.createAndJoinWallet(clients, 2, 2, function(err, w) {
|
||||||
should.not.exist(err);
|
should.not.exist(err);
|
||||||
clients[0].export(function(err, str) {
|
clients[0].export({}, function(err, str) {
|
||||||
should.not.exist(err);
|
should.not.exist(err);
|
||||||
var original = JSON.parse(fsmock._get('client0'));
|
var original = JSON.parse(fsmock._get('client0'));
|
||||||
clients[2].import(str, function(err, wallet) {
|
clients[2].import(str, function(err, wallet) {
|
||||||
|
@ -460,6 +548,7 @@ describe('client API ', function() {
|
||||||
should.not.exist(err);
|
should.not.exist(err);
|
||||||
clients[1].rejectTxProposal(x, 'rejection comment', function(err, tx1) {
|
clients[1].rejectTxProposal(x, 'rejection comment', function(err, tx1) {
|
||||||
should.not.exist(err);
|
should.not.exist(err);
|
||||||
|
|
||||||
clients[2].getTxProposals({}, function(err, txs) {
|
clients[2].getTxProposals({}, function(err, txs) {
|
||||||
should.not.exist(err);
|
should.not.exist(err);
|
||||||
txs[0].message.should.equal('some message');
|
txs[0].message.should.equal('some message');
|
||||||
|
|
|
@ -200,7 +200,7 @@ describe('Copay server', function() {
|
||||||
var xpriv = TestData.copayers[0].xPrivKey;
|
var xpriv = TestData.copayers[0].xPrivKey;
|
||||||
var priv = Bitcore.HDPrivateKey
|
var priv = Bitcore.HDPrivateKey
|
||||||
.fromString(xpriv)
|
.fromString(xpriv)
|
||||||
.derive('m/1/0')
|
.derive('m/1/1')
|
||||||
.privateKey
|
.privateKey
|
||||||
.toString();
|
.toString();
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ var keyPair = {
|
||||||
|
|
||||||
var message = {
|
var message = {
|
||||||
text: 'hello world',
|
text: 'hello world',
|
||||||
signature: '3045022100addd20e5413865d65d561ad2979f2289a40d52594b1f804840babd9a63e4ebbf02204b86285e1fcab02df772e7a1325fc4b511ecad79a8f80a2bd1ad8bfa858ac3d4', // with 5c0e043a513032907d181325a8e7990b076c0af15ed13dc5e611cda9bb3ae52a
|
signature: '304402204c5c754f35ac3f4766f246a71f956dee8233964a51991c261f2611c79e906641022070cb6ed2b1c3afae8110f110f332bebf30b3154ff8cf56fb1fe8db5cc09ce07d', // with e57d978f1d4a43df38a451ca2adf70c0416c202a89badf068e0d6de2023879cc (m/1/1 of copayer's xpriv)
|
||||||
};
|
};
|
||||||
|
|
||||||
var copayers = [{
|
var copayers = [{
|
||||||
|
@ -13,71 +13,52 @@ var copayers = [{
|
||||||
xPrivKey: 'xprv9s21ZrQH143K2rMHbXTJmWTuFx6ssqn1vyRoZqPkCXYchBSkp5ey8kMJe84sxfXq5uChWH4gk94rWbXZt2opN9kg4ufKGvUM7HQSLjnoh7e',
|
xPrivKey: 'xprv9s21ZrQH143K2rMHbXTJmWTuFx6ssqn1vyRoZqPkCXYchBSkp5ey8kMJe84sxfXq5uChWH4gk94rWbXZt2opN9kg4ufKGvUM7HQSLjnoh7e',
|
||||||
xPubKey: 'xpub661MyMwAqRbcFLRkhYzK8eQdoywNHJVsJCMQNDoMks5bZymuMcyDgYfnVQYq2Q9npnVmdTAthYGc3N3uxm5sEdnTpSqBc4YYTAhNnoSxCm9',
|
xPubKey: 'xpub661MyMwAqRbcFLRkhYzK8eQdoywNHJVsJCMQNDoMks5bZymuMcyDgYfnVQYq2Q9npnVmdTAthYGc3N3uxm5sEdnTpSqBc4YYTAhNnoSxCm9',
|
||||||
xPubKeySignature: '30440220192ae7345d980f45f908bd63ccad60ce04270d07b91f1a9d92424a07a38af85202201591f0f71dd4e79d9206d2306862e6b8375e13a62c193953d768e884b6fb5a46', // signed using keyPair.priv
|
xPubKeySignature: '30440220192ae7345d980f45f908bd63ccad60ce04270d07b91f1a9d92424a07a38af85202201591f0f71dd4e79d9206d2306862e6b8375e13a62c193953d768e884b6fb5a46', // signed using keyPair.priv
|
||||||
privKey: '5c0e043a513032907d181325a8e7990b076c0af15ed13dc5e611cda9bb3ae52a', // derived with 'm/1/0'
|
privKey: 'e57d978f1d4a43df38a451ca2adf70c0416c202a89badf068e0d6de2023879cc', // derived with 'm/1/1'
|
||||||
pubKey: '03814ac7decf64321a3c6967bfb746112fdb5b583531cd512cc3787eaf578947dc',
|
|
||||||
}, {
|
}, {
|
||||||
id: '0235223413f8219260aa892c518969701af7c339284afecc7044f98dd47d48754b',
|
id: '0235223413f8219260aa892c518969701af7c339284afecc7044f98dd47d48754b',
|
||||||
xPrivKey: 'xprv9s21ZrQH143K2JgXh8Va3Taq22D2gXw2nYULffV5dc9acQvAmB3KhomPKGwV9AbVsBcAXMW2QxCnvmHU1rVtHRfZwTxdEEAN5ZojRYdryQ1',
|
xPrivKey: 'xprv9s21ZrQH143K2JgXh8Va3Taq22D2gXw2nYULffV5dc9acQvAmB3KhomPKGwV9AbVsBcAXMW2QxCnvmHU1rVtHRfZwTxdEEAN5ZojRYdryQ1',
|
||||||
xPubKey: 'xpub661MyMwAqRbcEnkzoA2aQbXZa43X5zet9mPwU3thBwgZVDFKJiMaFc5sAYS97qVtMxpvceittobtoH2JKmpweSN1CLSe91hiE1Wrf5YJhsQ',
|
xPubKey: 'xpub661MyMwAqRbcEnkzoA2aQbXZa43X5zet9mPwU3thBwgZVDFKJiMaFc5sAYS97qVtMxpvceittobtoH2JKmpweSN1CLSe91hiE1Wrf5YJhsQ',
|
||||||
xPubKeySignature: '3045022100b9079d0d9b70da828b0e9c776fe01c5c13fc254a314c0b09a638c8695ec9360c022079922950779080569163c692ed8ee882f9ef37e7b2dff03de9c6756e7599960e',
|
xPubKeySignature: '3045022100b9079d0d9b70da828b0e9c776fe01c5c13fc254a314c0b09a638c8695ec9360c022079922950779080569163c692ed8ee882f9ef37e7b2dff03de9c6756e7599960e',
|
||||||
privKey: '7708f0b7a60da9b88893e41eb2f59acf13ddc38edd794e1af9c2b57d35e99d85',
|
|
||||||
pubKey: '0266efb3b02973233636b153296486cdbc1728e1f42a1062f030af193ab14e1321',
|
|
||||||
}, {
|
}, {
|
||||||
id: '023f31628856799ce6a12d02d362fbf6acc2c112207eeeb7bd34a048ec2127645d',
|
id: '023f31628856799ce6a12d02d362fbf6acc2c112207eeeb7bd34a048ec2127645d',
|
||||||
xPrivKey: 'xprv9s21ZrQH143K2DoxHNrecLmp121HU4nZRB57jj2cGmSkp9Wrgz2AevFT98AcYocYXEyyWDwC1JUn13beDjQU87FwfCaHiWhgoSx9G31tmDa',
|
xPrivKey: 'xprv9s21ZrQH143K2DoxHNrecLmp121HU4nZRB57jj2cGmSkp9Wrgz2AevFT98AcYocYXEyyWDwC1JUn13beDjQU87FwfCaHiWhgoSx9G31tmDa',
|
||||||
xPubKey: 'xpub661MyMwAqRbcEhtRPQPeyUiYZ3qmsXWQnPziY7SDq6yjgwr1EXLRCiZvzQsfnQehH8hBzCxNPYCJXx51QKcbervpkBK931H1A9F39z5E1XD',
|
xPubKey: 'xpub661MyMwAqRbcEhtRPQPeyUiYZ3qmsXWQnPziY7SDq6yjgwr1EXLRCiZvzQsfnQehH8hBzCxNPYCJXx51QKcbervpkBK931H1A9F39z5E1XD',
|
||||||
xPubKeySignature: '3045022100ed04aca131acf6f030018a7e3dd564788bbad5528e9edb7880f032ae6917bf10022022879ca8a60700c9c3bf9d603ae2555d4be1914084c912579b4bc09a432e9c32',
|
xPubKeySignature: '3045022100ed04aca131acf6f030018a7e3dd564788bbad5528e9edb7880f032ae6917bf10022022879ca8a60700c9c3bf9d603ae2555d4be1914084c912579b4bc09a432e9c32',
|
||||||
privKey: 'a8e36ab9e065a5cc0551938c225d40542b7780a84aff55cb52e4eb4d7f64de11',
|
|
||||||
pubKey: '033e44b94789a591380787effbad0a01f41aedd7ed315c931efc51284e99ae34a9',
|
|
||||||
}, {
|
}, {
|
||||||
id: '024062aae3d6575b03a8532e618feeb70d6926c069e24cca0cb1a24db4dec6562e',
|
id: '024062aae3d6575b03a8532e618feeb70d6926c069e24cca0cb1a24db4dec6562e',
|
||||||
xPrivKey: 'xprv9s21ZrQH143K3dHn6j7zuLSnnMpBAceHfQFDm6R3wbkD26pTSUuZ7JY4549T8mMhwwUeq6SW6guDwy6cUhqs6PwoF2svKKkLjJdeKi1BzUn',
|
xPrivKey: 'xprv9s21ZrQH143K3dHn6j7zuLSnnMpBAceHfQFDm6R3wbkD26pTSUuZ7JY4549T8mMhwwUeq6SW6guDwy6cUhqs6PwoF2svKKkLjJdeKi1BzUn',
|
||||||
xPubKey: 'xpub661MyMwAqRbcG7NFCkf1GUPXLPefa5N92dApZUpfVwHBtu9bz2Dof6rXvKjioVpLXRBHKeyX2AxLdtaCbUtz2zx3dE9F4mPkDsARrPaGsSL',
|
xPubKey: 'xpub661MyMwAqRbcG7NFCkf1GUPXLPefa5N92dApZUpfVwHBtu9bz2Dof6rXvKjioVpLXRBHKeyX2AxLdtaCbUtz2zx3dE9F4mPkDsARrPaGsSL',
|
||||||
xPubKeySignature: '3045022100a17dee46810e379aa37104ec1a4e20276aa41eac67b7e555475b15db6f6ee8ca0220472d114368f6d78bd8dba5c5fed77b22437341621d7879331bc48f7ee7701853',
|
xPubKeySignature: '3045022100a17dee46810e379aa37104ec1a4e20276aa41eac67b7e555475b15db6f6ee8ca0220472d114368f6d78bd8dba5c5fed77b22437341621d7879331bc48f7ee7701853',
|
||||||
privKey: '68cc2c1776f3456fcdef12812e634b6adbd676c5b8168a4b2547cf73c5363cfd',
|
|
||||||
pubKey: '027597e3a18c829dfdd92f875ae87f2aa4654cbb13db28a92a19401cf6f8ac18cb',
|
|
||||||
}, {
|
}, {
|
||||||
id: '031114b2afe5d1e87d834ac71798028724f8f89e2d3324b5e6abb513fdcde2e69d',
|
id: '031114b2afe5d1e87d834ac71798028724f8f89e2d3324b5e6abb513fdcde2e69d',
|
||||||
xPrivKey: 'xprv9s21ZrQH143K3Wac3NKJ8nZsBsn4HboPtCD2LDB8zkXcmo9q97efAi9i6KkcyBoJ4vjD59corGsdJebmNXud4nH3bCBSo64uWfwauo3Kdco',
|
xPrivKey: 'xprv9s21ZrQH143K3Wac3NKJ8nZsBsn4HboPtCD2LDB8zkXcmo9q97efAi9i6KkcyBoJ4vjD59corGsdJebmNXud4nH3bCBSo64uWfwauo3Kdco',
|
||||||
xPubKey: 'xpub661MyMwAqRbcFzf59PrJVvWbjucYh4XFFR8d8bakZ64bebUygexuiWUBwa7EnFosoFstoocLQTrLPeAQThonSrYTDQ18gkS219dLJuwUHDb',
|
xPubKey: 'xpub661MyMwAqRbcFzf59PrJVvWbjucYh4XFFR8d8bakZ64bebUygexuiWUBwa7EnFosoFstoocLQTrLPeAQThonSrYTDQ18gkS219dLJuwUHDb',
|
||||||
xPubKeySignature: '30440220023c1902434aaca0c2ed3c92262d56ee4296cd5c7598b009b8556deab2df89e70220714117970debf5cc1232441aa2d1ce335b5b99958acb2d000c4241e2a2fc567f',
|
xPubKeySignature: '30440220023c1902434aaca0c2ed3c92262d56ee4296cd5c7598b009b8556deab2df89e70220714117970debf5cc1232441aa2d1ce335b5b99958acb2d000c4241e2a2fc567f',
|
||||||
privKey: 'fc97d94e97857b2016a1e68ab0313ce2ea5a791638e8eb0bb6d5a5aa72de01df',
|
|
||||||
pubKey: '03a3e04a3fb218b074306ca2a4995ec1ea97ef2b73dd54edfbae9ee56651dd21cc',
|
|
||||||
}, {
|
}, {
|
||||||
id: '03d5352705f1f587dad9814cbdae1ec627a9252f18ca677cd9d5243d41d44a7fea',
|
id: '03d5352705f1f587dad9814cbdae1ec627a9252f18ca677cd9d5243d41d44a7fea',
|
||||||
xPrivKey: 'xprv9s21ZrQH143K2Hxh1Xj59WjAimbDuubNBFknLYPr563BBziVuLhMvw7F3CkYMYj1y6QSbDnKQVHrMGekXi7awjLwKR1XWcMoR3eKEcH65Pa',
|
xPrivKey: 'xprv9s21ZrQH143K2Hxh1Xj59WjAimbDuubNBFknLYPr563BBziVuLhMvw7F3CkYMYj1y6QSbDnKQVHrMGekXi7awjLwKR1XWcMoR3eKEcH65Pa',
|
||||||
xPubKey: 'xpub661MyMwAqRbcEn3A7ZG5WefuGoRiKNKDYUgP8voTdRaA4o3eSt1cUjRitVc75a2gsifRDufbicYXeQCchDvkRKSSTWi1uy7PPaNHnerGvTa',
|
xPubKey: 'xpub661MyMwAqRbcEn3A7ZG5WefuGoRiKNKDYUgP8voTdRaA4o3eSt1cUjRitVc75a2gsifRDufbicYXeQCchDvkRKSSTWi1uy7PPaNHnerGvTa',
|
||||||
xPubKeySignature: '3044022042f063cd154a359f1d49202d79efc0737c47077ee50e36ed3d796327b9b29a9602206a4baf2902b49d1cb265eb31b9ab12d470fd023a11d9d425679a1630ba0b0e29',
|
xPubKeySignature: '3044022042f063cd154a359f1d49202d79efc0737c47077ee50e36ed3d796327b9b29a9602206a4baf2902b49d1cb265eb31b9ab12d470fd023a11d9d425679a1630ba0b0e29',
|
||||||
privKey: '72381546174e6165e39853510bf3353645cb2e19e0499d3bb1d8aef0027352eb',
|
|
||||||
pubKey: '037e4163f69c3e2b05e980e3ac5dac06730894b1ba520a15a9d8221245d4bfe2a4',
|
|
||||||
}, {
|
}, {
|
||||||
id: '036fa483c6f226afecb2782a69ab11d1cf43c26f277280cb2ec69f88681d8a4418',
|
id: '036fa483c6f226afecb2782a69ab11d1cf43c26f277280cb2ec69f88681d8a4418',
|
||||||
xPrivKey: 'xprv9s21ZrQH143K2L2wxVQ5nJ8FrTjz2KHB4wy83Xu6y6jdxmzrKNWvh6g7apPJqwj5NjhrmyJ6TYe9Hk4fbYx4tRg7Zk4Y7dAdgej4RVeUBTn',
|
xPrivKey: 'xprv9s21ZrQH143K2L2wxVQ5nJ8FrTjz2KHB4wy83Xu6y6jdxmzrKNWvh6g7apPJqwj5NjhrmyJ6TYe9Hk4fbYx4tRg7Zk4Y7dAdgej4RVeUBTn',
|
||||||
xPubKey: 'xpub661MyMwAqRbcEp7R4Ww69S4zQVaURn12SAtiqvJiXSGcqaKzruqBEtzbS97V145KiYxsW4g9M3pqsibfc5mtbMn4R52v7bnnrHGAoiHb1pz',
|
xPubKey: 'xpub661MyMwAqRbcEp7R4Ww69S4zQVaURn12SAtiqvJiXSGcqaKzruqBEtzbS97V145KiYxsW4g9M3pqsibfc5mtbMn4R52v7bnnrHGAoiHb1pz',
|
||||||
xPubKeySignature: '304402207b082a63fc39b90a0f18edeb20d191430f11a9e5378681f15a68aac052aa858202201fd127b374e4a301a45f0c73d2a747d156a60075e643308e489a672bd0a7b4fb',
|
xPubKeySignature: '304402207b082a63fc39b90a0f18edeb20d191430f11a9e5378681f15a68aac052aa858202201fd127b374e4a301a45f0c73d2a747d156a60075e643308e489a672bd0a7b4fb',
|
||||||
privKey: 'daff48eab1268c23e9ecde993b93ace4a375e7c627da0d3662746c7e3fecbdfa',
|
|
||||||
pubKey: '02035c09deeef7436df39a09ef2167129c686c6177185216525b9d778af96bacd7',
|
|
||||||
}, {
|
}, {
|
||||||
id: '03bc5f0504c8dd480926dbe90449ccc7b2fc4c96e79f1cb8ffcd31731e2ee8db9b',
|
id: '03bc5f0504c8dd480926dbe90449ccc7b2fc4c96e79f1cb8ffcd31731e2ee8db9b',
|
||||||
xPrivKey: 'xprv9s21ZrQH143K3Pqe7LhTkE84VM6GysvSvggfhS3KbHvgBLaaQeR9YNePRo3vpLMVBv5SNhNVAaEDyj6Q8vMRVYM4X9bWYCiPgsJXkH8WzX1',
|
xPrivKey: 'xprv9s21ZrQH143K3Pqe7LhTkE84VM6GysvSvggfhS3KbHvgBLaaQeR9YNePRo3vpLMVBv5SNhNVAaEDyj6Q8vMRVYM4X9bWYCiPgsJXkH8WzX1',
|
||||||
xPubKey: 'xpub661MyMwAqRbcFLRkhYzK8eQdoywNHJVsJCMQNDoMks5bZymuMcyDgYfnVQYq2Q9npnVmdTAthYGc3N3uxm5sEdnTpSqBc4YYTAhNnoSxCm9',
|
xPubKey: 'xpub661MyMwAqRbcFLRkhYzK8eQdoywNHJVsJCMQNDoMks5bZymuMcyDgYfnVQYq2Q9npnVmdTAthYGc3N3uxm5sEdnTpSqBc4YYTAhNnoSxCm9',
|
||||||
xPubKeySignature: '30440220192ae7345d980f45f908bd63ccad60ce04270d07b91f1a9d92424a07a38af85202201591f0f71dd4e79d9206d2306862e6b8375e13a62c193953d768e884b6fb5a46',
|
xPubKeySignature: '30440220192ae7345d980f45f908bd63ccad60ce04270d07b91f1a9d92424a07a38af85202201591f0f71dd4e79d9206d2306862e6b8375e13a62c193953d768e884b6fb5a46',
|
||||||
privKey: '5c0e043a513032907d181325a8e7990b076c0af15ed13dc5e611cda9bb3ae52a',
|
|
||||||
pubKey: '03814ac7decf64321a3c6967bfb746112fdb5b583531cd512cc3787eaf578947dc',
|
|
||||||
}, {
|
}, {
|
||||||
id: '02ec9ec4fd013c67312e0365128d470a89e13e53d9b44f026c5eca0b6e2ab9ae29',
|
id: '02ec9ec4fd013c67312e0365128d470a89e13e53d9b44f026c5eca0b6e2ab9ae29',
|
||||||
xPrivKey: 'xprv9s21ZrQH143K2QSAHGxQhUsJYFDcZ6h2oiTjKFPmbnzeNzXgRW73NwX7ifBgbJ35eHGR7toyj9CCXB6Wzf5iCjj3YDuJuvBoJFJsiQAdTUH',
|
xPrivKey: 'xprv9s21ZrQH143K2QSAHGxQhUsJYFDcZ6h2oiTjKFPmbnzeNzXgRW73NwX7ifBgbJ35eHGR7toyj9CCXB6Wzf5iCjj3YDuJuvBoJFJsiQAdTUH',
|
||||||
xPubKey: 'xpub661MyMwAqRbcEtWdPJVR4cp36H46xZQtAwPL7doPA8XdFnrpy3RHvjqbZxERYNMd4E2tt84xy4F2PqtKkHFDzZbSAaUabp36oZDwwPEqFjK',
|
xPubKey: 'xpub661MyMwAqRbcEtWdPJVR4cp36H46xZQtAwPL7doPA8XdFnrpy3RHvjqbZxERYNMd4E2tt84xy4F2PqtKkHFDzZbSAaUabp36oZDwwPEqFjK',
|
||||||
xPubKeySignature: '304502210081a88684d4e27cab752d0df6a746aeb4bbcac57e73edd3847ffb43f1cf6740b20220312598f47dc5e775ea2ba97048764675afa4796173115049b4dacc8882b5c7b7',
|
xPubKeySignature: '304502210081a88684d4e27cab752d0df6a746aeb4bbcac57e73edd3847ffb43f1cf6740b20220312598f47dc5e775ea2ba97048764675afa4796173115049b4dacc8882b5c7b7',
|
||||||
privKey: '01273975489d85ac06f2e47677149420cd4901264cb40e3a756ff901acfc11f6',
|
|
||||||
pubKey: '0210eed257f41c9a991f8bd9523f66c3a83c1aab27cd2ccf233f8f5c3caef77e7a',
|
|
||||||
}, {
|
}, {
|
||||||
id: '02ec5f9178f77b306bc92362f3c1ef8f10b8cce44dc255ba20435f24d6459981db',
|
id: '02ec5f9178f77b306bc92362f3c1ef8f10b8cce44dc255ba20435f24d6459981db',
|
||||||
xPrivKey: 'xprv9s21ZrQH143K2bj7Azs1rCkumDJmbNveDA96wDJThzsDEJjBngkFXEr646AbvrTAfRd2scqq7hN48fGXesobx4sKRkddCrLaCpoWUkMJErj',
|
xPrivKey: 'xprv9s21ZrQH143K2bj7Azs1rCkumDJmbNveDA96wDJThzsDEJjBngkFXEr646AbvrTAfRd2scqq7hN48fGXesobx4sKRkddCrLaCpoWUkMJErj',
|
||||||
xPubKey: 'xpub661MyMwAqRbcF5oaH2Q2DLheKF9FzqeVaP4hjbi5GLQC774LLE4W53AZuMztQ6e6SMmEMj8K8zsP3iMMnJgK2PawWZCh7QcdgAg7eJWSJFr',
|
xPubKey: 'xpub661MyMwAqRbcF5oaH2Q2DLheKF9FzqeVaP4hjbi5GLQC774LLE4W53AZuMztQ6e6SMmEMj8K8zsP3iMMnJgK2PawWZCh7QcdgAg7eJWSJFr',
|
||||||
xPubKeySignature: '304402207781231f8bd9a679938057373702afdeec43e84b5b239e2e4dc8e35c63e4ee7102207f7ed929c81dfb59ebd14f56609dcd8255de6337c967704340a2089080fd896f',
|
xPubKeySignature: '304402207781231f8bd9a679938057373702afdeec43e84b5b239e2e4dc8e35c63e4ee7102207f7ed929c81dfb59ebd14f56609dcd8255de6337c967704340a2089080fd896f',
|
||||||
privKey: 'dda3b5d7c6a9294a71b3ae116e69be756bed55f1caf68205e7889baa5fc76dd6',
|
|
||||||
pubKey: '03182e14c3d256359ba478d3c7842ad882141f0e61ef905dcb6b5c9786f958f325',
|
|
||||||
}, ];
|
}, ];
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue