Skip to content

feat: encrypt private keys at rest using keystore#44

Open
mvanhorn wants to merge 2 commits intoPolymarket:mainfrom
mvanhorn:osc/18-encrypted-key-storage
Open

feat: encrypt private keys at rest using keystore#44
mvanhorn wants to merge 2 commits intoPolymarket:mainfrom
mvanhorn:osc/18-encrypted-key-storage

Conversation

@mvanhorn
Copy link

@mvanhorn mvanhorn commented Mar 10, 2026

Summary

  • Private keys in ~/.config/polymarket/config.json were stored as plaintext - any process running as the same user could read them
  • wallet create and wallet import now encrypt keys using the standard Ethereum keystore format (AES-128-CTR + scrypt), the same format used by MetaMask, Geth, and Foundry
  • All commands that need the key prompt for a password (or read POLYMARKET_PASSWORD env var for CI/scripts)
  • --no-password flag available for plaintext storage (not recommended)
  • New wallet export subcommand to decrypt and display the key for backup
  • Backward compatible: existing plaintext configs continue to work

New dependencies

  • rpassword - secure terminal password input (no echo)
  • rand 0.8 - RNG for keystore encryption
  • alloy signer-keystore feature - standard keystore encrypt/decrypt

Fixes #18
Supersedes #19 (pre-refactor, needs full rewrite after #13)

Test plan

  • cargo fmt --check passes
  • cargo clippy -- -D warnings passes
  • cargo test passes (131 tests)
  • polymarket wallet create prompts for password and stores encrypted keystore
  • polymarket wallet create --no-password stores plaintext (with warning)
  • polymarket wallet import <key> prompts for password
  • polymarket wallet export decrypts and shows key
  • All commands prompt for password when key is encrypted
  • POLYMARKET_PASSWORD=xxx polymarket wallet address works without interactive prompt

This contribution was developed with AI assistance (Claude Code).


Note

High Risk
Changes wallet key storage and retrieval to support encrypted keystore files and password prompting, which is security-sensitive and affects all commands that need a private key. Misconfiguration or edge cases (missing keystore, password handling, filesystem moves) could block access to funds or break automation.

Overview
Private keys are no longer stored in plaintext by default: wallet create/wallet import now write an Ethereum keystore file under ~/.config/polymarket/keystore and mark the config with an encrypted flag, prompting for a password (or using POLYMARKET_PASSWORD).

Adds wallet export to decrypt and print the private key for backup, and introduces --no-password on create/import to explicitly keep legacy plaintext storage (with warnings/cleanup of leftover keystore files). This also updates dependencies to enable Alloy keystore support and adds rpassword/rand, with the lockfile pulling in keystore/crypto crates.

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

Wallet create and import now encrypt private keys using the standard
Ethereum keystore format (AES-128-CTR + scrypt), the same format used
by MetaMask, Geth, and Foundry. Users are prompted for a password on
key creation/import and when any command needs the key. The
POLYMARKET_PASSWORD env var supports non-interactive use (CI/scripts).
Use --no-password to opt into plaintext storage (not recommended).
Adds wallet export subcommand to decrypt and display the key.

Fixes Polymarket#18

Co-Authored-By: Claude Opus 4.6 <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 2 potential issues.

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

…xt path

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@mvanhorn
Copy link
Author

Addressed both cursor findings in d491d80:

  1. Atomic keystore write - save_encrypted now writes to a .tmp subdirectory first. Old keystore files are only deleted after the new one is successfully written, then moved into place. If encrypt_keystore fails, the old keystore remains intact.

  2. Stale keystore cleanup in plaintext path - Added cleanup_keystore_files() helper called in both cmd_create and cmd_import plaintext branches, so old encrypted keystore files don't linger on disk when overwriting with --force --no-password.

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