Interactive verifier demo (#35)

* Add interactive verifier demo
* Serve and load plugin from local file
* Show screenname at the end + removed attestation code
* No more need for tlsn-js
* Updated Docker container
* Use environment variable to enable/disable POAPs
* Improved UI
* use LRU for tracking sessions

Co-authored-by: Hendrik Eeckhaut <hendrik@eeckhaut.org>
This commit is contained in:
tsukino
2025-08-26 02:16:25 -07:00
committed by GitHub
parent 87e1723628
commit 3f89b56fcc
32 changed files with 2014 additions and 3087 deletions

33
.dockerignore Normal file
View File

@@ -0,0 +1,33 @@
# Dependencies
node_modules/
npm-debug.log*
# Build outputs
build/
# Development files
.git/
.gitignore
.github/
README.md
.env.local
.env.development
*.md
# IDE files
.vscode/
.idea/
*.swp
*.swo
# OS files
.DS_Store
Thumbs.db
# Docker files
Dockerfile*
docker-compose*.yml
# Logs
*.log

View File

@@ -6,7 +6,8 @@ aws s3 cp s3://tlsn-plugin-demo/firebase-admin.json server/util/
aws ecr get-login-password --region eu-central-1 | docker login --username AWS --password-stdin 490752553772.dkr.ecr.eu-central-1.amazonaws.com
docker build -t tlsn-plugin-demo .
docker build --build-arg ENABLE_POAP=true --build-arg VERIFIER_URL="http://demo.tlsnotary.org:7047" -t tlsn-demo .
docker tag tlsn-plugin-demo:latest 490752553772.dkr.ecr.eu-central-1.amazonaws.com/tlsn-plugin-demo:latest
docker push 490752553772.dkr.ecr.eu-central-1.amazonaws.com/tlsn-plugin-demo:latest

1
.gitignore vendored
View File

@@ -4,5 +4,4 @@
.idea
build
.env
rs/**/target
firebase-admin.json

View File

@@ -1,16 +1,103 @@
FROM node:latest
###########################################
# Build Stage 1: Build the Verifier
###########################################
FROM ubuntu:24.04 as verifier-builder
ARG VERIFIER_URL="http://localhost:7047"
# Install build dependencies
RUN apt-get update && \
apt-get install -y \
curl \
git \
bash \
ca-certificates \
pkg-config \
libssl-dev \
build-essential \
npm \
sudo \
&& rm -rf /var/lib/apt/lists/*
# Install Rust and Cargo
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | bash -s -- -y
ENV PATH="/root/.cargo/bin:${PATH}"
# Install Node.js 18.x
RUN curl -fsSL https://deb.nodesource.com/setup_18.x | bash - && \
apt-get install -y nodejs
# Install xtp and Extism
RUN curl https://static.dylibso.com/cli/install.sh -s | bash
RUN curl https://raw.githubusercontent.com/extism/js-pdk/main/install.sh -s | bash
ENV PATH="/root/.xtp/bin:$PATH"
# Clone and build TLSNotary (interactive-verifier branch)
WORKDIR /
RUN git clone --branch interactive-verifier --single-branch https://github.com/tlsnotary/tlsn.git
# Build the Verifier plugin
WORKDIR /tlsn/crates/notary/plugin/js
RUN chmod +x prepare.sh && ./prepare.sh && xtp plugin build
# Build the Verifier server
WORKDIR /tlsn/crates/notary/server
RUN cargo build --release
# Build the prover plugin
WORKDIR /
RUN git clone --branch interactive-verifier --single-branch https://github.com/tlsnotary/tlsn-plugin-boilerplate.git
WORKDIR /tlsn-plugin-boilerplate
# replace "http://localhost:7047" with $VERIFIER_URL in config.json and src/index.ts
RUN sed -i "s|http://localhost:7047|$VERIFIER_URL|g" config.json src/index.ts
RUN npm ci; npm run build
###########################################
# Build stage 2: Build the demo application
###########################################
FROM node:18-slim AS demo-builder
ARG ENABLE_POAP=false
ENV POAP=${ENABLE_POAP}
ARG PORT=3030
WORKDIR /app
ENV PATH="${PATH}:/root/.cargo/bin"
COPY package*.json ./
RUN npm ci
COPY . .
RUN apt-get update && apt-get install -y curl && apt-get install netcat-openbsd -y
RUN curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf | sh -s -- -y
RUN npm install
RUN npm i --prefix rs/0.1.0-alpha.12/
COPY --from=verifier-builder /tlsn-plugin-boilerplate/dist/*.tlsn.wasm ./static
RUN npm run build
EXPOSE ${PORT}
CMD ["node", "build/server/index.bundle.js"]
###########################################
# Build Stage 3: Runtime
###########################################
FROM node:18-slim AS production
ARG ENABLE_POAP=false
ENV POAP=${ENABLE_POAP}
RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates && \
apt-get clean && rm -rf /var/lib/apt/lists/*
WORKDIR /app
RUN addgroup --gid 1001 nodejs && \
useradd --system --uid 1001 --gid 1001 --no-create-home nodejs
COPY package*.json ./
RUN npm ci --only=production && npm cache clean --force
COPY --from=demo-builder --chown=nodejs:nodejs /app/build ./build
WORKDIR /verifier
COPY --from=verifier-builder /tlsn/target/release/notary-server ./verifier
COPY --from=verifier-builder /tlsn/crates/notary/plugin/wasm/*.wasm /plugin/wasm/
# Install PM2 for process management
RUN npm install -g pm2
COPY ecosystem.config.js /ecosystem.config.js
EXPOSE 3030 7047
ENV VERIFIER_SECRET_KEY="TMBak7!!66ku$kRQ"
CMD ["pm2-runtime", "/ecosystem.config.js"]

View File

@@ -2,7 +2,7 @@
Welcome to the **TLSNotary Plugin Demo**! This demo showcases how TLSNotary can be used to verify private user data in web applications.
In this demo, you'll prove that you own a Twitter account to the web server. The website will verify your attestation and, as a reward, you'll receive a POAP (Proof of Attendance Protocol) token — while stocks last!
In this demo, you'll prove that you own a Twitter account to the web server. The website will verify your proof and, as a reward, you'll receive a POAP (Proof of Attendance Protocol) token — while stocks last!
---
@@ -25,8 +25,15 @@ This demo works by leveraging the [Provider API]((https://github.com/tlsnotary/t
## Installing and Running
1. Clone this repository
2. Run `npm install`
3. Insert a `poaps.txt` of POAP mint links in `server/util`
4. Run `npm run dev`
5. Enjoy the demo
1. Build:
```sh
docker build -t tlsn-demo .
```
* To build with POAPS enabled, add `--build-arg ENABLE_POAP=true`
* To set the verifier url, add `--build-arg VERIFIER_URL="http://demo.tlsnotary.org:7047"`
E.g. `docker build --build-arg ENABLE_POAP=true -t tlsn-demo .`
2. Run:
```sh
docker run --rm -p 3030:3030 -p 7047:7047 -it tlsn-demo
```
3. Visit <http://localhost:3030>

15
ecosystem.config.js Normal file
View File

@@ -0,0 +1,15 @@
module.exports = {
apps: [
{
name: "verifier",
cwd: "/verifier",
script: "./verifier"
},
{
name: "demo",
cwd: "/app",
script: "node",
args: "build/server/index.bundle.js"
}
]
};

1879
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
{
"name": "tlsn-content",
"name": "tlsn-plugin-demo",
"version": "0.0.0",
"description": "",
"main": "",
@@ -24,55 +24,43 @@
"author": "",
"license": "ISC",
"dependencies": {
"@emotion/react": "^11.13.3",
"@emotion/styled": "^11.13.0",
"@fortawesome/fontawesome-free": "^6.6.0",
"@mui/material": "^6.1.2",
"async-mutex": "^0.5.0",
"buffer": "^6.0.3",
"charwise": "^3.0.1",
"classnames": "^2.5.1",
"comlink": "^4.4.1",
"concurrently": "^9.0.1",
"crypto-browserify": "^3.12.1",
"dotenv": "^16.4.5",
"express": "^4.21.1",
"fast-deep-equal": "^3.1.3",
"firebase-admin": "^13.1.0",
"isomorphic-fetch": "^3.0.0",
"node-loader": "^2.0.0",
"nodemon": "^3.1.7",
"react": "^18.2.0",
"lru-cache": "^11.1.0",
"react": "^18.3.1",
"react-confetti-explosion": "^2.1.2",
"react-dom": "^18.2.0",
"react-redux": "^8.1.2",
"react-dom": "^18.3.1",
"react-router": "^6.15.0",
"react-router-dom": "^6.15.0",
"redux": "^4.2.1",
"redux-logger": "^3.0.6",
"redux-thunk": "^2.4.2",
"stream-browserify": "^3.0.0",
"tailwindcss": "^3.4.13",
"tlsn-js": "^0.1.0-alpha.12.0"
"stream-browserify": "^3.0.0"
},
"devDependencies": {
"@babel/core": "^7.25.2",
"@babel/plugin-proposal-class-properties": "^7.18.6",
"@babel/preset-env": "^7.25.4",
"@babel/preset-react": "^7.24.7",
"@mui/material": "^6.1.2",
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.15",
"@types/chrome": "^0.0.273",
"@tailwindcss/postcss": "^4.0.0-alpha.29",
"@types/node": "^22.7.4",
"@types/react": "^18.3.10",
"@types/react-dom": "^18.3.0",
"@types/react-router-dom": "^5.3.3",
"@types/redux-logger": "^3.0.13",
"@types/webextension-polyfill": "^0.12.1",
"autoprefixer": "^10.4.20",
"babel-eslint": "^10.1.0",
"babel-loader": "^9.2.1",
"babel-preset-react-app": "^10.0.1",
"clean-webpack-plugin": "^4.0.0",
"compression-webpack-plugin": "^11.1.0",
"concurrently": "^9.0.1",
"copy-webpack-plugin": "^12.0.2",
"css-loader": "^7.1.2",
"eslint": "8.1.0",
@@ -89,19 +77,18 @@
"html-loader": "^4.2.0",
"html-webpack-plugin": "^5.5.0",
"image-webpack-loader": "^8.1.0",
"node-loader": "^2.0.0",
"nodemon": "^3.1.7",
"postcss-loader": "^7.3.3",
"postcss-preset-env": "^9.1.1",
"prettier": "^3.3.3",
"react-refresh": "^0.14.2",
"sass": "^1.80.6",
"sass-loader": "^16.0.3",
"source-map-loader": "^3.0.1",
"style-loader": "^3.3.1",
"terser-webpack-plugin": "^5.3.6",
"tailwindcss": "^3.4.13",
"ts-loader": "^9.4.2",
"type-fest": "^3.5.2",
"typescript": "^4.9.4",
"wasm-loader": "^1.3.0",
"typescript": "^5.8.3",
"webpack": "^5.75.0",
"webpack-cli": "^4.10.0",
"webpack-dev-server": "^4.11.1"

File diff suppressed because it is too large Load Diff

View File

@@ -1,22 +0,0 @@
[package]
name = "verifier"
version = "0.1.0"
license = "ISC"
edition = "2021"
exclude = ["index.node"]
[lib]
crate-type = ["cdylib"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
chrono = "0.4"
neon = "1"
elliptic-curve = { version = "0.13.5", features = ["pkcs8"] }
k256 = { version = "0.13", features = ["pem", "ecdsa"] }
serde = { version = "1.0.147", features = ["derive"] }
serde_json = "1.0"
hex = "0.4"
bincode = { version = "1.3" }
tlsn-core = { git = "https://github.com/tlsnotary/tlsn.git", tag = "v0.1.0-alpha.12", package = "tlsn-core" }

View File

@@ -1,119 +0,0 @@
# verifier
This project was bootstrapped by [create-neon](https://www.npmjs.com/package/create-neon).
## Installing verifier
Installing verifier requires a [supported version of Node and Rust](https://github.com/neon-bindings/neon#platform-support).
You can install the project with npm. In the project directory, run:
```sh
$ npm install
```
This fully installs the project, including installing any dependencies and running the build.
## Building verifier
If you have already installed the project and only want to run the build, run:
```sh
$ npm run build
```
This command uses the [cargo-cp-artifact](https://github.com/neon-bindings/cargo-cp-artifact) utility to run the Rust build and copy the built library into `./index.node`.
## Exploring verifier
After building verifier, you can explore its exports at the Node REPL:
```sh
$ npm install
$ node
> require('.').hello()
"hello node"
```
## Available Scripts
In the project directory, you can run:
### `npm install`
Installs the project, including running `npm run build`.
### `npm build`
Builds the Node addon (`index.node`) from source.
Additional [`cargo build`](https://doc.rust-lang.org/cargo/commands/cargo-build.html) arguments may be passed to `npm build` and `npm build-*` commands. For example, to enable a [cargo feature](https://doc.rust-lang.org/cargo/reference/features.html):
```
npm run build -- --feature=beetle
```
#### `npm build-debug`
Alias for `npm build`.
#### `npm build-release`
Same as [`npm build`](#npm-build) but, builds the module with the [`release`](https://doc.rust-lang.org/cargo/reference/profiles.html#release) profile. Release builds will compile slower, but run faster.
### `npm test`
Runs the unit tests by calling `cargo test`. You can learn more about [adding tests to your Rust code](https://doc.rust-lang.org/book/ch11-01-writing-tests.html) from the [Rust book](https://doc.rust-lang.org/book/).
## Project Layout
The directory structure of this project is:
```
verifier/
├── Cargo.toml
├── README.md
├── index.node
├── package.json
├── src/
| └── lib.rs
└── target/
```
### Cargo.toml
The Cargo [manifest file](https://doc.rust-lang.org/cargo/reference/manifest.html), which informs the `cargo` command.
### README.md
This file.
### index.node
The Node addon—i.e., a binary Node module—generated by building the project. This is the main module for this package, as dictated by the `"main"` key in `package.json`.
Under the hood, a [Node addon](https://nodejs.org/api/addons.html) is a [dynamically-linked shared object](https://en.wikipedia.org/wiki/Library_(computing)#Shared_libraries). The `"build"` script produces this file by copying it from within the `target/` directory, which is where the Rust build produces the shared object.
### package.json
The npm [manifest file](https://docs.npmjs.com/cli/v7/configuring-npm/package-json), which informs the `npm` command.
### src/
The directory tree containing the Rust source code for the project.
### src/lib.rs
The Rust library's main module.
### target/
Binary artifacts generated by the Rust build.
## Learn More
To learn more about Neon, see the [Neon documentation](https://neon-bindings.com).
To learn more about Rust, see the [Rust documentation](https://www.rust-lang.org).
To learn more about Node, see the [Node documentation](https://nodejs.org).

Binary file not shown.

View File

@@ -1,27 +0,0 @@
{
"name": "verifier",
"version": "0.1.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "verifier",
"version": "0.1.0",
"hasInstallScript": true,
"license": "ISC",
"devDependencies": {
"cargo-cp-artifact": "^0.1"
}
},
"node_modules/cargo-cp-artifact": {
"version": "0.1.9",
"resolved": "https://registry.npmjs.org/cargo-cp-artifact/-/cargo-cp-artifact-0.1.9.tgz",
"integrity": "sha512-6F+UYzTaGB+awsTXg0uSJA1/b/B3DDJzpKVRu0UmyI7DmNeaAl2RFHuTGIN6fEgpadRxoXGb7gbC1xo4C3IdyA==",
"dev": true,
"license": "MIT",
"bin": {
"cargo-cp-artifact": "bin/cargo-cp-artifact.js"
}
}
}
}

View File

@@ -1,18 +0,0 @@
{
"name": "verifier",
"version": "0.1.0",
"description": "",
"main": "index.node",
"scripts": {
"build": "cargo-cp-artifact -nc index.node -- cargo build --message-format=json-render-diagnostics",
"build-debug": "npm run build --",
"build-release": "npm run build -- --release",
"install": "npm run build-release",
"test": "cargo test"
},
"author": "",
"license": "ISC",
"devDependencies": {
"cargo-cp-artifact": "^0.1"
}
}

File diff suppressed because one or more lines are too long

View File

@@ -1,159 +0,0 @@
use k256::pkcs8::DecodePublicKey;
use k256::pkcs8::EncodePublicKey;
use neon::prelude::*;
use tlsn_core::{
presentation::{Presentation, PresentationOutput},
signing::VerifyingKey,
CryptoProvider,
};
fn verify(mut cx: FunctionContext) -> JsResult<JsObject> {
let presentation_cx = cx.argument::<JsString>(0)?.value(&mut cx);
let notary_key_cx = cx.argument::<JsString>(1)?.value(&mut cx);
let (sent, recv, time) =
verify_presentation(presentation_cx, notary_key_cx).or_else(|e| cx.throw_error(e))?;
let obj: Handle<JsObject> = cx.empty_object();
let sent_str = cx.string(sent);
obj.set(&mut cx, "sent", sent_str)?;
let recv_str = cx.string(recv);
obj.set(&mut cx, "recv", recv_str)?;
let session_time = cx.number(time as f64);
obj.set(&mut cx, "time", session_time)?;
Ok(obj)
}
fn verify_presentation(
presentation_cx: String,
notary_key_cx: String,
) -> Result<(String, String, u64), String> {
let bytes: Vec<u8> = hex::decode(presentation_cx.as_str()).map_err(|e| e.to_string())?;
let presentation: Presentation = bincode::deserialize(&bytes).map_err(|e| e.to_string())?;
let VerifyingKey {
alg: _,
data: key_data,
} = presentation.verifying_key();
let notary_key = k256::PublicKey::from_public_key_pem(notary_key_cx.as_str())
.map_err(|x| format!("Invalid notary key: {}", x))?;
let verifying_key = k256::PublicKey::from_sec1_bytes(key_data)
.map_err(|x| format!("Invalid verifying key: {}", x))?;
if notary_key != verifying_key {
Err("The verifying key does not match the notary key")?;
}
let provider = CryptoProvider::default();
let PresentationOutput {
connection_info,
transcript,
..
} = presentation
.verify(&provider)
.map_err(|x| format!("Presentation verification failed: {}", x))?;
let (sent, recv) = transcript
.map(|mut partial_transcript| {
partial_transcript.set_unauthed(b'X');
let sent = String::from_utf8_lossy(partial_transcript.sent_unsafe()).to_string();
let recv = String::from_utf8_lossy(partial_transcript.received_unsafe()).to_string();
(sent, recv)
})
.unwrap_or_default();
Ok((sent, recv, connection_info.time))
}
#[neon::main]
fn main(mut cx: ModuleContext) -> NeonResult<()> {
cx.export_function("verify", verify)?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use serde::Deserialize;
#[derive(Deserialize, Debug)]
struct Presentation {
version: String,
data: String,
}
fn example_presentation() -> String {
let example = include_str!("example.json");
let presentation: Presentation = serde_json::from_str(&example).unwrap();
assert_eq!("0.1.0-alpha.12", presentation.version);
presentation.data
}
fn extract_verifying_key_as_pem(presentation_data: String) -> Result<String, String> {
let bytes: Vec<u8> = hex::decode(presentation_data.as_str()).map_err(|e| e.to_string())?;
let presentation: tlsn_core::presentation::Presentation = bincode::deserialize(&bytes).map_err(|e| e.to_string())?;
let VerifyingKey {
alg: _,
data: key_data,
} = presentation.verifying_key();
let verifying_key = k256::PublicKey::from_sec1_bytes(&key_data)
.map_err(|x| format!("Invalid verifying key: {}", x))?;
let pem = verifying_key.to_public_key_pem(k256::pkcs8::LineEnding::LF)
.map_err(|x| format!("Failed to convert to PEM: {}", x))?;
Ok(pem)
}
#[test]
fn test_verify_with_correct_key() {
let correct_key = extract_verifying_key_as_pem(example_presentation())
.expect("Failed to extract verifying key from presentation");
let (sent, recv, time) =
verify_presentation(example_presentation(), correct_key).unwrap();
assert_eq!(1748415894, time);
assert!(sent.contains("host: raw.githubusercontent.com"));
assert!(sent.contains("XXXXXXXXXXXXXXXXXX"));
assert!(recv.contains("HTTP/1.1 200 OK"));
assert!(recv.contains("Content-Type: text/plain"));
}
#[test]
fn test_verify_with_your_current_key() {
let your_notary_key = String::from(
"-----BEGIN PUBLIC KEY-----
MDYwEAYHKoZIzj0CAQYFK4EEAAoDIgACWq2qrz9HJbTB32D4WowdXQfnCaBS5eas
rPwHd4svpUo=
-----END PUBLIC KEY-----"
);
let res = verify_presentation(example_presentation(), your_notary_key);
assert_eq!(
res,
Err("The verifying key does not match the notary key".to_string())
);
}
#[test]
fn test_verify_wrong_key() {
let invalid_key = String::from(
"-----BEGIN PUBLIC KEY-----
MDYwEAYHKoZIzj0CAQYFK4EEAAoDIgABm3AS+GGr3gEwbDOWNJTR7oWF/xJ6LBf+
z9KxqnGiW9o=
-----END PUBLIC KEY-----"
);
let res = verify_presentation(example_presentation(), invalid_key);
assert!(res.is_err());
let error_msg = res.unwrap_err();
assert!(error_msg.contains("Invalid notary key") || error_msg.contains("does not match"));
}
}

View File

@@ -1,15 +1,11 @@
import express from 'express';
import React from 'react';
import { renderToString } from 'react-dom/server';
import { StaticRouter } from 'react-router-dom/server';
import App from '../web/pages/App';
import configureAppStore, { AppRootState } from '../web/store';
import { Provider } from 'react-redux';
import { Mutex } from 'async-mutex';
//@ts-ignore
import { verify } from '../rs/0.1.0-alpha.12/index.node';
import { convertNotaryWsToHttp, fetchPublicKeyFromNotary } from './util/index';
import { assignPoapToUser } from './util/index';
import { LRUCache } from 'lru-cache'
const app = express();
const port = 3030;
@@ -38,90 +34,73 @@ app.use(express.static('build/ui'));
app.use(express.json());
const mutex = new Mutex();
app.get('*', (req, res) => {
try {
const storeConfig: AppRootState = {
attestation: {
raw: {
version: '0.1.0-alpha.12',
data: '',
meta: {
notaryUrl: '',
websocketProxyUrl: '',
pluginUrl: '',
},
},
},
};
const sessions = new LRUCache<string, string>({
max: 10000,
ttl: 1000 * 60 * 60, // 60 minutes
});
const store = configureAppStore(storeConfig);
const html = renderToString(
<Provider store={store}>
<StaticRouter location={req.url}>
<App />
</StaticRouter>
</Provider>,
);
const preloadedState = store.getState();
res.send(`
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" type="image/png" href="/favicon.png" />
<title>TLSN Plugin Demo</title>
<script>
window.__PRELOADED_STATE__ = ${JSON.stringify(preloadedState)};
</script>
<script defer src="/index.bundle.js"></script>
</head>
<body>
<div id="root">${html}</div>
<div id="modal-root"></div>
</body>
</html>
`);
} catch (e) {
res.send(`
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" type="image/png" href="/favicon.png" />
<title>TLSN Plugin Demo</title>
<script>
window.__PRELOADED_STATE__ = {};
</script>
<script defer src="/index.bundle.js"></script>
</head>
<body>
<div id="root"></div>
<div id="modal-root"></div>
</body>
</html>
`);
app.post('/update-session', async (req, res) => {
if (
req.headers['x-verifier-secret-key'] !== process.env.VERIFIER_SECRET_KEY
) {
return res.status(403).json({ error: 'Unauthorized' });
}
console.log('Update session:', req.body);
const { screen_name, session_id } = req.body;
if (!screen_name || !session_id) {
return res.status(400).json({ error: 'Missing screen_name or session_id' });
}
sessions.set(session_id, screen_name);
console.log('Updated session:', session_id, screen_name);
res.json({ success: true });
});
app.post('/check-session', async (req, res) => {
const { session_id } = req.body;
if (!session_id) {
return res.status(400).json({ error: 'Missing session_id' });
}
let screen_name: string | undefined;
for (let i = 0; i < 5; i++) {
screen_name = sessions.get(session_id);
if (screen_name) break;
await new Promise(resolve => setTimeout(resolve, 1000));
}
console.log('Check session:', session_id, screen_name);
if (!screen_name) {
return res.status(404).json({ error: 'Session not found' });
}
res.json({ screen_name });
});
app.post('/poap-claim', async (req, res) => {
const { screenName } = req.body;
if (!screenName) {
return res.status(400).json({ error: 'Missing screen_name' });
const { sessionId } = req.body;
const sn = sessions.get(sessionId);
console.log('Checking Poap claim:', sessionId, sn);
if (!sn) {
return res.status(400).json({ error: 'Invalid screen_name or sessionId' });
}
try {
await mutex.runExclusive(async () => {
const poapLink = await assignPoapToUser(screenName);
if (process.env.POAP !== 'true') {
return res.status(404).json({ error: 'No POAPs available in development mode' });
}
const poapLink = await assignPoapToUser(sn);
if (!poapLink) {
return res.status(404).json({ error: 'No POAPs available' });
}
console.log('Serving POAP link:', poapLink);
return res.json({ poapLink });
});
} catch (error) {
@@ -130,26 +109,41 @@ app.post('/poap-claim', async (req, res) => {
}
});
app.post('/verify-attestation', async (req, res) => {
const { attestation } = req.body;
if (!attestation) {
return res.status(400).send('Missing attestation');
}
function renderHTML(html: string = '', preloadedState: any = {}) {
return `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" type="image/png" href="/favicon.png" />
<title>TLSN Plugin Demo</title>
<script>
window.__PRELOADED_STATE__ = ${JSON.stringify(preloadedState)};
</script>
<script defer src="/index.bundle.js"></script>
</head>
<body>
<div id="root">${html}</div>
<div id="modal-root"></div>
</body>
</html>
`;
}
app.get('*', (req, res) => {
try {
const notaryUrl = attestation.meta.notaryUrl;
const notaryPem = await fetchPublicKeyFromNotary(notaryUrl);
const html = renderToString(
<StaticRouter location={req.url}>
<App />
</StaticRouter>
);
const presentation = await verify(attestation.data, notaryPem);
const presentationObj = {
sent: presentation.sent,
recv: presentation.recv,
};
res.json({ presentationObj });
} catch (e) {
console.error(e);
res.status(500).send('Error verifying attestation');
res.send(renderHTML(html));
} catch (error) {
console.error('SSR Error:', error);
// Send client-only version on SSR error
res.send(renderHTML());
}
});

View File

@@ -2,35 +2,27 @@ import path from 'path';
import fs from 'fs';
import admin from 'firebase-admin';
const serviceAccount = JSON.parse(
fs.readFileSync(path.join(__dirname, 'util/firebase-admin.json'), 'utf8'),
);
let serviceAccount: any;
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
});
const db = admin.firestore();
let db: any;
export function convertNotaryWsToHttp(notaryWs: string) {
const { protocol, pathname, hostname, port } = new URL(notaryWs);
const p = protocol === 'wss:' ? 'https:' : 'http:';
const pt = port ? `:${port}` : '';
const path = pathname === '/' ? '' : pathname.replace('/notarize', '');
const h = hostname === 'localhost' ? '127.0.0.1' : hostname;
return p + '//' + h + pt + path;
}
if (process.env.POAP !== 'true') {
console.log(`POAP feature is disabled`);
db = {};
} else {
console.log(`POAP feature is enabled`);
// Use the real config in production
serviceAccount = JSON.parse(
fs.readFileSync(path.join(__dirname, 'util/firebase-admin.json'), 'utf8')
);
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
});
db = admin.firestore();
export async function fetchPublicKeyFromNotary(notaryUrl: string) {
try {
const url = new URL(notaryUrl);
const res = await fetch(notaryUrl + '/info');
const json: any = await res.json();
if (!json.publicKey) throw new Error('invalid response');
return json.publicKey;
} catch (e) {
console.error('Failed to fetch public key from notary', e);
return null;
}
}
export const getUserPoap = async (

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

View File

@@ -13,7 +13,7 @@
"moduleResolution": "node",
"resolveJsonModule": true,
"noEmit": false,
"jsx": "react"
"jsx": "react-jsx",
},
"include": [
"web"

View File

@@ -37,7 +37,10 @@ export default function Button(props: Props): ReactElement {
{...btnProps}
>
{loading ? (
<Icon className="animate-spin" fa="fa-solid fa-spinner" size={2} />
<>
<span>Running TLSNotary plugin...</span>
<div className="animate-spin rounded-full h-5 w-5 border-2 border-white border-t-transparent"></div>
</>
) : (
children
)}

View File

@@ -5,10 +5,9 @@ import Step from '@mui/material/Step';
import Box from '@mui/material/Box';
import StepLabel from '@mui/material/StepLabel';
import classNames from 'classnames';
import type { PresentationJSON } from 'tlsn-js/build/types';
import Button from '../Button';
import ConfettiExplosion, { ConfettiProps } from 'react-confetti-explosion';
import { formatDataPreview } from '../../utils/utils';
import OverviewSvg from '../../../static/overview_prover_verifier.svg';
const steps = ['Connect Extension', 'Run Plugin'];
@@ -16,9 +15,8 @@ export default function Steps(): ReactElement {
const [extensionInstalled, setExtensionInstalled] = useState(false);
const [step, setStep] = useState<number>(0);
const [client, setClient] = useState<any>(null);
const [pluginData, setPluginData] = useState<PresentationJSON | null>(null);
const [sessionId, setSessionId] = useState<string>('');
const [loading, setLoading] = useState<boolean>(false);
const [transcript, setTranscript] = useState<any>(null);
const [screenName, setScreenName] = useState<string>('');
const [exploding, setExploding] = useState<boolean>(false);
@@ -53,17 +51,6 @@ export default function Steps(): ReactElement {
};
}, []);
useEffect(() => {
if (transcript) {
const match = transcript.recv.match(/"screen_name":"([^"]+)"/);
const screenName = match ? match[1] : null;
setScreenName(screenName);
if (screenName) {
setExploding(true);
}
}
}, [transcript]);
async function handleConnect() {
try {
//@ts-ignore
@@ -77,21 +64,24 @@ export default function Steps(): ReactElement {
async function handleRunPlugin() {
try {
setLoading(true);
const pluginData = await client.runPlugin(
'https://raw.githubusercontent.com/tlsnotary/tlsn-extension/cc3264f058ad2ebb0791830a1217fdd8bffd543f/src/assets/plugins/twitter_profile.wasm',
const _sessionId = await client.runPlugin(
window.location.origin + '/twitter_profile.tlsn.wasm',
);
setPluginData(pluginData);
console.log(pluginData);
const response = await fetch('/verify-attestation', {
setSessionId(_sessionId);
console.log('Session ID:', _sessionId);
const response = await fetch('/check-session', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ attestation: pluginData }),
body: JSON.stringify({ session_id: _sessionId }),
});
console.log('Check session response:', response);
if (response.status === 200) {
const data = await response.json();
setTranscript(data.presentationObj);
console.log('Response: Plugin data:', data);
setScreenName(data.screen_name);
setStep(1);
} else {
console.log(await response.text());
@@ -108,7 +98,7 @@ export default function Steps(): ReactElement {
{extensionInstalled ? (
<>
<div className="flex flex-row items-center gap-2 text-slate-600 font-bold pb-2">
Connected{' '}
Extension Connected{' '}
<div
className={classNames(
'rounded-full h-[10px] w-[10px] border-[2px]',
@@ -119,61 +109,197 @@ export default function Steps(): ReactElement {
)}
></div>
</div>
<Box className="w-full max-w-3xl mt-6 pb-4">
<Stepper activeStep={step} alternativeLabel>
{steps.map((label) => (
<Step key={label}>
<StepLabel>{label}</StepLabel>
</Step>
))}
</Stepper>
</Box>
<div className="flex gap-3">
{step === 0 && (
<button onClick={handleConnect} className="button">
Connect
</button>
)}
{step === 1 && !pluginData && (
<div className="flex flex-col items-center justify-center gap-2">
<ul className="flex flex-col items-center justify-center gap-1">
<li className="text-base font-light">
This will open a new tab to Twitter/X and the sidebar for
the extension
</li>
<li className="text-base font-light">
Click through the steps in the sidebar
</li>
<li className="text-base font-light">
Don't close the sidebar or refresh the page until
notarization is finished
</li>
<li className="text-base font-light">
If successful, the attestation and verified data will be
displayed below
</li>
</ul>
<Button onClick={handleRunPlugin} loading={loading}>
Run Plugin
</Button>
{step === 1 && !sessionId && (
<div className="flex flex-col items-center justify-center gap-6 max-w-4xl">
<div className="text-center space-y-4 w-full flex flex-col items-center">
<h2 className="text-2xl font-semibold text-gray-900">
Ready to Prove Your Twitter Identity
</h2>
<p className="text-lg text-gray-600 max-w-2xl">
Click the button below to start the verification process
</p>
<Button
onClick={handleRunPlugin}
loading={loading}
className="bg-blue-600 hover:bg-blue-700 !text-white px-12 py-4 text-xl font-semibold min-w-[300px] shadow-lg rounded-lg transition-all duration-200"
>
{loading ? (
<>
<div className="animate-spin rounded-full h-6 w-6 border-2 border-white border-t-transparent"></div>
<span>Processing...</span>
</>
) : (
<>
🔐 Prove Twitter Screen Name
</>
)}
</Button>
</div>
<div className="bg-blue-50 border border-blue-200 rounded-lg p-6 w-full">
<h3 className="text-lg font-semibold text-blue-900 mb-4 text-center">
What happens when you click "Prove Twitter Screen Name"?
</h3>
<div className="space-y-4 text-gray-700">
<div className="flex items-start gap-3">
<div className="bg-blue-500 text-white rounded-full w-6 h-6 flex items-center justify-center text-sm font-semibold flex-shrink-0 mt-0.5">
1
</div>
<p className="text-base leading-relaxed">
The TLSNotary extension will open a popup, asking permission to run the plugin and send the unredacted data (just the screen name) to the verifier server.
</p>
</div>
<div className="flex items-start gap-3">
<div className="bg-blue-500 text-white rounded-full w-6 h-6 flex items-center justify-center text-sm font-semibold flex-shrink-0 mt-0.5">
2
</div>
<div>
<p className="text-base leading-relaxed mb-2">
If you accept, the extension will open X/Twitter in a new tab with a sidebar showing these steps:
</p>
<div className="ml-4 space-y-2">
<div className="flex items-center gap-2">
<div className="w-2 h-2 bg-blue-300 rounded-full"></div>
<span className="text-sm text-gray-600">Go to your Twitter profile</span>
</div>
<div className="flex items-center gap-2">
<div className="w-2 h-2 bg-blue-300 rounded-full"></div>
<span className="text-sm text-gray-600">Log in if you haven't yet</span>
</div>
<div className="flex items-center gap-2">
<div className="w-2 h-2 bg-blue-300 rounded-full"></div>
<span className="text-sm text-gray-600">The extension proves your Twitter handle to the verifier server</span>
</div>
</div>
<p className="text-base leading-relaxed mb-2">
Click on the buttons in the sidebar to proceed.
</p>
</div>
</div>
<div className="flex items-start gap-3">
<div className="bg-blue-500 text-white rounded-full w-6 h-6 flex items-center justify-center text-sm font-semibold flex-shrink-0 mt-0.5">
3
</div>
<p className="text-base leading-relaxed">
{process.env.POAP === 'true' ? (
<>
If successful, your screen name will be shown here and you can claim a POAP.
</>
) : (
<>
If successful, your screen name will be shown here.
</>
)}
</p>
</div>
</div>
<div className="mt-4 p-3 bg-yellow-50 border border-yellow-200 rounded">
<p className="text-sm text-yellow-800">
💡 <strong>Tip:</strong> When step 3 is running, you can close the Twitter/X tab, but don't close the sidebar.
</p>
</div>
</div>
</div>
)}
{step === 1 && pluginData && screenName && (
<div className="flex flex-col items-center justify-center gap-2">
<h3 className="text-lg font-semibold text-center">
Optional: Claim Your POAP
</h3>
<ClaimPoap screen_name={screenName} exploding={exploding} />
{step === 1 && sessionId && screenName && (
<div className="flex flex-col items-center justify-center gap-6 max-w-4xl w-full">
{/* Success Header with Animation */}
<div className="text-center space-y-4 animate-fade-in">
<div className="bg-green-100 border-2 border-green-300 rounded-full w-20 h-20 flex items-center justify-center mx-auto mb-4 animate-bounce">
<span className="text-4xl"></span>
</div>
<h2 className="text-3xl font-bold text-green-800 mb-2">
🎉 Verification Successful! 🎉
</h2>
<div className="bg-gradient-to-r from-green-50 to-emerald-50 border-2 border-green-200 rounded-xl p-6 shadow-lg">
<h3 className="text-xl font-semibold text-gray-900 mb-2">
Successfully verified your Twitter identity
</h3>
<div className="bg-white rounded-lg p-4 border border-green-200">
<p className="text-lg text-gray-700">
Screen name: <span className="font-bold text-green-700 text-xl">@{screenName}</span>
</p>
</div>
</div>
</div>
{/* POAP Section */}
{process.env.POAP === 'true' && (
<div className="bg-gradient-to-r from-yellow-50 to-amber-50 border-2 border-yellow-300 rounded-xl p-8 w-full shadow-lg">
<div className="text-center space-y-4">
<div className="bg-yellow-100 border-2 border-yellow-300 rounded-full w-16 h-16 flex items-center justify-center mx-auto">
<span className="text-3xl">🎁</span>
</div>
<h3 className="text-2xl font-bold text-yellow-800">
Claim Your Reward!
</h3>
<p className="text-lg text-yellow-700 max-w-2xl mx-auto">
You've successfully proven your Twitter identity! Now claim your exclusive POAP token as proof of this achievement.
</p>
<div className="pt-4">
<ClaimPoap sessionId={sessionId} exploding={exploding} />
</div>
</div>
</div>
)}
{/* What's Next Section */}
<div className="bg-blue-50 border border-blue-200 rounded-xl p-6 w-full">
<h4 className="text-lg font-semibold text-blue-900 mb-3 text-center">
What just happened?
</h4>
<div className="grid md:grid-cols-2 gap-4 text-sm text-blue-800">
<div className="flex items-start gap-2">
<span className="text-blue-600">🔒</span>
<div>
<p className="font-semibold">Privacy Preserved</p>
<p>Your sensitive data stayed private - only your screen name was verified</p>
</div>
</div>
<div className="flex items-start gap-2">
<span className="text-blue-600">🛡</span>
<div>
<p className="font-semibold">Cryptographic Proof</p>
<p>TLSNotary created a verifiable proof without exposing your credentials to the verifier</p>
</div>
</div>
</div>
</div>
{/* Try Again Button */}
<div className="text-center">
<button
onClick={() => {
setSessionId('');
setScreenName('');
setStep(1);
}}
className="bg-gray-500 hover:bg-gray-600 text-white px-8 py-3 rounded-lg font-semibold transition-colors"
>
🔄 Try Again
</button>
</div>
</div>
)}
</div>
{pluginData && (
<DisplayPluginData
step={step}
pluginData={pluginData}
transcript={transcript}
/>
)}
</>
) : (
<InstallExtensionPrompt />
@@ -182,66 +308,12 @@ export default function Steps(): ReactElement {
);
}
function DisplayPluginData({
step,
pluginData,
transcript,
}: {
step: number;
pluginData: any;
transcript: any;
}): ReactElement {
const [tab, setTab] = useState<'sent' | 'recv'>('sent');
return (
<div className="flex justify-center items-center space-x-4 mt-8">
<div className="w-96">
<div className="p-2 bg-gray-200 border-t rounded-t-md text-center text-lg font-semibold">
Attestation
</div>
<div className="p-4 bg-gray-100 border rounded-b-md h-96 text-left overflow-auto">
<pre className="text-sm text-gray-700 whitespace-pre-wrap text-[12px]">
{formatDataPreview(pluginData)}
</pre>
</div>
</div>
<div className="w-96">
<div className="p-2 bg-gray-200 border-t rounded-t-md text-center text-lg font-semibold">
Presentation
</div>
<div className="bg-gray-100 border rounded-b-md h-96 overflow-auto">
<div className="flex border-b">
<button
onClick={() => setTab('sent')}
className={`p-2 w-1/2 text-center ${tab === 'sent' ? 'bg-slate-500 text-white' : 'bg-white text-black'}`}
>
Sent
</button>
<button
onClick={() => setTab('recv')}
className={`p-2 w-1/2 text-center ${tab === 'recv' ? 'bg-slate-500 text-white' : 'bg-white text-black'}`}
>
Received
</button>
</div>
<div className="p-4 text-left">
<pre className="text-[10px] text-gray-700 whitespace-pre-wrap">
{transcript &&
(tab === 'sent' ? transcript.sent : transcript.recv)}
</pre>
</div>
</div>
</div>
</div>
);
}
function ClaimPoap({
screen_name,
exploding,
sessionId,
}: {
screen_name: string;
exploding: boolean;
sessionId?: string;
}): ReactElement {
const [poapLink, setPoapLink] = useState<string>('');
const [error, setError] = useState<string | null>(null);
@@ -251,13 +323,13 @@ function ClaimPoap({
setLoading(true);
setError(null);
try {
if (!screen_name) return;
if (!sessionId) return;
const response = await fetch('/poap-claim', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ screenName: screen_name }),
body: JSON.stringify({ sessionId }),
});
if (response.status === 200) {
const data = await response.json();
@@ -301,36 +373,131 @@ function ClaimPoap({
}
function InstallExtensionPrompt() {
const handleRefresh = () => {
window.location.reload();
};
return (
<div className="flex flex-col justify-center items-center gap-2">
<div className="flex flex-col justify center items-center gap-2 pb-4">
<h1 className="text-base font-light">
<div className="flex flex-col items-center gap-8 max-w-4xl mx-auto p-6">
{/* Header Section */}
<div className="text-center space-y-4">
<h1 className="text-3xl font-bold text-gray-900">
Welcome to the TLSNotary Plugin Demo!
</h1>
<p className="text-base font-light">
This demo shows how TLSNotary can be used to verify private user data
in a webapp.
</p>
<p className="text-base font-light">
In this demo you'll prove that you own a Twitter/X account to the
webserver.
</p>
<p className="text-base font-light">
The webserver will verify your attestation and give a POAP in return (
<span className="font-semibold">while supplies last</span>)
<p className="text-xl text-gray-600 leading-relaxed">
Verify private user data in web applications using zero-knowledge proofs
</p>
<img className="mx-auto max-w-full h-auto mt-4" src={OverviewSvg} alt="TLSNotary Prover-Verifier Overview" />
</div>
{/* Demo Description */}
<div className="bg-gradient-to-r from-blue-50 to-indigo-50 border border-blue-200 rounded-xl p-8 w-full">
<h2 className="text-2xl font-semibold text-blue-900 mb-6 text-center">
How this demo works
</h2>
<div className="grid md:grid-cols-3 gap-6">
<div className="text-center">
<div className="bg-blue-500 text-white rounded-full w-12 h-12 flex items-center justify-center mx-auto mb-4 text-lg font-bold">
1
</div>
<h3 className="font-semibold text-gray-900 mb-2">Connect Extension</h3>
<p className="text-gray-600 text-sm">
Install and connect the TLSNotary browser extension
</p>
</div>
<div className="text-center">
<div className="bg-blue-500 text-white rounded-full w-12 h-12 flex items-center justify-center mx-auto mb-4 text-lg font-bold">
2
</div>
<h3 className="font-semibold text-gray-900 mb-2">Prove Ownership</h3>
<p className="text-gray-600 text-sm">
Prove you own a Twitter/X account without revealing sensitive data
</p>
</div>
<div className="text-center">
<div className="bg-blue-500 text-white rounded-full w-12 h-12 flex items-center justify-center mx-auto mb-4 text-lg font-bold">
3
</div>
<h3 className="font-semibold text-gray-900 mb-2">
{process.env.POAP === 'true' ? 'Get Rewarded' : 'Verification Complete'}
</h3>
<p className="text-gray-600 text-sm">
{process.env.POAP === 'true'
? 'Receive a POAP token as proof of verification'
: 'Your Twitter screen name is verified'
}
</p>
</div>
</div>
{process.env.POAP === 'true' && (
<div className="mt-6 p-4 bg-yellow-50 border border-yellow-200 rounded-lg">
<p className="text-center text-yellow-800">
🎁 <strong>Special offer:</strong> Get a POAP (Proof of Attendance Protocol) token after verification!{' '}<br />
<span className="font-semibold">(while supplies last)</span>
</p>
</div>
)}
</div>
{/* Installation Section */}
<div className="bg-white border-2 border-gray-200 rounded-xl p-8 w-full shadow-sm">
<div className="text-center space-y-6">
<div className="space-y-2">
<h2 className="text-xl font-semibold text-gray-900">
Get Started
</h2>
<p className="text-gray-600">
Install the TLSNotary extension to begin the verification process
</p>
{/* Add the manual refresh notice */}
<div className="bg-blue-50 border border-blue-200 rounded-lg p-3 mt-4">
<p className="text-sm text-blue-800">
<strong>Note:</strong> This page cannot automatically detect when the extension is installed.
You'll need to refresh the page after installation. We've added a refresh button below for your convenience.
</p>
</div>
</div>
<div className="flex flex-col sm:flex-row items-center justify-center gap-4">
<a
href="https://chromewebstore.google.com/detail/tlsn-extension/gcfkkledipjbgdbimfpijgbkhajiaaph"
target="_blank"
className="button bg-blue-600 hover:bg-blue-700 text-white px-8 py-3 text-lg font-semibold min-w-[200px]"
>
📥 Install Extension
</a>
<button
onClick={handleRefresh}
className="button bg-gray-100 hover:bg-gray-200 text-gray-700 px-8 py-3 text-lg font-semibold min-w-[200px]"
>
🔄 Refresh Page
</button>
</div>
</div>
</div>
{/* Additional Info */}
<div className="text-center space-y-4 text-gray-600 max-w-2xl">
<h3 className="text-lg font-semibold text-gray-900">
What is TLSNotary?
</h3>
<p className="text-sm leading-relaxed">
TLSNotary enables privacy-preserving verification of web data. Instead of sharing your actual data,
you can prove specific facts about it using cryptographic proofs, keeping your sensitive information private.
</p>
<div className="flex items-center justify-center gap-6 text-xs text-gray-500 pt-4">
<a href="https://tlsnotary.org" className="hover:text-blue-600 transition-colors">Learn More</a>
<a href="https://tlsnotary.org/docs/intro" className="hover:text-blue-600 transition-colors">Documentation</a>
<a href="https://github.com/tlsnotary" className="hover:text-blue-600 transition-colors">GitHub</a>
</div>
</div>
<p className="font-bold">Please install the extension to proceed </p>
<p className="font-bold">
You will need to refresh your browser after installing the extension
</p>
<a
href="https://chromewebstore.google.com/detail/tlsn-extension/gcfkkledipjbgdbimfpijgbkhajiaaph"
target="_blank"
className="button"
>
Install TLSN Extension
</a>
</div>
);
}

View File

@@ -1,14 +1,8 @@
import 'isomorphic-fetch';
import type {} from 'redux-thunk/extend-redux';
import * as React from 'react';
import { hydrateRoot } from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom';
import App from './pages/App';
import { Provider } from 'react-redux';
import configureAppStore from './store';
// @ts-ignore
const store = configureAppStore(window.__PRELOADED_STATE__);
// @ts-ignore
delete window.__PRELOADED_STATE__;
@@ -16,11 +10,14 @@ delete window.__PRELOADED_STATE__;
(async () => {
hydrateRoot(
document.getElementById('root')!,
<Provider store={store}>
<BrowserRouter>
<App />
</BrowserRouter>
</Provider>,
<BrowserRouter
future={{
v7_startTransition: true,
v7_relativeSplatPath: true,
}}
>
<App />
</BrowserRouter>
);
})();

View File

@@ -2,9 +2,4 @@
@tailwind components;
@tailwind utilities;
$fa-font-path: "~@fortawesome/fontawesome-free/webfonts";
@import "../../../node_modules/@fortawesome/fontawesome-free/scss/fontawesome";
@import "../../../node_modules/@fortawesome/fontawesome-free/scss/brands";
@import "../../../node_modules/@fortawesome/fontawesome-free/scss/solid";
@import "../../../node_modules/@fortawesome/fontawesome-free/scss/regular";
@import "~@fortawesome/fontawesome-free/css/all.css";

View File

@@ -1,51 +0,0 @@
import { Attestation, AttestedData } from '../utils/types';
enum ActionType {
SET_ATTESTATION = 'attestation/SET_ATTESTATION',
}
export type Action<payload = any> = {
type: ActionType;
payload: payload;
error?: boolean;
meta?: any;
};
type AttestationData = {
raw: Attestation;
};
export type State = {
raw: Attestation;
};
export const initState: State = {
raw: {
version: '0.1.0-alpha.12',
data: '',
meta: {
notaryUrl: '',
websocketProxyUrl: '',
pluginUrl: '',
},
},
};
export const setAttestation = (
attestation: AttestationData,
): Action<AttestationData> => ({
type: ActionType.SET_ATTESTATION,
payload: attestation,
});
export default function attestation(state = initState, action: Action): State {
switch (action.type) {
case ActionType.SET_ATTESTATION:
return {
...state,
raw: action.payload,
};
default:
return state;
}
}

View File

@@ -1,29 +0,0 @@
import { applyMiddleware, combineReducers, createStore } from 'redux';
import thunk from 'redux-thunk';
import { createLogger } from 'redux-logger';
import attestation from './attestation';
const rootReducer = combineReducers({
attestation,
});
export type AppRootState = ReturnType<typeof rootReducer>;
const createStoreWithMiddleware =
process.env.NODE_ENV === 'development'
? applyMiddleware(
thunk,
createLogger({
collapsed: true,
}),
)(createStore)
: applyMiddleware(thunk)(createStore);
function configureAppStore(preloadedState?: AppRootState) {
const { attestation } = preloadedState || {};
return createStoreWithMiddleware(rootReducer, {
attestation,
});
}
export default configureAppStore;

View File

@@ -1,20 +0,0 @@
export interface AttestedData {
version: '0.1.0-alpha.12';
time: number;
sent: string;
recv: string;
notaryUrl: string;
notaryKey: string;
websocketProxyUrl?: string;
verifierKey?: string;
}
export type Attestation = {
version: '0.1.0-alpha.12';
data: string;
meta: {
notaryUrl: string;
websocketProxyUrl: string;
pluginUrl?: string;
};
};

View File

@@ -1,19 +0,0 @@
import type { PresentationJSON } from 'tlsn-js/build/types';
export const formatDataPreview = (data: PresentationJSON) => {
if (!data) return '';
return Object.entries(data)
.map(([key, value]) => {
if (typeof value === 'object' && value !== null) {
return `${key}: ${JSON.stringify(value, null, 2)}`;
} else if (key === 'data') {
const maxLength = 160;
const previewData = value.toString().substring(0, maxLength);
const formattedData = previewData.match(/.{1,20}/g)?.join('\n');
return `${key}: ${formattedData}... ${value.length} more`;
} else {
return `${key}: ${value}`;
}
})
.join('\n');
};

View File

@@ -1,7 +0,0 @@
import * as Comlink from 'comlink';
import init, { Presentation } from 'tlsn-js';
Comlink.expose({
init,
Presentation,
});

View File

@@ -8,6 +8,8 @@ var { CleanWebpackPlugin } = require('clean-webpack-plugin');
const ASSET_PATH = process.env.ASSET_PATH || '/';
const isDevelopment = process.env.NODE_ENV !== 'production';
const isPOAP = process.env.POAP === 'true';
const options = {
target: 'node',
@@ -24,6 +26,9 @@ const options = {
clean: true,
publicPath: ASSET_PATH,
},
externals: {
express: 'commonjs express',
},
module: {
rules: [
{
@@ -42,7 +47,6 @@ const options = {
},
{
test: /\.(ts|tsx)$/,
exclude: /node_modules\/(?!(tlsn-js|tlsn-js-v5)\/).*/,
use: [
{
loader: require.resolve("ts-loader"),
@@ -85,11 +89,13 @@ const options = {
new CleanWebpackPlugin({ verbose: false }),
new webpack.ProgressPlugin(),
new webpack.EnvironmentPlugin(['NODE_ENV']),
new CopyWebpackPlugin({
patterns: [
{ from: 'server/util/firebase-admin.json', to: 'util/' },
],
}),
isPOAP
? new CopyWebpackPlugin({
patterns: [
{ from: 'server/util/firebase-admin.json', to: 'util/' },
],
})
: null,
new CompressionPlugin({
algorithm: 'gzip',
test: /\.(js|css|html|svg)$/,

View File

@@ -78,7 +78,6 @@ var options = {
},
{
test: /\.(ts|tsx)$/,
exclude: /node_modules\/(?!(tlsn-js|tlsn-js-v5)\/).*/,
use: [
{
loader: require.resolve("ts-loader"),
@@ -137,7 +136,7 @@ var options = {
new CleanWebpackPlugin({ verbose: false }),
new webpack.ProgressPlugin(),
// expose and write the allowed env vars on the compiled bundle
new webpack.EnvironmentPlugin(['NODE_ENV']),
new webpack.EnvironmentPlugin({ NODE_ENV: 'development', POAP: 'false' }),
// new HtmlWebpackPlugin({
// template: path.join(__dirname, 'static', 'index.html'),
// filename: 'index.html',
@@ -146,11 +145,6 @@ var options = {
// }),
new CopyWebpackPlugin({
patterns: [
{
from: 'node_modules/tlsn-js/build',
to: path.join(__dirname, 'build', 'ui'),
force: true,
},
{
from: "static/favicon.png",
to: path.join(__dirname, "build", "ui"),
@@ -161,6 +155,11 @@ var options = {
to: path.join(__dirname, "build", "ui"),
force: true,
},
{
from: "static/twitter_profile.tlsn.wasm",
to: path.join(__dirname, "build", "ui"),
force: true,
},
],
}),
new webpack.ProvidePlugin({
@@ -169,21 +168,7 @@ var options = {
].filter(Boolean),
infrastructureLogging: {
level: 'info',
},
// Required by wasm-bindgen-rayon, in order to use SharedArrayBuffer on the Web
// Ref:
// - https://github.com/GoogleChromeLabs/wasm-bindgen-rayon#setting-up
// - https://web.dev/i18n/en/coop-coep/
devServer: {
headers: {
'Cross-Origin-Embedder-Policy': 'require-corp',
'Cross-Origin-Opener-Policy': 'same-origin',
},
},
// Enable WebAssembly support
experiments: {
asyncWebAssembly: true,
},
}
};
if (process.env.NODE_ENV === 'development') {