424 lines
8.9 KiB
JavaScript
424 lines
8.9 KiB
JavaScript
/*!
|
|
* memwallet.js - in-memory wallet object for bcoin
|
|
* Copyright (c) 2014-2016, Christopher Jeffrey (MIT License).
|
|
* https://github.com/bcoin-org/bcoin
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
const assert = require('assert');
|
|
const Network = require('../../lib/protocol/network');
|
|
const MTX = require('../../lib/primitives/mtx');
|
|
const HD = require('../../lib/hd/hd');
|
|
const Bloom = require('../../lib/utils/bloom');
|
|
const KeyRing = require('../../lib/primitives/keyring');
|
|
const Outpoint = require('../../lib/primitives/outpoint');
|
|
const Coin = require('../../lib/primitives/coin');
|
|
|
|
function MemWallet(options) {
|
|
if (!(this instanceof MemWallet))
|
|
return new MemWallet(options);
|
|
|
|
this.network = Network.primary;
|
|
this.master = null;
|
|
this.key = null;
|
|
this.witness = false;
|
|
this.account = 0;
|
|
this.receiveDepth = 1;
|
|
this.changeDepth = 1;
|
|
this.receive = null;
|
|
this.change = null;
|
|
this.map = new Set();
|
|
this.coins = new Map();
|
|
this.spent = new Map();
|
|
this.paths = new Map();
|
|
this.balance = 0;
|
|
this.txs = 0;
|
|
this.filter = Bloom.fromRate(1000000, 0.001, -1);
|
|
|
|
if (options)
|
|
this.fromOptions(options);
|
|
|
|
this.init();
|
|
}
|
|
|
|
MemWallet.prototype.fromOptions = function fromOptions(options) {
|
|
if (options.network != null) {
|
|
assert(options.network);
|
|
this.network = Network.get(options.network);
|
|
}
|
|
|
|
if (options.master != null) {
|
|
assert(options.master);
|
|
this.master = HD.PrivateKey.fromOptions(options.master, this.network);
|
|
}
|
|
|
|
if (options.key != null) {
|
|
assert(HD.isPrivate(options.key));
|
|
this.key = options.key;
|
|
}
|
|
|
|
if (options.witness != null) {
|
|
assert(typeof options.witness === 'boolean');
|
|
this.witness = options.witness;
|
|
}
|
|
|
|
if (options.account != null) {
|
|
assert(typeof options.account === 'number');
|
|
this.account = options.account;
|
|
}
|
|
|
|
if (options.receiveDepth != null) {
|
|
assert(typeof options.receiveDepth === 'number');
|
|
this.receiveDepth = options.receiveDepth;
|
|
}
|
|
|
|
if (options.changeDepth != null) {
|
|
assert(typeof options.changeDepth === 'number');
|
|
this.changeDepth = options.changeDepth;
|
|
}
|
|
|
|
return this;
|
|
};
|
|
|
|
MemWallet.prototype.init = function init() {
|
|
let i;
|
|
|
|
if (!this.master)
|
|
this.master = HD.PrivateKey.generate();
|
|
|
|
if (!this.key)
|
|
this.key = this.master.deriveAccount(44, this.account);
|
|
|
|
i = this.receiveDepth;
|
|
while (i--)
|
|
this.createReceive();
|
|
|
|
i = this.changeDepth;
|
|
while (i--)
|
|
this.createChange();
|
|
};
|
|
|
|
MemWallet.prototype.createReceive = function createReceive() {
|
|
const index = this.receiveDepth++;
|
|
const key = this.deriveReceive(index);
|
|
const hash = key.getHash('hex');
|
|
this.filter.add(hash, 'hex');
|
|
this.paths.set(hash, new Path(hash, 0, index));
|
|
this.receive = key;
|
|
return key;
|
|
};
|
|
|
|
MemWallet.prototype.createChange = function createChange() {
|
|
const index = this.changeDepth++;
|
|
const key = this.deriveChange(index);
|
|
const hash = key.getHash('hex');
|
|
this.filter.add(hash, 'hex');
|
|
this.paths.set(hash, new Path(hash, 1, index));
|
|
this.change = key;
|
|
return key;
|
|
};
|
|
|
|
MemWallet.prototype.deriveReceive = function deriveReceive(index) {
|
|
return this.deriveKey(0, index);
|
|
};
|
|
|
|
MemWallet.prototype.deriveChange = function deriveChange(index) {
|
|
return this.deriveKey(1, index);
|
|
};
|
|
|
|
MemWallet.prototype.derivePath = function derivePath(path) {
|
|
return this.deriveKey(path.branch, path.index);
|
|
};
|
|
|
|
MemWallet.prototype.deriveKey = function deriveKey(branch, index) {
|
|
let key = this.master.deriveAccount(44, this.account);
|
|
key = key.derive(branch).derive(index);
|
|
const ring = new KeyRing({
|
|
network: this.network,
|
|
privateKey: key.privateKey,
|
|
witness: this.witness
|
|
});
|
|
ring.witness = this.witness;
|
|
return ring;
|
|
};
|
|
|
|
MemWallet.prototype.getKey = function getKey(hash) {
|
|
const path = this.paths.get(hash);
|
|
|
|
if (!path)
|
|
return null;
|
|
|
|
return this.derivePath(path);
|
|
};
|
|
|
|
MemWallet.prototype.getPath = function getPath(hash) {
|
|
return this.paths.get(hash);
|
|
};
|
|
|
|
MemWallet.prototype.getCoin = function getCoin(key) {
|
|
return this.coins.get(key);
|
|
};
|
|
|
|
MemWallet.prototype.getUndo = function getUndo(key) {
|
|
return this.spent.get(key);
|
|
};
|
|
|
|
MemWallet.prototype.addCoin = function addCoin(coin) {
|
|
const op = Outpoint(coin.hash, coin.index);
|
|
const key = op.toKey();
|
|
|
|
this.filter.add(op.toRaw());
|
|
|
|
this.spent.delete(key);
|
|
|
|
this.coins.set(key, coin);
|
|
this.balance += coin.value;
|
|
};
|
|
|
|
MemWallet.prototype.removeCoin = function removeCoin(key) {
|
|
const coin = this.coins.get(key);
|
|
|
|
if (!coin)
|
|
return;
|
|
|
|
this.spent.set(key, coin);
|
|
this.balance -= coin.value;
|
|
|
|
this.coins.delete(key);
|
|
};
|
|
|
|
MemWallet.prototype.getAddress = function getAddress() {
|
|
return this.receive.getAddress();
|
|
};
|
|
|
|
MemWallet.prototype.getReceive = function getReceive() {
|
|
return this.receive.getAddress();
|
|
};
|
|
|
|
MemWallet.prototype.getChange = function getChange() {
|
|
return this.change.getAddress();
|
|
};
|
|
|
|
MemWallet.prototype.getCoins = function getCoins() {
|
|
const coins = [];
|
|
|
|
for (const coin of this.coins.values())
|
|
coins.push(coin);
|
|
|
|
return coins;
|
|
};
|
|
|
|
MemWallet.prototype.syncKey = function syncKey(path) {
|
|
switch (path.branch) {
|
|
case 0:
|
|
if (path.index === this.receiveDepth - 1)
|
|
this.createReceive();
|
|
break;
|
|
case 1:
|
|
if (path.index === this.changeDepth - 1)
|
|
this.createChange();
|
|
break;
|
|
default:
|
|
assert(false);
|
|
break;
|
|
}
|
|
};
|
|
|
|
MemWallet.prototype.addBlock = function addBlock(entry, txs) {
|
|
for (let i = 0; i < txs.length; i++) {
|
|
const tx = txs[i];
|
|
this.addTX(tx, entry.height);
|
|
}
|
|
};
|
|
|
|
MemWallet.prototype.removeBlock = function removeBlock(entry, txs) {
|
|
for (let i = txs.length - 1; i >= 0; i--) {
|
|
const tx = txs[i];
|
|
this.removeTX(tx, entry.height);
|
|
}
|
|
};
|
|
|
|
MemWallet.prototype.addTX = function addTX(tx, height) {
|
|
const hash = tx.hash('hex');
|
|
let result = false;
|
|
|
|
if (height == null)
|
|
height = -1;
|
|
|
|
if (this.map.has(hash))
|
|
return true;
|
|
|
|
for (let i = 0; i < tx.inputs.length; i++) {
|
|
const input = tx.inputs[i];
|
|
const op = input.prevout.toKey();
|
|
const coin = this.getCoin(op);
|
|
|
|
if (!coin)
|
|
continue;
|
|
|
|
result = true;
|
|
|
|
this.removeCoin(op);
|
|
}
|
|
|
|
for (let i = 0; i < tx.outputs.length; i++) {
|
|
const output = tx.outputs[i];
|
|
const addr = output.getHash('hex');
|
|
|
|
if (!addr)
|
|
continue;
|
|
|
|
const path = this.getPath(addr);
|
|
|
|
if (!path)
|
|
continue;
|
|
|
|
result = true;
|
|
|
|
const coin = Coin.fromTX(tx, i, height);
|
|
|
|
this.addCoin(coin);
|
|
this.syncKey(path);
|
|
}
|
|
|
|
if (result) {
|
|
this.txs++;
|
|
this.map.add(hash);
|
|
}
|
|
|
|
return result;
|
|
};
|
|
|
|
MemWallet.prototype.removeTX = function removeTX(tx, height) {
|
|
const hash = tx.hash('hex');
|
|
let result = false;
|
|
|
|
if (!this.map.has(hash))
|
|
return false;
|
|
|
|
for (let i = 0; i < tx.outputs.length; i++) {
|
|
const op = Outpoint(hash, i).toKey();
|
|
const coin = this.getCoin(op);
|
|
|
|
if (!coin)
|
|
continue;
|
|
|
|
result = true;
|
|
|
|
this.removeCoin(op);
|
|
}
|
|
|
|
for (let i = 0; i < tx.inputs.length; i++) {
|
|
const input = tx.inputs[i];
|
|
const op = input.prevout.toKey();
|
|
const coin = this.getUndo(op);
|
|
|
|
if (!coin)
|
|
continue;
|
|
|
|
result = true;
|
|
|
|
this.addCoin(coin);
|
|
}
|
|
|
|
if (result)
|
|
this.txs--;
|
|
|
|
this.map.delete(hash);
|
|
|
|
return result;
|
|
};
|
|
|
|
MemWallet.prototype.deriveInputs = function deriveInputs(mtx) {
|
|
const keys = [];
|
|
|
|
for (let i = 0; i < mtx.inputs.length; i++) {
|
|
const input = mtx.inputs[i];
|
|
const coin = mtx.view.getOutputFor(input);
|
|
|
|
if (!coin)
|
|
continue;
|
|
|
|
const addr = coin.getHash('hex');
|
|
|
|
if (!addr)
|
|
continue;
|
|
|
|
const path = this.getPath(addr);
|
|
|
|
if (!path)
|
|
continue;
|
|
|
|
const key = this.derivePath(path);
|
|
|
|
keys.push(key);
|
|
}
|
|
|
|
return keys;
|
|
};
|
|
|
|
MemWallet.prototype.fund = function fund(mtx, options) {
|
|
const coins = this.getCoins();
|
|
|
|
if (!options)
|
|
options = {};
|
|
|
|
return mtx.fund(coins, {
|
|
selection: options.selection || 'age',
|
|
round: options.round,
|
|
depth: options.depth,
|
|
hardFee: options.hardFee,
|
|
subtractFee: options.subtractFee,
|
|
changeAddress: this.getChange(),
|
|
height: -1,
|
|
rate: options.rate,
|
|
maxFee: options.maxFee
|
|
});
|
|
};
|
|
|
|
MemWallet.prototype.template = function template(mtx) {
|
|
const keys = this.deriveInputs(mtx);
|
|
mtx.template(keys);
|
|
};
|
|
|
|
MemWallet.prototype.sign = function sign(mtx) {
|
|
const keys = this.deriveInputs(mtx);
|
|
mtx.template(keys);
|
|
mtx.sign(keys);
|
|
};
|
|
|
|
MemWallet.prototype.create = async function create(options) {
|
|
const mtx = new MTX(options);
|
|
|
|
await this.fund(mtx, options);
|
|
|
|
assert(mtx.getFee() <= MTX.Selector.MAX_FEE, 'TX exceeds MAX_FEE.');
|
|
|
|
mtx.sortMembers();
|
|
|
|
if (options.locktime != null)
|
|
mtx.setLocktime(options.locktime);
|
|
|
|
this.sign(mtx);
|
|
|
|
if (!mtx.isSigned())
|
|
throw new Error('Cannot sign tx.');
|
|
|
|
return mtx;
|
|
};
|
|
|
|
MemWallet.prototype.send = async function send(options) {
|
|
const mtx = await this.create(options);
|
|
this.addTX(mtx.toTX());
|
|
return mtx;
|
|
};
|
|
|
|
function Path(hash, branch, index) {
|
|
this.hash = hash;
|
|
this.branch = branch;
|
|
this.index = index;
|
|
}
|
|
|
|
module.exports = MemWallet;
|