diff --git a/.gitignore b/.gitignore index 713153906f..4b9b054a4c 100644 --- a/.gitignore +++ b/.gitignore @@ -20,4 +20,10 @@ Cargo.lock .nvimrc # vscode project specific settings -.vscode/ \ No newline at end of file +.vscode/ + +# transcript +*.json + +# env var +*.env diff --git a/tlsn/Cargo.toml b/tlsn/Cargo.toml index dd6561829b..13afc8ea1e 100644 --- a/tlsn/Cargo.toml +++ b/tlsn/Cargo.toml @@ -1,5 +1,11 @@ [workspace] -members = ["tlsn-core", "tlsn-notary", "tlsn-prover", "tests-integration"] +members = [ + "tlsn-core", + "tlsn-notary", + "tlsn-prover", + "tests-integration", + "examples", +] resolver = "2" [workspace.dependencies] diff --git a/tlsn/examples/.env.example b/tlsn/examples/.env.example new file mode 100644 index 0000000000..afda2fd549 --- /dev/null +++ b/tlsn/examples/.env.example @@ -0,0 +1,5 @@ +CONVERSATION_ID="20124652-973145016511139841" +CLIENT_UUID="e6f00000-cccc-dddd-bbbb-eeeeeefaaa27" +AUTH_TOKEN="670ccccccbe2bbbbbbbc1025aaaaaafa55555551" +ACCESS_TOKEN="AAAAAAAAAAAAAAAAAAAAANRILgAA...4puTs%3D1Zv7...WjCpTnA" +CSRF_TOKEN="77d8ef46bd57f722ea7e9f...f4235a713040bfcaac1cd6909" diff --git a/tlsn/examples/Cargo.toml b/tlsn/examples/Cargo.toml new file mode 100644 index 0000000000..ff8f3a8e9c --- /dev/null +++ b/tlsn/examples/Cargo.toml @@ -0,0 +1,49 @@ +[package] +name = "tlsn-examples" +version = "0.0.0" +edition = "2021" +publish = false + +[dev-dependencies] +tlsn-prover.workspace = true +tlsn-notary.workspace = true +tlsn-core.workspace = true + +futures.workspace = true +tokio = { workspace = true, features = [ + "rt", + "rt-multi-thread", + "macros", + "net", + "io-std", + "fs", +] } +tokio-util.workspace = true + +tracing.workspace = true +tracing-subscriber.workspace = true + +hyper = { version = "0.14", features = ["client", "http1"] } + +p256 = { workspace = true, features = ["ecdsa"] } +webpki-roots.workspace = true + +async-tls = { version = "0.12", default-features = false, features = [ + "client", +] } + +serde = { version = "1.0.147", features = ["derive"] } +serde_json = "1.0" +eyre = "0.6.8" +rustls = { version = "0.21" } +rustls-pemfile = { version = "1.0.2" } +tokio-rustls = { version = "0.24.1" } +dotenv = "0.15.0" + +[[example]] +name = "twitter_dm" +path = "twitter_dm.rs" + +[[example]] +name = "simple_notary" +path = "simple_notary.rs" diff --git a/tlsn/examples/rootCA.crt b/tlsn/examples/rootCA.crt new file mode 100644 index 0000000000..d12936ff51 --- /dev/null +++ b/tlsn/examples/rootCA.crt @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE----- +MIICzDCCAbQCCQDDGiT0U3jAszANBgkqhkiG9w0BAQsFADAoMRIwEAYDVQQKDAl0 +bHNub3RhcnkxEjAQBgNVBAMMCXRsc25vdGFyeTAeFw0yMzA2MjYxMTE2MTZaFw0y +ODA2MjQxMTE2MTZaMCgxEjAQBgNVBAoMCXRsc25vdGFyeTESMBAGA1UEAwwJdGxz +bm90YXJ5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA7Vf+O9l4WNXE +Xh48MwjnvZ9wGN/Ls+jzzF1Q+J/QfXAYR/REQgJQmuk6sBgJyXUW7Dr5dKAY5tfL +rjfSaLhdMSxBH/tMepf5HVfEo6jvgk1bdR43DIZw7Z0hfuGUo6qOue8LZry2Nl+9 +VZpG64quRZ///4LdMBQyXcS2yeWKU10yVNBvstKW0i8krqQfbWOIG1nu5nDg5onB +paKUvbyrLyuHLz8gzKDFezxADTugq2KRXYKIZmyRucK+kmnJnZ/k46GZ84Vju15v +ktC0CvaR9IfvLfJMAo1Y0lUR4HjQkEAfjnDFYj5B18KFxXABraVD8UxjeMbAHTjf +i1lV0yp+qQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQABxRni6FZIFeK0KCS1Nrks +ONLVPfvDSNEKpImWFFoJbaSAAankTiQM1nKTY9SRIhqG2t+xJ6c8+qe905lFFvOy +r85LMb3z2ZWs4ez6Uy6IdpSdkTULk+1huE/Y9ZqRJ5aQy7PqiHTe+mNDFmHXGdcS +azHywd4hQeRQhCBXlAG7I18uZR9DPtGaJnvZlfbpD6Iq7x3ocfGhQiV9VJS1JaQ3 +Z7CJs2pa4da5FXQMAbKI2f7V5kbn3bjMp57yeYFo5wJMhEeSFqkrojR0oZDzfxW9 +b0W/PI4R4d2hUvX0fwrQyXbGo8HvYDFUhlMMSF60gUNcbpF6P93tXxR2FM/hnu+T +-----END CERTIFICATE----- diff --git a/tlsn/examples/simple_notary.rs b/tlsn/examples/simple_notary.rs new file mode 100644 index 0000000000..6955a8082a --- /dev/null +++ b/tlsn/examples/simple_notary.rs @@ -0,0 +1,58 @@ +/// This is a simple implementation of the notary server with minimal functionalities (without TLS, does not support WebSocket and configuration etc.) +/// For a more functional notary server implementation, please use https://github.com/tlsnotary/notary-server +use std::env; + +use tokio::net::TcpListener; +use tokio_util::compat::TokioAsyncReadCompatExt; + +use tlsn_notary::{bind_notary, NotaryConfig}; + +#[tokio::main] +async fn main() { + tracing_subscriber::fmt::init(); + + // Allow passing an address to listen on as the first argument of this + // program, but otherwise we'll just set up our TCP listener on + // 127.0.0.1:8080 for connections. + let addr = env::args() + .nth(1) + .unwrap_or_else(|| "127.0.0.1:8080".to_string()); + + // Next up we create a TCP listener which will listen for incoming + // connections. This TCP listener is bound to the address we determined + // above and must be associated with an event loop. + let listener = TcpListener::bind(&addr).await.unwrap(); + + println!("Listening on: {}", addr); + + // Generate a signing key + let signing_key = p256::ecdsa::SigningKey::from_bytes(&[1u8; 32].into()).unwrap(); + + loop { + // Asynchronously wait for an inbound socket. + let (socket, socket_addr) = listener.accept().await.unwrap(); + + println!("Accepted connection from: {}", socket_addr); + + { + let signing_key = signing_key.clone(); + + // Spawn notarization task to be run concurrently + tokio::spawn(async move { + // Setup default notary config. Normally a different ID would be generated + // for each notarization. + let config = NotaryConfig::builder().id("example").build().unwrap(); + + // Bind the notary to the socket + let (notary, notary_fut) = bind_notary(config, socket.compat()).unwrap(); + + // Run the notary + tokio::try_join!( + notary_fut, + notary.notarize::(&signing_key) + ) + .unwrap(); + }); + } + } +} diff --git a/tlsn/examples/twitter_dm.md b/tlsn/examples/twitter_dm.md new file mode 100644 index 0000000000..caf7d4a532 --- /dev/null +++ b/tlsn/examples/twitter_dm.md @@ -0,0 +1,105 @@ +# Notarize Twitter DMs + +The `twtter_dm.rs` example sets up a TLS connection with Twitter and notarizes the requested DMs. The full received transcript is notarized in one commitment, so nothing is redacted. The result is written to a local JSON file (`twitter_dm.json`) for easier inspection. + +This involves 3 steps: +1. Configure the inputs +2. Start the (local) notary server +3. Notarize + +## Inputs + +In this tlsn/examples folder, create a `.env` file. +Then in that `.env` file, set the values of the following constants by following the format shown in this [example env file](./.env.example). + +| Name | Example | Location in Request Headers Section (within Network Tab of Developer Tools) | +| --------------- | ------------------------------------------------------- |---------------------------------------------------------------------------------- | +| CONVERSATION_ID | `20124652-973145016511139841` | Look for `Referer`, then extract the `ID` in `https://twitter.com/messages/` | +| CLIENT_UUID | `e6f00000-cccc-dddd-bbbb-eeeeeefaaa27` | Look for `X-Client-Uuid`, then copy the entire value | +| AUTH_TOKEN | `670ccccccbe2bbbbbbbc1025aaaaaafa55555551` | Look for `Cookie`, then extract the `token` in `;auth_token=;` | +| ACCESS_TOKEN | `AAAAAAAAAAAAAAAAAAAAANRILgAA...4puTs%3D1Zv7...WjCpTnA` | Look for `Authorization`, then extract the `token` in `Bearer ` | +| CSRF_TOKEN | `77d8ef46bd57f722ea7e9f...f4235a713040bfcaac1cd6909` | Look for `X-Csrf-Token`, then copy the entire value | + +You can obtain these parameters by opening [Twitter](https://twitter.com/messages/) in your browser and accessing the message history you want to notarize. Please note that notarizing only works for short transcripts at the moment, so choose a contact with a short history. + +Next, open the **Developer Tools**, go to the **Network** tab, and refresh the page. Then, click on **Search** and type `uuid` as shown in the screenshot below — all of these constants should be under the **Request Headers** section. Refer to the table above on where to find each of the constant value. + +![Screenshot](twitter_dm_browser.png) + +## Start the notary server + +``` +git clone https://github.com/tlsnotary/notary-server +cd notary-server +cargo run --release +``` + +The notary server will now be running in the background waiting for connections. + +For more information on how to configure the notary server, please refer to [this](https://github.com/tlsnotary/notary-server#running-the-server). + +## Notarize + +In this tlsn/examples folder, run the following command: + +```sh +RUST_LOG=debug,yamux=info cargo run --release --example twitter_dm +``` + +If everything goes well, you should see output similar to the following: + +```log + Compiling tlsn-examples v0.0.0 (/Users/heeckhau/tlsnotary/tlsn/tlsn/examples) + Finished release [optimized] target(s) in 8.52s + Running `/Users/heeckhau/tlsnotary/tlsn/tlsn/target/release/examples/twitter_dm` +2023-08-15T12:49:38.532924Z DEBUG rustls::client::hs: No cached session for DnsName("tlsnotaryserver.io") +2023-08-15T12:49:38.533384Z DEBUG rustls::client::hs: Not resuming any session +2023-08-15T12:49:38.543493Z DEBUG rustls::client::hs: Using ciphersuite TLS13_AES_256_GCM_SHA384 +2023-08-15T12:49:38.543632Z DEBUG rustls::client::tls13: Not resuming +2023-08-15T12:49:38.543792Z DEBUG rustls::client::tls13: TLS1.3 encrypted extensions: [ServerNameAck] +2023-08-15T12:49:38.543803Z DEBUG rustls::client::hs: ALPN protocol is None +2023-08-15T12:49:38.544305Z DEBUG twitter_dm: Sending configuration request +2023-08-15T12:49:38.544556Z DEBUG hyper::proto::h1::io: flushed 163 bytes +2023-08-15T12:49:38.546069Z DEBUG hyper::proto::h1::io: parsed 3 headers +2023-08-15T12:49:38.546078Z DEBUG hyper::proto::h1::conn: incoming body is content-length (52 bytes) +2023-08-15T12:49:38.546168Z DEBUG hyper::proto::h1::conn: incoming body completed +2023-08-15T12:49:38.546187Z DEBUG twitter_dm: Sent configuration request +2023-08-15T12:49:38.546192Z DEBUG twitter_dm: Response OK +2023-08-15T12:49:38.546224Z DEBUG twitter_dm: Notarization response: NotarizationSessionResponse { session_id: "2675e0f9-d06c-499b-8e9e-2b893a6d7356" } +2023-08-15T12:49:38.546257Z DEBUG twitter_dm: Sending notarization request +2023-08-15T12:49:38.546291Z DEBUG hyper::proto::h1::io: flushed 152 bytes +2023-08-15T12:49:38.546743Z DEBUG hyper::proto::h1::io: parsed 3 headers +2023-08-15T12:49:38.546748Z DEBUG hyper::proto::h1::conn: incoming body is empty +2023-08-15T12:49:38.546766Z DEBUG twitter_dm: Sent notarization request +2023-08-15T12:49:38.546772Z DEBUG twitter_dm: Switched protocol OK +2023-08-15T12:49:40.088422Z DEBUG twitter_dm: Sending request +2023-08-15T12:49:40.088464Z DEBUG hyper::proto::h1::io: flushed 950 bytes +2023-08-15T12:49:40.143884Z DEBUG tls_client::client::hs: ALPN protocol is None +2023-08-15T12:49:40.143893Z DEBUG tls_client::client::hs: Using ciphersuite Tls12(Tls12CipherSuite { suite: TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, algorithm: AES_128_GCM }) +2023-08-15T12:49:40.144666Z DEBUG tls_client::client::tls12: ECDHE curve is ECParameters { curve_type: NamedCurve, named_group: secp256r1 } +2023-08-15T12:49:40.144687Z DEBUG tls_client::client::tls12: Server DNS name is DnsName(DnsName(DnsName("twitter.com"))) +2023-08-15T12:51:01.336491Z DEBUG hyper::proto::h1::io: parsed 31 headers +2023-08-15T12:51:01.336507Z DEBUG hyper::proto::h1::conn: incoming body is content-length (4330 bytes) +2023-08-15T12:51:01.336516Z DEBUG hyper::proto::h1::conn: incoming body completed +2023-08-15T12:51:01.336528Z DEBUG twitter_dm: Sent request +2023-08-15T12:51:01.336537Z DEBUG twitter_dm: Request OK +2023-08-15T12:51:01.336585Z DEBUG twitter_dm: { + "conversation_timeline": { + "entries": [ + { + "message": { + "conversation_id": "20124652-45653288", + ... + "withheld_in_countries": [] + } + } + } +} +2023-08-15T12:51:08.854818Z DEBUG twitter_dm: Notarization complete! +``` + +If the transcript was too long, you may encounter the following error: + +``` +thread 'tokio-runtime-worker' panicked at 'called `Result::unwrap()` on an `Err` value: IOError(Custom { kind: InvalidData, error: BackendError(DecryptionError("Other: KOSReceiverActor is not setup")) })', /Users/heeckhau/tlsnotary/tlsn/tlsn/tlsn-prover/src/lib.rs:173:50 +``` diff --git a/tlsn/examples/twitter_dm.rs b/tlsn/examples/twitter_dm.rs new file mode 100644 index 0000000000..297fd2b4ad --- /dev/null +++ b/tlsn/examples/twitter_dm.rs @@ -0,0 +1,324 @@ +/// This prover implementation talks to the notary server implemented in https://github.com/tlsnotary/notary-server, instead of the simple_notary.rs in this example directory +use eyre::Result; +use futures::AsyncWriteExt; +use hyper::{body::to_bytes, client::conn::Parts, Body, Request, StatusCode}; +use rustls::{Certificate, ClientConfig, RootCertStore}; +use serde::{Deserialize, Serialize}; +use std::{ + env, + fs::File as StdFile, + io::BufReader, + net::{IpAddr, SocketAddr}, + ops::Range, + sync::Arc, +}; +use tokio::{fs::File, io::AsyncWriteExt as _}; +use tokio_rustls::TlsConnector; +use tokio_util::compat::{FuturesAsyncReadCompatExt, TokioAsyncReadCompatExt}; +use tracing::debug; + +use tlsn_prover::{bind_prover, ProverConfig}; + +// Setting of the application server +const SERVER_DOMAIN: &str = "twitter.com"; +const ROUTE: &str = "i/api/1.1/dm/conversation"; +const USER_AGENT: &str = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36"; + +// Setting of the notary server — make sure these are the same with those in the notary-server repository used (https://github.com/tlsnotary/notary-server) +const NOTARY_DOMAIN: &str = "127.0.0.1"; +const NOTARY_PORT: u16 = 7047; +const NOTARY_CA_CERT_PATH: &str = "./rootCA.crt"; + +// Configuration of notarization +const NOTARY_MAX_TRANSCRIPT_SIZE: usize = 16384; + +/// Response object of the /session API +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct NotarizationSessionResponse { + 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, +} + +/// 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, +} + +#[tokio::main] +async fn main() { + tracing_subscriber::fmt::init(); + + // Load secret variables frome environment for twitter server connection + dotenv::dotenv().ok(); + let conversation_id = env::var("CONVERSATION_ID").unwrap(); + let client_uuid = env::var("CLIENT_UUID").unwrap(); + let auth_token = env::var("AUTH_TOKEN").unwrap(); + let access_token = env::var("ACCESS_TOKEN").unwrap(); + let csrf_token = env::var("CSRF_TOKEN").unwrap(); + + // Connect to the Notary via TLS-TCP + let mut certificate_file_reader = read_pem_file(NOTARY_CA_CERT_PATH).await.unwrap(); + let mut certificates: Vec = rustls_pemfile::certs(&mut certificate_file_reader) + .unwrap() + .into_iter() + .map(Certificate) + .collect(); + let certificate = certificates.remove(0); + + let mut root_store = RootCertStore::empty(); + root_store.add(&certificate).unwrap(); + + let client_notary_config = ClientConfig::builder() + .with_safe_defaults() + .with_root_certificates(root_store) + .with_no_client_auth(); + let notary_connector = TlsConnector::from(Arc::new(client_notary_config)); + + let notary_socket = tokio::net::TcpStream::connect(SocketAddr::new( + IpAddr::V4(NOTARY_DOMAIN.parse().unwrap()), + NOTARY_PORT, + )) + .await + .unwrap(); + + let notary_tls_socket = notary_connector + // Require the domain name of notary server to be the same as that in the server cert + .connect("tlsnotaryserver.io".try_into().unwrap(), notary_socket) + .await + .unwrap(); + + // Attach the hyper HTTP client to the notary TLS connection to send request to the /session endpoint to configure notarization and obtain session id + let (mut request_sender, connection) = hyper::client::conn::handshake(notary_tls_socket) + .await + .unwrap(); + + // Spawn the HTTP task to be run concurrently + let connection_task = tokio::spawn(connection.without_shutdown()); + + // Build the HTTP request to configure notarization + let payload = serde_json::to_string(&NotarizationSessionRequest { + client_type: ClientType::Tcp, + max_transcript_size: Some(NOTARY_MAX_TRANSCRIPT_SIZE), + }) + .unwrap(); + let request = Request::builder() + .uri(format!("https://{NOTARY_DOMAIN}:{NOTARY_PORT}/session")) + .method("POST") + .header("Host", NOTARY_DOMAIN.clone()) + // Need to specify application/json for axum to parse it as json + .header("Content-Type", "application/json") + .body(Body::from(payload)) + .unwrap(); + + debug!("Sending configuration request"); + + let configuration_response = request_sender.send_request(request).await.unwrap(); + + debug!("Sent configuration request"); + + assert!(configuration_response.status() == StatusCode::OK); + + debug!("Response OK"); + + // Pretty printing :) + let payload = to_bytes(configuration_response.into_body()) + .await + .unwrap() + .to_vec(); + let notarization_response = + serde_json::from_str::(&String::from_utf8_lossy(&payload)) + .unwrap(); + + debug!("Notarization response: {:?}", notarization_response,); + + // Send notarization request via HTTP, where the underlying TCP connection will be extracted later + let request = Request::builder() + .uri(format!("https://{NOTARY_DOMAIN}:{NOTARY_PORT}/notarize")) + .method("GET") + .header("Host", NOTARY_DOMAIN) + .header("Connection", "Upgrade") + // Need to specify this upgrade header for server to extract tcp connection later + .header("Upgrade", "TCP") + // Need to specify the session_id so that notary server knows the right configuration to use + // as the configuration is set in the previous HTTP call + .header("X-Session-Id", notarization_response.session_id.clone()) + .body(Body::empty()) + .unwrap(); + + debug!("Sending notarization request"); + + let response = request_sender.send_request(request).await.unwrap(); + + debug!("Sent notarization request"); + + assert!(response.status() == StatusCode::SWITCHING_PROTOCOLS); + + debug!("Switched protocol OK"); + + // Claim back the TLS socket after HTTP exchange is done + let Parts { + io: notary_tls_socket, + .. + } = connection_task.await.unwrap().unwrap(); + + // Connect to the Server + // Basic default prover config using the session_id returned from /session endpoint just now + let config = ProverConfig::builder() + .id(notarization_response.session_id) + .server_dns(SERVER_DOMAIN) + .build() + .unwrap(); + + let client_socket = tokio::net::TcpStream::connect((SERVER_DOMAIN, 443)) + .await + .unwrap(); + + // Bind the Prover to the sockets + let (tls_connection, prover_fut, mux_fut) = + bind_prover(config, client_socket.compat(), notary_tls_socket.compat()) + .await + .unwrap(); + + // Spawn the Prover and Mux tasks to be run concurrently + tokio::spawn(mux_fut); + let prover_task = tokio::spawn(prover_fut); + + // Attach the hyper HTTP client to the TLS connection + let (mut request_sender, connection) = hyper::client::conn::handshake(tls_connection.compat()) + .await + .unwrap(); + + // Spawn the HTTP task to be run concurrently + let connection_task = tokio::spawn(connection.without_shutdown()); + + // Build the HTTP request to fetch the DMs + let request = Request::builder() + .uri(format!( + "https://{SERVER_DOMAIN}/{ROUTE}/{conversation_id}.json" + )) + .header("Host", SERVER_DOMAIN) + .header("Accept", "*/*") + .header("Accept-Encoding", "identity") + .header("Connection", "close") + .header("User-Agent", USER_AGENT) + .header("Authorization", format!("Bearer {access_token}")) + .header( + "Cookie", + format!("auth_token={auth_token}; ct0={csrf_token}"), + ) + .header("Authority", SERVER_DOMAIN) + .header("X-Twitter-Auth-Type", "OAuth2Session") + .header("x-twitter-active-user", "yes") + .header("X-Client-Uuid", client_uuid) + .header("X-Csrf-Token", csrf_token.clone()) + .body(Body::empty()) + .unwrap(); + + debug!("Sending request"); + + let response = request_sender.send_request(request).await.unwrap(); + + debug!("Sent request"); + + assert!(response.status() == StatusCode::OK); + + debug!("Request OK"); + + // Pretty printing :) + let payload = to_bytes(response.into_body()).await.unwrap().to_vec(); + let parsed = + serde_json::from_str::(&String::from_utf8_lossy(&payload)).unwrap(); + debug!("{}", serde_json::to_string_pretty(&parsed).unwrap()); + + // Close the connection to the server + let mut client_socket = connection_task.await.unwrap().unwrap().io.into_inner(); + client_socket.close().await.unwrap(); + + // The Prover task should be done now, so we can grab it. + let mut prover = prover_task.await.unwrap().unwrap(); + + // Identify the ranges in the transcript that contain secrets + let (public_ranges, private_ranges) = find_ranges( + prover.sent_transcript().data(), + &[ + access_token.as_bytes(), + auth_token.as_bytes(), + csrf_token.as_bytes(), + ], + ); + + // Commit to the outbound transcript, isolating the data that contain secrets + for range in public_ranges.iter().chain(private_ranges.iter()) { + prover.add_commitment_sent(range.clone()).unwrap(); + } + + // Commit to the full received transcript in one shot, as we don't need to redact anything + let recv_len = prover.recv_transcript().data().len(); + prover.add_commitment_recv(0..recv_len as u32).unwrap(); + + // Finalize, returning the notarized session + let notarized_session = prover.finalize().await.unwrap(); + + debug!("Notarization complete!"); + + // Dump the notarized session to a file + let mut file = tokio::fs::File::create("twitter_dm.json").await.unwrap(); + file.write_all( + serde_json::to_string_pretty(¬arized_session) + .unwrap() + .as_bytes(), + ) + .await + .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], sub_seq: &[&[u8]]) -> (Vec>, Vec>) { + let mut private_ranges = Vec::new(); + for s in sub_seq { + for (idx, w) in seq.windows(s.len()).enumerate() { + if w == *s { + private_ranges.push(idx as u32..(idx + w.len()) as u32); + } + } + } + + 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() as u32 { + public_ranges.push(last_end..seq.len() as u32); + } + + (public_ranges, private_ranges) +} + +/// Read a PEM-formatted file and return its buffer reader +async fn read_pem_file(file_path: &str) -> Result> { + let key_file = File::open(file_path).await?.into_std().await; + Ok(BufReader::new(key_file)) +} diff --git a/tlsn/examples/twitter_dm_browser.png b/tlsn/examples/twitter_dm_browser.png new file mode 100644 index 0000000000..96df52498c Binary files /dev/null and b/tlsn/examples/twitter_dm_browser.png differ