From 8420c18ca3bfd4d3df42760fccf621d75a433463 Mon Sep 17 00:00:00 2001
From: Niko PLP <niko@nextgraph.org>
Date: Tue, 13 Aug 2024 17:36:47 +0300
Subject: [PATCH] download recovery PDF

---
 Cargo.lock                            |  1 +
 nextgraph/examples/in_memory.rs       |  2 +
 nextgraph/examples/persistent.rs      |  2 +
 nextgraph/src/local_broker.rs         | 14 +++++-
 ng-app/src-tauri/Cargo.toml           |  1 +
 ng-app/src-tauri/src/lib.rs           | 16 +++++++
 ng-app/src/App.svelte                 |  2 +-
 ng-app/src/lib/Login.svelte           |  2 +-
 ng-app/src/locales/en.json            |  3 ++
 ng-app/src/routes/WalletCreate.svelte | 69 +++++++++++++++++++++++----
 ng-sdk-js/src/lib.rs                  |  8 ++--
 ng-wallet/src/lib.rs                  |  4 ++
 ng-wallet/src/types.rs                | 21 +++++++-
 13 files changed, 130 insertions(+), 15 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index 232990f..3f42fa1 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3323,6 +3323,7 @@ dependencies = [
  "tauri-plugin-barcode-scanner",
  "tauri-plugin-window",
  "tauri-utils",
+ "zeroize",
 ]
 
 [[package]]
diff --git a/nextgraph/examples/in_memory.rs b/nextgraph/examples/in_memory.rs
index 558a668..24e27f9 100644
--- a/nextgraph/examples/in_memory.rs
+++ b/nextgraph/examples/in_memory.rs
@@ -55,6 +55,8 @@ async fn main() -> std::io::Result<()> {
         core_bootstrap: BootstrapContentV0::new_localhost(peer_id_of_server_broker),
         core_registration: None,
         additional_bootstrap: None,
+        pdf: false,
+        device_name: "test".to_string(),
     })
     .await?;
 
diff --git a/nextgraph/examples/persistent.rs b/nextgraph/examples/persistent.rs
index 1ff38cf..f1e78c7 100644
--- a/nextgraph/examples/persistent.rs
+++ b/nextgraph/examples/persistent.rs
@@ -63,6 +63,8 @@ async fn main() -> std::io::Result<()> {
         core_bootstrap: BootstrapContentV0::new_localhost(peer_id_of_server_broker),
         core_registration: None,
         additional_bootstrap: None,
+        pdf: false,
+        device_name: "test".to_string(),
     })
     .await?;
 
diff --git a/nextgraph/src/local_broker.rs b/nextgraph/src/local_broker.rs
index 80b4f21..19dfd7b 100644
--- a/nextgraph/src/local_broker.rs
+++ b/nextgraph/src/local_broker.rs
@@ -1570,10 +1570,20 @@ pub async fn wallet_create_v0(params: CreateWalletV0) -> Result<CreateWalletResu
     // let session = broker.opened_sessions_list[session_info.session_id as usize]
     //     .as_mut()
     //     .unwrap();
-
+    let with_pdf = intermediate.pdf;
+    let pin = intermediate.pin;
     let (mut res, site, brokers) =
         create_wallet_second_step_v0(intermediate, &mut session.verifier).await?;
 
+    if with_pdf {
+        let wallet_recovery =
+            wallet_to_wallet_recovery(&res.wallet, res.pazzle.clone(), res.mnemonic, pin);
+
+        if let Ok(pdf_buffer) = wallet_recovery_pdf(wallet_recovery, 600).await {
+            res.pdf_file = pdf_buffer;
+        };
+    }
+
     //log_info!("VERIFIER DUMP {:?}", session.verifier);
 
     broker.wallets.get_mut(&res.wallet_name).unwrap().wallet = res.wallet.clone();
@@ -2857,6 +2867,8 @@ mod test {
             core_bootstrap: BootstrapContentV0::new_localhost(peer_id_of_server_broker),
             core_registration: None,
             additional_bootstrap: None,
+            pdf: false,
+            device_name: "test".to_string(),
         })
         .await
         .expect("wallet_create_v0");
diff --git a/ng-app/src-tauri/Cargo.toml b/ng-app/src-tauri/Cargo.toml
index 5b5a0ab..8fb72f9 100644
--- a/ng-app/src-tauri/Cargo.toml
+++ b/ng-app/src-tauri/Cargo.toml
@@ -30,6 +30,7 @@ serde_json = "1.0"
 serde_bytes = "0.11.7"
 async-std = { version = "1.12.0", features = ["attributes", "unstable"] }
 sys-locale = { version = "0.3.1" }
