Wallet Seed Encryption + BIP39 (#2004)
* add encryption/decryption of wallet seed * rustfmt * update passphrase to encrypt/decrypt wallet * rustfmt * add conversion for old wallet seeds * add ability to restore old seed * rustfmt * add bip39 recovery phrase support, conversion from existing seed formats * rustfmt * error recovery * rustfmt * add password prompts where required * rustfmt * add note specifying wallet conversion * update documentation * doc update
This commit is contained in:
Generated
+13
@@ -687,6 +687,7 @@ dependencies = [
|
||||
"humansize 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"reqwest 0.9.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rpassword 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.80 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_json 1.0.33 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tar 0.4.20 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -954,6 +955,7 @@ dependencies = [
|
||||
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"prettytable-rs 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ring 0.13.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.80 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_derive 1.0.80 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_json 1.0.33 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -1973,6 +1975,16 @@ dependencies = [
|
||||
"digest 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rpassword"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.9"
|
||||
@@ -3009,6 +3021,7 @@ dependencies = [
|
||||
"checksum reqwest 0.9.5 (registry+https://github.com/rust-lang/crates.io-index)" = "ab52e462d1e15891441aeefadff68bdea005174328ce3da0a314f2ad313ec837"
|
||||
"checksum ring 0.13.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7ed733c36010c3d4d4718588f16a6c06a670b01c0047029ae81c3ca0acd81ff5"
|
||||
"checksum ripemd160 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "482aa56cc68aaeccdaaff1cc5a72c247da8bbad3beb174ca5741f274c22883fb"
|
||||
"checksum rpassword 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d127299b02abda51634f14025aec43ae87a7aa7a95202b6a868ec852607d1451"
|
||||
"checksum rustc-demangle 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "bcfe5b13211b4d78e5c2cadfebd7769197d95c639c35a50057eb4c05de811395"
|
||||
"checksum rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)" = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda"
|
||||
"checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
|
||||
|
||||
@@ -29,6 +29,7 @@ serde = "1"
|
||||
serde_json = "1"
|
||||
log = "0.4"
|
||||
term = "0.5"
|
||||
rpassword = "2.0.0"
|
||||
|
||||
grin_api = { path = "./api", version = "0.4.1" }
|
||||
grin_config = { path = "./config", version = "0.4.1" }
|
||||
|
||||
+61
-21
@@ -18,8 +18,9 @@ When creating a new wallet, the file structure should be:
|
||||
* `grin-wallet.toml` contains configuration information for the wallet. You can modify values within
|
||||
to change ports, the address of your grin node, or logging values.
|
||||
|
||||
* `wallet_data/wallet.seed` is your master seed file. You must back this file up somewhere in order to
|
||||
be able to recover or restore your wallet (along with its password, if given).
|
||||
* `wallet_data/wallet.seed` is your master seed file. Its contents are encrypted with your password (required).
|
||||
You should back this file up somewhere in order to be able to recover or restore your wallet. Your seed file
|
||||
can also be recovered using a seed phrase if you lose this file or forget your password.
|
||||
|
||||
### Data Directory
|
||||
|
||||
@@ -59,12 +60,12 @@ and the results verified against the latest chain information.
|
||||
|
||||
##### Password
|
||||
|
||||
All keys generated by your wallet are combinations of the master seed + a password. If no password is provided, it's assumed this
|
||||
password is blank. If you do provide a password, all operations will use the seed+password and you will need the password to view or
|
||||
spend any generated outputs. The password is specified with `-p`
|
||||
Your wallet.seed file, which contains your wallet's unique master seed, is encrypted with your password. Your password is specified
|
||||
at wallet creation time, and must be provided for any wallet operation. You will be prompted for your password when required, but
|
||||
you can also specify it on the command line by providing the `-p`argument.
|
||||
|
||||
```sh
|
||||
[host]$ grin wallet -p mypassword info
|
||||
[host]$ grin wallet -p mypass info
|
||||
```
|
||||
|
||||
## Basic Wallet Commands
|
||||
@@ -74,22 +75,62 @@ spend any generated outputs. The password is specified with `-p`
|
||||
|
||||
### init
|
||||
|
||||
Before using a wallet a new `grin-wallet.toml` configuration file, seed file `wallet.seed` and storage database need
|
||||
Before using a wallet a new `grin-wallet.toml` configuration file, master seed contained in `wallet.seed` and storage database need
|
||||
to be generated via the init command as follows:
|
||||
|
||||
By default this will place your wallet files into `~/.grin`. It is VERY IMPORTANT that you back up the `~/.grin/wallet_data/wallet.seed`
|
||||
file somewhere safe and private, and ensure you somehow remember the password used to generate the wallet.
|
||||
```sh
|
||||
[host]$ grin wallet init
|
||||
```
|
||||
|
||||
Alternatively, if you'd like to run a wallet in a directory other than the default, you can run:
|
||||
You will be prompted to enter a password for the new wallet. By default, your wallet files will be placed into `~/.grin`. Alternatively,
|
||||
if you'd like to run a wallet in a directory other than the default, you can run:
|
||||
|
||||
```sh
|
||||
[host]$ grin wallet [-p password] init -h
|
||||
[host]$ grin wallet -p mypass init -h
|
||||
```
|
||||
|
||||
This will create a `grin-wallet.toml` file in the current directory configured to use the data files in the current directory,
|
||||
as well as all needed data files. When running any `grin wallet` command, grin will check the current directory to see if
|
||||
a `grin-wallet.toml` file exists. If not it will use the default in `~/.grin`
|
||||
|
||||
The init command will also print a 24 (or 12) word recovery phrase, which you should write down and store in a non-digital format. This
|
||||
phrase can be used to re-create your master seed file if it gets lost or corrupted, or you forget the wallet password. If you'd prefer
|
||||
to use a 12-word recovery phrase, you can also pass in the `--short_wordlist` or `-s` parameter.
|
||||
|
||||
It is also highly recommended that you back up the `~/.grin/wallet_data/wallet.seed` file somewhere safe and private,
|
||||
and ensure you somehow remember the password used to encrypt the wallet seed file.
|
||||
|
||||
### recover
|
||||
|
||||
The `recover` command is used to regenerate your wallet seed file from your recovery phrase. Note that this operation only
|
||||
restores your seed file, not the outputs stored in your wallet. If, for instance, you forget your wallet password, you can
|
||||
delete the `wallet_data/wallet.seed` file from your wallet data directory, run `grin wallet recover`, and (provided you used
|
||||
the correct recovery phrase,) your wallet contents should again be usable.
|
||||
|
||||
To recover your wallet seed, delete (or backup) the wallet's `wallet_data/wallet.seed` file, then run:
|
||||
|
||||
```sh
|
||||
[host]$ grin wallet recover -p "[12 or 24 word passphrase separated by spaces"
|
||||
|
||||
e.g:
|
||||
|
||||
[host]$ grin wallet recover -p "shiver alarm excuse turtle absorb surface lunch virtual want remind hard slow vacuum park silver asthma engage library battle jelly buffalo female inquiry wire"
|
||||
```
|
||||
|
||||
If you're restoring a wallet from scratch, you'll then need to use the `grin wallet restore` command to scan the chain
|
||||
for your outputs and restore them. See the `grin wallet restore` command below for details of the entire process.
|
||||
|
||||
You can also view your recovery phrase with your password by running the recover command without any arguments, e.g:
|
||||
|
||||
|
||||
```sh
|
||||
[host]$ grin wallet recover
|
||||
Password:
|
||||
Your recovery phrase is:
|
||||
shiver alarm excuse turtle absorb surface lunch virtual want remind hard slow vacuum park silver asthma engage library battle jelly buffalo female inquiry wire
|
||||
Please back-up these words in a non-digital format.
|
||||
```
|
||||
|
||||
### account
|
||||
|
||||
To create a new account, use the 'grin wallet account' command with the argument '-c', e.g.:
|
||||
@@ -296,7 +337,7 @@ Wallet Outputs - Block Height: 49
|
||||
|
||||
#### cancel
|
||||
|
||||
Everything before Step 6 in the send phase above happens completely locally in the wallets' data storage and separately from the chain.
|
||||
Everything before Step 6 in the send phase above happens completely locally in the wallets' data storage and separately from the chain.
|
||||
Since it's very easy for a sender, (through error or malice,) to fail to post a transaction to the chain, it's very possible for the contents
|
||||
of a wallet to become locked, with all outputs unable to be selected because the wallet is waiting for a transaction that will never hit
|
||||
the chain to complete. For example, in the output from `grin wallet txs -i 6` above, the transaction is showing as `confirmed == false`
|
||||
@@ -342,7 +383,7 @@ This will create a file called tx_3.json containing your raw transaction data. N
|
||||
|
||||
##### restore
|
||||
|
||||
If for some reason the wallet cancel commands above don't work, or you need to restore from a backed up `wallet.seed` file and password, you can perform a full wallet restore.
|
||||
If for some reason the wallet cancel commands above don't work, you need to restore from a backed up `wallet.seed` file and password, or have recovered the wallet seed from a recovery phrase, you can perform a full wallet restore.
|
||||
|
||||
To do this, generate an empty wallet somewhere with:
|
||||
|
||||
@@ -357,18 +398,17 @@ Delete the newly generated wallet data directory and seed file:
|
||||
[host@new_wallet_dir]# rm wallet_data/wallet.seed
|
||||
```
|
||||
|
||||
Then copy your backed up `wallet.seed` file into the new `wallet_data` directory, ensuring it's called `wallet.seed`
|
||||
If you need to recover your wallet seed from a recovery phrase, use the `grin wallet recover -p "[recovery phrase]" command
|
||||
as outlined above. Otherwise, if you're restoring from a backed-up seed file, simply copy your backed up `wallet.seed` file
|
||||
into the new `wallet_data` directory, ensuring it's called `wallet.seed`
|
||||
|
||||
```sh
|
||||
[host@new_wallet_dir]# cp OLD_WALLET.seed wallet_data/wallet.seed
|
||||
```
|
||||
|
||||
Then ensure that you're running a grin node, and makes sure nothing is attempting to mine into your wallet. Then, in the
|
||||
wallet directory:
|
||||
Ensure the Grin node with which your wallet is talking is running, and make sure nothing is attempting to mine into your wallet.
|
||||
Then, in the wallet directory:
|
||||
|
||||
```sh
|
||||
grin wallet restore
|
||||
```
|
||||
|
||||
Note this operation can potentially take a long time. Once it's done, your wallet outputs should be restored, and you can
|
||||
transact with your restored wallet as before the backup.
|
||||
transact with your restored wallet as before the backup. Your transaction log history is not restored, and will simply
|
||||
contain incoming transactions for each output found.
|
||||
|
||||
@@ -43,6 +43,16 @@ impl Keychain for ExtKeychain {
|
||||
Ok(keychain)
|
||||
}
|
||||
|
||||
fn from_mnemonic(word_list: &str, extension_word: &str) -> Result<Self, Error> {
|
||||
let secp = secp::Secp256k1::with_caps(secp::ContextFlag::Commit);
|
||||
let master = ExtendedPrivKey::from_mnemonic(&secp, word_list, extension_word)?;
|
||||
let keychain = ExtKeychain {
|
||||
secp: secp,
|
||||
master: master,
|
||||
};
|
||||
Ok(keychain)
|
||||
}
|
||||
|
||||
/// For testing - probably not a good idea to use outside of tests.
|
||||
fn from_random_seed() -> Result<ExtKeychain, Error> {
|
||||
let seed: String = thread_rng().sample_iter(&Alphanumeric).take(16).collect();
|
||||
@@ -85,8 +95,7 @@ impl Keychain for ExtKeychain {
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
}).collect();
|
||||
|
||||
let mut neg_keys: Vec<SecretKey> = blind_sum
|
||||
.negative_key_ids
|
||||
@@ -98,8 +107,7 @@ impl Keychain for ExtKeychain {
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
}).collect();
|
||||
|
||||
pos_keys.extend(
|
||||
&blind_sum
|
||||
@@ -220,8 +228,7 @@ mod test {
|
||||
&BlindSum::new()
|
||||
.add_blinding_factor(BlindingFactor::from_secret_key(skey1))
|
||||
.add_blinding_factor(BlindingFactor::from_secret_key(skey2))
|
||||
)
|
||||
.unwrap(),
|
||||
).unwrap(),
|
||||
BlindingFactor::from_secret_key(skey3),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -432,6 +432,7 @@ impl ExtKeychainPath {
|
||||
|
||||
pub trait Keychain: Sync + Send + Clone {
|
||||
fn from_seed(seed: &[u8]) -> Result<Self, Error>;
|
||||
fn from_mnemonic(word_list: &str, extension_word: &str) -> Result<Self, Error>;
|
||||
fn from_random_seed() -> Result<Self, Error>;
|
||||
fn root_key_id() -> Identifier;
|
||||
fn derive_key_id(depth: u8, d1: u32, d2: u32, d3: u32, d4: u32) -> Identifier;
|
||||
|
||||
@@ -263,7 +263,7 @@ impl LocalServerContainer {
|
||||
self.wallet_config.data_file_dir = self.working_dir.clone();
|
||||
|
||||
let _ = fs::create_dir_all(self.wallet_config.clone().data_file_dir);
|
||||
let r = wallet::WalletSeed::init_file(&self.wallet_config);
|
||||
let r = wallet::WalletSeed::init_file(&self.wallet_config, 32, "");
|
||||
|
||||
let client_n = HTTPNodeClient::new(&self.wallet_config.check_node_api_http_addr, None);
|
||||
|
||||
@@ -295,9 +295,9 @@ impl LocalServerContainer {
|
||||
|
||||
pub fn get_wallet_seed(config: &WalletConfig) -> wallet::WalletSeed {
|
||||
let _ = fs::create_dir_all(config.clone().data_file_dir);
|
||||
wallet::WalletSeed::init_file(config).unwrap();
|
||||
wallet::WalletSeed::init_file(config, 32, "").unwrap();
|
||||
let wallet_seed =
|
||||
wallet::WalletSeed::from_file(config).expect("Failed to read wallet seed file.");
|
||||
wallet::WalletSeed::from_file(config, "").expect("Failed to read wallet seed file.");
|
||||
wallet_seed
|
||||
}
|
||||
|
||||
@@ -306,7 +306,7 @@ impl LocalServerContainer {
|
||||
wallet_seed: &wallet::WalletSeed,
|
||||
) -> wallet::WalletInfo {
|
||||
let keychain: keychain::ExtKeychain = wallet_seed
|
||||
.derive_keychain("")
|
||||
.derive_keychain()
|
||||
.expect("Failed to derive keychain from seed file and passphrase.");
|
||||
let client_n = HTTPNodeClient::new(&config.check_node_api_http_addr, None);
|
||||
let mut wallet = LMDBBackend::new(config.clone(), "", client_n)
|
||||
@@ -329,10 +329,10 @@ impl LocalServerContainer {
|
||||
.expect("Could not parse amount as a number with optional decimal point.");
|
||||
|
||||
let wallet_seed =
|
||||
wallet::WalletSeed::from_file(config).expect("Failed to read wallet seed file.");
|
||||
wallet::WalletSeed::from_file(config, "").expect("Failed to read wallet seed file.");
|
||||
|
||||
let keychain: keychain::ExtKeychain = wallet_seed
|
||||
.derive_keychain("")
|
||||
.derive_keychain()
|
||||
.expect("Failed to derive keychain from seed file and passphrase.");
|
||||
|
||||
let client_n = HTTPNodeClient::new(&config.check_node_api_http_addr, None);
|
||||
|
||||
@@ -883,7 +883,7 @@ pub fn create_wallet(
|
||||
) -> Arc<Mutex<WalletInst<HTTPNodeClient, keychain::ExtKeychain>>> {
|
||||
let mut wallet_config = WalletConfig::default();
|
||||
wallet_config.data_file_dir = String::from(dir);
|
||||
let _ = wallet::WalletSeed::init_file(&wallet_config);
|
||||
let _ = wallet::WalletSeed::init_file(&wallet_config, 32, "");
|
||||
let mut wallet: LMDBBackend<HTTPNodeClient, keychain::ExtKeychain> =
|
||||
LMDBBackend::new(wallet_config.clone(), "", client_n).unwrap_or_else(|e| {
|
||||
panic!("Error creating wallet: {:?} Config: {:?}", e, wallet_config)
|
||||
|
||||
+122
-20
@@ -13,6 +13,7 @@
|
||||
// limitations under the License.
|
||||
|
||||
use clap::ArgMatches;
|
||||
use rpassword;
|
||||
use std::collections::HashMap;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::thread;
|
||||
@@ -31,9 +32,10 @@ use keychain;
|
||||
use servers::start_webwallet_server;
|
||||
use util::file::get_first_line;
|
||||
|
||||
pub fn _init_wallet_seed(wallet_config: WalletConfig) {
|
||||
if let Err(_) = WalletSeed::from_file(&wallet_config) {
|
||||
WalletSeed::init_file(&wallet_config).expect("Failed to create wallet seed file.");
|
||||
pub fn _init_wallet_seed(wallet_config: WalletConfig, password: &str) {
|
||||
if let Err(_) = WalletSeed::from_file(&wallet_config, password) {
|
||||
WalletSeed::init_file(&wallet_config, 32, password)
|
||||
.expect("Failed to create wallet seed file.");
|
||||
};
|
||||
}
|
||||
|
||||
@@ -48,6 +50,33 @@ pub fn seed_exists(wallet_config: WalletConfig) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn prompt_password(args: &ArgMatches) -> String {
|
||||
match args.value_of("pass") {
|
||||
None => {
|
||||
println!("Temporary note:");
|
||||
println!(
|
||||
"If this is your first time running your wallet since BIP32 (word lists) \
|
||||
were implemented, your seed will be converted to \
|
||||
the new format. Please ensure the provided password is correct."
|
||||
);
|
||||
println!("If this goes wrong, your old 'wallet.seed' file has been saved as 'wallet.seed.bak' \
|
||||
Rename this file to back to `wallet.seed` and try again");
|
||||
rpassword::prompt_password_stdout("Password: ").unwrap()
|
||||
}
|
||||
Some(p) => p.to_owned(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn prompt_password_confirm() -> String {
|
||||
let first = rpassword::prompt_password_stdout("Password: ").unwrap();
|
||||
let second = rpassword::prompt_password_stdout("Confirm Password: ").unwrap();
|
||||
if first != second {
|
||||
println!("Passwords do not match");
|
||||
std::process::exit(0);
|
||||
}
|
||||
first
|
||||
}
|
||||
|
||||
pub fn wallet_command(wallet_args: &ArgMatches, config: GlobalWalletConfig) -> i32 {
|
||||
// just get defaults from the global config
|
||||
let mut wallet_config = config.members.unwrap().wallet;
|
||||
@@ -74,15 +103,22 @@ pub fn wallet_command(wallet_args: &ArgMatches, config: GlobalWalletConfig) -> i
|
||||
}
|
||||
let node_api_secret = get_first_line(wallet_config.node_api_secret_path.clone());
|
||||
|
||||
// Derive the keychain based on seed from seed file and specified passphrase.
|
||||
// Decrypt the seed from the seed file and derive the keychain.
|
||||
// Generate the initial wallet seed if we are running "wallet init".
|
||||
if let ("init", Some(_)) = wallet_args.subcommand() {
|
||||
WalletSeed::init_file(&wallet_config).expect("Failed to init wallet seed file.");
|
||||
if let ("init", Some(r)) = wallet_args.subcommand() {
|
||||
let list_length = match r.is_present("short_wordlist") {
|
||||
false => 32,
|
||||
true => 16,
|
||||
};
|
||||
println!("Please enter a password for your new wallet");
|
||||
let passphrase = prompt_password_confirm();
|
||||
WalletSeed::init_file(&wallet_config, list_length, &passphrase)
|
||||
.expect("Failed to init wallet seed file.");
|
||||
info!("Wallet seed file created");
|
||||
let client_n =
|
||||
HTTPNodeClient::new(&wallet_config.check_node_api_http_addr, node_api_secret);
|
||||
let _: LMDBBackend<HTTPNodeClient, keychain::ExtKeychain> =
|
||||
LMDBBackend::new(wallet_config.clone(), "", client_n).unwrap_or_else(|e| {
|
||||
LMDBBackend::new(wallet_config.clone(), &passphrase, client_n).unwrap_or_else(|e| {
|
||||
panic!(
|
||||
"Error creating DB for wallet: {} Config: {:?}",
|
||||
e, wallet_config
|
||||
@@ -95,13 +131,45 @@ pub fn wallet_command(wallet_args: &ArgMatches, config: GlobalWalletConfig) -> i
|
||||
return 0;
|
||||
}
|
||||
|
||||
let passphrase = match wallet_args.value_of("pass") {
|
||||
None => {
|
||||
error!("Failed to read passphrase.");
|
||||
return 1;
|
||||
// Recover a seed from a recovery phrase
|
||||
if let ("recover", Some(r)) = wallet_args.subcommand() {
|
||||
if !r.is_present("recovery_phrase") {
|
||||
// only needed to display phrase
|
||||
let passphrase = prompt_password(wallet_args);
|
||||
let seed = match WalletSeed::from_file(&wallet_config, &passphrase) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
println!("Can't open wallet seed file (check password): {}", e);
|
||||
std::process::exit(0);
|
||||
}
|
||||
};
|
||||
let _ = seed.show_recovery_phrase();
|
||||
std::process::exit(0);
|
||||
}
|
||||
Some(p) => p,
|
||||
};
|
||||
let word_list = match r.value_of("recovery_phrase") {
|
||||
Some(w) => w,
|
||||
None => {
|
||||
println!("Recovery word phrase must be provided (in quotes)");
|
||||
std::process::exit(0);
|
||||
}
|
||||
};
|
||||
// check word list is okay before asking for password
|
||||
if WalletSeed::from_mnemonic(word_list).is_err() {
|
||||
println!("Recovery word phrase is invalid");
|
||||
std::process::exit(0);
|
||||
}
|
||||
println!("Please provide a new password for the recovered wallet");
|
||||
let passphrase = prompt_password_confirm();
|
||||
let res = WalletSeed::recover_from_phrase(&wallet_config, word_list, &passphrase);
|
||||
if let Err(e) = res {
|
||||
thread::sleep(Duration::from_millis(200));
|
||||
error!("Error recovering seed with list '{}' - {}", word_list, e);
|
||||
return 0;
|
||||
}
|
||||
|
||||
thread::sleep(Duration::from_millis(200));
|
||||
return 0;
|
||||
}
|
||||
|
||||
let account = match wallet_args.value_of("account") {
|
||||
None => {
|
||||
@@ -111,6 +179,9 @@ pub fn wallet_command(wallet_args: &ArgMatches, config: GlobalWalletConfig) -> i
|
||||
Some(p) => p,
|
||||
};
|
||||
|
||||
// all further commands always need a password
|
||||
let passphrase = prompt_password(wallet_args);
|
||||
|
||||
// Handle listener startup commands
|
||||
{
|
||||
let api_secret = get_first_line(wallet_config.api_secret_path.clone());
|
||||
@@ -146,10 +217,14 @@ pub fn wallet_command(wallet_args: &ArgMatches, config: GlobalWalletConfig) -> i
|
||||
.listen(
|
||||
params,
|
||||
wallet_config.clone(),
|
||||
passphrase,
|
||||
&passphrase,
|
||||
account,
|
||||
node_api_secret.clone(),
|
||||
).unwrap_or_else(|e| {
|
||||
if e.kind() == ErrorKind::WalletSeedDecryption {
|
||||
println!("Error decrypting wallet seed (check provided password)");
|
||||
std::process::exit(0);
|
||||
}
|
||||
panic!(
|
||||
"Error creating wallet listener: {:?} Config: {:?}",
|
||||
e, wallet_config
|
||||
@@ -159,10 +234,19 @@ pub fn wallet_command(wallet_args: &ArgMatches, config: GlobalWalletConfig) -> i
|
||||
("owner_api", Some(_api_args)) => {
|
||||
let wallet = instantiate_wallet(
|
||||
wallet_config.clone(),
|
||||
passphrase,
|
||||
&passphrase,
|
||||
account,
|
||||
node_api_secret.clone(),
|
||||
);
|
||||
).unwrap_or_else(|e| {
|
||||
if e.kind() == grin_wallet::ErrorKind::Encryption {
|
||||
println!("Error decrypting wallet seed (check provided password)");
|
||||
std::process::exit(0);
|
||||
}
|
||||
panic!(
|
||||
"Error creating wallet listener: {:?} Config: {:?}",
|
||||
e, wallet_config
|
||||
);
|
||||
});
|
||||
// TLS is disabled because we bind to localhost
|
||||
controller::owner_listener(wallet.clone(), "127.0.0.1:13420", api_secret, None)
|
||||
.unwrap_or_else(|e| {
|
||||
@@ -175,10 +259,19 @@ pub fn wallet_command(wallet_args: &ArgMatches, config: GlobalWalletConfig) -> i
|
||||
("web", Some(_api_args)) => {
|
||||
let wallet = instantiate_wallet(
|
||||
wallet_config.clone(),
|
||||
passphrase,
|
||||
&passphrase,
|
||||
account,
|
||||
node_api_secret.clone(),
|
||||
);
|
||||
).unwrap_or_else(|e| {
|
||||
if e.kind() == grin_wallet::ErrorKind::Encryption {
|
||||
println!("Error decrypting wallet seed (check provided password)");
|
||||
std::process::exit(0);
|
||||
}
|
||||
panic!(
|
||||
"Error creating wallet listener: {:?} Config: {:?}",
|
||||
e, wallet_config
|
||||
);
|
||||
});
|
||||
// start owner listener and run static file server
|
||||
start_webwallet_server();
|
||||
controller::owner_listener(wallet.clone(), "127.0.0.1:13420", api_secret, tls_conf)
|
||||
@@ -195,10 +288,19 @@ pub fn wallet_command(wallet_args: &ArgMatches, config: GlobalWalletConfig) -> i
|
||||
|
||||
let wallet = instantiate_wallet(
|
||||
wallet_config.clone(),
|
||||
passphrase,
|
||||
&passphrase,
|
||||
account,
|
||||
node_api_secret.clone(),
|
||||
);
|
||||
).unwrap_or_else(|e| {
|
||||
if e.kind() == grin_wallet::ErrorKind::Encryption {
|
||||
println!("Error decrypting wallet seed (check provided password)");
|
||||
std::process::exit(0);
|
||||
}
|
||||
panic!(
|
||||
"Error instantiating wallet: {:?} Config: {:?}",
|
||||
e, wallet_config
|
||||
);
|
||||
});
|
||||
|
||||
let res = controller::owner_single_use(wallet.clone(), |api| {
|
||||
match wallet_args.subcommand() {
|
||||
|
||||
+19
-6
@@ -25,6 +25,7 @@ extern crate serde;
|
||||
extern crate serde_json;
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
extern crate rpassword;
|
||||
extern crate term;
|
||||
|
||||
extern crate grin_api as api;
|
||||
@@ -153,9 +154,8 @@ fn real_main() -> i32 {
|
||||
.arg(Arg::with_name("pass")
|
||||
.short("p")
|
||||
.long("pass")
|
||||
.help("Wallet passphrase used to generate the private key seed")
|
||||
.takes_value(true)
|
||||
.default_value(""))
|
||||
.help("Wallet passphrase used to encrypt wallet seed")
|
||||
.takes_value(true))
|
||||
.arg(Arg::with_name("account")
|
||||
.short("a")
|
||||
.long("account")
|
||||
@@ -343,10 +343,23 @@ fn real_main() -> i32 {
|
||||
.short("h")
|
||||
.long("here")
|
||||
.help("Create wallet files in the current directory instead of the default ~/.grin directory")
|
||||
.takes_value(false))
|
||||
.arg(Arg::with_name("short_wordlist")
|
||||
.help("Generate a 12 word recovery phrase/seed instead of default 24.")
|
||||
.short("s")
|
||||
.long("short_wordlist")
|
||||
.takes_value(false)))
|
||||
|
||||
.subcommand(SubCommand::with_name("recover")
|
||||
.about("recover (create a new wallet.seed file) from a recovery phrase")
|
||||
.arg(Arg::with_name("recovery_phrase")
|
||||
.help("12 or 24 word recovery phrase (encased in quotes).")
|
||||
.short("p")
|
||||
.long("recovery_phrase")
|
||||
.takes_value(true)))
|
||||
|
||||
.subcommand(SubCommand::with_name("restore")
|
||||
.about("Attempt to restore wallet contents from the chain using seed and password. \
|
||||
.about("Attempt to restore wallet contents from the chain using seed. \
|
||||
NOTE: Backup wallet.* and run `wallet listen` before running restore.")))
|
||||
|
||||
.get_matches();
|
||||
@@ -381,9 +394,9 @@ fn real_main() -> i32 {
|
||||
panic!("Error loading wallet configuration: {}", e);
|
||||
});
|
||||
if !cmd::seed_exists(w.members.as_ref().unwrap().wallet.clone()) {
|
||||
if let ("init", Some(_)) = wallet_args.subcommand() {
|
||||
if "init" == wallet_args.subcommand().0 || "recover" == wallet_args.subcommand().0 {
|
||||
} else {
|
||||
println!("Wallet seed file doesn't exist. Run `grin wallet -p [password] init` first");
|
||||
println!("Wallet seed file doesn't exist. Run `grin wallet init` first");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ term = "0.5"
|
||||
tokio = "0.1.7"
|
||||
tokio-core = "0.1"
|
||||
tokio-retry = "0.1"
|
||||
ring = "0.13"
|
||||
uuid = { version = "0.6", features = ["serde", "v4"] }
|
||||
url = "1.7.0"
|
||||
chrono = { version = "0.4.4", features = ["serde"] }
|
||||
|
||||
@@ -71,7 +71,8 @@ impl WalletCommAdapter for HTTPWalletCommAdapter {
|
||||
node_api_secret: Option<String>,
|
||||
) -> Result<(), Error> {
|
||||
let wallet =
|
||||
instantiate_wallet(config.clone(), passphrase, account, node_api_secret.clone());
|
||||
instantiate_wallet(config.clone(), passphrase, account, node_api_secret.clone())
|
||||
.context(ErrorKind::WalletSeedDecryption)?;
|
||||
let listen_addr = params.get("api_listen_addr").unwrap();
|
||||
let tls_conf = match params.get("certificate") {
|
||||
Some(s) => Some(api::TLSConfig::new(
|
||||
|
||||
@@ -87,6 +87,14 @@ pub enum ErrorKind {
|
||||
#[fail(display = "Wallet seed doesn't exist error")]
|
||||
WalletSeedDoesntExist,
|
||||
|
||||
/// Enc/Decryption Error
|
||||
#[fail(display = "Enc/Decryption error")]
|
||||
Encryption,
|
||||
|
||||
/// BIP 39 word list
|
||||
#[fail(display = "BIP39 Mnemonic (word list) Error")]
|
||||
Mnemonic,
|
||||
|
||||
/// Other
|
||||
#[fail(display = "Generic error: {}", _0)]
|
||||
GenericError(String),
|
||||
|
||||
+8
-15
@@ -35,6 +35,7 @@ extern crate failure;
|
||||
extern crate failure_derive;
|
||||
extern crate futures;
|
||||
extern crate hyper;
|
||||
extern crate ring;
|
||||
extern crate tokio;
|
||||
extern crate tokio_core;
|
||||
extern crate tokio_retry;
|
||||
@@ -62,7 +63,7 @@ pub use error::{Error, ErrorKind};
|
||||
pub use libwallet::types::{BlockFees, CbData, NodeClient, WalletBackend, WalletInfo, WalletInst};
|
||||
pub use lmdb_wallet::{wallet_db_exists, LMDBBackend};
|
||||
pub use node_clients::{create_coinbase, HTTPNodeClient};
|
||||
pub use types::{WalletConfig, WalletSeed, SEED_FILE};
|
||||
pub use types::{EncryptedWalletSeed, WalletConfig, WalletSeed, SEED_FILE};
|
||||
|
||||
use std::sync::Arc;
|
||||
use util::Mutex;
|
||||
@@ -73,20 +74,12 @@ pub fn instantiate_wallet(
|
||||
passphrase: &str,
|
||||
account: &str,
|
||||
node_api_secret: Option<String>,
|
||||
) -> Arc<Mutex<WalletInst<HTTPNodeClient, keychain::ExtKeychain>>> {
|
||||
) -> Result<Arc<Mutex<WalletInst<HTTPNodeClient, keychain::ExtKeychain>>>, Error> {
|
||||
// First test decryption, so we can abort early if we have the wrong password
|
||||
let _ = WalletSeed::from_file(&wallet_config, passphrase)?;
|
||||
let client_n = HTTPNodeClient::new(&wallet_config.check_node_api_http_addr, node_api_secret);
|
||||
let mut db_wallet = LMDBBackend::new(wallet_config.clone(), passphrase, client_n)
|
||||
.unwrap_or_else(|e| {
|
||||
panic!(
|
||||
"Error creating DB wallet: {} Config: {:?}",
|
||||
e, wallet_config
|
||||
);
|
||||
});
|
||||
db_wallet
|
||||
.set_parent_key_id_by_name(account)
|
||||
.unwrap_or_else(|e| {
|
||||
panic!("Error starting wallet: {}", e);
|
||||
});
|
||||
let mut db_wallet = LMDBBackend::new(wallet_config.clone(), passphrase, client_n)?;
|
||||
db_wallet.set_parent_key_id_by_name(account)?;
|
||||
info!("Using LMDB Backend for wallet");
|
||||
Arc::new(Mutex::new(db_wallet))
|
||||
Ok(Arc::new(Mutex::new(db_wallet)))
|
||||
}
|
||||
|
||||
@@ -148,6 +148,10 @@ pub enum ErrorKind {
|
||||
#[fail(display = "Wallet seed doesn't exist error")]
|
||||
WalletSeedDoesntExist,
|
||||
|
||||
/// Wallet seed doesn't exist
|
||||
#[fail(display = "Wallet seed decryption error")]
|
||||
WalletSeedDecryption,
|
||||
|
||||
/// Transaction doesn't exist
|
||||
#[fail(display = "Transaction {} doesn't exist", _0)]
|
||||
TransactionDoesntExist(String),
|
||||
|
||||
@@ -121,12 +121,10 @@ where
|
||||
{
|
||||
/// Initialise with whatever stored credentials we have
|
||||
fn open_with_credentials(&mut self) -> Result<(), Error> {
|
||||
let wallet_seed = WalletSeed::from_file(&self.config)
|
||||
let wallet_seed = WalletSeed::from_file(&self.config, &self.passphrase)
|
||||
.context(ErrorKind::CallbackImpl("Error opening wallet"))?;
|
||||
let keychain = wallet_seed.derive_keychain(&self.passphrase);
|
||||
let keychain = wallet_seed.derive_keychain();
|
||||
self.keychain = Some(keychain.context(ErrorKind::CallbackImpl("Error deriving keychain"))?);
|
||||
// Just blow up password for now after it's been used
|
||||
self.passphrase = String::from("");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
+181
-20
@@ -12,7 +12,6 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use std::cmp::min;
|
||||
use std::fs::{self, File};
|
||||
use std::io::{Read, Write};
|
||||
use std::path::Path;
|
||||
@@ -20,11 +19,15 @@ use std::path::MAIN_SEPARATOR;
|
||||
|
||||
use blake2;
|
||||
use rand::{thread_rng, Rng};
|
||||
use serde_json;
|
||||
|
||||
use ring::aead;
|
||||
use ring::{digest, pbkdf2};
|
||||
|
||||
use core::global::ChainTypes;
|
||||
use error::{Error, ErrorKind};
|
||||
use failure::ResultExt;
|
||||
use keychain::Keychain;
|
||||
use keychain::{mnemonic, Keychain};
|
||||
use util;
|
||||
|
||||
pub const SEED_FILE: &'static str = "wallet.seed";
|
||||
@@ -79,16 +82,20 @@ impl WalletConfig {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub struct WalletSeed([u8; 32]);
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct WalletSeed(Vec<u8>);
|
||||
|
||||
impl WalletSeed {
|
||||
pub fn from_bytes(bytes: &[u8]) -> WalletSeed {
|
||||
let mut seed = [0; 32];
|
||||
for i in 0..min(32, bytes.len()) {
|
||||
seed[i] = bytes[i];
|
||||
WalletSeed(bytes.to_vec())
|
||||
}
|
||||
|
||||
pub fn from_mnemonic(word_list: &str) -> Result<WalletSeed, Error> {
|
||||
let res = mnemonic::to_entropy(word_list);
|
||||
match res {
|
||||
Ok(s) => Ok(WalletSeed::from_bytes(&s)),
|
||||
Err(_) => Err(ErrorKind::Mnemonic.into()),
|
||||
}
|
||||
WalletSeed(seed)
|
||||
}
|
||||
|
||||
pub fn from_hex(hex: &str) -> Result<WalletSeed, Error> {
|
||||
@@ -101,18 +108,72 @@ impl WalletSeed {
|
||||
util::to_hex(self.0.to_vec())
|
||||
}
|
||||
|
||||
pub fn derive_keychain<K: Keychain>(&self, password: &str) -> Result<K, Error> {
|
||||
let seed = blake2::blake2b::blake2b(64, &password.as_bytes(), &self.0);
|
||||
let result = K::from_seed(seed.as_bytes())?;
|
||||
pub fn to_mnemonic(&self) -> Result<String, Error> {
|
||||
let result = mnemonic::from_entropy(&self.0);
|
||||
match result {
|
||||
Ok(r) => Ok(r),
|
||||
Err(_) => Err(ErrorKind::Mnemonic.into()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn derive_keychain_old(old_wallet_seed: [u8; 32], password: &str) -> Vec<u8> {
|
||||
let seed = blake2::blake2b::blake2b(64, password.as_bytes(), &old_wallet_seed);
|
||||
seed.as_bytes().to_vec()
|
||||
}
|
||||
|
||||
pub fn derive_keychain<K: Keychain>(&self) -> Result<K, Error> {
|
||||
let result = K::from_seed(&self.0)?;
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub fn init_new() -> WalletSeed {
|
||||
let seed: [u8; 32] = thread_rng().gen();
|
||||
pub fn init_new(seed_length: usize) -> WalletSeed {
|
||||
let mut seed: Vec<u8> = vec![];
|
||||
let mut rng = thread_rng();
|
||||
for _ in 0..seed_length {
|
||||
seed.push(rng.gen());
|
||||
}
|
||||
WalletSeed(seed)
|
||||
}
|
||||
|
||||
pub fn init_file(wallet_config: &WalletConfig) -> Result<WalletSeed, Error> {
|
||||
pub fn recover_from_phrase(
|
||||
wallet_config: &WalletConfig,
|
||||
word_list: &str,
|
||||
password: &str,
|
||||
) -> Result<(), Error> {
|
||||
let seed_file_path = &format!(
|
||||
"{}{}{}",
|
||||
wallet_config.data_file_dir, MAIN_SEPARATOR, SEED_FILE,
|
||||
);
|
||||
if Path::new(seed_file_path).exists() {
|
||||
error!(
|
||||
"wallet seed file {} exists. \
|
||||
Please backup and delete this file before attempting recovery.",
|
||||
seed_file_path
|
||||
);
|
||||
return Err(ErrorKind::WalletSeedExists)?;
|
||||
}
|
||||
let seed = WalletSeed::from_mnemonic(word_list)?;
|
||||
let enc_seed = EncryptedWalletSeed::from_seed(&seed, password)?;
|
||||
let enc_seed_json = serde_json::to_string_pretty(&enc_seed).context(ErrorKind::Format)?;
|
||||
let mut file = File::create(seed_file_path).context(ErrorKind::IO)?;
|
||||
file.write_all(&enc_seed_json.as_bytes())
|
||||
.context(ErrorKind::IO)?;
|
||||
warn!("Seed created from word list");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn show_recovery_phrase(&self) -> Result<(), Error> {
|
||||
println!("Your recovery phrase is:");
|
||||
println!("{}", self.to_mnemonic()?);
|
||||
println!("Please back-up these words in a non-digital format.");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn init_file(
|
||||
wallet_config: &WalletConfig,
|
||||
seed_length: usize,
|
||||
password: &str,
|
||||
) -> Result<WalletSeed, Error> {
|
||||
// create directory if it doesn't exist
|
||||
fs::create_dir_all(&wallet_config.data_file_dir).context(ErrorKind::IO)?;
|
||||
|
||||
@@ -121,20 +182,24 @@ impl WalletSeed {
|
||||
wallet_config.data_file_dir, MAIN_SEPARATOR, SEED_FILE,
|
||||
);
|
||||
|
||||
debug!("Generating wallet seed file at: {}", seed_file_path);
|
||||
warn!("Generating wallet seed file at: {}", seed_file_path);
|
||||
|
||||
if Path::new(seed_file_path).exists() {
|
||||
Err(ErrorKind::WalletSeedExists)?
|
||||
} else {
|
||||
let seed = WalletSeed::init_new();
|
||||
let seed = WalletSeed::init_new(seed_length);
|
||||
let enc_seed = EncryptedWalletSeed::from_seed(&seed, password)?;
|
||||
let enc_seed_json =
|
||||
serde_json::to_string_pretty(&enc_seed).context(ErrorKind::Format)?;
|
||||
let mut file = File::create(seed_file_path).context(ErrorKind::IO)?;
|
||||
file.write_all(&seed.to_hex().as_bytes())
|
||||
file.write_all(&enc_seed_json.as_bytes())
|
||||
.context(ErrorKind::IO)?;
|
||||
seed.show_recovery_phrase()?;
|
||||
Ok(seed)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_file(wallet_config: &WalletConfig) -> Result<WalletSeed, Error> {
|
||||
pub fn from_file(wallet_config: &WalletConfig, password: &str) -> Result<WalletSeed, Error> {
|
||||
// create directory if it doesn't exist
|
||||
fs::create_dir_all(&wallet_config.data_file_dir).context(ErrorKind::IO)?;
|
||||
|
||||
@@ -143,13 +208,44 @@ impl WalletSeed {
|
||||
wallet_config.data_file_dir, MAIN_SEPARATOR, SEED_FILE,
|
||||
);
|
||||
|
||||
debug!("Using wallet seed file at: {}", seed_file_path,);
|
||||
debug!("Using wallet seed file at: {}", seed_file_path);
|
||||
|
||||
if Path::new(seed_file_path).exists() {
|
||||
let mut file = File::open(seed_file_path).context(ErrorKind::IO)?;
|
||||
let mut buffer = String::new();
|
||||
file.read_to_string(&mut buffer).context(ErrorKind::IO)?;
|
||||
let wallet_seed = WalletSeed::from_hex(&buffer.trim())?;
|
||||
let enc_seed: EncryptedWalletSeed =
|
||||
match serde_json::from_str(&buffer).context(ErrorKind::Format) {
|
||||
Ok(s) => s,
|
||||
Err(_) => {
|
||||
println!("Attempting to convert old wallet seed file to new format");
|
||||
// TODO: remove for mainnet
|
||||
// try to convert from old format
|
||||
let mut bak_file = File::create(format!("{}.bak", seed_file_path))
|
||||
.context(ErrorKind::IO)?;
|
||||
let mut file = File::create(seed_file_path).context(ErrorKind::IO)?;
|
||||
let old_wallet_seed = WalletSeed::from_hex(&buffer.trim())?;
|
||||
bak_file
|
||||
.write_all(&old_wallet_seed.to_hex().as_bytes())
|
||||
.context(ErrorKind::IO)?;
|
||||
let mut c_wallet_seed = [0u8; 32];
|
||||
c_wallet_seed.copy_from_slice(&old_wallet_seed.0[0..32]);
|
||||
let converted_wallet_seed =
|
||||
WalletSeed::derive_keychain_old(c_wallet_seed, password);
|
||||
let enc_seed = EncryptedWalletSeed::from_seed(
|
||||
&WalletSeed::from_bytes(&converted_wallet_seed),
|
||||
password,
|
||||
)?;
|
||||
let enc_seed_json =
|
||||
serde_json::to_string_pretty(&enc_seed).context(ErrorKind::Format)?;
|
||||
file.write_all(&enc_seed_json.as_bytes())
|
||||
.context(ErrorKind::IO)?;
|
||||
println!("Seed file conversion done");
|
||||
println!("Consider moving funds to a newly-created wallet to support recovery phrases");
|
||||
enc_seed
|
||||
}
|
||||
};
|
||||
let wallet_seed = enc_seed.decrypt(password)?;
|
||||
Ok(wallet_seed)
|
||||
} else {
|
||||
error!(
|
||||
@@ -161,3 +257,68 @@ impl WalletSeed {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Encrypted wallet seed, for storing on disk and decrypting
|
||||
/// with provided password
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
|
||||
pub struct EncryptedWalletSeed {
|
||||
encrypted_seed: String,
|
||||
/// Salt, not so useful in single case but include anyhow for situations
|
||||
/// where someone wants to store many of these
|
||||
pub salt: String,
|
||||
/// Nonce
|
||||
pub nonce: String,
|
||||
}
|
||||
|
||||
impl EncryptedWalletSeed {
|
||||
/// Create a new encrypted seed from the given seed + password
|
||||
pub fn from_seed(seed: &WalletSeed, password: &str) -> Result<EncryptedWalletSeed, Error> {
|
||||
let salt: [u8; 8] = thread_rng().gen();
|
||||
let nonce: [u8; 12] = thread_rng().gen();
|
||||
let password = password.as_bytes();
|
||||
let mut key = [0; 32];
|
||||
pbkdf2::derive(&digest::SHA512, 100, &salt, password, &mut key);
|
||||
let content = seed.0.to_vec();
|
||||
let mut enc_bytes = content.clone();
|
||||
let suffix_len = aead::CHACHA20_POLY1305.tag_len();
|
||||
for _ in 0..suffix_len {
|
||||
enc_bytes.push(0);
|
||||
}
|
||||
let sealing_key =
|
||||
aead::SealingKey::new(&aead::CHACHA20_POLY1305, &key).context(ErrorKind::Encryption)?;
|
||||
aead::seal_in_place(&sealing_key, &nonce, &[], &mut enc_bytes, suffix_len)
|
||||
.context(ErrorKind::Encryption)?;
|
||||
Ok(EncryptedWalletSeed {
|
||||
encrypted_seed: util::to_hex(enc_bytes.to_vec()),
|
||||
salt: util::to_hex(salt.to_vec()),
|
||||
nonce: util::to_hex(nonce.to_vec()),
|
||||
})
|
||||
}
|
||||
|
||||
/// Decrypt seed
|
||||
pub fn decrypt(&self, password: &str) -> Result<WalletSeed, Error> {
|
||||
let mut encrypted_seed = match util::from_hex(self.encrypted_seed.clone()) {
|
||||
Ok(s) => s,
|
||||
Err(_) => return Err(ErrorKind::Encryption)?,
|
||||
};
|
||||
let salt = match util::from_hex(self.salt.clone()) {
|
||||
Ok(s) => s,
|
||||
Err(_) => return Err(ErrorKind::Encryption)?,
|
||||
};
|
||||
let nonce = match util::from_hex(self.nonce.clone()) {
|
||||
Ok(s) => s,
|
||||
Err(_) => return Err(ErrorKind::Encryption)?,
|
||||
};
|
||||
let password = password.as_bytes();
|
||||
let mut key = [0; 32];
|
||||
pbkdf2::derive(&digest::SHA512, 100, &salt, password, &mut key);
|
||||
|
||||
let opening_key =
|
||||
aead::OpeningKey::new(&aead::CHACHA20_POLY1305, &key).context(ErrorKind::Encryption)?;
|
||||
let decrypted_data = aead::open_in_place(&opening_key, &nonce, &[], 0, &mut encrypted_seed)
|
||||
.context(ErrorKind::Encryption)?;
|
||||
|
||||
Ok(WalletSeed::from_bytes(&decrypted_data))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,7 +163,7 @@ where
|
||||
{
|
||||
let mut wallet_config = WalletConfig::default();
|
||||
wallet_config.data_file_dir = String::from(dir);
|
||||
let _ = wallet::WalletSeed::init_file(&wallet_config);
|
||||
let _ = wallet::WalletSeed::init_file(&wallet_config, 32, "");
|
||||
let mut wallet = LMDBBackend::new(wallet_config.clone(), "", n_client)
|
||||
.unwrap_or_else(|e| panic!("Error creating wallet: {:?} Config: {:?}", e, wallet_config));
|
||||
wallet.open_with_credentials().unwrap_or_else(|e| {
|
||||
|
||||
@@ -114,7 +114,7 @@ fn file_exchange_test_impl(test_dir: &str) -> Result<(), libwallet::Error> {
|
||||
//"mining",
|
||||
//"listener",
|
||||
)?;
|
||||
/// output tx file
|
||||
// output tx file
|
||||
let file_adapter = FileWalletCommAdapter::new();
|
||||
file_adapter.send_tx_async(&send_file, &mut slate)?;
|
||||
api.tx_lock_outputs(&slate, lock_fn)?;
|
||||
|
||||
@@ -26,6 +26,7 @@ use util::secp;
|
||||
use util::secp::key::{PublicKey, SecretKey};
|
||||
use wallet::libtx::{aggsig, proof};
|
||||
use wallet::libwallet::types::Context;
|
||||
use wallet::{EncryptedWalletSeed, WalletSeed};
|
||||
|
||||
use rand::thread_rng;
|
||||
|
||||
@@ -481,3 +482,22 @@ fn test_rewind_range_proof() {
|
||||
assert_eq!(proof_info.success, false);
|
||||
assert_eq!(proof_info.value, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wallet_seed_encrypt() {
|
||||
let password = "passwoid";
|
||||
let wallet_seed = WalletSeed::init_new(32);
|
||||
let mut enc_wallet_seed = EncryptedWalletSeed::from_seed(&wallet_seed, password).unwrap();
|
||||
println!("EWS: {:?}", enc_wallet_seed);
|
||||
let decrypted_wallet_seed = enc_wallet_seed.decrypt(password).unwrap();
|
||||
assert_eq!(wallet_seed, decrypted_wallet_seed);
|
||||
|
||||
// Wrong password
|
||||
let decrypted_wallet_seed = enc_wallet_seed.decrypt("");
|
||||
assert!(decrypted_wallet_seed.is_err());
|
||||
|
||||
// Wrong nonce
|
||||
enc_wallet_seed.nonce = "wrongnonce".to_owned();
|
||||
let decrypted_wallet_seed = enc_wallet_seed.decrypt(password);
|
||||
assert!(decrypted_wallet_seed.is_err());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user