diff --git a/Cargo.lock b/Cargo.lock index 475508ffe3..be787cce7b 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", - "cpufeatures 0.2.1", + "cipher 0.3.0", + "cpufeatures 0.2.11", "opaque-debug", ] +[[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", @@ -138,6 +149,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" @@ -379,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" @@ -481,6 +496,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.5.1", + "zeroize", +] + [[package]] name = "bitcoin" version = "0.29.2" @@ -507,7 +533,7 @@ dependencies = [ "ripemd160", "serialization", "sha-1", - "sha2 0.9.9", + "sha2 0.10.7", "sha3 0.9.1", "siphasher", ] @@ -604,7 +630,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" dependencies = [ - "block-padding", + "block-padding 0.2.1", "generic-array", ] @@ -623,8 +649,8 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2cb03d1bed155d89dce0f845b7899b18a9a163e148fd004e1c28421a783e2d8e" dependencies = [ - "block-padding", - "cipher", + "block-padding 0.2.1", + "cipher 0.3.0", ] [[package]] @@ -633,6 +659,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", +] + [[package]] name = "blocking" version = "0.4.6" @@ -835,6 +870,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.74" @@ -863,8 +907,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c80e5460aa66fe3b91d40bcbdab953a597b60053e34d684ac6903f863b680a6" dependencies = [ "cfg-if 1.0.0", - "cipher", - "cpufeatures 0.2.1", + "cipher 0.3.0", + "cpufeatures 0.2.11", "zeroize", ] @@ -876,7 +920,7 @@ checksum = "a18446b09be63d457bbec447509e85f662f32952b035ce892290396bc0b0cff5" dependencies = [ "aead", "chacha20", - "cipher", + "cipher 0.3.0", "poly1305", "zeroize", ] @@ -918,6 +962,16 @@ dependencies = [ "generic-array", ] +[[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" @@ -977,7 +1031,7 @@ dependencies = [ "async-std", "async-trait", "base58", - "base64 0.10.1", + "base64 0.21.7", "bincode", "bip32", "bitcoin", @@ -1063,7 +1117,7 @@ dependencies = [ "serde_json", "serialization", "serialization_derive", - "sha2 0.9.9", + "sha2 0.10.7", "sha3 0.9.1", "solana-client", "solana-sdk", @@ -1074,7 +1128,6 @@ dependencies = [ "tendermint-config", "tendermint-rpc", "time 0.3.20", - "tiny-bip39", "tokio", "tokio-rustls", "tokio-tungstenite-wasm", @@ -1178,7 +1231,7 @@ dependencies = [ "serde_derive", "serde_json", "serde_repr", - "sha2 0.9.9", + "sha2 0.10.7", "shared_ref_counter", "tokio", "uuid 1.2.2", @@ -1303,7 +1356,7 @@ dependencies = [ "k256", "prost", "prost-types", - "rand_core 0.6.3", + "rand_core 0.6.4", "serde", "serde_json", "subtle-encoding", @@ -1322,9 +1375,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", ] @@ -1487,17 +1540,25 @@ checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" name = "crypto" version = "1.0.0" dependencies = [ + "aes 0.8.3", + "argon2", "arrayref", "async-trait", + "base64 0.21.7", "bip32", + "bip39", "bitcrypto", "bs58 0.4.0", + "cbc", + "cfg-if 1.0.0", + "cipher 0.4.4", "common", "derive_more", "enum-primitive-derive", "enum_derives", "futures 0.3.28", "hex", + "hmac 0.12.1", "http 0.2.7", "hw_common", "keys", @@ -1518,10 +1579,12 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "tiny-bip39", + "sha2 0.10.7", + "tokio", "trezor", "wasm-bindgen-test", "web3", + "zeroize", ] [[package]] @@ -1531,16 +1594,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03c6a1d5fa1de37e071642dfa44ec552ca5b299adb128fab16138e24b548fd21" dependencies = [ "generic-array", - "rand_core 0.6.3", + "rand_core 0.6.4", "subtle", "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", "typenum", @@ -1597,7 +1660,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a232f92a03f37dd7d7dd2adc67166c77e9cd88de5b019b9a9eecfaeaf7bfd481" dependencies = [ - "cipher", + "cipher 0.3.0", ] [[package]] @@ -2021,7 +2084,7 @@ dependencies = [ "ff 0.11.1", "generic-array", "group 0.11.0", - "rand_core 0.6.3", + "rand_core 0.6.4", "sec1", "subtle", "zeroize", @@ -2298,7 +2361,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", ] @@ -2393,7 +2456,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", @@ -2694,7 +2757,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", ] @@ -2853,7 +2916,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", ] @@ -3211,6 +3274,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", +] + [[package]] name = "instant" version = "0.1.12" @@ -3425,9 +3498,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" @@ -4542,7 +4615,7 @@ dependencies = [ name = "mm2_metrics" version = "0.1.0" dependencies = [ - "base64 0.10.1", + "base64 0.21.7", "common", "derive_more", "futures 0.3.28", @@ -4638,7 +4711,7 @@ dependencies = [ "secp256k1 0.20.3", "serde", "serde_bytes", - "sha2 0.9.9", + "sha2 0.10.7", "smallvec 1.6.1", "syn 2.0.38", "tokio", @@ -5203,6 +5276,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", +] + [[package]] name = "paste" version = "1.0.7" @@ -5756,7 +5840,7 @@ checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" dependencies = [ "libc", "rand_chacha 0.3.1", - "rand_core 0.6.3", + "rand_core 0.6.4", "rand_hc 0.3.1", ] @@ -5787,7 +5871,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]] @@ -5816,9 +5900,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", ] @@ -5847,7 +5931,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7" dependencies = [ - "rand_core 0.6.3", + "rand_core 0.6.4", ] [[package]] @@ -6772,7 +6856,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", ] @@ -6784,7 +6868,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if 1.0.0", - "cpufeatures 0.2.1", + "cpufeatures 0.2.11", "digest 0.10.7", ] @@ -6796,7 +6880,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", ] @@ -6808,7 +6892,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", ] @@ -6857,7 +6941,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]] @@ -6927,7 +7011,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", @@ -7781,7 +7865,7 @@ dependencies = [ "serde", "serde_json", "serialization", - "sha2 0.9.9", + "sha2 0.10.7", "test_helpers", ] @@ -9548,7 +9632,7 @@ name = "zcash_primitives" version = "0.5.0" source = "git+https://github.com/KomodoPlatform/librustzcash.git?tag=k-1.4.0#debae777f20f5b5fd263e26877258f7600b52cab" dependencies = [ - "aes", + "aes 0.7.5", "bitvec 0.18.5", "blake2b_simd", "blake2s_simd", diff --git a/mm2src/adex_cli/Cargo.lock b/mm2src/adex_cli/Cargo.lock index 35ee092f9f..4a3fa3fec0 100644 --- a/mm2src/adex_cli/Cargo.lock +++ b/mm2src/adex_cli/Cargo.lock @@ -23,7 +23,6 @@ dependencies = [ "directories", "env_logger 0.7.1", "gstuff", - "h2", "http 0.2.9", "hyper", "hyper-rustls", @@ -159,6 +158,28 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be4dc07131ffa69b8072d35f5007352af944213cde02545e2103680baed38fcd" +[[package]] +name = "async-stream" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" +dependencies = [ + "proc-macro2", + "quote 1.0.27", + "syn 2.0.16", +] + [[package]] name = "async-trait" version = "0.1.52" @@ -217,15 +238,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.13.1" @@ -234,9 +246,9 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "base64" -version = "0.21.2" +version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" [[package]] name = "bech32" @@ -282,7 +294,7 @@ dependencies = [ "ripemd160", "serialization", "sha-1", - "sha2", + "sha2 0.10.8", "sha3", "siphasher", ] @@ -326,6 +338,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-buffer" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" +dependencies = [ + "generic-array", +] + [[package]] name = "block-padding" version = "0.2.1" @@ -516,6 +537,7 @@ dependencies = [ "primitive-types", "rand 0.7.3", "regex", + "rustc-hash", "ser_error", "ser_error_derive", "serde", @@ -523,7 +545,7 @@ dependencies = [ "serde_derive", "serde_json", "serde_repr", - "sha2", + "sha2 0.10.8", "tokio", "uuid", "wasm-bindgen", @@ -673,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" @@ -762,6 +794,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.2", + "crypto-common", +] + [[package]] name = "directories" version = "5.0.1" @@ -1116,8 +1158,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", ] @@ -1218,7 +1260,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "126888268dcc288495a26bf004b38c5fdbb31682f992c84ceb046a1f0fe38840" dependencies = [ "crypto-mac", - "digest", + "digest 0.9.0", ] [[package]] @@ -1654,7 +1696,7 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a4964177ddfdab1e3a2b37aec7cf320e14169abb0ed73999f558136409178d5" dependencies = [ - "base64 0.21.2", + "base64 0.21.7", "hyper", "indexmap 1.9.3", "ipnet", @@ -1776,7 +1818,7 @@ dependencies = [ name = "mm2_metrics" version = "0.1.0" dependencies = [ - "base64 0.10.1", + "base64 0.21.7", "common", "derive_more", "futures 0.3.28", @@ -1797,6 +1839,7 @@ name = "mm2_net" version = "0.1.0" dependencies = [ "async-trait", + "base64 0.21.7", "bytes 1.4.0", "cfg-if 1.0.0", "common", @@ -1806,19 +1849,25 @@ dependencies = [ "futures-util", "gstuff", "http 0.2.9", + "http-body 0.4.5", + "httparse", "hyper", "js-sys", "lazy_static", "mm2_core", "mm2_err_handle", "mm2_state_machine", + "pin-project", "prost", "rand 0.7.3", "rustls 0.20.4", "serde", "serde_json", + "thiserror", "tokio", "tokio-rustls", + "tonic", + "tower-service", "wasm-bindgen", "wasm-bindgen-futures", "wasm-bindgen-test", @@ -2075,6 +2124,32 @@ dependencies = [ "crypto-mac", ] +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pin-project" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" +dependencies = [ + "proc-macro2", + "quote 1.0.27", + "syn 2.0.16", +] + [[package]] name = "pin-project-lite" version = "0.2.9" @@ -2385,6 +2460,7 @@ dependencies = [ "libc", "rand_core 0.4.2", "rdrand", + "wasm-bindgen", "winapi", ] @@ -2550,8 +2626,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", ] @@ -2679,7 +2755,7 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2" dependencies = [ - "base64 0.21.2", + "base64 0.21.7", ] [[package]] @@ -2920,10 +2996,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", ] @@ -2933,21 +3009,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", ] @@ -3187,7 +3274,7 @@ dependencies = [ "pbkdf2", "rand 0.7.3", "rustc-hash", - "sha2", + "sha2 0.9.9", "thiserror", "unicode-normalization", "wasm-bindgen", @@ -3277,6 +3364,17 @@ dependencies = [ "webpki 0.22.0", ] +[[package]] +name = "tokio-stream" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + [[package]] name = "tokio-util" version = "0.7.8" @@ -3300,6 +3398,37 @@ dependencies = [ "serde", ] +[[package]] +name = "tonic" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be9d60db39854b30b835107500cf0aca0b0d14d6e1c3de124217c23a29c2ddb" +dependencies = [ + "async-stream", + "async-trait", + "base64 0.13.1", + "bytes 1.4.0", + "futures-core", + "futures-util", + "http 0.2.9", + "http-body 0.4.5", + "percent-encoding", + "pin-project", + "prost", + "prost-derive", + "tokio-stream", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" + [[package]] name = "tower-service" version = "0.3.2" diff --git a/mm2src/coins/Cargo.toml b/mm2src/coins/Cargo.toml index aeee782425..6e52cf073c 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" @@ -95,13 +95,12 @@ 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 } tokio-tungstenite-wasm = { git = "https://github.com/KomodoPlatform/tokio-tungstenite-wasm", rev = "d20abdb", features = ["rustls-tls-native-roots"]} -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/coins/utxo/slp.rs b/mm2src/coins/utxo/slp.rs index 2b5087e0e0..f96bf73055 100644 --- a/mm2src/coins/utxo/slp.rs +++ b/mm2src/coins/utxo/slp.rs @@ -28,6 +28,8 @@ use crate::{BalanceFut, CheckIfMyPaymentSentArgs, CoinBalance, CoinFutSpawner, C WatcherSearchForSwapTxSpendInput, 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}; @@ -1117,7 +1119,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 188a644ba6..3657144a66 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 88ff31c2d8..ec9dd060ea 100644 --- a/mm2src/coins/utxo/utxo_common.rs +++ b/mm2src/coins/utxo/utxo_common.rs @@ -32,6 +32,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; @@ -2952,7 +2954,7 @@ pub fn sign_message(coin: &UtxoCoinFields, message: &str) -> SignatureResult( @@ -2962,7 +2964,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/common/Cargo.toml b/mm2src/common/Cargo.toml index 4637818b35..788947b002 100644 --- a/mm2src/common/Cargo.toml +++ b/mm2src/common/Cargo.toml @@ -42,7 +42,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 80fd38a212..a0d5128d00 100644 --- a/mm2src/crypto/Cargo.toml +++ b/mm2src/crypto/Cargo.toml @@ -7,17 +7,24 @@ edition = "2018" doctest = false [dependencies] +aes = "0.8.3" +argon2 = { version = "0.5.2", features = ["zeroize"] } arrayref = "0.3" async-trait = "0.1" +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" } bs58 = "0.4.0" +cbc = "0.1.2" +cipher = "0.4.4" common = { path = "../common" } derive_more = "0.99" enum_derives = { path = "../derives/enum_derives" } 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" } @@ -36,14 +43,20 @@ 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] +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 new file mode 100644 index 0000000000..a0c750f58c --- /dev/null +++ b/mm2src/crypto/src/decrypt.rs @@ -0,0 +1,63 @@ +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::*; +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. +/// +/// # 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 = 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()))?; + 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 new file mode 100644 index 0000000000..30c5246aa1 --- /dev/null +++ b/mm2src/crypto/src/encrypt.rs @@ -0,0 +1,118 @@ +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}; +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. +} + +/// `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, +} + +/// 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. +/// +/// # 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, + 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: STANDARD.encode(iv), + ciphertext: STANDARD.encode(ciphertext), + tag: STANDARD.encode(tag), + }; + + Ok(encrypted_data) +} 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/key_derivation.rs b/mm2src/crypto/src/key_derivation.rs new file mode 100644 index 0000000000..74500c3d52 --- /dev/null +++ b/mm2src/crypto/src/key_derivation.rs @@ -0,0 +1,222 @@ +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; + +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; + +#[allow(dead_code)] +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()) } +} + +/// 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. +} + +/// Derives AES and HMAC keys from a given password and salts for mnemonic encryption/decryption. +/// +/// # 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, + 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). +/// +/// # Returns +/// A tuple containing the encryption and authentication keys as byte arrays, or a [`KeyDerivationError`] in case of failure. +#[allow(dead_code)] +pub(crate) fn derive_encryption_authentication_keys( + master_secret: &[u8; 64], + encryption_path: &str, + authentication_path: &str, +) -> MmResult<([u8; 32], [u8; 32]), KeyDerivationError> { + 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(MASTER_NODE_HMAC_KEY).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_key = derive_key_from_path(&master_key_material, encryption_path)?; + + // Derive authentication key + let authentication_key = derive_key_from_path(&master_key_material, authentication_path)?; + + Ok((encryption_key, authentication_key)) +} + +#[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); + } + + // https://github.com/satoshilabs/slips/blob/master/slip-0021.md#example + cross_test!(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"), + ENCRYPTION_PATH, + AUTHENTICATION_PATH, + ) + .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 da9ff99a29..04820759f9 100644 --- a/mm2src/crypto/src/lib.rs +++ b/mm2src/crypto/src/lib.rs @@ -2,13 +2,18 @@ mod bip32_child; mod crypto_ctx; +mod decrypt; +mod encrypt; mod global_hd_ctx; 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; +mod slip21; mod standard_hd_path; mod xpub; @@ -18,6 +23,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}; @@ -26,6 +32,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, 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..c92e23c05b --- /dev/null +++ b/mm2src/crypto/src/mnemonic.rs @@ -0,0 +1,185 @@ +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 argon2::password_hash::SaltString; +use bip39::{Language, Mnemonic}; +use derive_more::Display; +use mm2_core::mm_ctx::MmArc; +use mm2_err_handle::prelude::*; + +const DEFAULT_WORD_COUNT: u64 = 12; + +#[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 = "Error decoding string: {}", _0)] + DecodeError(String), + #[display(fmt = "Error encrypting mnemonic: {}", _0)] + EncryptionError(String), + #[display(fmt = "Error decrypting mnemonic: {}", _0)] + DecryptionError(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::KeyDerivationError(e.to_string()) } +} + +impl From for MnemonicError { + fn from(e: KeyDerivationError) -> Self { MnemonicError::KeyDerivationError(e.to_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. +/// +/// # 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) +} + +/// 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. +/// +/// # Returns +/// `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 { + 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); + + let key_derivation_details = KeyDerivationDetails::Argon2 { + params: Argon2Params::default(), + salt_aes: salt_aes.as_str().to_string(), + salt_hmac: salt_hmac.as_str().to_string(), + }; + + // 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 `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. +/// +/// # 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: &EncryptedData, password: &str) -> MmResult { + // Re-create the salts from Base64-encoded strings + 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)?), + _ => { + 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)?; + + // Decrypt the ciphertext + 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).map_to_mm(|e| MnemonicError::DecodeError(e.to_string()))?; + let mnemonic = Mnemonic::parse_normalized(&mnemonic_str)?; + Ok(mnemonic) +} + +#[cfg(any(test, target_arch = "wasm32"))] +mod tests { + use super::*; + use common::cross_test; + + 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"; + + // 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); + }); + + 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"; + + // 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/crypto/src/privkey.rs b/mm2src/crypto/src/privkey.rs index c25a09680c..620b214955 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}; @@ -117,10 +118,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/crypto/src/slip21.rs b/mm2src/crypto/src/slip21.rs new file mode 100644 index 0000000000..bc2bb976d5 --- /dev/null +++ b/mm2src/crypto/src/slip21.rs @@ -0,0 +1,108 @@ +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. +/// +/// # 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. +/// +/// # 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(any(test, target_arch = "wasm32"))] +mod tests { + use super::*; + use common::cross_test; + use std::convert::TryInto; + + 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"; + + // 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); + }); +} diff --git a/mm2src/gossipsub/Cargo.toml b/mm2src/gossipsub/Cargo.toml index bc4c37e8cb..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" } @@ -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 04de2e4d87..5c184e0dfb 100644 --- a/mm2src/mm2_core/src/mm_ctx.rs +++ b/mm2src/mm2_core/src/mm_ctx.rs @@ -109,6 +109,11 @@ 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>>, /// The RPC sender forwarding requests to writing part of underlying stream. #[cfg(target_arch = "wasm32")] pub wasm_rpc: Constructible, @@ -165,6 +170,9 @@ 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")] wasm_rpc: Constructible::default(), #[cfg(not(target_arch = "wasm32"))] @@ -278,6 +286,12 @@ 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") + } + /// MM database path. /// Defaults to a relative "DB". /// @@ -393,15 +407,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_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 b89ff84dad..d6e8ab15d4 100644 --- a/mm2src/mm2_db/src/indexed_db/indexed_db.rs +++ b/mm2src/mm2_db/src/indexed_db/indexed_db.rs @@ -86,14 +86,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, @@ -104,18 +104,22 @@ 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(hex::encode) + .unwrap_or_else(|| "KOMODEFI".to_string()) + } } 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_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 50c9f13c83..726ae612f4 100644 --- a/mm2src/mm2_main/src/lp_native_dex.rs +++ b/mm2src/mm2_main/src/lp_native_dex.rs @@ -22,7 +22,7 @@ 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::{from_hw_error, CryptoCtx, HwError, HwProcessingError, HwRpcError, WithHwRpcError}; use derive_more::Display; use enum_derives::EnumFromTrait; use mm2_core::mm_ctx::{MmArc, MmCtx}; @@ -53,6 +53,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! { @@ -201,10 +202,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 = "Error initializing wallet: {}", _0)] + WalletInitError(String), #[display(fmt = "NETWORK event initialization failed: {}", _0)] NetworkEventInitFailed(String), #[display(fmt = "HEARTBEAT event initialization failed: {}", _0)] @@ -246,25 +245,23 @@ 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), } } } @@ -338,10 +335,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")); @@ -502,25 +495,21 @@ 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); - 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(), - })?; - - // 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)?, - }; + #[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).await?; + lp_init_continue(ctx.clone()).await?; let ctx_id = ctx.ffi_handle().map_to_mm(MmInitError::Internal)?; diff --git a/mm2src/mm2_main/src/lp_wallet.rs b/mm2src/mm2_main/src/lp_wallet.rs new file mode 100644 index 0000000000..e84ea8e98c --- /dev/null +++ b/mm2src/mm2_main/src/lp_wallet.rs @@ -0,0 +1,505 @@ +use common::HttpStatusCode; +use crypto::{decrypt_mnemonic, encrypt_mnemonic, generate_mnemonic, CryptoCtx, CryptoInitError, EncryptedData, + 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, WalletsDBError}; + use mm2_core::mm_ctx::from_ctx; + use mm2_db::indexed_db::{ConstructibleDb, DbLocked, InitDbResult}; + 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_if_available, save_encrypted_passphrase, WalletsStorageError}; +} + +#[cfg(not(target_arch = "wasm32"))] +#[path = "lp_wallet/mnemonics_storage.rs"] +mod mnemonics_storage; +#[cfg(target_arch = "wasm32")] +#[path = "lp_wallet/mnemonics_wasm_db.rs"] +mod mnemonics_wasm_db; + +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 = "The '{}' field not found in the config", field)] + FieldNotFoundInConfig { + field: 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 or decrypting mnemonic: {}", _0)] + MnemonicError(String), + #[display(fmt = "Error initializing crypto context: {}", _0)] + CryptoInitError(String), + InternalError(String), +} + +impl From for WalletInitError { + 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, +} + +#[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 { + 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, if available. +/// +/// 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_if_available( + ctx: &MmArc, + wallet_password: &str, +) -> MmResult, ReadPassphraseError> { + match read_encrypted_passphrase_if_available(ctx) + .await + .mm_err(|e| ReadPassphraseError::WalletsStorageError(e.to_string()))? + { + Some(encrypted_passphrase) => { + let mnemonic = decrypt_mnemonic(&encrypted_passphrase, wallet_password) + .mm_err(|e| ReadPassphraseError::DecryptionError(e.to_string()))?; + Ok(Some(mnemonic.to_string())) + }, + None => Ok(None), + } +} + +#[derive(Debug, Deserialize, Serialize)] +#[serde(untagged)] +enum Passphrase { + Encrypted(EncryptedData), + 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)) +} + +/// 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_if_available(ctx, 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_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)) + }, + 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: EncryptedData, + 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_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)) + }, + 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, +) -> 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(_))) => Err(WalletInitError::FieldNotFoundInConfig { + field: "wallet_name".to_owned(), + } + .into()), + + (Some(wallet_name), passphrase_option) => { + let wallet_password = deserialize_config_field::(ctx, "wallet_password")?; + process_wallet_with_name(ctx, &wallet_name, passphrase_option, &wallet_password).await + }, + } +} + +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: +/// - 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. +/// +/// # 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 (wallet_name, passphrase) = deserialize_wallet_config(ctx)?; + ctx.wallet_name + .pin(wallet_name.clone()) + .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)?; + } + + 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 [`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: EncryptedData }, + PlainText { mnemonic: String }, +} + +impl From for MnemonicForRpc { + fn from(encrypted_mnemonic_data: EncryptedData) -> 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 [`EncryptedData`], the JSON representation would be: +/// ```json +/// { +/// "format": "encrypted", +/// "encrypted_mnemonic_data": { +/// // EncryptedData 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. +/// +/// # 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_if_available(&ctx) + .await? + .ok_or_else(|| GetMnemonicError::InvalidRequest("Wallet mnemonic file not found".to_string()))?; + Ok(GetMnemonicResponse { + mnemonic: encrypted_mnemonic.into(), + }) + }, + MnemonicFormat::PlainText(wallet_password) => { + let plaintext_mnemonic = read_and_decrypt_passphrase_if_available(&ctx, &wallet_password) + .await? + .ok_or_else(|| GetMnemonicError::InvalidRequest("Wallet mnemonic file not found".to_string()))?; + 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 new file mode 100644 index 0000000000..3cf40e61fb --- /dev/null +++ b/mm2src/mm2_main/src/lp_wallet/mnemonics_storage.rs @@ -0,0 +1,63 @@ +use crypto::EncryptedData; +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 = "Error writing to file: {}", _0)] + 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. +/// +/// # Returns +/// Result indicating success or an error. +pub(super) async fn save_encrypted_passphrase( + ctx: &MmArc, + wallet_name: &str, + encrypted_passphrase_data: &EncryptedData, +) -> WalletsStorageResult<()> { + let wallet_path = ctx.wallet_file_path(wallet_name); + 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, if available. +/// +/// 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 +/// +/// # 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 +/// `EncryptedData`. +pub(super) async fn read_encrypted_passphrase_if_available(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 {}: {}", + 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..f7bf2674e4 --- /dev/null +++ b/mm2src/mm2_main/src/lp_wallet/mnemonics_wasm_db.rs @@ -0,0 +1,146 @@ +use crate::mm2::lp_wallet::WalletsContext; +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, DbTransactionError, DbUpgrader, IndexedDb, IndexedDbBuilder, + InitDbError, 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>; + +#[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), +} + +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, + 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 { + const TABLE_NAME: &'static str = "mnemonics"; + + fn on_upgrade_needed(upgrader: &DbUpgrader, mut old_version: u32, new_version: u32) -> OnUpgradeResult<()> { + while old_version < new_version { + match old_version { + 0 => { + 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, + old_version, + new_version, + }) + }, + } + + old_version += 1; + } + Ok(()) + } +} + +pub(super) async fn save_encrypted_passphrase( + ctx: &MmArc, + wallet_name: &str, + encrypted_passphrase_data: &EncryptedData, +) -> WalletsDBResult<()> { + let wallets_ctx = WalletsContext::from_ctx(ctx).map_to_mm(WalletsDBError::Internal)?; + + 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(), + 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?; + + Ok(()) +} + +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?; + let transaction = db.transaction().await?; + let table = transaction.table::().await?; + + 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()))?; + 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() +} diff --git a/mm2src/mm2_main/src/mm2.rs b/mm2src/mm2_main/src/mm2.rs index 49c713ae43..ecaec4fd9e 100644 --- a/mm2src/mm2_main/src/mm2.rs +++ b/mm2src/mm2_main/src/mm2.rs @@ -65,6 +65,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; diff --git a/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs b/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs index 9af6dc7b4b..1596fc9bc3 100644 --- a/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs +++ b/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs @@ -5,6 +5,7 @@ use crate::mm2::lp_native_dex::init_metamask::{cancel_connect_metamask, connect_ 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_swap::swap_v2_rpcs::{active_swaps_rpc, my_recent_swaps_rpc, my_swap_status_rpc}; +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}, @@ -171,6 +172,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, 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( diff --git a/mm2src/mm2_p2p/Cargo.toml b/mm2src/mm2_p2p/Cargo.toml index d1b85635f7..ab1923bd6d 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 8e9c359111..03d6a301db 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;