feat: display mnemonic on wallet creation and support for login

pull/22/head
Laurin Weger 3 months ago
parent 7f24dfecd2
commit 1d475fd9c2
No known key found for this signature in database
GPG Key ID: 9B372BB0B792770F
  1. 37
      nextgraph/src/local_broker.rs
  2. 26
      ng-app/src-tauri/src/lib.rs
  3. 8
      ng-app/src/api.ts
  4. 223
      ng-app/src/lib/Login.svelte
  5. 34
      ng-app/src/routes/WalletCreate.svelte
  6. 15
      ng-app/src/worker.js
  7. 1
      ng-repo/src/errors.rs
  8. 47
      ng-sdk-js/src/lib.rs
  9. 26
      ng-wallet/src/bip39.rs
  10. 7
      ng-wallet/src/lib.rs
  11. 3
      ng-wallet/src/types.rs

@ -40,6 +40,7 @@ use ng_verifier::types::*;
use ng_verifier::verifier::Verifier; use ng_verifier::verifier::Verifier;
use ng_wallet::emojis::encode_pazzle; 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::*}; use ng_wallet::{create_wallet_first_step_v0, create_wallet_second_step_v0, types::*};
#[cfg(not(target_family = "wasm"))] #[cfg(not(target_family = "wasm"))]
@ -1555,6 +1556,27 @@ pub fn wallet_open_with_pazzle(
Ok(opened_wallet) 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<u16>,
pin: [u8; 4],
) -> Result<SensitiveWallet, NgError> {
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. /// 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]. /// 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) 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<String>,
pin: [u8; 4],
) -> Result<SensitiveWallet, NgError> {
let encoded: Vec<u16> = encode_mnemonic(mnemonic)?;
wallet_open_with_mnemonic(wallet, encoded, pin)
}
/// Imports a wallet into the LocalBroker so the user can then access its content. /// 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]. /// the wallet should have been previous opened with [wallet_open_with_pazzle_words].

