Skip to content

Commit

Permalink
feat: validate mnemonic phrase against BIP39 wordlist (#739)
Browse files Browse the repository at this point in the history
Co-authored-by: Zachary Johnson <[email protected]>
Co-authored-by: theborakompanioni <[email protected]>
  • Loading branch information
3 people authored Apr 22, 2024
1 parent 24a9026 commit 69e8fa7
Show file tree
Hide file tree
Showing 4 changed files with 2,099 additions and 5 deletions.
4 changes: 2 additions & 2 deletions src/components/MnemonicPhraseInput.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useEffect, useRef, useState } from 'react'
import MnemonicWordInput from './MnemonicWordInput'
import { Bip39MnemonicWordInput } from './MnemonicWordInput'

interface MnemonicPhraseInputProps {
columns?: number
Expand Down Expand Up @@ -44,7 +44,7 @@ export default function MnemonicPhraseInput({
const isCurrentActive = wordIndex === activeIndex
return (
<div className="col" key={wordIndex}>
<MnemonicWordInput
<Bip39MnemonicWordInput
forwardRef={(el: HTMLInputElement) => (inputRefs.current[wordIndex] = el)}
index={wordIndex}
value={givenWord}
Expand Down
37 changes: 37 additions & 0 deletions src/components/MnemonicWordInput.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { render, screen } from '../testUtils'
import { Bip39MnemonicWordInput, MnemonicWordInputProps } from './MnemonicWordInput'

const NOOP = () => {}

describe('<Bip39MnemonicWordInput />', () => {
const validBip39MnemonicWord = 'abandon'
const invalidBip39MnemonicWord = 'not a bip39 word!'

const setup = (props: MnemonicWordInputProps) => {
render(<Bip39MnemonicWordInput {...props} />)
}

it('should render without errors', async () => {
setup({ index: 0, value: '', setValue: NOOP })

expect(await screen.findByTestId('mnemonic-word-input')).toBeVisible()
})

it('should report if input is NOT included in the BIP-39 wordlist', async () => {
setup({ index: 0, value: invalidBip39MnemonicWord, setValue: NOOP })

const input = await screen.findByTestId('mnemonic-word-input')
expect(input).toBeVisible()
expect(input).toHaveClass('is-invalid')
expect(input).not.toHaveClass('is-valid')
})

it('should report if input IS INCLUDED in the BIP-39 wordlist', async () => {
setup({ index: 0, value: validBip39MnemonicWord, setValue: NOOP })

const input = await screen.findByTestId('mnemonic-word-input')
expect(input).toBeVisible()
expect(input).toHaveClass('is-valid')
expect(input).not.toHaveClass('is-invalid')
})
})
13 changes: 10 additions & 3 deletions src/components/MnemonicWordInput.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { useMemo } from 'react'
import * as rb from 'react-bootstrap'
import { useTranslation } from 'react-i18next'
import { MNEMONIC_WORDS } from '../constants/bip39words'
import styles from './MnemonicWordInput.module.css'

interface MnemonicWordInputProps {
forwardRef: (el: HTMLInputElement) => void
export interface MnemonicWordInputProps {
forwardRef?: (el: HTMLInputElement) => void
index: number
value: string
setValue: (value: string, index: number) => void
Expand All @@ -28,6 +30,7 @@ const MnemonicWordInput = ({
<rb.InputGroup>
<rb.InputGroup.Text className={styles.seedwordIndexBackup}>{index + 1}.</rb.InputGroup.Text>
<rb.Form.Control
data-testid="mnemonic-word-input"
ref={forwardRef}
type="text"
placeholder={`${t('create_wallet.placeholder_seed_word_input')} ${index + 1}`}
Expand All @@ -45,4 +48,8 @@ const MnemonicWordInput = ({
)
}

export default MnemonicWordInput
export const Bip39MnemonicWordInput = ({ value, ...props }: MnemonicWordInputProps) => {
const isBip39Value = useMemo(() => MNEMONIC_WORDS.includes(value), [value])

return <MnemonicWordInput {...props} value={value} isValid={isBip39Value && (props.isValid ?? true)} />
}
Loading

0 comments on commit 69e8fa7

Please sign in to comment.