basic setup

This commit is contained in:
0xtsukino
2023-11-14 07:10:20 +03:00
parent 93ab8fb0e8
commit a9fa4151ac
18 changed files with 157 additions and 1427 deletions

7
.gitignore vendored
View File

@@ -1,7 +1,6 @@
wasm/prover/target/
wasm/prover/pkg/
wasm/prover/Cargo.lock
node_modules/
.idea/
.DS_Store
pnpm-lock.yaml
pnpm-lock.yaml
build/
test-build/

View File

@@ -1,52 +1,33 @@
{
"name": "tlsn-js",
"version": "0.0.1",
"description": "JS library for TLS Notary",
"main": "build/index.js",
"types": "build/index.d.ts",
"description": "",
"main": "index.js",
"scripts": {
"test:coverage": "NODE_ENV=development TS_NODE_COMPILER_OPTIONS='{\"module\": \"commonjs\" }' nyc --reporter=lcov --require ts-node/register tape './src/**/*.test.ts' | tap-spec; nyc report ---reporter=text",
"test": "NODE_ENV=development TS_NODE_COMPILER_OPTIONS='{\"module\": \"commonjs\" }' tape -r ts-node/register './src/**/*.test.ts' | tap-spec",
"build:wasm": "wasm-pack build --target web wasm/prover",
"build:types": "tsc --project tsconfig.compile.json",
"build:src": "webpack --config webpack.config.js",
"build": "NODE_ENV=production concurrently --kill-others-on-fail npm:build:types npm:build:src",
"dev": "NODE_ENV=development webpack-dev-server --config webpack.config.js --hot",
"eslint": "eslint . --fix",
"tsc": "tsc --noEmit",
"lint": "concurrently npm:tsc npm:eslint"
},
"files": ["build/", "src/", "readme.md"],
"author": "0xtsukino",
"license": "MIT",
"dependencies": {
"buffer": "^6.0.3",
"comlink": "^4.4.1"
"build:test": "webpack --config webpack.test.config.js",
"test": "webpack-dev-server --config webpack.test.config.js --hot"
},
"devDependencies": {
"@babel/core": "^7.20.12",
"@babel/plugin-proposal-class-properties": "^7.18.6",
"@babel/preset-env": "^7.20.2",
"@types/node": "^20.4.10",
"babel-eslint": "^10.1.0",
"babel-loader": "^9.1.2",
"babel-preset-react-app": "^10.0.1",
"clean-webpack-plugin": "^4.0.0",
"copy-webpack-plugin": "^11.0.0",
"css-loader": "^6.7.3",
"eslint": "^8.31.0",
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-import": "^2.27.4",
"eslint-plugin-prettier": "^5.0.0",
"html-loader": "^4.2.0",
"html-webpack-plugin": "^5.5.0",
"prettier": "^3.0.2",
"source-map-loader": "^3.0.1",
"terser-webpack-plugin": "^5.3.6",
"ts-loader": "^9.4.2",
"typescript": "^4.9.4",
"browserify": "^17.0.0",
"concurrently": "^5.1.0",
"constants-browserify": "^1.0.0",
"copy-webpack-plugin": "^5.0.5",
"crypto-browserify": "^3.12.0",
"eslint": "^5.16.0",
"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",
"process": "^0.11.10",
"stream-browserify": "^3.0.0",
"ts-loader": "^6.2.1",
"typescript": "^4.9.3",
"webpack": "^5.75.0",
"webpack-cli": "^4.10.0",
"webpack-dev-server": "^4.11.1"
}
"webpack-cli": "^5.0.0",
"webpack-dev-server": "^4.11.1",
"webpack-node-externals": "^3.0.0"
},
"author": "",
"license": "ISC"
}

View File

@@ -1,26 +0,0 @@
# `tlsn-js`
`tlsn-js` is a library made to interact with the `tlsn` protocol.
It has two functions: `prove` and `verify`
## Install as NPM Package
```
npm install tlsn-js
```
## Development
```
npm install
npm run lint
npm test
```
## Build
```
npm install
npm run build
```

