// Asserts that the WASM-compiled `ui/core` produces canonical bytes // byte-for-byte identical to the gateway-side fixtures committed in // `ui/core/canon/testdata/`. The fixtures themselves are exercised by // the Go-side parity test in `gateway/authn/parity_with_ui_core_test.go`, // so passing both proves Go-Go parity AND Go-TS parity through WASM. // // The signature parity check additionally proves that a signature // produced by Node's `ed25519` over the WASM-built canonical bytes is // accepted by the gateway's `VerifyResponseSignature` / // `VerifyEventSignature` (re-checked here via Core.verifyResponse / // Core.verifyEvent against the same fixture key). import { createPrivateKey, createPublicKey, sign as cryptoSign, } from "node:crypto"; import { readFileSync } from "node:fs"; import { resolve } from "node:path"; import { beforeAll, describe, expect, test } from "vitest"; import type { Core } from "../src/platform/core/index"; import { loadWasmCoreForTest } from "./setup-wasm"; const TESTDATA_DIR = resolve( process.cwd(), "..", "core", "canon", "testdata", ) + "/"; interface RequestFixture { protocol_version: string; device_session_id: string; message_type: string; timestamp_ms: number; request_id: string; payload: string; payload_hash_hex: string; expected_canonical_bytes_hex: string; private_key_seed_hex: string; public_key_base64: string; expected_signature_hex: string; } interface ResponseFixture { protocol_version: string; request_id: string; timestamp_ms: number; result_code: string; payload: string; payload_hash_hex: string; expected_canonical_bytes_hex: string; private_key_seed_hex: string; public_key_base64: string; expected_signature_hex: string; } interface EventFixture { event_type: string; event_id: string; timestamp_ms: number; request_id: string; trace_id: string; payload: string; payload_hash_hex: string; expected_canonical_bytes_hex: string; private_key_seed_hex: string; public_key_base64: string; expected_signature_hex: string; } let core: Core; beforeAll(async () => { core = await loadWasmCoreForTest(); }); describe("WasmCore canon parity with gateway fixtures", () => { test.each([ "request_user_account_get.json", "request_user_games_command.json", "request_lobby_my_games_list.json", ])("%s — canonical bytes byte-for-byte equal", (name) => { const fixture = readJson(name); const canonical = core.signRequest({ protocolVersion: fixture.protocol_version, deviceSessionId: fixture.device_session_id, messageType: fixture.message_type, timestampMs: BigInt(fixture.timestamp_ms), requestId: fixture.request_id, payloadHash: hexToBytes(fixture.payload_hash_hex), }); expect(bytesToHex(canonical)).toBe(fixture.expected_canonical_bytes_hex); const signature = signEd25519( hexToBytes(fixture.private_key_seed_hex), canonical, ); expect(bytesToHex(signature)).toBe(fixture.expected_signature_hex); }); test("response_ok.json — verifyResponse accepts canonical signature", () => { const fixture = readJson("response_ok.json"); const publicKey = base64ToBytes(fixture.public_key_base64); const signature = hexToBytes(fixture.expected_signature_hex); const fields = { protocolVersion: fixture.protocol_version, requestId: fixture.request_id, timestampMs: BigInt(fixture.timestamp_ms), resultCode: fixture.result_code, payloadHash: hexToBytes(fixture.payload_hash_hex), }; expect(core.verifyResponse(publicKey, signature, fields)).toBe(true); const tampered = new Uint8Array(signature); tampered[0] ^= 0xff; expect(core.verifyResponse(publicKey, tampered, fields)).toBe(false); }); test("event_gateway_server_time.json — verifyEvent accepts canonical signature", () => { const fixture = readJson("event_gateway_server_time.json"); const publicKey = base64ToBytes(fixture.public_key_base64); const signature = hexToBytes(fixture.expected_signature_hex); const fields = { eventType: fixture.event_type, eventId: fixture.event_id, timestampMs: BigInt(fixture.timestamp_ms), requestId: fixture.request_id, traceId: fixture.trace_id, payloadHash: hexToBytes(fixture.payload_hash_hex), }; expect(core.verifyEvent(publicKey, signature, fields)).toBe(true); const tampered = new Uint8Array(signature); tampered[0] ^= 0xff; expect(core.verifyEvent(publicKey, tampered, fields)).toBe(false); }); }); function readJson(name: string): T { return JSON.parse(readFileSync(`${TESTDATA_DIR}${name}`, "utf8")) as T; } function hexToBytes(value: string): Uint8Array { const length = value.length / 2; const out = new Uint8Array(length); for (let i = 0; i < length; i++) { out[i] = parseInt(value.substr(i * 2, 2), 16); } return out; } function bytesToHex(value: Uint8Array): string { return Array.from(value, (byte) => byte.toString(16).padStart(2, "0"), ).join(""); } function base64ToBytes(value: string): Uint8Array { return Uint8Array.from(Buffer.from(value, "base64")); } // Node's `ed25519` accepts a raw 32-byte seed packaged as a PKCS#8 DER // blob; this helper synthesises that blob so the test fixture seed // matches the Go-side `ed25519.NewKeyFromSeed` shape used by the // committed canon golden values. function signEd25519(seed: Uint8Array, message: Uint8Array): Uint8Array { const pkcs8 = pkcs8FromEd25519Seed(seed); const key = createPrivateKey({ key: pkcs8, format: "der", type: "pkcs8" }); const signature = cryptoSign(null, message, key); return new Uint8Array(signature); } function pkcs8FromEd25519Seed(seed: Uint8Array): Buffer { if (seed.length !== 32) { throw new Error(`ed25519 seed must be 32 bytes, got ${seed.length}`); } // PKCS#8 wrapping for an Ed25519 raw seed: // SEQUENCE { // INTEGER 0, // SEQUENCE { OID 1.3.101.112 (Ed25519) }, // OCTET STRING { OCTET STRING { seed } } // } const prefix = Buffer.from([ 0x30, 0x2e, 0x02, 0x01, 0x00, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x70, 0x04, 0x22, 0x04, 0x20, ]); return Buffer.concat([prefix, Buffer.from(seed)]); } // `createPublicKey` is imported only so future tests can derive the // public key from the same seed without going through the WASM bridge. // Suppress the unused-import warning by referencing the binding here. void createPublicKey;