This commit is contained in:
tsukino
2023-12-19 21:11:10 -08:00
parent c9b53219f2
commit ddcf75fc5f
14 changed files with 23587 additions and 130 deletions

2
.gitignore vendored
View File

@@ -7,4 +7,4 @@ node_modules/
.DS_Store
pnpm-lock.yaml
build/
test-build/
dev-build/

View File

@@ -3,8 +3,8 @@ test-build
node_modules
wasm
postcss.config.js
webpack.config.js
webpack.test.config.js
webpack.build.config.js
webpack.web.dev.config.js
*.json
*.scss

23313
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "tlsn-js",
"version": "0.0.1",
"version": "0.0.2",
"description": "",
"repository": "https://github.com/tlsnotary/tlsn-js",
"main": "build/index.js",
@@ -8,17 +8,19 @@
"files": [
"build/",
"src/",
"wasm/prover/pkg",
"readme.md"
],
"scripts": {
"build:src": "webpack --config webpack.config.js",
"build:src": "webpack --config webpack.build.config.js",
"build:types": "tsc --project tsconfig.compile.json",
"build": "NODE_ENV=production concurrently npm:build:src npm:build:types",
"update:wasm": "sh utils/check-wasm.sh -f",
"build:wasm": "wasm-pack build --target web wasm/prover",
"build:test": "webpack --config webpack.test.config.js",
"watch:test": "webpack --config webpack.test.config.js --watch",
"serve:test": "serve --config ../serve.json ./test-build -l 3001",
"test": "concurrently npm:watch:test npm:serve:test",
"watch:dev": "webpack --config webpack.web.dev.config.js --watch",
"serve:dev": "serve --config ../../serve.json ./dev-build/web -l 3001",
"predev": "sh utils/check-wasm.sh",
"dev": "concurrently npm:watch:dev npm:serve:dev",
"lint:eslint": "eslint . --fix",
"lint:tsc": "tsc --noEmit",
"lint": "concurrently npm:lint:tsc npm:lint:eslint"
@@ -27,6 +29,7 @@
"comlink": "^4.4.1"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^6.15.0",
"browserify": "^17.0.0",
"concurrently": "^5.1.0",
"constants-browserify": "^1.0.0",
@@ -35,12 +38,12 @@
"eslint": "^8.47.0",
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-prettier": "^5.0.0",
"prettier": "^3.0.2",
"file-loader": "^5.0.2",
"html-webpack-plugin": "~5.3.2",
"https-browserify": "^1.0.0",
"image-webpack-loader": "^6.0.0",
"node-loader": "^0.6.0",
"prettier": "^3.0.2",
"process": "^0.11.10",
"serve": "14.2.1",
"stream-browserify": "^3.0.0",
@@ -52,5 +55,8 @@
"webpack-node-externals": "^3.0.0"
},
"author": "",
"license": "ISC"
"license": "ISC",
"engines": {
"node": ">= 16.20.2"
}
}

View File

