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