+zeroize = { version = "1.7.0", features = ["zeroize_derive"] }
 ng-async-tungstenite = {  git = "https://git.nextgraph.org/NextGraph/async-tungstenite.git", branch = "nextgraph",  features = ["async-std-runtime", "async-native-tls"] }
 tauri = { version = "2.0.0-alpha.14", features = [] }
 # add the "devtools" feature if devtools in the production build should be activated
diff --git a/ng-app/src-tauri/src/lib.rs b/ng-app/src-tauri/src/lib.rs
index 3d90702..a269452 100644
--- a/ng-app/src-tauri/src/lib.rs
+++ b/ng-app/src-tauri/src/lib.rs
@@ -18,6 +18,7 @@ use sys_locale::get_locales;
 use tauri::scope::ipc::RemoteDomainAccessScope;
 use tauri::utils::config::WindowConfig;
 use tauri::{path::BaseDirectory, App, Manager};
+use zeroize::Zeroize;
 
 use ng_repo::errors::NgError;
 use ng_repo::log::*;
@@ -160,6 +161,7 @@ async fn wallet_create(
     //log_debug!("wallet_create from rust {:?}", params);
     params.result_with_wallet_file = !params.local_save;
     let local_save = params.local_save;
+    let pdf = params.pdf;
     let mut cwr = nextgraph::local_broker::wallet_create_v0(params)
         .await
         .map_err(|e| e.to_string())?;
@@ -173,8 +175,22 @@ async fn wallet_create(
             )
             .unwrap();
         let _r = write(path, &cwr.wallet_file);
+        cwr.wallet_file.zeroize();
         cwr.wallet_file = vec![];
     }
+    if pdf {
+        // save pdf file to Downloads folder
+        let path = app
+            .path()
+            .resolve(
+                format!("wallet-{}.pdf", cwr.wallet_name),
+                BaseDirectory::Download,
+            )
+            .unwrap();
+        let _r = write(path, &cwr.pdf_file);
+        cwr.pdf_file.zeroize();
+        cwr.pdf_file = vec![];
+    }
     Ok(cwr)
 }
 
diff --git a/ng-app/src/App.svelte b/ng-app/src/App.svelte
index f018b5f..cf6798c 100644
--- a/ng-app/src/App.svelte
+++ b/ng-app/src/App.svelte
@@ -151,7 +151,7 @@
       window.wallet_channel = wallet_channel;
       wallet_channel.postMessage({ cmd: "startup" }, location.href);
       wallet_channel.onmessage = async (event) => {
-        //console.log(event.data.cmd, event.data);
+        // console.log(event.data.cmd, event.data);
         if (!location.href.startsWith(event.origin)) return;
         switch (event.data.cmd) {
           case "startup":
diff --git a/ng-app/src/lib/Login.svelte b/ng-app/src/lib/Login.svelte
index dcbb460..0e8bdcb 100644
--- a/ng-app/src/lib/Login.svelte
+++ b/ng-app/src/lib/Login.svelte
@@ -411,7 +411,7 @@
       <div class="max-w-xl lg:px-8 mx-auto px-4 text-primary-700">
         <div class="flex flex-col justify-centerspace-x-12 mt-4 mb-4">
           <!-- Device Name, if trusted-->
-          {#if for_import}
+          {#if for_import && trusted}
             <label for="device-name-input" class="text-sm text-black">
               {$t("pages.login.device_name_label")}
             </label>
diff --git a/ng-app/src/locales/en.json b/ng-app/src/locales/en.json
index e47c572..7959fb6 100644
--- a/ng-app/src/locales/en.json
+++ b/ng-app/src/locales/en.json
@@ -468,6 +468,9 @@
       "download_wallet_description": "Please download your wallet and keep it in a safe location",
       "download_wallet": "Download my wallet",
       "download_wallet_done": "Your wallet file has been downloaded into your \"Downloads\" folder, with the name<br /><span class=\"text-black\"> {download_name}</span ><br /> <span class=\"font-bold\" >Please move it to a safe and durable place.</span ><br />",
+      "download_pdf_description": "Please download your Recovery PDF, print it, and delete it.",
+      "download_pdf": "Download my PDF",
+      "download_pdf_done": "Your Recovery PDF file has been downloaded into your \"Downloads\" folder, with the name<br /><span class=\"text-black\"> {pdf_name}</span ><br /> <span class=\"font-bold\" >Please print it and then delete it.</span ><br />",
       "your_pazzle": "Here below is your Pazzle. <br /> The <span class=\"font-bold\">order</span> of each image is <span class=\"font-bold\">important</span>!",
       "your_mnemonic": "And here is your mnemonic (your alternative passphrase):",
       "unlock_tips_1": "You can use both the pazzle or the mnemonic to unlock your wallet. The pazzle is easier to remember. The mnemonic is useful in some special cases. We recommend that you use the pazzle. <span class=\"font-bold text-xl\" >Copy both on a piece of paper.</span > You should try to memorize the pazzle. Once you did, you won't need the paper anymore.",
diff --git a/ng-app/src/routes/WalletCreate.svelte b/ng-app/src/routes/WalletCreate.svelte
index 2953f3d..ba6eeb3 100644
--- a/ng-app/src/routes/WalletCreate.svelte
+++ b/ng-app/src/routes/WalletCreate.svelte
@@ -128,8 +128,11 @@
   let ready;
   let download_link;
   let download_name;
+  let pdf_link;
+  let pdf_name;
   let cloud_link;
   let animateDownload = true;
+  let animatePdf = true;
   let invitation;
   let pre_invitation;
 
@@ -228,7 +231,7 @@
       trusted: true,
       cloud: false,
       bootstrap: false,
-      pdf: false,
+      pdf: !mobile,
     };
     await tick();
     scrollToTop();
@@ -257,7 +260,8 @@
       core_bootstrap: invitation.V0.bootstrap,
       core_registration,
       additional_bootstrap,
-      //TODO: device_name,
+      device_name,
+      pdf: options.pdf
     };
     //console.log("do wallet with params", params);
     try {
@@ -275,11 +279,12 @@
           window.wallet_channel.postMessage(new_in_mem, location.href);
         }
       }
-      console.log("pazzle", ready.pazzle);
-      console.log("pazzle words", display_pazzle(ready.pazzle));
-      console.log("mnemonic", ready.mnemonic);
-      console.log("mnemonic words", ready.mnemonic_str);
+      // console.log("pazzle", ready.pazzle);
+      // console.log("pazzle words", display_pazzle(ready.pazzle));
+      // console.log("mnemonic", ready.mnemonic);
+      // console.log("mnemonic words", ready.mnemonic_str);
       download_name = "wallet-" + ready.wallet_name + ".ngw";
+      pdf_name = "wallet-" + ready.wallet_name + ".pdf";
       if (options.cloud) {
         cloud_link = "https://nextgraph.one/#/w/" + ready.wallet_name;
       }
@@ -289,6 +294,12 @@
         });
         download_link = URL.createObjectURL(blob);
       }
