refactor: selective disclosure api (#380)

* refactor: selective disclosure api

* remove incomplete substring proof API

* remove unnecessary type annotation

* simplify tests

* switch from unit structs to empty structs

* skip committing empty strings

* fix notary server test

* rename RecordKind to MessageKind

* update json commit error doc

* commits -> commits to

* update commit_array doc

* function argument doc styling

* Update tlsn/tlsn-core/src/proof/substrings.rs

Co-authored-by: dan <themighty1@users.noreply.github.com>

---------

Co-authored-by: dan <themighty1@users.noreply.github.com>
This commit is contained in:
sinu.eth
2024-02-08 13:52:15 -08:00
committed by GitHub
parent f9f4a08b6e
commit bb50e3dacf
38 changed files with 929 additions and 1803 deletions

View File

@@ -25,9 +25,9 @@ tracing = [
tlsn-block-cipher = { path = "../cipher/block-cipher" }
tlsn-stream-cipher = { path = "../cipher/stream-cipher" }
tlsn-universal-hash = { path = "../universal-hash" }
mpz-core = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "ecb8c54" }
mpz-garble = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "ecb8c54" }
tlsn-utils-aio = { git = "https://github.com/tlsnotary/tlsn-utils", rev = "8d8ffe1" }
mpz-core = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "90a3778" }
mpz-garble = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "90a3778" }
tlsn-utils-aio = { git = "https://github.com/tlsnotary/tlsn-utils", rev = "33052c6" }
async-trait = "0.1"
derive_builder = "0.12"

View File

@@ -4,9 +4,9 @@ resolver = "2"
[workspace.dependencies]
# tlsn
mpz-circuits = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "ecb8c54" }
mpz-garble = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "ecb8c54" }
tlsn-utils = { git = "https://github.com/tlsnotary/tlsn-utils", rev = "8d8ffe1" }
mpz-circuits = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "90a3778" }
mpz-garble = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "90a3778" }
tlsn-utils = { git = "https://github.com/tlsnotary/tlsn-utils", rev = "33052c6" }
# crypto
aes = "0.8"

View File

@@ -13,9 +13,9 @@ lto = true
[dev-dependencies]
mpz-garble = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "ecb8c54" }
mpz-ot = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "ecb8c54" }
mpz-share-conversion = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "ecb8c54" }
mpz-garble = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "90a3778" }
mpz-ot = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "90a3778" }
mpz-share-conversion = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "90a3778" }
tlsn-block-cipher = { path = "../cipher/block-cipher" }
tlsn-stream-cipher = { path = "../cipher/stream-cipher" }
tlsn-universal-hash = { path = "../universal-hash" }
@@ -23,7 +23,7 @@ tlsn-aead = { path = "../aead" }
tlsn-key-exchange = { path = "../key-exchange" }
tlsn-point-addition = { path = "../point-addition" }
tlsn-hmac-sha256 = { path = "../prf/hmac-sha256" }
tlsn-utils-aio = { git = "https://github.com/tlsnotary/tlsn-utils", rev = "8d8ffe1" }
tlsn-utils-aio = { git = "https://github.com/tlsnotary/tlsn-utils", rev = "33052c6" }
uid-mux = { path = "../uid-mux" }

View File

@@ -17,11 +17,11 @@ tracing = ["dep:tracing", "tlsn-point-addition/tracing"]
mock = []
[dependencies]
mpz-garble = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "ecb8c54" }
mpz-ot = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "ecb8c54" }
mpz-circuits = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "ecb8c54" }
tlsn-utils-aio = { git = "https://github.com/tlsnotary/tlsn-utils", rev = "8d8ffe1" }
mpz-share-conversion-core = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "ecb8c54" }
mpz-garble = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "90a3778" }
mpz-ot = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "90a3778" }
mpz-circuits = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "90a3778" }
tlsn-utils-aio = { git = "https://github.com/tlsnotary/tlsn-utils", rev = "33052c6" }
mpz-share-conversion-core = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "90a3778" }
tlsn-point-addition = { path = "../point-addition" }
p256 = { version = "0.13", features = ["ecdh"] }
async-trait = "0.1"

View File

@@ -17,9 +17,9 @@ mock = ["dep:mpz-core"]
tracing = ["dep:tracing"]
[dependencies]
mpz-core = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "ecb8c54", optional = true }
mpz-share-conversion = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "ecb8c54" }
mpz-share-conversion-core = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "ecb8c54" }
mpz-core = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "90a3778", optional = true }
mpz-share-conversion = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "90a3778" }
mpz-share-conversion-core = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "90a3778" }
p256 = { version = "0.13", features = ["arithmetic"] }
tracing = { version = "0.1", optional = true }
async-trait = "0.1"

View File

@@ -4,8 +4,8 @@ resolver = "2"
[workspace.dependencies]
# tlsn
mpz-circuits = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "ecb8c54" }
mpz-garble = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "ecb8c54" }
mpz-circuits = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "90a3778" }
mpz-garble = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "90a3778" }
# async
async-trait = "0.1"

View File

@@ -18,7 +18,7 @@ mock = []
[dependencies]
tlsn-hmac-sha256-circuits = { path = "../hmac-sha256-circuits" }
tlsn-utils-aio = { git = "https://github.com/tlsnotary/tlsn-utils", rev = "8d8ffe1" }
tlsn-utils-aio = { git = "https://github.com/tlsnotary/tlsn-utils", rev = "33052c6" }
mpz-garble.workspace = true
mpz-circuits.workspace = true

View File

@@ -30,9 +30,9 @@ tracing = [
tlsn-tls-core = { path = "../tls-core", features = ["serde"] }
tlsn-tls-backend = { path = "../tls-backend" }
mpz-core = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "ecb8c54" }
mpz-garble = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "ecb8c54" }
mpz-share-conversion = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "ecb8c54" }
mpz-core = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "90a3778" }
mpz-garble = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "90a3778" }
mpz-share-conversion = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "90a3778" }
tlsn-block-cipher = { path = "../../cipher/block-cipher" }
tlsn-stream-cipher = { path = "../../cipher/stream-cipher" }
@@ -42,7 +42,7 @@ tlsn-key-exchange = { path = "../../key-exchange" }
tlsn-point-addition = { path = "../../point-addition" }
tlsn-hmac-sha256 = { path = "../../prf/hmac-sha256" }
tlsn-utils-aio = { git = "https://github.com/tlsnotary/tlsn-utils", rev = "8d8ffe1" }
tlsn-utils-aio = { git = "https://github.com/tlsnotary/tlsn-utils", rev = "33052c6" }
p256.workspace = true
rand.workspace = true
@@ -59,7 +59,7 @@ ludi = { git = "https://github.com/sinui0/ludi", rev = "b590de5" }
tlsn-tls-client = { path = "../tls-client" }
tlsn-tls-client-async = { path = "../tls-client-async" }
tls-server-fixture = { path = "../tls-server-fixture" }
mpz-ot = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "ecb8c54" }
mpz-ot = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "90a3778" }
uid-mux = { path = "../../uid-mux" }
tracing-subscriber.workspace = true

View File

@@ -12,7 +12,7 @@ edition = "2021"
tracing = ["dep:tracing"]
[dependencies]
tlsn-utils-aio = { git = "https://github.com/tlsnotary/tlsn-utils", rev = "8d8ffe1" }
tlsn-utils-aio = { git = "https://github.com/tlsnotary/tlsn-utils", rev = "33052c6" }
async-trait = "0.1"
futures = "0.3"

View File

@@ -16,9 +16,9 @@ mock = []
[dependencies]
# tlsn
mpz-core = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "ecb8c54" }
mpz-share-conversion-core = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "ecb8c54" }
mpz-share-conversion = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "ecb8c54" }
mpz-core = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "90a3778" }
mpz-share-conversion-core = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "90a3778" }
mpz-share-conversion = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "90a3778" }
# async
async-trait = "0.1"

View File

@@ -300,8 +300,8 @@ async fn test_tcp_prover<S: AsyncWrite + AsyncRead + Send + Unpin + 'static>(
let builder = prover.commitment_builder();
builder.commit_sent(0..sent_len).unwrap();
builder.commit_recv(0..recv_len).unwrap();
builder.commit_sent(&(0..sent_len)).unwrap();
builder.commit_recv(&(0..recv_len)).unwrap();
_ = prover.finalize().await.unwrap();
@@ -472,8 +472,8 @@ async fn test_websocket_prover() {
let builder = prover.commitment_builder();
builder.commit_sent(0..sent_len).unwrap();
builder.commit_recv(0..recv_len).unwrap();
builder.commit_sent(&(0..sent_len)).unwrap();
builder.commit_recv(&(0..recv_len)).unwrap();
_ = prover.finalize().await.unwrap();

View File

@@ -4,7 +4,7 @@ members = [
"tlsn-common",
"tlsn-verifier",
"tlsn-prover",
# "tlsn-formats",
"tlsn-formats",
"tlsn-server-fixture",
"tests-integration",
"examples",
@@ -18,8 +18,7 @@ tlsn-common = { path = "tlsn-common" }
tlsn-prover = { path = "tlsn-prover" }
tlsn-verifier = { path = "tlsn-verifier" }
tlsn-server-fixture = { path = "tlsn-server-fixture" }
#tlsn-formats = { path = "tlsn-formats" }
tlsn-formats = { path = "tlsn-formats" }
tlsn-tls-core = { path = "../components/tls/tls-core" }
tlsn-tls-mpc = { path = "../components/tls/tls-mpc" }
@@ -28,16 +27,16 @@ tlsn-tls-client-async = { path = "../components/tls/tls-client-async" }
tls-server-fixture = { path = "../components/tls/tls-server-fixture" }
uid-mux = { path = "../components/uid-mux" }
tlsn-utils = { git = "https://github.com/tlsnotary/tlsn-utils", rev = "8d8ffe1" }
tlsn-utils-aio = { git = "https://github.com/tlsnotary/tlsn-utils", rev = "8d8ffe1" }
mpz-core = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "ecb8c54" }
mpz-circuits = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "ecb8c54" }
mpz-garble-core = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "ecb8c54" }
mpz-garble = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "ecb8c54" }
mpz-ot = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "ecb8c54" }
mpz-share-conversion = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "ecb8c54" }
tlsn-utils = { git = "https://github.com/tlsnotary/tlsn-utils", rev = "33052c6" }
tlsn-utils-aio = { git = "https://github.com/tlsnotary/tlsn-utils", rev = "33052c6" }
spansy = { git = "https://github.com/tlsnotary/tlsn-utils", rev = "33052c6" }
mpz-core = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "90a3778" }
mpz-circuits = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "90a3778" }
mpz-garble-core = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "90a3778" }
mpz-garble = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "90a3778" }
mpz-ot = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "90a3778" }
mpz-share-conversion = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "90a3778" }
futures = "0.3"
tokio-util = "0.7"
@@ -59,7 +58,6 @@ bincode = "1"
hex = "0.4"
bytes = "1.4"
opaque-debug = "0.3"
spansy = { git = "https://github.com/sinui0/spansy", rev = "becb33d" }
tracing = "0.1"
tracing-subscriber = "0.3"

View File

