Skip to content

Commit

Permalink
feat: Passphrase and HDPath for seed phrase (#1870)
Browse files Browse the repository at this point in the history
* feat: init passphrase

* feat: passphrase and hdPath

* fix: cache passphrase

* fix: text

* fix: bugs

* fix: text

* feat: update keyring

---------

Co-authored-by: vvvvvv1vvvvvv <galennnnnnn@gmail.com>
  • Loading branch information
heisenberg-2077 and vvvvvv1vvvvvv committed Nov 21, 2023
1 parent b612ca3 commit 4b3eef4
Show file tree
Hide file tree
Showing 30 changed files with 464 additions and 274 deletions.
20 changes: 15 additions & 5 deletions _raw/locales/en/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,8 @@
"seedPhrase": "Seed Phrase address",
"watchAddress": "Unable to sign with watch-only address",
"safe": "Safe address",
"coboSafe": "Cobo Argus Address"
"coboSafe": "Cobo Argus Address",
"seedPhraseWithPassphrase": "Seed Phrase address (Passphrase)"
},
"qrcode": {
"signWith": "Sign with {{brand}}",
Expand Down Expand Up @@ -817,7 +818,10 @@
"sort-by-balance": "Sort by balance",
"sort-by-address-type": "Sort by address type",
"sort-by-address-note": "Sort by address note",
"sort-address": "Sort Address"
"sort-address": "Sort Address",
"enterThePassphrase": "Enter the Passphrase",
"enterPassphraseTitle": "Enter Passphrase to Sign",
"passphraseError": "Passphrase invalid"
},
"dashboard": {
"home": {
Expand Down Expand Up @@ -1138,11 +1142,13 @@
"verifySeedPhrase": "Verify Seed Phrase",
"fillInTheBackupSeedPhraseInOrder": "Fill in the backup seed phrase in order",
"wordPhrase": "I have a <1>{{count}}</1>-word phrase",
"wordPhraseAndPassphrase": "I have a <1>{{count}}</1>-word phrase with Passphrase",
"clearAll": "Clear All",
"pastedAndClear": "Pasted and clipboard cleared",
"invalidContent": "Invalid content",
"inputInvalidCount_one": "1 input do not conform to Seed Phrase norms, please check.",
"inputInvalidCount_other": "{{count}} inputs do not conform to Seed Phrase norms, please check."
"inputInvalidCount_other": "{{count}} inputs do not conform to Seed Phrase norms, please check.",
"passphrase": "Passphrase"
},
"metamask": {
"step1": " Export seed phrase or private key from MetaMask <br /> <1>Click to view tutorial <1/></1>",
Expand Down Expand Up @@ -1276,7 +1282,10 @@
},
"mnemonic": {
"hdPathType": {
"default": "Default: The Default HD path for importing a seed phrase is used."
"default": "Default: The Default HD path for importing a seed phrase is used.",
"bip44": "BIP44 Standard: HDpath defined by the BIP44 protocol.",
"ledgerLive": "Ledger Live: Ledger official HD path.",
"legacy": "Legacy: HD path used by MEW / Mycrypto."
},
"hdPathTypeNoChain": {
"default": "Default: The Default HD path for importing a seed phrase is used."
Expand Down Expand Up @@ -1869,6 +1878,7 @@
"SIGN_PERMISSION_OPTIONS": {
"MAINNET_AND_TESTNET": "Mainnet & Testnet",
"TESTNET": "Only Testnets"
}
},
"IMPORTED_HD_KEYRING_NEED_PASSPHRASE": "Imported by Seed Phrase (Passphrase)"
}
}
5 changes: 4 additions & 1 deletion _raw/locales/zh_CN/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -517,7 +517,10 @@
},
"mnemonic": {
"hdPathType": {
"default": "默认:使用导入助记词的默认 HDpath"
"default": "默认:使用导入助记词的默认 HDpath",
"ledgerLive": "Ledger Live:Ledger 官方 HDpath",
"bip44": "BIP44标准:BIP44 协议定义的 HDpath",
"legacy": "旧版:MEW / Mycrypto 使用的 HDpath"
},
"hdPathTypeNoChain": {
"default": "默认:使用导入助记词的默认 HDpath"
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
"@onekeyfe/hd-core": "^0.3.27",
"@onekeyfe/hd-web-sdk": "^0.3.27",
"@rabby-wallet/eth-gnosis-keyring": "^0.0.1",
"@rabby-wallet/eth-hd-keyring": "^4.0.1",
"@rabby-wallet/eth-hd-keyring": "^4.1.0",
"@rabby-wallet/eth-lattice-keyring": "^1.0.5",
"@rabby-wallet/eth-simple-keyring": "^5.0.1",
"@rabby-wallet/eth-trezor-keyring": "^2.2.0",
Expand Down
103 changes: 84 additions & 19 deletions src/background/controller/wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ import { CoboSafeAccount } from '@/utils/cobo-agrus-sdk/cobo-agrus-sdk';
import CoboArgusKeyring from '../service/keyring/eth-cobo-argus-keyring';
import { GET_WALLETCONNECT_CONFIG } from '@/utils/walletconnect';
import { estimateL1Fee } from '@/utils/l2';
import HdKeyring from '@rabby-wallet/eth-hd-keyring';

const stashKeyrings: Record<string | number, any> = {};

Expand Down Expand Up @@ -1996,7 +1997,7 @@ export class WalletController extends BaseController {
getPreMnemonics = () => keyringService.getPreMnemonics();
generatePreMnemonic = () => keyringService.generatePreMnemonic();
removePreMnemonics = () => keyringService.removePreMnemonics();
createKeyringWithMnemonics = async (mnemonic) => {
createKeyringWithMnemonics = async (mnemonic: string) => {
const keyring = await keyringService.createKeyringWithMnemonics(mnemonic);
keyringService.removePreMnemonics();
// return this._setCurrentAccountFromKeyring(keyring);
Expand Down Expand Up @@ -2090,23 +2091,28 @@ export class WalletController extends BaseController {
};

getKeyringByMnemonic = (
mnemonic: string
): (DisplayedKeryring & { index: number }) | undefined => {
return keyringService.keyrings.find((item) => {
return item.type === KEYRING_CLASS.MNEMONIC && item.mnemonic === mnemonic;
mnemonic: string,
passphrase = ''
): HdKeyring | undefined => {
const keyring = keyringService.keyrings.find((item) => {
return (
item.type === KEYRING_CLASS.MNEMONIC &&
item.mnemonic === mnemonic &&
item.checkPassphrase(passphrase)
);
});

keyring?.setPassphrase(passphrase);

return keyring;
};

_getMnemonicKeyringByAddress = (address: string) => {
return keyringService.keyrings.find((item) => {
return (
item.type === KEYRING_CLASS.MNEMONIC &&
item.mnemonic &&
Object.keys(item._index2wallet).some((key) => {
return (
item._index2wallet[key][0].toLowerCase() === address.toLowerCase()
);
})
item.accounts.includes(address)
);
});
};
Expand Down Expand Up @@ -2154,20 +2160,73 @@ export class WalletController extends BaseController {
return keyring.mnemonic;
};

getMnemonicAddressIndex = async (address: string) => {
private getMnemonicKeyring = async (
type: 'address' | 'publickey',
value: string
) => {
let keyring;
if (type === 'address') {
keyring = await this._getMnemonicKeyringByAddress(value);
} else {
keyring = await this.getMnemonicKeyRingFromPublicKey(value);
}

if (!keyring) {
throw new Error(t('background.error.notFoundKeyringByAddress'));
}

return keyring;
};

getMnemonicKeyringIfNeedPassphrase = async (
type: 'address' | 'publickey',
value: string
) => {
const keyring = await this.getMnemonicKeyring(type, value);
return keyring.needPassphrase;
};

getMnemonicKeyringPassphrase = async (
type: 'address' | 'publickey',
value: string
) => {
const keyring = await this.getMnemonicKeyring(type, value);
return keyring.passphrase;
};

checkPassphraseBelongToMnemonic = async (
type: 'address' | 'publickey',
value: string,
passphrase: string
) => {
const keyring = await this.getMnemonicKeyring(type, value);
const result = keyring.checkPassphrase(passphrase);
if (result) {
keyring.setPassphrase(passphrase);
}
return result;
};

getMnemonicAddressInfo = async (address: string) => {
const keyring = this._getMnemonicKeyringByAddress(address);
if (!keyring) {
throw new Error(t('background.error.notFoundKeyringByAddress'));
}
return await keyring.getIndexByAddress(address);
return await keyring.getInfoByAddress(address);
};

generateKeyringWithMnemonic = async (mnemonic: string) => {
generateKeyringWithMnemonic = async (
mnemonic: string,
passphrase: string
) => {
// keep passphrase is empty string if not set
passphrase = passphrase || '';

if (!bip39.validateMnemonic(mnemonic, wordlist)) {
throw new Error(t('background.error.invalidMnemonic'));
}
// If import twice use same kerying
let keyring = this.getKeyringByMnemonic(mnemonic);
// If import twice use same keyring
let keyring = this.getKeyringByMnemonic(mnemonic, passphrase);
const result = {
keyringId: null as number | null,
isExistedKR: false,
Expand All @@ -2177,7 +2236,7 @@ export class WalletController extends BaseController {
KEYRING_CLASS.MNEMONIC
);

keyring = new Keyring({ mnemonic });
keyring = new Keyring({ mnemonic, passphrase });
keyringService.updateHdKeyringIndex(keyring);
result.keyringId = this.addKeyringToStash(keyring);
keyringService.addKeyring(keyring);
Expand All @@ -2198,7 +2257,10 @@ export class WalletController extends BaseController {

updateKeyringInStash = (keyring) => {
let keyringId = Object.keys(stashKeyrings).find((key) => {
return stashKeyrings[key].mnemonic === keyring.mnemonic;
return (
stashKeyrings[key].mnemonic === keyring.mnemonic &&
stashKeyrings[key].publicKey === keyring.publicKey
);
}) as number | undefined;

if (!keyringId) {
Expand Down Expand Up @@ -2597,9 +2659,10 @@ export class WalletController extends BaseController {
requestHDKeyringByMnemonics = (
mnemonics: string,
methodName: string,
passphrase: string,
...params: any[]
) => {
const keyring = this.getKeyringByMnemonic(mnemonics);
const keyring = this.getKeyringByMnemonic(mnemonics, passphrase);
if (!keyring) {
throw new Error(
'failed to requestHDKeyringByMnemonics, no keyring found.'
Expand All @@ -2612,11 +2675,12 @@ export class WalletController extends BaseController {

activeAndPersistAccountsByMnemonics = async (
mnemonics: string,
passphrase: string,
accountsToImport: Required<
Pick<Account, 'address' | 'alianName' | 'index'>
>[]
) => {
const keyring = this.getKeyringByMnemonic(mnemonics);
const keyring = this.getKeyringByMnemonic(mnemonics, passphrase);
if (!keyring) {
throw new Error(
'[activeAndPersistAccountsByMnemonics] no keyring found.'
Expand All @@ -2625,6 +2689,7 @@ export class WalletController extends BaseController {
await this.requestHDKeyringByMnemonics(
mnemonics,
'activeAccounts',
passphrase,
accountsToImport.map((acc) => acc.index! - 1)
);

Expand Down
32 changes: 0 additions & 32 deletions src/background/service/keyring/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -733,38 +733,6 @@ export class KeyringService extends EventEmitter {
return keyring.exportAccount(address, { withAppKeyOrigin: origin });
}

//
// PRIVATE METHODS
//

/**
* Create First Key Tree
*
* - Clears the existing vault
* - Creates a new vault
* - Creates a random new HD Keyring with 1 account
* - Makes that account the selected account
* - Faucets that account on testnet
* - Puts the current seed words into the state tree
*
* @returns {Promise<void>} - A promise that resovles if the operation was successful.
*/
createFirstKeyTree() {
this.clearKeyrings();
return this.addNewKeyring('HD Key Tree', { activeIndexes: [0] })
.then((keyring) => {
return keyring.getAccounts();
})
.then(([firstAccount]) => {
if (!firstAccount) {
throw new Error('KeyringController - No account found on keychain.');
}
const hexAccount = normalizeAddress(firstAccount);
this.emit('newVault', hexAccount);
return null;
});
}

/**
* Persist All Keyrings
*
Expand Down
7 changes: 6 additions & 1 deletion src/ui/component/AuthenticationModal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ interface AuthenticationModalProps extends WrappedComponentProps {
title?: string;
description?: string;
checklist?: string[];
placeholder?: string;
}

const Description = styled.div`
Expand Down Expand Up @@ -109,6 +110,7 @@ const AuthenticationModal = ({
cancelText,
confirmText = 'Confirm',
title = 'Enter Password',
placeholder,
}: AuthenticationModalProps) => {
const [visible, setVisible] = useState(false);
const [form] = Form.useForm();
Expand Down Expand Up @@ -207,7 +209,10 @@ const AuthenticationModal = ({
>
<Input
className="popup-input"
placeholder={t('component.AuthenticationModal.passwordPlaceholder')}
placeholder={
placeholder ??
t('component.AuthenticationModal.passwordPlaceholder')
}
type="password"
size="large"
autoFocus
Expand Down

0 comments on commit 4b3eef4

Please sign in to comment.