Skip to content

Add --keystore flag for signing with an encrypted keystore - #795

Open
palango wants to merge 2 commits into
palango/keystores-drop-ethereumjs-walletfrom
palango/cli-keystore-flag
Open

Add --keystore flag for signing with an encrypted keystore#795
palango wants to merge 2 commits into
palango/keystores-drop-ethereumjs-walletfrom
palango/cli-keystore-flag

Conversation

@palango

@palango palango commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Stacked on #792base that PR's branch, not master. Merge #792 first and this will retarget cleanly.

Why

celocli could sign with a raw private key or a Ledger, but had no way to use an encrypted keystore file — which is how geth and cast users actually keep keys on disk. --privateKey puts the key in shell history and the process table; a keystore does not.

Incidentally this gives @celo/keystores its first consumer. Nothing in the monorepo imported it before.

Usage

# a keystore file, password prompted
celocli transfer:celo --keystore ~/keystore/UTC--2024-…--8233d802… --to 0x… --value 1

# a directory of keystores; --from picks the account
celocli transfer:celo --keystore ~/keystore --from 0x8233d802… --to 0x… --value 1

# non-interactive
celocli transfer:celo --keystore ~/ks/UTC--… --passwordFile ~/.celo-password --to 0x… --value 1

--keystore is mutually exclusive with --privateKey, --useLedger and --useAKV.

Design

The resolved key flows into the same slot --privateKey already used, so both the ContractKit path (getKit) and the viem path (getWalletClient) are covered by one change rather than two new branches. Resolution is memoized, so a keystore is decrypted — and its password requested — once per command, no matter how many times the key is needed.

Deliberate divergences from cast

  • --passwordFile, not --password-file. Every multi-word flag in this CLI is camelCase (--privateKey, --useLedger, --derivationPath).
  • No --password in cleartext. It leaks into shell history and ps. cast names its equivalent --unsafe-password on most subcommands, which says enough.
  • No ETH_KEYSTORE / ETH_PASSWORD env vars. No flag in this CLI currently uses oclif's env:, and cast's own tracker has a standing bug where a stray ETH_KEYSTORE hijacks an explicit --private-key (foundry#2691).
  • Directories are supported. Worth noting cast's help text claims "folder or file" but its code explicitly rejects directories — its docs are stale. Supporting them here is a small superset.
  • No --account. That only makes sense with cast's ~/.foundry/keystores convention, which Celo has no equivalent of.

FileKeystore is deliberately not used: it appends keystore/ to whatever directory it is given and creates directories as a side effect, neither of which suits a flag pointing straight at a keystore directory.

Verification

20 automated tests. utils/keystore.test.ts covers the resolver against real encryptV3/decryptV3 round-trips — file and directory forms, case-insensitive --from, wrong password, non-keystore files skipped, empty directory, password-file trailing newline, prompt vs no-prompt. base.test.ts covers flag exclusivity and the wiring itself (wallet client address, password asked once, --from mismatch). Only the public RPC client is ever stubbed; every key is really decrypted and every address really derived.

Also exercised end-to-end through the built binary against Celo Sepolia — file, directory, and interactive prompt all reach:

✔  0x8233D802bdC645d0D1B9B2E6face6e5825905081 can sign txs
✘  Account has at least 0.000000000000000001 CELO

failing only on the empty test account, as it should.

Two things a reviewer should know

  • --keystore does not work with bridge:*. Those commands bypass BaseCommand and build their own signer. Tracked in Bridge commands duplicate signer construction and miss new signer types #794, deliberately out of scope here.
  • The anvil-backed integration suite could not be run. anvil 1.7.1 cannot parse @celo/devchain-anvil's checked-in l2-devchain.json (data did not match any variant of untagged enum SerializableTransactionType). This is pre-existing and unrelated — untouched test files such as commands/account/balance.test.ts fail identically on master. Every test added here was written to run without anvil, and all 20 pass.

PR-Codex overview

This PR introduces a --keystore flag for the CLI, allowing users to sign transactions with an encrypted keystore file. It enhances the @celo/keystores package by exporting V3 keystore primitives and adds functionality for managing keystores in commands.

Detailed summary

  • Added --keystore flag for transaction signing.
  • Updated @celo/keystores package version in package.json.
  • Introduced passwordFile option for keystore commands.
  • Enhanced privateKeyFromKeystore function for better error handling.
  • Implemented tests for keystore functionality and edge cases.
  • Modified BaseCommand to integrate keystore signing logic.

✨ Ask PR-Codex anything about this PR by commenting with /codex {your question}

celocli could sign with a raw private key or a Ledger, but had no way to
use an encrypted keystore file, which is how geth and cast users keep
keys on disk.

--keystore takes a keystore file, or a directory of them in which case
--from selects the account. The password comes from --passwordFile, or a
hidden prompt when that is not given. Resolution is memoized so a
keystore is decrypted, and its password requested, once per command.

The resolved key flows into the same paths --privateKey already uses, so
both the ContractKit and viem signing paths are covered without further
branching. Export the V3 primitives from @celo/keystores so the CLI can
decrypt a single file without going through FileKeystore.
Both --useLedger and --useAKV now declare --keystore as exclusive, matching
the reciprocal style of the other signer flags so the conflict shows up in
generated help from either side. Enforcement already worked one-sided.

Add tests for the BaseCommand wiring itself: that the wallet client signs
with the keystore's address, that the password is requested once however
many times the key is needed, and that a mismatched --from is reported
against the keystore. Only the public client is stubbed; the key really is
decrypted and its address really is derived.

Bump @celo/keystores to minor, since exporting the V3 primitives adds
public API.
@palango
palango requested a review from a team as a code owner August 19, 2026 15:33
@changeset-bot

changeset-bot Bot commented Aug 19, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 0874c69

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 2 packages
Name Type
@celo/celocli Minor
@celo/keystores Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits.

@github-actions

github-actions Bot commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

size-limit report 📦

Path Size
import * from '@celo/core' (csj) 1.81 KB (0%)
import * from '@celo/core' (esm) 1.56 KB (0%)

@codecov

codecov Bot commented Aug 19, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 69.03%. Comparing base (1434bfd) to head (0874c69).

Additional details and impacted files
@@                             Coverage Diff                              @@
##           palango/keystores-drop-ethereumjs-wallet     #795      +/-   ##
============================================================================
- Coverage                                     69.61%   69.03%   -0.58%     
============================================================================
  Files                                           213      153      -60     
  Lines                                         11312     8911    -2401     
  Branches                                       1973     1672     -301     
============================================================================
- Hits                                           7875     6152    -1723     
+ Misses                                         3349     2688     -661     
+ Partials                                         88       71      -17     
Components Coverage Δ
celocli ∅ <ø> (∅)
sdk 67.70% <ø> (ø)
wallets 73.68% <ø> (ø)
viem-sdks 94.15% <ø> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant