From b6df7020ab87b996a98d8f5c3be9f0ca6f283d1c Mon Sep 17 00:00:00 2001 From: "Justin R. Evans" Date: Mon, 4 Nov 2024 12:11:40 -0500 Subject: [PATCH] feat: derive default shielded keys from private key import --- .../extension/src/App/Accounts/ViewingKey.tsx | 2 +- .../extension/src/Setup/Common/Completion.tsx | 7 +-- .../src/background/keyring/handler.ts | 4 +- .../src/background/keyring/keyring.ts | 58 ++++++++++++------- .../src/background/keyring/messages.ts | 3 +- .../src/background/keyring/service.ts | 10 +++- 6 files changed, 53 insertions(+), 31 deletions(-) diff --git a/apps/extension/src/App/Accounts/ViewingKey.tsx b/apps/extension/src/App/Accounts/ViewingKey.tsx index 60b2b2639..26963d4e1 100644 --- a/apps/extension/src/App/Accounts/ViewingKey.tsx +++ b/apps/extension/src/App/Accounts/ViewingKey.tsx @@ -15,7 +15,7 @@ export const ViewingKey = (): JSX.Element => {

Your viewing key grants the holder access to all your balances and - transadction data. Please keep it secure to protect your data. + transaction data. Please keep it secure to protect your data.

= (props) => { // Do not derive shielded if this is an imported private key, and // ignore accounts with a non-zero 'change' path component: - if (accountSecret.t !== "PrivateKey" && path.change === 0) { + if (path.change === 0) { setStatusInfo("Generating Shielded Account"); const shieldedAccount = await requester.sendMessage( Ports.Background, - // If this is a default path, don't use zip32 index - // TODO: Should we include index of 0 on default path? new DeriveAccountMsg( path, AccountType.ShieldedKeys, - storedAccount.alias + storedAccount.alias, + storedAccount.type ) ); setShieldedAccountAddress(shieldedAccount.address); diff --git a/apps/extension/src/background/keyring/handler.ts b/apps/extension/src/background/keyring/handler.ts index 951cf0776..d4e5910c3 100644 --- a/apps/extension/src/background/keyring/handler.ts +++ b/apps/extension/src/background/keyring/handler.ts @@ -153,8 +153,8 @@ const handleDeriveAccountMsg: ( service: KeyRingService ) => InternalHandler = (service) => { return async (_, msg) => { - const { path, accountType, alias } = msg; - return await service.deriveAccount(path, accountType, alias); + const { path, accountType, alias, parentType } = msg; + return await service.deriveAccount(path, accountType, alias, parentType); }; }; diff --git a/apps/extension/src/background/keyring/keyring.ts b/apps/extension/src/background/keyring/keyring.ts index fd5f0713c..064e3af3d 100644 --- a/apps/extension/src/background/keyring/keyring.ts +++ b/apps/extension/src/background/keyring/keyring.ts @@ -1,4 +1,4 @@ -import { PhraseSize } from "@namada/sdk/web"; +import { PhraseSize, ShieldedKeys } from "@namada/sdk/web"; import { KVStore } from "@namada/storage"; import { AccountType, @@ -21,6 +21,7 @@ import { UtilityStore, } from "./types"; +import { fromHex } from "@cosmjs/encoding"; import { SdkService } from "background/sdk"; import { VaultService } from "background/vault"; import { KeyStore, KeyStoreType, SensitiveType, VaultStorage } from "storage"; @@ -255,9 +256,10 @@ export class KeyRing { } public deriveShieldedAccount( - seed: Uint8Array, + secret: Uint8Array, bip44Path: Bip44Path, - parentId: string + parentId: string, + parentType = AccountType.Mnemonic ): DerivedAccountInfo { const storedPath = makeStoredPath(AccountType.ShieldedKeys, bip44Path); const id = generateId( @@ -273,13 +275,22 @@ export class KeyRing { storedPath.index || "none" ); const keysNs = this.sdkService.getSdk().getKeys(); - const { address, viewingKey, spendingKey } = keysNs.deriveShieldedFromSeed( - seed, - bip44Path, - // Derives default shielded keys account from bip44Path.account - // TODO: Expose function to accept { account, index } - { account: bip44Path.account } - ); + + let shieldedKeys: ShieldedKeys; + + if (parentType === AccountType.Mnemonic) { + shieldedKeys = keysNs.deriveShieldedFromSeed(secret, bip44Path, { + account: bip44Path.account, + }); + } else if (parentType === AccountType.PrivateKey) { + shieldedKeys = keysNs.deriveShieldedFromPrivateKey(secret, { + account: bip44Path.account, + }); + } else { + throw new Error(`Invalid account type! ${parentType}`); + } + + const { address, viewingKey, spendingKey } = shieldedKeys; return { address, @@ -289,9 +300,9 @@ export class KeyRing { }; } - private async getParentSeed(): Promise<{ + private async getParentSecret(parentType = AccountType.Mnemonic): Promise<{ parentId: string; - seed: Uint8Array; + secret: Uint8Array; }> { const activeAccount = await this.getActiveAccount(); @@ -299,17 +310,17 @@ export class KeyRing { throw "No active account has been found"; } - const storedMnemonic = await this.vaultStorage.findOneOrFail( + const storedSecret = await this.vaultStorage.findOneOrFail( KeyStore, "id", activeAccount.id ); - const parentId = storedMnemonic.public.id; + const parentId = storedSecret.public.id; try { const sensitiveData = await this.vaultService.reveal( - storedMnemonic.sensitive + storedSecret.sensitive ); if (!sensitiveData) { @@ -320,10 +331,14 @@ export class KeyRing { const { text, passphrase } = sensitiveData; - const mnemonicSdk = this.sdkService.getSdk().getMnemonic(); - const seed = mnemonicSdk.toSeed(text, passphrase); + if (parentType === AccountType.Mnemonic) { + const mnemonicSdk = this.sdkService.getSdk().getMnemonic(); + const seed = mnemonicSdk.toSeed(text, passphrase); - return { parentId, seed }; + return { parentId, secret: seed }; + } else { + return { parentId, secret: fromHex(text) }; + } } catch (e) { console.error(e); throw Error("Could not decrypt mnemonic using the provided password"); @@ -364,7 +379,8 @@ export class KeyRing { public async deriveAccount( bip44Path: Bip44Path, type: AccountType, - alias: string + alias: string, + parentType?: AccountType ): Promise { await this.vaultService.assertIsUnlocked(); // Prepare path for either BIP44 or ZIP32 stored value @@ -379,9 +395,9 @@ export class KeyRing { this.deriveTransparentAccount : this.deriveShieldedAccount).bind(this); - const { seed, parentId } = await this.getParentSeed(); + const { secret, parentId } = await this.getParentSecret(parentType); - const info = deriveFn(seed, bip44Path, parentId); + const info = deriveFn(secret, bip44Path, parentId, parentType); // Check whether keys already exist for this account const existingAccount = await this.queryAccountByAddress(info.address); diff --git a/apps/extension/src/background/keyring/messages.ts b/apps/extension/src/background/keyring/messages.ts index 76531d9fb..1e40f1c6f 100644 --- a/apps/extension/src/background/keyring/messages.ts +++ b/apps/extension/src/background/keyring/messages.ts @@ -221,7 +221,8 @@ export class DeriveAccountMsg extends Message { constructor( public readonly path: Bip44Path, public readonly accountType: AccountType, - public readonly alias: string + public readonly alias: string, + public readonly parentType?: AccountType ) { super(); } diff --git a/apps/extension/src/background/keyring/service.ts b/apps/extension/src/background/keyring/service.ts index 48c400fba..cb771f092 100644 --- a/apps/extension/src/background/keyring/service.ts +++ b/apps/extension/src/background/keyring/service.ts @@ -99,9 +99,15 @@ export class KeyRingService { async deriveAccount( path: Bip44Path, type: AccountType, - alias: string + alias: string, + parentType?: AccountType ): Promise { - const account = await this._keyRing.deriveAccount(path, type, alias); + const account = await this._keyRing.deriveAccount( + path, + type, + alias, + parentType + ); await this.broadcaster.updateAccounts(); return account; }