BlueWallet/class/wallets/abstract-hd-wallet.ts

315 lines
9.4 KiB
TypeScript
Raw Normal View History

2018-07-21 13:52:54 +01:00
import { LegacyWallet } from './legacy-wallet';
import * as bip39 from 'bip39';
import { BIP32Interface } from 'bip32';
2022-04-17 22:22:25 +02:00
import * as bip39custom from '../../blue_modules/bip39';
import BlueElectrum from '../../blue_modules/BlueElectrum';
import { Transaction } from './types';
2018-07-21 13:52:54 +01:00
type AbstractHDWalletStatics = {
derivationPath?: string;
};
2020-03-09 18:44:42 +00:00
/**
* @deprecated
*/
2018-07-21 13:52:54 +01:00
export class AbstractHDWallet extends LegacyWallet {
static type = 'abstract';
static typeReadable = 'abstract';
next_free_address_index: number; // eslint-disable-line camelcase
next_free_change_address_index: number; // eslint-disable-line camelcase
internal_addresses_cache: Record<number, string>; // eslint-disable-line camelcase
external_addresses_cache: Record<number, string>; // eslint-disable-line camelcase
_xpub: string;
usedAddresses: string[];
_address_to_wif_cache: Record<string, string>; // eslint-disable-line camelcase
gap_limit: number; // eslint-disable-line camelcase
passphrase?: string;
_node0?: BIP32Interface;
_node1?: BIP32Interface;
2018-07-21 13:52:54 +01:00
constructor() {
super();
2022-01-26 12:50:41 -05:00
const Constructor = this.constructor as unknown as AbstractHDWalletStatics;
2018-07-21 13:52:54 +01:00
this.next_free_address_index = 0;
this.next_free_change_address_index = 0;
this.internal_addresses_cache = {}; // index => address
this.external_addresses_cache = {}; // index => address
this._xpub = ''; // cache
2018-08-08 01:05:34 +01:00
this.usedAddresses = [];
this._address_to_wif_cache = {};
this.gap_limit = 20;
this._derivationPath = Constructor.derivationPath;
2018-07-21 13:52:54 +01:00
}
getNextFreeAddressIndex(): number {
return this.next_free_address_index;
}
getNextFreeChangeAddressIndex(): number {
return this.next_free_change_address_index;
}
prepareForSerialization(): void {
// deleting structures that cant be serialized
delete this._node0;
delete this._node1;
}
generate(): Promise<void> {
throw new Error('Not implemented');
}
allowSend(): boolean {
2018-08-08 01:05:34 +01:00
return false;
2018-07-21 13:52:54 +01:00
}
getTransactions(): Transaction[] {
throw new Error('Not implemented');
}
/**
* @return {Buffer} wallet seed
*/
_getSeed(): Buffer {
const mnemonic = this.secret;
2021-05-14 19:07:33 +03:00
const passphrase = this.passphrase;
return bip39.mnemonicToSeedSync(mnemonic, passphrase);
}
setSecret(newSecret: string): this {
this.secret = newSecret.trim().toLowerCase();
2018-07-21 13:52:54 +01:00
this.secret = this.secret.replace(/[^a-zA-Z0-9]/g, ' ').replace(/\s+/g, ' ');
// Try to match words to the default bip39 wordlist and complete partial words
const wordlist = bip39.wordlists[bip39.getDefaultWordlist()];
const lookupMap = wordlist.reduce((map, word) => {
const prefix3 = word.substr(0, 3);
const prefix4 = word.substr(0, 4);
map.set(prefix3, !map.has(prefix3) ? word : false);
map.set(prefix4, !map.has(prefix4) ? word : false);
return map;
}, new Map<string, string | false>());
this.secret = this.secret
.split(' ')
.map(word => lookupMap.get(word) || word)
.join(' ');
2018-07-21 13:52:54 +01:00
return this;
}
setPassphrase(passphrase: string): void {
2021-05-14 19:07:33 +03:00
this.passphrase = passphrase;
}
getPassphrase(): string | undefined {
2021-05-14 19:07:33 +03:00
return this.passphrase;
}
/**
* @return {Boolean} is mnemonic in `this.secret` valid
*/
validateMnemonic(): boolean {
2022-04-17 22:22:25 +02:00
return bip39custom.validateMnemonic(this.secret);
2018-07-21 13:52:54 +01:00
}
/**
* Derives from hierarchy, returns next free address
* (the one that has no transactions). Looks for several,
* gives up if none found, and returns the used one
*
* @return {Promise.<string>}
*/
async getAddressAsync(): Promise<string> {
// looking for free external address
let freeAddress = '';
let c;
for (c = 0; c < this.gap_limit + 1; c++) {
if (this.next_free_address_index + c < 0) continue;
const address = this._getExternalAddressByIndex(this.next_free_address_index + c);
2018-07-31 23:37:35 +01:00
this.external_addresses_cache[this.next_free_address_index + c] = address; // updating cache just for any case
2019-05-03 14:24:08 +01:00
let txs = [];
try {
txs = await BlueElectrum.getTransactionsByAddress(address);
2022-01-27 11:20:25 -05:00
} catch (Err: any) {
2019-05-03 14:24:08 +01:00
console.warn('BlueElectrum.getTransactionsByAddress()', Err.message);
}
2019-03-31 00:20:18 +00:00
if (txs.length === 0) {
// found free address
2019-03-31 00:20:18 +00:00
freeAddress = address;
this.next_free_address_index += c; // now points to _this one_
break;
}
}
if (!freeAddress) {
// could not find in cycle above, give up
freeAddress = this._getExternalAddressByIndex(this.next_free_address_index + c); // we didnt check this one, maybe its free
this.next_free_address_index += c; // now points to this one
}
this._address = freeAddress;
return freeAddress;
}
2018-07-31 23:37:35 +01:00
/**
* Derives from hierarchy, returns next free CHANGE address
* (the one that has no transactions). Looks for several,
* gives up if none found, and returns the used one
*
* @return {Promise.<string>}
*/
async getChangeAddressAsync(): Promise<string> {
2018-07-31 23:37:35 +01:00
// looking for free internal address
let freeAddress = '';
let c;
for (c = 0; c < this.gap_limit + 1; c++) {
2018-07-31 23:37:35 +01:00
if (this.next_free_change_address_index + c < 0) continue;
const address = this._getInternalAddressByIndex(this.next_free_change_address_index + c);
2018-07-31 23:37:35 +01:00
this.internal_addresses_cache[this.next_free_change_address_index + c] = address; // updating cache just for any case
2019-05-03 14:24:08 +01:00
let txs = [];
try {
txs = await BlueElectrum.getTransactionsByAddress(address);
2022-01-27 11:20:25 -05:00
} catch (Err: any) {
2019-05-03 14:24:08 +01:00
console.warn('BlueElectrum.getTransactionsByAddress()', Err.message);
}
2019-03-31 00:20:18 +00:00
if (txs.length === 0) {
2018-07-31 23:37:35 +01:00
// found free address
2019-03-31 00:20:18 +00:00
freeAddress = address;
2018-07-31 23:37:35 +01:00
this.next_free_change_address_index += c; // now points to _this one_
break;
}
}
if (!freeAddress) {
// could not find in cycle above, give up
freeAddress = this._getInternalAddressByIndex(this.next_free_change_address_index + c); // we didnt check this one, maybe its free
this.next_free_change_address_index += c; // now points to this one
2018-07-31 23:37:35 +01:00
}
this._address = freeAddress;
2018-07-31 23:37:35 +01:00
return freeAddress;
}
/**
* Should not be used in HD wallets
*
* @deprecated
* @return {string}
*/
getAddress(): string | false {
return this._address;
2018-07-21 13:52:54 +01:00
}
_getExternalWIFByIndex(index: number): string | false {
2018-07-21 13:52:54 +01:00
throw new Error('Not implemented');
}
_getInternalWIFByIndex(index: number): string | false {
2018-07-21 13:52:54 +01:00
throw new Error('Not implemented');
}
_getExternalAddressByIndex(index: number): string {
2018-07-21 13:52:54 +01:00
throw new Error('Not implemented');
}
_getInternalAddressByIndex(index: number): string {
2018-07-21 13:52:54 +01:00
throw new Error('Not implemented');
}
getXpub(): string {
2018-07-21 13:52:54 +01:00
throw new Error('Not implemented');
}
/**
* Async function to fetch all transactions. Use getter to get actual txs.
* Also, sets internals:
* `this.internal_addresses_cache`
* `this.external_addresses_cache`
*
* @returns {Promise<void>}
*/
async fetchTransactions(): Promise<void> {
throw new Error('not implemented');
2018-07-21 13:52:54 +01:00
}
2018-08-08 01:05:34 +01:00
/**
* Given that `address` is in our HD hierarchy, try to find
* corresponding WIF
*
* @param address {String} In our HD hierarchy
* @return {String} WIF if found
*/
_getWifForAddress(address: string): string {
2018-08-08 01:05:34 +01:00
if (this._address_to_wif_cache[address]) return this._address_to_wif_cache[address]; // cache hit
// fast approach, first lets iterate over all addressess we have in cache
for (const indexStr of Object.keys(this.internal_addresses_cache)) {
const index = parseInt(indexStr);
2018-08-08 01:05:34 +01:00
if (this._getInternalAddressByIndex(index) === address) {
return (this._address_to_wif_cache[address] = <string>this._getInternalWIFByIndex(index));
2018-08-08 01:05:34 +01:00
}
}
for (const indexStr of Object.keys(this.external_addresses_cache)) {
const index = parseInt(indexStr);
2018-08-08 01:05:34 +01:00
if (this._getExternalAddressByIndex(index) === address) {
return (this._address_to_wif_cache[address] = <string>this._getExternalWIFByIndex(index));
2018-08-08 01:05:34 +01:00
}
}
2018-12-23 00:43:50 -05:00
// no luck - lets iterate over all addresses we have up to first unused address index
2019-05-28 21:30:03 +01:00
for (let c = 0; c <= this.next_free_change_address_index + this.gap_limit; c++) {
const possibleAddress = this._getInternalAddressByIndex(c);
2018-08-08 01:05:34 +01:00
if (possibleAddress === address) {
return (this._address_to_wif_cache[address] = <string>this._getInternalWIFByIndex(c));
2018-08-08 01:05:34 +01:00
}
}
2019-05-28 21:30:03 +01:00
for (let c = 0; c <= this.next_free_address_index + this.gap_limit; c++) {
const possibleAddress = this._getExternalAddressByIndex(c);
2018-08-08 01:05:34 +01:00
if (possibleAddress === address) {
return (this._address_to_wif_cache[address] = <string>this._getExternalWIFByIndex(c));
2018-08-08 01:05:34 +01:00
}
}
throw new Error('Could not find WIF for ' + address);
}
async fetchBalance(): Promise<void> {
throw new Error('Not implemented');
2019-04-13 23:21:41 +01:00
}
/**
* @inheritDoc
*/
async fetchUtxo(): Promise<void> {
throw new Error('Not implemented');
2018-12-22 02:46:35 +00:00
}
_getDerivationPathByAddress(address: string): string | false {
2019-09-27 15:49:56 +01:00
throw new Error('Not implemented');
}
_getNodePubkeyByIndex(node: number, index: number): Buffer | undefined {
2019-09-27 15:49:56 +01:00
throw new Error('Not implemented');
}
/**
* @returns {string} Root derivation path for wallet if any
*/
getDerivationPath() {
return this._derivationPath;
}
/*
* Set derivation path for the wallet
*
* @param {String} path - path
*/
setDerivationPath(path: string) {
this._derivationPath = path;
}
2018-07-21 13:52:54 +01:00
}