From 0b1360f61d16fbaadc326e5fd947e53ab20cd8a3 Mon Sep 17 00:00:00 2001
From: "Justin R. Evans" <330911+jurevans@users.noreply.github.com>
Date: Fri, 14 Oct 2022 11:34:56 -0400
Subject: [PATCH] feat/94 - Extension: Storage updates (#97)
* Begin setting up KeyRing & accounts storage
* Adding a simple typed state class to keyring
* Address helper to obtain ImplicitAddress, cargo update
* Continue hooking up KeyRing to services
* Fix broken imports, confirm message works in popup
* Adding msg type for fetch generated mnemonic, update tests
* Beginning components package; get styled-components with theme working
* Disable devtool sourcemapping, add plugin for extension reloading (all browsers)
* Properly include svg assets, port additional components into shared
package
* Fix issue on reloader plugin
* fix module resolution, clean up imports
* Remove unnecessary assignment
* Consolidate types from Keplr into our own
* Validate mnemonic phrase before storing, better error handling in wasm
* Minor clean up, better type State class so as not to instantiate
directly
* Adding mnemonic/password creation screens, added README for types
* Split set-up flow into new tab for initial account
* Begin wiring up account derivation in service, generate implicit address
to store and return
* Add account derivation to completion process, load and display accounts
* Better error-handling, add user feedback on account creation
* Adding AccountListing components
* Clean up configs, layout, fixed bug in key storage
* Improved naming conventions, add implementation for scrypt, tests
* Implement optional custom Scrypt params
* Updated documentation
* Add aes dependency, being basic implementation
* KeyRing state is no longer duplicated, storage is only source of truth
* Add icon for copy to clipboard, additional styling, update styled config
* description -> alias to match cli, clean up
* Adding UI for adding a new derived account with basic validation
* Fix bug where alias is not being saved
* Add "alias" as a field during setup
* Updating for consistency, rough pass at styling derivation form
* Move path items to number, validate inputs, set primes appropriately
* Begin implementing kdf derived key as bytes
* Fix naming on file extension, begin components for password auth
* Add basic login/logout functionality, hook up to service backend
* Clean up effects, check keyring status on auth
* Fix minor bug when locking/unlocking wallet. Switch to numeric input
* Fix sourcemap warning, reuse lock button wrapper, minor style
* Update kdf libs for password hashing, tests, serializing params to
JsValue
* Add serializable struct and associated tests (params + bytes)
* Fix naming convention, create storage type containing params
* Moving storage updates to separate PR
* Adding support for argon2 and scrypt pbkdf hashing
* Cover Scrypt in jest tests
* Clean up
* Remove unused dependencies in this branch
* 2 more dependencies that are unneeded
* Adding dependencies
* Clean up
* Add serialize to key+params (argon2), add tests
* Fix for Cargo
* Improve custom params, add salt as an option
* Initial implementation of AES+Argon2 with related tests
* Clarify naming in jest test
* Update scrypt to accept salt, optional params, similar tests with AES
* Clean up
* Add missing test for scrypt with provided salt, clean up
* Clean up Rust lib, begin to implement encrypt/decrypt in KeyRing
* Add simple Rng mod for generating random bytes
* Touch up TS types
* clean up tests
* Connect KeyRing instance to new storage types, update UI
* Minor styling, match account hierarchy to other wallets
* Improve naming conventions, comments, updated for consistency
* Couple minor fixes that slipped through the cracks
* Minor updates per feedback
* Minor updates per PR feedback
---
.../App/Accounts/AccountListing.components.ts | 22 +-
.../Accounts/AccountListing.components.tsx | 52 ----
.../src/App/Accounts/AccountListing.tsx | 41 ++-
.../src/App/Accounts/Accounts.components.ts | 30 +--
apps/extension/src/App/Accounts/Accounts.tsx | 18 +-
.../extension/src/App/Accounts/AddAccount.tsx | 76 +++---
apps/extension/src/App/Accounts/index.tsx | 3 -
apps/extension/src/App/App.tsx | 7 +
apps/extension/src/App/Login/Login.tsx | 2 +-
.../Steps/Completion/Completion.tsx | 38 +--
.../src/background/keyring/crypto.ts | 65 +++++
.../extension/src/background/keyring/index.ts | 1 +
.../src/background/keyring/keyring.ts | 117 +++++----
.../src/background/keyring/messages.ts | 16 +-
.../extension/src/background/keyring/types.ts | 56 ++++-
apps/extension/src/background/types/Store.ts | 2 +-
packages/crypto/lib/Cargo.lock | 213 +++++++++++++++-
packages/crypto/lib/Cargo.toml | 6 +
packages/crypto/lib/src/crypto/aes.rs | 80 ++++++
packages/crypto/lib/src/crypto/argon2.rs | 230 +++++++++++++++++
packages/crypto/lib/src/crypto/bip32.rs | 23 +-
packages/crypto/lib/src/crypto/bip39.rs | 20 +-
packages/crypto/lib/src/crypto/mod.rs | 6 +
packages/crypto/lib/src/crypto/rng.rs | 56 +++++
packages/crypto/lib/src/crypto/salt.rs | 62 +++++
packages/crypto/lib/src/crypto/scrypt.rs | 234 ++++++++++++++++++
packages/crypto/src/__tests__/crypto.test.ts | 128 +++++++++-
27 files changed, 1330 insertions(+), 274 deletions(-)
delete mode 100644 apps/extension/src/App/Accounts/AccountListing.components.tsx
delete mode 100644 apps/extension/src/App/Accounts/index.tsx
create mode 100644 apps/extension/src/background/keyring/crypto.ts
create mode 100644 packages/crypto/lib/src/crypto/aes.rs
create mode 100644 packages/crypto/lib/src/crypto/argon2.rs
create mode 100644 packages/crypto/lib/src/crypto/rng.rs
create mode 100644 packages/crypto/lib/src/crypto/salt.rs
create mode 100644 packages/crypto/lib/src/crypto/scrypt.rs
diff --git a/apps/extension/src/App/Accounts/AccountListing.components.ts b/apps/extension/src/App/Accounts/AccountListing.components.ts
index de97ac5ab12..2c1671445d3 100644
--- a/apps/extension/src/App/Accounts/AccountListing.components.ts
+++ b/apps/extension/src/App/Accounts/AccountListing.components.ts
@@ -4,13 +4,14 @@ export const AccountListingContainer = styled.div`
display: flex;
flex-direction: row;
width: 100%;
+ font-family: "Space Grotesk", sans-serif;
background-color: ${(props) => props.theme.colors.utility1.main};
+ font-size: 11px;
color: ${(props) => props.theme.colors.utility1.main40};
box-sizing: border-box;
border: 1px solid ${(props) => props.theme.colors.utility1.main};
border-radius: 8px;
box-sizing: border-box;
- font-size: 10px;
padding: 4px 8px;
`;
@@ -21,20 +22,25 @@ export const Details = styled.div`
flex: 3;
`;
+export const DerivationPath = styled.div``;
+
+export const Address = styled.div``;
+
+export const Alias = styled.div`
+ color: ${(props) => props.theme.colors.utility1.main20};
+ font-weight: 500;
+ padding: 4px 0;
+`;
+
export const Buttons = styled.div`
display: flex;
flex-direction: row;
justify-content: flex-end;
+ align-items: center;
flex: 1;
`;
-export const DerivationPath = styled.div``;
-
-export const Address = styled.div``;
-
-export const Alias = styled.div``;
-
-export const CopyToClipboard = styled.a`
+export const Button = styled.a`
text-decoration: underline;
padding: 5px;
transition: "1 sec";
diff --git a/apps/extension/src/App/Accounts/AccountListing.components.tsx b/apps/extension/src/App/Accounts/AccountListing.components.tsx
deleted file mode 100644
index de97ac5ab12..00000000000
--- a/apps/extension/src/App/Accounts/AccountListing.components.tsx
+++ /dev/null
@@ -1,52 +0,0 @@
-import styled from "styled-components";
-
-export const AccountListingContainer = styled.div`
- display: flex;
- flex-direction: row;
- width: 100%;
- background-color: ${(props) => props.theme.colors.utility1.main};
- color: ${(props) => props.theme.colors.utility1.main40};
- box-sizing: border-box;
- border: 1px solid ${(props) => props.theme.colors.utility1.main};
- border-radius: 8px;
- box-sizing: border-box;
- font-size: 10px;
- padding: 4px 8px;
-`;
-
-export const Details = styled.div`
- display: flex;
- flex-direction: column;
- justify-content: flex-start;
- flex: 3;
-`;
-
-export const Buttons = styled.div`
- display: flex;
- flex-direction: row;
- justify-content: flex-end;
- flex: 1;
-`;
-
-export const DerivationPath = styled.div``;
-
-export const Address = styled.div``;
-
-export const Alias = styled.div``;
-
-export const CopyToClipboard = styled.a`
- text-decoration: underline;
- padding: 5px;
- transition: "1 sec";
- border-radius: 4px;
-
- & > div > svg > path {
- fill: ${(props) => props.theme.colors.utility1.main40};
- stroke: ${(props) => props.theme.colors.utility1.main40};
- }
-
- &:active {
- border: 1px solid ${(props) => props.theme.colors.primary.main};
- background-color: ${(props) => props.theme.colors.primary.main};
- }
-`;
diff --git a/apps/extension/src/App/Accounts/AccountListing.tsx b/apps/extension/src/App/Accounts/AccountListing.tsx
index e02aff86adb..4105dc4acaa 100644
--- a/apps/extension/src/App/Accounts/AccountListing.tsx
+++ b/apps/extension/src/App/Accounts/AccountListing.tsx
@@ -1,3 +1,4 @@
+import { useNavigate } from "react-router-dom";
import { Icon, IconName } from "@anoma/components";
import {
AccountListingContainer,
@@ -6,43 +7,63 @@ import {
Details,
Alias,
DerivationPath,
- CopyToClipboard,
+ Button,
} from "./AccountListing.components";
-import { DerivedAccount } from "background/keyring";
+import { Bip44Path, DerivedAccount, KeyStoreType } from "background/keyring";
+import { TopLevelRoute } from "App/types";
type Props = {
+ // The parent Bip44 "account"
+ parent?: number;
+ // The child account
account: DerivedAccount;
+ parentAlias?: string;
};
const textToClipboard = (content: string): void => {
navigator.clipboard.writeText(content);
};
-const AccountListing = ({ account }: Props): JSX.Element => {
- const { address, alias, bip44Path, establishedAddress } = account;
- const coinType = "0'";
+const formatDerivationPath = (
+ isChildAccount: boolean,
+ { account, change, index = 0 }: Bip44Path
+): string => (isChildAccount ? `/${account}'/${change}/${index}` : "");
+
+const AccountListing = ({ account, parentAlias }: Props): JSX.Element => {
+ const { address, alias, path, establishedAddress, type } = account;
+ const navigate = useNavigate();
+ const isChildAccount = type !== KeyStoreType.Mnemonic;
return (
- {alias && {alias}}
- m/44'/{coinType}/{`${bip44Path.account}'`}/{bip44Path.change}/
- {bip44Path.index}
+ {isChildAccount && parentAlias}{" "}
+ {formatDerivationPath(isChildAccount, path)}
+ {alias && {alias}}
{address}
{establishedAddress && {establishedAddress}}
- {
+ navigate(TopLevelRoute.AddAccount);
+ }}
+ >
+
+
+ )}
+
+
);
diff --git a/apps/extension/src/App/Accounts/Accounts.components.ts b/apps/extension/src/App/Accounts/Accounts.components.ts
index e7ae321a5bd..2f68f6d7fba 100644
--- a/apps/extension/src/App/Accounts/Accounts.components.ts
+++ b/apps/extension/src/App/Accounts/Accounts.components.ts
@@ -4,7 +4,7 @@ export const AccountsContainer = styled.div`
display: flex;
flex-direction: column;
height: 100%;
- max-height: 400px;
+ max-height: 420px;
width: 100%;
box-sizing: border-box;
padding: 0 8px;
@@ -24,34 +24,6 @@ export const AccountsListItem = styled.li`
color: ${(props) => props.theme.colors.utility1.main60};
`;
-export const ButtonContainer = styled.div`
- display: flex;
- flex-direction: row;
- justify-content: flex-end;
- box-sizing: border-box;
-`;
-
-export const Button = styled.button`
- display: flex;
- flex-direction: row;
- justify-contents: start;
- align-items: center;
- cursor: pointer;
- color: ${(props) => props.theme.colors.utility1.main40};
- background-color: ${(props) => props.theme.colors.utility1.main};
- border: 1px solid ${(props) => props.theme.colors.utility1.main};
- border-radius: 8px;
-
- & > div > svg > path {
- fill: ${(props) => props.theme.colors.utility1.main40};
- stroke: ${(props) => props.theme.colors.utility1.main40};
- }
-`;
-
-export const ButtonText = styled.span`
- padding: 8px;
-`;
-
export const ThemedScrollbarContainer = styled.div`
overflow-y: auto;
diff --git a/apps/extension/src/App/Accounts/Accounts.tsx b/apps/extension/src/App/Accounts/Accounts.tsx
index 060f902bf59..4e3e9ded0fa 100644
--- a/apps/extension/src/App/Accounts/Accounts.tsx
+++ b/apps/extension/src/App/Accounts/Accounts.tsx
@@ -1,15 +1,8 @@
-import { useNavigate } from "react-router-dom";
-import { Icon, IconName } from "@anoma/components";
-
-import { TopLevelRoute } from "App/types";
import { DerivedAccount } from "background/keyring";
import {
AccountsContainer,
AccountsList,
AccountsListItem,
- ButtonContainer,
- Button,
- ButtonText,
ThemedScrollbarContainer,
} from "./Accounts.components";
import { AccountListing } from "App/Accounts";
@@ -19,7 +12,8 @@ type Props = {
};
const Accounts = ({ accounts }: Props): JSX.Element => {
- const navigate = useNavigate();
+ const parentAccount = accounts[0];
+ const alias = parentAccount?.alias;
return (
@@ -27,16 +21,10 @@ const Accounts = ({ accounts }: Props): JSX.Element => {
{accounts.map((account) => (
-
+
))}
-
-
-
);
diff --git a/apps/extension/src/App/Accounts/AddAccount.tsx b/apps/extension/src/App/Accounts/AddAccount.tsx
index 8dfe398334a..8de976406b7 100644
--- a/apps/extension/src/App/Accounts/AddAccount.tsx
+++ b/apps/extension/src/App/Accounts/AddAccount.tsx
@@ -4,7 +4,11 @@ import { Button, ButtonVariant, Input, InputVariant } from "@anoma/components";
import { ExtensionRequester } from "extension";
import { Ports } from "router";
-import { DerivedAccount, DeriveAccountMsg } from "background/keyring";
+import {
+ DerivedAccount,
+ DeriveAccountMsg,
+ KeyStoreType,
+} from "background/keyring";
import {
AddAccountContainer,
AddAccountForm,
@@ -21,24 +25,24 @@ import {
} from "./AddAccount.components";
type Props = {
+ // The parent Bip44 "account"
+ parentAccount: number;
accounts: DerivedAccount[];
requester: ExtensionRequester;
setAccounts: (accounts: DerivedAccount[]) => void;
};
const validateAccount = (
- newAccount: { account: number; change: number; index: number },
+ account: number,
+ newAccount: { change: number; index: number },
accounts: DerivedAccount[]
): boolean => {
- const newPath = [
- newAccount.account,
- newAccount.change,
- newAccount.index,
- ].join("/");
+ const newPath = [account, newAccount.change, newAccount.index].join("/");
let isValid = true;
- accounts.forEach((a: DerivedAccount) => {
- const { bip44Path } = a;
- const { account, change, index } = bip44Path;
+ accounts.forEach((derivedAccount: DerivedAccount) => {
+ const {
+ path: { account, change, index },
+ } = derivedAccount;
const existingPath = [account, change, index].join("/");
if (newPath === existingPath) {
@@ -51,14 +55,15 @@ const validateAccount = (
const findNextIndex = (accounts: DerivedAccount[]): number => {
let maxIndex = 0;
- accounts.forEach((account) => {
- const { index } = account.bip44Path;
- if (index > maxIndex) {
- maxIndex = index;
- }
- });
- return maxIndex + 1;
+ accounts
+ .filter((account) => account.type !== KeyStoreType.Mnemonic)
+ .forEach((account) => {
+ const { index = 0 } = account.path;
+ maxIndex = index + 1;
+ });
+
+ return maxIndex;
};
enum Status {
@@ -67,10 +72,14 @@ enum Status {
Failed,
}
-const AddAccount: React.FC = ({ accounts, requester, setAccounts }) => {
+const AddAccount: React.FC = ({
+ parentAccount,
+ accounts,
+ requester,
+ setAccounts,
+}) => {
const navigate = useNavigate();
const [alias, setAlias] = useState("");
- const [account, setAccount] = useState(0);
const [change, setChange] = useState(0);
const [index, setIndex] = useState(findNextIndex(accounts));
const [bip44Error, setBip44Error] = useState("");
@@ -78,11 +87,11 @@ const AddAccount: React.FC = ({ accounts, requester, setAccounts }) => {
const [formError, setFormError] = useState("");
const [formStatus, setFormStatus] = useState(Status.Idle);
- const bip44Prefix = "m/44'";
- const coinType = "0'";
+ const bip44Prefix = "m/44";
+ const coinType = 0;
useEffect(() => {
- const isValid = validateAccount({ account, change, index }, accounts);
+ const isValid = validateAccount(parentAccount, { change, index }, accounts);
if (!isValid) {
setBip44Error("Invalid path! This path is already in use.");
setIsFormValid(false);
@@ -90,7 +99,7 @@ const AddAccount: React.FC = ({ accounts, requester, setAccounts }) => {
setBip44Error("");
setIsFormValid(true);
}
- }, [account, change, index]);
+ }, [parentAccount, change, index]);
const handleAccountAdd = async (): Promise => {
setFormStatus(Status.Pending);
@@ -98,7 +107,7 @@ const AddAccount: React.FC = ({ accounts, requester, setAccounts }) => {
const derivedAccount: DerivedAccount =
await requester.sendMessage(
Ports.Background,
- new DeriveAccountMsg({ account, change, index }, alias)
+ new DeriveAccountMsg({ account: parentAccount, change, index }, alias)
);
setAccounts([...accounts, derivedAccount]);
navigate(-1);
@@ -120,6 +129,8 @@ const AddAccount: React.FC = ({ accounts, requester, setAccounts }) => {
const handleFocus = (e: React.ChangeEvent): void =>
e.target.select();
+ const parentDerivationPath = `${bip44Prefix}'/${coinType}'/${parentAccount}'/`;
+
return (
@@ -136,20 +147,11 @@ const AddAccount: React.FC = ({ accounts, requester, setAccounts }) => {
diff --git a/apps/extension/src/App/Accounts/index.tsx b/apps/extension/src/App/Accounts/index.tsx
deleted file mode 100644
index b51a51d24c7..00000000000
--- a/apps/extension/src/App/Accounts/index.tsx
+++ /dev/null
@@ -1,3 +0,0 @@
-export { default as Accounts } from "./Accounts";
-export { default as AccountListing } from "./AccountListing";
-export { default as AddAccount } from "./AddAccount";
diff --git a/apps/extension/src/App/App.tsx b/apps/extension/src/App/App.tsx
index 517e90a6b8d..3df9b385d4f 100644
--- a/apps/extension/src/App/App.tsx
+++ b/apps/extension/src/App/App.tsx
@@ -8,6 +8,7 @@ import { Ports } from "router";
import {
CheckIsLockedMsg,
DerivedAccount,
+ KeyStoreType,
QueryAccountsMsg,
} from "background/keyring";
@@ -83,6 +84,11 @@ export const App: React.FC = () => {
}
}, [status, accounts]);
+ const parent = accounts.find(
+ (account) => account.type === KeyStoreType.Mnemonic
+ );
+ const parentAccount = parent?.path?.account ?? 0;
+
return (
@@ -111,6 +117,7 @@ export const App: React.FC = () => {
element={
= ({ requester }) => {
const [password, setPassword] = useState("");
const [status, setStatus] = useState();
- const handleSubmit = async () => {
+ const handleSubmit = async (): Promise => {
setStatus(Status.Pending);
try {
const { status: lockStatus } = await requester.sendMessage(
diff --git a/apps/extension/src/Setup/AccountCreation/Steps/Completion/Completion.tsx b/apps/extension/src/Setup/AccountCreation/Steps/Completion/Completion.tsx
index 1482a7bc99e..5d13d8e0b7b 100644
--- a/apps/extension/src/Setup/AccountCreation/Steps/Completion/Completion.tsx
+++ b/apps/extension/src/Setup/AccountCreation/Steps/Completion/Completion.tsx
@@ -3,7 +3,7 @@ import { ExtensionRequester } from "extension";
import browser from "webextension-polyfill";
import { Button, ButtonVariant } from "@anoma/components";
-import { DeriveAccountMsg, SaveMnemonicMsg } from "background/keyring";
+import { SaveMnemonicMsg } from "background/keyring";
import { Ports } from "router";
import {
@@ -30,7 +30,6 @@ enum Status {
const Completion: React.FC = (props) => {
const { alias, mnemonic, password, requester } = props;
const [mnemonicStatus, setMnemonicStatus] = useState(Status.Pending);
- const [accountStatus, setAccountStatus] = useState(Status.Pending);
const [isComplete, setIsComplete] = useState(false);
useEffect(() => {
@@ -39,7 +38,7 @@ const Completion: React.FC = (props) => {
try {
await requester.sendMessage(
Ports.Background,
- new SaveMnemonicMsg(mnemonic, password)
+ new SaveMnemonicMsg(mnemonic, password, alias)
);
setMnemonicStatus(Status.Completed);
} catch (e) {
@@ -47,17 +46,6 @@ const Completion: React.FC = (props) => {
setMnemonicStatus(Status.Failed);
}
- try {
- await requester.sendMessage(
- Ports.Background,
- new DeriveAccountMsg({ account: 0, change: 0, index: 0 }, alias)
- );
- setAccountStatus(Status.Completed);
- } catch (e) {
- console.error(e);
- setAccountStatus(Status.Failed);
- }
-
setIsComplete(true);
})();
}
@@ -73,25 +61,15 @@ const Completion: React.FC = (props) => {
popup to view your accounts.
)}
-
{!isComplete && (
One moment while your wallet is being created...
)}
-
-
- -
- Encrypting and storing mnemonic:{" "}
- {mnemonicStatus === Status.Completed && Complete!}
- {mnemonicStatus === Status.Pending && Pending...}
- {mnemonicStatus === Status.Failed && Failed}
-
- -
- Deriving and storing initial account:{" "}
- {accountStatus === Status.Completed && Complete!}
- {accountStatus === Status.Pending && Pending...}
- {accountStatus === Status.Failed && Failed}
-
-
+
+ Encrypting and storing mnemonic:{" "}
+ {mnemonicStatus === Status.Completed && Complete!}
+ {mnemonicStatus === Status.Pending && Pending...}
+ {mnemonicStatus === Status.Failed && Failed}
+