Files
tlsn/crates/examples/discord/discord_dm.rs
Hendrik Eeckhaut dfc162929d fix(examples): fix examples for alpha.7 release (#603)
* doc: Fix examples for alpha7 release

+ Use secp256k1 key for notary server fixture
+ fix tower issue
+ Fixed doctes issues (Avoid doc test failures when ignored tests are run)
+ Run wasm tests in incognitto mode to avoid chromiumoxide ws errors

* Added comment

* minor improvements

* formatting

* polish attestation example

* use shorthand fs write

* clean

* simplify discord example

---------

Co-authored-by: sinu <65924192+sinui0@users.noreply.github.com>
2024-10-02 20:39:47 -07:00

217 lines
7.0 KiB
Rust

// 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 std::{env, str};
use tokio_util::compat::{FuturesAsyncReadCompatExt, TokioAsyncReadCompatExt};
use tracing::debug;
use utils::range::RangeSet;
use tlsn_common::config::ProtocolConfig;
use tlsn_core::{request::RequestConfig, transcript::TranscriptCommitConfig};
use tlsn_prover::{Prover, ProverConfig};
// Setting of the application server
const SERVER_DOMAIN: &str = "discord.com";
// 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;
// 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 channel_id = env::var("CHANNEL_ID").unwrap();
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
.expect("Could not connect to notary. Make sure it is running.");
// Set up protocol configuration for prover.
let protocol_config = ProtocolConfig::builder()
.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_config = ProverConfig::builder()
.server_name(SERVER_DOMAIN)
.protocol_config(protocol_config)
.build()
.unwrap();
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!(
"https://{SERVER_DOMAIN}/api/v9/channels/{channel_id}/messages?limit=2"
))
.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());
// 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 sent_transcript = prover.transcript().sent();
let recv_transcript = prover.transcript().received();
// Identify the ranges in the outbound data which contain data which we want to
// disclose
let (sent_public_ranges, _) = find_ranges(sent_transcript, &[auth_token.as_bytes()]);
#[allow(clippy::single_range_in_vec_init)]
let recv_public_ranges = RangeSet::from([0..recv_transcript.len()]);
let mut builder = TranscriptCommitConfig::builder(prover.transcript());
// Commit to public ranges
builder.commit_sent(&sent_public_ranges).unwrap();
builder.commit_recv(&recv_public_ranges).unwrap();
let config = builder.build().unwrap();
prover.transcript_commit(config);
// Finalize, returning the notarized session
let request_config = RequestConfig::default();
let (attestation, secrets) = prover.finalize(&request_config).await.unwrap();
debug!("Notarization complete!");
tokio::fs::write(
"discord.attestation.tlsn",
bincode::serialize(&attestation).unwrap(),
)
.await
.unwrap();
tokio::fs::write(
"discord.secrets.tlsn",
bincode::serialize(&secrets).unwrap(),
)
.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]]) -> (RangeSet<usize>, RangeSet<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());
}
(
RangeSet::from(public_ranges),
RangeSet::from(private_ranges),
)
}