diff --git a/packages/seedless-onboarding-controller/seedless-onboarding-controller.tgz b/packages/seedless-onboarding-controller/seedless-onboarding-controller.tgz index 9d2dbcd7935..2cc948fd3a6 100644 Binary files a/packages/seedless-onboarding-controller/seedless-onboarding-controller.tgz and b/packages/seedless-onboarding-controller/seedless-onboarding-controller.tgz differ diff --git a/packages/seedless-onboarding-controller/src/SecretMetadata.ts b/packages/seedless-onboarding-controller/src/SecretMetadata.ts new file mode 100644 index 00000000000..63643155051 --- /dev/null +++ b/packages/seedless-onboarding-controller/src/SecretMetadata.ts @@ -0,0 +1,241 @@ +import { + base64ToBytes, + bytesToBase64, + stringToBytes, + bytesToString, +} from '@metamask/utils'; + +import { + SeedlessOnboardingControllerError, + SecretType, + SecretMetadataVersion, +} from './constants'; +import type { SecretDataType, SecretMetadataOptions } from './types'; + +type ISecretMetadata = { + data: DataType; + timestamp: number; + type: SecretType; + version: SecretMetadataVersion; + toBytes: () => Uint8Array; +}; + +// SecretMetadata type without the data and toBytes methods +// in which the data is base64 encoded for more compacted metadata +type SecretMetadataJson = Omit< + ISecretMetadata, + 'data' | 'toBytes' +> & { + data: string; // base64 encoded string +}; + +/** + * SecretMetadata is a class that adds metadata to the secret. + * + * It contains the secret and the timestamp when it was created. + * It is used to store the secret in the metadata store. + * + * @example + * ```ts + * const secretMetadata = new SecretMetadata(secret); + * ``` + */ +export class SecretMetadata + implements ISecretMetadata +{ + readonly #data: DataType; + + readonly #timestamp: number; + + readonly #type: SecretType; + + readonly #version: SecretMetadataVersion; + + /** + * Create a new SecretMetadata instance. + * + * @param data - The secret to add metadata to. + * @param options - The options for the secret metadata. + * @param options.timestamp - The timestamp when the secret was created. + * @param options.type - The type of the secret. + */ + constructor(data: DataType, options?: Partial) { + this.#data = data; + this.#timestamp = options?.timestamp ?? Date.now(); + this.#type = options?.type ?? SecretType.Mnemonic; + this.#version = options?.version ?? SecretMetadataVersion.V1; + } + + /** + * Create an Array of SecretMetadata instances from an array of secrets. + * + * To respect the order of the secrets, we add the index to the timestamp + * so that the first secret backup will have the oldest timestamp + * and the last secret backup will have the newest timestamp. + * + * @param data - The data to add metadata to. + * @param data.value - The SeedPhrase/PrivateKey to add metadata to. + * @param data.options - The options for the seed phrase metadata. + * @returns The SecretMetadata instances. + */ + static fromBatch( + data: { + value: DataType; + options?: Partial; + }[], + ): SecretMetadata[] { + const timestamp = Date.now(); + return data.map((d, index) => { + // To respect the order of the seed phrases, we add the index to the timestamp + // so that the first seed phrase backup will have the oldest timestamp + // and the last seed phrase backup will have the newest timestamp + const backupCreatedAt = d.options?.timestamp ?? timestamp + index * 5; + return new SecretMetadata(d.value, { + timestamp: backupCreatedAt, + type: d.options?.type, + }); + }); + } + + /** + * Assert that the provided value is a valid seed phrase metadata. + * + * @param value - The value to check. + * @throws If the value is not a valid seed phrase metadata. + */ + static assertIsValidSecretMetadataJson< + DataType extends SecretDataType = Uint8Array, + >(value: unknown): asserts value is SecretMetadataJson { + if ( + typeof value !== 'object' || + !value || + !('data' in value) || + typeof value.data !== 'string' || + !('timestamp' in value) || + typeof value.timestamp !== 'number' + ) { + throw new Error(SeedlessOnboardingControllerError.InvalidSecretMetadata); + } + } + + /** + * Parse the SecretMetadata from the metadata store and return the array of SecretMetadata instances. + * + * This method also sorts the secrets by timestamp in ascending order, i.e. the oldest secret will be the first element in the array. + * + * @param secretMetadataArr - The array of SecretMetadata from the metadata store. + * @param filterType - The type of the secret to filter. + * @returns The array of SecretMetadata instances. + */ + static parseSecretsFromMetadataStore< + DataType extends SecretDataType = Uint8Array, + >( + secretMetadataArr: Uint8Array[], + filterType?: SecretType, + ): SecretMetadata[] { + const parsedSecertMetadata = secretMetadataArr.map((metadata) => + SecretMetadata.fromRawMetadata(metadata), + ); + + const secrets = SecretMetadata.sort(parsedSecertMetadata); + + if (filterType) { + return secrets.filter((secret) => secret.type === filterType); + } + + return secrets; + } + + /** + * Parse and create the SecretMetadata instance from the raw metadata bytes. + * + * @param rawMetadata - The raw metadata. + * @returns The parsed secret metadata. + */ + static fromRawMetadata( + rawMetadata: Uint8Array, + ): SecretMetadata { + const serializedMetadata = bytesToString(rawMetadata); + const parsedMetadata = JSON.parse(serializedMetadata); + + SecretMetadata.assertIsValidSecretMetadataJson(parsedMetadata); + + // if the type is not provided, we default to Mnemonic for the backwards compatibility + const type = parsedMetadata.type ?? SecretType.Mnemonic; + const version = parsedMetadata.version ?? SecretMetadataVersion.V1; + + let data: DataType; + try { + data = base64ToBytes(parsedMetadata.data) as DataType; + } catch { + data = parsedMetadata.data as DataType; + } + + return new SecretMetadata(data, { + timestamp: parsedMetadata.timestamp, + type, + version, + }); + } + + /** + * Sort the seed phrases by timestamp. + * + * @param data - The secret metadata array to sort. + * @param order - The order to sort the seed phrases. Default is `desc`. + * + * @returns The sorted secret metadata array. + */ + static sort( + data: SecretMetadata[], + order: 'asc' | 'desc' = 'asc', + ): SecretMetadata[] { + return data.sort((a, b) => { + if (order === 'asc') { + return a.timestamp - b.timestamp; + } + return b.timestamp - a.timestamp; + }); + } + + get data(): DataType { + return this.#data; + } + + get timestamp() { + return this.#timestamp; + } + + get type() { + return this.#type; + } + + get version() { + return this.#version; + } + + /** + * Serialize the secret metadata and convert it to a Uint8Array. + * + * @returns The serialized SecretMetadata value in bytes. + */ + toBytes(): Uint8Array { + let _data: unknown = this.#data; + if (this.#data instanceof Uint8Array) { + // encode the raw secret to base64 encoded string + // to create more compacted metadata + _data = bytesToBase64(this.#data); + } + + // serialize the metadata to a JSON string + const serializedMetadata = JSON.stringify({ + data: _data, + timestamp: this.#timestamp, + type: this.#type, + version: this.#version, + }); + + // convert the serialized metadata to bytes(Uint8Array) + return stringToBytes(serializedMetadata); + } +} diff --git a/packages/seedless-onboarding-controller/src/SeedPhraseMetadata.ts b/packages/seedless-onboarding-controller/src/SeedPhraseMetadata.ts deleted file mode 100644 index 6c86a4fede7..00000000000 --- a/packages/seedless-onboarding-controller/src/SeedPhraseMetadata.ts +++ /dev/null @@ -1,179 +0,0 @@ -import { - base64ToBytes, - bytesToBase64, - stringToBytes, - bytesToString, -} from '@metamask/utils'; - -import { SeedlessOnboardingControllerError } from './constants'; - -type ISeedPhraseMetadata = { - data: Uint8Array; - timestamp: number; - toBytes: () => Uint8Array; -}; - -// SeedPhraseMetadata type without the seedPhrase and toBytes methods -// in which the seedPhrase is base64 encoded for more compacted metadata -type IBase64SeedPhraseMetadata = Omit< - ISeedPhraseMetadata, - 'data' | 'toBytes' -> & { - data: string; // base64 encoded string -}; - -/** - * SeedPhraseMetadata is a class that adds metadata to the seed phrase. - * - * It contains the seed phrase and the timestamp when it was created. - * It is used to store the seed phrase in the metadata store. - * - * @example - * ```ts - * const seedPhraseMetadata = new SeedPhraseMetadata(seedPhrase); - * ``` - */ -export class SeedPhraseMetadata implements ISeedPhraseMetadata { - readonly #data: Uint8Array; - - readonly #timestamp: number; - - /** - * Create a new SeedPhraseMetadata instance. - * - * @param data - The seed phrase data to add metadata to. - * @param timestamp - The timestamp when the seed phrase was created. - */ - constructor(data: Uint8Array, timestamp: number = Date.now()) { - this.#data = data; - this.#timestamp = timestamp; - } - - /** - * Create an Array of SeedPhraseMetadata instances from an array of seed phrases. - * - * To respect the order of the seed phrases, we add the index to the timestamp - * so that the first seed phrase backup will have the oldest timestamp - * and the last seed phrase backup will have the newest timestamp. - * - * @param seedPhrases - The seed phrases to add metadata to. - * @returns The SeedPhraseMetadata instances. - */ - static fromBatchSeedPhrases(seedPhrases: Uint8Array[]): SeedPhraseMetadata[] { - const timestamp = Date.now(); - return seedPhrases.map((seedPhrase, index) => { - // To respect the order of the seed phrases, we add the index to the timestamp - // so that the first seed phrase backup will have the oldest timestamp - // and the last seed phrase backup will have the newest timestamp - const backupCreatedAt = timestamp + index * 5; - return new SeedPhraseMetadata(seedPhrase, backupCreatedAt); - }); - } - - /** - * Assert that the provided value is a valid seed phrase metadata. - * - * @param value - The value to check. - * @throws If the value is not a valid seed phrase metadata. - */ - static assertIsBase64SeedphraseMetadata( - value: unknown, - ): asserts value is IBase64SeedPhraseMetadata { - if ( - typeof value !== 'object' || - !value || - !('data' in value) || - typeof value.data !== 'string' || - !('timestamp' in value) || - typeof value.timestamp !== 'number' - ) { - throw new Error( - SeedlessOnboardingControllerError.InvalidSeedPhraseMetadata, - ); - } - } - - /** - * Parse the seed phrase metadata from the metadata store and return the array of raw seed phrases. - * - * This method also sorts the seed phrases by timestamp in ascending order, i.e. the oldest seed phrase will be the first element in the array. - * - * @param seedPhraseMetadataArr - The array of SeedPhrase Metadata from the metadata store. - * @returns The array of raw seed phrases. - */ - static parseSeedPhraseFromMetadataStore( - seedPhraseMetadataArr: Uint8Array[], - ): Uint8Array[] { - const parsedSeedPhraseMetadata = seedPhraseMetadataArr.map((metadata) => - SeedPhraseMetadata.fromRawMetadata(metadata), - ); - - const seedPhrases = SeedPhraseMetadata.sort(parsedSeedPhraseMetadata); - - return seedPhrases.map((seedPhraseMetadata) => seedPhraseMetadata.data); - } - - /** - * Parse and create the SeedPhraseMetadata instance from the raw metadata. - * - * @param rawMetadata - The raw metadata. - * @returns The parsed seed phrase metadata. - */ - static fromRawMetadata(rawMetadata: Uint8Array): SeedPhraseMetadata { - const serializedMetadata = bytesToString(rawMetadata); - const parsedMetadata = JSON.parse(serializedMetadata); - - SeedPhraseMetadata.assertIsBase64SeedphraseMetadata(parsedMetadata); - - const seedPhraseBytes = base64ToBytes(parsedMetadata.data); - return new SeedPhraseMetadata(seedPhraseBytes, parsedMetadata.timestamp); - } - - /** - * Sort the seed phrases by timestamp. - * - * @param seedPhrases - The seed phrases to sort. - * @param order - The order to sort the seed phrases. Default is `desc`. - * - * @returns The sorted seed phrases. - */ - static sort( - seedPhrases: SeedPhraseMetadata[], - order: 'asc' | 'desc' = 'asc', - ): SeedPhraseMetadata[] { - return seedPhrases.sort((a, b) => { - if (order === 'asc') { - return a.timestamp - b.timestamp; - } - return b.timestamp - a.timestamp; - }); - } - - get data() { - return this.#data; - } - - get timestamp() { - return this.#timestamp; - } - - /** - * Serialize the seed phrase metadata and convert it to a Uint8Array. - * - * @returns The serialized SeedPhraseMetadata value in bytes. - */ - toBytes(): Uint8Array { - // encode the raw SeedPhrase to base64 encoded string - // to create more compacted metadata - const b64SeedPhrase = bytesToBase64(this.#data); - - // serialize the metadata to a JSON string - const serializedMetadata = JSON.stringify({ - data: b64SeedPhrase, - timestamp: this.#timestamp, - }); - - // convert the serialized metadata to bytes(Uint8Array) - return stringToBytes(serializedMetadata); - } -} diff --git a/packages/seedless-onboarding-controller/src/SeedlessOnboardingController.test.ts b/packages/seedless-onboarding-controller/src/SeedlessOnboardingController.test.ts index 01799048ba9..225ec21e37a 100644 --- a/packages/seedless-onboarding-controller/src/SeedlessOnboardingController.test.ts +++ b/packages/seedless-onboarding-controller/src/SeedlessOnboardingController.test.ts @@ -24,13 +24,15 @@ import { Web3AuthNetwork, SeedlessOnboardingControllerError, AuthConnection, + SecretType, + SecretMetadataVersion, } from './constants'; import { RecoveryError } from './errors'; +import { SecretMetadata } from './SecretMetadata'; import { getDefaultSeedlessOnboardingControllerState, SeedlessOnboardingController, } from './SeedlessOnboardingController'; -import { SeedPhraseMetadata } from './SeedPhraseMetadata'; import type { AllowedActions, AllowedEvents, @@ -48,7 +50,7 @@ import { } from '../tests/__fixtures__/topfClient'; import { createMockSecretDataGetResponse, - MULTIPLE_MOCK_SEEDPHRASE_METADATA, + MULTIPLE_MOCK_SECRET_METADATA, } from '../tests/mocks/toprf'; import { MockToprfEncryptorDecryptor } from '../tests/mocks/toprfEncryptor'; import MockVaultEncryptor from '../tests/mocks/vaultEncryptor'; @@ -1263,7 +1265,7 @@ describe('SeedlessOnboardingController', () => { const mockSecretDataGet = handleMockSecretDataGet({ status: 200, body: createMockSecretDataGetResponse( - MULTIPLE_MOCK_SEEDPHRASE_METADATA, + MULTIPLE_MOCK_SECRET_METADATA, MOCK_PASSWORD, ), }); @@ -2117,29 +2119,45 @@ describe('SeedlessOnboardingController', () => { }); describe('SeedPhraseMetadata', () => { - it('should be able to create a seed phrase metadata', () => { + it('should be able to create a seed phrase metadata with default options', () => { // should be able to create a SeedPhraseMetadata instance via constructor - const seedPhraseMetadata = new SeedPhraseMetadata(MOCK_SEED_PHRASE); + const seedPhraseMetadata = new SecretMetadata(MOCK_SEED_PHRASE); expect(seedPhraseMetadata.data).toBeDefined(); expect(seedPhraseMetadata.timestamp).toBeDefined(); + expect(seedPhraseMetadata.type).toBe(SecretType.Mnemonic); + expect(seedPhraseMetadata.version).toBe(SecretMetadataVersion.V1); // should be able to create a SeedPhraseMetadata instance with a timestamp via constructor const timestamp = 18_000; - const seedPhraseMetadata2 = new SeedPhraseMetadata( - MOCK_SEED_PHRASE, + const seedPhraseMetadata2 = new SecretMetadata(MOCK_SEED_PHRASE, { timestamp, - ); + }); expect(seedPhraseMetadata2.data).toBeDefined(); expect(seedPhraseMetadata2.timestamp).toBe(timestamp); expect(seedPhraseMetadata2.data).toStrictEqual(MOCK_SEED_PHRASE); + expect(seedPhraseMetadata2.type).toBe(SecretType.Mnemonic); + }); + + it('should be able to add metadata to a seed phrase', () => { + const timestamp = 18_000; + const seedPhraseMetadata = new SecretMetadata(MOCK_SEED_PHRASE, { + type: SecretType.PrivateKey, + timestamp, + }); + expect(seedPhraseMetadata.type).toBe(SecretType.PrivateKey); + expect(seedPhraseMetadata.timestamp).toBe(timestamp); }); it('should be able to correctly create `SeedPhraseMetadata` Array for batch seedphrases', () => { const seedPhrases = ['seed phrase 1', 'seed phrase 2', 'seed phrase 3']; - const rawSeedPhrases = seedPhrases.map(stringToBytes); + const rawSeedPhrases = seedPhrases.map((srp) => ({ + value: stringToBytes(srp), + options: { + type: SecretType.Mnemonic, + }, + })); - const seedPhraseMetadataArray = - SeedPhraseMetadata.fromBatchSeedPhrases(rawSeedPhrases); + const seedPhraseMetadataArray = SecretMetadata.fromBatch(rawSeedPhrases); expect(seedPhraseMetadataArray).toHaveLength(seedPhrases.length); // check the timestamp, the first one should be the oldest @@ -2152,10 +2170,10 @@ describe('SeedlessOnboardingController', () => { }); it('should be able to serialized and parse a seed phrase metadata', () => { - const seedPhraseMetadata = new SeedPhraseMetadata(MOCK_SEED_PHRASE); + const seedPhraseMetadata = new SecretMetadata(MOCK_SEED_PHRASE); const serializedSeedPhraseBytes = seedPhraseMetadata.toBytes(); - const parsedSeedPhraseMetadata = SeedPhraseMetadata.fromRawMetadata( + const parsedSeedPhraseMetadata = SecretMetadata.fromRawMetadata( serializedSeedPhraseBytes, ); expect(parsedSeedPhraseMetadata.data).toBeDefined(); @@ -2164,17 +2182,15 @@ describe('SeedlessOnboardingController', () => { }); it('should be able to sort seed phrase metadata', () => { - const mockSeedPhraseMetadata1 = new SeedPhraseMetadata( - MOCK_SEED_PHRASE, - 1000, - ); - const mockSeedPhraseMetadata2 = new SeedPhraseMetadata( - MOCK_SEED_PHRASE, - 2000, - ); + const mockSeedPhraseMetadata1 = new SecretMetadata(MOCK_SEED_PHRASE, { + timestamp: 1000, + }); + const mockSeedPhraseMetadata2 = new SecretMetadata(MOCK_SEED_PHRASE, { + timestamp: 2000, + }); // sort in ascending order - const sortedSeedPhraseMetadata = SeedPhraseMetadata.sort( + const sortedSeedPhraseMetadata = SecretMetadata.sort( [mockSeedPhraseMetadata1, mockSeedPhraseMetadata2], 'asc', ); @@ -2183,7 +2199,7 @@ describe('SeedlessOnboardingController', () => { ); // sort in descending order - const sortedSeedPhraseMetadataDesc = SeedPhraseMetadata.sort( + const sortedSeedPhraseMetadataDesc = SecretMetadata.sort( [mockSeedPhraseMetadata1, mockSeedPhraseMetadata2], 'desc', ); @@ -2191,5 +2207,86 @@ describe('SeedlessOnboardingController', () => { sortedSeedPhraseMetadataDesc[1].timestamp, ); }); + + it('should be able to overwrite the default Generic DataType', () => { + const secret1 = new SecretMetadata('private-key-1', { + type: SecretType.PrivateKey, + }); + expect(secret1.data).toBe('private-key-1'); + expect(secret1.type).toBe(SecretType.PrivateKey); + expect(secret1.version).toBe(SecretMetadataVersion.V1); + + // should be able to convert to bytes + const secret1Bytes = secret1.toBytes(); + const parsedSecret1 = + SecretMetadata.fromRawMetadata(secret1Bytes); + expect(parsedSecret1.data).toBe('private-key-1'); + expect(parsedSecret1.type).toBe(SecretType.PrivateKey); + expect(parsedSecret1.version).toBe(SecretMetadataVersion.V1); + + const secret2 = new SecretMetadata(MOCK_SEED_PHRASE, { + type: SecretType.Mnemonic, + }); + expect(secret2.data).toStrictEqual(MOCK_SEED_PHRASE); + expect(secret2.type).toBe(SecretType.Mnemonic); + + const secret2Bytes = secret2.toBytes(); + const parsedSecret2 = + SecretMetadata.fromRawMetadata(secret2Bytes); + expect(parsedSecret2.data).toStrictEqual(MOCK_SEED_PHRASE); + expect(parsedSecret2.type).toBe(SecretType.Mnemonic); + }); + + it('should be able to parse the array of Mixed SecretMetadata', () => { + const MOCK_PRIVATE_KEY = 'private-key-1'; + const secret1 = new SecretMetadata(MOCK_PRIVATE_KEY, { + type: SecretType.PrivateKey, + }); + const secret2 = new SecretMetadata(MOCK_SEED_PHRASE, { + type: SecretType.Mnemonic, + }); + + const secrets = [secret1.toBytes(), secret2.toBytes()]; + + const parsedSecrets = + SecretMetadata.parseSecretsFromMetadataStore(secrets); + expect(parsedSecrets).toHaveLength(2); + expect(parsedSecrets[0].data).toBe(MOCK_PRIVATE_KEY); + expect(parsedSecrets[0].type).toBe(SecretType.PrivateKey); + expect(parsedSecrets[1].data).toStrictEqual(MOCK_SEED_PHRASE); + expect(parsedSecrets[1].type).toBe(SecretType.Mnemonic); + }); + + it('should be able to filter the array of SecretMetadata by type', () => { + const MOCK_PRIVATE_KEY = 'MOCK_PRIVATE_KEY'; + const secret1 = new SecretMetadata(MOCK_PRIVATE_KEY, { + type: SecretType.PrivateKey, + }); + const secret2 = new SecretMetadata(MOCK_SEED_PHRASE, { + type: SecretType.Mnemonic, + }); + const secret3 = new SecretMetadata(MOCK_SEED_PHRASE); + + const secrets = [secret1.toBytes(), secret2.toBytes(), secret3.toBytes()]; + + const mnemonicSecrets = SecretMetadata.parseSecretsFromMetadataStore( + secrets, + SecretType.Mnemonic, + ); + expect(mnemonicSecrets).toHaveLength(2); + expect(mnemonicSecrets[0].data).toStrictEqual(MOCK_SEED_PHRASE); + expect(mnemonicSecrets[0].type).toBe(SecretType.Mnemonic); + expect(mnemonicSecrets[1].data).toStrictEqual(MOCK_SEED_PHRASE); + expect(mnemonicSecrets[1].type).toBe(SecretType.Mnemonic); + + const privateKeySecrets = SecretMetadata.parseSecretsFromMetadataStore( + secrets, + SecretType.PrivateKey, + ); + + expect(privateKeySecrets).toHaveLength(1); + expect(privateKeySecrets[0].data).toBe(MOCK_PRIVATE_KEY); + expect(privateKeySecrets[0].type).toBe(SecretType.PrivateKey); + }); }); }); diff --git a/packages/seedless-onboarding-controller/src/SeedlessOnboardingController.ts b/packages/seedless-onboarding-controller/src/SeedlessOnboardingController.ts index bb083a43f9d..281be8691ac 100644 --- a/packages/seedless-onboarding-controller/src/SeedlessOnboardingController.ts +++ b/packages/seedless-onboarding-controller/src/SeedlessOnboardingController.ts @@ -19,12 +19,13 @@ import { Mutex } from 'async-mutex'; import { type AuthConnection, controllerName, + SecretType, SeedlessOnboardingControllerError, Web3AuthNetwork, } from './constants'; import { RecoveryError } from './errors'; import { projectLogger, createModuleLogger } from './logger'; -import { SeedPhraseMetadata } from './SeedPhraseMetadata'; +import { SecretMetadata } from './SecretMetadata'; import type { MutuallyExclusiveCallback, SeedlessOnboardingControllerMessenger, @@ -313,7 +314,11 @@ export class SeedlessOnboardingController extends BaseController< }); } - return SeedPhraseMetadata.parseSeedPhraseFromMetadataStore(secretData); + const secrets = SecretMetadata.parseSecretsFromMetadataStore( + secretData, + SecretType.Mnemonic, + ); + return secrets.map((secret) => secret.data); } catch (error) { log('Error fetching seed phrase metadata', error); throw new Error( @@ -550,7 +555,7 @@ export class SeedlessOnboardingController extends BaseController< try { const { keyringId, seedPhrase, encKey, authKeyPair } = params; - const seedPhraseMetadata = new SeedPhraseMetadata(seedPhrase); + const seedPhraseMetadata = new SecretMetadata(seedPhrase); const secretData = seedPhraseMetadata.toBytes(); await this.#withPersistedSeedPhraseBackupsState(async () => { await this.toprfClient.addSecretDataItem({ diff --git a/packages/seedless-onboarding-controller/src/constants.ts b/packages/seedless-onboarding-controller/src/constants.ts index df26d8a55c5..8428546e048 100644 --- a/packages/seedless-onboarding-controller/src/constants.ts +++ b/packages/seedless-onboarding-controller/src/constants.ts @@ -11,6 +11,15 @@ export enum AuthConnection { Apple = 'apple', } +export enum SecretType { + Mnemonic = 'mnemonic', + PrivateKey = 'privateKey', +} + +export enum SecretMetadataVersion { + V1 = 'v1', +} + export enum SeedlessOnboardingControllerError { ControllerLocked = `${controllerName} - The operation cannot be completed while the controller is locked.`, AuthenticationError = `${controllerName} - Authentication error`, @@ -25,7 +34,7 @@ export enum SeedlessOnboardingControllerError { InvalidVaultData = `${controllerName} - Invalid vault data`, VaultDataError = `${controllerName} - The decrypted vault has an unexpected shape.`, VaultError = `${controllerName} - Cannot unlock without a previous vault.`, - InvalidSeedPhraseMetadata = `${controllerName} - Invalid seed phrase metadata`, + InvalidSecretMetadata = `${controllerName} - Invalid secret metadata`, FailedToEncryptAndStoreSeedPhraseBackup = `${controllerName} - Failed to encrypt and store seed phrase backup`, FailedToFetchSeedPhraseMetadata = `${controllerName} - Failed to fetch seed phrase metadata`, FailedToChangePassword = `${controllerName} - Failed to change password`, diff --git a/packages/seedless-onboarding-controller/src/types.ts b/packages/seedless-onboarding-controller/src/types.ts index 052bd920163..0fb2d6340a0 100644 --- a/packages/seedless-onboarding-controller/src/types.ts +++ b/packages/seedless-onboarding-controller/src/types.ts @@ -12,6 +12,8 @@ import type { MutexInterface } from 'async-mutex'; import type { AuthConnection, controllerName, + SecretMetadataVersion, + SecretType, Web3AuthNetwork, } from './constants'; @@ -181,3 +183,23 @@ export type VaultData = { */ toprfAuthKeyPair: string; }; + +export type SecretDataType = Uint8Array | string | number; + +/** + * The constructor options for the seed phrase metadata. + */ +export type SecretMetadataOptions = { + /** + * The timestamp when the seed phrase was created. + */ + timestamp: number; + /** + * The type of the seed phrase. + */ + type: SecretType; + /** + * The version of the seed phrase metadata. + */ + version: SecretMetadataVersion; +}; diff --git a/packages/seedless-onboarding-controller/tests/mocks/toprf.ts b/packages/seedless-onboarding-controller/tests/mocks/toprf.ts index ce8e2185bc2..a240e8a1127 100644 --- a/packages/seedless-onboarding-controller/tests/mocks/toprf.ts +++ b/packages/seedless-onboarding-controller/tests/mocks/toprf.ts @@ -50,7 +50,7 @@ export const MOCK_RELEASE_METADATA_LOCK_RESPONSE = { status: 1, }; -export const MULTIPLE_MOCK_SEEDPHRASE_METADATA = [ +export const MULTIPLE_MOCK_SECRET_METADATA = [ { data: new Uint8Array(Buffer.from('seedPhrase1', 'utf-8')), timestamp: 10,