|
|
@ -11,7 +11,7 @@ var fs = require('fs') |
|
|
|
var Bitcore = require('bitcore') |
|
|
|
var SignUtils = require('./signutils'); |
|
|
|
|
|
|
|
var BASE_URL = 'http://localhost:3001/copay/api/'; |
|
|
|
var BASE_URL = 'http://localhost:3001/copay/api'; |
|
|
|
|
|
|
|
function _createProposalOpts(opts, signingKey) { |
|
|
|
var msg = opts.toAddress + '|' + opts.amount + '|' + opts.message; |
|
|
@ -43,29 +43,28 @@ function _signRequest(url, args, privKey) { |
|
|
|
return SignUtils.sign(message, privKey); |
|
|
|
}; |
|
|
|
|
|
|
|
function _createXPrivKey() { |
|
|
|
return new Bitcore.HDPrivateKey().toString(); |
|
|
|
function _createXPrivKey(network) { |
|
|
|
return new Bitcore.HDPrivateKey(network).toString(); |
|
|
|
}; |
|
|
|
|
|
|
|
function CliLib(opts) { |
|
|
|
function ClientLib(opts) { |
|
|
|
if (!opts.filename) { |
|
|
|
throw new Error('Please set the config filename'); |
|
|
|
} |
|
|
|
this.filename = opts.filename; |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
CliLib.prototype._save = function(data) { |
|
|
|
ClientLib.prototype._save = function(data) { |
|
|
|
fs.writeFileSync(this.filename, JSON.stringify(data)); |
|
|
|
}; |
|
|
|
|
|
|
|
CliLib.prototype._load = function() { |
|
|
|
ClientLib.prototype._load = function() { |
|
|
|
try { |
|
|
|
return JSON.parse(fs.readFileSync(this.filename)); |
|
|
|
} catch (ex) {} |
|
|
|
}; |
|
|
|
|
|
|
|
CliLib.prototype._loadAndCheck = function() { |
|
|
|
ClientLib.prototype._loadAndCheck = function() { |
|
|
|
var data = this._load(); |
|
|
|
if (!data) { |
|
|
|
log.error('Wallet file not found.'); |
|
|
@ -85,18 +84,18 @@ CliLib.prototype._loadAndCheck = function() { |
|
|
|
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; |
|
|
|
network = network || 'livenet'; |
|
|
|
|
|
|
|
var data = this._load(); |
|
|
|
if (data) return cb('File ' + this.filename + ' already contains a wallet'); |
|
|
|
|
|
|
|
// Generate wallet key pair to verify copayers
|
|
|
|
var privKey = new Bitcore.PrivateKey(); |
|
|
|
var privKey = new Bitcore.PrivateKey(null, network); |
|
|
|
var pubKey = privKey.toPublicKey(); |
|
|
|
|
|
|
|
data = { |
|
|
|
xPrivKey: _createXPrivKey(), |
|
|
|
m: m, |
|
|
|
n: n, |
|
|
|
walletPrivKey: privKey.toString(), |
|
|
@ -107,7 +106,7 @@ CliLib.prototype.createWallet = function(walletName, copayerName, m, n, network, |
|
|
|
m: m, |
|
|
|
n: n, |
|
|
|
pubKey: pubKey.toString(), |
|
|
|
network: network || 'livenet', |
|
|
|
network: network, |
|
|
|
}; |
|
|
|
|
|
|
|
request({ |
|
|
@ -123,11 +122,10 @@ CliLib.prototype.createWallet = function(walletName, copayerName, m, n, network, |
|
|
|
} |
|
|
|
|
|
|
|
var walletId = body.walletId; |
|
|
|
var secret = walletId + ':' + privKey.toString(); |
|
|
|
var secret = walletId + ':' + privKey.toString() + ':' + (network ? 'T' : 'L'); |
|
|
|
data.secret = secret; |
|
|
|
|
|
|
|
self._save(data); |
|
|
|
|
|
|
|
self._joinWallet(data, secret, copayerName, function(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; |
|
|
|
data = data || {}; |
|
|
|
|
|
|
|
var secretSplit = secret.split(':'); |
|
|
|
var walletId = secretSplit[0]; |
|
|
|
|
|
|
|
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 xPubKeySignature = SignUtils.sign(xPubKey.toString(), walletPrivKey); |
|
|
|
|
|
|
|
var signingPrivKey = (new Bitcore.HDPrivateKey(data.xPrivKey)).derive('m/1/0').privateKey; |
|
|
|
|
|
|
|
var args = { |
|
|
|
walletId: walletId, |
|
|
|
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 data = this._load(); |
|
|
|
if (data) return cb('File ' + this.filename + ' already contains a wallet'); |
|
|
|
|
|
|
|
data = { |
|
|
|
xPrivKey: _createXPrivKey(), |
|
|
|
}; |
|
|
|
|
|
|
|
self._joinWallet(data, secret, copayerName, cb); |
|
|
|
}; |
|
|
|
|
|
|
|
CliLib.prototype.status = function(cb) { |
|
|
|
ClientLib.prototype.status = function(cb) { |
|
|
|
var self = this; |
|
|
|
|
|
|
|
var data = this._loadAndCheck(); |
|
|
@ -253,7 +250,7 @@ CliLib.prototype.status = function(cb) { |
|
|
|
* @param inArgs.amount |
|
|
|
* @param inArgs.message |
|
|
|
*/ |
|
|
|
CliLib.prototype.send = function(inArgs, cb) { |
|
|
|
ClientLib.prototype.send = function(inArgs, cb) { |
|
|
|
var self = this; |
|
|
|
|
|
|
|
var data = this._loadAndCheck(); |
|
|
@ -282,16 +279,16 @@ CliLib.prototype.send = function(inArgs, cb) { |
|
|
|
}; |
|
|
|
|
|
|
|
// 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
|
|
|
|
CliLib.prototype.addresses = function(cb) { |
|
|
|
ClientLib.prototype.addresses = function(cb) { |
|
|
|
var self = this; |
|
|
|
|
|
|
|
var data = this._loadAndCheck(); |
|
|
@ -320,7 +317,7 @@ CliLib.prototype.addresses = function(cb) { |
|
|
|
|
|
|
|
// Creates a new address
|
|
|
|
// TODO: verify derivation!!
|
|
|
|
CliLib.prototype.address = function(cb) { |
|
|
|
ClientLib.prototype.address = function(cb) { |
|
|
|
var self = this; |
|
|
|
|
|
|
|
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 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 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(); |
|
|
|
|
|
|
|
|
|
|
|
//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 = CliLib; |
|
|
|
module.exports = ClientLib; |