Skip to content

Commit

Permalink
start
Browse files Browse the repository at this point in the history
  • Loading branch information
FroVolod committed Jan 16, 2025
1 parent 366c3ee commit 942ebb6
Show file tree
Hide file tree
Showing 6 changed files with 377 additions and 0 deletions.
114 changes: 114 additions & 0 deletions src/commands/account/get_public_key/from_keychain/mod.rs
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
}
}
54 changes: 54 additions & 0 deletions src/commands/account/get_public_key/from_ledger/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
#[derive(Debug, Clone, interactive_clap::InteractiveClap)]
#[interactive_clap(input_context = crate::GlobalContext)]
#[interactive_clap(output_context = PublicKeyFromLedgerContext)]
pub struct PublicKeyFromLedger {
#[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 src/commands/account/get_public_key/from_legacy_keychain/mod.rs
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
}
}
28 changes: 28 additions & 0 deletions src/commands/account/get_public_key/from_seed_phrase/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
use std::str::FromStr;

#[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,
}

#[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 seed_phrase_hd_path_default = slipped10::BIP32Path::from_str("m/44'/397'/0'").unwrap();
let public_key = crate::common::get_public_key_from_seed_phrase(
seed_phrase_hd_path_default,
&scope.master_seed_phrase,
)?;
eprintln!("\nPublic key: {}", public_key);

Ok(Self)
}
}
42 changes: 42 additions & 0 deletions src/commands/account/get_public_key/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
use strum::{EnumDiscriminants, EnumIter, EnumMessage};

mod from_keychain;
#[cfg(feature = "ledger")]
mod from_ledger;
mod from_legacy_keychain;
mod from_seed_phrase;

#[derive(Debug, Clone, interactive_clap::InteractiveClap)]
#[interactive_clap(context = crate::GlobalContext)]
pub struct GetPublicKey {
#[interactive_clap(subcommand)]
get_public_key_mode: GetPublicKeyMode,
}

#[derive(Debug, Clone, EnumDiscriminants, interactive_clap::InteractiveClap)]
#[interactive_clap(context = crate::GlobalContext)]
#[strum_discriminants(derive(EnumMessage, EnumIter))]
/// Where do you want to get the public key from?
pub enum GetPublicKeyMode {
#[cfg(feature = "ledger")]
#[strum_discriminants(strum(
message = "from-ledger - Get the public key stored on your Ledger Nano device"
))]
/// Get the public key stored on your Ledger Nano device
FromLedger(self::from_ledger::PublicKeyFromLedger),
#[strum_discriminants(strum(
message = "from-seed-phrase - Get the public key with the seed phrase"
))]
/// Get the public key with the seed phrase
FromSeedPhrase(self::from_seed_phrase::PublicKeyFromSeedPhrase),
#[strum_discriminants(strum(
message = "from-keychain - Get the public key stored in a secure keychain"
))]
/// Get the public key stored in a secure keychain
FromKeychain(self::from_keychain::PublicKeyFromKeychain),
#[strum_discriminants(strum(
message = "from-legacy-keychain - Get the public key stored in the legacy keychain (compatible with the old near CLI)"
))]
/// Get the public key stored in the legacy keychain (compatible with the old near CLI)
FromLegacyKeychain(self::from_legacy_keychain::PublicKeyFromKeychain),
}
6 changes: 6 additions & 0 deletions src/commands/account/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ pub mod create_account;
mod delete_account;
mod delete_key;
mod export_account;
mod get_public_key;
mod import_account;
mod list_keys;
pub mod storage_management;
Expand Down Expand Up @@ -55,6 +56,11 @@ pub enum AccountActions {
))]
/// View a list of access keys of an account
ListKeys(self::list_keys::ViewListKeys),
#[strum_discriminants(strum(
message = "get-public-key - Get the public key (full access key) to your account"
))]
/// Get the public key (full access key) to your account
GetPublicKey(self::get_public_key::GetPublicKey),
#[strum_discriminants(strum(
message = "add-key - Add an access key to an account"
))]
Expand Down

0 comments on commit 942ebb6

Please sign in to comment.