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,
|
||||
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);
|
||||
|
|
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 './placeholder-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 { 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:
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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');
|
||||
|
|
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