Skip to content

Commit 0aeb4e1

Browse files
added cast wallet public key (#10196)
* added cast wallet public key * added test and made smol changes * fmt * Update crates/cast/src/cmd/wallet/mod.rs --------- Co-authored-by: zerosnacks <95942363+zerosnacks@users.noreply.github.com>
1 parent 9dbf916 commit 0aeb4e1

File tree

2 files changed

+54
-1
lines changed

2 files changed

+54
-1
lines changed

crates/cast/src/cmd/wallet/mod.rs

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@ use alloy_chains::Chain;
33
use alloy_dyn_abi::TypedData;
44
use alloy_primitives::{hex, Address, PrimitiveSignature as Signature, B256, U256};
55
use alloy_provider::Provider;
6-
use alloy_signer::Signer;
6+
use alloy_signer::{
7+
k256::{elliptic_curve::sec1::ToEncodedPoint, SecretKey},
8+
Signer,
9+
};
710
use alloy_signer_local::{
811
coins_bip39::{English, Entropy, Mnemonic},
912
MnemonicBuilder, PrivateKeySigner,
@@ -210,7 +213,16 @@ pub enum WalletSubcommands {
210213
#[command(flatten)]
211214
wallet: WalletOpts,
212215
},
216+
/// Get the public key for the given private key.
217+
#[command(visible_aliases = &["pubkey"])]
218+
PublicKey {
219+
/// If provided, the public key will be derived from the specified private key.
220+
#[arg(long = "raw-private-key", value_name = "PRIVATE_KEY")]
221+
private_key_override: Option<String>,
213222

223+
#[command(flatten)]
224+
wallet: WalletOpts,
225+
},
214226
/// Decrypt a keystore file to get the private key
215227
#[command(name = "decrypt-keystore", visible_alias = "dk")]
216228
DecryptKeystore {
@@ -398,6 +410,34 @@ impl WalletSubcommands {
398410
let addr = wallet.address();
399411
sh_println!("{}", addr.to_checksum(None))?;
400412
}
413+
Self::PublicKey { wallet, private_key_override } => {
414+
let wallet = private_key_override
415+
.map(|pk| WalletOpts {
416+
raw: RawWalletOpts { private_key: Some(pk), ..Default::default() },
417+
..Default::default()
418+
})
419+
.unwrap_or(wallet)
420+
.signer()
421+
.await?;
422+
423+
let private_key_bytes = match wallet {
424+
WalletSigner::Local(wallet) => wallet.credential().to_bytes(),
425+
_ => eyre::bail!("Only local wallets are supported by this command"),
426+
};
427+
428+
let secret_key = SecretKey::from_slice(&private_key_bytes)
429+
.map_err(|e| eyre::eyre!("Invalid private key: {}", e))?;
430+
431+
// Get the public key from the private key
432+
let public_key = secret_key.public_key();
433+
434+
// Serialize it as uncompressed (65 bytes: 0x04 || X (32 bytes) || Y (32 bytes))
435+
let pubkey_bytes = public_key.to_encoded_point(false);
436+
// Strip the 1-byte prefix (0x04) to get 64 bytes for Ethereum use
437+
let ethereum_pubkey = &pubkey_bytes.as_bytes()[1..];
438+
439+
sh_println!("0x{}", hex::encode(ethereum_pubkey))?;
440+
}
401441
Self::Sign { message, data, from_file, no_hash, wallet } => {
402442
let wallet = wallet.signer().await?;
403443
let sig = if data {

crates/cast/tests/cli/main.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -500,7 +500,20 @@ casttest!(wallet_private_key_from_mnemonic_option, |_prj, cmd| {
500500
501501
"#]]);
502502
});
503+
// tests that `cast wallet public-key` correctly derives and outputs the public key
504+
casttest!(wallet_public_key_with_private_key, |_prj, cmd| {
505+
cmd.args([
506+
"wallet",
507+
"public-key",
508+
"--raw-private-key",
509+
"0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d"
510+
])
511+
.assert_success()
512+
.stdout_eq(str![[r#"
513+
0xba5734d8f7091719471e7f7ed6b9df170dc70cc661ca05e688601ad984f068b0d67351e5f06073092499336ab0839ef8a521afd334e53807205fa2f08eec74f4
503514
515+
"#]]);
516+
});
504517
// tests that `cast wallet private-key` with derivation path outputs the private key
505518
casttest!(wallet_private_key_with_derivation_path, |_prj, cmd| {
506519
cmd.args([

0 commit comments

Comments
 (0)