From 0d661260cc5767d0caae067cd1825ef772abbe9f Mon Sep 17 00:00:00 2001 From: Brian Pearce Date: Tue, 26 Mar 2024 11:34:28 +0100 Subject: [PATCH] feat: ledger key manager interface (#5644) Description --- This PR expands the console wallet and key manager interfaces to support hardware wallets. It *does not* introduce functionality to communicate with the hardware wallet, simply the interface to support it. Another PR will be opened swapping out the placeholder software key manager for the hardware key manager where appropriate. Motivation and Context --- So people can use their most excellent hardware wallets with the MinoTari console wallet. How Has This Been Tested? --- Manually on osx What process can a PR reviewer use to test or verify this change? --- Breaking Changes --- - [x] None - [ ] Requires data directory on base node to be deleted - [ ] Requires hard fork - [ ] Other - Please specify --------- Co-authored-by: C.Lee Taylor --- .gitignore | 4 + Cargo.lock | 68 +++ .../minotari_console_wallet/Cargo.toml | 32 +- .../minotari_console_wallet/src/init/mod.rs | 65 ++- .../minotari_console_wallet/src/lib.rs | 6 +- .../minotari_ledger_wallet/Cargo.lock | 541 ++++++++++++++++++ .../minotari_ledger_wallet/Cargo.toml | 36 ++ applications/minotari_ledger_wallet/README.md | 117 ++++ .../minotari_ledger_wallet/app_nanosplus.json | 21 + .../minotari_ledger_wallet/key_14x14.gif | Bin 0 -> 145 bytes .../minotari_ledger_wallet/key_16x16.gif | Bin 0 -> 155 bytes .../rust-toolchain.toml | 2 + .../minotari_ledger_wallet/rustfmt.toml | 27 + .../minotari_ledger_wallet/src/hashing.rs | 75 +++ .../minotari_ledger_wallet/src/main.rs | 189 ++++++ .../minotari_ledger_wallet/src/utils.rs | 123 ++++ base_layer/common_types/Cargo.toml | 5 + base_layer/common_types/src/lib.rs | 1 + base_layer/common_types/src/wallet_types.rs | 44 ++ base_layer/core/Cargo.toml | 6 + .../src/transactions/key_manager/error.rs | 34 ++ .../transactions/key_manager/initializer.rs | 12 +- .../src/transactions/key_manager/inner.rs | 76 ++- .../key_manager/memory_db_key_manager.rs | 3 + .../core/src/transactions/key_manager/mod.rs | 2 +- .../src/transactions/key_manager/wrapper.rs | 7 +- .../transaction_components/error.rs | 4 +- base_layer/wallet/src/config.rs | 5 +- base_layer/wallet/src/storage/database.rs | 22 +- .../wallet/src/storage/sqlite_db/wallet.rs | 9 + base_layer/wallet/src/wallet.rs | 22 + .../transaction_service_tests/service.rs | 2 + base_layer/wallet_ffi/src/lib.rs | 2 + infrastructure/derive/Cargo.toml | 4 +- scripts/install_ubuntu_dependencies.sh | 1 + 35 files changed, 1530 insertions(+), 37 deletions(-) create mode 100644 applications/minotari_ledger_wallet/Cargo.lock create mode 100644 applications/minotari_ledger_wallet/Cargo.toml create mode 100644 applications/minotari_ledger_wallet/README.md create mode 100644 applications/minotari_ledger_wallet/app_nanosplus.json create mode 100644 applications/minotari_ledger_wallet/key_14x14.gif create mode 100644 applications/minotari_ledger_wallet/key_16x16.gif create mode 100644 applications/minotari_ledger_wallet/rust-toolchain.toml create mode 100644 applications/minotari_ledger_wallet/rustfmt.toml create mode 100644 applications/minotari_ledger_wallet/src/hashing.rs create mode 100644 applications/minotari_ledger_wallet/src/main.rs create mode 100644 applications/minotari_ledger_wallet/src/utils.rs create mode 100644 base_layer/common_types/src/wallet_types.rs diff --git a/.gitignore b/.gitignore index 06dae84105..08666063a4 100644 --- a/.gitignore +++ b/.gitignore @@ -68,3 +68,7 @@ pie/ integration_tests/cucumber-output-junit.xml integration_tests/log/ + +# Ignore MinoTari Ledger Wallet +app_nanosplus.json +app_nanos.json diff --git a/Cargo.lock b/Cargo.lock index eef32e9822..325b755c2f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2280,6 +2280,18 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" +[[package]] +name = "hidapi" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "798154e4b6570af74899d71155fb0072d5b17e6aa12f39c8ef22c60fb8ec99e7" +dependencies = [ + "cc", + "libc", + "pkg-config", + "winapi", +] + [[package]] name = "hkdf" version = "0.12.3" @@ -2723,6 +2735,51 @@ dependencies = [ "spin 0.5.2", ] +[[package]] +name = "ledger-apdu" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe435806c197dfeaa5efcded5e623c4b8230fd28fdf1e91e7a86e40ef2acbf90" +dependencies = [ + "arrayref", + "no-std-compat", + "snafu", +] + +[[package]] +name = "ledger-transport" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1117f2143d92c157197785bf57711d7b02f2cfa101e162f8ca7900fb7f976321" +dependencies = [ + "async-trait", + "ledger-apdu", +] + +[[package]] +name = "ledger-transport" +version = "0.10.0" +source = "git+https://github.com/Zondax/ledger-rs?rev=20e2a20#20e2a2076d799d449ff6f07eb0128548b358d9bc" +dependencies = [ + "async-trait", + "ledger-apdu", +] + +[[package]] +name = "ledger-transport-hid" +version = "0.10.0" +source = "git+https://github.com/Zondax/ledger-rs?rev=20e2a20#20e2a2076d799d449ff6f07eb0128548b358d9bc" +dependencies = [ + "byteorder", + "cfg-if", + "hex", + "hidapi", + "ledger-transport 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc", + "log", + "thiserror", +] + [[package]] name = "libc" version = "0.2.149" @@ -3099,6 +3156,7 @@ dependencies = [ "crossterm 0.25.0", "digest 0.10.7", "futures 0.3.29", + "ledger-transport-hid", "log", "log4rs", "minotari_app_grpc", @@ -3538,6 +3596,12 @@ dependencies = [ "memoffset 0.6.5", ] +[[package]] +name = "no-std-compat" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b93853da6d84c2e3c7d730d6473e8817692dd89be387eb01b94d7f108ecb5b8c" + [[package]] name = "nohash-hasher" version = "0.2.0" @@ -5631,6 +5695,8 @@ dependencies = [ "primitive-types", "rand", "serde", + "strum", + "strum_macros", "tari_common", "tari_crypto", "tari_utilities", @@ -5800,6 +5866,8 @@ dependencies = [ "futures 0.3.29", "hex", "integer-encoding", + "ledger-transport 0.10.0 (git+https://github.com/Zondax/ledger-rs?rev=20e2a20)", + "ledger-transport-hid", "libsqlite3-sys", "lmdb-zero", "log", diff --git a/applications/minotari_console_wallet/Cargo.toml b/applications/minotari_console_wallet/Cargo.toml index fa04df82d7..f6eab9db48 100644 --- a/applications/minotari_console_wallet/Cargo.toml +++ b/applications/minotari_console_wallet/Cargo.toml @@ -21,7 +21,7 @@ tari_script = { path = "../../infrastructure/tari_script" } tari_shutdown = { path = "../../infrastructure/shutdown" } tari_utilities = { version = "0.7" } minotari_wallet = { path = "../../base_layer/wallet", features = [ - "bundled_sqlite", + "bundled_sqlite", ] } tari_hashing = { path = "../../hashing" } @@ -31,26 +31,28 @@ console-subscriber = "0.1.8" # Uncomment for normal use (non tokio-console tracing) tokio = { version = "1.36", features = ["signal"] } +blake2 = "0.10" chrono = { version = "0.4.19", default-features = false } clap = { version = "3.2", features = ["derive", "env"] } config = "0.13.0" crossterm = { version = "0.25.0" } digest = "0.10" futures = { version = "^0.3.16", default-features = false, features = [ - "alloc", + "alloc", ] } +ledger-transport-hid = { git = "https://github.com/Zondax/ledger-rs", rev = "20e2a20", optional = true } +log = { version = "0.4.8", features = ["std"] } log4rs = { version = "1.3.0", default_features = false, features = [ - "config_parsing", - "threshold_filter", - "yaml_format", - "console_appender", - "rolling_file_appender", - "compound_policy", - "size_trigger", - "fixed_window_roller", - "delete_roller", + "config_parsing", + "threshold_filter", + "yaml_format", + "console_appender", + "rolling_file_appender", + "compound_policy", + "size_trigger", + "fixed_window_roller", + "delete_roller", ] } -log = { version = "0.4.8", features = ["std"] } qrcode = { version = "0.12" } rand = "0.8" regex = "1.5.4" @@ -66,10 +68,9 @@ thiserror = "1.0.26" tonic = "0.8.3" unicode-segmentation = "1.6.0" unicode-width = "0.1" +url = "2.3.1" zeroize = "1" zxcvbn = "2" -url = "2.3.1" -blake2 = "0.10" [dependencies.tari_core] path = "../../base_layer/core" @@ -86,8 +87,9 @@ tari_features = { path = "../../common/tari_features", version = "1.0.0-pre.11a" [features] default = ["libtor"] -libtor = ["tari_libtor"] grpc = [] +ledger = ["ledger-transport-hid"] +libtor = ["tari_libtor"] [package.metadata.cargo-machete] # We need to specify extra features for log4rs even though it is not used directly in this crate diff --git a/applications/minotari_console_wallet/src/init/mod.rs b/applications/minotari_console_wallet/src/init/mod.rs index 6d24fb8c28..e2cd0f9b1e 100644 --- a/applications/minotari_console_wallet/src/init/mod.rs +++ b/applications/minotari_console_wallet/src/init/mod.rs @@ -22,8 +22,10 @@ #![allow(dead_code, unused)] -use std::{fs, path::PathBuf, str::FromStr, sync::Arc, time::Instant}; +use std::{fs, io, path::PathBuf, str::FromStr, sync::Arc, time::Instant}; +#[cfg(feature = "ledger")] +use ledger_transport_hid::{hidapi::HidApi, TransportNativeHID}; use log::*; use minotari_app_utilities::identity_management::setup_node_identity; use minotari_wallet::{ @@ -33,7 +35,7 @@ use minotari_wallet::{ database::{WalletBackend, WalletDatabase}, sqlite_utilities::initialize_sqlite_database_backends, }, - wallet::{derive_comms_secret_key, read_or_create_master_seed}, + wallet::{derive_comms_secret_key, read_or_create_master_seed, read_or_create_wallet_type}, Wallet, WalletConfig, WalletSqlite, @@ -48,6 +50,7 @@ use tari_common::{ }, exit_codes::{ExitCode, ExitError}, }; +use tari_common_types::wallet_types::WalletType; use tari_comms::{ multiaddr::Multiaddr, peer_manager::{Peer, PeerFeatures, PeerQuery}, @@ -248,6 +251,7 @@ pub async fn change_password( None, shutdown_signal, non_interactive_mode, + None, ) .await?; @@ -379,6 +383,7 @@ pub async fn init_wallet( recovery_seed: Option, shutdown_signal: ShutdownSignal, non_interactive_mode: bool, + wallet_type: Option, ) -> Result { fs::create_dir_all( config @@ -414,6 +419,7 @@ pub async fn init_wallet( }; let master_seed = read_or_create_master_seed(recovery_seed.clone(), &wallet_db)?; + let wallet_type = read_or_create_wallet_type(wallet_type, &wallet_db); let node_identity = match config.wallet.identity_file.as_ref() { Some(identity_file) => { @@ -459,6 +465,7 @@ pub async fn init_wallet( key_manager_backend, shutdown_signal, master_seed, + wallet_type.unwrap(), ) .await .map_err(|e| match e { @@ -804,6 +811,60 @@ pub(crate) fn boot_with_password( Ok((boot_mode, password)) } +pub fn prompt_wallet_type( + boot_mode: WalletBoot, + wallet_config: &WalletConfig, + non_interactive: bool, +) -> Option { + if non_interactive { + return Some(WalletType::Software); + } + + if wallet_config.wallet_type.is_some() { + return wallet_config.wallet_type; + } + + match boot_mode { + WalletBoot::New => { + #[cfg(not(feature = "ledger"))] + return Some(WalletType::Software); + + #[cfg(feature = "ledger")] + { + if prompt("\r\nWould you like to use a connected hardware wallet? (Supported types: Ledger)") { + print!("Scanning for connected Ledger hardware device... "); + let err = "No connected device was found. Please make sure the device is plugged in before + continuing."; + match TransportNativeHID::new(&HidApi::new().expect(err)) { + Ok(_) => { + println!("Device found."); + let account = prompt_ledger_account().expect("An account value"); + Some(WalletType::Ledger(account)) + }, + Err(e) => panic!("{}", e), + } + } else { + Some(WalletType::Software) + } + } + }, + _ => None, + } +} + +pub fn prompt_ledger_account() -> Option { + let question = + "\r\nPlease enter an account number for your ledger. A simple 1-9, easily remembered numbers are suggested."; + println!("{}", question); + let mut input = "".to_string(); + io::stdin().read_line(&mut input).unwrap(); + let input = input.trim(); + match input.parse() { + Ok(num) => Some(num), + Err(_e) => Some(1), + } +} + #[cfg(test)] mod test { use tari_utilities::SafePassword; diff --git a/applications/minotari_console_wallet/src/lib.rs b/applications/minotari_console_wallet/src/lib.rs index f2450261e9..95cb8c32da 100644 --- a/applications/minotari_console_wallet/src/lib.rs +++ b/applications/minotari_console_wallet/src/lib.rs @@ -64,7 +64,7 @@ use tokio::runtime::Runtime; use wallet_modes::{command_mode, grpc_mode, recovery_mode, script_mode, tui_mode, WalletMode}; pub use crate::config::ApplicationConfig; -use crate::init::{boot_with_password, confirm_direct_only_send, confirm_seed_words, wallet_mode}; +use crate::init::{boot_with_password, confirm_direct_only_send, confirm_seed_words, prompt_wallet_type, wallet_mode}; pub const LOG_TARGET: &str = "wallet::console_wallet::main"; @@ -128,6 +128,9 @@ pub fn run_wallet_with_cli( let recovery_seed = get_recovery_seed(boot_mode, &cli)?; + // This is deactivated at the moment as full support is not yet complete + let wallet_type = prompt_wallet_type(boot_mode, &config.wallet, cli.non_interactive_mode); + // get command line password if provided let seed_words_file_name = cli.seed_words_file_name.clone(); @@ -167,6 +170,7 @@ pub fn run_wallet_with_cli( recovery_seed, shutdown_signal, cli.non_interactive_mode, + wallet_type, ))?; if !cli.non_interactive_mode && diff --git a/applications/minotari_ledger_wallet/Cargo.lock b/applications/minotari_ledger_wallet/Cargo.lock new file mode 100644 index 0000000000..75933241a0 --- /dev/null +++ b/applications/minotari_ledger_wallet/Cargo.lock @@ -0,0 +1,541 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "ahash" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", +] + +[[package]] +name = "atomic-polyfill" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4" +dependencies = [ + "critical-section", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "blake2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "borsh" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4114279215a005bc675e386011e594e1d9b800918cea18fcadadcce864a2046b" +dependencies = [ + "borsh-derive", + "hashbrown", +] + +[[package]] +name = "borsh-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0754613691538d51f329cce9af41d7b7ca150bc973056f1156611489475f54f7" +dependencies = [ + "borsh-derive-internal", + "borsh-schema-derive-internal", + "proc-macro-crate", + "proc-macro2", + "syn 1.0.109", +] + +[[package]] +name = "borsh-derive-internal" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afb438156919598d2c7bad7e1c0adf3d26ed3840dbc010db1a882a65583ca2fb" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "borsh-schema-derive-internal" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634205cc43f74a1b9046ef87c4540ebda95696ec0f315024860cad7c5b0f5ccd" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "cc" +version = "1.0.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "305fe645edc1442a0fa8b6726ba61d422798d37a52e12eaecf4b022ebbb88f01" +dependencies = [ + "libc", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + +[[package]] +name = "cpufeatures" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" +dependencies = [ + "libc", +] + +[[package]] +name = "critical-section" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7059fff8937831a9ae6f0fe4d658ffabf58f2ca96aa9dec1c889f936f705f216" + +[[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 = "curve25519-dalek-derive" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fdaf97f4804dcebfa5862639bc9ce4121e82140bec2a987ac5140294865b5b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.28", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + +[[package]] +name = "doc-comment" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" + +[[package]] +name = "embedded-alloc" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8931e47e33c5d3194fbcf9cc82df0919193bd2fa40008f388eb1d28fd9c9ea6b" +dependencies = [ + "critical-section", + "linked_list_allocator", +] + +[[package]] +name = "fiat-crypto" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e825f6987101665dea6ec934c09ec6d721de7bc1bf92248e1d5810c8cd636b77" + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "gif" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3edd93c6756b4dfaf2709eafcc345ba2636565295c198a9cfbf75fa5e3e00b06" +dependencies = [ + "color_quant", + "weezl", +] + +[[package]] +name = "hashbrown" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +dependencies = [ + "ahash", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "include_gif" +version = "0.1.0" +source = "git+https://github.com/LedgerHQ/sdk_include_gif#699d28c6157518c4493899e2eeaa8af08346e5e7" +dependencies = [ + "gif", + "syn 1.0.109", +] + +[[package]] +name = "keccak" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f6d5ed8676d904364de097082f4e7d240b571b67989ced0240f08b7f966f940" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "libc" +version = "0.2.147" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" + +[[package]] +name = "linked_list_allocator" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afa463f5405ee81cdb9cc2baf37e08ec7e4c8209442b5d72c04cfb2cd6e6286" + +[[package]] +name = "log" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" + +[[package]] +name = "minotari_ledger_wallet" +version = "0.52.0-pre.0" +dependencies = [ + "blake2", + "borsh", + "critical-section", + "digest", + "embedded-alloc", + "nanos_sdk", + "nanos_ui", + "tari_crypto", +] + +[[package]] +name = "nanos_sdk" +version = "0.2.1" +source = "git+https://github.com/LedgerHQ/ledger-nanos-sdk.git#4d9bfc6183d94cee6edb239c39286be3825cc179" +dependencies = [ + "cc", + "num-traits", + "rand_core", +] + +[[package]] +name = "nanos_ui" +version = "0.2.0" +source = "git+https://github.com/LedgerHQ/ledger-nanos-ui.git?rev=6a7c4a3eb41ee0b09c8fd4dcc5be4f3a1f5d7b45#6a7c4a3eb41ee0b09c8fd4dcc5be4f3a1f5d7b45" +dependencies = [ + "include_gif", + "nanos_sdk", +] + +[[package]] +name = "num-traits" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +dependencies = [ + "atomic-polyfill", + "critical-section", +] + +[[package]] +name = "platforms" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d7ddaed09e0eb771a79ab0fd64609ba0afb0a8366421957936ad14cbd13630" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro-crate" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785" +dependencies = [ + "toml", +] + +[[package]] +name = "proc-macro2" +version = "1.0.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50f3b39ccfb720540debaa0164757101c08ecb8d326b15358ce76a62c7e85965" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "semver" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" + +[[package]] +name = "serde" +version = "1.0.183" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32ac8da02677876d532745a130fc9d8e6edfa81a269b107c5b00829b91d8eb3c" + +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest", + "keccak", +] + +[[package]] +name = "snafu" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4de37ad025c587a29e8f3f5605c00f70b98715ef90b9061a815b9e59e9042d6" +dependencies = [ + "doc-comment", + "snafu-derive", +] + +[[package]] +name = "snafu-derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990079665f075b699031e9c08fd3ab99be5029b96f3b78dc0709e8f77e4efebf" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04361975b3f5e348b2189d8dc55bc942f278b2d482a6a0365de5bdd62d351567" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tari-curve25519-dalek" +version = "4.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b8e2644aae57a832e475ebc31199ab1114ebd7fe4d2621e67e89bdd9c8ac38" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "fiat-crypto", + "platforms", + "rand_core", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "tari_crypto" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc09581fc1a9709e54be25e0a50437dc405370b3f5795ee65dc913f4f7e726e5" +dependencies = [ + "blake2", + "digest", + "log", + "once_cell", + "rand_chacha", + "rand_core", + "sha3", + "snafu", + "tari-curve25519-dalek", + "tari_utilities", + "zeroize", +] + +[[package]] +name = "tari_utilities" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "367d17d09cf48e4cf45222fd48536e206f8ef3aaa5eed449c7df38d2ab4586c6" +dependencies = [ + "generic-array", + "snafu", + "zeroize", +] + +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + +[[package]] +name = "typenum" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" + +[[package]] +name = "unicode-ident" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "weezl" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9193164d4de03a926d909d3bc7c30543cecb35400c02114792c2cae20d5e2dbb" + +[[package]] +name = "zeroize" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.28", +] diff --git a/applications/minotari_ledger_wallet/Cargo.toml b/applications/minotari_ledger_wallet/Cargo.toml new file mode 100644 index 0000000000..0bd80617e6 --- /dev/null +++ b/applications/minotari_ledger_wallet/Cargo.toml @@ -0,0 +1,36 @@ +[package] +name = "minotari_ledger_wallet" +version = "0.52.0-pre.0" +authors = ["The Tari Development Community"] +license = "BSD-3-Clause" +edition = "2021" + + +[dependencies] +# lock to rev as soon as this is fixed: https://github.com/rust-lang/rust/issues/98666 +nanos_sdk = { git = "https://github.com/LedgerHQ/ledger-nanos-sdk.git" } +nanos_ui = { git = "https://github.com/LedgerHQ/ledger-nanos-ui.git", rev = "6a7c4a3eb41ee0b09c8fd4dcc5be4f3a1f5d7b45" } + +tari_crypto = { version = "0.18", default-features = false } + +embedded-alloc = "0.5.0" +critical-section = { version = "1.1.1" } +digest = { version = "0.10", default-features = false } +borsh = { version = "0.10", default-features = false } +blake2 = { version = "0.10", default-features = false } + +[profile.release] +opt-level = 's' +lto = "fat" # same as `true` +panic = "abort" + +[package.metadata.nanos] +name = "MinoTari Wallet" +curve = ["secp256k1", "ed25519"] +flags = "0" +icon = "key_16x16.gif" +icon_small = "key_14x14.gif" +path = ["44'/1022'","m/5261654'","m/44'"] +api_level = "1" + +[workspace] diff --git a/applications/minotari_ledger_wallet/README.md b/applications/minotari_ledger_wallet/README.md new file mode 100644 index 0000000000..3d03356929 --- /dev/null +++ b/applications/minotari_ledger_wallet/README.md @@ -0,0 +1,117 @@ +# Instructions + +## Setup + +Ledger does not build with the standard library, so we need to install `rust-src`. This can be done with: +``` +rustup component add rust-src --toolchain nightly +``` + +For loading a BOLOS application to a Ledger device, Ledger has actually written a command, called +[Cargo Ledger](https://github.com/LedgerHQ/cargo-ledger). This we need to install with: +``` +cargo install --git https://github.com/LedgerHQ/cargo-ledger +``` + +As per the [Cargo Ledger setup instructions](https://github.com/LedgerHQ/cargo-ledger#setup) run the following to add +new build targets for the current rust toolchain: + +``` +cargo ledger setup +``` + +Next up we need install the supporting Python libraries from Ledger to control Ledger devices, +[LedgerCTL](https://github.com/LedgerHQ/ledgerctl). This we do with: +``` +pip3 install --upgrade protobuf setuptools ecdsa +pip3 install git+https://github.com/LedgerHQ/ledgerctl +``` + +Lastly install the ARM GCC toolchain: `arm-none-eabi-gcc` for your OS (https://developer.arm.com/downloads/-/gnu-rm). +For MacOS, we can use brew with: +``` +brew install armmbed/formulae/arm-none-eabi-gcc +``` + +## Device configuration + +See https://github.com/LedgerHQ/ledgerctl#device-configuration + +Install a custom certificate on the device to help with development. Start the device in recovery mode (varies per device) +- Nano S Plus: Hold the left button while turning on, and follow on screen instructions +- Nano S: Hold the right button while turning on + +Once in recovery mode run the following where is simply the name of the CA. It can be anything: + +``` +ledgerctl install-ca +``` + +## Runtime + +Open a terminal in the subfolder `./applications/ledger` + +_**Note:** Windows users should start a "x64 Native Tools Command Prompt for VS 2019" to have the build tools available +and then start a python shell within that terminal to have the Python libraries available._ + +### Build `ledger` + +To build, run + +``` +cargo ledger build {TARGET} -- "-Zbuild-std=std,alloc" +``` + +where TARGET = nanosplus, nanos, etc. + +### Build and install `ledger` + +This must be run from a Python shell (`pip3 --version` should work). To build and load, run + +``` +cargo ledger build {TARGET} --load -- "-Zbuild-std=std,alloc" +``` +where TARGET = nanosplus, nanos, etc. + +**Errors** + +If the auto-load does not work ("ledgerwallet.client.CommException: Exception : Invalid status 6512 (Unknown reason)"), +try to do a manual installation. + +### Manual installation + +- First delete the application if it was already installed + +``` +`ledgerctl delete "MinoTari Wallet"` +``` + +- Install with + +``` +`ledgerctl install app_nanosplus.json` +``` +**Note:** In some cases the `cargo ledger build` action will invalidate `app_nanosplus.json` by setting the first line +to `"apiLevel": "0",` - ensure it is set to `"apiLevel": "1",` + +### Running the ledger application + +Start the `MinoTari Wallet` application on the Ledger by navigating to the app and pressing both buttons. You should +see `MinoTari Wallet` displayed on the screen. Now your device is ready to be used with the console wallet. + +_**Note:** To manually exit the application, press both buttons on the Ledger._ + +**Errors** + +- If the `MinoTari Wallet` application on the Ledger is not started when trying to access it with a desktop + application, you should see the following error on the desktop: + + `Error: Ledger application not started` + +- If the wrong application is started on the Ledger, you should see the following error on the desktop: + + `Error: Processing error 'Ledger application is not the MinoTari Wallet application: expected ...'` + +- If the `MinoTari Wallet` application has an incorrect version, you should see the following error on the desktop: + + `Error: Processing error 'MinoTari Wallet application version mismatch: expected ...'` diff --git a/applications/minotari_ledger_wallet/app_nanosplus.json b/applications/minotari_ledger_wallet/app_nanosplus.json new file mode 100644 index 0000000000..e1545ac66b --- /dev/null +++ b/applications/minotari_ledger_wallet/app_nanosplus.json @@ -0,0 +1,21 @@ +{ + "apiLevel": "1", + "binary": "target/nanosplus/release/app.hex", + "dataSize": 0, + "derivationPath": { + "curves": [ + "secp256k1", + "ed25519" + ], + "paths": [ + "44'/1022'", + "m/5261654'", + "m/44'" + ] + }, + "flags": "0", + "icon": "key_14x14.gif", + "name": "MinoTari Wallet", + "targetId": "0x33100004", + "version": "0.52.0-pre.0" +} \ No newline at end of file diff --git a/applications/minotari_ledger_wallet/key_14x14.gif b/applications/minotari_ledger_wallet/key_14x14.gif new file mode 100644 index 0000000000000000000000000000000000000000..2bab27badea681466df189271821b6354dfc29d9 GIT binary patch literal 145 zcmZ?wbh9u|D>()DVbWF3OqWEDr~2@9Addo p72ViltHI;4{yR5crR&3u(_)MoPo-YAjO`L)eeNsp!j*x+8URtMFlqn* literal 0 HcmV?d00001 diff --git a/applications/minotari_ledger_wallet/key_16x16.gif b/applications/minotari_ledger_wallet/key_16x16.gif new file mode 100644 index 0000000000000000000000000000000000000000..55e198b16c9411d3bbf49cdda5273592c1a154a7 GIT binary patch literal 155 zcmZ?wbh9u|6krfw*vtR|%F4=`nwk+25lu}^vuDpJ$v=))!Vmkzj^cK z%a(PhantomData); + +impl DomainSeparatedConsensusHasher { + /// Create a new hasher with the given label + pub fn new(label: &'static str) -> ConsensusHasher> { + let mut digest = Blake2b::::new(); + M::add_domain_separation_tag(&mut digest, label); + ConsensusHasher::from_digest(digest) + } +} + +/// Consensus hasher +#[derive(Clone)] +pub struct ConsensusHasher { + writer: WriteHashWrapper, +} + +impl ConsensusHasher { + fn from_digest(digest: D) -> Self { + Self { + writer: WriteHashWrapper(digest), + } + } +} + +impl ConsensusHasher +where D: Digest +{ + /// Finalize the hasher and return the hash + pub fn finalize(self) -> [u8; 64] { + self.writer.0.finalize().into() + } + + /// Update the hasher with the given data + pub fn update_consensus_encode(&mut self, data: &T) { + BorshSerialize::serialize(data, &mut self.writer) + .expect("Incorrect implementation of BorshSerialize encountered. Implementations MUST be infallible."); + } + + /// Update the hasher with the given data + pub fn chain(mut self, data: &T) -> Self { + self.update_consensus_encode(data); + self + } +} + +#[derive(Clone)] +struct WriteHashWrapper(D); + +impl Write for WriteHashWrapper { + fn write(&mut self, buf: &[u8]) -> BorshResult { + self.0.update(buf); + Ok(buf.len()) + } + + fn flush(&mut self) -> BorshResult<()> { + Ok(()) + } +} diff --git a/applications/minotari_ledger_wallet/src/main.rs b/applications/minotari_ledger_wallet/src/main.rs new file mode 100644 index 0000000000..48e2d0e8a9 --- /dev/null +++ b/applications/minotari_ledger_wallet/src/main.rs @@ -0,0 +1,189 @@ +// Copyright 2022 The Tari Project +// SPDX-License-Identifier: BSD-3-Clause + +//! # MinoTari Ledger Wallet + +#![no_std] +#![no_main] +#![feature(alloc_error_handler)] + +extern crate alloc; +use core::{cmp::min, mem::MaybeUninit}; + +use critical_section::RawRestoreState; +use nanos_sdk::{ + buttons::ButtonEvent, + io, + io::{ApduHeader, Reply, StatusWords, SyscallError}, +}; +use nanos_ui::ui; +use tari_crypto::{ristretto::RistrettoSecretKey, tari_utilities::ByteArray}; + +use crate::{ + alloc::string::ToString, + utils::{byte_to_hex, get_raw_key, u64_to_string}, +}; + +static MINOTARI_LEDGER_ID: u32 = 535348; +static MINOTARI_ACCOUNT_ID: u32 = 7041; + +pub mod hashing; +pub mod utils; + +nanos_sdk::set_panic!(nanos_sdk::exiting_panic); + +/// Allocator heap size +const HEAP_SIZE: usize = 1024 * 26; + +/// Statically allocated heap memory +static mut HEAP_MEM: [MaybeUninit; HEAP_SIZE] = [MaybeUninit::uninit(); HEAP_SIZE]; + +/// Bind global allocator +#[global_allocator] +static HEAP: embedded_alloc::Heap = embedded_alloc::Heap::empty(); + +/// Error handler for allocation +#[alloc_error_handler] +fn alloc_error(_: core::alloc::Layout) -> ! { + ui::SingleMessage::new("allocation error!").show_and_wait(); + nanos_sdk::exit_app(250) +} + +/// Initialise allocator +pub fn init() { + unsafe { HEAP.init(HEAP_MEM.as_ptr() as usize, HEAP_SIZE) } +} + +struct MyCriticalSection; +critical_section::set_impl!(MyCriticalSection); + +unsafe impl critical_section::Impl for MyCriticalSection { + unsafe fn acquire() -> RawRestoreState { + // nothing, it's all good, don't worry bout it + } + + unsafe fn release(_token: RawRestoreState) { + // nothing, it's all good, don't worry bout it + } +} + +/// App Version parameters +const NAME: &str = env!("CARGO_PKG_NAME"); +const VERSION: &str = env!("CARGO_PKG_VERSION"); + +enum Instruction { + GetVersion, + GetPrivateKey, + BadInstruction(u8), + Exit, +} + +impl From for Instruction { + fn from(header: io::ApduHeader) -> Instruction { + match header.ins { + 0x01 => Self::GetVersion, + 0x02 => Self::GetPrivateKey, + 0x03 => Self::Exit, + other => Self::BadInstruction(other), + } + } +} + +#[no_mangle] +extern "C" fn sample_main() { + let mut comm = io::Comm::new(); + init(); + let messages = alloc::vec!["MinoTari Wallet", "keep the app open..", "[exit = both buttons]"]; + let mut index = 0; + ui::SingleMessage::new(messages[index]).show(); + loop { + let event = comm.next_event::(); + match event { + io::Event::Button(ButtonEvent::BothButtonsRelease) => nanos_sdk::exit_app(0), + io::Event::Button(ButtonEvent::RightButtonRelease) => { + index = min(index + 1, messages.len() - 1); + ui::SingleMessage::new(messages[index]).show() + }, + io::Event::Button(ButtonEvent::LeftButtonRelease) => { + if index > 0 { + index -= 1; + } + ui::SingleMessage::new(messages[index]).show() + }, + io::Event::Button(_) => {}, + io::Event::Command(apdu_header) => match handle_apdu(&mut comm, apdu_header.into()) { + Ok(()) => comm.reply_ok(), + Err(e) => comm.reply(e), + }, + io::Event::Ticker => {}, + } + } +} + +// Perform ledger instructions +fn handle_apdu(comm: &mut io::Comm, instruction: Instruction) -> Result<(), Reply> { + if comm.rx == 0 { + return Err(io::StatusWords::NothingReceived.into()); + } + + match instruction { + Instruction::GetVersion => { + ui::SingleMessage::new("GetVersion...").show(); + let name_bytes = NAME.as_bytes(); + let version_bytes = VERSION.as_bytes(); + comm.append(&[1]); // Format + comm.append(&[name_bytes.len() as u8]); + comm.append(name_bytes); + comm.append(&[version_bytes.len() as u8]); + comm.append(version_bytes); + comm.append(&[0]); // No flags + ui::SingleMessage::new("GetVersion... Done").show(); + comm.reply_ok(); + }, + Instruction::GetPrivateKey => { + // first 5 bytes are instruction details + let offset = 5; + let mut address_index_bytes = [0u8; 8]; + address_index_bytes.clone_from_slice(comm.get(offset, offset + 8)); + let address_index = crate::u64_to_string(u64::from_le_bytes(address_index_bytes)); + + let mut msg = "GetPrivateKey... ".to_string(); + msg.push_str(&address_index); + ui::SingleMessage::new(&msg).show(); + + let mut bip32_path = "m/44'/".to_string(); + bip32_path.push_str(&MINOTARI_LEDGER_ID.to_string()); + bip32_path.push_str(&"'/"); + bip32_path.push_str(&MINOTARI_ACCOUNT_ID.to_string()); + bip32_path.push_str(&"'/0/"); + bip32_path.push_str(&address_index); + let path: [u32; 5] = nanos_sdk::ecc::make_bip32_path(bip32_path.as_bytes()); + + let raw_key = get_raw_key(&path)?; + + let k = match RistrettoSecretKey::from_bytes(&raw_key) { + Ok(val) => val, + Err(_) => { + ui::SingleMessage::new("Err: key conversion").show(); + return Err(SyscallError::InvalidParameter.into()); + }, + }; + comm.append(&[1]); // version + comm.append(k.as_bytes()); + comm.reply_ok(); + }, + Instruction::BadInstruction(val) => { + let mut error = "BadInstruction... ! (".to_string(); + error.push_str(&crate::byte_to_hex(val)); + error.push_str(&")"); + ui::SingleMessage::new(&error).show(); + return Err(StatusWords::BadIns.into()); + }, + Instruction::Exit => { + ui::SingleMessage::new("Exit...").show(); + comm.reply_ok(); + nanos_sdk::exit_app(0) + }, + } + Ok(()) +} diff --git a/applications/minotari_ledger_wallet/src/utils.rs b/applications/minotari_ledger_wallet/src/utils.rs new file mode 100644 index 0000000000..e7c609f46a --- /dev/null +++ b/applications/minotari_ledger_wallet/src/utils.rs @@ -0,0 +1,123 @@ +// Copyright 2022 The Tari Project +// SPDX-License-Identifier: BSD-3-Clause + +//! # MinoTari Ledger Wallet - Utils + +use nanos_sdk::{ + ecc::{bip32_derive, CurvesId, CxError, Secret}, + io::SyscallError, +}; +use nanos_ui::ui; +use tari_crypto::hash_domain; + +use crate::{ + alloc::string::{String, ToString}, + hashing::DomainSeparatedConsensusHasher, +}; + +hash_domain!(LedgerHashDomain, "com.tari.genesis_tools.applications.mp_ldeger", 0); + +/// Convert a u64 to a string without using the standard library +pub fn u64_to_string(number: u64) -> String { + let mut buffer = [0u8; 20]; // Maximum length for a 64-bit integer (including null terminator) + let mut pos = 0; + + if number == 0 { + buffer[pos] = b'0'; + pos += 1; + } else { + let mut num = number; + + let mut digits = [0u8; 20]; + let mut num_digits = 0; + + while num > 0 { + digits[num_digits] = b'0' + (num % 10) as u8; + num /= 10; + num_digits += 1; + } + + while num_digits > 0 { + num_digits -= 1; + buffer[pos] = digits[num_digits]; + pos += 1; + } + } + + String::from_utf8_lossy(&buffer[..pos]).to_string() +} + +/// Convert a single byte to a hex string +pub fn byte_to_hex(byte: u8) -> String { + const HEX_CHARS: [u8; 16] = *b"0123456789abcdef"; + let hex = [HEX_CHARS[(byte >> 4) as usize], HEX_CHARS[(byte & 0x0F) as usize]]; + String::from_utf8_lossy(&hex).to_string() +} + +// Convert CxError to a string for display +fn cx_error_to_string(e: CxError) -> String { + let err = match e { + CxError::Carry => "Carry", + CxError::Locked => "Locked", + CxError::Unlocked => "Unlocked", + CxError::NotLocked => "NotLocked", + CxError::NotUnlocked => "NotUnlocked", + CxError::InternalError => "InternalError", + CxError::InvalidParameterSize => "InvalidParameterSize", + CxError::InvalidParameterValue => "InvalidParameterValue", + CxError::InvalidParameter => "InvalidParameter", + CxError::NotInvertible => "NotInvertible", + CxError::Overflow => "Overflow", + CxError::MemoryFull => "MemoryFull", + CxError::NoResidue => "NoResidue", + CxError::PointAtInfinity => "PointAtInfinity", + CxError::InvalidPoint => "InvalidPoint", + CxError::InvalidCurve => "InvalidCurve", + CxError::GenericError => "GenericError", + }; + err.to_string() +} + +// Get a raw 32 byte key hash from the BIP32 path. +// - The wrapper function for the syscall `os_perso_derive_node_bip32`, `bip32_derive`, requires a 96 byte buffer when +// called with `CurvesId::Ed25519` as it checks the consistency of the curve choice and key length in order to prevent +// the underlying syscall from panicking. +// - The syscall `os_perso_derive_node_bip32` returns 96 bytes as: +// private key: 64 bytes +// chain: 32 bytes +// Example: +// d8a57c1be0c52e9643485e77aac56d72fa6c4eb831466c2abd2d320c82d3d14929811c598c13d431bad433e037dbd97265492cea42bc2e3aad15440210a20a2d0000000000000000000000000000000000000000000000000000000000000000 +// - This function applies domain separated hashing to the 64 byte private key of the returned buffer to get 32 +// uniformly distributed random bytes. +fn get_raw_key_hash(path: &[u32]) -> Result<[u8; 64], String> { + let mut key = Secret::<96>::new(); + let raw_key_64 = match bip32_derive(CurvesId::Ed25519, path, key.as_mut()) { + Ok(_) => { + let binding = &key.as_ref()[..64]; + let raw_key_64: [u8; 64] = match binding.try_into() { + Ok(v) => v, + Err(_) => return Err("Err: get_raw_key".to_string()), + }; + raw_key_64 + }, + Err(e) => return Err(cx_error_to_string(e)), + }; + + Ok(DomainSeparatedConsensusHasher::::new("raw_key") + .chain(&raw_key_64) + .finalize()) +} + +/// Get a raw 32 byte key hash from the BIP32 path. In cas of an error, display an interactive message on the device. +pub fn get_raw_key(path: &[u32]) -> Result<[u8; 64], SyscallError> { + match get_raw_key_hash(&path) { + Ok(val) => Ok(val), + Err(e) => { + let mut msg = "".to_string(); + msg.push_str("Err: raw key >>..."); + ui::SingleMessage::new(&msg).show_and_wait(); + ui::SingleMessage::new(&e).show(); + Err(SyscallError::InvalidParameter.into()) + }, + } +} diff --git a/base_layer/common_types/Cargo.toml b/base_layer/common_types/Cargo.toml index d40d364e50..6149733266 100644 --- a/base_layer/common_types/Cargo.toml +++ b/base_layer/common_types/Cargo.toml @@ -19,7 +19,12 @@ newtype-ops = "0.1" once_cell = "1.8.0" rand = "0.8" serde = { version = "1.0.106", features = ["derive"] } +strum = "0.22" +strum_macros = "0.22" thiserror = "1.0.29" base64 = "0.21.0" blake2 = "0.10" primitive-types = { version = "0.12", features = ["serde"] } + +[package.metadata.cargo-machete] +ignored = ["strum", "strum_macros"] # this is so we can run cargo machete without getting false positive about macro dependancies diff --git a/base_layer/common_types/src/lib.rs b/base_layer/common_types/src/lib.rs index ee6a2eb0f6..f1cccdc01b 100644 --- a/base_layer/common_types/src/lib.rs +++ b/base_layer/common_types/src/lib.rs @@ -32,3 +32,4 @@ pub mod tari_address; pub mod transaction; mod tx_id; pub mod types; +pub mod wallet_types; diff --git a/base_layer/common_types/src/wallet_types.rs b/base_layer/common_types/src/wallet_types.rs new file mode 100644 index 0000000000..052df957ea --- /dev/null +++ b/base_layer/common_types/src/wallet_types.rs @@ -0,0 +1,44 @@ +// Copyright 2023 The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use std::{ + fmt, + fmt::{Display, Formatter}, +}; + +use serde::{Deserialize, Serialize}; +use strum_macros::EnumString; + +#[derive(Debug, EnumString, Clone, Copy, Serialize, Deserialize)] +pub enum WalletType { + Software, + Ledger(usize), +} + +impl Display for WalletType { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + WalletType::Software => write!(f, "Software"), + WalletType::Ledger(account) => write!(f, "Ledger({account})"), + } + } +} diff --git a/base_layer/core/Cargo.toml b/base_layer/core/Cargo.toml index 8b675ae7b2..b114a41311 100644 --- a/base_layer/core/Cargo.toml +++ b/base_layer/core/Cargo.toml @@ -23,6 +23,10 @@ base_node = [ ] base_node_proto = [] benches = ["base_node"] +ledger = [ + "ledger-transport", + "ledger-transport-hid" +] metrics = ["tari_metrics"] [dependencies] @@ -62,6 +66,8 @@ fs2 = "0.4.0" futures = { version = "^0.3.16", features = ["async-await"] } hex = "0.4.2" integer-encoding = "3.0" +ledger-transport = { git = "https://github.com/Zondax/ledger-rs", rev = "20e2a20", optional = true } +ledger-transport-hid = { git = "https://github.com/Zondax/ledger-rs", rev = "20e2a20", optional = true } lmdb-zero = "0.4.4" log = "0.4" log-mdc = "0.1.0" diff --git a/base_layer/core/src/transactions/key_manager/error.rs b/base_layer/core/src/transactions/key_manager/error.rs index ef92873c28..c26a7f618d 100644 --- a/base_layer/core/src/transactions/key_manager/error.rs +++ b/base_layer/core/src/transactions/key_manager/error.rs @@ -20,8 +20,10 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +use serde::{Deserialize, Serialize}; use tari_crypto::signatures::CommitmentAndPublicKeySignatureError; use tari_key_manager::error::KeyManagerError; +use tari_utilities::ByteArrayError; use thiserror::Error; use crate::transactions::transaction_components::TransactionError; @@ -41,3 +43,35 @@ impl From for CoreKeyManagerError { CoreKeyManagerError::CommitmentAndPublicKeySignatureError(err.to_string()) } } + +/// Ledger device errors. +#[derive(Debug, PartialEq, Error, Deserialize, Serialize, Clone, Eq)] +pub enum LedgerDeviceError { + /// HID API error + #[error("HID API error `{0}`")] + HidApi(String), + /// Native HID transport error + #[error("Native HID transport error `{0}`")] + NativeTransport(String), + /// Ledger application not started + #[error("Ledger application not started")] + ApplicationNotStarted, + /// Ledger application instruction error + #[error("Ledger application instruction error `{0}`")] + Instruction(String), + /// Ledger application processing error + #[error("Processing error `{0}`")] + Processing(String), + /// Conversion error to or from ledger + #[error("Conversion failed: {0}")] + ByteArrayError(String), + /// Not yet supported + #[error("Ledger is not fully supported")] + NotSupported, +} + +impl From for LedgerDeviceError { + fn from(e: ByteArrayError) -> Self { + LedgerDeviceError::ByteArrayError(e.to_string()) + } +} diff --git a/base_layer/core/src/transactions/key_manager/initializer.rs b/base_layer/core/src/transactions/key_manager/initializer.rs index 34a0434156..55c6824cbe 100644 --- a/base_layer/core/src/transactions/key_manager/initializer.rs +++ b/base_layer/core/src/transactions/key_manager/initializer.rs @@ -28,7 +28,7 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use tari_common_types::types::PublicKey; +use tari_common_types::{types::PublicKey, wallet_types::WalletType}; use tari_key_manager::{ cipher_seed::CipherSeed, key_manager_service::storage::database::{KeyManagerBackend, KeyManagerDatabase}, @@ -44,17 +44,24 @@ where T: KeyManagerBackend backend: Option, master_seed: CipherSeed, crypto_factories: CryptoFactories, + wallet_type: WalletType, } impl TransactionKeyManagerInitializer where T: KeyManagerBackend + 'static { /// Creates a new [TransactionKeyManagerInitializer] from the provided [KeyManagerBackend] and [CipherSeed] - pub fn new(backend: T, master_seed: CipherSeed, crypto_factories: CryptoFactories) -> Self { + pub fn new( + backend: T, + master_seed: CipherSeed, + crypto_factories: CryptoFactories, + wallet_type: WalletType, + ) -> Self { Self { backend: Some(backend), master_seed, crypto_factories, + wallet_type, } } } @@ -73,6 +80,7 @@ where T: KeyManagerBackend + 'static self.master_seed.clone(), KeyManagerDatabase::new(backend), self.crypto_factories.clone(), + self.wallet_type, )?; context.register_handle(key_manager); diff --git a/base_layer/core/src/transactions/key_manager/inner.rs b/base_layer/core/src/transactions/key_manager/inner.rs index 85ad670052..94bbe955c2 100644 --- a/base_layer/core/src/transactions/key_manager/inner.rs +++ b/base_layer/core/src/transactions/key_manager/inner.rs @@ -19,14 +19,25 @@ // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#[cfg(feature = "ledger")] +use std::sync::{Arc, Mutex}; use std::{collections::HashMap, ops::Shl}; use blake2::Blake2b; use digest::consts::U64; +#[cfg(feature = "ledger")] +use ledger_transport::APDUCommand; +#[cfg(feature = "ledger")] +use ledger_transport_hid::TransportNativeHID; use log::*; +#[cfg(feature = "ledger")] +use once_cell::sync::Lazy; use rand::rngs::OsRng; use strum::IntoEnumIterator; -use tari_common_types::types::{ComAndPubSignature, Commitment, PrivateKey, PublicKey, RangeProof, Signature}; +use tari_common_types::{ + types::{ComAndPubSignature, Commitment, PrivateKey, PublicKey, RangeProof, Signature}, + wallet_types::WalletType, +}; use tari_comms::types::CommsDHKE; use tari_crypto::{ commitment::{ExtensionDegree, HomomorphicCommitmentFactory}, @@ -54,33 +65,32 @@ use tari_key_manager::{ use tari_utilities::{hex::Hex, ByteArray}; use tokio::sync::RwLock; -use crate::{ - one_sided::diffie_hellman_stealth_domain_hasher, - transactions::{ - transaction_components::{KernelFeatures, TransactionError, TransactionInput, TransactionInputVersion}, - CryptoFactories, - }, -}; - const LOG_TARGET: &str = "key_manager::key_manager_service"; const KEY_MANAGER_MAX_SEARCH_DEPTH: u64 = 1_000_000; use crate::{ common::ConfidentialOutputHasher, + one_sided::diffie_hellman_stealth_domain_hasher, transactions::{ key_manager::{ interface::{TransactionKeyManagerBranch, TxoStage}, + LedgerDeviceError, TariKeyId, }, tari_amount::MicroMinotari, transaction_components::{ EncryptedData, + KernelFeatures, RangeProofType, + TransactionError, + TransactionInput, + TransactionInputVersion, TransactionKernel, TransactionKernelVersion, TransactionOutput, TransactionOutputVersion, }, + CryptoFactories, }, }; @@ -95,8 +105,12 @@ pub struct TransactionKeyManagerInner { db: KeyManagerDatabase, master_seed: CipherSeed, crypto_factories: CryptoFactories, + wallet_type: WalletType, } +#[cfg(feature = "ledger")] +pub static TRANSPORT: Lazy>>> = Lazy::new(|| Arc::new(Mutex::new(None))); + impl TransactionKeyManagerInner where TBackend: KeyManagerBackend + 'static { @@ -108,12 +122,14 @@ where TBackend: KeyManagerBackend + 'static master_seed: CipherSeed, db: KeyManagerDatabase, crypto_factories: CryptoFactories, + wallet_type: WalletType, ) -> Result { let mut km = TransactionKeyManagerInner { key_managers: HashMap::new(), db, master_seed, crypto_factories, + wallet_type, }; km.add_standard_core_branches()?; Ok(km) @@ -429,6 +445,46 @@ where TBackend: KeyManagerBackend + 'static // Transaction input section (transactions > transaction_components > transaction_input) // ----------------------------------------------------------------------------------------------------------------- + pub async fn get_script_private_key(&self, script_key_id: &TariKeyId) -> Result { + match self.wallet_type { + WalletType::Software => self.get_private_key(script_key_id).await.map_err(|e| e.into()), + WalletType::Ledger(_account) => { + #[cfg(not(feature = "ledger"))] + return Err(TransactionError::LedgerDeviceError(LedgerDeviceError::NotSupported)); + + #[cfg(feature = "ledger")] + { + let data = script_key_id.managed_index().expect("and index").to_le_bytes().to_vec(); + let command = APDUCommand { + cla: 0x80, + ins: 0x02, // GetPrivateKey - see `./applications/mp_ledger/src/main.rs/Instruction` + p1: 0x00, + p2: 0x00, + data, + }; + let binding = TRANSPORT.lock().expect("lock exists"); + let transport = binding.as_ref().expect("transport exists"); + match transport.exchange(&command) { + Ok(result) => { + if result.data().len() < 33 { + return Err(LedgerDeviceError::Processing(format!( + "'get_private_key' insufficient data - expected 33 got {} bytes ({:?})", + result.data().len(), + result + )) + .into()); + } + PrivateKey::from_canonical_bytes(&result.data()[1..33]) + .map_err(|e| TransactionError::InvalidSignatureError(e.to_string())) + }, + Err(e) => Err(LedgerDeviceError::Instruction(format!("GetPrivateKey: {}", e)).into()), + } + } + // end script private key + }, + } + } + pub async fn get_script_signature( &self, script_key_id: &TariKeyId, @@ -443,8 +499,8 @@ where TBackend: KeyManagerBackend + 'static let ephemeral_commitment = self.crypto_factories.commitment.commit(&r_x, &r_a); let ephemeral_pubkey = PublicKey::from_secret_key(&r_y); let commitment = self.get_commitment(spend_key_id, value).await?; - let script_private_key = self.get_private_key(script_key_id).await?; let spend_private_key = self.get_private_key(spend_key_id).await?; + let script_private_key = self.get_script_private_key(script_key_id).await?; let challenge = TransactionInput::finalize_script_signature_challenge( txi_version, diff --git a/base_layer/core/src/transactions/key_manager/memory_db_key_manager.rs b/base_layer/core/src/transactions/key_manager/memory_db_key_manager.rs index 35e6776036..d5c35fe2a6 100644 --- a/base_layer/core/src/transactions/key_manager/memory_db_key_manager.rs +++ b/base_layer/core/src/transactions/key_manager/memory_db_key_manager.rs @@ -25,6 +25,7 @@ use std::{iter, mem::size_of}; use chacha20poly1305::{Key, KeyInit, XChaCha20Poly1305}; use rand::{distributions::Alphanumeric, rngs::OsRng, Rng, RngCore}; use tari_common_sqlite::connection::{DbConnection, DbConnectionUrl}; +use tari_common_types::wallet_types::WalletType; use tari_key_manager::{ cipher_seed::CipherSeed, key_manager_service::storage::{database::KeyManagerDatabase, sqlite_db::KeyManagerSqliteDatabase}, @@ -50,11 +51,13 @@ pub fn create_memory_db_key_manager_with_range_proof_size(size: usize) -> Memory let key_ga = Key::from_slice(&key); let db_cipher = XChaCha20Poly1305::new(key_ga); let factory = CryptoFactories::new(size); + let wallet_type = WalletType::Software; TransactionKeyManagerWrapper::>::new( cipher, KeyManagerDatabase::new(KeyManagerSqliteDatabase::init(connection, db_cipher)), factory, + wallet_type, ) .unwrap() } diff --git a/base_layer/core/src/transactions/key_manager/mod.rs b/base_layer/core/src/transactions/key_manager/mod.rs index 2d803f06f0..ec8033b7df 100644 --- a/base_layer/core/src/transactions/key_manager/mod.rs +++ b/base_layer/core/src/transactions/key_manager/mod.rs @@ -46,4 +46,4 @@ pub use memory_db_key_manager::{ }; mod error; -pub use error::CoreKeyManagerError; +pub use error::{CoreKeyManagerError, LedgerDeviceError}; diff --git a/base_layer/core/src/transactions/key_manager/wrapper.rs b/base_layer/core/src/transactions/key_manager/wrapper.rs index 2823c55db2..9e316892e0 100644 --- a/base_layer/core/src/transactions/key_manager/wrapper.rs +++ b/base_layer/core/src/transactions/key_manager/wrapper.rs @@ -24,7 +24,10 @@ use std::sync::Arc; use blake2::Blake2b; use digest::consts::U64; -use tari_common_types::types::{ComAndPubSignature, Commitment, PrivateKey, PublicKey, RangeProof, Signature}; +use tari_common_types::{ + types::{ComAndPubSignature, Commitment, PrivateKey, PublicKey, RangeProof, Signature}, + wallet_types::WalletType, +}; use tari_comms::types::CommsDHKE; use tari_crypto::{hashing::DomainSeparatedHash, ristretto::RistrettoComSig}; use tari_key_manager::{ @@ -80,12 +83,14 @@ where TBackend: KeyManagerBackend + 'static master_seed: CipherSeed, db: KeyManagerDatabase, crypto_factories: CryptoFactories, + wallet_type: WalletType, ) -> Result { Ok(TransactionKeyManagerWrapper { transaction_key_manager_inner: Arc::new(RwLock::new(TransactionKeyManagerInner::new( master_seed, db, crypto_factories, + wallet_type, )?)), }) } diff --git a/base_layer/core/src/transactions/transaction_components/error.rs b/base_layer/core/src/transactions/transaction_components/error.rs index f7bf4d72a4..f2d4b84b34 100644 --- a/base_layer/core/src/transactions/transaction_components/error.rs +++ b/base_layer/core/src/transactions/transaction_components/error.rs @@ -32,7 +32,7 @@ use tari_key_manager::key_manager_service::KeyManagerServiceError; use tari_script::ScriptError; use thiserror::Error; -use crate::transactions::transaction_components::EncryptedDataError; +use crate::transactions::{key_manager::LedgerDeviceError, transaction_components::EncryptedDataError}; //---------------------------------------- TransactionError ----------------------------------------------------// #[derive(Clone, Debug, PartialEq, Error, Deserialize, Serialize, Eq)] @@ -73,6 +73,8 @@ pub enum TransactionError { KeyManagerError(String), #[error("EncryptedData error: {0}")] EncryptedDataError(String), + #[error("Ledger device error: {0}")] + LedgerDeviceError(#[from] LedgerDeviceError), } impl From for TransactionError { diff --git a/base_layer/wallet/src/config.rs b/base_layer/wallet/src/config.rs index 813b323d01..2d8a7ef0eb 100644 --- a/base_layer/wallet/src/config.rs +++ b/base_layer/wallet/src/config.rs @@ -32,7 +32,7 @@ use tari_common::{ configuration::{serializers, Network, StringList}, SubConfigPath, }; -use tari_common_types::grpc_authentication::GrpcAuthentication; +use tari_common_types::{grpc_authentication::GrpcAuthentication, wallet_types::WalletType}; use tari_comms::multiaddr::Multiaddr; use tari_p2p::P2pConfig; use tari_utilities::SafePassword; @@ -118,6 +118,8 @@ pub struct WalletConfig { pub use_libtor: bool, /// A path to the file that stores the base node identity and secret key pub identity_file: Option, + /// The type of wallet software, or specific type of hardware + pub wallet_type: Option, } impl Default for WalletConfig { @@ -156,6 +158,7 @@ impl Default for WalletConfig { num_required_confirmations: 3, use_libtor: true, identity_file: None, + wallet_type: None, } } } diff --git a/base_layer/wallet/src/storage/database.rs b/base_layer/wallet/src/storage/database.rs index cdca0cd43a..f38e3c90ca 100644 --- a/base_layer/wallet/src/storage/database.rs +++ b/base_layer/wallet/src/storage/database.rs @@ -27,7 +27,7 @@ use std::{ use chrono::NaiveDateTime; use log::*; -use tari_common_types::chain_metadata::ChainMetadata; +use tari_common_types::{chain_metadata::ChainMetadata, wallet_types::WalletType}; use tari_comms::{ multiaddr::Multiaddr, peer_manager::{IdentitySignature, PeerFeatures}, @@ -90,6 +90,7 @@ pub enum DbKey { WalletBirthday, LastAccessedNetwork, LastAccessedVersion, + WalletType, } impl DbKey { @@ -109,6 +110,7 @@ impl DbKey { DbKey::CommsIdentitySignature => "CommsIdentitySignature".to_string(), DbKey::LastAccessedNetwork => "LastAccessedNetwork".to_string(), DbKey::LastAccessedVersion => "LastAccessedVersion".to_string(), + DbKey::WalletType => "WalletType".to_string(), } } } @@ -129,6 +131,7 @@ pub enum DbValue { WalletBirthday(String), LastAccessedNetwork(String), LastAccessedVersion(String), + WalletType(WalletType), } #[derive(Clone)] @@ -141,6 +144,7 @@ pub enum DbKeyValuePair { CommsFeatures(PeerFeatures), CommsIdentitySignature(Box), NetworkAndVersion((String, String)), + WalletType(WalletType), } pub enum WriteOperation { @@ -384,6 +388,21 @@ where T: WalletBackend + 'static pub fn delete_burnt_proof(&self, id: u32) -> Result<(), WalletStorageError> { self.db.delete_burnt_proof(id) } + + pub fn get_wallet_type(&self) -> Result, WalletStorageError> { + match self.db.fetch(&DbKey::WalletType) { + Ok(None) => Ok(None), + Ok(Some(DbValue::WalletType(k))) => Ok(Some(k)), + Ok(Some(other)) => unexpected_result(DbKey::WalletType, other), + Err(e) => log_error(DbKey::WalletType, e), + } + } + + pub fn set_wallet_type(&self, wallet_type: WalletType) -> Result<(), WalletStorageError> { + self.db + .write(WriteOperation::Insert(DbKeyValuePair::WalletType(wallet_type)))?; + Ok(()) + } } impl Display for DbValue { @@ -404,6 +423,7 @@ impl Display for DbValue { DbValue::CommsIdentitySignature(_) => f.write_str("CommsIdentitySignature"), DbValue::LastAccessedNetwork(network) => f.write_str(&format!("LastAccessedNetwork: {}", network)), DbValue::LastAccessedVersion(version) => f.write_str(&format!("LastAccessedVersion: {}", version)), + DbValue::WalletType(wallet_type) => f.write_str(&format!("WalletType: {:?}", wallet_type)), } } } diff --git a/base_layer/wallet/src/storage/sqlite_db/wallet.rs b/base_layer/wallet/src/storage/sqlite_db/wallet.rs index 659d5eebe6..5be73d2888 100644 --- a/base_layer/wallet/src/storage/sqlite_db/wallet.rs +++ b/base_layer/wallet/src/storage/sqlite_db/wallet.rs @@ -422,6 +422,11 @@ impl WalletSqliteDatabase { WalletSettingSql::new(DbKey::LastAccessedNetwork, network).set(&mut conn)?; WalletSettingSql::new(DbKey::LastAccessedVersion, version).set(&mut conn)?; }, + DbKeyValuePair::WalletType(wallet_type) => { + kvp_text = "WalletType"; + WalletSettingSql::new(DbKey::WalletType, serde_json::to_string(&wallet_type).unwrap()) + .set(&mut conn)?; + }, } if start.elapsed().as_millis() > 0 { @@ -461,6 +466,7 @@ impl WalletSqliteDatabase { DbKey::SecondaryKeySalt | DbKey::SecondaryKeyHash | DbKey::WalletBirthday | + DbKey::WalletType | DbKey::CommsIdentitySignature | DbKey::LastAccessedNetwork | DbKey::LastAccessedVersion => { @@ -510,6 +516,9 @@ impl WalletBackend for WalletSqliteDatabase { DbKey::SecondaryKeySalt => WalletSettingSql::get(key, &mut conn)?.map(DbValue::SecondaryKeySalt), DbKey::SecondaryKeyHash => WalletSettingSql::get(key, &mut conn)?.map(DbValue::SecondaryKeyHash), DbKey::WalletBirthday => WalletSettingSql::get(key, &mut conn)?.map(DbValue::WalletBirthday), + DbKey::WalletType => { + WalletSettingSql::get(key, &mut conn)?.map(|d| DbValue::WalletType(serde_json::from_str(&d).unwrap())) + }, DbKey::LastAccessedNetwork => WalletSettingSql::get(key, &mut conn)?.map(DbValue::LastAccessedNetwork), DbKey::LastAccessedVersion => WalletSettingSql::get(key, &mut conn)?.map(DbValue::LastAccessedVersion), DbKey::CommsIdentitySignature => WalletSettingSql::get(key, &mut conn)? diff --git a/base_layer/wallet/src/wallet.rs b/base_layer/wallet/src/wallet.rs index 998392bc8d..9728957771 100644 --- a/base_layer/wallet/src/wallet.rs +++ b/base_layer/wallet/src/wallet.rs @@ -32,6 +32,7 @@ use tari_common_types::{ tari_address::TariAddress, transaction::{ImportStatus, TxId}, types::{ComAndPubSignature, Commitment, PrivateKey, PublicKey, SignatureWithDomain}, + wallet_types::WalletType, }; use tari_comms::{ multiaddr::{Error as MultiaddrError, Multiaddr}, @@ -165,6 +166,7 @@ where key_manager_backend: TKeyManagerBackend, shutdown_signal: ShutdownSignal, master_seed: CipherSeed, + wallet_type: WalletType, ) -> Result { let buf_size = cmp::max(WALLET_BUFFER_MIN_SIZE, config.buffer_size); let (publisher, subscription_factory) = pubsub_connector(buf_size); @@ -203,6 +205,7 @@ where key_manager_backend, master_seed, factories.clone(), + wallet_type, )) .add_initializer(TransactionServiceInitializer::::new( config.transaction_service_config, @@ -761,6 +764,25 @@ pub fn read_or_create_master_seed( Ok(master_seed) } +pub fn read_or_create_wallet_type( + wallet_type: Option, + db: &WalletDatabase, +) -> Result { + let db_wallet_type = db.get_wallet_type()?; + + match (db_wallet_type, wallet_type) { + (None, None) => { + panic!("Something is very wrong, no wallet type was found in the DB, or provided (on first run)") + }, + (Some(_), Some(_)) => panic!("Something is very wrong we have a wallet type from the DB and on first run"), + (None, Some(t)) => { + db.set_wallet_type(t)?; + Ok(t) + }, + (Some(t), None) => Ok(t), + } +} + pub fn derive_comms_secret_key(master_seed: &CipherSeed) -> Result { let comms_key_manager = KeyManager::::from( master_seed.clone(), diff --git a/base_layer/wallet/tests/transaction_service_tests/service.rs b/base_layer/wallet/tests/transaction_service_tests/service.rs index 188e8674f9..5a25872894 100644 --- a/base_layer/wallet/tests/transaction_service_tests/service.rs +++ b/base_layer/wallet/tests/transaction_service_tests/service.rs @@ -91,6 +91,7 @@ use tari_common_types::{ tari_address::TariAddress, transaction::{ImportStatus, TransactionDirection, TransactionStatus, TxId}, types::{FixedHash, PrivateKey, PublicKey, Signature}, + wallet_types::WalletType, }; use tari_comms::{ message::EnvelopeBody, @@ -241,6 +242,7 @@ async fn setup_transaction_service>( kms_backend, cipher, factories.clone(), + WalletType::Software, )) .add_initializer(TransactionServiceInitializer::<_, _, MemoryDbKeyManager>::new( TransactionServiceConfig { diff --git a/base_layer/wallet_ffi/src/lib.rs b/base_layer/wallet_ffi/src/lib.rs index f6cac76020..8fb30829e3 100644 --- a/base_layer/wallet_ffi/src/lib.rs +++ b/base_layer/wallet_ffi/src/lib.rs @@ -123,6 +123,7 @@ use tari_common_types::{ tari_address::{TariAddress, TariAddressError}, transaction::{TransactionDirection, TransactionStatus, TxId}, types::{ComAndPubSignature, Commitment, PublicKey, SignatureWithDomain}, + wallet_types::WalletType, }; use tari_comms::{ multiaddr::Multiaddr, @@ -5528,6 +5529,7 @@ pub unsafe extern "C" fn wallet_create( key_manager_backend, shutdown.to_signal(), master_seed, + WalletType::Software, )); match w { diff --git a/infrastructure/derive/Cargo.toml b/infrastructure/derive/Cargo.toml index d24688c14e..62040fd737 100644 --- a/infrastructure/derive/Cargo.toml +++ b/infrastructure/derive/Cargo.toml @@ -6,7 +6,7 @@ repository = "https://github.com/tari-project/tari" homepage = "https://tari.com" readme = "README.md" license = "BSD-3-Clause" -version = "1.0.0-pre.11a +version = "1.0.0-pre.11a" edition = "2018" [lib] @@ -15,4 +15,4 @@ proc-macro = true [dependencies] quote = "0.6.11" syn = "0.15.29" -proc-macro2 = "0.4.27" +proc-macro2 = "0.4.27" diff --git a/scripts/install_ubuntu_dependencies.sh b/scripts/install_ubuntu_dependencies.sh index 2bb35cf3b7..d3113b6930 100755 --- a/scripts/install_ubuntu_dependencies.sh +++ b/scripts/install_ubuntu_dependencies.sh @@ -20,4 +20,5 @@ apt-get install --no-install-recommends --assume-yes \ protobuf-compiler \ libncurses5-dev \ libncursesw5-dev \ + libudev-dev \ zip