Repository: dusk-network/rusk (language: rust)

Issue:
Secure seed with ephemeral encryption
Add encrypt / decrypt logic in the profile generator seeder.


Discussion / hints:

Fix patch:
diff --git a/web-wallet/CHANGELOG.md b/web-wallet/CHANGELOG.md
index a39ff4c25a..0db25e9920 100644
--- a/web-wallet/CHANGELOG.md
+++ b/web-wallet/CHANGELOG.md
@@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ### Added
 
+- Add ephemeral encryption to secure the seed while in memory [#3354]
+
 ### Changed
 
 - Update Transactions list design [#1922]
@@ -522,6 +524,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 [#3287]: https://github.com/dusk-network/rusk/issues/3287
 [#3333]: https://github.com/dusk-network/rusk/issues/3333
 [#3339]: https://github.com/dusk-network/rusk/issues/3339
+[#3354]: https://github.com/dusk-network/rusk/issues/3354
 [#3356]: https://github.com/dusk-network/rusk/issues/3356
 
 <!-- VERSIONS -->
diff --git a/web-wallet/src/lib/services/loginInfoStorage/index.js b/web-wallet/src/lib/services/loginInfoStorage/index.js
index 5538624484..568317af72 100644
--- a/web-wallet/src/lib/services/loginInfoStorage/index.js
+++ b/web-wallet/src/lib/services/loginInfoStorage/index.js
@@ -10,7 +10,7 @@ const fromStorageString = unless(
 const toStorageString = compose(JSON.stringify, mapValuesWith(bytesToBase64));
 
 const loginInfoStorage = {
-  /** @returns {MnemonicEncryptInfo | null} */
+  /** @returns {WalletEncryptInfo | null} */
   get() {
     return fromStorageString(localStorage.getItem(storeKey));
   },
@@ -19,7 +19,7 @@ const loginInfoStorage = {
     localStorage.removeItem(storeKey);
   },
 
-  /** @param {MnemonicEncryptInfo} info */
+  /** @param {WalletEncryptInfo} info */
   set(info) {
     localStorage.setItem(storeKey, toStorageString(info));
   },
diff --git a/web-wallet/src/lib/wallet/decryptBuffer.js b/web-wallet/src/lib/wallet/decryptBuffer.js
new file mode 100644
index 0000000000..ccf5b82d72
--- /dev/null
+++ b/web-wallet/src/lib/wallet/decryptBuffer.js
@@ -0,0 +1,15 @@
+import getDerivedKey from "./getDerivedKey";
+
+/**
+ * @param {WalletEncryptInfo} encryptInfo
+ * @param {string} pwd
+ * @returns {Promise<ArrayBuffer>}
+ */
+async function decryptBuffer(encryptInfo, pwd) {
+  const { data, iv, salt } = encryptInfo;
+  const key = await getDerivedKey(pwd, salt);
+
+  return await crypto.subtle.decrypt({ iv, name: "AES-GCM" }, key, data);
+}
+
+export default decryptBuffer;
diff --git a/web-wallet/src/lib/wallet/decryptMnemonic.js b/web-wallet/src/lib/wallet/decryptMnemonic.js
index 133ea5d536..ad4d0eac73 100644
--- a/web-wallet/src/lib/wallet/decryptMnemonic.js
+++ b/web-wallet/src/lib/wallet/decryptMnemonic.js
@@ -1,20 +1,11 @@
-import getDerivedKey from "./getDerivedKey";
+import decryptBuffer from "./decryptBuffer";
 
 /**
- * @param {MnemonicEncryptInfo} mnemonicEncryptInfo
- * @param {String} pwd
- * @returns {Promise<String>}
+ * @param {WalletEncryptInfo} encryptInfo
+ * @param {string} pwd
+ * @returns {Promise<string>}
  */
-async function decryptMnemonic(mnemonicEncryptInfo, pwd) {
-  const { data, iv, salt } = mnemonicEncryptInfo;
-  const key = await getDerivedKey(pwd, salt);
-  const plaintext = await crypto.subtle.decrypt(
-    { iv, name: "AES-GCM" },
-    key,
-    data
-  );
-
-  return new TextDecoder().decode(plaintext);
-}
+const decryptMnemonic = async (encryptInfo, pwd) =>
+  new TextDecoder().decode(await decryptBuffer(encryptInfo, pwd));
 
 export default decryptMnemonic;
diff --git a/web-wallet/src/lib/wallet/encryptBuffer.js b/web-wallet/src/lib/wallet/encryptBuffer.js
new file mode 100644
index 0000000000..7f78eee1db
--- /dev/null
+++ b/web-wallet/src/lib/wallet/encryptBuffer.js
@@ -0,0 +1,19 @@
+import getDerivedKey from "./getDerivedKey";
+
+/**
+ * @param {BufferSource} buffer
+ * @param {string} pwd
+ * @returns {Promise<WalletEncryptInfo>}
+ */
+async function encryptBuffer(buffer, pwd) {
+  const salt = crypto.getRandomValues(new Uint8Array(32));
+  const iv = crypto.getRandomValues(new Uint8Array(12));
+  const key = await getDerivedKey(pwd, salt);
+  const data = new Uint8Array(
+    await crypto.subtle.encrypt({ iv, name: "AES-GCM" }, key, buffer)
+  );
+
+  return { data, iv, salt };
+}
+
+export default encryptBuffer;
diff --git a/web-wallet/src/lib/wallet/encryptMnemonic.js b/web-wallet/src/lib/wallet/encryptMnemonic.js
index fa29a156cf..dede3bbbc5 100644
--- a/web-wallet/src/lib/wallet/encryptMnemonic.js
+++ b/web-wallet/src/lib/wallet/encryptMnemonic.js
@@ -1,20 +1,11 @@
-import getDerivedKey from "./getDerivedKey";
+import encryptBuffer from "./encryptBuffer";
 
 /**
- * @param {String} mnemonic
- * @param {String} pwd
- * @returns {Promise<MnemonicEncryptInfo>}
+ * @param {string} mnemonic
+ * @param {string} pwd
+ * @returns {Promise<WalletEncryptInfo>}
  */
-async function encryptMnemonic(mnemonic, pwd) {
-  const plaintext = new TextEncoder().encode(mnemonic);
-  const salt = crypto.getRandomValues(new Uint8Array(32));
-  const iv = crypto.getRandomValues(new Uint8Array(12));
-  const key = await getDerivedKey(pwd, salt);
-  const data = new Uint8Array(
-    await crypto.subtle.encrypt({ iv, name: "AES-GCM" }, key, plaintext)
-  );
-
-  return { data, iv, salt };
-}
+const encryptMnemonic = async (mnemonic, pwd) =>
+  await encryptBuffer(new TextEncoder().encode(mnemonic), pwd);
 
 export default encryptMnemonic;
diff --git a/web-wallet/src/lib/wallet/index.js b/web-wallet/src/lib/wallet/index.js
index 8607aaa279..d3b10d5f3b 100644
--- a/web-wallet/src/lib/wallet/index.js
+++ b/web-wallet/src/lib/wallet/index.js
@@ -1,4 +1,6 @@
+export { default as decryptBuffer } from "./decryptBuffer";
 export { default as decryptMnemonic } from "./decryptMnemonic";
+export { default as encryptBuffer } from "./encryptBuffer";
 export { default as encryptMnemonic } from "./encryptMnemonic";
 export { default as getSeedFromMnemonic } from "./getSeedFromMnemonic";
 export { default as initializeWallet } from "./initializeWallet";
diff --git a/web-wallet/src/lib/wallet/initializeWallet.js b/web-wallet/src/lib/wallet/initializeWallet.js
index 3bf7fc1fcf..98c8cfdbc8 100644
--- a/web-wallet/src/lib/wallet/initializeWallet.js
+++ b/web-wallet/src/lib/wallet/initializeWallet.js
@@ -5,12 +5,14 @@ import profileGeneratorFrom from "./profileGeneratorFrom";
 
 /**
  * @param {string} mnemonic
- * @param {bigint | undefined} syncFrom
+ * @param {bigint} [syncFrom]
  */
-async function initializeWallet(mnemonic, syncFrom = undefined) {
+async function initializeWallet(mnemonic, syncFrom) {
   settingsStore.reset();
 
-  const profileGenerator = profileGeneratorFrom(getSeedFromMnemonic(mnemonic));
+  const profileGenerator = await profileGeneratorFrom(
+    getSeedFromMnemonic(mnemonic)
+  );
 
   walletStore.clearLocalDataAndInit(profileGenerator, syncFrom);
 }
diff --git a/web-wallet/src/lib/wallet/profileGeneratorFrom.js b/web-wallet/src/lib/wallet/profileGeneratorFrom.js
index 30040b32f7..72c01af213 100644
--- a/web-wallet/src/lib/wallet/profileGeneratorFrom.js
+++ b/web-wallet/src/lib/wallet/profileGeneratorFrom.js
@@ -1,13 +1,23 @@
 import { ProfileGenerator } from "$lib/vendor/w3sper.js/src/mod";
 
-/** @type {(seed: Uint8Array) => ProfileGenerator} */
-function profileGeneratorFrom(seed) {
-  /*
-   * For now we create a function that returns
-   * a constant value.
-   * In future we can add some encrypt / decrypt logic.
-   */
-  const seeder = async () => seed;
+import decryptBuffer from "./decryptBuffer";
+import encryptBuffer from "./encryptBuffer";
+
+/** @type {(seed: Uint8Array) => Promise<ProfileGenerator>} */
+async function profileGeneratorFrom(seed) {
+  // creating a local copy
+  seed = seed.slice();
+
+  const pwd = new TextDecoder().decode(
+    crypto.getRandomValues(new Uint8Array(32))
+  );
+  const encryptInfo = await encryptBuffer(seed, pwd);
+
+  // destroying data inside the local copy
+  seed.fill(0);
+
+  const seeder = async () =>
+    new Uint8Array(await decryptBuffer(encryptInfo, pwd));
 
   return new ProfileGenerator(seeder);
 }
diff --git a/web-wallet/src/lib/wallet/wallet.d.ts b/web-wallet/src/lib/wallet/wallet.d.ts
index bcc2a847cf..64d1170e62 100644
--- a/web-wallet/src/lib/wallet/wallet.d.ts
+++ b/web-wallet/src/lib/wallet/wallet.d.ts
@@ -1,4 +1,4 @@
-type MnemonicEncryptInfo = {
+type WalletEncryptInfo = {
   data: Uint8Array;
   iv: Uint8Array;
   salt: Uint8Array;
diff --git a/web-wallet/src/routes/(welcome)/unlock/+page.svelte b/web-wallet/src/routes/(welcome)/unlock/+page.svelte
index c592b0fdfb..2734f30249 100644
--- a/web-wallet/src/routes/(welcome)/unlock/+page.svelte
+++ b/web-wallet/src/routes/(welcome)/unlock/+page.svelte
@@ -29,7 +29,7 @@
 
   /** @type {(seed: Uint8Array) => Promise<import("$lib/vendor/w3sper.js/src/mod").ProfileGenerator>} */
   async function checkLocalData(seed) {
-    const profileGenerator = profileGeneratorFrom(seed);
+    const profileGenerator = await profileGeneratorFrom(seed);
     const defaultAddress = (await profileGenerator.default).address.toString();
     const currentAddress = $settingsStore.userId;
 
@@ -46,7 +46,7 @@
       ? getSeedFromMnemonic(mnemonic)
       : Promise.reject(new InvalidMnemonicError());
 
-  /** @type {(loginInfo: MnemonicEncryptInfo) => (pwd: string) => Promise<Uint8Array>} */
+  /** @type {(loginInfo: WalletEncryptInfo) => (pwd: string) => Promise<Uint8Array>} */
   const getSeedFromInfo = (loginInfo) => (pwd) =>
     decryptMnemonic(loginInfo, pwd).then(getSeedFromMnemonic, () =>
       Promise.reject(new InvalidPasswordError())


Test patch:
diff --git a/web-wallet/src/lib/wallet/__tests__/decryptBuffer.spec.js b/web-wallet/src/lib/wallet/__tests__/decryptBuffer.spec.js
new file mode 100644
index 0000000000..116d5d95c4
--- /dev/null
+++ b/web-wallet/src/lib/wallet/__tests__/decryptBuffer.spec.js
@@ -0,0 +1,16 @@
+import { describe, expect, it } from "vitest";
+import { generateMnemonic } from "bip39";
+
+import { decryptBuffer, encryptBuffer } from "..";
+
+describe("decryptBuffer", () => {
+  const plaintext = new TextEncoder().encode(generateMnemonic());
+  const pwd = "some password";
+
+  it("should be able to decrypt the mnemonic phrase using the given password", async () => {
+    const encryptInfo = await encryptBuffer(plaintext, pwd);
+    const decrypted = await decryptBuffer(encryptInfo, pwd);
+
+    expect(decrypted.toString()).toBe(plaintext.buffer.toString());
+  });
+});
diff --git a/web-wallet/src/lib/wallet/__tests__/encryptBuffer.spec.js b/web-wallet/src/lib/wallet/__tests__/encryptBuffer.spec.js
new file mode 100644
index 0000000000..632dd0b635
--- /dev/null
+++ b/web-wallet/src/lib/wallet/__tests__/encryptBuffer.spec.js
@@ -0,0 +1,21 @@
+import { describe, expect, it } from "vitest";
+import { generateMnemonic } from "bip39";
+
+import { encryptBuffer, getSeedFromMnemonic } from "..";
+
+describe("encryptBuffer", () => {
+  it("should be able to encrypt a buffer using the given password", async () => {
+    const pwd = "some password";
+    const buffer = getSeedFromMnemonic(generateMnemonic());
+    const result = await encryptBuffer(buffer, pwd);
+
+    expect(result).toMatchObject({
+      data: expect.any(Uint8Array),
+      iv: expect.any(Uint8Array),
+      salt: expect.any(Uint8Array),
+    });
+    expect(result.data.toString()).not.toBe(buffer.toString());
+    expect(result.iv.length).toBe(12);
+    expect(result.salt.length).toBe(32);
+  });
+});
diff --git a/web-wallet/src/lib/wallet/__tests__/encryptMnemonic.spec.js b/web-wallet/src/lib/wallet/__tests__/encryptMnemonic.spec.js
index 6a0161738a..58a68132d4 100644
--- a/web-wallet/src/lib/wallet/__tests__/encryptMnemonic.spec.js
+++ b/web-wallet/src/lib/wallet/__tests__/encryptMnemonic.spec.js
@@ -15,6 +15,9 @@ describe("encryptMnemonic", () => {
       iv: expect.any(Uint8Array),
       salt: expect.any(Uint8Array),
     });
+    expect(result.data.toString()).not.toBe(
+      new TextEncoder().encode(mnemonic).toString()
+    );
     expect(result.iv.length).toBe(12);
     expect(result.salt.length).toBe(32);
   });
diff --git a/web-wallet/src/lib/wallet/__tests__/profileGeneratorFrom.spec.js b/web-wallet/src/lib/wallet/__tests__/profileGeneratorFrom.spec.js
index 643aa82aec..af7caa4785 100644
--- a/web-wallet/src/lib/wallet/__tests__/profileGeneratorFrom.spec.js
+++ b/web-wallet/src/lib/wallet/__tests__/profileGeneratorFrom.spec.js
@@ -29,13 +29,16 @@ describe("profileGeneratorFrom", () => {
     const mnemonic =
       "cart dad sail wreck robot grit combine noble rap farm slide sad";
     const seed = getSeedFromMnemonic(mnemonic);
+    const seedCopy = seed.slice();
 
-    profileGeneratorFrom(seed);
+    await profileGeneratorFrom(seed);
 
-    const seederResult = ProfileGeneratorMock.mock.calls[0][0]();
+    const seederResult = await ProfileGeneratorMock.mock.calls[0][0]();
 
     expect(ProfileGeneratorMock).toHaveBeenCalledTimes(1);
-    expect(seederResult).toBeInstanceOf(Promise);
-    await expect(seederResult).resolves.toBe(seed);
+    expect(seederResult.toString()).toBe(seed.toString());
+
+    // ensures that the function doesn't mutate the seed
+    expect(seed.toString()).toBe(seedCopy.toString());
   });
 });
diff --git a/web-wallet/src/routes/(welcome)/setup/create/__tests__/page.spec.js b/web-wallet/src/routes/(welcome)/setup/create/__tests__/page.spec.js
index 382a4b827e..19fe1739e9 100644
--- a/web-wallet/src/routes/(welcome)/setup/create/__tests__/page.spec.js
+++ b/web-wallet/src/routes/(welcome)/setup/create/__tests__/page.spec.js
@@ -49,7 +49,8 @@ describe("Create", async () => {
   const seed = walletLib.getSeedFromMnemonic(mnemonic);
   const userId = await walletLib
     .profileGeneratorFrom(seed)
-    .default.then(getKey("address"))
+    .then(getKey("default"))
+    .then(getKey("address"))
     .then(String);
 
   const blockHeightSpy = vi
@@ -68,7 +69,7 @@ describe("Create", async () => {
     .mockResolvedValue(undefined);
   const initWalletSpy = vi.spyOn(walletLib, "initializeWallet");
 
-  afterEach(async () => {
+  afterEach(() => {
     cleanup();
     settingsStore.reset();
     blockHeightSpy.mockClear();
@@ -299,8 +300,6 @@ describe("Create", async () => {
       await fireEvent.click(getByRole("button", { name: word }));
     });
 
-    await tick();
-
     await fireEvent.click(getByRole("button", { name: "Next" }));
     await fireEvent.click(getByRole("button", { name: "Next" }));
 
@@ -310,8 +309,6 @@ describe("Create", async () => {
   });
 
   it("ensures the All Done step renders as expected", async () => {
-    vi.useFakeTimers();
-
     const { container, getByRole, getAllByRole } = render(Create);
 
     await fireEvent.click(getByRole("button", { name: "Accept" }));
@@ -328,8 +325,6 @@ describe("Create", async () => {
       await fireEvent.click(getByRole("button", { name: word }));
     });
 
-    await tick();
-
     await fireEvent.click(getByRole("button", { name: "Next" }));
     await fireEvent.click(getByRole("button", { name: "Next" }));
     await fireEvent.click(getByRole("button", { name: "Next" }));
@@ -337,21 +332,14 @@ describe("Create", async () => {
     expect(container.firstChild).toMatchSnapshot();
 
     /*
-     * We wait for the sync promise to complete, because it
-     * sets the `userId` in the settingsStore.
-     * Without waiting for it, the setting of the `userId`
-     * happens after the `settingsStore.reset()` call in the
-     * `afterEach` hook and leaves the store "dirty" for
-     * subsequent tests.
+     * We wait for the `clearLocalDataAndInit` call inside `initializeWallet`
+     * as the promise is not awaited and can happen after the test ends
+     * invalidating the checks of other tests.
      */
-    await vi.runOnlyPendingTimersAsync();
-
-    vi.useRealTimers();
+    await vi.waitUntil(() => clearAndInitSpy.mock.calls.length === 1);
   });
 
   it("should initialize the wallet without setting a password", async () => {
-    vi.useFakeTimers();
-
     const { getByRole, getAllByRole } = render(Create);
 
     // ToS step
@@ -373,8 +361,6 @@ describe("Create", async () => {
       await fireEvent.click(getByRole("button", { name: word }));
     });
 
-    await tick();
-
     await fireEvent.click(getByRole("button", { name: "Next" }));
 
     // Set Password step
@@ -387,33 +373,25 @@ describe("Create", async () => {
     // Network Syncing step
     await fireEvent.click(getByRole("button", { name: "Next" }));
 
-    // All Done step
-    await fireEvent.click(getByRole("button", { name: "Next" }));
-
-    await vi.waitUntil(() => gotoSpy.mock.calls.length > 0);
-
     expect(settingsResetSpy).toHaveBeenCalledTimes(1);
     expect(initWalletSpy).toHaveBeenCalledTimes(1);
     expect(initWalletSpy).toHaveBeenCalledWith(mnemonic);
+
+    await vi.waitUntil(() => clearAndInitSpy.mock.calls.length === 1);
+
     expect(clearAndInitSpy).toHaveBeenCalledTimes(1);
     expect(clearAndInitSpy).toHaveBeenCalledWith(
       expect.any(ProfileGenerator),
       undefined
     );
-    expect(gotoSpy).toHaveBeenCalledTimes(1);
-    expect(gotoSpy).toHaveBeenCalledWith("/dashboard");
 
-    /*
-     * We wait for the sync promise to complete, because it
-     * sets the `userId` in the settingsStore.
-     * Without waiting for it, the setting of the `userId`
-     * happens after the `settingsStore.reset()` call in the
-     * `afterEach` hook and leaves the store "dirty" for
-     * subsequent tests.
-     */
-    await vi.runOnlyPendingTimersAsync();
+    // All Done step
+    await fireEvent.click(getByRole("button", { name: "Next" }));
+
+    await vi.waitUntil(() => gotoSpy.mock.calls.length > 0);
 
-    vi.useRealTimers();
+    expect(gotoSpy).toHaveBeenCalledTimes(1);
+    expect(gotoSpy).toHaveBeenCalledWith("/dashboard");
   });
 
   it("should initialize the wallet encrypted mnemonic saved in localStorage", async () => {
@@ -438,8 +416,6 @@ describe("Create", async () => {
       await fireEvent.click(getByRole("button", { name: word }));
     });
 
-    await tick();
-
     await fireEvent.click(getByRole("button", { name: "Next" }));
 
     // Set Password step
@@ -449,6 +425,7 @@ describe("Create", async () => {
     await fireInput(asInput(getByPlaceholderText("Confirm Password")), pwd);
 
     expect(loginInfoStorage.get()).toBeNull();
+
     await fireEvent.click(getByRole("button", { name: "Next" }));
     await vi.waitFor(() => {
       expect(loginInfoStorage.get()).not.toBeNull();
@@ -457,19 +434,23 @@ describe("Create", async () => {
     // Network Syncing step
     await fireEvent.click(getByRole("button", { name: "Next" }));
 
-    // All Done step
-    await fireEvent.click(getByRole("button", { name: "Next" }));
-
-    await vi.waitUntil(() => gotoSpy.mock.calls.length > 0);
-
     expect(settingsResetSpy).toHaveBeenCalledTimes(1);
     expect(initWalletSpy).toHaveBeenCalledTimes(1);
     expect(initWalletSpy).toHaveBeenCalledWith(mnemonic);
+
+    await vi.waitUntil(() => clearAndInitSpy.mock.calls.length === 1);
+
     expect(clearAndInitSpy).toHaveBeenCalledTimes(1);
     expect(clearAndInitSpy).toHaveBeenCalledWith(
       expect.any(ProfileGenerator),
       undefined
     );
+
+    // All Done step
+    await fireEvent.click(getByRole("button", { name: "Next" }));
+
+    await vi.waitUntil(() => gotoSpy.mock.calls.length > 0);
+
     expect(gotoSpy).toHaveBeenCalledTimes(1);
     expect(gotoSpy).toHaveBeenCalledWith("/dashboard");
   });
diff --git a/web-wallet/src/routes/(welcome)/setup/restore/__tests__/page.spec.js b/web-wallet/src/routes/(welcome)/setup/restore/__tests__/page.spec.js
index 8bd6ca7f97..0b6b7e607c 100644
--- a/web-wallet/src/routes/(welcome)/setup/restore/__tests__/page.spec.js
+++ b/web-wallet/src/routes/(welcome)/setup/restore/__tests__/page.spec.js
@@ -40,7 +40,8 @@ describe("Restore", async () => {
   const seed = walletLib.getSeedFromMnemonic(mnemonic);
   const userId = await walletLib
     .profileGeneratorFrom(seed)
-    .default.then(getKey("address"))
+    .then(getKey("default"))
+    .then(getKey("address"))
     .then(String);
   const gotoSpy = vi.spyOn(navigation, "goto");
   const settingsResetSpy = vi.spyOn(settingsStore, "reset");
@@ -150,7 +151,7 @@ describe("Restore", async () => {
     await fireEvent.click(
       getByRole("button", { name: "Paste mnemonic phrase" })
     );
-    await tick();
+
     expect(nextButton).toBeEnabled();
     await fireEvent.click(nextButton);
 
@@ -163,22 +164,26 @@ describe("Restore", async () => {
     // Block Height Step
     await fireEvent.click(getByRole("button", { name: "Next" }));
 
-    // Syncing Step
-    await fireEvent.click(getByRole("button", { name: "Next" }));
-
-    // All Done Step
-    await fireEvent.click(getByRole("button", { name: "Next" }));
-
-    await vi.waitUntil(() => gotoSpy.mock.calls.length > 0);
-
     expect(settingsResetSpy).toHaveBeenCalledTimes(1);
     expect(initWalletSpy).toHaveBeenCalledTimes(1);
     expect(initWalletSpy).toHaveBeenCalledWith(mnemonic, 0n);
+
+    await vi.waitUntil(() => clearAndInitSpy.mock.calls.length === 1);
+
     expect(clearAndInitSpy).toHaveBeenCalledTimes(1);
     expect(clearAndInitSpy).toHaveBeenCalledWith(
       expect.any(ProfileGenerator),
       0n
     );
+
+    // Syncing Step
+    await fireEvent.click(getByRole("button", { name: "Next" }));
+
+    // All Done Step
+    await fireEvent.click(getByRole("button", { name: "Next" }));
+
+    await vi.waitUntil(() => gotoSpy.mock.calls.length > 0);
+
     expect(gotoSpy).toHaveBeenCalledTimes(1);
     expect(gotoSpy).toHaveBeenCalledWith("/dashboard");
   });
@@ -217,22 +222,26 @@ describe("Restore", async () => {
     // Block Height step
     await fireEvent.click(getByRole("button", { name: "Next" }));
 
-    // Network Sync step
-    await fireEvent.click(getByRole("button", { name: "Next" }));
-
-    // All Done step
-    await fireEvent.click(getByRole("button", { name: "Next" }));
-
-    await vi.waitUntil(() => gotoSpy.mock.calls.length > 0);
-
     expect(settingsResetSpy).toHaveBeenCalledTimes(1);
     expect(initWalletSpy).toHaveBeenCalledTimes(1);
     expect(initWalletSpy).toHaveBeenCalledWith(mnemonic, 0n);
+
+    await vi.waitUntil(() => clearAndInitSpy.mock.calls.length === 1);
+
     expect(clearAndInitSpy).toHaveBeenCalledTimes(1);
     expect(clearAndInitSpy).toHaveBeenCalledWith(
       expect.any(ProfileGenerator),
       0n
     );
+
+    // Network Sync step
+    await fireEvent.click(getByRole("button", { name: "Next" }));
+
+    // All Done step
+    await fireEvent.click(getByRole("button", { name: "Next" }));
+
+    await vi.waitUntil(() => gotoSpy.mock.calls.length > 0);
+
     expect(gotoSpy).toHaveBeenCalledTimes(1);
     expect(gotoSpy).toHaveBeenCalledWith("/dashboard");
   });
diff --git a/web-wallet/src/routes/(welcome)/unlock/__tests__/page.spec.js b/web-wallet/src/routes/(welcome)/unlock/__tests__/page.spec.js
index 3831ca2ec8..47b3cc119a 100644
--- a/web-wallet/src/routes/(welcome)/unlock/__tests__/page.spec.js
+++ b/web-wallet/src/routes/(welcome)/unlock/__tests__/page.spec.js
@@ -40,7 +40,8 @@ describe("Unlock Wallet", async () => {
   const loginInfo = await encryptMnemonic(mnemonic, pwd);
   const seed = getSeedFromMnemonic(mnemonic);
   const userId = await profileGeneratorFrom(seed)
-    .default.then(getKey("address"))
+    .then(getKey("default"))
+    .then(getKey("address"))
     .then(String);
 
   const getErrorElement = () => document.querySelector(".banner--error");