Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

--restore-wallet restores words from file #295

Merged
merged 2 commits into from
Nov 5, 2024

Conversation

Psycho-Pirate
Copy link
Contributor

Added functionality to store words everytime new wallet is generated or wallet is restored by user.

--restore-wallet reads from the file if it exists, otherwise asks for words from user.

Copy link
Owner

@vincenzopalazzo vincenzopalazzo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

light review, I should checkout the branch! but in the meanwhile I left some comments

@Harshit933
Copy link
Collaborator

There is some problem with the wallet design as of now. If there is a wallet that already exists in the .lampo/regtest/wallet.dat and if we accidently run lampod-cli --network <chain> the phrase in the wallet.dat file is replaced with a freshly generated one [This is a major issue], maybe we can check if the wallet.dat file already exists then don't proceed with generating new phrases, could be related to #296. Also, I think we should password protect the file and write only the encrypted seed phrase in the wallet.dat file but it could be a work for another PR.

@vincenzopalazzo
Copy link
Owner

I played with the code, what is wrong with my solution? @Psycho-Pirate

It solved some problems and removed the mnemonic as a Option type because there was some problem with the write and read the file.

However, I am open to discussing if there is any case that I am not keeping into count?

Probably this is also the problem that @Harshit933 point out inside the review

P.S: At this stage of lampo we should refactor the code and make it better, and not try to work with the current codebase, because most of the time there is no optimal code.


The best option to see the diff in action is to use git apply <file>

diff --git a/lampod-cli/src/main.rs b/lampod-cli/src/main.rs
index 5864816..c9a2d8f 100644
--- a/lampod-cli/src/main.rs
+++ b/lampod-cli/src/main.rs
@@ -48,33 +48,29 @@ fn main() -> error::Result<()> {
     Ok(())
 }

