scanning.
This commit is contained in:
parent
5095f78dbf
commit
a95ab18379
@ -126,8 +126,12 @@ Address.prototype.toScript = function toScript() {
|
||||
* @returns {Base58String}
|
||||
*/
|
||||
|
||||
Address.prototype.toString = function toString() {
|
||||
return this.toBase58();
|
||||
Address.prototype.toString = function toString(enc) {
|
||||
if (enc === 'hex')
|
||||
return this.getHash('hex');
|
||||
if (enc === 'base58')
|
||||
enc = null;
|
||||
return this.toBase58(enc);
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@ -175,7 +175,7 @@ Mnemonic.prototype.toSeed = function toSeed() {
|
||||
if (!this.phrase)
|
||||
this.phrase = this.createMnemonic();
|
||||
|
||||
this.seed = utils.pbkdf2(
|
||||
this.seed = utils.pbkdf2Sync(
|
||||
nfkd(this.phrase),
|
||||
nfkd('mnemonic' + this.passphrase),
|
||||
2048, 64, 'sha512');
|
||||
@ -1083,9 +1083,9 @@ HDPrivateKey.prototype.fromBase58 = function fromBase58(xkey) {
|
||||
|
||||
HDPrivateKey.prototype.fromRaw = function fromRaw(raw) {
|
||||
var p = new BufferReader(raw);
|
||||
var i, type, prefix;
|
||||
var i, version, type, prefix;
|
||||
|
||||
this.version = p.readU32BE();
|
||||
version = p.readU32BE();
|
||||
this.depth = p.readU8();
|
||||
this.parentFingerPrint = p.readBytes(4);
|
||||
this.childIndex = p.readU32BE();
|
||||
@ -1097,7 +1097,7 @@ HDPrivateKey.prototype.fromRaw = function fromRaw(raw) {
|
||||
for (i = 0; i < network.types.length; i++) {
|
||||
type = network.types[i];
|
||||
prefix = network[type].prefixes.xprivkey;
|
||||
if (this.version === prefix)
|
||||
if (version === prefix)
|
||||
break;
|
||||
}
|
||||
|
||||
@ -1616,9 +1616,9 @@ HDPublicKey.prototype.fromBase58 = function fromBase58(xkey) {
|
||||
|
||||
HDPublicKey.prototype.fromRaw = function fromRaw(raw) {
|
||||
var p = new BufferReader(raw);
|
||||
var i, type, prefix;
|
||||
var i, version, type, prefix;
|
||||
|
||||
this.version = p.readU32BE();
|
||||
version = p.readU32BE();
|
||||
this.depth = p.readU8();
|
||||
this.parentFingerPrint = p.readBytes(4);
|
||||
this.childIndex = p.readU32BE();
|
||||
@ -1629,7 +1629,7 @@ HDPublicKey.prototype.fromRaw = function fromRaw(raw) {
|
||||
for (i = 0; i < network.types.length; i++) {
|
||||
type = network.types[i];
|
||||
prefix = network[type].prefixes.xpubkey;
|
||||
if (this.version === prefix)
|
||||
if (version === prefix)
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@ -776,7 +776,7 @@ MTX.prototype.maxSize = function maxSize(options, force) {
|
||||
options = {};
|
||||
|
||||
if (options instanceof bcoin.wallet)
|
||||
options = { wallet: options, m: options.m, n: options.n };
|
||||
options = { wallet: options };
|
||||
|
||||
if (options.wallet)
|
||||
wallet = options.wallet;
|
||||
@ -791,6 +791,7 @@ MTX.prototype.maxSize = function maxSize(options, force) {
|
||||
if (!wallet)
|
||||
return;
|
||||
|
||||
// Hack
|
||||
address = wallet.receiveAddress;
|
||||
|
||||
if (address.program && hash.length === 20)
|
||||
|
||||
@ -33,7 +33,6 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
var crypto = require('crypto');
|
||||
var utils = require('./utils');
|
||||
|
||||
/**
|
||||
@ -70,7 +69,7 @@ function scrypt(passwd, salt, N, r, p, len, callback) {
|
||||
XY = new Buffer(256 * r);
|
||||
V = new Buffer(128 * r * N);
|
||||
|
||||
crypto.pbkdf2(passwd, salt, 1, p * 128 * r, 'sha256', function(err, B) {
|
||||
utils.pbkdf2(passwd, salt, 1, p * 128 * r, 'sha256', function(err, B) {
|
||||
if (err)
|
||||
return callback(err);
|
||||
|
||||
@ -80,7 +79,7 @@ function scrypt(passwd, salt, N, r, p, len, callback) {
|
||||
if (err)
|
||||
return callback(err);
|
||||
|
||||
crypto.pbkdf2(passwd, B, 1, len, 'sha256', callback);
|
||||
utils.pbkdf2(passwd, B, 1, len, 'sha256', callback);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@ -69,12 +69,12 @@ function scrypt(passwd, salt, N, r, p, len) {
|
||||
XY = new Buffer(256 * r);
|
||||
V = new Buffer(128 * r * N);
|
||||
|
||||
B = utils.pbkdf2(passwd, salt, 1, p * 128 * r, 'sha256');
|
||||
B = utils.pbkdf2Sync(passwd, salt, 1, p * 128 * r, 'sha256');
|
||||
|
||||
for (i = 0; i < p; i++)
|
||||
smix(B, i * 128 * r, r, N, V, XY);
|
||||
|
||||
return utils.pbkdf2(passwd, B, 1, len, 'sha256');
|
||||
return utils.pbkdf2Sync(passwd, B, 1, len, 'sha256');
|
||||
}
|
||||
|
||||
function salsa20_8(B) {
|
||||
|
||||
@ -201,15 +201,61 @@ WalletMap.prototype.getPaths = function getPaths(address) {
|
||||
return this.table[address];
|
||||
};
|
||||
|
||||
WalletMap.prototype.toJSON = function toJSON() {
|
||||
return {
|
||||
inputs: this.inputs.map(function(input) {
|
||||
return input.toJSON();
|
||||
}),
|
||||
outputs: this.outputs.map(function(output) {
|
||||
return output.toJSON();
|
||||
}),
|
||||
paths: this.paths.map(function(path) {
|
||||
return path.toJSON();
|
||||
}),
|
||||
accounts: this.accounts.map(function(path) {
|
||||
return {
|
||||
id: path.id,
|
||||
name: path.name,
|
||||
account: path.account
|
||||
};
|
||||
}),
|
||||
wallets: this.wallets
|
||||
};
|
||||
};
|
||||
|
||||
function MapMember() {
|
||||
this.value = 0;
|
||||
this.hash = null;
|
||||
this.wallets = [];
|
||||
this.address = null;
|
||||
this.paths = [];
|
||||
this.accounts = [];
|
||||
this.wallets = [];
|
||||
}
|
||||
|
||||
MapMember.prototype.toJSON = function toJSON() {
|
||||
return {
|
||||
value: utils.btc(this.value),
|
||||
address: this.address
|
||||
? this.address.toBase58()
|
||||
: null,
|
||||
hash: this.address
|
||||
? this.address.getHash('hex')
|
||||
: null,
|
||||
paths: this.paths.map(function(path) {
|
||||
return path.toJSON();
|
||||
}),
|
||||
accounts: this.accounts.map(function(path) {
|
||||
return {
|
||||
id: path.id,
|
||||
name: path.name,
|
||||
account: path.account
|
||||
};
|
||||
}),
|
||||
wallets: this.wallets
|
||||
};
|
||||
};
|
||||
|
||||
MapMember.fromMember = function fromMember(table, io) {
|
||||
var hash = io.getHash('hex');
|
||||
var address = io.getAddress();
|
||||
var member = new MapMember();
|
||||
var i, paths;
|
||||
|
||||
@ -217,19 +263,20 @@ MapMember.fromMember = function fromMember(table, io) {
|
||||
? io.coin.value
|
||||
: io.value || 0;
|
||||
|
||||
if (!hash)
|
||||
if (!address)
|
||||
return member;
|
||||
|
||||
paths = table[hash];
|
||||
paths = table[address.getHash('hex')];
|
||||
|
||||
assert(paths);
|
||||
|
||||
member.hash = hash;
|
||||
member.address = address;
|
||||
member.paths = paths;
|
||||
|
||||
for (i = 0; i < paths.length; i++)
|
||||
member.wallets.push(paths[i].id);
|
||||
|
||||
member.accounts = uniq(member.paths);
|
||||
member.wallets = utils.uniq(member.wallets);
|
||||
|
||||
return member;
|
||||
@ -258,7 +305,7 @@ TXDB.prototype.getMap = function getMap(tx, callback) {
|
||||
|
||||
map = WalletMap.fromTX(table, tx);
|
||||
|
||||
utils.print(map);
|
||||
utils.print(map.toJSON());
|
||||
|
||||
return callback(null, map);
|
||||
});
|
||||
@ -276,7 +323,7 @@ TXDB.prototype.mapAddresses = function mapAddresses(address, callback) {
|
||||
var count = 0;
|
||||
var i, keys, values;
|
||||
|
||||
return utils.forEachSerial(address, function(address, next) {
|
||||
utils.forEachSerial(address, function(address, next) {
|
||||
self.walletdb.getAddress(address, function(err, paths) {
|
||||
if (err)
|
||||
return next(err);
|
||||
|
||||
@ -358,7 +358,7 @@ utils.hmac = function hmac(alg, data, salt) {
|
||||
* @returns {Buffer}
|
||||
*/
|
||||
|
||||
utils.pbkdf2 = function pbkdf2(key, salt, iter, len, alg) {
|
||||
utils.pbkdf2Sync = function pbkdf2Sync(key, salt, iter, len, alg) {
|
||||
if (typeof key === 'string')
|
||||
key = new Buffer(key, 'utf8');
|
||||
|
||||
@ -381,7 +381,7 @@ utils.pbkdf2 = function pbkdf2(key, salt, iter, len, alg) {
|
||||
* @param {Function} callback
|
||||
*/
|
||||
|
||||
utils.pbkdf2Async = function pbkdf2Async(key, salt, iter, len, alg, callback) {
|
||||
utils.pbkdf2 = function pbkdf2(key, salt, iter, len, alg, callback) {
|
||||
var result;
|
||||
|
||||
if (typeof key === 'string')
|
||||
@ -403,13 +403,13 @@ utils.pbkdf2Async = function pbkdf2Async(key, salt, iter, len, alg, callback) {
|
||||
};
|
||||
|
||||
/**
|
||||
* Derive a key using pbkdf2 with 25,000 iterations.
|
||||
* Derive a key using pbkdf2 with 50,000 iterations.
|
||||
* @param {Buffer|String} passphrase
|
||||
* @param {Function} callback
|
||||
*/
|
||||
|
||||
utils.derive = function derive(passphrase, callback) {
|
||||
utils.pbkdf2Async(passphrase, 'bcoin', 25000, 32, 'sha256', callback);
|
||||
utils.pbkdf2(passphrase, 'bcoin', 50000, 32, 'sha256', callback);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -423,6 +423,7 @@ utils.derive = function derive(passphrase, callback) {
|
||||
utils.encrypt = function encrypt(data, passphrase, iv, callback) {
|
||||
assert(Buffer.isBuffer(data));
|
||||
assert(passphrase, 'No passphrase.');
|
||||
assert(Buffer.isBuffer(iv));
|
||||
|
||||
utils.derive(passphrase, function(err, key) {
|
||||
if (err)
|
||||
@ -474,6 +475,7 @@ utils.encipher = function encipher(data, key, iv) {
|
||||
utils.decrypt = function decrypt(data, passphrase, iv, callback) {
|
||||
assert(Buffer.isBuffer(data));
|
||||
assert(passphrase, 'No passphrase.');
|
||||
assert(Buffer.isBuffer(iv));
|
||||
|
||||
utils.derive(passphrase, function(err, key) {
|
||||
if (err)
|
||||
|
||||
@ -295,7 +295,7 @@ Wallet.prototype.lock = function lock() {
|
||||
*/
|
||||
|
||||
Wallet.prototype.unlock = function unlock(passphrase, timeout, callback) {
|
||||
this.master.toKey(passphrase, timeout, callback);
|
||||
this.master.unlock(passphrase, timeout, callback);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -340,7 +340,7 @@ Wallet.prototype.createAccount = function createAccount(options, callback, force
|
||||
|
||||
callback = utils.wrap(callback, unlock);
|
||||
|
||||
this.master.toKey(options.passphrase, options.timeout, function(err, master) {
|
||||
this.unlock(options.passphrase, options.timeout, function(err, master) {
|
||||
if (err)
|
||||
return callback(err);
|
||||
|
||||
@ -918,19 +918,26 @@ Wallet.prototype.getRedeem = function getRedeem(hash, callback) {
|
||||
|
||||
/**
|
||||
* Scan for active accounts and addresses. Used for importing a wallet.
|
||||
* @param {Function} getByAddress - Must be a function which accepts
|
||||
* @param {Function} scanner - Must be a function which accepts
|
||||
* a {@link Base58Address} as well as a callback and returns
|
||||
* transactions by address.
|
||||
* @param {Function} callback - Return [Error, Number] (total number
|
||||
* of addresses allocated).
|
||||
*/
|
||||
|
||||
Wallet.prototype.scan = function scan(getByAddress, callback) {
|
||||
Wallet.prototype.scan = function scan(maxGap, scanner, callback) {
|
||||
var self = this;
|
||||
var total = 0;
|
||||
var index = 0;
|
||||
var unlock;
|
||||
|
||||
unlock = this.locker.lock(scan, [getByAddress, callback]);
|
||||
if (typeof maxGap === 'function') {
|
||||
callback = scanner;
|
||||
scanner = maxGap;
|
||||
maxGap = null;
|
||||
}
|
||||
|
||||
unlock = this.locker.lock(scan, [maxGap, scanner, callback]);
|
||||
|
||||
if (!unlock)
|
||||
return;
|
||||
@ -940,22 +947,24 @@ Wallet.prototype.scan = function scan(getByAddress, callback) {
|
||||
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) {
|
||||
(function next() {
|
||||
self.getAccount(index++, function(err, account) {
|
||||
if (err)
|
||||
return callback(err);
|
||||
|
||||
if (result === 0)
|
||||
if (!account)
|
||||
return callback(null, total);
|
||||
|
||||
total += result;
|
||||
account.scan(maxGap, scanner, function(err, result) {
|
||||
if (err)
|
||||
return callback(err);
|
||||
|
||||
self.createAccount(self.options, next, true);
|
||||
});
|
||||
})(null, this.account);
|
||||
total += result;
|
||||
|
||||
next();
|
||||
});
|
||||
}, true);
|
||||
})();
|
||||
};
|
||||
|
||||
/**
|
||||
@ -1012,7 +1021,7 @@ Wallet.prototype.sign = function sign(tx, options, callback) {
|
||||
if (err)
|
||||
return callback(err);
|
||||
|
||||
self.master.toKey(options.passphrase, options.timeout, function(err, master) {
|
||||
self.unlock(options.passphrase, options.timeout, function(err, master) {
|
||||
if (err)
|
||||
return callback(err);
|
||||
|
||||
@ -1545,7 +1554,7 @@ function Account(db, options) {
|
||||
|
||||
this.db = db;
|
||||
this.network = db.network;
|
||||
this.lookahead = Account.LOOKAHEAD;
|
||||
this.lookahead = Account.MAX_LOOKAHEAD;
|
||||
|
||||
this.loaded = false;
|
||||
this.loading = false;
|
||||
@ -1636,7 +1645,14 @@ Account.fromOptions = function fromOptions(db, options) {
|
||||
* @const {Number}
|
||||
*/
|
||||
|
||||
Account.LOOKAHEAD = 5;
|
||||
Account.MAX_LOOKAHEAD = 5;
|
||||
|
||||
/*
|
||||
* Default address gap for scanning.
|
||||
* @const {Number}
|
||||
*/
|
||||
|
||||
Account.MAX_GAP = 20;
|
||||
|
||||
/**
|
||||
* Attempt to intialize the account (generating
|
||||
@ -2040,16 +2056,25 @@ Account.prototype.setDepth = function setDepth(receiveDepth, changeDepth, callba
|
||||
|
||||
/**
|
||||
* Scan for addresses.
|
||||
* @param {Function} getByAddress - Must be a callback which accepts
|
||||
* @param {Function} scanner - Must be a callback which accepts
|
||||
* a callback and returns transactions by address.
|
||||
* @param {Function} callback - Return [Error, Number] (total number
|
||||
* of addresses allocated).
|
||||
*/
|
||||
|
||||
Account.prototype.scan = function scan(getByAddress, callback) {
|
||||
Account.prototype.scan = function scan(maxGap, scanner, callback) {
|
||||
var self = this;
|
||||
var total = 0;
|
||||
|
||||
if (typeof maxGap === 'function') {
|
||||
callback = scanner;
|
||||
scanner = maxGap;
|
||||
maxGap = null;
|
||||
}
|
||||
|
||||
if (maxGap == null)
|
||||
maxGap = Account.MAX_GAP;
|
||||
|
||||
if (!this.initialized)
|
||||
return callback(new Error('Account is not initialized.'));
|
||||
|
||||
@ -2068,37 +2093,63 @@ Account.prototype.scan = function scan(getByAddress, callback) {
|
||||
}
|
||||
|
||||
(function chainCheck(change) {
|
||||
var address = change ? self.changeAddress : self.receiveAddress;
|
||||
var depth = change ? self.changeDepth : self.receiveDepth;
|
||||
var index = 0;
|
||||
var gap = 0;
|
||||
|
||||
(function next(err, address) {
|
||||
if (err)
|
||||
return callback(err);
|
||||
function createAddress(callback) {
|
||||
if (index === depth)
|
||||
return self.createAddress(change, callback);
|
||||
return callback(null, self.deriveAddress(change, index++));
|
||||
}
|
||||
|
||||
getByAddress(address.getAddress(), function(err, txs) {
|
||||
(function next() {
|
||||
createAddress(function(err, address) {
|
||||
if (err)
|
||||
return callback(err);
|
||||
|
||||
addTX(txs, function(err, result) {
|
||||
scanner(address.getAddress(), function(err, txs) {
|
||||
if (err)
|
||||
return callback(err);
|
||||
|
||||
if (result) {
|
||||
total++;
|
||||
gap = 0;
|
||||
return self.createAddress(change, next);
|
||||
}
|
||||
addTX(txs, function(err, result) {
|
||||
if (err)
|
||||
return callback(err);
|
||||
|
||||
if (++gap < 20)
|
||||
return self.createAddress(change, next);
|
||||
// Special case for maxGap=0
|
||||
if (maxGap === 0 && index === depth) {
|
||||
if (!change)
|
||||
return chainCheck(true);
|
||||
return callback(null, total);
|
||||
}
|
||||
|
||||
if (!change)
|
||||
return chainCheck(true);
|
||||
if (result) {
|
||||
total++;
|
||||
gap = 0;
|
||||
return next();
|
||||
}
|
||||
|
||||
return callback(null, total);
|
||||
if (++gap < Account.MAX_GAP)
|
||||
return next();
|
||||
|
||||
if (!change) {
|
||||
self.receiveDepth = Math.max(depth, self.receiveDepth - gap);
|
||||
self.receiveAddress = self.deriveReceive(self.receiveDepth - 1);
|
||||
return chainCheck(true);
|
||||
}
|
||||
|
||||
self.changeDepth = Math.max(depth, self.changeDepth - gap);
|
||||
self.changeAddress = self.deriveChange(self.changeDepth - 1);
|
||||
|
||||
self.save(function(err) {
|
||||
if (err)
|
||||
return callback(err);
|
||||
return callback(null, total);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
})(null, address);
|
||||
})();
|
||||
})(false);
|
||||
};
|
||||
|
||||
@ -2353,11 +2404,11 @@ MasterKey.fromOptions = function fromOptions(options) {
|
||||
* @returns {HDPrivateKey}
|
||||
*/
|
||||
|
||||
MasterKey.prototype.toKey = function toKey(passphrase, timeout, callback) {
|
||||
MasterKey.prototype.unlock = function _unlock(passphrase, timeout, callback) {
|
||||
var self = this;
|
||||
var unlock;
|
||||
|
||||
unlock = this.locker.lock(toKey, [passphrase, timeout, callback]);
|
||||
unlock = this.locker.lock(_unlock, [passphrase, timeout, callback]);
|
||||
|
||||
if (!unlock)
|
||||
return;
|
||||
@ -2538,6 +2589,16 @@ MasterKey.prototype.toRaw = function toRaw(writer) {
|
||||
p.writeVarBytes(this.iv);
|
||||
p.writeVarBytes(this.ciphertext);
|
||||
|
||||
// Future-proofing:
|
||||
// algorithm (0=pbkdf2, 1=scrypt)
|
||||
p.writeU8(0);
|
||||
// iterations (pbkdf2) / N (scrypt)
|
||||
p.writeU32(50000);
|
||||
// r (scrypt)
|
||||
p.writeU32(0);
|
||||
// p (scrypt)
|
||||
p.writeU32(0);
|
||||
|
||||
if (!writer)
|
||||
p = p.render();
|
||||
|
||||
|
||||
@ -1211,6 +1211,16 @@ Path.prototype.inspect = function() {
|
||||
+ '>';
|
||||
};
|
||||
|
||||
Path.prototype.toJSON = function toJSON() {
|
||||
return {
|
||||
id: this.id,
|
||||
name: this.name,
|
||||
account: this.account,
|
||||
change: this.change,
|
||||
index: this.index
|
||||
};
|
||||
};
|
||||
|
||||
Path.prototype.toRaw = function toRaw(writer) {
|
||||
var p = new BufferWriter(writer);
|
||||
|
||||
|
||||
@ -873,6 +873,7 @@ describe('Wallet', function() {
|
||||
assert.ifError(err);
|
||||
w1.master.stop();
|
||||
w1.master.key = null;
|
||||
utils.print(w1.toJSON());
|
||||
|
||||
// Coinbase
|
||||
var t1 = bcoin.mtx()
|
||||
|
||||
Loading…
Reference in New Issue
Block a user