Skip to content

feat(wallet): encrypt private keys at rest using eth-keystore#45

Open
Aboudjem wants to merge 9 commits intoPolymarket:mainfrom
Aboudjem:fix/encrypted-keystore
Open

feat(wallet): encrypt private keys at rest using eth-keystore#45
Aboudjem wants to merge 9 commits intoPolymarket:mainfrom
Aboudjem:fix/encrypted-keystore

Conversation

@Aboudjem
Copy link

@Aboudjem Aboudjem commented Mar 12, 2026

Summary

  • Private keys are now encrypted at rest using AES-128-CTR + scrypt (via Alloy's keystore)
  • Password prompt on wallet create/import, with POLYMARKET_PASSWORD env var for CI/automation
  • Backward compatible: detects plaintext keys and offers automatic migration
  • New wallet export subcommand to decrypt and display key when needed
  • Key resolution priority: --private-key flag → env var → auto-migrate → keystore prompt

Changes

File What
src/password.rs New module: password prompting with retries + env var fallback
src/config.rs Keystore encrypt/decrypt, migration logic
src/auth.rs Updated key resolution chain
src/commands/wallet.rs create/import use encryption, new export subcommand
src/commands/setup.rs Uses encrypted save path
Cargo.toml Added alloy/signer-keystore, rpassword, rand, secrecy

Test plan

  • cargo clippy -D warnings — clean
  • cargo fmt --check — clean
  • 84 unit + 49 integration tests passing
  • New tests: keystore round-trip (encrypt → decrypt same address) + wrong-password rejection

Fixes #18


Note

Medium Risk
Changes how wallet private keys are stored and loaded (introducing encrypted keystore files, password prompts, and migration), which can break auth flows or lock users out if edge cases aren’t handled correctly.

Overview
Encrypts wallet private keys at rest and adds password-based key management. Wallet creation/import now writes an encrypted keystore.json (and stores only non-sensitive settings in config.json), with new password prompting (including POLYMARKET_PASSWORD support) and retries.

Updates key resolution and CLI workflows. Auth/provider setup now resolves keys via --private-key/env var, auto-migrates legacy plaintext config keys to the encrypted keystore, or prompts to decrypt the keystore; wallet export is added to decrypt/print the key when needed, and setup/wallet commands are adjusted to detect existing keystores and avoid accidental overwrite.

Dependency updates add Alloy keystore support plus secrecy, rpassword, and rand, and tests are extended to cover keystore encrypt/decrypt behavior and CLI help output.

Written by Cursor Bugbot for commit 7cc2e4c. This will update automatically on new commits. Configure here.

Aboudjem and others added 2 commits March 12, 2026 02:40
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…g keystore

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Aboudjem and others added 4 commits March 17, 2026 11:38
When a user has an existing encrypted keystore and fails password
authentication (e.g., 3 wrong attempts), the setup flow previously
fell through to setup_wallet() which silently overwrote the keystore.
Now we bail with a clear error message instead.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
If save_key_encrypted succeeds but save_wallet_settings fails, the
plaintext key would remain permanently alongside the encrypted copy.
Now the keystore is deleted on failure so the user can retry cleanly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ting

After auto-migrating a plaintext key to encrypted keystore,
resolve_key_string was calling load_key_encrypted which performs a
redundant scrypt decryption. Since we already have the plaintext key
in memory from the config, return it directly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…g them

resolve_key previously used `if let Ok(Some(config)) = load_config()`
which silently ignored I/O and parse errors. Now returns Result so
callers can surface config file problems to the user.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

anyhow::bail!(
"Failed to unlock existing keystore: {e}\n\
Run `polymarket wallet export` with the correct password, \
or `polymarket wallet delete` to start fresh."
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Error message references non-existent wallet delete command

Medium Severity

The error message tells users to run polymarket wallet delete to start fresh, but no delete subcommand exists. The actual command is polymarket wallet reset. Users who encounter a keystore unlock failure during setup will be given instructions that don't work.

Fix in Cursor Fix in Web

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.

Private keys stored as plaintext in config file

1 participant