mirror of
https://github.com/tlsnotary/tlsn.git
synced 2026-01-09 21:38:00 -05:00
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:
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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" }
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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))]
|
||||
|
||||
@@ -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))?;
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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())),
|
||||
}
|
||||
}
|
||||
}
|
||||
387
tlsn/tlsn-formats/src/http/commit.rs
Normal file
387
tlsn/tlsn-formats/src/http/commit.rs
Normal 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 {}
|
||||
@@ -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(())
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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(_)));
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
250
tlsn/tlsn-formats/src/json/commit.rs
Normal file
250
tlsn/tlsn-formats/src/json/commit.rs
Normal 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 {}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -13,4 +13,7 @@
|
||||
|
||||
pub mod http;
|
||||
pub mod json;
|
||||
mod unknown;
|
||||
|
||||
#[doc(hidden)]
|
||||
pub use spansy;
|
||||
pub use spansy::ParseError;
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {}
|
||||
|
||||
@@ -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};
|
||||
|
||||
Reference in New Issue
Block a user