View on GitHub
File Changes
// @flow
import React, { Component } from 'react';
-
import { join } from 'lodash';
import { observer } from 'mobx-react';
import { defineMessages, intlShape } from 'react-intl';
import { Autocomplete } from 'react-polymorph/lib/components/Autocomplete';
import { AutocompleteSkin } from 'react-polymorph/lib/skins/simple/AutocompleteSkin';
import WalletRestoreDialog from './widgets/WalletRestoreDialog';
import ReactToolboxMobxForm from '../../../utils/ReactToolboxMobxForm';
import globalMessages from '../../../i18n/global-messages';
-
import { isValidMnemonic } from '../../../../../common/crypto/decrypt';
import validWords from '../../../../../common/crypto/valid-words.en';
-
import {
-
  WALLET_KINDS,
-
  WALLET_DAEDALUS_WORD_COUNT,
-
  WALLET_YOROI_WORD_COUNT,
-
  WALLET_HARDWARE_WORD_COUNT,
-
} from '../../../config/walletRestoreConfig';
import type {
  WalletKind,
  WalletDaedalusKind,
  WalletYoroiKind,
  WalletHardwareKind,
} from '../../../types/walletRestoreTypes';
-
import { PAPER_WALLET_RECOVERY_PHRASE_WORD_COUNT } from '../../../config/cryptoConfig';

                      
const messages = defineMessages({
  autocompletePlaceholder: {
  onClose: Function,
  onBack: Function,
  onSetWalletMnemonics: Function,
+
  onValidateMnemonics: Function,
  mnemonics: Array<string>,
  walletKind: ?WalletKind,
  walletKindDaedalus: ?WalletDaedalusKind,
  walletKindYoroi: ?WalletYoroiKind,
  walletKindHardware: ?WalletHardwareKind,
+
  expectedWordCount: Array<number> | number,
+
  maxWordCount: number,
};

                      
@observer

                      
  recoveryPhraseAutocomplete: Autocomplete;

                      
-
  get expectedWordCount() {
-
    const {
-
      walletKind,
-
      walletKindDaedalus,
-
      walletKindYoroi,
-
      walletKindHardware,
-
    } = this.props;
-
    let expectedWordCount = 0;
-
    if (walletKindDaedalus && walletKind === WALLET_KINDS.DAEDALUS) {
-
      expectedWordCount = WALLET_DAEDALUS_WORD_COUNT[walletKindDaedalus];
-
    } else if (walletKindYoroi && walletKind === WALLET_KINDS.YOROI) {
-
      expectedWordCount = WALLET_YOROI_WORD_COUNT[walletKindYoroi];
-
    } else if (walletKindHardware) {
-
      expectedWordCount = WALLET_HARDWARE_WORD_COUNT[walletKindHardware];
-
    }
-
    return expectedWordCount;
-
  }
-

                      
-
  get maxWordCount() {
-
    const { expectedWordCount } = this;
-
    return Array.isArray(expectedWordCount)
-
      ? Math.max(...expectedWordCount)
-
      : expectedWordCount;
-
  }
-

                      
  form = new ReactToolboxMobxForm(
    {
      fields: {
        recoveryPhrase: {
          value: this.props.mnemonics,
          validators: () => {
            const { intl } = this.context;
-
            const { mnemonics: enteredWords } = this.props;
-
            const { expectedWordCount } = this;
-
            const wordCount = enteredWords.length;
+
            const {
+
              mnemonics,
+
              onValidateMnemonics,
+
              expectedWordCount,
+
            } = this.props;
+
            const wordCount = mnemonics.length;
            const isPhraseComplete = Array.isArray(expectedWordCount)
              ? expectedWordCount.includes(wordCount)
              : wordCount === expectedWordCount;
                    }),
              ];
            }
-
            const value = join(enteredWords, ' ');
            return [
-
              this.expectedWordCount === PAPER_WALLET_RECOVERY_PHRASE_WORD_COUNT
-
                ? true
-
                : isValidMnemonic(value, wordCount),
+
              onValidateMnemonics(mnemonics, wordCount),
              intl.formatMessage(messages.invalidRecoveryPhrase),
            ];
          },

                      
  render() {
    const { intl } = this.context;
-
    const { onClose, onBack, mnemonics, onSetWalletMnemonics } = this.props;
+
    const {
+
      onClose,
+
      onBack,
+
      mnemonics,
+
      onSetWalletMnemonics,
+
      maxWordCount,
+
      expectedWordCount,
+
    } = this.props;
    const recoveryPhraseField = this.form.$('recoveryPhrase');
-
    const { expectedWordCount, maxWordCount } = this;
    return (
      <WalletRestoreDialog
        stepNumber={1}
import MnemonicsDialog from '../../../../components/wallet/wallet-restore/MnemonicsDialog';
import type { InjectedDialogContainerStepProps } from '../../../../types/injectedPropsType';
import { InjectedDialogContainerStepDefaultProps } from '../../../../types/injectedPropsType';
+
import { isValidMnemonic } from '../../../../../../common/crypto/decrypt';
+
import {
+
  getScrambledInput,
+
  unscramblePaperWalletMnemonic,
+
} from '../../../../utils/crypto';
+

                      
+
import {
+
  WALLET_KINDS,
+
  WALLET_DAEDALUS_WORD_COUNT,
+
  WALLET_YOROI_WORD_COUNT,
+
  WALLET_HARDWARE_WORD_COUNT,
+
} from '../../../../config/walletRestoreConfig';
+
import {
+
  PAPER_WALLET_RECOVERY_PHRASE_WORD_COUNT,
+
  LEGACY_WALLET_RECOVERY_PHRASE_WORD_COUNT,
+
} from '../../../../config/cryptoConfig';
+
import type {
+
  WalletKind,
+
  WalletDaedalusKind,
+
  WalletYoroiKind,
+
  WalletHardwareKind,
+
} from '../../../../types/walletRestoreTypes';

                      
type Props = InjectedDialogContainerStepProps;
const DefaultProps = InjectedDialogContainerStepDefaultProps;
  handleSetWalletMnemonics = (mnemonics: Array<string>) =>
    this.props.actions.wallets.restoreWalletSetMnemonics.trigger({ mnemonics });

                      
+
  handleValidateMnemonics = (mnemonics: Array<string>): boolean => {
+
    let enteredWords = mnemonics;
+
    let numberOfWords = mnemonics.length;
+
    const {
+
      walletKind,
+
      walletKindDaedalus,
+
      walletKindYoroi,
+
      walletKindHardware,
+
    } = this.props.stores.wallets;
+
    const expectedWordCount = this.getExpectedWordCount(
+
      walletKind,
+
      walletKindDaedalus,
+
      walletKindYoroi,
+
      walletKindHardware
+
    );
+
    if (expectedWordCount === PAPER_WALLET_RECOVERY_PHRASE_WORD_COUNT) {
+
      numberOfWords = LEGACY_WALLET_RECOVERY_PHRASE_WORD_COUNT;
+
      const { passphrase, scrambledInput } = getScrambledInput(mnemonics);
+
      try {
+
        enteredWords = unscramblePaperWalletMnemonic(
+
          passphrase,
+
          scrambledInput
+
        );
+
      } catch (e) {
+
        return false;
+
      }
+
    }
+
    return isValidMnemonic(enteredWords.join(' '), numberOfWords);
+
  };
+

                      
+
  getExpectedWordCount = (
+
    walletKind: ?WalletKind,
+
    walletKindDaedalus: ?WalletDaedalusKind,
+
    walletKindYoroi: ?WalletYoroiKind,
+
    walletKindHardware: ?WalletHardwareKind
+
  ): Array<number> | number => {
+
    let expectedWordCount = 0;
+
    if (walletKindDaedalus && walletKind === WALLET_KINDS.DAEDALUS) {
+
      expectedWordCount = WALLET_DAEDALUS_WORD_COUNT[walletKindDaedalus];
+
    } else if (walletKindYoroi && walletKind === WALLET_KINDS.YOROI) {
+
      expectedWordCount = WALLET_YOROI_WORD_COUNT[walletKindYoroi];
+
    } else if (walletKindHardware) {
+
      expectedWordCount = WALLET_HARDWARE_WORD_COUNT[walletKindHardware];
+
    }
+
    return expectedWordCount;
+
  };
+

                      
+
  getMaxWordCount = (expectedWordCount: Array<number> | number): number =>
+
    Array.isArray(expectedWordCount)
+
      ? Math.max(...expectedWordCount)
+
      : expectedWordCount;
+

                      
  render() {
    const { onContinue, onClose, onBack, stores } = this.props;
    const {
      walletKindHardware,
      mnemonics,
    } = stores.wallets;
+
    const expectedWordCount = this.getExpectedWordCount(
+
      walletKind,
+
      walletKindDaedalus,
+
      walletKindYoroi,
+
      walletKindHardware
+
    );
+
    const maxWordCount = this.getMaxWordCount(expectedWordCount);
    return (
      <MnemonicsDialog
        onClose={onClose}
        onContinue={onContinue}
        onBack={onBack}
+
        onValidateMnemonics={this.handleValidateMnemonics}
        walletKind={walletKind}
        walletKindDaedalus={walletKindDaedalus}
        walletKindYoroi={walletKindYoroi}
        walletKindHardware={walletKindHardware}
        onSetWalletMnemonics={this.handleSetWalletMnemonics}
        mnemonics={mnemonics}
+
        expectedWordCount={expectedWordCount}
+
        maxWordCount={maxWordCount}
      />
    );
  }
// @flow
import { observable, action, computed, runInAction, flow } from 'mobx';
-
import { get, chunk, find, findIndex, isEqual } from 'lodash';
+
import { get, find, findIndex, isEqual } from 'lodash';
import moment from 'moment';
import { BigNumber } from 'bignumber.js';
import { Address } from 'cardano-js';
import { WalletTransaction } from '../domains/WalletTransaction';
import { MAX_ADA_WALLETS_COUNT } from '../config/numbersConfig';
import { i18nContext } from '../utils/i18nContext';
-
import { mnemonicToSeedHex } from '../utils/crypto';
+
import { mnemonicToSeedHex, getScrambledInput } from '../utils/crypto';
import { paperWalletPdfGenerator } from '../utils/paperWalletPdfGenerator';
import { addressPDFGenerator } from '../utils/addressPDFGenerator';
import { downloadRewardsCsv } from '../utils/rewardsCsvGenerator';
  WALLET_HARDWARE_KINDS,
  RESTORE_WALLET_STEPS,
} from '../config/walletRestoreConfig';
-
import { ADA_CERTIFICATE_MNEMONIC_LENGTH } from '../config/cryptoConfig';
import type {
  WalletKind,
  WalletDaedalusKind,
  };

                      
  _unscrambleMnemonics = async (): Array<string> => {
-
    // Reset getWalletRecoveryPhraseFromCertificateRequest to clear previous errors
-
    this.getWalletRecoveryPhraseFromCertificateRequest.reset();
-

                      
    // Split recovery phrase to 18 (scrambled mnemonics) + 9 (mnemonics seed) mnemonics
-
    const recoveryPhraseArray = this.mnemonics;
-
    const chunked = chunk(recoveryPhraseArray, ADA_CERTIFICATE_MNEMONIC_LENGTH);
-
    const scrambledInput = chunked[0]; // first 18 mnemonics
-
    const certificatePassword = chunked[1]; // last 9 mnemonics
-
    const spendingPassword = mnemonicToSeedHex(certificatePassword.join(' '));
+
    const { passphrase, scrambledInput } = getScrambledInput(this.mnemonics);

                      
    // Unscramble 18-word wallet certificate mnemonic to 12-word mnemonic
    const unscrambledRecoveryPhrase: Array<string> = await this.getWalletRecoveryPhraseFromCertificateRequest.execute(
      {
-
        passphrase: spendingPassword,
-
        scrambledInput: scrambledInput.join(' '),
+
        passphrase,
+
        scrambledInput,
      }
    ).promise;

                      
      WALLET_KINDS.DAEDALUS &&
      this.walletKindDaedalus === WALLET_DAEDALUS_KINDS.BALANCE_27_WORD
    ) {
+
      // Reset getWalletRecoveryPhraseFromCertificateRequest to clear previous errors
+
      this.getWalletRecoveryPhraseFromCertificateRequest.reset();
      data.recoveryPhrase = await this._unscrambleMnemonics();
    }

                      
// @flow
import bip39 from 'bip39';
import { Buffer } from 'safe-buffer';
+
import { chunk } from 'lodash';
import { pbkdf2Sync as pbkdf2 } from 'pbkdf2';
import * as unorm from 'unorm';
import CardanoCrypto from 'rust-cardano-crypto';
import validWords from '../../../common/crypto/valid-words.en';
+
import { ADA_CERTIFICATE_MNEMONIC_LENGTH } from '../config/cryptoConfig';

                      
/**
  CS = ENT / 32
  return scrambledInput.split(' ');
};

                      
+
export const getScrambledInput = (mnemonics: Array<string>) => {
+
  const chunked = chunk(mnemonics, ADA_CERTIFICATE_MNEMONIC_LENGTH);
+
  const scrambledInput = chunked[0].join(' '); // first 18 mnemonics
+
  const certificatePassword = chunked[1]; // last 9 mnemonics
+
  const passphrase = mnemonicToSeedHex(certificatePassword.join(' '));
+
  return { passphrase, scrambledInput };
+
};
+

                      
export const unscramblePaperWalletMnemonic = (
  passphrase: string,
  scrambledInput: string
        walletKindYoroi={walletKindSpecificSelect}
        walletKindHardware={walletKindSpecificSelect}
        mnemonics={[]}
+
        expectedWordCount={0}
+
        maxWordCount={0}
+
        onValidateMnemonics={action('onValidateMnemonics')}
      />
    );
  })