switching to password based login

refactor
Niko PLP 20 hours ago
parent 26f6f3dc2a
commit 158169af27
  1. 2
      app/ui-common/package.json
  2. 4
      app/ui-common/src/api.ts
  3. 22
      app/ui-common/src/lib/Login.svelte
  4. 10
      app/ui-common/src/lib/components/PasswordInput.svelte
  5. 18
      app/ui-common/src/locales/en.json
  6. 1716
      app/ui-common/src/routes/WalletCreate.svelte
  7. 149
      app/ui-common/src/routes/WalletLogin.svelte
  8. 2
      engine/broker/auth/package.json
  9. 326
      engine/wallet/src/lib.rs
  10. 59
      engine/wallet/src/types.rs
  11. 2
      infra/ngaccount/web/src/routes/Create.svelte
  12. 2
      infra/ngnet/bootstrap/package.json
  13. 2
      infra/ngnet/redir/package.json
  14. 24
      pnpm-lock.yaml
  15. 22
      sdk/js/lib-wasm/src/lib.rs
  16. 37
      sdk/rust/src/local_broker.rs

@ -55,7 +55,7 @@
"shx": "^0.3.4", "shx": "^0.3.4",
"svelte": "^3.54.0", "svelte": "^3.54.0",
"svelte-check": "^3.0.0", "svelte-check": "^3.0.0",
"svelte-heros-v2": "^0.10.12", "svelte-heros-v2": "^1.3.0",
"svelte-preprocess": "^5.0.3", "svelte-preprocess": "^5.0.3",
"svelte-time": "^0.8.0", "svelte-time": "^0.8.0",
"tailwindcss": "^3.3.1", "tailwindcss": "^3.3.1",

@ -25,12 +25,12 @@ export default api;
export const NG_EU_BSP = "https://nextgraph.eu"; export const NG_EU_BSP = "https://nextgraph.eu";
export const NG_EU_BSP_REGISTER = import.meta.env.PROD export const NG_EU_BSP_REGISTER = import.meta.env.PROD
? "https://account.nextgraph.eu/#/create" ? import.meta.env.NG_ENV_ALT ? "https://pnm.allelo.eco" : "https://account.nextgraph.eu/#/create"
: "http://account-dev.nextgraph.eu:5173/#/create"; : "http://account-dev.nextgraph.eu:5173/#/create";
export const NG_ONE_BSP = "https://nextgraph.one"; export const NG_ONE_BSP = "https://nextgraph.one";
export const NG_ONE_BSP_REGISTER = import.meta.env.PROD export const NG_ONE_BSP_REGISTER = import.meta.env.PROD
? "https://account.nextgraph.one/#/create" ? import.meta.env.NG_ENV_ALT ? "https://account.allelo.eco/#/create" : "https://account.nextgraph.one/#/create"
: "http://account-dev.nextgraph.one:5173/#/create"; : "http://account-dev.nextgraph.one:5173/#/create";
export const APP_ACCOUNT_REGISTERED_SUFFIX = "/#/user/registered"; export const APP_ACCOUNT_REGISTERED_SUFFIX = "/#/user/registered";

@ -54,7 +54,6 @@
load_svg(); load_svg();
//console.log(wallet); //console.log(wallet);
await init(); await init();
}); });
async function init() { async function init() {
@ -256,7 +255,11 @@
} }
} catch (e) { } catch (e) {
console.error(e); console.error(e);
if (e.message && e.message.includes("constructor") || (typeof e === "string" && e.includes("constructor") )) e = "BrowserTooOld"; if (
(e.message && e.message.includes("constructor")) ||
(typeof e === "string" && e.includes("constructor"))
)
e = "BrowserTooOld";
error = e; error = e;
step = "end"; step = "end";
dispatch("error", { error: e }); dispatch("error", { error: e });
@ -272,7 +275,10 @@
async function on_pin_key(val) { async function on_pin_key(val) {
pin_code = [...pin_code, val]; pin_code = [...pin_code, val];
if (pin_code.length == 4) { if (pin_code.length == 4) {
setTimeout(()=>window.document.getElementById("confirm_pin_btn").focus(),50); setTimeout(
() => window.document.getElementById("confirm_pin_btn").focus(),
50
);
} }
} }
@ -590,7 +596,10 @@
class:h-[160px]={!mobile} class:h-[160px]={!mobile}
class:h-[93px]={mobile} class:h-[93px]={mobile}
class:text-8xl={!mobile} class:text-8xl={!mobile}
on:click={async () => {window.document.activeElement.blur(); await on_pin_key(num)}} on:click={async () => {
window.document.activeElement.blur();
await on_pin_key(num);
}}
disabled={pin_code.length >= 4} disabled={pin_code.length >= 4}
> >
<span>{num}</span> <span>{num}</span>
@ -606,7 +615,10 @@
class:h-[160px]={!mobile} class:h-[160px]={!mobile}
class:h-[93px]={mobile} class:h-[93px]={mobile}
class:text-8xl={!mobile} class:text-8xl={!mobile}
on:click={async () => {window.document.activeElement.blur();await on_pin_key(shuffle_pin[9])}} on:click={async () => {
window.document.activeElement.blur();
await on_pin_key(shuffle_pin[9]);
}}
disabled={pin_code.length >= 4} disabled={pin_code.length >= 4}
> >
<span>{shuffle_pin[9]}</span> <span>{shuffle_pin[9]}</span>

@ -18,8 +18,8 @@
export let auto_complete: string | undefined = undefined; export let auto_complete: string | undefined = undefined;
import { createEventDispatcher } from "svelte"; import { createEventDispatcher } from "svelte";
export let show: boolean = false; export let show: boolean = false;
export let autofocus = false;
let input; export let input = undefined;
let type: "password" | "text" = "password"; let type: "password" | "text" = "password";
$: type = show ? "text" : "password"; $: type = show ? "text" : "password";
@ -44,7 +44,7 @@
if (e.key == "Enter" || e.keyCode == 13) { if (e.key == "Enter" || e.keyCode == 13) {
dispatch("enter"); dispatch("enter");
} }
} };
</script> </script>
<div class="relative"> <div class="relative">
@ -55,7 +55,7 @@
{placeholder} {placeholder}
{id} {id}
{type} {type}
autofocus {autofocus}
on:input={handleInput} on:input={handleInput}
class={`${className} pr-12 text-md block`} class={`${className} pr-12 text-md block`}
autocomplete={auto_complete} autocomplete={auto_complete}
@ -63,7 +63,7 @@
/> />
<div <div
class={`${classNameToggle} absolute inset-y-0 pr-3 flex items-center text-sm leading-5`} class={`${classNameToggle} clickable absolute inset-y-0 pr-3 flex items-center text-sm leading-5`}
> >
<svg <svg
fill="none" fill="none"

