walletdb: custom addresses.

This commit is contained in:
Christopher Jeffrey 2016-08-19 01:19:09 -07:00
parent 2b3c6622d3
commit eecae63cf3
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
5 changed files with 204 additions and 46 deletions

View File

@ -3555,7 +3555,7 @@ RPC.prototype.listunspent = function listunspent(args, callback) {
txid: utils.revHex(coin.hash),
vout: coin.index,
address: address ? address.toBase58(self.network) : null,
account: ring ? ring.name : undefined,
account: ring ? ring.path.name : undefined,
redeemScript: ring && ring.script
? ring.script.toJSON()
: undefined,

View File

@ -36,13 +36,7 @@ function KeyRing(options, network) {
this.publicKey = null;
this.privateKey = null;
this.script = null;
this.wid = 0;
this.id = null;
this.name = null;
this.account = 0;
this.change = 0;
this.index = 0;
this.path = null;
this._keyHash = null;
this._keyAddress = null;
@ -351,13 +345,6 @@ KeyRing.prototype.fromAccount = function fromAccount(account, key, keys, change,
this.witness = account.witness;
this.wid = account.wid;
this.id = account.id;
this.name = account.name;
this.account = account.accountIndex;
this.change = change;
this.index = index;
return this;
};
@ -818,12 +805,12 @@ KeyRing.prototype.toJSON = function toJSON() {
key: this.publicKey.toString('hex'),
script: this.script ? this.script.toRaw().toString('hex') : null,
type: constants.scriptTypesByVal[this.type].toLowerCase(),
wid: this.wid,
id: this.id,
name: this.name,
account: this.account,
change: this.change,
index: this.index,
wid: this.path ? this.path.wid : undefined,
id: this.path ? this.path.id : undefined,
name: this.path ? this.path.name : undefined,
account: this.path ? this.path.account : undefined,
change: this.path ? this.path.change : undefined,
index: this.path ? this.path.index : undefined,
address: this.getAddress('base58'),
programAddress: this.getProgramAddress('base58')
};
@ -884,14 +871,12 @@ KeyRing.prototype.toRaw = function toRaw(writer) {
var p = new BufferWriter(writer);
var i;
p.writeU32(this.network.magic);
p.writeU8(this.witness ? 1 : 0);
p.writeVarBytes(this.publicKey);
if (this.privateKey)
p.writeVarBytes(this.privateKey);
else
p.writeVarint(0);
p.writeVarBytes(this.publicKey);
if (this.script)
p.writeVarBytes(this.script.toRaw());
@ -912,16 +897,18 @@ KeyRing.prototype.toRaw = function toRaw(writer) {
KeyRing.prototype.fromRaw = function fromRaw(data) {
var p = new BufferReader(data);
var i, count;
var i, count, key;
this.network = bcoin.network.fromMagic(p.readU32());
this.witness = p.readU8() === 1;
this.publicKey = p.readVarBytes();
this.privateKey = p.readVarBytes();
key = p.readVarBytes();
if (this.privateKey.length === 0)
this.privateKey = null;
if (key.length === 32) {
this.privateKey = key;
this.publicKey = bcoin.ec.publicKeyCreate(key, true);
} else {
this.publicKey = key;
}
this.script = p.readVarBytes();

View File

@ -628,6 +628,46 @@ Wallet.prototype.createReceive = function createReceive(account, callback) {
return this.createAddress(account, false, callback);
};
Wallet.prototype.importKey = function importKey(account, ring, callback) {
var self = this;
if (typeof ring === 'function') {
callback = ring;
ring = account;
account = 0;
}
callback = this._lockWrite(importKey, [account, ring, callback]);
if (!callback)
return;
this.getAccount(account, function(err, account) {
if (err)
return callback(err);
if (!account)
return callback(new Error('Account not found.'));
self.start();
ring.path = bcoin.walletdb.Path.fromAccount(account, ring, -1, -1);
ring.path.imported = ring.toRaw();
account.saveAddress([ring], function(err) {
if (err) {
self.drop();
return callback(err);
}
self.commit(function(err) {
if (err)
return callback(err);
callback();
});
});
}, true);
};
/**
* Create a new change address (increments receiveDepth).
* @param {(Number|String)?} account
@ -1049,7 +1089,7 @@ Wallet.prototype.deriveInputs = function deriveInputs(tx, master, callback) {
if (!account)
return next();
ring = account.deriveAddress(path.change, path.index, master);
ring = account.derivePath(path, master);
rings.push(ring);
next();
@ -1091,7 +1131,7 @@ Wallet.prototype.getKeyring = function getKeyring(address, callback) {
if (!account)
return callback();
ring = account.deriveAddress(path.change, path.index, self.master.key);
ring = account.derivePath(path, self.master.key);
callback(null, ring);
});
@ -1347,7 +1387,7 @@ Wallet.prototype.getRedeem = function getRedeem(hash, callback) {
if (!account)
return callback();
ring = account.deriveAddress(path.change, path.index);
ring = account.derivePath(path);
if (ring.program && hash.length === 20) {
if (utils.equal(hash, ring.programHash))
@ -2500,6 +2540,27 @@ Account.prototype.deriveChange = function deriveChange(index, master) {
return this.deriveAddress(true, index, master);
};
Account.prototype.derivePath = function derivePath(path, master) {
var ring, script;
// Imported key.
if (path.index === -1) {
assert(path.imported);
assert(this.n === 1);
ring = bcoin.keyring.fromRaw(path.imported);
ring.path = path;
return ring;
}
// Custom redeem script.
if (path.script)
script = new bcoin.script(path.script);
ring = this.deriveAddress(path.change, path.index, master, script);
return ring;
};
/**
* Derive an address at `index`. Do not increment depth.
* @param {Boolean} change - Whether the address on the change branch.
@ -2507,7 +2568,7 @@ Account.prototype.deriveChange = function deriveChange(index, master) {
* @returns {KeyRing}
*/
Account.prototype.deriveAddress = function deriveAddress(change, index, master) {
Account.prototype.deriveAddress = function deriveAddress(change, index, master, script) {
var keys = [];
var i, key, shared, ring;
@ -2520,19 +2581,27 @@ Account.prototype.deriveAddress = function deriveAddress(change, index, master)
key = this.accountKey.derive(change).derive(index);
}
keys.push(key.publicKey);
if (script) {
// Custom redeem script.
assert(this.n === 1);
ring = bcoin.keyring.fromScript(key.publicKey, script, this.network);
} else {
keys.push(key.publicKey);
for (i = 0; i < this.keys.length; i++) {
shared = this.keys[i];
shared = shared.derive(change).derive(index);
keys.push(shared.publicKey);
for (i = 0; i < this.keys.length; i++) {
shared = this.keys[i];
shared = shared.derive(change).derive(index);
keys.push(shared.publicKey);
}
ring = bcoin.keyring.fromAccount(this, key, keys, change, index);
}
ring = bcoin.keyring.fromAccount(this, key, keys, change, index);
if (master)
ring.privateKey = key.privateKey;
ring.path = bcoin.walletdb.Path.fromAccount(this, ring, change, index);
return ring;
};

View File

@ -946,7 +946,7 @@ WalletDB.prototype.saveAddress = function saveAddress(wid, rings, callback) {
for (i = 0; i < rings.length; i++) {
ring = rings[i];
path = Path.fromKeyRing(ring);
path = ring.path;
items.push([ring.getAddress(), path]);
@ -1594,6 +1594,9 @@ function Path() {
this.change = 0;
this.index = 0;
this.imported = null;
this.script = null;
// Currently unused.
this.type = bcoin.script.types.PUBKEYHASH;
this.version = -1;
@ -1615,8 +1618,24 @@ Path.prototype.fromRaw = function fromRaw(data) {
this.wid = p.readU32();
this.name = p.readVarString('utf8');
this.account = p.readU32();
this.change = p.readU32();
this.index = p.readU32();
switch (p.readU8()) {
case 0:
this.change = p.readU32();
this.index = p.readU32();
if (p.left() > 0)
this.script = p.readVarBytes();
break;
case 1:
this.imported = p.readVarBytes();
this.change = -1;
this.index = -1;
break;
default:
assert(false);
break;
}
this.version = p.readU8();
this.type = p.readU8();
@ -1647,8 +1666,19 @@ Path.prototype.toRaw = function toRaw(writer) {
p.writeU32(this.wid);
p.writeVarString(this.name, 'utf8');
p.writeU32(this.account);
p.writeU32(this.change);
p.writeU32(this.index);
if (this.index !== -1) {
p.writeU8(0);
p.writeU32(this.change);
p.writeU32(this.index);
if (this.script)
p.writeVarBytes(this.script);
} else {
assert(this.imported);
p.writeU8(1);
p.writeVarBytes(this.imported);
}
p.writeU8(this.version === -1 ? 0xff : this.version);
p.writeU8(this.type);
@ -1681,6 +1711,26 @@ Path.prototype.fromKeyRing = function fromKeyRing(ring) {
return this;
};
Path.prototype.fromAccount = function fromAccount(account, ring, change, index) {
this.wid = account.wid;
this.name = account.name;
this.account = account.accountIndex;
this.change = change;
this.index = index;
this.version = ring.witness ? 0 : -1;
this.type = ring.getType();
this.id = account.id;
this.hash = ring.getHash('hex');
return this;
};
Path.fromAccount = function fromAccount(account, ring, change, index) {
return new Path().fromAccount(account, ring, change, index);
};
/**
* Instantiate path from keyring.
* @param {WalletID} wid

View File

@ -1063,6 +1063,58 @@ describe('Wallet', function() {
});
});
it('should import key', function(cb) {
var key = bcoin.keyring.generate();
walletdb.create(function(err, w1) {
assert.ifError(err);
w1.importKey(key, function(err) {
assert.ifError(err);
w1.getKeyring(key.getHash('hex'), function(err, k) {
if (err)
return callback(err);
assert.equal(k.getHash('hex'), key.getHash('hex'));
// Coinbase
var t1 = bcoin.mtx()
.addOutput(key.getAddress(), 5460)
.addOutput(key.getAddress(), 5460)
.addOutput(key.getAddress(), 5460)
.addOutput(key.getAddress(), 5460);
t1.addInput(dummyInput);
t1 = t1.toTX();
walletdb.addTX(t1, function(err) {
assert.ifError(err);
w1.getTX(t1.hash('hex'), function(err, tx) {
assert.ifError(err);
assert(tx);
assert.equal(t1.hash('hex'), tx.hash('hex'));
var options = {
rate: 10000,
round: true,
outputs: [{ address: w1.getAddress(), value: 7000 }]
};
// Create new transaction
w1.createTX(options, function(err, t2) {
assert.ifError(err);
w1.sign(t2, function(err) {
assert.ifError(err);
assert(t2.verify());
assert(t2.inputs[0].prevout.hash === tx.hash('hex'));
cb();
});
});
});
});
});
});
});
});
it('should cleanup', function(cb) {
walletdb.dump(function(err, records) {
assert.ifError(err);