V2.1 (#21)
Working version Reviewed-on: #21
This commit was merged in pull request #21.
This commit is contained in:
Executable
+17
@@ -0,0 +1,17 @@
|
||||
# Canonical ledger indexes
|
||||
|
||||
The ledger maintains SQLite indexes for canonical NFT ownership/sequence, mint ordering, block identity, and transaction inclusion. The UI uses these indexes for wallet decks, NFT metadata, and current-chain pagination; they do not change consensus rules or replace proof validation.
|
||||
|
||||
## Persistence and reorganization behavior
|
||||
|
||||
Accepted blocks, pending transactions, index connect/disconnect updates, undo records, and canonical event history commit in one SQLite transaction. Losing side branches and mempool submissions do not create canonical ownership events. On a reorganization, disconnects apply tip-first and connects ancestor-first. A failed write leaves both the stored ledger and in-memory tip unchanged.
|
||||
|
||||
On initial open, stored blocks are replayed and validated before the local index is built or checked. Later opens verify indexed block identity, ownership, transaction inclusion, and event continuity against that validated chain. Inconsistent state fails closed; no automatic reset or history pruning occurs. These local indexes and event records are not consensus data or a public proof that a node observed an honest network.
|
||||
|
||||
Wallet-deck results are paged and can be pinned to a canonical tip; stale tips return HTTP 409. Canonical block pages are bounded. Direct trades continue to be validated as complete atomic transactions against the canonical ownership state. No market journal, reconciliation worker, payment observer, or NFT sale-lock signer is part of this design.
|
||||
|
||||
## Validation and capacity limits
|
||||
|
||||
The regression suite covers indexed ownership and sequence against replay for transfers, atomic multi-card trades and forks; append/disconnect/reconnect ordering; side-branch and mempool exclusion; durable restart; failed writes; and stale-tip pagination.
|
||||
|
||||
The [canonical-index benchmark](../scripts/benchmark-canonical-index.ts), available as `npm run benchmark:index -- 10000`, creates and removes its own temporary SQL fixture. Its synthetic results are a baseline only—not production acceptance limits. Real PoW validation, startup replay, full DAG memory, concurrent HTTP load, and the intended deployment hardware must be measured independently. Indexes improve lookups but do not solve the retained-DAG and full-state costs described in the [consensus reference](testnet-consensus.md) and [production plan](card-only-production-plan.md).
|
||||
Executable
+113
@@ -0,0 +1,113 @@
|
||||
# Card-only production readiness plan
|
||||
|
||||
**Status:** proposed readiness plan, updated 2026-10-28. This is not launch approval, security certification, or permission to expose a public node.
|
||||
|
||||
## Product and release boundary
|
||||
|
||||
Wikideck's current product scope is **RandomX mining, collecting cards, and direct player-to-player card transfers/trades**. This release will not implement or offer a marketplace, auctions, sales, custody, XMR deposits/withdrawals, or real-money checkout. Direct card trades are signed and applied atomically by the chain; they are not paid orders.
|
||||
|
||||
The nearest reasonable release target is a **publicly hosted, non-monetary testnet beta**. It must be labeled experimental and valueless. It is not a mainnet launch, a promise of ownership permanence, an investment product, or an independently secured network. Mining consumes compute, electricity, and time; no reward rate or profitability is promised.
|
||||
|
||||
Mainnet or valuable-card use is a separate, higher bar. It remains **NO-GO** until consensus, wallet, network, capacity, deployment, and operator risk-acceptance gates below have evidence and named sign-off. The operator has chosen not to commission external audits; this is a conscious acceptance of additional residual risk, not evidence that the protocol or implementation is secure.
|
||||
|
||||
## Current starting point
|
||||
|
||||
- Mining, local encrypted wallets, collections, transfers, and atomic card-for-card trades are implemented for the experimental testnet.
|
||||
- Marketplace, custody, sale-settlement, and Monero payment source, UI, APIs, deployment profiles, active tests, and operator documentation have been removed from the supported repository. Direct on-chain card transfers/trades remain core functionality.
|
||||
- Existing runtime volumes and historical backup packages may contain local financial journals or external database bindings. Removing source code does not delete or resolve those records; preserve them separately and do not claim new ledger-only backups include them.
|
||||
- The current network has no public bootnodes or automatic peer discovery. Peers must be configured by operators; containers on one host do not provide independent network security.
|
||||
- The inherited initial proof-of-work target accepts one in sixteen hashes, and the ten-block retarget is not production-calibrated. Nodes retain the DAG in memory, rebuild state roots across the NFT set, and revalidate stored proofs at startup.
|
||||
- Browser-wallet encryption is implemented, but same-origin code can abuse an unlocked wallet; old plaintext copies and cross-browser recovery remain concerns.
|
||||
|
||||
See the [mainnet launch limitations](mainnet-launch.md), [consensus reference](testnet-consensus.md), [wallet limitations](wallet-security.md), and [security review](security-review.md). Existing passing tests and local rehearsals are useful evidence, not production certification.
|
||||
|
||||
## Ordered release gates
|
||||
|
||||
### Gate 0 — freeze the card-only scope · implemented
|
||||
|
||||
- Publish the scope above in the product and operator docs. Market/custody/payment source, UI, APIs, dependencies, tests, deployment files, and operator tasks are removed from the supported repository.
|
||||
- Preserve existing node and financial data during retirement. Do not remove Compose volumes, old journals, PostgreSQL schemas, or historical backups. Review/archive any existing market journal before stopping a market deployment; the card-only node will not reconcile or resolve it.
|
||||
- Version the node backup contract for ledger-only backups. Refuse to create a ledger-only package if legacy financial data or a PostgreSQL custody binding remains in the selected data directory. Keep old paired backup verification/restore available as a data-preservation path; restored legacy files are inert and are not reopened by the card-only application.
|
||||
- Update the production plan, operator launch checklist, security scope, support text, and release notes so they no longer imply that market, custody, or payment work is part of this release.
|
||||
- Choose and document the only supported launch target: testnet beta or a separately reviewed mainnet. Do not mix the two identities or reuse testnet data/backups as mainnet state.
|
||||
|
||||
**Exit evidence:** scope owner approval is still required. Source/deployment removals are complete; API regression checks cover removed routes while preserving card trades; backup v2 and legacy-data refusal are covered; old paired backups remain portable. No active product docs promise sales or payments.
|
||||
|
||||
### Gate 1 — internal protocol and application review · complete by operator decision
|
||||
|
||||
**Operator decision (2026-10-28):** no external audit or independent review will be commissioned for this release. The product will proceed using internal code review, regression tests, local adversarial exercises, and explicit operator risk acceptance. This does not certify the software or establish that undiscovered critical defects are absent.
|
||||
|
||||
Internal source-assisted review covered consensus serialization/validation, signatures, state commitments, fork choice, timestamps/retarget, peer URL/DNS/pagination/response handling, RandomX native boundary, wallet signing/recovery documentation and tests.
|
||||
|
||||
Internal findings and dispositions:
|
||||
|
||||
- **Clock rollback could strand accepted SQLite history.** Startup replay applied the live future-skew admission bound to every persisted block. A block accepted at the allowed future edge could fail replay if the clock later moved backward. Startup now replays previously accepted SQLite history without reapplying that live-time bound. New blocks imported from legacy JSON retain a pinned import-time clock and imported-block cutoff; their future timestamps are checked on import and on later replays, so an untrusted legacy file cannot seed arbitrarily future-dated history. Regression tests cover both rollback recovery and repeated rejection of future-dated legacy data.
|
||||
- **Invalid work reached full NFT-state materialization.** Block validation computed the state transition/root before checking the claimed and actual RandomX proof. Cheap commitment/signature/claimed-target checks now precede native proof verification, and the complete state transition/root is computed only after the native proof matches. A regression uses an instrumented state to assert that a wrong claimed proof is rejected before NFT-state enumeration.
|
||||
- Added a deterministic 30-block versus 16-block competing-fork regression: equal cumulative work keeps the current tip, and a strictly higher-work branch then causes a 30-block reorganization while preserving both DAG branches. This is a local FakePow consensus-state test, not a multi-host security rehearsal.
|
||||
|
||||
Current validation evidence: the Docker test image and TypeScript/Vite build succeeded; the complete offline suite passed **95/95** with the native RandomX HTTP integration; the packaged native engine's upstream known-vector self-test passed. Tests cover signatures/transaction replay, atomic trades, commitments, retarget/key boundaries, equal-work fork choice, reorg/requeue, malformed schemas, peer DNS/manifest/pagination/response rejection, mempool and RPC bounds, wallet backup crypto, and restart/revalidation. These checks are the selected assurance approach; they are not a security certification.
|
||||
|
||||
**Residual risks carried forward:** no external auditor or independent reviewer will sign off. The candidate is not frozen. Multi-host eclipse/partition and clock-skew rehearsals, hostile populated-state load/reorg tests, and broad browser-wallet/XSS/recovery exercises remain open engineering work for Gates 2–5. The operator accepts proceeding without external audit; do not describe the result as audited or independently certified.
|
||||
|
||||
**Exit evidence:** internal findings fixed with regression coverage; current build, offline suite and native known-vector results recorded; operator accepts the unaudited-risk boundary. Gate 1 is **complete under this explicitly reduced scope**. This completion is not external assurance, and its remaining multi-host/load/wallet exercises continue under the following gates.
|
||||
|
||||
### Gate 2 — establish a credible network · in progress
|
||||
|
||||
- **Mainnet candidate inspection (2026-10-28, read-only):** `wikideck-randomx-mainnet-1`, candidate chain ID `3ef36901f49d57127ebb0bf43625c7334e9b8ef817fd31953761723b5474ef6c`, genesis `72be7cdac8e625562bc8a49a82a406f41b96a6fde285c9961dceafdbaca8cd1d`, candidate genesis time 2027-01-01 00:00 UTC. The genesis time has not arrived; this is a candidate manifest, not an announced launch. No production node was started and no approval or data volume was created.
|
||||
- **Repeat benchmark (2026-10-28):** same executable and container limits, 32 hashes measured 25.11 H/s warm (p50 39.13 ms, p95 48.18 ms), 883 ms cold initialization, and 642 ms for a key/cache switch. These two short samples are not a confidence interval and do not establish normal host variability. Both are verification-path hashes, not miner throughput, dedicated-host measurements, aggregate network hashpower, or a security budget. Do not extrapolate them to choose consensus parameters.
|
||||
- The first bounded sample in the same container was 23.35 H/s warm (p50 41.09 ms, p95 55.81 ms), 796 ms cold initialization, and 594 ms for a key/cache switch. Together these two observations show run-to-run variation only; they are not intended-host miner measurements.
|
||||
- **Bootstrap policy for the first beta:** use a fixed, operator-curated HTTPS peer allowlist supplied as `WIKIDECK_PEERS`; do not add DHT, automatic discovery, or peers learned from untrusted network messages. Treat configured peers as trust/bootstrap contacts only; each node independently validates every block and transaction. The current protocol provides no peer identity or independent-operation proof beyond the configured URL and TLS connection.
|
||||
- **Local rehearsal status:** the complete offline suite's native RandomX HTTP integration syncs two separately started node processes over loopback, gossips a mined transfer, and verifies restart/replay. This is local functional evidence only. A disposable three-node Compose attempt on 2026-10-28 could not allocate a Docker bridge subnet because the configured address pools were exhausted; Compose failed before creating services or volumes. No multi-host/failure-domain rehearsal has been performed.
|
||||
- **Mainnet peer configuration remains empty/unverified:** the repository has no operator-supplied public HTTPS origins, ownership/failure-domain evidence, or exercised TLS endpoints for the two required mainnet peers. The compose profile only forwards operator-provided peers and origin; placeholders are not launch infrastructure.
|
||||
- For a multi-node rehearsal, use at least three separately administered nodes across distinct failure domains and network providers; record operator, host/region/provider, address/origin, chain ID, software digest, availability window, and which node supplies each bootstrap connection. Three containers or aliases on one host do not count as independent.
|
||||
- Measure expected and adversarial hashpower on intended hardware; quantify rewrite costs and concentration risk. Decide whether this experimental beta retains the current easy target or whether a future mainnet identity needs different initial difficulty, retarget, timestamp, and stall-recovery rules. Do not alter a launched chain's consensus.
|
||||
- Rehearse bootstrap with an empty node, peer loss/recovery, partition/rejoin, conflicting branches, stale/malicious configured peer, and independent mining. Record the observed tips, cumulative work, sync lag, and recovery time. Peer URL count alone is not evidence that peers are online or independent.
|
||||
- State the finality/reorganization limits honestly. There is no formal finality today; a confirmation count is not an absolute guarantee.
|
||||
|
||||
**Exit evidence:** explicit testnet-only beta decision (or separately approved mainnet candidate), documented bootstrap peer allowlist and ownership/failure domains, repeated intended-hardware hashrate data with variance, multi-host partition/reorg/stall rehearsal, a written risk/attack-cost estimate, and an operator-owned peer/upgrade/incident plan. Internal sign-off only; no external audit is planned.
|
||||
|
||||
### Gate 3 — demonstrate capacity and recovery limits
|
||||
|
||||
- Define the expected chain length, block rate, peer count, user/API load, disk budget, memory ceiling, startup time, and latency/error objectives.
|
||||
- Run repeated tests on populated long-chain fixtures and the intended hardware, with concurrent mining, peer sync, wallet/deck reads, transfers/trades, reorgs, and hostile traffic. Measure tail latency, memory, CPU, disk/WAL growth, and restart/revalidation time.
|
||||
- Test clean shutdown, process crash, disk pressure, corrupted/stale state, isolated restore, and replay using the exact release binary. Do not treat `/healthz` alone as readiness.
|
||||
- Define and test an archival, pruning, or capacity transition before resource growth can make nodes unable to sync or restart. Do not alter consensus history silently to reclaim storage.
|
||||
|
||||
**Exit evidence:** reproducible benchmark report with workload and limits, enforced resource budgets, failure/reorg results, and a successful isolated restore/replay drill.
|
||||
|
||||
### Gate 4 — wallet, trades, and user protection
|
||||
|
||||
- Test create, lock, unlock, backup, restore in a fresh browser/profile, and recovery after app/browser upgrade across the supported browser matrix. Test signed transfer/trade review, stale offers, pending state, and reorg outcomes.
|
||||
- Perform an internal threat review of same-origin script compromise, extensions, password entry, old plaintext-wallet migration, downloaded backup handling, and key-loss support. Decide whether the beta accepts browser-only signing; document residual risks rather than implying external wallet assurance.
|
||||
- Publish experimental/no-value disclosures, mining resource risks, irreversible-transfer warnings, privacy/data-retention terms, Wikipedia text/image attribution and license handling, support contact, abuse-report path, and stop conditions.
|
||||
- Select and publish an appropriate project software license before public distribution. A Wikideck license does not replace Wikipedia or dependency licenses.
|
||||
|
||||
**Exit evidence:** internal wallet threat review, verified recovery drill, tested trade flows, reviewed participant disclosures, public support/abuse contacts, and license decision.
|
||||
|
||||
### Gate 5 — operate and release safely
|
||||
|
||||
- Prefer a managed host unless the intended self-hosted site proves ISP permission, reachable ingress/CGNAT handling, DNS/TLS renewal, trusted-proxy correctness, no direct backend exposure, firewalling, restricted administration, patching, and power/network resilience.
|
||||
- Deploy independent monitoring and alert delivery for peer/tip freshness, reorgs, native miner failures, latency/queue saturation, disk/WAL growth, and service availability. Assign a real responder and incident/rollback procedure; do not imply HA or 24/7 response without it.
|
||||
- Encrypt backups off-host and rehearse cold restore. Define and meet recovery point/time objectives; protect wallet recovery separately from node data. Preserve database/WAL consistency and verify restored proofs/ownership before resuming service.
|
||||
- Freeze the release artifact: reviewed base-image digest, dependency/native versions, SBOM, vulnerability and native-code scans, build provenance/signature, configuration manifest, and rollback image. Re-run the complete suite and smoke test against that exact artifact.
|
||||
- Run a limited, non-monetary soak and record go/no-go, known risks, release hashes, operator, reviewer, and stop criteria. Do not convert a beta into mainnet by changing an environment variable or changing the chain identity after launch.
|
||||
|
||||
**Exit evidence:** intended-host deployment test, alert drill, off-host restore result with measured RPO/RTO, signed/frozen artifacts, exact-release test report, and recorded operator/reviewer approval for the beta's limited exposure.
|
||||
|
||||
## Release decisions
|
||||
|
||||
### Non-monetary public beta
|
||||
|
||||
May be considered only after all applicable gates pass, the node remains on the explicitly labeled experimental testnet, cards are not represented as valuable, and the operator accepts the single-host or stated availability limits. This is a production-hosted beta, not production chain security.
|
||||
|
||||
### Mainnet or valuable cards
|
||||
|
||||
Requires all gates plus measured and operator-accepted consensus/economic risks, calibrated and frozen consensus parameters, a credible peer/miner topology, reviewed wallet protections, sustained capacity and adversarial evidence, and explicit governance/upgrade and incident procedures. External audit is intentionally not a requirement under the operator's decision, so residual risk remains higher and must be explicitly accepted before any mainnet activation. Until then, retain **NO-GO**.
|
||||
|
||||
### Markets and real money
|
||||
|
||||
Out of scope for this product plan. No market/custody/payment milestone is a prerequisite to the card-only beta. Any later decision to add sales or funds requires a new product scope, threat model, legal review, settlement/custody design, and separately approved release plan; it must not be inferred from this document.
|
||||
|
||||
## Evidence record
|
||||
|
||||
For every gate, record an owner, exact release/manifest, test or rehearsal, measured result, artifact location/hash, reviewer, residual risk, and disposition. Missing evidence is **unverified**, not passed. A successful build, local container startup, peer count, test total, or environment flag is never launch approval by itself.
|
||||
Executable
+62
@@ -0,0 +1,62 @@
|
||||
# Consensus calibration and adversarial analysis — stage 3
|
||||
|
||||
**Decision remains NO-GO.** This stage supplies reproducible offline tools and bounded measurements, not new launch parameters or an operational network. Consensus, initial target, genesis, and chain identity are unchanged.
|
||||
|
||||
## Tools and safety boundaries
|
||||
|
||||
- `analyze:consensus`: deterministic JSON report calling the **actual** `expectedTarget`, `medianTime` and `workFor` helpers. Six 60-record traces: steady 900-second spacing, rapid one-second spacing, slow 7,200-second spacing, a seven-day pause at a window boundary, minimum-median timestamps with normally advancing wall time, and timestamps at the allowed +120-second future edge.
|
||||
- Records contain only fields read by those mathematical helpers. They are **not valid blocks**, have no PoW/signatures/state commitments, are never submitted to a ledger and do not add a fake-PoW production option. Spacing is externally imposed rather than sampled from constant-hashrate mining. The comparison of cumulative work is not proof that a miner can actually produce those traces.
|
||||
- `benchmark:randomx`: requires `--consent`, uses the trusted local reference binary and accepts `--hashes` (4–256, default 32), optional `--fast` (explicit >2.3 GiB memory choice), and `--network-hps` (integer 1–10^12, default 1). Unknown/invalid arguments fail before starting native work. A 60-second wall-time deadline closes the engine; native requests also retain their own bounds. No endpoint, wallet, ledger, payout or launch option exists. CPU/energy consumption is explicit.
|
||||
- Benchmark output includes executable SHA-256, platform/Node version, mode, cold hash time, repeat hash, warm sequential IPC-inclusive rate, hash latency samples and one epoch/cache switch. The declared network hashrate is a **hypothesis**, never inferred by multiplying a local worker rate by an assumed number of machines. Output targets are illustrative strings only and are never written into consensus/configuration.
|
||||
|
||||
These tools can be invoked via the package scripts on Node 24. The Docker test target includes the trusted Linux-native engine; offline benchmark execution can use a network-disabled disposable container. Do not benchmark a production node or enable fast mode without an explicit memory/energy budget. Keep reports with the exact image/native digest and host/container constraints.
|
||||
|
||||
## Verified arithmetic and interpretation
|
||||
|
||||
Wikideck interprets the 256-bit RandomX output as a big-endian integer and accepts hash <= target. Assuming uniform hash output, success probability is `(target + 1) / 2^256`, expected trials are `2^256 / (target + 1)`, and expected seconds at H hashes/s are expected trials divided by H. These are expectations, not schedules or upper time bounds.
|
||||
|
||||
The current `0fff…ffff` maximum/initial target requires **16 expected hashes**. The 15-minute setting controls the retarget goal; it does not force miners to wait 15 minutes before submitting an initially easy block. For a specified integer network rate H and goal S seconds, the analysis computes `floor(2^256 / (H*S)) - 1`, bounded by the existing allowed target range. This formula assumes stable aggregate honest work and does not choose a safe launch rate, security budget, or rounding-independent exact interval.
|
||||
|
||||
The report also includes `(q/(1-q))^z` for q < 0.5 (1 for q >= 0.5), under an idealized fixed-difficulty Poisson race with attacker share q and a **known current deficit** of z blocks. This is the probability of ever catching up under those assumptions, **not** a guarantee that a block is final: attacker progress while a block accumulates confirmations is not modeled. There is no network latency, eclipse, selfish mining, timestamp manipulation, adaptive difficulty, economic cost or attack detection model.
|
||||
|
||||
## Local native measurement (2026-10-20)
|
||||
|
||||
One sample, offline Linux x64 Docker, **1 CPU quota / 1 GiB memory limit**, Node v24.22.0, light mode, 32 sequential warm hashes, existing pinned RandomX v1.2.1 implementation:
|
||||
|
||||
| Measurement | Result |
|
||||
| --- | --- |
|
||||
| Executable SHA-256 | 5e1b99ea9df075d75a676a4ea1090e62d7738d03ac6316c41f59dedfffac4318 |
|
||||
| Cold hash (including initialization/startup) | 7,379 ms |
|
||||
| Immediate repeated same-key hash | 1,520 ms |
|
||||
| Warm batch elapsed | 16,645 ms |
|
||||
| Warm sequential IPC-inclusive rate | **1.9225 H/s** |
|
||||
| Warm p50 / p95 individual request | 552 / 755 ms |
|
||||
| One epoch/cache switch hash | 4,226 ms |
|
||||
| Initial expected interval at this measured single-worker rate | **8.323 seconds** |
|
||||
|
||||
The host was **not a dedicated controlled benchmark machine**; co-tenancy, scheduling, container CPU throttling, virtualization and thermal conditions were not instrumented. The surprising repeat-vs-warm timings reinforce that one short sample cannot certify capacity. It is a `hash`/verification-path measurement with per-request IPC, not native miner batch throughput; it includes neither full block validation nor state-root/SQLite/network overhead. Cache initialization/switch measurements are single samples, not statistically characterized distributions. Fast mode was not measured. Node `process.memoryUsage` would omit the native subprocess, so no misleading whole-engine RSS claim is made.
|
||||
|
||||
For an **explicitly hypothetical** aggregate 100 H/s network and 900-second target, the tool produced illustrative target `0000ba69dbdd3ac13d7c4a74c8111cb7aecee4bd24e8d5b85d9222dc8bfdff5b` (90,000 expected hashes). This was **not applied** and is not a recommendation. Expected rewrite cost, miner diversity, rate variance, launch surge/decline and adversarial hashrate remain unmeasured.
|
||||
|
||||
## Deterministic scenario findings
|
||||
|
||||
1. **Steady/slow at maximum target:** stable 900-second spacing retains the initial target; 8× slower spacing cannot ease above maximum. There is no wall-clock emergency easing during a stall.
|
||||
2. **Rapid trace:** difficulty does not change before the ten-block boundary. After ten one-second-spaced records the target is quartered by the elapsed-time floor (`floor(8100/4) = 2025 seconds`). Further imposed rapid windows keep increasing required work; this does not mean the same hashrate could sustain those arrival times.
|
||||
3. **Seven-day boundary pause:** the pause is placed before the first block of the third ten-block window. The endpoint difference within that window excludes the inter-window gap. This illustrates a boundary blind spot, not a fully executed exploit: the choice of where a pause occurs matters and requires review of alternative retarget designs. The current maximum target also limits what this particular trace can reveal about recovery from already-high difficulty.
|
||||
4. **Median-minimum trace:** timestamps just above the actual prior median can satisfy timestamp bounds while lagging wall time substantially. Retarget uses timestamp differences, not independently verified elapsed mining time. Analyze time-warp/oscillation attacks with quantified hashpower and viable block production before selecting rules.
|
||||
5. **Constant future-edge offset:** +120 seconds is allowed by the current timestamp rule. A constant offset cancels out of interval differences; it is not by itself evidence of a difficulty exploit. Clock skew and shifting offsets still need adversarial analysis. Validator tests reject timestamps at/below median or beyond future allowance **before proof work** and exercise the exact allowed edge reaching subsequent commitment checks.
|
||||
6. **Partition work comparison:** synthetic steady and rapid branches have different accumulated work, and the existing strict-work rule favors the greater-work branch. Existing ledger/native integration tests cover real validated branch/reorg handling separately. The synthetic report is not a multi-host partition, adversarial mining or economic-security rehearsal.
|
||||
|
||||
## Validation and remaining launch evidence
|
||||
|
||||
Build and full regression suite passed **92 tests, 0 failed, 0 skipped**, including real RandomX HTTP integration and six new calibration/timestamp tests. Tests assert inclusive-target arithmetic, bounded inputs, exact window/clamp behavior, reproducibility, scenario timestamp bounds, probability assumptions, consent refusal before engine access and validator timestamp rejection. There is no claim of formal consensus correctness.
|
||||
|
||||
Before production calibration can be approved:
|
||||
|
||||
- Benchmark the exact final native/image build on multiple intended independent miner/validator hosts; record CPU model, operating mode, effective quotas, competing load, cold/warm/cache-switch distributions and batch-miner throughput. Use repeated runs and documented confidence/variance, not this single sample.
|
||||
- Establish credible aggregate honest hashrate, launch ramp/surge/drop ranges and attack/rewrite cost assumptions; independently review target choice and the inherited ten-block/clamped endpoint retarget, including gaps across windows and timestamp attacks.
|
||||
- Add stochastic mining/partition/clock/adaptive-hashpower models and sustained multi-host rehearsals with valid reference proofs, full state commitments, mempool/reorg persistence, networking and realistic resource pressure.
|
||||
- Freeze the selected rules and actual genesis date with a new identity where required. Publish the manifest/artifact hashes and record internal findings plus explicit operator risk acceptance. Do not patch an active chain's rules in place.
|
||||
- Document reorganization and confirmation limitations honestly; no finite confirmation count is absolute finality on this unbootstrapped PoW chain.
|
||||
|
||||
See [security review](security-review.md), [launch evidence register](operator-launch.md) and the read-only preflight. The calibration blocker remains open.
|
||||
Executable
+57
@@ -0,0 +1,57 @@
|
||||
# Mainnet-first, gated candidate
|
||||
|
||||
The repository defaults to a **mainnet candidate**, not an audited or launched production network. Shipping code does not establish independent miners, economic security, or an operational public network. Market and payment features have been removed from the card-only product.
|
||||
|
||||
**Current production decision: NO-GO.** See the [2026-10-20 security review](security-review.md) and [operator evidence register and launch procedure](operator-launch.md). The read-only `launch:preflight` package script (or node entry-point `--launch-preflight`) reports local gates and unresolved blockers without starting a node or setting approvals. It deliberately exits nonzero while production readiness is blocked.
|
||||
|
||||
## What is implemented
|
||||
|
||||
| Property | Mainnet-first default | Explicit development fixture |
|
||||
| --- | --- | --- |
|
||||
| Selection | `WIKIDECK_NETWORK=mainnet` (or unset) | `WIKIDECK_NETWORK=testnet` |
|
||||
| Network ID | `wikideck-randomx-mainnet-1` | `wikideck-randomx-testnet-6` |
|
||||
| Protocol | Version 6 | Existing fixed local testnet version 6 |
|
||||
| Genesis timestamp | Fixed launch default: 2026-11-01 00:00 UTC (`1793491200`); ready to start now | Fixed at 2026-10-19 00:00 UTC |
|
||||
| Ledger directory | mainnet | testnet |
|
||||
| Browser wallet | IndexedDB name derived from exact chain ID | Separate chain identity |
|
||||
| New backup | v2 header authenticates exact chain ID | Same v2 format; legacy v1 is accepted only on the original testnet |
|
||||
|
||||
Both profiles use real RandomX validation, cumulative-work branch selection, the 15-minute target, mint maturity, signed NFT transfers, atomic card trades, and SQLite persistence. The mainnet candidate has no premine, token migration, hidden checkpoint, or fake-PoW option. Changing the network identifier and candidate genesis derives distinct chain/genesis IDs, RandomX initial keys, signature domains, and consensus manifest checks. Existing testnet values and data remain unchanged.
|
||||
|
||||
**Cards and backups do not migrate across chain identities.** Browser storage and backup headers are bound to the exact chain ID; peers reject mismatched manifests, and transactions are checked against the active chain. The selected profile and genesis are pinned at startup/browser build, never silently adopted from an RPC response. The original fixed-testnet v1 backups remain supported only on that testnet.
|
||||
|
||||
## Inspect without launching
|
||||
|
||||
The node entry point accepts `--network-manifest`. With `WIKIDECK_NETWORK=mainnet` set in the process environment, this prints the candidate network/chain/genesis/timestamp and exits **before** opening databases, starting a RandomX engine, listening, or contacting peers. It does not require activation credentials or approval and does not launch anything.
|
||||
|
||||
Browser builds select `VITE_WIKIDECK_NETWORK=mainnet`; server/miner processes select `WIKIDECK_NETWORK=mainnet`. These must match. `WIKIDECK_GENESIS_TIME` is also part of the chain identity. Docker's network/genesis **build arguments** pin the browser and immutable image manifest; a runtime variable cannot alter an already compiled UI. To choose another genesis, set it for the image build and runtime together, publish the resulting manifest, then use that immutable image and exact timestamp for every node and browser. A runtime-only genesis override fails closed. A mismatched UI rejects the node rather than signing for it.
|
||||
|
||||
## Explicit launch gates
|
||||
|
||||
[compose.yaml](../compose.yaml) and [compose.mainnet.yaml](../compose.mainnet.yaml) are mainnet-first definitions, use separate named volumes, bind only loopback for an external HTTPS reverse proxy, and do **not** auto-restart a failed candidate. They have no default peers, approval, or public origin. The default service therefore exits until the operator supplies real deployment values. The testnet Docker fixture is separate and explicit. **This code change does not establish or certify a public mainnet.**
|
||||
|
||||
Starting the candidate node or native miner requires all of:
|
||||
|
||||
1. `WIKIDECK_MAINNET_LAUNCH` equals the exact candidate chain ID printed by the manifest command.
|
||||
2. `WIKIDECK_MAINNET_RISK_ACK=operator-accepts-unaudited-risk` records the operator's explicit acceptance that no external audit is planned and residual risks remain. This is not a safety certification; the manifest honestly reports `audited: false`.
|
||||
3. The candidate genesis timestamp has arrived. If the operator changes the timestamp or consensus manifest before launch, the chain ID also changes and the old approval is invalidated. Freeze and publish the final manifest before launch.
|
||||
4. An HTTPS `PUBLIC_ORIGIN` and at least two distinct configured peer origins. Public deployments require public HTTPS peers by default. An operator-controlled private candidate cluster may explicitly set `WIKIDECK_ALLOW_PRIVATE_PEERS=1` to permit private/HTTP peer RPC; this does not relax browser HTTPS, chain approval, risk acknowledgment, or genesis-time gates. Configuration count cannot prove the peers are online, independent, or sufficiently distributed; verify that operationally. Existing public peer DNS/origin controls still apply when the private opt-in is off, and HTTPS certificate verification is never disabled.
|
||||
|
||||
For Kubernetes public peering, give every node a stable, unique public HTTPS endpoint. For an explicitly private candidate, separate single-replica Deployments, Services and exclusive PVCs may use distinct ClusterIP DNS origins with the private opt-in. Each validator lists the other two internal Services, never itself. The browser-facing node needs a TLS ingress/reverse proxy; internal-only validators may share that HTTPS browser origin without their own ingress. NetworkPolicy must restrict peer RPC to the selected validator pods on target TCP port 3001. Private HTTP RPC is unauthenticated and unencrypted unless protected by an independently configured mesh; do not use it across untrusted networks. Three validators in one cluster/storage failure domain are not independent operators or proof of production readiness.
|
||||
|
||||
See the [Kubernetes mainnet preparation checklist](../deploy/kubernetes/README.md) for the deployment contract and the values needed before manifests can be finalized.
|
||||
Unknown profile names fail closed. A direct import of the HTTP entry point is also guarded. Library-level manifest/consensus/storage inspection remains possible for tests and review; activation gates are accident prevention and operator risk acceptance, not a security boundary against someone who edits the source.
|
||||
|
||||
## Required before an actual production launch
|
||||
|
||||
- **Internal readiness review:** track code findings and regression evidence in-repository. No external audit is planned; the operator accepts the additional risk and must not describe the candidate as audited or certified.
|
||||
- **Security model:** quantify hashpower, chain-rewrite cost and majority attacks. The inherited easy initial target and ten-block retarget are **not production-calibrated**. Choose reviewed launch difficulty/retarget behavior and test start-up/stall/recovery scenarios. Independent mining participation and sufficient hashpower are not provided by running multiple containers on one host.
|
||||
- **Adversarial rehearsal:** sustained multi-host simulations, partitions/eclipses, competing branches, deep reorganizations, clock skew, bad peers, load/mempool saturation, corrupted state and restart recovery. Test the candidate rules with no valuable assets.
|
||||
- **Operations:** independently administered public HTTPS nodes and miners across failure domains, pinned release artifacts and dependencies, firewall/reverse proxy/rate-limit policies, bounded storage and sync strategy, metrics, alerts, rotation and emergency-response procedures.
|
||||
- **Recovery:** verified offline wallet recovery, consistent ledger snapshots, full replay/revalidation, documented incident and fork handling. No silent ledger reset or reuse of testnet data.
|
||||
- **Launch governance:** select the actual genesis date, freeze the identity/configuration, publish binaries and configuration hashes, agree peer/miner readiness and upgrade policy, and explicitly record the operator's risk acceptance. Do not treat the flag as proof of safety or peer readiness.
|
||||
- **User and legal review:** no monetary value or security is promised. NFT rights do not imply article/image copyright ownership. Publish experimental-use, privacy, support, and abuse-report terms appropriate to the release.
|
||||
|
||||
## Still not production-ready
|
||||
|
||||
This remains experimental software. Nodes retain the DAG and overlays in memory, rebuild state roots over the full NFT set, and revalidate proofs at startup. The HTTP UI trusts its connected node rather than independently validating the chain. There is no automatic peer discovery, established bootstrap infrastructure, formal finality, independent audit, or production abuse system. Mainnet selection does not remove any of these limitations. See the [card-only production plan](card-only-production-plan.md) for the current readiness gates.
|
||||
Executable
+45
@@ -0,0 +1,45 @@
|
||||
# Node resource controls — stage 1
|
||||
|
||||
This stage bounds public RPC transport/admission and browser state loading without changing consensus, chain identity, or launch approvals. **It does not make the node production-ready.**
|
||||
|
||||
## Admission and transport
|
||||
|
||||
API and P2P traffic have separate process-local token buckets. API saturation does not spend P2P tokens. Both lanes still share the event loop, ledger mutation queue, native engine and host resources: this is admission separation, not CPU/process isolation. Budgets are global per process rather than per wallet/IP, so changing wallet or source IP cannot bypass the global limit. Existing per-IP limits remain in place; no permissive proxy trust was enabled.
|
||||
|
||||
Admission precedes JSON parsing, signature verification and route work. Excess traffic receives 503 plus `Retry-After: 1`; there is no unbounded retry queue. JSON request bodies remain capped at 68 KiB and compressed request inflation is disabled. Response JSON is capped before transmission. Byte budget is charged on serialized JSON bytes, not network/TLS framing. Small error responses are not charged recursively when rejecting a payload/budget; edge limits are still needed.
|
||||
|
||||
| Variable | Default | Accepted range |
|
||||
| --- | --- | --- |
|
||||
| `WIKIDECK_RPC_REQUESTS_PER_SECOND` | 120 | 1–1000 |
|
||||
| `WIKIDECK_RPC_WRITES_PER_SECOND` | 10 | 1–100 |
|
||||
| `WIKIDECK_RPC_MAX_ACTIVE` | 32 | 1–256 |
|
||||
| `WIKIDECK_RPC_RESPONSE_BYTES` | 2097152 (2 MiB) | 128 KiB–2 MiB |
|
||||
| `WIKIDECK_RPC_BYTES_PER_SECOND` | 8388608 (8 MiB/s) | 2–128 MiB/s |
|
||||
| `WIKIDECK_RPC_LEGACY_BLOCKS` | 128 | 0–128 |
|
||||
|
||||
Token bursts equal one second of the configured rate. P2P uses a separate fixed lane of 40 requests/s, 10 writes/s and 16 active HTTP responses, with the same configured byte limits. Active slots are released on response finish/connection close; asynchronous route work already admitted may still be completing after disconnection. Existing bounded native, ledger and Wikimedia queues/concurrency remain necessary.
|
||||
|
||||
HTTP has at most 256 accepted connections, 10-second headers timeout, 15-second request timeout, 5-second idle keepalive and a 30-second socket inactivity timeout. These are process transport protections, not a substitute for proxy timeouts/connection limits, global ingress limits, OS limits or load-balanced per-instance policy. Body parsing is no longer ahead of admission. Health bypasses the API lanes for liveness, but still shares server connections and CPU.
|
||||
|
||||
Configuration is validated before opening node resources. Lower limits can shed legitimate traffic, including sync; test settings against the intended topology before deploying. Do not raise limits merely to hide overload. Custom settings require container environment forwarding when deploying with Compose.
|
||||
|
||||
## Bounded state API
|
||||
|
||||
- `GET /api/summary`: network/chain/genesis, canonical tip/height/work, process-local revision, peers/maturity and bounded pending transactions. No full-chain copy. Revision is only a local cache invalidation counter, not consensus or a durable sequence.
|
||||
- `GET /api/canonical?tip=<hash>&offset=0&limit=16`: up to **16 canonical blocks**, pinned to the current tip. Tip changes return 409 so the caller restarts. Offset cannot exceed current height. Each page uses the cached canonical branch and clones only its slice. The first page after a new tip may rebuild that branch in O(height).
|
||||
- Legacy `/api/state` and `/api/chain` still return full snapshots only up to the configured legacy height and response-byte cap. Beyond it, they return 413 with paging instructions **before full snapshot cloning**. They never silently truncate ownership history. Legacy scripts/API clients must migrate to paging for longer chains.
|
||||
- `/p2p/status` and `/p2p/mempool` now obtain only the fields needed instead of cloning the chain. P2P block inventory semantics are unchanged.
|
||||
|
||||
The browser and trade signing refresh use bounded streaming response reads (2 MiB/page), pagination/network/tip/parent continuity checks, one restart on tip conflict, shared in-flight loads, and append-history cache reuse. A final summary confirms the loaded tip. These checks are **not independent PoW/consensus verification**: the UI still trusts the node.
|
||||
|
||||
The existing snapshot-based UI still assembles complete ownership history, capped at **10,000 blocks or 64 MiB of encoded history** with a 30-second fetch deadline. If exceeded, it fails explicitly rather than presenting partial ownership as complete. That is a client resource guard, not a solution for an archival-sized wallet/explorer. Full state objects and parsed JS memory cost more than encoded JSON. A truly indexed wallet and fully paged explorer remain required before closing SEC-01. An unchanged refresh requests two summaries and no block pages; append loading verifies the cached prefix and requests only new blocks.
|
||||
|
||||
## Tests and safe load tooling
|
||||
|
||||
Final validation: **86 tests passed, 0 failed, 0 skipped** in the Node 24 Docker test image, including real RandomX HTTP integration. TypeScript/Vite build passed. The suite includes deterministic token refill/write/active/byte limits; a held-response 40-request loopback burst (4 admitted, 36 shed); health/P2P-lane availability during API saturation; canonical paging defensive copies/tip conflicts/legacy guards; client append caching, malformed pages/continuity, bounded history, single-flight loading and response size checks; non-recursive oversized-response errors; and paging against the real RandomX HTTP fixture.
|
||||
|
||||
`test:rpc-load` invokes the read-only [local smoke-load script](../scripts/rpc-load.ts). It requires `--consent`, accepts only **literal loopback IP origins** (not remote hosts or DNS names), at most 32 workers and 1,000 total requests. Options: `--node`, `--concurrency`, `--requests`. It reads summary and canonical-chain endpoints and checks health, with response/time bounds. It never submits a block or transaction. Use only a disposable isolated node: even read traffic consumes CPU/IO. Output includes status counts, bytes and p50/p95 latency; rejected load is visible, not counted as successful capacity.
|
||||
|
||||
Initial smoke measurements on a disposable empty-chain loopback Docker node are historical local baselines, not performance guarantees. They do not measure hashing load, long-chain costs, or hostile Internet traffic. The deterministic held-response test separately verifies overload shedding and peer/health-lane availability. Final test totals must be rerun against the selected release artifact.
|
||||
|
||||
Neither measurement establishes that a realistic long-chain public deployment meets its latency/throughput SLO. Production-scale chain/journal fixtures, slow clients, adversarial payloads, sustained concurrency, disk pressure and multi-host recovery measurements remain outstanding. No launch-readiness finding is marked resolved by this stage.
|
||||
Executable
+42
@@ -0,0 +1,42 @@
|
||||
# Deployment, private monitoring, and recovery
|
||||
|
||||
**Production remains NO-GO.** These procedures are operator preparation, not an installed public edge, configured alerts, independent review, or deployment approval. Do not remove persistent node volumes as part of source cleanup.
|
||||
|
||||
## Reverse-proxy trust
|
||||
|
||||
`WIKIDECK_TRUSTED_PROXIES` is empty/untrusted by default. It accepts up to 16 comma-separated literal IPs or bounded CIDRs. Configure only the actual source address of a trusted edge connection. The edge must overwrite forwarded headers, and firewall rules must prevent direct backend access. A trusted-proxy setting does not configure the firewall.
|
||||
|
||||
The [nginx example](../deploy/nginx.conf.example) is not installed or deployment-validated. Replace its reserved hostname and certificate paths, and validate TLS, renewal, forwarded-IP handling, body/connection/time limits, and edge admission controls on the intended host. Container proxy source addresses may differ from host loopback; inspect the actual topology instead of copying a trust list blindly.
|
||||
|
||||
## Private diagnostics
|
||||
|
||||
Set `WIKIDECK_OPS_PORT` to an unused port (1–65535) to enable a separate HTTP listener bound to `127.0.0.1`; leave it unset to disable it. Never publish or reverse-proxy this port. Only `GET /status` is supported. It returns bounded, no-store aggregate JSON; any local process with loopback access can read it.
|
||||
|
||||
Reported diagnostics include node uptime/RSS, event-loop delay, canonical height and tip age, DAG and pending counts, native-engine status, configured/connected peers, RPC budgets, and available disk plus ledger database/WAL sizes. Native subprocess memory is not included in Node RSS. Peer count is not a security score. Health is liveness, not sync readiness or consensus assurance.
|
||||
|
||||
`ops:check -- --port <private-port>` reads only literal loopback with bounded time and response size. It emits warnings for stale/empty chain, peer isolation, native-engine failure, queue pressure, event-loop delay, and unavailable/low disk. Thresholds are starting points, not production SLOs. Route alerts to an external system and test delivery; this repository does not install a scheduler or alert destination.
|
||||
|
||||
## Stopped-node ledger backup
|
||||
|
||||
`node:backup` supports `create`, `verify`, and `restore`. New version-2 packages contain only the selected network's ledger database. Stop the node cleanly first; the tool takes the same exclusive network lock as startup, uses SQLite `VACUUM INTO`, checks SQLite integrity and chain metadata, hashes the snapshot, and writes the manifest last. It refuses to overwrite a destination, restore in place, or run when a node lock exists. Restore targets must not exist.
|
||||
|
||||
Example workflow, using the `node:backup` package command with explicit `--source`, `--destination`, and `--consent` as required by the CLI:
|
||||
|
||||
1. Stop the node and confirm all writers to its data directory are stopped.
|
||||
2. Create the backup in a new directory outside the node data directory. Keep wallet backups separately; this tool does not include browser wallets, secrets, configuration, binaries, or logs.
|
||||
3. Run `verify` against the backup before copying it off-host.
|
||||
4. Restore only to a new isolated data directory. The tool reports `proofsRevalidated: false`; reopen with the trusted RandomX implementation and verify the chain before resuming service.
|
||||
|
||||
Backups are not encrypted or authenticated by this tool. Store them in restricted, encrypted off-host storage, protect the manifest separately, and test cold restoration. Hashes detect accidental corruption, not a coherent malicious replacement. Filesystem, power-loss, Windows ACL, and network-share durability need validation on the actual storage platform.
|
||||
|
||||
## Retiring legacy financial data safely
|
||||
|
||||
Removing market/custody source code does **not** delete or resolve data left by an older deployment. The card-only node does not open or reconcile those files. If the selected network directory contains `market.sqlite`, `custody-sandbox.sqlite`, or a `custody-postgres.json` binding, new ledger-only backup creation refuses to proceed rather than silently omit those records. Preserve the existing volume and any PostgreSQL database; arrange an operator-reviewed archive before retiring it.
|
||||
|
||||
Historical version-1 paired backups containing `ledger.sqlite` and `market.sqlite` remain verifiable and restorable as opaque files for preservation. Restoration does not reactivate market services or imply that financial obligations have been resolved. External PostgreSQL data is not contained in a SQLite package. Do not delete old volumes, journals, database credentials, or backups as part of this source cleanup.
|
||||
|
||||
An operator must choose retention, recovery point/time objectives, off-host storage, and restore ownership. Never remove a lock or replace a running volume just to make recovery succeed.
|
||||
|
||||
## Remaining operational evidence
|
||||
|
||||
Verify the intended HTTPS/proxy/firewall topology, peer and tip freshness, alert delivery, disk growth, backup encryption/retention, and restore/replay time on the actual host. Assign an incident responder and document stop/rollback criteria. See the [card-only production plan](card-only-production-plan.md), [operator launch register](operator-launch.md), and [security review](security-review.md). No startup or payment approval is granted by these tools.
|
||||
Executable
+60
@@ -0,0 +1,60 @@
|
||||
# Operator launch preparation and hold points
|
||||
|
||||
The [card-only production readiness plan](card-only-production-plan.md) defines the product scope and release gates. The supported product is mining, collecting, and direct card-for-card trades. Market, custody, and payment source has been removed. **Current decision: HOLD / NO-GO for mainnet, valuable assets, or public exposure until the relevant evidence below is reviewed.**
|
||||
|
||||
A successful build, local container startup, peer URL count, genesis timestamp, or configured approval flag is not proof of network security. The read-only launch preflight reports local blockers; it does not certify the release. The operator has chosen not to commission external audits and accepts the corresponding residual risk.
|
||||
|
||||
## Safe inspection
|
||||
|
||||
Use Node.js 24 and a clean, reviewed checkout. For mainnet assessment, select `WIKIDECK_NETWORK=mainnet` and run `launch:preflight`. Use `--network-manifest` for chain identity inspection. These commands do not start a node, create a ledger, start RandomX, or contact peers.
|
||||
|
||||
Mainnet startup is gated by a chain-specific approval, explicit unaudited-risk acceptance, the candidate genesis time, HTTPS origin, and multiple configured HTTPS peers. These are accident-prevention checks only; they do not prove peers are online/independent or that a network is secure. Keep deployment and release permissions restricted.
|
||||
|
||||
## Evidence required before a public non-monetary testnet beta
|
||||
|
||||
A beta is not mainnet production, valuable-asset use, or a claim of independent network security. Keep testnet explicitly selected, cards valueless, and the deployment described as experimental. Before public exposure:
|
||||
|
||||
- Freeze the card-only scope, testnet identity, known risks, privacy and data-retention terms, user support/contact, abuse-report path, and stop conditions.
|
||||
- Prefer managed hosting unless the intended host proves ISP permission/reachability, TLS renewal, trusted-proxy correctness, no direct backend exposure, firewall and admin controls, patching, and power/network resilience.
|
||||
- Run repeated load, mining, peer-sync, reorganization, crash/restart, and restore tests on the exact release candidate and intended hardware. Set measurable latency, error, memory, CPU, disk, and recovery limits.
|
||||
- Review wallet migration, lock/unlock, signing, backup, and fresh-profile recovery. Explain same-origin compromise and irreversible-transfer risks.
|
||||
- Deploy external monitoring and verify alert delivery for peer/tip freshness, reorgs, native failures, overload, disk growth, and service availability. Name a responder and test incident/rollback procedures.
|
||||
- Encrypt backups off-host and rehearse a cold restore/replay. Define recovery objectives and keep wallet backups separate from node backups.
|
||||
- Freeze image/dependency/native versions, generate an SBOM, scan the final artifacts, and record exact release hashes and a beta go/no-go. Publish a project license before public distribution.
|
||||
|
||||
If these conditions cannot be verified on a self-hosted site, do not expose it publicly; use a managed host or remain local-only.
|
||||
|
||||
## Mainnet and valuable-card blockers
|
||||
|
||||
Mainnet or valuable-card use requires the beta evidence plus internally reviewed and measured consensus parameters, adversarial multi-host partition/reorg/hashpower testing, a credible bootstrap and independent-peer/miner plan, production capacity evidence, and explicit governance/upgrade and incident procedures. External audit is not planned; the operator must record acceptance of this extra risk. The current initial target is deliberately easy and the retarget is not production-calibrated. Nodes retain the DAG in memory and replay proofs at startup. There is no formal finality or automatic peer discovery. Keep mainnet **NO-GO** until the remaining network and operational evidence is recorded and the operator explicitly accepts the residual risks.
|
||||
|
||||
## Hold-point register
|
||||
|
||||
Record owner, exact release/manifest, evidence location and hash, test/rehearsal, reviewer, residual risk, and disposition. Empty or unverified means **not passed**.
|
||||
|
||||
| Hold point | Required evidence | Current status |
|
||||
| --- | --- | --- |
|
||||
| Internal protocol/application review | Findings documented against the release; fixes have regression coverage; operator records unaudited-risk acceptance | Internal review and regression tests recorded; no external audit planned |
|
||||
| Consensus launch design | Final manifest; measured starting target/hashpower; timestamp, retarget, partition, and reorg analysis | Missing |
|
||||
| Node availability | Realistic long-chain and hostile-traffic capacity report with explicit limits | Missing |
|
||||
| Production wallet | Same-origin threat model, user confirmation, supported-browser lifecycle and offline recovery drill | Unverified |
|
||||
| Peer/miner topology | Independently administered hosts/failure domains, verified online peers, and sufficient independent hashpower | Unverified |
|
||||
| Edge and host security | TLS/certificate renewal, firewall, backend isolation, proxy trust, patching and restricted admin | Unverified |
|
||||
| Monitoring/on-call | External alert routes and successful drills for isolation, reorg, native failure, overload and disk pressure | Unverified |
|
||||
| Restore/reconciliation | Encrypted off-host ledger backup, isolated restore and proof replay; separate wallet recovery | Unverified |
|
||||
| Release supply chain | Frozen image digest, SBOM, dependency/native/container scans, provenance, and restricted release access | Partial; repeat for final artifact |
|
||||
| Incident/governance | Named decision owners, upgrade/fork policy, communications, rollback and stop criteria | Missing |
|
||||
|
||||
## Freeze and authorize
|
||||
|
||||
1. Fix known critical code findings in a new release and add regression coverage; do not change consensus rules under a launched chain ID. This is internal engineering, not external audit.
|
||||
2. Freeze the reviewed genesis/manifest, exact build/runtime profile, image digest, peer origins, and artifact hashes.
|
||||
3. Rehearse initial sync, independent mining, partitions/reconnects, invalid traffic, disk pressure, restart, and isolated full restore. Do not delete production data or weaken rules to pass a drill.
|
||||
4. Verify DNS/TLS, proxy forwarded-IP handling, no direct backend access, permissions, resource capacity, and monitoring on intended hosts. `/healthz` is liveness only.
|
||||
5. Record a go/no-go with operator name, evidence, known residual risks, change window, recovery objectives, and stop conditions in the controlled operations system.
|
||||
|
||||
Do not set the risk acknowledgement merely to bypass gates. It records acceptance of known risks; it does not attest that an audit occurred or make the system secure.
|
||||
|
||||
## Stop conditions
|
||||
|
||||
Treat deep reorgs, isolation, divergent manifests, sustained overload, disk exhaustion, native-engine failure, key compromise, or loss of monitoring as incidents. Stop accepting new exposure using the approved procedure. Stopping a node does not undo confirmed transactions. Preserve logs and backups; never delete the ledger, shorten history, or change genesis to get a node to start. Remove a stale lock only after verifying all owning processes have exited. Resolve chain conflicts under a reviewed incident policy, not with an ad hoc checkpoint or stale restore.
|
||||
@@ -1,8 +1,8 @@
|
||||
# Native RandomX engine
|
||||
|
||||
This is the CPU proof engine used by the Wikideck testnet node and explicit opt-in
|
||||
native miner. Mining runs only when the operator starts the native CLI; the browser
|
||||
does not mine or launch native programs. The Docker image builds this pinned
|
||||
Wikideck's native CPU proof engine uses RandomX, the algorithm also used by Monero,
|
||||
designed to keep general-purpose consumer CPUs competitive. Mining a Wikideck block
|
||||
rewards its miner with a Wikipedia NFT card on Wikideck's independent chain. The Docker image builds this pinned
|
||||
implementation and runs its known-vector test. There is no GPU path, simulated hash,
|
||||
or production fallback hash.
|
||||
|
||||
|
||||
Executable
+31
@@ -0,0 +1,31 @@
|
||||
# Security review and production readiness
|
||||
|
||||
## Decision: NO-GO for public production or valuable assets
|
||||
|
||||
This is an internal source-assisted readiness review, not a penetration test, formal consensus proof, or production certification. The operator has chosen not to commission external audits and accepts that residual risk. The card-only product consists of RandomX mining, wallets, collections, transfers, and direct atomic card trades. Market, custody, and payment components have been removed from the supported source tree; no real-money feature is part of this release.
|
||||
|
||||
A limited, non-monetary testnet beta may be considered only after the release-specific operational, wallet, capacity, recovery, and review gates in the [card-only production plan](card-only-production-plan.md) are met. Mainnet or valuable-card use remains **NO-GO**.
|
||||
|
||||
## Remaining production blockers
|
||||
|
||||
| Finding | Risk | Required disposition |
|
||||
| --- | --- | --- |
|
||||
| Consensus/economic security | Current initial proof-of-work target is deliberately easy and the ten-block retarget is not production-calibrated. Hashpower concentration, timestamp/retarget manipulation, eclipse, censorship, and reorganization costs have not been independently measured. RandomX use does not inherit Monero's security. | Measure representative hashpower; run multi-host clock, partition, stall, fork, and reorg simulations; document operator-accepted risk and freeze parameters/identity before launch. |
|
||||
| Node availability and scale | The DAG remains in memory, state-root work visits the NFT set, and startup revalidates stored proofs. Work, RAM, disk, and recovery time grow with history. | Repeated long-chain and hostile-traffic tests on intended hardware; define and validate latency, resource, storage, and recovery limits; establish an archival/capacity strategy. |
|
||||
| Wallet/key protection | Encrypted local storage helps while locked, but a compromised same-origin page, extension, browser, or OS can abuse an unlocked signer. Old plaintext copies and cross-browser recovery require careful handling. | Independent wallet review; supported-browser migration/lock/sign/backup/restore drills; document residual compromise and key-loss risks. Decide whether isolated/hardware signing is required. |
|
||||
| Deployment and operations | The real TLS/proxy/firewall topology, peer/miner independence, off-host recovery, alert delivery, and on-call response have not been verified. A liveness response is not readiness. | Verify intended hosts and external edge; test alert routes and incident procedures; encrypt off-host backups and complete an isolated restore/replay with measured recovery objectives. |
|
||||
| Release supply chain | A successful build or dependency advisory check does not establish artifact integrity or native/container safety. | Freeze base/dependency/native versions and image digest; produce an SBOM, scans, build provenance/signature, and restricted release process for the exact artifact. |
|
||||
| Governance and user terms | No production operator/reviewer sign-off, public support/abuse path, or published project software license is established. | Name accountable decision makers; publish license, support, privacy, experimental/no-value disclosures, abuse reporting, upgrade policy, and stop conditions. |
|
||||
|
||||
These are readiness gaps, not claims that each item is an exploited vulnerability. Local tests and rehearsals are useful, but they do not establish security or verify the intended production host.
|
||||
|
||||
## Implemented groundwork (not certification)
|
||||
|
||||
- Global RPC admission and response-byte budgets, bounded canonical pages, and client response/history checks reduce some resource-exhaustion exposure; they do not solve retained-DAG growth or prove a production capacity envelope. See [node resource controls](node-resources.md).
|
||||
- Local wallet vault encryption, explicit legacy migration, session expiry, and lock-aware signing improve storage lifecycle. They cannot protect an unlocked page from same-origin compromise. See [wallet security](wallet-security.md).
|
||||
- Stopped-node ledger backup/verify/restore and native-proof replay tests provide a local recovery starting point. They are not encrypted/off-host backup service, filesystem power-loss certification, or production RPO/RTO evidence. See [operations and recovery](operations-recovery.md).
|
||||
- The launch preflight is read-only and intentionally reports NO-GO while evidence is missing. Startup approval flags are accident-prevention gates, not proof of audit or readiness.
|
||||
|
||||
## Review evidence and limits
|
||||
|
||||
The exact release candidate must be rebuilt and fully retested after scope cleanup. No claims are made here about the user's production host, public peer topology, Internet-facing testing, native-code assurance, alert delivery, off-host recovery, or mainnet security. External audits are not planned; retain the NO-GO for mainnet/valuable assets until the remaining network, deployment, and recovery evidence is complete and the operator records informed risk acceptance. A successful test suite or container startup alone is insufficient.
|
||||
+23
-15
@@ -17,7 +17,7 @@ implementation-specific, and changing them can create a different chain.
|
||||
|
||||
## Network, genesis and article selection
|
||||
|
||||
- Network: `wikideck-randomx-testnet-4`, block version **4**. Genesis has no blocks, NFTs or work; first mined height is 1. No privileged issuance/admin mint key. Version 4 has a new chain identity; prior-version ledger files and wallet backups are not compatible.
|
||||
- Network: `wikideck-randomx-testnet-6`, block version **6**. Genesis has no blocks, NFTs or work; first mined height is 1. No privileged issuance/admin mint key. Version 6 adds atomic NFT trade bundles and has a new chain identity; prior-version ledger files and wallet backups are not compatible.
|
||||
- `GENESIS_TIME = 1792411200` (Unix seconds). Let `S(x)` mean lowercase hex SHA-256 of UTF-8 `x`, and `J(x)` mean compact JavaScript `JSON.stringify(x)`; field order below is consensus-critical.
|
||||
- `MAX_TARGET` is `0fff` followed by 60 `f` digits. `CHAIN_ID = S(J([NETWORK, CONSENSUS, GENESIS_TIME, MAX_TARGET]))`, using the constants object's insertion order in the implementation. `CONSENSUS` identifies the random Wikipedia source and mint-statistics snapshot format.
|
||||
- `GENESIS_HASH = S(NETWORK + ':genesis:' + CHAIN_ID)`; `INITIAL_KEY = S(NETWORK + ':initial-key:' + CHAIN_ID)`. Nodes independently derive the same manifest and reject other network/chain IDs.
|
||||
@@ -42,47 +42,55 @@ The miner signs `J([NETWORK, 'block-claim', blockID])` using ECDSA P-256/SHA-256
|
||||
|
||||
## Targets, timestamps and epoch boundaries
|
||||
|
||||
- Desired spacing: **30 seconds**; retarget interval: **10 ancestors**. Heights 1–10 use the initial target; height 11 first retargets. Otherwise inherit the parent target.
|
||||
- At each retarget, compare the last and first timestamps of the previous 10 blocks: expected span is **9 × 30 = 270 seconds**. Clamp elapsed to `floor(270/4)` through `270*4`, multiply the parent target by elapsed/270 with integer division, then clamp target to `[1, MAX_TARGET]`. Selection uses work, not height or wall-clock estimates.
|
||||
- Desired spacing: **15 minutes (900 seconds)**; retarget interval: **10 ancestors**. Heights 1–10 use the initial target; height 11 first retargets. Otherwise inherit the parent target.
|
||||
- At each retarget, compare the last and first timestamps of the previous 10 blocks: expected span is **9 × 900 = 8,100 seconds**. Clamp elapsed to `floor(8,100/4) = 2,025` through `8,100*4 = 32,400` seconds, multiply the parent target by elapsed/8,100 with integer division, then clamp target to `[1, MAX_TARGET]`. Selection uses work, not height or wall-clock estimates.
|
||||
- A timestamp must exceed the median of up to **11** ancestors (sorted upper-middle for even counts; genesis time if none) and must be at most **120 seconds** ahead of validation time. Keep operator clocks synchronized.
|
||||
- Epoch key source height is `floor(max(0, height - 64) / 2048) * 2048`. Zero means `INITIAL_KEY`; otherwise use the ancestor block hash at that source height. Thus heights **1–2111** use the initial key, **2112–4159** use height 2048's hash, and height **4160** switches to height 4096's hash. Forks derive keys from their own ancestors.
|
||||
|
||||
## Native NFT transition and commitments
|
||||
|
||||
Each block executes its ordered transfers against its parent's NFT state, **then mints one NFT** to the block owner. State entries contain owner, sequence (initially 0), minted height and page ID. Transfers cannot spend this block's new issuance.
|
||||
Each block executes its ordered transactions against its parent's NFT state, **then mints one NFT** to the block owner. State entries contain owner, sequence (initially 0), minted height and page ID. Transactions cannot spend this block's new issuance.
|
||||
|
||||
A transfer carries network, chain ID, `cardHash` (**NFT token ID**, despite the legacy field name), from, to, sequence, public key and signature. Its signed message is `J([NETWORK, 'nft-transfer', chainId, cardHash, from, to, sequence])`. **Transfer ID is SHA-256 of that message, excluding ECDSA signature bytes** to avoid signature-malleability identity changes. Require the current owner, matching public key, a different recipient, exact current sequence, valid signature, and inclusion height minus mint height **>= 2**; increment sequence on application. Replays and duplicate IDs within a block fail.
|
||||
An individual transfer carries network, chain ID, `cardHash` (**NFT token ID**, despite the legacy field name), from, to, sequence, public key and signature. Its signed message is `J([NETWORK, 'nft-transfer', chainId, cardHash, from, to, sequence])`. **Transfer ID is SHA-256 of that message, excluding ECDSA signature bytes** to avoid signature-malleability identity changes. Require the current owner, matching public key, a different recipient, exact current sequence, valid signature, and inclusion height minus mint height **>= 2**; increment sequence on application. Replays and duplicate IDs within a block fail.
|
||||
|
||||
`transactionsRoot` commits the **ordered transfer IDs**. `stateRoot` commits the complete **post-transfer, post-mint** state: sort token IDs lexicographically; each input leaf is `S(J([id, owner, sequence, mintedHeight, pageId]))`. `merkleRoot` returns `S(NETWORK + ':empty')` for no leaves. For nonempty input, hash each raw 32-byte input leaf with byte prefix `0x00`; hash pairs of raw children with byte prefix `0x01`, duplicating an odd final child at each layer. Finally return `S(J([NETWORK, 'merkle', originalLeafCount, treeRootHex]))`. Count commitment prevents odd-padding ambiguity; leaf/node prefixes separate domains. These commitment semantics, the RandomX version, and big-endian proof convention are pinned in the genesis consensus manifest.
|
||||
A trade is one `type: 'trade'` transaction with proposer and counterparty addresses, ordered offered/requested NFT legs (token ID plus current sequence), and one P-256 signature from each party over `J([NETWORK, 'nft-trade', chainId, proposer, counterparty, offeredLegs, requestedLegs])`, where each leg is `[cardHash, sequence]`. Both public keys must derive the respective party addresses. Each side must contain 1–8 distinct NFTs; duplicates across either side are rejected. At inclusion, validation checks **all** selected NFTs against the same pre-trade state, owners, sequences, maturity, and both signatures before writing any changes. If any leg is invalid, the entire trade is rejected; if valid, all offered ownerships move to the counterparty and all requested ownerships to the proposer, with each sequence incremented once. A trade counts as one mempool and block transaction and is committed by one transaction ID. It cannot be partially included or partially replayed. Offers are not reservations; stale offers fail validation.
|
||||
|
||||
The mempool holds at most **256** ordered transfers, validating sequentially against the next inclusion height; a template takes the first **64**. The protocol can accept chained transfers of the same NFT in order; the browser conservatively blocks a second pending transfer for a card. A 202 response changes **no canonical ownership**. Mint at height `h` is eligible for inclusion at `h+2` (pool admission can happen at tip `h+1`). Confirmations are `tipHeight - mintedHeight + 1`, not a finality guarantee.
|
||||
`transactionsRoot` commits the **ordered transaction IDs** (individual transfer IDs or atomic trade IDs). `stateRoot` commits the complete **post-transaction, post-mint** state: sort token IDs lexicographically; each input leaf is `S(J([id, owner, sequence, mintedHeight, pageId]))`. `merkleRoot` returns `S(NETWORK + ':empty')` for no leaves. For nonempty input, hash each raw 32-byte input leaf with byte prefix `0x00`; hash pairs of raw children with byte prefix `0x01`, duplicating an odd final child at each layer. Finally return `S(J([NETWORK, 'merkle', originalLeafCount, treeRootHex]))`. Count commitment prevents odd-padding ambiguity; leaf/node prefixes separate domains. These commitment semantics, the RandomX version, and big-endian proof convention are pinned in the genesis consensus manifest.
|
||||
|
||||
The mempool holds at most **256** ordered transactions (transfers or trade bundles), validating sequentially against the next inclusion height; a template takes the first **64 transaction units**. A 202 response changes **no canonical ownership**. Mint at height `h` is eligible for inclusion at `h+2` (pool admission can happen at tip `h+1`). Confirmations are `tipHeight - mintedHeight + 1`, not a finality guarantee.
|
||||
|
||||
## Fork choice, reorgs and storage
|
||||
|
||||
Per-block work is `floor(2^256 / (target + 1))`; accumulate from genesis work zero. Keep all valid known-parent branches. Adopt a tip only for **strictly greater cumulative work**; equal-work ties retain the current tip, including across replay of persisted insertion order. A shorter, harder branch can win.
|
||||
|
||||
On reorg, reconstruct ownership from the winning branch. Orphan mints disappear from canonical ownership; disconnected transfers are reconsidered before existing pending entries, in order, only if still valid and not already committed. Conflicting, immature or orphan-issuance transfers are discarded. No checkpoints/finality or rollback protection beyond observed work; an isolated node has no peer-backed security.
|
||||
On reorg, reconstruct ownership from the winning branch. Orphan mints disappear from canonical ownership; disconnected transactions are reconsidered before existing pending entries, in order, only if still valid and not already committed. A trade is requeued as one unit only when every leg remains valid; otherwise the complete bundle is discarded. No checkpoints/finality or rollback protection beyond observed work; an isolated node has no peer-backed security.
|
||||
|
||||
The [ledger](../server/testnet/ledger.ts) stores the entire DAG and pending pool in `data/testnet/ledger.json`, under the `WIKIDECK_DATA_DIR` testnet subdirectory. The file is created when the node first writes ledger data. Defaults use project data; Docker uses `/app/data`. Disk caps: **128 MiB, 10,000 total blocks including side branches**; blocks: **64 KiB and 64 transfers**. Each in-memory DAG node stores a full NFT-state snapshot: potentially **quadratic memory growth**, plus full-file rewrite cost. These are experimental stop limits, not scalable storage or a public DoS defense.
|
||||
The [ledger](../server/testnet/ledger.ts) is stored in `data/testnet/ledger.sqlite`, under the `WIKIDECK_DATA_DIR` testnet subdirectory (Docker uses `/app/data`). SQLite WAL mode with full synchronous commits stores each accepted block once in an indexed table and replaces the bounded mempool in the same transaction; writes no longer serialize/rewrite the entire DAG, and there is no 128 MiB/10,000-block ledger cap. A matching v6 legacy `ledger.json` is schema-checked and imported transactionally once; the source JSON is left untouched as a recovery copy. Earlier-protocol ledgers are not compatible and fail closed. Back up the complete stopped-node data directory, including SQLite database/WAL files. Do not copy only `ledger.sqlite` while the node is running.
|
||||
|
||||
Mutations are serialized (queue cap 256); persist via a synchronized temporary file and atomic rename before exposing new state. Restart independently revalidates every saved proof and transaction; malformed, oversized or mismatched files fail closed without automatic reset. HTTP startup exclusively creates a data-directory lock named testnet-node.lock. It is removed on normal shutdown, **not automatically after a crash**: stop and verify all owning processes have exited before manual stale-lock removal. Never bypass a live lock or share a writer directory. Back up stopped-node volumes/files and wallet exports separately; destructive volume reset cannot recover assets from a wallet alone.
|
||||
The in-memory DAG keeps block records and cumulative work, while each block's NFT state is a persistent overlay containing only that block's changes rather than a full state copy. State-root calculation still visits the complete NFT state, and startup still revalidates every stored proof; CPU and startup work therefore grow with chain size. This is materially more storage-efficient, but not yet an archival-scale chain database or a public DoS defense. Blocks remain individually limited to **64 KiB and 64 transaction units**; the mempool remains capped at **256 transaction units**. Mutations are serialized (queue cap 256). HTTP startup exclusively creates a data-directory lock named `testnet-node.lock`. It is removed on normal shutdown, **not automatically after a crash**: stop and verify all owning processes have exited before manual stale-lock removal. Never bypass a live lock or share a writer directory. Back up node data and wallet exports separately; deleting a volume cannot be recovered from a wallet alone.
|
||||
|
||||
## Fixed peers and HTTP surface
|
||||
|
||||
Peers are **explicit operator-configured URL origins**, up to 16, not discovered from network input. Default: HTTPS/public addresses only, checked and pinned during DNS lookup; no credentials, paths, queries, fragments or redirects. `WIKIDECK_ALLOW_PRIVATE_PEERS=1` permits private/HTTP destinations only by explicit operator choice (used for local Docker tests). No bootnodes, DHT, automatic public deployment or claimed Sybil resistance.
|
||||
|
||||
Sync polls every **5 seconds**, checks network/chain ID, imports at most **32** topologically ordered DAG blocks per peer/pass, and validates remote pool entries locally. Accepted blocks/transfers are gossiped to available configured peers; busy-peer gossip is dropped, with polling for catch-up. Responses are capped at 2 MiB with a 10-second deadline. Peer count is current outbound connectivity metadata, not consensus membership or a security score.
|
||||
Sync polls every **5 seconds**, checks network/chain ID, imports at most **32** topologically ordered DAG blocks per peer/pass, and validates remote pool entries locally. Accepted blocks and transactions (including complete trade bundles) are gossiped to configured peers; busy-peer gossip is dropped, with polling for catch-up. Responses are capped at 2 MiB with a 10-second deadline. Peer count is current outbound connectivity metadata, not consensus membership or a security score.
|
||||
|
||||
| Route | Purpose |
|
||||
| --- | --- |
|
||||
| `GET /healthz` | Process health/network, not sync or consensus security. |
|
||||
| `GET /api/state`, `/api/chain` | Canonical blocks, mined/pending transfers, tip/work and peer count. |
|
||||
| `GET /api/state`, `/api/chain` | Canonical blocks, mined/pending transactions, tip/work and peer count. |
|
||||
| `GET /api/summary`, `/api/canonical?tip=<hash>&offset=0&limit=16` | Bounded state metadata and current-tip-pinned canonical pages. Full legacy snapshots are limited to 128 blocks and the response-byte budget. See [resource controls](node-resources.md). |
|
||||
| `GET /api/network` | Selected profile, pinned chain/genesis, and candidate launch status. |
|
||||
| `GET /api/nfts/:id`, `/api/peers` | Canonical NFT owner/sequence/confirmations, article and at-mint statistics; configured peer status. |
|
||||
| `GET /api/decks/:address?offset=0&limit=24` | Public canonical deck for a wallet address at one tip; paged, maximum 24 cards per response. This reveals public address holdings. |
|
||||
| `GET /api/wikipedia/:pageId` | Cached current enrichment for positive English Wikipedia page IDs: categories, views, size, languages and display-only scores. Never a consensus input. |
|
||||
| `POST /api/mining/template` | Rate-limited public miner identity; fetches a random article with a picture plus fresh at-mint statistics. Returns 503 when either cannot be fetched. |
|
||||
| `POST /api/blocks` | Independently validates a signed block, proof, article snapshot, required Wikimedia image URL, and statistics snapshot consistency. Repeated page IDs are accepted. |
|
||||
| `POST /api/transfer` | Signed transfer admission; 202 means pending. |
|
||||
| `POST /api/transfer` | Signed single-card transfer admission; 202 means pending. |
|
||||
| `POST /api/trades` | Validates and admits one signed, atomic multi-card trade bundle; 202 means the whole bundle is pending. |
|
||||
| `GET /p2p/status`, `/p2p/blocks?offset=0`, `/p2p/mempool` | Manifest/size/tip, 32-block inventory pages, pending transactions. |
|
||||
| `POST /p2p/block`, `/p2p/transaction` | Same validation as local block/transfer submission. |
|
||||
| `POST /p2p/block`, `/p2p/transaction` | Same validation as local block/transaction submission; transaction endpoint accepts a transfer or atomic trade. |
|
||||
|
||||
POSTs require JSON, enforce supplied browser origins and signatures where applicable; peers without an Origin header are not authenticated by this check. HTTP parsing is capped at 68 KiB; API/P2P rate limits are 300/minute and block/transfer POSTs additionally 30/minute. There are no fees, peer authentication or economic spam deterrents. Same-origin browser RPC trusts the node; it does not independently verify these commitments. Deploy no funds: majority-hashpower, eclipse, censorship, reorg and resource-exhaustion risks require independent audits and adversarial testing before any broader launch.
|
||||
POSTs require JSON, enforce supplied browser origins and signatures where applicable; peers without an Origin header are not authenticated by this check. HTTP parsing is capped at 68 KiB; API/P2P rate limits are 300/minute and block/transaction/trade submissions additionally 30/minute. There are no consensus fees, peer authentication, or economic spam deterrents. Same-origin browser RPC trusts the node; it does not independently verify these commitments. Deploy no valuable assets: majority-hashpower, eclipse, censorship, reorg and resource-exhaustion risks remain and are not externally audited under the operator's current policy.
|
||||
|
||||
Testnet storage and chain identities above remain unchanged. The [gated mainnet candidate](mainnet-launch.md) selects separate chain/signature domains, storage, lock and wallet identities; it is not a running or certified production network.
|
||||
|
||||
Executable
+45
@@ -0,0 +1,45 @@
|
||||
# Encrypted local wallet — stage 2
|
||||
|
||||
## What changes
|
||||
|
||||
The browser no longer creates or returns a plaintext wallet at startup. Open **Wallet backup & recovery** and choose:
|
||||
|
||||
- **Create encrypted wallet:** explicitly generate a new P-256 identity and choose a local password of 12–1024 characters.
|
||||
- **Encrypt existing wallet:** migrate the previous same-origin plaintext record, preserving its exact public key/address. This is required before the old key can sign in the new UI.
|
||||
- **Unlock wallet:** decrypt an existing local vault in this tab with its local password.
|
||||
- **Recover and save encrypted wallet:** decrypt a portable backup with its backup password and re-encrypt it under a new local password. Existing storage, even when locked, requires explicit replacement confirmation. Back up that identity first; replacement does not transfer its assets.
|
||||
|
||||
The local password and backup password may differ. They have no reset endpoint, are not sent to a server, and are not stored in local/session storage. Forgotten local passwords require a working portable backup and its password. Without either a usable local vault/password or backup/password, access is lost.
|
||||
|
||||
## Storage and migration guarantees
|
||||
|
||||
Vault records use the existing reviewed backup encryption primitive: PBKDF2-SHA256 (600,000 iterations), random 16-byte salt, AES-256-GCM with random 12-byte IV and authenticated format/network/public-key headers. The local wrapper stores a format/version and random revision plus the encrypted backup. It stores **no plaintext private CryptoKey or password** for newly created/migrated vaults. Public-key metadata remains visible; encryption does not hide wallet identity or on-chain holdings.
|
||||
|
||||
The existing testnet/mainnet IndexedDB names remain separate and unchanged to find legacy records safely. Migration reads the legacy key only after the user opts in, encrypts and decrypts to verify identity, then replaces the record in a committed IndexedDB readwrite transaction. No automatic deletion, new identity or network migration occurs. Storage failure leaves the original record intact. The old CryptoKey is never removed before encrypted replacement commits.
|
||||
|
||||
Cross-tab readwrite compare-and-swap checks the expected stored revision/record in the same transaction; optional Web Locks additionally serialize storage. A competing create/migration/restore must fail rather than overwrite a newly stored identity. This is application consistency, not protection against a malicious script deliberately editing IndexedDB. Old application versions/tabs must be closed before migration; cached old bundles are not guaranteed to understand a vault record.
|
||||
|
||||
Replacing a record does **not** securely erase previous browser storage pages, OS backups, disk snapshots, old downloaded files or keys previously copied by other scripts. If the old identity may have been exposed, encryption is not revocation: a separately planned transfer to a fresh identity is required. This work never transfers assets or silently rotates keys.
|
||||
|
||||
## Session and locking
|
||||
|
||||
UI components receive a public `Wallet` handle only; private keys remain inside the wallet module. All NFT transfer and trade signatures go through its session signer. A stale handle from before locking/re-unlocking is rejected even for the same address.
|
||||
|
||||
- Explicit **Lock wallet now** clears the module's session references immediately.
|
||||
- Five minutes without trusted pointer/keyboard input locks the session. RPC polling and automated signing do **not** reset idle time.
|
||||
- Four hours since unlocking locks it even if active.
|
||||
- Background/hidden-tab, pagehide and reload end the session. Hiding a tab during encryption/export/unlock may cancel its result; encrypted storage may already have committed, in which case it remains locked and can be unlocked again.
|
||||
- BroadcastChannel messages contain only a lock signal, never keys, passwords or decrypted backups. Manual/background locks and replacement invalidate other tabs where supported. If unavailable, storage revision is checked before returning signatures/backups; do not assume immediate cross-tab notification on every browser.
|
||||
- Lock epochs are checked across asynchronous crypto/storage steps. A signature finishing after a detected lock/replacement is discarded. Signing/export also recheck vault revision. An already returned/submitted signature cannot be revoked by locking; signed transactions/offers can remain valid on-chain.
|
||||
|
||||
Timers can be delayed by the browser or event-loop load. Expiry is checked again on signer/export/get-wallet access so delayed timer callbacks do not authorize an expired session. JS does not provide guaranteed zeroization of strings or CryptoKey/browser memory; clearing references is not a memory-forensics guarantee.
|
||||
|
||||
Form password values clear after operations, lock notifications, hiding the page and unmounting. Browser/password-manager behavior is outside this application's memory guarantees. Backups remain the existing portable format for native miner compatibility. A downloaded backup password does not implicitly change the local password.
|
||||
|
||||
## Validation and limitations
|
||||
|
||||
Node/WebCrypto storage-boundary tests cover encrypted record-only persistence, explicit no-wallet startup, signature verification, locked/stale handles, idle/absolute expiry, lock during unlock/export, wrong passwords, replacement consent, storage retry/quota failure, conflicting storage revisions, same-identity legacy migration and preservation after failed migration. Existing cryptographic tamper, wrong-network and native-miner backup tests remain.
|
||||
|
||||
Real browser smoke checks on a disposable loopback origin passed create → lock → unlock and reload → locked. IndexedDB inspection confirmed the vault wrapper/ciphertext and absence of a top-level private key/password without retrieving private material. Tests use ephemeral test credentials only. Final Node 24 TypeScript/Vite build passed; the complete suite passed **86 tests, 0 failed, 0 skipped**, including real RandomX HTTP integration and expanded wallet storage/lifecycle assertions.
|
||||
|
||||
**Wallet risks remain accepted but unresolved.** At-rest encryption reduces profile-theft exposure when locked but cannot protect an unlocked page or password entry from XSS, a malicious release, privileged extensions or a compromised browser/OS. The in-memory key intentionally remains extractable to support existing backup export. This is not hardware-isolated signing or a browser-extension wallet. External wallet audits are not planned; multi-browser/background/cross-tab races and destructive crash/restore drills remain internal engineering work. Do not use valuable assets merely because local encryption exists.
|
||||
Reference in New Issue
Block a user