From 512f3de412cafed99fc6344eb4c7666eae169119 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Tue, 31 May 2016 05:44:44 -0700 Subject: [PATCH] scanning. local. etc. --- lib/bcoin/hd.js | 116 +++++++++++++++++++++++++++++++++++++++++- lib/bcoin/ldb.js | 2 +- lib/bcoin/wallet.js | 101 +++++++++++++++++++++--------------- lib/bcoin/walletdb.js | 30 +++++++++++ 4 files changed, 206 insertions(+), 43 deletions(-) diff --git a/lib/bcoin/hd.js b/lib/bcoin/hd.js index 7240b123..51db0ca8 100644 --- a/lib/bcoin/hd.js +++ b/lib/bcoin/hd.js @@ -113,6 +113,9 @@ function Mnemonic(options) { if (!options) options = {}; + if (typeof options === 'string') + options = { phrase: options }; + this.bits = options.bits || 128; this.entropy = options.entropy; this.phrase = options.phrase; @@ -300,13 +303,25 @@ HD.fromMnemonic = function fromMnemonic(options, network) { * Instantiate an HD key from a jsonified key object. * @param {Object} json - The jsonified transaction object. * @param {String?} passphrase - * @returns {HDPrivateKey} + * @returns {HDPrivateKey|HDPublicKey} */ HD.fromJSON = function fromJSON(json, passphrase) { return HDPrivateKey.fromJSON(json, passphrase); }; +/** + * Instantiate an HD key from serialized data. + * @param {Buffer} data + * @returns {HDPrivateKey|HDPublicKey} + */ + +HD.fromRaw = function fromRaw(data) { + if (HDPrivateKey.hasPrefix(data)) + return HDPrivateKey.fromRaw(data); + return HDPublicKey.fromRaw(data); +}; + /** * Generate an hdkey from any number of options. * @param {Object|Mnemonic|Buffer} options - mnemonic, mnemonic @@ -335,6 +350,9 @@ HD.fromAny = function fromAny(options, network) { if (HDPublicKey.isExtended(xkey)) return HDPublicKey.fromBase58(xkey); + if (HD.hasPrefix(options)) + return HD.fromRaw(options); + return HDPrivateKey.fromMnemonic(options, network); }; @@ -349,6 +367,17 @@ HD.isExtended = function isExtended(data) { || HDPublicKey.isExtended(data); }; +/** + * Test whether an object is in the form of a serialized hd key. + * @param {Buffer} data + * @returns {NetworkType} + */ + +HD.hasPrefix = function hasPrefix(data) { + return HDPrivateKey.hasPrefix(data) + || HDPublicKey.hasPrefix(data); +}; + /** * Parse a derivation path and return an array of indexes. * @see https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki @@ -655,6 +684,30 @@ HDPrivateKey.isExtended = function isExtended(data) { return false; }; +/** + * Test whether a buffer has a valid network prefix. + * @param {Buffer} data + * @returns {NetworkType} + */ + +HDPrivateKey.hasPrefix = function hasPrefix(data) { + var i, version, prefix, type; + + if (!Buffer.isBuffer(data)) + return false; + + version = data.readUInt32BE(0, true); + + for (i = 0; i < network.types.length; i++) { + type = network.types[i]; + prefix = network[type].prefixes.xprivkey; + if (version === prefix) + return type; + } + + return false; +}; + /** * Test whether a string is a valid path. * @param {String} path @@ -865,6 +918,12 @@ HDPrivateKey.parseBase58 = function parseBase58(xkey) { return data; }; +/** + * Parse a raw serialized key. + * @param {Buffer} raw + * @returns {Object} + */ + HDPrivateKey.parseRaw = function parseRaw(raw) { var p = new BufferReader(raw, true); var data = {}; @@ -903,6 +962,12 @@ HDPrivateKey.prototype.toBase58 = function toBase58(network) { return utils.toBase58(this.toRaw(network)); }; +/** + * Serialize the key + * @param {Network|String} network + * @returns {Buffer} + */ + HDPrivateKey.prototype.toRaw = function toRaw(network, writer) { var p = new BufferWriter(writer); @@ -936,6 +1001,12 @@ HDPrivateKey.fromBase58 = function fromBase58(xkey) { return new HDPrivateKey(HDPrivateKey.parseBase58(xkey)); }; +/** + * Instantiate key from serialized data. + * @param {Buffer} raw + * @returns {HDPrivateKey} + */ + HDPrivateKey.fromRaw = function fromRaw(raw) { return new HDPrivateKey(HDPrivateKey.parseRaw(raw)); }; @@ -1312,6 +1383,30 @@ HDPublicKey.isExtended = function isExtended(data) { return false; }; +/** + * Test whether a buffer has a valid network prefix. + * @param {Buffer} data + * @returns {NetworkType} + */ + +HDPublicKey.hasPrefix = function hasPrefix(data) { + var i, version, prefix, type; + + if (!Buffer.isBuffer(data)) + return false; + + version = data.readUInt32BE(0, true); + + for (i = 0; i < network.types.length; i++) { + type = network.types[i]; + prefix = network[type].prefixes.xpubkey; + if (version === prefix) + return type; + } + + return false; +}; + /** * Parse a base58 extended public key. * @param {Base58String} xkey @@ -1324,10 +1419,15 @@ HDPublicKey.parseBase58 = function parseBase58(xkey) { return data; }; +/** + * Parse a raw serialized key. + * @param {Buffer} raw + * @returns {Object} + */ + HDPublicKey.parseRaw = function parseRaw(raw) { var p = new BufferReader(raw, true); var data = {}; - var i, type, prefix; data.version = p.readU32BE(); data.depth = p.readU8(); @@ -1361,6 +1461,12 @@ HDPublicKey.prototype.toBase58 = function toBase58(network) { return utils.toBase58(this.toRaw(network)); }; +/** + * Serialize the key + * @param {Network|String} network + * @returns {Buffer} + */ + HDPublicKey.prototype.toRaw = function toRaw(network, writer) { var p = new BufferWriter(writer); @@ -1393,6 +1499,12 @@ HDPublicKey.fromBase58 = function fromBase58(xkey) { return new HDPublicKey(HDPublicKey.parseBase58(xkey)); }; +/** + * Instantiate key from serialized data. + * @param {Buffer} raw + * @returns {HDPublicKey} + */ + HDPublicKey.fromRaw = function fromRaw(data) { return new HDPublicKey(HDPublicKey.parseRaw(data)); }; diff --git a/lib/bcoin/ldb.js b/lib/bcoin/ldb.js index f71c6fc8..f6dd3f48 100644 --- a/lib/bcoin/ldb.js +++ b/lib/bcoin/ldb.js @@ -128,7 +128,7 @@ ldb.parseOptions = function parseOptions(options) { else if (bcoin.isBrowser) db = require('level-js'); else - db = require(options.db); + db = require(backend.name); return utils.merge({}, options, { backend: backend.name, diff --git a/lib/bcoin/wallet.js b/lib/bcoin/wallet.js index 5cf733a6..a8621d1e 100644 --- a/lib/bcoin/wallet.js +++ b/lib/bcoin/wallet.js @@ -71,16 +71,21 @@ function Wallet(options) { this.loaded = false; this.loading = false; this.account = null; + this.local = false; if (!this.id) this.id = this.getID(); - // An in-memory database for testing. - if (!this.db) { + if (this.options.passphrase) + this.master.encrypt(this.options.passphrase); + + if (!this.db || typeof this.db === 'string') { + this.local = true; this.db = new bcoin.walletdb({ network: this.network, - name: this.id, - db: 'memory' + name: 'wallet-' + this.id, + location: this.options.location, + db: this.db || 'memory' }); } } @@ -174,9 +179,6 @@ Wallet.prototype.init = function init(callback) { self.account = account; - if (options.passphrase) - self.master.encrypt(options.passphrase); - if (Buffer.isBuffer(options.passphrase)) options.passphrase.fill(0); @@ -189,11 +191,25 @@ Wallet.prototype.init = function init(callback) { if (err) return callback(err); + if (self.local) { + return self.db.getRaw(self.id, function(err, data) { + if (err) + return callback(err); + + if (data) { + utils.merge(self, data); + return self.getAccount(0, done); + } + + self.initialized = true; + self.createAccount(options, done); + }); + } + if (self.initialized) return self.getAccount(0, done); self.initialized = true; - self.createAccount(options, done); }); }; @@ -439,7 +455,7 @@ Wallet.prototype.fill = function fill(tx, options, callback) { options = {}; if (!this.initialized) - return callback(new Error('Cannot use uninitialized wallet.')); + return callback(new Error('Wallet is not initialized.')); this.getCoins(options.account, function(err, coins) { if (err) @@ -862,24 +878,30 @@ Wallet.prototype.scan = function scan(getByAddress, callback) { var self = this; var total = 0; - (function next() { - self.createAccount(self.options, function(err, account) { + if (!this.initialized) + return callback(new Error('Wallet is not initialized.')); + + (function next(err, account) { + if (err) + return callback(err); + + account.scan(getByAddress, function(err, result) { if (err) return callback(err); - account.scan(getByAddress, function(err, result) { - if (err) - return callback(err); - - if (result === 0) + if (result === 0) { + return self.save(function(err) { + if (err) + return callback(err); return callback(null, total); + }); + } - total += result; + total += result; - next(); - }); + self.createAccount(self.options, next); }); - })(); + })(null, this.account); }; /** @@ -2034,7 +2056,7 @@ Account.prototype.scan = function scan(getByAddress, callback) { var total = 0; if (!this.initialized) - return callback(new Error('Account is uninitialized.')); + return callback(new Error('Account is not initialized.')); function addTX(txs, calback) { if (!Array.isArray(txs) || txs.length === 0) @@ -2051,38 +2073,37 @@ Account.prototype.scan = function scan(getByAddress, callback) { } (function chainCheck(change) { + var address = change ? self.changeAddress : self.receiveAddress; var gap = 0; - (function next() { - self.createAddress(change, function(err, address) { + (function next(err, address) { + if (err) + return callback(err); + + getByAddress(address.getAddress(), function(err, txs) { if (err) return callback(err); - getByAddress(address.getAddress(), function(err, txs) { + addTX(txs, function(err, result) { if (err) return callback(err); - addTX(txs, function(err, result) { - if (err) - return callback(err); + if (result) { + total++; + gap = 0; + return self.createAddress(change, next); + } - if (result) { - total++; - gap = 0; - return next(); - } + if (++gap < 20) + return self.createAddress(change, next); - if (++gap < 20) - return next(); + if (!change) + return chainCheck(true); - if (!change) - return chainCheck(true); - - return callback(null, total); - }); + return callback(null, total); }); }); - })(); + })(null, address); })(false); }; diff --git a/lib/bcoin/walletdb.js b/lib/bcoin/walletdb.js index e0d0aebf..65397adf 100644 --- a/lib/bcoin/walletdb.js +++ b/lib/bcoin/walletdb.js @@ -340,6 +340,36 @@ WalletDB.prototype.get = function get(id, callback) { }); }; +/** + * Get raw wallet from the database, do not instantiate. + * @param {WalletID} id + * @param {Function} callback - Returns [Error, {@link Wallet}]. + */ + +WalletDB.prototype.getRaw = function getRaw(id, callback) { + var self = this; + var wallet; + + if (!id) + return callback(); + + this.db.get('w/' + id, function(err, data) { + if (err && err.type !== 'NotFoundError') + return callback(); + + if (!data) + return callback(); + + try { + data = bcoin.wallet.parseRaw(data); + } catch (e) { + return callback(e); + } + + return callback(null, data); + }); +}; + /** * Save a wallet to the database. * @param {Wallet} wallet