mirror of
https://github.com/tlsnotary/tlsn.git
synced 2026-01-09 14:48:13 -05:00
feat(tlsn-wasm): wasm bindings (#536)
* feat(tlsn-wasm): wasm bindings * fix wasm ci * clippy * clippy allow * add build config and remove redundant to_vec
This commit is contained in:
25
.github/workflows/ci.yml
vendored
25
.github/workflows/ci.yml
vendored
@@ -59,7 +59,7 @@ jobs:
|
||||
- name: Test
|
||||
run: cargo test
|
||||
build-wasm:
|
||||
name: Build for target wasm32-unknown-unknown
|
||||
name: Build and test wasm
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
@@ -71,11 +71,30 @@ jobs:
|
||||
targets: wasm32-unknown-unknown
|
||||
toolchain: stable
|
||||
|
||||
- name: Install nightly rust toolchain
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
targets: wasm32-unknown-unknown,x86_64-unknown-linux-gnu
|
||||
toolchain: nightly
|
||||
components: rust-src
|
||||
|
||||
- name: Install chromedriver
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y chromium-chromedriver
|
||||
|
||||
- name: Install wasm-pack
|
||||
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
|
||||
|
||||
- name: Use caching
|
||||
uses: Swatinem/rust-cache@v2.5.0
|
||||
with:
|
||||
workspaces: crates/wasm-test-runner -> ../target
|
||||
|
||||
- name: Build
|
||||
run: cargo build -p tlsn-tls-client -p tlsn-tls-core -p tlsn-core --target wasm32-unknown-unknown
|
||||
- name: Run tests
|
||||
run: |
|
||||
cd crates/wasm-test-runner
|
||||
RUST_LOG=debug ./run.sh
|
||||
tests-integration:
|
||||
name: Run tests release build
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -23,7 +23,9 @@ members = [
|
||||
"crates/tls/core",
|
||||
"crates/tls/mpc",
|
||||
"crates/tls/server-fixture",
|
||||
"crates/wasm-test-runner",
|
||||
"crates/verifier",
|
||||
"crates/wasm",
|
||||
]
|
||||
resolver = "2"
|
||||
|
||||
@@ -97,8 +99,10 @@ http-body-util = { version = "0.1" }
|
||||
hyper = { version = "1.1" }
|
||||
hyper-util = { version = "0.1" }
|
||||
log = { version = "0.4" }
|
||||
once_cell = { version = "1.19" }
|
||||
opaque-debug = { version = "0.3" }
|
||||
p256 = { version = "0.13" }
|
||||
pin-project-lite = { version = "0.2" }
|
||||
rand = { version = "0.8" }
|
||||
rand_chacha = { version = "0.3" }
|
||||
rand_core = { version = "0.6" }
|
||||
|
||||
@@ -32,6 +32,11 @@ impl ProverConfig {
|
||||
ProverConfigBuilder::default()
|
||||
}
|
||||
|
||||
/// Returns the instance id.
|
||||
pub fn id(&self) -> &str {
|
||||
&self.id
|
||||
}
|
||||
|
||||
/// Returns the maximum number of bytes that can be sent.
|
||||
pub fn max_sent_data(&self) -> usize {
|
||||
self.max_sent_data
|
||||
|
||||
@@ -38,12 +38,13 @@ use uid_mux::FramedUidMux as _;
|
||||
#[cfg(feature = "formats")]
|
||||
use crate::http::{state as http_state, HttpProver, HttpProverError};
|
||||
|
||||
use tracing::{debug, debug_span, instrument, Instrument};
|
||||
use tracing::{debug, info_span, instrument, Instrument, Span};
|
||||
|
||||
/// A prover instance.
|
||||
#[derive(Debug)]
|
||||
pub struct Prover<T: state::ProverState> {
|
||||
config: ProverConfig,
|
||||
span: Span,
|
||||
state: T,
|
||||
}
|
||||
|
||||
@@ -54,8 +55,10 @@ impl Prover<state::Initialized> {
|
||||
///
|
||||
/// * `config` - The configuration for the prover.
|
||||
pub fn new(config: ProverConfig) -> Self {
|
||||
let span = info_span!("prover", id = config.id());
|
||||
Self {
|
||||
config,
|
||||
span,
|
||||
state: state::Initialized,
|
||||
}
|
||||
}
|
||||
@@ -68,7 +71,7 @@ impl Prover<state::Initialized> {
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `socket` - The socket to the TLS verifier.
|
||||
#[instrument(level = "debug", skip_all, err)]
|
||||
#[instrument(parent = &self.span, level = "debug", skip_all, err)]
|
||||
pub async fn setup<S: AsyncWrite + AsyncRead + Send + Unpin + 'static>(
|
||||
self,
|
||||
socket: S,
|
||||
@@ -97,6 +100,7 @@ impl Prover<state::Initialized> {
|
||||
|
||||
Ok(Prover {
|
||||
config: self.config,
|
||||
span: self.span,
|
||||
state: state::Setup {
|
||||
io,
|
||||
mux_ctrl,
|
||||
@@ -119,7 +123,7 @@ impl Prover<state::Setup> {
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `socket` - The socket to the server.
|
||||
#[instrument(level = "debug", skip_all, err)]
|
||||
#[instrument(parent = &self.span, level = "debug", skip_all, err)]
|
||||
pub async fn connect<S: AsyncWrite + AsyncRead + Send + Unpin + 'static>(
|
||||
self,
|
||||
socket: S,
|
||||
@@ -149,6 +153,7 @@ impl Prover<state::Setup> {
|
||||
let start_time = web_time::UNIX_EPOCH.elapsed().unwrap().as_secs();
|
||||
|
||||
let fut = Box::pin({
|
||||
let span = self.span.clone();
|
||||
let mpc_ctrl = mpc_ctrl.clone();
|
||||
async move {
|
||||
let conn_fut = async {
|
||||
@@ -168,6 +173,7 @@ impl Prover<state::Setup> {
|
||||
|
||||
Ok(Prover {
|
||||
config: self.config,
|
||||
span: self.span,
|
||||
state: state::Closed {
|
||||
io,
|
||||
mux_ctrl,
|
||||
@@ -185,7 +191,7 @@ impl Prover<state::Setup> {
|
||||
},
|
||||
})
|
||||
}
|
||||
.instrument(debug_span!("prover"))
|
||||
.instrument(span)
|
||||
});
|
||||
|
||||
Ok((
|
||||
@@ -222,6 +228,7 @@ impl Prover<state::Closed> {
|
||||
pub fn start_notarize(self) -> Prover<Notarize> {
|
||||
Prover {
|
||||
config: self.config,
|
||||
span: self.span,
|
||||
state: self.state.into(),
|
||||
}
|
||||
}
|
||||
@@ -233,6 +240,7 @@ impl Prover<state::Closed> {
|
||||
pub fn start_prove(self) -> Prover<Prove> {
|
||||
Prover {
|
||||
config: self.config,
|
||||
span: self.span,
|
||||
state: self.state.into(),
|
||||
}
|
||||
}
|
||||
@@ -245,6 +253,8 @@ async fn setup_mpc_backend(
|
||||
mux: &MuxControl,
|
||||
exec: &mut Executor,
|
||||
) -> Result<(MpcTlsLeader, DEAPThread, OTReceiver), ProverError> {
|
||||
debug!("starting MPC backend setup");
|
||||
|
||||
let mut ot_sender = kos::Sender::new(
|
||||
config.build_ot_sender_config(),
|
||||
chou_orlandi::Receiver::new(config.build_base_ot_receiver_config()),
|
||||
|
||||
@@ -28,7 +28,7 @@ impl Prover<Notarize> {
|
||||
}
|
||||
|
||||
/// Finalizes the notarization returning a [`NotarizedSession`].
|
||||
#[instrument(level = "debug", skip_all, err)]
|
||||
#[instrument(parent = &self.span, level = "debug", skip_all, err)]
|
||||
pub async fn finalize(self) -> Result<NotarizedSession, ProverError> {
|
||||
let Notarize {
|
||||
mut io,
|
||||
|
||||
@@ -60,7 +60,7 @@ impl Prover<ProveState> {
|
||||
}
|
||||
|
||||
/// Prove transcript values
|
||||
#[instrument(level = "debug", skip_all, err)]
|
||||
#[instrument(parent = &self.span, level = "debug", skip_all, err)]
|
||||
pub async fn prove(&mut self) -> Result<(), ProverError> {
|
||||
let mut proving_info = std::mem::take(&mut self.state.proving_info);
|
||||
|
||||
@@ -127,7 +127,7 @@ impl Prover<ProveState> {
|
||||
}
|
||||
|
||||
/// Finalize the proving
|
||||
#[instrument(level = "debug", skip_all, err)]
|
||||
#[instrument(parent = &self.span, level = "debug", skip_all, err)]
|
||||
pub async fn finalize(self) -> Result<(), ProverError> {
|
||||
let ProveState {
|
||||
mut io,
|
||||
|
||||
@@ -38,3 +38,4 @@ rand = { workspace = true }
|
||||
signature = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
web-time = { workspace = true }
|
||||
@@ -12,7 +12,7 @@ use mpz_common::Allocate;
|
||||
use serio::StreamExt;
|
||||
use uid_mux::FramedUidMux;
|
||||
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
use web_time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
use futures::{AsyncRead, AsyncWrite, TryFutureExt};
|
||||
use mpz_garble::config::Role as DEAPRole;
|
||||
@@ -27,19 +27,22 @@ use tlsn_common::{
|
||||
};
|
||||
use tlsn_core::{proof::SessionInfo, RedactedTranscript, SessionHeader, Signature};
|
||||
|
||||
use tracing::{debug, info, instrument};
|
||||
use tracing::{debug, info, info_span, instrument, Span};
|
||||
|
||||
/// A Verifier instance.
|
||||
pub struct Verifier<T: state::VerifierState> {
|
||||
config: VerifierConfig,
|
||||
span: Span,
|
||||
state: T,
|
||||
}
|
||||
|
||||
impl Verifier<state::Initialized> {
|
||||
/// Creates a new verifier.
|
||||
pub fn new(config: VerifierConfig) -> Self {
|
||||
let span = info_span!("verifier", id = config.id());
|
||||
Self {
|
||||
config,
|
||||
span,
|
||||
state: state::Initialized,
|
||||
}
|
||||
}
|
||||
@@ -51,6 +54,7 @@ impl Verifier<state::Initialized> {
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `socket` - The socket to the prover.
|
||||
#[instrument(parent = &self.span, level = "info", skip_all, err)]
|
||||
pub async fn setup<S: AsyncWrite + AsyncRead + Send + Unpin + 'static>(
|
||||
self,
|
||||
socket: S,
|
||||
@@ -85,6 +89,7 @@ impl Verifier<state::Initialized> {
|
||||
|
||||
Ok(Verifier {
|
||||
config: self.config,
|
||||
span: self.span,
|
||||
state: state::Setup {
|
||||
io,
|
||||
mux_ctrl,
|
||||
@@ -106,6 +111,7 @@ impl Verifier<state::Initialized> {
|
||||
///
|
||||
/// * `socket` - The socket to the prover.
|
||||
/// * `signer` - The signer used to sign the notarization result.
|
||||
#[instrument(parent = &self.span, level = "info", skip_all, err)]
|
||||
pub async fn notarize<S: AsyncWrite + AsyncRead + Send + Unpin + 'static, T>(
|
||||
self,
|
||||
socket: S,
|
||||
@@ -130,6 +136,7 @@ impl Verifier<state::Initialized> {
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `socket` - The socket to the prover.
|
||||
#[instrument(parent = &self.span, level = "info", skip_all, err)]
|
||||
pub async fn verify<S: AsyncWrite + AsyncRead + Send + Unpin + 'static>(
|
||||
self,
|
||||
socket: S,
|
||||
@@ -144,6 +151,7 @@ impl Verifier<state::Initialized> {
|
||||
|
||||
impl Verifier<state::Setup> {
|
||||
/// Runs the verifier until the TLS connection is closed.
|
||||
#[instrument(parent = &self.span, level = "info", skip_all, err)]
|
||||
pub async fn run(self) -> Result<Verifier<state::Closed>, VerifierError> {
|
||||
let state::Setup {
|
||||
io,
|
||||
@@ -177,6 +185,7 @@ impl Verifier<state::Setup> {
|
||||
|
||||
Ok(Verifier {
|
||||
config: self.config,
|
||||
span: self.span,
|
||||
state: state::Closed {
|
||||
io,
|
||||
mux_ctrl,
|
||||
@@ -203,6 +212,7 @@ impl Verifier<state::Closed> {
|
||||
pub fn start_notarize(self) -> Verifier<Notarize> {
|
||||
Verifier {
|
||||
config: self.config,
|
||||
span: self.span,
|
||||
state: self.state.into(),
|
||||
}
|
||||
}
|
||||
@@ -214,6 +224,7 @@ impl Verifier<state::Closed> {
|
||||
pub fn start_verify(self) -> Verifier<Verify> {
|
||||
Verifier {
|
||||
config: self.config,
|
||||
span: self.span,
|
||||
state: self.state.into(),
|
||||
}
|
||||
}
|
||||
@@ -227,6 +238,8 @@ async fn setup_mpc_backend(
|
||||
exec: &mut Executor,
|
||||
encoder_seed: [u8; 32],
|
||||
) -> Result<(MpcTlsFollower, DEAPThread, OTSender), VerifierError> {
|
||||
debug!("starting MPC backend setup");
|
||||
|
||||
let mut ot_sender = kos::Sender::new(
|
||||
config.build_ot_sender_config(),
|
||||
chou_orlandi::Receiver::new(config.build_base_ot_receiver_config()),
|
||||
|
||||
@@ -19,7 +19,7 @@ impl Verifier<Notarize> {
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `signer` - The signer used to sign the notarization result.
|
||||
#[instrument(level = "debug", skip_all, err)]
|
||||
#[instrument(parent = &self.span, level = "debug", skip_all, err)]
|
||||
pub async fn finalize<T>(self, signer: &impl Signer<T>) -> Result<SessionHeader, VerifierError>
|
||||
where
|
||||
T: Into<Signature>,
|
||||
|
||||
@@ -12,7 +12,7 @@ use tlsn_core::{
|
||||
RedactedTranscript, TranscriptSlice,
|
||||
};
|
||||
|
||||
use tracing::info;
|
||||
use tracing::{info, instrument};
|
||||
|
||||
impl Verifier<VerifyState> {
|
||||
/// Receives the **purported** transcript from the Prover.
|
||||
@@ -20,6 +20,7 @@ impl Verifier<VerifyState> {
|
||||
/// # Warning
|
||||
///
|
||||
/// The content of the received transcripts can not be considered authentic until after finalization.
|
||||
#[instrument(parent = &self.span, level = "info", skip_all, err)]
|
||||
pub async fn receive(
|
||||
&mut self,
|
||||
) -> Result<(RedactedTranscript, RedactedTranscript), VerifierError> {
|
||||
@@ -110,6 +111,7 @@ impl Verifier<VerifyState> {
|
||||
}
|
||||
|
||||
/// Verifies the TLS session.
|
||||
#[instrument(parent = &self.span, level = "info", skip_all, err)]
|
||||
pub async fn finalize(self) -> Result<SessionInfo, VerifierError> {
|
||||
let VerifyState {
|
||||
mut io,
|
||||
|
||||
30
crates/wasm-test-runner/Cargo.toml
Normal file
30
crates/wasm-test-runner/Cargo.toml
Normal file
@@ -0,0 +1,30 @@
|
||||
[package]
|
||||
name = "tlsn-wasm-test-runner"
|
||||
version = "0.0.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
tlsn-core = { workspace = true }
|
||||
tlsn-prover = { workspace = true }
|
||||
tlsn-server-fixture = { workspace = true }
|
||||
tlsn-tls-core = { workspace = true }
|
||||
tlsn-verifier = { workspace = true }
|
||||
|
||||
websocket-relay = { git = "https://github.com/tlsnotary/tlsn-utils", rev = "73a6be1" }
|
||||
|
||||
anyhow = { workspace = true }
|
||||
axum = { workspace = true }
|
||||
chromiumoxide = { version = "0.6", features = ["tokio-runtime"] }
|
||||
futures = { workspace = true }
|
||||
once_cell = { workspace = true }
|
||||
p256 = { workspace = true }
|
||||
rand = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
tokio = { workspace = true, features = ["full"] }
|
||||
tokio-tungstenite = { version = "0.23", features = ["url"] }
|
||||
tokio-util = { workspace = true, features = ["compat"] }
|
||||
tower = { version = "0.4" }
|
||||
tower-http = { version = "0.5", features = ["fs", "set-header"] }
|
||||
tracing = { workspace = true }
|
||||
tracing-subscriber = { workspace = true, features = ["env-filter"] }
|
||||
4
crates/wasm-test-runner/run.sh
Executable file
4
crates/wasm-test-runner/run.sh
Executable file
@@ -0,0 +1,4 @@
|
||||
RUSTFLAGS='-C target-feature=+atomics,+bulk-memory,+mutable-globals' \
|
||||
rustup run nightly \
|
||||
wasm-pack build ../wasm --target web --no-pack --out-dir=../wasm-test-runner/static/generated -- -Zbuild-std=panic_abort,std --features test \
|
||||
&& cargo run --release
|
||||
86
crates/wasm-test-runner/src/chrome_driver.rs
Normal file
86
crates/wasm-test-runner/src/chrome_driver.rs
Normal file
@@ -0,0 +1,86 @@
|
||||
use anyhow::{anyhow, Result};
|
||||
use chromiumoxide::{
|
||||
cdp::{
|
||||
browser_protocol::log::{EventEntryAdded, LogEntryLevel},
|
||||
js_protocol::runtime::EventExceptionThrown,
|
||||
},
|
||||
Browser, BrowserConfig, Page,
|
||||
};
|
||||
use futures::{Future, FutureExt, StreamExt};
|
||||
use std::env;
|
||||
use tracing::{debug, error, instrument};
|
||||
|
||||
use crate::{TestResult, DEFAULT_SERVER_IP, DEFAULT_WASM_PORT};
|
||||
|
||||
#[instrument]
|
||||
pub async fn run() -> Result<Vec<TestResult>> {
|
||||
let config = BrowserConfig::builder().build().map_err(|s| anyhow!(s))?;
|
||||
|
||||
debug!("launching chromedriver");
|
||||
|
||||
let (mut browser, mut handler) = Browser::launch(config).await?;
|
||||
|
||||
debug!("chromedriver started");
|
||||
|
||||
tokio::spawn(async move {
|
||||
while let Some(res) = handler.next().await {
|
||||
res.unwrap();
|
||||
}
|
||||
});
|
||||
|
||||
let wasm_port: u16 = env::var("WASM_PORT")
|
||||
.map(|port| port.parse().expect("port should be valid integer"))
|
||||
.unwrap_or(DEFAULT_WASM_PORT);
|
||||
let wasm_addr: String = env::var("WASM_IP").unwrap_or_else(|_| DEFAULT_SERVER_IP.to_string());
|
||||
|
||||
let page = browser
|
||||
.new_page(&format!("http://{}:{}/index.html", wasm_addr, wasm_port))
|
||||
.await?;
|
||||
|
||||
tokio::spawn(register_listeners(&page).await?);
|
||||
|
||||
page.wait_for_navigation().await?;
|
||||
let results: Vec<TestResult> = page
|
||||
.evaluate(
|
||||
r#"
|
||||
(async () => {
|
||||
await window.testWorker.init();
|
||||
return await window.testWorker.run();
|
||||
})();
|
||||
"#,
|
||||
)
|
||||
.await?
|
||||
.into_value()?;
|
||||
|
||||
browser.close().await?;
|
||||
browser.wait().await?;
|
||||
|
||||
Ok(results)
|
||||
}
|
||||
|
||||
async fn register_listeners(page: &Page) -> Result<impl Future<Output = ()>> {
|
||||
let mut logs = page.event_listener::<EventEntryAdded>().await?.fuse();
|
||||
let mut exceptions = page.event_listener::<EventExceptionThrown>().await?.fuse();
|
||||
|
||||
Ok(futures::future::join(
|
||||
async move {
|
||||
while let Some(event) = logs.next().await {
|
||||
let entry = &event.entry;
|
||||
match entry.level {
|
||||
LogEntryLevel::Error => {
|
||||
error!("{:?}", entry);
|
||||
}
|
||||
_ => {
|
||||
debug!("{:?}: {}", entry.timestamp, entry.text);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
async move {
|
||||
while let Some(event) = exceptions.next().await {
|
||||
error!("{:?}", event);
|
||||
}
|
||||
},
|
||||
)
|
||||
.map(|_| ()))
|
||||
}
|
||||
37
crates/wasm-test-runner/src/lib.rs
Normal file
37
crates/wasm-test-runner/src/lib.rs
Normal file
@@ -0,0 +1,37 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
pub mod chrome_driver;
|
||||
pub mod server_fixture;
|
||||
pub mod tlsn_fixture;
|
||||
pub mod wasm_server;
|
||||
pub mod ws;
|
||||
|
||||
pub static DEFAULT_SERVER_IP: &str = "127.0.0.1";
|
||||
pub static DEFAULT_WASM_PORT: u16 = 8013;
|
||||
pub static DEFAULT_WS_PORT: u16 = 8080;
|
||||
pub static DEFAULT_SERVER_PORT: u16 = 8083;
|
||||
pub static DEFAULT_VERIFIER_PORT: u16 = 8010;
|
||||
pub static DEFAULT_NOTARY_PORT: u16 = 8011;
|
||||
pub static DEFAULT_PROVER_PORT: u16 = 8012;
|
||||
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
pub struct TestResult {
|
||||
pub name: String,
|
||||
pub passed: bool,
|
||||
pub error: Option<String>,
|
||||
}
|
||||
|
||||
impl Display for TestResult {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
if self.passed {
|
||||
write!(f, "{}: passed", self.name)?;
|
||||
} else {
|
||||
write!(f, "{}: failed", self.name)?;
|
||||
if let Some(error) = &self.error {
|
||||
write!(f, "\ncaused by: {}", error)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
42
crates/wasm-test-runner/src/main.rs
Normal file
42
crates/wasm-test-runner/src/main.rs
Normal file
@@ -0,0 +1,42 @@
|
||||
use anyhow::Result;
|
||||
|
||||
fn init_tracing() {
|
||||
use tracing_subscriber::EnvFilter;
|
||||
|
||||
tracing_subscriber::fmt()
|
||||
.with_env_filter(EnvFilter::from_default_env())
|
||||
.init();
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
init_tracing();
|
||||
|
||||
let fut_wasm = tlsn_wasm_test_runner::wasm_server::start().await?;
|
||||
let fut_proxy = tlsn_wasm_test_runner::ws::start().await?;
|
||||
let fut_tlsn = tlsn_wasm_test_runner::tlsn_fixture::start().await?;
|
||||
let fut_server = tlsn_wasm_test_runner::server_fixture::start().await?;
|
||||
|
||||
tokio::spawn(async move {
|
||||
futures::future::try_join4(fut_wasm, fut_proxy, fut_tlsn, fut_server)
|
||||
.await
|
||||
.unwrap()
|
||||
});
|
||||
|
||||
let results = tlsn_wasm_test_runner::chrome_driver::run().await?;
|
||||
|
||||
for result in &results {
|
||||
println!("{}", result);
|
||||
}
|
||||
|
||||
let passed = results.iter().filter(|r| r.passed).count();
|
||||
let failed = results.iter().filter(|r| !r.passed).count();
|
||||
|
||||
println!("{} passed, {} failed", passed, failed);
|
||||
|
||||
if results.iter().any(|r| !r.passed) {
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
32
crates/wasm-test-runner/src/server_fixture.rs
Normal file
32
crates/wasm-test-runner/src/server_fixture.rs
Normal file
@@ -0,0 +1,32 @@
|
||||
use std::{env, net::IpAddr};
|
||||
|
||||
use anyhow::Result;
|
||||
use futures::Future;
|
||||
use tokio::net::TcpListener;
|
||||
use tokio_util::compat::TokioAsyncReadCompatExt;
|
||||
use tracing::{info, instrument};
|
||||
|
||||
use crate::{DEFAULT_SERVER_IP, DEFAULT_SERVER_PORT};
|
||||
|
||||
#[instrument]
|
||||
pub async fn start() -> Result<impl Future<Output = Result<()>>> {
|
||||
let port: u16 = env::var("SERVER_PORT")
|
||||
.map(|port| port.parse().expect("port should be valid integer"))
|
||||
.unwrap_or(DEFAULT_SERVER_PORT);
|
||||
let addr: IpAddr = env::var("SERVER_IP")
|
||||
.map(|addr| addr.parse().expect("should be valid IP address"))
|
||||
.unwrap_or(IpAddr::V4(DEFAULT_SERVER_IP.parse().unwrap()));
|
||||
|
||||
let listener = TcpListener::bind((addr, port)).await?;
|
||||
|
||||
info!("listening on: {}", listener.local_addr()?);
|
||||
|
||||
Ok(async move {
|
||||
loop {
|
||||
let (socket, addr) = listener.accept().await?;
|
||||
info!("accepted connection from: {}", addr);
|
||||
|
||||
tokio::spawn(tlsn_server_fixture::bind(socket.compat()));
|
||||
}
|
||||
})
|
||||
}
|
||||
166
crates/wasm-test-runner/src/tlsn_fixture.rs
Normal file
166
crates/wasm-test-runner/src/tlsn_fixture.rs
Normal file
@@ -0,0 +1,166 @@
|
||||
use std::{env, net::IpAddr};
|
||||
|
||||
use anyhow::Result;
|
||||
use futures::{AsyncReadExt, AsyncWriteExt, Future};
|
||||
use tls_core::{anchors::RootCertStore, verify::WebPkiVerifier};
|
||||
use tlsn_core::Direction;
|
||||
use tlsn_prover::tls::{Prover, ProverConfig};
|
||||
use tlsn_server_fixture::{CA_CERT_DER, SERVER_DOMAIN};
|
||||
use tlsn_verifier::tls::{Verifier, VerifierConfig};
|
||||
use tokio::net::{TcpListener, TcpStream};
|
||||
use tokio_util::compat::TokioAsyncReadCompatExt;
|
||||
use tracing::{info, instrument};
|
||||
|
||||
use crate::{
|
||||
DEFAULT_NOTARY_PORT, DEFAULT_PROVER_PORT, DEFAULT_SERVER_IP, DEFAULT_SERVER_PORT,
|
||||
DEFAULT_VERIFIER_PORT,
|
||||
};
|
||||
|
||||
#[instrument]
|
||||
pub async fn start() -> Result<impl Future<Output = Result<()>>> {
|
||||
let verifier_port: u16 = env::var("VERIFIER_PORT")
|
||||
.map(|port| port.parse().expect("port should be valid integer"))
|
||||
.unwrap_or(DEFAULT_VERIFIER_PORT);
|
||||
let notary_port: u16 = env::var("NOTARY_PORT")
|
||||
.map(|port| port.parse().expect("port should be valid integer"))
|
||||
.unwrap_or(DEFAULT_NOTARY_PORT);
|
||||
let prover_port: u16 = env::var("PROVER_PORT")
|
||||
.map(|port| port.parse().expect("port should be valid integer"))
|
||||
.unwrap_or(DEFAULT_PROVER_PORT);
|
||||
let addr: IpAddr = env::var("TLSN_IP")
|
||||
.map(|addr| addr.parse().expect("should be valid IP address"))
|
||||
.unwrap_or(IpAddr::V4(DEFAULT_SERVER_IP.parse().unwrap()));
|
||||
|
||||
let verifier_listener = TcpListener::bind((addr, verifier_port)).await?;
|
||||
let notary_listener = TcpListener::bind((addr, notary_port)).await?;
|
||||
let prover_listener = TcpListener::bind((addr, prover_port)).await?;
|
||||
|
||||
Ok(async move {
|
||||
loop {
|
||||
tokio::select! {
|
||||
res = verifier_listener.accept() => {
|
||||
let (socket, addr) = res?;
|
||||
info!("verifier accepted connection from: {}", addr);
|
||||
|
||||
tokio::spawn(handle_verifier(socket));
|
||||
},
|
||||
res = notary_listener.accept() => {
|
||||
let (socket, addr) = res?;
|
||||
info!("notary accepted connection from: {}", addr);
|
||||
|
||||
tokio::spawn(handle_notary(socket));
|
||||
},
|
||||
res = prover_listener.accept() => {
|
||||
let (socket, addr) = res?;
|
||||
info!("prover accepted connection from: {}", addr);
|
||||
|
||||
tokio::spawn(handle_prover(socket));
|
||||
},
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip_all, err)]
|
||||
async fn handle_verifier(io: TcpStream) -> Result<()> {
|
||||
let mut root_store = RootCertStore::empty();
|
||||
root_store
|
||||
.add(&tls_core::key::Certificate(
|
||||
tlsn_server_fixture::CA_CERT_DER.to_vec(),
|
||||
))
|
||||
.unwrap();
|
||||
|
||||
let config = VerifierConfig::builder()
|
||||
.id("test")
|
||||
.max_sent_data(1024)
|
||||
.max_recv_data(1024)
|
||||
.cert_verifier(WebPkiVerifier::new(root_store, None))
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let verifier = Verifier::new(config);
|
||||
|
||||
verifier.verify(io.compat()).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip_all, err)]
|
||||
async fn handle_notary(io: TcpStream) -> Result<()> {
|
||||
let config = VerifierConfig::builder()
|
||||
.id("test")
|
||||
.max_sent_data(1024)
|
||||
.max_recv_data(1024)
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let verifier = Verifier::new(config);
|
||||
let signing_key = p256::ecdsa::SigningKey::random(&mut rand::thread_rng());
|
||||
|
||||
verifier
|
||||
.notarize::<_, p256::ecdsa::Signature>(io.compat(), &signing_key)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip_all, err)]
|
||||
async fn handle_prover(io: TcpStream) -> Result<()> {
|
||||
let mut root_store = tls_core::anchors::RootCertStore::empty();
|
||||
root_store
|
||||
.add(&tls_core::key::Certificate(CA_CERT_DER.to_vec()))
|
||||
.unwrap();
|
||||
|
||||
let prover = Prover::new(
|
||||
ProverConfig::builder()
|
||||
.id("test")
|
||||
.server_dns(SERVER_DOMAIN)
|
||||
.max_sent_data(1024)
|
||||
.max_recv_data(1024)
|
||||
.root_cert_store(root_store)
|
||||
.build()
|
||||
.unwrap(),
|
||||
)
|
||||
.setup(io.compat())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let port: u16 = env::var("SERVER_PORT")
|
||||
.map(|port| port.parse().expect("port should be valid integer"))
|
||||
.unwrap_or(DEFAULT_SERVER_PORT);
|
||||
let addr: IpAddr = env::var("SERVER_IP")
|
||||
.map(|addr| addr.parse().expect("should be valid IP address"))
|
||||
.unwrap_or(IpAddr::V4(DEFAULT_SERVER_IP.parse().unwrap()));
|
||||
|
||||
let client_socket = TcpStream::connect((addr, port)).await.unwrap();
|
||||
|
||||
let (mut tls_connection, prover_fut) = prover.connect(client_socket.compat()).await.unwrap();
|
||||
let prover_ctrl = prover_fut.control();
|
||||
let prover_task = tokio::spawn(prover_fut);
|
||||
|
||||
// Defer decryption until after the server closes the connection.
|
||||
prover_ctrl.defer_decryption().await.unwrap();
|
||||
|
||||
tls_connection
|
||||
.write_all(b"GET / HTTP/1.1\r\nConnection: close\r\n\r\n")
|
||||
.await
|
||||
.unwrap();
|
||||
tls_connection.close().await.unwrap();
|
||||
|
||||
let mut response = vec![0u8; 1024];
|
||||
tls_connection.read_to_end(&mut response).await.unwrap();
|
||||
|
||||
let mut prover = prover_task.await.unwrap().unwrap().start_prove();
|
||||
|
||||
let sent_transcript_len = prover.sent_transcript().data().len();
|
||||
let recv_transcript_len = prover.recv_transcript().data().len();
|
||||
|
||||
// Reveal parts of the transcript
|
||||
_ = prover.reveal(0..sent_transcript_len - 1, Direction::Sent);
|
||||
_ = prover.reveal(2..recv_transcript_len, Direction::Received);
|
||||
prover.prove().await.unwrap();
|
||||
|
||||
prover.finalize().await.unwrap();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
49
crates/wasm-test-runner/src/wasm_server.rs
Normal file
49
crates/wasm-test-runner/src/wasm_server.rs
Normal file
@@ -0,0 +1,49 @@
|
||||
use std::{env, net::IpAddr};
|
||||
|
||||
use anyhow::Result;
|
||||
use axum::{
|
||||
http::{HeaderName, HeaderValue},
|
||||
Router,
|
||||
};
|
||||
use futures::Future;
|
||||
use tokio::net::TcpListener;
|
||||
use tower::ServiceBuilder;
|
||||
use tower_http::{services::ServeDir, set_header::SetResponseHeaderLayer};
|
||||
use tracing::{info, instrument};
|
||||
|
||||
use crate::{DEFAULT_SERVER_IP, DEFAULT_WASM_PORT};
|
||||
|
||||
#[instrument]
|
||||
pub async fn start() -> Result<impl Future<Output = Result<()>>> {
|
||||
let port: u16 = env::var("WASM_PORT")
|
||||
.map(|port| port.parse().expect("port should be valid integer"))
|
||||
.unwrap_or(DEFAULT_WASM_PORT);
|
||||
let addr: IpAddr = env::var("WASM_IP")
|
||||
.map(|addr| addr.parse().expect("should be valid IP address"))
|
||||
.unwrap_or(IpAddr::V4(DEFAULT_SERVER_IP.parse().unwrap()));
|
||||
|
||||
let files = ServeDir::new("static");
|
||||
|
||||
let service = ServiceBuilder::new()
|
||||
.layer(SetResponseHeaderLayer::if_not_present(
|
||||
HeaderName::from_static("cross-origin-embedder-policy"),
|
||||
HeaderValue::from_static("require-corp"),
|
||||
))
|
||||
.layer(SetResponseHeaderLayer::if_not_present(
|
||||
HeaderName::from_static("cross-origin-opener-policy"),
|
||||
HeaderValue::from_static("same-origin"),
|
||||
))
|
||||
.service(files);
|
||||
|
||||
// build our application with a single route
|
||||
let app = Router::new().fallback_service(service);
|
||||
|
||||
let listener = TcpListener::bind((addr, port)).await?;
|
||||
|
||||
info!("listening on {}", listener.local_addr()?);
|
||||
|
||||
Ok(async move {
|
||||
axum::serve(listener, app).await?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
26
crates/wasm-test-runner/src/ws.rs
Normal file
26
crates/wasm-test-runner/src/ws.rs
Normal file
@@ -0,0 +1,26 @@
|
||||
use std::{env, net::IpAddr};
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use futures::Future;
|
||||
use tokio::net::TcpListener;
|
||||
use tracing::{info, instrument};
|
||||
|
||||
use crate::{DEFAULT_SERVER_IP, DEFAULT_WS_PORT};
|
||||
|
||||
#[instrument]
|
||||
pub async fn start() -> Result<impl Future<Output = Result<()>>> {
|
||||
let port: u16 = env::var("PROXY_PORT")
|
||||
.map(|port| port.parse().expect("port should be valid integer"))
|
||||
.unwrap_or(DEFAULT_WS_PORT);
|
||||
let addr: IpAddr = env::var("PROXY_IP")
|
||||
.map(|addr| addr.parse().expect("should be valid IP address"))
|
||||
.unwrap_or(IpAddr::V4(DEFAULT_SERVER_IP.parse().unwrap()));
|
||||
|
||||
let listener = TcpListener::bind((addr, port))
|
||||
.await
|
||||
.context("failed to bind to address")?;
|
||||
|
||||
info!("listening on: {}", listener.local_addr()?);
|
||||
|
||||
Ok(websocket_relay::run(listener))
|
||||
}
|
||||
7
crates/wasm-test-runner/static/index.html
Normal file
7
crates/wasm-test-runner/static/index.html
Normal file
@@ -0,0 +1,7 @@
|
||||
<!DOCTYPE html>
|
||||
<head>
|
||||
</head>
|
||||
<body>
|
||||
<script src="index.js" type="module"></script>
|
||||
</body>
|
||||
</html>
|
||||
5
crates/wasm-test-runner/static/index.js
Normal file
5
crates/wasm-test-runner/static/index.js
Normal file
@@ -0,0 +1,5 @@
|
||||
import * as Comlink from "https://unpkg.com/comlink/dist/esm/comlink.mjs";
|
||||
|
||||
const testWorker = Comlink.wrap(new Worker("worker.js", { type: "module" }));
|
||||
|
||||
window.testWorker = testWorker;
|
||||
41
crates/wasm-test-runner/static/worker.js
Normal file
41
crates/wasm-test-runner/static/worker.js
Normal file
@@ -0,0 +1,41 @@
|
||||
import * as Comlink from "https://unpkg.com/comlink/dist/esm/comlink.mjs";
|
||||
import init_wasm, { init_logging, initThreadPool } from "./generated/tlsn_wasm.js";
|
||||
|
||||
const module = await import("./generated/tlsn_wasm.js");
|
||||
|
||||
class TestWorker {
|
||||
async init() {
|
||||
try {
|
||||
await init_wasm();
|
||||
init_logging();
|
||||
console.log("initialized logging");
|
||||
await initThreadPool(8);
|
||||
console.log("initialized worker");
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
run() {
|
||||
let promises = [];
|
||||
for (const [name, func] of Object.entries(module)) {
|
||||
|
||||
if(name.startsWith("test_") && (typeof func === 'function')) {
|
||||
promises.push(func().then(_ => { return {
|
||||
name: name,
|
||||
passed: true,
|
||||
} }).catch(error => { return {
|
||||
name: name,
|
||||
passed: false,
|
||||
error: error.toString(),
|
||||
} }));
|
||||
}
|
||||
}
|
||||
return Promise.all(promises);
|
||||
}
|
||||
}
|
||||
|
||||
const worker = new TestWorker();
|
||||
|
||||
Comlink.expose(worker);
|
||||
8
crates/wasm/.cargo/config.toml
Normal file
8
crates/wasm/.cargo/config.toml
Normal file
@@ -0,0 +1,8 @@
|
||||
[build]
|
||||
target = "wasm32-unknown-unknown"
|
||||
|
||||
[unstable]
|
||||
build-std = ["panic_abort", "std"]
|
||||
|
||||
[target.wasm32-unknown-unknown]
|
||||
rustflags = ["-C", "target-feature=+atomics,+bulk-memory,+mutable-globals"]
|
||||
51
crates/wasm/Cargo.toml
Normal file
51
crates/wasm/Cargo.toml
Normal file
@@ -0,0 +1,51 @@
|
||||
[package]
|
||||
name = "tlsn-wasm"
|
||||
version = "0.1.0-alpha.6"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[package.metadata.wasm-pack.profile.debug]
|
||||
debug = false
|
||||
|
||||
[package.metadata.wasm-pack.profile.release]
|
||||
opt-level = "z"
|
||||
wasm-opt = true
|
||||
|
||||
[features]
|
||||
default = []
|
||||
test = []
|
||||
|
||||
[dependencies]
|
||||
tlsn-core = { path = "../core" }
|
||||
tlsn-prover = { path = "../prover" }
|
||||
tlsn-tls-client-async = { path = "../tls/client-async" }
|
||||
tlsn-tls-core = { path = "../tls/core" }
|
||||
tlsn-verifier = { path = "../verifier" }
|
||||
|
||||
bincode = { workspace = true }
|
||||
console_error_panic_hook = { version = "0.1" }
|
||||
enum-try-as-inner = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
getrandom = { version = "0.2", features = ["js"] }
|
||||
http-body-util = { version = "0.1" }
|
||||
hyper = { workspace = true, features = ["client", "http1"] }
|
||||
parking_lot = { version = "0.12", features = ["nightly"] }
|
||||
pin-project-lite = { workspace = true }
|
||||
ring = { version = "0.17", features = ["wasm32_unknown_unknown_js"] }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde-wasm-bindgen = { version = "0.6" }
|
||||
serde_json = { version = "1.0" }
|
||||
time = { version = "0.3", features = ["wasm-bindgen"] }
|
||||
tracing = { workspace = true }
|
||||
tracing-subscriber = { workspace = true, features = ["time"] }
|
||||
tracing-web = { version = "0.1" }
|
||||
tsify-next = { version = "0.5", default-features = false, features = ["js"] }
|
||||
wasm-bindgen = { version = "0.2" }
|
||||
wasm-bindgen-futures = { version = "0.4" }
|
||||
# Use the patched ws_stream_wasm to fix the issue https://github.com/najamelan/ws_stream_wasm/issues/12#issuecomment-1711902958
|
||||
ws_stream_wasm = { git = "https://github.com/tlsnotary/ws_stream_wasm", rev = "2ed12aad9f0236e5321f577672f309920b2aef51" }
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
wasm-bindgen-rayon = { version = "1.0", features = ["no-bundler"] }
|
||||
13
crates/wasm/README.md
Normal file
13
crates/wasm/README.md
Normal file
@@ -0,0 +1,13 @@
|
||||
# TLSNotary WASM bindings
|
||||
|
||||
## Build
|
||||
|
||||
This crate must be built using the nightly rust compiler with the following flags:
|
||||
|
||||
```bash
|
||||
RUSTFLAGS="-C target-feature=+atomics,+bulk-memory,+mutable-globals" \
|
||||
rustup run nightly \
|
||||
wasm-pack build --target web . -Z build-std=panic_abort,std
|
||||
```
|
||||
|
||||
|
||||
2
crates/wasm/rust-toolchain
Normal file
2
crates/wasm/rust-toolchain
Normal file
@@ -0,0 +1,2 @@
|
||||
[toolchain]
|
||||
channel = "nightly"
|
||||
86
crates/wasm/src/io.rs
Normal file
86
crates/wasm/src/io.rs
Normal file
@@ -0,0 +1,86 @@
|
||||
use core::slice;
|
||||
use std::{
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
use pin_project_lite::pin_project;
|
||||
|
||||
pin_project! {
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct FuturesIo<T> {
|
||||
#[pin]
|
||||
inner: T,
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> FuturesIo<T> {
|
||||
/// Create a new `FuturesIo` wrapping the given I/O object.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This wrapper is only safe to use if the inner I/O object does not under any circumstance
|
||||
/// read from the buffer passed to `poll_read` in the `futures::AsyncRead` implementation.
|
||||
pub(crate) fn new(inner: T) -> Self {
|
||||
Self { inner }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> hyper::rt::Write for FuturesIo<T>
|
||||
where
|
||||
T: futures::AsyncWrite + Unpin,
|
||||
{
|
||||
fn poll_write(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
buf: &[u8],
|
||||
) -> Poll<Result<usize, std::io::Error>> {
|
||||
self.project().inner.poll_write(cx, buf)
|
||||
}
|
||||
|
||||
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), std::io::Error>> {
|
||||
self.project().inner.poll_flush(cx)
|
||||
}
|
||||
|
||||
fn poll_shutdown(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
) -> Poll<Result<(), std::io::Error>> {
|
||||
self.project().inner.poll_close(cx)
|
||||
}
|
||||
|
||||
fn poll_write_vectored(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
bufs: &[std::io::IoSlice<'_>],
|
||||
) -> Poll<Result<usize, std::io::Error>> {
|
||||
self.project().inner.poll_write_vectored(cx, bufs)
|
||||
}
|
||||
}
|
||||
|
||||
// Adapted from https://github.com/hyperium/hyper-util/blob/99b77a5a6f75f24bc0bcb4ca74b5f26a07b19c80/src/rt/tokio.rs
|
||||
impl<T> hyper::rt::Read for FuturesIo<T>
|
||||
where
|
||||
T: futures::AsyncRead + Unpin,
|
||||
{
|
||||
fn poll_read(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
mut buf: hyper::rt::ReadBufCursor<'_>,
|
||||
) -> Poll<Result<(), std::io::Error>> {
|
||||
// Safety: buf_slice should only be written to, so it's safe to convert `&mut [MaybeUninit<u8>]` to `&mut [u8]`.
|
||||
let buf_slice = unsafe {
|
||||
slice::from_raw_parts_mut(buf.as_mut().as_mut_ptr() as *mut u8, buf.as_mut().len())
|
||||
};
|
||||
|
||||
let n = match futures::AsyncRead::poll_read(self.project().inner, cx, buf_slice) {
|
||||
Poll::Ready(Ok(n)) => n,
|
||||
other => return other.map_ok(|_| ()),
|
||||
};
|
||||
|
||||
unsafe {
|
||||
buf.advance(n);
|
||||
}
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
}
|
||||
59
crates/wasm/src/lib.rs
Normal file
59
crates/wasm/src/lib.rs
Normal file
@@ -0,0 +1,59 @@
|
||||
//! TLSNotary WASM bindings.
|
||||
|
||||
#![deny(unreachable_pub, unused_must_use, clippy::all)]
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
pub(crate) mod io;
|
||||
mod log;
|
||||
pub mod prover;
|
||||
#[cfg(feature = "test")]
|
||||
pub mod tests;
|
||||
pub mod types;
|
||||
pub mod verifier;
|
||||
|
||||
use log::LoggingConfig;
|
||||
use tracing::error;
|
||||
use tracing_subscriber::{
|
||||
filter::FilterFn,
|
||||
fmt::{format::FmtSpan, time::UtcTime},
|
||||
layer::SubscriberExt,
|
||||
util::SubscriberInitExt,
|
||||
};
|
||||
use tracing_web::MakeWebConsoleWriter;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
#[cfg(feature = "test")]
|
||||
pub use tests::*;
|
||||
|
||||
/// Initializes logging.
|
||||
#[wasm_bindgen]
|
||||
pub fn init_logging(config: Option<LoggingConfig>) {
|
||||
let mut config = config.unwrap_or_default();
|
||||
|
||||
// Default is NONE
|
||||
let fmt_span = config
|
||||
.span_events
|
||||
.take()
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.map(FmtSpan::from)
|
||||
.fold(FmtSpan::NONE, |acc, span| acc | span);
|
||||
|
||||
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_span_events(fmt_span)
|
||||
.without_time()
|
||||
.with_writer(MakeWebConsoleWriter::new()); // write events to the console
|
||||
|
||||
tracing_subscriber::registry()
|
||||
.with(FilterFn::new(log::filter(config)))
|
||||
.with(fmt_layer)
|
||||
.init();
|
||||
|
||||
// https://github.com/rustwasm/console_error_panic_hook
|
||||
std::panic::set_hook(Box::new(|info| {
|
||||
error!("panic occurred: {:?}", info);
|
||||
console_error_panic_hook::hook(info);
|
||||
}));
|
||||
}
|
||||
88
crates/wasm/src/log.rs
Normal file
88
crates/wasm/src/log.rs
Normal file
@@ -0,0 +1,88 @@
|
||||
use serde::Deserialize;
|
||||
use tracing::{Level, Metadata};
|
||||
use tracing_subscriber::fmt::format::FmtSpan;
|
||||
use tsify_next::Tsify;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Tsify, Deserialize)]
|
||||
#[tsify(from_wasm_abi)]
|
||||
pub enum LoggingLevel {
|
||||
Trace,
|
||||
Debug,
|
||||
Info,
|
||||
Warn,
|
||||
Error,
|
||||
}
|
||||
|
||||
impl From<LoggingLevel> for Level {
|
||||
fn from(value: LoggingLevel) -> Self {
|
||||
match value {
|
||||
LoggingLevel::Trace => Level::TRACE,
|
||||
LoggingLevel::Debug => Level::DEBUG,
|
||||
LoggingLevel::Info => Level::INFO,
|
||||
LoggingLevel::Warn => Level::WARN,
|
||||
LoggingLevel::Error => Level::ERROR,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Tsify, Deserialize)]
|
||||
#[tsify(from_wasm_abi)]
|
||||
pub enum SpanEvent {
|
||||
New,
|
||||
Close,
|
||||
Active,
|
||||
}
|
||||
|
||||
impl From<SpanEvent> for FmtSpan {
|
||||
fn from(value: SpanEvent) -> Self {
|
||||
match value {
|
||||
SpanEvent::New => FmtSpan::NEW,
|
||||
SpanEvent::Close => FmtSpan::CLOSE,
|
||||
SpanEvent::Active => FmtSpan::ACTIVE,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Tsify, Deserialize)]
|
||||
#[tsify(from_wasm_abi)]
|
||||
pub struct LoggingConfig {
|
||||
pub level: Option<LoggingLevel>,
|
||||
pub crate_filters: Option<Vec<CrateLogFilter>>,
|
||||
pub span_events: Option<Vec<SpanEvent>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Tsify, Deserialize)]
|
||||
#[tsify(from_wasm_abi)]
|
||||
pub struct CrateLogFilter {
|
||||
pub level: LoggingLevel,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
pub(crate) fn filter(config: LoggingConfig) -> impl Fn(&Metadata) -> bool {
|
||||
let default_level: Level = config.level.unwrap_or(LoggingLevel::Info).into();
|
||||
let crate_filters = config
|
||||
.crate_filters
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.map(|filter| (filter.name, Level::from(filter.level)))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
move |meta| {
|
||||
let level = if let Some(crate_name) = meta.target().split("::").next() {
|
||||
crate_filters
|
||||
.iter()
|
||||
.find_map(|(filter_name, filter_level)| {
|
||||
if crate_name.eq_ignore_ascii_case(filter_name) {
|
||||
Some(filter_level)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.unwrap_or(&default_level)
|
||||
} else {
|
||||
&default_level
|
||||
};
|
||||
|
||||
meta.level() <= level
|
||||
}
|
||||
}
|
||||
29
crates/wasm/src/prover/config.rs
Normal file
29
crates/wasm/src/prover/config.rs
Normal file
@@ -0,0 +1,29 @@
|
||||
use serde::Deserialize;
|
||||
use tsify_next::Tsify;
|
||||
|
||||
#[derive(Debug, Tsify, Deserialize)]
|
||||
#[tsify(from_wasm_abi)]
|
||||
pub struct ProverConfig {
|
||||
pub id: String,
|
||||
pub server_dns: String,
|
||||
pub max_sent_data: Option<usize>,
|
||||
pub max_recv_data: Option<usize>,
|
||||
}
|
||||
|
||||
impl From<ProverConfig> for tlsn_prover::tls::ProverConfig {
|
||||
fn from(value: ProverConfig) -> Self {
|
||||
let mut builder = tlsn_prover::tls::ProverConfig::builder();
|
||||
builder.id(value.id);
|
||||
builder.server_dns(value.server_dns);
|
||||
|
||||
if let Some(value) = value.max_sent_data {
|
||||
builder.max_sent_data(value);
|
||||
}
|
||||
|
||||
if let Some(value) = value.max_recv_data {
|
||||
builder.max_recv_data(value);
|
||||
}
|
||||
|
||||
builder.build().unwrap()
|
||||
}
|
||||
}
|
||||
204
crates/wasm/src/prover/mod.rs
Normal file
204
crates/wasm/src/prover/mod.rs
Normal file
@@ -0,0 +1,204 @@
|
||||
mod config;
|
||||
|
||||
pub use config::ProverConfig;
|
||||
|
||||
use enum_try_as_inner::EnumTryAsInner;
|
||||
use futures::TryFutureExt;
|
||||
use http_body_util::{BodyExt, Full};
|
||||
use hyper::body::Bytes;
|
||||
use tls_client_async::TlsConnection;
|
||||
use tlsn_core::Direction;
|
||||
use tlsn_prover::tls::{state, Prover};
|
||||
use tracing::info;
|
||||
use wasm_bindgen::{prelude::*, JsError};
|
||||
use wasm_bindgen_futures::spawn_local;
|
||||
use ws_stream_wasm::WsMeta;
|
||||
|
||||
use crate::{io::FuturesIo, types::*};
|
||||
|
||||
type Result<T> = std::result::Result<T, JsError>;
|
||||
|
||||
#[wasm_bindgen(js_name = Prover)]
|
||||
pub struct JsProver {
|
||||
state: State,
|
||||
}
|
||||
|
||||
#[derive(Debug, EnumTryAsInner)]
|
||||
#[derive_err(Debug)]
|
||||
enum State {
|
||||
Initialized(Prover<state::Initialized>),
|
||||
Setup(Prover<state::Setup>),
|
||||
Closed(Prover<state::Closed>),
|
||||
Complete,
|
||||
Error,
|
||||
}
|
||||
|
||||
impl State {
|
||||
fn take(&mut self) -> Self {
|
||||
std::mem::replace(self, State::Error)
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_class = Prover)]
|
||||
impl JsProver {
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(config: ProverConfig) -> JsProver {
|
||||
JsProver {
|
||||
state: State::Initialized(Prover::new(config.into())),
|
||||
}
|
||||
}
|
||||
|
||||
/// Set up the prover.
|
||||
///
|
||||
/// This performs all MPC setup prior to establishing the connection to the
|
||||
/// application server.
|
||||
pub async fn setup(&mut self, verifier_url: &str) -> Result<()> {
|
||||
let prover = self.state.take().try_into_initialized()?;
|
||||
|
||||
info!("connecting to verifier");
|
||||
|
||||
let (_, verifier_conn) = WsMeta::connect(verifier_url, None).await?;
|
||||
|
||||
info!("connected to verifier");
|
||||
|
||||
let prover = prover.setup(verifier_conn.into_io()).await?;
|
||||
|
||||
self.state = State::Setup(prover);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Send the HTTP request to the server.
|
||||
pub async fn send_request(
|
||||
&mut self,
|
||||
ws_proxy_url: &str,
|
||||
request: HttpRequest,
|
||||
) -> Result<HttpResponse> {
|
||||
let prover = self.state.take().try_into_setup()?;
|
||||
|
||||
info!("connecting to server");
|
||||
|
||||
let (_, server_conn) = WsMeta::connect(ws_proxy_url, None).await?;
|
||||
|
||||
info!("connected to server");
|
||||
|
||||
let (tls_conn, prover_fut) = prover.connect(server_conn.into_io()).await?;
|
||||
let prover_ctrl = prover_fut.control();
|
||||
|
||||
info!("sending request");
|
||||
|
||||
let (response, prover) = futures::try_join!(
|
||||
async move {
|
||||
prover_ctrl.defer_decryption().await?;
|
||||
send_request(tls_conn, request).await
|
||||
},
|
||||
prover_fut.map_err(Into::into),
|
||||
)?;
|
||||
|
||||
info!("response received");
|
||||
|
||||
self.state = State::Closed(prover);
|
||||
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
/// Returns the transcript.
|
||||
pub fn transcript(&self) -> Result<Transcript> {
|
||||
let prover = self.state.try_as_closed()?;
|
||||
|
||||
let sent = prover.sent_transcript().data().clone();
|
||||
let recv = prover.recv_transcript().data().clone();
|
||||
|
||||
Ok(Transcript {
|
||||
sent: sent.to_vec(),
|
||||
recv: recv.to_vec(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Runs the notarization protocol.
|
||||
pub async fn notarize(&mut self, commit: Commit) -> Result<NotarizedSession> {
|
||||
let mut prover = self.state.take().try_into_closed()?.start_notarize();
|
||||
|
||||
info!("starting notarization");
|
||||
|
||||
let builder = prover.commitment_builder();
|
||||
|
||||
for range in commit.sent {
|
||||
builder.commit_sent(&range)?;
|
||||
}
|
||||
|
||||
for range in commit.recv {
|
||||
builder.commit_recv(&range)?;
|
||||
}
|
||||
|
||||
let notarized_session = prover.finalize().await?;
|
||||
|
||||
info!("notarization complete");
|
||||
|
||||
Ok(notarized_session.into())
|
||||
}
|
||||
|
||||
/// Reveals data to the verifier and finalizes the protocol.
|
||||
pub async fn reveal(&mut self, reveal: Reveal) -> Result<()> {
|
||||
let mut prover = self.state.take().try_into_closed()?.start_prove();
|
||||
|
||||
info!("revealing data");
|
||||
|
||||
for range in reveal.sent {
|
||||
prover.reveal(range, Direction::Sent)?;
|
||||
}
|
||||
|
||||
for range in reveal.recv {
|
||||
prover.reveal(range, Direction::Received)?;
|
||||
}
|
||||
|
||||
prover.prove().await?;
|
||||
prover.finalize().await?;
|
||||
|
||||
info!("Finalized");
|
||||
|
||||
self.state = State::Complete;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Prover<state::Initialized>> for JsProver {
|
||||
fn from(value: Prover<state::Initialized>) -> Self {
|
||||
JsProver {
|
||||
state: State::Initialized(value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn send_request(conn: TlsConnection, request: HttpRequest) -> Result<HttpResponse> {
|
||||
let conn = FuturesIo::new(conn);
|
||||
let request = hyper::Request::<Full<Bytes>>::try_from(request)?;
|
||||
|
||||
let (mut request_sender, conn) = hyper::client::conn::http1::handshake(conn).await?;
|
||||
|
||||
spawn_local(async move { conn.await.expect("connection runs to completion") });
|
||||
|
||||
let response = request_sender.send_request(request).await?;
|
||||
|
||||
let (response, body) = response.into_parts();
|
||||
|
||||
// TODO: return the body
|
||||
let _body = body.collect().await?;
|
||||
|
||||
let headers = response
|
||||
.headers
|
||||
.into_iter()
|
||||
.map(|(k, v)| {
|
||||
(
|
||||
k.map(|k| k.to_string()).unwrap_or_default(),
|
||||
v.as_bytes().to_vec(),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(HttpResponse {
|
||||
status: response.status.as_u16(),
|
||||
headers,
|
||||
})
|
||||
}
|
||||
142
crates/wasm/src/tests.rs
Normal file
142
crates/wasm/src/tests.rs
Normal file
@@ -0,0 +1,142 @@
|
||||
#![allow(clippy::single_range_in_vec_init)]
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use tls_core::verify::WebPkiVerifier;
|
||||
use tlsn_prover::tls::{Prover, ProverConfig};
|
||||
use tlsn_verifier::tls::{Verifier, VerifierConfig};
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
use crate::{
|
||||
prover::JsProver,
|
||||
types::{Commit, HttpRequest, Method, Reveal},
|
||||
verifier::JsVerifier,
|
||||
};
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub use wasm_bindgen_rayon::init_thread_pool;
|
||||
|
||||
static CA_CERT_DER: &[u8] = include_bytes!("../../server-fixture/src/tls/root_ca_cert.der");
|
||||
static SERVER_DOMAIN: &str = "test-server.io";
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub async fn test_prove() -> Result<(), JsValue> {
|
||||
let mut root_store = tls_core::anchors::RootCertStore::empty();
|
||||
root_store
|
||||
.add(&tls_core::key::Certificate(CA_CERT_DER.to_vec()))
|
||||
.unwrap();
|
||||
|
||||
let prover = Prover::new(
|
||||
ProverConfig::builder()
|
||||
.id("test")
|
||||
.server_dns(SERVER_DOMAIN)
|
||||
.root_cert_store(root_store)
|
||||
.max_sent_data(1024)
|
||||
.max_recv_data(1024)
|
||||
.build()
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
let mut prover = JsProver::from(prover);
|
||||
|
||||
let uri = format!("https://{}/bytes?size=512", SERVER_DOMAIN);
|
||||
|
||||
prover
|
||||
.setup("ws://localhost:8080/tcp?addr=localhost%3A8010")
|
||||
.await?;
|
||||
|
||||
prover
|
||||
.send_request(
|
||||
"ws://localhost:8080/tcp?addr=localhost%3A8083",
|
||||
HttpRequest {
|
||||
method: Method::GET,
|
||||
uri,
|
||||
headers: HashMap::from([("Accept".to_string(), b"*".to_vec())]),
|
||||
body: None,
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
||||
prover
|
||||
.reveal(Reveal {
|
||||
sent: vec![0..10],
|
||||
recv: vec![0..10],
|
||||
})
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub async fn test_notarize() -> Result<(), JsValue> {
|
||||
let mut root_store = tls_core::anchors::RootCertStore::empty();
|
||||
root_store
|
||||
.add(&tls_core::key::Certificate(CA_CERT_DER.to_vec()))
|
||||
.unwrap();
|
||||
|
||||
let prover = Prover::new(
|
||||
ProverConfig::builder()
|
||||
.id("test")
|
||||
.server_dns(SERVER_DOMAIN)
|
||||
.root_cert_store(root_store)
|
||||
.max_sent_data(1024)
|
||||
.max_recv_data(1024)
|
||||
.build()
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
let mut prover = JsProver::from(prover);
|
||||
|
||||
let uri = format!("https://{SERVER_DOMAIN}/bytes?size=512");
|
||||
|
||||
prover
|
||||
.setup("ws://localhost:8080/tcp?addr=localhost%3A8011")
|
||||
.await?;
|
||||
|
||||
prover
|
||||
.send_request(
|
||||
"ws://localhost:8080/tcp?addr=localhost%3A8083",
|
||||
HttpRequest {
|
||||
method: Method::GET,
|
||||
uri,
|
||||
headers: HashMap::from([("Accept".to_string(), b"*".to_vec())]),
|
||||
body: None,
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
||||
let _ = prover.transcript()?;
|
||||
|
||||
prover
|
||||
.notarize(Commit {
|
||||
sent: vec![0..10],
|
||||
recv: vec![0..10],
|
||||
})
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub async fn test_verifier() -> Result<(), JsValue> {
|
||||
let mut root_store = tls_core::anchors::RootCertStore::empty();
|
||||
root_store
|
||||
.add(&tls_core::key::Certificate(CA_CERT_DER.to_vec()))
|
||||
.unwrap();
|
||||
|
||||
let config = VerifierConfig::builder()
|
||||
.id("test")
|
||||
.max_sent_data(1024)
|
||||
.max_recv_data(1024)
|
||||
.cert_verifier(WebPkiVerifier::new(root_store, None))
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let mut verifier = JsVerifier::from(Verifier::new(config));
|
||||
verifier
|
||||
.connect("ws://localhost:8080/tcp?addr=localhost%3A8012")
|
||||
.await?;
|
||||
verifier.verify().await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
173
crates/wasm/src/types.rs
Normal file
173
crates/wasm/src/types.rs
Normal file
@@ -0,0 +1,173 @@
|
||||
use std::{collections::HashMap, ops::Range};
|
||||
|
||||
use http_body_util::Full;
|
||||
use hyper::body::Bytes;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value as JsonValue;
|
||||
use tlsn_core::commitment::CommitmentKind;
|
||||
use tsify_next::Tsify;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
#[derive(Debug, Tsify, Deserialize)]
|
||||
#[tsify(from_wasm_abi)]
|
||||
#[serde(untagged)]
|
||||
#[non_exhaustive]
|
||||
pub enum Body {
|
||||
Json(JsonValue),
|
||||
}
|
||||
|
||||
#[derive(Debug, Tsify, Deserialize)]
|
||||
#[tsify(from_wasm_abi)]
|
||||
pub enum Method {
|
||||
GET,
|
||||
POST,
|
||||
PUT,
|
||||
DELETE,
|
||||
}
|
||||
|
||||
impl From<Method> for hyper::Method {
|
||||
fn from(value: Method) -> Self {
|
||||
match value {
|
||||
Method::GET => hyper::Method::GET,
|
||||
Method::POST => hyper::Method::POST,
|
||||
Method::PUT => hyper::Method::PUT,
|
||||
Method::DELETE => hyper::Method::DELETE,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Tsify, Deserialize)]
|
||||
#[tsify(from_wasm_abi)]
|
||||
pub struct HttpRequest {
|
||||
pub uri: String,
|
||||
pub method: Method,
|
||||
pub headers: HashMap<String, Vec<u8>>,
|
||||
pub body: Option<Body>,
|
||||
}
|
||||
|
||||
impl TryFrom<HttpRequest> for hyper::Request<Full<Bytes>> {
|
||||
type Error = JsError;
|
||||
|
||||
fn try_from(value: HttpRequest) -> Result<Self, Self::Error> {
|
||||
let mut builder = hyper::Request::builder();
|
||||
builder = builder.uri(value.uri).method(value.method);
|
||||
for (name, value) in value.headers {
|
||||
builder = builder.header(name, value);
|
||||
}
|
||||
|
||||
if let Some(body) = value.body {
|
||||
let body = match body {
|
||||
Body::Json(value) => Full::new(Bytes::from(serde_json::to_vec(&value).unwrap())),
|
||||
};
|
||||
builder.body(body).map_err(Into::into)
|
||||
} else {
|
||||
builder.body(Full::new(Bytes::new())).map_err(Into::into)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Tsify, Serialize)]
|
||||
#[tsify(into_wasm_abi)]
|
||||
pub struct HttpResponse {
|
||||
pub status: u16,
|
||||
pub headers: Vec<(String, Vec<u8>)>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Tsify, Serialize)]
|
||||
#[tsify(into_wasm_abi)]
|
||||
pub struct Transcript {
|
||||
pub sent: Vec<u8>,
|
||||
pub recv: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Tsify, Deserialize)]
|
||||
#[tsify(from_wasm_abi)]
|
||||
pub struct Commit {
|
||||
pub sent: Vec<Range<usize>>,
|
||||
pub recv: Vec<Range<usize>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Tsify, Deserialize)]
|
||||
#[tsify(from_wasm_abi)]
|
||||
pub struct Reveal {
|
||||
pub sent: Vec<Range<usize>>,
|
||||
pub recv: Vec<Range<usize>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[wasm_bindgen]
|
||||
#[serde(transparent)]
|
||||
pub struct NotarizedSession(tlsn_core::NotarizedSession);
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl NotarizedSession {
|
||||
/// Builds a new proof.
|
||||
pub fn proof(&self, reveal: Reveal) -> Result<TlsProof, JsError> {
|
||||
let mut builder = self.0.data().build_substrings_proof();
|
||||
|
||||
for range in reveal.sent.iter() {
|
||||
builder.reveal_sent(range, CommitmentKind::Blake3)?;
|
||||
}
|
||||
|
||||
for range in reveal.recv.iter() {
|
||||
builder.reveal_recv(range, CommitmentKind::Blake3)?;
|
||||
}
|
||||
|
||||
let substring_proof = builder.build()?;
|
||||
|
||||
Ok(TlsProof(tlsn_core::proof::TlsProof {
|
||||
session: self.0.session_proof(),
|
||||
substrings: substring_proof,
|
||||
}))
|
||||
}
|
||||
|
||||
/// Returns the transcript.
|
||||
pub fn transcript(&self) -> Transcript {
|
||||
Transcript {
|
||||
sent: self.0.data().sent_transcript().data().to_vec(),
|
||||
recv: self.0.data().recv_transcript().data().to_vec(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Serializes to a byte array.
|
||||
pub fn serialize(&self) -> Vec<u8> {
|
||||
bincode::serialize(self).expect("NotarizedSession is serializable")
|
||||
}
|
||||
|
||||
/// Deserializes from a byte array.
|
||||
pub fn deserialize(bytes: Vec<u8>) -> Result<NotarizedSession, JsError> {
|
||||
Ok(bincode::deserialize(&bytes)?)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<tlsn_core::NotarizedSession> for NotarizedSession {
|
||||
fn from(value: tlsn_core::NotarizedSession) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[wasm_bindgen]
|
||||
#[serde(transparent)]
|
||||
pub struct TlsProof(tlsn_core::proof::TlsProof);
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl TlsProof {
|
||||
pub fn serialize(&self) -> Vec<u8> {
|
||||
bincode::serialize(self).expect("TlsProof is serializable")
|
||||
}
|
||||
|
||||
pub fn deserialize(bytes: Vec<u8>) -> Result<TlsProof, JsError> {
|
||||
Ok(bincode::deserialize(&bytes)?)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Tsify, Serialize)]
|
||||
#[tsify(into_wasm_abi)]
|
||||
pub struct VerifierData {
|
||||
pub server_dns: String,
|
||||
pub sent: Vec<u8>,
|
||||
pub sent_auth_ranges: Vec<Range<usize>>,
|
||||
pub received: Vec<u8>,
|
||||
pub received_auth_ranges: Vec<Range<usize>>,
|
||||
}
|
||||
26
crates/wasm/src/verifier/config.rs
Normal file
26
crates/wasm/src/verifier/config.rs
Normal file
@@ -0,0 +1,26 @@
|
||||
use serde::Deserialize;
|
||||
use tsify_next::Tsify;
|
||||
|
||||
#[derive(Debug, Tsify, Deserialize)]
|
||||
#[tsify(from_wasm_abi)]
|
||||
pub struct VerifierConfig {
|
||||
pub id: String,
|
||||
pub max_sent_data: Option<usize>,
|
||||
pub max_received_data: Option<usize>,
|
||||
}
|
||||
|
||||
impl From<VerifierConfig> for tlsn_verifier::tls::VerifierConfig {
|
||||
fn from(value: VerifierConfig) -> Self {
|
||||
let mut builder = tlsn_verifier::tls::VerifierConfig::builder();
|
||||
|
||||
if let Some(value) = value.max_sent_data {
|
||||
builder = builder.max_sent_data(value);
|
||||
}
|
||||
|
||||
if let Some(value) = value.max_received_data {
|
||||
builder = builder.max_recv_data(value);
|
||||
}
|
||||
|
||||
builder.id(value.id).build().unwrap()
|
||||
}
|
||||
}
|
||||
92
crates/wasm/src/verifier/mod.rs
Normal file
92
crates/wasm/src/verifier/mod.rs
Normal file
@@ -0,0 +1,92 @@
|
||||
mod config;
|
||||
|
||||
pub use config::VerifierConfig;
|
||||
|
||||
use enum_try_as_inner::EnumTryAsInner;
|
||||
use tlsn_verifier::tls::{
|
||||
state::{self, Initialized},
|
||||
Verifier,
|
||||
};
|
||||
use tracing::info;
|
||||
use wasm_bindgen::prelude::*;
|
||||
use ws_stream_wasm::{WsMeta, WsStream};
|
||||
|
||||
use crate::types::VerifierData;
|
||||
|
||||
type Result<T> = std::result::Result<T, JsError>;
|
||||
|
||||
#[wasm_bindgen(js_name = Verifier)]
|
||||
pub struct JsVerifier {
|
||||
state: State,
|
||||
}
|
||||
|
||||
#[derive(EnumTryAsInner)]
|
||||
#[derive_err(Debug)]
|
||||
enum State {
|
||||
Initialized(Verifier<state::Initialized>),
|
||||
Connected((Verifier<state::Initialized>, WsStream)),
|
||||
Complete,
|
||||
Error,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for State {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "State")
|
||||
}
|
||||
}
|
||||
|
||||
impl State {
|
||||
fn take(&mut self) -> Self {
|
||||
std::mem::replace(self, State::Error)
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_class = Verifier)]
|
||||
impl JsVerifier {
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(config: VerifierConfig) -> JsVerifier {
|
||||
JsVerifier {
|
||||
state: State::Initialized(Verifier::new(config.into())),
|
||||
}
|
||||
}
|
||||
|
||||
/// Connect to the prover.
|
||||
pub async fn connect(&mut self, prover_url: &str) -> Result<()> {
|
||||
let verifier = self.state.take().try_into_initialized()?;
|
||||
|
||||
info!("Connecting to prover");
|
||||
|
||||
let (_, prover_conn) = WsMeta::connect(prover_url, None).await?;
|
||||
|
||||
info!("Connected to prover");
|
||||
|
||||
self.state = State::Connected((verifier, prover_conn));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Verifies the connection and finalizes the protocol.
|
||||
pub async fn verify(&mut self) -> Result<VerifierData> {
|
||||
let (verifier, prover_conn) = self.state.take().try_into_connected()?;
|
||||
|
||||
let (sent, recv, info) = verifier.verify(prover_conn.into_io()).await?;
|
||||
|
||||
self.state = State::Complete;
|
||||
|
||||
Ok(VerifierData {
|
||||
server_dns: info.server_name.as_str().to_string(),
|
||||
sent: sent.data().to_vec(),
|
||||
sent_auth_ranges: sent.authed().iter_ranges().collect(),
|
||||
received: recv.data().to_vec(),
|
||||
received_auth_ranges: recv.authed().iter_ranges().collect(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<tlsn_verifier::tls::Verifier<Initialized>> for JsVerifier {
|
||||
fn from(value: tlsn_verifier::tls::Verifier<Initialized>) -> Self {
|
||||
Self {
|
||||
state: State::Initialized(value),
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user