WIP: HD wallet

This commit is contained in:
Overtorment 2018-08-01 00:52:03 +01:00
parent 73c3281cbf
commit 0ffb127e82
5 changed files with 122 additions and 40 deletions

View file

@ -42,6 +42,15 @@ it('can create a Segwit HD (BIP49)', async function() {
assert.equal(hd._getInternalAddressByIndex(hd.next_free_change_address_index), freeChangeAddress);
});
it('Segwit HD (BIP49) can generate addressess only via ypub', async function() {
let ypub = 'ypub6WhHmKBmHNjcrUVNCa3sXduH9yxutMipDcwiKW31vWjcMbfhQHjXdyx4rqXbEtVgzdbhFJ5mZJWmfWwnP4Vjzx97admTUYKQt6b9D7jjSCp';
let hd = new HDSegwitP2SHWallet();
hd._xpub = ypub;
assert.equal('3GcKN7q7gZuZ8eHygAhHrvPa5zZbG5Q1rK', hd._getExternalAddressByIndex(0));
assert.equal('35p5LwCAE7mH2css7onyQ1VuS1jgWtQ4U3', hd._getExternalAddressByIndex(1));
assert.equal('32yn5CdevZQLk3ckuZuA8fEKBco8mEkLei', hd._getInternalAddressByIndex(0));
});
it('can generate Segwit HD (BIP49)', async () => {
let hd = new HDSegwitP2SHWallet();
let hashmap = {};
@ -117,6 +126,14 @@ it('can create a Legacy HD (BIP44)', async function() {
assert.equal(hd._getExternalAddressByIndex(hd.next_free_address_index), freeAddress);
});
it('Legacy HD (BIP44) can generate addressess based on xpub', async function() {
let xpub = 'xpub6CQdfC3v9gU86eaSn7AhUFcBVxiGhdtYxdC5Cw2vLmFkfth2KXCMmYcPpvZviA89X6DXDs4PJDk5QVL2G2xaVjv7SM4roWHr1gR4xB3Z7Ps';
let hd = new HDLegacyP2PKHWallet();
hd._xpub = xpub;
assert.equal(hd._getExternalAddressByIndex(0), '12eQ9m4sgAwTSQoNXkRABKhCXCsjm2jdVG');
assert.equal(hd._getInternalAddressByIndex(0), '1KZjqYHm7a1DjhjcdcjfQvYfF2h6PqatjX');
});
it('HD breadwallet works', async function() {
jasmine.DEFAULT_TIMEOUT_INTERVAL = 300 * 1000;
let hdBread = new HDLegacyBreadwalletWallet();

View file

@ -126,7 +126,6 @@ export class AbstractHDWallet extends LegacyWallet {
return freeAddress;
}
/**
* Derives from hierarchy, returns next free CHANGE address
* (the one that has no transactions). Looks for several,

View file

@ -56,29 +56,47 @@ export class HDLegacyP2PKHWallet extends AbstractHDWallet {
_getExternalAddressByIndex(index) {
index = index * 1; // cast to int
if (this.external_addresses_cache[index]) return this.external_addresses_cache[index]; // cache hit
let mnemonic = this.secret;
let seed = bip39.mnemonicToSeed(mnemonic);
let root = bitcoin.HDNode.fromSeedBuffer(seed);
let path = "m/44'/0'/0'/0/" + index;
let child = root.derivePath(path);
if (!this._xpub) {
let mnemonic = this.secret;
let seed = bip39.mnemonicToSeed(mnemonic);
let root = bitcoin.HDNode.fromSeedBuffer(seed);
let path = "m/44'/0'/0'/0/" + index;
let child = root.derivePath(path);
let w = new LegacyWallet();
w.setSecret(child.keyPair.toWIF());
return (this.external_addresses_cache[index] = w.getAddress());
let w = new LegacyWallet();
w.setSecret(child.keyPair.toWIF());
return (this.external_addresses_cache[index] = w.getAddress());
} else {
let node = bitcoin.HDNode.fromBase58(this._xpub);
let address = node
.derive(0)
.derive(0)
.getAddress();
return (this.external_addresses_cache[index] = address);
}
}
_getInternalAddressByIndex(index) {
index = index * 1; // cast to int
if (this.internal_addresses_cache[index]) return this.internal_addresses_cache[index]; // cache hit
let mnemonic = this.secret;
let seed = bip39.mnemonicToSeed(mnemonic);
let root = bitcoin.HDNode.fromSeedBuffer(seed);
if (!this._xpub) {
let mnemonic = this.secret;
let seed = bip39.mnemonicToSeed(mnemonic);
let root = bitcoin.HDNode.fromSeedBuffer(seed);
let path = "m/44'/0'/0'/1/" + index;
let child = root.derivePath(path);
let path = "m/44'/0'/0'/1/" + index;
let child = root.derivePath(path);
let w = new LegacyWallet();
w.setSecret(child.keyPair.toWIF());
return (this.internal_addresses_cache[index] = w.getAddress());
let w = new LegacyWallet();
w.setSecret(child.keyPair.toWIF());
return (this.internal_addresses_cache[index] = w.getAddress());
} else {
let node = bitcoin.HDNode.fromBase58(this._xpub);
let address = node
.derive(1)
.derive(0)
.getAddress();
return (this.internal_addresses_cache[index] = address);
}
}
}

View file

@ -89,34 +89,82 @@ export class HDSegwitP2SHWallet extends AbstractHDWallet {
_getExternalAddressByIndex(index) {
index = index * 1; // cast to int
if (this.external_addresses_cache[index]) return this.external_addresses_cache[index]; // cache hit
let mnemonic = this.secret;
let seed = bip39.mnemonicToSeed(mnemonic);
let root = bitcoin.HDNode.fromSeedBuffer(seed);
let path = "m/49'/0'/0'/0/" + index;
let child = root.derivePath(path);
if (!this._xpub) {
let mnemonic = this.secret;
let seed = bip39.mnemonicToSeed(mnemonic);
let root = bitcoin.HDNode.fromSeedBuffer(seed);
let path = "m/49'/0'/0'/0/" + index;
let child = root.derivePath(path);
let keyhash = bitcoin.crypto.hash160(child.getPublicKeyBuffer());
let scriptSig = bitcoin.script.witnessPubKeyHash.output.encode(keyhash);
let addressBytes = bitcoin.crypto.hash160(scriptSig);
let outputScript = bitcoin.script.scriptHash.output.encode(addressBytes);
return (this.external_addresses_cache[index] = bitcoin.address.fromOutputScript(outputScript, bitcoin.networks.bitcoin));
let keyhash = bitcoin.crypto.hash160(child.getPublicKeyBuffer());
let scriptSig = bitcoin.script.witnessPubKeyHash.output.encode(keyhash);
let addressBytes = bitcoin.crypto.hash160(scriptSig);
let outputScript = bitcoin.script.scriptHash.output.encode(addressBytes);
return (this.external_addresses_cache[index] = bitcoin.address.fromOutputScript(outputScript, bitcoin.networks.bitcoin));
} else {
let b58 = require('bs58check');
// eslint-disable-next-line
function ypubToXpub(ypub) {
var data = b58.decode(ypub);
data = data.slice(4);
data = Buffer.concat([Buffer.from('0488b21e', 'hex'), data]);
return b58.encode(data);
}
// eslint-disable-next-line
function nodeToP2shSegwitAddress(hdNode) {
let pubkeyBuf = hdNode.keyPair.getPublicKeyBuffer();
let hash = bitcoin.crypto.hash160(pubkeyBuf);
let redeemScript = bitcoin.script.witnessPubKeyHash.output.encode(hash);
let hash2 = bitcoin.crypto.hash160(redeemScript);
let scriptPubkey = bitcoin.script.scriptHash.output.encode(hash2);
return bitcoin.address.fromOutputScript(scriptPubkey);
}
let xpub = ypubToXpub(this._xpub);
let hdNode = bitcoin.HDNode.fromBase58(xpub);
let address = nodeToP2shSegwitAddress(hdNode.derive(0).derive(index));
return (this.external_addresses_cache[index] = address);
}
}
_getInternalAddressByIndex(index) {
index = index * 1; // cast to int
if (this.internal_addresses_cache[index]) return this.internal_addresses_cache[index]; // cache hit
let mnemonic = this.secret;
let seed = bip39.mnemonicToSeed(mnemonic);
let root = bitcoin.HDNode.fromSeedBuffer(seed);
if (!this._xpub) {
let mnemonic = this.secret;
let seed = bip39.mnemonicToSeed(mnemonic);
let root = bitcoin.HDNode.fromSeedBuffer(seed);
let path = "m/49'/0'/0'/1/" + index;
let child = root.derivePath(path);
let path = "m/49'/0'/0'/1/" + index;
let child = root.derivePath(path);
let keyhash = bitcoin.crypto.hash160(child.getPublicKeyBuffer());
let scriptSig = bitcoin.script.witnessPubKeyHash.output.encode(keyhash);
let addressBytes = bitcoin.crypto.hash160(scriptSig);
let outputScript = bitcoin.script.scriptHash.output.encode(addressBytes);
return (this.internal_addresses_cache[index] = bitcoin.address.fromOutputScript(outputScript, bitcoin.networks.bitcoin));
let keyhash = bitcoin.crypto.hash160(child.getPublicKeyBuffer());
let scriptSig = bitcoin.script.witnessPubKeyHash.output.encode(keyhash);
let addressBytes = bitcoin.crypto.hash160(scriptSig);
let outputScript = bitcoin.script.scriptHash.output.encode(addressBytes);
return (this.internal_addresses_cache[index] = bitcoin.address.fromOutputScript(outputScript, bitcoin.networks.bitcoin));
} else {
let b58 = require('bs58check');
// eslint-disable-next-line
function ypubToXpub(ypub) {
var data = b58.decode(ypub);
data = data.slice(4);
data = Buffer.concat([Buffer.from('0488b21e', 'hex'), data]);
return b58.encode(data);
}
// eslint-disable-next-line
function nodeToP2shSegwitAddress(hdNode) {
let pubkeyBuf = hdNode.keyPair.getPublicKeyBuffer();
let hash = bitcoin.crypto.hash160(pubkeyBuf);
let redeemScript = bitcoin.script.witnessPubKeyHash.output.encode(hash);
let hash2 = bitcoin.crypto.hash160(redeemScript);
let scriptPubkey = bitcoin.script.scriptHash.output.encode(hash2);
return bitcoin.address.fromOutputScript(scriptPubkey);
}
let xpub = ypubToXpub(this._xpub);
let hdNode = bitcoin.HDNode.fromBase58(xpub);
let address = nodeToP2shSegwitAddress(hdNode.derive(1).derive(index));
return (this.internal_addresses_cache[index] = address);
}
}
/**
@ -157,8 +205,8 @@ export class HDSegwitP2SHWallet extends AbstractHDWallet {
}
let addresses = this.usedAddresses.join('|');
addresses += '|' + this._getExternalAddressByIndex(this.next_free_address_index)
addresses += '|' + this._getInternalAddressByIndex(this.next_free_change_address_index)
addresses += '|' + this._getExternalAddressByIndex(this.next_free_address_index);
addresses += '|' + this._getInternalAddressByIndex(this.next_free_change_address_index);
const api = new Frisbee({ baseURI: 'https://blockchain.info' });
this.transactions = [];

View file

@ -256,7 +256,7 @@ export class LegacyWallet extends AbstractWallet {
if (tx.block_height && tx.block_height === -1) {
// unconfirmed
console.log(tx);
if ((+new Date(tx.received)) < ((+new Date()) - 3600*24*1000)) {
if (+new Date(tx.received) < +new Date() - 3600 * 24 * 1000) {
// nop, too old unconfirmed tx - skipping it
} else {
txsUnconf.push(tx);