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

add: methods to store a seed temporarily #1056

Merged
merged 11 commits into from
Nov 1, 2024
Merged
Show file tree
Hide file tree
Changes from 5 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
95 changes: 90 additions & 5 deletions src/controllers/keystore/keystore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import scrypt from 'scrypt-js'

import EmittableError from '../../classes/EmittableError'
import { HD_PATH_TEMPLATE_TYPE } from '../../consts/derivation'
import { Banner } from '../../interfaces/banner'
import {
ExternalKey,
InternalKey,
Expand Down Expand Up @@ -104,6 +105,12 @@ export class KeystoreController extends EventEmitter {

#keystoreSeeds: KeystoreSeed[] = []

// when importing a seed, save it temporary here before deciding
// whether to place it in #keystoreSeeds or delete it
//
// this should be done only if there isn't a saved seed already
#tempSeed: KeystoreSeed | undefined = undefined
borislav-itskov marked this conversation as resolved.
Show resolved Hide resolved

#keystoreSigners: Partial<{ [key in Key['type']]: KeystoreSignerType }>

#keystoreKeys: StoredKey[] = []
Expand Down Expand Up @@ -407,7 +414,7 @@ export class KeystoreController extends EventEmitter {
})
}

async #addSeed({ seed, hdPathTemplate }: KeystoreSeed) {
async #getEncryptedSeed(seed: KeystoreSeed['seed']): Promise<string> {
await this.#initialLoadPromise

if (this.#mainKey === null)
Expand All @@ -429,7 +436,7 @@ export class KeystoreController extends EventEmitter {
// this fist seed phrase will become the saved seed phrase of the wallet
if (this.#keystoreSeeds.length) {
throw new EmittableError({
message: 'You can have only one saved seed phrase for that wallet',
message: 'You can have only one saved seed in the extension',
level: 'major',
error: new Error(
'keystore: seed phase already added. Storing multiple seed phrases not supported yet'
Expand All @@ -439,9 +446,61 @@ export class KeystoreController extends EventEmitter {

// Set up the cipher
const counter = new aes.Counter(this.#mainKey!.iv) // TS compiler fails to detect we check for null above
const aesCtr = new aes.ModeOfOperation.ctr(this.#mainKey!.key, counter) // TS compiler fails to detect we check for null above
const aesCtr = new aes.ModeOfOperation.ctr(this.#mainKey!.key, counter) // TS compiler fails to detect we check for null above\
return hexlify(aesCtr.encrypt(new TextEncoder().encode(seed)))
}

async addSeedToTemp({ seed, hdPathTemplate }: KeystoreSeed) {
borislav-itskov marked this conversation as resolved.
Show resolved Hide resolved
this.#tempSeed = {
seed: await this.#getEncryptedSeed(seed),
hdPathTemplate
}

this.emitUpdate()
}

async deleteTempSeed() {
this.#tempSeed = undefined
this.emitUpdate()
}

async moveTempSeed() {
borislav-itskov marked this conversation as resolved.
Show resolved Hide resolved
if (this.#mainKey === null)
throw new EmittableError({
message: KEYSTORE_UNEXPECTED_ERROR_MESSAGE,
level: 'major',
error: new Error('keystore: needs to be unlocked')
})

// Currently we support only one seed phrase to be added to the keystore
// this fist seed phrase will become the saved seed phrase of the wallet
if (this.#keystoreSeeds.length) {
throw new EmittableError({
message: 'You can have only one saved seed in the extension',
level: 'major',
error: new Error(
'keystore: seed phase already added. Storing multiple seed phrases not supported yet'
)
})
}

if (!this.#tempSeed) {
throw new EmittableError({
message:
'Imported seed no longer exists in the extension. If you want to save it, please re-import it',
level: 'major',
error: new Error('keystore: imported seed deleted although a request to save it was made')
})
}

this.#keystoreSeeds.push(this.#tempSeed)
this.#tempSeed = undefined
this.emitUpdate()
}

async #addSeed({ seed, hdPathTemplate }: KeystoreSeed) {
this.#keystoreSeeds.push({
seed: hexlify(aesCtr.encrypt(new TextEncoder().encode(seed))),
seed: await this.#getEncryptedSeed(seed),
hdPathTemplate
})
await this.#storage.set('keystoreSeeds', this.#keystoreSeeds)
Expand Down Expand Up @@ -833,14 +892,40 @@ export class KeystoreController extends EventEmitter {
return !!this.#keystoreSeeds.length
}

get hasKeystoreTempSeed() {
return this.#tempSeed !== undefined
}

get banners(): Banner[] {
if (!this.#tempSeed) return []

return [
{
id: 'tempSeed',
type: 'warning',
category: 'temp-seed-not-confirmed',
title: 'You have an unsaved imported seed',
text: '',
actions: [
{
label: 'Check',
actionName: 'confirm-temp-seed'
}
]
}
]
}

toJSON() {
return {
...this,
...super.toJSON(),
isUnlocked: this.isUnlocked, // includes the getter in the stringified instance
keys: this.keys,
hasPasswordSecret: this.hasPasswordSecret,
hasKeystoreSavedSeed: this.hasKeystoreSavedSeed
hasKeystoreSavedSeed: this.hasKeystoreSavedSeed,
hasKeystoreTempSeed: this.hasKeystoreTempSeed,
banners: this.banners
}
}
}
5 changes: 5 additions & 0 deletions src/interfaces/banner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export type BannerCategory =
| 'swap-and-bridge-in-progress'
| 'swap-and-bridge-ready'
| 'swap-and-bridge-completed'
| 'temp-seed-not-confirmed'

export interface Banner {
id: number | string
Expand Down Expand Up @@ -78,3 +79,7 @@ export type Action =
actionName: 'close-swap-and-bridge'
meta: { activeRouteId: number }
}
| {
label: 'Check'
actionName: 'confirm-temp-seed'
}
Loading