commit
b8532f662f
21
app.js
21
app.js
|
@ -39,6 +39,9 @@ app.use(bodyParser.json({
|
||||||
limit: POST_LIMIT
|
limit: POST_LIMIT
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
app.use(require('morgan')('dev'));
|
||||||
|
|
||||||
|
|
||||||
var port = process.env.COPAY_PORT || 3001;
|
var port = process.env.COPAY_PORT || 3001;
|
||||||
var router = express.Router();
|
var router = express.Router();
|
||||||
|
|
||||||
|
@ -59,7 +62,6 @@ function returnError(err, res, req) {
|
||||||
}
|
}
|
||||||
var m = message || err.toString();
|
var m = message || err.toString();
|
||||||
|
|
||||||
console.log('[app.js.60]'); //TODO
|
|
||||||
log.error('Error: ' + req.url + ' :' + code + ':' + m);
|
log.error('Error: ' + req.url + ' :' + code + ':' + m);
|
||||||
res.status(code || 500).json({
|
res.status(code || 500).json({
|
||||||
error: m,
|
error: m,
|
||||||
|
@ -144,6 +146,17 @@ router.get('/v1/wallets/', function(req, res) {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
router.get('/v1/txproposals/', function(req, res) {
|
||||||
|
getServerWithAuth(req, res, function(server) {
|
||||||
|
server.getPendingTxs({}, function(err, pendings) {
|
||||||
|
if (err) return returnError(err, res, req);
|
||||||
|
res.json(pendings);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
router.post('/v1/txproposals/', function(req, res) {
|
router.post('/v1/txproposals/', function(req, res) {
|
||||||
getServerWithAuth(req, res, function(server) {
|
getServerWithAuth(req, res, function(server) {
|
||||||
server.createTx(req.body, function(err, txp) {
|
server.createTx(req.body, function(err, txp) {
|
||||||
|
@ -181,9 +194,9 @@ router.get('/v1/balance/', function(req, res) {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post('/v1/txproposals/:id/signatures', function(req, res) {
|
router.post('/v1/txproposals/:id/signatures/', function(req, res) {
|
||||||
req.body.txProposalId = req.params['id'];
|
|
||||||
getServerWithAuth(req, res, function(server) {
|
getServerWithAuth(req, res, function(server) {
|
||||||
|
req.body.txProposalId = req.params['id'];
|
||||||
server.signTx(req.body, function(err, txp) {
|
server.signTx(req.body, function(err, txp) {
|
||||||
if (err) return returnError(err, res, req);
|
if (err) return returnError(err, res, req);
|
||||||
res.end();
|
res.end();
|
||||||
|
@ -192,8 +205,8 @@ router.post('/v1/txproposals/:id/signatures', function(req, res) {
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post('/v1/txproposals/:id/rejections', function(req, res) {
|
router.post('/v1/txproposals/:id/rejections', function(req, res) {
|
||||||
req.body.txProposalId = req.params['id'];
|
|
||||||
getServerWithAuth(req, res, function(server) {
|
getServerWithAuth(req, res, function(server) {
|
||||||
|
req.body.txProposalId = req.params['id'];
|
||||||
server.signTx(req.body, function(err, txp) {
|
server.signTx(req.body, function(err, txp) {
|
||||||
if (err) return returnError(err, res, req);
|
if (err) return returnError(err, res, req);
|
||||||
res.end();
|
res.end();
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
|
|
||||||
var program = require('commander');
|
var program = require('commander');
|
||||||
var cli = require('../lib/clilib.js');
|
|
||||||
|
|
||||||
program
|
program
|
||||||
.version('0.0.1')
|
.version('0.0.1')
|
||||||
|
@ -12,6 +11,7 @@ program
|
||||||
.command('addresses', 'list addresses')
|
.command('addresses', 'list addresses')
|
||||||
.command('balance', 'wallet balance')
|
.command('balance', 'wallet balance')
|
||||||
.command('send <address> <amount> <note>', 'send bitcoins')
|
.command('send <address> <amount> <note>', 'send bitcoins')
|
||||||
|
.command('sign <txpId>', 'sign a Transaction Proposal')
|
||||||
.parse(process.argv);
|
.parse(process.argv);
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
|
|
||||||
var program = require('commander');
|
var program = require('commander');
|
||||||
var CliLib = require('../lib/clilib.js');
|
var ClientLib = require('../lib/clientlib.js');
|
||||||
var common = require('./common');
|
var common = require('./common');
|
||||||
|
|
||||||
program
|
program
|
||||||
|
@ -11,7 +11,7 @@ program
|
||||||
.parse(process.argv);
|
.parse(process.argv);
|
||||||
|
|
||||||
var args = program.args;
|
var args = program.args;
|
||||||
var cli = new CliLib({
|
var cli = new ClientLib({
|
||||||
filename: program.config
|
filename: program.config
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -2,8 +2,8 @@
|
||||||
|
|
||||||
var _ = require('lodash');
|
var _ = require('lodash');
|
||||||
var program = require('commander');
|
var program = require('commander');
|
||||||
var CliLib = require('../lib/clilib.js');
|
|
||||||
var common = require('./common');
|
var common = require('./common');
|
||||||
|
var ClientLib = require('../lib/clientlib.js');
|
||||||
|
|
||||||
program
|
program
|
||||||
.version('0.0.1')
|
.version('0.0.1')
|
||||||
|
@ -12,7 +12,7 @@ program
|
||||||
.parse(process.argv);
|
.parse(process.argv);
|
||||||
|
|
||||||
var args = program.args;
|
var args = program.args;
|
||||||
var cli = new CliLib({
|
var cli = new ClientLib({
|
||||||
filename: program.config
|
filename: program.config
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
|
|
||||||
var program = require('commander');
|
var program = require('commander');
|
||||||
var CliLib = require('../lib/clilib.js');
|
var ClientLib = require('../lib/clientlib.js');
|
||||||
var common = require('./common');
|
var common = require('./common');
|
||||||
|
|
||||||
program
|
program
|
||||||
|
@ -11,7 +11,7 @@ program
|
||||||
.parse(process.argv);
|
.parse(process.argv);
|
||||||
|
|
||||||
var args = program.args;
|
var args = program.args;
|
||||||
var cli = new CliLib({
|
var cli = new ClientLib({
|
||||||
filename: program.config
|
filename: program.config
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
|
|
||||||
var program = require('commander');
|
var program = require('commander');
|
||||||
var CliLib = require('../lib/clilib.js');
|
var ClientLib = require('../lib/clientlib.js');
|
||||||
var common = require('./common');
|
var common = require('./common');
|
||||||
|
|
||||||
program
|
program
|
||||||
|
@ -21,7 +21,7 @@ var network = program.network;
|
||||||
|
|
||||||
var mn = common.parseMN(args[1]);
|
var mn = common.parseMN(args[1]);
|
||||||
|
|
||||||
var cli = new CliLib({
|
var cli = new ClientLib({
|
||||||
filename: program.config
|
filename: program.config
|
||||||
});
|
});
|
||||||
cli.createWallet(walletName, copayerName, mn[0], mn[1], network, function(err, secret) {
|
cli.createWallet(walletName, copayerName, mn[0], mn[1], network, function(err, secret) {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
|
|
||||||
var program = require('commander');
|
var program = require('commander');
|
||||||
var CliLib = require('../lib/clilib.js');
|
var ClientLib = require('../lib/clientlib.js');
|
||||||
var common = require('./common');
|
var common = require('./common');
|
||||||
|
|
||||||
program
|
program
|
||||||
|
@ -17,7 +17,7 @@ if (!args[0])
|
||||||
var secret = args[0];
|
var secret = args[0];
|
||||||
var copayerName = args[1] || process.env.USER;
|
var copayerName = args[1] || process.env.USER;
|
||||||
|
|
||||||
var cli = new CliLib({
|
var cli = new ClientLib({
|
||||||
filename: program.config
|
filename: program.config
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
|
|
||||||
var program = require('commander');
|
var program = require('commander');
|
||||||
var CliLib = require('../lib/clilib.js');
|
var ClientLib = require('../lib/clientlib.js');
|
||||||
var common = require('./common');
|
var common = require('./common');
|
||||||
|
|
||||||
program
|
program
|
||||||
|
@ -19,8 +19,8 @@ if (!args[0] || !args[1] || !args[2])
|
||||||
var amount = args[1];
|
var amount = args[1];
|
||||||
var message = args[2];
|
var message = args[2];
|
||||||
|
|
||||||
var cli = new CliLib({
|
var cli = new ClientLib({
|
||||||
filename: program.config
|
filename: program.config
|
||||||
});
|
});
|
||||||
|
|
||||||
cli.send({toAddress: address, amount: amount, message:message}, function(err, x) {
|
cli.send({toAddress: address, amount: amount, message:message}, function(err, x) {
|
||||||
|
@ -30,4 +30,4 @@ cli.send({toAddress: address, amount: amount, message:message}, function(err, x)
|
||||||
|
|
||||||
if (program.verbose)
|
if (program.verbose)
|
||||||
console.log('* Raw Server Response:\n', x); //TODO
|
console.log('* Raw Server Response:\n', x); //TODO
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
var _ = require('lodash');
|
||||||
|
var program = require('commander');
|
||||||
|
var ClientLib = require('../lib/clientlib.js');
|
||||||
|
var common = require('./common');
|
||||||
|
|
||||||
|
program
|
||||||
|
.version('0.0.1')
|
||||||
|
.option('-c,--config [file]', 'Wallet config filename')
|
||||||
|
.option('-v,--verbose', 'be verbose')
|
||||||
|
.usage('[options] <txpid>')
|
||||||
|
.parse(process.argv);
|
||||||
|
|
||||||
|
var args = program.args;
|
||||||
|
if (!args[0])
|
||||||
|
program.help();
|
||||||
|
|
||||||
|
var txpid = args[0];
|
||||||
|
|
||||||
|
var cli = new ClientLib({
|
||||||
|
filename: program.config
|
||||||
|
});
|
||||||
|
|
||||||
|
cli.txProposals({}, function(err, x) {
|
||||||
|
common.die(err);
|
||||||
|
|
||||||
|
if (program.verbose)
|
||||||
|
console.log('* Raw Server Response:\n', x); //TODO
|
||||||
|
|
||||||
|
var txps = _.filter(x, function(x) {
|
||||||
|
return _.endsWith(common.shortID(x.id), txpid);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!txps.length)
|
||||||
|
common.die('Could not find TX Proposal:' + txpid);
|
||||||
|
|
||||||
|
if (txps.length > 1)
|
||||||
|
common.die('More that one TX Proposals match:' + txpid + ' : ' + _.map(txps, function(x) {
|
||||||
|
return x.id;
|
||||||
|
}).join(' '));;
|
||||||
|
|
||||||
|
var txp = txps[0];
|
||||||
|
cli.sign(txp, function(err, x) {
|
||||||
|
common.die(err);
|
||||||
|
|
||||||
|
if (program.verbose)
|
||||||
|
console.log('* Raw Server Response:\n', x); //TODO
|
||||||
|
|
||||||
|
console.log('Transaction signed.');
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,7 +1,9 @@
|
||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
var _ = require('lodash');
|
||||||
var program = require('commander');
|
var program = require('commander');
|
||||||
var CliLib = require('../lib/clilib.js');
|
|
||||||
|
var ClientLib = require('../lib/clientlib.js');
|
||||||
var common = require('./common');
|
var common = require('./common');
|
||||||
|
|
||||||
program
|
program
|
||||||
|
@ -11,14 +13,33 @@ program
|
||||||
.parse(process.argv);
|
.parse(process.argv);
|
||||||
|
|
||||||
var args = program.args;
|
var args = program.args;
|
||||||
var cli = new CliLib({
|
var cli = new ClientLib({
|
||||||
filename: program.config
|
filename: program.config
|
||||||
});
|
});
|
||||||
|
|
||||||
cli.status(function(err, x) {
|
cli.status(function(err, res) {
|
||||||
common.die(err);
|
common.die(err);
|
||||||
|
|
||||||
|
var x = res.wallet;
|
||||||
console.log('* Wallet %s [%s]: %d-%d %s ', x.name, x.isTestnet ? 'testnet' : 'livenet', x.m, x.n, x.status);
|
console.log('* Wallet %s [%s]: %d-%d %s ', x.name, x.isTestnet ? 'testnet' : 'livenet', x.m, x.n, x.status);
|
||||||
|
|
||||||
|
var x = res.balance;
|
||||||
|
console.log('* Balance %d (Locked: %d)', x.totalAmount, x.lockedAmount);
|
||||||
|
|
||||||
|
if (!_.isEmpty(res.pendingTxps)) {
|
||||||
|
console.log("* TX Proposals:")
|
||||||
|
_.each(res.pendingTxps, function(x) {
|
||||||
|
console.log("\t%s [%s by %s] %dSAT => %s", common.shortID(x.id), x.message, x.creatorName, x.amount, x.toAddress);
|
||||||
|
|
||||||
|
if (!_.isEmpty(x.actions)) {
|
||||||
|
console.log('\t\t * Actions');
|
||||||
|
console.log('\t\t', _.map(x.actions, function(a) {
|
||||||
|
return a.copayerName + ': ' + a.type + ''
|
||||||
|
}).join('. '));
|
||||||
|
}
|
||||||
|
|
||||||
if (program.verbose)
|
if (program.verbose)
|
||||||
console.log('* Raw Server Response:\n', x); //TODO
|
console.log('* Raw Server Response:\n', res); //TODO
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -24,4 +24,8 @@ common.parseMN = function(MN) {
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
common.shortID = function(id) {
|
||||||
|
return id.substr(id.length - 4);
|
||||||
|
};
|
||||||
|
|
||||||
module.exports = common;
|
module.exports = common;
|
||||||
|
|
|
@ -11,7 +11,7 @@ var fs = require('fs')
|
||||||
var Bitcore = require('bitcore')
|
var Bitcore = require('bitcore')
|
||||||
var SignUtils = require('./signutils');
|
var SignUtils = require('./signutils');
|
||||||
|
|
||||||
var BASE_URL = 'http://localhost:3001/copay/api/';
|
var BASE_URL = 'http://localhost:3001/copay/api';
|
||||||
|
|
||||||
function _createProposalOpts(opts, signingKey) {
|
function _createProposalOpts(opts, signingKey) {
|
||||||
var msg = opts.toAddress + '|' + opts.amount + '|' + opts.message;
|
var msg = opts.toAddress + '|' + opts.amount + '|' + opts.message;
|
||||||
|
@ -43,29 +43,28 @@ function _signRequest(url, args, privKey) {
|
||||||
return SignUtils.sign(message, privKey);
|
return SignUtils.sign(message, privKey);
|
||||||
};
|
};
|
||||||
|
|
||||||
function _createXPrivKey() {
|
function _createXPrivKey(network) {
|
||||||
return new Bitcore.HDPrivateKey().toString();
|
return new Bitcore.HDPrivateKey(network).toString();
|
||||||
};
|
};
|
||||||
|
|
||||||
function CliLib(opts) {
|
function ClientLib(opts) {
|
||||||
if (!opts.filename) {
|
if (!opts.filename) {
|
||||||
throw new Error('Please set the config filename');
|
throw new Error('Please set the config filename');
|
||||||
}
|
}
|
||||||
this.filename = opts.filename;
|
this.filename = opts.filename;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
ClientLib.prototype._save = function(data) {
|
||||||
CliLib.prototype._save = function(data) {
|
|
||||||
fs.writeFileSync(this.filename, JSON.stringify(data));
|
fs.writeFileSync(this.filename, JSON.stringify(data));
|
||||||
};
|
};
|
||||||
|
|
||||||
CliLib.prototype._load = function() {
|
ClientLib.prototype._load = function() {
|
||||||
try {
|
try {
|
||||||
return JSON.parse(fs.readFileSync(this.filename));
|
return JSON.parse(fs.readFileSync(this.filename));
|
||||||
} catch (ex) {}
|
} catch (ex) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
CliLib.prototype._loadAndCheck = function() {
|
ClientLib.prototype._loadAndCheck = function() {
|
||||||
var data = this._load();
|
var data = this._load();
|
||||||
if (!data) {
|
if (!data) {
|
||||||
log.error('Wallet file not found.');
|
log.error('Wallet file not found.');
|
||||||
|
@ -85,18 +84,18 @@ CliLib.prototype._loadAndCheck = function() {
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
CliLib.prototype.createWallet = function(walletName, copayerName, m, n, network, cb) {
|
ClientLib.prototype.createWallet = function(walletName, copayerName, m, n, network, cb) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
network = network || 'livenet';
|
||||||
|
|
||||||
var data = this._load();
|
var data = this._load();
|
||||||
if (data) return cb('File ' + this.filename + ' already contains a wallet');
|
if (data) return cb('File ' + this.filename + ' already contains a wallet');
|
||||||
|
|
||||||
// Generate wallet key pair to verify copayers
|
// Generate wallet key pair to verify copayers
|
||||||
var privKey = new Bitcore.PrivateKey();
|
var privKey = new Bitcore.PrivateKey(null, network);
|
||||||
var pubKey = privKey.toPublicKey();
|
var pubKey = privKey.toPublicKey();
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
xPrivKey: _createXPrivKey(),
|
|
||||||
m: m,
|
m: m,
|
||||||
n: n,
|
n: n,
|
||||||
walletPrivKey: privKey.toString(),
|
walletPrivKey: privKey.toString(),
|
||||||
|
@ -107,7 +106,7 @@ CliLib.prototype.createWallet = function(walletName, copayerName, m, n, network,
|
||||||
m: m,
|
m: m,
|
||||||
n: n,
|
n: n,
|
||||||
pubKey: pubKey.toString(),
|
pubKey: pubKey.toString(),
|
||||||
network: network || 'livenet',
|
network: network,
|
||||||
};
|
};
|
||||||
|
|
||||||
request({
|
request({
|
||||||
|
@ -123,11 +122,10 @@ CliLib.prototype.createWallet = function(walletName, copayerName, m, n, network,
|
||||||
}
|
}
|
||||||
|
|
||||||
var walletId = body.walletId;
|
var walletId = body.walletId;
|
||||||
var secret = walletId + ':' + privKey.toString();
|
var secret = walletId + ':' + privKey.toString() + ':' + (network ? 'T' : 'L');
|
||||||
data.secret = secret;
|
data.secret = secret;
|
||||||
|
|
||||||
self._save(data);
|
self._save(data);
|
||||||
|
|
||||||
self._joinWallet(data, secret, copayerName, function(err) {
|
self._joinWallet(data, secret, copayerName, function(err) {
|
||||||
if (err) return cb(err);
|
if (err) return cb(err);
|
||||||
|
|
||||||
|
@ -136,18 +134,21 @@ CliLib.prototype.createWallet = function(walletName, copayerName, m, n, network,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
CliLib.prototype._joinWallet = function(data, secret, copayerName, cb) {
|
ClientLib.prototype._joinWallet = function(data, secret, copayerName, cb) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
data = data || {};
|
||||||
|
|
||||||
var secretSplit = secret.split(':');
|
var secretSplit = secret.split(':');
|
||||||
var walletId = secretSplit[0];
|
var walletId = secretSplit[0];
|
||||||
|
|
||||||
var walletPrivKey = Bitcore.PrivateKey.fromString(secretSplit[1]);
|
var walletPrivKey = Bitcore.PrivateKey.fromString(secretSplit[1]);
|
||||||
|
var network = secretSplit[2] == 'T' ? 'testnet' : 'livenet';
|
||||||
|
data.xPrivKey = _createXPrivKey(network);
|
||||||
|
|
||||||
var xPubKey = new Bitcore.HDPublicKey(data.xPrivKey);
|
var xPubKey = new Bitcore.HDPublicKey(data.xPrivKey);
|
||||||
var xPubKeySignature = SignUtils.sign(xPubKey.toString(), walletPrivKey);
|
var xPubKeySignature = SignUtils.sign(xPubKey.toString(), walletPrivKey);
|
||||||
|
|
||||||
var signingPrivKey = (new Bitcore.HDPrivateKey(data.xPrivKey)).derive('m/1/0').privateKey;
|
var signingPrivKey = (new Bitcore.HDPrivateKey(data.xPrivKey)).derive('m/1/0').privateKey;
|
||||||
|
|
||||||
var args = {
|
var args = {
|
||||||
walletId: walletId,
|
walletId: walletId,
|
||||||
name: copayerName,
|
name: copayerName,
|
||||||
|
@ -180,20 +181,16 @@ CliLib.prototype._joinWallet = function(data, secret, copayerName, cb) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
CliLib.prototype.joinWallet = function(secret, copayerName, cb) {
|
ClientLib.prototype.joinWallet = function(secret, copayerName, cb) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
var data = this._load();
|
var data = this._load();
|
||||||
if (data) return cb('File ' + this.filename + ' already contains a wallet');
|
if (data) return cb('File ' + this.filename + ' already contains a wallet');
|
||||||
|
|
||||||
data = {
|
|
||||||
xPrivKey: _createXPrivKey(),
|
|
||||||
};
|
|
||||||
|
|
||||||
self._joinWallet(data, secret, copayerName, cb);
|
self._joinWallet(data, secret, copayerName, cb);
|
||||||
};
|
};
|
||||||
|
|
||||||
CliLib.prototype.status = function(cb) {
|
ClientLib.prototype.status = function(cb) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
var data = this._loadAndCheck();
|
var data = this._loadAndCheck();
|
||||||
|
@ -253,7 +250,7 @@ CliLib.prototype.status = function(cb) {
|
||||||
* @param inArgs.amount
|
* @param inArgs.amount
|
||||||
* @param inArgs.message
|
* @param inArgs.message
|
||||||
*/
|
*/
|
||||||
CliLib.prototype.send = function(inArgs, cb) {
|
ClientLib.prototype.send = function(inArgs, cb) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
var data = this._loadAndCheck();
|
var data = this._loadAndCheck();
|
||||||
|
@ -282,16 +279,16 @@ CliLib.prototype.send = function(inArgs, cb) {
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO check change address
|
// TODO check change address
|
||||||
CliLib.prototype.sign = function(proposalId, cb) {
|
ClientLib.prototype.sign = function(proposalId, cb) {
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
CliLib.prototype.reject = function(proposalId, cb) {
|
ClientLib.prototype.reject = function(proposalId, cb) {
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Get addresses
|
// Get addresses
|
||||||
CliLib.prototype.addresses = function(cb) {
|
ClientLib.prototype.addresses = function(cb) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
var data = this._loadAndCheck();
|
var data = this._loadAndCheck();
|
||||||
|
@ -320,7 +317,7 @@ CliLib.prototype.addresses = function(cb) {
|
||||||
|
|
||||||
// Creates a new address
|
// Creates a new address
|
||||||
// TODO: verify derivation!!
|
// TODO: verify derivation!!
|
||||||
CliLib.prototype.address = function(cb) {
|
ClientLib.prototype.address = function(cb) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
var data = this._loadAndCheck();
|
var data = this._loadAndCheck();
|
||||||
|
@ -346,11 +343,11 @@ CliLib.prototype.address = function(cb) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
CliLib.prototype.history = function(limit, cb) {
|
ClientLib.prototype.history = function(limit, cb) {
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
CliLib.prototype.balance = function(cb) {
|
ClientLib.prototype.balance = function(cb) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
var data = this._loadAndCheck();
|
var data = this._loadAndCheck();
|
||||||
|
@ -377,7 +374,7 @@ CliLib.prototype.balance = function(cb) {
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
CliLib.prototype.txProposals = function(cb) {
|
ClientLib.prototype.txProposals = function(opts, cb) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
var data = this._loadAndCheck();
|
var data = this._loadAndCheck();
|
||||||
|
@ -403,5 +400,65 @@ CliLib.prototype.txProposals = function(cb) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
ClientLib.prototype.sign = function(txp, cb) {
|
||||||
|
var self = this;
|
||||||
|
var data = this._loadAndCheck();
|
||||||
|
|
||||||
module.exports = CliLib;
|
|
||||||
|
//Derive proper key to sign, for each input
|
||||||
|
var privs = [],
|
||||||
|
derived = {};
|
||||||
|
|
||||||
|
var network = new Bitcore.Address(txp.toAddress).network.name;
|
||||||
|
var xpriv = new Bitcore.HDPrivateKey(data.xPrivKey, network);
|
||||||
|
|
||||||
|
_.each(txp.inputs, function(i) {
|
||||||
|
if (!derived[i.path]) {
|
||||||
|
derived[i.path] = xpriv.derive(i.path).privateKey;
|
||||||
|
}
|
||||||
|
privs.push(derived[i.path]);
|
||||||
|
});
|
||||||
|
|
||||||
|
var t = new Bitcore.Transaction();
|
||||||
|
_.each(txp.inputs, function(i) {
|
||||||
|
t.from(i, i.publicKeys, txp.requiredSignatures);
|
||||||
|
});
|
||||||
|
|
||||||
|
t.to(txp.toAddress, txp.amount)
|
||||||
|
.change(txp.changeAddress)
|
||||||
|
.sign(privs);
|
||||||
|
|
||||||
|
var signatures = [];
|
||||||
|
_.each(privs, function(p) {
|
||||||
|
var s = t.getSignatures(p)[0].signature.toDER().toString('hex');
|
||||||
|
signatures.push(s);
|
||||||
|
});
|
||||||
|
|
||||||
|
var url = '/v1/txproposals/' + txp.id + '/signatures/';
|
||||||
|
var args = {
|
||||||
|
signatures: signatures
|
||||||
|
};
|
||||||
|
var reqSignature = _signRequest(url, args, data.signingPrivKey);
|
||||||
|
console.log('[clientlib.js.441:reqSignature:]',url, args, reqSignature); //TODO
|
||||||
|
|
||||||
|
request({
|
||||||
|
headers: {
|
||||||
|
'x-identity': data.copayerId,
|
||||||
|
'x-signature': reqSignature,
|
||||||
|
},
|
||||||
|
method: 'post',
|
||||||
|
url: _getUrl(url),
|
||||||
|
body: args,
|
||||||
|
json: true,
|
||||||
|
}, function(err, res, body) {
|
||||||
|
if (err) return cb(err);
|
||||||
|
if (res.statusCode != 200) {
|
||||||
|
_parseError(body);
|
||||||
|
return cb('Request error');
|
||||||
|
}
|
||||||
|
return cb(null, body);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = ClientLib;
|
|
@ -69,6 +69,19 @@ TxProposal.prototype._updateStatus = function() {
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
TxProposal.prototype._getCurrentSignatures = function() {
|
||||||
|
var acceptedActions = _.filter(this.actions, function(x) {
|
||||||
|
return x && x.type == 'accept';
|
||||||
|
});
|
||||||
|
|
||||||
|
return _.map(acceptedActions, function(x) {
|
||||||
|
return {
|
||||||
|
signatures: x.signatures,
|
||||||
|
xpub: x.xpub,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
TxProposal.prototype._getBitcoreTx = function() {
|
TxProposal.prototype._getBitcoreTx = function() {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
|
@ -81,6 +94,13 @@ TxProposal.prototype._getBitcoreTx = function() {
|
||||||
.change(this.changeAddress);
|
.change(this.changeAddress);
|
||||||
|
|
||||||
t._updateChangeOutput();
|
t._updateChangeOutput();
|
||||||
|
|
||||||
|
|
||||||
|
var sigs = this._getCurrentSignatures();
|
||||||
|
_.each(sigs, function(x) {
|
||||||
|
self._addSignaturesToBitcoreTx(t, x.signatures, x.xpub);
|
||||||
|
});
|
||||||
|
|
||||||
return t;
|
return t;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -121,22 +141,20 @@ TxProposal.prototype.getActionBy = function(copayerId) {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
TxProposal.prototype.addAction = function(copayerId, type, signatures) {
|
TxProposal.prototype.addAction = function(copayerId, type, signatures, xpub) {
|
||||||
var action = new TxProposalAction({
|
var action = new TxProposalAction({
|
||||||
copayerId: copayerId,
|
copayerId: copayerId,
|
||||||
type: type,
|
type: type,
|
||||||
signatures: signatures,
|
signatures: signatures,
|
||||||
|
xpub: xpub,
|
||||||
});
|
});
|
||||||
this.actions[copayerId] = action;
|
this.actions[copayerId] = action;
|
||||||
this._updateStatus();
|
this._updateStatus();
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: no sure we should receive xpub or a list of pubkeys (pre derived)
|
TxProposal.prototype._addSignaturesToBitcoreTx = function(t, signatures, xpub) {
|
||||||
TxProposal.prototype.checkSignatures = function(signatures, xpub) {
|
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
var t = this._getBitcoreTx();
|
|
||||||
|
|
||||||
if (signatures.length != this.inputs.length)
|
if (signatures.length != this.inputs.length)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
@ -159,17 +177,25 @@ TxProposal.prototype.checkSignatures = function(signatures, xpub) {
|
||||||
|
|
||||||
t.applySignature(s);
|
t.applySignature(s);
|
||||||
oks++;
|
oks++;
|
||||||
} catch (e) {
|
} catch (e) {};
|
||||||
// TODO only for debug now
|
|
||||||
console.log('DEBUG ONLY:', e.message); //TODO
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
return oks === t.inputs.length;
|
|
||||||
|
if (oks != t.inputs.length)
|
||||||
|
throw new Error('wrong signatures');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
TxProposal.prototype.sign = function(copayerId, signatures) {
|
TxProposal.prototype.sign = function(copayerId, signatures, xpub) {
|
||||||
this.addAction(copayerId, 'accept', signatures);
|
|
||||||
|
// Tests signatures are OK
|
||||||
|
var t = this._getBitcoreTx();
|
||||||
|
try {
|
||||||
|
this._addSignaturesToBitcoreTx(t, signatures, xpub);
|
||||||
|
this.addAction(copayerId, 'accept', signatures, xpub);
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
TxProposal.prototype.reject = function(copayerId) {
|
TxProposal.prototype.reject = function(copayerId) {
|
||||||
|
|
|
@ -7,6 +7,7 @@ function TxProposalAction(opts) {
|
||||||
this.copayerId = opts.copayerId;
|
this.copayerId = opts.copayerId;
|
||||||
this.type = opts.type || (opts.signatures ? 'accept' : 'reject');
|
this.type = opts.type || (opts.signatures ? 'accept' : 'reject');
|
||||||
this.signatures = opts.signatures;
|
this.signatures = opts.signatures;
|
||||||
|
this.xpub = opts.xpub;
|
||||||
};
|
};
|
||||||
|
|
||||||
TxProposalAction.fromObj = function (obj) {
|
TxProposalAction.fromObj = function (obj) {
|
||||||
|
@ -16,6 +17,7 @@ TxProposalAction.fromObj = function (obj) {
|
||||||
x.copayerId = obj.copayerId;
|
x.copayerId = obj.copayerId;
|
||||||
x.type = obj.type;
|
x.type = obj.type;
|
||||||
x.signatures = obj.signatures;
|
x.signatures = obj.signatures;
|
||||||
|
x.xpub = obj.xpub;
|
||||||
|
|
||||||
return x;
|
return x;
|
||||||
};
|
};
|
||||||
|
|
|
@ -341,8 +341,11 @@ CopayServer.prototype._getUtxos = function(cb) {
|
||||||
var networkName = Bitcore.Address(addressStrs[0]).toObject().network;
|
var networkName = Bitcore.Address(addressStrs[0]).toObject().network;
|
||||||
|
|
||||||
var bc = self._getBlockExplorer('insight', networkName);
|
var bc = self._getBlockExplorer('insight', networkName);
|
||||||
bc.getUnspentUtxos(addressStrs, function(err, utxos) {
|
bc.getUnspentUtxos(addressStrs, function(err, inutxos) {
|
||||||
if (err) return cb(err);
|
if (err) return cb(err);
|
||||||
|
var utxos = _.map(inutxos, function(i) {
|
||||||
|
return i.toObject();
|
||||||
|
});
|
||||||
|
|
||||||
self.getPendingTxs({}, function(err, txps) {
|
self.getPendingTxs({}, function(err, txps) {
|
||||||
if (err) return cb(err);
|
if (err) return cb(err);
|
||||||
|
@ -581,6 +584,7 @@ CopayServer.prototype.removePendingTx = function(opts, cb) {
|
||||||
|
|
||||||
CopayServer.prototype._broadcastTx = function(txp, cb) {
|
CopayServer.prototype._broadcastTx = function(txp, cb) {
|
||||||
var raw = txp.getRawTx();
|
var raw = txp.getRawTx();
|
||||||
|
console.log('[server.js.586:raw:]',raw); //TODO
|
||||||
var bc = this._getBlockExplorer('insight', txp.getNetworkName());
|
var bc = this._getBlockExplorer('insight', txp.getNetworkName());
|
||||||
bc.broadcast(raw, function(err, txid) {
|
bc.broadcast(raw, function(err, txid) {
|
||||||
return cb(err, txid);
|
return cb(err, txid);
|
||||||
|
@ -617,11 +621,9 @@ CopayServer.prototype.signTx = function(opts, cb) {
|
||||||
|
|
||||||
var copayer = wallet.getCopayer(self.copayerId);
|
var copayer = wallet.getCopayer(self.copayerId);
|
||||||
|
|
||||||
if (!txp.checkSignatures(opts.signatures, copayer.xPubKey))
|
if (!txp.sign(self.copayerId, opts.signatures, copayer.xPubKey))
|
||||||
return cb(new ClientError('BADSIGNATURES', 'Bad signatures'));
|
return cb(new ClientError('BADSIGNATURES', 'Bad signatures'));
|
||||||
|
|
||||||
txp.sign(self.copayerId, opts.signatures);
|
|
||||||
|
|
||||||
self.storage.storeTx(self.walletId, txp, function(err) {
|
self.storage.storeTx(self.walletId, txp, function(err) {
|
||||||
if (err) return cb(err);
|
if (err) return cb(err);
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"async": "^0.9.0",
|
"async": "^0.9.0",
|
||||||
"bitcore": "0.10.0",
|
"bitcore": "^0.10.3",
|
||||||
"bitcore-explorers": "^0.9.1",
|
"bitcore-explorers": "^0.9.1",
|
||||||
"body-parser": "^1.11.0",
|
"body-parser": "^1.11.0",
|
||||||
"commander": "^2.6.0",
|
"commander": "^2.6.0",
|
||||||
|
@ -26,7 +26,8 @@
|
||||||
"inherits": "^2.0.1",
|
"inherits": "^2.0.1",
|
||||||
"leveldown": "^0.10.0",
|
"leveldown": "^0.10.0",
|
||||||
"levelup": "^0.19.0",
|
"levelup": "^0.19.0",
|
||||||
"lodash": "^2.4.1",
|
"lodash": "^3.2.0",
|
||||||
|
"morgan": "*",
|
||||||
"npmlog": "^0.1.1",
|
"npmlog": "^0.1.1",
|
||||||
"preconditions": "^1.0.7",
|
"preconditions": "^1.0.7",
|
||||||
"request": "^2.53.0",
|
"request": "^2.53.0",
|
||||||
|
|
|
@ -114,7 +114,14 @@ helpers.createUtxos = function(server, wallet, amounts, cb) {
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
helpers.stubBlockExplorer = function(server, utxos, txid) {
|
helpers.stubBlockExplorer = function(server, inUtxos, txid) {
|
||||||
|
|
||||||
|
var utxos = _.map(inUtxos, function(x) {
|
||||||
|
x.toObject = function() {
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
return x;
|
||||||
|
});
|
||||||
|
|
||||||
var bc = sinon.stub();
|
var bc = sinon.stub();
|
||||||
bc.getUnspentUtxos = sinon.stub().callsArgWith(1, null, utxos);
|
bc.getUnspentUtxos = sinon.stub().callsArgWith(1, null, utxos);
|
||||||
|
|
|
@ -28,15 +28,25 @@ describe('TXProposal', function() {
|
||||||
describe('#sign', function() {
|
describe('#sign', function() {
|
||||||
it('should sign 2-2', function() {
|
it('should sign 2-2', function() {
|
||||||
var txp = TXP.fromObj(aTXP());
|
var txp = TXP.fromObj(aTXP());
|
||||||
txp.sign('1', theSignatures);
|
txp.sign('1', theSignatures, theXPub);
|
||||||
txp.isAccepted().should.equal(false);
|
txp.isAccepted().should.equal(false);
|
||||||
txp.isRejected().should.equal(false);
|
txp.isRejected().should.equal(false);
|
||||||
txp.sign('2', theSignatures);
|
txp.sign('2', theSignatures, theXPub);
|
||||||
txp.isAccepted().should.equal(true);
|
txp.isAccepted().should.equal(true);
|
||||||
txp.isRejected().should.equal(false);
|
txp.isRejected().should.equal(false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('#getRawTx', function() {
|
||||||
|
it('should generate correct raw transaction for signed 2-2', function() {
|
||||||
|
var txp = TXP.fromObj(aTXP());
|
||||||
|
txp.sign('1', theSignatures, theXPub);
|
||||||
|
txp.getRawTx().should.equal('0100000001ab069f7073be9b491bb1ad4233a45d2e383082ccc7206df905662d6d8499e66e080000009200483045022100896aeb8db75fec22fddb5facf791927a996eb3aee23ee6deaa15471ea46047de02204c0c33f42a9d3ff93d62738712a8c8a5ecd21b45393fdd144e7b01b5a186f1f9014752210319008ffe1b3e208f5ebed8f46495c056763f87b07930a7027a92ee477fb0cb0f2103b5f035af8be40d0db5abb306b7754949ab39032cf99ad177691753b37d10130152aeffffffff0280f0fa02000000001976a91451224bca38efcaa31d5340917c3f3f713b8b20e488ac70c9fa020000000017a914778192003f0e9e1d865c082179cc3dae5464b03d8700000000');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
describe('#reject', function() {
|
describe('#reject', function() {
|
||||||
it('should reject 2-2', function() {
|
it('should reject 2-2', function() {
|
||||||
var txp = TXP.fromObj(aTXP());
|
var txp = TXP.fromObj(aTXP());
|
||||||
|
@ -59,26 +69,10 @@ describe('TXProposal', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
describe('#checkSignatures', function() {
|
|
||||||
it('should check signatures', function() {
|
|
||||||
var txp = TXP.fromObj(aTXP());
|
|
||||||
var xpriv = new Bitcore.HDPrivateKey(theXPriv);
|
|
||||||
var priv = xpriv.derive(txp.inputPaths[0]).privateKey;
|
|
||||||
|
|
||||||
var t = txp._getBitcoreTx();
|
|
||||||
t.sign(priv);
|
|
||||||
|
|
||||||
var s = t.getSignatures(priv)[0].signature.toDER().toString('hex');
|
|
||||||
var xpub = new Bitcore.HDPublicKey(xpriv);
|
|
||||||
|
|
||||||
var res = txp.checkSignatures([s], xpub);
|
|
||||||
res.should.equal(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
var theXPriv = 'xprv9s21ZrQH143K2rMHbXTJmWTuFx6ssqn1vyRoZqPkCXYchBSkp5ey8kMJe84sxfXq5uChWH4gk94rWbXZt2opN9kg4ufKGvUM7HQSLjnoh7e';
|
var theXPriv = 'xprv9s21ZrQH143K2rMHbXTJmWTuFx6ssqn1vyRoZqPkCXYchBSkp5ey8kMJe84sxfXq5uChWH4gk94rWbXZt2opN9kg4ufKGvUM7HQSLjnoh7e';
|
||||||
|
var theXPub = 'xpub661MyMwAqRbcFLRkhYzK8eQdoywNHJVsJCMQNDoMks5bZymuMcyDgYfnVQYq2Q9npnVmdTAthYGc3N3uxm5sEdnTpSqBc4YYTAhNnoSxCm9';
|
||||||
var theSignatures = ['3045022100896aeb8db75fec22fddb5facf791927a996eb3aee23ee6deaa15471ea46047de02204c0c33f42a9d3ff93d62738712a8c8a5ecd21b45393fdd144e7b01b5a186f1f9'];
|
var theSignatures = ['3045022100896aeb8db75fec22fddb5facf791927a996eb3aee23ee6deaa15471ea46047de02204c0c33f42a9d3ff93d62738712a8c8a5ecd21b45393fdd144e7b01b5a186f1f9'];
|
||||||
|
|
||||||
var aTXP = function() {
|
var aTXP = function() {
|
||||||
|
|
Loading…
Reference in New Issue