Skip to content

Commit 4ba81a3

Browse files
committed
Address pr feedback.
1 parent b66d0fa commit 4ba81a3

5 files changed

Lines changed: 67 additions & 20 deletions

File tree

FULL_HELP_DOCS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1138,7 +1138,7 @@ Add a new identity (keypair, ledger, OS specific secure store)
11381138

11391139
- `--public-key <PUBLIC_KEY>` — Add a public key, ed25519, or muxed account, e.g. G1.., M2..
11401140
- `--overwrite` — Overwrite existing identity if it already exists. When combined with --secure-store, also replaces the existing Secure Store entry
1141-
- `--hd-path <HD_PATH>` — When importing a seed phrase, which `hd_path` to derive the key at. Persisted on the identity (or its Secure Store entry) so later commands derive the same account without re-passing the flag. Not valid with `--public-key` or a raw secret key
1141+
- `--hd-path <HD_PATH>` — When importing a seed phrase, which `hd_path` to derive the key at. Persisted on the identity so later commands derive the same account without re-passing the flag. Not valid with `--public-key` or a raw secret key
11421142

11431143
###### **Options (Global):**
11441144

cmd/crates/soroban-test/tests/it/integration/secure_store.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,4 +169,35 @@ async fn secure_store_key_management() {
169169
"expected cached public_key to match the address derived at hd_path 5, \
170170
but identity file was:\n{identity_toml}"
171171
);
172+
173+
// Strip the cached public_key but keep hd_path = 5: simulates a legacy
174+
// identity that persisted hd_path but never cached the derived pubkey.
175+
// `keys address` without --hd-path must rederive at the persisted index 5
176+
// and write the index-5 pubkey back to the cache — not silently overwrite
177+
// it with the index-0 pubkey.
178+
let stripped: String = identity_toml
179+
.lines()
180+
.filter(|line| !line.trim_start().starts_with("public_key"))
181+
.collect::<Vec<_>>()
182+
.join("\n");
183+
std::fs::write(&identity_path, stripped).unwrap();
184+
185+
let address_after_rederive = sandbox
186+
.new_assert_cmd("keys")
187+
.args(["address", add_hd_path])
188+
.assert()
189+
.success()
190+
.stdout_as_str();
191+
assert_eq!(
192+
address_after_rederive.trim(),
193+
address_hd_path.trim(),
194+
"expected `keys address {add_hd_path}` (no --hd-path) to derive at the persisted hd_path 5"
195+
);
196+
197+
let identity_toml = std::fs::read_to_string(&identity_path).unwrap();
198+
assert!(
199+
identity_toml.contains(&format!("public_key = \"{}\"", address_hd_path.trim())),
200+
"expected cache write-back to use the persisted hd_path, \
201+
but identity file was:\n{identity_toml}"
202+
);
172203
}

cmd/soroban-cli/src/commands/keys/add.rs

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,6 @@ pub enum Error {
4242

4343
#[error("--hd-path is not valid with a secret key; secret keys cannot be derived")]
4444
HdPathNotSupportedForSecretKey,
45-
46-
#[error("--hd-path is not valid with --public-key; public keys cannot be derived")]
47-
HdPathNotSupportedForPublicKey,
4845
}
4946

5047
#[derive(Debug, clap::Parser, Clone)]
@@ -60,17 +57,23 @@ pub struct Cmd {
6057
pub config_locator: locator::Args,
6158

6259
/// Add a public key, ed25519, or muxed account, e.g. G1.., M2..
63-
#[arg(long, conflicts_with = "seed_phrase", conflicts_with = "secret_key")]
60+
#[arg(
61+
long,
62+
conflicts_with = "seed_phrase",
63+
conflicts_with = "secret_key",
64+
conflicts_with = "hd_path"
65+
)]
6466
pub public_key: Option<String>,
6567

