diff --git a/packages/page-accounts/src/modals/Create.tsx b/packages/page-accounts/src/modals/Create.tsx index d8a6405e6f..1180e929cf 100644 --- a/packages/page-accounts/src/modals/Create.tsx +++ b/packages/page-accounts/src/modals/Create.tsx @@ -17,7 +17,7 @@ import { useApi, useLedger, useStepper } from '@polkadot/react-hooks'; import { keyring } from '@polkadot/ui-keyring'; import { settings } from '@polkadot/ui-settings'; import { isHex, u8aToHex } from '@polkadot/util'; -import { hdLedger, keyExtractSuri, mnemonicGenerate, mnemonicValidate, randomAsU8a } from '@polkadot/util-crypto'; +import { hdLedger, hdValidatePath, keyExtractSuri, mnemonicGenerate, mnemonicValidate, randomAsU8a } from '@polkadot/util-crypto'; import { useTranslation } from '../translate'; import CreateConfirmation from './CreateConfirmation'; @@ -25,6 +25,8 @@ import CreateSuriLedger from './CreateSuriLedger'; import ExternalWarning from './ExternalWarning'; import PasswordInput from './PasswordInput'; +const ETH_DEFAULT_PATH = "m/44'/60'/0'/0/0"; + type PairType = 'ecdsa' | 'ed25519' | 'ed25519-ledger' | 'ethereum' | 'sr25519'; interface Props extends ModalProps { @@ -64,12 +66,14 @@ const STEPS_COUNT = 3; function getSuri (seed: string, derivePath: string, pairType: PairType): string { return pairType === 'ed25519-ledger' ? u8aToHex(hdLedger(seed, derivePath).secretKey.slice(0, 32)) - : `${seed}${derivePath}`; + : pairType === 'ethereum' + ? `${seed}/${derivePath}` + : `${seed}${derivePath}`; } function deriveValidate (seed: string, seedType: SeedType, derivePath: string, pairType: PairType): DeriveValidationOutput { try { - const { password, path } = keyExtractSuri(`${seed}${derivePath}`); + const { password, path } = keyExtractSuri(pairType === 'ethereum' ? `${seed}/${derivePath}` : `${seed}${derivePath}`); let result: DeriveValidationOutput = {}; // show a warning in case the password contains an unintended / character @@ -87,6 +91,10 @@ function deriveValidate (seed: string, seedType: SeedType, derivePath: string, p return { ...result, error: 'PASSWORD_IGNORED' }; } + if (pairType === 'ethereum' && !hdValidatePath(derivePath)) { + return { ...result, error: 'INVALID_DERIVATION_PATH' }; + } + return result; } catch (error) { return { error: (error as Error).message }; @@ -134,19 +142,18 @@ function generateSeed (_seed: string | undefined | null, derivePath: string, see } function updateAddress (seed: string, derivePath: string, seedType: SeedType, pairType: PairType): AddressState { - const deriveValidation = deriveValidate(seed, seedType, derivePath, pairType); - + let address: string | null = null; + let deriveValidation: DeriveValidationOutput = deriveValidate(seed, seedType, derivePath, pairType); let isSeedValid = seedType === 'raw' ? rawValidate(seed) : mnemonicValidate(seed); - let address: string | null = null; if (!deriveValidation?.error && isSeedValid) { try { address = addressFromSeed(seed, derivePath, pairType); } catch (error) { console.error(error); - + deriveValidation = { error: (error as Error).message ? (error as Error).message : (error as Error).toString() }; isSeedValid = false; } } @@ -197,7 +204,11 @@ function Create ({ className = '', onClose, onStatusChange, seed: propsSeed, typ const { t } = useTranslation(); const { api, isDevelopment, isEthereum } = useApi(); const { isLedgerEnabled } = useLedger(); - const [{ address, derivePath, deriveValidation, isSeedValid, pairType, seed, seedType }, setAddress] = useState(() => generateSeed(propsSeed, '', propsSeed ? 'raw' : 'bip', isEthereum ? 'ethereum' : propsType)); + const [{ address, derivePath, deriveValidation, isSeedValid, pairType, seed, seedType }, setAddress] = useState(() => generateSeed( + propsSeed, + isEthereum ? ETH_DEFAULT_PATH : '', + propsSeed ? 'raw' : 'bip', isEthereum ? 'ethereum' : propsType + )); const [isMnemonicSaved, setIsMnemonicSaved] = useState(false); const [step, nextStep, prevStep] = useStepper(); const [isBusy, setIsBusy] = useState(false); @@ -208,6 +219,7 @@ function Create ({ className = '', onClose, onStatusChange, seed: propsSeed, typ const isValid = isFirstStepValid && isSecondStepValid; const errorIndex = useRef>({ + INVALID_DERIVATION_PATH: t('This is an invalid derivation path.'), PASSWORD_IGNORED: t('Password are ignored for hex seed'), SOFT_NOT_ALLOWED: t('Soft derivation paths are not allowed on ed25519'), WARNING_SLASH_PASSWORD: t('Your password contains at least one "/" character. Disregard this warning if it is intended.') @@ -240,9 +252,9 @@ function Create ({ className = '', onClose, onStatusChange, seed: propsSeed, typ const _onChangePairType = useCallback( (newPairType: PairType) => setAddress( - updateAddress(seed, '', seedType, newPairType) + updateAddress(seed, isEthereum ? ETH_DEFAULT_PATH : '', seedType, newPairType) ), - [seed, seedType] + [seed, seedType, isEthereum] ); const _selectSeedType = useCallback( @@ -387,13 +399,15 @@ function Create ({ className = '', onClose, onStatusChange, seed: propsSeed, typ label={t('secret derivation path')} onChange={_onChangePath} placeholder={ - seedType === 'raw' - ? pairType === 'sr25519' - ? t('//hard/soft') - : t('//hard') - : pairType === 'sr25519' - ? t('//hard/soft///password') - : t('//hard///password') + pairType === 'ethereum' + ? ETH_DEFAULT_PATH + : seedType === 'raw' + ? pairType === 'sr25519' + ? t('//hard/soft') + : t('//hard') + : pairType === 'sr25519' + ? t('//hard/soft///password') + : t('//hard///password') } tabIndex={-1} value={derivePath}