-
Notifications
You must be signed in to change notification settings - Fork 94
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
feat(crypto): mnemonic generation/encryption/decryption/storage #2014
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Amazing work! I have a few minor comments 🙂
mm2src/mm2_main/src/lp_wallet.rs
Outdated
// The passphrase will then be encrypted and saved whether it was generated or provided. | ||
let wallet_name = deserialize_config_field::<Option<String>>(&ctx, "wallet_name")?; | ||
|
||
let passphrase = match (wallet_name, passphrase) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Relying on existence of wallet_name
might a bit confusing (just IMHO/nitpick). What do you think about using more explicit config field like "use_encrypted_wallet"?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you mean that use_encrypted_wallet
is a bool?
I wanted to cover the multi seed support for GUIs, so we need different wallet names as identifiers.
1 - For new wallet creation, only a wallet name and a password is passed, a seed is generated by mm2 and saved in wallet_name.dat
2 - If a wallet name already exists, we decrypt the wallet_name.dat
using the password
3 - To import a wallet, we use wallet_name
, passphrase
and password
I guess I didn't cover the case where a seed file is uploaded, so it will be provided as encrypted to mm2, I can add this case I guess. Also, I can maybe use a struct for all the wallet data, so the parameter passed can look like the below:
"wallet": {
"name": "wallet_name", // Optional for legacy operations where the passphrase is provided each time from GUIs
"password": "PASSWORD",
"passphrase": "PASSPHRASE" // This is an optional enum for either encrypted or plaintext
}
This will not be easy to use on cli though.
One more note: For GUIs to migrate, they will need to provide the seed phrase, wallet name, password from their files similar to how importing a wallet works. GUIs can then delete their seed file which uses different encryption algorithms that we plan to deprecate.
What is your opinion on all this @artemii235 :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just understood now that you meant to use use_encrypted_wallet
in addition to wallet_name
, Will this be needed if I used a struct as shown in the previous comment?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's not needed actually 🙂 Thanks for the detailed explanation!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I also shouldn't use a struct as I suggested to maintain backward compatibility for the passphrase
field. I will add the option to pass an encrypted passphrase for the case of uploading a seed file in next iteration, so I will leave this unresolved until then.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will add the option to pass an encrypted passphrase for the case of uploading a seed file in next iteration, so I will leave this unresolved until then.
Done
Do I understand it right that we sometimes call 'mnemonic' as 'passphrase' in the code (as I can see in bip39 'passphrase' is an optional extra string to additionally protect the mnemonic)? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Impressive work!
I have just comment now
Yeah, |
It was a bit confusing for me first time. Maybe we could make a separate PR for this |
I meant |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One more note
…DEFAULT_WORD_COUNT = 12
I changed the default word count of mnemonics to |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔥
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for the work, here are some notes from the first review:
/// Reads the encrypted passphrase data from the file associated with the given wallet name. | ||
/// | ||
/// This function is responsible for retrieving the encrypted passphrase data from a file. | ||
/// The data is expected to be in the format of `EncryptedData`, which includes | ||
/// all necessary components for decryption, such as the encryption algorithm, key derivation | ||
/// details, salts, IV, ciphertext, and HMAC tag. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not keep this encrypted data in a database (similar to how it's done in WASM)? Having separate files could cause problems for future development (e.g., if you need to perform transactional operations, you'll have to implement additional file locks).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I opted for file-based storage for encrypted passphrase/mnemonic data to allow portability, this also aligns with the industry standards afaik (e.g. wallet.dat file for bitcoin core and for komodo daemon), please also check the use case here #2014 (comment) where the user can upload a seed file.
For encrypted wallet data using the seed (e.g. swap files), how to save the data is not implemented yet. I will probably add methods for both file storage and database. Will add this comment to the checklist here #2014 (comment) to remember.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Storing them in the database wouldn't change the behaviour for users I believe. You just change the way you keep its data for mm2 (e.g., you can still upload seed files). Is there any other reason for keeping them as a file on native (non-WASM) mode ? If not, I would keep this behaviour simple and same on all supported mm2 platforms and keep the mm2 internal data in a central storage (in this case mm2 db).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think storing encrypted passphrase/mnemonic data in a separate file instead of the main mm2 db makes backups easier for end users. They can just backup a small(KB sized) encrypted file instead of a possibly GB sized mm2 db.
Also, it will be easier to document recovery of passphrase from backup (independent of using mm2) if it was a separate file instead of a part of a db
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If not, I would keep this behaviour simple and same on all supported mm2 platforms and keep the mm2 internal data in a central storage (in this case mm2 db).
We would need a new global database for all mnemonics/seeds, since MM2.db
path depends on the public key hash, which in turn depends on the seed. Please note that mm2 can be initialized with only one wallet/mnemonic at a time for now and that MM2.db
or MM2-shared.db
is specific to each wallet/mnemonic, and not shared across all wallets. The seed file is retrieved when starting mm2, based on the wallet name, there are no additional transactional operations for this file, except when using the get_mnemonic
RPC.
We now have 2 options:
1 - Create a file for each wallet's encrypted mnemonic in the the db root directory. This can even allow us to support importing/exporting seeds to/from different wallets by supporting different file formats in the future, e.g. https://cryptobook.nakov.com/symmetric-key-ciphers/ethereum-wallet-encryption. This is why I mentioned the industry standards.
komodo-defi-framework/mm2src/mm2_core/src/mm_ctx.rs
Lines 289 to 293 in 66085b0
#[cfg(not(target_arch = "wasm32"))] | |
pub fn wallet_file_path(&self, wallet_name: &str) -> PathBuf { | |
let db_root = path_to_db_root(self.conf["dbdir"].as_str()); | |
db_root.join(wallet_name.to_string() + ".dat") | |
} |
2 - Create a global database in the db root directory that is only used when starting mm2 and for one RPC. If a user wants to import their seed file to another wallet without using mm2 they would have to access the database to get it.
I think option 1 is better :)
- Remove arguments descriptions in doc comments. - Fix read passphrase functions names by adding `if_available`. - Improve cross-platform test compatibility.
/// Err(e) => println!("Error: {:?}", e), | ||
/// } | ||
/// ``` | ||
pub async fn get_mnemonic_rpc(ctx: MmArc, req: GetMnemonicRequest) -> MmResult<GetMnemonicResponse, GetMnemonicError> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder maybe we could have a special rpc to return if mnemonic file was created. So GUI can ensure that migration to the api mnemonic has already happened and begin to use it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not need IMHO, using the new wallet_name
config field ensures that
komodo-defi-framework/mm2src/mm2_main/src/lp_wallet.rs
Lines 159 to 162 in 4f666c1
// New approach for passphrase, `wallet_name` is needed in the config to enable multi-wallet support. | |
// In this case the passphrase will be generated if not provided. | |
// The passphrase will then be encrypted and saved whether it was generated or provided. | |
let wallet_name = deserialize_config_field::<Option<String>>(ctx, "wallet_name")?; |
MnemonicFormat::PlainText(wallet_password) => { | ||
let plaintext_mnemonic = read_and_decrypt_passphrase_if_available(&ctx, &wallet_password) | ||
.await? | ||
.ok_or_else(|| GetMnemonicError::InvalidRequest("Wallet mnemonic file not found".to_string()))?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess both 'Wallet passphrase file' and 'Wallet mnemonic file' are actually the same file. I think this error may confuse the user and maybe it is better to return the same error description in both cases?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
mm2src/mm2_main/src/lp_wallet.rs
Outdated
MnemonicError(String), | ||
#[display(fmt = "Error initializing crypto context: {}", _0)] | ||
CryptoInitError(String), | ||
Internal(String), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe good to name internal errors everywhere identically ('InternalError')
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
- Rename `SYMMETRIC_KEY_SEED` to `MASTER_NODE_HMAC_KEY` for clarity - Change `initialize_wallet_passphrase` to take a reference to `MmArc` - Remove unnecessary comments and fix some errors
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Left some dependency information (please note that I haven't checked for sec advisories on them).
@@ -7,17 +7,24 @@ edition = "2018" | |||
doctest = false | |||
|
|||
[dependencies] | |||
aes = "0.8.3" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Q: Can we use version 0.7.5
instead of duplicating this dependency?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wanted to use the latest version for such an important encryption, after this #1957 is merged I can try to update 0.7.5
to 0.8.3
instead in librustzcash
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for the fixes.
Copied none finished items in this checklist to the parent issue #1939 (comment). Will resolve conflicts then write docs for QA and merge this PR. |
@KomodoPlatform/qa This Pull Request introduces a new optional parameter, When
So, when wallet_name is passed, seed management is completely handled by mm2 instead of GUIs. If it's not passed, it's the legacy approach where seed is managed by GUIs and the passphrase/mnemonic is passed to mm2 during initialization. A new API method called Request for encrypted passphrase:{
"userpass": "{{userpass}}",
"mmrpc": "2.0",
"method": "get_mnemonic",
"params": {
"format": "encrypted"
}
} Response for encrypted passphrase request:{
"format": "encrypted",
"encrypted_mnemonic_data": {
// Encrypted data
}
} Request for plaintext passphrase:{
"userpass": "{{userpass}}",
"mmrpc": "2.0",
"method": "get_mnemonic",
"params": {
"format": "plaintext",
"password": "password123" // The password used to encrypt the passphrase when the wallet was created
}
} Response for plaintext passphrase request:{
"format": "plaintext",
"mnemonic": "your_mnemonic_here"
} |
* dev: feat(nft-swap): nft swap protocol v2 POC (KomodoPlatform#2084) fix(zcoin): syncing and activation improvements (KomodoPlatform#2089) feat(crypto): mnemonic generation/encryption/decryption/storage (KomodoPlatform#2014) fix(eth): error handling in RPCs (KomodoPlatform#2090)
#1939
Checklist
aes
,cbc
,cipher
,hmac
Todo:
wallet_rmd160
.tiny-bip39
crate and usebip39
instead in adex-cli.aes
has some features to optimize compilation size vs performance on certain architectures.Future: