hd keys. address pruning. txpool balance.
This commit is contained in:
parent
b9149a0855
commit
b835fff86c
@ -83,6 +83,14 @@ function Address(options) {
|
||||
|
||||
inherits(Address, EventEmitter);
|
||||
|
||||
Address.prototype.__defineGetter__('balance', function() {
|
||||
return this.getBalance();
|
||||
});
|
||||
|
||||
Address.prototype.getBalance = function getBalance() {
|
||||
return this._wallet.tx.getAddressBalance(this.getAddress());
|
||||
};
|
||||
|
||||
Address.prototype.setRedeem = function setRedeem(redeem) {
|
||||
var old = this.getScriptAddress();
|
||||
|
||||
|
||||
@ -85,7 +85,7 @@ function HDSeed(options) {
|
||||
|
||||
HDSeed.prototype.createSeed = function createSeed(passphrase) {
|
||||
this.passphrase = passphrase || '';
|
||||
return pbkdf2(this.mnemonic, 'mnemonic' + passphrase, 2048, 64);
|
||||
return pbkdf2(this.mnemonic, 'mnemonic' + this.passphrase, 2048, 64);
|
||||
};
|
||||
|
||||
HDSeed._mnemonic = function _mnemonic(entropy) {
|
||||
@ -634,11 +634,16 @@ HDPrivateKey.prototype._build = function _build(data) {
|
||||
};
|
||||
|
||||
HDPrivateKey.prototype.derive = function derive(index, hardened) {
|
||||
var data, hash, leftPart, chainCode, privateKey;
|
||||
var cached, data, hash, leftPart, chainCode, privateKey;
|
||||
|
||||
if (typeof index === 'string')
|
||||
return this.deriveString(index);
|
||||
|
||||
cached = cache.get(this.xprivkey, index);
|
||||
|
||||
if (cached)
|
||||
return cached;
|
||||
|
||||
hardened = index >= constants.hd.hardened ? true : hardened;
|
||||
if (index < constants.hd.hardened && hardened)
|
||||
index += constants.hd.hardened;
|
||||
@ -656,7 +661,7 @@ HDPrivateKey.prototype.derive = function derive(index, hardened) {
|
||||
.mod(ec.curve.n)
|
||||
.toArray('be', 32);
|
||||
|
||||
return new HDPrivateKey({
|
||||
var child = new HDPrivateKey({
|
||||
version: this.version,
|
||||
depth: new bn(this.depth).toNumber() + 1,
|
||||
parentFingerPrint: this.fingerPrint,
|
||||
@ -665,6 +670,10 @@ HDPrivateKey.prototype.derive = function derive(index, hardened) {
|
||||
privateKey: privateKey,
|
||||
checksum: null
|
||||
});
|
||||
|
||||
cache.set(this.xprivkey, index, child);
|
||||
|
||||
return child;
|
||||
};
|
||||
|
||||
// https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki
|
||||
@ -928,11 +937,16 @@ HDPublicKey.prototype._build = function _build(data) {
|
||||
};
|
||||
|
||||
HDPublicKey.prototype.derive = function derive(index, hardened) {
|
||||
var data, hash, leftPart, chainCode, pair, point, publicKey;
|
||||
var cached, data, hash, leftPart, chainCode, pair, point, publicKey;
|
||||
|
||||
if (typeof index === 'string')
|
||||
return this.deriveString(index);
|
||||
|
||||
cached = cache.get(this.xpubkey, index);
|
||||
|
||||
if (cached)
|
||||
return cached;
|
||||
|
||||
if (index >= constants.hd.hardened || hardened)
|
||||
throw new Error('invalid index');
|
||||
|
||||
@ -948,7 +962,7 @@ HDPublicKey.prototype.derive = function derive(index, hardened) {
|
||||
point = ec.curve.g.mul(leftPart).add(pair.pub);
|
||||
publicKey = bcoin.ecdsa.keyPair({ pub: point }).getPublic(true, 'array');
|
||||
|
||||
return new HDPublicKey({
|
||||
var child = new HDPublicKey({
|
||||
version: this.version,
|
||||
depth: new bn(this.depth).toNumber() + 1,
|
||||
parentFingerPrint: this.fingerPrint,
|
||||
@ -957,6 +971,10 @@ HDPublicKey.prototype.derive = function derive(index, hardened) {
|
||||
publicKey: publicKey,
|
||||
checksum: null
|
||||
});
|
||||
|
||||
cache.set(this.xpubkey, index, child);
|
||||
|
||||
return child;
|
||||
};
|
||||
|
||||
HDPublicKey.isValidPath = function isValidPath(arg) {
|
||||
@ -1123,6 +1141,30 @@ function pbkdf2(key, salt, iterations, dkLen) {
|
||||
return DK;
|
||||
}
|
||||
|
||||
var cache = {
|
||||
data: {},
|
||||
count: 0
|
||||
};
|
||||
|
||||
cache.set = function(key, index, value) {
|
||||
key = key + '/' + index;
|
||||
|
||||
if (this.count > 200) {
|
||||
this.data = {};
|
||||
this.count = 0;
|
||||
}
|
||||
|
||||
if (this.data[key] === undefined)
|
||||
this.count++;
|
||||
|
||||
this.data[key] = value;
|
||||
};
|
||||
|
||||
cache.get = function(key, index) {
|
||||
key = key + '/' + index;
|
||||
return this.data[key];
|
||||
};
|
||||
|
||||
/**
|
||||
* Expose
|
||||
*/
|
||||
|
||||
@ -16,6 +16,8 @@ var EventEmitter = require('events').EventEmitter;
|
||||
*/
|
||||
|
||||
function TXPool(wallet) {
|
||||
var self = this;
|
||||
|
||||
if (!(this instanceof TXPool))
|
||||
return new TXPool(wallet);
|
||||
|
||||
@ -29,6 +31,20 @@ function TXPool(wallet) {
|
||||
this._orphans = {};
|
||||
this._lastTs = 0;
|
||||
this._loaded = false;
|
||||
this._addresses = {};
|
||||
this._sent = new bn(0);
|
||||
this._received = new bn(0);
|
||||
this._balance = new bn(0);
|
||||
|
||||
this._wallet.on('remove address', function(address) {
|
||||
address = self._addresses[address.getAddress()];
|
||||
if (address) {
|
||||
self._balance.isub(address.balance);
|
||||
self._sent.isub(address.sent);
|
||||
self._received.isub(address.received);
|
||||
delete self._addresses[address];
|
||||
}
|
||||
});
|
||||
|
||||
// Load TXs from storage
|
||||
this._init();
|
||||
@ -120,6 +136,8 @@ TXPool.prototype.add = function add(tx, noWrite, strict) {
|
||||
if (!tx.verify(index))
|
||||
return;
|
||||
|
||||
this._addInput(tx, index);
|
||||
|
||||
delete this._unspent[key];
|
||||
updated = true;
|
||||
continue;
|
||||
@ -167,6 +185,7 @@ TXPool.prototype.add = function add(tx, noWrite, strict) {
|
||||
this._removeTX(orphan.tx, noWrite);
|
||||
return false;
|
||||
}
|
||||
this._addInput(orphan.tx, index);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -178,6 +197,8 @@ TXPool.prototype.add = function add(tx, noWrite, strict) {
|
||||
if (!this._wallet.ownOutput(tx, i))
|
||||
continue;
|
||||
|
||||
this._addOutput(tx, i);
|
||||
|
||||
key = hash + '/' + i;
|
||||
orphans = this._orphans[key];
|
||||
|
||||
@ -220,9 +241,15 @@ TXPool.prototype._storeTX = function _storeTX(hash, tx, noWrite) {
|
||||
|
||||
TXPool.prototype._removeTX = function _removeTX(tx, noWrite) {
|
||||
var self = this;
|
||||
var key;
|
||||
|
||||
for (var i = 0; i < tx.outputs.length; i++)
|
||||
delete this._unspent[tx.hash('hex') + '/' + i];
|
||||
for (var i = 0; i < tx.outputs.length; i++) {
|
||||
key = tx.hash('hex') + '/' + i;
|
||||
if (this._unspent[key]) {
|
||||
delete this._unspent[key];
|
||||
this._removeOutput(tx, i);
|
||||
}
|
||||
}
|
||||
|
||||
if (!this._storage || noWrite)
|
||||
return;
|
||||
@ -246,21 +273,123 @@ TXPool.prototype.prune = function prune(pruneOrphans) {
|
||||
this._orphans = {};
|
||||
};
|
||||
|
||||
TXPool.prototype.getAll = function getAll() {
|
||||
TXPool.prototype.getAll = function getAll(address) {
|
||||
if (!address)
|
||||
address = this._wallet;
|
||||
|
||||
return Object.keys(this._all).map(function(key) {
|
||||
return this._all[key];
|
||||
}, this).filter(function(tx) {
|
||||
return this._wallet.ownOutput(tx)
|
||||
|| this._wallet.ownInput(tx);
|
||||
}, this);
|
||||
return address.ownOutput(tx)
|
||||
|| address.ownInput(tx);
|
||||
});
|
||||
};
|
||||
|
||||
TXPool.prototype.getUnspent = function getUnspent() {
|
||||
TXPool.prototype._addOutput = function _addOutput(tx, i, remove) {
|
||||
var i, data, address, addr, output;
|
||||
|
||||
output = tx.outputs[i];
|
||||
data = bcoin.script.getOutputData(output.script);
|
||||
|
||||
if (!this._wallet.ownOutput(tx, i))
|
||||
return;
|
||||
|
||||
if (data.scriptAddress)
|
||||
data.addresses = [data.scriptAddress];
|
||||
|
||||
for (i = 0; i < data.addresses.length; i++) {
|
||||
addr = data.addresses[i];
|
||||
if (!this._addresses[addr]) {
|
||||
this._addresses[addr] = {
|
||||
received: new bn(0),
|
||||
sent: new bn(0),
|
||||
balance: new bn(0)
|
||||
};
|
||||
}
|
||||
if (!remove) {
|
||||
this._addresses[addr].balance.iadd(output.value);
|
||||
this._addresses[addr].received.iadd(output.value);
|
||||
} else {
|
||||
this._addresses[addr].balance.isub(output.value);
|
||||
this._addresses[addr].received.isub(output.value);
|
||||
}
|
||||
}
|
||||
|
||||
if (!remove) {
|
||||
this._balance.iadd(output.value);
|
||||
this._received.iadd(output.value);
|
||||
} else {
|
||||
this._balance.isub(output.value);
|
||||
this._received.isub(output.value);
|
||||
}
|
||||
};
|
||||
|
||||
TXPool.prototype._removeOutput = function _removeOutput(tx, i) {
|
||||
return this._addOutput(tx, i, true);
|
||||
};
|
||||
|
||||
TXPool.prototype._addInput = function _addInput(tx, i, remove) {
|
||||
var i, input, prev, data, address, addr, output;
|
||||
|
||||
input = tx.inputs[i];
|
||||
assert(input.prevout.tx);
|
||||
|
||||
if (!this._wallet.ownOutput(input.prevout.tx, input.prevout.index))
|
||||
return;
|
||||
|
||||
prev = input.prevout.tx.outputs[input.prevout.index];
|
||||
data = bcoin.script.getInputData(input.script, prev.script);
|
||||
|
||||
if (data.scriptAddress)
|
||||
data.addresses = [data.scriptAddress];
|
||||
|
||||
for (i = 0; i < data.addresses.length; i++) {
|
||||
addr = data.addresses[i];
|
||||
if (!this._addresses[addr]) {
|
||||
this._addresses[addr] = {
|
||||
received: new bn(0),
|
||||
sent: new bn(0),
|
||||
balance: new bn(0)
|
||||
};
|
||||
}
|
||||
if (!remove) {
|
||||
this._addresses[addr].balance.isub(prev.value);
|
||||
this._addresses[addr].sent.iadd(prev.value);
|
||||
} else {
|
||||
this._addresses[addr].balance.iadd(prev.value);
|
||||
this._addresses[addr].sent.isub(prev.value);
|
||||
}
|
||||
}
|
||||
|
||||
if (!remove) {
|
||||
this._balance.isub(prev.value);
|
||||
this._sent.iadd(prev.value);
|
||||
} else {
|
||||
this._balance.iadd(prev.value);
|
||||
this._sent.isub(prev.value);
|
||||
}
|
||||
};
|
||||
|
||||
TXPool.prototype._removeInput = function _removeInput(tx, i) {
|
||||
return this._addInput(tx, i, true);
|
||||
};
|
||||
|
||||
TXPool.prototype.getAddressBalance = function getAddressBalance(address) {
|
||||
if (this._addresses[address])
|
||||
return this._addresses[address].balance.clone();
|
||||
|
||||
return new bn(0);
|
||||
};
|
||||
|
||||
TXPool.prototype.getUnspent = function getUnspent(address) {
|
||||
if (!address)
|
||||
address = this._wallet;
|
||||
|
||||
return Object.keys(this._unspent).map(function(key) {
|
||||
return this._unspent[key];
|
||||
}, this).filter(function(item) {
|
||||
return this._wallet.ownOutput(item.tx, item.index);
|
||||
}, this);
|
||||
return address.ownOutput(item.tx, item.index);
|
||||
});
|
||||
};
|
||||
|
||||
TXPool.prototype.getPending = function getPending() {
|
||||
@ -271,9 +400,9 @@ TXPool.prototype.getPending = function getPending() {
|
||||
});
|
||||
};
|
||||
|
||||
TXPool.prototype.getBalance = function getBalance() {
|
||||
TXPool.prototype.getBalance = function getBalance(address) {
|
||||
var acc = new bn(0);
|
||||
var unspent = this.getUnspent();
|
||||
var unspent = this.getUnspent(address);
|
||||
if (unspent.length === 0)
|
||||
return acc;
|
||||
|
||||
@ -282,6 +411,12 @@ TXPool.prototype.getBalance = function getBalance() {
|
||||
}, acc);
|
||||
};
|
||||
|
||||
TXPool.prototype.getBalance = function getBalance(address) {
|
||||
if (address)
|
||||
return this.getAddressBalance(address);
|
||||
return this._balance.clone();
|
||||
};
|
||||
|
||||
// Legacy
|
||||
TXPool.prototype.all = TXPool.prototype.getAll;
|
||||
TXPool.prototype.unspent = TXPool.prototype.getUnspent;
|
||||
@ -321,7 +456,7 @@ TXPool.fromJSON = function fromJSON(wallet, json) {
|
||||
});
|
||||
});
|
||||
|
||||
return tx;
|
||||
return txPool;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@ -31,10 +31,12 @@ function Wallet(options) {
|
||||
|
||||
options = utils.merge({}, options);
|
||||
|
||||
if (options.hd) {
|
||||
options.master = options.hd !== true
|
||||
? bcoin.hd.privateKey(options.hd)
|
||||
: bcoin.hd.privateKey();
|
||||
options.hd = options.hd !== false;
|
||||
|
||||
if (options.hd && !options.master) {
|
||||
options.master = options.hd === true
|
||||
? bcoin.hd.privateKey()
|
||||
: bcoin.hd.privateKey(options.hd);
|
||||
delete options.hd;
|
||||
}
|
||||
|
||||
@ -47,24 +49,16 @@ function Wallet(options) {
|
||||
if (options.pub)
|
||||
options.publicKey = options.pub;
|
||||
|
||||
if (options.privateKey
|
||||
|| options.publicKey
|
||||
|| options.pair
|
||||
|| options.personalization
|
||||
|| options.entropy
|
||||
|| options.passphrase
|
||||
|| options.compressed) {
|
||||
if ((options.pair instanceof bcoin.hd.privateKey)
|
||||
|| options.pair instanceof bcoin.hd.publicKey) {
|
||||
options.master = options.pair;
|
||||
delete options.pair;
|
||||
} else if (options.privateKey instanceof bcoin.hd.privateKey) {
|
||||
options.master = options.privateKey;
|
||||
delete options.privateKey;
|
||||
} else if (options.publicKey instanceof bcoin.hd.publicKey) {
|
||||
options.master = options.publicKey;
|
||||
delete options.publicKey;
|
||||
}
|
||||
if ((options.pair instanceof bcoin.hd.privateKey)
|
||||
|| (options.pair instanceof bcoin.hd.publicKey)) {
|
||||
options.master = options.pair;
|
||||
delete options.pair;
|
||||
} else if (options.privateKey instanceof bcoin.hd.privateKey) {
|
||||
options.master = options.privateKey;
|
||||
delete options.privateKey;
|
||||
} else if (options.publicKey instanceof bcoin.hd.publicKey) {
|
||||
options.master = options.publicKey;
|
||||
delete options.publicKey;
|
||||
}
|
||||
|
||||
this.options = options;
|
||||
@ -137,20 +131,11 @@ function Wallet(options) {
|
||||
if (!options.addresses)
|
||||
options.addresses = [];
|
||||
|
||||
if (options.privateKey
|
||||
|| options.publicKey
|
||||
|| options.pair
|
||||
|| options.personalization
|
||||
|| options.entropy
|
||||
|| options.passphrase
|
||||
|| options.compressed) {
|
||||
if (this._isKeyOptions(options)) {
|
||||
options.addresses.unshift({
|
||||
privateKey: options.privateKey,
|
||||
publicKey: options.publicKey,
|
||||
pair: options.pair,
|
||||
personalization: options.personalization,
|
||||
entropy: options.entropy,
|
||||
compressed: options.compressed,
|
||||
type: this.type,
|
||||
subtype: this.subtype,
|
||||
m: this.m,
|
||||
@ -189,11 +174,8 @@ function Wallet(options) {
|
||||
} else if (this.normal) {
|
||||
// Try to find the last receiving address if there is one.
|
||||
receiving = options.addresses.filter(function(address) {
|
||||
return !address.change
|
||||
&& ((address.priv || address.privateKey)
|
||||
|| (address.pub || address.publicKey)
|
||||
|| (address.key || address.pair));
|
||||
}).pop();
|
||||
return !address.change && this._isKeyOptions(address);
|
||||
}, this).pop();
|
||||
if (receiving) {
|
||||
this.current = bcoin.address(receiving);
|
||||
} else {
|
||||
@ -224,11 +206,38 @@ function Wallet(options) {
|
||||
|
||||
inherits(Wallet, EventEmitter);
|
||||
|
||||
Wallet.prototype._pruneAddresses = function _pruneAddresses(options) {
|
||||
var addresses = this.addresses.slice();
|
||||
var address;
|
||||
|
||||
for (i = 0; i < addresses.length; i++) {
|
||||
address = addresses[i];
|
||||
|
||||
if (address === this.current || address === this.changeAddress)
|
||||
continue;
|
||||
|
||||
if (!address.change)
|
||||
continue;
|
||||
|
||||
if (!address.derived)
|
||||
continue;
|
||||
|
||||
if (address.getBalance().cmpn(0) === 0)
|
||||
this.removeAddress(address);
|
||||
}
|
||||
};
|
||||
|
||||
Wallet.prototype._isKeyOptions = function _isKeyOptions(options) {
|
||||
return (options.priv || options.privateKey)
|
||||
|| (options.pub || options.publicKey)
|
||||
|| (options.key || options.pair);
|
||||
};
|
||||
|
||||
// Wallet ID:
|
||||
// bip45: Purpose key address
|
||||
// bip44: Account key address
|
||||
// normal: Address of first key in wallet
|
||||
Wallet.prototype.getID = function() {
|
||||
Wallet.prototype.getID = function getID() {
|
||||
if (this.bip45)
|
||||
return bcoin.address.key2addr(this.purposeKey.publicKey);
|
||||
|
||||
@ -244,7 +253,7 @@ Wallet.prototype.getID = function() {
|
||||
assert(false);
|
||||
};
|
||||
|
||||
Wallet.prototype._initAddresses = function() {
|
||||
Wallet.prototype._initAddresses = function _initAddresses() {
|
||||
var options = this.options;
|
||||
var i;
|
||||
|
||||
@ -438,6 +447,7 @@ Wallet.prototype._init = function init() {
|
||||
|
||||
if (this.tx._loaded) {
|
||||
this.loading = false;
|
||||
this._pruneAddresses();
|
||||
return;
|
||||
}
|
||||
|
||||
@ -458,6 +468,7 @@ Wallet.prototype._init = function init() {
|
||||
self.current = self.createAddress();
|
||||
if (self.changeAddress.ownOutput(tx))
|
||||
self.changeAddress = self.createAddress(true);
|
||||
self._pruneAddresses();
|
||||
}
|
||||
self.emit('tx', tx);
|
||||
});
|
||||
@ -465,6 +476,7 @@ Wallet.prototype._init = function init() {
|
||||
this.tx.once('load', function(ts) {
|
||||
self.loading = false;
|
||||
self.lastTs = ts;
|
||||
self._pruneAddresses();
|
||||
self.emit('load', ts);
|
||||
});
|
||||
|
||||
|
||||
@ -85,6 +85,11 @@ describe('HD', function() {
|
||||
master.hdpub._unbuild(master.hdpub.xpubkey);
|
||||
});
|
||||
|
||||
it('should deserialize and reserialize', function() {
|
||||
var key = bcoin.hd.priv();
|
||||
assert.equal(bcoin.hd.fromJSON(key.toJSON()).xprivkey, key.xprivkey);
|
||||
});
|
||||
|
||||
it('should create an hd seed', function() {
|
||||
var seed = new bcoin.hd.seed({
|
||||
// I have the same combination on my luggage:
|
||||
|
||||
@ -115,25 +115,31 @@ describe('Wallet', function() {
|
||||
|
||||
// Coinbase
|
||||
var t1 = bcoin.tx().out(w, 50000).out(w, 1000);
|
||||
// balance: 51000
|
||||
w.sign(t1);
|
||||
var t2 = bcoin.tx().input(t1, 0)
|
||||
var t2 = bcoin.tx().input(t1, 0) // 50000
|
||||
.out(w, 24000)
|
||||
.out(w, 24000);
|
||||
// balance: 49000
|
||||
w.sign(t2);
|
||||
var t3 = bcoin.tx().input(t1, 1)
|
||||
.input(t2, 0)
|
||||
var t3 = bcoin.tx().input(t1, 1) // 1000
|
||||
.input(t2, 0) // 24000
|
||||
.out(w, 23000);
|
||||
// balance: 47000
|
||||
w.sign(t3);
|
||||
var t4 = bcoin.tx().input(t2, 1)
|
||||
.input(t3, 0)
|
||||
var t4 = bcoin.tx().input(t2, 1) // 24000
|
||||
.input(t3, 0) // 23000
|
||||
.out(w, 11000)
|
||||
.out(w, 11000);
|
||||
// balance: 22000
|
||||
w.sign(t4);
|
||||
var f1 = bcoin.tx().input(t4, 1)
|
||||
var f1 = bcoin.tx().input(t4, 1) // 11000
|
||||
.out(f, 10000);
|
||||
// balance: 11000
|
||||
w.sign(f1);
|
||||
var fake = bcoin.tx().input(t1, 1)
|
||||
var fake = bcoin.tx().input(t1, 1) // 1000 (already redeemed)
|
||||
.out(w, 500);
|
||||
// balance: 11000
|
||||
|
||||
// Just for debugging
|
||||
t1.hint = 't1';
|
||||
|
||||
Loading…
Reference in New Issue
Block a user