2015-02-12 11:42:32 -08:00
'use strict' ;
var _ = require ( 'lodash' ) ;
2015-02-15 08:14:36 -08:00
var util = require ( 'util' ) ;
2015-02-12 11:42:32 -08:00
var async = require ( 'async' ) ;
var log = require ( 'npmlog' ) ;
var request = require ( 'request' )
log . debug = log . verbose ;
var Bitcore = require ( 'bitcore' )
2015-02-15 06:33:04 -08:00
var SignUtils = require ( '../signutils' ) ;
2015-02-16 06:17:44 -08:00
var BitcoinUtils = require ( '../bitcoinutils' ) ;
2015-02-12 11:42:32 -08:00
2015-02-13 11:07:47 -08:00
var BASE _URL = 'http://localhost:3001/copay/api' ;
2015-02-12 11:42:32 -08:00
2015-02-13 07:45:05 -08:00
function _createProposalOpts ( opts , signingKey ) {
var msg = opts . toAddress + '|' + opts . amount + '|' + opts . message ;
opts . proposalSignature = SignUtils . sign ( msg , signingKey ) ;
return opts ;
} ;
2015-02-12 11:50:10 -08:00
function _getUrl ( path ) {
2015-02-12 11:42:32 -08:00
return BASE _URL + path ;
} ;
2015-02-12 19:00:54 -08:00
function _parseError ( body ) {
if ( _ . isString ( body ) ) {
2015-02-13 07:45:05 -08:00
try {
2015-02-13 08:35:20 -08:00
body = JSON . parse ( body ) ;
2015-02-13 07:45:05 -08:00
} catch ( e ) {
2015-02-13 08:35:20 -08:00
body = {
error : body
} ;
2015-02-13 07:45:05 -08:00
}
2015-02-12 19:00:54 -08:00
}
var code = body . code || 'ERROR' ;
var message = body . error || 'There was an unknown error processing the request' ;
log . error ( code , message ) ;
} ;
2015-02-15 06:12:04 -08:00
function _signRequest ( method , url , args , privKey ) {
var message = method . toLowerCase ( ) + '|' + url + '|' + JSON . stringify ( args ) ;
2015-02-12 19:00:54 -08:00
return SignUtils . sign ( message , privKey ) ;
} ;
2015-02-12 11:42:32 -08:00
2015-02-13 13:24:35 -08:00
function _createXPrivKey ( network ) {
return new Bitcore . HDPrivateKey ( network ) . toString ( ) ;
2015-02-12 19:00:54 -08:00
} ;
2015-02-12 11:42:32 -08:00
2015-02-15 06:33:04 -08:00
function API ( opts ) {
2015-02-15 08:14:36 -08:00
if ( ! opts . storage ) {
throw new Error ( 'Must provide storage option' ) ;
}
this . storage = opts . storage ;
this . verbose = ! ! opts . verbose ;
if ( this . verbose ) {
log . level = 'debug' ;
2015-02-12 18:57:16 -08:00
}
2015-02-12 11:42:32 -08:00
} ;
2015-02-15 06:33:04 -08:00
API . prototype . _loadAndCheck = function ( ) {
2015-02-15 08:14:36 -08:00
var data = this . storage . load ( ) ;
2015-02-12 19:00:54 -08:00
if ( ! data ) {
log . error ( 'Wallet file not found.' ) ;
process . exit ( 1 ) ;
}
2015-02-13 08:35:20 -08:00
2015-02-12 20:21:24 -08:00
if ( data . verified == 'corrupt' ) {
log . error ( 'The wallet is tagged as corrupt. Some of the copayers cannot be verified to have known the wallet secret.' ) ;
process . exit ( 1 ) ;
}
2015-02-12 19:00:54 -08:00
if ( data . n > 1 ) {
var pkrComplete = data . publicKeyRing && data . m && data . publicKeyRing . length === data . n ;
if ( ! pkrComplete ) {
log . warn ( 'The file ' + this . filename + ' is incomplete. It will allow you to operate with the wallet but it should not be trusted as a backup. Please wait for all copayers to join the wallet and run the tool with -export flag.' )
}
}
return data ;
2015-02-12 13:54:17 -08:00
} ;
2015-02-15 06:12:04 -08:00
2015-02-15 08:03:48 -08:00
API . prototype . _doRequest = function ( method , url , args , data , cb ) {
2015-02-15 06:12:04 -08:00
var reqSignature = _signRequest ( method , url , args , data . signingPrivKey ) ;
2015-02-13 20:28:43 -08:00
var absUrl = _getUrl ( url ) ;
2015-02-15 08:14:36 -08:00
var args = {
2015-02-13 20:28:43 -08:00
headers : {
'x-identity' : data . copayerId ,
'x-signature' : reqSignature ,
} ,
2015-02-15 06:12:04 -08:00
method : method ,
2015-02-13 20:28:43 -08:00
url : absUrl ,
body : args ,
json : true ,
2015-02-15 08:14:36 -08:00
} ;
log . verbose ( 'Request Args' , util . inspect ( args ) ) ;
request ( args , function ( err , res , body ) {
log . verbose ( 'Response:' , err , body ) ;
2015-02-13 20:28:43 -08:00
if ( err ) return cb ( err ) ;
if ( res . statusCode != 200 ) {
_parseError ( body ) ;
return cb ( 'Request error' ) ;
}
return cb ( null , body ) ;
} ) ;
} ;
2015-02-15 06:33:04 -08:00
API . prototype . _doPostRequest = function ( url , args , data , cb ) {
2015-02-13 20:28:43 -08:00
return this . _doRequest ( 'post' , url , args , data , cb ) ;
} ;
2015-02-15 06:33:04 -08:00
API . prototype . _doGetRequest = function ( url , data , cb ) {
2015-02-13 20:28:43 -08:00
return this . _doRequest ( 'get' , url , { } , data , cb ) ;
} ;
2015-02-12 11:42:32 -08:00
2015-02-15 06:33:04 -08:00
API . prototype . createWallet = function ( walletName , copayerName , m , n , network , cb ) {
2015-02-12 19:00:54 -08:00
var self = this ;
2015-02-13 13:24:35 -08:00
network = network || 'livenet' ;
2015-02-13 17:59:05 -08:00
if ( ! _ . contains ( [ 'testnet' , 'livenet' ] , network ) )
return cb ( 'Invalid network' ) ;
2015-02-12 19:00:54 -08:00
2015-02-15 08:14:36 -08:00
var data = this . storage . load ( ) ;
2015-02-13 07:02:30 -08:00
if ( data ) return cb ( 'File ' + this . filename + ' already contains a wallet' ) ;
2015-02-12 13:54:17 -08:00
2015-02-13 05:58:49 -08:00
// Generate wallet key pair to verify copayers
2015-02-13 13:24:35 -08:00
var privKey = new Bitcore . PrivateKey ( null , network ) ;
2015-02-13 05:58:49 -08:00
var pubKey = privKey . toPublicKey ( ) ;
2015-02-12 13:54:17 -08:00
data = {
m : m ,
2015-02-13 05:58:49 -08:00
n : n ,
walletPrivKey : privKey . toString ( ) ,
2015-02-16 06:17:44 -08:00
network : network ,
2015-02-12 13:54:17 -08:00
} ;
2015-02-12 11:42:32 -08:00
var args = {
name : walletName ,
m : m ,
n : n ,
pubKey : pubKey . toString ( ) ,
2015-02-13 13:24:35 -08:00
network : network ,
2015-02-12 11:42:32 -08:00
} ;
2015-02-13 20:28:43 -08:00
var url = '/v1/wallets/' ;
2015-02-12 11:42:32 -08:00
2015-02-13 20:28:43 -08:00
this . _doPostRequest ( url , args , data , function ( err , body ) {
2015-02-15 06:33:04 -08:00
if ( err ) return cb ( err ) ;
2015-02-12 19:00:54 -08:00
var walletId = body . walletId ;
2015-02-13 17:59:05 -08:00
var secret = walletId + ':' + privKey . toString ( ) + ':' + ( network == 'testnet' ? 'T' : 'L' ) ;
2015-02-15 13:26:05 -08:00
var ret ;
if ( n > 1 )
ret = data . secret = secret ;
2015-02-12 13:54:17 -08:00
2015-02-15 08:14:36 -08:00
self . storage . save ( data ) ;
2015-02-12 19:00:54 -08:00
self . _joinWallet ( data , secret , copayerName , function ( err ) {
2015-02-12 11:42:32 -08:00
if ( err ) return cb ( err ) ;
2015-02-15 13:26:05 -08:00
return cb ( null , ret ) ;
2015-02-12 11:42:32 -08:00
} ) ;
} ) ;
} ;
2015-02-15 06:33:04 -08:00
API . prototype . _joinWallet = function ( data , secret , copayerName , cb ) {
2015-02-12 19:00:54 -08:00
var self = this ;
2015-02-13 13:24:35 -08:00
data = data || { } ;
2015-02-12 13:54:17 -08:00
2015-02-12 19:23:59 -08:00
var secretSplit = secret . split ( ':' ) ;
2015-02-12 11:42:32 -08:00
var walletId = secretSplit [ 0 ] ;
2015-02-13 13:24:35 -08:00
2015-02-13 11:26:33 -08:00
var walletPrivKey = Bitcore . PrivateKey . fromString ( secretSplit [ 1 ] ) ;
2015-02-13 13:24:35 -08:00
var network = secretSplit [ 2 ] == 'T' ? 'testnet' : 'livenet' ;
2015-02-13 13:53:49 -08:00
data . xPrivKey = _createXPrivKey ( network ) ;
2015-02-12 11:42:32 -08:00
2015-02-12 19:00:54 -08:00
var xPubKey = new Bitcore . HDPublicKey ( data . xPrivKey ) ;
2015-02-13 11:26:33 -08:00
var xPubKeySignature = SignUtils . sign ( xPubKey . toString ( ) , walletPrivKey ) ;
2015-02-12 19:23:59 -08:00
var signingPrivKey = ( new Bitcore . HDPrivateKey ( data . xPrivKey ) ) . derive ( 'm/1/0' ) . privateKey ;
2015-02-12 11:42:32 -08:00
var args = {
walletId : walletId ,
name : copayerName ,
2015-02-12 19:00:54 -08:00
xPubKey : xPubKey . toString ( ) ,
2015-02-12 11:42:32 -08:00
xPubKeySignature : xPubKeySignature ,
} ;
2015-02-13 20:28:43 -08:00
var url = '/v1/wallets/' + walletId + '/copayers' ;
2015-02-12 11:42:32 -08:00
2015-02-13 20:28:43 -08:00
this . _doPostRequest ( url , args , data , function ( err , body ) {
2015-02-12 19:00:54 -08:00
var wallet = body . wallet ;
data . copayerId = body . copayerId ;
2015-02-13 11:26:33 -08:00
data . walletPrivKey = walletPrivKey ;
2015-02-12 19:00:54 -08:00
data . signingPrivKey = signingPrivKey . toString ( ) ;
data . m = wallet . m ;
data . n = wallet . n ;
data . publicKeyRing = wallet . publicKeyRing ;
2015-02-15 08:14:36 -08:00
self . storage . save ( data ) ;
2015-02-12 19:00:54 -08:00
return cb ( ) ;
} ) ;
} ;
2015-02-12 13:54:17 -08:00
2015-02-15 06:33:04 -08:00
API . prototype . joinWallet = function ( secret , copayerName , cb ) {
2015-02-12 19:00:54 -08:00
var self = this ;
2015-02-12 13:54:17 -08:00
2015-02-15 08:14:36 -08:00
var data = this . storage . load ( ) ;
2015-02-13 07:02:30 -08:00
if ( data ) return cb ( 'File ' + this . filename + ' already contains a wallet' ) ;
2015-02-12 13:54:17 -08:00
2015-02-12 19:00:54 -08:00
self . _joinWallet ( data , secret , copayerName , cb ) ;
2015-02-12 11:42:32 -08:00
} ;
2015-02-15 06:33:04 -08:00
API . prototype . getStatus = function ( cb ) {
2015-02-12 19:00:54 -08:00
var self = this ;
var data = this . _loadAndCheck ( ) ;
2015-02-12 13:54:17 -08:00
2015-02-12 19:50:57 -08:00
var url = '/v1/wallets/' ;
2015-02-13 20:28:43 -08:00
this . _doGetRequest ( url , data , function ( err , body ) {
2015-02-12 11:42:32 -08:00
if ( err ) return cb ( err ) ;
2015-02-12 20:21:24 -08:00
2015-02-13 20:28:43 -08:00
var wallet = body ;
2015-02-13 05:58:49 -08:00
if ( wallet . n > 0 && wallet . status === 'complete' && ! data . verified ) {
var pubKey = Bitcore . PrivateKey . fromString ( data . walletPrivKey ) . toPublicKey ( ) . toString ( ) ;
2015-02-12 20:21:24 -08:00
var fake = [ ] ;
_ . each ( wallet . copayers , function ( copayer ) {
2015-02-13 08:35:20 -08:00
console . log ( '[clilib.js.224]' , copayer . xPubKey , copayer . xPubKeySignature , pubKey ) ; //TODO
2015-02-13 05:58:49 -08:00
if ( ! SignUtils . verify ( copayer . xPubKey , copayer . xPubKeySignature , pubKey ) ) {
2015-02-13 08:35:20 -08:00
console . log ( '[clilib.js.227] FAKE' ) ; //TODO
2015-02-12 20:21:24 -08:00
fake . push ( copayer ) ;
}
} ) ;
if ( fake . length > 0 ) {
log . error ( 'Some copayers in the wallet could not be verified to have known the wallet secret' ) ;
data . verified = 'corrupt' ;
} else {
data . verified = 'ok' ;
}
2015-02-15 08:14:36 -08:00
self . storage . save ( data ) ;
2015-02-12 20:21:24 -08:00
}
2015-02-12 11:42:32 -08:00
2015-02-12 20:21:24 -08:00
return cb ( null , wallet ) ;
2015-02-12 11:42:32 -08:00
} ) ;
} ;
2015-02-13 07:45:05 -08:00
/ * *
* send
*
* @ param inArgs
* @ param inArgs . toAddress
* @ param inArgs . amount
* @ param inArgs . message
* /
2015-02-15 06:33:04 -08:00
API . prototype . sendTxProposal = function ( inArgs , cb ) {
2015-02-13 06:38:25 -08:00
var self = this ;
var data = this . _loadAndCheck ( ) ;
2015-02-13 07:45:05 -08:00
var args = _createProposalOpts ( inArgs , data . signingPrivKey ) ;
2015-02-13 06:38:25 -08:00
2015-02-13 07:45:05 -08:00
var url = '/v1/txproposals/' ;
2015-02-13 20:28:43 -08:00
this . _doPostRequest ( url , args , data , cb ) ;
2015-02-12 11:42:32 -08:00
} ;
2015-02-13 07:55:07 -08:00
// Get addresses
2015-02-15 06:33:04 -08:00
API . prototype . getAddresses = function ( cb ) {
2015-02-13 07:55:07 -08:00
var self = this ;
var data = this . _loadAndCheck ( ) ;
var url = '/v1/addresses/' ;
2015-02-13 20:28:43 -08:00
this . _doGetRequest ( url , data , cb ) ;
2015-02-13 07:55:07 -08:00
} ;
// Creates a new address
// TODO: verify derivation!!
2015-02-15 06:33:04 -08:00
API . prototype . createAddress = function ( cb ) {
2015-02-13 07:55:07 -08:00
var self = this ;
var data = this . _loadAndCheck ( ) ;
var url = '/v1/addresses/' ;
2015-02-16 06:20:19 -08:00
this . _doPostRequest ( url , { } , data , function ( err , address ) {
2015-02-16 06:17:44 -08:00
if ( err ) return cb ( err ) ;
if ( data . publicKeyRing . length != data . n )
return cb ( 'Wallet Incomplete, cannot derive address.' )
2015-02-16 06:20:19 -08:00
var local = BitcoinUtils . deriveAddress ( data . publicKeyRing , address . path , data . m , data . network ) ;
if ( local . address != address . address || JSON . stringify ( local . publicKeys ) != JSON . stringify ( address . publicKeys ) )
2015-02-16 06:17:44 -08:00
return cb ( 'Server sent a fake address.' ) ;
return cb ( null , address ) ;
} ) ;
2015-02-12 11:42:32 -08:00
} ;
2015-02-15 06:33:04 -08:00
API . prototype . history = function ( limit , cb ) {
2015-02-12 11:42:32 -08:00
} ;
2015-02-15 06:33:04 -08:00
API . prototype . getBalance = function ( cb ) {
2015-02-13 08:35:20 -08:00
var self = this ;
var data = this . _loadAndCheck ( ) ;
var url = '/v1/balance/' ;
2015-02-13 20:28:43 -08:00
this . _doGetRequest ( url , data , cb ) ;
2015-02-13 08:35:20 -08:00
} ;
2015-02-15 06:33:04 -08:00
API . prototype . getTxProposals = function ( opts , cb ) {
2015-02-13 08:35:20 -08:00
var self = this ;
var data = this . _loadAndCheck ( ) ;
var url = '/v1/txproposals/' ;
2015-02-13 20:28:43 -08:00
this . _doGetRequest ( url , data , cb ) ;
2015-02-13 08:35:20 -08:00
} ;
2015-02-15 06:33:04 -08:00
API . prototype . signTxProposal = function ( txp , cb ) {
2015-02-13 12:02:56 -08:00
var self = this ;
var data = this . _loadAndCheck ( ) ;
//Derive proper key to sign, for each input
var privs = [ ] ,
derived = { } ;
2015-02-13 13:53:49 -08:00
2015-02-13 13:24:35 -08:00
var network = new Bitcore . Address ( txp . toAddress ) . network . name ;
var xpriv = new Bitcore . HDPrivateKey ( data . xPrivKey , network ) ;
2015-02-13 12:02:56 -08:00
_ . 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 ) ;
} ) ;
2015-02-13 13:24:35 -08:00
2015-02-13 13:53:49 -08:00
var url = '/v1/txproposals/' + txp . id + '/signatures/' ;
var args = {
signatures : signatures
} ;
2015-02-13 20:28:43 -08:00
this . _doPostRequest ( url , args , data , cb ) ;
2015-02-13 12:02:56 -08:00
} ;
2015-02-15 06:33:04 -08:00
API . prototype . rejectTxProposal = function ( txp , reason , cb ) {
2015-02-13 17:51:40 -08:00
var self = this ;
var data = this . _loadAndCheck ( ) ;
var url = '/v1/txproposals/' + txp . id + '/rejections/' ;
var args = {
reason : reason || '' ,
} ;
2015-02-13 20:28:43 -08:00
this . _doPostRequest ( url , args , data , cb ) ;
2015-02-13 17:51:40 -08:00
} ;
2015-02-13 08:35:20 -08:00
2015-02-15 13:52:48 -08:00
API . prototype . broadcastTxProposal = function ( txp , cb ) {
var self = this ;
var data = this . _loadAndCheck ( ) ;
var url = '/v1/txproposals/' + txp . id + '/broadcast/' ;
this . _doPostRequest ( url , { } , data , cb ) ;
} ;
2015-02-15 08:03:48 -08:00
API . prototype . removeTxProposal = function ( txp , cb ) {
2015-02-14 07:54:00 -08:00
var self = this ;
var data = this . _loadAndCheck ( ) ;
var url = '/v1/txproposals/' + txp . id ;
2015-02-15 07:46:38 -08:00
this . _doRequest ( 'delete' , url , { } , data , cb ) ;
2015-02-14 07:54:00 -08:00
} ;
2015-02-15 06:33:04 -08:00
module . exports = API ;