1
src/app.tsx Normal file
View File

@@ -0,0 +1 @@
console.log('hi')

View File

@@ -1,5 +0,0 @@
import tape from 'tape';
tape('test', (t) => {
t.end();
});

View File

@@ -1,54 +0,0 @@
import * as Comlink from 'comlink';
const TLSN: any = Comlink.wrap(
new Worker(new URL('./worker.ts', import.meta.url)),
);
let _tlsn: any | null = null;
async function getTLSN(): Promise<any | null> {
if (_tlsn) return _tlsn;
_tlsn = await new TLSN();
return _tlsn;
}
export const prove = async (
options: {
url: string,
method?: string;
headers?: { [key: string]: string };
body?: string;
maxTranscriptSize?: number;
notaryUrl: string;
websocketProxyUrl: string;
secretHeaders?: string[];
secretResps?: string[];
},
) => {
const {
url,
method = 'GET',
headers = {},
body,
secretHeaders = [],
secretResps = [],
maxTranscriptSize = 20000,
notaryUrl,
websocketProxyUrl,
} = options;
const tlsn = await getTLSN();
const proof = await tlsn.prover(url, {
method,
headers,
body,
maxTranscriptSize,
notaryUrl,
websocketProxyUrl,
secretHeaders,
secretResps,
});
return JSON.parse(proof);
}

View File

@@ -1,421 +0,0 @@
export interface Proof {
session: Session
substrings: Substrings
}
export interface Session {
header: Header
server_name: ServerName
signature: Signature
handshake_data_decommitment: HandshakeDataDecommitment
}
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 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: number[][]
ocsp_response: any[]
scts: any
}
export interface ServerKxDetails {
kx_params: number[]
kx_sig: KxSig
}
export interface KxSig {
scheme: string
sig: number[]
}
export interface Substrings {
openings: Openings
inclusion_proof: InclusionProof
}
export interface Openings {
"0": N0[]
"1": N1[]
"2": N2[]
"3": N3[]
"4": N4[]
"5": N5[]
"6": N6[]
"7": N7[]
"8": N8[]
"9": N9[]
"10": N10[]
"11": N11[]
"12": N12[]
"13": N13[]
"14": N14[]
"15": N15[]
"16": N16[]
"17": N17[]
"35": N35[]
}
export interface N0 {
kind?: string
ranges?: Range[]
direction?: string
Blake3?: Blake3
}
export interface Range {
start: number
end: number
}
export interface Blake3 {
data: number[]
nonce: number[]
}
export interface N1 {
kind?: string
ranges?: Range2[]
direction?: string
Blake3?: Blake32
}
export interface Range2 {
start: number
end: number
}
export interface Blake32 {
data: number[]
nonce: number[]
}
export interface N2 {
kind?: string
ranges?: Range3[]
direction?: string
Blake3?: Blake33
}
export interface Range3 {
start: number
end: number
}
export interface Blake33 {
data: number[]
nonce: number[]
}
export interface N3 {
kind?: string
ranges?: Range4[]
direction?: string
Blake3?: Blake34
}
export interface Range4 {
start: number
end: number
}
export interface Blake34 {
data: number[]
nonce: number[]
}
export interface N4 {
kind?: string
ranges?: Range5[]
direction?: string
Blake3?: Blake35
}
export interface Range5 {
start: number
end: number
}
export interface Blake35 {
data: number[]
nonce: number[]
}
export interface N5 {
kind?: string
ranges?: Range6[]
direction?: string
Blake3?: Blake36
}
export interface Range6 {
start: number
end: number
}
export interface Blake36 {
data: number[]
nonce: number[]
}
export interface N6 {
kind?: string
ranges?: Range7[]
direction?: string
Blake3?: Blake37
}
export interface Range7 {
start: number
end: number
}
export interface Blake37 {
data: number[]
nonce: number[]
}
export interface N7 {
kind?: string
ranges?: Range8[]
direction?: string
Blake3?: Blake38
}
export interface Range8 {
start: number
end: number
}
export interface Blake38 {
data: number[]
nonce: number[]
}
export interface N8 {
kind?: string
ranges?: Range9[]
direction?: string
Blake3?: Blake39
}
export interface Range9 {
start: number
end: number
}
export interface Blake39 {
data: number[]
nonce: number[]
}
export interface N9 {
kind?: string
ranges?: Range10[]
direction?: string
Blake3?: Blake310
}
export interface Range10 {
start: number
end: number
}
export interface Blake310 {
data: number[]
nonce: number[]
}
export interface N10 {
kind?: string
ranges?: Range11[]
direction?: string
Blake3?: Blake311
}
export interface Range11 {
start: number
end: number
}
export interface Blake311 {
data: number[]
nonce: number[]
}
export interface N11 {
kind?: string
ranges?: Range12[]
direction?: string
Blake3?: Blake312
}
export interface Range12 {
start: number
end: number
}
export interface Blake312 {
data: number[]
nonce: number[]
}
export interface N12 {
kind?: string
ranges?: Range13[]
direction?: string
Blake3?: Blake313
}
export interface Range13 {
start: number
end: number
}
export interface Blake313 {
data: number[]
nonce: number[]
}
export interface N13 {
kind?: string
ranges?: Range14[]
direction?: string
Blake3?: Blake314
}
export interface Range14 {
start: number
end: number
}
export interface Blake314 {
data: number[]
nonce: number[]
}
export interface N14 {
kind?: string
ranges?: Range15[]
direction?: string
Blake3?: Blake315
}
export interface Range15 {
start: number
end: number
}
export interface Blake315 {
data: number[]
nonce: number[]
}
export interface N15 {
kind?: string
ranges?: Range16[]
direction?: string
Blake3?: Blake316
}
export interface Range16 {
start: number
end: number
}
export interface Blake316 {
data: number[]
nonce: number[]
}
export interface N16 {
kind?: string
ranges?: Range17[]
direction?: string
Blake3?: Blake317
}
export interface Range17 {
start: number
end: number
}
export interface Blake317 {
data: number[]
nonce: number[]
}
export interface N17 {
kind?: string
ranges?: Range18[]
direction?: string
Blake3?: Blake318
}
export interface Range18 {
start: number
end: number
}
export interface Blake318 {
data: number[]
nonce: number[]
}
export interface N35 {
kind?: string
ranges?: Range19[]
direction?: string
Blake3?: Blake319
}
export interface Range19 {
start: number
end: number
}
export interface Blake319 {
data: number[]
nonce: number[]
}
export interface InclusionProof {
proof: number[]
total_leaves: number
}

