Skip to content

Commit

Permalink
add tlsn manifest support
Browse files Browse the repository at this point in the history
  • Loading branch information
lonerapier committed Feb 21, 2025
1 parent d3c3355 commit 9e761a0
Show file tree
Hide file tree
Showing 6 changed files with 139 additions and 8 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ tlsn-prover ={ git="https://github.com/tlsnotary/tlsn.git", tag="v0.1.0-alpha.7
tlsn-common ={ git="https://github.com/tlsnotary/tlsn.git", tag="v0.1.0-alpha.7" }
tlsn-core ={ git="https://github.com/tlsnotary/tlsn.git", tag="v0.1.0-alpha.7" }
tls-client ={ git="https://github.com/tlsnotary/tlsn.git", tag="v0.1.0-alpha.7", package="tlsn-tls-client" }
tlsn-utils ={ git="https://github.com/tlsnotary/tlsn-utils", rev="e7b2db6" }

tls-client2 ={ git="https://github.com/pluto/tls-origo-legacy", package="tls-client" }
tls-core ={ git="https://github.com/pluto/tls-origo-legacy", package="tls-core" }
Expand Down
1 change: 1 addition & 0 deletions client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ tls-client ={ workspace=true }
tls-client2 ={ workspace=true }
tls-core ={ workspace=true }
tlsn-common ={ workspace=true }
tlsn-utils ={ workspace=true }
tlsn-formats ={ git="https://github.com/tlsnotary/tlsn.git", tag="v0.1.0-alpha.7" }
client-side-prover={ git="https://github.com/pluto/client-side-prover", rev="8e7eb839e901dcee416179116bb0f9c4f7ae683c" }

Expand Down
2 changes: 1 addition & 1 deletion client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ pub async fn prover_inner_tlsn(mut config: config::Config) -> Result<Proof, erro
tlsn_native::setup_tcp_connection(&mut config, prover_config).await
};

let p = tlsn::notarize(prover).await?;
let p = tlsn::notarize(prover, &config.proving.manifest).await?;

// TODO(WJ 2025-02-04): We might want to return an presentation instead of an attestation here, no
// sure yet. The thought process here is that the verify api on TLSN takes a presentation, not
Expand Down
98 changes: 92 additions & 6 deletions client/src/tlsn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@ use std::time::Duration;