-fn write_words_to_file<P: AsRef<Path>>(path: P, words: Option<String>) -> io::Result<()> {
+fn write_words_to_file<P: AsRef<Path>>(path: P, words: String) -> error::Result<()> {
     let mut file = OpenOptions::new()
         .write(true)
         .create(true)
         .truncate(true)
         .open(path)?;

-    match words {
-        Some(ref value) => {
-            file.write_all(value.as_bytes())?;
-        }
-        None => {}
-    }
-
+    // FIXME: we should give the possibility to encrypt this file.
+    file.write_all(words.as_bytes())?;
     Ok(())
 }

-fn load_words_from_file<P: AsRef<Path>>(path: P) -> io::Result<Option<String>> {
-    let mut file = File::open(path)?;
+fn load_words_from_file<P: AsRef<Path>>(path: P) -> error::Result<String> {
+    let mut file = File::open(path.as_ref())?;
     let mut content = String::new();

     file.read_to_string(&mut content)?;

     if content.is_empty() {
-        Ok(None)
+        let path = path.as_ref().to_string_lossy().to_string();
+        error::bail!("The content of the wallet located at `{path}`. You lost the secret? Please report a bug this should never happens")
     } else {
-        Ok(Some(content))
+        Ok(content)
     }
 }

@@ -85,24 +81,6 @@ fn run(args: LampoCliArgs) -> error::Result<()> {
     // After this point the configuration is ready!
     let mut lampo_conf: LampoConf = args.try_into()?;

-    let words_path = format!("{}/", lampo_conf.path());
-    let mnemonic = if restore_wallet {
-        if Path::new(&format!("{}/wallet.dat", words_path)).exists() {
-            // Load the mnemonic from the file
-            load_words_from_file(format!("{}/wallet.dat", words_path))?
-        } else {
-            // If file doesn't exist, ask for user input
-            let inputs: String = term::input(
-                "BIP 39 Mnemonic",
-                None,
-                Some("To restore the wallet, lampo needs the BIP39 mnemonic with words separated by spaces."),
-            )?;
-            Some(inputs)
-        }
-    } else {
-        None
-    };
-
     log::debug!(target: "lampod-cli", "init wallet ..");
     // init the logger here
     logger::init(
@@ -141,47 +119,90 @@ fn run(args: LampoCliArgs) -> error::Result<()> {
         _ => error::bail!("client {:?} not supported", client),
     };

-    let wallet = if let Some(ref _private_key) = lampo_conf.private_key {
-        unimplemented!()
-    } else if mnemonic.is_none() {
-        let (wallet, mnemonic) = match client.kind() {
-            lampo_common::backend::BackendKind::Core => {
-                CoreWalletManager::new(Arc::new(lampo_conf.clone()))?
-            }
-            lampo_common::backend::BackendKind::Nakamoto => {
-                error::bail!("wallet is not implemented for nakamoto")
-            }
-        };
-
-        radicle_term::success!("Wallet Generated, please store these words in a safe way");
-        radicle_term::println(
-            radicle_term::format::badge_primary("wallet-keys"),
-            format!("{}", radicle_term::format::highlight(&mnemonic)),
-        );
-        write_words_to_file(format!("{}/wallet.dat", words_path), Some(mnemonic))?;
-        wallet
+    if let Some(ref _private_key) = lampo_conf.private_key {
+        error::bail!("Option to force a private key not available at the moment")
+    }
+
+    let words_path = format!("{}/", lampo_conf.path());
+    // There are several case in this if-else, that are:
+    // 1. lampo is running on a fresh os:
+    //   1.1: the user has a wallet, so need to specify `--restore-wallet`
+    //   1.2: the user does not have a wallet so lampo should generate a new seed for it and store it in a file.
+    // 2. lampo is running on os where there is a wallet, lampo will load the seeds from wallet:
+    //   2.1: The user keep specify --restore-wallet, so lampo should return an error an tell the user that there is already a wallet
+    //   2.2: The user do not specify the --restore-wallet, so lampo load from disk the file, if there is no file it return an error
+    // FIXME: there is a problem of code duplication here, we should move this code in utils functions.
+    let wallet = if restore_wallet {
+        if Path::new(&format!("{}/wallet.dat", words_path)).exists() {
+            // Load the mnemonic from the file
+            let mnemonic = load_words_from_file(format!("{}/wallet.dat", words_path))?;
+            let wallet = match client.kind() {
+                lampo_common::backend::BackendKind::Core => {
+                    // SAFETY: It is safe to unwrap the mnemonic because we check it
+                    // before.
+                    CoreWalletManager::restore(Arc::new(lampo_conf.clone()), &mnemonic)?
+                }
+                lampo_common::backend::BackendKind::Nakamoto => {
+                    error::bail!("wallet is not implemented for nakamoto")
+                }
+            };
+            wallet
+        } else {
+            // If file doesn't exist, ask for user input
+            let mnemonic: String = term::input(
+                "BIP 39 Mnemonic",
+                None,
+                Some("To restore the wallet, lampo needs the BIP39 mnemonic with words separated by spaces."),
+            )?;
+            // FIXME: make some sanity check about the mnemonic string
+            let wallet = match client.kind() {
+                lampo_common::backend::BackendKind::Core => {
+                    // SAFETY: It is safe to unwrap the mnemonic because we check it
+                    // before.
+                    CoreWalletManager::restore(Arc::new(lampo_conf.clone()), &mnemonic)?
+                }
+                lampo_common::backend::BackendKind::Nakamoto => {
+                    error::bail!("wallet is not implemented for nakamoto")
+                }
+            };
+
+            write_words_to_file(format!("{}/wallet.dat", words_path), mnemonic)?;
+            wallet
+        }
     } else {
-        match client.kind() {
-            lampo_common::backend::BackendKind::Core => {
-                // SAFETY: It is safe to unwrap the mnemonic because we check it
-                // before.
-                CoreWalletManager::restore(
-                    Arc::new(lampo_conf.clone()),
-                    &mnemonic.clone().unwrap(),
-                )?
-            }
-            lampo_common::backend::BackendKind::Nakamoto => {
-                error::bail!("wallet is not implemented for nakamoto")
-            }
+        // There is a file, so we load it, and we tell the user through logs that we found it
+        if Path::new(&format!("{}/wallet.dat", words_path)).exists() {
+            // Load the mnemonic from the file
+            let mnemonic = load_words_from_file(format!("{}/wallet.dat", words_path))?;
+            let wallet = match client.kind() {
+                lampo_common::backend::BackendKind::Core => {
+                    // SAFETY: It is safe to unwrap the mnemonic because we check it
+                    // before.
+                    CoreWalletManager::restore(Arc::new(lampo_conf.clone()), &mnemonic)?
+                }
+                lampo_common::backend::BackendKind::Nakamoto => {
+                    error::bail!("wallet is not implemented for nakamoto")
+                }
+            };
+            wallet
+        } else {
+            let (wallet, mnemonic) = match client.kind() {
+                lampo_common::backend::BackendKind::Core => {
+                    CoreWalletManager::new(Arc::new(lampo_conf.clone()))?
+                }
+                lampo_common::backend::BackendKind::Nakamoto => {
+                    error::bail!("wallet is not implemented for nakamoto")
+                }
+            };
+
+            write_words_to_file(format!("{}/wallet.dat", words_path), mnemonic)?;
+            wallet
         }
     };
+
     log::debug!(target: "lampod-cli", "wallet created with success");
     let mut lampod = LampoDaemon::new(lampo_conf.clone(), Arc::new(wallet));

-    if !Path::new(&format!("{}/wallet.dat", words_path)).exists() {
-        write_words_to_file(format!("{}/wallet.dat", words_path), mnemonic)?;
-    }
-
     // Init the lampod
     lampod.init(client)?;

@Harshit933
Copy link
Collaborator

@vincenzopalazzo Looks good to me.

@Psycho-Pirate
Copy link
Contributor Author

I played with the code, what is wrong with my solution? @Psycho-Pirate

It solved some problems and removed the mnemonic as a Option type because there was some problem with the write and read the file.

However, I am open to discussing if there is any case that I am not keeping into count?

Probably this is also the problem that @Harshit933 point out inside the review

P.S: At this stage of lampo we should refactor the code and make it better, and not try to work with the current codebase, because most of the time there is no optimal code.

This looks good and covers everything. We can go forward with this approach. But I have some suggestions:

  1. When restore_wallet is false, we are checking for the file and loading the wallet if file exists. I think here we should exit with error saying something like "Wallet already exists. Please use --restore-wallet".

  2. In case we are generating a new wallet we should also display the words generated as we were doing in the existing code.

@vincenzopalazzo
Copy link
Owner

When restore_wallet is false, we are checking for the file and loading the wallet if file exists. I think here we should exit with error saying something like "Wallet already exists. Please use --restore-wallet".

This sounds good to me, thanks for the suggestion.

In case we are generating a new wallet we should also display the words generated as we were doing in the existing code.

This is a privacy leak and we should avoid it. Then we should add a command in lampod-cli show-bip39 that return the words back, what do you think about this?

@Psycho-Pirate
Copy link
Contributor Author

This is a privacy leak and we should avoid it. Then we should add a command in lampod-cli show-bip39 that return the words back, what do you think about this?

I agree! So I will go ahead with this approach and I will add the command lampod-cli show-bip39 in a followup PR

Copy link
Owner

@vincenzopalazzo vincenzopalazzo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok two comment and we are ready to go, sorry about the confusion here anyway!

// before.
CoreWalletManager::restore(Arc::new(lampo_conf.clone()), &mnemonic)?
}
lampo_common::backend::BackendKind::Nakamoto => {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same consideration here for a folloup PR

}
// If there is a file, we tell the user to use --restore-wallet
if Path::new(&format!("{}/wallet.dat", words_path)).exists() {
error::bail!("Wallet already exists. Please use --restore-wallet")
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I reconsider my self here. This make scripting or using systemd in linux much harder because it is not that trivial run two flavor of command.

Probably you can print a warning message here? and say that lampo is restarting an existing wallet?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay

Copy link
Owner

@vincenzopalazzo vincenzopalazzo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another iteration and probably we are good

if Path::new(&format!("{}/wallet.dat", words_path)).exists() {
error::bail!("Wallet already exists. Please use --restore-wallet")
// Load the mnemonic from the file
radicle_term::warning("Loading from existing wallet");
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, use log::warn("Loading from existing wallet");

@vincenzopalazzo
Copy link
Owner

@Psycho-Pirate when you are ready please reassign my review but I think I self review that you do yourself on the code would be good too.

In addition, we need to work on how you present the git history (6 comment for a git apply <patch> that I give are too many https://github.com/vincenzopalazzo/lampo.rs/pull/295/commits)

I think this is a good reading https://cbea.ms/git-commit/ and this is a bad example of commit history that cause a close of the PR #284 (comment)

@Psycho-Pirate
Copy link
Contributor Author

Should I also squash the commits into one?

@vincenzopalazzo
Copy link
Owner

Should I also squash the commits into one?

Up to you, the history should make sense and be easy to cherry-pick, probably this is a good reading https://www.kernel.org/doc/html/v4.10/process/submitting-patches.html#separate-your-changes

@Psycho-Pirate
Copy link
Contributor Author

Squashed it to two commits for cleaner commit history

Copy link
Owner

@vincenzopalazzo vincenzopalazzo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACK aca1633

Assuming that you tested out 😸

@vincenzopalazzo vincenzopalazzo merged commit 0d3a954 into vincenzopalazzo:main Nov 5, 2024
3 of 5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants