Repository: opentensor/btwallet (language: python)

Issue:
return encrypted password, remove debug
You can store your password with the following code:
```python
from bittensor_wallet import Wallet

my_wallet = Wallet()

# the same approach you can use `for hotkey_file` if this is encrypted.
boolean_result = my_wallet.coldkey_file.save_password_to_env("mypassword")
```

In `bittensor-wallet` version 2.0.2 method `save_password_to_env` returns `True` in successful case and `False` if something wrong. I gonna replace the return result to encrypted password. Then you can get it like this:
```python
from bittensor_wallet import Wallet

my_wallet = Wallet()

my_encrypted_password = my_wallet.coldkey_file.save_password_to_env("mypassword")
```
set_coldkeypub arguments passed in wrong order
Method called with `overwrite=overwrite, encrypt=false`, but the method accepts these two arguments in the other order - `encrypt` first, then `overwrite`:

https://github.com/opentensor/btwallet/blob/main/src/wallet.rs#L831
```rust
self.set_coldkeypub(keypair, overwrite, false, py)?;
```

https://github.com/opentensor/btwallet/blob/main/src/wallet.rs#L515-L521
```rust
pub fn set_coldkeypub(
    &mut self,
    keypair: Keypair,
    encrypt: bool,
    overwrite: bool,
    py: Python,
) -> PyResult<()> {
```



Discussion / hints:

Fix patch:
diff --git a/.circleci/config.yml b/.circleci/config.yml
index e0dfd44..c2e1053 100755
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -33,6 +33,8 @@ jobs:
       - run:
           name: Set Up Virtual Environment
           command: |
+            curl https://sh.rustup.rs -sSf | sh -s -- -y
+            . "$HOME/.cargo/env"
             python -m venv .venv
             . .venv/bin/activate
             python -m pip install --upgrade pip
diff --git a/CHANGELOG.MD b/CHANGELOG.MD
index f87d439..e2032af 100644
--- a/CHANGELOG.MD
+++ b/CHANGELOG.MD
@@ -5,6 +5,9 @@
 ## What's Changed
 
 * Add the ability to use environment variables to store hot/coldkey passwords by @roman-opentensor in https://github.com/opentensor/btwallet/pull/46
+* Fix wallet paths outside userhome by @camfairchild in https://github.com/opentensor/btwallet/pull/59
+* fix https://github.com/opentensor/btwallet/issues/51 by @camfairchild in https://github.com/opentensor/btwallet/pull/56
+* password fixes by @roman-opentensor 
 * fix/roman/fix-config-parsing by @roman-opentensor in https://github.com/opentensor/btwallet/pull/47
 
 **Full Changelog**: https://github.com/opentensor/btwallet/compare/v2.0.1...v2.0.2
diff --git a/bittensor_wallet/__init__.py b/bittensor_wallet/__init__.py
index 78d8376..b58bbfe 100644
--- a/bittensor_wallet/__init__.py
+++ b/bittensor_wallet/__init__.py
@@ -1,16 +1,16 @@
 from bittensor_wallet.bittensor_wallet import (
     # classes
-    Config,
-    Keyfile,
-    Keypair,
-    Wallet,
+    Config as Config,
+    Keyfile as Keyfile,
+    Keypair as Keypair,
+    Wallet as Wallet,
     # modules
-    config,
-    errors,
-    keyfile,
-    keypair,
-    utils,
-    wallet,
+    config as config,
+    errors as errors,
+    keyfile as keyfile,
+    keypair as keypair,
+    utils as utils,
+    wallet as wallet,
 )
 
 __version__ = "2.0.0"
diff --git a/bittensor_wallet/errors/__init__.py b/bittensor_wallet/errors/__init__.py
index 9bd501f..2e86768 100644
--- a/bittensor_wallet/errors/__init__.py
+++ b/bittensor_wallet/errors/__init__.py
@@ -2,3 +2,4 @@
 
 ConfigurationError = _.ConfigurationError
 KeyFileError = _.KeyFileError