use http_body_util::BodyExt;
use hyper::{body::Bytes, Request};
use proofs::program::manifest::Manifest;
use serde::{Deserialize, Serialize};
use spansy::{
json::{parse, JsonValue},
Spanned,
};
pub use tlsn_core::attestation::Attestation;
use tlsn_core::{
presentation::{Presentation, PresentationOutput},
Expand All @@ -16,10 +21,15 @@ use tlsn_core::{
use tlsn_formats::http::{DefaultHttpCommitter, HttpCommit, HttpTranscript};
use tlsn_prover::{state::Closed, Prover};
use tracing::debug;
use utils::range::ToRangeSet;
use web_proof_circuits_witness_generator::json::JsonKey;

use crate::errors;

pub async fn notarize(prover: Prover<Closed>) -> Result<Presentation, errors::ClientErrors> {
pub async fn notarize(
prover: Prover<Closed>,
manifest: &Option<Manifest>,
) -> Result<Presentation, errors::ClientErrors> {
let mut prover = prover.start_notarize();
let transcript = HttpTranscript::parse(prover.transcript())?;

Expand All @@ -32,7 +42,7 @@ pub async fn notarize(prover: Prover<Closed>) -> Result<Presentation, errors::Cl
let config = RequestConfig::default();
let (attestation, secrets) = prover.finalize(&config).await?;

let presentation = present(attestation, secrets).await?;
let presentation = present(manifest, attestation, secrets).await?;
Ok(presentation)
}

Expand All @@ -45,29 +55,105 @@ pub struct VerifyResult {
}

pub async fn present(
manifest: &Option<Manifest>,
attestation: Attestation,
secrets: Secrets,
) -> Result<Presentation, errors::ClientErrors> {
// get the manifest
let manifest = match manifest {
Some(manifest) => manifest,
None => return Err(errors::ClientErrors::Other("Manifest is missing".to_string())),
};

// Parse the HTTP transcript.
let transcript = HttpTranscript::parse(secrets.transcript())?;

// Build a transcript proof.
let mut builder = secrets.transcript_proof_builder();

let request = &transcript.requests[0];
// Reveal the structure of the request without the headers or body.
builder.reveal_sent(&request.without_data())?;
// Reveal the request target.
builder.reveal_sent(&request.request.target)?;
// Reveal all headers except the value of the User-Agent header.
// Reveal request headers in manifetst.
for header in &request.headers {
if !header.name.as_str().eq_ignore_ascii_case("User-Agent") {
if manifest.request.headers.contains_key(header.name.as_str().to_ascii_lowercase().as_str()) {
builder.reveal_sent(header)?;
} else {
builder.reveal_sent(&header.without_value())?;
}
}
// Reveal the entire response.
builder.reveal_recv(&transcript.responses[0])?;

// Reveal the response start line and headers.
let response = &transcript.responses[0];
builder.reveal_recv(&response.without_data())?;
// todo: do we need to reveal target value? isn't it already done in previous line?
for header in &response.headers {
if manifest.response.headers.contains_key(header.name.as_str().to_ascii_lowercase().as_str()) {
builder.reveal_recv(header)?;
} else {
builder.reveal_recv(&header.without_value())?;
}
}

let response_body = match &response.body {
Some(body) => body,
None => return Err(errors::ClientErrors::Other("Response body is missing".to_string())),
};

let body_span = response_body.span();
dbg!(&body_span);

// reveal keys specified in manifest
// reveal values specified in manifest

let content_span = response_body.content.span();
let initial_index = match content_span.indices().min() {
Some(index) => index,
None => return Err(errors::ClientErrors::Other("Content span is empty".to_string())),
};
dbg!(initial_index);

let mut content_value = parse(content_span.clone().to_bytes()).unwrap();
content_value.offset(initial_index);
dbg!(&content_value);

for key in manifest.response.body.json.iter() {
let key = match key {
JsonKey::String(s) => s.clone(),
JsonKey::Num(n) => n.to_string(),
};

match content_value {
JsonValue::Object(ref v) => {
// reveal object without pairs
builder.reveal_recv(&v.without_pairs())?;

for kv in v.elems.iter() {
if key.as_str() == kv.key {
// reveal key
builder.reveal_recv(&kv.key.to_range_set())?;
}
}
},
JsonValue::Array(ref v) => {
// reveal array without elements
builder.reveal_recv(&v.without_values())?;
},
_ => {},
};
let key_span = content_value.get(key.as_str());
match key_span {
Some(key_span) => {
content_value = key_span.clone();
},
None =>
return Err(errors::ClientErrors::Other(format!("Key {} not found in response body", key))),
}
}

builder.reveal_recv(&content_value.to_range_set())?;

let transcript_proof = builder.build()?;

Expand Down
44 changes: 43 additions & 1 deletion fixture/client.tlsn_tcp_local.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,47 @@
"target_body": "",
"max_sent_data": 10000,
"max_recv_data": 10000,
"proving": {}
"proving": {
"manifest": {
"manifestVersion": "1",
"id": "reddit-user-karma",
"title": "Total Reddit Karma",
"description": "Generate a proof that you have a certain amount of karma",
"prepareUrl": "https://www.reddit.com/login/",
"request": {
"method": "GET",
"version": "HTTP/1.1",
"url": "https://gist.githubusercontent.com/mattes/23e64faadb5fd4b5112f379903d2572e/raw/74e517a60c21a5c11d94fec8b572f68addfade39/example.json",
"headers": {
"accept-encoding": "identity"
},
"body": {
"userId": "<% userId %>"
},
"vars": {
"userId": {
"regex": "[a-z]{,20}+"
},
"token": {
"type": "base64",
"length": 32
}
}
},
"response": {
"status": "200",
"version": "HTTP/1.1",
"message": "OK",
"headers": {
"content-type": "text/plain; charset=utf-8"
},
"body": {
"json": [
"hello"
],
"contains": "this_string_exists_in_body"
}
}
}
}
}

0 comments on commit 9e761a0

Please sign in to comment.