+      if (ready.pdf_file.length) {
+        const blob = new Blob([ready.pdf_file], {
+          type: "application/octet-stream",
+        });
+        pdf_link = URL.createObjectURL(blob);
+      }
     } catch (e) {
       console.error(e);
       error = e;
@@ -1518,7 +1529,7 @@
               {#if !tauri_platform}{@html $t(
                   "pages.login.trust_device_allow_cookies"
                 )}{/if}<br /><br />
-              <Toggle bind:checked={options.trusted}
+              <Toggle bind:checked={options.trusted} on:change={()=> {if (!options.trusted) options.pdf=false;}}
                 >{@html $t(
                   "pages.wallet_create.save_wallet_options.trust_toggle"
                 )}</Toggle
@@ -1575,7 +1586,7 @@
                 "pages.wallet_create.save_wallet_options.pdf_description"
               )}
               <br /><br />
-              <Toggle disabled bind:checked={options.pdf}
+              <Toggle bind:checked={options.pdf}
                 >{@html $t(
                   "pages.wallet_create.save_wallet_options.pdf_toggle"
                 )}</Toggle
@@ -1718,6 +1729,47 @@
                     values: { download_name },
                   })}
                 {/if}
+                {#if pdf_link}
+                  {@html $t(
+                    "pages.wallet_create.download_pdf_description"
+                  )}<br />
+                  <a
+                    href={pdf_link}
+                    target="_blank"
+                    download={pdf_name}
+                  >
+                    <button
+                      tabindex="-1"
+                      class:animate-bounce={animatePdf}
+                      on:click={() => (animatePdf = false)}
+                      class="mt-10 mb-8 text-white bg-primary-700 hover:bg-primary-700/90 focus:ring-4 focus:ring-primary-700/50 font-medium rounded-lg text-lg px-5 py-2.5 text-center inline-flex items-center dark:focus:ring-primary-700/55"
+                    >
+                      <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
+                          stroke-linecap="round"
+                          stroke-linejoin="round"
+                          d="M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m.75 12l3 3m0 0l3-3m-3 3v-6m-1.5-9H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z"
+                        />
+                      </svg>
+
+                      {@html $t("pages.wallet_create.download_pdf")}
+                    </button>
+                  </a>
+
+                  <br />
+                {:else if options.pdf}
+                  {@html $t("pages.wallet_create.download_pdf_done", {
+                    values: { pdf_name },
+                  })}
+                {/if}
                 <!-- Pazzle -->
                 {@html $t("pages.wallet_create.your_pazzle")}
               </div>
@@ -1854,6 +1906,7 @@
                 creating = false;
                 error = undefined;
                 animateDownload = true;
+                animatePdf = true;
               }}
             >
               {$t("buttons.start_over")}
