rewrite wallet. allow multiple addresses. utilize hd.

This commit is contained in:
Christopher Jeffrey 2016-02-01 21:36:41 -08:00
parent f9d960ca23
commit 42e17bc0f2
7 changed files with 1443 additions and 513 deletions

View File

@ -27,6 +27,8 @@ bcoin.tx = require('./bcoin/tx');
bcoin.txPool = require('./bcoin/tx-pool');
bcoin.block = require('./bcoin/block');
bcoin.chain = require('./bcoin/chain');
bcoin.keypair = require('./bcoin/keypair');
bcoin.address = require('./bcoin/address');
bcoin.wallet = require('./bcoin/wallet');
bcoin.peer = require('./bcoin/peer');
bcoin.pool = require('./bcoin/pool');

431
lib/bcoin/address.js Normal file
View File

@ -0,0 +1,431 @@
/**
* address.js - address object for bcoin
* Copyright (c) 2014-2015, Fedor Indutny (MIT License)
* https://github.com/indutny/bcoin
*/
var bcoin = require('../bcoin');
var hash = require('hash.js');
var bn = require('bn.js');
var inherits = require('inherits');
var EventEmitter = require('events').EventEmitter;
var utils = bcoin.utils;
var assert = utils.assert;
var constants = bcoin.protocol.constants;
var network = bcoin.protocol.network;
/**
* Address
*/
function Address(options) {
if (!(this instanceof Address))
return new Address(options);
EventEmitter.call(this);
if (!options)
options = {};
this.options = options;
this.storage = options.storage;
this.label = options.label || '';
this.change = !!options.change;
this.key = bcoin.keypair({
priv: options.priv,
pub: options.pub,
hd: options.hd,
key: options.key,
personalization: options.personalization,
entropy: options.entropy,
compressed: options.compressed
});
// Compatability
if (options.multisig) {
if (options.multisig.type)
options.type = options.multisig.type;
if (options.multisig.keys)
options.keys = options.multisig.keys;
if (options.multisig.m)
options.m = options.multisig.m;
if (options.multisig.n)
options.n = options.multisig.n;
}
this.type = options.type || 'pubkeyhash';
this.subtype = options.subtype;
this.keys = [];
this.m = options.m || 1;
this.n = options.n || 1;
this.redeem = null;
if (this.n > 1) {
if (this.type !== 'multisig')
this.type = 'scripthash';
if (this.type === 'scripthash')
this.subtype = 'multisig';
}
if (network.prefixes[this.type] == null)
throw new Error('Unknown prefix: ' + this.type);
this.nmax = this.type === 'scripthash'
? (this.key.compressed ? 15 : 7)
: 3;
if (this.m < 1 || this.m > this.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());
(options.keys || []).forEach(function(key) {
this.addKey(key);
}, this);
if (options.redeem)
this.setRedeem(options.redeem);
this.prefix = 'bt/address/' + this.getKeyAddress() + '/';
}
inherits(Address, EventEmitter);
Address.prototype.setRedeem = function setRedeem(redeem) {
var old = this.getScriptAddress();
if (!utils.isBytes(redeem))
redeem = bcoin.script.encode(redeem);
this.type = 'scripthash';
this.subtype = null;
this.redeem = redeem;
this.emit('scriptaddress', old, this.getScriptAddress());
};
Address.prototype.addKey = function addKey(key) {
var old = this.getScriptAddress();
key = utils.toBuffer(key);
var has = this.keys.some(function(k) {
return utils.isEqual(k, key);
});
if (has)
return;
this.keys.push(key);
this.keys = utils.sortKeys(this.keys);
this.emit('scriptaddress', old, this.getScriptAddress());
};
Address.prototype.removeKey = function removeKey(key) {
var old = this.getScriptAddress();
key = utils.toBuffer(key);
var index = this.keys.map(function(key, i) {
return utils.isEqual(key, pub) ? i : null;
}).filter(function(i) {
return i !== null;
})[0];
if (index == null)
return;
this.keys.splice(index, 1);
this.keys = utils.sortKeys(this.keys);
this.emit('scriptaddress', old, this.getScriptAddress());
};
Address.prototype.getPrivateKey = function getPrivateKey(enc) {
return this.key.getPrivate(enc);
};
Address.toSecret = function toSecret(priv, compressed) {
return bcoin.keypair.toSecret(priv, compressed);
};
Address.fromSecret = function fromSecret(priv) {
return bcoin.keypair.fromSecret(priv);
};
Address.prototype.getScript = function getScript() {
if (this.type !== 'scripthash')
return;
if (this.redeem)
return this.redeem.slice();
if (this.subtype === 'pubkey')
return bcoin.script.encode([this.getPublicKey(), 'checksig']);
if (this.subtype === 'pubkeyhash' || this.keys.length < this.n) {
return bcoin.script.encode([
'dup',
'hash160',
this.getKeyHash(),
'equalverify',
'checksig'
]);
}
return bcoin.script.encode(
bcoin.script.createMultisig(this.keys, this.m, this.n)
);
};
Address.prototype.getScriptHash = function getScriptHash() {
if (this.type !== 'scripthash')
return;
return utils.ripesha(this.getScript());
};
Address.prototype.getScriptAddress = function getScriptAddress() {
if (this.type !== 'scripthash')
return;
return Address.hash2addr(this.getScriptHash(), this.type);
};
Address.prototype.getPublicKey = function getPublicKey(enc) {
return this.key.getPublic(enc);
};
Address.prototype.getKeyHash = function getKeyHash() {
return Address.key2hash(this.getPublicKey());
};
Address.prototype.getKeyAddress = function getKeyAddress() {
return Address.hash2addr(this.getKeyHash(), 'pubkeyhash');
};
Address.prototype.getHash = function getHash() {
if (this.type === 'scripthash')
return this.getScriptHash();
return this.getKeyHash();
};
Address.prototype.getAddress = function getAddress() {
if (this.type === 'scripthash')
return this.getScriptAddress();
return this.getKeyAddress();
};
Address.key2hash = function key2hash(key) {
key = utils.toBuffer(key);
return utils.ripesha(key);
};
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.__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 validateAddress(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) {
var scripthash = this.getScriptHash();
var hash = this.getKeyHash();
var key = this.getPublicKey();
var keys = this.keys;
var outputs = tx.outputs.filter(function(output, i) {
var s = output.script;
if (index != null && index !== i)
return false;
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);
if (outputs.length === 0)
return false;
return outputs;
};
Address.prototype.ownInput = function ownInput(tx, index) {
var scripthash = this.getScriptHash();
var hash = this.getKeyHash();
var key = this.getPublicKey();
var redeem = this.getScript();
var keys = this.keys;
var inputs = tx.inputs.filter(function(input, i) {
var s;
if (!input.prevout.tx && this.tx._all[input.prevout.hash])
input.prevout.tx = this.tx._all[input.prevout.hash];
if (index != null && index !== i)
return false;
// 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;
}
if (!input.prevout.tx)
return false;
s = input.prevout.tx.getSubscript(input.prevout.index);
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);
if (inputs.length === 0)
return false;
return inputs;
};
Address.prototype.toJSON = function toJSON(encrypt) {
return {
v: 1,
name: 'address',
network: network.type,
label: this.label,
change: this.change,
address: this.getKeyAddress(),
scriptaddress: this.getScriptAddress(),
key: this.key.toJSON(encrypt),
type: this.type,
subtype: this.subtype,
redeem: this.redeem ? utils.toHex(this.redeem) : null,
keys: this.keys.map(utils.toBase58),
m: this.m,
n: this.n
};
};
Address.fromJSON = function fromJSON(json, decrypt) {
var priv, pub, xprivkey, multisig, compressed, key, w;
assert.equal(json.v, 1);
assert.equal(json.name, 'address');
if (json.network)
assert.equal(json.network, network.type);
w = new Address({
label: json.label,
change: json.change,
key: bcoin.keypair.fromJSON(json.key, decrypt),
multisig: multisig,
type: json.type,
subtype: json.subtype,
redeem: json.redeem ? utils.toArray(json.redeem, 'hex') : null,
keys: json.keys.map(utils.fromBase58),
m: json.m,
n: json.n
});
return w;
};
/**
* Expose
*/
module.exports = Address;

View File

@ -126,9 +126,12 @@ function HDPrivateKey(options) {
if (!options)
options = { seed: bcoin.hd.seed() };
if (typeof options === 'string' && options.indexOf('xprv') === 0)
if (HDPrivateKey.isExtended(options))
options = { xkey: options };
if (HDPublicKey.isExtended(options))
return new HDPublicKey(options);
if (options.passphrase !== undefined
|| options.bits
|| options.entropy
@ -157,8 +160,171 @@ function HDPrivateKey(options) {
this.data = data;
this._build(data);
if (new bn(data.parentFingerPrint).cmpn(0) === 0) {
this.isMaster = true;
this.master = this;
} else {
this.master = options.master;
}
this.purpose = options.purpose != null
? options.purpose
: 44;
this.coinType = options.coinType != null
? options.coinType
: (network.type === 'main' ? 0 : 1);
this.accountIndex = options.accountIndex != null
? options.accountIndex
: 0;
this.isChange = options.isChange != null
? options.isChange
: false;
// this.addressIndex = options.addressIndex != null
// ? options.addressIndex
// : 0;
this.isPrivate = true;
}
HDPrivateKey.prototype.scan = function scan(txs) {
var keys = [];
assert(this.isMaster);
// 0. get the root node
var root = master
.derive(44, true)
.derive(network.type === 'main' ? 0 : 1, true);
return (function scanner(accountIndex) {
var account, chain, addressIndex, address, addr, i;
var gap = 0;
// 1. derive the first account's node (index = 0)
account = root.derive(accountIndex, true);
// 2. derive the external chain node of this account
chain = account.derive(0);
// 3. scan addresses of the external chain;
// respect the gap limit described below
addressIndex = 0;
main:
for (;;) {
address = chain.derive(addressIndex++);
addr = bcoin.address.hash2addr(
bcoin.address.key2hash(address.publicKey),
'pubkey');
for (i = 0; i < txs.length; i++) {
txs[i].fillPrevout(txs);
if (txs[i].testInputs(addr) || txs[i].testOutputs(addr)) {
keys.push(address);
gap = 0;
} else {
// 4. if no transactions are found on the
// external chain, stop discovery
if (++gap >= 20)
return keys;
}
}
}
// 5. if there are some transactions, increase
// the account index and go to step 1
return scanner(accountIndex + 1);
})(0);
};
HDPrivateKey.prototype.__defineGetter__('addressIndex', function() {
var index = this.childIndex;
assert(index < contants.hd.hardened);
return index;
});
HDPrivateKey.prototype._deriveBIP44 = function _deriveBIP44() {
var child;
assert(this.isMaster);
if (options.purpose == null)
options.purpose = 44;
if (options.coinType == null)
options.coinType = network.type === 'main' ? 0 : 1;
child = this
.derive(options.purpose, true)
.derive(options.coinType, true)
.derive(options.accountIndex, true)
.derive(options.isChange ? 1 : 0)
.derive(options.addressIndex);
child.purpose = options.purpose;
child.coinType = options.coinType;
child.accountIndex = options.accountIndex;
child.isChange = options.isChange;
child.addressIndex = options.addressIndex;
return child;
};
HDPrivateKey.prototype.deriveAccount = function deriveAccount(accountIndex) {
return this._deriveBIP44({
purpose: this.purpose,
coinType: this.coinType,
accountIndex: accountIndex != null ? accountIndex : this.accountIndex + 1,
isChange: false,
addressIndex: 0
});
};
HDPrivateKey.prototype.deriveChange = function deriveChange(addressIndex, accountIndex) {
return this._deriveBIP44({
purpose: this.purpose,
coinType: this.coinType,
accountIndex: accountIndex != null ? accountIndex : this.accountIndex,
isChange: true,
addressIndex: addressIndex != null ? addressIndex : this.addressIndex + 1
});
};
HDPrivateKey.prototype.deriveAddress = function deriveAddress(addressIndex, accountIndex) {
return this._deriveBIP44({
purpose: this.purpose,
coinType: this.coinType,
accountIndex: accountIndex != null ? accountIndex : this.accountIndex,
isChange: false,
addressIndex: addressIndex != null ? addressIndex : this.addressIndex + 1
});
};
HDPrivateKey.prototype.toPath = function toPath(data) {
assert(!this.isMaster);
return HDPrivateKey.getPath(
this.accountIndex, this.addressIndex, this.isChange);
};
HDPrivateKey.getPath = function toPath(accountIndex, addressIndex, isChange) {
return 'm/44\'/'
+ (network.type === 'main' ? '0' : '1') + '\'' + '/'
+ accountIndex + '\'/'
+ (isChange ? '1' : '0') + '/'
+ addressIndex;
};
HDPrivateKey.isExtended = function isExtended(data) {
if (typeof data !== 'string')
return false;
return data.indexOf('xprv') === 0 || data.indexOf('tprv') === 0;
};
HDPrivateKey.prototype._normalize = function _normalize(data, version) {
data.version = version || network.prefixes.xprivkey;
data.privateKey = data.privateKey || data.priv;
@ -350,7 +516,8 @@ HDPrivateKey.prototype.derive = function derive(index, hardened) {
childIndex: index,
chainCode: chainCode,
privateKey: privateKey,
checksum: null
checksum: null,
master: this.master
});
};
@ -378,6 +545,19 @@ HDPrivateKey._getIndexes = function _getIndexes(path) {
return null;
index = +step;
if (i === 0) {
indexes.purpose = index;
} else if (i === 1) {
indexes.coinType = index;
} else if (i === 2) {
indexes.accountIndex = index;
} else if (i === 3) {
indexes.isChange = index === 1;
} else if (i === 4) {
indexes.addressIndex = index;
}
if (hardened)
index += constants.hd.hardened;
@ -388,15 +568,16 @@ HDPrivateKey._getIndexes = function _getIndexes(path) {
};
HDPrivateKey.isValidPath = function isValidPath(path, hardened) {
var indexes;
if (typeof path === 'string') {
var indexes = HDPrivateKey._getIndexes(path);
indexes = HDPrivateKey._getIndexes(path);
return indexes !== null && indexes.every(HDPrivateKey.isValidPath);
}
if (typeof path === 'number') {
if (path < constants.hd.hardened && hardened) {
if (path < constants.hd.hardened && hardened)
path += constants.hd.hardened;
}
return path >= 0 && path < constants.hd.maxIndex;
}
@ -404,14 +585,24 @@ HDPrivateKey.isValidPath = function isValidPath(path, hardened) {
};
HDPrivateKey.prototype.deriveString = function deriveString(path) {
var indexes, child;
if (!HDPrivateKey.isValidPath(path))
throw new Error('invalid path');
var indexes = HDPrivateKey._getIndexes(path);
indexes = HDPrivateKey._getIndexes(path);
return indexes.reduce(function(prev, index) {
child = indexes.reduce(function(prev, index, i) {
return prev.derive(index);
}, this);
child.purpose = indexes.purpose;
child.coinType = indexes.coinType;
child.accountIndex = indexes.accountIndex;
child.isChange = indexes.isChange;
child.addressIndex = indexes.addressIndex;
return child;
};
/**
@ -427,7 +618,7 @@ function HDPublicKey(options) {
if (!options)
throw new Error('No options for HDPublicKey');
if (typeof options === 'string' && options.indexOf('xpub') === 0)
if (HDPublicKey.isExtended(data))
options = { xkey: options };
data = options.xkey
@ -439,8 +630,56 @@ function HDPublicKey(options) {
this.data = data;
this._build(data);
if (new bn(data.parentFingerPrint).cmpn(0) === 0) {
this.isMaster = true;
this.master = this;
} else {
this.master = options.master;
}
this.purpose = options.purpose != null
? options.purpose
: 44;
this.coinType = options.coinType != null
? options.coinType
: (network.type === 'main' ? 0 : 1);
this.accountIndex = options.accountIndex != null
? options.accountIndex
: 0;
this.isChange = options.isChange != null
? options.isChange
: false;
// this.addressIndex = options.addressIndex != null
// ? options.addressIndex
// : 0;
this.isPublic = true;
}
HDPublicKey.prototype.__defineGetter__('addressIndex', function() {
var index = this.childIndex;
assert(index < contants.hd.hardened);
return index;
});
HDPublicKey.prototype._deriveBIP44 = HDPrivateKey.prototype._deriveBIP44;
HDPublicKey.prototype.deriveAccount = HDPrivateKey.prototype.deriveAccount;
HDPublicKey.prototype.deriveChange = HDPrivateKey.prototype.deriveChange;
HDPublicKey.prototype.deriveAddress = HDPrivateKey.prototype.deriveAddress;
HDPublicKey.prototype.toPath = HDPrivateKey.prototype.toPath;
HDPublicKey.isExtended = function isExtended(data) {
if (typeof data !== 'string')
return false;
return data.indexOf('xpub') === 0 || data.indexOf('tpub') === 0;
};
HDPublicKey.prototype._normalize = HDPrivateKey.prototype._normalize;
HDPublicKey.prototype._unbuild = function _unbuild(xkey) {
@ -546,7 +785,8 @@ HDPublicKey.prototype.derive = function derive(index, hardened) {
childIndex: index,
chainCode: chainCode,
publicKey: publicKey,
checksum: null
checksum: null,
master: this.master
});
};

270
lib/bcoin/keypair.js Normal file
View File

@ -0,0 +1,270 @@
/**
* keypair.js - keypair object for bcoin
* Copyright (c) 2014-2015, Fedor Indutny (MIT License)
* https://github.com/indutny/bcoin
*/
var bcoin = require('../bcoin');
var hash = require('hash.js');
var bn = require('bn.js');
var inherits = require('inherits');
var EventEmitter = require('events').EventEmitter;
var utils = bcoin.utils;
var assert = utils.assert;
var constants = bcoin.protocol.constants;
var network = bcoin.protocol.network;
/**
* KeyPair
*/
function KeyPair(options) {
if (!(this instanceof KeyPair))
return new KeyPair(options);
if (!options)
options = {};
if (options instanceof KeyPair)
return options;
if (options.key instanceof KeyPair)
return options.key;
this.options = options;
this.key = options.key || null;
this.hd = options.hd || null;
this.compressed = options.compressed !== false;
if (options.priv instanceof bcoin.hd.priv) {
this.hd = options.priv;
this.key = options.priv.pair;
} else if (options.pub instanceof bcoin.hd.pub) {
this.hd = options.pub;
this.key = options.pub.pair;
} else if (options.hd) {
this.hd = typeof options.hd === 'object'
? bcoin.hd.priv(options.hd)
: bcoin.hd.priv();
this.key = this.hd.pair;
} else if (options.key) {
if ((options.key instanceof bcoin.hd.priv)
|| (options.key instanceof bcoin.hd.pub)) {
this.hd = options.key;
this.key = options.key.pair;
} else {
this.key = options.key;
}
} else if (options.priv || options.pub) {
this.key = bcoin.ecdsa.keyPair({
priv: options.priv,
pub: options.pub
});
} else {
this.key = bcoin.ecdsa.genKeyPair({
pers: options.personalization,
entropy: options.entropy
|| (options.passphrase ? utils.sha256(options.passphrase) : null)
});
}
}
KeyPair.prototype.__defineGetter__('priv', function() {
return this.key.getPrivate();
});
KeyPair.prototype.__defineGetter__('pub', function() {
return this.key.getPublic();
});
KeyPair.prototype.getPrivate = function getPrivate(enc) {
var priv = this.key.getPrivate();
if (!priv)
return;
priv = priv.toArray();
if (enc === 'base58')
return KeyPair.toSecret(priv, this.compressed);
if (enc === 'hex')
return utils.toHex(priv);
return priv;
};
KeyPair.prototype.getPublic = function getPublic(enc) {
var pub = this.key.getPublic(this.compressed, 'array');
if (enc === 'base58')
return utils.toBase58(pub);
if (enc === 'hex')
return utils.toHex(pub);
return pub;
};
KeyPair.prototype.toSecret = function toSecret() {
return KeyPair.toSecret(this.getPrivate(), this.compressed);
};
KeyPair.toSecret = function toSecret(priv, compressed) {
var arr, chk;
// We'll be using ncompressed public key as an address
arr = [network.prefixes.privkey];
// 0-pad key
while (arr.length + priv.length < 33)
arr.push(0);
arr = arr.concat(priv);
if (compressed)
arr.push(1);
chk = utils.checksum(arr);
return utils.toBase58(arr.concat(chk));
};
KeyPair.fromSecret = function fromSecret(priv) {
var key, compressed;
key = utils.fromBase58(priv);
assert(utils.isEqual(key.slice(-4), utils.checksum(key.slice(0, -4))));
assert.equal(key[0], network.prefixes.privkey);
key = key.slice(0, -4);
if (key.length === 34) {
assert.equal(key[33], 1);
priv = key.slice(1, -1);
compressed = true;
} else {
priv = key.slice(1);
compressed = false;
}
return new KeyPair({
priv: priv,
compressed: compressed
});
};
KeyPair.prototype.toJSON = function toJSON(encrypt) {
var json = {
v: 1,
name: 'keypair',
encrypted: encrypt ? true : false
};
if (this.hd) {
if (this.hd.xprivkey) {
if (this.hd.seed) {
json.mnemonic = encrypt
? encrypt(this.hd.seed.mnemonic)
: this.hd.seed.mnemonic;
json.passphrase = encrypt
? encrypt(this.hd.seed.passphrase)
: this.hd.seed.passphrase;
return json;
}
json.xpriv = encrypt
? encrypt(this.hd.xprivkey)
: this.hd.xprivkey;
return json;
}
json.xpub = this.hd.xpubkey;
return json;
}
if (this.key.priv) {
json.priv = encrypt
? encrypt(this.getPrivate('base58'))
: this.getPrivate('base58');
return json;
}
json.pub = this.getPublic('hex');
return json;
};
KeyPair.fromJSON = function fromJSON(json, decrypt) {
var key, priv, pub, compressed, xprivkey;
var path = {};
assert.equal(json.v, 1);
assert.equal(json.name, 'keypair');
if (json.encrypted && !decrypt)
throw new Error('Cannot decrypt address');
if (json.mnemonic) {
return new KeyPair({
key: bcoin.hd.priv({
seed: bcoin.hd.seed({
mnemonic: json.encrypted
? decrypt(json.mnemonic)
: json.mnemonic,
passphrase: json.encrypted
? decrypt(json.passphrase)
: json.passphrase
})
})
});
}
if (json.xpriv) {
xprivkey = json.xpriv;
if (json.encrypted)
xprivkey = decrypt(xprivkey);
return new KeyPair({
key: bcoin.hd.priv({
xkey: xprivkey
})
});
}
if (json.xpub) {
return new KeyPair({
key: bcoin.hd.pub({
xkey: json.xpub
})
});
}
if (json.priv) {
priv = json.priv;
if (json.encrypted)
priv = decrypt(priv);
key = KeyPair.fromSecret(json.priv);
priv = key.priv;
compressed = key.compressed;
return new KeyPair({
priv: priv,
compressed: compressed
});
}
if (json.pub) {
pub = bcoin.utils.toArray(json.pub, 'hex');
compressed = pub[0] !== 0x04;
return new KeyPair({
pub: pub,
compressed: compressed
});
}
assert(false);
};
/**
* Expose
*/
module.exports = KeyPair;

View File

@ -921,6 +921,7 @@ Pool.prototype._removePeer = function _removePeer(peer) {
};
Pool.prototype.watch = function watch(id) {
var self = this;
var hid, i;
if (id instanceof bcoin.wallet) {
@ -941,14 +942,25 @@ Pool.prototype.watch = function watch(id) {
this.bloom.add(id, 'hex');
}
if (this.peers.load)
this.peers.load.updateWatch();
// Send it to peers
if (this._pendingWatch)
return;
for (i = 0; i < this.peers.block.length; i++)
this.peers.block[i].updateWatch();
this._pendingWatch = true;
utils.nextTick(function() {
self._pendingWatch = false;
if (self.peers.load)
self.peers.load.updateWatch();
for (i = 0; i < self.peers.block.length; i++)
self.peers.block[i].updateWatch();
});
};
Pool.prototype.unwatch = function unwatch(id) {
var self = this;
var i;
id = utils.toHex(id);
@ -968,11 +980,20 @@ Pool.prototype.unwatch = function unwatch(id) {
}, this);
// Resend it to peers
if (this.peers.load)
this.peers.load.updateWatch();
if (this._pendingWatch)
return;
for (i = 0; i < this.peers.block.length; i++)
this.peers.block[i].updateWatch();
this._pendingWatch = true;
utils.nextTick(function() {
self._pendingWatch = false;
if (self.peers.load)
self.peers.load.updateWatch();
for (i = 0; i < self.peers.block.length; i++)
self.peers.block[i].updateWatch();
});
};
// See "Filter matching algorithm":
@ -1084,32 +1105,58 @@ Pool.prototype.removeWallet = function removeWallet(w) {
this.unwatchWallet(w);
};
Pool.prototype.watchWallet = function watchWallet(w) {
if (w.type === 'scripthash') {
Pool.prototype.watchAddress = function watchAddress(address) {
if (address.type === 'scripthash') {
// For the redeem script hash in outputs:
this.watch(w.getScriptHash());
this.watch(address.getScriptHash());
// For the redeem script in inputs:
this.watch(w.getScript());
this.watch(address.getScript());
}
// For the pubkey hash in outputs:
this.watch(w.getKeyHash());
this.watch(address.getKeyHash());
// For the pubkey in inputs:
this.watch(w.getPublicKey());
this.watch(address.getPublicKey());
};
Pool.prototype.unwatchWallet = function unwatchWallet(w) {
if (w.type === 'scripthash') {
Pool.prototype.unwatchAddress = function unwatchAddress(address) {
if (address.type === 'scripthash') {
// For the redeem script hash in p2sh outputs:
this.unwatch(w.getScriptHash());
this.unwatch(address.getScriptHash());
// For the redeem script in p2sh inputs:
this.unwatch(w.getScript());
this.unwatch(address.getScript());
}
// For the pubkey hash in p2pk/multisig outputs:
this.unwatch(w.getKeyHash());
this.unwatch(address.getKeyHash());
// For the pubkey in p2pkh inputs:
this.unwatch(w.getPublicKey());
this.unwatch(address.getPublicKey());
};
Pool.prototype.watchWallet = function watchWallet(w) {
var self = this;
w.addresses.forEach(function(address) {
this.watchAddress(address);
}, this);
w.on('add address', w._poolOnAdd = function(address) {
self.watchAddress(address);
});
w.on('remove address', w._poolOnRemove = function(address) {
self.unwatchAddress(address);
});
};
Pool.prototype.unwatchWallet = function unwatchWallet(w) {
w.addresses.forEach(function(address) {
this.unwatchAddress(address);
}, this);
w.removeListener('add address', w._poolOnAdd);
w.removeListener('remove address', w._poolOnRemove);
delete w._poolOnAdd;
delete w._poolOnRemove;
};
Pool.prototype.searchWallet = function(w) {

View File

@ -197,19 +197,20 @@ TX.prototype.scriptInput = function scriptInput(index, pub, redeem) {
input = this.inputs[index];
assert(input);
// Already has a script template (at least)
// if (input.script.length)
// return;
// We should have previous outputs by now.
assert(input.prevout.tx);
// Already has a script template (at least)
if (input.script.length)
return;
// Get the previous output's subscript
s = input.prevout.tx.getSubscript(input.prevout.index);
// P2SH
if (bcoin.script.isScripthash(s)) {
assert(redeem);
if (!redeem)
return false;
s = bcoin.script.getSubscript(bcoin.script.decode(redeem));
} else {
redeem = null;
@ -217,12 +218,34 @@ TX.prototype.scriptInput = function scriptInput(index, pub, redeem) {
if (bcoin.script.isPubkey(s)) {
// P2PK
if (!utils.isEqual(s[0], pub))
return false;
// Already has a script template (at least)
if (input.script.length)
return true;
input.script = [[]];
} else if (bcoin.script.isPubkeyhash(s)) {
// P2PKH
if (!utils.isEqual(s[2], bcoin.wallet.key2hash(pub)))
return false;
// Already has a script template (at least)
if (input.script.length)
return true;
input.script = [[], pub];
} else if (bcoin.script.isMultisig(s)) {
// Multisig
for (i = 0; i < s.length; i++) {
if (utils.isEqual(s[i], pub))
break;
}
if (i === s.length)
return false;
// Already has a script template (at least)
if (input.script.length)
return true;
// Technically we should create m signature slots,
// but we create n signature slots so we can order
// the signatures properly.
@ -235,6 +258,18 @@ TX.prototype.scriptInput = function scriptInput(index, pub, redeem) {
for (i = 0; i < n; i++)
input.script[i + 1] = [];
} else {
for (i = 0; i < s.length; i++) {
if (utils.isEqual(s[i], pub))
break;
}
if (i === s.length)
return false;
// Already has a script template (at least)
if (input.script.length)
return true;
// Likely a non-standard scripthash multisig
// input. Determine n value by counting keys.
// Also, only allow nonstandard types for
@ -256,6 +291,8 @@ TX.prototype.scriptInput = function scriptInput(index, pub, redeem) {
// now that the redeem script is available.
this._recalculateFee();
}
return true;
};
TX.prototype.createSignature = function createSignature(index, key, type) {
@ -501,10 +538,12 @@ TX.prototype.scriptSig = function scriptSig(index, key, pub, redeem, type) {
assert(input);
// Build script for input
this.scriptInput(index, pub, redeem);
if (!this.scriptInput(index, pub, redeem))
return false;
// Sign input
this.signInput(index, key, type);
if (!this.signInput(index, key, type))
return false;
return input.script;
};
@ -1131,6 +1170,111 @@ TX.prototype.getFunds = function getFunds(side) {
// Legacy
TX.prototype.funds = TX.prototype.getFunds;
TX.prototype.testInputs = function testInputs(addressTable, index, collect) {
var inputs = [];
var i, input, prev, data, j;
if (typeof addressTable === 'string')
addressTable = [addressTable];
if (Array.isArray(addressTable)) {
addressTable = addressTable.reduce(function(address) {
out[address] = true;
return out;
}, {});
}
for (i = 0; i < this.inputs.length; i++) {
if (index != null && i !== index)
continue;
input = this.inputs[i];
if (input.prevout.tx)
prev = input.prevout.tx.outputs[input.prevout.index].script;
else
prev = null;
data = bcoin.script.getInputData(input.script, prev);
if (data.addresses) {
for (j = 0; j < data.addresses.length; j++) {
if (addressTable[data.addresses[j]] != null) {
if (!collect)
return true;
inputs.push(input);
}
}
}
if (data.scriptaddress) {
if (addressTable[data.scriptaddress] != null) {
if (!collect)
return true;
inputs.push(input);
}
}
}
if (!collect)
return false;
if (inputs.length === 0)
return false;
return inputs;
};
TX.prototype.testOutputs = function testOutputs(addressTable, index, collect) {
var outputs = [];
var i, output, data, j;
if (typeof addressTable === 'string')
addressTable = [addressTable];
if (Array.isArray(addressTable)) {
addressTable = addressTable.reduce(function(address) {
out[address] = true;
return out;
}, {});
}
for (i = 0; i < this.outputs.length; i++) {
if (index != null && i !== index)
continue;
output = this.outputs[i];
data = bcoin.script.getOutputData(output.script);
if (data.addresses) {
for (j = 0; j < data.addresses.length; j++) {
if (addressTable[data.addresses[j]] != null) {
if (!collect)
return true;
outputs.push(output);
}
}
}
if (data.scriptaddress) {
if (addressTable[data.scriptaddress] != null) {
if (!collect)
return true;
outputs.push(output);
}
}
}
if (!collect)
return false;
if (outputs.length === 0)
return false;
return outputs;
};
TX.prototype.avoidFeeSnipping = function avoidFeeSnipping() {
if (!this.chain)
return;

View File

@ -18,115 +18,36 @@ var network = bcoin.protocol.network;
* Wallet
*/
function Wallet(options, passphrase) {
function Wallet(options) {
if (!(this instanceof Wallet))
return new Wallet(options, passphrase);
return new Wallet(options);
EventEmitter.call(this);
if (typeof options === 'string' && typeof passphrase === 'string') {
options = {
scope: options,
passphrase: passphrase
};
}
if (!options)
options = {};
this.options = options;
this.compressed = options.compressed !== false;
this.addresses = [];
this.master = null;
this._addressTable = {};
this.accountIndex = options.accountIndex || 0;
this.addressIndex = options.addressIndex || 0;
this.changeIndex = options.changeIndex || 0;
if (options.addresses) {
options.addresses.forEach(function(address) {
this.addAddress(address);
}, this);
} else {
this.addAddress(options);
}
this.storage = options.storage;
this.label = options.label || '';
this.key = null;
this.loaded = false;
this.lastTs = 0;
this.changeAddress = options.changeAddress || null;
this.redeem = options.redeem || options.script;
if (options.priv instanceof bcoin.hd.priv) {
this.hd = options.priv;
this.key = this.hd;
} else if (options.pub instanceof bcoin.hd.pub) {
this.hd = options.pub;
this.key = this.hd;
} else if (options.hd) {
this.hd = typeof options.hd === 'object'
? bcoin.hd.priv(options.hd)
: bcoin.hd.priv();
this.key = this.hd;
} else if (options.key) {
if ((options.key instanceof bcoin.hd.priv)
|| (options.key instanceof bcoin.hd.pub)) {
this.hd = options.key;
this.key = options.key;
} else {
this.key = options.key;
}
} else if (options.passphrase) {
this.key = bcoin.ecdsa.genKeyPair({
pers: options.scope,
entropy: hash.sha256().update(options.passphrase).digest()
});
} else if (options.priv || options.pub) {
this.key = bcoin.ecdsa.keyPair({
priv: options.priv,
pub: options.pub
});
} else {
this.key = bcoin.ecdsa.genKeyPair();
}
// Compatability
if (options.multisig) {
if (options.multisig.type)
options.type = options.multisig.type;
if (options.multisig.keys)
options.keys = options.multisig.keys;
if (options.multisig.m)
options.m = options.multisig.m;
if (options.multisig.n)
options.n = options.multisig.n;
}
this.type = options.type || 'pubkeyhash';
this.subtype = options.subtype;
this.keys = [];
this.m = options.m || 1;
this.n = options.n || 1;
if (this.n > 1) {
if (this.type !== 'multisig')
this.type = 'scripthash';
if (this.type === 'scripthash')
this.subtype = 'multisig';
}
if (network.prefixes[this.type] == null)
throw new Error('Unknown prefix: ' + this.type);
this.nmax = this.type === 'scripthash'
? (this.compressed ? 15 : 7)
: 3;
if (this.m < 1 || this.m > this.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());
(options.keys || []).forEach(function(key) {
this.addKey(key);
}, this);
if (this.redeem) {
if (!utils.isBytes(this.redeem))
this.redeem = bcoin.script.encode(this.redeem);
this.type = 'scripthash';
this.subtype = null;
}
this.prefix = 'bt/wallet/' + this.getKeyAddress() + '/';
@ -170,342 +91,205 @@ Wallet.prototype._init = function init() {
});
};
Wallet.prototype.addKey = function addKey(key) {
key = utils.toBuffer(key);
Wallet.prototype.__defineGetter__('address', function() {
return this.addresses[0];
});
var has = this.keys.some(function(k) {
return utils.isEqual(k, key);
});
Wallet.prototype._getAddressTable = function() {
var addresses = {};
var i, address;
if (has)
for (i = 0; i < this.addresses.length; i++) {
address = this.addresses[i];
if (address.type === 'scripthash')
addresses[address.getScriptAddress()] = i;
addresses[address.getKeyAddress()] = i;
}
return addresses;
};
// Faster than indexOf if we have tons of addresses
Wallet.prototype._addressIndex = function _addressIndex(address) {
var addr;
if (!(address instanceof bcoin.address))
address = bcoin.address(address);
if (address.type === 'scripthash') {
addr = address.getScriptAddress();
if (this._addressTable[addr] != null)
return this._addressTable[addr];
}
addr = address.getKeyAddress();
if (this._addressTable[addr] != null)
return this._addressTable[addr];
return -1;
};
Wallet.prototype.createChangeAddress = function createChangeAddress(address) {
if (!options)
options = {};
if (this.master) {
options.change = true;
options.priv = this.master.deriveAddress(this.addressIndex++, this.accountIndex);
}
return this.addAddress({ change: true });
};
Wallet.prototype.createNewAddress = function createNewAddress(address, options) {
if (!options)
options = {};
if (this.master)
options.priv = this.master.deriveAddress(this.addressIndex++, this.accountIndex);
return this.addAddress(options);
};
Wallet.prototype.hasAddress = function hasAddress(address) {
return this._addressIndex(address) != -1;
};
Wallet.prototype.findAddress = function findAddress(address) {
var i = this._addressIndex(address);
if (i === -1)
return;
this.keys.push(key);
return this.addresses[i];
};
this.keys = utils.sortKeys(this.keys);
Wallet.prototype.addAddress = function addAddress(address) {
var self = this;
var index;
if (!(address instanceof bcoin.address))
address = bcoin.address(address);
if (this._addressIndex(address) !== -1)
return;
if (address._wallet)
address._wallet.removeAddress(address);
address._wallet = this;
index = this.addresses.push(address) - 1;
if (address.hd && address.hd.isMaster && adress.hd.isPrivate) {
assert(!this.master);
this.master = address;
}
address.on('scriptaddress', address._onUpdate = function(old, cur) {
self._addressTable[cur] = self._addressTable[old];
delete self._addressTable[old];
});
if (address.type === 'scripthash')
this._addressTable[address.getScriptAddress()] = index;
this._addressTable[address.getKeyAddress()] = index;
this.emit('add address', address);
return address;
};
Wallet.prototype.removeAddress = function removeAddress(address) {
var i;
assert(address instanceof bcoin.address);
i = this._addressIndex(address);
if (i === -1)
return;
assert(address._wallet === this);
assert(address._onUpdate);
this.addresses.splice(i, 1);
address.removeListener('scriptaddress', address._onUpdate);
this._addressTable = this._getAddressTable();
delete address._onUpdate;
delete address._wallet;
this.emit('remove address', address);
return address;
};
Wallet.prototype.addKey = function addKey(key, i) {
return this.address.addKey(key);
};
Wallet.prototype.removeKey = function removeKey(key) {
key = utils.toBuffer(key);
var index = this.keys.map(function(key, i) {
return utils.isEqual(key, pub) ? i : null;
}).filter(function(i) {
return i !== null;
})[0];
if (index == null)
return;
this.keys.splice(index, 1);
this.keys = utils.sortKeys(this.keys);
return this.address.removeKey(key);
};
Wallet.prototype.derive = function derive() {
var options = this.options;
if (!this.hd)
throw new Error('Wallet is not HD');
options.priv = this.hd.derive.apply(this.hd, arguments);
return new Wallet(options);
this.addAddress(this.address.derive.apply(this.address, arguments));
};
Wallet.prototype.getPrivateKey = function getPrivateKey(enc) {
var priv = this.key.getPrivate();
var arr, chk;
if (!priv)
return;
priv = priv.toArray();
if (!enc)
return priv;
if (enc === 'base58')
return Wallet.toSecret(priv, this.compressed);
else if (enc === 'hex')
return utils.toHex(priv);
else
return priv;
};
Wallet.toSecret = function toSecret(priv, compressed) {
var arr, chk;
// We'll be using ncompressed public key as an address
arr = [network.prefixes.privkey];
// 0-pad key
while (arr.length + priv.length < 33)
arr.push(0);
arr = arr.concat(priv);
if (compressed)
arr.push(1);
chk = utils.checksum(arr);
return utils.toBase58(arr.concat(chk));
};
Wallet.fromSecret = function fromSecret(priv) {
var key, compressed;
key = bcoin.utils.fromBase58(priv);
assert(utils.isEqual(key.slice(-4), utils.checksum(key.slice(0, -4))));
assert.equal(key[0], network.prefixes.privkey);
key = key.slice(0, -4);
if (key.length === 34) {
assert.equal(key[33], 1);
priv = key.slice(1, -1);
compressed = true;
} else {
priv = key.slice(1);
compressed = false;
}
return {
priv: priv,
compressed: compressed
};
return this.address.getPrivateKey(enc);
};
Wallet.prototype.getScript = function getScript() {
if (this.type !== 'scripthash')
return;
if (this.redeem)
return this.redeem.slice();
if (this.subtype === 'pubkey')
return bcoin.script.encode([this.getPublicKey(), 'checksig']);
if (this.subtype === 'pubkeyhash') {
return bcoin.script.encode([
'dup',
'hash160',
this.getKeyHash(),
'equalverify',
'checksig'
]);
}
return bcoin.script.encode(
bcoin.script.createMultisig(this.keys, this.m, this.n)
);
return this.address.getScript();
};
Wallet.prototype.getScriptHash = function getScriptHash() {
if (this.type !== 'scripthash')
return;
return utils.ripesha(this.getScript());
return this.address.getScriptHash();
};
Wallet.prototype.getScriptAddress = function getScriptAddress() {
if (this.type !== 'scripthash')
return;
return Wallet.hash2addr(this.getScriptHash(), this.type);
return this.address.getScriptAddress();
};
Wallet.prototype.getPublicKey = function getPublicKey(enc) {
var pub = this.key.getPublic(this.compressed, 'array');
if (enc === 'base58')
return utils.toBase58(pub);
else if (enc === 'hex')
return utils.toHex(pub);
else
return pub;
return this.address.getPublicKey(enc);
};
Wallet.prototype.getKeyHash = function getKeyHash() {
return Wallet.key2hash(this.getPublicKey());
return this.address.getKeyHash();
};
Wallet.prototype.getKeyAddress = function getKeyAddress() {
return Wallet.hash2addr(this.getKeyHash(), 'pubkeyhash');
return this.address.getKeyAddress();
};
Wallet.prototype.getHash = function getHash() {
if (this.type === 'scripthash')
return this.getScriptHash();
return this.getKeyHash();
return this.address.getHash();
};
Wallet.prototype.getAddress = function getAddress() {
if (this.type === 'scripthash')
return this.getScriptAddress();
return this.getKeyAddress();
};
Wallet.key2hash = function key2hash(key) {
key = utils.toBuffer(key);
return utils.ripesha(key);
};
Wallet.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);
};
Wallet.__defineGetter__('prefixes', function() {
if (Wallet._prefixes) return Wallet._prefixes;
Wallet._prefixes = ['pubkeyhash', 'scripthash'].reduce(function(out, prefix) {
var ch = Wallet.hash2addr(Wallet.key2hash([]), prefix)[0];
out[ch] = prefix;
return out;
}, {});
return Wallet._prefixes;
});
Wallet.addr2hash = function addr2hash(addr, prefix) {
var chk;
if (prefix == null && typeof addr === 'string')
prefix = Wallet.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);
};
Wallet.validateAddress = function validateAddress(addr, prefix) {
if (!addr)
return false;
var p = Wallet.addr2hash(addr, prefix);
return p.length !== 0;
};
Wallet.prototype.ownOutput = function ownOutput(tx, index) {
var scripthash = this.getScriptHash();
var hash = this.getKeyHash();
var key = this.getPublicKey();
var keys = this.keys;
var outputs = tx.outputs.filter(function(output, i) {
var s = output.script;
if (index != null && index !== i)
return false;
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);
if (outputs.length === 0)
return false;
return outputs;
return this.address.getAddress();
};
Wallet.prototype.ownInput = function ownInput(tx, index) {
var scripthash = this.getScriptHash();
var hash = this.getKeyHash();
var key = this.getPublicKey();
var redeem = this.getScript();
var keys = this.keys;
this.fillPrevout(tx);
return tx.testInputs(this._addressTable, index, true);
};
var inputs = tx.inputs.filter(function(input, i) {
var s;
if (!input.prevout.tx && this.tx._all[input.prevout.hash])
input.prevout.tx = this.tx._all[input.prevout.hash];
if (index != null && index !== i)
return false;
// 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;
}
if (!input.prevout.tx)
return false;
s = input.prevout.tx.getSubscript(input.prevout.index);
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);
if (inputs.length === 0)
return false;
return inputs;
Wallet.prototype.ownOutput = function ownOutput(tx, index) {
return tx.testOutputs(this._addressTable, index, true);
};
Wallet.prototype.fill = function fill(tx, address, fee) {
var result;
if (!address)
address = this.changeAddress || this.getAddress();
address = this.createChangeAddress();
result = tx.fill(this.getUnspent(), address, fee);
@ -527,65 +311,72 @@ Wallet.prototype.fillPrevout = function fillPrevout(tx) {
Wallet.prototype.fillTX = Wallet.prototype.fillPrevout;
Wallet.prototype.scriptInputs = function scriptInputs(tx) {
var pub = this.getPublicKey();
var redeem = this.getScript();
var inputs = tx.inputs;
return this.addresses.reduce(function(total, address) {
var pub = address.getPublicKey();
var redeem = address.getScript();
tx.inputs.forEach(function(input, i) {
if (!input.prevout.tx && this.tx._all[input.prevout.hash])
input.prevout.tx = this.tx._all[input.prevout.hash];
inputs = inputs.filter(function(input, i) {
if (!input.prevout.tx && this.tx._all[input.prevout.hash])
input.prevout.tx = this.tx._all[input.prevout.hash];
if (!input.prevout.tx || !this.ownOutput(input.prevout.tx))
return;
if (!input.prevout.tx || !this.ownOutput(input.prevout.tx))
return false;
if (tx.scriptInput(i, pub, redeem))
total++;
}, this);
tx.scriptInput(i, pub, redeem);
return true;
}, this);
return inputs.length;
return total;
}, 0, this);
};
Wallet.prototype.signInputs = function signInputs(tx, type) {
var key = this.key;
var inputs = tx.inputs;
var self = this;
inputs = inputs.filter(function(input, i) {
if (!input.prevout.tx && this.tx._all[input.prevout.hash])
input.prevout.tx = this.tx._all[input.prevout.hash];
return this.addresses.reduce(function(total, address) {
if (!address.key.priv)
return total;
if (!input.prevout.tx || !this.ownOutput(input.prevout.tx))
return false;
tx.inputs.forEach(function(input, i) {
if (!input.prevout.tx && self.tx._all[input.prevout.hash])
input.prevout.tx = self.tx._all[input.prevout.hash];
tx.signInput(i, key, type);
if (!input.prevout.tx || !self.ownOutput(input.prevout.tx))
return;
return true;
}, this);
if (tx.signInput(i, address.key, type))
total++;
});
return inputs.length;
return total;
}, 0);
};
Wallet.prototype.sign = function sign(tx, type) {
var pub = this.getPublicKey();
var redeem = this.getScript();
var key = this.key;
var inputs = tx.inputs;
var self = this;
// Add signature script to each input
inputs = inputs.filter(function(input, i) {
if (!input.prevout.tx && this.tx._all[input.prevout.hash])
input.prevout.tx = this.tx._all[input.prevout.hash];
return this.addresses.reduce(function(total, address) {
var pub = address.getPublicKey();
var redeem = address.getScript();
var key = address.key;
// Filter inputs that this wallet own
if (!input.prevout.tx || !this.ownOutput(input.prevout.tx))
return false;
if (!key.priv)
return total;
tx.scriptSig(i, key, pub, redeem, type);
// Add signature script to each input
tx.inputs.forEach(function(input, i) {
if (!input.prevout.tx && self.tx._all[input.prevout.hash])
input.prevout.tx = self.tx._all[input.prevout.hash];
return true;
}, this);
// Filter inputs that this wallet own
if (!input.prevout.tx || !self.ownOutput(input.prevout.tx))
return;
return inputs.length;
if (tx.scriptSig(i, key, pub, redeem, type))
total++;
});
return total;
}, 0);
};
Wallet.prototype.addTX = function addTX(tx, block) {
@ -647,82 +438,87 @@ Wallet.prototype.toAddress = function toAddress() {
Wallet.prototype.toJSON = function toJSON(encrypt) {
return {
v: 2,
v: 3,
name: 'wallet',
network: network.type,
encrypted: encrypt ? true : false,
label: this.label,
address: this.getKeyAddress(),
scriptaddress: this.getScriptAddress(),
accountIndex: this.accountIndex,
addressIndex: this.addressIndex,
changeIndex: this.changeIndex,
addresses: this.addresses.filter(function(address) {
if (!address.hd)
return true;
if (address.change)
return false;
return true;
}).map(function(address) {
return address.toJSON(encrypt);
}),
balance: utils.toBTC(this.getBalance()),
pub: this.getPublicKey('hex'),
priv: encrypt
? encrypt(this.getPrivateKey('base58'))
: this.getPrivateKey('base58'),
xprivkey: this.hd
? (encrypt ? encrypt(this.hd.xprivkey) : this.hd.xprivkey)
: null,
type: this.type,
subtype: this.subtype,
redeem: this.redeem ? utils.toHex(this.redeem) : null,
keys: this.keys.map(utils.toBase58),
m: this.m,
n: this.n,
tx: this.tx.toJSON()
};
};
Wallet.fromJSON = function fromJSON(json, decrypt) {
var priv, pub, xprivkey, multisig, compressed, key, w;
var priv, pub, xprivkey, multisig, compressed, key, w, i;
assert.equal(json.v, 2);
assert.equal(json.v, 3);
assert.equal(json.name, 'wallet');
if (json.network)
assert.equal(json.network, network.type);
if (json.encrypted && !decrypt)
throw new Error('Cannot decrypt wallet');
if (json.priv) {
priv = json.priv;
if (json.encrypted)
priv = decrypt(priv);
key = Wallet.fromSecret(json.priv);
priv = key.priv;
compressed = key.compressed;
} else {
pub = bcoin.utils.toArray(json.pub, 'hex');
compressed = pub[0] !== 0x04;
}
if (json.xprivkey) {
xprivkey = json.xprivkey;
if (json.encrypted)
xprivkey = decrypt(xprivkey);
priv = bcoin.hd.priv(xprivkey);
}
w = new Wallet({
label: json.label,
priv: priv,
pub: pub,
compressed: compressed,
multisig: multisig,
type: json.type,
subtype: json.subtype,
redeem: json.redeem ? utils.toArray(json.redeem, 'hex') : null,
keys: json.keys.map(utils.fromBase58),
m: json.m,
n: json.n
accountIndex: json.accountIndex,
addressIndex: json.addressIndex,
changeIndex: json.changeIndex,
addresses: json.addresses.map(function(address) {
return bcoin.address.fromJSON(address, decrypt);
})
});
w.tx.fromJSON(json.tx);
// Make sure we have all the change
// addresses (we don't save them)
for (i = 0; i < w.changeIndex; i++) {
w.addKey({
change: true,
key: w.master.deriveChange(i, w.accountIndex)
});
}
return w;
};
// Compat - Legacy
Wallet.toSecret = function toSecret(priv, compressed) {
return bcoin.keypair.toSecret(priv, compressed);
};
Wallet.fromSecret = function fromSecret(priv) {
return bcoin.keypair.fromSecret(priv);
};
Wallet.key2hash = function key2hash(key) {
return bcoin.address.key2hash(key);
};
Wallet.hash2addr = function hash2addr(hash, prefix) {
return bcoin.address.hash2addr(hash, prefix);
};
Wallet.addr2hash = function addr2hash(addr, prefix) {
return bcoin.address.addr2hash(addr, prefix);
};
Wallet.validateAddress = function validateAddress(addr, prefix) {
return bcoin.address.validateAddress(addr, prefix);
};
/**
* Expose
*/