+PasswordError = _.PasswordError
diff --git a/pyproject.toml b/pyproject.toml
index e079831..ebc9d2d 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -10,7 +10,7 @@ dependencies = [
     "termcolor",
     "eth-utils<2.3.0",
     "password_strength",
-    "cryptography~=42.0.5",
+    "cryptography~=43.0.1",
     "ansible~=6.7",
     "ansible_vault~=2.1",
     "munch~=2.5.0",
diff --git a/src/errors.rs b/src/errors.rs
index 0ee77e8..3e95fa3 100644
--- a/src/errors.rs
+++ b/src/errors.rs
@@ -2,13 +2,14 @@ use pyo3::exceptions::PyException;
 use pyo3::prelude::*;
 use std::{error, fmt};
 
+// KeyFileError
 #[pyclass(extends=PyException)]
 #[derive(Debug)]
 pub struct KeyFileError {
     pub message: String,
 }
 
-/// Error thrown when the keyfile is corrupt, non-writable, non-readable or the password used to decrypt is invalid.
+/// Error thrown when the keyfile is corrupt, non-writable, non-readable.
 #[pymethods]
 impl KeyFileError {
     #[new]
@@ -31,6 +32,7 @@ impl fmt::Display for KeyFileError {
 
 impl error::Error for KeyFileError {}
 
+// ConfigurationError
 #[pyclass(extends=PyException)]
 #[derive(Debug)]
 pub struct ConfigurationError {
@@ -59,3 +61,33 @@ impl fmt::Display for ConfigurationError {
 }
 
 impl error::Error for ConfigurationError {}
+
+// PasswordError
+#[pyclass(extends=PyException)]
+#[derive(Debug)]
+pub struct PasswordError {
+    pub message: String,
+}
+
+/// PasswordError occurs if the password used for decryption is invalid.
+#[pymethods]
+impl PasswordError {
+    #[new]
+    #[pyo3(signature = (message=None))]
+    pub fn new(message: Option<String>) -> Self {
+        let msg = message.unwrap_or_default();
+        PasswordError { message: msg }
+    }
+
+    pub fn __str__(&self) -> String {
+        self.message.clone()
+    }
+}
+
+impl fmt::Display for PasswordError {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(f, "PasswordError: {}", self.message)
+    }
+}
+
+impl error::Error for PasswordError {}
diff --git a/src/keyfile.rs b/src/keyfile.rs
index 829dd70..c6a7bbe 100644
--- a/src/keyfile.rs
+++ b/src/keyfile.rs
@@ -19,7 +19,7 @@ use passwords::analyzer;
 use passwords::scorer;
 use serde_json::json;
 
-use crate::errors::KeyFileError;
+use crate::errors::{KeyFileError, PasswordError};
 use crate::keypair::Keypair;
 use crate::utils;
 
@@ -414,7 +414,7 @@ pub fn decrypt_keyfile_data(
             .ok_or(PyErr::new::<PyRuntimeError, _>("Invalid nonce."))?;
         let ciphertext = &data[secretbox::NONCEBYTES..];
         secretbox::open(ciphertext, &nonce, key)
-            .map_err(|_| PyErr::new::<KeyFileError, _>("Wrong password for nacl decryption."))
+            .map_err(|_| PyErr::new::<PasswordError, _>("Wrong password for nacl decryption."))
     }
 
     // decrypt of keyfile_data with legacy way
@@ -428,7 +428,7 @@ pub fn decrypt_keyfile_data(
         let keyfile_data_str = from_utf8(keyfile_data)?;
         fernet
             .decrypt(keyfile_data_str)
-            .map_err(|_| PyErr::new::<KeyFileError, _>("Wrong password for nacl decryption."))
+            .map_err(|_| PyErr::new::<PasswordError, _>("Wrong password for legacy decryption."))
     }
 
     let mut password = password;
@@ -453,21 +453,21 @@ pub fn decrypt_keyfile_data(
     if keyfile_data_is_encrypted_nacl(py, keyfile_data)? {
         let key = derive_key(password.as_bytes());
         let decrypted_data = nacl_decrypt(keyfile_data, &key)
-            .map_err(|_| PyErr::new::<KeyFileError, _>("Wrong password for decryption."))?;
+            .map_err(|_| PyErr::new::<PasswordError, _>("Wrong password for decryption."))?;
         return Ok(PyBytes::new_bound(py, &decrypted_data).into_py(py));
     }
 
     // Ansible Vault decryption
     if keyfile_data_is_encrypted_ansible(py, keyfile_data)? {
         let decrypted_data = decrypt_vault(keyfile_data, password.as_str())
-            .map_err(|_| PyErr::new::<KeyFileError, _>("Wrong password for decryption."))?;
+            .map_err(|_| PyErr::new::<PasswordError, _>("Wrong password for decryption."))?;
         return Ok(PyBytes::new_bound(py, &decrypted_data).into_py(py));
     }
 
     // Legacy decryption
     if keyfile_data_is_encrypted_legacy(py, keyfile_data)? {
         let decrypted_data = legacy_decrypt(&password, keyfile_data)
-            .map_err(|_| PyErr::new::<KeyFileError, _>("Wrong password for decryption."))?;
+            .map_err(|_| PyErr::new::<PasswordError, _>("Wrong password for decryption."))?;
         return Ok(PyBytes::new_bound(py, &decrypted_data).into_py(py));
     }
 
@@ -498,10 +498,6 @@ fn encrypt_password(key: String, value: String) -> String {
         let encrypted_char = (c as u8) ^ (key.chars().nth(i % key.len()).unwrap() as u8);
         encrypted.push(encrypted_char as char);
     }
-    println!(
-        ">>> key: {}, value: {}, encrypted: {}",
-        key, value, encrypted
-    );
     encrypted
 }
 
@@ -1048,7 +1044,7 @@ impl Keyfile {
 
     /// Saves the key's password to the associated local environment variable.
     #[pyo3(signature = (password=None))]
-    fn save_password_to_env(&self, password: Option<String>, py: Python) -> PyResult<bool> {
+    fn save_password_to_env(&self, password: Option<String>, py: Python) -> PyResult<String> {
         // checking the password
         let password = match password {
             Some(pwd) => pwd,
@@ -1056,7 +1052,7 @@ impl Keyfile {
                 Ok(pwd) => pwd,
                 Err(e) => {
                     utils::print(format!("Error asking password: {:?}.\n", e));
-                    return Ok(false);
+                    return Ok("".parse()?);
                 }
             },
         };
@@ -1066,21 +1062,21 @@ impl Keyfile {
                 // encrypt password
                 let encrypted_password = encrypt_password(self.env_var_name()?, password);
                 // store encrypted password
-                env::set_var(&env_var_name, encrypted_password);
+                env::set_var(&env_var_name, &encrypted_password);
 
                 let message = format!(
                     "The password has been saved to environment variable '{}'.\n",
                     env_var_name
                 );
                 utils::print(message);
-                Ok(true)
+                Ok(encrypted_password)
             }
             Err(e) => {
                 utils::print(format!(
                     "Error saving environment variable name: {:?}.\n",
                     e
                 ));
-                Ok(false)
+                Ok("".parse()?)
             }
         }
     }
diff --git a/src/lib.rs b/src/lib.rs
index 98db472..c54af28 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -36,6 +36,7 @@ fn register_errors_module(main_module: &Bound<'_, PyModule>) -> PyResult<()> {
     let errors_module = PyModule::new_bound(main_module.py(), "errors")?;
     errors_module.add_class::<errors::ConfigurationError>()?;
     errors_module.add_class::<errors::KeyFileError>()?;
+    errors_module.add_class::<errors::PasswordError>()?;
     main_module.add_submodule(&errors_module)
 }
 


Test patch:
diff --git a/tests/test_keyfile.py b/tests/test_keyfile.py
index 182e9f1..2663c70 100644
--- a/tests/test_keyfile.py
+++ b/tests/test_keyfile.py
@@ -23,7 +23,6 @@
 
 import pytest
 from bip39 import bip39_validate
-from jedi.common import monkeypatch
 
 from bittensor_wallet.errors import ConfigurationError, KeyFileError
 from bittensor_wallet.keyfile import Keyfile