mirror of
https://github.com/tlsnotary/tlsn-js.git
synced 2026-01-09 15:07:59 -05:00
feat: return raw data from transcript and move parsing to client side (#92)
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -6,3 +6,4 @@ dev-build/
|
||||
test-build/
|
||||
./demo/node_modules
|
||||
utils/tlsn
|
||||
.vscode
|
||||
@@ -16,7 +16,7 @@
|
||||
"react-dom": "^18.2.0",
|
||||
"react-loader-spinner": "^6.1.6",
|
||||
"style-loader": "^4.0.0",
|
||||
"tlsn-js": "file:../../.."
|
||||
"tlsn-js": "../../.."
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.0.26",
|
||||
@@ -36,4 +36,4 @@
|
||||
"webpack-cli": "^4.10.0",
|
||||
"webpack-dev-server": "^4.11.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,8 @@ import { Watch } from 'react-loader-spinner';
|
||||
import { Prover as TProver } from 'tlsn-js';
|
||||
import { type Method } from 'tlsn-wasm';
|
||||
import './app.scss';
|
||||
import { HTTPParser } from 'http-parser-js';
|
||||
import { Commit, mapStringToRange, subtractRanges } from 'tlsn-js';
|
||||
|
||||
const { init, Prover }: any = Comlink.wrap(
|
||||
new Worker(new URL('./worker.ts', import.meta.url)),
|
||||
@@ -78,25 +80,42 @@ function App(): ReactElement {
|
||||
}
|
||||
|
||||
try {
|
||||
const { sent, recv } = transcript;
|
||||
const {
|
||||
info: recvInfo,
|
||||
headers: recvHeaders,
|
||||
body: recvBody,
|
||||
} = parseHttpMessage(Buffer.from(recv), 'response');
|
||||
|
||||
const body = JSON.parse(recvBody[0].toString());
|
||||
|
||||
console.time('reveal');
|
||||
const reveal = {
|
||||
sent: [
|
||||
transcript.ranges.sent.info!,
|
||||
transcript.ranges.sent.headers!['connection'],
|
||||
transcript.ranges.sent.headers!['host'],
|
||||
transcript.ranges.sent.headers!['content-type'],
|
||||
transcript.ranges.sent.headers!['content-length'],
|
||||
...transcript.ranges.sent.lineBreaks,
|
||||
],
|
||||
const reveal: Commit = {
|
||||
sent: subtractRanges(
|
||||
{ start: 0, end: sent.length },
|
||||
mapStringToRange(
|
||||
['secret: test_secret'],
|
||||
Buffer.from(sent).toString('utf-8'),
|
||||
),
|
||||
),
|
||||
recv: [
|
||||
transcript.ranges.recv.info!,
|
||||
transcript.ranges.recv.headers['server'],
|
||||
transcript.ranges.recv.headers['date'],
|
||||
transcript.ranges.recv.headers['content-type'],
|
||||
transcript.ranges.recv.json!['name'],
|
||||
transcript.ranges.recv.json!['eye_color'],
|
||||
transcript.ranges.recv.json!['gender'],
|
||||
...transcript.ranges.recv.lineBreaks,
|
||||
...mapStringToRange(
|
||||
[
|
||||
recvInfo,
|
||||
`${recvHeaders[4]}: ${recvHeaders[5]}\r\n`,
|
||||
`${recvHeaders[6]}: ${recvHeaders[7]}\r\n`,
|
||||
`${recvHeaders[8]}: ${recvHeaders[9]}\r\n`,
|
||||
`${recvHeaders[10]}: ${recvHeaders[11]}\r\n`,
|
||||
`${recvHeaders[12]}: ${recvHeaders[13]}`,
|
||||
`${recvHeaders[14]}: ${recvHeaders[15]}`,
|
||||
`${recvHeaders[16]}: ${recvHeaders[17]}`,
|
||||
`${recvHeaders[18]}: ${recvHeaders[19]}`,
|
||||
`"name":"${body.name}"`,
|
||||
`"gender":"${body.gender}"`,
|
||||
`"eye_color":"${body.eye_color}"`,
|
||||
],
|
||||
Buffer.from(recv).toString('utf-8'),
|
||||
),
|
||||
],
|
||||
};
|
||||
console.log('Start reveal:', reveal);
|
||||
@@ -133,10 +152,12 @@ function App(): ReactElement {
|
||||
|
||||
<div className="text-center text-gray-700 mb-6">
|
||||
<p>
|
||||
Before clicking the <span className="font-semibold">Start</span> button,
|
||||
make sure the <i>interactive verifier</i> and the{' '}
|
||||
<i>web socket proxy</i> are running.</p>
|
||||
<p>Check the{' '}
|
||||
Before clicking the <span className="font-semibold">Start</span>{' '}
|
||||
button, make sure the <i>interactive verifier</i> and the{' '}
|
||||
<i>web socket proxy</i> are running.
|
||||
</p>
|
||||
<p>
|
||||
Check the{' '}
|
||||
<a href="README.md" className="text-blue-600 hover:underline">
|
||||
README
|
||||
</a>{' '}
|
||||
@@ -208,3 +229,35 @@ function App(): ReactElement {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function parseHttpMessage(buffer: Buffer, type: 'request' | 'response') {
|
||||
const parser = new HTTPParser(
|
||||
type === 'request' ? HTTPParser.REQUEST : HTTPParser.RESPONSE,
|
||||
);
|
||||
const body: Buffer[] = [];
|
||||
let complete = false;
|
||||
let headers: string[] = [];
|
||||
|
||||
parser.onBody = (t) => {
|
||||
body.push(t);
|
||||
};
|
||||
|
||||
parser.onHeadersComplete = (res) => {
|
||||
headers = res.headers;
|
||||
};
|
||||
|
||||
parser.onMessageComplete = () => {
|
||||
complete = true;
|
||||
};
|
||||
|
||||
parser.execute(buffer);
|
||||
parser.finish();
|
||||
|
||||
if (!complete) throw new Error(`Could not parse ${type.toUpperCase()}`);
|
||||
|
||||
return {
|
||||
info: buffer.toString('utf-8').split('\r\n')[0] + '\r\n',
|
||||
headers,
|
||||
body,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
"css-loader": "^7.1.2",
|
||||
"postcss": "^8.4.49",
|
||||
"postcss-loader": "^8.1.1",
|
||||
"http-parser-js": "^0.5.9",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-loader-spinner": "^6.1.6",
|
||||
|
||||
@@ -8,10 +8,12 @@ import {
|
||||
Commit,
|
||||
NotaryServer,
|
||||
Transcript,
|
||||
mapStringToRange,
|
||||
subtractRanges,
|
||||
} from 'tlsn-js';
|
||||
import { PresentationJSON } from 'tlsn-js/build/types';
|
||||
import './app.scss';
|
||||
|
||||
import { HTTPParser } from 'http-parser-js';
|
||||
const { init, Prover, Presentation }: any = Comlink.wrap(
|
||||
new Worker(new URL('./worker.ts', import.meta.url)),
|
||||
);
|
||||
@@ -22,8 +24,12 @@ const root = createRoot(container!);
|
||||
root.render(<App />);
|
||||
|
||||
const local = true; // Toggle between local and remote notary
|
||||
const notaryUrl = local ? 'http://localhost:7047' : 'https://notary.pse.dev/v0.1.0-alpha.7';
|
||||
const websocketProxyUrl = local ? 'ws://localhost:55688' : 'wss://notary.pse.dev/proxy?token=swapi.dev';
|
||||
const notaryUrl = local
|
||||
? 'http://localhost:7047'
|
||||
: 'https://notary.pse.dev/v0.1.0-alpha.7';
|
||||
const websocketProxyUrl = local
|
||||
? 'ws://localhost:55688'
|
||||
: 'wss://notary.pse.dev/proxy?token=swapi.dev';
|
||||
const loggingLevel = 'Info'; // https://github.com/tlsnotary/tlsn/blob/main/crates/wasm/src/log.rs#L8
|
||||
|
||||
const serverUrl = 'https://swapi.dev/api/people/1';
|
||||
@@ -59,6 +65,7 @@ function App(): ReactElement {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
secret: 'test_secret',
|
||||
},
|
||||
body: {
|
||||
hello: 'world',
|
||||
@@ -71,23 +78,44 @@ function App(): ReactElement {
|
||||
|
||||
console.time('transcript');
|
||||
const transcript = await prover.transcript();
|
||||
console.log(transcript);
|
||||
const { sent, recv } = transcript;
|
||||
console.log(new Transcript({ sent, recv }));
|
||||
console.timeEnd('transcript');
|
||||
console.time('commit');
|
||||
|
||||
const {
|
||||
info: recvInfo,
|
||||
headers: recvHeaders,
|
||||
body: recvBody,
|
||||
} = parseHttpMessage(Buffer.from(recv), 'response');
|
||||
|
||||
const body = JSON.parse(recvBody[0].toString());
|
||||
|
||||
const commit: Commit = {
|
||||
sent: [
|
||||
transcript.ranges.sent.info!,
|
||||
transcript.ranges.sent.headers!['content-type'],
|
||||
transcript.ranges.sent.headers!['host'],
|
||||
...transcript.ranges.sent.lineBreaks,
|
||||
],
|
||||
sent: subtractRanges(
|
||||
{ start: 0, end: sent.length },
|
||||
mapStringToRange(
|
||||
['secret: test_secret'],
|
||||
Buffer.from(sent).toString('utf-8'),
|
||||
),
|
||||
),
|
||||
recv: [
|
||||
transcript.ranges.recv.info!,
|
||||
transcript.ranges.recv.headers!['server'],
|
||||
transcript.ranges.recv.headers!['date'],
|
||||
transcript.ranges.recv.json!['name'],
|
||||
transcript.ranges.recv.json!['gender'],
|
||||
...transcript.ranges.recv.lineBreaks,
|
||||
...mapStringToRange(
|
||||
[
|
||||
recvInfo,
|
||||
`${recvHeaders[4]}: ${recvHeaders[5]}\r\n`,
|
||||
`${recvHeaders[6]}: ${recvHeaders[7]}\r\n`,
|
||||
`${recvHeaders[8]}: ${recvHeaders[9]}\r\n`,
|
||||
`${recvHeaders[10]}: ${recvHeaders[11]}\r\n`,
|
||||
`${recvHeaders[12]}: ${recvHeaders[13]}`,
|
||||
`${recvHeaders[14]}: ${recvHeaders[15]}`,
|
||||
`${recvHeaders[16]}: ${recvHeaders[17]}`,
|
||||
`${recvHeaders[18]}: ${recvHeaders[19]}`,
|
||||
`"name":"${body.name}"`,
|
||||
`"gender":"${body.gender}"`,
|
||||
],
|
||||
Buffer.from(recv).toString('utf-8'),
|
||||
),
|
||||
],
|
||||
};
|
||||
const notarizationOutputs = await prover.notarize(commit);
|
||||
@@ -164,12 +192,16 @@ function App(): ReactElement {
|
||||
</h1>
|
||||
<div className="mb-4 text-base font-light max-w-2xl">
|
||||
<p>
|
||||
This demo showcases how to use TLSNotary in a React/TypeScript app with the tlsn-js library.
|
||||
We will fetch JSON data from the Star Wars API, notarize the TLS request using TLSNotary,
|
||||
and verify the proof. The demo runs entirely in the browser.
|
||||
This demo showcases how to use TLSNotary in a React/TypeScript app
|
||||
with the tlsn-js library. We will fetch JSON data from the Star Wars
|
||||
API, notarize the TLS request using TLSNotary, and verify the proof.
|
||||
The demo runs entirely in the browser.
|
||||
</p>
|
||||
<p>
|
||||
<a href="https://docs.tlsnotary.org/quick_start/tlsn-js.html" className="text-blue-500 hover:underline">
|
||||
<a
|
||||
href="https://docs.tlsnotary.org/quick_start/tlsn-js.html"
|
||||
className="text-blue-500 hover:underline"
|
||||
>
|
||||
More info
|
||||
</a>
|
||||
</p>
|
||||
@@ -199,7 +231,8 @@ function App(): ReactElement {
|
||||
|
||||
<div className="mb-4">
|
||||
<p className="mb-2 text-base font-light">
|
||||
There are two versions of the demo: one with a normal config and one with a helper method.
|
||||
There are two versions of the demo: one with a normal config and one
|
||||
with a helper method.
|
||||
</p>
|
||||
<div className="flex justify-center gap-4">
|
||||
<button
|
||||
@@ -273,3 +306,35 @@ function App(): ReactElement {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function parseHttpMessage(buffer: Buffer, type: 'request' | 'response') {
|
||||
const parser = new HTTPParser(
|
||||
type === 'request' ? HTTPParser.REQUEST : HTTPParser.RESPONSE,
|
||||
);
|
||||
const body: Buffer[] = [];
|
||||
let complete = false;
|
||||
let headers: string[] = [];
|
||||
|
||||
parser.onBody = (t) => {
|
||||
body.push(t);
|
||||
};
|
||||
|
||||
parser.onHeadersComplete = (res) => {
|
||||
headers = res.headers;
|
||||
};
|
||||
|
||||
parser.onMessageComplete = () => {
|
||||
complete = true;
|
||||
};
|
||||
|
||||
parser.execute(buffer);
|
||||
parser.finish();
|
||||
|
||||
if (!complete) throw new Error(`Could not parse ${type.toUpperCase()}`);
|
||||
|
||||
return {
|
||||
info: buffer.toString('utf-8').split('\r\n')[0] + '\r\n',
|
||||
headers,
|
||||
body,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
"react-dom": "^18.2.0",
|
||||
"react-loader-spinner": "^6.1.6",
|
||||
"tailwindcss": "^3.4.14",
|
||||
"tlsn-js": "0.1.0-alpha.7.1",
|
||||
"tlsn-js": "../../",
|
||||
"ws": "^8.18.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -7,9 +7,12 @@ import {
|
||||
Verifier as TVerifier,
|
||||
Commit,
|
||||
Transcript,
|
||||
subtractRanges,
|
||||
mapStringToRange,
|
||||
} from 'tlsn-js';
|
||||
import './app.scss';
|
||||
import WebSocketStream from './stream';
|
||||
import { HTTPParser } from 'http-parser-js';
|
||||
|
||||
const { init, Prover, Verifier }: any = Comlink.wrap(
|
||||
new Worker(new URL('./worker.ts', import.meta.url)),
|
||||
@@ -152,26 +155,46 @@ function App(): ReactElement {
|
||||
|
||||
addProverLog('Response received');
|
||||
addProverLog('Transcript sent');
|
||||
addProverLog(transcript.sent);
|
||||
addProverLog(Buffer.from(transcript.sent).toString('utf-8'));
|
||||
addProverLog('Transcript received');
|
||||
addProverLog(transcript.recv);
|
||||
addProverLog(Buffer.from(transcript.recv).toString('utf-8'));
|
||||
|
||||
addProverLog('Revealing data to verifier');
|
||||
|
||||
const { sent, recv } = transcript;
|
||||
const {
|
||||
info: recvInfo,
|
||||
headers: recvHeaders,
|
||||
body: recvBody,
|
||||
} = parseHttpMessage(Buffer.from(recv), 'response');
|
||||
|
||||
const body = JSON.parse(recvBody[0].toString());
|
||||
// Prover only reveals parts the transcript to the verifier
|
||||
const commit: Commit = {
|
||||
sent: [
|
||||
transcript.ranges.sent.info!,
|
||||
transcript.ranges.sent.headers!['content-type'],
|
||||
transcript.ranges.sent.headers!['host'],
|
||||
...transcript.ranges.sent.lineBreaks,
|
||||
],
|
||||
sent: subtractRanges(
|
||||
{ start: 0, end: sent.length },
|
||||
mapStringToRange(
|
||||
['secret: test_secret'],
|
||||
Buffer.from(sent).toString('utf-8'),
|
||||
),
|
||||
),
|
||||
recv: [
|
||||
transcript.ranges.recv.info!,
|
||||
transcript.ranges.recv.headers!['server'],
|
||||
transcript.ranges.recv.headers!['date'],
|
||||
transcript.ranges.recv.json!['name'],
|
||||
transcript.ranges.recv.json!['gender'],
|
||||
...transcript.ranges.recv.lineBreaks,
|
||||
...mapStringToRange(
|
||||
[
|
||||
recvInfo,
|
||||
`${recvHeaders[4]}: ${recvHeaders[5]}\r\n`,
|
||||
`${recvHeaders[6]}: ${recvHeaders[7]}\r\n`,
|
||||
`${recvHeaders[8]}: ${recvHeaders[9]}\r\n`,
|
||||
`${recvHeaders[10]}: ${recvHeaders[11]}\r\n`,
|
||||
`${recvHeaders[12]}: ${recvHeaders[13]}`,
|
||||
`${recvHeaders[14]}: ${recvHeaders[15]}`,
|
||||
`${recvHeaders[16]}: ${recvHeaders[17]}`,
|
||||
`${recvHeaders[18]}: ${recvHeaders[19]}`,
|
||||
`"name":"${body.name}"`,
|
||||
`"gender":"${body.gender}"`,
|
||||
],
|
||||
Buffer.from(recv).toString('utf-8'),
|
||||
),
|
||||
],
|
||||
};
|
||||
await prover.reveal(commit);
|
||||
@@ -279,3 +302,35 @@ function Button(props: any) {
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function parseHttpMessage(buffer: Buffer, type: 'request' | 'response') {
|
||||
const parser = new HTTPParser(
|
||||
type === 'request' ? HTTPParser.REQUEST : HTTPParser.RESPONSE,
|
||||
);
|
||||
const body: Buffer[] = [];
|
||||
let complete = false;
|
||||
let headers: string[] = [];
|
||||
|
||||
parser.onBody = (t) => {
|
||||
body.push(t);
|
||||
};
|
||||
|
||||
parser.onHeadersComplete = (res) => {
|
||||
headers = res.headers;
|
||||
};
|
||||
|
||||
parser.onMessageComplete = () => {
|
||||
complete = true;
|
||||
};
|
||||
|
||||
parser.execute(buffer);
|
||||
parser.finish();
|
||||
|
||||
if (!complete) throw new Error(`Could not parse ${type.toUpperCase()}`);
|
||||
|
||||
return {
|
||||
info: buffer.toString('utf-8').split('\r\n')[0] + '\r\n',
|
||||
headers,
|
||||
body,
|
||||
};
|
||||
}
|
||||
|
||||
27324
package-lock.json
generated
27324
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
14
package.json
14
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "tlsn-js",
|
||||
"version": "0.1.0-alpha.7.1",
|
||||
"version": "0.1.0-alpha.7.2",
|
||||
"description": "",
|
||||
"repository": "https://github.com/tlsnotary/tlsn-js",
|
||||
"main": "build/lib.js",
|
||||
@@ -23,16 +23,17 @@
|
||||
"lint:eslint": "eslint . --fix",
|
||||
"lint:tsc": "tsc --noEmit",
|
||||
"lint": "concurrently npm:lint:tsc npm:lint:eslint",
|
||||
"run:test": "TS_NODE_COMPILER_OPTIONS='{\"module\": \"commonjs\"}' mocha -r ts-node/register 'test/testRunner.ts'",
|
||||
"test": "npm run build:tlsn-binaries && npm run build:test && npm run run:test",
|
||||
"test:only": "npm run build:test && npm run run:test"
|
||||
"run:spec": "TS_NODE_COMPILER_OPTIONS='{\"module\": \"commonjs\"}' mocha -r ts-node/register 'test/specs/*.ts'",
|
||||
"run:e2e": "TS_NODE_COMPILER_OPTIONS='{\"module\": \"commonjs\"}' mocha -r ts-node/register 'test/testRunner.ts'",
|
||||
"test": "npm run build:tlsn-binaries && npm run build:test && npm run run:e2e",
|
||||
"test:only": "npm run build:test && npm run run:e2e"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/mocha": "^10.0.6",
|
||||
"@types/serve-handler": "^6.1.4",
|
||||
"browserify": "^17.0.0",
|
||||
"buffer": "^6.0.3",
|
||||
"comlink": "^4.4.1",
|
||||
"comlink": "4.4.1",
|
||||
"concurrently": "^5.1.0",
|
||||
"constants-browserify": "^1.0.0",
|
||||
"copy-webpack-plugin": "^5.0.5",
|
||||
@@ -43,6 +44,7 @@
|
||||
"file-loader": "^5.0.2",
|
||||
"html-webpack-plugin": "~5.3.2",
|
||||
"https-browserify": "^1.0.0",
|
||||
"http-parser-js": "^0.5.9",
|
||||
"image-webpack-loader": "^6.0.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"mocha": "^10.2.0",
|
||||
@@ -71,4 +73,4 @@
|
||||
"dependencies": {
|
||||
"tlsn-wasm": "^0.1.0-alpha.7.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
11249
pnpm-lock.yaml
generated
11249
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
22
readme.md
22
readme.md
@@ -41,7 +41,7 @@ Comlink.expose({
|
||||
```
|
||||
```ts
|
||||
// app.ts
|
||||
import { NotaryServer } from 'tlsn-js';
|
||||
import { NotaryServer, subtractRanges, mapStringToRange } from 'tlsn-js';
|
||||
const { init, Prover, NotarizedSession, TlsProof }: any = Comlink.wrap(
|
||||
new Worker(new URL('./worker.ts', import.meta.url)),
|
||||
);
|
||||
@@ -72,19 +72,15 @@ const transcript = await prover.transcript();
|
||||
|
||||
// Select ranges to commit
|
||||
const commit: Commit = {
|
||||
sent: [
|
||||
transcript.ranges.sent.info!,
|
||||
transcript.ranges.sent.headers!['content-type'],
|
||||
transcript.ranges.sent.headers!['host'],
|
||||
...transcript.ranges.sent.lineBreaks,
|
||||
],
|
||||
sent: subtractRanges(
|
||||
{ start: 0, end: transcript.sent.length },
|
||||
mapStringToRange(
|
||||
['secret: test_secret'],
|
||||
Buffer.from(transcript.sent).toString('utf-8'),
|
||||
),
|
||||
),
|
||||
recv: [
|
||||
transcript.ranges.recv.info!,
|
||||
transcript.ranges.recv.headers!['server'],
|
||||
transcript.ranges.recv.headers!['date'],
|
||||
transcript.ranges.recv.json!['name'],
|
||||
transcript.ranges.recv.json!['gender'],
|
||||
...transcript.ranges.recv.lineBreaks,
|
||||
{ start: 0, end: transcript.recv.length },
|
||||
],
|
||||
};
|
||||
|
||||
|
||||
78
src/lib.ts
78
src/lib.ts
@@ -19,18 +19,14 @@ import initWasm, {
|
||||
ConnectionInfo,
|
||||
PartialTranscript,
|
||||
} from 'tlsn-wasm';
|
||||
import {
|
||||
arrayToHex,
|
||||
processTranscript,
|
||||
expect,
|
||||
headerToMap,
|
||||
hexToArray,
|
||||
} from './utils';
|
||||
import { ParsedTranscriptData, PresentationJSON } from './types';
|
||||
import { arrayToHex, expect, headerToMap, hexToArray } from './utils';
|
||||
import { PresentationJSON } from './types';
|
||||
import { Buffer } from 'buffer';
|
||||
import { Transcript, subtractRanges, mapStringToRange } from './transcript';
|
||||
|
||||
let LOGGING_LEVEL: LoggingLevel = 'Info';
|
||||
|
||||
function debug(...args: any[]) {
|
||||
function debug(...args: unknown[]) {
|
||||
if (['Debug', 'Trace'].includes(LOGGING_LEVEL)) {
|
||||
console.log('tlsn-js DEBUG', ...args);
|
||||
}
|
||||
@@ -78,7 +74,7 @@ export class Prover {
|
||||
headers?: {
|
||||
[name: string]: string;
|
||||
};
|
||||
body?: any;
|
||||
body?: unknown;
|
||||
maxSentData?: number;
|
||||
maxRecvData?: number;
|
||||
maxRecvDataOnline?: number;
|
||||
@@ -166,27 +162,14 @@ export class Prover {
|
||||
return this.#prover.setup(verifierUrl);
|
||||
}
|
||||
|
||||
async transcript(): Promise<{
|
||||
sent: string;
|
||||
recv: string;
|
||||
ranges: { recv: ParsedTranscriptData; sent: ParsedTranscriptData };
|
||||
}> {
|
||||
async transcript(): Promise<{ sent: number[]; recv: number[] }> {
|
||||
const transcript = this.#prover.transcript();
|
||||
const recv = Buffer.from(transcript.recv).toString();
|
||||
const sent = Buffer.from(transcript.sent).toString();
|
||||
return {
|
||||
recv,
|
||||
sent,
|
||||
ranges: {
|
||||
recv: processTranscript(recv),
|
||||
sent: processTranscript(sent),
|
||||
},
|
||||
};
|
||||
return { sent: transcript.sent, recv: transcript.recv };
|
||||
}
|
||||
|
||||
static getHeaderMap(
|
||||
url: string,
|
||||
body?: any,
|
||||
body?: unknown,
|
||||
headers?: { [key: string]: string },
|
||||
) {
|
||||
const hostname = new URL(url).hostname;
|
||||
@@ -217,7 +200,7 @@ export class Prover {
|
||||
url: string;
|
||||
method?: Method;
|
||||
headers?: { [key: string]: string };
|
||||
body?: any;
|
||||
body?: unknown;
|
||||
},
|
||||
): Promise<{
|
||||
status: number;
|
||||
@@ -481,45 +464,7 @@ export class NotaryServer {
|
||||
}
|
||||
}
|
||||
|
||||
export class Transcript {
|
||||
#sent: number[];
|
||||
#recv: number[];
|
||||
|
||||
constructor(params: { sent: number[]; recv: number[] }) {
|
||||
this.#recv = params.recv;
|
||||
this.#sent = params.sent;
|
||||
}
|
||||
|
||||
static processRanges(text: string) {
|
||||
return processTranscript(text);
|
||||
}
|
||||
|
||||
recv(redactedSymbol = '*') {
|
||||
return this.#recv.reduce((recv: string, num) => {
|
||||
recv =
|
||||
recv + (num === 0 ? redactedSymbol : Buffer.from([num]).toString());
|
||||
return recv;
|
||||
}, '');
|
||||
}
|
||||
|
||||
sent(redactedSymbol = '*') {
|
||||
return this.#sent.reduce((sent: string, num) => {
|
||||
sent =
|
||||
sent + (num === 0 ? redactedSymbol : Buffer.from([num]).toString());
|
||||
return sent;
|
||||
}, '');
|
||||
}
|
||||
|
||||
text = (redactedSymbol = '*') => {
|
||||
return {
|
||||
sent: this.sent(redactedSymbol),
|
||||
recv: this.recv(redactedSymbol),
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export {
|
||||
type ParsedTranscriptData,
|
||||
type LoggingLevel,
|
||||
type LoggingConfig,
|
||||
type Commit,
|
||||
@@ -530,4 +475,7 @@ export {
|
||||
type VerifierOutput,
|
||||
type ConnectionInfo,
|
||||
type PartialTranscript,
|
||||
Transcript,
|
||||
mapStringToRange,
|
||||
subtractRanges,
|
||||
};
|
||||
|
||||
103
src/transcript.ts
Normal file
103
src/transcript.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
import { Buffer } from 'buffer';
|
||||
|
||||
export class Transcript {
|
||||
#sent: number[];
|
||||
#recv: number[];
|
||||
|
||||
constructor(params: { sent: number[]; recv: number[] }) {
|
||||
this.#recv = params.recv;
|
||||
this.#sent = params.sent;
|
||||
}
|
||||
|
||||
get raw() {
|
||||
return {
|
||||
recv: this.#recv,
|
||||
sent: this.#sent,
|
||||
};
|
||||
}
|
||||
|
||||
recv(redactedSymbol = '*') {
|
||||
return this.#recv.reduce((recv: string, num) => {
|
||||
recv =
|
||||
recv + (num === 0 ? redactedSymbol : Buffer.from([num]).toString());
|
||||
return recv;
|
||||
}, '');
|
||||
}
|
||||
|
||||
sent(redactedSymbol = '*') {
|
||||
return this.#sent.reduce((sent: string, num) => {
|
||||
sent =
|
||||
sent + (num === 0 ? redactedSymbol : Buffer.from([num]).toString());
|
||||
return sent;
|
||||
}, '');
|
||||
}
|
||||
|
||||
text = (redactedSymbol = '*') => {
|
||||
return {
|
||||
sent: this.sent(redactedSymbol),
|
||||
recv: this.recv(redactedSymbol),
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export function subtractRanges(
|
||||
ranges: { start: number; end: number },
|
||||
negatives: { start: number; end: number }[],
|
||||
): { start: number; end: number }[] {
|
||||
const returnVal: { start: number; end: number }[] = [ranges];
|
||||
|
||||
negatives
|
||||
.sort((a, b) => (a.start < b.start ? -1 : 1))
|
||||
.forEach(({ start, end }) => {
|
||||
const last = returnVal.pop()!;
|
||||
|
||||
if (start < last.start || end > last.end) {
|
||||
console.error('invalid ranges');
|
||||
return;
|
||||
}
|
||||
|
||||
if (start === last.start && end === last.end) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (start === last.start && end < last.end) {
|
||||
returnVal.push({ start: end, end: last.end });
|
||||
return;
|
||||
}
|
||||
|
||||
if (start > last.start && end < last.end) {
|
||||
returnVal.push({ start: last.start, end: start });
|
||||
returnVal.push({ start: end, end: last.end });
|
||||
return;
|
||||
}
|
||||
|
||||
if (start > last.start && end === last.end) {
|
||||
returnVal.push({ start: last.start, end: start });
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
return returnVal;
|
||||
}
|
||||
|
||||
export function mapStringToRange(secrets: string[], text: string) {
|
||||
return secrets
|
||||
.map((secret: string) => {
|
||||
const byteIdx = indexOfString(text, secret);
|
||||
return byteIdx > -1
|
||||
? {
|
||||
start: byteIdx,
|
||||
end: byteIdx + bytesSize(secret),
|
||||
}
|
||||
: null;
|
||||
})
|
||||
.filter((data: any) => !!data) as { start: number; end: number }[];
|
||||
}
|
||||
|
||||
function indexOfString(str: string, substr: string): number {
|
||||
return Buffer.from(str).indexOf(Buffer.from(substr));
|
||||
}
|
||||
|
||||
function bytesSize(str: string): number {
|
||||
return Buffer.from(str).byteLength;
|
||||
}
|
||||
@@ -3,15 +3,6 @@ export type CommitData = {
|
||||
end: number;
|
||||
};
|
||||
|
||||
export type ParsedTranscriptData = {
|
||||
all: CommitData;
|
||||
info: CommitData;
|
||||
headers: { [key: string]: CommitData };
|
||||
body?: CommitData;
|
||||
json?: { [path: string]: CommitData };
|
||||
lineBreaks: CommitData[];
|
||||
};
|
||||
|
||||
export type PresentationJSON = {
|
||||
version: '0.1.0-alpha.7';
|
||||
data: string;
|
||||
|
||||
396
src/utils.ts
396
src/utils.ts
@@ -1,401 +1,5 @@
|
||||
import { ParsedTranscriptData } from './types';
|
||||
import { Buffer } from 'buffer';
|
||||
|
||||
type Stack =
|
||||
| {
|
||||
type: 'object';
|
||||
symbol: string;
|
||||
range: [number, number];
|
||||
id: number;
|
||||
}
|
||||
| {
|
||||
type: 'array';
|
||||
symbol: string;
|
||||
range: [number, number];
|
||||
data: string;
|
||||
id: number;
|
||||
}
|
||||
| {
|
||||
type: 'object_key';
|
||||
symbol: string;
|
||||
range: [number, number];
|
||||
data: string;
|
||||
path: string;
|
||||
id: number;
|
||||
objectId: number;
|
||||
}
|
||||
| {
|
||||
type: 'object_value';
|
||||
symbol: string;
|
||||
range: [number, number];
|
||||
data: string;
|
||||
id: number;
|
||||
keyId: number;
|
||||
objectId: number;
|
||||
}
|
||||
| {
|
||||
type: 'object_value_string';
|
||||
symbol: string;
|
||||
range: [number, number];
|
||||
data: string;
|
||||
path: string;
|
||||
id: number;
|
||||
objectId: number;
|
||||
valueId: number;
|
||||
}
|
||||
| {
|
||||
type: 'object_value_number';
|
||||
symbol: string;
|
||||
range: [number, number];
|
||||
data: string;
|
||||
path: string;
|
||||
id: number;
|
||||
objectId: number;
|
||||
valueId: number;
|
||||
};
|
||||
|
||||
type Commitment = {
|
||||
name?: string;
|
||||
path?: string;
|
||||
start: number;
|
||||
end: number;
|
||||
};
|
||||
|
||||
export function processJSON(str: string): Commitment[] {
|
||||
const json = JSON.parse(str);
|
||||
expect(typeof json === 'object', 'json string must be an object');
|
||||
|
||||
const stack: Stack[] = [];
|
||||
const values: Stack[] = [];
|
||||
const keys: string[] = [];
|
||||
let nonce = 0,
|
||||
keyId = 0;
|
||||
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
const char = str.charAt(i);
|
||||
let last = stack[stack.length - 1];
|
||||
|
||||
if (last?.type === 'object_key') {
|
||||
if (char === '"') {
|
||||
last.range[1] = i;
|
||||
const key = stack.pop();
|
||||
expect(key!.type === 'object_key');
|
||||
if (key?.type === 'object_key') {
|
||||
keys.push(key.data);
|
||||
key.path = keys.join('.');
|
||||
values.push(key);
|
||||
keyId = last.id;
|
||||
}
|
||||
} else {
|
||||
last.data = last.data + char;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (last?.type === 'object_value_string') {
|
||||
if (char === '"') {
|
||||
last.range[1] = i;
|
||||
values.push(stack.pop()!);
|
||||
const objectValue = stack.pop();
|
||||
expect(
|
||||
objectValue?.type === 'object_value',
|
||||
'expect stack to be object_value',
|
||||
);
|
||||
objectValue!.range[1] = i;
|
||||
values.push(objectValue!);
|
||||
keys.pop();
|
||||
} else {
|
||||
last.data = last.data + char;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (last?.type === 'array') {
|
||||
if (char === ']') {
|
||||
last.range[1] = i;
|
||||
values.push(stack.pop()!);
|
||||
} else if (char === '[') {
|
||||
stack.push({
|
||||
symbol: '[',
|
||||
type: 'array',
|
||||
id: nonce++,
|
||||
range: [i, -1],
|
||||
data: '',
|
||||
});
|
||||
} else {
|
||||
last.data = last.data + char;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (last?.type === 'object_value_number') {
|
||||
if (char === ',' || char === '}') {
|
||||
last.range[1] = i - 1;
|
||||
values.push(stack.pop()!);
|
||||
const objectValue = stack.pop();
|
||||
expect(
|
||||
objectValue?.type === 'object_value',
|
||||
'expect stack to be object_value',
|
||||
);
|
||||
objectValue!.range[1] = i - 1;
|
||||
values.push(objectValue!);
|
||||
last = stack[stack.length - 1];
|
||||
} else {
|
||||
last.data = last.data + char;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (last?.type === 'object_value') {
|
||||
if (char === '}') {
|
||||
last.range[1] = i - 1;
|
||||
values.push(stack.pop()!);
|
||||
const object = stack.pop();
|
||||
expect(object?.type === 'object_value', 'expect stack to be object');
|
||||
object!.range[1] = i;
|
||||
values.push(object!);
|
||||
keys.pop();
|
||||
} else if (char === ',') {
|
||||
last.range[1] = i - 1;
|
||||
values.push(stack.pop()!);
|
||||
keys.pop();
|
||||
} else if (char === '{') {
|
||||
stack.push({
|
||||
symbol: '{',
|
||||
type: 'object',
|
||||
id: nonce++,
|
||||
range: [i, -1],
|
||||
});
|
||||
} else if (char === '[') {
|
||||
stack.push({
|
||||
symbol: '[',
|
||||
type: 'array',
|
||||
id: nonce++,
|
||||
range: [i, -1],
|
||||
data: '',
|
||||
});
|
||||
} else if (char === '"') {
|
||||
stack.push({
|
||||
symbol: '"',
|
||||
type: 'object_value_string',
|
||||
objectId: last.objectId,
|
||||
valueId: last.id,
|
||||
id: nonce++,
|
||||
data: '',
|
||||
range: [i, -1],
|
||||
path: '',
|
||||
});
|
||||
} else if (/^\d$/.test(char)) {
|
||||
stack.push({
|
||||
symbol: '"',
|
||||
type: 'object_value_number',
|
||||
objectId: last.objectId,
|
||||
valueId: last.id,
|
||||
id: nonce++,
|
||||
data: '',
|
||||
range: [i, -1],
|
||||
path: '',
|
||||
});
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (last?.type === 'object') {
|
||||
switch (char) {
|
||||
case '}':
|
||||
last.range[1] = i;
|
||||
values.push(stack.pop()!);
|
||||
continue;
|
||||
case '"':
|
||||
stack.push({
|
||||
symbol: '"',
|
||||
type: 'object_key',
|
||||
objectId: last.id,
|
||||
id: nonce++,
|
||||
data: '',
|
||||
range: [i, -1],
|
||||
path: '',
|
||||
});
|
||||
continue;
|
||||
case ':':
|
||||
stack.push({
|
||||
symbol: ':',
|
||||
type: 'object_value',
|
||||
objectId: last.id,
|
||||
keyId: keyId,
|
||||
id: nonce++,
|
||||
range: [i, -1],
|
||||
data: '',
|
||||
});
|
||||
continue;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
switch (char) {
|
||||
case '{':
|
||||
stack.push({
|
||||
symbol: '{',
|
||||
type: 'object',
|
||||
id: nonce++,
|
||||
range: [i, -1],
|
||||
});
|
||||
break;
|
||||
case '[':
|
||||
stack.push({
|
||||
symbol: '[',
|
||||
type: 'array',
|
||||
id: nonce++,
|
||||
range: [i, -1],
|
||||
data: '',
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
expect(!stack.length, 'invalid stack length');
|
||||
|
||||
const commitments: {
|
||||
[key: string]: Commitment;
|
||||
} = {};
|
||||
|
||||
for (const value of values) {
|
||||
if (value.type === 'object_key') {
|
||||
commitments[value.id] = {
|
||||
...(commitments[value.id] || {}),
|
||||
path: value.path,
|
||||
start: value.range[0],
|
||||
};
|
||||
} else if (value.type === 'object_value') {
|
||||
commitments[value.keyId] = {
|
||||
...(commitments[value.keyId] || {}),
|
||||
end: value.range[1] + 1,
|
||||
};
|
||||
} else if (value.type === 'object') {
|
||||
commitments[value.id] = {
|
||||
start: value.range[0],
|
||||
end: value.range[1] + 1,
|
||||
};
|
||||
} else if (value.type === 'array') {
|
||||
commitments[value.id] = {
|
||||
start: value.range[0],
|
||||
end: value.range[1] + 1,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return Object.values(commitments).map(({ path, start, end }) => ({
|
||||
path,
|
||||
start,
|
||||
end,
|
||||
}));
|
||||
}
|
||||
|
||||
export function processTranscript(transcript: string): ParsedTranscriptData {
|
||||
// const commitments: Commitment[] = [];
|
||||
const returnVal: ParsedTranscriptData = {
|
||||
all: {
|
||||
start: 0,
|
||||
end: transcript.length,
|
||||
},
|
||||
info: {
|
||||
start: 0,
|
||||
end: transcript.indexOf('\n') + 1,
|
||||
},
|
||||
headers: {},
|
||||
lineBreaks: [],
|
||||
};
|
||||
|
||||
let text = '',
|
||||
ptr = -1,
|
||||
lineIndex = 0,
|
||||
isBody = false;
|
||||
|
||||
for (let i = 0; i < transcript.length; i++) {
|
||||
const char = transcript.charAt(i);
|
||||
|
||||
if (char === '\r') {
|
||||
_processEOL(text, i, lineIndex++);
|
||||
returnVal.lineBreaks.push({
|
||||
start: i,
|
||||
end: i + 1,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
if (char === '\n') {
|
||||
text = '';
|
||||
ptr = -1;
|
||||
returnVal.lineBreaks.push({
|
||||
start: i,
|
||||
end: i + 1,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ptr === -1) {
|
||||
ptr = i;
|
||||
}
|
||||
|
||||
text = text + char;
|
||||
}
|
||||
|
||||
_processEOL(text, transcript.length - 1, lineIndex++);
|
||||
|
||||
return returnVal;
|
||||
|
||||
function _processEOL(txt: string, index: number, lineIndex: number) {
|
||||
try {
|
||||
if (!txt) return;
|
||||
if (!isNaN(Number(txt))) {
|
||||
isBody = true;
|
||||
return;
|
||||
}
|
||||
|
||||
const json = JSON.parse(txt);
|
||||
|
||||
returnVal.body = {
|
||||
start: ptr,
|
||||
end: index,
|
||||
};
|
||||
|
||||
if (typeof json === 'object') {
|
||||
const jsonCommits = processJSON(txt);
|
||||
jsonCommits.forEach((commit) => {
|
||||
if (commit.path) {
|
||||
returnVal.json = returnVal.json || {};
|
||||
returnVal.json[commit.path] = {
|
||||
start: commit.start + ptr,
|
||||
end: commit.end + ptr,
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
const [name, value] = txt.split(': ');
|
||||
if (lineIndex === 0) {
|
||||
returnVal.info = {
|
||||
start: ptr,
|
||||
end: index,
|
||||
};
|
||||
} else if (!isBody && value) {
|
||||
returnVal.headers = returnVal.headers || {};
|
||||
returnVal.headers[name.toLowerCase()] = {
|
||||
start: ptr,
|
||||
end: index,
|
||||
};
|
||||
} else if (isBody) {
|
||||
returnVal.body = {
|
||||
// value: txt,
|
||||
start: ptr,
|
||||
end: index,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function expect(cond: any, msg = 'invalid expression') {
|
||||
if (!cond) throw new Error(msg);
|
||||
}
|
||||
|
||||
147
test/e2e/full-integration-swapi.spec.ts
Normal file
147
test/e2e/full-integration-swapi.spec.ts
Normal file
@@ -0,0 +1,147 @@
|
||||
import {
|
||||
Prover as _Prover,
|
||||
NotaryServer,
|
||||
Presentation as _Presentation,
|
||||
Commit,
|
||||
mapStringToRange,
|
||||
subtractRanges,
|
||||
Transcript,
|
||||
} from '../../src/lib';
|
||||
import * as Comlink from 'comlink';
|
||||
import { assert } from '../utils';
|
||||
import { HTTPParser } from 'http-parser-js';
|
||||
|
||||
const { init, Prover, Presentation }: any = Comlink.wrap(
|
||||
// @ts-ignore
|
||||
new Worker(new URL('../worker.ts', import.meta.url)),
|
||||
);
|
||||
|
||||
(async function () {
|
||||
try {
|
||||
await init({ loggingLevel: 'Debug' });
|
||||
// @ts-ignore
|
||||
console.log('test start');
|
||||
console.time('prove');
|
||||
const prover = (await new Prover({
|
||||
id: 'test',
|
||||
serverDns: 'swapi.dev',
|
||||
})) as _Prover;
|
||||
const notary = NotaryServer.from('http://localhost:7047');
|
||||
await prover.setup(await notary.sessionUrl());
|
||||
await prover.sendRequest('wss://notary.pse.dev/proxy?token=swapi.dev', {
|
||||
url: 'https://swapi.dev/api/people/1',
|
||||
headers: {
|
||||
'content-type': 'application/json',
|
||||
secret: 'test_secret',
|
||||
},
|
||||
});
|
||||
const transcript = await prover.transcript();
|
||||
const { sent, recv } = transcript;
|
||||
const {
|
||||
info: recvInfo,
|
||||
headers: recvHeaders,
|
||||
body: recvBody,
|
||||
} = parseHttpMessage(Buffer.from(recv), 'response');
|
||||
|
||||
const body = JSON.parse(recvBody[0].toString());
|
||||
|
||||
const commit: Commit = {
|
||||
sent: subtractRanges(
|
||||
{ start: 0, end: sent.length },
|
||||
mapStringToRange(
|
||||
['secret: test_secret'],
|
||||
Buffer.from(sent).toString('utf-8'),
|
||||
),
|
||||
),
|
||||
recv: [
|
||||
...mapStringToRange(
|
||||
[
|
||||
recvInfo,
|
||||
`${recvHeaders[4]}: ${recvHeaders[5]}\r\n`,
|
||||
`${recvHeaders[6]}: ${recvHeaders[7]}\r\n`,
|
||||
`${recvHeaders[8]}: ${recvHeaders[9]}\r\n`,
|
||||
`${recvHeaders[10]}: ${recvHeaders[11]}\r\n`,
|
||||
`${recvHeaders[12]}: ${recvHeaders[13]}`,
|
||||
`${recvHeaders[14]}: ${recvHeaders[15]}`,
|
||||
`${recvHeaders[16]}: ${recvHeaders[17]}`,
|
||||
`${recvHeaders[18]}: ${recvHeaders[19]}`,
|
||||
`"name":"${body.name}"`,
|
||||
`"hair_color":"${body.hair_color}"`,
|
||||
`"skin_color":"${body.skin_color}"`,
|
||||
],
|
||||
Buffer.from(recv).toString('utf-8'),
|
||||
),
|
||||
],
|
||||
};
|
||||
console.log(commit);
|
||||
const notarizationOutput = await prover.notarize(commit);
|
||||
const presentation = (await new Presentation({
|
||||
attestationHex: notarizationOutput.attestation,
|
||||
secretsHex: notarizationOutput.secrets,
|
||||
reveal: commit,
|
||||
})) as _Presentation;
|
||||
console.log('presentation:', await presentation.serialize());
|
||||
console.timeEnd('prove');
|
||||
|
||||
console.time('verify');
|
||||
const { transcript: partialTranscript, server_name } =
|
||||
await presentation.verify();
|
||||
const verifyingKey = await presentation.verifyingKey();
|
||||
console.timeEnd('verify');
|
||||
|
||||
console.log('verifyingKey', verifyingKey);
|
||||
const t = new Transcript({
|
||||
sent: partialTranscript.sent,
|
||||
recv: partialTranscript.recv,
|
||||
});
|
||||
const sentStr = t.sent();
|
||||
const recvStr = t.recv();
|
||||
assert(sentStr.includes('host: swapi.dev'));
|
||||
assert(!sentStr.includes('secret: test_secret'));
|
||||
assert(recvStr.includes('"name":"Luke Skywalker"'));
|
||||
assert(recvStr.includes('"hair_color":"blond"'));
|
||||
assert(recvStr.includes('"skin_color":"fair"'));
|
||||
assert(server_name === 'swapi.dev');
|
||||
|
||||
// @ts-ignore
|
||||
document.getElementById('full-integration-swapi').textContent = 'OK';
|
||||
} catch (err) {
|
||||
console.log('caught error from wasm');
|
||||
console.error(err);
|
||||
|
||||
// @ts-ignore
|
||||
document.getElementById('full-integration-swapi').textContent = err.message;
|
||||
}
|
||||
})();
|
||||
|
||||
function parseHttpMessage(buffer: Buffer, type: 'request' | 'response') {
|
||||
const parser = new HTTPParser(
|
||||
type === 'request' ? HTTPParser.REQUEST : HTTPParser.RESPONSE,
|
||||
);
|
||||
const body: Buffer[] = [];
|
||||
let complete = false;
|
||||
let headers: string[] = [];
|
||||
|
||||
parser.onBody = (t) => {
|
||||
body.push(t);
|
||||
};
|
||||
|
||||
parser.onHeadersComplete = (res) => {
|
||||
headers = res.headers;
|
||||
};
|
||||
|
||||
parser.onMessageComplete = () => {
|
||||
complete = true;
|
||||
};
|
||||
|
||||
parser.execute(buffer);
|
||||
parser.finish();
|
||||
|
||||
if (!complete) throw new Error(`Could not parse ${type.toUpperCase()}`);
|
||||
|
||||
return {
|
||||
info: buffer.toString('utf-8').split('\r\n')[0] + '\r\n',
|
||||
headers,
|
||||
body,
|
||||
};
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
import {
|
||||
Prover as _Prover,
|
||||
NotaryServer,
|
||||
Presentation as _Presentation,
|
||||
} from '../../src/lib';
|
||||
import * as Comlink from 'comlink';
|
||||
import { Transcript } from '../../src/lib';
|
||||
import { assert } from '../utils';
|
||||
|
||||
const { init, Prover, Presentation }: any = Comlink.wrap(
|
||||
// @ts-ignore
|
||||
new Worker(new URL('../worker.ts', import.meta.url)),
|
||||
);
|
||||
|
||||
(async function () {
|
||||
try {
|
||||
await init({ loggingLevel: 'Debug' });
|
||||
// @ts-ignore
|
||||
console.log('test start');
|
||||
console.time('prove');
|
||||
const prover = (await new Prover({
|
||||
id: 'test',
|
||||
serverDns: 'swapi.dev',
|
||||
})) as _Prover;
|
||||
const notary = NotaryServer.from('http://localhost:7047');
|
||||
await prover.setup(await notary.sessionUrl());
|
||||
await prover.sendRequest('wss://notary.pse.dev/proxy?token=swapi.dev', {
|
||||
url: 'https://swapi.dev/api/people/1',
|
||||
headers: {
|
||||
'content-type': 'application/json',
|
||||
secret: 'test_secret',
|
||||
},
|
||||
});
|
||||
const transcript = await prover.transcript();
|
||||
console.log({ transcript });
|
||||
const commit = {
|
||||
sent: [
|
||||
...Object.entries(transcript.ranges.sent.headers)
|
||||
.filter(([k]) => k !== 'secret')
|
||||
.map(([, v]) => v),
|
||||
transcript.ranges.sent.info,
|
||||
...transcript.ranges.sent.lineBreaks,
|
||||
],
|
||||
recv: [
|
||||
...Object.entries(transcript.ranges.recv.headers).map(([, v]) => v),
|
||||
transcript.ranges.recv.info,
|
||||
...transcript.ranges.recv.lineBreaks,
|
||||
transcript.ranges.recv.json!['name'],
|
||||
transcript.ranges.recv.json!['hair_color'],
|
||||
transcript.ranges.recv.json!['skin_color'],
|
||||
],
|
||||
};
|
||||
console.log(commit);
|
||||
const notarizationOutput = await prover.notarize(commit);
|
||||
const presentation = (await new Presentation({
|
||||
attestationHex: notarizationOutput.attestation,
|
||||
secretsHex: notarizationOutput.secrets,
|
||||
reveal: commit,
|
||||
})) as _Presentation;
|
||||
console.log('presentation:', await presentation.serialize());
|
||||
console.timeEnd('prove');
|
||||
|
||||
console.time('verify');
|
||||
const { transcript: partialTranscript, server_name } =
|
||||
await presentation.verify();
|
||||
const verifyingKey = await presentation.verifyingKey();
|
||||
console.timeEnd('verify');
|
||||
|
||||
console.log('verifyingKey', verifyingKey);
|
||||
const t = new Transcript({
|
||||
sent: partialTranscript.sent,
|
||||
recv: partialTranscript.recv,
|
||||
});
|
||||
const sent = t.sent();
|
||||
const recv = t.recv();
|
||||
assert(sent.includes('host: swapi.dev'));
|
||||
assert(!sent.includes('secret: test_secret'));
|
||||
assert(recv.includes('"name":"Luke Skywalker"'));
|
||||
assert(recv.includes('"hair_color":"blond"'));
|
||||
assert(recv.includes('"skin_color":"fair"'));
|
||||
assert(server_name === 'swapi.dev');
|
||||
|
||||
// @ts-ignore
|
||||
document.getElementById('full-integration-swapi').textContent = 'OK';
|
||||
} catch (err) {
|
||||
console.log('caught error from wasm');
|
||||
console.error(err);
|
||||
|
||||
// @ts-ignore
|
||||
document.getElementById('full-integration-swapi').textContent = err.message;
|
||||
}
|
||||
})();
|
||||
90
test/specs/transcript.ts
Normal file
90
test/specs/transcript.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
import { describe, it } from 'mocha';
|
||||
import * as assert from 'assert';
|
||||
import { Transcript } from '../../src/transcript';
|
||||
|
||||
describe('Transcript parsing', () => {
|
||||
it('should parse transcript correctly', async () => {
|
||||
const transcript = new Transcript({ sent: swapiSent, recv: swapiRecv });
|
||||
assert.strictEqual(
|
||||
Buffer.from(transcript.raw.sent).toString('utf-8'),
|
||||
'GET https://swapi.dev/api/people/1 HTTP/1.1\r\nconnection: close\r\ncontent-length: 25\r\ncontent-type: application/json\r\nhost: swapi.dev\r\n\r\n{"hello":"world","one":1}',
|
||||
);
|
||||
assert.strictEqual(
|
||||
Buffer.from(transcript.raw.recv).toString('utf-8'),
|
||||
'HTTP/1.1 200 OK\r\nServer: nginx/1.16.1\r\nDate: Fri, 07 Feb 2025 07:37:11 GMT\r\nContent-Type: application/json\r\nTransfer-Encoding: chunked\r\nConnection: close\r\nVary: Accept, Cookie\r\nX-Frame-Options: SAMEORIGIN\r\nETag: \"ee398610435c328f4d0a4e1b0d2f7bbc\"\r\nAllow: GET, HEAD, OPTIONS\r\nStrict-Transport-Security: max-age=15768000\r\n\r\n287\r\n{\"name\":\"Luke Skywalker\",\"height\":\"172\",\"mass\":\"77\",\"hair_color\":\"blond\",\"skin_color\":\"fair\",\"eye_color\":\"blue\",\"birth_year\":\"19BBY\",\"gender\":\"male\",\"homeworld\":\"https://swapi.dev/api/planets/1/\",\"films\":[\"https://swapi.dev/api/films/1/\",\"https://swapi.dev/api/films/2/\",\"https://swapi.dev/api/films/3/\",\"https://swapi.dev/api/films/6/\"],\"species\":[],\"vehicles\":[\"https://swapi.dev/api/vehicles/14/\",\"https://swapi.dev/api/vehicles/30/\"],\"starships\":[\"https://swapi.dev/api/starships/12/\",\"https://swapi.dev/api/starships/22/\"],\"created\":\"2014-12-09T13:50:51.644000Z\",\"edited\":\"2014-12-20T21:17:56.891000Z\",\"url\":\"https://swapi.dev/api/people/1/\"}\r\n0\r\n\r\n',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
const swapiRecv = [
|
||||
72, 84, 84, 80, 47, 49, 46, 49, 32, 50, 48, 48, 32, 79, 75, 13, 10, 83, 101,
|
||||
114, 118, 101, 114, 58, 32, 110, 103, 105, 110, 120, 47, 49, 46, 49, 54, 46,
|
||||
49, 13, 10, 68, 97, 116, 101, 58, 32, 70, 114, 105, 44, 32, 48, 55, 32, 70,
|
||||
101, 98, 32, 50, 48, 50, 53, 32, 48, 55, 58, 51, 55, 58, 49, 49, 32, 71, 77,
|
||||
84, 13, 10, 67, 111, 110, 116, 101, 110, 116, 45, 84, 121, 112, 101, 58, 32,
|
||||
97, 112, 112, 108, 105, 99, 97, 116, 105, 111, 110, 47, 106, 115, 111, 110,
|
||||
13, 10, 84, 114, 97, 110, 115, 102, 101, 114, 45, 69, 110, 99, 111, 100, 105,
|
||||
110, 103, 58, 32, 99, 104, 117, 110, 107, 101, 100, 13, 10, 67, 111, 110, 110,
|
||||
101, 99, 116, 105, 111, 110, 58, 32, 99, 108, 111, 115, 101, 13, 10, 86, 97,
|
||||
114, 121, 58, 32, 65, 99, 99, 101, 112, 116, 44, 32, 67, 111, 111, 107, 105,
|
||||
101, 13, 10, 88, 45, 70, 114, 97, 109, 101, 45, 79, 112, 116, 105, 111, 110,
|
||||
115, 58, 32, 83, 65, 77, 69, 79, 82, 73, 71, 73, 78, 13, 10, 69, 84, 97, 103,
|
||||
58, 32, 34, 101, 101, 51, 57, 56, 54, 49, 48, 52, 51, 53, 99, 51, 50, 56, 102,
|
||||
52, 100, 48, 97, 52, 101, 49, 98, 48, 100, 50, 102, 55, 98, 98, 99, 34, 13,
|
||||
10, 65, 108, 108, 111, 119, 58, 32, 71, 69, 84, 44, 32, 72, 69, 65, 68, 44,
|
||||
32, 79, 80, 84, 73, 79, 78, 83, 13, 10, 83, 116, 114, 105, 99, 116, 45, 84,
|
||||
114, 97, 110, 115, 112, 111, 114, 116, 45, 83, 101, 99, 117, 114, 105, 116,
|
||||
121, 58, 32, 109, 97, 120, 45, 97, 103, 101, 61, 49, 53, 55, 54, 56, 48, 48,
|
||||
48, 13, 10, 13, 10, 50, 56, 55, 13, 10, 123, 34, 110, 97, 109, 101, 34, 58,
|
||||
34, 76, 117, 107, 101, 32, 83, 107, 121, 119, 97, 108, 107, 101, 114, 34, 44,
|
||||
34, 104, 101, 105, 103, 104, 116, 34, 58, 34, 49, 55, 50, 34, 44, 34, 109, 97,
|
||||
115, 115, 34, 58, 34, 55, 55, 34, 44, 34, 104, 97, 105, 114, 95, 99, 111, 108,
|
||||
111, 114, 34, 58, 34, 98, 108, 111, 110, 100, 34, 44, 34, 115, 107, 105, 110,
|
||||
95, 99, 111, 108, 111, 114, 34, 58, 34, 102, 97, 105, 114, 34, 44, 34, 101,
|
||||
121, 101, 95, 99, 111, 108, 111, 114, 34, 58, 34, 98, 108, 117, 101, 34, 44,
|
||||
34, 98, 105, 114, 116, 104, 95, 121, 101, 97, 114, 34, 58, 34, 49, 57, 66, 66,
|
||||
89, 34, 44, 34, 103, 101, 110, 100, 101, 114, 34, 58, 34, 109, 97, 108, 101,
|
||||
34, 44, 34, 104, 111, 109, 101, 119, 111, 114, 108, 100, 34, 58, 34, 104, 116,
|
||||
116, 112, 115, 58, 47, 47, 115, 119, 97, 112, 105, 46, 100, 101, 118, 47, 97,
|
||||
112, 105, 47, 112, 108, 97, 110, 101, 116, 115, 47, 49, 47, 34, 44, 34, 102,
|
||||
105, 108, 109, 115, 34, 58, 91, 34, 104, 116, 116, 112, 115, 58, 47, 47, 115,
|
||||
119, 97, 112, 105, 46, 100, 101, 118, 47, 97, 112, 105, 47, 102, 105, 108,
|
||||
109, 115, 47, 49, 47, 34, 44, 34, 104, 116, 116, 112, 115, 58, 47, 47, 115,
|
||||
119, 97, 112, 105, 46, 100, 101, 118, 47, 97, 112, 105, 47, 102, 105, 108,
|
||||
109, 115, 47, 50, 47, 34, 44, 34, 104, 116, 116, 112, 115, 58, 47, 47, 115,
|
||||
119, 97, 112, 105, 46, 100, 101, 118, 47, 97, 112, 105, 47, 102, 105, 108,
|
||||
109, 115, 47, 51, 47, 34, 44, 34, 104, 116, 116, 112, 115, 58, 47, 47, 115,
|
||||
119, 97, 112, 105, 46, 100, 101, 118, 47, 97, 112, 105, 47, 102, 105, 108,
|
||||
109, 115, 47, 54, 47, 34, 93, 44, 34, 115, 112, 101, 99, 105, 101, 115, 34,
|
||||
58, 91, 93, 44, 34, 118, 101, 104, 105, 99, 108, 101, 115, 34, 58, 91, 34,
|
||||
104, 116, 116, 112, 115, 58, 47, 47, 115, 119, 97, 112, 105, 46, 100, 101,
|
||||
118, 47, 97, 112, 105, 47, 118, 101, 104, 105, 99, 108, 101, 115, 47, 49, 52,
|
||||
47, 34, 44, 34, 104, 116, 116, 112, 115, 58, 47, 47, 115, 119, 97, 112, 105,
|
||||
46, 100, 101, 118, 47, 97, 112, 105, 47, 118, 101, 104, 105, 99, 108, 101,
|
||||
115, 47, 51, 48, 47, 34, 93, 44, 34, 115, 116, 97, 114, 115, 104, 105, 112,
|
||||
115, 34, 58, 91, 34, 104, 116, 116, 112, 115, 58, 47, 47, 115, 119, 97, 112,
|
||||
105, 46, 100, 101, 118, 47, 97, 112, 105, 47, 115, 116, 97, 114, 115, 104,
|
||||
105, 112, 115, 47, 49, 50, 47, 34, 44, 34, 104, 116, 116, 112, 115, 58, 47,
|
||||
47, 115, 119, 97, 112, 105, 46, 100, 101, 118, 47, 97, 112, 105, 47, 115, 116,
|
||||
97, 114, 115, 104, 105, 112, 115, 47, 50, 50, 47, 34, 93, 44, 34, 99, 114,
|
||||
101, 97, 116, 101, 100, 34, 58, 34, 50, 48, 49, 52, 45, 49, 50, 45, 48, 57,
|
||||
84, 49, 51, 58, 53, 48, 58, 53, 49, 46, 54, 52, 52, 48, 48, 48, 90, 34, 44,
|
||||
34, 101, 100, 105, 116, 101, 100, 34, 58, 34, 50, 48, 49, 52, 45, 49, 50, 45,
|
||||
50, 48, 84, 50, 49, 58, 49, 55, 58, 53, 54, 46, 56, 57, 49, 48, 48, 48, 90,
|
||||
34, 44, 34, 117, 114, 108, 34, 58, 34, 104, 116, 116, 112, 115, 58, 47, 47,
|
||||
115, 119, 97, 112, 105, 46, 100, 101, 118, 47, 97, 112, 105, 47, 112, 101,
|
||||
111, 112, 108, 101, 47, 49, 47, 34, 125, 13, 10, 48, 13, 10, 13, 10,
|
||||
];
|
||||
|
||||
const swapiSent = [
|
||||
71, 69, 84, 32, 104, 116, 116, 112, 115, 58, 47, 47, 115, 119, 97, 112, 105,
|
||||
46, 100, 101, 118, 47, 97, 112, 105, 47, 112, 101, 111, 112, 108, 101, 47, 49,
|
||||
32, 72, 84, 84, 80, 47, 49, 46, 49, 13, 10, 99, 111, 110, 110, 101, 99, 116,
|
||||
105, 111, 110, 58, 32, 99, 108, 111, 115, 101, 13, 10, 99, 111, 110, 116, 101,
|
||||
110, 116, 45, 108, 101, 110, 103, 116, 104, 58, 32, 50, 53, 13, 10, 99, 111,
|
||||
110, 116, 101, 110, 116, 45, 116, 121, 112, 101, 58, 32, 97, 112, 112, 108,
|
||||
105, 99, 97, 116, 105, 111, 110, 47, 106, 115, 111, 110, 13, 10, 104, 111,
|
||||
115, 116, 58, 32, 115, 119, 97, 112, 105, 46, 100, 101, 118, 13, 10, 13, 10,
|
||||
123, 34, 104, 101, 108, 108, 111, 34, 58, 34, 119, 111, 114, 108, 100, 34, 44,
|
||||
34, 111, 110, 101, 34, 58, 49, 125,
|
||||
];
|
||||
@@ -145,7 +145,7 @@ after(async function () {
|
||||
});
|
||||
|
||||
describe('tlsn-js test suite', function () {
|
||||
fs.readdirSync(path.join(__dirname, 'specs')).forEach((file) => {
|
||||
fs.readdirSync(path.join(__dirname, 'e2e')).forEach((file) => {
|
||||
const [id] = file.split('.');
|
||||
it(`Test ID: ${id}`, async function () {
|
||||
const content = await check(id);
|
||||
|
||||
@@ -37,8 +37,8 @@ module.exports = [
|
||||
target: 'web',
|
||||
mode: isProd ? 'production' : 'development',
|
||||
entry: {
|
||||
'full-integration-swapi.spec': path.join(__dirname, 'test', 'specs', 'full-integration-swapi.spec.ts'),
|
||||
'simple-verify': path.join(__dirname, 'test', 'specs', 'simple-verify.spec.ts'),
|
||||
'full-integration-swapi.spec': path.join(__dirname, 'test', 'e2e', 'full-integration-swapi.spec.ts'),
|
||||
'simple-verify': path.join(__dirname, 'test', 'e2e', 'simple-verify.spec.ts'),
|
||||
},
|
||||
output: {
|
||||
path: __dirname + '/test-build',
|
||||
|
||||
Reference in New Issue
Block a user