Compare commits

...

98 Commits

Author SHA1 Message Date
th4s
4727df6fd4 WIP: removing actor... 2025-12-08 14:26:48 +01:00
th4s
a0c7d469f6 feat(tlsn): implement sans-io api for prover and verifier 2025-12-08 12:23:05 +01:00
th4s
0bf4a857b9 refactor: sans-io TLS IO (#1036)
* refactor: add tls-client trait

* cleanup

* fix clippy

* add start state

* assert that mpc future is pending
2025-12-01 18:55:10 +01:00
th4s
4d449b2a1d refactor(tls-client): make tls_client compatible with a synchronous API (#1027)
* refactor(tls-client): make `write_plaintext` sync and remove async api

* restore `complete_io`

* do not potentially block in `write_all_plaintext`
2025-12-01 18:55:10 +01:00
Hendrik Eeckhaut
cec4756e0e ci: set GITHUB_TOKEN env 2025-11-28 14:33:19 +01:00
Hendrik Eeckhaut
0919e1f2b3 clippy: allow deprecated aead::generic_array 2025-11-28 14:33:19 +01:00
Hendrik Eeckhaut
43b9f57e1f build: update Rust to version 1.91.1 2025-11-26 16:53:08 +01:00
dan
c51331d63d test: use ideal vm for testing (#1049) 2025-11-07 12:56:09 +00:00
dan
3905d9351c chore: clean up deps (#1048) 2025-11-07 10:36:41 +00:00
dan
f8a67bc8e7 feat(core): support proving keccak256 commitments (#1046) 2025-11-07 09:18:44 +00:00
dan
952a7011bf feat(cipher): use AES pre/post key schedule circuits (#1042) 2025-11-07 09:08:08 +00:00
Ram
0673818e4e chore: fix links to key exchange doc page (#1045) 2025-11-04 23:07:58 +01:00
dan
a5749d81f1 fix(attestation): verify sig during validation (#1037) 2025-10-30 07:59:57 +00:00
sinu.eth
f2e119bb66 refactor: move and rewrite configuration (#1034)
* refactor: move and rewrite configuration

* fix wasm
2025-10-27 11:47:42 -07:00
Hendrik Eeckhaut
271ac3771e Fix example (#1033)
* fix: provide encoder secret to attestation
* Add missing entry in example's README file
2025-10-24 10:33:32 +02:00
Benjamin Martinez Picech
f69dd7a239 refactor(tlsn-core): redeclaration of content type into core (#1026)
* redeclaration of content type into core

* fix compilation error

* comment removal

* Lint and format fixes

* fix wasm build

* Unknown content type

* format fix
2025-10-23 15:47:53 +02:00
sinu.eth
79f5160cae feat(tlsn): insecure mode (#1031) 2025-10-22 10:18:11 -07:00
sinu.eth
5fef2af698 fix(example): close prover (#1025) 2025-10-17 10:39:08 -07:00
sinu.eth
5b2083e211 refactor(tlsn): invert control of config validation (#1023)
* refactor(tlsn): invert control of config validation

* clippy
2025-10-17 10:19:02 -07:00
sinu.eth
d26bb02d2e chore: update to alpha.14-pre (#1022) 2025-10-15 11:11:43 -07:00
sinu
a766b64184 ci: add manual trigger to main update 2025-10-15 10:11:01 -07:00
sinu.eth
0885d40ddf chore: release v0.1.0-alpha.13 (#1019) 2025-10-15 09:38:52 -07:00
sinu.eth
610411aae4 ci: relax clippy (#1020) 2025-10-15 09:27:55 -07:00
sinu.eth
37df1baed7 feat(core): proof config builder reveal all methods (#1017)
Co-authored-by: Hendrik Eeckhaut <hendrik@eeckhaut.org>
2025-10-14 08:56:28 -07:00
dan
aeaebc5c60 chore(harness): expose debug flag in dockerfile (#1018) 2025-10-14 11:19:30 +00:00
sinu.eth
2e7e3db11d fix: fully identify signature algorithm (#1015) 2025-10-13 09:57:34 +02:00
sinu.eth
0a68837d0a fix: empty auth ranges (#1016) 2025-10-10 15:44:38 -07:00
sinu.eth
0ec2392716 chore(tlsn): add transcript auth tests (#1014)
* chore(tlsn): add transcript auth tests

* clippy
2025-10-10 14:10:17 -07:00
sinu.eth
f99fce5b5a fix(tlsn): do not implicitly reveal encoder secret (#1011) 2025-10-10 08:39:24 -07:00
sinu.eth
6b9f44e7e5 feat(tlsn): disclose encryption key (#1010)
Co-authored-by: th4s <th4s@metavoid.xyz>
2025-10-10 08:32:50 -07:00
dan
bf1cf2302a fix(harness): add harness debug feature (#1012) 2025-10-10 14:20:42 +00:00
sinu.eth
2884be17e0 feat(tlsn): partial plaintext auth (#1006)
Co-authored-by: th4s <th4s@metavoid.xyz>
2025-10-09 11:22:23 -07:00
sinu.eth
df8d79c152 fix(wasm): explicitly enable link args for wasm (#1007) 2025-10-09 08:34:11 -07:00
yuroitaki
82d509266b feat: add blake3 transcript commitment (#1000)
* Add blake3.

* Update mpz version.

---------

Co-authored-by: yuroitaki <>
2025-10-08 10:13:07 +08:00
dan
d5ad768e7c chore: improve error msg (#1003) 2025-10-03 05:43:58 +00:00
Hendrik Eeckhaut
d25fb320d4 build: update Rust to version 1.90.0 2025-09-24 09:32:56 +02:00
Hendrik Eeckhaut
0539268da7 Interactive noir example (#981)
demo for interactive zk age proof

Co-authored-by: th4s <th4s@metavoid.xyz>
2025-09-19 16:55:10 +02:00
dan
427b2896b5 allow root_store to be None (#995) 2025-09-19 15:15:04 +02:00
Hendrik Eeckhaut
89d1e594d1 privacy-scaling-explorations -> privacy-ethereum (#993) 2025-09-11 16:48:01 +02:00
sinu.eth
b4380f021e refactor: decouple ProveConfig from PartialTranscript (#991) 2025-09-11 09:13:52 +02:00
sinu.eth
8a823d18ec refactor(core): replace Idx with RangeSet (#988)
* refactor(core): replace Idx with RangeSet

* clippy
2025-09-10 15:44:40 -07:00
sinu.eth
7bcfc56bd8 fix(tls-core): remove deprecated webpki error variants (#992)
* fix(tls-core): remove deprecated webpki error variants

* clippy
2025-09-10 15:24:07 -07:00
sinu.eth
2909d5ebaa chore: bump mpz to 3d90b6c (#990) 2025-09-10 14:38:48 -07:00
sinu.eth
7918494ccc fix(core): fix dev dependencies (#989) 2025-09-10 14:25:04 -07:00
sinu.eth
92dd47b376 fix(core): enable zeroize derive (#987) 2025-09-10 14:11:41 -07:00
th4s
5474a748ce feat(core): Add transcript fixture (#983)
* feat(core): add transcript fixture for testing

* add feedback

* remove packages from dev dependencies
2025-09-10 22:58:10 +02:00
yuroitaki
92da5adc24 chore: update attestation example (#966)
* Add attestation example.

* Apply fmt.

* Apply clippy fix.

* Rebase.

* Improved readme + more default loggging in prove example

* Removed wrong AI generated "learn more" links

* re-export ContentType in tlsn-core

* remove unnecessary checks from example

---------

Co-authored-by: yuroitaki <>
Co-authored-by: Hendrik Eeckhaut <hendrik@eeckhaut.org>
Co-authored-by: sinu <65924192+sinui0@users.noreply.github.com>
2025-09-10 09:37:17 -07:00
Hendrik Eeckhaut
e0ce1ad31a build:Update to unpatched ws_stream_wasm crate (#975) 2025-09-01 16:33:00 +02:00
Hendrik Eeckhaut
3b76877920 build: reduce wasm size (#977) 2025-09-01 11:28:12 +02:00
Hendrik Eeckhaut
783355772a docs: corrected commands in docker.md of the harness (#976) 2025-08-28 17:00:18 +02:00
dan
e5c59da90b chore: fix tests (#974) 2025-08-26 08:42:48 +00:00
dan
f059c53c2d use zk config; bump mpz (#973) 2025-08-26 08:23:24 +00:00
sinu.eth
a1367b5428 refactor(tlsn): change network setting default to reduce data transfer (#971) 2025-08-22 14:00:23 -07:00
sinu.eth
9d8124ac9d chore: bump mpz to 1b00912 (#970) 2025-08-21 09:46:29 -07:00
dan
5034366c72 fix(hmac-sha256): compute PHash and AHash concurrently (#969)
---------

Co-authored-by: th4s <th4s@metavoid.xyz>
2025-08-21 06:41:59 +00:00
sinu.eth
afd8f44261 feat(tlsn): serializable config (#968) 2025-08-18 09:03:04 -07:00
sinu.eth
21086d2883 refactor: clean up web pki (#967)
* refactor: clean up web pki

* fix time import

* clippy

* fix wasm
2025-08-18 08:36:04 -07:00
dan
cca9a318a4 fix(harness): improve harness stability (#962) 2025-08-15 09:17:20 +00:00
dan
cb804a6025 fix(harness): disable tracing events (#961) 2025-08-15 07:13:12 +00:00
th4s
9f849e7c18 fix(encoding): set correct frame limit (#963)
* fix(encoding): set correct frame limit

* bugfix for `TranscriptRefs::len`

* use current frame limit as cushion room
2025-08-13 09:57:00 +02:00
th4s
389bceddef chore: bump rust version, fix lints and satisfy clippy (#964)
* chore(lints): fix lints and satisfy clippy

* bump rust version in ci
2025-08-12 10:50:31 -07:00
th4s
657838671a chore: remove notarize methods for prover and verifier (#952)
* feat: remove notarize methods for prover and verifier

* clean up imports

* remove remaining notarize methods

* clean up imports

* remove wasm attestation bindings

---------

Co-authored-by: sinu <65924192+sinui0@users.noreply.github.com>
2025-08-06 09:38:43 -07:00
yuroitaki
2f072b2578 chore: remove notary crates (#953)
Co-authored-by: Hendrik Eeckhaut <hendrik@eeckhaut.org>
2025-08-04 10:41:45 +02:00
sinu.eth
33153d1124 refactor: move web-spawn under web feature (#949)
* refactor: move web-spawn under web feature

* add arch conditional
2025-07-29 07:11:16 -07:00
Hendrik Eeckhaut
2d399d5e24 chore: Update latency/bandwidth plots for new harness (#923)
* Updated latency/bandwidth plots for new harness
* Fix harness Docker build
2025-07-23 10:58:46 +02:00
dan
b6d7249b6d fix(harness): restore multithreading for browser benches (#943) 2025-07-23 05:40:58 +00:00
dan
2a8c1c3382 fix(harness): add custom wasm-pack to build script (#940) 2025-07-22 06:29:12 +00:00
dan
7c27162875 fix(harness): pull latest docker images (#941) 2025-07-21 06:07:48 +00:00
sinu.eth
eef813712d refactor: extract attestation functionality into dedicated crate (#936)
* refactor: extract attestation functionality into dedicated crate

* commit lock

* fix integration test

* clippy

* fix docs

* fix import

* fix wasm types

* fix doctest

* verifier config default rootstore

* fix integration test

* fix notary integration tests
2025-07-09 09:54:11 -07:00
sinu.eth
2e94e08fa6 build(wasm): enable simd128 feature (#937) 2025-07-04 11:22:56 -07:00
dan
97d9475335 fix(harness): do not close connection too early (#935) 2025-07-02 09:40:52 -07:00
sinu.eth
38820d6a3f refactor: consolidate into tlsn crate (#934)
* refactor: consolidate into tlsn crate

* clean up dead code

* bump lock file

* rustfmt

* fix examples

* fix docs script

* clippy

* clippy
2025-07-02 09:40:28 -07:00
sinu.eth
af85fa100f build(wasm): add wasm profile and optimize for perf (#933) 2025-07-02 08:52:47 -07:00
Hendrik Eeckhaut
008b901913 ci: docker image for new harness
* update Docker for new harness
* disable shm in Chrome
2025-06-27 17:58:12 +01:00
Hendrik Eeckhaut
db85f68328 build: update Rust to version 1.88.0 2025-06-27 16:40:29 +01:00
Hendrik Eeckhaut
fb80aa4cc9 chore: Set version number to 0.1.0-alpha.13-pre (#931) 2025-06-20 14:41:33 +02:00
Hendrik Eeckhaut
8dae57d6a7 ci: fix problem with multiple tlsn-wasm build artefacts (#930) 2025-06-20 10:57:35 +02:00
dan
f2ff4ba792 chore: release v0.1.0-alpha.12 (#928) 2025-06-19 09:05:34 +00:00
dan
9bf3371873 chore(wasm): expose client auth config to js (#927) 2025-06-19 07:15:09 +00:00
dan
9d853eb496 feat(prover): client authentication (#916) 2025-06-17 14:02:14 +00:00
sinu.eth
6923ceefd3 fix(harness): iptable rule and bench config variable (#925)
* fix(harness): iptable rule and bench config variable

* rustfmt
2025-06-16 13:18:34 -04:00
sinu.eth
5239c2328a chore: bump mpz to ccc0057 (#924) 2025-06-16 07:42:49 -07:00
Hendrik Eeckhaut
6a7c5384a9 build: fixed version numbers 2025-06-12 14:24:55 +02:00
th4s
7e469006c0 fix(prf): adapt logic to new default setting (#920) 2025-06-11 20:34:47 +02:00
dan
55091b5e94 fix: set TCP_NODELAY for prover and notary (#911) 2025-06-10 08:13:12 +00:00
dan
bc1eba18c9 feat(mpc-tls): use concurrent ot setup and gc preprocessing (#910)
* feat(mpc-tls): use concurrent ot setup and gc preprocessing

* bump mpz

* increase muxer stream count

* update Cargo.lock

---------

Co-authored-by: sinu.eth <65924192+sinui0@users.noreply.github.com>
2025-06-06 15:39:35 -07:00
sinu.eth
c128ab16ce fix(harness): retry browser connection until timeout (#914)
* fix(harness): retry browser connection until timeout

* add timeout to executor shutdown

* shutdown timeout error msg

* clippy
2025-06-06 15:01:28 -07:00
sinu.eth
a87125ff88 fix(ci): wasm tests (#913) 2025-06-06 13:51:34 -07:00
sinu.eth
0933d711d2 feat: harness (#703)
* feat: harness

* delete tests.rs build artifact

* fix binary path

* seconds -> milliseconds

* update lock

* add empty tests module

* rustfmt

* ToString -> Display

* output tests module into build artifacts

* clippy

* rustfmt
2025-06-06 13:34:32 -07:00
sinu.eth
79c230f2fa refactor(mpc-tls): remove commit-reveal from tag verification (#907) 2025-06-06 06:39:12 +00:00
dan
345d5d45ad feat: prove server mac key (#868)
* feat(mpc-tls): prove server mac key

* remove stray dep

* move mac key into `SessionKeys`

* fix key translation

* remove dangling dep

* move ghash mod to tlsn-common

* fix clippy lints

* treat all recv recs as unauthenticated

* detach zkvm first, then prove

* decrypt with aes_gcm, decode mac key only in zkvm

* encapsulate into `fn verify_tags`; inline mod `zk_aes_ecb`

* handle error

* fix dangling and clippy

* bump Cargo.lock
2025-06-05 09:19:41 -07:00
Hendrik Eeckhaut
55a26aad77 build: Lock + document Cargo.lock (#885) 2025-06-04 09:12:06 +02:00
Hendrik Eeckhaut
1132d441e1 docs: improve example readme (#904) 2025-06-04 08:56:55 +02:00
Hendrik Eeckhaut
fa2fdfd601 feat: add logging to server fixture (#903) 2025-06-04 08:49:33 +02:00
Hendrik Eeckhaut
24e10d664f Fix wasm-pack warnings (#888) 2025-06-03 22:38:54 +02:00
yuroitaki
c0e084c1ca fix(wasm): expose revealing server identity. (#898)
* Add reveal server identity.

* Fix test.

* Remove defualt.

---------

Co-authored-by: yuroitaki <>
2025-05-30 10:39:13 +08:00
Jakub Konka
b6845dfc5c feat(notary): add JWT-based authorization mode (#817)
* feat(server): add JWT-based authorization mode

This mode is an alternative to whitelist authorization mode.
It extracts the JWT from the authorization header (bearer token),
validates token's signature, claimed expiry times and additional
(user-configurable) claims.

* Fix formatting and lints

* Address review comments

* feat(server): remove JwtClaimType config property

* Fix missing README comments

* Address review comments

* Address review comments

---------

Co-authored-by: yuroitaki <25913766+yuroitaki@users.noreply.github.com>
2025-05-28 12:51:18 +08:00
sinu.eth
31def9ea81 chore: bump prerelease version (#895) 2025-05-27 11:43:42 -07:00
363 changed files with 16929 additions and 22235 deletions

View File

@@ -21,17 +21,21 @@ jobs:
- name: Build Docker Image
run: |
docker build -t tlsn-bench . -f ./crates/benches/binary/benches.Dockerfile --build-arg BENCH_TYPE=${{ github.event.inputs.bench_type }}
docker build -t tlsn-bench . -f ./crates/harness/harness.Dockerfile
- name: Run Benchmarks
run: |
docker run --privileged -v ${{ github.workspace }}/crates/benches/binary:/benches tlsn-bench
docker run --privileged -v ./crates/harness/:/benches tlsn-bench bash -c "runner setup; runner --target ${{ github.event.inputs.bench_type }} bench"
- name: Plot Benchmarks
run: |
docker run -v ./crates/harness/:/benches tlsn-bench bash -c "tlsn-harness-plot /benches/bench.toml /benches/metrics.csv --min-max-band --prover-kind ${{ github.event.inputs.bench_type }}"
- name: Upload graphs
uses: actions/upload-artifact@v4
with:
name: benchmark_graphs
path: |
./crates/benches/binary/runtime_vs_latency.html
./crates/benches/binary/runtime_vs_bandwidth.html
./crates/benches/binary/download_size_vs_memory.html
./crates/harness/metrics.csv
./crates/harness/bench.toml
./crates/harness/runtime_vs_latency.html
./crates/harness/runtime_vs_bandwidth.html

View File

@@ -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.87.0
RUST_VERSION: 1.91.1
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 -- -D warnings
run: cargo clippy --keep-going --all-features --all-targets --locked
fmt:
name: Check formatting
@@ -81,10 +80,10 @@ jobs:
uses: Swatinem/rust-cache@v2.7.7
- name: Build
run: cargo build --all-targets
run: cargo build --all-targets --locked
- name: Test
run: cargo test --no-fail-fast
run: cargo test --no-fail-fast --locked
wasm:
name: Build and Test wasm
@@ -112,25 +111,29 @@ jobs:
sudo apt-get install -y chromium-chromedriver
- name: Install wasm-pack
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
# we install a specific version which supports custom profiles
run: cargo install --git https://github.com/rustwasm/wasm-pack.git --rev 32e52ca
- name: Use caching
uses: Swatinem/rust-cache@v2.7.7
- name: Build harness
working-directory: crates/harness
run: ./build.sh
- name: Run tests
working-directory: crates/harness
run: |
cd crates/wasm-test-runner
./run.sh
./bin/runner setup
./bin/runner --target browser test
- name: Run build
run: |
cd crates/wasm
./build.sh
working-directory: crates/wasm
run: ./build.sh
- name: Dry Run NPM Publish
run: |
cd crates/wasm/pkg
npm publish --dry-run
working-directory: crates/wasm/pkg
run: npm publish --dry-run
- name: Save tlsn-wasm package for tagged builds
if: startsWith(github.ref, 'refs/tags/')
@@ -155,11 +158,8 @@ 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 --profile tests-integration --workspace --exclude tlsn-tls-client --exclude tlsn-tls-core --no-fail-fast -- --include-ignored
run: cargo test --locked --profile tests-integration --workspace --exclude tlsn-tls-client --exclude tlsn-tls-core --no-fail-fast -- --include-ignored
coverage:
runs-on: ubuntu-latest
@@ -174,7 +174,7 @@ jobs:
- name: Install cargo-llvm-cov
uses: taiki-e/install-action@cargo-llvm-cov
- name: Generate code coverage
run: cargo llvm-cov --all-features --workspace --lcov --output-path lcov.info
run: cargo llvm-cov --all-features --workspace --locked --lcov --output-path lcov.info
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
with:
@@ -182,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 --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
@@ -391,4 +199,4 @@ jobs:
draft: true
tag_name: ${{ github.ref_name }}
prerelease: true
generate_release_notes: true
generate_release_notes: true

View File

@@ -6,7 +6,7 @@ on:
tag:
description: 'Tag to publish to NPM'
required: true
default: 'v0.1.0-alpha.11'
default: 'v0.1.0-alpha.14-pre'
jobs:
release:
@@ -22,7 +22,7 @@ jobs:
RUN_ID=$(gh api \
-H "Accept: application/vnd.github+json" \
"/repos/tlsnotary/tlsn/actions/workflows/ci.yml/runs?per_page=100" \
--jq '.workflow_runs[] | select(.head_branch == "${{ github.event.inputs.tag }}") | .id')
--jq '.workflow_runs[] | select(.head_branch == "${{ github.event.inputs.tag }}") | .id' | sort | tail -1)
if [ -z "$RUN_ID" ]; then
echo "No run found for tag ${{ github.event.inputs.tag }}"

View File

@@ -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' }}

View File

@@ -1,6 +1,7 @@
name: Fast-forward main branch to published release tag
on:
workflow_dispatch:
release:
types: [published]

3
.gitignore vendored
View File

@@ -29,6 +29,3 @@ target/
# metrics
*.csv
# Cargo.lock
Cargo.lock

View File

@@ -61,3 +61,21 @@ Comments for function arguments must adhere to this pattern:
/// * `arg2` - The second argument.
pub fn compute(...
```
## Cargo.lock
We check in `Cargo.lock` to ensure reproducible builds. It must be updated whenever `Cargo.toml` changes. The TLSNotary team typically updates `Cargo.lock` in a separate commit after dependency changes.
If you want to hide `Cargo.lock` changes from your local `git diff`, run:
```sh
git update-index --assume-unchanged Cargo.lock
```
To start tracking changes again:
```sh
git update-index --no-assume-unchanged Cargo.lock
```
> ⚠️ Note: This only affects your local view. The file is still tracked in the repository and will be checked and used in CI.

5500
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,11 +1,6 @@
[workspace]
members = [
"crates/benches/binary",
"crates/benches/browser/core",
"crates/benches/browser/native",
"crates/benches/browser/wasm",
"crates/benches/library",
"crates/common",
"crates/attestation",
"crates/components/deap",
"crates/components/cipher",
"crates/components/hmac-sha256",
@@ -14,23 +9,19 @@ members = [
"crates/data-fixtures",
"crates/examples",
"crates/formats",
"crates/notary/client",
"crates/notary/common",
"crates/notary/server",
"crates/notary/tests-integration",
"crates/prover",
"crates/server-fixture/certs",
"crates/server-fixture/server",
"crates/tests-integration",
"crates/tls/backend",
"crates/tls/client",
"crates/tls/client-async",
"crates/tls/core",
"crates/mpc-tls",
"crates/tls/server-fixture",
"crates/verifier",
"crates/wasm",
"crates/wasm-test-runner",
"crates/harness/core",
"crates/harness/executor",
"crates/harness/runner",
"crates/harness/plot",
"crates/tlsn",
]
resolver = "2"
@@ -44,16 +35,16 @@ resolver = "2"
inherits = "release"
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" }
tlsn-benches-browser-core = { path = "crates/benches/browser/core" }
tlsn-benches-browser-native = { path = "crates/benches/browser/native" }
tlsn-benches-library = { path = "crates/benches/library" }
tlsn-common = { path = "crates/common" }
tlsn-core = { path = "crates/core" }
tlsn-data-fixtures = { path = "crates/data-fixtures" }
tlsn-deap = { path = "crates/components/deap" }
@@ -61,48 +52,57 @@ tlsn-formats = { path = "crates/formats" }
tlsn-hmac-sha256 = { path = "crates/components/hmac-sha256" }
tlsn-key-exchange = { path = "crates/components/key-exchange" }
tlsn-mpc-tls = { path = "crates/mpc-tls" }
tlsn-prover = { path = "crates/prover" }
tlsn-server-fixture = { path = "crates/server-fixture/server" }
tlsn-server-fixture-certs = { path = "crates/server-fixture/certs" }
tlsn-tls-backend = { path = "crates/tls/backend" }
tlsn-tls-client = { path = "crates/tls/client" }
tlsn-tls-client-async = { path = "crates/tls/client-async" }
tlsn-tls-core = { path = "crates/tls/core" }
tlsn-utils = { git = "https://github.com/tlsnotary/tlsn-utils", rev = "6168663" }
tlsn-verifier = { path = "crates/verifier" }
tlsn-harness-core = { path = "crates/harness/core" }
tlsn-harness-executor = { path = "crates/harness/executor" }
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", tag = "v0.1.0-alpha.3" }
mpz-memory-core = { git = "https://github.com/privacy-scaling-explorations/mpz", tag = "v0.1.0-alpha.3" }
mpz-common = { git = "https://github.com/privacy-scaling-explorations/mpz", tag = "v0.1.0-alpha.3" }
mpz-core = { git = "https://github.com/privacy-scaling-explorations/mpz", tag = "v0.1.0-alpha.3" }
mpz-vm-core = { git = "https://github.com/privacy-scaling-explorations/mpz", tag = "v0.1.0-alpha.3" }
mpz-garble = { git = "https://github.com/privacy-scaling-explorations/mpz", tag = "v0.1.0-alpha.3" }
mpz-garble-core = { git = "https://github.com/privacy-scaling-explorations/mpz", tag = "v0.1.0-alpha.3" }
mpz-ole = { git = "https://github.com/privacy-scaling-explorations/mpz", tag = "v0.1.0-alpha.3" }
mpz-ot = { git = "https://github.com/privacy-scaling-explorations/mpz", tag = "v0.1.0-alpha.3" }
mpz-share-conversion = { git = "https://github.com/privacy-scaling-explorations/mpz", tag = "v0.1.0-alpha.3" }
mpz-fields = { git = "https://github.com/privacy-scaling-explorations/mpz", tag = "v0.1.0-alpha.3" }
mpz-zk = { git = "https://github.com/privacy-scaling-explorations/mpz", tag = "v0.1.0-alpha.3" }
mpz-hash = { git = "https://github.com/privacy-scaling-explorations/mpz", tag = "v0.1.0-alpha.3" }
mpz-circuits = { git = "https://github.com/privacy-ethereum/mpz", rev = "bd80826" }
mpz-circuits-data = { git = "https://github.com/privacy-ethereum/mpz", rev = "bd80826" }
mpz-memory-core = { git = "https://github.com/privacy-ethereum/mpz", rev = "bd80826" }
mpz-common = { git = "https://github.com/privacy-ethereum/mpz", rev = "bd80826" }
mpz-core = { git = "https://github.com/privacy-ethereum/mpz", rev = "bd80826" }
mpz-vm-core = { git = "https://github.com/privacy-ethereum/mpz", rev = "bd80826" }
mpz-garble = { git = "https://github.com/privacy-ethereum/mpz", rev = "bd80826" }
mpz-garble-core = { git = "https://github.com/privacy-ethereum/mpz", rev = "bd80826" }
mpz-ole = { git = "https://github.com/privacy-ethereum/mpz", rev = "bd80826" }
mpz-ot = { git = "https://github.com/privacy-ethereum/mpz", rev = "bd80826" }
mpz-share-conversion = { git = "https://github.com/privacy-ethereum/mpz", rev = "bd80826" }
mpz-fields = { git = "https://github.com/privacy-ethereum/mpz", rev = "bd80826" }
mpz-zk = { git = "https://github.com/privacy-ethereum/mpz", rev = "bd80826" }
mpz-hash = { git = "https://github.com/privacy-ethereum/mpz", rev = "bd80826" }
mpz-ideal-vm = { git = "https://github.com/privacy-ethereum/mpz", rev = "bd80826" }
futures-plex = { git = "https://github.com/tlsnotary/tlsn-utils", rev = "0b46dc0" }
rangeset = { version = "0.2" }
serio = { version = "0.2" }
spansy = { git = "https://github.com/tlsnotary/tlsn-utils", rev = "6168663" }
uid-mux = { version = "0.2" }
websocket-relay = { git = "https://github.com/tlsnotary/tlsn-utils", rev = "6168663" }
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" }
blake3 = { version = "1.5" }
bon = { version = "3.6" }
bytes = { version = "1.4" }
cfg-if = { version = "1" }
chromiumoxide = { version = "0.7" }
chrono = { version = "0.4" }
cipher = { version = "0.4" }
clap = { version = "4.5" }
criterion = { version = "0.5" }
ctr = { version = "0.9" }
derive_builder = { version = "0.12" }
@@ -111,23 +111,25 @@ 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" }
ipnet = { version = "2.11" }
inventory = { version = "0.3" }
itybity = { version = "0.2" }
js-sys = { version = "0.3" }
k256 = { version = "0.13" }
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" }
rand_chacha = { version = "0.9" }
rand_core = { version = "0.9" }
@@ -139,21 +141,29 @@ 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" }
serde_json = { version = "1.0" }
sha2 = { version = "0.10" }
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" }
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" }
webpki-roots = { version = "1.0" }
webpki-root-certs = { version = "1.0" }
ws_stream_wasm = { version = "0.7.5" }
zeroize = { version = "1.8" }

View File

@@ -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

View File

@@ -0,0 +1,39 @@
[package]
name = "tlsn-attestation"
version = "0.1.0-alpha.14-pre"
edition = "2024"
[features]
default = []
fixtures = ["tlsn-core/fixtures", "dep:tlsn-data-fixtures"]
[dependencies]
tlsn-tls-core = { workspace = true }
tlsn-core = { workspace = true, features = ["mozilla-certs"] }
tlsn-data-fixtures = { workspace = true, optional = true }
bcs = { workspace = true }
blake3 = { workspace = true }
p256 = { workspace = true, features = ["serde"] }
k256 = { workspace = true }
opaque-debug = { workspace = true }
rand = { workspace = true }
serde = { workspace = true, features = ["derive"] }
thiserror = { workspace = true }
tiny-keccak = { workspace = true, features = ["keccak"] }
[dev-dependencies]
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"] }
tlsn-data-fixtures = { workspace = true }
[lints]
workspace = true
[[test]]
name = "api"
required-features = ["fixtures"]

View File

@@ -1,19 +1,17 @@
use std::error::Error;
use rand::{rng, Rng};
use rand::{Rng, rng};
use crate::{
attestation::{
Attestation, AttestationConfig, Body, Extension, FieldId, Header, ServerCertCommitment,
VERSION,
},
use tlsn_core::{
connection::{ConnectionInfo, ServerEphemKey},
hash::HashAlgId,
request::Request,
serialize::CanonicalSerialize,
transcript::{TranscriptCommitment, encoding::EncoderSecret},
};
use crate::{
Attestation, AttestationConfig, Body, CryptoProvider, Extension, FieldId, Header,
ServerCertCommitment, VERSION, request::Request, serialize::CanonicalSerialize,
signing::SignatureAlgId,
transcript::TranscriptCommitment,
CryptoProvider,
};
/// Attestation builder state for accepting a request.
@@ -27,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>,
}
@@ -88,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,
},
@@ -108,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);
@@ -131,6 +137,7 @@ impl AttestationBuilder<'_, Sign> {
connection_info,
server_ephemeral_key,
cert_commitment,
encoder_secret,
extensions,
transcript_commitments,
} = self.state;
@@ -161,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))
@@ -233,7 +241,7 @@ impl std::fmt::Display for AttestationBuilderError {
}
if let Some(source) = &self.source {
write!(f, " caused by: {}", source)?;
write!(f, " caused by: {source}")?;
}
Ok(())
@@ -243,14 +251,15 @@ impl std::fmt::Display for AttestationBuilderError {
#[cfg(test)]
mod test {
use rstest::{fixture, rstest};
use tlsn_data_fixtures::http::{request::GET_WITH_HEADER, response::OK_JSON};
use crate::{
connection::{HandshakeData, HandshakeDataV1_2},
fixtures::{encoding_provider, request_fixture, ConnectionFixture, RequestFixture},
use tlsn_core::{
connection::{CertBinding, CertBindingV1_2},
fixtures::{ConnectionFixture, encoding_provider},
hash::Blake3,
transcript::Transcript,
};
use tlsn_data_fixtures::http::{request::GET_WITH_HEADER, response::OK_JSON};
use crate::fixtures::{RequestFixture, request_fixture};
use super::*;
@@ -400,10 +409,13 @@ 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");
};
attestation_builder.server_ephemeral_key(server_ephemeral_key);
@@ -468,10 +480,13 @@ 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");
};
attestation_builder
.connection_info(connection_info)

View File

@@ -1,9 +1,9 @@
use std::{fmt::Debug, sync::Arc};
use tlsn_core::hash::HashAlgId;
use crate::{
attestation::{Extension, InvalidExtension},
hash::{HashAlgId, DEFAULT_SUPPORTED_HASH_ALGS},
signing::SignatureAlgId,
Extension, InvalidExtension, hash::DEFAULT_SUPPORTED_HASH_ALGS, signing::SignatureAlgId,
};
type ExtensionValidator = Arc<dyn Fn(&[Extension]) -> Result<(), InvalidExtension> + Send + Sync>;
@@ -124,7 +124,7 @@ impl AttestationConfigBuilder {
///
/// # Example
/// ```
/// # use tlsn_core::attestation::{AttestationConfig, InvalidExtension};
/// # use tlsn_attestation::{AttestationConfig, InvalidExtension};
/// # let mut builder = AttestationConfig::builder();
/// builder.extension_validator(|extensions| {
/// for extension in extensions {

View File

@@ -0,0 +1,149 @@
//! Types for committing details of a connection.
//!
//! ## Commitment
//!
//! During the TLS handshake the Notary receives the Server's ephemeral public
//! key, and this key serves as a binding commitment to the identity of the
//! Server. The ephemeral key itself does not reveal the Server's identity, but
//! it is bound to it via a signature created using the Server's
//! X.509 certificate.
//!
//! A Prover can withhold the Server's signature and certificate chain from the
//! Notary to improve privacy and censorship resistance.
//!
//! ## Proving the Server's identity
//!
//! A Prover can prove the Server's identity to a Verifier by sending a
//! [`ServerIdentityProof`]. This proof contains all the information required to
//! establish the link between the TLS connection and the Server's X.509
//! certificate. A Verifier checks the Server's certificate against their own
//! trust anchors, the same way a typical TLS client would.
use serde::{Deserialize, Serialize};
use tlsn_core::{
connection::{HandshakeData, HandshakeVerificationError, ServerEphemKey, ServerName},
hash::{Blinded, HashAlgorithm, HashProviderError, TypedHash},
};
use crate::{CryptoProvider, hash::HashAlgorithmExt, serialize::impl_domain_separator};
/// Opens a [`ServerCertCommitment`].
#[derive(Clone, Serialize, Deserialize)]
pub struct ServerCertOpening(Blinded<HandshakeData>);
impl_domain_separator!(ServerCertOpening);
opaque_debug::implement!(ServerCertOpening);
impl ServerCertOpening {
pub(crate) fn new(data: HandshakeData) -> Self {
Self(Blinded::new(data))
}
pub(crate) fn commit(&self, hasher: &dyn HashAlgorithm) -> ServerCertCommitment {
ServerCertCommitment(TypedHash {
alg: hasher.id(),
value: hasher.hash_separated(self),
})
}
/// Returns the server identity data.
pub fn data(&self) -> &HandshakeData {
self.0.data()
}
}
/// Commitment to a server certificate.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ServerCertCommitment(pub(crate) TypedHash);
impl_domain_separator!(ServerCertCommitment);
/// TLS server identity proof.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServerIdentityProof {
name: ServerName,
opening: ServerCertOpening,
}
impl ServerIdentityProof {
pub(crate) fn new(name: ServerName, opening: ServerCertOpening) -> Self {
Self { name, opening }
}
/// Verifies the server identity proof.
///
/// # Arguments
///
/// * `provider` - Crypto provider.
/// * `time` - The time of the connection.
/// * `server_ephemeral_key` - The server's ephemeral key.
/// * `commitment` - Commitment to the server certificate.
pub fn verify_with_provider(
self,
provider: &CryptoProvider,
time: u64,
server_ephemeral_key: &ServerEphemKey,
commitment: &ServerCertCommitment,
) -> Result<ServerName, ServerIdentityProofError> {
let hasher = provider.hash.get(&commitment.0.alg)?;
if commitment.0.value != hasher.hash_separated(&self.opening) {
return Err(ServerIdentityProofError {
kind: ErrorKind::Commitment,
message: "certificate opening does not match commitment".to_string(),
});
}
// Verify certificate and identity.
self.opening
.data()
.verify(&provider.cert, time, server_ephemeral_key, &self.name)?;
Ok(self.name)
}
}
/// Error for [`ServerIdentityProof`].
#[derive(Debug, thiserror::Error)]
#[error("server identity proof error: {kind}: {message}")]
pub struct ServerIdentityProofError {
kind: ErrorKind,
message: String,
}
impl From<HashProviderError> for ServerIdentityProofError {
fn from(err: HashProviderError) -> Self {
Self {
kind: ErrorKind::Provider,
message: err.to_string(),
}
}
}
impl From<HandshakeVerificationError> for ServerIdentityProofError {
fn from(err: HandshakeVerificationError) -> Self {
Self {
kind: ErrorKind::Certificate,
message: err.to_string(),
}
}
}
#[derive(Debug)]
enum ErrorKind {
Provider,
Commitment,
Certificate,
}
impl std::fmt::Display for ErrorKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ErrorKind::Provider => write!(f, "provider"),
ErrorKind::Commitment => write!(f, "commitment"),
ErrorKind::Certificate => write!(f, "certificate"),
}
}
}

View File

@@ -2,7 +2,7 @@ use std::error::Error;
use serde::{Deserialize, Serialize};
use crate::hash::impl_domain_separator;
use crate::serialize::impl_domain_separator;
/// An attestation extension.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]

View File

@@ -0,0 +1,192 @@
//! Attestation fixtures.
use tlsn_core::{
connection::{CertBinding, CertBindingV1_2},
fixtures::ConnectionFixture,
hash::HashAlgorithm,
transcript::{
Transcript, TranscriptCommitConfigBuilder, TranscriptCommitment,
encoding::{EncodingProvider, EncodingTree},
},
};
use crate::{
Attestation, AttestationConfig, CryptoProvider, Extension,
request::{Request, RequestConfig},
signing::{
KeyAlgId, SignatureAlgId, SignatureVerifier, SignatureVerifierProvider, Signer,
SignerProvider,
},
};
/// A Request fixture used for testing.
#[allow(missing_docs)]
pub struct RequestFixture {
pub encoding_tree: EncodingTree,
pub request: Request,
}
/// Returns a request fixture for testing.
pub fn request_fixture(
transcript: Transcript,
encodings_provider: impl EncodingProvider,
connection: ConnectionFixture,
encoding_hasher: impl HashAlgorithm,
extensions: Vec<Extension>,
) -> RequestFixture {
let provider = CryptoProvider::default();
let (sent_len, recv_len) = transcript.len();
let ConnectionFixture {
server_name,
server_cert_data,
..
} = connection;
let mut transcript_commitment_builder = TranscriptCommitConfigBuilder::new(&transcript);
transcript_commitment_builder
.commit_sent(&(0..sent_len))
.unwrap()
.commit_recv(&(0..recv_len))
.unwrap();
let transcripts_commitment_config = transcript_commitment_builder.build().unwrap();
// Prover constructs encoding tree.
let encoding_tree = EncodingTree::new(
&encoding_hasher,
transcripts_commitment_config.iter_encoding(),
&encodings_provider,
)
.unwrap();
let mut builder = RequestConfig::builder();
for extension in extensions {
builder.extension(extension);
}
let request_config = builder.build().unwrap();
let mut request_builder = Request::builder(&request_config);
request_builder
.server_name(server_name)
.handshake_data(server_cert_data)
.transcript(transcript);
let (request, _) = request_builder.build(&provider).unwrap();
RequestFixture {
encoding_tree,
request,
}
}
/// Returns an attestation fixture for testing.
pub fn attestation_fixture(
request: Request,
connection: ConnectionFixture,
signature_alg: SignatureAlgId,
transcript_commitments: &[TranscriptCommitment],
) -> Attestation {
let ConnectionFixture {
connection_info,
server_cert_data,
..
} = connection;
let CertBinding::V1_2(CertBindingV1_2 {
server_ephemeral_key,
..
}) = server_cert_data.binding
else {
panic!("expected v1.2 binding data");
};
let mut provider = CryptoProvider::default();
match signature_alg {
SignatureAlgId::SECP256K1 => provider.signer.set_secp256k1(&[42u8; 32]).unwrap(),
SignatureAlgId::SECP256K1ETH => provider.signer.set_secp256k1eth(&[43u8; 32]).unwrap(),
SignatureAlgId::SECP256R1 => provider.signer.set_secp256r1(&[44u8; 32]).unwrap(),
_ => unimplemented!(),
};
let attestation_config = AttestationConfig::builder()
.supported_signature_algs([signature_alg])
.build()
.unwrap();
let mut attestation_builder = Attestation::builder(&attestation_config)
.accept_request(request)
.unwrap();
attestation_builder
.connection_info(connection_info)
.server_ephemeral_key(server_ephemeral_key)
.transcript_commitments(transcript_commitments.to_vec());
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
}

View File

@@ -0,0 +1,19 @@
use tlsn_core::hash::{Hash, HashAlgId, HashAlgorithm};
use crate::serialize::{CanonicalSerialize, DomainSeparator};
pub(crate) const DEFAULT_SUPPORTED_HASH_ALGS: &[HashAlgId] =
&[HashAlgId::SHA256, HashAlgId::BLAKE3, HashAlgId::KECCAK256];
pub(crate) trait HashAlgorithmExt: HashAlgorithm {
#[allow(dead_code)]
fn hash_canonical<T: CanonicalSerialize>(&self, data: &T) -> Hash {
self.hash(&data.serialize())
}
fn hash_separated<T: DomainSeparator + CanonicalSerialize>(&self, data: &T) -> Hash {
self.hash_prefixed(data.domain(), &data.serialize())
}
}
impl<T: HashAlgorithm + ?Sized> HashAlgorithmExt for T {}

View File

@@ -1,4 +1,28 @@
//! Attestation types.
//! TLSNotary attestation types.
//!
//! # Introduction
//!
//! This library provides core functionality for TLSNotary **attestations**.
//!
//! Once the TLS commitment protocol has been completed the Prover holds a
//! collection of commitments pertaining to the TLS connection. Most
//! importantly, the Prover is committed to the
//! [`ServerName`](tlsn_core::connection::ServerName),
//! and the [`Transcript`](tlsn_core::transcript::Transcript) of application
//! data. Subsequently, the Prover can request an [`Attestation`] from the
//! Notary who will include the commitments as well as any additional
//! information which may be useful to an attestation Verifier.
//!
//! Holding an attestation, the Prover can construct a
//! [`Presentation`](crate::presentation::Presentation) which facilitates
//! selectively disclosing various aspects of the TLS connection to a Verifier.
//! If the Verifier trusts the Notary, or more specifically the verifying key of
//! the attestation, then the Verifier can trust the authenticity of the
//! information disclosed in the presentation.
//!
//! **Be sure to check out the various submodules for more information.**
//!
//! # Structure
//!
//! An attestation is a cryptographically signed document issued by a Notary who
//! witnessed a TLS connection. It contains various fields which can be used to
@@ -24,37 +48,194 @@
//! extensions](crate::request::RequestConfigBuilder::extension)
//! to their attestation request, provided that the Notary supports them
//! (disallowed by default). A Notary may also be configured to
//! [validate](crate::attestation::AttestationConfigBuilder::extension_validator)
//! [validate](crate::AttestationConfigBuilder::extension_validator)
//! any extensions requested by a Prover using custom application logic.
//! Additionally, a Notary may
//! [include](crate::attestation::AttestationBuilder::extension)
//! [include](crate::AttestationBuilder::extension)
//! their own extensions.
//!
//! # Committing to the transcript
//!
//! The TLS commitment protocol produces commitments to the entire transcript of
//! application data. However, we may want to disclose only a subset of the data
//! in a presentation. Prior to attestation, the Prover has the opportunity to
//! slice and dice the commitments into smaller sections which can be
//! selectively disclosed. Additionally, the Prover may want to use different
//! commitment schemes depending on the context they expect to disclose.
//!
//! The primary API for this process is the
//! [`TranscriptCommitConfigBuilder`](tlsn_core::transcript::TranscriptCommitConfigBuilder)
//! which is used to build up a configuration.
//!
//! ```no_run
//! # use tlsn_core::transcript::{TranscriptCommitConfigBuilder, Transcript, Direction};
//! # use tlsn_core::hash::HashAlgId;
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
//! # let transcript: Transcript = unimplemented!();
//! let (sent_len, recv_len) = transcript.len();
//!
//! // Create a new configuration builder.
//! let mut builder = TranscriptCommitConfigBuilder::new(&transcript);
//!
//! // Specify all the transcript commitments we want to make.
//! builder
//! // Use BLAKE3 for encoding commitments.
//! .encoding_hash_alg(HashAlgId::BLAKE3)
//! // Commit to all sent data.
//! .commit_sent(&(0..sent_len))?
//! // Commit to the first 10 bytes of sent data.
//! .commit_sent(&(0..10))?
//! // Skip some bytes so it can be omitted in the presentation.
//! .commit_sent(&(20..sent_len))?
//! // Commit to all received data.
//! .commit_recv(&(0..recv_len))?;
//!
//! let config = builder.build()?;
//! # Ok(())
//! # }
//! ```
//!
//! # Requesting an attestation
//!
//! The first step in the attestation protocol is for the Prover to make a
//! [`Request`](crate::request::Request), which can be configured using the
//! associated [builder](crate::request::RequestConfigBuilder). With it the
//! Prover can configure some of the details of the attestation, such as which
//! cryptographic algorithms are used (if the Notary supports them).
//!
//! The Prover may also request for extensions to be added to the attestation,
//! see [here](#extensions) for more information.
//!
//! Upon being issued an attestation, the Prover will also hold a corresponding
//! [`Secrets`] which contains all private information. This pair can be stored
//! and used later to construct a
//! [`Presentation`](crate::presentation::Presentation), [see
//! below](#constructing-a-presentation).
//!
//! # Issuing an attestation
//!
//! Upon receiving a request, the Notary can issue an [`Attestation`] which can
//! be configured using the associated
//! [builder](crate::AttestationConfigBuilder).
//!
//! The Notary's [`CryptoProvider`] must be configured with an appropriate
//! signing key for attestations. See
//! [`SignerProvider`](crate::signing::SignerProvider) for more information.
//!
//! # Constructing a presentation
//!
//! A Prover can use an [`Attestation`] and the corresponding [`Secrets`] to
//! construct a verifiable [`Presentation`](crate::presentation::Presentation).
//!
//! ```no_run
//! # use tlsn_attestation::{Attestation, CryptoProvider, Secrets, presentation::Presentation};
//! # use tlsn_core::transcript::{TranscriptCommitmentKind, Direction};
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
//! # let attestation: Attestation = unimplemented!();
//! # let secrets: Secrets = unimplemented!();
//! # let crypto_provider: CryptoProvider = unimplemented!();
//! let (_sent_len, recv_len) = secrets.transcript().len();
//!
//! // First, we decide which application data we would like to disclose.
//! let mut builder = secrets.transcript_proof_builder();
//!
//! builder
//! // Use transcript encoding commitments.
//! .commitment_kinds(&[TranscriptCommitmentKind::Encoding])
//! // Disclose the first 10 bytes of the sent data.
//! .reveal(&(0..10), Direction::Sent)?
//! // Disclose all of the received data.
//! .reveal(&(0..recv_len), Direction::Received)?;
//!
//! let transcript_proof = builder.build()?;
//!
//! // Most cases we will also disclose the server identity.
//! let identity_proof = secrets.identity_proof();
//!
//! // Now we can construct the presentation.
//! let mut builder = attestation.presentation_builder(&crypto_provider);
//!
//! builder
//! .identity_proof(identity_proof)
//! .transcript_proof(transcript_proof);
//!
//! // Finally, we build the presentation. Send it to a verifier!
//! let presentation: Presentation = builder.build()?;
//! # Ok(())
//! # }
//! ```
//!
//! # Verifying a presentation
//!
//! Verifying a presentation is as simple as checking the verifier trusts the
//! verifying key then calling
//! [`Presentation::verify`](crate::presentation::Presentation::verify).
//!
//! ```no_run
//! # use tlsn_attestation::{CryptoProvider, presentation::{Presentation, PresentationOutput}, signing::VerifyingKey};
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
//! # let presentation: Presentation = unimplemented!();
//! # let trusted_key: VerifyingKey = unimplemented!();
//! # let crypto_provider: CryptoProvider = unimplemented!();
//! // Assert that we trust the verifying key.
//! assert_eq!(presentation.verifying_key(), &trusted_key);
//!
//! let PresentationOutput {
//! attestation,
//! server_name,
//! connection_info,
//! transcript,
//! ..
//! } = presentation.verify(&crypto_provider)?;
//! # Ok(())
//! # }
//! ```
#![deny(missing_docs, unreachable_pub, unused_must_use)]
#![deny(clippy::all)]
#![forbid(unsafe_code)]
mod builder;
mod config;
pub mod connection;
mod extension;
#[cfg(any(test, feature = "fixtures"))]
pub mod fixtures;
pub(crate) mod hash;
pub mod presentation;
mod proof;
mod provider;
pub mod request;
mod secrets;
pub(crate) mod serialize;
pub mod signing;
use std::fmt;
use rand::distr::{Distribution, StandardUniform};
use serde::{Deserialize, Serialize};
use crate::{
connection::{ConnectionInfo, ServerCertCommitment, ServerEphemKey},
hash::{impl_domain_separator, Hash, HashAlgorithm, HashAlgorithmExt, TypedHash},
use tlsn_core::{
connection::{ConnectionInfo, ServerEphemKey},
hash::{Hash, HashAlgorithm, TypedHash},
merkle::MerkleTree,
transcript::{TranscriptCommitment, encoding::EncoderSecret},
};
use crate::{
connection::ServerCertCommitment,
hash::HashAlgorithmExt,
presentation::PresentationBuilder,
serialize::impl_domain_separator,
signing::{Signature, VerifyingKey},
transcript::TranscriptCommitment,
CryptoProvider,
};
pub use builder::{AttestationBuilder, AttestationBuilderError};
pub use config::{AttestationConfig, AttestationConfigBuilder, AttestationConfigError};
pub use extension::{Extension, InvalidExtension};
pub use proof::{AttestationError, AttestationProof};
pub use provider::CryptoProvider;
pub use secrets::Secrets;
/// Current version of attestations.
pub const VERSION: Version = Version(0);
@@ -127,8 +308,6 @@ pub enum FieldKind {
}
/// Attestation header.
///
/// See [module level documentation](crate::attestation) for more information.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Header {
/// An identifier for the attestation.
@@ -142,14 +321,13 @@ pub struct Header {
impl_domain_separator!(Header);
/// Attestation body.
///
/// See [module level documentation](crate::attestation) for more information.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Body {
verifying_key: Field<VerifyingKey>,
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>>,
}
@@ -195,6 +373,7 @@ impl Body {
connection_info: conn_info,
server_ephemeral_key,
cert_commitment,
encoder_secret,
extensions,
transcript_commitments,
} = self;
@@ -212,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)));
}
@@ -246,8 +432,6 @@ impl Body {
}
/// An attestation document.
///
/// See [module level documentation](crate::attestation) for more information.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Attestation {
/// The signature of the attestation.

View File

@@ -26,12 +26,15 @@ use std::fmt;
use serde::{Deserialize, Serialize};
use crate::{
attestation::{Attestation, AttestationError, AttestationProof, Extension},
connection::{ConnectionInfo, ServerIdentityProof, ServerIdentityProofError, ServerName},
signing::VerifyingKey,
use tlsn_core::{
connection::{ConnectionInfo, ServerName},
transcript::{PartialTranscript, TranscriptProof, TranscriptProofError},
CryptoProvider,
};
use crate::{
Attestation, AttestationError, AttestationProof, CryptoProvider, Extension,
connection::{ServerIdentityProof, ServerIdentityProofError},
signing::VerifyingKey,
};
/// A verifiable presentation.
@@ -86,8 +89,13 @@ impl Presentation {
let transcript = transcript
.map(|transcript| {
transcript.verify_with_provider(
provider,
&provider.hash,
&attestation.body.connection_info().transcript_length,
attestation
.body
.encoder_secret
.as_ref()
.map(|field| &field.data),
attestation.body.transcript_commitments(),
)
})
@@ -186,7 +194,7 @@ impl fmt::Display for PresentationBuilderError {
}
if let Some(source) = &self.source {
write!(f, " caused by: {}", source)?;
write!(f, " caused by: {source}")?;
}
Ok(())
@@ -227,7 +235,7 @@ impl fmt::Display for PresentationError {
}
if let Some(source) = &self.source {
write!(f, " caused by: {}", source)?;
write!(f, " caused by: {source}")?;
}
Ok(())

View File

@@ -2,13 +2,15 @@ use std::fmt;
use serde::{Deserialize, Serialize};
use crate::{
attestation::{Attestation, Body, Header},
use tlsn_core::{
hash::HashAlgorithm,
merkle::{MerkleProof, MerkleTree},
};
use crate::{
Attestation, Body, CryptoProvider, Header,
serialize::CanonicalSerialize,
signing::{Signature, VerifyingKey},
CryptoProvider,
};
/// Proof of an attestation.
@@ -165,7 +167,7 @@ impl fmt::Display for AttestationError {
}
if let Some(source) = &self.source {
write!(f, " caused by: {}", source)?;
write!(f, " caused by: {source}")?;
}
Ok(())

View File

@@ -1,12 +1,6 @@
use tls_core::{
anchors::{OwnedTrustAnchor, RootCertStore},
verify::WebPkiVerifier,
};
use tlsn_core::{hash::HashProvider, webpki::ServerCertVerifier};
use crate::{
hash::HashProvider,
signing::{SignatureVerifierProvider, SignerProvider},
};
use crate::signing::{SignatureVerifierProvider, SignerProvider};
/// Cryptography provider.
///
@@ -17,7 +11,7 @@ use crate::{
/// implementations.
///
/// Algorithms are uniquely identified using an 8-bit ID, eg.
/// [`HashAlgId`](crate::hash::HashAlgId), half of which is reserved for the
/// [`HashAlgId`](tlsn_core::hash::HashAlgId), half of which is reserved for the
/// officially supported algorithms. If you think that a new algorithm should be
/// added to the official set, please open an issue. Beware that other parties
/// may assign different algorithms to the same ID as you, and we make no effort
@@ -30,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.
@@ -47,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)
}

View File

@@ -18,11 +18,11 @@ mod config;
use serde::{Deserialize, Serialize};
use tlsn_core::hash::HashAlgId;
use crate::{
attestation::{Attestation, Extension},
connection::ServerCertCommitment,
hash::HashAlgId,
signing::SignatureAlgId,
Attestation, CryptoProvider, Extension, connection::ServerCertCommitment,
serialize::CanonicalSerialize, signing::SignatureAlgId,
};
pub use builder::{RequestBuilder, RequestBuilderError};
@@ -39,64 +39,124 @@ 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 {
use tlsn_core::{
connection::TranscriptLength,
fixtures::{ConnectionFixture, encoding_provider},
hash::{Blake3, HashAlgId},
transcript::Transcript,
};
use tlsn_data_fixtures::http::{request::GET_WITH_HEADER, response::OK_JSON};
use crate::{
connection::{ServerCertOpening, TranscriptLength},
fixtures::{
attestation_fixture, encoding_provider, request_fixture, ConnectionFixture,
RequestFixture,
},
hash::{Blake3, HashAlgId},
signing::SignatureAlgId,
transcript::Transcript,
CryptoProvider,
connection::ServerCertOpening,
fixtures::{RequestFixture, attestation_fixture, custom_provider_fixture, request_fixture},
request::{AttestationValidationError, ErrorKind},
signing::SignatureAlgId,
};
#[test]
@@ -115,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]
@@ -136,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());
}
@@ -158,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())
}
@@ -186,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,
..
})
))
}
}

View File

@@ -1,16 +1,19 @@
use crate::{
connection::{ServerCertData, ServerCertOpening, ServerName},
request::{Request, RequestConfig},
secrets::Secrets,
use tlsn_core::{
connection::{HandshakeData, ServerName},
transcript::{Transcript, TranscriptCommitment, TranscriptSecret},
CryptoProvider,
};
use crate::{
CryptoProvider, Secrets,
connection::ServerCertOpening,
request::{Request, RequestConfig},
};
/// Builder for [`Request`].
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>,
@@ -22,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(),
@@ -35,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
}
@@ -66,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,

View File

@@ -1,7 +1,6 @@
use crate::{
attestation::Extension, hash::HashAlgId, signing::SignatureAlgId,
transcript::TranscriptCommitConfig,
};
use tlsn_core::{hash::HashAlgId, transcript::TranscriptCommitConfig};
use crate::{Extension, signing::SignatureAlgId};
/// Request configuration.
#[derive(Debug, Clone)]

View File

@@ -1,11 +1,13 @@
use serde::{Deserialize, Serialize};
use crate::{
connection::{ServerCertOpening, ServerIdentityProof, ServerName},
use tlsn_core::{
connection::ServerName,
transcript::{Transcript, TranscriptCommitment, TranscriptProofBuilder, TranscriptSecret},
};
/// Secret data of an [`Attestation`](crate::attestation::Attestation).
use crate::connection::{ServerCertOpening, ServerIdentityProof};
/// Secret data of an [`Attestation`](crate::Attestation).
#[derive(Clone, Serialize, Deserialize)]
pub struct Secrets {
pub(crate) server_name: ServerName,

View File

@@ -0,0 +1,54 @@
/// Canonical serialization of TLSNotary types.
///
/// This trait is used to serialize types into a canonical byte representation.
pub(crate) trait CanonicalSerialize {
/// Serializes the type.
fn serialize(&self) -> Vec<u8>;
}
impl<T> CanonicalSerialize for T
where
T: serde::Serialize,
{
fn serialize(&self) -> Vec<u8> {
// For now we use BCS for serialization. In future releases we will want to
// consider this further, particularly with respect to EVM compatibility.
bcs::to_bytes(self).unwrap()
}
}
/// A type with a domain separator which is used during hashing to mitigate type
/// confusion attacks.
pub(crate) trait DomainSeparator {
/// Returns the domain separator for the type.
fn domain(&self) -> &[u8];
}
macro_rules! impl_domain_separator {
($type:ty) => {
impl $crate::serialize::DomainSeparator for $type {
fn domain(&self) -> &[u8] {
use std::sync::LazyLock;
// Computes a 16 byte hash of the type's name to use as a domain separator.
static DOMAIN: LazyLock<[u8; 16]> = LazyLock::new(|| {
let domain: [u8; 32] = blake3::hash(stringify!($type).as_bytes()).into();
domain[..16].try_into().unwrap()
});
&*DOMAIN
}
}
};
}
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::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);

View File

@@ -4,7 +4,7 @@ use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use crate::hash::impl_domain_separator;
use crate::serialize::impl_domain_separator;
/// Key algorithm identifier.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
@@ -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 {
@@ -242,8 +258,8 @@ mod secp256k1 {
use std::sync::{Arc, Mutex};
use k256::ecdsa::{
signature::{SignerMut, Verifier},
Signature as Secp256K1Signature, SigningKey,
signature::{SignerMut, Verifier},
};
use super::*;
@@ -318,8 +334,8 @@ mod secp256r1 {
use std::sync::{Arc, Mutex};
use p256::ecdsa::{
signature::{SignerMut, Verifier},
Signature as Secp256R1Signature, SigningKey,
signature::{SignerMut, Verifier},
};
use super::*;
@@ -394,7 +410,7 @@ mod secp256k1eth {
use std::sync::{Arc, Mutex};
use k256::ecdsa::{
signature::hazmat::PrehashVerifier, Signature as Secp256K1Signature, SigningKey,
Signature as Secp256K1Signature, SigningKey, signature::hazmat::PrehashVerifier,
};
use tiny_keccak::{Hasher, Keccak};
@@ -437,8 +453,7 @@ mod secp256k1eth {
// Based on Ethereum Yellow Paper Appendix F, only values 0 and 1 are valid.
if recid > 1 {
return Err(SignatureError(format!(
"expected recovery id 0 or 1, got {:?}",
recid
"expected recovery id 0 or 1, got {recid:?}"
)));
}
// `ecrecover` expects that 0 and 1 are mapped to 27 and 28.

View File

@@ -1,17 +1,18 @@
use tlsn_core::{
attestation::{Attestation, AttestationConfig},
connection::{HandshakeData, HandshakeDataV1_2},
fixtures::{self, encoder_secret, ConnectionFixture},
hash::Blake3,
use tlsn_attestation::{
Attestation, AttestationConfig, CryptoProvider,
presentation::PresentationOutput,
request::{Request, RequestConfig},
signing::SignatureAlgId,
};
use tlsn_core::{
connection::{CertBinding, CertBindingV1_2},
fixtures::{self, ConnectionFixture, encoder_secret},
hash::Blake3,
transcript::{
encoding::{EncodingCommitment, EncodingTree},
Direction, Transcript, TranscriptCommitConfigBuilder, TranscriptCommitment,
TranscriptSecret,
encoding::{EncodingCommitment, EncodingTree},
},
CryptoProvider,
};
use tlsn_data_fixtures::http::{request::GET_WITH_HEADER, response::OK_JSON};
@@ -35,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!()
};
@@ -63,7 +64,6 @@ fn test_api() {
let encoding_commitment = EncodingCommitment {
root: encoding_tree.root(),
secret: encoder_secret(),
};
let request_config = RequestConfig::default();
@@ -71,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)],
@@ -95,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();

View File

@@ -1,74 +0,0 @@
[package]
edition = "2021"
name = "tlsn-benches"
publish = false
version = "0.0.0"
[lints]
workspace = true
[features]
default = []
# Enables benchmarks in the browser.
browser-bench = ["tlsn-benches-browser-native"]
[dependencies]
mpz-common = { workspace = true }
mpz-core = { workspace = true }
mpz-garble = { workspace = true }
mpz-ot = { workspace = true, features = ["ideal"] }
tlsn-benches-library = { workspace = true }
tlsn-benches-browser-native = { workspace = true, optional = true }
tlsn-common = { workspace = true }
tlsn-core = { workspace = true }
tlsn-hmac-sha256 = { workspace = true }
tlsn-prover = { workspace = true }
tlsn-server-fixture = { workspace = true }
tlsn-server-fixture-certs = { workspace = true }
tlsn-tls-core = { workspace = true }
tlsn-verifier = { workspace = true }
anyhow = { workspace = true }
async-trait = { workspace = true }
charming = { version = "0.3.1", features = ["ssr"] }
csv = "1.3.0"
dhat = { version = "0.3.3" }
env_logger = { version = "0.6.0", default-features = false }
futures = { workspace = true }
serde = { workspace = true }
tokio = { workspace = true, features = [
"rt",
"rt-multi-thread",
"macros",
"net",
"io-std",
"fs",
] }
tokio-util = { workspace = true }
toml = "0.8.11"
tracing-subscriber = { workspace = true, features = ["env-filter"] }
rand = { workspace = true }
[[bin]]
name = "bench"
path = "bin/bench.rs"
[[bin]]
name = "prover"
path = "bin/prover.rs"
[[bin]]
name = "prover-memory"
path = "bin/prover_memory.rs"
[[bin]]
name = "verifier"
path = "bin/verifier.rs"
[[bin]]
name = "verifier-memory"
path = "bin/verifier_memory.rs"
[[bin]]
name = "plot"
path = "bin/plot.rs"

View File

@@ -1,53 +0,0 @@
# TLSNotary bench utilities
This crate provides utilities for benchmarking protocol performance under various network conditions and usage patterns.
As the protocol is mostly IO bound, it's important to track how it performs in low bandwidth and/or high latency environments. To do this we set up temporary network namespaces and add virtual ethernet interfaces which we can control using the linux `tc` (Traffic Control) utility.
## Configuration
See the `bench.toml` file for benchmark configurations.
## Preliminaries
To run the benchmarks you will need `iproute2` installed, eg:
```sh
sudo apt-get install iproute2 -y
```
## Running benches
Running the benches requires root privileges because they will set up virtual interfaces. The script is designed to fully clean up when the benches are done, but run them at your own risk.
#### Native benches
Make sure you're in the `crates/benches/` directory, build the binaries, and then run the script:
```sh
cd binary
cargo build --release
sudo ./bench.sh
```
#### Browser benches
(Note, we recommend running browser benches inside a docker container (see docker.md) to avoid
facing incompatibility issues observed in the latest versions of Chrome.)
With a Chrome browser installed on your system, make sure you're in the `crates/benches/`
directory, build the wasm module, build the binaries, and then run the script:
```sh
cd browser/wasm
wasm-pack build --release --target web
cd ../../binary
cargo build --release --features browser-bench
sudo ./bench.sh
```
## Metrics
After you run the benches you will see a `metrics.csv` file in the working directory. It will be owned by `root`, so you probably want to run
```sh
sudo chown $USER metrics.csv
```

View File

@@ -1,13 +0,0 @@
#! /bin/bash
# Check if we are running as root.
if [ "$EUID" -ne 0 ]; then
echo "This script must be run as root"
exit
fi
# Run the benchmark binary.
../../../target/release/bench
# Plot the results.
../../../target/release/plot metrics.csv

View File

@@ -1,45 +0,0 @@
[[benches]]
name = "latency"
upload = 250
upload-delay = [10, 25, 50]
download = 250
download-delay = [10, 25, 50]
upload-size = 1024
download-size = 4096
defer-decryption = true
memory-profile = false
[[benches]]
name = "download_bandwidth"
upload = 250
upload-delay = 25
download = [10, 25, 50, 100, 250]
download-delay = 25
upload-size = 1024
download-size = 4096
defer-decryption = true
memory-profile = false
[[benches]]
name = "upload_bandwidth"
upload = [10, 25, 50, 100, 250]
upload-delay = 25
download = 250
download-delay = 25
upload-size = 1024
download-size = 4096
defer-decryption = [false, true]
memory-profile = false
[[benches]]
name = "download_volume"
upload = 250
upload-delay = 25
download = 250
download-delay = 25
upload-size = 1024
# It was observed that setting download-size > 30K causes browser errors that need to
# be investigated.
download-size = [1024, 4096, 16384]
defer-decryption = true
memory-profile = true

View File

@@ -1,56 +0,0 @@
FROM rust AS builder
WORKDIR /usr/src/tlsn
COPY . .
ARG BENCH_TYPE=native
RUN \
rustup update; \
if [ "$BENCH_TYPE" = "browser" ]; then \
# ring's build script needs clang.
apt update && apt install -y clang; \
rustup install nightly; \
rustup component add rust-src --toolchain nightly; \
cargo install wasm-pack; \
cd crates/benches/browser/wasm; \
wasm-pack build --release --target web; \
cd ../../binary; \
cargo build --release --features browser-bench; \
else \
cd crates/benches/binary; \
cargo build --release; \
fi
FROM debian:latest
ARG BENCH_TYPE=native
RUN apt update && apt upgrade -y && apt install -y --no-install-recommends \
iproute2 \
sudo
RUN \
if [ "$BENCH_TYPE" = "browser" ]; then \
# Using Chromium since Chrome for Linux is not available on ARM.
apt install -y chromium; \
fi
RUN apt clean && rm -rf /var/lib/apt/lists/*
COPY --from=builder \
["/usr/src/tlsn/target/release/bench", \
"/usr/src/tlsn/target/release/prover", \
"/usr/src/tlsn/target/release/prover-memory", \
"/usr/src/tlsn/target/release/verifier", \
"/usr/src/tlsn/target/release/verifier-memory", \
"/usr/src/tlsn/target/release/plot", \
"/usr/local/bin/"]
ENV PROVER_PATH="/usr/local/bin/prover"
ENV VERIFIER_PATH="/usr/local/bin/verifier"
ENV PROVER_MEMORY_PATH="/usr/local/bin/prover-memory"
ENV VERIFIER_MEMORY_PATH="/usr/local/bin/verifier-memory"
VOLUME [ "/benches" ]
WORKDIR "/benches"
CMD ["/bin/bash", "-c", "bench && bench --memory-profiling && plot /benches/metrics.csv && cat /benches/metrics.csv"]

View File

@@ -1,62 +0,0 @@
use std::{env, process::Command, thread, time::Duration};
use tlsn_benches::{clean_up, set_up};
fn main() {
let args: Vec<String> = env::args().collect();
let is_memory_profiling = args.contains(&"--memory-profiling".to_string());
let (prover_path, verifier_path) = if is_memory_profiling {
(
std::env::var("PROVER_MEMORY_PATH")
.unwrap_or_else(|_| "../../../target/release/prover-memory".to_string()),
std::env::var("VERIFIER_MEMORY_PATH")
.unwrap_or_else(|_| "../../../target/release/verifier-memory".to_string()),
)
} else {
(
std::env::var("PROVER_PATH")
.unwrap_or_else(|_| "../../../target/release/prover".to_string()),
std::env::var("VERIFIER_PATH")
.unwrap_or_else(|_| "../../../target/release/verifier".to_string()),
)
};
if let Err(e) = set_up() {
println!("Error setting up: {}", e);
clean_up();
}
// Run prover and verifier binaries in parallel.
let Ok(mut verifier) = Command::new("ip")
.arg("netns")
.arg("exec")
.arg("verifier-ns")
.arg(verifier_path)
.spawn()
else {
println!("Failed to start verifier");
return clean_up();
};
// Allow the verifier some time to start listening before the prover attempts to
// connect.
thread::sleep(Duration::from_secs(1));
let Ok(mut prover) = Command::new("ip")
.arg("netns")
.arg("exec")
.arg("prover-ns")
.arg(prover_path)
.spawn()
else {
println!("Failed to start prover");
return clean_up();
};
// Wait for both to finish.
_ = prover.wait();
_ = verifier.wait();
clean_up();
}

View File

@@ -1,248 +0,0 @@
use tlsn_benches::metrics::Metrics;
use charming::{
component::{
Axis, DataView, Feature, Legend, Restore, SaveAsImage, Title, Toolbox, ToolboxDataZoom,
},
element::{NameLocation, Orient, Tooltip, Trigger},
series::{Line, Scatter},
theme::Theme,
Chart, HtmlRenderer,
};
use csv::Reader;
const THEME: Theme = Theme::Default;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let csv_file = std::env::args()
.nth(1)
.expect("Usage: plot <path_to_csv_file>");
let mut rdr = Reader::from_path(csv_file)?;
// Prepare data for plotting.
let all_data: Vec<Metrics> = rdr
.deserialize::<Metrics>()
.collect::<Result<Vec<_>, _>>()?; // Attempt to collect all results, return an error if any fail.
let _chart = runtime_vs_latency(&all_data)?;
let _chart = runtime_vs_bandwidth(&all_data)?;
// Memory profiling is not compatible with browser benches.
if cfg!(not(feature = "browser-bench")) {
let _chart = download_size_vs_memory(&all_data)?;
}
Ok(())
}
fn download_size_vs_memory(all_data: &[Metrics]) -> Result<Chart, Box<dyn std::error::Error>> {
const TITLE: &str = "Download Size vs Memory";
let prover_kind: String = all_data
.first()
.map(|s| s.kind.clone().into())
.unwrap_or_default();
let data: Vec<Vec<f32>> = all_data
.iter()
.filter(|record| record.name == "download_volume" && record.heap_max_bytes.is_some())
.map(|record| {
vec![
record.download_size as f32,
record.heap_max_bytes.unwrap() as f32 / 1024.0 / 1024.0,
]
})
.collect();
// https://github.com/yuankunzhang/charming
let chart = Chart::new()
.title(
Title::new()
.text(TITLE)
.subtext(format!("{} Prover", prover_kind)),
)
.tooltip(Tooltip::new().trigger(Trigger::Axis))
.legend(Legend::new().orient(Orient::Vertical))
.toolbox(
Toolbox::new().show(true).feature(
Feature::new()
.save_as_image(SaveAsImage::new())
.restore(Restore::new())
.data_zoom(ToolboxDataZoom::new().y_axis_index("none"))
.data_view(DataView::new().read_only(false)),
),
)
.x_axis(
Axis::new()
.scale(true)
.name("Download Size (bytes)")
.name_gap(30)
.name_location(NameLocation::Center),
)
.y_axis(
Axis::new()
.scale(true)
.name("Heap Memory (Mbytes)")
.name_gap(40)
.name_location(NameLocation::Middle),
)
.series(
Scatter::new()
.name("Allocated Heap Memory")
.symbol_size(10)
.data(data),
);
// Save the chart as HTML file.
HtmlRenderer::new(TITLE, 1000, 800)
.theme(THEME)
.save(&chart, "download_size_vs_memory.html")
.unwrap();
Ok(chart)
}
fn runtime_vs_latency(all_data: &[Metrics]) -> Result<Chart, Box<dyn std::error::Error>> {
const TITLE: &str = "Runtime vs Latency";
let prover_kind: String = all_data
.first()
.map(|s| s.kind.clone().into())
.unwrap_or_default();
let data: Vec<Vec<f32>> = all_data
.iter()
.filter(|record| record.name == "latency")
.map(|record| {
let total_delay = record.upload_delay + record.download_delay; // Calculate the sum of upload and download delays.
vec![total_delay as f32, record.runtime as f32]
})
.collect();
// https://github.com/yuankunzhang/charming
let chart = Chart::new()
.title(
Title::new()
.text(TITLE)
.subtext(format!("{} Prover", prover_kind)),
)
.tooltip(Tooltip::new().trigger(Trigger::Axis))
.legend(Legend::new().orient(Orient::Vertical))
.toolbox(
Toolbox::new().show(true).feature(
Feature::new()
.save_as_image(SaveAsImage::new())
.restore(Restore::new())
.data_zoom(ToolboxDataZoom::new().y_axis_index("none"))
.data_view(DataView::new().read_only(false)),
),
)
.x_axis(
Axis::new()
.scale(true)
.name("Upload + Download Latency (ms)")
.name_location(NameLocation::Center),
)
.y_axis(
Axis::new()
.scale(true)
.name("Runtime (s)")
.name_location(NameLocation::Middle),
)
.series(
Scatter::new()
.name("Combined Latency")
.symbol_size(10)
.data(data),
);
// Save the chart as HTML file.
HtmlRenderer::new(TITLE, 1000, 800)
.theme(THEME)
.save(&chart, "runtime_vs_latency.html")
.unwrap();
Ok(chart)
}
fn runtime_vs_bandwidth(all_data: &[Metrics]) -> Result<Chart, Box<dyn std::error::Error>> {
const TITLE: &str = "Runtime vs Bandwidth";
let prover_kind: String = all_data
.first()
.map(|s| s.kind.clone().into())
.unwrap_or_default();
let download_data: Vec<Vec<f32>> = all_data
.iter()
.filter(|record| record.name == "download_bandwidth")
.map(|record| vec![record.download as f32, record.runtime as f32])
.collect();
let upload_deferred_data: Vec<Vec<f32>> = all_data
.iter()
.filter(|record| record.name == "upload_bandwidth" && record.defer_decryption)
.map(|record| vec![record.upload as f32, record.runtime as f32])
.collect();
let upload_non_deferred_data: Vec<Vec<f32>> = all_data
.iter()
.filter(|record| record.name == "upload_bandwidth" && !record.defer_decryption)
.map(|record| vec![record.upload as f32, record.runtime as f32])
.collect();
// https://github.com/yuankunzhang/charming
let chart = Chart::new()
.title(
Title::new()
.text(TITLE)
.subtext(format!("{} Prover", prover_kind)),
)
.tooltip(Tooltip::new().trigger(Trigger::Axis))
.legend(Legend::new().orient(Orient::Vertical))
.toolbox(
Toolbox::new().show(true).feature(
Feature::new()
.save_as_image(SaveAsImage::new())
.restore(Restore::new())
.data_zoom(ToolboxDataZoom::new().y_axis_index("none"))
.data_view(DataView::new().read_only(false)),
),
)
.x_axis(
Axis::new()
.scale(true)
.name("Bandwidth (Mbps)")
.name_location(NameLocation::Center),
)
.y_axis(
Axis::new()
.scale(true)
.name("Runtime (s)")
.name_location(NameLocation::Middle),
)
.series(
Line::new()
.name("Download bandwidth")
.symbol_size(10)
.data(download_data),
)
.series(
Line::new()
.name("Upload bandwidth (deferred decryption)")
.symbol_size(10)
.data(upload_deferred_data),
)
.series(
Line::new()
.name("Upload bandwidth")
.symbol_size(10)
.data(upload_non_deferred_data),
);
// Save the chart as HTML file.
HtmlRenderer::new(TITLE, 1000, 800)
.theme(THEME)
.save(&chart, "runtime_vs_bandwidth.html")
.unwrap();
Ok(chart)
}

View File

@@ -1,8 +0,0 @@
//! A Prover without memory profiling.
use tlsn_benches::prover_main::prover_main;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
prover_main(false).await
}

View File

@@ -1,15 +0,0 @@
//! A Prover with memory profiling.
use tlsn_benches::prover_main::prover_main;
#[global_allocator]
static ALLOC: dhat::Alloc = dhat::Alloc;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
if cfg!(feature = "browser-bench") {
// Memory profiling is not compatible with browser benches.
return Ok(());
}
prover_main(true).await
}

View File

@@ -1,8 +0,0 @@
//! A Verifier without memory profiling.
use tlsn_benches::verifier_main::verifier_main;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
verifier_main(false).await
}

View File

@@ -1,15 +0,0 @@
//! A Verifier with memory profiling.
use tlsn_benches::verifier_main::verifier_main;
#[global_allocator]
static ALLOC: dhat::Alloc = dhat::Alloc;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
if cfg!(feature = "browser-bench") {
// Memory profiling is not compatible with browser benches.
return Ok(());
}
verifier_main(true).await
}

View File

@@ -1,13 +0,0 @@
# Run the TLSN benches with Docker
In the root folder of this repository, run:
```
# Change to BENCH_TYPE=browser if you want benchmarks to run in the browser.
docker build -t tlsn-bench . -f ./crates/benches/binary/benches.Dockerfile --build-arg BENCH_TYPE=native
```
Next run the benches with:
```
docker run -it --privileged -v ./crates/benches/binary:/benches tlsn-bench
```
The `--privileged` parameter is required because this test bench needs permission to create networks with certain parameters

View File

@@ -1,123 +0,0 @@
use serde::{Deserialize, Serialize};
#[derive(Deserialize)]
#[serde(untagged)]
pub enum Field<T> {
Single(T),
Multiple(Vec<T>),
}
#[derive(Deserialize)]
pub struct Config {
pub benches: Vec<Bench>,
}
#[derive(Deserialize)]
pub struct Bench {
pub name: String,
pub upload: Field<usize>,
#[serde(rename = "upload-delay")]
pub upload_delay: Field<usize>,
pub download: Field<usize>,
#[serde(rename = "download-delay")]
pub download_delay: Field<usize>,
#[serde(rename = "upload-size")]
pub upload_size: Field<usize>,
#[serde(rename = "download-size")]
pub download_size: Field<usize>,
#[serde(rename = "defer-decryption")]
pub defer_decryption: Field<bool>,
#[serde(rename = "memory-profile")]
pub memory_profile: Field<bool>,
}
impl Bench {
/// Flattens the config into a list of instances
pub fn flatten(self) -> Vec<BenchInstance> {
let mut instances = vec![];
let upload = match self.upload {
Field::Single(u) => vec![u],
Field::Multiple(u) => u,
};
let upload_delay = match self.upload_delay {
Field::Single(u) => vec![u],
Field::Multiple(u) => u,
};
let download = match self.download {
Field::Single(u) => vec![u],
Field::Multiple(u) => u,
};
let download_latency = match self.download_delay {
Field::Single(u) => vec![u],
Field::Multiple(u) => u,
};
let upload_size = match self.upload_size {
Field::Single(u) => vec![u],
Field::Multiple(u) => u,
};
let download_size = match self.download_size {
Field::Single(u) => vec![u],
Field::Multiple(u) => u,
};
let defer_decryption = match self.defer_decryption {
Field::Single(u) => vec![u],
Field::Multiple(u) => u,
};
let memory_profile = match self.memory_profile {
Field::Single(u) => vec![u],
Field::Multiple(u) => u,
};
for u in upload {
for ul in &upload_delay {
for d in &download {
for dl in &download_latency {
for us in &upload_size {
for ds in &download_size {
for dd in &defer_decryption {
for mp in &memory_profile {
instances.push(BenchInstance {
name: self.name.clone(),
upload: u,
upload_delay: *ul,
download: *d,
download_delay: *dl,
upload_size: *us,
download_size: *ds,
defer_decryption: *dd,
memory_profile: *mp,
});
}
}
}
}
}
}
}
}
instances
}
}
#[derive(Debug, Clone, Serialize)]
pub struct BenchInstance {
pub name: String,
pub upload: usize,
pub upload_delay: usize,
pub download: usize,
pub download_delay: usize,
pub upload_size: usize,
pub download_size: usize,
pub defer_decryption: bool,
/// Whether this instance should be used for memory profiling.
pub memory_profile: bool,
}

View File

@@ -1,272 +0,0 @@
pub mod config;
pub mod metrics;
pub mod prover;
pub mod prover_main;
pub mod verifier_main;
use std::{
io,
process::{Command, Stdio},
};
pub const PROVER_NAMESPACE: &str = "prover-ns";
pub const PROVER_INTERFACE: &str = "prover-veth";
pub const PROVER_SUBNET: &str = "10.10.1.0/24";
pub const VERIFIER_NAMESPACE: &str = "verifier-ns";
pub const VERIFIER_INTERFACE: &str = "verifier-veth";
pub const VERIFIER_SUBNET: &str = "10.10.1.1/24";
pub fn set_up() -> io::Result<()> {
// Create network namespaces
create_network_namespace(PROVER_NAMESPACE)?;
create_network_namespace(VERIFIER_NAMESPACE)?;
// Create veth pair and attach to namespaces
create_veth_pair(
PROVER_NAMESPACE,
PROVER_INTERFACE,
VERIFIER_NAMESPACE,
VERIFIER_INTERFACE,
)?;
// Set devices up
set_device_up(PROVER_NAMESPACE, PROVER_INTERFACE)?;
set_device_up(VERIFIER_NAMESPACE, VERIFIER_INTERFACE)?;
// Bring up the loopback interface.
set_device_up(PROVER_NAMESPACE, "lo")?;
set_device_up(VERIFIER_NAMESPACE, "lo")?;
// Assign IPs
assign_ip_to_interface(PROVER_NAMESPACE, PROVER_INTERFACE, PROVER_SUBNET)?;
assign_ip_to_interface(VERIFIER_NAMESPACE, VERIFIER_INTERFACE, VERIFIER_SUBNET)?;
// Set default routes
set_default_route(
PROVER_NAMESPACE,
PROVER_INTERFACE,
PROVER_SUBNET.split('/').next().unwrap(),
)?;
set_default_route(
VERIFIER_NAMESPACE,
VERIFIER_INTERFACE,
VERIFIER_SUBNET.split('/').next().unwrap(),
)?;
Ok(())
}
pub fn clean_up() {
// Delete interface pair
if let Err(e) = Command::new("ip")
.args([
"netns",
"exec",
PROVER_NAMESPACE,
"ip",
"link",
"delete",
PROVER_INTERFACE,
])
.status()
{
println!("Error deleting interface {}: {}", PROVER_INTERFACE, e);
}
// Delete namespaces
if let Err(e) = Command::new("ip")
.args(["netns", "del", PROVER_NAMESPACE])
.status()
{
println!("Error deleting namespace {}: {}", PROVER_NAMESPACE, e);
}
if let Err(e) = Command::new("ip")
.args(["netns", "del", VERIFIER_NAMESPACE])
.status()
{
println!("Error deleting namespace {}: {}", VERIFIER_NAMESPACE, e);
}
}
/// Sets the interface parameters.
///
/// Must be run in the correct namespace.
///
/// # Arguments
///
/// * `egress` - The egress bandwidth in mbps.
/// * `burst` - The burst in mbps.
/// * `delay` - The delay in ms.
pub fn set_interface(interface: &str, egress: usize, burst: usize, delay: usize) -> io::Result<()> {
// Clear rules
let output = Command::new("tc")
.arg("qdisc")
.arg("del")
.arg("dev")
.arg(interface)
.arg("root")
.stdout(Stdio::piped())
.output()?;
if output.stderr == "Error: Cannot delete qdisc with handle of zero.\n".as_bytes() {
// This error is informative, do not log it to stderr.
} else if !output.status.success() {
return Err(io::Error::other("Failed to clear rules"));
}
// Egress
Command::new("tc")
.arg("qdisc")
.arg("add")
.arg("dev")
.arg(interface)
.arg("root")
.arg("handle")
.arg("1:")
.arg("tbf")
.arg("rate")
.arg(format!("{}mbit", egress))
.arg("burst")
.arg(format!("{}mbit", burst))
.arg("latency")
.arg("60s")
.status()?;
// Delay
Command::new("tc")
.arg("qdisc")
.arg("add")
.arg("dev")
.arg(interface)
.arg("parent")
.arg("1:1")
.arg("handle")
.arg("10:")
.arg("netem")
.arg("delay")
.arg(format!("{}ms", delay))
.status()?;
Ok(())
}
/// Create a network namespace with the given name if it does not already exist.
fn create_network_namespace(name: &str) -> io::Result<()> {
// Check if namespace already exists
if Command::new("ip")
.args(["netns", "list"])
.output()?
.stdout
.windows(name.len())
.any(|ns| ns == name.as_bytes())
{
println!("Namespace {} already exists", name);
return Ok(());
} else {
println!("Creating namespace {}", name);
Command::new("ip").args(["netns", "add", name]).status()?;
}
Ok(())
}
fn create_veth_pair(
left_namespace: &str,
left_interface: &str,
right_namespace: &str,
right_interface: &str,
) -> io::Result<()> {
// Check if interfaces are already present in namespaces
if is_interface_present_in_namespace(left_namespace, left_interface)?
|| is_interface_present_in_namespace(right_namespace, right_interface)?
{
println!("Virtual interface already exists.");
return Ok(());
}
// Create veth pair
Command::new("ip")
.args([
"link",
"add",
left_interface,
"type",
"veth",
"peer",
"name",
right_interface,
])
.status()?;
println!(
"Created veth pair {} and {}",
left_interface, right_interface
);
// Attach veth pair to namespaces
attach_interface_to_namespace(left_namespace, left_interface)?;
attach_interface_to_namespace(right_namespace, right_interface)?;
Ok(())
}
fn attach_interface_to_namespace(namespace: &str, interface: &str) -> io::Result<()> {
Command::new("ip")
.args(["link", "set", interface, "netns", namespace])
.status()?;
println!("Attached {} to namespace {}", interface, namespace);
Ok(())
}
fn set_default_route(namespace: &str, interface: &str, ip: &str) -> io::Result<()> {
Command::new("ip")
.args([
"netns", "exec", namespace, "ip", "route", "add", "default", "via", ip, "dev",
interface,
])
.status()?;
println!(
"Set default route for namespace {} ip {} to {}",
namespace, ip, interface
);
Ok(())
}
fn is_interface_present_in_namespace(
namespace: &str,
interface: &str,
) -> Result<bool, std::io::Error> {
Ok(Command::new("ip")
.args([
"netns", "exec", namespace, "ip", "link", "list", "dev", interface,
])
.output()?
.stdout
.windows(interface.len())
.any(|ns| ns == interface.as_bytes()))
}
fn set_device_up(namespace: &str, interface: &str) -> io::Result<()> {
Command::new("ip")
.args([
"netns", "exec", namespace, "ip", "link", "set", interface, "up",
])
.status()?;
Ok(())
}
fn assign_ip_to_interface(namespace: &str, interface: &str, ip: &str) -> io::Result<()> {
Command::new("ip")
.args([
"netns", "exec", namespace, "ip", "addr", "add", ip, "dev", interface,
])
.status()?;
Ok(())
}

View File

@@ -1,31 +0,0 @@
use serde::{Deserialize, Serialize};
use tlsn_benches_library::ProverKind;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Metrics {
pub name: String,
/// The kind of the prover, either native or browser.
pub kind: ProverKind,
/// Upload bandwidth in Mbps.
pub upload: usize,
/// Upload latency in ms.
pub upload_delay: usize,
/// Download bandwidth in Mbps.
pub download: usize,
/// Download latency in ms.
pub download_delay: usize,
/// Total bytes sent to the server.
pub upload_size: usize,
/// Total bytes received from the server.
pub download_size: usize,
/// Whether deferred decryption was used.
pub defer_decryption: bool,
/// The total runtime of the benchmark in seconds.
pub runtime: u64,
/// The total amount of data uploaded to the verifier in bytes.
pub uploaded: u64,
/// The total amount of data downloaded from the verifier in bytes.
pub downloaded: u64,
/// The peak heap memory usage in bytes.
pub heap_max_bytes: Option<usize>,
}

View File

@@ -1,57 +0,0 @@
use std::time::Instant;
use tlsn_benches_library::{run_prover, AsyncIo, ProverKind, ProverTrait};
use async_trait::async_trait;
pub struct NativeProver {
upload_size: usize,
download_size: usize,
defer_decryption: bool,
io: Option<Box<dyn AsyncIo>>,
client_conn: Option<Box<dyn AsyncIo>>,
}
#[async_trait]
impl ProverTrait for NativeProver {
async fn setup(
upload_size: usize,
download_size: usize,
defer_decryption: bool,
io: Box<dyn AsyncIo>,
client_conn: Box<dyn AsyncIo>,
) -> anyhow::Result<Self>
where
Self: Sized,
{
Ok(Self {
upload_size,
download_size,
defer_decryption,
io: Some(io),
client_conn: Some(client_conn),
})
}
async fn run(&mut self) -> anyhow::Result<u64> {
let io = std::mem::take(&mut self.io).unwrap();
let client_conn = std::mem::take(&mut self.client_conn).unwrap();
let start_time = Instant::now();
run_prover(
self.upload_size,
self.download_size,
self.defer_decryption,
io,
client_conn,
)
.await?;
Ok(Instant::now().duration_since(start_time).as_secs())
}
fn kind(&self) -> ProverKind {
ProverKind::Native
}
}

View File

@@ -1,171 +0,0 @@
//! Contains the actual main() function of the prover binary. It is moved here
//! in order to enable cargo to build two prover binaries - with and without
//! memory profiling.
use std::{
fs::metadata,
io::Write,
sync::{
atomic::{AtomicU64, Ordering},
Arc,
},
};
use crate::{
config::{BenchInstance, Config},
metrics::Metrics,
set_interface, PROVER_INTERFACE,
};
use anyhow::Context;
use tlsn_benches_library::{AsyncIo, ProverTrait};
use tlsn_server_fixture::bind;
use csv::WriterBuilder;
use tokio_util::{
compat::TokioAsyncReadCompatExt,
io::{InspectReader, InspectWriter},
};
use tracing_subscriber::{fmt::format::FmtSpan, EnvFilter};
#[cfg(not(feature = "browser-bench"))]
use crate::prover::NativeProver as BenchProver;
#[cfg(feature = "browser-bench")]
use tlsn_benches_browser_native::BrowserProver as BenchProver;
pub async fn prover_main(is_memory_profiling: bool) -> anyhow::Result<()> {
let config_path = std::env::var("CFG").unwrap_or_else(|_| "bench.toml".to_string());
let config: Config = toml::from_str(
&std::fs::read_to_string(config_path).context("failed to read config file")?,
)
.context("failed to parse config")?;
tracing_subscriber::fmt()
.with_env_filter(EnvFilter::from_default_env())
.with_span_events(FmtSpan::NEW | FmtSpan::CLOSE)
.init();
let ip = std::env::var("VERIFIER_IP").unwrap_or_else(|_| "10.10.1.1".to_string());
let port: u16 = std::env::var("VERIFIER_PORT")
.map(|port| port.parse().expect("port is valid u16"))
.unwrap_or(8000);
let verifier_host = (ip.as_str(), port);
let mut file = std::fs::OpenOptions::new()
.create(true)
.append(true)
.open("metrics.csv")
.context("failed to open metrics file")?;
{
let mut metric_wrt = WriterBuilder::new()
// If file is not empty, assume that the CSV header is already present in the file.
.has_headers(metadata("metrics.csv")?.len() == 0)
.from_writer(&mut file);
for bench in config.benches {
let instances = bench.flatten();
for instance in instances {
if is_memory_profiling && !instance.memory_profile {
continue;
}
println!("{:?}", &instance);
let io = tokio::net::TcpStream::connect(verifier_host)
.await
.context("failed to open tcp connection")?;
metric_wrt.serialize(
run_instance(instance, io, is_memory_profiling)
.await
.context("failed to run instance")?,
)?;
metric_wrt.flush()?;
}
}
}
file.flush()?;
Ok(())
}
async fn run_instance(
instance: BenchInstance,
io: impl AsyncIo,
is_memory_profiling: bool,
) -> anyhow::Result<Metrics> {
let uploaded = Arc::new(AtomicU64::new(0));
let downloaded = Arc::new(AtomicU64::new(0));
let io = InspectWriter::new(
InspectReader::new(io, {
let downloaded = downloaded.clone();
move |data| {
downloaded.fetch_add(data.len() as u64, Ordering::Relaxed);
}
}),
{
let uploaded = uploaded.clone();
move |data| {
uploaded.fetch_add(data.len() as u64, Ordering::Relaxed);
}
},
);
let BenchInstance {
name,
upload,
upload_delay,
download,
download_delay,
upload_size,
download_size,
defer_decryption,
memory_profile,
} = instance.clone();
set_interface(PROVER_INTERFACE, upload, 1, upload_delay)?;
let _profiler = if is_memory_profiling {
assert!(memory_profile, "Instance doesn't have `memory_profile` set");
// Build a testing profiler as it won't output to stderr.
Some(dhat::Profiler::builder().testing().build())
} else {
None
};
let (client_conn, server_conn) = tokio::io::duplex(1 << 16);
tokio::spawn(bind(server_conn.compat()));
let mut prover = BenchProver::setup(
upload_size,
download_size,
defer_decryption,
Box::new(io),
Box::new(client_conn),
)
.await?;
let runtime = prover.run().await?;
let heap_max_bytes = if is_memory_profiling {
Some(dhat::HeapStats::get().max_bytes)
} else {
None
};
Ok(Metrics {
name,
kind: prover.kind(),
upload,
upload_delay,
download,
download_delay,
upload_size,
download_size,
defer_decryption,
runtime,
uploaded: uploaded.load(Ordering::SeqCst),
downloaded: downloaded.load(Ordering::SeqCst),
heap_max_bytes,
})
}

View File

@@ -1,128 +0,0 @@
//! Contains the actual main() function of the verifier binary. It is moved here
//! in order to enable cargo to build two verifier binaries - with and without
//! memory profiling.
use crate::{
config::{BenchInstance, Config},
set_interface, VERIFIER_INTERFACE,
};
use tls_core::verify::WebPkiVerifier;
use tlsn_common::config::ProtocolConfigValidator;
use tlsn_core::{CryptoProvider, VerifyConfig};
use tlsn_server_fixture_certs::CA_CERT_DER;
use tlsn_verifier::{Verifier, VerifierConfig};
use anyhow::Context;
use tokio::io::{AsyncRead, AsyncWrite};
use tokio_util::compat::TokioAsyncReadCompatExt;
use tracing_subscriber::{fmt::format::FmtSpan, EnvFilter};
pub async fn verifier_main(is_memory_profiling: bool) -> anyhow::Result<()> {
let config_path = std::env::var("CFG").unwrap_or_else(|_| "bench.toml".to_string());
let config: Config = toml::from_str(
&std::fs::read_to_string(config_path).context("failed to read config file")?,
)
.context("failed to parse config")?;
tracing_subscriber::fmt()
.with_env_filter(EnvFilter::from_default_env())
.with_span_events(FmtSpan::NEW | FmtSpan::CLOSE)
.init();
let ip = std::env::var("VERIFIER_IP").unwrap_or_else(|_| "10.10.1.1".to_string());
let port: u16 = std::env::var("VERIFIER_PORT")
.map(|port| port.parse().expect("port is valid u16"))
.unwrap_or(8000);
let host = (ip.as_str(), port);
let listener = tokio::net::TcpListener::bind(host)
.await
.context("failed to bind to port")?;
for bench in config.benches {
for instance in bench.flatten() {
if is_memory_profiling && !instance.memory_profile {
continue;
}
let (io, _) = listener
.accept()
.await
.context("failed to accept connection")?;
run_instance(instance, io, is_memory_profiling)
.await
.context("failed to run instance")?;
}
}
Ok(())
}
async fn run_instance<S: AsyncWrite + AsyncRead + Send + Sync + Unpin + 'static>(
instance: BenchInstance,
io: S,
is_memory_profiling: bool,
) -> anyhow::Result<()> {
let BenchInstance {
download,
download_delay,
upload_size,
download_size,
memory_profile,
..
} = instance;
set_interface(VERIFIER_INTERFACE, download, 1, download_delay)?;
let _profiler = if is_memory_profiling {
assert!(memory_profile, "Instance doesn't have `memory_profile` set");
// Build a testing profiler as it won't output to stderr.
Some(dhat::Profiler::builder().testing().build())
} else {
None
};
let provider = CryptoProvider {
cert: cert_verifier(),
..Default::default()
};
let config_validator = ProtocolConfigValidator::builder()
.max_sent_data(upload_size + 256)
.max_recv_data(download_size + 256)
.build()
.unwrap();
let verifier = Verifier::new(
VerifierConfig::builder()
.protocol_config_validator(config_validator)
.crypto_provider(provider)
.build()?,
);
verifier
.verify(io.compat(), &VerifyConfig::default())
.await?;
println!("verifier done");
if is_memory_profiling {
// XXX: we may want to profile the Verifier's memory usage at a future
// point.
// println!(
// "verifier peak heap memory usage: {}",
// dhat::HeapStats::get().max_bytes
// );
}
Ok(())
}
fn cert_verifier() -> WebPkiVerifier {
let mut root_store = tls_core::anchors::RootCertStore::empty();
root_store
.add(&tls_core::key::Certificate(CA_CERT_DER.to_vec()))
.unwrap();
WebPkiVerifier::new(root_store, None)
}

View File

@@ -1,16 +0,0 @@
[package]
edition = "2021"
name = "tlsn-benches-browser-core"
publish = false
version = "0.0.0"
[lints]
workspace = true
[dependencies]
tlsn-benches-library = { workspace = true }
serio = { workspace = true }
serde = { workspace = true }
tokio-util= { workspace = true, features = ["compat", "io-util"] }

View File

@@ -1,68 +0,0 @@
//! Contains core types shared by the native and the wasm components.
use std::{
io::Error,
pin::Pin,
task::{Context, Poll},
};
use tlsn_benches_library::AsyncIo;
use serio::{
codec::{Bincode, Framed},
Sink, Stream,
};
use tokio_util::codec::LengthDelimitedCodec;
pub mod msg;
/// A sink/stream for serializable types with a framed transport.
pub struct FramedIo {
inner:
serio::Framed<tokio_util::codec::Framed<Box<dyn AsyncIo>, LengthDelimitedCodec>, Bincode>,
}
impl FramedIo {
/// Creates a new `FramedIo` from the given async `io`.
#[allow(clippy::default_constructed_unit_structs)]
pub fn new(io: Box<dyn AsyncIo>) -> Self {
let io = LengthDelimitedCodec::builder().new_framed(io);
Self {
inner: Framed::new(io, Bincode::default()),
}
}
}
impl Sink for FramedIo {
type Error = Error;
fn poll_ready(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Pin::new(&mut self.inner).poll_ready(cx)
}
fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Pin::new(&mut self.inner).poll_close(cx)
}
fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Pin::new(&mut self.inner).poll_flush(cx)
}
fn start_send<Item: serio::Serialize>(
mut self: Pin<&mut Self>,
item: Item,
) -> std::result::Result<(), Self::Error> {
Pin::new(&mut self.inner).start_send(item)
}
}
impl Stream for FramedIo {
type Error = Error;
fn poll_next<Item: serio::Deserialize>(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Option<Result<Item, Error>>> {
Pin::new(&mut self.inner).poll_next(cx)
}
}

View File

@@ -1,17 +0,0 @@
//! Messages exchanged by the native and the wasm components of the browser
//! prover.
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, PartialEq)]
/// The config sent to the wasm component.
pub struct Config {
pub upload_size: usize,
pub download_size: usize,
pub defer_decryption: bool,
}
#[derive(Serialize, Deserialize, PartialEq)]
/// Sent by the wasm component when proving process is finished. Contains total
/// runtime in seconds.
pub struct Runtime(pub u64);

View File

@@ -1,25 +0,0 @@
[package]
edition = "2021"
name = "tlsn-benches-browser-native"
publish = false
version = "0.0.0"
[lints]
workspace = true
[dependencies]
tlsn-benches-browser-core = { workspace = true }
tlsn-benches-library = { workspace = true }
serio = { workspace = true }
websocket-relay = { workspace = true }
anyhow = { workspace = true }
async-trait = { workspace = true }
chromiumoxide = { version = "0.6.0" , features = ["tokio-runtime"] }
futures = { workspace = true }
rust-embed = "8.5.0"
tokio = { workspace = true, features = ["rt", "io-std"] }
tracing = { workspace = true }
warp = "0.3.7"
warp-embed = "0.5.0"

View File

@@ -1,336 +0,0 @@
//! Contains the native component of the browser prover.
//!
//! Conceptually the browser prover consists of the native and the wasm
//! components. The native component is responsible for starting the browser,
//! loading the wasm component and driving it.
use std::{env, net::IpAddr, time::Duration};
use anyhow::{anyhow, Context, Result};
use async_trait::async_trait;
use chromiumoxide::{
cdp::{
browser_protocol::log::{EventEntryAdded, LogEntryLevel},
js_protocol::runtime::EventExceptionThrown,
},
Browser, BrowserConfig, Page,
};
use futures::{Future, FutureExt, StreamExt};
use rust_embed::RustEmbed;
use serio::{stream::IoStreamExt, SinkExt as _};
use tokio::{io, io::AsyncWriteExt, net::TcpListener, task::JoinHandle};
use tracing::{debug, error, info};
use warp::Filter;
use tlsn_benches_browser_core::{
msg::{Config, Runtime},
FramedIo,
};
use tlsn_benches_library::{AsyncIo, ProverKind, ProverTrait};
/// The IP on which the wasm component is served.
pub static DEFAULT_WASM_IP: &str = "127.0.0.1";
/// The IP of the websocket relay.
pub static DEFAULT_WS_IP: &str = "127.0.0.1";
/// The port on which the wasm component is served.
pub static DEFAULT_WASM_PORT: u16 = 9001;
/// The port of the websocket relay.
pub static DEFAULT_WS_PORT: u16 = 9002;
/// The port for the wasm component to communicate with the TLS server.
pub static DEFAULT_WASM_TO_SERVER_PORT: u16 = 9003;
/// The port for the wasm component to communicate with the verifier.
pub static DEFAULT_WASM_TO_VERIFIER_PORT: u16 = 9004;
/// The port for the wasm component to communicate with the native component.
pub static DEFAULT_WASM_TO_NATIVE_PORT: u16 = 9005;
// The `pkg` dir will be embedded into the binary at compile-time.
#[derive(RustEmbed)]
#[folder = "../wasm/pkg"]
struct Data;
/// The native component of the prover which runs in the browser.
pub struct BrowserProver {
/// Io for communication with the wasm component.
wasm_io: FramedIo,
/// The browser spawned by the prover.
browser: Browser,
/// A handle to the http server.
http_server: JoinHandle<()>,
/// Handles to the relays.
relays: Vec<JoinHandle<Result<(), anyhow::Error>>>,
}
#[async_trait]
impl ProverTrait for BrowserProver {
async fn setup(
upload_size: usize,
download_size: usize,
defer_decryption: bool,
verifier_io: Box<dyn AsyncIo>,
server_io: Box<dyn AsyncIo>,
) -> anyhow::Result<Self>
where
Self: Sized,
{
let wasm_port: u16 = env::var("WASM_PORT")
.map(|port| port.parse().expect("port should be valid integer"))
.unwrap_or(DEFAULT_WASM_PORT);
let ws_port: u16 = env::var("WS_PORT")
.map(|port| port.parse().expect("port should be valid integer"))
.unwrap_or(DEFAULT_WS_PORT);
let wasm_to_server_port: u16 = env::var("WASM_TO_SERVER_PORT")
.map(|port| port.parse().expect("port should be valid integer"))
.unwrap_or(DEFAULT_WASM_TO_SERVER_PORT);
let wasm_to_verifier_port: u16 = env::var("WASM_TO_VERIFIER_PORT")
.map(|port| port.parse().expect("port should be valid integer"))
.unwrap_or(DEFAULT_WASM_TO_VERIFIER_PORT);
let wasm_to_native_port: u16 = env::var("WASM_TO_NATIVE_PORT")
.map(|port| port.parse().expect("port should be valid integer"))
.unwrap_or(DEFAULT_WASM_TO_NATIVE_PORT);
let wasm_ip: IpAddr = env::var("WASM_IP")
.map(|addr| addr.parse().expect("should be valid IP address"))
.unwrap_or(IpAddr::V4(DEFAULT_WASM_IP.parse().unwrap()));
let ws_ip: IpAddr = env::var("WS_IP")
.map(|addr| addr.parse().expect("should be valid IP address"))
.unwrap_or(IpAddr::V4(DEFAULT_WS_IP.parse().unwrap()));
let mut relays = Vec::with_capacity(4);
relays.push(spawn_websocket_relay(ws_ip, ws_port).await?);
// Create a framed connection to the wasm component.
let (wasm_left, wasm_right) = tokio::io::duplex(1 << 16);
relays.push(spawn_port_relay(wasm_to_native_port, Box::new(wasm_right)).await?);
let mut wasm_io = FramedIo::new(Box::new(wasm_left));
let http_server = spawn_http_server(wasm_ip, wasm_port)?;
// Relay data from the wasm component to the server.
relays.push(spawn_port_relay(wasm_to_server_port, server_io).await?);
// Relay data from the wasm component to the verifier.
relays.push(spawn_port_relay(wasm_to_verifier_port, verifier_io).await?);
info!("spawning browser");
// Note that the browser must be spawned only when the WebSocket relay is
// running.
let browser = spawn_browser(
wasm_ip,
ws_ip,
wasm_port,
ws_port,
wasm_to_server_port,
wasm_to_verifier_port,
wasm_to_native_port,
)
.await?;
// Without this sleep, it was observed that `wasm_io.send(Config)`
// msg does not reach the browser component.
tokio::time::sleep(Duration::from_secs(2)).await;
info!("sending config to the browser component");
wasm_io
.send(Config {
upload_size,
download_size,
defer_decryption,
})
.await?;
Ok(Self {
wasm_io,
browser,
http_server,
relays,
})
}
async fn run(&mut self) -> anyhow::Result<u64> {
let runtime: Runtime = self.wasm_io.expect_next().await.unwrap();
_ = self.clean_up().await?;
Ok(runtime.0)
}
fn kind(&self) -> ProverKind {
ProverKind::Browser
}
}
impl BrowserProver {
async fn clean_up(&mut self) -> anyhow::Result<()> {
// Kill the http server.
self.http_server.abort();
// Kill all relays.
let _ = self
.relays
.iter_mut()
.map(|task| task.abort())
.collect::<Vec<_>>();
// Close the browser.
self.browser.close().await?;
self.browser.wait().await?;
Ok(())
}
}
pub async fn spawn_websocket_relay(
ip: IpAddr,
port: u16,
) -> anyhow::Result<JoinHandle<Result<(), anyhow::Error>>> {
let listener = TcpListener::bind((ip, port)).await?;
Ok(tokio::spawn(websocket_relay::run(listener)))
}
/// Binds to the given localhost `port`, accepts a connection and relays data
/// between the connection and the `channel`.
pub async fn spawn_port_relay(
port: u16,
channel: Box<dyn AsyncIo>,
) -> anyhow::Result<JoinHandle<Result<(), anyhow::Error>>> {
let listener = tokio::net::TcpListener::bind(("127.0.0.1", port))
.await
.context("failed to bind to port")?;
let handle = tokio::spawn(async move {
let (tcp, _) = listener
.accept()
.await
.context("failed to accept a connection")
.unwrap();
relay_data(Box::new(tcp), channel).await
});
Ok(handle)
}
/// Relays data between two sources.
pub async fn relay_data(left: Box<dyn AsyncIo>, right: Box<dyn AsyncIo>) -> Result<()> {
let (mut left_read, mut left_write) = io::split(left);
let (mut right_read, mut right_write) = io::split(right);
let left_to_right = async {
io::copy(&mut left_read, &mut right_write).await?;
right_write.shutdown().await
};
let right_to_left = async {
io::copy(&mut right_read, &mut left_write).await?;
left_write.shutdown().await
};
tokio::try_join!(left_to_right, right_to_left)?;
Ok(())
}
/// Spawns the browser and starts the wasm component.
async fn spawn_browser(
wasm_ip: IpAddr,
ws_ip: IpAddr,
wasm_port: u16,
ws_port: u16,
wasm_to_server_port: u16,
wasm_to_verifier_port: u16,
wasm_to_native_port: u16,
) -> anyhow::Result<Browser> {
// Chrome requires --no-sandbox when running as root.
let config = BrowserConfig::builder()
.no_sandbox()
.incognito()
.build()
.map_err(|s| anyhow!(s))?;
debug!("launching chromedriver");
let (browser, mut handler) = Browser::launch(config).await?;
debug!("chromedriver started");
tokio::spawn(async move {
while let Some(res) = handler.next().await {
res.unwrap();
}
});
let page = browser
.new_page(&format!("http://{}:{}/index.html", wasm_ip, wasm_port))
.await?;
tokio::spawn(register_listeners(&page).await?);
page.wait_for_navigation().await?;
// Note that `format!` needs double {{ }} in order to escape them.
let _ = page
.evaluate_function(&format!(
r#"
async function() {{
await window.benchWorker.init();
// Do not `await` run() or else it will block the browser.
window.benchWorker.run("{}", {}, {}, {}, {});
}}
"#,
ws_ip, ws_port, wasm_to_server_port, wasm_to_verifier_port, wasm_to_native_port
))
.await?;
Ok(browser)
}
pub fn spawn_http_server(ip: IpAddr, port: u16) -> anyhow::Result<JoinHandle<()>> {
let handle = tokio::spawn(async move {
// Serve embedded files with additional headers.
let data_serve = warp_embed::embed(&Data);
let data_serve_with_headers = data_serve
.map(|reply| {
warp::reply::with_header(reply, "Cross-Origin-Opener-Policy", "same-origin")
})
.map(|reply| {
warp::reply::with_header(reply, "Cross-Origin-Embedder-Policy", "require-corp")
});
warp::serve(data_serve_with_headers).run((ip, port)).await;
});
Ok(handle)
}
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(|_| ()))
}

View File

@@ -1,16 +0,0 @@
[build]
target = "wasm32-unknown-unknown"
[unstable]
build-std = ["panic_abort", "std"]
[target.wasm32-unknown-unknown]
rustflags = [
"-C",
"target-feature=+atomics,+bulk-memory,+mutable-globals",
"-C",
# 4GB
"link-arg=--max-memory=4294967296",
"--cfg",
'getrandom_backend="wasm_js"',
]

View File

@@ -1,35 +0,0 @@
[package]
edition = "2021"
name = "tlsn-benches-browser-wasm"
publish = false
version = "0.0.0"
[lints]
workspace = true
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies]
tlsn-benches-browser-core = { workspace = true }
tlsn-benches-library = { workspace = true }
tlsn-wasm = { path = "../../../wasm" }
serio = { workspace = true }
anyhow = { workspace = true }
rayon = { workspace = true }
tracing = { workspace = true }
wasm-bindgen = { version = "0.2" }
wasm-bindgen-futures = { version = "0.4" }
web-spawn = { workspace = true, features = ["no-bundler"] }
web-time = { workspace = true }
# Use the patched ws_stream_wasm to fix the issue https://github.com/najamelan/ws_stream_wasm/issues/12#issuecomment-1711902958
ws_stream_wasm = { version = "0.7.4", git = "https://github.com/tlsnotary/ws_stream_wasm", rev = "2ed12aad9f0236e5321f577672f309920b2aef51", features = [
"tokio_io",
] }
[package.metadata.wasm-pack.profile.release]
# Note: these wasm-pack options should match those in crates/wasm/Cargo.toml
opt-level = "z"
wasm-opt = true

View File

@@ -1,7 +0,0 @@
<!DOCTYPE html>
<head>
</head>
<body>
<script src="index.js" type="module"></script>
</body>
</html>

View File

@@ -1,5 +0,0 @@
import * as Comlink from "./comlink.mjs";
const benchWorker = Comlink.wrap(new Worker("worker.js", { type: "module" }));
window.benchWorker = benchWorker;

View File

@@ -1,41 +0,0 @@
import * as Comlink from "./comlink.mjs";
import init_wasm, * as wasm from './tlsn_benches_browser_wasm.js';
class BenchWorker {
async init() {
try {
await init_wasm();
// Using Error level since excessive logging may interfere with the
// benchmark results.
await wasm.initialize_bench({ level: "Error" }, navigator.hardwareConcurrency);
} catch (e) {
console.error(e);
throw e;
}
}
async run(
ws_ip,
ws_port,
wasm_to_server_port,
wasm_to_verifier_port,
wasm_to_native_port
) {
try {
await wasm.wasm_main(
ws_ip,
ws_port,
wasm_to_server_port,
wasm_to_verifier_port,
wasm_to_native_port);
} catch (e) {
console.error(e);
throw e;
}
}
}
const worker = new BenchWorker();
Comlink.expose(worker);

View File

@@ -1,4 +0,0 @@
[toolchain]
channel = "nightly"
components = ["rust-src"]
targets = ["wasm32-unknown-unknown"]

View File

@@ -1,115 +0,0 @@
#![cfg(target_arch = "wasm32")]
//! Contains the wasm component of the browser prover.
//!
//! Conceptually the browser prover consists of the native and the wasm
//! components.
use anyhow::Result;
use serio::{stream::IoStreamExt, SinkExt as _};
use tracing::info;
use wasm_bindgen::prelude::*;
use web_time::Instant;
use ws_stream_wasm::WsMeta;
use tlsn_benches_browser_core::{
msg::{Config, Runtime},
FramedIo,
};
use tlsn_benches_library::run_prover;
use tlsn_wasm::LoggingConfig;
#[wasm_bindgen]
pub async fn wasm_main(
ws_ip: String,
ws_port: u16,
wasm_to_server_port: u16,
wasm_to_verifier_port: u16,
wasm_to_native_port: u16,
) -> Result<(), JsError> {
// Wrapping main() since wasm_bindgen doesn't support anyhow.
main(
ws_ip,
ws_port,
wasm_to_server_port,
wasm_to_verifier_port,
wasm_to_native_port,
)
.await
.map_err(|err| JsError::new(&err.to_string()))
}
pub async fn main(
ws_ip: String,
ws_port: u16,
wasm_to_server_port: u16,
wasm_to_verifier_port: u16,
wasm_to_native_port: u16,
) -> Result<()> {
info!("starting main");
// Connect to the server.
let (_, server_io_ws) = WsMeta::connect(
&format!(
"ws://{}:{}/tcp?addr=localhost%3A{}",
ws_ip, ws_port, wasm_to_server_port
),
None,
)
.await?;
let server_io = server_io_ws.into_io();
// Connect to the verifier.
let (_, verifier_io_ws) = WsMeta::connect(
&format!(
"ws://{}:{}/tcp?addr=localhost%3A{}",
ws_ip, ws_port, wasm_to_verifier_port
),
None,
)
.await?;
let verifier_io = verifier_io_ws.into_io();
// Connect to the native component of the browser prover.
let (_, native_io_ws) = WsMeta::connect(
&format!(
"ws://{}:{}/tcp?addr=localhost%3A{}",
ws_ip, ws_port, wasm_to_native_port
),
None,
)
.await?;
let mut native_io = FramedIo::new(Box::new(native_io_ws.into_io()));
info!("expecting config from the native component");
let cfg: Config = native_io.expect_next().await?;
let start_time = Instant::now();
info!("running the prover");
run_prover(
cfg.upload_size,
cfg.download_size,
cfg.defer_decryption,
Box::new(verifier_io),
Box::new(server_io),
)
.await?;
native_io
.send(Runtime(start_time.elapsed().as_secs()))
.await?;
Ok(())
}
/// Initializes the module.
#[wasm_bindgen]
pub async fn initialize_bench(
logging_config: Option<LoggingConfig>,
thread_count: usize,
) -> Result<(), JsValue> {
tlsn_wasm::initialize(logging_config, thread_count).await
}

View File

@@ -1,22 +0,0 @@
[package]
edition = "2021"
name = "tlsn-benches-library"
publish = false
version = "0.0.0"
[lints]
workspace = true
[dependencies]
tlsn-common = { workspace = true }
tlsn-core = { workspace = true }
tlsn-prover = { workspace = true }
tlsn-server-fixture-certs = { workspace = true }
tlsn-tls-core = { workspace = true }
anyhow = "1.0"
async-trait = "0.1.81"
futures = { version = "0.3", features = ["compat"] }
serde = { workspace = true }
tokio = {version = "1", default-features = false, features = ["rt", "macros"]}
tokio-util= {version = "0.7", features = ["compat", "io"]}

View File

@@ -1,137 +0,0 @@
use tls_core::{anchors::RootCertStore, verify::WebPkiVerifier};
use tlsn_common::config::ProtocolConfig;
use tlsn_core::{CryptoProvider, ProveConfig};
use tlsn_prover::{Prover, ProverConfig};
use tlsn_server_fixture_certs::{CA_CERT_DER, SERVER_DOMAIN};
use anyhow::Context;
use async_trait::async_trait;
use futures::{future::try_join, AsyncReadExt as _, AsyncWriteExt as _, TryFutureExt};
use serde::{Deserialize, Serialize};
use tokio::io::{AsyncRead, AsyncWrite};
use tokio_util::compat::TokioAsyncReadCompatExt;
pub trait AsyncIo: AsyncRead + AsyncWrite + Send + Sync + Unpin + 'static {}
impl<T> AsyncIo for T where T: AsyncRead + AsyncWrite + Send + Sync + Unpin + 'static {}
#[async_trait]
pub trait ProverTrait {
/// Sets up the prover preparing it to be run. Returns a prover ready to be
/// run.
async fn setup(
upload_size: usize,
download_size: usize,
defer_decryption: bool,
verifier_io: Box<dyn AsyncIo>,
server_io: Box<dyn AsyncIo>,
) -> anyhow::Result<Self>
where
Self: Sized;
/// Runs the prover. Returns the total run time in seconds.
async fn run(&mut self) -> anyhow::Result<u64>;
/// Returns the kind of the prover.
fn kind(&self) -> ProverKind;
}
#[derive(Debug, Clone, Serialize, Deserialize)]
/// The kind of a prover.
pub enum ProverKind {
/// The prover compiled into a native binary.
Native,
/// The prover compiled into a wasm binary.
Browser,
}
impl From<ProverKind> for String {
fn from(value: ProverKind) -> Self {
match value {
ProverKind::Native => "Native".to_string(),
ProverKind::Browser => "Browser".to_string(),
}
}
}
pub async fn run_prover(
upload_size: usize,
download_size: usize,
defer_decryption: bool,
io: Box<dyn AsyncIo>,
client_conn: Box<dyn AsyncIo>,
) -> anyhow::Result<()> {
let provider = CryptoProvider {
cert: WebPkiVerifier::new(root_store(), None),
..Default::default()
};
let mut protocol_config = ProtocolConfig::builder();
if defer_decryption {
protocol_config
.max_sent_data(upload_size + 256)
.max_recv_data(download_size + 256)
} else {
protocol_config
.max_sent_data(upload_size + 256)
.max_recv_data(download_size + 256)
.max_recv_data_online(download_size + 256)
};
let protocol_config = protocol_config
.defer_decryption_from_start(defer_decryption)
.build()
.unwrap();
let prover = Prover::new(
ProverConfig::builder()
.server_name(SERVER_DOMAIN)
.protocol_config(protocol_config)
.crypto_provider(provider)
.build()
.context("invalid prover config")?,
)
.setup(io.compat())
.await?;
let (mut mpc_tls_connection, prover_fut) = prover.connect(client_conn.compat()).await?;
let tls_fut = async move {
let request = format!(
"GET /bytes?size={} HTTP/1.1\r\nConnection: close\r\nData: {}\r\n\r\n",
download_size,
String::from_utf8(vec![0x42u8; upload_size]).unwrap(),
);
mpc_tls_connection.write_all(request.as_bytes()).await?;
mpc_tls_connection.close().await?;
let mut response = vec![];
mpc_tls_connection.read_to_end(&mut response).await?;
dbg!(response.len());
Ok::<(), anyhow::Error>(())
};
let (mut prover, _) = try_join(prover_fut.map_err(anyhow::Error::from), tls_fut).await?;
let (sent_len, recv_len) = prover.transcript().len();
let mut builder = ProveConfig::builder(prover.transcript());
builder.reveal_sent(&(0..sent_len)).unwrap();
builder.reveal_recv(&(0..recv_len)).unwrap();
let config = builder.build().unwrap();
prover.prove(&config).await?;
prover.close().await?;
Ok(())
}
fn root_store() -> RootCertStore {
let mut root_store = RootCertStore::empty();
root_store
.add(&tls_core::key::Certificate(CA_CERT_DER.to_vec()))
.unwrap();
root_store
}

View File

@@ -1,43 +0,0 @@
[package]
name = "tlsn-common"
description = "Common code shared between tlsn-prover and tlsn-verifier"
version = "0.1.0-alpha.11"
edition = "2021"
[lints]
workspace = true
[features]
default = []
[dependencies]
tlsn-core = { workspace = true }
tlsn-tls-core = { workspace = true }
tlsn-cipher = { workspace = true }
mpz-core = { workspace = true }
mpz-common = { workspace = true }
mpz-memory-core = { workspace = true }
mpz-hash = { workspace = true }
mpz-vm-core = { workspace = true }
mpz-zk = { workspace = true }
async-trait = { workspace = true }
derive_builder = { workspace = true }
futures = { workspace = true }
once_cell = { workspace = true }
opaque-debug = { workspace = true }
rand = { workspace = true }
rangeset = { workspace = true }
serio = { workspace = true, features = ["codec", "bincode"] }
thiserror = { workspace = true }
tracing = { workspace = true }
uid-mux = { workspace = true, features = ["serio"] }
serde = { workspace = true, features = ["derive"] }
semver = { version = "1.0", features = ["serde"] }
[target.'cfg(target_arch = "wasm32")'.dependencies]
wasm-bindgen = { version = "0.2" }
web-spawn = { workspace = true }
[dev-dependencies]
rstest = { workspace = true }

View File

@@ -1,110 +0,0 @@
//! Plaintext commitment and proof of encryption.
pub mod hash;
use mpz_core::bitvec::BitVec;
use mpz_memory_core::{binary::Binary, DecodeFutureTyped};
use mpz_vm_core::{prelude::*, Vm};
use crate::{
transcript::Record,
zk_aes::{ZkAesCtr, ZkAesCtrError},
Role,
};
/// Commits the plaintext of the provided records, returning a proof of
/// encryption.
///
/// Writes the plaintext VM reference to the provided records.
pub fn commit_records<'record>(
vm: &mut dyn Vm<Binary>,
aes: &mut ZkAesCtr,
records: impl IntoIterator<Item = &'record mut Record>,
) -> Result<RecordProof, RecordProofError> {
let mut ciphertexts = Vec::new();
for record in records {
if record.plaintext_ref.is_some() {
return Err(ErrorRepr::PlaintextRefAlreadySet.into());
}
let (plaintext_ref, ciphertext_ref) = aes
.encrypt(vm, record.explicit_nonce.clone(), record.ciphertext.len())
.map_err(ErrorRepr::Aes)?;
record.plaintext_ref = Some(plaintext_ref);
if let Role::Prover = aes.role() {
let Some(plaintext) = record.plaintext.clone() else {
return Err(ErrorRepr::MissingPlaintext.into());
};
vm.assign(plaintext_ref, plaintext)
.map_err(RecordProofError::vm)?;
}
vm.commit(plaintext_ref).map_err(RecordProofError::vm)?;
let ciphertext = vm.decode(ciphertext_ref).map_err(RecordProofError::vm)?;
ciphertexts.push((ciphertext, record.ciphertext.clone()));
}
Ok(RecordProof { ciphertexts })
}
/// Proof of encryption.
#[derive(Debug)]
#[must_use]
#[allow(clippy::type_complexity)]
pub struct RecordProof {
ciphertexts: Vec<(DecodeFutureTyped<BitVec, Vec<u8>>, Vec<u8>)>,
}
impl RecordProof {
/// Verifies the proof.
pub fn verify(self) -> Result<(), RecordProofError> {
let Self { ciphertexts } = self;
for (mut ciphertext, expected) in ciphertexts {
let ciphertext = ciphertext
.try_recv()
.map_err(RecordProofError::vm)?
.ok_or_else(|| ErrorRepr::NotDecoded)?;
if ciphertext != expected {
return Err(ErrorRepr::InvalidCiphertext.into());
}
}
Ok(())
}
}
/// Error for [`RecordProof`].
#[derive(Debug, thiserror::Error)]
#[error(transparent)]
pub struct RecordProofError(#[from] ErrorRepr);
impl RecordProofError {
fn vm<E>(err: E) -> Self
where
E: Into<Box<dyn std::error::Error + Send + Sync + 'static>>,
{
Self(ErrorRepr::Vm(err.into()))
}
}
#[derive(Debug, thiserror::Error)]
#[error("record proof error: {0}")]
enum ErrorRepr {
#[error("VM error: {0}")]
Vm(Box<dyn std::error::Error + Send + Sync + 'static>),
#[error("zk aes error: {0}")]
Aes(ZkAesCtrError),
#[error("plaintext is missing")]
MissingPlaintext,
#[error("plaintext reference is already set")]
PlaintextRefAlreadySet,
#[error("ciphertext was not decoded")]
NotDecoded,
#[error("ciphertext does not match expected")]
InvalidCiphertext,
}

View File

@@ -1,363 +0,0 @@
//! TLSNotary protocol config and config utilities.
use core::fmt;
use once_cell::sync::Lazy;
use semver::Version;
use serde::{Deserialize, Serialize};
use std::error::Error;
// Default is 32 bytes to decrypt the TLS protocol messages.
const DEFAULT_MAX_RECV_ONLINE: usize = 32;
// Default maximum number of TLS records to allow.
//
// This would allow for up to 50Mb upload from prover to verifier.
const DEFAULT_RECORDS_LIMIT: usize = 256;
// Current version that is running.
static VERSION: Lazy<Version> = Lazy::new(|| {
Version::parse(env!("CARGO_PKG_VERSION"))
.map_err(|err| ProtocolConfigError::new(ErrorKind::Version, err))
.unwrap()
});
/// Protocol configuration to be set up initially by prover and verifier.
#[derive(derive_builder::Builder, Clone, Debug, Deserialize, Serialize)]
#[builder(build_fn(validate = "Self::validate"))]
pub struct ProtocolConfig {
/// Maximum number of bytes that can be sent.
max_sent_data: usize,
/// Maximum number of application data records that can be sent.
#[builder(setter(strip_option), default)]
max_sent_records: Option<usize>,
/// Maximum number of bytes that can be decrypted online, i.e. while the
/// MPC-TLS connection is active.
#[builder(default = "DEFAULT_MAX_RECV_ONLINE")]
max_recv_data_online: usize,
/// Maximum number of bytes that can be received.
max_recv_data: usize,
/// Maximum number of application data records that can be received.
#[builder(setter(strip_option), default)]
max_recv_records: Option<usize>,
/// Whether the `deferred decryption` feature is toggled on from the start
/// of the MPC-TLS connection.
#[builder(default = "true")]
defer_decryption_from_start: bool,
/// Network settings.
#[builder(default)]
network: NetworkSetting,
/// Version that is being run by prover/verifier.
#[builder(setter(skip), default = "VERSION.clone()")]
version: Version,
}
impl ProtocolConfigBuilder {
fn validate(&self) -> Result<(), String> {
if self.max_recv_data_online > self.max_recv_data {
return Err(
"max_recv_data_online must be smaller or equal to max_recv_data".to_string(),
);
}
Ok(())
}
}
impl ProtocolConfig {
/// Creates a new builder for `ProtocolConfig`.
pub fn builder() -> ProtocolConfigBuilder {
ProtocolConfigBuilder::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 application data records that can
/// be received.
pub fn max_recv_records(&self) -> Option<usize> {
self.max_recv_records
}
/// 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
}
}
/// Protocol configuration validator used by checker (i.e. verifier) to perform
/// compatibility check with the peer's (i.e. the prover's) configuration.
#[derive(derive_builder::Builder, Clone, Debug)]
pub struct ProtocolConfigValidator {
/// Maximum number of bytes that can be sent.
max_sent_data: usize,
/// Maximum number of application data records that can be sent.
#[builder(default = "DEFAULT_RECORDS_LIMIT")]
max_sent_records: usize,
/// Maximum number of bytes that can be received.
max_recv_data: usize,
/// Maximum number of application data records that can be received.
#[builder(default = "DEFAULT_RECORDS_LIMIT")]
max_recv_records: usize,
/// Version that is being run by checker.
#[builder(setter(skip), default = "VERSION.clone()")]
version: Version,
}
impl ProtocolConfigValidator {
/// Creates a new builder for `ProtocolConfigValidator`.
pub fn builder() -> ProtocolConfigValidatorBuilder {
ProtocolConfigValidatorBuilder::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) -> usize {
self.max_sent_records
}
/// 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 application data records that can
/// be received.
pub fn max_recv_records(&self) -> usize {
self.max_recv_records
}
/// Performs compatibility check of the protocol configuration between
/// prover and verifier.
pub fn validate(&self, config: &ProtocolConfig) -> Result<(), ProtocolConfigError> {
self.check_max_transcript_size(config.max_sent_data, config.max_recv_data)?;
self.check_max_records(config.max_sent_records, config.max_recv_records)?;
self.check_version(&config.version)?;
Ok(())
}
// Checks if both the sent and recv data are within limits.
fn check_max_transcript_size(
&self,
max_sent_data: usize,
max_recv_data: usize,
) -> Result<(), ProtocolConfigError> {
if max_sent_data > self.max_sent_data {
return Err(ProtocolConfigError::max_transcript_size(format!(
"max_sent_data {:?} is greater than the configured limit {:?}",
max_sent_data, self.max_sent_data,
)));
}
if max_recv_data > self.max_recv_data {
return Err(ProtocolConfigError::max_transcript_size(format!(
"max_recv_data {:?} is greater than the configured limit {:?}",
max_recv_data, self.max_recv_data,
)));
}
Ok(())
}
fn check_max_records(
&self,
max_sent_records: Option<usize>,
max_recv_records: Option<usize>,
) -> Result<(), ProtocolConfigError> {
if let Some(max_sent_records) = max_sent_records {
if max_sent_records > self.max_sent_records {
return Err(ProtocolConfigError::max_record_count(format!(
"max_sent_records {} is greater than the configured limit {}",
max_sent_records, self.max_sent_records,
)));
}
}
if let Some(max_recv_records) = max_recv_records {
if max_recv_records > self.max_recv_records {
return Err(ProtocolConfigError::max_record_count(format!(
"max_recv_records {} is greater than the configured limit {}",
max_recv_records, self.max_recv_records,
)));
}
}
Ok(())
}
// Checks if both versions are the same (might support check for different but
// compatible versions in the future).
fn check_version(&self, peer_version: &Version) -> Result<(), ProtocolConfigError> {
if *peer_version != self.version {
return Err(ProtocolConfigError::version(format!(
"prover's version {:?} is different from verifier's version {:?}",
peer_version, self.version
)));
}
Ok(())
}
}
/// Settings for the network environment.
///
/// Provides optimization options to adapt the protocol to different network
/// situations.
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub enum NetworkSetting {
/// Prefers a bandwidth-heavy protocol.
Bandwidth,
/// Prefers a latency-heavy protocol.
Latency,
}
impl Default for NetworkSetting {
fn default() -> Self {
Self::Bandwidth
}
}
/// A ProtocolConfig error.
#[derive(thiserror::Error, Debug)]
pub struct ProtocolConfigError {
kind: ErrorKind,
#[source]
source: Option<Box<dyn Error + Send + Sync>>,
}
impl ProtocolConfigError {
fn new<E>(kind: ErrorKind, source: E) -> Self
where
E: Into<Box<dyn Error + Send + Sync>>,
{
Self {
kind,
source: Some(source.into()),
}
}
fn max_transcript_size(msg: impl Into<String>) -> Self {
Self {
kind: ErrorKind::MaxTranscriptSize,
source: Some(msg.into().into()),
}
}
fn max_record_count(msg: impl Into<String>) -> Self {
Self {
kind: ErrorKind::MaxRecordCount,
source: Some(msg.into().into()),
}
}
fn version(msg: impl Into<String>) -> Self {
Self {
kind: ErrorKind::Version,
source: Some(msg.into().into()),
}
}
}
impl fmt::Display for ProtocolConfigError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.kind {
ErrorKind::MaxTranscriptSize => write!(f, "max transcript size exceeded")?,
ErrorKind::MaxRecordCount => write!(f, "max record count exceeded")?,
ErrorKind::Version => write!(f, "version error")?,
}
if let Some(ref source) = self.source {
write!(f, " caused by: {}", source)?;
}
Ok(())
}
}
#[derive(Debug)]
enum ErrorKind {
MaxTranscriptSize,
MaxRecordCount,
Version,
}
#[cfg(test)]
mod test {
use super::*;
use rstest::{fixture, rstest};
const TEST_MAX_SENT_LIMIT: usize = 1 << 12;
const TEST_MAX_RECV_LIMIT: usize = 1 << 14;
#[fixture]
#[once]
fn config_validator() -> ProtocolConfigValidator {
ProtocolConfigValidator::builder()
.max_sent_data(TEST_MAX_SENT_LIMIT)
.max_recv_data(TEST_MAX_RECV_LIMIT)
.build()
.unwrap()
}
#[rstest]
#[case::same_max_sent_recv_data(TEST_MAX_SENT_LIMIT, TEST_MAX_RECV_LIMIT)]
#[case::smaller_max_sent_data(1 << 11, TEST_MAX_RECV_LIMIT)]
#[case::smaller_max_recv_data(TEST_MAX_SENT_LIMIT, 1 << 13)]
#[case::smaller_max_sent_recv_data(1 << 7, 1 << 9)]
fn test_check_success(
config_validator: &ProtocolConfigValidator,
#[case] max_sent_data: usize,
#[case] max_recv_data: usize,
) {
let peer_config = ProtocolConfig::builder()
.max_sent_data(max_sent_data)
.max_recv_data(max_recv_data)
.build()
.unwrap();
assert!(config_validator.validate(&peer_config).is_ok())
}
#[rstest]
#[case::bigger_max_sent_data(1 << 13, TEST_MAX_RECV_LIMIT)]
#[case::bigger_max_recv_data(1 << 10, 1 << 16)]
#[case::bigger_max_sent_recv_data(1 << 14, 1 << 21)]
fn test_check_fail(
config_validator: &ProtocolConfigValidator,
#[case] max_sent_data: usize,
#[case] max_recv_data: usize,
) {
let peer_config = ProtocolConfig::builder()
.max_sent_data(max_sent_data)
.max_recv_data(max_recv_data)
.build()
.unwrap();
assert!(config_validator.validate(&peer_config).is_err())
}
}

View File

@@ -1,238 +0,0 @@
//! Encoding commitment protocol.
use std::ops::Range;
use mpz_common::Context;
use mpz_memory_core::{
binary::U8,
correlated::{Delta, Key, Mac},
Vector,
};
use rand::Rng;
use serde::{Deserialize, Serialize};
use serio::{stream::IoStreamExt, SinkExt};
use tlsn_core::{
hash::HashAlgorithm,
transcript::{
encoding::{
new_encoder, Encoder, EncoderSecret, EncodingCommitment, EncodingProvider,
EncodingProviderError, EncodingTree, EncodingTreeError,
},
Direction, Idx,
},
};
use crate::transcript::TranscriptRefs;
/// Bytes of encoding, per byte.
const ENCODING_SIZE: usize = 128;
#[derive(Debug, Serialize, Deserialize)]
struct Encodings {
sent: Vec<u8>,
recv: Vec<u8>,
}
/// Transfers the encodings using the provided seed and keys.
///
/// The keys must be consistent with the global delta used in the encodings.
pub async fn transfer<'a>(
ctx: &mut Context,
refs: &TranscriptRefs,
delta: &Delta,
f: impl Fn(Vector<U8>) -> &'a [Key],
) -> Result<EncodingCommitment, EncodingError> {
let secret = EncoderSecret::new(rand::rng().random(), delta.as_block().to_bytes());
let encoder = new_encoder(&secret);
let sent_keys: Vec<u8> = refs
.sent()
.iter()
.copied()
.flat_map(&f)
.flat_map(|key| key.as_block().as_bytes())
.copied()
.collect();
let recv_keys: Vec<u8> = refs
.recv()
.iter()
.copied()
.flat_map(&f)
.flat_map(|key| key.as_block().as_bytes())
.copied()
.collect();
assert_eq!(sent_keys.len() % ENCODING_SIZE, 0);
assert_eq!(recv_keys.len() % ENCODING_SIZE, 0);
let mut sent_encoding = Vec::with_capacity(sent_keys.len());
let mut recv_encoding = Vec::with_capacity(recv_keys.len());
encoder.encode_range(
Direction::Sent,
0..sent_keys.len() / ENCODING_SIZE,
&mut sent_encoding,
);
encoder.encode_range(
Direction::Received,
0..recv_keys.len() / ENCODING_SIZE,
&mut recv_encoding,
);
sent_encoding
.iter_mut()
.zip(sent_keys)
.for_each(|(enc, key)| *enc ^= key);
recv_encoding
.iter_mut()
.zip(recv_keys)
.for_each(|(enc, key)| *enc ^= key);
ctx.io_mut()
.send(Encodings {
sent: sent_encoding,
recv: recv_encoding,
})
.await?;
let root = ctx.io_mut().expect_next().await?;
ctx.io_mut().send(secret.clone()).await?;
Ok(EncodingCommitment {
root,
secret: secret.clone(),
})
}
/// Receives the encodings using the provided MACs.
///
/// The MACs must be consistent with the global delta used in the encodings.
pub async fn receive<'a>(
ctx: &mut Context,
hasher: &(dyn HashAlgorithm + Send + Sync),
refs: &TranscriptRefs,
f: impl Fn(Vector<U8>) -> &'a [Mac],
idxs: impl IntoIterator<Item = &(Direction, Idx)>,
) -> Result<(EncodingCommitment, EncodingTree), EncodingError> {
let Encodings { mut sent, mut recv } = ctx.io_mut().expect_next().await?;
let sent_macs: Vec<u8> = refs
.sent()
.iter()
.copied()
.flat_map(&f)
.flat_map(|mac| mac.as_bytes())
.copied()
.collect();
let recv_macs: Vec<u8> = refs
.recv()
.iter()
.copied()
.flat_map(&f)
.flat_map(|mac| mac.as_bytes())
.copied()
.collect();
assert_eq!(sent_macs.len() % ENCODING_SIZE, 0);
assert_eq!(recv_macs.len() % ENCODING_SIZE, 0);
if sent.len() != sent_macs.len() {
return Err(ErrorRepr::IncorrectMacCount {
direction: Direction::Sent,
expected: sent_macs.len(),
got: sent.len(),
}
.into());
}
if recv.len() != recv_macs.len() {
return Err(ErrorRepr::IncorrectMacCount {
direction: Direction::Received,
expected: recv_macs.len(),
got: recv.len(),
}
.into());
}
sent.iter_mut()
.zip(sent_macs)
.for_each(|(enc, mac)| *enc ^= mac);
recv.iter_mut()
.zip(recv_macs)
.for_each(|(enc, mac)| *enc ^= mac);
let provider = Provider { sent, recv };
let tree = EncodingTree::new(hasher, idxs, &provider)?;
let root = tree.root();
ctx.io_mut().send(root.clone()).await?;
let secret = ctx.io_mut().expect_next().await?;
let commitment = EncodingCommitment { root, secret };
Ok((commitment, tree))
}
#[derive(Debug)]
struct Provider {
sent: Vec<u8>,
recv: Vec<u8>,
}
impl EncodingProvider for Provider {
fn provide_encoding(
&self,
direction: Direction,
range: Range<usize>,
dest: &mut Vec<u8>,
) -> Result<(), EncodingProviderError> {
let encodings = match direction {
Direction::Sent => &self.sent,
Direction::Received => &self.recv,
};
let start = range.start * ENCODING_SIZE;
let end = range.end * ENCODING_SIZE;
if end > encodings.len() {
return Err(EncodingProviderError);
}
dest.extend_from_slice(&encodings[start..end]);
Ok(())
}
}
/// Encoding protocol error.
#[derive(Debug, thiserror::Error)]
#[error(transparent)]
pub struct EncodingError(#[from] ErrorRepr);
#[derive(Debug, thiserror::Error)]
#[error("encoding protocol error: {0}")]
enum ErrorRepr {
#[error("I/O error: {0}")]
Io(std::io::Error),
#[error("incorrect MAC count for {direction}: expected {expected}, got {got}")]
IncorrectMacCount {
direction: Direction,
expected: usize,
got: usize,
},
#[error("encoding tree error: {0}")]
EncodingTree(EncodingTreeError),
}
impl From<std::io::Error> for EncodingError {
fn from(value: std::io::Error) -> Self {
Self(ErrorRepr::Io(value))
}
}
impl From<EncodingTreeError> for EncodingError {
fn from(value: EncodingTreeError) -> Self {
Self(ErrorRepr::EncodingTree(value))
}
}

View File

@@ -1,25 +0,0 @@
//! Common code shared between `tlsn-prover` and `tlsn-verifier`.
#![deny(missing_docs, unreachable_pub, unused_must_use)]
#![deny(clippy::all)]
#![forbid(unsafe_code)]
pub mod commit;
pub mod config;
pub mod context;
pub mod encoding;
pub mod msg;
pub mod mux;
pub mod transcript;
pub mod zk_aes;
/// The party's role in the TLSN protocol.
///
/// A Notary is classified as a Verifier.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Role {
/// The prover.
Prover,
/// The verifier.
Verifier,
}

View File

@@ -1,14 +0,0 @@
//! Message types.
use serde::{Deserialize, Serialize};
use tlsn_core::connection::{ServerCertData, ServerName};
/// Message sent from Prover to Verifier to prove the server identity.
#[derive(Debug, Serialize, Deserialize)]
pub struct ServerIdentityProof {
/// Server name.
pub name: ServerName,
/// Server identity data.
pub data: ServerCertData,
}

View File

@@ -1,304 +0,0 @@
//! TLS transcript.
use mpz_memory_core::{
binary::{Binary, U8},
MemoryExt, Vector,
};
use mpz_vm_core::{Vm, VmError};
use rangeset::Intersection;
use tls_core::msgs::enums::ContentType;
use tlsn_core::transcript::{Direction, Idx, PartialTranscript, Transcript};
/// A transcript of sent and received TLS records.
#[derive(Debug, Default, Clone)]
pub struct TlsTranscript {
/// Records sent by the prover.
pub sent: Vec<Record>,
/// Records received by the prover.
pub recv: Vec<Record>,
}
impl TlsTranscript {
/// Returns the application data transcript.
pub fn to_transcript(&self) -> Result<Transcript, IncompleteTranscript> {
let mut sent = Vec::new();
let mut recv = Vec::new();
for record in self
.sent
.iter()
.filter(|record| record.typ == ContentType::ApplicationData)
{
let plaintext = record
.plaintext
.as_ref()
.ok_or(IncompleteTranscript {})?
.clone();
sent.extend_from_slice(&plaintext);
}
for record in self
.recv
.iter()
.filter(|record| record.typ == ContentType::ApplicationData)
{
let plaintext = record
.plaintext
.as_ref()
.ok_or(IncompleteTranscript {})?
.clone();
recv.extend_from_slice(&plaintext);
}
Ok(Transcript::new(sent, recv))
}
/// Returns the application data transcript references.
pub fn to_transcript_refs(&self) -> Result<TranscriptRefs, IncompleteTranscript> {
let mut sent = Vec::new();
let mut recv = Vec::new();
for record in self
.sent
.iter()
.filter(|record| record.typ == ContentType::ApplicationData)
{
let plaintext_ref = record
.plaintext_ref
.as_ref()
.ok_or(IncompleteTranscript {})?;
sent.push(*plaintext_ref);
}
for record in self
.recv
.iter()
.filter(|record| record.typ == ContentType::ApplicationData)
{
let plaintext_ref = record
.plaintext_ref
.as_ref()
.ok_or(IncompleteTranscript {})?;
recv.push(*plaintext_ref);
}
Ok(TranscriptRefs { sent, recv })
}
}
/// A TLS record.
#[derive(Clone)]
pub struct Record {
/// Sequence number.
pub seq: u64,
/// Content type.
pub typ: ContentType,
/// Plaintext.
pub plaintext: Option<Vec<u8>>,
/// VM reference to the plaintext.
pub plaintext_ref: Option<Vector<U8>>,
/// Explicit nonce.
pub explicit_nonce: Vec<u8>,
/// Ciphertext.
pub ciphertext: Vec<u8>,
}
opaque_debug::implement!(Record);
/// References to the application plaintext in the transcript.
#[derive(Debug, Default, Clone)]
pub struct TranscriptRefs {
sent: Vec<Vector<U8>>,
recv: Vec<Vector<U8>>,
}
impl TranscriptRefs {
/// Returns the sent plaintext references.
pub fn sent(&self) -> &[Vector<U8>] {
&self.sent
}
/// Returns the received plaintext references.
pub fn recv(&self) -> &[Vector<U8>] {
&self.recv
}
/// Returns VM references for the given direction and index, otherwise
/// `None` if the index is out of bounds.
pub fn get(&self, direction: Direction, idx: &Idx) -> Option<Vec<Vector<U8>>> {
if idx.is_empty() {
return Some(Vec::new());
}
let refs = match direction {
Direction::Sent => &self.sent,
Direction::Received => &self.recv,
};
// Computes the transcript range for each reference.
let mut start = 0;
let mut slice_iter = refs.iter().map(move |slice| {
let out = (slice, start..start + slice.len());
start += slice.len();
out
});
let mut slices = Vec::new();
let (mut slice, mut slice_range) = slice_iter.next()?;
for range in idx.iter_ranges() {
loop {
if let Some(intersection) = slice_range.intersection(&range) {
let start = intersection.start - slice_range.start;
let end = intersection.end - slice_range.start;
slices.push(slice.get(start..end).expect("range should be in bounds"));
}
// Proceed to next range if the current slice extends beyond. Otherwise, proceed
// to the next slice.
if range.end <= slice_range.end {
break;
} else {
(slice, slice_range) = slice_iter.next()?;
}
}
}
Some(slices)
}
}
/// Error for [`TranscriptRefs::from_transcript`].
#[derive(Debug, thiserror::Error)]
#[error("not all application plaintext was committed to in the TLS transcript")]
pub struct IncompleteTranscript {}
/// Decodes the transcript.
pub fn decode_transcript(
vm: &mut dyn Vm<Binary>,
sent: &Idx,
recv: &Idx,
refs: &TranscriptRefs,
) -> Result<(), VmError> {
let sent_refs = refs.get(Direction::Sent, sent).expect("index is in bounds");
let recv_refs = refs
.get(Direction::Received, recv)
.expect("index is in bounds");
for slice in sent_refs.into_iter().chain(recv_refs) {
// Drop the future, we don't need it.
drop(vm.decode(slice)?);
}
Ok(())
}
/// Verifies a partial transcript.
pub fn verify_transcript(
vm: &mut dyn Vm<Binary>,
transcript: &PartialTranscript,
refs: &TranscriptRefs,
) -> Result<(), InconsistentTranscript> {
let sent_refs = refs
.get(Direction::Sent, transcript.sent_authed())
.expect("index is in bounds");
let recv_refs = refs
.get(Direction::Received, transcript.received_authed())
.expect("index is in bounds");
let mut authenticated_data = Vec::new();
for data in sent_refs.into_iter().chain(recv_refs) {
let plaintext = vm
.get(data)
.expect("reference is valid")
.expect("plaintext is decoded");
authenticated_data.extend_from_slice(&plaintext);
}
let mut purported_data = Vec::with_capacity(authenticated_data.len());
for range in transcript.sent_authed().iter_ranges() {
purported_data.extend_from_slice(&transcript.sent_unsafe()[range]);
}
for range in transcript.received_authed().iter_ranges() {
purported_data.extend_from_slice(&transcript.received_unsafe()[range]);
}
if purported_data != authenticated_data {
return Err(InconsistentTranscript {});
}
Ok(())
}
/// Error for [`verify_transcript`].
#[derive(Debug, thiserror::Error)]
#[error("inconsistent transcript")]
pub struct InconsistentTranscript {}
#[cfg(test)]
mod tests {
use super::TranscriptRefs;
use mpz_memory_core::{binary::U8, FromRaw, Slice, Vector};
use rangeset::RangeSet;
use std::ops::Range;
use tlsn_core::transcript::{Direction, Idx};
// TRANSCRIPT_REFS:
//
// 48..96 -> 6 slots
// 112..176 -> 8 slots
// 240..288 -> 6 slots
// 352..392 -> 5 slots
// 440..480 -> 5 slots
const TRANSCRIPT_REFS: &[Range<usize>] = &[48..96, 112..176, 240..288, 352..392, 440..480];
const IDXS: &[Range<usize>] = &[0..4, 5..10, 14..16, 16..28];
// 1. Take slots 0..4, 4 slots -> 48..80 (4)
// 2. Take slots 5..10, 5 slots -> 88..96 (1) + 112..144 (4)
// 3. Take slots 14..16, 2 slots -> 240..256 (2)
// 4. Take slots 16..28, 12 slots -> 256..288 (4) + 352..392 (5) + 440..464 (3)
//
// 5. Merge slots 240..256 and 256..288 => 240..288 and get EXPECTED_REFS
const EXPECTED_REFS: &[Range<usize>] =
&[48..80, 88..96, 112..144, 240..288, 352..392, 440..464];
#[test]
fn test_transcript_refs_get() {
let transcript_refs: Vec<Vector<U8>> = TRANSCRIPT_REFS
.iter()
.cloned()
.map(|range| Vector::from_raw(Slice::from_range_unchecked(range)))
.collect();
let transcript_refs = TranscriptRefs {
sent: transcript_refs.clone(),
recv: transcript_refs,
};
let vm_refs = transcript_refs
.get(Direction::Sent, &idx_fixture())
.unwrap();
let expected_refs: Vec<Vector<U8>> = EXPECTED_REFS
.iter()
.cloned()
.map(|range| Vector::from_raw(Slice::from_range_unchecked(range)))
.collect();
assert_eq!(
vm_refs.len(),
expected_refs.len(),
"Length of actual and expected refs are not equal"
);
for (&expected, actual) in expected_refs.iter().zip(vm_refs) {
assert_eq!(expected, actual);
}
}
fn idx_fixture() -> Idx {
let set = RangeSet::from(IDXS);
Idx::builder().union(&set).build()
}
}

View File

@@ -1,210 +0,0 @@
//! Zero-knowledge AES-CTR encryption.
use cipher::{
aes::{Aes128, AesError},
Cipher, CipherError, Keystream,
};
use mpz_memory_core::{
binary::{Binary, U8},
Array, Vector,
};
use mpz_vm_core::{prelude::*, Vm};
use crate::Role;
type Nonce = Array<U8, 8>;
type Ctr = Array<U8, 4>;
type Block = Array<U8, 16>;
const START_CTR: u32 = 2;
/// ZK AES-CTR encryption.
#[derive(Debug)]
pub struct ZkAesCtr {
role: Role,
aes: Aes128,
state: State,
}
impl ZkAesCtr {
/// Creates a new ZK AES-CTR instance.
pub fn new(role: Role) -> Self {
Self {
role,
aes: Aes128::default(),
state: State::Init,
}
}
/// Returns the role.
pub fn role(&self) -> &Role {
&self.role
}
/// Allocates `len` bytes for encryption.
pub fn alloc(&mut self, vm: &mut dyn Vm<Binary>, len: usize) -> Result<(), ZkAesCtrError> {
let State::Init = self.state.take() else {
Err(ErrorRepr::State {
reason: "must be in init state to allocate",
})?
};
// Round up to the nearest block size.
let len = 16 * len.div_ceil(16);
let input = vm.alloc_vec::<U8>(len).map_err(ZkAesCtrError::vm)?;
let keystream = self.aes.alloc_keystream(vm, len)?;
match self.role {
Role::Prover => vm.mark_private(input).map_err(ZkAesCtrError::vm)?,
Role::Verifier => vm.mark_blind(input).map_err(ZkAesCtrError::vm)?,
}
self.state = State::Ready { input, keystream };
Ok(())
}
/// Sets the key and IV for the cipher.
pub fn set_key(&mut self, key: Array<U8, 16>, iv: Array<U8, 4>) {
self.aes.set_key(key);
self.aes.set_iv(iv);
}
/// Proves the encryption of `len` bytes.
///
/// Here we only assign certain values in the VM but the actual proving
/// happens later when the plaintext is assigned and the VM is executed.
///
/// # Arguments
///
/// * `vm` - Virtual machine.
/// * `explicit_nonce` - Explicit nonce.
/// * `len` - Length of the plaintext in bytes.
///
/// # Returns
///
/// A VM reference to the plaintext and the ciphertext.
pub fn encrypt(
&mut self,
vm: &mut dyn Vm<Binary>,
explicit_nonce: Vec<u8>,
len: usize,
) -> Result<(Vector<U8>, Vector<U8>), ZkAesCtrError> {
let State::Ready { input, keystream } = &mut self.state else {
Err(ErrorRepr::State {
reason: "must be in ready state to encrypt",
})?
};
let explicit_nonce: [u8; 8] =
explicit_nonce
.try_into()
.map_err(|explicit_nonce: Vec<_>| ErrorRepr::ExplicitNonceLength {
expected: 8,
actual: explicit_nonce.len(),
})?;
let block_count = len.div_ceil(16);
let padded_len = block_count * 16;
let padding_len = padded_len - len;
if padded_len > input.len() {
Err(ErrorRepr::InsufficientPreprocessing {
expected: padded_len,
actual: input.len(),
})?
}
let mut input = input.split_off(input.len() - padded_len);
let keystream = keystream.consume(padded_len)?;
let mut output = keystream.apply(vm, input)?;
// Assign counter block inputs.
let mut ctr = START_CTR..;
keystream.assign(vm, explicit_nonce, move || {
ctr.next().expect("range is unbounded").to_be_bytes()
})?;
// Assign zeroes to the padding.
if padding_len > 0 {
let padding = input.split_off(input.len() - padding_len);
// To simplify the impl, we don't mark the padding as public, that's why only
// the prover assigns it.
if let Role::Prover = self.role {
vm.assign(padding, vec![0; padding_len])
.map_err(ZkAesCtrError::vm)?;
}
vm.commit(padding).map_err(ZkAesCtrError::vm)?;
output.truncate(len);
}
Ok((input, output))
}
}
enum State {
Init,
Ready {
input: Vector<U8>,
keystream: Keystream<Nonce, Ctr, Block>,
},
Error,
}
impl State {
fn take(&mut self) -> Self {
std::mem::replace(self, State::Error)
}
}
impl std::fmt::Debug for State {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
State::Init => write!(f, "Init"),
State::Ready { .. } => write!(f, "Ready"),
State::Error => write!(f, "Error"),
}
}
}
/// Error for [`ZkAesCtr`].
#[derive(Debug, thiserror::Error)]
#[error(transparent)]
pub struct ZkAesCtrError(#[from] ErrorRepr);
impl ZkAesCtrError {
fn vm<E>(err: E) -> Self
where
E: Into<Box<dyn std::error::Error + Send + Sync + 'static>>,
{
Self(ErrorRepr::Vm(err.into()))
}
}
#[derive(Debug, thiserror::Error)]
#[error("zk aes error")]
enum ErrorRepr {
#[error("invalid state: {reason}")]
State { reason: &'static str },
#[error("cipher error: {0}")]
Cipher(Box<dyn std::error::Error + Send + Sync + 'static>),
#[error("vm error: {0}")]
Vm(Box<dyn std::error::Error + Send + Sync + 'static>),
#[error("invalid explicit nonce length: expected {expected}, got {actual}")]
ExplicitNonceLength { expected: usize, actual: usize },
#[error("insufficient preprocessing: expected {expected}, got {actual}")]
InsufficientPreprocessing { expected: usize, actual: usize },
}
impl From<AesError> for ZkAesCtrError {
fn from(err: AesError) -> Self {
Self(ErrorRepr::Cipher(Box::new(err)))
}
}
impl From<CipherError> for ZkAesCtrError {
fn from(err: CipherError) -> Self {
Self(ErrorRepr::Cipher(Box::new(err)))
}
}

View File

@@ -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.11"
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 }

View File

@@ -36,7 +36,7 @@ impl Display for AesError {
}
if let Some(source) = &self.source {
write!(f, " caused by: {}", source)?;
write!(f, " caused by: {source}")?;
}
Ok(())

View File

@@ -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();

View File

@@ -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>;

View File

@@ -1,6 +1,6 @@
[package]
name = "tlsn-deap"
version = "0.1.0-alpha.11"
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 }

View File

@@ -382,37 +382,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 +468,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 +548,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 {

View File

@@ -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.11"
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"] }

View File

@@ -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();

View File

@@ -56,7 +56,7 @@ impl fmt::Display for PrfError {
}
if let Some(ref source) = self.source {
write!(f, " caused by: {}", source)?;
write!(f, " caused by: {source}")?;
}
Ok(())

View File

@@ -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()) {

View File

@@ -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();

View File

@@ -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);

View File

@@ -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];

View File

@@ -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,
_ => (),
}

View File

@@ -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);

View File

@@ -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.11"
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"] }

View File

@@ -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)
}
}

View File

@@ -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)]

View File

@@ -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);
}
}

View File

@@ -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};

View File

@@ -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.11"
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,13 +28,13 @@ tlsn-tls-core = { workspace = true, features = ["serde"] }
tlsn-utils = { workspace = true }
rangeset = { workspace = true, features = ["serde"] }
bcs = { workspace = true }
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 }
k256 = { workspace = true }
opaque-debug = { workspace = true }
p256 = { workspace = true, features = ["serde"] }
rand = { workspace = true }
rand_core = { workspace = true }
rand_chacha = { workspace = true }
@@ -36,21 +43,21 @@ rstest = { workspace = true, optional = true }
serde = { workspace = true }
sha2 = { workspace = true }
thiserror = { workspace = true }
tiny-keccak = { version = "2.0", features = ["keccak"] }
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]
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 }
aead = { workspace = true, features = ["alloc"] }
aes-gcm = { workspace = true }
generic-array = { workspace = true }
bincode = { workspace = true }
hex = { workspace = true }
rstest = { workspace = true }
tlsn-data-fixtures = { workspace = true }
rand06-compat = { workspace = true }
[[test]]
name = "api"
required-features = ["fixtures"]
webpki-root-certs = { workspace = true }

View File

@@ -0,0 +1,7 @@
//! Configuration types.
pub mod prove;
pub mod prover;
pub mod tls;
pub mod tls_commit;
pub mod verifier;

View File

@@ -0,0 +1,189 @@
//! Proving configuration.
use rangeset::{RangeSet, ToRangeSet, UnionMut};
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,
},
}

View 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 {}

View 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 },
}

View 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 },
}

View 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,
})
}
}
}

View 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 },
}

View File

@@ -1,44 +1,12 @@
//! TLS connection types.
//!
//! ## Commitment
//!
//! During the TLS handshake the Notary receives the Server's ephemeral public
//! key, and this key serves as a binding commitment to the identity of the
//! Server. The ephemeral key itself does not reveal the Server's identity, but
//! it is bound to it via a signature created using the Server's
//! X.509 certificate.
//!
//! A Prover can withhold the Server's signature and certificate chain from the
//! Notary to improve privacy and censorship resistance.
//!
//! ## Proving the Server's identity
//!
//! A Prover can prove the Server's identity to a Verifier by sending a
//! [`ServerIdentityProof`]. This proof contains all the information required to
//! establish the link between the TLS connection and the Server's X.509
//! certificate. A Verifier checks the Server's certificate against their own
//! trust anchors, the same way a typical TLS client would.
mod commit;
mod proof;
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 _,
};
use web_time::{Duration, UNIX_EPOCH};
use tls_core::msgs::{codec::Codec, enums::NamedGroup, handshake::ServerECDHParams};
use crate::{hash::impl_domain_separator, CryptoProvider};
pub use commit::{ServerCertCommitment, ServerCertOpening};
pub use proof::{ServerIdentityProof, ServerIdentityProofError};
use crate::webpki::{CertificateDer, ServerCertVerifier, ServerCertVerifierError};
/// TLS version.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
@@ -62,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")]
@@ -106,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>,
}
@@ -198,8 +208,6 @@ pub struct ServerEphemKey {
pub key: Vec<u8>,
}
impl_domain_separator!(ServerEphemKey);
impl ServerEphemKey {
/// Encodes the key exchange parameters as in TLS.
pub(crate) fn kx_params(&self) -> Vec<u8> {
@@ -240,8 +248,6 @@ pub struct ConnectionInfo {
pub transcript_length: TranscriptLength,
}
impl_domain_separator!(ConnectionInfo);
/// Transcript length information.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct TranscriptLength {
@@ -251,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.
@@ -262,87 +268,80 @@ 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),
}
impl_domain_separator!(HandshakeData);
/// Server certificate and handshake data.
/// Verify data from the TLS handshake finished messages.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServerCertData {
/// Certificate chain.
pub certs: Vec<Certificate>,
/// Server signature of the key exchange parameters.
pub sig: ServerSignature,
/// TLS handshake data.
pub handshake: HandshakeData,
pub struct VerifyData {
/// Client finished verify data.
pub client_finished: Vec<u8>,
/// Server finished verify data.
pub server_finished: Vec<u8>,
}
impl ServerCertData {
/// Verifies the server certificate data.
/// TLS handshake data.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HandshakeData {
/// Server certificate chain.
pub certs: Vec<CertificateDer>,
/// Server certificate signature over the binding message.
pub sig: ServerSignature,
/// Certificate binding.
pub binding: CertBinding,
}
impl HandshakeData {
/// Verifies the handshake data.
///
/// # Arguments
///
/// * `provider` - The crypto provider to use for verification.
/// * `verifier` - Cerificate verifier.
/// * `time` - The time of the connection.
/// * `server_ephemeral_key` - The server's ephemeral key.
/// * `server_name` - The server name.
pub fn verify_with_provider(
pub fn verify(
&self,
provider: &CryptoProvider,
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.
provider
.cert
.verify_server_cert(
end_entity,
intermediates,
&server_name,
&mut [].into_iter(),
&[],
UNIX_EPOCH + Duration::from_secs(time),
)
.map_err(|_| CertificateVerificationError::InvalidCert)?;
verifier
.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();
@@ -350,12 +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
}
};
provider
.cert
.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(())
}
@@ -364,55 +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, provider::default_cert_verifier, transcript::Transcript,
};
use crate::{fixtures::ConnectionFixture, transcript::Transcript, webpki::RootCertStore};
use hex::FromHex;
use rstest::*;
use tls_core::verify::WebPkiVerifier;
use tlsn_data_fixtures::http::{request::GET_WITH_HEADER, response::OK_JSON};
#[fixture]
#[once]
fn crypto_provider() -> CryptoProvider {
let mut store = default_cert_verifier().root_store().clone();
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(),
);
store.add(&cert).unwrap();
CryptoProvider {
hash: Default::default(),
cert: WebPkiVerifier::new(store, None),
signer: Default::default(),
signature: Default::default(),
}
ServerCertVerifier::new(&root_store).unwrap()
}
fn tlsnotary() -> ConnectionFixture {
@@ -428,7 +443,7 @@ mod tests {
#[case::tlsnotary(tlsnotary())]
#[case::appliedzkp(appliedzkp())]
fn test_verify_cert_chain_sucess_ca_implicit(
crypto_provider: &CryptoProvider,
verifier: &ServerCertVerifier,
#[case] mut data: ConnectionFixture,
) {
// Remove the CA cert
@@ -436,11 +451,11 @@ mod tests {
assert!(data
.server_cert_data
.verify_with_provider(
crypto_provider,
.verify(
verifier,
data.connection_info.time,
data.server_ephemeral_key(),
&ServerName::from(data.server_name.as_ref()),
&data.server_name,
)
.is_ok());
}
@@ -451,16 +466,16 @@ mod tests {
#[case::tlsnotary(tlsnotary())]
#[case::appliedzkp(appliedzkp())]
fn test_verify_cert_chain_success_ca_explicit(
crypto_provider: &CryptoProvider,
verifier: &ServerCertVerifier,
#[case] data: ConnectionFixture,
) {
assert!(data
.server_cert_data
.verify_with_provider(
crypto_provider,
.verify(
verifier,
data.connection_info.time,
data.server_ephemeral_key(),
&ServerName::from(data.server_name.as_ref()),
&data.server_name,
)
.is_ok());
}
@@ -470,22 +485,22 @@ mod tests {
#[case::tlsnotary(tlsnotary())]
#[case::appliedzkp(appliedzkp())]
fn test_verify_cert_chain_fail_bad_time(
crypto_provider: &CryptoProvider,
verifier: &ServerCertVerifier,
#[case] data: ConnectionFixture,
) {
// unix time when the cert chain was NOT valid
let bad_time: u64 = 1571465711;
let err = data.server_cert_data.verify_with_provider(
crypto_provider,
let err = data.server_cert_data.verify(
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(_)
));
}
@@ -494,7 +509,7 @@ mod tests {
#[case::tlsnotary(tlsnotary())]
#[case::appliedzkp(appliedzkp())]
fn test_verify_cert_chain_fail_no_interm_cert(
crypto_provider: &CryptoProvider,
verifier: &ServerCertVerifier,
#[case] mut data: ConnectionFixture,
) {
// Remove the CA cert
@@ -502,16 +517,16 @@ mod tests {
// Remove the intermediate cert
data.server_cert_data.certs.pop();
let err = data.server_cert_data.verify_with_provider(
crypto_provider,
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(_)
));
}
@@ -521,22 +536,22 @@ mod tests {
#[case::tlsnotary(tlsnotary())]
#[case::appliedzkp(appliedzkp())]
fn test_verify_cert_chain_fail_no_interm_cert_with_ca_cert(
crypto_provider: &CryptoProvider,
verifier: &ServerCertVerifier,
#[case] mut data: ConnectionFixture,
) {
// Remove the intermediate cert
data.server_cert_data.certs.remove(1);
let err = data.server_cert_data.verify_with_provider(
crypto_provider,
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(_)
));
}
@@ -545,24 +560,24 @@ mod tests {
#[case::tlsnotary(tlsnotary())]
#[case::appliedzkp(appliedzkp())]
fn test_verify_cert_chain_fail_bad_ee_cert(
crypto_provider: &CryptoProvider,
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_with_provider(
crypto_provider,
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(_)
));
}
@@ -571,23 +586,23 @@ mod tests {
#[case::tlsnotary(tlsnotary())]
#[case::appliedzkp(appliedzkp())]
fn test_verify_sig_ke_params_fail_bad_client_random(
crypto_provider: &CryptoProvider,
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_with_provider(
crypto_provider,
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
));
}
@@ -596,21 +611,21 @@ mod tests {
#[case::tlsnotary(tlsnotary())]
#[case::appliedzkp(appliedzkp())]
fn test_verify_sig_ke_params_fail_bad_sig(
crypto_provider: &CryptoProvider,
verifier: &ServerCertVerifier,
#[case] mut data: ConnectionFixture,
) {
data.server_cert_data.sig.sig[31] = data.server_cert_data.sig.sig[31].wrapping_add(1);
let err = data.server_cert_data.verify_with_provider(
crypto_provider,
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
));
}
@@ -619,13 +634,13 @@ mod tests {
#[case::tlsnotary(tlsnotary())]
#[case::appliedzkp(appliedzkp())]
fn test_check_dns_name_present_in_cert_fail_bad_host(
crypto_provider: &CryptoProvider,
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_with_provider(
crypto_provider,
let err = data.server_cert_data.verify(
verifier,
data.connection_info.time,
data.server_ephemeral_key(),
&bad_name,
@@ -633,7 +648,7 @@ mod tests {
assert!(matches!(
err.unwrap_err(),
CertificateVerificationError::InvalidCert
HandshakeVerificationError::ServerCert(_)
));
}
@@ -641,25 +656,22 @@ mod tests {
#[rstest]
#[case::tlsnotary(tlsnotary())]
#[case::appliedzkp(appliedzkp())]
fn test_invalid_ephemeral_key(
crypto_provider: &CryptoProvider,
#[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(),
};
let err = data.server_cert_data.verify_with_provider(
crypto_provider,
let err = data.server_cert_data.verify(
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
));
}
@@ -668,22 +680,22 @@ mod tests {
#[case::tlsnotary(tlsnotary())]
#[case::appliedzkp(appliedzkp())]
fn test_verify_cert_chain_fail_no_cert(
crypto_provider: &CryptoProvider,
verifier: &ServerCertVerifier,
#[case] mut data: ConnectionFixture,
) {
// Empty certs
data.server_cert_data.certs = Vec::new();
let err = data.server_cert_data.verify_with_provider(
crypto_provider,
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::MissingCerts
HandshakeVerificationError::MissingCerts
));
}
}

View File

@@ -1,40 +0,0 @@
//! Types for committing details of a connection.
use serde::{Deserialize, Serialize};
use crate::{
connection::ServerCertData,
hash::{impl_domain_separator, Blinded, HashAlgorithm, HashAlgorithmExt, TypedHash},
};
/// Opens a [`ServerCertCommitment`].
#[derive(Clone, Serialize, Deserialize)]
pub struct ServerCertOpening(Blinded<ServerCertData>);
impl_domain_separator!(ServerCertOpening);
opaque_debug::implement!(ServerCertOpening);
impl ServerCertOpening {
pub(crate) fn new(data: ServerCertData) -> Self {
Self(Blinded::new(data))
}
pub(crate) fn commit(&self, hasher: &dyn HashAlgorithm) -> ServerCertCommitment {
ServerCertCommitment(TypedHash {
alg: hasher.id(),
value: hasher.hash_separated(self),
})
}
/// Returns the server identity data.
pub fn data(&self) -> &ServerCertData {
self.0.data()
}
}
/// Commitment to a server certificate.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ServerCertCommitment(pub(crate) TypedHash);
impl_domain_separator!(ServerCertCommitment);

View File

@@ -1,103 +0,0 @@
//! Types for proving details of a connection.
use serde::{Deserialize, Serialize};
use crate::{
connection::{
commit::{ServerCertCommitment, ServerCertOpening},
CertificateVerificationError, ServerEphemKey, ServerName,
},
hash::{HashAlgorithmExt, HashProviderError},
CryptoProvider,
};
/// TLS server identity proof.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServerIdentityProof {
name: ServerName,
opening: ServerCertOpening,
}
impl ServerIdentityProof {
pub(crate) fn new(name: ServerName, opening: ServerCertOpening) -> Self {
Self { name, opening }
}
/// Verifies the server identity proof.
///
/// # Arguments
///
/// * `provider` - The crypto provider to use for verification.
/// * `time` - The time of the connection.
/// * `server_ephemeral_key` - The server's ephemeral key.
/// * `commitment` - Commitment to the server certificate.
pub fn verify_with_provider(
self,
provider: &CryptoProvider,
time: u64,
server_ephemeral_key: &ServerEphemKey,
commitment: &ServerCertCommitment,
) -> Result<ServerName, ServerIdentityProofError> {
let hasher = provider.hash.get(&commitment.0.alg)?;
if commitment.0.value != hasher.hash_separated(&self.opening) {
return Err(ServerIdentityProofError {
kind: ErrorKind::Commitment,
message: "certificate opening does not match commitment".to_string(),
});
}
// Verify certificate and identity.
self.opening.data().verify_with_provider(
provider,
time,
server_ephemeral_key,
&self.name,
)?;
Ok(self.name)
}
}
/// Error for [`ServerIdentityProof`].
#[derive(Debug, thiserror::Error)]
#[error("server identity proof error: {kind}: {message}")]
pub struct ServerIdentityProofError {
kind: ErrorKind,
message: String,
}
impl From<HashProviderError> for ServerIdentityProofError {
fn from(err: HashProviderError) -> Self {
Self {
kind: ErrorKind::Provider,
message: err.to_string(),
}
}
}
impl From<CertificateVerificationError> for ServerIdentityProofError {
fn from(err: CertificateVerificationError) -> Self {
Self {
kind: ErrorKind::Certificate,
message: err.to_string(),
}
}
}
#[derive(Debug)]
enum ErrorKind {
Provider,
Commitment,
Certificate,
}
impl std::fmt::Display for ErrorKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ErrorKind::Provider => write!(f, "provider"),
ErrorKind::Commitment => write!(f, "commitment"),
ErrorKind::Certificate => write!(f, "certificate"),
}
}
}

View File

@@ -0,0 +1,16 @@
use rangeset::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_ranges() {
write!(f, "{}..{}", range.start, range.end)?;
if range.end < self.0.end().unwrap_or(0) {
f.write_str(", ")?;
}
}
f.write_str("}")
}
}

Some files were not shown because too many files have changed in this diff Show More