@ -86,6 +86,30 @@ async fn wallet_open_with_pazzle(
Ok(wallet) Ok(wallet)
} }
#[tauri::command(rename_all = "snake_case")]
async fn wallet_open_with_mnemonic(
wallet: Wallet,
mnemonic: Vec<u16>,
pin: [u8; 4],
_app: tauri::AppHandle,
) -> Result<SensitiveWallet, String> {
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<String>,
pin: [u8; 4],
_app: tauri::AppHandle,
) -> Result<SensitiveWallet, String> {
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")] #[tauri::command(rename_all = "snake_case")]
async fn wallet_get_file(wallet_name: String, app: tauri::AppHandle) -> Result<(), String> { async fn wallet_get_file(wallet_name: String, app: tauri::AppHandle) -> Result<(), String> {
let ser = nextgraph::local_broker::wallet_get_file(&wallet_name) 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_pazzle_opening,
wallet_gen_shuffle_for_pin, wallet_gen_shuffle_for_pin,
wallet_open_with_pazzle, wallet_open_with_pazzle,
wallet_open_with_mnemonic,
wallet_open_with_mnemonic_words,
wallet_was_opened, wallet_was_opened,
wallet_create, wallet_create,
wallet_read_file, wallet_read_file,

@ -16,6 +16,7 @@ const mapping = {
"wallet_gen_shuffle_for_pazzle_opening": ["pazzle_length"], "wallet_gen_shuffle_for_pazzle_opening": ["pazzle_length"],
"wallet_gen_shuffle_for_pin": [], "wallet_gen_shuffle_for_pin": [],
"wallet_open_with_pazzle": ["wallet","pazzle","pin"], "wallet_open_with_pazzle": ["wallet","pazzle","pin"],
"wallet_open_with_mnemonic_words": ["wallet","mnemonic_words","pin"],
"wallet_was_opened": ["opened_wallet"], "wallet_was_opened": ["opened_wallet"],
"wallet_create": ["params"], "wallet_create": ["params"],
"wallet_read_file": ["file"], "wallet_read_file": ["file"],
@ -169,7 +170,7 @@ const handler = {
return false; return false;
} else if (path[0] === "get_local_url") { } else if (path[0] === "get_local_url") {
return false; 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 = {}; let arg:any = {};
args.map((el,ix) => arg[mapping[path[0]][ix]]=el) args.map((el,ix) => arg[mapping[path[0]][ix]]=el)
let img = Array.from(new Uint8Array(arg.wallet.V0.content.security_img)); 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:{}}}; arg.wallet = {V0:{id:arg.wallet.V0.id, sig:arg.wallet.V0.sig, content:{}}};
Object.assign(arg.wallet.V0.content,old_content); Object.assign(arg.wallet.V0.content,old_content);
arg.wallet.V0.content.security_img = img; arg.wallet.V0.content.security_img = img;
return tauri.invoke(path[0],arg) return tauri.invoke(path[0],arg);
} } else {
else {
let arg = {}; let arg = {};
args.map((el,ix) => arg[mapping[path[0]][ix]]=el) args.map((el,ix) => arg[mapping[path[0]][ix]]=el)
return tauri.invoke(path[0],arg) return tauri.invoke(path[0],arg)

@ -25,6 +25,8 @@
Backspace, Backspace,
ArrowPath, ArrowPath,
LockOpen, LockOpen,
Key,
CheckCircle,
} from "svelte-heros-v2"; } from "svelte-heros-v2";
//import Worker from "../worker.js?worker&inline"; //import Worker from "../worker.js?worker&inline";
export let wallet; export let wallet;
@ -63,9 +65,15 @@
loaded = true; loaded = true;
} }
function letsgo() { function start_with_pazzle() {
loaded = false; loaded = false;
step = "pazzle"; step = "pazzle";
unlockWith = "pazzle";
}
function start_with_mnemonic() {
loaded = false;
step = "mnemonic";
unlockWith = "mnemonic";
} }
let emojis2 = []; let emojis2 = [];
@ -92,6 +100,10 @@
let trusted = false; let trusted = false;
let mnemonic = "";
let unlockWith: "pazzle" | "mnemonic" | undefined;
function order() { function order() {
step = "order"; step = "order";
ordered = []; ordered = [];
@ -114,10 +126,8 @@
let cat_idx = shuffle.category_indices[pazzlePage]; let cat_idx = shuffle.category_indices[pazzlePage];
let cat = emojis[emoji_cat[cat_idx]]; let cat = emojis[emoji_cat[cat_idx]];
let idx = shuffle.emoji_indices[pazzlePage][val]; 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 }; selection[pazzlePage] = { cat: cat_idx, index: idx };
console.debug(selection, cat, cat_idx, idx, val);
if (pazzlePage == pazzle_length - 1) { if (pazzlePage == pazzle_length - 1) {
order(); order();
@ -130,22 +140,23 @@
step = "opening"; step = "opening";
let pazzle = []; let pazzle = [];
for (const emoji of ordered) { for (const emoji of ordered) {
pazzle.push((emoji.cat << 4) + emoji.index); pazzle.push((emoji.cat << 4) + emoji.index);
} }
//console.log(pazzle); const mnemonic_words = mnemonic.split(" ");
//console.log(wallet);
// open the wallet // open the wallet
try { try {
if (tauri_platform) { if (tauri_platform) {
let opened_wallet = await ng.wallet_open_with_pazzle( let opened_wallet =
wallet, unlockWith === "pazzle"
pazzle, ? await ng.wallet_open_with_pazzle(wallet, pazzle, pin_code)
pin_code : await ng.wallet_open_with_mnemonic_words(
); wallet,
mnemonic_words,
pin_code
);
// try { // try {
// let client = await ng.wallet_was_opened(opened_wallet); // let client = await ng.wallet_was_opened(opened_wallet);
// opened_wallet.V0.client = client; // opened_wallet.V0.client = client;
@ -180,7 +191,11 @@
myWorker.onmessage = async (msg) => { myWorker.onmessage = async (msg) => {
//console.log("Message received from worker", msg.data); //console.log("Message received from worker", msg.data);
if (msg.data.loaded) { 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"); //console.log("postMessage");
} else if (msg.data.success) { } else if (msg.data.success) {
//console.log(msg.data); //console.log(msg.data);
@ -229,6 +244,7 @@
async function select_order(val) { async function select_order(val) {
ordered.push(val); ordered.push(val);
val.sel = ordered.length; val.sel = ordered.length;
console.debug("ordered", ordered);
selection = selection; selection = selection;
if (ordered.length == pazzle_length - 1) { if (ordered.length == pazzle_length - 1) {
let last = selection.find((emoji) => !emoji.sel); let last = selection.find((emoji) => !emoji.sel);
@ -294,8 +310,15 @@
class:max-w-[600px]={!mobile} class:max-w-[600px]={!mobile}
> >
{#if step == "load"} {#if step == "load"}
<div class=" max-w-xl lg:px-8 mx-auto px-4 mt-10"> <div
<h2 class="pb-5 text-xl">How to open your wallet, step by step:</h2> class="flex flex-col justify-center p-4"
class:min-w-[310px]={mobile}
class:min-w-[500px]={!mobile}
class:max-w-[360px]={mobile}
class:max-w-[600px]={!mobile}
>
<h2 class="pb-5 text-xl self-start">How to open your wallet:</h2>
<h3 class="pb-2 text-lg self-start">By your Pazzle</h3>
<ul class="mb-8 ml-3 space-y-4 text-left list-decimal"> <ul class="mb-8 ml-3 space-y-4 text-left list-decimal">
<li> <li>
For each one of the 9 categories of images, you will be presented with For each one of the 9 categories of images, you will be presented with
@ -331,74 +354,86 @@
on the digits. on the digits.
</li> </li>
</ul> </ul>
</div>
<div class=" max-w-xl lg:px-8 mx-auto px-4 text-primary-700"> <h3 class="pb-2 text-lg self-start">
{#if !loaded} By your 12 word Mnemonic (passphrase)
Loading pazzle... </h3>
<svg <ul class="mb-8 ml-3 space-y-4 text-left list-decimal">
class="animate-spin my-4 h-14 w-14 mx-auto" <li>
xmlns="http://www.w3.org/2000/svg" Enter your twelve word mnemonic in the input field. The words must be
fill="none" separated by spaces.
stroke="currentColor" </li>
viewBox="0 0 24 24" <li>Enter the PIN code that you chose when you created your wallet.</li>
> </ul>
<circle
class="opacity-25" <div class=" max-w-xl lg:px-8 mx-auto px-4 text-primary-700">
cx="12" {#if !loaded}
cy="12" Loading wallet...
r="10" <svg
class="animate-spin my-4 h-14 w-14 mx-auto"
xmlns="http://www.w3.org/2000/svg"
fill="none"
stroke="currentColor" stroke="currentColor"
stroke-width="4" stroke-width="4"
/>
<path
class="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
/>
</svg>
{:else}
<div class="flex justify-center space-x-12 mt-4 mb-4">
<button
on:click={cancel}
class="mt-1 mb-2 bg-red-100 hover:bg-red-100/90 focus:ring-4 focus:ring-primary-100/50 rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-700/55"
><XCircle
tabindex="-1"
class="w-8 h-8 mr-2 -ml-1 transition duration-75 group-hover:text-gray-900 dark:group-hover:text-white"
/>Cancel</button
> >
<button <path
on:click={letsgo} class="opacity-75"
class="mt-1 text-white bg-primary-700 hover:bg-primary-700/90 focus:ring-4 focus:ring-primary-100/50 rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-700/55 mb-2" fill="currentColor"
> d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
<PuzzlePiece
tabindex="-1"
class="w-8 h-8 mr-2 -ml-1 transition duration-75 group-hover:text-gray-900 dark:group-hover:text-white"
/> />
Open my wallet now! </svg>
</button> {:else}
</div> <!-- Save wallet? -->
{/if} {#if for_import}
</div> <div class="max-w-xl lg:px-8 mx-auto px-4 mb-8">
{#if for_import} <span class="text-xl">Do you trust this device? </span> <br />
<div class=" max-w-xl lg:px-8 mx-auto px-4 mb-8"> <div class="flex justify-center items-center my-4">
<span class="text-xl">Do you trust this device? </span> <br /> <Toggle class="" bind:checked={trusted}
<div class="flex justify-center items-center my-4"> >Yes, save my wallet on this device</Toggle
<Toggle class="" bind:checked={trusted} >
>Yes, save my wallet on this device</Toggle </div>
> <p class="text-sm">
</div> If you do, if this device is yours or is used by few trusted
<p class="text-sm"> persons of your family or workplace, and you would like to login
If you do, if this device is yours or is used by few trusted persons again from this device in the future, then you can save your
of your family or workplace, and you would like to login again from wallet on this device. To the contrary, if this device is public
this device in the future, then you can save your wallet on this and shared by strangers, do not save your wallet here. {#if !tauri_platform}By
device. To the contrary, if this device is public and shared by selecting this option, you agree to save some cookies on your
strangers, do not save your wallet here. {#if !tauri_platform}By browser.{/if}<br />
selecting this option, you agree to save some cookies on your </p>
browser.{/if}<br /> </div>
</p> {/if}
<div class="flex flex-col justify-centerspace-x-12 mt-4 mb-4">
<button
on:click={start_with_pazzle}
class="mt-1 text-white bg-primary-700 hover:bg-primary-700/90 focus:ring-4 focus:ring-primary-100/50 rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-700/55 mb-2"
>
<PuzzlePiece
tabindex="-1"
class="w-8 h-8 mr-2 -ml-1 transition duration-75 group-hover:text-gray-900 dark:group-hover:text-white"
/>
Open with Pazzle!
</button>
<a
on:click={start_with_mnemonic}
class="mt-1 text-lg px-5 py-2.5 text-center inline-flex items-center mb-2 underline cursor-pointer"
>
Open with Mnemonic instead
</a>
<button
on:click={cancel}
class="mt-1 mb-2 bg-red-100 hover:bg-red-100/90 focus:ring-4 focus:ring-primary-100/50 rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-700/55"
><XCircle
tabindex="-1"
class="w-8 h-8 mr-2 -ml-1 transition duration-75 group-hover:text-gray-900 dark:group-hover:text-white"
/>Cancel</button
>
</div>
{/if}
</div> </div>
{/if} </div>
<!-- The following have navigation buttons and fixed layout --> <!-- The following steps have navigation buttons and fixed layout -->
{:else if step == "pazzle" || step == "order" || step == "pin" || step == "mnemonic"} {:else if step == "pazzle" || step == "order" || step == "pin" || step == "mnemonic"}
<div <div
class="flex flex-col justify-center h-screen p-4" class="flex flex-col justify-center h-screen p-4"
@ -432,6 +467,33 @@
{/each} {/each}
</div> </div>
{/each} {/each}
{:else if step == "mnemonic"}
<form on:submit|preventDefault={start_pin}>
<label
for="mnemonic-input"
class="block mb-2 text-xl text-gray-900 dark:text-white"
>Your 12 word mnemonic</label
>
<input
type="password"
id="mnemonic-input"
placeholder="Enter your 12 word mnemonic here separated by spaces"
bind:value={mnemonic}
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
/>
<div class="flex">
<button
type="submit"
class="mt-1 ml-auto text-white bg-primary-700 disabled:opacity-65 focus:ring-4 focus:ring-primary-100/50 rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-700/55"
on:click={start_pin}
disabled={mnemonic.split(" ").length !== 12}
><CheckCircle
tabindex="-1"
class="w-8 h-8 mr-2 -ml-1 transition duration-75 group-hover:text-gray-900 dark:group-hover:text-white"
/>Confirm</button
>
</div>
</form>
{:else if step == "order"} {:else if step == "order"}
<p class="max-w-xl md:mx-auto lg:max-w-2xl mb-2"> <p class="max-w-xl md:mx-auto lg:max-w-2xl mb-2">
<span class="text-xl">Click your emojis in the correct order</span> <span class="text-xl">Click your emojis in the correct order</span>
@ -638,11 +700,16 @@
.sel { .sel {
position: absolute; position: absolute;
display: flex;
width: 100%; width: 100%;
top: 45%; height: 100%;
top: 0;
left: 0; left: 0;
font-size: 100px; font-size: 100px;
font-weight: 700; font-weight: 700;
justify-content: center;
align-items: center;
padding-top: 25%;
} }
.sel-emoji { .sel-emoji {

@ -1529,18 +1529,38 @@
with the name<br /><span class="text-black"> with the name<br /><span class="text-black">
{download_name}</span {download_name}</span
><br /> ><br />
Please move it to a safe and durable place.<br /><br /> <span class="font-bold"
>Please move it to a safe and durable place.</span
><br /><br />
{/if} {/if}
Here is your Pazzle:<br /><br /> <!-- Pazzle -->
Here is your Pazzle (The <span class="font-bold">order</span> of
each image is
<span class="font-bold">important</span>):
<br />
<br />
{#each display_pazzle(ready.pazzle) as emoji} {#each display_pazzle(ready.pazzle) as emoji}
<span>{emoji}</span><br /> <span>{emoji}</span><br />
{/each} {/each}
<br />
<br /><br />
<!-- Mnemonic -->
And here is your mnemonic:<br />
<span class="select-none">"</span>{ready.mnemonic_str.join(
" "
)}<span class="select-none">"</span> <br /><br />
<br /><br />
You can use both the pazzle and the mnemonic to unlock your wallet. The
pazzle is easier to remember. The mnemonic is convenient, when you use
a secure password manager which can copy it to the corresponding wallet
unlock field. Copy both on a piece of paper. Use that until you memorized
it, then throw it away.
<br /><br /> <br /><br />
Copy it on a piece of paper. Use that until you memorized it, then throw Now click on "Continue to Login" and select your wallet.<br />It is
it away.<br /> The order of each image is important!<br /> important that you login with this wallet at least once from this {#if tauri_platform}device{:else}browser
Now click on "Continue to Login" and select your wallet.<br /><br tab{/if},<br />
/>It is important that you login with this wallet at least once from
this {#if tauri_platform}device{:else}browser tab{/if},<br />
while connected to the internet, so your personal site can be created while connected to the internet, so your personal site can be created
on your broker.<br /><br /> on your broker.<br /><br />
<a href="/wallet/login" use:link> <a href="/wallet/login" use:link>

@ -7,11 +7,20 @@ onmessage = (e) => {
//console.log("Message received by worker", e.data); //console.log("Message received by worker", e.data);
(async function() { (async function() {
try { try {
let secret_wallet = await ng.wallet_open_with_pazzle( let secret_wallet;
if (e.data.pazzle) {
secret_wallet = await ng.wallet_open_with_pazzle(
e.data.wallet,
e.data.pazzle,
e.data.pin_code
);
} else if (e.data.mnemonic_words) {
secret_wallet = await ng.wallet_open_with_mnemonic_words(
e.data.wallet, e.data.wallet,
e.data.pazzle, e.data.mnemonic_words,
e.data.pin_code e.data.pin_code
); );
}
postMessage({success:secret_wallet}); postMessage({success:secret_wallet});
} catch (e) { } catch (e) {
postMessage({error:e}); postMessage({error:e});

@ -37,6 +37,7 @@ pub enum NgError {
InvalidArgument, InvalidArgument,
PermissionDenied, PermissionDenied,
InvalidPazzle, InvalidPazzle,
InvalidMnemonic,
CommitLoadError(CommitLoadError), CommitLoadError(CommitLoadError),
ObjectParseError(ObjectParseError), ObjectParseError(ObjectParseError),
StorageError(StorageError), StorageError(StorageError),

@ -134,6 +134,53 @@ pub fn wallet_open_with_pazzle(
} }
} }
#[wasm_bindgen]
pub fn wallet_open_with_mnemonic(
wallet: JsValue,
mnemonic: Vec<u16>,
pin: JsValue,
) -> Result<JsValue, JsValue> {
let encrypted_wallet = serde_wasm_bindgen::from_value::<Wallet>(wallet)
.map_err(|_| "Deserialization error of wallet")?;
let pin = serde_wasm_bindgen::from_value::<[u8; 4]>(pin)
.map_err(|_| "Deserialization error of pin")?;
let res = nextgraph::local_broker::wallet_open_with_mnemonic(&encrypted_wallet, mnemonic, pin);
match res {
Ok(r) => Ok(r
.serialize(&serde_wasm_bindgen::Serializer::new().serialize_maps_as_objects(true))
.unwrap()),
Err(e) => Err(e.to_string().into()),
}
}
#[wasm_bindgen]
pub fn wallet_open_with_mnemonic_words(
wallet: JsValue,
mnemonic_words: Array,
pin: JsValue,
) -> Result<JsValue, JsValue> {
let encrypted_wallet = serde_wasm_bindgen::from_value::<Wallet>(wallet)
.map_err(|_| "Deserialization error of wallet")?;
let pin = serde_wasm_bindgen::from_value::<[u8; 4]>(pin)
.map_err(|_| "Deserialization error of pin")?;
let mnemonic_vec: Vec<String> = mnemonic_words
.iter()
.map(|word| word.as_string().unwrap())
.collect();
let res = nextgraph::local_broker::wallet_open_with_mnemonic_words(
&encrypted_wallet,
&mnemonic_vec,
pin,
);
match res {
Ok(r) => Ok(r
.serialize(&serde_wasm_bindgen::Serializer::new().serialize_maps_as_objects(true))
.unwrap()),
Err(e) => Err(e.to_string().into()),
}
}
#[wasm_bindgen] #[wasm_bindgen]
pub fn wallet_update(wallet_id: JsValue, operations: JsValue) -> Result<JsValue, JsValue> { pub fn wallet_update(wallet_id: JsValue, operations: JsValue) -> Result<JsValue, JsValue> {
let _wallet = serde_wasm_bindgen::from_value::<WalletId>(wallet_id) let _wallet = serde_wasm_bindgen::from_value::<WalletId>(wallet_id)

@ -1,3 +1,6 @@
use ng_repo::errors::NgError;
use std::collections::HashMap;
#[allow(non_upper_case_globals)] #[allow(non_upper_case_globals)]
pub const bip39_wordlist: [&str; 2048] = [ pub const bip39_wordlist: [&str; 2048] = [
"abandon", "ability", "able", "about", "above", "absent", "absorb", "abstract", "absurd", "abandon", "ability", "able", "about", "above", "absent", "absorb", "abstract", "absurd",
@ -212,3 +215,26 @@ pub const bip39_wordlist: [&str; 2048] = [
"write", "wrong", "yard", "year", "yellow", "you", "young", "youth", "zebra", "zero", "zone", "write", "wrong", "yard", "year", "yellow", "you", "young", "youth", "zebra", "zero", "zone",
"zoo", "zoo",
]; ];
lazy_static! {
pub static ref BIP39_WORD_MAP: HashMap<String, u16> = {
let mut m = HashMap::new();
for (i, word) in bip39_wordlist.iter().enumerate() {
m.insert(word.to_string(), i as u16);
}
m
};
}
/// Taking a list of bip39 words, returns a list of u16 codes
pub fn encode_mnemonic(words: &Vec<String>) -> Result<Vec<u16>, NgError> {
let mut res = vec![];
for word in words {
res.push(
*BIP39_WORD_MAP
.get(word.as_str())
.ok_or(NgError::InvalidMnemonic)?,
);
}
Ok(res)
}

@ -362,7 +362,7 @@ pub fn open_wallet_with_pazzle(
} }
pub fn open_wallet_with_mnemonic( pub fn open_wallet_with_mnemonic(
wallet: Wallet, wallet: &Wallet,
mut mnemonic: [u16; 12], mut mnemonic: [u16; 12],
mut pin: [u8; 4], mut pin: [u8; 4],
) -> Result<SensitiveWallet, NgWalletError> { ) -> Result<SensitiveWallet, NgWalletError> {
@ -388,7 +388,7 @@ pub fn open_wallet_with_mnemonic(
mnemonic_key.zeroize(); mnemonic_key.zeroize();
Ok(SensitiveWallet::V0(dec_encrypted_block( Ok(SensitiveWallet::V0(dec_encrypted_block(
v0.content.encrypted, v0.content.encrypted.clone(),
master_key, master_key,
v0.content.peer_id, v0.content.peer_id,
v0.content.nonce, v0.content.nonce,
@ -786,6 +786,7 @@ pub async fn create_wallet_second_step_v0(
wallet_file, wallet_file,
pazzle, pazzle,
mnemonic: mnemonic.clone(), mnemonic: mnemonic.clone(),
mnemonic_str: display_mnemonic(&mnemonic),
wallet_name: params.wallet_name.clone(), wallet_name: params.wallet_name.clone(),
client: params.client.clone(), client: params.client.clone(),
user, user,
@ -893,7 +894,7 @@ mod test {
let _opening_mnemonic = Instant::now(); let _opening_mnemonic = Instant::now();
let _w = open_wallet_with_mnemonic(Wallet::V0(v0.clone()), res.mnemonic, pin.clone()) let _w = open_wallet_with_mnemonic(&Wallet::V0(v0.clone()), res.mnemonic, pin.clone())
.expect("open with mnemonic"); .expect("open with mnemonic");
//log_debug!("encrypted part {:?}", w); //log_debug!("encrypted part {:?}", w);

@ -1303,6 +1303,8 @@ pub struct CreateWalletResultV0 {
/// randomly generated mnemonic. It is an alternate way to open the wallet. /// randomly generated mnemonic. It is an alternate way to open the wallet.
/// A BIP39 list of 12 words. We argue that the Pazzle is easier to remember than this. /// A BIP39 list of 12 words. We argue that the Pazzle is easier to remember than this.
pub mnemonic: [u16; 12], pub mnemonic: [u16; 12],
/// The words of the mnemonic, in a human readable form.
pub mnemonic_str: Vec<String>,
#[zeroize(skip)] #[zeroize(skip)]
/// a string identifying uniquely the wallet /// a string identifying uniquely the wallet
pub wallet_name: String, pub wallet_name: String,
@ -1368,6 +1370,7 @@ pub enum NgWalletError {
InvalidPin, InvalidPin,
InvalidPazzle, InvalidPazzle,
InvalidPazzleLength, InvalidPazzleLength,
InvalidMnemonic,
InvalidSecurityImage, InvalidSecurityImage,
InvalidSecurityText, InvalidSecurityText,
SubmissionError, SubmissionError,

Loading…
Cancel
Save