feat(validator-readiness): keystore security hardening + Filecoin (Lighthouse) provider adapter#16
Conversation
…i-account store, list-accounts
Security-sensitive keystore work for task_300626_3 Part 1 (P3-T0.2b).
Flag for Umer's needs-security-review before merge.
- In-memory TTL password cache (_PASSWORD_CACHE) replaces the on-disk
CONFIG_DIR/.session cache; stale .session files are cleaned up on
load. No cross-invocation persistence by design (D1) — only
DIN_WALLET_PASSWORD env avoids re-prompting across commands.
- connect-wallet --keystore <path>: imports a standard eth-account
JSON keystore, validates by decrypting to derive the checksum
address, preserves the original keystore verbatim.
- Named multi-account keystore: CONFIG_DIR/wallets/wallet_<name>.json
with a shared wrapper schema ({version, address, keystore, source,
name}); _extract_keystore() normalizes wrapper/bare/demo forms.
connect-wallet --name <name> saves under that name; legacy
wallet.json still loads as "default" (named default wins over
legacy when both exist).
- validate_account_name() enforces ^[A-Za-z0-9_-]{1,64}$ and is
called from wallet_path_for_name() itself, the one function every
read/write path funnels through — so path safety is a boundary
invariant, not something every caller has to remember.
- Atomic writes (temp file at 0o600 + os.replace + defensive chmod)
so wallet files end up 0o600 even when overwriting a pre-existing
loose-permission file; wallets/ dir created at 0o700.
- New `dincli system list-accounts` command enumerates named wallets
+ legacy wallet.json without decrypting, dedups so a named default
hides the legacy row.
- read_wallet/todo updated to resolve the active named wallet instead
of hardcoding legacy wallet.json.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Top-level --wallet option in main.py (mirrors --network), --wallet added to GlobalOptionsGroup so it can appear anywhere on the command line, and DinContext.select_wallet()/resolved_wallet_name resolving --wallet -> DIN_WALLET_NAME env -> config wallet_name -> "default". select_wallet() validates the name and fails fast with a clean exit, and main.py resolves the wallet before printing network status so an invalid --wallet exits before any other output is printed. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…lates, and production-key callouts - New Documentation/guides/wallet-setup.md: why a dedicated burner wallet, eth-account/OWS keystore generation, funding (10 DIN stake + ETH gas), and the key-management tiers table. - New Documentation/guides/keystore-migration.md: old .env pattern vs encrypted keystores, migration via --keystore import, docker/operator setup, named-wallet precedence and runtime selection. - New /.env.example (root, authoritative) and hardhat/.env.example (signposted, non-authoritative since Hardhat reads ../.env) with dev-only ETH_PRIVATE_KEY_<n> labeling and a keystore pointer. - dincli/docker/node/.env.example + README.md: key-management section, wallets/ dir in the file tree, .session marked legacy-cleanup-only. - Production-keystore callouts added wherever GettingStarted.md, DEVELOPMENT_SETUP.md, Model-workflow.md, common.md, DINCLI_Containerizaton_Guide.md, setup.md, and ipfs.md instruct raw-key/--account setup or link wallet-setup.md as a prerequisite. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Covers: existing key-loading paths (positional/--key-file/--account/ getpass/demo), --keystore import (valid/wrong-password/missing/ malformed), named accounts + --wallet/DIN_WALLET_NAME runtime selection, legacy wallet.json fallback and named-default precedence, read-wallet/todo wallet-awareness, wrapper-schema normalization, name validation, atomic-write permissions, in-memory password cache, mutual exclusivity, and CliRunner-based routing coverage for the account-resolution skip-list. 46 tests, all passing. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…R env var + provider-scoped API keys (D6/D7)
Part 2 of task_300626_3 (Filecoin Provider Adapter), per
Plans/part2-filecoin-lighthouse.md.
- New dincli/services/ipfs_lighthouse.py: upload_via_lighthouse posts
to upload.lighthouse.storage/api/v0/add with Bearer auth and
extracts the nested data.Hash response shape (different from
Filebase's flat shape); retrieve_via_lighthouse gets from
gateway.lighthouse.storage/ipfs/{cid} (no auth). Malformed
responses raise a clear RuntimeError instead of leaking
KeyError/JSONDecodeError.
- Wired into ipfs.py's upload_to_ipfs/retrieve_from_ipfs dispatch as
a genuine first-class provider ("lighthouse" in
SUPPORTED_IPFS_PROVIDERS), not the generic dynamic-module "custom"
path (D5) — matches the task's concrete deliverables (dedicated
module, dedicated env var, dedicated test file).
- D6: resolve_ipfs_config() now also checks IPFS_PROVIDER env var as
a fallback when no ipfs_provider is set in config (config wins if
set); applies to all providers, not just Lighthouse.
- D7: API keys are now stored per-provider
(ipfs_api_key_<provider>) instead of one flat ipfs_api_key field,
fixing a silent-misconfiguration bug — switching from Filebase to
Lighthouse without a fresh --api-key no longer silently reuses the
Filebase token. Legacy flat ipfs_api_key still resolves for
filebase (back-compat); LIGHTHOUSE_API_KEY env var is a fallback
for lighthouse.
- configure_ipfs validates each provider's key independently.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…r references - ipfs.md: four modes instead of three, full lighthouse provider section mirroring the filebase section, migration notes updated for the provider-scoped API key change. - CLAUDE.md: "three interchangeable backends" -> four, Lighthouse added to the one-line list — this file is read as authoritative project context every session, so it needs to stay accurate. - ReadMe.md: "three supported IPFS modes" -> four. - setup.md: new "Option B - Lighthouse (Filecoin-backed IPFS)" alongside the existing Filebase option; env provider renumbered to Option C, custom to Option D. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…rovider selection and API-key scoping New tests/test_ipfs_lighthouse.py: - Upload: extracts nested Hash from Lighthouse's data.Hash response shape; malformed response raises RuntimeError, not KeyError; missing API key raises ValueError before any HTTP call; HTTP error wraps as RuntimeError matching Filebase's format. - Retrieve: uses the exact gateway URL, GET method, no auth header; HTTP error wraps as RuntimeError. Extended tests/test_ipfs_config.py (regression + new-behavior): - resolve_ipfs_config returns lighthouse provider when config-sourced and when IPFS_PROVIDER env-sourced; config wins over env when both are set. - Filebase regression: legacy flat ipfs_api_key still resolves. - Cross-provider isolation: a Filebase key is never reused as a Lighthouse key. - Existing env-provider tests still pass unchanged. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Guard against a wallet.json / keystore.json containing a raw private key accidentally landing in a commit during manual testing. dincli's real wallet state lives under CONFIG_DIR (outside the repo), so these patterns are purely a safety net for the keystore-security work in this branch. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
connect-wallet passed --name straight into validate_account_name at the top of the command, so an invalid name raised a raw ValueError traceback instead of the console.print + Exit(1) pattern used everywhere else (including set-wallet). Wrap it to match. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
@umeradl flagging for security review before merge, per task_300626_3 — the keystore/wallet commits (5cfd390, 4c7881a, 8436e50) and the .gitignore key-file guard (3d1bf04) are the ones needing your eyes. Also see the |
Merge status: partially merged into
|
Code review — Part 1 (keystore hardening) + Part 2 (Lighthouse adapter)Reviewed at commit Status: Part 1 (keystore/multi-account hardening) is merged into Exceptional work
Bugs to fix
Missed deliverables
Test results (reproduced independently, not taken on faith)
Net takePart 1 (keystore hardening) is solid — no security blockers, good test coverage, path-traversal handling is done at the right layer. It's merged. Part 2 (Lighthouse) is well-built adapter code but currently ships a provider that can't reliably retrieve what it stores; that's the one item actually blocking this PR from closing. Bugs #2, #3, #5 are open items worth fixing on this branch regardless of the Lighthouse timeline; #1 and #6 are already fixed on |
Cherry-picks the keystore/multi-account portion of PR #16 into develop: encrypted named keystores, --keystore import, in-memory password cache, --wallet/set-wallet/list-accounts, and the wallet-setup + keystore-migration guides. The Lighthouse/Filecoin IPFS adapter (Part 2) is held back — its own live test found the retrieval gateway returns 402 even for the uploader's own file, so it stays out of develop until that's resolved. PR #16 remains open for that follow-up. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…om develop Carry back the two review fixes already applied on develop so they don't regress on the next rebase (PR InfiniteZeroFoundation#16 review, bugs InfiniteZeroFoundation#1 and InfiniteZeroFoundation#6): - list-accounts: source label used [..] which Rich parsed as a markup tag and dropped; use (..) so imported/created/legacy renders. - docker node README: .session is a file, not a directory — drop the trailing slash.
…teZeroFoundation#2, InfiniteZeroFoundation#3, InfiniteZeroFoundation#5 in connect-wallet Bugs InfiniteZeroFoundation#2/InfiniteZeroFoundation#3 both touch connect_wallet and InfiniteZeroFoundation#3/InfiniteZeroFoundation#5 share the _get_password signature + a new _UNSET sentinel, so they land together as one coherent, cherry-pickable unit rather than incoherent split commits. InfiniteZeroFoundation#2 — overwrite guard: connect-wallet now confirms before overwriting an existing named keystore, with --yes/-y to bypass (mirrors send-eth). `yes` is normalized to a real bool so direct callers that omit it (which receive a truthy OptionInfo) don't silently skip the guard. Keystore non-secret validation (exists/parse/_extract_keystore) is hoisted above the guard; the passphrase getpass + decrypt stay after it, so no secret is requested before the overwrite decision and a doomed --keystore fails fast without prompting. Guard keys on the exact target file; a legacy wallet.json for 'default' is a non-destructive migration and intentionally not guarded. Docstring corrected: demo mode is config-driven, not tied to --yes. InfiniteZeroFoundation#3 — new-wallet password: _get_password gains is_new_wallet; when creating or overwriting a wallet the in-memory cache is skipped so a fresh keystore is never silently encrypted with a stale cached password. DIN_WALLET_PASSWORD env is still honored (deliberate automation path); only the cache is ignored. InfiniteZeroFoundation#5 — single .env parse per unlock: load_account fetches DIN_WALLET_PASSWORD once and threads it into _get_password/_cache_password_in_memory via an _UNSET sentinel, instead of each self-parsing .env (get_env_key is not memoized). Callers that omit env_pass self-fetch as before. Adds 7 tests: overwrite guard (omitted-yes/decline/accept/--yes/doomed-input), stale-cache-not-reused on create, and single-env-parse on unlock.
Update: review items addressed (Part 1)@umeradl — pushed two commits to
|
… OWS section Closes the PR InfiniteZeroFoundation#16 review's 'OWS feasibility is desk research, not a hands-on spike' gap with an actual spike (ows v1.4.2 + Python SDK installed, wallet created, real Optimism Sepolia tx signed and cryptographically verified): Developer/discussion/ows-delegation-feasibility.md. Verdict is scoped: CONFIRMED that OWS can sign an EVM tx without exposing the private key (verified via CLI and in-process Python SDK, byte-identical, signer recovers to the OWS address); NOT proven for the stronger production claim of mandatory scoped-policy delegation — the verified path is unscoped local vault signing that isn't even passphrase-gated. Policy-enforced signing, passphrase behavior, and an eth-account-compatible adapter are follow-up work. Also corrects wallet-setup.md, which shipped a broken flow: OWS does NOT export an Ethereum JSON keystore (ows wallet export prints the raw secret, interactive only), so the 'export keystore -> connect-wallet --keystore' loop can't work. Fixed the duplicate import, made the imported wallet active (set-wallet), fixed the delegation status (planned, not shipped) and the policy guidance, and showed the actual export command.
OWS delegation — hands-on feasibility spike (closes the review's "desk-research only" gap)@umeradl — redid the OWS assessment as an actual spike: installed Full write-up (on my fork branch, part of this PR's diff): Verdict — scoped precisely
Caveats found (not in OWS docs)
RecommendationKeep named multi-keystore as the shipped default (self-contained, passphrase-gated). Treat OWS delegation as a follow-up, not production-ready as described: a future task must verify (a) policy-enforced signing end-to-end, (b) passphrase-protected wallets + non-interactive credential passing, (c) an eth-account-compatible Also in this commit —
|
PR draft — Validator Readiness: Keystore Security + Filecoin (Lighthouse) Adapter
Not opened yet. Draft body below, ready for
gh pr create --base develop --title "..." --body-file Plans/pr-description-draft.mdonce reviewed.Suggested title:
feat(validator-readiness): keystore security hardening + Filecoin (Lighthouse) provider adapterneeds-security-review
The wallet/keystore-handling commits must be reviewed by @umeradl before merge, per
task_300626_3: the two feature commits (5cfd390in-memory password cache /--keystoreimport / named store,4c7881a--walletwiring) plus the follow-up fix (8436e50clean invalid---namehandling inconnect-wallet) and the.gitignoreguard (3d1bf04stray root-level key files). Everything else (docs, IPFS/Lighthouse adapter) does not need the same scrutiny.Summary
Implements both parts of
task_300626_3:--keystoreimport, in-memory password cache (removes on-disk plaintext session cache), burner-wallet + migration guides, production-key callouts across docs.env/filebase/customproviders.Part 1 — Keystore Security
connect-wallet --keystore <path>: imports a standard eth-account JSON keystore, decrypts to verify + derive the checksummed address, preserves the original keystore verbatim (source: "imported").connect-wallet --name <name>: named multi-account keystores atwallets/wallet_<name>.json(shared wrapper schema); legacywallet.jsonstill loads asdefault(named default wins when both exist).--wallet <name>/DIN_WALLET_NAMEenv / configwallet_name: runtime account selection, resolved throughDinContext; newdincli system set-wallet <name>persists a default. Resolution:--wallet→ env → config →"default".^[A-Za-z0-9_-]{1,64}$) enforced at the filesystem-boundary function (wallet_path_for_name) itself, not just at CLI entry points — so it holds regardless of call path.CONFIG_DIR/.sessionfile (no plaintext password ever written to disk); stale.sessionfiles are cleaned up automatically. This intentionally removes cross-invocation password caching — the only no-reprompt option across separate commands isDIN_WALLET_PASSWORDin.env.dincli system list-accounts: enumerates named wallets + legacy fallback without decrypting.Documentation/guides/wallet-setup.md(burner wallet rationale, generation viaeth-account/OWS, funding — 10 DIN stake + ETH gas),Documentation/guides/keystore-migration.md(old.envpattern → encrypted keystore migration path)..envsetup (setup.md,GettingStarted.md,common.md,Model-workflow.md,DINCLI_Containerizaton_Guide.md,DEVELOPMENT_SETUP.md, docker node README/.env.example).OWS (Open Wallet Standard) feasibility
Assessed per the task's ask. OWS (
openwallet.sh) has a real CLI (ows wallet create,sign message,sign tx,mnemonic generate/derive), Node/Python SDKs, and an MCP server — but its documented command reference has noexportsubcommand, sowallet-setup.mduses it only for keystore creation, not export, and points operators toows --help/upstream docs for the current export surface rather than asserting an unverified command. Recommendation: document-only for now — full signing delegation (dincli never holding the raw key) is plausible given OWS's MCP/REST/SDK interfaces, but implementing that integration is out of scope for this task.Design deviations (flagged for confirmation, not blocking)
.sessioncache is a real plaintext-on-disk risk; a module-level cache can't replicate cross-command convenience since eachdincliinvocation is a new process.DIN_WALLET_PASSWORDenv is the only remaining no-reprompt path.--accountstays anint(dev key index);--namelabels saved keystores;--walletselects at runtimedincli system set-wallet <name>to persist a default wallet name in config--wallet/env/config three-tier resolution needs a way to actually set the config tier; nothing did before.wallet_path_for_name()itselfPart 2 — Filecoin Provider Adapter (Lighthouse)
dincli/services/ipfs_lighthouse.py:upload_via_lighthouse/retrieve_via_lighthouse, verified against Lighthouse's own GitHub docs (not assumed) —POST https://upload.lighthouse.storage/api/v0/add,Bearerauth, multipartfilefield, nested{"data": {"Hash": ...}}response (different from Filebase's flat shape — this one place would silently break on a copy-paste from the Filebase code, so it's explicitly tested)."lighthouse"inSUPPORTED_IPFS_PROVIDERS), not the generic dynamic-modulecustompath — matches the task's concrete deliverables (dedicated module, dedicated test file, dedicated env var) better than the discussion doc's more general "use the custom path" framing.IPFS_PROVIDERenv var for provider selection (config wins if set — this is a real, generic addition toresolve_ipfs_config(), not Lighthouse-specific) andLIGHTHOUSE_API_KEYas an API-key fallback, satisfying the task's literal env-var ask on top of the config-first mechanism every other provider already uses.ipfs_api_key_<provider>) instead of one shared flat field — fixes a real bug found during review: switchingconfigure-ipfsfromfilebasetolighthousewithout a fresh--api-keyused to silently reuse the Filebase token instead of failing with a clear error. Legacy flat key still resolves forfilebase(back-compat).ipfs.md(newlighthousesection), plusCLAUDE.md/ReadMe.md/setup.mdupdated so they don't keep describing a stale "three provider" picture.test_ipfs_lighthouse.py(new) andtest_ipfs_config.py(extended) — nested-hash extraction, malformed-response handling, missing-key error, HTTP errors, exact retrieve URL/method, env-var provider selection, config-over-env precedence, cross-provider key isolation.Live-tested with a real account (not just mocks): upload works exactly as documented and returns a valid CID. Retrieval does not —
GET https://gateway.lighthouse.storage/ipfs/<cid>returns402 Payment Required, with and without the uploader's own API key attached (identical response either way — this isn't an auth-header issue). A generic public IPFS gateway (ipfs.io) also fails for the same CID: "no providers found for the CID" — the content isn't discoverable on the public IPFS network at all.This contradicts two explicit assumptions this task was built on:
This is not a bug in
ipfs_lighthouse.py— the adapter matches Lighthouse's documented API contract exactly, and the upload path is fully correct. It's an account/platform-level gate (Lighthouse appears to use HTTP 402 + likely the x402 micropayment protocol for gateway retrieval) that neither the discussion doc nor the task anticipated. Recommendation: before relying on Lighthouse for production retrieval, confirm whether this resolves with a funded/paid account or is a structural characteristic of the service — this may affect whether Lighthouse remains the right "first adapter" per the discussion doc, versus Storacha or another option. Raising this with Umer/Abraham directly; flagging here so it's not lost.Test plan
All new code paths, all previously-existing tests (regression-checked), no new failures introduced.
Not in scope / deliberately deferred