diff --git a/packages/adena-extension/src/common/provider/wallet/wallet-provider.tsx b/packages/adena-extension/src/common/provider/wallet/wallet-provider.tsx index 2356d775f..7096726bc 100644 --- a/packages/adena-extension/src/common/provider/wallet/wallet-provider.tsx +++ b/packages/adena-extension/src/common/provider/wallet/wallet-provider.tsx @@ -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'; @@ -17,6 +17,7 @@ export interface WalletContextProps { initWallet: () => Promise; initNetworkMetainfos: () => Promise; changeNetwork: (network: NetworkMetainfo) => Promise; + clearWallet: () => Promise; } export const WalletContext = createContext(null); @@ -82,11 +83,19 @@ export const WalletProvider: React.FC> = ({ chi async function updateWallet(wallet: Wallet): Promise { setWallet(wallet); - const password = await walletService.loadWalletPassword(); - await walletService.saveWallet(wallet, password); + await walletService.updateWallet(wallet); return true; } + async function clearWallet(): Promise { + await setWallet(new AdenaWallet()); + + await Promise.all([ + async (): Promise => await setWallet(null), + async (): Promise => await setCurrentAccount(null), + ]); + } + async function initCurrentAccount(wallet: Wallet): Promise { const currentAccountId = await accountService.getCurrentAccountId(); const currentAccount = @@ -142,6 +151,7 @@ export const WalletProvider: React.FC> = ({ chi updateWallet, initNetworkMetainfos, changeNetwork, + clearWallet, }} > {children} diff --git a/packages/adena-extension/src/components/molecules/web-seed-box/index.tsx b/packages/adena-extension/src/components/molecules/web-seed-box/index.tsx index 74c619d6d..07c9653ba 100644 --- a/packages/adena-extension/src/components/molecules/web-seed-box/index.tsx +++ b/packages/adena-extension/src/components/molecules/web-seed-box/index.tsx @@ -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 ( - - {_.map(seeds, (seed, index) => ( - - - {index + 1} - - - {seed} - - {showBlur && } - + + {seeds.map((_, index) => ( + + (canvasRefs.current[index] = el)} + width={BOX_WIDTH} + height={BOX_HEIGHT} + /> + ))} - + ); }; diff --git a/packages/adena-extension/src/components/pages/router/side-menu-layout/side-menu-container.tsx b/packages/adena-extension/src/components/pages/router/side-menu-layout/side-menu-container.tsx index a94074eb9..84c266c43 100644 --- a/packages/adena-extension/src/components/pages/router/side-menu-layout/side-menu-container.tsx +++ b/packages/adena-extension/src/components/pages/router/side-menu-layout/side-menu-container.tsx @@ -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'; @@ -26,6 +26,7 @@ interface SideMenuContainerProps { const SideMenuContainer: React.FC = ({ open, setOpen }) => { const { openLink, openRegister } = useLink(); const { walletService } = useAdenaContext(); + const { clearWallet } = useWalletContext(); const navigate = useNavigate(); const { changeCurrentAccount } = useCurrentAccount(); const { currentNetwork, scannerParameters } = useNetwork(); @@ -95,6 +96,7 @@ const SideMenuContainer: React.FC = ({ 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 }); diff --git a/packages/adena-extension/src/hooks/web/use-account-import-screen.ts b/packages/adena-extension/src/hooks/web/use-account-import-screen.ts index 5a2e0f4ee..d866f751a 100644 --- a/packages/adena-extension/src/hooks/web/use-account-import-screen.ts +++ b/packages/adena-extension/src/hooks/web/use-account-import-screen.ts @@ -5,6 +5,7 @@ import { isAirgapAccount, isHDWalletKeyring, Keyring, + mnemonicToEntropy, PrivateKeyKeyring, SeedAccount, SingleAccount, @@ -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) { diff --git a/packages/adena-extension/src/migrates/migrations/v008/storage-model-v008.ts b/packages/adena-extension/src/migrates/migrations/v008/storage-model-v008.ts index b59f0b05f..2b0e4fa40 100644 --- a/packages/adena-extension/src/migrates/migrations/v008/storage-model-v008.ts +++ b/packages/adena-extension/src/migrates/migrations/v008/storage-model-v008.ts @@ -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[]; diff --git a/packages/adena-extension/src/migrates/migrations/v009/storage-migration-v009.spec.ts b/packages/adena-extension/src/migrates/migrations/v009/storage-migration-v009.spec.ts new file mode 100644 index 000000000..27d4cf4b8 --- /dev/null +++ b/packages/adena-extension/src/migrates/migrations/v009/storage-migration-v009.spec.ts @@ -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', + ); + }); +}); diff --git a/packages/adena-extension/src/migrates/migrations/v009/storage-migration-v009.ts b/packages/adena-extension/src/migrates/migrations/v009/storage-migration-v009.ts new file mode 100644 index 000000000..13694140e --- /dev/null +++ b/packages/adena-extension/src/migrates/migrations/v009/storage-migration-v009.ts @@ -0,0 +1,124 @@ +import { StorageModel } from '@common/storage'; +import { Migration } from '@migrates/migrator'; +import { AdenaWallet, decryptAES, mnemonicToEntropy } from 'adena-module'; +import { + SerializedModelV008, + StorageModelDataV008, + WalletModelV008, +} from '../v008/storage-model-v008'; +import { SerializedModelV009, StorageModelDataV009 } from './storage-model-v009'; + +export class StorageMigration009 implements Migration { + public readonly version = 9; + + async up( + current: StorageModel, + password?: string, + ): Promise> { + if (!password) { + return current; + } + + if (!this.validateModelV008(current.data)) { + throw new Error('Storage Data does not match version V009'); + } + const previous: StorageModelDataV008 = current.data; + const serialized = await this.migrateSerialized(previous.SERIALIZED, password || ''); + return { + version: this.version, + data: { + ...previous, + SERIALIZED: serialized, + }, + }; + } + + private validateModelV008(currentData: StorageModelDataV008): boolean { + const storageDataKeys = [ + 'NETWORKS', + 'CURRENT_CHAIN_ID', + 'CURRENT_NETWORK_ID', + 'SERIALIZED', + 'ENCRYPTED_STORED_PASSWORD', + 'CURRENT_ACCOUNT_ID', + 'ESTABLISH_SITES', + 'ADDRESS_BOOK', + 'ACCOUNT_TOKEN_METAINFOS', + 'ACCOUNT_GRC721_COLLECTIONS', + 'ACCOUNT_GRC721_PINNED_PACKAGES', + ]; + const currentDataKeys = Object.keys(currentData); + const hasKeys = storageDataKeys.every((dataKey) => { + return currentDataKeys.includes(dataKey); + }); + + if (!hasKeys) { + return false; + } + if (!Array.isArray(currentData.NETWORKS)) { + return false; + } + if (typeof currentData.CURRENT_CHAIN_ID !== 'string') { + return false; + } + if (typeof currentData.CURRENT_NETWORK_ID !== 'string') { + return false; + } + if (typeof currentData.SERIALIZED !== 'string') { + return false; + } + if (typeof currentData.ENCRYPTED_STORED_PASSWORD !== 'string') { + return false; + } + if (typeof currentData.CURRENT_ACCOUNT_ID !== 'string') { + return false; + } + if (currentData.ACCOUNT_NAMES && typeof currentData.ACCOUNT_NAMES !== 'object') { + return false; + } + if (currentData.ESTABLISH_SITES && typeof currentData.ESTABLISH_SITES !== 'object') { + return false; + } + return true; + } + + private async migrateSerialized( + serialized: SerializedModelV008, + password: string, + ): Promise { + if (!password || !serialized) { + return ''; + } + + let decrypted = await decryptAES(serialized, password); + let wallet = JSON.parse(decrypted) as WalletModelV008; + + let keyrings = wallet.keyrings.map((keyring) => { + if (keyring.type === 'HD_WALLET' && keyring.mnemonic) { + return { + id: keyring.id, + type: keyring.type, + seed: keyring.seed, + mnemonicEntropy: Array.from(mnemonicToEntropy(keyring.mnemonic)), + }; + } + + return keyring; + }); + + let changedWallet = new AdenaWallet({ + accounts: wallet.accounts, + keyrings: keyrings, + currentAccountId: wallet.currentAccountId, + }); + + const serializedWallet = await changedWallet.serialize(password); + + keyrings = []; + wallet = { accounts: [], keyrings: [] }; + decrypted = ''; + changedWallet = new AdenaWallet(); + + return serializedWallet; + } +} diff --git a/packages/adena-extension/src/migrates/migrations/v009/storage-model-v009.ts b/packages/adena-extension/src/migrates/migrations/v009/storage-model-v009.ts new file mode 100644 index 000000000..33724d1c3 --- /dev/null +++ b/packages/adena-extension/src/migrates/migrations/v009/storage-model-v009.ts @@ -0,0 +1,145 @@ +export type StorageModelV009 = { + version: 9; + data: StorageModelDataV009; +}; + +export type StorageModelDataV009 = { + NETWORKS: NetworksModelV009; + CURRENT_CHAIN_ID: CurrentChainIdModelV009; + CURRENT_NETWORK_ID: CurrentNetworkIdModelV009; + SERIALIZED: SerializedModelV009; + ENCRYPTED_STORED_PASSWORD: EncryptedStoredPasswordModelV009; + CURRENT_ACCOUNT_ID: CurrentAccountIdModelV009; + ACCOUNT_NAMES: AccountNamesModelV009; + ESTABLISH_SITES: EstablishSitesModelV009; + ADDRESS_BOOK: AddressBookModelV009; + ACCOUNT_TOKEN_METAINFOS: AccountTokenMetainfoModelV009; + QUESTIONNAIRE_EXPIRED_DATE: QuestionnaireExpiredDateModelV009; + WALLET_CREATION_GUIDE_CONFIRM_DATE: WalletCreationGuideConfirmDateModelV009; + ADD_ACCOUNT_GUIDE_CONFIRM_DATE: AddAccountGuideConfirmDateModelV009; + ACCOUNT_GRC721_COLLECTIONS: AccountGRC721CollectionsV009; + ACCOUNT_GRC721_PINNED_PACKAGES: AccountGRC721PinnedPackagesV009; +}; + +export type NetworksModelV009 = { + id: string; + default: boolean; + main: boolean; + chainId: string; + chainName: string; + networkId: string; + networkName: string; + addressPrefix: string; + rpcUrl: string; + indexerUrl: string; + gnoUrl: string; + apiUrl: string; + linkUrl: string; + deleted?: boolean; +}[]; + +export type CurrentChainIdModelV009 = string; + +export type CurrentNetworkIdModelV009 = string; + +export type SerializedModelV009 = string; + +export type QuestionnaireExpiredDateModelV009 = number | null; + +export type WalletCreationGuideConfirmDateModelV009 = number | null; + +export type AddAccountGuideConfirmDateModelV009 = number | null; + +export type WalletModelV009 = { + accounts: AccountDataModelV009[]; + keyrings: KeyringDataModelV009[]; + currentAccountId?: string; +}; + +type AccountDataModelV009 = { + id?: string; + index: number; + type: 'HD_WALLET' | 'PRIVATE_KEY' | 'LEDGER' | 'WEB3_AUTH' | 'AIRGAP'; + name: string; + keyringId: string; + hdPath?: number; + publicKey: number[]; + addressBytes?: number[]; +}; + +type KeyringDataModelV009 = { + id?: string; + type: 'HD_WALLET' | 'PRIVATE_KEY' | 'LEDGER' | 'WEB3_AUTH' | 'AIRGAP'; + publicKey?: number[]; + privateKey?: number[]; + seed?: number[]; + mnemonicEntropy?: number[]; + addressBytes?: number[]; +}; + +export type EncryptedStoredPasswordModelV009 = string; + +export type CurrentAccountIdModelV009 = string; + +type AccountId = string; +type NetworkId = string; + +export type AccountNamesModelV009 = { [key in AccountId]: string }; + +export type EstablishSitesModelV009 = { + [key in AccountId]: { + hostname: string; + chainId: string; + account: string; + name: string; + favicon: string | null; + establishedTime: string; + }[]; +}; + +export type AddressBookModelV009 = string; + +export type AccountTokenMetainfoModelV009 = { + [key in string]: { + main: boolean; + tokenId: string; + networkId: string; + display: boolean; + type: 'gno-native' | 'grc20' | 'ibc-native' | 'ibc-tokens'; + name: string; + symbol: string; + decimals: number; + description?: string; + websiteUrl?: string; + image: string; + denom?: string; + pkgPath?: string; + originChain?: string; + originDenom?: string; + originType?: string; + path?: string; + channel?: string; + port?: string; + }[]; +}; + +export type AccountGRC721CollectionsV009 = { + [key in AccountId]: { + [key in NetworkId]: { + tokenId: string; + networkId: string; + display: boolean; + type: 'grc721'; + packagePath: string; + name: string; + symbol: string; + image: string | null; + isTokenUri: boolean; + isMetadata: boolean; + }[]; + }; +}; + +export type AccountGRC721PinnedPackagesV009 = { + [key in AccountId]: { [key in NetworkId]: string[] }; +}; diff --git a/packages/adena-extension/src/migrates/storage-migrator.spec.ts b/packages/adena-extension/src/migrates/storage-migrator.spec.ts index 892c6e119..10e30cd75 100644 --- a/packages/adena-extension/src/migrates/storage-migrator.spec.ts +++ b/packages/adena-extension/src/migrates/storage-migrator.spec.ts @@ -45,6 +45,7 @@ describe('StorageMigrator', () => { it('getCurrent success', async () => { const migrator = new StorageMigrator(StorageMigrator.migrations(), storage); + migrator.setPassword('123'); const current = await migrator.getCurrent(); expect(current).not.toBeNull(); @@ -63,18 +64,16 @@ describe('StorageMigrator', () => { it('migrate success', async () => { const migrator = new StorageMigrator(StorageMigrator.migrations(), storage); + migrator.setPassword('123'); const current = await migrator.getCurrent(); const migrated = await migrator.migrate(current); expect(migrated).not.toBeNull(); - expect(migrated?.version).toBe(8); + expect(migrated?.version).toBe(9); expect(migrated?.data).not.toBeNull(); expect(migrated?.data.NETWORKS).toHaveLength(3); expect(migrated?.data.CURRENT_CHAIN_ID).toBe(''); expect(migrated?.data.CURRENT_NETWORK_ID).toBe(''); - expect(migrated?.data.SERIALIZED).toBe( - 'U2FsdGVkX19eI8kOCI/T9o1Ru0b2wdj5rHxmG4QbLQ0yZH4kDa8/gg6Ac2JslvEm', - ); expect(migrated?.data.ENCRYPTED_STORED_PASSWORD).toBe(''); expect(migrated?.data.CURRENT_ACCOUNT_ID).toBe(''); expect(migrated?.data.ACCOUNT_NAMES).toEqual({}); @@ -89,7 +88,7 @@ describe('StorageMigrator', () => { const migrated = await migrator.migrate(current); expect(migrated).not.toBeNull(); - expect(migrated?.version).toBe(8); + expect(migrated?.version).toBe(9); expect(migrated?.data).not.toBeNull(); expect(migrated?.data.SERIALIZED).not.toBe(''); expect(migrated?.data.ADDRESS_BOOK).toBe(''); diff --git a/packages/adena-extension/src/migrates/storage-migrator.ts b/packages/adena-extension/src/migrates/storage-migrator.ts index ad83a4572..d70739c59 100644 --- a/packages/adena-extension/src/migrates/storage-migrator.ts +++ b/packages/adena-extension/src/migrates/storage-migrator.ts @@ -14,6 +14,8 @@ import { StorageMigration007 } from './migrations/v007/storage-migration-v007'; import { StorageModelV007 } from './migrations/v007/storage-model-v007'; import { StorageMigration008 } from './migrations/v008/storage-migration-v008'; import { StorageModelV008 } from './migrations/v008/storage-model-v008'; +import { StorageMigration009 } from './migrations/v009/storage-migration-v009'; +import { StorageModelV009 } from './migrations/v009/storage-model-v009'; import { Migration, Migrator } from './migrator'; const LegacyStorageKeys = [ @@ -89,6 +91,7 @@ export class StorageMigrator implements Migrator { async deserialize( data: string | undefined, ): Promise< + | StorageModelV009 | StorageModelV008 | StorageModelV007 | StorageModelV006 @@ -111,6 +114,7 @@ export class StorageMigrator implements Migrator { // Retrieves the current storage data, performing deserialization async getCurrent(): Promise< + | StorageModelV009 | StorageModelV008 | StorageModelV007 | StorageModelV006 @@ -174,6 +178,7 @@ export class StorageMigrator implements Migrator { // eslint-disable-next-line @typescript-eslint/no-explicit-any json: any, ): Promise< + | StorageModelV009 | StorageModelV008 | StorageModelV007 | StorageModelV006 @@ -183,6 +188,9 @@ export class StorageMigrator implements Migrator { | StorageModelV002 | StorageModelV001 > { + if (json?.version === 9) { + return json as StorageModelV009; + } if (json?.version === 8) { return json as StorageModelV008; } @@ -240,6 +248,7 @@ export class StorageMigrator implements Migrator { new StorageMigration006(), new StorageMigration007(), new StorageMigration008(), + new StorageMigration009(), ]; } } diff --git a/packages/adena-extension/src/repositories/wallet/wallet.ts b/packages/adena-extension/src/repositories/wallet/wallet.ts index f33ee5793..fa9033534 100644 --- a/packages/adena-extension/src/repositories/wallet/wallet.ts +++ b/packages/adena-extension/src/repositories/wallet/wallet.ts @@ -70,10 +70,7 @@ export class WalletRepository { } try { - const password = await decryptPassword(iv, encryptedPassword); - this.updateStoragePassword(password); - - return password; + return decryptPassword(iv, encryptedPassword); } catch (e) { throw new WalletError('NOT_FOUND_PASSWORD'); } diff --git a/packages/adena-extension/src/services/wallet/wallet.ts b/packages/adena-extension/src/services/wallet/wallet.ts index 4789dda2a..04d6ad4b8 100644 --- a/packages/adena-extension/src/services/wallet/wallet.ts +++ b/packages/adena-extension/src/services/wallet/wallet.ts @@ -127,6 +127,19 @@ export class WalletService { } }; + public updateWallet = async (wallet: Wallet): Promise => { + let password = await this.walletRepository.getWalletPassword(); + const serializedWallet = await wallet.serialize(password); + password = ''; + + await this.walletRepository.updateSerializedWallet(serializedWallet); + try { + chrome?.action?.setPopup({ popup: 'popup.html' }); + } catch (e) { + console.error(e); + } + }; + /** * This function deserializes the wallet with the password. * @@ -175,15 +188,6 @@ export class WalletService { return false; }; - public getRawPassword = async (): Promise => { - try { - const rawPassword = await this.walletRepository.getWalletPassword(); - return rawPassword; - } catch (e) { - return ''; - } - }; - public updatePassword = async (password: string): Promise => { await this.walletRepository.updateWalletPassword(password); return true; diff --git a/packages/adena-module/src/crypto/index.ts b/packages/adena-module/src/crypto/index.ts index 1fbce8785..5300bf5fd 100644 --- a/packages/adena-module/src/crypto/index.ts +++ b/packages/adena-module/src/crypto/index.ts @@ -1,13 +1,13 @@ -export { Bip39, EnglishMnemonic } from './bip39'; +export { Bip39, EnglishMnemonic, entropyToMnemonic, mnemonicToEntropy } from './bip39'; export { type HashFunction } from './hash'; export { Argon2id, - type Argon2idOptions, Ed25519, Ed25519Keypair, isArgon2idOptions, xchacha20NonceLength, Xchacha20poly1305Ietf, + type Argon2idOptions, } from './libsodium'; export { Random } from './random'; export { Sha256, sha256, Sha512, sha512 } from './sha'; diff --git a/packages/adena-module/src/wallet/keyring/address-keyring.spec.ts b/packages/adena-module/src/wallet/keyring/address-keyring.spec.ts index 8e5212969..d7cc632db 100644 --- a/packages/adena-module/src/wallet/keyring/address-keyring.spec.ts +++ b/packages/adena-module/src/wallet/keyring/address-keyring.spec.ts @@ -9,26 +9,7 @@ describe('create address keyring', () => { expect(addressKeyring.id).not.toBeNull(); expect([...addressKeyring.addressBytes]).toStrictEqual([ - 146, - 15, - 181, - 241, - 124, - 45, - 175, - 116, - 187, - 21, - 153, - 183, - 203, - 224, - 41, - 90, - 94, - 30, - 201, - 183, + 146, 15, 181, 241, 124, 45, 175, 116, 187, 21, 153, 183, 203, 224, 41, 90, 94, 30, 201, 183, ]); expect(addressKeyring.type.toString()).toBe('AIRGAP'); }); @@ -44,125 +25,13 @@ describe('tx encode of address keyring', () => { { typeUrl: '/vm.m_call', value: new Uint8Array([ - 10, - 40, - 103, - 49, - 106, - 103, - 56, - 109, - 116, - 117, - 116, - 117, - 57, - 107, - 104, - 104, - 102, - 119, - 99, - 52, - 110, - 120, - 109, - 117, - 104, - 99, - 112, - 102, - 116, - 102, - 48, - 112, - 97, - 106, - 100, - 104, - 102, - 118, - 115, - 113, - 102, - 53, - 26, - 20, - 103, - 110, - 111, - 46, - 108, - 97, - 110, - 100, - 47, - 114, - 47, - 100, - 101, - 109, - 111, - 47, - 116, - 111, - 110, - 103, - 34, - 8, - 84, - 114, - 97, - 110, - 115, - 102, - 101, - 114, - 42, - 40, - 103, - 49, - 107, - 99, - 100, - 100, - 51, - 110, - 48, - 100, - 52, - 55, - 50, - 103, - 50, - 112, - 53, - 108, - 56, - 115, - 118, - 121, - 103, - 57, - 116, - 48, - 119, - 113, - 54, - 104, - 53, - 56, - 53, - 55, - 110, - 113, - 57, - 57, - 50, - 102, - 42, - 1, - 49, + 10, 40, 103, 49, 106, 103, 56, 109, 116, 117, 116, 117, 57, 107, 104, 104, 102, 119, 99, + 52, 110, 120, 109, 117, 104, 99, 112, 102, 116, 102, 48, 112, 97, 106, 100, 104, 102, + 118, 115, 113, 102, 53, 26, 20, 103, 110, 111, 46, 108, 97, 110, 100, 47, 114, 47, 100, + 101, 109, 111, 47, 116, 111, 110, 103, 34, 8, 84, 114, 97, 110, 115, 102, 101, 114, 42, + 40, 103, 49, 107, 99, 100, 100, 51, 110, 48, 100, 52, 55, 50, 103, 50, 112, 53, 108, 56, + 115, 118, 121, 103, 57, 116, 48, 119, 113, 54, 104, 53, 56, 53, 55, 110, 113, 57, 57, + 50, 102, 42, 1, 49, ]), }, ], @@ -179,106 +48,15 @@ describe('tx encode of address keyring', () => { pubKey: { typeUrl: '/tm.PubKeySecp256k1', value: new Uint8Array([ - 3, - 225, - 97, - 54, - 219, - 23, - 30, - 50, - 223, - 72, - 153, - 53, - 148, - 31, - 5, - 110, - 34, - 248, - 152, - 99, - 227, - 115, - 157, - 10, - 183, - 205, - 73, - 236, - 66, - 131, - 156, - 157, - 178, + 3, 225, 97, 54, 219, 23, 30, 50, 223, 72, 153, 53, 148, 31, 5, 110, 34, 248, 152, 99, + 227, 115, 157, 10, 183, 205, 73, 236, 66, 131, 156, 157, 178, ]), }, signature: new Uint8Array([ - 232, - 153, - 55, - 130, - 206, - 122, - 227, - 1, - 147, - 186, - 211, - 93, - 56, - 60, - 237, - 53, - 73, - 96, - 243, - 207, - 214, - 30, - 99, - 15, - 152, - 145, - 145, - 100, - 112, - 238, - 87, - 77, - 12, - 115, - 211, - 57, - 128, - 101, - 129, - 100, - 49, - 41, - 27, - 43, - 11, - 178, - 172, - 227, - 48, - 60, - 102, - 177, - 59, - 181, - 60, - 187, - 83, - 97, - 244, - 227, - 208, - 170, - 97, - 25, + 232, 153, 55, 130, 206, 122, 227, 1, 147, 186, 211, 93, 56, 60, 237, 53, 73, 96, 243, + 207, 214, 30, 99, 15, 152, 145, 145, 100, 112, 238, 87, 77, 12, 115, 211, 57, 128, 101, + 129, 100, 49, 41, 27, 43, 11, 178, 172, 227, 48, 60, 102, 177, 59, 181, 60, 187, 83, 97, + 244, 227, 208, 170, 97, 25, ]), }, ], diff --git a/packages/adena-module/src/wallet/keyring/hd-wallet-keyring.ts b/packages/adena-module/src/wallet/keyring/hd-wallet-keyring.ts index 05447d7c0..7e980feaf 100644 --- a/packages/adena-module/src/wallet/keyring/hd-wallet-keyring.ts +++ b/packages/adena-module/src/wallet/keyring/hd-wallet-keyring.ts @@ -8,7 +8,7 @@ import { } from '@gnolang/tm2-js-client'; import { v4 as uuidv4 } from 'uuid'; -import { Bip39, EnglishMnemonic } from '../../crypto'; +import { Bip39, EnglishMnemonic, entropyToMnemonic, mnemonicToEntropy } from '../../crypto'; import { Document, makeSignedTx, useTm2Wallet } from './../..'; import { Keyring, KeyringData, KeyringType } from './keyring'; @@ -16,19 +16,23 @@ export class HDWalletKeyring implements Keyring { public readonly id: string; public readonly type: KeyringType = 'HD_WALLET'; public readonly seed: Uint8Array; - public readonly mnemonic: string; + public readonly mnemonicEntropy: Uint8Array; - constructor({ id, mnemonic, seed }: KeyringData) { - if (!mnemonic || !seed) { + constructor({ id, mnemonicEntropy, seed }: KeyringData) { + if (!mnemonicEntropy || !seed) { throw new Error('Invalid parameter values'); } this.id = id || uuidv4(); - this.mnemonic = mnemonic; + this.mnemonicEntropy = Uint8Array.from(mnemonicEntropy); this.seed = Uint8Array.from(seed); } + getMnemonic() { + return entropyToMnemonic(this.mnemonicEntropy); + } + async getKeypair(hdPath: number) { - const { privateKey, publicKey } = await generateKeyPair(this.mnemonic, hdPath); + const { privateKey, publicKey } = await generateKeyPair(this.getMnemonic(), hdPath); return { privateKey, publicKey: publicKey }; } @@ -47,7 +51,7 @@ export class HDWalletKeyring implements Keyring { id: this.id, type: this.type, seed: Array.from(this.seed), - mnemonic: this.mnemonic, + mnemonicEntropy: Array.from(this.mnemonicEntropy), }; } @@ -59,7 +63,7 @@ export class HDWalletKeyring implements Keyring { signed: Tx; signature: TxSignature[]; }> { - const wallet = await useTm2Wallet(document).fromMnemonic(this.mnemonic, { + const wallet = await useTm2Wallet(document).fromMnemonic(this.getMnemonic(), { accountIndex: hdPath, }); wallet.connect(provider); @@ -75,13 +79,13 @@ export class HDWalletKeyring implements Keyring { } async broadcastTxSync(provider: Provider, signedTx: Tx, hdPath: number = 0) { - const wallet = await Tm2Wallet.fromMnemonic(this.mnemonic, { accountIndex: hdPath }); + const wallet = await Tm2Wallet.fromMnemonic(this.getMnemonic(), { accountIndex: hdPath }); wallet.connect(provider); return wallet.sendTransaction(signedTx, TransactionEndpoint.BROADCAST_TX_SYNC); } async broadcastTxCommit(provider: Provider, signedTx: Tx, hdPath: number = 0) { - const wallet = await Tm2Wallet.fromMnemonic(this.mnemonic, { accountIndex: hdPath }); + const wallet = await Tm2Wallet.fromMnemonic(this.getMnemonic(), { accountIndex: hdPath }); wallet.connect(provider); return wallet.sendTransaction(signedTx, TransactionEndpoint.BROADCAST_TX_COMMIT); } @@ -89,6 +93,10 @@ export class HDWalletKeyring implements Keyring { public static async fromMnemonic(mnemonic: string) { const englishMnemonic = new EnglishMnemonic(mnemonic); const seed = await Bip39.mnemonicToSeed(englishMnemonic); - return new HDWalletKeyring({ mnemonic, seed: Array.from(seed) }); + const mnemonicEntropy = await mnemonicToEntropy(englishMnemonic.toString()); + return new HDWalletKeyring({ + mnemonicEntropy: Array.from(mnemonicEntropy), + seed: Array.from(seed), + }); } } diff --git a/packages/adena-module/src/wallet/keyring/keyring.ts b/packages/adena-module/src/wallet/keyring/keyring.ts index 95bb518dc..7105b566a 100644 --- a/packages/adena-module/src/wallet/keyring/keyring.ts +++ b/packages/adena-module/src/wallet/keyring/keyring.ts @@ -45,7 +45,7 @@ export interface KeyringData { publicKey?: number[]; privateKey?: number[]; seed?: number[]; - mnemonic?: string; + mnemonicEntropy?: number[]; addressBytes?: number[]; } diff --git a/packages/adena-module/src/wallet/wallet.spec.ts b/packages/adena-module/src/wallet/wallet.spec.ts index 70f4622cd..541f0ffb1 100644 --- a/packages/adena-module/src/wallet/wallet.spec.ts +++ b/packages/adena-module/src/wallet/wallet.spec.ts @@ -84,7 +84,7 @@ describe('serialize', () => { it('deserialize success', async () => { const serialized = - 'U2FsdGVkX1+vUpoJEgX2E4Wsyl+PmwfSAv1k6mO8UJ8Q6vlmJ279Ap7asAoLSBO97TAZ8MSbB859aVC6T0RsdKmtf96ZymI0JqvLiNIcr7v8iPtEqE+Jgfu6umTsBUnMilo/bC89Y3OlfCQ6xaaWeYpfOZzcQC27twXAndDGTXIrGy5W+P9tr0IUE94BwON8LC9j0L2md2qnF/tAw/MNp9neKszWxNT1J8xtSjIjpQulRnz0bhbYuZoV4casjTSWEvzqY9+QYY2PXrRmU3gdvaXtxe05n+Y0k36MmPRVnqYM1Gumvs0LRDKrTSLhAG8xHndO7uvuM1sVbJUpmWsQ/QvBNepxga+JS4Fn4kG9cR4g/dAKkha6dSUJBQmkP7ZD4Sf+6scBFhvM0/jidKyMaj6J6h+6DbHT/Uoh16Fj2gpuFpfx0xr9ZrtaKH788rEo56WKbmZLUnHmjx/5FtEGiI47ocm5XRinFa7KlYDS4uDgVf+mG6+qNTtZ8NRBTdVfMjtnfGWJgQ+6U6WGN97Y5cMHIgdjPgVN42IYjO5galE3XaEvbZfkQVZBDa+k6IR4C8e7F+Mq+16I8kRKJPSTyHeVeo4GUqa1+IxaXKHGzB1SHV1cHLbBn7tkm2Dr4tdJoAt4JrusvPbL/TLSQg2YdNH9zzBqYlU05846Lgs1hniBHlJ00gKSB7C7vPJtBQerDAnwyOiRjVXih8/D40OPXue5RXY2EZK3+zsQNq5/D1BK8JR+PZo2+BYhuUQwxu5OXSpbeles6I3ZR/scgf05zMtXRTPbO28whAWbt6s+OKC8N+3oBUXlVe/cDX+GJHJcmwd1nIJu/TDggrsuMZ5Z16kEeaEhQ5Y/L8UJ3vvzWyMS96F9GzVP3UncKmKt2qgoYleeix1vHW5m+W6WDXyn/7IUvtyPQnDuCGvWCQ5e7s7Yv20/zHFrm2kMbt5FPMITmRhSU9ARPu6JXco/mQ6UrsQvfZjpQNxDtmWDT+kNt/Ku5OZrLmmqyQImY6rdD37l4GBu6Zqape0WCtoMS4UJVCojydEPsX4wc5MSwjgAv2Nj2cLkofhJKcNjWdKrAr3WxPHDFiThejEpx35Kf7Y4Xw6x7ci+iaYXHkJkcmFp65QV9+en76YyW1zIMJ00nK4T5BML3w9MghE0IfmrhFc0H23Qck/WumRqJ46IH7jISx8ZmtsNdG7gS/na124Vu6zv'; + 'U2FsdGVkX1/E9wVYaJL0UGOKTC0nxbdu1Jd0qdCqsjErlbEw4ruhRw+9Bxx1rVEMV20Gv86YRRprVZGTUHmuwU5gKcX/3DE0j9cvUn781WMff81DZcczqQMj1C0hhknmz/D/t34G7UWUeQyMQrSr7k6gyNo/KVyNVkAAlNuWCIkZGkzKkH+wPJdvliFLgxUaVbTuBKcO6fuZ1vvB2hbUZ0vE10/dIF3H5BTRYt6I64IbHNXEoQiz0ZzU+LItNMFbGELYLqi9xJKPaJY6T1fejibc6qxq0hhHvdSXT28rOciavvHGKi5lYpWWfQVl31WxmNZPmE8Hu21qF0P9rmY+x+t0jCtTOSmg+2r3WJOmHorOWChxVoCrob6qyGEZS7WSJ/nKo04IuroxO7xpnWXiyQvxv4TcsDSTtu298MRN4USViRodchJCe7mvvXO9yxEbXf3YcOQ7MF/tjyIXHd3cm4mcchbA4jQ2JPvlbb5+6kejThYqkHna3K8h1X2Pi9LFwdgDXZuajW2JtINPEpDxKx/yuMjrJaqtubzi2FIRTtC9NJauf5XizWDAN22m7rMbuV6KRNwwkleEB67CjdlOnJx7f0KxdopPT/XRlXivimUTf8nABAc7egWcaUmbvhtTnxOs9RYjIfq9Mid2AGk4Ehpsq8mLujOPVBLDdS5o+BUt4xTRV1/+ZIJKa/3m5bhWZWU2/pUMhXnxg8YKg6lba2cFwHu15KvE5+gjofNIgnXkwNCMRtEpaCgODBnz25RWcq+ktpiQI6nn6e9jIc9jPNtTX5mxuWC5G7yx7oSsGR4fbKd0xJZaZrn+I+1wyR39L7FFszWXOyCvyDDq0FxH7M0PiAVZbO+440XtRTEvblwEeJWlrR6Jze8XPjjfy/ehwYXNHfOoocP3SO/NYR7AuPxskDVoIdW9ZYu+FT4tL3yK8ew43MJqKYBWL483aUSEF9IofQr+9qOFSimWxCHzPmTRUvkf4gu1pHUoNU0o+oKN7CYw4UMJ1G0crXMBPwCKPWIgD0NGed2vJKjdYgAzApazXmc3s5DH2Jn2rO6fsU8c7egEm6CzUYO6rg2Eq9xvC6sscgbbpP3rEhTVCwc9ESd6p0htM+oq7I8AfNDBH8HUrLrkht4WoiHAZJZjo27FV+EWVcz+TtH6BoZ6jPbqB+zkeUbGFEcNuLK6IRvAAZEoCPDGNJSgJZLf7ULxMTAr4k9aJwh827gjEp9tw586rKAGC//AYG1FzyLVupvA7kQ='; const wallet = await AdenaWallet.deserialize(serialized, 'PASSWORD'); expect(wallet).toBeTruthy(); diff --git a/packages/adena-module/src/wallet/wallet.ts b/packages/adena-module/src/wallet/wallet.ts index d6d757d2c..dbadaba47 100644 --- a/packages/adena-module/src/wallet/wallet.ts +++ b/packages/adena-module/src/wallet/wallet.ts @@ -200,7 +200,7 @@ export class AdenaWallet implements Wallet { if (!isHDWalletKeyring(k)) { return false; } - return keyring.mnemonic === k.mnemonic; + return keyring.mnemonicEntropy === k.mnemonicEntropy; }); } @@ -225,7 +225,7 @@ export class AdenaWallet implements Wallet { if (!isHDWalletKeyring(this.currentKeyring)) { throw new Error('Mnemonic words not found'); } - return this.currentKeyring.mnemonic; + return this.currentKeyring.getMnemonic(); } getLastAccountIndexBy(keyring: Keyring) {