mirror of
https://github.com/tlsnotary/tlsn-js.git
synced 2026-01-08 06:34:09 -05:00
feat: upgrade to alpha.6 and new tlsn-wasm prover (#61)
* Revert "chore: release v0.1.0-alpha.6 (#57)"
This reverts commit 292b4263d7.
* parsed json
* add json commitments
* parse json from transcript
* wip
* wip
* wip
* feat: update to alpha.6
* chore: commit wasm pkg directory
* chore: version nump
* fix: test suite for alpha.6
* fix: remove wasm build from ci
* fix: update pnpm lockfile
* fix: remove test:wasm
* fix: linter and add new devDependency
This commit is contained in:
6
.github/workflows/build.yaml
vendored
6
.github/workflows/build.yaml
vendored
@@ -53,18 +53,12 @@ jobs:
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-store-
|
||||
|
||||
- name: Install wasm-pack
|
||||
run: npm install -g wasm-pack
|
||||
|
||||
- name: Install nightly tool-chain
|
||||
run: rustup component add rust-src --toolchain nightly-x86_64-unknown-linux-gnu
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Build WASM
|
||||
run: npm run build:wasm
|
||||
|
||||
- name: Build
|
||||
run: npm run build
|
||||
|
||||
|
||||
9
.github/workflows/test.yaml
vendored
9
.github/workflows/test.yaml
vendored
@@ -65,21 +65,12 @@ jobs:
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-store-
|
||||
|
||||
- name: Install wasm-pack
|
||||
run: npm install -g wasm-pack
|
||||
|
||||
- name: Install nightly tool-chain
|
||||
run: rustup component add rust-src --toolchain nightly-x86_64-unknown-linux-gnu
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Build WASM
|
||||
run: npm run build:wasm
|
||||
|
||||
- name: Test WASM
|
||||
run: npm run test:wasm
|
||||
|
||||
- name: Build Test dependencies
|
||||
run: npm run build:tlsn-binaries
|
||||
|
||||
|
||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -1,6 +1,5 @@
|
||||
wasm/prover/pkg
|
||||
wasm/prover/target
|
||||
wasm/prover/Cargo.lock
|
||||
wasm-pack.log
|
||||
node_modules/
|
||||
.idea/
|
||||
@@ -9,7 +8,4 @@ build/
|
||||
dev-build/
|
||||
test-build/
|
||||
./demo/node_modules
|
||||
./demo/package-lock.json
|
||||
package-lock.json
|
||||
pnpm-lock.yaml
|
||||
yarn.lock
|
||||
utils/tlsn
|
||||
|
||||
@@ -1,89 +0,0 @@
|
||||
import React, { ReactElement, useCallback, useEffect, useState } from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import { prove, verify, set_logging_filter } from 'tlsn-js';
|
||||
import { Proof } from 'tlsn-js/build/types';
|
||||
import { Watch } from 'react-loader-spinner';
|
||||
|
||||
const container = document.getElementById('root');
|
||||
const root = createRoot(container!);
|
||||
|
||||
root.render(<App />);
|
||||
|
||||
function App(): ReactElement {
|
||||
const [processing, setProcessing] = useState(false);
|
||||
const [result, setResult] = useState<{
|
||||
time: number;
|
||||
sent: string;
|
||||
recv: string;
|
||||
notaryUrl: string;
|
||||
} | null>(null);
|
||||
const [proof, setProof] = useState<Proof | null>(null);
|
||||
|
||||
const onClick = useCallback(async () => {
|
||||
setProcessing(true);
|
||||
await set_logging_filter('info,tlsn_extension_rs=debug');
|
||||
const p = await prove('https://swapi.dev/api/people/1', {
|
||||
method: 'GET',
|
||||
maxTranscriptSize: 16384,
|
||||
notaryUrl: 'http://localhost:7047',
|
||||
websocketProxyUrl: 'ws://localhost:55688',
|
||||
});
|
||||
setProof(p);
|
||||
}, [setProof, setProcessing]);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
if (proof) {
|
||||
const r = await verify(proof);
|
||||
setResult(r);
|
||||
setProcessing(false);
|
||||
}
|
||||
})();
|
||||
}, [proof, setResult]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<button onClick={!processing ? onClick : undefined} disabled={processing}>
|
||||
Start demo
|
||||
</button>
|
||||
<div>
|
||||
<b>Proof: </b>
|
||||
{!processing && !proof ? (
|
||||
<i>not started</i>
|
||||
) : !proof ? (
|
||||
<>
|
||||
Proving data from swapi...
|
||||
<Watch
|
||||
visible={true}
|
||||
height="40"
|
||||
width="40"
|
||||
radius="48"
|
||||
color="#000000"
|
||||
ariaLabel="watch-loading"
|
||||
wrapperStyle={{}}
|
||||
wrapperClass=""
|
||||
/>
|
||||
Open <i>Developer tools</i> to follow progress
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<details>
|
||||
<summary>View Proof</summary>
|
||||
<pre>{JSON.stringify(proof, null, 2)}</pre>
|
||||
</details>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<b>Verification: </b>
|
||||
{!proof ? (
|
||||
<i>not started</i>
|
||||
) : !result ? (
|
||||
<i>verifying</i>
|
||||
) : (
|
||||
<pre>{JSON.stringify(result, null, 2)}</pre>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -4,11 +4,13 @@
|
||||
"description": "",
|
||||
"main": "webpack.js",
|
||||
"scripts": {
|
||||
"dev": "webpack-dev-server --config webpack.js"
|
||||
"dev": "webpack-dev-server --config webpack.js",
|
||||
"build": "webpack --config webpack.js"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"comlink": "^4.4.1",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-loader-spinner": "^6.1.6",
|
||||
@@ -19,12 +21,15 @@
|
||||
"@types/react-dom": "^18.0.10",
|
||||
"babel-loader": "^9.1.3",
|
||||
"copy-webpack-plugin": "^11.0.0",
|
||||
"crypto-browserify": "^3.12.0",
|
||||
"html-webpack-plugin": "^5.5.0",
|
||||
"source-map-loader": "^5.0.0",
|
||||
"stream-browserify": "^3.0.0",
|
||||
"ts-loader": "^9.4.2",
|
||||
"typescript": "^4.9.4",
|
||||
"vm-browserify": "^1.1.2",
|
||||
"webpack": "^5.75.0",
|
||||
"webpack-cli": "^4.10.0",
|
||||
"webpack-dev-server": "^4.11.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
3428
demo/react-ts-webpack/pnpm-lock.yaml
generated
Normal file
3428
demo/react-ts-webpack/pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
150
demo/react-ts-webpack/src/app.tsx
Normal file
150
demo/react-ts-webpack/src/app.tsx
Normal file
@@ -0,0 +1,150 @@
|
||||
import React, { ReactElement, useCallback, useEffect, useState } from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import * as Comlink from 'comlink';
|
||||
import { Watch } from 'react-loader-spinner';
|
||||
import {
|
||||
Prover as TProver,
|
||||
NotarizedSession as TNotarizedSession,
|
||||
TlsProof as TTlsProof,
|
||||
Commit,
|
||||
NotaryServer,
|
||||
ProofData,
|
||||
} from 'tlsn-js';
|
||||
|
||||
const { init, Prover, NotarizedSession, TlsProof }: any = Comlink.wrap(
|
||||
new Worker(new URL('./worker.ts', import.meta.url)),
|
||||
);
|
||||
|
||||
const container = document.getElementById('root');
|
||||
const root = createRoot(container!);
|
||||
|
||||
root.render(<App />);
|
||||
|
||||
function App(): ReactElement {
|
||||
const [processing, setProcessing] = useState(false);
|
||||
const [result, setResult] = useState<ProofData | null>(null);
|
||||
const [proofHex, setProofHex] = useState<null | string>(null);
|
||||
|
||||
const onClick = useCallback(async () => {
|
||||
setProcessing(true);
|
||||
const notary = NotaryServer.from(`http://localhost:7047`);
|
||||
console.time('submit');
|
||||
await init({ loggingLevel: 'Debug' });
|
||||
const prover = (await new Prover({
|
||||
serverDns: 'swapi.dev',
|
||||
})) as TProver;
|
||||
|
||||
await prover.setup(await notary.sessionUrl());
|
||||
const resp = await prover.sendRequest('ws://localhost:55688', {
|
||||
url: 'https://swapi.dev/api/people/1',
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: {
|
||||
hello: 'world',
|
||||
one: 1,
|
||||
},
|
||||
});
|
||||
|
||||
console.timeEnd('submit');
|
||||
console.log(resp);
|
||||
|
||||
console.time('transcript');
|
||||
const transcript = await prover.transcript();
|
||||
console.log(transcript);
|
||||
console.timeEnd('transcript');
|
||||
console.time('commit');
|
||||
const commit: Commit = {
|
||||
sent: [
|
||||
transcript.ranges.sent.info!,
|
||||
transcript.ranges.sent.headers!['content-type'],
|
||||
transcript.ranges.sent.headers!['host'],
|
||||
...transcript.ranges.sent.lineBreaks,
|
||||
],
|
||||
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,
|
||||
],
|
||||
};
|
||||
console.log(commit);
|
||||
const session = await prover.notarize(commit);
|
||||
console.timeEnd('commit');
|
||||
console.time('proof');
|
||||
|
||||
const notarizedSession = (await new NotarizedSession(
|
||||
session,
|
||||
)) as TNotarizedSession;
|
||||
|
||||
const proofHex = await notarizedSession.proof(commit);
|
||||
|
||||
console.timeEnd('proof');
|
||||
setProofHex(proofHex);
|
||||
}, [setProofHex, setProcessing]);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
if (proofHex) {
|
||||
const proof = (await new TlsProof(proofHex)) as TTlsProof;
|
||||
const notary = NotaryServer.from(`http://localhost:7047`);
|
||||
const notaryKey = await notary.publicKey();
|
||||
const proofData = await proof.verify({
|
||||
typ: 'P256',
|
||||
key: notaryKey,
|
||||
});
|
||||
setResult(proofData);
|
||||
setProcessing(false);
|
||||
}
|
||||
})();
|
||||
}, [proofHex, setResult]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<button onClick={!processing ? onClick : undefined} disabled={processing}>
|
||||
Start demo
|
||||
</button>
|
||||
<div>
|
||||
<b>Proof: </b>
|
||||
{!processing && !proofHex ? (
|
||||
<i>not started</i>
|
||||
) : !proofHex ? (
|
||||
<>
|
||||
Proving data from swapi...
|
||||
<Watch
|
||||
visible={true}
|
||||
height="40"
|
||||
width="40"
|
||||
radius="48"
|
||||
color="#000000"
|
||||
ariaLabel="watch-loading"
|
||||
wrapperStyle={{}}
|
||||
wrapperClass=""
|
||||
/>
|
||||
Open <i>Developer tools</i> to follow progress
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<details>
|
||||
<summary>View Proof</summary>
|
||||
<pre>{JSON.stringify(proofHex, null, 2)}</pre>
|
||||
</details>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<b>Verification: </b>
|
||||
{!proofHex ? (
|
||||
<i>not started</i>
|
||||
) : !result ? (
|
||||
<i>verifying</i>
|
||||
) : (
|
||||
<pre>{JSON.stringify(result, null, 2)}</pre>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
9
demo/react-ts-webpack/src/worker.ts
Normal file
9
demo/react-ts-webpack/src/worker.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import * as Comlink from 'comlink';
|
||||
import init, { Prover, NotarizedSession, TlsProof } from 'tlsn-js';
|
||||
|
||||
Comlink.expose({
|
||||
init,
|
||||
Prover,
|
||||
NotarizedSession,
|
||||
TlsProof,
|
||||
});
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"target": "es2015",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": false,
|
||||
"skipLibCheck": true,
|
||||
@@ -15,5 +15,7 @@
|
||||
"noEmit": false,
|
||||
"jsx": "react"
|
||||
},
|
||||
"include": ["app.tsx"]
|
||||
"include": [
|
||||
"src/app.tsx"
|
||||
]
|
||||
}
|
||||
13
demo/react-ts-webpack/webpack.js
vendored
13
demo/react-ts-webpack/webpack.js
vendored
@@ -27,7 +27,7 @@ var options = {
|
||||
],
|
||||
mode: 'development',
|
||||
entry: {
|
||||
app: path.join(__dirname, 'app.tsx'),
|
||||
app: path.join(__dirname, 'src', 'app.tsx'),
|
||||
},
|
||||
output: {
|
||||
filename: '[name].bundle.js',
|
||||
@@ -51,6 +51,9 @@ var options = {
|
||||
test: /\.(ts|tsx)$/,
|
||||
exclude: /node_modules/,
|
||||
use: [
|
||||
{
|
||||
loader: 'source-map-loader',
|
||||
},
|
||||
{
|
||||
loader: require.resolve('ts-loader'),
|
||||
},
|
||||
@@ -75,6 +78,11 @@ var options = {
|
||||
extensions: fileExtensions
|
||||
.map((extension) => '.' + extension)
|
||||
.concat(['.js', '.jsx', '.ts', '.tsx', '.css']),
|
||||
fallback: {
|
||||
crypto: require.resolve('crypto-browserify'),
|
||||
stream: require.resolve('stream-browserify'),
|
||||
vm: require.resolve('vm-browserify'),
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
new CopyWebpackPlugin({
|
||||
@@ -91,6 +99,9 @@ var options = {
|
||||
filename: 'index.html',
|
||||
cache: false,
|
||||
}),
|
||||
new webpack.ProvidePlugin({
|
||||
process: 'process/browser',
|
||||
}),
|
||||
new webpack.ProvidePlugin({
|
||||
Buffer: ['buffer', 'Buffer'],
|
||||
}),
|
||||
|
||||
26760
package-lock.json
generated
Normal file
26760
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
25
package.json
25
package.json
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"name": "tlsn-js",
|
||||
"version": "v0.1.0-alpha.6.0",
|
||||
"version": "v0.1.0-alpha.6.1",
|
||||
"description": "",
|
||||
"repository": "https://github.com/tlsnotary/tlsn-js",
|
||||
"main": "build/index.js",
|
||||
"types": "build/index.d.ts",
|
||||
"main": "build/lib.js",
|
||||
"types": "build/lib.d.ts",
|
||||
"files": [
|
||||
"build/",
|
||||
"src/",
|
||||
@@ -16,30 +16,27 @@
|
||||
"serve:test": "serve --config ../serve.json ./test-build -l 3001",
|
||||
"build:src": "webpack --config webpack.build.config.js",
|
||||
"build:types": "tsc --project tsconfig.compile.json",
|
||||
"build:lib": "NODE_ENV=production concurrently npm:build:src npm:build:types",
|
||||
"build": "npm run build:wasm && npm run build:lib",
|
||||
"update:wasm": "sh utils/check-wasm.sh -f",
|
||||
"test:wasm": "cd wasm/prover; wasm-pack test --firefox --release --headless",
|
||||
"build:wasm": "wasm-pack build --target web wasm/prover",
|
||||
"build:tlsn-binaries": "sh utils/build-tlsn-binaries.sh",
|
||||
"build:lib": "NODE_ENV=production concurrently npm:build:src npm:build:types",
|
||||
"build": "npm run build:lib",
|
||||
"update:wasm": "sh utils/check-wasm.sh -f",
|
||||
"watch:dev": "webpack --config webpack.web.dev.config.js --watch",
|
||||
"predev": "sh utils/check-wasm.sh",
|
||||
"lint:wasm": "cd wasm/prover; cargo clippy --target wasm32-unknown-unknown",
|
||||
"dev": "concurrently npm:watch:dev npm:serve:test",
|
||||
"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"
|
||||
},
|
||||
"dependencies": {
|
||||
"comlink": "^4.4.1"
|
||||
"test": "npm run build:tlsn-binaries && npm run build:test && npm run run:test",
|
||||
"test:only": "npm run build:test && npm run run:test"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/expect": "^24.3.0",
|
||||
"@types/mocha": "^10.0.6",
|
||||
"@types/serve-handler": "^6.1.4",
|
||||
"browserify": "^17.0.0",
|
||||
"buffer": "^6.0.3",
|
||||
"comlink": "^4.4.1",
|
||||
"concurrently": "^5.1.0",
|
||||
"constants-browserify": "^1.0.0",
|
||||
"copy-webpack-plugin": "^5.0.5",
|
||||
@@ -75,4 +72,4 @@
|
||||
"engines": {
|
||||
"node": ">= 16.20.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
8613
pnpm-lock.yaml
generated
Normal file
8613
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -12,11 +12,11 @@ To run your own websocket proxy for `https://swapi.dev` **locally**:
|
||||
|
||||
1. Install [websocat](https://github.com/vi/websocat):
|
||||
|
||||
| tool | command |
|
||||
| ------ | ------------------------------ |
|
||||
| cargo | `cargo install websocat` |
|
||||
| brew | `brew install websocat` |
|
||||
| source | https://github.com/vi/websocat |
|
||||
| tool | command |
|
||||
|--------|-------------------------------|
|
||||
| cargo | `cargo install websocat` |
|
||||
| brew | `brew install websocat` |
|
||||
| source | https://github.com/vi/websocat|
|
||||
|
||||
2. Run a websocket proxy for `https://swapi.dev`:
|
||||
```sh
|
||||
@@ -31,7 +31,7 @@ For this demo, we also need to run a local notary server.
|
||||
|
||||
1. Clone the TLSNotary repository:
|
||||
```shell
|
||||
git clone https://github.com/tlsnotary/tlsn.git --branch "v0.1.0-alpha.6"
|
||||
git clone https://github.com/tlsnotary/tlsn.git --branch "v0.1.0-alpha.5"
|
||||
```
|
||||
2. Edit the notary server config file (`notary-server/config/config.yaml`) to turn off TLS so that the browser extension can connect to the local notary server without requiring extra steps to accept self-signed certificates in the browser.
|
||||
```yaml
|
||||
|
||||
104
src/index.ts
104
src/index.ts
@@ -1,104 +0,0 @@
|
||||
import TLSN from './tlsn';
|
||||
import { DEFAULT_LOGGING_FILTER } from './tlsn';
|
||||
import { Proof } from './types';
|
||||
|
||||
let _tlsn: TLSN;
|
||||
const current_logging_filter = DEFAULT_LOGGING_FILTER;
|
||||
|
||||
async function getTLSN(logging_filter?: string): Promise<TLSN> {
|
||||
const logging_filter_changed =
|
||||
logging_filter && logging_filter == current_logging_filter;
|
||||
|
||||
if (!logging_filter_changed && _tlsn) return _tlsn;
|
||||
// @ts-ignore
|
||||
if (logging_filter) _tlsn = await new TLSN(logging_filter);
|
||||
else _tlsn = await new TLSN();
|
||||
return _tlsn;
|
||||
}
|
||||
|
||||
/**
|
||||
* If you want to change the default logging filter, call this method before calling prove or verify
|
||||
* For the filter syntax consult: https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html#example-syntax
|
||||
* @param logging_filter
|
||||
*/
|
||||
export const set_logging_filter = async (logging_filter: string) => {
|
||||
getTLSN(logging_filter);
|
||||
};
|
||||
|
||||
export const prove = async (
|
||||
url: string,
|
||||
options: {
|
||||
notaryUrl: string;
|
||||
websocketProxyUrl: string;
|
||||
method?: string;
|
||||
headers?: { [key: string]: string };
|
||||
body?: string;
|
||||
maxSentData?: number;
|
||||
maxRecvData?: number;
|
||||
maxTranscriptSize?: number;
|
||||
secretHeaders?: string[];
|
||||
secretResps?: string[];
|
||||
},
|
||||
): Promise<Proof> => {
|
||||
const {
|
||||
method,
|
||||
headers = {},
|
||||
body = '',
|
||||
maxSentData,
|
||||
maxRecvData,
|
||||
maxTranscriptSize = 16384,
|
||||
notaryUrl,
|
||||
websocketProxyUrl,
|
||||
secretHeaders,
|
||||
secretResps,
|
||||
} = options;
|
||||
|
||||
const tlsn = await getTLSN();
|
||||
|
||||
headers['Host'] = new URL(url).host;
|
||||
headers['Connection'] = 'close';
|
||||
|
||||
const proof = await tlsn.prove(url, {
|
||||
method,
|
||||
headers,
|
||||
body,
|
||||
maxSentData,
|
||||
maxRecvData,
|
||||
maxTranscriptSize,
|
||||
notaryUrl,
|
||||
websocketProxyUrl,
|
||||
secretHeaders,
|
||||
secretResps,
|
||||
});
|
||||
|
||||
return {
|
||||
...proof,
|
||||
notaryUrl,
|
||||
};
|
||||
};
|
||||
|
||||
export const verify = async (
|
||||
proof: Proof,
|
||||
publicKeyOverride?: string,
|
||||
): Promise<{
|
||||
time: number;
|
||||
sent: string;
|
||||
recv: string;
|
||||
notaryUrl: string;
|
||||
}> => {
|
||||
const publicKey =
|
||||
publicKeyOverride || (await fetchPublicKeyFromNotary(proof.notaryUrl));
|
||||
const tlsn = await getTLSN();
|
||||
const result = await tlsn.verify(proof, publicKey);
|
||||
return {
|
||||
...result,
|
||||
notaryUrl: proof.notaryUrl,
|
||||
};
|
||||
};
|
||||
|
||||
async function fetchPublicKeyFromNotary(notaryUrl: string) {
|
||||
const res = await fetch(notaryUrl + '/info');
|
||||
const json: any = await res.json();
|
||||
if (!json.publicKey) throw new Error('invalid response');
|
||||
return json.publicKey;
|
||||
}
|
||||
303
src/lib.ts
Normal file
303
src/lib.ts
Normal file
@@ -0,0 +1,303 @@
|
||||
import initWasm, {
|
||||
initThreadPool,
|
||||
init_logging,
|
||||
LoggingLevel,
|
||||
LoggingConfig,
|
||||
NotarizedSession as WasmNotarizedSession,
|
||||
Transcript,
|
||||
TlsProof as WasmTlsProof,
|
||||
type Commit,
|
||||
type Reveal,
|
||||
Verifier as WasmVerifier,
|
||||
Prover as WasmProver,
|
||||
type ProverConfig,
|
||||
type Method,
|
||||
VerifierConfig,
|
||||
VerifierData,
|
||||
NotaryPublicKey,
|
||||
} from '../wasm/pkg/tlsn_wasm';
|
||||
import { arrayToHex, processTranscript, stringToBuffer, expect } from './utils';
|
||||
import type { ParsedTranscriptData, ProofData } from './types';
|
||||
|
||||
let LOGGING_LEVEL: LoggingLevel = 'Info';
|
||||
|
||||
function debug(...args: any[]) {
|
||||
if (['Debug', 'Trace'].includes(LOGGING_LEVEL)) {
|
||||
console.log('tlsn-js DEBUG', ...args);
|
||||
}
|
||||
}
|
||||
|
||||
export default async function init(config?: {
|
||||
loggingLevel?: LoggingLevel;
|
||||
hardwareConcurrency?: number;
|
||||
}) {
|
||||
const {
|
||||
loggingLevel = 'Info',
|
||||
hardwareConcurrency = navigator.hardwareConcurrency,
|
||||
} = config || {};
|
||||
|
||||
LOGGING_LEVEL = loggingLevel;
|
||||
|
||||
const res = await initWasm();
|
||||
|
||||
init_logging({
|
||||
level: loggingLevel,
|
||||
crate_filters: undefined,
|
||||
span_events: undefined,
|
||||
});
|
||||
|
||||
// 6422528 ~= 6.12 mb
|
||||
debug('res.memory=', res.memory);
|
||||
debug('res.memory.buffer.length=', res.memory.buffer.byteLength);
|
||||
debug('DEBUG', 'initialize thread pool');
|
||||
|
||||
await initThreadPool(hardwareConcurrency);
|
||||
debug('initialized thread pool');
|
||||
}
|
||||
|
||||
export class Prover {
|
||||
#prover: WasmProver;
|
||||
#config: ProverConfig;
|
||||
|
||||
constructor(config: {
|
||||
id?: string;
|
||||
serverDns: string;
|
||||
maxSentData?: number;
|
||||
maxRecvData?: number;
|
||||
}) {
|
||||
this.#config = {
|
||||
id: config.id || String(Date.now()),
|
||||
server_dns: config.serverDns,
|
||||
max_recv_data: config.maxRecvData,
|
||||
max_sent_data: config.maxSentData,
|
||||
};
|
||||
this.#prover = new WasmProver(this.#config);
|
||||
}
|
||||
|
||||
get id() {
|
||||
return this.#config.id;
|
||||
}
|
||||
|
||||
async free() {
|
||||
return this.#prover.free();
|
||||
}
|
||||
|
||||
async setup(verifierUrl: string): Promise<void> {
|
||||
return this.#prover.setup(verifierUrl);
|
||||
}
|
||||
|
||||
async transcript(): Promise<{
|
||||
sent: string;
|
||||
recv: string;
|
||||
ranges: { recv: ParsedTranscriptData; sent: ParsedTranscriptData };
|
||||
}> {
|
||||
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),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async sendRequest(
|
||||
wsProxyUrl: string,
|
||||
request: {
|
||||
url: string;
|
||||
method?: Method;
|
||||
headers?: { [key: string]: string };
|
||||
body?: any;
|
||||
},
|
||||
): Promise<{
|
||||
status: number;
|
||||
headers: { [key: string]: string };
|
||||
}> {
|
||||
const { url, method = 'GET', headers = {}, body } = request;
|
||||
const hostname = new URL(url).hostname;
|
||||
const headerMap: Map<string, number[]> = new Map();
|
||||
|
||||
headerMap.set('Host', stringToBuffer(hostname));
|
||||
headerMap.set('Connection', stringToBuffer('close'));
|
||||
Object.entries(headers).forEach(([key, value]) => {
|
||||
headerMap.set(key, stringToBuffer(value));
|
||||
});
|
||||
|
||||
const resp = await this.#prover.send_request(wsProxyUrl, {
|
||||
uri: url,
|
||||
method,
|
||||
headers: headerMap,
|
||||
body,
|
||||
});
|
||||
debug('prover.sendRequest', resp);
|
||||
|
||||
return {
|
||||
status: resp.status,
|
||||
headers: resp.headers.reduce(
|
||||
(acc: { [key: string]: string }, [name, arr]) => {
|
||||
acc[name] = Buffer.from(arr).toString();
|
||||
return acc;
|
||||
},
|
||||
{},
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
async notarize(commit: Commit): Promise<string> {
|
||||
const notarizedSession = await this.#prover.notarize(commit);
|
||||
return arrayToHex(notarizedSession.serialize());
|
||||
}
|
||||
}
|
||||
|
||||
export class Verifier {
|
||||
#config: VerifierConfig;
|
||||
#verifier: WasmVerifier;
|
||||
|
||||
constructor(config: VerifierConfig) {
|
||||
this.#config = config;
|
||||
this.#verifier = new WasmVerifier(this.#config);
|
||||
}
|
||||
|
||||
get id() {
|
||||
return this.#config.id;
|
||||
}
|
||||
|
||||
async verify(): Promise<VerifierData> {
|
||||
return this.#verifier.verify();
|
||||
}
|
||||
|
||||
async connect(proverUrl: string): Promise<void> {
|
||||
return this.#verifier.connect(proverUrl);
|
||||
}
|
||||
}
|
||||
|
||||
export class NotarizedSession {
|
||||
#session: WasmNotarizedSession;
|
||||
|
||||
constructor(serializedSessionHex: string) {
|
||||
this.#session = WasmNotarizedSession.deserialize(
|
||||
new Uint8Array(Buffer.from(serializedSessionHex, 'hex')),
|
||||
);
|
||||
}
|
||||
|
||||
async free() {
|
||||
return this.#session.free();
|
||||
}
|
||||
|
||||
async proof(reveal: Reveal) {
|
||||
const proof = this.#session.proof(reveal);
|
||||
console.log(proof);
|
||||
return arrayToHex(proof.serialize());
|
||||
}
|
||||
|
||||
async serialize() {
|
||||
return arrayToHex(this.#session.serialize());
|
||||
}
|
||||
}
|
||||
|
||||
export class TlsProof {
|
||||
#proof: WasmTlsProof;
|
||||
|
||||
constructor(serializedProofHex: string) {
|
||||
this.#proof = WasmTlsProof.deserialize(
|
||||
new Uint8Array(Buffer.from(serializedProofHex, 'hex')),
|
||||
);
|
||||
}
|
||||
|
||||
async free() {
|
||||
return this.#proof.free();
|
||||
}
|
||||
|
||||
async serialize() {
|
||||
return arrayToHex(this.#proof.serialize());
|
||||
}
|
||||
|
||||
async verify(
|
||||
notaryPublicKey: NotaryPublicKey,
|
||||
redactedSymbol = '*',
|
||||
): Promise<ProofData> {
|
||||
const { received, received_auth_ranges, sent, ...rest } =
|
||||
this.#proof.verify(notaryPublicKey);
|
||||
|
||||
return {
|
||||
...rest,
|
||||
recv_auth_ranges: received_auth_ranges,
|
||||
recv: received.reduce((recv: string, num) => {
|
||||
recv =
|
||||
recv + (num === 0 ? redactedSymbol : Buffer.from([num]).toString());
|
||||
return recv;
|
||||
}, ''),
|
||||
sent: sent.reduce((sent: string, num) => {
|
||||
sent =
|
||||
sent + (num === 0 ? redactedSymbol : Buffer.from([num]).toString());
|
||||
return sent;
|
||||
}, ''),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export class NotaryServer {
|
||||
#url: string;
|
||||
|
||||
static from(url: string) {
|
||||
return new NotaryServer(url);
|
||||
}
|
||||
|
||||
constructor(url: string) {
|
||||
this.#url = url;
|
||||
}
|
||||
|
||||
get url() {
|
||||
return this.#url;
|
||||
}
|
||||
|
||||
async publicKey(): Promise<string> {
|
||||
const res = await fetch(this.#url + '/info');
|
||||
const { publicKey } = await res.json();
|
||||
expect(
|
||||
typeof publicKey === 'string' && !!publicKey.length,
|
||||
'invalid public key',
|
||||
);
|
||||
return publicKey!;
|
||||
}
|
||||
|
||||
async sessionUrl(
|
||||
maxSentData?: number,
|
||||
maxRecvData?: number,
|
||||
): Promise<string> {
|
||||
const resp = await fetch(`${this.#url}/session`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
clientType: 'Websocket',
|
||||
maxRecvData,
|
||||
maxSentData,
|
||||
}),
|
||||
});
|
||||
const { sessionId } = await resp.json();
|
||||
expect(
|
||||
typeof sessionId === 'string' && !!sessionId.length,
|
||||
'invalid session id',
|
||||
);
|
||||
const url = new URL(this.#url);
|
||||
const protocol = url.protocol === 'https:' ? 'wss' : 'ws';
|
||||
const pathname = url.pathname;
|
||||
return `${protocol}://${url.host}${pathname === '/' ? '' : pathname}/notarize?sessionId=${sessionId!}`;
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
type ParsedTranscriptData,
|
||||
type ProofData,
|
||||
type LoggingLevel,
|
||||
type LoggingConfig,
|
||||
type Transcript,
|
||||
type Commit,
|
||||
type Reveal,
|
||||
type ProverConfig,
|
||||
};
|
||||
96
src/tlsn.ts
96
src/tlsn.ts
@@ -1,96 +0,0 @@
|
||||
import init, {
|
||||
initThreadPool,
|
||||
prover,
|
||||
verify,
|
||||
setup_tracing_web,
|
||||
} from '../wasm/prover/pkg/tlsn_extension_rs';
|
||||
|
||||
export const DEFAULT_LOGGING_FILTER: string = 'info,tlsn_extension_rs=debug';
|
||||
|
||||
export default class TLSN {
|
||||
private startPromise: Promise<void>;
|
||||
private resolveStart!: () => void;
|
||||
private logging_filter: string;
|
||||
|
||||
/**
|
||||
* Initializes a new instance of the TLSN class.
|
||||
*
|
||||
* @param logging_filter - Optional logging filter string.
|
||||
* Defaults to DEFAULT_LOGGING_FILTER
|
||||
*/
|
||||
constructor(logging_filter: string = DEFAULT_LOGGING_FILTER) {
|
||||
this.logging_filter = logging_filter;
|
||||
|
||||
this.startPromise = new Promise((resolve) => {
|
||||
this.resolveStart = resolve;
|
||||
});
|
||||
this.start();
|
||||
}
|
||||
|
||||
async start() {
|
||||
// console.log('start');
|
||||
const numConcurrency = navigator.hardwareConcurrency;
|
||||
// console.log('!@# navigator.hardwareConcurrency=', numConcurrency);
|
||||
await init();
|
||||
setup_tracing_web(this.logging_filter);
|
||||
// const res = await init();
|
||||
// console.log('!@# res.memory=', res.memory);
|
||||
// 6422528 ~= 6.12 mb
|
||||
// console.log('!@# res.memory.buffer.length=', res.memory.buffer.byteLength);
|
||||
await initThreadPool(numConcurrency);
|
||||
this.resolveStart();
|
||||
}
|
||||
|
||||
async waitForStart() {
|
||||
return this.startPromise;
|
||||
}
|
||||
|
||||
async prove(
|
||||
url: string,
|
||||
options?: {
|
||||
method?: string;
|
||||
headers?: { [key: string]: string };
|
||||
body?: string;
|
||||
maxSentData?: number;
|
||||
maxRecvData?: number;
|
||||
maxTranscriptSize?: number;
|
||||
notaryUrl?: string;
|
||||
websocketProxyUrl?: string;
|
||||
secretHeaders?: string[];
|
||||
secretResps?: string[];
|
||||
},
|
||||
) {
|
||||
await this.waitForStart();
|
||||
// console.log('worker', url, {
|
||||
// ...options,
|
||||
// notaryUrl: options?.notaryUrl,
|
||||
// websocketProxyUrl: options?.websocketProxyUrl,
|
||||
// });
|
||||
const resProver = await prover(
|
||||
url,
|
||||
{
|
||||
...options,
|
||||
notaryUrl: options?.notaryUrl,
|
||||
websocketProxyUrl: options?.websocketProxyUrl,
|
||||
},
|
||||
options?.secretHeaders || [],
|
||||
options?.secretResps || [],
|
||||
);
|
||||
const resJSON = JSON.parse(resProver);
|
||||
// console.log('!@# resProver,resJSON=', { resProver, resJSON });
|
||||
// console.log('!@# resAfter.memory=', resJSON.memory);
|
||||
// 1105920000 ~= 1.03 gb
|
||||
// console.log(
|
||||
// '!@# resAfter.memory.buffer.length=',
|
||||
// resJSON.memory?.buffer?.byteLength,
|
||||
// );
|
||||
|
||||
return resJSON;
|
||||
}
|
||||
|
||||
async verify(proof: any, pubkey: string) {
|
||||
await this.waitForStart();
|
||||
const raw = await verify(JSON.stringify(proof), pubkey);
|
||||
return JSON.parse(raw);
|
||||
}
|
||||
}
|
||||
116
src/types.ts
116
src/types.ts
@@ -1,98 +1,22 @@
|
||||
export interface Proof {
|
||||
session: Session;
|
||||
substrings: Substrings;
|
||||
notaryUrl: string;
|
||||
}
|
||||
|
||||
export interface Session {
|
||||
header: Header;
|
||||
signature: Signature;
|
||||
session_info: SessionInfo;
|
||||
}
|
||||
|
||||
export interface SessionInfo {
|
||||
server_name: ServerName;
|
||||
handshake_decommitment: HandshakeDecommitment;
|
||||
}
|
||||
|
||||
export interface HandshakeDecommitment {
|
||||
nonce: number[];
|
||||
data: Data;
|
||||
}
|
||||
|
||||
export interface Data {
|
||||
server_cert_details: ServerCERTDetails;
|
||||
server_kx_details: ServerKxDetails;
|
||||
client_random: number[];
|
||||
server_random: number[];
|
||||
}
|
||||
|
||||
export interface ServerCERTDetails {
|
||||
cert_chain: Array<number[]>;
|
||||
ocsp_response: number[];
|
||||
scts: null;
|
||||
}
|
||||
|
||||
export interface ServerKxDetails {
|
||||
kx_params: number[];
|
||||
kx_sig: KxSig;
|
||||
}
|
||||
|
||||
export interface KxSig {
|
||||
scheme: string;
|
||||
sig: number[];
|
||||
}
|
||||
|
||||
export interface Header {
|
||||
encoder_seed: number[];
|
||||
merkle_root: number[];
|
||||
sent_len: number;
|
||||
recv_len: number;
|
||||
handshake_summary: HandshakeSummary;
|
||||
}
|
||||
|
||||
export interface HandshakeSummary {
|
||||
time: number;
|
||||
server_public_key: ServerPublicKey;
|
||||
handshake_commitment: number[];
|
||||
}
|
||||
|
||||
export interface ServerPublicKey {
|
||||
group: string;
|
||||
key: number[];
|
||||
}
|
||||
|
||||
export interface ServerName {
|
||||
Dns: string;
|
||||
}
|
||||
|
||||
export interface Signature {
|
||||
P256: string;
|
||||
}
|
||||
|
||||
export interface Substrings {
|
||||
openings: { [key: string]: Opening[] };
|
||||
inclusion_proof: InclusionProof;
|
||||
}
|
||||
|
||||
export interface InclusionProof {
|
||||
proof: any[];
|
||||
total_leaves: number;
|
||||
}
|
||||
|
||||
export interface Opening {
|
||||
kind?: string;
|
||||
ranges?: Range[];
|
||||
direction?: string;
|
||||
Blake3?: Blake3;
|
||||
}
|
||||
|
||||
export interface Blake3 {
|
||||
data: number[];
|
||||
nonce: number[];
|
||||
}
|
||||
|
||||
export interface Range {
|
||||
export type CommitData = {
|
||||
start: number;
|
||||
end: number;
|
||||
}
|
||||
};
|
||||
|
||||
export type ParsedTranscriptData = {
|
||||
all: CommitData;
|
||||
info: CommitData;
|
||||
headers: { [key: string]: CommitData };
|
||||
body?: CommitData;
|
||||
json?: { [path: string]: CommitData };
|
||||
lineBreaks: CommitData[];
|
||||
};
|
||||
|
||||
export type ProofData = {
|
||||
time: number;
|
||||
server_dns: string;
|
||||
sent: string;
|
||||
sent_auth_ranges: { start: number; end: number }[];
|
||||
recv: string;
|
||||
recv_auth_ranges: { start: number; end: number }[];
|
||||
};
|
||||
|
||||
408
src/utils.ts
Normal file
408
src/utils.ts
Normal file
@@ -0,0 +1,408 @@
|
||||
import { ParsedTranscriptData } from './types';
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
export function stringToBuffer(str: string): number[] {
|
||||
return Buffer.from(str).toJSON().data;
|
||||
}
|
||||
|
||||
export function arrayToHex(uintArr: Uint8Array): string {
|
||||
return Buffer.from(uintArr).toString('hex');
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
import * as Comlink from 'comlink';
|
||||
import TLSN from './tlsn';
|
||||
|
||||
export default TLSN;
|
||||
|
||||
Comlink.expose(TLSN);
|
||||
@@ -1,39 +1,80 @@
|
||||
import { prove, verify } from '../../src';
|
||||
import {
|
||||
Prover as _Prover,
|
||||
NotaryServer,
|
||||
NotarizedSession as _NotarizedSession,
|
||||
TlsProof as _TlsProof,
|
||||
} from '../../src/lib';
|
||||
import { assert } from '../utils';
|
||||
import * as Comlink from 'comlink';
|
||||
|
||||
const { init, Prover, NotarizedSession, TlsProof }: 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 proof = await prove('https://swapi.dev/api/people/1', {
|
||||
method: 'GET',
|
||||
headers: { secret: 'test_secret' },
|
||||
maxTranscriptSize: 16384,
|
||||
notaryUrl: process.env.LOCAL_NOTARY
|
||||
? 'http://localhost:7047'
|
||||
: 'https://notary.pse.dev/v0.1.0-alpha.6',
|
||||
websocketProxyUrl: process.env.LOCAL_WS
|
||||
? 'ws://localhost:55688'
|
||||
: 'wss://notary.pse.dev/proxy?token=swapi.dev',
|
||||
secretHeaders: ['test_secret'],
|
||||
secretResps: ['blond', 'fair'],
|
||||
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 sessionHex = await prover.notarize(commit);
|
||||
const session = (await new NotarizedSession(
|
||||
sessionHex,
|
||||
)) as _NotarizedSession;
|
||||
const proofHex = await session.proof(commit);
|
||||
console.log('proof:', proofHex);
|
||||
const proof = (await new TlsProof(proofHex)) as _TlsProof;
|
||||
console.timeEnd('prove');
|
||||
|
||||
console.log('Proof: ', JSON.stringify(proof));
|
||||
|
||||
console.time('verify');
|
||||
const result = await verify(proof);
|
||||
const result = await proof.verify({
|
||||
typ: 'P256',
|
||||
key: await notary.publicKey(),
|
||||
});
|
||||
console.timeEnd('verify');
|
||||
|
||||
console.log(result);
|
||||
|
||||
assert(result.sent.includes('host: swapi.dev'));
|
||||
assert(result.sent.includes('secret: XXXXXXXXXXX'));
|
||||
assert(result.recv.includes('Luke Skywalker'));
|
||||
assert(result.recv.includes('"hair_color":"XXXXX"'));
|
||||
assert(result.recv.includes('"skin_color":"XXXX"'));
|
||||
assert(!result.sent.includes('secret: test_secret'));
|
||||
assert(result.recv.includes('"name":"Luke Skywalker"'));
|
||||
assert(result.recv.includes('"hair_color":"blond"'));
|
||||
assert(result.recv.includes('"skin_color":"fair"'));
|
||||
|
||||
// @ts-ignore
|
||||
document.getElementById('full-integration-swapi').textContent = 'OK';
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -46,8 +46,8 @@ const spawnTlsnServerFixture = () => {
|
||||
|
||||
let localNotaryServer: ChildProcess;
|
||||
const spawnLocalNotaryServer = async () => {
|
||||
const localNotaryServerPath = './utils/tlsn/notary-server/';
|
||||
localNotaryServer = exec(`target/release/notary-server`, {
|
||||
const localNotaryServerPath = './utils/tlsn/notary/server';
|
||||
localNotaryServer = exec(`../target/release/notary-server`, {
|
||||
cwd: localNotaryServerPath,
|
||||
});
|
||||
localNotaryServer.stdout?.on('data', (data) => {
|
||||
@@ -74,7 +74,7 @@ const spawnLocalNotaryServer = async () => {
|
||||
|
||||
const configureNotarySerer = () => {
|
||||
try {
|
||||
const configPath = './utils/tlsn/notary-server/config/config.yaml';
|
||||
const configPath = './utils/tlsn/notary/server/config/config.yaml';
|
||||
const fileContents = fs.readFileSync(configPath, 'utf8');
|
||||
const data = yaml.load(fileContents) as any;
|
||||
data.tls.enabled = false;
|
||||
|
||||
9
test/worker.ts
Normal file
9
test/worker.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import * as Comlink from 'comlink';
|
||||
import init, { Prover, NotarizedSession, TlsProof } from '../src/lib';
|
||||
|
||||
Comlink.expose({
|
||||
init,
|
||||
Prover,
|
||||
NotarizedSession,
|
||||
TlsProof,
|
||||
});
|
||||
@@ -2,7 +2,7 @@
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"module": "esnext",
|
||||
"target": "es5",
|
||||
"target": "es2015",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
|
||||
@@ -22,7 +22,7 @@ fi
|
||||
# Checkout the specific tag
|
||||
git checkout "v0.1.0-alpha.6"
|
||||
|
||||
for dir in "tlsn/tlsn-server-fixture/" "notary-server"; do
|
||||
for dir in "tlsn/tlsn-server-fixture/" "notary/server"; do
|
||||
# Change to the specific subdirectory
|
||||
cd ${dir}
|
||||
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
[target.wasm32-unknown-unknown]
|
||||
rustflags = [
|
||||
"-C",
|
||||
"target-feature=+atomics,+bulk-memory,+mutable-globals",
|
||||
]
|
||||
|
||||
[unstable]
|
||||
build-std = ["panic_abort", "std"]
|
||||
13
wasm/pkg/README.md
Normal file
13
wasm/pkg/README.md
Normal file
@@ -0,0 +1,13 @@
|
||||
# TLSNotary WASM bindings
|
||||
|
||||
## Build
|
||||
|
||||
This crate must be built using the nightly rust compiler with the following flags:
|
||||
|
||||
```bash
|
||||
RUSTFLAGS='-C target-feature=+atomics,+bulk-memory,+mutable-globals' \
|
||||
rustup run nightly \
|
||||
wasm-pack build --target web . -- -Zbuild-std=panic_abort,std
|
||||
```
|
||||
|
||||
|
||||
15
wasm/pkg/package.json
Normal file
15
wasm/pkg/package.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "tlsn-wasm",
|
||||
"type": "module",
|
||||
"version": "0.1.0-alpha.6",
|
||||
"files": [
|
||||
"tlsn_wasm_bg.wasm",
|
||||
"tlsn_wasm.js",
|
||||
"tlsn_wasm.d.ts"
|
||||
],
|
||||
"main": "tlsn_wasm.js",
|
||||
"types": "tlsn_wasm.d.ts",
|
||||
"sideEffects": [
|
||||
"./snippets/*"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
onmessage = function (ev) {
|
||||
let [ia, index, value] = ev.data;
|
||||
ia = new Int32Array(ia.buffer);
|
||||
let result = Atomics.wait(ia, index, value);
|
||||
postMessage(result);
|
||||
};
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright 2022 Google Inc. All Rights Reserved.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// Note: this is never used, but necessary to prevent a bug in Firefox
|
||||
// (https://bugzilla.mozilla.org/show_bug.cgi?id=1702191) where it collects
|
||||
// Web Workers that have a shared WebAssembly memory with the main thread,
|
||||
// but are not explicitly rooted via a `Worker` instance.
|
||||
//
|
||||
// By storing them in a variable, we can keep `Worker` objects around and
|
||||
// prevent them from getting GC-d.
|
||||
let _workers;
|
||||
|
||||
export async function startWorkers(module, memory, builder) {
|
||||
if (builder.numThreads() === 0) {
|
||||
throw new Error(`num_threads must be > 0.`);
|
||||
}
|
||||
|
||||
const workerInit = {
|
||||
module,
|
||||
memory,
|
||||
receiver: builder.receiver()
|
||||
};
|
||||
|
||||
_workers = await Promise.all(
|
||||
Array.from({ length: builder.numThreads() }, async () => {
|
||||
// Self-spawn into a new Worker.
|
||||
//
|
||||
// TODO: while `new URL('...', import.meta.url) becomes a semi-standard
|
||||
// way to get asset URLs relative to the module across various bundlers
|
||||
// and browser, ideally we should switch to `import.meta.resolve`
|
||||
// once it becomes a standard.
|
||||
const worker = new Worker(
|
||||
new URL('./workerHelpers.worker.js', import.meta.url),
|
||||
{
|
||||
type: 'module'
|
||||
}
|
||||
);
|
||||
worker.postMessage(workerInit);
|
||||
await new Promise(resolve =>
|
||||
worker.addEventListener('message', resolve, { once: true })
|
||||
);
|
||||
return worker;
|
||||
})
|
||||
);
|
||||
builder.build();
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright 2022 Google Inc. All Rights Reserved.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// Note: our JS should have been generated in
|
||||
// `[out-dir]/snippets/wasm-bindgen-rayon-[hash]/workerHelpers.worker.js`,
|
||||
// resolve the main module via `../../..`.
|
||||
//
|
||||
// This might need updating if the generated structure changes on wasm-bindgen
|
||||
// side ever in the future, but works well with bundlers today. The whole
|
||||
// point of this crate, after all, is to abstract away unstable features
|
||||
// and temporary bugs so that you don't need to deal with them in your code.
|
||||
import initWbg, { wbg_rayon_start_worker } from '../../../tlsn_wasm.js';
|
||||
|
||||
onmessage = async ({ data: { module, memory, receiver } }) => {
|
||||
await initWbg(module, memory);
|
||||
postMessage(true);
|
||||
wbg_rayon_start_worker(receiver);
|
||||
};
|
||||
294
wasm/pkg/tlsn_wasm.d.ts
vendored
Normal file
294
wasm/pkg/tlsn_wasm.d.ts
vendored
Normal file
@@ -0,0 +1,294 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* Initializes logging.
|
||||
* @param {LoggingConfig | undefined} [config]
|
||||
*/
|
||||
export function init_logging(config?: LoggingConfig): void;
|
||||
/**
|
||||
* @param {number} num_threads
|
||||
* @returns {Promise<any>}
|
||||
*/
|
||||
export function initThreadPool(num_threads: number): Promise<any>;
|
||||
/**
|
||||
* @param {number} receiver
|
||||
*/
|
||||
export function wbg_rayon_start_worker(receiver: number): void;
|
||||
export type Body = JsonValue;
|
||||
|
||||
export type Method = "GET" | "POST" | "PUT" | "DELETE";
|
||||
|
||||
export interface HttpRequest {
|
||||
uri: string;
|
||||
method: Method;
|
||||
headers: Map<string, number[]>;
|
||||
body: Body | undefined;
|
||||
}
|
||||
|
||||
export interface HttpResponse {
|
||||
status: number;
|
||||
headers: [string, number[]][];
|
||||
}
|
||||
|
||||
export interface Transcript {
|
||||
sent: number[];
|
||||
recv: number[];
|
||||
}
|
||||
|
||||
export interface Commit {
|
||||
sent: { start: number; end: number }[];
|
||||
recv: { start: number; end: number }[];
|
||||
}
|
||||
|
||||
export interface Reveal {
|
||||
sent: { start: number; end: number }[];
|
||||
recv: { start: number; end: number }[];
|
||||
}
|
||||
|
||||
export type KeyType = "P256";
|
||||
|
||||
export interface NotaryPublicKey {
|
||||
typ: KeyType;
|
||||
key: string;
|
||||
}
|
||||
|
||||
export interface ProofData {
|
||||
time: number;
|
||||
server_dns: string;
|
||||
sent: number[];
|
||||
sent_auth_ranges: { start: number; end: number }[];
|
||||
received: number[];
|
||||
received_auth_ranges: { start: number; end: number }[];
|
||||
}
|
||||
|
||||
export interface VerifierData {
|
||||
server_dns: string;
|
||||
sent: number[];
|
||||
sent_auth_ranges: { start: number; end: number }[];
|
||||
received: number[];
|
||||
received_auth_ranges: { start: number; end: number }[];
|
||||
}
|
||||
|
||||
export interface CrateLogFilter {
|
||||
level: LoggingLevel;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface LoggingConfig {
|
||||
level: LoggingLevel | undefined;
|
||||
crate_filters: CrateLogFilter[] | undefined;
|
||||
span_events: SpanEvent[] | undefined;
|
||||
}
|
||||
|
||||
export type SpanEvent = "New" | "Close" | "Active";
|
||||
|
||||
export type LoggingLevel = "Trace" | "Debug" | "Info" | "Warn" | "Error";
|
||||
|
||||
export interface VerifierConfig {
|
||||
id: string;
|
||||
max_sent_data: number | undefined;
|
||||
max_received_data: number | undefined;
|
||||
}
|
||||
|
||||
export interface ProverConfig {
|
||||
id: string;
|
||||
server_dns: string;
|
||||
max_sent_data: number | undefined;
|
||||
max_recv_data: number | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
export class NotarizedSession {
|
||||
free(): void;
|
||||
/**
|
||||
* Builds a new proof.
|
||||
* @param {Reveal} reveal
|
||||
* @returns {TlsProof}
|
||||
*/
|
||||
proof(reveal: Reveal): TlsProof;
|
||||
/**
|
||||
* Returns the transcript.
|
||||
* @returns {Transcript}
|
||||
*/
|
||||
transcript(): Transcript;
|
||||
/**
|
||||
* Serializes to a byte array.
|
||||
* @returns {Uint8Array}
|
||||
*/
|
||||
serialize(): Uint8Array;
|
||||
/**
|
||||
* Deserializes from a byte array.
|
||||
* @param {Uint8Array} bytes
|
||||
* @returns {NotarizedSession}
|
||||
*/
|
||||
static deserialize(bytes: Uint8Array): NotarizedSession;
|
||||
}
|
||||
/**
|
||||
*/
|
||||
export class Prover {
|
||||
free(): void;
|
||||
/**
|
||||
* @param {ProverConfig} config
|
||||
*/
|
||||
constructor(config: ProverConfig);
|
||||
/**
|
||||
* Set up the prover.
|
||||
*
|
||||
* This performs all MPC setup prior to establishing the connection to the
|
||||
* application server.
|
||||
* @param {string} verifier_url
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
setup(verifier_url: string): Promise<void>;
|
||||
/**
|
||||
* Send the HTTP request to the server.
|
||||
* @param {string} ws_proxy_url
|
||||
* @param {HttpRequest} request
|
||||
* @returns {Promise<HttpResponse>}
|
||||
*/
|
||||
send_request(ws_proxy_url: string, request: HttpRequest): Promise<HttpResponse>;
|
||||
/**
|
||||
* Returns the transcript.
|
||||
* @returns {Transcript}
|
||||
*/
|
||||
transcript(): Transcript;
|
||||
/**
|
||||
* Runs the notarization protocol.
|
||||
* @param {Commit} commit
|
||||
* @returns {Promise<NotarizedSession>}
|
||||
*/
|
||||
notarize(commit: Commit): Promise<NotarizedSession>;
|
||||
/**
|
||||
* Reveals data to the verifier and finalizes the protocol.
|
||||
* @param {Reveal} reveal
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
reveal(reveal: Reveal): Promise<void>;
|
||||
}
|
||||
/**
|
||||
*/
|
||||
export class TlsProof {
|
||||
free(): void;
|
||||
/**
|
||||
* @returns {Uint8Array}
|
||||
*/
|
||||
serialize(): Uint8Array;
|
||||
/**
|
||||
* @param {Uint8Array} bytes
|
||||
* @returns {TlsProof}
|
||||
*/
|
||||
static deserialize(bytes: Uint8Array): TlsProof;
|
||||
/**
|
||||
* Verifies the proof using the provided notary public key.
|
||||
* @param {NotaryPublicKey} notary_key
|
||||
* @returns {ProofData}
|
||||
*/
|
||||
verify(notary_key: NotaryPublicKey): ProofData;
|
||||
}
|
||||
/**
|
||||
*/
|
||||
export class Verifier {
|
||||
free(): void;
|
||||
/**
|
||||
* @param {VerifierConfig} config
|
||||
*/
|
||||
constructor(config: VerifierConfig);
|
||||
/**
|
||||
* Connect to the prover.
|
||||
* @param {string} prover_url
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
connect(prover_url: string): Promise<void>;
|
||||
/**
|
||||
* Verifies the connection and finalizes the protocol.
|
||||
* @returns {Promise<VerifierData>}
|
||||
*/
|
||||
verify(): Promise<VerifierData>;
|
||||
}
|
||||
/**
|
||||
*/
|
||||
export class wbg_rayon_PoolBuilder {
|
||||
free(): void;
|
||||
/**
|
||||
* @returns {number}
|
||||
*/
|
||||
numThreads(): number;
|
||||
/**
|
||||
* @returns {number}
|
||||
*/
|
||||
receiver(): number;
|
||||
/**
|
||||
*/
|
||||
build(): void;
|
||||
}
|
||||
|
||||
export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module;
|
||||
|
||||
export interface InitOutput {
|
||||
readonly __wbg_notarizedsession_free: (a: number) => void;
|
||||
readonly notarizedsession_proof: (a: number, b: number, c: number) => void;
|
||||
readonly notarizedsession_transcript: (a: number) => number;
|
||||
readonly notarizedsession_serialize: (a: number, b: number) => void;
|
||||
readonly notarizedsession_deserialize: (a: number, b: number, c: number) => void;
|
||||
readonly __wbg_tlsproof_free: (a: number) => void;
|
||||
readonly tlsproof_serialize: (a: number, b: number) => void;
|
||||
readonly tlsproof_deserialize: (a: number, b: number, c: number) => void;
|
||||
readonly tlsproof_verify: (a: number, b: number, c: number) => void;
|
||||
readonly __wbg_verifier_free: (a: number) => void;
|
||||
readonly verifier_new: (a: number) => number;
|
||||
readonly verifier_connect: (a: number, b: number, c: number) => number;
|
||||
readonly verifier_verify: (a: number) => number;
|
||||
readonly init_logging: (a: number) => void;
|
||||
readonly __wbg_prover_free: (a: number) => void;
|
||||
readonly prover_new: (a: number) => number;
|
||||
readonly prover_setup: (a: number, b: number, c: number) => number;
|
||||
readonly prover_send_request: (a: number, b: number, c: number, d: number) => number;
|
||||
readonly prover_transcript: (a: number, b: number) => void;
|
||||
readonly prover_notarize: (a: number, b: number) => number;
|
||||
readonly prover_reveal: (a: number, b: number) => number;
|
||||
readonly __wbg_wbg_rayon_poolbuilder_free: (a: number) => void;
|
||||
readonly wbg_rayon_poolbuilder_numThreads: (a: number) => number;
|
||||
readonly wbg_rayon_poolbuilder_receiver: (a: number) => number;
|
||||
readonly wbg_rayon_poolbuilder_build: (a: number) => void;
|
||||
readonly initThreadPool: (a: number) => number;
|
||||
readonly wbg_rayon_start_worker: (a: number) => void;
|
||||
readonly ring_core_0_17_8_bn_mul_mont: (a: number, b: number, c: number, d: number, e: number, f: number) => void;
|
||||
readonly memory: WebAssembly.Memory;
|
||||
readonly __wbindgen_malloc: (a: number, b: number) => number;
|
||||
readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
|
||||
readonly __wbindgen_export_3: WebAssembly.Table;
|
||||
readonly _dyn_core__ops__function__FnMut_____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h55b2cafb95688ebd: (a: number, b: number) => void;
|
||||
readonly _dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__hd2e6f08741139974: (a: number, b: number, c: number) => void;
|
||||
readonly _dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h6f377bea5980efdf: (a: number, b: number, c: number) => void;
|
||||
readonly _dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h71d6551dc02f3cc7: (a: number, b: number, c: number) => void;
|
||||
readonly __wbindgen_add_to_stack_pointer: (a: number) => number;
|
||||
readonly __wbindgen_free: (a: number, b: number, c: number) => void;
|
||||
readonly __wbindgen_exn_store: (a: number) => void;
|
||||
readonly wasm_bindgen__convert__closures__invoke2_mut__h0a86b19f1fa78a2d: (a: number, b: number, c: number, d: number) => void;
|
||||
readonly __wbindgen_thread_destroy: (a?: number, b?: number) => void;
|
||||
readonly __wbindgen_start: () => void;
|
||||
}
|
||||
|
||||
export type SyncInitInput = BufferSource | WebAssembly.Module;
|
||||
/**
|
||||
* Instantiates the given `module`, which can either be bytes or
|
||||
* a precompiled `WebAssembly.Module`.
|
||||
*
|
||||
* @param {SyncInitInput} module
|
||||
* @param {WebAssembly.Memory} maybe_memory
|
||||
*
|
||||
* @returns {InitOutput}
|
||||
*/
|
||||
export function initSync(module: SyncInitInput, maybe_memory?: WebAssembly.Memory): InitOutput;
|
||||
|
||||
/**
|
||||
* If `module_or_path` is {RequestInfo} or {URL}, makes a request and
|
||||
* for everything else, calls `WebAssembly.instantiate` directly.
|
||||
*
|
||||
* @param {InitInput | Promise<InitInput>} module_or_path
|
||||
* @param {WebAssembly.Memory} maybe_memory
|
||||
*
|
||||
* @returns {Promise<InitOutput>}
|
||||
*/
|
||||
export default function __wbg_init (module_or_path?: InitInput | Promise<InitInput>, maybe_memory?: WebAssembly.Memory): Promise<InitOutput>;
|
||||
1315
wasm/pkg/tlsn_wasm.js
Normal file
1315
wasm/pkg/tlsn_wasm.js
Normal file
File diff suppressed because it is too large
Load Diff
BIN
wasm/pkg/tlsn_wasm_bg.wasm
Normal file
BIN
wasm/pkg/tlsn_wasm_bg.wasm
Normal file
Binary file not shown.
44
wasm/pkg/tlsn_wasm_bg.wasm.d.ts
vendored
Normal file
44
wasm/pkg/tlsn_wasm_bg.wasm.d.ts
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
export function __wbg_notarizedsession_free(a: number): void;
|
||||
export function notarizedsession_proof(a: number, b: number, c: number): void;
|
||||
export function notarizedsession_transcript(a: number): number;
|
||||
export function notarizedsession_serialize(a: number, b: number): void;
|
||||
export function notarizedsession_deserialize(a: number, b: number, c: number): void;
|
||||
export function __wbg_tlsproof_free(a: number): void;
|
||||
export function tlsproof_serialize(a: number, b: number): void;
|
||||
export function tlsproof_deserialize(a: number, b: number, c: number): void;
|
||||
export function tlsproof_verify(a: number, b: number, c: number): void;
|
||||
export function __wbg_verifier_free(a: number): void;
|
||||
export function verifier_new(a: number): number;
|
||||
export function verifier_connect(a: number, b: number, c: number): number;
|
||||
export function verifier_verify(a: number): number;
|
||||
export function init_logging(a: number): void;
|
||||
export function __wbg_prover_free(a: number): void;
|
||||
export function prover_new(a: number): number;
|
||||
export function prover_setup(a: number, b: number, c: number): number;
|
||||
export function prover_send_request(a: number, b: number, c: number, d: number): number;
|
||||
export function prover_transcript(a: number, b: number): void;
|
||||
export function prover_notarize(a: number, b: number): number;
|
||||
export function prover_reveal(a: number, b: number): number;
|
||||
export function __wbg_wbg_rayon_poolbuilder_free(a: number): void;
|
||||
export function wbg_rayon_poolbuilder_numThreads(a: number): number;
|
||||
export function wbg_rayon_poolbuilder_receiver(a: number): number;
|
||||
export function wbg_rayon_poolbuilder_build(a: number): void;
|
||||
export function initThreadPool(a: number): number;
|
||||
export function wbg_rayon_start_worker(a: number): void;
|
||||
export function ring_core_0_17_8_bn_mul_mont(a: number, b: number, c: number, d: number, e: number, f: number): void;
|
||||
export const memory: WebAssembly.Memory;
|
||||
export function __wbindgen_malloc(a: number, b: number): number;
|
||||
export function __wbindgen_realloc(a: number, b: number, c: number, d: number): number;
|
||||
export const __wbindgen_export_3: WebAssembly.Table;
|
||||
export function _dyn_core__ops__function__FnMut_____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h55b2cafb95688ebd(a: number, b: number): void;
|
||||
export function _dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__hd2e6f08741139974(a: number, b: number, c: number): void;
|
||||
export function _dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h6f377bea5980efdf(a: number, b: number, c: number): void;
|
||||
export function _dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h71d6551dc02f3cc7(a: number, b: number, c: number): void;
|
||||
export function __wbindgen_add_to_stack_pointer(a: number): number;
|
||||
export function __wbindgen_free(a: number, b: number, c: number): void;
|
||||
export function __wbindgen_exn_store(a: number): void;
|
||||
export function wasm_bindgen__convert__closures__invoke2_mut__h0a86b19f1fa78a2d(a: number, b: number, c: number, d: number): void;
|
||||
export function __wbindgen_thread_destroy(a: number, b: number): void;
|
||||
export function __wbindgen_start(): void;
|
||||
@@ -1,91 +0,0 @@
|
||||
[package]
|
||||
authors = ["The tlsn-extension Developers"]
|
||||
description = "tlsn-js library for using TLSNotary in browsers"
|
||||
edition = "2018"
|
||||
license = "MIT OR Apache-2.0"
|
||||
name = "tlsn-extension-rs"
|
||||
rust-version = "1.56"
|
||||
version = "0.1.0"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
chrono = "0.4"
|
||||
elliptic-curve = { version = "0.13.5", features = ["pkcs8"] }
|
||||
futures = "0.3"
|
||||
futures-util = "0.3.28"
|
||||
getrandom = { version = "0.2", features = ["js"] }
|
||||
js-sys = "0.3.64"
|
||||
p256 = { version = "0.13", features = ["pem", "ecdsa"] }
|
||||
rayon = "1.5"
|
||||
serde = { version = "1.0.147", features = ["derive"] }
|
||||
serde-wasm-bindgen = "0.6.1"
|
||||
serde_json = "1.0"
|
||||
tracing = "0.1"
|
||||
url = { version = "2.0", features = ["serde"] }
|
||||
wasm-bindgen = "0.2.87"
|
||||
wasm-bindgen-futures = "0.4.37"
|
||||
wasm-bindgen-rayon = "1.0"
|
||||
pin-project-lite = "0.2.4"
|
||||
|
||||
http-body-util = "0.1"
|
||||
hyper = { version = "1.1", features = ["client", "http1"] }
|
||||
hyper-util = { version = "0.1", features = ["http1"] }
|
||||
tracing-subscriber = { version = "0.3", features = ["time","env-filter"] }
|
||||
tracing-web = "0.1.2"
|
||||
|
||||
ring = { version = "0.17", features = ["wasm32_unknown_unknown_js"] }
|
||||
|
||||
# time crate: https://crates.io/crates/time
|
||||
# NOTE: It is required, otherwise "time not implemented on this platform" error happens right after "!@# 2".
|
||||
# Probably due to tokio's time feature is used in tlsn-prover?
|
||||
time = { version = "0.3.34", features = ["wasm-bindgen"] }
|
||||
# Used to calculate elapsed time.
|
||||
web-time = "1.0"
|
||||
|
||||
tlsn-core = { git = "https://github.com/tlsnotary/tlsn.git", tag = "v0.1.0-alpha.6", package = "tlsn-core" }
|
||||
tlsn-prover = { git = "https://github.com/tlsnotary/tlsn.git", tag = "v0.1.0-alpha.6", package = "tlsn-prover" }
|
||||
|
||||
web-sys = { version = "0.3.4", features = [
|
||||
"BinaryType",
|
||||
"Blob",
|
||||
"ErrorEvent",
|
||||
"FileReader",
|
||||
"MessageEvent",
|
||||
"ProgressEvent",
|
||||
"WebSocket",
|
||||
"console",
|
||||
'Document',
|
||||
'HtmlElement',
|
||||
'HtmlInputElement',
|
||||
'Window',
|
||||
'Worker',
|
||||
'Headers',
|
||||
'Request',
|
||||
'RequestInit',
|
||||
'RequestMode',
|
||||
'Response',
|
||||
] }
|
||||
|
||||
# Use the patched ws_stream_wasm to fix the issue https://github.com/najamelan/ws_stream_wasm/issues/12#issuecomment-1711902958
|
||||
ws_stream_wasm = { version = "0.7.4", git = "https://github.com/tlsnotary/ws_stream_wasm", rev = "2ed12aad9f0236e5321f577672f309920b2aef51" }
|
||||
|
||||
# The `console_error_panic_hook` crate provides better debugging of panics by
|
||||
# logging them with `console.error`. This is great for development, but requires
|
||||
# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for
|
||||
# code size when deploying.
|
||||
console_error_panic_hook = { version = "0.1.7" }
|
||||
|
||||
strum = { version = "0.26.1" }
|
||||
strum_macros = "0.26.1"
|
||||
|
||||
[dev-dependencies]
|
||||
wasm-bindgen-test = "0.3.34"
|
||||
|
||||
[profile.release]
|
||||
lto = true # Enable Link Time Optimization
|
||||
opt-level = "z" # Optimize for size
|
||||
|
||||
[package.metadata.wasm-pack.profile.release]
|
||||
wasm-opt = true
|
||||
@@ -1,88 +0,0 @@
|
||||
use core::slice;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
use pin_project_lite::pin_project;
|
||||
|
||||
pin_project! {
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct FuturesIo<T> {
|
||||
#[pin]
|
||||
inner: T,
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> FuturesIo<T> {
|
||||
/// Create a new `FuturesIo` wrapping the given I/O object.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This wrapper is only safe to use if the inner I/O object does not under any circumstance
|
||||
/// read from the buffer passed to `poll_read` in the `futures::AsyncRead` implementation.
|
||||
pub(crate) unsafe fn new(inner: T) -> Self {
|
||||
Self { inner }
|
||||
}
|
||||
|
||||
pub(crate) fn into_inner(self) -> T {
|
||||
self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> hyper::rt::Write for FuturesIo<T>
|
||||
where
|
||||
T: futures::AsyncWrite + Unpin,
|
||||
{
|
||||
fn poll_write(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
buf: &[u8],
|
||||
) -> Poll<Result<usize, std::io::Error>> {
|
||||
self.project().inner.poll_write(cx, buf)
|
||||
}
|
||||
|
||||
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), std::io::Error>> {
|
||||
self.project().inner.poll_flush(cx)
|
||||
}
|
||||
|
||||
fn poll_shutdown(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
) -> Poll<Result<(), std::io::Error>> {
|
||||
self.project().inner.poll_close(cx)
|
||||
}
|
||||
|
||||
fn poll_write_vectored(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
bufs: &[std::io::IoSlice<'_>],
|
||||
) -> Poll<Result<usize, std::io::Error>> {
|
||||
self.project().inner.poll_write_vectored(cx, bufs)
|
||||
}
|
||||
}
|
||||
|
||||
// Adapted from https://github.com/hyperium/hyper-util/blob/99b77a5a6f75f24bc0bcb4ca74b5f26a07b19c80/src/rt/tokio.rs
|
||||
impl<T> hyper::rt::Read for FuturesIo<T>
|
||||
where
|
||||
T: futures::AsyncRead + Unpin,
|
||||
{
|
||||
fn poll_read(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
mut buf: hyper::rt::ReadBufCursor<'_>,
|
||||
) -> Poll<Result<(), std::io::Error>> {
|
||||
// Safety: buf_slice should only be written to, so it's safe to convert `&mut [MaybeUninit<u8>]` to `&mut [u8]`.
|
||||
let buf_slice = unsafe {
|
||||
slice::from_raw_parts_mut(buf.as_mut().as_mut_ptr() as *mut u8, buf.as_mut().len())
|
||||
};
|
||||
|
||||
let n = match futures::AsyncRead::poll_read(self.project().inner, cx, buf_slice) {
|
||||
Poll::Ready(Ok(n)) => n,
|
||||
other => return other.map_ok(|_| ()),
|
||||
};
|
||||
|
||||
unsafe {
|
||||
buf.advance(n);
|
||||
}
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
pub(crate) mod hyper_io;
|
||||
mod request_opt;
|
||||
mod requests;
|
||||
|
||||
pub mod prover;
|
||||
pub use prover::prover;
|
||||
|
||||
pub mod verify;
|
||||
use tracing::error;
|
||||
pub use verify::verify;
|
||||
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
pub use crate::request_opt::{RequestOptions, VerifyResult};
|
||||
|
||||
pub use wasm_bindgen_rayon::init_thread_pool;
|
||||
|
||||
use js_sys::JSON;
|
||||
|
||||
use wasm_bindgen_futures::JsFuture;
|
||||
use web_sys::{Request, RequestInit, Response};
|
||||
|
||||
use std::panic;
|
||||
use tracing::debug;
|
||||
use tracing_subscriber::fmt::format::Pretty;
|
||||
use tracing_subscriber::fmt::time::UtcTime;
|
||||
use tracing_subscriber::prelude::*;
|
||||
use tracing_subscriber::EnvFilter;
|
||||
|
||||
use tracing_web::{performance_layer, MakeWebConsoleWriter};
|
||||
|
||||
extern crate console_error_panic_hook;
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn setup_tracing_web(logging_filter: &str) {
|
||||
let fmt_layer = tracing_subscriber::fmt::layer()
|
||||
.with_ansi(false) // Only partially supported across browsers
|
||||
.with_timer(UtcTime::rfc_3339()) // std::time is not available in browsers
|
||||
// .with_thread_ids(true)
|
||||
// .with_thread_names(true)
|
||||
.with_writer(MakeWebConsoleWriter::new()); // write events to the console
|
||||
let perf_layer = performance_layer().with_details_from_fields(Pretty::default());
|
||||
|
||||
let filter_layer = EnvFilter::builder()
|
||||
.parse(logging_filter)
|
||||
.unwrap_or_default();
|
||||
|
||||
tracing_subscriber::registry()
|
||||
.with(filter_layer)
|
||||
.with(fmt_layer)
|
||||
.with(perf_layer)
|
||||
.init(); // Install these as subscribers to tracing events
|
||||
|
||||
// https://github.com/rustwasm/console_error_panic_hook
|
||||
panic::set_hook(Box::new(|info| {
|
||||
error!("panic occurred: {:?}", info);
|
||||
console_error_panic_hook::hook(info);
|
||||
}));
|
||||
|
||||
debug!("🪵 Logging set up 🪵")
|
||||
}
|
||||
|
||||
pub async fn fetch_as_json_string(url: &str, opts: &RequestInit) -> Result<String, JsValue> {
|
||||
let request = Request::new_with_str_and_init(url, opts)?;
|
||||
let window = web_sys::window().expect("Window object");
|
||||
let resp_value = JsFuture::from(window.fetch_with_request(&request)).await?;
|
||||
assert!(resp_value.is_instance_of::<Response>());
|
||||
let resp: Response = resp_value.dyn_into()?;
|
||||
let json = JsFuture::from(resp.json()?).await?;
|
||||
let stringified = JSON::stringify(&json)?;
|
||||
stringified
|
||||
.as_string()
|
||||
.ok_or_else(|| JsValue::from_str("Could not stringify JSON"))
|
||||
}
|
||||
@@ -1,479 +0,0 @@
|
||||
use futures::channel::oneshot;
|
||||
use std::ops::Range;
|
||||
use tlsn_prover::tls::{Prover, ProverConfig};
|
||||
use wasm_bindgen_futures::spawn_local;
|
||||
use web_time::Instant;
|
||||
|
||||
use ws_stream_wasm::*;
|
||||
|
||||
use crate::hyper_io::FuturesIo;
|
||||
use crate::request_opt::RequestOptions;
|
||||
use crate::requests::{ClientType, NotarizationSessionRequest, NotarizationSessionResponse};
|
||||
|
||||
pub use wasm_bindgen_rayon::init_thread_pool;
|
||||
|
||||
use crate::fetch_as_json_string;
|
||||
pub use crate::request_opt::VerifyResult;
|
||||
use futures::AsyncWriteExt;
|
||||
use http_body_util::{BodyExt, Full};
|
||||
use hyper::{body::Bytes, Request, StatusCode};
|
||||
|
||||
use js_sys::Array;
|
||||
use strum::EnumMessage;
|
||||
use tlsn_core::proof::TlsProof;
|
||||
use url::Url;
|
||||
use wasm_bindgen::prelude::*;
|
||||
use web_sys::{Headers, RequestInit, RequestMode};
|
||||
|
||||
use tracing::{debug, info, trace};
|
||||
|
||||
#[derive(strum_macros::EnumMessage, Debug, Clone, Copy)]
|
||||
#[allow(dead_code)]
|
||||
enum ProverPhases {
|
||||
#[strum(message = "Connect application server with websocket proxy")]
|
||||
ConnectWsProxy,
|
||||
#[strum(message = "Build prover config")]
|
||||
BuildProverConfig,
|
||||
#[strum(message = "Set up prover")]
|
||||
SetUpProver,
|
||||
#[strum(message = "Bind the prover to the server connection")]
|
||||
BindProverToConnection,
|
||||
#[strum(message = "Spawn the prover thread")]
|
||||
SpawnProverThread,
|
||||
#[strum(message = "Attach the hyper HTTP client to the TLS connection")]
|
||||
AttachHttpClient,
|
||||
#[strum(message = "Spawn the HTTP task to be run concurrently")]
|
||||
SpawnHttpTask,
|
||||
#[strum(message = "Build request")]
|
||||
BuildRequest,
|
||||
#[strum(message = "Start MPC-TLS connection with the server")]
|
||||
StartMpcConnection,
|
||||
#[strum(message = "Received response from the server")]
|
||||
ReceivedResponse,
|
||||
#[strum(message = "Parsing response from the server")]
|
||||
ParseResponse,
|
||||
#[strum(message = "Close the connection to the server")]
|
||||
CloseConnection,
|
||||
#[strum(message = "Start notarization")]
|
||||
StartNotarization,
|
||||
#[strum(message = "Commit to data")]
|
||||
Commit,
|
||||
#[strum(message = "Finalize")]
|
||||
Finalize,
|
||||
#[strum(message = "Notarization complete")]
|
||||
NotarizationComplete,
|
||||
#[strum(message = "Create Proof")]
|
||||
CreateProof,
|
||||
}
|
||||
|
||||
fn log_phase(phase: ProverPhases) {
|
||||
info!("tlsn-js {}: {}", phase as u8, phase.get_message().unwrap());
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub async fn prover(
|
||||
target_url_str: &str,
|
||||
val: JsValue,
|
||||
secret_headers: JsValue,
|
||||
secret_body: JsValue,
|
||||
) -> Result<String, JsValue> {
|
||||
debug!("target_url: {}", target_url_str);
|
||||
let target_url = Url::parse(target_url_str)
|
||||
.map_err(|e| JsValue::from_str(&format!("Could not parse target_url: {:?}", e)))?;
|
||||
|
||||
debug!(
|
||||
"target_url.host: {}",
|
||||
target_url
|
||||
.host()
|
||||
.ok_or(JsValue::from_str("Could not get target host"))?
|
||||
);
|
||||
let options: RequestOptions = serde_wasm_bindgen::from_value(val)
|
||||
.map_err(|e| JsValue::from_str(&format!("Could not deserialize options: {:?}", e)))?;
|
||||
debug!("options.notary_url: {}", options.notary_url.as_str());
|
||||
|
||||
let start_time = Instant::now();
|
||||
|
||||
/*
|
||||
* Connect Notary with websocket
|
||||
*/
|
||||
|
||||
let mut opts = RequestInit::new();
|
||||
opts.method("POST");
|
||||
// opts.method("GET");
|
||||
opts.mode(RequestMode::Cors);
|
||||
|
||||
// set headers
|
||||
let headers = Headers::new()
|
||||
.map_err(|e| JsValue::from_str(&format!("Could not create headers: {:?}", e)))?;
|
||||
let notary_url = Url::parse(options.notary_url.as_str())
|
||||
.map_err(|e| JsValue::from_str(&format!("Could not parse notary_url: {:?}", e)))?;
|
||||
let notary_ssl = notary_url.scheme() == "https" || notary_url.scheme() == "wss";
|
||||
let notary_host = notary_url.authority();
|
||||
let notary_path = notary_url.path();
|
||||
let notary_path_str = if notary_path == "/" { "" } else { notary_path };
|
||||
|
||||
headers
|
||||
.append("Host", notary_host)
|
||||
.map_err(|e| JsValue::from_str(&format!("Could not append Host header: {:?}", e)))?;
|
||||
headers
|
||||
.append("Content-Type", "application/json")
|
||||
.map_err(|e| {
|
||||
JsValue::from_str(&format!("Could not append Content-Type header: {:?}", e))
|
||||
})?;
|
||||
opts.headers(&headers);
|
||||
|
||||
info!("notary_host: {}", notary_host);
|
||||
// set body
|
||||
let payload = serde_json::to_string(&NotarizationSessionRequest {
|
||||
client_type: ClientType::Websocket,
|
||||
max_sent_data: options.max_sent_data,
|
||||
max_recv_data: options.max_recv_data,
|
||||
})
|
||||
.map_err(|e| JsValue::from_str(&format!("Could not serialize request: {:?}", e)))?;
|
||||
opts.body(Some(&JsValue::from_str(&payload)));
|
||||
|
||||
// url
|
||||
let url = format!(
|
||||
"{}://{}{}/session",
|
||||
if notary_ssl { "https" } else { "http" },
|
||||
notary_host,
|
||||
notary_path_str
|
||||
);
|
||||
debug!("Request: {}", url);
|
||||
let rust_string = fetch_as_json_string(&url, &opts)
|
||||
.await
|
||||
.map_err(|e| JsValue::from_str(&format!("Could not fetch session: {:?}", e)))?;
|
||||
let notarization_response =
|
||||
serde_json::from_str::<NotarizationSessionResponse>(&rust_string)
|
||||
.map_err(|e| JsValue::from_str(&format!("Could not deserialize response: {:?}", e)))?;
|
||||
debug!("Response: {}", rust_string);
|
||||
|
||||
debug!("Notarization response: {:?}", notarization_response,);
|
||||
let notary_wss_url = format!(
|
||||
"{}://{}{}/notarize?sessionId={}",
|
||||
if notary_ssl { "wss" } else { "ws" },
|
||||
notary_host,
|
||||
notary_path_str,
|
||||
notarization_response.session_id
|
||||
);
|
||||
let (_, notary_ws_stream) = WsMeta::connect(notary_wss_url, None)
|
||||
.await
|
||||
.expect_throw("assume the notary ws connection succeeds");
|
||||
let notary_ws_stream_into = notary_ws_stream.into_io();
|
||||
|
||||
log_phase(ProverPhases::BuildProverConfig);
|
||||
|
||||
let target_host = target_url
|
||||
.host_str()
|
||||
.ok_or(JsValue::from_str("Could not get target host"))?;
|
||||
|
||||
// Basic default prover config
|
||||
let mut builder = ProverConfig::builder();
|
||||
|
||||
if let Some(max_sent_data) = options.max_sent_data {
|
||||
builder.max_sent_data(max_sent_data);
|
||||
}
|
||||
if let Some(max_recv_data) = options.max_recv_data {
|
||||
builder.max_recv_data(max_recv_data);
|
||||
}
|
||||
let config = builder
|
||||
.id(notarization_response.session_id)
|
||||
.server_dns(target_host)
|
||||
.build()
|
||||
.map_err(|e| JsValue::from_str(&format!("Could not build prover config: {:?}", e)))?;
|
||||
|
||||
|
||||
|
||||
// Create a Prover and set it up with the Notary
|
||||
// This will set up the MPC backend prior to connecting to the server.
|
||||
log_phase(ProverPhases::SetUpProver);
|
||||
let prover = Prover::new(config)
|
||||
.setup(notary_ws_stream_into)
|
||||
.await
|
||||
.map_err(|e| JsValue::from_str(&format!("Could not set up prover: {:?}", e)))?;
|
||||
|
||||
/*
|
||||
Connect Application Server with websocket proxy
|
||||
*/
|
||||
log_phase(ProverPhases::ConnectWsProxy);
|
||||
|
||||
let (_, client_ws_stream) = WsMeta::connect(options.websocket_proxy_url, None)
|
||||
.await
|
||||
.expect_throw("assume the client ws connection succeeds");
|
||||
|
||||
// Bind the Prover to the server connection.
|
||||
// The returned `mpc_tls_connection` is an MPC TLS connection to the Server: all data written
|
||||
// to/read from it will be encrypted/decrypted using MPC with the Notary.
|
||||
log_phase(ProverPhases::BindProverToConnection);
|
||||
let (mpc_tls_connection, prover_fut) =
|
||||
prover.connect(client_ws_stream.into_io()).await.unwrap();
|
||||
let mpc_tls_connection = unsafe { FuturesIo::new(mpc_tls_connection) };
|
||||
|
||||
let prover_ctrl = prover_fut.control();
|
||||
|
||||
log_phase(ProverPhases::SpawnProverThread);
|
||||
let (prover_sender, prover_receiver) = oneshot::channel();
|
||||
let handled_prover_fut = async {
|
||||
let result = prover_fut.await;
|
||||
let _ = prover_sender.send(result);
|
||||
};
|
||||
spawn_local(handled_prover_fut);
|
||||
|
||||
// Attach the hyper HTTP client to the TLS connection
|
||||
log_phase(ProverPhases::AttachHttpClient);
|
||||
let (mut request_sender, connection) =
|
||||
hyper::client::conn::http1::handshake(mpc_tls_connection)
|
||||
.await
|
||||
.map_err(|e| JsValue::from_str(&format!("Could not handshake: {:?}", e)))?;
|
||||
|
||||
// Spawn the HTTP task to be run concurrently
|
||||
log_phase(ProverPhases::SpawnHttpTask);
|
||||
let (connection_sender, connection_receiver) = oneshot::channel();
|
||||
let connection_fut = connection.without_shutdown();
|
||||
let handled_connection_fut = async {
|
||||
let result = connection_fut.await;
|
||||
let _ = connection_sender.send(result);
|
||||
};
|
||||
spawn_local(handled_connection_fut);
|
||||
|
||||
log_phase(ProverPhases::BuildRequest);
|
||||
let mut req_with_header = Request::builder()
|
||||
.uri(target_url_str)
|
||||
.method(options.method.as_str());
|
||||
|
||||
for (key, value) in options.headers {
|
||||
info!("adding header: {} - {}", key.as_str(), value.as_str());
|
||||
req_with_header = req_with_header.header(key.as_str(), value.as_str());
|
||||
}
|
||||
|
||||
let req_with_body = if options.body.is_empty() {
|
||||
info!("empty body");
|
||||
req_with_header.body(Full::new(Bytes::default()))
|
||||
} else {
|
||||
info!("added body - {}", options.body.as_str());
|
||||
req_with_header.body(Full::from(options.body))
|
||||
};
|
||||
|
||||
let unwrapped_request = req_with_body
|
||||
.map_err(|e| JsValue::from_str(&format!("Could not build request: {:?}", e)))?;
|
||||
|
||||
log_phase(ProverPhases::StartMpcConnection);
|
||||
|
||||
// Defer decryption of the response.
|
||||
prover_ctrl
|
||||
.defer_decryption()
|
||||
.await
|
||||
.map_err(|e| JsValue::from_str(&format!("failed to enable deferred decryption: {}", e)))?;
|
||||
|
||||
// Send the request to the Server and get a response via the MPC TLS connection
|
||||
let response = request_sender
|
||||
.send_request(unwrapped_request)
|
||||
.await
|
||||
.map_err(|e| JsValue::from_str(&format!("Could not send request: {:?}", e)))?;
|
||||
|
||||
log_phase(ProverPhases::ReceivedResponse);
|
||||
if response.status() != StatusCode::OK {
|
||||
return Err(JsValue::from_str(&format!(
|
||||
"Response status is not OK: {:?}",
|
||||
response.status()
|
||||
)));
|
||||
}
|
||||
|
||||
log_phase(ProverPhases::ParseResponse);
|
||||
// Pretty printing :)
|
||||
let payload = response
|
||||
.into_body()
|
||||
.collect()
|
||||
.await
|
||||
.map_err(|e| JsValue::from_str(&format!("Could not get response body: {:?}", e)))?
|
||||
.to_bytes();
|
||||
let parsed = serde_json::from_str::<serde_json::Value>(&String::from_utf8_lossy(&payload))
|
||||
.map_err(|e| JsValue::from_str(&format!("Could not parse response: {:?}", e)))?;
|
||||
let response_pretty = serde_json::to_string_pretty(&parsed)
|
||||
.map_err(|e| JsValue::from_str(&format!("Could not serialize response: {:?}", e)))?;
|
||||
info!("Response: {}", response_pretty);
|
||||
|
||||
// Close the connection to the server
|
||||
log_phase(ProverPhases::CloseConnection);
|
||||
let mut client_socket = connection_receiver
|
||||
.await
|
||||
.map_err(|e| {
|
||||
JsValue::from_str(&format!(
|
||||
"Could not receive from connection_receiver: {:?}",
|
||||
e
|
||||
))
|
||||
})?
|
||||
.map_err(|e| JsValue::from_str(&format!("Could not get TlsConnection: {:?}", e)))?
|
||||
.io
|
||||
.into_inner();
|
||||
client_socket
|
||||
.close()
|
||||
.await
|
||||
.map_err(|e| JsValue::from_str(&format!("Could not close socket: {:?}", e)))?;
|
||||
|
||||
// The Prover task should be done now, so we can grab it.
|
||||
log_phase(ProverPhases::StartNotarization);
|
||||
let prover = prover_receiver
|
||||
.await
|
||||
.map_err(|e| {
|
||||
JsValue::from_str(&format!("Could not receive from prover_receiver: {:?}", e))
|
||||
})?
|
||||
.map_err(|e| JsValue::from_str(&format!("Could not get Prover: {:?}", e)))?;
|
||||
let mut prover = prover.start_notarize();
|
||||
|
||||
let secret_headers_vecs = string_list_to_bytes_vec(&secret_headers)?;
|
||||
let secret_headers_slices: Vec<&[u8]> = secret_headers_vecs
|
||||
.iter()
|
||||
.map(|vec| vec.as_slice())
|
||||
.collect();
|
||||
|
||||
// Identify the ranges in the transcript that contain revealed_headers
|
||||
let (sent_public_ranges, sent_private_ranges) = find_ranges(
|
||||
prover.sent_transcript().data(),
|
||||
secret_headers_slices.as_slice(),
|
||||
);
|
||||
|
||||
let secret_body_vecs = string_list_to_bytes_vec(&secret_body)?;
|
||||
let secret_body_slices: Vec<&[u8]> =
|
||||
secret_body_vecs.iter().map(|vec| vec.as_slice()).collect();
|
||||
|
||||
// Identify the ranges in the transcript that contain the only data we want to reveal later
|
||||
let (recv_public_ranges, recv_private_ranges) = find_ranges(
|
||||
prover.recv_transcript().data(),
|
||||
secret_body_slices.as_slice(),
|
||||
);
|
||||
|
||||
log_phase(ProverPhases::Commit);
|
||||
|
||||
let _recv_len = prover.recv_transcript().data().len();
|
||||
|
||||
let builder = prover.commitment_builder();
|
||||
|
||||
// Commit to the outbound and inbound transcript, isolating the data that contain secrets
|
||||
let sent_pub_commitment_ids = sent_public_ranges
|
||||
.iter()
|
||||
.map(|range| {
|
||||
builder.commit_sent(range).map_err(|e| {
|
||||
JsValue::from_str(&format!("Error committing sent pub range: {:?}", e))
|
||||
})
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
sent_private_ranges.iter().try_for_each(|range| {
|
||||
builder
|
||||
.commit_sent(range)
|
||||
.map_err(|e| {
|
||||
JsValue::from_str(&format!("Error committing sent private range: {:?}", e))
|
||||
})
|
||||
.map(|_| ())
|
||||
})?;
|
||||
|
||||
let recv_pub_commitment_ids = recv_public_ranges
|
||||
.iter()
|
||||
.map(|range| {
|
||||
builder.commit_recv(range).map_err(|e| {
|
||||
JsValue::from_str(&format!("Error committing recv public ranges: {:?}", e))
|
||||
})
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
recv_private_ranges.iter().try_for_each(|range| {
|
||||
builder
|
||||
.commit_recv(range)
|
||||
.map_err(|e| {
|
||||
JsValue::from_str(&format!("Error committing recv private range: {:?}", e))
|
||||
})
|
||||
.map(|_| ())
|
||||
})?;
|
||||
|
||||
// Finalize, returning the notarized session
|
||||
log_phase(ProverPhases::Finalize);
|
||||
let notarized_session = prover
|
||||
.finalize()
|
||||
.await
|
||||
.map_err(|e| JsValue::from_str(&format!("Error finalizing prover: {:?}", e)))?;
|
||||
|
||||
log_phase(ProverPhases::NotarizationComplete);
|
||||
|
||||
// Create a proof for all committed data in this session
|
||||
log_phase(ProverPhases::CreateProof);
|
||||
let session_proof = notarized_session.session_proof();
|
||||
|
||||
let mut proof_builder = notarized_session.data().build_substrings_proof();
|
||||
|
||||
// Reveal everything except the redacted stuff (which for the response it's everything except the screen_name)
|
||||
sent_pub_commitment_ids
|
||||
.iter()
|
||||
.chain(recv_pub_commitment_ids.iter())
|
||||
.try_for_each(|id| {
|
||||
proof_builder
|
||||
.reveal_by_id(*id)
|
||||
.map_err(|e| JsValue::from_str(&format!("Could not reveal commitment: {:?}", e)))
|
||||
.map(|_| ())
|
||||
})?;
|
||||
|
||||
let substrings_proof = proof_builder
|
||||
.build()
|
||||
.map_err(|e| JsValue::from_str(&format!("Could not build proof: {:?}", e)))?;
|
||||
|
||||
let proof = TlsProof {
|
||||
session: session_proof,
|
||||
substrings: substrings_proof,
|
||||
};
|
||||
|
||||
let res = serde_json::to_string_pretty(&proof)
|
||||
.map_err(|e| JsValue::from_str(&format!("Could not serialize proof: {:?}", e)))?;
|
||||
|
||||
let duration = start_time.elapsed();
|
||||
info!("!@# request took {} seconds", duration.as_secs());
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
/// Find the ranges of the public and private parts of a sequence.
|
||||
///
|
||||
/// Returns a tuple of `(public, private)` ranges.
|
||||
fn find_ranges(seq: &[u8], private_seq: &[&[u8]]) -> (Vec<Range<usize>>, Vec<Range<usize>>) {
|
||||
let mut private_ranges = Vec::new();
|
||||
for s in private_seq {
|
||||
for (idx, w) in seq.windows(s.len()).enumerate() {
|
||||
if w == *s {
|
||||
private_ranges.push(idx..(idx + w.len()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut sorted_ranges = private_ranges.clone();
|
||||
sorted_ranges.sort_by_key(|r| r.start);
|
||||
|
||||
let mut public_ranges = Vec::new();
|
||||
let mut last_end = 0;
|
||||
for r in sorted_ranges {
|
||||
if r.start > last_end {
|
||||
public_ranges.push(last_end..r.start);
|
||||
}
|
||||
last_end = r.end;
|
||||
}
|
||||
|
||||
if last_end < seq.len() {
|
||||
public_ranges.push(last_end..seq.len());
|
||||
}
|
||||
|
||||
(public_ranges, private_ranges)
|
||||
}
|
||||
|
||||
fn string_list_to_bytes_vec(secrets: &JsValue) -> Result<Vec<Vec<u8>>, JsValue> {
|
||||
let array: Array = Array::from(secrets);
|
||||
let length = array.length();
|
||||
let mut byte_slices: Vec<Vec<u8>> = Vec::new();
|
||||
|
||||
for i in 0..length {
|
||||
let secret_js: JsValue = array.get(i);
|
||||
let secret_str: String = secret_js
|
||||
.as_string()
|
||||
.ok_or(JsValue::from_str("Could not convert secret to string"))?;
|
||||
let secret_bytes = secret_str.into_bytes();
|
||||
byte_slices.push(secret_bytes);
|
||||
}
|
||||
Ok(byte_slices)
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// Requestion Options of Fetch API
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RequestOptions {
|
||||
pub method: String, // *GET, POST, PUT, DELETE, etc.
|
||||
// pub mode: String, // no-cors, *cors, same-origin
|
||||
// pub cache: String, // *default, no-cache, reload, force-cache, only-if-cached
|
||||
// pub credentials: String, // include, *same-origin, omit
|
||||
pub headers: HashMap<String, String>,
|
||||
// pub redirect: String, // manual, *follow, error
|
||||
// pub referrer_policy: String, // no-referrer, *no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin, strict-origin, strict-origin-when-cross-origin, unsafe-url
|
||||
pub body: String, // body data type must match "Content-Type" header
|
||||
pub max_sent_data: Option<usize>,
|
||||
pub max_recv_data: Option<usize>,
|
||||
pub notary_url: String,
|
||||
pub websocket_proxy_url: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct VerifyResult {
|
||||
pub server_name: String,
|
||||
pub time: u64,
|
||||
pub sent: String,
|
||||
pub recv: String,
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Response object of the /session API
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct NotarizationSessionResponse {
|
||||
/// Unique session id that is generated by notary and shared to prover
|
||||
pub session_id: String,
|
||||
}
|
||||
|
||||
/// Request object of the /session API
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct NotarizationSessionRequest {
|
||||
pub client_type: ClientType,
|
||||
/// Maximum number of bytes that can be sent in bytes.
|
||||
pub max_sent_data: Option<usize>,
|
||||
/// Maximum number of bytes that can be received in bytes.
|
||||
pub max_recv_data: Option<usize>,
|
||||
}
|
||||
|
||||
/// Types of client that the prover is using
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub enum ClientType {
|
||||
/// Client that has access to the transport layer
|
||||
Tcp,
|
||||
/// Client that cannot directly access transport layer, e.g. browser extension
|
||||
Websocket,
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
use tracing::info;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
use crate::request_opt::VerifyResult;
|
||||
|
||||
use elliptic_curve::pkcs8::DecodePublicKey;
|
||||
use std::time::Duration;
|
||||
use tlsn_core::proof::{SessionProof, TlsProof};
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub async fn verify(proof: &str, notary_pubkey_str: &str) -> Result<String, JsValue> {
|
||||
let proof: TlsProof = serde_json::from_str(proof)
|
||||
.map_err(|e| JsValue::from_str(&format!("Could not deserialize proof: {:?}", e)))?;
|
||||
|
||||
let TlsProof {
|
||||
// The session proof establishes the identity of the server and the commitments
|
||||
// to the TLS transcript.
|
||||
session,
|
||||
// The substrings proof proves select portions of the transcript, while redacting
|
||||
// anything the Prover chose not to disclose.
|
||||
substrings,
|
||||
} = proof;
|
||||
|
||||
info!(
|
||||
"!@# notary_pubkey {}, {}",
|
||||
notary_pubkey_str,
|
||||
notary_pubkey_str.len()
|
||||
);
|
||||
session
|
||||
.verify_with_default_cert_verifier(get_notary_pubkey(notary_pubkey_str)?)
|
||||
.map_err(|e| JsValue::from_str(&format!("Session verification failed: {:?}", e)))?;
|
||||
|
||||
let SessionProof {
|
||||
// The session header that was signed by the Notary is a succinct commitment to the TLS transcript.
|
||||
header,
|
||||
// This is the server name, checked against the certificate chain shared in the TLS handshake.
|
||||
session_info,
|
||||
..
|
||||
} = session;
|
||||
|
||||
// The time at which the session was recorded
|
||||
let time = chrono::DateTime::UNIX_EPOCH + Duration::from_secs(header.time());
|
||||
|
||||
// Verify the substrings proof against the session header.
|
||||
//
|
||||
// This returns the redacted transcripts
|
||||
let (mut sent, mut recv) = substrings
|
||||
.verify(&header)
|
||||
.map_err(|e| JsValue::from_str(&format!("Could not verify substrings: {:?}", e)))?;
|
||||
|
||||
// Replace the bytes which the Prover chose not to disclose with 'X'
|
||||
sent.set_redacted(b'X');
|
||||
recv.set_redacted(b'X');
|
||||
|
||||
info!("-------------------------------------------------------------------");
|
||||
info!(
|
||||
"Successfully verified that the bytes below came from a session with {:?} at {}.",
|
||||
session_info.server_name, time
|
||||
);
|
||||
info!("Note that the bytes which the Prover chose not to disclose are shown as X.");
|
||||
info!("Bytes sent:");
|
||||
info!(
|
||||
"{}",
|
||||
String::from_utf8(sent.data().to_vec()).map_err(|e| JsValue::from_str(&format!(
|
||||
"Could not convert sent data to string: {:?}",
|
||||
e
|
||||
)))?
|
||||
);
|
||||
info!("Bytes received:");
|
||||
info!(
|
||||
"{}",
|
||||
String::from_utf8(recv.data().to_vec()).map_err(|e| JsValue::from_str(&format!(
|
||||
"Could not convert recv data to string: {:?}",
|
||||
e
|
||||
)))?
|
||||
);
|
||||
info!("-------------------------------------------------------------------");
|
||||
|
||||
let result = VerifyResult {
|
||||
server_name: String::from(session_info.server_name.as_str()),
|
||||
time: header.time(),
|
||||
sent: String::from_utf8(sent.data().to_vec()).map_err(|e| {
|
||||
JsValue::from_str(&format!("Could not convert sent data to string: {:?}", e))
|
||||
})?,
|
||||
recv: String::from_utf8(recv.data().to_vec()).map_err(|e| {
|
||||
JsValue::from_str(&format!("Could not convert recv data to string: {:?}", e))
|
||||
})?,
|
||||
};
|
||||
let res = serde_json::to_string_pretty(&result)
|
||||
.map_err(|e| JsValue::from_str(&format!("Could not serialize result: {:?}", e)))?;
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn print_type_of<T: ?Sized>(_: &T) {
|
||||
info!("{}", std::any::type_name::<T>());
|
||||
}
|
||||
|
||||
/// Returns a Notary pubkey trusted by this Verifier
|
||||
fn get_notary_pubkey(pubkey: &str) -> Result<p256::PublicKey, JsValue> {
|
||||
p256::PublicKey::from_public_key_pem(pubkey)
|
||||
.map_err(|e| JsValue::from_str(&format!("Could not get notary pubkey: {:?}", e)))
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
//! Test suite for the Web and headless browsers.
|
||||
|
||||
#![cfg(target_arch = "wasm32")]
|
||||
|
||||
extern crate wasm_bindgen_test;
|
||||
use serde_json::Value;
|
||||
use std::{collections::HashMap, str};
|
||||
use wasm_bindgen_test::*;
|
||||
use web_sys::RequestInit;
|
||||
|
||||
extern crate tlsn_extension_rs;
|
||||
use tlsn_extension_rs::*;
|
||||
|
||||
macro_rules! log {
|
||||
( $( $t:tt )* ) => {
|
||||
web_sys::console::log_1(&format!( $( $t )* ).into());
|
||||
}
|
||||
}
|
||||
|
||||
wasm_bindgen_test_configure!(run_in_browser);
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
async fn test_fetch() {
|
||||
let url = "https://swapi.info/api/";
|
||||
let mut opts = RequestInit::new();
|
||||
opts.method("GET");
|
||||
|
||||
let rust_string: String = tlsn_extension_rs::fetch_as_json_string(&url, &opts)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert!(rust_string.contains("starships"));
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
async fn verify() {
|
||||
let pem = str::from_utf8(include_bytes!("../../../test/assets/notary.pem")).unwrap();
|
||||
let proof = str::from_utf8(include_bytes!(
|
||||
"../../../test/assets/simple_proof_redacted.json"
|
||||
))
|
||||
.unwrap();
|
||||
let m: HashMap<String, Value> = serde_json::from_str(
|
||||
&str::from_utf8(include_bytes!(
|
||||
"../../../test/assets/simple_proof_expected.json"
|
||||
))
|
||||
.unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let result = tlsn_extension_rs::verify(proof, pem).await.expect("result");
|
||||
|
||||
log!("result: {}", &result);
|
||||
|
||||
let r: VerifyResult = serde_json::from_str::<VerifyResult>(&result).unwrap();
|
||||
|
||||
assert_eq!(r.server_name, m["serverName"]);
|
||||
assert!(r.recv.contains("<title>XXXXXXXXXXXXXX</title>"));
|
||||
assert_eq!(r.time, m["time"].as_u64().unwrap());
|
||||
assert_eq!(r.sent, m["sent"].as_str().unwrap());
|
||||
assert_eq!(r.recv, m["recv"].as_str().unwrap());
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"moz:firefoxOptions": {
|
||||
"prefs": {
|
||||
"media.navigator.streams.fake": true,
|
||||
"media.navigator.permission.disabled": true
|
||||
},
|
||||
"args": []
|
||||
},
|
||||
"goog:chromeOptions": {
|
||||
"args": [
|
||||
"--use-fake-device-for-media-stream",
|
||||
"--use-fake-ui-for-media-stream",
|
||||
"--headless",
|
||||
"--disable-gpu",
|
||||
"--no-sandbox",
|
||||
"--disable-dev-shm-usage",
|
||||
"--window-size=1280,800"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -30,9 +30,9 @@ module.exports = [
|
||||
{
|
||||
mode: isProd ? 'production' : 'development',
|
||||
entry: {
|
||||
index: path.join(__dirname, 'src', 'index.ts'),
|
||||
lib: path.join(__dirname, 'src', 'lib.ts'),
|
||||
},
|
||||
target: 'web',
|
||||
target: 'webworker',
|
||||
devtool: 'source-map',
|
||||
resolve: {
|
||||
extensions: ['.ts', '.js'],
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
const webpack = require('webpack');
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
const path = require('path');
|
||||
const { compilerOptions } = require('./tsconfig.json');
|
||||
|
||||
const isProd = process.env.NODE_ENV === 'production';
|
||||
|
||||
@@ -49,25 +48,25 @@ module.exports = [
|
||||
devtool: 'source-map',
|
||||
resolve: {
|
||||
extensions: ['.ts', '.tsx', '.js', '.jsx', '.png', '.svg'],
|
||||
modules: [
|
||||
path.resolve('./node_modules'),
|
||||
path.resolve(__dirname, compilerOptions.baseUrl),
|
||||
],
|
||||
fallback: {
|
||||
browserify: require.resolve('browserify'),
|
||||
stream: require.resolve('stream-browserify'),
|
||||
path: require.resolve('path-browserify'),
|
||||
crypto: require.resolve('crypto-browserify'),
|
||||
os: require.resolve('os-browserify/browser'),
|
||||
http: require.resolve('stream-http'),
|
||||
https: require.resolve('https-browserify'),
|
||||
assert: require.resolve('assert/'),
|
||||
events: require.resolve('events/'),
|
||||
'ansi-html-community': require.resolve('ansi-html-community'),
|
||||
'html-entities': require.resolve('html-entities'),
|
||||
constants: false,
|
||||
fs: false,
|
||||
},
|
||||
// modules: [
|
||||
// path.resolve('./node_modules'),
|
||||
// path.resolve(__dirname, compilerOptions.baseUrl),
|
||||
// ],
|
||||
// fallback: {
|
||||
// browserify: require.resolve('browserify'),
|
||||
// stream: require.resolve('stream-browserify'),
|
||||
// path: require.resolve('path-browserify'),
|
||||
// crypto: require.resolve('crypto-browserify'),
|
||||
// os: require.resolve('os-browserify/browser'),
|
||||
// http: require.resolve('stream-http'),
|
||||
// https: require.resolve('https-browserify'),
|
||||
// assert: require.resolve('assert/'),
|
||||
// events: require.resolve('events/'),
|
||||
// 'ansi-html-community': require.resolve('ansi-html-community'),
|
||||
// 'html-entities': require.resolve('html-entities'),
|
||||
// constants: false,
|
||||
// fs: false,
|
||||
// },
|
||||
},
|
||||
module: {
|
||||
rules: [...rules, ...rendererRules],
|
||||
|
||||
Reference in New Issue
Block a user