Skip to content

Commit

Permalink
Wallet Seed Encryption + BIP39 (#2004)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
yeastplume authored Nov 24, 2018
1 parent 80a09c3 commit 5992542
Show file tree
Hide file tree
Showing 19 changed files with 465 additions and 102 deletions.
13 changes: 13 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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" }
Expand Down
82 changes: 61 additions & 21 deletions doc/wallet/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand All @@ -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.:
Expand Down Expand Up @@ -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`
Expand Down Expand Up @@ -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:
Expand All @@ -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`

```sh
[host@new_wallet_dir]# cp OLD_WALLET.seed wallet_data/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`
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.
19 changes: 13 additions & 6 deletions keychain/src/keychain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -85,8 +95,7 @@ impl Keychain for ExtKeychain {
} else {
None
}
})
.collect();
}).collect();

let mut neg_keys: Vec<SecretKey> = blind_sum
.negative_key_ids
Expand All @@ -98,8 +107,7 @@ impl Keychain for ExtKeychain {
} else {
None
}
})
.collect();
}).collect();

pos_keys.extend(
&blind_sum
Expand Down Expand Up @@ -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),
);
}
Expand Down
1 change: 1 addition & 0 deletions keychain/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
12 changes: 6 additions & 6 deletions servers/tests/framework/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -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
}

Expand All @@ -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)
Expand All @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion servers/tests/simulnet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Loading

0 comments on commit 5992542

Please sign in to comment.