View File

@@ -1,67 +0,0 @@
import * as Comlink from 'comlink';
import init, {
initThreadPool,
prover,
verify,
} from '../wasm/prover/pkg/tlsn_extension_rs';
import {Proof} from "./types";
export class TLSN {
private startPromise: any;
private resolveStart: any;
constructor() {
this.startPromise = new Promise((resolve) => {
this.resolveStart = resolve;
});
this.start();
}
async start() {
const numConcurrency = navigator.hardwareConcurrency;
const res = await init();
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[];
},
): Promise<Proof> {
await this.waitForStart();
const resProver = await prover(
url,
{
...options,
notaryUrl: options?.notaryUrl,
websocketProxyUrl: options?.websocketProxyUrl,
},
options?.secretHeaders || [],
options?.secretResps || [],
);
return JSON.parse(resProver);
}
async verify(proof: Proof, pubkey: string) {
await this.waitForStart();
const raw = await verify(JSON.stringify(proof), pubkey);
return JSON.parse(raw);
}
}
Comlink.expose(TLSN);

15
static/index.template.ejs Normal file
View File

@@ -0,0 +1,15 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="manifest" href="/manifest.json">
<title>tlsn-js development</title>
</head>
<body>
<script>
global = globalThis //<- this should be enough
</script>
<div id="root"></div>
</body>
</html>

View File

@@ -1,11 +0,0 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"noEmit": false,
"declaration": true,
"declarationMap": true,
"emitDeclarationOnly": true,
"sourceMap": true,
"outDir": "build"
}
}

