Skip to content

Commit

Permalink
feat: ledger recovery (#6383)
Browse files Browse the repository at this point in the history
Description
---
Utilize the view key from the ledger to perform recovery on a wallet,
and support one-sided transactions.

Motivation and Context
---
Allow recovery.

How Has This Been Tested?
---
Manually

- [x] Software -> Software
    - [x] One-sided
    - [x] Interactive
- [x] Ledger -> Software
    - [x] One-sided
    - [x] Interactive
- [x] Ledger -> Ledger
    - [x] Interactive
    - [x] One-sided

- [x] Software recovery
- [x] Ledger recovery

What process can a PR reviewer use to test or verify this change?
---
Please run a manual test between wallets.

Breaking Changes
---

- [ ] None
- [x] Requires data directory on base node to be deleted
- [x] Requires hard fork
- [ ] Other - Please specify

---------

Co-authored-by: SW van Heerden <[email protected]>
  • Loading branch information
brianp and SWvheerden authored Jul 2, 2024
1 parent 29f618e commit fb2de35
Show file tree
Hide file tree
Showing 39 changed files with 420 additions and 193 deletions.
4 changes: 2 additions & 2 deletions Cargo.lock

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

6 changes: 3 additions & 3 deletions applications/minotari_app_grpc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ version = "1.0.0-pre.14"
edition = "2018"

[dependencies]
tari_common_types = { path = "../../base_layer/common_types" }
tari_common_types = { path = "../../base_layer/common_types" }
tari_comms = { path = "../../comms/core" }
tari_core = { path = "../../base_layer/core" }
tari_crypto = { version = "0.20.1" }
tari_crypto = { version = "0.20.2" }
tari_script = { path = "../../infrastructure/tari_script" }
tari_utilities = { version = "0.7" }

Expand All @@ -27,7 +27,7 @@ rcgen = "0.11.3"
subtle = "2.5.0"
thiserror = "1"
tokio = { version = "1.36", features = ["fs"] }
tonic = { version = "0.8.3", features = ["tls"]}
tonic = { version = "0.8.3", features = ["tls"] }
zeroize = "1"

[build-dependencies]
Expand Down
2 changes: 1 addition & 1 deletion applications/minotari_console_wallet/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ tari_common_types = { path = "../../base_layer/common_types" }
tari_comms = { path = "../../comms/core" }
tari_comms_dht = { path = "../../comms/dht" }
tari_contacts = { path = "../../base_layer/contacts" }
tari_crypto = { version = "0.20.1" }
tari_crypto = { version = "0.20.2" }
tari_key_manager = { path = "../../base_layer/key_manager" }
tari_libtor = { path = "../../infrastructure/libtor", optional = true }
tari_p2p = { path = "../../base_layer/p2p", features = ["auto-update"] }
Expand Down
78 changes: 52 additions & 26 deletions applications/minotari_console_wallet/src/init/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -431,22 +431,7 @@ pub async fn init_wallet(

let master_seed = read_or_create_master_seed(recovery_seed.clone(), &wallet_db)?;

let node_identity = match config.wallet.identity_file.as_ref() {
Some(identity_file) => {
warn!(
target: LOG_TARGET,
"Node identity overridden by file {}",
identity_file.to_string_lossy()
);
setup_node_identity(
identity_file,
node_addresses.to_vec(),
true,
PeerFeatures::COMMUNICATION_CLIENT,
)?
},
None => setup_identity_from_db(&wallet_db, &master_seed, node_addresses.to_vec())?,
};
let node_identity = setup_identity_from_db(&wallet_db, &master_seed, node_addresses.to_vec())?;

let mut wallet_config = config.wallet.clone();
if let TransportType::Tor = config.wallet.p2p.transport.transport_type {
Expand Down Expand Up @@ -832,19 +817,25 @@ pub fn prompt_wallet_type(
}

match boot_mode {
WalletBoot::New => {
WalletBoot::New | WalletBoot::Recovery => {
#[cfg(not(feature = "ledger"))]
return Some(WalletType::default());

#[cfg(feature = "ledger")]
{
if prompt("\r\nWould you like to use a connected hardware wallet? (Supported types: Ledger) (Y/n)") {
let connected_hardware_msg = match boot_mode {
WalletBoot::Recovery => {
"\r\nWas your wallet connected to a hardware device? (Supported types: Ledger) (Y/n)"
},
_ => "\r\nWould you like to use a connected hardware wallet? (Supported types: Ledger) (Y/n)",
};
if prompt(connected_hardware_msg) {
print!("Scanning for connected Ledger hardware device... ");
match get_transport() {
Ok(hid) => {
println!("Device found.");
let account = prompt_ledger_account().expect("An account value");
let ledger = LedgerWallet::new(account, wallet_config.network, None);
let account = prompt_ledger_account(boot_mode).expect("An account value");
let ledger = LedgerWallet::new(account, wallet_config.network, None, None);
match ledger
.build_command(Instruction::GetPublicAlpha, vec![])
.execute_with_transport(&hid)
Expand All @@ -860,13 +851,43 @@ pub fn prompt_wallet_type(
);
}

let key = match PublicKey::from_canonical_bytes(&result.data()[1..33]) {
let public_alpha = match PublicKey::from_canonical_bytes(&result.data()[1..33]) {
Ok(k) => k,
Err(e) => panic!("{}", e),
};

let ledger = LedgerWallet::new(account, wallet_config.network, Some(key));
Some(WalletType::Ledger(ledger))
match ledger
.build_command(Instruction::GetViewKey, vec![])
.execute_with_transport(&hid)
{
Ok(result) => {
debug!(target: LOG_TARGET, "result length: {}, data: {:?}", result.data().len(), result.data());
if result.data().len() < 33 {
debug!(target: LOG_TARGET, "result less than 33");
panic!(
"'get_view_key' insufficient data - expected 33 got {} bytes \
({:?})",
result.data().len(),
result
);
}

let view_key = match PrivateKey::from_canonical_bytes(&result.data()[1..33])
{
Ok(k) => k,
Err(e) => panic!("{}", e),
};

let ledger = LedgerWallet::new(
account,
wallet_config.network,
Some(public_alpha),
Some(view_key),
);
Some(WalletType::Ledger(ledger))
},
Err(e) => panic!("{}", e),
}
},
Err(e) => panic!("{}", e),
}
Expand All @@ -882,9 +903,14 @@ pub fn prompt_wallet_type(
}
}

pub fn prompt_ledger_account() -> Option<u64> {
let question =
"\r\nPlease enter an account number for your ledger. A simple 1-9, easily remembered numbers are suggested.";
pub fn prompt_ledger_account(boot_mode: WalletBoot) -> Option<u64> {
let question = match boot_mode {
WalletBoot::Recovery => "\r\nPlease enter the account number you previously used for your device.",
_ => {
"\r\nPlease enter an account number for your device. A simple 1-9, easily remembered numbers are suggested."
},
};

println!("{}", question);
let mut input = "".to_string();
io::stdin().read_line(&mut input).unwrap();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,14 +98,13 @@ impl SendTab {
Span::raw(" field, "),
Span::styled("A", Style::default().add_modifier(Modifier::BOLD)),
Span::raw(" to edit "),
Span::styled("Amount/Token", Style::default().add_modifier(Modifier::BOLD)),
Span::raw(", "),
Span::styled("Amount/Token, ", Style::default().add_modifier(Modifier::BOLD)),
Span::styled("F", Style::default().add_modifier(Modifier::BOLD)),
Span::raw(" to edit "),
Span::styled("Fee-Per-Gram", Style::default().add_modifier(Modifier::BOLD)),
Span::raw(" field, "),
Span::styled("C", Style::default().add_modifier(Modifier::BOLD)),
Span::raw(" to select a contact."),
Span::raw(" to select a contact, "),
Span::styled("P", Style::default().add_modifier(Modifier::BOLD)),
Span::raw(" to edit "),
Span::styled("Payment-id", Style::default().add_modifier(Modifier::BOLD)),
Expand Down
2 changes: 1 addition & 1 deletion applications/minotari_ledger_wallet/comms/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ license = "BSD-3-Clause"
edition = "2021"

[dependencies]
tari_crypto = { version = "0.20.0", default-features = false }
tari_crypto = { version = "0.20.2", default-features = false }

ledger-transport = { git = "https://github.com/Zondax/ledger-rs", rev = "20e2a20" }
ledger-transport-hid = { git = "https://github.com/Zondax/ledger-rs", rev = "20e2a20" }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ pub enum Instruction {
GetScriptOffset = 0x06,
GetMetadataSignature = 0x07,
GetScriptSignatureFromChallenge = 0x08,
GetViewKey = 0x09,
GetDHSharedSecret = 0x10,
}

impl Instruction {
Expand Down
6 changes: 3 additions & 3 deletions applications/minotari_ledger_wallet/wallet/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,17 @@ license = "BSD-3-Clause"
edition = "2021"

[dependencies]
tari_crypto = { version = "0.20.1", default-features = false, features = ["borsh"]}
tari_crypto = { version = "0.20.2", default-features = false, features = ["borsh"] }
tari_hashing = { path = "../../../hashing", version = "1.0.0-pre.14" }

blake2 = { version = "0.10", default-features = false }
blake2 = { version = "0.10", default-features = false }
borsh = { version = "1.2", default-features = false }
critical-section = { version = "1.1.1" }
digest = { version = "0.10", default-features = false }
embedded-alloc = "0.5.0"
include_gif = "1.0.1"
ledger_device_sdk = "1.7.1"
zeroize = { version = "1" , default-features = false }
zeroize = { version = "1", default-features = false }

# once_cell defined here just to lock the version. Other dependencies may try to go to 1.19 which is incompatabile with
# ledger at this time. 1.19 removes "atomic-polyfill" and replaces it with "portable-atomic" which can not build due to
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Copyright 2024 The Tari Project
// SPDX-License-Identifier: BSD-3-Clause

use core::ops::Deref;

use ledger_device_sdk::io::Comm;
use tari_crypto::{ristretto::RistrettoPublicKey, tari_utilities::ByteArray};

use crate::{
utils::{derive_from_bip32_key, get_key_from_canonical_bytes},
AppSW,
KeyType,
RESPONSE_VERSION,
};

pub fn handler_get_dh_shared_secret(comm: &mut Comm) -> Result<(), AppSW> {
let data = comm.get_data().map_err(|_| AppSW::WrongApduLength)?;

let mut account_bytes = [0u8; 8];
account_bytes.clone_from_slice(&data[0..8]);
let account = u64::from_le_bytes(account_bytes);

let mut index_bytes = [0u8; 8];
index_bytes.clone_from_slice(&data[8..16]);
let index = u64::from_le_bytes(index_bytes);

let mut key_bytes = [0u8; 8];
key_bytes.clone_from_slice(&data[16..24]);
let key_int = u64::from_le_bytes(key_bytes);
let key = KeyType::from_branch_key(key_int);

let public_key: RistrettoPublicKey = get_key_from_canonical_bytes(&data[24..56])?;

let shared_secret_key = match derive_from_bip32_key(account, index, key) {
Ok(k) => k.deref() * public_key,
Err(e) => return Err(e),
};

comm.append(&[RESPONSE_VERSION]); // version
comm.append(shared_secret_key.as_bytes());
comm.reply_ok();

Ok(())
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@

use alloc::format;

use ledger_device_sdk::{io::Comm, random::Random, ui::gadgets::SingleMessage};
use ledger_device_sdk::{io::Comm, ui::gadgets::SingleMessage};
use tari_crypto::{
commitment::HomomorphicCommitmentFactory,
keys::PublicKey,
ristretto::{
pedersen::{extended_commitment_factory::ExtendedPedersenCommitmentFactory, PedersenCommitment},
Expand Down Expand Up @@ -36,14 +35,14 @@ pub fn handler_get_script_signature_from_challenge(comm: &mut Comm) -> Result<()
let blinding_factor: Zeroizing<RistrettoSecretKey> =
get_key_from_canonical_bytes::<RistrettoSecretKey>(&data[8..40])?.into();
let script_private_key = alpha_hasher(alpha, blinding_factor)?;
let script_public_key = RistrettoPublicKey::from_secret_key(&script_private_key);
let _script_public_key = RistrettoPublicKey::from_secret_key(&script_private_key);

let value: Zeroizing<RistrettoSecretKey> =
get_key_from_canonical_bytes::<RistrettoSecretKey>(&data[40..72])?.into();
let spend_private_key: Zeroizing<RistrettoSecretKey> =
get_key_from_canonical_bytes::<RistrettoSecretKey>(&data[72..104])?.into();

let commitment: PedersenCommitment = get_key_from_canonical_bytes(&data[104..136])?;
let _commitment: PedersenCommitment = get_key_from_canonical_bytes(&data[104..136])?;

let mut challenge = [0u8; 64];
challenge.clone_from_slice(&data[136..200]);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Copyright 2024 The Tari Project
// SPDX-License-Identifier: BSD-3-Clause

use ledger_device_sdk::io::Comm;
use tari_crypto::tari_utilities::ByteArray;

use crate::{utils::derive_from_bip32_key, AppSW, KeyType, RESPONSE_VERSION, STATIC_VIEW_INDEX};

pub fn handler_get_view_key(comm: &mut Comm) -> Result<(), AppSW> {
let data = comm.get_data().map_err(|_| AppSW::WrongApduLength)?;

let mut account_bytes = [0u8; 8];
account_bytes.clone_from_slice(&data[0..8]);
let account = u64::from_le_bytes(account_bytes);

let p = match derive_from_bip32_key(account, STATIC_VIEW_INDEX, KeyType::ViewKey) {
Ok(k) => k,
Err(e) => return Err(e),
};

comm.append(&[RESPONSE_VERSION]); // version
comm.append(p.as_bytes());
comm.reply_ok();

Ok(())
}
Loading

0 comments on commit fb2de35

Please sign in to comment.