wallet refactor. fixes.

This commit is contained in:
Christopher Jeffrey 2016-02-10 16:42:02 -08:00
parent 9fc6848dce
commit 6cc4c866dc
14 changed files with 409 additions and 485 deletions

View File

@ -41,32 +41,23 @@ function Address(options) {
this.path = options.path; this.path = options.path;
this.type = options.type || 'pubkeyhash'; this.type = options.type || 'pubkeyhash';
this.subtype = options.subtype;
this.keys = []; this.keys = [];
this.m = options.m || 1; this.m = options.m || 1;
this.n = options.n || 1; this.n = options.n || 1;
this.redeem = null; this.redeem = null;
if (this.n > 1) { if (this.n > 1)
if (this.type !== 'multisig') this.type = 'multisig';
this.type = 'scripthash';
if (this.type === 'scripthash')
this.subtype = 'multisig';
}
if (network.prefixes[this.type] == null) assert(this.type === 'pubkeyhash' || this.type === 'multisig');
throw new Error('Unknown prefix: ' + this.type); this.prefixType = this.type === 'multisig' ? 'scripthash' : 'pubkeyhash';
this.nmax = this.type === 'scripthash' if (network.prefixes[this.prefixType] == null)
? (this.key.compressed ? 15 : 7) throw new Error('Unknown prefix: ' + this.prefixType);
: 3;
if (this.m < 1 || this.m > this.n) if (this.m < 1 || this.m > this.n)
throw new Error('m ranges between 1 and n'); throw new Error('m ranges between 1 and n');
if (this.n < 1 || this.n > this.nmax)
throw new Error('n ranges between 1 and ' + this.nmax);
this.addKey(this.getPublicKey()); this.addKey(this.getPublicKey());
(options.keys || []).forEach(function(key) { (options.keys || []).forEach(function(key) {
@ -76,7 +67,7 @@ function Address(options) {
if (options.redeem || options.script) if (options.redeem || options.script)
this.setRedeem(options.redeem || options.script); this.setRedeem(options.redeem || options.script);
this.prefix = 'bt/address/' + this.getKeyAddress() + '/'; this.prefix = 'bt/address/' + this.getID() + '/';
} }
inherits(Address, EventEmitter); inherits(Address, EventEmitter);
@ -85,6 +76,10 @@ Address.prototype.__defineGetter__('balance', function() {
return this.getBalance(); return this.getBalance();
}); });
Address.prototype.getID = function getID() {
return this.getKeyAddress();
};
Address.prototype.getAll = function getAll() { Address.prototype.getAll = function getAll() {
return this._wallet.getAll(this); return this._wallet.getAll(this);
}; };
@ -115,8 +110,8 @@ Address.prototype.setRedeem = function setRedeem(redeem) {
if (!utils.isBytes(redeem)) if (!utils.isBytes(redeem))
redeem = bcoin.script.encode(redeem); redeem = bcoin.script.encode(redeem);
this.type = 'scripthash'; this.type = 'multisig';
this.subtype = null; this.prefixType = 'scripthash';
this.redeem = redeem; this.redeem = redeem;
this.emit('update script', old, this.getScriptAddress()); this.emit('update script', old, this.getScriptAddress());
}; };
@ -192,49 +187,53 @@ Address.fromSecret = function fromSecret(privateKey) {
}; };
Address.prototype.getScript = function getScript() { Address.prototype.getScript = function getScript() {
if (this.type !== 'scripthash') var redeem;
if (this.prefixType !== 'scripthash')
return; return;
if (this._script) if (this._script)
return this._script; return this._script;
if (this.redeem) if (this.redeem) {
return this._script = this.redeem.slice(); redeem = this.redeem.slice();
assert(utils.isBytes(redeem));
} else if (this.keys.length < this.n) {
redeem = bcoin.script.createPubkeyhash(this.getKeyHash());
redeem = bcoin.script.encode(redeem);
} else {
redeem = bcoin.script.createMultisig(this.keys, this.m, this.n);
redeem = bcoin.script.encode(redeem);
}
if (this.subtype === 'pubkey') if (redeem.length > 520)
this._script = bcoin.script.createPubkey(this.getPublicKey()); throw new Error('Redeem script too large (520 byte limit).');
else if (this.subtype === 'pubkeyhash' || this.keys.length < this.n)
this._script = bcoin.script.createPubkeyhash(this.getKeyHash());
else if (this.subtype === 'multisig')
this._script = bcoin.script.createMultisig(this.keys, this.m, this.n);
else
assert(false);
this._script = bcoin.script.encode(this._script); this._script = redeem;
return this._script; return this._script;
}; };
Address.prototype.getScriptHash = function getScriptHash() { Address.prototype.getScriptHash = function getScriptHash() {
if (this.type !== 'scripthash') if (this.prefixType !== 'scripthash')
return; return;
if (this._scriptHash) if (this._scriptHash)
return this._scriptHash; return this._scriptHash;
this._scriptHash = utils.ripesha(this.getScript()); this._scriptHash = Address.hash160(this.getScript());
return this._scriptHash; return this._scriptHash;
}; };
Address.prototype.getScriptAddress = function getScriptAddress() { Address.prototype.getScriptAddress = function getScriptAddress() {
if (this.type !== 'scripthash') if (this.prefixType !== 'scripthash')
return; return;
if (this._scriptAddress) if (this._scriptAddress)
return this._scriptAddress; return this._scriptAddress;
this._scriptAddress = Address.hash2addr(this.getScriptHash(), this.type); this._scriptAddress = Address.toAddress(this.getScriptHash(), this.prefixType);
return this._scriptAddress; return this._scriptAddress;
}; };
@ -256,7 +255,7 @@ Address.prototype.getKeyHash = function getKeyHash() {
if (this._hash) if (this._hash)
return this._hash; return this._hash;
this._hash = Address.key2hash(this.getPublicKey()); this._hash = Address.hash160(this.getPublicKey());
return this._hash; return this._hash;
}; };
@ -265,127 +264,41 @@ Address.prototype.getKeyAddress = function getKeyAddress() {
if (this._address) if (this._address)
return this._address; return this._address;
this._address = Address.hash2addr(this.getKeyHash(), 'pubkeyhash'); this._address = Address.toAddress(this.getKeyHash(), 'pubkeyhash');
return this._address; return this._address;
}; };
Address.prototype.getHash = function getHash() { Address.prototype.getHash = function getHash() {
if (this.type === 'scripthash') if (this.prefixType === 'scripthash')
return this.getScriptHash(); return this.getScriptHash();
return this.getKeyHash(); return this.getKeyHash();
}; };
Address.prototype.getAddress = function getAddress() { Address.prototype.getAddress = function getAddress() {
if (this.type === 'scripthash') if (this.prefixType === 'scripthash')
return this.getScriptAddress(); return this.getScriptAddress();
return this.getKeyAddress(); return this.getKeyAddress();
}; };
Address.key2hash = function key2hash(key) { Address.prototype._getAddressTable = function _getAddressTable() {
key = utils.toBuffer(key); var addressTable = {};
return utils.ripesha(key); addressTable[this.getAddress()] = true;
return addressTable;
}; };
Address.hash2addr = function hash2addr(hash, prefix) {
var addr;
hash = utils.toArray(hash, 'hex');
prefix = network.prefixes[prefix || 'pubkeyhash'];
hash = [ prefix ].concat(hash);
addr = hash.concat(utils.checksum(hash));
return utils.toBase58(addr);
};
Address.key2addr = function key2addr(key, prefix) {
return Address.hash2addr(Address.key2hash(key), prefix);
};
Address.__defineGetter__('prefixes', function() {
if (Address._prefixes)
return Address._prefixes;
Address._prefixes = ['pubkeyhash', 'scripthash'].reduce(function(out, prefix) {
var ch = Address.hash2addr(Address.key2hash([]), prefix)[0];
out[ch] = prefix;
return out;
}, {});
return Address._prefixes;
});
Address.addr2hash = function addr2hash(addr, prefix) {
var chk;
if (prefix == null && typeof addr === 'string')
prefix = Address.prefixes[addr[0]];
if (!utils.isBuffer(addr))
addr = utils.fromBase58(addr);
prefix = network.prefixes[prefix || 'pubkeyhash'];
if (addr.length !== 25)
return [];
if (addr[0] !== prefix)
return [];
chk = utils.checksum(addr.slice(0, -4));
if (utils.readU32(chk, 0) !== utils.readU32(addr, 21))
return [];
return addr.slice(1, -4);
};
Address.validate = function validate(addr, prefix) {
if (!addr || typeof addr !== 'string')
return false;
var p = Address.addr2hash(addr, prefix);
return p.length !== 0;
};
Address.validateAddress = Address.validate;
Address.prototype.ownOutput = function ownOutput(tx, index) { Address.prototype.ownOutput = function ownOutput(tx, index) {
var scriptHash = this.getScriptHash(); var addressTable = this._getAddressTable();
var hash = this.getKeyHash();
var key = this.getPublicKey();
var keys = this.keys;
var outputs = tx.outputs; var outputs = tx.outputs;
if ((tx instanceof bcoin.output) || (tx instanceof bcoin.coin)) if ((tx instanceof bcoin.output) || (tx instanceof bcoin.coin))
outputs = [tx]; outputs = [tx];
outputs = outputs.filter(function(output, i) { outputs = outputs.filter(function(output, i) {
var s = output.script;
if (index != null && index !== i) if (index != null && index !== i)
return false; return false;
return output.testScript(key, hash, keys, scriptHash, null); return output.test(addressTable, true);
if (bcoin.script.isPubkey(s, key))
return true;
if (bcoin.script.isPubkeyhash(s, hash))
return true;
if (bcoin.script.isMultisig(s, keys))
return true;
if (scriptHash) {
if (bcoin.script.isScripthash(s, scriptHash))
return true;
}
return false;
}, this); }, this);
if (outputs.length === 0) if (outputs.length === 0)
@ -395,11 +308,7 @@ Address.prototype.ownOutput = function ownOutput(tx, index) {
}; };
Address.prototype.ownInput = function ownInput(tx, index) { Address.prototype.ownInput = function ownInput(tx, index) {
var scriptHash = this.getScriptHash(); var addressTable = this._getAddressTable();
var hash = this.getKeyHash();
var key = this.getPublicKey();
var redeem = this.getScript();
var keys = this.keys;
var inputs = tx.inputs; var inputs = tx.inputs;
if (tx instanceof bcoin.input) { if (tx instanceof bcoin.input) {
@ -415,25 +324,9 @@ Address.prototype.ownInput = function ownInput(tx, index) {
return false; return false;
if (input.output) if (input.output)
return !!this.ownOutput(input.output); return input.output.test(addressTable, true);
return input.testScript(key, redeem, null); return input.test(addressTable, true);
// if (bcoin.script.isPubkeyInput(input.script, key, tx, i))
// return true;
if (bcoin.script.isPubkeyhashInput(input.script, key))
return true;
// if (bcoin.script.isMultisigInput(input.script, keys, tx, i))
// return true;
if (redeem) {
if (bcoin.script.isScripthashInput(input.script, redeem))
return true;
}
return false;
}, this); }, this);
if (inputs.length === 0) if (inputs.length === 0)
@ -544,7 +437,7 @@ Address.prototype.__defineGetter__('address', function() {
return this.getAddress(); return this.getAddress();
}); });
Address.prototype.toAddress = function toAddress() { Address.prototype.toExplore = function toExplore() {
return { return {
address: this.getAddress(), address: this.getAddress(),
hash160: utils.toHex(this.getHash()), hash160: utils.toHex(this.getHash()),
@ -555,6 +448,77 @@ Address.prototype.toAddress = function toAddress() {
}; };
}; };
Address.hash160 = function hash160(key) {
key = utils.toBuffer(key);
return utils.ripesha(key);
};
Address.toAddress = function toAddress(hash, prefix) {
var addr;
hash = utils.toArray(hash, 'hex');
prefix = network.prefixes[prefix || 'pubkeyhash'];
hash = [prefix].concat(hash);
addr = hash.concat(utils.checksum(hash));
return utils.toBase58(addr);
};
Address.compile = function compile(key, prefix) {
return Address.toAddress(Address.hash160(key), prefix);
};
Address.toHash = function toHash(addr, prefix) {
var chk;
if (prefix == null && typeof addr === 'string')
prefix = Address.prefixes[addr[0]];
if (!utils.isBuffer(addr))
addr = utils.fromBase58(addr);
prefix = network.prefixes[prefix || 'pubkeyhash'];
if (addr.length !== 25)
return [];
if (addr[0] !== prefix)
return [];
chk = utils.checksum(addr.slice(0, -4));
if (utils.readU32(chk, 0) !== utils.readU32(addr, 21))
return [];
return addr.slice(1, -4);
};
Address.__defineGetter__('prefixes', function() {
if (Address._prefixes)
return Address._prefixes;
Address._prefixes = ['pubkeyhash', 'scripthash'].reduce(function(out, prefix) {
var ch = Address.compile([], prefix)[0];
out[ch] = prefix;
return out;
}, {});
return Address._prefixes;
});
Address.validate = function validate(addr, prefix) {
if (!addr || typeof addr !== 'string')
return false;
var p = Address.toHash(addr, prefix);
return p.length !== 0;
};
Address.validateAddress = Address.validate;
Address.prototype.toJSON = function toJSON(encrypt) { Address.prototype.toJSON = function toJSON(encrypt) {
return { return {
v: 1, v: 1,
@ -565,11 +529,9 @@ Address.prototype.toJSON = function toJSON(encrypt) {
derived: this.derived, derived: this.derived,
index: this.index, index: this.index,
path: this.path, path: this.path,
address: this.getKeyAddress(), address: this.getAddress(),
scriptAddress: this.getScriptAddress(),
key: this.key.toJSON(encrypt), key: this.key.toJSON(encrypt),
type: this.type, type: this.type,
subtype: this.subtype,
redeem: this.redeem ? utils.toHex(this.redeem) : null, redeem: this.redeem ? utils.toHex(this.redeem) : null,
keys: this.keys.map(utils.toBase58), keys: this.keys.map(utils.toBase58),
m: this.m, m: this.m,
@ -594,7 +556,6 @@ Address.fromJSON = function fromJSON(json, decrypt) {
path: json.path, path: json.path,
key: bcoin.keypair.fromJSON(json.key, decrypt), key: bcoin.keypair.fromJSON(json.key, decrypt),
type: json.type, type: json.type,
subtype: json.subtype,
redeem: json.redeem ? utils.toArray(json.redeem, 'hex') : null, redeem: json.redeem ? utils.toArray(json.redeem, 'hex') : null,
keys: json.keys.map(utils.fromBase58), keys: json.keys.map(utils.fromBase58),
m: json.m, m: json.m,

View File

@ -639,9 +639,13 @@ Chain.prototype.getTip = function getTip() {
}; };
Chain.prototype.isFull = function isFull() { Chain.prototype.isFull = function isFull() {
var delta;
if (!this.tip) if (!this.tip)
return false; return false;
var delta = utils.now() - this.tip.ts;
delta = utils.now() - this.tip.ts;
return delta < 40 * 60; return delta < 40 * 60;
}; };
@ -653,14 +657,15 @@ Chain.prototype.fillPercent = function fillPercent() {
Chain.prototype.hashRange = function hashRange(start, end) { Chain.prototype.hashRange = function hashRange(start, end) {
var hashes = []; var hashes = [];
var i;
start = this.byTime(start); start = this.byTime(start);
end = this.byTime(end); end = this.byTime(end);
if (!start || !end) if (!start || !end)
return []; return hashes;
for (var i = start.height; i < end.height + 1; i++) for (i = start.height; i < end.height + 1; i++)
hashes.push(this.db.get(i).hash); hashes.push(this.db.get(i).hash);
return hashes; return hashes;
@ -743,11 +748,17 @@ Chain.prototype.getHeight = function getHeight(hash) {
Chain.prototype.getNextBlock = function getNextBlock(hash) { Chain.prototype.getNextBlock = function getNextBlock(hash) {
var entry = this.byHash(hash); var entry = this.byHash(hash);
var next;
if (!entry || !entry.next) if (!entry)
return null; return null;
return entry.next.hash; next = entry.next;
if (!next)
return;
return next.hash;
}; };
Chain.prototype.getSize = function getSize() { Chain.prototype.getSize = function getSize() {

View File

@ -53,7 +53,8 @@ ChainBlock.prototype.getProof = function getProof() {
}; };
ChainBlock.prototype.getChainwork = function() { ChainBlock.prototype.getChainwork = function() {
return (this.prev ? this.prev.chainwork : new bn(0)).add(this.getProof()); var prev = this.prev;
return (prev ? prev.chainwork : new bn(0)).add(this.getProof());
}; };
ChainBlock.prototype.getMedianTime = function() { ChainBlock.prototype.getMedianTime = function() {

View File

@ -107,6 +107,9 @@ ChainDB.prototype._free = function(buf) {
}; };
ChainDB.prototype.exists = function exists() { ChainDB.prototype.exists = function exists() {
if (!bcoin.fs)
return true;
try { try {
fs.statSync(this.file); fs.statSync(this.file);
return true; return true;
@ -116,6 +119,9 @@ ChainDB.prototype.exists = function exists() {
}; };
ChainDB.prototype.getSize = function getSize() { ChainDB.prototype.getSize = function getSize() {
if (!bcoin.fs)
return this.ramdisk.size;
try { try {
return fs.statSync(this.file).size; return fs.statSync(this.file).size;
} catch (e) { } catch (e) {
@ -306,18 +312,26 @@ ChainDB.prototype.remove = function remove(height) {
while (this.isNull(height)) while (this.isNull(height))
height--; height--;
if (height < 0) assert(height >= 0);
height = 0;
fs.ftruncateSync(this.fd, (height + 1) * BLOCK_SIZE); this.truncate(height);
this.size = (height + 1) * BLOCK_SIZE;
this.tip = height;
} }
return true; return true;
}; };
ChainDB.prototype.truncate = function truncate(height) {
this.size = (height + 1) * BLOCK_SIZE;
this.tip = height;
if (!bcoin.fs) {
this.ramdisk.truncate(this.size);
return;
}
fs.ftruncateSync(this.fd, this.size);
};
ChainDB.prototype.isNull = function isNull(height) { ChainDB.prototype.isNull = function isNull(height) {
var data = this._readSync(4, height * BLOCK_SIZE); var data = this._readSync(4, height * BLOCK_SIZE);
if (!data) if (!data)

View File

@ -199,7 +199,7 @@ HDPrivateKey.prototype.scan44 = function scan44(options, txByAddress, callback)
// respect the gap limit described below // respect the gap limit described below
return (function next() { return (function next() {
var address = chain.derive(addressIndex++); var address = chain.derive(addressIndex++);
var addr = bcoin.address.key2addr(address.publicKey); var addr = bcoin.address.compile(address.publicKey);
return txByAddress(addr, function(err, txs) { return txByAddress(addr, function(err, txs) {
var result; var result;
@ -346,7 +346,7 @@ HDPrivateKey.prototype.scan45 = function scan45(options, txByAddress, callback)
return (function next() { return (function next() {
var address = chain.derive(addressIndex++); var address = chain.derive(addressIndex++);
var addr = bcoin.address.key2addr(address.publicKey); var addr = bcoin.address.compile(address.publicKey);
return txByAddress(addr, function(err, txs) { return txByAddress(addr, function(err, txs) {
var result; var result;
@ -837,7 +837,7 @@ function HDPublicKey(options) {
if (!options) if (!options)
throw new Error('No options for HDPublicKey'); throw new Error('No options for HDPublicKey');
if (HDPublicKey.isExtended(data)) if (HDPublicKey.isExtended(options))
options = { xkey: options }; options = { xkey: options };
data = options.xkey data = options.xkey

View File

@ -104,7 +104,7 @@ Input.prototype.__defineGetter__('id', function() {
}); });
Input.prototype.__defineGetter__('address', function() { Input.prototype.__defineGetter__('address', function() {
return this.data.scriptAddress || this.addresses[0]; return this.data.address;
}); });
Input.prototype.__defineGetter__('signatures', function() { Input.prototype.__defineGetter__('signatures', function() {
@ -163,34 +163,6 @@ Input.prototype.__defineGetter__('value', function() {
return this.output.value; return this.output.value;
}); });
Input.prototype.__defineGetter__('addr', function() {
return this.address;
});
Input.prototype.__defineGetter__('addrs', function() {
return this.addresses;
});
Input.prototype.__defineGetter__('pub', function() {
return this.key;
});
Input.prototype.__defineGetter__('pubs', function() {
return this.keys;
});
Input.prototype.__defineGetter__('sig', function() {
return this.signature;
});
Input.prototype.__defineGetter__('sigs', function() {
return this.signatures;
});
Input.prototype.__defineGetter__('scriptaddr', function() {
return this.scriptAddress;
});
// Schema and defaults for data object: // Schema and defaults for data object:
// { // {
// type: String, // type: String,
@ -253,25 +225,18 @@ Input.prototype.getData = function getData() {
return Input.getData(this); return Input.getData(this);
}; };
Input.prototype.getAddresses = function getAddresses() { Input.prototype.getType = function getType() {
return this.getData().addresses; var prev = this.output ? this.output.script : null;
}; return bcoin.script.getInputType(this.script, prev);
Input.prototype.getScriptAddress = function getScriptAddress() {
return this.getData().scriptAddress;
};
Input.prototype.getKeyAddress = function getKeyAddress() {
return this.getData().addresses[0];
}; };
Input.prototype.getAddress = function getAddress() { Input.prototype.getAddress = function getAddress() {
var data = this.getData(); var prev = this.output ? this.output.script : null;
return bcoin.script.getInputAddress(this.script, prev);
};
if (data.scriptAddress) Input.prototype.isRBF = function isRBF() {
return data.scriptAddress; return this.sequence === 0xffffffff - 1;
return data.addresses[0];
}; };
Input.prototype.isFinal = function isFinal() { Input.prototype.isFinal = function isFinal() {
@ -338,17 +303,16 @@ Input.prototype.testScript = function testScript(key, redeem, type) {
}; };
Input.prototype.test = function test(addressTable) { Input.prototype.test = function test(addressTable) {
var data = this.getData(); var address = this.getAddress();
var i;
if (data.scriptAddress) { if (address) {
if (addressTable[data.scriptAddress] != null) if (Array.isArray(addressTable)) {
return true; if (addressTable.indexOf(address) !== -1)
} return true;
} else {
for (i = 0; i < data.addresses.length; i++) { if (addressTable[address] != null)
if (addressTable[data.addresses[i]] != null) return true;
return true; }
} }
return false; return false;

View File

@ -75,7 +75,7 @@ Output.prototype.__defineGetter__('id', function() {
}); });
Output.prototype.__defineGetter__('address', function() { Output.prototype.__defineGetter__('address', function() {
return this.data.scriptAddress || this.addresses[0]; return this.data.address;
}); });
Output.prototype.__defineGetter__('signatures', function() { Output.prototype.__defineGetter__('signatures', function() {
@ -122,35 +122,6 @@ Output.prototype.__defineGetter__('text', function() {
return this.data.text; return this.data.text;
}); });
// Legacy
Output.prototype.__defineGetter__('addr', function() {
return this.address;
});
Output.prototype.__defineGetter__('addrs', function() {
return this.addresses;
});
Output.prototype.__defineGetter__('pub', function() {
return this.key;
});
Output.prototype.__defineGetter__('pubs', function() {
return this.keys;
});
Output.prototype.__defineGetter__('sig', function() {
return this.signature;
});
Output.prototype.__defineGetter__('sigs', function() {
return this.signatures;
});
Output.prototype.__defineGetter__('scriptaddr', function() {
return this.scriptAddress;
});
// Schema and defaults for data object: // Schema and defaults for data object:
// { // {
// type: String, // type: String,
@ -195,25 +166,12 @@ Output.prototype.getData = function getData() {
return Output.getData(this); return Output.getData(this);
}; };
Output.prototype.getAddresses = function getAddresses() { Output.prototype.getType = function getType() {
return this.getData().addresses; return bcoin.script.getOutputType(this.script);
};
Output.prototype.getScriptAddress = function getScriptAddress() {
return this.getData().scriptAddress;
};
Output.prototype.getKeyAddress = function getKeyAddress() {
return this.getData().addresses[0];
}; };
Output.prototype.getAddress = function getAddress() { Output.prototype.getAddress = function getAddress() {
var data = this.getData(); return bcoin.script.getOutputAddress(this.script);
if (data.scriptAddress)
return data.scriptAddress;
return data.addresses[0];
}; };
Output.prototype.getID = function getID() { Output.prototype.getID = function getID() {
@ -255,17 +213,16 @@ Output.prototype.testScript = function testScript(key, hash, keys, scriptHash, t
}; };
Output.prototype.test = function test(addressTable) { Output.prototype.test = function test(addressTable) {
var data = this.getData(); var address = this.getAddress();
var i;
if (data.scriptAddress) { if (address) {
if (addressTable[data.scriptAddress] != null) if (Array.isArray(addressTable)) {
return true; if (addressTable.indexOf(address) !== -1)
} return true;
} else {
for (i = 0; i < data.addresses.length; i++) { if (addressTable[address] != null)
if (addressTable[data.addresses[i]] != null) return true;
return true; }
} }
return false; return false;

View File

@ -205,13 +205,18 @@ exports.block = {
exports.tx = { exports.tx = {
maxSize: 100000, maxSize: 100000,
fee: 10000, minFee: 10000,
dust: 5460,
bareMultisig: true, bareMultisig: true,
freeThreshold: exports.coin.muln(144).divn(250), freeThreshold: exports.coin.muln(144).divn(250),
maxFreeSize: 1000 maxFreeSize: 1000
}; };
exports.tx.dustThreshold = new bn(182)
.muln(exports.tx.minFee)
.divn(1000)
.muln(3)
.toNumber();
exports.script = { exports.script = {
maxSize: 10000, maxSize: 10000,
maxStack: 1000, maxStack: 1000,

View File

@ -27,9 +27,7 @@ network.set = function set(type) {
main = network.main = {}; main = network.main = {};
main.prefixes = { main.prefixes = {
pubkey: 0,
pubkeyhash: 0, pubkeyhash: 0,
multisig: 0,
scripthash: 5, scripthash: 5,
privkey: 128, privkey: 128,
xpubkey: 0x0488b21e, xpubkey: 0x0488b21e,
@ -132,9 +130,7 @@ testnet = network.testnet = {};
testnet.type = 'testnet'; testnet.type = 'testnet';
testnet.prefixes = { testnet.prefixes = {
pubkey: 111,
pubkeyhash: 111, pubkeyhash: 111,
multisig: 111,
scripthash: 196, scripthash: 196,
privkey: 239, privkey: 239,
xpubkey: 0x043587cf, xpubkey: 0x043587cf,
@ -220,9 +216,7 @@ regtest = network.regtest = {};
regtest.type = 'testnet'; regtest.type = 'testnet';
regtest.prefixes = { regtest.prefixes = {
pubkey: 111,
pubkeyhash: 111, pubkeyhash: 111,
multisig: 111,
scripthash: 196, scripthash: 196,
privkey: 239, privkey: 239,
xpubkey: 0x043587cf, xpubkey: 0x043587cf,

View File

@ -762,7 +762,7 @@ script.execute = function execute(data, stack, tx, index, flags, recurse) {
n2 = script.num(stack.pop()); n2 = script.num(stack.pop());
n1 = script.num(stack.pop()); n1 = script.num(stack.pop());
val = n2.cmp(n1) <= 0 && n1.cmp(n3) < 0; val = n2.cmp(n1) <= 0 && n1.cmp(n3) < 0;
stack.push(val.cmpn(0) !== 0 ? [ 1 ] : []); stack.push(val.cmpn(0) !== 0 ? [1] : []);
break; break;
} }
@ -811,7 +811,7 @@ script.execute = function execute(data, stack, tx, index, flags, recurse) {
if (!res) if (!res)
return false; return false;
} else { } else {
stack.push(res ? [ 1 ] : []); stack.push(res ? [1] : []);
} }
break; break;
} }
@ -841,7 +841,7 @@ script.execute = function execute(data, stack, tx, index, flags, recurse) {
if (!res) if (!res)
return false; return false;
} else { } else {
stack.push(res ? [ 1 ] : []); stack.push(res ? [1] : []);
} }
break; break;
@ -918,7 +918,7 @@ script.execute = function execute(data, stack, tx, index, flags, recurse) {
if (!res) if (!res)
return false; return false;
} else { } else {
stack.push(res ? [ 1 ] : []); stack.push(res ? [1] : []);
} }
break; break;
@ -1204,10 +1204,11 @@ script.getRedeem = function getRedeem(s) {
if (!Array.isArray(s[s.length - 1])) if (!Array.isArray(s[s.length - 1]))
return; return;
return bcoin.script.decode(s[s.length - 1]); return script.decode(s[s.length - 1]);
}; };
script.getType = function getType(s) { script.getType =
script.getOutputType = function getOutputType(s) {
return (script.isPubkey(s) && 'pubkey') return (script.isPubkey(s) && 'pubkey')
|| (script.isPubkeyhash(s) && 'pubkeyhash') || (script.isPubkeyhash(s) && 'pubkeyhash')
|| (script.isMultisig(s) && 'multisig') || (script.isMultisig(s) && 'multisig')
@ -1268,19 +1269,20 @@ script.isEncoded = function isEncoded(s) {
}; };
script._locktime = function _locktime(s) { script._locktime = function _locktime(s) {
var i;
if (s.length < 2) if (s.length < 2)
return; return;
for (i = 0; i < s.length; i++) { if (!utils.isBuffer(s[0]))
if (utils.isBuffer(s[i]) && s[i + 1] === 'checklocktimeverify') return;
return s[i];
} if (s[1] !== 'checklocktimeverify')
return;
return s[0];
}; };
script.isLocktime = function isLocktime(s) { script.isLocktime = function isLocktime(s) {
return !!script._locktime(s); return script._locktime(s) != null;
}; };
script.getLocktime = function getLocktime(s) { script.getLocktime = function getLocktime(s) {
@ -1349,13 +1351,6 @@ script._getInputData = function _getInputData(s, type) {
assert(typeof type === 'string'); assert(typeof type === 'string');
if (type === 'pubkey') { if (type === 'pubkey') {
if (s.length < 1) {
return {
type: 'pubkey',
side: 'input',
none: true
};
}
sig = s[0]; sig = s[0];
return { return {
type: 'pubkey', type: 'pubkey',
@ -1366,35 +1361,22 @@ script._getInputData = function _getInputData(s, type) {
} }
if (type === 'pubkeyhash') { if (type === 'pubkeyhash') {
if (s.length < 2) {
return {
type: 'pubkeyhash',
side: 'input',
none: true
};
}
sig = s[0]; sig = s[0];
key = s[1]; key = s[1];
hash = bcoin.wallet.key2hash(key); hash = bcoin.address.hash160(key);
address = bcoin.wallet.hash2addr(hash, 'pubkeyhash'); address = bcoin.address.toAddress(hash, 'pubkeyhash');
return { return {
type: 'pubkeyhash', type: 'pubkeyhash',
side: 'input', side: 'input',
signatures: [sig], signatures: [sig],
keys: [key], keys: [key],
hashes: [hash], hashes: [hash],
address: address,
addresses: [address] addresses: [address]
}; };
} }
if (type === 'multisig') { if (type === 'multisig') {
if (s.length < 2) {
return {
type: 'multisig',
side: 'input',
none: true
};
}
sig = s.slice(1); sig = s.slice(1);
return { return {
type: 'multisig', type: 'multisig',
@ -1406,19 +1388,12 @@ script._getInputData = function _getInputData(s, type) {
} }
if (type === 'scripthash') { if (type === 'scripthash') {
if (s.length < 1) {
return {
type: 'scripthash',
side: 'input',
none: true
};
}
raw = s[s.length - 1]; raw = s[s.length - 1];
redeem = script.decode(raw); redeem = script.decode(raw);
locktime = script.getLocktime(redeem); locktime = script.getLocktime(redeem);
hash = bcoin.wallet.key2hash(raw); hash = bcoin.address.hash160(raw);
address = bcoin.wallet.hash2addr(hash, 'scripthash'); address = bcoin.address.toAddress(hash, 'scripthash');
output = script.getOutputData(script.getSubscript(redeem)); output = script.getOutputData(redeem, true);
input = script._getInputData(s.slice(0, -1), output.type); input = script._getInputData(s.slice(0, -1), output.type);
delete input.none; delete input.none;
return utils.merge(input, output, { return utils.merge(input, output, {
@ -1426,6 +1401,7 @@ script._getInputData = function _getInputData(s, type) {
side: 'input', side: 'input',
subtype: output.type, subtype: output.type,
redeem: redeem, redeem: redeem,
address: address,
scriptHash: hash, scriptHash: hash,
scriptAddress: address, scriptAddress: address,
locktime: locktime locktime: locktime
@ -1435,45 +1411,56 @@ script._getInputData = function _getInputData(s, type) {
return script.getUnknownData(s); return script.getUnknownData(s);
}; };
script.getOutputData = function getOutputData(s) { script.getOutputData = function getOutputData(s, inScriptHash) {
var key, hash, address; var key, hash, address, mhash, maddress;
if (script.isPubkey(s)) { if (script.isPubkey(s)) {
key = s[0]; key = s[0];
hash = bcoin.wallet.key2hash(key); hash = bcoin.address.hash160(key);
address = bcoin.wallet.hash2addr(hash, 'pubkey'); // Convert p2pk to p2pkh addresses
address = bcoin.address.toAddress(hash, 'pubkeyhash');
return { return {
type: 'pubkey', type: 'pubkey',
side: 'output', side: 'output',
keys: [key], keys: [key],
hashes: [hash], hashes: [hash],
address: address,
addresses: [address] addresses: [address]
}; };
} }
if (script.isPubkeyhash(s)) { if (script.isPubkeyhash(s)) {
hash = s[2]; hash = s[2];
address = bcoin.address.toAddress(hash, 'pubkeyhash');
return { return {
type: 'pubkeyhash', type: 'pubkeyhash',
side: 'output', side: 'output',
hashes: [hash], hashes: [hash],
addresses: [bcoin.wallet.hash2addr(hash, 'pubkeyhash')] address: address,
addresses: [address]
}; };
} }
if (script.isMultisig(s)) { if (script.isMultisig(s)) {
key = s.slice(1, -2); key = s.slice(1, -2);
hash = key.map(function(key) { hash = key.map(function(key) {
return bcoin.wallet.key2hash(key); return bcoin.address.hash160(key);
}); });
// Convert bare multisig to p2pkh addresses
address = hash.map(function(hash) { address = hash.map(function(hash) {
return bcoin.wallet.hash2addr(hash, 'multisig'); return bcoin.address.toAddress(hash, 'pubkeyhash');
}); });
// Convert bare multisig script to scripthash address
if (!inScriptHash) {
mhash = bcoin.address.hash160(s._raw || script.encode(s));
maddress = bcoin.address.toAddress(mhash, 'scripthash');
}
return { return {
type: 'multisig', type: 'multisig',
side: 'output', side: 'output',
keys: key, keys: key,
hashes: hash, hashes: hash,
address: maddress,
addresses: address, addresses: address,
m: s[0], m: s[0],
n: s[s.length - 2] n: s[s.length - 2]
@ -1482,11 +1469,13 @@ script.getOutputData = function getOutputData(s) {
if (script.isScripthash(s)) { if (script.isScripthash(s)) {
hash = s[1]; hash = s[1];
address = bcoin.address.toAddress(hash, 'scripthash');
return { return {
type: 'scripthash', type: 'scripthash',
side: 'output', side: 'output',
address: address,
scriptHash: hash, scriptHash: hash,
scriptAddress: bcoin.wallet.hash2addr(hash, 'scripthash') scriptAddress: address
}; };
} }
@ -1515,11 +1504,11 @@ script.getUnknownData = function getUnknownData(s) {
} }
hash = key.map(function(key) { hash = key.map(function(key) {
return bcoin.wallet.key2hash(key); return bcoin.address.hash160(key);
}); });
address = hash.map(function(hash) { address = hash.map(function(hash) {
return bcoin.wallet.hash2addr(hash, 'pubkey'); return bcoin.address.toAddress(hash, 'pubkeyhash');
}); });
return { return {
@ -1532,6 +1521,41 @@ script.getUnknownData = function getUnknownData(s) {
}; };
}; };
script.getInputAddress = function getInputAddress(s, prev) {
if (prev)
return script.getOutputAddress(prev);
if (script.isPubkeyInput(s))
return;
if (script.isPubkeyhashInput(s))
return bcoin.address.compile(s[1], 'pubkeyhash');
if (script.isMultisigInput(s))
return;
if (script.isScripthashInput(s))
return bcoin.address.compile(s[s.length - 1], 'scripthash');
};
script.getOutputAddress = function getOutputAddress(s) {
// Convert p2pk to p2pkh addresses
if (script.isPubkey(s))
return bcoin.address.compile(s[0], 'pubkeyhash');
if (script.isPubkeyhash(s))
return bcoin.address.toAddress(s[2], 'pubkeyhash')
// Convert bare multisig to scripthash address
if (script.isMultisig(s)) {
s = s._raw || script.encode(s);
return bcoin.address.compile(s, 'scripthash');
}
if (script.isScripthash(s))
return bcoin.address.toAddress(s[1], 'scripthash');
};
script.isPubkey = function isPubkey(s, key) { script.isPubkey = function isPubkey(s, key) {
var res; var res;
@ -1679,7 +1703,10 @@ script.isNulldata = function isNulldata(s) {
return s[1]; return s[1];
}; };
script.getInputType = function getInputType(s) { script.getInputType = function getInputType(s, prev) {
if (prev)
return script.getOutputType(prev);
return (script.isPubkeyInput(s) && 'pubkey') return (script.isPubkeyInput(s) && 'pubkey')
|| (script.isPubkeyhashInput(s) && 'pubkeyhash') || (script.isPubkeyhashInput(s) && 'pubkeyhash')
|| (script.isMultisigInput(s) && 'multisig') || (script.isMultisigInput(s) && 'multisig')
@ -1793,6 +1820,9 @@ script.isScripthashInput = function isScripthashInput(s, data, strict) {
// If the last data element is a valid // If the last data element is a valid
// signature or key, it's _extremely_ // signature or key, it's _extremely_
// unlikely this is a scripthash. // unlikely this is a scripthash.
if (script.isDummy(raw))
return false;
if (script.isSignatureEncoding(raw)) if (script.isSignatureEncoding(raw))
return false; return false;
@ -1815,7 +1845,7 @@ script.isScripthashInput = function isScripthashInput(s, data, strict) {
// P2SH redeem scripts can be nonstandard: make // P2SH redeem scripts can be nonstandard: make
// it easier for other functions to parse this. // it easier for other functions to parse this.
redeem = script.getSubscript(script.decode(raw)); redeem = script.decode(raw);
// Get the "real" scriptSig // Get the "real" scriptSig
s = s.slice(0, -1); s = s.slice(0, -1);
@ -2185,7 +2215,7 @@ script.getScripthashSigops = function getScripthashSigops(s) {
if (!script.isPushOnly(s)) if (!script.isPushOnly(s))
return 0; return 0;
s = script.getSubscript(script.decode(s[s.length - 1])); s = script.getRedeem(s);
return script.getSigops(s, true); return script.getSigops(s, true);
}; };

View File

@ -257,7 +257,7 @@ TX.prototype.scriptInput = function scriptInput(index, pub, redeem) {
input.script = [[]]; input.script = [[]];
} else if (bcoin.script.isPubkeyhash(s)) { } else if (bcoin.script.isPubkeyhash(s)) {
// P2PKH // P2PKH
if (!utils.isEqual(s[2], bcoin.wallet.key2hash(pub))) if (!utils.isEqual(s[2], bcoin.address.hash160(pub)))
return false; return false;
// Already has a script template (at least) // Already has a script template (at least)
if (input.script.length) if (input.script.length)
@ -403,7 +403,7 @@ TX.prototype.signInput = function signInput(index, key, type) {
// Get pubkey and pubkey hash. // Get pubkey and pubkey hash.
pub = key.getPublic(true, 'array'); pub = key.getPublic(true, 'array');
pkh = bcoin.wallet.key2hash(pub); pkh = bcoin.address.hash160(pub);
// Add signatures. // Add signatures.
if (bcoin.script.isPubkey(s)) { if (bcoin.script.isPubkey(s)) {
@ -731,13 +731,13 @@ TX.prototype.scriptOutput = function scriptOutput(index, options) {
// https://github.com/bitcoin/bips/blob/master/bip-0016.mediawiki // https://github.com/bitcoin/bips/blob/master/bip-0016.mediawiki
// hash160 [20-byte-redeemscript-hash] equal // hash160 [20-byte-redeemscript-hash] equal
script = bcoin.script.createScripthash( script = bcoin.script.createScripthash(
bcoin.wallet.addr2hash(options.address, 'scripthash') bcoin.address.toHash(options.address, 'scripthash')
); );
} else if (options.address) { } else if (options.address) {
// P2PKH Transaction // P2PKH Transaction
// dup hash160 [pubkey-hash] equalverify checksig // dup hash160 [pubkey-hash] equalverify checksig
script = bcoin.script.createPubkeyhash( script = bcoin.script.createPubkeyhash(
bcoin.wallet.addr2hash(options.address, 'pubkeyhash') bcoin.address.toHash(options.address, 'pubkeyhash')
); );
} else if (options.key) { } else if (options.key) {
// P2PK Transaction // P2PK Transaction
@ -814,9 +814,6 @@ TX.prototype.signatureHash = function signatureHash(index, s, type) {
// if strictenc is not enabled. // if strictenc is not enabled.
// assert(utils.isFinite(type)); // assert(utils.isFinite(type));
// Remove code separators.
// s = script.getSubscript(s);
// Remove all signatures. // Remove all signatures.
for (i = 0; i < copy.inputs.length; i++) for (i = 0; i < copy.inputs.length; i++)
copy.inputs[i].script = []; copy.inputs[i].script = [];
@ -881,7 +878,7 @@ TX.prototype.tbsHash = function tbsHash(enc, force) {
if (!this._tbsHash || force) { if (!this._tbsHash || force) {
for (i = 0; i < copy.inputs.length; i++) { for (i = 0; i < copy.inputs.length; i++) {
if (!copy.isCoinbase()) if (!copy.inputs[i].isCoinbase())
copy.inputs[i].script = []; copy.inputs[i].script = [];
} }
@ -1008,14 +1005,7 @@ TX.prototype.maxSize = function maxSize() {
} }
// Byte for varint size of input script // Byte for varint size of input script
if (size < 0xfd) size += utils.sizeIntv(size);
size += 0;
else if (size <= 0xffff)
size += 2;
else if (size <= 0xffffffff)
size += 4;
else
size += 8;
total += size; total += size;
} }
@ -1027,7 +1017,7 @@ TX.prototype.getInputs = function getInputs(unspent, address, fee) {
var tx = this.clone(); var tx = this.clone();
var cost = tx.getOutputValue(); var cost = tx.getOutputValue();
var totalkb = 1; var totalkb = 1;
var total = cost.addn(constants.tx.fee); var total = cost.addn(constants.tx.minFee);
var inputs = []; var inputs = [];
var lastAdded = 0; var lastAdded = 0;
var size, newkb, change; var size, newkb, change;
@ -1042,18 +1032,25 @@ TX.prototype.getInputs = function getInputs(unspent, address, fee) {
return a.height - b.height; return a.height - b.height;
}); });
function addInput(unspent) { function addCoins() {
// Add new inputs until TX will have enough var i, index;
// funds to cover both minimum post cost
// and fee. for (i = lastAdded; i < unspent.length; i++) {
var index = tx._addInput(unspent); // Add new inputs until TX will have enough
inputs.push(tx.inputs[index]); // funds to cover both minimum post cost
lastAdded++; // and fee.
return tx.getInputValue().cmp(total) < 0; index = tx._addInput(unspent[i]);
inputs.push(tx.inputs[index]);
lastAdded++;
// Stop once we're full.
if (tx.getInputValue().cmp(total) >= 0)
break;
}
} }
// Transfer `total` funds maximum. // Transfer `total` funds maximum.
unspent.every(addInput); addCoins();
if (!fee) { if (!fee) {
// Add dummy output (for `change`) to // Add dummy output (for `change`) to
@ -1064,9 +1061,9 @@ TX.prototype.getInputs = function getInputs(unspent, address, fee) {
}); });
// if (this.subtractFee) { // if (this.subtractFee) {
// var f = new bn((Math.ceil(tx.maxSize() / 1024) - 1) * constants.tx.fee); // var f = new bn((Math.ceil(tx.maxSize() / 1024) - 1) * constants.tx.minFee);
// for (var j = 0; j < this.outputs.length; j++) { // for (var j = 0; j < this.outputs.length; j++) {
// if (this.outputs[j].value.cmp(f.addn(constants.tx.dust)) >= 0) { // if (this.outputs[j].value.cmp(f.addn(constants.tx.dustThreshold)) >= 0) {
// this.outputs[j].value = this.outputs[j].value.sub(f); // this.outputs[j].value = this.outputs[j].value.sub(f);
// break; // break;
// } // }
@ -1081,12 +1078,12 @@ TX.prototype.getInputs = function getInputs(unspent, address, fee) {
size = tx.maxSize(); size = tx.maxSize();
newkb = Math.ceil(size / 1024) - totalkb; newkb = Math.ceil(size / 1024) - totalkb;
total.iaddn(newkb * constants.tx.fee); total.iaddn(newkb * constants.tx.minFee);
totalkb += newkb; totalkb += newkb;
// Failed to get enough funds, add more inputs. // Failed to get enough funds, add more inputs.
if (tx.getInputValue().cmp(total) < 0) if (tx.getInputValue().cmp(total) < 0)
unspent.slice(lastAdded).every(addInput); addCoins();
} while (tx.getInputValue().cmp(total) < 0 && lastAdded < unspent.length); } while (tx.getInputValue().cmp(total) < 0 && lastAdded < unspent.length);
} }
@ -1134,7 +1131,7 @@ TX.prototype.fill = function fill(unspent, address, fee) {
this.addInput(input); this.addInput(input);
}, this); }, this);
if (result.change.cmpn(constants.tx.dust) < 0) { if (result.change.cmpn(constants.tx.dustThreshold) < 0) {
// Do nothing. Change is added to fee. // Do nothing. Change is added to fee.
assert.equal( assert.equal(
this.getFee().toNumber(), this.getFee().toNumber(),
@ -1210,7 +1207,7 @@ TX.prototype._recalculateFee = function recalculateFee() {
} }
size = this.maxSize(); size = this.maxSize();
real = Math.ceil(size / 1024) * constants.tx.fee; real = Math.ceil(size / 1024) * constants.tx.minFee;
fee = this.getFee().toNumber(); fee = this.getFee().toNumber();
// if (this.hardFee) // if (this.hardFee)
@ -1233,7 +1230,7 @@ TX.prototype._recalculateFee = function recalculateFee() {
output.value.iaddn(fee - real); output.value.iaddn(fee - real);
} }
if (output.value.cmpn(constants.tx.dust) < 0) { if (output.value.cmpn(constants.tx.dustThreshold) < 0) {
this.outputs.pop(); this.outputs.pop();
this.changeIndex = -1; this.changeIndex = -1;
return; return;
@ -1428,15 +1425,22 @@ TX.prototype.setLocktime = function setLocktime(locktime) {
}; };
TX.prototype.increaseFee = function increaseFee(fee) { TX.prototype.increaseFee = function increaseFee(fee) {
var i, input; var i, input, result;
this.inputs = [];
if (this.changeIndex !== -1)
this.outputs.splice(this.changeIndex, 1);
this.hardFee = fee || this.getFee().add(new bn(10000)); this.hardFee = fee || this.getFee().add(new bn(10000));
this.fill(); result = this.fill();
for (i = 0; i < this.inputs.length; i++) { for (i = 0; i < this.inputs.length; i++) {
input = this.inputs[i]; input = this.inputs[i];
input.sequence = 0xffffffff - 1; input.sequence = 0xffffffff - 1;
} }
return !!result.inputs;
}; };
TX.prototype.hasPrevout = function hasPrevout() { TX.prototype.hasPrevout = function hasPrevout() {
@ -1559,7 +1563,7 @@ TX.prototype.isStandard = function isStandard() {
if (type === 'multisig' && !constants.tx.bareMultisig) if (type === 'multisig' && !constants.tx.bareMultisig)
return false; return false;
if (output.value.cmpn(constants.tx.dust) < 0) if (output.value.cmpn(constants.tx.dustThreshold) < 0)
return false; return false;
} }

View File

@ -797,7 +797,7 @@ utils.sortHDKeys = function sortHDKeys(keys) {
}); });
}; };
utils.uniq = function(obj) { utils.uniq = function uniq(obj) {
var out = []; var out = [];
var i = 0; var i = 0;
@ -809,6 +809,22 @@ utils.uniq = function(obj) {
return out; return out;
}; };
utils.uniqs = function uniqs(obj) {
var table = {};
var out = [];
var i = 0;
for (; i < obj.length; i++) {
if (!table[obj[i]]) {
out.push(obj[i]);
table[obj[i]] = true;
}
}
return out;
};
utils.fromCompact = function fromCompact(compact) { utils.fromCompact = function fromCompact(compact) {
var exponent = compact >> 24; var exponent = compact >> 24;
var negative = (compact >> 23) & 0x01; var negative = (compact >> 23) & 0x01;

View File

@ -77,6 +77,7 @@ function Wallet(options) {
this.accountIndex = options.accountIndex || 0; this.accountIndex = options.accountIndex || 0;
this.addressDepth = options.addressDepth || 0; this.addressDepth = options.addressDepth || 0;
this.changeDepth = options.changeDepth || 0; this.changeDepth = options.changeDepth || 0;
this.copayBIP45 = options.copayBIP45 || false;
this.cosignerIndex = -1; this.cosignerIndex = -1;
this.sharedCosignerIndex = constants.hd.hardened - 1; this.sharedCosignerIndex = constants.hd.hardened - 1;
this.purposeKeys = options.purposeKeys || []; this.purposeKeys = options.purposeKeys || [];
@ -84,26 +85,21 @@ function Wallet(options) {
this.hd = !!this.master; this.hd = !!this.master;
this.type = options.type || 'pubkeyhash'; this.type = options.type || 'pubkeyhash';
this.subtype = options.subtype || null;
this.derivation = options.derivation || null; this.derivation = options.derivation || null;
this.compressed = options.compressed !== false; this.compressed = options.compressed !== false;
this.keys = []; this.keys = [];
this.m = options.m || 1; this.m = options.m || 1;
this.n = options.n || 1; this.n = options.n || 1;
this.nmax = this.type === 'scripthash'
? (this.compressed ? 15 : 7)
: 3;
if (this.n > 1) { if (this.n > 1)
if (this.type !== 'multisig') this.type = 'multisig';
this.type = 'scripthash';
if (this.type === 'scripthash') assert(this.type === 'pubkeyhash' || this.type === 'multisig');
this.subtype = 'multisig'; this.prefixType = this.type === 'multisig' ? 'scripthash' : 'pubkeyhash';
}
if (!this.derivation) { if (!this.derivation) {
if (this.master) { if (this.master) {
if (this.type === 'scripthash' && this.subtype === 'multisig') if (this.type === 'multisig')
this.derivation = 'bip45'; this.derivation = 'bip45';
else else
this.derivation = 'bip44'; this.derivation = 'bip44';
@ -112,15 +108,12 @@ function Wallet(options) {
} }
} }
if (network.prefixes[this.type] == null) if (network.prefixes[this.prefixType] == null)
throw new Error('Unknown prefix: ' + this.type); throw new Error('Unknown prefix: ' + this.prefixType);
if (this.m < 1 || this.m > this.n) if (this.m < 1 || this.m > this.n)
throw new Error('m ranges between 1 and n'); throw new Error('m ranges between 1 and n');
if (this.n < 1 || this.n > this.nmax)
throw new Error('n ranges between 1 and ' + this.nmax);
if (this.derivation === 'bip45') { if (this.derivation === 'bip45') {
this.purposeKey = this.master.isPurpose45() this.purposeKey = this.master.isPurpose45()
? this.master ? this.master
@ -140,7 +133,6 @@ function Wallet(options) {
publicKey: options.publicKey, publicKey: options.publicKey,
pair: options.pair, pair: options.pair,
type: this.type, type: this.type,
subtype: this.subtype,
m: this.m, m: this.m,
n: this.n, n: this.n,
keys: [], keys: [],
@ -162,23 +154,7 @@ function Wallet(options) {
// generate the last receiving address. However, since "normal" wallets // generate the last receiving address. However, since "normal" wallets
// cannot deterministically generate keys, we have to buffer the generated // cannot deterministically generate keys, we have to buffer the generated
// key for later. // key for later.
if (this.derivation === 'bip44') { if (this.derivation === 'normal') {
// Generate the last known receiving address
key = this.createKey(false, Math.max(0, this.addressDepth - 1));
this.currentAddress = bcoin.address({
privateKey: key.privateKey,
publicKey: key.publicKey,
compressed: key.compressed,
index: key.index,
path: key.path,
type: this.type,
subtype: this.subtype,
m: this.m,
n: this.n,
keys: options.keys,
derived: true
});
} else if (this.derivation === 'normal') {
// Try to find the last receiving address if there is one. // Try to find the last receiving address if there is one.
receiving = options.addresses.filter(function(address) { receiving = options.addresses.filter(function(address) {
return !address.change && this._isKeyOptions(address); return !address.change && this._isKeyOptions(address);
@ -197,11 +173,11 @@ function Wallet(options) {
index: key.index, index: key.index,
path: key.path, path: key.path,
type: this.type, type: this.type,
subtype: this.subtype,
m: this.m, m: this.m,
n: this.n, n: this.n,
keys: options.keys keys: options.keys
}); });
this.currentAddress._wallet = this;
} }
} }
@ -219,7 +195,7 @@ inherits(Wallet, EventEmitter);
Wallet.prototype._pruneAddresses = function _pruneAddresses(options) { Wallet.prototype._pruneAddresses = function _pruneAddresses(options) {
var addresses = this.addresses.slice(); var addresses = this.addresses.slice();
var address; var i, address;
for (i = 0; i < addresses.length; i++) { for (i = 0; i < addresses.length; i++) {
address = addresses[i]; address = addresses[i];
@ -250,17 +226,17 @@ Wallet.prototype._isKeyOptions = function _isKeyOptions(options) {
// normal: Address of first key in wallet // normal: Address of first key in wallet
Wallet.prototype.getID = function getID() { Wallet.prototype.getID = function getID() {
if (this.derivation === 'bip45') if (this.derivation === 'bip45')
return bcoin.address.key2addr(this.purposeKey.publicKey); return bcoin.address.compile(this.purposeKey.publicKey);
if (this.derivation === 'bip44') if (this.derivation === 'bip44')
return bcoin.address.key2addr(this.purposeKey.publicKey); return bcoin.address.compile(this.purposeKey.publicKey);
if (this.derivation === 'normal') { if (this.derivation === 'normal') {
if (this.addresses.length) if (this.addresses.length)
return this.addresses[0].getKeyAddress(); return this.addresses[0].getKeyAddress();
if (this._firstKey) if (this._firstKey)
return bcoin.address.key2addr(this._firstKey.publicKey); return bcoin.address.compile(this._firstKey.publicKey);
} }
assert(false); assert(false);
@ -316,6 +292,14 @@ Wallet.prototype._initAddresses = function _initAddresses() {
Wallet.prototype.addKey = function addKey(key) { Wallet.prototype.addKey = function addKey(key) {
var hdKey, has, i; var hdKey, has, i;
if (key instanceof bcoin.wallet) {
assert(key.derivation === this.derivation);
if (key.derivation === 'bip44' || key.derivation === 'bip45')
key = key.purposeKey;
else
key = key.currentAddress.publicKey;
}
if (bcoin.hd.privateKey.isExtended(key)) if (bcoin.hd.privateKey.isExtended(key))
key = bcoin.hd.privateKey(key); key = bcoin.hd.privateKey(key);
else if (bcoin.hd.publicKey.isExtended(key)) else if (bcoin.hd.publicKey.isExtended(key))
@ -387,6 +371,7 @@ Wallet.prototype.finalizeKeys = function finalizeKeys(key) {
for (i = 0; i < this.purposeKeys.length; i++) { for (i = 0; i < this.purposeKeys.length; i++) {
if (this.purposeKeys[i].xpubkey === this.purposeKey.xpubkey) { if (this.purposeKeys[i].xpubkey === this.purposeKey.xpubkey) {
this.cosignerIndex = i; this.cosignerIndex = i;
this._cosignerIndex = i;
break; break;
} }
} }
@ -394,6 +379,7 @@ Wallet.prototype.finalizeKeys = function finalizeKeys(key) {
assert(this.cosignerIndex !== -1); assert(this.cosignerIndex !== -1);
this._initAddresses(); this._initAddresses();
return; return;
} }
@ -402,6 +388,7 @@ Wallet.prototype.finalizeKeys = function finalizeKeys(key) {
for (i = 0; i < this.keys.length; i++) { for (i = 0; i < this.keys.length; i++) {
if (utils.isEqual(this.keys[i], this.currentAddress.publicKey)) { if (utils.isEqual(this.keys[i], this.currentAddress.publicKey)) {
this.cosignerIndex = i; this.cosignerIndex = i;
this._cosignerIndex = i;
break; break;
} }
} }
@ -416,6 +403,14 @@ Wallet.prototype.removeKey = function removeKey(key) {
assert(!this._keysFinalized); assert(!this._keysFinalized);
if (key instanceof bcoin.wallet) {
assert(key.derivation === this.derivation);
if (key.derivation === 'bip44' || key.derivation === 'bip45')
key = key.purposeKey;
else
key = key.currentAddress.publicKey;
}
if (bcoin.hd.privateKey.isExtended(key)) if (bcoin.hd.privateKey.isExtended(key))
key = bcoin.hd.privateKey(key); key = bcoin.hd.privateKey(key);
else if (bcoin.hd.publicKey.isExtended(key)) else if (bcoin.hd.publicKey.isExtended(key))
@ -527,9 +522,7 @@ Wallet.prototype._getAddressTable = function() {
for (i = 0; i < this.addresses.length; i++) { for (i = 0; i < this.addresses.length; i++) {
address = this.addresses[i]; address = this.addresses[i];
if (address.type === 'scripthash') addresses[address.getAddress()] = i;
addresses[address.getScriptAddress()] = i;
addresses[address.getKeyAddress()] = i;
} }
return addresses; return addresses;
@ -545,20 +538,13 @@ Wallet.prototype._addressIndex = function _addressIndex(address) {
if (!(address instanceof bcoin.address)) if (!(address instanceof bcoin.address))
address = bcoin.address(address); address = bcoin.address(address);
if (address.type === 'scripthash') { addr = address.getAddress();
addr = address.getScriptAddress();
if (this._addressTable[addr] != null)
return this._addressTable[addr];
}
addr = address.getKeyAddress();
if (this._addressTable[addr] != null) if (this._addressTable[addr] != null)
return this._addressTable[addr]; return this._addressTable[addr];
return -1; return -1;
}; };
// TODO: fromPath here
Wallet.prototype.createAddress = function createAddress(change, index) { Wallet.prototype.createAddress = function createAddress(change, index) {
var self = this; var self = this;
var key = this.createKey(change, index); var key = this.createKey(change, index);
@ -573,7 +559,6 @@ Wallet.prototype.createAddress = function createAddress(change, index) {
index: key.index, index: key.index,
path: key.path, path: key.path,
type: this.type, type: this.type,
subtype: this.subtype,
m: this.m, m: this.m,
n: this.n, n: this.n,
keys: [], keys: [],
@ -610,7 +595,7 @@ Wallet.prototype.createAddress = function createAddress(change, index) {
this.keys = utils.sortKeys(options.keys); this.keys = utils.sortKeys(options.keys);
} else if (this.derivation === 'normal') { } else if (this.derivation === 'normal') {
this.keys.forEach(function(key, i) { this.keys.forEach(function(key, i) {
if (i !== this.cosignerIndex) if (i !== this._cosignerIndex)
options.keys.push(key); options.keys.push(key);
}, this); }, this);
options.keys.push(key.publicKey); options.keys.push(key.publicKey);
@ -658,10 +643,7 @@ Wallet.prototype.addAddress = function addAddress(address) {
self.emit('add address', address); self.emit('add address', address);
}); });
if (address.type === 'scripthash') this._addressTable[address.getAddress()] = index;
this._addressTable[address.getScriptAddress()] = index;
this._addressTable[address.getKeyAddress()] = index;
if (address.label && this._labelTable[address.label] == null) if (address.label && this._labelTable[address.label] == null)
this._labelTable[address.label] = index; this._labelTable[address.label] = index;
@ -832,30 +814,19 @@ Wallet.prototype.ownOutput = function ownOutput(tx, index) {
return tx.testOutputs(this._addressTable, index, true); return tx.testOutputs(this._addressTable, index, true);
}; };
Wallet.prototype.fill = function fill(tx, address, fee) { Wallet.prototype.fill = function fill(tx, options) {
var unspent, items, result; var address, unspent;
if (!options)
options = {};
assert(this._initialized); assert(this._initialized);
if (!address) address = this.changeAddress.getAddress();
address = this.changeAddress.getKeyAddress();
unspent = this.getUnspent(); unspent = this.getUnspent();
items = unspent.filter(function(coin) { result = tx.fill(unspent, address, options.fee);
if (bcoin.script.isScripthash(coin.script))
return this.type === 'scripthash';
if (bcoin.script.isMultisig(coin.script))
return this.type === 'multisig';
return true;
}, this);
if (tx.getInputs(unspent, address, fee).inputs)
unspent = items;
result = tx.fill(unspent, address, fee);
if (!result.inputs) if (!result.inputs)
return false; return false;
@ -1040,14 +1011,13 @@ Wallet.prototype.toJSON = function toJSON(encrypt) {
name: 'wallet', name: 'wallet',
network: network.type, network: network.type,
type: this.type, type: this.type,
subtype: this.subtype,
m: this.m, m: this.m,
n: this.n, n: this.n,
derivation: this.derivation, derivation: this.derivation,
copayBIP45: this.copayBIP45,
accountIndex: this.accountIndex, accountIndex: this.accountIndex,
addressDepth: this.addressDepth, addressDepth: this.addressDepth,
changeDepth: this.changeDepth, changeDepth: this.changeDepth,
cosignerIndex: this.cosignerIndex,
master: this.master ? this.master.toJSON(encrypt) : null, master: this.master ? this.master.toJSON(encrypt) : null,
addresses: this.addresses.filter(function(address) { addresses: this.addresses.filter(function(address) {
return !address.derived; return !address.derived;
@ -1077,14 +1047,13 @@ Wallet.fromJSON = function fromJSON(json, decrypt) {
wallet = new Wallet({ wallet = new Wallet({
type: json.type, type: json.type,
subtype: json.subtype,
m: json.m, m: json.m,
n: json.n, n: json.n,
derivation: json.derivation, derivation: json.derivation,
copayBIP45: json.copayBIP45,
accountIndex: json.accountIndex, accountIndex: json.accountIndex,
addressDepth: json.addressDepth, addressDepth: json.addressDepth,
changeDepth: json.changeDepth, changeDepth: json.changeDepth,
cosignerIndex: json.cosignerIndex,
master: json.master master: json.master
? bcoin.hd.fromJSON(json.master, decrypt) ? bcoin.hd.fromJSON(json.master, decrypt)
: null, : null,
@ -1109,19 +1078,19 @@ Wallet.fromSecret = function fromSecret(privateKey) {
}; };
Wallet.key2hash = function key2hash(key) { Wallet.key2hash = function key2hash(key) {
return bcoin.address.key2hash(key); return bcoin.address.hash160(key);
}; };
Wallet.hash2addr = function hash2addr(hash, prefix) { Wallet.hash2addr = function hash2addr(hash, prefix) {
return bcoin.address.hash2addr(hash, prefix); return bcoin.address.toAddress(hash, prefix);
}; };
Wallet.addr2hash = function addr2hash(addr, prefix) { Wallet.addr2hash = function addr2hash(addr, prefix) {
return bcoin.address.addr2hash(addr, prefix); return bcoin.address.toHash(addr, prefix);
}; };
Wallet.validateAddress = function validateAddress(addr, prefix) { Wallet.validateAddress = function validateAddress(addr, prefix) {
return bcoin.address.validateAddress(addr, prefix); return bcoin.address.validate(addr, prefix);
}; };
/** /**

View File

@ -81,7 +81,7 @@ describe('Wallet', function() {
// var k2 = w.getPublicKey().concat(1); // var k2 = w.getPublicKey().concat(1);
var k2 = bcoin.ecdsa.genKeyPair().getPublic(true, 'array'); var k2 = bcoin.ecdsa.genKeyPair().getPublic(true, 'array');
w.addKey(k2); w.addKey(k2);
assert.equal(w.getKeyAddress(), w.getAddress()); // assert.equal(w.getKeyAddress(), w.getAddress());
// Input transcation // Input transcation
var src = bcoin.tx({ var src = bcoin.tx({
@ -222,7 +222,7 @@ describe('Wallet', function() {
tx.out(to, 5460); tx.out(to, 5460);
var cost = tx.funds('out'); var cost = tx.funds('out');
var total = cost.add(new bn(constants.tx.fee)); var total = cost.add(new bn(constants.tx.minFee));
var unspent1 = w1.unspent(); var unspent1 = w1.unspent();
var unspent2 = w2.unspent(); var unspent2 = w2.unspent();
@ -236,7 +236,7 @@ describe('Wallet', function() {
tx.input(unspent2[0]); tx.input(unspent2[0]);
var left = tx.funds('in').sub(total); var left = tx.funds('in').sub(total);
if (left.cmpn(constants.tx.dust) < 0) { if (left.cmpn(constants.tx.dustThreshold) < 0) {
tx.outputs[tx.outputs.length - 2].value.iadd(left); tx.outputs[tx.outputs.length - 2].value.iadd(left);
left = new bn(0); left = new bn(0);
} }
@ -270,37 +270,35 @@ describe('Wallet', function() {
// Create 3 2-of-3 wallets with our pubkeys as "shared keys" // Create 3 2-of-3 wallets with our pubkeys as "shared keys"
var w1 = bcoin.wallet({ var w1 = bcoin.wallet({
derivation: 'bip44', derivation: 'bip44',
type: 'scripthash', type: 'multisig',
subtype: 'multisig',
m: 2, m: 2,
n: 3 n: 3
}); });
var w2 = bcoin.wallet({ var w2 = bcoin.wallet({
derivation: 'bip44', derivation: 'bip44',
type: 'scripthash', type: 'multisig',
subtype: 'multisig',
m: 2, m: 2,
n: 3 n: 3
}); });
var w3 = bcoin.wallet({ var w3 = bcoin.wallet({
derivation: 'bip44', derivation: 'bip44',
type: 'scripthash', type: 'multisig',
subtype: 'multisig',
m: 2, m: 2,
n: 3 n: 3
}); });
// w3 = bcoin.wallet.fromJSON(w3.toJSON());
var receive = bcoin.wallet(); var receive = bcoin.wallet();
w1.addKey(w2.purposeKey); w1.addKey(w2);
w1.addKey(w3.purposeKey); w1.addKey(w3);
w2.addKey(w1.purposeKey); w2.addKey(w1);
w2.addKey(w3.purposeKey); w2.addKey(w3);
w3.addKey(w1.purposeKey); w3.addKey(w1);
w3.addKey(w2.purposeKey); w3.addKey(w2);
w3 = bcoin.wallet.fromJSON(w3.toJSON());
// Our p2sh address // Our p2sh address
var addr = w1.getAddress(); var addr = w1.getAddress();