Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Added the ability to get a public key for an account #436

Merged
merged 4 commits into from
Jan 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use std::io::Write;

use color_eyre::eyre::Context;
use inquire::CustomType;

#[derive(Debug, Clone, interactive_clap::InteractiveClap)]
#[interactive_clap(input_context = crate::GlobalContext)]
Expand Down Expand Up @@ -74,10 +73,6 @@ impl SaveWithSeedPhrase {
pub fn input_seed_phrase_hd_path(
_context: &crate::GlobalContext,
) -> color_eyre::eyre::Result<Option<crate::types::slip10::BIP32Path>> {
Ok(Some(
CustomType::new("Enter seed phrase HD Path (if you not sure leave blank for default):")
.with_starting_input("m/44'/397'/0'")
.prompt()?,
))
crate::transaction_signature_options::sign_with_seed_phrase::input_seed_phrase_hd_path()
}
}
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 {
akorchyn marked this conversation as resolved.
Show resolved Hide resolved
#[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
}
}
55 changes: 55 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,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 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."
akorchyn marked this conversation as resolved.
Show resolved Hide resolved
);
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 src/commands/account/get_public_key/from_seed_phrase/mod.rs
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()
}
}
Loading
Loading