Skip to content
This repository was archived by the owner on Mar 25, 2025. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions tlsn/examples/health_care/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
USER_AGENT=
AUTHORIZATION=
250 changes: 250 additions & 0 deletions tlsn/examples/health_care/health_care.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,250 @@
// This example shows how to notarize Discord DMs.
//
// The example uses the notary server implemented in ../../../notary/server

use http_body_util::{BodyExt, Empty};
use hyper::{body::Bytes, Request, StatusCode};
use hyper_util::rt::TokioIo;
use notary_client::{Accepted, NotarizationRequest, NotaryClient};
use serde::{Deserialize, Serialize};
use std::{env, ops::Range, str};
use tlsn_core::proof::TlsProof;
use tlsn_prover::tls::{Prover, ProverConfig};
use tokio::io::AsyncWriteExt as _;
use tokio_util::compat::{FuturesAsyncReadCompatExt, TokioAsyncReadCompatExt};
use tracing::debug;

// Setting of the application server
const SERVER_DOMAIN: &str = "myhealthbank.nhi.gov.tw";
const PAGE_ID: &str = "IHKE3301S01";

// Setting of the notary server — make sure these are the same with the config in ../../../notary/server
const NOTARY_HOST: &str = "127.0.0.1";
const NOTARY_PORT: u16 = 7047;

// P/S: If the following limits are increased, please ensure max-transcript-size of
// the notary server's config (../../../notary/server) is increased too, where
// max-transcript-size = MAX_SENT_DATA + MAX_RECV_DATA
//
// Maximum number of bytes that can be sent from prover to server
const MAX_SENT_DATA: usize = 1 << 12;
// Maximum number of bytes that can be received by prover from server
const MAX_RECV_DATA: usize = 1 << 14;