View File

@@ -1,22 +1,23 @@
{
"compilerOptions": {
"noEmit": true,
"baseUrl": ".",
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"moduleResolution": "node",
"module": "esnext",
"target": "es2015",
"lib": ["esnext", "dom"],
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"useUnknownInCatchVariables": false,
"strictPropertyInitialization": false
"sourceMap": true,
"noEmit": true,
"jsx": "react"
},
"include": ["src"],
"exclude": ["**/*.test.ts"]
"include": [
"src"
]
}

View File

@@ -1,111 +0,0 @@
[package]
name = "tlsn-extension-rs"
version = "0.1.0"
authors = ["The tlsn-extension Developers"]
edition = "2018"
rust-version = "1.56"
[lib]
crate-type = ["cdylib"]
[dependencies]
rayon = "1.5"
wasm-bindgen-rayon = "1.0"
wasm-bindgen = "0.2.87"
js-sys = "0.3.64"
tracing = "0.1"
getrandom = { version = "0.2", features = ["js"] }
ws_stream_wasm = "0.7.4"
wasm-bindgen-futures = "0.4.37"
tokio-util = "0.7"
futures = "0.3"
serde_json = "1.0"
serde = { version = "1.0.147", features = ["derive"] }
serde-wasm-bindgen = "0.4"
url = { version = "2.0", features = ["serde"] }
futures-util = "0.3.28"
chrono = "0.4"
elliptic-curve = { version = "0.13.5", features = ["pkcs8"] }
p256 = { version = "0.13", features = ["pem", "ecdsa"] }
hyper = { version = "0.14", features = ["client", "http1"] }
console_error_panic_hook = "0.1.7"
tracing-web = "0.1.2"
tracing-subscriber = { version = "0.3", features = ["time"] }
# 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", features = ["wasm-bindgen"] }
# Used to calculate elapsed time.
web-time = "0.2"
# tlsn-prover = { path = "../tlsn/tlsn/tlsn-prover", features = ["tracing"] }
[dependencies.tlsn-prover]
git = "https://github.com/mhchia/tlsn.git"
branch = "dev-20231006-webtime-2"
package = "tlsn-prover"
features = ["tracing"]
[dependencies.tlsn-core]
git = "https://github.com/mhchia/tlsn.git"
branch = "dev-20231006-webtime-2"
package = "tlsn-core"
[dependencies.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',
]
# Replace ring with the forked version `ring-xous` implemented in pure rust
# to make it compiled to wasm.
# Refs:
# - Rationale for `ring-xous`: https://www.bunniestudios.com/blog/?p=6521
# - Issue for wasm comptability: https://github.com/briansmith/ring/issues/918
[patch.crates-io.ring]
git="https://github.com/betrusted-io/ring-xous"
branch="0.16.20-cleanup"
[patch.crates-io.ws_stream_wasm]
# path = "../../../others/ws_stream_wasm"
# Use the patched ws_stream_wasm to fix the issue https://github.com/najamelan/ws_stream_wasm/issues/12#issuecomment-1711902958
git="https://github.com/mhchia/ws_stream_wasm"
branch="dev"
# [patch.'https://github.com/tlsnotary/tlsn-utils']
# # Use single cpu backend
# tlsn-utils = { git = 'https://www.github.com/mhchia/tlsn-utils.git', rev = "46327f0" }
# tlsn-utils-aio = { git = 'https://www.github.com/mhchia/tlsn-utils.git', rev = "46327f0" }
# 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", optional = true }
[dev-dependencies]
wasm-bindgen-test = "0.3.34"
[profile.release]
# Tell `rustc` to optimize for small code size.
[package.metadata.wasm-pack.profile.release]
wasm-opt = false

View File

