commit
bf2dda472b
|
@ -1,12 +1,15 @@
|
||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
|
|
||||||
var _ = require('lodash');
|
var _ = require('lodash');
|
||||||
|
var fs = require('fs');
|
||||||
var program = require('commander');
|
var program = require('commander');
|
||||||
var utils = require('./cli-utils');
|
var utils = require('./cli-utils');
|
||||||
program = utils.configureCommander(program);
|
program = utils.configureCommander(program);
|
||||||
|
|
||||||
program
|
program
|
||||||
.usage('[options] <txpid>')
|
.usage('[options] <txpid>')
|
||||||
|
.option('-i, --input [filename]', 'use signatures from file')
|
||||||
|
.option('-o, --output [filename]', 'write signatures to file')
|
||||||
.parse(process.argv);
|
.parse(process.argv);
|
||||||
|
|
||||||
var args = program.args;
|
var args = program.args;
|
||||||
|
@ -17,6 +20,27 @@ client.getTxProposals({}, function(err, txps) {
|
||||||
utils.die(err);
|
utils.die(err);
|
||||||
|
|
||||||
var txp = utils.findOneTxProposal(txps, txpid);
|
var txp = utils.findOneTxProposal(txps, txpid);
|
||||||
|
|
||||||
|
if (program.output) {
|
||||||
|
client.getSignatures(txp, function(err, signatures) {
|
||||||
|
utils.die(err);
|
||||||
|
var out = {
|
||||||
|
id: txp.id,
|
||||||
|
signatures: signatures,
|
||||||
|
};
|
||||||
|
fs.writeFileSync(program.output, JSON.stringify(out));
|
||||||
|
console.log('Signatures written to file.');
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
|
||||||
|
if (program.input) {
|
||||||
|
var infile = JSON.parse(fs.readFileSync(program.input));
|
||||||
|
if (infile.id != txp.id)
|
||||||
|
utils.die('Signatures does not match Transaction')
|
||||||
|
|
||||||
|
txp.signatures = infile.signatures;
|
||||||
|
}
|
||||||
|
|
||||||
client.signTxProposal(txp, function(err, tx) {
|
client.signTxProposal(txp, function(err, tx) {
|
||||||
utils.die(err);
|
utils.die(err);
|
||||||
if (tx.status == 'broadcasted')
|
if (tx.status == 'broadcasted')
|
||||||
|
@ -24,4 +48,5 @@ client.getTxProposals({}, function(err, txps) {
|
||||||
else
|
else
|
||||||
console.log('Transaction signed by you.');
|
console.log('Transaction signed by you.');
|
||||||
});
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -579,17 +579,7 @@ API.prototype.getTxProposals = function(opts, cb) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
API.prototype.signTxProposal = function(txp, cb) {
|
API.prototype._getSignaturesFor = function(txp, data) {
|
||||||
$.checkArgument(txp.creatorId);
|
|
||||||
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
this._loadAndCheck(function(err, data) {
|
|
||||||
if (err) return cb(err);
|
|
||||||
|
|
||||||
if (!Verifier.checkTxProposal(data, txp)) {
|
|
||||||
return cb(new ServerCompromisedError('Server sent fake transaction proposal'));
|
|
||||||
}
|
|
||||||
|
|
||||||
//Derive proper key to sign, for each input
|
//Derive proper key to sign, for each input
|
||||||
var privs = [],
|
var privs = [],
|
||||||
|
@ -622,6 +612,38 @@ API.prototype.signTxProposal = function(txp, cb) {
|
||||||
return s.signature.toDER().toString('hex');
|
return s.signature.toDER().toString('hex');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return signatures;
|
||||||
|
};
|
||||||
|
|
||||||
|
API.prototype.getSignatures = function(txp, cb) {
|
||||||
|
$.checkArgument(txp.creatorId);
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
this._loadAndCheck(function(err, data) {
|
||||||
|
if (err) return cb(err);
|
||||||
|
|
||||||
|
if (!Verifier.checkTxProposal(data, txp)) {
|
||||||
|
return cb(new ServerCompromisedError('Transaction proposal is invalid'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return cb(null, self._getSignaturesFor(txp, data));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
API.prototype.signTxProposal = function(txp, cb) {
|
||||||
|
$.checkArgument(txp.creatorId);
|
||||||
|
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
this._loadAndCheck(function(err, data) {
|
||||||
|
if (err) return cb(err);
|
||||||
|
|
||||||
|
if (!Verifier.checkTxProposal(data, txp)) {
|
||||||
|
return cb(new ServerCompromisedError('Server sent fake transaction proposal'));
|
||||||
|
}
|
||||||
|
|
||||||
|
var signatures = txp.signatures || self._getSignaturesFor(txp, data);
|
||||||
|
|
||||||
var url = '/v1/txproposals/' + txp.id + '/signatures/';
|
var url = '/v1/txproposals/' + txp.id + '/signatures/';
|
||||||
var args = {
|
var args = {
|
||||||
signatures: signatures
|
signatures: signatures
|
||||||
|
|
|
@ -353,7 +353,7 @@ describe('client API ', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Access control & export', function() {
|
describe('Access control', function() {
|
||||||
it('should not be able to create address if not rwPubKey', function(done) {
|
it('should not be able to create address if not rwPubKey', function(done) {
|
||||||
helpers.createAndJoinWallet(clients, 1, 1, function(err) {
|
helpers.createAndJoinWallet(clients, 1, 1, function(err) {
|
||||||
should.not.exist(err);
|
should.not.exist(err);
|
||||||
|
@ -438,7 +438,8 @@ describe('client API ', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
describe('Air gapped flows', function() {
|
||||||
it('should be able get Tx proposals from a file', function(done) {
|
it('should be able get Tx proposals from a file', function(done) {
|
||||||
helpers.createAndJoinWallet(clients, 1, 2, function(err, w) {
|
helpers.createAndJoinWallet(clients, 1, 2, function(err, w) {
|
||||||
should.not.exist(err);
|
should.not.exist(err);
|
||||||
|
@ -499,9 +500,43 @@ describe('client API ', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
it('should be able export signatures and sign later from a ro client',
|
||||||
|
function(done) {
|
||||||
|
helpers.createAndJoinWallet(clients, 1, 1, function(err, w) {
|
||||||
|
should.not.exist(err);
|
||||||
|
clients[0].createAddress(function(err, x0) {
|
||||||
|
should.not.exist(err);
|
||||||
|
blockExplorerMock.setUtxo(x0, 1, 1);
|
||||||
|
blockExplorerMock.setUtxo(x0, 1, 2);
|
||||||
|
var opts = {
|
||||||
|
amount: 150000000,
|
||||||
|
toAddress: 'n2TBMPzPECGUfcT2EByiTJ12TPZkhN2mN5',
|
||||||
|
message: 'hello 1-1',
|
||||||
|
};
|
||||||
|
clients[0].sendTxProposal(opts, function(err, txp) {
|
||||||
|
should.not.exist(err);
|
||||||
|
clients[0].getSignatures(txp, function(err, signatures) {
|
||||||
|
should.not.exist(err);
|
||||||
|
signatures.length.should.equal(txp.inputs.length);
|
||||||
|
signatures[0].length.should.above(62 * 2);
|
||||||
|
|
||||||
|
txp.signatures = signatures;
|
||||||
|
|
||||||
|
// Make client RO
|
||||||
|
var data = JSON.parse(fsmock._get('client0'));
|
||||||
|
delete data.xPrivKey;
|
||||||
|
fsmock._set('client0', JSON.stringify(data));
|
||||||
|
|
||||||
|
clients[0].signTxProposal(txp, function(err, txp) {
|
||||||
|
should.not.exist(err);
|
||||||
|
txp.status.should.equal('broadcasted');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Address Creation', function() {
|
describe('Address Creation', function() {
|
||||||
|
@ -887,7 +922,9 @@ describe('client API ', function() {
|
||||||
should.not.exist(err);
|
should.not.exist(err);
|
||||||
clients[0].createAddress(function(err, x0) {
|
clients[0].createAddress(function(err, x0) {
|
||||||
should.not.exist(err);
|
should.not.exist(err);
|
||||||
clients[0].getMainAddresses({doNotVerify: true}, function(err, addr) {
|
clients[0].getMainAddresses({
|
||||||
|
doNotVerify: true
|
||||||
|
}, function(err, addr) {
|
||||||
should.not.exist(err);
|
should.not.exist(err);
|
||||||
addr.length.should.equal(2);
|
addr.length.should.equal(2);
|
||||||
done();
|
done();
|
||||||
|
@ -940,9 +977,20 @@ describe('client API ', function() {
|
||||||
};
|
};
|
||||||
clients[0].sendTxProposal(opts, function(err, x) {
|
clients[0].sendTxProposal(opts, function(err, x) {
|
||||||
should.not.exist(err);
|
should.not.exist(err);
|
||||||
|
clients[0].getStatus( function(err, st) {
|
||||||
|
should.not.exist(err);
|
||||||
|
var x = st.pendingTxps[0];
|
||||||
x.status.should.equal('pending');
|
x.status.should.equal('pending');
|
||||||
x.requiredRejections.should.equal(2);
|
x.requiredRejections.should.equal(2);
|
||||||
x.requiredSignatures.should.equal(2);
|
x.requiredSignatures.should.equal(2);
|
||||||
|
var w = st.wallet;
|
||||||
|
w.copayers.length.should.equal(3);
|
||||||
|
w.status.should.equal('complete');
|
||||||
|
var b = st.balance;
|
||||||
|
b.totalAmount.should.equal(1000000000);
|
||||||
|
b.lockedAmount.should.equal(1000000000);
|
||||||
|
|
||||||
|
|
||||||
clients[0].signTxProposal(x, function(err, tx) {
|
clients[0].signTxProposal(x, function(err, tx) {
|
||||||
should.not.exist(err, err);
|
should.not.exist(err, err);
|
||||||
tx.status.should.equal('pending');
|
tx.status.should.equal('pending');
|
||||||
|
@ -957,6 +1005,7 @@ describe('client API ', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('Send, reject, 2 signs and broadcast in 2-3 wallet', function(done) {
|
it('Send, reject, 2 signs and broadcast in 2-3 wallet', function(done) {
|
||||||
helpers.createAndJoinWallet(clients, 2, 3, function(err, w) {
|
helpers.createAndJoinWallet(clients, 2, 3, function(err, w) {
|
||||||
|
|
Loading…
Reference in New Issue