Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: improve mnemonic handling #685

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Wallet } from 'adena-module';
import { AdenaWallet, Wallet } from 'adena-module';
import React, { createContext, useEffect, useState } from 'react';
import { useRecoilState, useSetRecoilState } from 'recoil';

Expand All @@ -17,6 +17,7 @@ export interface WalletContextProps {
initWallet: () => Promise<boolean>;
initNetworkMetainfos: () => Promise<boolean>;
changeNetwork: (network: NetworkMetainfo) => Promise<NetworkMetainfo>;
clearWallet: () => Promise<void>;
}

export const WalletContext = createContext<WalletContextProps | null>(null);
Expand Down Expand Up @@ -82,11 +83,19 @@ export const WalletProvider: React.FC<React.PropsWithChildren<unknown>> = ({ chi

async function updateWallet(wallet: Wallet): Promise<boolean> {
setWallet(wallet);
const password = await walletService.loadWalletPassword();
await walletService.saveWallet(wallet, password);
await walletService.updateWallet(wallet);
return true;
}

async function clearWallet(): Promise<void> {
await setWallet(new AdenaWallet());

await Promise.all([
async (): Promise<void> => await setWallet(null),
async (): Promise<void> => await setCurrentAccount(null),
]);
}

async function initCurrentAccount(wallet: Wallet): Promise<boolean> {
const currentAccountId = await accountService.getCurrentAccountId();
const currentAccount =
Expand Down Expand Up @@ -142,6 +151,7 @@ export const WalletProvider: React.FC<React.PropsWithChildren<unknown>> = ({ chi
updateWallet,
initNetworkMetainfos,
changeNetwork,
clearWallet,
}}
>
{children}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,68 +1,105 @@
import React from 'react';
import styled, { useTheme } from 'styled-components';
import _ from 'lodash';

import { Row, View, WebText } from '../../atoms';
import { getTheme } from '@styles/theme';
import mixins from '@styles/mixins';
import { useEffect, useRef } from 'react';
import styled from 'styled-components';

interface WebSeedBoxProps {
seeds: string[];
showBlur?: boolean;
}

const StyledContainer = styled(View)`
const CanvasContainer = styled.div`
width: 100%;
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 12px;
`;

const StyledItem = styled(Row) <{ showBlur: boolean }>`
const CanvasWrapper = styled.div<{ blur: boolean }>`
position: relative;
overflow: hidden;
width: 100%;
height: 40px;
border-radius: 10px;
border: 1px solid
${({ showBlur, theme }): string => (showBlur ? theme.webNeutral._800 : theme.webNeutral._600)};
box-shadow: 0px 0px 0px 3px rgba(255, 255, 255, 0.04), 0px 1px 3px 0px rgba(0, 0, 0, 0.1),
${({ blur, theme }): string => (blur ? theme.webNeutral._800 : theme.webNeutral._600)};
box-shadow:
0px 0px 0px 3px rgba(255, 255, 255, 0.04),
0px 1px 3px 0px rgba(0, 0, 0, 0.1),
0px 1px 2px 0px rgba(0, 0, 0, 0.06);
overflow: hidden;
`;

const StyledNoText = styled(WebText)`
${mixins.flex()}
background: #181b1f;
border-right: 1px solid ${getTheme('webNeutral', '_800')};
width: 40px;
height: 100%;
align-items: center;
`;

const StyledBlurScreen = styled(View)`
position: absolute;
width: 100%;
height: 100%;
z-index: 1;
background-color: #0000000a;
backdrop-filter: blur(4px);
border-radius: 10px;
`;
const BOX_WIDTH = 185;
const BOX_HEIGHT = 40;
const NUMBER_BOX_WIDTH = 40;

export const WebSeedBox = ({ seeds, showBlur = true }: WebSeedBoxProps): JSX.Element => {
const theme = useTheme();
const canvasRefs = useRef<(HTMLCanvasElement | null)[]>([]);

useEffect(() => {
const dpr = window?.devicePixelRatio || 1;

seeds.forEach((seed, index) => {
const canvas = canvasRefs.current[index];
if (!canvas) return;
const ctx = canvas.getContext('2d');
if (!ctx) return;

canvas.width = BOX_WIDTH;
canvas.height = BOX_HEIGHT;
canvas.style.width = `${BOX_WIDTH}px`;
canvas.style.height = `${BOX_HEIGHT}px`;

// Clear canvas
ctx.clearRect(0, 0, BOX_WIDTH, BOX_HEIGHT);

// Fill number box
ctx.fillStyle = '#181b1f';
ctx.beginPath();
ctx.roundRect(0, 0, NUMBER_BOX_WIDTH, BOX_HEIGHT);
ctx.fill();

// Draw number box border
ctx.beginPath();
ctx.moveTo(NUMBER_BOX_WIDTH + 1, 0);
ctx.lineTo(NUMBER_BOX_WIDTH + 1, BOX_HEIGHT);
ctx.strokeStyle = '#36383D';
ctx.lineWidth = 1;
ctx.stroke();

// Draw seed box blur screen
if (showBlur) {
ctx.filter = 'blur(4px)';
}

// Write seed number text
ctx.fillStyle = '#808080';
ctx.font = '16px Inter';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(`${index + 1}`, NUMBER_BOX_WIDTH / 2, BOX_HEIGHT / 2, NUMBER_BOX_WIDTH);

// Write seed text
ctx.fillStyle = '#FFFFFF';
ctx.font = '16px bold Inter';
ctx.textAlign = 'left';
ctx.fillText(seed, NUMBER_BOX_WIDTH + 12, BOX_HEIGHT / 2);

ctx.filter = 'none';

ctx.scale(dpr, dpr);
});
}, [seeds, showBlur, window?.devicePixelRatio]);

return (
<StyledContainer>
{_.map(seeds, (seed, index) => (
<StyledItem key={`seeds-${index}`} showBlur={showBlur}>
<StyledNoText type='body4' color={theme.webNeutral._500}>
{index + 1}
</StyledNoText>
<WebText type='body4' color={theme.webNeutral._100} style={{ paddingLeft: 12 }}>
{seed}
</WebText>
{showBlur && <StyledBlurScreen />}
</StyledItem>
<CanvasContainer>
{seeds.map((_, index) => (
<CanvasWrapper key={index} blur={showBlur}>
<canvas
ref={(el): HTMLCanvasElement | null => (canvasRefs.current[index] = el)}
width={BOX_WIDTH}
height={BOX_HEIGHT}
/>
</CanvasWrapper>
))}
</StyledContainer>
</CanvasContainer>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { useNavigate } from 'react-router-dom';
import { formatNickname } from '@common/utils/client-utils';
import SideMenu from '@components/pages/router/side-menu/side-menu';
import { useAccountName } from '@hooks/use-account-name';
import { useAdenaContext } from '@hooks/use-context';
import { useAdenaContext, useWalletContext } from '@hooks/use-context';
import { useCurrentAccount } from '@hooks/use-current-account';
import { useLoadAccounts } from '@hooks/use-load-accounts';
import { useNetwork } from '@hooks/use-network';
Expand All @@ -26,6 +26,7 @@ interface SideMenuContainerProps {
const SideMenuContainer: React.FC<SideMenuContainerProps> = ({ open, setOpen }) => {
const { openLink, openRegister } = useLink();
const { walletService } = useAdenaContext();
const { clearWallet } = useWalletContext();
const navigate = useNavigate();
const { changeCurrentAccount } = useCurrentAccount();
const { currentNetwork, scannerParameters } = useNetwork();
Expand Down Expand Up @@ -95,6 +96,7 @@ const SideMenuContainer: React.FC<SideMenuContainerProps> = ({ open, setOpen })
const lock = useCallback(async () => {
setOpen(false);
await walletService.lockWallet();
await clearWallet();
await chrome.runtime.sendMessage(CommandMessage.command('clearPopup'));
await loadAccounts();
navigate(RoutePath.Login, { replace: true });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
isAirgapAccount,
isHDWalletKeyring,
Keyring,
mnemonicToEntropy,
PrivateKeyKeyring,
SeedAccount,
SingleAccount,
Expand Down Expand Up @@ -208,9 +209,11 @@ const useAccountImportScreen = ({ wallet }: { wallet: Wallet }): UseAccountImpor
} else if (step === 'SELECT_ACCOUNT') {
let resultWallet = wallet.clone();

const entropy = mnemonicToEntropy(inputValue);

const storedKeyring = resultWallet.keyrings
.filter(isHDWalletKeyring)
.find((keyring) => keyring.mnemonic === inputValue.trim());
.find((keyring) => keyring.mnemonicEntropy === entropy);
const keyring = storedKeyring || (await HDWalletKeyring.fromMnemonic(inputValue));

for (const account of loadedAccounts) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ type AccountDataModelV008 = {
addressBytes?: number[];
};

type KeyringDataModelV008 = {
export type KeyringDataModelV008 = {
id?: string;
type: 'HD_WALLET' | 'PRIVATE_KEY' | 'LEDGER' | 'WEB3_AUTH' | 'AIRGAP';
publicKey?: number[];
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { decryptAES } from 'adena-module';
import { StorageMigration009 } from './storage-migration-v009';

const mockStorageData = {
NETWORKS: [],
CURRENT_CHAIN_ID: 'test5',
CURRENT_NETWORK_ID: 'test5',
SERIALIZED: 'U2FsdGVkX19eI8kOCI/T9o1Ru0b2wdj5rHxmG4QbLQ0yZH4kDa8/gg6Ac2JslvEm',
ENCRYPTED_STORED_PASSWORD: '',
CURRENT_ACCOUNT_ID: '',
ACCOUNT_NAMES: {},
ESTABLISH_SITES: {},
ADDRESS_BOOK: '',
ACCOUNT_TOKEN_METAINFOS: {},
QUESTIONNAIRE_EXPIRED_DATE: null,
WALLET_CREATION_GUIDE_CONFIRM_DATE: null,
ADD_ACCOUNT_GUIDE_CONFIRM_DATE: null,
ACCOUNT_GRC721_COLLECTIONS: {},
ACCOUNT_GRC721_PINNED_PACKAGES: {},
};

describe('serialized wallet migration V009', () => {
it('version', () => {
const migration = new StorageMigration009();
expect(migration.version).toBe(9);
});

it('up success', async () => {
const mockData = {
version: 8,
data: mockStorageData,
};
const migration = new StorageMigration009();
const result = await migration.up(mockData, '123');

expect(result.version).toBe(9);
});

it('up password success', async () => {
const mockData = {
version: 1,
data: mockStorageData,
};
const password = '123';
const migration = new StorageMigration009();
const result = await migration.up(mockData, password);

expect(result.version).toBe(9);
expect(result.data).not.toBeNull();

const serialized = result.data.SERIALIZED;
const decrypted = await decryptAES(serialized, password);
const wallet = JSON.parse(decrypted);

expect(wallet.accounts).toHaveLength(0);
expect(wallet.keyrings).toHaveLength(0);
});

it('up failed throw error', async () => {
const mockData: any = {
version: 1,
data: { ...mockStorageData, SERIALIZED: null },
};
const migration = new StorageMigration009();

await expect(migration.up(mockData, '123')).rejects.toThrow(
'Storage Data does not match version V009',
);
});
});
Loading