@@ -1,485 +0,0 @@
mod requests;
mod requestOpt;
use std::panic;
use std::ops::Range;
use web_time::Instant;
use hyper::{body::to_bytes, Body, Request, StatusCode};
use futures::{AsyncWriteExt, TryFutureExt};
use futures::channel::oneshot;
use tlsn_prover::{Prover, ProverConfig};
// use tokio::io::AsyncWriteExt as _;
use tokio_util::compat::{FuturesAsyncReadCompatExt, TokioAsyncReadCompatExt};
use tokio_util::compat::FuturesAsyncWriteCompatExt;
use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::spawn_local;
use tracing_web::{MakeConsoleWriter, performance_layer};
use tracing_subscriber::fmt::format::Pretty;
use tracing_subscriber::fmt::time::UtcTime;
use tracing_subscriber::prelude::*;
use ws_stream_wasm::{*};
use crate::requests::{NotarizationSessionRequest, NotarizationSessionResponse, ClientType};
use crate::requestOpt::{RequestOptions, VerifyResult};
pub use wasm_bindgen_rayon::init_thread_pool;
// use rayon::iter::IntoParallelRefIterator;
use rayon::prelude::*;
use wasm_bindgen_futures::JsFuture;
use web_sys::{Request as WebsysRequest, RequestInit, Headers, RequestMode, Response};
use js_sys::{JSON, Array};
use url::Url;
use tlsn_core::proof::{SessionProof, TlsProof};
use std::time::Duration;
use elliptic_curve::pkcs8::DecodePublicKey;
// A macro to provide `println!(..)`-style syntax for `console.log` logging.
macro_rules! log {
( $( $t:tt )* ) => {
web_sys::console::log_1(&format!( $( $t )* ).into());
}
}
extern crate console_error_panic_hook;
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_namespace = self)]
fn fetch(request: &web_sys::Request) -> js_sys::Promise;
}
async fn fetch_as_json_string(url: &str, opts: &RequestInit) -> Result<String, JsValue> {
let request = WebsysRequest::new_with_str_and_init(url, opts)?;
let promise = fetch(&request);
let future = JsFuture::from(promise);
let resp_value = future.await?;
let resp: Response = resp_value.dyn_into().unwrap();
let json = JsFuture::from(resp.json()?).await?;
let stringified = JSON::stringify(&json).unwrap();
Ok(stringified.as_string().unwrap())
}
#[wasm_bindgen]
pub async fn prover(
targetUrl: &str,
val: JsValue,
secret_headers: JsValue,
secret_body: JsValue,
) -> Result<String, JsValue> {
log!("target_url: {}", targetUrl);
let target_url = Url::parse(targetUrl).expect("url must be valid");
log!("target_url.host: {}", target_url.host().unwrap());
let options: RequestOptions = serde_wasm_bindgen::from_value(val).unwrap();
log!("done!");
log!("options.notary_url: {}", options.notary_url.as_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_writer(MakeConsoleWriter); // write events to the console
// let perf_layer = performance_layer()
// .with_details_from_fields(Pretty::default());
// tracing_subscriber::registry()
// .with(tracing_subscriber::filter::LevelFilter::DEBUG)
// .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(console_error_panic_hook::hook));
let start_time = Instant::now();
/*
* Connect Notary with websocket
*/
let mut opts = RequestInit::new();
log!("method: {}", "POST");
opts.method("POST");
// opts.method("GET");
opts.mode(RequestMode::Cors);
// set headers
let headers = Headers::new().unwrap();
let notary_url = Url::parse(options.notary_url.as_str()).expect("url must be valid");
let notary_ssl = notary_url.scheme() == "https" || notary_url.scheme() == "wss";
let notary_host = notary_url.authority();
headers.append("Host", notary_host).unwrap();
headers.append("Content-Type", "application/json").unwrap();
opts.headers(&headers);
log!("notary_host: {}", notary_host);
// set body
let payload = serde_json::to_string(&NotarizationSessionRequest {
client_type: ClientType::Websocket,
max_transcript_size: Some(options.max_transcript_size),
})
.unwrap();
opts.body(Some(&JsValue::from_str(&payload)));
// url
let url = format!(
"{}://{}/session",
if notary_ssl { "https" } else { "http" },
notary_host
);
log!("Request: {}", url);
let rust_string = fetch_as_json_string(&url, &opts).await.unwrap();
let notarization_response = serde_json::from_str::<NotarizationSessionResponse>(&rust_string).unwrap();
log!("Response: {}", rust_string);
log!("Notarization response: {:?}", notarization_response,);
let notary_wss_url = format!(
"{}://{}/notarize?sessionId={}",
if notary_ssl { "wss" } else { "ws" },
notary_host,
notarization_response.session_id
);
let (mut notary_ws_meta, mut notary_ws_stream) = WsMeta::connect(
notary_wss_url,
None
).await
.expect_throw( "assume the notary ws connection succeeds" );
let mut notary_ws_stream_into = notary_ws_stream.into_io();
/*
Connect Application Server with websocket proxy
*/
let (mut client_ws_meta, mut client_ws_stream) = WsMeta::connect(
options.websocket_proxy_url,
None ).await
.expect_throw( "assume the client ws connection succeeds" );
let mut client_ws_stream_into = client_ws_stream.into_io();
log!("!@# 0");
let target_host = target_url.host_str().unwrap();
// Basic default prover config
let config = ProverConfig::builder()
.id(notarization_response.session_id)
.server_dns(target_host)
.build()
.unwrap();
log!("!@# 1");
// Create a Prover and set it up with the Notary
// This will set up the MPC backend prior to connecting to the server.
let prover = Prover::new(config)
.setup(notary_ws_stream_into)
.await
.unwrap();
// 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.
let (mpc_tls_connection, prover_fut) = prover.connect(client_ws_stream_into).await.unwrap();
log!("!@# 3");
// let prover_task = tokio::spawn(prover_fut);
let (prover_sender, prover_receiver) = oneshot::channel();
let handled_prover_fut = async {
match prover_fut.await {
Ok(prover_result) => {
// Send the prover
let _ = prover_sender.send(prover_result);
},
Err(err) => {
panic!("An error occurred in prover_fut: {:?}", err);
}
}
};
spawn_local(handled_prover_fut);
log!("!@# 7");
// Attach the hyper HTTP client to the TLS connection
let (mut request_sender, connection) = hyper::client::conn::handshake(mpc_tls_connection.compat())
.await
.unwrap();
log!("!@# 8");
// Spawn the HTTP task to be run concurrently
// let connection_task = tokio::spawn(connection.without_shutdown());
let (connection_sender, connection_receiver) = oneshot::channel();
let connection_fut = connection.without_shutdown();
let handled_connection_fut = async {
match connection_fut.await {
Ok(connection_result) => {
// Send the connection
let _ = connection_sender.send(connection_result);
},
Err(err) => {
panic!("An error occurred in connection_task: {:?}", err);
}
}
};
spawn_local(handled_connection_fut);
log!("!@# 9 - {} request to {}", options.method.as_str(), targetUrl);
let mut req_with_header = Request::builder()
.uri(targetUrl)
.method(options.method.as_str());
for (key, value) in options.headers {
log!("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() {
log!("empty body");
req_with_body = req_with_header.body(Body::empty());
} else {
log!("added body - {}", options.body.as_str());
req_with_body = req_with_header.body(Body::from(options.body));
}
let unwrapped_request = req_with_body.unwrap();
log!("Starting an MPC TLS connection with the server");
// Send the request to the Server and get a response via the MPC TLS connection
let response = request_sender.send_request(unwrapped_request).await.unwrap();
log!("Got a response from the server");
assert!(response.status() == StatusCode::OK);
log!("Request OK");
// Pretty printing :)
let payload = to_bytes(response.into_body()).await.unwrap().to_vec();
let parsed =
serde_json::from_str::<serde_json::Value>(&String::from_utf8_lossy(&payload)).unwrap();
log!("!@# 10");
log!("{}", serde_json::to_string_pretty(&parsed).unwrap());
log!("!@# 11");
// Close the connection to the server
// let mut client_socket = connection_task.await.unwrap().unwrap().io.into_inner();
let mut client_socket = connection_receiver.await.unwrap().io.into_inner();
log!("!@# 12");
client_socket.close().await.unwrap();
log!("!@# 13");
// The Prover task should be done now, so we can grab it.
// let mut prover = prover_task.await.unwrap().unwrap();
let mut prover = prover_receiver.await.unwrap();
let mut prover = prover.start_notarize();
log!("!@# 14");
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!("!@# 15");
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.clone()).unwrap())
.collect::<Vec<_>>();
sent_private_ranges.iter().for_each(|range| {
builder.commit_sent(range.clone()).unwrap();
});
let recv_pub_commitment_ids = recv_public_ranges
.iter()
.map(|range| builder.commit_recv(range.clone()).unwrap())
.collect::<Vec<_>>();
recv_private_ranges.iter().for_each(|range| {
builder.commit_recv(range.clone()).unwrap();
});
// Finalize, returning the notarized session
let notarized_session = prover.finalize().await.unwrap();
log!("Notarization complete!");
// Create a proof for all committed data in this session
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())
.for_each(|id| {
proof_builder.reveal(*id).unwrap();
});
let substrings_proof = proof_builder.build().unwrap();
let proof = TlsProof {
session: session_proof,
substrings: substrings_proof,
};
let res = serde_json::to_string_pretty(&proof).unwrap();
let duration = start_time.elapsed();
log!("!@# request takes: {} seconds", duration.as_secs());
Ok(res)
}
#[wasm_bindgen]
pub async fn verify(
proof: &str,
notary_pubkey_str: &str,
) -> Result<String, JsValue> {
log!("!@# proof {}", proof);
let proof: TlsProof = serde_json::from_str(proof).unwrap();
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;
log!("!@# notary_pubkey {}, {}", notary_pubkey_str, notary_pubkey_str.len());
session
.verify_with_default_cert_verifier(get_notary_pubkey(notary_pubkey_str))
.unwrap();
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.
server_name,
..
} = 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).unwrap();
// Replace the bytes which the Prover chose not to disclose with 'X'
sent.set_redacted(b'X');
recv.set_redacted(b'X');
log!("-------------------------------------------------------------------");
log!(
"Successfully verified that the bytes below came from a session with {:?} at {}.",
server_name, time
);
log!("Note that the bytes which the Prover chose not to disclose are shown as X.");
log!("Bytes sent:");
log!("{}", String::from_utf8(sent.data().to_vec()).unwrap());
log!("Bytes received:");
log!("{}", String::from_utf8(recv.data().to_vec()).unwrap());
log!("-------------------------------------------------------------------");
let result = VerifyResult {
server_name: String::from(server_name.as_str()),
time: header.time(),
sent: String::from_utf8(sent.data().to_vec()).unwrap(),
recv: String::from_utf8(recv.data().to_vec()).unwrap(),
};
let res = serde_json::to_string_pretty(&result).unwrap();
Ok(res)
}
fn print_type_of<T: ?Sized>(_: &T) {
log!("{}", std::any::type_name::<T>())
}
/// Returns a Notary pubkey trusted by this Verifier
fn get_notary_pubkey(pubkey: &str) -> p256::PublicKey {
// from https://github.com/tlsnotary/notary-server/tree/main/src/fixture/notary/notary.key
// converted with `openssl ec -in notary.key -pubout -outform PEM`
p256::PublicKey::from_public_key_pem(pubkey).unwrap()
}
/// 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) -> Vec<Vec<u8>> {
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().unwrap();
let secret_bytes = secret_str.into_bytes();
byte_slices.push(secret_bytes);
}
byte_slices
}

