-
Notifications
You must be signed in to change notification settings - Fork 64
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Added the ability to get a public key for an account (#436)
Also resolves #371 ![Screenshot 2025-01-16 at 21 39 20](https://github.com/user-attachments/assets/8da87e89-c76f-4bb2-89a8-83945ce0d4c6) --------- Co-authored-by: FroVolod <[email protected]>
- Loading branch information
Showing
10 changed files
with
399 additions
and
19 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
114 changes: 114 additions & 0 deletions
114
src/commands/account/get_public_key/from_keychain/mod.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
use color_eyre::eyre::WrapErr; | ||
|
||
use crate::common::JsonRpcClientExt; | ||
use crate::common::RpcQueryResponseExt; | ||
|
||
#[derive(Debug, Clone, interactive_clap::InteractiveClap)] | ||
#[interactive_clap(input_context = crate::GlobalContext)] | ||
#[interactive_clap(output_context = PublicKeyFromKeychainContext)] | ||
pub struct PublicKeyFromKeychain { | ||
#[interactive_clap(skip_default_input_arg)] | ||
/// For which account do you need to view the public key? | ||
owner_account_id: crate::types::account_id::AccountId, | ||
#[interactive_clap(named_arg)] | ||
/// Select network | ||
network_config: crate::network::Network, | ||
} | ||
|
||
#[derive(Clone)] | ||
pub struct PublicKeyFromKeychainContext(crate::network::NetworkContext); | ||
|
||
impl PublicKeyFromKeychainContext { | ||
pub fn from_previous_context( | ||
previous_context: crate::GlobalContext, | ||
scope: &<PublicKeyFromKeychain as interactive_clap::ToInteractiveClapContextScope>::InteractiveClapContextScope, | ||
) -> color_eyre::eyre::Result<Self> { | ||
let account_id = scope.owner_account_id.clone(); | ||
|
||
let on_after_getting_network_callback: crate::network::OnAfterGettingNetworkCallback = | ||
std::sync::Arc::new({ | ||
move |network_config| { | ||
if previous_context.offline { | ||
eprintln!( | ||
"\nThe signer's public key cannot be verified and retrieved offline." | ||
); | ||
return Ok(()); | ||
} | ||
let service_name = std::borrow::Cow::Owned(format!( | ||
"near-{}-{}", | ||
network_config.network_name, &account_id | ||
)); | ||
|
||
let password = { | ||
let access_key_list = network_config | ||
.json_rpc_client() | ||
.blocking_call_view_access_key_list( | ||
&account_id.clone().into(), | ||
near_primitives::types::Finality::Final.into(), | ||
) | ||
.wrap_err_with(|| { | ||
format!("Failed to fetch access key list for {}", account_id) | ||
})? | ||
.access_key_list_view()?; | ||
|
||
let res = access_key_list | ||
.keys | ||
.into_iter() | ||
.filter(|key| { | ||
matches!( | ||
key.access_key.permission, | ||
near_primitives::views::AccessKeyPermissionView::FullAccess | ||
) | ||
}) | ||
.map(|key| key.public_key) | ||
.find_map(|public_key| { | ||
let keyring = keyring::Entry::new( | ||
&service_name, | ||
&format!("{}:{}", account_id, public_key), | ||
) | ||
.ok()?; | ||
keyring.get_password().ok() | ||
}); | ||
|
||
match res { | ||
Some(password) => password, | ||
None => { | ||
// no access keys found | ||
eprintln!("\nNo access keys found in keychain",); | ||
return Ok(()); | ||
} | ||
} | ||
}; | ||
|
||
let account_key_pair: crate::transaction_signature_options::AccountKeyPair = | ||
serde_json::from_str(&password).wrap_err("Error reading data")?; | ||
eprintln!("\nPublic key: {}", account_key_pair.public_key); | ||
|
||
Ok(()) | ||
} | ||
}); | ||
|
||
Ok(Self(crate::network::NetworkContext { | ||
config: previous_context.config, | ||
interacting_with_account_ids: vec![scope.owner_account_id.clone().into()], | ||
on_after_getting_network_callback, | ||
})) | ||
} | ||
} | ||
|
||
impl PublicKeyFromKeychain { | ||
pub fn input_owner_account_id( | ||
context: &crate::GlobalContext, | ||
) -> color_eyre::eyre::Result<Option<crate::types::account_id::AccountId>> { | ||
crate::common::input_signer_account_id_from_used_account_list( | ||
&context.config.credentials_home_dir, | ||
"For which account do you need to view the public key?", | ||
) | ||
} | ||
} | ||
|
||
impl From<PublicKeyFromKeychainContext> for crate::network::NetworkContext { | ||
fn from(item: PublicKeyFromKeychainContext) -> Self { | ||
item.0 | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
#[derive(Debug, Clone, interactive_clap::InteractiveClap)] | ||
#[interactive_clap(input_context = crate::GlobalContext)] | ||
#[interactive_clap(output_context = PublicKeyFromLedgerContext)] | ||
pub struct PublicKeyFromLedger { | ||
#[interactive_clap(long)] | ||
#[interactive_clap(skip_default_input_arg)] | ||
seed_phrase_hd_path: crate::types::slip10::BIP32Path, | ||
} | ||
|
||
#[derive(Debug, Clone)] | ||
pub struct PublicKeyFromLedgerContext {} | ||
|
||
impl PublicKeyFromLedgerContext { | ||
pub fn from_previous_context( | ||
_previous_context: crate::GlobalContext, | ||
scope: &<PublicKeyFromLedger as interactive_clap::ToInteractiveClapContextScope>::InteractiveClapContextScope, | ||
) -> color_eyre::eyre::Result<Self> { | ||
let seed_phrase_hd_path = scope.seed_phrase_hd_path.clone(); | ||
eprintln!("Opening the NEAR application... Please approve opening the application"); | ||
near_ledger::open_near_application().map_err(|ledger_error| { | ||
color_eyre::Report::msg(format!("An error happened while trying to open the NEAR application on the ledger: {ledger_error:?}")) | ||
})?; | ||
|
||
std::thread::sleep(std::time::Duration::from_secs(1)); | ||
|
||
eprintln!( | ||
"Please allow getting the PublicKey on Ledger device (HD Path: {})", | ||
seed_phrase_hd_path | ||
); | ||
let public_key = near_ledger::get_public_key(seed_phrase_hd_path.into()).map_err( | ||
|near_ledger_error| { | ||
color_eyre::Report::msg(format!( | ||
"An error occurred while trying to get PublicKey from Ledger device: {:?}", | ||
near_ledger_error | ||
)) | ||
}, | ||
)?; | ||
eprintln!( | ||
"\nPublic key: {}", | ||
near_crypto::PublicKey::ED25519(near_crypto::ED25519PublicKey::from( | ||
public_key.to_bytes(), | ||
)) | ||
); | ||
|
||
Ok(Self {}) | ||
} | ||
} | ||
|
||
impl PublicKeyFromLedger { | ||
pub fn input_seed_phrase_hd_path( | ||
_context: &crate::GlobalContext, | ||
) -> color_eyre::eyre::Result<Option<crate::types::slip10::BIP32Path>> { | ||
crate::transaction_signature_options::sign_with_ledger::input_seed_phrase_hd_path() | ||
} | ||
} |
133 changes: 133 additions & 0 deletions
133
src/commands/account/get_public_key/from_legacy_keychain/mod.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
use color_eyre::eyre::WrapErr; | ||
|
||
use crate::common::JsonRpcClientExt; | ||
use crate::common::RpcQueryResponseExt; | ||
|
||
#[derive(Debug, Clone, interactive_clap::InteractiveClap)] | ||
#[interactive_clap(input_context = crate::GlobalContext)] | ||
#[interactive_clap(output_context = PublicKeyFromLegacyKeychainContext)] | ||
pub struct PublicKeyFromKeychain { | ||
#[interactive_clap(skip_default_input_arg)] | ||
/// For which account do you need to view the public key? | ||
owner_account_id: crate::types::account_id::AccountId, | ||
#[interactive_clap(named_arg)] | ||
/// Select network | ||
network_config: crate::network::Network, | ||
} | ||
|
||
#[derive(Clone)] | ||
pub struct PublicKeyFromLegacyKeychainContext(crate::network::NetworkContext); | ||
|
||
impl PublicKeyFromLegacyKeychainContext { | ||
pub fn from_previous_context( | ||
previous_context: crate::GlobalContext, | ||
scope: &<PublicKeyFromKeychain as interactive_clap::ToInteractiveClapContextScope>::InteractiveClapContextScope, | ||
) -> color_eyre::eyre::Result<Self> { | ||
let config = previous_context.config.clone(); | ||
let account_id = scope.owner_account_id.clone(); | ||
|
||
let on_after_getting_network_callback: crate::network::OnAfterGettingNetworkCallback = | ||
std::sync::Arc::new({ | ||
move |network_config| { | ||
let keychain_folder = config | ||
.credentials_home_dir | ||
.join(&network_config.network_name); | ||
let signer_keychain_folder = keychain_folder.join(account_id.to_string()); | ||
let signer_access_key_file_path: std::path::PathBuf = { | ||
if previous_context.offline { | ||
eprintln!( | ||
"\nThe signer's public key cannot be verified and retrieved offline." | ||
); | ||
return Ok(()); | ||
} | ||
if signer_keychain_folder.exists() { | ||
let full_access_key_filenames = network_config | ||
.json_rpc_client() | ||
.blocking_call_view_access_key_list( | ||
&account_id.clone().into(), | ||
near_primitives::types::Finality::Final.into(), | ||
) | ||
.wrap_err_with(|| { | ||
format!( | ||
"Failed to fetch access KeyList for {}", | ||
account_id | ||
) | ||
})? | ||
.access_key_list_view()? | ||
.keys | ||
.iter() | ||
.filter( | ||
|access_key_info| match access_key_info.access_key.permission { | ||
near_primitives::views::AccessKeyPermissionView::FullAccess => true, | ||
near_primitives::views::AccessKeyPermissionView::FunctionCall { | ||
.. | ||
} => false, | ||
}, | ||
) | ||
.map(|access_key_info| { | ||
format!( | ||
"{}.json", | ||
access_key_info.public_key.to_string().replace(":", "_") | ||
) | ||
.into() | ||
}) | ||
.collect::<std::collections::HashSet<std::ffi::OsString>>(); | ||
|
||
signer_keychain_folder | ||
.read_dir() | ||
.wrap_err("There are no access keys found in the keychain for the signer account. Import an access key for an account before signing transactions with keychain.")? | ||
.filter_map(Result::ok) | ||
.find(|entry| full_access_key_filenames.contains(&entry.file_name())) | ||
.map(|signer_access_key| signer_access_key.path()) | ||
.unwrap_or_else(|| keychain_folder.join(format!( | ||
"{}.json", | ||
account_id | ||
))) | ||
} else { | ||
keychain_folder.join(format!("{}.json", account_id)) | ||
} | ||
}; | ||
let signer_access_key_json = | ||
std::fs::read(&signer_access_key_file_path).wrap_err_with(|| { | ||
format!( | ||
"Access key file for account <{}> on network <{}> not found! \nSearch location: {:?}", | ||
account_id, | ||
network_config.network_name, signer_access_key_file_path | ||
) | ||
})?; | ||
let account_key_pair: crate::transaction_signature_options::AccountKeyPair = | ||
serde_json::from_slice(&signer_access_key_json).wrap_err_with(|| { | ||
format!( | ||
"Error reading data from file: {:?}", | ||
&signer_access_key_file_path | ||
) | ||
})?; | ||
eprintln!("\nPublic key: {}", account_key_pair.public_key); | ||
Ok(()) | ||
} | ||
}); | ||
|
||
Ok(Self(crate::network::NetworkContext { | ||
config: previous_context.config, | ||
interacting_with_account_ids: vec![scope.owner_account_id.clone().into()], | ||
on_after_getting_network_callback, | ||
})) | ||
} | ||
} | ||
|
||
impl PublicKeyFromKeychain { | ||
pub fn input_owner_account_id( | ||
context: &crate::GlobalContext, | ||
) -> color_eyre::eyre::Result<Option<crate::types::account_id::AccountId>> { | ||
crate::common::input_signer_account_id_from_used_account_list( | ||
&context.config.credentials_home_dir, | ||
"For which account do you need to view the public key?", | ||
) | ||
} | ||
} | ||
|
||
impl From<PublicKeyFromLegacyKeychainContext> for crate::network::NetworkContext { | ||
fn from(item: PublicKeyFromLegacyKeychainContext) -> Self { | ||
item.0 | ||
} | ||
} |
36 changes: 36 additions & 0 deletions
36
src/commands/account/get_public_key/from_seed_phrase/mod.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
#[derive(Debug, Clone, interactive_clap::InteractiveClap)] | ||
#[interactive_clap(input_context = crate::GlobalContext)] | ||
#[interactive_clap(output_context = PublicKeyFromSeedPhraseContext)] | ||
pub struct PublicKeyFromSeedPhrase { | ||
/// Enter the seed-phrase: | ||
master_seed_phrase: String, | ||
#[interactive_clap(long)] | ||
#[interactive_clap(skip_default_input_arg)] | ||
seed_phrase_hd_path: crate::types::slip10::BIP32Path, | ||
} | ||
|
||
#[derive(Debug, Clone)] | ||
pub struct PublicKeyFromSeedPhraseContext; | ||
|
||
impl PublicKeyFromSeedPhraseContext { | ||
pub fn from_previous_context( | ||
_previous_context: crate::GlobalContext, | ||
scope: &<PublicKeyFromSeedPhrase as interactive_clap::ToInteractiveClapContextScope>::InteractiveClapContextScope, | ||
) -> color_eyre::eyre::Result<Self> { | ||
let public_key = crate::common::get_public_key_from_seed_phrase( | ||
scope.seed_phrase_hd_path.clone().into(), | ||
&scope.master_seed_phrase, | ||
)?; | ||
eprintln!("\nPublic key: {}", public_key); | ||
|
||
Ok(Self) | ||
} | ||
} | ||
|
||
impl PublicKeyFromSeedPhrase { | ||
pub fn input_seed_phrase_hd_path( | ||
_context: &crate::GlobalContext, | ||
) -> color_eyre::eyre::Result<Option<crate::types::slip10::BIP32Path>> { | ||
crate::transaction_signature_options::sign_with_seed_phrase::input_seed_phrase_hd_path() | ||
} | ||
} |
Oops, something went wrong.