#[tokio::main]
async fn main() {
tracing_subscriber::fmt::init();

// Load secret variables frome environment for discord server connection
dotenv::dotenv().ok();
let auth_token = env::var("AUTHORIZATION").unwrap();
let user_agent = env::var("USER_AGENT").unwrap();

// Build a client to connect to the notary server.
let notary_client = NotaryClient::builder()
.host(NOTARY_HOST)
.port(NOTARY_PORT)
// WARNING: Always use TLS to connect to notary server, except if notary is running locally
// e.g. this example, hence `enable_tls` is set to False (else it always defaults to True).
.enable_tls(false)
.build()
.unwrap();

// Send requests for configuration and notarization to the notary server.
let notarization_request = NotarizationRequest::builder()
.max_sent_data(MAX_SENT_DATA)
.max_recv_data(MAX_RECV_DATA)
.build()
.unwrap();

let Accepted {
io: notary_connection,
id: session_id,
..
} = notary_client
.request_notarization(notarization_request)
.await
.unwrap();

// Configure a new prover with the unique session id returned from notary client.
let prover_config = ProverConfig::builder()
.id(session_id)
.server_dns(SERVER_DOMAIN)
.max_sent_data(MAX_SENT_DATA)
.max_recv_data(MAX_RECV_DATA)
.build()
.unwrap();

// Create a new prover and set up the MPC backend.
let prover = Prover::new(prover_config)
.setup(notary_connection.compat())
.await
.unwrap();

// Open a new socket to the application server.
let client_socket = tokio::net::TcpStream::connect((SERVER_DOMAIN, 443))
.await
.unwrap();

// Bind the Prover to server connection
let (tls_connection, prover_fut) = prover.connect(client_socket.compat()).await.unwrap();

// Spawn the Prover to be run concurrently
let prover_task = tokio::spawn(prover_fut);

// Attach the hyper HTTP client to the TLS connection
let (mut request_sender, connection) =
hyper::client::conn::http1::handshake(TokioIo::new(tls_connection.compat()))
.await
.unwrap();

// Spawn the HTTP task to be run concurrently
tokio::spawn(connection);

// Build the HTTP request to fetch the DMs
let request = Request::builder()
.uri(format!("/api/ihke3000/{PAGE_ID}/page_load"))
.header("Host", SERVER_DOMAIN)
.header("Accept", "*/*")
.header("Accept-Language", "en-US,en;q=0.5")
.header("Accept-Encoding", "identity")
.header("User-Agent", user_agent)
.header("Authorization", &auth_token)
.header("Connection", "close")
.body(Empty::<Bytes>::new())
.unwrap();

debug!("Sending request");

let response = request_sender.send_request(request).await.unwrap();

debug!("Sent request");

assert!(response.status() == StatusCode::OK, "{}", response.status());

debug!("Request OK");

// Pretty printing :)
let payload = response.into_body().collect().await.unwrap().to_bytes();
let parsed =
serde_json::from_str::<serde_json::Value>(&String::from_utf8_lossy(&payload)).unwrap();
debug!("{}", serde_json::to_string_pretty(&parsed).unwrap());

let root: Root = serde_json::from_str(&String::from_utf8_lossy(&payload)).unwrap();
root.sP_IHKE3101S01_Data_1.iter().for_each(|d| {
// personal expense
println!("personal expense: {:?}", d.parT_AMT);
// health insurance point
println!("health insurance point: {:?}", d.appL_DOT);
});

// The Prover task should be done now, so we can grab it.
let prover = prover_task.await.unwrap().unwrap();

// Prepare for notarization
let mut prover = prover.start_notarize();

// Identify the ranges in the transcript that contain secrets
let (public_ranges, private_ranges) =
find_ranges(prover.sent_transcript().data(), &[auth_token.as_bytes()]);

let recv_len = prover.recv_transcript().data().len();

let builder = prover.commitment_builder();

// Collect commitment ids for the outbound transcript
let mut commitment_ids = public_ranges
.iter()
.chain(private_ranges.iter())
.map(|range| builder.commit_sent(range).unwrap())
.collect::<Vec<_>>();

// Commit to the full received transcript in one shot, as we don't need to redact anything
commitment_ids.push(builder.commit_recv(&(0..recv_len)).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("health_care_notarized_session.json")
.await
.unwrap();
file.write_all(
serde_json::to_string_pretty(&notarized_session)
.unwrap()
.as_bytes(),
)
.await
.unwrap();

let session_proof = notarized_session.session_proof();

let mut proof_builder = notarized_session.data().build_substrings_proof();

// Reveal everything but the auth token (which was assigned commitment id 2)
proof_builder.reveal_by_id(commitment_ids[0]).unwrap();
proof_builder.reveal_by_id(commitment_ids[1]).unwrap();
proof_builder.reveal_by_id(commitment_ids[3]).unwrap();

let substrings_proof = proof_builder.build().unwrap();

let proof = TlsProof {
session: session_proof,
substrings: substrings_proof,
};

// Dump the proof to a file.
let mut file = tokio::fs::File::create("health_care_proof.json")
.await
.unwrap();
file.write_all(serde_json::to_string_pretty(&proof).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<Range<usize>>, Vec<Range<usize>>) {
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..(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)
}

#[derive(Serialize, Deserialize, Debug)]
struct DataItem {
appL_DOT: u32,
cN_T: u32,
feE_Y: u32,
parT_AMT: u32,
}

#[derive(Serialize, Deserialize, Debug)]
struct Root {
sP_IHKE3101S01_Data_1: Vec<DataItem>,
}
76 changes: 76 additions & 0 deletions tlsn/examples/health_care/health_care_verifier.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
use std::{str, time::Duration};

use elliptic_curve::pkcs8::DecodePublicKey;

use tlsn_core::proof::{SessionProof, TlsProof};

/// A simple verifier which reads a proof generated by `health_care.rs` from "health_care_proof.json", verifies
/// it and prints the verified data to the console.
fn main() {
// Deserialize the proof
let proof = std::fs::read_to_string("health_care_proof.json").unwrap();
let proof: TlsProof = serde_json::from_str(proof.as_str()).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;

// Verify the session proof against the Notary's public key
//
// This verifies the identity of the server using a default certificate verifier which trusts
// the root certificates from the `webpki-roots` crate.
session
.verify_with_default_cert_verifier(notary_pubkey())
.unwrap();

let SessionProof {
// The session header that was signed by the Notary is a succinct commitment to the TLS transcript.
header,
// This is the session_info, which contains the server_name, that is checked against the
// certificate chain shared in the TLS handshake.
session_info,
..
} = 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');

println!("-------------------------------------------------------------------");
println!(
"Successfully verified that the bytes below came from a session with {:?} at {}.",
session_info.server_name, time
);
println!("Note that the bytes which the Prover chose not to disclose are shown as X.");
println!();
println!("Bytes sent:");
println!();
print!("{}", String::from_utf8(sent.data().to_vec()).unwrap());
println!();
println!("Bytes received:");
println!();
println!("{}", String::from_utf8(recv.data().to_vec()).unwrap());
println!("-------------------------------------------------------------------");
}

/// Returns a Notary pubkey trusted by this Verifier
fn notary_pubkey() -> p256::PublicKey {
let pem_file = str::from_utf8(include_bytes!(
"../../../notary/server/fixture/notary/notary.pub"
))
.unwrap();
p256::PublicKey::from_public_key_pem(pem_file).unwrap()
}