Skip to content

Commit

Permalink
feat/721: Return and display invalid word in mnemonic phrase (#732)
Browse files Browse the repository at this point in the history
* feat: return and display invalid word in phrase

* fix: point to tiny-bip39 commit until this feature is released

* fix: clean up
  • Loading branch information
jurevans authored Apr 22, 2024
1 parent 2460852 commit 28841b9
Show file tree
Hide file tree
Showing 6 changed files with 34 additions and 11 deletions.
3 changes: 3 additions & 0 deletions apps/extension/src/Setup/Common/SeedPhraseList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ type SeedPhraseListProps = {
words: string[];
sensitive?: boolean;
columns?: number;
invalidWordIndex?: number;
onChange?: (index: number, value: string) => void;
onPaste?: (index: number, e: React.ClipboardEvent<HTMLInputElement>) => void;
};
Expand All @@ -17,6 +18,7 @@ export const SeedPhraseList = ({
onPaste,
columns = 3,
sensitive = true,
invalidWordIndex,
}: SeedPhraseListProps): JSX.Element => {
const list = (
<ol
Expand All @@ -29,6 +31,7 @@ export const SeedPhraseList = ({
key={`seed-phrase-list-${idx}`}
word={word}
idx={idx}
invalidWordIndex={invalidWordIndex}
onChange={onChange}
onPaste={onPaste}
/>
Expand Down
13 changes: 8 additions & 5 deletions apps/extension/src/Setup/Common/SeedPhraseListItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,28 @@ import clsx from "clsx";

type SeedPhraseListItemProps = {
idx: number;
invalidWordIndex?: number;
word: string;
onChange?: (index: number, value: string) => void;
onPaste?: (idx: number, e: React.ClipboardEvent<HTMLInputElement>) => void;
};

export const SeedPhraseListItem = ({
idx,
invalidWordIndex,
word,
onChange,
onPaste,
}: SeedPhraseListItemProps): JSX.Element => {
const hasError = idx === invalidWordIndex;
return (
<li
className={clsx(
"relative bg-black rounded-sm text-neutral-500 text-sm font-light",
"px-1 py-3 h-[48px]"
)}
>
{onChange ? (
{onChange ?
<span
className={clsx(
"flex items-center absolute left-0 top-0 w-full h-full",
Expand All @@ -35,21 +38,21 @@ export const SeedPhraseListItem = ({
<Input
label=""
className="-mt-2 ml-1"
variant="PasswordOnBlur"
variant={hasError ? "Text" : "PasswordOnBlur"}
hideIcon={true}
onChange={(e) => onChange(idx, e.target.value)}
onPaste={(e) => onPaste && onPaste(idx, e)}
value={word}
error={hasError}
/>
</span>
) : (
<span
: <span
className={clsx("absolute font-light left-2.5 top-[1em] select-none")}
>
<i className="not-italic">{idx + 1} </i>
<span className="text-white font-bold ml-1">{word}</span>
</span>
)}
}
</li>
);
};
21 changes: 18 additions & 3 deletions apps/extension/src/Setup/ImportAccount/SeedPhraseImport.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export const SeedPhraseImport: React.FC<Props> = ({ onConfirm }) => {
const requester = useRequester();
const [privateKey, setPrivateKey] = useState("");
const [passphrase, setPassphrase] = useState("");
const [invalidWordIndex, setInvalidWordIndex] = useState<number>();
const [showPassphrase, setShowPassphrase] = useState(false);
const [mnemonicType, setMnemonicType] = useState<MnemonicTypes>(
MnemonicTypes.TwelveWords
Expand Down Expand Up @@ -65,8 +66,8 @@ export const SeedPhraseImport: React.FC<Props> = ({ onConfirm }) => {
})();

const isSubmitButtonDisabled =
mnemonicType === MnemonicTypes.PrivateKey
? privateKey === "" || privateKeyError !== ""
mnemonicType === MnemonicTypes.PrivateKey ?
privateKey === "" || privateKeyError !== ""
: mnemonics.slice(0, mnemonicType).some((mnemonic) => !mnemonic);

const onPaste = useCallback(
Expand Down Expand Up @@ -135,7 +136,20 @@ export const SeedPhraseImport: React.FC<Props> = ({ onConfirm }) => {
setMnemonicError(undefined);
onConfirm({ t: "Mnemonic", seedPhrase: actualMnemonics, passphrase });
} else {
setMnemonicError(error);
const isInvalidWord = /^invalid word in phrase with index \d+$/.test(
`${error}`
);
if (isInvalidWord) {
// get index from error
const matches = error?.match(/\d+$/);
const invalidWordIndex = matches ? parseInt(matches[0]) : undefined;
setInvalidWordIndex(invalidWordIndex);
typeof invalidWordIndex === "number" ?
setMnemonicError(`Word #${invalidWordIndex + 1} is invalid!`)
: setMnemonicError(error);
} else {
setMnemonicError(error);
}
}
}
}, [mnemonics, mnemonicType, privateKey, passphrase, showPassphrase]);
Expand Down Expand Up @@ -210,6 +224,7 @@ export const SeedPhraseImport: React.FC<Props> = ({ onConfirm }) => {

{mnemonicType !== MnemonicTypes.PrivateKey && (
<SeedPhraseList
invalidWordIndex={invalidWordIndex}
sensitive={false}
columns={mnemonicType === MnemonicTypes.TwentyFourWords ? 4 : 3}
words={fillArray(mnemonics.slice(0, mnemonicType), mnemonicType)}
Expand Down
3 changes: 1 addition & 2 deletions packages/crypto/lib/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/crypto/lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ thiserror = "1.0.30"
rand = {version = "0.7", features = ["wasm-bindgen"]}
wasm-bindgen = "0.2.86"
zeroize = "1.6.0"
tiny-bip39 = "1.0.0"
tiny-bip39 = { git = "https://github.com/anoma/tiny-bip39", rev = "743d537349c8deab14409ce726b868dcde90fd8e" }
slip10_ed25519 = "0.1.3"

[dev-dependencies]
Expand Down
3 changes: 3 additions & 0 deletions packages/crypto/lib/src/crypto/bip39.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ impl Mnemonic {
}

pub fn from_phrase(mut phrase: String) -> Result<Mnemonic, String> {
// First validate phrase, provide error to client if this fails
M::validate(&phrase, Language::English).map_err(|e| format!("{}", e))?;

let mnemonic = M::from_phrase(&phrase, Language::English)
.map_err(|e| format!("{}: {:?}", Bip39Error::InvalidPhrase, e))?;

Expand Down

0 comments on commit 28841b9

Please sign in to comment.