import { createHash, randomUUID } from 'node:crypto'; import type { Pool, PoolClient } from 'pg'; import { z } from 'zod'; import type { MoneroDepositAddressRpc } from './monero-observer.ts'; import { validateXmrSubaddress } from './xmr.ts'; import { IS_PILOT, NETWORK } from '../../shared/network.ts'; import { CHAIN_ID } from '../testnet/consensus.ts'; import type { PilotCustodyIdentity } from './pilot-custody-identity.ts'; import { assertPilotReservationDatabaseEmpty, verifyPilotCustodyIdentity } from './pilot-custody-identity.ts'; import { validatePilotMoneroDepositRequest, type PilotDepositRequestContext } from './pilot-monero-deposit-request.ts'; import { CUSTODY_WRITE_LOCK, verifyCustodySchema } from './postgres-schema.ts'; const MAX_INDEX = 0xffff_ffff; const uuidPattern = /^[a-f0-9]{8}(-[a-f0-9]{4}){3}-[a-f0-9]{12}$/i; const hexPattern = /^[a-f0-9]{64}$/; export type PilotDepositReservation = { allocationId: string; requestId: string; custodianId: string; marketNetwork: 'wikideck-randomx-pilot-1'; marketChainId: string; moneroNetwork: 'mainnet'; beneficiary: string; walletGenerationId: string; walletFingerprint: string; accountIndex: number; addressIndex: number; requestDigest: string; allocationDigest: string; reservedAt: number; }; type ReservationRow = { allocation_id: string; request_id: string; custodian_id: string; market_network: 'wikideck-randomx-pilot-1'; market_chain_id: string; monero_network: 'mainnet'; beneficiary: string; wallet_generation_id: string; wallet_fingerprint: string; account_index: string; address_index: string; request_digest: string; allocation_digest: string; reserved_at: string; }; export type PilotDepositAllocationIntent = { requestId: string; custodianId: string; marketNetwork: 'wikideck-randomx-pilot-1'; marketChainId: string; moneroNetwork: 'mainnet'; beneficiary: string; walletGenerationId: string; walletFingerprint: string; accountIndex: number; requestDigest: string; }; type ReservationAddressRow = ReservationRow & { status: 'reserved' | 'creating' | 'registered'; create_started_at: string | null; monero_address: string | null; registered_at: string | null; }; function toReservation(row: ReservationRow): PilotDepositReservation { return { allocationId: row.allocation_id, requestId: row.request_id, custodianId: row.custodian_id, marketNetwork: row.market_network, marketChainId: row.market_chain_id, moneroNetwork: row.monero_network, beneficiary: row.beneficiary, walletGenerationId: row.wallet_generation_id, walletFingerprint: row.wallet_fingerprint, accountIndex: Number(row.account_index), addressIndex: Number(row.address_index), requestDigest: row.request_digest, allocationDigest: row.allocation_digest, reservedAt: Number(row.reserved_at) }; } function validateIdentity(identity: PilotCustodyIdentity, intent: PilotDepositAllocationIntent, reservedAt: number): void { if (!IS_PILOT || NETWORK !== 'wikideck-randomx-pilot-1' || identity.version !== 1 || identity.mode !== 'pilot-custody' || identity.policy !== 'pilot-custody-v1' || identity.marketNetwork !== NETWORK || identity.marketChainId !== CHAIN_ID || identity.moneroNetwork !== 'mainnet' || identity.activationStatus !== 'identity-only') { throw new Error('Pilot deposit reservation requires the configured identity-only pilot custody profile'); } if (!Number.isSafeInteger(reservedAt) || reservedAt < 0 || !uuidPattern.test(intent.requestId) || !uuidPattern.test(intent.custodianId) || !uuidPattern.test(intent.walletGenerationId) || intent.custodianId.toLowerCase() !== identity.custodianId.toLowerCase() || intent.marketNetwork !== identity.marketNetwork || intent.marketChainId !== identity.marketChainId || intent.moneroNetwork !== identity.moneroNetwork || !hexPattern.test(intent.beneficiary) || !hexPattern.test(intent.walletFingerprint) || !hexPattern.test(intent.requestDigest) || !Number.isSafeInteger(intent.accountIndex) || intent.accountIndex < 0 || intent.accountIndex > MAX_INDEX) { throw new Error('Invalid pilot deposit allocation intent'); } } function allocationDigest(intent: PilotDepositAllocationIntent): string { return createHash('sha256').update(JSON.stringify([ 'wikideck-pilot-monero-deposit-allocation-v1', intent.custodianId, intent.marketNetwork, intent.marketChainId, intent.moneroNetwork, intent.beneficiary, intent.walletGenerationId, intent.walletFingerprint, intent.accountIndex, ])).digest('hex'); } async function assertIdentityRecord(client: PoolClient, identity: PilotCustodyIdentity): Promise { const result = await client.query(`SELECT version,mode,policy,market_network,market_chain_id,monero_network, custodian_id,activation_status FROM wk_custody.pilot_custody_identity WHERE singleton=1`); if (!result.rowCount) throw new Error('Pilot custody identity is not initialized'); const row = result.rows[0]; if (Number(row.version) !== identity.version || row.mode !== identity.mode || row.policy !== identity.policy || row.market_network !== identity.marketNetwork || row.market_chain_id !== identity.marketChainId || row.monero_network !== identity.moneroNetwork || row.custodian_id !== identity.custodianId || row.activation_status !== identity.activationStatus) { throw new Error('Pilot custody identity changed or does not match the database binding'); } } /** * Persist a never-reused Monero address index for a signed, validated pilot request. * This does not call Wallet-RPC, create/register an address, or permit crediting funds. */ export async function reservePilotMoneroDepositIndex(pool: Pool, identity: PilotCustodyIdentity, intentInput: PilotDepositAllocationIntent, reservedAt = Date.now()): Promise<{ reservation: PilotDepositReservation; replayed: boolean }> { const intent = { ...intentInput, custodianId: intentInput.custodianId.toLowerCase() }; validateIdentity(identity, intent, reservedAt); const allocationHash = allocationDigest(intent); const client = await pool.connect(); try { await client.query('BEGIN'); await client.query("SET LOCAL statement_timeout='10s'; SET LOCAL lock_timeout='3s'; SET LOCAL idle_in_transaction_session_timeout='10s'; SET LOCAL synchronous_commit=on"); await client.query('SELECT pg_advisory_xact_lock($1::bigint)', [CUSTODY_WRITE_LOCK]); await verifyCustodySchema(client); await assertPilotReservationDatabaseEmpty(client); await assertIdentityRecord(client, identity); // Serialize index allocation across app processes using the same watch-only wallet/account. await client.query('SELECT pg_advisory_xact_lock(hashtextextended($1,0))', [ `wikideck-pilot-monero-reservation-v1:${intent.walletGenerationId}:${intent.accountIndex}`, ]); const byRequest = await client.query(`SELECT * FROM wk_custody.pilot_monero_deposit_reservations WHERE request_id=$1`, [intent.requestId]); if (byRequest.rowCount) { const row = byRequest.rows[0]; if (row.request_digest !== intent.requestDigest || row.allocation_digest !== allocationHash) { throw new Error('Pilot deposit request ID was reused with different signed terms'); } await client.query('COMMIT'); return { reservation: toReservation(row), replayed: true }; } const byBeneficiary = await client.query(`SELECT * FROM wk_custody.pilot_monero_deposit_reservations WHERE custodian_id=$1 AND market_network=$2 AND market_chain_id=$3 AND beneficiary=$4 AND wallet_generation_id=$5`, [intent.custodianId, intent.marketNetwork, intent.marketChainId, intent.beneficiary, intent.walletGenerationId]); if (byBeneficiary.rowCount) { const row = byBeneficiary.rows[0]; if (row.allocation_digest !== allocationHash) { throw new Error('Beneficiary already has a different pilot reservation for this wallet generation'); } await client.query('COMMIT'); return { reservation: toReservation(row), replayed: true }; } const next = await client.query<{ next_index: string }>(`SELECT (COALESCE(MAX(address_index),-1)+1)::text AS next_index FROM wk_custody.pilot_monero_deposit_reservations WHERE wallet_generation_id=$1 AND account_index=$2`, [intent.walletGenerationId, intent.accountIndex]); const index = BigInt(next.rows[0].next_index); if (index > BigInt(MAX_INDEX)) throw new Error('Pilot Monero subaddress index space is exhausted for this account'); const inserted = await client.query(`INSERT INTO wk_custody.pilot_monero_deposit_reservations( allocation_id,request_id,custodian_id,market_network,market_chain_id,monero_network,beneficiary, wallet_generation_id,wallet_fingerprint,account_index,address_index,request_digest,allocation_digest,reserved_at) VALUES($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14) RETURNING *`, [randomUUID(), intent.requestId, intent.custodianId, intent.marketNetwork, intent.marketChainId, intent.moneroNetwork, intent.beneficiary, intent.walletGenerationId, intent.walletFingerprint, intent.accountIndex, index.toString(), intent.requestDigest, allocationHash, reservedAt]); await client.query('COMMIT'); return { reservation: toReservation(inserted.rows[0]), replayed: false }; } catch (error) { await client.query('ROLLBACK').catch(() => {}); if (typeof error === 'object' && error !== null && 'code' in error && error.code === '23505') { throw new Error('Pilot deposit reservation conflicts with an existing request, beneficiary, or wallet index'); } throw error; } finally { client.release(); } } /** Full signed-request path: verify durable identity, cryptographically validate, then reserve an index. */ export async function reservePilotMoneroDepositRequest(pool: Pool, context: Omit, rawRequest: unknown, reservedAt = Date.now()) { const custodianId = z.object({ custodianId: z.string().uuid() }).passthrough().parse(rawRequest).custodianId; const identity = await verifyPilotCustodyIdentity(pool, custodianId); const intent = validatePilotMoneroDepositRequest({ ...context, identity }, rawRequest); return reservePilotMoneroDepositIndex(pool, identity, intent, reservedAt); } async function registerPilotAddress(pool: Pool, reservation: PilotDepositReservation, moneroAddress: string, registeredAt: number): Promise<{ reservation: PilotDepositReservation; address: string; replayed: boolean }> { const checkedAddress = await validateXmrSubaddress(moneroAddress); if (!Number.isSafeInteger(registeredAt) || registeredAt < reservation.reservedAt) { throw new Error('Pilot address registration predates its durable index reservation'); } const client = await pool.connect(); try { await client.query('BEGIN'); await client.query("SET LOCAL statement_timeout='10s'; SET LOCAL lock_timeout='3s'; SET LOCAL synchronous_commit=on"); const result = await client.query(`SELECT * FROM wk_custody.pilot_monero_deposit_reservations WHERE allocation_id=$1 AND wallet_generation_id=$2`, [reservation.allocationId, reservation.walletGenerationId]); if (!result.rowCount) throw new Error('Pilot deposit reservation was not found for this wallet generation'); const row = result.rows[0]; if (row.status === 'registered') { if (row.monero_address !== checkedAddress) throw new Error('Registered pilot deposit address cannot be replaced'); await client.query('COMMIT'); return { reservation: toReservation(row), address: checkedAddress, replayed: true }; } if (row.status !== 'creating' || row.create_started_at === null || row.monero_address !== null || row.registered_at !== null || registeredAt < Number(row.create_started_at)) { throw new Error('Pilot deposit reservation registration state is inconsistent'); } const updated = await client.query(`UPDATE wk_custody.pilot_monero_deposit_reservations SET status='registered',monero_address=$2,registered_at=$3 WHERE allocation_id=$1 AND status='creating' AND monero_address IS NULL RETURNING *`, [reservation.allocationId, checkedAddress, registeredAt]); if (!updated.rowCount) { const already = await client.query(`SELECT * FROM wk_custody.pilot_monero_deposit_reservations WHERE allocation_id=$1 AND status='registered'`, [reservation.allocationId]); if (!already.rowCount || already.rows[0].monero_address !== checkedAddress) { throw new Error('Pilot deposit reservation changed during address registration'); } await client.query('COMMIT'); return { reservation: toReservation(already.rows[0]), address: checkedAddress, replayed: true }; } await client.query('COMMIT'); return { reservation: toReservation(updated.rows[0]), address: checkedAddress, replayed: false }; } catch (error) { await client.query('ROLLBACK').catch(() => {}); throw error; } finally { client.release(); } } /** * Operator/library-only address creation for a durable reservation. A lost create_address * response is recovered by the stable label at the exact reserved index on the next call. * No HTTP route invokes this function and the result is not a credit or payment authorization. */ export async function createOrRecoverPilotMoneroDepositAddress(pool: Pool, rpc: MoneroDepositAddressRpc, context: Omit, rawRequest: unknown, now = Date.now()): Promise<{ reservation: PilotDepositReservation; address: string; replayed: boolean }> { const reserved = await reservePilotMoneroDepositRequest(pool, context, rawRequest, now); const initial = await pool.query(`SELECT * FROM wk_custody.pilot_monero_deposit_reservations WHERE allocation_id=$1 AND wallet_generation_id=$2`, [reserved.reservation.allocationId, reserved.reservation.walletGenerationId]); if (!initial.rowCount) throw new Error('Pilot deposit reservation was not found for its wallet generation'); if (initial.rows[0].status === 'registered') { const address = await validateXmrSubaddress(initial.rows[0].monero_address ?? ''); return { reservation: toReservation(initial.rows[0]), address, replayed: true }; } const row = initial.rows[0]; const lockKey = `wikideck-pilot-deposit-address-v1:${row.wallet_generation_id}:${row.account_index}`; const lockClient = await pool.connect(); let locked = false; try { await lockClient.query('SELECT pg_advisory_lock(hashtextextended($1,0))', [lockKey]); locked = true; // Another worker may have completed while this call waited for the per-wallet fence. const current = await pool.query(`SELECT * FROM wk_custody.pilot_monero_deposit_reservations WHERE allocation_id=$1 AND wallet_generation_id=$2`, [row.allocation_id, row.wallet_generation_id]); if (!current.rowCount) throw new Error('Pilot deposit reservation disappeared'); if (current.rows[0].status === 'registered') { const address = await validateXmrSubaddress(current.rows[0].monero_address ?? ''); return { reservation: toReservation(current.rows[0]), address, replayed: true }; } const allocation = current.rows[0]; if (await rpc.verifyWallet() !== allocation.wallet_fingerprint) { throw new Error('Configured Monero wallet identity differs from the durable pilot reservation'); } const expectedIndex = Number(allocation.address_index); const expectedLabel = `wikideck-pilot-deposit-v1:${allocation.allocation_id}`; const createWasAlreadyStarted = allocation.status === 'creating'; let existing = await rpc.getSubaddress(Number(allocation.account_index), expectedIndex); if (!createWasAlreadyStarted) { // Persist the side-effect intent first. If the process or transport fails afterwards, // retry is reconciliation-only: it may recover an existing labeled index but never create twice. const createStartedAt = Math.max(now, Number(allocation.reserved_at)); const started = await pool.query(`UPDATE wk_custody.pilot_monero_deposit_reservations SET status='creating',create_started_at=$2 WHERE allocation_id=$1 AND status='reserved' RETURNING allocation_id`, [allocation.allocation_id, createStartedAt]); if (started.rowCount !== 1) throw new Error('Pilot address create intent could not be committed before Wallet-RPC'); } if (!existing) { if (createWasAlreadyStarted) { throw new Error('Pilot Wallet-RPC create outcome is uncertain; reconcile the reserved index and wallet before retrying'); } const created = await rpc.createSubaddress(Number(allocation.account_index), expectedLabel); if (created.addressIndex !== expectedIndex) { throw new Error('Monero wallet created an unexpected pilot subaddress index; reservation requires operator reconciliation'); } // Never trust the create response alone; query the exact reserved index and stable label. existing = await rpc.getSubaddress(Number(allocation.account_index), expectedIndex); if (!existing || existing.address !== created.address) { throw new Error('Monero wallet subaddress could not be recovered at the reserved pilot index'); } } if (existing.label !== expectedLabel) { throw new Error('Reserved pilot Monero subaddress index is occupied by a different wallet label'); } const address = await validateXmrSubaddress(existing.address); const reservation = toReservation(allocation); return await registerPilotAddress(pool, reservation, address, now); } finally { if (locked) await lockClient.query('SELECT pg_advisory_unlock(hashtextextended($1,0))', [lockKey]).catch(() => {}); lockClient.release(); } }