6668
/// Overwrite existing identity if it already exists. When combined with
6769
/// --secure-store, also replaces the existing Secure Store entry.
6870
#[arg(long)]
6971
pub overwrite: bool,
7072

71-
/// When importing a seed phrase, which `hd_path` to derive the key at. Persisted on
72-
/// the identity (or its Secure Store entry) so later commands derive the same account
73-
/// without re-passing the flag. Not valid with `--public-key` or a raw secret key.
73+
/// When importing a seed phrase, which `hd_path` to derive the key at.
74+
/// Persisted on the identity so later commands derive the same account
75+
/// without re-passing the flag. Not valid with `--public-key` or a raw
76+
/// secret key.
7477
#[arg(long)]
7578
pub hd_path: Option<usize>,
7679
}
@@ -79,10 +82,6 @@ impl Cmd {
7982
pub fn run(&self, global_args: &global::Args) -> Result<(), Error> {
8083
let print = Print::new(global_args.quiet);
8184

82-
if self.public_key.is_some() && self.hd_path.is_some() {
83-
return Err(Error::HdPathNotSupportedForPublicKey);
84-
}
85-
8685
if self.config_locator.read_identity(&self.name).is_ok() {
8786
if !self.overwrite {
8887
return Err(Error::IdentityAlreadyExists(self.name.to_string()));
@@ -268,10 +267,22 @@ mod tests {
268267
}
269268

270269
#[test]
271-
fn test_run_rejects_hd_path_with_public_key() {
272-
let (_tmp, _locator, cmd) = cmd_with_public_key(PUBLIC_KEY, Some(3));
273-
let result = cmd.run(&global_args());
274-
assert!(matches!(result, Err(Error::HdPathNotSupportedForPublicKey)));
270+
fn test_clap_rejects_hd_path_with_public_key() {
271+
// clap-level conflict: --public-key cannot be combined with --hd-path.
272+
// Driving through `try_parse_from` rather than constructing `Cmd`
273+
// directly is what exercises the conflict.
274+
use clap::Parser;
275+
276+
let result = Cmd::try_parse_from([
277+
"add",
278+
"test_name",
279+
"--public-key",
280+
PUBLIC_KEY,
281+
"--hd-path",
282+
"3",
283+
]);
284+
let err = result.expect_err("clap must reject --public-key + --hd-path");
285+
assert_eq!(err.kind(), clap::error::ErrorKind::ArgumentConflict);
275286
}
276287

277288
#[test]

cmd/soroban-cli/src/config/locator.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -322,14 +322,19 @@ impl Args {
322322
if let Key::Secret(Secret::SecureStore {
323323
entry_name,
324324
public_key: None,
325-
..
325+
hd_path: persisted_hd_path,
326326
}) = &key
327327
{
328-
let pk = secure_store::get_public_key(entry_name, hd_path)?;
328+
// Honor the persisted hd_path when the caller passes None. Without
329+
// this the cache gets populated at index 0 even when the identity
330+
// was added with `--hd-path N`, which silently locks every later
331+
// read to the wrong account.
332+
let effective = hd_path.or(*persisted_hd_path);
333+
let pk = secure_store::get_public_key(entry_name, effective)?;
329334
let migrated = Key::Secret(Secret::SecureStore {
330335
entry_name: entry_name.clone(),
331336
public_key: Some(pk.to_string()),
332-
hd_path,
337+
hd_path: effective,
333338
});
334339
// Best-effort write-back: if persistence fails we still return the
335340
// freshly-derived value so the current call succeeds.

cmd/soroban-cli/src/config/secret.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ impl Secret {
182182
let hd_path: u32 = hd_path
183183
.unwrap_or_default()
184184
.try_into()
185-
.expect("uszie bigger than u32");
185+
.expect("usize bigger than u32");
186186
SignerKind::Ledger(ledger::new(hd_path).await?)
187187
}
188188
Secret::SecureStore {

0 commit comments

Comments
 (0)