mirror of
https://github.com/BlueWallet/BlueWallet.git
synced 2025-02-22 23:08:07 +01:00
Add Electrum seed recovery for Segwit as well (not just legacy)
This commit is contained in:
parent
707004a102
commit
68485b9a47
7 changed files with 165 additions and 0 deletions
|
@ -12,6 +12,7 @@ import {
|
||||||
PlaceholderWallet,
|
PlaceholderWallet,
|
||||||
LightningCustodianWallet,
|
LightningCustodianWallet,
|
||||||
HDLegacyElectrumSeedP2PKHWallet,
|
HDLegacyElectrumSeedP2PKHWallet,
|
||||||
|
HDSegwitElectrumSeedP2WPKHWallet,
|
||||||
} from './';
|
} from './';
|
||||||
import WatchConnectivity from '../WatchConnectivity';
|
import WatchConnectivity from '../WatchConnectivity';
|
||||||
import DeviceQuickActions from './quickActions';
|
import DeviceQuickActions from './quickActions';
|
||||||
|
@ -266,6 +267,9 @@ export class AppStorage {
|
||||||
case HDLegacyElectrumSeedP2PKHWallet.type:
|
case HDLegacyElectrumSeedP2PKHWallet.type:
|
||||||
unserializedWallet = HDLegacyElectrumSeedP2PKHWallet.fromJson(key);
|
unserializedWallet = HDLegacyElectrumSeedP2PKHWallet.fromJson(key);
|
||||||
break;
|
break;
|
||||||
|
case HDSegwitElectrumSeedP2WPKHWallet.type:
|
||||||
|
unserializedWallet = HDSegwitElectrumSeedP2WPKHWallet.fromJson(key);
|
||||||
|
break;
|
||||||
case LightningCustodianWallet.type:
|
case LightningCustodianWallet.type:
|
||||||
/** @type {LightningCustodianWallet} */
|
/** @type {LightningCustodianWallet} */
|
||||||
unserializedWallet = LightningCustodianWallet.fromJson(key);
|
unserializedWallet = LightningCustodianWallet.fromJson(key);
|
||||||
|
|
101
class/hd-segwit-electrum-seed-p2wpkh-wallet.js
Normal file
101
class/hd-segwit-electrum-seed-p2wpkh-wallet.js
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -14,3 +14,4 @@ export * from './hd-segwit-bech32-wallet';
|
||||||
export * from './hd-segwit-bech32-transaction';
|
export * from './hd-segwit-bech32-transaction';
|
||||||
export * from './placeholder-wallet';
|
export * from './placeholder-wallet';
|
||||||
export * from './hd-legacy-electrum-seed-p2pkh-wallet';
|
export * from './hd-legacy-electrum-seed-p2pkh-wallet';
|
||||||
|
export * from './hd-segwit-electrum-seed-p2wpkh-wallet';
|
||||||
|
|
|
@ -7,6 +7,8 @@ import { WatchOnlyWallet } from './watch-only-wallet';
|
||||||
import { HDSegwitBech32Wallet } from './hd-segwit-bech32-wallet';
|
import { HDSegwitBech32Wallet } from './hd-segwit-bech32-wallet';
|
||||||
import { PlaceholderWallet } from './placeholder-wallet';
|
import { PlaceholderWallet } from './placeholder-wallet';
|
||||||
import { SegwitBech32Wallet } from './segwit-bech32-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 {
|
export default class WalletGradient {
|
||||||
static hdSegwitP2SHWallet = ['#65ceef', '#68bbe1'];
|
static hdSegwitP2SHWallet = ['#65ceef', '#68bbe1'];
|
||||||
|
@ -30,6 +32,7 @@ export default class WalletGradient {
|
||||||
gradient = WalletGradient.legacyWallet;
|
gradient = WalletGradient.legacyWallet;
|
||||||
break;
|
break;
|
||||||
case HDLegacyP2PKHWallet.type:
|
case HDLegacyP2PKHWallet.type:
|
||||||
|
case HDLegacyElectrumSeedP2PKHWallet.type:
|
||||||
gradient = WalletGradient.hdLegacyP2PKHWallet;
|
gradient = WalletGradient.hdLegacyP2PKHWallet;
|
||||||
break;
|
break;
|
||||||
case HDLegacyBreadwalletWallet.type:
|
case HDLegacyBreadwalletWallet.type:
|
||||||
|
@ -39,6 +42,7 @@ export default class WalletGradient {
|
||||||
gradient = WalletGradient.hdSegwitP2SHWallet;
|
gradient = WalletGradient.hdSegwitP2SHWallet;
|
||||||
break;
|
break;
|
||||||
case HDSegwitBech32Wallet.type:
|
case HDSegwitBech32Wallet.type:
|
||||||
|
case HDSegwitElectrumSeedP2WPKHWallet.type:
|
||||||
gradient = WalletGradient.hdSegwitBech32Wallet;
|
gradient = WalletGradient.hdSegwitBech32Wallet;
|
||||||
break;
|
break;
|
||||||
case LightningCustodianWallet.type:
|
case LightningCustodianWallet.type:
|
||||||
|
@ -70,6 +74,7 @@ export default class WalletGradient {
|
||||||
gradient = WalletGradient.legacyWallet;
|
gradient = WalletGradient.legacyWallet;
|
||||||
break;
|
break;
|
||||||
case HDLegacyP2PKHWallet.type:
|
case HDLegacyP2PKHWallet.type:
|
||||||
|
case HDLegacyElectrumSeedP2PKHWallet.type:
|
||||||
gradient = WalletGradient.hdLegacyP2PKHWallet;
|
gradient = WalletGradient.hdLegacyP2PKHWallet;
|
||||||
break;
|
break;
|
||||||
case HDLegacyBreadwalletWallet.type:
|
case HDLegacyBreadwalletWallet.type:
|
||||||
|
@ -79,6 +84,7 @@ export default class WalletGradient {
|
||||||
gradient = WalletGradient.hdSegwitP2SHWallet;
|
gradient = WalletGradient.hdSegwitP2SHWallet;
|
||||||
break;
|
break;
|
||||||
case HDSegwitBech32Wallet.type:
|
case HDSegwitBech32Wallet.type:
|
||||||
|
case HDSegwitElectrumSeedP2WPKHWallet.type:
|
||||||
gradient = WalletGradient.hdSegwitBech32Wallet;
|
gradient = WalletGradient.hdSegwitBech32Wallet;
|
||||||
break;
|
break;
|
||||||
case SegwitBech32Wallet.type:
|
case SegwitBech32Wallet.type:
|
||||||
|
|
|
@ -11,6 +11,7 @@ import {
|
||||||
PlaceholderWallet,
|
PlaceholderWallet,
|
||||||
SegwitBech32Wallet,
|
SegwitBech32Wallet,
|
||||||
HDLegacyElectrumSeedP2PKHWallet,
|
HDLegacyElectrumSeedP2PKHWallet,
|
||||||
|
HDSegwitElectrumSeedP2WPKHWallet,
|
||||||
} from '../class';
|
} from '../class';
|
||||||
import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
|
import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
|
||||||
const EV = require('../events');
|
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 {
|
try {
|
||||||
let hdElectrumSeedLegacy = new HDLegacyElectrumSeedP2PKHWallet();
|
let hdElectrumSeedLegacy = new HDLegacyElectrumSeedP2PKHWallet();
|
||||||
hdElectrumSeedLegacy.setSecret(importText);
|
hdElectrumSeedLegacy.setSecret(importText);
|
||||||
|
|
|
@ -7,6 +7,10 @@ describe('HDLegacyElectrumSeedP2PKHWallet', () => {
|
||||||
let hd = new HDLegacyElectrumSeedP2PKHWallet();
|
let hd = new HDLegacyElectrumSeedP2PKHWallet();
|
||||||
hd.setSecret('receive happy wash prosper update pet neck acid try profit proud hungry ');
|
hd.setSecret('receive happy wash prosper update pet neck acid try profit proud hungry ');
|
||||||
assert.ok(hd.validateMnemonic());
|
assert.ok(hd.validateMnemonic());
|
||||||
|
assert.strictEqual(
|
||||||
|
hd.getXpub(),
|
||||||
|
'xpub661MyMwAqRbcG6vx5SspHUzrhRtPKyeGp41JJLBi3kgeMCFkR6mzGkhEttBHTZg6FYYij52pqD2cW7XsutiZrRukXNLqeo87mZAV5k5bC22',
|
||||||
|
);
|
||||||
|
|
||||||
let address = hd._getExternalAddressByIndex(0);
|
let address = hd._getExternalAddressByIndex(0);
|
||||||
assert.strictEqual(address, '1Ca9ZVshGdKiiMEMNTG1bYqbifYMZMwV8');
|
assert.strictEqual(address, '1Ca9ZVshGdKiiMEMNTG1bYqbifYMZMwV8');
|
||||||
|
|
39
tests/unit/hd-segwit-electrum-seed-p2wpkh-wallet.test.js
Normal file
39
tests/unit/hd-segwit-electrum-seed-p2wpkh-wallet.test.js
Normal 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());
|
||||||
|
});
|
||||||
|
});
|
Loading…
Add table
Reference in a new issue