From 3a29f30f65f573b1703ec5e60b95d7566c721f8c Mon Sep 17 00:00:00 2001 From: Laurin Weger Date: Sat, 29 Jun 2024 17:50:14 +0200 Subject: [PATCH] feat: display mnemonic on wallet creation and support for login --- nextgraph/src/local_broker.rs | 37 ++++ ng-app/src-tauri/src/lib.rs | 26 +++ ng-app/src/api.ts | 8 +- ng-app/src/lib/Login.svelte | 264 ++++++++++++++++++-------- ng-app/src/routes/WalletCreate.svelte | 34 +++- ng-app/src/worker.js | 15 +- ng-repo/src/errors.rs | 1 + ng-sdk-js/src/lib.rs | 47 +++++ ng-wallet/src/bip39.rs | 26 +++ ng-wallet/src/lib.rs | 7 +- ng-wallet/src/types.rs | 3 + 11 files changed, 371 insertions(+), 97 deletions(-) diff --git a/nextgraph/src/local_broker.rs b/nextgraph/src/local_broker.rs index 79725c0..edf7e60 100644 --- a/nextgraph/src/local_broker.rs +++ b/nextgraph/src/local_broker.rs @@ -40,6 +40,7 @@ use ng_verifier::types::*; use ng_verifier::verifier::Verifier; use ng_wallet::emojis::encode_pazzle; +use ng_wallet::bip39::encode_mnemonic; use ng_wallet::{create_wallet_first_step_v0, create_wallet_second_step_v0, types::*}; #[cfg(not(target_family = "wasm"))] @@ -1555,6 +1556,27 @@ pub fn wallet_open_with_pazzle( Ok(opened_wallet) } +#[doc(hidden)] +/// This is a bit hard to use as the mnemonic words are encoded in u16. +/// prefer the function wallet_open_with_mnemonic_words +pub fn wallet_open_with_mnemonic( + wallet: &Wallet, + mnemonic: Vec, + pin: [u8; 4], +) -> Result { + if mnemonic.len() != 12 { + return Err(NgError::InvalidMnemonic); + } + // Convert from vec to array. + let mut mnemonic_arr = [0u16; 12]; + for (place, element) in mnemonic_arr.iter_mut().zip(mnemonic.iter()) { + *place = *element; + } + let opened_wallet = ng_wallet::open_wallet_with_mnemonic(wallet, mnemonic_arr, pin)?; + + Ok(opened_wallet) +} + /// Opens a wallet by providing an ordered list of words, and the pin. /// /// If you are opening a wallet that is already known to the LocalBroker, you must then call [wallet_was_opened]. @@ -1569,6 +1591,21 @@ pub fn wallet_open_with_pazzle_words( wallet_open_with_pazzle(wallet, encode_pazzle(pazzle_words)?, pin) } + +/// Opens a wallet by providing an ordered list of mnemonic words, and the pin. +/// +/// If you are opening a wallet that is already known to the LocalBroker, you must then call [wallet_was_opened]. +/// Otherwise, if you are importing, then you must call [wallet_import]. +pub fn wallet_open_with_mnemonic_words( + wallet: &Wallet, + mnemonic: &Vec, + pin: [u8; 4], +) -> Result { + let encoded: Vec = encode_mnemonic(mnemonic)?; + + wallet_open_with_mnemonic(wallet, encoded, pin) +} + /// Imports a wallet into the LocalBroker so the user can then access its content. /// /// the wallet should have been previous opened with [wallet_open_with_pazzle_words]. diff --git a/ng-app/src-tauri/src/lib.rs b/ng-app/src-tauri/src/lib.rs index 8a28099..20db194 100644 --- a/ng-app/src-tauri/src/lib.rs +++ b/ng-app/src-tauri/src/lib.rs @@ -86,6 +86,30 @@ async fn wallet_open_with_pazzle( Ok(wallet) } +#[tauri::command(rename_all = "snake_case")] +async fn wallet_open_with_mnemonic( + wallet: Wallet, + mnemonic: Vec, + pin: [u8; 4], + _app: tauri::AppHandle, +) -> Result { + let wallet = nextgraph::local_broker::wallet_open_with_mnemonic(&wallet, mnemonic, pin) + .map_err(|e| e.to_string())?; + Ok(wallet) +} + +#[tauri::command(rename_all = "snake_case")] +async fn wallet_open_with_mnemonic_words( + wallet: Wallet, + mnemonic_words: Vec, + pin: [u8; 4], + _app: tauri::AppHandle, +) -> Result { + let wallet = nextgraph::local_broker::wallet_open_with_mnemonic_words(&wallet, &mnemonic_words, pin) + .map_err(|e| e.to_string())?; + Ok(wallet) +} + #[tauri::command(rename_all = "snake_case")] async fn wallet_get_file(wallet_name: String, app: tauri::AppHandle) -> Result<(), String> { let ser = nextgraph::local_broker::wallet_get_file(&wallet_name) @@ -493,6 +517,8 @@ impl AppBuilder { wallet_gen_shuffle_for_pazzle_opening, wallet_gen_shuffle_for_pin, wallet_open_with_pazzle, + wallet_open_with_mnemonic, + wallet_open_with_mnemonic_words, wallet_was_opened, wallet_create, wallet_read_file, diff --git a/ng-app/src/api.ts b/ng-app/src/api.ts index 9d7da81..0426a6c 100644 --- a/ng-app/src/api.ts +++ b/ng-app/src/api.ts @@ -16,6 +16,7 @@ const mapping = { "wallet_gen_shuffle_for_pazzle_opening": ["pazzle_length"], "wallet_gen_shuffle_for_pin": [], "wallet_open_with_pazzle": ["wallet","pazzle","pin"], + "wallet_open_with_mnemonic_words": ["wallet","mnemonic_words","pin"], "wallet_was_opened": ["opened_wallet"], "wallet_create": ["params"], "wallet_read_file": ["file"], @@ -169,7 +170,7 @@ const handler = { return false; } else if (path[0] === "get_local_url") { return false; - } else if (path[0] === "wallet_open_with_pazzle") { + } else if (path[0] === "wallet_open_with_pazzle" || path[0] === "wallet_open_with_mnemonic_words") { let arg:any = {}; args.map((el,ix) => arg[mapping[path[0]][ix]]=el) let img = Array.from(new Uint8Array(arg.wallet.V0.content.security_img)); @@ -177,9 +178,8 @@ const handler = { arg.wallet = {V0:{id:arg.wallet.V0.id, sig:arg.wallet.V0.sig, content:{}}}; Object.assign(arg.wallet.V0.content,old_content); arg.wallet.V0.content.security_img = img; - return tauri.invoke(path[0],arg) - } - else { + return tauri.invoke(path[0],arg); + } else { let arg = {}; args.map((el,ix) => arg[mapping[path[0]][ix]]=el) return tauri.invoke(path[0],arg) diff --git a/ng-app/src/lib/Login.svelte b/ng-app/src/lib/Login.svelte index 8bdf5ed..5a09035 100644 --- a/ng-app/src/lib/Login.svelte +++ b/ng-app/src/lib/Login.svelte @@ -25,6 +25,8 @@ ArrowLeftCircle, ArrowPath, LockOpen, + Key, + CheckCircle, } from "svelte-heros-v2"; //import Worker from "../worker.js?worker&inline"; export let wallet; @@ -32,7 +34,7 @@ let tauri_platform = import.meta.env.TAURI_PLATFORM; let mobile = tauri_platform == "android" || tauri_platform == "ios"; - + mobile = true; const dispatch = createEventDispatcher(); onMount(async () => { @@ -64,9 +66,15 @@ loaded = true; } - function letsgo() { + function start_with_pazzle() { loaded = false; step = "pazzle"; + unlockWith = "pazzle"; + } + function start_with_mnemonic() { + loaded = false; + step = "mnemonic"; + unlockWith = "mnemonic"; } let emojis2 = []; @@ -95,6 +103,10 @@ let trusted = false; + let mnemonic = ""; + + let unlockWith: "pazzle" | "mnemonic" | undefined; + function order() { step = "order"; ordered = []; @@ -122,10 +134,8 @@ let cat_idx = shuffle.category_indices[pazzlePage]; let cat = emojis[emoji_cat[cat_idx]]; let idx = shuffle.emoji_indices[pazzlePage][val]; - //console.log(cat_idx, emoji_cat[cat_idx], idx, cat[idx].code); selection[pazzlePage] = { cat: cat_idx, index: idx }; - console.debug(selection, cat, cat_idx, idx, val); if (pazzlePage == pazzle_length - 1) { order(); @@ -138,22 +148,23 @@ step = "opening"; let pazzle = []; - for (const emoji of ordered) { pazzle.push((emoji.cat << 4) + emoji.index); } - //console.log(pazzle); - //console.log(wallet); + const mnemonic_words = mnemonic.split(" "); // open the wallet try { if (tauri_platform) { - let opened_wallet = await ng.wallet_open_with_pazzle( - wallet, - pazzle, - pin_code - ); + let opened_wallet = + unlockWith === "pazzle" + ? await ng.wallet_open_with_pazzle(wallet, pazzle, pin_code) + : await ng.wallet_open_with_mnemonic_words( + wallet, + mnemonic_words, + pin_code + ); // try { // let client = await ng.wallet_was_opened(opened_wallet); // opened_wallet.V0.client = client; @@ -188,7 +199,11 @@ myWorker.onmessage = async (msg) => { //console.log("Message received from worker", msg.data); if (msg.data.loaded) { - myWorker.postMessage({ wallet, pazzle, pin_code }); + if (unlockWith === "pazzle") { + myWorker.postMessage({ wallet, pazzle, pin_code }); + } else { + myWorker.postMessage({ wallet, mnemonic_words, pin_code }); + } //console.log("postMessage"); } else if (msg.data.success) { //console.log(msg.data); @@ -240,10 +255,10 @@ async function select_order(val, pos) { delete last_one[pos]; - console.debug(last_one, val); ordered.push(val); val.sel = ordered.length; + console.debug("ordered", ordered); selection = selection; if (ordered.length == pazzle_length - 1) { let last = selection[Object.keys(last_one)[0]]; @@ -258,8 +273,24 @@
{#if step == "load"} -
-

How to open your wallet, step by step:

+
+

How to open your wallet:

+

By your 12 word Mnemonic

+
    +
  • + Enter your twelve word mnemonic in the input field. The words must be + separated by spaces. +
  • +
  • Enter the PIN code that you chose when you created your wallet.
  • +
+ +

By your Pazzle

  • For each one of the 9 categories of images, you will be presented with @@ -295,73 +326,141 @@ on the digits.
-
-
- {#if !loaded} - Loading pazzle... - - + {#if !loaded} + Loading wallet... + - - - {:else} -
- + + + {:else} + + {#if for_import} +
+ Do you trust this device?
+
+ Yes, save my wallet on this device +
+

+ If you do, if this device is yours or is used by few trusted + persons of your family or workplace, and you would like to login + again from this device in the future, then you can save your + wallet on this device. To the contrary, if this device is public + and shared by strangers, do not save your wallet here. {#if !tauri_platform}By + selecting this option, you agree to save some cookies on your + browser.{/if}
+

+
+ {/if} + +
+ + + +
+ {/if} +
+
+ {:else if step == "mnemonic"} +
+
+ + +
-
- {/if} -
- {#if for_import} -
- Do you trust this device?
-
- Yes, save my wallet on this deviceConfirm
-

- If you do, if this device is yours or is used by few trusted persons - of your family or workplace, and you would like to login again from - this device in the future, then you can save your wallet on this - device. To the contrary, if this device is public and shared by - strangers, do not save your wallet here. {#if !tauri_platform}By - selecting this option, you agree to save some cookies on your - browser.{/if}
-

+ +
+ +
- {/if} +
{:else if step == "pazzle"}
{#each [0, 1, 2] as row}
- {#each selection.slice(0 + row * 3, 3 + row * 3) || [] as emoji, i} + {#each selection.slice(0 + row * 3, 3 + row * 3) || [] as emoji, i (emoji.sel + "-" + i)} {#if !emoji.sel}
{emoji.sel}
@@ -503,7 +602,7 @@ Enter your PIN code

{#each [0, 1, 2] as row} -
+
{#each shuffle_pin.slice(0 + row * 3, 3 + row * 3) as num}