Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 4 additions & 32 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@
},
"dependencies": {
"@csfloat/cs2-inspect-serializer": "^1.1.0",
"@csfloat/tlsn-wasm": "0.1.0-alpha.14",
"@csfloat/tlsn-wasm": "0.1.0-alpha.15",
"buffer": "^6.0.3",
"comlink": "^4.4.2"
}
Expand Down
4 changes: 2 additions & 2 deletions src/environment.dev.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
export const environment = {
csfloat_base_api_url: 'http://localhost:8080/api',
notary: {
tlsn: 'https://notary.csfloat.com',
ws: 'wss://notary.csfloat.com/proxy',
tlsn: 'http://localhost:7047',
ws: 'ws://localhost:7047/proxy',
loggingLevel: 'Error',
},
reverse_watch_base_api_url: 'http://localhost:3434/api',
Expand Down
4 changes: 2 additions & 2 deletions src/environment.staging.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
export const environment = {
csfloat_base_api_url: 'https://csfloat.build/api',
notary: {
tlsn: 'https://notary.csfloat.com',
ws: 'wss://notary.csfloat.com/proxy',
tlsn: 'https://notary.csfloat.com/v15',
ws: 'wss://notary.csfloat.com/v15/proxy',
loggingLevel: 'Error',
},
reverse_watch_base_api_url: 'https://reverse.watch/api',
Expand Down
4 changes: 2 additions & 2 deletions src/environment.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
export const environment = {
csfloat_base_api_url: 'https://csfloat.com/api',
notary: {
tlsn: 'https://notary.csfloat.com',
ws: 'wss://notary.csfloat.com/proxy',
tlsn: 'https://notary.csfloat.com/v15',
ws: 'wss://notary.csfloat.com/v15/proxy',
loggingLevel: 'Warn',
},
reverse_watch_base_api_url: 'https://reverse.watch/api',
Expand Down
5 changes: 5 additions & 0 deletions src/lib/notary/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import {NetworkSetting} from '@csfloat/tlsn-wasm';

export type NotaryProverMode = 'proxy' | 'mpc';

export enum ProofType {
TRADE_OFFERS = 'trade_offers',
TRADE_OFFER = 'trade_offer',
Expand Down Expand Up @@ -71,6 +73,9 @@ export type NotaryProveRequest = {

// Optionally override the network setting for proving
network_setting?: NetworkSetting;

// Proxy-TLS is the default. MPC retains the legacy browser TCP-proxy flow.
prover_mode?: NotaryProverMode;
};
} & ProveRequestPayloads[T];
}[ProofType];
35 changes: 28 additions & 7 deletions src/offscreen/handlers/notary_prove.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@ import {
import {getSteamRequestURL} from '../../lib/notary/utils';
import * as Comlink from 'comlink';
import {environment} from '../../environment';
import type {LoggingLevel, Method, Prover as TProver, Reveal} from '@csfloat/tlsn-wasm';
import type {IoChannel, LoggingLevel, Method, Prover as TProver, ProverMode, Reveal} from '@csfloat/tlsn-wasm';
import {NotarySessionClient} from './notary_session_client';
import {Remote} from 'comlink';
import {wait} from '../../lib/utils/snips';
import {WebSocketIoChannel} from '../io_channel';

const {init, Prover}: any = Comlink.wrap(new Worker(new URL('../worker.ts', import.meta.url)));

Expand Down Expand Up @@ -65,36 +66,51 @@ export const TLSNProveOffscreenHandler = new ClosableOffscreenHandler<

const session = await NotarySessionClient.create(maxRecvData, maxSentData, maybeNotaryToken);

let verifierChannel: WebSocketIoChannel | undefined;
let serverChannel: WebSocketIoChannel | undefined;
try {
const proverMode: ProverMode = request.notary_request.meta?.prover_mode === 'mpc' ? 'Mpc' : 'Proxy';

// Create and setup prover
const prover = (await new Prover({
server_name: 'api.steampowered.com',
mode: proverMode,
max_recv_data: maxRecvData,
max_sent_data: maxSentData,
max_sent_records: undefined,
max_recv_data_online: undefined,
max_recv_records_online: undefined,
network: request.notary_request.meta?.network_setting ?? 'Latency',
defer_decryption_from_start: true,
client_auth: undefined,
root_certs: undefined,
})) as Remote<TProver>;

let verifierUrl = `${environment.notary.tlsn}/verifier?sessionId=${session.getID()}`;
if (maybeNotaryToken) {
verifierUrl += `&token=${maybeNotaryToken}`;
}

await prover.setup(verifierUrl);
verifierChannel = await WebSocketIoChannel.connect(toWebSocketUrl(verifierUrl));
await prover.setup(Comlink.proxy(verifierChannel));

// Convert headers to Map<string, number[]> for WASM
const headerMap = new Map<string, number[]>();
for (const [key, value] of Object.entries(headers)) {
headerMap.set(key, Buffer.from(value).toJSON().data);
}

let wsUrl = `${environment.notary.ws}`;
if (maybeNotaryToken) {
wsUrl += `?token=${maybeNotaryToken}`;
let serverIo: IoChannel | undefined;
if (proverMode === 'Mpc') {
let wsUrl = `${environment.notary.ws}`;
if (maybeNotaryToken) {
wsUrl += `?token=${maybeNotaryToken}`;
}
serverChannel = await WebSocketIoChannel.connect(wsUrl);
serverIo = Comlink.proxy(serverChannel);
}

// Send HTTP request via proxy
await prover.send_request(wsUrl, {
await prover.send_request(serverIo, {
uri: serverURL,
method: 'GET' as Method,
headers: headerMap,
Expand All @@ -118,6 +134,7 @@ export const TLSNProveOffscreenHandler = new ClosableOffscreenHandler<

return await completedPromise;
} finally {
await Promise.allSettled([verifierChannel?.close(), serverChannel?.close()]);
session.close();
}
},
Expand All @@ -129,6 +146,10 @@ export const TLSNProveOffscreenHandler = new ClosableOffscreenHandler<
}
);

function toWebSocketUrl(url: string): string {
return url.replace(/^http:/, 'ws:').replace(/^https:/, 'wss:');
}

/**
* Estimates the total request byte size over the wire if sent over HTTP 1.1
*
Expand Down
88 changes: 88 additions & 0 deletions src/offscreen/io_channel.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import {afterEach, beforeEach, describe, expect, it} from 'vitest';
import {WebSocketIoChannel} from './io_channel';

class FakeWebSocket extends EventTarget {
static readonly CONNECTING = 0;
static readonly OPEN = 1;
static readonly CLOSING = 2;
static readonly CLOSED = 3;
static latest?: FakeWebSocket;

readonly sent: Uint8Array[] = [];
binaryType: BinaryType = 'blob';
readyState = FakeWebSocket.CONNECTING;

constructor(readonly url: string) {
super();
FakeWebSocket.latest = this;
}

open(): void {
this.readyState = FakeWebSocket.OPEN;
this.dispatchEvent(new Event('open'));
}

message(data: ArrayBuffer): void {
const event = new Event('message');
Object.defineProperty(event, 'data', {value: data});
this.dispatchEvent(event);
}

send(data: ArrayBufferView | ArrayBuffer): void {
const bytes =
data instanceof ArrayBuffer
? new Uint8Array(data)
: new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
this.sent.push(new Uint8Array(bytes));
}

close(): void {
if (this.readyState === FakeWebSocket.CLOSED) {
return;
}
this.readyState = FakeWebSocket.CLOSED;
this.dispatchEvent(new Event('close'));
}
}

const originalWebSocket = globalThis.WebSocket;

describe('WebSocketIoChannel', () => {
beforeEach(() => {
FakeWebSocket.latest = undefined;
globalThis.WebSocket = FakeWebSocket as unknown as typeof WebSocket;
});

afterEach(() => {
globalThis.WebSocket = originalWebSocket;
});

it('queues binary messages and returns EOF on close', async () => {
const channelPromise = WebSocketIoChannel.connect('wss://notary.test/verifier');
const socket = FakeWebSocket.latest!;
socket.open();
const channel = await channelPromise;

socket.message(new Uint8Array([1, 2, 3]).buffer);
await expect(channel.read()).resolves.toEqual(new Uint8Array([1, 2, 3]));

const pendingRead = channel.read();
socket.close();
await expect(pendingRead).resolves.toBeNull();
await expect(channel.read()).resolves.toBeNull();
});

it('preserves write order and rejects writes after close', async () => {
const channelPromise = WebSocketIoChannel.connect('wss://notary.test/verifier');
const socket = FakeWebSocket.latest!;
socket.open();
const channel = await channelPromise;

await channel.write(new Uint8Array([1]));
await channel.write(new Uint8Array([2]));
expect(socket.sent).toEqual([new Uint8Array([1]), new Uint8Array([2])]);

await channel.close();
await expect(channel.write(new Uint8Array([3]))).rejects.toThrow('closed');
});
});
Loading