@@ -145,11 +145,11 @@ async fn main() {
let mut commitment_ids = public_ranges
.iter()
.chain(private_ranges.iter())
.map(|range| builder.commit_sent(range.clone()).unwrap())
.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());
commitment_ids.push(builder.commit_recv(&(0..recv_len)).unwrap());
// Finalize, returning the notarized session
let notarized_session = prover.finalize().await.unwrap();
@@ -173,9 +173,9 @@ async fn main() {
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(commitment_ids[0]).unwrap();
proof_builder.reveal(commitment_ids[1]).unwrap();
proof_builder.reveal(commitment_ids[3]).unwrap();
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();

View File

@@ -152,8 +152,8 @@ async fn build_proof_without_redactions(mut prover: Prover<Notarize>) -> TlsProo
let recv_len = prover.recv_transcript().data().len();
let builder = prover.commitment_builder();
let sent_commitment = builder.commit_sent(0..sent_len).unwrap();
let recv_commitment = builder.commit_recv(0..recv_len).unwrap();
let sent_commitment = builder.commit_sent(&(0..sent_len)).unwrap();
let recv_commitment = builder.commit_recv(&(0..recv_len)).unwrap();
// Finalize, returning the notarized session
let notarized_session = prover.finalize().await.unwrap();
@@ -162,8 +162,8 @@ async fn build_proof_without_redactions(mut prover: Prover<Notarize>) -> TlsProo
let mut proof_builder = notarized_session.data().build_substrings_proof();
// Reveal all the public ranges
proof_builder.reveal(sent_commitment).unwrap();
proof_builder.reveal(recv_commitment).unwrap();
proof_builder.reveal_by_id(sent_commitment).unwrap();
proof_builder.reveal_by_id(recv_commitment).unwrap();
let substrings_proof = proof_builder.build().unwrap();
@@ -197,12 +197,12 @@ async fn build_proof_with_redactions(mut prover: Prover<Notarize>) -> TlsProof {
// Commit to each range of the public outbound data which we want to disclose
let sent_commitments: Vec<_> = sent_public_ranges
.iter()
.map(|r| builder.commit_sent(r.clone()).unwrap())
.map(|range| builder.commit_sent(range).unwrap())
.collect();
// Commit to each range of the public inbound data which we want to disclose
let recv_commitments: Vec<_> = recv_public_ranges
.iter()
.map(|r| builder.commit_recv(r.clone()).unwrap())
.map(|range| builder.commit_recv(range).unwrap())
.collect();
// Finalize, returning the notarized session
@@ -213,10 +213,10 @@ async fn build_proof_with_redactions(mut prover: Prover<Notarize>) -> TlsProof {
// Reveal all the public ranges
for commitment_id in sent_commitments {
proof_builder.reveal(commitment_id).unwrap();
proof_builder.reveal_by_id(commitment_id).unwrap();
}
for commitment_id in recv_commitments {
proof_builder.reveal(commitment_id).unwrap();
proof_builder.reveal_by_id(commitment_id).unwrap();
}
let substrings_proof = proof_builder.build().unwrap();

View File

@@ -163,15 +163,15 @@ async fn main() {
// Commit to send public data and collect commitment ids for the outbound transcript
let mut commitment_ids = public_ranges
.iter()
.map(|range| builder.commit_sent(range.clone()).unwrap())
.map(|range| builder.commit_sent(range).unwrap())
.collect::<Vec<_>>();
// Commit to private data. This is not needed for proof creation but ensures the data
// is in the notarized session file for optional future disclosure.
private_ranges.iter().for_each(|range| {
builder.commit_sent(range.clone()).unwrap();
builder.commit_sent(range).unwrap();
});
// Commit to the received (public) data.
commitment_ids.push(builder.commit_recv(0..recv_len).unwrap());
commitment_ids.push(builder.commit_recv(&(0..recv_len)).unwrap());
// Finalize, returning the notarized session
let notarized_session = prover.finalize().await.unwrap();
@@ -192,7 +192,7 @@ async fn main() {
let mut proof_builder = notarized_session.data().build_substrings_proof();
for commitment_id in commitment_ids {
proof_builder.reveal(commitment_id).unwrap();
proof_builder.reveal_by_id(commitment_id).unwrap();
}
let substrings_proof = proof_builder.build().unwrap();

View File

@@ -64,8 +64,8 @@ async fn prover<T: AsyncWrite + AsyncRead + Send + Unpin + 'static>(notary_socke
let builder = prover.commitment_builder();
// Commit to everything
builder.commit_sent(0..sent_tx_len).unwrap();
builder.commit_recv(0..recv_tx_len).unwrap();
builder.commit_sent(&(0..sent_tx_len)).unwrap();
builder.commit_recv(&(0..recv_tx_len)).unwrap();
let _notarized_session = prover.finalize().await.unwrap();
}

View File

@@ -80,10 +80,10 @@ async fn prover<T: AsyncWrite + AsyncRead + Send + Unpin + 'static>(notary_socke
let builder = prover.commitment_builder();
// Commit to everything
builder.commit_sent(0..sent_tx_len).unwrap();
builder.commit_recv(0..recv_tx_len).unwrap();
builder.commit_sent(&(0..sent_tx_len)).unwrap();
builder.commit_recv(&(0..recv_tx_len)).unwrap();
let _notarized_session = prover.finalize().await.unwrap();
prover.finalize().await.unwrap();
}
#[instrument(skip(socket))]

View File

@@ -2,7 +2,7 @@ use std::collections::HashMap;
use bimap::BiMap;
use mpz_core::hash::Hash;
use utils::range::RangeSet;
use utils::range::{RangeSet, ToRangeSet};
use crate::{
commitment::{
@@ -21,8 +21,13 @@ pub enum TranscriptCommitmentBuilderError {
#[error("can not commit to an empty range")]
EmptyRange,
/// Range out of bounds
#[error("range out of bounds")]
RangeOutOfBounds,
#[error("range out of bounds: {upper_commitment} > {upper_transcript}")]
RangeOutOfBounds {
/// The upper bound of the commitment range
upper_commitment: usize,
/// The upper bound of the transcript range
upper_transcript: usize,
},
/// Failed to retrieve encodings for the provided transcript ranges.
#[error("failed to retrieve encodings for the provided transcript ranges")]
MissingEncodings,
@@ -69,17 +74,29 @@ impl TranscriptCommitmentBuilder {
/// Commits to the provided ranges of the `sent` transcript.
pub fn commit_sent(
&mut self,
ranges: impl Into<RangeSet<usize>>,
ranges: &dyn ToRangeSet<usize>,
) -> Result<CommitmentId, TranscriptCommitmentBuilderError> {
self.add_substrings_commitment(ranges.into(), Direction::Sent)
self.add_substrings_commitment(&ranges.to_range_set(), Direction::Sent)
}
/// Commits to the provided ranges of the `received` transcript.
pub fn commit_recv(
&mut self,
ranges: impl Into<RangeSet<usize>>,
ranges: &dyn ToRangeSet<usize>,
) -> Result<CommitmentId, TranscriptCommitmentBuilderError> {
self.add_substrings_commitment(ranges.into(), Direction::Received)
self.add_substrings_commitment(&ranges.to_range_set(), Direction::Received)
}
/// Commits to the provided ranges of the transcript.
pub fn commit(
&mut self,
ranges: &dyn ToRangeSet<usize>,
direction: Direction,
) -> Result<CommitmentId, TranscriptCommitmentBuilderError> {
match direction {
Direction::Sent => self.commit_sent(ranges),
Direction::Received => self.commit_recv(ranges),
}
}
/// Gets the commitment id for the provided commitment info.
@@ -101,7 +118,7 @@ impl TranscriptCommitmentBuilder {
/// Add a commitment to substrings of the transcript
fn add_substrings_commitment(
&mut self,
ranges: RangeSet<usize>,
ranges: &RangeSet<usize>,
direction: Direction,
) -> Result<CommitmentId, TranscriptCommitmentBuilderError> {
let max = ranges
@@ -113,10 +130,13 @@ impl TranscriptCommitmentBuilder {
};
if max > len {
return Err(TranscriptCommitmentBuilderError::RangeOutOfBounds);
return Err(TranscriptCommitmentBuilderError::RangeOutOfBounds {
upper_commitment: max,
upper_transcript: len,
});
}
let ids: Vec<_> = get_value_ids(&ranges, direction).collect();
let ids: Vec<_> = get_value_ids(ranges, direction).collect();
let id_refs = ids.iter().map(|id| id.as_ref()).collect::<Vec<_>>();
@@ -135,7 +155,7 @@ impl TranscriptCommitmentBuilder {
self.commitment_info
.insert_no_overwrite(
id,
CommitmentInfo::new(commitment.kind(), ranges, direction),
CommitmentInfo::new(commitment.kind(), ranges.clone(), direction),
)
.map_err(|(id, _)| TranscriptCommitmentBuilderError::Duplicate(id))?;

View File

@@ -178,11 +178,11 @@ impl TranscriptCommitments {
pub fn get_id_by_info(
&self,
kind: CommitmentKind,
ranges: RangeSet<usize>,
ranges: &RangeSet<usize>,
direction: Direction,
) -> Option<CommitmentId> {
self.commitment_info
.get_by_right(&CommitmentInfo::new(kind, ranges, direction))
.get_by_right(&CommitmentInfo::new(kind, ranges.clone(), direction))
.copied()
}

View File

@@ -2,7 +2,8 @@
use crate::{
commitment::{
Commitment, CommitmentId, CommitmentInfo, CommitmentOpening, TranscriptCommitments,
Commitment, CommitmentId, CommitmentInfo, CommitmentKind, CommitmentOpening,
TranscriptCommitments,
},
merkle::MerkleProof,
transcript::get_value_ids,
@@ -13,7 +14,7 @@ use mpz_circuits::types::ValueType;
use mpz_garble_core::Encoder;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use utils::range::{RangeDisjoint, RangeSet, RangeUnion};
use utils::range::{RangeDisjoint, RangeSet, RangeUnion, ToRangeSet};
/// An error for [`SubstringsProofBuilder`]
#[derive(Debug, thiserror::Error)]
@@ -22,6 +23,9 @@ pub enum SubstringsProofBuilderError {
/// Invalid commitment id.
#[error("invalid commitment id: {0:?}")]
InvalidCommitmentId(CommitmentId),
/// Missing commitment.
#[error("missing commitment")]
MissingCommitment,
/// Invalid commitment type.
#[error("commitment {0:?} is not a substrings commitment")]
InvalidCommitmentType(CommitmentId),
@@ -60,8 +64,44 @@ impl<'a> SubstringsProofBuilder<'a> {
self.commitments
}
/// Reveals data corresponding to the provided ranges in the sent direction.
pub fn reveal_sent(
&mut self,
ranges: &dyn ToRangeSet<usize>,
commitment_kind: CommitmentKind,
) -> Result<&mut Self, SubstringsProofBuilderError> {
self.reveal(ranges, Direction::Sent, commitment_kind)
}
/// Reveals data corresponding to the provided transcript subsequence in the received direction.
pub fn reveal_recv(
&mut self,
ranges: &dyn ToRangeSet<usize>,
commitment_kind: CommitmentKind,
) -> Result<&mut Self, SubstringsProofBuilderError> {
self.reveal(ranges, Direction::Received, commitment_kind)
}
/// Reveals data corresponding to the provided ranges and direction.
pub fn reveal(
&mut self,
ranges: &dyn ToRangeSet<usize>,
direction: Direction,
commitment_kind: CommitmentKind,
) -> Result<&mut Self, SubstringsProofBuilderError> {
let com = self
.commitments
.get_id_by_info(commitment_kind, &ranges.to_range_set(), direction)
.ok_or(SubstringsProofBuilderError::MissingCommitment)?;
self.reveal_by_id(com)
}
/// Reveals data corresponding to the provided commitment id
pub fn reveal(&mut self, id: CommitmentId) -> Result<&mut Self, SubstringsProofBuilderError> {
pub fn reveal_by_id(
&mut self,
id: CommitmentId,
) -> Result<&mut Self, SubstringsProofBuilderError> {
let commitment = self
.commitments()
.get(&id)

View File

@@ -78,8 +78,8 @@ fn test_api() {
let mut commitment_builder =
TranscriptCommitmentBuilder::new(encodings_provider, data_sent.len(), data_recv.len());
let commitment_id_1 = commitment_builder.commit_sent(range1.clone()).unwrap();
let commitment_id_2 = commitment_builder.commit_recv(range2.clone()).unwrap();
let commitment_id_1 = commitment_builder.commit_sent(&range1).unwrap();
let commitment_id_2 = commitment_builder.commit_recv(&range2).unwrap();
let commitments = commitment_builder.build().unwrap();
@@ -154,9 +154,9 @@ fn test_api() {
let mut substrings_proof_builder = session.data().build_substrings_proof();
substrings_proof_builder
.reveal(commitment_id_1)
.reveal_by_id(commitment_id_1)
.unwrap()
.reveal(commitment_id_2)
.reveal_by_id(commitment_id_2)
.unwrap();
let substrings_proof = substrings_proof_builder.build().unwrap();

View File

@@ -1,136 +0,0 @@
use serde::{Deserialize, Serialize};
use tlsn_core::{
commitment::{CommitmentId, TranscriptCommitmentBuilder, TranscriptCommitments},
proof::SubstringsProofBuilder,
Direction,
};
use crate::{
http::{HttpCommitmentBuilderError, HttpProofBuilderError},
json::{JsonBody, JsonCommitmentBuilder, JsonProofBuilder},
unknown::{UnknownCommitmentBuilder, UnknownProofBuilder, UnknownSpan},
};
/// A body of an HTTP request or response
#[derive(Debug, Serialize, Deserialize)]
#[non_exhaustive]
pub enum Body {
/// A JSON body
Json(JsonBody),
/// A body with an unsupported content type
Unknown(UnknownSpan),
}
/// Builder for commitments to an HTTP body.
#[derive(Debug)]
#[non_exhaustive]
pub enum BodyCommitmentBuilder<'a> {
/// Builder for commitments to a JSON body.
Json(JsonCommitmentBuilder<'a>),
/// Builder for commitments to a body with an unknown format.
Unknown(UnknownCommitmentBuilder<'a>),
}
impl<'a> BodyCommitmentBuilder<'a> {
pub(crate) fn new(
builder: &'a mut TranscriptCommitmentBuilder,
value: &'a Body,
direction: Direction,
built: &'a mut bool,
) -> Self {
match value {
Body::Json(body) => BodyCommitmentBuilder::Json(JsonCommitmentBuilder::new(
builder, &body.0, direction, built,
)),
Body::Unknown(body) => BodyCommitmentBuilder::Unknown(UnknownCommitmentBuilder::new(
builder, body, direction, built,
)),
}
}
/// Commits to the entire body.
pub fn all(&mut self) -> Result<CommitmentId, HttpCommitmentBuilderError> {
match self {
BodyCommitmentBuilder::Json(builder) => builder
.all()
.map_err(|e| HttpCommitmentBuilderError::Body(e.to_string())),
BodyCommitmentBuilder::Unknown(builder) => builder
.all()
.map_err(|e| HttpCommitmentBuilderError::Body(e.to_string())),
}
}
/// Builds the commitment to the body.
pub fn build(self) -> Result<(), HttpCommitmentBuilderError> {
match self {
BodyCommitmentBuilder::Json(builder) => builder
.build()
.map_err(|e| HttpCommitmentBuilderError::Body(e.to_string())),
BodyCommitmentBuilder::Unknown(builder) => builder
.build()
.map_err(|e| HttpCommitmentBuilderError::Body(e.to_string())),
}
}
}
/// Builder for proofs of an HTTP body.
#[derive(Debug)]
#[non_exhaustive]
pub enum BodyProofBuilder<'a, 'b> {
/// Builder for proofs of a JSON body.
Json(JsonProofBuilder<'a, 'b>),
/// Builder for proofs of a body with an unknown format.
Unknown(UnknownProofBuilder<'a, 'b>),
}
impl<'a, 'b> BodyProofBuilder<'a, 'b> {
pub(crate) fn new(
builder: &'a mut SubstringsProofBuilder<'b>,
commitments: &'a TranscriptCommitments,
value: &'a Body,
direction: Direction,
built: &'a mut bool,
) -> Self {
match value {
Body::Json(body) => BodyProofBuilder::Json(JsonProofBuilder::new(
builder,
commitments,
&body.0,
direction,
built,
)),
Body::Unknown(body) => BodyProofBuilder::Unknown(UnknownProofBuilder::new(
builder,
commitments,
body,
direction,
built,
)),
}
}
/// Proves the entire body.
pub fn all(&mut self) -> Result<(), HttpProofBuilderError> {
match self {
BodyProofBuilder::Json(builder) => builder
.all()
.map_err(|e| HttpProofBuilderError::Body(e.to_string())),
BodyProofBuilder::Unknown(builder) => builder
.all()
.map_err(|e| HttpProofBuilderError::Body(e.to_string())),
}
}
/// Builds the proof for the body.
pub fn build(self) -> Result<(), HttpProofBuilderError> {
match self {
BodyProofBuilder::Json(builder) => builder
.build()
.map_err(|e| HttpProofBuilderError::Body(e.to_string())),
BodyProofBuilder::Unknown(builder) => builder
.build()
.map_err(|e| HttpProofBuilderError::Body(e.to_string())),
}
}
}

View File

@@ -0,0 +1,387 @@
use std::error::Error;
use tlsn_core::{commitment::TranscriptCommitmentBuilder, Direction};
use crate::{
http::{Body, BodyContent, Header, HttpTranscript, MessageKind, Request, Response, Target},
json::{DefaultJsonCommitter, JsonCommit},
};
/// HTTP commitment error.
#[derive(Debug, thiserror::Error)]
#[error("http commit error: {msg}")]
pub struct HttpCommitError {
idx: Option<usize>,
record_kind: MessageKind,
msg: String,
#[source]
source: Option<Box<dyn Error + Send + Sync>>,
}
impl HttpCommitError {
/// Creates a new HTTP commitment error.
///
/// # Arguments
///
/// * `record_kind` - The kind of the record (request or response).
/// * `msg` - The error message.
pub fn new(record_kind: MessageKind, msg: impl Into<String>) -> Self {
Self {
idx: None,
record_kind,
msg: msg.into(),
source: None,
}
}
/// Creates a new HTTP commitment error with a source.
///
/// # Arguments
///
/// * `idx` - The index of the request or response in the transcript.
/// * `record_kind` - The kind of the record (request or response).
/// * `msg` - The error message.
/// * `source` - The source error.
pub fn new_with_source<E>(record_kind: MessageKind, msg: impl Into<String>, source: E) -> Self
where
E: Into<Box<dyn Error + Send + Sync>>,
{
Self {
idx: None,
record_kind,
msg: msg.into(),
source: Some(source.into()),
}
}
/// Sets the index of the request or response in the transcript.
pub fn set_index(&mut self, idx: usize) {
self.idx = Some(idx);
}
/// Returns the index of the request or response in the transcript, if set.
pub fn index(&self) -> Option<usize> {
self.idx
}
/// Returns the error message.
pub fn msg(&self) -> &str {
&self.msg
}
/// Returns the kind of record (request or response).
pub fn record_kind(&self) -> &MessageKind {
&self.record_kind
}
}
/// An HTTP data committer.
#[allow(unused_variables)]
pub trait HttpCommit {
/// Commits to an HTTP transcript.
///
/// The default implementation commits to each request and response in the transcript separately.
///
/// # Arguments
///
/// * `builder` - The transcript commitment builder.
/// * `direction` - The direction of the transcript (sent or received).
/// * `transcript` - The transcript to commit.
fn commit_transcript(
&mut self,
builder: &mut TranscriptCommitmentBuilder,
transcript: &HttpTranscript,
) -> Result<(), HttpCommitError> {
for request in &transcript.requests {
self.commit_request(builder, Direction::Sent, request)?;
}
for response in &transcript.responses {
self.commit_response(builder, Direction::Received, response)?;
}
Ok(())
}
/// Commits to a request.
///
/// The default implementation commits to the request excluding the target, headers and body. Additionally,
/// it commits to the target, headers and body separately.
///
/// # Arguments
///
/// * `builder` - The transcript commitment builder.
/// * `direction` - The direction of the request (sent or received).
/// * `request` - The request to commit to.
fn commit_request(
&mut self,
builder: &mut TranscriptCommitmentBuilder,
direction: Direction,
request: &Request,
) -> Result<(), HttpCommitError> {
builder
.commit(&request.without_data(), direction)
.map_err(|e| {
HttpCommitError::new_with_source(
MessageKind::Request,
"failed to commit to request with excluded data",
e,
)
})?;
self.commit_target(builder, direction, request, &request.request.target)?;
for header in &request.headers {
self.commit_request_header(builder, direction, request, header)?;
}
if let Some(body) = &request.body {
self.commit_request_body(builder, direction, request, body)?;
}
Ok(())
}
/// Commits to a request target.
///
/// The default implementation commits to the target as a whole.
///
/// # Arguments
///
/// * `builder` - The transcript commitment builder.
/// * `direction` - The direction of the request (sent or received).
/// * `request` - The parent request.
/// * `target` - The target to commit to.
fn commit_target(
&mut self,
builder: &mut TranscriptCommitmentBuilder,
direction: Direction,
request: &Request,
target: &Target,
) -> Result<(), HttpCommitError> {
builder.commit(target, direction).map_err(|e| {
HttpCommitError::new_with_source(
MessageKind::Request,
"failed to commit to target in request",
e,
)
})?;
Ok(())
}
/// Commits to a request header.
///
/// The default implementation commits to the entire header, and the header excluding the value.
///
/// # Arguments
///
/// * `builder` - The transcript commitment builder.
/// * `direction` - The direction of the request (sent or received).
/// * `parent` - The parent request.
/// * `header` - The header to commit to.
fn commit_request_header(
&mut self,
builder: &mut TranscriptCommitmentBuilder,
direction: Direction,
parent: &Request,
header: &Header,
) -> Result<(), HttpCommitError> {
builder.commit(header, direction).map_err(|e| {
HttpCommitError::new_with_source(
MessageKind::Request,
format!("failed to commit to \"{}\" header", header.name.as_str()),
e,
)
})?;
builder
.commit(&header.without_value(), direction)
.map_err(|e| {
HttpCommitError::new_with_source(
MessageKind::Request,
format!(
"failed to commit to \"{}\" header excluding value",
header.name.as_str()
),
e,
)
})?;
Ok(())
}
/// Commits to a request body.
///
/// The default implementation commits using the default implementation for the
/// format type of the body. If the format of the body is unknown, it commits to the
/// body as a whole.
///
/// # Arguments
///
/// * `builder` - The transcript commitment builder.
/// * `direction` - The direction of the request (sent or received).
/// * `parent` - The parent request.
/// * `body` - The body to commit to.
fn commit_request_body(
&mut self,
builder: &mut TranscriptCommitmentBuilder,
direction: Direction,
parent: &Request,
body: &Body,
) -> Result<(), HttpCommitError> {
match &body.content {
BodyContent::Json(body) => {
DefaultJsonCommitter::default()
.commit_value(builder, body, direction)
.map_err(|e| {
HttpCommitError::new_with_source(
MessageKind::Request,
"failed to commit to JSON body",
e,
)
})?;
}
body => {
builder.commit(body, direction).map_err(|e| {
HttpCommitError::new_with_source(
MessageKind::Request,
"failed to commit to unknown content body",
e,
)
})?;
}
}
Ok(())
}
/// Commits to a response.
///
/// The default implementation commits to the response excluding the headers and body. Additionally,
/// it commits to the headers and body separately.
///
/// # Arguments
///
/// * `builder` - The transcript commitment builder.
/// * `direction` - The direction of the response (sent or received).
/// * `response` - The response to commit to.
fn commit_response(
&mut self,
builder: &mut TranscriptCommitmentBuilder,
direction: Direction,
response: &Response,
) -> Result<(), HttpCommitError> {
builder
.commit(&response.without_data(), direction)
.map_err(|e| {
HttpCommitError::new_with_source(
MessageKind::Response,
"failed to commit to response excluding data",
e,
)
})?;
for header in &response.headers {
self.commit_response_header(builder, direction, response, header)?;
}
if let Some(body) = &response.body {
self.commit_response_body(builder, direction, response, body)?;
}
Ok(())
}
/// Commits to a response header.
///
/// The default implementation commits to the entire header, and the header excluding the value.
///
/// # Arguments
///
/// * `builder` - The transcript commitment builder.
/// * `direction` - The direction of the response (sent or received).
/// * `parent` - The parent response.
/// * `header` - The header to commit to.
fn commit_response_header(
&mut self,
builder: &mut TranscriptCommitmentBuilder,
direction: Direction,
parent: &Response,
header: &Header,
) -> Result<(), HttpCommitError> {
builder.commit(header, direction).map_err(|e| {
HttpCommitError::new_with_source(
MessageKind::Response,
format!("failed to commit to \"{}\" header", header.name.as_str()),
e,
)
})?;
builder
.commit(&header.without_value(), direction)
.map_err(|e| {
HttpCommitError::new_with_source(
MessageKind::Response,
format!(
"failed to commit to \"{}\" header excluding value in response",
header.name.as_str()
),
e,
)
})?;
Ok(())
}
/// Commits to a response body.
///
/// The default implementation commits using the default implementation for the
/// format type of the body. If the format of the body is unknown, it commits to the
/// body as a whole.
///
/// # Arguments
///
/// * `builder` - The transcript commitment builder.
/// * `direction` - The direction of the response (sent or received).
/// * `parent` - The parent response.
/// * `body` - The body to commit to.
fn commit_response_body(
&mut self,
builder: &mut TranscriptCommitmentBuilder,
direction: Direction,
parent: &Response,
body: &Body,
) -> Result<(), HttpCommitError> {
match &body.content {
BodyContent::Json(body) => {
DefaultJsonCommitter::default()
.commit_value(builder, body, direction)
.map_err(|e| {
HttpCommitError::new_with_source(
MessageKind::Response,
"failed to commit to JSON body",
e,
)
})?;
}
body => {
builder.commit(body, direction).map_err(|e| {
HttpCommitError::new_with_source(
MessageKind::Request,
"failed to commit to unknown content body",
e,
)
})?;
}
}
Ok(())
}
}
/// The default HTTP committer.
#[derive(Debug, Default, Clone)]
pub struct DefaultHttpCommitter {}
impl HttpCommit for DefaultHttpCommitter {}

View File

@@ -1,332 +0,0 @@
use std::fmt::Debug;
use crate::http::{Body, BodyCommitmentBuilder, Request, Response};
use spansy::Spanned;
use tlsn_core::{
commitment::{CommitmentId, TranscriptCommitmentBuilder, TranscriptCommitmentBuilderError},
Direction,
};
use utils::range::{RangeSet, RangeSubset, RangeUnion};
use super::PUBLIC_HEADERS;
/// HTTP commitment builder error.
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum HttpCommitmentBuilderError {
/// Header is missing.
#[error("header with name \"{0}\" does not exist.")]
MissingHeader(String),
/// Body commitment error.
#[error("body commitment error: {0}")]
Body(String),
/// Transcript commitment builder error.
#[error("commitment builder error: {0}")]
Commitment(#[from] TranscriptCommitmentBuilderError),
}
/// Builder for commitments to data in an HTTP connection.
#[derive(Debug)]
pub struct HttpCommitmentBuilder<'a> {
builder: &'a mut TranscriptCommitmentBuilder,
requests: &'a [(Request, Option<Body>)],
responses: &'a [(Response, Option<Body>)],
built_requests: Vec<bool>,
built_responses: Vec<bool>,
}
impl<'a> HttpCommitmentBuilder<'a> {
#[doc(hidden)]
pub fn new(
builder: &'a mut TranscriptCommitmentBuilder,
requests: &'a [(Request, Option<Body>)],
responses: &'a [(Response, Option<Body>)],
) -> Self {
Self {
builder,
requests,
responses,
built_requests: vec![false; requests.len()],
built_responses: vec![false; responses.len()],
}
}
/// Returns a commitment builder for the request at the given index.
///
/// # Arguments
///
/// * `index` - The index of the request.
#[must_use]
pub fn request(&mut self, index: usize) -> Option<HttpRequestCommitmentBuilder<'_>> {
self.requests.get(index).map(|request| {
HttpRequestCommitmentBuilder::new(
self.builder,
&request.0,
request.1.as_ref(),
&mut self.built_requests[index],
)
})
}
/// Returns a commitment builder for the response at the given index.
///
/// # Arguments
///
/// * `index` - The index of the response.
#[must_use]
pub fn response(&mut self, index: usize) -> Option<HttpResponseCommitmentBuilder<'_>> {
self.responses.get(index).map(|response| {
HttpResponseCommitmentBuilder::new(
self.builder,
&response.0,
response.1.as_ref(),
&mut self.built_responses[index],
)
})
}
/// Builds commitments to the HTTP requests and responses.
///
/// This automatically will commit to all header values which have no yet been committed.
pub fn build(mut self) -> Result<(), HttpCommitmentBuilderError> {
// Builds all request commitments
for i in 0..self.requests.len() {
if !self.built_requests[i] {
self.request(i).unwrap().build()?;
}
}
// Build all response commitments
for i in 0..self.responses.len() {
if !self.built_responses[i] {
self.response(i).unwrap().build()?;
}
}
Ok(())
}
}
/// Builder for commitments to an HTTP request.
#[derive(Debug)]
pub struct HttpRequestCommitmentBuilder<'a> {
builder: &'a mut TranscriptCommitmentBuilder,
request: &'a Request,
body: Option<&'a Body>,
committed: RangeSet<usize>,
built: &'a mut bool,
body_built: bool,
}
impl<'a> HttpRequestCommitmentBuilder<'a> {
pub(crate) fn new(
builder: &'a mut TranscriptCommitmentBuilder,
request: &'a Request,
body: Option<&'a Body>,
built: &'a mut bool,
) -> Self {
Self {
builder,
request,
body,
committed: RangeSet::default(),
built,
body_built: false,
}
}
/// Commits to the path of the request.
pub fn path(&mut self) -> Result<CommitmentId, HttpCommitmentBuilderError> {
let range = self.request.0.path.range();
let id = self.builder.commit_sent(range.clone())?;
self.committed = self.committed.union(&range);
Ok(id)
}
/// Commits the value of the header with the given name.
///
/// # Arguments
///
/// * `name` - The name of the header value to commit.
pub fn header(&mut self, name: &str) -> Result<CommitmentId, HttpCommitmentBuilderError> {
let header = self
.request
.0
.header(name)
.ok_or(HttpCommitmentBuilderError::MissingHeader(name.to_string()))?;
let range = header.value.span().range();
let id = self.builder.commit_sent(range.clone())?;
self.committed = self.committed.union(&range);
Ok(id)
}
/// Commits all request headers.
///
/// Returns a vector of the names of the headers that were committed and their commitment IDs.
pub fn headers(&mut self) -> Result<Vec<(String, CommitmentId)>, HttpCommitmentBuilderError> {
let mut commitments = Vec::new();
for header in &self.request.0.headers {
let name = header.name.span().as_str().to_string();
let id = self.header(&name)?;
commitments.push((name, id));
}
Ok(commitments)
}
/// Returns a commitment builder for the request body if it exists.
pub fn body(&mut self) -> Option<BodyCommitmentBuilder<'_>> {
self.body.map(|body| {
BodyCommitmentBuilder::new(self.builder, body, Direction::Sent, &mut self.body_built)
})
}
/// Finishes building the request commitment.
///
/// This commits to everything that has not already been committed, including a commitment
/// to the format data of the request.
pub fn build(mut self) -> Result<(), HttpCommitmentBuilderError> {
// Commit to the path if it has not already been committed.
let path_range = self.request.0.path.range();
if !path_range.is_subset(&self.committed) {
self.path()?;
}
// Commit to any headers that have not already been committed.
for header in &self.request.0.headers {
let name = header.name.span().as_str().to_ascii_lowercase();
// Public headers can not be committed separately
if PUBLIC_HEADERS.contains(&name.as_str()) {
continue;
}
let range = header.value.span().range();
if !range.is_subset(&self.committed) {
self.header(&name)?;
}
}
self.builder.commit_sent(self.request.public_ranges())?;
if self.body.is_some() && !self.body_built {
self.body().unwrap().build()?;
}
*self.built = true;
Ok(())
}
}
/// Builder for commitments to an HTTP response.
#[derive(Debug)]
pub struct HttpResponseCommitmentBuilder<'a> {
builder: &'a mut TranscriptCommitmentBuilder,
response: &'a Response,
body: Option<&'a Body>,
committed: RangeSet<usize>,
built: &'a mut bool,
body_built: bool,
}
impl<'a> HttpResponseCommitmentBuilder<'a> {
pub(crate) fn new(
builder: &'a mut TranscriptCommitmentBuilder,
response: &'a Response,
body: Option<&'a Body>,
built: &'a mut bool,
) -> Self {
Self {
builder,
response,
body,
committed: RangeSet::default(),
built,
body_built: false,
}
}
/// Commits the value of the header with the given name.
///
/// # Arguments
///
/// * `name` - The name of the header value to commit.
pub fn header(&mut self, name: &str) -> Result<CommitmentId, HttpCommitmentBuilderError> {
let header = self
.response
.0
.header(name)
.ok_or(HttpCommitmentBuilderError::MissingHeader(name.to_string()))?;
self.builder
.commit_recv(header.value.span().range())
.map_err(From::from)
}
/// Commits all response headers.
///
/// Returns a vector of the names of the headers that were committed and their commitment IDs.
pub fn headers(&mut self) -> Result<Vec<(String, CommitmentId)>, HttpCommitmentBuilderError> {
let mut commitments = Vec::new();
for header in &self.response.0.headers {
let name = header.name.span().as_str().to_string();
let id = self.header(&name)?;
commitments.push((name, id));
}
Ok(commitments)
}
/// Returns a commitment builder for the response body if it exists.
pub fn body(&mut self) -> Option<BodyCommitmentBuilder<'_>> {
self.body.map(|body| {
BodyCommitmentBuilder::new(
self.builder,
body,
Direction::Received,
&mut self.body_built,
)
})
}
/// Finishes building the response commitment.
///
/// This commits to everything that has not already been committed, including a commitment
/// to the format data of the response.
pub fn build(mut self) -> Result<(), HttpCommitmentBuilderError> {
// Commit to any headers that have not already been committed.
for header in &self.response.0.headers {
let name = header.name.span().as_str().to_ascii_lowercase();
// Public headers can not be committed separately
if PUBLIC_HEADERS.contains(&name.as_str()) {
continue;
}
let range = header.value.span().range();
if !range.is_subset(&self.committed) {
self.header(&name)?;
}
}
self.builder.commit_recv(self.response.public_ranges())?;
if self.body.is_some() && !self.body_built {
self.body().unwrap().build()?;
}
*self.built = true;
Ok(())
}
}

View File

@@ -1,75 +1,48 @@
//! Tooling for working with HTTP data.
mod body;
mod commitment;
mod parse;
mod proof;
mod commit;
mod session;
pub use body::{Body, BodyCommitmentBuilder, BodyProofBuilder};
pub use commitment::{
HttpCommitmentBuilder, HttpCommitmentBuilderError, HttpRequestCommitmentBuilder,
HttpResponseCommitmentBuilder,
};
pub use parse::{parse_body, parse_requests, parse_responses, ParseError};
pub use proof::{HttpProofBuilder, HttpProofBuilderError};
pub use commit::{DefaultHttpCommitter, HttpCommit, HttpCommitError};
pub use session::NotarizedHttpSession;
use serde::{Deserialize, Serialize};
use spansy::Spanned;
use utils::range::{RangeDifference, RangeSet, RangeUnion};
#[doc(hidden)]
pub use spansy::http;
static PUBLIC_HEADERS: &[&str] = &["content-length", "content-type"];
pub use http::{
parse_request, parse_response, Body, BodyContent, Header, HeaderName, HeaderValue, Method,
Reason, Request, RequestLine, Requests, Response, Responses, Status, Target,
};
use tlsn_core::Transcript;
/// An HTTP request.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Request(pub(crate) spansy::http::Request);
impl Request {
pub(crate) fn public_ranges(&self) -> RangeSet<usize> {
let mut private_ranges = RangeSet::default();
let path_range = self.0.path.range();
private_ranges = private_ranges.union(&path_range);
for header in &self.0.headers {
let name = header.name.span().as_str().to_ascii_lowercase();
let range = header.value.span().range();
if !PUBLIC_HEADERS.contains(&name.as_str()) {
private_ranges = private_ranges.union(&range);
}
}
if let Some(body) = &self.0.body {
private_ranges = private_ranges.union(&body.span().range());
}
self.0.span().range().difference(&private_ranges)
}
/// The kind of HTTP message.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum MessageKind {
/// An HTTP request.
Request,
/// An HTTP response.
Response,
}
/// An HTTP response.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Response(pub(crate) spansy::http::Response);
/// An HTTP transcript.
#[derive(Debug)]
pub struct HttpTranscript {
/// The requests sent to the server.
pub requests: Vec<Request>,
/// The responses received from the server.
pub responses: Vec<Response>,
}
impl Response {
pub(crate) fn public_ranges(&self) -> RangeSet<usize> {
let mut private_ranges = RangeSet::default();
impl HttpTranscript {
/// Parses the HTTP transcript from the provided transcripts.
pub fn parse(tx: &Transcript, rx: &Transcript) -> Result<Self, spansy::ParseError> {
let requests = Requests::new(tx.data().clone()).collect::<Result<Vec<_>, _>>()?;
let responses = Responses::new(rx.data().clone()).collect::<Result<Vec<_>, _>>()?;
for header in &self.0.headers {
let name = header.name.span().as_str().to_ascii_lowercase();
let range = header.value.span().range();
if !PUBLIC_HEADERS.contains(&name.as_str()) {
private_ranges = private_ranges.union(&range);
}
}
if let Some(body) = &self.0.body {
private_ranges = private_ranges.union(&body.span().range());
}
self.0.span().range().difference(&private_ranges)
Ok(Self {
requests,
responses,
})
}
}
@@ -77,7 +50,6 @@ impl Response {
mod tests {
use super::*;
use bytes::Bytes;
use tlsn_core::{
commitment::{CommitmentKind, TranscriptCommitmentBuilder},
fixtures,
@@ -85,6 +57,8 @@ mod tests {
Direction, Transcript,
};
use crate::json::JsonValue;
static TX: &[u8] = b"GET / HTTP/1.1\r\nHost: localhost\r\n\r\n\
POST /hello HTTP/1.1\r\nHost: localhost\r\nContent-Length: 44\r\nContent-Type: application/json\r\n\r\n\
{\"foo\": \"bar\", \"bazz\": 123, \"buzz\": [1,\"5\"]}";
@@ -96,44 +70,51 @@ mod tests {
#[test]
fn test_http_commit() {
let mut transcript_commitment_builder = TranscriptCommitmentBuilder::new(
let transcript_tx = Transcript::new(TX);
let transcript_rx = Transcript::new(RX);
let mut builder = TranscriptCommitmentBuilder::new(
fixtures::encoding_provider(TX, RX),
TX.len(),
RX.len(),
);
let requests = parse_requests(Bytes::copy_from_slice(TX)).unwrap();
let responses = parse_responses(Bytes::copy_from_slice(RX)).unwrap();
let transcript = HttpTranscript::parse(&transcript_tx, &transcript_rx).unwrap();
HttpCommitmentBuilder::new(&mut transcript_commitment_builder, &requests, &responses)
.build()
let mut committer = DefaultHttpCommitter::default();
committer
.commit_transcript(&mut builder, &transcript)
.unwrap();
let commitments = transcript_commitment_builder.build().unwrap();
let commitments = builder.build().unwrap();
// Path
assert!(commitments
.get_id_by_info(CommitmentKind::Blake3, (4..5).into(), Direction::Sent)
.get_id_by_info(CommitmentKind::Blake3, &(4..5).into(), Direction::Sent)
.is_some());
// Host
// Host header
assert!(commitments
.get_id_by_info(CommitmentKind::Blake3, (22..31).into(), Direction::Sent)
.get_id_by_info(CommitmentKind::Blake3, &(16..33).into(), Direction::Sent)
.is_some());
// foo
// foo value
assert!(commitments
.get_id_by_info(CommitmentKind::Blake3, (137..140).into(), Direction::Sent)
.get_id_by_info(CommitmentKind::Blake3, &(137..140).into(), Direction::Sent)
.is_some());
// Cookie
// Cookie header
assert!(commitments
.get_id_by_info(CommitmentKind::Blake3, (25..43).into(), Direction::Received)
.get_id_by_info(
CommitmentKind::Blake3,
&(17..45).into(),
Direction::Received
)
.is_some());
// Body
assert!(commitments
.get_id_by_info(
CommitmentKind::Blake3,
(180..194).into(),
&(180..194).into(),
Direction::Received
)
.is_some());
@@ -144,53 +125,66 @@ mod tests {
let transcript_tx = Transcript::new(TX);
let transcript_rx = Transcript::new(RX);
let mut transcript_commitment_builder = TranscriptCommitmentBuilder::new(
let mut builder = TranscriptCommitmentBuilder::new(
fixtures::encoding_provider(TX, RX),
TX.len(),
RX.len(),
);
let requests = parse_requests(Bytes::copy_from_slice(TX)).unwrap();
let responses = parse_responses(Bytes::copy_from_slice(RX)).unwrap();
let transcript = HttpTranscript::parse(&transcript_tx, &transcript_rx).unwrap();
HttpCommitmentBuilder::new(&mut transcript_commitment_builder, &requests, &responses)
.build()
let mut committer = DefaultHttpCommitter::default();
committer
.commit_transcript(&mut builder, &transcript)
.unwrap();
let commitments = transcript_commitment_builder.build().unwrap();
let commitments = builder.build().unwrap();
let spb = SubstringsProofBuilder::new(&commitments, &transcript_tx, &transcript_rx);
let mut builder = SubstringsProofBuilder::new(&commitments, &transcript_tx, &transcript_rx);
let mut builder = HttpProofBuilder::new(spb, &commitments, &requests, &responses);
let mut req_0 = builder.request(0).unwrap();
req_0.path().unwrap();
req_0.header("host").unwrap();
let mut req_1 = builder.request(1).unwrap();
req_1.path().unwrap();
let BodyProofBuilder::Json(mut json) = req_1.body().unwrap() else {
let req_0 = &transcript.requests[0];
let req_1 = &transcript.requests[1];
let BodyContent::Json(JsonValue::Object(req_1_body)) =
&req_1.body.as_ref().unwrap().content
else {
unreachable!();
};
let resp_0 = &transcript.responses[0];
let resp_1 = &transcript.responses[1];
json.path("bazz").unwrap();
builder
.reveal_sent(&req_0.without_data(), CommitmentKind::Blake3)
.unwrap()
.reveal_sent(&req_0.request.target, CommitmentKind::Blake3)
.unwrap()
.reveal_sent(
req_0.headers_with_name("host").next().unwrap(),
CommitmentKind::Blake3,
)
.unwrap();
let mut resp_0 = builder.response(0).unwrap();
builder
.reveal_sent(&req_1.without_data(), CommitmentKind::Blake3)
.unwrap()
.reveal_sent(&req_1_body.without_pairs(), CommitmentKind::Blake3)
.unwrap()
.reveal_sent(req_1_body.get("bazz").unwrap(), CommitmentKind::Blake3)
.unwrap();
resp_0.header("cookie").unwrap();
builder
.reveal_recv(&resp_0.without_data(), CommitmentKind::Blake3)
.unwrap()
.reveal_recv(
resp_0.headers_with_name("cookie").next().unwrap(),
CommitmentKind::Blake3,
)
.unwrap();
assert!(matches!(resp_0.body().unwrap(), BodyProofBuilder::Json(_)));
let mut resp_1 = builder.response(1).unwrap();
let BodyProofBuilder::Unknown(mut unknown) = resp_1.body().unwrap() else {
unreachable!();
};
unknown.all().unwrap();
builder
.reveal_recv(&resp_1.without_data(), CommitmentKind::Blake3)
.unwrap()
.reveal_recv(resp_1.body.as_ref().unwrap(), CommitmentKind::Blake3)
.unwrap();
let proof = builder.build().unwrap();

View File

@@ -1,262 +0,0 @@
use bytes::Bytes;
use spansy::{
http::{Requests, Responses},
json::{self},
Spanned,
};
use crate::{
http::{Body, Request, Response},
json::JsonBody,
unknown::UnknownSpan,
};
/// An HTTP transcript parse error
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum ParseError {
/// Failed to parse request
#[error("failed to parse request at index {index}: {reason}")]
Request {
/// The index of the request
index: usize,
/// The reason for the error
reason: String,
},
/// Failed to parse response
#[error("failed to parse response at index {index}: {reason}")]
Response {
/// The index of the response
index: usize,
/// The reason for the error
reason: String,
},
/// Failed to parse JSON body
#[error("failed to parse JSON at index {index}: {reason}")]
Json {
/// The index of the request or response
index: usize,
/// The reason for the error
reason: String,
},
}
/// Parses a body of an HTTP request or response
///
/// # Arguments
///
/// * `index` - The index of the request or response
/// * `content_type` - The content type of the body
/// * `body` - The body data
/// * `offset` - The offset of the body from the start of the transcript
///
/// # Panics
///
/// Panics if the range and body length do not match.
pub fn parse_body(
index: usize,
content_type: &[u8],
body: Bytes,
offset: usize,
) -> Result<Body, ParseError> {
if content_type.get(..16) == Some(b"application/json".as_slice()) {
let mut body = json::parse(body).map_err(|e| ParseError::Json {
index,
reason: e.to_string(),
})?;
body.offset(offset);
Ok(Body::Json(JsonBody(body)))
} else {
Ok(Body::Unknown(UnknownSpan::new(offset..offset + body.len())))
}
}
/// Parses the requests of an HTTP transcript.
///
/// # Arguments
///
/// * `data` - The HTTP transcript data
pub fn parse_requests(data: Bytes) -> Result<Vec<(Request, Option<Body>)>, ParseError> {
let mut requests = Vec::new();
for (index, request) in Requests::new(data.clone()).enumerate() {
let request = request.map_err(|e| ParseError::Request {
index,
reason: e.to_string(),
})?;
let body = if let Some(ref body) = request.body {
let range = body.span().range();
let body = data.slice(range.clone());
let body = if let Some(content_type) = request.header("content-type") {
parse_body(
index,
content_type.value.span().as_bytes(),
body,
range.start,
)?
} else {
Body::Unknown(UnknownSpan::new(range))
};
Some(body)
} else {
None
};
requests.push((Request(request), body));
}
Ok(requests)
}
/// Parses the responses of an HTTP transcript.
///
/// # Arguments
///
/// * `data` - The HTTP transcript data
pub fn parse_responses(data: Bytes) -> Result<Vec<(Response, Option<Body>)>, ParseError> {
let mut responses = Vec::new();
for (index, response) in Responses::new(data.clone()).enumerate() {
let response = response.map_err(|e| ParseError::Response {
index,
reason: e.to_string(),
})?;
let body = if let Some(ref body) = response.body {
let range = body.span().range();
let body = data.slice(range.clone());
let body = if let Some(content_type) = response.header("content-type") {
parse_body(
index,
content_type.value.span().as_bytes(),
body,
range.start,
)?
} else {
Body::Unknown(UnknownSpan::new(range))
};
Some(body)
} else {
None
};
responses.push((Response(response), body));
}
Ok(responses)
}
#[cfg(test)]
mod tests {
use super::*;
use bytes::Bytes;
#[test]
fn test_parse_body_json() {
let body = b"{\"foo\": \"bar\"}";
let body = parse_body(0, b"application/json", Bytes::copy_from_slice(body), 0).unwrap();
let Body::Json(body) = body else {
unreachable!();
};
let range = body.0.span().range();
assert_eq!(range.start, 0);
assert_eq!(range.end, 14);
assert_eq!(body.0.span().as_str(), "{\"foo\": \"bar\"}");
let foo = body.0.get("foo").unwrap();
let range = foo.span().range();
assert_eq!(range.start, 9);
assert_eq!(range.end, 12);
assert_eq!(foo.span().as_str(), "bar");
}
#[test]
fn test_parse_body_json_offset() {
let body = b" {\"foo\": \"bar\"}";
let body = parse_body(
0,
b"application/json",
Bytes::copy_from_slice(&body[4..]),
4,
)
.unwrap();
let Body::Json(body) = body else {
unreachable!();
};
let range = body.0.span().range();
assert_eq!(range.start, 4);
assert_eq!(range.end, 18);
assert_eq!(body.0.span().as_str(), "{\"foo\": \"bar\"}");
let foo = body.0.get("foo").unwrap();
let range = foo.span().range();
assert_eq!(range.start, 13);
assert_eq!(range.end, 16);
assert_eq!(foo.span().as_str(), "bar");
}
#[test]
fn test_parse_body_unknown() {
let body = b"foo";
let body = parse_body(0, b"text/plain", Bytes::copy_from_slice(body), 0).unwrap();
assert!(matches!(body, Body::Unknown(_)));
}
#[test]
fn test_parse_requests() {
let reqs = b"GET / HTTP/1.1\r\nHost: localhost\r\n\r\n\
POST /hello HTTP/1.1\r\nHost: localhost\r\nContent-Length: 14\r\nContent-Type: application/json\r\n\r\n\
{\"foo\": \"bar\"}";
let requests = parse_requests(Bytes::copy_from_slice(reqs)).unwrap();
assert_eq!(requests.len(), 2);
assert!(requests[0].1.is_none());
assert!(requests[1].1.is_some());
let Body::Json(body) = requests[1].1.as_ref().unwrap() else {
unreachable!();
};
let foo = body.0.get("foo").unwrap();
let range = foo.span().range();
assert_eq!(range.start, 137);
assert_eq!(range.end, 140);
}
#[test]
fn test_parse_responses() {
let resps =
b"HTTP/1.1 200 OK\r\nContent-Length: 14\r\nContent-Type: application/json\r\n\r\n\
{\"foo\": \"bar\"}\r\n\
HTTP/1.1 200 OK\r\nContent-Length: 14\r\nContent-Type: text/plain\r\n\r\n\
Hello World!!!";
let responses = parse_responses(Bytes::copy_from_slice(resps)).unwrap();
assert_eq!(responses.len(), 2);
assert!(responses[0].1.is_some());
assert!(responses[1].1.is_some());
assert!(matches!(responses[0].1.as_ref().unwrap(), Body::Json(_)));
assert!(matches!(responses[1].1.as_ref().unwrap(), Body::Unknown(_)));
}
}

View File

@@ -1,315 +0,0 @@
use std::ops::Range;
use crate::http::{body::BodyProofBuilder, Body, Request, Response};
use spansy::Spanned;
use tlsn_core::{
commitment::{CommitmentId, CommitmentKind, TranscriptCommitments},
proof::{SubstringsProof, SubstringsProofBuilder, SubstringsProofBuilderError},
Direction,
};
/// HTTP proof builder error.
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum HttpProofBuilderError {
/// Header is missing.
#[error("header with name \"{0}\" does not exist.")]
MissingHeader(String),
/// Body proof error.
#[error("body proof error: {0}")]
Body(String),
/// Missing commitment for value.
#[error("missing commitment for {0}")]
MissingCommitment(String),
/// Substrings proof builder error.
#[error("proof builder error: {0}")]
Proof(#[from] SubstringsProofBuilderError),
}
/// Builder for proofs of data in an HTTP connection.
#[derive(Debug)]
pub struct HttpProofBuilder<'a, 'b> {
builder: SubstringsProofBuilder<'b>,
commitments: &'a TranscriptCommitments,
requests: &'a [(Request, Option<Body>)],
responses: &'a [(Response, Option<Body>)],
built_requests: Vec<bool>,
built_responses: Vec<bool>,
}
impl<'a, 'b> HttpProofBuilder<'a, 'b> {
#[doc(hidden)]
pub fn new(
builder: SubstringsProofBuilder<'b>,
commitments: &'a TranscriptCommitments,
requests: &'a [(Request, Option<Body>)],
responses: &'a [(Response, Option<Body>)],
) -> Self {
Self {
builder,
commitments,
requests,
responses,
built_requests: vec![false; requests.len()],
built_responses: vec![false; responses.len()],
}
}
/// Returns a proof builder for the given request, if it exists.
///
/// # Arguments
///
/// * `index` - The index of the request to build a proof for.
pub fn request<'c>(&'c mut self, index: usize) -> Option<HttpRequestProofBuilder<'c, 'b>>
where
'a: 'c,
{
self.requests
.get(index)
.map(|request| HttpRequestProofBuilder {
builder: &mut self.builder,
commitments: self.commitments,
request: &request.0,
body: request.1.as_ref(),
built: &mut self.built_requests[index],
body_built: false,
})
}
/// Returns a proof builder for the given response, if it exists.
///
/// # Arguments
///
/// * `index` - The index of the response to build a proof for.
pub fn response<'c>(&'c mut self, index: usize) -> Option<HttpResponseProofBuilder<'c, 'b>>
where
'a: 'c,
{
self.responses
.get(index)
.map(|response| HttpResponseProofBuilder {
builder: &mut self.builder,
commitments: self.commitments,
response: &response.0,
body: response.1.as_ref(),
built: &mut self.built_responses[index],
body_built: false,
})
}
/// Builds the HTTP transcript proof.
pub fn build(mut self) -> Result<SubstringsProof, HttpProofBuilderError> {
// Build any remaining request proofs
for i in 0..self.requests.len() {
if !self.built_requests[i] {
self.request(i).unwrap().build()?;
}
}
// Build any remaining response proofs
for i in 0..self.responses.len() {
if !self.built_responses[i] {
self.response(i).unwrap().build()?;
}
}
self.builder.build().map_err(From::from)
}
}
#[derive(Debug)]
pub struct HttpRequestProofBuilder<'a, 'b> {
builder: &'a mut SubstringsProofBuilder<'b>,
commitments: &'a TranscriptCommitments,
request: &'a Request,
body: Option<&'a Body>,
built: &'a mut bool,
// TODO: this field will be used in the future to support advanced configurations
// but for now we don't want to build the body proof unless it is specifically requested
body_built: bool,
}
impl<'a, 'b> HttpRequestProofBuilder<'a, 'b> {
/// Reveals the entirety of the request.
///
/// # Arguments
///
/// * `body` - Whether to reveal the entirety of the request body as well.
pub fn all(&mut self, body: bool) -> Result<&mut Self, HttpProofBuilderError> {
let id = self
.commit_id(self.request.0.span().range())
.ok_or_else(|| {
HttpProofBuilderError::MissingCommitment("the entire request".to_string())
})?;
self.builder.reveal(id)?;
if body && self.body.is_some() {
self.body().unwrap().all()?;
}
Ok(self)
}
/// Reveals the path of the request.
pub fn path(&mut self) -> Result<&mut Self, HttpProofBuilderError> {
let id = self
.commit_id(self.request.0.path.range())
.ok_or_else(|| HttpProofBuilderError::MissingCommitment("path".to_string()))?;
self.builder.reveal(id)?;
Ok(self)
}
/// Reveals the value of the given header.
///
/// # Arguments
///
/// * `name` - The name of the header value to reveal.
pub fn header(&mut self, name: &str) -> Result<&mut Self, HttpProofBuilderError> {
let header = self
.request
.0
.header(name)
.ok_or_else(|| HttpProofBuilderError::MissingHeader(name.to_string()))?;
let id = self.commit_id(header.value.span().range()).ok_or_else(|| {
HttpProofBuilderError::MissingCommitment(format!("header \"{}\"", name))
})?;
self.builder.reveal(id)?;
Ok(self)
}
/// Returns a proof builder for the request body, if it exists.
pub fn body<'c>(&'c mut self) -> Option<BodyProofBuilder<'c, 'b>> {
self.body.map(|body| {
BodyProofBuilder::new(
self.builder,
self.commitments,
body,
Direction::Sent,
&mut self.body_built,
)
})
}
/// Builds the HTTP request proof.
pub fn build(self) -> Result<(), HttpProofBuilderError> {
let public_id = self
.commitments
.get_id_by_info(
CommitmentKind::Blake3,
self.request.public_ranges(),
Direction::Sent,
)
.ok_or_else(|| HttpProofBuilderError::MissingCommitment("public data".to_string()))?;
self.builder.reveal(public_id)?;
*self.built = true;
Ok(())
}
fn commit_id(&self, range: Range<usize>) -> Option<CommitmentId> {
// TODO: support different kinds of commitments
self.commitments
.get_id_by_info(CommitmentKind::Blake3, range.into(), Direction::Sent)
}
}
#[derive(Debug)]
pub struct HttpResponseProofBuilder<'a, 'b: 'a> {
builder: &'a mut SubstringsProofBuilder<'b>,
commitments: &'a TranscriptCommitments,
response: &'a Response,
body: Option<&'a Body>,
built: &'a mut bool,
// TODO: this field will be used in the future to support advanced configurations
// but for now we don't want to build the body proof unless it is specifically requested
body_built: bool,
}
impl<'a, 'b> HttpResponseProofBuilder<'a, 'b> {
/// Reveals the entirety of the response.
///
/// # Arguments
///
/// * `body` - Whether to reveal the entirety of the response body as well.
pub fn all(&mut self, body: bool) -> Result<&mut Self, HttpProofBuilderError> {
let id = self
.commit_id(self.response.0.span().range())
.ok_or_else(|| {
HttpProofBuilderError::MissingCommitment("the entire response".to_string())
})?;
self.builder.reveal(id)?;
if body && self.body.is_some() {
self.body().unwrap().all()?;
}
Ok(self)
}
/// Reveals the value of the given header.
///
/// # Arguments
///
/// * `name` - The name of the header value to reveal.
pub fn header(&mut self, name: &str) -> Result<&mut Self, HttpProofBuilderError> {
let header = self
.response
.0
.header(name)
.ok_or_else(|| HttpProofBuilderError::MissingHeader(name.to_string()))?;
let id = self.commit_id(header.value.span().range()).ok_or_else(|| {
HttpProofBuilderError::MissingCommitment(format!("header \"{}\"", name))
})?;
self.builder.reveal(id)?;
Ok(self)
}
/// Returns a proof builder for the response body, if it exists.
pub fn body<'c>(&'c mut self) -> Option<BodyProofBuilder<'c, 'b>> {
self.body.map(|body| {
BodyProofBuilder::new(
self.builder,
self.commitments,
body,
Direction::Received,
&mut self.body_built,
)
})
}
/// Builds the HTTP response proof.
pub fn build(self) -> Result<(), HttpProofBuilderError> {
let public_id = self
.commitments
.get_id_by_info(
CommitmentKind::Blake3,
self.response.public_ranges(),
Direction::Received,
)
.ok_or_else(|| HttpProofBuilderError::MissingCommitment("public data".to_string()))?;
self.builder.reveal(public_id)?;
*self.built = true;
Ok(())
}
fn commit_id(&self, range: Range<usize>) -> Option<CommitmentId> {
// TODO: support different kinds of commitments
self.commitments
.get_id_by_info(CommitmentKind::Blake3, range.into(), Direction::Received)
}
}

View File

@@ -1,31 +1,21 @@
use serde::{Deserialize, Serialize};
use tlsn_core::{proof::SessionProof, NotarizedSession};
use crate::http::{Body, Request, Response};
use super::HttpProofBuilder;
use crate::http::HttpTranscript;
/// A notarized HTTP session.
#[derive(Debug, Serialize, Deserialize)]
#[derive(Debug)]
pub struct NotarizedHttpSession {
session: NotarizedSession,
requests: Vec<(Request, Option<Body>)>,
responses: Vec<(Response, Option<Body>)>,
transcript: HttpTranscript,
}
impl NotarizedHttpSession {
/// Creates a new notarized HTTP session.
#[doc(hidden)]
pub fn new(
session: NotarizedSession,
requests: Vec<(Request, Option<Body>)>,
responses: Vec<(Response, Option<Body>)>,
) -> Self {
pub fn new(session: NotarizedSession, transcript: HttpTranscript) -> Self {
Self {
session,
requests,
responses,
transcript,
}
}
@@ -34,18 +24,13 @@ impl NotarizedHttpSession {
&self.session
}
/// Returns the HTTP transcript.
pub fn transcript(&self) -> &HttpTranscript {
&self.transcript
}
/// Returns a proof for the TLS session.
pub fn session_proof(&self) -> SessionProof {
self.session.session_proof()
}
/// Returns a proof builder for the HTTP session.
pub fn proof_builder(&self) -> HttpProofBuilder {
HttpProofBuilder::new(
self.session.data().build_substrings_proof(),
self.session.data().commitments(),
&self.requests,
&self.responses,
)
}
}

View File

@@ -0,0 +1,250 @@
use std::error::Error;
use spansy::{json::KeyValue, Spanned};
use tlsn_core::{commitment::TranscriptCommitmentBuilder, Direction};
use crate::json::{Array, Bool, JsonValue, Null, Number, Object, String as JsonString};
/// JSON commitment error.
#[derive(Debug, thiserror::Error)]
#[error("json commitment error: {msg}")]
pub struct JsonCommitError {
msg: String,
#[source]
source: Option<Box<dyn Error + Send + Sync>>,
}
impl JsonCommitError {
/// Creates a new JSON commitment error.
///
/// # Arguments
///
/// * `msg` - The error message.
pub fn new(msg: impl Into<String>) -> Self {
Self {
msg: msg.into(),
source: None,
}
}
/// Creates a new JSON commitment error with a source.
///
/// # Arguments
///
/// * `msg` - The error message.
/// * `source` - The source error.
pub fn new_with_source<E>(msg: impl Into<String>, source: E) -> Self
where
E: Into<Box<dyn Error + Send + Sync>>,
{
Self {
msg: msg.into(),
source: Some(source.into()),
}
}
/// Returns the error message.
pub fn msg(&self) -> &str {
&self.msg
}
}
/// A JSON committer.
pub trait JsonCommit {
/// Commits to a JSON value.
///
/// # Arguments
///
/// * `builder` - The commitment builder.
/// * `value` - The JSON value to commit.
/// * `direction` - The direction of the data (sent or received).
fn commit_value(
&mut self,
builder: &mut TranscriptCommitmentBuilder,
value: &JsonValue,
direction: Direction,
) -> Result<(), JsonCommitError> {
match value {
JsonValue::Object(obj) => self.commit_object(builder, obj, direction),
JsonValue::Array(arr) => self.commit_array(builder, arr, direction),
JsonValue::String(string) => self.commit_string(builder, string, direction),
JsonValue::Number(number) => self.commit_number(builder, number, direction),
JsonValue::Bool(boolean) => self.commit_bool(builder, boolean, direction),
JsonValue::Null(null) => self.commit_null(builder, null, direction),
}
}
/// Commits to a JSON object.
///
/// The default implementation commits the object without any of the key-value pairs, then
/// commits each key-value pair individually.
///
/// # Arguments
///
/// * `builder` - The commitment builder.
/// * `object` - The JSON object to commit.
/// * `direction` - The direction of the data (sent or received).
fn commit_object(
&mut self,
builder: &mut TranscriptCommitmentBuilder,
object: &Object,
direction: Direction,
) -> Result<(), JsonCommitError> {
builder
.commit(&object.without_pairs(), direction)
.map_err(|e| JsonCommitError::new_with_source("failed to commit object", e))?;
for kv in &object.elems {
self.commit_key_value(builder, kv, direction)?;
}
Ok(())
}
/// Commits to a JSON key-value pair.
///
/// The default implementation commits the pair without the value, and then commits the value
/// separately.
///
/// # Arguments
///
/// * `builder` - The commitment builder.
/// * `kv` - The JSON key-value pair to commit.
/// * `direction` - The direction of the data (sent or received).
fn commit_key_value(
&mut self,
builder: &mut TranscriptCommitmentBuilder,
kv: &KeyValue,
direction: Direction,
) -> Result<(), JsonCommitError> {
builder
.commit(&kv.without_value(), direction)
.map_err(|e| {
JsonCommitError::new_with_source(
"failed to commit key-value pair excluding the value",
e,
)
})?;
self.commit_value(builder, &kv.value, direction)
}
/// Commits to a JSON array.
///
/// The default implementation commits to the entire array, then commits the array
/// excluding all values and separators.
///
/// # Arguments
///
/// * `builder` - The commitment builder.
/// * `array` - The JSON array to commit.
/// * `direction` - The direction of the data (sent or received).
fn commit_array(
&mut self,
builder: &mut TranscriptCommitmentBuilder,
array: &Array,
direction: Direction,
) -> Result<(), JsonCommitError> {
builder
.commit(array, direction)
.map_err(|e| JsonCommitError::new_with_source("failed to commit array", e))?;
builder
.commit(&array.without_values(), direction)
.map_err(|e| {
JsonCommitError::new_with_source("failed to commit array excluding values", e)
})?;
// TODO: Commit each value separately, but we need a strategy for handling
// separators.
Ok(())
}
/// Commits to a JSON string.
///
/// # Arguments
///
/// * `builder` - The commitment builder.
/// * `string` - The JSON string to commit.
/// * `direction` - The direction of the data (sent or received).
fn commit_string(
&mut self,
builder: &mut TranscriptCommitmentBuilder,
string: &JsonString,
direction: Direction,
) -> Result<(), JsonCommitError> {
// Skip empty strings.
if string.span().is_empty() {
return Ok(());
}
builder
.commit(string, direction)
.map(|_| ())
.map_err(|e| JsonCommitError::new_with_source("failed to commit string", e))
}
/// Commits to a JSON number.
///
/// # Arguments
///
/// * `builder` - The commitment builder.
/// * `number` - The JSON number to commit.
/// * `direction` - The direction of the data (sent or received).
fn commit_number(
&mut self,
builder: &mut TranscriptCommitmentBuilder,
number: &Number,
direction: Direction,
) -> Result<(), JsonCommitError> {
builder
.commit(number, direction)
.map(|_| ())
.map_err(|e| JsonCommitError::new_with_source("failed to commit number", e))
}
/// Commits to a JSON boolean value.
///
/// # Arguments
///
/// * `builder` - The commitment builder.
/// * `boolean` - The JSON boolean to commit.
/// * `direction` - The direction of the data (sent or received).
fn commit_bool(
&mut self,
builder: &mut TranscriptCommitmentBuilder,
boolean: &Bool,
direction: Direction,
) -> Result<(), JsonCommitError> {
builder
.commit(boolean, direction)
.map(|_| ())
.map_err(|e| JsonCommitError::new_with_source("failed to commit boolean", e))
}
/// Commits to a JSON null value.
///
/// # Arguments
///
/// * `builder` - The commitment builder.
/// * `null` - The JSON null to commit.
/// * `direction` - The direction of the data (sent or received).
fn commit_null(
&mut self,
builder: &mut TranscriptCommitmentBuilder,
null: &Null,
direction: Direction,
) -> Result<(), JsonCommitError> {
builder
.commit(null, direction)
.map(|_| ())
.map_err(|e| JsonCommitError::new_with_source("failed to commit null", e))
}
}
/// Default committer for JSON values.
#[derive(Debug, Default, Clone)]
pub struct DefaultJsonCommitter {}
impl JsonCommit for DefaultJsonCommitter {}

View File

@@ -1,157 +0,0 @@
use spansy::{
json::{JsonValue, JsonVisit},
Spanned,
};
use tlsn_core::{
commitment::{
CommitmentId, CommitmentKind, TranscriptCommitmentBuilder, TranscriptCommitmentBuilderError,
},
Direction,
};
use super::public_ranges;
/// JSON commitment builder error.
#[derive(Debug, thiserror::Error)]
pub enum JsonCommitmentBuilderError {
/// Invalid path.
#[error("invalid path: {0}")]
InvalidPath(String),
/// Transcript commitment builder error.
#[error("commitment builder error: {0}")]
Commitment(#[from] TranscriptCommitmentBuilderError),
}
/// Builder for commitments to a JSON value.
#[derive(Debug)]
pub struct JsonCommitmentBuilder<'a> {
builder: &'a mut TranscriptCommitmentBuilder,
value: &'a JsonValue,
direction: Direction,
built: &'a mut bool,
}
impl<'a> JsonCommitmentBuilder<'a> {
pub(crate) fn new(
builder: &'a mut TranscriptCommitmentBuilder,
value: &'a JsonValue,
direction: Direction,
built: &'a mut bool,
) -> Self {
JsonCommitmentBuilder {
builder,
value,
direction,
built,
}
}
/// Commits to the entire JSON value.
pub fn all(&mut self) -> Result<CommitmentId, JsonCommitmentBuilderError> {
match self.direction {
Direction::Sent => self.builder.commit_sent(self.value.span().range()),
Direction::Received => self.builder.commit_recv(self.value.span().range()),
}
.map_err(From::from)
}
/// Commits to the value at the given path.
pub fn path(&mut self, path: &str) -> Result<CommitmentId, JsonCommitmentBuilderError> {
let value = self.value.get(path).ok_or_else(|| {
JsonCommitmentBuilderError::InvalidPath(format!("invalid path: {}", path))
})?;
let range = value.span().range();
match self.direction {
Direction::Sent => self.builder.commit_sent(range),
Direction::Received => self.builder.commit_recv(range),
}
.map_err(From::from)
}
/// Finishes building commitments the a JSON value.
pub fn build(self) -> Result<(), JsonCommitmentBuilderError> {
let public_ranges = public_ranges(self.value);
match self.direction {
Direction::Sent => self.builder.commit_sent(public_ranges)?,
Direction::Received => self.builder.commit_recv(public_ranges)?,
};
let mut visitor = JsonCommitter {
builder: self.builder,
direction: self.direction,
err: None,
};
visitor.visit_value(self.value);
if let Some(err) = visitor.err {
err?
}
*self.built = true;
Ok(())
}
}
struct JsonCommitter<'a> {
builder: &'a mut TranscriptCommitmentBuilder,
direction: Direction,
err: Option<Result<(), JsonCommitmentBuilderError>>,
}
impl<'a> JsonVisit for JsonCommitter<'a> {
fn visit_number(&mut self, node: &spansy::json::Number) {
if self.err.is_some() {
return;
}
let range = node.span().range();
if self
.builder
.get_id(CommitmentKind::Blake3, range.clone(), self.direction)
.is_some()
{
return;
}
let res = match self.direction {
Direction::Sent => self.builder.commit_sent(range),
Direction::Received => self.builder.commit_recv(range),
}
.map(|_| ())
.map_err(From::from);
if res.is_err() {
self.err = Some(res);
}
}
fn visit_string(&mut self, node: &spansy::json::String) {
if self.err.is_some() {
return;
}
let range = node.span().range();
if self
.builder
.get_id(CommitmentKind::Blake3, range.clone(), self.direction)
.is_some()
{
return;
}
let res = match self.direction {
Direction::Sent => self.builder.commit_sent(range),
Direction::Received => self.builder.commit_recv(range),
}
.map(|_| ())
.map_err(From::from);
if res.is_err() {
self.err = Some(res);
}
}
}

View File

@@ -1,44 +1,10 @@
//! Tooling for working with JSON data.
mod commitment;
mod proof;
mod commit;
pub use commitment::{JsonCommitmentBuilder, JsonCommitmentBuilderError};
pub use proof::{JsonProofBuilder, JsonProofBuilderError};
use spansy::json;
use serde::{Deserialize, Serialize};
use spansy::{
json::{JsonValue, JsonVisit},
Spanned,
pub use commit::{DefaultJsonCommitter, JsonCommit, JsonCommitError};
pub use json::{
Array, Bool, JsonKey, JsonValue, JsonVisit, KeyValue, Null, Number, Object, String,
};
use utils::range::{RangeDifference, RangeSet, RangeUnion};
/// A JSON body
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct JsonBody(pub(crate) JsonValue);
/// Computes all the public ranges of a JSON value.
///
/// Right now this is just the ranges of all the numbers and strings.
pub(crate) fn public_ranges(value: &JsonValue) -> RangeSet<usize> {
#[derive(Default)]
struct PrivateRanges {
private_ranges: RangeSet<usize>,
}
// For now only numbers and strings are redactable.
impl JsonVisit for PrivateRanges {
fn visit_number(&mut self, node: &spansy::json::Number) {
self.private_ranges = self.private_ranges.union(&node.span().range());
}
fn visit_string(&mut self, node: &spansy::json::String) {
self.private_ranges = self.private_ranges.union(&node.span().range());
}
}
let mut visitor = PrivateRanges::default();
visitor.visit_value(value);
value.span().range().difference(&visitor.private_ranges)
}

View File

@@ -1,105 +0,0 @@
use std::ops::Range;
use spansy::{json::JsonValue, Spanned};
use tlsn_core::{
commitment::{CommitmentId, CommitmentKind, TranscriptCommitments},
proof::{SubstringsProofBuilder, SubstringsProofBuilderError},
Direction,
};
use crate::json::public_ranges;
/// JSON proof builder error.
#[derive(Debug, thiserror::Error)]
pub enum JsonProofBuilderError {
/// Missing value
#[error("missing value at path: {0}")]
MissingValue(String),
/// Missing commitment.
#[error("missing commitment")]
MissingCommitment,
/// Substrings proof builder error.
#[error("proof builder error: {0}")]
Proof(#[from] SubstringsProofBuilderError),
}
/// Builder for proofs of a JSON value.
#[derive(Debug)]
pub struct JsonProofBuilder<'a, 'b> {
builder: &'a mut SubstringsProofBuilder<'b>,
commitments: &'a TranscriptCommitments,
value: &'a JsonValue,
direction: Direction,
built: &'a mut bool,
}
impl<'a, 'b> JsonProofBuilder<'a, 'b> {
pub(crate) fn new(
builder: &'a mut SubstringsProofBuilder<'b>,
commitments: &'a TranscriptCommitments,
value: &'a JsonValue,
direction: Direction,
built: &'a mut bool,
) -> Self {
JsonProofBuilder {
builder,
commitments,
value,
direction,
built,
}
}
/// Proves the entire JSON value.
pub fn all(&mut self) -> Result<(), JsonProofBuilderError> {
let id = self
.commit_id(self.value.span().range())
.ok_or(JsonProofBuilderError::MissingCommitment)?;
self.builder.reveal(id)?;
Ok(())
}
/// Proves the value at the given path.
///
/// # Arguments
///
/// * `path` - The path to the value to prove.
pub fn path(&mut self, path: &str) -> Result<(), JsonProofBuilderError> {
let value = self
.value
.get(path)
.ok_or_else(|| JsonProofBuilderError::MissingValue(format!("\"{}\"", path)))?;
let id = self
.commit_id(value.span().range())
.ok_or(JsonProofBuilderError::MissingCommitment)?;
self.builder.reveal(id)?;
Ok(())
}
/// Finishes building the JSON proof.
pub fn build(self) -> Result<(), JsonProofBuilderError> {
let public_ranges = public_ranges(self.value);
let public_id = self
.commitments
.get_id_by_info(CommitmentKind::Blake3, public_ranges, self.direction)
.ok_or(JsonProofBuilderError::MissingCommitment)?;
self.builder.reveal(public_id)?;
*self.built = true;
Ok(())
}
fn commit_id(&self, range: Range<usize>) -> Option<CommitmentId> {
// TODO: support different kinds of commitments
self.commitments
.get_id_by_info(CommitmentKind::Blake3, range.into(), self.direction)
}
}

View File

@@ -13,4 +13,7 @@
pub mod http;
pub mod json;
mod unknown;
#[doc(hidden)]
pub use spansy;
pub use spansy::ParseError;

View File

@@ -1,190 +0,0 @@
use std::ops::Range;
use serde::{Deserialize, Serialize};
use tlsn_core::{
commitment::{
CommitmentId, CommitmentKind, TranscriptCommitmentBuilder,
TranscriptCommitmentBuilderError, TranscriptCommitments,
},
proof::{SubstringsProofBuilder, SubstringsProofBuilderError},
Direction,
};
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum UnknownCommitmentBuilderError {
/// The provided range is out of bounds of the span.
#[error("provided range is out of bounds of the span")]
OutOfBounds,
#[error("commitment builder error: {0}")]
Commitment(#[from] TranscriptCommitmentBuilderError),
}
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum UnknownProofBuilderError {
/// Missing commitment.
#[error("missing commitment")]
MissingCommitment,
/// The provided range is out of bounds of the span.
#[error("provided range is out of bounds of the span")]
OutOfBounds,
/// Substrings proof builder error.
#[error("proof builder error: {0}")]
Proof(#[from] SubstringsProofBuilderError),
}
/// A span within the transcript with an unknown format.
#[derive(Debug, Serialize, Deserialize)]
pub struct UnknownSpan(pub(crate) Range<usize>);
impl UnknownSpan {
pub(crate) fn new(span: Range<usize>) -> Self {
UnknownSpan(span)
}
}
/// A builder for commitments to spans with an unknown format.
#[derive(Debug)]
pub struct UnknownCommitmentBuilder<'a> {
builder: &'a mut TranscriptCommitmentBuilder,
span: Range<usize>,
direction: Direction,
built: &'a mut bool,
}
impl<'a> UnknownCommitmentBuilder<'a> {
pub(crate) fn new(
builder: &'a mut TranscriptCommitmentBuilder,
span: &'a UnknownSpan,
direction: Direction,
built: &'a mut bool,
) -> Self {
UnknownCommitmentBuilder {
builder,
span: span.0.clone(),
direction,
built,
}
}
/// Commits to the given range within the span.
pub fn range(
&mut self,
range: Range<usize>,
) -> Result<CommitmentId, UnknownCommitmentBuilderError> {
let span_range = self.span.clone();
let start = span_range.start + range.start;
let end = span_range.start + range.end;
if start >= end || end > span_range.end {
return Err(UnknownCommitmentBuilderError::OutOfBounds);
}
match self.direction {
Direction::Sent => self.builder.commit_sent(start..end),
Direction::Received => self.builder.commit_recv(start..end),
}
.map_err(From::from)
}
/// Commits to the entire body.
pub fn all(&mut self) -> Result<CommitmentId, UnknownCommitmentBuilderError> {
match self.direction {
Direction::Sent => self.builder.commit_sent(self.span.clone()),
Direction::Received => self.builder.commit_recv(self.span.clone()),
}
.map_err(From::from)
}
/// Builds the commitment.
pub fn build(self) -> Result<(), UnknownCommitmentBuilderError> {
// commit to the entire span
match self.direction {
Direction::Sent => self.builder.commit_sent(self.span.clone()),
Direction::Received => self.builder.commit_recv(self.span.clone()),
}?;
*self.built = true;
Ok(())
}
}
/// A proof builder for spans with an unknown format.
#[derive(Debug)]
pub struct UnknownProofBuilder<'a, 'b> {
builder: &'a mut SubstringsProofBuilder<'b>,
commitments: &'a TranscriptCommitments,
span: Range<usize>,
direction: Direction,
built: &'a mut bool,
}
impl<'a, 'b> UnknownProofBuilder<'a, 'b> {
pub(crate) fn new(
builder: &'a mut SubstringsProofBuilder<'b>,
commitments: &'a TranscriptCommitments,
span: &'a UnknownSpan,
direction: Direction,
built: &'a mut bool,
) -> Self {
UnknownProofBuilder {
builder,
commitments,
span: span.0.clone(),
direction,
built,
}
}
/// Reveals the entire span.
pub fn all(&mut self) -> Result<(), UnknownProofBuilderError> {
let id = self
.commit_id(self.span.clone())
.ok_or(UnknownProofBuilderError::MissingCommitment)?;
self.builder.reveal(id)?;
Ok(())
}
/// Reveals the given range within the span.
///
/// # Arguments
///
/// * `range` - The range to reveal.
pub fn range(&mut self, range: Range<usize>) -> Result<(), UnknownProofBuilderError> {
let span_range = self.span.clone();
let start = span_range.start + range.start;
let end = span_range.start + range.end;
if start >= end || end > span_range.end {
return Err(UnknownProofBuilderError::OutOfBounds);
}
let id = self
.commit_id(start..end)
.ok_or(UnknownProofBuilderError::MissingCommitment)?;
self.builder.reveal(id)?;
Ok(())
}
/// Builds the proof.
pub fn build(self) -> Result<(), UnknownProofBuilderError> {
*self.built = true;
Ok(())
}
fn commit_id(&self, range: Range<usize>) -> Option<CommitmentId> {
// TODO: support different kinds of commitments
self.commitments
.get_id_by_info(CommitmentKind::Blake3, range.into(), self.direction)
}
}

View File

@@ -9,8 +9,8 @@ version = "0.1.0-alpha.3"
edition = "2021"
[features]
default = []
#formats = ["dep:tlsn-formats"]
default = ["formats"]
formats = ["dep:tlsn-formats"]
tracing = [
"dep:tracing",
"tlsn-tls-client-async/tracing",
@@ -24,7 +24,7 @@ tlsn-tls-client.workspace = true
tlsn-tls-client-async.workspace = true
tlsn-core.workspace = true
tlsn-common.workspace = true
#tlsn-formats = { workspace = true, optional = true }
tlsn-formats = { workspace = true, optional = true }
tlsn-tls-mpc.workspace = true
tlsn-utils.workspace = true

View File

@@ -7,20 +7,14 @@
pub mod state;
use tlsn_formats::http::{parse_requests, parse_responses, ParseError};
use tlsn_formats::{
http::{DefaultHttpCommitter, HttpCommit, HttpCommitError, HttpTranscript},
ParseError,
};
use crate::tls::{state as prover_state, Prover, ProverError};
pub use tlsn_formats::{
http::{
HttpCommitmentBuilder, HttpCommitmentBuilderError, HttpProofBuilder, HttpProofBuilderError,
HttpRequestCommitmentBuilder, HttpResponseCommitmentBuilder, NotarizedHttpSession,
},
json::{
JsonBody, JsonCommitmentBuilder, JsonCommitmentBuilderError, JsonProofBuilder,
JsonProofBuilderError,
},
};
pub use tlsn_formats::http::NotarizedHttpSession;
/// HTTP prover error.
#[derive(Debug, thiserror::Error)]
@@ -28,9 +22,6 @@ pub enum HttpProverError {
/// An error originated from the TLS prover.
#[error(transparent)]
Prover(#[from] ProverError),
/// Commitment error.
#[error(transparent)]
Commitment(#[from] HttpCommitmentBuilderError),
/// An error occurred while parsing the HTTP data.
#[error(transparent)]
Parse(#[from] ParseError),
@@ -44,15 +35,10 @@ pub struct HttpProver<S: state::State> {
impl HttpProver<state::Closed> {
/// Creates a new HTTP prover.
pub fn new(prover: Prover<prover_state::Closed>) -> Result<Self, HttpProverError> {
let requests = parse_requests(prover.sent_transcript().data().clone())?;
let responses = parse_responses(prover.recv_transcript().data().clone())?;
let transcript = HttpTranscript::parse(prover.sent_transcript(), prover.recv_transcript())?;
Ok(Self {
state: state::Closed {
prover,
requests,
responses,
},
state: state::Closed { prover, transcript },
})
}
@@ -64,29 +50,26 @@ impl HttpProver<state::Closed> {
HttpProver {
state: state::Notarize {
prover: self.state.prover.start_notarize(),
requests: self.state.requests,
responses: self.state.responses,
transcript: self.state.transcript,
},
}
}
}
impl HttpProver<state::Notarize> {
/// Generates commitments to the HTTP session prior to finalization.
pub fn commit(&mut self) -> Result<(), HttpProverError> {
self.commitment_builder().build()?;
Ok(())
/// Generates commitments to the HTTP session using the provided committer.
pub fn commit_with<C: HttpCommit>(&mut self, committer: &mut C) -> Result<(), HttpCommitError> {
committer.commit_transcript(
self.state.prover.commitment_builder(),
&self.state.transcript,
)
}
/// Returns a commitment builder for the HTTP session.
///
/// This is for more advanced use cases, you should prefer using `commit` instead.
pub fn commitment_builder(&mut self) -> HttpCommitmentBuilder {
HttpCommitmentBuilder::new(
/// Generates commitments to the HTTP session using the default committer.
pub fn commit(&mut self) -> Result<(), HttpCommitError> {
DefaultHttpCommitter::default().commit_transcript(
self.state.prover.commitment_builder(),
&self.state.requests,
&self.state.responses,
&self.state.transcript,
)
}
@@ -94,8 +77,7 @@ impl HttpProver<state::Notarize> {
pub async fn finalize(self) -> Result<NotarizedHttpSession, HttpProverError> {
Ok(NotarizedHttpSession::new(
self.state.prover.finalize().await?,
self.state.requests,
self.state.responses,
self.state.transcript,
))
}
}

View File

@@ -1,6 +1,6 @@
//! HTTP prover state.
use tlsn_formats::http::{Body, Request, Response};
use tlsn_formats::http::HttpTranscript;
use crate::tls::{state as prover_state, Prover};
@@ -10,15 +10,13 @@ pub trait State: sealed::Sealed {}
/// Connection closed state.
pub struct Closed {
pub(super) prover: Prover<prover_state::Closed>,
pub(super) requests: Vec<(Request, Option<Body>)>,
pub(super) responses: Vec<(Response, Option<Body>)>,
pub(super) transcript: HttpTranscript,
}
/// Notarizing state.
pub struct Notarize {
pub(super) prover: Prover<prover_state::Notarize>,
pub(super) requests: Vec<(Request, Option<Body>)>,
pub(super) responses: Vec<(Response, Option<Body>)>,
pub(super) transcript: HttpTranscript,
}
impl State for Closed {}

View File

@@ -39,7 +39,7 @@ use tlsn_core::transcript::Transcript;
use utils_aio::mux::MuxChannel;
#[cfg(feature = "formats")]
use http::{state as http_state, HttpProver, HttpProverError};
use crate::http::{state as http_state, HttpProver, HttpProverError};
#[cfg(feature = "tracing")]
use tracing::{debug, debug_span, instrument, Instrument};