mirror of
https://github.com/BlueWallet/BlueWallet.git
synced 2025-01-19 05:45:15 +01:00
ADD: add passphrase support to wallets
This commit is contained in:
parent
ada9e6ba0a
commit
bd91e7df59
@ -52,7 +52,8 @@ export class AbstractHDWallet extends LegacyWallet {
|
||||
*/
|
||||
_getSeed() {
|
||||
const mnemonic = this.secret;
|
||||
return bip39.mnemonicToSeedSync(mnemonic);
|
||||
const passphrase = this.passphrase;
|
||||
return bip39.mnemonicToSeedSync(mnemonic, passphrase);
|
||||
}
|
||||
|
||||
setSecret(newSecret) {
|
||||
@ -61,6 +62,14 @@ export class AbstractHDWallet extends LegacyWallet {
|
||||
return this;
|
||||
}
|
||||
|
||||
setPassphrase(passphrase) {
|
||||
this.passphrase = passphrase;
|
||||
}
|
||||
|
||||
getPassphrase() {
|
||||
return this.passphrase;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {Boolean} is mnemonic in `this.secret` valid
|
||||
*/
|
||||
@ -68,10 +77,6 @@ export class AbstractHDWallet extends LegacyWallet {
|
||||
return bip39.validateMnemonic(this.secret);
|
||||
}
|
||||
|
||||
getMnemonicToSeedHex() {
|
||||
return bip39.mnemonicToSeedSync(this.secret).toString('hex');
|
||||
}
|
||||
|
||||
/**
|
||||
* Derives from hierarchy, returns next free address
|
||||
* (the one that has no transactions). Looks for several,
|
||||
|
@ -5,9 +5,6 @@ const mn = require('electrum-mnemonic');
|
||||
const HDNode = require('bip32');
|
||||
|
||||
const PREFIX = mn.PREFIXES.standard;
|
||||
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
|
||||
@ -32,7 +29,9 @@ export class HDLegacyElectrumSeedP2PKHWallet extends HDLegacyP2PKHWallet {
|
||||
if (this._xpub) {
|
||||
return this._xpub; // cache hit
|
||||
}
|
||||
const root = bitcoin.bip32.fromSeed(mn.mnemonicToSeedSync(this.secret, MNEMONIC_TO_SEED_OPTS));
|
||||
const args = { prefix: PREFIX };
|
||||
if (this.passphrase) args.passphrase = this.passphrase;
|
||||
const root = bitcoin.bip32.fromSeed(mn.mnemonicToSeedSync(this.secret, args));
|
||||
this._xpub = root.neutered().toBase58();
|
||||
return this._xpub;
|
||||
}
|
||||
@ -63,7 +62,9 @@ export class HDLegacyElectrumSeedP2PKHWallet extends HDLegacyP2PKHWallet {
|
||||
|
||||
_getWIFByIndex(internal, index) {
|
||||
if (!this.secret) return false;
|
||||
const root = bitcoin.bip32.fromSeed(mn.mnemonicToSeedSync(this.secret, MNEMONIC_TO_SEED_OPTS));
|
||||
const args = { prefix: PREFIX };
|
||||
if (this.passphrase) args.passphrase = this.passphrase;
|
||||
const root = bitcoin.bip32.fromSeed(mn.mnemonicToSeedSync(this.secret, args));
|
||||
const path = `m/${internal ? 1 : 0}/${index}`;
|
||||
const child = root.derivePath(path);
|
||||
|
||||
|
@ -6,9 +6,6 @@ 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
|
||||
@ -33,7 +30,9 @@ export class HDSegwitElectrumSeedP2WPKHWallet extends HDSegwitBech32Wallet {
|
||||
if (this._xpub) {
|
||||
return this._xpub; // cache hit
|
||||
}
|
||||
const root = bitcoin.bip32.fromSeed(mn.mnemonicToSeedSync(this.secret, MNEMONIC_TO_SEED_OPTS));
|
||||
const args = { prefix: PREFIX };
|
||||
if (this.passphrase) args.passphrase = this.passphrase;
|
||||
const root = bitcoin.bip32.fromSeed(mn.mnemonicToSeedSync(this.secret, args));
|
||||
const xpub = root.derivePath("m/0'").neutered().toBase58();
|
||||
|
||||
// bitcoinjs does not support zpub yet, so we just convert it from xpub
|
||||
@ -73,7 +72,9 @@ export class HDSegwitElectrumSeedP2WPKHWallet extends HDSegwitBech32Wallet {
|
||||
|
||||
_getWIFByIndex(internal, index) {
|
||||
if (!this.secret) return false;
|
||||
const root = bitcoin.bip32.fromSeed(mn.mnemonicToSeedSync(this.secret, MNEMONIC_TO_SEED_OPTS));
|
||||
const args = { prefix: PREFIX };
|
||||
if (this.passphrase) args.passphrase = this.passphrase;
|
||||
const root = bitcoin.bip32.fromSeed(mn.mnemonicToSeedSync(this.secret, args));
|
||||
const path = `m/0'/${internal ? 1 : 0}/${index}`;
|
||||
const child = root.derivePath(path);
|
||||
|
||||
|
@ -8,7 +8,7 @@ import { HDSegwitBech32Wallet } from './hd-segwit-bech32-wallet';
|
||||
// collection of SLIP39 functions
|
||||
const SLIP39Mixin = {
|
||||
_getSeed() {
|
||||
const master = slip39.recoverSecret(this.secret);
|
||||
const master = slip39.recoverSecret(this.secret, this.passphrase);
|
||||
return Buffer.from(master);
|
||||
},
|
||||
|
||||
|
@ -50,4 +50,21 @@ describe('HDLegacyElectrumSeedP2PKHWallet', () => {
|
||||
hd.setSecret('bs');
|
||||
assert.ok(!hd.validateMnemonic());
|
||||
});
|
||||
|
||||
it('can use mnemonic with passphrase', () => {
|
||||
const mnemonic = 'receive happy wash prosper update pet neck acid try profit proud hungry ';
|
||||
const passphrase = 'super secret passphrase';
|
||||
const hd = new HDLegacyElectrumSeedP2PKHWallet();
|
||||
hd.setSecret(mnemonic);
|
||||
hd.setPassphrase(passphrase);
|
||||
|
||||
assert.strictEqual(
|
||||
hd.getXpub(),
|
||||
'xpub661MyMwAqRbcGSUBZaVtq8qEoRkJM1TZNNvUJEgQvtiZE73gS1wKWQoTj6R2E46UDYS2SBpmGGrSHGsJUNxtr1krixFuq8JA772pG43Mo6R',
|
||||
);
|
||||
|
||||
assert.strictEqual(hd._getExternalAddressByIndex(0), '13sPvsrgRN8XibZNHtZXNqVDJPnNZLjTap');
|
||||
assert.strictEqual(hd._getInternalAddressByIndex(0), '16oEuy5H7ejmapqc2AtKAYerdfkDkoyrDX');
|
||||
assert.strictEqual(hd._getExternalWIFByIndex(0), 'Ky9WTDUTTZUKKYSPEE6uah2y5sJa89z6177kD23xh5cq1znX2HDj');
|
||||
});
|
||||
});
|
||||
|
@ -1,6 +1,7 @@
|
||||
import assert from 'assert';
|
||||
import * as bitcoin from 'bitcoinjs-lib';
|
||||
|
||||
import { HDLegacyP2PKHWallet } from '../../class';
|
||||
const assert = require('assert');
|
||||
const bitcoin = require('bitcoinjs-lib');
|
||||
|
||||
describe('Legacy HD (BIP44)', () => {
|
||||
it('works', async () => {
|
||||
@ -156,4 +157,22 @@ describe('Legacy HD (BIP44)', () => {
|
||||
hd.setSecret(mnemonic);
|
||||
assert.strictEqual(hd.getMasterFingerprintHex(), '73C5DA0A');
|
||||
});
|
||||
|
||||
// from electrum tests https://github.com/spesmilo/electrum/blob/9c1a51547a301e765b9b0f9935c6d940bb9d658e/electrum/tests/test_wallet_vertical.py#L292
|
||||
it('can use mnemonic with passphrase', () => {
|
||||
const mnemonic = 'treat dwarf wealth gasp brass outside high rent blood crowd make initial';
|
||||
const UNICODE_HORROR = '₿ 😀 😈 う けたま わる w͢͢͝h͡o͢͡ ̸͢k̵͟n̴͘ǫw̸̛s͘ ̀́w͘͢ḩ̵a҉̡͢t ̧̕h́o̵r͏̵rors̡ ̶͡͠lį̶e͟͟ ̶͝in͢ ͏t̕h̷̡͟e ͟͟d̛a͜r̕͡k̢̨ ͡h̴e͏a̷̢̡rt́͏ ̴̷͠ò̵̶f̸ u̧͘ní̛͜c͢͏o̷͏d̸͢e̡͝?͞';
|
||||
const hd = new HDLegacyP2PKHWallet();
|
||||
hd.setSecret(mnemonic);
|
||||
hd.setPassphrase(UNICODE_HORROR);
|
||||
|
||||
assert.strictEqual(
|
||||
hd.getXpub(),
|
||||
'xpub6D85QDBajeLe2JXJrZeGyQCaw47PWKi3J9DPuHakjTkVBWCxVQQkmMVMSSfnw39tj9FntbozpRtb1AJ8ubjeVSBhyK4M5mzdvsXZzKPwodT',
|
||||
);
|
||||
|
||||
assert.strictEqual(hd._getExternalAddressByIndex(0), '1F88g2naBMhDB7pYFttPWGQgryba3hPevM');
|
||||
assert.strictEqual(hd._getInternalAddressByIndex(0), '1H4QD1rg2zQJ4UjuAVJr5eW1fEM8WMqyxh');
|
||||
assert.strictEqual(hd._getExternalWIFByIndex(0), 'L3HLzdVcwo4711gFiZG4fiLzLVNJpR6nejfo6J85wuYn9YF2G5zk');
|
||||
});
|
||||
});
|
||||
|
@ -1,5 +1,5 @@
|
||||
import assert from 'assert';
|
||||
import { HDSegwitBech32Wallet } from '../../class';
|
||||
const assert = require('assert');
|
||||
|
||||
describe('Bech32 Segwit HD (BIP84)', () => {
|
||||
it('can create', async function () {
|
||||
@ -163,4 +163,21 @@ describe('Bech32 Segwit HD (BIP84)', () => {
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
it('can use mnemonic with passphrase', () => {
|
||||
const mnemonic = 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about';
|
||||
const passphrase = 'super secret passphrase';
|
||||
const hd = new HDSegwitBech32Wallet();
|
||||
hd.setSecret(mnemonic);
|
||||
hd.setPassphrase(passphrase);
|
||||
|
||||
assert.strictEqual(
|
||||
hd.getXpub(),
|
||||
'zpub6qNvUL1qNQwaReccveprgd4urE2EUvShpcFe7WB9tzf9L4NJNcWhPzJSk4fzNXqBNZdRr6135hBKaqqp5RVvyxZ6eMbZXL6u5iK4zrfkCaQ',
|
||||
);
|
||||
|
||||
assert.strictEqual(hd._getExternalAddressByIndex(0), 'bc1qgaj3satczjem43pz46ct6r3758twhnny4y720z');
|
||||
assert.strictEqual(hd._getInternalAddressByIndex(0), 'bc1qthe7wh5eplzxczslvthyrer36ph3kxpnfnxgjg');
|
||||
assert.strictEqual(hd._getExternalWIFByIndex(0), 'L1tfV6fbRjDNwGQdJqHC9fneM9bTHigApnWgoKoU8JwgziwbbE7i');
|
||||
});
|
||||
});
|
||||
|
@ -48,4 +48,22 @@ describe('HDSegwitElectrumSeedP2WPKHWallet', () => {
|
||||
hd.setSecret('bs');
|
||||
assert.ok(!hd.validateMnemonic());
|
||||
});
|
||||
|
||||
// from electrum tests https://github.com/spesmilo/electrum/blob/9c1a51547a301e765b9b0f9935c6d940bb9d658e/electrum/tests/test_wallet_vertical.py#L130
|
||||
it('can use mnemonic with passphrase', () => {
|
||||
const mnemonic = 'bitter grass shiver impose acquire brush forget axis eager alone wine silver';
|
||||
const UNICODE_HORROR = '₿ 😀 😈 う けたま わる w͢͢͝h͡o͢͡ ̸͢k̵͟n̴͘ǫw̸̛s͘ ̀́w͘͢ḩ̵a҉̡͢t ̧̕h́o̵r͏̵rors̡ ̶͡͠lį̶e͟͟ ̶͝in͢ ͏t̕h̷̡͟e ͟͟d̛a͜r̕͡k̢̨ ͡h̴e͏a̷̢̡rt́͏ ̴̷͠ò̵̶f̸ u̧͘ní̛͜c͢͏o̷͏d̸͢e̡͝?͞';
|
||||
const hd = new HDSegwitElectrumSeedP2WPKHWallet();
|
||||
hd.setSecret(mnemonic);
|
||||
hd.setPassphrase(UNICODE_HORROR);
|
||||
|
||||
assert.strictEqual(
|
||||
hd.getXpub(),
|
||||
'zpub6nD7dvF6ArArjskKHZLmEL9ky8FqaSti1LN5maDWGwFrqwwGTp1b6ic4EHwciFNaYDmCXcQYxXSiF9BjcLCMPcaYkVN2nQD6QjYQ8vpSR3Z',
|
||||
);
|
||||
|
||||
assert.strictEqual(hd._getExternalAddressByIndex(0), 'bc1qx94dutas7ysn2my645cyttujrms5d9p57f6aam');
|
||||
assert.strictEqual(hd._getInternalAddressByIndex(0), 'bc1qcywwsy87sdp8vz5rfjh3sxdv6rt95kujdqq38g');
|
||||
assert.strictEqual(hd._getExternalWIFByIndex(0), 'KyBagP6JHrNTGanqBSDVzKrsBTVbD9hhkTeVe1zEhewKeCU6wJb7');
|
||||
});
|
||||
});
|
||||
|
@ -1,5 +1,6 @@
|
||||
import assert from 'assert';
|
||||
|
||||
import { SegwitP2SHWallet, SegwitBech32Wallet, HDSegwitP2SHWallet, HDLegacyP2PKHWallet, LegacyWallet } from '../../class';
|
||||
const assert = require('assert');
|
||||
|
||||
describe('P2SH Segwit HD (BIP49)', () => {
|
||||
it('can create a wallet', async () => {
|
||||
@ -106,7 +107,7 @@ describe('P2SH Segwit HD (BIP49)', () => {
|
||||
'honey risk juice trip orient galaxy win situate shoot anchor bounce remind horse traffic exotic since escape mimic ramp skin judge owner topple erode';
|
||||
let hd = new HDSegwitP2SHWallet();
|
||||
hd.setSecret(mnemonic);
|
||||
const seed1 = hd.getMnemonicToSeedHex();
|
||||
const seed1 = hd._getSeed().toString('hex');
|
||||
assert.ok(hd.validateMnemonic());
|
||||
|
||||
mnemonic = 'hell';
|
||||
@ -120,7 +121,7 @@ describe('P2SH Segwit HD (BIP49)', () => {
|
||||
' honey risk juice trip orient galaxy win !situate ;; shoot ;;; anchor Bounce remind\nhorse \n traffic exotic since escape mimic ramp skin judge owner topple erode ';
|
||||
hd = new HDSegwitP2SHWallet();
|
||||
hd.setSecret(mnemonic);
|
||||
const seed2 = hd.getMnemonicToSeedHex();
|
||||
const seed2 = hd._getSeed().toString('hex');
|
||||
assert.strictEqual(seed1, seed2);
|
||||
assert.ok(hd.validateMnemonic());
|
||||
});
|
||||
@ -189,4 +190,21 @@ describe('P2SH Segwit HD (BIP49)', () => {
|
||||
hd.setSecret(mnemonic);
|
||||
assert.strictEqual(hd.getMasterFingerprintHex(), '73C5DA0A');
|
||||
});
|
||||
|
||||
it('can use mnemonic with passphrase', () => {
|
||||
const mnemonic = 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about';
|
||||
const passphrase = 'super secret passphrase';
|
||||
const hd = new HDSegwitP2SHWallet();
|
||||
hd.setSecret(mnemonic);
|
||||
hd.setPassphrase(passphrase);
|
||||
|
||||
assert.strictEqual(
|
||||
hd.getXpub(),
|
||||
'ypub6Xa3WiyXHriYt1fxZGWS8B1iduw92yxHCMWKSwJkW6w92FUCTJxwWQQHLXjRHBSsMLY6SvRu8ErqFEC3JmrkTHEm7KSUfbzhUhj7Yopo2JR',
|
||||
);
|
||||
|
||||
assert.strictEqual(hd._getExternalAddressByIndex(0), '3BtnNenqpGTXwwjb5a1KgzzoKV4TjCuySm');
|
||||
assert.strictEqual(hd._getInternalAddressByIndex(0), '3EJctafkUBvcSHYhunQRa2iYUHjrMGLXBV');
|
||||
assert.strictEqual(hd._getExternalWIFByIndex(0), 'L489rJZvUMrFsNop9EyuG2XdEmyKNTbjC1DWkg9WGEc1ddK6jgDg');
|
||||
});
|
||||
});
|
||||
|
@ -75,4 +75,60 @@ describe('SLIP39 wallets tests', () => {
|
||||
assert.strictEqual(w._getInternalAddressByIndex(0), 'bc1qgx35amln8aryyr0lw6j2729l3gemzjftp5xrne');
|
||||
assert.strictEqual(w._getInternalAddressByIndex(1), 'bc1q48v0hcuz2jjsls628wj8jtn7rqp8wsyz2gxdxm');
|
||||
});
|
||||
|
||||
// tests below are from https://github.com/spesmilo/electrum/pull/6917/files#diff-2940d8023ed102277f9c8b91135a9d6fa90fd2752b7b6147c1b5911f26db6d7fR497
|
||||
it('SLIP39LegacyP2PKHWallet can use passphrase', async () => {
|
||||
const w = new SLIP39LegacyP2PKHWallet();
|
||||
w.setSecret(
|
||||
'extra extend academic bishop cricket bundle tofu goat apart victim enlarge program behavior permit course armed jerky faint language modern\n' +
|
||||
'extra extend academic acne away best indicate impact square oasis prospect painting voting guest either argue username racism enemy eclipse\n' +
|
||||
'extra extend academic arcade born dive legal hush gross briefing talent drug much home firefly toxic analysis idea umbrella slice',
|
||||
);
|
||||
w.setPassphrase('TREZOR');
|
||||
|
||||
assert.strictEqual(
|
||||
w.getXpub(),
|
||||
'xpub6CDgeTHt97zmW24XoYdjH9PVFMUj6qcYAe9SZXMKRKJbvTNeZhutNkQqajLyZrQ9DCqdnGenKhBD6UTrT1nHnoLCfFHkdeX8hDsZx1je6b2',
|
||||
);
|
||||
|
||||
assert.strictEqual(w._getExternalAddressByIndex(0), '1NomKAUNnbASwbPuGHmkSVmnrJS5tZeVce');
|
||||
assert.strictEqual(w._getInternalAddressByIndex(0), '1Aw4wpXsAyEHSgMZqPdyewoAtJqH9Jaso3');
|
||||
assert.strictEqual(w._getExternalWIFByIndex(0), 'L55ZfXJgyXNPFrFtq2eqnwAkrjrarkKdmbkTEWxYXd2srXcfj3KF');
|
||||
});
|
||||
|
||||
it('SLIP39SegwitP2SHWallet can use passphrase', async () => {
|
||||
const w = new SLIP39SegwitP2SHWallet();
|
||||
w.setSecret(
|
||||
'hobo romp academic axis august founder knife legal recover alien expect emphasis loan kitchen involve teacher capture rebuild trial numb spider forward ladle lying voter typical security quantity hawk legs idle leaves gasoline\n' +
|
||||
'hobo romp academic agency ancestor industry argue sister scene midst graduate profile numb paid headset airport daisy flame express scene usual welcome quick silent downtown oral critical step remove says rhythm venture aunt',
|
||||
);
|
||||
w.setPassphrase('TREZOR');
|
||||
|
||||
assert.strictEqual(
|
||||
w.getXpub(),
|
||||
'ypub6Y6aCjkcjCP2y7jZStTevc8Tj3GjoXncqC4ReMzdVZWScB68vKZSZBZ88ENvuPUXXBBR58JXkuz1UrwLnCFvnFTUEpzu5yQabeYBRyd7Edf',
|
||||
);
|
||||
|
||||
assert.strictEqual(w._getExternalAddressByIndex(0), '3GCgNoWWVqVdhBxWxrnWQHgwLtffGSYn7D');
|
||||
assert.strictEqual(w._getInternalAddressByIndex(0), '3FVvdRhR7racZhmcvrGAqX9eJoP8Sw3ypp');
|
||||
});
|
||||
|
||||
it('SLIP39SegwitBech32Wallet can use passphrase', async () => {
|
||||
const w = new SLIP39SegwitBech32Wallet();
|
||||
w.setSecret(
|
||||
'eraser senior beard romp adorn nuclear spill corner cradle style ancient family general leader ambition exchange unusual garlic promise voice\n' +
|
||||
'eraser senior ceramic snake clay various huge numb argue hesitate auction category timber browser greatest hanger petition script leaf pickup\n' +
|
||||
'eraser senior ceramic shaft dynamic become junior wrist silver peasant force math alto coal amazing segment yelp velvet image paces\n' +
|
||||
'eraser senior ceramic round column hawk trust auction smug shame alive greatest sheriff living perfect corner chest sled fumes adequate',
|
||||
);
|
||||
w.setPassphrase('TREZOR');
|
||||
|
||||
assert.strictEqual(
|
||||
w.getXpub(),
|
||||
'zpub6rs6bFckxdWVHBucVX129aNAYqiwPwh1HgsWt6HEQBa9F9QBKRcYzsw7WZR7rPSCWKmRVTUaEgrGrHStx2LSTpbgAEerbnrh4XxkRXbUUZF',
|
||||
);
|
||||
|
||||
assert.strictEqual(w._getExternalAddressByIndex(0), 'bc1qaggygkqgqjjpt58zrmhvjz5m9dj8mjshw0lpgu');
|
||||
assert.strictEqual(w._getInternalAddressByIndex(0), 'bc1q8l6hcvlczu4mtjcnlwhczw7vdxnvwccpjl3cwz');
|
||||
});
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user