Added wallet-identity check before allocation
This commit is contained in:
@@ -3,7 +3,7 @@ import assert from 'node:assert/strict';
|
||||
import { keccak } from 'hash-wasm';
|
||||
import { ADDITIONAL_MARKET_FEE } from '../shared/market.ts';
|
||||
import { createMoneroDepositAddressRpc, createMoneroWalletObserver, loadMoneroObserverConfig,
|
||||
type MoneroFixtureTransport } from '../server/market/monero-observer.ts';
|
||||
moneroWalletFingerprint, type MoneroFixtureTransport } from '../server/market/monero-observer.ts';
|
||||
|
||||
const config = { network: 'mainnet' as const, url: 'http://127.0.0.1:18083/json_rpc', username: 'fixture', password: 'not-a-live-secret' };
|
||||
function response(id: string, result: Record<string, unknown>, status = 200, type = 'application/json') {
|
||||
@@ -63,16 +63,25 @@ test('deposit-address RPC client is narrowly allow-listed and creates only one v
|
||||
const transport: MoneroFixtureTransport = async (_url, init) => {
|
||||
const body = JSON.parse(init.body as string) as { id: string; method: string; params: unknown };
|
||||
calls.push({ method: body.method, params: body.params });
|
||||
if (body.method === 'get_address') return response(body.id, { address: ADDITIONAL_MARKET_FEE.address,
|
||||
addresses: [{ address: subaddress, label: 'wikideck-deposit-v1:fixture', address_index: 7, used: false }], status: 'OK' });
|
||||
if (body.method === 'get_wallet_info') return response(body.id, { address: ADDITIONAL_MARKET_FEE.address,
|
||||
network_type: 'Mainnet', wallet_type: 'Watch only' });
|
||||
if (body.method === 'get_address') {
|
||||
const params = body.params as { address_index?: number[] };
|
||||
return response(body.id, { address: ADDITIONAL_MARKET_FEE.address,
|
||||
...(params.address_index ? { addresses: [{ address: subaddress, label: 'wikideck-deposit-v1:fixture', address_index: 7, used: false }] } : {}),
|
||||
status: 'OK' });
|
||||
}
|
||||
if (body.method === 'create_address') return response(body.id, { address: subaddress, address_index: 7,
|
||||
address_indices: [7], addresses: [subaddress], status: 'OK' });
|
||||
return response(body.id, { height: 100, status: 'OK' });
|
||||
};
|
||||
const rpc = createMoneroDepositAddressRpc(config, transport);
|
||||
assert.equal(await rpc.verifyWallet(), await moneroWalletFingerprint(ADDITIONAL_MARKET_FEE.address));
|
||||
assert.deepEqual(await rpc.getSubaddress(0, 7), { address: subaddress, label: 'wikideck-deposit-v1:fixture' });
|
||||
assert.deepEqual(await rpc.createSubaddress(0, 'wikideck-deposit-v1:fixture'), { address: subaddress, addressIndex: 7 });
|
||||
assert.deepEqual(calls, [
|
||||
{ method: 'get_wallet_info', params: {} },
|
||||
{ method: 'get_address', params: { account_index: 0 } },
|
||||
{ method: 'get_address', params: { account_index: 0, address_index: [7] } },
|
||||
{ method: 'create_address', params: { account_index: 0, label: 'wikideck-deposit-v1:fixture', count: 1 } },
|
||||
]);
|
||||
@@ -87,6 +96,14 @@ test('deposit-address RPC client is narrowly allow-listed and creates only one v
|
||||
};
|
||||
await assert.rejects(createMoneroDepositAddressRpc(config, onlyAddressTransport).getSubaddress(0, 0), /expected/);
|
||||
assert.equal(unexpectedCalls, 1);
|
||||
|
||||
const spendCapableWallet: MoneroFixtureTransport = async (_url, init) => {
|
||||
const body = JSON.parse(init.body as string) as { id: string; method: string };
|
||||
if (body.method === 'get_wallet_info') return response(body.id, { address: ADDITIONAL_MARKET_FEE.address,
|
||||
network_type: 'Mainnet', wallet_type: 'Normal' });
|
||||
return response(body.id, { address: ADDITIONAL_MARKET_FEE.address, status: 'OK' });
|
||||
};
|
||||
await assert.rejects(createMoneroDepositAddressRpc(config, spendCapableWallet).verifyWallet(), /expected/);
|
||||
});
|
||||
|
||||
test('observer refuses non-loopback plaintext, URL credentials and malformed height ranges', async () => {
|
||||
|
||||
@@ -17,6 +17,7 @@ import { CustodyMarket } from '../../server/market/custody.ts';
|
||||
import { migrateCustody, grantCustodyRuntime, grantMoneroObserverRuntime, CUSTODY_SCHEMA_V1, CUSTODY_SCHEMA_V1_DIGEST,
|
||||
CUSTODY_MIGRATION_V2, CUSTODY_SCHEMA_V2_DIGEST } from '../../server/market/postgres-schema.ts';
|
||||
import { persistMoneroObservationScan } from '../../server/market/monero-observation-store.ts';
|
||||
import { moneroWalletFingerprint } from '../../server/market/monero-observer.ts';
|
||||
import { createOrRecoverMoneroDepositSubaddress, registerMoneroDepositSubaddress,
|
||||
reserveMoneroDepositAllocation } from '../../server/market/monero-deposit-allocation-store.ts';
|
||||
import { importSqliteCustody } from '../../server/market/custody-import.ts';
|
||||
@@ -289,13 +290,16 @@ test('deposit allocation reserves unique beneficiary-bound indexes before RPC an
|
||||
[first.allocation.allocationId, other.actor]), /identity is immutable/);
|
||||
|
||||
const recoveryGeneration = randomUUID();
|
||||
const recoveryFingerprint = await moneroWalletFingerprint(ADDITIONAL_MARKET_FEE.address);
|
||||
const recoveryReservation = await reserveMoneroDepositAllocation(f.admin, { ...input(randomUUID(), buyer.actor),
|
||||
walletGenerationId: recoveryGeneration, walletFingerprint: 'a'.repeat(64) });
|
||||
walletGenerationId: recoveryGeneration, walletFingerprint: recoveryFingerprint });
|
||||
const walletAddresses = new Map<string, { address: string; label: string }>();
|
||||
const recoveryAddress = await syntheticSubaddress(6);
|
||||
let createCalls = 0;
|
||||
let createCalls = 0, addressReads = 0, liveFingerprint = 'b'.repeat(64);
|
||||
const fakeRpc = {
|
||||
async verifyWallet() { return liveFingerprint; },
|
||||
async getSubaddress(accountIndex: number, addressIndex: number) {
|
||||
addressReads++;
|
||||
return walletAddresses.get(`${accountIndex}:${addressIndex}`) ?? null;
|
||||
},
|
||||
async createSubaddress(accountIndex: number, label: string) {
|
||||
@@ -306,6 +310,10 @@ test('deposit allocation reserves unique beneficiary-bound indexes before RPC an
|
||||
return { address: row.address, addressIndex: recoveryReservation.allocation.addressIndex };
|
||||
},
|
||||
};
|
||||
await assert.rejects(createOrRecoverMoneroDepositSubaddress(f.admin, fakeRpc,
|
||||
recoveryReservation.allocation.allocationId, recoveryGeneration, reservedAt + 4), /identity differs/);
|
||||
assert.equal(addressReads, 0); assert.equal(createCalls, 0, 'wallet mismatch must fail before address operations');
|
||||
liveFingerprint = recoveryFingerprint;
|
||||
await assert.rejects(createOrRecoverMoneroDepositSubaddress(f.admin, fakeRpc,
|
||||
recoveryReservation.allocation.allocationId, recoveryGeneration, reservedAt + 5), /response lost/);
|
||||
const recovered = await createOrRecoverMoneroDepositSubaddress(f.admin, fakeRpc,
|
||||
|
||||
Reference in New Issue
Block a user