Add Electrum seed recovery for Segwit as well (not just legacy)

This commit is contained in:
junderw 2020-05-04 18:52:01 +09:00 committed by Overtorment
parent 707004a102
commit 68485b9a47
7 changed files with 165 additions and 0 deletions

View file

@ -12,6 +12,7 @@ import {
PlaceholderWallet,
LightningCustodianWallet,
HDLegacyElectrumSeedP2PKHWallet,
HDSegwitElectrumSeedP2WPKHWallet,
} from './';
import WatchConnectivity from '../WatchConnectivity';
import DeviceQuickActions from './quickActions';
@ -266,6 +267,9 @@ export class AppStorage {
case HDLegacyElectrumSeedP2PKHWallet.type:
unserializedWallet = HDLegacyElectrumSeedP2PKHWallet.fromJson(key);
break;
case HDSegwitElectrumSeedP2WPKHWallet.type:
unserializedWallet = HDSegwitElectrumSeedP2WPKHWallet.fromJson(key);
break;
case LightningCustodianWallet.type:
/** @type {LightningCustodianWallet} */
unserializedWallet = LightningCustodianWallet.fromJson(key);

View file

@ -0,0 +1,101 @@
import { HDSegwitBech32Wallet } from './';
const bitcoin = require('bitcoinjs-lib');
const mn = require('electrum-mnemonic');
const HDNode = require('bip32');
const PREFIX = mn.PREFIXES.segwit;
const MNEMONIC_TO_SEED_OPTS = {
prefix: PREFIX,
};
/**
* 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 HDSegwitElectrumSeedP2WPKHWallet extends HDSegwitBech32Wallet {
static type = 'HDSegwitElectrumSeedP2WPKHWallet';
static typeReadable = 'HD Electrum (BIP32 P2WPKH)';
validateMnemonic() {
return mn.validateMnemonic(this.secret, PREFIX);
}
async generate() {
throw new Error('Not implemented');
}
getXpub() {
if (this._xpub) {
return this._xpub; // cache hit
}
const root = bitcoin.bip32.fromSeed(mn.mnemonicToSeedSync(this.secret, MNEMONIC_TO_SEED_OPTS));
this._xpub = root
.derivePath("m/0'")
.neutered()
.toBase58();
return this._xpub;
}
_getInternalAddressByIndex(index) {
index = index * 1; // cast to int
if (this.internal_addresses_cache[index]) return this.internal_addresses_cache[index]; // cache hit
const node = bitcoin.bip32.fromBase58(this.getXpub());
const address = bitcoin.payments.p2wpkh({
pubkey: node.derive(1).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
const node = bitcoin.bip32.fromBase58(this.getXpub());
const address = bitcoin.payments.p2wpkh({
pubkey: node.derive(0).derive(index).publicKey,
}).address;
return (this.external_addresses_cache[index] = address);
}
_getWIFByIndex(internal, index) {
const root = bitcoin.bip32.fromSeed(mn.mnemonicToSeedSync(this.secret, MNEMONIC_TO_SEED_OPTS));
const path = `m/0'/${internal ? 1 : 0}/${index}`;
const child = root.derivePath(path);
return child.toWIF();
}
allowSendMax() {
return true;
}
_getNodePubkeyByIndex(node, index) {
index = index * 1; // cast to int
if (node === 0 && !this._node0) {
const xpub = this.getXpub();
const hdNode = HDNode.fromBase58(xpub);
this._node0 = hdNode.derive(node);
}
if (node === 1 && !this._node1) {
const xpub = this.getXpub();
const hdNode = HDNode.fromBase58(xpub);
this._node1 = hdNode.derive(node);
}
if (node === 0) {
return this._node0.derive(index).publicKey;
}
if (node === 1) {
return this._node1.derive(index).publicKey;
}
}
}

View file

@ -14,3 +14,4 @@ export * from './hd-segwit-bech32-wallet';
export * from './hd-segwit-bech32-transaction';
export * from './placeholder-wallet';
export * from './hd-legacy-electrum-seed-p2pkh-wallet';
export * from './hd-segwit-electrum-seed-p2wpkh-wallet';

View file

@ -7,6 +7,8 @@ import { WatchOnlyWallet } from './watch-only-wallet';
import { HDSegwitBech32Wallet } from './hd-segwit-bech32-wallet';
import { PlaceholderWallet } from './placeholder-wallet';
import { SegwitBech32Wallet } from './segwit-bech32-wallet';
import { HDLegacyElectrumSeedP2PKHWallet } from './hd-legacy-electrum-seed-p2pkh-wallet';
import { HDSegwitElectrumSeedP2WPKHWallet } from './hd-segwit-electrum-seed-p2wpkh-wallet';
export default class WalletGradient {
static hdSegwitP2SHWallet = ['#65ceef', '#68bbe1'];
@ -30,6 +32,7 @@ export default class WalletGradient {
gradient = WalletGradient.legacyWallet;
break;
case HDLegacyP2PKHWallet.type:
case HDLegacyElectrumSeedP2PKHWallet.type:
gradient = WalletGradient.hdLegacyP2PKHWallet;
break;
case HDLegacyBreadwalletWallet.type:
@ -39,6 +42,7 @@ export default class WalletGradient {
gradient = WalletGradient.hdSegwitP2SHWallet;
break;
case HDSegwitBech32Wallet.type:
case HDSegwitElectrumSeedP2WPKHWallet.type:
gradient = WalletGradient.hdSegwitBech32Wallet;
break;
case LightningCustodianWallet.type:
@ -70,6 +74,7 @@ export default class WalletGradient {
gradient = WalletGradient.legacyWallet;
break;
case HDLegacyP2PKHWallet.type:
case HDLegacyElectrumSeedP2PKHWallet.type:
gradient = WalletGradient.hdLegacyP2PKHWallet;
break;
case HDLegacyBreadwalletWallet.type:
@ -79,6 +84,7 @@ export default class WalletGradient {
gradient = WalletGradient.hdSegwitP2SHWallet;
break;
case HDSegwitBech32Wallet.type:
case HDSegwitElectrumSeedP2WPKHWallet.type:
gradient = WalletGradient.hdSegwitBech32Wallet;
break;
case SegwitBech32Wallet.type:

View file

@ -11,6 +11,7 @@ import {
PlaceholderWallet,
SegwitBech32Wallet,
HDLegacyElectrumSeedP2PKHWallet,
HDSegwitElectrumSeedP2WPKHWallet,
} from '../class';
import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
const EV = require('../events');
@ -204,6 +205,15 @@ export default class WalletImport {
}
}
try {
let hdElectrumSeedLegacy = new HDSegwitElectrumSeedP2WPKHWallet();
hdElectrumSeedLegacy.setSecret(importText);
if (await hdElectrumSeedLegacy.wasEverUsed()) {
// not fetching txs or balances, fuck it, yolo, life is too short
return WalletImport._saveWallet(hdElectrumSeedLegacy);
}
} catch (_) {}
try {
let hdElectrumSeedLegacy = new HDLegacyElectrumSeedP2PKHWallet();
hdElectrumSeedLegacy.setSecret(importText);

View file

@ -7,6 +7,10 @@ describe('HDLegacyElectrumSeedP2PKHWallet', () => {
let hd = new HDLegacyElectrumSeedP2PKHWallet();
hd.setSecret('receive happy wash prosper update pet neck acid try profit proud hungry ');
assert.ok(hd.validateMnemonic());
assert.strictEqual(
hd.getXpub(),
'xpub661MyMwAqRbcG6vx5SspHUzrhRtPKyeGp41JJLBi3kgeMCFkR6mzGkhEttBHTZg6FYYij52pqD2cW7XsutiZrRukXNLqeo87mZAV5k5bC22',
);
let address = hd._getExternalAddressByIndex(0);
assert.strictEqual(address, '1Ca9ZVshGdKiiMEMNTG1bYqbifYMZMwV8');

View file

@ -0,0 +1,39 @@
/* global describe, it */
import { HDSegwitElectrumSeedP2WPKHWallet } from '../../class';
let assert = require('assert');
describe('HDSegwitElectrumSeedP2WPKHWallet', () => {
it('can import mnemonics and generate addresses and WIFs', async function() {
let hd = new HDSegwitElectrumSeedP2WPKHWallet();
hd.setSecret('method goddess humble crumble output snake essay carpet monster barely trip betray ');
assert.ok(hd.validateMnemonic());
assert.strictEqual(
hd.getXpub(),
'xpub68RzTumZwSbVWwETioxTSk2PhBvBRDGNRHepHUC5x2gptbSVWhkezF3NKbq9sCJhnNKcPx2McNWJtFFdXLx97cknHhuDTDQsFg5cG7MSMY7',
);
let address = hd._getExternalAddressByIndex(0);
assert.strictEqual(address, 'bc1q2yv6rhtw9ycqeq2rkch65sucf66ytwsd3csawr');
address = hd._getInternalAddressByIndex(0);
assert.strictEqual(address, 'bc1qvdu80q26ghe66zq8tf5y09qr29vay4cg65mvuk');
let wif = hd._getExternalWIFByIndex(0);
assert.strictEqual(wif, 'L5a1N5JQzT9wDUmVS9hb2mrd1SMkwPfrWYS8C3Kngp7kiuBkpY2V');
wif = hd._getInternalWIFByIndex(0);
assert.strictEqual(wif, 'KwsLfaB2y9QZRd5cxY3uM3L4r2fE7ZPzocwjkPbp1cSFMFfE9tBq');
assert.strictEqual(
hd._getPubkeyByAddress(hd._getExternalAddressByIndex(0)).toString('hex'),
'023cb68c37a1ca627c414e63dfb23706091eafb50e50d7de4e2a1a56d7085d42e6',
);
assert.strictEqual(
hd._getPubkeyByAddress(hd._getInternalAddressByIndex(0)).toString('hex'),
'02e7e6a8dc1fe62f7de88a7de3c5030f36ec6aec28c610bc1d573435fab18b9f94',
);
hd.setSecret('bs');
assert.ok(!hd.validateMnemonic());
});
});