operator/library-only Wallet-RPC address creation and recovery

This commit is contained in:
2026-10-22 18:55:05 -04:00
parent 2ecb42c019
commit 83d3d104c1
8 changed files with 386 additions and 16 deletions
+6 -3
View File
@@ -18,9 +18,9 @@ The independent private chain uses a fixed pilot genesis time and its own determ
This stack does **not** connect to the operator's Monero daemon, create real Monero deposit addresses, scan a wallet, approve real credits, construct or relay withdrawals, or settle NFT sales against active consensus. Having a full node supplies only the blockchain daemon; it is not a Wallet-RPC service, watch-only deposit wallet, or spending wallet. Deposit enrollment and withdrawal request flags remain rejected outside the mainnet profile, and mainnet remains separately gated. Do not expose the service publicly or solicit funds.
### Pilot custody identity and durable deposit reservations (schema v11)
### Pilot custody identity and deposit reservations (schema v13)
Schema v10 added the immutable identity binding with separate fields for Wikideck market network/chain (`wikideck-randomx-pilot-1` and pilot chain ID) and Monero network (`mainnet`). Schema v11 adds a separate pilot reservation table. A signed beneficiary/wallet request can now reserve a never-reused subaddress index transactionally, with request replay, same-beneficiary recovery, concurrent index allocation, and conflict protection. Mainnet allocation/withdrawal constraints and v1-v10 migration definitions remain unchanged.
Schema v10 added the immutable identity binding with separate fields for Wikideck market network/chain (`wikideck-randomx-pilot-1` and pilot chain ID) and Monero network (`mainnet`). Schema v11 adds the separate pilot reservation table; v12 adds one-time address registration; v13 persists a create-intent state before the Wallet-RPC side effect. A signed beneficiary/wallet request reserves a never-reused subaddress index transactionally, with request replay, same-beneficiary recovery, concurrent index allocation, and conflict protection. Mainnet allocation/withdrawal constraints and v1-v12 migration definitions remain unchanged.
The reservation table has a composite foreign key to the immutable identity row, so direct SQL inserts cannot bind a reservation to a different custodian, Wikideck chain, or Monero network. The focused disposable PostgreSQL lifecycle test verifies index sequencing, replay, concurrent reservations, beneficiary conflict, and denied financial/address access for the restricted role. This implementation still does not create any address.
@@ -31,12 +31,15 @@ After an operator creates/provisions a distinct empty database and its schema-ow
1. With the `pilot` profile selected, run `custody:db migrate-pilot-schema` with `WIKIDECK_PILOT_SCHEMA_MIGRATION=explicit-pilot-schema-migration-only`.
2. Run `custody:db initialize-pilot-custody-identity <CUSTODIAN_UUID>` with `WIKIDECK_PILOT_MODE=private-mock-only`, `WIKIDECK_PILOT_CUSTODY_IDENTITY=initialize-pilot-custody-v1`, and `WIKIDECK_PILOT_CUSTODY_OPERATIONS=identity-only-no-funds`.
3. If using the reservation library against that database, create a separate least-privilege role out of band and run `custody:db grant-pilot-deposit-reservation-runtime <ROLE>` with `WIKIDECK_PILOT_CUSTODY_OPERATIONS=reservation-only-no-addresses`.
4. Before any operator-triggered address exercise, create a separate Wallet-RPC role out of band and run `custody:db grant-pilot-deposit-address-runtime <ROLE>` with `WIKIDECK_PILOT_CUSTODY_OPERATIONS=operator-triggered-addresses-only`.
The schema migration does not grant runtime roles. The reservation role can read schema-version digests and the immutable pilot identity, execute a fixed isolation predicate, and read/insert immutable reservation rows. It cannot read custody metadata, balances, journals, mock custody, observer data, withdrawal intents, or alter reservation history. The isolation predicate uses a fixed-search-path schema-owner function; the role does not receive broad SELECT access just to check that the dedicated database remains unpopulated.
The CLI refuses other custody actions on the pilot profile; mainnet keeps its existing separate controls.
The library path verifies the signed pilot request, checks both network identities and configured wallet-generation/fingerprint/account, then reserves an index. It intentionally stops there: no Wallet-RPC call, address creation/registration, address response, observer, credit, withdrawal, or HTTP enrollment route exists. A durable reserved index is never reclaimed; wallet-side index advancement or lost RPC responses must be reconciled before address-generation support is added. Existing mainnet enrollment remains unchanged. Do not treat a reserved index or valid signature as approval to accept funds.
The operator/library-only address path verifies the signed pilot request, checks both network identities and configured wallet-generation/fingerprint/account, and uses the narrow Wallet-RPC client to inspect/create at the exact reserved index. It requires the stable reservation label and independently reads back/validates the returned mainnet subaddress before recording it. A create-intent timestamp is committed before the RPC side effect. If the process loses the response but the address exists at that exact index/label, a retry recovers it without creating another address. If no matching address exists after a create intent, retries stop with an uncertain outcome for manual reconciliation; they do **not** issue another create call. An unexpected consumed index is likewise held for operator investigation.
There is still no HTTP enrollment route, observer startup, customer credit, withdrawal, or sale activation. The function returns an address only to its operator/library caller. Keep Wallet-RPC off the public Internet and the credentials private. Existing mainnet enrollment remains unchanged. Do not treat an allocated address as approval to accept funds.
### Reusing the operator-hosted daemon
+3 -2
View File
@@ -9,7 +9,8 @@ Updated: 2026-10-22. Implementation plan for a private, operator-supervised func
- **Next integration slice implemented:** added an operator-triggered, read-only `monerod get_info` probe, opt-in Compose check service, and mocked regression coverage. The Linux test image builds and focused probe/deployment tests pass (6/6). The probe validates expected Mainnet/Stagenet identity and synchronization, but has not been run against the operator's node because its private endpoint/configuration has not been provided. It does not enable wallet access, addresses, deposits, or credits. See [the probe setup and security boundary](real-xmr-pilot-deployment.md).
- **Pilot identity/schema slice implemented:** schema v10 additively records a separate identity binding `wikideck-randomx-pilot-1` + current pilot chain ID to Monero `mainnet`, with an immutable `identity-only` activation status. Schema v11 adds separate immutable pilot reservation rows. Explicit pilot-only migration, identity, and reservation-role CLI actions require exact acknowledgements, reject non-pilot profiles, and refuse reused financial databases.
- **Durable pilot allocation implemented:** a signed pilot request now reserves the next never-reused index transactionally, serializes concurrent allocators, replays idempotently, and rejects request/beneficiary conflicts. PostgreSQL enforces the reservation's custodian, market-chain, and Monero-network binding with a composite foreign key to the immutable pilot identity. A dedicated role can read schema digests and pilot identity, execute the isolation check, and read/insert reservations only; it cannot read balances, register addresses, or access observation, credit, or withdrawal tables. This code does not call Wallet-RPC, return a Monero address, or connect to HTTP.
- **Validation:** focused PostgreSQL allocator lifecycle passed, covering first reservation, request replay, same-beneficiary recovery, concurrent index allocation, conflict rejection, and least-privilege denials. The schema v1-to-v11 migration test passed and the Linux test image builds. The serial offline suite passed 189/189 after the allocator implementation; no operator database or real node was accessed.
- **Wallet-RPC address create/recover implemented:** the operator/library-only function verifies the configured wallet fingerprint, checks the reserved index and stable allocation label, persists a create intent before `create_address`, then reads back and validates the exact Mainnet subaddress before registering it once. Lost responses recover by the stable label without repeating creation; ambiguous missing responses remain in a durable `creating` state and stop for operator reconciliation. No HTTP enrollment, observer, credit, withdrawal, or sale path is enabled.
- **Validation:** serial offline suite passed 189/189; disposable PostgreSQL suite passed 44/44, including schema v13 migration and the pilot reservation/address lifecycle tests. The focused address test verifies lost-response recovery and that an unresolved create is not sent twice. No operator database or real Monero node was accessed.
- **Step 1 is not complete.** The current pilot stack does not connect to that external daemon and still has no Wallet-RPC gateway, observer, real-fund deposit or withdrawal path. It is useful for isolated UI/mock-flow work only and must not receive funds.
- **Validation history:** earlier pilot-only checks passed 10/10. An earlier full-suite attempt had a transient RandomX peer-gossip failure; the most recent serial offline suite subsequently exited 0.
- **Steps 2–5:** not started. Existing deposit/withdrawal APIs still require the separately gated mainnet profile; the new pilot record is identity metadata only. Sale-lock transactions remain candidate-only and are not accepted by active consensus.
@@ -121,4 +122,4 @@ Keep the functional essentials: signed authorization, correct network binding, p
## Next coding task
**Next: implement Wallet-RPC create/recover for durable pilot reservations through the existing private mTLS wallet gateway, with lost-response recovery and out-of-band index-consumption tests.** Do not expose enrollment over HTTP or enable credits. Keep addresses private and operator-triggered until actual withdrawals, recovery, and financial review are ready. Run the daemon health check when protected endpoint config is available; no duplicate daemon is planned. Keep the deployment mock-only until network binding, reviewed credit, actual withdrawal execution, recovery, and sale settlement are connected. Continue treasury execution and active sale-lock settlement as parallel tracks once their shared identity boundary is defined. Completion means a buyer purchasing a card and the seller receiving withdrawable XMR—not another scaffold or release checklist.
**Next: add an operator-only CLI entrypoint for the pilot allocation/create-or-recover path, then test it end-to-end with the private mTLS Wallet-RPC gateway.** Keep HTTP enrollment disabled. Review actual protected wallet configuration, wallet type, and external-daemon network binding before creating any addresses. Deposits remain unusable until observation, reviewed credit, withdrawal execution, and recovery are all connected.