2021-01-28 12:54:23 +00:00
|
|
|
import { AbstractHDElectrumWallet } from './abstract-hd-electrum-wallet';
|
2021-04-02 15:40:18 +01:00
|
|
|
import b58 from 'bs58check';
|
2022-01-17 15:22:15 +00:00
|
|
|
import BIP32Factory from 'bip32';
|
|
|
|
import * as ecc from 'tiny-secp256k1';
|
|
|
|
|
2021-01-28 12:54:23 +00:00
|
|
|
const bitcoin = require('bitcoinjs-lib');
|
|
|
|
const { CipherSeed } = require('aezeed');
|
2022-01-17 15:22:15 +00:00
|
|
|
const bip32 = BIP32Factory(ecc);
|
2021-01-28 12:54:23 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* AEZEED mnemonics support, which is used in LND
|
|
|
|
* Support only BIP84 (native segwit) derivations
|
|
|
|
*
|
|
|
|
* @see https://github.com/lightningnetwork/lnd/tree/master/aezeed
|
|
|
|
* @see https://github.com/bitcoinjs/aezeed
|
|
|
|
* @see https://github.com/lightningnetwork/lnd/issues/4960
|
|
|
|
* @see https://github.com/guggero/chantools/blob/master/doc/chantools_genimportscript.md
|
|
|
|
* @see https://github.com/lightningnetwork/lnd/blob/master/keychain/derivation.go
|
|
|
|
*/
|
|
|
|
export class HDAezeedWallet extends AbstractHDElectrumWallet {
|
|
|
|
static type = 'HDAezeedWallet';
|
|
|
|
static typeReadable = 'HD Aezeed';
|
2021-03-11 13:45:49 +03:00
|
|
|
static segwitType = 'p2wpkh';
|
2021-03-23 18:58:13 +03:00
|
|
|
static derivationPath = "m/84'/0'/0'";
|
2021-01-28 12:54:23 +00:00
|
|
|
|
|
|
|
setSecret(newSecret) {
|
|
|
|
this.secret = newSecret.trim();
|
2021-07-07 10:52:42 +03:00
|
|
|
this.secret = this.secret.replace(/[^a-zA-Z0-9]/g, ' ').replace(/\s+/g, ' ');
|
2021-01-28 12:54:23 +00:00
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
_getEntropyCached() {
|
|
|
|
if (this._entropyHex) {
|
|
|
|
// cache hit
|
|
|
|
return Buffer.from(this._entropyHex, 'hex');
|
|
|
|
} else {
|
|
|
|
throw new Error('Entropy cache is not filled');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-02 15:40:18 +01:00
|
|
|
getXpub() {
|
|
|
|
// first, getting xpub
|
2021-11-25 15:50:40 +00:00
|
|
|
const root = bip32.fromSeed(this._getEntropyCached());
|
2021-04-02 15:40:18 +01:00
|
|
|
|
|
|
|
const path = "m/84'/0'/0'";
|
|
|
|
const child = root.derivePath(path).neutered();
|
|
|
|
const xpub = child.toBase58();
|
|
|
|
|
|
|
|
// bitcoinjs does not support zpub yet, so we just convert it from xpub
|
|
|
|
let data = b58.decode(xpub);
|
|
|
|
data = data.slice(4);
|
|
|
|
data = Buffer.concat([Buffer.from('04b24746', 'hex'), data]);
|
|
|
|
this._xpub = b58.encode(data);
|
|
|
|
|
|
|
|
return this._xpub;
|
|
|
|
}
|
|
|
|
|
2021-07-17 22:37:06 +02:00
|
|
|
validateMnemonic() {
|
2021-01-28 12:54:23 +00:00
|
|
|
throw new Error('Use validateMnemonicAsync()');
|
|
|
|
}
|
|
|
|
|
|
|
|
async validateMnemonicAsync() {
|
2021-07-07 10:52:42 +03:00
|
|
|
const passphrase = this.getPassphrase() || 'aezeed';
|
2021-01-28 12:54:23 +00:00
|
|
|
try {
|
2021-07-07 10:52:42 +03:00
|
|
|
const cipherSeed1 = await CipherSeed.fromMnemonic(this.secret, passphrase);
|
2021-01-28 12:54:23 +00:00
|
|
|
this._entropyHex = cipherSeed1.entropy.toString('hex'); // save cache
|
|
|
|
return !!cipherSeed1.entropy;
|
|
|
|
} catch (_) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async mnemonicInvalidPassword() {
|
2021-07-07 10:52:42 +03:00
|
|
|
const passphrase = this.getPassphrase() || 'aezeed';
|
2021-01-28 12:54:23 +00:00
|
|
|
try {
|
2021-07-07 10:52:42 +03:00
|
|
|
const cipherSeed1 = await CipherSeed.fromMnemonic(this.secret, passphrase);
|
2021-01-28 12:54:23 +00:00
|
|
|
this._entropyHex = cipherSeed1.entropy.toString('hex'); // save cache
|
|
|
|
} catch (error) {
|
|
|
|
return error.message === 'Invalid Password';
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
async generate() {
|
|
|
|
throw new Error('Not implemented');
|
|
|
|
}
|
|
|
|
|
2021-01-30 12:11:30 +00:00
|
|
|
_getNode0() {
|
2021-11-25 15:50:40 +00:00
|
|
|
const root = bip32.fromSeed(this._getEntropyCached());
|
2021-01-30 12:11:30 +00:00
|
|
|
const node = root.derivePath("m/84'/0'/0'");
|
|
|
|
return node.derive(0);
|
|
|
|
}
|
|
|
|
|
|
|
|
_getNode1() {
|
2021-11-25 15:50:40 +00:00
|
|
|
const root = bip32.fromSeed(this._getEntropyCached());
|
2021-01-30 12:11:30 +00:00
|
|
|
const node = root.derivePath("m/84'/0'/0'");
|
|
|
|
return node.derive(1);
|
|
|
|
}
|
|
|
|
|
2021-01-28 12:54:23 +00:00
|
|
|
_getInternalAddressByIndex(index) {
|
|
|
|
index = index * 1; // cast to int
|
|
|
|
if (this.internal_addresses_cache[index]) return this.internal_addresses_cache[index]; // cache hit
|
|
|
|
|
2021-01-30 12:11:30 +00:00
|
|
|
this._node1 = this._node1 || this._getNode1(); // cache
|
|
|
|
|
2021-01-28 12:54:23 +00:00
|
|
|
const address = bitcoin.payments.p2wpkh({
|
|
|
|
pubkey: this._node1.derive(index).publicKey,
|
|
|
|
}).address;
|
|
|
|
|
|
|
|
return (this.internal_addresses_cache[index] = address);
|
|
|
|
}
|
|
|
|
|
|
|
|
_getExternalAddressByIndex(index) {
|
|
|
|
index = index * 1; // cast to int
|
|
|
|
if (this.external_addresses_cache[index]) return this.external_addresses_cache[index]; // cache hit
|
|
|
|
|
2021-01-30 12:11:30 +00:00
|
|
|
this._node0 = this._node0 || this._getNode0(); // cache
|
|
|
|
|
2021-01-28 12:54:23 +00:00
|
|
|
const address = bitcoin.payments.p2wpkh({
|
|
|
|
pubkey: this._node0.derive(index).publicKey,
|
|
|
|
}).address;
|
|
|
|
|
|
|
|
return (this.external_addresses_cache[index] = address);
|
|
|
|
}
|
|
|
|
|
|
|
|
_getWIFByIndex(internal, index) {
|
|
|
|
if (!this.secret) return false;
|
2021-11-25 15:50:40 +00:00
|
|
|
const root = bip32.fromSeed(this._getEntropyCached());
|
2021-01-28 12:54:23 +00:00
|
|
|
const path = `m/84'/0'/0'/${internal ? 1 : 0}/${index}`;
|
|
|
|
const child = root.derivePath(path);
|
|
|
|
|
|
|
|
return child.toWIF();
|
|
|
|
}
|
|
|
|
|
|
|
|
_getNodePubkeyByIndex(node, index) {
|
2021-01-30 12:11:30 +00:00
|
|
|
index = index * 1; // cast to int
|
|
|
|
|
|
|
|
if (node === 0 && !this._node0) {
|
|
|
|
this._node0 = this._getNode0();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (node === 1 && !this._node1) {
|
|
|
|
this._node1 = this._getNode1();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (node === 0) {
|
|
|
|
return this._node0.derive(index).publicKey;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (node === 1) {
|
|
|
|
return this._node1.derive(index).publicKey;
|
|
|
|
}
|
2021-01-28 12:54:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
getIdentityPubkey() {
|
2021-11-25 15:50:40 +00:00
|
|
|
const root = bip32.fromSeed(this._getEntropyCached());
|
2021-01-28 12:54:23 +00:00
|
|
|
const node = root.derivePath("m/1017'/0'/6'/0/0");
|
|
|
|
|
|
|
|
return node.publicKey.toString('hex');
|
|
|
|
}
|
|
|
|
|
|
|
|
// since its basically a bip84 wallet, we allow all other standard BIP84 features:
|
|
|
|
|
|
|
|
allowSend() {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
allowHodlHodlTrading() {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
allowRBF() {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
allowPayJoin() {
|
|
|
|
return true;
|
|
|
|
}
|
2021-03-09 14:26:56 +03:00
|
|
|
|
2021-09-09 12:00:11 +01:00
|
|
|
isSegwit() {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2021-03-09 14:26:56 +03:00
|
|
|
allowSignVerifyMessage() {
|
|
|
|
return true;
|
|
|
|
}
|
2021-04-15 20:52:48 +03:00
|
|
|
|
|
|
|
allowXpub() {
|
|
|
|
return true;
|
|
|
|
}
|
2021-01-28 12:54:23 +00:00
|
|
|
}
|