Signed, beneficiary-bount Monero subaddress enrollment
This commit is contained in:
Executable
+74
@@ -0,0 +1,74 @@
|
||||
import type { Pool } from 'pg';
|
||||
import { address, verifySignature } from '../testnet/consensus.ts';
|
||||
import { moneroDepositAddressRequestSchema, moneroDepositAddressMessage } from '../../shared/monero-deposit.ts';
|
||||
import { createOrRecoverMoneroDepositSubaddress, reserveMoneroDepositAllocation } from './monero-deposit-allocation-store.ts';
|
||||
import type { MoneroDepositAddressRpc } from './monero-observer.ts';
|
||||
|
||||
const MAX_REQUEST_LIFETIME_MS = 5 * 60_000;
|
||||
|
||||
type MainnetEnrollmentContext = {
|
||||
custodianId: string;
|
||||
chainId: string;
|
||||
walletGenerationId: string;
|
||||
walletFingerprint: string;
|
||||
accountIndex: number;
|
||||
now?: () => number;
|
||||
};
|
||||
|
||||
export type MoneroDepositAddressResult = {
|
||||
network: 'mainnet';
|
||||
allocationId: string;
|
||||
walletGenerationId: string;
|
||||
addressIndex: number;
|
||||
address: string;
|
||||
replayed: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* Verify an expiring customer-wallet signature and durably allocate that customer's one mainnet
|
||||
* subaddress. This only enrolls an address; it never credits a balance, starts a sale, or moves XMR.
|
||||
* Do not expose a public route until the matching credit, withdrawal, and recovery paths are ready.
|
||||
*/
|
||||
export async function enrollMoneroDepositAddress(pool: Pool, rpc: MoneroDepositAddressRpc,
|
||||
context: MainnetEnrollmentContext, rawRequest: unknown): Promise<MoneroDepositAddressResult> {
|
||||
const request = moneroDepositAddressRequestSchema.parse(rawRequest);
|
||||
if (request.network !== 'wikideck-randomx-mainnet-1' || request.custodianId !== context.custodianId ||
|
||||
request.chainId !== context.chainId || request.walletGenerationId !== context.walletGenerationId ||
|
||||
request.walletFingerprint !== context.walletFingerprint || request.accountIndex !== context.accountIndex) {
|
||||
throw new Error('Deposit address request does not match this mainnet custodian and wallet generation');
|
||||
}
|
||||
if (address(request.publicKey) !== request.actor) throw new Error('Deposit address signer does not own the requested beneficiary');
|
||||
const { signature, ...unsigned } = request;
|
||||
const message = moneroDepositAddressMessage(unsigned);
|
||||
if (!verifySignature(request.publicKey, request.actor, message, signature)) {
|
||||
throw new Error('Deposit address request signature is invalid');
|
||||
}
|
||||
const now = context.now?.() ?? Date.now();
|
||||
if (!Number.isSafeInteger(now) || now < 0 || request.expiresAt < now ||
|
||||
request.expiresAt > now + MAX_REQUEST_LIFETIME_MS) {
|
||||
throw new Error('Deposit address request is expired or outside the allowed window');
|
||||
}
|
||||
|
||||
const { allocation } = await reserveMoneroDepositAllocation(pool, {
|
||||
requestId: request.requestId,
|
||||
custodianId: context.custodianId,
|
||||
marketNetwork: 'wikideck-randomx-mainnet-1',
|
||||
marketChainId: context.chainId,
|
||||
beneficiary: request.actor,
|
||||
walletGenerationId: context.walletGenerationId,
|
||||
walletFingerprint: context.walletFingerprint,
|
||||
accountIndex: context.accountIndex,
|
||||
reservedAt: now,
|
||||
});
|
||||
const registered = await createOrRecoverMoneroDepositSubaddress(pool, rpc, allocation.allocationId,
|
||||
context.walletGenerationId, now);
|
||||
if (registered.allocation.beneficiary !== request.actor || registered.allocation.custodianId !== context.custodianId ||
|
||||
registered.allocation.marketChainId !== context.chainId || registered.allocation.moneroNetwork !== 'mainnet' ||
|
||||
registered.allocation.status !== 'registered' || !registered.allocation.moneroAddress) {
|
||||
throw new Error('Registered Monero deposit address does not match the signed beneficiary allocation');
|
||||
}
|
||||
return { network: 'mainnet', allocationId: registered.allocation.allocationId,
|
||||
walletGenerationId: registered.allocation.walletGenerationId,
|
||||
addressIndex: registered.allocation.addressIndex, address: registered.allocation.moneroAddress,
|
||||
replayed: registered.replayed };
|
||||
}
|
||||
Reference in New Issue
Block a user