View File

@@ -1,29 +0,0 @@
use std::{collections::HashMap};
use serde::{Deserialize, Serialize};
/// 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_transcript_size: 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,
}

View File

@@ -1,29 +0,0 @@
use std::{collections::HashMap, sync::Arc};
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 transcript size in bytes
pub max_transcript_size: 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,
}

View File

@@ -1,76 +0,0 @@
const webpack = require('webpack');
const path = require('path');
const isProd = process.env.NODE_ENV === 'production';
const envPlugin = new webpack.EnvironmentPlugin({
NODE_ENV: 'development',
});
const rules = [
{
test: /\.tsx?$/,
exclude: [/(node_modules|.webpack)/],
rules: [
{
loader: 'ts-loader',
options: {
configFile: 'tsconfig.json',
transpileOnly: true,
},
},
],
},
{
test: /\.node$/,
use: 'node-loader',
},
];
module.exports = [
{
mode: isProd ? 'production' : 'development',
entry: {
browser: path.join(__dirname, 'src', 'index.ts'),
},
devtool: 'source-map',
resolve: {
extensions: ['.ts', '.js'],
fallback: {
crypto: require.resolve('crypto-browserify'),
// os: require.resolve('os-browserify/browser'),
stream: require.resolve('stream-browserify'),
// "assert": require.resolve("assert"),
// "url": require.resolve("url"),
// "zlib": require.resolve("browserify-zlib"),
// "http": require.resolve("stream-http"),
// "https": require.resolve("https-browserify"),
// constants: require.resolve('constants-browserify'),
// fs: false,
buffer: require.resolve('buffer/'),
process: require.resolve('process/browser'),
},
},
node: {
__dirname: true,
},
module: {
rules: [...rules],
},
output: {
path: __dirname + '/build',
filename: `[name].js`,
libraryTarget: 'umd',
globalObject: 'this',
umdNamedDefine: true,
},
plugins: [
envPlugin,
new webpack.ProvidePlugin({
Buffer: ['buffer', 'Buffer'],
}),
new webpack.ProvidePlugin({
process: 'process',
}),
],
},
];

