2022-01-17 15:22:15 +00:00
|
|
|
import BIP32Factory from 'bip32';
|
2024-02-23 07:32:47 +00:00
|
|
|
import * as bitcoin from 'bitcoinjs-lib';
|
|
|
|
import * as mn from 'electrum-mnemonic';
|
2024-05-20 10:54:13 +01:00
|
|
|
|
2022-10-02 18:33:30 +01:00
|
|
|
import ecc from '../../blue_modules/noble_ecc';
|
2024-02-23 07:32:47 +00:00
|
|
|
import { HDLegacyP2PKHWallet } from './hd-legacy-p2pkh-wallet';
|
2020-04-16 15:40:23 +01:00
|
|
|
|
2022-01-17 15:22:15 +00:00
|
|
|
const bip32 = BIP32Factory(ecc);
|
2020-05-04 12:53:14 +09:00
|
|
|
const PREFIX = mn.PREFIXES.standard;
|
2020-05-04 09:16:26 +09:00
|
|
|
|
2024-02-23 07:32:47 +00:00
|
|
|
type SeedOpts = {
|
|
|
|
prefix?: string;
|
|
|
|
passphrase?: string;
|
|
|
|
};
|
|
|
|
|
2020-04-16 15:40:23 +01:00
|
|
|
/**
|
|
|
|
* ElectrumSeed means that instead of BIP39 seed format it works with the format invented by Electrum wallet. Otherwise
|
|
|
|
* its a regular HD wallet that has all the properties of parent class.
|
|
|
|
*
|
|
|
|
* @see https://electrum.readthedocs.io/en/latest/seedphrase.html
|
|
|
|
*/
|
|
|
|
export class HDLegacyElectrumSeedP2PKHWallet extends HDLegacyP2PKHWallet {
|
2024-03-15 23:05:15 +03:00
|
|
|
static readonly type = 'HDlegacyElectrumSeedP2PKH';
|
|
|
|
static readonly typeReadable = 'HD Legacy Electrum (BIP32 P2PKH)';
|
|
|
|
// @ts-ignore: override
|
|
|
|
public readonly type = HDLegacyElectrumSeedP2PKHWallet.type;
|
|
|
|
// @ts-ignore: override
|
|
|
|
public readonly typeReadable = HDLegacyElectrumSeedP2PKHWallet.typeReadable;
|
|
|
|
static readonly derivationPath = 'm';
|
2020-04-16 15:40:23 +01:00
|
|
|
|
|
|
|
validateMnemonic() {
|
2020-05-04 12:53:14 +09:00
|
|
|
return mn.validateMnemonic(this.secret, PREFIX);
|
2020-04-16 15:40:23 +01:00
|
|
|
}
|
|
|
|
|
2023-04-06 19:29:34 +03:00
|
|
|
allowBIP47() {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2020-04-22 16:13:18 +01:00
|
|
|
async generate() {
|
|
|
|
throw new Error('Not implemented');
|
|
|
|
}
|
|
|
|
|
2020-04-16 15:40:23 +01:00
|
|
|
getXpub() {
|
|
|
|
if (this._xpub) {
|
|
|
|
return this._xpub; // cache hit
|
|
|
|
}
|
2024-02-23 07:32:47 +00:00
|
|
|
const args: SeedOpts = { prefix: PREFIX };
|
2021-05-14 19:07:33 +03:00
|
|
|
if (this.passphrase) args.passphrase = this.passphrase;
|
2022-01-17 15:22:15 +00:00
|
|
|
const root = bip32.fromSeed(mn.mnemonicToSeedSync(this.secret, args));
|
2020-05-04 18:32:00 +09:00
|
|
|
this._xpub = root.neutered().toBase58();
|
2020-04-16 15:40:23 +01:00
|
|
|
return this._xpub;
|
|
|
|
}
|
|
|
|
|
2024-02-23 07:32:47 +00:00
|
|
|
_getInternalAddressByIndex(index: number) {
|
2020-04-16 15:40:23 +01:00
|
|
|
index = index * 1; // cast to int
|
|
|
|
if (this.internal_addresses_cache[index]) return this.internal_addresses_cache[index]; // cache hit
|
|
|
|
|
2022-01-17 15:22:15 +00:00
|
|
|
const node = bip32.fromBase58(this.getXpub());
|
2020-04-16 15:40:23 +01:00
|
|
|
const address = bitcoin.payments.p2pkh({
|
|
|
|
pubkey: node.derive(1).derive(index).publicKey,
|
|
|
|
}).address;
|
2024-02-23 07:32:47 +00:00
|
|
|
if (!address) {
|
|
|
|
throw new Error('Internal error: no address in _getInternalAddressByIndex');
|
|
|
|
}
|
2020-04-16 15:40:23 +01:00
|
|
|
|
|
|
|
return (this.internal_addresses_cache[index] = address);
|
|
|
|
}
|
|
|
|
|
2024-02-23 07:32:47 +00:00
|
|
|
_getExternalAddressByIndex(index: number) {
|
2020-04-16 15:40:23 +01:00
|
|
|
index = index * 1; // cast to int
|
|
|
|
if (this.external_addresses_cache[index]) return this.external_addresses_cache[index]; // cache hit
|
|
|
|
|
2022-01-17 15:22:15 +00:00
|
|
|
const node = bip32.fromBase58(this.getXpub());
|
2020-04-16 15:40:23 +01:00
|
|
|
const address = bitcoin.payments.p2pkh({
|
|
|
|
pubkey: node.derive(0).derive(index).publicKey,
|
|
|
|
}).address;
|
2024-02-23 07:32:47 +00:00
|
|
|
if (!address) {
|
|
|
|
throw new Error('Internal error: no address in _getExternalAddressByIndex');
|
|
|
|
}
|
2020-04-16 15:40:23 +01:00
|
|
|
|
|
|
|
return (this.external_addresses_cache[index] = address);
|
|
|
|
}
|
|
|
|
|
2024-02-23 07:32:47 +00:00
|
|
|
_getWIFByIndex(internal: boolean, index: number): string | false {
|
2020-10-05 12:12:54 +01:00
|
|
|
if (!this.secret) return false;
|
2024-02-23 07:32:47 +00:00
|
|
|
const args: SeedOpts = { prefix: PREFIX };
|
2021-05-14 19:07:33 +03:00
|
|
|
if (this.passphrase) args.passphrase = this.passphrase;
|
2022-01-17 15:22:15 +00:00
|
|
|
const root = bip32.fromSeed(mn.mnemonicToSeedSync(this.secret, args));
|
2020-04-16 15:40:23 +01:00
|
|
|
const path = `m/${internal ? 1 : 0}/${index}`;
|
|
|
|
const child = root.derivePath(path);
|
|
|
|
|
|
|
|
return child.toWIF();
|
|
|
|
}
|
2020-04-22 16:13:18 +01:00
|
|
|
|
2024-02-23 07:32:47 +00:00
|
|
|
_getNodePubkeyByIndex(node: number, index: number) {
|
2020-04-22 16:13:18 +01:00
|
|
|
index = index * 1; // cast to int
|
|
|
|
|
|
|
|
if (node === 0 && !this._node0) {
|
|
|
|
const xpub = this.getXpub();
|
2022-01-17 15:22:15 +00:00
|
|
|
const hdNode = bip32.fromBase58(xpub);
|
2020-04-22 16:13:18 +01:00
|
|
|
this._node0 = hdNode.derive(node);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (node === 1 && !this._node1) {
|
|
|
|
const xpub = this.getXpub();
|
2022-01-17 15:22:15 +00:00
|
|
|
const hdNode = bip32.fromBase58(xpub);
|
2020-04-22 16:13:18 +01:00
|
|
|
this._node1 = hdNode.derive(node);
|
|
|
|
}
|
|
|
|
|
2024-02-23 07:32:47 +00:00
|
|
|
if (node === 0 && this._node0) {
|
2020-04-22 16:13:18 +01:00
|
|
|
return this._node0.derive(index).publicKey;
|
|
|
|
}
|
|
|
|
|
2024-02-23 07:32:47 +00:00
|
|
|
if (node === 1 && this._node1) {
|
2020-04-22 16:13:18 +01:00
|
|
|
return this._node1.derive(index).publicKey;
|
|
|
|
}
|
2024-02-23 07:32:47 +00:00
|
|
|
|
|
|
|
throw new Error('Internal error: this._node0 or this._node1 is undefined');
|
2020-04-22 16:13:18 +01:00
|
|
|
}
|
2020-04-16 15:40:23 +01:00
|
|
|
}
|