@ -372,8 +372,10 @@
"open_with_pazzle": "Open With Pazzle instead", "open_with_pazzle": "Open With Pazzle instead",
"login_cancel": "Cancel Login", "login_cancel": "Cancel Login",
"open_with_mnemonic": "Open with Mnemonic", "open_with_mnemonic": "Open with Mnemonic",
"open": "Open my wallet",
"enter_mnemonic": "Enter your 12 words mnemonic", "enter_mnemonic": "Enter your 12 words mnemonic",
"mnemonic_placeholder": "12 words separated by spaces", "mnemonic_placeholder": "12 words separated by spaces",
"enter_password": "Enter your password",
"select_emoji": "Select your image for category:<br />{category}", "select_emoji": "Select your image for category:<br />{category}",
"order_emojis": "Select each image in the correct order", "order_emojis": "Select each image in the correct order",
"enter_pin": "Enter your PIN code", "enter_pin": "Enter your PIN code",
@ -439,7 +441,13 @@
"self_hosted_broker": "Self-hosted broker", "self_hosted_broker": "Self-hosted broker",
"ng_box": "NG Box (owned or invited)", "ng_box": "NG Box (owned or invited)",
"install_app": "Install the app", "install_app": "Install the app",
"registration_success": "You have been successfully registered to {broker}", "registration_success": "You are creating an account at {broker}",
"choose_username": {
"title": "Now choose your username and password",
"warning": "Please note that we do not offer password recovery mechanism for now.<br/> We won't be able to help you if you forget your password."
},
"type_username_placeholder": "Type in your username",
"type_password_placeholder": "Type in your password",
"choose_pin": { "choose_pin": {
"title": "Let's start creating your wallet by choosing a PIN code", "title": "Let's start creating your wallet by choosing a PIN code",
"description": "We recommend you to choose a PIN code that you already know very well. <br />We at NextGraph will never see your PIN.", "description": "We recommend you to choose a PIN code that you already know very well. <br />We at NextGraph will never see your PIN.",
@ -591,7 +599,7 @@
"InvalidSignature": "The signature is invalid.", "InvalidSignature": "The signature is invalid.",
"IncompleteSignature": "The signature is incomplete.", "IncompleteSignature": "The signature is incomplete.",
"SerializationError": "The data could not be serialized.", "SerializationError": "The data could not be serialized.",
"EncryptionError": "Your wallet could not be opened. You probably did a mistake.", "EncryptionError": "Your wallet could not be opened. You probably did a mistake when entering your credentials.",
"DecryptionError": "Error with decryption.", "DecryptionError": "Error with decryption.",
"InvalidValue": "The value is invalid.", "InvalidValue": "The value is invalid.",
"ConnectionNotFound": "The connection was not found.", "ConnectionNotFound": "The connection was not found.",
@ -659,9 +667,13 @@
"WsError": "WebSocket error", "WsError": "WebSocket error",
"cannot_load_this_file": "Cannot load this file", "cannot_load_this_file": "Cannot load this file",
"graph_not_found": "Graph not found", "graph_not_found": "Graph not found",
"SocialQueryAlreadyStarted": "Social Query already started",
"ContactAlreadyExists": "Contact already added to your account",
"ContactNotFound": "You don't have any contact. We cannot start the Social Query",
"InvalidProfile": "Your profile is incomplete. You should add a name before you can share your profile with others",
"no_wasm_on_old_safari": "Your Safari browser is too old (version before 14.1). As a result we cannot load Automerge, needed for this document. Please upgrade your macOS or iOS system", "no_wasm_on_old_safari": "Your Safari browser is too old (version before 14.1). As a result we cannot load Automerge, needed for this document. Please upgrade your macOS or iOS system",
"BrowserTooOld": "Your browser is too old. Please upgrade it, use another browser, or install our native app. If you are using jshelter or another javascript protection mechanism, please deactivate it as we need access to the WebWorker facility of your browser.", "BrowserTooOld": "Your browser is too old. Please upgrade it, use another browser, or install our native app. If you are using jshelter or another javascript protection mechanism, please deactivate it as we need access to the WebWorker facility of your browser.",
"NoLocalStorage": "You have disabled local storage in your browser. Please allow the current website (and https://nextgraph.net website) to store data in this browser as otherwise we cannot proceed with Wallet creation. After allowing storage, please refresh the current page." "NoLocalStorage": "You have disabled local storage in your browser. Please allow the current website (and also https://nextgraph.net website) to store data in this browser as otherwise we cannot proceed with Wallet creation. After allowing storage, please refresh the current page."
}, },
"auth":{ "auth":{
"select_broker":"<b>{origin}</b><br/>wants to access your wallet<br/><br/>Please select your broker in the list below:", "select_broker":"<b>{origin}</b><br/>wants to access your wallet<br/><br/>Please select your broker in the list below:",

File diff suppressed because it is too large Load Diff

@ -35,11 +35,18 @@
display_error, display_error,
wallet_from_import, wallet_from_import,
redirect_after_login, redirect_after_login,
redirect_if_wallet_is redirect_if_wallet_is,
} from "../store"; } from "../store";
import { CheckBadge, ExclamationTriangle, QrCode, Cloud } from "svelte-heros-v2"; import {
CheckBadge,
ExclamationTriangle,
QrCode,
Cloud,
ArrowRightEndOnRectangle,
} from "svelte-heros-v2";
let tauri_platform = import.meta.env.TAURI_PLATFORM; let tauri_platform = import.meta.env.TAURI_PLATFORM;
let mobile = tauri_platform == "android" || tauri_platform == "ios";
let wallet; let wallet;
let selected; let selected;
@ -108,21 +115,23 @@
wallet = $wallet_from_import; wallet = $wallet_from_import;
importing = true; importing = true;
} }
}); });
async function loggedin() { async function loggedin() {
step = "loggedin"; step = "loggedin";
if ($redirect_after_login) { if ($redirect_after_login) {
if (!$redirect_if_wallet_is || $redirect_if_wallet_is == $active_wallet?.id) { if (
let redir=$redirect_after_login; !$redirect_if_wallet_is ||
$redirect_after_login=undefined; $redirect_if_wallet_is == $active_wallet?.id
$redirect_if_wallet_is=undefined; ) {
push("#"+redir); let redir = $redirect_after_login;
$redirect_after_login = undefined;
$redirect_if_wallet_is = undefined;
push("#" + redir);
} else { } else {
$redirect_after_login=undefined; $redirect_after_login = undefined;
$redirect_if_wallet_is=undefined; $redirect_if_wallet_is = undefined;
push("#/"); push("#/");
} }
} else { } else {
@ -150,10 +159,24 @@
try { try {
if (importing) { if (importing) {
step = "loggedin"; step = "loggedin";
$redirect_after_login=undefined; $redirect_after_login = undefined;
$redirect_if_wallet_is=undefined; $redirect_if_wallet_is = undefined;
let in_memory = !event.detail.trusted; let in_memory = !event.detail.trusted;
//console.log("IMPORTING", in_memory, event.detail.wallet, wallet); //console.log("IMPORTING", in_memory, event.detail.wallet, wallet);
// TODO : register bootstrap when importing
// if (!in_memory && !tauri_platform) {
// let bootstrap_iframe_msgs =
// await ng.get_bootstrap_iframe_msgs_for_brokers(
// event.detail.wallet.V0.brokers
// );
// let res = await register_bootstrap(bootstrap_iframe_msgs);
// if (res !== true) {
// throw new Error(
// "We could not save your bootstrap information at nextgraph.net. This is needed for links and third-party webapps to work properly. so we are stopping here. Reason: " +
// res
// );
// }
// }
let client = await ng.wallet_import( let client = await ng.wallet_import(
wallet, wallet,
event.detail.wallet, event.detail.wallet,
@ -190,11 +213,13 @@
event.detail.wallet.V0.client = client; event.detail.wallet.V0.client = client;
} }
} catch (e) { } catch (e) {
if (importing) {wallet = undefined;} if (importing) {
importing = false; wallet = undefined;
error = e; }
step = "open"; importing = false;
return; error = e;
step = "open";
return;
} }
//await tick(); //await tick();
active_wallet.set(event.detail); active_wallet.set(event.detail);
@ -352,16 +377,38 @@
select(wallet_entry[0]); select(wallet_entry[0]);
}} }}
> >
<span class="securitytxt" {#if wallet_entry[1].wallet.V0.content.password}
>{wallet_entry[1].wallet.V0.content.security_txt} <div class="pt-5">
</span> <ArrowRightEndOnRectangle
<img class="w-16 h-16"
alt={wallet_entry[1].wallet.V0.content.security_txt} style="display:inline;"
class="securityimg" />
src={convert_img_to_url( <div>
wallet_entry[1].wallet.V0.content.security_img {#if mobile}Tap{:else}Click{/if} here to login with your wallet
)} </div>
/> </div>
<div class="p-5">
<button
tabindex="-1"
style="overflow-wrap: anywhere;"
class="mt-1 text-white bg-primary-700 hover:bg-primary-700/90 focus:ring-4 focus:ring-primary-100/50 font-medium rounded-lg text-lg px-5 py-1.5 text-center inline-flex items-center dark:focus:ring-primary-700/55 mb-2"
>
{wallet_entry[1].wallet.V0.content.security_txt}
</button>
</div>
{:else}
<span class="securitytxt"
>{wallet_entry[1].wallet.V0.content.security_txt}
</span>
<img
alt={wallet_entry[1].wallet.V0.content.security_txt}
class="securityimg"
src={convert_img_to_url(
wallet_entry[1].wallet.V0.content.security_img
)}
/>
{/if}
</div> </div>
{/each} {/each}
<div class="wallet-box"> <div class="wallet-box">
@ -373,7 +420,7 @@
class:mt-2.5={!without_create} class:mt-2.5={!without_create}
class="text-primary-700 bg-primary-100 hover:bg-primary-100/90 focus:ring-4 focus:ring-primary-700/50 font-medium rounded-lg text-lg px-5 py-1.5 text-center inline-flex items-center justify-center dark:focus:ring-primary-100/55 mb-2" class="text-primary-700 bg-primary-100 hover:bg-primary-100/90 focus:ring-4 focus:ring-primary-700/50 font-medium rounded-lg text-lg px-5 py-1.5 text-center inline-flex items-center justify-center dark:focus:ring-primary-100/55 mb-2"
> >
<Cloud class="w-8 h-8 mr-2 -ml-1" tabindex="-1"/> <Cloud class="w-8 h-8 mr-2 -ml-1" tabindex="-1" />
{$t("pages.wallet_login.with_username")} {$t("pages.wallet_login.with_username")}
</button> </button>
</a> </a>
@ -412,7 +459,7 @@
tabindex="-1" tabindex="-1"
class="mt-1 text-primary-700 bg-primary-100 hover:bg-primary-100/90 focus:ring-4 focus:ring-primary-700/50 font-medium rounded-lg text-lg px-5 py-1.5 text-center inline-flex items-center justify-center dark:focus:ring-primary-100/55 mb-2" class="mt-1 text-primary-700 bg-primary-100 hover:bg-primary-100/90 focus:ring-4 focus:ring-primary-700/50 font-medium rounded-lg text-lg px-5 py-1.5 text-center inline-flex items-center justify-center dark:focus:ring-primary-100/55 mb-2"
> >
<QrCode class="w-8 h-8 mr-2 -ml-1" tabindex="-1"/> <QrCode class="w-8 h-8 mr-2 -ml-1" tabindex="-1" />
{$t("pages.wallet_login.import_qr")} {$t("pages.wallet_login.import_qr")}
</button> </button>
</a> </a>
@ -441,29 +488,29 @@
</button> </button>
</a> </a>
{#if !without_create} {#if !without_create}
<a href="/wallet/create" use:link> <a href="/wallet/create" use:link>
<button <button
tabindex="-1" tabindex="-1"
class="mt-1 text-white bg-primary-700 hover:bg-primary-700/90 focus:ring-4 focus:ring-primary-100/50 font-medium rounded-lg text-lg px-5 py-1.5 text-center inline-flex items-center dark:focus:ring-primary-700/55 mb-2" class="mt-1 text-white bg-primary-700 hover:bg-primary-700/90 focus:ring-4 focus:ring-primary-100/50 font-medium rounded-lg text-lg px-5 py-1.5 text-center inline-flex items-center dark:focus:ring-primary-700/55 mb-2"
>
<svg
class="w-8 h-8 mr-2 -ml-1"
fill="none"
stroke="currentColor"
stroke-width="1.5"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
> >
<path <svg
stroke-linecap="round" class="w-8 h-8 mr-2 -ml-1"
stroke-linejoin="round" fill="none"
d="M19 7.5v3m0 0v3m0-3h3m-3 0h-3m-2.25-4.125a3.375 3.375 0 11-6.75 0 3.375 3.375 0 016.75 0zM4 19.235v-.11a6.375 6.375 0 0112.75 0v.109A12.318 12.318 0 0110.374 21c-2.331 0-4.512-.645-6.374-1.766z" stroke="currentColor"
/> stroke-width="1.5"
</svg> viewBox="0 0 24 24"
{$t("pages.wallet_login.new_wallet")} xmlns="http://www.w3.org/2000/svg"
</button> aria-hidden="true"
</a> >
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M19 7.5v3m0 0v3m0-3h3m-3 0h-3m-2.25-4.125a3.375 3.375 0 11-6.75 0 3.375 3.375 0 016.75 0zM4 19.235v-.11a6.375 6.375 0 0112.75 0v.109A12.318 12.318 0 0110.374 21c-2.331 0-4.512-.645-6.374-1.766z"
/>
</svg>
{$t("pages.wallet_login.new_wallet")}
</button>
</a>
{/if} {/if}
</div> </div>
</div> </div>

@ -28,7 +28,7 @@
"vite": "^4.3.9", "vite": "^4.3.9",
"postcss": "^8.4.23", "postcss": "^8.4.23",
"postcss-load-config": "^4.0.1", "postcss-load-config": "^4.0.1",
"svelte-heros-v2": "^0.10.12", "svelte-heros-v2": "^1.3.0",
"svelte-preprocess": "^5.0.3", "svelte-preprocess": "^5.0.3",
"tailwindcss": "^3.3.1", "tailwindcss": "^3.3.1",
"autoprefixer": "^10.4.14", "autoprefixer": "^10.4.14",

@ -332,13 +332,18 @@ pub fn open_wallet_with_pazzle(
match wallet { match wallet {
Wallet::V0(v0) => { Wallet::V0(v0) => {
let login = v0
.content
.pazzle
.as_ref()
.ok_or(NgWalletError::LoginMethodNotSupported)?;
pazzle.extend_from_slice(&pin); pazzle.extend_from_slice(&pin);
let mut pazzle_key = derive_key_from_pass(pazzle, v0.content.salt_pazzle, v0.id); let mut pazzle_key = derive_key_from_pass(pazzle, login.salt, v0.id);
// pazzle is zeroized in derive_key_from_pass // pazzle is zeroized in derive_key_from_pass
pin.zeroize(); pin.zeroize();
let master_key = dec_master_key( let master_key = dec_master_key(
v0.content.enc_master_key_pazzle, login.enc_master_key,
&pazzle_key, &pazzle_key,
v0.content.master_nonce, v0.content.master_nonce,
v0.id, v0.id,
@ -374,16 +379,21 @@ pub fn open_wallet_with_mnemonic(
match wallet { match wallet {
Wallet::V0(v0) => { Wallet::V0(v0) => {
let login = v0
.content
.mnemonic
.as_ref()
.ok_or(NgWalletError::LoginMethodNotSupported)?;
let mut mnemonic_key = derive_key_from_pass( let mut mnemonic_key = derive_key_from_pass(
[transmute_to_bytes(&mnemonic), &pin].concat(), [transmute_to_bytes(&mnemonic), &pin].concat(),
v0.content.salt_mnemonic, login.salt,
v0.id, v0.id,
); );
mnemonic.zeroize(); mnemonic.zeroize();
pin.zeroize(); pin.zeroize();
let master_key = dec_master_key( let master_key = dec_master_key(
v0.content.enc_master_key_mnemonic, login.enc_master_key,
&mnemonic_key, &mnemonic_key,
v0.content.master_nonce, v0.content.master_nonce,
v0.id, v0.id,
@ -403,6 +413,48 @@ pub fn open_wallet_with_mnemonic(
} }
} }
pub fn open_wallet_with_password(
wallet: &Wallet,
mut pass: String,
) -> Result<SensitiveWallet, NgWalletError> {
verify(&wallet.content_as_bytes(), wallet.sig(), wallet.id())
.map_err(|_e| NgWalletError::InvalidSignature)?;
let mut password = pass.trim().to_string();
pass.zeroize();
match wallet {
Wallet::V0(v0) => {
let login = v0
.content
.password
.as_ref()
.ok_or(NgWalletError::LoginMethodNotSupported)?;
let mut password_key =
derive_key_from_pass(password.as_bytes().to_vec(), login.salt, v0.id);
password.zeroize();
let master_key = dec_master_key(
login.enc_master_key,
&password_key,
v0.content.master_nonce,
v0.id,
)?;
password_key.zeroize();
Ok(SensitiveWallet::V0(dec_encrypted_block(
v0.content.encrypted.clone(),
master_key,
v0.content.peer_id,
v0.content.nonce,
v0.content.timestamp,
v0.id,
)?))
}
_ => unimplemented!(),
}
}
pub fn display_mnemonic(mnemonic: &[u16; 12]) -> Vec<String> { pub fn display_mnemonic(mnemonic: &[u16; 12]) -> Vec<String> {
let res: Vec<String> = mnemonic let res: Vec<String> = mnemonic
.into_iter() .into_iter()
@ -446,7 +498,7 @@ pub fn gen_shuffle_for_pin() -> Vec<u8> {
pub fn create_wallet_first_step_v0( pub fn create_wallet_first_step_v0(
params: CreateWalletV0, params: CreateWalletV0,
) -> Result<CreateWalletIntermediaryV0, NgWalletError> { ) -> Result<CreateWalletIntermediaryV0, NgWalletError> {
// pazzle_length can only be 9, 12, or 15 // pazzle_length can only be 0, 9, 12, or 15
if params.pazzle_length != 9 if params.pazzle_length != 9
//&& params.pazzle_length != 12 //&& params.pazzle_length != 12
//&& params.pazzle_length != 15 //&& params.pazzle_length != 15
@ -462,68 +514,74 @@ pub fn create_wallet_first_step_v0(
// return Err(NgWalletError::InvalidPin); // return Err(NgWalletError::InvalidPin);
// } // }
// each digit shouldnt be greater than 9 if params.pazzle_length == 0 && !params.mnemonic && params.password.is_none() {
if params.pin[0] > 9 || params.pin[1] > 9 || params.pin[2] > 9 || params.pin[3] > 9 { return Err(NgWalletError::NoLoginMethod);
return Err(NgWalletError::InvalidPin);
} }
// check for same digit doesnt appear 3 times if let Some(pin) = params.pin {
if (params.pin[0] == params.pin[1] && params.pin[0] == params.pin[2]) // each digit shouldnt be greater than 9
|| (params.pin[0] == params.pin[1] && params.pin[0] == params.pin[3]) if pin[0] > 9 || pin[1] > 9 || pin[2] > 9 || pin[3] > 9 {
|| (params.pin[0] == params.pin[2] && params.pin[0] == params.pin[3]) return Err(NgWalletError::InvalidPin);
|| (params.pin[1] == params.pin[2] && params.pin[1] == params.pin[3]) }
{
return Err(NgWalletError::InvalidPin);
}
// check for ascending series // check for same digit doesnt appear 3 times
if params.pin[1] == params.pin[0] + 1 if (pin[0] == pin[1] && pin[0] == pin[2])
&& params.pin[2] == params.pin[1] + 1 || (pin[0] == pin[1] && pin[0] == pin[3])
&& params.pin[3] == params.pin[2] + 1 || (pin[0] == pin[2] && pin[0] == pin[3])
{ || (pin[1] == pin[2] && pin[1] == pin[3])
return Err(NgWalletError::InvalidPin); {
} return Err(NgWalletError::InvalidPin);
}
// check for descending series // check for ascending series
if params.pin[3] >= 3 if pin[1] == pin[0] + 1 && pin[2] == pin[1] + 1 && pin[3] == pin[2] + 1 {
&& params.pin[2] == params.pin[3] - 1 return Err(NgWalletError::InvalidPin);
&& params.pin[1] == params.pin[2] - 1 }
&& params.pin[0] == params.pin[1] - 1
{ // check for descending series
return Err(NgWalletError::InvalidPin); if pin[3] >= 3 && pin[2] == pin[3] - 1 && pin[1] == pin[2] - 1 && pin[0] == pin[1] - 1 {
return Err(NgWalletError::InvalidPin);
}
} else if params.pazzle_length > 0 || params.mnemonic {
return Err(NgWalletError::MnemonicOrPazzleNeedAPin);
} }
// check validity of security text // check validity of security text
let words: Vec<_> = params.security_txt.split_whitespace().collect(); let words: Vec<_> = params.security_txt.split_whitespace().collect();
let new_string = words.join(" "); let new_string = words.join(" ");
let count = new_string.chars().count(); let count = new_string.chars().count();
if count < 10 || count > 100 { if count < 2 || count > 100 {
return Err(NgWalletError::InvalidSecurityText); return Err(NgWalletError::InvalidSecurityText);
} }
// check validity of image // check validity of image
let decoded_img = ImageReader::new(Cursor::new(&params.security_img)) let img_vec = if let Some(security_img) = &params.security_img {
.with_guessed_format() let decoded_img = ImageReader::new(Cursor::new(security_img))
.map_err(|_e| NgWalletError::InvalidSecurityImage)? .with_guessed_format()
.decode() .map_err(|_e| NgWalletError::InvalidSecurityImage)?
.map_err(|_e| NgWalletError::InvalidSecurityImage)?; .decode()
.map_err(|_e| NgWalletError::InvalidSecurityImage)?;
if decoded_img.height() < 150 || decoded_img.width() < 150 {
return Err(NgWalletError::InvalidSecurityImage); if decoded_img.height() < 150 || decoded_img.width() < 150 {
} return Err(NgWalletError::InvalidSecurityImage);
}
let resized_img = if decoded_img.height() == 400 && decoded_img.width() == 400 { let resized_img = if decoded_img.height() == 400 && decoded_img.width() == 400 {
decoded_img decoded_img
} else {
decoded_img.resize_to_fill(400, 400, FilterType::Triangle)
};
let buffer: Vec<u8> = Vec::with_capacity(100000);
let mut cursor = Cursor::new(buffer);
resized_img
.write_to(&mut cursor, ImageOutputFormat::Jpeg(72))
.map_err(|_e| NgWalletError::InvalidSecurityImage)?;
Some(cursor.into_inner())
} else { } else {
decoded_img.resize_to_fill(400, 400, FilterType::Triangle) None
}; };
let buffer: Vec<u8> = Vec::with_capacity(100000);
let mut cursor = Cursor::new(buffer);
resized_img
.write_to(&mut cursor, ImageOutputFormat::Jpeg(72))
.map_err(|_e| NgWalletError::InvalidSecurityImage)?;
// creating the wallet keys // creating the wallet keys
let (wallet_privkey, wallet_id) = generate_keypair(); let (wallet_privkey, wallet_id) = generate_keypair();
@ -541,10 +599,12 @@ pub fn create_wallet_first_step_v0(
client, client,
user_privkey, user_privkey,
in_memory: !params.local_save, in_memory: !params.local_save,
security_img: cursor.into_inner(), security_img: img_vec,
security_txt: new_string, security_txt: new_string,
pazzle_length: params.pazzle_length, pazzle_length: params.pazzle_length,
pin: params.pin, pin: params.pin,
password: params.password.as_ref().map(|p| p.trim().to_string()),
mnemonic: params.mnemonic,
send_bootstrap: params.send_bootstrap, send_bootstrap: params.send_bootstrap,
send_wallet: params.send_wallet, send_wallet: params.send_wallet,
result_with_wallet_file: params.result_with_wallet_file, result_with_wallet_file: params.result_with_wallet_file,
@ -583,25 +643,34 @@ pub async fn create_wallet_second_step_v0(
let mut ran = thread_rng(); let mut ran = thread_rng();
let mut category_indices: Vec<u8> = (0..params.pazzle_length).collect(); let pazzle = if params.pazzle_length > 0 {
category_indices.shuffle(&mut ran); let mut category_indices: Vec<u8> = (0..params.pazzle_length).collect();
category_indices.shuffle(&mut ran);
let between = Uniform::try_from(0..15).unwrap();
let mut pazzle = vec![0u8; params.pazzle_length.into()];
for (ix, i) in pazzle.iter_mut().enumerate() {
//*i = ran.gen_range(0, 15) + (category_indices[ix] << 4);
*i = between.sample(&mut ran) + (category_indices[ix] << 4);
}
//log_debug!("pazzle {:?}", pazzle); let between = Uniform::try_from(0..15).unwrap();
let between = Uniform::try_from(0..2048).unwrap(); let mut pazzle = vec![0u8; params.pazzle_length.into()];
let mut mnemonic = [0u16; 12]; for (ix, i) in pazzle.iter_mut().enumerate() {
for i in &mut mnemonic { //*i = ran.gen_range(0, 15) + (category_indices[ix] << 4);
//*i = ran.gen_range(0, 2048); *i = between.sample(&mut ran) + (category_indices[ix] << 4);
*i = between.sample(&mut ran); }
} //log_debug!("pazzle {:?}", pazzle);
Some(pazzle)
} else {
None
};
//log_debug!("mnemonic {:?}", display_mnemonic(&mnemonic)); let mnemonic = if params.mnemonic {
let between = Uniform::try_from(0..2048).unwrap();
let mut mnemonic = [0u16; 12];
for i in &mut mnemonic {
//*i = ran.gen_range(0, 2048);
*i = between.sample(&mut ran);
}
//log_debug!("mnemonic {:?}", display_mnemonic(&mnemonic));
Some(mnemonic)
} else {
None
};
//slice_as_array!(&mnemonic, [String; 12]) //slice_as_array!(&mnemonic, [String; 12])
//.ok_or(NgWalletError::InternalError)? //.ok_or(NgWalletError::InternalError)?
@ -676,35 +745,71 @@ pub async fn create_wallet_second_step_v0(
let mut master_key = [0u8; 32]; let mut master_key = [0u8; 32];
getrandom::fill(&mut master_key).map_err(|_e| NgWalletError::InternalError)?; getrandom::fill(&mut master_key).map_err(|_e| NgWalletError::InternalError)?;
let mut salt_pazzle = [0u8; 16]; let pazzle_login = if let Some(pazzle) = &pazzle {
let mut enc_master_key_pazzle = [0u8; 48]; let mut salt_pazzle = [0u8; 16];
if params.pazzle_length > 0 {
//log_debug!("salt_pazzle {:?}", salt_pazzle);
getrandom::fill(&mut salt_pazzle).map_err(|_e| NgWalletError::InternalError)?; getrandom::fill(&mut salt_pazzle).map_err(|_e| NgWalletError::InternalError)?;
let mut pazzle_key = derive_key_from_pass( let mut pazzle_key = derive_key_from_pass(
[pazzle.clone(), params.pin.to_vec()].concat(), [pazzle.clone(), params.pin.unwrap().to_vec()].concat(),
salt_pazzle, salt_pazzle,
wallet_id, wallet_id,
); );
enc_master_key_pazzle = enc_master_key(&master_key, &pazzle_key, 0, wallet_id)?; let enc_master_key_pazzle = enc_master_key(&master_key, &pazzle_key, 0, wallet_id)?;
pazzle_key.zeroize(); pazzle_key.zeroize();
} Some(LoginMethod {
salt: salt_pazzle,
enc_master_key: enc_master_key_pazzle,
})
} else {
None
};
let mut salt_mnemonic = [0u8; 16]; let mnemonic_login = if let Some(mnemonic) = mnemonic {
getrandom::fill(&mut salt_mnemonic).map_err(|_e| NgWalletError::InternalError)?; let mut salt_mnemonic = [0u8; 16];
getrandom::fill(&mut salt_mnemonic).map_err(|_e| NgWalletError::InternalError)?;
//log_debug!("salt_pazzle {:?}", salt_pazzle); //log_debug!("salt_mnemonic {:?}", salt_mnemonic);
//log_debug!("salt_mnemonic {:?}", salt_mnemonic);
let mut mnemonic_key = derive_key_from_pass( let mut mnemonic_key = derive_key_from_pass(
[transmute_to_bytes(&mnemonic), &params.pin].concat(), [transmute_to_bytes(&mnemonic), &params.pin.unwrap()].concat(),
salt_mnemonic, salt_mnemonic,
wallet_id, wallet_id,
); );
let enc_master_key_mnemonic = enc_master_key(&master_key, &mnemonic_key, 0, wallet_id)?;
mnemonic_key.zeroize();
Some(LoginMethod {
salt: salt_mnemonic,
enc_master_key: enc_master_key_mnemonic,
})
} else {
None
};
let password = if let Some(password) = &params.password {
let mut salt_password = [0u8; 16];
getrandom::fill(&mut salt_password).map_err(|_e| NgWalletError::InternalError)?;
//log_debug!("salt_password {:?}", salt_password);
let mut password_key =
derive_key_from_pass(password.as_bytes().to_vec(), salt_password, wallet_id);
let enc_master_key_password = enc_master_key(&master_key, &password_key, 0, wallet_id)?;
password_key.zeroize();
let enc_master_key_mnemonic = enc_master_key(&master_key, &mnemonic_key, 0, wallet_id)?; Some(LoginMethod {
mnemonic_key.zeroize(); salt: salt_password,
enc_master_key: enc_master_key_password,
})
} else {
None
};
let timestamp = now_timestamp(); let timestamp = now_timestamp();
@ -720,13 +825,15 @@ pub async fn create_wallet_second_step_v0(
master_key.zeroize(); master_key.zeroize();
let wallet_content = WalletContentV0 { let wallet_content = WalletContentV0 {
security_img: params.security_img.clone(), security_img: params
.security_img
.as_ref()
.map(|b| serde_bytes::ByteBuf::from(b.as_slice())),
security_txt: params.security_txt.clone(), security_txt: params.security_txt.clone(),
pazzle_length: params.pazzle_length, pazzle_length: params.pazzle_length,
salt_pazzle, mnemonic: mnemonic_login,
salt_mnemonic, pazzle: pazzle_login,
enc_master_key_pazzle, password,
enc_master_key_mnemonic,
master_nonce: 0, master_nonce: 0,
timestamp, timestamp,
peer_id: PubKey::nil(), peer_id: PubKey::nil(),
@ -736,7 +843,7 @@ pub async fn create_wallet_second_step_v0(
let ser_wallet = serde_bare::to_vec(&wallet_content).unwrap(); let ser_wallet = serde_bare::to_vec(&wallet_content).unwrap();
let sig = sign(&params.wallet_privkey, &wallet_id, &ser_wallet).unwrap(); let sig: Sig = sign(&params.wallet_privkey, &wallet_id, &ser_wallet).unwrap();
let wallet_v0 = WalletV0 { let wallet_v0 = WalletV0 {
// ID // ID
@ -774,7 +881,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), mnemonic_str: mnemonic.map_or(vec![], |m| display_mnemonic(&m)),
wallet_name: params.wallet_name.clone(), wallet_name: params.wallet_name.clone(),
client: params.client.clone(), client: params.client.clone(),
user, user,
@ -833,10 +940,12 @@ mod test {
let _creation = Instant::now(); let _creation = Instant::now();
let res = create_wallet_first_step_v0(CreateWalletV0::new( let res = create_wallet_first_step_v0(CreateWalletV0::new(
img_buffer, Some(img_buffer),
" know yourself ".to_string(), " know yourself ".to_string(),
pin, Some(pin),
9, 9,
None,
true,
false, false,
false, false,
BootstrapContentV0::new_localhost(PubKey::nil()), BootstrapContentV0::new_localhost(PubKey::nil()),
@ -863,16 +972,23 @@ mod test {
let _ = file.write_all(&ser_wallet); let _ = file.write_all(&ser_wallet);
log_debug!("wallet id: {}", res.wallet.id()); log_debug!("wallet id: {}", res.wallet.id());
log_debug!("pazzle {:?}", display_pazzle_one(&res.pazzle)); log_debug!(
log_debug!("mnemonic {:?}", display_mnemonic(&res.mnemonic)); "pazzle {:?}",
display_pazzle_one(res.pazzle.as_ref().expect("no pazzle"))
);
log_debug!(
"mnemonic {:?}",
display_mnemonic(&res.mnemonic.expect("no mnemonic"))
);
log_debug!("pin {:?}", pin); log_debug!("pin {:?}", pin);
if let Wallet::V0(v0) = &res.wallet { if let Wallet::V0(v0) = &res.wallet {
log_debug!("security text: {:?}", v0.content.security_txt); log_debug!("security text: {:?}", v0.content.security_txt);
let img = v0.content.security_img.as_ref().expect("no securit image");
let mut file = let mut file =
File::create("tests/generated_security_image.jpg").expect("open write file"); File::create("tests/generated_security_image.jpg").expect("open write file");
let _ = file.write_all(&v0.content.security_img); let _ = file.write_all(img);
let f = File::open("tests/generated_security_image.jpg.compare") let f = File::open("tests/generated_security_image.jpg.compare")
.expect("open of generated_security_image.jpg.compare"); .expect("open of generated_security_image.jpg.compare");
@ -883,12 +999,16 @@ mod test {
.read_to_end(&mut generated_security_image_compare) .read_to_end(&mut generated_security_image_compare)
.expect("read of generated_security_image.jpg.compare"); .expect("read of generated_security_image.jpg.compare");
assert_eq!(v0.content.security_img, generated_security_image_compare); assert_eq!(img, &generated_security_image_compare);
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(
.expect("open with mnemonic"); &Wallet::V0(v0.clone()),
res.mnemonic.expect("no mnemonic"),
pin.clone(),
)
.expect("open with mnemonic");
//log_debug!("encrypted part {:?}", w); //log_debug!("encrypted part {:?}", w);
log_info!( log_info!(
@ -898,8 +1018,12 @@ mod test {
if v0.content.pazzle_length > 0 { if v0.content.pazzle_length > 0 {
let _opening_pazzle = Instant::now(); let _opening_pazzle = Instant::now();
let _w = open_wallet_with_pazzle(&Wallet::V0(v0.clone()), res.pazzle.clone(), pin) let _w = open_wallet_with_pazzle(
.expect("open with pazzle"); &Wallet::V0(v0.clone()),
res.pazzle.as_ref().expect("no pazzle").clone(),
pin,
)
.expect("open with pazzle");
log_info!( log_info!(
"opening of wallet with pazzle took: {} ms", "opening of wallet with pazzle took: {} ms",
_opening_pazzle.elapsed().as_millis() _opening_pazzle.elapsed().as_millis()

@ -660,27 +660,32 @@ impl SensitiveWalletV0 {
} }
} }
/// Login method
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct LoginMethod {
pub salt: [u8; 16],
// encrypted master keys.
// AD = wallet_id
#[serde(with = "BigArray")]
pub enc_master_key: [u8; 48],
}
/// Wallet content Version 0 /// Wallet content Version 0
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
pub struct WalletContentV0 { pub struct WalletContentV0 {
#[serde(with = "serde_bytes")] pub security_img: Option<serde_bytes::ByteBuf>,
pub security_img: Vec<u8>,
pub security_txt: String, pub security_txt: String,
/// can be 9, 12 or 15 (or 0, in this case salt_pazzle and enc_master_key_pazzle are filled with zeros and should not be used) /// can be 9, 12 or 15 (or 0, if pazzle is deactivated)
pub pazzle_length: u8, pub pazzle_length: u8,
pub salt_pazzle: [u8; 16], pub pazzle: Option<LoginMethod>,
pub salt_mnemonic: [u8; 16], pub mnemonic: Option<LoginMethod>,
// encrypted master keys. first is encrypted with pazzle, second is encrypted with mnemonic pub password: Option<LoginMethod>,
// AD = wallet_id
#[serde(with = "BigArray")]
pub enc_master_key_pazzle: [u8; 48],
#[serde(with = "BigArray")]
pub enc_master_key_mnemonic: [u8; 48],
// nonce for the encryption of masterkey // nonce for the encryption of masterkey
// incremented only if the masterkey changes // incremented only if the masterkey changes
@ -1243,7 +1248,7 @@ pub struct CreateWalletV0 {
/// Please be aware that other users who are sharing the same device, will be able to see this image. /// Please be aware that other users who are sharing the same device, will be able to see this image.
#[zeroize(skip)] #[zeroize(skip)]
#[serde(with = "serde_bytes")] #[serde(with = "serde_bytes")]
pub security_img: Vec<u8>, pub security_img: Option<Vec<u8>>,
/// A string of characters of minimum length 10. /// A string of characters of minimum length 10.
/// This phrase will be presented to the user every time they are about to enter their pazzle and PIN in order to unlock their wallet. /// This phrase will be presented to the user every time they are about to enter their pazzle and PIN in order to unlock their wallet.
/// It should be something the user will remember, but not something too personal. /// It should be something the user will remember, but not something too personal.
@ -1256,10 +1261,15 @@ pub struct CreateWalletV0 {
/// The PIN and the rest of the Wallet will never be sent to NextGraph or any other third party (check the source code if you don't believe us). /// The PIN and the rest of the Wallet will never be sent to NextGraph or any other third party (check the source code if you don't believe us).
/// It cannot be a series like 1234 or 8765. The same digit cannot repeat more than once. By example 4484 is invalid. /// It cannot be a series like 1234 or 8765. The same digit cannot repeat more than once. By example 4484 is invalid.
/// Try to avoid birth date, last digits of phone number, or zip code for privacy concern /// Try to avoid birth date, last digits of phone number, or zip code for privacy concern
pub pin: [u8; 4], pub pin: Option<[u8; 4]>,
/// For now, only 9 is supported. 12 and 15 are planned. /// For now, only 9 is supported. 12 and 15 are planned.
/// A value of 0 will deactivate the pazzle mechanism on this Wallet, and only the mnemonic could be used to open it. /// A value of 0 will deactivate the pazzle mechanism on this Wallet, and only the mnemonic could be used to open it.
pub pazzle_length: u8, pub pazzle_length: u8,
pub password: Option<String>,
pub mnemonic: bool,
#[zeroize(skip)] #[zeroize(skip)]
/// Not implemented yet. Will send the bootstrap to our cloud servers, if needed /// Not implemented yet. Will send the bootstrap to our cloud servers, if needed
pub send_bootstrap: bool, pub send_bootstrap: bool,
@ -1294,10 +1304,12 @@ pub struct CreateWalletV0 {
impl CreateWalletV0 { impl CreateWalletV0 {
pub fn new( pub fn new(
security_img: Vec<u8>, security_img: Option<Vec<u8>>,
security_txt: String, security_txt: String,
pin: [u8; 4], pin: Option<[u8; 4]>,
pazzle_length: u8, pazzle_length: u8,
password: Option<String>,
mnemonic: bool,
send_bootstrap: bool, send_bootstrap: bool,
send_wallet: bool, send_wallet: bool,
core_bootstrap: BootstrapContentV0, core_bootstrap: BootstrapContentV0,
@ -1313,6 +1325,8 @@ impl CreateWalletV0 {
security_txt, security_txt,
pin, pin,
pazzle_length, pazzle_length,
password,
mnemonic,
send_bootstrap, send_bootstrap,
send_wallet, send_wallet,
core_bootstrap, core_bootstrap,
@ -1351,10 +1365,10 @@ pub struct CreateWalletResultV0 {
/// The binary file that can be saved to disk and given to the user /// The binary file that can be saved to disk and given to the user
pub wallet_file: Vec<u8>, pub wallet_file: Vec<u8>,
/// randomly generated pazzle /// randomly generated pazzle
pub pazzle: Vec<u8>, pub pazzle: Option<Vec<u8>>,
/// 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: Option<[u16; 12]>,
/// The words of the mnemonic, in a human readable form. /// The words of the mnemonic, in a human readable form.
pub mnemonic_str: Vec<String>, pub mnemonic_str: Vec<String>,
#[zeroize(skip)] #[zeroize(skip)]
@ -1400,13 +1414,17 @@ pub struct CreateWalletIntermediaryV0 {
pub in_memory: bool, pub in_memory: bool,
#[zeroize(skip)] #[zeroize(skip)]
pub security_img: Vec<u8>, pub security_img: Option<Vec<u8>>,
pub security_txt: String, pub security_txt: String,
pub pazzle_length: u8, pub pazzle_length: u8,
pub pin: [u8; 4], pub mnemonic: bool,
pub password: Option<String>,
pub pin: Option<[u8; 4]>,
#[zeroize(skip)] #[zeroize(skip)]
pub send_bootstrap: bool, pub send_bootstrap: bool,
@ -1439,6 +1457,9 @@ pub enum NgWalletError {
NoCreateWalletPresent, NoCreateWalletPresent,
InvalidBootstrap, InvalidBootstrap,
SerializationError, SerializationError,
MnemonicOrPazzleNeedAPin,
NoLoginMethod,
LoginMethodNotSupported,
} }
impl From<NgWalletError> for NgError { impl From<NgWalletError> for NgError {

@ -75,7 +75,7 @@
window.location.href = result.url; window.location.href = result.url;
} else { } else {
wait = true; wait = true;
window.history.go(-1); window.location.href = document.referrer;
} }
} }
} }

@ -23,7 +23,7 @@
"svelte": "^3.58.0", "svelte": "^3.58.0",
"postcss": "^8.4.23", "postcss": "^8.4.23",
"postcss-load-config": "^4.0.1", "postcss-load-config": "^4.0.1",
"svelte-heros-v2": "^0.10.12", "svelte-heros-v2": "^1.3.0",
"svelte-preprocess": "^5.0.3", "svelte-preprocess": "^5.0.3",
"tailwindcss": "^3.3.1", "tailwindcss": "^3.3.1",
"vite-plugin-svelte-svg": "^2.2.1", "vite-plugin-svelte-svg": "^2.2.1",

@ -26,7 +26,7 @@
"vite": "^4.3.9", "vite": "^4.3.9",
"postcss": "^8.4.23", "postcss": "^8.4.23",
"postcss-load-config": "^4.0.1", "postcss-load-config": "^4.0.1",
"svelte-heros-v2": "^0.10.12", "svelte-heros-v2": "^1.3.0",
"svelte-preprocess": "^5.0.3", "svelte-preprocess": "^5.0.3",
"tailwindcss": "^3.3.1", "tailwindcss": "^3.3.1",
"autoprefixer": "^10.4.14", "autoprefixer": "^10.4.14",

@ -203,8 +203,8 @@ importers:
specifier: ^3.0.0 specifier: ^3.0.0
version: 3.8.6(@babel/core@7.28.4)(postcss-load-config@4.0.2(postcss@8.5.6))(postcss@8.5.6)(svelte@3.59.2) version: 3.8.6(@babel/core@7.28.4)(postcss-load-config@4.0.2(postcss@8.5.6))(postcss@8.5.6)(svelte@3.59.2)
svelte-heros-v2: svelte-heros-v2:
specifier: ^0.10.12 specifier: ^1.3.0
version: 0.10.12(svelte@3.59.2) version: 1.3.0(svelte@3.59.2)
svelte-preprocess: svelte-preprocess:
specifier: ^5.0.3 specifier: ^5.0.3
version: 5.1.4(@babel/core@7.28.4)(postcss-load-config@4.0.2(postcss@8.5.6))(postcss@8.5.6)(svelte@3.59.2)(typescript@4.9.5) version: 5.1.4(@babel/core@7.28.4)(postcss-load-config@4.0.2(postcss@8.5.6))(postcss@8.5.6)(svelte@3.59.2)(typescript@4.9.5)
@ -279,8 +279,8 @@ importers:
specifier: ^3.58.0 specifier: ^3.58.0
version: 3.59.2 version: 3.59.2
svelte-heros-v2: svelte-heros-v2:
specifier: ^0.10.12 specifier: ^1.3.0
version: 0.10.12(svelte@3.59.2) version: 1.3.0(svelte@3.59.2)
svelte-preprocess: svelte-preprocess:
specifier: ^5.0.3 specifier: ^5.0.3
version: 5.1.4(@babel/core@7.28.4)(postcss-load-config@4.0.2(postcss@8.5.6))(postcss@8.5.6)(svelte@3.59.2)(typescript@5.9.2) version: 5.1.4(@babel/core@7.28.4)(postcss-load-config@4.0.2(postcss@8.5.6))(postcss@8.5.6)(svelte@3.59.2)(typescript@5.9.2)
@ -453,8 +453,8 @@ importers:
specifier: ^3.58.0 specifier: ^3.58.0
version: 3.59.2 version: 3.59.2
svelte-heros-v2: svelte-heros-v2:
specifier: ^0.10.12 specifier: ^1.3.0
version: 0.10.12(svelte@3.59.2) version: 1.3.0(svelte@3.59.2)
svelte-preprocess: svelte-preprocess:
specifier: ^5.0.3 specifier: ^5.0.3
version: 5.1.4(@babel/core@7.28.4)(postcss-load-config@4.0.2(postcss@8.5.6))(postcss@8.5.6)(svelte@3.59.2)(typescript@5.9.2) version: 5.1.4(@babel/core@7.28.4)(postcss-load-config@4.0.2(postcss@8.5.6))(postcss@8.5.6)(svelte@3.59.2)(typescript@5.9.2)
@ -520,8 +520,8 @@ importers:
specifier: ^3.58.0 specifier: ^3.58.0
version: 3.59.2 version: 3.59.2
svelte-heros-v2: svelte-heros-v2:
specifier: ^0.10.12 specifier: ^1.3.0
version: 0.10.12(svelte@3.59.2) version: 1.3.0(svelte@3.59.2)
svelte-preprocess: svelte-preprocess:
specifier: ^5.0.3 specifier: ^5.0.3
version: 5.1.4(@babel/core@7.28.4)(postcss-load-config@4.0.2(postcss@8.5.6))(postcss@8.5.6)(svelte@3.59.2)(typescript@5.9.2) version: 5.1.4(@babel/core@7.28.4)(postcss-load-config@4.0.2(postcss@8.5.6))(postcss@8.5.6)(svelte@3.59.2)(typescript@5.9.2)
@ -6546,10 +6546,10 @@ packages:
svelte: ^4.0.0 || ^5.0.0-next.0 svelte: ^4.0.0 || ^5.0.0-next.0
typescript: '>=5.0.0' typescript: '>=5.0.0'
svelte-heros-v2@0.10.12: svelte-heros-v2@1.3.0:
resolution: {integrity: sha512-0wspy0z9UFS9f/iPKQQ1JDHlNY6e7h+LVW+wJ0qJnuWDpvsJllmoCX2g0frYbMPDWZJEwh2pkO25Dp3lDGCxGQ==} resolution: {integrity: sha512-H+s2Z907WU8sLG/dOYGfiIq7mxtACm6LM+A8jdcDCWtjyyoOmtL2waZEKKXsLrcwO5g5/D6i0TqSs0UJuchRoA==}
peerDependencies: peerDependencies:
svelte: ^3.54.0 || ^4.0.0 svelte: ^3.54.0 || ^4.0.0 || ^5.0.0
svelte-hmr@0.15.3: svelte-hmr@0.15.3:
resolution: {integrity: sha512-41snaPswvSf8TJUhlkoJBekRrABDXDMdpNpT2tfHIv4JuhgvHqLMhEPGtaQn0BmbNSTkuz2Ed20DF2eHw0SmBQ==} resolution: {integrity: sha512-41snaPswvSf8TJUhlkoJBekRrABDXDMdpNpT2tfHIv4JuhgvHqLMhEPGtaQn0BmbNSTkuz2Ed20DF2eHw0SmBQ==}
@ -14507,7 +14507,7 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- picomatch - picomatch
svelte-heros-v2@0.10.12(svelte@3.59.2): svelte-heros-v2@1.3.0(svelte@3.59.2):
dependencies: dependencies:
svelte: 3.59.2 svelte: 3.59.2

@ -175,6 +175,18 @@ pub fn privkey_to_string(privkey: JsValue) -> Result<String, JsValue> {
Ok(format!("{p}")) Ok(format!("{p}"))
} }
pub fn wallet_open_with_password(wallet: JsValue, password: String) -> Result<JsValue, JsValue> {
let encrypted_wallet = serde_wasm_bindgen::from_value::<Wallet>(wallet)
.map_err(|_| "Deserialization error of wallet")?;
let res = nextgraph::local_broker::wallet_open_with_password(&encrypted_wallet, password);
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_open_with_pazzle( pub fn wallet_open_with_pazzle(
wallet: JsValue, wallet: JsValue,
@ -924,7 +936,7 @@ static INIT_LOCAL_BROKER: Lazy<Box<ConfigInitFn>> = Lazy::new(|| {
pub async fn wallet_create(params: JsValue) -> Result<JsValue, JsValue> { pub async fn wallet_create(params: JsValue) -> Result<JsValue, JsValue> {
init_local_broker_with_lazy(&INIT_LOCAL_BROKER).await; init_local_broker_with_lazy(&INIT_LOCAL_BROKER).await;
let mut params = serde_wasm_bindgen::from_value::<CreateWalletV0>(params) let mut params = serde_wasm_bindgen::from_value::<CreateWalletV0>(params)
.map_err(|_| "Deserialization error of args")?; .map_err(|e| format!("Deserialization error of args {e}"))?;
params.result_with_wallet_file = true; params.result_with_wallet_file = true;
let res = nextgraph::local_broker::wallet_create_v0(params).await; let res = nextgraph::local_broker::wallet_create_v0(params).await;
match res { match res {
@ -2143,10 +2155,12 @@ pub async fn gen_wallet_for_test(ngd_peer_id: String) -> Result<JsValue, String>
let peer_id_of_server_broker = decode_key(&ngd_peer_id).map_err(|e: NgError| e.to_string())?; let peer_id_of_server_broker = decode_key(&ngd_peer_id).map_err(|e: NgError| e.to_string())?;
let wallet_result = wallet_create_v0(CreateWalletV0 { let wallet_result = wallet_create_v0(CreateWalletV0 {
security_img: Vec::from(EMPTY_IMG), security_img: None,
security_txt: "testsecurityphrase".to_string(), security_txt: "testsecurityphrase".to_string(),
pin: [1, 2, 1, 2], pin: Some([1, 2, 1, 2]),
pazzle_length: 9, pazzle_length: 9,
mnemonic: true,
password: None,
send_bootstrap: false, send_bootstrap: false,
send_wallet: false, send_wallet: false,
result_with_wallet_file: false, result_with_wallet_file: false,
@ -2161,7 +2175,7 @@ pub async fn gen_wallet_for_test(ngd_peer_id: String) -> Result<JsValue, String>
.expect("wallet_create_v0"); .expect("wallet_create_v0");
let mut mnemonic_words = Vec::with_capacity(12); let mut mnemonic_words = Vec::with_capacity(12);
display_mnemonic(&wallet_result.mnemonic) display_mnemonic(&wallet_result.mnemonic.unwrap())
.iter() .iter()
.for_each(|word| { .for_each(|word| {
mnemonic_words.push(word.clone()); mnemonic_words.push(word.clone());

@ -1589,6 +1589,8 @@ pub async fn wallet_create_v0(params: CreateWalletV0) -> Result<CreateWalletResu
return Err(NgError::CannotSaveWhenInMemoryConfig); return Err(NgError::CannotSaveWhenInMemoryConfig);
} }
let in_memory = !params.local_save; let in_memory = !params.local_save;
let has_pazzle = params.pazzle_length > 0;
let has_mnemonic = params.mnemonic;
let intermediate = create_wallet_first_step_v0(params)?; let intermediate = create_wallet_first_step_v0(params)?;
let lws: LocalWalletStorageV0 = (&intermediate).into(); let lws: LocalWalletStorageV0 = (&intermediate).into();
@ -1619,9 +1621,13 @@ pub async fn wallet_create_v0(params: CreateWalletV0) -> Result<CreateWalletResu
let (mut res, site, brokers) = let (mut res, site, brokers) =
create_wallet_second_step_v0(intermediate, &mut session.verifier).await?; create_wallet_second_step_v0(intermediate, &mut session.verifier).await?;
if with_pdf { if with_pdf && pin.is_some() && has_mnemonic && has_pazzle {
let wallet_recovery = let wallet_recovery = wallet_to_wallet_recovery(
wallet_to_wallet_recovery(&res.wallet, res.pazzle.clone(), res.mnemonic, pin); &res.wallet,
res.pazzle.clone().unwrap(),
res.mnemonic.clone().unwrap(),
pin.unwrap(),
);
if let Ok(pdf_buffer) = wallet_recovery_pdf(wallet_recovery, 600).await { if let Ok(pdf_buffer) = wallet_recovery_pdf(wallet_recovery, 600).await {
res.pdf_file = pdf_buffer; res.pdf_file = pdf_buffer;
@ -1706,7 +1712,7 @@ pub fn wallet_to_wallet_recovery(
match wallet { match wallet {
Wallet::V0(v0) => { Wallet::V0(v0) => {
let mut content = v0.content.clone(); let mut content = v0.content.clone();
content.security_img = vec![]; content.security_img = None;
content.security_txt = String::new(); content.security_txt = String::new();
NgQRCodeWalletRecoveryV0 { NgQRCodeWalletRecoveryV0 {
wallet: serde_bare::to_vec(&content).unwrap(), wallet: serde_bare::to_vec(&content).unwrap(),
@ -2145,6 +2151,16 @@ pub async fn wallet_get_file(wallet_name: &String) -> Result<Vec<u8>, NgError> {
} }
} }
#[doc(hidden)]
pub fn wallet_open_with_password(
wallet: &Wallet,
password: String,
) -> Result<SensitiveWallet, NgError> {
let opened_wallet = ng_wallet::open_wallet_with_password(wallet, password)?;
Ok(opened_wallet)
}
#[doc(hidden)] #[doc(hidden)]
/// This is a bit hard to use as the pazzle words are encoded in unsigned bytes. /// This is a bit hard to use as the pazzle words are encoded in unsigned bytes.
/// prefer the function wallet_open_with_pazzle_words /// prefer the function wallet_open_with_pazzle_words
@ -3045,10 +3061,12 @@ mod test {
let peer_id_of_server_broker = PubKey::nil(); let peer_id_of_server_broker = PubKey::nil();
let wallet_result = wallet_create_v0(CreateWalletV0 { let wallet_result = wallet_create_v0(CreateWalletV0 {
security_img, security_img: Some(security_img),
security_txt: "know yourself".to_string(), security_txt: "know yourself".to_string(),
pin: [1, 2, 1, 2], pin: Some([1, 2, 1, 2]),
pazzle_length: 9, pazzle_length: 9,
password: None,
mnemonic: true,
send_bootstrap: false, send_bootstrap: false,
send_wallet: false, send_wallet: false,
result_with_wallet_file: true, result_with_wallet_file: true,
@ -3063,9 +3081,10 @@ mod test {
.await .await
.expect("wallet_create_v0"); .expect("wallet_create_v0");
let pazzle = display_pazzle(&wallet_result.pazzle); let pazzle_vec = wallet_result.pazzle.clone().unwrap();
let pazzle = display_pazzle(&pazzle_vec);
let mut pazzle_words = vec![]; let mut pazzle_words = vec![];
println!("Your pazzle is: {:?}", wallet_result.pazzle); println!("Your pazzle is: {:?}", pazzle_vec);
for emoji in pazzle { for emoji in pazzle {
println!(" {}:\t{}", emoji.0, emoji.1); println!(" {}:\t{}", emoji.0, emoji.1);
pazzle_words.push(emoji.1.to_string()); pazzle_words.push(emoji.1.to_string());
@ -3080,7 +3099,7 @@ mod test {
println!("Your mnemonic is:"); println!("Your mnemonic is:");
let mut mnemonic_words = vec![]; let mut mnemonic_words = vec![];
display_mnemonic(&wallet_result.mnemonic) display_mnemonic(&wallet_result.mnemonic.unwrap())
.iter() .iter()
.for_each(|word| { .for_each(|word| {
mnemonic_words.push(word.clone()); mnemonic_words.push(word.clone());

Loading…
Cancel
Save