diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index 32fd75c69..fb448d506 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -4,7 +4,8 @@ var EventEmitter = require('events').EventEmitter; var _ = require('underscore'); var async = require('async'); var preconditions = require('preconditions').singleton(); -var util = require('util'); +var inherits = require('inherits'); +var events = require('events'); var bitcore = require('bitcore'); var bignum = bitcore.Bignum; @@ -26,6 +27,33 @@ var PrivateKey = require('./PrivateKey'); var WalletLock = require('./WalletLock'); var copayConfig = require('../../../config'); +/** + * @desc + * Wallet manages a private key for Copay, network, storage of the wallet for + * persistance, and blockchain information. + * + * @TODO: Split this leviathan. + * + * @param {Object} opts + * @param {Storage} opts.storage - an object that can persist the wallet + * @param {Network} opts.network - used to send and retrieve messages from + * copayers + * @param {Blockchain} opts.blockchain - source of truth for what happens in + * the blockchain (utxos, balances) + * @param {number} opts.requiredCopayers - number of required copayers to + * release funds + * @param {number} opts.totalCopayers - number of copayers in the wallet + * @param {boolean} opts.spendUnconfirmed - whether it's safe to spend + * unconfirmed outputs or not + * @param {PublicKeyRing} opts.publicKeyRing - an instance of {@link PublicKeyRing} + * @param {TxProposals} opts.txProposals - an instance of {@link TxProposals} + * @param {PrivateKey} opts.privateKey - an instance of {@link PrivateKey} + * @param {string} opts.version - the version of copay where this wallet was + * created + * @TODO: figure out if reconnectDelay is set in milliseconds + * @param {number} opts.reconnectDelay - amount of seconds to wait before + * attempting to reconnect + */ function Wallet(opts) { var self = this; @@ -66,8 +94,17 @@ function Wallet(opts) { this.network.setHexNonces(opts.networkNonces); } -util.inherits(Wallet, EventEmitter); +inherits(Wallet, events.EventEmitter); +/** + * @TODO: Document this. Its usage is kind of weird + * + * @static + * @property lockTime + * @property signhash + * @property fee + * @property feeSat + */ Wallet.builderOpts = { lockTime: null, signhash: bitcore.Transaction.SIGNHASH_ALL, @@ -75,20 +112,45 @@ Wallet.builderOpts = { feeSat: null, }; +/** + * @desc Retrieve a random id for the wallet + * @TODO: Discuss changing to a UUID + * @return {string} 8 bytes, hexa encoded + */ Wallet.getRandomId = function() { var r = bitcore.SecureRandom.getPseudoRandomBuffer(8).toString('hex'); return r; }; +/** + * @desc Get a random 8 byte number and encode it as a hexa string + * @return {string} + */ Wallet.getRandomNumber = function() { var r = bitcore.SecureRandom.getPseudoRandomBuffer(5).toString('hex'); return r; }; +/** + * @desc Set the copayer id for the owner of this wallet + * @param {string} pubkey - the pubkey to set to the {@link Wallet#seededCopayerId} property + */ Wallet.prototype.seedCopayer = function(pubKey) { this.seededCopayerId = pubKey; }; +/** + * @desc Handles an 'indexes' message. + * + * Processes the data using {@link HDParams#fromList} and merges it with the + * {@link Wallet#publicKeyRing}. + * + * Triggers a {@link Wallet#store} if the internal state has changed. + * + * @param {string} senderId - the sender id + * @param {Object} data - the data recived, {@see HDParams#fromList} + * @emits {publicKeyRingUpdated} + */ Wallet.prototype._onIndexes = function(senderId, data) { log.debug('RECV INDEXES:', data); var inIndexes = HDParams.fromList(data.indexes); @@ -99,6 +161,26 @@ Wallet.prototype._onIndexes = function(senderId, data) { } }; +/** + * @desc + * Handles a 'PUBLICKEYRING' message from senderId. + * + * data.publicKeyRing is expected to be processed correctly by + * {@link PublicKeyRing#fromObj}. + * + * After successful deserialization, {@link Wallet#publicKeyRing} is merged + * with the received data, a call to {@link Wallet#store} is performed if the + * internal state has changed. + * + * This locks new incoming connections in case the public key ring is completed + * + * @param {string} senderId - the sender id + * @param {Object} data - the data recived, {@see HDParams#fromList} + * @param {Object} data.publicKeyRing - data to be deserialized into a {@link PublicKeyRing} + * using {@link PublicKeyRing#fromObj} + * @emits {publicKeyRingUpdated} + * @emits {connectionError} + */ Wallet.prototype._onPublicKeyRing = function(senderId, data) { log.debug('RECV PUBLICKEYRING:', data); @@ -126,7 +208,14 @@ Wallet.prototype._onPublicKeyRing = function(senderId, data) { } }; - +/** + * @desc + * Demultiplexes calls to update TxProposal updates + * + * @param {string} senderId - the copayer that sent this event + * @param {Object} m - the data received + * @emits {txProposalEvent} + */ Wallet.prototype._processProposalEvents = function(senderId, m) { var ev; if (m) { @@ -153,7 +242,6 @@ Wallet.prototype._processProposalEvents = function(senderId, m) { }; - /* OTDO events.push({ type: 'signed', @@ -161,6 +249,14 @@ cId: k, txId: ntxid }); */ +/** + * @desc + * Retrieves a keymap from from a transaction proposal set extracts a maps from + * public key to cosignerId for each signed input of the transaction proposal. + * + * @param {TxProposals} txp - the transaction proposals + * @return {Object} + */ Wallet.prototype._getKeyMap = function(txp) { preconditions.checkArgument(txp); var inSig0, keyMapAll = {}; @@ -191,7 +287,18 @@ Wallet.prototype._getKeyMap = function(txp) { return keyMapAll; }; +/** + * @callback transactionCallback + * @param {false|Transaction} returnValue + */ +/** + * @desc + * Asyncchronously check with the blockchain if a given transaction was sent. + * + * @param {string} ntxid - the transaction + * @param {transactionCallback} cb + */ Wallet.prototype._checkSentTx = function(ntxid, cb) { var txp = this.txProposals.get(ntxid); var tx = txp.builder.build(); @@ -204,7 +311,15 @@ Wallet.prototype._checkSentTx = function(ntxid, cb) { }); }; - +/** + * @desc + * Handles a 'TXPROPOSAL' network message + * + * @param {string} senderId - the id of the sender + * @param {Object} data - the data received + * @param {Object} data.txProposal - first parameter for {@link TxProposals#merge} + * @emits txProposalsUpdated + */ Wallet.prototype._onTxProposal = function(senderId, data) { var self = this; log.debug('RECV TXPROPOSAL: ', data); @@ -241,7 +356,16 @@ Wallet.prototype._onTxProposal = function(senderId, data) { this._processProposalEvents(senderId, m); }; - +/** + * @desc + * Handle a REJECT message received + * + * @param {string} senderId + * @param {Object} data + * @param {string} data.ntxid + * @emits txProposalsUpdated + * @emits txProposalEvent + */ Wallet.prototype._onReject = function(senderId, data) { preconditions.checkState(data.ntxid); log.debug('RECV REJECT:', data); @@ -265,6 +389,16 @@ Wallet.prototype._onReject = function(senderId, data) { }); }; +/** + * @desc + * Handle a SEEN message received + * + * @param {string} senderId + * @param {Object} data + * @param {string} data.ntxid + * @emits txProposalsUpdated + * @emits txProposalEvent + */ Wallet.prototype._onSeen = function(senderId, data) { preconditions.checkState(data.ntxid); log.debug('RECV SEEN:', data); @@ -281,8 +415,18 @@ Wallet.prototype._onSeen = function(senderId, data) { }; - - +/** + * @desc + * Handle a ADDRESSBOOK message received + * + * {@see Wallet#verifyAddressbookEntry} + * + * @param {string} senderId + * @param {Object} data + * @param {Object} data.addressBook + * @emits addressBookUpdated + * @emits txProposalEvent + */ Wallet.prototype._onAddressBook = function(senderId, data) { preconditions.checkState(data.addressBook); log.debug('RECV ADDRESSBOOK:', data); @@ -303,7 +447,10 @@ Wallet.prototype._onAddressBook = function(senderId, data) { } }; - +/** + * @desc Updates the wallet's last modified timestamp and triggers a save + * @param {number} ts - the timestamp + */ Wallet.prototype.updateTimestamp = function(ts) { preconditions.checkArgument(ts); preconditions.checkArgument(_.isNumber(ts)); @@ -311,12 +458,24 @@ Wallet.prototype.updateTimestamp = function(ts) { this.store(); }; - +/** + * @desc Called when there are no messages in the server + * Triggers a call to {@link Wallet#sendWalletReady} + */ Wallet.prototype._onNoMessages = function() { log.debug('No messages at the server. Requesting sync'); //TODO this.sendWalletReady(); }; +/** + * @desc Demultiplexes a new message received through the wire + * + * @param {string} senderId - the sender id + * @param {Object} data - the received object + * @param {number} ts - the timestamp when this object was received + * + * @emits corrupt + */ Wallet.prototype._onData = function(senderId, data, ts) { preconditions.checkArgument(senderId); preconditions.checkArgument(data); @@ -363,7 +522,7 @@ Wallet.prototype._onData = function(senderId, data, ts) { case 'addressbook': this._onAddressBook(senderId, data); break; - // unused messages + // unused messages case 'disconnect': //case 'an other unused message': break; @@ -375,6 +534,11 @@ Wallet.prototype._onData = function(senderId, data, ts) { this.updateTimestamp(ts); }; +/** + * @desc Handles a connect message + * @param {string} newCopayerId - the new copayer in the wallet + * @emits connect + */ Wallet.prototype._onConnect = function(newCopayerId) { if (newCopayerId) { log.debug('#### Setting new COPAYER:', newCopayerId); @@ -384,10 +548,20 @@ Wallet.prototype._onConnect = function(newCopayerId) { this.emit('connect', peerID); }; +/** + * @desc Returns the network name for this wallet ('testnet' or 'livenet') + * @return {string} + */ Wallet.prototype.getNetworkName = function() { return this.publicKeyRing.network.name; }; +/** + * @desc Serialize options into an object + * @return {Object} with keys id, spendUnconfirmed, + * requiredCopayers, totalCopayers, name, + * version + */ Wallet.prototype._optsToObj = function() { var obj = { id: this.id, @@ -401,35 +575,57 @@ Wallet.prototype._optsToObj = function() { return obj; }; - +/** + * @desc Retrieve the copayerId pubkey for a given index + * @param {number=} index - the index of the copayer, ours by default + * @return {string} hex-encoded pubkey + */ Wallet.prototype.getCopayerId = function(index) { return this.publicKeyRing.getCopayerId(index || 0); }; - +/** + * @desc Get my own pubkey + * @return {string} hex-encoded pubkey + */ Wallet.prototype.getMyCopayerId = function() { return this.getCopayerId(0); //copayer id is hex of a public key }; +/** + * @desc Retrieve my private key + * @return {string} hex-encoded private key + */ Wallet.prototype.getMyCopayerIdPriv = function() { return this.privateKey.getIdPriv(); //copayer idpriv is hex of a private key }; - +/** + * @desc Returns the secret value for other users to join this wallet + * @return {string} my own pubkey, base58 encoded + */ Wallet.prototype.getSecretNumber = function() { if (this.secretNumber) return this.secretNumber; this.secretNumber = Wallet.getRandomNumber(); return this.secretNumber; }; +/** + * @desc Returns the secret number used to prevent MitM attacks from Insight + * @return {string} + */ Wallet.prototype.getSecret = function() { var buf = new Buffer(this.getMyCopayerId() + this.getSecretNumber(), 'hex'); var str = Base58Check.encode(buf); return str; }; - - +/** + * @desc Returns an object with a pubKey value, an hex representation + * of a public key + * @param {string} secretB - the secret to be base58-decoded + * @return {Object} + */ Wallet.decodeSecret = function(secretB) { var secret = Base58Check.decode(secretB); var pubKeyBuf = secret.slice(0, 33); @@ -440,12 +636,28 @@ Wallet.decodeSecret = function(secretB) { } }; - +/** + * @desc Locks other sessions from connecting to the wallet + * @see Async#lockIncommingConnections + */ Wallet.prototype._lockIncomming = function() { this.network.lockIncommingConnections(this.publicKeyRing.getAllCopayerIds()); }; -Wallet.prototype.netStart = function(callback) { +/** + * @desc Sets up the networking with other peers. + * + * @emits connect + * @emits data + * + * @emits ready + * @emits publicKeyRingUpdated + * @emits txProposalsUpdated + * + * @TODO: FIX PROTOCOL -- emit with a space is shitty + * @emits no messages + */ +Wallet.prototype.netStart = function() { var self = this; var net = this.network; @@ -479,6 +691,10 @@ Wallet.prototype.netStart = function(callback) { }); }; +/** + * @desc Retrieves the public keys of all the copayers in the ring + * @return {string[]} hex-encoded public keys of copayers + */ Wallet.prototype.getRegisteredCopayerIds = function() { var l = this.publicKeyRing.registeredCopayers(); var copayers = []; @@ -489,6 +705,12 @@ Wallet.prototype.getRegisteredCopayerIds = function() { return copayers; }; +/** + * @desc Retrieves the public keys of all the peers in the network + * @TODO: Isn't this deprecated? Now that we don't use peerjs + * + * @return {string[]} hex-encoded public keys of copayers + */ Wallet.prototype.getRegisteredPeerIds = function() { var l = this.publicKeyRing.registeredCopayers(); if (this.registeredPeerIds.length !== l) { @@ -508,6 +730,12 @@ Wallet.prototype.getRegisteredPeerIds = function() { return this.registeredPeerIds; }; +/** + * @TODO: Review design of this call + * @desc Send a keepalive to this wallet's {@link WalletLock} instance. + * + * @emits locked - in case the wallet is opened in another instance + */ Wallet.prototype.keepAlive = function() { try { this.lock.keepAlive(); @@ -517,6 +745,9 @@ Wallet.prototype.keepAlive = function() { } }; +/** + * @desc Store the wallet's state + */ Wallet.prototype.store = function() { this.keepAlive(); @@ -525,6 +756,10 @@ Wallet.prototype.store = function() { log.debug('Wallet stored'); }; +/** + * @desc Serialize the wallet into a plain object. + * @return {Object} + */ Wallet.prototype.toObj = function() { var optsObj = this._optsToObj(); @@ -545,8 +780,24 @@ Wallet.prototype.toObj = function() { return walletObj; }; -// fromObj => from a trusted source +/** + * @desc Retrieve the wallet state from a trusted object + * + * @param {Object} o + * @param {Object[]} o.addressBook - Stores known associations of bitcoin addresses to names + * @param {Object} o.privateKey - Private key to be deserialized by {@link PrivateKey#fromObj} + * @param {string} o.networkName - 'livenet' or 'testnet' + * @param {Object} o.publicKeyRing - PublicKeyRing to be deserialized by {@link PublicKeyRing#fromObj} + * @param {number} o.lastTimestamp - last time this wallet object was deserialized + * @param {Object} o.txProposals - TxProposals to be deserialized by {@link TxProposals#fromObj} + * @param {string} o.nickname - user's nickname + * @param {Storage} storage - a Storage instance to store the data of the wallet + * @param {Network} network - a Network instance to communicate with peers + * @param {Blockchain} blockchain - a Blockchain instance to retrieve state from the blockchain + */ Wallet.fromObj = function(o, storage, network, blockchain) { + + // TODO: What is this supposed to do? var opts = JSON.parse(JSON.stringify(o.opts)); opts.addressBook = o.addressBook; @@ -590,15 +841,28 @@ Wallet.fromObj = function(o, storage, network, blockchain) { return new Wallet(opts); }; +/** + * @desc Return a base64 encrypted version of the wallet + * @return {string} base64 encoded string + */ Wallet.prototype.toEncryptedObj = function() { var walletObj = this.toObj(); return this.storage.export(walletObj); }; +/** + * @desc Send a message to other peers + * @param {string[]} recipients - the pubkey of the recipients of the message + * @param {Object} obj - the data to be sent to them + */ Wallet.prototype.send = function(recipients, obj) { this.network.send(recipients, obj); }; +/** + * @desc Send the set of TxProposals to some peers + * @param {string[]} recipients - the pubkeys of the recipients + */ Wallet.prototype.sendAllTxProposals = function(recipients) { var ntxids = this.txProposals.getNtxids(), that = this; @@ -607,6 +871,11 @@ Wallet.prototype.sendAllTxProposals = function(recipients) { }); }; +/** + * @desc Send a TxProposal identified by transaction id to a set of recipients + * @param {string} ntxid - the transaction proposal id + * @param {string[]=} recipients - the pubkeys of the recipients + */ Wallet.prototype.sendTxProposal = function(ntxid, recipients) { preconditions.checkArgument(ntxid); @@ -618,6 +887,10 @@ Wallet.prototype.sendTxProposal = function(ntxid, recipients) { }); }; +/** + * @desc Notifies other peers that a transaction proposal was seen + * @param {string} ntxid + */ Wallet.prototype.sendSeen = function(ntxid) { preconditions.checkArgument(ntxid); log.debug('### SENDING seen: ' + ntxid + ' TO: All'); @@ -628,6 +901,10 @@ Wallet.prototype.sendSeen = function(ntxid) { }); }; +/** + * @desc Notifies other peers that a transaction proposal was rejected + * @param {string} ntxid + */ Wallet.prototype.sendReject = function(ntxid) { preconditions.checkArgument(ntxid); log.debug('### SENDING reject: ' + ntxid + ' TO: All'); @@ -638,7 +915,10 @@ Wallet.prototype.sendReject = function(ntxid) { }); }; - +/** + * @desc Notify other peers that a wallet has been backed up and it's ready to be used + * @param {string[]=} recipients - the pubkeys of the recipients + */ Wallet.prototype.sendWalletReady = function(recipients) { log.debug('### SENDING WalletReady TO:', recipients || 'All'); @@ -648,6 +928,11 @@ Wallet.prototype.sendWalletReady = function(recipients) { }); }; +/** + * @desc Notify other peers of the walletId + * @TODO: Why is this needed? Can't everybody just calculate the walletId? + * @param {string[]=} recipients - the pubkeys of the recipients + */ Wallet.prototype.sendWalletId = function(recipients) { log.debug('### SENDING walletId TO:', recipients || 'All', this.id); @@ -659,7 +944,10 @@ Wallet.prototype.sendWalletId = function(recipients) { }); }; - +/** + * @desc Send the current PublicKeyRing to other recipients + * @param {string[]=} recipients - the pubkeys of the recipients + */ Wallet.prototype.sendPublicKeyRing = function(recipients) { log.debug('### SENDING publicKeyRing TO:', recipients || 'All', this.publicKeyRing.toObj()); var publicKeyRing = this.publicKeyRing.toObj(); @@ -670,6 +958,11 @@ Wallet.prototype.sendPublicKeyRing = function(recipients) { walletId: this.id, }); }; + +/** + * @desc Send the current indexes of our public key ring to other peers + * @param {string[]=} recipients - the pubkeys of the recipients + */ Wallet.prototype.sendIndexes = function(recipients) { var indexes = HDParams.serialize(this.publicKeyRing.indexes); log.debug('### INDEXES TO:', recipients || 'All', indexes); @@ -681,6 +974,10 @@ Wallet.prototype.sendIndexes = function(recipients) { }); }; +/** + * @desc Send our addressBook to other recipients + * @param {string[]=} recipients - the pubkeys of the recipients + */ Wallet.prototype.sendAddressBook = function(recipients) { log.debug('### SENDING addressBook TO:', recipients || 'All', this.addressBook); this.send(recipients, { @@ -690,15 +987,33 @@ Wallet.prototype.sendAddressBook = function(recipients) { }); }; +/** + * @desc Retrieve this wallet's name + * @return {string} + */ Wallet.prototype.getName = function() { return this.name || this.id; }; +/** + * @desc Generate a new address + * @param {boolean} isChange - whether to generate a change address or a receive address + * @return {string[]} a list of all the addresses generated so far for the wallet + */ Wallet.prototype._doGenerateAddress = function(isChange) { return this.publicKeyRing.generateAddress(isChange, this.publicKey); }; - +/** + * @callback addressCallback + * @param {string} addr - all the addresses of the wallet + */ +/** + * @desc Generate a new address + * @param {boolean} isChange - whether to generate a change address or a receive address + * @param {addressCallback} cb + * @return {string[]} a list of all the addresses generated so far for the wallet + */ Wallet.prototype.generateAddress = function(isChange, cb) { var addr = this._doGenerateAddress(isChange); this.sendIndexes(); @@ -707,7 +1022,12 @@ Wallet.prototype.generateAddress = function(isChange, cb) { return addr; }; - +/** + * @desc Retrieve all the Transaction proposals (see {@link TxProposals}) + * @return {Object[]} each object returned represents a transaction proposal, with two additional + * booleans: signedByUs and rejectedByUs. An optional third boolean signals + * whether the transaction was finally rejected (finallyRejected set to true). + */ Wallet.prototype.getTxProposals = function() { var ret = []; var copayers = this.getRegisteredCopayerIds(); @@ -726,6 +1046,11 @@ Wallet.prototype.getTxProposals = function() { return ret; }; +/** + * @desc Removes old transactions + * @param {boolean} deleteAll - if true, remove all the transactions + * @return {number} the number of deleted proposals + */ Wallet.prototype.purgeTxProposals = function(deleteAll) { var m = this.txProposals.length(); @@ -740,6 +1065,11 @@ Wallet.prototype.purgeTxProposals = function(deleteAll) { return m - n; }; +/** + * @desc Reject a proposal + * @param {string} ntxid the id of the transaction proposal to reject + * @emits txProposalsUpdated + */ Wallet.prototype.reject = function(ntxid) { var txp = this.txProposals.reject(ntxid, this.getMyCopayerId()); this.sendReject(ntxid); @@ -747,6 +1077,16 @@ Wallet.prototype.reject = function(ntxid) { this.emit('txProposalsUpdated'); }; +/** + * @callback signCallback + * @param {boolean} ret - true if it was successfully signed + */ +/** + * @desc Sign a proposal + * @param {string} ntxid the id of the transaction proposal to sign + * @param {signCallback} cb - a callback to be called on successful signing + * @emits txProposalsUpdated + */ Wallet.prototype.sign = function(ntxid, cb) { preconditions.checkState(!_.isUndefined(this.getMyCopayerId())); var self = this; @@ -783,7 +1123,15 @@ Wallet.prototype.sign = function(ntxid, cb) { }, 10); }; - +/** + * @callback broadcastCallback + * @param {string} txid - the transaction id on the blockchain + */ +/** + * @desc Broadcasts a transaction to the blockchain + * @param {string} ntxid - the transaction proposal id + * @param {broadcastCallback} cb + */ Wallet.prototype.sendTx = function(ntxid, cb) { var txp = this.txProposals.get(ntxid); @@ -820,6 +1168,12 @@ Wallet.prototype.sendTx = function(ntxid, cb) { }); }; +/** + * @desc Create a Payment Protocol transaction + * @param {Object|string} options - if it's a string, parse it as the uri + * @param {string} options.uri the url for the transaction + * @param {Function} cb + */ Wallet.prototype.createPaymentTx = function(options, cb) { var self = this; @@ -863,6 +1217,13 @@ Wallet.prototype.createPaymentTx = function(options, cb) { }); }; +/** + * @desc Creates a Payment TxProposal from a uri + * @param {Object} options + * @param {string=} options.uri + * @param {string=} options.url + * @param {Function} cb + */ Wallet.prototype.fetchPaymentTx = function(options, cb) { var self = this; @@ -890,6 +1251,19 @@ Wallet.prototype.fetchPaymentTx = function(options, cb) { }); }; +/** + * @desc Analyzes a payment request and generates a transaction proposal for it. + * @param {Object} options + * @param {PayProRequest} pr + * @param {string} pr.payment_details_version + * @param {string} pr.pki_type + * @param {Object} pr.data + * @param {string} pr.serialized_payment_details + * @param {string} pr.signature + * @param {string} options.memo + * @param {string} options.comment + * @param {Function} cb + */ Wallet.prototype.receivePaymentRequest = function(options, pr, cb) { var self = this; @@ -1007,6 +1381,18 @@ Wallet.prototype.receivePaymentRequest = function(options, pr, cb) { }); }; +/** + * @desc Send a payment transaction to a server, complying with BIP70 + * + * @TODO: Get this out of here. + * + * @param {string} ntxid - the transaction proposal id + * @param {Object} options + * @param {string} options.refund_to + * @param {string} options.memo + * @param {string} options.comment + * @param {Function} cb + */ Wallet.prototype.sendPaymentTx = function(ntxid, options, cb) { var self = this; @@ -1115,6 +1501,9 @@ Wallet.prototype.sendPaymentTx = function(ntxid, options, cb) { }); }; +/** + * @desc Handles a PaymentRequestACK from the server + */ Wallet.prototype.receivePaymentRequestACK = function(ntxid, tx, txp, ack, cb) { var self = this; @@ -1162,6 +1551,10 @@ Wallet.prototype.receivePaymentRequestACK = function(ntxid, tx, txp, ack, cb) { return cb(txid, txp.merchant); }; +/** + * @desc Create a Payment Transaction Sync (see BIP70) + * @TODO: Document better + */ Wallet.prototype.createPaymentTxSync = function(options, merchantData, unspent) { var self = this; var priv = this.privateKey; @@ -1281,10 +1674,14 @@ Wallet.prototype.createPaymentTxSync = function(options, merchantData, unspent) return ntxid; }; -// This essentially ensures that a copayer hasn't tampered with a -// PaymentRequest message from a payment server. It verifies the signature -// based on the cert, and checks to ensure the desired outputs are the same as -// the ones on the tx proposal. +/** + * @desc Verifies a PaymentRequest sent by another peer + * This essentially ensures that a copayer hasn't tampered with a + * PaymentRequest message from a payment server. It verifies the signature + * based on the cert, and checks to ensure the desired outputs are the same as + * the ones on the tx proposal. + * @TODO: Document better + */ Wallet.prototype.verifyPaymentRequest = function(ntxid) { if (!ntxid) return false; @@ -1429,6 +1826,10 @@ Wallet.prototype.verifyPaymentRequest = function(ntxid) { return true; }; +/** + * @desc Mark that a user has seen a given TxProposal + * @return {boolean} true if the internal state has changed + */ Wallet.prototype.addSeenToTxProposals = function() { var ret = false; var myId = this.getMyCopayerId(); @@ -1444,27 +1845,54 @@ Wallet.prototype.addSeenToTxProposals = function() { return ret; }; -// TODO: remove this method and use getAddressesInfo everywhere +/** + * @desc Alias for {@link PublicKeyRing#getAddresses} + * @TODO: remove this method and use getAddressesInfo everywhere + * @return {Buffer[]} + */ Wallet.prototype.getAddresses = function(opts) { return this.publicKeyRing.getAddresses(opts); }; +/** + * @desc Retrieves all addresses as strings. + * + * @param {Object} opts - Same options as {@link PublicKeyRing#getAddresses} + * @return {string[]} + */ Wallet.prototype.getAddressesStr = function(opts) { return this.getAddresses(opts).map(function(a) { return a.toString(); }); }; +/** + * @desc Alias for {@link PublicKeyRing#getAddressesInfo} + */ Wallet.prototype.getAddressesInfo = function(opts) { return this.publicKeyRing.getAddressesInfo(opts, this.publicKey); }; - +/** + * @desc Returns true if a given address was generated by deriving our master public key + * @return {boolean} + */ Wallet.prototype.addressIsOwn = function(addrStr, opts) { var addrList = this.getAddressesStr(opts); return _.any(addrList, function(value) { return value === addrStr; }); }; -//retunrs values in SATOSHIs + +/** + * @callback {getBalanceCallback} + * @param {string=} err - an error, if any + * @param {number} balance - total number of satoshis for all addresses + * @param {Object} balanceByAddr - maps string addresses to satoshis + * @param {number} safeBalance - total number of satoshis in UTXOs that are not part of any TxProposal + */ +/** + * @desc Returns the balances for all addresses in Satoshis + * @param {getBalanceCallback} cb + */ Wallet.prototype.getBalance = function(cb) { var balance = 0; var safeBalance = 0; @@ -1502,16 +1930,30 @@ Wallet.prototype.getBalance = function(cb) { }; -// See +// See // https://github.com/bitpay/copay/issues/1056 // // maxRejectCount should equal requiredCopayers -// strictly. -// -Wallet.prototype.maxRejectCount = function(cb) { +// strictly. +/** + * @desc Get the number of copayers that need to reject a transaction so it can't be signed + * @return {number} + */ +Wallet.prototype.maxRejectCount = function() { return this.totalCopayers - this.requiredCopayers; }; +/** + * @callback getUnspentCallback + * @TODO: Document this better + * @param {string} error + * @param {Object[]} safeUnspendList + * @param {Object[]} unspentList + */ +/** + * @desc Get a list of unspent transaction outputs + * @param {getUnspentCallback} cb + */ Wallet.prototype.getUnspent = function(cb) { var self = this; this.blockchain.getUnspent(this.getAddressesStr(), function(err, unspentList) { @@ -1581,6 +2023,10 @@ Wallet.prototype.removeTxWithSpentInputs = function(cb) { }; +/** + * @desc Create a transaction proposal + * @TODO: Document more + */ Wallet.prototype.createTx = function(toAddress, amountSatStr, comment, opts, cb) { var self = this; @@ -1624,6 +2070,10 @@ Wallet.prototype.createTx = function(toAddress, amountSatStr, comment, opts, cb) }); }; +/** + * @desc Create a transaction proposal + * @TODO: Document more + */ Wallet.prototype.createTxSync = function(toAddress, amountSatStr, comment, utxos, opts) { var pkr = this.publicKeyRing; var priv = this.privateKey; @@ -1687,6 +2137,13 @@ Wallet.prototype.createTxSync = function(toAddress, amountSatStr, comment, utxos return ntxid; }; +/** + * @desc Updates all the indexes for the current publicKeyRing + * + * Triggers a wallet {@link Wallet#store} call + * @param {Function} callback - called when all indexes have been updated. Receives an error, if any, as first argument + * @emits publicKeyRingUpdated + */ Wallet.prototype.updateIndexes = function(callback) { var self = this; log.debug('Updating indexes...'); @@ -1704,8 +2161,13 @@ Wallet.prototype.updateIndexes = function(callback) { self.store(); callback(); }); -} +}; +/** + * @desc Updates the lastly used index + * @param {Object} index - an index, as used by {@link PublicKeyRing} + * @param {Function} callback - called with no arguments when done updating + */ Wallet.prototype.updateIndex = function(index, callback) { var self = this; var SCANN_WINDOW = 20; @@ -1721,8 +2183,17 @@ Wallet.prototype.updateIndex = function(index, callback) { callback(); }); }); -} +}; +/** + * @desc Derive addresses using the given parameters + * + * @param {number} index - the index to start with + * @param {number} amount - number of addresses to derive + * @param {boolean} isChange - derive change addresses or receive addresses + * @param {number} copayerIndex - the index of the copayer for whom to derive addresses + * @return {string[]} the result of calling {@link PublicKeyRing#getAddress} + */ Wallet.prototype.deriveAddresses = function(index, amount, isChange, copayerIndex) { preconditions.checkArgument(amount); preconditions.shouldBeDefined(copayerIndex); @@ -1732,11 +2203,26 @@ Wallet.prototype.deriveAddresses = function(index, amount, isChange, copayerInde ret[i] = this.publicKeyRing.getAddress(index + i, isChange, copayerIndex).toString(); } return ret; -} +}; -// This function scans the publicKeyRing branch starting at index @start and reports the index with last activity, -// using a scan window of @gap. The argument @change defines the branch to scan: internal or external. -// Returns -1 if no activity is found in range. +/** + * @callback {indexDiscoveryCallback} + * @param {?} err + * @param {number} lastActivityIndex + */ +/** + * @desc Scans the block chain for the last index with activity for a copayer + * + * This function scans the publicKeyRing branch starting at index @start and reports the index with last activity, + * using a scan window of @gap. The argument @change defines the branch to scan: internal or external. + * Returns -1 if no activity is found in range. + * @param {number} start - the number for which to start scanning + * @param {boolean} change - whether to search for in the change branch or the receive branch + * @param {number} copayerIndex - the index of the copayer + * @param {number} gap - the maximum number of addresses to scan after the last active address + * @param {indexDiscoveryCallback} cb - callback + * @return {number} -1 if there's no activity in the range provided + */ Wallet.prototype.indexDiscovery = function(start, change, copayerIndex, gap, cb) { preconditions.shouldBeDefined(copayerIndex); preconditions.checkArgument(gap); @@ -1771,9 +2257,11 @@ Wallet.prototype.indexDiscovery = function(start, change, copayerIndex, gap, cb) cb(null, lastActive); } ); -} - +}; +/** + * @desc Closes the wallet and disconnects all services + */ Wallet.prototype.close = function() { log.debug('## CLOSING'); this.lock.release(); @@ -1781,16 +2269,30 @@ Wallet.prototype.close = function() { this.blockchain.destroy(); }; +/** + * @desc Returns the name of the network ('livenet' or 'testnet') + * @return {string} + */ Wallet.prototype.getNetwork = function() { return this.network; }; +/** + * @desc Throws an error if an address already exists in the address book + * @private + */ Wallet.prototype._checkAddressBook = function(key) { if (this.addressBook[key] && this.addressBook[key].copayerId != -1) { throw new Error('This address already exists in your Address Book: ' + address); } }; +/** + * @desc Add an entry to the address book + * + * @param {string} key - the address to be added + * @param {string} label - a name for the address + */ Wallet.prototype.setAddressBook = function(key, label) { this._checkAddressBook(key); var copayerId = this.getMyCopayerId(); @@ -1813,6 +2315,14 @@ Wallet.prototype.setAddressBook = function(key, label) { this.store(); }; +/** + * @desc Verifies that an addressbook entry is correctly signed by a copayer + * + * @param {Object} rcvEntry - the entry in the address book + * @param {string} senderId - the pubkey of a copayer + * @param {string} key - the base58 encoded address + * @return {boolean} true if the signature matches + */ Wallet.prototype.verifyAddressbookEntry = function(rcvEntry, senderId, key) { if (!key) throw new Error('Keys are required'); var signature = rcvEntry.signature; @@ -1823,29 +2333,53 @@ Wallet.prototype.verifyAddressbookEntry = function(rcvEntry, senderId, key) { createdTs: rcvEntry.createdTs }; return this.verifySignedJson(senderId, payload, signature); -} +}; +/** + * @desc Hides or unhides an address book entry + * @param {string} key - the address in the addressbook + */ Wallet.prototype.toggleAddressBookEntry = function(key) { if (!key) throw new Error('Key is required'); this.addressBook[key].hidden = !this.addressBook[key].hidden; this.store(); }; +/** + * @desc Returns true if there are more than one cosigners + * @return {boolean} + */ Wallet.prototype.isShared = function() { return this.totalCopayers > 1; -} +}; +/** + * @desc Returns true if the keyring is complete and all users have backed up the wallet + * @return {boolean} + */ Wallet.prototype.isReady = function() { var ret = this.publicKeyRing.isComplete() && this.publicKeyRing.isFullyBackup(); return ret; }; +/** + * @desc Mark that our backup is ready and send a sync to other users. + * + * Also backs up the wallet + */ Wallet.prototype.setBackupReady = function() { this.publicKeyRing.setBackupReady(); this.sendPublicKeyRing(); this.store(); }; +/** + * @desc Sign a JSON + * + * @TODO: THIS WON'T WORK ALLWAYS! JSON.stringify doesn't warants an order + * @param {Object} payload - the payload to verify + * @return {string} base64 encoded string + */ Wallet.prototype.signJson = function(payload) { var key = new bitcore.Key(); key.private = new Buffer(this.getMyCopayerIdPriv(), 'hex'); @@ -1854,6 +2388,16 @@ Wallet.prototype.signJson = function(payload) { return sign.toString('hex'); } +/** + * @desc Verify that a JSON object is correctly signed + * + * @TODO: THIS WON'T WORK ALLWAYS! JSON.stringify doesn't warants an order + * + * @param {string} senderId - a sender's public key, hex encoded + * @param {Object} payload - the object to verify + * @param {string} signature - a sender's public key, hex encoded + * @return {boolean} + */ Wallet.prototype.verifySignedJson = function(senderId, payload, signature) { var pubkey = new Buffer(senderId, 'hex'); var sign = new Buffer(signature, 'hex'); @@ -1870,6 +2414,10 @@ Wallet.prototype.verifySignedJson = function(senderId, payload, signature) { // var $http = angular.bootstrap().get('$http'); // } +/** + * @desc Create a HTTP request + * @TODO: This shouldn't be a wallet responsibility + */ Wallet.request = function(options, callback) { if (_.isString(options)) { options = {