* feat: webp2p demo ui update

* feat: webp2p demo ui update

* fix: webp2p webpack

* chore: linting

* feat: styled react-ts demo, fixed interactive demo bug

* chore: cleanup

* Improved React demo

* fix: unnessecary dependencies in package

* Improved interactive demo

* Fixed readme

* Removed unused type

---------

Co-authored-by: Hendrik Eeckhaut <hendrik@eeckhaut.org>
This commit is contained in:
Tanner
2025-02-05 11:21:13 -08:00
committed by GitHub
parent 14eee4a2e3
commit 0133efe529
19 changed files with 4408 additions and 1788 deletions

View File

@@ -9,9 +9,13 @@
"author": "",
"license": "ISC",
"dependencies": {
"@fortawesome/fontawesome-free": "^6.7.1",
"css-loader": "^7.1.2",
"postcss-loader": "^8.1.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-loader-spinner": "^6.1.6",
"style-loader": "^4.0.0",
"tlsn-js": "file:../../.."
},
"devDependencies": {
@@ -20,7 +24,12 @@
"babel-loader": "^9.1.3",
"copy-webpack-plugin": "^11.0.0",
"html-webpack-plugin": "^5.5.0",
"postcss": "^8.4.49",
"postcss-preset-env": "^10.1.1",
"sass": "^1.82.0",
"sass-loader": "^16.0.4",
"source-map-loader": "^5.0.0",
"tailwindcss": "^3.4.16",
"ts-loader": "^9.4.2",
"typescript": "^4.9.4",
"webpack": "^5.75.0",

View File

@@ -0,0 +1,4 @@
const tailwindcss = require("tailwindcss");
module.exports = {
plugins: ["postcss-preset-env", tailwindcss],
};

View File

@@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

View File

@@ -4,6 +4,7 @@ import * as Comlink from 'comlink';
import { Watch } from 'react-loader-spinner';
import { Prover as TProver } from 'tlsn-js';
import { type Method } from 'tlsn-wasm';
import './app.scss';
const { init, Prover }: any = Comlink.wrap(
new Worker(new URL('./worker.ts', import.meta.url)),
@@ -14,6 +15,11 @@ const root = createRoot(container!);
root.render(<App />);
const serverUrl = 'https://swapi.dev/api/people/1';
// let websocketProxyUrl = 'wss://notary.pse.dev/proxy';
const websocketProxyUrl = 'ws://localhost:55688';
const verifierProxyUrl = 'ws://localhost:9816/verify';
function App(): ReactElement {
const [processing, setProcessing] = useState(false);
const [result, setResult] = useState<string | null>(null);
@@ -21,16 +27,14 @@ function App(): ReactElement {
const onClick = useCallback(async () => {
setProcessing(true);
const url = 'https://swapi.dev/api/people/1';
const url = serverUrl;
const method: Method = 'GET';
const headers = {
secret: "TLSNotary's private key",
'Content-Type': 'application/json',
};
const body = {};
// let websocketProxyUrl = 'wss://notary.pse.dev/proxy';
const websocketProxyUrl = 'ws://localhost:55688';
const verifierProxyUrl = 'ws://localhost:9816/verify';
const hostname = new URL(url).hostname;
let prover: TProver;
@@ -98,7 +102,6 @@ function App(): ReactElement {
console.log('Start reveal:', reveal);
await prover.reveal(reveal);
console.timeEnd('reveal');
} catch (error) {
console.dir(error);
console.error('Error during data reveal:', error);
@@ -114,50 +117,92 @@ function App(): ReactElement {
received: transcript.recv,
});
setResult('Unredacted data successfully revealed to Verifier. Check the Verifier\'s console output to see what exactly was shared and revealed.');
setResult(
"Unredacted data successfully revealed to Verifier. Check the Verifier's console output to see what exactly was shared and revealed.",
);
setProcessing(false);
}, [setResult, setProcessing]);
return (
<div>
<h1>TLSNotary interactive prover demo</h1>
<div>
Before clicking the start button, make sure the{' '}
<i>interactive verifier</i> and the <i>web socket proxy</i> are running.
Check the <a href="README.md">README</a> for the details.
<div className="flex flex-col items-center justify-center w-full min-h-screen bg-gray-50 p-4">
<h1 className="text-4xl font-bold text-slate-500 mb-2">TLSNotary</h1>
<span className="text-lg text-gray-600 mb-4">
Interactive Prover Demo
</span>
<div className="text-center text-gray-700 mb-6">
<p>
Before clicking the <span className="font-semibold">Start</span> button,
make sure the <i>interactive verifier</i> and the{' '}
<i>web socket proxy</i> are running.</p>
<p>Check the{' '}
<a href="README.md" className="text-blue-600 hover:underline">
README
</a>{' '}
for the details.
</p>
<table className="text-left table-auto w-full mt-4">
<thead>
<tr>
<th className="px-4 py-2 text-left">Demo Settings</th>
<th className="px-4 py-2 text-left">URL</th>
</tr>
</thead>
<tbody>
<tr>
<td className="border px-4 py-2">Server</td>
<td className="border px-4 py-2">{serverUrl}</td>
</tr>
<tr>
<td className="border px-4 py-2">Verifier</td>
<td className="border px-4 py-2">{verifierProxyUrl}</td>
</tr>
<tr>
<td className="border px-4 py-2">WebSocket Proxy</td>
<td className="border px-4 py-2">{websocketProxyUrl}</td>
</tr>
</tbody>
</table>
</div>
<br />
<button onClick={!processing ? onClick : undefined} disabled={processing}>
<button
onClick={!processing ? onClick : undefined}
disabled={processing}
className={`px-6 py-2 rounded-lg font-medium text-white
${processing ? 'bg-slate-400 cursor-not-allowed' : 'bg-slate-600 hover:bg-slate-700'}
`}
>
Start Prover
</button>
<br />
<div>
<b>Proof: </b>
<div className="mt-6 w-full max-w-3xl text-center">
<b className="text-lg font-medium text-gray-800">Proof: </b>
{!processing && !result ? (
<i>not started yet</i>
<i className="text-gray-500">Not started yet</i>
) : !result ? (
<>
Proving data from swapi...
<div className="flex flex-col items-center justify-center">
<p className="text-gray-700 mb-2">Proving data from swapi...</p>
<Watch
visible={true}
height="40"
width="40"
radius="48"
color="#000000"
color="#4A5568"
ariaLabel="watch-loading"
wrapperStyle={{}}
wrapperClass=""
/>
Open <i>Developer tools</i> to follow progress
</>
<p className="text-sm text-gray-500 mt-2">
Open <i>Developer Tools</i> to follow progress
</p>
</div>
) : (
<>
<pre>{JSON.stringify(result, null, 2)}</pre>
</>
<div className="bg-gray-100 border border-gray-300 p-4 rounded-lg mt-4">
<pre className="text-left text-sm text-gray-800 whitespace-pre-wrap overflow-auto">
{JSON.stringify(result, null, 2)}
</pre>
</div>
)}
</div>
</div>

View File

@@ -0,0 +1,12 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ['./src/**/*.{js,jsx,ts,tsx}'],
theme: {
extend: {
colors: {
primary: '#243f5f',
},
},
},
plugins: [],
};

View File

@@ -20,7 +20,7 @@
"jsx": "react"
},
"include": [
"app.tsx",
"worker.ts"
"src/app.tsx",
"src/worker.ts"
]
}
}

View File

@@ -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'),
},
@@ -68,6 +71,29 @@ var options = {
],
exclude: /node_modules/,
},
{
// look for .css or .scss files
test: /\.(css|scss)$/,
// in the `web` directory
use: [
{
loader: 'style-loader',
},
{
loader: 'css-loader',
options: { importLoaders: 1 },
},
{
loader: 'postcss-loader',
},
{
loader: 'sass-loader',
options: {
sourceMap: true,
},
},
],
},
],
},
resolve: {
@@ -75,6 +101,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({
@@ -87,15 +118,16 @@ var options = {
],
}),
new CopyWebpackPlugin({
patterns: [
{ from: '../README.md', to: 'README.md' },
],
patterns: [{ from: '../README.md', to: 'README.md' }],
}),
new HtmlWebpackPlugin({
template: path.join(__dirname, 'index.ejs'),
filename: 'index.html',
cache: false,
}),
new webpack.ProvidePlugin({
process: 'process/browser',
}),
new webpack.ProvidePlugin({
Buffer: ['buffer', 'Buffer'],
}),
@@ -105,13 +137,16 @@ var options = {
// - https://github.com/GoogleChromeLabs/wasm-bindgen-rayon#setting-up
// - https://web.dev/i18n/en/coop-coep/
devServer: {
port: 3456,
port: 8080,
host: 'localhost',
hot: true,
headers: {
'Cross-Origin-Embedder-Policy': 'require-corp',
'Cross-Origin-Opener-Policy': 'same-origin',
},
client: {
overlay: false,
},
},
};

View File

@@ -11,9 +11,16 @@
"license": "ISC",
"dependencies": {
"comlink": "^4.4.1",
"css-loader": "^7.1.2",
"postcss": "^8.4.49",
"postcss-loader": "^8.1.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-loader-spinner": "^6.1.6",
"sass": "^1.83.1",
"sass-loader": "^16.0.4",
"style-loader": "^4.0.0",
"tailwindcss": "^3.4.17",
"tlsn-js": "../../"
},
"devDependencies": {
@@ -23,6 +30,7 @@
"copy-webpack-plugin": "^11.0.0",
"crypto-browserify": "^3.12.0",
"html-webpack-plugin": "^5.5.0",
"postcss-preset-env": "^10.1.3",
"source-map-loader": "^5.0.0",
"stream-browserify": "^3.0.0",
"ts-loader": "^9.4.2",

View File

@@ -0,0 +1,4 @@
const tailwindcss = require("tailwindcss");
module.exports = {
plugins: ["postcss-preset-env", tailwindcss],
};

View File

@@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

View File

@@ -10,6 +10,7 @@ import {
Transcript,
} from 'tlsn-js';
import { PresentationJSON } from 'tlsn-js/build/types';
import './app.scss';
const { init, Prover, Presentation }: any = Comlink.wrap(
new Worker(new URL('./worker.ts', import.meta.url)),
@@ -20,6 +21,14 @@ const root = createRoot(container!);
root.render(<App />);
const local = true; // Toggle between local and remote notary
const notaryUrl = local ? 'http://localhost:7047' : 'https://notary.pse.dev/v0.1.0-alpha.7';
const websocketProxyUrl = local ? 'ws://localhost:55688' : 'wss://notary.pse.dev/proxy?token=swapi.dev';
const loggingLevel = 'Info'; // https://github.com/tlsnotary/tlsn/blob/main/crates/wasm/src/log.rs#L8
const serverUrl = 'https://swapi.dev/api/people/1';
const serverDns = 'swapi.dev';
function App(): ReactElement {
const [initialized, setInitialized] = useState(false);
const [processing, setProcessing] = useState(false);
@@ -29,22 +38,24 @@ function App(): ReactElement {
useEffect(() => {
(async () => {
await init({ loggingLevel: 'Info' });
await init({ loggingLevel: loggingLevel });
setInitialized(true);
})();
}, []);
const onClick = useCallback(async () => {
setProcessing(true);
const notary = NotaryServer.from(`http://localhost:7047`);
const notary = NotaryServer.from(notaryUrl);
console.time('submit');
const prover = (await new Prover({
serverDns: 'swapi.dev',
serverDns: serverDns,
maxRecvData: 2048,
})) as TProver;
await prover.setup(await notary.sessionUrl());
const resp = await prover.sendRequest('ws://localhost:55688', {
url: 'https://swapi.dev/api/people/1',
const resp = await prover.sendRequest(websocketProxyUrl, {
url: serverUrl,
method: 'GET',
headers: {
'Content-Type': 'application/json',
@@ -98,13 +109,14 @@ function App(): ReactElement {
const onAltClick = useCallback(async () => {
setProcessing(true);
const proof = await (Prover.notarize as typeof TProver.notarize)({
notaryUrl: 'http://localhost:7047',
websocketProxyUrl: 'ws://localhost:55688',
url: 'https://swapi.dev/api/people/1',
notaryUrl: notaryUrl,
websocketProxyUrl: websocketProxyUrl,
url: serverUrl,
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
body: {
hello: 'world',
one: 1,
@@ -124,7 +136,7 @@ function App(): ReactElement {
const proof = (await new Presentation(
presentationJSON.data,
)) as TPresentation;
const notary = NotaryServer.from(`http://localhost:7047`);
const notary = NotaryServer.from(notaryUrl);
const notaryKey = await notary.publicKey('hex');
const verifierOutput = await proof.verify();
const transcript = new Transcript({
@@ -146,60 +158,117 @@ function App(): ReactElement {
}, [presentationJSON, setResult]);
return (
<div>
<div>
<button
onClick={!processing ? onClick : undefined}
disabled={processing || !initialized}
>
Start Demo (Normal config)
</button>
<div className="bg-slate-100 min-h-screen p-6 text-slate-800 flex flex-col items-center">
<h1 className="text-2xl font-bold mb-6 text-slate-700">
TLSNotary React TypeScript Demo{' '}
</h1>
<div className="mb-4 text-base font-light max-w-2xl">
<p>
This demo showcases how to use TLSNotary in a React/TypeScript app with the tlsn-js library.
We will fetch JSON data from the Star Wars API, notarize the TLS request using TLSNotary,
and verify the proof. The demo runs entirely in the browser.
</p>
<p>
<a href="https://docs.tlsnotary.org/quick_start/tlsn-js.html" className="text-blue-500 hover:underline">
More info
</a>
</p>
<table className="table-auto w-full mt-4">
<thead>
<tr>
<th className="px-4 py-2 text-left">Demo Settings</th>
<th className="px-4 py-2 text-left">URL</th>
</tr>
</thead>
<tbody>
<tr>
<td className="border px-4 py-2">Server</td>
<td className="border px-4 py-2">{serverUrl}</td>
</tr>
<tr>
<td className="border px-4 py-2">Notary Server</td>
<td className="border px-4 py-2">{notaryUrl}</td>
</tr>
<tr>
<td className="border px-4 py-2">WebSocket Proxy</td>
<td className="border px-4 py-2">{websocketProxyUrl}</td>
</tr>
</tbody>
</table>
</div>
<div>
<button
onClick={!processing ? onAltClick : undefined}
disabled={processing || !initialized}
>
Start Demo 2 (With helper method)
</button>
<div className="mb-4">
<p className="mb-2 text-base font-light">
There are two versions of the demo: one with a normal config and one with a helper method.
</p>
<div className="flex justify-center gap-4">
<button
onClick={!processing ? onClick : undefined}
disabled={processing || !initialized}
className={`px-4 py-2 rounded-md text-white shadow-md font-semibold
${processing || !initialized ? 'bg-slate-400 cursor-not-allowed' : 'bg-slate-600 hover:bg-slate-700'}`}
>
Start Demo (Normal config)
</button>
<button
onClick={!processing ? onAltClick : undefined}
disabled={processing || !initialized}
className={`px-4 py-2 rounded-md text-white shadow-md font-semibold
${processing || !initialized ? 'bg-slate-400 cursor-not-allowed' : 'bg-slate-600 hover:bg-slate-700'}`}
>
Start Demo 2 (With helper method)
</button>
</div>
</div>
<div>
<b>Proof: </b>
{!processing && !presentationJSON ? (
<i>not started</i>
) : !presentationJSON ? (
<>
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(presentationJSON, null, 2)}</pre>
{processing && (
<div className="mt-6 flex justify-center items-center">
<Watch
visible={true}
height="40"
width="40"
radius="48"
color="#1E293B"
ariaLabel="watch-loading"
wrapperStyle={{}}
wrapperClass=""
/>
</div>
)}
<div className="flex flex-col sm:flex-row gap-6 w-full max-w-4xl">
<div className="flex-1 bg-slate-50 border border-slate-200 rounded p-4">
<b className="text-slate-600">Proof: </b>
{!processing && !presentationJSON ? (
<i className="text-slate-500">not started</i>
) : !presentationJSON ? (
<div className="flex flex-col items-start space-y-2">
<span>Proving data from {serverDns}...</span>
<span className="text-slate-500">
Open <i>Developer tools</i> to follow progress
</span>
</div>
) : (
<details className="bg-slate-50 border border-slate-200 rounded p-2">
<summary className="cursor-pointer text-slate-600">
View Proof
</summary>
<pre className="mt-2 p-2 bg-slate-100 rounded text-sm text-slate-800">
{JSON.stringify(presentationJSON, null, 2)}
</pre>
</details>
</>
)}
</div>
<div>
<b>Verification: </b>
{!presentationJSON ? (
<i>not started</i>
) : !result ? (
<i>verifying</i>
) : (
<pre>{JSON.stringify(result, null, 2)}</pre>
)}
)}
</div>
<div className="flex-1 bg-slate-50 border border-slate-200 rounded p-4">
<b className="text-slate-600">Verification: </b>
{!presentationJSON ? (
<i className="text-slate-500">not started</i>
) : !result ? (
<i className="text-slate-500">verifying</i>
) : (
<pre className="mt-2 p-2 bg-slate-100 rounded text-sm text-slate-800">
{JSON.stringify(result, null, 2)}
</pre>
)}
</div>
</div>
</div>
);

View File

@@ -0,0 +1,12 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ['./src/**/*.{js,jsx,ts,tsx}'],
theme: {
extend: {
colors: {
primary: '#243f5f',
},
},
},
plugins: [],
};

View File

@@ -71,6 +71,29 @@ var options = {
],
exclude: /node_modules/,
},
{
// look for .css or .scss files
test: /\.(css|scss)$/,
// in the `web` directory
use: [
{
loader: 'style-loader',
},
{
loader: 'css-loader',
options: { importLoaders: 1 },
},
{
loader: 'postcss-loader',
},
{
loader: 'sass-loader',
options: {
sourceMap: true,
},
},
],
},
],
},
resolve: {
@@ -111,10 +134,16 @@ var options = {
// - https://github.com/GoogleChromeLabs/wasm-bindgen-rayon#setting-up
// - https://web.dev/i18n/en/coop-coep/
devServer: {
port: 8080,
host: 'localhost',
hot: true,
headers: {
'Cross-Origin-Embedder-Policy': 'require-corp',
'Cross-Origin-Opener-Policy': 'same-origin',
},
client: {
overlay: false,
},
},
};

View File

@@ -192,49 +192,60 @@ function App(): ReactElement {
}, [ready]);
return (
<div className="w-screen h-screen flex flex-col overflow-hidden">
<div className="w-full p-2.5 bg-slate-200 mb-5 flex-shrink-0">
<h1>Web-to-Web P2P Demo</h1>
<p>
<div className="w-screen h-screen flex flex-col bg-slate-100 overflow-hidden">
<div className="w-full p-4 bg-slate-800 text-white flex-shrink-0 shadow-md">
<h1 className="text-xl font-bold">Web-to-Web P2P Demo</h1>
<p className="text-sm mt-1">
This demo showcases peer-to-peer communication between a web prover
and a web verifier using TLSNotary. The prover fetches data from{' '}
<a href="https://swapi.dev" target="_blank" rel="noopener noreferrer">
<a
href="https://swapi.dev"
target="_blank"
rel="noopener noreferrer"
className="underline text-blue-400 hover:text-blue-300"
>
swapi.dev
</a>{' '}
and proves it to the verifier.
</p>
</div>
<div className="grid grid-rows-2 grid-cols-2 p-2 gap-2 flex-grow">
<div className="flex flex-col items-center border border-slate-300 bg-slate-50 rounded row-span-1 col-span-1 p-4 gap-2">
<div className="font-semibold">Prover</div>
<div className="flex flex-col text-sm bg-white border border-slate-300 w-full flex-grow cursor-text py-1 overflow-y-auto">
<div className="grid grid-rows-2 grid-cols-2 gap-4 p-4 flex-grow">
<div className="flex flex-col items-center border border-slate-300 bg-white rounded-lg shadow-md row-span-1 col-span-1 p-4 gap-2">
<div className="font-semibold text-slate-700 text-lg">Prover</div>
<div className="flex flex-col text-sm bg-slate-50 border border-slate-200 w-full flex-grow py-2 overflow-y-auto rounded">
{proverMessages.map((m, index) => (
<span key={index} className="px-2 py-1 text-slate-600 break-all">
{m}
</span>
))}
</div>
</div>
<div className="flex flex-col items-center border border-slate-300 bg-slate-100 rounded row-span-1 col-span-1 p-4 gap-2">
<div className="font-semibold">Verifier</div>
<div className="flex flex-col text-sm bg-white border border-slate-300 w-full flex-grow cursor-text py-1 overflow-y-auto">
{verifierMessages.map((m, index) => (
<span
key={index}
className="px-1 py-0.5 text-slate-600 break-all"
className="px-3 py-1 text-slate-600 break-words"
>
{m}
</span>
))}
</div>
</div>
<div className="flex flex-row justify-center row-span-1 col-span-2">
<div className="flex flex-col items-center border border-slate-300 bg-white rounded-lg shadow-md row-span-1 col-span-1 p-4 gap-2">
<div className="font-semibold text-slate-700 text-lg">Verifier</div>
<div className="flex flex-col text-sm bg-slate-50 border border-slate-200 w-full flex-grow py-2 overflow-y-auto rounded">
{verifierMessages.map((m, index) => (
<span
key={index}
className="px-3 py-1 text-slate-600 break-words"
>
{m}
</span>
))}
</div>
</div>
<div className="flex flex-row justify-center items-center row-span-1 col-span-2">
<Button
className="h-fit"
className="bg-slate-800 text-white font-semibold px-6 py-3 rounded-lg shadow-md hover:bg-slate-700 disabled:opacity-50"
disabled={!ready || started}
onClick={start}
>
<div>
<div className="flex items-center">
{ready && !started ? (
<>Start Demo</>
) : (
@@ -243,10 +254,8 @@ function App(): ReactElement {
height="40"
width="40"
radius="48"
color="#000000"
color="#ffffff"
ariaLabel="watch-loading"
wrapperStyle={{}}
wrapperClass=""
/>
)}
</div>

View File

@@ -134,7 +134,7 @@ var options = {
// - https://github.com/GoogleChromeLabs/wasm-bindgen-rayon#setting-up
// - https://web.dev/i18n/en/coop-coep/
devServer: {
port: 3456,
port: 8080,
host: 'localhost',
hot: true,
headers: {

3615
package-lock.json generated

File diff suppressed because it is too large Load Diff

2092
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -273,7 +273,6 @@ export class Prover {
async reveal(reveal: Reveal) {
return this.#prover.reveal(reveal);
}
}
export class Verifier {