@@ -1,6 +1,6 @@
# tlsn-js
NPM Modules for proving and verifying using TLS Notary.
NPM Modules for proving and verifying using TLS Notary in the browser.
The prover requires a [notary-server](https://github.com/tlsnotary/notary-server) and websockify proxy
@@ -43,12 +43,15 @@ npm install tlsn-js
## Development
```
# make sure you have rust install
# https://www.rust-lang.org/tools/install
npm install
npm run lint
npm test
# this serve a page that will execute the example code at http://localhost:3001
npm run dev
```
## Build
## Build for NPM
```
npm install

View File

@@ -1,20 +1,20 @@
import * as Comlink from 'comlink';
import type TLSN from './worker';
import { Proof } from './types';
const TLSN: any = Comlink.wrap(
const T = Comlink.wrap<TLSN>(
new Worker(new URL('./worker.ts', import.meta.url)),
);
let _tlsn: any | null = null;
let _tlsn: Comlink.Remote<TLSN>;
async function getTLSN(): Promise<any | null> {
async function getTLSN(): Promise<Comlink.Remote<TLSN>> {
if (_tlsn) return _tlsn;
_tlsn = await new TLSN();
// @ts-ignore
_tlsn = await new T();
return _tlsn;
}
export const DEFAULT_LOCAL_SERVER_PUBKEY = `-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEBv36FI4ZFszJa0DQFJ3wWCXvVLFr\ncRzMG5kaTeHGoSzDu6cFqx3uEWYpFGo6C0EOUgf+mEgbktLrXocv5yHzKg==\n-----END PUBLIC KEY-----`;
export const NOTARY_SERVER_PUBKEY = `-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAExpX/4R4z40gI6C/j9zAM39u58LJu\n3Cx5tXTuqhhu/tirnBi5GniMmspOTEsps4ANnPLpMmMSfhJ+IFHbc3qVOA==\n-----END PUBLIC KEY-----\n`;
export const prove = async (
url: string,
options: {
@@ -27,7 +27,7 @@ export const prove = async (
secretHeaders?: string[];
secretResps?: string[];
},
) => {
): Promise<Proof> => {
const {
method,
headers = {},
@@ -41,10 +41,10 @@ export const prove = async (
const tlsn = await getTLSN();
headers["Host"] = new URL(url).host;
headers["Connection"] = "close";
headers['Host'] = new URL(url).host;
headers['Connection'] = 'close';
return tlsn.prover(url, {
const proof = await tlsn.prove(url, {
method,
headers,
body,
@@ -54,13 +54,28 @@ export const prove = async (
secretHeaders,
secretResps,
});
return {
...proof,
notaryUrl,
};
};
export const verify = async (
proof: { session: any; substrings: any },
pubkey = NOTARY_SERVER_PUBKEY,
) => {
proof: Proof,
): Promise<{
time: number;
sent: string;
recv: string;
notaryUrl: string;
}> => {
const res = await fetch(proof.notaryUrl + '/info');
const json: any = await res.json();
const publicKey = json.publicKey as string;
const tlsn = await getTLSN();
const result = await tlsn.verify(proof, pubkey);
return result;
const result = await tlsn.verify(proof, publicKey);
return {
...result,
notaryUrl: proof.notaryUrl,
};
};

82
src/tlsn.ts Normal file
View File

@@ -0,0 +1,82 @@
import init, { initThreadPool, prover, verify } from '../wasm/prover/pkg';
export default class TLSN {
private startPromise: any;
private resolveStart: any;
constructor() {
console.log('worker module initiated.');
this.startPromise = new Promise((resolve) => {
this.resolveStart = resolve;
});
this.start();
}
async start() {
console.log('start');
const numConcurrency = navigator.hardwareConcurrency;
console.log('!@# navigator.hardwareConcurrency=', numConcurrency);
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;
maxTranscriptSize?: number;
notaryUrl?: string;
websocketProxyUrl?: string;
secretHeaders?: string[];
secretResps?: string[];
},
) {
try {
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;
} catch (e: any) {
console.log(e);
return e;
}
}
async verify(proof: any, pubkey: string) {
await this.waitForStart();
const raw = await verify(JSON.stringify(proof), pubkey);
return JSON.parse(raw);
}
}

94
src/types.ts Normal file
View File

@@ -0,0 +1,94 @@
export interface Proof {
session: Session;
substrings: Substrings;
notaryUrl: string;
}
export interface Session {
header: Header;
server_name: ServerName;
signature: Signature;
handshake_data_decommitment: HandshakeDataDecommitment;
}
export interface HandshakeDataDecommitment {
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: any[];
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 {
start: number;
end: number;
}

View File

@@ -1,89 +1,6 @@
import * as Comlink from 'comlink';
import init, {
initThreadPool,
prover,
verify,
} from '../wasm/prover/pkg/tlsn_extension_rs';
import TLSN from './tlsn';
export default class TLSN {
private startPromise: any;
private resolveStart: any;
export default TLSN;
constructor() {
console.log('worker module initiated.');
this.startPromise = new Promise((resolve) => {
this.resolveStart = resolve;
});
this.start();
}
async start() {
console.log('start');
const numConcurrency = navigator.hardwareConcurrency;
console.log('!@# navigator.hardwareConcurrency=', numConcurrency);
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 prover(
url: string,
options?: {
method?: string;
headers?: { [key: string]: string };
body?: string;
maxTranscriptSize?: number;
notaryUrl?: string;
websocketProxyUrl?: string;
secretHeaders?: string[];
secretResps?: string[];
},
) {
try {
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;
} catch (e: any) {
console.log(e);
return e;
}
}
async verify(proof: any, pubkey: string) {
await this.waitForStart();
const raw = await verify(JSON.stringify(proof), pubkey);
return JSON.parse(raw);
}
}
Comlink.expose(TLSN);
Comlink.expose(TLSN);

32
utils/check-wasm.sh Normal file
View File

@@ -0,0 +1,32 @@
#!/bin/bash
DIRECTORY="./wasm/prover/target"
FORCE_FLAG=0
# Check if --force or -f flag is passed
for arg in "$@"
do
if [ "$arg" == "--force" ] || [ "$arg" == "-f" ]
then
FORCE_FLAG=1
break
fi
done
# If force flag is set, remove the directories/files and run the npm command
if [ $FORCE_FLAG -eq 1 ]
then
echo "Force flag detected, removing directories and files."
rm -rf ./wasm/prover/pkg
rm -rf ./wasm/prover/target
rm -f ./wasm/prover/Cargo.lock
echo "Running npm run build:wasm"
npm run build:wasm
# If the directory does not exist or is empty, run the npm command
elif [ ! -d "$DIRECTORY" ] || [ -z "$(ls -A $DIRECTORY)" ]
then
echo "Running npm run build:wasm"
npm run build:wasm
else
echo "$DIRECTORY exists and is not empty."
fi

View File

@@ -1,17 +1,18 @@
import { prove, verify, NOTARY_SERVER_PUBKEY } from '../src';
import { prove, verify } from '../src';
(async function runTest() {
console.time('prove');
const proof = await prove('https://swapi.dev/api/people/1', {
method: 'GET',
maxTranscriptSize: 16384,
notaryUrl: 'https://notary.efprivacyscaling.org',
notaryUrl: 'http://localhost:7047',
websocketProxyUrl: 'ws://notary.efprivacyscaling.org:55688?token=swapi.dev',
});
console.timeEnd('prove');
console.time('verify');
const result = await verify(proof, NOTARY_SERVER_PUBKEY);
const result = await verify(proof);
console.timeEnd('verify');
console.log(result);
})();
})();

View File

@@ -158,6 +158,7 @@ pub async fn prover(
let config = ProverConfig::builder()
.id(notarization_response.session_id)
.server_dns(target_host)
.max_transcript_size(options.max_transcript_size)
.build()
.unwrap();
@@ -253,7 +254,6 @@ pub async fn prover(
log!("Got a response from the server");
log!("{}", response.status());
assert!(response.status() == StatusCode::OK);
log!("Request OK");

View File

@@ -1,6 +1,5 @@
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CopyPlugin = require('copy-webpack-plugin');
const path = require('path');
const { compilerOptions } = require('./tsconfig.json');
@@ -36,10 +35,10 @@ module.exports = [
target: 'web',
mode: isProd ? 'production' : 'development',
entry: {
test: path.join(__dirname, 'utils', 'test.ts'),
test: path.join(__dirname, 'utils', 'web.ts'),
},
output: {
path: __dirname + '/test-build',
path: __dirname + '/dev-build/web',
publicPath: '/',
filename: `[name].js`,
},
@@ -80,17 +79,12 @@ module.exports = [
new HtmlWebpackPlugin({
template: './static/index.template.ejs',
filename: `index.html`,
title: process.env.APP_TITLE || 'Zkitter',
inject: true,
}),
new webpack.ContextReplacementPlugin(/gun/),
],
stats: 'minimal',
devServer: {
historyApiFallback: true,
},
// optimization: {
// runtimeChunk: 'single'
// },
},
];