diff --git a/ng-sdk-js/src/lib.rs b/ng-sdk-js/src/lib.rs
index b0e39fc..a8759f5 100644
--- a/ng-sdk-js/src/lib.rs
+++ b/ng-sdk-js/src/lib.rs
@@ -566,9 +566,11 @@ pub async fn add_in_memory_wallet(lws_js: JsValue) -> Result<(), String> {
     if !lws.in_memory {
         return Err("This is not an in memory wallet".to_string());
     }
-    nextgraph::local_broker::wallet_add(lws)
-        .await
-        .map_err(|e: NgError| e.to_string())
+    match nextgraph::local_broker::wallet_add(lws).await {
+        Ok(_) => Ok(()),
+        Err(NgError::WalletAlreadyAdded) => Ok(()),
+        Err(e) => Err(e.to_string()),
+    }
 }
 
 #[cfg(not(wasmpack_target = "nodejs"))]
diff --git a/ng-wallet/src/lib.rs b/ng-wallet/src/lib.rs
index bd60573..2f25d11 100644
--- a/ng-wallet/src/lib.rs
+++ b/ng-wallet/src/lib.rs
@@ -548,6 +548,7 @@ pub fn create_wallet_first_step_v0(
         core_bootstrap: params.core_bootstrap.clone(),
         core_registration: params.core_registration,
         additional_bootstrap: params.additional_bootstrap.clone(),
+        pdf: params.pdf,
     };
     Ok(intermediary)
 }
@@ -773,6 +774,7 @@ pub async fn create_wallet_second_step_v0(
             user,
             in_memory: params.in_memory,
             session_id: 0,
+            pdf_file: vec![],
         },
         site,
         brokers,
@@ -832,6 +834,8 @@ mod test {
             BootstrapContentV0::new_localhost(PubKey::nil()),
             None,
             None,
+            false,
+            "test".to_string(),
         ))
         .expect("create_wallet_first_step_v0");
 
diff --git a/ng-wallet/src/types.rs b/ng-wallet/src/types.rs
index 90d38ee..f18a89d 100644
--- a/ng-wallet/src/types.rs
+++ b/ng-wallet/src/types.rs
@@ -1248,6 +1248,14 @@ pub struct CreateWalletV0 {
     #[zeroize(skip)]
     /// Bootstrap of another server that you might use in order to connect to NextGraph network. It can be another interface on the same `core` server.
     pub additional_bootstrap: Option<BootstrapContentV0>,
+
+    #[zeroize(skip)]
+    /// Should generate a recovery PDF containing all the information of the wallet in plain text.
+    pub pdf: bool,
+
+    #[zeroize(skip)]
+    /// short name of the device
+    pub device_name: String,
 }
 
 impl CreateWalletV0 {
@@ -1261,6 +1269,8 @@ impl CreateWalletV0 {
         core_bootstrap: BootstrapContentV0,
         core_registration: Option<[u8; 32]>,
         additional_bootstrap: Option<BootstrapContentV0>,
+        pdf: bool,
+        device_name: String,
     ) -> Self {
         CreateWalletV0 {
             result_with_wallet_file: false,
@@ -1274,6 +1284,8 @@ impl CreateWalletV0 {
             core_bootstrap,
             core_registration,
             additional_bootstrap,
+            pdf,
+            device_name,
         }
     }
 }
@@ -1324,6 +1336,10 @@ pub struct CreateWalletResultV0 {
     pub in_memory: bool,
 
     pub session_id: u64,
+
+    #[serde(with = "serde_bytes")]
+    /// The PDF file that can be printed by the user
+    pub pdf_file: Vec<u8>,
 }
 
 impl CreateWalletResultV0 {
@@ -1369,6 +1385,8 @@ pub struct CreateWalletIntermediaryV0 {
     pub core_registration: Option<[u8; 32]>,
     #[zeroize(skip)]
     pub additional_bootstrap: Option<BootstrapContentV0>,
+    #[zeroize(skip)]
+    pub pdf: bool,
 }
 
 #[derive(Debug, Eq, PartialEq, Clone)]
@@ -1441,8 +1459,9 @@ pub struct NgQRCodeWalletTransferV0 {
     pub is_rendezvous: bool,
 }
 
-#[derive(Clone, Debug, Serialize, Deserialize)]
+#[derive(Clone, Debug, Zeroize, ZeroizeOnDrop, Serialize, Deserialize)]
 pub struct NgQRCodeWalletRecoveryV0 {
+    #[zeroize(skip)]
     pub wallet: WalletContentV0, //of which security_img is emptied
     pub pazzle: Vec<u8>,
     pub mnemonic: [u16; 12],