View File

@@ -1,57 +0,0 @@
const webpack = require('webpack');
const path = require('path');
const isProd = process.env.NODE_ENV === 'production';
const envPlugin = new webpack.EnvironmentPlugin({
NODE_ENV: 'development',
});
const rules = [
{
test: /\.tsx?$/,
exclude: [/(node_modules|.webpack)/],
rules: [
{
loader: 'ts-loader',
options: {
configFile: 'tsconfig.json',
transpileOnly: true,
},
},
],
},
{
test: /\.node$/,
use: 'node-loader',
},
];
module.exports = [
{
mode: isProd ? 'production' : 'development',
entry: {
index: path.join(__dirname, 'src', 'index.ts'),
},
target: 'node',
devtool: 'source-map',
resolve: {
extensions: ['.ts', '.js'],
},
node: {
__dirname: true,
},
module: {
rules: [...rules],
},
output: {
path: __dirname + '/build',
filename: `[name].js`,
libraryTarget: 'umd',
globalObject: 'this',
umdNamedDefine: true,
},
plugins: [
envPlugin,
],
},
];

104
webpack.test.config.js Normal file
View File

@@ -0,0 +1,104 @@
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');
const isProd = process.env.NODE_ENV === 'production';
const devServerEntries = [
// 'webpack-dev-server/client?http://localhost:8080',
// 'webpack/hot/only-dev-server',
];
const envPlugin = new webpack.EnvironmentPlugin({
NODE_ENV: 'development',
});
const rules = [
{
test: /\.node$/,
use: 'node-loader',
},
{
test: /\.tsx?$/,
exclude: /(node_modules|.webpack)/,
use: [
{
loader: 'ts-loader',
options: {
transpileOnly: true,
},
},
],
},
];
const rendererRules = [];
module.exports = [
{
target: 'web',
mode: isProd ? 'production' : 'development',
entry: {
app: path.join(__dirname, 'src', 'app.tsx'),
},
output: {
path: __dirname + '/build',
publicPath: isProd ? '/' : 'http://localhost:8080/',
filename: `[name].js`,
},
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,
},
},
module: {
rules: [...rules, ...rendererRules],
},
plugins: [
envPlugin,
new webpack.ProvidePlugin({
Buffer: ['buffer', 'Buffer'],
}),
new webpack.ProvidePlugin({
process: 'process',
}),
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,
proxy: {
'/rest': {
target: `http://127.0.0.1:8080`,
secure: true,
},
},
},
// optimization: {
// runtimeChunk: 'single'
// },
},
];