mirror of
https://github.com/BlueWallet/BlueWallet.git
synced 2025-02-22 15:04:50 +01:00
ADD: support for Electrum Seed format, legacy (closes #954)
This commit is contained in:
parent
4baa1f38a6
commit
bceacaca29
9 changed files with 152 additions and 0 deletions
|
@ -12,6 +12,9 @@ const reverse = require('buffer-reverse');
|
|||
|
||||
const { RNRandomBytes } = NativeModules;
|
||||
|
||||
/**
|
||||
* Electrum - means that it utilizes Electrum protocol for blockchain data
|
||||
*/
|
||||
export class AbstractHDElectrumWallet extends AbstractHDWallet {
|
||||
static type = 'abstract';
|
||||
static typeReadable = 'abstract';
|
||||
|
@ -986,4 +989,16 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
|
|||
if (broadcast.indexOf('successfully') !== -1) return true;
|
||||
return broadcast.length === 64; // this means return string is txid (precise length), so it was broadcasted ok
|
||||
}
|
||||
|
||||
/**
|
||||
* Probes zero address in external hierarchy for transactions, if there are any returns TRUE.
|
||||
* Zero address is a pretty good indicator, since its a first one to fund the wallet. How can you use the wallet and
|
||||
* not fund it first?
|
||||
*
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
async wasEverUsed() {
|
||||
let txs = await BlueElectrum.getTransactionsByAddress(this._getExternalAddressByIndex(0));
|
||||
return txs.length > 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -171,4 +171,8 @@ export class AbstractWallet {
|
|||
useWithHardwareWalletEnabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
async wasEverUsed() {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
}
|
||||
|
|
65
class/hd-legacy-electrum-seed-p2pkh-wallet.js
Normal file
65
class/hd-legacy-electrum-seed-p2pkh-wallet.js
Normal file
|
@ -0,0 +1,65 @@
|
|||
import { HDLegacyP2PKHWallet } from './';
|
||||
|
||||
const bitcoin = require('bitcoinjs-lib');
|
||||
const mn = require('electrum-mnemonic');
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
static type = 'HDlegacyElectrumSeedP2PKH';
|
||||
static typeReadable = 'HD Legacy Electrum (BIP32 P2PKH)';
|
||||
|
||||
validateMnemonic() {
|
||||
try {
|
||||
mn.mnemonicToSeedSync(this.secret);
|
||||
return true;
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
getXpub() {
|
||||
if (this._xpub) {
|
||||
return this._xpub; // cache hit
|
||||
}
|
||||
const root = bitcoin.bip32.fromSeed(mn.mnemonicToSeedSync(this.secret));
|
||||
this._xpub = root.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.p2pkh({
|
||||
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.p2pkh({
|
||||
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));
|
||||
const path = `m/${internal ? 1 : 0}/${index}`;
|
||||
const child = root.derivePath(path);
|
||||
|
||||
return child.toWIF();
|
||||
}
|
||||
}
|
|
@ -13,3 +13,4 @@ export * from './abstract-hd-wallet';
|
|||
export * from './hd-segwit-bech32-wallet';
|
||||
export * from './hd-segwit-bech32-transaction';
|
||||
export * from './placeholder-wallet';
|
||||
export * from './hd-legacy-electrum-seed-p2pkh-wallet';
|
||||
|
|
|
@ -10,6 +10,7 @@ import {
|
|||
LightningCustodianWallet,
|
||||
PlaceholderWallet,
|
||||
SegwitBech32Wallet,
|
||||
HDLegacyElectrumSeedP2PKHWallet,
|
||||
} from '../class';
|
||||
import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
|
||||
const EV = require('../events');
|
||||
|
@ -97,6 +98,7 @@ export default class WalletImport {
|
|||
// 1. check if its HDSegwitP2SHWallet (BIP49)
|
||||
// 2. check if its HDLegacyP2PKHWallet (BIP44)
|
||||
// 3. check if its HDLegacyBreadwalletWallet (no BIP, just "m/0")
|
||||
// 3.1 check HD Electrum legacy
|
||||
// 4. check if its Segwit WIF (P2SH)
|
||||
// 5. check if its Legacy WIF
|
||||
// 6. check if its address (watch-only wallet)
|
||||
|
@ -202,6 +204,13 @@ export default class WalletImport {
|
|||
}
|
||||
}
|
||||
|
||||
let hdElectrumSeedLegacy = new HDLegacyElectrumSeedP2PKHWallet();
|
||||
hdElectrumSeedLegacy.setSecret(importText);
|
||||
if (await hdElectrumSeedLegacy.wasEverUsed()) {
|
||||
// not fetching txs or balances, fuck it, yolo, life is too short
|
||||
return WalletImport._saveWallet(hdElectrumSeedLegacy);
|
||||
}
|
||||
|
||||
let hd2 = new HDSegwitP2SHWallet();
|
||||
hd2.setSecret(importText);
|
||||
if (hd2.validateMnemonic()) {
|
||||
|
|
10
package-lock.json
generated
10
package-lock.json
generated
|
@ -4460,6 +4460,16 @@
|
|||
"version": "git+https://github.com/BlueWallet/rn-electrum-client.git#2a5bb11dd9a8d89f328049d9ed59bce49d88a15d",
|
||||
"from": "git+https://github.com/BlueWallet/rn-electrum-client.git#2a5bb11dd9a8d89f328049d9ed59bce49d88a15d"
|
||||
},
|
||||
"electrum-mnemonic": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/electrum-mnemonic/-/electrum-mnemonic-1.0.5.tgz",
|
||||
"integrity": "sha512-Wq5vFTTZzHu6w6GSxSNdSgEouqK5x7nky7XGQOnIvS5gx2on7EHysAqB6sSGOhhD5MnoT8nn9LhZPSBrDWkllw==",
|
||||
"requires": {
|
||||
"create-hmac": "^1.1.7",
|
||||
"pbkdf2": "^3.0.17",
|
||||
"randombytes": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"elliptic": {
|
||||
"version": "6.5.2",
|
||||
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.2.tgz",
|
||||
|
|
|
@ -45,6 +45,7 @@
|
|||
"e2e:debug": "(test -f android/app/build/outputs/apk/debug/app-debug.apk && test -f android/app/build/outputs/apk/androidTest/debug/app-debug-androidTest.apk) || detox build -c android.emu.debug; detox test -c android.emu.debug",
|
||||
"lint": "./node_modules/.bin/eslint *.js screen/**/*.js screen/ class/ models/ loc/ tests/integration/ tests/e2e/ tests/unit/",
|
||||
"lint:fix": "./node_modules/.bin/eslint *.js screen/**/*.js screen/ class/ models/ loc/ tests/integration/ tests/e2e/ tests/unit/ --fix",
|
||||
"lint:quickfix": "git status --porcelain | grep -v '\\.json' | grep '\\.js' --color=never | awk '{print $2}' | xargs ./node_modules/.bin/eslint --fix; exit 0",
|
||||
"unit": "node node_modules/jest/bin/jest.js tests/unit/*"
|
||||
},
|
||||
"jest": {
|
||||
|
@ -79,6 +80,7 @@
|
|||
"dayjs": "1.8.23",
|
||||
"ecurve": "1.0.6",
|
||||
"electrum-client": "git+https://github.com/BlueWallet/rn-electrum-client.git#2a5bb11dd9a8d89f328049d9ed59bce49d88a15d",
|
||||
"electrum-mnemonic": "1.0.5",
|
||||
"eslint-config-prettier": "6.10.0",
|
||||
"eslint-config-standard": "12.0.0",
|
||||
"eslint-config-standard-react": "7.0.2",
|
||||
|
@ -90,6 +92,7 @@
|
|||
"lottie-react-native": "3.1.1",
|
||||
"node-libs-react-native": "1.0.3",
|
||||
"path-browserify": "1.0.0",
|
||||
"pbkdf2": "3.0.17",
|
||||
"prettier": "1.19.1",
|
||||
"process": "0.11.10",
|
||||
"prop-types": "15.7.2",
|
||||
|
|
|
@ -235,4 +235,19 @@ describe('Bech32 Segwit HD (BIP84)', () => {
|
|||
assert.strictEqual(totalInput - totalOutput, fee);
|
||||
assert.strictEqual(outputs[outputs.length - 1].address, changeAddress);
|
||||
});
|
||||
|
||||
it('wasEverUsed() works', async () => {
|
||||
if (!process.env.HD_MNEMONIC) {
|
||||
console.error('process.env.HD_MNEMONIC not set, skipped');
|
||||
return;
|
||||
}
|
||||
|
||||
let hd = new HDSegwitBech32Wallet();
|
||||
hd.setSecret(process.env.HD_MNEMONIC);
|
||||
assert.ok(await hd.wasEverUsed());
|
||||
|
||||
hd = new HDSegwitBech32Wallet();
|
||||
await hd.generate();
|
||||
assert.ok(!(await hd.wasEverUsed()), hd.getSecret());
|
||||
});
|
||||
});
|
||||
|
|
30
tests/unit/hd-legacy-electrum-seed-p2pkh-wallet.test.js
Normal file
30
tests/unit/hd-legacy-electrum-seed-p2pkh-wallet.test.js
Normal file
|
@ -0,0 +1,30 @@
|
|||
/* global describe, it */
|
||||
import { HDLegacyElectrumSeedP2PKHWallet } from '../../class';
|
||||
let assert = require('assert');
|
||||
|
||||
describe('HDLegacyElectrumSeedP2PKHWallet', () => {
|
||||
it('can import mnemonics and generate addresses and WIFs', async function() {
|
||||
if (!process.env.HD_ELECTRUM_SEED_LEGACY) {
|
||||
console.error('process.env.HD_ELECTRUM_SEED_LEGACY not set, skipped');
|
||||
return;
|
||||
}
|
||||
let hd = new HDLegacyElectrumSeedP2PKHWallet();
|
||||
hd.setSecret(process.env.HD_ELECTRUM_SEED_LEGACY);
|
||||
assert.ok(hd.validateMnemonic());
|
||||
|
||||
let address = hd._getExternalAddressByIndex(0);
|
||||
assert.strictEqual(address, '1Ca9ZVshGdKiiMEMNTG1bYqbifYMZMwV8');
|
||||
|
||||
address = hd._getInternalAddressByIndex(0);
|
||||
assert.strictEqual(address, '1JygAvTQS9haAYgRfPSdHgmXd3syjB8Fnp');
|
||||
|
||||
let wif = hd._getExternalWIFByIndex(0);
|
||||
assert.strictEqual(wif, 'KxGPz9dyib26p6bL2vQPvBPHBMA8iHVqEetg3x5XA4Rk1trZ11Kz');
|
||||
|
||||
wif = hd._getInternalWIFByIndex(0);
|
||||
assert.strictEqual(wif, 'L52d26QmYGW8ctHo1omM5fZeJMgaonSkEWCGpnEekNvkVUoqTsNF');
|
||||
|
||||
hd.setSecret('bs');
|
||||
assert.ok(!hd.validateMnemonic());
|
||||
});
|
||||
});
|
Loading…
Add table
Reference in a new issue