From 03c0e59a14f9e73d9f380195b3e06742d5684ecd Mon Sep 17 00:00:00 2001 From: shamardy Date: Fri, 24 Nov 2023 20:09:03 +0200 Subject: [PATCH 01/20] wip: Implement mnemonic generation, encryption, decryption, and storage functionality --- Cargo.lock | 372 ++++++++++++------ mm2src/adex_cli/Cargo.lock | 68 +++- mm2src/coins/Cargo.toml | 3 +- mm2src/coins/solana/solana_common_tests.rs | 11 +- mm2src/coins/solana/solana_tests.rs | 5 +- mm2src/common/Cargo.toml | 2 +- mm2src/crypto/Cargo.toml | 8 +- mm2src/crypto/src/global_hd_ctx.rs | 13 +- mm2src/crypto/src/lib.rs | 2 + mm2src/crypto/src/mnemonic.rs | 352 +++++++++++++++++ mm2src/crypto/src/privkey.rs | 8 +- mm2src/gossipsub/Cargo.toml | 2 +- mm2src/mm2_bitcoin/crypto/Cargo.toml | 2 +- mm2src/mm2_bitcoin/crypto/src/lib.rs | 2 +- mm2src/mm2_bitcoin/spv_validation/Cargo.toml | 2 +- .../spv_validation/src/helpers_validation.rs | 2 +- mm2src/mm2_core/src/mm_ctx.rs | 24 +- mm2src/mm2_libp2p/Cargo.toml | 2 +- mm2src/mm2_main/src/lp_native_dex.rs | 190 ++++++++- mm2src/mm2_p2p/Cargo.toml | 2 +- mm2src/mm2_p2p/src/lib.rs | 1 + 21 files changed, 889 insertions(+), 184 deletions(-) create mode 100644 mm2src/crypto/src/mnemonic.rs diff --git a/Cargo.lock b/Cargo.lock index 1911e05d20..06c4d54ad3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -50,7 +50,7 @@ checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" dependencies = [ "cfg-if 1.0.0", "cipher", - "cpufeatures 0.2.1", + "cpufeatures 0.2.11", "opaque-debug 0.3.0", ] @@ -138,6 +138,19 @@ version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "595d3cfa7a60d4555cb5067b99f07142a08ea778de5cf993f7b75c7d8fabc486" +[[package]] +name = "argon2" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17ba4cac0a46bc1d2912652a751c47f2a9f3a7fe89bcae2275d418f5270402f9" +dependencies = [ + "base64ct", + "blake2", + "cpufeatures 0.2.11", + "password-hash", + "zeroize", +] + [[package]] name = "arrayref" version = "0.3.6" @@ -239,7 +252,7 @@ checksum = "10f203db73a71dfa2fb6dd22763990fa26f3d2625a6da2da900d23b87d26be27" dependencies = [ "proc-macro2 1.0.63", "quote 1.0.28", - "syn 1.0.95", + "syn 1.0.109", ] [[package]] @@ -256,7 +269,7 @@ checksum = "061a7acccaa286c011ddc30970520b98fa40e00c9d644633fb26b5fc63a265e3" dependencies = [ "proc-macro2 1.0.63", "quote 1.0.28", - "syn 1.0.95", + "syn 1.0.109", ] [[package]] @@ -303,7 +316,7 @@ checksum = "acee9fd5073ab6b045a275b3e709c163dd36c90685219cb21804a147b58dba43" dependencies = [ "async-trait", "axum-core", - "bitflags", + "bitflags 1.3.2", "bytes 1.4.0", "futures-util", "http 0.2.7", @@ -481,6 +494,17 @@ dependencies = [ "zeroize", ] +[[package]] +name = "bip39" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93f2635620bf0b9d4576eb7bb9a38a55df78bd1205d26fa994b25911a69f212f" +dependencies = [ + "bitcoin_hashes", + "rand_core 0.6.4", + "zeroize", +] + [[package]] name = "bitcoin" version = "0.29.2" @@ -507,7 +531,7 @@ dependencies = [ "ripemd160", "serialization", "sha-1", - "sha2 0.9.9", + "sha2 0.10.7", "sha3 0.9.1", "siphasher", ] @@ -518,6 +542,12 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitflags" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" + [[package]] name = "bitvec" version = "0.18.5" @@ -701,7 +731,7 @@ dependencies = [ "borsh-schema-derive-internal", "proc-macro-crate 0.1.5", "proc-macro2 1.0.63", - "syn 1.0.95", + "syn 1.0.109", ] [[package]] @@ -712,7 +742,7 @@ checksum = "5449c28a7b352f2d1e592a8a28bf139bc71afb0764a14f3c02500935d8c44065" dependencies = [ "proc-macro2 1.0.63", "quote 1.0.28", - "syn 1.0.95", + "syn 1.0.109", ] [[package]] @@ -723,7 +753,7 @@ checksum = "cdbd5696d8bfa21d53d9fe39a714a18538bad11492a42d066dbbc395fb1951c0" dependencies = [ "proc-macro2 1.0.63", "quote 1.0.28", - "syn 1.0.95", + "syn 1.0.109", ] [[package]] @@ -789,7 +819,7 @@ checksum = "8e215f8c2f9f79cb53c8335e687ffd07d5bfcb6fe5fc80723762d0be46e7cc54" dependencies = [ "proc-macro2 1.0.63", "quote 1.0.28", - "syn 1.0.95", + "syn 1.0.109", ] [[package]] @@ -854,11 +884,12 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.74" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581f5dba903aac52ea3feb5ec4810848460ee833876f1f9b0fdeab1f19091574" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" dependencies = [ "jobserver", + "libc", ] [[package]] @@ -881,7 +912,7 @@ checksum = "5c80e5460aa66fe3b91d40bcbdab953a597b60053e34d684ac6903f863b680a6" dependencies = [ "cfg-if 1.0.0", "cipher", - "cpufeatures 0.2.1", + "cpufeatures 0.2.11", "zeroize", ] @@ -943,7 +974,7 @@ checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" dependencies = [ "ansi_term", "atty", - "bitflags", + "bitflags 1.3.2", "strsim", "textwrap", "unicode-width", @@ -956,7 +987,7 @@ version = "0.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" dependencies = [ - "bitflags", + "bitflags 1.3.2", ] [[package]] @@ -965,7 +996,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4344512281c643ae7638bbabc3af17a11307803ec8f0fcad9fae512a8bf36467" dependencies = [ - "bitflags", + "bitflags 1.3.2", ] [[package]] @@ -1075,7 +1106,7 @@ dependencies = [ "serde_json", "serialization", "serialization_derive", - "sha2 0.9.9", + "sha2 0.10.7", "sha3 0.9.1", "solana-client", "solana-sdk", @@ -1085,7 +1116,6 @@ dependencies = [ "spv_validation", "tendermint-config", "tendermint-rpc", - "tiny-bip39", "tokio", "tokio-rustls", "tokio-tungstenite-wasm", @@ -1184,7 +1214,7 @@ dependencies = [ "serde_derive", "serde_json", "serde_repr", - "sha2 0.9.9", + "sha2 0.10.7", "shared_ref_counter", "tokio", "uuid 1.2.2", @@ -1309,7 +1339,7 @@ dependencies = [ "k256", "prost", "prost-types", - "rand_core 0.6.3", + "rand_core 0.6.4", "serde", "serde_json", "subtle-encoding", @@ -1328,9 +1358,9 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.1" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469" +checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0" dependencies = [ "libc", ] @@ -1493,9 +1523,12 @@ checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" name = "crypto" version = "1.0.0" dependencies = [ + "argon2", "arrayref", "async-trait", + "base64 0.11.0", "bip32", + "bip39", "bitcrypto", "bs58 0.4.0", "common", @@ -1504,6 +1537,7 @@ dependencies = [ "enum_from", "futures 0.3.28", "hex 0.4.3", + "hmac 0.12.1", "http 0.2.7", "hw_common", "keys", @@ -1513,6 +1547,7 @@ dependencies = [ "mm2_eth", "mm2_metamask", "num-traits", + "openssl", "parking_lot 0.12.0", "primitives", "rpc", @@ -1524,10 +1559,11 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "tiny-bip39", + "sha2 0.10.7", "trezor", "wasm-bindgen-test", "web3", + "zeroize", ] [[package]] @@ -1537,16 +1573,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03c6a1d5fa1de37e071642dfa44ec552ca5b299adb128fab16138e24b548fd21" dependencies = [ "generic-array 0.14.5", - "rand_core 0.6.3", + "rand_core 0.6.4", "subtle 2.4.0", "zeroize", ] [[package]] name = "crypto-common" -version = "0.1.3" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array 0.14.5", "typenum", @@ -1678,7 +1714,7 @@ dependencies = [ "proc-macro2 1.0.63", "quote 1.0.28", "scratch", - "syn 1.0.95", + "syn 1.0.109", ] [[package]] @@ -1695,7 +1731,7 @@ checksum = "b846f081361125bfc8dc9d3940c84e1fd83ba54bbca7b17cd29483c828be0704" dependencies = [ "proc-macro2 1.0.63", "quote 1.0.28", - "syn 1.0.95", + "syn 1.0.109", ] [[package]] @@ -1732,7 +1768,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fdf3fce3ce863539ec1d7fd1b6dcc3c645663376b43ed376bbf887733e4f772" dependencies = [ "data-encoding", - "syn 1.0.95", + "syn 1.0.109", ] [[package]] @@ -1789,7 +1825,7 @@ checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" dependencies = [ "proc-macro2 1.0.63", "quote 1.0.28", - "syn 1.0.95", + "syn 1.0.109", ] [[package]] @@ -1800,7 +1836,7 @@ checksum = "41cb0e6161ad61ed084a36ba71fbba9e3ac5aee3606fb607fe08da6acbcf3d8c" dependencies = [ "proc-macro2 1.0.63", "quote 1.0.28", - "syn 1.0.95", + "syn 1.0.109", ] [[package]] @@ -2018,7 +2054,7 @@ dependencies = [ "ff 0.11.1", "generic-array 0.14.5", "group 0.11.0", - "rand_core 0.6.3", + "rand_core 0.6.4", "sec1", "subtle 2.4.0", "zeroize", @@ -2054,7 +2090,7 @@ dependencies = [ "heck", "proc-macro2 1.0.63", "quote 1.0.28", - "syn 1.0.95", + "syn 1.0.109", ] [[package]] @@ -2065,7 +2101,7 @@ checksum = "c375b9c5eadb68d0a6efee2999fef292f45854c3444c86f09d8ab086ba942b0e" dependencies = [ "num-traits", "quote 1.0.28", - "syn 1.0.95", + "syn 1.0.109", ] [[package]] @@ -2075,7 +2111,7 @@ dependencies = [ "itertools", "proc-macro2 1.0.63", "quote 1.0.28", - "syn 1.0.95", + "syn 1.0.109", ] [[package]] @@ -2241,7 +2277,7 @@ checksum = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4" dependencies = [ "proc-macro2 1.0.63", "quote 1.0.28", - "syn 1.0.95", + "syn 1.0.109", "synstructure", ] @@ -2295,7 +2331,7 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "131655483be284720a17d74ff97592b8e76576dc25563148601df2d7c9080924" dependencies = [ - "rand_core 0.6.3", + "rand_core 0.6.4", "subtle 2.4.0", ] @@ -2334,7 +2370,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfcf0ed7fe52a17a03854ec54a9f76d6d84508d1c0e66bc1793301c73fc8493c" dependencies = [ "byteorder", - "rand 0.8.4", + "rand 0.8.5", "rustc-hex", "static_assertions", ] @@ -2373,6 +2409,21 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "form_urlencoded" version = "1.0.1" @@ -2700,7 +2751,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc5ac374b108929de78460075f3dc439fa66df9d8fc77e8f12caa5165fcf0c89" dependencies = [ "ff 0.11.1", - "rand_core 0.6.3", + "rand_core 0.6.4", "subtle 2.4.0", ] @@ -2835,7 +2886,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84f2a5541afe0725f0b95619d6af614f48c1b176385b8aa30918cfb8c4bfafc8" dependencies = [ "hmac 0.11.0", - "rand_core 0.6.3", + "rand_core 0.6.4", "sha2 0.9.9", "zeroize", ] @@ -2900,6 +2951,15 @@ dependencies = [ "hmac 0.8.1", ] +[[package]] +name = "home" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" +dependencies = [ + "windows-sys 0.48.0", +] + [[package]] name = "hostname" version = "0.3.1" @@ -3150,7 +3210,7 @@ checksum = "d5dacb10c5b3bb92d46ba347505a9041e676bb20ad220101326bffb0c93031ee" dependencies = [ "proc-macro2 1.0.63", "quote 1.0.28", - "syn 1.0.95", + "syn 1.0.109", ] [[package]] @@ -3393,7 +3453,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6607c62aa161d23d17a9072cc5da0be67cdfc89d3afb1e8d9c842bebc2525ffe" dependencies = [ "arrayvec 0.5.1", - "bitflags", + "bitflags 1.3.2", "cfg-if 1.0.0", "ryu", "static_assertions", @@ -3401,9 +3461,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.147" +version = "0.2.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" +checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" [[package]] name = "libloading" @@ -3500,7 +3560,7 @@ dependencies = [ "parking_lot 0.12.0", "pin-project", "quick-protobuf", - "rand 0.8.4", + "rand 0.8.5", "rw-stream-sink", "smallvec 1.6.1", "thiserror", @@ -3537,7 +3597,7 @@ dependencies = [ "log", "quick-protobuf", "quick-protobuf-codec", - "rand 0.8.4", + "rand 0.8.5", "smallvec 1.6.1", "thiserror", ] @@ -3565,7 +3625,7 @@ dependencies = [ "prometheus-client", "quick-protobuf", "quick-protobuf-codec", - "rand 0.8.4", + "rand 0.8.5", "regex", "sha2 0.10.7", "smallvec 1.6.1", @@ -3607,7 +3667,7 @@ dependencies = [ "log", "multihash", "quick-protobuf", - "rand 0.8.4", + "rand 0.8.5", "sha2 0.10.7", "thiserror", "zeroize", @@ -3625,7 +3685,7 @@ dependencies = [ "libp2p-identity", "libp2p-swarm", "log", - "rand 0.8.4", + "rand 0.8.5", "smallvec 1.6.1", "socket2 0.5.3", "tokio", @@ -3664,7 +3724,7 @@ dependencies = [ "multihash", "once_cell", "quick-protobuf", - "rand 0.8.4", + "rand 0.8.5", "sha2 0.10.7", "snow", "static_assertions", @@ -3686,7 +3746,7 @@ dependencies = [ "libp2p-identity", "libp2p-swarm", "log", - "rand 0.8.4", + "rand 0.8.5", "void", ] @@ -3702,7 +3762,7 @@ dependencies = [ "libp2p-identity", "libp2p-swarm", "log", - "rand 0.8.4", + "rand 0.8.5", "smallvec 1.6.1", "void", ] @@ -3723,7 +3783,7 @@ dependencies = [ "log", "multistream-select", "once_cell", - "rand 0.8.4", + "rand 0.8.5", "smallvec 1.6.1", "tokio", "void", @@ -3833,7 +3893,7 @@ dependencies = [ "libsecp256k1-core 0.3.0", "libsecp256k1-gen-ecmult 0.3.0", "libsecp256k1-gen-genmult 0.3.0", - "rand 0.8.4", + "rand 0.8.5", "serde", "sha2 0.9.9", "typenum", @@ -4002,6 +4062,12 @@ version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" +[[package]] +name = "linux-raw-sys" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829" + [[package]] name = "lock_api" version = "0.4.6" @@ -4604,7 +4670,7 @@ dependencies = [ "secp256k1 0.20.3", "serde", "serde_bytes", - "sha2 0.9.9", + "sha2 0.10.7", "smallvec 1.6.1", "syn 2.0.23", "tokio", @@ -4685,7 +4751,7 @@ checksum = "3048ef3680533a27f9f8e7d6a0bce44dc61e4895ea0f42709337fa1c8616fefe" dependencies = [ "proc-macro2 1.0.63", "quote 1.0.28", - "syn 1.0.95", + "syn 1.0.109", ] [[package]] @@ -4766,7 +4832,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9ea4302b9759a7a88242299225ea3688e63c85ea136371bb6cf94fd674efaab" dependencies = [ "anyhow", - "bitflags", + "bitflags 1.3.2", "byteorder", "libc", "netlink-packet-core", @@ -4828,7 +4894,7 @@ version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f866317acbd3a240710c63f065ffb1e4fd466259045ccb504130b7f668f35c6" dependencies = [ - "bitflags", + "bitflags 1.3.2", "cc", "cfg-if 1.0.0", "libc", @@ -4841,7 +4907,7 @@ version = "0.24.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069" dependencies = [ - "bitflags", + "bitflags 1.3.2", "cfg-if 1.0.0", "libc", ] @@ -4885,7 +4951,7 @@ checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" dependencies = [ "proc-macro2 1.0.63", "quote 1.0.28", - "syn 1.0.95", + "syn 1.0.109", ] [[package]] @@ -4949,7 +5015,7 @@ dependencies = [ "proc-macro-crate 1.1.3", "proc-macro2 1.0.63", "quote 1.0.28", - "syn 1.0.95", + "syn 1.0.109", ] [[package]] @@ -4994,12 +5060,50 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +[[package]] +name = "openssl" +version = "0.10.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79a4c6c3a2b158f7f8f2a2fc5a969fa3a068df6fc9dbb4a43845436e3af7c800" +dependencies = [ + "bitflags 2.4.1", + "cfg-if 1.0.0", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2 1.0.63", + "quote 1.0.28", + "syn 2.0.23", +] + [[package]] name = "openssl-probe" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" +[[package]] +name = "openssl-sys" +version = "0.9.96" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3812c071ba60da8b5677cc12bcb1d42989a65553772897a7e0355545a819838f" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "ordered-float" version = "3.7.0" @@ -5030,7 +5134,7 @@ dependencies = [ "proc-macro-error", "proc-macro2 1.0.63", "quote 1.0.28", - "syn 1.0.95", + "syn 1.0.109", ] [[package]] @@ -5076,7 +5180,7 @@ dependencies = [ "proc-macro-crate 1.1.3", "proc-macro2 1.0.63", "quote 1.0.28", - "syn 1.0.95", + "syn 1.0.109", ] [[package]] @@ -5104,7 +5208,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f557c32c6d268a07c921471619c0295f5efad3a0e76d4f97a05c091a51d110b2" dependencies = [ "proc-macro2 1.0.63", - "syn 1.0.95", + "syn 1.0.109", "synstructure", ] @@ -5184,6 +5288,17 @@ dependencies = [ "windows-sys 0.32.0", ] +[[package]] +name = "password-hash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" +dependencies = [ + "base64ct", + "rand_core 0.6.4", + "subtle 2.4.0", +] + [[package]] name = "paste" version = "1.0.7" @@ -5328,7 +5443,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" dependencies = [ "autocfg 1.1.0", - "bitflags", + "bitflags 1.3.2", "cfg-if 1.0.0", "concurrent-queue 2.2.0", "libc", @@ -5379,7 +5494,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f28f53e8b192565862cf99343194579a022eb9c7dd3a8d03134734803c7b3125" dependencies = [ "proc-macro2 1.0.63", - "syn 1.0.95", + "syn 1.0.109", ] [[package]] @@ -5434,7 +5549,7 @@ dependencies = [ "proc-macro-error-attr", "proc-macro2 1.0.63", "quote 1.0.28", - "syn 1.0.95", + "syn 1.0.109", "version_check", ] @@ -5498,7 +5613,7 @@ checksum = "72b6a5217beb0ad503ee7fa752d451c905113d70721b937126158f3106a48cc1" dependencies = [ "proc-macro2 1.0.63", "quote 1.0.28", - "syn 1.0.95", + "syn 1.0.109", ] [[package]] @@ -5543,7 +5658,7 @@ dependencies = [ "itertools", "proc-macro2 1.0.63", "quote 1.0.28", - "syn 1.0.95", + "syn 1.0.109", ] [[package]] @@ -5731,14 +5846,13 @@ dependencies = [ [[package]] name = "rand" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha 0.3.1", - "rand_core 0.6.3", - "rand_hc 0.3.1", + "rand_core 0.6.4", ] [[package]] @@ -5768,7 +5882,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core 0.6.3", + "rand_core 0.6.4", ] [[package]] @@ -5797,9 +5911,9 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ "getrandom 0.2.9", ] @@ -5822,15 +5936,6 @@ dependencies = [ "rand_core 0.5.1", ] -[[package]] -name = "rand_hc" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7" -dependencies = [ - "rand_core 0.6.3", -] - [[package]] name = "rand_isaac" version = "0.1.1" @@ -5899,7 +6004,7 @@ version = "10.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c49596760fce12ca21550ac21dc5a9617b2ea4b6e0aa7d8dab8ff2824fc2bba" dependencies = [ - "bitflags", + "bitflags 1.3.2", ] [[package]] @@ -5960,7 +6065,7 @@ version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" dependencies = [ - "bitflags", + "bitflags 1.3.2", ] [[package]] @@ -6001,7 +6106,7 @@ checksum = "0c523ccaed8ac4b0288948849a350b37d3035827413c458b6a40ddb614bb4f72" dependencies = [ "proc-macro2 1.0.63", "quote 1.0.28", - "syn 1.0.95", + "syn 1.0.109", ] [[package]] @@ -6211,7 +6316,7 @@ version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "01e213bc3ecb39ac32e81e51ebe31fd888a940515173e3a18a35f8c6e896422a" dependencies = [ - "bitflags", + "bitflags 1.3.2", "fallible-iterator", "fallible-streaming-iterator", "hashlink", @@ -6280,7 +6385,7 @@ version = "0.36.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd5c6ff11fecd55b40746d1995a02f2eb375bf8c00d192d521ee09f42bef37bc" dependencies = [ - "bitflags", + "bitflags 1.3.2", "errno 0.2.8", "io-lifetimes", "libc", @@ -6294,7 +6399,7 @@ version = "0.37.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2aae838e49b3d63e9274e1c01833cc8139d3fec468c3b84688c628f44b1ae11d" dependencies = [ - "bitflags", + "bitflags 1.3.2", "errno 0.3.1", "io-lifetimes", "libc", @@ -6302,6 +6407,19 @@ dependencies = [ "windows-sys 0.45.0", ] +[[package]] +name = "rustix" +version = "0.38.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc99bc2d4f1fed22595588a013687477aedf3cdcfb26558c559edb67b4d9b22e" +dependencies = [ + "bitflags 2.4.1", + "errno 0.3.1", + "libc", + "linux-raw-sys 0.4.11", + "windows-sys 0.48.0", +] + [[package]] name = "rustls" version = "0.19.1" @@ -6419,7 +6537,7 @@ dependencies = [ "proc-macro-crate 1.1.3", "proc-macro2 1.0.63", "quote 1.0.28", - "syn 1.0.95", + "syn 1.0.109", ] [[package]] @@ -6549,7 +6667,7 @@ version = "2.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" dependencies = [ - "bitflags", + "bitflags 1.3.2", "core-foundation", "core-foundation-sys", "libc", @@ -6613,7 +6731,7 @@ dependencies = [ "proc-macro2 1.0.63", "quote 1.0.28", "ser_error", - "syn 1.0.95", + "syn 1.0.109", ] [[package]] @@ -6676,7 +6794,7 @@ checksum = "2dc6b7951b17b051f3210b063f12cc17320e2fe30ae05b0fe2a3abb068551c76" dependencies = [ "proc-macro2 1.0.63", "quote 1.0.28", - "syn 1.0.95", + "syn 1.0.109", ] [[package]] @@ -6730,7 +6848,7 @@ checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6" dependencies = [ "block-buffer 0.9.0", "cfg-if 1.0.0", - "cpufeatures 0.2.1", + "cpufeatures 0.2.11", "digest 0.9.0", "opaque-debug 0.3.0", ] @@ -6755,7 +6873,7 @@ checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" dependencies = [ "block-buffer 0.9.0", "cfg-if 1.0.0", - "cpufeatures 0.2.1", + "cpufeatures 0.2.11", "digest 0.9.0", "opaque-debug 0.3.0", ] @@ -6767,7 +6885,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8" dependencies = [ "cfg-if 1.0.0", - "cpufeatures 0.2.1", + "cpufeatures 0.2.11", "digest 0.10.7", ] @@ -6816,7 +6934,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02658e48d89f2bec991f9a78e69cfa4c316f8d6a6c4ec12fae1aeb263d486788" dependencies = [ "digest 0.9.0", - "rand_core 0.6.3", + "rand_core 0.6.4", ] [[package]] @@ -6886,7 +7004,7 @@ dependencies = [ "blake2", "chacha20poly1305", "curve25519-dalek 4.0.0-rc.1", - "rand_core 0.6.3", + "rand_core 0.6.4", "ring", "rustc_version 0.4.0", "sha2 0.10.7", @@ -6935,7 +7053,7 @@ dependencies = [ "futures 0.3.28", "httparse", "log", - "rand 0.8.4", + "rand 0.8.5", "sha-1", ] @@ -7160,7 +7278,7 @@ dependencies = [ "proc-macro2 1.0.63", "quote 1.0.28", "rustc_version 0.4.0", - "syn 1.0.95", + "syn 1.0.109", ] [[package]] @@ -7256,7 +7374,7 @@ checksum = "0a463f546a2f5842d35974bd4691ae5ceded6785ec24db440f773723f6ce4e11" dependencies = [ "base64 0.13.0", "bincode", - "bitflags", + "bitflags 1.3.2", "blake3", "borsh", "borsh-derive", @@ -7410,7 +7528,7 @@ dependencies = [ "assert_matches", "base64 0.13.0", "bincode", - "bitflags", + "bitflags 1.3.2", "borsh", "bs58 0.4.0", "bytemuck", @@ -7462,7 +7580,7 @@ dependencies = [ "proc-macro2 1.0.63", "quote 1.0.28", "rustversion", - "syn 1.0.95", + "syn 1.0.109", ] [[package]] @@ -7558,7 +7676,7 @@ version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77963e2aa8fadb589118c3aede2e78b6c4bcf1c01d588fbf33e915b390825fbd" dependencies = [ - "bitflags", + "bitflags 1.3.2", "byteorder", "hash-db", "hash256-std-hasher", @@ -7585,7 +7703,7 @@ checksum = "d676664972e22a0796176e81e7bec41df461d1edf52090955cdab55f2c956ff2" dependencies = [ "proc-macro2 1.0.63", "quote 1.0.28", - "syn 1.0.95", + "syn 1.0.109", ] [[package]] @@ -7615,7 +7733,7 @@ dependencies = [ "proc-macro-crate 1.1.3", "proc-macro2 1.0.63", "quote 1.0.28", - "syn 1.0.95", + "syn 1.0.109", ] [[package]] @@ -7740,7 +7858,7 @@ dependencies = [ "serde", "serde_json", "serialization", - "sha2 0.9.9", + "sha2 0.10.7", "test_helpers", ] @@ -7837,9 +7955,9 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.95" +version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbaf6116ab8924f39d52792136fb74fd60a80194cf1b1c6ffa6453eef1c3f942" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2 1.0.63", "quote 1.0.28", @@ -7880,7 +7998,7 @@ checksum = "b834f2d66f734cb897113e34aaff2f1ab4719ca946f9a7358dba8f8064148701" dependencies = [ "proc-macro2 1.0.63", "quote 1.0.28", - "syn 1.0.95", + "syn 1.0.109", "unicode-xid 0.2.0", ] @@ -7890,7 +8008,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" dependencies = [ - "bitflags", + "bitflags 1.3.2", "core-foundation", "system-configuration-sys", ] @@ -8442,7 +8560,7 @@ dependencies = [ "proc-macro2 1.0.63", "prost-build", "quote 1.0.28", - "syn 1.0.95", + "syn 1.0.109", ] [[package]] @@ -8456,7 +8574,7 @@ dependencies = [ "indexmap", "pin-project", "pin-project-lite 0.2.9", - "rand 0.8.4", + "rand 0.8.5", "slab", "tokio", "tokio-util", @@ -8471,7 +8589,7 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d342c6d58709c0a6d48d48dabbb62d4ef955cf5f0f3bbfd845838e7ae88dbae" dependencies = [ - "bitflags", + "bitflags 1.3.2", "bytes 1.4.0", "futures-core", "futures-util", @@ -8609,7 +8727,7 @@ dependencies = [ "idna", "ipnet", "lazy_static", - "rand 0.8.4", + "rand 0.8.5", "smallvec 1.6.1", "socket2 0.4.9", "thiserror", @@ -8657,7 +8775,7 @@ dependencies = [ "http 0.2.7", "httparse", "log", - "rand 0.8.4", + "rand 0.8.5", "rustls 0.20.4", "sha-1", "thiserror", @@ -8707,9 +8825,9 @@ checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee" [[package]] name = "unicode-normalization" -version = "0.1.19" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" dependencies = [ "tinyvec", ] @@ -8824,7 +8942,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "422ee0de9031b5b948b97a8fc04e3aa35230001a722ddd27943e0be31564ce4c" dependencies = [ "getrandom 0.2.9", - "rand 0.8.4", + "rand 0.8.5", "serde", ] @@ -9016,7 +9134,7 @@ dependencies = [ "log", "parking_lot 0.12.0", "pin-project", - "rand 0.8.4", + "rand 0.8.5", "rlp", "serde", "serde-wasm-bindgen", @@ -9075,12 +9193,14 @@ dependencies = [ [[package]] name = "which" -version = "4.1.0" +version = "4.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b55551e42cbdf2ce2bedd2203d0cc08dba002c27510f86dab6d0ce304cba3dfe" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" dependencies = [ "either", - "libc", + "home", + "once_cell", + "rustix 0.38.25", ] [[package]] @@ -9416,7 +9536,7 @@ dependencies = [ "log", "nohash-hasher", "parking_lot 0.12.0", - "rand 0.8.4", + "rand 0.8.5", "static_assertions", ] @@ -9557,7 +9677,7 @@ checksum = "3f8f187641dad4f680d25c4bfc4225b418165984179f26ca76ec4fb6441d3a17" dependencies = [ "proc-macro2 1.0.63", "quote 1.0.28", - "syn 1.0.95", + "syn 1.0.109", "synstructure", ] diff --git a/mm2src/adex_cli/Cargo.lock b/mm2src/adex_cli/Cargo.lock index 7d513ab8d2..27dc36bbfa 100644 --- a/mm2src/adex_cli/Cargo.lock +++ b/mm2src/adex_cli/Cargo.lock @@ -290,7 +290,7 @@ dependencies = [ "ripemd160", "serialization", "sha-1", - "sha2", + "sha2 0.10.8", "sha3", "siphasher", ] @@ -334,6 +334,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + [[package]] name = "block-padding" version = "0.2.1" @@ -531,7 +540,7 @@ dependencies = [ "serde_derive", "serde_json", "serde_repr", - "sha2", + "sha2 0.10.8", "tokio", "uuid", "wasm-bindgen", @@ -686,6 +695,16 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + [[package]] name = "crypto-mac" version = "0.8.0" @@ -774,6 +793,16 @@ dependencies = [ "generic-array", ] +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer 0.10.4", + "crypto-common", +] + [[package]] name = "directories" version = "5.0.1" @@ -1123,8 +1152,8 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2432787a9b8f0d58dca43fe2240399479b7582dc8afa2126dc7652b864029e47" dependencies = [ - "block-buffer", - "digest", + "block-buffer 0.9.0", + "digest 0.9.0", "opaque-debug", ] @@ -1234,7 +1263,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "126888268dcc288495a26bf004b38c5fdbb31682f992c84ceb046a1f0fe38840" dependencies = [ "crypto-mac", - "digest", + "digest 0.9.0", ] [[package]] @@ -2567,8 +2596,8 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2eca4ecc81b7f313189bf73ce724400a07da2a6dac19588b03c8bd76a2dcc251" dependencies = [ - "block-buffer", - "digest", + "block-buffer 0.9.0", + "digest 0.9.0", "opaque-debug", ] @@ -2952,10 +2981,10 @@ version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6" dependencies = [ - "block-buffer", + "block-buffer 0.9.0", "cfg-if 1.0.0", "cpufeatures", - "digest", + "digest 0.9.0", "opaque-debug", ] @@ -2965,21 +2994,32 @@ version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" dependencies = [ - "block-buffer", + "block-buffer 0.9.0", "cfg-if 1.0.0", "cpufeatures", - "digest", + "digest 0.9.0", "opaque-debug", ] +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if 1.0.0", + "cpufeatures", + "digest 0.10.7", +] + [[package]] name = "sha3" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f81199417d4e5de3f04b1e871023acea7389672c4135918f05aa9cbf2f2fa809" dependencies = [ - "block-buffer", - "digest", + "block-buffer 0.9.0", + "digest 0.9.0", "keccak", "opaque-debug", ] @@ -3219,7 +3259,7 @@ dependencies = [ "pbkdf2", "rand 0.7.3", "rustc-hash", - "sha2", + "sha2 0.9.9", "thiserror", "unicode-normalization", "wasm-bindgen", diff --git a/mm2src/coins/Cargo.toml b/mm2src/coins/Cargo.toml index b2ccc8c227..9cf9c1434c 100644 --- a/mm2src/coins/Cargo.toml +++ b/mm2src/coins/Cargo.toml @@ -95,12 +95,11 @@ serde_json = { version = "1", features = ["preserve_order", "raw_value"] } serialization = { path = "../mm2_bitcoin/serialization" } serialization_derive = { path = "../mm2_bitcoin/serialization_derive" } spv_validation = { path = "../mm2_bitcoin/spv_validation" } -sha2 = "0.9" +sha2 = "0.10" sha3 = "0.9" utxo_signer = { path = "utxo_signer" } # using the same version as cosmrs tendermint-rpc = { version = "=0.23.7", default-features = false } -tiny-bip39 = "0.8.0" url = { version = "2.2.2", features = ["serde"] } uuid = { version = "1.2.2", features = ["fast-rng", "serde", "v4"] } # One of web3 dependencies is the old `tokio-uds 0.1.7` which fails cross-compiling to ARM. diff --git a/mm2src/coins/solana/solana_common_tests.rs b/mm2src/coins/solana/solana_common_tests.rs index 32b030b425..7706cc8abf 100644 --- a/mm2src/coins/solana/solana_common_tests.rs +++ b/mm2src/coins/solana/solana_common_tests.rs @@ -1,7 +1,6 @@ use super::*; use crate::solana::spl::{SplToken, SplTokenFields}; -use bip39::Language; -use crypto::privkey::key_pair_from_seed; +use crypto::privkey::{bip39_seed_from_passphrase, key_pair_from_seed}; use ed25519_dalek_bip32::{DerivationPath, ExtendedSecretKey}; use mm2_core::mm_ctx::MmCtxBuilder; use solana_client::rpc_client::RpcClient; @@ -22,13 +21,11 @@ pub fn solana_net_to_url(net_type: SolanaNet) -> String { } } -pub fn generate_key_pair_from_seed(seed: String) -> Keypair { +pub fn generate_key_pair_from_seed(seed: &str) -> Keypair { let derivation_path = DerivationPath::from_str("m/44'/501'/0'").unwrap(); - let mnemonic = bip39::Mnemonic::from_phrase(seed.as_str(), Language::English).unwrap(); - let seed = bip39::Seed::new(&mnemonic, ""); - let seed_bytes: &[u8] = seed.as_bytes(); + let seed = bip39_seed_from_passphrase(seed).unwrap(); - let ext = ExtendedSecretKey::from_seed(seed_bytes) + let ext = ExtendedSecretKey::from_seed(&seed.0) .unwrap() .derive(&derivation_path) .unwrap(); diff --git a/mm2src/coins/solana/solana_tests.rs b/mm2src/coins/solana/solana_tests.rs index 05939b68c5..2f88f1df0d 100644 --- a/mm2src/coins/solana/solana_tests.rs +++ b/mm2src/coins/solana/solana_tests.rs @@ -35,9 +35,8 @@ fn solana_keypair_from_secp() { fn solana_prerequisites() { // same test as trustwallet { - let fin = generate_key_pair_from_seed( - "hood vacant left trim hard mushroom device flavor ask better arrest again".to_string(), - ); + let fin = + generate_key_pair_from_seed("hood vacant left trim hard mushroom device flavor ask better arrest again"); let public_address = fin.pubkey().to_string(); let priv_key = &fin.secret().to_bytes()[..].to_base58(); assert_eq!(public_address.len(), 44); diff --git a/mm2src/common/Cargo.toml b/mm2src/common/Cargo.toml index 47608b2b02..25b83abf54 100644 --- a/mm2src/common/Cargo.toml +++ b/mm2src/common/Cargo.toml @@ -41,7 +41,7 @@ serde_derive = "1" serde_json = { version = "1", features = ["preserve_order", "raw_value"] } ser_error = { path = "../derives/ser_error" } ser_error_derive = { path = "../derives/ser_error_derive" } -sha2 = "0.9" +sha2 = "0.10" shared_ref_counter = { path = "shared_ref_counter", optional = true } uuid = { version = "1.2.2", features = ["fast-rng", "serde", "v4"] } instant = { version = "0.1.12" } diff --git a/mm2src/crypto/Cargo.toml b/mm2src/crypto/Cargo.toml index ff28977341..2cd1dc4972 100644 --- a/mm2src/crypto/Cargo.toml +++ b/mm2src/crypto/Cargo.toml @@ -7,9 +7,12 @@ edition = "2018" doctest = false [dependencies] +argon2 = { version = "0.5.2", features = ["zeroize"] } arrayref = "0.3" async-trait = "0.1" +base64 = "0.11.0" bip32 = { version = "0.2.2", default-features = false, features = ["alloc", "secp256k1-ffi"] } +bip39 = { version = "2.0.0", features = ["rand_core", "zeroize"], default-features = false } bitcrypto = { path = "../mm2_bitcoin/crypto" } bs58 = "0.4.0" common = { path = "../common" } @@ -18,6 +21,7 @@ enum_from = { path = "../derives/enum_from" } enum-primitive-derive = "0.2" futures = "0.3" hex = "0.4.2" +hmac = "0.12.1" http = "0.2" hw_common = { path = "../hw_common" } keys = { path = "../mm2_bitcoin/keys" } @@ -25,6 +29,7 @@ lazy_static = "1.4" mm2_core = { path = "../mm2_core" } mm2_err_handle = { path = "../mm2_err_handle" } num-traits = "0.2" +openssl = "0.10.60" parking_lot = { version = "0.12.0", features = ["nightly"] } primitives = { path = "../mm2_bitcoin/primitives" } rpc = { path = "../mm2_bitcoin/rpc" } @@ -36,8 +41,9 @@ ser_error_derive = { path = "../derives/ser_error_derive" } serde = "1.0" serde_derive = "1.0" serde_json = { version = "1", features = ["preserve_order", "raw_value"] } -tiny-bip39 = "0.8.0" +sha2 = "0.10" trezor = { path = "../trezor" } +zeroize = { version = "1.5", features = ["zeroize_derive"] } [target.'cfg(target_arch = "wasm32")'.dependencies] mm2_eth = { path = "../mm2_eth" } diff --git a/mm2src/crypto/src/global_hd_ctx.rs b/mm2src/crypto/src/global_hd_ctx.rs index 79326e9889..15cb2cffbb 100644 --- a/mm2src/crypto/src/global_hd_ctx.rs +++ b/mm2src/crypto/src/global_hd_ctx.rs @@ -8,6 +8,7 @@ use keys::{KeyPair, Secret as Secp256k1Secret}; use mm2_err_handle::prelude::*; use std::ops::Deref; use std::sync::Arc; +use zeroize::{Zeroize, ZeroizeOnDrop}; const HARDENED: bool = true; const NON_HARDENED: bool = false; @@ -23,8 +24,11 @@ impl Deref for GlobalHDAccountArc { fn deref(&self) -> &Self::Target { &self.0 } } +#[derive(Zeroize, ZeroizeOnDrop)] +pub struct Bip39Seed(pub [u8; 64]); + pub struct GlobalHDAccountCtx { - bip39_seed: bip39::Seed, + bip39_seed: Bip39Seed, bip39_secp_priv_key: ExtendedPrivateKey, } @@ -32,8 +36,7 @@ impl GlobalHDAccountCtx { pub fn new(passphrase: &str) -> CryptoInitResult<(Mm2InternalKeyPair, GlobalHDAccountCtx)> { let bip39_seed = bip39_seed_from_passphrase(passphrase)?; let bip39_secp_priv_key: ExtendedPrivateKey = - ExtendedPrivateKey::new(bip39_seed.as_bytes()) - .map_to_mm(|e| PrivKeyError::InvalidPrivKey(e.to_string()))?; + ExtendedPrivateKey::new(bip39_seed.0).map_to_mm(|e| PrivKeyError::InvalidPrivKey(e.to_string()))?; let derivation_path = mm2_internal_der_path(); @@ -57,10 +60,10 @@ impl GlobalHDAccountCtx { pub fn into_arc(self) -> GlobalHDAccountArc { GlobalHDAccountArc(Arc::new(self)) } /// Returns the root BIP39 seed. - pub fn root_seed(&self) -> &bip39::Seed { &self.bip39_seed } + pub fn root_seed(&self) -> &Bip39Seed { &self.bip39_seed } /// Returns the root BIP39 seed as bytes. - pub fn root_seed_bytes(&self) -> &[u8] { self.bip39_seed.as_bytes() } + pub fn root_seed_bytes(&self) -> &[u8] { &self.bip39_seed.0 } /// Returns the root BIP39 private key. pub fn root_priv_key(&self) -> &ExtendedPrivateKey { &self.bip39_secp_priv_key } diff --git a/mm2src/crypto/src/lib.rs b/mm2src/crypto/src/lib.rs index da9ff99a29..a3815cf418 100644 --- a/mm2src/crypto/src/lib.rs +++ b/mm2src/crypto/src/lib.rs @@ -7,6 +7,7 @@ mod hw_client; mod hw_ctx; mod hw_error; pub mod hw_rpc_task; +pub mod mnemonic; pub mod privkey; mod shared_db_id; mod standard_hd_path; @@ -26,6 +27,7 @@ pub use hw_common::primitives::{Bip32Error, ChildNumber, DerivationPath, EcdsaCu pub use hw_ctx::{HardwareWalletArc, HardwareWalletCtx}; pub use hw_error::{from_hw_error, HwError, HwResult, HwRpcError, WithHwRpcError}; pub use keys::Secret as Secp256k1Secret; +pub use mnemonic::{decrypt_mnemonic, encrypt_mnemonic, generate_mnemonic, EncryptedMnemonicData, MnemonicError}; pub use standard_hd_path::{Bip44Chain, StandardHDCoinAddress, StandardHDPath, StandardHDPathError, StandardHDPathToAccount, StandardHDPathToCoin, UnknownChainError}; pub use trezor; diff --git a/mm2src/crypto/src/mnemonic.rs b/mm2src/crypto/src/mnemonic.rs new file mode 100644 index 0000000000..9374bbf9cd --- /dev/null +++ b/mm2src/crypto/src/mnemonic.rs @@ -0,0 +1,352 @@ +use argon2::password_hash::{PasswordHasher, SaltString}; +use argon2::Argon2; +use bip39::{Language, Mnemonic}; +use common::drop_mutability; +use derive_more::Display; +use hmac::{Hmac, Mac}; +use mm2_core::mm_ctx::MmArc; +use mm2_err_handle::prelude::*; +use openssl::symm::{decrypt, encrypt, Cipher}; +use sha2::Sha256; +use std::convert::TryInto; + +const DEFAULT_WORD_COUNT: u64 = 24; + +#[derive(Debug, Display)] +pub enum MnemonicError { + #[display(fmt = "BIP39 mnemonic error: {}", _0)] + BIP39Error(String), + #[display(fmt = "Error generating random bytes: {}", _0)] + UnableToGenerateRandomBytes(String), + #[display(fmt = "Error hashing password: {}", _0)] + PasswordHashingFailed(String), + #[display(fmt = "AES cipher error: {}", _0)] + AESCipherError(String), + #[display(fmt = "Error decoding string: {}", _0)] + DecodeError(String), + #[display(fmt = "Error verifying HMAC tag: {}", _0)] + HMACError(String), + Internal(String), +} + +impl From for MnemonicError { + fn from(e: bip39::Error) -> Self { MnemonicError::BIP39Error(e.to_string()) } +} + +impl From for MnemonicError { + fn from(e: argon2::password_hash::Error) -> Self { MnemonicError::PasswordHashingFailed(e.to_string()) } +} + +impl From for MnemonicError { + fn from(e: openssl::error::ErrorStack) -> Self { MnemonicError::AESCipherError(e.to_string()) } +} + +impl From for MnemonicError { + fn from(e: base64::DecodeError) -> Self { MnemonicError::DecodeError(e.to_string()) } +} + +/// Parameters for the Argon2 key derivation function. +/// +/// This struct defines the configuration parameters used by Argon2, one of the +/// most secure and widely used key derivation functions, especially for +/// password hashing. +#[derive(Serialize, Deserialize, Debug)] +pub struct Argon2Params { + /// The specific variant of the Argon2 algorithm used (e.g., Argon2id). + algorithm: String, + + /// The version of the Argon2 algorithm (e.g., 0x13 for the latest version). + version: String, + + /// The memory cost parameter defining the memory usage of the algorithm. + /// Expressed in kibibytes (KiB). + m_cost: u32, + + /// The time cost parameter defining the execution time and number of + /// iterations of the algorithm. + t_cost: u32, + + /// The parallelism cost parameter defining the number of parallel threads. + p_cost: u32, +} + +impl Default for Argon2Params { + fn default() -> Self { + Argon2Params { + algorithm: "Argon2id".to_string(), + version: "0x13".to_string(), + m_cost: 65536, + t_cost: 2, + p_cost: 1, + } + } +} + +/// Enum representing different key derivation details. +/// +/// This enum allows for flexible specification of various key derivation +/// algorithms and their parameters, making it easier to extend and support +/// multiple algorithms in the future. +#[derive(Serialize, Deserialize, Debug)] +pub enum KeyDerivationDetails { + /// Argon2 algorithm with its specific parameters. + Argon2(Argon2Params), + // Placeholder for future algorithms. + // Future algorithms can be added here. +} + +impl Default for KeyDerivationDetails { + fn default() -> Self { KeyDerivationDetails::Argon2(Argon2Params::default()) } +} + +/// Represents encrypted mnemonic data for a wallet. +/// +/// This struct encapsulates all essential components required to securely encrypt +/// and subsequently decrypt a wallet mnemonic. It is designed to be self-contained, +/// meaning it includes not only the encrypted data but also all the necessary metadata +/// and parameters for decryption. This makes the struct portable and convenient for +/// use in various scenarios, allowing decryption of the mnemonic in different +/// environments or applications, provided the correct password is supplied. +/// +/// It includes the following: +/// - The encryption algorithm used, ensuring compatibility during decryption. +/// - Detailed key derivation details, including the algorithm and its parameters, +/// essential for recreating the encryption key from the user's password. +/// - The Base64-encoded salt for AES key derivation, IV (Initialization Vector), +/// and the ciphertext itself. +/// - If HMAC is used, it also includes the salt for HMAC key derivation and the HMAC tag, +/// which are crucial for ensuring the integrity and authenticity of the encrypted data. +/// +/// The structure is typically used for wallet encryption in blockchain-based applications, +/// providing a robust and comprehensive approach to securing sensitive mnemonic data.. +#[derive(Serialize, Deserialize, Debug)] +pub struct EncryptedMnemonicData { + /// The encryption algorithm used to encrypt the mnemonic. + /// Example: "AES-256-CBC". + encryption_algorithm: String, + + /// Detailed information about the key derivation process. This includes + /// the specific algorithm used (e.g., Argon2) and its parameters. + key_derivation_details: KeyDerivationDetails, + + /// The salt used in the key derivation process for the AES key. + /// Stored as a Base64-encoded string. + salt_aes: String, + + /// The initialization vector (IV) used in the AES encryption process. + /// The IV ensures that the encryption process produces unique ciphertext + /// for the same plaintext and key when encrypted multiple times. + /// Stored as a Base64-encoded string. + iv: String, + + /// The encrypted mnemonic data. This is the ciphertext generated + /// using the specified encryption algorithm, key, and IV. + /// Stored as a Base64-encoded string. + ciphertext: String, + + /// The salt used in the key derivation process for the HMAC key. + /// This is applicable if HMAC is used for ensuring data integrity and authenticity. + /// Stored as a Base64-encoded string. + salt_hmac: String, + + /// The HMAC tag used for verifying the integrity and authenticity of the encrypted data. + /// This tag is crucial for validating that the data has not been tampered with. + /// Stored as a Base64-encoded string. + tag: String, +} + +/// Generates a new mnemonic passphrase. +/// +/// This function creates a new mnemonic passphrase using a specified word count and randomness source. +/// The generated mnemonic is intended for use as a wallet mnemonic. +/// +/// # Arguments +/// * `ctx` - The `MmArc` context containing the application configuration. +/// +/// # Returns +/// `MmInitResult` - The generated mnemonic passphrase or an error if generation fails. +/// +/// # Errors +/// Returns `MmInitError::Internal` if mnemonic generation fails. +pub fn generate_mnemonic(ctx: &MmArc) -> MmResult { + let mut rng = bip39::rand_core::OsRng; + let word_count = ctx.conf["word_count"].as_u64().unwrap_or(DEFAULT_WORD_COUNT) as usize; + let mnemonic = Mnemonic::generate_in_with(&mut rng, Language::English, word_count)?; + Ok(mnemonic) +} + +/// Derives AES and HMAC keys from a given password and salts. +/// +/// # Arguments +/// * `password` - The password used for key derivation. +/// * `salt_aes` - The salt used for AES key derivation. +/// * `salt_hmac` - The salt used for HMAC key derivation. +/// +/// # Returns +/// A tuple containing the AES key and HMAC key as byte arrays, or a `MnemonicError` in case of failure. +fn derive_aes_hmac_keys( + password: &str, + salt_aes: &SaltString, + salt_hmac: &SaltString, +) -> MmResult<([u8; 32], [u8; 32]), MnemonicError> { + let argon2 = Argon2::default(); + + // Derive AES Key + let aes_password_hash = argon2.hash_password(password.as_bytes(), salt_aes)?; + let key_aes_output = aes_password_hash + .serialize() + .hash() + .ok_or_else(|| MnemonicError::PasswordHashingFailed("Error finding AES key hashing output".to_string()))?; + let key_aes = key_aes_output + .as_bytes() + .try_into() + .map_err(|_| MnemonicError::PasswordHashingFailed("Invalid AES key length".to_string()))?; + + // Derive HMAC Key + let hmac_password_hash = argon2.hash_password(password.as_bytes(), salt_hmac)?; + let key_hmac_output = hmac_password_hash + .serialize() + .hash() + .ok_or_else(|| MnemonicError::PasswordHashingFailed("Error finding HMAC key hashing output".to_string()))?; + let key_hmac = key_hmac_output + .as_bytes() + .try_into() + .map_err(|_| MnemonicError::PasswordHashingFailed("Invalid HMAC key length".to_string()))?; + + Ok((key_aes, key_hmac)) +} + +/// Encrypts a mnemonic phrase using a specified password. +/// +/// This function performs several operations: +/// - It generates salts for AES and HMAC key derivation. +/// - It derives the keys using the Argon2 algorithm. +/// - It encrypts the mnemonic using AES-256-CBC. +/// - It creates an HMAC tag for verifying the integrity and authenticity of the encrypted data. +/// +/// # Arguments +/// * `mnemonic` - A `&str` reference to the mnemonic that needs to be encrypted. +/// * `password` - A `&str` reference to the password used for key derivation. +/// +/// # Returns +/// `MmResult` - The result is either an `EncryptedMnemonicData` +/// struct containing all the necessary components for decryption, or a `MnemonicError` in case of failure. +/// +/// # Errors +/// This function can return various errors related to key derivation, encryption, and data encoding. +pub fn encrypt_mnemonic(mnemonic: &str, password: &str) -> MmResult { + use argon2::password_hash::rand_core::OsRng; + + // Generate salt for AES key + let salt_aes = SaltString::generate(&mut OsRng); + + // Generate salt for HMAC key + let salt_hmac = SaltString::generate(&mut OsRng); + + // Generate IV + let mut iv = [0u8; 16]; + common::os_rng(&mut iv).map_to_mm(|e| MnemonicError::UnableToGenerateRandomBytes(e.to_string()))?; + drop_mutability!(iv); + + // Derive AES and HMAC keys + let (key_aes, key_hmac) = derive_aes_hmac_keys(password, &salt_aes, &salt_hmac)?; + + // Create an AES-256-CBC cipher instance, encrypt the data with the key and the IV and get the ciphertext + let cipher = Cipher::aes_256_cbc(); + let ciphertext = encrypt(cipher, &key_aes, Some(&iv), mnemonic.as_bytes())?; + + // Create HMAC tag + let mut mac = Hmac::::new_from_slice(&key_hmac).map_to_mm(|e| MnemonicError::Internal(e.to_string()))?; + mac.update(&ciphertext); + mac.update(&iv); + let tag = mac.finalize().into_bytes(); + + let encrypted_mnemonic_data = EncryptedMnemonicData { + encryption_algorithm: "AES-256-CBC".to_string(), + key_derivation_details: KeyDerivationDetails::default(), + salt_aes: salt_aes.as_str().to_string(), + iv: base64::encode(&iv), + ciphertext: base64::encode(&ciphertext), + salt_hmac: salt_hmac.as_str().to_string(), + tag: base64::encode(&tag), + }; + + Ok(encrypted_mnemonic_data) +} + +/// Decrypts an encrypted mnemonic phrase using a specified password. +/// +/// This function performs the reverse operations of `encrypt_mnemonic`. It: +/// - Decodes and re-creates the necessary salts, IV, and ciphertext from the `EncryptedMnemonicData`. +/// - Derives the AES and HMAC keys using the Argon2 algorithm. +/// - Verifies the integrity and authenticity of the data using the HMAC tag. +/// - Decrypts the mnemonic using AES-256-CBC. +/// +/// # Arguments +/// * `encrypted_data` - A reference to the `EncryptedMnemonicData` containing the encrypted mnemonic and related metadata. +/// * `password` - A `&str` reference to the password used for key derivation. +/// +/// # Returns +/// `MmResult` - The result is either a `Mnemonic` instance if decryption is successful, +/// or a `MnemonicError` in case of failure. +/// +/// # Errors +/// This function can return various errors related to decoding, key derivation, encryption, and HMAC verification. +pub fn decrypt_mnemonic(encrypted_data: &EncryptedMnemonicData, password: &str) -> MmResult { + // Decode the Base64-encoded values + let iv = base64::decode(&encrypted_data.iv)?; + let ciphertext = base64::decode(&encrypted_data.ciphertext)?; + let tag = base64::decode(&encrypted_data.tag)?; + + // Re-create the salts from Base64-encoded strings + let salt_aes = SaltString::from_b64(&encrypted_data.salt_aes)?; + let salt_hmac = SaltString::from_b64(&encrypted_data.salt_hmac)?; + + // Re-create the keys from the password and salts + let (key_aes, key_hmac) = derive_aes_hmac_keys(password, &salt_aes, &salt_hmac)?; + + // Verify HMAC tag before decrypting + let mut mac = Hmac::::new_from_slice(&key_hmac).map_to_mm(|e| MnemonicError::Internal(e.to_string()))?; + mac.update(&ciphertext); + mac.update(&iv); + mac.verify_slice(&tag) + .map_to_mm(|e| MnemonicError::HMACError(e.to_string()))?; + + // Decrypt the ciphertext + let cipher = Cipher::aes_256_cbc(); + let decrypted_data = decrypt(cipher, &key_aes, Some(&iv), &ciphertext)?; + + // Convert decrypted data back to a string + let mnemonic_str = String::from_utf8(decrypted_data).map_to_mm(|e| MnemonicError::DecodeError(e.to_string()))?; + let mnemonic = Mnemonic::parse_normalized(&mnemonic_str)?; + Ok(mnemonic) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_encrypt_decrypt_mnemonic() { + let mnemonic = "tank abandon bind salon remove wisdom net size aspect direct source fossil"; + let password = "password"; + + // Verify that the mnemonic is valid + let parsed_mnemonic = Mnemonic::parse_normalized(mnemonic); + assert!(parsed_mnemonic.is_ok()); + let parsed_mnemonic = parsed_mnemonic.unwrap(); + + // Encrypt the mnemonic + let encrypted_data = encrypt_mnemonic(mnemonic, password); + assert!(encrypted_data.is_ok()); + let encrypted_data = encrypted_data.unwrap(); + + // Decrypt the mnemonic + let decrypted_mnemonic = decrypt_mnemonic(&encrypted_data, password); + assert!(decrypted_mnemonic.is_ok()); + let decrypted_mnemonic = decrypted_mnemonic.unwrap(); + + // Verify if decrypted mnemonic matches the original + assert_eq!(decrypted_mnemonic, parsed_mnemonic); + } +} diff --git a/mm2src/crypto/src/privkey.rs b/mm2src/crypto/src/privkey.rs index 98e18e4df6..7e10e89220 100644 --- a/mm2src/crypto/src/privkey.rs +++ b/mm2src/crypto/src/privkey.rs @@ -19,6 +19,7 @@ // marketmaker // +use crate::global_hd_ctx::Bip39Seed; use bitcrypto::{sha256, ChecksumType}; use derive_more::Display; use keys::{Error as KeysError, KeyPair, Private, Secret as Secp256k1Secret}; @@ -115,10 +116,11 @@ pub fn key_pair_from_secret(secret: &[u8]) -> PrivKeyResult { Ok(KeyPair::from_private(private)?) } -pub fn bip39_seed_from_passphrase(passphrase: &str) -> PrivKeyResult { - let mnemonic = bip39::Mnemonic::from_phrase(passphrase, bip39::Language::English) +pub fn bip39_seed_from_passphrase(passphrase: &str) -> PrivKeyResult { + let mnemonic = bip39::Mnemonic::parse_in_normalized(bip39::Language::English, passphrase) .map_to_mm(|e| PrivKeyError::ErrorParsingPassphrase(e.to_string()))?; - Ok(bip39::Seed::new(&mnemonic, "")) + let seed = mnemonic.to_seed_normalized(""); + Ok(Bip39Seed(seed)) } #[derive(Clone, Copy, Debug)] diff --git a/mm2src/gossipsub/Cargo.toml b/mm2src/gossipsub/Cargo.toml index bc4c37e8cb..a6312a4e61 100644 --- a/mm2src/gossipsub/Cargo.toml +++ b/mm2src/gossipsub/Cargo.toml @@ -25,7 +25,7 @@ libp2p-core = { git = "https://github.com/libp2p/rust-libp2p.git", tag ="v0.45.1 log = "0.4.17" prost = "0.10" rand = "0.7" -sha2 = "0.9" +sha2 = "0.10" smallvec = "1.1.0" unsigned-varint = { version = "0.4.0", features = ["futures-codec"] } wasm-timer = "0.2.4" diff --git a/mm2src/mm2_bitcoin/crypto/Cargo.toml b/mm2src/mm2_bitcoin/crypto/Cargo.toml index fba1c0851f..20a62aa693 100644 --- a/mm2src/mm2_bitcoin/crypto/Cargo.toml +++ b/mm2src/mm2_bitcoin/crypto/Cargo.toml @@ -11,7 +11,7 @@ groestl = "0.9" primitives = { path = "../primitives" } ripemd160 = "0.9.0" sha-1 = "0.9" -sha2 = "0.9" +sha2 = "0.10" sha3 = "0.9" siphasher = "0.1.1" serialization = { path = "../serialization" } diff --git a/mm2src/mm2_bitcoin/crypto/src/lib.rs b/mm2src/mm2_bitcoin/crypto/src/lib.rs index afaa9e4102..f8e6e59388 100644 --- a/mm2src/mm2_bitcoin/crypto/src/lib.rs +++ b/mm2src/mm2_bitcoin/crypto/src/lib.rs @@ -11,7 +11,7 @@ use groestl::Groestl512; use primitives::hash::{H160, H256, H32, H512}; use ripemd160::{Digest, Ripemd160}; use sha1::Sha1; -use sha2::Sha256; +use sha2::{Digest as Sha2Digest, Sha256}; use sha3::Keccak256; use siphasher::sip::SipHasher24; use std::hash::Hasher; diff --git a/mm2src/mm2_bitcoin/spv_validation/Cargo.toml b/mm2src/mm2_bitcoin/spv_validation/Cargo.toml index 1627ff24a3..53e840955b 100644 --- a/mm2src/mm2_bitcoin/spv_validation/Cargo.toml +++ b/mm2src/mm2_bitcoin/spv_validation/Cargo.toml @@ -17,7 +17,7 @@ ripemd160 = "0.9.0" rustc-hex = "2" serde = "1.0" serialization = { path = "../serialization" } -sha2 = "0.9" +sha2 = "0.10" test_helpers = { path = "../test_helpers" } [dev-dependencies] diff --git a/mm2src/mm2_bitcoin/spv_validation/src/helpers_validation.rs b/mm2src/mm2_bitcoin/spv_validation/src/helpers_validation.rs index d3ce01813d..fcdd5ce5ab 100644 --- a/mm2src/mm2_bitcoin/spv_validation/src/helpers_validation.rs +++ b/mm2src/mm2_bitcoin/spv_validation/src/helpers_validation.rs @@ -4,8 +4,8 @@ use crate::work::{next_block_bits, NextBlockBitsError}; use chain::{BlockHeader, RawHeaderError}; use derive_more::Display; use primitives::hash::H256; -use ripemd160::Digest; use serialization::parse_compact_int; +use sha2::digest::Digest; use sha2::Sha256; #[derive(Clone, Debug, Display, Eq, PartialEq)] diff --git a/mm2src/mm2_core/src/mm_ctx.rs b/mm2src/mm2_core/src/mm_ctx.rs index d2537425c1..fed3f46d06 100644 --- a/mm2src/mm2_core/src/mm_ctx.rs +++ b/mm2src/mm2_core/src/mm_ctx.rs @@ -255,6 +255,16 @@ impl MmCtx { }) } + #[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") + } + + /// Checks if a wallet with the given name exists. + #[cfg(not(target_arch = "wasm32"))] + pub fn check_if_wallet_exists(&self, wallet_name: &str) -> bool { self.wallet_file_path(wallet_name).exists() } + /// MM database path. /// Defaults to a relative "DB". /// @@ -361,15 +371,21 @@ impl Drop for MmCtx { } } -/// This function can be used later by an FFI function to open a GUI storage. +/// Returns the path to the MM database root. #[cfg(not(target_arch = "wasm32"))] -pub fn path_to_dbdir(db_root: Option<&str>, db_id: &H160) -> PathBuf { +fn path_to_db_root(db_root: Option<&str>) -> &Path { const DEFAULT_ROOT: &str = "DB"; - let path = match db_root { + match db_root { Some(dbdir) if !dbdir.is_empty() => Path::new(dbdir), _ => Path::new(DEFAULT_ROOT), - }; + } +} + +/// This function can be used later by an FFI function to open a GUI storage. +#[cfg(not(target_arch = "wasm32"))] +pub fn path_to_dbdir(db_root: Option<&str>, db_id: &H160) -> PathBuf { + let path = path_to_db_root(db_root); path.join(hex::encode(db_id.as_slice())) } diff --git a/mm2src/mm2_libp2p/Cargo.toml b/mm2src/mm2_libp2p/Cargo.toml index 3a0f283e36..17f406c3b4 100644 --- a/mm2src/mm2_libp2p/Cargo.toml +++ b/mm2src/mm2_libp2p/Cargo.toml @@ -24,7 +24,7 @@ regex = "1" rmp-serde = "0.14.3" serde = { version = "1.0", features = ["derive"] } serde_bytes = "0.11.5" -sha2 = "0.9" +sha2 = "0.10" void = "1.0" wasm-timer = "0.2.4" diff --git a/mm2src/mm2_main/src/lp_native_dex.rs b/mm2src/mm2_main/src/lp_native_dex.rs index c00ad9916f..85b918410f 100644 --- a/mm2src/mm2_main/src/lp_native_dex.rs +++ b/mm2src/mm2_main/src/lp_native_dex.rs @@ -22,7 +22,8 @@ use bitcrypto::sha256; use coins::register_balance_update_handler; use common::executor::{SpawnFuture, Timer}; use common::log::{info, warn}; -use crypto::{from_hw_error, CryptoCtx, CryptoInitError, HwError, HwProcessingError, HwRpcError, WithHwRpcError}; +use crypto::{decrypt_mnemonic, encrypt_mnemonic, from_hw_error, generate_mnemonic, CryptoCtx, CryptoInitError, + EncryptedMnemonicData, HwError, HwProcessingError, HwRpcError, MnemonicError, WithHwRpcError}; use derive_more::Display; use enum_from::EnumFromTrait; use mm2_core::mm_ctx::{MmArc, MmCtx}; @@ -35,6 +36,7 @@ use mm2_metrics::mm_gauge; use mm2_net::network_event::NetworkEvent; use mm2_net::p2p::P2PContext; use rpc_task::RpcTaskError; +use serde::de::DeserializeOwned; use serde_json::{self as json}; use std::fs; use std::io; @@ -140,6 +142,8 @@ pub enum MmInitError { }, #[display(fmt = "P2P initializing error: '{}'", _0)] P2PError(P2PInitError), + #[display(fmt = "I/O error: '{}'", _0)] + IOError(String), #[display(fmt = "Error creating DB director '{:?}': {}", path, error)] ErrorCreatingDbDir { path: PathBuf, @@ -165,6 +169,8 @@ pub enum MmInitError { EmptyPassphrase, #[display(fmt = "Invalid passphrase: {}", _0)] InvalidPassphrase(String), + #[display(fmt = "Mnemonic error: {}", _0)] + MnemonicError(String), #[display(fmt = "NETWORK event initialization failed: {}", _0)] NetworkEventInitFailed(String), #[from_trait(WithHwRpcError::hw_rpc_error)] @@ -227,6 +233,10 @@ impl From for MmInitError { } } +impl From for MmInitError { + fn from(e: MnemonicError) -> Self { MmInitError::MnemonicError(e.to_string()) } +} + impl From for MmInitError { fn from(e: HwError) -> Self { from_hw_error(e) } } @@ -452,18 +462,167 @@ pub async fn lp_init_continue(ctx: MmArc) -> MmInitResult<()> { Ok(()) } -#[cfg_attr(target_arch = "wasm32", allow(unused_variables))] -/// * `ctx_cb` - callback used to share the `MmCtx` ID with the call site. -pub async fn lp_init(ctx: MmArc, version: String, datetime: String) -> MmInitResult<()> { - info!("Version: {} DT {}", version, datetime); +// Utility function for deserialization to reduce repetition +fn deserialize_config_field(ctx: &MmArc, field: &str) -> MmInitResult { + json::from_value::(ctx.conf[field].clone()).map_to_mm(|e| MmInitError::ErrorDeserializingConfig { + field: field.to_owned(), + error: e.to_string(), + }) +} - if !ctx.conf["passphrase"].is_null() { - let passphrase: String = - json::from_value(ctx.conf["passphrase"].clone()).map_to_mm(|e| MmInitError::ErrorDeserializingConfig { - field: "passphrase".to_owned(), - error: e.to_string(), - })?; +/// Saves the passphrase to a file associated with the given wallet name. +/// +/// # Arguments +/// +/// * `wallet_name` - The name of the wallet. +/// * `passphrase` - The passphrase to save. +/// +/// # Returns +/// Result indicating success or an error. +pub async fn save_encrypted_passphrase_to_file( + ctx: &MmArc, + wallet_name: &str, + encrypted_passphrase_data: &EncryptedMnemonicData, +) -> MmInitResult<()> { + let wallet_path = ctx.wallet_file_path(wallet_name); + let dbdir = ctx.dbdir(); + fs::create_dir_all(&dbdir).map_to_mm(|e| MmInitError::ErrorCreatingDbDir { + path: dbdir.clone(), + error: e.to_string(), + })?; + ensure_file_is_writable(&wallet_path).map_to_mm(|_| MmInitError::DbFileIsNotWritable { + path: wallet_path.display().to_string(), + })?; + mm2_io::fs::write_json(encrypted_passphrase_data, &wallet_path, true) + .await + .mm_err(|e| MmInitError::Internal(format!("Error saving passphrase to file {:?}: {}", wallet_path, e))) +} + +// Utility function to handle passphrase encryption and saving +async fn encrypt_and_save_passphrase( + ctx: &MmArc, + wallet_name: &str, + passphrase: &str, + wallet_password: &str, +) -> MmInitResult<()> { + let encrypted_passphrase_data = encrypt_mnemonic(passphrase, wallet_password)?; + save_encrypted_passphrase_to_file(ctx, wallet_name, &encrypted_passphrase_data).await +} + +/// 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 `EncryptedPassphraseData`, which includes +/// all necessary components for decryption, such as the encryption algorithm, key derivation +/// details, salts, IV, ciphertext, and HMAC tag. +/// +/// # Arguments +/// +/// * `ctx` - The `MmArc` context, providing access to application configuration and state. +/// * `wallet_name` - The name of the wallet whose encrypted passphrase data is to be read. +/// +/// # Returns +/// `io::Result` - The encrypted passphrase data or an error if the +/// reading process fails. +/// +/// # Errors +/// Returns an `io::Error` if the file cannot be read or the data cannot be deserialized into +/// `EncryptedPassphraseData`. +pub async fn read_encrypted_passphrase_from_file( + ctx: &MmArc, + wallet_name: &str, +) -> MmInitResult { + let wallet_path = ctx.wallet_file_path(wallet_name); + let maybe_passphrase = mm2_io::fs::read_json(&wallet_path).await.mm_err(|e| { + MmInitError::IOError(format!( + "Error reading passphrase from file {}: {}", + wallet_path.display(), + e + )) + })?; + match maybe_passphrase { + Some(passphrase) => Ok(passphrase), + None => MmError::err(MmInitError::Internal("Passphrase not found".to_string())), + } +} + +/// Reads and decrypts the passphrase from a file associated with the given wallet name. +/// +/// This function first reads the passphrase from the file. Since the passphrase is stored in an encrypted +/// format, it decrypts it before returning. +/// +/// # Arguments +/// * `ctx` - The `MmArc` context containing the application state and configuration. +/// * `wallet_name` - The name of the wallet for which the passphrase is to be retrieved. +/// +/// # Returns +/// `MmInitResult` - The decrypted passphrase or an error if any operation fails. +/// +/// # Errors +/// Returns specific `MmInitError` variants for different failure scenarios. +async fn read_and_decrypt_passphrase(ctx: &MmArc, wallet_name: &str, wallet_password: &str) -> MmInitResult { + let encrypted_passphrase = read_encrypted_passphrase_from_file(ctx, wallet_name).await?; + let mnemonic = decrypt_mnemonic(&encrypted_passphrase, wallet_password)?; + Ok(mnemonic.to_string()) +} + +/// Initializes and manages the wallet passphrase. +/// +/// This function handles several scenarios based on the configuration: +/// - Deserializes the passphrase and wallet name from the configuration. +/// - If both wallet name and passphrase are `None`, the function sets up the context for "no login mode" +/// This mode can be entered after the function's execution, allowing access to Komodo DeFi Framework +/// functionalities that don't require a passphrase (e.g., viewing the orderbook). +/// - If a wallet name is provided without a passphrase, it first checks for the existence of a +/// passphrase file associated with the wallet. If no file is found, it generates a new passphrase, +/// encrypts it, and saves it, enabling multi-wallet support. +/// - If a passphrase is provided (with or without a wallet name), it uses the provided passphrase +/// and handles encryption and storage as needed. +/// - Initializes the cryptographic context based on the `enable_hd` configuration. +/// +/// # Arguments +/// * `ctx` - The `MmArc` context containing the application state and configuration. +/// +/// # Returns +/// `MmInitResult<()>` - Result indicating success or failure of the initialization process. +/// +/// # Errors +/// Returns `MmInitError` if deserialization fails or if there are issues in passphrase handling. +/// +async fn initialize_wallet_passphrase(ctx: MmArc) -> MmInitResult<()> { + let passphrase = deserialize_config_field::>(&ctx, "passphrase")?; + // 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::>(&ctx, "wallet_name")?; + + let passphrase = match (wallet_name, passphrase) { + (None, None) => None, + // Legacy approach for passphrase, no `wallet_name` is needed in the config, in this case the passphrase is not encrypted and saved. + (None, Some(passphrase)) => Some(passphrase), + // New mode, passphrase is not provided. Generate, encrypt and save passphrase. + // passphrase is provided. encrypt and save passphrase. + (Some(wallet_name), maybe_passphrase) => { + let wallet_password = deserialize_config_field::(&ctx, "wallet_password")?; + if ctx.check_if_wallet_exists(&wallet_name) { + let passphrase_from_file = read_and_decrypt_passphrase(&ctx, &wallet_name, &wallet_password).await?; + match maybe_passphrase { + Some(passphrase) if passphrase == passphrase_from_file => Some(passphrase), + None => Some(passphrase_from_file), + _ => return MmError::err(MmInitError::InvalidPassphrase("Passphrase doesn't match the one from file, please create a new wallet if you want to use a new passphrase".to_string())), + } + } else { + let new_passphrase = match maybe_passphrase { + Some(passphrase) => passphrase, + None => generate_mnemonic(&ctx)?.to_string(), + }; + encrypt_and_save_passphrase(&ctx, &wallet_name, &new_passphrase, &wallet_password).await?; + Some(new_passphrase) + } + }, + }; + if let Some(passphrase) = passphrase { // This defaults to false to maintain backward compatibility. match ctx.conf["enable_hd"].as_bool().unwrap_or(false) { true => CryptoCtx::init_with_global_hd_account(ctx.clone(), &passphrase)?, @@ -471,6 +630,15 @@ pub async fn lp_init(ctx: MmArc, version: String, datetime: String) -> MmInitRes }; } + Ok(()) +} + +pub async fn lp_init(ctx: MmArc, version: String, datetime: String) -> MmInitResult<()> { + info!("Version: {} DT {}", version, datetime); + + // This either initializes the cryptographic context or sets up the context for "no login mode". + initialize_wallet_passphrase(ctx.clone()).await?; + lp_init_continue(ctx.clone()).await?; let ctx_id = ctx.ffi_handle().map_to_mm(MmInitError::Internal)?; diff --git a/mm2src/mm2_p2p/Cargo.toml b/mm2src/mm2_p2p/Cargo.toml index a4331bcb4b..a29c1bccbb 100644 --- a/mm2src/mm2_p2p/Cargo.toml +++ b/mm2src/mm2_p2p/Cargo.toml @@ -22,7 +22,7 @@ rmp-serde = "0.14.3" secp256k1 = { version = "0.20", features = ["rand"] } serde = { version = "1.0", default-features = false } serde_bytes = "0.11.5" -sha2 = "0.9" +sha2 = "0.10" smallvec = "1.6.1" syn = "2.0.18" void = "1.0" diff --git a/mm2src/mm2_p2p/src/lib.rs b/mm2src/mm2_p2p/src/lib.rs index fd13446f6e..43ec14a74a 100644 --- a/mm2src/mm2_p2p/src/lib.rs +++ b/mm2src/mm2_p2p/src/lib.rs @@ -10,6 +10,7 @@ use lazy_static::lazy_static; use secp256k1::{Message as SecpMessage, PublicKey as Secp256k1Pubkey, Secp256k1, SecretKey, SignOnly, Signature, VerifyOnly}; use serde::{de, Deserialize, Serialize, Serializer}; +use sha2::digest::Update; use sha2::{Digest, Sha256}; pub use crate::swarm_runtime::SwarmRuntime; From 714e72d67265c9c718042609d16eabf58c9d64eb Mon Sep 17 00:00:00 2001 From: shamardy Date: Tue, 28 Nov 2023 11:36:06 +0200 Subject: [PATCH 02/20] Wasm encryption/decryption and wallets mnemonic files storage --- Cargo.lock | 124 +++++----- mm2src/crypto/Cargo.toml | 4 +- mm2src/crypto/src/mnemonic.rs | 54 +++-- mm2src/mm2_core/src/mm_ctx.rs | 4 - mm2src/mm2_db/src/indexed_db/indexed_db.rs | 6 +- mm2src/mm2_main/src/lp_native_dex.rs | 221 ++---------------- mm2src/mm2_main/src/lp_wallet.rs | 165 +++++++++++++ .../src/lp_wallet/mnemonics_storage.rs | 72 ++++++ .../src/lp_wallet/mnemonics_wasm_db.rs | 146 ++++++++++++ mm2src/mm2_main/src/mm2.rs | 1 + 10 files changed, 513 insertions(+), 284 deletions(-) create mode 100644 mm2src/mm2_main/src/lp_wallet.rs create mode 100644 mm2src/mm2_main/src/lp_wallet/mnemonics_storage.rs create mode 100644 mm2src/mm2_main/src/lp_wallet/mnemonics_wasm_db.rs diff --git a/Cargo.lock b/Cargo.lock index 06c4d54ad3..4ea1498a63 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -49,11 +49,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" dependencies = [ "cfg-if 1.0.0", - "cipher", + "cipher 0.3.0", "cpufeatures 0.2.11", "opaque-debug 0.3.0", ] +[[package]] +name = "aes" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2" +dependencies = [ + "cfg-if 1.0.0", + "cipher 0.4.4", + "cpufeatures 0.2.11", +] + [[package]] name = "aes-gcm" version = "0.9.2" @@ -61,8 +72,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc3be92e19a7ef47457b8e6f90707e12b6ac5d20c6f3866584fa3be0787d839f" dependencies = [ "aead", - "aes", - "cipher", + "aes 0.7.5", + "cipher 0.3.0", "ctr", "ghash", "subtle 2.4.0", @@ -666,7 +677,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2cb03d1bed155d89dce0f845b7899b18a9a163e148fd004e1c28421a783e2d8e" dependencies = [ "block-padding 0.2.1", - "cipher", + "cipher 0.3.0", ] [[package]] @@ -684,6 +695,15 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" +[[package]] +name = "block-padding" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" +dependencies = [ + "generic-array 0.14.5", +] + [[package]] name = "blocking" version = "0.4.6" @@ -882,6 +902,15 @@ dependencies = [ "thiserror", ] +[[package]] +name = "cbc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" +dependencies = [ + "cipher 0.4.4", +] + [[package]] name = "cc" version = "1.0.83" @@ -911,7 +940,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c80e5460aa66fe3b91d40bcbdab953a597b60053e34d684ac6903f863b680a6" dependencies = [ "cfg-if 1.0.0", - "cipher", + "cipher 0.3.0", "cpufeatures 0.2.11", "zeroize", ] @@ -924,7 +953,7 @@ checksum = "a18446b09be63d457bbec447509e85f662f32952b035ce892290396bc0b0cff5" dependencies = [ "aead", "chacha20", - "cipher", + "cipher 0.3.0", "poly1305", "zeroize", ] @@ -966,6 +995,16 @@ dependencies = [ "generic-array 0.14.5", ] +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + [[package]] name = "clap" version = "2.33.3" @@ -1523,6 +1562,7 @@ checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" name = "crypto" version = "1.0.0" dependencies = [ + "aes 0.8.3", "argon2", "arrayref", "async-trait", @@ -1531,6 +1571,8 @@ dependencies = [ "bip39", "bitcrypto", "bs58 0.4.0", + "cbc", + "cipher 0.4.4", "common", "derive_more", "enum-primitive-derive", @@ -1547,7 +1589,6 @@ dependencies = [ "mm2_eth", "mm2_metamask", "num-traits", - "openssl", "parking_lot 0.12.0", "primitives", "rpc", @@ -1649,7 +1690,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a232f92a03f37dd7d7dd2adc67166c77e9cd88de5b019b9a9eecfaeaf7bfd481" dependencies = [ - "cipher", + "cipher 0.3.0", ] [[package]] @@ -2409,21 +2450,6 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - [[package]] name = "form_urlencoded" version = "1.0.1" @@ -2441,7 +2467,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd910db5f9ca4dc3116f8c46367825807aa2b942f72565f16b4be0b208a00a9e" dependencies = [ "block-modes", - "cipher", + "cipher 0.3.0", "libm 0.2.7", "num-bigint", "num-integer", @@ -3247,6 +3273,16 @@ dependencies = [ "regex", ] +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "block-padding 0.3.3", + "generic-array 0.14.5", +] + [[package]] name = "instant" version = "0.1.12" @@ -5060,50 +5096,12 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" -[[package]] -name = "openssl" -version = "0.10.60" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79a4c6c3a2b158f7f8f2a2fc5a969fa3a068df6fc9dbb4a43845436e3af7c800" -dependencies = [ - "bitflags 2.4.1", - "cfg-if 1.0.0", - "foreign-types", - "libc", - "once_cell", - "openssl-macros", - "openssl-sys", -] - -[[package]] -name = "openssl-macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" -dependencies = [ - "proc-macro2 1.0.63", - "quote 1.0.28", - "syn 2.0.23", -] - [[package]] name = "openssl-probe" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" -[[package]] -name = "openssl-sys" -version = "0.9.96" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3812c071ba60da8b5677cc12bcb1d42989a65553772897a7e0355545a819838f" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - [[package]] name = "ordered-float" version = "3.7.0" @@ -9617,7 +9615,7 @@ name = "zcash_primitives" version = "0.5.0" source = "git+https://github.com/KomodoPlatform/librustzcash.git?tag=k-1.3.0#443fef0cf301b04375f76128e7436b4de02d1c4d" dependencies = [ - "aes", + "aes 0.7.5", "bitvec 0.18.5", "blake2b_simd", "blake2s_simd", diff --git a/mm2src/crypto/Cargo.toml b/mm2src/crypto/Cargo.toml index 2cd1dc4972..4495c2e649 100644 --- a/mm2src/crypto/Cargo.toml +++ b/mm2src/crypto/Cargo.toml @@ -7,6 +7,7 @@ edition = "2018" doctest = false [dependencies] +aes = "0.8.3" argon2 = { version = "0.5.2", features = ["zeroize"] } arrayref = "0.3" async-trait = "0.1" @@ -15,6 +16,8 @@ bip32 = { version = "0.2.2", default-features = false, features = ["alloc", "sec bip39 = { version = "2.0.0", features = ["rand_core", "zeroize"], default-features = false } bitcrypto = { path = "../mm2_bitcoin/crypto" } bs58 = "0.4.0" +cbc = "0.1.2" +cipher = "0.4.4" common = { path = "../common" } derive_more = "0.99" enum_from = { path = "../derives/enum_from" } @@ -29,7 +32,6 @@ lazy_static = "1.4" mm2_core = { path = "../mm2_core" } mm2_err_handle = { path = "../mm2_err_handle" } num-traits = "0.2" -openssl = "0.10.60" parking_lot = { version = "0.12.0", features = ["nightly"] } primitives = { path = "../mm2_bitcoin/primitives" } rpc = { path = "../mm2_bitcoin/rpc" } diff --git a/mm2src/crypto/src/mnemonic.rs b/mm2src/crypto/src/mnemonic.rs index 9374bbf9cd..e6e5787dfa 100644 --- a/mm2src/crypto/src/mnemonic.rs +++ b/mm2src/crypto/src/mnemonic.rs @@ -1,3 +1,5 @@ +use aes::cipher::{block_padding::Pkcs7, BlockDecryptMut, BlockEncryptMut, KeyIvInit}; +use aes::Aes256; use argon2::password_hash::{PasswordHasher, SaltString}; use argon2::Argon2; use bip39::{Language, Mnemonic}; @@ -6,13 +8,15 @@ use derive_more::Display; use hmac::{Hmac, Mac}; use mm2_core::mm_ctx::MmArc; use mm2_err_handle::prelude::*; -use openssl::symm::{decrypt, encrypt, Cipher}; use sha2::Sha256; use std::convert::TryInto; const DEFAULT_WORD_COUNT: u64 = 24; -#[derive(Debug, Display)] +type Aes256CbcEnc = cbc::Encryptor; +type Aes256CbcDec = cbc::Decryptor; + +#[derive(Debug, Display, PartialEq)] pub enum MnemonicError { #[display(fmt = "BIP39 mnemonic error: {}", _0)] BIP39Error(String), @@ -37,10 +41,6 @@ impl From for MnemonicError { fn from(e: argon2::password_hash::Error) -> Self { MnemonicError::PasswordHashingFailed(e.to_string()) } } -impl From for MnemonicError { - fn from(e: openssl::error::ErrorStack) -> Self { MnemonicError::AESCipherError(e.to_string()) } -} - impl From for MnemonicError { fn from(e: base64::DecodeError) -> Self { MnemonicError::DecodeError(e.to_string()) } } @@ -252,12 +252,17 @@ pub fn encrypt_mnemonic(mnemonic: &str, password: &str) -> MmResult(&mut buffer, msg_len) + .map_to_mm(|e| MnemonicError::AESCipherError(e.to_string()))?; // Create HMAC tag let mut mac = Hmac::::new_from_slice(&key_hmac).map_to_mm(|e| MnemonicError::Internal(e.to_string()))?; - mac.update(&ciphertext); + mac.update(ciphertext); mac.update(&iv); let tag = mac.finalize().into_bytes(); @@ -295,7 +300,7 @@ pub fn encrypt_mnemonic(mnemonic: &str, password: &str) -> MmResult MmResult { // Decode the Base64-encoded values let iv = base64::decode(&encrypted_data.iv)?; - let ciphertext = base64::decode(&encrypted_data.ciphertext)?; + let mut ciphertext = base64::decode(&encrypted_data.ciphertext)?; let tag = base64::decode(&encrypted_data.tag)?; // Re-create the salts from Base64-encoded strings @@ -313,11 +318,13 @@ pub fn decrypt_mnemonic(encrypted_data: &EncryptedMnemonicData, password: &str) .map_to_mm(|e| MnemonicError::HMACError(e.to_string()))?; // Decrypt the ciphertext - let cipher = Cipher::aes_256_cbc(); - let decrypted_data = decrypt(cipher, &key_aes, Some(&iv), &ciphertext)?; + let decrypted_data = Aes256CbcDec::new(&key_aes.into(), iv.as_slice().into()) + .decrypt_padded_mut::(&mut ciphertext) + .map_to_mm(|e| MnemonicError::AESCipherError(e.to_string()))?; // Convert decrypted data back to a string - let mnemonic_str = String::from_utf8(decrypted_data).map_to_mm(|e| MnemonicError::DecodeError(e.to_string()))?; + let mnemonic_str = + String::from_utf8(decrypted_data.to_vec()).map_to_mm(|e| MnemonicError::DecodeError(e.to_string()))?; let mnemonic = Mnemonic::parse_normalized(&mnemonic_str)?; Ok(mnemonic) } @@ -349,4 +356,25 @@ mod tests { // Verify if decrypted mnemonic matches the original assert_eq!(decrypted_mnemonic, parsed_mnemonic); } + + #[test] + fn test_mnemonic_with_last_byte_zero() { + let mnemonic = "tank abandon bind salon remove wisdom net size aspect direct source fossil\0".to_string(); + let password = "password"; + + // Encrypt the mnemonic + let encrypted_data = encrypt_mnemonic(&mnemonic, password); + assert!(encrypted_data.is_ok()); + let encrypted_data = encrypted_data.unwrap(); + + // Decrypt the mnemonic + let decrypted_mnemonic = decrypt_mnemonic(&encrypted_data, password); + assert!(decrypted_mnemonic.is_err()); + + // Verify that the error is due to parsing and not padding + assert!(decrypted_mnemonic + .unwrap_err() + .to_string() + .contains("mnemonic contains an unknown word (word 11)")); + } } diff --git a/mm2src/mm2_core/src/mm_ctx.rs b/mm2src/mm2_core/src/mm_ctx.rs index fed3f46d06..d828cc0b9c 100644 --- a/mm2src/mm2_core/src/mm_ctx.rs +++ b/mm2src/mm2_core/src/mm_ctx.rs @@ -261,10 +261,6 @@ impl MmCtx { db_root.join(wallet_name.to_string() + ".dat") } - /// Checks if a wallet with the given name exists. - #[cfg(not(target_arch = "wasm32"))] - pub fn check_if_wallet_exists(&self, wallet_name: &str) -> bool { self.wallet_file_path(wallet_name).exists() } - /// MM database path. /// Defaults to a relative "DB". /// diff --git a/mm2src/mm2_db/src/indexed_db/indexed_db.rs b/mm2src/mm2_db/src/indexed_db/indexed_db.rs index c1d56cd20d..d5ab29ae36 100644 --- a/mm2src/mm2_db/src/indexed_db/indexed_db.rs +++ b/mm2src/mm2_db/src/indexed_db/indexed_db.rs @@ -110,9 +110,9 @@ impl DbIdentifier { } pub struct IndexedDbBuilder { - db_name: String, - db_version: u32, - tables: HashMap, + pub db_name: String, + pub db_version: u32, + pub tables: HashMap, } impl IndexedDbBuilder { diff --git a/mm2src/mm2_main/src/lp_native_dex.rs b/mm2src/mm2_main/src/lp_native_dex.rs index 85b918410f..b4fbe45c6d 100644 --- a/mm2src/mm2_main/src/lp_native_dex.rs +++ b/mm2src/mm2_main/src/lp_native_dex.rs @@ -22,8 +22,7 @@ use bitcrypto::sha256; use coins::register_balance_update_handler; use common::executor::{SpawnFuture, Timer}; use common::log::{info, warn}; -use crypto::{decrypt_mnemonic, encrypt_mnemonic, from_hw_error, generate_mnemonic, CryptoCtx, CryptoInitError, - EncryptedMnemonicData, HwError, HwProcessingError, HwRpcError, MnemonicError, WithHwRpcError}; +use crypto::{from_hw_error, CryptoCtx, HwError, HwProcessingError, HwRpcError, WithHwRpcError}; use derive_more::Display; use enum_from::EnumFromTrait; use mm2_core::mm_ctx::{MmArc, MmCtx}; @@ -36,7 +35,6 @@ use mm2_metrics::mm_gauge; use mm2_net::network_event::NetworkEvent; use mm2_net::p2p::P2PContext; use rpc_task::RpcTaskError; -use serde::de::DeserializeOwned; use serde_json::{self as json}; use std::fs; use std::io; @@ -52,6 +50,7 @@ use crate::mm2::lp_ordermatch::{broadcast_maker_orders_keep_alive_loop, clean_me lp_ordermatch_loop, orders_kick_start, BalanceUpdateOrdermatchHandler, OrdermatchInitError}; use crate::mm2::lp_swap::{running_swaps_num, swap_kick_starts}; +use crate::mm2::lp_wallet::{initialize_wallet_passphrase, WalletInitError}; use crate::mm2::rpc::spawn_rpc; cfg_native! { @@ -142,8 +141,6 @@ pub enum MmInitError { }, #[display(fmt = "P2P initializing error: '{}'", _0)] P2PError(P2PInitError), - #[display(fmt = "I/O error: '{}'", _0)] - IOError(String), #[display(fmt = "Error creating DB director '{:?}': {}", path, error)] ErrorCreatingDbDir { path: PathBuf, @@ -165,12 +162,8 @@ pub enum MmInitError { SwapsKickStartError(String), #[display(fmt = "Order kick start error: {}", _0)] OrdersKickStartError(String), - #[display(fmt = "Passphrase cannot be an empty string")] - EmptyPassphrase, - #[display(fmt = "Invalid passphrase: {}", _0)] - InvalidPassphrase(String), - #[display(fmt = "Mnemonic error: {}", _0)] - MnemonicError(String), + #[display(fmt = "Error initializing wallet: {}", _0)] + WalletInitError(String), #[display(fmt = "NETWORK event initialization failed: {}", _0)] NetworkEventInitFailed(String), #[from_trait(WithHwRpcError::hw_rpc_error)] @@ -210,33 +203,27 @@ impl From for MmInitError { } } -impl From for MmInitError { - fn from(e: InitMessageServiceError) -> Self { +impl From for MmInitError { + fn from(e: WalletInitError) -> Self { match e { - InitMessageServiceError::ErrorDeserializingConfig { field, error } => { + WalletInitError::ErrorDeserializingConfig { field, error } => { MmInitError::ErrorDeserializingConfig { field, error } }, + other => MmInitError::WalletInitError(other.to_string()), } } } -impl From for MmInitError { - fn from(e: CryptoInitError) -> Self { +impl From for MmInitError { + fn from(e: InitMessageServiceError) -> Self { match e { - e @ CryptoInitError::InitializedAlready | e @ CryptoInitError::NotInitialized => { - MmInitError::Internal(e.to_string()) + InitMessageServiceError::ErrorDeserializingConfig { field, error } => { + MmInitError::ErrorDeserializingConfig { field, error } }, - CryptoInitError::EmptyPassphrase => MmInitError::EmptyPassphrase, - CryptoInitError::InvalidPassphrase(pass) => MmInitError::InvalidPassphrase(pass.to_string()), - CryptoInitError::Internal(internal) => MmInitError::Internal(internal), } } } -impl From for MmInitError { - fn from(e: MnemonicError) -> Self { MmInitError::MnemonicError(e.to_string()) } -} - impl From for MmInitError { fn from(e: HwError) -> Self { from_hw_error(e) } } @@ -305,10 +292,6 @@ pub fn fix_directories(ctx: &MmCtx) -> MmInitResult<()> { fix_shared_dbdir(ctx)?; let dbdir = ctx.dbdir(); - fs::create_dir_all(&dbdir).map_to_mm(|e| MmInitError::ErrorCreatingDbDir { - path: dbdir.clone(), - error: e.to_string(), - })?; if !ensure_dir_is_writable(&dbdir.join("SWAPS")) { return MmError::err(MmInitError::db_directory_is_not_writable("SWAPS")); @@ -462,180 +445,18 @@ pub async fn lp_init_continue(ctx: MmArc) -> MmInitResult<()> { Ok(()) } -// Utility function for deserialization to reduce repetition -fn deserialize_config_field(ctx: &MmArc, field: &str) -> MmInitResult { - json::from_value::(ctx.conf[field].clone()).map_to_mm(|e| MmInitError::ErrorDeserializingConfig { - field: field.to_owned(), - error: e.to_string(), - }) -} - -/// Saves the passphrase to a file associated with the given wallet name. -/// -/// # Arguments -/// -/// * `wallet_name` - The name of the wallet. -/// * `passphrase` - The passphrase to save. -/// -/// # Returns -/// Result indicating success or an error. -pub async fn save_encrypted_passphrase_to_file( - ctx: &MmArc, - wallet_name: &str, - encrypted_passphrase_data: &EncryptedMnemonicData, -) -> MmInitResult<()> { - let wallet_path = ctx.wallet_file_path(wallet_name); - let dbdir = ctx.dbdir(); - fs::create_dir_all(&dbdir).map_to_mm(|e| MmInitError::ErrorCreatingDbDir { - path: dbdir.clone(), - error: e.to_string(), - })?; - ensure_file_is_writable(&wallet_path).map_to_mm(|_| MmInitError::DbFileIsNotWritable { - path: wallet_path.display().to_string(), - })?; - mm2_io::fs::write_json(encrypted_passphrase_data, &wallet_path, true) - .await - .mm_err(|e| MmInitError::Internal(format!("Error saving passphrase to file {:?}: {}", wallet_path, e))) -} - -// Utility function to handle passphrase encryption and saving -async fn encrypt_and_save_passphrase( - ctx: &MmArc, - wallet_name: &str, - passphrase: &str, - wallet_password: &str, -) -> MmInitResult<()> { - let encrypted_passphrase_data = encrypt_mnemonic(passphrase, wallet_password)?; - save_encrypted_passphrase_to_file(ctx, wallet_name, &encrypted_passphrase_data).await -} - -/// 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 `EncryptedPassphraseData`, which includes -/// all necessary components for decryption, such as the encryption algorithm, key derivation -/// details, salts, IV, ciphertext, and HMAC tag. -/// -/// # Arguments -/// -/// * `ctx` - The `MmArc` context, providing access to application configuration and state. -/// * `wallet_name` - The name of the wallet whose encrypted passphrase data is to be read. -/// -/// # Returns -/// `io::Result` - The encrypted passphrase data or an error if the -/// reading process fails. -/// -/// # Errors -/// Returns an `io::Error` if the file cannot be read or the data cannot be deserialized into -/// `EncryptedPassphraseData`. -pub async fn read_encrypted_passphrase_from_file( - ctx: &MmArc, - wallet_name: &str, -) -> MmInitResult { - let wallet_path = ctx.wallet_file_path(wallet_name); - let maybe_passphrase = mm2_io::fs::read_json(&wallet_path).await.mm_err(|e| { - MmInitError::IOError(format!( - "Error reading passphrase from file {}: {}", - wallet_path.display(), - e - )) - })?; - match maybe_passphrase { - Some(passphrase) => Ok(passphrase), - None => MmError::err(MmInitError::Internal("Passphrase not found".to_string())), - } -} - -/// Reads and decrypts the passphrase from a file associated with the given wallet name. -/// -/// This function first reads the passphrase from the file. Since the passphrase is stored in an encrypted -/// format, it decrypts it before returning. -/// -/// # Arguments -/// * `ctx` - The `MmArc` context containing the application state and configuration. -/// * `wallet_name` - The name of the wallet for which the passphrase is to be retrieved. -/// -/// # Returns -/// `MmInitResult` - The decrypted passphrase or an error if any operation fails. -/// -/// # Errors -/// Returns specific `MmInitError` variants for different failure scenarios. -async fn read_and_decrypt_passphrase(ctx: &MmArc, wallet_name: &str, wallet_password: &str) -> MmInitResult { - let encrypted_passphrase = read_encrypted_passphrase_from_file(ctx, wallet_name).await?; - let mnemonic = decrypt_mnemonic(&encrypted_passphrase, wallet_password)?; - Ok(mnemonic.to_string()) -} - -/// Initializes and manages the wallet passphrase. -/// -/// This function handles several scenarios based on the configuration: -/// - Deserializes the passphrase and wallet name from the configuration. -/// - If both wallet name and passphrase are `None`, the function sets up the context for "no login mode" -/// This mode can be entered after the function's execution, allowing access to Komodo DeFi Framework -/// functionalities that don't require a passphrase (e.g., viewing the orderbook). -/// - If a wallet name is provided without a passphrase, it first checks for the existence of a -/// passphrase file associated with the wallet. If no file is found, it generates a new passphrase, -/// encrypts it, and saves it, enabling multi-wallet support. -/// - If a passphrase is provided (with or without a wallet name), it uses the provided passphrase -/// and handles encryption and storage as needed. -/// - Initializes the cryptographic context based on the `enable_hd` configuration. -/// -/// # Arguments -/// * `ctx` - The `MmArc` context containing the application state and configuration. -/// -/// # Returns -/// `MmInitResult<()>` - Result indicating success or failure of the initialization process. -/// -/// # Errors -/// Returns `MmInitError` if deserialization fails or if there are issues in passphrase handling. -/// -async fn initialize_wallet_passphrase(ctx: MmArc) -> MmInitResult<()> { - let passphrase = deserialize_config_field::>(&ctx, "passphrase")?; - // 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::>(&ctx, "wallet_name")?; - - let passphrase = match (wallet_name, passphrase) { - (None, None) => None, - // Legacy approach for passphrase, no `wallet_name` is needed in the config, in this case the passphrase is not encrypted and saved. - (None, Some(passphrase)) => Some(passphrase), - // New mode, passphrase is not provided. Generate, encrypt and save passphrase. - // passphrase is provided. encrypt and save passphrase. - (Some(wallet_name), maybe_passphrase) => { - let wallet_password = deserialize_config_field::(&ctx, "wallet_password")?; - if ctx.check_if_wallet_exists(&wallet_name) { - let passphrase_from_file = read_and_decrypt_passphrase(&ctx, &wallet_name, &wallet_password).await?; - match maybe_passphrase { - Some(passphrase) if passphrase == passphrase_from_file => Some(passphrase), - None => Some(passphrase_from_file), - _ => return MmError::err(MmInitError::InvalidPassphrase("Passphrase doesn't match the one from file, please create a new wallet if you want to use a new passphrase".to_string())), - } - } else { - let new_passphrase = match maybe_passphrase { - Some(passphrase) => passphrase, - None => generate_mnemonic(&ctx)?.to_string(), - }; - encrypt_and_save_passphrase(&ctx, &wallet_name, &new_passphrase, &wallet_password).await?; - Some(new_passphrase) - } - }, - }; - - if let Some(passphrase) = passphrase { - // This defaults to false to maintain backward compatibility. - match ctx.conf["enable_hd"].as_bool().unwrap_or(false) { - true => CryptoCtx::init_with_global_hd_account(ctx.clone(), &passphrase)?, - false => CryptoCtx::init_with_iguana_passphrase(ctx.clone(), &passphrase)?, - }; - } - - Ok(()) -} - pub async fn lp_init(ctx: MmArc, version: String, datetime: String) -> MmInitResult<()> { info!("Version: {} DT {}", version, datetime); + #[cfg(not(target_arch = "wasm32"))] + { + let dbdir = ctx.dbdir(); + fs::create_dir_all(&dbdir).map_to_mm(|e| MmInitError::ErrorCreatingDbDir { + path: dbdir.clone(), + error: e.to_string(), + })?; + } + // This either initializes the cryptographic context or sets up the context for "no login mode". initialize_wallet_passphrase(ctx.clone()).await?; diff --git a/mm2src/mm2_main/src/lp_wallet.rs b/mm2src/mm2_main/src/lp_wallet.rs new file mode 100644 index 0000000000..8ff62b1589 --- /dev/null +++ b/mm2src/mm2_main/src/lp_wallet.rs @@ -0,0 +1,165 @@ +use crypto::{decrypt_mnemonic, encrypt_mnemonic, generate_mnemonic, CryptoCtx, CryptoInitError, MnemonicError}; +use mm2_core::mm_ctx::MmArc; +use mm2_err_handle::prelude::*; +use serde::de::DeserializeOwned; +use serde_json::{self as json}; + +#[cfg(not(target_arch = "wasm32"))] +#[path = "lp_wallet/mnemonics_storage.rs"] +mod mnemonics_storage; +#[cfg(not(target_arch = "wasm32"))] +use mnemonics_storage::{read_encrypted_passphrase, save_encrypted_passphrase}; +#[cfg(target_arch = "wasm32")] +#[path = "lp_wallet/mnemonics_wasm_db.rs"] +mod mnemonics_wasm_db; +#[cfg(target_arch = "wasm32")] +use mnemonics_wasm_db::{read_encrypted_passphrase, save_encrypted_passphrase}; + +type WalletInitResult = Result>; + +#[derive(Debug, Deserialize, Display, Serialize)] +pub enum WalletInitError { + #[display(fmt = "Error deserializing '{}' config field: {}", field, error)] + ErrorDeserializingConfig { + field: String, + error: String, + }, + #[display(fmt = "Wallets storage error: {}", _0)] + WalletsStorageError(String), + #[display( + fmt = "Passphrase doesn't match the one from file, please create a new wallet if you want to use a new passphrase" + )] + PassphraseMismatch, + #[display(fmt = "Error generating mnemonic: {}", _0)] + GenerateMnemonicError(String), + #[display(fmt = "Error initializing crypto context: {}", _0)] + CryptoInitError(String), + Internal(String), +} + +impl From for WalletInitError { + fn from(e: MnemonicError) -> Self { WalletInitError::GenerateMnemonicError(e.to_string()) } +} + +impl From for WalletInitError { + fn from(e: CryptoInitError) -> Self { WalletInitError::CryptoInitError(e.to_string()) } +} + +// Utility function for deserialization to reduce repetition +fn deserialize_config_field(ctx: &MmArc, field: &str) -> WalletInitResult { + json::from_value::(ctx.conf[field].clone()).map_to_mm(|e| WalletInitError::ErrorDeserializingConfig { + field: field.to_owned(), + error: e.to_string(), + }) +} + +// Utility function to handle passphrase encryption and saving +async fn encrypt_and_save_passphrase( + ctx: &MmArc, + wallet_name: &str, + passphrase: &str, + wallet_password: &str, +) -> WalletInitResult<()> { + let encrypted_passphrase_data = encrypt_mnemonic(passphrase, wallet_password)?; + save_encrypted_passphrase(ctx, wallet_name, &encrypted_passphrase_data) + .await + .mm_err(|e| WalletInitError::WalletsStorageError(e.to_string())) +} + +/// Reads and decrypts the passphrase from a file associated with the given wallet name. +/// +/// This function first reads the passphrase from the file. Since the passphrase is stored in an encrypted +/// format, it decrypts it before returning. +/// +/// # Arguments +/// * `ctx` - The `MmArc` context containing the application state and configuration. +/// * `wallet_name` - The name of the wallet for which the passphrase is to be retrieved. +/// +/// # Returns +/// `MmInitResult` - The decrypted passphrase or an error if any operation fails. +/// +/// # Errors +/// Returns specific `MmInitError` variants for different failure scenarios. +async fn read_and_decrypt_passphrase( + ctx: &MmArc, + wallet_name: &str, + wallet_password: &str, +) -> WalletInitResult> { + match read_encrypted_passphrase(ctx, wallet_name) + .await + .mm_err(|e| WalletInitError::WalletsStorageError(e.to_string()))? + { + Some(encrypted_passphrase) => { + let mnemonic = decrypt_mnemonic(&encrypted_passphrase, wallet_password)?; + Ok(Some(mnemonic.to_string())) + }, + None => Ok(None), + } +} + +/// Initializes and manages the wallet passphrase. +/// +/// This function handles several scenarios based on the configuration: +/// - Deserializes the passphrase and wallet name from the configuration. +/// - If both wallet name and passphrase are `None`, the function sets up the context for "no login mode" +/// This mode can be entered after the function's execution, allowing access to Komodo DeFi Framework +/// functionalities that don't require a passphrase (e.g., viewing the orderbook). +/// - If a wallet name is provided without a passphrase, it first checks for the existence of a +/// passphrase file associated with the wallet. If no file is found, it generates a new passphrase, +/// encrypts it, and saves it, enabling multi-wallet support. +/// - If a passphrase is provided (with or without a wallet name), it uses the provided passphrase +/// and handles encryption and storage as needed. +/// - Initializes the cryptographic context based on the `enable_hd` configuration. +/// +/// # Arguments +/// * `ctx` - The `MmArc` context containing the application state and configuration. +/// +/// # Returns +/// `MmInitResult<()>` - Result indicating success or failure of the initialization process. +/// +/// # Errors +/// Returns `MmInitError` if deserialization fails or if there are issues in passphrase handling. +/// +pub(crate) async fn initialize_wallet_passphrase(ctx: MmArc) -> WalletInitResult<()> { + let passphrase = deserialize_config_field::>(&ctx, "passphrase")?; + // 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::>(&ctx, "wallet_name")?; + + let passphrase = match (wallet_name, passphrase) { + (None, None) => None, + // Legacy approach for passphrase, no `wallet_name` is needed in the config, in this case the passphrase is not encrypted and saved. + (None, Some(passphrase)) => Some(passphrase), + // New mode, passphrase is not provided. Generate, encrypt and save passphrase. + // passphrase is provided. encrypt and save passphrase. + (Some(wallet_name), maybe_passphrase) => { + let wallet_password = deserialize_config_field::(&ctx, "wallet_password")?; + match read_and_decrypt_passphrase(&ctx, &wallet_name, &wallet_password).await? { + Some(passphrase_from_file) => match maybe_passphrase { + Some(passphrase) if passphrase == passphrase_from_file => Some(passphrase), + None => Some(passphrase_from_file), + _ => return MmError::err(WalletInitError::PassphraseMismatch), + }, + None => { + let new_passphrase = match maybe_passphrase { + Some(passphrase) => passphrase, + None => generate_mnemonic(&ctx)?.to_string(), + }; + encrypt_and_save_passphrase(&ctx, &wallet_name, &new_passphrase, &wallet_password).await?; + Some(new_passphrase) + }, + } + }, + }; + + if let Some(passphrase) = passphrase { + // This defaults to false to maintain backward compatibility. + match ctx.conf["enable_hd"].as_bool().unwrap_or(false) { + true => CryptoCtx::init_with_global_hd_account(ctx.clone(), &passphrase)?, + false => CryptoCtx::init_with_iguana_passphrase(ctx.clone(), &passphrase)?, + }; + } + + Ok(()) +} diff --git a/mm2src/mm2_main/src/lp_wallet/mnemonics_storage.rs b/mm2src/mm2_main/src/lp_wallet/mnemonics_storage.rs new file mode 100644 index 0000000000..cfe389d040 --- /dev/null +++ b/mm2src/mm2_main/src/lp_wallet/mnemonics_storage.rs @@ -0,0 +1,72 @@ +use crypto::EncryptedMnemonicData; +use mm2_core::mm_ctx::MmArc; +use mm2_err_handle::prelude::*; +use mm2_io::fs::ensure_file_is_writable; + +type WalletsStorageResult = Result>; + +#[derive(Debug, Deserialize, Display, Serialize)] +pub enum WalletsStorageError { + #[display(fmt = "{} db file is not writable", path)] + DbFileIsNotWritable { path: String }, + #[display(fmt = "Error writing to file: {}", _0)] + FsWriteError(String), + #[display(fmt = "Error reading from file: {}", _0)] + FsReadError(String), +} + +/// Saves the passphrase to a file associated with the given wallet name. +/// +/// # Arguments +/// +/// * `wallet_name` - The name of the wallet. +/// * `passphrase` - The passphrase to save. +/// +/// # Returns +/// Result indicating success or an error. +pub(super) async fn save_encrypted_passphrase( + ctx: &MmArc, + wallet_name: &str, + encrypted_passphrase_data: &EncryptedMnemonicData, +) -> WalletsStorageResult<()> { + let wallet_path = ctx.wallet_file_path(wallet_name); + ensure_file_is_writable(&wallet_path).map_to_mm(|_| WalletsStorageError::DbFileIsNotWritable { + path: wallet_path.display().to_string(), + })?; + mm2_io::fs::write_json(encrypted_passphrase_data, &wallet_path, true) + .await + .mm_err(|e| WalletsStorageError::FsWriteError(e.to_string())) +} + +/// 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 `EncryptedPassphraseData`, which includes +/// all necessary components for decryption, such as the encryption algorithm, key derivation +/// details, salts, IV, ciphertext, and HMAC tag. +/// +/// # Arguments +/// +/// * `ctx` - The `MmArc` context, providing access to application configuration and state. +/// * `wallet_name` - The name of the wallet whose encrypted passphrase data is to be read. +/// +/// # Returns +/// `io::Result` - The encrypted passphrase data or an error if the +/// reading process fails. +/// +/// # Errors +/// Returns an `io::Error` if the file cannot be read or the data cannot be deserialized into +/// `EncryptedPassphraseData`. +pub(super) async fn read_encrypted_passphrase( + ctx: &MmArc, + wallet_name: &str, +) -> WalletsStorageResult> { + let wallet_path = ctx.wallet_file_path(wallet_name); + mm2_io::fs::read_json(&wallet_path).await.mm_err(|e| { + WalletsStorageError::FsReadError(format!( + "Error reading passphrase from file {}: {}", + wallet_path.display(), + e + )) + }) +} diff --git a/mm2src/mm2_main/src/lp_wallet/mnemonics_wasm_db.rs b/mm2src/mm2_main/src/lp_wallet/mnemonics_wasm_db.rs new file mode 100644 index 0000000000..e7bd986bfe --- /dev/null +++ b/mm2src/mm2_main/src/lp_wallet/mnemonics_wasm_db.rs @@ -0,0 +1,146 @@ +use crypto::EncryptedMnemonicData; +use mm2_core::mm_ctx::MmArc; +use mm2_core::DbNamespaceId; +use mm2_db::indexed_db::{DbIdentifier, DbInstance, DbUpgrader, IndexedDb, IndexedDbBuilder, OnUpgradeResult, + TableSignature}; +use mm2_err_handle::prelude::*; +use std::collections::HashMap; + +type WalletsDBResult = Result>; + +#[derive(Debug, Deserialize, Display, Serialize)] +pub enum WalletsDBError { + #[display(fmt = "Error deserializing '{}': {}", field, error)] + DeserializationError { + field: String, + error: String, + }, + #[display(fmt = "Error serializing '{}': {}", field, error)] + SerializationError { + field: String, + error: String, + }, + Internal(String), +} + +#[derive(Debug, Deserialize, Serialize)] +struct MnemonicsTable { + wallet_name: String, + encrypted_mnemonic: String, +} + +impl TableSignature for MnemonicsTable { + fn table_name() -> &'static str { "mnemonics" } + + fn on_upgrade_needed(upgrader: &DbUpgrader, old_version: u32, new_version: u32) -> OnUpgradeResult<()> { + if let (0, 1) = (old_version, new_version) { + let table = upgrader.create_table(Self::table_name())?; + table.create_index("wallet_name", true)?; + } + Ok(()) + } +} + +pub(super) async fn save_encrypted_passphrase( + ctx: &MmArc, + wallet_name: &str, + encrypted_passphrase_data: &EncryptedMnemonicData, +) -> WalletsDBResult<()> { + const DB_VERSION: u32 = 1; + + // Create the database identifier + let db_name = "wallets"; + let db_id = match ctx.db_namespace { + DbNamespaceId::Main => format!("MAIN::KOMODEFI::{}", db_name), + DbNamespaceId::Test(u) => format!("TEST_{}::KOMODEFI::{}", u, db_name), + }; + + let indexed_db_builder = IndexedDbBuilder { + db_name: db_id, + db_version: 1, + tables: HashMap::new(), + }; + + // Initialize the database instance with the mnemonic table + let db = indexed_db_builder + .with_version(DB_VERSION) + .with_table::() + .build() + .await + .mm_err(|e| WalletsDBError::Internal(e.to_string()))?; + + let transaction = db + .transaction() + .await + .mm_err(|e| WalletsDBError::Internal(e.to_string()))?; + let table = transaction + .table::() + .await + .mm_err(|e| WalletsDBError::Internal(e.to_string()))?; + + let mnemonics_table_item = MnemonicsTable { + wallet_name: wallet_name.to_string(), + encrypted_mnemonic: serde_json::to_string(encrypted_passphrase_data).map_err(|e| { + WalletsDBError::SerializationError { + field: "encrypted_mnemonic".to_string(), + error: e.to_string(), + } + })?, + }; + table + .add_item(&mnemonics_table_item) + .await + .mm_err(|e| WalletsDBError::Internal(e.to_string()))?; + + Ok(()) +} + +pub(super) async fn read_encrypted_passphrase( + ctx: &MmArc, + wallet_name: &str, +) -> WalletsDBResult> { + const DB_VERSION: u32 = 1; + + // Create the database identifier + let db_name = "wallets"; + let db_id = match ctx.db_namespace { + DbNamespaceId::Main => format!("MAIN::KOMODEFI::{}", db_name), + DbNamespaceId::Test(u) => format!("TEST_{}::KOMODEFI::{}", u, db_name), + }; + + let indexed_db_builder = IndexedDbBuilder { + db_name: db_id, + db_version: 1, + tables: HashMap::new(), + }; + + // Initialize the database instance with the mnemonic table + let db = indexed_db_builder + .with_version(DB_VERSION) + .with_table::() + .build() + .await + .mm_err(|e| WalletsDBError::Internal(e.to_string()))?; + + let transaction = db + .transaction() + .await + .mm_err(|e| WalletsDBError::Internal(e.to_string()))?; + let table = transaction + .table::() + .await + .mm_err(|e| WalletsDBError::Internal(e.to_string()))?; + + match table.get_item_by_unique_index("wallet_name", wallet_name).await { + Ok(Some((_item_id, wallet_table_item))) => serde_json::from_str(&wallet_table_item.encrypted_mnemonic) + .map_to_mm(|e| WalletsDBError::DeserializationError { + field: "encrypted_mnemonic".to_string(), + error: e.to_string(), + }), + Ok(None) => Ok(None), + Err(e) => MmError::err(WalletsDBError::Internal(format!( + "Error retrieving encrypted passphrase: {}", + e + ))), + } +} diff --git a/mm2src/mm2_main/src/mm2.rs b/mm2src/mm2_main/src/mm2.rs index f57c6dd711..4abdcec8a7 100644 --- a/mm2src/mm2_main/src/mm2.rs +++ b/mm2src/mm2_main/src/mm2.rs @@ -63,6 +63,7 @@ pub mod database; #[path = "lp_ordermatch.rs"] pub mod lp_ordermatch; #[path = "lp_stats.rs"] pub mod lp_stats; #[path = "lp_swap.rs"] pub mod lp_swap; +#[path = "lp_wallet.rs"] pub mod lp_wallet; #[path = "rpc.rs"] pub mod rpc; pub const PASSWORD_MAXIMUM_CONSECUTIVE_CHARACTERS: usize = 3; From d0e3f4495ee39d7f4fe813b30dbed67dae41eac4 Mon Sep 17 00:00:00 2001 From: shamardy Date: Tue, 28 Nov 2023 12:44:31 +0200 Subject: [PATCH 03/20] rollback some Cargo.lock changes that were done due to the use of openssl --- Cargo.lock | 221 +++++++++++++++++++++++------------------------------ 1 file changed, 97 insertions(+), 124 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4ea1498a63..9f69477a90 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -263,7 +263,7 @@ checksum = "10f203db73a71dfa2fb6dd22763990fa26f3d2625a6da2da900d23b87d26be27" dependencies = [ "proc-macro2 1.0.63", "quote 1.0.28", - "syn 1.0.109", + "syn 1.0.95", ] [[package]] @@ -280,7 +280,7 @@ checksum = "061a7acccaa286c011ddc30970520b98fa40e00c9d644633fb26b5fc63a265e3" dependencies = [ "proc-macro2 1.0.63", "quote 1.0.28", - "syn 1.0.109", + "syn 1.0.95", ] [[package]] @@ -327,7 +327,7 @@ checksum = "acee9fd5073ab6b045a275b3e709c163dd36c90685219cb21804a147b58dba43" dependencies = [ "async-trait", "axum-core", - "bitflags 1.3.2", + "bitflags", "bytes 1.4.0", "futures-util", "http 0.2.7", @@ -512,7 +512,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93f2635620bf0b9d4576eb7bb9a38a55df78bd1205d26fa994b25911a69f212f" dependencies = [ "bitcoin_hashes", - "rand_core 0.6.4", + "rand_core 0.5.1", "zeroize", ] @@ -553,12 +553,6 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" -[[package]] -name = "bitflags" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" - [[package]] name = "bitvec" version = "0.18.5" @@ -751,7 +745,7 @@ dependencies = [ "borsh-schema-derive-internal", "proc-macro-crate 0.1.5", "proc-macro2 1.0.63", - "syn 1.0.109", + "syn 1.0.95", ] [[package]] @@ -762,7 +756,7 @@ checksum = "5449c28a7b352f2d1e592a8a28bf139bc71afb0764a14f3c02500935d8c44065" dependencies = [ "proc-macro2 1.0.63", "quote 1.0.28", - "syn 1.0.109", + "syn 1.0.95", ] [[package]] @@ -773,7 +767,7 @@ checksum = "cdbd5696d8bfa21d53d9fe39a714a18538bad11492a42d066dbbc395fb1951c0" dependencies = [ "proc-macro2 1.0.63", "quote 1.0.28", - "syn 1.0.109", + "syn 1.0.95", ] [[package]] @@ -839,7 +833,7 @@ checksum = "8e215f8c2f9f79cb53c8335e687ffd07d5bfcb6fe5fc80723762d0be46e7cc54" dependencies = [ "proc-macro2 1.0.63", "quote 1.0.28", - "syn 1.0.109", + "syn 1.0.95", ] [[package]] @@ -913,12 +907,11 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.83" +version = "1.0.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +checksum = "581f5dba903aac52ea3feb5ec4810848460ee833876f1f9b0fdeab1f19091574" dependencies = [ "jobserver", - "libc", ] [[package]] @@ -1013,7 +1006,7 @@ checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" dependencies = [ "ansi_term", "atty", - "bitflags 1.3.2", + "bitflags", "strsim", "textwrap", "unicode-width", @@ -1026,7 +1019,7 @@ version = "0.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" dependencies = [ - "bitflags 1.3.2", + "bitflags", ] [[package]] @@ -1035,7 +1028,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4344512281c643ae7638bbabc3af17a11307803ec8f0fcad9fae512a8bf36467" dependencies = [ - "bitflags 1.3.2", + "bitflags", ] [[package]] @@ -1755,7 +1748,7 @@ dependencies = [ "proc-macro2 1.0.63", "quote 1.0.28", "scratch", - "syn 1.0.109", + "syn 1.0.95", ] [[package]] @@ -1772,7 +1765,7 @@ checksum = "b846f081361125bfc8dc9d3940c84e1fd83ba54bbca7b17cd29483c828be0704" dependencies = [ "proc-macro2 1.0.63", "quote 1.0.28", - "syn 1.0.109", + "syn 1.0.95", ] [[package]] @@ -1809,7 +1802,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fdf3fce3ce863539ec1d7fd1b6dcc3c645663376b43ed376bbf887733e4f772" dependencies = [ "data-encoding", - "syn 1.0.109", + "syn 1.0.95", ] [[package]] @@ -1866,7 +1859,7 @@ checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" dependencies = [ "proc-macro2 1.0.63", "quote 1.0.28", - "syn 1.0.109", + "syn 1.0.95", ] [[package]] @@ -1877,7 +1870,7 @@ checksum = "41cb0e6161ad61ed084a36ba71fbba9e3ac5aee3606fb607fe08da6acbcf3d8c" dependencies = [ "proc-macro2 1.0.63", "quote 1.0.28", - "syn 1.0.109", + "syn 1.0.95", ] [[package]] @@ -2131,7 +2124,7 @@ dependencies = [ "heck", "proc-macro2 1.0.63", "quote 1.0.28", - "syn 1.0.109", + "syn 1.0.95", ] [[package]] @@ -2142,7 +2135,7 @@ checksum = "c375b9c5eadb68d0a6efee2999fef292f45854c3444c86f09d8ab086ba942b0e" dependencies = [ "num-traits", "quote 1.0.28", - "syn 1.0.109", + "syn 1.0.95", ] [[package]] @@ -2152,7 +2145,7 @@ dependencies = [ "itertools", "proc-macro2 1.0.63", "quote 1.0.28", - "syn 1.0.109", + "syn 1.0.95", ] [[package]] @@ -2318,7 +2311,7 @@ checksum = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4" dependencies = [ "proc-macro2 1.0.63", "quote 1.0.28", - "syn 1.0.109", + "syn 1.0.95", "synstructure", ] @@ -2411,7 +2404,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfcf0ed7fe52a17a03854ec54a9f76d6d84508d1c0e66bc1793301c73fc8493c" dependencies = [ "byteorder", - "rand 0.8.5", + "rand 0.8.4", "rustc-hex", "static_assertions", ] @@ -2977,15 +2970,6 @@ dependencies = [ "hmac 0.8.1", ] -[[package]] -name = "home" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" -dependencies = [ - "windows-sys 0.48.0", -] - [[package]] name = "hostname" version = "0.3.1" @@ -3236,7 +3220,7 @@ checksum = "d5dacb10c5b3bb92d46ba347505a9041e676bb20ad220101326bffb0c93031ee" dependencies = [ "proc-macro2 1.0.63", "quote 1.0.28", - "syn 1.0.109", + "syn 1.0.95", ] [[package]] @@ -3489,7 +3473,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6607c62aa161d23d17a9072cc5da0be67cdfc89d3afb1e8d9c842bebc2525ffe" dependencies = [ "arrayvec 0.5.1", - "bitflags 1.3.2", + "bitflags", "cfg-if 1.0.0", "ryu", "static_assertions", @@ -3596,7 +3580,7 @@ dependencies = [ "parking_lot 0.12.0", "pin-project", "quick-protobuf", - "rand 0.8.5", + "rand 0.8.4", "rw-stream-sink", "smallvec 1.6.1", "thiserror", @@ -3633,7 +3617,7 @@ dependencies = [ "log", "quick-protobuf", "quick-protobuf-codec", - "rand 0.8.5", + "rand 0.8.4", "smallvec 1.6.1", "thiserror", ] @@ -3661,7 +3645,7 @@ dependencies = [ "prometheus-client", "quick-protobuf", "quick-protobuf-codec", - "rand 0.8.5", + "rand 0.8.4", "regex", "sha2 0.10.7", "smallvec 1.6.1", @@ -3703,7 +3687,7 @@ dependencies = [ "log", "multihash", "quick-protobuf", - "rand 0.8.5", + "rand 0.8.4", "sha2 0.10.7", "thiserror", "zeroize", @@ -3721,7 +3705,7 @@ dependencies = [ "libp2p-identity", "libp2p-swarm", "log", - "rand 0.8.5", + "rand 0.8.4", "smallvec 1.6.1", "socket2 0.5.3", "tokio", @@ -3760,7 +3744,7 @@ dependencies = [ "multihash", "once_cell", "quick-protobuf", - "rand 0.8.5", + "rand 0.8.4", "sha2 0.10.7", "snow", "static_assertions", @@ -3782,7 +3766,7 @@ dependencies = [ "libp2p-identity", "libp2p-swarm", "log", - "rand 0.8.5", + "rand 0.8.4", "void", ] @@ -3798,7 +3782,7 @@ dependencies = [ "libp2p-identity", "libp2p-swarm", "log", - "rand 0.8.5", + "rand 0.8.4", "smallvec 1.6.1", "void", ] @@ -3819,7 +3803,7 @@ dependencies = [ "log", "multistream-select", "once_cell", - "rand 0.8.5", + "rand 0.8.4", "smallvec 1.6.1", "tokio", "void", @@ -3929,7 +3913,7 @@ dependencies = [ "libsecp256k1-core 0.3.0", "libsecp256k1-gen-ecmult 0.3.0", "libsecp256k1-gen-genmult 0.3.0", - "rand 0.8.5", + "rand 0.8.4", "serde", "sha2 0.9.9", "typenum", @@ -4098,12 +4082,6 @@ version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" -[[package]] -name = "linux-raw-sys" -version = "0.4.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829" - [[package]] name = "lock_api" version = "0.4.6" @@ -4787,7 +4765,7 @@ checksum = "3048ef3680533a27f9f8e7d6a0bce44dc61e4895ea0f42709337fa1c8616fefe" dependencies = [ "proc-macro2 1.0.63", "quote 1.0.28", - "syn 1.0.109", + "syn 1.0.95", ] [[package]] @@ -4868,7 +4846,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9ea4302b9759a7a88242299225ea3688e63c85ea136371bb6cf94fd674efaab" dependencies = [ "anyhow", - "bitflags 1.3.2", + "bitflags", "byteorder", "libc", "netlink-packet-core", @@ -4930,7 +4908,7 @@ version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f866317acbd3a240710c63f065ffb1e4fd466259045ccb504130b7f668f35c6" dependencies = [ - "bitflags 1.3.2", + "bitflags", "cc", "cfg-if 1.0.0", "libc", @@ -4943,7 +4921,7 @@ version = "0.24.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069" dependencies = [ - "bitflags 1.3.2", + "bitflags", "cfg-if 1.0.0", "libc", ] @@ -4987,7 +4965,7 @@ checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" dependencies = [ "proc-macro2 1.0.63", "quote 1.0.28", - "syn 1.0.109", + "syn 1.0.95", ] [[package]] @@ -5051,7 +5029,7 @@ dependencies = [ "proc-macro-crate 1.1.3", "proc-macro2 1.0.63", "quote 1.0.28", - "syn 1.0.109", + "syn 1.0.95", ] [[package]] @@ -5132,7 +5110,7 @@ dependencies = [ "proc-macro-error", "proc-macro2 1.0.63", "quote 1.0.28", - "syn 1.0.109", + "syn 1.0.95", ] [[package]] @@ -5178,7 +5156,7 @@ dependencies = [ "proc-macro-crate 1.1.3", "proc-macro2 1.0.63", "quote 1.0.28", - "syn 1.0.109", + "syn 1.0.95", ] [[package]] @@ -5206,7 +5184,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f557c32c6d268a07c921471619c0295f5efad3a0e76d4f97a05c091a51d110b2" dependencies = [ "proc-macro2 1.0.63", - "syn 1.0.109", + "syn 1.0.95", "synstructure", ] @@ -5441,7 +5419,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" dependencies = [ "autocfg 1.1.0", - "bitflags 1.3.2", + "bitflags", "cfg-if 1.0.0", "concurrent-queue 2.2.0", "libc", @@ -5492,7 +5470,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f28f53e8b192565862cf99343194579a022eb9c7dd3a8d03134734803c7b3125" dependencies = [ "proc-macro2 1.0.63", - "syn 1.0.109", + "syn 1.0.95", ] [[package]] @@ -5547,7 +5525,7 @@ dependencies = [ "proc-macro-error-attr", "proc-macro2 1.0.63", "quote 1.0.28", - "syn 1.0.109", + "syn 1.0.95", "version_check", ] @@ -5611,7 +5589,7 @@ checksum = "72b6a5217beb0ad503ee7fa752d451c905113d70721b937126158f3106a48cc1" dependencies = [ "proc-macro2 1.0.63", "quote 1.0.28", - "syn 1.0.109", + "syn 1.0.95", ] [[package]] @@ -5656,7 +5634,7 @@ dependencies = [ "itertools", "proc-macro2 1.0.63", "quote 1.0.28", - "syn 1.0.109", + "syn 1.0.95", ] [[package]] @@ -5844,13 +5822,14 @@ dependencies = [ [[package]] name = "rand" -version = "0.8.5" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" dependencies = [ "libc", "rand_chacha 0.3.1", "rand_core 0.6.4", + "rand_hc 0.3.1", ] [[package]] @@ -5934,6 +5913,15 @@ dependencies = [ "rand_core 0.5.1", ] +[[package]] +name = "rand_hc" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7" +dependencies = [ + "rand_core 0.6.4", +] + [[package]] name = "rand_isaac" version = "0.1.1" @@ -6002,7 +5990,7 @@ version = "10.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c49596760fce12ca21550ac21dc5a9617b2ea4b6e0aa7d8dab8ff2824fc2bba" dependencies = [ - "bitflags 1.3.2", + "bitflags", ] [[package]] @@ -6063,7 +6051,7 @@ version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" dependencies = [ - "bitflags 1.3.2", + "bitflags", ] [[package]] @@ -6104,7 +6092,7 @@ checksum = "0c523ccaed8ac4b0288948849a350b37d3035827413c458b6a40ddb614bb4f72" dependencies = [ "proc-macro2 1.0.63", "quote 1.0.28", - "syn 1.0.109", + "syn 1.0.95", ] [[package]] @@ -6314,7 +6302,7 @@ version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "01e213bc3ecb39ac32e81e51ebe31fd888a940515173e3a18a35f8c6e896422a" dependencies = [ - "bitflags 1.3.2", + "bitflags", "fallible-iterator", "fallible-streaming-iterator", "hashlink", @@ -6383,7 +6371,7 @@ version = "0.36.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd5c6ff11fecd55b40746d1995a02f2eb375bf8c00d192d521ee09f42bef37bc" dependencies = [ - "bitflags 1.3.2", + "bitflags", "errno 0.2.8", "io-lifetimes", "libc", @@ -6397,7 +6385,7 @@ version = "0.37.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2aae838e49b3d63e9274e1c01833cc8139d3fec468c3b84688c628f44b1ae11d" dependencies = [ - "bitflags 1.3.2", + "bitflags", "errno 0.3.1", "io-lifetimes", "libc", @@ -6405,19 +6393,6 @@ dependencies = [ "windows-sys 0.45.0", ] -[[package]] -name = "rustix" -version = "0.38.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc99bc2d4f1fed22595588a013687477aedf3cdcfb26558c559edb67b4d9b22e" -dependencies = [ - "bitflags 2.4.1", - "errno 0.3.1", - "libc", - "linux-raw-sys 0.4.11", - "windows-sys 0.48.0", -] - [[package]] name = "rustls" version = "0.19.1" @@ -6535,7 +6510,7 @@ dependencies = [ "proc-macro-crate 1.1.3", "proc-macro2 1.0.63", "quote 1.0.28", - "syn 1.0.109", + "syn 1.0.95", ] [[package]] @@ -6665,7 +6640,7 @@ version = "2.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" dependencies = [ - "bitflags 1.3.2", + "bitflags", "core-foundation", "core-foundation-sys", "libc", @@ -6729,7 +6704,7 @@ dependencies = [ "proc-macro2 1.0.63", "quote 1.0.28", "ser_error", - "syn 1.0.109", + "syn 1.0.95", ] [[package]] @@ -6792,7 +6767,7 @@ checksum = "2dc6b7951b17b051f3210b063f12cc17320e2fe30ae05b0fe2a3abb068551c76" dependencies = [ "proc-macro2 1.0.63", "quote 1.0.28", - "syn 1.0.109", + "syn 1.0.95", ] [[package]] @@ -7051,7 +7026,7 @@ dependencies = [ "futures 0.3.28", "httparse", "log", - "rand 0.8.5", + "rand 0.8.4", "sha-1", ] @@ -7276,7 +7251,7 @@ dependencies = [ "proc-macro2 1.0.63", "quote 1.0.28", "rustc_version 0.4.0", - "syn 1.0.109", + "syn 1.0.95", ] [[package]] @@ -7372,7 +7347,7 @@ checksum = "0a463f546a2f5842d35974bd4691ae5ceded6785ec24db440f773723f6ce4e11" dependencies = [ "base64 0.13.0", "bincode", - "bitflags 1.3.2", + "bitflags", "blake3", "borsh", "borsh-derive", @@ -7526,7 +7501,7 @@ dependencies = [ "assert_matches", "base64 0.13.0", "bincode", - "bitflags 1.3.2", + "bitflags", "borsh", "bs58 0.4.0", "bytemuck", @@ -7578,7 +7553,7 @@ dependencies = [ "proc-macro2 1.0.63", "quote 1.0.28", "rustversion", - "syn 1.0.109", + "syn 1.0.95", ] [[package]] @@ -7674,7 +7649,7 @@ version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77963e2aa8fadb589118c3aede2e78b6c4bcf1c01d588fbf33e915b390825fbd" dependencies = [ - "bitflags 1.3.2", + "bitflags", "byteorder", "hash-db", "hash256-std-hasher", @@ -7701,7 +7676,7 @@ checksum = "d676664972e22a0796176e81e7bec41df461d1edf52090955cdab55f2c956ff2" dependencies = [ "proc-macro2 1.0.63", "quote 1.0.28", - "syn 1.0.109", + "syn 1.0.95", ] [[package]] @@ -7731,7 +7706,7 @@ dependencies = [ "proc-macro-crate 1.1.3", "proc-macro2 1.0.63", "quote 1.0.28", - "syn 1.0.109", + "syn 1.0.95", ] [[package]] @@ -7953,9 +7928,9 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.109" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +checksum = "fbaf6116ab8924f39d52792136fb74fd60a80194cf1b1c6ffa6453eef1c3f942" dependencies = [ "proc-macro2 1.0.63", "quote 1.0.28", @@ -7996,7 +7971,7 @@ checksum = "b834f2d66f734cb897113e34aaff2f1ab4719ca946f9a7358dba8f8064148701" dependencies = [ "proc-macro2 1.0.63", "quote 1.0.28", - "syn 1.0.109", + "syn 1.0.95", "unicode-xid 0.2.0", ] @@ -8006,7 +7981,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" dependencies = [ - "bitflags 1.3.2", + "bitflags", "core-foundation", "system-configuration-sys", ] @@ -8558,7 +8533,7 @@ dependencies = [ "proc-macro2 1.0.63", "prost-build", "quote 1.0.28", - "syn 1.0.109", + "syn 1.0.95", ] [[package]] @@ -8572,7 +8547,7 @@ dependencies = [ "indexmap", "pin-project", "pin-project-lite 0.2.9", - "rand 0.8.5", + "rand 0.8.4", "slab", "tokio", "tokio-util", @@ -8587,7 +8562,7 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d342c6d58709c0a6d48d48dabbb62d4ef955cf5f0f3bbfd845838e7ae88dbae" dependencies = [ - "bitflags 1.3.2", + "bitflags", "bytes 1.4.0", "futures-core", "futures-util", @@ -8725,7 +8700,7 @@ dependencies = [ "idna", "ipnet", "lazy_static", - "rand 0.8.5", + "rand 0.8.4", "smallvec 1.6.1", "socket2 0.4.9", "thiserror", @@ -8773,7 +8748,7 @@ dependencies = [ "http 0.2.7", "httparse", "log", - "rand 0.8.5", + "rand 0.8.4", "rustls 0.20.4", "sha-1", "thiserror", @@ -8823,9 +8798,9 @@ checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee" [[package]] name = "unicode-normalization" -version = "0.1.22" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" dependencies = [ "tinyvec", ] @@ -8940,7 +8915,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "422ee0de9031b5b948b97a8fc04e3aa35230001a722ddd27943e0be31564ce4c" dependencies = [ "getrandom 0.2.9", - "rand 0.8.5", + "rand 0.8.4", "serde", ] @@ -9132,7 +9107,7 @@ dependencies = [ "log", "parking_lot 0.12.0", "pin-project", - "rand 0.8.5", + "rand 0.8.4", "rlp", "serde", "serde-wasm-bindgen", @@ -9191,14 +9166,12 @@ dependencies = [ [[package]] name = "which" -version = "4.4.2" +version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +checksum = "b55551e42cbdf2ce2bedd2203d0cc08dba002c27510f86dab6d0ce304cba3dfe" dependencies = [ "either", - "home", - "once_cell", - "rustix 0.38.25", + "libc", ] [[package]] @@ -9534,7 +9507,7 @@ dependencies = [ "log", "nohash-hasher", "parking_lot 0.12.0", - "rand 0.8.5", + "rand 0.8.4", "static_assertions", ] @@ -9675,7 +9648,7 @@ checksum = "3f8f187641dad4f680d25c4bfc4225b418165984179f26ca76ec4fb6441d3a17" dependencies = [ "proc-macro2 1.0.63", "quote 1.0.28", - "syn 1.0.109", + "syn 1.0.95", "synstructure", ] From 2e508717580a8ef70dbf3a93f68e3914f760740f Mon Sep 17 00:00:00 2001 From: shamardy Date: Tue, 28 Nov 2023 12:49:45 +0200 Subject: [PATCH 04/20] rollback some adex-cli Cargo.lock changes to be the same as cargo.lock for mm2 --- mm2src/adex_cli/Cargo.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mm2src/adex_cli/Cargo.lock b/mm2src/adex_cli/Cargo.lock index 27dc36bbfa..7c6b794dd1 100644 --- a/mm2src/adex_cli/Cargo.lock +++ b/mm2src/adex_cli/Cargo.lock @@ -336,9 +336,9 @@ dependencies = [ [[package]] name = "block-buffer" -version = "0.10.4" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" dependencies = [ "generic-array", ] @@ -799,7 +799,7 @@ version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer 0.10.4", + "block-buffer 0.10.2", "crypto-common", ] From eb04b12e42400882e333de07ed829ea7e5387b1d Mon Sep 17 00:00:00 2001 From: shamardy Date: Thu, 30 Nov 2023 12:16:42 +0200 Subject: [PATCH 05/20] Fix review notes: use consts where applicable, refactor MnemonicsTable upgrade logic --- mm2src/crypto/src/mnemonic.rs | 18 ++++++++---- .../mm2_db/src/indexed_db/drivers/upgrader.rs | 11 +++++++ .../src/lp_wallet/mnemonics_wasm_db.rs | 29 +++++++++++++++---- 3 files changed, 46 insertions(+), 12 deletions(-) diff --git a/mm2src/crypto/src/mnemonic.rs b/mm2src/crypto/src/mnemonic.rs index e6e5787dfa..cca481adb7 100644 --- a/mm2src/crypto/src/mnemonic.rs +++ b/mm2src/crypto/src/mnemonic.rs @@ -11,7 +11,13 @@ use mm2_err_handle::prelude::*; use sha2::Sha256; use std::convert::TryInto; +const ARGON2_ALGORITHM: &str = "Argon2id"; +const ARGON2ID_VERSION: &str = "0x13"; +const ARGON2ID_M_COST: u32 = 65536; +const ARGON2ID_T_COST: u32 = 2; +const ARGON2ID_P_COST: u32 = 1; const DEFAULT_WORD_COUNT: u64 = 24; +const ENCRYPTION_ALGORITHM: &str = "AES-256-CBC"; type Aes256CbcEnc = cbc::Encryptor; type Aes256CbcDec = cbc::Decryptor; @@ -73,11 +79,11 @@ pub struct Argon2Params { impl Default for Argon2Params { fn default() -> Self { Argon2Params { - algorithm: "Argon2id".to_string(), - version: "0x13".to_string(), - m_cost: 65536, - t_cost: 2, - p_cost: 1, + algorithm: ARGON2_ALGORITHM.to_string(), + version: ARGON2ID_VERSION.to_string(), + m_cost: ARGON2ID_M_COST, + t_cost: ARGON2ID_T_COST, + p_cost: ARGON2ID_P_COST, } } } @@ -267,7 +273,7 @@ pub fn encrypt_mnemonic(mnemonic: &str, password: &str) -> MmResult &'static str { "mnemonics" } - fn on_upgrade_needed(upgrader: &DbUpgrader, old_version: u32, new_version: u32) -> OnUpgradeResult<()> { - if let (0, 1) = (old_version, new_version) { - let table = upgrader.create_table(Self::table_name())?; - table.create_index("wallet_name", true)?; + fn on_upgrade_needed(upgrader: &DbUpgrader, mut old_version: u32, new_version: u32) -> OnUpgradeResult<()> { + while old_version < new_version { + match old_version { + 0 => { + // do nothing explicitly because the table should be created on upgrade + // from version 1 to 2 in order to avoid breaking existing databases + }, + 1 => { + let table = upgrader.create_table(Self::table_name())?; + table.create_index("wallet_name", true)?; + }, + unsupported_version => { + return MmError::err(OnUpgradeError::UnsupportedVersion { + unsupported_version, + old_version, + new_version, + }) + }, + } + + old_version += 1; } Ok(()) } From 825ecdb8a59e0303c33fbe807376c7ac05dc869c Mon Sep 17 00:00:00 2001 From: shamardy Date: Thu, 30 Nov 2023 13:55:12 +0200 Subject: [PATCH 06/20] Fix review notes: add EncryptionAlgorithm enum --- mm2src/crypto/src/mnemonic.rs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/mm2src/crypto/src/mnemonic.rs b/mm2src/crypto/src/mnemonic.rs index cca481adb7..72254a9089 100644 --- a/mm2src/crypto/src/mnemonic.rs +++ b/mm2src/crypto/src/mnemonic.rs @@ -17,7 +17,6 @@ const ARGON2ID_M_COST: u32 = 65536; const ARGON2ID_T_COST: u32 = 2; const ARGON2ID_P_COST: u32 = 1; const DEFAULT_WORD_COUNT: u64 = 24; -const ENCRYPTION_ALGORITHM: &str = "AES-256-CBC"; type Aes256CbcEnc = cbc::Encryptor; type Aes256CbcDec = cbc::Decryptor; @@ -51,6 +50,15 @@ impl From for MnemonicError { fn from(e: base64::DecodeError) -> Self { MnemonicError::DecodeError(e.to_string()) } } +/// Enum representing different encryption algorithms. +#[derive(Serialize, Deserialize, Debug)] +enum EncryptionAlgorithm { + /// AES-256-CBC algorithm. + AES256CBC, + // Placeholder for future algorithms. + // Future algorithms can be added here. +} + /// Parameters for the Argon2 key derivation function. /// /// This struct defines the configuration parameters used by Argon2, one of the @@ -129,7 +137,7 @@ impl Default for KeyDerivationDetails { pub struct EncryptedMnemonicData { /// The encryption algorithm used to encrypt the mnemonic. /// Example: "AES-256-CBC". - encryption_algorithm: String, + encryption_algorithm: EncryptionAlgorithm, /// Detailed information about the key derivation process. This includes /// the specific algorithm used (e.g., Argon2) and its parameters. @@ -273,7 +281,7 @@ pub fn encrypt_mnemonic(mnemonic: &str, password: &str) -> MmResult Date: Fri, 1 Dec 2023 16:12:24 +0200 Subject: [PATCH 07/20] Fix review notes: fix on_upgrade_needed fn for mnemonics table, make DEFAULT_WORD_COUNT = 12 --- mm2src/crypto/src/mnemonic.rs | 2 +- mm2src/mm2_main/src/lp_wallet/mnemonics_wasm_db.rs | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/mm2src/crypto/src/mnemonic.rs b/mm2src/crypto/src/mnemonic.rs index 72254a9089..4ebc9f5292 100644 --- a/mm2src/crypto/src/mnemonic.rs +++ b/mm2src/crypto/src/mnemonic.rs @@ -16,7 +16,7 @@ const ARGON2ID_VERSION: &str = "0x13"; const ARGON2ID_M_COST: u32 = 65536; const ARGON2ID_T_COST: u32 = 2; const ARGON2ID_P_COST: u32 = 1; -const DEFAULT_WORD_COUNT: u64 = 24; +const DEFAULT_WORD_COUNT: u64 = 12; type Aes256CbcEnc = cbc::Encryptor; type Aes256CbcDec = cbc::Decryptor; diff --git a/mm2src/mm2_main/src/lp_wallet/mnemonics_wasm_db.rs b/mm2src/mm2_main/src/lp_wallet/mnemonics_wasm_db.rs index 96fa3e7c3e..f278942c11 100644 --- a/mm2src/mm2_main/src/lp_wallet/mnemonics_wasm_db.rs +++ b/mm2src/mm2_main/src/lp_wallet/mnemonics_wasm_db.rs @@ -36,13 +36,10 @@ impl TableSignature for MnemonicsTable { while old_version < new_version { match old_version { 0 => { - // do nothing explicitly because the table should be created on upgrade - // from version 1 to 2 in order to avoid breaking existing databases - }, - 1 => { let table = upgrader.create_table(Self::table_name())?; table.create_index("wallet_name", true)?; }, + // handle new versions here if needed unsupported_version => { return MmError::err(OnUpgradeError::UnsupportedVersion { unsupported_version, From 69117b8f57f75c948667476f74d65d830054a125 Mon Sep 17 00:00:00 2001 From: shamardy Date: Fri, 1 Dec 2023 20:20:53 +0200 Subject: [PATCH 08/20] Fix review notes: fix doc comments of read_encrypted_passphrase --- mm2src/mm2_main/src/lp_wallet/mnemonics_storage.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mm2src/mm2_main/src/lp_wallet/mnemonics_storage.rs b/mm2src/mm2_main/src/lp_wallet/mnemonics_storage.rs index cfe389d040..676c1ed9a3 100644 --- a/mm2src/mm2_main/src/lp_wallet/mnemonics_storage.rs +++ b/mm2src/mm2_main/src/lp_wallet/mnemonics_storage.rs @@ -41,7 +41,7 @@ pub(super) async fn save_encrypted_passphrase( /// 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 `EncryptedPassphraseData`, which includes +/// The data is expected to be in the format of `EncryptedMnemonicData`, which includes /// all necessary components for decryption, such as the encryption algorithm, key derivation /// details, salts, IV, ciphertext, and HMAC tag. /// From 3bdb2d5bb98a30260c14327a3ec30e6764c17658 Mon Sep 17 00:00:00 2001 From: shamardy Date: Tue, 9 Jan 2024 14:44:34 +0200 Subject: [PATCH 09/20] allow importing of an encrypted passphrase --- mm2src/mm2_main/src/lp_wallet.rs | 126 ++++++++++++++++++++++--------- 1 file changed, 89 insertions(+), 37 deletions(-) diff --git a/mm2src/mm2_main/src/lp_wallet.rs b/mm2src/mm2_main/src/lp_wallet.rs index 8ff62b1589..7800fa1bee 100644 --- a/mm2src/mm2_main/src/lp_wallet.rs +++ b/mm2src/mm2_main/src/lp_wallet.rs @@ -1,4 +1,5 @@ -use crypto::{decrypt_mnemonic, encrypt_mnemonic, generate_mnemonic, CryptoCtx, CryptoInitError, MnemonicError}; +use crypto::{decrypt_mnemonic, encrypt_mnemonic, generate_mnemonic, CryptoCtx, CryptoInitError, EncryptedMnemonicData, + MnemonicError}; use mm2_core::mm_ctx::MmArc; use mm2_err_handle::prelude::*; use serde::de::DeserializeOwned; @@ -24,6 +25,10 @@ pub enum WalletInitError { field: String, error: String, }, + #[display(fmt = "The '{}' field not found in the config", field)] + FieldNotFoundInConfig { + field: String, + }, #[display(fmt = "Wallets storage error: {}", _0)] WalletsStorageError(String), #[display( @@ -97,6 +102,86 @@ async fn read_and_decrypt_passphrase( } } +#[derive(Debug, Deserialize, Serialize)] +#[serde(untagged)] +enum Passphrase { + Encrypted(EncryptedMnemonicData), + Decrypted(String), +} + +fn deserialize_wallet_config(ctx: &MmArc) -> WalletInitResult<(Option, Option)> { + let passphrase = deserialize_config_field::>(ctx, "passphrase")?; + // 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::>(ctx, "wallet_name")?; + Ok((wallet_name, passphrase)) +} + +async fn handle_passphrase_logic( + ctx: &MmArc, + wallet_name: Option, + passphrase: Option, +) -> WalletInitResult> { + match (wallet_name, passphrase) { + (None, None) => Ok(None), + // Legacy approach for passphrase, no `wallet_name` is needed in the config, in this case the passphrase is not encrypted and saved. + (None, Some(Passphrase::Decrypted(passphrase))) => Ok(Some(passphrase)), + // Importing an encrypted passphrase without a wallet name is not supported since it's not possible to save the passphrase. + (None, Some(Passphrase::Encrypted(_))) => MmError::err(WalletInitError::FieldNotFoundInConfig { + field: "wallet_name".to_owned(), + }), + // Passphrase is not provided. Generate, encrypt and save passphrase if not already saved. + (Some(wallet_name), None) => { + let wallet_password = deserialize_config_field::(ctx, "wallet_password")?; + match read_and_decrypt_passphrase(ctx, &wallet_name, &wallet_password).await? { + Some(passphrase_from_file) => Ok(Some(passphrase_from_file)), + None => { + let new_passphrase = generate_mnemonic(ctx)?.to_string(); + encrypt_and_save_passphrase(ctx, &wallet_name, &new_passphrase, &wallet_password).await?; + Ok(Some(new_passphrase)) + }, + } + }, + // Passphrase is provided. Encrypt and save passphrase if not already saved. + (Some(wallet_name), Some(Passphrase::Decrypted(passphrase))) => { + let wallet_password = deserialize_config_field::(ctx, "wallet_password")?; + match read_and_decrypt_passphrase(ctx, &wallet_name, &wallet_password).await? { + Some(passphrase_from_file) if passphrase == passphrase_from_file => Ok(Some(passphrase)), + None => { + encrypt_and_save_passphrase(ctx, &wallet_name, &passphrase, &wallet_password).await?; + Ok(Some(passphrase)) + }, + _ => MmError::err(WalletInitError::PassphraseMismatch), + } + }, + // Encrypted passphrase is provided. Decrypt and save encrypted passphrase if not already saved. + (Some(wallet_name), Some(Passphrase::Encrypted(encrypted_passphrase_data))) => { + let wallet_password = deserialize_config_field::(ctx, "wallet_password")?; + let passphrase = decrypt_mnemonic(&encrypted_passphrase_data, &wallet_password)?.to_string(); + match read_and_decrypt_passphrase(ctx, &wallet_name, &wallet_password).await? { + Some(passphrase_from_file) if passphrase == passphrase_from_file => Ok(Some(passphrase)), + None => { + save_encrypted_passphrase(ctx, &wallet_name, &encrypted_passphrase_data) + .await + .mm_err(|e| WalletInitError::WalletsStorageError(e.to_string()))?; + Ok(Some(passphrase)) + }, + _ => MmError::err(WalletInitError::PassphraseMismatch), + } + }, + } +} + +fn initialize_crypto_context(ctx: &MmArc, passphrase: &str) -> WalletInitResult<()> { + // This defaults to false to maintain backward compatibility. + match ctx.conf["enable_hd"].as_bool().unwrap_or(false) { + true => CryptoCtx::init_with_global_hd_account(ctx.clone(), passphrase)?, + false => CryptoCtx::init_with_iguana_passphrase(ctx.clone(), passphrase)?, + }; + Ok(()) +} + /// Initializes and manages the wallet passphrase. /// /// This function handles several scenarios based on the configuration: @@ -121,44 +206,11 @@ async fn read_and_decrypt_passphrase( /// Returns `MmInitError` if deserialization fails or if there are issues in passphrase handling. /// pub(crate) async fn initialize_wallet_passphrase(ctx: MmArc) -> WalletInitResult<()> { - let passphrase = deserialize_config_field::>(&ctx, "passphrase")?; - // 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::>(&ctx, "wallet_name")?; - - let passphrase = match (wallet_name, passphrase) { - (None, None) => None, - // Legacy approach for passphrase, no `wallet_name` is needed in the config, in this case the passphrase is not encrypted and saved. - (None, Some(passphrase)) => Some(passphrase), - // New mode, passphrase is not provided. Generate, encrypt and save passphrase. - // passphrase is provided. encrypt and save passphrase. - (Some(wallet_name), maybe_passphrase) => { - let wallet_password = deserialize_config_field::(&ctx, "wallet_password")?; - match read_and_decrypt_passphrase(&ctx, &wallet_name, &wallet_password).await? { - Some(passphrase_from_file) => match maybe_passphrase { - Some(passphrase) if passphrase == passphrase_from_file => Some(passphrase), - None => Some(passphrase_from_file), - _ => return MmError::err(WalletInitError::PassphraseMismatch), - }, - None => { - let new_passphrase = match maybe_passphrase { - Some(passphrase) => passphrase, - None => generate_mnemonic(&ctx)?.to_string(), - }; - encrypt_and_save_passphrase(&ctx, &wallet_name, &new_passphrase, &wallet_password).await?; - Some(new_passphrase) - }, - } - }, - }; + let (wallet_name, passphrase) = deserialize_wallet_config(&ctx)?; + let passphrase = handle_passphrase_logic(&ctx, wallet_name, passphrase).await?; if let Some(passphrase) = passphrase { - // This defaults to false to maintain backward compatibility. - match ctx.conf["enable_hd"].as_bool().unwrap_or(false) { - true => CryptoCtx::init_with_global_hd_account(ctx.clone(), &passphrase)?, - false => CryptoCtx::init_with_iguana_passphrase(ctx.clone(), &passphrase)?, - }; + initialize_crypto_context(&ctx, &passphrase)?; } Ok(()) From 2df7834851747cbe6785d7be7ebbcd0dbefeb854 Mon Sep 17 00:00:00 2001 From: shamardy Date: Wed, 10 Jan 2024 13:27:18 +0200 Subject: [PATCH 10/20] refactor handle_passphrase_logic --- mm2src/mm2_main/src/lp_wallet.rs | 139 ++++++++++++++++++++++--------- 1 file changed, 98 insertions(+), 41 deletions(-) diff --git a/mm2src/mm2_main/src/lp_wallet.rs b/mm2src/mm2_main/src/lp_wallet.rs index 7800fa1bee..e22e651706 100644 --- a/mm2src/mm2_main/src/lp_wallet.rs +++ b/mm2src/mm2_main/src/lp_wallet.rs @@ -118,7 +118,97 @@ fn deserialize_wallet_config(ctx: &MmArc) -> WalletInitResult<(Option, O Ok((wallet_name, passphrase)) } -async fn handle_passphrase_logic( +/// Passphrase is not provided. Generate, encrypt and save passphrase if not already saved. +async fn retrieve_or_create_passphrase( + ctx: &MmArc, + wallet_name: &str, + wallet_password: &str, +) -> WalletInitResult> { + match read_and_decrypt_passphrase(ctx, wallet_name, wallet_password).await? { + Some(passphrase_from_file) => { + // If an existing passphrase is found, return it + Ok(Some(passphrase_from_file)) + }, + None => { + // If no passphrase is found, generate a new one + let new_passphrase = generate_mnemonic(ctx)?.to_string(); + // Encrypt and save the new passphrase + encrypt_and_save_passphrase(ctx, wallet_name, &new_passphrase, wallet_password).await?; + Ok(Some(new_passphrase)) + }, + } +} + +/// Passphrase is provided in plaintext. Encrypt and save passphrase if not already saved. +async fn confirm_or_encrypt_and_store_passphrase( + ctx: &MmArc, + wallet_name: &str, + passphrase: &str, + wallet_password: &str, +) -> WalletInitResult> { + match read_and_decrypt_passphrase(ctx, wallet_name, wallet_password).await? { + Some(passphrase_from_file) if passphrase == passphrase_from_file => { + // If an existing passphrase is found and it matches the provided passphrase, return it + Ok(Some(passphrase_from_file)) + }, + None => { + // If no passphrase is found in the file, encrypt and save the provided passphrase + encrypt_and_save_passphrase(ctx, wallet_name, passphrase, wallet_password).await?; + Ok(Some(passphrase.to_string())) + }, + _ => { + // If an existing passphrase is found and it does not match the provided passphrase, return an error + Err(WalletInitError::PassphraseMismatch.into()) + }, + } +} + +/// Encrypted passphrase is provided. Decrypt and save encrypted passphrase if not already saved. +async fn decrypt_validate_or_save_passphrase( + ctx: &MmArc, + wallet_name: &str, + encrypted_passphrase_data: EncryptedMnemonicData, + wallet_password: &str, +) -> WalletInitResult> { + // Decrypt the provided encrypted passphrase + let decrypted_passphrase = decrypt_mnemonic(&encrypted_passphrase_data, wallet_password)?.to_string(); + + match read_and_decrypt_passphrase(ctx, wallet_name, wallet_password).await? { + Some(passphrase_from_file) if decrypted_passphrase == passphrase_from_file => { + // If an existing passphrase is found and it matches the decrypted passphrase, return it + Ok(Some(decrypted_passphrase)) + }, + None => { + save_encrypted_passphrase(ctx, wallet_name, &encrypted_passphrase_data) + .await + .mm_err(|e| WalletInitError::WalletsStorageError(e.to_string()))?; + Ok(Some(decrypted_passphrase)) + }, + _ => { + // If an existing passphrase is found and it does not match the decrypted passphrase, return an error + Err(WalletInitError::PassphraseMismatch.into()) + }, + } +} + +async fn process_wallet_with_name( + ctx: &MmArc, + wallet_name: &str, + passphrase: Option, + wallet_password: &str, +) -> WalletInitResult> { + match passphrase { + None => retrieve_or_create_passphrase(ctx, wallet_name, wallet_password).await, + Some(Passphrase::Decrypted(passphrase)) => { + confirm_or_encrypt_and_store_passphrase(ctx, wallet_name, &passphrase, wallet_password).await + }, + Some(Passphrase::Encrypted(encrypted_data)) => { + decrypt_validate_or_save_passphrase(ctx, wallet_name, encrypted_data, wallet_password).await + }, + } +} + +async fn process_passphrase_logic( ctx: &MmArc, wallet_name: Option, passphrase: Option, @@ -128,47 +218,14 @@ async fn handle_passphrase_logic( // Legacy approach for passphrase, no `wallet_name` is needed in the config, in this case the passphrase is not encrypted and saved. (None, Some(Passphrase::Decrypted(passphrase))) => Ok(Some(passphrase)), // Importing an encrypted passphrase without a wallet name is not supported since it's not possible to save the passphrase. - (None, Some(Passphrase::Encrypted(_))) => MmError::err(WalletInitError::FieldNotFoundInConfig { + (None, Some(Passphrase::Encrypted(_))) => Err(WalletInitError::FieldNotFoundInConfig { field: "wallet_name".to_owned(), - }), - // Passphrase is not provided. Generate, encrypt and save passphrase if not already saved. - (Some(wallet_name), None) => { - let wallet_password = deserialize_config_field::(ctx, "wallet_password")?; - match read_and_decrypt_passphrase(ctx, &wallet_name, &wallet_password).await? { - Some(passphrase_from_file) => Ok(Some(passphrase_from_file)), - None => { - let new_passphrase = generate_mnemonic(ctx)?.to_string(); - encrypt_and_save_passphrase(ctx, &wallet_name, &new_passphrase, &wallet_password).await?; - Ok(Some(new_passphrase)) - }, - } - }, - // Passphrase is provided. Encrypt and save passphrase if not already saved. - (Some(wallet_name), Some(Passphrase::Decrypted(passphrase))) => { - let wallet_password = deserialize_config_field::(ctx, "wallet_password")?; - match read_and_decrypt_passphrase(ctx, &wallet_name, &wallet_password).await? { - Some(passphrase_from_file) if passphrase == passphrase_from_file => Ok(Some(passphrase)), - None => { - encrypt_and_save_passphrase(ctx, &wallet_name, &passphrase, &wallet_password).await?; - Ok(Some(passphrase)) - }, - _ => MmError::err(WalletInitError::PassphraseMismatch), - } - }, - // Encrypted passphrase is provided. Decrypt and save encrypted passphrase if not already saved. - (Some(wallet_name), Some(Passphrase::Encrypted(encrypted_passphrase_data))) => { + } + .into()), + + (Some(wallet_name), passphrase_option) => { let wallet_password = deserialize_config_field::(ctx, "wallet_password")?; - let passphrase = decrypt_mnemonic(&encrypted_passphrase_data, &wallet_password)?.to_string(); - match read_and_decrypt_passphrase(ctx, &wallet_name, &wallet_password).await? { - Some(passphrase_from_file) if passphrase == passphrase_from_file => Ok(Some(passphrase)), - None => { - save_encrypted_passphrase(ctx, &wallet_name, &encrypted_passphrase_data) - .await - .mm_err(|e| WalletInitError::WalletsStorageError(e.to_string()))?; - Ok(Some(passphrase)) - }, - _ => MmError::err(WalletInitError::PassphraseMismatch), - } + process_wallet_with_name(ctx, &wallet_name, passphrase_option, &wallet_password).await }, } } @@ -207,7 +264,7 @@ fn initialize_crypto_context(ctx: &MmArc, passphrase: &str) -> WalletInitResult< /// pub(crate) async fn initialize_wallet_passphrase(ctx: MmArc) -> WalletInitResult<()> { let (wallet_name, passphrase) = deserialize_wallet_config(&ctx)?; - let passphrase = handle_passphrase_logic(&ctx, wallet_name, passphrase).await?; + let passphrase = process_passphrase_logic(&ctx, wallet_name, passphrase).await?; if let Some(passphrase) = passphrase { initialize_crypto_context(&ctx, &passphrase)?; From 839beb683180c2824e0434eb6e9f182c64eaf48d Mon Sep 17 00:00:00 2001 From: shamardy Date: Wed, 17 Jan 2024 14:32:47 +0200 Subject: [PATCH 11/20] make global wallets db part of ctx --- mm2src/mm2_core/src/mm_ctx.rs | 5 ++ mm2src/mm2_db/src/indexed_db/db_lock.rs | 16 +++- mm2src/mm2_db/src/indexed_db/indexed_db.rs | 13 ++- mm2src/mm2_main/src/lp_wallet.rs | 37 ++++++++- .../src/lp_wallet/mnemonics_wasm_db.rs | 79 +++++++++---------- 5 files changed, 97 insertions(+), 53 deletions(-) diff --git a/mm2src/mm2_core/src/mm_ctx.rs b/mm2src/mm2_core/src/mm_ctx.rs index d828cc0b9c..a24ffdedae 100644 --- a/mm2src/mm2_core/src/mm_ctx.rs +++ b/mm2src/mm2_core/src/mm_ctx.rs @@ -107,6 +107,9 @@ pub struct MmCtx { pub swaps_ctx: Mutex>>, /// The context belonging to the `lp_stats` mod: `StatsContext` pub stats_ctx: Mutex>>, + /// The context belonging to the `lp_wallet` mod: `WalletsContext`. + #[cfg(target_arch = "wasm32")] + pub wallets_ctx: Mutex>>, /// The RPC sender forwarding requests to writing part of underlying stream. #[cfg(target_arch = "wasm32")] pub wasm_rpc: Constructible, @@ -159,6 +162,8 @@ impl MmCtx { swaps_ctx: Mutex::new(None), stats_ctx: Mutex::new(None), #[cfg(target_arch = "wasm32")] + wallets_ctx: Mutex::new(None), + #[cfg(target_arch = "wasm32")] wasm_rpc: Constructible::default(), #[cfg(not(target_arch = "wasm32"))] sqlite_connection: Constructible::default(), diff --git a/mm2src/mm2_db/src/indexed_db/db_lock.rs b/mm2src/mm2_db/src/indexed_db/db_lock.rs index 2a650f0c5f..1ca10262f9 100644 --- a/mm2src/mm2_db/src/indexed_db/db_lock.rs +++ b/mm2src/mm2_db/src/indexed_db/db_lock.rs @@ -14,7 +14,7 @@ pub struct ConstructibleDb { /// It's better to use something like [`Constructible`], but it doesn't provide a method to get the inner value by the mutable reference. mutex: AsyncMutex>, db_namespace: DbNamespaceId, - wallet_rmd160: H160, + wallet_rmd160: Option, } impl ConstructibleDb { @@ -26,7 +26,7 @@ impl ConstructibleDb { ConstructibleDb { mutex: AsyncMutex::new(None), db_namespace: ctx.db_namespace, - wallet_rmd160: *ctx.rmd160(), + wallet_rmd160: Some(*ctx.rmd160()), } } @@ -37,7 +37,17 @@ impl ConstructibleDb { ConstructibleDb { mutex: AsyncMutex::new(None), db_namespace: ctx.db_namespace, - wallet_rmd160: *ctx.shared_db_id(), + wallet_rmd160: Some(*ctx.shared_db_id()), + } + } + + /// Creates a new uninitialized `Db` instance shared between all wallets/seed. + /// This can be initialized later using [`ConstructibleDb::get_or_initialize`]. + pub fn new_global_db(ctx: &MmArc) -> Self { + ConstructibleDb { + mutex: AsyncMutex::new(None), + db_namespace: ctx.db_namespace, + wallet_rmd160: None, } } diff --git a/mm2src/mm2_db/src/indexed_db/indexed_db.rs b/mm2src/mm2_db/src/indexed_db/indexed_db.rs index d5ab29ae36..4d539db86a 100644 --- a/mm2src/mm2_db/src/indexed_db/indexed_db.rs +++ b/mm2src/mm2_db/src/indexed_db/indexed_db.rs @@ -83,14 +83,14 @@ pub struct DbIdentifier { namespace_id: DbNamespaceId, /// The `RIPEMD160(SHA256(x))` where x is secp256k1 pubkey derived from passphrase. /// This value is used to distinguish different databases corresponding to user's different seed phrases. - wallet_rmd160: H160, + wallet_rmd160: Option, db_name: &'static str, } impl DbIdentifier { pub fn db_name(&self) -> &'static str { self.db_name } - pub fn new(namespace_id: DbNamespaceId, wallet_rmd160: H160) -> DbIdentifier { + pub fn new(namespace_id: DbNamespaceId, wallet_rmd160: Option) -> DbIdentifier { DbIdentifier { namespace_id, wallet_rmd160, @@ -101,12 +101,17 @@ impl DbIdentifier { pub fn for_test(db_name: &'static str) -> DbIdentifier { DbIdentifier { namespace_id: DbNamespaceId::for_test(), - wallet_rmd160: H160::default(), + wallet_rmd160: Some(H160::default()), db_name, } } - pub fn display_rmd160(&self) -> String { hex::encode(*self.wallet_rmd160) } + pub fn display_rmd160(&self) -> String { + self.wallet_rmd160 + .map(|rmd160| hex::encode(rmd160)) + .unwrap_or_else(|| "KOMODEFI".to_string()) + // hex::encode(*self.wallet_rmd160) + } } pub struct IndexedDbBuilder { diff --git a/mm2src/mm2_main/src/lp_wallet.rs b/mm2src/mm2_main/src/lp_wallet.rs index e22e651706..55af987ccf 100644 --- a/mm2src/mm2_main/src/lp_wallet.rs +++ b/mm2src/mm2_main/src/lp_wallet.rs @@ -5,16 +5,26 @@ use mm2_err_handle::prelude::*; use serde::de::DeserializeOwned; use serde_json::{self as json}; +cfg_wasm32! { + use crate::mm2::lp_wallet::mnemonics_wasm_db::WalletsDb; + use mm2_core::mm_ctx::from_ctx; + use mm2_db::indexed_db::{ConstructibleDb, DbLocked, InitDbResult}; + use mnemonics_wasm_db::{read_encrypted_passphrase, save_encrypted_passphrase}; + use std::sync::Arc; + + type WalletsDbLocked<'a> = DbLocked<'a, WalletsDb>; +} + +cfg_native! { + use mnemonics_storage::{read_encrypted_passphrase, save_encrypted_passphrase}; +} + #[cfg(not(target_arch = "wasm32"))] #[path = "lp_wallet/mnemonics_storage.rs"] mod mnemonics_storage; -#[cfg(not(target_arch = "wasm32"))] -use mnemonics_storage::{read_encrypted_passphrase, save_encrypted_passphrase}; #[cfg(target_arch = "wasm32")] #[path = "lp_wallet/mnemonics_wasm_db.rs"] mod mnemonics_wasm_db; -#[cfg(target_arch = "wasm32")] -use mnemonics_wasm_db::{read_encrypted_passphrase, save_encrypted_passphrase}; type WalletInitResult = Result>; @@ -50,6 +60,25 @@ impl From for WalletInitError { fn from(e: CryptoInitError) -> Self { WalletInitError::CryptoInitError(e.to_string()) } } +#[cfg(target_arch = "wasm32")] +struct WalletsContext { + wallets_db: ConstructibleDb, +} + +#[cfg(target_arch = "wasm32")] +impl WalletsContext { + /// Obtains a reference to this crate context, creating it if necessary. + fn from_ctx(ctx: &MmArc) -> Result, String> { + Ok(try_s!(from_ctx(&ctx.wallets_ctx, move || { + Ok(WalletsContext { + #[cfg(target_arch = "wasm32")] + wallets_db: ConstructibleDb::new_global_db(ctx), + }) + }))) + } + pub async fn wallets_db(&self) -> InitDbResult> { self.wallets_db.get_or_initialize().await } +} + // Utility function for deserialization to reduce repetition fn deserialize_config_field(ctx: &MmArc, field: &str) -> WalletInitResult { json::from_value::(ctx.conf[field].clone()).map_to_mm(|e| WalletInitError::ErrorDeserializingConfig { diff --git a/mm2src/mm2_main/src/lp_wallet/mnemonics_wasm_db.rs b/mm2src/mm2_main/src/lp_wallet/mnemonics_wasm_db.rs index f278942c11..060d9b17a6 100644 --- a/mm2src/mm2_main/src/lp_wallet/mnemonics_wasm_db.rs +++ b/mm2src/mm2_main/src/lp_wallet/mnemonics_wasm_db.rs @@ -1,10 +1,15 @@ +use crate::mm2::lp_wallet::WalletsContext; +use async_trait::async_trait; use crypto::EncryptedMnemonicData; use mm2_core::mm_ctx::MmArc; use mm2_core::DbNamespaceId; -use mm2_db::indexed_db::{DbIdentifier, DbInstance, DbUpgrader, IndexedDb, IndexedDbBuilder, OnUpgradeError, - OnUpgradeResult, TableSignature}; +use mm2_db::indexed_db::{DbIdentifier, DbInstance, DbUpgrader, IndexedDb, IndexedDbBuilder, InitDbResult, + OnUpgradeError, OnUpgradeResult, TableSignature}; use mm2_err_handle::prelude::*; use std::collections::HashMap; +use std::ops::Deref; + +const DB_VERSION: u32 = 1; type WalletsDBResult = Result>; @@ -29,6 +34,30 @@ struct MnemonicsTable { encrypted_mnemonic: String, } +pub struct WalletsDb { + inner: IndexedDb, +} + +#[async_trait] +impl DbInstance for WalletsDb { + const DB_NAME: &'static str = "wallets"; + + async fn init(db_id: DbIdentifier) -> InitDbResult { + let inner = IndexedDbBuilder::new(db_id) + .with_version(DB_VERSION) + .with_table::() + .build() + .await?; + Ok(WalletsDb { inner }) + } +} + +impl Deref for WalletsDb { + type Target = IndexedDb; + + fn deref(&self) -> &Self::Target { &self.inner } +} + impl TableSignature for MnemonicsTable { fn table_name() -> &'static str { "mnemonics" } @@ -60,26 +89,9 @@ pub(super) async fn save_encrypted_passphrase( wallet_name: &str, encrypted_passphrase_data: &EncryptedMnemonicData, ) -> WalletsDBResult<()> { - const DB_VERSION: u32 = 1; - - // Create the database identifier - let db_name = "wallets"; - let db_id = match ctx.db_namespace { - DbNamespaceId::Main => format!("MAIN::KOMODEFI::{}", db_name), - DbNamespaceId::Test(u) => format!("TEST_{}::KOMODEFI::{}", u, db_name), - }; - - let indexed_db_builder = IndexedDbBuilder { - db_name: db_id, - db_version: 1, - tables: HashMap::new(), - }; - - // Initialize the database instance with the mnemonic table - let db = indexed_db_builder - .with_version(DB_VERSION) - .with_table::() - .build() + let wallets_ctx = WalletsContext::from_ctx(ctx).map_to_mm(WalletsDBError::Internal)?; + let db = wallets_ctx + .wallets_db() .await .mm_err(|e| WalletsDBError::Internal(e.to_string()))?; @@ -113,26 +125,9 @@ pub(super) async fn read_encrypted_passphrase( ctx: &MmArc, wallet_name: &str, ) -> WalletsDBResult> { - const DB_VERSION: u32 = 1; - - // Create the database identifier - let db_name = "wallets"; - let db_id = match ctx.db_namespace { - DbNamespaceId::Main => format!("MAIN::KOMODEFI::{}", db_name), - DbNamespaceId::Test(u) => format!("TEST_{}::KOMODEFI::{}", u, db_name), - }; - - let indexed_db_builder = IndexedDbBuilder { - db_name: db_id, - db_version: 1, - tables: HashMap::new(), - }; - - // Initialize the database instance with the mnemonic table - let db = indexed_db_builder - .with_version(DB_VERSION) - .with_table::() - .build() + let wallets_ctx = WalletsContext::from_ctx(ctx).map_to_mm(WalletsDBError::Internal)?; + let db = wallets_ctx + .wallets_db() .await .mm_err(|e| WalletsDBError::Internal(e.to_string()))?; From 4b7c014e3749b30d3646f3cdffcdc10d4e685bc8 Mon Sep 17 00:00:00 2001 From: shamardy Date: Wed, 17 Jan 2024 16:14:25 +0200 Subject: [PATCH 12/20] fix wasm clippy --- mm2src/mm2_db/src/indexed_db/indexed_db.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/mm2src/mm2_db/src/indexed_db/indexed_db.rs b/mm2src/mm2_db/src/indexed_db/indexed_db.rs index 4d539db86a..3a4cb5b218 100644 --- a/mm2src/mm2_db/src/indexed_db/indexed_db.rs +++ b/mm2src/mm2_db/src/indexed_db/indexed_db.rs @@ -108,9 +108,8 @@ impl DbIdentifier { pub fn display_rmd160(&self) -> String { self.wallet_rmd160 - .map(|rmd160| hex::encode(rmd160)) + .map(hex::encode) .unwrap_or_else(|| "KOMODEFI".to_string()) - // hex::encode(*self.wallet_rmd160) } } From 5a65a48aeb35488daa69a23453431e5a925160f0 Mon Sep 17 00:00:00 2001 From: shamardy Date: Tue, 6 Feb 2024 23:49:42 +0200 Subject: [PATCH 13/20] add get_mnemonic rpc --- mm2src/mm2_core/src/mm_ctx.rs | 3 + mm2src/mm2_main/src/lp_wallet.rs | 242 +++++++++++++++++- .../src/lp_wallet/mnemonics_storage.rs | 16 +- .../src/lp_wallet/mnemonics_wasm_db.rs | 12 +- .../mm2_main/src/rpc/dispatcher/dispatcher.rs | 2 + 5 files changed, 253 insertions(+), 22 deletions(-) diff --git a/mm2src/mm2_core/src/mm_ctx.rs b/mm2src/mm2_core/src/mm_ctx.rs index a24ffdedae..491daed952 100644 --- a/mm2src/mm2_core/src/mm_ctx.rs +++ b/mm2src/mm2_core/src/mm_ctx.rs @@ -107,6 +107,8 @@ pub struct MmCtx { pub swaps_ctx: Mutex>>, /// The context belonging to the `lp_stats` mod: `StatsContext` pub stats_ctx: Mutex>>, + /// Wallet name for this mm2 instance. Optional for backwards compatibility. + pub wallet_name: Constructible>, /// The context belonging to the `lp_wallet` mod: `WalletsContext`. #[cfg(target_arch = "wasm32")] pub wallets_ctx: Mutex>>, @@ -161,6 +163,7 @@ impl MmCtx { coins_needed_for_kick_start: Mutex::new(HashSet::new()), swaps_ctx: Mutex::new(None), stats_ctx: Mutex::new(None), + wallet_name: Constructible::default(), #[cfg(target_arch = "wasm32")] wallets_ctx: Mutex::new(None), #[cfg(target_arch = "wasm32")] diff --git a/mm2src/mm2_main/src/lp_wallet.rs b/mm2src/mm2_main/src/lp_wallet.rs index 55af987ccf..d6ce56b08d 100644 --- a/mm2src/mm2_main/src/lp_wallet.rs +++ b/mm2src/mm2_main/src/lp_wallet.rs @@ -1,12 +1,14 @@ +use common::HttpStatusCode; use crypto::{decrypt_mnemonic, encrypt_mnemonic, generate_mnemonic, CryptoCtx, CryptoInitError, EncryptedMnemonicData, MnemonicError}; +use http::StatusCode; use mm2_core::mm_ctx::MmArc; use mm2_err_handle::prelude::*; use serde::de::DeserializeOwned; use serde_json::{self as json}; cfg_wasm32! { - use crate::mm2::lp_wallet::mnemonics_wasm_db::WalletsDb; + use crate::mm2::lp_wallet::mnemonics_wasm_db::{WalletsDb, WalletsDBError}; use mm2_core::mm_ctx::from_ctx; use mm2_db::indexed_db::{ConstructibleDb, DbLocked, InitDbResult}; use mnemonics_wasm_db::{read_encrypted_passphrase, save_encrypted_passphrase}; @@ -16,7 +18,7 @@ cfg_wasm32! { } cfg_native! { - use mnemonics_storage::{read_encrypted_passphrase, save_encrypted_passphrase}; + use mnemonics_storage::{read_encrypted_passphrase, save_encrypted_passphrase, WalletsStorageError}; } #[cfg(not(target_arch = "wasm32"))] @@ -45,21 +47,38 @@ pub enum WalletInitError { fmt = "Passphrase doesn't match the one from file, please create a new wallet if you want to use a new passphrase" )] PassphraseMismatch, - #[display(fmt = "Error generating mnemonic: {}", _0)] - GenerateMnemonicError(String), + #[display(fmt = "Error generating or decrypting mnemonic: {}", _0)] + MnemonicError(String), #[display(fmt = "Error initializing crypto context: {}", _0)] CryptoInitError(String), Internal(String), } impl From for WalletInitError { - fn from(e: MnemonicError) -> Self { WalletInitError::GenerateMnemonicError(e.to_string()) } + fn from(e: MnemonicError) -> Self { WalletInitError::MnemonicError(e.to_string()) } } impl From for WalletInitError { fn from(e: CryptoInitError) -> Self { WalletInitError::CryptoInitError(e.to_string()) } } +#[derive(Debug, Deserialize, Display, Serialize)] +pub enum ReadPassphraseError { + #[display(fmt = "Wallets storage error: {}", _0)] + WalletsStorageError(String), + #[display(fmt = "Error decrypting passphrase: {}", _0)] + DecryptionError(String), +} + +impl From for WalletInitError { + fn from(e: ReadPassphraseError) -> Self { + match e { + ReadPassphraseError::WalletsStorageError(e) => WalletInitError::WalletsStorageError(e), + ReadPassphraseError::DecryptionError(e) => WalletInitError::MnemonicError(e), + } + } +} + #[cfg(target_arch = "wasm32")] struct WalletsContext { wallets_db: ConstructibleDb, @@ -116,15 +135,15 @@ async fn encrypt_and_save_passphrase( /// Returns specific `MmInitError` variants for different failure scenarios. async fn read_and_decrypt_passphrase( ctx: &MmArc, - wallet_name: &str, wallet_password: &str, -) -> WalletInitResult> { - match read_encrypted_passphrase(ctx, wallet_name) +) -> MmResult, ReadPassphraseError> { + match read_encrypted_passphrase(ctx) .await - .mm_err(|e| WalletInitError::WalletsStorageError(e.to_string()))? + .mm_err(|e| ReadPassphraseError::WalletsStorageError(e.to_string()))? { Some(encrypted_passphrase) => { - let mnemonic = decrypt_mnemonic(&encrypted_passphrase, wallet_password)?; + let mnemonic = decrypt_mnemonic(&encrypted_passphrase, wallet_password) + .mm_err(|e| ReadPassphraseError::DecryptionError(e.to_string()))?; Ok(Some(mnemonic.to_string())) }, None => Ok(None), @@ -153,7 +172,7 @@ async fn retrieve_or_create_passphrase( wallet_name: &str, wallet_password: &str, ) -> WalletInitResult> { - match read_and_decrypt_passphrase(ctx, wallet_name, wallet_password).await? { + match read_and_decrypt_passphrase(ctx, wallet_password).await? { Some(passphrase_from_file) => { // If an existing passphrase is found, return it Ok(Some(passphrase_from_file)) @@ -175,7 +194,7 @@ async fn confirm_or_encrypt_and_store_passphrase( passphrase: &str, wallet_password: &str, ) -> WalletInitResult> { - match read_and_decrypt_passphrase(ctx, wallet_name, wallet_password).await? { + match read_and_decrypt_passphrase(ctx, wallet_password).await? { Some(passphrase_from_file) if passphrase == passphrase_from_file => { // If an existing passphrase is found and it matches the provided passphrase, return it Ok(Some(passphrase_from_file)) @@ -202,7 +221,7 @@ async fn decrypt_validate_or_save_passphrase( // Decrypt the provided encrypted passphrase let decrypted_passphrase = decrypt_mnemonic(&encrypted_passphrase_data, wallet_password)?.to_string(); - match read_and_decrypt_passphrase(ctx, wallet_name, wallet_password).await? { + match read_and_decrypt_passphrase(ctx, wallet_password).await? { Some(passphrase_from_file) if decrypted_passphrase == passphrase_from_file => { // If an existing passphrase is found and it matches the decrypted passphrase, return it Ok(Some(decrypted_passphrase)) @@ -293,6 +312,9 @@ fn initialize_crypto_context(ctx: &MmArc, passphrase: &str) -> WalletInitResult< /// pub(crate) async fn initialize_wallet_passphrase(ctx: MmArc) -> WalletInitResult<()> { let (wallet_name, passphrase) = deserialize_wallet_config(&ctx)?; + ctx.wallet_name + .pin(wallet_name.clone()) + .map_to_mm(WalletInitError::Internal)?; let passphrase = process_passphrase_logic(&ctx, wallet_name, passphrase).await?; if let Some(passphrase) = passphrase { @@ -301,3 +323,197 @@ pub(crate) async fn initialize_wallet_passphrase(ctx: MmArc) -> WalletInitResult Ok(()) } + +/// `MnemonicFormat` is an enum representing the format of a mnemonic. +/// +/// It has two variants: +/// - `Encrypted`: This variant represents an encrypted mnemonic. It does not carry any associated data. +/// - `PlainText`: This variant represents a plaintext mnemonic. It carries the password to decrypt the mnemonic in string format. +#[derive(Debug, Deserialize)] +#[serde(tag = "format", content = "password", rename_all = "lowercase")] +pub enum MnemonicFormat { + Encrypted, + PlainText(String), +} + +/// `GetMnemonicRequest` is a struct representing a request to get a mnemonic. +/// +/// It contains a single field, `mnemonic_format`, which is an instance of the `MnemonicFormat` enum. +/// The `#[serde(flatten)]` attribute is used so that the fields of the `MnemonicFormat` enum are included +/// directly in the `GetMnemonicRequest` when it is deserialized, rather than nested under a +/// `mnemonic_format` field. +/// +/// # Examples +/// +/// For a `GetMnemonicRequest` where the `MnemonicFormat` is `Encrypted`, the JSON representation would be: +/// ```json +/// { +/// "format": "encrypted" +/// } +/// ``` +/// +/// For a `GetMnemonicRequest` where the `MnemonicFormat` is `PlainText` with a password of "password123", the JSON representation would be: +/// ```json +/// { +/// "format": "plaintext", +/// "password": "password123" +/// } +/// ``` +#[derive(Debug, Deserialize)] +pub struct GetMnemonicRequest { + #[serde(flatten)] + pub mnemonic_format: MnemonicFormat, +} + +/// `MnemonicForRpc` is an enum representing the format of a mnemonic for RPC communication. +/// +/// It has two variants: +/// - `Encrypted`: This variant represents an encrypted mnemonic. It carries the [`EncryptedMnemonicData`] struct. +/// - `PlainText`: This variant represents a plaintext mnemonic. It carries the mnemonic as a `String`. +#[derive(Serialize)] +#[serde(tag = "format", rename_all = "lowercase")] +pub enum MnemonicForRpc { + Encrypted { + encrypted_mnemonic_data: EncryptedMnemonicData, + }, + PlainText { + mnemonic: String, + }, +} + +impl From for MnemonicForRpc { + fn from(encrypted_mnemonic_data: EncryptedMnemonicData) -> Self { + MnemonicForRpc::Encrypted { + encrypted_mnemonic_data, + } + } +} + +impl From for MnemonicForRpc { + fn from(mnemonic: String) -> Self { MnemonicForRpc::PlainText { mnemonic } } +} + +/// [`GetMnemonicResponse`] is a struct representing the response to a get mnemonic request. +/// +/// It contains a single field, `mnemonic`, which is an instance of the [`MnemonicForRpc`] enum. +/// The `#[serde(flatten)]` attribute is used so that the fields of the [`MnemonicForRpc`] enum are included +/// directly in the [`GetMnemonicResponse`] when it is serialized, rather than nested under a +/// `mnemonic` field. +/// +/// # Examples +/// +/// For a [`GetMnemonicResponse`] where the [`MnemonicForRpc`] is `Encrypted` with some [`EncryptedMnemonicData`], the JSON representation would be: +/// ```json +/// { +/// "format": "encrypted", +/// "encrypted_mnemonic_data": { +/// // EncryptedMnemonicData fields go here +/// } +/// } +/// ``` +/// +/// For a `GetMnemonicResponse` where the `MnemonicForRpc` is `PlainText` with a mnemonic of "your_mnemonic_here", the JSON representation would be: +/// ```json +/// { +/// "format": "plaintext", +/// "mnemonic": "your_mnemonic_here" +/// } +/// ``` +#[derive(Serialize)] +pub struct GetMnemonicResponse { + #[serde(flatten)] + pub mnemonic: MnemonicForRpc, +} + +#[derive(Debug, Display, Serialize, SerializeErrorType)] +#[serde(tag = "error_type", content = "error_data")] +pub enum GetMnemonicError { + #[display(fmt = "Invalid request error: {}", _0)] + InvalidRequest(String), + #[display(fmt = "Wallets storage error: {}", _0)] + WalletsStorageError(String), + #[display(fmt = "Internal error: {}", _0)] + Internal(String), +} + +impl HttpStatusCode for GetMnemonicError { + fn status_code(&self) -> StatusCode { + match self { + GetMnemonicError::InvalidRequest(_) => StatusCode::BAD_REQUEST, + GetMnemonicError::WalletsStorageError(_) | GetMnemonicError::Internal(_) => { + StatusCode::INTERNAL_SERVER_ERROR + }, + } + } +} + +#[cfg(not(target_arch = "wasm32"))] +impl From for GetMnemonicError { + fn from(e: WalletsStorageError) -> Self { GetMnemonicError::WalletsStorageError(e.to_string()) } +} + +#[cfg(target_arch = "wasm32")] +impl From for GetMnemonicError { + fn from(e: WalletsDBError) -> Self { GetMnemonicError::WalletsStorageError(e.to_string()) } +} + +impl From for GetMnemonicError { + fn from(e: ReadPassphraseError) -> Self { GetMnemonicError::WalletsStorageError(e.to_string()) } +} + +/// Retrieves the wallet mnemonic in the requested format. +/// +/// # Arguments +/// +/// * `ctx` - The [`MmArc`] context containing the application state and configuration. +/// * `req` - The [`GetMnemonicRequest`] containing the requested mnemonic format. +/// +/// # Returns +/// +/// A `Result` type containing: +/// +/// * [`Ok`]([`GetMnemonicResponse`]) - The wallet mnemonic in the requested format. +/// * [`MmError`]<[`GetMnemonicError>`]> - Returns specific [`GetMnemonicError`] variants for different failure scenarios. +/// +/// # Errors +/// +/// This function will return an error in the following situations: +/// +/// * The wallet name is not found in the context. +/// * The wallet is initialized without a name. +/// * The wallet passphrase file is not found for `MnemonicFormat::Encrypted`. +/// * The wallet mnemonic file is not found for `MnemonicFormat::PlainText`. +/// +/// # Examples +/// +/// ```rust +/// let ctx = MmArc::new(MmCtx::default()); +/// let req = GetMnemonicRequest { +/// mnemonic_format: MnemonicFormat::Encrypted, +/// }; +/// let result = get_mnemonic_rpc(ctx, req).await; +/// match result { +/// Ok(response) => println!("Mnemonic: {:?}", response.mnemonic), +/// Err(e) => println!("Error: {:?}", e), +/// } +/// ``` +pub async fn get_mnemonic_rpc(ctx: MmArc, req: GetMnemonicRequest) -> MmResult { + match req.mnemonic_format { + MnemonicFormat::Encrypted => { + let encrypted_mnemonic = read_encrypted_passphrase(&ctx) + .await? + .ok_or_else(|| GetMnemonicError::InvalidRequest("Wallet passphrase file not found".to_string()))?; + Ok(GetMnemonicResponse { + mnemonic: encrypted_mnemonic.into(), + }) + }, + MnemonicFormat::PlainText(wallet_password) => { + let plaintext_mnemonic = read_and_decrypt_passphrase(&ctx, &wallet_password) + .await? + .ok_or_else(|| GetMnemonicError::InvalidRequest("Wallet mnemonic file not found".to_string()))?; + Ok(GetMnemonicResponse { + mnemonic: plaintext_mnemonic.into(), + }) + }, + } +} diff --git a/mm2src/mm2_main/src/lp_wallet/mnemonics_storage.rs b/mm2src/mm2_main/src/lp_wallet/mnemonics_storage.rs index 676c1ed9a3..164bc84dd3 100644 --- a/mm2src/mm2_main/src/lp_wallet/mnemonics_storage.rs +++ b/mm2src/mm2_main/src/lp_wallet/mnemonics_storage.rs @@ -13,6 +13,8 @@ pub enum WalletsStorageError { FsWriteError(String), #[display(fmt = "Error reading from file: {}", _0)] FsReadError(String), + #[display(fmt = "Internal error: {}", _0)] + Internal(String), } /// Saves the passphrase to a file associated with the given wallet name. @@ -57,11 +59,15 @@ pub(super) async fn save_encrypted_passphrase( /// # Errors /// Returns an `io::Error` if the file cannot be read or the data cannot be deserialized into /// `EncryptedPassphraseData`. -pub(super) async fn read_encrypted_passphrase( - ctx: &MmArc, - wallet_name: &str, -) -> WalletsStorageResult> { - let wallet_path = ctx.wallet_file_path(wallet_name); +pub(super) async fn read_encrypted_passphrase(ctx: &MmArc) -> WalletsStorageResult> { + let wallet_name = ctx + .wallet_name + .ok_or(WalletsStorageError::Internal( + "`wallet_name` not initialized yet!".to_string(), + ))? + .clone() + .ok_or_else(|| WalletsStorageError::Internal("`wallet_name` cannot be None!".to_string()))?; + let wallet_path = ctx.wallet_file_path(&wallet_name); mm2_io::fs::read_json(&wallet_path).await.mm_err(|e| { WalletsStorageError::FsReadError(format!( "Error reading passphrase from file {}: {}", diff --git a/mm2src/mm2_main/src/lp_wallet/mnemonics_wasm_db.rs b/mm2src/mm2_main/src/lp_wallet/mnemonics_wasm_db.rs index 060d9b17a6..2f0fa5ae69 100644 --- a/mm2src/mm2_main/src/lp_wallet/mnemonics_wasm_db.rs +++ b/mm2src/mm2_main/src/lp_wallet/mnemonics_wasm_db.rs @@ -121,10 +121,7 @@ pub(super) async fn save_encrypted_passphrase( Ok(()) } -pub(super) async fn read_encrypted_passphrase( - ctx: &MmArc, - wallet_name: &str, -) -> WalletsDBResult> { +pub(super) async fn read_encrypted_passphrase(ctx: &MmArc) -> WalletsDBResult> { let wallets_ctx = WalletsContext::from_ctx(ctx).map_to_mm(WalletsDBError::Internal)?; let db = wallets_ctx .wallets_db() @@ -140,6 +137,13 @@ pub(super) async fn read_encrypted_passphrase( .await .mm_err(|e| WalletsDBError::Internal(e.to_string()))?; + let wallet_name = ctx + .wallet_name + .ok_or(WalletsDBError::Internal( + "`wallet_name` not initialized yet!".to_string(), + ))? + .clone() + .ok_or_else(|| WalletsDBError::Internal("`wallet_name` can't be None!".to_string()))?; match table.get_item_by_unique_index("wallet_name", wallet_name).await { Ok(Some((_item_id, wallet_table_item))) => serde_json::from_str(&wallet_table_item.encrypted_mnemonic) .map_to_mm(|e| WalletsDBError::DeserializationError { diff --git a/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs b/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs index 5645b9a68f..9df217d1a6 100644 --- a/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs +++ b/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs @@ -4,6 +4,7 @@ use crate::mm2::lp_native_dex::init_hw::{cancel_init_trezor, init_trezor, init_t use crate::mm2::lp_native_dex::init_metamask::{cancel_connect_metamask, connect_metamask, connect_metamask_status}; use crate::mm2::lp_ordermatch::{best_orders_rpc_v2, orderbook_rpc_v2, start_simple_market_maker_bot, stop_simple_market_maker_bot}; +use crate::mm2::lp_wallet::get_mnemonic_rpc; use crate::mm2::rpc::rate_limiter::{process_rate_limit, RateLimitContext}; use crate::{mm2::lp_stats::{add_node_to_version_stat, remove_node_from_version_stat, start_version_stat_collection, stop_version_stat_collection, update_version_stat_collection}, @@ -167,6 +168,7 @@ async fn dispatcher_v2(request: MmRpcRequest, ctx: MmArc) -> DispatcherResult handle_mmrpc(ctx, request, get_current_mtp_rpc).await, "get_enabled_coins" => handle_mmrpc(ctx, request, get_enabled_coins).await, "get_locked_amount" => handle_mmrpc(ctx, request, get_locked_amount_rpc).await, + "get_mnemonic" => handle_mmrpc(ctx, request, get_mnemonic_rpc).await, "get_my_address" => handle_mmrpc(ctx, request, get_my_address).await, "get_new_address" => handle_mmrpc(ctx, request, get_new_address).await, "get_nft_list" => handle_mmrpc(ctx, request, get_nft_list).await, From 6d5e271f51c957cc6540f08e069ae75cbeec31c5 Mon Sep 17 00:00:00 2001 From: shamardy Date: Fri, 9 Feb 2024 21:22:51 +0200 Subject: [PATCH 14/20] add SLIP-0021 keys derivation --- mm2src/crypto/src/key_derivation.rs | 152 ++++++++++++++++++++++++++++ mm2src/crypto/src/lib.rs | 1 + mm2src/crypto/src/mnemonic.rs | 60 ++--------- 3 files changed, 164 insertions(+), 49 deletions(-) create mode 100644 mm2src/crypto/src/key_derivation.rs diff --git a/mm2src/crypto/src/key_derivation.rs b/mm2src/crypto/src/key_derivation.rs new file mode 100644 index 0000000000..8535dcd600 --- /dev/null +++ b/mm2src/crypto/src/key_derivation.rs @@ -0,0 +1,152 @@ +use argon2::password_hash::SaltString; +use argon2::{Argon2, PasswordHasher}; +use common::drop_mutability; +use derive_more::Display; +use hmac::{Hmac, Mac}; +use mm2_err_handle::mm_error::MmResult; +use mm2_err_handle::prelude::*; +use sha2::Sha512; +use std::convert::TryInto; + +type HmacSha512 = Hmac; + +#[derive(Debug, Display, PartialEq)] +pub enum KeyDerivationError { + #[display(fmt = "Error hashing password: {}", _0)] + PasswordHashingFailed(String), + #[display(fmt = "Error initializing HMAC")] + HmacInitialization, + #[display(fmt = "Invalid key length")] + InvalidKeyLength, +} + +impl From for KeyDerivationError { + fn from(e: argon2::password_hash::Error) -> Self { KeyDerivationError::PasswordHashingFailed(e.to_string()) } +} + +/// Derives AES and HMAC keys from a given password and salts for mnemonic encryption/decryption. +/// +/// # Arguments +/// * `password` - The password used for key derivation. +/// * `salt_aes` - The salt used for AES key derivation. +/// * `salt_hmac` - The salt used for HMAC key derivation. +/// +/// # Returns +/// A tuple containing the AES key and HMAC key as byte arrays, or a `MnemonicError` in case of failure. +pub fn derive_keys_for_mnemonic( + password: &str, + salt_aes: &SaltString, + salt_hmac: &SaltString, +) -> MmResult<([u8; 32], [u8; 32]), KeyDerivationError> { + let argon2 = Argon2::default(); + + // Derive AES Key + let aes_password_hash = argon2.hash_password(password.as_bytes(), salt_aes)?; + let key_aes_output = aes_password_hash + .serialize() + .hash() + .ok_or_else(|| KeyDerivationError::PasswordHashingFailed("Error finding AES key hashing output".to_string()))?; + let key_aes = key_aes_output + .as_bytes() + .try_into() + .map_err(|_| KeyDerivationError::PasswordHashingFailed("Invalid AES key length".to_string()))?; + + // Derive HMAC Key + let hmac_password_hash = argon2.hash_password(password.as_bytes(), salt_hmac)?; + let key_hmac_output = hmac_password_hash.serialize().hash().ok_or_else(|| { + KeyDerivationError::PasswordHashingFailed("Error finding HMAC key hashing output".to_string()) + })?; + let key_hmac = key_hmac_output + .as_bytes() + .try_into() + .map_err(|_| KeyDerivationError::PasswordHashingFailed("Invalid HMAC key length".to_string()))?; + + Ok((key_aes, key_hmac)) +} + +/// Splits a path into its components and derives a key for each component. +fn derive_key_from_path(master_node: &[u8], path: &str) -> MmResult<[u8; 32], KeyDerivationError> { + let mut current_key_material = master_node.to_vec(); + for segment in path.split('/').filter(|s| !s.is_empty()) { + let mut mac = HmacSha512::new_from_slice(¤t_key_material[..32]) + .map_err(|_| KeyDerivationError::HmacInitialization)?; + mac.update(b"\x00"); + mac.update(segment.as_bytes()); + drop_mutability!(mac); + + let hmac_result = mac.finalize().into_bytes(); + current_key_material = hmac_result.to_vec(); + } + drop_mutability!(current_key_material); + + current_key_material[32..64] + .try_into() + .map_to_mm(|_| KeyDerivationError::InvalidKeyLength) +} + +/// Derives encryption and authentication keys from the master private key using [SLIP-0021](https://github.com/satoshilabs/slips/blob/master/slip-0021.md). +/// +/// # Arguments +/// * `master_secret` - The master private key used for key derivation. Can be a BIP-39 seed if this is used to derive keys for wallet data/files encryption. +/// * `derivation_path` - The additional derivation path used for encryption and authentication key derivation. +/// +/// # Returns +/// A tuple containing the encryption and authentication keys as byte arrays, or an error in case of failure. +fn derive_encryption_authentication_keys( + master_secret: &[u8; 64], + derivation_path: &str, +) -> MmResult<([u8; 32], [u8; 32]), KeyDerivationError> { + const SYMMETRIC_KEY_SEED: &[u8] = b"Symmetric key seed"; + const ENCRYPTION_PATH: &str = "SLIP-0021/Master encryption key/"; + const AUTHENTICATION_PATH: &str = "SLIP-0021/Authentication key/"; + + // Generate the master node `m` according to SLIP-0021. + let mut mac = + HmacSha512::new_from_slice(SYMMETRIC_KEY_SEED).map_to_mm(|_| KeyDerivationError::HmacInitialization)?; + mac.update(master_secret); + drop_mutability!(mac); + let master_key_material = mac.finalize().into_bytes(); + + // Derive encryption key + let encryption_path = ENCRYPTION_PATH.to_string() + derivation_path; + let encryption_key = derive_key_from_path(&master_key_material, &encryption_path)?; + + // Derive authentication key + let authentication_path = AUTHENTICATION_PATH.to_string() + derivation_path; + let authentication_key = derive_key_from_path(&master_key_material, &authentication_path)?; + + Ok((encryption_key, authentication_key)) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + // https://github.com/satoshilabs/slips/blob/master/slip-0021.md#example + fn test_slip_0021_key_derivation() { + let master_secret = hex::decode("c76c4ac4f4e4a00d6b274d5c39c700bb4a7ddc04fbc6f78e85ca75007b5b495f74a9043eeb77bdd53aa6fc3a0e31462270316fa04b8c19114c8798706cd02ac8").unwrap(); + + let expected_encryption_key = + hex::decode("ea163130e35bbafdf5ddee97a17b39cef2be4b4f390180d65b54cf05c6a82fde").unwrap(); + let expected_authentication_key = + hex::decode("47194e938ab24cc82bfa25f6486ed54bebe79c40ae2a5a32ea6db294d81861a6").unwrap(); + + // Directly derive the encryption and authentication keys from the master secret + let (derived_encryption_key, derived_authentication_key) = + derive_encryption_authentication_keys(&master_secret.try_into().expect("Invalid master secret"), "") + .expect("Key derivation failed"); + + // Verify the derived keys against the expected values + assert_eq!( + derived_encryption_key, + expected_encryption_key.as_slice(), + "Derived encryption key does not match expected value" + ); + assert_eq!( + derived_authentication_key, + expected_authentication_key.as_slice(), + "Derived authentication key does not match expected value" + ); + } +} diff --git a/mm2src/crypto/src/lib.rs b/mm2src/crypto/src/lib.rs index a3815cf418..2a8db1a8e7 100644 --- a/mm2src/crypto/src/lib.rs +++ b/mm2src/crypto/src/lib.rs @@ -7,6 +7,7 @@ mod hw_client; mod hw_ctx; mod hw_error; pub mod hw_rpc_task; +mod key_derivation; pub mod mnemonic; pub mod privkey; mod shared_db_id; diff --git a/mm2src/crypto/src/mnemonic.rs b/mm2src/crypto/src/mnemonic.rs index 4ebc9f5292..c87d1bfe0f 100644 --- a/mm2src/crypto/src/mnemonic.rs +++ b/mm2src/crypto/src/mnemonic.rs @@ -1,7 +1,7 @@ +use crate::key_derivation::{derive_keys_for_mnemonic, KeyDerivationError}; use aes::cipher::{block_padding::Pkcs7, BlockDecryptMut, BlockEncryptMut, KeyIvInit}; use aes::Aes256; -use argon2::password_hash::{PasswordHasher, SaltString}; -use argon2::Argon2; +use argon2::password_hash::SaltString; use bip39::{Language, Mnemonic}; use common::drop_mutability; use derive_more::Display; @@ -9,7 +9,6 @@ use hmac::{Hmac, Mac}; use mm2_core::mm_ctx::MmArc; use mm2_err_handle::prelude::*; use sha2::Sha256; -use std::convert::TryInto; const ARGON2_ALGORITHM: &str = "Argon2id"; const ARGON2ID_VERSION: &str = "0x13"; @@ -27,8 +26,8 @@ pub enum MnemonicError { BIP39Error(String), #[display(fmt = "Error generating random bytes: {}", _0)] UnableToGenerateRandomBytes(String), - #[display(fmt = "Error hashing password: {}", _0)] - PasswordHashingFailed(String), + #[display(fmt = "Error deriving key: {}", _0)] + KeyDerivationError(String), #[display(fmt = "AES cipher error: {}", _0)] AESCipherError(String), #[display(fmt = "Error decoding string: {}", _0)] @@ -43,13 +42,17 @@ impl From for MnemonicError { } impl From for MnemonicError { - fn from(e: argon2::password_hash::Error) -> Self { MnemonicError::PasswordHashingFailed(e.to_string()) } + fn from(e: argon2::password_hash::Error) -> Self { MnemonicError::KeyDerivationError(e.to_string()) } } impl From for MnemonicError { fn from(e: base64::DecodeError) -> Self { MnemonicError::DecodeError(e.to_string()) } } +impl From for MnemonicError { + fn from(e: KeyDerivationError) -> Self { MnemonicError::KeyDerivationError(e.to_string()) } +} + /// Enum representing different encryption algorithms. #[derive(Serialize, Deserialize, Debug)] enum EncryptionAlgorithm { @@ -189,47 +192,6 @@ pub fn generate_mnemonic(ctx: &MmArc) -> MmResult { Ok(mnemonic) } -/// Derives AES and HMAC keys from a given password and salts. -/// -/// # Arguments -/// * `password` - The password used for key derivation. -/// * `salt_aes` - The salt used for AES key derivation. -/// * `salt_hmac` - The salt used for HMAC key derivation. -/// -/// # Returns -/// A tuple containing the AES key and HMAC key as byte arrays, or a `MnemonicError` in case of failure. -fn derive_aes_hmac_keys( - password: &str, - salt_aes: &SaltString, - salt_hmac: &SaltString, -) -> MmResult<([u8; 32], [u8; 32]), MnemonicError> { - let argon2 = Argon2::default(); - - // Derive AES Key - let aes_password_hash = argon2.hash_password(password.as_bytes(), salt_aes)?; - let key_aes_output = aes_password_hash - .serialize() - .hash() - .ok_or_else(|| MnemonicError::PasswordHashingFailed("Error finding AES key hashing output".to_string()))?; - let key_aes = key_aes_output - .as_bytes() - .try_into() - .map_err(|_| MnemonicError::PasswordHashingFailed("Invalid AES key length".to_string()))?; - - // Derive HMAC Key - let hmac_password_hash = argon2.hash_password(password.as_bytes(), salt_hmac)?; - let key_hmac_output = hmac_password_hash - .serialize() - .hash() - .ok_or_else(|| MnemonicError::PasswordHashingFailed("Error finding HMAC key hashing output".to_string()))?; - let key_hmac = key_hmac_output - .as_bytes() - .try_into() - .map_err(|_| MnemonicError::PasswordHashingFailed("Invalid HMAC key length".to_string()))?; - - Ok((key_aes, key_hmac)) -} - /// Encrypts a mnemonic phrase using a specified password. /// /// This function performs several operations: @@ -263,7 +225,7 @@ pub fn encrypt_mnemonic(mnemonic: &str, password: &str) -> MmResult::new_from_slice(&key_hmac).map_to_mm(|e| MnemonicError::Internal(e.to_string()))?; From c37316e8f837114a88454d41b8fdcb08b575038a Mon Sep 17 00:00:00 2001 From: shamardy Date: Fri, 9 Feb 2024 22:26:32 +0200 Subject: [PATCH 15/20] add encrypt.rs for encryption of general data regardless of key derivation method --- mm2src/crypto/src/encrypt.rs | 103 +++++++++ mm2src/crypto/src/key_derivation.rs | 73 ++++++- mm2src/crypto/src/lib.rs | 4 +- mm2src/crypto/src/mnemonic.rs | 195 +++--------------- mm2src/mm2_main/src/lp_wallet.rs | 24 +-- .../src/lp_wallet/mnemonics_storage.rs | 10 +- .../src/lp_wallet/mnemonics_wasm_db.rs | 6 +- 7 files changed, 224 insertions(+), 191 deletions(-) create mode 100644 mm2src/crypto/src/encrypt.rs diff --git a/mm2src/crypto/src/encrypt.rs b/mm2src/crypto/src/encrypt.rs new file mode 100644 index 0000000000..2c41836a5e --- /dev/null +++ b/mm2src/crypto/src/encrypt.rs @@ -0,0 +1,103 @@ +use crate::key_derivation::KeyDerivationDetails; +use aes::cipher::{block_padding::Pkcs7, BlockEncryptMut, KeyIvInit}; +use aes::Aes256; +use common::drop_mutability; +use derive_more::Display; +use hmac::{Hmac, Mac}; +use mm2_err_handle::prelude::*; +use sha2::Sha256; + +type Aes256CbcEnc = cbc::Encryptor; + +#[derive(Debug, Display, PartialEq)] +pub enum EncryptionError { + #[display(fmt = "Error generating random bytes: {}", _0)] + UnableToGenerateRandomBytes(String), + #[display(fmt = "AES cipher error: {}", _0)] + AESCipherError(String), + Internal(String), +} + +/// Enum representing different encryption algorithms. +#[derive(Serialize, Deserialize, Debug)] +pub enum EncryptionAlgorithm { + /// AES-256-CBC algorithm. + AES256CBC, + // Placeholder for future algorithms. + // Future algorithms can be added here. +} + +/// `EncryptedData` represents encrypted data for a wallet. +/// +/// This struct encapsulates all essential components required to securely encrypt +/// and subsequently decrypt a wallet mnemonic and data. It is designed to be self-contained, +/// meaning it includes not only the encrypted data but also all the necessary metadata +/// and parameters for decryption. This makes the struct portable and convenient for +/// use in various scenarios, allowing decryption of the mnemonic in different +/// environments or applications, provided the correct password or seed is supplied. +/// +/// The `EncryptedData` struct is typically used for wallet encryption in blockchain-based applications, +/// providing a robust and comprehensive approach to securing sensitive mnemonic data. +#[derive(Serialize, Deserialize, Debug)] +pub struct EncryptedData { + /// The encryption algorithm used to encrypt the mnemonic. + /// Example: "AES-256-CBC". + pub encryption_algorithm: EncryptionAlgorithm, + + /// Detailed information about the key derivation process. This includes + /// the specific algorithm used (e.g., Argon2) and its parameters. + pub key_derivation_details: KeyDerivationDetails, + + /// The initialization vector (IV) used in the AES encryption process. + /// The IV ensures that the encryption process produces unique ciphertext + /// for the same plaintext and key when encrypted multiple times. + /// Stored as a Base64-encoded string. + pub iv: String, + + /// The encrypted mnemonic data. This is the ciphertext generated + /// using the specified encryption algorithm, key, and IV. + /// Stored as a Base64-encoded string. + pub ciphertext: String, + + /// The HMAC tag used for verifying the integrity and authenticity of the encrypted data. + /// This tag is crucial for validating that the data has not been tampered with. + /// Stored as a Base64-encoded string. + pub tag: String, +} + +pub fn encrypt_data( + data: &[u8], + key_derivation_details: KeyDerivationDetails, + key_aes: &[u8; 32], + key_hmac: &[u8; 32], +) -> MmResult { + // Generate IV + let mut iv = [0u8; 16]; + common::os_rng(&mut iv).map_to_mm(|e| EncryptionError::UnableToGenerateRandomBytes(e.to_string()))?; + drop_mutability!(iv); + + // Create an AES-256-CBC cipher instance, encrypt the data with the key and the IV and get the ciphertext + let msg_len = data.len(); + let buffer_len = msg_len + 16 - (msg_len % 16); + let mut buffer = vec![0u8; buffer_len]; + buffer[..msg_len].copy_from_slice(data); + let ciphertext = Aes256CbcEnc::new(key_aes.into(), &iv.into()) + .encrypt_padded_mut::(&mut buffer, msg_len) + .map_to_mm(|e| EncryptionError::AESCipherError(e.to_string()))?; + + // Create HMAC tag + let mut mac = Hmac::::new_from_slice(key_hmac).map_to_mm(|e| EncryptionError::Internal(e.to_string()))?; + mac.update(ciphertext); + mac.update(&iv); + let tag = mac.finalize().into_bytes(); + + let encrypted_data = EncryptedData { + encryption_algorithm: EncryptionAlgorithm::AES256CBC, + key_derivation_details, + iv: base64::encode(&iv), + ciphertext: base64::encode(&ciphertext), + tag: base64::encode(&tag), + }; + + Ok(encrypted_data) +} diff --git a/mm2src/crypto/src/key_derivation.rs b/mm2src/crypto/src/key_derivation.rs index 8535dcd600..1fc93a0809 100644 --- a/mm2src/crypto/src/key_derivation.rs +++ b/mm2src/crypto/src/key_derivation.rs @@ -8,6 +8,12 @@ use mm2_err_handle::prelude::*; use sha2::Sha512; use std::convert::TryInto; +const ARGON2_ALGORITHM: &str = "Argon2id"; +const ARGON2ID_VERSION: &str = "0x13"; +const ARGON2ID_M_COST: u32 = 65536; +const ARGON2ID_T_COST: u32 = 2; +const ARGON2ID_P_COST: u32 = 1; + type HmacSha512 = Hmac; #[derive(Debug, Display, PartialEq)] @@ -24,6 +30,71 @@ impl From for KeyDerivationError { fn from(e: argon2::password_hash::Error) -> Self { KeyDerivationError::PasswordHashingFailed(e.to_string()) } } +/// Parameters for the Argon2 key derivation function. +/// +/// This struct defines the configuration parameters used by Argon2, one of the +/// most secure and widely used key derivation functions, especially for +/// password hashing. +#[derive(Serialize, Deserialize, Debug)] +pub struct Argon2Params { + /// The specific variant of the Argon2 algorithm used (e.g., Argon2id). + algorithm: String, + + /// The version of the Argon2 algorithm (e.g., 0x13 for the latest version). + version: String, + + /// The memory cost parameter defining the memory usage of the algorithm. + /// Expressed in kibibytes (KiB). + m_cost: u32, + + /// The time cost parameter defining the execution time and number of + /// iterations of the algorithm. + t_cost: u32, + + /// The parallelism cost parameter defining the number of parallel threads. + p_cost: u32, +} + +impl Default for Argon2Params { + fn default() -> Self { + Argon2Params { + algorithm: ARGON2_ALGORITHM.to_string(), + version: ARGON2ID_VERSION.to_string(), + m_cost: ARGON2ID_M_COST, + t_cost: ARGON2ID_T_COST, + p_cost: ARGON2ID_P_COST, + } + } +} + +/// Enum representing different key derivation details. +/// +/// This enum allows for flexible specification of various key derivation +/// algorithms and their parameters, making it easier to extend and support +/// multiple algorithms in the future. +#[derive(Serialize, Deserialize, Debug)] +pub enum KeyDerivationDetails { + /// Argon2 algorithm for key derivation. + Argon2 { + /// The parameters for the Argon2 key derivation function. + params: Argon2Params, + /// The salt used in the key derivation process for the AES key. + /// Stored as a Base64-encoded string. + salt_aes: String, + /// The salt used in the key derivation process for the HMAC key. + /// This is applicable if HMAC is used for ensuring data integrity and authenticity. + /// Stored as a Base64-encoded string. + salt_hmac: String, + }, + /// Algorithm for deriving a hierarchy of symmetric keys from a master secret according to [SLIP-0021](https://github.com/satoshilabs/slips/blob/master/slip-0021.md). + SLIP0021 { + encryption_path: String, + authentication_path: String, + }, + // Placeholder for future algorithms. + // Future algorithms can be added here. +} + /// Derives AES and HMAC keys from a given password and salts for mnemonic encryption/decryption. /// /// # Arguments @@ -33,7 +104,7 @@ impl From for KeyDerivationError { /// /// # Returns /// A tuple containing the AES key and HMAC key as byte arrays, or a `MnemonicError` in case of failure. -pub fn derive_keys_for_mnemonic( +pub(crate) fn derive_keys_for_mnemonic( password: &str, salt_aes: &SaltString, salt_hmac: &SaltString, diff --git a/mm2src/crypto/src/lib.rs b/mm2src/crypto/src/lib.rs index 2a8db1a8e7..2df500d9f4 100644 --- a/mm2src/crypto/src/lib.rs +++ b/mm2src/crypto/src/lib.rs @@ -2,6 +2,7 @@ mod bip32_child; mod crypto_ctx; +mod encrypt; mod global_hd_ctx; mod hw_client; mod hw_ctx; @@ -20,6 +21,7 @@ mod xpub; pub use bip32_child::{Bip32Child, Bip32DerPathError, Bip32DerPathOps, Bip44Tail}; pub use crypto_ctx::{CryptoCtx, CryptoCtxError, CryptoInitError, CryptoInitResult, HwCtxInitError, KeyPairPolicy}; +pub use encrypt::EncryptedData; pub use global_hd_ctx::{derive_secp256k1_secret, GlobalHDAccountArc}; pub use hw_client::{HwClient, HwConnectionStatus, HwDeviceInfo, HwProcessingError, HwPubkey, HwWalletType, TrezorConnectProcessor}; @@ -28,7 +30,7 @@ pub use hw_common::primitives::{Bip32Error, ChildNumber, DerivationPath, EcdsaCu pub use hw_ctx::{HardwareWalletArc, HardwareWalletCtx}; pub use hw_error::{from_hw_error, HwError, HwResult, HwRpcError, WithHwRpcError}; pub use keys::Secret as Secp256k1Secret; -pub use mnemonic::{decrypt_mnemonic, encrypt_mnemonic, generate_mnemonic, EncryptedMnemonicData, MnemonicError}; +pub use mnemonic::{decrypt_mnemonic, encrypt_mnemonic, generate_mnemonic, MnemonicError}; pub use standard_hd_path::{Bip44Chain, StandardHDCoinAddress, StandardHDPath, StandardHDPathError, StandardHDPathToAccount, StandardHDPathToCoin, UnknownChainError}; pub use trezor; diff --git a/mm2src/crypto/src/mnemonic.rs b/mm2src/crypto/src/mnemonic.rs index c87d1bfe0f..8ee71cc65a 100644 --- a/mm2src/crypto/src/mnemonic.rs +++ b/mm2src/crypto/src/mnemonic.rs @@ -1,31 +1,24 @@ -use crate::key_derivation::{derive_keys_for_mnemonic, KeyDerivationError}; -use aes::cipher::{block_padding::Pkcs7, BlockDecryptMut, BlockEncryptMut, KeyIvInit}; +use crate::encrypt::encrypt_data; +use crate::key_derivation::{derive_keys_for_mnemonic, Argon2Params, KeyDerivationDetails, KeyDerivationError}; +use crate::EncryptedData; +use aes::cipher::{block_padding::Pkcs7, BlockDecryptMut, KeyIvInit}; use aes::Aes256; use argon2::password_hash::SaltString; use bip39::{Language, Mnemonic}; -use common::drop_mutability; use derive_more::Display; use hmac::{Hmac, Mac}; use mm2_core::mm_ctx::MmArc; use mm2_err_handle::prelude::*; use sha2::Sha256; -const ARGON2_ALGORITHM: &str = "Argon2id"; -const ARGON2ID_VERSION: &str = "0x13"; -const ARGON2ID_M_COST: u32 = 65536; -const ARGON2ID_T_COST: u32 = 2; -const ARGON2ID_P_COST: u32 = 1; const DEFAULT_WORD_COUNT: u64 = 12; -type Aes256CbcEnc = cbc::Encryptor; type Aes256CbcDec = cbc::Decryptor; #[derive(Debug, Display, PartialEq)] pub enum MnemonicError { #[display(fmt = "BIP39 mnemonic error: {}", _0)] BIP39Error(String), - #[display(fmt = "Error generating random bytes: {}", _0)] - UnableToGenerateRandomBytes(String), #[display(fmt = "Error deriving key: {}", _0)] KeyDerivationError(String), #[display(fmt = "AES cipher error: {}", _0)] @@ -34,6 +27,8 @@ pub enum MnemonicError { DecodeError(String), #[display(fmt = "Error verifying HMAC tag: {}", _0)] HMACError(String), + #[display(fmt = "Error encrypting mnemonic: {}", _0)] + EncryptionError(String), Internal(String), } @@ -53,125 +48,6 @@ impl From for MnemonicError { fn from(e: KeyDerivationError) -> Self { MnemonicError::KeyDerivationError(e.to_string()) } } -/// Enum representing different encryption algorithms. -#[derive(Serialize, Deserialize, Debug)] -enum EncryptionAlgorithm { - /// AES-256-CBC algorithm. - AES256CBC, - // Placeholder for future algorithms. - // Future algorithms can be added here. -} - -/// Parameters for the Argon2 key derivation function. -/// -/// This struct defines the configuration parameters used by Argon2, one of the -/// most secure and widely used key derivation functions, especially for -/// password hashing. -#[derive(Serialize, Deserialize, Debug)] -pub struct Argon2Params { - /// The specific variant of the Argon2 algorithm used (e.g., Argon2id). - algorithm: String, - - /// The version of the Argon2 algorithm (e.g., 0x13 for the latest version). - version: String, - - /// The memory cost parameter defining the memory usage of the algorithm. - /// Expressed in kibibytes (KiB). - m_cost: u32, - - /// The time cost parameter defining the execution time and number of - /// iterations of the algorithm. - t_cost: u32, - - /// The parallelism cost parameter defining the number of parallel threads. - p_cost: u32, -} - -impl Default for Argon2Params { - fn default() -> Self { - Argon2Params { - algorithm: ARGON2_ALGORITHM.to_string(), - version: ARGON2ID_VERSION.to_string(), - m_cost: ARGON2ID_M_COST, - t_cost: ARGON2ID_T_COST, - p_cost: ARGON2ID_P_COST, - } - } -} - -/// Enum representing different key derivation details. -/// -/// This enum allows for flexible specification of various key derivation -/// algorithms and their parameters, making it easier to extend and support -/// multiple algorithms in the future. -#[derive(Serialize, Deserialize, Debug)] -pub enum KeyDerivationDetails { - /// Argon2 algorithm with its specific parameters. - Argon2(Argon2Params), - // Placeholder for future algorithms. - // Future algorithms can be added here. -} - -impl Default for KeyDerivationDetails { - fn default() -> Self { KeyDerivationDetails::Argon2(Argon2Params::default()) } -} - -/// Represents encrypted mnemonic data for a wallet. -/// -/// This struct encapsulates all essential components required to securely encrypt -/// and subsequently decrypt a wallet mnemonic. It is designed to be self-contained, -/// meaning it includes not only the encrypted data but also all the necessary metadata -/// and parameters for decryption. This makes the struct portable and convenient for -/// use in various scenarios, allowing decryption of the mnemonic in different -/// environments or applications, provided the correct password is supplied. -/// -/// It includes the following: -/// - The encryption algorithm used, ensuring compatibility during decryption. -/// - Detailed key derivation details, including the algorithm and its parameters, -/// essential for recreating the encryption key from the user's password. -/// - The Base64-encoded salt for AES key derivation, IV (Initialization Vector), -/// and the ciphertext itself. -/// - If HMAC is used, it also includes the salt for HMAC key derivation and the HMAC tag, -/// which are crucial for ensuring the integrity and authenticity of the encrypted data. -/// -/// The structure is typically used for wallet encryption in blockchain-based applications, -/// providing a robust and comprehensive approach to securing sensitive mnemonic data.. -#[derive(Serialize, Deserialize, Debug)] -pub struct EncryptedMnemonicData { - /// The encryption algorithm used to encrypt the mnemonic. - /// Example: "AES-256-CBC". - encryption_algorithm: EncryptionAlgorithm, - - /// Detailed information about the key derivation process. This includes - /// the specific algorithm used (e.g., Argon2) and its parameters. - key_derivation_details: KeyDerivationDetails, - - /// The salt used in the key derivation process for the AES key. - /// Stored as a Base64-encoded string. - salt_aes: String, - - /// The initialization vector (IV) used in the AES encryption process. - /// The IV ensures that the encryption process produces unique ciphertext - /// for the same plaintext and key when encrypted multiple times. - /// Stored as a Base64-encoded string. - iv: String, - - /// The encrypted mnemonic data. This is the ciphertext generated - /// using the specified encryption algorithm, key, and IV. - /// Stored as a Base64-encoded string. - ciphertext: String, - - /// The salt used in the key derivation process for the HMAC key. - /// This is applicable if HMAC is used for ensuring data integrity and authenticity. - /// Stored as a Base64-encoded string. - salt_hmac: String, - - /// The HMAC tag used for verifying the integrity and authenticity of the encrypted data. - /// This tag is crucial for validating that the data has not been tampered with. - /// Stored as a Base64-encoded string. - tag: String, -} - /// Generates a new mnemonic passphrase. /// /// This function creates a new mnemonic passphrase using a specified word count and randomness source. @@ -205,12 +81,12 @@ pub fn generate_mnemonic(ctx: &MmArc) -> MmResult { /// * `password` - A `&str` reference to the password used for key derivation. /// /// # Returns -/// `MmResult` - The result is either an `EncryptedMnemonicData` +/// `MmResult` - The result is either an `EncryptedData` /// struct containing all the necessary components for decryption, or a `MnemonicError` in case of failure. /// /// # Errors /// This function can return various errors related to key derivation, encryption, and data encoding. -pub fn encrypt_mnemonic(mnemonic: &str, password: &str) -> MmResult { +pub fn encrypt_mnemonic(mnemonic: &str, password: &str) -> MmResult { use argon2::password_hash::rand_core::OsRng; // Generate salt for AES key @@ -219,52 +95,29 @@ pub fn encrypt_mnemonic(mnemonic: &str, password: &str) -> MmResult(&mut buffer, msg_len) - .map_to_mm(|e| MnemonicError::AESCipherError(e.to_string()))?; - - // Create HMAC tag - let mut mac = Hmac::::new_from_slice(&key_hmac).map_to_mm(|e| MnemonicError::Internal(e.to_string()))?; - mac.update(ciphertext); - mac.update(&iv); - let tag = mac.finalize().into_bytes(); - - let encrypted_mnemonic_data = EncryptedMnemonicData { - encryption_algorithm: EncryptionAlgorithm::AES256CBC, - key_derivation_details: KeyDerivationDetails::default(), + let key_derivation_details = KeyDerivationDetails::Argon2 { + params: Argon2Params::default(), salt_aes: salt_aes.as_str().to_string(), - iv: base64::encode(&iv), - ciphertext: base64::encode(&ciphertext), salt_hmac: salt_hmac.as_str().to_string(), - tag: base64::encode(&tag), }; - Ok(encrypted_mnemonic_data) + // Derive AES and HMAC keys + let (key_aes, key_hmac) = derive_keys_for_mnemonic(password, &salt_aes, &salt_hmac)?; + + encrypt_data(mnemonic.as_bytes(), key_derivation_details, &key_aes, &key_hmac) + .mm_err(|e| MnemonicError::EncryptionError(e.to_string())) } /// Decrypts an encrypted mnemonic phrase using a specified password. /// /// This function performs the reverse operations of `encrypt_mnemonic`. It: -/// - Decodes and re-creates the necessary salts, IV, and ciphertext from the `EncryptedMnemonicData`. +/// - Decodes and re-creates the necessary salts, IV, and ciphertext from the `EncryptedData`. /// - Derives the AES and HMAC keys using the Argon2 algorithm. /// - Verifies the integrity and authenticity of the data using the HMAC tag. /// - Decrypts the mnemonic using AES-256-CBC. /// /// # Arguments -/// * `encrypted_data` - A reference to the `EncryptedMnemonicData` containing the encrypted mnemonic and related metadata. +/// * `encrypted_data` - A reference to the `EncryptedData` containing the encrypted mnemonic and related metadata. /// * `password` - A `&str` reference to the password used for key derivation. /// /// # Returns @@ -273,15 +126,23 @@ pub fn encrypt_mnemonic(mnemonic: &str, password: &str) -> MmResult MmResult { +pub fn decrypt_mnemonic(encrypted_data: &EncryptedData, password: &str) -> MmResult { // Decode the Base64-encoded values let iv = base64::decode(&encrypted_data.iv)?; let mut ciphertext = base64::decode(&encrypted_data.ciphertext)?; let tag = base64::decode(&encrypted_data.tag)?; // Re-create the salts from Base64-encoded strings - let salt_aes = SaltString::from_b64(&encrypted_data.salt_aes)?; - let salt_hmac = SaltString::from_b64(&encrypted_data.salt_hmac)?; + let (salt_aes, salt_hmac) = match &encrypted_data.key_derivation_details { + KeyDerivationDetails::Argon2 { + salt_aes, salt_hmac, .. + } => (SaltString::from_b64(salt_aes)?, SaltString::from_b64(salt_hmac)?), + KeyDerivationDetails::SLIP0021 { .. } => { + return MmError::err(MnemonicError::KeyDerivationError( + "Key derivation details should be Argon2!".to_string(), + )) + }, + }; // Re-create the keys from the password and salts let (key_aes, key_hmac) = derive_keys_for_mnemonic(password, &salt_aes, &salt_hmac)?; diff --git a/mm2src/mm2_main/src/lp_wallet.rs b/mm2src/mm2_main/src/lp_wallet.rs index d6ce56b08d..aaa443b96a 100644 --- a/mm2src/mm2_main/src/lp_wallet.rs +++ b/mm2src/mm2_main/src/lp_wallet.rs @@ -1,5 +1,5 @@ use common::HttpStatusCode; -use crypto::{decrypt_mnemonic, encrypt_mnemonic, generate_mnemonic, CryptoCtx, CryptoInitError, EncryptedMnemonicData, +use crypto::{decrypt_mnemonic, encrypt_mnemonic, generate_mnemonic, CryptoCtx, CryptoInitError, EncryptedData, MnemonicError}; use http::StatusCode; use mm2_core::mm_ctx::MmArc; @@ -153,7 +153,7 @@ async fn read_and_decrypt_passphrase( #[derive(Debug, Deserialize, Serialize)] #[serde(untagged)] enum Passphrase { - Encrypted(EncryptedMnemonicData), + Encrypted(EncryptedData), Decrypted(String), } @@ -215,7 +215,7 @@ async fn confirm_or_encrypt_and_store_passphrase( async fn decrypt_validate_or_save_passphrase( ctx: &MmArc, wallet_name: &str, - encrypted_passphrase_data: EncryptedMnemonicData, + encrypted_passphrase_data: EncryptedData, wallet_password: &str, ) -> WalletInitResult> { // Decrypt the provided encrypted passphrase @@ -368,21 +368,17 @@ pub struct GetMnemonicRequest { /// `MnemonicForRpc` is an enum representing the format of a mnemonic for RPC communication. /// /// It has two variants: -/// - `Encrypted`: This variant represents an encrypted mnemonic. It carries the [`EncryptedMnemonicData`] struct. +/// - `Encrypted`: This variant represents an encrypted mnemonic. It carries the [`EncryptedData`] struct. /// - `PlainText`: This variant represents a plaintext mnemonic. It carries the mnemonic as a `String`. #[derive(Serialize)] #[serde(tag = "format", rename_all = "lowercase")] pub enum MnemonicForRpc { - Encrypted { - encrypted_mnemonic_data: EncryptedMnemonicData, - }, - PlainText { - mnemonic: String, - }, + Encrypted { encrypted_mnemonic_data: EncryptedData }, + PlainText { mnemonic: String }, } -impl From for MnemonicForRpc { - fn from(encrypted_mnemonic_data: EncryptedMnemonicData) -> Self { +impl From for MnemonicForRpc { + fn from(encrypted_mnemonic_data: EncryptedData) -> Self { MnemonicForRpc::Encrypted { encrypted_mnemonic_data, } @@ -402,12 +398,12 @@ impl From for MnemonicForRpc { /// /// # Examples /// -/// For a [`GetMnemonicResponse`] where the [`MnemonicForRpc`] is `Encrypted` with some [`EncryptedMnemonicData`], the JSON representation would be: +/// For a [`GetMnemonicResponse`] where the [`MnemonicForRpc`] is `Encrypted` with some [`EncryptedData`], the JSON representation would be: /// ```json /// { /// "format": "encrypted", /// "encrypted_mnemonic_data": { -/// // EncryptedMnemonicData fields go here +/// // EncryptedData fields go here /// } /// } /// ``` diff --git a/mm2src/mm2_main/src/lp_wallet/mnemonics_storage.rs b/mm2src/mm2_main/src/lp_wallet/mnemonics_storage.rs index 164bc84dd3..0f849ac523 100644 --- a/mm2src/mm2_main/src/lp_wallet/mnemonics_storage.rs +++ b/mm2src/mm2_main/src/lp_wallet/mnemonics_storage.rs @@ -1,4 +1,4 @@ -use crypto::EncryptedMnemonicData; +use crypto::EncryptedData; use mm2_core::mm_ctx::MmArc; use mm2_err_handle::prelude::*; use mm2_io::fs::ensure_file_is_writable; @@ -29,7 +29,7 @@ pub enum WalletsStorageError { pub(super) async fn save_encrypted_passphrase( ctx: &MmArc, wallet_name: &str, - encrypted_passphrase_data: &EncryptedMnemonicData, + encrypted_passphrase_data: &EncryptedData, ) -> WalletsStorageResult<()> { let wallet_path = ctx.wallet_file_path(wallet_name); ensure_file_is_writable(&wallet_path).map_to_mm(|_| WalletsStorageError::DbFileIsNotWritable { @@ -43,7 +43,7 @@ pub(super) async fn save_encrypted_passphrase( /// 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 `EncryptedMnemonicData`, which includes +/// 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. /// @@ -58,8 +58,8 @@ pub(super) async fn save_encrypted_passphrase( /// /// # Errors /// Returns an `io::Error` if the file cannot be read or the data cannot be deserialized into -/// `EncryptedPassphraseData`. -pub(super) async fn read_encrypted_passphrase(ctx: &MmArc) -> WalletsStorageResult> { +/// `EncryptedData`. +pub(super) async fn read_encrypted_passphrase(ctx: &MmArc) -> WalletsStorageResult> { let wallet_name = ctx .wallet_name .ok_or(WalletsStorageError::Internal( diff --git a/mm2src/mm2_main/src/lp_wallet/mnemonics_wasm_db.rs b/mm2src/mm2_main/src/lp_wallet/mnemonics_wasm_db.rs index 2f0fa5ae69..1640102344 100644 --- a/mm2src/mm2_main/src/lp_wallet/mnemonics_wasm_db.rs +++ b/mm2src/mm2_main/src/lp_wallet/mnemonics_wasm_db.rs @@ -1,6 +1,6 @@ use crate::mm2::lp_wallet::WalletsContext; use async_trait::async_trait; -use crypto::EncryptedMnemonicData; +use crypto::EncryptedData; use mm2_core::mm_ctx::MmArc; use mm2_core::DbNamespaceId; use mm2_db::indexed_db::{DbIdentifier, DbInstance, DbUpgrader, IndexedDb, IndexedDbBuilder, InitDbResult, @@ -87,7 +87,7 @@ impl TableSignature for MnemonicsTable { pub(super) async fn save_encrypted_passphrase( ctx: &MmArc, wallet_name: &str, - encrypted_passphrase_data: &EncryptedMnemonicData, + encrypted_passphrase_data: &EncryptedData, ) -> WalletsDBResult<()> { let wallets_ctx = WalletsContext::from_ctx(ctx).map_to_mm(WalletsDBError::Internal)?; let db = wallets_ctx @@ -121,7 +121,7 @@ pub(super) async fn save_encrypted_passphrase( Ok(()) } -pub(super) async fn read_encrypted_passphrase(ctx: &MmArc) -> WalletsDBResult> { +pub(super) async fn read_encrypted_passphrase(ctx: &MmArc) -> WalletsDBResult> { let wallets_ctx = WalletsContext::from_ctx(ctx).map_to_mm(WalletsDBError::Internal)?; let db = wallets_ctx .wallets_db() From 37f16fbd958f613978f2a9c02317489b5243529c Mon Sep 17 00:00:00 2001 From: shamardy Date: Sat, 10 Feb 2024 00:07:11 +0200 Subject: [PATCH 16/20] add decrypt.rs for decryption of general data regardless of key derivation method --- mm2src/crypto/src/decrypt.rs | 66 +++++++++++++++++++++++++++++ mm2src/crypto/src/encrypt.rs | 20 +++++++++ mm2src/crypto/src/key_derivation.rs | 3 ++ mm2src/crypto/src/lib.rs | 1 + mm2src/crypto/src/mnemonic.rs | 37 +++------------- 5 files changed, 96 insertions(+), 31 deletions(-) create mode 100644 mm2src/crypto/src/decrypt.rs diff --git a/mm2src/crypto/src/decrypt.rs b/mm2src/crypto/src/decrypt.rs new file mode 100644 index 0000000000..18aaf1c1b3 --- /dev/null +++ b/mm2src/crypto/src/decrypt.rs @@ -0,0 +1,66 @@ +use crate::EncryptedData; +use aes::cipher::{block_padding::Pkcs7, BlockDecryptMut, KeyIvInit}; +use aes::Aes256; +use derive_more::Display; +use hmac::{Hmac, Mac}; +use mm2_err_handle::prelude::*; +use sha2::Sha256; + +type Aes256CbcDec = cbc::Decryptor; + +#[derive(Debug, Display, PartialEq)] +pub enum DecryptionError { + #[display(fmt = "AES cipher error: {}", _0)] + AESCipherError(String), + #[display(fmt = "Error decoding string: {}", _0)] + DecodeError(String), + #[display(fmt = "HMAC error: {}", _0)] + HMACError(String), + Internal(String), +} + +impl From for DecryptionError { + fn from(e: base64::DecodeError) -> Self { DecryptionError::DecodeError(e.to_string()) } +} + +/// Decrypts the provided encrypted data using AES-256-CBC decryption and HMAC for integrity check. +/// +/// This function performs several operations: +/// - It decodes the Base64-encoded values of the IV, ciphertext, and HMAC tag from the `EncryptedData`. +/// - It verifies the HMAC tag before decrypting to ensure the integrity of the data. +/// - It creates an AES-256-CBC cipher instance and decrypts the ciphertext with the provided key and the decoded IV. +/// +/// # Arguments +/// * `encrypted_data` - A reference to the [`EncryptedData`] containing the encrypted data and related metadata. +/// * `key_aes` - A byte array reference to the AES key used for decryption. +/// * `key_hmac` - A byte array reference to the HMAC key used for verifying the HMAC tag. +/// +/// # Returns +/// `MmResult, DecryptionError>` - The result is either a byte vector containing the decrypted data, +/// or a [`DecryptionError`] in case of failure. +/// +/// # Errors +/// This function can return various errors related to Base64 decoding, HMAC verification, and AES decryption. +pub fn decrypt_data( + encrypted_data: &EncryptedData, + key_aes: &[u8; 32], + key_hmac: &[u8; 32], +) -> MmResult, DecryptionError> { + // Decode the Base64-encoded values + let iv = base64::decode(&encrypted_data.iv)?; + let mut ciphertext = base64::decode(&encrypted_data.ciphertext)?; + let tag = base64::decode(&encrypted_data.tag)?; + + // Verify HMAC tag before decrypting + let mut mac = Hmac::::new_from_slice(key_hmac).map_to_mm(|e| DecryptionError::Internal(e.to_string()))?; + mac.update(&ciphertext); + mac.update(&iv); + mac.verify_slice(&tag) + .map_to_mm(|e| DecryptionError::HMACError(e.to_string()))?; + + // Decrypt the ciphertext and return the result + Aes256CbcDec::new(key_aes.into(), iv.as_slice().into()) + .decrypt_padded_mut::(&mut ciphertext) + .map_to_mm(|e| DecryptionError::AESCipherError(e.to_string())) + .map(|plaintext| plaintext.to_vec()) +} diff --git a/mm2src/crypto/src/encrypt.rs b/mm2src/crypto/src/encrypt.rs index 2c41836a5e..094c7a2dab 100644 --- a/mm2src/crypto/src/encrypt.rs +++ b/mm2src/crypto/src/encrypt.rs @@ -65,6 +65,26 @@ pub struct EncryptedData { pub tag: String, } +/// Encrypts the provided data using AES-256-CBC encryption and HMAC for integrity check. +/// +/// This function performs several operations: +/// - It generates an Initialization Vector (IV) for the AES encryption. +/// - It creates an AES-256-CBC cipher instance and encrypts the data with the provided key and the generated IV. +/// - It creates an HMAC tag for verifying the integrity of the encrypted data. +/// - It constructs an [`EncryptedData`] instance containing all the necessary components for decryption. +/// +/// # Arguments +/// * `data` - A byte slice reference to the data that needs to be encrypted. +/// * `key_derivation_details` - A [`KeyDerivationDetails`] instance containing detailed information about the key derivation process. +/// * `key_aes` - A byte array reference to the AES key used for encryption. +/// * `key_hmac` - A byte array reference to the HMAC key used for creating the HMAC tag. +/// +/// # Returns +/// `MmResult` - The result is either an [`EncryptedData`] +/// struct containing all the necessary components for decryption, or an [`EncryptionError`] in case of failure. +/// +/// # Errors +/// This function can return various errors related to IV generation, AES encryption, HMAC creation, and data encoding. pub fn encrypt_data( data: &[u8], key_derivation_details: KeyDerivationDetails, diff --git a/mm2src/crypto/src/key_derivation.rs b/mm2src/crypto/src/key_derivation.rs index 1fc93a0809..5e1901f207 100644 --- a/mm2src/crypto/src/key_derivation.rs +++ b/mm2src/crypto/src/key_derivation.rs @@ -14,6 +14,7 @@ const ARGON2ID_M_COST: u32 = 65536; const ARGON2ID_T_COST: u32 = 2; const ARGON2ID_P_COST: u32 = 1; +#[allow(dead_code)] type HmacSha512 = Hmac; #[derive(Debug, Display, PartialEq)] @@ -104,6 +105,7 @@ pub enum KeyDerivationDetails { /// /// # Returns /// A tuple containing the AES key and HMAC key as byte arrays, or a `MnemonicError` in case of failure. +#[allow(dead_code)] pub(crate) fn derive_keys_for_mnemonic( password: &str, salt_aes: &SaltString, @@ -163,6 +165,7 @@ fn derive_key_from_path(master_node: &[u8], path: &str) -> MmResult<[u8; 32], Ke /// /// # Returns /// A tuple containing the encryption and authentication keys as byte arrays, or an error in case of failure. +#[allow(dead_code)] fn derive_encryption_authentication_keys( master_secret: &[u8; 64], derivation_path: &str, diff --git a/mm2src/crypto/src/lib.rs b/mm2src/crypto/src/lib.rs index 2df500d9f4..7dc1e1d2fc 100644 --- a/mm2src/crypto/src/lib.rs +++ b/mm2src/crypto/src/lib.rs @@ -2,6 +2,7 @@ mod bip32_child; mod crypto_ctx; +mod decrypt; mod encrypt; mod global_hd_ctx; mod hw_client; diff --git a/mm2src/crypto/src/mnemonic.rs b/mm2src/crypto/src/mnemonic.rs index 8ee71cc65a..31ecf09ba8 100644 --- a/mm2src/crypto/src/mnemonic.rs +++ b/mm2src/crypto/src/mnemonic.rs @@ -1,34 +1,27 @@ +use crate::decrypt::decrypt_data; use crate::encrypt::encrypt_data; use crate::key_derivation::{derive_keys_for_mnemonic, Argon2Params, KeyDerivationDetails, KeyDerivationError}; use crate::EncryptedData; -use aes::cipher::{block_padding::Pkcs7, BlockDecryptMut, KeyIvInit}; -use aes::Aes256; use argon2::password_hash::SaltString; use bip39::{Language, Mnemonic}; use derive_more::Display; -use hmac::{Hmac, Mac}; use mm2_core::mm_ctx::MmArc; use mm2_err_handle::prelude::*; -use sha2::Sha256; const DEFAULT_WORD_COUNT: u64 = 12; -type Aes256CbcDec = cbc::Decryptor; - #[derive(Debug, Display, PartialEq)] pub enum MnemonicError { #[display(fmt = "BIP39 mnemonic error: {}", _0)] BIP39Error(String), #[display(fmt = "Error deriving key: {}", _0)] KeyDerivationError(String), - #[display(fmt = "AES cipher error: {}", _0)] - AESCipherError(String), #[display(fmt = "Error decoding string: {}", _0)] DecodeError(String), - #[display(fmt = "Error verifying HMAC tag: {}", _0)] - HMACError(String), #[display(fmt = "Error encrypting mnemonic: {}", _0)] EncryptionError(String), + #[display(fmt = "Error decrypting mnemonic: {}", _0)] + DecryptionError(String), Internal(String), } @@ -40,10 +33,6 @@ impl From for MnemonicError { fn from(e: argon2::password_hash::Error) -> Self { MnemonicError::KeyDerivationError(e.to_string()) } } -impl From for MnemonicError { - fn from(e: base64::DecodeError) -> Self { MnemonicError::DecodeError(e.to_string()) } -} - impl From for MnemonicError { fn from(e: KeyDerivationError) -> Self { MnemonicError::KeyDerivationError(e.to_string()) } } @@ -127,11 +116,6 @@ pub fn encrypt_mnemonic(mnemonic: &str, password: &str) -> MmResult MmResult { - // Decode the Base64-encoded values - let iv = base64::decode(&encrypted_data.iv)?; - let mut ciphertext = base64::decode(&encrypted_data.ciphertext)?; - let tag = base64::decode(&encrypted_data.tag)?; - // Re-create the salts from Base64-encoded strings let (salt_aes, salt_hmac) = match &encrypted_data.key_derivation_details { KeyDerivationDetails::Argon2 { @@ -147,21 +131,12 @@ pub fn decrypt_mnemonic(encrypted_data: &EncryptedData, password: &str) -> MmRes // Re-create the keys from the password and salts let (key_aes, key_hmac) = derive_keys_for_mnemonic(password, &salt_aes, &salt_hmac)?; - // Verify HMAC tag before decrypting - let mut mac = Hmac::::new_from_slice(&key_hmac).map_to_mm(|e| MnemonicError::Internal(e.to_string()))?; - mac.update(&ciphertext); - mac.update(&iv); - mac.verify_slice(&tag) - .map_to_mm(|e| MnemonicError::HMACError(e.to_string()))?; - // Decrypt the ciphertext - let decrypted_data = Aes256CbcDec::new(&key_aes.into(), iv.as_slice().into()) - .decrypt_padded_mut::(&mut ciphertext) - .map_to_mm(|e| MnemonicError::AESCipherError(e.to_string()))?; + let decrypted_data = + decrypt_data(encrypted_data, &key_aes, &key_hmac).mm_err(|e| MnemonicError::DecryptionError(e.to_string()))?; // Convert decrypted data back to a string - let mnemonic_str = - String::from_utf8(decrypted_data.to_vec()).map_to_mm(|e| MnemonicError::DecodeError(e.to_string()))?; + let mnemonic_str = String::from_utf8(decrypted_data).map_to_mm(|e| MnemonicError::DecodeError(e.to_string()))?; let mnemonic = Mnemonic::parse_normalized(&mnemonic_str)?; Ok(mnemonic) } From 9ad3358837df984e5b52e7740284fdaa4968d0c3 Mon Sep 17 00:00:00 2001 From: shamardy Date: Mon, 12 Feb 2024 16:02:52 +0200 Subject: [PATCH 17/20] Introduce SLIP-0021 encryption and decryption in slip21.rs --- mm2src/crypto/src/key_derivation.rs | 28 +++---- mm2src/crypto/src/lib.rs | 1 + mm2src/crypto/src/mnemonic.rs | 2 +- mm2src/crypto/src/slip21.rs | 112 ++++++++++++++++++++++++++++ 4 files changed, 129 insertions(+), 14 deletions(-) create mode 100644 mm2src/crypto/src/slip21.rs diff --git a/mm2src/crypto/src/key_derivation.rs b/mm2src/crypto/src/key_derivation.rs index 5e1901f207..1742e2c973 100644 --- a/mm2src/crypto/src/key_derivation.rs +++ b/mm2src/crypto/src/key_derivation.rs @@ -161,18 +161,18 @@ fn derive_key_from_path(master_node: &[u8], path: &str) -> MmResult<[u8; 32], Ke /// /// # Arguments /// * `master_secret` - The master private key used for key derivation. Can be a BIP-39 seed if this is used to derive keys for wallet data/files encryption. -/// * `derivation_path` - The additional derivation path used for encryption and authentication key derivation. +/// * `encryption_path` - The derivation path used for encryption key derivation. +/// * `authentication_path` - The derivation path used for authentication key derivation. /// /// # Returns -/// A tuple containing the encryption and authentication keys as byte arrays, or an error in case of failure. +/// A tuple containing the encryption and authentication keys as byte arrays, or a [`KeyDerivationError`] in case of failure. #[allow(dead_code)] -fn derive_encryption_authentication_keys( +pub(crate) fn derive_encryption_authentication_keys( master_secret: &[u8; 64], - derivation_path: &str, + encryption_path: &str, + authentication_path: &str, ) -> MmResult<([u8; 32], [u8; 32]), KeyDerivationError> { const SYMMETRIC_KEY_SEED: &[u8] = b"Symmetric key seed"; - const ENCRYPTION_PATH: &str = "SLIP-0021/Master encryption key/"; - const AUTHENTICATION_PATH: &str = "SLIP-0021/Authentication key/"; // Generate the master node `m` according to SLIP-0021. let mut mac = @@ -182,12 +182,10 @@ fn derive_encryption_authentication_keys( let master_key_material = mac.finalize().into_bytes(); // Derive encryption key - let encryption_path = ENCRYPTION_PATH.to_string() + derivation_path; - let encryption_key = derive_key_from_path(&master_key_material, &encryption_path)?; + let encryption_key = derive_key_from_path(&master_key_material, encryption_path)?; // Derive authentication key - let authentication_path = AUTHENTICATION_PATH.to_string() + derivation_path; - let authentication_key = derive_key_from_path(&master_key_material, &authentication_path)?; + let authentication_key = derive_key_from_path(&master_key_material, authentication_path)?; Ok((encryption_key, authentication_key)) } @@ -195,6 +193,7 @@ fn derive_encryption_authentication_keys( #[cfg(test)] mod tests { use super::*; + use crate::slip21::{AUTHENTICATION_PATH, ENCRYPTION_PATH}; #[test] // https://github.com/satoshilabs/slips/blob/master/slip-0021.md#example @@ -207,9 +206,12 @@ mod tests { hex::decode("47194e938ab24cc82bfa25f6486ed54bebe79c40ae2a5a32ea6db294d81861a6").unwrap(); // Directly derive the encryption and authentication keys from the master secret - let (derived_encryption_key, derived_authentication_key) = - derive_encryption_authentication_keys(&master_secret.try_into().expect("Invalid master secret"), "") - .expect("Key derivation failed"); + let (derived_encryption_key, derived_authentication_key) = derive_encryption_authentication_keys( + &master_secret.try_into().expect("Invalid master secret"), + ENCRYPTION_PATH, + AUTHENTICATION_PATH, + ) + .expect("Key derivation failed"); // Verify the derived keys against the expected values assert_eq!( diff --git a/mm2src/crypto/src/lib.rs b/mm2src/crypto/src/lib.rs index 7dc1e1d2fc..04820759f9 100644 --- a/mm2src/crypto/src/lib.rs +++ b/mm2src/crypto/src/lib.rs @@ -13,6 +13,7 @@ mod key_derivation; pub mod mnemonic; pub mod privkey; mod shared_db_id; +mod slip21; mod standard_hd_path; mod xpub; diff --git a/mm2src/crypto/src/mnemonic.rs b/mm2src/crypto/src/mnemonic.rs index 31ecf09ba8..c57c7337ac 100644 --- a/mm2src/crypto/src/mnemonic.rs +++ b/mm2src/crypto/src/mnemonic.rs @@ -121,7 +121,7 @@ pub fn decrypt_mnemonic(encrypted_data: &EncryptedData, password: &str) -> MmRes KeyDerivationDetails::Argon2 { salt_aes, salt_hmac, .. } => (SaltString::from_b64(salt_aes)?, SaltString::from_b64(salt_hmac)?), - KeyDerivationDetails::SLIP0021 { .. } => { + _ => { return MmError::err(MnemonicError::KeyDerivationError( "Key derivation details should be Argon2!".to_string(), )) diff --git a/mm2src/crypto/src/slip21.rs b/mm2src/crypto/src/slip21.rs new file mode 100644 index 0000000000..bf8cb4eeb2 --- /dev/null +++ b/mm2src/crypto/src/slip21.rs @@ -0,0 +1,112 @@ +use crate::decrypt::decrypt_data; +use crate::encrypt::encrypt_data; +use crate::key_derivation::{derive_encryption_authentication_keys, KeyDerivationDetails, KeyDerivationError}; +use crate::EncryptedData; +use derive_more::Display; +use mm2_err_handle::prelude::*; + +#[allow(dead_code)] +pub(crate) const ENCRYPTION_PATH: &str = "SLIP-0021/Master encryption key/"; +#[allow(dead_code)] +pub(crate) const AUTHENTICATION_PATH: &str = "SLIP-0021/Authentication key/"; + +#[derive(Debug, Display, PartialEq)] +#[allow(dead_code)] +pub enum SLIP21Error { + #[display(fmt = "Error deriving key: {}", _0)] + KeyDerivationError(String), + #[display(fmt = "Error encrypting mnemonic: {}", _0)] + EncryptionFailed(String), + #[display(fmt = "Error decrypting mnemonic: {}", _0)] + DecryptionFailed(String), +} + +impl From for SLIP21Error { + fn from(e: KeyDerivationError) -> Self { SLIP21Error::KeyDerivationError(e.to_string()) } +} + +/// Encrypts data using SLIP-0021 derived keys. +/// +/// # Arguments +/// * `data` - The data to be encrypted. +/// * `master_secret` - The master private key used for key derivation. +/// * `derivation_path` - The additional derivation path for key derivation. +/// +/// # Returns +/// `MmResult` - The encrypted data along with metadata for decryption, or an error. +#[allow(dead_code)] +pub fn encrypt_with_slip21( + data: &[u8], + master_secret: &[u8; 64], + derivation_path: &str, +) -> MmResult { + let encryption_path = ENCRYPTION_PATH.to_string() + derivation_path; + let authentication_path = AUTHENTICATION_PATH.to_string() + derivation_path; + + // Derive encryption and authentication keys using SLIP-0021 + let (key_aes, key_hmac) = + derive_encryption_authentication_keys(master_secret, &encryption_path, &authentication_path)?; + + let key_derivation_details = KeyDerivationDetails::SLIP0021 { + encryption_path, + authentication_path, + }; + + encrypt_data(data, key_derivation_details, &key_aes, &key_hmac) + .mm_err(|e| SLIP21Error::EncryptionFailed(e.to_string())) +} + +/// Decrypts data encrypted with SLIP-0021 derived keys. +/// +/// # Arguments +/// * `encrypted_data` - The encrypted data. +/// * `master_secret` - The master private key used for key derivation. +/// +/// # Returns +/// `MmResult, DecryptionError>` - The decrypted data, or an error. +#[allow(dead_code)] +pub fn decrypt_with_slip21(encrypted_data: &EncryptedData, master_secret: &[u8; 64]) -> MmResult, SLIP21Error> { + let (encryption_path, authentication_path) = match &encrypted_data.key_derivation_details { + KeyDerivationDetails::SLIP0021 { + encryption_path, + authentication_path, + } => (encryption_path, authentication_path), + _ => { + return MmError::err(SLIP21Error::KeyDerivationError( + "Key derivation details should be SLIP0021!".to_string(), + )) + }, + }; + + // Derive encryption and authentication keys using SLIP-0021 + let (key_aes, key_hmac) = + derive_encryption_authentication_keys(master_secret, encryption_path, authentication_path)?; + + decrypt_data(encrypted_data, &key_aes, &key_hmac).mm_err(|e| SLIP21Error::DecryptionFailed(e.to_string())) +} + +#[cfg(test)] +mod tests { + use super::*; + use std::convert::TryInto; + + #[test] + fn test_encrypt_decrypt_with_slip21() { + let data = b"Example data to encrypt and decrypt using SLIP-0021"; + let master_secret = hex::decode("c76c4ac4f4e4a00d6b274d5c39c700bb4a7ddc04fbc6f78e85ca75007b5b495f74a9043eeb77bdd53aa6fc3a0e31462270316fa04b8c19114c8798706cd02ac8").unwrap().try_into().unwrap(); + let derivation_path = "test/path"; + + // Encrypt the data + let encrypted_data_result = encrypt_with_slip21(data, &master_secret, derivation_path); + assert!(encrypted_data_result.is_ok()); + let encrypted_data = encrypted_data_result.unwrap(); + + // Decrypt the data + let decrypted_data_result = decrypt_with_slip21(&encrypted_data, &master_secret); + assert!(decrypted_data_result.is_ok()); + let decrypted_data = decrypted_data_result.unwrap(); + + // Verify if decrypted data matches the original data + assert_eq!(data.to_vec(), decrypted_data); + } +} From 4f666c13d664488fba529e44e6d1b3391e5da030 Mon Sep 17 00:00:00 2001 From: shamardy Date: Wed, 14 Feb 2024 16:16:00 +0200 Subject: [PATCH 18/20] Review Fixes: - Remove arguments descriptions in doc comments. - Fix read passphrase functions names by adding `if_available`. - Improve cross-platform test compatibility. --- Cargo.lock | 2 + mm2src/crypto/Cargo.toml | 5 ++ mm2src/crypto/src/decrypt.rs | 5 -- mm2src/crypto/src/encrypt.rs | 6 -- mm2src/crypto/src/key_derivation.rs | 10 --- mm2src/crypto/src/mnemonic.rs | 29 +++---- mm2src/crypto/src/slip21.rs | 22 +++--- mm2src/mm2_main/src/lp_wallet.rs | 37 ++++----- .../src/lp_wallet/mnemonics_storage.rs | 23 +----- .../src/lp_wallet/mnemonics_wasm_db.rs | 77 ++++++++----------- 10 files changed, 76 insertions(+), 140 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 09293ce3d9..27d3aa6f3a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1566,6 +1566,7 @@ dependencies = [ "bitcrypto", "bs58 0.4.0", "cbc", + "cfg-if 1.0.0", "cipher 0.4.4", "common", "derive_more", @@ -1595,6 +1596,7 @@ dependencies = [ "serde_derive", "serde_json", "sha2 0.10.7", + "tokio", "trezor", "wasm-bindgen-test", "web3", diff --git a/mm2src/crypto/Cargo.toml b/mm2src/crypto/Cargo.toml index 34279a2ce3..1c6575dd6f 100644 --- a/mm2src/crypto/Cargo.toml +++ b/mm2src/crypto/Cargo.toml @@ -48,10 +48,15 @@ trezor = { path = "../trezor" } zeroize = { version = "1.5", features = ["zeroize_derive"] } [target.'cfg(target_arch = "wasm32")'.dependencies] +cfg-if = "1.0" mm2_eth = { path = "../mm2_eth" } mm2_metamask = { path = "../mm2_metamask" } wasm-bindgen-test = { version = "0.3.2" } web3 = { git = "https://github.com/KomodoPlatform/rust-web3", tag = "v0.19.0", default-features = false } +[dev-dependencies] +cfg-if = "1.0" +tokio = { version = "1.20", default-features = false } + [features] trezor-udp = ["trezor/trezor-udp"] diff --git a/mm2src/crypto/src/decrypt.rs b/mm2src/crypto/src/decrypt.rs index 18aaf1c1b3..f85f5c8928 100644 --- a/mm2src/crypto/src/decrypt.rs +++ b/mm2src/crypto/src/decrypt.rs @@ -30,11 +30,6 @@ impl From for DecryptionError { /// - It verifies the HMAC tag before decrypting to ensure the integrity of the data. /// - It creates an AES-256-CBC cipher instance and decrypts the ciphertext with the provided key and the decoded IV. /// -/// # Arguments -/// * `encrypted_data` - A reference to the [`EncryptedData`] containing the encrypted data and related metadata. -/// * `key_aes` - A byte array reference to the AES key used for decryption. -/// * `key_hmac` - A byte array reference to the HMAC key used for verifying the HMAC tag. -/// /// # Returns /// `MmResult, DecryptionError>` - The result is either a byte vector containing the decrypted data, /// or a [`DecryptionError`] in case of failure. diff --git a/mm2src/crypto/src/encrypt.rs b/mm2src/crypto/src/encrypt.rs index 094c7a2dab..57d2af70f9 100644 --- a/mm2src/crypto/src/encrypt.rs +++ b/mm2src/crypto/src/encrypt.rs @@ -73,12 +73,6 @@ pub struct EncryptedData { /// - It creates an HMAC tag for verifying the integrity of the encrypted data. /// - It constructs an [`EncryptedData`] instance containing all the necessary components for decryption. /// -/// # Arguments -/// * `data` - A byte slice reference to the data that needs to be encrypted. -/// * `key_derivation_details` - A [`KeyDerivationDetails`] instance containing detailed information about the key derivation process. -/// * `key_aes` - A byte array reference to the AES key used for encryption. -/// * `key_hmac` - A byte array reference to the HMAC key used for creating the HMAC tag. -/// /// # Returns /// `MmResult` - The result is either an [`EncryptedData`] /// struct containing all the necessary components for decryption, or an [`EncryptionError`] in case of failure. diff --git a/mm2src/crypto/src/key_derivation.rs b/mm2src/crypto/src/key_derivation.rs index 1742e2c973..576ed463b9 100644 --- a/mm2src/crypto/src/key_derivation.rs +++ b/mm2src/crypto/src/key_derivation.rs @@ -98,11 +98,6 @@ pub enum KeyDerivationDetails { /// Derives AES and HMAC keys from a given password and salts for mnemonic encryption/decryption. /// -/// # Arguments -/// * `password` - The password used for key derivation. -/// * `salt_aes` - The salt used for AES key derivation. -/// * `salt_hmac` - The salt used for HMAC key derivation. -/// /// # Returns /// A tuple containing the AES key and HMAC key as byte arrays, or a `MnemonicError` in case of failure. #[allow(dead_code)] @@ -159,11 +154,6 @@ fn derive_key_from_path(master_node: &[u8], path: &str) -> MmResult<[u8; 32], Ke /// Derives encryption and authentication keys from the master private key using [SLIP-0021](https://github.com/satoshilabs/slips/blob/master/slip-0021.md). /// -/// # Arguments -/// * `master_secret` - The master private key used for key derivation. Can be a BIP-39 seed if this is used to derive keys for wallet data/files encryption. -/// * `encryption_path` - The derivation path used for encryption key derivation. -/// * `authentication_path` - The derivation path used for authentication key derivation. -/// /// # Returns /// A tuple containing the encryption and authentication keys as byte arrays, or a [`KeyDerivationError`] in case of failure. #[allow(dead_code)] diff --git a/mm2src/crypto/src/mnemonic.rs b/mm2src/crypto/src/mnemonic.rs index c57c7337ac..c92e23c05b 100644 --- a/mm2src/crypto/src/mnemonic.rs +++ b/mm2src/crypto/src/mnemonic.rs @@ -42,9 +42,6 @@ impl From for MnemonicError { /// This function creates a new mnemonic passphrase using a specified word count and randomness source. /// The generated mnemonic is intended for use as a wallet mnemonic. /// -/// # Arguments -/// * `ctx` - The `MmArc` context containing the application configuration. -/// /// # Returns /// `MmInitResult` - The generated mnemonic passphrase or an error if generation fails. /// @@ -65,10 +62,6 @@ pub fn generate_mnemonic(ctx: &MmArc) -> MmResult { /// - It encrypts the mnemonic using AES-256-CBC. /// - It creates an HMAC tag for verifying the integrity and authenticity of the encrypted data. /// -/// # Arguments -/// * `mnemonic` - A `&str` reference to the mnemonic that needs to be encrypted. -/// * `password` - A `&str` reference to the password used for key derivation. -/// /// # Returns /// `MmResult` - The result is either an `EncryptedData` /// struct containing all the necessary components for decryption, or a `MnemonicError` in case of failure. @@ -105,10 +98,6 @@ pub fn encrypt_mnemonic(mnemonic: &str, password: &str) -> MmResult` - The result is either a `Mnemonic` instance if decryption is successful, /// or a `MnemonicError` in case of failure. @@ -141,12 +130,17 @@ pub fn decrypt_mnemonic(encrypted_data: &EncryptedData, password: &str) -> MmRes Ok(mnemonic) } -#[cfg(test)] +#[cfg(any(test, target_arch = "wasm32"))] mod tests { use super::*; + use common::cross_test; - #[test] - fn test_encrypt_decrypt_mnemonic() { + common::cfg_wasm32! { + use wasm_bindgen_test::*; + wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); + } + + cross_test!(test_encrypt_decrypt_mnemonic, { let mnemonic = "tank abandon bind salon remove wisdom net size aspect direct source fossil"; let password = "password"; @@ -167,10 +161,9 @@ mod tests { // Verify if decrypted mnemonic matches the original assert_eq!(decrypted_mnemonic, parsed_mnemonic); - } + }); - #[test] - fn test_mnemonic_with_last_byte_zero() { + cross_test!(test_mnemonic_with_last_byte_zero, { let mnemonic = "tank abandon bind salon remove wisdom net size aspect direct source fossil\0".to_string(); let password = "password"; @@ -188,5 +181,5 @@ mod tests { .unwrap_err() .to_string() .contains("mnemonic contains an unknown word (word 11)")); - } + }); } diff --git a/mm2src/crypto/src/slip21.rs b/mm2src/crypto/src/slip21.rs index bf8cb4eeb2..bc2bb976d5 100644 --- a/mm2src/crypto/src/slip21.rs +++ b/mm2src/crypto/src/slip21.rs @@ -27,11 +27,6 @@ impl From for SLIP21Error { /// Encrypts data using SLIP-0021 derived keys. /// -/// # Arguments -/// * `data` - The data to be encrypted. -/// * `master_secret` - The master private key used for key derivation. -/// * `derivation_path` - The additional derivation path for key derivation. -/// /// # Returns /// `MmResult` - The encrypted data along with metadata for decryption, or an error. #[allow(dead_code)] @@ -58,10 +53,6 @@ pub fn encrypt_with_slip21( /// Decrypts data encrypted with SLIP-0021 derived keys. /// -/// # Arguments -/// * `encrypted_data` - The encrypted data. -/// * `master_secret` - The master private key used for key derivation. -/// /// # Returns /// `MmResult, DecryptionError>` - The decrypted data, or an error. #[allow(dead_code)] @@ -85,13 +76,18 @@ pub fn decrypt_with_slip21(encrypted_data: &EncryptedData, master_secret: &[u8; decrypt_data(encrypted_data, &key_aes, &key_hmac).mm_err(|e| SLIP21Error::DecryptionFailed(e.to_string())) } -#[cfg(test)] +#[cfg(any(test, target_arch = "wasm32"))] mod tests { use super::*; + use common::cross_test; use std::convert::TryInto; - #[test] - fn test_encrypt_decrypt_with_slip21() { + common::cfg_wasm32! { + use wasm_bindgen_test::*; + wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); + } + + cross_test!(test_encrypt_decrypt_with_slip21, { let data = b"Example data to encrypt and decrypt using SLIP-0021"; let master_secret = hex::decode("c76c4ac4f4e4a00d6b274d5c39c700bb4a7ddc04fbc6f78e85ca75007b5b495f74a9043eeb77bdd53aa6fc3a0e31462270316fa04b8c19114c8798706cd02ac8").unwrap().try_into().unwrap(); let derivation_path = "test/path"; @@ -108,5 +104,5 @@ mod tests { // Verify if decrypted data matches the original data assert_eq!(data.to_vec(), decrypted_data); - } + }); } diff --git a/mm2src/mm2_main/src/lp_wallet.rs b/mm2src/mm2_main/src/lp_wallet.rs index aaa443b96a..2726246909 100644 --- a/mm2src/mm2_main/src/lp_wallet.rs +++ b/mm2src/mm2_main/src/lp_wallet.rs @@ -11,14 +11,14 @@ cfg_wasm32! { use crate::mm2::lp_wallet::mnemonics_wasm_db::{WalletsDb, WalletsDBError}; use mm2_core::mm_ctx::from_ctx; use mm2_db::indexed_db::{ConstructibleDb, DbLocked, InitDbResult}; - use mnemonics_wasm_db::{read_encrypted_passphrase, save_encrypted_passphrase}; + use mnemonics_wasm_db::{read_encrypted_passphrase_if_available, save_encrypted_passphrase}; use std::sync::Arc; type WalletsDbLocked<'a> = DbLocked<'a, WalletsDb>; } cfg_native! { - use mnemonics_storage::{read_encrypted_passphrase, save_encrypted_passphrase, WalletsStorageError}; + use mnemonics_storage::{read_encrypted_passphrase_if_available, save_encrypted_passphrase, WalletsStorageError}; } #[cfg(not(target_arch = "wasm32"))] @@ -119,25 +119,22 @@ async fn encrypt_and_save_passphrase( .mm_err(|e| WalletInitError::WalletsStorageError(e.to_string())) } -/// Reads and decrypts the passphrase from a file associated with the given wallet name. +/// Reads and decrypts the passphrase from a file associated with the given wallet name, if available. /// -/// This function first reads the passphrase from the file. Since the passphrase is stored in an encrypted -/// format, it decrypts it before returning. -/// -/// # Arguments -/// * `ctx` - The `MmArc` context containing the application state and configuration. -/// * `wallet_name` - The name of the wallet for which the passphrase is to be retrieved. +/// This function first checks if a passphrase is available. If a passphrase is found, +/// since it is stored in an encrypted format, it decrypts it before returning. If no passphrase is found, +/// it returns `None`. /// /// # Returns /// `MmInitResult` - The decrypted passphrase or an error if any operation fails. /// /// # Errors /// Returns specific `MmInitError` variants for different failure scenarios. -async fn read_and_decrypt_passphrase( +async fn read_and_decrypt_passphrase_if_available( ctx: &MmArc, wallet_password: &str, ) -> MmResult, ReadPassphraseError> { - match read_encrypted_passphrase(ctx) + match read_encrypted_passphrase_if_available(ctx) .await .mm_err(|e| ReadPassphraseError::WalletsStorageError(e.to_string()))? { @@ -172,7 +169,7 @@ async fn retrieve_or_create_passphrase( wallet_name: &str, wallet_password: &str, ) -> WalletInitResult> { - match read_and_decrypt_passphrase(ctx, wallet_password).await? { + match read_and_decrypt_passphrase_if_available(ctx, wallet_password).await? { Some(passphrase_from_file) => { // If an existing passphrase is found, return it Ok(Some(passphrase_from_file)) @@ -194,7 +191,7 @@ async fn confirm_or_encrypt_and_store_passphrase( passphrase: &str, wallet_password: &str, ) -> WalletInitResult> { - match read_and_decrypt_passphrase(ctx, wallet_password).await? { + match read_and_decrypt_passphrase_if_available(ctx, wallet_password).await? { Some(passphrase_from_file) if passphrase == passphrase_from_file => { // If an existing passphrase is found and it matches the provided passphrase, return it Ok(Some(passphrase_from_file)) @@ -221,7 +218,7 @@ async fn decrypt_validate_or_save_passphrase( // Decrypt the provided encrypted passphrase let decrypted_passphrase = decrypt_mnemonic(&encrypted_passphrase_data, wallet_password)?.to_string(); - match read_and_decrypt_passphrase(ctx, wallet_password).await? { + match read_and_decrypt_passphrase_if_available(ctx, wallet_password).await? { Some(passphrase_from_file) if decrypted_passphrase == passphrase_from_file => { // If an existing passphrase is found and it matches the decrypted passphrase, return it Ok(Some(decrypted_passphrase)) @@ -301,9 +298,6 @@ fn initialize_crypto_context(ctx: &MmArc, passphrase: &str) -> WalletInitResult< /// and handles encryption and storage as needed. /// - Initializes the cryptographic context based on the `enable_hd` configuration. /// -/// # Arguments -/// * `ctx` - The `MmArc` context containing the application state and configuration. -/// /// # Returns /// `MmInitResult<()>` - Result indicating success or failure of the initialization process. /// @@ -459,11 +453,6 @@ impl From for GetMnemonicError { /// Retrieves the wallet mnemonic in the requested format. /// -/// # Arguments -/// -/// * `ctx` - The [`MmArc`] context containing the application state and configuration. -/// * `req` - The [`GetMnemonicRequest`] containing the requested mnemonic format. -/// /// # Returns /// /// A `Result` type containing: @@ -496,7 +485,7 @@ impl From for GetMnemonicError { pub async fn get_mnemonic_rpc(ctx: MmArc, req: GetMnemonicRequest) -> MmResult { match req.mnemonic_format { MnemonicFormat::Encrypted => { - let encrypted_mnemonic = read_encrypted_passphrase(&ctx) + let encrypted_mnemonic = read_encrypted_passphrase_if_available(&ctx) .await? .ok_or_else(|| GetMnemonicError::InvalidRequest("Wallet passphrase file not found".to_string()))?; Ok(GetMnemonicResponse { @@ -504,7 +493,7 @@ pub async fn get_mnemonic_rpc(ctx: MmArc, req: GetMnemonicRequest) -> MmResult { - let plaintext_mnemonic = read_and_decrypt_passphrase(&ctx, &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()))?; Ok(GetMnemonicResponse { diff --git a/mm2src/mm2_main/src/lp_wallet/mnemonics_storage.rs b/mm2src/mm2_main/src/lp_wallet/mnemonics_storage.rs index 0f849ac523..3cf40e61fb 100644 --- a/mm2src/mm2_main/src/lp_wallet/mnemonics_storage.rs +++ b/mm2src/mm2_main/src/lp_wallet/mnemonics_storage.rs @@ -7,8 +7,6 @@ type WalletsStorageResult = Result>; #[derive(Debug, Deserialize, Display, Serialize)] pub enum WalletsStorageError { - #[display(fmt = "{} db file is not writable", path)] - DbFileIsNotWritable { path: String }, #[display(fmt = "Error writing to file: {}", _0)] FsWriteError(String), #[display(fmt = "Error reading from file: {}", _0)] @@ -19,11 +17,6 @@ pub enum WalletsStorageError { /// Saves the passphrase to a file associated with the given wallet name. /// -/// # Arguments -/// -/// * `wallet_name` - The name of the wallet. -/// * `passphrase` - The passphrase to save. -/// /// # Returns /// Result indicating success or an error. pub(super) async fn save_encrypted_passphrase( @@ -32,25 +25,17 @@ pub(super) async fn save_encrypted_passphrase( encrypted_passphrase_data: &EncryptedData, ) -> WalletsStorageResult<()> { let wallet_path = ctx.wallet_file_path(wallet_name); - ensure_file_is_writable(&wallet_path).map_to_mm(|_| WalletsStorageError::DbFileIsNotWritable { - path: wallet_path.display().to_string(), - })?; + ensure_file_is_writable(&wallet_path).map_to_mm(WalletsStorageError::FsWriteError)?; mm2_io::fs::write_json(encrypted_passphrase_data, &wallet_path, true) .await .mm_err(|e| WalletsStorageError::FsWriteError(e.to_string())) } -/// Reads the encrypted passphrase data from the file associated with the given wallet name. +/// Reads the encrypted passphrase data from the file associated with the given wallet name, if available. /// -/// This function is responsible for retrieving the encrypted passphrase data from a file. +/// This function is responsible for retrieving the encrypted passphrase data from a file, if it exists. /// 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. -/// -/// # Arguments -/// -/// * `ctx` - The `MmArc` context, providing access to application configuration and state. -/// * `wallet_name` - The name of the wallet whose encrypted passphrase data is to be read. /// /// # Returns /// `io::Result` - The encrypted passphrase data or an error if the @@ -59,7 +44,7 @@ pub(super) async fn save_encrypted_passphrase( /// # Errors /// Returns an `io::Error` if the file cannot be read or the data cannot be deserialized into /// `EncryptedData`. -pub(super) async fn read_encrypted_passphrase(ctx: &MmArc) -> WalletsStorageResult> { +pub(super) async fn read_encrypted_passphrase_if_available(ctx: &MmArc) -> WalletsStorageResult> { let wallet_name = ctx .wallet_name .ok_or(WalletsStorageError::Internal( diff --git a/mm2src/mm2_main/src/lp_wallet/mnemonics_wasm_db.rs b/mm2src/mm2_main/src/lp_wallet/mnemonics_wasm_db.rs index 1640102344..7f3206df14 100644 --- a/mm2src/mm2_main/src/lp_wallet/mnemonics_wasm_db.rs +++ b/mm2src/mm2_main/src/lp_wallet/mnemonics_wasm_db.rs @@ -3,8 +3,8 @@ use async_trait::async_trait; use crypto::EncryptedData; use mm2_core::mm_ctx::MmArc; use mm2_core::DbNamespaceId; -use mm2_db::indexed_db::{DbIdentifier, DbInstance, DbUpgrader, IndexedDb, IndexedDbBuilder, InitDbResult, - OnUpgradeError, OnUpgradeResult, TableSignature}; +use mm2_db::indexed_db::{DbIdentifier, DbInstance, DbTransactionError, DbUpgrader, IndexedDb, IndexedDbBuilder, + InitDbError, InitDbResult, OnUpgradeError, OnUpgradeResult, TableSignature}; use mm2_err_handle::prelude::*; use std::collections::HashMap; use std::ops::Deref; @@ -28,6 +28,14 @@ pub enum WalletsDBError { Internal(String), } +impl From for WalletsDBError { + fn from(e: InitDbError) -> Self { WalletsDBError::Internal(e.to_string()) } +} + +impl From for WalletsDBError { + fn from(e: DbTransactionError) -> Self { WalletsDBError::Internal(e.to_string()) } +} + #[derive(Debug, Deserialize, Serialize)] struct MnemonicsTable { wallet_name: String, @@ -90,19 +98,10 @@ pub(super) async fn save_encrypted_passphrase( encrypted_passphrase_data: &EncryptedData, ) -> WalletsDBResult<()> { let wallets_ctx = WalletsContext::from_ctx(ctx).map_to_mm(WalletsDBError::Internal)?; - let db = wallets_ctx - .wallets_db() - .await - .mm_err(|e| WalletsDBError::Internal(e.to_string()))?; - - let transaction = db - .transaction() - .await - .mm_err(|e| WalletsDBError::Internal(e.to_string()))?; - let table = transaction - .table::() - .await - .mm_err(|e| WalletsDBError::Internal(e.to_string()))?; + + let db = wallets_ctx.wallets_db().await?; + let transaction = db.transaction().await?; + let table = transaction.table::().await?; let mnemonics_table_item = MnemonicsTable { wallet_name: wallet_name.to_string(), @@ -113,29 +112,17 @@ pub(super) async fn save_encrypted_passphrase( } })?, }; - table - .add_item(&mnemonics_table_item) - .await - .mm_err(|e| WalletsDBError::Internal(e.to_string()))?; + table.add_item(&mnemonics_table_item).await?; Ok(()) } -pub(super) async fn read_encrypted_passphrase(ctx: &MmArc) -> WalletsDBResult> { +pub(super) async fn read_encrypted_passphrase_if_available(ctx: &MmArc) -> WalletsDBResult> { let wallets_ctx = WalletsContext::from_ctx(ctx).map_to_mm(WalletsDBError::Internal)?; - let db = wallets_ctx - .wallets_db() - .await - .mm_err(|e| WalletsDBError::Internal(e.to_string()))?; - - let transaction = db - .transaction() - .await - .mm_err(|e| WalletsDBError::Internal(e.to_string()))?; - let table = transaction - .table::() - .await - .mm_err(|e| WalletsDBError::Internal(e.to_string()))?; + + let db = wallets_ctx.wallets_db().await?; + let transaction = db.transaction().await?; + let table = transaction.table::().await?; let wallet_name = ctx .wallet_name @@ -144,16 +131,16 @@ pub(super) async fn read_encrypted_passphrase(ctx: &MmArc) -> WalletsDBResult serde_json::from_str(&wallet_table_item.encrypted_mnemonic) - .map_to_mm(|e| WalletsDBError::DeserializationError { - field: "encrypted_mnemonic".to_string(), - error: e.to_string(), - }), - Ok(None) => Ok(None), - Err(e) => MmError::err(WalletsDBError::Internal(format!( - "Error retrieving encrypted passphrase: {}", - e - ))), - } + table + .get_item_by_unique_index("wallet_name", wallet_name) + .await? + .map(|(_item_id, wallet_table_item)| { + serde_json::from_str(&wallet_table_item.encrypted_mnemonic).map_to_mm(|e| { + WalletsDBError::DeserializationError { + field: "encrypted_mnemonic".to_string(), + error: e.to_string(), + } + }) + }) + .transpose() } From ba0fb593f8de8ef02c834f1f91dfd584d05ed992 Mon Sep 17 00:00:00 2001 From: shamardy Date: Fri, 16 Feb 2024 02:57:03 +0200 Subject: [PATCH 19/20] Review fixes: - 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 --- mm2src/crypto/src/encrypt.rs | 1 - mm2src/crypto/src/key_derivation.rs | 18 +++++++++++------- mm2src/mm2_main/src/lp_native_dex.rs | 2 +- mm2src/mm2_main/src/lp_wallet.rs | 15 ++++++++------- 4 files changed, 20 insertions(+), 16 deletions(-) diff --git a/mm2src/crypto/src/encrypt.rs b/mm2src/crypto/src/encrypt.rs index 57d2af70f9..d3ae6a2e13 100644 --- a/mm2src/crypto/src/encrypt.rs +++ b/mm2src/crypto/src/encrypt.rs @@ -24,7 +24,6 @@ pub enum EncryptionAlgorithm { /// AES-256-CBC algorithm. AES256CBC, // Placeholder for future algorithms. - // Future algorithms can be added here. } /// `EncryptedData` represents encrypted data for a wallet. diff --git a/mm2src/crypto/src/key_derivation.rs b/mm2src/crypto/src/key_derivation.rs index 576ed463b9..74500c3d52 100644 --- a/mm2src/crypto/src/key_derivation.rs +++ b/mm2src/crypto/src/key_derivation.rs @@ -93,7 +93,6 @@ pub enum KeyDerivationDetails { authentication_path: String, }, // Placeholder for future algorithms. - // Future algorithms can be added here. } /// Derives AES and HMAC keys from a given password and salts for mnemonic encryption/decryption. @@ -162,11 +161,11 @@ pub(crate) fn derive_encryption_authentication_keys( encryption_path: &str, authentication_path: &str, ) -> MmResult<([u8; 32], [u8; 32]), KeyDerivationError> { - const SYMMETRIC_KEY_SEED: &[u8] = b"Symmetric key seed"; + const MASTER_NODE_HMAC_KEY: &[u8] = b"Symmetric key seed"; // Generate the master node `m` according to SLIP-0021. let mut mac = - HmacSha512::new_from_slice(SYMMETRIC_KEY_SEED).map_to_mm(|_| KeyDerivationError::HmacInitialization)?; + HmacSha512::new_from_slice(MASTER_NODE_HMAC_KEY).map_to_mm(|_| KeyDerivationError::HmacInitialization)?; mac.update(master_secret); drop_mutability!(mac); let master_key_material = mac.finalize().into_bytes(); @@ -180,14 +179,19 @@ pub(crate) fn derive_encryption_authentication_keys( Ok((encryption_key, authentication_key)) } -#[cfg(test)] +#[cfg(any(test, target_arch = "wasm32"))] mod tests { use super::*; use crate::slip21::{AUTHENTICATION_PATH, ENCRYPTION_PATH}; + use common::cross_test; + + common::cfg_wasm32! { + use wasm_bindgen_test::*; + wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); + } - #[test] // https://github.com/satoshilabs/slips/blob/master/slip-0021.md#example - fn test_slip_0021_key_derivation() { + cross_test!(test_slip_0021_key_derivation, { let master_secret = hex::decode("c76c4ac4f4e4a00d6b274d5c39c700bb4a7ddc04fbc6f78e85ca75007b5b495f74a9043eeb77bdd53aa6fc3a0e31462270316fa04b8c19114c8798706cd02ac8").unwrap(); let expected_encryption_key = @@ -214,5 +218,5 @@ mod tests { expected_authentication_key.as_slice(), "Derived authentication key does not match expected value" ); - } + }); } diff --git a/mm2src/mm2_main/src/lp_native_dex.rs b/mm2src/mm2_main/src/lp_native_dex.rs index df8b9bd0a4..587fb9ae71 100644 --- a/mm2src/mm2_main/src/lp_native_dex.rs +++ b/mm2src/mm2_main/src/lp_native_dex.rs @@ -500,7 +500,7 @@ pub async fn lp_init(ctx: MmArc, version: String, datetime: String) -> MmInitRes } // This either initializes the cryptographic context or sets up the context for "no login mode". - initialize_wallet_passphrase(ctx.clone()).await?; + initialize_wallet_passphrase(&ctx).await?; lp_init_continue(ctx.clone()).await?; diff --git a/mm2src/mm2_main/src/lp_wallet.rs b/mm2src/mm2_main/src/lp_wallet.rs index 2726246909..e84ea8e98c 100644 --- a/mm2src/mm2_main/src/lp_wallet.rs +++ b/mm2src/mm2_main/src/lp_wallet.rs @@ -51,7 +51,7 @@ pub enum WalletInitError { MnemonicError(String), #[display(fmt = "Error initializing crypto context: {}", _0)] CryptoInitError(String), - Internal(String), + InternalError(String), } impl From for WalletInitError { @@ -95,6 +95,7 @@ impl WalletsContext { }) }))) } + pub async fn wallets_db(&self) -> InitDbResult> { self.wallets_db.get_or_initialize().await } } @@ -304,15 +305,15 @@ fn initialize_crypto_context(ctx: &MmArc, passphrase: &str) -> WalletInitResult< /// # Errors /// Returns `MmInitError` if deserialization fails or if there are issues in passphrase handling. /// -pub(crate) async fn initialize_wallet_passphrase(ctx: MmArc) -> WalletInitResult<()> { - let (wallet_name, passphrase) = deserialize_wallet_config(&ctx)?; +pub(crate) async fn initialize_wallet_passphrase(ctx: &MmArc) -> WalletInitResult<()> { + let (wallet_name, passphrase) = deserialize_wallet_config(ctx)?; ctx.wallet_name .pin(wallet_name.clone()) - .map_to_mm(WalletInitError::Internal)?; - let passphrase = process_passphrase_logic(&ctx, wallet_name, passphrase).await?; + .map_to_mm(WalletInitError::InternalError)?; + let passphrase = process_passphrase_logic(ctx, wallet_name, passphrase).await?; if let Some(passphrase) = passphrase { - initialize_crypto_context(&ctx, &passphrase)?; + initialize_crypto_context(ctx, &passphrase)?; } Ok(()) @@ -487,7 +488,7 @@ pub async fn get_mnemonic_rpc(ctx: MmArc, req: GetMnemonicRequest) -> MmResult { let encrypted_mnemonic = read_encrypted_passphrase_if_available(&ctx) .await? - .ok_or_else(|| GetMnemonicError::InvalidRequest("Wallet passphrase file not found".to_string()))?; + .ok_or_else(|| GetMnemonicError::InvalidRequest("Wallet mnemonic file not found".to_string()))?; Ok(GetMnemonicResponse { mnemonic: encrypted_mnemonic.into(), }) From eb26362631c680e2d85b55603ea5504ee7429cc8 Mon Sep 17 00:00:00 2001 From: shamardy Date: Fri, 16 Feb 2024 19:27:57 +0200 Subject: [PATCH 20/20] Review fixes: Update base64 crate to 0.21.2 and replace deprecated functions --- Cargo.lock | 15 +++------------ mm2src/coins/Cargo.toml | 2 +- mm2src/coins/utxo/slp.rs | 4 +++- .../coins/utxo/utxo_builder/utxo_coin_builder.rs | 5 +++-- mm2src/coins/utxo/utxo_common.rs | 6 ++++-- mm2src/crypto/Cargo.toml | 2 +- mm2src/crypto/src/decrypt.rs | 8 +++++--- mm2src/crypto/src/encrypt.rs | 8 +++++--- mm2src/gossipsub/Cargo.toml | 2 +- mm2src/mm2_metrics/Cargo.toml | 2 +- mm2src/mm2_metrics/src/mm_metrics.rs | 4 +++- 11 files changed, 30 insertions(+), 28 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 27d3aa6f3a..91eb25147a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -403,15 +403,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6107fe1be6682a68940da878d9e9f5e90ca5745b3dec9fd1bb393c8777d4f581" -[[package]] -name = "base64" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b25d992356d2eb0ed82172f5248873db5560c4721f564b13cb5193bda5e668e" -dependencies = [ - "byteorder", -] - [[package]] name = "base64" version = "0.11.0" @@ -1057,7 +1048,7 @@ dependencies = [ "async-std", "async-trait", "base58", - "base64 0.10.1", + "base64 0.21.2", "bincode", "bip32", "bitcoin", @@ -1560,7 +1551,7 @@ dependencies = [ "argon2", "arrayref", "async-trait", - "base64 0.11.0", + "base64 0.21.2", "bip32", "bip39", "bitcrypto", @@ -4603,7 +4594,7 @@ dependencies = [ name = "mm2_metrics" version = "0.1.0" dependencies = [ - "base64 0.10.1", + "base64 0.21.2", "common", "derive_more", "futures 0.3.28", diff --git a/mm2src/coins/Cargo.toml b/mm2src/coins/Cargo.toml index 02b7b4d95a..c0c5ccca36 100644 --- a/mm2src/coins/Cargo.toml +++ b/mm2src/coins/Cargo.toml @@ -26,7 +26,7 @@ doctest = false [dependencies] async-std = { version = "1.5", features = ["unstable"] } async-trait = "0.1.52" -base64 = "0.10.0" +base64 = "0.21.2" base58 = "0.2.0" bip32 = { version = "0.2.2", default-features = false, features = ["alloc", "secp256k1-ffi"] } bitcoin_hashes = "0.11" diff --git a/mm2src/coins/utxo/slp.rs b/mm2src/coins/utxo/slp.rs index 9899b58c7c..5dbf1ef94d 100644 --- a/mm2src/coins/utxo/slp.rs +++ b/mm2src/coins/utxo/slp.rs @@ -28,6 +28,8 @@ use crate::{BalanceFut, CheckIfMyPaymentSentArgs, CoinBalance, CoinFutSpawner, C WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawError, WithdrawFee, WithdrawFut, WithdrawRequest}; use async_trait::async_trait; +use base64::engine::general_purpose::STANDARD; +use base64::Engine; use bitcrypto::dhash160; use chain::constants::SEQUENCE_FINAL; use chain::{OutPoint, TransactionOutput}; @@ -1115,7 +1117,7 @@ impl MarketCoinOps for SlpToken { let message_hash = self .sign_message_hash(message) .ok_or(VerificationError::PrefixNotFound)?; - let signature = CompactSignature::from(base64::decode(signature)?); + let signature = CompactSignature::from(STANDARD.decode(signature)?); let pubkey = Public::recover_compact(&H256::from(message_hash), &signature)?; let address_from_pubkey = self.platform_coin.address_from_pubkey(&pubkey); let slp_address = self diff --git a/mm2src/coins/utxo/utxo_builder/utxo_coin_builder.rs b/mm2src/coins/utxo/utxo_builder/utxo_coin_builder.rs index 6f10e9d791..d9ea5af6c7 100644 --- a/mm2src/coins/utxo/utxo_builder/utxo_coin_builder.rs +++ b/mm2src/coins/utxo/utxo_builder/utxo_coin_builder.rs @@ -632,7 +632,8 @@ pub trait UtxoCoinBuilderCommonOps { #[cfg(not(target_arch = "wasm32"))] fn native_client(&self) -> UtxoCoinBuildResult { - use base64::{encode_config as base64_encode, URL_SAFE}; + use base64::engine::general_purpose::URL_SAFE; + use base64::Engine; let native_conf_path = self.confpath()?; let network = self.network()?; @@ -655,7 +656,7 @@ pub trait UtxoCoinBuilderCommonOps { let client = Arc::new(NativeClientImpl { coin_ticker, uri: format!("http://127.0.0.1:{}", rpc_port), - auth: format!("Basic {}", base64_encode(&auth_str, URL_SAFE)), + auth: format!("Basic {}", URL_SAFE.encode(auth_str)), event_handlers, request_id: 0u64.into(), list_unspent_concurrent_map: ConcurrentRequestMap::new(), diff --git a/mm2src/coins/utxo/utxo_common.rs b/mm2src/coins/utxo/utxo_common.rs index ddec247769..58d4be394f 100644 --- a/mm2src/coins/utxo/utxo_common.rs +++ b/mm2src/coins/utxo/utxo_common.rs @@ -31,6 +31,8 @@ use crate::{CanRefundHtlc, CoinBalance, CoinWithDerivationMethod, ConfirmPayment WithdrawSenderAddress, EARLY_CONFIRMATION_ERR_LOG, INVALID_RECEIVER_ERR_LOG, INVALID_REFUND_TX_ERR_LOG, INVALID_SCRIPT_ERR_LOG, INVALID_SENDER_ERR_LOG, OLD_TRANSACTION_ERR_LOG}; use crate::{MmCoinEnum, WatcherReward, WatcherRewardError}; +use base64::engine::general_purpose::STANDARD; +use base64::Engine; pub use bitcrypto::{dhash160, sha256, ChecksumType}; use bitcrypto::{dhash256, ripemd160}; use chain::constants::SEQUENCE_FINAL; @@ -2919,7 +2921,7 @@ pub fn sign_message(coin: &UtxoCoinFields, message: &str) -> SignatureResult( @@ -2929,7 +2931,7 @@ pub fn verify_message( address: &str, ) -> VerificationResult { let message_hash = sign_message_hash(coin.as_ref(), message).ok_or(VerificationError::PrefixNotFound)?; - let signature = CompactSignature::from(base64::decode(signature_base64)?); + let signature = CompactSignature::from(STANDARD.decode(signature_base64)?); let recovered_pubkey = Public::recover_compact(&H256::from(message_hash), &signature)?; let received_address = checked_address_from_str(coin, address)?; Ok(AddressHashEnum::from(recovered_pubkey.address_hash()) == *received_address.hash()) diff --git a/mm2src/crypto/Cargo.toml b/mm2src/crypto/Cargo.toml index 1c6575dd6f..e7acf53c20 100644 --- a/mm2src/crypto/Cargo.toml +++ b/mm2src/crypto/Cargo.toml @@ -11,7 +11,7 @@ aes = "0.8.3" argon2 = { version = "0.5.2", features = ["zeroize"] } arrayref = "0.3" async-trait = "0.1" -base64 = "0.11.0" +base64 = "0.21.2" bip32 = { version = "0.2.2", default-features = false, features = ["alloc", "secp256k1-ffi"] } bip39 = { version = "2.0.0", features = ["rand_core", "zeroize"], default-features = false } bitcrypto = { path = "../mm2_bitcoin/crypto" } diff --git a/mm2src/crypto/src/decrypt.rs b/mm2src/crypto/src/decrypt.rs index f85f5c8928..a0c750f58c 100644 --- a/mm2src/crypto/src/decrypt.rs +++ b/mm2src/crypto/src/decrypt.rs @@ -1,6 +1,8 @@ use crate::EncryptedData; use aes::cipher::{block_padding::Pkcs7, BlockDecryptMut, KeyIvInit}; use aes::Aes256; +use base64::engine::general_purpose::STANDARD; +use base64::Engine; use derive_more::Display; use hmac::{Hmac, Mac}; use mm2_err_handle::prelude::*; @@ -42,9 +44,9 @@ pub fn decrypt_data( key_hmac: &[u8; 32], ) -> MmResult, DecryptionError> { // Decode the Base64-encoded values - let iv = base64::decode(&encrypted_data.iv)?; - let mut ciphertext = base64::decode(&encrypted_data.ciphertext)?; - let tag = base64::decode(&encrypted_data.tag)?; + let iv = STANDARD.decode(&encrypted_data.iv)?; + let mut ciphertext = STANDARD.decode(&encrypted_data.ciphertext)?; + let tag = STANDARD.decode(&encrypted_data.tag)?; // Verify HMAC tag before decrypting let mut mac = Hmac::::new_from_slice(key_hmac).map_to_mm(|e| DecryptionError::Internal(e.to_string()))?; diff --git a/mm2src/crypto/src/encrypt.rs b/mm2src/crypto/src/encrypt.rs index d3ae6a2e13..30c5246aa1 100644 --- a/mm2src/crypto/src/encrypt.rs +++ b/mm2src/crypto/src/encrypt.rs @@ -1,6 +1,8 @@ use crate::key_derivation::KeyDerivationDetails; use aes::cipher::{block_padding::Pkcs7, BlockEncryptMut, KeyIvInit}; use aes::Aes256; +use base64::engine::general_purpose::STANDARD; +use base64::Engine; use common::drop_mutability; use derive_more::Display; use hmac::{Hmac, Mac}; @@ -107,9 +109,9 @@ pub fn encrypt_data( let encrypted_data = EncryptedData { encryption_algorithm: EncryptionAlgorithm::AES256CBC, key_derivation_details, - iv: base64::encode(&iv), - ciphertext: base64::encode(&ciphertext), - tag: base64::encode(&tag), + iv: STANDARD.encode(iv), + ciphertext: STANDARD.encode(ciphertext), + tag: STANDARD.encode(tag), }; Ok(encrypted_data) diff --git a/mm2src/gossipsub/Cargo.toml b/mm2src/gossipsub/Cargo.toml index a6312a4e61..b34f1911a3 100644 --- a/mm2src/gossipsub/Cargo.toml +++ b/mm2src/gossipsub/Cargo.toml @@ -13,7 +13,7 @@ categories = ["network-programming", "asynchronous"] doctest = false [dependencies] -base64 = "0.11.0" +base64 = "0.21.2" bytes = "0.5.4" byteorder = "1.3.2" common = { path = "../common" } diff --git a/mm2src/mm2_metrics/Cargo.toml b/mm2src/mm2_metrics/Cargo.toml index 75735228b5..e7a7c60887 100644 --- a/mm2src/mm2_metrics/Cargo.toml +++ b/mm2src/mm2_metrics/Cargo.toml @@ -7,7 +7,7 @@ edition = "2021" doctest = false [dependencies] -base64 = "0.10.0" +base64 = "0.21.2" common = { path = "../common" } derive_more = "0.99" futures = { version = "0.3", package = "futures", features = ["compat", "async-await", "thread-pool"] } diff --git a/mm2src/mm2_metrics/src/mm_metrics.rs b/mm2src/mm2_metrics/src/mm_metrics.rs index c64feb09d7..758280f420 100644 --- a/mm2src/mm2_metrics/src/mm_metrics.rs +++ b/mm2src/mm2_metrics/src/mm_metrics.rs @@ -257,6 +257,8 @@ pub mod prometheus { use crate::{MetricsArc, MetricsWeak}; use super::*; + use base64::engine::general_purpose::URL_SAFE; + use base64::Engine; use futures::future::{Future, FutureExt}; use hyper::http::{self, header, Request, Response, StatusCode}; use hyper::service::{make_service_fn, service_fn}; @@ -365,7 +367,7 @@ pub mod prometheus { .map_err(|err| MmMetricsError::PrometheusServerError(err.to_string())))? })?; - let expected = format!("Basic {}", base64::encode_config(&expected.userpass, base64::URL_SAFE)); + let expected = format!("Basic {}", URL_SAFE.encode(expected.userpass)); if header_value != expected { return Err(MmError::new(MmMetricsError::PrometheusInvalidCredentials(