mirror of
https://github.com/tlsnotary/tlsn.git
synced 2026-01-11 15:47:58 -05:00
Compare commits
68 Commits
poc/wasmti
...
plot_py
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b76775fc7c | ||
|
|
72041d1f07 | ||
|
|
ac1df8fc75 | ||
|
|
3cb7c5c0b4 | ||
|
|
b41d678829 | ||
|
|
1ebefa27d8 | ||
|
|
4fe5c1defd | ||
|
|
0e8e547300 | ||
|
|
22cc88907a | ||
|
|
cec4756e0e | ||
|
|
0919e1f2b3 | ||
|
|
43b9f57e1f | ||
|
|
c51331d63d | ||
|
|
3905d9351c | ||
|
|
f8a67bc8e7 | ||
|
|
952a7011bf | ||
|
|
0673818e4e | ||
|
|
a5749d81f1 | ||
|
|
f2e119bb66 | ||
|
|
271ac3771e | ||
|
|
f69dd7a239 | ||
|
|
79f5160cae | ||
|
|
5fef2af698 | ||
|
|
5b2083e211 | ||
|
|
d26bb02d2e | ||
|
|
a766b64184 | ||
|
|
0885d40ddf | ||
|
|
610411aae4 | ||
|
|
37df1baed7 | ||
|
|
aeaebc5c60 | ||
|
|
2e7e3db11d | ||
|
|
0a68837d0a | ||
|
|
0ec2392716 | ||
|
|
f99fce5b5a | ||
|
|
6b9f44e7e5 | ||
|
|
bf1cf2302a | ||
|
|
2884be17e0 | ||
|
|
df8d79c152 | ||
|
|
82d509266b | ||
|
|
d5ad768e7c | ||
|
|
d25fb320d4 | ||
|
|
0539268da7 | ||
|
|
427b2896b5 | ||
|
|
89d1e594d1 | ||
|
|
b4380f021e | ||
|
|
8a823d18ec | ||
|
|
7bcfc56bd8 | ||
|
|
2909d5ebaa | ||
|
|
7918494ccc | ||
|
|
92dd47b376 | ||
|
|
5474a748ce | ||
|
|
92da5adc24 | ||
|
|
e0ce1ad31a | ||
|
|
3b76877920 | ||
|
|
783355772a | ||
|
|
e5c59da90b | ||
|
|
f059c53c2d | ||
|
|
a1367b5428 | ||
|
|
9d8124ac9d | ||
|
|
5034366c72 | ||
|
|
afd8f44261 | ||
|
|
21086d2883 | ||
|
|
cca9a318a4 | ||
|
|
cb804a6025 | ||
|
|
9f849e7c18 | ||
|
|
389bceddef | ||
|
|
657838671a | ||
|
|
2f072b2578 |
210
.github/workflows/ci.yml
vendored
210
.github/workflows/ci.yml
vendored
@@ -11,7 +11,6 @@ on:
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: read
|
||||
attestations: write
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
@@ -19,11 +18,11 @@ env:
|
||||
# We need a higher number of parallel rayon tasks than the default (which is 4)
|
||||
# in order to prevent a deadlock, c.f.
|
||||
# - https://github.com/tlsnotary/tlsn/issues/548
|
||||
# - https://github.com/privacy-scaling-explorations/mpz/issues/178
|
||||
# - https://github.com/privacy-ethereum/mpz/issues/178
|
||||
# 32 seems to be big enough for the foreseeable future
|
||||
RAYON_NUM_THREADS: 32
|
||||
GIT_COMMIT_HASH: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
RUST_VERSION: 1.88.0
|
||||
RUST_VERSION: 1.92.0
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
jobs:
|
||||
clippy:
|
||||
@@ -34,7 +33,7 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install rust toolchain
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: ${{ env.RUST_VERSION }}
|
||||
components: clippy
|
||||
@@ -43,7 +42,7 @@ jobs:
|
||||
uses: Swatinem/rust-cache@v2.7.7
|
||||
|
||||
- name: Clippy
|
||||
run: cargo clippy --keep-going --all-features --all-targets --locked -- -D warnings
|
||||
run: cargo clippy --keep-going --all-features --all-targets --locked
|
||||
|
||||
fmt:
|
||||
name: Check formatting
|
||||
@@ -159,9 +158,6 @@ jobs:
|
||||
- name: Use caching
|
||||
uses: Swatinem/rust-cache@v2.7.7
|
||||
|
||||
- name: Add custom DNS entry to /etc/hosts for notary TLS test
|
||||
run: echo "127.0.0.1 tlsnotaryserver.io" | sudo tee -a /etc/hosts
|
||||
|
||||
- name: Run integration tests
|
||||
run: cargo test --locked --profile tests-integration --workspace --exclude tlsn-tls-client --exclude tlsn-tls-core --no-fail-fast -- --include-ignored
|
||||
|
||||
@@ -186,201 +182,9 @@ jobs:
|
||||
files: lcov.info
|
||||
fail_ci_if_error: true
|
||||
|
||||
build-sgx:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build-and-test
|
||||
container:
|
||||
image: rust:latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Clang
|
||||
run: |
|
||||
apt update
|
||||
apt install -y clang
|
||||
|
||||
- name: Use caching
|
||||
uses: Swatinem/rust-cache@v2.7.7
|
||||
|
||||
- name: Build Rust Binary
|
||||
run: |
|
||||
cargo build --locked --bin notary-server --release --features tee_quote
|
||||
cp --verbose target/release/notary-server $GITHUB_WORKSPACE
|
||||
|
||||
- name: Upload Binary for use in the Gramine Job
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: notary-server
|
||||
path: notary-server
|
||||
if-no-files-found: error
|
||||
|
||||
gramine-sgx:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build-sgx
|
||||
container:
|
||||
image: gramineproject/gramine:latest
|
||||
if: github.ref == 'refs/heads/dev' || (startsWith(github.ref, 'refs/tags/v') && contains(github.ref, '.'))
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Restore SGX signing key from secrets
|
||||
run: |
|
||||
mkdir -p "${HOME}/.config/gramine/"
|
||||
echo "${{ secrets.SGX_SIGNING_KEY }}" > "${HOME}/.config/gramine/enclave-key.pem"
|
||||
# verify key
|
||||
openssl rsa -in "${HOME}/.config/gramine/enclave-key.pem" -check -noout
|
||||
|
||||
- name: Download notary-server binary from build job
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: notary-server
|
||||
path: crates/notary/server/tee
|
||||
|
||||
- name: Install jq
|
||||
run: |
|
||||
apt update
|
||||
apt install -y jq
|
||||
|
||||
- name: Use Gramine to calculate measurements
|
||||
run: |
|
||||
cd crates/notary/server/tee
|
||||
|
||||
chmod +x notary-server
|
||||
|
||||
gramine-manifest \
|
||||
-Dlog_level=debug \
|
||||
-Darch_libdir=/lib/x86_64-linux-gnu \
|
||||
-Dself_exe=notary-server \
|
||||
notary-server.manifest.template \
|
||||
notary-server.manifest
|
||||
|
||||
gramine-sgx-sign \
|
||||
--manifest notary-server.manifest \
|
||||
--output notary-server.manifest.sgx
|
||||
|
||||
gramine-sgx-sigstruct-view --verbose --output-format=json notary-server.sig | tee >> notary-server-sigstruct.json
|
||||
|
||||
cat notary-server-sigstruct.json
|
||||
|
||||
mr_enclave=$(jq -r '.mr_enclave' notary-server-sigstruct.json)
|
||||
mr_signer=$(jq -r '.mr_signer' notary-server-sigstruct.json)
|
||||
|
||||
echo "mrenclave=$mr_enclave" >>"$GITHUB_OUTPUT"
|
||||
echo "#### sgx mrenclave" | tee >>$GITHUB_STEP_SUMMARY
|
||||
echo "\`\`\`mr_enclave: ${mr_enclave}\`\`\`" | tee >>$GITHUB_STEP_SUMMARY
|
||||
echo "\`\`\`mr_signer: ${mr_signer}\`\`\`" | tee >>$GITHUB_STEP_SUMMARY
|
||||
|
||||
- name: Upload notary-server and signatures
|
||||
id: upload-notary-server-sgx
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: notary-server-sgx.zip
|
||||
path: |
|
||||
crates/notary/server/tee/notary-server
|
||||
crates/notary/server/tee/notary-server-sigstruct.json
|
||||
crates/notary/server/tee/notary-server.sig
|
||||
crates/notary/server/tee/notary-server.manifest
|
||||
crates/notary/server/tee/notary-server.manifest.sgx
|
||||
crates/notary/server/tee/README.md
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Attest Build Provenance
|
||||
if: startsWith(github.ref, 'refs/tags/') || github.ref == 'refs/heads/dev'
|
||||
uses: actions/attest-build-provenance@v2
|
||||
with:
|
||||
subject-name: notary-server-sgx.zip
|
||||
subject-digest: sha256:${{ steps.upload-notary-server-sgx.outputs.artifact-digest }}
|
||||
|
||||
- uses: geekyeggo/delete-artifact@v5 # Delete notary-server from the build job, It is part of the zipfile with the signature
|
||||
with:
|
||||
name: notary-server
|
||||
|
||||
gramine-sgx-docker:
|
||||
runs-on: ubuntu-latest
|
||||
needs: gramine-sgx
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
env:
|
||||
CONTAINER_REGISTRY: ghcr.io
|
||||
if: github.ref == 'refs/heads/dev' || (startsWith(github.ref, 'refs/tags/v') && contains(github.ref, '.'))
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
sparse-checkout: './crates/notary/server/tee/notary-server-sgx.Dockerfile'
|
||||
|
||||
- name: Download notary-server-sgx.zip from gramine-sgx job
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: notary-server-sgx.zip
|
||||
path: ./notary-server-sgx
|
||||
|
||||
- name: Log in to the Container registry
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ${{ env.CONTAINER_REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Extract metadata (tags, labels) for Docker image of notary server
|
||||
id: meta-notary-server-sgx
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: ${{ env.CONTAINER_REGISTRY }}/${{ github.repository }}/notary-server-sgx
|
||||
|
||||
- name: Build and push Docker image of notary server
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
tags: ${{ steps.meta-notary-server-sgx.outputs.tags }}
|
||||
labels: ${{ steps.meta-notary-server-sgx.outputs.labels }}
|
||||
file: ./crates/notary/server/tee/notary-server-sgx.Dockerfile
|
||||
|
||||
build_and_publish_notary_server_image:
|
||||
name: Build and publish notary server's image
|
||||
runs-on: ubuntu-latest
|
||||
needs: build-and-test
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
env:
|
||||
CONTAINER_REGISTRY: ghcr.io
|
||||
if: github.ref == 'refs/heads/dev' || (startsWith(github.ref, 'refs/tags/v') && contains(github.ref, '.'))
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Log in to the Container registry
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ${{ env.CONTAINER_REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Extract metadata (tags, labels) for Docker image of notary server
|
||||
id: meta-notary-server
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: ${{ env.CONTAINER_REGISTRY }}/${{ github.repository }}/notary-server
|
||||
|
||||
- name: Build and push Docker image of notary server
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
tags: ${{ steps.meta-notary-server.outputs.tags }}
|
||||
labels: ${{ steps.meta-notary-server.outputs.labels }}
|
||||
file: ./crates/notary/server/notary-server.Dockerfile
|
||||
|
||||
create-release-draft:
|
||||
name: Create Release Draft
|
||||
needs: build_and_publish_notary_server_image
|
||||
needs: build-and-test
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
@@ -395,4 +199,4 @@ jobs:
|
||||
draft: true
|
||||
tag_name: ${{ github.ref_name }}
|
||||
prerelease: true
|
||||
generate_release_notes: true
|
||||
generate_release_notes: true
|
||||
|
||||
2
.github/workflows/releng.yml
vendored
2
.github/workflows/releng.yml
vendored
@@ -6,7 +6,7 @@ on:
|
||||
tag:
|
||||
description: 'Tag to publish to NPM'
|
||||
required: true
|
||||
default: 'v0.1.0-alpha.13-pre'
|
||||
default: 'v0.1.0-alpha.14-pre'
|
||||
|
||||
jobs:
|
||||
release:
|
||||
|
||||
1
.github/workflows/rustdoc.yml
vendored
1
.github/workflows/rustdoc.yml
vendored
@@ -23,7 +23,6 @@ jobs:
|
||||
- name: "rustdoc"
|
||||
run: crates/wasm/build-docs.sh
|
||||
|
||||
|
||||
- name: Deploy
|
||||
uses: peaceiris/actions-gh-pages@v3
|
||||
if: ${{ github.ref == 'refs/heads/dev' }}
|
||||
|
||||
1
.github/workflows/updatemain.yml
vendored
1
.github/workflows/updatemain.yml
vendored
@@ -1,6 +1,7 @@
|
||||
name: Fast-forward main branch to published release tag
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
|
||||
5662
Cargo.lock
generated
5662
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
63
Cargo.toml
63
Cargo.toml
@@ -9,10 +9,6 @@ members = [
|
||||
"crates/data-fixtures",
|
||||
"crates/examples",
|
||||
"crates/formats",
|
||||
"crates/notary/client",
|
||||
"crates/notary/common",
|
||||
"crates/notary/server",
|
||||
"crates/notary/tests-integration",
|
||||
"crates/server-fixture/certs",
|
||||
"crates/server-fixture/server",
|
||||
"crates/tls/backend",
|
||||
@@ -43,11 +39,10 @@ opt-level = 1
|
||||
[profile.wasm]
|
||||
inherits = "release"
|
||||
lto = true
|
||||
panic = "abort"
|
||||
codegen-units = 1
|
||||
|
||||
[workspace.dependencies]
|
||||
notary-client = { path = "crates/notary/client" }
|
||||
notary-common = { path = "crates/notary/common" }
|
||||
notary-server = { path = "crates/notary/server" }
|
||||
tls-server-fixture = { path = "crates/tls/server-fixture" }
|
||||
tlsn-attestation = { path = "crates/attestation" }
|
||||
tlsn-cipher = { path = "crates/components/cipher" }
|
||||
@@ -71,31 +66,33 @@ tlsn-harness-runner = { path = "crates/harness/runner" }
|
||||
tlsn-wasm = { path = "crates/wasm" }
|
||||
tlsn = { path = "crates/tlsn" }
|
||||
|
||||
mpz-circuits = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "ccc0057" }
|
||||
mpz-memory-core = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "ccc0057" }
|
||||
mpz-common = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "ccc0057" }
|
||||
mpz-core = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "ccc0057" }
|
||||
mpz-vm-core = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "ccc0057" }
|
||||
mpz-garble = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "ccc0057" }
|
||||
mpz-garble-core = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "ccc0057" }
|
||||
mpz-ole = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "ccc0057" }
|
||||
mpz-ot = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "ccc0057" }
|
||||
mpz-share-conversion = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "ccc0057" }
|
||||
mpz-fields = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "ccc0057" }
|
||||
mpz-zk = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "ccc0057" }
|
||||
mpz-hash = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "ccc0057" }
|
||||
mpz-circuits = { git = "https://github.com/privacy-ethereum/mpz", rev = "9c343f8" }
|
||||
mpz-circuits-data = { git = "https://github.com/privacy-ethereum/mpz", rev = "9c343f8" }
|
||||
mpz-memory-core = { git = "https://github.com/privacy-ethereum/mpz", rev = "9c343f8" }
|
||||
mpz-common = { git = "https://github.com/privacy-ethereum/mpz", rev = "9c343f8" }
|
||||
mpz-core = { git = "https://github.com/privacy-ethereum/mpz", rev = "9c343f8" }
|
||||
mpz-vm-core = { git = "https://github.com/privacy-ethereum/mpz", rev = "9c343f8" }
|
||||
mpz-garble = { git = "https://github.com/privacy-ethereum/mpz", rev = "9c343f8" }
|
||||
mpz-garble-core = { git = "https://github.com/privacy-ethereum/mpz", rev = "9c343f8" }
|
||||
mpz-ole = { git = "https://github.com/privacy-ethereum/mpz", rev = "9c343f8" }
|
||||
mpz-ot = { git = "https://github.com/privacy-ethereum/mpz", rev = "9c343f8" }
|
||||
mpz-share-conversion = { git = "https://github.com/privacy-ethereum/mpz", rev = "9c343f8" }
|
||||
mpz-fields = { git = "https://github.com/privacy-ethereum/mpz", rev = "9c343f8" }
|
||||
mpz-zk = { git = "https://github.com/privacy-ethereum/mpz", rev = "9c343f8" }
|
||||
mpz-hash = { git = "https://github.com/privacy-ethereum/mpz", rev = "9c343f8" }
|
||||
mpz-ideal-vm = { git = "https://github.com/privacy-ethereum/mpz", rev = "9c343f8" }
|
||||
|
||||
rangeset = { version = "0.2" }
|
||||
rangeset = { version = "0.4" }
|
||||
serio = { version = "0.2" }
|
||||
spansy = { git = "https://github.com/tlsnotary/tlsn-utils", rev = "6168663" }
|
||||
spansy = { git = "https://github.com/tlsnotary/tlsn-utils", rev = "6f1a934" }
|
||||
uid-mux = { version = "0.2" }
|
||||
websocket-relay = { git = "https://github.com/tlsnotary/tlsn-utils", rev = "6168663" }
|
||||
websocket-relay = { git = "https://github.com/tlsnotary/tlsn-utils", rev = "6f1a934" }
|
||||
|
||||
aead = { version = "0.4" }
|
||||
aes = { version = "0.8" }
|
||||
aes-gcm = { version = "0.9" }
|
||||
anyhow = { version = "1.0" }
|
||||
async-trait = { version = "0.1" }
|
||||
async-tungstenite = { version = "0.28.2" }
|
||||
axum = { version = "0.8" }
|
||||
bcs = { version = "0.1" }
|
||||
bincode = { version = "1.3" }
|
||||
@@ -115,13 +112,11 @@ elliptic-curve = { version = "0.13" }
|
||||
enum-try-as-inner = { version = "0.1" }
|
||||
env_logger = { version = "0.10" }
|
||||
futures = { version = "0.3" }
|
||||
futures-rustls = { version = "0.26" }
|
||||
futures-util = { version = "0.3" }
|
||||
futures-rustls = { version = "0.25" }
|
||||
generic-array = { version = "0.14" }
|
||||
ghash = { version = "0.5" }
|
||||
hex = { version = "0.4" }
|
||||
hmac = { version = "0.12" }
|
||||
http = { version = "1.1" }
|
||||
http-body-util = { version = "0.1" }
|
||||
hyper = { version = "1.1" }
|
||||
hyper-util = { version = "0.1" }
|
||||
@@ -134,7 +129,6 @@ log = { version = "0.4" }
|
||||
once_cell = { version = "1.19" }
|
||||
opaque-debug = { version = "0.3" }
|
||||
p256 = { version = "0.13" }
|
||||
pkcs8 = { version = "0.10" }
|
||||
pin-project-lite = { version = "0.2" }
|
||||
pollster = { version = "0.4" }
|
||||
rand = { version = "0.9" }
|
||||
@@ -148,6 +142,8 @@ rs_merkle = { git = "https://github.com/tlsnotary/rs-merkle.git", rev = "85f3e82
|
||||
rstest = { version = "0.17" }
|
||||
rustls = { version = "0.21" }
|
||||
rustls-pemfile = { version = "1.0" }
|
||||
rustls-webpki = { version = "0.103" }
|
||||
rustls-pki-types = { version = "1.12" }
|
||||
sct = { version = "0.7" }
|
||||
semver = { version = "1.0" }
|
||||
serde = { version = "1.0" }
|
||||
@@ -157,23 +153,18 @@ signature = { version = "2.2" }
|
||||
thiserror = { version = "1.0" }
|
||||
tiny-keccak = { version = "2.0" }
|
||||
tokio = { version = "1.38" }
|
||||
tokio-rustls = { version = "0.24" }
|
||||
tokio-util = { version = "0.7" }
|
||||
toml = { version = "0.8" }
|
||||
tower = { version = "0.5" }
|
||||
tower-http = { version = "0.5" }
|
||||
tower-service = { version = "0.3" }
|
||||
tower-util = { version = "0.3.1" }
|
||||
tracing = { version = "0.1" }
|
||||
tracing-subscriber = { version = "0.3" }
|
||||
uuid = { version = "1.4" }
|
||||
wasm-bindgen = { version = "0.2" }
|
||||
wasm-bindgen-futures = { version = "0.4" }
|
||||
web-spawn = { version = "0.2" }
|
||||
web-time = { version = "0.2" }
|
||||
webpki = { version = "0.22" }
|
||||
webpki-roots = { version = "0.26" }
|
||||
ws_stream_tungstenite = { version = "0.14" }
|
||||
# Use the patched ws_stream_wasm to fix the issue https://github.com/najamelan/ws_stream_wasm/issues/12#issuecomment-1711902958
|
||||
ws_stream_wasm = { git = "https://github.com/tlsnotary/ws_stream_wasm", rev = "2ed12aad9f0236e5321f577672f309920b2aef51" }
|
||||
webpki-roots = { version = "1.0" }
|
||||
webpki-root-certs = { version = "1.0" }
|
||||
ws_stream_wasm = { version = "0.7.5" }
|
||||
zeroize = { version = "1.8" }
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
[actions-url]: https://github.com/tlsnotary/tlsn/actions?query=workflow%3Aci+branch%3Adev
|
||||
|
||||
[Website](https://tlsnotary.org) |
|
||||
[Documentation](https://docs.tlsnotary.org) |
|
||||
[Documentation](https://tlsnotary.org/docs/intro) |
|
||||
[API Docs](https://tlsnotary.github.io/tlsn) |
|
||||
[Discord](https://discord.gg/9XwESXtcN7)
|
||||
|
||||
@@ -44,12 +44,9 @@ at your option.
|
||||
## Directory
|
||||
|
||||
- [examples](./crates/examples/): Examples on how to use the TLSNotary protocol.
|
||||
- [tlsn-prover](./crates/prover/): The library for the prover component.
|
||||
- [tlsn-verifier](./crates/verifier/): The library for the verifier component.
|
||||
- [notary](./crates/notary/): Implements the [notary server](https://docs.tlsnotary.org/intro.html#tls-verification-with-a-general-purpose-notary) and its client.
|
||||
- [components](./crates/components/): Houses low-level libraries.
|
||||
- [tlsn](./crates/tlsn/): The TLSNotary library.
|
||||
|
||||
This repository contains the source code for the Rust implementation of the TLSNotary protocol. For additional tools and implementations related to TLSNotary, visit <https://github.com/tlsnotary>. This includes repositories such as [`tlsn-js`](https://github.com/tlsnotary/tlsn-js), [`tlsn-extension`](https://github.com/tlsnotary/tlsn-extension), [`explorer`](https://github.com/tlsnotary/explorer), among others.
|
||||
This repository contains the source code for the Rust implementation of the TLSNotary protocol. For additional tools and implementations related to TLSNotary, visit <https://github.com/tlsnotary>. This includes repositories such as [`tlsn-js`](https://github.com/tlsnotary/tlsn-js), [`tlsn-extension`](https://github.com/tlsnotary/tlsn-extension), among others.
|
||||
|
||||
|
||||
## Development
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "tlsn-attestation"
|
||||
version = "0.1.0-alpha.13-pre"
|
||||
version = "0.1.0-alpha.14-pre"
|
||||
edition = "2024"
|
||||
|
||||
[features]
|
||||
@@ -9,7 +9,7 @@ fixtures = ["tlsn-core/fixtures", "dep:tlsn-data-fixtures"]
|
||||
|
||||
[dependencies]
|
||||
tlsn-tls-core = { workspace = true }
|
||||
tlsn-core = { workspace = true }
|
||||
tlsn-core = { workspace = true, features = ["mozilla-certs"] }
|
||||
tlsn-data-fixtures = { workspace = true, optional = true }
|
||||
|
||||
bcs = { workspace = true }
|
||||
@@ -21,12 +21,11 @@ rand = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
thiserror = { workspace = true }
|
||||
tiny-keccak = { workspace = true, features = ["keccak"] }
|
||||
webpki-roots = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
alloy-primitives = { version = "0.8.22", default-features = false }
|
||||
alloy-signer = { version = "0.12", default-features = false }
|
||||
alloy-signer-local = { version = "0.12", default-features = false }
|
||||
alloy-primitives = { version = "1.3.1", default-features = false }
|
||||
alloy-signer = { version = "1.0", default-features = false }
|
||||
alloy-signer-local = { version = "1.0", default-features = false }
|
||||
rand06-compat = { workspace = true }
|
||||
rstest = { workspace = true }
|
||||
tlsn-core = { workspace = true, features = ["fixtures"] }
|
||||
|
||||
@@ -5,7 +5,7 @@ use rand::{Rng, rng};
|
||||
use tlsn_core::{
|
||||
connection::{ConnectionInfo, ServerEphemKey},
|
||||
hash::HashAlgId,
|
||||
transcript::TranscriptCommitment,
|
||||
transcript::{TranscriptCommitment, encoding::EncoderSecret},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
@@ -25,6 +25,7 @@ pub struct Sign {
|
||||
connection_info: Option<ConnectionInfo>,
|
||||
server_ephemeral_key: Option<ServerEphemKey>,
|
||||
cert_commitment: ServerCertCommitment,
|
||||
encoder_secret: Option<EncoderSecret>,
|
||||
extensions: Vec<Extension>,
|
||||
transcript_commitments: Vec<TranscriptCommitment>,
|
||||
}
|
||||
@@ -86,6 +87,7 @@ impl<'a> AttestationBuilder<'a, Accept> {
|
||||
connection_info: None,
|
||||
server_ephemeral_key: None,
|
||||
cert_commitment,
|
||||
encoder_secret: None,
|
||||
transcript_commitments: Vec::new(),
|
||||
extensions,
|
||||
},
|
||||
@@ -106,6 +108,12 @@ impl AttestationBuilder<'_, Sign> {
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the secret for encoding commitments.
|
||||
pub fn encoder_secret(&mut self, secret: EncoderSecret) -> &mut Self {
|
||||
self.state.encoder_secret = Some(secret);
|
||||
self
|
||||
}
|
||||
|
||||
/// Adds an extension to the attestation.
|
||||
pub fn extension(&mut self, extension: Extension) -> &mut Self {
|
||||
self.state.extensions.push(extension);
|
||||
@@ -129,6 +137,7 @@ impl AttestationBuilder<'_, Sign> {
|
||||
connection_info,
|
||||
server_ephemeral_key,
|
||||
cert_commitment,
|
||||
encoder_secret,
|
||||
extensions,
|
||||
transcript_commitments,
|
||||
} = self.state;
|
||||
@@ -159,6 +168,7 @@ impl AttestationBuilder<'_, Sign> {
|
||||
AttestationBuilderError::new(ErrorKind::Field, "handshake data was not set")
|
||||
})?),
|
||||
cert_commitment: field_id.next(cert_commitment),
|
||||
encoder_secret: encoder_secret.map(|secret| field_id.next(secret)),
|
||||
extensions: extensions
|
||||
.into_iter()
|
||||
.map(|extension| field_id.next(extension))
|
||||
@@ -242,7 +252,7 @@ impl std::fmt::Display for AttestationBuilderError {
|
||||
mod test {
|
||||
use rstest::{fixture, rstest};
|
||||
use tlsn_core::{
|
||||
connection::{HandshakeData, HandshakeDataV1_2},
|
||||
connection::{CertBinding, CertBindingV1_2},
|
||||
fixtures::{ConnectionFixture, encoding_provider},
|
||||
hash::Blake3,
|
||||
transcript::Transcript,
|
||||
@@ -399,10 +409,10 @@ mod test {
|
||||
server_cert_data, ..
|
||||
} = connection;
|
||||
|
||||
let HandshakeData::V1_2(HandshakeDataV1_2 {
|
||||
let CertBinding::V1_2(CertBindingV1_2 {
|
||||
server_ephemeral_key,
|
||||
..
|
||||
}) = server_cert_data.handshake
|
||||
}) = server_cert_data.binding
|
||||
else {
|
||||
panic!("expected v1.2 handshake data");
|
||||
};
|
||||
@@ -470,10 +480,10 @@ mod test {
|
||||
..
|
||||
} = connection;
|
||||
|
||||
let HandshakeData::V1_2(HandshakeDataV1_2 {
|
||||
let CertBinding::V1_2(CertBindingV1_2 {
|
||||
server_ephemeral_key,
|
||||
..
|
||||
}) = server_cert_data.handshake
|
||||
}) = server_cert_data.binding
|
||||
else {
|
||||
panic!("expected v1.2 handshake data");
|
||||
};
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use tlsn_core::{
|
||||
connection::{CertificateVerificationError, ServerCertData, ServerEphemKey, ServerName},
|
||||
connection::{HandshakeData, HandshakeVerificationError, ServerEphemKey, ServerName},
|
||||
hash::{Blinded, HashAlgorithm, HashProviderError, TypedHash},
|
||||
};
|
||||
|
||||
@@ -30,14 +30,14 @@ use crate::{CryptoProvider, hash::HashAlgorithmExt, serialize::impl_domain_separ
|
||||
|
||||
/// Opens a [`ServerCertCommitment`].
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub struct ServerCertOpening(Blinded<ServerCertData>);
|
||||
pub struct ServerCertOpening(Blinded<HandshakeData>);
|
||||
|
||||
impl_domain_separator!(ServerCertOpening);
|
||||
|
||||
opaque_debug::implement!(ServerCertOpening);
|
||||
|
||||
impl ServerCertOpening {
|
||||
pub(crate) fn new(data: ServerCertData) -> Self {
|
||||
pub(crate) fn new(data: HandshakeData) -> Self {
|
||||
Self(Blinded::new(data))
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ impl ServerCertOpening {
|
||||
}
|
||||
|
||||
/// Returns the server identity data.
|
||||
pub fn data(&self) -> &ServerCertData {
|
||||
pub fn data(&self) -> &HandshakeData {
|
||||
self.0.data()
|
||||
}
|
||||
}
|
||||
@@ -122,8 +122,8 @@ impl From<HashProviderError> for ServerIdentityProofError {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CertificateVerificationError> for ServerIdentityProofError {
|
||||
fn from(err: CertificateVerificationError) -> Self {
|
||||
impl From<HandshakeVerificationError> for ServerIdentityProofError {
|
||||
fn from(err: HandshakeVerificationError) -> Self {
|
||||
Self {
|
||||
kind: ErrorKind::Certificate,
|
||||
message: err.to_string(),
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
//! Attestation fixtures.
|
||||
|
||||
use tlsn_core::{
|
||||
connection::{HandshakeData, HandshakeDataV1_2},
|
||||
connection::{CertBinding, CertBindingV1_2},
|
||||
fixtures::ConnectionFixture,
|
||||
hash::HashAlgorithm,
|
||||
transcript::{
|
||||
@@ -13,14 +12,12 @@ use tlsn_core::{
|
||||
use crate::{
|
||||
Attestation, AttestationConfig, CryptoProvider, Extension,
|
||||
request::{Request, RequestConfig},
|
||||
signing::SignatureAlgId,
|
||||
signing::{
|
||||
KeyAlgId, SignatureAlgId, SignatureVerifier, SignatureVerifierProvider, Signer,
|
||||
SignerProvider,
|
||||
},
|
||||
};
|
||||
|
||||
/// Returns a notary signing key fixture.
|
||||
pub fn notary_signing_key() -> p256::ecdsa::SigningKey {
|
||||
p256::ecdsa::SigningKey::from_slice(&[1; 32]).unwrap()
|
||||
}
|
||||
|
||||
/// A Request fixture used for testing.
|
||||
#[allow(missing_docs)]
|
||||
pub struct RequestFixture {
|
||||
@@ -72,7 +69,7 @@ pub fn request_fixture(
|
||||
let mut request_builder = Request::builder(&request_config);
|
||||
request_builder
|
||||
.server_name(server_name)
|
||||
.server_cert_data(server_cert_data)
|
||||
.handshake_data(server_cert_data)
|
||||
.transcript(transcript);
|
||||
|
||||
let (request, _) = request_builder.build(&provider).unwrap();
|
||||
@@ -96,18 +93,19 @@ pub fn attestation_fixture(
|
||||
..
|
||||
} = connection;
|
||||
|
||||
let HandshakeData::V1_2(HandshakeDataV1_2 {
|
||||
let CertBinding::V1_2(CertBindingV1_2 {
|
||||
server_ephemeral_key,
|
||||
..
|
||||
}) = server_cert_data.handshake
|
||||
}) = server_cert_data.binding
|
||||
else {
|
||||
panic!("expected v1.2 handshake data");
|
||||
panic!("expected v1.2 binding data");
|
||||
};
|
||||
|
||||
let mut provider = CryptoProvider::default();
|
||||
match signature_alg {
|
||||
SignatureAlgId::SECP256K1 => provider.signer.set_secp256k1(&[42u8; 32]).unwrap(),
|
||||
SignatureAlgId::SECP256R1 => provider.signer.set_secp256r1(&[42u8; 32]).unwrap(),
|
||||
SignatureAlgId::SECP256K1ETH => provider.signer.set_secp256k1eth(&[43u8; 32]).unwrap(),
|
||||
SignatureAlgId::SECP256R1 => provider.signer.set_secp256r1(&[44u8; 32]).unwrap(),
|
||||
_ => unimplemented!(),
|
||||
};
|
||||
|
||||
@@ -127,3 +125,68 @@ pub fn attestation_fixture(
|
||||
|
||||
attestation_builder.build(&provider).unwrap()
|
||||
}
|
||||
|
||||
/// Returns a crypto provider which supports only a custom signature alg.
|
||||
pub fn custom_provider_fixture() -> CryptoProvider {
|
||||
const CUSTOM_SIG_ALG_ID: SignatureAlgId = SignatureAlgId::new(128);
|
||||
|
||||
// A dummy signer.
|
||||
struct DummySigner {}
|
||||
impl Signer for DummySigner {
|
||||
fn alg_id(&self) -> SignatureAlgId {
|
||||
CUSTOM_SIG_ALG_ID
|
||||
}
|
||||
|
||||
fn sign(
|
||||
&self,
|
||||
msg: &[u8],
|
||||
) -> Result<crate::signing::Signature, crate::signing::SignatureError> {
|
||||
Ok(crate::signing::Signature {
|
||||
alg: CUSTOM_SIG_ALG_ID,
|
||||
data: msg.to_vec(),
|
||||
})
|
||||
}
|
||||
|
||||
fn verifying_key(&self) -> crate::signing::VerifyingKey {
|
||||
crate::signing::VerifyingKey {
|
||||
alg: KeyAlgId::new(128),
|
||||
data: vec![1, 2, 3, 4],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A dummy verifier.
|
||||
struct DummyVerifier {}
|
||||
impl SignatureVerifier for DummyVerifier {
|
||||
fn alg_id(&self) -> SignatureAlgId {
|
||||
CUSTOM_SIG_ALG_ID
|
||||
}
|
||||
|
||||
fn verify(
|
||||
&self,
|
||||
_key: &crate::signing::VerifyingKey,
|
||||
msg: &[u8],
|
||||
sig: &[u8],
|
||||
) -> Result<(), crate::signing::SignatureError> {
|
||||
if msg == sig {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(crate::signing::SignatureError::from_str(
|
||||
"invalid signature",
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut provider = CryptoProvider::default();
|
||||
|
||||
let mut signer_provider = SignerProvider::default();
|
||||
signer_provider.set_signer(Box::new(DummySigner {}));
|
||||
provider.signer = signer_provider;
|
||||
|
||||
let mut verifier_provider = SignatureVerifierProvider::empty();
|
||||
verifier_provider.set_verifier(Box::new(DummyVerifier {}));
|
||||
provider.signature = verifier_provider;
|
||||
|
||||
provider
|
||||
}
|
||||
|
||||
@@ -219,7 +219,7 @@ use tlsn_core::{
|
||||
connection::{ConnectionInfo, ServerEphemKey},
|
||||
hash::{Hash, HashAlgorithm, TypedHash},
|
||||
merkle::MerkleTree,
|
||||
transcript::TranscriptCommitment,
|
||||
transcript::{TranscriptCommitment, encoding::EncoderSecret},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
@@ -327,6 +327,7 @@ pub struct Body {
|
||||
connection_info: Field<ConnectionInfo>,
|
||||
server_ephemeral_key: Field<ServerEphemKey>,
|
||||
cert_commitment: Field<ServerCertCommitment>,
|
||||
encoder_secret: Option<Field<EncoderSecret>>,
|
||||
extensions: Vec<Field<Extension>>,
|
||||
transcript_commitments: Vec<Field<TranscriptCommitment>>,
|
||||
}
|
||||
@@ -372,6 +373,7 @@ impl Body {
|
||||
connection_info: conn_info,
|
||||
server_ephemeral_key,
|
||||
cert_commitment,
|
||||
encoder_secret,
|
||||
extensions,
|
||||
transcript_commitments,
|
||||
} = self;
|
||||
@@ -389,6 +391,13 @@ impl Body {
|
||||
),
|
||||
];
|
||||
|
||||
if let Some(encoder_secret) = encoder_secret {
|
||||
fields.push((
|
||||
encoder_secret.id,
|
||||
hasher.hash_separated(&encoder_secret.data),
|
||||
));
|
||||
}
|
||||
|
||||
for field in extensions.iter() {
|
||||
fields.push((field.id, hasher.hash_separated(&field.data)));
|
||||
}
|
||||
|
||||
@@ -91,6 +91,11 @@ impl Presentation {
|
||||
transcript.verify_with_provider(
|
||||
&provider.hash,
|
||||
&attestation.body.connection_info().transcript_length,
|
||||
attestation
|
||||
.body
|
||||
.encoder_secret
|
||||
.as_ref()
|
||||
.map(|field| &field.data),
|
||||
attestation.body.transcript_commitments(),
|
||||
)
|
||||
})
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
use tls_core::{
|
||||
anchors::{OwnedTrustAnchor, RootCertStore},
|
||||
verify::WebPkiVerifier,
|
||||
};
|
||||
use tlsn_core::hash::HashProvider;
|
||||
use tlsn_core::{hash::HashProvider, webpki::ServerCertVerifier};
|
||||
|
||||
use crate::signing::{SignatureVerifierProvider, SignerProvider};
|
||||
|
||||
@@ -28,7 +24,7 @@ pub struct CryptoProvider {
|
||||
/// This is used to verify the server's certificate chain.
|
||||
///
|
||||
/// The default verifier uses the Mozilla root certificates.
|
||||
pub cert: WebPkiVerifier,
|
||||
pub cert: ServerCertVerifier,
|
||||
/// Signer provider.
|
||||
///
|
||||
/// This is used for signing attestations.
|
||||
@@ -45,21 +41,9 @@ impl Default for CryptoProvider {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
hash: Default::default(),
|
||||
cert: default_cert_verifier(),
|
||||
cert: ServerCertVerifier::mozilla(),
|
||||
signer: Default::default(),
|
||||
signature: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn default_cert_verifier() -> WebPkiVerifier {
|
||||
let mut root_store = RootCertStore::empty();
|
||||
root_store.add_server_trust_anchors(webpki_roots::TLS_SERVER_ROOTS.iter().map(|ta| {
|
||||
OwnedTrustAnchor::from_subject_spki_name_constraints(
|
||||
ta.subject.as_ref(),
|
||||
ta.subject_public_key_info.as_ref(),
|
||||
ta.name_constraints.as_ref().map(|nc| nc.as_ref()),
|
||||
)
|
||||
}));
|
||||
WebPkiVerifier::new(root_store, None)
|
||||
}
|
||||
|
||||
@@ -20,7 +20,10 @@ use serde::{Deserialize, Serialize};
|
||||
|
||||
use tlsn_core::hash::HashAlgId;
|
||||
|
||||
use crate::{Attestation, Extension, connection::ServerCertCommitment, signing::SignatureAlgId};
|
||||
use crate::{
|
||||
Attestation, CryptoProvider, Extension, connection::ServerCertCommitment,
|
||||
serialize::CanonicalSerialize, signing::SignatureAlgId,
|
||||
};
|
||||
|
||||
pub use builder::{RequestBuilder, RequestBuilderError};
|
||||
pub use config::{RequestConfig, RequestConfigBuilder, RequestConfigBuilderError};
|
||||
@@ -36,49 +39,107 @@ pub struct Request {
|
||||
|
||||
impl Request {
|
||||
/// Returns a new request builder.
|
||||
pub fn builder(config: &RequestConfig) -> RequestBuilder {
|
||||
pub fn builder(config: &RequestConfig) -> RequestBuilder<'_> {
|
||||
RequestBuilder::new(config)
|
||||
}
|
||||
|
||||
/// Validates the content of the attestation against this request.
|
||||
pub fn validate(&self, attestation: &Attestation) -> Result<(), InconsistentAttestation> {
|
||||
pub fn validate(
|
||||
&self,
|
||||
attestation: &Attestation,
|
||||
provider: &CryptoProvider,
|
||||
) -> Result<(), AttestationValidationError> {
|
||||
if attestation.signature.alg != self.signature_alg {
|
||||
return Err(InconsistentAttestation(format!(
|
||||
return Err(AttestationValidationError::inconsistent(format!(
|
||||
"signature algorithm: expected {:?}, got {:?}",
|
||||
self.signature_alg, attestation.signature.alg
|
||||
)));
|
||||
}
|
||||
|
||||
if attestation.header.root.alg != self.hash_alg {
|
||||
return Err(InconsistentAttestation(format!(
|
||||
return Err(AttestationValidationError::inconsistent(format!(
|
||||
"hash algorithm: expected {:?}, got {:?}",
|
||||
self.hash_alg, attestation.header.root.alg
|
||||
)));
|
||||
}
|
||||
|
||||
if attestation.body.cert_commitment() != &self.server_cert_commitment {
|
||||
return Err(InconsistentAttestation(
|
||||
"server certificate commitment does not match".to_string(),
|
||||
return Err(AttestationValidationError::inconsistent(
|
||||
"server certificate commitment does not match",
|
||||
));
|
||||
}
|
||||
|
||||
// TODO: improve the O(M*N) complexity of this check.
|
||||
for extension in &self.extensions {
|
||||
if !attestation.body.extensions().any(|e| e == extension) {
|
||||
return Err(InconsistentAttestation(
|
||||
"extension is missing from the attestation".to_string(),
|
||||
return Err(AttestationValidationError::inconsistent(
|
||||
"extension is missing from the attestation",
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
let verifier = provider
|
||||
.signature
|
||||
.get(&attestation.signature.alg)
|
||||
.map_err(|_| {
|
||||
AttestationValidationError::provider(format!(
|
||||
"provider not configured for signature algorithm id {:?}",
|
||||
attestation.signature.alg,
|
||||
))
|
||||
})?;
|
||||
|
||||
verifier
|
||||
.verify(
|
||||
&attestation.body.verifying_key.data,
|
||||
&CanonicalSerialize::serialize(&attestation.header),
|
||||
&attestation.signature.data,
|
||||
)
|
||||
.map_err(|_| {
|
||||
AttestationValidationError::inconsistent("failed to verify the signature")
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Error for [`Request::validate`].
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[error("inconsistent attestation: {0}")]
|
||||
pub struct InconsistentAttestation(String);
|
||||
#[error("attestation validation error: {kind}: {message}")]
|
||||
pub struct AttestationValidationError {
|
||||
kind: ErrorKind,
|
||||
message: String,
|
||||
}
|
||||
|
||||
impl AttestationValidationError {
|
||||
fn inconsistent(msg: impl Into<String>) -> Self {
|
||||
Self {
|
||||
kind: ErrorKind::Inconsistent,
|
||||
message: msg.into(),
|
||||
}
|
||||
}
|
||||
|
||||
fn provider(msg: impl Into<String>) -> Self {
|
||||
Self {
|
||||
kind: ErrorKind::Provider,
|
||||
message: msg.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum ErrorKind {
|
||||
Inconsistent,
|
||||
Provider,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for ErrorKind {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
ErrorKind::Inconsistent => write!(f, "inconsistent"),
|
||||
ErrorKind::Provider => write!(f, "provider"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
@@ -93,7 +154,8 @@ mod test {
|
||||
use crate::{
|
||||
CryptoProvider,
|
||||
connection::ServerCertOpening,
|
||||
fixtures::{RequestFixture, attestation_fixture, request_fixture},
|
||||
fixtures::{RequestFixture, attestation_fixture, custom_provider_fixture, request_fixture},
|
||||
request::{AttestationValidationError, ErrorKind},
|
||||
signing::SignatureAlgId,
|
||||
};
|
||||
|
||||
@@ -113,7 +175,9 @@ mod test {
|
||||
let attestation =
|
||||
attestation_fixture(request.clone(), connection, SignatureAlgId::SECP256K1, &[]);
|
||||
|
||||
assert!(request.validate(&attestation).is_ok())
|
||||
let provider = CryptoProvider::default();
|
||||
|
||||
assert!(request.validate(&attestation, &provider).is_ok())
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -134,7 +198,9 @@ mod test {
|
||||
|
||||
request.signature_alg = SignatureAlgId::SECP256R1;
|
||||
|
||||
let res = request.validate(&attestation);
|
||||
let provider = CryptoProvider::default();
|
||||
|
||||
let res = request.validate(&attestation, &provider);
|
||||
assert!(res.is_err());
|
||||
}
|
||||
|
||||
@@ -156,7 +222,9 @@ mod test {
|
||||
|
||||
request.hash_alg = HashAlgId::SHA256;
|
||||
|
||||
let res = request.validate(&attestation);
|
||||
let provider = CryptoProvider::default();
|
||||
|
||||
let res = request.validate(&attestation, &provider);
|
||||
assert!(res.is_err())
|
||||
}
|
||||
|
||||
@@ -184,11 +252,62 @@ mod test {
|
||||
});
|
||||
let opening = ServerCertOpening::new(server_cert_data);
|
||||
|
||||
let crypto_provider = CryptoProvider::default();
|
||||
let provider = CryptoProvider::default();
|
||||
request.server_cert_commitment =
|
||||
opening.commit(crypto_provider.hash.get(&HashAlgId::BLAKE3).unwrap());
|
||||
opening.commit(provider.hash.get(&HashAlgId::BLAKE3).unwrap());
|
||||
|
||||
let res = request.validate(&attestation);
|
||||
let res = request.validate(&attestation, &provider);
|
||||
assert!(res.is_err())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wrong_sig() {
|
||||
let transcript = Transcript::new(GET_WITH_HEADER, OK_JSON);
|
||||
let connection = ConnectionFixture::tlsnotary(transcript.length());
|
||||
|
||||
let RequestFixture { request, .. } = request_fixture(
|
||||
transcript,
|
||||
encoding_provider(GET_WITH_HEADER, OK_JSON),
|
||||
connection.clone(),
|
||||
Blake3::default(),
|
||||
Vec::new(),
|
||||
);
|
||||
|
||||
let mut attestation =
|
||||
attestation_fixture(request.clone(), connection, SignatureAlgId::SECP256K1, &[]);
|
||||
|
||||
// Corrupt the signature.
|
||||
attestation.signature.data[1] = attestation.signature.data[1].wrapping_add(1);
|
||||
|
||||
let provider = CryptoProvider::default();
|
||||
|
||||
assert!(request.validate(&attestation, &provider).is_err())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wrong_provider() {
|
||||
let transcript = Transcript::new(GET_WITH_HEADER, OK_JSON);
|
||||
let connection = ConnectionFixture::tlsnotary(transcript.length());
|
||||
|
||||
let RequestFixture { request, .. } = request_fixture(
|
||||
transcript,
|
||||
encoding_provider(GET_WITH_HEADER, OK_JSON),
|
||||
connection.clone(),
|
||||
Blake3::default(),
|
||||
Vec::new(),
|
||||
);
|
||||
|
||||
let attestation =
|
||||
attestation_fixture(request.clone(), connection, SignatureAlgId::SECP256K1, &[]);
|
||||
|
||||
let provider = custom_provider_fixture();
|
||||
|
||||
assert!(matches!(
|
||||
request.validate(&attestation, &provider),
|
||||
Err(AttestationValidationError {
|
||||
kind: ErrorKind::Provider,
|
||||
..
|
||||
})
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use tlsn_core::{
|
||||
connection::{ServerCertData, ServerName},
|
||||
connection::{HandshakeData, ServerName},
|
||||
transcript::{Transcript, TranscriptCommitment, TranscriptSecret},
|
||||
};
|
||||
|
||||
@@ -13,7 +13,7 @@ use crate::{
|
||||
pub struct RequestBuilder<'a> {
|
||||
config: &'a RequestConfig,
|
||||
server_name: Option<ServerName>,
|
||||
server_cert_data: Option<ServerCertData>,
|
||||
handshake_data: Option<HandshakeData>,
|
||||
transcript: Option<Transcript>,
|
||||
transcript_commitments: Vec<TranscriptCommitment>,
|
||||
transcript_commitment_secrets: Vec<TranscriptSecret>,
|
||||
@@ -25,7 +25,7 @@ impl<'a> RequestBuilder<'a> {
|
||||
Self {
|
||||
config,
|
||||
server_name: None,
|
||||
server_cert_data: None,
|
||||
handshake_data: None,
|
||||
transcript: None,
|
||||
transcript_commitments: Vec::new(),
|
||||
transcript_commitment_secrets: Vec::new(),
|
||||
@@ -38,9 +38,9 @@ impl<'a> RequestBuilder<'a> {
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the server identity data.
|
||||
pub fn server_cert_data(&mut self, data: ServerCertData) -> &mut Self {
|
||||
self.server_cert_data = Some(data);
|
||||
/// Sets the handshake data.
|
||||
pub fn handshake_data(&mut self, data: HandshakeData) -> &mut Self {
|
||||
self.handshake_data = Some(data);
|
||||
self
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ impl<'a> RequestBuilder<'a> {
|
||||
let Self {
|
||||
config,
|
||||
server_name,
|
||||
server_cert_data,
|
||||
handshake_data: server_cert_data,
|
||||
transcript,
|
||||
transcript_commitments,
|
||||
transcript_commitment_secrets,
|
||||
|
||||
@@ -46,8 +46,9 @@ pub(crate) use impl_domain_separator;
|
||||
|
||||
impl_domain_separator!(tlsn_core::connection::ServerEphemKey);
|
||||
impl_domain_separator!(tlsn_core::connection::ConnectionInfo);
|
||||
impl_domain_separator!(tlsn_core::connection::HandshakeData);
|
||||
impl_domain_separator!(tlsn_core::connection::CertBinding);
|
||||
impl_domain_separator!(tlsn_core::transcript::TranscriptCommitment);
|
||||
impl_domain_separator!(tlsn_core::transcript::TranscriptSecret);
|
||||
impl_domain_separator!(tlsn_core::transcript::encoding::EncoderSecret);
|
||||
impl_domain_separator!(tlsn_core::transcript::encoding::EncodingCommitment);
|
||||
impl_domain_separator!(tlsn_core::transcript::hash::PlaintextHash);
|
||||
|
||||
@@ -202,6 +202,14 @@ impl SignatureVerifierProvider {
|
||||
.map(|s| &**s)
|
||||
.ok_or(UnknownSignatureAlgId(*alg))
|
||||
}
|
||||
|
||||
/// Returns am empty provider.
|
||||
#[cfg(any(test, feature = "fixtures"))]
|
||||
pub fn empty() -> Self {
|
||||
Self {
|
||||
verifiers: HashMap::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Signature verifier.
|
||||
@@ -229,6 +237,14 @@ impl_domain_separator!(VerifyingKey);
|
||||
#[error("signature verification failed: {0}")]
|
||||
pub struct SignatureError(String);
|
||||
|
||||
impl SignatureError {
|
||||
/// Creates a new error with the given message.
|
||||
#[allow(clippy::should_implement_trait)]
|
||||
pub fn from_str(msg: &str) -> Self {
|
||||
Self(msg.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
/// A signature.
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct Signature {
|
||||
|
||||
@@ -5,7 +5,7 @@ use tlsn_attestation::{
|
||||
signing::SignatureAlgId,
|
||||
};
|
||||
use tlsn_core::{
|
||||
connection::{HandshakeData, HandshakeDataV1_2},
|
||||
connection::{CertBinding, CertBindingV1_2},
|
||||
fixtures::{self, ConnectionFixture, encoder_secret},
|
||||
hash::Blake3,
|
||||
transcript::{
|
||||
@@ -36,10 +36,10 @@ fn test_api() {
|
||||
server_cert_data,
|
||||
} = ConnectionFixture::tlsnotary(transcript.length());
|
||||
|
||||
let HandshakeData::V1_2(HandshakeDataV1_2 {
|
||||
let CertBinding::V1_2(CertBindingV1_2 {
|
||||
server_ephemeral_key,
|
||||
..
|
||||
}) = server_cert_data.handshake.clone()
|
||||
}) = server_cert_data.binding.clone()
|
||||
else {
|
||||
unreachable!()
|
||||
};
|
||||
@@ -64,7 +64,6 @@ fn test_api() {
|
||||
|
||||
let encoding_commitment = EncodingCommitment {
|
||||
root: encoding_tree.root(),
|
||||
secret: encoder_secret(),
|
||||
};
|
||||
|
||||
let request_config = RequestConfig::default();
|
||||
@@ -72,7 +71,7 @@ fn test_api() {
|
||||
|
||||
request_builder
|
||||
.server_name(server_name.clone())
|
||||
.server_cert_data(server_cert_data)
|
||||
.handshake_data(server_cert_data)
|
||||
.transcript(transcript)
|
||||
.transcript_commitments(
|
||||
vec![TranscriptSecret::Encoding(encoding_tree)],
|
||||
@@ -96,12 +95,13 @@ fn test_api() {
|
||||
.connection_info(connection_info.clone())
|
||||
// Server key Notary received during handshake
|
||||
.server_ephemeral_key(server_ephemeral_key)
|
||||
.encoder_secret(encoder_secret())
|
||||
.transcript_commitments(vec![TranscriptCommitment::Encoding(encoding_commitment)]);
|
||||
|
||||
let attestation = attestation_builder.build(&provider).unwrap();
|
||||
|
||||
// Prover validates the attestation is consistent with its request.
|
||||
request.validate(&attestation).unwrap();
|
||||
request.validate(&attestation, &provider).unwrap();
|
||||
|
||||
let mut transcript_proof_builder = secrets.transcript_proof_builder();
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ description = "This crate provides implementations of ciphers for two parties"
|
||||
keywords = ["tls", "mpc", "2pc", "aes"]
|
||||
categories = ["cryptography"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
version = "0.1.0-alpha.13-pre"
|
||||
version = "0.1.0-alpha.14-pre"
|
||||
edition = "2021"
|
||||
|
||||
[lints]
|
||||
@@ -15,7 +15,7 @@ workspace = true
|
||||
name = "cipher"
|
||||
|
||||
[dependencies]
|
||||
mpz-circuits = { workspace = true }
|
||||
mpz-circuits = { workspace = true, features = ["aes"] }
|
||||
mpz-vm-core = { workspace = true }
|
||||
mpz-memory-core = { workspace = true }
|
||||
|
||||
@@ -24,11 +24,9 @@ thiserror = { workspace = true }
|
||||
aes = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
mpz-garble = { workspace = true }
|
||||
mpz-common = { workspace = true }
|
||||
mpz-ot = { workspace = true }
|
||||
mpz-common = { workspace = true, features = ["test-utils"] }
|
||||
mpz-ideal-vm = { workspace = true }
|
||||
|
||||
tokio = { version = "1", features = ["macros", "rt", "rt-multi-thread"] }
|
||||
rand = { workspace = true }
|
||||
ctr = { workspace = true }
|
||||
cipher = { workspace = true }
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
use crate::{Cipher, CtrBlock, Keystream};
|
||||
use async_trait::async_trait;
|
||||
use mpz_circuits::circuits::AES128;
|
||||
use mpz_circuits::{AES128_KS, AES128_POST_KS};
|
||||
use mpz_memory_core::binary::{Binary, U8};
|
||||
use mpz_vm_core::{prelude::*, Call, Vm};
|
||||
use std::fmt::Debug;
|
||||
@@ -12,13 +12,35 @@ mod error;
|
||||
pub use error::AesError;
|
||||
use error::ErrorKind;
|
||||
|
||||
/// AES key schedule: 11 round keys, 16 bytes each.
|
||||
type KeySchedule = Array<U8, 176>;
|
||||
|
||||
/// Computes AES-128.
|
||||
#[derive(Default, Debug)]
|
||||
pub struct Aes128 {
|
||||
key: Option<Array<U8, 16>>,
|
||||
key_schedule: Option<KeySchedule>,
|
||||
iv: Option<Array<U8, 4>>,
|
||||
}
|
||||
|
||||
impl Aes128 {
|
||||
// Allocates key schedule.
|
||||
//
|
||||
// Expects the key to be already set.
|
||||
fn alloc_key_schedule(&self, vm: &mut dyn Vm<Binary>) -> Result<KeySchedule, AesError> {
|
||||
let ks: KeySchedule = vm
|
||||
.call(
|
||||
Call::builder(AES128_KS.clone())
|
||||
.arg(self.key.expect("key is set"))
|
||||
.build()
|
||||
.expect("call should be valid"),
|
||||
)
|
||||
.map_err(|err| AesError::new(ErrorKind::Vm, err))?;
|
||||
|
||||
Ok(ks)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Cipher for Aes128 {
|
||||
type Error = AesError;
|
||||
@@ -45,18 +67,22 @@ impl Cipher for Aes128 {
|
||||
}
|
||||
|
||||
fn alloc_block(
|
||||
&self,
|
||||
&mut self,
|
||||
vm: &mut dyn Vm<Binary>,
|
||||
input: Array<U8, 16>,
|
||||
) -> Result<Self::Block, Self::Error> {
|
||||
let key = self
|
||||
.key
|
||||
self.key
|
||||
.ok_or_else(|| AesError::new(ErrorKind::Key, "key not set"))?;
|
||||
|
||||
if self.key_schedule.is_none() {
|
||||
self.key_schedule = Some(self.alloc_key_schedule(vm)?);
|
||||
}
|
||||
let ks = *self.key_schedule.as_ref().expect("key schedule was set");
|
||||
|
||||
let output = vm
|
||||
.call(
|
||||
Call::builder(AES128.clone())
|
||||
.arg(key)
|
||||
Call::builder(AES128_POST_KS.clone())
|
||||
.arg(ks)
|
||||
.arg(input)
|
||||
.build()
|
||||
.expect("call should be valid"),
|
||||
@@ -67,11 +93,10 @@ impl Cipher for Aes128 {
|
||||
}
|
||||
|
||||
fn alloc_ctr_block(
|
||||
&self,
|
||||
&mut self,
|
||||
vm: &mut dyn Vm<Binary>,
|
||||
) -> Result<CtrBlock<Self::Nonce, Self::Counter, Self::Block>, Self::Error> {
|
||||
let key = self
|
||||
.key
|
||||
self.key
|
||||
.ok_or_else(|| AesError::new(ErrorKind::Key, "key not set"))?;
|
||||
let iv = self
|
||||
.iv
|
||||
@@ -89,10 +114,15 @@ impl Cipher for Aes128 {
|
||||
vm.mark_public(counter)
|
||||
.map_err(|err| AesError::new(ErrorKind::Vm, err))?;
|
||||
|
||||
if self.key_schedule.is_none() {
|
||||
self.key_schedule = Some(self.alloc_key_schedule(vm)?);
|
||||
}
|
||||
let ks = *self.key_schedule.as_ref().expect("key schedule was set");
|
||||
|
||||
let output = vm
|
||||
.call(
|
||||
Call::builder(AES128.clone())
|
||||
.arg(key)
|
||||
Call::builder(AES128_POST_KS.clone())
|
||||
.arg(ks)
|
||||
.arg(iv)
|
||||
.arg(explicit_nonce)
|
||||
.arg(counter)
|
||||
@@ -109,12 +139,11 @@ impl Cipher for Aes128 {
|
||||
}
|
||||
|
||||
fn alloc_keystream(
|
||||
&self,
|
||||
&mut self,
|
||||
vm: &mut dyn Vm<Binary>,
|
||||
len: usize,
|
||||
) -> Result<Keystream<Self::Nonce, Self::Counter, Self::Block>, Self::Error> {
|
||||
let key = self
|
||||
.key
|
||||
self.key
|
||||
.ok_or_else(|| AesError::new(ErrorKind::Key, "key not set"))?;
|
||||
let iv = self
|
||||
.iv
|
||||
@@ -143,10 +172,15 @@ impl Cipher for Aes128 {
|
||||
let blocks = inputs
|
||||
.into_iter()
|
||||
.map(|(explicit_nonce, counter)| {
|
||||
if self.key_schedule.is_none() {
|
||||
self.key_schedule = Some(self.alloc_key_schedule(vm)?);
|
||||
}
|
||||
let ks = *self.key_schedule.as_ref().expect("key schedule was set");
|
||||
|
||||
let output = vm
|
||||
.call(
|
||||
Call::builder(AES128.clone())
|
||||
.arg(key)
|
||||
Call::builder(AES128_POST_KS.clone())
|
||||
.arg(ks)
|
||||
.arg(iv)
|
||||
.arg(explicit_nonce)
|
||||
.arg(counter)
|
||||
@@ -172,15 +206,12 @@ mod tests {
|
||||
use super::*;
|
||||
use crate::Cipher;
|
||||
use mpz_common::context::test_st_context;
|
||||
use mpz_garble::protocol::semihonest::{Evaluator, Garbler};
|
||||
use mpz_ideal_vm::IdealVm;
|
||||
use mpz_memory_core::{
|
||||
binary::{Binary, U8},
|
||||
correlated::Delta,
|
||||
Array, MemoryExt, Vector, ViewExt,
|
||||
};
|
||||
use mpz_ot::ideal::cot::ideal_cot;
|
||||
use mpz_vm_core::{Execute, Vm};
|
||||
use rand::{rngs::StdRng, SeedableRng};
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_aes_ctr() {
|
||||
@@ -190,10 +221,11 @@ mod tests {
|
||||
let start_counter = 3u32;
|
||||
|
||||
let (mut ctx_a, mut ctx_b) = test_st_context(8);
|
||||
let (mut gen, mut ev) = mock_vm();
|
||||
let mut gen = IdealVm::new();
|
||||
let mut ev = IdealVm::new();
|
||||
|
||||
let aes_gen = setup_ctr(key, iv, &mut gen);
|
||||
let aes_ev = setup_ctr(key, iv, &mut ev);
|
||||
let mut aes_gen = setup_ctr(key, iv, &mut gen);
|
||||
let mut aes_ev = setup_ctr(key, iv, &mut ev);
|
||||
|
||||
let msg = vec![42u8; 128];
|
||||
|
||||
@@ -252,10 +284,11 @@ mod tests {
|
||||
let input = [5_u8; 16];
|
||||
|
||||
let (mut ctx_a, mut ctx_b) = test_st_context(8);
|
||||
let (mut gen, mut ev) = mock_vm();
|
||||
let mut gen = IdealVm::new();
|
||||
let mut ev = IdealVm::new();
|
||||
|
||||
let aes_gen = setup_block(key, &mut gen);
|
||||
let aes_ev = setup_block(key, &mut ev);
|
||||
let mut aes_gen = setup_block(key, &mut gen);
|
||||
let mut aes_ev = setup_block(key, &mut ev);
|
||||
|
||||
let block_ref_gen: Array<U8, 16> = gen.alloc().unwrap();
|
||||
gen.mark_public(block_ref_gen).unwrap();
|
||||
@@ -294,18 +327,6 @@ mod tests {
|
||||
assert_eq!(ciphertext_gen, expected);
|
||||
}
|
||||
|
||||
fn mock_vm() -> (impl Vm<Binary>, impl Vm<Binary>) {
|
||||
let mut rng = StdRng::seed_from_u64(0);
|
||||
let delta = Delta::random(&mut rng);
|
||||
|
||||
let (cot_send, cot_recv) = ideal_cot(delta.into_inner());
|
||||
|
||||
let gen = Garbler::new(cot_send, [0u8; 16], delta);
|
||||
let ev = Evaluator::new(cot_recv);
|
||||
|
||||
(gen, ev)
|
||||
}
|
||||
|
||||
fn setup_ctr(key: [u8; 16], iv: [u8; 4], vm: &mut dyn Vm<Binary>) -> Aes128 {
|
||||
let key_ref: Array<U8, 16> = vm.alloc().unwrap();
|
||||
vm.mark_public(key_ref).unwrap();
|
||||
|
||||
@@ -55,7 +55,7 @@ pub trait Cipher {
|
||||
|
||||
/// Allocates a single block in ECB mode.
|
||||
fn alloc_block(
|
||||
&self,
|
||||
&mut self,
|
||||
vm: &mut dyn Vm<Binary>,
|
||||
input: Self::Block,
|
||||
) -> Result<Self::Block, Self::Error>;
|
||||
@@ -63,7 +63,7 @@ pub trait Cipher {
|
||||
/// Allocates a single block in counter mode.
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn alloc_ctr_block(
|
||||
&self,
|
||||
&mut self,
|
||||
vm: &mut dyn Vm<Binary>,
|
||||
) -> Result<CtrBlock<Self::Nonce, Self::Counter, Self::Block>, Self::Error>;
|
||||
|
||||
@@ -75,7 +75,7 @@ pub trait Cipher {
|
||||
/// * `len` - Length of the stream in bytes.
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn alloc_keystream(
|
||||
&self,
|
||||
&mut self,
|
||||
vm: &mut dyn Vm<Binary>,
|
||||
len: usize,
|
||||
) -> Result<Keystream<Self::Nonce, Self::Counter, Self::Block>, Self::Error>;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "tlsn-deap"
|
||||
version = "0.1.0-alpha.13-pre"
|
||||
version = "0.1.0-alpha.14-pre"
|
||||
edition = "2021"
|
||||
|
||||
[lints]
|
||||
@@ -19,11 +19,8 @@ futures = { workspace = true }
|
||||
tokio = { workspace = true, features = ["sync"] }
|
||||
|
||||
[dev-dependencies]
|
||||
mpz-circuits = { workspace = true }
|
||||
mpz-garble = { workspace = true }
|
||||
mpz-ot = { workspace = true }
|
||||
mpz-zk = { workspace = true }
|
||||
mpz-circuits = { workspace = true, features = ["aes"] }
|
||||
mpz-common = { workspace = true, features = ["test-utils"] }
|
||||
mpz-ideal-vm = { workspace = true }
|
||||
|
||||
tokio = { workspace = true, features = ["macros", "rt", "rt-multi-thread"] }
|
||||
rand = { workspace = true }
|
||||
rand06-compat = { workspace = true }
|
||||
|
||||
@@ -15,7 +15,7 @@ use mpz_vm_core::{
|
||||
memory::{binary::Binary, DecodeFuture, Memory, Repr, Slice, View},
|
||||
Call, Callable, Execute, Vm, VmError,
|
||||
};
|
||||
use rangeset::{Difference, RangeSet, UnionMut};
|
||||
use rangeset::{ops::Set, set::RangeSet};
|
||||
use tokio::sync::{Mutex, MutexGuard, OwnedMutexGuard};
|
||||
|
||||
type Error = DeapError;
|
||||
@@ -210,10 +210,12 @@ where
|
||||
}
|
||||
|
||||
fn commit_raw(&mut self, slice: Slice) -> Result<(), VmError> {
|
||||
let slice_range = slice.to_range();
|
||||
|
||||
// Follower's private inputs are not committed in the ZK VM until finalization.
|
||||
let input_minus_follower = slice.to_range().difference(&self.follower_input_ranges);
|
||||
let input_minus_follower = slice_range.difference(&self.follower_input_ranges);
|
||||
let mut zk = self.zk.try_lock().unwrap();
|
||||
for input in input_minus_follower.iter_ranges() {
|
||||
for input in input_minus_follower {
|
||||
zk.commit_raw(
|
||||
self.memory_map
|
||||
.try_get(Slice::from_range_unchecked(input))?,
|
||||
@@ -266,7 +268,7 @@ where
|
||||
mpc.mark_private_raw(slice)?;
|
||||
// Follower's private inputs will become public during finalization.
|
||||
zk.mark_public_raw(self.memory_map.try_get(slice)?)?;
|
||||
self.follower_input_ranges.union_mut(&slice.to_range());
|
||||
self.follower_input_ranges.union_mut(slice.to_range());
|
||||
self.follower_inputs.push(slice);
|
||||
}
|
||||
}
|
||||
@@ -282,7 +284,7 @@ where
|
||||
mpc.mark_blind_raw(slice)?;
|
||||
// Follower's private inputs will become public during finalization.
|
||||
zk.mark_public_raw(self.memory_map.try_get(slice)?)?;
|
||||
self.follower_input_ranges.union_mut(&slice.to_range());
|
||||
self.follower_input_ranges.union_mut(slice.to_range());
|
||||
self.follower_inputs.push(slice);
|
||||
}
|
||||
Role::Follower => {
|
||||
@@ -382,37 +384,27 @@ enum ErrorRepr {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use mpz_circuits::circuits::AES128;
|
||||
use mpz_circuits::AES128;
|
||||
use mpz_common::context::test_st_context;
|
||||
use mpz_core::Block;
|
||||
use mpz_garble::protocol::semihonest::{Evaluator, Garbler};
|
||||
use mpz_ot::ideal::{cot::ideal_cot, rcot::ideal_rcot};
|
||||
use mpz_ideal_vm::IdealVm;
|
||||
use mpz_vm_core::{
|
||||
memory::{binary::U8, correlated::Delta, Array},
|
||||
memory::{binary::U8, Array},
|
||||
prelude::*,
|
||||
};
|
||||
use mpz_zk::{Prover, Verifier};
|
||||
use rand::{rngs::StdRng, SeedableRng};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_deap() {
|
||||
let mut rng = StdRng::seed_from_u64(0);
|
||||
let delta_mpc = Delta::random(&mut rng);
|
||||
let delta_zk = Delta::random(&mut rng);
|
||||
|
||||
let (mut ctx_a, mut ctx_b) = test_st_context(8);
|
||||
let (rcot_send, rcot_recv) = ideal_rcot(Block::ZERO, delta_zk.into_inner());
|
||||
let (cot_send, cot_recv) = ideal_cot(delta_mpc.into_inner());
|
||||
|
||||
let gb = Garbler::new(cot_send, [0u8; 16], delta_mpc);
|
||||
let ev = Evaluator::new(cot_recv);
|
||||
let prover = Prover::new(rcot_recv);
|
||||
let verifier = Verifier::new(delta_zk, rcot_send);
|
||||
let leader_mpc = IdealVm::new();
|
||||
let leader_zk = IdealVm::new();
|
||||
let follower_mpc = IdealVm::new();
|
||||
let follower_zk = IdealVm::new();
|
||||
|
||||
let mut leader = Deap::new(Role::Leader, gb, prover);
|
||||
let mut follower = Deap::new(Role::Follower, ev, verifier);
|
||||
let mut leader = Deap::new(Role::Leader, leader_mpc, leader_zk);
|
||||
let mut follower = Deap::new(Role::Follower, follower_mpc, follower_zk);
|
||||
|
||||
let (ct_leader, ct_follower) = futures::join!(
|
||||
async {
|
||||
@@ -478,21 +470,15 @@ mod tests {
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_deap_desync_memory() {
|
||||
let mut rng = StdRng::seed_from_u64(0);
|
||||
let delta_mpc = Delta::random(&mut rng);
|
||||
let delta_zk = Delta::random(&mut rng);
|
||||
|
||||
let (mut ctx_a, mut ctx_b) = test_st_context(8);
|
||||
let (rcot_send, rcot_recv) = ideal_rcot(Block::ZERO, delta_zk.into_inner());
|
||||
let (cot_send, cot_recv) = ideal_cot(delta_mpc.into_inner());
|
||||
|
||||
let gb = Garbler::new(cot_send, [0u8; 16], delta_mpc);
|
||||
let ev = Evaluator::new(cot_recv);
|
||||
let prover = Prover::new(rcot_recv);
|
||||
let verifier = Verifier::new(delta_zk, rcot_send);
|
||||
let leader_mpc = IdealVm::new();
|
||||
let leader_zk = IdealVm::new();
|
||||
let follower_mpc = IdealVm::new();
|
||||
let follower_zk = IdealVm::new();
|
||||
|
||||
let mut leader = Deap::new(Role::Leader, gb, prover);
|
||||
let mut follower = Deap::new(Role::Follower, ev, verifier);
|
||||
let mut leader = Deap::new(Role::Leader, leader_mpc, leader_zk);
|
||||
let mut follower = Deap::new(Role::Follower, follower_mpc, follower_zk);
|
||||
|
||||
// Desynchronize the memories.
|
||||
let _ = leader.zk().alloc_raw(1).unwrap();
|
||||
@@ -564,21 +550,15 @@ mod tests {
|
||||
// detection by the follower.
|
||||
#[tokio::test]
|
||||
async fn test_malicious() {
|
||||
let mut rng = StdRng::seed_from_u64(0);
|
||||
let delta_mpc = Delta::random(&mut rng);
|
||||
let delta_zk = Delta::random(&mut rng);
|
||||
|
||||
let (mut ctx_a, mut ctx_b) = test_st_context(8);
|
||||
let (rcot_send, rcot_recv) = ideal_rcot(Block::ZERO, delta_zk.into_inner());
|
||||
let (cot_send, cot_recv) = ideal_cot(delta_mpc.into_inner());
|
||||
|
||||
let gb = Garbler::new(cot_send, [1u8; 16], delta_mpc);
|
||||
let ev = Evaluator::new(cot_recv);
|
||||
let prover = Prover::new(rcot_recv);
|
||||
let verifier = Verifier::new(delta_zk, rcot_send);
|
||||
let leader_mpc = IdealVm::new();
|
||||
let leader_zk = IdealVm::new();
|
||||
let follower_mpc = IdealVm::new();
|
||||
let follower_zk = IdealVm::new();
|
||||
|
||||
let mut leader = Deap::new(Role::Leader, gb, prover);
|
||||
let mut follower = Deap::new(Role::Follower, ev, verifier);
|
||||
let mut leader = Deap::new(Role::Leader, leader_mpc, leader_zk);
|
||||
let mut follower = Deap::new(Role::Follower, follower_mpc, follower_zk);
|
||||
|
||||
let (_, follower_res) = futures::join!(
|
||||
async {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::ops::Range;
|
||||
|
||||
use mpz_vm_core::{memory::Slice, VmError};
|
||||
use rangeset::Subset;
|
||||
use rangeset::ops::Set;
|
||||
|
||||
/// A mapping between the memories of the MPC and ZK VMs.
|
||||
#[derive(Debug, Default)]
|
||||
|
||||
@@ -5,7 +5,7 @@ description = "A 2PC implementation of TLS HMAC-SHA256 PRF"
|
||||
keywords = ["tls", "mpc", "2pc", "hmac", "sha256"]
|
||||
categories = ["cryptography"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
version = "0.1.0-alpha.13-pre"
|
||||
version = "0.1.0-alpha.14-pre"
|
||||
edition = "2021"
|
||||
|
||||
[lints]
|
||||
@@ -20,14 +20,13 @@ mpz-core = { workspace = true }
|
||||
mpz-circuits = { workspace = true }
|
||||
mpz-hash = { workspace = true }
|
||||
|
||||
sha2 = { workspace = true, features = ["compress"] }
|
||||
thiserror = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
sha2 = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
mpz-ot = { workspace = true, features = ["ideal"] }
|
||||
mpz-garble = { workspace = true }
|
||||
mpz-common = { workspace = true, features = ["test-utils"] }
|
||||
mpz-ideal-vm = { workspace = true }
|
||||
|
||||
criterion = { workspace = true, features = ["async_tokio"] }
|
||||
tokio = { workspace = true, features = ["macros", "rt", "rt-multi-thread"] }
|
||||
|
||||
@@ -4,14 +4,12 @@ use criterion::{criterion_group, criterion_main, Criterion};
|
||||
|
||||
use hmac_sha256::{Mode, MpcPrf};
|
||||
use mpz_common::context::test_mt_context;
|
||||
use mpz_garble::protocol::semihonest::{Evaluator, Garbler};
|
||||
use mpz_ot::ideal::cot::ideal_cot;
|
||||
use mpz_ideal_vm::IdealVm;
|
||||
use mpz_vm_core::{
|
||||
memory::{binary::U8, correlated::Delta, Array},
|
||||
memory::{binary::U8, Array},
|
||||
prelude::*,
|
||||
Execute,
|
||||
};
|
||||
use rand::{rngs::StdRng, SeedableRng};
|
||||
|
||||
#[allow(clippy::unit_arg)]
|
||||
fn criterion_benchmark(c: &mut Criterion) {
|
||||
@@ -29,8 +27,6 @@ criterion_group!(benches, criterion_benchmark);
|
||||
criterion_main!(benches);
|
||||
|
||||
async fn prf(mode: Mode) {
|
||||
let mut rng = StdRng::seed_from_u64(0);
|
||||
|
||||
let pms = [42u8; 32];
|
||||
let client_random = [69u8; 32];
|
||||
let server_random: [u8; 32] = [96u8; 32];
|
||||
@@ -39,11 +35,8 @@ async fn prf(mode: Mode) {
|
||||
let mut leader_ctx = leader_exec.new_context().await.unwrap();
|
||||
let mut follower_ctx = follower_exec.new_context().await.unwrap();
|
||||
|
||||
let delta = Delta::random(&mut rng);
|
||||
let (ot_send, ot_recv) = ideal_cot(delta.into_inner());
|
||||
|
||||
let mut leader_vm = Garbler::new(ot_send, [0u8; 16], delta);
|
||||
let mut follower_vm = Evaluator::new(ot_recv);
|
||||
let mut leader_vm = IdealVm::new();
|
||||
let mut follower_vm = IdealVm::new();
|
||||
|
||||
let leader_pms: Array<U8, 32> = leader_vm.alloc().unwrap();
|
||||
leader_vm.mark_public(leader_pms).unwrap();
|
||||
|
||||
@@ -54,10 +54,11 @@ mod tests {
|
||||
use crate::{
|
||||
hmac::hmac_sha256,
|
||||
sha256, state_to_bytes,
|
||||
test_utils::{compute_inner_local, compute_outer_partial, mock_vm},
|
||||
test_utils::{compute_inner_local, compute_outer_partial},
|
||||
};
|
||||
use mpz_common::context::test_st_context;
|
||||
use mpz_hash::sha256::Sha256;
|
||||
use mpz_ideal_vm::IdealVm;
|
||||
use mpz_vm_core::{
|
||||
memory::{
|
||||
binary::{U32, U8},
|
||||
@@ -83,7 +84,8 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn test_hmac_circuit() {
|
||||
let (mut ctx_a, mut ctx_b) = test_st_context(8);
|
||||
let (mut leader, mut follower) = mock_vm();
|
||||
let mut leader = IdealVm::new();
|
||||
let mut follower = IdealVm::new();
|
||||
|
||||
let (inputs, references) = test_fixtures();
|
||||
for (input, &reference) in inputs.iter().zip(references.iter()) {
|
||||
|
||||
@@ -72,10 +72,11 @@ fn state_to_bytes(input: [u32; 8]) -> [u8; 32] {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{
|
||||
test_utils::{mock_vm, prf_cf_vd, prf_keys, prf_ms, prf_sf_vd},
|
||||
test_utils::{prf_cf_vd, prf_keys, prf_ms, prf_sf_vd},
|
||||
Mode, MpcPrf, SessionKeys,
|
||||
};
|
||||
use mpz_common::context::test_st_context;
|
||||
use mpz_ideal_vm::IdealVm;
|
||||
use mpz_vm_core::{
|
||||
memory::{binary::U8, Array, MemoryExt, ViewExt},
|
||||
Execute,
|
||||
@@ -123,7 +124,8 @@ mod tests {
|
||||
|
||||
// Set up vm and prf
|
||||
let (mut ctx_a, mut ctx_b) = test_st_context(128);
|
||||
let (mut leader, mut follower) = mock_vm();
|
||||
let mut leader = IdealVm::new();
|
||||
let mut follower = IdealVm::new();
|
||||
|
||||
let leader_pms: Array<U8, 32> = leader.alloc().unwrap();
|
||||
leader.mark_public(leader_pms).unwrap();
|
||||
|
||||
@@ -339,8 +339,9 @@ fn gen_merge_circ(size: usize) -> Arc<Circuit> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{prf::merge_outputs, test_utils::mock_vm};
|
||||
use crate::prf::merge_outputs;
|
||||
use mpz_common::context::test_st_context;
|
||||
use mpz_ideal_vm::IdealVm;
|
||||
use mpz_vm_core::{
|
||||
memory::{binary::U8, Array, MemoryExt, ViewExt},
|
||||
Execute,
|
||||
@@ -349,7 +350,8 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn test_merge_outputs() {
|
||||
let (mut ctx_a, mut ctx_b) = test_st_context(8);
|
||||
let (mut leader, mut follower) = mock_vm();
|
||||
let mut leader = IdealVm::new();
|
||||
let mut follower = IdealVm::new();
|
||||
|
||||
let input1: [u8; 32] = std::array::from_fn(|i| i as u8);
|
||||
let input2: [u8; 32] = std::array::from_fn(|i| i as u8 + 32);
|
||||
|
||||
@@ -137,10 +137,11 @@ impl Prf {
|
||||
mod tests {
|
||||
use crate::{
|
||||
prf::{compute_partial, function::Prf},
|
||||
test_utils::{mock_vm, phash},
|
||||
test_utils::phash,
|
||||
Mode,
|
||||
};
|
||||
use mpz_common::context::test_st_context;
|
||||
use mpz_ideal_vm::IdealVm;
|
||||
use mpz_vm_core::{
|
||||
memory::{binary::U8, Array, MemoryExt, ViewExt},
|
||||
Execute,
|
||||
@@ -166,7 +167,8 @@ mod tests {
|
||||
let mut rng = ThreadRng::default();
|
||||
|
||||
let (mut ctx_a, mut ctx_b) = test_st_context(8);
|
||||
let (mut leader, mut follower) = mock_vm();
|
||||
let mut leader = IdealVm::new();
|
||||
let mut follower = IdealVm::new();
|
||||
|
||||
let key: [u8; 32] = rng.random();
|
||||
let start_seed: Vec<u8> = vec![42; 64];
|
||||
|
||||
@@ -40,7 +40,6 @@ enum PrfState {
|
||||
inner_partial: [u32; 8],
|
||||
a_output: DecodeFutureTyped<BitVec, [u8; 32]>,
|
||||
},
|
||||
FinishLastP,
|
||||
Done,
|
||||
}
|
||||
|
||||
@@ -137,16 +136,18 @@ impl PrfFunction {
|
||||
assign_inner_local(vm, p.inner_local, *inner_partial, &msg)?;
|
||||
|
||||
if *iter == self.iterations {
|
||||
self.state = PrfState::FinishLastP;
|
||||
self.state = PrfState::Done;
|
||||
} else {
|
||||
self.state = PrfState::ComputeA {
|
||||
iter: *iter + 1,
|
||||
inner_partial: *inner_partial,
|
||||
msg: output.to_vec(),
|
||||
}
|
||||
};
|
||||
};
|
||||
// We recurse, so that this PHash and the next AHash could
|
||||
// be computed in a single VM execute call.
|
||||
self.flush(vm)?;
|
||||
}
|
||||
}
|
||||
PrfState::FinishLastP => self.state = PrfState::Done,
|
||||
_ => (),
|
||||
}
|
||||
|
||||
|
||||
@@ -1,25 +1,10 @@
|
||||
use crate::{sha256, state_to_bytes};
|
||||
use mpz_garble::protocol::semihonest::{Evaluator, Garbler};
|
||||
use mpz_ot::ideal::cot::{ideal_cot, IdealCOTReceiver, IdealCOTSender};
|
||||
use mpz_vm_core::memory::correlated::Delta;
|
||||
use rand::{rngs::StdRng, Rng, SeedableRng};
|
||||
|
||||
pub(crate) const SHA256_IV: [u32; 8] = [
|
||||
0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19,
|
||||
];
|
||||
|
||||
pub(crate) fn mock_vm() -> (Garbler<IdealCOTSender>, Evaluator<IdealCOTReceiver>) {
|
||||
let mut rng = StdRng::seed_from_u64(0);
|
||||
let delta = Delta::random(&mut rng);
|
||||
|
||||
let (cot_send, cot_recv) = ideal_cot(delta.into_inner());
|
||||
|
||||
let gen = Garbler::new(cot_send, [0u8; 16], delta);
|
||||
let ev = Evaluator::new(cot_recv);
|
||||
|
||||
(gen, ev)
|
||||
}
|
||||
|
||||
pub(crate) fn prf_ms(pms: [u8; 32], client_random: [u8; 32], server_random: [u8; 32]) -> [u8; 48] {
|
||||
let mut label_start_seed = b"master secret".to_vec();
|
||||
label_start_seed.extend_from_slice(&client_random);
|
||||
|
||||
@@ -5,7 +5,7 @@ description = "Implementation of the 3-party key-exchange protocol"
|
||||
keywords = ["tls", "mpc", "2pc", "pms", "key-exchange"]
|
||||
categories = ["cryptography"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
version = "0.1.0-alpha.13-pre"
|
||||
version = "0.1.0-alpha.14-pre"
|
||||
edition = "2021"
|
||||
|
||||
[lints]
|
||||
@@ -40,6 +40,7 @@ tokio = { workspace = true, features = ["sync"] }
|
||||
[dev-dependencies]
|
||||
mpz-ot = { workspace = true, features = ["ideal"] }
|
||||
mpz-garble = { workspace = true }
|
||||
mpz-ideal-vm = { workspace = true }
|
||||
|
||||
rand_core = { workspace = true }
|
||||
tokio = { workspace = true, features = ["macros", "rt", "rt-multi-thread"] }
|
||||
|
||||
@@ -459,9 +459,7 @@ mod tests {
|
||||
use mpz_common::context::test_st_context;
|
||||
use mpz_core::Block;
|
||||
use mpz_fields::UniformRand;
|
||||
use mpz_garble::protocol::semihonest::{Evaluator, Garbler};
|
||||
use mpz_memory_core::correlated::Delta;
|
||||
use mpz_ot::ideal::cot::{ideal_cot, IdealCOTReceiver, IdealCOTSender};
|
||||
use mpz_ideal_vm::IdealVm;
|
||||
use mpz_share_conversion::ideal::{
|
||||
ideal_share_convert, IdealShareConvertReceiver, IdealShareConvertSender,
|
||||
};
|
||||
@@ -484,7 +482,8 @@ mod tests {
|
||||
async fn test_key_exchange() {
|
||||
let mut rng = StdRng::seed_from_u64(0).compat();
|
||||
let (mut ctx_a, mut ctx_b) = test_st_context(8);
|
||||
let (mut gen, mut ev) = mock_vm();
|
||||
let mut gen = IdealVm::new();
|
||||
let mut ev = IdealVm::new();
|
||||
|
||||
let leader_private_key = SecretKey::random(&mut rng);
|
||||
let follower_private_key = SecretKey::random(&mut rng);
|
||||
@@ -625,7 +624,8 @@ mod tests {
|
||||
async fn test_malicious_key_exchange(#[case] malicious: Malicious) {
|
||||
let mut rng = StdRng::seed_from_u64(0);
|
||||
let (mut ctx_a, mut ctx_b) = test_st_context(8);
|
||||
let (mut gen, mut ev) = mock_vm();
|
||||
let mut gen = IdealVm::new();
|
||||
let mut ev = IdealVm::new();
|
||||
|
||||
let leader_private_key = SecretKey::random(&mut rng.compat_by_ref());
|
||||
let follower_private_key = SecretKey::random(&mut rng.compat_by_ref());
|
||||
@@ -704,7 +704,8 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn test_circuit() {
|
||||
let (mut ctx_a, mut ctx_b) = test_st_context(8);
|
||||
let (gen, ev) = mock_vm();
|
||||
let gen = IdealVm::new();
|
||||
let ev = IdealVm::new();
|
||||
|
||||
let share_a0_bytes = [5_u8; 32];
|
||||
let share_a1_bytes = [2_u8; 32];
|
||||
@@ -834,16 +835,4 @@ mod tests {
|
||||
|
||||
(leader, follower)
|
||||
}
|
||||
|
||||
fn mock_vm() -> (Garbler<IdealCOTSender>, Evaluator<IdealCOTReceiver>) {
|
||||
let mut rng = StdRng::seed_from_u64(0);
|
||||
let delta = Delta::random(&mut rng);
|
||||
|
||||
let (cot_send, cot_recv) = ideal_cot(delta.into_inner());
|
||||
|
||||
let gen = Garbler::new(cot_send, [0u8; 16], delta);
|
||||
let ev = Evaluator::new(cot_recv);
|
||||
|
||||
(gen, ev)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
//! with the server alone and forward all messages from and to the follower.
|
||||
//!
|
||||
//! A detailed description of this protocol can be found in our documentation
|
||||
//! <https://docs.tlsnotary.org/protocol/notarization/key_exchange.html>.
|
||||
//! <https://tlsnotary.org/docs/mpc/key_exchange>.
|
||||
|
||||
#![deny(missing_docs, unreachable_pub, unused_must_use)]
|
||||
#![deny(clippy::all)]
|
||||
|
||||
@@ -26,8 +26,7 @@ pub fn create_mock_key_exchange_pair() -> (MockKeyExchange, MockKeyExchange) {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use mpz_garble::protocol::semihonest::{Evaluator, Garbler};
|
||||
use mpz_ot::ideal::cot::{IdealCOTReceiver, IdealCOTSender};
|
||||
use mpz_ideal_vm::IdealVm;
|
||||
|
||||
use super::*;
|
||||
use crate::KeyExchange;
|
||||
@@ -40,12 +39,12 @@ mod tests {
|
||||
|
||||
is_key_exchange::<
|
||||
MpcKeyExchange<IdealShareConvertSender<P256>, IdealShareConvertReceiver<P256>>,
|
||||
Garbler<IdealCOTSender>,
|
||||
IdealVm,
|
||||
>(leader);
|
||||
|
||||
is_key_exchange::<
|
||||
MpcKeyExchange<IdealShareConvertSender<P256>, IdealShareConvertReceiver<P256>>,
|
||||
Evaluator<IdealCOTReceiver>,
|
||||
IdealVm,
|
||||
>(follower);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
//! protocol has semi-honest security.
|
||||
//!
|
||||
//! The protocol is described in
|
||||
//! <https://docs.tlsnotary.org/protocol/notarization/key_exchange.html>
|
||||
//! <https://tlsnotary.org/docs/mpc/key_exchange>
|
||||
|
||||
use crate::{KeyExchangeError, Role};
|
||||
use mpz_common::{Context, Flush};
|
||||
|
||||
@@ -5,7 +5,7 @@ description = "Core types for TLSNotary"
|
||||
keywords = ["tls", "mpc", "2pc", "types"]
|
||||
categories = ["cryptography"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
version = "0.1.0-alpha.13-pre"
|
||||
version = "0.1.0-alpha.14-pre"
|
||||
edition = "2021"
|
||||
|
||||
[lints]
|
||||
@@ -13,7 +13,14 @@ workspace = true
|
||||
|
||||
[features]
|
||||
default = []
|
||||
fixtures = ["dep:hex", "dep:tlsn-data-fixtures"]
|
||||
mozilla-certs = ["dep:webpki-root-certs", "dep:webpki-roots"]
|
||||
fixtures = [
|
||||
"dep:hex",
|
||||
"dep:tlsn-data-fixtures",
|
||||
"dep:aead",
|
||||
"dep:aes-gcm",
|
||||
"dep:generic-array",
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
tlsn-data-fixtures = { workspace = true, optional = true }
|
||||
@@ -21,6 +28,9 @@ tlsn-tls-core = { workspace = true, features = ["serde"] }
|
||||
tlsn-utils = { workspace = true }
|
||||
rangeset = { workspace = true, features = ["serde"] }
|
||||
|
||||
aead = { workspace = true, features = ["alloc"], optional = true }
|
||||
aes-gcm = { workspace = true, optional = true }
|
||||
generic-array = { workspace = true, optional = true }
|
||||
bimap = { version = "0.6", features = ["serde"] }
|
||||
blake3 = { workspace = true }
|
||||
hex = { workspace = true, optional = true }
|
||||
@@ -35,11 +45,21 @@ sha2 = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
tiny-keccak = { workspace = true, features = ["keccak"] }
|
||||
web-time = { workspace = true }
|
||||
webpki-roots = { workspace = true }
|
||||
webpki-roots = { workspace = true, optional = true }
|
||||
webpki-root-certs = { workspace = true, optional = true }
|
||||
rustls-webpki = { workspace = true, features = ["ring"] }
|
||||
rustls-pki-types = { workspace = true }
|
||||
itybity = { workspace = true }
|
||||
zeroize = { workspace = true, features = ["zeroize_derive"] }
|
||||
|
||||
[dev-dependencies]
|
||||
aead = { workspace = true, features = ["alloc"] }
|
||||
aes-gcm = { workspace = true }
|
||||
generic-array = { workspace = true }
|
||||
bincode = { workspace = true }
|
||||
hex = { workspace = true }
|
||||
rstest = { workspace = true }
|
||||
tlsn-core = { workspace = true, features = ["fixtures"] }
|
||||
tlsn-attestation = { workspace = true, features = ["fixtures"] }
|
||||
tlsn-data-fixtures = { workspace = true }
|
||||
webpki-root-certs = { workspace = true }
|
||||
|
||||
7
crates/core/src/config.rs
Normal file
7
crates/core/src/config.rs
Normal file
@@ -0,0 +1,7 @@
|
||||
//! Configuration types.
|
||||
|
||||
pub mod prove;
|
||||
pub mod prover;
|
||||
pub mod tls;
|
||||
pub mod tls_commit;
|
||||
pub mod verifier;
|
||||
189
crates/core/src/config/prove.rs
Normal file
189
crates/core/src/config/prove.rs
Normal file
@@ -0,0 +1,189 @@
|
||||
//! Proving configuration.
|
||||
|
||||
use rangeset::set::{RangeSet, ToRangeSet};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::transcript::{Direction, Transcript, TranscriptCommitConfig, TranscriptCommitRequest};
|
||||
|
||||
/// Configuration to prove information to the verifier.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ProveConfig {
|
||||
server_identity: bool,
|
||||
reveal: Option<(RangeSet<usize>, RangeSet<usize>)>,
|
||||
transcript_commit: Option<TranscriptCommitConfig>,
|
||||
}
|
||||
|
||||
impl ProveConfig {
|
||||
/// Creates a new builder.
|
||||
pub fn builder(transcript: &Transcript) -> ProveConfigBuilder<'_> {
|
||||
ProveConfigBuilder::new(transcript)
|
||||
}
|
||||
|
||||
/// Returns `true` if the server identity is to be proven.
|
||||
pub fn server_identity(&self) -> bool {
|
||||
self.server_identity
|
||||
}
|
||||
|
||||
/// Returns the sent and received ranges of the transcript to be revealed,
|
||||
/// respectively.
|
||||
pub fn reveal(&self) -> Option<&(RangeSet<usize>, RangeSet<usize>)> {
|
||||
self.reveal.as_ref()
|
||||
}
|
||||
|
||||
/// Returns the transcript commitment configuration.
|
||||
pub fn transcript_commit(&self) -> Option<&TranscriptCommitConfig> {
|
||||
self.transcript_commit.as_ref()
|
||||
}
|
||||
|
||||
/// Returns a request.
|
||||
pub fn to_request(&self) -> ProveRequest {
|
||||
ProveRequest {
|
||||
server_identity: self.server_identity,
|
||||
reveal: self.reveal.clone(),
|
||||
transcript_commit: self
|
||||
.transcript_commit
|
||||
.clone()
|
||||
.map(|config| config.to_request()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Builder for [`ProveConfig`].
|
||||
#[derive(Debug)]
|
||||
pub struct ProveConfigBuilder<'a> {
|
||||
transcript: &'a Transcript,
|
||||
server_identity: bool,
|
||||
reveal: Option<(RangeSet<usize>, RangeSet<usize>)>,
|
||||
transcript_commit: Option<TranscriptCommitConfig>,
|
||||
}
|
||||
|
||||
impl<'a> ProveConfigBuilder<'a> {
|
||||
/// Creates a new builder.
|
||||
pub fn new(transcript: &'a Transcript) -> Self {
|
||||
Self {
|
||||
transcript,
|
||||
server_identity: false,
|
||||
reveal: None,
|
||||
transcript_commit: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Proves the server identity.
|
||||
pub fn server_identity(&mut self) -> &mut Self {
|
||||
self.server_identity = true;
|
||||
self
|
||||
}
|
||||
|
||||
/// Configures transcript commitments.
|
||||
pub fn transcript_commit(&mut self, transcript_commit: TranscriptCommitConfig) -> &mut Self {
|
||||
self.transcript_commit = Some(transcript_commit);
|
||||
self
|
||||
}
|
||||
|
||||
/// Reveals the given ranges of the transcript.
|
||||
pub fn reveal(
|
||||
&mut self,
|
||||
direction: Direction,
|
||||
ranges: &dyn ToRangeSet<usize>,
|
||||
) -> Result<&mut Self, ProveConfigError> {
|
||||
let idx = ranges.to_range_set();
|
||||
|
||||
if idx.end().unwrap_or(0) > self.transcript.len_of_direction(direction) {
|
||||
return Err(ProveConfigError(ErrorRepr::IndexOutOfBounds {
|
||||
direction,
|
||||
actual: idx.end().unwrap_or(0),
|
||||
len: self.transcript.len_of_direction(direction),
|
||||
}));
|
||||
}
|
||||
|
||||
let (sent, recv) = self.reveal.get_or_insert_default();
|
||||
match direction {
|
||||
Direction::Sent => sent.union_mut(&idx),
|
||||
Direction::Received => recv.union_mut(&idx),
|
||||
}
|
||||
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
/// Reveals the given ranges of the sent data transcript.
|
||||
pub fn reveal_sent(
|
||||
&mut self,
|
||||
ranges: &dyn ToRangeSet<usize>,
|
||||
) -> Result<&mut Self, ProveConfigError> {
|
||||
self.reveal(Direction::Sent, ranges)
|
||||
}
|
||||
|
||||
/// Reveals all of the sent data transcript.
|
||||
pub fn reveal_sent_all(&mut self) -> Result<&mut Self, ProveConfigError> {
|
||||
let len = self.transcript.len_of_direction(Direction::Sent);
|
||||
let (sent, _) = self.reveal.get_or_insert_default();
|
||||
sent.union_mut(&(0..len));
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
/// Reveals the given ranges of the received data transcript.
|
||||
pub fn reveal_recv(
|
||||
&mut self,
|
||||
ranges: &dyn ToRangeSet<usize>,
|
||||
) -> Result<&mut Self, ProveConfigError> {
|
||||
self.reveal(Direction::Received, ranges)
|
||||
}
|
||||
|
||||
/// Reveals all of the received data transcript.
|
||||
pub fn reveal_recv_all(&mut self) -> Result<&mut Self, ProveConfigError> {
|
||||
let len = self.transcript.len_of_direction(Direction::Received);
|
||||
let (_, recv) = self.reveal.get_or_insert_default();
|
||||
recv.union_mut(&(0..len));
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
/// Builds the configuration.
|
||||
pub fn build(self) -> Result<ProveConfig, ProveConfigError> {
|
||||
Ok(ProveConfig {
|
||||
server_identity: self.server_identity,
|
||||
reveal: self.reveal,
|
||||
transcript_commit: self.transcript_commit,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Request to prove statements about the connection.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ProveRequest {
|
||||
server_identity: bool,
|
||||
reveal: Option<(RangeSet<usize>, RangeSet<usize>)>,
|
||||
transcript_commit: Option<TranscriptCommitRequest>,
|
||||
}
|
||||
|
||||
impl ProveRequest {
|
||||
/// Returns `true` if the server identity is to be proven.
|
||||
pub fn server_identity(&self) -> bool {
|
||||
self.server_identity
|
||||
}
|
||||
|
||||
/// Returns the sent and received ranges of the transcript to be revealed,
|
||||
/// respectively.
|
||||
pub fn reveal(&self) -> Option<&(RangeSet<usize>, RangeSet<usize>)> {
|
||||
self.reveal.as_ref()
|
||||
}
|
||||
|
||||
/// Returns the transcript commitment configuration.
|
||||
pub fn transcript_commit(&self) -> Option<&TranscriptCommitRequest> {
|
||||
self.transcript_commit.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
/// Error for [`ProveConfig`].
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[error(transparent)]
|
||||
pub struct ProveConfigError(#[from] ErrorRepr);
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
enum ErrorRepr {
|
||||
#[error("range is out of bounds of the transcript ({direction}): {actual} > {len}")]
|
||||
IndexOutOfBounds {
|
||||
direction: Direction,
|
||||
actual: usize,
|
||||
len: usize,
|
||||
},
|
||||
}
|
||||
33
crates/core/src/config/prover.rs
Normal file
33
crates/core/src/config/prover.rs
Normal file
@@ -0,0 +1,33 @@
|
||||
//! Prover configuration.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Prover configuration.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ProverConfig {}
|
||||
|
||||
impl ProverConfig {
|
||||
/// Creates a new builder.
|
||||
pub fn builder() -> ProverConfigBuilder {
|
||||
ProverConfigBuilder::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Builder for [`ProverConfig`].
|
||||
#[derive(Debug, Default)]
|
||||
pub struct ProverConfigBuilder {}
|
||||
|
||||
impl ProverConfigBuilder {
|
||||
/// Builds the configuration.
|
||||
pub fn build(self) -> Result<ProverConfig, ProverConfigError> {
|
||||
Ok(ProverConfig {})
|
||||
}
|
||||
}
|
||||
|
||||
/// Error for [`ProverConfig`].
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[error(transparent)]
|
||||
pub struct ProverConfigError(#[from] ErrorRepr);
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
enum ErrorRepr {}
|
||||
111
crates/core/src/config/tls.rs
Normal file
111
crates/core/src/config/tls.rs
Normal file
@@ -0,0 +1,111 @@
|
||||
//! TLS client configuration.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
connection::ServerName,
|
||||
webpki::{CertificateDer, PrivateKeyDer, RootCertStore},
|
||||
};
|
||||
|
||||
/// TLS client configuration.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct TlsClientConfig {
|
||||
server_name: ServerName,
|
||||
/// Root certificates.
|
||||
root_store: RootCertStore,
|
||||
/// Certificate chain and a matching private key for client
|
||||
/// authentication.
|
||||
client_auth: Option<(Vec<CertificateDer>, PrivateKeyDer)>,
|
||||
}
|
||||
|
||||
impl TlsClientConfig {
|
||||
/// Creates a new builder.
|
||||
pub fn builder() -> TlsConfigBuilder {
|
||||
TlsConfigBuilder::default()
|
||||
}
|
||||
|
||||
/// Returns the server name.
|
||||
pub fn server_name(&self) -> &ServerName {
|
||||
&self.server_name
|
||||
}
|
||||
|
||||
/// Returns the root certificates.
|
||||
pub fn root_store(&self) -> &RootCertStore {
|
||||
&self.root_store
|
||||
}
|
||||
|
||||
/// Returns a certificate chain and a matching private key for client
|
||||
/// authentication.
|
||||
pub fn client_auth(&self) -> Option<&(Vec<CertificateDer>, PrivateKeyDer)> {
|
||||
self.client_auth.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
/// Builder for [`TlsClientConfig`].
|
||||
#[derive(Debug, Default)]
|
||||
pub struct TlsConfigBuilder {
|
||||
server_name: Option<ServerName>,
|
||||
root_store: Option<RootCertStore>,
|
||||
client_auth: Option<(Vec<CertificateDer>, PrivateKeyDer)>,
|
||||
}
|
||||
|
||||
impl TlsConfigBuilder {
|
||||
/// Sets the server name.
|
||||
pub fn server_name(mut self, server_name: ServerName) -> Self {
|
||||
self.server_name = Some(server_name);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the root certificates to use for verifying the server's
|
||||
/// certificate.
|
||||
pub fn root_store(mut self, store: RootCertStore) -> Self {
|
||||
self.root_store = Some(store);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets a DER-encoded certificate chain and a matching private key for
|
||||
/// client authentication.
|
||||
///
|
||||
/// Often the chain will consist of a single end-entity certificate.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `cert_key` - A tuple containing the certificate chain and the private
|
||||
/// key.
|
||||
///
|
||||
/// - Each certificate in the chain must be in the X.509 format.
|
||||
/// - The key must be in the ASN.1 format (either PKCS#8 or PKCS#1).
|
||||
pub fn client_auth(mut self, cert_key: (Vec<CertificateDer>, PrivateKeyDer)) -> Self {
|
||||
self.client_auth = Some(cert_key);
|
||||
self
|
||||
}
|
||||
|
||||
/// Builds the TLS configuration.
|
||||
pub fn build(self) -> Result<TlsClientConfig, TlsConfigError> {
|
||||
let server_name = self.server_name.ok_or(ErrorRepr::MissingField {
|
||||
field: "server_name",
|
||||
})?;
|
||||
|
||||
let root_store = self.root_store.ok_or(ErrorRepr::MissingField {
|
||||
field: "root_store",
|
||||
})?;
|
||||
|
||||
Ok(TlsClientConfig {
|
||||
server_name,
|
||||
root_store,
|
||||
client_auth: self.client_auth,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// TLS configuration error.
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[error(transparent)]
|
||||
pub struct TlsConfigError(#[from] ErrorRepr);
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[error("tls config error")]
|
||||
enum ErrorRepr {
|
||||
#[error("missing required field: {field}")]
|
||||
MissingField { field: &'static str },
|
||||
}
|
||||
94
crates/core/src/config/tls_commit.rs
Normal file
94
crates/core/src/config/tls_commit.rs
Normal file
@@ -0,0 +1,94 @@
|
||||
//! TLS commitment configuration.
|
||||
|
||||
pub mod mpc;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// TLS commitment configuration.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct TlsCommitConfig {
|
||||
protocol: TlsCommitProtocolConfig,
|
||||
}
|
||||
|
||||
impl TlsCommitConfig {
|
||||
/// Creates a new builder.
|
||||
pub fn builder() -> TlsCommitConfigBuilder {
|
||||
TlsCommitConfigBuilder::default()
|
||||
}
|
||||
|
||||
/// Returns the protocol configuration.
|
||||
pub fn protocol(&self) -> &TlsCommitProtocolConfig {
|
||||
&self.protocol
|
||||
}
|
||||
|
||||
/// Returns a TLS commitment request.
|
||||
pub fn to_request(&self) -> TlsCommitRequest {
|
||||
TlsCommitRequest {
|
||||
config: self.protocol.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Builder for [`TlsCommitConfig`].
|
||||
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
|
||||
pub struct TlsCommitConfigBuilder {
|
||||
protocol: Option<TlsCommitProtocolConfig>,
|
||||
}
|
||||
|
||||
impl TlsCommitConfigBuilder {
|
||||
/// Sets the protocol configuration.
|
||||
pub fn protocol<C>(mut self, protocol: C) -> Self
|
||||
where
|
||||
C: Into<TlsCommitProtocolConfig>,
|
||||
{
|
||||
self.protocol = Some(protocol.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Builds the configuration.
|
||||
pub fn build(self) -> Result<TlsCommitConfig, TlsCommitConfigError> {
|
||||
let protocol = self
|
||||
.protocol
|
||||
.ok_or(ErrorRepr::MissingField { name: "protocol" })?;
|
||||
|
||||
Ok(TlsCommitConfig { protocol })
|
||||
}
|
||||
}
|
||||
|
||||
/// TLS commitment protocol configuration.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[non_exhaustive]
|
||||
pub enum TlsCommitProtocolConfig {
|
||||
/// MPC-TLS configuration.
|
||||
Mpc(mpc::MpcTlsConfig),
|
||||
}
|
||||
|
||||
impl From<mpc::MpcTlsConfig> for TlsCommitProtocolConfig {
|
||||
fn from(config: mpc::MpcTlsConfig) -> Self {
|
||||
Self::Mpc(config)
|
||||
}
|
||||
}
|
||||
|
||||
/// TLS commitment request.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct TlsCommitRequest {
|
||||
config: TlsCommitProtocolConfig,
|
||||
}
|
||||
|
||||
impl TlsCommitRequest {
|
||||
/// Returns the protocol configuration.
|
||||
pub fn protocol(&self) -> &TlsCommitProtocolConfig {
|
||||
&self.config
|
||||
}
|
||||
}
|
||||
|
||||
/// Error for [`TlsCommitConfig`].
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[error(transparent)]
|
||||
pub struct TlsCommitConfigError(#[from] ErrorRepr);
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
enum ErrorRepr {
|
||||
#[error("missing field: {name}")]
|
||||
MissingField { name: &'static str },
|
||||
}
|
||||
241
crates/core/src/config/tls_commit/mpc.rs
Normal file
241
crates/core/src/config/tls_commit/mpc.rs
Normal file
@@ -0,0 +1,241 @@
|
||||
//! MPC-TLS commitment protocol configuration.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
// Default is 32 bytes to decrypt the TLS protocol messages.
|
||||
const DEFAULT_MAX_RECV_ONLINE: usize = 32;
|
||||
|
||||
/// MPC-TLS commitment protocol configuration.
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[serde(try_from = "unchecked::MpcTlsConfigUnchecked")]
|
||||
pub struct MpcTlsConfig {
|
||||
/// Maximum number of bytes that can be sent.
|
||||
max_sent_data: usize,
|
||||
/// Maximum number of application data records that can be sent.
|
||||
max_sent_records: Option<usize>,
|
||||
/// Maximum number of bytes that can be decrypted online, i.e. while the
|
||||
/// MPC-TLS connection is active.
|
||||
max_recv_data_online: usize,
|
||||
/// Maximum number of bytes that can be received.
|
||||
max_recv_data: usize,
|
||||
/// Maximum number of received application data records that can be
|
||||
/// decrypted online, i.e. while the MPC-TLS connection is active.
|
||||
max_recv_records_online: Option<usize>,
|
||||
/// Whether the `deferred decryption` feature is toggled on from the start
|
||||
/// of the MPC-TLS connection.
|
||||
defer_decryption_from_start: bool,
|
||||
/// Network settings.
|
||||
network: NetworkSetting,
|
||||
}
|
||||
|
||||
impl MpcTlsConfig {
|
||||
/// Creates a new builder.
|
||||
pub fn builder() -> MpcTlsConfigBuilder {
|
||||
MpcTlsConfigBuilder::default()
|
||||
}
|
||||
|
||||
/// Returns the maximum number of bytes that can be sent.
|
||||
pub fn max_sent_data(&self) -> usize {
|
||||
self.max_sent_data
|
||||
}
|
||||
|
||||
/// Returns the maximum number of application data records that can
|
||||
/// be sent.
|
||||
pub fn max_sent_records(&self) -> Option<usize> {
|
||||
self.max_sent_records
|
||||
}
|
||||
|
||||
/// Returns the maximum number of bytes that can be decrypted online.
|
||||
pub fn max_recv_data_online(&self) -> usize {
|
||||
self.max_recv_data_online
|
||||
}
|
||||
|
||||
/// Returns the maximum number of bytes that can be received.
|
||||
pub fn max_recv_data(&self) -> usize {
|
||||
self.max_recv_data
|
||||
}
|
||||
|
||||
/// Returns the maximum number of received application data records that
|
||||
/// can be decrypted online.
|
||||
pub fn max_recv_records_online(&self) -> Option<usize> {
|
||||
self.max_recv_records_online
|
||||
}
|
||||
|
||||
/// Returns whether the `deferred decryption` feature is toggled on from the
|
||||
/// start of the MPC-TLS connection.
|
||||
pub fn defer_decryption_from_start(&self) -> bool {
|
||||
self.defer_decryption_from_start
|
||||
}
|
||||
|
||||
/// Returns the network settings.
|
||||
pub fn network(&self) -> NetworkSetting {
|
||||
self.network
|
||||
}
|
||||
}
|
||||
|
||||
fn validate(config: MpcTlsConfig) -> Result<MpcTlsConfig, MpcTlsConfigError> {
|
||||
if config.max_recv_data_online > config.max_recv_data {
|
||||
return Err(ErrorRepr::InvalidValue {
|
||||
name: "max_recv_data_online",
|
||||
reason: format!(
|
||||
"must be <= max_recv_data ({} > {})",
|
||||
config.max_recv_data_online, config.max_recv_data
|
||||
),
|
||||
}
|
||||
.into());
|
||||
}
|
||||
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
/// Builder for [`MpcTlsConfig`].
|
||||
#[derive(Debug, Default)]
|
||||
pub struct MpcTlsConfigBuilder {
|
||||
max_sent_data: Option<usize>,
|
||||
max_sent_records: Option<usize>,
|
||||
max_recv_data_online: Option<usize>,
|
||||
max_recv_data: Option<usize>,
|
||||
max_recv_records_online: Option<usize>,
|
||||
defer_decryption_from_start: Option<bool>,
|
||||
network: Option<NetworkSetting>,
|
||||
}
|
||||
|
||||
impl MpcTlsConfigBuilder {
|
||||
/// Sets the maximum number of bytes that can be sent.
|
||||
pub fn max_sent_data(mut self, max_sent_data: usize) -> Self {
|
||||
self.max_sent_data = Some(max_sent_data);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the maximum number of application data records that can be sent.
|
||||
pub fn max_sent_records(mut self, max_sent_records: usize) -> Self {
|
||||
self.max_sent_records = Some(max_sent_records);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the maximum number of bytes that can be decrypted online.
|
||||
pub fn max_recv_data_online(mut self, max_recv_data_online: usize) -> Self {
|
||||
self.max_recv_data_online = Some(max_recv_data_online);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the maximum number of bytes that can be received.
|
||||
pub fn max_recv_data(mut self, max_recv_data: usize) -> Self {
|
||||
self.max_recv_data = Some(max_recv_data);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the maximum number of received application data records that can
|
||||
/// be decrypted online.
|
||||
pub fn max_recv_records_online(mut self, max_recv_records_online: usize) -> Self {
|
||||
self.max_recv_records_online = Some(max_recv_records_online);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets whether the `deferred decryption` feature is toggled on from the
|
||||
/// start of the MPC-TLS connection.
|
||||
pub fn defer_decryption_from_start(mut self, defer_decryption_from_start: bool) -> Self {
|
||||
self.defer_decryption_from_start = Some(defer_decryption_from_start);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the network settings.
|
||||
pub fn network(mut self, network: NetworkSetting) -> Self {
|
||||
self.network = Some(network);
|
||||
self
|
||||
}
|
||||
|
||||
/// Builds the configuration.
|
||||
pub fn build(self) -> Result<MpcTlsConfig, MpcTlsConfigError> {
|
||||
let Self {
|
||||
max_sent_data,
|
||||
max_sent_records,
|
||||
max_recv_data_online,
|
||||
max_recv_data,
|
||||
max_recv_records_online,
|
||||
defer_decryption_from_start,
|
||||
network,
|
||||
} = self;
|
||||
|
||||
let max_sent_data = max_sent_data.ok_or(ErrorRepr::MissingField {
|
||||
name: "max_sent_data",
|
||||
})?;
|
||||
|
||||
let max_recv_data_online = max_recv_data_online.unwrap_or(DEFAULT_MAX_RECV_ONLINE);
|
||||
let max_recv_data = max_recv_data.ok_or(ErrorRepr::MissingField {
|
||||
name: "max_recv_data",
|
||||
})?;
|
||||
|
||||
let defer_decryption_from_start = defer_decryption_from_start.unwrap_or(true);
|
||||
let network = network.unwrap_or_default();
|
||||
|
||||
validate(MpcTlsConfig {
|
||||
max_sent_data,
|
||||
max_sent_records,
|
||||
max_recv_data_online,
|
||||
max_recv_data,
|
||||
max_recv_records_online,
|
||||
defer_decryption_from_start,
|
||||
network,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Settings for the network environment.
|
||||
///
|
||||
/// Provides optimization options to adapt the protocol to different network
|
||||
/// situations.
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize, Default)]
|
||||
pub enum NetworkSetting {
|
||||
/// Reduces network round-trips at the expense of consuming more network
|
||||
/// bandwidth.
|
||||
Bandwidth,
|
||||
/// Reduces network bandwidth utilization at the expense of more network
|
||||
/// round-trips.
|
||||
#[default]
|
||||
Latency,
|
||||
}
|
||||
|
||||
/// Error for [`MpcTlsConfig`].
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[error(transparent)]
|
||||
pub struct MpcTlsConfigError(#[from] ErrorRepr);
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
enum ErrorRepr {
|
||||
#[error("missing field: {name}")]
|
||||
MissingField { name: &'static str },
|
||||
#[error("invalid value for field({name}): {reason}")]
|
||||
InvalidValue { name: &'static str, reason: String },
|
||||
}
|
||||
|
||||
mod unchecked {
|
||||
use super::*;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub(super) struct MpcTlsConfigUnchecked {
|
||||
max_sent_data: usize,
|
||||
max_sent_records: Option<usize>,
|
||||
max_recv_data_online: usize,
|
||||
max_recv_data: usize,
|
||||
max_recv_records_online: Option<usize>,
|
||||
defer_decryption_from_start: bool,
|
||||
network: NetworkSetting,
|
||||
}
|
||||
|
||||
impl TryFrom<MpcTlsConfigUnchecked> for MpcTlsConfig {
|
||||
type Error = MpcTlsConfigError;
|
||||
|
||||
fn try_from(value: MpcTlsConfigUnchecked) -> Result<Self, Self::Error> {
|
||||
validate(MpcTlsConfig {
|
||||
max_sent_data: value.max_sent_data,
|
||||
max_sent_records: value.max_sent_records,
|
||||
max_recv_data_online: value.max_recv_data_online,
|
||||
max_recv_data: value.max_recv_data,
|
||||
max_recv_records_online: value.max_recv_records_online,
|
||||
defer_decryption_from_start: value.defer_decryption_from_start,
|
||||
network: value.network,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
56
crates/core/src/config/verifier.rs
Normal file
56
crates/core/src/config/verifier.rs
Normal file
@@ -0,0 +1,56 @@
|
||||
//! Verifier configuration.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::webpki::RootCertStore;
|
||||
|
||||
/// Verifier configuration.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct VerifierConfig {
|
||||
root_store: RootCertStore,
|
||||
}
|
||||
|
||||
impl VerifierConfig {
|
||||
/// Creates a new builder.
|
||||
pub fn builder() -> VerifierConfigBuilder {
|
||||
VerifierConfigBuilder::default()
|
||||
}
|
||||
|
||||
/// Returns the root certificate store.
|
||||
pub fn root_store(&self) -> &RootCertStore {
|
||||
&self.root_store
|
||||
}
|
||||
}
|
||||
|
||||
/// Builder for [`VerifierConfig`].
|
||||
#[derive(Debug, Default)]
|
||||
pub struct VerifierConfigBuilder {
|
||||
root_store: Option<RootCertStore>,
|
||||
}
|
||||
|
||||
impl VerifierConfigBuilder {
|
||||
/// Sets the root certificate store.
|
||||
pub fn root_store(mut self, root_store: RootCertStore) -> Self {
|
||||
self.root_store = Some(root_store);
|
||||
self
|
||||
}
|
||||
|
||||
/// Builds the configuration.
|
||||
pub fn build(self) -> Result<VerifierConfig, VerifierConfigError> {
|
||||
let root_store = self
|
||||
.root_store
|
||||
.ok_or(ErrorRepr::MissingField { name: "root_store" })?;
|
||||
Ok(VerifierConfig { root_store })
|
||||
}
|
||||
}
|
||||
|
||||
/// Error for [`VerifierConfig`].
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[error(transparent)]
|
||||
pub struct VerifierConfigError(#[from] ErrorRepr);
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
enum ErrorRepr {
|
||||
#[error("missing field: {name}")]
|
||||
MissingField { name: &'static str },
|
||||
}
|
||||
@@ -2,16 +2,11 @@
|
||||
|
||||
use std::fmt;
|
||||
|
||||
use rustls_pki_types as webpki_types;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tls_core::{
|
||||
msgs::{
|
||||
codec::Codec,
|
||||
enums::NamedGroup,
|
||||
handshake::{DigitallySignedStruct, ServerECDHParams},
|
||||
},
|
||||
verify::{ServerCertVerifier as _, WebPkiVerifier},
|
||||
};
|
||||
use web_time::{Duration, UNIX_EPOCH};
|
||||
use tls_core::msgs::{codec::Codec, enums::NamedGroup, handshake::ServerECDHParams};
|
||||
|
||||
use crate::webpki::{CertificateDer, ServerCertVerifier, ServerCertVerifierError};
|
||||
|
||||
/// TLS version.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
@@ -35,40 +30,82 @@ impl TryFrom<tls_core::msgs::enums::ProtocolVersion> for TlsVersion {
|
||||
}
|
||||
}
|
||||
|
||||
/// Server's name, a.k.a. the DNS name.
|
||||
/// Server's name.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub struct ServerName(String);
|
||||
pub enum ServerName {
|
||||
/// DNS name.
|
||||
Dns(DnsName),
|
||||
}
|
||||
|
||||
impl ServerName {
|
||||
/// Creates a new server name.
|
||||
pub fn new(name: String) -> Self {
|
||||
Self(name)
|
||||
}
|
||||
|
||||
/// Returns the name as a string.
|
||||
pub fn as_str(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for ServerName {
|
||||
fn from(name: &str) -> Self {
|
||||
Self(name.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<str> for ServerName {
|
||||
fn as_ref(&self) -> &str {
|
||||
&self.0
|
||||
pub(crate) fn to_webpki(&self) -> webpki_types::ServerName<'static> {
|
||||
match self {
|
||||
ServerName::Dns(name) => webpki_types::ServerName::DnsName(
|
||||
webpki_types::DnsName::try_from(name.0.as_str())
|
||||
.expect("name was validated")
|
||||
.to_owned(),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ServerName {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
ServerName::Dns(name) => write!(f, "{name}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// DNS name.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
#[serde(try_from = "String")]
|
||||
pub struct DnsName(String);
|
||||
|
||||
impl DnsName {
|
||||
/// Returns the DNS name as a string.
|
||||
pub fn as_str(&self) -> &str {
|
||||
self.0.as_str()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for DnsName {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<str> for DnsName {
|
||||
fn as_ref(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
/// Error returned when a DNS name is invalid.
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[error("invalid DNS name")]
|
||||
pub struct InvalidDnsNameError {}
|
||||
|
||||
impl TryFrom<&str> for DnsName {
|
||||
type Error = InvalidDnsNameError;
|
||||
|
||||
fn try_from(value: &str) -> Result<Self, Self::Error> {
|
||||
// Borrow validation from rustls
|
||||
match webpki_types::DnsName::try_from_str(value) {
|
||||
Ok(_) => Ok(DnsName(value.to_string())),
|
||||
Err(_) => Err(InvalidDnsNameError {}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<String> for DnsName {
|
||||
type Error = InvalidDnsNameError;
|
||||
|
||||
fn try_from(value: String) -> Result<Self, Self::Error> {
|
||||
Self::try_from(value.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
/// Type of a public key.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
@@ -79,84 +116,84 @@ pub enum KeyType {
|
||||
SECP256R1 = 0x0017,
|
||||
}
|
||||
|
||||
/// Signature scheme on the key exchange parameters.
|
||||
/// Signature algorithm used on the key exchange parameters.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
#[allow(non_camel_case_types, missing_docs)]
|
||||
pub enum SignatureScheme {
|
||||
RSA_PKCS1_SHA1 = 0x0201,
|
||||
ECDSA_SHA1_Legacy = 0x0203,
|
||||
RSA_PKCS1_SHA256 = 0x0401,
|
||||
ECDSA_NISTP256_SHA256 = 0x0403,
|
||||
RSA_PKCS1_SHA384 = 0x0501,
|
||||
ECDSA_NISTP384_SHA384 = 0x0503,
|
||||
RSA_PKCS1_SHA512 = 0x0601,
|
||||
ECDSA_NISTP521_SHA512 = 0x0603,
|
||||
RSA_PSS_SHA256 = 0x0804,
|
||||
RSA_PSS_SHA384 = 0x0805,
|
||||
RSA_PSS_SHA512 = 0x0806,
|
||||
ED25519 = 0x0807,
|
||||
pub enum SignatureAlgorithm {
|
||||
ECDSA_NISTP256_SHA256,
|
||||
ECDSA_NISTP256_SHA384,
|
||||
ECDSA_NISTP384_SHA256,
|
||||
ECDSA_NISTP384_SHA384,
|
||||
ED25519,
|
||||
RSA_PKCS1_2048_8192_SHA256,
|
||||
RSA_PKCS1_2048_8192_SHA384,
|
||||
RSA_PKCS1_2048_8192_SHA512,
|
||||
RSA_PSS_2048_8192_SHA256_LEGACY_KEY,
|
||||
RSA_PSS_2048_8192_SHA384_LEGACY_KEY,
|
||||
RSA_PSS_2048_8192_SHA512_LEGACY_KEY,
|
||||
}
|
||||
|
||||
impl TryFrom<tls_core::msgs::enums::SignatureScheme> for SignatureScheme {
|
||||
type Error = &'static str;
|
||||
|
||||
fn try_from(value: tls_core::msgs::enums::SignatureScheme) -> Result<Self, Self::Error> {
|
||||
use tls_core::msgs::enums::SignatureScheme as Core;
|
||||
use SignatureScheme::*;
|
||||
Ok(match value {
|
||||
Core::RSA_PKCS1_SHA1 => RSA_PKCS1_SHA1,
|
||||
Core::ECDSA_SHA1_Legacy => ECDSA_SHA1_Legacy,
|
||||
Core::RSA_PKCS1_SHA256 => RSA_PKCS1_SHA256,
|
||||
Core::ECDSA_NISTP256_SHA256 => ECDSA_NISTP256_SHA256,
|
||||
Core::RSA_PKCS1_SHA384 => RSA_PKCS1_SHA384,
|
||||
Core::ECDSA_NISTP384_SHA384 => ECDSA_NISTP384_SHA384,
|
||||
Core::RSA_PKCS1_SHA512 => RSA_PKCS1_SHA512,
|
||||
Core::ECDSA_NISTP521_SHA512 => ECDSA_NISTP521_SHA512,
|
||||
Core::RSA_PSS_SHA256 => RSA_PSS_SHA256,
|
||||
Core::RSA_PSS_SHA384 => RSA_PSS_SHA384,
|
||||
Core::RSA_PSS_SHA512 => RSA_PSS_SHA512,
|
||||
Core::ED25519 => ED25519,
|
||||
_ => return Err("unsupported signature scheme"),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SignatureScheme> for tls_core::msgs::enums::SignatureScheme {
|
||||
fn from(value: SignatureScheme) -> Self {
|
||||
use tls_core::msgs::enums::SignatureScheme::*;
|
||||
match value {
|
||||
SignatureScheme::RSA_PKCS1_SHA1 => RSA_PKCS1_SHA1,
|
||||
SignatureScheme::ECDSA_SHA1_Legacy => ECDSA_SHA1_Legacy,
|
||||
SignatureScheme::RSA_PKCS1_SHA256 => RSA_PKCS1_SHA256,
|
||||
SignatureScheme::ECDSA_NISTP256_SHA256 => ECDSA_NISTP256_SHA256,
|
||||
SignatureScheme::RSA_PKCS1_SHA384 => RSA_PKCS1_SHA384,
|
||||
SignatureScheme::ECDSA_NISTP384_SHA384 => ECDSA_NISTP384_SHA384,
|
||||
SignatureScheme::RSA_PKCS1_SHA512 => RSA_PKCS1_SHA512,
|
||||
SignatureScheme::ECDSA_NISTP521_SHA512 => ECDSA_NISTP521_SHA512,
|
||||
SignatureScheme::RSA_PSS_SHA256 => RSA_PSS_SHA256,
|
||||
SignatureScheme::RSA_PSS_SHA384 => RSA_PSS_SHA384,
|
||||
SignatureScheme::RSA_PSS_SHA512 => RSA_PSS_SHA512,
|
||||
SignatureScheme::ED25519 => ED25519,
|
||||
impl fmt::Display for SignatureAlgorithm {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
SignatureAlgorithm::ECDSA_NISTP256_SHA256 => write!(f, "ECDSA_NISTP256_SHA256"),
|
||||
SignatureAlgorithm::ECDSA_NISTP256_SHA384 => write!(f, "ECDSA_NISTP256_SHA384"),
|
||||
SignatureAlgorithm::ECDSA_NISTP384_SHA256 => write!(f, "ECDSA_NISTP384_SHA256"),
|
||||
SignatureAlgorithm::ECDSA_NISTP384_SHA384 => write!(f, "ECDSA_NISTP384_SHA384"),
|
||||
SignatureAlgorithm::ED25519 => write!(f, "ED25519"),
|
||||
SignatureAlgorithm::RSA_PKCS1_2048_8192_SHA256 => {
|
||||
write!(f, "RSA_PKCS1_2048_8192_SHA256")
|
||||
}
|
||||
SignatureAlgorithm::RSA_PKCS1_2048_8192_SHA384 => {
|
||||
write!(f, "RSA_PKCS1_2048_8192_SHA384")
|
||||
}
|
||||
SignatureAlgorithm::RSA_PKCS1_2048_8192_SHA512 => {
|
||||
write!(f, "RSA_PKCS1_2048_8192_SHA512")
|
||||
}
|
||||
SignatureAlgorithm::RSA_PSS_2048_8192_SHA256_LEGACY_KEY => {
|
||||
write!(f, "RSA_PSS_2048_8192_SHA256_LEGACY_KEY")
|
||||
}
|
||||
SignatureAlgorithm::RSA_PSS_2048_8192_SHA384_LEGACY_KEY => {
|
||||
write!(f, "RSA_PSS_2048_8192_SHA384_LEGACY_KEY")
|
||||
}
|
||||
SignatureAlgorithm::RSA_PSS_2048_8192_SHA512_LEGACY_KEY => {
|
||||
write!(f, "RSA_PSS_2048_8192_SHA512_LEGACY_KEY")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// X.509 certificate, DER encoded.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Certificate(pub Vec<u8>);
|
||||
|
||||
impl From<tls_core::key::Certificate> for Certificate {
|
||||
fn from(cert: tls_core::key::Certificate) -> Self {
|
||||
Self(cert.0)
|
||||
impl From<tls_core::verify::SignatureAlgorithm> for SignatureAlgorithm {
|
||||
fn from(value: tls_core::verify::SignatureAlgorithm) -> Self {
|
||||
use tls_core::verify::SignatureAlgorithm as Core;
|
||||
match value {
|
||||
Core::ECDSA_NISTP256_SHA256 => SignatureAlgorithm::ECDSA_NISTP256_SHA256,
|
||||
Core::ECDSA_NISTP256_SHA384 => SignatureAlgorithm::ECDSA_NISTP256_SHA384,
|
||||
Core::ECDSA_NISTP384_SHA256 => SignatureAlgorithm::ECDSA_NISTP384_SHA256,
|
||||
Core::ECDSA_NISTP384_SHA384 => SignatureAlgorithm::ECDSA_NISTP384_SHA384,
|
||||
Core::ED25519 => SignatureAlgorithm::ED25519,
|
||||
Core::RSA_PKCS1_2048_8192_SHA256 => SignatureAlgorithm::RSA_PKCS1_2048_8192_SHA256,
|
||||
Core::RSA_PKCS1_2048_8192_SHA384 => SignatureAlgorithm::RSA_PKCS1_2048_8192_SHA384,
|
||||
Core::RSA_PKCS1_2048_8192_SHA512 => SignatureAlgorithm::RSA_PKCS1_2048_8192_SHA512,
|
||||
Core::RSA_PSS_2048_8192_SHA256_LEGACY_KEY => {
|
||||
SignatureAlgorithm::RSA_PSS_2048_8192_SHA256_LEGACY_KEY
|
||||
}
|
||||
Core::RSA_PSS_2048_8192_SHA384_LEGACY_KEY => {
|
||||
SignatureAlgorithm::RSA_PSS_2048_8192_SHA384_LEGACY_KEY
|
||||
}
|
||||
Core::RSA_PSS_2048_8192_SHA512_LEGACY_KEY => {
|
||||
SignatureAlgorithm::RSA_PSS_2048_8192_SHA512_LEGACY_KEY
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Server's signature of the key exchange parameters.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ServerSignature {
|
||||
/// Signature scheme.
|
||||
pub scheme: SignatureScheme,
|
||||
/// Signature algorithm.
|
||||
pub alg: SignatureAlgorithm,
|
||||
/// Signature data.
|
||||
pub sig: Vec<u8>,
|
||||
}
|
||||
@@ -220,9 +257,9 @@ pub struct TranscriptLength {
|
||||
pub received: u32,
|
||||
}
|
||||
|
||||
/// TLS 1.2 handshake data.
|
||||
/// TLS 1.2 certificate binding.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct HandshakeDataV1_2 {
|
||||
pub struct CertBindingV1_2 {
|
||||
/// Client random.
|
||||
pub client_random: [u8; 32],
|
||||
/// Server random.
|
||||
@@ -231,13 +268,18 @@ pub struct HandshakeDataV1_2 {
|
||||
pub server_ephemeral_key: ServerEphemKey,
|
||||
}
|
||||
|
||||
/// TLS handshake data.
|
||||
/// TLS certificate binding.
|
||||
///
|
||||
/// This is the data that the server signs using its public key in the
|
||||
/// certificate it presents during the TLS handshake. This provides a binding
|
||||
/// between the server's identity and the ephemeral keys used to authenticate
|
||||
/// the TLS session.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[non_exhaustive]
|
||||
pub enum HandshakeData {
|
||||
/// TLS 1.2 handshake data.
|
||||
V1_2(HandshakeDataV1_2),
|
||||
pub enum CertBinding {
|
||||
/// TLS 1.2 certificate binding.
|
||||
V1_2(CertBindingV1_2),
|
||||
}
|
||||
|
||||
/// Verify data from the TLS handshake finished messages.
|
||||
@@ -249,19 +291,19 @@ pub struct VerifyData {
|
||||
pub server_finished: Vec<u8>,
|
||||
}
|
||||
|
||||
/// Server certificate and handshake data.
|
||||
/// TLS handshake data.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ServerCertData {
|
||||
/// Certificate chain.
|
||||
pub certs: Vec<Certificate>,
|
||||
/// Server signature of the key exchange parameters.
|
||||
pub struct HandshakeData {
|
||||
/// Server certificate chain.
|
||||
pub certs: Vec<CertificateDer>,
|
||||
/// Server certificate signature over the binding message.
|
||||
pub sig: ServerSignature,
|
||||
/// TLS handshake data.
|
||||
pub handshake: HandshakeData,
|
||||
/// Certificate binding.
|
||||
pub binding: CertBinding,
|
||||
}
|
||||
|
||||
impl ServerCertData {
|
||||
/// Verifies the server certificate data.
|
||||
impl HandshakeData {
|
||||
/// Verifies the handshake data.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
@@ -271,53 +313,35 @@ impl ServerCertData {
|
||||
/// * `server_name` - The server name.
|
||||
pub fn verify(
|
||||
&self,
|
||||
verifier: &WebPkiVerifier,
|
||||
verifier: &ServerCertVerifier,
|
||||
time: u64,
|
||||
server_ephemeral_key: &ServerEphemKey,
|
||||
server_name: &ServerName,
|
||||
) -> Result<(), CertificateVerificationError> {
|
||||
) -> Result<(), HandshakeVerificationError> {
|
||||
#[allow(irrefutable_let_patterns)]
|
||||
let HandshakeData::V1_2(HandshakeDataV1_2 {
|
||||
let CertBinding::V1_2(CertBindingV1_2 {
|
||||
client_random,
|
||||
server_random,
|
||||
server_ephemeral_key: expected_server_ephemeral_key,
|
||||
}) = &self.handshake
|
||||
}) = &self.binding
|
||||
else {
|
||||
unreachable!("only TLS 1.2 is implemented")
|
||||
};
|
||||
|
||||
if server_ephemeral_key != expected_server_ephemeral_key {
|
||||
return Err(CertificateVerificationError::InvalidServerEphemeralKey);
|
||||
return Err(HandshakeVerificationError::InvalidServerEphemeralKey);
|
||||
}
|
||||
|
||||
// Verify server name.
|
||||
let server_name = tls_core::dns::ServerName::try_from(server_name.as_ref())
|
||||
.map_err(|_| CertificateVerificationError::InvalidIdentity(server_name.clone()))?;
|
||||
|
||||
// Verify server certificate.
|
||||
let cert_chain = self
|
||||
let (end_entity, intermediates) = self
|
||||
.certs
|
||||
.clone()
|
||||
.into_iter()
|
||||
.map(|cert| tls_core::key::Certificate(cert.0))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let (end_entity, intermediates) = cert_chain
|
||||
.split_first()
|
||||
.ok_or(CertificateVerificationError::MissingCerts)?;
|
||||
.ok_or(HandshakeVerificationError::MissingCerts)?;
|
||||
|
||||
// Verify the end entity cert is valid for the provided server name
|
||||
// and that it chains to at least one of the roots we trust.
|
||||
verifier
|
||||
.verify_server_cert(
|
||||
end_entity,
|
||||
intermediates,
|
||||
&server_name,
|
||||
&mut [].into_iter(),
|
||||
&[],
|
||||
UNIX_EPOCH + Duration::from_secs(time),
|
||||
)
|
||||
.map_err(|_| CertificateVerificationError::InvalidCert)?;
|
||||
.verify_server_cert(end_entity, intermediates, server_name, time)
|
||||
.map_err(HandshakeVerificationError::ServerCert)?;
|
||||
|
||||
// Verify the signature matches the certificate and key exchange parameters.
|
||||
let mut message = Vec::new();
|
||||
@@ -325,11 +349,34 @@ impl ServerCertData {
|
||||
message.extend_from_slice(server_random);
|
||||
message.extend_from_slice(&server_ephemeral_key.kx_params());
|
||||
|
||||
let dss = DigitallySignedStruct::new(self.sig.scheme.into(), self.sig.sig.clone());
|
||||
use webpki::ring as alg;
|
||||
let sig_alg = match self.sig.alg {
|
||||
SignatureAlgorithm::ECDSA_NISTP256_SHA256 => alg::ECDSA_P256_SHA256,
|
||||
SignatureAlgorithm::ECDSA_NISTP256_SHA384 => alg::ECDSA_P256_SHA384,
|
||||
SignatureAlgorithm::ECDSA_NISTP384_SHA256 => alg::ECDSA_P384_SHA256,
|
||||
SignatureAlgorithm::ECDSA_NISTP384_SHA384 => alg::ECDSA_P384_SHA384,
|
||||
SignatureAlgorithm::ED25519 => alg::ED25519,
|
||||
SignatureAlgorithm::RSA_PKCS1_2048_8192_SHA256 => alg::RSA_PKCS1_2048_8192_SHA256,
|
||||
SignatureAlgorithm::RSA_PKCS1_2048_8192_SHA384 => alg::RSA_PKCS1_2048_8192_SHA384,
|
||||
SignatureAlgorithm::RSA_PKCS1_2048_8192_SHA512 => alg::RSA_PKCS1_2048_8192_SHA512,
|
||||
SignatureAlgorithm::RSA_PSS_2048_8192_SHA256_LEGACY_KEY => {
|
||||
alg::RSA_PSS_2048_8192_SHA256_LEGACY_KEY
|
||||
}
|
||||
SignatureAlgorithm::RSA_PSS_2048_8192_SHA384_LEGACY_KEY => {
|
||||
alg::RSA_PSS_2048_8192_SHA384_LEGACY_KEY
|
||||
}
|
||||
SignatureAlgorithm::RSA_PSS_2048_8192_SHA512_LEGACY_KEY => {
|
||||
alg::RSA_PSS_2048_8192_SHA512_LEGACY_KEY
|
||||
}
|
||||
};
|
||||
|
||||
verifier
|
||||
.verify_tls12_signature(&message, end_entity, &dss)
|
||||
.map_err(|_| CertificateVerificationError::InvalidServerSignature)?;
|
||||
let end_entity = webpki_types::CertificateDer::from(end_entity.0.as_slice());
|
||||
let end_entity = webpki::EndEntityCert::try_from(&end_entity)
|
||||
.map_err(|_| HandshakeVerificationError::InvalidEndEntityCertificate)?;
|
||||
|
||||
end_entity
|
||||
.verify_signature(sig_alg, &message, &self.sig.sig)
|
||||
.map_err(|_| HandshakeVerificationError::InvalidServerSignature)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -338,58 +385,49 @@ impl ServerCertData {
|
||||
/// Errors that can occur when verifying a certificate chain or signature.
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[allow(missing_docs)]
|
||||
pub enum CertificateVerificationError {
|
||||
#[error("invalid server identity: {0}")]
|
||||
InvalidIdentity(ServerName),
|
||||
pub enum HandshakeVerificationError {
|
||||
#[error("invalid end entity certificate")]
|
||||
InvalidEndEntityCertificate,
|
||||
#[error("missing server certificates")]
|
||||
MissingCerts,
|
||||
#[error("invalid server certificate")]
|
||||
InvalidCert,
|
||||
#[error("invalid server signature")]
|
||||
InvalidServerSignature,
|
||||
#[error("invalid server ephemeral key")]
|
||||
InvalidServerEphemeralKey,
|
||||
#[error("server certificate verification failed: {0}")]
|
||||
ServerCert(ServerCertVerifierError),
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{fixtures::ConnectionFixture, transcript::Transcript};
|
||||
use crate::{fixtures::ConnectionFixture, transcript::Transcript, webpki::RootCertStore};
|
||||
|
||||
use hex::FromHex;
|
||||
use rstest::*;
|
||||
use tls_core::{
|
||||
anchors::{OwnedTrustAnchor, RootCertStore},
|
||||
verify::WebPkiVerifier,
|
||||
};
|
||||
use tlsn_data_fixtures::http::{request::GET_WITH_HEADER, response::OK_JSON};
|
||||
|
||||
#[fixture]
|
||||
#[once]
|
||||
fn verifier() -> WebPkiVerifier {
|
||||
let mut root_store = RootCertStore::empty();
|
||||
root_store.add_server_trust_anchors(webpki_roots::TLS_SERVER_ROOTS.iter().map(|ta| {
|
||||
OwnedTrustAnchor::from_subject_spki_name_constraints(
|
||||
ta.subject.as_ref(),
|
||||
ta.subject_public_key_info.as_ref(),
|
||||
ta.name_constraints.as_ref().map(|nc| nc.as_ref()),
|
||||
)
|
||||
}));
|
||||
fn verifier() -> ServerCertVerifier {
|
||||
let mut root_store = RootCertStore {
|
||||
roots: webpki_root_certs::TLS_SERVER_ROOT_CERTS
|
||||
.iter()
|
||||
.map(|c| CertificateDer(c.to_vec()))
|
||||
.collect(),
|
||||
};
|
||||
|
||||
// Add a cert which is no longer included in the Mozilla root store.
|
||||
let cert = tls_core::key::Certificate(
|
||||
root_store.roots.push(
|
||||
appliedzkp()
|
||||
.server_cert_data
|
||||
.certs
|
||||
.last()
|
||||
.expect("chain is valid")
|
||||
.0
|
||||
.clone(),
|
||||
);
|
||||
|
||||
root_store.add(&cert).unwrap();
|
||||
|
||||
WebPkiVerifier::new(root_store, None)
|
||||
ServerCertVerifier::new(&root_store).unwrap()
|
||||
}
|
||||
|
||||
fn tlsnotary() -> ConnectionFixture {
|
||||
@@ -405,7 +443,7 @@ mod tests {
|
||||
#[case::tlsnotary(tlsnotary())]
|
||||
#[case::appliedzkp(appliedzkp())]
|
||||
fn test_verify_cert_chain_sucess_ca_implicit(
|
||||
verifier: &WebPkiVerifier,
|
||||
verifier: &ServerCertVerifier,
|
||||
#[case] mut data: ConnectionFixture,
|
||||
) {
|
||||
// Remove the CA cert
|
||||
@@ -417,7 +455,7 @@ mod tests {
|
||||
verifier,
|
||||
data.connection_info.time,
|
||||
data.server_ephemeral_key(),
|
||||
&ServerName::from(data.server_name.as_ref()),
|
||||
&data.server_name,
|
||||
)
|
||||
.is_ok());
|
||||
}
|
||||
@@ -428,7 +466,7 @@ mod tests {
|
||||
#[case::tlsnotary(tlsnotary())]
|
||||
#[case::appliedzkp(appliedzkp())]
|
||||
fn test_verify_cert_chain_success_ca_explicit(
|
||||
verifier: &WebPkiVerifier,
|
||||
verifier: &ServerCertVerifier,
|
||||
#[case] data: ConnectionFixture,
|
||||
) {
|
||||
assert!(data
|
||||
@@ -437,7 +475,7 @@ mod tests {
|
||||
verifier,
|
||||
data.connection_info.time,
|
||||
data.server_ephemeral_key(),
|
||||
&ServerName::from(data.server_name.as_ref()),
|
||||
&data.server_name,
|
||||
)
|
||||
.is_ok());
|
||||
}
|
||||
@@ -447,7 +485,7 @@ mod tests {
|
||||
#[case::tlsnotary(tlsnotary())]
|
||||
#[case::appliedzkp(appliedzkp())]
|
||||
fn test_verify_cert_chain_fail_bad_time(
|
||||
verifier: &WebPkiVerifier,
|
||||
verifier: &ServerCertVerifier,
|
||||
#[case] data: ConnectionFixture,
|
||||
) {
|
||||
// unix time when the cert chain was NOT valid
|
||||
@@ -457,12 +495,12 @@ mod tests {
|
||||
verifier,
|
||||
bad_time,
|
||||
data.server_ephemeral_key(),
|
||||
&ServerName::from(data.server_name.as_ref()),
|
||||
&data.server_name,
|
||||
);
|
||||
|
||||
assert!(matches!(
|
||||
err.unwrap_err(),
|
||||
CertificateVerificationError::InvalidCert
|
||||
HandshakeVerificationError::ServerCert(_)
|
||||
));
|
||||
}
|
||||
|
||||
@@ -471,7 +509,7 @@ mod tests {
|
||||
#[case::tlsnotary(tlsnotary())]
|
||||
#[case::appliedzkp(appliedzkp())]
|
||||
fn test_verify_cert_chain_fail_no_interm_cert(
|
||||
verifier: &WebPkiVerifier,
|
||||
verifier: &ServerCertVerifier,
|
||||
#[case] mut data: ConnectionFixture,
|
||||
) {
|
||||
// Remove the CA cert
|
||||
@@ -483,12 +521,12 @@ mod tests {
|
||||
verifier,
|
||||
data.connection_info.time,
|
||||
data.server_ephemeral_key(),
|
||||
&ServerName::from(data.server_name.as_ref()),
|
||||
&data.server_name,
|
||||
);
|
||||
|
||||
assert!(matches!(
|
||||
err.unwrap_err(),
|
||||
CertificateVerificationError::InvalidCert
|
||||
HandshakeVerificationError::ServerCert(_)
|
||||
));
|
||||
}
|
||||
|
||||
@@ -498,7 +536,7 @@ mod tests {
|
||||
#[case::tlsnotary(tlsnotary())]
|
||||
#[case::appliedzkp(appliedzkp())]
|
||||
fn test_verify_cert_chain_fail_no_interm_cert_with_ca_cert(
|
||||
verifier: &WebPkiVerifier,
|
||||
verifier: &ServerCertVerifier,
|
||||
#[case] mut data: ConnectionFixture,
|
||||
) {
|
||||
// Remove the intermediate cert
|
||||
@@ -508,12 +546,12 @@ mod tests {
|
||||
verifier,
|
||||
data.connection_info.time,
|
||||
data.server_ephemeral_key(),
|
||||
&ServerName::from(data.server_name.as_ref()),
|
||||
&data.server_name,
|
||||
);
|
||||
|
||||
assert!(matches!(
|
||||
err.unwrap_err(),
|
||||
CertificateVerificationError::InvalidCert
|
||||
HandshakeVerificationError::ServerCert(_)
|
||||
));
|
||||
}
|
||||
|
||||
@@ -522,24 +560,24 @@ mod tests {
|
||||
#[case::tlsnotary(tlsnotary())]
|
||||
#[case::appliedzkp(appliedzkp())]
|
||||
fn test_verify_cert_chain_fail_bad_ee_cert(
|
||||
verifier: &WebPkiVerifier,
|
||||
verifier: &ServerCertVerifier,
|
||||
#[case] mut data: ConnectionFixture,
|
||||
) {
|
||||
let ee: &[u8] = include_bytes!("./fixtures/data/unknown/ee.der");
|
||||
|
||||
// Change the end entity cert
|
||||
data.server_cert_data.certs[0] = Certificate(ee.to_vec());
|
||||
data.server_cert_data.certs[0] = CertificateDer(ee.to_vec());
|
||||
|
||||
let err = data.server_cert_data.verify(
|
||||
verifier,
|
||||
data.connection_info.time,
|
||||
data.server_ephemeral_key(),
|
||||
&ServerName::from(data.server_name.as_ref()),
|
||||
&data.server_name,
|
||||
);
|
||||
|
||||
assert!(matches!(
|
||||
err.unwrap_err(),
|
||||
CertificateVerificationError::InvalidCert
|
||||
HandshakeVerificationError::ServerCert(_)
|
||||
));
|
||||
}
|
||||
|
||||
@@ -548,23 +586,23 @@ mod tests {
|
||||
#[case::tlsnotary(tlsnotary())]
|
||||
#[case::appliedzkp(appliedzkp())]
|
||||
fn test_verify_sig_ke_params_fail_bad_client_random(
|
||||
verifier: &WebPkiVerifier,
|
||||
verifier: &ServerCertVerifier,
|
||||
#[case] mut data: ConnectionFixture,
|
||||
) {
|
||||
let HandshakeData::V1_2(HandshakeDataV1_2 { client_random, .. }) =
|
||||
&mut data.server_cert_data.handshake;
|
||||
let CertBinding::V1_2(CertBindingV1_2 { client_random, .. }) =
|
||||
&mut data.server_cert_data.binding;
|
||||
client_random[31] = client_random[31].wrapping_add(1);
|
||||
|
||||
let err = data.server_cert_data.verify(
|
||||
verifier,
|
||||
data.connection_info.time,
|
||||
data.server_ephemeral_key(),
|
||||
&ServerName::from(data.server_name.as_ref()),
|
||||
&data.server_name,
|
||||
);
|
||||
|
||||
assert!(matches!(
|
||||
err.unwrap_err(),
|
||||
CertificateVerificationError::InvalidServerSignature
|
||||
HandshakeVerificationError::InvalidServerSignature
|
||||
));
|
||||
}
|
||||
|
||||
@@ -573,7 +611,7 @@ mod tests {
|
||||
#[case::tlsnotary(tlsnotary())]
|
||||
#[case::appliedzkp(appliedzkp())]
|
||||
fn test_verify_sig_ke_params_fail_bad_sig(
|
||||
verifier: &WebPkiVerifier,
|
||||
verifier: &ServerCertVerifier,
|
||||
#[case] mut data: ConnectionFixture,
|
||||
) {
|
||||
data.server_cert_data.sig.sig[31] = data.server_cert_data.sig.sig[31].wrapping_add(1);
|
||||
@@ -582,12 +620,12 @@ mod tests {
|
||||
verifier,
|
||||
data.connection_info.time,
|
||||
data.server_ephemeral_key(),
|
||||
&ServerName::from(data.server_name.as_ref()),
|
||||
&data.server_name,
|
||||
);
|
||||
|
||||
assert!(matches!(
|
||||
err.unwrap_err(),
|
||||
CertificateVerificationError::InvalidServerSignature
|
||||
HandshakeVerificationError::InvalidServerSignature
|
||||
));
|
||||
}
|
||||
|
||||
@@ -596,10 +634,10 @@ mod tests {
|
||||
#[case::tlsnotary(tlsnotary())]
|
||||
#[case::appliedzkp(appliedzkp())]
|
||||
fn test_check_dns_name_present_in_cert_fail_bad_host(
|
||||
verifier: &WebPkiVerifier,
|
||||
verifier: &ServerCertVerifier,
|
||||
#[case] data: ConnectionFixture,
|
||||
) {
|
||||
let bad_name = ServerName::from("badhost.com");
|
||||
let bad_name = ServerName::Dns(DnsName::try_from("badhost.com").unwrap());
|
||||
|
||||
let err = data.server_cert_data.verify(
|
||||
verifier,
|
||||
@@ -610,7 +648,7 @@ mod tests {
|
||||
|
||||
assert!(matches!(
|
||||
err.unwrap_err(),
|
||||
CertificateVerificationError::InvalidCert
|
||||
HandshakeVerificationError::ServerCert(_)
|
||||
));
|
||||
}
|
||||
|
||||
@@ -618,7 +656,7 @@ mod tests {
|
||||
#[rstest]
|
||||
#[case::tlsnotary(tlsnotary())]
|
||||
#[case::appliedzkp(appliedzkp())]
|
||||
fn test_invalid_ephemeral_key(verifier: &WebPkiVerifier, #[case] data: ConnectionFixture) {
|
||||
fn test_invalid_ephemeral_key(verifier: &ServerCertVerifier, #[case] data: ConnectionFixture) {
|
||||
let wrong_ephemeral_key = ServerEphemKey {
|
||||
typ: KeyType::SECP256R1,
|
||||
key: Vec::<u8>::from_hex(include_bytes!("./fixtures/data/unknown/pubkey")).unwrap(),
|
||||
@@ -628,12 +666,12 @@ mod tests {
|
||||
verifier,
|
||||
data.connection_info.time,
|
||||
&wrong_ephemeral_key,
|
||||
&ServerName::from(data.server_name.as_ref()),
|
||||
&data.server_name,
|
||||
);
|
||||
|
||||
assert!(matches!(
|
||||
err.unwrap_err(),
|
||||
CertificateVerificationError::InvalidServerEphemeralKey
|
||||
HandshakeVerificationError::InvalidServerEphemeralKey
|
||||
));
|
||||
}
|
||||
|
||||
@@ -642,7 +680,7 @@ mod tests {
|
||||
#[case::tlsnotary(tlsnotary())]
|
||||
#[case::appliedzkp(appliedzkp())]
|
||||
fn test_verify_cert_chain_fail_no_cert(
|
||||
verifier: &WebPkiVerifier,
|
||||
verifier: &ServerCertVerifier,
|
||||
#[case] mut data: ConnectionFixture,
|
||||
) {
|
||||
// Empty certs
|
||||
@@ -652,12 +690,12 @@ mod tests {
|
||||
verifier,
|
||||
data.connection_info.time,
|
||||
data.server_ephemeral_key(),
|
||||
&ServerName::from(data.server_name.as_ref()),
|
||||
&data.server_name,
|
||||
);
|
||||
|
||||
assert!(matches!(
|
||||
err.unwrap_err(),
|
||||
CertificateVerificationError::MissingCerts
|
||||
HandshakeVerificationError::MissingCerts
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
16
crates/core/src/display.rs
Normal file
16
crates/core/src/display.rs
Normal file
@@ -0,0 +1,16 @@
|
||||
use rangeset::set::RangeSet;
|
||||
|
||||
pub(crate) struct FmtRangeSet<'a>(pub &'a RangeSet<usize>);
|
||||
|
||||
impl<'a> std::fmt::Display for FmtRangeSet<'a> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str("{")?;
|
||||
for range in self.0.iter() {
|
||||
write!(f, "{}..{}", range.start, range.end)?;
|
||||
if range.end < self.0.end().unwrap_or(0) {
|
||||
f.write_str(", ")?;
|
||||
}
|
||||
}
|
||||
f.write_str("}")
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
//! Fixtures for testing
|
||||
|
||||
mod provider;
|
||||
pub mod transcript;
|
||||
|
||||
pub use provider::FixtureEncodingProvider;
|
||||
|
||||
@@ -8,13 +9,15 @@ use hex::FromHex;
|
||||
|
||||
use crate::{
|
||||
connection::{
|
||||
Certificate, ConnectionInfo, HandshakeData, HandshakeDataV1_2, KeyType, ServerCertData,
|
||||
ServerEphemKey, ServerName, ServerSignature, SignatureScheme, TlsVersion, TranscriptLength,
|
||||
CertBinding, CertBindingV1_2, ConnectionInfo, DnsName, HandshakeData, KeyType,
|
||||
ServerEphemKey, ServerName, ServerSignature, SignatureAlgorithm, TlsVersion,
|
||||
TranscriptLength,
|
||||
},
|
||||
transcript::{
|
||||
encoding::{EncoderSecret, EncodingProvider},
|
||||
Transcript,
|
||||
},
|
||||
webpki::CertificateDer,
|
||||
};
|
||||
|
||||
/// A fixture containing various TLS connection data.
|
||||
@@ -23,33 +26,35 @@ use crate::{
|
||||
pub struct ConnectionFixture {
|
||||
pub server_name: ServerName,
|
||||
pub connection_info: ConnectionInfo,
|
||||
pub server_cert_data: ServerCertData,
|
||||
pub server_cert_data: HandshakeData,
|
||||
}
|
||||
|
||||
impl ConnectionFixture {
|
||||
/// Returns a connection fixture for tlsnotary.org.
|
||||
pub fn tlsnotary(transcript_length: TranscriptLength) -> Self {
|
||||
ConnectionFixture {
|
||||
server_name: ServerName::new("tlsnotary.org".to_string()),
|
||||
server_name: ServerName::Dns(DnsName::try_from("tlsnotary.org").unwrap()),
|
||||
connection_info: ConnectionInfo {
|
||||
time: 1671637529,
|
||||
version: TlsVersion::V1_2,
|
||||
transcript_length,
|
||||
},
|
||||
server_cert_data: ServerCertData {
|
||||
server_cert_data: HandshakeData {
|
||||
certs: vec![
|
||||
Certificate(include_bytes!("fixtures/data/tlsnotary.org/ee.der").to_vec()),
|
||||
Certificate(include_bytes!("fixtures/data/tlsnotary.org/inter.der").to_vec()),
|
||||
Certificate(include_bytes!("fixtures/data/tlsnotary.org/ca.der").to_vec()),
|
||||
CertificateDer(include_bytes!("fixtures/data/tlsnotary.org/ee.der").to_vec()),
|
||||
CertificateDer(
|
||||
include_bytes!("fixtures/data/tlsnotary.org/inter.der").to_vec(),
|
||||
),
|
||||
CertificateDer(include_bytes!("fixtures/data/tlsnotary.org/ca.der").to_vec()),
|
||||
],
|
||||
sig: ServerSignature {
|
||||
scheme: SignatureScheme::RSA_PKCS1_SHA256,
|
||||
alg: SignatureAlgorithm::RSA_PKCS1_2048_8192_SHA256,
|
||||
sig: Vec::<u8>::from_hex(include_bytes!(
|
||||
"fixtures/data/tlsnotary.org/signature"
|
||||
))
|
||||
.unwrap(),
|
||||
},
|
||||
handshake: HandshakeData::V1_2(HandshakeDataV1_2 {
|
||||
binding: CertBinding::V1_2(CertBindingV1_2 {
|
||||
client_random: <[u8; 32]>::from_hex(include_bytes!(
|
||||
"fixtures/data/tlsnotary.org/client_random"
|
||||
))
|
||||
@@ -73,26 +78,28 @@ impl ConnectionFixture {
|
||||
/// Returns a connection fixture for appliedzkp.org.
|
||||
pub fn appliedzkp(transcript_length: TranscriptLength) -> Self {
|
||||
ConnectionFixture {
|
||||
server_name: ServerName::new("appliedzkp.org".to_string()),
|
||||
server_name: ServerName::Dns(DnsName::try_from("appliedzkp.org").unwrap()),
|
||||
connection_info: ConnectionInfo {
|
||||
time: 1671637529,
|
||||
version: TlsVersion::V1_2,
|
||||
transcript_length,
|
||||
},
|
||||
server_cert_data: ServerCertData {
|
||||
server_cert_data: HandshakeData {
|
||||
certs: vec![
|
||||
Certificate(include_bytes!("fixtures/data/appliedzkp.org/ee.der").to_vec()),
|
||||
Certificate(include_bytes!("fixtures/data/appliedzkp.org/inter.der").to_vec()),
|
||||
Certificate(include_bytes!("fixtures/data/appliedzkp.org/ca.der").to_vec()),
|
||||
CertificateDer(include_bytes!("fixtures/data/appliedzkp.org/ee.der").to_vec()),
|
||||
CertificateDer(
|
||||
include_bytes!("fixtures/data/appliedzkp.org/inter.der").to_vec(),
|
||||
),
|
||||
CertificateDer(include_bytes!("fixtures/data/appliedzkp.org/ca.der").to_vec()),
|
||||
],
|
||||
sig: ServerSignature {
|
||||
scheme: SignatureScheme::ECDSA_NISTP256_SHA256,
|
||||
alg: SignatureAlgorithm::ECDSA_NISTP256_SHA256,
|
||||
sig: Vec::<u8>::from_hex(include_bytes!(
|
||||
"fixtures/data/appliedzkp.org/signature"
|
||||
))
|
||||
.unwrap(),
|
||||
},
|
||||
handshake: HandshakeData::V1_2(HandshakeDataV1_2 {
|
||||
binding: CertBinding::V1_2(CertBindingV1_2 {
|
||||
client_random: <[u8; 32]>::from_hex(include_bytes!(
|
||||
"fixtures/data/appliedzkp.org/client_random"
|
||||
))
|
||||
@@ -115,10 +122,10 @@ impl ConnectionFixture {
|
||||
|
||||
/// Returns the server_ephemeral_key fixture.
|
||||
pub fn server_ephemeral_key(&self) -> &ServerEphemKey {
|
||||
let HandshakeData::V1_2(HandshakeDataV1_2 {
|
||||
let CertBinding::V1_2(CertBindingV1_2 {
|
||||
server_ephemeral_key,
|
||||
..
|
||||
}) = &self.server_cert_data.handshake;
|
||||
}) = &self.server_cert_data.binding;
|
||||
server_ephemeral_key
|
||||
}
|
||||
}
|
||||
|
||||
201
crates/core/src/fixtures/transcript.rs
Normal file
201
crates/core/src/fixtures/transcript.rs
Normal file
@@ -0,0 +1,201 @@
|
||||
//! Transcript fixtures for testing.
|
||||
|
||||
use aead::Payload as AeadPayload;
|
||||
use aes_gcm::{aead::Aead, Aes128Gcm, NewAead};
|
||||
#[allow(deprecated)]
|
||||
use generic_array::GenericArray;
|
||||
use rand::{rngs::StdRng, Rng, SeedableRng};
|
||||
use tls_core::msgs::{
|
||||
base::Payload,
|
||||
codec::Codec,
|
||||
enums::{HandshakeType, ProtocolVersion},
|
||||
handshake::{HandshakeMessagePayload, HandshakePayload},
|
||||
message::{OpaqueMessage, PlainMessage},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
connection::{TranscriptLength, VerifyData},
|
||||
fixtures::ConnectionFixture,
|
||||
transcript::{ContentType, Record, TlsTranscript},
|
||||
};
|
||||
|
||||
/// The key used for encryption of the sent and received transcript.
|
||||
pub const KEY: [u8; 16] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
|
||||
|
||||
/// The iv used for encryption of the sent and received transcript.
|
||||
pub const IV: [u8; 4] = [1, 3, 3, 7];
|
||||
|
||||
/// The record size in bytes.
|
||||
pub const RECORD_SIZE: usize = 512;
|
||||
|
||||
/// Creates a transript fixture for testing.
|
||||
pub fn transcript_fixture(sent: &[u8], recv: &[u8]) -> TlsTranscript {
|
||||
TranscriptGenerator::new(KEY, IV).generate(sent, recv)
|
||||
}
|
||||
|
||||
struct TranscriptGenerator {
|
||||
key: [u8; 16],
|
||||
iv: [u8; 4],
|
||||
}
|
||||
|
||||
impl TranscriptGenerator {
|
||||
fn new(key: [u8; 16], iv: [u8; 4]) -> Self {
|
||||
Self { key, iv }
|
||||
}
|
||||
|
||||
fn generate(&self, sent: &[u8], recv: &[u8]) -> TlsTranscript {
|
||||
let mut rng = StdRng::from_seed([1; 32]);
|
||||
|
||||
let transcript_len = TranscriptLength {
|
||||
sent: sent.len() as u32,
|
||||
received: recv.len() as u32,
|
||||
};
|
||||
let tlsn = ConnectionFixture::tlsnotary(transcript_len);
|
||||
|
||||
let time = tlsn.connection_info.time;
|
||||
let version = tlsn.connection_info.version;
|
||||
let server_cert_chain = tlsn.server_cert_data.certs;
|
||||
let server_signature = tlsn.server_cert_data.sig;
|
||||
let cert_binding = tlsn.server_cert_data.binding;
|
||||
|
||||
let cf_vd: [u8; 12] = rng.random();
|
||||
let sf_vd: [u8; 12] = rng.random();
|
||||
|
||||
let verify_data = VerifyData {
|
||||
client_finished: cf_vd.to_vec(),
|
||||
server_finished: sf_vd.to_vec(),
|
||||
};
|
||||
|
||||
let sent = self.gen_records(cf_vd, sent);
|
||||
let recv = self.gen_records(sf_vd, recv);
|
||||
|
||||
TlsTranscript::new(
|
||||
time,
|
||||
version,
|
||||
Some(server_cert_chain),
|
||||
Some(server_signature),
|
||||
cert_binding,
|
||||
verify_data,
|
||||
sent,
|
||||
recv,
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn gen_records(&self, vd: [u8; 12], plaintext: &[u8]) -> Vec<Record> {
|
||||
let mut records = Vec::new();
|
||||
|
||||
let handshake = self.gen_handshake(vd);
|
||||
records.push(handshake);
|
||||
|
||||
for (seq, msg) in (1_u64..).zip(plaintext.chunks(RECORD_SIZE)) {
|
||||
let record = self.gen_app_data(seq, msg);
|
||||
records.push(record);
|
||||
}
|
||||
|
||||
records
|
||||
}
|
||||
|
||||
fn gen_app_data(&self, seq: u64, plaintext: &[u8]) -> Record {
|
||||
assert!(
|
||||
plaintext.len() <= 1 << 14,
|
||||
"plaintext len per record must be smaller than 2^14 bytes"
|
||||
);
|
||||
|
||||
let explicit_nonce: [u8; 8] = seq.to_be_bytes();
|
||||
let msg = PlainMessage {
|
||||
typ: ContentType::ApplicationData.into(),
|
||||
version: ProtocolVersion::TLSv1_2,
|
||||
payload: Payload::new(plaintext),
|
||||
};
|
||||
let opaque = aes_gcm_encrypt(self.key, self.iv, seq, explicit_nonce, &msg);
|
||||
|
||||
let mut payload = opaque.payload.0;
|
||||
let mut ciphertext = payload.split_off(8);
|
||||
let tag = ciphertext.split_off(ciphertext.len() - 16);
|
||||
|
||||
Record {
|
||||
seq,
|
||||
typ: ContentType::ApplicationData,
|
||||
plaintext: Some(plaintext.to_vec()),
|
||||
explicit_nonce: explicit_nonce.to_vec(),
|
||||
ciphertext,
|
||||
tag: Some(tag),
|
||||
}
|
||||
}
|
||||
|
||||
fn gen_handshake(&self, vd: [u8; 12]) -> Record {
|
||||
let seq = 0_u64;
|
||||
let explicit_nonce = seq.to_be_bytes();
|
||||
|
||||
let mut plaintext = Vec::new();
|
||||
|
||||
let payload = Payload(vd.to_vec());
|
||||
let hs_payload = HandshakePayload::Finished(payload);
|
||||
let handshake_message = HandshakeMessagePayload {
|
||||
typ: HandshakeType::Finished,
|
||||
payload: hs_payload,
|
||||
};
|
||||
handshake_message.encode(&mut plaintext);
|
||||
|
||||
let msg = PlainMessage {
|
||||
typ: ContentType::Handshake.into(),
|
||||
version: ProtocolVersion::TLSv1_2,
|
||||
payload: Payload::new(plaintext.clone()),
|
||||
};
|
||||
|
||||
let opaque = aes_gcm_encrypt(self.key, self.iv, seq, explicit_nonce, &msg);
|
||||
let mut payload = opaque.payload.0;
|
||||
let mut ciphertext = payload.split_off(8);
|
||||
let tag = ciphertext.split_off(ciphertext.len() - 16);
|
||||
|
||||
Record {
|
||||
seq,
|
||||
typ: ContentType::Handshake,
|
||||
plaintext: Some(plaintext),
|
||||
explicit_nonce: explicit_nonce.to_vec(),
|
||||
ciphertext,
|
||||
tag: Some(tag),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn aes_gcm_encrypt(
|
||||
key: [u8; 16],
|
||||
iv: [u8; 4],
|
||||
seq: u64,
|
||||
explicit_nonce: [u8; 8],
|
||||
msg: &PlainMessage,
|
||||
) -> OpaqueMessage {
|
||||
let mut aad = [0u8; 13];
|
||||
|
||||
aad[..8].copy_from_slice(&seq.to_be_bytes());
|
||||
aad[8] = msg.typ.get_u8();
|
||||
aad[9..11].copy_from_slice(&msg.version.get_u16().to_be_bytes());
|
||||
aad[11..13].copy_from_slice(&(msg.payload.0.len() as u16).to_be_bytes());
|
||||
let payload = AeadPayload {
|
||||
msg: &msg.payload.0,
|
||||
aad: &aad,
|
||||
};
|
||||
|
||||
let mut nonce = [0u8; 12];
|
||||
nonce[..4].copy_from_slice(&iv);
|
||||
nonce[4..].copy_from_slice(&explicit_nonce);
|
||||
#[allow(deprecated)]
|
||||
let nonce = GenericArray::from_slice(&nonce);
|
||||
let cipher = Aes128Gcm::new_from_slice(&key).unwrap();
|
||||
|
||||
// ciphertext will have the MAC appended
|
||||
let ciphertext = cipher.encrypt(nonce, payload).unwrap();
|
||||
|
||||
// prepend the explicit nonce
|
||||
let mut nonce_ct_mac = vec![0u8; 0];
|
||||
nonce_ct_mac.extend(explicit_nonce.iter());
|
||||
nonce_ct_mac.extend(ciphertext.iter());
|
||||
|
||||
OpaqueMessage {
|
||||
typ: msg.typ,
|
||||
version: msg.version,
|
||||
payload: Payload::new(nonce_ct_mac),
|
||||
}
|
||||
}
|
||||
@@ -191,6 +191,11 @@ impl Hash {
|
||||
len: value.len(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a byte slice of the hash value.
|
||||
pub fn as_bytes(&self) -> &[u8] {
|
||||
&self.value[..self.len]
|
||||
}
|
||||
}
|
||||
|
||||
impl rs_merkle::Hash for Hash {
|
||||
@@ -291,14 +296,14 @@ mod sha2 {
|
||||
fn hash(&self, data: &[u8]) -> super::Hash {
|
||||
let mut hasher = ::sha2::Sha256::default();
|
||||
hasher.update(data);
|
||||
super::Hash::new(hasher.finalize().as_slice())
|
||||
super::Hash::new(hasher.finalize().as_ref())
|
||||
}
|
||||
|
||||
fn hash_prefixed(&self, prefix: &[u8], data: &[u8]) -> super::Hash {
|
||||
let mut hasher = ::sha2::Sha256::default();
|
||||
hasher.update(prefix);
|
||||
hasher.update(data);
|
||||
super::Hash::new(hasher.finalize().as_slice())
|
||||
super::Hash::new(hasher.finalize().as_ref())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,205 +10,22 @@ pub mod fixtures;
|
||||
pub mod hash;
|
||||
pub mod merkle;
|
||||
pub mod transcript;
|
||||
pub mod webpki;
|
||||
pub use rangeset;
|
||||
pub mod config;
|
||||
pub(crate) mod display;
|
||||
|
||||
use rangeset::ToRangeSet;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
connection::{ServerCertData, ServerName},
|
||||
connection::ServerName,
|
||||
transcript::{
|
||||
Direction, Idx, PartialTranscript, Transcript, TranscriptCommitConfig,
|
||||
TranscriptCommitRequest, TranscriptCommitment, TranscriptSecret,
|
||||
encoding::EncoderSecret, PartialTranscript, TranscriptCommitment, TranscriptSecret,
|
||||
},
|
||||
};
|
||||
|
||||
/// Configuration to prove information to the verifier.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ProveConfig {
|
||||
server_identity: bool,
|
||||
transcript: Option<PartialTranscript>,
|
||||
transcript_commit: Option<TranscriptCommitConfig>,
|
||||
}
|
||||
|
||||
impl ProveConfig {
|
||||
/// Creates a new builder.
|
||||
pub fn builder(transcript: &Transcript) -> ProveConfigBuilder {
|
||||
ProveConfigBuilder::new(transcript)
|
||||
}
|
||||
|
||||
/// Returns `true` if the server identity is to be proven.
|
||||
pub fn server_identity(&self) -> bool {
|
||||
self.server_identity
|
||||
}
|
||||
|
||||
/// Returns the transcript to be proven.
|
||||
pub fn transcript(&self) -> Option<&PartialTranscript> {
|
||||
self.transcript.as_ref()
|
||||
}
|
||||
|
||||
/// Returns the transcript commitment configuration.
|
||||
pub fn transcript_commit(&self) -> Option<&TranscriptCommitConfig> {
|
||||
self.transcript_commit.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
/// Builder for [`ProveConfig`].
|
||||
#[derive(Debug)]
|
||||
pub struct ProveConfigBuilder<'a> {
|
||||
transcript: &'a Transcript,
|
||||
server_identity: bool,
|
||||
reveal_sent: Idx,
|
||||
reveal_recv: Idx,
|
||||
transcript_commit: Option<TranscriptCommitConfig>,
|
||||
}
|
||||
|
||||
impl<'a> ProveConfigBuilder<'a> {
|
||||
/// Creates a new builder.
|
||||
pub fn new(transcript: &'a Transcript) -> Self {
|
||||
Self {
|
||||
transcript,
|
||||
server_identity: false,
|
||||
reveal_sent: Idx::default(),
|
||||
reveal_recv: Idx::default(),
|
||||
transcript_commit: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Proves the server identity.
|
||||
pub fn server_identity(&mut self) -> &mut Self {
|
||||
self.server_identity = true;
|
||||
self
|
||||
}
|
||||
|
||||
/// Configures transcript commitments.
|
||||
pub fn transcript_commit(&mut self, transcript_commit: TranscriptCommitConfig) -> &mut Self {
|
||||
self.transcript_commit = Some(transcript_commit);
|
||||
self
|
||||
}
|
||||
|
||||
/// Reveals the given ranges of the transcript.
|
||||
pub fn reveal(
|
||||
&mut self,
|
||||
direction: Direction,
|
||||
ranges: &dyn ToRangeSet<usize>,
|
||||
) -> Result<&mut Self, ProveConfigBuilderError> {
|
||||
let idx = Idx::new(ranges.to_range_set());
|
||||
|
||||
if idx.end() > self.transcript.len_of_direction(direction) {
|
||||
return Err(ProveConfigBuilderError(
|
||||
ProveConfigBuilderErrorRepr::IndexOutOfBounds {
|
||||
direction,
|
||||
actual: idx.end(),
|
||||
len: self.transcript.len_of_direction(direction),
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
match direction {
|
||||
Direction::Sent => self.reveal_sent.union_mut(&idx),
|
||||
Direction::Received => self.reveal_recv.union_mut(&idx),
|
||||
}
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
/// Reveals the given ranges of the sent data transcript.
|
||||
pub fn reveal_sent(
|
||||
&mut self,
|
||||
ranges: &dyn ToRangeSet<usize>,
|
||||
) -> Result<&mut Self, ProveConfigBuilderError> {
|
||||
self.reveal(Direction::Sent, ranges)
|
||||
}
|
||||
|
||||
/// Reveals the given ranges of the received data transcript.
|
||||
pub fn reveal_recv(
|
||||
&mut self,
|
||||
ranges: &dyn ToRangeSet<usize>,
|
||||
) -> Result<&mut Self, ProveConfigBuilderError> {
|
||||
self.reveal(Direction::Received, ranges)
|
||||
}
|
||||
|
||||
/// Builds the configuration.
|
||||
pub fn build(self) -> Result<ProveConfig, ProveConfigBuilderError> {
|
||||
let transcript = if !self.reveal_sent.is_empty() || !self.reveal_recv.is_empty() {
|
||||
Some(
|
||||
self.transcript
|
||||
.to_partial(self.reveal_sent, self.reveal_recv),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Ok(ProveConfig {
|
||||
server_identity: self.server_identity,
|
||||
transcript,
|
||||
transcript_commit: self.transcript_commit,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Error for [`ProveConfigBuilder`].
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[error(transparent)]
|
||||
pub struct ProveConfigBuilderError(#[from] ProveConfigBuilderErrorRepr);
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
enum ProveConfigBuilderErrorRepr {
|
||||
#[error("range is out of bounds of the transcript ({direction}): {actual} > {len}")]
|
||||
IndexOutOfBounds {
|
||||
direction: Direction,
|
||||
actual: usize,
|
||||
len: usize,
|
||||
},
|
||||
}
|
||||
|
||||
/// Configuration to verify information from the prover.
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct VerifyConfig {}
|
||||
|
||||
impl VerifyConfig {
|
||||
/// Creates a new builder.
|
||||
pub fn builder() -> VerifyConfigBuilder {
|
||||
VerifyConfigBuilder::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// Builder for [`VerifyConfig`].
|
||||
#[derive(Debug, Default)]
|
||||
pub struct VerifyConfigBuilder {}
|
||||
|
||||
impl VerifyConfigBuilder {
|
||||
/// Creates a new builder.
|
||||
pub fn new() -> Self {
|
||||
Self {}
|
||||
}
|
||||
|
||||
/// Builds the configuration.
|
||||
pub fn build(self) -> Result<VerifyConfig, VerifyConfigBuilderError> {
|
||||
Ok(VerifyConfig {})
|
||||
}
|
||||
}
|
||||
|
||||
/// Error for [`VerifyConfigBuilder`].
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[error(transparent)]
|
||||
pub struct VerifyConfigBuilderError(#[from] VerifyConfigBuilderErrorRepr);
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
enum VerifyConfigBuilderErrorRepr {}
|
||||
|
||||
/// Payload sent to the verifier.
|
||||
#[doc(hidden)]
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct ProvePayload {
|
||||
/// Server identity data.
|
||||
pub server_identity: Option<(ServerName, ServerCertData)>,
|
||||
/// Transcript data.
|
||||
pub transcript: Option<PartialTranscript>,
|
||||
/// Transcript commitment configuration.
|
||||
pub transcript_commit: Option<TranscriptCommitRequest>,
|
||||
}
|
||||
|
||||
/// Prover output.
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct ProverOutput {
|
||||
/// Transcript commitments.
|
||||
pub transcript_commitments: Vec<TranscriptCommitment>,
|
||||
@@ -219,11 +36,14 @@ pub struct ProverOutput {
|
||||
opaque_debug::implement!(ProverOutput);
|
||||
|
||||
/// Verifier output.
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct VerifierOutput {
|
||||
/// Server identity.
|
||||
pub server_name: Option<ServerName>,
|
||||
/// Transcript data.
|
||||
pub transcript: Option<PartialTranscript>,
|
||||
/// Encoding commitment secret.
|
||||
pub encoder_secret: Option<EncoderSecret>,
|
||||
/// Transcript commitments.
|
||||
pub transcript_commitments: Vec<TranscriptCommitment>,
|
||||
}
|
||||
|
||||
@@ -26,7 +26,11 @@ mod tls;
|
||||
|
||||
use std::{fmt, ops::Range};
|
||||
|
||||
use rangeset::{Difference, IndexRanges, RangeSet, Subset, ToRangeSet, Union, UnionMut};
|
||||
use rangeset::{
|
||||
iter::RangeIterator,
|
||||
ops::{Index, Set},
|
||||
set::RangeSet,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::connection::TranscriptLength;
|
||||
@@ -38,7 +42,7 @@ pub use commit::{
|
||||
pub use proof::{
|
||||
TranscriptProof, TranscriptProofBuilder, TranscriptProofBuilderError, TranscriptProofError,
|
||||
};
|
||||
pub use tls::{Record, TlsTranscript};
|
||||
pub use tls::{ContentType, Record, TlsTranscript};
|
||||
|
||||
/// A transcript contains the plaintext of all application data communicated
|
||||
/// between the Prover and the Server.
|
||||
@@ -95,19 +99,25 @@ impl Transcript {
|
||||
|
||||
/// Returns the subsequence of the transcript with the provided index,
|
||||
/// returning `None` if the index is out of bounds.
|
||||
pub fn get(&self, direction: Direction, idx: &Idx) -> Option<Subsequence> {
|
||||
pub fn get(&self, direction: Direction, idx: &RangeSet<usize>) -> Option<Subsequence> {
|
||||
let data = match direction {
|
||||
Direction::Sent => &self.sent,
|
||||
Direction::Received => &self.received,
|
||||
};
|
||||
|
||||
if idx.end() > data.len() {
|
||||
if idx.end().unwrap_or(0) > data.len() {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(
|
||||
Subsequence::new(idx.clone(), data.index_ranges(&idx.0))
|
||||
.expect("data is same length as index"),
|
||||
Subsequence::new(
|
||||
idx.clone(),
|
||||
data.index(idx).fold(Vec::new(), |mut acc, s| {
|
||||
acc.extend_from_slice(s);
|
||||
acc
|
||||
}),
|
||||
)
|
||||
.expect("data is same length as index"),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -121,15 +131,19 @@ impl Transcript {
|
||||
///
|
||||
/// * `sent_idx` - The indices of the sent data to include.
|
||||
/// * `recv_idx` - The indices of the received data to include.
|
||||
pub fn to_partial(&self, sent_idx: Idx, recv_idx: Idx) -> PartialTranscript {
|
||||
pub fn to_partial(
|
||||
&self,
|
||||
sent_idx: RangeSet<usize>,
|
||||
recv_idx: RangeSet<usize>,
|
||||
) -> PartialTranscript {
|
||||
let mut sent = vec![0; self.sent.len()];
|
||||
let mut received = vec![0; self.received.len()];
|
||||
|
||||
for range in sent_idx.iter_ranges() {
|
||||
for range in sent_idx.iter() {
|
||||
sent[range.clone()].copy_from_slice(&self.sent[range]);
|
||||
}
|
||||
|
||||
for range in recv_idx.iter_ranges() {
|
||||
for range in recv_idx.iter() {
|
||||
received[range.clone()].copy_from_slice(&self.received[range]);
|
||||
}
|
||||
|
||||
@@ -156,9 +170,9 @@ pub struct PartialTranscript {
|
||||
/// Data received by the Prover from the Server.
|
||||
received: Vec<u8>,
|
||||
/// Index of `sent` which have been authenticated.
|
||||
sent_authed_idx: Idx,
|
||||
sent_authed_idx: RangeSet<usize>,
|
||||
/// Index of `received` which have been authenticated.
|
||||
received_authed_idx: Idx,
|
||||
received_authed_idx: RangeSet<usize>,
|
||||
}
|
||||
|
||||
/// `PartialTranscript` in a compressed form.
|
||||
@@ -170,9 +184,9 @@ pub struct CompressedPartialTranscript {
|
||||
/// Received data which has been authenticated.
|
||||
received_authed: Vec<u8>,
|
||||
/// Index of `sent_authed`.
|
||||
sent_idx: Idx,
|
||||
sent_idx: RangeSet<usize>,
|
||||
/// Index of `received_authed`.
|
||||
recv_idx: Idx,
|
||||
recv_idx: RangeSet<usize>,
|
||||
/// Total bytelength of sent data in the original partial transcript.
|
||||
sent_total: usize,
|
||||
/// Total bytelength of received data in the original partial transcript.
|
||||
@@ -182,12 +196,20 @@ pub struct CompressedPartialTranscript {
|
||||
impl From<PartialTranscript> for CompressedPartialTranscript {
|
||||
fn from(uncompressed: PartialTranscript) -> Self {
|
||||
Self {
|
||||
sent_authed: uncompressed
|
||||
.sent
|
||||
.index_ranges(&uncompressed.sent_authed_idx.0),
|
||||
sent_authed: uncompressed.sent.index(&uncompressed.sent_authed_idx).fold(
|
||||
Vec::new(),
|
||||
|mut acc, s| {
|
||||
acc.extend_from_slice(s);
|
||||
acc
|
||||
},
|
||||
),
|
||||
received_authed: uncompressed
|
||||
.received
|
||||
.index_ranges(&uncompressed.received_authed_idx.0),
|
||||
.index(&uncompressed.received_authed_idx)
|
||||
.fold(Vec::new(), |mut acc, s| {
|
||||
acc.extend_from_slice(s);
|
||||
acc
|
||||
}),
|
||||
sent_idx: uncompressed.sent_authed_idx,
|
||||
recv_idx: uncompressed.received_authed_idx,
|
||||
sent_total: uncompressed.sent.len(),
|
||||
@@ -203,7 +225,7 @@ impl From<CompressedPartialTranscript> for PartialTranscript {
|
||||
|
||||
let mut offset = 0;
|
||||
|
||||
for range in compressed.sent_idx.iter_ranges() {
|
||||
for range in compressed.sent_idx.iter() {
|
||||
sent[range.clone()]
|
||||
.copy_from_slice(&compressed.sent_authed[offset..offset + range.len()]);
|
||||
offset += range.len();
|
||||
@@ -211,7 +233,7 @@ impl From<CompressedPartialTranscript> for PartialTranscript {
|
||||
|
||||
let mut offset = 0;
|
||||
|
||||
for range in compressed.recv_idx.iter_ranges() {
|
||||
for range in compressed.recv_idx.iter() {
|
||||
received[range.clone()]
|
||||
.copy_from_slice(&compressed.received_authed[offset..offset + range.len()]);
|
||||
offset += range.len();
|
||||
@@ -237,8 +259,8 @@ impl PartialTranscript {
|
||||
Self {
|
||||
sent: vec![0; sent_len],
|
||||
received: vec![0; received_len],
|
||||
sent_authed_idx: Idx::default(),
|
||||
received_authed_idx: Idx::default(),
|
||||
sent_authed_idx: RangeSet::default(),
|
||||
received_authed_idx: RangeSet::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -259,10 +281,10 @@ impl PartialTranscript {
|
||||
}
|
||||
|
||||
/// Returns whether the index is in bounds of the transcript.
|
||||
pub fn contains(&self, direction: Direction, idx: &Idx) -> bool {
|
||||
pub fn contains(&self, direction: Direction, idx: &RangeSet<usize>) -> bool {
|
||||
match direction {
|
||||
Direction::Sent => idx.end() <= self.sent.len(),
|
||||
Direction::Received => idx.end() <= self.received.len(),
|
||||
Direction::Sent => idx.end().unwrap_or(0) <= self.sent.len(),
|
||||
Direction::Received => idx.end().unwrap_or(0) <= self.received.len(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -289,23 +311,27 @@ impl PartialTranscript {
|
||||
}
|
||||
|
||||
/// Returns the index of sent data which have been authenticated.
|
||||
pub fn sent_authed(&self) -> &Idx {
|
||||
pub fn sent_authed(&self) -> &RangeSet<usize> {
|
||||
&self.sent_authed_idx
|
||||
}
|
||||
|
||||
/// Returns the index of received data which have been authenticated.
|
||||
pub fn received_authed(&self) -> &Idx {
|
||||
pub fn received_authed(&self) -> &RangeSet<usize> {
|
||||
&self.received_authed_idx
|
||||
}
|
||||
|
||||
/// Returns the index of sent data which haven't been authenticated.
|
||||
pub fn sent_unauthed(&self) -> Idx {
|
||||
Idx(RangeSet::from(0..self.sent.len()).difference(&self.sent_authed_idx.0))
|
||||
pub fn sent_unauthed(&self) -> RangeSet<usize> {
|
||||
(0..self.sent.len())
|
||||
.difference(&self.sent_authed_idx)
|
||||
.into_set()
|
||||
}
|
||||
|
||||
/// Returns the index of received data which haven't been authenticated.
|
||||
pub fn received_unauthed(&self) -> Idx {
|
||||
Idx(RangeSet::from(0..self.received.len()).difference(&self.received_authed_idx.0))
|
||||
pub fn received_unauthed(&self) -> RangeSet<usize> {
|
||||
(0..self.received.len())
|
||||
.difference(&self.received_authed_idx)
|
||||
.into_set()
|
||||
}
|
||||
|
||||
/// Returns an iterator over the authenticated data in the transcript.
|
||||
@@ -315,7 +341,7 @@ impl PartialTranscript {
|
||||
Direction::Received => (&self.received, &self.received_authed_idx),
|
||||
};
|
||||
|
||||
authed.0.iter().map(|i| data[i])
|
||||
authed.iter_values().map(move |i| data[i])
|
||||
}
|
||||
|
||||
/// Unions the authenticated data of this transcript with another.
|
||||
@@ -335,26 +361,20 @@ impl PartialTranscript {
|
||||
"received data are not the same length"
|
||||
);
|
||||
|
||||
for range in other
|
||||
.sent_authed_idx
|
||||
.0
|
||||
.difference(&self.sent_authed_idx.0)
|
||||
.iter_ranges()
|
||||
{
|
||||
for range in other.sent_authed_idx.difference(&self.sent_authed_idx) {
|
||||
self.sent[range.clone()].copy_from_slice(&other.sent[range]);
|
||||
}
|
||||
|
||||
for range in other
|
||||
.received_authed_idx
|
||||
.0
|
||||
.difference(&self.received_authed_idx.0)
|
||||
.iter_ranges()
|
||||
.difference(&self.received_authed_idx)
|
||||
{
|
||||
self.received[range.clone()].copy_from_slice(&other.received[range]);
|
||||
}
|
||||
|
||||
self.sent_authed_idx = self.sent_authed_idx.union(&other.sent_authed_idx);
|
||||
self.received_authed_idx = self.received_authed_idx.union(&other.received_authed_idx);
|
||||
self.sent_authed_idx.union_mut(&other.sent_authed_idx);
|
||||
self.received_authed_idx
|
||||
.union_mut(&other.received_authed_idx);
|
||||
}
|
||||
|
||||
/// Unions an authenticated subsequence into this transcript.
|
||||
@@ -366,11 +386,11 @@ impl PartialTranscript {
|
||||
match direction {
|
||||
Direction::Sent => {
|
||||
seq.copy_to(&mut self.sent);
|
||||
self.sent_authed_idx = self.sent_authed_idx.union(&seq.idx);
|
||||
self.sent_authed_idx.union_mut(&seq.idx);
|
||||
}
|
||||
Direction::Received => {
|
||||
seq.copy_to(&mut self.received);
|
||||
self.received_authed_idx = self.received_authed_idx.union(&seq.idx);
|
||||
self.received_authed_idx.union_mut(&seq.idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -381,10 +401,10 @@ impl PartialTranscript {
|
||||
///
|
||||
/// * `value` - The value to set the unauthenticated bytes to
|
||||
pub fn set_unauthed(&mut self, value: u8) {
|
||||
for range in self.sent_unauthed().iter_ranges() {
|
||||
for range in self.sent_unauthed().iter() {
|
||||
self.sent[range].fill(value);
|
||||
}
|
||||
for range in self.received_unauthed().iter_ranges() {
|
||||
for range in self.received_unauthed().iter() {
|
||||
self.received[range].fill(value);
|
||||
}
|
||||
}
|
||||
@@ -399,13 +419,13 @@ impl PartialTranscript {
|
||||
pub fn set_unauthed_range(&mut self, value: u8, direction: Direction, range: Range<usize>) {
|
||||
match direction {
|
||||
Direction::Sent => {
|
||||
for range in range.difference(&self.sent_authed_idx.0).iter_ranges() {
|
||||
self.sent[range].fill(value);
|
||||
for r in range.difference(&self.sent_authed_idx) {
|
||||
self.sent[r].fill(value);
|
||||
}
|
||||
}
|
||||
Direction::Received => {
|
||||
for range in range.difference(&self.received_authed_idx.0).iter_ranges() {
|
||||
self.received[range].fill(value);
|
||||
for r in range.difference(&self.received_authed_idx) {
|
||||
self.received[r].fill(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -433,130 +453,19 @@ impl fmt::Display for Direction {
|
||||
}
|
||||
}
|
||||
|
||||
/// Transcript index.
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub struct Idx(RangeSet<usize>);
|
||||
|
||||
impl Idx {
|
||||
/// Creates a new index builder.
|
||||
pub fn builder() -> IdxBuilder {
|
||||
IdxBuilder::default()
|
||||
}
|
||||
|
||||
/// Creates an empty index.
|
||||
pub fn empty() -> Self {
|
||||
Self(RangeSet::default())
|
||||
}
|
||||
|
||||
/// Creates a new transcript index.
|
||||
pub fn new(ranges: impl Into<RangeSet<usize>>) -> Self {
|
||||
Self(ranges.into())
|
||||
}
|
||||
|
||||
/// Returns the start of the index.
|
||||
pub fn start(&self) -> usize {
|
||||
self.0.min().unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Returns the end of the index, non-inclusive.
|
||||
pub fn end(&self) -> usize {
|
||||
self.0.end().unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Returns an iterator over the values in the index.
|
||||
pub fn iter(&self) -> impl Iterator<Item = usize> + '_ {
|
||||
self.0.iter()
|
||||
}
|
||||
|
||||
/// Returns an iterator over the ranges of the index.
|
||||
pub fn iter_ranges(&self) -> impl Iterator<Item = Range<usize>> + '_ {
|
||||
self.0.iter_ranges()
|
||||
}
|
||||
|
||||
/// Returns the number of values in the index.
|
||||
pub fn len(&self) -> usize {
|
||||
self.0.len()
|
||||
}
|
||||
|
||||
/// Returns whether the index is empty.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.0.is_empty()
|
||||
}
|
||||
|
||||
/// Returns the number of disjoint ranges in the index.
|
||||
pub fn count(&self) -> usize {
|
||||
self.0.len_ranges()
|
||||
}
|
||||
|
||||
pub(crate) fn as_range_set(&self) -> &RangeSet<usize> {
|
||||
&self.0
|
||||
}
|
||||
|
||||
/// Returns the union of this index with another.
|
||||
pub(crate) fn union(&self, other: &Idx) -> Idx {
|
||||
Idx(self.0.union(&other.0))
|
||||
}
|
||||
|
||||
/// Unions this index with another.
|
||||
pub(crate) fn union_mut(&mut self, other: &Idx) {
|
||||
self.0.union_mut(&other.0);
|
||||
}
|
||||
|
||||
/// Returns the difference between `self` and `other`.
|
||||
pub(crate) fn difference(&self, other: &Idx) -> Idx {
|
||||
Idx(self.0.difference(&other.0))
|
||||
}
|
||||
|
||||
/// Returns `true` if `self` is a subset of `other`.
|
||||
pub(crate) fn is_subset(&self, other: &Idx) -> bool {
|
||||
self.0.is_subset(&other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Idx {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.write_str("Idx([")?;
|
||||
let count = self.0.len_ranges();
|
||||
for (i, range) in self.0.iter_ranges().enumerate() {
|
||||
write!(f, "{}..{}", range.start, range.end)?;
|
||||
if i < count - 1 {
|
||||
write!(f, ", ")?;
|
||||
}
|
||||
}
|
||||
f.write_str("])")?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Builder for [`Idx`].
|
||||
#[derive(Debug, Default)]
|
||||
pub struct IdxBuilder(RangeSet<usize>);
|
||||
|
||||
impl IdxBuilder {
|
||||
/// Unions ranges.
|
||||
pub fn union(self, ranges: &dyn ToRangeSet<usize>) -> Self {
|
||||
IdxBuilder(self.0.union(&ranges.to_range_set()))
|
||||
}
|
||||
|
||||
/// Builds the index.
|
||||
pub fn build(self) -> Idx {
|
||||
Idx(self.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// Transcript subsequence.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(try_from = "validation::SubsequenceUnchecked")]
|
||||
pub struct Subsequence {
|
||||
/// Index of the subsequence.
|
||||
idx: Idx,
|
||||
idx: RangeSet<usize>,
|
||||
/// Data of the subsequence.
|
||||
data: Vec<u8>,
|
||||
}
|
||||
|
||||
impl Subsequence {
|
||||
/// Creates a new subsequence.
|
||||
pub fn new(idx: Idx, data: Vec<u8>) -> Result<Self, InvalidSubsequence> {
|
||||
pub fn new(idx: RangeSet<usize>, data: Vec<u8>) -> Result<Self, InvalidSubsequence> {
|
||||
if idx.len() != data.len() {
|
||||
return Err(InvalidSubsequence(
|
||||
"index length does not match data length",
|
||||
@@ -567,7 +476,7 @@ impl Subsequence {
|
||||
}
|
||||
|
||||
/// Returns the index of the subsequence.
|
||||
pub fn index(&self) -> &Idx {
|
||||
pub fn index(&self) -> &RangeSet<usize> {
|
||||
&self.idx
|
||||
}
|
||||
|
||||
@@ -583,7 +492,7 @@ impl Subsequence {
|
||||
}
|
||||
|
||||
/// Returns the inner parts of the subsequence.
|
||||
pub fn into_parts(self) -> (Idx, Vec<u8>) {
|
||||
pub fn into_parts(self) -> (RangeSet<usize>, Vec<u8>) {
|
||||
(self.idx, self.data)
|
||||
}
|
||||
|
||||
@@ -594,7 +503,7 @@ impl Subsequence {
|
||||
/// Panics if the subsequence ranges are out of bounds.
|
||||
pub(crate) fn copy_to(&self, dest: &mut [u8]) {
|
||||
let mut offset = 0;
|
||||
for range in self.idx.iter_ranges() {
|
||||
for range in self.idx.iter() {
|
||||
dest[range.clone()].copy_from_slice(&self.data[offset..offset + range.len()]);
|
||||
offset += range.len();
|
||||
}
|
||||
@@ -611,7 +520,7 @@ mod validation {
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub(super) struct SubsequenceUnchecked {
|
||||
idx: Idx,
|
||||
idx: RangeSet<usize>,
|
||||
data: Vec<u8>,
|
||||
}
|
||||
|
||||
@@ -633,8 +542,8 @@ mod validation {
|
||||
pub(super) struct CompressedPartialTranscriptUnchecked {
|
||||
sent_authed: Vec<u8>,
|
||||
received_authed: Vec<u8>,
|
||||
sent_idx: Idx,
|
||||
recv_idx: Idx,
|
||||
sent_idx: RangeSet<usize>,
|
||||
recv_idx: RangeSet<usize>,
|
||||
sent_total: usize,
|
||||
recv_total: usize,
|
||||
}
|
||||
@@ -651,8 +560,8 @@ mod validation {
|
||||
));
|
||||
}
|
||||
|
||||
if unchecked.sent_idx.end() > unchecked.sent_total
|
||||
|| unchecked.recv_idx.end() > unchecked.recv_total
|
||||
if unchecked.sent_idx.end().unwrap_or(0) > unchecked.sent_total
|
||||
|| unchecked.recv_idx.end().unwrap_or(0) > unchecked.recv_total
|
||||
{
|
||||
return Err(InvalidCompressedPartialTranscript(
|
||||
"ranges are not in bounds of the data",
|
||||
@@ -681,8 +590,8 @@ mod validation {
|
||||
CompressedPartialTranscriptUnchecked {
|
||||
received_authed: vec![1, 2, 3, 11, 12, 13],
|
||||
sent_authed: vec![4, 5, 6, 14, 15, 16],
|
||||
recv_idx: Idx(RangeSet::new(&[1..4, 11..14])),
|
||||
sent_idx: Idx(RangeSet::new(&[4..7, 14..17])),
|
||||
recv_idx: RangeSet::from([1..4, 11..14]),
|
||||
sent_idx: RangeSet::from([4..7, 14..17]),
|
||||
sent_total: 20,
|
||||
recv_total: 20,
|
||||
}
|
||||
@@ -719,13 +628,7 @@ mod validation {
|
||||
mut partial_transcript: CompressedPartialTranscriptUnchecked,
|
||||
) {
|
||||
// Change the total to be less than the last range's end bound.
|
||||
let end = partial_transcript
|
||||
.sent_idx
|
||||
.0
|
||||
.iter_ranges()
|
||||
.next_back()
|
||||
.unwrap()
|
||||
.end;
|
||||
let end = partial_transcript.sent_idx.iter().next_back().unwrap().end;
|
||||
|
||||
partial_transcript.sent_total = end - 1;
|
||||
|
||||
@@ -753,31 +656,25 @@ mod tests {
|
||||
|
||||
#[fixture]
|
||||
fn partial_transcript() -> PartialTranscript {
|
||||
transcript().to_partial(
|
||||
Idx::new(RangeSet::new(&[1..4, 6..9])),
|
||||
Idx::new(RangeSet::new(&[2..5, 7..10])),
|
||||
)
|
||||
transcript().to_partial(RangeSet::from([1..4, 6..9]), RangeSet::from([2..5, 7..10]))
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_transcript_get_subsequence(transcript: Transcript) {
|
||||
let subseq = transcript
|
||||
.get(Direction::Received, &Idx(RangeSet::from([0..4, 7..10])))
|
||||
.get(Direction::Received, &RangeSet::from([0..4, 7..10]))
|
||||
.unwrap();
|
||||
assert_eq!(subseq.data, vec![0, 1, 2, 3, 7, 8, 9]);
|
||||
|
||||
let subseq = transcript
|
||||
.get(Direction::Sent, &Idx(RangeSet::from([0..4, 9..12])))
|
||||
.get(Direction::Sent, &RangeSet::from([0..4, 9..12]))
|
||||
.unwrap();
|
||||
assert_eq!(subseq.data, vec![0, 1, 2, 3, 9, 10, 11]);
|
||||
|
||||
let subseq = transcript.get(
|
||||
Direction::Received,
|
||||
&Idx(RangeSet::from([0..4, 7..10, 11..13])),
|
||||
);
|
||||
let subseq = transcript.get(Direction::Received, &RangeSet::from([0..4, 7..10, 11..13]));
|
||||
assert_eq!(subseq, None);
|
||||
|
||||
let subseq = transcript.get(Direction::Sent, &Idx(RangeSet::from([0..4, 7..10, 11..13])));
|
||||
let subseq = transcript.get(Direction::Sent, &RangeSet::from([0..4, 7..10, 11..13]));
|
||||
assert_eq!(subseq, None);
|
||||
}
|
||||
|
||||
@@ -790,7 +687,7 @@ mod tests {
|
||||
|
||||
#[rstest]
|
||||
fn test_transcript_to_partial_success(transcript: Transcript) {
|
||||
let partial = transcript.to_partial(Idx::new(0..2), Idx::new(3..7));
|
||||
let partial = transcript.to_partial(RangeSet::from(0..2), RangeSet::from(3..7));
|
||||
assert_eq!(partial.sent_unsafe(), [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
|
||||
assert_eq!(
|
||||
partial.received_unsafe(),
|
||||
@@ -801,29 +698,30 @@ mod tests {
|
||||
#[rstest]
|
||||
#[should_panic]
|
||||
fn test_transcript_to_partial_failure(transcript: Transcript) {
|
||||
let _ = transcript.to_partial(Idx::new(0..14), Idx::new(3..7));
|
||||
let _ = transcript.to_partial(RangeSet::from(0..14), RangeSet::from(3..7));
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_partial_transcript_contains(transcript: Transcript) {
|
||||
let partial = transcript.to_partial(Idx::new(0..2), Idx::new(3..7));
|
||||
assert!(partial.contains(Direction::Sent, &Idx::new([0..5, 7..10])));
|
||||
assert!(!partial.contains(Direction::Received, &Idx::new([4..6, 7..13])))
|
||||
let partial = transcript.to_partial(RangeSet::from(0..2), RangeSet::from(3..7));
|
||||
assert!(partial.contains(Direction::Sent, &RangeSet::from([0..5, 7..10])));
|
||||
assert!(!partial.contains(Direction::Received, &RangeSet::from([4..6, 7..13])))
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_partial_transcript_unauthed(transcript: Transcript) {
|
||||
let partial = transcript.to_partial(Idx::new(0..2), Idx::new(3..7));
|
||||
assert_eq!(partial.sent_unauthed(), Idx::new(2..12));
|
||||
assert_eq!(partial.received_unauthed(), Idx::new([0..3, 7..12]));
|
||||
let partial = transcript.to_partial(RangeSet::from(0..2), RangeSet::from(3..7));
|
||||
assert_eq!(partial.sent_unauthed(), RangeSet::from(2..12));
|
||||
assert_eq!(partial.received_unauthed(), RangeSet::from([0..3, 7..12]));
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_partial_transcript_union_success(transcript: Transcript) {
|
||||
// Non overlapping ranges.
|
||||
let mut simple_partial = transcript.to_partial(Idx::new(0..2), Idx::new(3..7));
|
||||
let mut simple_partial = transcript.to_partial(RangeSet::from(0..2), RangeSet::from(3..7));
|
||||
|
||||
let other_simple_partial = transcript.to_partial(Idx::new(3..5), Idx::new(1..2));
|
||||
let other_simple_partial =
|
||||
transcript.to_partial(RangeSet::from(3..5), RangeSet::from(1..2));
|
||||
|
||||
simple_partial.union_transcript(&other_simple_partial);
|
||||
|
||||
@@ -835,12 +733,16 @@ mod tests {
|
||||
simple_partial.received_unsafe(),
|
||||
[0, 1, 0, 3, 4, 5, 6, 0, 0, 0, 0, 0]
|
||||
);
|
||||
assert_eq!(simple_partial.sent_authed(), &Idx::new([0..2, 3..5]));
|
||||
assert_eq!(simple_partial.received_authed(), &Idx::new([1..2, 3..7]));
|
||||
assert_eq!(simple_partial.sent_authed(), &RangeSet::from([0..2, 3..5]));
|
||||
assert_eq!(
|
||||
simple_partial.received_authed(),
|
||||
&RangeSet::from([1..2, 3..7])
|
||||
);
|
||||
|
||||
// Overwrite with another partial transcript.
|
||||
|
||||
let another_simple_partial = transcript.to_partial(Idx::new(1..4), Idx::new(6..9));
|
||||
let another_simple_partial =
|
||||
transcript.to_partial(RangeSet::from(1..4), RangeSet::from(6..9));
|
||||
|
||||
simple_partial.union_transcript(&another_simple_partial);
|
||||
|
||||
@@ -852,13 +754,17 @@ mod tests {
|
||||
simple_partial.received_unsafe(),
|
||||
[0, 1, 0, 3, 4, 5, 6, 7, 8, 0, 0, 0]
|
||||
);
|
||||
assert_eq!(simple_partial.sent_authed(), &Idx::new(0..5));
|
||||
assert_eq!(simple_partial.received_authed(), &Idx::new([1..2, 3..9]));
|
||||
assert_eq!(simple_partial.sent_authed(), &RangeSet::from(0..5));
|
||||
assert_eq!(
|
||||
simple_partial.received_authed(),
|
||||
&RangeSet::from([1..2, 3..9])
|
||||
);
|
||||
|
||||
// Overlapping ranges.
|
||||
let mut overlap_partial = transcript.to_partial(Idx::new(4..6), Idx::new(3..7));
|
||||
let mut overlap_partial = transcript.to_partial(RangeSet::from(4..6), RangeSet::from(3..7));
|
||||
|
||||
let other_overlap_partial = transcript.to_partial(Idx::new(3..5), Idx::new(5..9));
|
||||
let other_overlap_partial =
|
||||
transcript.to_partial(RangeSet::from(3..5), RangeSet::from(5..9));
|
||||
|
||||
overlap_partial.union_transcript(&other_overlap_partial);
|
||||
|
||||
@@ -870,13 +776,16 @@ mod tests {
|
||||
overlap_partial.received_unsafe(),
|
||||
[0, 0, 0, 3, 4, 5, 6, 7, 8, 0, 0, 0]
|
||||
);
|
||||
assert_eq!(overlap_partial.sent_authed(), &Idx::new([3..5, 4..6]));
|
||||
assert_eq!(overlap_partial.received_authed(), &Idx::new([3..7, 5..9]));
|
||||
assert_eq!(overlap_partial.sent_authed(), &RangeSet::from([3..5, 4..6]));
|
||||
assert_eq!(
|
||||
overlap_partial.received_authed(),
|
||||
&RangeSet::from([3..7, 5..9])
|
||||
);
|
||||
|
||||
// Equal ranges.
|
||||
let mut equal_partial = transcript.to_partial(Idx::new(4..6), Idx::new(3..7));
|
||||
let mut equal_partial = transcript.to_partial(RangeSet::from(4..6), RangeSet::from(3..7));
|
||||
|
||||
let other_equal_partial = transcript.to_partial(Idx::new(4..6), Idx::new(3..7));
|
||||
let other_equal_partial = transcript.to_partial(RangeSet::from(4..6), RangeSet::from(3..7));
|
||||
|
||||
equal_partial.union_transcript(&other_equal_partial);
|
||||
|
||||
@@ -888,13 +797,15 @@ mod tests {
|
||||
equal_partial.received_unsafe(),
|
||||
[0, 0, 0, 3, 4, 5, 6, 0, 0, 0, 0, 0]
|
||||
);
|
||||
assert_eq!(equal_partial.sent_authed(), &Idx::new(4..6));
|
||||
assert_eq!(equal_partial.received_authed(), &Idx::new(3..7));
|
||||
assert_eq!(equal_partial.sent_authed(), &RangeSet::from(4..6));
|
||||
assert_eq!(equal_partial.received_authed(), &RangeSet::from(3..7));
|
||||
|
||||
// Subset ranges.
|
||||
let mut subset_partial = transcript.to_partial(Idx::new(4..10), Idx::new(3..11));
|
||||
let mut subset_partial =
|
||||
transcript.to_partial(RangeSet::from(4..10), RangeSet::from(3..11));
|
||||
|
||||
let other_subset_partial = transcript.to_partial(Idx::new(6..9), Idx::new(5..6));
|
||||
let other_subset_partial =
|
||||
transcript.to_partial(RangeSet::from(6..9), RangeSet::from(5..6));
|
||||
|
||||
subset_partial.union_transcript(&other_subset_partial);
|
||||
|
||||
@@ -906,30 +817,32 @@ mod tests {
|
||||
subset_partial.received_unsafe(),
|
||||
[0, 0, 0, 3, 4, 5, 6, 7, 8, 9, 10, 0]
|
||||
);
|
||||
assert_eq!(subset_partial.sent_authed(), &Idx::new(4..10));
|
||||
assert_eq!(subset_partial.received_authed(), &Idx::new(3..11));
|
||||
assert_eq!(subset_partial.sent_authed(), &RangeSet::from(4..10));
|
||||
assert_eq!(subset_partial.received_authed(), &RangeSet::from(3..11));
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[should_panic]
|
||||
fn test_partial_transcript_union_failure(transcript: Transcript) {
|
||||
let mut partial = transcript.to_partial(Idx::new(4..10), Idx::new(3..11));
|
||||
let mut partial = transcript.to_partial(RangeSet::from(4..10), RangeSet::from(3..11));
|
||||
|
||||
let other_transcript = Transcript::new(
|
||||
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
|
||||
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12],
|
||||
);
|
||||
|
||||
let other_partial = other_transcript.to_partial(Idx::new(6..9), Idx::new(5..6));
|
||||
let other_partial = other_transcript.to_partial(RangeSet::from(6..9), RangeSet::from(5..6));
|
||||
|
||||
partial.union_transcript(&other_partial);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_partial_transcript_union_subseq_success(transcript: Transcript) {
|
||||
let mut partial = transcript.to_partial(Idx::new(4..10), Idx::new(3..11));
|
||||
let sent_seq = Subsequence::new(Idx::new([0..3, 5..7]), [0, 1, 2, 5, 6].into()).unwrap();
|
||||
let recv_seq = Subsequence::new(Idx::new([0..4, 5..7]), [0, 1, 2, 3, 5, 6].into()).unwrap();
|
||||
let mut partial = transcript.to_partial(RangeSet::from(4..10), RangeSet::from(3..11));
|
||||
let sent_seq =
|
||||
Subsequence::new(RangeSet::from([0..3, 5..7]), [0, 1, 2, 5, 6].into()).unwrap();
|
||||
let recv_seq =
|
||||
Subsequence::new(RangeSet::from([0..4, 5..7]), [0, 1, 2, 3, 5, 6].into()).unwrap();
|
||||
|
||||
partial.union_subsequence(Direction::Sent, &sent_seq);
|
||||
partial.union_subsequence(Direction::Received, &recv_seq);
|
||||
@@ -939,30 +852,31 @@ mod tests {
|
||||
partial.received_unsafe(),
|
||||
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 0]
|
||||
);
|
||||
assert_eq!(partial.sent_authed(), &Idx::new([0..3, 4..10]));
|
||||
assert_eq!(partial.received_authed(), &Idx::new(0..11));
|
||||
assert_eq!(partial.sent_authed(), &RangeSet::from([0..3, 4..10]));
|
||||
assert_eq!(partial.received_authed(), &RangeSet::from(0..11));
|
||||
|
||||
// Overwrite with another subseq.
|
||||
let other_sent_seq = Subsequence::new(Idx::new(0..3), [3, 2, 1].into()).unwrap();
|
||||
let other_sent_seq = Subsequence::new(RangeSet::from(0..3), [3, 2, 1].into()).unwrap();
|
||||
|
||||
partial.union_subsequence(Direction::Sent, &other_sent_seq);
|
||||
assert_eq!(partial.sent_unsafe(), [3, 2, 1, 0, 4, 5, 6, 7, 8, 9, 0, 0]);
|
||||
assert_eq!(partial.sent_authed(), &Idx::new([0..3, 4..10]));
|
||||
assert_eq!(partial.sent_authed(), &RangeSet::from([0..3, 4..10]));
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[should_panic]
|
||||
fn test_partial_transcript_union_subseq_failure(transcript: Transcript) {
|
||||
let mut partial = transcript.to_partial(Idx::new(4..10), Idx::new(3..11));
|
||||
let mut partial = transcript.to_partial(RangeSet::from(4..10), RangeSet::from(3..11));
|
||||
|
||||
let sent_seq = Subsequence::new(Idx::new([0..3, 13..15]), [0, 1, 2, 5, 6].into()).unwrap();
|
||||
let sent_seq =
|
||||
Subsequence::new(RangeSet::from([0..3, 13..15]), [0, 1, 2, 5, 6].into()).unwrap();
|
||||
|
||||
partial.union_subsequence(Direction::Sent, &sent_seq);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_partial_transcript_set_unauthed_range(transcript: Transcript) {
|
||||
let mut partial = transcript.to_partial(Idx::new(4..10), Idx::new(3..7));
|
||||
let mut partial = transcript.to_partial(RangeSet::from(4..10), RangeSet::from(3..7));
|
||||
|
||||
partial.set_unauthed_range(7, Direction::Sent, 2..5);
|
||||
partial.set_unauthed_range(5, Direction::Sent, 0..2);
|
||||
@@ -979,13 +893,13 @@ mod tests {
|
||||
#[rstest]
|
||||
#[should_panic]
|
||||
fn test_subsequence_new_invalid_len() {
|
||||
let _ = Subsequence::new(Idx::new([0..3, 5..8]), [0, 1, 2, 5, 6].into()).unwrap();
|
||||
let _ = Subsequence::new(RangeSet::from([0..3, 5..8]), [0, 1, 2, 5, 6].into()).unwrap();
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[should_panic]
|
||||
fn test_subsequence_copy_to_invalid_len() {
|
||||
let seq = Subsequence::new(Idx::new([0..3, 5..7]), [0, 1, 2, 5, 6].into()).unwrap();
|
||||
let seq = Subsequence::new(RangeSet::from([0..3, 5..7]), [0, 1, 2, 5, 6].into()).unwrap();
|
||||
|
||||
let mut data: [u8; 3] = [0, 1, 2];
|
||||
seq.copy_to(&mut data);
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
use std::{collections::HashSet, fmt};
|
||||
|
||||
use rangeset::ToRangeSet;
|
||||
use rangeset::set::ToRangeSet;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
@@ -10,7 +10,7 @@ use crate::{
|
||||
transcript::{
|
||||
encoding::{EncodingCommitment, EncodingTree},
|
||||
hash::{PlaintextHash, PlaintextHashSecret},
|
||||
Direction, Idx, Transcript,
|
||||
Direction, RangeSet, Transcript,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -66,17 +66,17 @@ pub enum TranscriptSecret {
|
||||
}
|
||||
|
||||
/// Configuration for transcript commitments.
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct TranscriptCommitConfig {
|
||||
encoding_hash_alg: HashAlgId,
|
||||
has_encoding: bool,
|
||||
has_hash: bool,
|
||||
commits: Vec<((Direction, Idx), TranscriptCommitmentKind)>,
|
||||
commits: Vec<((Direction, RangeSet<usize>), TranscriptCommitmentKind)>,
|
||||
}
|
||||
|
||||
impl TranscriptCommitConfig {
|
||||
/// Creates a new commit config builder.
|
||||
pub fn builder(transcript: &Transcript) -> TranscriptCommitConfigBuilder {
|
||||
pub fn builder(transcript: &Transcript) -> TranscriptCommitConfigBuilder<'_> {
|
||||
TranscriptCommitConfigBuilder::new(transcript)
|
||||
}
|
||||
|
||||
@@ -96,7 +96,7 @@ impl TranscriptCommitConfig {
|
||||
}
|
||||
|
||||
/// Returns an iterator over the encoding commitment indices.
|
||||
pub fn iter_encoding(&self) -> impl Iterator<Item = &(Direction, Idx)> {
|
||||
pub fn iter_encoding(&self) -> impl Iterator<Item = &(Direction, RangeSet<usize>)> {
|
||||
self.commits.iter().filter_map(|(idx, kind)| match kind {
|
||||
TranscriptCommitmentKind::Encoding => Some(idx),
|
||||
_ => None,
|
||||
@@ -104,7 +104,7 @@ impl TranscriptCommitConfig {
|
||||
}
|
||||
|
||||
/// Returns an iterator over the hash commitment indices.
|
||||
pub fn iter_hash(&self) -> impl Iterator<Item = (&(Direction, Idx), &HashAlgId)> {
|
||||
pub fn iter_hash(&self) -> impl Iterator<Item = (&(Direction, RangeSet<usize>), &HashAlgId)> {
|
||||
self.commits.iter().filter_map(|(idx, kind)| match kind {
|
||||
TranscriptCommitmentKind::Hash { alg } => Some((idx, alg)),
|
||||
_ => None,
|
||||
@@ -114,7 +114,19 @@ impl TranscriptCommitConfig {
|
||||
/// Returns a request for the transcript commitments.
|
||||
pub fn to_request(&self) -> TranscriptCommitRequest {
|
||||
TranscriptCommitRequest {
|
||||
encoding: self.has_encoding,
|
||||
encoding: self.has_encoding.then(|| {
|
||||
let mut sent = RangeSet::default();
|
||||
let mut recv = RangeSet::default();
|
||||
|
||||
for (dir, idx) in self.iter_encoding() {
|
||||
match dir {
|
||||
Direction::Sent => sent.union_mut(idx),
|
||||
Direction::Received => recv.union_mut(idx),
|
||||
}
|
||||
}
|
||||
|
||||
(sent, recv)
|
||||
}),
|
||||
hash: self
|
||||
.iter_hash()
|
||||
.map(|((dir, idx), alg)| (*dir, idx.clone(), *alg))
|
||||
@@ -134,7 +146,7 @@ pub struct TranscriptCommitConfigBuilder<'a> {
|
||||
has_encoding: bool,
|
||||
has_hash: bool,
|
||||
default_kind: TranscriptCommitmentKind,
|
||||
commits: HashSet<((Direction, Idx), TranscriptCommitmentKind)>,
|
||||
commits: HashSet<((Direction, RangeSet<usize>), TranscriptCommitmentKind)>,
|
||||
}
|
||||
|
||||
impl<'a> TranscriptCommitConfigBuilder<'a> {
|
||||
@@ -175,15 +187,15 @@ impl<'a> TranscriptCommitConfigBuilder<'a> {
|
||||
direction: Direction,
|
||||
kind: TranscriptCommitmentKind,
|
||||
) -> Result<&mut Self, TranscriptCommitConfigBuilderError> {
|
||||
let idx = Idx::new(ranges.to_range_set());
|
||||
let idx = ranges.to_range_set();
|
||||
|
||||
if idx.end() > self.transcript.len_of_direction(direction) {
|
||||
if idx.end().unwrap_or(0) > self.transcript.len_of_direction(direction) {
|
||||
return Err(TranscriptCommitConfigBuilderError::new(
|
||||
ErrorKind::Index,
|
||||
format!(
|
||||
"range is out of bounds of the transcript ({}): {} > {}",
|
||||
direction,
|
||||
idx.end(),
|
||||
idx.end().unwrap_or(0),
|
||||
self.transcript.len_of_direction(direction)
|
||||
),
|
||||
));
|
||||
@@ -289,14 +301,14 @@ impl fmt::Display for TranscriptCommitConfigBuilderError {
|
||||
/// Request to compute transcript commitments.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct TranscriptCommitRequest {
|
||||
encoding: bool,
|
||||
hash: Vec<(Direction, Idx, HashAlgId)>,
|
||||
encoding: Option<(RangeSet<usize>, RangeSet<usize>)>,
|
||||
hash: Vec<(Direction, RangeSet<usize>, HashAlgId)>,
|
||||
}
|
||||
|
||||
impl TranscriptCommitRequest {
|
||||
/// Returns `true` if an encoding commitment is requested.
|
||||
pub fn encoding(&self) -> bool {
|
||||
self.encoding
|
||||
pub fn has_encoding(&self) -> bool {
|
||||
self.encoding.is_some()
|
||||
}
|
||||
|
||||
/// Returns `true` if a hash commitment is requested.
|
||||
@@ -305,9 +317,14 @@ impl TranscriptCommitRequest {
|
||||
}
|
||||
|
||||
/// Returns an iterator over the hash commitments.
|
||||
pub fn iter_hash(&self) -> impl Iterator<Item = &(Direction, Idx, HashAlgId)> {
|
||||
pub fn iter_hash(&self) -> impl Iterator<Item = &(Direction, RangeSet<usize>, HashAlgId)> {
|
||||
self.hash.iter()
|
||||
}
|
||||
|
||||
/// Returns the ranges of the encoding commitments.
|
||||
pub fn encoding(&self) -> Option<&(RangeSet<usize>, RangeSet<usize>)> {
|
||||
self.encoding.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -19,6 +19,4 @@ use crate::hash::TypedHash;
|
||||
pub struct EncodingCommitment {
|
||||
/// Merkle root of the encoding commitments.
|
||||
pub root: TypedHash,
|
||||
/// Seed used to generate the encodings.
|
||||
pub secret: EncoderSecret,
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::{collections::HashMap, fmt};
|
||||
|
||||
use rangeset::{RangeSet, UnionMut};
|
||||
use rangeset::set::RangeSet;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
@@ -8,8 +8,8 @@ use crate::{
|
||||
merkle::{MerkleError, MerkleProof},
|
||||
transcript::{
|
||||
commit::MAX_TOTAL_COMMITTED_DATA,
|
||||
encoding::{new_encoder, Encoder, EncodingCommitment},
|
||||
Direction, Idx,
|
||||
encoding::{new_encoder, Encoder, EncoderSecret, EncodingCommitment},
|
||||
Direction,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -17,7 +17,7 @@ use crate::{
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub(super) struct Opening {
|
||||
pub(super) direction: Direction,
|
||||
pub(super) idx: Idx,
|
||||
pub(super) idx: RangeSet<usize>,
|
||||
pub(super) blinder: Blinder,
|
||||
}
|
||||
|
||||
@@ -48,13 +48,14 @@ impl EncodingProof {
|
||||
pub fn verify_with_provider(
|
||||
&self,
|
||||
provider: &HashProvider,
|
||||
secret: &EncoderSecret,
|
||||
commitment: &EncodingCommitment,
|
||||
sent: &[u8],
|
||||
recv: &[u8],
|
||||
) -> Result<(Idx, Idx), EncodingProofError> {
|
||||
) -> Result<(RangeSet<usize>, RangeSet<usize>), EncodingProofError> {
|
||||
let hasher = provider.get(&commitment.root.alg)?;
|
||||
|
||||
let encoder = new_encoder(&commitment.secret);
|
||||
let encoder = new_encoder(secret);
|
||||
let Self {
|
||||
inclusion_proof,
|
||||
openings,
|
||||
@@ -89,20 +90,20 @@ impl EncodingProof {
|
||||
};
|
||||
|
||||
// Make sure the ranges are within the bounds of the transcript.
|
||||
if idx.end() > data.len() {
|
||||
if idx.end().unwrap_or(0) > data.len() {
|
||||
return Err(EncodingProofError::new(
|
||||
ErrorKind::Proof,
|
||||
format!(
|
||||
"index out of bounds of the transcript ({}): {} > {}",
|
||||
direction,
|
||||
idx.end(),
|
||||
idx.end().unwrap_or(0),
|
||||
data.len()
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
expected_leaf.clear();
|
||||
for range in idx.iter_ranges() {
|
||||
for range in idx.iter() {
|
||||
encoder.encode_data(*direction, range.clone(), &data[range], &mut expected_leaf);
|
||||
}
|
||||
expected_leaf.extend_from_slice(blinder.as_bytes());
|
||||
@@ -111,7 +112,7 @@ impl EncodingProof {
|
||||
// present in the merkle tree.
|
||||
leaves.push((*id, hasher.hash(&expected_leaf)));
|
||||
|
||||
auth.union_mut(idx.as_range_set());
|
||||
auth.union_mut(idx);
|
||||
}
|
||||
|
||||
// Verify that the expected hashes are present in the merkle tree.
|
||||
@@ -121,7 +122,7 @@ impl EncodingProof {
|
||||
// data is authentic.
|
||||
inclusion_proof.verify(hasher, &commitment.root, leaves)?;
|
||||
|
||||
Ok((Idx(auth_sent), Idx(auth_recv)))
|
||||
Ok((auth_sent, auth_recv))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -232,10 +233,7 @@ mod test {
|
||||
use crate::{
|
||||
fixtures::{encoder_secret, encoder_secret_tampered_seed, encoding_provider},
|
||||
hash::Blake3,
|
||||
transcript::{
|
||||
encoding::{EncoderSecret, EncodingTree},
|
||||
Idx, Transcript,
|
||||
},
|
||||
transcript::{encoding::EncodingTree, Transcript},
|
||||
};
|
||||
|
||||
use super::*;
|
||||
@@ -246,21 +244,18 @@ mod test {
|
||||
commitment: EncodingCommitment,
|
||||
}
|
||||
|
||||
fn new_encoding_fixture(secret: EncoderSecret) -> EncodingFixture {
|
||||
fn new_encoding_fixture() -> EncodingFixture {
|
||||
let transcript = Transcript::new(POST_JSON, OK_JSON);
|
||||
|
||||
let idx_0 = (Direction::Sent, Idx::new(0..POST_JSON.len()));
|
||||
let idx_1 = (Direction::Received, Idx::new(0..OK_JSON.len()));
|
||||
let idx_0 = (Direction::Sent, RangeSet::from(0..POST_JSON.len()));
|
||||
let idx_1 = (Direction::Received, RangeSet::from(0..OK_JSON.len()));
|
||||
|
||||
let provider = encoding_provider(transcript.sent(), transcript.received());
|
||||
let tree = EncodingTree::new(&Blake3::default(), [&idx_0, &idx_1], &provider).unwrap();
|
||||
|
||||
let proof = tree.proof([&idx_0, &idx_1].into_iter()).unwrap();
|
||||
|
||||
let commitment = EncodingCommitment {
|
||||
root: tree.root(),
|
||||
secret,
|
||||
};
|
||||
let commitment = EncodingCommitment { root: tree.root() };
|
||||
|
||||
EncodingFixture {
|
||||
transcript,
|
||||
@@ -275,11 +270,12 @@ mod test {
|
||||
transcript,
|
||||
proof,
|
||||
commitment,
|
||||
} = new_encoding_fixture(encoder_secret_tampered_seed());
|
||||
} = new_encoding_fixture();
|
||||
|
||||
let err = proof
|
||||
.verify_with_provider(
|
||||
&HashProvider::default(),
|
||||
&encoder_secret_tampered_seed(),
|
||||
&commitment,
|
||||
transcript.sent(),
|
||||
transcript.received(),
|
||||
@@ -295,13 +291,19 @@ mod test {
|
||||
transcript,
|
||||
proof,
|
||||
commitment,
|
||||
} = new_encoding_fixture(encoder_secret());
|
||||
} = new_encoding_fixture();
|
||||
|
||||
let sent = &transcript.sent()[transcript.sent().len() - 1..];
|
||||
let recv = &transcript.received()[transcript.received().len() - 2..];
|
||||
|
||||
let err = proof
|
||||
.verify_with_provider(&HashProvider::default(), &commitment, sent, recv)
|
||||
.verify_with_provider(
|
||||
&HashProvider::default(),
|
||||
&encoder_secret(),
|
||||
&commitment,
|
||||
sent,
|
||||
recv,
|
||||
)
|
||||
.unwrap_err();
|
||||
|
||||
assert!(matches!(err.kind, ErrorKind::Proof));
|
||||
@@ -313,15 +315,16 @@ mod test {
|
||||
transcript,
|
||||
mut proof,
|
||||
commitment,
|
||||
} = new_encoding_fixture(encoder_secret());
|
||||
} = new_encoding_fixture();
|
||||
|
||||
let Opening { idx, .. } = proof.openings.values_mut().next().unwrap();
|
||||
|
||||
*idx = Idx::new([0..3, 13..15]);
|
||||
*idx = RangeSet::from([0..3, 13..15]);
|
||||
|
||||
let err = proof
|
||||
.verify_with_provider(
|
||||
&HashProvider::default(),
|
||||
&encoder_secret(),
|
||||
&commitment,
|
||||
transcript.sent(),
|
||||
transcript.received(),
|
||||
@@ -337,7 +340,7 @@ mod test {
|
||||
transcript,
|
||||
mut proof,
|
||||
commitment,
|
||||
} = new_encoding_fixture(encoder_secret());
|
||||
} = new_encoding_fixture();
|
||||
|
||||
let Opening { blinder, .. } = proof.openings.values_mut().next().unwrap();
|
||||
|
||||
@@ -346,6 +349,7 @@ mod test {
|
||||
let err = proof
|
||||
.verify_with_provider(
|
||||
&HashProvider::default(),
|
||||
&encoder_secret(),
|
||||
&commitment,
|
||||
transcript.sent(),
|
||||
transcript.received(),
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use bimap::BiMap;
|
||||
use rangeset::set::RangeSet;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
@@ -11,7 +12,7 @@ use crate::{
|
||||
proof::{EncodingProof, Opening},
|
||||
EncodingProvider,
|
||||
},
|
||||
Direction, Idx,
|
||||
Direction,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -22,7 +23,7 @@ pub enum EncodingTreeError {
|
||||
#[error("index is out of bounds of the transcript")]
|
||||
OutOfBounds {
|
||||
/// The index.
|
||||
index: Idx,
|
||||
index: RangeSet<usize>,
|
||||
/// The transcript length.
|
||||
transcript_length: usize,
|
||||
},
|
||||
@@ -30,13 +31,13 @@ pub enum EncodingTreeError {
|
||||
#[error("encoding provider is missing an encoding for an index")]
|
||||
MissingEncoding {
|
||||
/// The index which is missing.
|
||||
index: Idx,
|
||||
index: RangeSet<usize>,
|
||||
},
|
||||
/// Index is missing from the tree.
|
||||
#[error("index is missing from the tree")]
|
||||
MissingLeaf {
|
||||
/// The index which is missing.
|
||||
index: Idx,
|
||||
index: RangeSet<usize>,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -49,11 +50,11 @@ pub struct EncodingTree {
|
||||
blinders: Vec<Blinder>,
|
||||
/// Mapping between the index of a leaf and the transcript index it
|
||||
/// corresponds to.
|
||||
idxs: BiMap<usize, (Direction, Idx)>,
|
||||
idxs: BiMap<usize, (Direction, RangeSet<usize>)>,
|
||||
/// Union of all transcript indices in the sent direction.
|
||||
sent_idx: Idx,
|
||||
sent_idx: RangeSet<usize>,
|
||||
/// Union of all transcript indices in the received direction.
|
||||
received_idx: Idx,
|
||||
received_idx: RangeSet<usize>,
|
||||
}
|
||||
|
||||
opaque_debug::implement!(EncodingTree);
|
||||
@@ -68,15 +69,15 @@ impl EncodingTree {
|
||||
/// * `provider` - The encoding provider.
|
||||
pub fn new<'idx>(
|
||||
hasher: &dyn HashAlgorithm,
|
||||
idxs: impl IntoIterator<Item = &'idx (Direction, Idx)>,
|
||||
idxs: impl IntoIterator<Item = &'idx (Direction, RangeSet<usize>)>,
|
||||
provider: &dyn EncodingProvider,
|
||||
) -> Result<Self, EncodingTreeError> {
|
||||
let mut this = Self {
|
||||
tree: MerkleTree::new(hasher.id()),
|
||||
blinders: Vec::new(),
|
||||
idxs: BiMap::new(),
|
||||
sent_idx: Idx::empty(),
|
||||
received_idx: Idx::empty(),
|
||||
sent_idx: RangeSet::default(),
|
||||
received_idx: RangeSet::default(),
|
||||
};
|
||||
|
||||
let mut leaves = Vec::new();
|
||||
@@ -98,7 +99,7 @@ impl EncodingTree {
|
||||
let blinder: Blinder = rand::random();
|
||||
|
||||
encoding.clear();
|
||||
for range in idx.iter_ranges() {
|
||||
for range in idx.iter() {
|
||||
provider
|
||||
.provide_encoding(direction, range, &mut encoding)
|
||||
.map_err(|_| EncodingTreeError::MissingEncoding { index: idx.clone() })?;
|
||||
@@ -138,7 +139,7 @@ impl EncodingTree {
|
||||
/// * `idxs` - The transcript indices to prove.
|
||||
pub fn proof<'idx>(
|
||||
&self,
|
||||
idxs: impl Iterator<Item = &'idx (Direction, Idx)>,
|
||||
idxs: impl Iterator<Item = &'idx (Direction, RangeSet<usize>)>,
|
||||
) -> Result<EncodingProof, EncodingTreeError> {
|
||||
let mut openings = HashMap::new();
|
||||
for dir_idx in idxs {
|
||||
@@ -171,11 +172,11 @@ impl EncodingTree {
|
||||
}
|
||||
|
||||
/// Returns whether the tree contains the given transcript index.
|
||||
pub fn contains(&self, idx: &(Direction, Idx)) -> bool {
|
||||
pub fn contains(&self, idx: &(Direction, RangeSet<usize>)) -> bool {
|
||||
self.idxs.contains_right(idx)
|
||||
}
|
||||
|
||||
pub(crate) fn idx(&self, direction: Direction) -> &Idx {
|
||||
pub(crate) fn idx(&self, direction: Direction) -> &RangeSet<usize> {
|
||||
match direction {
|
||||
Direction::Sent => &self.sent_idx,
|
||||
Direction::Received => &self.received_idx,
|
||||
@@ -183,7 +184,7 @@ impl EncodingTree {
|
||||
}
|
||||
|
||||
/// Returns the committed transcript indices.
|
||||
pub(crate) fn transcript_indices(&self) -> impl Iterator<Item = &(Direction, Idx)> {
|
||||
pub(crate) fn transcript_indices(&self) -> impl Iterator<Item = &(Direction, RangeSet<usize>)> {
|
||||
self.idxs.right_values()
|
||||
}
|
||||
}
|
||||
@@ -200,7 +201,7 @@ mod tests {
|
||||
|
||||
fn new_tree<'seq>(
|
||||
transcript: &Transcript,
|
||||
idxs: impl Iterator<Item = &'seq (Direction, Idx)>,
|
||||
idxs: impl Iterator<Item = &'seq (Direction, RangeSet<usize>)>,
|
||||
) -> Result<EncodingTree, EncodingTreeError> {
|
||||
let provider = encoding_provider(transcript.sent(), transcript.received());
|
||||
|
||||
@@ -211,8 +212,8 @@ mod tests {
|
||||
fn test_encoding_tree() {
|
||||
let transcript = Transcript::new(POST_JSON, OK_JSON);
|
||||
|
||||
let idx_0 = (Direction::Sent, Idx::new(0..POST_JSON.len()));
|
||||
let idx_1 = (Direction::Received, Idx::new(0..OK_JSON.len()));
|
||||
let idx_0 = (Direction::Sent, RangeSet::from(0..POST_JSON.len()));
|
||||
let idx_1 = (Direction::Received, RangeSet::from(0..OK_JSON.len()));
|
||||
|
||||
let tree = new_tree(&transcript, [&idx_0, &idx_1].into_iter()).unwrap();
|
||||
|
||||
@@ -221,14 +222,12 @@ mod tests {
|
||||
|
||||
let proof = tree.proof([&idx_0, &idx_1].into_iter()).unwrap();
|
||||
|
||||
let commitment = EncodingCommitment {
|
||||
root: tree.root(),
|
||||
secret: encoder_secret(),
|
||||
};
|
||||
let commitment = EncodingCommitment { root: tree.root() };
|
||||
|
||||
let (auth_sent, auth_recv) = proof
|
||||
.verify_with_provider(
|
||||
&HashProvider::default(),
|
||||
&encoder_secret(),
|
||||
&commitment,
|
||||
transcript.sent(),
|
||||
transcript.received(),
|
||||
@@ -243,10 +242,10 @@ mod tests {
|
||||
fn test_encoding_tree_multiple_ranges() {
|
||||
let transcript = Transcript::new(POST_JSON, OK_JSON);
|
||||
|
||||
let idx_0 = (Direction::Sent, Idx::new(0..1));
|
||||
let idx_1 = (Direction::Sent, Idx::new(1..POST_JSON.len()));
|
||||
let idx_2 = (Direction::Received, Idx::new(0..1));
|
||||
let idx_3 = (Direction::Received, Idx::new(1..OK_JSON.len()));
|
||||
let idx_0 = (Direction::Sent, RangeSet::from(0..1));
|
||||
let idx_1 = (Direction::Sent, RangeSet::from(1..POST_JSON.len()));
|
||||
let idx_2 = (Direction::Received, RangeSet::from(0..1));
|
||||
let idx_3 = (Direction::Received, RangeSet::from(1..OK_JSON.len()));
|
||||
|
||||
let tree = new_tree(&transcript, [&idx_0, &idx_1, &idx_2, &idx_3].into_iter()).unwrap();
|
||||
|
||||
@@ -259,25 +258,23 @@ mod tests {
|
||||
.proof([&idx_0, &idx_1, &idx_2, &idx_3].into_iter())
|
||||
.unwrap();
|
||||
|
||||
let commitment = EncodingCommitment {
|
||||
root: tree.root(),
|
||||
secret: encoder_secret(),
|
||||
};
|
||||
let commitment = EncodingCommitment { root: tree.root() };
|
||||
|
||||
let (auth_sent, auth_recv) = proof
|
||||
.verify_with_provider(
|
||||
&HashProvider::default(),
|
||||
&encoder_secret(),
|
||||
&commitment,
|
||||
transcript.sent(),
|
||||
transcript.received(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let mut expected_auth_sent = Idx::default();
|
||||
let mut expected_auth_sent = RangeSet::default();
|
||||
expected_auth_sent.union_mut(&idx_0.1);
|
||||
expected_auth_sent.union_mut(&idx_1.1);
|
||||
|
||||
let mut expected_auth_recv = Idx::default();
|
||||
let mut expected_auth_recv = RangeSet::default();
|
||||
expected_auth_recv.union_mut(&idx_2.1);
|
||||
expected_auth_recv.union_mut(&idx_3.1);
|
||||
|
||||
@@ -289,9 +286,9 @@ mod tests {
|
||||
fn test_encoding_tree_proof_missing_leaf() {
|
||||
let transcript = Transcript::new(POST_JSON, OK_JSON);
|
||||
|
||||
let idx_0 = (Direction::Sent, Idx::new(0..POST_JSON.len()));
|
||||
let idx_1 = (Direction::Received, Idx::new(0..4));
|
||||
let idx_2 = (Direction::Received, Idx::new(4..OK_JSON.len()));
|
||||
let idx_0 = (Direction::Sent, RangeSet::from(0..POST_JSON.len()));
|
||||
let idx_1 = (Direction::Received, RangeSet::from(0..4));
|
||||
let idx_2 = (Direction::Received, RangeSet::from(4..OK_JSON.len()));
|
||||
|
||||
let tree = new_tree(&transcript, [&idx_0, &idx_1].into_iter()).unwrap();
|
||||
|
||||
@@ -305,8 +302,8 @@ mod tests {
|
||||
fn test_encoding_tree_out_of_bounds() {
|
||||
let transcript = Transcript::new(POST_JSON, OK_JSON);
|
||||
|
||||
let idx_0 = (Direction::Sent, Idx::new(0..POST_JSON.len() + 1));
|
||||
let idx_1 = (Direction::Received, Idx::new(0..OK_JSON.len() + 1));
|
||||
let idx_0 = (Direction::Sent, RangeSet::from(0..POST_JSON.len() + 1));
|
||||
let idx_1 = (Direction::Received, RangeSet::from(0..OK_JSON.len() + 1));
|
||||
|
||||
let result = new_tree(&transcript, [&idx_0].into_iter()).unwrap_err();
|
||||
assert!(matches!(result, EncodingTreeError::MissingEncoding { .. }));
|
||||
@@ -321,7 +318,7 @@ mod tests {
|
||||
|
||||
let result = EncodingTree::new(
|
||||
&Blake3::default(),
|
||||
[(Direction::Sent, Idx::new(0..8))].iter(),
|
||||
[(Direction::Sent, RangeSet::from(0..8))].iter(),
|
||||
&provider,
|
||||
)
|
||||
.unwrap_err();
|
||||
|
||||
@@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
hash::{Blinder, HashAlgId, HashAlgorithm, TypedHash},
|
||||
transcript::{Direction, Idx},
|
||||
transcript::{Direction, RangeSet},
|
||||
};
|
||||
|
||||
/// Hashes plaintext with a blinder.
|
||||
@@ -23,7 +23,7 @@ pub struct PlaintextHash {
|
||||
/// Direction of the plaintext.
|
||||
pub direction: Direction,
|
||||
/// Index of plaintext.
|
||||
pub idx: Idx,
|
||||
pub idx: RangeSet<usize>,
|
||||
/// The hash of the data.
|
||||
pub hash: TypedHash,
|
||||
}
|
||||
@@ -34,7 +34,7 @@ pub struct PlaintextHashSecret {
|
||||
/// Direction of the plaintext.
|
||||
pub direction: Direction,
|
||||
/// Index of plaintext.
|
||||
pub idx: Idx,
|
||||
pub idx: RangeSet<usize>,
|
||||
/// The algorithm of the hash.
|
||||
pub alg: HashAlgId,
|
||||
/// Blinder for the hash.
|
||||
|
||||
@@ -1,17 +1,22 @@
|
||||
//! Transcript proofs.
|
||||
|
||||
use rangeset::{Cover, ToRangeSet};
|
||||
use rangeset::{
|
||||
iter::RangeIterator,
|
||||
ops::{Cover, Set},
|
||||
set::ToRangeSet,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{collections::HashSet, fmt};
|
||||
|
||||
use crate::{
|
||||
connection::TranscriptLength,
|
||||
display::FmtRangeSet,
|
||||
hash::{HashAlgId, HashProvider},
|
||||
transcript::{
|
||||
commit::{TranscriptCommitment, TranscriptCommitmentKind},
|
||||
encoding::{EncodingProof, EncodingProofError, EncodingTree},
|
||||
encoding::{EncoderSecret, EncodingProof, EncodingProofError, EncodingTree},
|
||||
hash::{hash_plaintext, PlaintextHash, PlaintextHashSecret},
|
||||
Direction, Idx, PartialTranscript, Transcript, TranscriptSecret,
|
||||
Direction, PartialTranscript, RangeSet, Transcript, TranscriptSecret,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -21,6 +26,12 @@ const DEFAULT_COMMITMENT_KINDS: &[TranscriptCommitmentKind] = &[
|
||||
TranscriptCommitmentKind::Hash {
|
||||
alg: HashAlgId::SHA256,
|
||||
},
|
||||
TranscriptCommitmentKind::Hash {
|
||||
alg: HashAlgId::BLAKE3,
|
||||
},
|
||||
TranscriptCommitmentKind::Hash {
|
||||
alg: HashAlgId::KECCAK256,
|
||||
},
|
||||
TranscriptCommitmentKind::Encoding,
|
||||
];
|
||||
|
||||
@@ -47,6 +58,7 @@ impl TranscriptProof {
|
||||
self,
|
||||
provider: &HashProvider,
|
||||
length: &TranscriptLength,
|
||||
encoder_secret: Option<&EncoderSecret>,
|
||||
commitments: impl IntoIterator<Item = &'a TranscriptCommitment>,
|
||||
) -> Result<PartialTranscript, TranscriptProofError> {
|
||||
let mut encoding_commitment = None;
|
||||
@@ -77,11 +89,18 @@ impl TranscriptProof {
|
||||
));
|
||||
}
|
||||
|
||||
let mut total_auth_sent = Idx::default();
|
||||
let mut total_auth_recv = Idx::default();
|
||||
let mut total_auth_sent = RangeSet::default();
|
||||
let mut total_auth_recv = RangeSet::default();
|
||||
|
||||
// Verify encoding proof.
|
||||
if let Some(proof) = self.encoding_proof {
|
||||
let secret = encoder_secret.ok_or_else(|| {
|
||||
TranscriptProofError::new(
|
||||
ErrorKind::Encoding,
|
||||
"contains an encoding proof but missing encoder secret",
|
||||
)
|
||||
})?;
|
||||
|
||||
let commitment = encoding_commitment.ok_or_else(|| {
|
||||
TranscriptProofError::new(
|
||||
ErrorKind::Encoding,
|
||||
@@ -91,6 +110,7 @@ impl TranscriptProof {
|
||||
|
||||
let (auth_sent, auth_recv) = proof.verify_with_provider(
|
||||
provider,
|
||||
secret,
|
||||
commitment,
|
||||
self.transcript.sent_unsafe(),
|
||||
self.transcript.received_unsafe(),
|
||||
@@ -120,7 +140,7 @@ impl TranscriptProof {
|
||||
Direction::Received => (self.transcript.received_unsafe(), &mut total_auth_recv),
|
||||
};
|
||||
|
||||
if idx.end() > plaintext.len() {
|
||||
if idx.end().unwrap_or(0) > plaintext.len() {
|
||||
return Err(TranscriptProofError::new(
|
||||
ErrorKind::Hash,
|
||||
"hash opening index is out of bounds",
|
||||
@@ -128,7 +148,7 @@ impl TranscriptProof {
|
||||
}
|
||||
|
||||
buffer.clear();
|
||||
for range in idx.iter_ranges() {
|
||||
for range in idx.iter() {
|
||||
buffer.extend_from_slice(&plaintext[range]);
|
||||
}
|
||||
|
||||
@@ -215,15 +235,15 @@ impl From<EncodingProofError> for TranscriptProofError {
|
||||
/// Union of ranges to reveal.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
struct QueryIdx {
|
||||
sent: Idx,
|
||||
recv: Idx,
|
||||
sent: RangeSet<usize>,
|
||||
recv: RangeSet<usize>,
|
||||
}
|
||||
|
||||
impl QueryIdx {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
sent: Idx::empty(),
|
||||
recv: Idx::empty(),
|
||||
sent: RangeSet::default(),
|
||||
recv: RangeSet::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -231,7 +251,7 @@ impl QueryIdx {
|
||||
self.sent.is_empty() && self.recv.is_empty()
|
||||
}
|
||||
|
||||
fn union(&mut self, direction: &Direction, other: &Idx) {
|
||||
fn union(&mut self, direction: &Direction, other: &RangeSet<usize>) {
|
||||
match direction {
|
||||
Direction::Sent => self.sent.union_mut(other),
|
||||
Direction::Received => self.recv.union_mut(other),
|
||||
@@ -241,7 +261,12 @@ impl QueryIdx {
|
||||
|
||||
impl std::fmt::Display for QueryIdx {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "sent: {}, received: {}", self.sent, self.recv)
|
||||
write!(
|
||||
f,
|
||||
"sent: {}, received: {}",
|
||||
FmtRangeSet(&self.sent),
|
||||
FmtRangeSet(&self.recv)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -253,8 +278,8 @@ pub struct TranscriptProofBuilder<'a> {
|
||||
transcript: &'a Transcript,
|
||||
encoding_tree: Option<&'a EncodingTree>,
|
||||
hash_secrets: Vec<&'a PlaintextHashSecret>,
|
||||
committed_sent: Idx,
|
||||
committed_recv: Idx,
|
||||
committed_sent: RangeSet<usize>,
|
||||
committed_recv: RangeSet<usize>,
|
||||
query_idx: QueryIdx,
|
||||
}
|
||||
|
||||
@@ -264,8 +289,8 @@ impl<'a> TranscriptProofBuilder<'a> {
|
||||
transcript: &'a Transcript,
|
||||
secrets: impl IntoIterator<Item = &'a TranscriptSecret>,
|
||||
) -> Self {
|
||||
let mut committed_sent = Idx::empty();
|
||||
let mut committed_recv = Idx::empty();
|
||||
let mut committed_sent = RangeSet::default();
|
||||
let mut committed_recv = RangeSet::default();
|
||||
|
||||
let mut encoding_tree = None;
|
||||
let mut hash_secrets = Vec::new();
|
||||
@@ -323,15 +348,15 @@ impl<'a> TranscriptProofBuilder<'a> {
|
||||
ranges: &dyn ToRangeSet<usize>,
|
||||
direction: Direction,
|
||||
) -> Result<&mut Self, TranscriptProofBuilderError> {
|
||||
let idx = Idx::new(ranges.to_range_set());
|
||||
let idx = ranges.to_range_set();
|
||||
|
||||
if idx.end() > self.transcript.len_of_direction(direction) {
|
||||
if idx.end().unwrap_or(0) > self.transcript.len_of_direction(direction) {
|
||||
return Err(TranscriptProofBuilderError::new(
|
||||
BuilderErrorKind::Index,
|
||||
format!(
|
||||
"range is out of bounds of the transcript ({}): {} > {}",
|
||||
direction,
|
||||
idx.end(),
|
||||
idx.end().unwrap_or(0),
|
||||
self.transcript.len_of_direction(direction)
|
||||
),
|
||||
));
|
||||
@@ -345,10 +370,13 @@ impl<'a> TranscriptProofBuilder<'a> {
|
||||
if idx.is_subset(committed) {
|
||||
self.query_idx.union(&direction, &idx);
|
||||
} else {
|
||||
let missing = idx.difference(committed);
|
||||
let missing = idx.difference(committed).into_set();
|
||||
return Err(TranscriptProofBuilderError::new(
|
||||
BuilderErrorKind::MissingCommitment,
|
||||
format!("commitment is missing for ranges in {direction} transcript: {missing}"),
|
||||
format!(
|
||||
"commitment is missing for ranges in {direction} transcript: {}",
|
||||
FmtRangeSet(&missing)
|
||||
),
|
||||
));
|
||||
}
|
||||
Ok(self)
|
||||
@@ -403,25 +431,23 @@ impl<'a> TranscriptProofBuilder<'a> {
|
||||
continue;
|
||||
};
|
||||
|
||||
let (sent_dir_idxs, sent_uncovered) =
|
||||
uncovered_query_idx.sent.as_range_set().cover_by(
|
||||
encoding_tree
|
||||
.transcript_indices()
|
||||
.filter(|(dir, _)| *dir == Direction::Sent),
|
||||
|(_, idx)| &idx.0,
|
||||
);
|
||||
let (sent_dir_idxs, sent_uncovered) = uncovered_query_idx.sent.cover_by(
|
||||
encoding_tree
|
||||
.transcript_indices()
|
||||
.filter(|(dir, _)| *dir == Direction::Sent),
|
||||
|(_, idx)| idx,
|
||||
);
|
||||
// Uncovered ranges will be checked with ranges of the next
|
||||
// preferred commitment kind.
|
||||
uncovered_query_idx.sent = Idx(sent_uncovered);
|
||||
uncovered_query_idx.sent = sent_uncovered;
|
||||
|
||||
let (recv_dir_idxs, recv_uncovered) =
|
||||
uncovered_query_idx.recv.as_range_set().cover_by(
|
||||
encoding_tree
|
||||
.transcript_indices()
|
||||
.filter(|(dir, _)| *dir == Direction::Received),
|
||||
|(_, idx)| &idx.0,
|
||||
);
|
||||
uncovered_query_idx.recv = Idx(recv_uncovered);
|
||||
let (recv_dir_idxs, recv_uncovered) = uncovered_query_idx.recv.cover_by(
|
||||
encoding_tree
|
||||
.transcript_indices()
|
||||
.filter(|(dir, _)| *dir == Direction::Received),
|
||||
|(_, idx)| idx,
|
||||
);
|
||||
uncovered_query_idx.recv = recv_uncovered;
|
||||
|
||||
let dir_idxs = sent_dir_idxs
|
||||
.into_iter()
|
||||
@@ -439,25 +465,23 @@ impl<'a> TranscriptProofBuilder<'a> {
|
||||
}
|
||||
}
|
||||
TranscriptCommitmentKind::Hash { alg } => {
|
||||
let (sent_hashes, sent_uncovered) =
|
||||
uncovered_query_idx.sent.as_range_set().cover_by(
|
||||
self.hash_secrets.iter().filter(|hash| {
|
||||
hash.direction == Direction::Sent && &hash.alg == alg
|
||||
}),
|
||||
|hash| &hash.idx.0,
|
||||
);
|
||||
let (sent_hashes, sent_uncovered) = uncovered_query_idx.sent.cover_by(
|
||||
self.hash_secrets.iter().filter(|hash| {
|
||||
hash.direction == Direction::Sent && &hash.alg == alg
|
||||
}),
|
||||
|hash| &hash.idx,
|
||||
);
|
||||
// Uncovered ranges will be checked with ranges of the next
|
||||
// preferred commitment kind.
|
||||
uncovered_query_idx.sent = Idx(sent_uncovered);
|
||||
uncovered_query_idx.sent = sent_uncovered;
|
||||
|
||||
let (recv_hashes, recv_uncovered) =
|
||||
uncovered_query_idx.recv.as_range_set().cover_by(
|
||||
self.hash_secrets.iter().filter(|hash| {
|
||||
hash.direction == Direction::Received && &hash.alg == alg
|
||||
}),
|
||||
|hash| &hash.idx.0,
|
||||
);
|
||||
uncovered_query_idx.recv = Idx(recv_uncovered);
|
||||
let (recv_hashes, recv_uncovered) = uncovered_query_idx.recv.cover_by(
|
||||
self.hash_secrets.iter().filter(|hash| {
|
||||
hash.direction == Direction::Received && &hash.alg == alg
|
||||
}),
|
||||
|hash| &hash.idx,
|
||||
);
|
||||
uncovered_query_idx.recv = recv_uncovered;
|
||||
|
||||
transcript_proof.hash_secrets.extend(
|
||||
sent_hashes
|
||||
@@ -562,12 +586,12 @@ impl fmt::Display for TranscriptProofBuilderError {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use rand::{Rng, SeedableRng};
|
||||
use rangeset::RangeSet;
|
||||
use rangeset::prelude::*;
|
||||
use rstest::rstest;
|
||||
use tlsn_data_fixtures::http::{request::GET_WITH_HEADER, response::OK_JSON};
|
||||
|
||||
use crate::{
|
||||
fixtures::encoding_provider,
|
||||
fixtures::{encoder_secret, encoding_provider},
|
||||
hash::{Blake3, Blinder, HashAlgId},
|
||||
transcript::TranscriptCommitConfigBuilder,
|
||||
};
|
||||
@@ -577,7 +601,7 @@ mod tests {
|
||||
#[rstest]
|
||||
fn test_verify_missing_encoding_commitment_root() {
|
||||
let transcript = Transcript::new(GET_WITH_HEADER, OK_JSON);
|
||||
let idxs = vec![(Direction::Received, Idx::new(0..transcript.len().1))];
|
||||
let idxs = vec![(Direction::Received, RangeSet::from(0..transcript.len().1))];
|
||||
let encoding_tree = EncodingTree::new(
|
||||
&Blake3::default(),
|
||||
&idxs,
|
||||
@@ -594,7 +618,12 @@ mod tests {
|
||||
|
||||
let provider = HashProvider::default();
|
||||
let err = transcript_proof
|
||||
.verify_with_provider(&provider, &transcript.length(), &[])
|
||||
.verify_with_provider(
|
||||
&provider,
|
||||
&transcript.length(),
|
||||
Some(&encoder_secret()),
|
||||
&[],
|
||||
)
|
||||
.err()
|
||||
.unwrap();
|
||||
|
||||
@@ -632,15 +661,17 @@ mod tests {
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_reveal_with_hash_commitment() {
|
||||
#[case::sha256(HashAlgId::SHA256)]
|
||||
#[case::blake3(HashAlgId::BLAKE3)]
|
||||
#[case::keccak256(HashAlgId::KECCAK256)]
|
||||
fn test_reveal_with_hash_commitment(#[case] alg: HashAlgId) {
|
||||
let mut rng = rand::rngs::StdRng::seed_from_u64(0);
|
||||
let provider = HashProvider::default();
|
||||
let transcript = Transcript::new(GET_WITH_HEADER, OK_JSON);
|
||||
|
||||
let direction = Direction::Sent;
|
||||
let idx = Idx::new(0..10);
|
||||
let idx = RangeSet::from(0..10);
|
||||
let blinder: Blinder = rng.random();
|
||||
let alg = HashAlgId::SHA256;
|
||||
let hasher = provider.get(&alg).unwrap();
|
||||
|
||||
let commitment = PlaintextHash {
|
||||
@@ -667,6 +698,7 @@ mod tests {
|
||||
.verify_with_provider(
|
||||
&provider,
|
||||
&transcript.length(),
|
||||
None,
|
||||
&[TranscriptCommitment::Hash(commitment)],
|
||||
)
|
||||
.unwrap();
|
||||
@@ -678,15 +710,17 @@ mod tests {
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_reveal_with_inconsistent_hash_commitment() {
|
||||
#[case::sha256(HashAlgId::SHA256)]
|
||||
#[case::blake3(HashAlgId::BLAKE3)]
|
||||
#[case::keccak256(HashAlgId::KECCAK256)]
|
||||
fn test_reveal_with_inconsistent_hash_commitment(#[case] alg: HashAlgId) {
|
||||
let mut rng = rand::rngs::StdRng::seed_from_u64(0);
|
||||
let provider = HashProvider::default();
|
||||
let transcript = Transcript::new(GET_WITH_HEADER, OK_JSON);
|
||||
|
||||
let direction = Direction::Sent;
|
||||
let idx = Idx::new(0..10);
|
||||
let idx = RangeSet::from(0..10);
|
||||
let blinder: Blinder = rng.random();
|
||||
let alg = HashAlgId::SHA256;
|
||||
let hasher = provider.get(&alg).unwrap();
|
||||
|
||||
let commitment = PlaintextHash {
|
||||
@@ -714,6 +748,7 @@ mod tests {
|
||||
.verify_with_provider(
|
||||
&provider,
|
||||
&transcript.length(),
|
||||
None,
|
||||
&[TranscriptCommitment::Hash(commitment)],
|
||||
)
|
||||
.unwrap_err();
|
||||
@@ -894,10 +929,10 @@ mod tests {
|
||||
match kind {
|
||||
BuilderErrorKind::Cover { uncovered, .. } => {
|
||||
if !uncovered_sent_rangeset.is_empty() {
|
||||
assert_eq!(uncovered.sent, Idx(uncovered_sent_rangeset));
|
||||
assert_eq!(uncovered.sent, uncovered_sent_rangeset);
|
||||
}
|
||||
if !uncovered_recv_rangeset.is_empty() {
|
||||
assert_eq!(uncovered.recv, Idx(uncovered_recv_rangeset));
|
||||
assert_eq!(uncovered.recv, uncovered_recv_rangeset);
|
||||
}
|
||||
}
|
||||
_ => panic!("unexpected error kind: {kind:?}"),
|
||||
|
||||
@@ -2,26 +2,69 @@
|
||||
|
||||
use crate::{
|
||||
connection::{
|
||||
Certificate, HandshakeData, HandshakeDataV1_2, ServerEphemKey, ServerSignature, TlsVersion,
|
||||
VerifyData,
|
||||
CertBinding, CertBindingV1_2, ServerEphemKey, ServerSignature, TlsVersion, VerifyData,
|
||||
},
|
||||
transcript::{Direction, Transcript},
|
||||
webpki::CertificateDer,
|
||||
};
|
||||
use tls_core::msgs::{
|
||||
alert::AlertMessagePayload,
|
||||
codec::{Codec, Reader},
|
||||
enums::{AlertDescription, ContentType, ProtocolVersion},
|
||||
enums::{AlertDescription, ProtocolVersion},
|
||||
handshake::{HandshakeMessagePayload, HandshakePayload},
|
||||
};
|
||||
|
||||
/// TLS record content type.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
pub enum ContentType {
|
||||
/// Change cipher spec protocol.
|
||||
ChangeCipherSpec,
|
||||
/// Alert protocol.
|
||||
Alert,
|
||||
/// Handshake protocol.
|
||||
Handshake,
|
||||
/// Application data protocol.
|
||||
ApplicationData,
|
||||
/// Heartbeat protocol.
|
||||
Heartbeat,
|
||||
/// Unknown protocol.
|
||||
Unknown(u8),
|
||||
}
|
||||
|
||||
impl From<ContentType> for tls_core::msgs::enums::ContentType {
|
||||
fn from(content_type: ContentType) -> Self {
|
||||
match content_type {
|
||||
ContentType::ChangeCipherSpec => tls_core::msgs::enums::ContentType::ChangeCipherSpec,
|
||||
ContentType::Alert => tls_core::msgs::enums::ContentType::Alert,
|
||||
ContentType::Handshake => tls_core::msgs::enums::ContentType::Handshake,
|
||||
ContentType::ApplicationData => tls_core::msgs::enums::ContentType::ApplicationData,
|
||||
ContentType::Heartbeat => tls_core::msgs::enums::ContentType::Heartbeat,
|
||||
ContentType::Unknown(id) => tls_core::msgs::enums::ContentType::Unknown(id),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<tls_core::msgs::enums::ContentType> for ContentType {
|
||||
fn from(content_type: tls_core::msgs::enums::ContentType) -> Self {
|
||||
match content_type {
|
||||
tls_core::msgs::enums::ContentType::ChangeCipherSpec => ContentType::ChangeCipherSpec,
|
||||
tls_core::msgs::enums::ContentType::Alert => ContentType::Alert,
|
||||
tls_core::msgs::enums::ContentType::Handshake => ContentType::Handshake,
|
||||
tls_core::msgs::enums::ContentType::ApplicationData => ContentType::ApplicationData,
|
||||
tls_core::msgs::enums::ContentType::Heartbeat => ContentType::Heartbeat,
|
||||
tls_core::msgs::enums::ContentType::Unknown(id) => ContentType::Unknown(id),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A transcript of TLS records sent and received by the prover.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TlsTranscript {
|
||||
time: u64,
|
||||
version: TlsVersion,
|
||||
server_cert_chain: Option<Vec<Certificate>>,
|
||||
server_cert_chain: Option<Vec<CertificateDer>>,
|
||||
server_signature: Option<ServerSignature>,
|
||||
handshake_data: HandshakeData,
|
||||
certificate_binding: CertBinding,
|
||||
sent: Vec<Record>,
|
||||
recv: Vec<Record>,
|
||||
}
|
||||
@@ -32,9 +75,9 @@ impl TlsTranscript {
|
||||
pub fn new(
|
||||
time: u64,
|
||||
version: TlsVersion,
|
||||
server_cert_chain: Option<Vec<Certificate>>,
|
||||
server_cert_chain: Option<Vec<CertificateDer>>,
|
||||
server_signature: Option<ServerSignature>,
|
||||
handshake_data: HandshakeData,
|
||||
certificate_binding: CertBinding,
|
||||
verify_data: VerifyData,
|
||||
sent: Vec<Record>,
|
||||
recv: Vec<Record>,
|
||||
@@ -198,7 +241,7 @@ impl TlsTranscript {
|
||||
version,
|
||||
server_cert_chain,
|
||||
server_signature,
|
||||
handshake_data,
|
||||
certificate_binding,
|
||||
sent,
|
||||
recv,
|
||||
})
|
||||
@@ -215,7 +258,7 @@ impl TlsTranscript {
|
||||
}
|
||||
|
||||
/// Returns the server certificate chain.
|
||||
pub fn server_cert_chain(&self) -> Option<&[Certificate]> {
|
||||
pub fn server_cert_chain(&self) -> Option<&[CertificateDer]> {
|
||||
self.server_cert_chain.as_deref()
|
||||
}
|
||||
|
||||
@@ -226,17 +269,17 @@ impl TlsTranscript {
|
||||
|
||||
/// Returns the server ephemeral key used in the TLS handshake.
|
||||
pub fn server_ephemeral_key(&self) -> &ServerEphemKey {
|
||||
match &self.handshake_data {
|
||||
HandshakeData::V1_2(HandshakeDataV1_2 {
|
||||
match &self.certificate_binding {
|
||||
CertBinding::V1_2(CertBindingV1_2 {
|
||||
server_ephemeral_key,
|
||||
..
|
||||
}) => server_ephemeral_key,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the handshake data.
|
||||
pub fn handshake_data(&self) -> &HandshakeData {
|
||||
&self.handshake_data
|
||||
/// Returns the certificate binding data.
|
||||
pub fn certificate_binding(&self) -> &CertBinding {
|
||||
&self.certificate_binding
|
||||
}
|
||||
|
||||
/// Returns the sent records.
|
||||
|
||||
187
crates/core/src/webpki.rs
Normal file
187
crates/core/src/webpki.rs
Normal file
@@ -0,0 +1,187 @@
|
||||
//! Web PKI types.
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
use rustls_pki_types::{self as webpki_types, pem::PemObject};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::connection::ServerName;
|
||||
|
||||
/// X.509 certificate, DER encoded.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct CertificateDer(pub Vec<u8>);
|
||||
|
||||
impl CertificateDer {
|
||||
/// Creates a DER-encoded certificate from a PEM-encoded certificate.
|
||||
pub fn from_pem_slice(pem: &[u8]) -> Result<Self, PemError> {
|
||||
let der = webpki_types::CertificateDer::from_pem_slice(pem).map_err(|_| PemError {})?;
|
||||
|
||||
Ok(Self(der.to_vec()))
|
||||
}
|
||||
}
|
||||
|
||||
/// Private key, DER encoded.
|
||||
#[derive(Debug, Clone, zeroize::ZeroizeOnDrop, Serialize, Deserialize)]
|
||||
pub struct PrivateKeyDer(pub Vec<u8>);
|
||||
|
||||
impl PrivateKeyDer {
|
||||
/// Creates a DER-encoded private key from a PEM-encoded private key.
|
||||
pub fn from_pem_slice(pem: &[u8]) -> Result<Self, PemError> {
|
||||
let der = webpki_types::PrivateKeyDer::from_pem_slice(pem).map_err(|_| PemError {})?;
|
||||
|
||||
Ok(Self(der.secret_der().to_vec()))
|
||||
}
|
||||
}
|
||||
|
||||
/// PEM parsing error.
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[error("failed to parse PEM object")]
|
||||
pub struct PemError {}
|
||||
|
||||
/// Root certificate store.
|
||||
///
|
||||
/// This stores root certificates which are used to verify end-entity
|
||||
/// certificates presented by a TLS server.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct RootCertStore {
|
||||
/// Unvalidated DER-encoded X.509 root certificates.
|
||||
pub roots: Vec<CertificateDer>,
|
||||
}
|
||||
|
||||
impl RootCertStore {
|
||||
/// Creates an empty root certificate store.
|
||||
pub fn empty() -> Self {
|
||||
Self { roots: Vec::new() }
|
||||
}
|
||||
|
||||
/// Creates a root certificate store with Mozilla root certificates.
|
||||
///
|
||||
/// These certificates are sourced from [`webpki-root-certs`](https://docs.rs/webpki-root-certs/latest/webpki_root_certs/). It is not recommended to use these unless the
|
||||
/// application binary can be recompiled and deployed on-demand in the case
|
||||
/// that the root certificates need to be updated.
|
||||
#[cfg(feature = "mozilla-certs")]
|
||||
pub fn mozilla() -> Self {
|
||||
Self {
|
||||
roots: webpki_root_certs::TLS_SERVER_ROOT_CERTS
|
||||
.iter()
|
||||
.map(|cert| CertificateDer(cert.to_vec()))
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Server certificate verifier.
|
||||
#[derive(Debug)]
|
||||
pub struct ServerCertVerifier {
|
||||
roots: Vec<webpki_types::TrustAnchor<'static>>,
|
||||
}
|
||||
|
||||
impl ServerCertVerifier {
|
||||
/// Creates a new server certificate verifier.
|
||||
pub fn new(roots: &RootCertStore) -> Result<Self, ServerCertVerifierError> {
|
||||
let roots = roots
|
||||
.roots
|
||||
.iter()
|
||||
.map(|cert| {
|
||||
webpki::anchor_from_trusted_cert(&webpki_types::CertificateDer::from(
|
||||
cert.0.as_slice(),
|
||||
))
|
||||
.map(|anchor| anchor.to_owned())
|
||||
.map_err(|err| ServerCertVerifierError::InvalidRootCertificate {
|
||||
cert: cert.clone(),
|
||||
reason: err.to_string(),
|
||||
})
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
Ok(Self { roots })
|
||||
}
|
||||
|
||||
/// Creates a server certificate verifier with Mozilla root certificates.
|
||||
///
|
||||
/// These certificates are sourced from [`webpki-root-certs`](https://docs.rs/webpki-root-certs/latest/webpki_root_certs/). It is not recommended to use these unless the
|
||||
/// application binary can be recompiled and deployed on-demand in the case
|
||||
/// that the root certificates need to be updated.
|
||||
#[cfg(feature = "mozilla-certs")]
|
||||
pub fn mozilla() -> Self {
|
||||
Self {
|
||||
roots: webpki_roots::TLS_SERVER_ROOTS.to_vec(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Verifies the server certificate was valid at the given time of
|
||||
/// presentation.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `end_entity` - End-entity certificate to verify.
|
||||
/// * `intermediates` - Intermediate certificates to a trust anchor.
|
||||
/// * `server_name` - Server DNS name.
|
||||
/// * `time` - Unix time the certificate was presented.
|
||||
pub fn verify_server_cert(
|
||||
&self,
|
||||
end_entity: &CertificateDer,
|
||||
intermediates: &[CertificateDer],
|
||||
server_name: &ServerName,
|
||||
time: u64,
|
||||
) -> Result<(), ServerCertVerifierError> {
|
||||
let cert = webpki_types::CertificateDer::from(end_entity.0.as_slice());
|
||||
let cert = webpki::EndEntityCert::try_from(&cert).map_err(|e| {
|
||||
ServerCertVerifierError::InvalidEndEntityCertificate {
|
||||
cert: end_entity.clone(),
|
||||
reason: e.to_string(),
|
||||
}
|
||||
})?;
|
||||
let intermediates = intermediates
|
||||
.iter()
|
||||
.map(|c| webpki_types::CertificateDer::from(c.0.as_slice()))
|
||||
.collect::<Vec<_>>();
|
||||
let server_name = server_name.to_webpki();
|
||||
let time = webpki_types::UnixTime::since_unix_epoch(Duration::from_secs(time));
|
||||
|
||||
cert.verify_for_usage(
|
||||
webpki::ALL_VERIFICATION_ALGS,
|
||||
&self.roots,
|
||||
&intermediates,
|
||||
time,
|
||||
webpki::KeyUsage::server_auth(),
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.map(|_| ())
|
||||
.map_err(|_| ServerCertVerifierError::InvalidPath)?;
|
||||
|
||||
cert.verify_is_valid_for_subject_name(&server_name)
|
||||
.map_err(|_| ServerCertVerifierError::InvalidServerName)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Error for [`ServerCertVerifier`].
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[error("server certificate verification failed: {0}")]
|
||||
pub enum ServerCertVerifierError {
|
||||
/// Root certificate store contains invalid certificate.
|
||||
#[error("root certificate store contains invalid certificate: {reason}")]
|
||||
InvalidRootCertificate {
|
||||
/// Invalid certificate.
|
||||
cert: CertificateDer,
|
||||
/// Reason for invalidity.
|
||||
reason: String,
|
||||
},
|
||||
/// End-entity certificate is invalid.
|
||||
#[error("end-entity certificate is invalid: {reason}")]
|
||||
InvalidEndEntityCertificate {
|
||||
/// Invalid certificate.
|
||||
cert: CertificateDer,
|
||||
/// Reason for invalidity.
|
||||
reason: String,
|
||||
},
|
||||
/// Failed to verify certificate path to provided trust anchors.
|
||||
#[error("failed to verify certificate path to provided trust anchors")]
|
||||
InvalidPath,
|
||||
/// Failed to verify certificate is valid for provided server name.
|
||||
#[error("failed to verify certificate is valid for provided server name")]
|
||||
InvalidServerName,
|
||||
}
|
||||
@@ -8,26 +8,24 @@ version = "0.0.0"
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
notary-client = { workspace = true }
|
||||
tlsn-core = { workspace = true }
|
||||
tlsn = { workspace = true }
|
||||
tlsn-formats = { workspace = true }
|
||||
tlsn-tls-core = { workspace = true }
|
||||
tls-server-fixture = { workspace = true }
|
||||
tlsn-server-fixture = { workspace = true }
|
||||
tlsn-server-fixture-certs = { workspace = true }
|
||||
spansy = { workspace = true }
|
||||
|
||||
anyhow = { workspace = true }
|
||||
bincode = { workspace = true }
|
||||
chrono = { workspace = true }
|
||||
clap = { version = "4.5", features = ["derive"] }
|
||||
dotenv = { version = "0.15.0" }
|
||||
futures = { workspace = true }
|
||||
http-body-util = { workspace = true }
|
||||
hex = { workspace = true }
|
||||
hyper = { workspace = true, features = ["client", "http1"] }
|
||||
hyper-util = { workspace = true, features = ["full"] }
|
||||
k256 = { workspace = true, features = ["ecdsa"] }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true }
|
||||
tokio = { workspace = true, features = [
|
||||
"rt",
|
||||
@@ -40,6 +38,17 @@ tokio = { workspace = true, features = [
|
||||
tokio-util = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
tracing-subscriber = { workspace = true }
|
||||
noir = { git = "https://github.com/zkmopro/noir-rs", tag = "v1.0.0-beta.8", features = [
|
||||
"barretenberg",
|
||||
] }
|
||||
|
||||
[[example]]
|
||||
name = "interactive"
|
||||
path = "interactive/interactive.rs"
|
||||
|
||||
[[example]]
|
||||
name = "interactive_zk"
|
||||
path = "interactive_zk/interactive_zk.rs"
|
||||
|
||||
[[example]]
|
||||
name = "attestation_prove"
|
||||
@@ -52,7 +61,3 @@ path = "attestation/present.rs"
|
||||
[[example]]
|
||||
name = "attestation_verify"
|
||||
path = "attestation/verify.rs"
|
||||
|
||||
[[example]]
|
||||
name = "interactive"
|
||||
path = "interactive/interactive.rs"
|
||||
|
||||
@@ -4,5 +4,7 @@ This folder contains examples demonstrating how to use the TLSNotary protocol.
|
||||
|
||||
* [Interactive](./interactive/README.md): Interactive Prover and Verifier session without a trusted notary.
|
||||
* [Attestation](./attestation/README.md): Performing a simple notarization with a trusted notary.
|
||||
* [Interactive_zk](./interactive_zk/README.md): Interactive Prover and Verifier session demonstrating zero-knowledge age verification using Noir.
|
||||
|
||||
Refer to <https://docs.tlsnotary.org/quick_start/index.html> for a quick start guide to using TLSNotary with these examples.
|
||||
|
||||
Refer to <https://tlsnotary.org/docs/quick_start> for a quick start guide to using TLSNotary with these examples.
|
||||
@@ -1,75 +1,81 @@
|
||||
## Simple Attestation Example: Notarize Public Data from example.com (Rust) <a name="rust-simple"></a>
|
||||
# Attestation Example
|
||||
|
||||
This example demonstrates the simplest possible use case for TLSNotary. A Prover notarizes data from a local test server with a local Notary.
|
||||
|
||||
**Overview**:
|
||||
1. Notarize a request and response from the test server and acquire an attestation of its content.
|
||||
2. Create a redacted, verifiable presentation using the attestation.
|
||||
3. Verify the presentation.
|
||||
This example demonstrates a **TLSNotary attestation workflow**: notarizing data from a server with a trusted third party (Notary), then creating verifiable presentations with selective disclosure of sensitive information to a Verifier.
|
||||
|
||||
### 1. Notarize
|
||||
## 🔍 How It Works
|
||||
|
||||
Before starting the notarization, set up the local test server and local notary.
|
||||
Run the following commands from the root of this repository (not from this example's folder):
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant P as Prover
|
||||
participant N as MPC-TLS<br/>Verifier
|
||||
participant S as Server<br/>Fixture
|
||||
participant V as Attestation<br/>Verifier
|
||||
|
||||
1. Run the test server:
|
||||
```shell
|
||||
RUST_LOG=info PORT=4000 cargo run --bin tlsn-server-fixture
|
||||
```
|
||||
2. Run the notary server:
|
||||
```shell
|
||||
cargo run --release --bin notary-server
|
||||
```
|
||||
3. Run the prove example:
|
||||
```shell
|
||||
SERVER_PORT=4000 cargo run --release --example attestation_prove
|
||||
```
|
||||
Note over P,S: 1. Notarization Phase
|
||||
P->>N: Establish MPC-TLS connection
|
||||
P->>S: Request (MPC-TLS)
|
||||
S->>P: Response (MPC-TLS)
|
||||
N->>P: Issue signed attestation
|
||||
|
||||
To see more details, run with additional debug information:
|
||||
```shell
|
||||
RUST_LOG=debug,yamux=info,uid_mux=info SERVER_PORT=4000 cargo run --release --example attestation_prove
|
||||
Note over P: 2. Presentation Phase
|
||||
P->>P: Create redacted presentation
|
||||
|
||||
Note over P,V: 3. Verification Phase
|
||||
P->>V: Share presentation
|
||||
V->>V: Verify attestation signature
|
||||
```
|
||||
|
||||
If notarization is successful, you should see the following output in the console:
|
||||
```log
|
||||
Starting an MPC TLS connection with the server
|
||||
Got a response from the server: 200 OK
|
||||
Notarization complete!
|
||||
### The Three-Step Process
|
||||
|
||||
1. **🔐 Notarize**: Prover collaborates with Notary to create an authenticated TLS session and obtain a signed attestation
|
||||
2. **✂️ Present**: Prover creates a selective presentation, choosing which data to reveal or redact
|
||||
3. **✅ Verify**: Anyone can verify the presentation's authenticity using the Notary's public key
|
||||
|
||||
## 🚀 Quick Start
|
||||
|
||||
### Step 1: Notarize Data
|
||||
|
||||
**Start the test server** (from repository root):
|
||||
```bash
|
||||
RUST_LOG=info PORT=4000 cargo run --bin tlsn-server-fixture
|
||||
```
|
||||
|
||||
**Run the notarization** (in a new terminal):
|
||||
```bash
|
||||
RUST_LOG=info SERVER_PORT=4000 cargo run --release --example attestation_prove
|
||||
```
|
||||
|
||||
**Expected output:**
|
||||
```
|
||||
Notarization completed successfully!
|
||||
The attestation has been written to `example-json.attestation.tlsn` and the corresponding secrets to `example-json.secrets.tlsn`.
|
||||
```
|
||||
|
||||
⚠️ Note: In this example, we run a local Notary server for demonstration purposes. In real-world applications, the Notary should be operated by a trusted third party. Refer to the [Notary Server Documentation](https://docs.tlsnotary.org/developers/notary_server.html) for more details on running a Notary server.
|
||||
### Step 2: Create Verifiable Presentation
|
||||
|
||||
### 2. Build a Verifiable Presentation
|
||||
|
||||
This step creates a verifiable presentation with optional redactions, which can be shared with any verifier.
|
||||
|
||||
Run the present example:
|
||||
```shell
|
||||
**Generate a redacted presentation:**
|
||||
```bash
|
||||
cargo run --release --example attestation_present
|
||||
```
|
||||
|
||||
If successful, you’ll see this output in the console:
|
||||
|
||||
```log
|
||||
**Expected output:**
|
||||
```
|
||||
Presentation built successfully!
|
||||
The presentation has been written to `example-json.presentation.tlsn`.
|
||||
```
|
||||
|
||||
You can create multiple presentations from the attestation and secrets in the notarization step, each with customized data redactions. You are invited to experiment!
|
||||
> 💡 **Tip**: You can create multiple presentations from the same attestation, each with different redactions!
|
||||
|
||||
### 3. Verify the Presentation
|
||||
### Step 3: Verify the Presentation
|
||||
|
||||
This step reads the presentation created above, verifies it, and prints the disclosed data to the console.
|
||||
|
||||
Run the verify binary:
|
||||
```shell
|
||||
**Verify the presentation:**
|
||||
```bash
|
||||
cargo run --release --example attestation_verify
|
||||
```
|
||||
|
||||
Upon success, you should see output similar to:
|
||||
```log
|
||||
**Expected output:**
|
||||
```
|
||||
Verifying presentation with {key algorithm} key: { hex encoded key }
|
||||
|
||||
**Ask yourself, do you trust this key?**
|
||||
@@ -79,33 +85,80 @@ Successfully verified that the data below came from a session with test-server.i
|
||||
Note that the data which the Prover chose not to disclose are shown as X.
|
||||
|
||||
Data sent:
|
||||
...
|
||||
|
||||
GET /formats/json HTTP/1.1
|
||||
host: test-server.io
|
||||
accept: */*
|
||||
accept-encoding: identity
|
||||
connection: close
|
||||
user-agent: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
||||
|
||||
Data received:
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
content-type: application/json
|
||||
content-length: 722
|
||||
connection: close
|
||||
date: Mon, 08 Sep 2025 09:18:29 GMT
|
||||
|
||||
XXXXXX1234567890XXXXXXXXXXXXXXXXXXXXXXXXJohn DoeXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX1.2XX
|
||||
```
|
||||
|
||||
⚠️ The presentation includes a “verifying key,” which the Notary used when issuing the attestation. If you trust this key, you can trust the authenticity of the presented data.
|
||||
## 🎯 Use Cases & Examples
|
||||
|
||||
### HTML
|
||||
### JSON Data (Default)
|
||||
Perfect for API responses, configuration data, or structured information:
|
||||
```bash
|
||||
# All three steps use JSON by default
|
||||
SERVER_PORT=4000 cargo run --release --example attestation_prove
|
||||
cargo run --release --example attestation_present
|
||||
cargo run --release --example attestation_verify
|
||||
```
|
||||
|
||||
In the example above, we notarized a JSON response. TLSNotary also supports notarizing HTML content. To run an HTML example, use:
|
||||
|
||||
```shell
|
||||
# notarize
|
||||
### HTML Content
|
||||
Ideal for web pages, forms, or any HTML-based data:
|
||||
```bash
|
||||
# Notarize HTML content
|
||||
SERVER_PORT=4000 cargo run --release --example attestation_prove -- html
|
||||
# present
|
||||
cargo run --release --example attestation_present -- html
|
||||
# verify
|
||||
cargo run --release --example attestation_verify -- html
|
||||
```
|
||||
|
||||
### Private Data
|
||||
|
||||
The examples above demonstrate how to use TLSNotary with publicly accessible data. TLSNotary can also be utilized for private data that requires authentication. To access this data, you can add the necessary headers (such as an authentication token) or cookies to your request. To run an example that uses an authentication token, execute the following command:
|
||||
|
||||
```shell
|
||||
# notarize
|
||||
### Authenticated/Private Data
|
||||
For APIs requiring authentication tokens, cookies, or private access:
|
||||
```bash
|
||||
# Notarize private data with authentication
|
||||
SERVER_PORT=4000 cargo run --release --example attestation_prove -- authenticated
|
||||
# present
|
||||
cargo run --release --example attestation_present -- authenticated
|
||||
# verify
|
||||
cargo run --release --example attestation_verify -- authenticated
|
||||
```
|
||||
```
|
||||
|
||||
### Debug Mode
|
||||
|
||||
For detailed logging and troubleshooting:
|
||||
```bash
|
||||
RUST_LOG=debug,yamux=info,uid_mux=info SERVER_PORT=4000 cargo run --release --example attestation_prove
|
||||
```
|
||||
|
||||
### Generated Files
|
||||
|
||||
After running the examples, you'll find:
|
||||
- **`*.attestation.tlsn`**: The cryptographically signed attestation from the Notary
|
||||
- **`*.secrets.tlsn`**: Cryptographic secrets needed to create presentations
|
||||
- **`*.presentation.tlsn`**: The verifiable presentation with your chosen redactions
|
||||
|
||||
## 🔐 Security Considerations
|
||||
|
||||
### Trust Model
|
||||
- ✅ **Notary Key**: The presentation includes the Notary's verifying key - The verifier must trust this key
|
||||
- ✅ **Data Authenticity**: Cryptographically guaranteed that data came from the specified server
|
||||
- ✅ **Tamper Evidence**: Any modification to the presentation will fail verification
|
||||
- ⚠️ **Notary Trust**: The verifier must trust the Notary not to collude with the Prover
|
||||
|
||||
### Production Deployment
|
||||
- 🏭 **Independent Notary**: Use a trusted third-party Notary service (not a local one)
|
||||
- 🔒 **Key Management**: Implement proper Notary key distribution and verification
|
||||
- 📋 **Audit Trail**: Maintain logs of notarization and verification events
|
||||
- 🔄 **Key Rotation**: Plan for Notary key updates and migration
|
||||
|
||||
> ⚠️ **Demo Notice**: This example uses a local test server and local Notary for demonstration. In production, use trusted third-party Notary services and real server endpoints.
|
||||
@@ -1,29 +1,45 @@
|
||||
// This example demonstrates how to use the Prover to acquire an attestation for
|
||||
// an HTTP request sent to example.com. The attestation and secrets are saved to
|
||||
// disk.
|
||||
// an HTTP request sent to a server fixture. The attestation and secrets are
|
||||
// saved to disk.
|
||||
|
||||
use std::env;
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use clap::Parser;
|
||||
use http_body_util::Empty;
|
||||
use hyper::{body::Bytes, Request, StatusCode};
|
||||
use hyper_util::rt::TokioIo;
|
||||
use spansy::Spanned;
|
||||
use tokio::{
|
||||
io::{AsyncRead, AsyncWrite},
|
||||
sync::oneshot::{self, Receiver, Sender},
|
||||
};
|
||||
use tokio_util::compat::{FuturesAsyncReadCompatExt, TokioAsyncReadCompatExt};
|
||||
use tracing::debug;
|
||||
use tracing::info;
|
||||
|
||||
use notary_client::{Accepted, NotarizationRequest, NotaryClient};
|
||||
use tls_server_fixture::{CA_CERT_DER, SERVER_DOMAIN};
|
||||
use tlsn::{
|
||||
attestation::request::RequestConfig,
|
||||
config::ProtocolConfig,
|
||||
prover::{Prover, ProverConfig, TlsConfig},
|
||||
transcript::TranscriptCommitConfig,
|
||||
attestation::{
|
||||
request::{Request as AttestationRequest, RequestConfig},
|
||||
signing::Secp256k1Signer,
|
||||
Attestation, AttestationConfig, CryptoProvider, Secrets,
|
||||
},
|
||||
config::{
|
||||
prove::ProveConfig,
|
||||
prover::ProverConfig,
|
||||
tls::TlsClientConfig,
|
||||
tls_commit::{mpc::MpcTlsConfig, TlsCommitConfig},
|
||||
verifier::VerifierConfig,
|
||||
},
|
||||
connection::{ConnectionInfo, HandshakeData, ServerName, TranscriptLength},
|
||||
prover::{state::Committed, Prover, ProverOutput},
|
||||
transcript::{ContentType, TranscriptCommitConfig},
|
||||
verifier::{Verifier, VerifierOutput},
|
||||
webpki::{CertificateDer, PrivateKeyDer, RootCertStore},
|
||||
};
|
||||
use tlsn_examples::ExampleType;
|
||||
use tlsn_formats::http::{DefaultHttpCommitter, HttpCommit, HttpTranscript};
|
||||
use tlsn_server_fixture::DEFAULT_FIXTURE_PORT;
|
||||
use tlsn_server_fixture_certs::{CLIENT_CERT, CLIENT_KEY};
|
||||
use tlsn_server_fixture_certs::{CA_CERT_DER, CLIENT_CERT_DER, CLIENT_KEY_DER, SERVER_DOMAIN};
|
||||
|
||||
// Setting of the application server.
|
||||
const USER_AGENT: &str = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36";
|
||||
@@ -37,117 +53,103 @@ struct Args {
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let args = Args::parse();
|
||||
async fn main() -> Result<()> {
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
let args = Args::parse();
|
||||
let (uri, extra_headers) = match args.example_type {
|
||||
ExampleType::Json => ("/formats/json", vec![]),
|
||||
ExampleType::Html => ("/formats/html", vec![]),
|
||||
ExampleType::Authenticated => ("/protected", vec![("Authorization", "random_auth_token")]),
|
||||
};
|
||||
|
||||
notarize(uri, extra_headers, &args.example_type).await
|
||||
let (notary_socket, prover_socket) = tokio::io::duplex(1 << 23);
|
||||
let (request_tx, request_rx) = oneshot::channel();
|
||||
let (attestation_tx, attestation_rx) = oneshot::channel();
|
||||
|
||||
tokio::spawn(async move {
|
||||
notary(notary_socket, request_rx, attestation_tx)
|
||||
.await
|
||||
.unwrap()
|
||||
});
|
||||
|
||||
prover(
|
||||
prover_socket,
|
||||
request_tx,
|
||||
attestation_rx,
|
||||
uri,
|
||||
extra_headers,
|
||||
&args.example_type,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn notarize(
|
||||
async fn prover<S: AsyncWrite + AsyncRead + Send + Sync + Unpin + 'static>(
|
||||
socket: S,
|
||||
req_tx: Sender<AttestationRequest>,
|
||||
resp_rx: Receiver<Attestation>,
|
||||
uri: &str,
|
||||
extra_headers: Vec<(&str, &str)>,
|
||||
example_type: &ExampleType,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
let notary_host: String = env::var("NOTARY_HOST").unwrap_or("127.0.0.1".into());
|
||||
let notary_port: u16 = env::var("NOTARY_PORT")
|
||||
.map(|port| port.parse().expect("port should be valid integer"))
|
||||
.unwrap_or(7047);
|
||||
) -> Result<()> {
|
||||
let server_host: String = env::var("SERVER_HOST").unwrap_or("127.0.0.1".into());
|
||||
let server_port: u16 = env::var("SERVER_PORT")
|
||||
.map(|port| port.parse().expect("port should be valid integer"))
|
||||
.unwrap_or(DEFAULT_FIXTURE_PORT);
|
||||
|
||||
// Build a client to connect to the notary server.
|
||||
let notary_client = NotaryClient::builder()
|
||||
.host(notary_host)
|
||||
.port(notary_port)
|
||||
// WARNING: Always use TLS to connect to notary server, except if notary is running locally
|
||||
// e.g. this example, hence `enable_tls` is set to False (else it always defaults to True).
|
||||
.enable_tls(false)
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
// Send requests for configuration and notarization to the notary server.
|
||||
let notarization_request = NotarizationRequest::builder()
|
||||
// We must configure the amount of data we expect to exchange beforehand, which will
|
||||
// be preprocessed prior to the connection. Reducing these limits will improve
|
||||
// performance.
|
||||
.max_sent_data(tlsn_examples::MAX_SENT_DATA)
|
||||
.max_recv_data(tlsn_examples::MAX_RECV_DATA)
|
||||
.build()?;
|
||||
|
||||
let Accepted {
|
||||
io: notary_connection,
|
||||
id: _session_id,
|
||||
..
|
||||
} = notary_client
|
||||
.request_notarization(notarization_request)
|
||||
.await
|
||||
.expect("Could not connect to notary. Make sure it is running.");
|
||||
|
||||
// Create a crypto provider accepting the server-fixture's self-signed
|
||||
// root certificate.
|
||||
//
|
||||
// This is only required for offline testing with the server-fixture. In
|
||||
// production, use `CryptoProvider::default()` instead.
|
||||
let mut root_store = tls_core::anchors::RootCertStore::empty();
|
||||
root_store
|
||||
.add(&tls_core::key::Certificate(CA_CERT_DER.to_vec()))
|
||||
.unwrap();
|
||||
|
||||
// Set up protocol configuration for prover.
|
||||
let mut prover_config_builder = ProverConfig::builder();
|
||||
prover_config_builder
|
||||
.server_name(SERVER_DOMAIN)
|
||||
.protocol_config(
|
||||
ProtocolConfig::builder()
|
||||
// We must configure the amount of data we expect to exchange beforehand, which will
|
||||
// be preprocessed prior to the connection. Reducing these limits will improve
|
||||
// performance.
|
||||
.max_sent_data(tlsn_examples::MAX_SENT_DATA)
|
||||
.max_recv_data(tlsn_examples::MAX_RECV_DATA)
|
||||
.build()?,
|
||||
);
|
||||
|
||||
// (Optional) Set up TLS client authentication if required by the server.
|
||||
prover_config_builder.tls_config(
|
||||
TlsConfig::builder()
|
||||
.client_auth_pem((vec![CLIENT_CERT.to_vec()], CLIENT_KEY.to_vec()))
|
||||
.unwrap()
|
||||
.build()?,
|
||||
);
|
||||
|
||||
let prover_config = prover_config_builder.build()?;
|
||||
|
||||
// Create a new prover and perform necessary setup.
|
||||
let prover = Prover::new(prover_config)
|
||||
.setup(notary_connection.compat())
|
||||
let prover = Prover::new(ProverConfig::builder().build()?)
|
||||
.commit(
|
||||
TlsCommitConfig::builder()
|
||||
// Select the TLS commitment protocol.
|
||||
.protocol(
|
||||
MpcTlsConfig::builder()
|
||||
// We must configure the amount of data we expect to exchange beforehand,
|
||||
// which will be preprocessed prior to the
|
||||
// connection. Reducing these limits will improve
|
||||
// performance.
|
||||
.max_sent_data(tlsn_examples::MAX_SENT_DATA)
|
||||
.max_recv_data(tlsn_examples::MAX_RECV_DATA)
|
||||
.build()?,
|
||||
)
|
||||
.build()?,
|
||||
socket.compat(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Open a TCP connection to the server.
|
||||
let client_socket = tokio::net::TcpStream::connect((server_host, server_port)).await?;
|
||||
|
||||
// Bind the prover to the server connection.
|
||||
// The returned `mpc_tls_connection` is an MPC TLS connection to the server: all
|
||||
// data written to/read from it will be encrypted/decrypted using MPC with
|
||||
// the notary.
|
||||
let (mpc_tls_connection, prover_fut) = prover.connect(client_socket.compat()).await?;
|
||||
let mpc_tls_connection = TokioIo::new(mpc_tls_connection.compat());
|
||||
let (tls_connection, prover_fut) = prover
|
||||
.connect(
|
||||
TlsClientConfig::builder()
|
||||
.server_name(ServerName::Dns(SERVER_DOMAIN.try_into()?))
|
||||
// Create a root certificate store with the server-fixture's self-signed
|
||||
// certificate. This is only required for offline testing with the
|
||||
// server-fixture.
|
||||
.root_store(RootCertStore {
|
||||
roots: vec![CertificateDer(CA_CERT_DER.to_vec())],
|
||||
})
|
||||
// (Optional) Set up TLS client authentication if required by the server.
|
||||
.client_auth((
|
||||
vec![CertificateDer(CLIENT_CERT_DER.to_vec())],
|
||||
PrivateKeyDer(CLIENT_KEY_DER.to_vec()),
|
||||
))
|
||||
.build()?,
|
||||
client_socket.compat(),
|
||||
)
|
||||
.await?;
|
||||
let tls_connection = TokioIo::new(tls_connection.compat());
|
||||
|
||||
// Spawn the prover task to be run concurrently in the background.
|
||||
let prover_task = tokio::spawn(prover_fut);
|
||||
|
||||
// Attach the hyper HTTP client to the connection.
|
||||
let (mut request_sender, connection) =
|
||||
hyper::client::conn::http1::handshake(mpc_tls_connection).await?;
|
||||
hyper::client::conn::http1::handshake(tls_connection).await?;
|
||||
|
||||
// Spawn the HTTP task to be run concurrently in the background.
|
||||
tokio::spawn(connection);
|
||||
@@ -168,17 +170,17 @@ async fn notarize(
|
||||
}
|
||||
let request = request_builder.body(Empty::<Bytes>::new())?;
|
||||
|
||||
println!("Starting an MPC TLS connection with the server");
|
||||
info!("Starting connection with the server");
|
||||
|
||||
// Send the request to the server and wait for the response.
|
||||
let response = request_sender.send_request(request).await?;
|
||||
|
||||
println!("Got a response from the server: {}", response.status());
|
||||
info!("Got a response from the server: {}", response.status());
|
||||
|
||||
assert!(response.status() == StatusCode::OK);
|
||||
|
||||
// The prover task should be done now, so we can await it.
|
||||
let mut prover = prover_task.await??;
|
||||
let prover = prover_task.await??;
|
||||
|
||||
// Parse the HTTP transcript.
|
||||
let transcript = HttpTranscript::parse(prover.transcript())?;
|
||||
@@ -189,10 +191,10 @@ async fn notarize(
|
||||
match body_content {
|
||||
tlsn_formats::http::BodyContent::Json(_json) => {
|
||||
let parsed = serde_json::from_str::<serde_json::Value>(&body)?;
|
||||
debug!("{}", serde_json::to_string_pretty(&parsed)?);
|
||||
info!("{}", serde_json::to_string_pretty(&parsed)?);
|
||||
}
|
||||
tlsn_formats::http::BodyContent::Unknown(_span) => {
|
||||
debug!("{}", &body);
|
||||
info!("{}", &body);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
@@ -220,10 +222,7 @@ async fn notarize(
|
||||
|
||||
let request_config = builder.build()?;
|
||||
|
||||
#[allow(deprecated)]
|
||||
let (attestation, secrets) = prover.notarize(&request_config).await?;
|
||||
|
||||
println!("Notarization complete!");
|
||||
let (attestation, secrets) = notarize(prover, &request_config, req_tx, resp_rx).await?;
|
||||
|
||||
// Write the attestation to disk.
|
||||
let attestation_path = tlsn_examples::get_file_path(example_type, "attestation");
|
||||
@@ -242,3 +241,168 @@ async fn notarize(
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn notarize(
|
||||
mut prover: Prover<Committed>,
|
||||
config: &RequestConfig,
|
||||
request_tx: Sender<AttestationRequest>,
|
||||
attestation_rx: Receiver<Attestation>,
|
||||
) -> Result<(Attestation, Secrets)> {
|
||||
let mut builder = ProveConfig::builder(prover.transcript());
|
||||
|
||||
if let Some(config) = config.transcript_commit() {
|
||||
builder.transcript_commit(config.clone());
|
||||
}
|
||||
|
||||
let disclosure_config = builder.build()?;
|
||||
|
||||
let ProverOutput {
|
||||
transcript_commitments,
|
||||
transcript_secrets,
|
||||
..
|
||||
} = prover.prove(&disclosure_config).await?;
|
||||
|
||||
let transcript = prover.transcript().clone();
|
||||
let tls_transcript = prover.tls_transcript().clone();
|
||||
prover.close().await?;
|
||||
|
||||
// Build an attestation request.
|
||||
let mut builder = AttestationRequest::builder(config);
|
||||
|
||||
builder
|
||||
.server_name(ServerName::Dns(SERVER_DOMAIN.try_into().unwrap()))
|
||||
.handshake_data(HandshakeData {
|
||||
certs: tls_transcript
|
||||
.server_cert_chain()
|
||||
.expect("server cert chain is present")
|
||||
.to_vec(),
|
||||
sig: tls_transcript
|
||||
.server_signature()
|
||||
.expect("server signature is present")
|
||||
.clone(),
|
||||
binding: tls_transcript.certificate_binding().clone(),
|
||||
})
|
||||
.transcript(transcript)
|
||||
.transcript_commitments(transcript_secrets, transcript_commitments);
|
||||
|
||||
let (request, secrets) = builder.build(&CryptoProvider::default())?;
|
||||
|
||||
// Send attestation request to notary.
|
||||
request_tx
|
||||
.send(request.clone())
|
||||
.map_err(|_| anyhow!("notary is not receiving attestation request"))?;
|
||||
|
||||
// Receive attestation from notary.
|
||||
let attestation = attestation_rx
|
||||
.await
|
||||
.map_err(|err| anyhow!("notary did not respond with attestation: {err}"))?;
|
||||
|
||||
// Signature verifier for the signature algorithm in the request.
|
||||
let provider = CryptoProvider::default();
|
||||
|
||||
// Check the attestation is consistent with the Prover's view.
|
||||
request.validate(&attestation, &provider)?;
|
||||
|
||||
Ok((attestation, secrets))
|
||||
}
|
||||
|
||||
async fn notary<S: AsyncWrite + AsyncRead + Send + Sync + Unpin + 'static>(
|
||||
socket: S,
|
||||
request_rx: Receiver<AttestationRequest>,
|
||||
attestation_tx: Sender<Attestation>,
|
||||
) -> Result<()> {
|
||||
// Create a root certificate store with the server-fixture's self-signed
|
||||
// certificate. This is only required for offline testing with the
|
||||
// server-fixture.
|
||||
let verifier_config = VerifierConfig::builder()
|
||||
.root_store(RootCertStore {
|
||||
roots: vec![CertificateDer(CA_CERT_DER.to_vec())],
|
||||
})
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let verifier = Verifier::new(verifier_config)
|
||||
.commit(socket.compat())
|
||||
.await?
|
||||
.accept()
|
||||
.await?
|
||||
.run()
|
||||
.await?;
|
||||
|
||||
let (
|
||||
VerifierOutput {
|
||||
transcript_commitments,
|
||||
encoder_secret,
|
||||
..
|
||||
},
|
||||
verifier,
|
||||
) = verifier.verify().await?.accept().await?;
|
||||
|
||||
let tls_transcript = verifier.tls_transcript().clone();
|
||||
|
||||
verifier.close().await?;
|
||||
|
||||
let sent_len = tls_transcript
|
||||
.sent()
|
||||
.iter()
|
||||
.filter_map(|record| {
|
||||
if let ContentType::ApplicationData = record.typ {
|
||||
Some(record.ciphertext.len())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.sum::<usize>();
|
||||
|
||||
let recv_len = tls_transcript
|
||||
.recv()
|
||||
.iter()
|
||||
.filter_map(|record| {
|
||||
if let ContentType::ApplicationData = record.typ {
|
||||
Some(record.ciphertext.len())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.sum::<usize>();
|
||||
|
||||
// Receive attestation request from prover.
|
||||
let request = request_rx.await?;
|
||||
|
||||
// Load a dummy signing key.
|
||||
let signing_key = k256::ecdsa::SigningKey::from_bytes(&[1u8; 32].into())?;
|
||||
let signer = Box::new(Secp256k1Signer::new(&signing_key.to_bytes())?);
|
||||
let mut provider = CryptoProvider::default();
|
||||
provider.signer.set_signer(signer);
|
||||
|
||||
// Build an attestation.
|
||||
let mut att_config_builder = AttestationConfig::builder();
|
||||
att_config_builder.supported_signature_algs(Vec::from_iter(provider.signer.supported_algs()));
|
||||
let att_config = att_config_builder.build()?;
|
||||
|
||||
let mut builder = Attestation::builder(&att_config).accept_request(request)?;
|
||||
builder
|
||||
.connection_info(ConnectionInfo {
|
||||
time: tls_transcript.time(),
|
||||
version: (*tls_transcript.version()),
|
||||
transcript_length: TranscriptLength {
|
||||
sent: sent_len as u32,
|
||||
received: recv_len as u32,
|
||||
},
|
||||
})
|
||||
.server_ephemeral_key(tls_transcript.server_ephemeral_key().clone())
|
||||
.transcript_commitments(transcript_commitments);
|
||||
|
||||
if let Some(encoder_secret) = encoder_secret {
|
||||
builder.encoder_secret(encoder_secret);
|
||||
}
|
||||
|
||||
let attestation = builder.build(&provider)?;
|
||||
|
||||
// Send attestation to prover.
|
||||
attestation_tx
|
||||
.send(attestation)
|
||||
.map_err(|_| anyhow!("prover is not receiving attestation"))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -6,14 +6,17 @@ use std::time::Duration;
|
||||
|
||||
use clap::Parser;
|
||||
|
||||
use tls_core::verify::WebPkiVerifier;
|
||||
use tls_server_fixture::CA_CERT_DER;
|
||||
use tlsn::attestation::{
|
||||
presentation::{Presentation, PresentationOutput},
|
||||
signing::VerifyingKey,
|
||||
CryptoProvider,
|
||||
use tlsn::{
|
||||
attestation::{
|
||||
presentation::{Presentation, PresentationOutput},
|
||||
signing::VerifyingKey,
|
||||
CryptoProvider,
|
||||
},
|
||||
verifier::ServerCertVerifier,
|
||||
webpki::{CertificateDer, RootCertStore},
|
||||
};
|
||||
use tlsn_examples::ExampleType;
|
||||
use tlsn_server_fixture_certs::CA_CERT_DER;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(version, about, long_about = None)]
|
||||
@@ -41,12 +44,11 @@ async fn verify_presentation(example_type: &ExampleType) -> Result<(), Box<dyn s
|
||||
//
|
||||
// This is only required for offline testing with the server-fixture. In
|
||||
// production, use `CryptoProvider::default()` instead.
|
||||
let mut root_store = tls_core::anchors::RootCertStore::empty();
|
||||
root_store
|
||||
.add(&tls_core::key::Certificate(CA_CERT_DER.to_vec()))
|
||||
.unwrap();
|
||||
let root_cert_store = RootCertStore {
|
||||
roots: vec![CertificateDer(CA_CERT_DER.to_vec())],
|
||||
};
|
||||
let crypto_provider = CryptoProvider {
|
||||
cert: WebPkiVerifier::new(root_store, None),
|
||||
cert: ServerCertVerifier::new(&root_cert_store)?,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ use std::{
|
||||
net::{IpAddr, SocketAddr},
|
||||
};
|
||||
|
||||
use anyhow::Result;
|
||||
use http_body_util::Empty;
|
||||
use hyper::{body::Bytes, Request, StatusCode, Uri};
|
||||
use hyper_util::rt::TokioIo;
|
||||
@@ -10,15 +11,22 @@ use tokio::io::{AsyncRead, AsyncWrite};
|
||||
use tokio_util::compat::{FuturesAsyncReadCompatExt, TokioAsyncReadCompatExt};
|
||||
use tracing::instrument;
|
||||
|
||||
use tls_server_fixture::CA_CERT_DER;
|
||||
use tlsn::{
|
||||
config::{ProtocolConfig, ProtocolConfigValidator},
|
||||
prover::{ProveConfig, Prover, ProverConfig, TlsConfig},
|
||||
config::{
|
||||
prove::ProveConfig,
|
||||
prover::ProverConfig,
|
||||
tls::TlsClientConfig,
|
||||
tls_commit::{mpc::MpcTlsConfig, TlsCommitConfig, TlsCommitProtocolConfig},
|
||||
verifier::VerifierConfig,
|
||||
},
|
||||
connection::ServerName,
|
||||
prover::Prover,
|
||||
transcript::PartialTranscript,
|
||||
verifier::{Verifier, VerifierConfig, VerifierOutput, VerifyConfig},
|
||||
verifier::{Verifier, VerifierOutput},
|
||||
webpki::{CertificateDer, RootCertStore},
|
||||
};
|
||||
use tlsn_server_fixture::DEFAULT_FIXTURE_PORT;
|
||||
use tlsn_server_fixture_certs::SERVER_DOMAIN;
|
||||
use tlsn_server_fixture_certs::{CA_CERT_DER, SERVER_DOMAIN};
|
||||
|
||||
const SECRET: &str = "TLSNotary's private key 🤡";
|
||||
|
||||
@@ -46,7 +54,7 @@ async fn main() {
|
||||
let (prover_socket, verifier_socket) = tokio::io::duplex(1 << 23);
|
||||
let prover = prover(prover_socket, &server_addr, &uri);
|
||||
let verifier = verifier(verifier_socket);
|
||||
let (_, transcript) = tokio::join!(prover, verifier);
|
||||
let (_, transcript) = tokio::try_join!(prover, verifier).unwrap();
|
||||
|
||||
println!("Successfully verified {}", &uri);
|
||||
println!(
|
||||
@@ -64,63 +72,57 @@ async fn prover<T: AsyncWrite + AsyncRead + Send + Unpin + 'static>(
|
||||
verifier_socket: T,
|
||||
server_addr: &SocketAddr,
|
||||
uri: &str,
|
||||
) {
|
||||
) -> Result<()> {
|
||||
let uri = uri.parse::<Uri>().unwrap();
|
||||
assert_eq!(uri.scheme().unwrap().as_str(), "https");
|
||||
let server_domain = uri.authority().unwrap().host();
|
||||
|
||||
// Create a root certificate store with the server-fixture's self-signed
|
||||
// certificate. This is only required for offline testing with the
|
||||
// server-fixture.
|
||||
let mut root_store = tls_core::anchors::RootCertStore::empty();
|
||||
root_store
|
||||
.add(&tls_core::key::Certificate(CA_CERT_DER.to_vec()))
|
||||
.unwrap();
|
||||
let mut tls_config_builder = TlsConfig::builder();
|
||||
tls_config_builder.root_store(root_store);
|
||||
let tls_config = tls_config_builder.build().unwrap();
|
||||
// Create a new prover and perform necessary setup.
|
||||
let prover = Prover::new(ProverConfig::builder().build()?)
|
||||
.commit(
|
||||
TlsCommitConfig::builder()
|
||||
// Select the TLS commitment protocol.
|
||||
.protocol(
|
||||
MpcTlsConfig::builder()
|
||||
// We must configure the amount of data we expect to exchange beforehand,
|
||||
// which will be preprocessed prior to the
|
||||
// connection. Reducing these limits will improve
|
||||
// performance.
|
||||
.max_sent_data(tlsn_examples::MAX_SENT_DATA)
|
||||
.max_recv_data(tlsn_examples::MAX_RECV_DATA)
|
||||
.build()?,
|
||||
)
|
||||
.build()?,
|
||||
verifier_socket.compat(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Set up protocol configuration for prover.
|
||||
let mut prover_config_builder = ProverConfig::builder();
|
||||
prover_config_builder
|
||||
.server_name(server_domain)
|
||||
.tls_config(tls_config)
|
||||
.protocol_config(
|
||||
ProtocolConfig::builder()
|
||||
.max_sent_data(MAX_SENT_DATA)
|
||||
.max_recv_data(MAX_RECV_DATA)
|
||||
.build()
|
||||
.unwrap(),
|
||||
);
|
||||
// Open a TCP connection to the server.
|
||||
let client_socket = tokio::net::TcpStream::connect(server_addr).await?;
|
||||
|
||||
let prover_config = prover_config_builder.build().unwrap();
|
||||
|
||||
// Create prover and connect to verifier.
|
||||
//
|
||||
// Perform the setup phase with the verifier.
|
||||
let prover = Prover::new(prover_config)
|
||||
.setup(verifier_socket.compat())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Connect to TLS Server.
|
||||
let tls_client_socket = tokio::net::TcpStream::connect(server_addr).await.unwrap();
|
||||
|
||||
// Pass server connection into the prover.
|
||||
let (mpc_tls_connection, prover_fut) =
|
||||
prover.connect(tls_client_socket.compat()).await.unwrap();
|
||||
|
||||
// Wrap the connection in a TokioIo compatibility layer to use it with hyper.
|
||||
let mpc_tls_connection = TokioIo::new(mpc_tls_connection.compat());
|
||||
// Bind the prover to the server connection.
|
||||
let (tls_connection, prover_fut) = prover
|
||||
.connect(
|
||||
TlsClientConfig::builder()
|
||||
.server_name(ServerName::Dns(SERVER_DOMAIN.try_into()?))
|
||||
// Create a root certificate store with the server-fixture's self-signed
|
||||
// certificate. This is only required for offline testing with the
|
||||
// server-fixture.
|
||||
.root_store(RootCertStore {
|
||||
roots: vec![CertificateDer(CA_CERT_DER.to_vec())],
|
||||
})
|
||||
.build()?,
|
||||
client_socket.compat(),
|
||||
)
|
||||
.await?;
|
||||
let tls_connection = TokioIo::new(tls_connection.compat());
|
||||
|
||||
// Spawn the Prover to run in the background.
|
||||
let prover_task = tokio::spawn(prover_fut);
|
||||
|
||||
// MPC-TLS Handshake.
|
||||
let (mut request_sender, connection) =
|
||||
hyper::client::conn::http1::handshake(mpc_tls_connection)
|
||||
.await
|
||||
.unwrap();
|
||||
hyper::client::conn::http1::handshake(tls_connection).await?;
|
||||
|
||||
// Spawn the connection to run in the background.
|
||||
tokio::spawn(connection);
|
||||
@@ -132,14 +134,13 @@ async fn prover<T: AsyncWrite + AsyncRead + Send + Unpin + 'static>(
|
||||
.header("Connection", "close")
|
||||
.header("Secret", SECRET)
|
||||
.method("GET")
|
||||
.body(Empty::<Bytes>::new())
|
||||
.unwrap();
|
||||
let response = request_sender.send_request(request).await.unwrap();
|
||||
.body(Empty::<Bytes>::new())?;
|
||||
let response = request_sender.send_request(request).await?;
|
||||
|
||||
assert!(response.status() == StatusCode::OK);
|
||||
|
||||
// Create proof for the Verifier.
|
||||
let mut prover = prover_task.await.unwrap().unwrap();
|
||||
let mut prover = prover_task.await??;
|
||||
|
||||
let mut builder = ProveConfig::builder(prover.transcript());
|
||||
|
||||
@@ -155,10 +156,8 @@ async fn prover<T: AsyncWrite + AsyncRead + Send + Unpin + 'static>(
|
||||
.expect("the secret should be in the sent data");
|
||||
|
||||
// Reveal everything except for the secret.
|
||||
builder.reveal_sent(&(0..pos)).unwrap();
|
||||
builder
|
||||
.reveal_sent(&(pos + SECRET.len()..prover.transcript().sent().len()))
|
||||
.unwrap();
|
||||
builder.reveal_sent(&(0..pos))?;
|
||||
builder.reveal_sent(&(pos + SECRET.len()..prover.transcript().sent().len()))?;
|
||||
|
||||
// Find the substring "Dick".
|
||||
let pos = prover
|
||||
@@ -169,52 +168,78 @@ async fn prover<T: AsyncWrite + AsyncRead + Send + Unpin + 'static>(
|
||||
.expect("the substring 'Dick' should be in the received data");
|
||||
|
||||
// Reveal everything except for the substring.
|
||||
builder.reveal_recv(&(0..pos)).unwrap();
|
||||
builder
|
||||
.reveal_recv(&(pos + 4..prover.transcript().received().len()))
|
||||
.unwrap();
|
||||
builder.reveal_recv(&(0..pos))?;
|
||||
builder.reveal_recv(&(pos + 4..prover.transcript().received().len()))?;
|
||||
|
||||
let config = builder.build().unwrap();
|
||||
let config = builder.build()?;
|
||||
|
||||
prover.prove(&config).await.unwrap();
|
||||
prover.close().await.unwrap();
|
||||
prover.prove(&config).await?;
|
||||
prover.close().await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(skip(socket))]
|
||||
async fn verifier<T: AsyncWrite + AsyncRead + Send + Sync + Unpin + 'static>(
|
||||
socket: T,
|
||||
) -> PartialTranscript {
|
||||
// Set up Verifier.
|
||||
let config_validator = ProtocolConfigValidator::builder()
|
||||
.max_sent_data(MAX_SENT_DATA)
|
||||
.max_recv_data(MAX_RECV_DATA)
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
) -> Result<PartialTranscript> {
|
||||
// Create a root certificate store with the server-fixture's self-signed
|
||||
// certificate. This is only required for offline testing with the
|
||||
// server-fixture.
|
||||
let mut root_store = tls_core::anchors::RootCertStore::empty();
|
||||
root_store
|
||||
.add(&tls_core::key::Certificate(CA_CERT_DER.to_vec()))
|
||||
.unwrap();
|
||||
|
||||
let verifier_config = VerifierConfig::builder()
|
||||
.root_store(root_store)
|
||||
.protocol_config_validator(config_validator)
|
||||
.build()
|
||||
.unwrap();
|
||||
.root_store(RootCertStore {
|
||||
roots: vec![CertificateDer(CA_CERT_DER.to_vec())],
|
||||
})
|
||||
.build()?;
|
||||
let verifier = Verifier::new(verifier_config);
|
||||
|
||||
// Receive authenticated data.
|
||||
let VerifierOutput {
|
||||
server_name,
|
||||
transcript,
|
||||
..
|
||||
} = verifier
|
||||
.verify(socket.compat(), &VerifyConfig::default())
|
||||
.await
|
||||
.unwrap();
|
||||
// Validate the proposed configuration and then run the TLS commitment protocol.
|
||||
let verifier = verifier.commit(socket.compat()).await?;
|
||||
|
||||
// This is the opportunity to ensure the prover does not attempt to overload the
|
||||
// verifier.
|
||||
let reject = if let TlsCommitProtocolConfig::Mpc(mpc_tls_config) = verifier.request().protocol()
|
||||
{
|
||||
if mpc_tls_config.max_sent_data() > MAX_SENT_DATA {
|
||||
Some("max_sent_data is too large")
|
||||
} else if mpc_tls_config.max_recv_data() > MAX_RECV_DATA {
|
||||
Some("max_recv_data is too large")
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
Some("expecting to use MPC-TLS")
|
||||
};
|
||||
|
||||
if reject.is_some() {
|
||||
verifier.reject(reject).await?;
|
||||
return Err(anyhow::anyhow!("protocol configuration rejected"));
|
||||
}
|
||||
|
||||
// Runs the TLS commitment protocol to completion.
|
||||
let verifier = verifier.accept().await?.run().await?;
|
||||
|
||||
// Validate the proving request and then verify.
|
||||
let verifier = verifier.verify().await?;
|
||||
|
||||
if !verifier.request().server_identity() {
|
||||
let verifier = verifier
|
||||
.reject(Some("expecting to verify the server name"))
|
||||
.await?;
|
||||
verifier.close().await?;
|
||||
return Err(anyhow::anyhow!("prover did not reveal the server name"));
|
||||
}
|
||||
|
||||
let (
|
||||
VerifierOutput {
|
||||
server_name,
|
||||
transcript,
|
||||
..
|
||||
},
|
||||
verifier,
|
||||
) = verifier.accept().await?;
|
||||
|
||||
verifier.close().await?;
|
||||
|
||||
let server_name = server_name.expect("prover should have revealed server name");
|
||||
let transcript = transcript.expect("prover should have revealed transcript data");
|
||||
@@ -234,9 +259,10 @@ async fn verifier<T: AsyncWrite + AsyncRead + Send + Sync + Unpin + 'static>(
|
||||
.unwrap_or_else(|| panic!("Expected valid data from {SERVER_DOMAIN}"));
|
||||
|
||||
// Check Session info: server name.
|
||||
let ServerName::Dns(server_name) = server_name;
|
||||
assert_eq!(server_name.as_str(), SERVER_DOMAIN);
|
||||
|
||||
transcript
|
||||
Ok(transcript)
|
||||
}
|
||||
|
||||
/// Render redacted bytes as `🙈`.
|
||||
|
||||
5
crates/examples/interactive_zk/.gitignore
vendored
Normal file
5
crates/examples/interactive_zk/.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
!noir/target/
|
||||
# Ignore everything inside noir/target
|
||||
noir/target/*
|
||||
# Except noir.json
|
||||
!noir/target/noir.json
|
||||
167
crates/examples/interactive_zk/README.md
Normal file
167
crates/examples/interactive_zk/README.md
Normal file
@@ -0,0 +1,167 @@
|
||||
# Interactive Zero-Knowledge Age Verification with TLSNotary
|
||||
|
||||
This example demonstrates **privacy-preserving age verification** using TLSNotary and zero-knowledge proofs. It allows a prover to demonstrate they are 18+ years old without revealing their actual birth date or any other personal information.
|
||||
|
||||
## 🔍 How It Works (simplified overview)
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant S as Tax Server<br/>(fixture)
|
||||
participant P as Prover
|
||||
participant V as Verifier
|
||||
|
||||
P->>S: Request tax data (with auth token) (MPC-TLS)
|
||||
S->>P: Tax data including `date_of_birth` (MPC-TLS)
|
||||
P->>V: Share transcript with redactions
|
||||
P->>V: Commit to blinded hash of birth date
|
||||
P->>P: Generate ZK proof of age ≥ 18
|
||||
P->>V: Send ZK proof
|
||||
V->>V: Verify transcript & ZK proof
|
||||
V->>V: ✅ Confirm: Prover is 18+ (no birth date revealed)
|
||||
```
|
||||
|
||||
### The Process
|
||||
|
||||
1. **MPC-TLS Session**: The Prover fetches tax information containing their birth date, while the Verifier jointly verifies the TLS session to ensure the data comes from the authentic server.
|
||||
2. **Selective Disclosure**:
|
||||
* The authorization token is **redacted**: the Verifier sees the plaintext request but not the token.
|
||||
* The birth date is **committed** as a blinded hash: the Verifier cannot see the date, but the Prover is cryptographically bound to it.
|
||||
(Depending on the use case more data can be redacted or revealed)
|
||||
3. **Zero-Knowledge Proof**: The Prover generates a ZK proof that the committed birth date corresponds to an age ≥ 18.
|
||||
4. **Verification**: The Verifier checks both the TLS transcript and the ZK proof, confirming age ≥ 18 without learning the actual date of birth.
|
||||
|
||||
|
||||
### Example Data
|
||||
|
||||
The tax server returns data like this:
|
||||
```json
|
||||
{
|
||||
"tax_year": 2024,
|
||||
"taxpayer": {
|
||||
"idnr": "12345678901",
|
||||
"first_name": "Max",
|
||||
"last_name": "Mustermann",
|
||||
"date_of_birth": "1985-03-12",
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 🔐 Zero-Knowledge Proof Details
|
||||
|
||||
The ZK circuit proves: **"I know a birth date that hashes to the committed value AND indicates I am 18+ years old"**
|
||||
|
||||
**Public Inputs:**
|
||||
- ✅ Verification date
|
||||
- ✅ Committed blinded hash of birth date
|
||||
|
||||
**Private Inputs (Hidden):**
|
||||
- 🔒 Actual birth date plaintext
|
||||
- 🔒 Random blinder used in hash commitment
|
||||
|
||||
**What the Verifier Learns:**
|
||||
- ✅ The prover is 18+ years old
|
||||
- ✅ The birth date is authentic (from the MPC-TLS session)
|
||||
|
||||
Everything else remains private.
|
||||
|
||||
## 🏃 Run the Example
|
||||
|
||||
1. **Start the test server** (from repository root):
|
||||
```bash
|
||||
RUST_LOG=info PORT=4000 cargo run --bin tlsn-server-fixture
|
||||
```
|
||||
|
||||
2. **Run the age verification** (in a new terminal):
|
||||
```bash
|
||||
SERVER_PORT=4000 cargo run --release --example interactive_zk
|
||||
```
|
||||
|
||||
3. **For detailed logs**:
|
||||
```bash
|
||||
RUST_LOG=debug,yamux=info,uid_mux=info SERVER_PORT=4000 cargo run --release --example interactive_zk
|
||||
```
|
||||
|
||||
### Expected Output
|
||||
|
||||
```
|
||||
Successfully verified https://test-server.io:4000/elster
|
||||
Age verified in ZK: 18+ ✅
|
||||
|
||||
Verified sent data:
|
||||
GET https://test-server.io:4000/elster HTTP/1.1
|
||||
host: test-server.io
|
||||
connection: close
|
||||
authorization: 🙈🙈🙈🙈🙈🙈🙈🙈🙈🙈🙈🙈🙈🙈🙈🙈🙈🙈🙈🙈🙈🙈🙈🙈🙈🙈
|
||||
|
||||
Verified received data:
|
||||
🙈🙈🙈🙈🙈🙈🙈🙈[truncated for brevity]...🙈🙈🙈🙈🙈"tax_year":2024🙈🙈🙈🙈🙈...
|
||||
```
|
||||
|
||||
> 💡 **Note**: In this demo, both Prover and Verifier run on the same machine. In production, they would operate on separate systems.
|
||||
> 💡 **Note**: This demo assumes that the tax server serves correct data, and that only the submitter of the tax data has access to the specified page.
|
||||
|
||||
## 🛠 Development
|
||||
|
||||
### Project Structure
|
||||
|
||||
```
|
||||
interactive_zk/
|
||||
├── prover.rs # Prover implementation
|
||||
├── verifier.rs # Verifier implementation
|
||||
├── types.rs # Shared types
|
||||
└── interactive_zk.rs # Main example runner
|
||||
├── noir/ # Zero-knowledge circuit
|
||||
│ ├── src/main.n # Noir circuit code
|
||||
│ ├── target/ # Compiled circuit artifacts
|
||||
│ └── Nargo.toml # Noir project config
|
||||
│ └── Prover.toml # Example input for `nargo execute`
|
||||
│ └── generate_test_data.rs # Rust script to generate Noir test data
|
||||
└── README.md
|
||||
```
|
||||
|
||||
### Noir Circuit Commands
|
||||
|
||||
We use [Mopro's `noir_rs`](https://zkmopro.org/docs/crates/noir-rs/) for ZK proof generation. The **circuit is pre-compiled and ready to use**. You don't need to install Noir tools to run the example. But if you want to change or test the circuit in isolation, you can use the following instructions.
|
||||
|
||||
Before you proceed, we recommend to double check that your Noir tooling matches the versions used in Mopro's `noir_rs`:
|
||||
```sh
|
||||
# Install correct Noir and BB versions (important for compatibility!)
|
||||
noirup --version 1.0.0-beta.8
|
||||
bbup -v 1.0.0-nightly.20250723
|
||||
```
|
||||
|
||||
If you don't have `noirup` and `bbup` installed yet, check [Noir's Quick Start](https://noir-lang.org/docs/getting_started/quick_start).
|
||||
|
||||
To compile the circuit, go to the `noir` folder and run `nargo compile`.
|
||||
|
||||
To check and experiment with the Noir circuit, you can use these commands:
|
||||
|
||||
* Execute Circuit: Compile the circuit and run it with sample data from `Prover.toml`:
|
||||
```sh
|
||||
nargo execute
|
||||
```
|
||||
* Generate Verification Key: Create the verification key needed to verify proofs
|
||||
```sh
|
||||
bb write_vk -b ./target/noir.json -o ./target
|
||||
```
|
||||
* Generate Proof: Create a zero-knowledge proof using the circuit and witness data.
|
||||
```sh
|
||||
bb prove --bytecode_path ./target/noir.json --witness_path ./target/noir.gz -o ./target
|
||||
```
|
||||
* Verify Proof: Verify that a proof is valid using the verification key.
|
||||
```sh
|
||||
bb verify -k ./target/vk -p ./target/proof
|
||||
```
|
||||
* Run the Noir tests:
|
||||
```sh
|
||||
nargo test --show-output
|
||||
```
|
||||
To create extra tests, you can use `./generate_test_data.rs` to help with generating correct blinders and hashes.
|
||||
|
||||
## 📚 Learn More
|
||||
|
||||
- [TLSNotary Documentation](https://docs.tlsnotary.org/)
|
||||
- [Noir Language Guide](https://noir-lang.org/)
|
||||
- [Zero-Knowledge Proofs Explained](https://ethereum.org/en/zero-knowledge-proofs/)
|
||||
- [Mopro ZK Toolkit](https://zkmopro.org/)
|
||||
60
crates/examples/interactive_zk/interactive_zk.rs
Normal file
60
crates/examples/interactive_zk/interactive_zk.rs
Normal file
@@ -0,0 +1,60 @@
|
||||
mod prover;
|
||||
mod types;
|
||||
mod verifier;
|
||||
|
||||
use anyhow::Result;
|
||||
use prover::prover;
|
||||
use std::{
|
||||
env,
|
||||
net::{IpAddr, SocketAddr},
|
||||
};
|
||||
use tlsn_server_fixture::DEFAULT_FIXTURE_PORT;
|
||||
use tlsn_server_fixture_certs::SERVER_DOMAIN;
|
||||
use verifier::verifier;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
let server_host: String = env::var("SERVER_HOST").unwrap_or("127.0.0.1".into());
|
||||
let server_port: u16 = env::var("SERVER_PORT")
|
||||
.map(|port| port.parse().expect("port should be valid integer"))
|
||||
.unwrap_or(DEFAULT_FIXTURE_PORT);
|
||||
|
||||
// We use SERVER_DOMAIN here to make sure it matches the domain in the test
|
||||
// server's certificate.
|
||||
let uri = format!("https://{SERVER_DOMAIN}:{server_port}/elster");
|
||||
let server_ip: IpAddr = server_host
|
||||
.parse()
|
||||
.map_err(|e| anyhow::anyhow!("Invalid IP address '{server_host}': {e}"))?;
|
||||
let server_addr = SocketAddr::from((server_ip, server_port));
|
||||
|
||||
// Connect prover and verifier.
|
||||
let (prover_socket, verifier_socket) = tokio::io::duplex(1 << 23);
|
||||
let (prover_extra_socket, verifier_extra_socket) = tokio::io::duplex(1 << 23);
|
||||
|
||||
let (_, transcript) = tokio::try_join!(
|
||||
prover(prover_socket, prover_extra_socket, &server_addr, &uri),
|
||||
verifier(verifier_socket, verifier_extra_socket)
|
||||
)?;
|
||||
|
||||
println!("---");
|
||||
println!("Successfully verified {}", &uri);
|
||||
println!("Age verified in ZK: 18+ ✅\n");
|
||||
|
||||
println!(
|
||||
"Verified sent data:\n{}",
|
||||
bytes_to_redacted_string(transcript.sent_unsafe())
|
||||
);
|
||||
println!(
|
||||
"Verified received data:\n{}",
|
||||
bytes_to_redacted_string(transcript.received_unsafe())
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Render redacted bytes as `🙈`.
|
||||
pub fn bytes_to_redacted_string(bytes: &[u8]) -> String {
|
||||
String::from_utf8_lossy(bytes).replace('\0', "🙈")
|
||||
}
|
||||
8
crates/examples/interactive_zk/noir/Nargo.toml
Normal file
8
crates/examples/interactive_zk/noir/Nargo.toml
Normal file
@@ -0,0 +1,8 @@
|
||||
[package]
|
||||
name = "noir"
|
||||
type = "bin"
|
||||
authors = [""]
|
||||
|
||||
[dependencies]
|
||||
sha256 = { tag = "v0.1.5", git = "https://github.com/noir-lang/sha256" }
|
||||
date = { tag = "v0.5.4", git = "https://github.com/madztheo/noir-date.git" }
|
||||
8
crates/examples/interactive_zk/noir/Prover.toml
Normal file
8
crates/examples/interactive_zk/noir/Prover.toml
Normal file
@@ -0,0 +1,8 @@
|
||||
blinder = [108, 93, 120, 205, 15, 35, 159, 124, 243, 96, 22, 128, 16, 149, 219, 216]
|
||||
committed_hash = [186, 158, 101, 39, 49, 48, 26, 83, 242, 96, 10, 221, 121, 174, 62, 50, 136, 132, 232, 58, 25, 32, 66, 196, 99, 85, 66, 85, 255, 1, 202, 254]
|
||||
date_of_birth = "1985-03-12"
|
||||
|
||||
[proof_date]
|
||||
day = "29"
|
||||
month = "08"
|
||||
year = "2025"
|
||||
64
crates/examples/interactive_zk/noir/generate_test_data.rs
Executable file
64
crates/examples/interactive_zk/noir/generate_test_data.rs
Executable file
@@ -0,0 +1,64 @@
|
||||
#!/usr/bin/env -S cargo +nightly -Zscript
|
||||
---
|
||||
[package]
|
||||
name = "generate_test_data"
|
||||
version = "0.0.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
sha2 = "0.10"
|
||||
rand = "0.8"
|
||||
chrono = "0.4"
|
||||
---
|
||||
use chrono::Datelike;
|
||||
use chrono::Local;
|
||||
use rand::RngCore;
|
||||
use sha2::{Digest, Sha256};
|
||||
|
||||
fn main() {
|
||||
// 1. Birthdate string (fixed)
|
||||
let dob_str = "1985-03-12"; // 10 bytes long
|
||||
|
||||
let proof_date = Local::now().date_naive();
|
||||
let proof_year = proof_date.year();
|
||||
let proof_month = proof_date.month();
|
||||
let proof_day = proof_date.day();
|
||||
|
||||
// 2. Generate random 16-byte blinder
|
||||
let mut blinder = [0u8; 16];
|
||||
rand::thread_rng().fill_bytes(&mut blinder);
|
||||
|
||||
// 3. Concatenate blinder + dob string bytes
|
||||
let mut preimage = Vec::with_capacity(26);
|
||||
preimage.extend_from_slice(dob_str.as_bytes());
|
||||
preimage.extend_from_slice(&blinder);
|
||||
|
||||
// 4. Hash it
|
||||
let hash = Sha256::digest(&preimage);
|
||||
|
||||
let blinder = blinder
|
||||
.iter()
|
||||
.map(|b| b.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
let committed_hash = hash
|
||||
.iter()
|
||||
.map(|b| b.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
|
||||
println!(
|
||||
"
|
||||
// Private input
|
||||
let date_of_birth = \"{dob_str}\";
|
||||
let blinder = [{blinder}];
|
||||
|
||||
// Public input
|
||||
let proof_date = date::Date {{ year: {proof_year}, month: {proof_month}, day: {proof_day} }};
|
||||
let committed_hash = [{committed_hash}];
|
||||
|
||||
main(proof_date, committed_hash, date_of_birth, blinder);
|
||||
"
|
||||
);
|
||||
}
|
||||
82
crates/examples/interactive_zk/noir/src/main.nr
Normal file
82
crates/examples/interactive_zk/noir/src/main.nr
Normal file
@@ -0,0 +1,82 @@
|
||||
use dep::date::Date;
|
||||
|
||||
fn main(
|
||||
// Public inputs
|
||||
proof_date: pub date::Date, // "2025-08-29"
|
||||
committed_hash: pub [u8; 32], // Hash of (blinder || dob string)
|
||||
// Private inputs
|
||||
date_of_birth: str<10>, // "1985-03-12"
|
||||
blinder: [u8; 16], // Random 16-byte blinder
|
||||
) {
|
||||
let is_18 = check_18(date_of_birth, proof_date);
|
||||
|
||||
let correct_hash = check_hash(date_of_birth, blinder, committed_hash);
|
||||
|
||||
assert(correct_hash);
|
||||
assert(is_18);
|
||||
}
|
||||
|
||||
fn check_18(date_of_birth: str<10>, proof_date: date::Date) -> bool {
|
||||
let dob = parse_birth_date(date_of_birth);
|
||||
let is_18 = dob.add_years(18).lt(proof_date);
|
||||
println(f"Is 18? {is_18}");
|
||||
is_18
|
||||
}
|
||||
|
||||
fn check_hash(date_of_birth: str<10>, blinder: [u8; 16], committed_hash: [u8; 32]) -> bool {
|
||||
let hash_input: [u8; 26] = make_hash_input(date_of_birth, blinder);
|
||||
let computed_hash = sha256::sha256_var(hash_input, 26);
|
||||
let correct_hash = computed_hash == committed_hash;
|
||||
println(f"Correct hash? {correct_hash}");
|
||||
correct_hash
|
||||
}
|
||||
|
||||
fn make_hash_input(dob: str<10>, blinder: [u8; 16]) -> [u8; 26] {
|
||||
let mut input: [u8; 26] = [0; 26];
|
||||
for i in 0..10 {
|
||||
input[i] = dob.as_bytes()[i];
|
||||
}
|
||||
for i in 0..16 {
|
||||
input[10 + i] = blinder[i];
|
||||
}
|
||||
input
|
||||
}
|
||||
|
||||
pub fn parse_birth_date(birth_date: str<10>) -> date::Date {
|
||||
let date: [u8; 10] = birth_date.as_bytes();
|
||||
let date_str: str<8> =
|
||||
[date[0], date[1], date[2], date[3], date[5], date[6], date[8], date[9]].as_str_unchecked();
|
||||
Date::from_str_long_year(date_str)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_max_is_over_18() {
|
||||
// Private input
|
||||
let date_of_birth = "1985-03-12";
|
||||
let blinder = [120, 80, 62, 10, 76, 60, 130, 98, 147, 161, 139, 126, 27, 236, 36, 56];
|
||||
|
||||
// Public input
|
||||
let proof_date = date::Date { year: 2025, month: 9, day: 2 };
|
||||
let committed_hash = [
|
||||
229, 118, 202, 216, 213, 230, 125, 163, 48, 178, 118, 225, 84, 7, 140, 63, 173, 255, 163,
|
||||
208, 163, 3, 63, 204, 37, 120, 254, 246, 202, 116, 122, 145,
|
||||
];
|
||||
|
||||
main(proof_date, committed_hash, date_of_birth, blinder);
|
||||
}
|
||||
|
||||
#[test(should_fail)]
|
||||
fn test_under_18() {
|
||||
// Private input
|
||||
let date_of_birth = "2010-08-01";
|
||||
let blinder = [160, 23, 57, 158, 141, 195, 155, 132, 109, 242, 48, 220, 70, 217, 229, 189];
|
||||
|
||||
// Public input
|
||||
let proof_date = date::Date { year: 2025, month: 8, day: 29 };
|
||||
let committed_hash = [
|
||||
16, 132, 194, 62, 232, 90, 157, 153, 4, 231, 1, 54, 226, 3, 87, 174, 129, 177, 80, 69, 37,
|
||||
222, 209, 91, 168, 156, 9, 109, 108, 144, 168, 109,
|
||||
];
|
||||
|
||||
main(proof_date, committed_hash, date_of_birth, blinder);
|
||||
}
|
||||
1
crates/examples/interactive_zk/noir/target/noir.json
Normal file
1
crates/examples/interactive_zk/noir/target/noir.json
Normal file
File diff suppressed because one or more lines are too long
390
crates/examples/interactive_zk/prover.rs
Normal file
390
crates/examples/interactive_zk/prover.rs
Normal file
@@ -0,0 +1,390 @@
|
||||
use std::net::SocketAddr;
|
||||
|
||||
use crate::types::received_commitments;
|
||||
|
||||
use super::types::ZKProofBundle;
|
||||
|
||||
use anyhow::Result;
|
||||
use chrono::{Datelike, Local, NaiveDate};
|
||||
use http_body_util::Empty;
|
||||
use hyper::{body::Bytes, header, Request, StatusCode, Uri};
|
||||
use hyper_util::rt::TokioIo;
|
||||
use k256::sha2::{Digest, Sha256};
|
||||
use noir::{
|
||||
barretenberg::{
|
||||
prove::prove_ultra_honk, srs::setup_srs_from_bytecode,
|
||||
verify::get_ultra_honk_verification_key,
|
||||
},
|
||||
witness::from_vec_str_to_witness_map,
|
||||
};
|
||||
use serde_json::Value;
|
||||
use spansy::{
|
||||
http::{BodyContent, Requests, Responses},
|
||||
Spanned,
|
||||
};
|
||||
use tls_server_fixture::{CA_CERT_DER, SERVER_DOMAIN};
|
||||
use tlsn::{
|
||||
config::{
|
||||
prove::{ProveConfig, ProveConfigBuilder},
|
||||
prover::ProverConfig,
|
||||
tls::TlsClientConfig,
|
||||
tls_commit::{mpc::MpcTlsConfig, TlsCommitConfig},
|
||||
},
|
||||
connection::ServerName,
|
||||
hash::HashAlgId,
|
||||
prover::Prover,
|
||||
transcript::{
|
||||
hash::{PlaintextHash, PlaintextHashSecret},
|
||||
Direction, TranscriptCommitConfig, TranscriptCommitConfigBuilder, TranscriptCommitmentKind,
|
||||
TranscriptSecret,
|
||||
},
|
||||
webpki::{CertificateDer, RootCertStore},
|
||||
};
|
||||
|
||||
use tlsn_examples::{MAX_RECV_DATA, MAX_SENT_DATA};
|
||||
use tokio::io::{AsyncRead, AsyncWrite, AsyncWriteExt};
|
||||
use tokio_util::compat::{FuturesAsyncReadCompatExt, TokioAsyncReadCompatExt};
|
||||
use tracing::instrument;
|
||||
|
||||
#[instrument(skip(verifier_socket, verifier_extra_socket))]
|
||||
pub async fn prover<T: AsyncWrite + AsyncRead + Send + Unpin + 'static>(
|
||||
verifier_socket: T,
|
||||
mut verifier_extra_socket: T,
|
||||
server_addr: &SocketAddr,
|
||||
uri: &str,
|
||||
) -> Result<()> {
|
||||
let uri = uri.parse::<Uri>()?;
|
||||
|
||||
if uri.scheme().map(|s| s.as_str()) != Some("https") {
|
||||
return Err(anyhow::anyhow!("URI must use HTTPS scheme"));
|
||||
}
|
||||
|
||||
let server_domain = uri
|
||||
.authority()
|
||||
.ok_or_else(|| anyhow::anyhow!("URI must have authority"))?
|
||||
.host();
|
||||
|
||||
// Create a new prover and perform necessary setup.
|
||||
let prover = Prover::new(ProverConfig::builder().build()?)
|
||||
.commit(
|
||||
TlsCommitConfig::builder()
|
||||
// Select the TLS commitment protocol.
|
||||
.protocol(
|
||||
MpcTlsConfig::builder()
|
||||
// We must configure the amount of data we expect to exchange beforehand,
|
||||
// which will be preprocessed prior to the
|
||||
// connection. Reducing these limits will improve
|
||||
// performance.
|
||||
.max_sent_data(MAX_SENT_DATA)
|
||||
.max_recv_data(MAX_RECV_DATA)
|
||||
.build()?,
|
||||
)
|
||||
.build()?,
|
||||
verifier_socket.compat(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Open a TCP connection to the server.
|
||||
let client_socket = tokio::net::TcpStream::connect(server_addr).await?;
|
||||
|
||||
// Bind the prover to the server connection.
|
||||
let (tls_connection, prover_fut) = prover
|
||||
.connect(
|
||||
TlsClientConfig::builder()
|
||||
.server_name(ServerName::Dns(SERVER_DOMAIN.try_into()?))
|
||||
// Create a root certificate store with the server-fixture's self-signed
|
||||
// certificate. This is only required for offline testing with the
|
||||
// server-fixture.
|
||||
.root_store(RootCertStore {
|
||||
roots: vec![CertificateDer(CA_CERT_DER.to_vec())],
|
||||
})
|
||||
.build()?,
|
||||
client_socket.compat(),
|
||||
)
|
||||
.await?;
|
||||
let tls_connection = TokioIo::new(tls_connection.compat());
|
||||
|
||||
// Spawn the Prover to run in the background.
|
||||
let prover_task = tokio::spawn(prover_fut);
|
||||
|
||||
// MPC-TLS Handshake.
|
||||
let (mut request_sender, connection) =
|
||||
hyper::client::conn::http1::handshake(tls_connection).await?;
|
||||
|
||||
// Spawn the connection to run in the background.
|
||||
tokio::spawn(connection);
|
||||
|
||||
// MPC-TLS: Send Request and wait for Response.
|
||||
let request = Request::builder()
|
||||
.uri(uri.clone())
|
||||
.header("Host", server_domain)
|
||||
.header("Connection", "close")
|
||||
.header(header::AUTHORIZATION, "Bearer random_auth_token")
|
||||
.method("GET")
|
||||
.body(Empty::<Bytes>::new())?;
|
||||
|
||||
let response = request_sender.send_request(request).await?;
|
||||
|
||||
if response.status() != StatusCode::OK {
|
||||
return Err(anyhow::anyhow!(
|
||||
"MPC-TLS request failed with status {}",
|
||||
response.status()
|
||||
));
|
||||
}
|
||||
|
||||
// Create proof for the Verifier.
|
||||
let mut prover = prover_task.await??;
|
||||
|
||||
let transcript = prover.transcript().clone();
|
||||
let mut prove_config_builder = ProveConfig::builder(&transcript);
|
||||
|
||||
// Reveal the DNS name.
|
||||
prove_config_builder.server_identity();
|
||||
|
||||
let sent: &[u8] = transcript.sent();
|
||||
let received: &[u8] = transcript.received();
|
||||
let sent_len = sent.len();
|
||||
let recv_len = received.len();
|
||||
tracing::info!("Sent length: {}, Received length: {}", sent_len, recv_len);
|
||||
|
||||
// Reveal the entire HTTP request except for the authorization bearer token
|
||||
reveal_request(sent, &mut prove_config_builder)?;
|
||||
|
||||
// Create hash commitment for the date of birth field from the response
|
||||
let mut transcript_commitment_builder = TranscriptCommitConfig::builder(&transcript);
|
||||
transcript_commitment_builder.default_kind(TranscriptCommitmentKind::Hash {
|
||||
alg: HashAlgId::SHA256,
|
||||
});
|
||||
reveal_received(
|
||||
received,
|
||||
&mut prove_config_builder,
|
||||
&mut transcript_commitment_builder,
|
||||
)?;
|
||||
|
||||
let transcripts_commitment_config = transcript_commitment_builder.build()?;
|
||||
prove_config_builder.transcript_commit(transcripts_commitment_config);
|
||||
|
||||
let prove_config = prove_config_builder.build()?;
|
||||
|
||||
// MPC-TLS prove
|
||||
let prover_output = prover.prove(&prove_config).await?;
|
||||
prover.close().await?;
|
||||
|
||||
// Prove birthdate is more than 18 years ago.
|
||||
let received_commitments = received_commitments(&prover_output.transcript_commitments);
|
||||
let received_commitment = received_commitments
|
||||
.first()
|
||||
.ok_or_else(|| anyhow::anyhow!("No received commitments found"))?; // committed hash (of date of birth string)
|
||||
let received_secrets = received_secrets(&prover_output.transcript_secrets);
|
||||
let received_secret = received_secrets
|
||||
.first()
|
||||
.ok_or_else(|| anyhow::anyhow!("No received secrets found"))?; // hash blinder
|
||||
let proof_input = prepare_zk_proof_input(received, received_commitment, received_secret)?;
|
||||
let proof_bundle = generate_zk_proof(&proof_input)?;
|
||||
|
||||
// Sent zk proof bundle to verifier
|
||||
let serialized_proof = bincode::serialize(&proof_bundle)?;
|
||||
verifier_extra_socket.write_all(&serialized_proof).await?;
|
||||
verifier_extra_socket.shutdown().await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Reveal everything from the request, except for the authorization token.
|
||||
fn reveal_request(request: &[u8], builder: &mut ProveConfigBuilder<'_>) -> Result<()> {
|
||||
let reqs = Requests::new_from_slice(request).collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
let req = reqs
|
||||
.first()
|
||||
.ok_or_else(|| anyhow::anyhow!("No requests found"))?;
|
||||
|
||||
if req.request.method.as_str() != "GET" {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Expected GET method, found {}",
|
||||
req.request.method.as_str()
|
||||
));
|
||||
}
|
||||
|
||||
let authorization_header = req
|
||||
.headers_with_name(header::AUTHORIZATION.as_str())
|
||||
.next()
|
||||
.ok_or_else(|| anyhow::anyhow!("Authorization header not found"))?;
|
||||
|
||||
let start_pos = authorization_header
|
||||
.span()
|
||||
.indices()
|
||||
.min()
|
||||
.ok_or_else(|| anyhow::anyhow!("Could not find authorization header start position"))?
|
||||
+ header::AUTHORIZATION.as_str().len()
|
||||
+ 2;
|
||||
let end_pos =
|
||||
start_pos + authorization_header.span().len() - header::AUTHORIZATION.as_str().len() - 2;
|
||||
|
||||
builder.reveal_sent(&(0..start_pos))?;
|
||||
builder.reveal_sent(&(end_pos..request.len()))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn reveal_received(
|
||||
received: &[u8],
|
||||
builder: &mut ProveConfigBuilder<'_>,
|
||||
transcript_commitment_builder: &mut TranscriptCommitConfigBuilder,
|
||||
) -> Result<()> {
|
||||
let resp = Responses::new_from_slice(received).collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
let response = resp
|
||||
.first()
|
||||
.ok_or_else(|| anyhow::anyhow!("No responses found"))?;
|
||||
let body = response
|
||||
.body
|
||||
.as_ref()
|
||||
.ok_or_else(|| anyhow::anyhow!("Response body not found"))?;
|
||||
|
||||
let BodyContent::Json(json) = &body.content else {
|
||||
return Err(anyhow::anyhow!("Expected JSON body content"));
|
||||
};
|
||||
|
||||
// reveal tax year
|
||||
let tax_year = json
|
||||
.get("tax_year")
|
||||
.ok_or_else(|| anyhow::anyhow!("tax_year field not found in JSON"))?;
|
||||
let start_pos = tax_year
|
||||
.span()
|
||||
.indices()
|
||||
.min()
|
||||
.ok_or_else(|| anyhow::anyhow!("Could not find tax_year start position"))?
|
||||
- 11;
|
||||
let end_pos = tax_year
|
||||
.span()
|
||||
.indices()
|
||||
.max()
|
||||
.ok_or_else(|| anyhow::anyhow!("Could not find tax_year end position"))?
|
||||
+ 1;
|
||||
builder.reveal_recv(&(start_pos..end_pos))?;
|
||||
|
||||
// commit to hash of date of birth
|
||||
let dob = json
|
||||
.get("taxpayer.date_of_birth")
|
||||
.ok_or_else(|| anyhow::anyhow!("taxpayer.date_of_birth field not found in JSON"))?;
|
||||
|
||||
transcript_commitment_builder.commit_recv(dob.span())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// extract secret from prover output
|
||||
fn received_secrets(transcript_secrets: &[TranscriptSecret]) -> Vec<&PlaintextHashSecret> {
|
||||
transcript_secrets
|
||||
.iter()
|
||||
.filter_map(|secret| match secret {
|
||||
TranscriptSecret::Hash(hash) if hash.direction == Direction::Received => Some(hash),
|
||||
_ => None,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ZKProofInput {
|
||||
dob: Vec<u8>,
|
||||
proof_date: NaiveDate,
|
||||
blinder: Vec<u8>,
|
||||
committed_hash: Vec<u8>,
|
||||
}
|
||||
|
||||
// Verify that the blinded, committed hash is correct
|
||||
fn prepare_zk_proof_input(
|
||||
received: &[u8],
|
||||
received_commitment: &PlaintextHash,
|
||||
received_secret: &PlaintextHashSecret,
|
||||
) -> Result<ZKProofInput> {
|
||||
assert_eq!(received_commitment.direction, Direction::Received);
|
||||
assert_eq!(received_commitment.hash.alg, HashAlgId::SHA256);
|
||||
|
||||
let hash = &received_commitment.hash;
|
||||
|
||||
let dob_start = received_commitment
|
||||
.idx
|
||||
.min()
|
||||
.ok_or_else(|| anyhow::anyhow!("No start index for DOB"))?;
|
||||
let dob_end = received_commitment
|
||||
.idx
|
||||
.end()
|
||||
.ok_or_else(|| anyhow::anyhow!("No end index for DOB"))?;
|
||||
let dob = received[dob_start..dob_end].to_vec();
|
||||
let blinder = received_secret.blinder.as_bytes().to_vec();
|
||||
let committed_hash = hash.value.as_bytes().to_vec();
|
||||
let proof_date = Local::now().date_naive();
|
||||
|
||||
assert_eq!(received_secret.direction, Direction::Received);
|
||||
assert_eq!(received_secret.alg, HashAlgId::SHA256);
|
||||
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(&dob);
|
||||
hasher.update(&blinder);
|
||||
let computed_hash = hasher.finalize();
|
||||
|
||||
if committed_hash != computed_hash.as_ref() as &[u8] {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Computed hash does not match committed hash"
|
||||
));
|
||||
}
|
||||
|
||||
Ok(ZKProofInput {
|
||||
dob,
|
||||
proof_date,
|
||||
committed_hash,
|
||||
blinder,
|
||||
})
|
||||
}
|
||||
|
||||
fn generate_zk_proof(proof_input: &ZKProofInput) -> Result<ZKProofBundle> {
|
||||
tracing::info!("🔒 Generating ZK proof with Noir...");
|
||||
|
||||
const PROGRAM_JSON: &str = include_str!("./noir/target/noir.json");
|
||||
|
||||
// 1. Load bytecode from program.json
|
||||
let json: Value = serde_json::from_str(PROGRAM_JSON)?;
|
||||
let bytecode = json["bytecode"]
|
||||
.as_str()
|
||||
.ok_or_else(|| anyhow::anyhow!("bytecode field not found in program.json"))?;
|
||||
|
||||
let mut inputs: Vec<String> = vec![];
|
||||
inputs.push(proof_input.proof_date.day().to_string());
|
||||
inputs.push(proof_input.proof_date.month().to_string());
|
||||
inputs.push(proof_input.proof_date.year().to_string());
|
||||
inputs.extend(proof_input.committed_hash.iter().map(|b| b.to_string()));
|
||||
inputs.extend(proof_input.dob.iter().map(|b| b.to_string()));
|
||||
inputs.extend(proof_input.blinder.iter().map(|b| b.to_string()));
|
||||
|
||||
let proof_date = proof_input.proof_date.to_string();
|
||||
tracing::info!(
|
||||
"Public inputs : Proof date ({}) and committed hash ({})",
|
||||
proof_date,
|
||||
hex::encode(&proof_input.committed_hash)
|
||||
);
|
||||
tracing::info!(
|
||||
"Private inputs: Blinder ({}) and Date of Birth ({})",
|
||||
hex::encode(&proof_input.blinder),
|
||||
String::from_utf8_lossy(&proof_input.dob)
|
||||
);
|
||||
|
||||
tracing::debug!("Witness inputs {:?}", inputs);
|
||||
|
||||
let input_refs: Vec<&str> = inputs.iter().map(String::as_str).collect();
|
||||
let witness = from_vec_str_to_witness_map(input_refs).map_err(|e| anyhow::anyhow!(e))?;
|
||||
|
||||
// Setup SRS
|
||||
setup_srs_from_bytecode(bytecode, None, false).map_err(|e| anyhow::anyhow!(e))?;
|
||||
|
||||
// Verification key
|
||||
let vk = get_ultra_honk_verification_key(bytecode, false).map_err(|e| anyhow::anyhow!(e))?;
|
||||
|
||||
// Generate proof
|
||||
let proof = prove_ultra_honk(bytecode, witness.clone(), vk.clone(), false)
|
||||
.map_err(|e| anyhow::anyhow!(e))?;
|
||||
tracing::info!("✅ Proof generated ({} bytes)", proof.len());
|
||||
|
||||
let proof_bundle = ZKProofBundle { vk, proof };
|
||||
Ok(proof_bundle)
|
||||
}
|
||||
21
crates/examples/interactive_zk/types.rs
Normal file
21
crates/examples/interactive_zk/types.rs
Normal file
@@ -0,0 +1,21 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tlsn::transcript::{hash::PlaintextHash, Direction, TranscriptCommitment};
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct ZKProofBundle {
|
||||
pub vk: Vec<u8>,
|
||||
pub proof: Vec<u8>,
|
||||
}
|
||||
|
||||
// extract commitment from prover output
|
||||
pub fn received_commitments(
|
||||
transcript_commitments: &[TranscriptCommitment],
|
||||
) -> Vec<&PlaintextHash> {
|
||||
transcript_commitments
|
||||
.iter()
|
||||
.filter_map(|commitment| match commitment {
|
||||
TranscriptCommitment::Hash(hash) if hash.direction == Direction::Received => Some(hash),
|
||||
_ => None,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
219
crates/examples/interactive_zk/verifier.rs
Normal file
219
crates/examples/interactive_zk/verifier.rs
Normal file
@@ -0,0 +1,219 @@
|
||||
use crate::types::received_commitments;
|
||||
|
||||
use super::types::ZKProofBundle;
|
||||
use anyhow::Result;
|
||||
use chrono::{Local, NaiveDate};
|
||||
use noir::barretenberg::verify::{get_ultra_honk_verification_key, verify_ultra_honk};
|
||||
use serde_json::Value;
|
||||
use tls_server_fixture::CA_CERT_DER;
|
||||
use tlsn::{
|
||||
config::{tls_commit::TlsCommitProtocolConfig, verifier::VerifierConfig},
|
||||
connection::ServerName,
|
||||
hash::HashAlgId,
|
||||
transcript::{Direction, PartialTranscript},
|
||||
verifier::{Verifier, VerifierOutput},
|
||||
webpki::{CertificateDer, RootCertStore},
|
||||
};
|
||||
use tlsn_examples::{MAX_RECV_DATA, MAX_SENT_DATA};
|
||||
use tlsn_server_fixture_certs::SERVER_DOMAIN;
|
||||
use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite};
|
||||
use tokio_util::compat::TokioAsyncReadCompatExt;
|
||||
use tracing::instrument;
|
||||
|
||||
#[instrument(skip(socket, extra_socket))]
|
||||
pub async fn verifier<T: AsyncWrite + AsyncRead + Send + Sync + Unpin + 'static>(
|
||||
socket: T,
|
||||
mut extra_socket: T,
|
||||
) -> Result<PartialTranscript> {
|
||||
let verifier = Verifier::new(
|
||||
VerifierConfig::builder()
|
||||
// Create a root certificate store with the server-fixture's self-signed
|
||||
// certificate. This is only required for offline testing with the
|
||||
// server-fixture.
|
||||
.root_store(RootCertStore {
|
||||
roots: vec![CertificateDer(CA_CERT_DER.to_vec())],
|
||||
})
|
||||
.build()?,
|
||||
);
|
||||
|
||||
// Validate the proposed configuration and then run the TLS commitment protocol.
|
||||
let verifier = verifier.commit(socket.compat()).await?;
|
||||
|
||||
// This is the opportunity to ensure the prover does not attempt to overload the
|
||||
// verifier.
|
||||
let reject = if let TlsCommitProtocolConfig::Mpc(mpc_tls_config) = verifier.request().protocol()
|
||||
{
|
||||
if mpc_tls_config.max_sent_data() > MAX_SENT_DATA {
|
||||
Some("max_sent_data is too large")
|
||||
} else if mpc_tls_config.max_recv_data() > MAX_RECV_DATA {
|
||||
Some("max_recv_data is too large")
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
Some("expecting to use MPC-TLS")
|
||||
};
|
||||
|
||||
if reject.is_some() {
|
||||
verifier.reject(reject).await?;
|
||||
return Err(anyhow::anyhow!("protocol configuration rejected"));
|
||||
}
|
||||
|
||||
// Runs the TLS commitment protocol to completion.
|
||||
let verifier = verifier.accept().await?.run().await?;
|
||||
|
||||
// Validate the proving request and then verify.
|
||||
let verifier = verifier.verify().await?;
|
||||
let request = verifier.request();
|
||||
|
||||
if !request.server_identity() || request.reveal().is_none() {
|
||||
let verifier = verifier
|
||||
.reject(Some(
|
||||
"expecting to verify the server name and transcript data",
|
||||
))
|
||||
.await?;
|
||||
verifier.close().await?;
|
||||
return Err(anyhow::anyhow!(
|
||||
"prover did not reveal the server name and transcript data"
|
||||
));
|
||||
}
|
||||
|
||||
let (
|
||||
VerifierOutput {
|
||||
server_name,
|
||||
transcript,
|
||||
transcript_commitments,
|
||||
..
|
||||
},
|
||||
verifier,
|
||||
) = verifier.accept().await?;
|
||||
|
||||
verifier.close().await?;
|
||||
|
||||
let server_name = server_name.expect("server name should be present");
|
||||
let transcript = transcript.expect("transcript should be present");
|
||||
|
||||
// Create hash commitment for the date of birth field from the response
|
||||
let sent = transcript.sent_unsafe().to_vec();
|
||||
let sent_data = String::from_utf8(sent.clone())
|
||||
.map_err(|e| anyhow::anyhow!("Verifier expected valid UTF-8 sent data: {e}"))?;
|
||||
|
||||
if !sent_data.contains(SERVER_DOMAIN) {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Verification failed: Expected host {SERVER_DOMAIN} not found in sent data"
|
||||
));
|
||||
}
|
||||
|
||||
// Check received data.
|
||||
let received_commitments = received_commitments(&transcript_commitments);
|
||||
let received_commitment = received_commitments
|
||||
.first()
|
||||
.ok_or_else(|| anyhow::anyhow!("Missing hash commitment"))?;
|
||||
|
||||
assert!(received_commitment.direction == Direction::Received);
|
||||
assert!(received_commitment.hash.alg == HashAlgId::SHA256);
|
||||
|
||||
let committed_hash = &received_commitment.hash;
|
||||
|
||||
// Check Session info: server name.
|
||||
let ServerName::Dns(server_name) = server_name;
|
||||
if server_name.as_str() != SERVER_DOMAIN {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Server name mismatch: expected {SERVER_DOMAIN}, got {}",
|
||||
server_name.as_str()
|
||||
));
|
||||
}
|
||||
|
||||
// Receive ZKProof information from prover
|
||||
let mut buf = Vec::new();
|
||||
extra_socket.read_to_end(&mut buf).await?;
|
||||
|
||||
if buf.is_empty() {
|
||||
return Err(anyhow::anyhow!("No ZK proof data received from prover"));
|
||||
}
|
||||
|
||||
let msg: ZKProofBundle = bincode::deserialize(&buf)
|
||||
.map_err(|e| anyhow::anyhow!("Failed to deserialize ZK proof bundle: {e}"))?;
|
||||
|
||||
// Verify zk proof
|
||||
const PROGRAM_JSON: &str = include_str!("./noir/target/noir.json");
|
||||
let json: Value = serde_json::from_str(PROGRAM_JSON)
|
||||
.map_err(|e| anyhow::anyhow!("Failed to parse Noir circuit: {e}"))?;
|
||||
|
||||
let bytecode = json["bytecode"]
|
||||
.as_str()
|
||||
.ok_or_else(|| anyhow::anyhow!("Bytecode field missing in noir.json"))?;
|
||||
|
||||
let vk = get_ultra_honk_verification_key(bytecode, false)
|
||||
.map_err(|e| anyhow::anyhow!("Failed to get verification key: {e}"))?;
|
||||
|
||||
if vk != msg.vk {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Verification key mismatch between computed and provided by prover"
|
||||
));
|
||||
}
|
||||
|
||||
let proof = msg.proof.clone();
|
||||
|
||||
// Validate proof has enough data.
|
||||
// The proof should start with the public inputs:
|
||||
// * We expect at least 3 * 32 bytes for the three date fields (day, month,
|
||||
// year)
|
||||
// * and 32*32 bytes for the hash
|
||||
let min_bytes = (32 + 3) * 32;
|
||||
if proof.len() < min_bytes {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Proof too short: expected at least {min_bytes} bytes, got {}",
|
||||
proof.len()
|
||||
));
|
||||
}
|
||||
|
||||
// Check that the proof date is correctly included in the proof
|
||||
let proof_date_day: u32 = u32::from_be_bytes(proof[28..32].try_into()?);
|
||||
let proof_date_month: u32 = u32::from_be_bytes(proof[60..64].try_into()?);
|
||||
let proof_date_year: i32 = i32::from_be_bytes(proof[92..96].try_into()?);
|
||||
let proof_date_from_proof =
|
||||
NaiveDate::from_ymd_opt(proof_date_year, proof_date_month, proof_date_day)
|
||||
.ok_or_else(|| anyhow::anyhow!("Invalid proof date in proof"))?;
|
||||
let today = Local::now().date_naive();
|
||||
if (today - proof_date_from_proof).num_days() < 0 {
|
||||
return Err(anyhow::anyhow!(
|
||||
"The proof date can only be today or in the past: provided {proof_date_from_proof}, today {today}"
|
||||
));
|
||||
}
|
||||
|
||||
// Check that the committed hash in the proof matches the hash from the
|
||||
// commitment
|
||||
let committed_hash_in_proof: Vec<u8> = proof
|
||||
.chunks(32)
|
||||
.skip(3) // skip the first 3 chunks
|
||||
.take(32)
|
||||
.map(|chunk| *chunk.last().unwrap_or(&0))
|
||||
.collect();
|
||||
let expected_hash = committed_hash.value.as_bytes().to_vec();
|
||||
if committed_hash_in_proof != expected_hash {
|
||||
tracing::error!(
|
||||
"❌ The hash in the proof does not match the committed hash in MPC-TLS: {} != {}",
|
||||
hex::encode(&committed_hash_in_proof),
|
||||
hex::encode(&expected_hash)
|
||||
);
|
||||
return Err(anyhow::anyhow!(
|
||||
"Hash in proof does not match committed hash in MPC-TLS"
|
||||
));
|
||||
}
|
||||
tracing::info!(
|
||||
"✅ The hash in the proof matches the committed hash in MPC-TLS ({})",
|
||||
hex::encode(&expected_hash)
|
||||
);
|
||||
|
||||
// Finally verify the proof
|
||||
let is_valid = verify_ultra_honk(msg.proof, msg.vk)
|
||||
.map_err(|e| anyhow::anyhow!("ZKProof Verification failed: {e}"))?;
|
||||
if !is_valid {
|
||||
tracing::error!("❌ Age verification ZKProof failed to verify");
|
||||
return Err(anyhow::anyhow!("Age verification ZKProof failed to verify"));
|
||||
}
|
||||
tracing::info!("✅ Age verification ZKProof successfully verified");
|
||||
|
||||
Ok(transcript)
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "tlsn-formats"
|
||||
version = "0.1.0-alpha.13-pre"
|
||||
version = "0.1.0-alpha.14-pre"
|
||||
edition = "2021"
|
||||
|
||||
[lints]
|
||||
|
||||
@@ -3,7 +3,19 @@
|
||||
# Ensure the script runs in the folder that contains this script
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
cargo build --release --package tlsn-harness-runner --package tlsn-harness-executor --package tlsn-server-fixture --package tlsn-harness-plot
|
||||
RUNNER_FEATURES=""
|
||||
EXECUTOR_FEATURES=""
|
||||
|
||||
if [ "$1" = "debug" ]; then
|
||||
RUNNER_FEATURES="--features debug"
|
||||
EXECUTOR_FEATURES="--no-default-features --features debug"
|
||||
fi
|
||||
|
||||
cargo build --release \
|
||||
--package tlsn-harness-runner $RUNNER_FEATURES \
|
||||
--package tlsn-harness-executor $EXECUTOR_FEATURES \
|
||||
--package tlsn-server-fixture \
|
||||
--package tlsn-harness-plot
|
||||
|
||||
mkdir -p bin
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ pub const DEFAULT_UPLOAD_SIZE: usize = 1024;
|
||||
pub const DEFAULT_DOWNLOAD_SIZE: usize = 4096;
|
||||
pub const DEFAULT_DEFER_DECRYPTION: bool = true;
|
||||
pub const DEFAULT_MEMORY_PROFILE: bool = false;
|
||||
pub const DEFAULT_REVEAL_ALL: bool = false;
|
||||
|
||||
pub const WARM_UP_BENCH: Bench = Bench {
|
||||
group: None,
|
||||
@@ -20,6 +21,7 @@ pub const WARM_UP_BENCH: Bench = Bench {
|
||||
download_size: 4096,
|
||||
defer_decryption: true,
|
||||
memory_profile: false,
|
||||
reveal_all: true,
|
||||
};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
@@ -79,6 +81,8 @@ pub struct BenchGroupItem {
|
||||
pub defer_decryption: Option<bool>,
|
||||
#[serde(rename = "memory-profile")]
|
||||
pub memory_profile: Option<bool>,
|
||||
#[serde(rename = "reveal-all")]
|
||||
pub reveal_all: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
@@ -97,6 +101,8 @@ pub struct BenchItem {
|
||||
pub defer_decryption: Option<bool>,
|
||||
#[serde(rename = "memory-profile")]
|
||||
pub memory_profile: Option<bool>,
|
||||
#[serde(rename = "reveal-all")]
|
||||
pub reveal_all: Option<bool>,
|
||||
}
|
||||
|
||||
impl BenchItem {
|
||||
@@ -132,6 +138,10 @@ impl BenchItem {
|
||||
if self.memory_profile.is_none() {
|
||||
self.memory_profile = group.memory_profile;
|
||||
}
|
||||
|
||||
if self.reveal_all.is_none() {
|
||||
self.reveal_all = group.reveal_all;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_bench(&self) -> Bench {
|
||||
@@ -145,6 +155,7 @@ impl BenchItem {
|
||||
download_size: self.download_size.unwrap_or(DEFAULT_DOWNLOAD_SIZE),
|
||||
defer_decryption: self.defer_decryption.unwrap_or(DEFAULT_DEFER_DECRYPTION),
|
||||
memory_profile: self.memory_profile.unwrap_or(DEFAULT_MEMORY_PROFILE),
|
||||
reveal_all: self.reveal_all.unwrap_or(DEFAULT_REVEAL_ALL),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -164,6 +175,8 @@ pub struct Bench {
|
||||
pub defer_decryption: bool,
|
||||
#[serde(rename = "memory-profile")]
|
||||
pub memory_profile: bool,
|
||||
#[serde(rename = "reveal-all")]
|
||||
pub reveal_all: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
|
||||
@@ -26,7 +26,7 @@ pub enum Id {
|
||||
One,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
|
||||
pub enum IoMode {
|
||||
Client,
|
||||
Server,
|
||||
|
||||
@@ -22,7 +22,10 @@ pub enum CmdOutput {
|
||||
GetTests(Vec<String>),
|
||||
Test(TestOutput),
|
||||
Bench(BenchOutput),
|
||||
Fail { reason: Option<String> },
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
Fail {
|
||||
reason: Option<String>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
|
||||
@@ -7,12 +7,12 @@ docker build --pull -t tlsn-bench . -f ./crates/harness/harness.Dockerfile
|
||||
|
||||
Next run the benches with:
|
||||
```
|
||||
docker run -it --privileged -v ./crates/harness/:/benches tlsn-bench bash -c "runner setup; runner bench"
|
||||
docker run -it --privileged -v $(pwd)/crates/harness/:/benches tlsn-bench bash -c "runner setup; runner bench"
|
||||
```
|
||||
The `--privileged` parameter is required because this test bench needs permission to create networks with certain parameters
|
||||
|
||||
To run the benches in a browser run:
|
||||
```
|
||||
docker run -it --privileged -v ./crates/harness/:/benches tlsn-bench bash -c "cd /; runner setup; runner --target browser bench"
|
||||
docker run -it --privileged -v $(pwd)/crates/harness/:/benches tlsn-bench bash -c "runner setup; runner --target browser bench"
|
||||
```
|
||||
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
[target.wasm32-unknown-unknown]
|
||||
rustflags = [
|
||||
"-C",
|
||||
"target-feature=+atomics,+bulk-memory,+mutable-globals,+simd128",
|
||||
"-C",
|
||||
"-Ctarget-feature=+atomics,+bulk-memory,+mutable-globals,+simd128",
|
||||
"-Clink-arg=--shared-memory",
|
||||
# 4GB
|
||||
"link-arg=--max-memory=4294967296",
|
||||
"-Clink-arg=--max-memory=4294967296",
|
||||
"-Clink-arg=--import-memory",
|
||||
"-Clink-arg=--export=__wasm_init_tls",
|
||||
"-Clink-arg=--export=__tls_size",
|
||||
"-Clink-arg=--export=__tls_align",
|
||||
"-Clink-arg=--export=__tls_base",
|
||||
"--cfg",
|
||||
'getrandom_backend="wasm_js"',
|
||||
]
|
||||
|
||||
@@ -4,18 +4,19 @@ version = "0.1.0"
|
||||
edition = "2024"
|
||||
publish = false
|
||||
|
||||
[features]
|
||||
# Disable tracing events as a workaround for issue 959.
|
||||
default = ["tracing/release_max_level_off"]
|
||||
# Used to debug the executor itself.
|
||||
debug = []
|
||||
|
||||
[lib]
|
||||
name = "harness_executor"
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[package.metadata.wasm-pack.profile.custom]
|
||||
wasm-opt = ["-O3"]
|
||||
|
||||
[dependencies]
|
||||
tlsn-harness-core = { workspace = true }
|
||||
tlsn = { workspace = true }
|
||||
tlsn-core = { workspace = true }
|
||||
tlsn-tls-core = { workspace = true }
|
||||
tlsn-server-fixture-certs = { workspace = true }
|
||||
|
||||
inventory = { workspace = true }
|
||||
@@ -33,6 +34,7 @@ tokio = { workspace = true, features = ["full"] }
|
||||
tokio-util = { workspace = true, features = ["compat"] }
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
tracing = { workspace = true }
|
||||
wasm-bindgen = { workspace = true }
|
||||
tlsn-wasm = { workspace = true }
|
||||
js-sys = { workspace = true }
|
||||
|
||||
@@ -5,8 +5,15 @@ use futures::{AsyncReadExt, AsyncWriteExt, TryFutureExt};
|
||||
|
||||
use harness_core::bench::{Bench, ProverMetrics};
|
||||
use tlsn::{
|
||||
config::ProtocolConfig,
|
||||
prover::{ProveConfig, Prover, ProverConfig, TlsConfig},
|
||||
config::{
|
||||
prove::ProveConfig,
|
||||
prover::ProverConfig,
|
||||
tls::TlsClientConfig,
|
||||
tls_commit::{TlsCommitConfig, mpc::MpcTlsConfig},
|
||||
},
|
||||
connection::ServerName,
|
||||
prover::Prover,
|
||||
webpki::{CertificateDer, RootCertStore},
|
||||
};
|
||||
use tlsn_server_fixture_certs::{CA_CERT_DER, SERVER_DOMAIN};
|
||||
|
||||
@@ -21,44 +28,47 @@ pub async fn bench_prover(provider: &IoProvider, config: &Bench) -> Result<Prove
|
||||
let sent = verifier_io.sent();
|
||||
let recv = verifier_io.recv();
|
||||
|
||||
let mut builder = ProtocolConfig::builder();
|
||||
builder.max_sent_data(config.upload_size);
|
||||
|
||||
builder.defer_decryption_from_start(config.defer_decryption);
|
||||
if !config.defer_decryption {
|
||||
builder.max_recv_data_online(config.download_size + RECV_PADDING);
|
||||
}
|
||||
builder.max_recv_data(config.download_size + RECV_PADDING);
|
||||
|
||||
let protocol_config = builder.build()?;
|
||||
|
||||
let mut root_store = tls_core::anchors::RootCertStore::empty();
|
||||
root_store
|
||||
.add(&tls_core::key::Certificate(CA_CERT_DER.to_vec()))
|
||||
.unwrap();
|
||||
|
||||
let mut tls_config_builder = TlsConfig::builder();
|
||||
tls_config_builder.root_store(root_store);
|
||||
let tls_config = tls_config_builder.build()?;
|
||||
|
||||
let prover = Prover::new(
|
||||
ProverConfig::builder()
|
||||
.tls_config(tls_config)
|
||||
.protocol_config(protocol_config)
|
||||
.server_name(SERVER_DOMAIN)
|
||||
.build()?,
|
||||
);
|
||||
let prover = Prover::new(ProverConfig::builder().build()?);
|
||||
|
||||
let time_start = web_time::Instant::now();
|
||||
|
||||
let prover = prover.setup(verifier_io).await?;
|
||||
let prover = prover
|
||||
.commit(
|
||||
TlsCommitConfig::builder()
|
||||
.protocol({
|
||||
let mut builder = MpcTlsConfig::builder()
|
||||
.max_sent_data(config.upload_size)
|
||||
.defer_decryption_from_start(config.defer_decryption);
|
||||
|
||||
if !config.defer_decryption {
|
||||
builder = builder.max_recv_data_online(config.download_size + RECV_PADDING);
|
||||
}
|
||||
|
||||
builder
|
||||
.max_recv_data(config.download_size + RECV_PADDING)
|
||||
.build()
|
||||
}?)
|
||||
.build()?,
|
||||
verifier_io,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let time_preprocess = time_start.elapsed().as_millis();
|
||||
let time_start_online = web_time::Instant::now();
|
||||
let uploaded_preprocess = sent.load(Ordering::Relaxed);
|
||||
let downloaded_preprocess = recv.load(Ordering::Relaxed);
|
||||
|
||||
let (mut conn, prover_fut) = prover.connect(provider.provide_server_io().await?).await?;
|
||||
let (mut conn, prover_fut) = prover
|
||||
.connect(
|
||||
TlsClientConfig::builder()
|
||||
.server_name(ServerName::Dns(SERVER_DOMAIN.try_into()?))
|
||||
.root_store(RootCertStore {
|
||||
roots: vec![CertificateDer(CA_CERT_DER.to_vec())],
|
||||
})
|
||||
.build()?,
|
||||
provider.provide_server_io().await?,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let (_, mut prover) = futures::try_join!(
|
||||
async {
|
||||
@@ -88,14 +98,27 @@ pub async fn bench_prover(provider: &IoProvider, config: &Bench) -> Result<Prove
|
||||
|
||||
let mut builder = ProveConfig::builder(prover.transcript());
|
||||
|
||||
// When reveal_all is false (the default), we exclude 1 byte to avoid the
|
||||
// reveal-all optimization and benchmark the realistic ZK authentication path.
|
||||
let reveal_sent_range = if config.reveal_all {
|
||||
0..sent_len
|
||||
} else {
|
||||
0..sent_len.saturating_sub(1)
|
||||
};
|
||||
let reveal_recv_range = if config.reveal_all {
|
||||
0..recv_len
|
||||
} else {
|
||||
0..recv_len.saturating_sub(1)
|
||||
};
|
||||
|
||||
builder
|
||||
.server_identity()
|
||||
.reveal_sent(&(0..sent_len))?
|
||||
.reveal_recv(&(0..recv_len))?;
|
||||
.reveal_sent(&reveal_sent_range)?
|
||||
.reveal_recv(&reveal_recv_range)?;
|
||||
|
||||
let config = builder.build()?;
|
||||
let prove_config = builder.build()?;
|
||||
|
||||
prover.prove(&config).await?;
|
||||
prover.prove(&prove_config).await?;
|
||||
prover.close().await?;
|
||||
|
||||
let time_total = time_start.elapsed().as_millis();
|
||||
|
||||
@@ -2,36 +2,31 @@ use anyhow::Result;
|
||||
|
||||
use harness_core::bench::Bench;
|
||||
use tlsn::{
|
||||
config::ProtocolConfigValidator,
|
||||
verifier::{Verifier, VerifierConfig, VerifyConfig},
|
||||
config::verifier::VerifierConfig,
|
||||
verifier::Verifier,
|
||||
webpki::{CertificateDer, RootCertStore},
|
||||
};
|
||||
use tlsn_server_fixture_certs::CA_CERT_DER;
|
||||
|
||||
use crate::{IoProvider, bench::RECV_PADDING};
|
||||
|
||||
pub async fn bench_verifier(provider: &IoProvider, config: &Bench) -> Result<()> {
|
||||
let mut builder = ProtocolConfigValidator::builder();
|
||||
builder
|
||||
.max_sent_data(config.upload_size)
|
||||
.max_recv_data(config.download_size + RECV_PADDING);
|
||||
|
||||
let protocol_config = builder.build()?;
|
||||
|
||||
let mut root_store = tls_core::anchors::RootCertStore::empty();
|
||||
root_store
|
||||
.add(&tls_core::key::Certificate(CA_CERT_DER.to_vec()))
|
||||
.unwrap();
|
||||
use crate::IoProvider;
|
||||
|
||||
pub async fn bench_verifier(provider: &IoProvider, _config: &Bench) -> Result<()> {
|
||||
let verifier = Verifier::new(
|
||||
VerifierConfig::builder()
|
||||
.root_store(root_store)
|
||||
.protocol_config_validator(protocol_config)
|
||||
.root_store(RootCertStore {
|
||||
roots: vec![CertificateDer(CA_CERT_DER.to_vec())],
|
||||
})
|
||||
.build()?,
|
||||
);
|
||||
|
||||
let verifier = verifier.setup(provider.provide_proto_io().await?).await?;
|
||||
let mut verifier = verifier.run().await?;
|
||||
verifier.verify(&VerifyConfig::default()).await?;
|
||||
let verifier = verifier
|
||||
.commit(provider.provide_proto_io().await?)
|
||||
.await?
|
||||
.accept()
|
||||
.await?
|
||||
.run()
|
||||
.await?;
|
||||
let (_, verifier) = verifier.verify().await?.accept().await?;
|
||||
verifier.close().await?;
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -81,7 +81,11 @@ mod native {
|
||||
mod wasm {
|
||||
use super::IoProvider;
|
||||
use crate::io::Io;
|
||||
use anyhow::Result;
|
||||
use anyhow::{Result, anyhow};
|
||||
use std::time::Duration;
|
||||
|
||||
const CHECK_WS_OPEN_DELAY_MS: usize = 50;
|
||||
const MAX_RETRIES: usize = 50;
|
||||
|
||||
impl IoProvider {
|
||||
/// Provides a connection to the server.
|
||||
@@ -107,7 +111,27 @@ mod wasm {
|
||||
&self.config.proto_1.0,
|
||||
self.config.proto_1.1,
|
||||
);
|
||||
let (_, io) = ws_stream_wasm::WsMeta::connect(url, None).await?;
|
||||
let mut retries = 0;
|
||||
|
||||
let io = loop {
|
||||
// Connect to the websocket relay.
|
||||
let (_, io) = ws_stream_wasm::WsMeta::connect(url.clone(), None).await?;
|
||||
|
||||
// Allow some time for the relay to initiate a connection to
|
||||
// the verifier.
|
||||
std::thread::sleep(Duration::from_millis(CHECK_WS_OPEN_DELAY_MS as u64));
|
||||
|
||||
// If the relay didn't close the io, most likely the verifier
|
||||
// accepted the connection.
|
||||
if io.ready_state() == ws_stream_wasm::WsState::Open {
|
||||
break io;
|
||||
}
|
||||
|
||||
retries += 1;
|
||||
if retries > MAX_RETRIES {
|
||||
return Err(anyhow!("verifier did not accept connection"));
|
||||
}
|
||||
};
|
||||
|
||||
Ok(io.into_io())
|
||||
}
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
use tls_core::anchors::RootCertStore;
|
||||
use tlsn::{
|
||||
config::{ProtocolConfig, ProtocolConfigValidator},
|
||||
config::{
|
||||
prove::ProveConfig,
|
||||
prover::ProverConfig,
|
||||
tls::TlsClientConfig,
|
||||
tls_commit::{TlsCommitConfig, mpc::MpcTlsConfig},
|
||||
verifier::VerifierConfig,
|
||||
},
|
||||
connection::ServerName,
|
||||
hash::HashAlgId,
|
||||
prover::{ProveConfig, Prover, ProverConfig, TlsConfig},
|
||||
prover::Prover,
|
||||
transcript::{TranscriptCommitConfig, TranscriptCommitment, TranscriptCommitmentKind},
|
||||
verifier::{Verifier, VerifierConfig, VerifierOutput, VerifyConfig},
|
||||
verifier::{Verifier, VerifierOutput},
|
||||
webpki::{CertificateDer, RootCertStore},
|
||||
};
|
||||
use tlsn_server_fixture_certs::{CA_CERT_DER, SERVER_DOMAIN};
|
||||
|
||||
@@ -21,37 +28,35 @@ const MAX_RECV_DATA: usize = 1 << 11;
|
||||
crate::test!("basic", prover, verifier);
|
||||
|
||||
async fn prover(provider: &IoProvider) {
|
||||
let mut root_store = RootCertStore::empty();
|
||||
root_store
|
||||
.add(&tls_core::key::Certificate(CA_CERT_DER.to_vec()))
|
||||
let prover = Prover::new(ProverConfig::builder().build().unwrap())
|
||||
.commit(
|
||||
TlsCommitConfig::builder()
|
||||
.protocol(
|
||||
MpcTlsConfig::builder()
|
||||
.max_sent_data(MAX_SENT_DATA)
|
||||
.max_recv_data(MAX_RECV_DATA)
|
||||
.defer_decryption_from_start(true)
|
||||
.build()
|
||||
.unwrap(),
|
||||
)
|
||||
.build()
|
||||
.unwrap(),
|
||||
provider.provide_proto_io().await.unwrap(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let mut tls_config_builder = TlsConfig::builder();
|
||||
tls_config_builder.root_store(root_store);
|
||||
|
||||
let tls_config = tls_config_builder.build().unwrap();
|
||||
|
||||
let prover = Prover::new(
|
||||
ProverConfig::builder()
|
||||
.server_name(SERVER_DOMAIN)
|
||||
.tls_config(tls_config)
|
||||
.protocol_config(
|
||||
ProtocolConfig::builder()
|
||||
.max_sent_data(MAX_SENT_DATA)
|
||||
.max_recv_data(MAX_RECV_DATA)
|
||||
.defer_decryption_from_start(true)
|
||||
.build()
|
||||
.unwrap(),
|
||||
)
|
||||
.build()
|
||||
.unwrap(),
|
||||
)
|
||||
.setup(provider.provide_proto_io().await.unwrap())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let (tls_connection, prover_fut) = prover
|
||||
.connect(provider.provide_server_io().await.unwrap())
|
||||
.connect(
|
||||
TlsClientConfig::builder()
|
||||
.server_name(ServerName::Dns(SERVER_DOMAIN.try_into().unwrap()))
|
||||
.root_store(RootCertStore {
|
||||
roots: vec![CertificateDer(CA_CERT_DER.to_vec())],
|
||||
})
|
||||
.build()
|
||||
.unwrap(),
|
||||
provider.provide_server_io().await.unwrap(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@@ -114,38 +119,38 @@ async fn prover(provider: &IoProvider) {
|
||||
}
|
||||
|
||||
async fn verifier(provider: &IoProvider) {
|
||||
let mut root_store = RootCertStore::empty();
|
||||
root_store
|
||||
.add(&tls_core::key::Certificate(CA_CERT_DER.to_vec()))
|
||||
.unwrap();
|
||||
|
||||
let config = VerifierConfig::builder()
|
||||
.protocol_config_validator(
|
||||
ProtocolConfigValidator::builder()
|
||||
.max_sent_data(MAX_SENT_DATA)
|
||||
.max_recv_data(MAX_RECV_DATA)
|
||||
.build()
|
||||
.unwrap(),
|
||||
)
|
||||
.root_store(root_store)
|
||||
.root_store(RootCertStore {
|
||||
roots: vec![CertificateDer(CA_CERT_DER.to_vec())],
|
||||
})
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let verifier = Verifier::new(config);
|
||||
|
||||
let VerifierOutput {
|
||||
server_name,
|
||||
transcript_commitments,
|
||||
..
|
||||
} = verifier
|
||||
.verify(
|
||||
provider.provide_proto_io().await.unwrap(),
|
||||
&VerifyConfig::default(),
|
||||
)
|
||||
let verifier = Verifier::new(config)
|
||||
.commit(provider.provide_proto_io().await.unwrap())
|
||||
.await
|
||||
.unwrap()
|
||||
.accept()
|
||||
.await
|
||||
.unwrap()
|
||||
.run()
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(server_name.unwrap().as_str(), SERVER_DOMAIN);
|
||||
let (
|
||||
VerifierOutput {
|
||||
server_name,
|
||||
transcript_commitments,
|
||||
..
|
||||
},
|
||||
verifier,
|
||||
) = verifier.verify().await.unwrap().accept().await.unwrap();
|
||||
|
||||
verifier.close().await.unwrap();
|
||||
|
||||
let ServerName::Dns(server_name) = server_name.unwrap();
|
||||
|
||||
assert_eq!(server_name.as_str(), SERVER_DOMAIN);
|
||||
assert!(
|
||||
transcript_commitments
|
||||
.iter()
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
FROM rust AS builder
|
||||
WORKDIR /usr/src/tlsn
|
||||
|
||||
ARG DEBUG=0
|
||||
|
||||
RUN \
|
||||
rustup update; \
|
||||
apt update && apt install -y clang; \
|
||||
@@ -10,7 +12,12 @@ RUN \
|
||||
COPY . .
|
||||
RUN \
|
||||
cd crates/harness; \
|
||||
./build.sh;
|
||||
# Pass `--build-arg DEBUG=1` to `docker build` if you need to debug the harness.
|
||||
if [ "$DEBUG" = "1" ]; then \
|
||||
./build.sh debug; \
|
||||
else \
|
||||
./build.sh; \
|
||||
fi
|
||||
|
||||
FROM debian:latest
|
||||
|
||||
|
||||
@@ -7,10 +7,9 @@ publish = false
|
||||
[dependencies]
|
||||
tlsn-harness-core = { workspace = true }
|
||||
# tlsn-server-fixture = { workspace = true }
|
||||
charming = { version = "0.5.1", features = ["ssr"] }
|
||||
csv = "1.3.0"
|
||||
charming = { version = "0.6.0", features = ["ssr"] }
|
||||
clap = { workspace = true, features = ["derive", "env"] }
|
||||
itertools = "0.14.0"
|
||||
polars = { version = "0.44", features = ["csv", "lazy"] }
|
||||
toml = { workspace = true }
|
||||
|
||||
|
||||
|
||||
111
crates/harness/plot/README.md
Normal file
111
crates/harness/plot/README.md
Normal file
@@ -0,0 +1,111 @@
|
||||
# TLSNotary Benchmark Plot Tool
|
||||
|
||||
Generates interactive HTML and SVG plots from TLSNotary benchmark results. Supports comparing multiple benchmark runs (e.g., before/after optimization, native vs browser).
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
tlsn-harness-plot <TOML> <CSV>... [OPTIONS]
|
||||
```
|
||||
|
||||
### Arguments
|
||||
|
||||
- `<TOML>` - Path to Bench.toml file defining benchmark structure
|
||||
- `<CSV>...` - One or more CSV files with benchmark results
|
||||
|
||||
### Options
|
||||
|
||||
- `-l, --labels <LABEL>...` - Labels for each dataset (optional)
|
||||
- If omitted, datasets are labeled "Dataset 1", "Dataset 2", etc.
|
||||
- Number of labels must match number of CSV files
|
||||
- `--min-max-band` - Add min/max bands to plots showing variance
|
||||
- `-h, --help` - Print help information
|
||||
|
||||
## Examples
|
||||
|
||||
### Single Dataset
|
||||
|
||||
```bash
|
||||
tlsn-harness-plot bench.toml results.csv
|
||||
```
|
||||
|
||||
Generates plots from a single benchmark run.
|
||||
|
||||
### Compare Two Runs
|
||||
|
||||
```bash
|
||||
tlsn-harness-plot bench.toml before.csv after.csv \
|
||||
--labels "Before Optimization" "After Optimization"
|
||||
```
|
||||
|
||||
Overlays two datasets to compare performance improvements.
|
||||
|
||||
### Multiple Datasets
|
||||
|
||||
```bash
|
||||
tlsn-harness-plot bench.toml native.csv browser.csv wasm.csv \
|
||||
--labels "Native" "Browser" "WASM"
|
||||
```
|
||||
|
||||
Compare three different runtime environments.
|
||||
|
||||
### With Min/Max Bands
|
||||
|
||||
```bash
|
||||
tlsn-harness-plot bench.toml run1.csv run2.csv \
|
||||
--labels "Config A" "Config B" \
|
||||
--min-max-band
|
||||
```
|
||||
|
||||
Shows variance ranges for each dataset.
|
||||
|
||||
## Output Files
|
||||
|
||||
The tool generates two files per benchmark group:
|
||||
|
||||
- `<output>.html` - Interactive HTML chart (zoomable, hoverable)
|
||||
- `<output>.svg` - Static SVG image for documentation
|
||||
|
||||
Default output filenames:
|
||||
- `runtime_vs_bandwidth.{html,svg}` - When `protocol_latency` is defined in group
|
||||
- `runtime_vs_latency.{html,svg}` - When `bandwidth` is defined in group
|
||||
|
||||
## Plot Format
|
||||
|
||||
Each dataset displays:
|
||||
- **Solid line** - Total runtime (preprocessing + online phase)
|
||||
- **Dashed line** - Online phase only
|
||||
- **Shaded area** (optional) - Min/max variance bands
|
||||
|
||||
Different datasets automatically use distinct colors for easy comparison.
|
||||
|
||||
## CSV Format
|
||||
|
||||
Expected columns in each CSV file:
|
||||
- `group` - Benchmark group name (must match TOML)
|
||||
- `bandwidth` - Network bandwidth in Kbps (for bandwidth plots)
|
||||
- `latency` - Network latency in ms (for latency plots)
|
||||
- `time_preprocess` - Preprocessing time in ms
|
||||
- `time_online` - Online phase time in ms
|
||||
- `time_total` - Total runtime in ms
|
||||
|
||||
## TOML Format
|
||||
|
||||
The benchmark TOML file defines groups with either:
|
||||
|
||||
```toml
|
||||
[[group]]
|
||||
name = "my_benchmark"
|
||||
protocol_latency = 50 # Fixed latency for bandwidth plots
|
||||
# OR
|
||||
bandwidth = 10000 # Fixed bandwidth for latency plots
|
||||
```
|
||||
|
||||
All datasets must use the same TOML file to ensure consistent benchmark structure.
|
||||
|
||||
## Tips
|
||||
|
||||
- Use descriptive labels to make plots self-documenting
|
||||
- Keep CSV files from the same benchmark configuration for valid comparisons
|
||||
- Min/max bands are useful for showing stability but can clutter plots with many datasets
|
||||
- Interactive HTML plots support zooming and hovering for detailed values
|
||||
@@ -1,17 +1,18 @@
|
||||
use std::f32;
|
||||
|
||||
use charming::{
|
||||
Chart, HtmlRenderer,
|
||||
Chart, HtmlRenderer, ImageRenderer,
|
||||
component::{Axis, Legend, Title},
|
||||
element::{AreaStyle, LineStyle, NameLocation, Orient, TextStyle, Tooltip, Trigger},
|
||||
element::{
|
||||
AreaStyle, ItemStyle, LineStyle, LineStyleType, NameLocation, Orient, TextStyle, Tooltip,
|
||||
Trigger,
|
||||
},
|
||||
series::Line,
|
||||
theme::Theme,
|
||||
};
|
||||
use clap::Parser;
|
||||
use harness_core::bench::{BenchItems, Measurement};
|
||||
use itertools::Itertools;
|
||||
|
||||
const THEME: Theme = Theme::Default;
|
||||
use harness_core::bench::BenchItems;
|
||||
use polars::prelude::*;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(author, version, about)]
|
||||
@@ -19,72 +20,131 @@ struct Cli {
|
||||
/// Path to the Bench.toml file with benchmark spec
|
||||
toml: String,
|
||||
|
||||
/// Path to the CSV file with benchmark results
|
||||
csv: String,
|
||||
/// Paths to CSV files with benchmark results (one or more)
|
||||
csv: Vec<String>,
|
||||
|
||||
/// Prover kind: native or browser
|
||||
#[arg(short, long, value_enum, default_value = "native")]
|
||||
prover_kind: ProverKind,
|
||||
/// Labels for each dataset (optional, defaults to "Dataset 1", "Dataset 2", etc.)
|
||||
#[arg(short, long, num_args = 0..)]
|
||||
labels: Vec<String>,
|
||||
|
||||
/// Add min/max bands to plots
|
||||
#[arg(long, default_value_t = false)]
|
||||
min_max_band: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, clap::ValueEnum)]
|
||||
enum ProverKind {
|
||||
Native,
|
||||
Browser,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for ProverKind {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
ProverKind::Native => write!(f, "Native"),
|
||||
ProverKind::Browser => write!(f, "Browser"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let cli = Cli::parse();
|
||||
|
||||
let mut rdr = csv::Reader::from_path(&cli.csv)?;
|
||||
if cli.csv.is_empty() {
|
||||
return Err("At least one CSV file must be provided".into());
|
||||
}
|
||||
|
||||
// Generate labels if not provided
|
||||
let labels: Vec<String> = if cli.labels.is_empty() {
|
||||
cli.csv
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, _)| format!("Dataset {}", i + 1))
|
||||
.collect()
|
||||
} else if cli.labels.len() != cli.csv.len() {
|
||||
return Err(format!(
|
||||
"Number of labels ({}) must match number of CSV files ({})",
|
||||
cli.labels.len(),
|
||||
cli.csv.len()
|
||||
)
|
||||
.into());
|
||||
} else {
|
||||
cli.labels.clone()
|
||||
};
|
||||
|
||||
// Load all CSVs and add dataset label
|
||||
let mut dfs = Vec::new();
|
||||
for (csv_path, label) in cli.csv.iter().zip(labels.iter()) {
|
||||
let mut df = CsvReadOptions::default()
|
||||
.try_into_reader_with_file_path(Some(csv_path.clone().into()))?
|
||||
.finish()?;
|
||||
|
||||
let label_series = Series::new("dataset_label".into(), vec![label.as_str(); df.height()]);
|
||||
df.with_column(label_series)?;
|
||||
dfs.push(df);
|
||||
}
|
||||
|
||||
// Combine all dataframes
|
||||
let df = dfs
|
||||
.into_iter()
|
||||
.reduce(|acc, df| acc.vstack(&df).unwrap())
|
||||
.unwrap();
|
||||
|
||||
let items: BenchItems = toml::from_str(&std::fs::read_to_string(&cli.toml)?)?;
|
||||
let groups = items.group;
|
||||
|
||||
// Prepare data for plotting.
|
||||
let all_data: Vec<Measurement> = rdr
|
||||
.deserialize::<Measurement>()
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
for group in groups {
|
||||
if group.protocol_latency.is_some() {
|
||||
let latency = group.protocol_latency.unwrap();
|
||||
plot_runtime_vs(
|
||||
&all_data,
|
||||
cli.min_max_band,
|
||||
&group.name,
|
||||
|r| r.bandwidth as f32 / 1000.0, // Kbps to Mbps
|
||||
"Runtime vs Bandwidth",
|
||||
format!("{} ms Latency, {} mode", latency, cli.prover_kind),
|
||||
"runtime_vs_bandwidth.html",
|
||||
"Bandwidth (Mbps)",
|
||||
)?;
|
||||
// Determine which field varies in benches for this group
|
||||
let benches_in_group: Vec<_> = items
|
||||
.bench
|
||||
.iter()
|
||||
.filter(|b| b.group.as_deref() == Some(&group.name))
|
||||
.collect();
|
||||
|
||||
if benches_in_group.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
if group.bandwidth.is_some() {
|
||||
let bandwidth = group.bandwidth.unwrap();
|
||||
// Check which field has varying values
|
||||
let bandwidth_varies = benches_in_group
|
||||
.windows(2)
|
||||
.any(|w| w[0].bandwidth != w[1].bandwidth);
|
||||
let latency_varies = benches_in_group
|
||||
.windows(2)
|
||||
.any(|w| w[0].protocol_latency != w[1].protocol_latency);
|
||||
let download_size_varies = benches_in_group
|
||||
.windows(2)
|
||||
.any(|w| w[0].download_size != w[1].download_size);
|
||||
|
||||
if download_size_varies {
|
||||
let upload_size = group.upload_size.unwrap_or(1024);
|
||||
plot_runtime_vs(
|
||||
&all_data,
|
||||
&df,
|
||||
&labels,
|
||||
cli.min_max_band,
|
||||
&group.name,
|
||||
|r| r.latency as f32,
|
||||
"download_size",
|
||||
1.0 / 1024.0, // bytes to KB
|
||||
"Runtime vs Response Size",
|
||||
format!("{} bytes upload size", upload_size),
|
||||
"runtime_vs_download_size",
|
||||
"Response Size (KB)",
|
||||
true, // legend on left
|
||||
)?;
|
||||
} else if bandwidth_varies {
|
||||
let latency = group.protocol_latency.unwrap_or(50);
|
||||
plot_runtime_vs(
|
||||
&df,
|
||||
&labels,
|
||||
cli.min_max_band,
|
||||
&group.name,
|
||||
"bandwidth",
|
||||
1.0 / 1000.0, // Kbps to Mbps
|
||||
"Runtime vs Bandwidth",
|
||||
format!("{} ms Latency", latency),
|
||||
"runtime_vs_bandwidth",
|
||||
"Bandwidth (Mbps)",
|
||||
false, // legend on right
|
||||
)?;
|
||||
} else if latency_varies {
|
||||
let bandwidth = group.bandwidth.unwrap_or(1000);
|
||||
plot_runtime_vs(
|
||||
&df,
|
||||
&labels,
|
||||
cli.min_max_band,
|
||||
&group.name,
|
||||
"latency",
|
||||
1.0,
|
||||
"Runtime vs Latency",
|
||||
format!("{} bps bandwidth, {} mode", bandwidth, cli.prover_kind),
|
||||
"runtime_vs_latency.html",
|
||||
format!("{} bps bandwidth", bandwidth),
|
||||
"runtime_vs_latency",
|
||||
"Latency (ms)",
|
||||
true, // legend on left
|
||||
)?;
|
||||
}
|
||||
}
|
||||
@@ -92,83 +152,51 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
struct DataPoint {
|
||||
min: f32,
|
||||
mean: f32,
|
||||
max: f32,
|
||||
}
|
||||
|
||||
struct Points {
|
||||
preprocess: DataPoint,
|
||||
online: DataPoint,
|
||||
total: DataPoint,
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn plot_runtime_vs<Fx>(
|
||||
all_data: &[Measurement],
|
||||
fn plot_runtime_vs(
|
||||
df: &DataFrame,
|
||||
labels: &[String],
|
||||
show_min_max: bool,
|
||||
group: &str,
|
||||
x_value: Fx,
|
||||
x_col: &str,
|
||||
x_scale: f32,
|
||||
title: &str,
|
||||
subtitle: String,
|
||||
output_file: &str,
|
||||
x_axis_label: &str,
|
||||
) -> Result<Chart, Box<dyn std::error::Error>>
|
||||
where
|
||||
Fx: Fn(&Measurement) -> f32,
|
||||
{
|
||||
fn data_point(values: &[f32]) -> DataPoint {
|
||||
let mean = values.iter().copied().sum::<f32>() / values.len() as f32;
|
||||
let max = values.iter().copied().reduce(f32::max).unwrap_or_default();
|
||||
let min = values.iter().copied().reduce(f32::min).unwrap_or_default();
|
||||
DataPoint { min, mean, max }
|
||||
}
|
||||
legend_left: bool,
|
||||
) -> Result<Chart, Box<dyn std::error::Error>> {
|
||||
let stats_df = df
|
||||
.clone()
|
||||
.lazy()
|
||||
.filter(col("group").eq(lit(group)))
|
||||
.with_column((col(x_col).cast(DataType::Float32) * lit(x_scale)).alias("x"))
|
||||
.with_columns([
|
||||
(col("time_preprocess").cast(DataType::Float32) / lit(1000.0)).alias("preprocess"),
|
||||
(col("time_online").cast(DataType::Float32) / lit(1000.0)).alias("online"),
|
||||
(col("time_total").cast(DataType::Float32) / lit(1000.0)).alias("total"),
|
||||
])
|
||||
.group_by([col("x"), col("dataset_label")])
|
||||
.agg([
|
||||
col("preprocess").min().alias("preprocess_min"),
|
||||
col("preprocess").mean().alias("preprocess_mean"),
|
||||
col("preprocess").max().alias("preprocess_max"),
|
||||
col("online").min().alias("online_min"),
|
||||
col("online").mean().alias("online_mean"),
|
||||
col("online").max().alias("online_max"),
|
||||
col("total").min().alias("total_min"),
|
||||
col("total").mean().alias("total_mean"),
|
||||
col("total").max().alias("total_max"),
|
||||
])
|
||||
.sort(["dataset_label", "x"], Default::default())
|
||||
.collect()?;
|
||||
|
||||
let stats: Vec<(f32, Points)> = all_data
|
||||
.iter()
|
||||
.filter(|r| r.group.as_deref() == Some(group))
|
||||
.map(|r| {
|
||||
(
|
||||
x_value(r),
|
||||
r.time_preprocess as f32 / 1000.0, // ms to s
|
||||
r.time_online as f32 / 1000.0,
|
||||
r.time_total as f32 / 1000.0,
|
||||
)
|
||||
})
|
||||
.sorted_by(|a, b| a.0.partial_cmp(&b.0).unwrap())
|
||||
.chunk_by(|entry| entry.0)
|
||||
.into_iter()
|
||||
.map(|(x, group)| {
|
||||
let group_vec: Vec<_> = group.collect();
|
||||
let preprocess = data_point(
|
||||
&group_vec
|
||||
.iter()
|
||||
.map(|(_, t, _, _)| *t)
|
||||
.collect::<Vec<f32>>(),
|
||||
);
|
||||
let online = data_point(
|
||||
&group_vec
|
||||
.iter()
|
||||
.map(|(_, _, t, _)| *t)
|
||||
.collect::<Vec<f32>>(),
|
||||
);
|
||||
let total = data_point(
|
||||
&group_vec
|
||||
.iter()
|
||||
.map(|(_, _, _, t)| *t)
|
||||
.collect::<Vec<f32>>(),
|
||||
);
|
||||
(
|
||||
x,
|
||||
Points {
|
||||
preprocess,
|
||||
online,
|
||||
total,
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
// Build legend entries
|
||||
let mut legend_data = Vec::new();
|
||||
for label in labels {
|
||||
legend_data.push(format!("Total Mean ({})", label));
|
||||
legend_data.push(format!("Online Mean ({})", label));
|
||||
}
|
||||
|
||||
let mut chart = Chart::new()
|
||||
.title(
|
||||
@@ -179,14 +207,6 @@ where
|
||||
.subtext_style(TextStyle::new().font_size(16)),
|
||||
)
|
||||
.tooltip(Tooltip::new().trigger(Trigger::Axis))
|
||||
.legend(
|
||||
Legend::new()
|
||||
.data(vec!["Preprocess Mean", "Online Mean", "Total Mean"])
|
||||
.top("80")
|
||||
.right("110")
|
||||
.orient(Orient::Vertical)
|
||||
.item_gap(10),
|
||||
)
|
||||
.x_axis(
|
||||
Axis::new()
|
||||
.name(x_axis_label)
|
||||
@@ -205,73 +225,156 @@ where
|
||||
.name_text_style(TextStyle::new().font_size(21)),
|
||||
);
|
||||
|
||||
chart = add_mean_series(chart, &stats, "Preprocess Mean", |p| p.preprocess.mean);
|
||||
chart = add_mean_series(chart, &stats, "Online Mean", |p| p.online.mean);
|
||||
chart = add_mean_series(chart, &stats, "Total Mean", |p| p.total.mean);
|
||||
// Add legend with conditional positioning
|
||||
let legend = Legend::new()
|
||||
.data(legend_data)
|
||||
.top("80")
|
||||
.orient(Orient::Vertical)
|
||||
.item_gap(10);
|
||||
|
||||
if show_min_max {
|
||||
chart = add_min_max_band(
|
||||
chart,
|
||||
&stats,
|
||||
"Preprocess Min/Max",
|
||||
|p| &p.preprocess,
|
||||
"#ccc",
|
||||
);
|
||||
chart = add_min_max_band(chart, &stats, "Online Min/Max", |p| &p.online, "#ccc");
|
||||
chart = add_min_max_band(chart, &stats, "Total Min/Max", |p| &p.total, "#ccc");
|
||||
let legend = if legend_left {
|
||||
legend.left("110")
|
||||
} else {
|
||||
legend.right("110")
|
||||
};
|
||||
|
||||
chart = chart.legend(legend);
|
||||
|
||||
// Define colors for each dataset
|
||||
let colors = vec![
|
||||
"#5470c6", "#91cc75", "#fac858", "#ee6666", "#73c0de", "#3ba272", "#fc8452", "#9a60b4",
|
||||
];
|
||||
|
||||
for (idx, label) in labels.iter().enumerate() {
|
||||
let color = colors.get(idx % colors.len()).unwrap();
|
||||
|
||||
// Total time - solid line
|
||||
chart = add_dataset_series(
|
||||
&chart,
|
||||
&stats_df,
|
||||
label,
|
||||
&format!("Total Mean ({})", label),
|
||||
"total_mean",
|
||||
false,
|
||||
color,
|
||||
)?;
|
||||
|
||||
// Online time - dashed line (same color as total)
|
||||
chart = add_dataset_series(
|
||||
&chart,
|
||||
&stats_df,
|
||||
label,
|
||||
&format!("Online Mean ({})", label),
|
||||
"online_mean",
|
||||
true,
|
||||
color,
|
||||
)?;
|
||||
|
||||
if show_min_max {
|
||||
chart = add_dataset_min_max_band(
|
||||
&chart,
|
||||
&stats_df,
|
||||
label,
|
||||
&format!("Total Min/Max ({})", label),
|
||||
"total",
|
||||
color,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
// Save the chart as HTML file.
|
||||
// Save the chart as HTML file (no theme)
|
||||
HtmlRenderer::new(title, 1000, 800)
|
||||
.theme(THEME)
|
||||
.save(&chart, output_file)
|
||||
.save(&chart, &format!("{}.html", output_file))
|
||||
.unwrap();
|
||||
|
||||
// Save SVG with default theme
|
||||
ImageRenderer::new(1000, 800)
|
||||
.theme(Theme::Default)
|
||||
.save(&chart, &format!("{}.svg", output_file))
|
||||
.unwrap();
|
||||
|
||||
// Save SVG with dark theme
|
||||
ImageRenderer::new(1000, 800)
|
||||
.theme(Theme::Dark)
|
||||
.save(&chart, &format!("{}_dark.svg", output_file))
|
||||
.unwrap();
|
||||
|
||||
Ok(chart)
|
||||
}
|
||||
|
||||
fn add_mean_series(
|
||||
chart: Chart,
|
||||
stats: &[(f32, Points)],
|
||||
name: &str,
|
||||
extract: impl Fn(&Points) -> f32,
|
||||
) -> Chart {
|
||||
chart.series(
|
||||
Line::new()
|
||||
.name(name)
|
||||
.data(
|
||||
stats
|
||||
.iter()
|
||||
.map(|(x, points)| vec![*x, extract(points)])
|
||||
.collect(),
|
||||
)
|
||||
.symbol_size(6),
|
||||
)
|
||||
fn add_dataset_series(
|
||||
chart: &Chart,
|
||||
df: &DataFrame,
|
||||
dataset_label: &str,
|
||||
series_name: &str,
|
||||
col_name: &str,
|
||||
dashed: bool,
|
||||
color: &str,
|
||||
) -> Result<Chart, Box<dyn std::error::Error>> {
|
||||
// Filter for specific dataset
|
||||
let mask = df.column("dataset_label")?.str()?.equal(dataset_label);
|
||||
let filtered = df.filter(&mask)?;
|
||||
|
||||
let x = filtered.column("x")?.f32()?;
|
||||
let y = filtered.column(col_name)?.f32()?;
|
||||
|
||||
let data: Vec<Vec<f32>> = x
|
||||
.into_iter()
|
||||
.zip(y.into_iter())
|
||||
.filter_map(|(x, y)| Some(vec![x?, y?]))
|
||||
.collect();
|
||||
|
||||
let mut line = Line::new()
|
||||
.name(series_name)
|
||||
.data(data)
|
||||
.symbol_size(6)
|
||||
.item_style(ItemStyle::new().color(color));
|
||||
|
||||
let mut line_style = LineStyle::new();
|
||||
if dashed {
|
||||
line_style = line_style.type_(LineStyleType::Dashed);
|
||||
}
|
||||
line = line.line_style(line_style.color(color));
|
||||
|
||||
Ok(chart.clone().series(line))
|
||||
}
|
||||
|
||||
fn add_min_max_band(
|
||||
chart: Chart,
|
||||
stats: &[(f32, Points)],
|
||||
fn add_dataset_min_max_band(
|
||||
chart: &Chart,
|
||||
df: &DataFrame,
|
||||
dataset_label: &str,
|
||||
name: &str,
|
||||
extract: impl Fn(&Points) -> &DataPoint,
|
||||
col_prefix: &str,
|
||||
color: &str,
|
||||
) -> Chart {
|
||||
chart.series(
|
||||
) -> Result<Chart, Box<dyn std::error::Error>> {
|
||||
// Filter for specific dataset
|
||||
let mask = df.column("dataset_label")?.str()?.equal(dataset_label);
|
||||
let filtered = df.filter(&mask)?;
|
||||
|
||||
let x = filtered.column("x")?.f32()?;
|
||||
let min_col = filtered.column(&format!("{}_min", col_prefix))?.f32()?;
|
||||
let max_col = filtered.column(&format!("{}_max", col_prefix))?.f32()?;
|
||||
|
||||
let max_data: Vec<Vec<f32>> = x
|
||||
.into_iter()
|
||||
.zip(max_col.into_iter())
|
||||
.filter_map(|(x, y)| Some(vec![x?, y?]))
|
||||
.collect();
|
||||
|
||||
let min_data: Vec<Vec<f32>> = x
|
||||
.into_iter()
|
||||
.zip(min_col.into_iter())
|
||||
.filter_map(|(x, y)| Some(vec![x?, y?]))
|
||||
.rev()
|
||||
.collect();
|
||||
|
||||
let data: Vec<Vec<f32>> = max_data.into_iter().chain(min_data).collect();
|
||||
|
||||
Ok(chart.clone().series(
|
||||
Line::new()
|
||||
.name(name)
|
||||
.data(
|
||||
stats
|
||||
.iter()
|
||||
.map(|(x, points)| vec![*x, extract(points).max])
|
||||
.chain(
|
||||
stats
|
||||
.iter()
|
||||
.rev()
|
||||
.map(|(x, points)| vec![*x, extract(points).min]),
|
||||
)
|
||||
.collect(),
|
||||
)
|
||||
.data(data)
|
||||
.show_symbol(false)
|
||||
.line_style(LineStyle::new().opacity(0.0))
|
||||
.area_style(AreaStyle::new().opacity(0.3).color(color)),
|
||||
)
|
||||
))
|
||||
}
|
||||
|
||||
105
crates/harness/plot/data/bandwidth.ipynb
Normal file
105
crates/harness/plot/data/bandwidth.ipynb
Normal file
File diff suppressed because one or more lines are too long
163
crates/harness/plot/data/download.ipynb
Normal file
163
crates/harness/plot/data/download.ipynb
Normal file
File diff suppressed because one or more lines are too long
92
crates/harness/plot/data/latency.ipynb
Normal file
92
crates/harness/plot/data/latency.ipynb
Normal file
File diff suppressed because one or more lines are too long
@@ -7,6 +7,10 @@ publish = false
|
||||
[lib]
|
||||
name = "harness_runner"
|
||||
|
||||
[features]
|
||||
# Used to debug the runner itself.
|
||||
debug = []
|
||||
|
||||
[dependencies]
|
||||
tlsn-harness-core = { workspace = true }
|
||||
tlsn-server-fixture = { workspace = true }
|
||||
|
||||
17
crates/harness/runner/src/debug_prelude.rs
Normal file
17
crates/harness/runner/src/debug_prelude.rs
Normal file
@@ -0,0 +1,17 @@
|
||||
#![allow(unused_imports)]
|
||||
pub use futures::FutureExt;
|
||||
|
||||
pub use tracing::{debug, error};
|
||||
|
||||
pub use chromiumoxide::{
|
||||
Browser, Page,
|
||||
cdp::{
|
||||
browser_protocol::{
|
||||
log::{EventEntryAdded, LogEntryLevel},
|
||||
network::{EnableParams, SetCacheDisabledParams},
|
||||
page::ReloadParams,
|
||||
},
|
||||
js_protocol::runtime::EventExceptionThrown,
|
||||
},
|
||||
handler::HandlerConfig,
|
||||
};
|
||||
@@ -8,6 +8,7 @@ use chromiumoxide::{
|
||||
network::{EnableParams, SetCacheDisabledParams},
|
||||
page::ReloadParams,
|
||||
},
|
||||
handler::HandlerConfig,
|
||||
};
|
||||
use futures::StreamExt;
|
||||
use harness_core::{
|
||||
@@ -20,6 +21,9 @@ use harness_core::{
|
||||
|
||||
use crate::{Target, network::Namespace, rpc::Rpc};
|
||||
|
||||
#[cfg(feature = "debug")]
|
||||
use crate::debug_prelude::*;
|
||||
|
||||
pub struct Executor {
|
||||
ns: Namespace,
|
||||
config: ExecutorConfig,
|
||||
@@ -65,20 +69,34 @@ impl Executor {
|
||||
Id::One => self.config.network().rpc_1,
|
||||
};
|
||||
|
||||
let process = duct::cmd!(
|
||||
"sudo",
|
||||
"ip",
|
||||
"netns",
|
||||
"exec",
|
||||
self.ns.name(),
|
||||
"env",
|
||||
let mut args = vec![
|
||||
"ip".into(),
|
||||
"netns".into(),
|
||||
"exec".into(),
|
||||
self.ns.name().into(),
|
||||
"env".into(),
|
||||
format!("CONFIG={}", serde_json::to_string(&self.config)?),
|
||||
executor_path
|
||||
)
|
||||
.stdout_capture()
|
||||
.stderr_capture()
|
||||
.unchecked()
|
||||
.start()?;
|
||||
];
|
||||
|
||||
if cfg!(feature = "debug") {
|
||||
let level = &std::env::var("RUST_LOG").unwrap_or("debug".to_string());
|
||||
args.push("env".into());
|
||||
args.push(format!("RUST_LOG={}", level));
|
||||
};
|
||||
|
||||
args.push(executor_path.to_str().expect("valid path").into());
|
||||
|
||||
let process = duct::cmd("sudo", args);
|
||||
|
||||
let process = if !cfg!(feature = "debug") {
|
||||
process
|
||||
.stdout_capture()
|
||||
.stderr_capture()
|
||||
.unchecked()
|
||||
.start()?
|
||||
} else {
|
||||
process.unchecked().start()?
|
||||
};
|
||||
|
||||
let rpc = Rpc::new_native(rpc_addr).await?;
|
||||
|
||||
@@ -118,16 +136,29 @@ impl Executor {
|
||||
"--no-sandbox",
|
||||
format!("--user-data-dir={tmp}"),
|
||||
format!("--allowed-ips=10.250.0.1"),
|
||||
)
|
||||
.stderr_capture()
|
||||
.stdout_capture()
|
||||
.start()?;
|
||||
);
|
||||
|
||||
let process = if !cfg!(feature = "debug") {
|
||||
process.stderr_capture().stdout_capture().start()?
|
||||
} else {
|
||||
process.start()?
|
||||
};
|
||||
|
||||
const TIMEOUT: usize = 10000;
|
||||
const DELAY: usize = 100;
|
||||
let mut retries = 0;
|
||||
let config = HandlerConfig {
|
||||
// Bump the timeout for long-running benches.
|
||||
request_timeout: Duration::from_secs(120),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let (browser, mut handler) = loop {
|
||||
match Browser::connect(format!("http://{}:{}", rpc_addr.0, PORT_BROWSER)).await
|
||||
match Browser::connect_with_config(
|
||||
format!("http://{}:{}", rpc_addr.0, PORT_BROWSER),
|
||||
config.clone(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(browser) => break browser,
|
||||
Err(e) => {
|
||||
@@ -143,6 +174,14 @@ impl Executor {
|
||||
tokio::spawn(async move {
|
||||
while let Some(res) = handler.next().await {
|
||||
if let Err(e) = res {
|
||||
if e.to_string()
|
||||
== "data did not match any variant of untagged enum Message"
|
||||
{
|
||||
// Do not log this error. It appears to be
|
||||
// caused by a bug upstream.
|
||||
// https://github.com/mattsse/chromiumoxide/issues/167
|
||||
continue;
|
||||
}
|
||||
eprintln!("chromium error: {e:?}");
|
||||
}
|
||||
}
|
||||
@@ -152,6 +191,38 @@ impl Executor {
|
||||
.new_page(&format!("http://{wasm_addr}:{wasm_port}/index.html"))
|
||||
.await?;
|
||||
|
||||
#[cfg(feature = "debug")]
|
||||
tokio::spawn(register_listeners(page.clone()).await?);
|
||||
|
||||
#[cfg(feature = "debug")]
|
||||
async fn register_listeners(page: Page) -> Result<impl Future<Output = ()>> {
|
||||
let mut logs = page.event_listener::<EventEntryAdded>().await?.fuse();
|
||||
let mut exceptions =
|
||||
page.event_listener::<EventExceptionThrown>().await?.fuse();
|
||||
|
||||
Ok(futures::future::join(
|
||||
async move {
|
||||
while let Some(event) = logs.next().await {
|
||||
let entry = &event.entry;
|
||||
match entry.level {
|
||||
LogEntryLevel::Error => {
|
||||
error!("{:?}", entry);
|
||||
}
|
||||
_ => {
|
||||
debug!("{:?}: {}", entry.timestamp, entry.text);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
async move {
|
||||
while let Some(event) = exceptions.next().await {
|
||||
error!("{:?}", event);
|
||||
}
|
||||
},
|
||||
)
|
||||
.map(|_| ()))
|
||||
}
|
||||
|
||||
page.execute(EnableParams::builder().build()).await?;
|
||||
page.execute(SetCacheDisabledParams {
|
||||
cache_disabled: true,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user