createTX. hd fixes.

This commit is contained in:
Christopher Jeffrey 2016-02-02 02:47:21 -08:00
parent 42e17bc0f2
commit 156601e40a
8 changed files with 443 additions and 202 deletions

View File

@ -580,6 +580,26 @@ pool.on('fork', function(tip1, tip2) {
});
```
### Transaction Building
Transaction building happens in 4 stages:
1. __Outputs__: Adding of the outputs (where we want to send the coins).
2. __Filling__: Filling of the unspent outputs as the inputs, calculation of
fee, and potential addition of a change address.
3. __Scripting__: Compilation of the of the input scripts (minus the
signatures). This will fill `n` empty signature slots (`OP_0`).
- __Potential recalculation of the fee__: Now that the redeem script is
available to the primitive TX object, it can likely calculate a lower fee.
Add value to the change address if the fee is lower than we thought.
4. __Signing__: Signing of the inputs. If this is a multisig transaction, we
have to wait for other signers to sign it before it is finalized.
- __Finalization__: Once the final signer signs the transaction, empty
signature slots will be removed as they are no longer necessary.
Once these steps are completed, the transaction should verify and is able to be
broadcast to the pool.
## API Documentation
### Objects

View File

@ -109,6 +109,7 @@ Address.prototype.setRedeem = function setRedeem(redeem) {
Address.prototype.addKey = function addKey(key) {
var old = this.getScriptAddress();
var cur;
key = utils.toBuffer(key);
@ -122,11 +123,16 @@ Address.prototype.addKey = function addKey(key) {
this.keys.push(key);
this.keys = utils.sortKeys(this.keys);
this.emit('scriptaddress', old, this.getScriptAddress());
cur = this.getScriptAddress();
if (old !== cur)
this.emit('scriptaddress', old, cur);
};
Address.prototype.removeKey = function removeKey(key) {
var old = this.getScriptAddress();
var cur;
key = utils.toBuffer(key);
@ -142,7 +148,11 @@ Address.prototype.removeKey = function removeKey(key) {
this.keys.splice(index, 1);
this.keys = utils.sortKeys(this.keys);
this.emit('scriptaddress', old, this.getScriptAddress());
cur = this.getScriptAddress();
if (old !== cur)
this.emit('scriptaddress', old, this.getScriptAddress());
};
Address.prototype.getPrivateKey = function getPrivateKey(enc) {

View File

@ -168,153 +168,178 @@ function HDPrivateKey(options) {
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) {
HDPrivateKey.prototype.scan = function scan(options, txByAddress, callback) {
var keys = [];
var purpose, coinType, root;
assert(this.isMaster);
if (!callback) {
callback = txByAddress;
txByAddress = options;
options = null;
}
if (!options)
options = {};
purpose = options.purpose;
coinType = options.coinType;
if (purpose == null)
purpose = 44;
if (coinType == null)
coinType = network.type === 'main' ? 0 : 1;
assert(utils.isFinite(purpose));
assert(utils.isFinite(coinType));
// 0. get the root node
var root = master
.derive(44, true)
.derive(network.type === 'main' ? 0 : 1, true);
root = this
.derive(purpose, true)
.derive(coinType, true);
return (function scanner(accountIndex) {
var account, chain, addressIndex, address, addr, i;
var addressIndex = 0;
var total = 0;
var gap = 0;
// 1. derive the first account's node (index = 0)
account = root.derive(accountIndex, true);
var account = root.derive(accountIndex, true);
// 2. derive the external chain node of this account
chain = account.derive(0);
var 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(
return (function next() {
var address = chain.derive(addressIndex++);
var 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;
}
}
}
return txByAddress(addr, function(err, txs) {
var result;
// 5. if there are some transactions, increase
// the account index and go to step 1
return scanner(accountIndex + 1);
if (err)
return callback(err);
if (txs) {
if (typeof txs === 'boolean')
result = txs;
else if (Array.isArray(txs))
result = txs.length > 0;
else
result = false;
}
if (result) {
keys.push(address);
total++;
gap = 0;
return next();
}
if (++gap < 20)
return next();
// 4. if no transactions are found on the
// external chain, stop discovery
if (total === 0)
return callback(null, 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() {
HDPrivateKey.prototype.deriveBIP44 = function deriveBIP44(options) {
var purpose = options.purpose;
var coinType = options.coinType;
var accountIndex = options.accountIndex;
var chain = options.chain;
var addressIndex = options.addressIndex;
var child;
assert(this.isMaster);
if (purpose == null)
purpose = 44;
if (options.purpose == null)
options.purpose = 44;
if (coinType == null)
coinType = network.type === 'main' ? 0 : 1;
if (options.coinType == null)
options.coinType = network.type === 'main' ? 0 : 1;
if (chain == null)
chain = options.change ? 1 : 0;
assert(utils.isFinite(purpose));
assert(utils.isFinite(coinType));
assert(utils.isFinite(accountIndex));
assert(utils.isFinite(chain));
assert(utils.isFinite(addressIndex));
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;
.derive(purpose, true)
.derive(coinType, true)
.derive(accountIndex, true)
.derive(chain)
.derive(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,
return this.deriveBIP44({
accountIndex: accountIndex,
chain: 0,
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.deriveChange = function deriveChange(accountIndex, addressIndex) {
return this.deriveBIP44({
accountIndex: accountIndex,
chain: 1,
addressIndex: addressIndex
});
};
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.deriveAddress = function deriveAddress(accountIndex, addressIndex) {
return this.deriveBIP44({
accountIndex: accountIndex,
chain: 0,
addressIndex: addressIndex
});
};
HDPrivateKey.prototype.toPath = function toPath(data) {
assert(!this.isMaster);
return HDPrivateKey.getPath(
this.accountIndex, this.addressIndex, this.isChange);
};
HDPrivateKey.getPath = function getPath(options) {
var purpose, coinType, accountIndex, chain, addressIndex;
HDPrivateKey.getPath = function toPath(accountIndex, addressIndex, isChange) {
return 'm/44\'/'
+ (network.type === 'main' ? '0' : '1') + '\'' + '/'
if (!options)
options = {};
purpose = options.purpose;
coinType = options.coinType;
accountIndex = options.accountIndex;
chain = options.chain;
addressIndex = options.addressIndex;
if (purpose == null)
purpose = 44;
if (coinType == null)
coinType = network.type === 'main' ? 0 : 1;
if (chain == null)
chain = options.change ? 1 : 0;
return 'm/' + purpose + '\'/'
+ coinType + '\'/'
+ accountIndex + '\'/'
+ (isChange ? '1' : '0') + '/'
+ chain + '/'
+ addressIndex;
};
@ -638,40 +663,13 @@ function HDPublicKey(options) {
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.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')

View File

@ -264,6 +264,23 @@ Input.prototype.getID = function getID() {
return '[' + this.type + ':' + hash.slice(0, 7) + ']';
};
Input.prototype.getLocktime = function getLocktime() {
var output, redeem, lock, type;
assert(this.prevout.tx);
output = this.prevout.tx.outputs[this.prevout.index];
redeem = output.script;
if (bcoin.script.isScripthash(redeem))
redeem = bcoin.script.getRedeem(this.script);
if (redeem[1] !== 'checklocktimeverify')
return;
return bcoin.script.getLocktime(redeem);
};
Input.prototype.inspect = function inspect() {
var output = this.output
? this.output.inspect()

View File

@ -1254,22 +1254,27 @@ script._lockTime = function _lockTime(s) {
return;
for (i = 0; i < s.length; i++) {
if (Array.isArray(s[i]) && s[i + 1] === 'checklocktimeverify')
if (utils.isBuffer(s[i]) && s[i + 1] === 'checklocktimeverify')
return s[i];
}
};
script.isLockTime = function isLockTime(s) {
return script._lockTime(s) != null;
return !!script._lockTime(s);
};
script.getLockTime = function getLockTime(s) {
var lockTime = script._lockTime(s);
if (!lockTime)
return 0;
return;
return script.num(lockTime, true);
lockTime = script.num(lockTime, true);
if (lockTime < constants.locktimeThreshold)
return { type: 'height', value: lockTime };
return { type: 'time', value: lockTime };
};
script.getInputData = function getData(s, prev) {

View File

@ -548,6 +548,82 @@ TX.prototype.scriptSig = function scriptSig(index, key, pub, redeem, type) {
return input.script;
};
TX.prototype.isSigned = function isSigned(index, required) {
var i, input, s, len, m, j, total;
for (i = 0; i < this.inputs.length; i++) {
input = this.inputs[i];
if (index != null && i !== index)
continue;
// We can't check for signatures unless
// we have the previous output.
assert(input.prevout.tx);
// Get the prevout's subscript
s = input.prevout.tx.getSubscript(input.prevout.index);
// Script length, needed for multisig
len = input.script.length;
// Grab the redeem script if P2SH
if (bcoin.script.isScripthash(s)) {
s = bcoin.script.getRedeem(input.script);
// Decrement `len` to avoid the redeem script
len--;
}
// Check for signatures.
// P2PK
if (bcoin.script.isPubkey(s)) {
if (!bcoin.script.isSignature(input.script[0]))
return false;
continue;
}
// P2PK
if (bcoin.script.isPubkeyhash(s)) {
if (!bcoin.script.isSignature(input.script[0]))
return false;
continue;
}
// Multisig
if (bcoin.script.isMultisig(s)) {
// Grab `m` value (number of required sigs).
m = s[0];
if (Array.isArray(m))
m = m[0] || 0;
// Ensure all members are signatures.
for (j = 1; j < len; j++) {
if (!bcoin.script.isSignature(input.script[j]))
return false;
}
// Ensure we have the correct number
// of required signatures.
if (len - 1 !== m)
return false;
continue;
}
// Unknown
total = 0;
for (j = 0; j < input.script.length; j++) {
if (bcoin.script.isSignatureEncoding(input.script[j]))
total++;
}
if (total !== required)
return false;
}
return true;
};
TX.prototype.addOutput = function addOutput(obj, value) {
var options, output;
@ -1170,6 +1246,34 @@ TX.prototype.getFunds = function getFunds(side) {
// Legacy
TX.prototype.funds = TX.prototype.getFunds;
TX.prototype.getTargetTime = function getTargetTime() {
var bestValue = 0;
var i, lockTime, bestType;
for (i = 0; i < this.inputs.length; i++) {
lockTime = this.inputs[i].getLocktime();
if (!lockTime)
continue;
// Incompatible types
if (bestType && bestType !== lockTime.type)
return;
bestType = lockTime.type;
if (lockTime.value < bestValue)
continue;
bestValue = lockTime.value;
}
return {
type: bestType || 'height',
value: bestValue
};
};
TX.prototype.testInputs = function testInputs(addressTable, index, collect) {
var inputs = [];
var i, input, prev, data, j;

View File

@ -19,6 +19,8 @@ var network = bcoin.protocol.network;
*/
function Wallet(options) {
var i;
if (!(this instanceof Wallet))
return new Wallet(options);
@ -29,21 +31,44 @@ function Wallet(options) {
this.options = options;
this.addresses = [];
this.master = null;
this.master = options.master || null;
this._addressTable = {};
this._labelMap = {};
this.accountIndex = options.accountIndex || 0;
this.addressIndex = options.addressIndex || 0;
this.changeIndex = options.changeIndex || 0;
if (options.addresses) {
if (options.addresses && options.addresses.length > 0) {
options.addresses.forEach(function(address) {
this.addAddress(address);
}, this);
} else {
this.addAddress(options);
this.createNewAddress(options);
}
// Create a non-master account address if we don't have one.
// Might not be necessary now.
if (this.master) {
for (i = 0; i < this.addresses.length; i++) {
if (this.addresses[i].key.hd && !this.addresses[i].change)
break;
}
if (i === this.addresses.length)
this.createNewAddress(options);
}
// Find the last change address if there is one.
for (i = this.addresses.length - 1; i >= 0; i--) {
if (this.addresses[i].change)
break;
}
if (i === -1)
this.changeAddress = this.createChangeAddress();
else
this.changeAddress = this.addresses[i];
this.storage = options.storage;
this.label = options.label || '';
this.loaded = false;
@ -77,6 +102,10 @@ Wallet.prototype._init = function init() {
});
this.tx.on('tx', function(tx) {
// TX using this change address was
// confirmed. Allocate a new change address.
if (self.changeAddress.ownOutput(tx))
self.changeAddress = self.createChangeAddress();
self.emit('tx', tx);
});
@ -129,25 +158,29 @@ Wallet.prototype._addressIndex = function _addressIndex(address) {
return -1;
};
Wallet.prototype.createChangeAddress = function createChangeAddress(address) {
Wallet.prototype.createChangeAddress = function createChangeAddress(options) {
if (!options)
options = {};
options.change = true;
if (this.master) {
options.priv =
this.master.key.hd.deriveChange(this.accountIndex, this.changeIndex++);
}
return this.addAddress(options);
};
Wallet.prototype.createNewAddress = function createNewAddress(options) {
if (!options)
options = {};
if (this.master) {
options.change = true;
options.priv = this.master.deriveAddress(this.addressIndex++, this.accountIndex);
options.priv =
this.master.key.hd.deriveAddress(this.accountIndex, this.addressIndex++);
}
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);
};
@ -174,6 +207,12 @@ Wallet.prototype.addAddress = function addAddress(address) {
if (this._addressIndex(address) !== -1)
return;
if (address.key.hd && address.key.hd.isMaster) {
assert(!this.master);
this.master = address;
return;
}
if (address._wallet)
address._wallet.removeAddress(address);
@ -181,14 +220,10 @@ Wallet.prototype.addAddress = function addAddress(address) {
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];
self.emit('add address', address);
});
if (address.type === 'scripthash')
@ -236,10 +271,6 @@ Wallet.prototype.removeKey = function removeKey(key) {
return this.address.removeKey(key);
};
Wallet.prototype.derive = function derive() {
this.addAddress(this.address.derive.apply(this.address, arguments));
};
Wallet.prototype.getPrivateKey = function getPrivateKey(enc) {
return this.address.getPrivateKey(enc);
};
@ -286,12 +317,33 @@ Wallet.prototype.ownOutput = function ownOutput(tx, index) {
};
Wallet.prototype.fill = function fill(tx, address, fee) {
var result;
var unspent, items, result;
if (!address)
address = this.createChangeAddress();
address = this.changeAddress.getKeyAddress();
result = tx.fill(this.getUnspent(), address, fee);
unspent = this.getUnspent();
// Avoid multisig if first address is not multisig
items = unspent.filter(function(item) {
var output = item.tx.outputs[item.index];
if (bcoin.script.isScripthash(output.script)) {
if (this.address.type === 'scripthash')
return true;
return false;
}
if (bcoin.script.isMultisig(output.script)) {
if (this.address.n > 1)
return true;
return false;
}
return true;
}, this);
if (tx.getInputs(unspent, address, fee).inputs)
unspent = items;
result = tx.fill(unspent, address, fee);
if (!result.inputs)
return false;
@ -310,23 +362,62 @@ Wallet.prototype.fillPrevout = function fillPrevout(tx) {
// Legacy
Wallet.prototype.fillTX = Wallet.prototype.fillPrevout;
Wallet.prototype.createTX = function createTX(outputs, fee) {
var tx = bcoin.tx();
var target;
if (!Array.isArray(outputs))
outputs = [outputs];
outputs.forEach(function(output) {
tx.addOutput(output);
});
if (!this.fill(tx, null, fee))
return;
// Find the necessary locktime if there is
// a checklocktimeverify script in the unspents.
target = tx.getTargetTime();
// No target value. The unspents have an
// incompatible locktime type.
if (!target)
return;
if (target.value > 0)
tx.setLockTime(target.value);
else
tx.avoidFeeSnipping();
this.sign(tx);
return tx;
};
Wallet.prototype.scriptInputs = function scriptInputs(tx) {
var self = this;
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];
if (!input.prevout.tx || !this.ownOutput(input.prevout.tx))
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];
if (!input.prevout.tx)
return;
if (!address.ownOutput(input.prevout.tx, input.prevout.index))
return;
if (tx.scriptInput(i, pub, redeem))
total++;
}, this);
});
return total;
}, 0, this);
}, 0);
};
Wallet.prototype.signInputs = function signInputs(tx, type) {
@ -340,7 +431,10 @@ Wallet.prototype.signInputs = function signInputs(tx, type) {
if (!input.prevout.tx && self.tx._all[input.prevout.hash])
input.prevout.tx = self.tx._all[input.prevout.hash];
if (!input.prevout.tx || !self.ownOutput(input.prevout.tx))
if (!input.prevout.tx)
return;
if (!address.ownOutput(input.prevout.tx, input.prevout.index))
return;
if (tx.signInput(i, address.key, type))
@ -368,7 +462,10 @@ Wallet.prototype.sign = function sign(tx, type) {
input.prevout.tx = self.tx._all[input.prevout.hash];
// Filter inputs that this wallet own
if (!input.prevout.tx || !self.ownOutput(input.prevout.tx))
if (!input.prevout.tx)
return;
if (!address.ownOutput(input.prevout.tx, input.prevout.index))
return;
if (tx.scriptSig(i, key, pub, redeem, type))
@ -445,8 +542,9 @@ Wallet.prototype.toJSON = function toJSON(encrypt) {
accountIndex: this.accountIndex,
addressIndex: this.addressIndex,
changeIndex: this.changeIndex,
master: this.master ? this.master.toJSON(encrypt) : null,
addresses: this.addresses.filter(function(address) {
if (!address.hd)
if (!address.key.hd)
return true;
if (address.change)
@ -475,6 +573,9 @@ Wallet.fromJSON = function fromJSON(json, decrypt) {
accountIndex: json.accountIndex,
addressIndex: json.addressIndex,
changeIndex: json.changeIndex,
master: json.master
? bcoin.address.fromJSON(json.master, decrypt)
: null,
addresses: json.addresses.map(function(address) {
return bcoin.address.fromJSON(address, decrypt);
})
@ -483,12 +584,14 @@ Wallet.fromJSON = function fromJSON(json, 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)
});
// addresses (we don't save them).
if (w.master) {
for (i = 0; i < w.changeIndex; i++) {
w.addAddress({
change: true,
key: w.master.key.hd.deriveChange(w.accountIndex, i)
});
}
}
return w;

View File

@ -263,55 +263,39 @@ describe('Wallet', function() {
});
it('should verify 2-of-3 p2sh tx', function(cb) {
var hd = bcoin.hd.priv();
var hd1 = hd.derive(0);
var hd2 = hd.derive(1);
var hd3 = hd.derive(2);
// Generate 3 key pairs
var key1 = bcoin.ecdsa.genKeyPair();
var key2 = bcoin.ecdsa.genKeyPair();
var key3 = bcoin.ecdsa.genKeyPair();
// var key1 = hd1;
// var key2 = hd2;
// var key3 = hd3;
// Grab the 3 pubkeys
var pub1 = key1.getPublic(true, 'array');
var pub2 = key2.getPublic(true, 'array');
var pub3 = key3.getPublic(true, 'array');
// Create 3 2-of-3 wallets with our pubkeys as "shared keys"
var w1 = bcoin.wallet({
key: key1,
multisig: {
type: 'scripthash',
keys: [pub2, pub3],
m: 2,
n: 3
}
});
var w2 = bcoin.wallet({
key: key2,
multisig: {
type: 'scripthash',
keys: [pub1, pub3],
m: 2,
n: 3
}
});
var w3 = bcoin.wallet({
key: key3,
hd: true,
multisig: {
type: 'scripthash',
keys: [pub1, pub2],
m: 2,
n: 3
}
});
var receive = bcoin.wallet();
w1.addKey(w2.getPublicKey());
w1.addKey(w3.getPublicKey());
w2.addKey(w1.getPublicKey());
w2.addKey(w3.getPublicKey());
w3.addKey(w1.getPublicKey());
w3.addKey(w2.getPublicKey());
// Our p2sh address
var addr = w1.getAddress();
assert.equal(w1.getAddress(), addr);