mirror of
https://github.com/tlsnotary/tlsn.git
synced 2026-01-10 22:08:09 -05:00
Compare commits
129 Commits
v0.1.0-alp
...
refactor/c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a56f2dd02b | ||
|
|
38a1ec3f72 | ||
|
|
1501bc661f | ||
|
|
0e2c2cb045 | ||
|
|
a662fb7511 | ||
|
|
be2e1ab95a | ||
|
|
d34d135bfe | ||
|
|
091d26bb63 | ||
|
|
f031fe9a8d | ||
|
|
12c9a5eb34 | ||
|
|
b4380f021e | ||
|
|
8a823d18ec | ||
|
|
7bcfc56bd8 | ||
|
|
2909d5ebaa | ||
|
|
7918494ccc | ||
|
|
92dd47b376 | ||
|
|
5474a748ce | ||
|
|
92da5adc24 | ||
|
|
e0ce1ad31a | ||
|
|
3b76877920 | ||
|
|
783355772a | ||
|
|
e5c59da90b | ||
|
|
f059c53c2d | ||
|
|
a1367b5428 | ||
|
|
9d8124ac9d | ||
|
|
5034366c72 | ||
|
|
afd8f44261 | ||
|
|
21086d2883 | ||
|
|
cca9a318a4 | ||
|
|
cb804a6025 | ||
|
|
9f849e7c18 | ||
|
|
389bceddef | ||
|
|
657838671a | ||
|
|
2f072b2578 | ||
|
|
33153d1124 | ||
|
|
2d399d5e24 | ||
|
|
b6d7249b6d | ||
|
|
2a8c1c3382 | ||
|
|
7c27162875 | ||
|
|
eef813712d | ||
|
|
2e94e08fa6 | ||
|
|
97d9475335 | ||
|
|
38820d6a3f | ||
|
|
af85fa100f | ||
|
|
008b901913 | ||
|
|
db85f68328 | ||
|
|
fb80aa4cc9 | ||
|
|
8dae57d6a7 | ||
|
|
f2ff4ba792 | ||
|
|
9bf3371873 | ||
|
|
9d853eb496 | ||
|
|
6923ceefd3 | ||
|
|
5239c2328a | ||
|
|
6a7c5384a9 | ||
|
|
7e469006c0 | ||
|
|
55091b5e94 | ||
|
|
bc1eba18c9 | ||
|
|
c128ab16ce | ||
|
|
a87125ff88 | ||
|
|
0933d711d2 | ||
|
|
79c230f2fa | ||
|
|
345d5d45ad | ||
|
|
55a26aad77 | ||
|
|
1132d441e1 | ||
|
|
fa2fdfd601 | ||
|
|
24e10d664f | ||
|
|
c0e084c1ca | ||
|
|
b6845dfc5c | ||
|
|
31def9ea81 | ||
|
|
878fe7e87d | ||
|
|
3348ac34b6 | ||
|
|
82767ca2d5 | ||
|
|
c9aaf2e0fa | ||
|
|
241ed3b5a3 | ||
|
|
56f088db7d | ||
|
|
f5250479bd | ||
|
|
0e2eabb833 | ||
|
|
ad530ca500 | ||
|
|
8b1cac6fe0 | ||
|
|
555f65e6b2 | ||
|
|
046485188c | ||
|
|
db53814ee7 | ||
|
|
d924bd6deb | ||
|
|
b3558bef9c | ||
|
|
33c4b9d16f | ||
|
|
edc2a1783d | ||
|
|
c2a6546deb | ||
|
|
2dfa386415 | ||
|
|
5a188e75c7 | ||
|
|
a8bf1026ca | ||
|
|
f900fc51cd | ||
|
|
6ccf102ec8 | ||
|
|
2c500b13bd | ||
|
|
2da0c242cb | ||
|
|
798c22409a | ||
|
|
3b5ac20d5b | ||
|
|
a063f8cc14 | ||
|
|
6f6b24e76c | ||
|
|
a28718923b | ||
|
|
19447aabe5 | ||
|
|
8afb7a4c11 | ||
|
|
43c6877ec0 | ||
|
|
39e14949a0 | ||
|
|
31f62982b5 | ||
|
|
6623734ca0 | ||
|
|
41e215f912 | ||
|
|
9e0f79125b | ||
|
|
7bdd3a724b | ||
|
|
baa486ccfd | ||
|
|
de7a47de5b | ||
|
|
3a57134b3a | ||
|
|
86fed1a90c | ||
|
|
82964c273b | ||
|
|
81aaa338e6 | ||
|
|
f331a7a3c5 | ||
|
|
adb407d03b | ||
|
|
3e54119867 | ||
|
|
71aa90de88 | ||
|
|
93535ca955 | ||
|
|
a34dd57926 | ||
|
|
92d7b59ee8 | ||
|
|
c8e9cb370e | ||
|
|
4dc5570a31 | ||
|
|
198e24c5e4 | ||
|
|
f16d7238e5 | ||
|
|
9253adaaa4 | ||
|
|
8c889ac498 | ||
|
|
f0e2200d22 | ||
|
|
224e41a186 |
3
.github/codecov.yml
vendored
Normal file
3
.github/codecov.yml
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
github_checks:
|
||||
annotations: false
|
||||
comment: false
|
||||
43
.github/scripts/gramine.sh
vendored
43
.github/scripts/gramine.sh
vendored
@@ -1,43 +0,0 @@
|
||||
#/bin/sh
|
||||
# this is to be ran in a docker container via an github action that has gramine set-up already e.g.,
|
||||
# notaryserverbuilds.azurecr.io/builder/gramine
|
||||
# with sgx hardware:
|
||||
# ./gramine.sh sgx
|
||||
#
|
||||
# without:
|
||||
# ./gramine.sh
|
||||
##
|
||||
|
||||
if [ -z "$1" ]
|
||||
then
|
||||
run='gramine-direct notary-server &'
|
||||
|
||||
else
|
||||
run='gramine-sgx notary-server &'
|
||||
fi
|
||||
|
||||
|
||||
|
||||
curl https://sh.rustup.rs -sSf | sh -s -- -y
|
||||
. "$HOME/.cargo/env"
|
||||
apt install libssl-dev
|
||||
|
||||
gramine-sgx-gen-private-key
|
||||
SGX=1 make
|
||||
gramine-sgx-sign -m notary-server.manifest -o notary-server.sgx
|
||||
mr_enclave=$(gramine-sgx-sigstruct-view --verbose --output-format=json notary-server.sig |jq .mr_enclave)
|
||||
echo "mrenclave=$mr_enclave" >> "$GITHUB_OUTPUT"
|
||||
echo "#### sgx mrenclave" | tee >> $GITHUB_STEP_SUMMARY
|
||||
echo "\`\`\`${mr_enclave}\`\`\`" | tee >> $GITHUB_STEP_SUMMARY
|
||||
eval "$run"
|
||||
sleep 5
|
||||
|
||||
if [ "$1" ]; then
|
||||
curl 127.0.0.1:7047/info
|
||||
else
|
||||
quote=$(curl 127.0.0.1:7047/info | jq .quote.rawQuote)
|
||||
echo $quote
|
||||
echo "quote=$quote" >> $GITHUB_OUTPUT
|
||||
echo "#### 🔒 signed quote ${quote}" | tee >> $GITHUB_STEP_SUMMARY
|
||||
echo "${quote}" | tee >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
14
.github/workflows/bench.yml
vendored
14
.github/workflows/bench.yml
vendored
@@ -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
|
||||
258
.github/workflows/ci.yml
vendored
258
.github/workflows/ci.yml
vendored
@@ -11,7 +11,6 @@ on:
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: read
|
||||
attestations: write
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
@@ -22,7 +21,7 @@ env:
|
||||
# - https://github.com/privacy-scaling-explorations/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.89.0
|
||||
|
||||
jobs:
|
||||
clippy:
|
||||
@@ -32,17 +31,17 @@ jobs:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install stable rust toolchain
|
||||
- name: Install rust toolchain
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: stable
|
||||
toolchain: ${{ env.RUST_VERSION }}
|
||||
components: clippy
|
||||
|
||||
- name: Use caching
|
||||
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 -- -D warnings
|
||||
|
||||
fmt:
|
||||
name: Check formatting
|
||||
@@ -71,19 +70,19 @@ jobs:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install stable rust toolchain
|
||||
- name: Install rust toolchain
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: stable
|
||||
toolchain: ${{ env.RUST_VERSION }}
|
||||
|
||||
- name: Use caching
|
||||
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
|
||||
@@ -92,11 +91,11 @@ jobs:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install stable rust toolchain
|
||||
- name: Install rust toolchain
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
targets: wasm32-unknown-unknown
|
||||
toolchain: stable
|
||||
toolchain: ${{ env.RUST_VERSION }}
|
||||
|
||||
- name: Install nightly rust toolchain
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
@@ -111,25 +110,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/')
|
||||
@@ -146,19 +149,16 @@ jobs:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install stable rust toolchain
|
||||
- name: Install rust toolchain
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: stable
|
||||
toolchain: ${{ env.RUST_VERSION }}
|
||||
|
||||
- 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
|
||||
@@ -166,14 +166,14 @@ jobs:
|
||||
CARGO_TERM_COLOR: always
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install stable rust toolchain
|
||||
- name: Install rust toolchain
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: stable
|
||||
toolchain: ${{ env.RUST_VERSION }}
|
||||
- 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:
|
||||
@@ -181,201 +181,21 @@ jobs:
|
||||
files: lcov.info
|
||||
fail_ci_if_error: true
|
||||
|
||||
build-sgx:
|
||||
runs-on: ubuntu-latest
|
||||
create-release-draft:
|
||||
name: Create Release Draft
|
||||
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
|
||||
environment: tee
|
||||
container:
|
||||
image: gramineproject/gramine:latest
|
||||
|
||||
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/config
|
||||
crates/notary/server/tee/notary-server-sgx.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: 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: Create Dockerfile
|
||||
run: |
|
||||
cat <<EOF > ./Dockerfile
|
||||
FROM gramineproject/gramine:latest
|
||||
WORKDIR /work
|
||||
COPY ./notary-server-sgx /work
|
||||
RUN chmod +x /work/notary-server
|
||||
LABEL org.opencontainers.image.source=https://github.com/tlsnotary/tlsn
|
||||
LABEL org.opencontainers.image.description="TLSNotary notary server in SGX/Gramine."
|
||||
ENTRYPOINT ["gramine-sgx", "notary-server"]
|
||||
EOF
|
||||
|
||||
- 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: ./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, '.'))
|
||||
contents: write
|
||||
if: 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
|
||||
- name: Create GitHub Release Draft
|
||||
uses: softprops/action-gh-release@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
|
||||
draft: true
|
||||
tag_name: ${{ github.ref_name }}
|
||||
prerelease: true
|
||||
generate_release_notes: true
|
||||
|
||||
59
.github/workflows/releng.yml
vendored
59
.github/workflows/releng.yml
vendored
@@ -6,22 +6,57 @@ on:
|
||||
tag:
|
||||
description: 'Tag to publish to NPM'
|
||||
required: true
|
||||
default: '0.1.0-alpha.9'
|
||||
default: 'v0.1.0-alpha.13-pre'
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
|
||||
steps:
|
||||
- name: Download build artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: ${{ github.event.inputs.tag }}-tlsn-wasm-pkg
|
||||
path: tlsn-wasm-pkg
|
||||
- name: Find and download tlsn-wasm build from the tagged ci workflow
|
||||
id: find_run
|
||||
run: |
|
||||
# Find the workflow run ID for the tag
|
||||
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' | sort | tail -1)
|
||||
|
||||
- name: NPM Publish for tlsn-wasm
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
run: |
|
||||
cd tlsn-wasm-pkg
|
||||
npm publish
|
||||
if [ -z "$RUN_ID" ]; then
|
||||
echo "No run found for tag ${{ github.event.inputs.tag }}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Found run: $RUN_ID"
|
||||
echo "run_id=$RUN_ID" >> "$GITHUB_OUTPUT"
|
||||
|
||||
# Find the download URL for the build artifact
|
||||
DOWNLOAD_URL=$(gh api \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
/repos/tlsnotary/tlsn/actions/runs/${RUN_ID}/artifacts \
|
||||
--jq '.artifacts[] | select(.name == "${{ github.event.inputs.tag }}-tlsn-wasm-pkg") | .archive_download_url')
|
||||
|
||||
if [ -z "$DOWNLOAD_URL" ]; then
|
||||
echo "No download url for build artifact ${{ github.event.inputs.tag }}-tlsn-wasm-pkg in run $RUN_ID"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Download and unzip the build artifact
|
||||
mkdir tlsn-wasm-pkg
|
||||
curl -L -H "Authorization: Bearer ${GH_TOKEN}" \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
-o tlsn-wasm-pkg.zip \
|
||||
${DOWNLOAD_URL}
|
||||
unzip -q tlsn-wasm-pkg.zip -d tlsn-wasm-pkg
|
||||
|
||||
|
||||
- name: NPM Publish for tlsn-wasm
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
run: |
|
||||
cd tlsn-wasm-pkg
|
||||
echo "//registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN}" > .npmrc
|
||||
npm publish
|
||||
rm .npmrc
|
||||
|
||||
9
.github/workflows/rustdoc.yml
vendored
9
.github/workflows/rustdoc.yml
vendored
@@ -21,18 +21,13 @@ jobs:
|
||||
toolchain: stable
|
||||
|
||||
- name: "rustdoc"
|
||||
run: cargo doc -p tlsn-core -p tlsn-prover -p tlsn-verifier --no-deps --all-features
|
||||
# --target-dir ${GITHUB_WORKSPACE}/docs
|
||||
run: crates/wasm/build-docs.sh
|
||||
|
||||
# https://dev.to/deciduously/prepare-your-rust-api-docs-for-github-pages-2n5i
|
||||
- name: "Add index file -> tlsn_prover"
|
||||
run: |
|
||||
echo "<meta http-equiv=\"refresh\" content=\"0; url=tlsn_prover\">" > target/doc/index.html
|
||||
|
||||
- name: Deploy
|
||||
uses: peaceiris/actions-gh-pages@v3
|
||||
if: ${{ github.ref == 'refs/heads/dev' }}
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
publish_dir: target/doc/
|
||||
publish_dir: target/wasm32-unknown-unknown/doc/
|
||||
# cname: rustdocs.tlsnotary.org
|
||||
|
||||
156
.github/workflows/tee-cd.yml
vendored
156
.github/workflows/tee-cd.yml
vendored
@@ -1,156 +0,0 @@
|
||||
name: azure-tee-release
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
attestations: write
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
ref:
|
||||
description: 'git branch'
|
||||
required: false
|
||||
default: 'dev'
|
||||
type: string
|
||||
|
||||
#on:
|
||||
# release:
|
||||
# types: [published]
|
||||
# branches:
|
||||
# - 'releases/**'
|
||||
|
||||
env:
|
||||
GIT_COMMIT_HASH: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
GIT_COMMIT_TIMESTAMP: ${{ github.event.repository.updated_at}}
|
||||
REGISTRY: notaryserverbuilds.azurecr.io
|
||||
IMAGE_NAME: ${{ github.repository }}
|
||||
|
||||
jobs:
|
||||
update-reverse-proxy:
|
||||
permissions:
|
||||
contents: write
|
||||
environment: tee
|
||||
runs-on: [self-hosted, linux]
|
||||
outputs:
|
||||
teeport: ${{ steps.portbump.outputs.newport}}
|
||||
deploy: ${{ steps.portbump.outputs.deploy}}
|
||||
steps:
|
||||
- name: checkout repository
|
||||
uses: actions/checkout@v4
|
||||
- name: update caddyfile
|
||||
id: portbump
|
||||
env:
|
||||
RELEASE_TAG: ${{ github.event.release.tag_name || inputs.ref }}
|
||||
run: |
|
||||
echo "tag: $RELEASE_TAG"
|
||||
NEXT_PORT=$(bash cd-scripts/tee/azure/updateproxy.sh 'cd-scripts/tee/azure/Caddyfile' $RELEASE_TAG)
|
||||
echo "newport=$NEXT_PORT" >> $GITHUB_OUTPUT
|
||||
echo "new deploy port: $NEXT_PORT 🚀" >> $GITHUB_STEP_SUMMARY
|
||||
chmod +r -R cd-scripts/tee/azure/
|
||||
- name: Deploy updated Caddyfile to server
|
||||
if: ${{ steps.portbump.outputs.deploy == 'new' }}
|
||||
uses: appleboy/scp-action@v0.1.7
|
||||
with:
|
||||
host: ${{ secrets.AZURE_TEE_PROD_HOST }}
|
||||
username: ${{ secrets.AZURE_PROD_TEE_USERNAME }}
|
||||
key: ${{ secrets.AZURE_TEE_PROD_KEY }}
|
||||
source: "cd-scripts/tee/azure/Caddyfile"
|
||||
target: "~/"
|
||||
- name: Reload Caddy on server
|
||||
if: ${{ steps.portbump.outputs.deploy == 'new' }}
|
||||
uses: appleboy/ssh-action@v1.0.3
|
||||
with:
|
||||
host: ${{ secrets.AZURE_TEE_PROD_HOST }}
|
||||
username: ${{ secrets.AZURE_PROD_TEE_USERNAME }}
|
||||
key: ${{ secrets.AZURE_TEE_PROD_KEY }}
|
||||
script: |
|
||||
sudo cp ~/cd-scripts/tee/azure/Caddyfile /etc/caddy/Caddyfile
|
||||
sudo systemctl reload caddy
|
||||
build-measure:
|
||||
environment: tee
|
||||
runs-on: [self-hosted, linux]
|
||||
needs: [ update-reverse-proxy ]
|
||||
container:
|
||||
image: notaryserverbuilds.azurecr.io/prod/gramine
|
||||
credentials:
|
||||
username: notaryserverbuilds
|
||||
password: ${{ secrets.AZURE_CR_BUILDS_PW }}
|
||||
env:
|
||||
GIT_COMMIT_HASH: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
volumes:
|
||||
- /var/run/aesmd/aesm.socket:/var/run/aesmd/aesm.socket
|
||||
options: "--device /dev/sgx_enclave"
|
||||
steps:
|
||||
- name: get code
|
||||
uses: actions/checkout@v4
|
||||
- name: sccache
|
||||
if: github.event_name != 'release'
|
||||
# && github.event_name != 'workflow_dispatch'
|
||||
uses: mozilla-actions/sccache-action@v0.0.6
|
||||
- name: set rust env for scc
|
||||
if: github.event_name != 'release'
|
||||
# && github.event_name != 'workflow_dispatch'
|
||||
run: |
|
||||
echo "SCCACHE_GHA_ENABLED=true" >> $GITHUB_ENV
|
||||
echo "RUSTC_WRAPPER=sccache" >> $GITHUB_ENV
|
||||
- name: reverse proxy port
|
||||
run: echo "${{needs.update-reverse-proxy.outputs.teeport}}" | tee >> $GITHUB_STEP_SUMMARY
|
||||
- name: get hardware measurement
|
||||
working-directory: ${{ github.workspace }}/crates/notary/server/tee
|
||||
run: |
|
||||
chmod +x ../../../../.github/scripts/gramine.sh && ../../../../.github/scripts/gramine.sh sgx
|
||||
artifact-deploy:
|
||||
environment: tee
|
||||
runs-on: [self-hosted, linux]
|
||||
needs: [ build-measure, update-reverse-proxy ]
|
||||
steps:
|
||||
- name: auth to registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: notaryserverbuilds.azurecr.io
|
||||
username: notaryserverbuilds
|
||||
password: ${{ secrets.AZURE_CR_BUILDS_PW }}
|
||||
- name: get code
|
||||
uses: actions/checkout@v4
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: Get Git commit timestamps
|
||||
run: echo "TIMESTAMP=$(git log -1 --pretty=%ct)" >> $GITHUB_ENV
|
||||
- name: Build and push
|
||||
id: deploypush
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
provenance: mode=max
|
||||
no-cache: true
|
||||
context: ${{ github.workspace }}/crates/notary/server/tee
|
||||
push: true
|
||||
tags: notaryserverbuilds.azurecr.io/prod/notary-sgx:${{ env.GIT_COMMIT_HASH }}
|
||||
labels: ${{needs.update-reverse-proxy.outputs.teeport}}
|
||||
env:
|
||||
# reproducible builds: https://github.com/moby/buildkit/blob/master/docs/build-repro.md#source_date_epoch
|
||||
SOURCE_DATE_EPOCH: ${{ env.TIMESTAMP }}
|
||||
- name: Generate SBOM
|
||||
uses: anchore/sbom-action@v0
|
||||
with:
|
||||
image: notaryserverbuilds.azurecr.io/prod/notary-sgx:${{ env.GIT_COMMIT_HASH }}
|
||||
format: 'cyclonedx-json'
|
||||
output-file: 'sbom.cyclonedx.json'
|
||||
# attestation section ::
|
||||
# https://docs.docker.com/build/ci/github-actions/attestations/
|
||||
- name: Attest
|
||||
uses: actions/attest-build-provenance@v1
|
||||
with:
|
||||
subject-name: notaryserverbuilds.azurecr.io/prod/notary-sgx
|
||||
subject-digest: ${{ steps.deploypush.outputs.digest }}
|
||||
push-to-registry: true
|
||||
-
|
||||
name: run
|
||||
run: |
|
||||
if [[ ${{ needs.update-reverse-proxy.outputs.deploy }} == 'new' ]]; then
|
||||
docker run --device /dev/sgx_enclave --device /dev/sgx_provision --volume=/var/run/aesmd/aesm.socket:/var/run/aesmd/aesm.socket -p ${{needs.update-reverse-proxy.outputs.teeport}}:7047 notaryserverbuilds.azurecr.io/prod/notary-sgx:${{ env.GIT_COMMIT_HASH }} &
|
||||
else
|
||||
old=$(docker ps --filter "name=${{needs.update-reverse-proxy.outputs.teeport}}")
|
||||
docker rm -f $old
|
||||
docker run --name ${{needs.update-reverse-proxy.outputs.teeport}} --device /dev/sgx_enclave --device /dev/sgx_provision --volume=/var/run/aesmd/aesm.socket:/var/run/aesmd/aesm.socket -p ${{needs.update-reverse-proxy.outputs.teeport}}:7047 notaryserverbuilds.azurecr.io/prod/notary-sgx:${{ env.GIT_COMMIT_HASH }} &
|
||||
fi
|
||||
24
.github/workflows/updatemain.yml
vendored
Normal file
24
.github/workflows/updatemain.yml
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
name: Fast-forward main branch to published release tag
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
ff-main-to-release:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
- name: Checkout main
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: main
|
||||
|
||||
- name: Fast-forward main to release tag
|
||||
run: |
|
||||
tag="${{ github.event.release.tag_name }}"
|
||||
git fetch origin "refs/tags/$tag:refs/tags/$tag"
|
||||
git merge --ff-only "refs/tags/$tag"
|
||||
git push origin main
|
||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -3,10 +3,6 @@
|
||||
debug/
|
||||
target/
|
||||
|
||||
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
|
||||
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
|
||||
Cargo.lock
|
||||
|
||||
# These are backup files generated by rustfmt
|
||||
**/*.rs.bk
|
||||
|
||||
@@ -32,4 +28,4 @@ Cargo.lock
|
||||
*.log
|
||||
|
||||
# metrics
|
||||
*.csv
|
||||
*.csv
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
7718
Cargo.lock
generated
Normal file
7718
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
116
Cargo.toml
116
Cargo.toml
@@ -1,102 +1,107 @@
|
||||
[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",
|
||||
"crates/components/hmac-sha256-circuits",
|
||||
"crates/components/key-exchange",
|
||||
"crates/core",
|
||||
"crates/data-fixtures",
|
||||
"crates/examples",
|
||||
"crates/formats",
|
||||
"crates/notary/client",
|
||||
"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"
|
||||
|
||||
[workspace.lints.rust]
|
||||
# unsafe_code = "forbid"
|
||||
|
||||
[workspace.lints.clippy]
|
||||
# enum_glob_use = "deny"
|
||||
|
||||
[profile.tests-integration]
|
||||
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-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" }
|
||||
tlsn-formats = { path = "crates/formats" }
|
||||
tlsn-hmac-sha256 = { path = "crates/components/hmac-sha256" }
|
||||
tlsn-hmac-sha256-circuits = { path = "crates/components/hmac-sha256-circuits" }
|
||||
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 = "6650a95" }
|
||||
tlsn-utils-aio = { git = "https://github.com/tlsnotary/tlsn-utils", rev = "6650a95" }
|
||||
tlsn-verifier = { path = "crates/verifier" }
|
||||
tlsn-utils = { git = "https://github.com/tlsnotary/tlsn-utils", rev = "6168663" }
|
||||
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.2" }
|
||||
mpz-memory-core = { git = "https://github.com/privacy-scaling-explorations/mpz", tag = "v0.1.0-alpha.2" }
|
||||
mpz-common = { git = "https://github.com/privacy-scaling-explorations/mpz", tag = "v0.1.0-alpha.2" }
|
||||
mpz-core = { git = "https://github.com/privacy-scaling-explorations/mpz", tag = "v0.1.0-alpha.2" }
|
||||
mpz-vm-core = { git = "https://github.com/privacy-scaling-explorations/mpz", tag = "v0.1.0-alpha.2" }
|
||||
mpz-garble = { git = "https://github.com/privacy-scaling-explorations/mpz", tag = "v0.1.0-alpha.2" }
|
||||
mpz-garble-core = { git = "https://github.com/privacy-scaling-explorations/mpz", tag = "v0.1.0-alpha.2" }
|
||||
mpz-ole = { git = "https://github.com/privacy-scaling-explorations/mpz", tag = "v0.1.0-alpha.2" }
|
||||
mpz-ot = { git = "https://github.com/privacy-scaling-explorations/mpz", tag = "v0.1.0-alpha.2" }
|
||||
mpz-share-conversion = { git = "https://github.com/privacy-scaling-explorations/mpz", tag = "v0.1.0-alpha.2" }
|
||||
mpz-fields = { git = "https://github.com/privacy-scaling-explorations/mpz", tag = "v0.1.0-alpha.2" }
|
||||
mpz-zk = { git = "https://github.com/privacy-scaling-explorations/mpz", tag = "v0.1.0-alpha.2" }
|
||||
mpz-circuits = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "3d90b6c" }
|
||||
mpz-memory-core = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "3d90b6c" }
|
||||
mpz-common = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "3d90b6c" }
|
||||
mpz-core = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "3d90b6c" }
|
||||
mpz-vm-core = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "3d90b6c" }
|
||||
mpz-garble = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "3d90b6c" }
|
||||
mpz-garble-core = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "3d90b6c" }
|
||||
mpz-ole = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "3d90b6c" }
|
||||
mpz-ot = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "3d90b6c" }
|
||||
mpz-share-conversion = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "3d90b6c" }
|
||||
mpz-fields = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "3d90b6c" }
|
||||
mpz-zk = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "3d90b6c" }
|
||||
mpz-hash = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "3d90b6c" }
|
||||
|
||||
rangeset = { version = "0.1" }
|
||||
rangeset = { version = "0.2" }
|
||||
serio = { version = "0.2" }
|
||||
spansy = { git = "https://github.com/tlsnotary/tlsn-utils", rev = "6650a95" }
|
||||
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 = "6650a95" }
|
||||
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" }
|
||||
cipher-crypto = { package = "cipher", version = "0.4" }
|
||||
clap = { version = "4.5" }
|
||||
criterion = { version = "0.5" }
|
||||
ctr = { version = "0.9" }
|
||||
derive_builder = { version = "0.12" }
|
||||
@@ -106,22 +111,25 @@ 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" }
|
||||
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" }
|
||||
lipsum = { version = "0.9" }
|
||||
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" }
|
||||
@@ -133,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" }
|
||||
|
||||
@@ -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
|
||||
|
||||
31
appspec.yml
31
appspec.yml
@@ -1,31 +0,0 @@
|
||||
# AWS CodeDeploy application specification file
|
||||
version: 0.0
|
||||
os: linux
|
||||
files:
|
||||
- source: /
|
||||
destination: /home/ubuntu/tlsn
|
||||
permissions:
|
||||
- object: /home/ubuntu/tlsn
|
||||
owner: ubuntu
|
||||
group: ubuntu
|
||||
hooks:
|
||||
BeforeInstall:
|
||||
- location: cd-scripts/appspec-scripts/before_install.sh
|
||||
timeout: 300
|
||||
runas: ubuntu
|
||||
AfterInstall:
|
||||
- location: cd-scripts/appspec-scripts/after_install.sh
|
||||
timeout: 300
|
||||
runas: ubuntu
|
||||
ApplicationStart:
|
||||
- location: cd-scripts/appspec-scripts/start_app.sh
|
||||
timeout: 300
|
||||
runas: ubuntu
|
||||
ApplicationStop:
|
||||
- location: cd-scripts/appspec-scripts/stop_app.sh
|
||||
timeout: 300
|
||||
runas: ubuntu
|
||||
ValidateService:
|
||||
- location: cd-scripts/appspec-scripts/validate_app.sh
|
||||
timeout: 300
|
||||
runas: ubuntu
|
||||
@@ -1,35 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
TAG=$(curl http://169.254.169.254/latest/meta-data/tags/instance/stable)
|
||||
APP_NAME=$(echo $APPLICATION_NAME | awk -F- '{ print $2 }')
|
||||
|
||||
if [ $APP_NAME = "stable" ]; then
|
||||
# Prepare directories for stable versions
|
||||
sudo mkdir ~/${APP_NAME}_${TAG}
|
||||
sudo mv ~/tlsn ~/${APP_NAME}_${TAG}
|
||||
sudo mkdir -p ~/${APP_NAME}_${TAG}/tlsn/notary/target/release
|
||||
sudo chown -R ubuntu.ubuntu ~/${APP_NAME}_${TAG}
|
||||
|
||||
# Download .git directory
|
||||
aws s3 cp s3://tlsn-deploy/$APP_NAME/.git ~/${APP_NAME}_${TAG}/tlsn/.git --recursive
|
||||
|
||||
# Download binary
|
||||
aws s3 cp s3://tlsn-deploy/$APP_NAME/notary-server ~/${APP_NAME}_${TAG}/tlsn/notary/target/release
|
||||
chmod +x ~/${APP_NAME}_${TAG}/tlsn/notary/target/release/notary-server
|
||||
else
|
||||
# Prepare directory for dev
|
||||
sudo rm -rf ~/$APP_NAME/tlsn
|
||||
sudo mv ~/tlsn/ ~/$APP_NAME
|
||||
sudo mkdir -p ~/$APP_NAME/tlsn/notary/target/release
|
||||
sudo chown -R ubuntu.ubuntu ~/$APP_NAME
|
||||
|
||||
# Download .git directory
|
||||
aws s3 cp s3://tlsn-deploy/$APP_NAME/.git ~/$APP_NAME/tlsn/.git --recursive
|
||||
|
||||
# Download binary
|
||||
aws s3 cp s3://tlsn-deploy/$APP_NAME/notary-server ~/$APP_NAME/tlsn/notary/target/release
|
||||
chmod +x ~/$APP_NAME/tlsn/notary/target/release/notary-server
|
||||
fi
|
||||
|
||||
exit 0
|
||||
@@ -1,20 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
APP_NAME=$(echo $APPLICATION_NAME | awk -F- '{ print $2 }')
|
||||
|
||||
if [ $APP_NAME = "stable" ]; then
|
||||
VERSIONS_DEPLOYED=$(find ~/ -maxdepth 1 -type d -name 'stable_*')
|
||||
VERSIONS_DEPLOYED_COUNT=$(echo $VERSIONS_DEPLOYED | wc -w)
|
||||
|
||||
if [ $VERSIONS_DEPLOYED_COUNT -gt 3 ]; then
|
||||
echo "More than 3 stable versions found"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
if [ ! -d ~/$APP_NAME ]; then
|
||||
mkdir ~/$APP_NAME
|
||||
fi
|
||||
fi
|
||||
|
||||
exit 0
|
||||
@@ -1,26 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Port tagging will also be used to manipulate proxy server via modify_proxy.sh script
|
||||
set -ex
|
||||
|
||||
TAG=$(curl http://169.254.169.254/latest/meta-data/tags/instance/stable)
|
||||
APP_NAME=$(echo $APPLICATION_NAME | awk -F- '{ print $2 }')
|
||||
|
||||
if [ $APP_NAME = "stable" ]; then
|
||||
# Check if all stable ports are in use. If true, terminate the deployment
|
||||
[[ $(netstat -lnt4 | egrep -c ':(7047|7057|7067)\s') -eq 3 ]] && { echo "All stable ports are in use"; exit 1; }
|
||||
STABLE_PORTS="7047 7057 7067"
|
||||
for PORT in $STABLE_PORTS; do
|
||||
PORT_LISTENING=$(netstat -lnt4 | egrep -cw $PORT || true)
|
||||
if [ $PORT_LISTENING -eq 0 ]; then
|
||||
~/${APP_NAME}_${TAG}/tlsn/notary/target/release/notary-server --config-file ~/.notary/${APP_NAME}_${PORT}/config.yaml &> ~/${APP_NAME}_${TAG}/tlsn/notary.log &
|
||||
# Create a tag that will be used for service validation
|
||||
INSTANCE_ID=$(curl http://169.254.169.254/latest/meta-data/instance-id)
|
||||
aws ec2 create-tags --resources $INSTANCE_ID --tags "Key=port,Value=$PORT"
|
||||
break
|
||||
fi
|
||||
done
|
||||
else
|
||||
~/$APP_NAME/tlsn/notary/target/release/notary-server --config-file ~/.notary/$APP_NAME/config.yaml &> ~/$APP_NAME/tlsn/notary.log &
|
||||
fi
|
||||
|
||||
exit 0
|
||||
@@ -1,36 +0,0 @@
|
||||
#!/bin/bash
|
||||
# AWS CodeDeploy hook sequence: https://docs.aws.amazon.com/codedeploy/latest/userguide/reference-appspec-file-structure-hooks.html#appspec-hooks-server
|
||||
set -ex
|
||||
|
||||
APP_NAME=$(echo $APPLICATION_NAME | awk -F- '{ print $2 }')
|
||||
|
||||
if [ $APP_NAME = "stable" ]; then
|
||||
VERSIONS_DEPLOYED=$(find ~/ -maxdepth 1 -type d -name 'stable_*')
|
||||
VERSIONS_DEPLOYED_COUNT=$(echo $VERSIONS_DEPLOYED | wc -w)
|
||||
|
||||
# Remove oldest version if exists
|
||||
if [ $VERSIONS_DEPLOYED_COUNT -eq 3 ]; then
|
||||
echo "Candidate versions to be removed:"
|
||||
OLDEST_DIR=""
|
||||
OLDEST_TIME=""
|
||||
|
||||
for DIR in $VERSIONS_DEPLOYED; do
|
||||
TIME=$(stat -c %W $DIR)
|
||||
|
||||
if [ -z $OLDEST_TIME ] || [ $TIME -lt $OLDEST_TIME ]; then
|
||||
OLDEST_DIR=$DIR
|
||||
OLDEST_TIME=$TIME
|
||||
fi
|
||||
done
|
||||
|
||||
echo "The oldest version is running under: $OLDEST_DIR"
|
||||
PID=$(lsof $OLDEST_DIR/tlsn/notary/target/release/notary-server | awk '{ print $2 }' | tail -1)
|
||||
kill -15 $PID || true
|
||||
rm -rf $OLDEST_DIR
|
||||
fi
|
||||
else
|
||||
PID=$(pgrep -f notary.*$APP_NAME)
|
||||
kill -15 $PID || true
|
||||
fi
|
||||
|
||||
exit 0
|
||||
@@ -1,21 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# Verify proccess is running
|
||||
APP_NAME=$(echo $APPLICATION_NAME | awk -F- '{ print $2 }')
|
||||
|
||||
# Verify that listening sockets exist
|
||||
if [ $APP_NAME = "stable" ]; then
|
||||
PORT=$(curl http://169.254.169.254/latest/meta-data/tags/instance/port)
|
||||
ps -ef | grep notary.*$APP_NAME.*$PORT | grep -v grep
|
||||
[ $? -eq 0 ] || exit 1
|
||||
else
|
||||
PORT=7048
|
||||
pgrep -f notary.*$APP_NAME
|
||||
[ $? -eq 0 ] || exit 1
|
||||
fi
|
||||
|
||||
EXPOSED_PORTS=$(netstat -lnt4 | egrep -cw $PORT)
|
||||
[ $EXPOSED_PORTS -eq 1 ] || exit 1
|
||||
|
||||
exit 0
|
||||
@@ -1,14 +0,0 @@
|
||||
#!/bin/bash
|
||||
# This script is executed on proxy side, in order to assign the available port to latest stable version
|
||||
set -e
|
||||
|
||||
PORT=$1
|
||||
VERSION=$2
|
||||
|
||||
sed -i "/# Port $PORT/{n;s/v[0-9].[0-9].[0-9]-[a-z]*.[0-9]*/$VERSION/g}" /etc/nginx/sites-available/tlsnotary-pse
|
||||
sed -i "/# Port $PORT/{n;n;s/v[0-9].[0-9].[0-9]-[a-z]*.[0-9]*/$VERSION/g}" /etc/nginx/sites-available/tlsnotary-pse
|
||||
|
||||
nginx -t
|
||||
nginx -s reload
|
||||
|
||||
exit 0
|
||||
@@ -1,90 +0,0 @@
|
||||
#
|
||||
# global block =>
|
||||
# email is for acme
|
||||
# # # #
|
||||
{
|
||||
key_type p256
|
||||
email mac@pse.dev # for acme
|
||||
servers {
|
||||
metrics
|
||||
}
|
||||
log {
|
||||
output stdout
|
||||
format console {
|
||||
time_format common_log
|
||||
time_local
|
||||
}
|
||||
level DEBUG
|
||||
}
|
||||
}
|
||||
|
||||
#
|
||||
# server block, acme turned on (default when using dns)
|
||||
# reverse proxy with fail_duration + lb will try upstreams sequentially (fallback)
|
||||
# e.g. => `reverse_proxy :4000 :5000 10.10.10.10:1000 tlsnotary.org:443`
|
||||
# will always deliver to :4000 if its up, but if :4000 is down for more than 4s it trys the next one
|
||||
# # # #
|
||||
|
||||
notary.codes {
|
||||
handle_path /v0.1.0-alpha.8* {
|
||||
reverse_proxy :4003 :3333 {
|
||||
lb_try_duration 4s
|
||||
fail_duration 10s
|
||||
lb_policy header X-Upstream {
|
||||
fallback first
|
||||
}
|
||||
}
|
||||
}
|
||||
handle_path /v0.1.0-alpha.7* {
|
||||
reverse_proxy :4002 :3333 {
|
||||
lb_try_duration 4s
|
||||
fail_duration 10s
|
||||
lb_policy header X-Upstream {
|
||||
fallback first
|
||||
}
|
||||
}
|
||||
}
|
||||
handle_path /v0.1.0-alpha.6* {
|
||||
reverse_proxy :4001 :3333 {
|
||||
lb_try_duration 4s
|
||||
fail_duration 10s
|
||||
lb_policy header X-Upstream {
|
||||
fallback first
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handle_path /nightly* {
|
||||
reverse_proxy :3333 {
|
||||
lb_try_duration 4s
|
||||
fail_duration 10s
|
||||
lb_policy header X-Upstream {
|
||||
fallback first
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handle_path /proxy* {
|
||||
reverse_proxy :55688 proxy.notary.codes:443 {
|
||||
lb_try_duration 4s
|
||||
fail_duration 10s
|
||||
lb_policy header X-Upstream {
|
||||
fallback first
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handle {
|
||||
root * /srv
|
||||
file_server
|
||||
}
|
||||
|
||||
handle_errors {
|
||||
@404 {
|
||||
expression {http.error.status_code} == 404
|
||||
}
|
||||
rewrite @404 /index.html
|
||||
file_server
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
global:
|
||||
scrape_interval: 15s
|
||||
|
||||
scrape_configs:
|
||||
- job_name: caddy
|
||||
static_configs:
|
||||
- targets: ['localhost:2019']
|
||||
@@ -1,84 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Variables (Update these as needed)x
|
||||
CADDYFILE=${1:-/etc/caddy/Caddyfile} # Path to your Caddyfile
|
||||
GIT_COMMIT_HASH=${2:-dev}
|
||||
BASE_PORT=6061 # The starting port for your reverse_proxy directives
|
||||
|
||||
# Function to check if handle_path for the given commit hash exists
|
||||
handle_path_exists() {
|
||||
local commit_hash=$1
|
||||
#echo "handle_path_exists $1 -- CADDYFILE: $CADDYFILE"
|
||||
grep -q "handle_path /${commit_hash}\*" "$CADDYFILE"
|
||||
}
|
||||
|
||||
# Function to extract the port for a given commit hash
|
||||
extract_port_for_commit() {
|
||||
local commit_hash=$1
|
||||
#echo "extract_port_for_commit $1 -- 2: $2"
|
||||
grep -Pzo "handle_path /${commit_hash}\* \{\n\s*reverse_proxy :(.*) " "$CADDYFILE" | grep -Poa "reverse_proxy :(.*) " | awk '{print $2}'
|
||||
}
|
||||
|
||||
# Function to get the last port in the Caddyfile
|
||||
get_last_port() {
|
||||
grep -Po "reverse_proxy :([0-9]+)" "$CADDYFILE" | awk -F: '{print $2}' | sort -n | tail -1
|
||||
}
|
||||
|
||||
# Function to add a new handle_path block with incremented port inside notary.codes block
|
||||
add_new_handle_path() {
|
||||
local new_port=$1
|
||||
local commit_hash=$2
|
||||
|
||||
# Use a temporary file for inserting the handle_path block
|
||||
tmp_file=$(mktemp)
|
||||
|
||||
# Add the new handle_path in the notary.codes block
|
||||
awk -v port="$new_port" -v hash="$commit_hash" '
|
||||
/notary\.codes \{/ {
|
||||
print;
|
||||
print " handle_path /" hash "* {";
|
||||
print " reverse_proxy :" port " :3333 {";
|
||||
print " lb_try_duration 4s";
|
||||
print " fail_duration 10s";
|
||||
print " lb_policy header X-Upstream {";
|
||||
print " fallback first";
|
||||
print " }";
|
||||
print " }";
|
||||
print " }";
|
||||
next;
|
||||
}
|
||||
{ print }
|
||||
' "$CADDYFILE" > "$tmp_file"
|
||||
|
||||
# Overwrite the original Caddyfile with the updated content
|
||||
mv "$tmp_file" "$CADDYFILE"
|
||||
|
||||
}
|
||||
#git action perms +r
|
||||
chmod 664 cd-scripts/tee/azure/Caddyfile
|
||||
|
||||
# Check if the commit hash already exists in a handle_path
|
||||
if handle_path_exists "$GIT_COMMIT_HASH"; then
|
||||
existing_port=$(extract_port_for_commit "$GIT_COMMIT_HASH")
|
||||
echo "${existing_port:1}"
|
||||
exit 0
|
||||
else
|
||||
# Get the last port used and increment it
|
||||
last_port=$(get_last_port)
|
||||
if [[ -z "$last_port" ]]; then
|
||||
last_port=$BASE_PORT
|
||||
fi
|
||||
new_port=$((last_port + 1))
|
||||
|
||||
# Add the new handle_path block inside notary.codes block
|
||||
add_new_handle_path "$new_port" "$GIT_COMMIT_HASH"
|
||||
echo $new_port
|
||||
# commit the changes
|
||||
git config user.name github-actions
|
||||
git config user.email github-actions@github.com
|
||||
git add -A
|
||||
git commit --quiet --allow-empty -m "azure tee reverse proxy => port:$NEXT_PORT/${RELEASE_TAG}"
|
||||
git push --quiet
|
||||
echo "deploy=new" >> $GITHUB_OUTPUT
|
||||
exit 0
|
||||
fi
|
||||
39
crates/attestation/Cargo.toml
Normal file
39
crates/attestation/Cargo.toml
Normal file
@@ -0,0 +1,39 @@
|
||||
[package]
|
||||
name = "tlsn-attestation"
|
||||
version = "0.1.0-alpha.13-pre"
|
||||
edition = "2024"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
fixtures = ["tlsn-core/fixtures", "dep:tlsn-data-fixtures"]
|
||||
|
||||
[dependencies]
|
||||
tlsn-tls-core = { workspace = true }
|
||||
tlsn-core = { workspace = true }
|
||||
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 = "0.8.22", default-features = false }
|
||||
alloy-signer = { version = "0.12", default-features = false }
|
||||
alloy-signer-local = { version = "0.12", 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"]
|
||||
@@ -1,35 +1,36 @@
|
||||
use std::error::Error;
|
||||
|
||||
use rand::{rng, Rng};
|
||||
use rand::{Rng, rng};
|
||||
|
||||
use tlsn_core::{
|
||||
connection::{ConnectionInfo, ServerEphemKey},
|
||||
hash::HashAlgId,
|
||||
transcript::TranscriptCommitment,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
attestation::{
|
||||
Attestation, AttestationConfig, Body, EncodingCommitment, FieldId, FieldKind, Header,
|
||||
ServerCertCommitment, VERSION,
|
||||
},
|
||||
connection::{ConnectionInfo, ServerEphemKey},
|
||||
hash::{HashAlgId, TypedHash},
|
||||
request::Request,
|
||||
serialize::CanonicalSerialize,
|
||||
Attestation, AttestationConfig, Body, CryptoProvider, Extension, FieldId, Header,
|
||||
ServerCertCommitment, VERSION, request::Request, serialize::CanonicalSerialize,
|
||||
signing::SignatureAlgId,
|
||||
transcript::encoding::EncoderSecret,
|
||||
CryptoProvider,
|
||||
};
|
||||
|
||||
/// Attestation builder state for accepting a request.
|
||||
#[derive(Debug)]
|
||||
pub struct Accept {}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Sign {
|
||||
signature_alg: SignatureAlgId,
|
||||
hash_alg: HashAlgId,
|
||||
connection_info: Option<ConnectionInfo>,
|
||||
server_ephemeral_key: Option<ServerEphemKey>,
|
||||
cert_commitment: ServerCertCommitment,
|
||||
encoding_commitment_root: Option<TypedHash>,
|
||||
encoder_secret: Option<EncoderSecret>,
|
||||
extensions: Vec<Extension>,
|
||||
transcript_commitments: Vec<TranscriptCommitment>,
|
||||
}
|
||||
|
||||
/// An attestation builder.
|
||||
#[derive(Debug)]
|
||||
pub struct AttestationBuilder<'a, T = Accept> {
|
||||
config: &'a AttestationConfig,
|
||||
state: T,
|
||||
@@ -55,7 +56,7 @@ impl<'a> AttestationBuilder<'a, Accept> {
|
||||
signature_alg,
|
||||
hash_alg,
|
||||
server_cert_commitment: cert_commitment,
|
||||
encoding_commitment_root,
|
||||
extensions,
|
||||
} = request;
|
||||
|
||||
if !config.supported_signature_algs().contains(&signature_alg) {
|
||||
@@ -72,15 +73,9 @@ impl<'a> AttestationBuilder<'a, Accept> {
|
||||
));
|
||||
}
|
||||
|
||||
if encoding_commitment_root.is_some()
|
||||
&& !config
|
||||
.supported_fields()
|
||||
.contains(&FieldKind::EncodingCommitment)
|
||||
{
|
||||
return Err(AttestationBuilderError::new(
|
||||
ErrorKind::Request,
|
||||
"encoding commitment is not supported",
|
||||
));
|
||||
if let Some(validator) = config.extension_validator() {
|
||||
validator(&extensions)
|
||||
.map_err(|err| AttestationBuilderError::new(ErrorKind::Extension, err))?;
|
||||
}
|
||||
|
||||
Ok(AttestationBuilder {
|
||||
@@ -91,8 +86,8 @@ impl<'a> AttestationBuilder<'a, Accept> {
|
||||
connection_info: None,
|
||||
server_ephemeral_key: None,
|
||||
cert_commitment,
|
||||
encoding_commitment_root,
|
||||
encoder_secret: None,
|
||||
transcript_commitments: Vec::new(),
|
||||
extensions,
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -111,9 +106,18 @@ impl AttestationBuilder<'_, Sign> {
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the encoder secret.
|
||||
pub fn encoder_secret(&mut self, secret: EncoderSecret) -> &mut Self {
|
||||
self.state.encoder_secret = Some(secret);
|
||||
/// Adds an extension to the attestation.
|
||||
pub fn extension(&mut self, extension: Extension) -> &mut Self {
|
||||
self.state.extensions.push(extension);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the transcript commitments.
|
||||
pub fn transcript_commitments(
|
||||
&mut self,
|
||||
transcript_commitments: Vec<TranscriptCommitment>,
|
||||
) -> &mut Self {
|
||||
self.state.transcript_commitments = transcript_commitments;
|
||||
self
|
||||
}
|
||||
|
||||
@@ -125,8 +129,8 @@ impl AttestationBuilder<'_, Sign> {
|
||||
connection_info,
|
||||
server_ephemeral_key,
|
||||
cert_commitment,
|
||||
encoding_commitment_root,
|
||||
encoder_secret,
|
||||
extensions,
|
||||
transcript_commitments,
|
||||
} = self.state;
|
||||
|
||||
let hasher = provider.hash.get(&hash_alg).map_err(|_| {
|
||||
@@ -144,19 +148,6 @@ impl AttestationBuilder<'_, Sign> {
|
||||
)
|
||||
})?;
|
||||
|
||||
let encoding_commitment = if let Some(root) = encoding_commitment_root {
|
||||
let Some(secret) = encoder_secret else {
|
||||
return Err(AttestationBuilderError::new(
|
||||
ErrorKind::Field,
|
||||
"encoding commitment requested but encoder_secret was not set",
|
||||
));
|
||||
};
|
||||
|
||||
Some(EncodingCommitment { root, secret })
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let mut field_id = FieldId::default();
|
||||
|
||||
let body = Body {
|
||||
@@ -168,8 +159,14 @@ impl AttestationBuilder<'_, Sign> {
|
||||
AttestationBuilderError::new(ErrorKind::Field, "handshake data was not set")
|
||||
})?),
|
||||
cert_commitment: field_id.next(cert_commitment),
|
||||
encoding_commitment: encoding_commitment.map(|commitment| field_id.next(commitment)),
|
||||
plaintext_hashes: Default::default(),
|
||||
extensions: extensions
|
||||
.into_iter()
|
||||
.map(|extension| field_id.next(extension))
|
||||
.collect(),
|
||||
transcript_commitments: transcript_commitments
|
||||
.into_iter()
|
||||
.map(|commitment| field_id.next(commitment))
|
||||
.collect(),
|
||||
};
|
||||
|
||||
let header = Header {
|
||||
@@ -203,6 +200,7 @@ enum ErrorKind {
|
||||
Config,
|
||||
Field,
|
||||
Signature,
|
||||
Extension,
|
||||
}
|
||||
|
||||
impl AttestationBuilderError {
|
||||
@@ -229,10 +227,11 @@ impl std::fmt::Display for AttestationBuilderError {
|
||||
ErrorKind::Config => f.write_str("config error")?,
|
||||
ErrorKind::Field => f.write_str("field error")?,
|
||||
ErrorKind::Signature => f.write_str("signature error")?,
|
||||
ErrorKind::Extension => f.write_str("extension error")?,
|
||||
}
|
||||
|
||||
if let Some(source) = &self.source {
|
||||
write!(f, " caused by: {}", source)?;
|
||||
write!(f, " caused by: {source}")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -242,16 +241,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::{
|
||||
encoder_secret, 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::*;
|
||||
|
||||
@@ -282,6 +280,7 @@ mod test {
|
||||
encoding_provider(GET_WITH_HEADER, OK_JSON),
|
||||
connection,
|
||||
Blake3::default(),
|
||||
Vec::new(),
|
||||
);
|
||||
|
||||
let attestation_config = AttestationConfig::builder()
|
||||
@@ -306,6 +305,7 @@ mod test {
|
||||
encoding_provider(GET_WITH_HEADER, OK_JSON),
|
||||
connection,
|
||||
Blake3::default(),
|
||||
Vec::new(),
|
||||
);
|
||||
|
||||
let attestation_config = AttestationConfig::builder()
|
||||
@@ -321,35 +321,6 @@ mod test {
|
||||
assert!(err.is_request());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_attestation_builder_accept_unsupported_encoding_commitment() {
|
||||
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,
|
||||
Blake3::default(),
|
||||
);
|
||||
|
||||
let attestation_config = AttestationConfig::builder()
|
||||
.supported_signature_algs([SignatureAlgId::SECP256K1])
|
||||
.supported_fields([
|
||||
FieldKind::ConnectionInfo,
|
||||
FieldKind::ServerEphemKey,
|
||||
FieldKind::ServerIdentityCommitment,
|
||||
])
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let err = Attestation::builder(&attestation_config)
|
||||
.accept_request(request)
|
||||
.err()
|
||||
.unwrap();
|
||||
assert!(err.is_request());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_attestation_builder_sign_missing_signer(attestation_config: &AttestationConfig) {
|
||||
let transcript = Transcript::new(GET_WITH_HEADER, OK_JSON);
|
||||
@@ -360,6 +331,7 @@ mod test {
|
||||
encoding_provider(GET_WITH_HEADER, OK_JSON),
|
||||
connection,
|
||||
Blake3::default(),
|
||||
Vec::new(),
|
||||
);
|
||||
|
||||
let attestation_builder = Attestation::builder(attestation_config)
|
||||
@@ -369,48 +341,10 @@ mod test {
|
||||
let mut provider = CryptoProvider::default();
|
||||
provider.signer.set_secp256r1(&[42u8; 32]).unwrap();
|
||||
|
||||
let err = attestation_builder.build(&provider).err().unwrap();
|
||||
let err = attestation_builder.build(&provider).unwrap_err();
|
||||
assert!(matches!(err.kind, ErrorKind::Config));
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_attestation_builder_sign_missing_encoding_seed(
|
||||
attestation_config: &AttestationConfig,
|
||||
crypto_provider: &CryptoProvider,
|
||||
) {
|
||||
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(),
|
||||
);
|
||||
|
||||
let mut attestation_builder = Attestation::builder(attestation_config)
|
||||
.accept_request(request)
|
||||
.unwrap();
|
||||
|
||||
let ConnectionFixture {
|
||||
connection_info,
|
||||
server_cert_data,
|
||||
..
|
||||
} = connection;
|
||||
|
||||
let HandshakeData::V1_2(HandshakeDataV1_2 {
|
||||
server_ephemeral_key,
|
||||
..
|
||||
}) = server_cert_data.handshake;
|
||||
|
||||
attestation_builder
|
||||
.connection_info(connection_info)
|
||||
.server_ephemeral_key(server_ephemeral_key);
|
||||
|
||||
let err = attestation_builder.build(crypto_provider).err().unwrap();
|
||||
assert!(matches!(err.kind, ErrorKind::Field));
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_attestation_builder_sign_missing_server_ephemeral_key(
|
||||
attestation_config: &AttestationConfig,
|
||||
@@ -424,6 +358,7 @@ mod test {
|
||||
encoding_provider(GET_WITH_HEADER, OK_JSON),
|
||||
connection.clone(),
|
||||
Blake3::default(),
|
||||
Vec::new(),
|
||||
);
|
||||
|
||||
let mut attestation_builder = Attestation::builder(attestation_config)
|
||||
@@ -434,11 +369,9 @@ mod test {
|
||||
connection_info, ..
|
||||
} = connection;
|
||||
|
||||
attestation_builder
|
||||
.connection_info(connection_info)
|
||||
.encoder_secret(encoder_secret());
|
||||
attestation_builder.connection_info(connection_info);
|
||||
|
||||
let err = attestation_builder.build(crypto_provider).err().unwrap();
|
||||
let err = attestation_builder.build(crypto_provider).unwrap_err();
|
||||
assert!(matches!(err.kind, ErrorKind::Field));
|
||||
}
|
||||
|
||||
@@ -455,6 +388,7 @@ mod test {
|
||||
encoding_provider(GET_WITH_HEADER, OK_JSON),
|
||||
connection.clone(),
|
||||
Blake3::default(),
|
||||
Vec::new(),
|
||||
);
|
||||
|
||||
let mut attestation_builder = Attestation::builder(attestation_config)
|
||||
@@ -465,16 +399,91 @@ 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)
|
||||
.encoder_secret(encoder_secret());
|
||||
attestation_builder.server_ephemeral_key(server_ephemeral_key);
|
||||
|
||||
let err = attestation_builder.build(crypto_provider).err().unwrap();
|
||||
let err = attestation_builder.build(crypto_provider).unwrap_err();
|
||||
assert!(matches!(err.kind, ErrorKind::Field));
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_attestation_builder_reject_extensions_by_default(
|
||||
attestation_config: &AttestationConfig,
|
||||
) {
|
||||
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![Extension {
|
||||
id: b"foo".to_vec(),
|
||||
value: b"bar".to_vec(),
|
||||
}],
|
||||
);
|
||||
|
||||
let err = Attestation::builder(attestation_config)
|
||||
.accept_request(request)
|
||||
.unwrap_err();
|
||||
|
||||
assert!(matches!(err.kind, ErrorKind::Extension));
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_attestation_builder_accept_extension(crypto_provider: &CryptoProvider) {
|
||||
let attestation_config = AttestationConfig::builder()
|
||||
.supported_signature_algs([SignatureAlgId::SECP256K1])
|
||||
.extension_validator(|_| Ok(()))
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
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![Extension {
|
||||
id: b"foo".to_vec(),
|
||||
value: b"bar".to_vec(),
|
||||
}],
|
||||
);
|
||||
|
||||
let mut attestation_builder = Attestation::builder(&attestation_config)
|
||||
.accept_request(request)
|
||||
.unwrap();
|
||||
|
||||
let ConnectionFixture {
|
||||
server_cert_data,
|
||||
connection_info,
|
||||
..
|
||||
} = connection;
|
||||
|
||||
let CertBinding::V1_2(CertBindingV1_2 {
|
||||
server_ephemeral_key,
|
||||
..
|
||||
}) = server_cert_data.binding
|
||||
else {
|
||||
panic!("expected v1.2 handshake data");
|
||||
};
|
||||
|
||||
attestation_builder
|
||||
.connection_info(connection_info)
|
||||
.server_ephemeral_key(server_ephemeral_key);
|
||||
|
||||
let attestation = attestation_builder.build(crypto_provider).unwrap();
|
||||
|
||||
assert_eq!(attestation.body.extensions().count(), 1);
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,12 @@
|
||||
use std::{fmt::Debug, sync::Arc};
|
||||
|
||||
use tlsn_core::hash::HashAlgId;
|
||||
|
||||
use crate::{
|
||||
attestation::FieldKind,
|
||||
hash::{HashAlgId, DEFAULT_SUPPORTED_HASH_ALGS},
|
||||
signing::SignatureAlgId,
|
||||
Extension, InvalidExtension, hash::DEFAULT_SUPPORTED_HASH_ALGS, signing::SignatureAlgId,
|
||||
};
|
||||
|
||||
const DEFAULT_SUPPORTED_FIELDS: &[FieldKind] = &[
|
||||
FieldKind::ConnectionInfo,
|
||||
FieldKind::ServerEphemKey,
|
||||
FieldKind::ServerIdentityCommitment,
|
||||
FieldKind::EncodingCommitment,
|
||||
];
|
||||
type ExtensionValidator = Arc<dyn Fn(&[Extension]) -> Result<(), InvalidExtension> + Send + Sync>;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[allow(dead_code)]
|
||||
@@ -44,11 +41,11 @@ impl AttestationConfigError {
|
||||
}
|
||||
|
||||
/// Attestation configuration.
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Clone)]
|
||||
pub struct AttestationConfig {
|
||||
supported_signature_algs: Vec<SignatureAlgId>,
|
||||
supported_hash_algs: Vec<HashAlgId>,
|
||||
supported_fields: Vec<FieldKind>,
|
||||
extension_validator: Option<ExtensionValidator>,
|
||||
}
|
||||
|
||||
impl AttestationConfig {
|
||||
@@ -65,17 +62,25 @@ impl AttestationConfig {
|
||||
&self.supported_hash_algs
|
||||
}
|
||||
|
||||
pub(crate) fn supported_fields(&self) -> &[FieldKind] {
|
||||
&self.supported_fields
|
||||
pub(crate) fn extension_validator(&self) -> Option<&ExtensionValidator> {
|
||||
self.extension_validator.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for AttestationConfig {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("AttestationConfig")
|
||||
.field("supported_signature_algs", &self.supported_signature_algs)
|
||||
.field("supported_hash_algs", &self.supported_hash_algs)
|
||||
.finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
/// Builder for [`AttestationConfig`].
|
||||
#[derive(Debug)]
|
||||
pub struct AttestationConfigBuilder {
|
||||
supported_signature_algs: Vec<SignatureAlgId>,
|
||||
supported_hash_algs: Vec<HashAlgId>,
|
||||
supported_fields: Vec<FieldKind>,
|
||||
extension_validator: Option<ExtensionValidator>,
|
||||
}
|
||||
|
||||
impl Default for AttestationConfigBuilder {
|
||||
@@ -83,7 +88,15 @@ impl Default for AttestationConfigBuilder {
|
||||
Self {
|
||||
supported_signature_algs: Vec::default(),
|
||||
supported_hash_algs: DEFAULT_SUPPORTED_HASH_ALGS.to_vec(),
|
||||
supported_fields: DEFAULT_SUPPORTED_FIELDS.to_vec(),
|
||||
extension_validator: Some(Arc::new(|e| {
|
||||
if !e.is_empty() {
|
||||
Err(InvalidExtension::new(
|
||||
"all extensions are disallowed by default",
|
||||
))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
})),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -107,9 +120,26 @@ impl AttestationConfigBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the supported attestation fields.
|
||||
pub fn supported_fields(&mut self, supported_fields: impl Into<Vec<FieldKind>>) -> &mut Self {
|
||||
self.supported_fields = supported_fields.into();
|
||||
/// Sets the extension validator.
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// # use tlsn_attestation::{AttestationConfig, InvalidExtension};
|
||||
/// # let mut builder = AttestationConfig::builder();
|
||||
/// builder.extension_validator(|extensions| {
|
||||
/// for extension in extensions {
|
||||
/// if extension.id != b"example.type" {
|
||||
/// return Err(InvalidExtension::new("invalid extension type"));
|
||||
/// }
|
||||
/// }
|
||||
/// Ok(())
|
||||
/// });
|
||||
/// ```
|
||||
pub fn extension_validator<F>(&mut self, f: F) -> &mut Self
|
||||
where
|
||||
F: Fn(&[Extension]) -> Result<(), InvalidExtension> + Send + Sync + 'static,
|
||||
{
|
||||
self.extension_validator = Some(Arc::new(f));
|
||||
self
|
||||
}
|
||||
|
||||
@@ -118,7 +148,16 @@ impl AttestationConfigBuilder {
|
||||
Ok(AttestationConfig {
|
||||
supported_signature_algs: self.supported_signature_algs.clone(),
|
||||
supported_hash_algs: self.supported_hash_algs.clone(),
|
||||
supported_fields: self.supported_fields.clone(),
|
||||
extension_validator: self.extension_validator.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for AttestationConfigBuilder {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("AttestationConfigBuilder")
|
||||
.field("supported_signature_algs", &self.supported_signature_algs)
|
||||
.field("supported_hash_algs", &self.supported_hash_algs)
|
||||
.finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
149
crates/attestation/src/connection.rs
Normal file
149
crates/attestation/src/connection.rs
Normal 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"),
|
||||
}
|
||||
}
|
||||
}
|
||||
35
crates/attestation/src/extension.rs
Normal file
35
crates/attestation/src/extension.rs
Normal file
@@ -0,0 +1,35 @@
|
||||
use std::error::Error;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::serialize::impl_domain_separator;
|
||||
|
||||
/// An attestation extension.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct Extension {
|
||||
/// Extension identifier.
|
||||
pub id: Vec<u8>,
|
||||
/// Extension data.
|
||||
pub value: Vec<u8>,
|
||||
}
|
||||
|
||||
impl_domain_separator!(Extension);
|
||||
|
||||
/// Invalid extension error.
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[error("invalid extension: {reason}")]
|
||||
pub struct InvalidExtension {
|
||||
reason: Box<dyn Error + Send + Sync + 'static>,
|
||||
}
|
||||
|
||||
impl InvalidExtension {
|
||||
/// Creates a new invalid extension error.
|
||||
pub fn new<E>(reason: E) -> Self
|
||||
where
|
||||
E: Into<Box<dyn Error + Send + Sync + 'static>>,
|
||||
{
|
||||
Self {
|
||||
reason: reason.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
124
crates/attestation/src/fixtures.rs
Normal file
124
crates/attestation/src/fixtures.rs
Normal file
@@ -0,0 +1,124 @@
|
||||
//! 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::SignatureAlgId,
|
||||
};
|
||||
|
||||
/// 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::SECP256R1 => provider.signer.set_secp256r1(&[42u8; 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()
|
||||
}
|
||||
19
crates/attestation/src/hash.rs
Normal file
19
crates/attestation/src/hash.rs
Normal 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 {}
|
||||
449
crates/attestation/src/lib.rs
Normal file
449
crates/attestation/src/lib.rs
Normal file
@@ -0,0 +1,449 @@
|
||||
//! 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
|
||||
//! verify statements about the connection and the associated application data.
|
||||
//!
|
||||
//! Attestations are comprised of two parts: a [`Header`] and a [`Body`].
|
||||
//!
|
||||
//! The header is the data structure which is signed by a Notary. It
|
||||
//! contains a unique identifier, the protocol version, and a Merkle root
|
||||
//! of the body fields.
|
||||
//!
|
||||
//! The body contains the fields of the attestation. These fields include data
|
||||
//! which can be used to verify aspects of a TLS connection, such as the
|
||||
//! server's identity, and facts about the transcript.
|
||||
//!
|
||||
//! # Extensions
|
||||
//!
|
||||
//! An attestation may be extended using [`Extension`] fields included in the
|
||||
//! body. Extensions (currently) have no canonical semantics, but may be used to
|
||||
//! implement application specific functionality.
|
||||
//!
|
||||
//! A Prover may [append
|
||||
//! 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::AttestationConfigBuilder::extension_validator)
|
||||
//! any extensions requested by a Prover using custom application logic.
|
||||
//! Additionally, a Notary may
|
||||
//! [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 tlsn_core::{
|
||||
connection::{ConnectionInfo, ServerEphemKey},
|
||||
hash::{Hash, HashAlgorithm, TypedHash},
|
||||
merkle::MerkleTree,
|
||||
transcript::TranscriptCommitment,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
connection::ServerCertCommitment,
|
||||
hash::HashAlgorithmExt,
|
||||
presentation::PresentationBuilder,
|
||||
serialize::impl_domain_separator,
|
||||
signing::{Signature, VerifyingKey},
|
||||
};
|
||||
|
||||
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);
|
||||
|
||||
/// Unique identifier for an attestation.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||
pub struct Uid(pub [u8; 16]);
|
||||
|
||||
impl From<[u8; 16]> for Uid {
|
||||
fn from(id: [u8; 16]) -> Self {
|
||||
Self(id)
|
||||
}
|
||||
}
|
||||
|
||||
impl Distribution<Uid> for StandardUniform {
|
||||
fn sample<R: rand::Rng + ?Sized>(&self, rng: &mut R) -> Uid {
|
||||
Uid(self.sample(rng))
|
||||
}
|
||||
}
|
||||
|
||||
/// Version of an attestation.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||
pub struct Version(u32);
|
||||
|
||||
impl_domain_separator!(Version);
|
||||
|
||||
/// Public attestation field.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Field<T> {
|
||||
/// Identifier of the field.
|
||||
pub id: FieldId,
|
||||
/// Field data.
|
||||
pub data: T,
|
||||
}
|
||||
|
||||
/// Identifier for a field.
|
||||
#[derive(
|
||||
Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize,
|
||||
)]
|
||||
pub struct FieldId(pub u32);
|
||||
|
||||
impl FieldId {
|
||||
pub(crate) fn next<T>(&mut self, data: T) -> Field<T> {
|
||||
let id = *self;
|
||||
self.0 += 1;
|
||||
|
||||
Field { id, data }
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for FieldId {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// Kind of an attestation field.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
#[repr(u8)]
|
||||
pub enum FieldKind {
|
||||
/// Connection information.
|
||||
ConnectionInfo = 0x01,
|
||||
/// Server ephemeral key.
|
||||
ServerEphemKey = 0x02,
|
||||
/// Server identity commitment.
|
||||
ServerIdentityCommitment = 0x03,
|
||||
/// Encoding commitment.
|
||||
EncodingCommitment = 0x04,
|
||||
/// Plaintext hash commitment.
|
||||
PlaintextHash = 0x05,
|
||||
}
|
||||
|
||||
/// Attestation header.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct Header {
|
||||
/// An identifier for the attestation.
|
||||
pub id: Uid,
|
||||
/// Version of the attestation.
|
||||
pub version: Version,
|
||||
/// Merkle root of the attestation fields.
|
||||
pub root: TypedHash,
|
||||
}
|
||||
|
||||
impl_domain_separator!(Header);
|
||||
|
||||
/// Attestation body.
|
||||
#[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>,
|
||||
extensions: Vec<Field<Extension>>,
|
||||
transcript_commitments: Vec<Field<TranscriptCommitment>>,
|
||||
}
|
||||
|
||||
impl Body {
|
||||
/// Returns an iterator over the extensions.
|
||||
pub fn extensions(&self) -> impl Iterator<Item = &Extension> {
|
||||
self.extensions.iter().map(|field| &field.data)
|
||||
}
|
||||
|
||||
/// Returns the attestation verifying key.
|
||||
pub fn verifying_key(&self) -> &VerifyingKey {
|
||||
&self.verifying_key.data
|
||||
}
|
||||
|
||||
/// Computes the Merkle root of the attestation fields.
|
||||
///
|
||||
/// This is only used when building an attestation.
|
||||
pub(crate) fn root(&self, hasher: &dyn HashAlgorithm) -> TypedHash {
|
||||
let mut tree = MerkleTree::new(hasher.id());
|
||||
let fields = self
|
||||
.hash_fields(hasher)
|
||||
.into_iter()
|
||||
.map(|(_, hash)| hash)
|
||||
.collect::<Vec<_>>();
|
||||
tree.insert(hasher, fields);
|
||||
tree.root()
|
||||
}
|
||||
|
||||
/// Returns the fields of the body hashed and sorted by id.
|
||||
///
|
||||
/// Each field is hashed with a domain separator to mitigate type confusion
|
||||
/// attacks.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// The order of fields is not stable across versions.
|
||||
pub(crate) fn hash_fields(&self, hasher: &dyn HashAlgorithm) -> Vec<(FieldId, Hash)> {
|
||||
// CRITICAL: ensure all fields are included! If a new field is added to the
|
||||
// struct without including it here, it will not be included in the attestation.
|
||||
let Self {
|
||||
verifying_key,
|
||||
connection_info: conn_info,
|
||||
server_ephemeral_key,
|
||||
cert_commitment,
|
||||
extensions,
|
||||
transcript_commitments,
|
||||
} = self;
|
||||
|
||||
let mut fields: Vec<(FieldId, Hash)> = vec![
|
||||
(verifying_key.id, hasher.hash_separated(&verifying_key.data)),
|
||||
(conn_info.id, hasher.hash_separated(&conn_info.data)),
|
||||
(
|
||||
server_ephemeral_key.id,
|
||||
hasher.hash_separated(&server_ephemeral_key.data),
|
||||
),
|
||||
(
|
||||
cert_commitment.id,
|
||||
hasher.hash_separated(&cert_commitment.data),
|
||||
),
|
||||
];
|
||||
|
||||
for field in extensions.iter() {
|
||||
fields.push((field.id, hasher.hash_separated(&field.data)));
|
||||
}
|
||||
|
||||
for field in transcript_commitments.iter() {
|
||||
fields.push((field.id, hasher.hash_separated(&field.data)));
|
||||
}
|
||||
|
||||
fields.sort_by_key(|(id, _)| *id);
|
||||
fields
|
||||
}
|
||||
|
||||
/// Returns the connection information.
|
||||
pub(crate) fn connection_info(&self) -> &ConnectionInfo {
|
||||
&self.connection_info.data
|
||||
}
|
||||
|
||||
/// Returns the server's ephemeral public key.
|
||||
pub(crate) fn server_ephemeral_key(&self) -> &ServerEphemKey {
|
||||
&self.server_ephemeral_key.data
|
||||
}
|
||||
|
||||
/// Returns the commitment to a server certificate.
|
||||
pub(crate) fn cert_commitment(&self) -> &ServerCertCommitment {
|
||||
&self.cert_commitment.data
|
||||
}
|
||||
|
||||
/// Returns the transcript commitments.
|
||||
pub(crate) fn transcript_commitments(&self) -> impl Iterator<Item = &TranscriptCommitment> {
|
||||
self.transcript_commitments.iter().map(|field| &field.data)
|
||||
}
|
||||
}
|
||||
|
||||
/// An attestation document.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Attestation {
|
||||
/// The signature of the attestation.
|
||||
pub signature: Signature,
|
||||
/// The attestation header.
|
||||
pub header: Header,
|
||||
/// The attestation body.
|
||||
pub body: Body,
|
||||
}
|
||||
|
||||
impl Attestation {
|
||||
/// Returns an attestation builder.
|
||||
pub fn builder(config: &AttestationConfig) -> AttestationBuilder<'_> {
|
||||
AttestationBuilder::new(config)
|
||||
}
|
||||
|
||||
/// Returns a presentation builder.
|
||||
pub fn presentation_builder<'a>(
|
||||
&'a self,
|
||||
provider: &'a CryptoProvider,
|
||||
) -> PresentationBuilder<'a> {
|
||||
PresentationBuilder::new(provider, self)
|
||||
}
|
||||
}
|
||||
@@ -26,12 +26,15 @@ use std::fmt;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
attestation::{Attestation, AttestationError, AttestationProof},
|
||||
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.
|
||||
@@ -84,16 +87,25 @@ impl Presentation {
|
||||
.transpose()?;
|
||||
|
||||
let transcript = transcript
|
||||
.map(|transcript| transcript.verify_with_provider(provider, &attestation.body))
|
||||
.map(|transcript| {
|
||||
transcript.verify_with_provider(
|
||||
&provider.hash,
|
||||
&attestation.body.connection_info().transcript_length,
|
||||
attestation.body.transcript_commitments(),
|
||||
)
|
||||
})
|
||||
.transpose()?;
|
||||
|
||||
let connection_info = attestation.body.connection_info().clone();
|
||||
|
||||
let extensions = attestation.body.extensions().cloned().collect();
|
||||
|
||||
Ok(PresentationOutput {
|
||||
attestation,
|
||||
server_name,
|
||||
connection_info,
|
||||
transcript,
|
||||
extensions,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -110,6 +122,8 @@ pub struct PresentationOutput {
|
||||
pub connection_info: ConnectionInfo,
|
||||
/// Authenticated transcript data.
|
||||
pub transcript: Option<PartialTranscript>,
|
||||
/// Extensions.
|
||||
pub extensions: Vec<Extension>,
|
||||
}
|
||||
|
||||
/// Builder for [`Presentation`].
|
||||
@@ -175,7 +189,7 @@ impl fmt::Display for PresentationBuilderError {
|
||||
}
|
||||
|
||||
if let Some(source) = &self.source {
|
||||
write!(f, " caused by: {}", source)?;
|
||||
write!(f, " caused by: {source}")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -216,7 +230,7 @@ impl fmt::Display for PresentationError {
|
||||
}
|
||||
|
||||
if let Some(source) = &self.source {
|
||||
write!(f, " caused by: {}", source)?;
|
||||
write!(f, " caused by: {source}")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -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(())
|
||||
@@ -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)
|
||||
}
|
||||
@@ -18,12 +18,9 @@ mod config;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
attestation::Attestation,
|
||||
connection::ServerCertCommitment,
|
||||
hash::{HashAlgId, TypedHash},
|
||||
signing::SignatureAlgId,
|
||||
};
|
||||
use tlsn_core::hash::HashAlgId;
|
||||
|
||||
use crate::{Attestation, Extension, connection::ServerCertCommitment, signing::SignatureAlgId};
|
||||
|
||||
pub use builder::{RequestBuilder, RequestBuilderError};
|
||||
pub use config::{RequestConfig, RequestConfigBuilder, RequestConfigBuilderError};
|
||||
@@ -34,12 +31,12 @@ pub struct Request {
|
||||
pub(crate) signature_alg: SignatureAlgId,
|
||||
pub(crate) hash_alg: HashAlgId,
|
||||
pub(crate) server_cert_commitment: ServerCertCommitment,
|
||||
pub(crate) encoding_commitment_root: Option<TypedHash>,
|
||||
pub(crate) extensions: Vec<Extension>,
|
||||
}
|
||||
|
||||
impl Request {
|
||||
/// Returns a new request builder.
|
||||
pub fn builder(config: &RequestConfig) -> RequestBuilder {
|
||||
pub fn builder(config: &RequestConfig) -> RequestBuilder<'_> {
|
||||
RequestBuilder::new(config)
|
||||
}
|
||||
|
||||
@@ -65,16 +62,11 @@ impl Request {
|
||||
));
|
||||
}
|
||||
|
||||
if let Some(encoding_commitment_root) = &self.encoding_commitment_root {
|
||||
let Some(encoding_commitment) = attestation.body.encoding_commitment() else {
|
||||
// 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(
|
||||
"encoding commitment is missing".to_string(),
|
||||
));
|
||||
};
|
||||
|
||||
if &encoding_commitment.root != encoding_commitment_root {
|
||||
return Err(InconsistentAttestation(
|
||||
"encoding commitment root does not match".to_string(),
|
||||
"extension is missing from the attestation".to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -90,20 +82,19 @@ pub struct InconsistentAttestation(String);
|
||||
|
||||
#[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 super::*;
|
||||
|
||||
use crate::{
|
||||
connection::{ServerCertOpening, TranscriptLength},
|
||||
fixtures::{
|
||||
attestation_fixture, encoder_secret, encoding_provider, request_fixture,
|
||||
ConnectionFixture, RequestFixture,
|
||||
},
|
||||
hash::{Blake3, Hash, HashAlgId},
|
||||
signing::SignatureAlgId,
|
||||
transcript::Transcript,
|
||||
CryptoProvider,
|
||||
connection::ServerCertOpening,
|
||||
fixtures::{RequestFixture, attestation_fixture, request_fixture},
|
||||
signing::SignatureAlgId,
|
||||
};
|
||||
|
||||
#[test]
|
||||
@@ -116,14 +107,11 @@ mod test {
|
||||
encoding_provider(GET_WITH_HEADER, OK_JSON),
|
||||
connection.clone(),
|
||||
Blake3::default(),
|
||||
Vec::new(),
|
||||
);
|
||||
|
||||
let attestation = attestation_fixture(
|
||||
request.clone(),
|
||||
connection,
|
||||
SignatureAlgId::SECP256K1,
|
||||
encoder_secret(),
|
||||
);
|
||||
let attestation =
|
||||
attestation_fixture(request.clone(), connection, SignatureAlgId::SECP256K1, &[]);
|
||||
|
||||
assert!(request.validate(&attestation).is_ok())
|
||||
}
|
||||
@@ -138,14 +126,11 @@ mod test {
|
||||
encoding_provider(GET_WITH_HEADER, OK_JSON),
|
||||
connection.clone(),
|
||||
Blake3::default(),
|
||||
Vec::new(),
|
||||
);
|
||||
|
||||
let attestation = attestation_fixture(
|
||||
request.clone(),
|
||||
connection,
|
||||
SignatureAlgId::SECP256K1,
|
||||
encoder_secret(),
|
||||
);
|
||||
let attestation =
|
||||
attestation_fixture(request.clone(), connection, SignatureAlgId::SECP256K1, &[]);
|
||||
|
||||
request.signature_alg = SignatureAlgId::SECP256R1;
|
||||
|
||||
@@ -163,14 +148,11 @@ mod test {
|
||||
encoding_provider(GET_WITH_HEADER, OK_JSON),
|
||||
connection.clone(),
|
||||
Blake3::default(),
|
||||
Vec::new(),
|
||||
);
|
||||
|
||||
let attestation = attestation_fixture(
|
||||
request.clone(),
|
||||
connection,
|
||||
SignatureAlgId::SECP256K1,
|
||||
encoder_secret(),
|
||||
);
|
||||
let attestation =
|
||||
attestation_fixture(request.clone(), connection, SignatureAlgId::SECP256K1, &[]);
|
||||
|
||||
request.hash_alg = HashAlgId::SHA256;
|
||||
|
||||
@@ -188,14 +170,11 @@ mod test {
|
||||
encoding_provider(GET_WITH_HEADER, OK_JSON),
|
||||
connection.clone(),
|
||||
Blake3::default(),
|
||||
Vec::new(),
|
||||
);
|
||||
|
||||
let attestation = attestation_fixture(
|
||||
request.clone(),
|
||||
connection,
|
||||
SignatureAlgId::SECP256K1,
|
||||
encoder_secret(),
|
||||
);
|
||||
let attestation =
|
||||
attestation_fixture(request.clone(), connection, SignatureAlgId::SECP256K1, &[]);
|
||||
|
||||
let ConnectionFixture {
|
||||
server_cert_data, ..
|
||||
@@ -212,32 +191,4 @@ mod test {
|
||||
let res = request.validate(&attestation);
|
||||
assert!(res.is_err())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wrong_encoding_commitment_root() {
|
||||
let transcript = Transcript::new(GET_WITH_HEADER, OK_JSON);
|
||||
let connection = ConnectionFixture::tlsnotary(transcript.length());
|
||||
|
||||
let RequestFixture { mut request, .. } = request_fixture(
|
||||
transcript,
|
||||
encoding_provider(GET_WITH_HEADER, OK_JSON),
|
||||
connection.clone(),
|
||||
Blake3::default(),
|
||||
);
|
||||
|
||||
let attestation = attestation_fixture(
|
||||
request.clone(),
|
||||
connection,
|
||||
SignatureAlgId::SECP256K1,
|
||||
encoder_secret(),
|
||||
);
|
||||
|
||||
request.encoding_commitment_root = Some(TypedHash {
|
||||
alg: HashAlgId::BLAKE3,
|
||||
value: Hash::default(),
|
||||
});
|
||||
|
||||
let res = request.validate(&attestation);
|
||||
assert!(res.is_err())
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,22 @@
|
||||
use tlsn_core::{
|
||||
connection::{HandshakeData, ServerName},
|
||||
transcript::{Transcript, TranscriptCommitment, TranscriptSecret},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
connection::{ServerCertData, ServerCertOpening, ServerName},
|
||||
index::Index,
|
||||
CryptoProvider, Secrets,
|
||||
connection::ServerCertOpening,
|
||||
request::{Request, RequestConfig},
|
||||
secrets::Secrets,
|
||||
transcript::{encoding::EncodingTree, Transcript},
|
||||
CryptoProvider,
|
||||
};
|
||||
|
||||
/// Builder for [`Request`].
|
||||
pub struct RequestBuilder<'a> {
|
||||
config: &'a RequestConfig,
|
||||
server_name: Option<ServerName>,
|
||||
server_cert_data: Option<ServerCertData>,
|
||||
encoding_tree: Option<EncodingTree>,
|
||||
handshake_data: Option<HandshakeData>,
|
||||
transcript: Option<Transcript>,
|
||||
transcript_commitments: Vec<TranscriptCommitment>,
|
||||
transcript_commitment_secrets: Vec<TranscriptSecret>,
|
||||
}
|
||||
|
||||
impl<'a> RequestBuilder<'a> {
|
||||
@@ -22,9 +25,10 @@ impl<'a> RequestBuilder<'a> {
|
||||
Self {
|
||||
config,
|
||||
server_name: None,
|
||||
server_cert_data: None,
|
||||
encoding_tree: None,
|
||||
handshake_data: None,
|
||||
transcript: None,
|
||||
transcript_commitments: Vec::new(),
|
||||
transcript_commitment_secrets: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,15 +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);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the tree to commit to the transcript encodings.
|
||||
pub fn encoding_tree(&mut self, tree: EncodingTree) -> &mut Self {
|
||||
self.encoding_tree = Some(tree);
|
||||
/// Sets the handshake data.
|
||||
pub fn handshake_data(&mut self, data: HandshakeData) -> &mut Self {
|
||||
self.handshake_data = Some(data);
|
||||
self
|
||||
}
|
||||
|
||||
@@ -52,6 +50,17 @@ impl<'a> RequestBuilder<'a> {
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the transcript commitments.
|
||||
pub fn transcript_commitments(
|
||||
&mut self,
|
||||
secrets: Vec<TranscriptSecret>,
|
||||
commitments: Vec<TranscriptCommitment>,
|
||||
) -> &mut Self {
|
||||
self.transcript_commitment_secrets = secrets;
|
||||
self.transcript_commitments = commitments;
|
||||
self
|
||||
}
|
||||
|
||||
/// Builds the attestation request and returns the corresponding secrets.
|
||||
pub fn build(
|
||||
self,
|
||||
@@ -60,9 +69,10 @@ impl<'a> RequestBuilder<'a> {
|
||||
let Self {
|
||||
config,
|
||||
server_name,
|
||||
server_cert_data,
|
||||
encoding_tree,
|
||||
handshake_data: server_cert_data,
|
||||
transcript,
|
||||
transcript_commitments,
|
||||
transcript_commitment_secrets,
|
||||
} = self;
|
||||
|
||||
let signature_alg = *config.signature_alg();
|
||||
@@ -85,21 +95,21 @@ impl<'a> RequestBuilder<'a> {
|
||||
|
||||
let server_cert_commitment = server_cert_opening.commit(hasher);
|
||||
|
||||
let encoding_commitment_root = encoding_tree.as_ref().map(|tree| tree.root());
|
||||
let extensions = config.extensions().to_vec();
|
||||
|
||||
let request = Request {
|
||||
signature_alg,
|
||||
hash_alg,
|
||||
server_cert_commitment,
|
||||
encoding_commitment_root,
|
||||
extensions,
|
||||
};
|
||||
|
||||
let secrets = Secrets {
|
||||
server_name,
|
||||
server_cert_opening,
|
||||
encoding_tree,
|
||||
plaintext_hashes: Index::default(),
|
||||
transcript,
|
||||
transcript_commitments,
|
||||
transcript_commitment_secrets,
|
||||
};
|
||||
|
||||
Ok((request, secrets))
|
||||
@@ -1,10 +1,14 @@
|
||||
use crate::{hash::HashAlgId, signing::SignatureAlgId};
|
||||
use tlsn_core::{hash::HashAlgId, transcript::TranscriptCommitConfig};
|
||||
|
||||
use crate::{Extension, signing::SignatureAlgId};
|
||||
|
||||
/// Request configuration.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RequestConfig {
|
||||
signature_alg: SignatureAlgId,
|
||||
hash_alg: HashAlgId,
|
||||
extensions: Vec<Extension>,
|
||||
transcript_commit: Option<TranscriptCommitConfig>,
|
||||
}
|
||||
|
||||
impl Default for RequestConfig {
|
||||
@@ -28,6 +32,16 @@ impl RequestConfig {
|
||||
pub fn hash_alg(&self) -> &HashAlgId {
|
||||
&self.hash_alg
|
||||
}
|
||||
|
||||
/// Returns the extensions.
|
||||
pub fn extensions(&self) -> &[Extension] {
|
||||
&self.extensions
|
||||
}
|
||||
|
||||
/// Returns the transcript commitment configuration.
|
||||
pub fn transcript_commit(&self) -> Option<&TranscriptCommitConfig> {
|
||||
self.transcript_commit.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
/// Builder for [`RequestConfig`].
|
||||
@@ -35,6 +49,8 @@ impl RequestConfig {
|
||||
pub struct RequestConfigBuilder {
|
||||
signature_alg: SignatureAlgId,
|
||||
hash_alg: HashAlgId,
|
||||
extensions: Vec<Extension>,
|
||||
transcript_commit: Option<TranscriptCommitConfig>,
|
||||
}
|
||||
|
||||
impl Default for RequestConfigBuilder {
|
||||
@@ -42,6 +58,8 @@ impl Default for RequestConfigBuilder {
|
||||
Self {
|
||||
signature_alg: SignatureAlgId::SECP256K1,
|
||||
hash_alg: HashAlgId::BLAKE3,
|
||||
extensions: Vec::new(),
|
||||
transcript_commit: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -59,11 +77,25 @@ impl RequestConfigBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
/// Adds an extension to the request.
|
||||
pub fn extension(&mut self, extension: Extension) -> &mut Self {
|
||||
self.extensions.push(extension);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the transcript commitment configuration.
|
||||
pub fn transcript_commit(&mut self, transcript_commit: TranscriptCommitConfig) -> &mut Self {
|
||||
self.transcript_commit = Some(transcript_commit);
|
||||
self
|
||||
}
|
||||
|
||||
/// Builds the config.
|
||||
pub fn build(self) -> Result<RequestConfig, RequestConfigBuilderError> {
|
||||
Ok(RequestConfig {
|
||||
signature_alg: self.signature_alg,
|
||||
hash_alg: self.hash_alg,
|
||||
extensions: self.extensions,
|
||||
transcript_commit: self.transcript_commit,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,20 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
connection::{ServerCertOpening, ServerIdentityProof, ServerName},
|
||||
index::Index,
|
||||
transcript::{
|
||||
encoding::EncodingTree, hash::PlaintextHashSecret, Transcript, TranscriptProofBuilder,
|
||||
},
|
||||
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,
|
||||
pub(crate) server_cert_opening: ServerCertOpening,
|
||||
pub(crate) encoding_tree: Option<EncodingTree>,
|
||||
pub(crate) plaintext_hashes: Index<PlaintextHashSecret>,
|
||||
pub(crate) transcript: Transcript,
|
||||
pub(crate) transcript_commitments: Vec<TranscriptCommitment>,
|
||||
pub(crate) transcript_commitment_secrets: Vec<TranscriptSecret>,
|
||||
}
|
||||
|
||||
opaque_debug::implement!(Secrets);
|
||||
@@ -38,10 +37,6 @@ impl Secrets {
|
||||
|
||||
/// Returns a transcript proof builder.
|
||||
pub fn transcript_proof_builder(&self) -> TranscriptProofBuilder<'_> {
|
||||
TranscriptProofBuilder::new(
|
||||
&self.transcript,
|
||||
self.encoding_tree.as_ref(),
|
||||
&self.plaintext_hashes,
|
||||
)
|
||||
TranscriptProofBuilder::new(&self.transcript, &self.transcript_commitment_secrets)
|
||||
}
|
||||
}
|
||||
53
crates/attestation/src/serialize.rs
Normal file
53
crates/attestation/src/serialize.rs
Normal file
@@ -0,0 +1,53 @@
|
||||
/// 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::EncodingCommitment);
|
||||
impl_domain_separator!(tlsn_core::transcript::hash::PlaintextHash);
|
||||
@@ -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)]
|
||||
@@ -242,8 +242,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 +318,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 +394,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 +437,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.
|
||||
@@ -1,13 +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,
|
||||
transcript::{encoding::EncodingTree, Direction, Transcript, TranscriptCommitConfigBuilder},
|
||||
CryptoProvider,
|
||||
};
|
||||
use tlsn_core::{
|
||||
connection::{CertBinding, CertBindingV1_2},
|
||||
fixtures::{self, ConnectionFixture, encoder_secret},
|
||||
hash::Blake3,
|
||||
transcript::{
|
||||
Direction, Transcript, TranscriptCommitConfigBuilder, TranscriptCommitment,
|
||||
TranscriptSecret,
|
||||
encoding::{EncodingCommitment, EncodingTree},
|
||||
},
|
||||
};
|
||||
use tlsn_data_fixtures::http::{request::GET_WITH_HEADER, response::OK_JSON};
|
||||
|
||||
@@ -31,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!()
|
||||
};
|
||||
@@ -54,18 +59,25 @@ fn test_api() {
|
||||
&Blake3::default(),
|
||||
transcripts_commitment_config.iter_encoding(),
|
||||
&encodings_provider,
|
||||
&transcript.length(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let encoding_commitment = EncodingCommitment {
|
||||
root: encoding_tree.root(),
|
||||
secret: encoder_secret(),
|
||||
};
|
||||
|
||||
let request_config = RequestConfig::default();
|
||||
let mut request_builder = Request::builder(&request_config);
|
||||
|
||||
request_builder
|
||||
.server_name(server_name.clone())
|
||||
.server_cert_data(server_cert_data)
|
||||
.handshake_data(server_cert_data)
|
||||
.transcript(transcript)
|
||||
.encoding_tree(encoding_tree);
|
||||
.transcript_commitments(
|
||||
vec![TranscriptSecret::Encoding(encoding_tree)],
|
||||
vec![TranscriptCommitment::Encoding(encoding_commitment.clone())],
|
||||
);
|
||||
|
||||
let (request, secrets) = request_builder.build(&provider).unwrap();
|
||||
|
||||
@@ -84,7 +96,7 @@ 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();
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
[package]
|
||||
edition = "2021"
|
||||
name = "tlsn-benches"
|
||||
publish = false
|
||||
version = "0.0.0"
|
||||
|
||||
[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"]}
|
||||
|
||||
[[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"
|
||||
@@ -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
|
||||
rustup run nightly 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
|
||||
```
|
||||
@@ -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
|
||||
@@ -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
|
||||
# Setting download-size higher than 45000 will cause a `Maximum call stack size exceeded`
|
||||
# error in the browser.
|
||||
download-size = [1024, 4096, 16384, 45000]
|
||||
defer-decryption = true
|
||||
memory-profile = true
|
||||
@@ -1,55 +0,0 @@
|
||||
FROM rust AS builder
|
||||
WORKDIR /usr/src/tlsn
|
||||
COPY . .
|
||||
|
||||
ARG BENCH_TYPE=native
|
||||
|
||||
RUN \
|
||||
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; \
|
||||
rustup run nightly 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"]
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
@@ -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,
|
||||
}
|
||||
@@ -1,273 +0,0 @@
|
||||
pub mod config;
|
||||
pub mod metrics;
|
||||
mod preprocess;
|
||||
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(())
|
||||
}
|
||||
@@ -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>,
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
use hmac_sha256::build_circuits;
|
||||
|
||||
pub async fn preprocess_prf_circuits() {
|
||||
build_circuits().await;
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -1,176 +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,
|
||||
preprocess::preprocess_prf_circuits,
|
||||
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")?;
|
||||
|
||||
// Preprocess the PRF circuits as they are allocating a lot of memory, which
|
||||
// don't need to be accounted for in the benchmarks.
|
||||
preprocess_prf_circuits().await;
|
||||
|
||||
{
|
||||
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,
|
||||
})
|
||||
}
|
||||
@@ -1,131 +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},
|
||||
preprocess::preprocess_prf_circuits,
|
||||
set_interface, VERIFIER_INTERFACE,
|
||||
};
|
||||
use tls_core::verify::WebPkiVerifier;
|
||||
use tlsn_common::config::ProtocolConfigValidator;
|
||||
use tlsn_core::CryptoProvider;
|
||||
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")?;
|
||||
|
||||
// Preprocess the PRF circuits as they are allocating a lot of memory, which
|
||||
// don't need to be accounted for in the benchmarks.
|
||||
preprocess_prf_circuits().await;
|
||||
|
||||
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()).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)
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
[package]
|
||||
edition = "2021"
|
||||
name = "tlsn-benches-browser-core"
|
||||
publish = false
|
||||
version = "0.0.0"
|
||||
|
||||
[dependencies]
|
||||
tlsn-benches-library = { workspace = true }
|
||||
|
||||
serio = { workspace = true }
|
||||
|
||||
serde = { workspace = true }
|
||||
tokio-util= { workspace = true, features = ["compat", "io-util"] }
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
@@ -1,22 +0,0 @@
|
||||
[package]
|
||||
edition = "2021"
|
||||
name = "tlsn-benches-browser-native"
|
||||
publish = false
|
||||
version = "0.0.0"
|
||||
|
||||
[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"
|
||||
@@ -1,331 +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};
|
||||
|
||||
use serio::{stream::IoStreamExt, SinkExt as _};
|
||||
use tlsn_benches_browser_core::{
|
||||
msg::{Config, Runtime},
|
||||
FramedIo,
|
||||
};
|
||||
use tlsn_benches_library::{AsyncIo, ProverKind, ProverTrait};
|
||||
|
||||
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 tokio::{io, io::AsyncWriteExt, net::TcpListener, task::JoinHandle};
|
||||
use tracing::{debug, error, info};
|
||||
use warp::Filter;
|
||||
|
||||
/// 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?);
|
||||
|
||||
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?);
|
||||
|
||||
// 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));
|
||||
|
||||
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?;
|
||||
|
||||
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.worker.init();
|
||||
// Do not `await` run() or else it will block the browser.
|
||||
window.worker.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(|_| ()))
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
[build]
|
||||
target = "wasm32-unknown-unknown"
|
||||
|
||||
[target.wasm32-unknown-unknown]
|
||||
rustflags = [
|
||||
"-C",
|
||||
"target-feature=+atomics,+bulk-memory,+mutable-globals",
|
||||
]
|
||||
|
||||
[unstable]
|
||||
build-std = ["panic_abort", "std"]
|
||||
@@ -1,30 +0,0 @@
|
||||
[package]
|
||||
edition = "2021"
|
||||
name = "tlsn-benches-browser-wasm"
|
||||
publish = false
|
||||
version = "0.0.0"
|
||||
|
||||
[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 }
|
||||
tracing = { workspace = true }
|
||||
wasm-bindgen = { version = "0.2.87" }
|
||||
wasm-bindgen-futures = { version = "0.4.37" }
|
||||
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
|
||||
@@ -1,7 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<head>
|
||||
</head>
|
||||
<body>
|
||||
<script src="index.js" type="module"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,7 +0,0 @@
|
||||
import * as Comlink from "./comlink.mjs";
|
||||
|
||||
async function init() {
|
||||
const worker = Comlink.wrap(new Worker("worker.js", { type: "module" }));
|
||||
window.worker = worker;
|
||||
}
|
||||
init();
|
||||
@@ -1,45 +0,0 @@
|
||||
import * as Comlink from "./comlink.mjs";
|
||||
|
||||
import init, { wasm_main, initialize } from './tlsn_benches_browser_wasm.js';
|
||||
|
||||
class Worker {
|
||||
async init() {
|
||||
try {
|
||||
await init();
|
||||
// Tracing may interfere with the benchmark results. We should enable it only for debugging.
|
||||
// init_logging({
|
||||
// level: 'Debug',
|
||||
// crate_filters: undefined,
|
||||
// span_events: undefined,
|
||||
// });
|
||||
await initialize({ thread_count: 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_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 Worker();
|
||||
|
||||
Comlink.expose(worker);
|
||||
@@ -1,2 +0,0 @@
|
||||
[toolchain]
|
||||
channel = "nightly"
|
||||
@@ -1,102 +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 serio::{stream::IoStreamExt, SinkExt as _};
|
||||
use tlsn_benches_browser_core::{
|
||||
msg::{Config, Runtime},
|
||||
FramedIo,
|
||||
};
|
||||
use tlsn_benches_library::run_prover;
|
||||
|
||||
use anyhow::Result;
|
||||
use tracing::info;
|
||||
use wasm_bindgen::prelude::*;
|
||||
use web_time::Instant;
|
||||
use ws_stream_wasm::WsMeta;
|
||||
|
||||
#[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();
|
||||
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(())
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
[package]
|
||||
edition = "2021"
|
||||
name = "tlsn-benches-library"
|
||||
publish = false
|
||||
version = "0.0.0"
|
||||
|
||||
[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"]}
|
||||
@@ -1,133 +0,0 @@
|
||||
use tls_core::{anchors::RootCertStore, verify::WebPkiVerifier};
|
||||
use tlsn_common::config::ProtocolConfig;
|
||||
use tlsn_core::{transcript::Idx, CryptoProvider};
|
||||
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 protocol_config = if defer_decryption {
|
||||
ProtocolConfig::builder()
|
||||
.max_sent_data(upload_size + 256)
|
||||
.max_recv_data(download_size + 256)
|
||||
.build()
|
||||
.unwrap()
|
||||
} else {
|
||||
ProtocolConfig::builder()
|
||||
.max_sent_data(upload_size + 256)
|
||||
.max_recv_data(download_size + 256)
|
||||
.max_recv_data_online(download_size + 256)
|
||||
.build()
|
||||
.unwrap()
|
||||
};
|
||||
|
||||
let prover = Prover::new(
|
||||
ProverConfig::builder()
|
||||
.server_name(SERVER_DOMAIN)
|
||||
.protocol_config(protocol_config)
|
||||
.defer_decryption_from_start(defer_decryption)
|
||||
.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 (prover_task, _) = try_join(prover_fut.map_err(anyhow::Error::from), tls_fut).await?;
|
||||
|
||||
let mut prover = prover_task.start_prove();
|
||||
|
||||
let (sent_len, recv_len) = prover.transcript().len();
|
||||
prover
|
||||
.prove_transcript(Idx::new(0..sent_len), Idx::new(0..recv_len))
|
||||
.await?;
|
||||
prover.finalize().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
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
[package]
|
||||
name = "tlsn-common"
|
||||
description = "Common code shared between tlsn-prover and tlsn-verifier"
|
||||
version = "0.1.0-alpha.9"
|
||||
edition = "2021"
|
||||
|
||||
[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-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 }
|
||||
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 }
|
||||
@@ -1,108 +0,0 @@
|
||||
//! Plaintext commitment and proof of encryption.
|
||||
|
||||
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,
|
||||
}
|
||||
@@ -1,173 +0,0 @@
|
||||
//! Encoding commitment protocol.
|
||||
|
||||
use mpz_common::Context;
|
||||
use mpz_core::Block;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serio::{stream::IoStreamExt, SinkExt};
|
||||
use tlsn_core::transcript::{
|
||||
encoding::{new_encoder, Encoder, EncoderSecret, EncodingProvider},
|
||||
Direction, Idx,
|
||||
};
|
||||
|
||||
/// 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.
|
||||
pub async fn transfer(
|
||||
ctx: &mut Context,
|
||||
secret: &EncoderSecret,
|
||||
sent_keys: impl IntoIterator<Item = &'_ Block>,
|
||||
recv_keys: impl IntoIterator<Item = &'_ Block>,
|
||||
) -> Result<(), EncodingError> {
|
||||
let encoder = new_encoder(secret);
|
||||
|
||||
let sent_keys: Vec<u8> = sent_keys
|
||||
.into_iter()
|
||||
.flat_map(|key| key.as_bytes())
|
||||
.copied()
|
||||
.collect();
|
||||
let recv_keys: Vec<u8> = recv_keys
|
||||
.into_iter()
|
||||
.flat_map(|key| key.as_bytes())
|
||||
.copied()
|
||||
.collect();
|
||||
|
||||
assert_eq!(sent_keys.len() % ENCODING_SIZE, 0);
|
||||
assert_eq!(recv_keys.len() % ENCODING_SIZE, 0);
|
||||
|
||||
let mut sent_encoding = encoder.encode_idx(
|
||||
Direction::Sent,
|
||||
&Idx::new(0..sent_keys.len() / ENCODING_SIZE),
|
||||
);
|
||||
let mut recv_encoding = encoder.encode_idx(
|
||||
Direction::Received,
|
||||
&Idx::new(0..recv_keys.len() / ENCODING_SIZE),
|
||||
);
|
||||
|
||||
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?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Receives the encodings using the provided MACs.
|
||||
pub async fn receive(
|
||||
ctx: &mut Context,
|
||||
sent_macs: impl IntoIterator<Item = &'_ Block>,
|
||||
recv_macs: impl IntoIterator<Item = &'_ Block>,
|
||||
) -> Result<impl EncodingProvider, EncodingError> {
|
||||
let Encodings { mut sent, mut recv } = ctx.io_mut().expect_next().await?;
|
||||
|
||||
let sent_macs: Vec<u8> = sent_macs
|
||||
.into_iter()
|
||||
.flat_map(|mac| mac.as_bytes())
|
||||
.copied()
|
||||
.collect();
|
||||
let recv_macs: Vec<u8> = recv_macs
|
||||
.into_iter()
|
||||
.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);
|
||||
|
||||
Ok(Provider { sent, recv })
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Provider {
|
||||
sent: Vec<u8>,
|
||||
recv: Vec<u8>,
|
||||
}
|
||||
|
||||
impl EncodingProvider for Provider {
|
||||
fn provide_encoding(&self, direction: Direction, idx: &Idx) -> Option<Vec<u8>> {
|
||||
let encodings = match direction {
|
||||
Direction::Sent => &self.sent,
|
||||
Direction::Received => &self.recv,
|
||||
};
|
||||
|
||||
let mut encoding = Vec::with_capacity(idx.len());
|
||||
for range in idx.iter_ranges() {
|
||||
let start = range.start * ENCODING_SIZE;
|
||||
let end = range.end * ENCODING_SIZE;
|
||||
|
||||
if end > encodings.len() {
|
||||
return None;
|
||||
}
|
||||
|
||||
encoding.extend_from_slice(&encodings[start..end]);
|
||||
}
|
||||
|
||||
Some(encoding)
|
||||
}
|
||||
}
|
||||
|
||||
/// 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,
|
||||
},
|
||||
}
|
||||
|
||||
impl From<std::io::Error> for EncodingError {
|
||||
fn from(value: std::io::Error) -> Self {
|
||||
Self(ErrorRepr::Io(value))
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -1,237 +0,0 @@
|
||||
//! TLS transcript.
|
||||
|
||||
use mpz_memory_core::{binary::U8, Vector};
|
||||
use rangeset::Intersection;
|
||||
use tls_core::msgs::enums::ContentType;
|
||||
use tlsn_core::transcript::{Direction, Idx, 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 {}
|
||||
|
||||
#[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()
|
||||
}
|
||||
}
|
||||
@@ -5,9 +5,12 @@ 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.9"
|
||||
version = "0.1.0-alpha.13-pre"
|
||||
edition = "2021"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[lib]
|
||||
name = "cipher"
|
||||
|
||||
@@ -27,6 +30,5 @@ mpz-ot = { workspace = true }
|
||||
|
||||
tokio = { version = "1", features = ["macros", "rt", "rt-multi-thread"] }
|
||||
rand = { workspace = true }
|
||||
rand06-compat = { workspace = true }
|
||||
ctr = { workspace = true }
|
||||
cipher = { workspace = true }
|
||||
cipher-crypto = { workspace = true }
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
use mpz_circuits::{circuits::aes128_trace, once_cell::sync::Lazy, trace, Circuit, CircuitBuilder};
|
||||
use std::sync::Arc;
|
||||
|
||||
/// `fn(key: [u8; 16], iv: [u8; 4], nonce: [u8; 8], ctr: [u8; 4]) -> [u8; 16]`
|
||||
pub(crate) static AES128_CTR: Lazy<Arc<Circuit>> = Lazy::new(|| {
|
||||
let builder = CircuitBuilder::new();
|
||||
|
||||
let key = builder.add_array_input::<u8, 16>();
|
||||
let iv = builder.add_array_input::<u8, 4>();
|
||||
let nonce = builder.add_array_input::<u8, 8>();
|
||||
let ctr = builder.add_array_input::<u8, 4>();
|
||||
|
||||
let keystream = aes_ctr_trace(builder.state(), key, iv, nonce, ctr);
|
||||
|
||||
builder.add_output(keystream);
|
||||
|
||||
Arc::new(builder.build().unwrap())
|
||||
});
|
||||
|
||||
/// `fn(key: [u8; 16], msg: [u8; 16]) -> [u8; 16]`
|
||||
pub(crate) static AES128_ECB: Lazy<Arc<Circuit>> = Lazy::new(|| {
|
||||
let builder = CircuitBuilder::new();
|
||||
|
||||
let key = builder.add_array_input::<u8, 16>();
|
||||
let message = builder.add_array_input::<u8, 16>();
|
||||
let block = aes128_trace(builder.state(), key, message);
|
||||
|
||||
builder.add_output(block);
|
||||
|
||||
Arc::new(builder.build().unwrap())
|
||||
});
|
||||
|
||||
#[trace]
|
||||
#[dep(aes_128, aes128_trace)]
|
||||
#[allow(dead_code)]
|
||||
fn aes_ctr(key: [u8; 16], iv: [u8; 4], explicit_nonce: [u8; 8], ctr: [u8; 4]) -> [u8; 16] {
|
||||
let block: Vec<_> = iv.into_iter().chain(explicit_nonce).chain(ctr).collect();
|
||||
aes_128(key, block.try_into().unwrap())
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn aes_128(key: [u8; 16], msg: [u8; 16]) -> [u8; 16] {
|
||||
use aes::{
|
||||
cipher::{BlockEncrypt, KeyInit},
|
||||
Aes128,
|
||||
};
|
||||
|
||||
let aes = Aes128::new_from_slice(&key).unwrap();
|
||||
let mut ciphertext = msg.into();
|
||||
aes.encrypt_block(&mut ciphertext);
|
||||
ciphertext.into()
|
||||
}
|
||||
@@ -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(())
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
|
||||
use crate::{Cipher, CtrBlock, Keystream};
|
||||
use async_trait::async_trait;
|
||||
use mpz_circuits::circuits::AES128;
|
||||
use mpz_memory_core::binary::{Binary, U8};
|
||||
use mpz_vm_core::{prelude::*, Call, Vm};
|
||||
use std::fmt::Debug;
|
||||
|
||||
mod circuit;
|
||||
mod error;
|
||||
|
||||
pub use error::AesError;
|
||||
@@ -55,7 +55,7 @@ impl Cipher for Aes128 {
|
||||
|
||||
let output = vm
|
||||
.call(
|
||||
Call::builder(circuit::AES128_ECB.clone())
|
||||
Call::builder(AES128.clone())
|
||||
.arg(key)
|
||||
.arg(input)
|
||||
.build()
|
||||
@@ -91,7 +91,7 @@ impl Cipher for Aes128 {
|
||||
|
||||
let output = vm
|
||||
.call(
|
||||
Call::builder(circuit::AES128_CTR.clone())
|
||||
Call::builder(AES128.clone())
|
||||
.arg(key)
|
||||
.arg(iv)
|
||||
.arg(explicit_nonce)
|
||||
@@ -145,7 +145,7 @@ impl Cipher for Aes128 {
|
||||
.map(|(explicit_nonce, counter)| {
|
||||
let output = vm
|
||||
.call(
|
||||
Call::builder(circuit::AES128_CTR.clone())
|
||||
Call::builder(AES128.clone())
|
||||
.arg(key)
|
||||
.arg(iv)
|
||||
.arg(explicit_nonce)
|
||||
@@ -172,7 +172,7 @@ mod tests {
|
||||
use super::*;
|
||||
use crate::Cipher;
|
||||
use mpz_common::context::test_st_context;
|
||||
use mpz_garble::protocol::semihonest::{Evaluator, Generator};
|
||||
use mpz_garble::protocol::semihonest::{Evaluator, Garbler};
|
||||
use mpz_memory_core::{
|
||||
binary::{Binary, U8},
|
||||
correlated::Delta,
|
||||
@@ -181,7 +181,6 @@ mod tests {
|
||||
use mpz_ot::ideal::cot::ideal_cot;
|
||||
use mpz_vm_core::{Execute, Vm};
|
||||
use rand::{rngs::StdRng, SeedableRng};
|
||||
use rand06_compat::Rand0_6CompatExt;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_aes_ctr() {
|
||||
@@ -297,11 +296,11 @@ mod tests {
|
||||
|
||||
fn mock_vm() -> (impl Vm<Binary>, impl Vm<Binary>) {
|
||||
let mut rng = StdRng::seed_from_u64(0);
|
||||
let delta = Delta::random(&mut rng.compat_by_ref());
|
||||
let delta = Delta::random(&mut rng);
|
||||
|
||||
let (cot_send, cot_recv) = ideal_cot(delta.into_inner());
|
||||
|
||||
let gen = Generator::new(cot_send, [0u8; 16], delta);
|
||||
let gen = Garbler::new(cot_send, [0u8; 16], delta);
|
||||
let ev = Evaluator::new(cot_recv);
|
||||
|
||||
(gen, ev)
|
||||
@@ -345,8 +344,8 @@ mod tests {
|
||||
start_ctr: usize,
|
||||
msg: Vec<u8>,
|
||||
) -> Vec<u8> {
|
||||
use ::cipher::{KeyIvInit, StreamCipher, StreamCipherSeek};
|
||||
use aes::Aes128;
|
||||
use cipher_crypto::{KeyIvInit, StreamCipher, StreamCipherSeek};
|
||||
use ctr::Ctr32BE;
|
||||
|
||||
let mut full_iv = [0u8; 16];
|
||||
@@ -366,7 +365,7 @@ mod tests {
|
||||
|
||||
fn aes128(key: [u8; 16], msg: [u8; 16]) -> [u8; 16] {
|
||||
use ::aes::Aes128 as TestAes128;
|
||||
use ::cipher::{BlockEncrypt, KeyInit};
|
||||
use cipher_crypto::{BlockEncrypt, KeyInit};
|
||||
|
||||
let mut msg = msg.into();
|
||||
let cipher = TestAes128::new(&key.into());
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
//! Ciphers and circuits.
|
||||
|
||||
use mpz_circuits::{types::ValueType, Circuit, CircuitBuilder, Tracer};
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Builds a circuit which XORs the provided values.
|
||||
pub(crate) fn build_xor_circuit(inputs: &[ValueType]) -> Arc<Circuit> {
|
||||
let builder = CircuitBuilder::new();
|
||||
|
||||
for input_ty in inputs {
|
||||
let input_0 = builder.add_input_by_type(input_ty.clone());
|
||||
let input_1 = builder.add_input_by_type(input_ty.clone());
|
||||
|
||||
let input_0 = Tracer::new(builder.state(), input_0);
|
||||
let input_1 = Tracer::new(builder.state(), input_1);
|
||||
let output = input_0 ^ input_1;
|
||||
builder.add_output(output);
|
||||
}
|
||||
|
||||
let circ = builder.build().expect("circuit should be valid");
|
||||
|
||||
Arc::new(circ)
|
||||
}
|
||||
@@ -10,17 +10,15 @@
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
pub mod aes;
|
||||
mod circuit;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use circuit::build_xor_circuit;
|
||||
use mpz_circuits::types::ValueType;
|
||||
use mpz_circuits::circuits::xor;
|
||||
use mpz_memory_core::{
|
||||
binary::{Binary, U8},
|
||||
FromRaw, MemoryExt, Repr, Slice, StaticSize, ToRaw, Vector,
|
||||
MemoryExt, Repr, Slice, StaticSize, ToRaw, Vector,
|
||||
};
|
||||
use mpz_vm_core::{prelude::*, CallBuilder, CallError, Vm};
|
||||
use std::collections::VecDeque;
|
||||
use mpz_vm_core::{prelude::*, Call, CallBuilder, CallError, Vm};
|
||||
use std::{collections::VecDeque, sync::Arc};
|
||||
|
||||
/// Provides computation of 2PC ciphers in counter and ECB mode.
|
||||
///
|
||||
@@ -99,6 +97,7 @@ pub struct CtrBlock<N, C, O> {
|
||||
/// Can be used to XOR with the cipher input to operate the cipher in counter
|
||||
/// mode.
|
||||
pub struct Keystream<N, C, O> {
|
||||
/// Sequential keystream blocks. Outputs are stored in contiguous memory.
|
||||
blocks: VecDeque<CtrBlock<N, C, O>>,
|
||||
}
|
||||
|
||||
@@ -117,25 +116,7 @@ where
|
||||
O: Repr<Binary> + StaticSize<Binary> + Copy,
|
||||
{
|
||||
/// Creates a new keystream from the provided blocks.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// * If the output of the keystream is not ordered and contiguous in
|
||||
/// memory.
|
||||
pub fn new(blocks: &[CtrBlock<N, C, O>]) -> Self {
|
||||
let mut pos = blocks
|
||||
.first()
|
||||
.map(|block| block.output.to_raw().ptr().as_usize())
|
||||
.unwrap_or(0);
|
||||
|
||||
for block in blocks {
|
||||
if block.output.to_raw().ptr().as_usize() != pos {
|
||||
panic!("output of keystream blocks must be ordered and contiguous in memory");
|
||||
}
|
||||
|
||||
pos += O::SIZE;
|
||||
}
|
||||
|
||||
Self {
|
||||
blocks: VecDeque::from_iter(blocks.iter().copied()),
|
||||
}
|
||||
@@ -178,7 +159,7 @@ where
|
||||
return Err(CipherError::new("no keystream material available"));
|
||||
}
|
||||
|
||||
let xor = build_xor_circuit(&[ValueType::new_array::<u8>(self.block_size())]);
|
||||
let xor = Arc::new(xor(self.block_size() * 8));
|
||||
let mut pos = 0;
|
||||
let mut outputs = Vec::with_capacity(self.blocks.len());
|
||||
for block in &self.blocks {
|
||||
@@ -195,20 +176,17 @@ where
|
||||
pos += self.block_size();
|
||||
}
|
||||
|
||||
// Calls were performed contiguously, so the output data is contiguous.
|
||||
let ptr = outputs
|
||||
.first()
|
||||
.map(|output| output.to_raw().ptr())
|
||||
.expect("keystream is not empty");
|
||||
let size = self.blocks.len() * O::SIZE;
|
||||
|
||||
let output = Vector::<U8>::from_raw(Slice::new_unchecked(ptr, size));
|
||||
let output = flatten_blocks(vm, outputs.iter().map(|block| block.to_raw()))?;
|
||||
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
/// Returns `len` bytes of the keystream as a vector.
|
||||
pub fn to_vector(&self, len: usize) -> Result<Vector<U8>, CipherError> {
|
||||
pub fn to_vector(
|
||||
&self,
|
||||
vm: &mut dyn Vm<Binary>,
|
||||
len: usize,
|
||||
) -> Result<Vector<U8>, CipherError> {
|
||||
if len == 0 {
|
||||
return Err(CipherError::new("length must be greater than 0"));
|
||||
} else if self.blocks.is_empty() {
|
||||
@@ -220,14 +198,8 @@ where
|
||||
return Err(CipherError::new("length does not match keystream length"));
|
||||
}
|
||||
|
||||
let ptr = self
|
||||
.blocks
|
||||
.front()
|
||||
.map(|block| block.output.to_raw().ptr())
|
||||
.expect("block count should be greater than 0");
|
||||
let size = block_count * O::SIZE;
|
||||
|
||||
let mut keystream = Vector::<U8>::from_raw(Slice::new_unchecked(ptr, size));
|
||||
let mut keystream =
|
||||
flatten_blocks(vm, self.blocks.iter().map(|block| block.output.to_raw()))?;
|
||||
keystream.truncate(len);
|
||||
|
||||
Ok(keystream)
|
||||
@@ -273,6 +245,34 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
fn flatten_blocks(
|
||||
vm: &mut dyn Vm<Binary>,
|
||||
blocks: impl IntoIterator<Item = Slice>,
|
||||
) -> Result<Vector<U8>, CipherError> {
|
||||
use mpz_circuits::CircuitBuilder;
|
||||
|
||||
let blocks = blocks.into_iter().collect::<Vec<_>>();
|
||||
let len: usize = blocks.iter().map(|block| block.len()).sum();
|
||||
|
||||
let mut builder = CircuitBuilder::new();
|
||||
for _ in 0..len {
|
||||
let i = builder.add_input();
|
||||
let o = builder.add_id_gate(i);
|
||||
builder.add_output(o);
|
||||
}
|
||||
|
||||
let circuit = builder.build().expect("flatten circuit should be valid");
|
||||
|
||||
let mut builder = Call::builder(Arc::new(circuit));
|
||||
for block in blocks {
|
||||
builder = builder.arg(block);
|
||||
}
|
||||
|
||||
let call = builder.build().map_err(CipherError::new)?;
|
||||
|
||||
vm.call(call).map_err(CipherError::new)
|
||||
}
|
||||
|
||||
/// A cipher error.
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[error("{source}")]
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
[package]
|
||||
name = "tlsn-deap"
|
||||
version = "0.1.0-alpha.9"
|
||||
version = "0.1.0-alpha.13-pre"
|
||||
edition = "2021"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
mpz-core = { workspace = true }
|
||||
mpz-common = { workspace = true }
|
||||
|
||||
@@ -4,19 +4,15 @@
|
||||
#![deny(clippy::all)]
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
use std::{
|
||||
mem,
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Arc,
|
||||
},
|
||||
};
|
||||
mod map;
|
||||
|
||||
use std::{mem, sync::Arc};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use mpz_common::{scoped_futures::ScopedFutureExt as _, Context};
|
||||
use mpz_common::Context;
|
||||
use mpz_core::bitvec::BitVec;
|
||||
use mpz_vm_core::{
|
||||
memory::{binary::Binary, DecodeFuture, Memory, Slice, View},
|
||||
memory::{binary::Binary, DecodeFuture, Memory, Repr, Slice, View},
|
||||
Call, Callable, Execute, Vm, VmError,
|
||||
};
|
||||
use rangeset::{Difference, RangeSet, UnionMut};
|
||||
@@ -38,11 +34,15 @@ pub struct Deap<Mpc, Zk> {
|
||||
role: Role,
|
||||
mpc: Arc<Mutex<Mpc>>,
|
||||
zk: Arc<Mutex<Zk>>,
|
||||
/// Private inputs of the follower.
|
||||
follower_inputs: RangeSet<usize>,
|
||||
/// Mapping between the memories of the MPC and ZK VMs.
|
||||
memory_map: map::MemoryMap,
|
||||
/// Ranges of the follower's private inputs in the MPC VM.
|
||||
follower_input_ranges: RangeSet<usize>,
|
||||
/// Private inputs of the follower in the MPC VM.
|
||||
follower_inputs: Vec<Slice>,
|
||||
/// Outputs of the follower from the ZK VM. The references
|
||||
/// correspond to the MPC VM.
|
||||
outputs: Vec<(Slice, DecodeFuture<BitVec>)>,
|
||||
/// Whether the memories of the two VMs are potentially desynchronized.
|
||||
desync: AtomicBool,
|
||||
}
|
||||
|
||||
impl<Mpc, Zk> Deap<Mpc, Zk> {
|
||||
@@ -52,9 +52,10 @@ impl<Mpc, Zk> Deap<Mpc, Zk> {
|
||||
role,
|
||||
mpc: Arc::new(Mutex::new(mpc)),
|
||||
zk: Arc::new(Mutex::new(zk)),
|
||||
follower_inputs: RangeSet::default(),
|
||||
memory_map: map::MemoryMap::default(),
|
||||
follower_input_ranges: RangeSet::default(),
|
||||
follower_inputs: Vec::default(),
|
||||
outputs: Vec::default(),
|
||||
desync: AtomicBool::new(false),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,34 +69,28 @@ impl<Mpc, Zk> Deap<Mpc, Zk> {
|
||||
|
||||
/// Returns a mutable reference to the ZK VM.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// After calling this method, allocations will no longer be allowed in the
|
||||
/// DEAP VM as the memory will potentially be desynchronized.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the mutex is locked by another thread.
|
||||
pub fn zk(&self) -> MutexGuard<'_, Zk> {
|
||||
self.desync.store(true, Ordering::Relaxed);
|
||||
self.zk.try_lock().unwrap()
|
||||
}
|
||||
|
||||
/// Returns an owned mutex guard to the ZK VM.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// After calling this method, allocations will no longer be allowed in the
|
||||
/// DEAP VM as the memory will potentially be desynchronized.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the mutex is locked by another thread.
|
||||
pub fn zk_owned(&self) -> OwnedMutexGuard<Zk> {
|
||||
self.desync.store(true, Ordering::Relaxed);
|
||||
self.zk.clone().try_lock_owned().unwrap()
|
||||
}
|
||||
|
||||
/// Translates a value from the MPC VM address space to the ZK VM address
|
||||
/// space.
|
||||
pub fn translate<T: Repr<Binary>>(&self, value: T) -> Result<T, VmError> {
|
||||
self.memory_map.try_get(value.to_raw()).map(T::from_raw)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn mpc(&self) -> MutexGuard<'_, Mpc> {
|
||||
self.mpc.try_lock().unwrap()
|
||||
@@ -124,18 +119,15 @@ where
|
||||
// MACs.
|
||||
let input_futs = self
|
||||
.follower_inputs
|
||||
.iter_ranges()
|
||||
.map(|input| mpc.decode_raw(Slice::from_range_unchecked(input)))
|
||||
.iter()
|
||||
.map(|&input| mpc.decode_raw(input))
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
mpc.execute_all(ctx).await?;
|
||||
|
||||
// Assign inputs to the ZK VM.
|
||||
for (mut decode, input) in input_futs
|
||||
.into_iter()
|
||||
.zip(self.follower_inputs.iter_ranges())
|
||||
{
|
||||
let input = Slice::from_range_unchecked(input);
|
||||
for (mut decode, &input) in input_futs.into_iter().zip(&self.follower_inputs) {
|
||||
let input = self.memory_map.try_get(input)?;
|
||||
|
||||
// Follower has already assigned the inputs.
|
||||
if let Role::Leader = self.role {
|
||||
@@ -184,31 +176,48 @@ where
|
||||
{
|
||||
type Error = VmError;
|
||||
|
||||
fn alloc_raw(&mut self, size: usize) -> Result<Slice, VmError> {
|
||||
if self.desync.load(Ordering::Relaxed) {
|
||||
return Err(VmError::memory(
|
||||
"DEAP VM memories are potentially desynchronized",
|
||||
));
|
||||
}
|
||||
fn is_alloc_raw(&self, slice: Slice) -> bool {
|
||||
self.mpc.try_lock().unwrap().is_alloc_raw(slice)
|
||||
}
|
||||
|
||||
self.zk.try_lock().unwrap().alloc_raw(size)?;
|
||||
self.mpc.try_lock().unwrap().alloc_raw(size)
|
||||
fn alloc_raw(&mut self, size: usize) -> Result<Slice, VmError> {
|
||||
let mpc_slice = self.mpc.try_lock().unwrap().alloc_raw(size)?;
|
||||
let zk_slice = self.zk.try_lock().unwrap().alloc_raw(size)?;
|
||||
|
||||
self.memory_map.insert(mpc_slice, zk_slice);
|
||||
|
||||
Ok(mpc_slice)
|
||||
}
|
||||
|
||||
fn is_assigned_raw(&self, slice: Slice) -> bool {
|
||||
self.mpc.try_lock().unwrap().is_assigned_raw(slice)
|
||||
}
|
||||
|
||||
fn assign_raw(&mut self, slice: Slice, data: BitVec) -> Result<(), VmError> {
|
||||
self.zk
|
||||
self.mpc
|
||||
.try_lock()
|
||||
.unwrap()
|
||||
.assign_raw(slice, data.clone())?;
|
||||
self.mpc.try_lock().unwrap().assign_raw(slice, data)
|
||||
|
||||
self.zk
|
||||
.try_lock()
|
||||
.unwrap()
|
||||
.assign_raw(self.memory_map.try_get(slice)?, data)
|
||||
}
|
||||
|
||||
fn is_committed_raw(&self, slice: Slice) -> bool {
|
||||
self.mpc.try_lock().unwrap().is_committed_raw(slice)
|
||||
}
|
||||
|
||||
fn commit_raw(&mut self, slice: Slice) -> Result<(), VmError> {
|
||||
// Follower's private inputs are not committed in the ZK VM until finalization.
|
||||
let input_minus_follower = slice.to_range().difference(&self.follower_inputs);
|
||||
let input_minus_follower = slice.to_range().difference(&self.follower_input_ranges);
|
||||
let mut zk = self.zk.try_lock().unwrap();
|
||||
for input in input_minus_follower.iter_ranges() {
|
||||
zk.commit_raw(Slice::from_range_unchecked(input))?;
|
||||
zk.commit_raw(
|
||||
self.memory_map
|
||||
.try_get(Slice::from_range_unchecked(input))?,
|
||||
)?;
|
||||
}
|
||||
|
||||
self.mpc.try_lock().unwrap().commit_raw(slice)
|
||||
@@ -219,7 +228,11 @@ where
|
||||
}
|
||||
|
||||
fn decode_raw(&mut self, slice: Slice) -> Result<DecodeFuture<BitVec>, VmError> {
|
||||
let fut = self.zk.try_lock().unwrap().decode_raw(slice)?;
|
||||
let fut = self
|
||||
.zk
|
||||
.try_lock()
|
||||
.unwrap()
|
||||
.decode_raw(self.memory_map.try_get(slice)?)?;
|
||||
self.outputs.push((slice, fut));
|
||||
|
||||
self.mpc.try_lock().unwrap().decode_raw(slice)
|
||||
@@ -234,8 +247,11 @@ where
|
||||
type Error = VmError;
|
||||
|
||||
fn mark_public_raw(&mut self, slice: Slice) -> Result<(), VmError> {
|
||||
self.zk.try_lock().unwrap().mark_public_raw(slice)?;
|
||||
self.mpc.try_lock().unwrap().mark_public_raw(slice)
|
||||
self.mpc.try_lock().unwrap().mark_public_raw(slice)?;
|
||||
self.zk
|
||||
.try_lock()
|
||||
.unwrap()
|
||||
.mark_public_raw(self.memory_map.try_get(slice)?)
|
||||
}
|
||||
|
||||
fn mark_private_raw(&mut self, slice: Slice) -> Result<(), VmError> {
|
||||
@@ -243,14 +259,15 @@ where
|
||||
let mut mpc = self.mpc.try_lock().unwrap();
|
||||
match self.role {
|
||||
Role::Leader => {
|
||||
zk.mark_private_raw(slice)?;
|
||||
mpc.mark_private_raw(slice)?;
|
||||
zk.mark_private_raw(self.memory_map.try_get(slice)?)?;
|
||||
}
|
||||
Role::Follower => {
|
||||
// Follower's private inputs will become public during finalization.
|
||||
zk.mark_public_raw(slice)?;
|
||||
mpc.mark_private_raw(slice)?;
|
||||
self.follower_inputs.union_mut(&slice.to_range());
|
||||
// Follower's private inputs will become public during finalization.
|
||||
zk.mark_public_raw(self.memory_map.try_get(slice)?)?;
|
||||
self.follower_input_ranges.union_mut(&slice.to_range());
|
||||
self.follower_inputs.push(slice);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -262,14 +279,15 @@ where
|
||||
let mut mpc = self.mpc.try_lock().unwrap();
|
||||
match self.role {
|
||||
Role::Leader => {
|
||||
// Follower's private inputs will become public during finalization.
|
||||
zk.mark_public_raw(slice)?;
|
||||
mpc.mark_blind_raw(slice)?;
|
||||
self.follower_inputs.union_mut(&slice.to_range());
|
||||
// Follower's private inputs will become public during finalization.
|
||||
zk.mark_public_raw(self.memory_map.try_get(slice)?)?;
|
||||
self.follower_input_ranges.union_mut(&slice.to_range());
|
||||
self.follower_inputs.push(slice);
|
||||
}
|
||||
Role::Follower => {
|
||||
zk.mark_blind_raw(slice)?;
|
||||
mpc.mark_blind_raw(slice)?;
|
||||
zk.mark_blind_raw(self.memory_map.try_get(slice)?)?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -283,14 +301,21 @@ where
|
||||
Zk: Vm<Binary>,
|
||||
{
|
||||
fn call_raw(&mut self, call: Call) -> Result<Slice, VmError> {
|
||||
if self.desync.load(Ordering::Relaxed) {
|
||||
return Err(VmError::memory(
|
||||
"DEAP VM memories are potentially desynchronized",
|
||||
));
|
||||
let (circ, inputs) = call.clone().into_parts();
|
||||
let mut builder = Call::builder(circ);
|
||||
|
||||
for input in inputs {
|
||||
builder = builder.arg(self.memory_map.try_get(input)?);
|
||||
}
|
||||
|
||||
self.zk.try_lock().unwrap().call_raw(call.clone())?;
|
||||
self.mpc.try_lock().unwrap().call_raw(call)
|
||||
let zk_call = builder.build().expect("call should be valid");
|
||||
|
||||
let output = self.mpc.try_lock().unwrap().call_raw(call)?;
|
||||
let zk_output = self.zk.try_lock().unwrap().call_raw(zk_call)?;
|
||||
|
||||
self.memory_map.insert(output, zk_output);
|
||||
|
||||
Ok(output)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -308,8 +333,8 @@ where
|
||||
let mut zk = self.zk.clone().try_lock_owned().unwrap();
|
||||
let mut mpc = self.mpc.clone().try_lock_owned().unwrap();
|
||||
ctx.try_join(
|
||||
|ctx| async move { zk.flush(ctx).await }.scope_boxed(),
|
||||
|ctx| async move { mpc.flush(ctx).await }.scope_boxed(),
|
||||
async move |ctx| zk.flush(ctx).await,
|
||||
async move |ctx| mpc.flush(ctx).await,
|
||||
)
|
||||
.await
|
||||
.map_err(VmError::execute)??;
|
||||
@@ -326,8 +351,8 @@ where
|
||||
let mut zk = self.zk.clone().try_lock_owned().unwrap();
|
||||
let mut mpc = self.mpc.clone().try_lock_owned().unwrap();
|
||||
ctx.try_join(
|
||||
|ctx| async move { zk.preprocess(ctx).await }.scope_boxed(),
|
||||
|ctx| async move { mpc.preprocess(ctx).await }.scope_boxed(),
|
||||
async move |ctx| zk.preprocess(ctx).await,
|
||||
async move |ctx| mpc.preprocess(ctx).await,
|
||||
)
|
||||
.await
|
||||
.map_err(VmError::execute)??;
|
||||
@@ -360,32 +385,31 @@ mod tests {
|
||||
use mpz_circuits::circuits::AES128;
|
||||
use mpz_common::context::test_st_context;
|
||||
use mpz_core::Block;
|
||||
use mpz_garble::protocol::semihonest::{Evaluator, Generator};
|
||||
use mpz_garble::protocol::semihonest::{Evaluator, Garbler};
|
||||
use mpz_ot::ideal::{cot::ideal_cot, rcot::ideal_rcot};
|
||||
use mpz_vm_core::{
|
||||
memory::{binary::U8, correlated::Delta, Array},
|
||||
prelude::*,
|
||||
};
|
||||
use mpz_zk::{Prover, Verifier};
|
||||
use mpz_zk::{Prover, ProverConfig, Verifier, VerifierConfig};
|
||||
use rand::{rngs::StdRng, SeedableRng};
|
||||
use rand06_compat::Rand0_6CompatExt;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_deap() {
|
||||
let mut rng = StdRng::seed_from_u64(0);
|
||||
let delta_mpc = Delta::random(&mut rng.compat_by_ref());
|
||||
let delta_zk = Delta::random(&mut rng.compat_by_ref());
|
||||
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 = Generator::new(cot_send, [0u8; 16], delta_mpc);
|
||||
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 prover = Prover::new(ProverConfig::default(), rcot_recv);
|
||||
let verifier = Verifier::new(VerifierConfig::default(), delta_zk, rcot_send);
|
||||
|
||||
let mut leader = Deap::new(Role::Leader, gb, prover);
|
||||
let mut follower = Deap::new(Role::Follower, ev, verifier);
|
||||
@@ -452,22 +476,106 @@ mod tests {
|
||||
assert_eq!(ct_leader, ct_follower);
|
||||
}
|
||||
|
||||
#[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(ProverConfig::default(), rcot_recv);
|
||||
let verifier = Verifier::new(VerifierConfig::default(), delta_zk, rcot_send);
|
||||
|
||||
let mut leader = Deap::new(Role::Leader, gb, prover);
|
||||
let mut follower = Deap::new(Role::Follower, ev, verifier);
|
||||
|
||||
// Desynchronize the memories.
|
||||
let _ = leader.zk().alloc_raw(1).unwrap();
|
||||
let _ = follower.zk().alloc_raw(1).unwrap();
|
||||
|
||||
let (ct_leader, ct_follower) = futures::join!(
|
||||
async {
|
||||
let key: Array<U8, 16> = leader.alloc().unwrap();
|
||||
let msg: Array<U8, 16> = leader.alloc().unwrap();
|
||||
|
||||
leader.mark_private(key).unwrap();
|
||||
leader.mark_blind(msg).unwrap();
|
||||
leader.assign(key, [42u8; 16]).unwrap();
|
||||
leader.commit(key).unwrap();
|
||||
leader.commit(msg).unwrap();
|
||||
|
||||
let ct: Array<U8, 16> = leader
|
||||
.call(
|
||||
Call::builder(AES128.clone())
|
||||
.arg(key)
|
||||
.arg(msg)
|
||||
.build()
|
||||
.unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
let ct = leader.decode(ct).unwrap();
|
||||
|
||||
leader.flush(&mut ctx_a).await.unwrap();
|
||||
leader.execute(&mut ctx_a).await.unwrap();
|
||||
leader.flush(&mut ctx_a).await.unwrap();
|
||||
leader.finalize(&mut ctx_a).await.unwrap();
|
||||
|
||||
ct.await.unwrap()
|
||||
},
|
||||
async {
|
||||
let key: Array<U8, 16> = follower.alloc().unwrap();
|
||||
let msg: Array<U8, 16> = follower.alloc().unwrap();
|
||||
|
||||
follower.mark_blind(key).unwrap();
|
||||
follower.mark_private(msg).unwrap();
|
||||
follower.assign(msg, [69u8; 16]).unwrap();
|
||||
follower.commit(key).unwrap();
|
||||
follower.commit(msg).unwrap();
|
||||
|
||||
let ct: Array<U8, 16> = follower
|
||||
.call(
|
||||
Call::builder(AES128.clone())
|
||||
.arg(key)
|
||||
.arg(msg)
|
||||
.build()
|
||||
.unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
let ct = follower.decode(ct).unwrap();
|
||||
|
||||
follower.flush(&mut ctx_b).await.unwrap();
|
||||
follower.execute(&mut ctx_b).await.unwrap();
|
||||
follower.flush(&mut ctx_b).await.unwrap();
|
||||
follower.finalize(&mut ctx_b).await.unwrap();
|
||||
|
||||
ct.await.unwrap()
|
||||
}
|
||||
);
|
||||
|
||||
assert_eq!(ct_leader, ct_follower);
|
||||
}
|
||||
|
||||
// Tests that the leader can not use different inputs in each VM without
|
||||
// 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.compat_by_ref());
|
||||
let delta_zk = Delta::random(&mut rng.compat_by_ref());
|
||||
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 = Generator::new(cot_send, [0u8; 16], delta_mpc);
|
||||
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 prover = Prover::new(ProverConfig::default(), rcot_recv);
|
||||
let verifier = Verifier::new(VerifierConfig::default(), delta_zk, rcot_send);
|
||||
|
||||
let mut leader = Deap::new(Role::Leader, gb, prover);
|
||||
let mut follower = Deap::new(Role::Follower, ev, verifier);
|
||||
|
||||
111
crates/components/deap/src/map.rs
Normal file
111
crates/components/deap/src/map.rs
Normal file
@@ -0,0 +1,111 @@
|
||||
use std::ops::Range;
|
||||
|
||||
use mpz_vm_core::{memory::Slice, VmError};
|
||||
use rangeset::Subset;
|
||||
|
||||
/// A mapping between the memories of the MPC and ZK VMs.
|
||||
#[derive(Debug, Default)]
|
||||
pub(crate) struct MemoryMap {
|
||||
mpc: Vec<Range<usize>>,
|
||||
zk: Vec<Range<usize>>,
|
||||
}
|
||||
|
||||
impl MemoryMap {
|
||||
/// Inserts a new allocation into the map.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// - If the slices are not inserted in the order they are allocated.
|
||||
/// - If the slices are not the same length.
|
||||
pub(crate) fn insert(&mut self, mpc: Slice, zk: Slice) {
|
||||
let mpc = mpc.to_range();
|
||||
let zk = zk.to_range();
|
||||
|
||||
assert_eq!(mpc.len(), zk.len(), "slices must be the same length");
|
||||
|
||||
if let Some(last) = self.mpc.last() {
|
||||
if last.end > mpc.start {
|
||||
panic!("slices must be provided in ascending order");
|
||||
}
|
||||
}
|
||||
|
||||
self.mpc.push(mpc);
|
||||
self.zk.push(zk);
|
||||
}
|
||||
|
||||
/// Returns the corresponding allocation in the ZK VM.
|
||||
pub(crate) fn try_get(&self, mpc: Slice) -> Result<Slice, VmError> {
|
||||
let mpc_range = mpc.to_range();
|
||||
let pos = match self
|
||||
.mpc
|
||||
.binary_search_by_key(&mpc_range.start, |range| range.start)
|
||||
{
|
||||
Ok(pos) => pos,
|
||||
Err(0) => return Err(VmError::memory(format!("invalid memory slice: {mpc}"))),
|
||||
Err(pos) => pos - 1,
|
||||
};
|
||||
|
||||
let candidate = &self.mpc[pos];
|
||||
if mpc_range.is_subset(candidate) {
|
||||
let offset = mpc_range.start - candidate.start;
|
||||
let start = self.zk[pos].start + offset;
|
||||
let slice = Slice::from_range_unchecked(start..start + mpc_range.len());
|
||||
|
||||
Ok(slice)
|
||||
} else {
|
||||
Err(VmError::memory(format!("invalid memory slice: {mpc}")))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_map() {
|
||||
let mut map = MemoryMap::default();
|
||||
map.insert(
|
||||
Slice::from_range_unchecked(0..10),
|
||||
Slice::from_range_unchecked(10..20),
|
||||
);
|
||||
|
||||
// Range is fully contained.
|
||||
assert_eq!(
|
||||
map.try_get(Slice::from_range_unchecked(0..10)).unwrap(),
|
||||
Slice::from_range_unchecked(10..20)
|
||||
);
|
||||
// Range is subset.
|
||||
assert_eq!(
|
||||
map.try_get(Slice::from_range_unchecked(1..9)).unwrap(),
|
||||
Slice::from_range_unchecked(11..19)
|
||||
);
|
||||
// Range is not subset.
|
||||
assert!(map.try_get(Slice::from_range_unchecked(0..11)).is_err());
|
||||
|
||||
// Insert another range.
|
||||
map.insert(
|
||||
Slice::from_range_unchecked(20..30),
|
||||
Slice::from_range_unchecked(30..40),
|
||||
);
|
||||
assert_eq!(
|
||||
map.try_get(Slice::from_range_unchecked(20..30)).unwrap(),
|
||||
Slice::from_range_unchecked(30..40)
|
||||
);
|
||||
assert_eq!(
|
||||
map.try_get(Slice::from_range_unchecked(21..29)).unwrap(),
|
||||
Slice::from_range_unchecked(31..39)
|
||||
);
|
||||
assert!(map.try_get(Slice::from_range_unchecked(19..21)).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn test_map_length_mismatch() {
|
||||
let mut map = MemoryMap::default();
|
||||
map.insert(
|
||||
Slice::from_range_unchecked(5..10),
|
||||
Slice::from_range_unchecked(20..30),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
[package]
|
||||
name = "tlsn-hmac-sha256-circuits"
|
||||
authors = ["TLSNotary Team"]
|
||||
description = "The 2PC circuits for TLS HMAC-SHA256 PRF"
|
||||
keywords = ["tls", "mpc", "2pc", "hmac", "sha256"]
|
||||
categories = ["cryptography"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
version = "0.1.0-alpha.9"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
name = "hmac_sha256_circuits"
|
||||
|
||||
[dependencies]
|
||||
mpz-circuits = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
ring = { workspace = true }
|
||||
@@ -1,159 +0,0 @@
|
||||
use std::cell::RefCell;
|
||||
|
||||
use mpz_circuits::{
|
||||
circuits::{sha256, sha256_compress, sha256_compress_trace, sha256_trace},
|
||||
types::{U32, U8},
|
||||
BuilderState, Tracer,
|
||||
};
|
||||
|
||||
static SHA256_INITIAL_STATE: [u32; 8] = [
|
||||
0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19,
|
||||
];
|
||||
|
||||
/// Returns the outer and inner states of HMAC-SHA256 with the provided key.
|
||||
///
|
||||
/// Outer state is H(key ⊕ opad)
|
||||
///
|
||||
/// Inner state is H(key ⊕ ipad)
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `builder_state` - Reference to builder state.
|
||||
/// * `key` - N-byte key (must be <= 64 bytes).
|
||||
pub fn hmac_sha256_partial_trace<'a>(
|
||||
builder_state: &'a RefCell<BuilderState>,
|
||||
key: &[Tracer<'a, U8>],
|
||||
) -> ([Tracer<'a, U32>; 8], [Tracer<'a, U32>; 8]) {
|
||||
assert!(key.len() <= 64);
|
||||
|
||||
let mut opad = [Tracer::new(
|
||||
builder_state,
|
||||
builder_state.borrow_mut().get_constant(0x5cu8),
|
||||
); 64];
|
||||
|
||||
let mut ipad = [Tracer::new(
|
||||
builder_state,
|
||||
builder_state.borrow_mut().get_constant(0x36u8),
|
||||
); 64];
|
||||
|
||||
key.iter().enumerate().for_each(|(i, k)| {
|
||||
opad[i] = opad[i] ^ *k;
|
||||
ipad[i] = ipad[i] ^ *k;
|
||||
});
|
||||
|
||||
let sha256_initial_state: [_; 8] = SHA256_INITIAL_STATE
|
||||
.map(|v| Tracer::new(builder_state, builder_state.borrow_mut().get_constant(v)));
|
||||
|
||||
let outer_state = sha256_compress_trace(builder_state, sha256_initial_state, opad);
|
||||
let inner_state = sha256_compress_trace(builder_state, sha256_initial_state, ipad);
|
||||
|
||||
(outer_state, inner_state)
|
||||
}
|
||||
|
||||
/// Reference implementation of HMAC-SHA256 partial function.
|
||||
///
|
||||
/// Returns the outer and inner states of HMAC-SHA256 with the provided key.
|
||||
///
|
||||
/// Outer state is H(key ⊕ opad)
|
||||
///
|
||||
/// Inner state is H(key ⊕ ipad)
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `key` - N-byte key (must be <= 64 bytes).
|
||||
pub fn hmac_sha256_partial(key: &[u8]) -> ([u32; 8], [u32; 8]) {
|
||||
assert!(key.len() <= 64);
|
||||
|
||||
let mut opad = [0x5cu8; 64];
|
||||
let mut ipad = [0x36u8; 64];
|
||||
|
||||
key.iter().enumerate().for_each(|(i, k)| {
|
||||
opad[i] ^= k;
|
||||
ipad[i] ^= k;
|
||||
});
|
||||
|
||||
let outer_state = sha256_compress(SHA256_INITIAL_STATE, opad);
|
||||
let inner_state = sha256_compress(SHA256_INITIAL_STATE, ipad);
|
||||
|
||||
(outer_state, inner_state)
|
||||
}
|
||||
|
||||
/// HMAC-SHA256 finalization function.
|
||||
///
|
||||
/// Returns the HMAC-SHA256 digest of the provided message using existing outer
|
||||
/// and inner states.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `outer_state` - 256-bit outer state.
|
||||
/// * `inner_state` - 256-bit inner state.
|
||||
/// * `msg` - N-byte message.
|
||||
pub fn hmac_sha256_finalize_trace<'a>(
|
||||
builder_state: &'a RefCell<BuilderState>,
|
||||
outer_state: [Tracer<'a, U32>; 8],
|
||||
inner_state: [Tracer<'a, U32>; 8],
|
||||
msg: &[Tracer<'a, U8>],
|
||||
) -> [Tracer<'a, U8>; 32] {
|
||||
sha256_trace(
|
||||
builder_state,
|
||||
outer_state,
|
||||
64,
|
||||
&sha256_trace(builder_state, inner_state, 64, msg),
|
||||
)
|
||||
}
|
||||
|
||||
/// Reference implementation of the HMAC-SHA256 finalization function.
|
||||
///
|
||||
/// Returns the HMAC-SHA256 digest of the provided message using existing outer
|
||||
/// and inner states.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `outer_state` - 256-bit outer state.
|
||||
/// * `inner_state` - 256-bit inner state.
|
||||
/// * `msg` - N-byte message.
|
||||
pub fn hmac_sha256_finalize(outer_state: [u32; 8], inner_state: [u32; 8], msg: &[u8]) -> [u8; 32] {
|
||||
sha256(outer_state, 64, &sha256(inner_state, 64, msg))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use mpz_circuits::{test_circ, CircuitBuilder};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_hmac_sha256_partial() {
|
||||
let builder = CircuitBuilder::new();
|
||||
let key = builder.add_array_input::<u8, 48>();
|
||||
let (outer_state, inner_state) = hmac_sha256_partial_trace(builder.state(), &key);
|
||||
builder.add_output(outer_state);
|
||||
builder.add_output(inner_state);
|
||||
let circ = builder.build().unwrap();
|
||||
|
||||
let key = [69u8; 48];
|
||||
|
||||
test_circ!(circ, hmac_sha256_partial, fn(&key) -> ([u32; 8], [u32; 8]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hmac_sha256_finalize() {
|
||||
let builder = CircuitBuilder::new();
|
||||
let outer_state = builder.add_array_input::<u32, 8>();
|
||||
let inner_state = builder.add_array_input::<u32, 8>();
|
||||
let msg = builder.add_array_input::<u8, 47>();
|
||||
let hash = hmac_sha256_finalize_trace(builder.state(), outer_state, inner_state, &msg);
|
||||
builder.add_output(hash);
|
||||
let circ = builder.build().unwrap();
|
||||
|
||||
let key = [69u8; 32];
|
||||
let (outer_state, inner_state) = hmac_sha256_partial(&key);
|
||||
let msg = [42u8; 47];
|
||||
|
||||
test_circ!(
|
||||
circ,
|
||||
hmac_sha256_finalize,
|
||||
fn(outer_state, inner_state, &msg) -> [u8; 32]
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
//! HMAC-SHA256 circuits.
|
||||
|
||||
#![deny(missing_docs, unreachable_pub, unused_must_use)]
|
||||
#![deny(clippy::all)]
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
mod hmac_sha256;
|
||||
mod prf;
|
||||
mod session_keys;
|
||||
mod verify_data;
|
||||
|
||||
pub use hmac_sha256::{
|
||||
hmac_sha256_finalize, hmac_sha256_finalize_trace, hmac_sha256_partial,
|
||||
hmac_sha256_partial_trace,
|
||||
};
|
||||
|
||||
pub use prf::{prf, prf_trace};
|
||||
pub use session_keys::{session_keys, session_keys_trace};
|
||||
pub use verify_data::{verify_data, verify_data_trace};
|
||||
|
||||
use mpz_circuits::{Circuit, CircuitBuilder, Tracer};
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Builds session key derivation circuit.
|
||||
#[tracing::instrument(level = "trace")]
|
||||
pub fn build_session_keys() -> Arc<Circuit> {
|
||||
let builder = CircuitBuilder::new();
|
||||
let pms = builder.add_array_input::<u8, 32>();
|
||||
let client_random = builder.add_array_input::<u8, 32>();
|
||||
let server_random = builder.add_array_input::<u8, 32>();
|
||||
let (cwk, swk, civ, siv, outer_state, inner_state) =
|
||||
session_keys_trace(builder.state(), pms, client_random, server_random);
|
||||
builder.add_output(cwk);
|
||||
builder.add_output(swk);
|
||||
builder.add_output(civ);
|
||||
builder.add_output(siv);
|
||||
builder.add_output(outer_state);
|
||||
builder.add_output(inner_state);
|
||||
Arc::new(builder.build().expect("session keys should build"))
|
||||
}
|
||||
|
||||
/// Builds a verify data circuit.
|
||||
#[tracing::instrument(level = "trace")]
|
||||
pub fn build_verify_data(label: &[u8]) -> Arc<Circuit> {
|
||||
let builder = CircuitBuilder::new();
|
||||
let outer_state = builder.add_array_input::<u32, 8>();
|
||||
let inner_state = builder.add_array_input::<u32, 8>();
|
||||
let handshake_hash = builder.add_array_input::<u8, 32>();
|
||||
let vd = verify_data_trace(
|
||||
builder.state(),
|
||||
outer_state,
|
||||
inner_state,
|
||||
&label
|
||||
.iter()
|
||||
.map(|v| Tracer::new(builder.state(), builder.get_constant(*v).to_inner()))
|
||||
.collect::<Vec<_>>(),
|
||||
handshake_hash,
|
||||
);
|
||||
builder.add_output(vd);
|
||||
Arc::new(builder.build().expect("verify data should build"))
|
||||
}
|
||||
@@ -1,227 +0,0 @@
|
||||
//! This module provides an implementation of the HMAC-SHA256 PRF defined in [RFC 5246](https://www.rfc-editor.org/rfc/rfc5246#section-5).
|
||||
|
||||
use std::cell::RefCell;
|
||||
|
||||
use mpz_circuits::{
|
||||
types::{U32, U8},
|
||||
BuilderState, Tracer,
|
||||
};
|
||||
|
||||
use crate::hmac_sha256::{hmac_sha256_finalize, hmac_sha256_finalize_trace};
|
||||
|
||||
fn p_hash_trace<'a>(
|
||||
builder_state: &'a RefCell<BuilderState>,
|
||||
outer_state: [Tracer<'a, U32>; 8],
|
||||
inner_state: [Tracer<'a, U32>; 8],
|
||||
seed: &[Tracer<'a, U8>],
|
||||
iterations: usize,
|
||||
) -> Vec<Tracer<'a, U8>> {
|
||||
// A() is defined as:
|
||||
//
|
||||
// A(0) = seed
|
||||
// A(i) = HMAC_hash(secret, A(i-1))
|
||||
let mut a_cache: Vec<_> = Vec::with_capacity(iterations + 1);
|
||||
a_cache.push(seed.to_vec());
|
||||
|
||||
for i in 0..iterations {
|
||||
let a_i = hmac_sha256_finalize_trace(builder_state, outer_state, inner_state, &a_cache[i]);
|
||||
a_cache.push(a_i.to_vec());
|
||||
}
|
||||
|
||||
// HMAC_hash(secret, A(i) + seed)
|
||||
let mut output: Vec<_> = Vec::with_capacity(iterations * 32);
|
||||
for i in 0..iterations {
|
||||
let mut a_i_seed = a_cache[i + 1].clone();
|
||||
a_i_seed.extend_from_slice(seed);
|
||||
|
||||
let hash = hmac_sha256_finalize_trace(builder_state, outer_state, inner_state, &a_i_seed);
|
||||
output.extend_from_slice(&hash);
|
||||
}
|
||||
|
||||
output
|
||||
}
|
||||
|
||||
fn p_hash(outer_state: [u32; 8], inner_state: [u32; 8], seed: &[u8], iterations: usize) -> Vec<u8> {
|
||||
// A() is defined as:
|
||||
//
|
||||
// A(0) = seed
|
||||
// A(i) = HMAC_hash(secret, A(i-1))
|
||||
let mut a_cache: Vec<_> = Vec::with_capacity(iterations + 1);
|
||||
a_cache.push(seed.to_vec());
|
||||
|
||||
for i in 0..iterations {
|
||||
let a_i = hmac_sha256_finalize(outer_state, inner_state, &a_cache[i]);
|
||||
a_cache.push(a_i.to_vec());
|
||||
}
|
||||
|
||||
// HMAC_hash(secret, A(i) + seed)
|
||||
let mut output: Vec<_> = Vec::with_capacity(iterations * 32);
|
||||
for i in 0..iterations {
|
||||
let mut a_i_seed = a_cache[i + 1].clone();
|
||||
a_i_seed.extend_from_slice(seed);
|
||||
|
||||
let hash = hmac_sha256_finalize(outer_state, inner_state, &a_i_seed);
|
||||
output.extend_from_slice(&hash);
|
||||
}
|
||||
|
||||
output
|
||||
}
|
||||
|
||||
/// Computes PRF(secret, label, seed).
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `builder_state` - Reference to builder state.
|
||||
/// * `outer_state` - The outer state of HMAC-SHA256.
|
||||
/// * `inner_state` - The inner state of HMAC-SHA256.
|
||||
/// * `seed` - The seed to use.
|
||||
/// * `label` - The label to use.
|
||||
/// * `bytes` - The number of bytes to output.
|
||||
pub fn prf_trace<'a>(
|
||||
builder_state: &'a RefCell<BuilderState>,
|
||||
outer_state: [Tracer<'a, U32>; 8],
|
||||
inner_state: [Tracer<'a, U32>; 8],
|
||||
seed: &[Tracer<'a, U8>],
|
||||
label: &[Tracer<'a, U8>],
|
||||
bytes: usize,
|
||||
) -> Vec<Tracer<'a, U8>> {
|
||||
let iterations = bytes / 32 + (bytes % 32 != 0) as usize;
|
||||
let mut label_seed = label.to_vec();
|
||||
label_seed.extend_from_slice(seed);
|
||||
|
||||
let mut output = p_hash_trace(
|
||||
builder_state,
|
||||
outer_state,
|
||||
inner_state,
|
||||
&label_seed,
|
||||
iterations,
|
||||
);
|
||||
output.truncate(bytes);
|
||||
|
||||
output
|
||||
}
|
||||
|
||||
/// Reference implementation of PRF(secret, label, seed).
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `outer_state` - The outer state of HMAC-SHA256.
|
||||
/// * `inner_state` - The inner state of HMAC-SHA256.
|
||||
/// * `seed` - The seed to use.
|
||||
/// * `label` - The label to use.
|
||||
/// * `bytes` - The number of bytes to output.
|
||||
pub fn prf(
|
||||
outer_state: [u32; 8],
|
||||
inner_state: [u32; 8],
|
||||
seed: &[u8],
|
||||
label: &[u8],
|
||||
bytes: usize,
|
||||
) -> Vec<u8> {
|
||||
let iterations = bytes / 32 + (bytes % 32 != 0) as usize;
|
||||
let mut label_seed = label.to_vec();
|
||||
label_seed.extend_from_slice(seed);
|
||||
|
||||
let mut output = p_hash(outer_state, inner_state, &label_seed, iterations);
|
||||
output.truncate(bytes);
|
||||
|
||||
output
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use mpz_circuits::{evaluate, CircuitBuilder};
|
||||
|
||||
use crate::hmac_sha256::hmac_sha256_partial;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_p_hash() {
|
||||
let builder = CircuitBuilder::new();
|
||||
let outer_state = builder.add_array_input::<u32, 8>();
|
||||
let inner_state = builder.add_array_input::<u32, 8>();
|
||||
let seed = builder.add_array_input::<u8, 64>();
|
||||
let output = p_hash_trace(builder.state(), outer_state, inner_state, &seed, 2);
|
||||
builder.add_output(output);
|
||||
let circ = builder.build().unwrap();
|
||||
|
||||
let outer_state = [0u32; 8];
|
||||
let inner_state = [1u32; 8];
|
||||
let seed = [42u8; 64];
|
||||
|
||||
let expected = p_hash(outer_state, inner_state, &seed, 2);
|
||||
let actual = evaluate!(circ, fn(outer_state, inner_state, &seed) -> Vec<u8>).unwrap();
|
||||
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_prf() {
|
||||
let builder = CircuitBuilder::new();
|
||||
let outer_state = builder.add_array_input::<u32, 8>();
|
||||
let inner_state = builder.add_array_input::<u32, 8>();
|
||||
let seed = builder.add_array_input::<u8, 64>();
|
||||
let label = builder.add_array_input::<u8, 13>();
|
||||
let output = prf_trace(builder.state(), outer_state, inner_state, &seed, &label, 48);
|
||||
builder.add_output(output);
|
||||
let circ = builder.build().unwrap();
|
||||
|
||||
let master_secret = [0u8; 48];
|
||||
let seed = [43u8; 64];
|
||||
let label = b"master secret";
|
||||
|
||||
let (outer_state, inner_state) = hmac_sha256_partial(&master_secret);
|
||||
|
||||
let expected = prf(outer_state, inner_state, &seed, label, 48);
|
||||
let actual =
|
||||
evaluate!(circ, fn(outer_state, inner_state, &seed, label) -> Vec<u8>).unwrap();
|
||||
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
let mut expected_ring = [0u8; 48];
|
||||
ring_prf::prf(&mut expected_ring, &master_secret, label, &seed);
|
||||
|
||||
assert_eq!(actual, expected_ring);
|
||||
}
|
||||
|
||||
// Borrowed from Rustls for testing
|
||||
// https://github.com/rustls/rustls/blob/main/rustls/src/tls12/prf.rs
|
||||
mod ring_prf {
|
||||
use ring::{hmac, hmac::HMAC_SHA256};
|
||||
|
||||
fn concat_sign(key: &hmac::Key, a: &[u8], b: &[u8]) -> hmac::Tag {
|
||||
let mut ctx = hmac::Context::with_key(key);
|
||||
ctx.update(a);
|
||||
ctx.update(b);
|
||||
ctx.sign()
|
||||
}
|
||||
|
||||
fn p(out: &mut [u8], secret: &[u8], seed: &[u8]) {
|
||||
let hmac_key = hmac::Key::new(HMAC_SHA256, secret);
|
||||
|
||||
// A(1)
|
||||
let mut current_a = hmac::sign(&hmac_key, seed);
|
||||
let chunk_size = HMAC_SHA256.digest_algorithm().output_len();
|
||||
for chunk in out.chunks_mut(chunk_size) {
|
||||
// P_hash[i] = HMAC_hash(secret, A(i) + seed)
|
||||
let p_term = concat_sign(&hmac_key, current_a.as_ref(), seed);
|
||||
chunk.copy_from_slice(&p_term.as_ref()[..chunk.len()]);
|
||||
|
||||
// A(i+1) = HMAC_hash(secret, A(i))
|
||||
current_a = hmac::sign(&hmac_key, current_a.as_ref());
|
||||
}
|
||||
}
|
||||
|
||||
fn concat(a: &[u8], b: &[u8]) -> Vec<u8> {
|
||||
let mut ret = Vec::new();
|
||||
ret.extend_from_slice(a);
|
||||
ret.extend_from_slice(b);
|
||||
ret
|
||||
}
|
||||
|
||||
pub(crate) fn prf(out: &mut [u8], secret: &[u8], label: &[u8], seed: &[u8]) {
|
||||
let joined_seed = concat(label, seed);
|
||||
p(out, secret, &joined_seed);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,200 +0,0 @@
|
||||
use std::cell::RefCell;
|
||||
|
||||
use mpz_circuits::{
|
||||
types::{U32, U8},
|
||||
BuilderState, Tracer,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
hmac_sha256::{hmac_sha256_partial, hmac_sha256_partial_trace},
|
||||
prf::{prf, prf_trace},
|
||||
};
|
||||
|
||||
/// Session Keys.
|
||||
///
|
||||
/// Computes expanded p1 which consists of client_write_key + server_write_key.
|
||||
/// Computes expanded p2 which consists of client_IV + server_IV.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `builder_state` - Reference to builder state.
|
||||
/// * `pms` - 32-byte premaster secret.
|
||||
/// * `client_random` - 32-byte client random.
|
||||
/// * `server_random` - 32-byte server random.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `client_write_key` - 16-byte client write key.
|
||||
/// * `server_write_key` - 16-byte server write key.
|
||||
/// * `client_IV` - 4-byte client IV.
|
||||
/// * `server_IV` - 4-byte server IV.
|
||||
/// * `outer_hash_state` - 256-bit master-secret outer HMAC state.
|
||||
/// * `inner_hash_state` - 256-bit master-secret inner HMAC state.
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub fn session_keys_trace<'a>(
|
||||
builder_state: &'a RefCell<BuilderState>,
|
||||
pms: [Tracer<'a, U8>; 32],
|
||||
client_random: [Tracer<'a, U8>; 32],
|
||||
server_random: [Tracer<'a, U8>; 32],
|
||||
) -> (
|
||||
[Tracer<'a, U8>; 16],
|
||||
[Tracer<'a, U8>; 16],
|
||||
[Tracer<'a, U8>; 4],
|
||||
[Tracer<'a, U8>; 4],
|
||||
[Tracer<'a, U32>; 8],
|
||||
[Tracer<'a, U32>; 8],
|
||||
) {
|
||||
let (pms_outer_state, pms_inner_state) = hmac_sha256_partial_trace(builder_state, &pms);
|
||||
|
||||
let master_secret = {
|
||||
let seed = client_random
|
||||
.iter()
|
||||
.chain(&server_random)
|
||||
.copied()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let label = b"master secret"
|
||||
.map(|v| Tracer::new(builder_state, builder_state.borrow_mut().get_constant(v)));
|
||||
|
||||
prf_trace(
|
||||
builder_state,
|
||||
pms_outer_state,
|
||||
pms_inner_state,
|
||||
&seed,
|
||||
&label,
|
||||
48,
|
||||
)
|
||||
};
|
||||
|
||||
let (master_secret_outer_state, master_secret_inner_state) =
|
||||
hmac_sha256_partial_trace(builder_state, &master_secret);
|
||||
|
||||
let key_material = {
|
||||
let seed = server_random
|
||||
.iter()
|
||||
.chain(&client_random)
|
||||
.copied()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let label = b"key expansion"
|
||||
.map(|v| Tracer::new(builder_state, builder_state.borrow_mut().get_constant(v)));
|
||||
|
||||
prf_trace(
|
||||
builder_state,
|
||||
master_secret_outer_state,
|
||||
master_secret_inner_state,
|
||||
&seed,
|
||||
&label,
|
||||
40,
|
||||
)
|
||||
};
|
||||
|
||||
let cwk = key_material[0..16].try_into().unwrap();
|
||||
let swk = key_material[16..32].try_into().unwrap();
|
||||
let civ = key_material[32..36].try_into().unwrap();
|
||||
let siv = key_material[36..40].try_into().unwrap();
|
||||
|
||||
(
|
||||
cwk,
|
||||
swk,
|
||||
civ,
|
||||
siv,
|
||||
master_secret_outer_state,
|
||||
master_secret_inner_state,
|
||||
)
|
||||
}
|
||||
|
||||
/// Reference implementation of session keys derivation.
|
||||
pub fn session_keys(
|
||||
pms: [u8; 32],
|
||||
client_random: [u8; 32],
|
||||
server_random: [u8; 32],
|
||||
) -> ([u8; 16], [u8; 16], [u8; 4], [u8; 4]) {
|
||||
let (pms_outer_state, pms_inner_state) = hmac_sha256_partial(&pms);
|
||||
|
||||
let master_secret = {
|
||||
let seed = client_random
|
||||
.iter()
|
||||
.chain(&server_random)
|
||||
.copied()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let label = b"master secret";
|
||||
|
||||
prf(pms_outer_state, pms_inner_state, &seed, label, 48)
|
||||
};
|
||||
|
||||
let (master_secret_outer_state, master_secret_inner_state) =
|
||||
hmac_sha256_partial(&master_secret);
|
||||
|
||||
let key_material = {
|
||||
let seed = server_random
|
||||
.iter()
|
||||
.chain(&client_random)
|
||||
.copied()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let label = b"key expansion";
|
||||
|
||||
prf(
|
||||
master_secret_outer_state,
|
||||
master_secret_inner_state,
|
||||
&seed,
|
||||
label,
|
||||
40,
|
||||
)
|
||||
};
|
||||
|
||||
let cwk = key_material[0..16].try_into().unwrap();
|
||||
let swk = key_material[16..32].try_into().unwrap();
|
||||
let civ = key_material[32..36].try_into().unwrap();
|
||||
let siv = key_material[36..40].try_into().unwrap();
|
||||
|
||||
(cwk, swk, civ, siv)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use mpz_circuits::{evaluate, CircuitBuilder};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_session_keys() {
|
||||
let builder = CircuitBuilder::new();
|
||||
let pms = builder.add_array_input::<u8, 32>();
|
||||
let client_random = builder.add_array_input::<u8, 32>();
|
||||
let server_random = builder.add_array_input::<u8, 32>();
|
||||
let (cwk, swk, civ, siv, outer_state, inner_state) =
|
||||
session_keys_trace(builder.state(), pms, client_random, server_random);
|
||||
builder.add_output(cwk);
|
||||
builder.add_output(swk);
|
||||
builder.add_output(civ);
|
||||
builder.add_output(siv);
|
||||
builder.add_output(outer_state);
|
||||
builder.add_output(inner_state);
|
||||
let circ = builder.build().unwrap();
|
||||
|
||||
let pms = [0u8; 32];
|
||||
let client_random = [42u8; 32];
|
||||
let server_random = [69u8; 32];
|
||||
|
||||
let (expected_cwk, expected_swk, expected_civ, expected_siv) =
|
||||
session_keys(pms, client_random, server_random);
|
||||
|
||||
let (cwk, swk, civ, siv, _, _) = evaluate!(
|
||||
circ,
|
||||
fn(
|
||||
pms,
|
||||
client_random,
|
||||
server_random,
|
||||
) -> ([u8; 16], [u8; 16], [u8; 4], [u8; 4], [u32; 8], [u32; 8])
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(cwk, expected_cwk);
|
||||
assert_eq!(swk, expected_swk);
|
||||
assert_eq!(civ, expected_civ);
|
||||
assert_eq!(siv, expected_siv);
|
||||
}
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
use std::cell::RefCell;
|
||||
|
||||
use mpz_circuits::{
|
||||
types::{U32, U8},
|
||||
BuilderState, Tracer,
|
||||
};
|
||||
|
||||
use crate::prf::{prf, prf_trace};
|
||||
|
||||
/// Computes verify_data as specified in RFC 5246, Section 7.4.9.
|
||||
///
|
||||
/// verify_data
|
||||
/// PRF(master_secret, finished_label,
|
||||
/// Hash(handshake_messages))[0..verify_data_length-1];
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `builder_state` - The builder state.
|
||||
/// * `outer_state` - The outer HMAC state of the master secret.
|
||||
/// * `inner_state` - The inner HMAC state of the master secret.
|
||||
/// * `label` - The label to use.
|
||||
/// * `hs_hash` - The handshake hash.
|
||||
pub fn verify_data_trace<'a>(
|
||||
builder_state: &'a RefCell<BuilderState>,
|
||||
outer_state: [Tracer<'a, U32>; 8],
|
||||
inner_state: [Tracer<'a, U32>; 8],
|
||||
label: &[Tracer<'a, U8>],
|
||||
hs_hash: [Tracer<'a, U8>; 32],
|
||||
) -> [Tracer<'a, U8>; 12] {
|
||||
let vd = prf_trace(builder_state, outer_state, inner_state, &hs_hash, label, 12);
|
||||
|
||||
vd.try_into().expect("vd is 12 bytes")
|
||||
}
|
||||
|
||||
/// Reference implementation of verify_data as specified in RFC 5246, Section
|
||||
/// 7.4.9.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `outer_state` - The outer HMAC state of the master secret.
|
||||
/// * `inner_state` - The inner HMAC state of the master secret.
|
||||
/// * `label` - The label to use.
|
||||
/// * `hs_hash` - The handshake hash.
|
||||
pub fn verify_data(
|
||||
outer_state: [u32; 8],
|
||||
inner_state: [u32; 8],
|
||||
label: &[u8],
|
||||
hs_hash: [u8; 32],
|
||||
) -> [u8; 12] {
|
||||
let vd = prf(outer_state, inner_state, &hs_hash, label, 12);
|
||||
|
||||
vd.try_into().expect("vd is 12 bytes")
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use mpz_circuits::{evaluate, CircuitBuilder};
|
||||
|
||||
const CF_LABEL: &[u8; 15] = b"client finished";
|
||||
|
||||
#[test]
|
||||
fn test_verify_data() {
|
||||
let builder = CircuitBuilder::new();
|
||||
let outer_state = builder.add_array_input::<u32, 8>();
|
||||
let inner_state = builder.add_array_input::<u32, 8>();
|
||||
let label = builder.add_array_input::<u8, 15>();
|
||||
let hs_hash = builder.add_array_input::<u8, 32>();
|
||||
let vd = verify_data_trace(builder.state(), outer_state, inner_state, &label, hs_hash);
|
||||
builder.add_output(vd);
|
||||
let circ = builder.build().unwrap();
|
||||
|
||||
let outer_state = [0u32; 8];
|
||||
let inner_state = [1u32; 8];
|
||||
let hs_hash = [42u8; 32];
|
||||
|
||||
let expected = prf(outer_state, inner_state, &hs_hash, CF_LABEL, 12);
|
||||
|
||||
let actual = evaluate!(
|
||||
circ,
|
||||
fn(outer_state, inner_state, CF_LABEL, hs_hash) -> [u8; 12]
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(actual.to_vec(), expected);
|
||||
}
|
||||
}
|
||||
@@ -5,28 +5,24 @@ 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.9"
|
||||
version = "0.1.0-alpha.13-pre"
|
||||
edition = "2021"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[lib]
|
||||
name = "hmac_sha256"
|
||||
|
||||
[features]
|
||||
default = ["mock"]
|
||||
rayon = ["mpz-common/rayon"]
|
||||
mock = []
|
||||
|
||||
[dependencies]
|
||||
tlsn-hmac-sha256-circuits = { workspace = true }
|
||||
|
||||
mpz-vm-core = { workspace = true }
|
||||
mpz-core = { workspace = true }
|
||||
mpz-circuits = { workspace = true }
|
||||
mpz-common = { workspace = true, features = ["cpu"] }
|
||||
mpz-hash = { workspace = true }
|
||||
|
||||
derive_builder = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
sha2 = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
mpz-ot = { workspace = true, features = ["ideal"] }
|
||||
@@ -36,7 +32,8 @@ mpz-common = { workspace = true, features = ["test-utils"] }
|
||||
criterion = { workspace = true, features = ["async_tokio"] }
|
||||
tokio = { workspace = true, features = ["macros", "rt", "rt-multi-thread"] }
|
||||
rand = { workspace = true }
|
||||
rand06-compat = { workspace = true }
|
||||
hex = { workspace = true }
|
||||
ring = { workspace = true }
|
||||
|
||||
[[bench]]
|
||||
name = "prf"
|
||||
|
||||
@@ -2,16 +2,16 @@
|
||||
|
||||
use criterion::{criterion_group, criterion_main, Criterion};
|
||||
|
||||
use hmac_sha256::{MpcPrf, PrfConfig, Role};
|
||||
use hmac_sha256::{Mode, MpcPrf};
|
||||
use mpz_common::context::test_mt_context;
|
||||
use mpz_garble::protocol::semihonest::{Evaluator, Generator};
|
||||
use mpz_garble::protocol::semihonest::{Evaluator, Garbler};
|
||||
use mpz_ot::ideal::cot::ideal_cot;
|
||||
use mpz_vm_core::{
|
||||
memory::{binary::U8, correlated::Delta, Array},
|
||||
prelude::*,
|
||||
Execute,
|
||||
};
|
||||
use rand::{rngs::StdRng, SeedableRng};
|
||||
use rand06_compat::Rand0_6CompatExt;
|
||||
|
||||
#[allow(clippy::unit_arg)]
|
||||
fn criterion_benchmark(c: &mut Criterion) {
|
||||
@@ -19,13 +19,16 @@ fn criterion_benchmark(c: &mut Criterion) {
|
||||
group.sample_size(10);
|
||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||
|
||||
group.bench_function("prf", |b| b.to_async(&rt).iter(prf));
|
||||
group.bench_function("prf_normal", |b| b.to_async(&rt).iter(|| prf(Mode::Normal)));
|
||||
group.bench_function("prf_reduced", |b| {
|
||||
b.to_async(&rt).iter(|| prf(Mode::Reduced))
|
||||
});
|
||||
}
|
||||
|
||||
criterion_group!(benches, criterion_benchmark);
|
||||
criterion_main!(benches);
|
||||
|
||||
async fn prf() {
|
||||
async fn prf(mode: Mode) {
|
||||
let mut rng = StdRng::seed_from_u64(0);
|
||||
|
||||
let pms = [42u8; 32];
|
||||
@@ -36,10 +39,10 @@ async fn prf() {
|
||||
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.compat_by_ref());
|
||||
let delta = Delta::random(&mut rng);
|
||||
let (ot_send, ot_recv) = ideal_cot(delta.into_inner());
|
||||
|
||||
let mut leader_vm = Generator::new(ot_send, [0u8; 16], delta);
|
||||
let mut leader_vm = Garbler::new(ot_send, [0u8; 16], delta);
|
||||
let mut follower_vm = Evaluator::new(ot_recv);
|
||||
|
||||
let leader_pms: Array<U8, 32> = leader_vm.alloc().unwrap();
|
||||
@@ -52,23 +55,17 @@ async fn prf() {
|
||||
follower_vm.assign(follower_pms, pms).unwrap();
|
||||
follower_vm.commit(follower_pms).unwrap();
|
||||
|
||||
let mut leader = MpcPrf::new(PrfConfig::builder().role(Role::Leader).build().unwrap());
|
||||
let mut follower = MpcPrf::new(PrfConfig::builder().role(Role::Follower).build().unwrap());
|
||||
let mut leader = MpcPrf::new(mode);
|
||||
let mut follower = MpcPrf::new(mode);
|
||||
|
||||
let leader_output = leader.alloc(&mut leader_vm, leader_pms).unwrap();
|
||||
let follower_output = follower.alloc(&mut follower_vm, follower_pms).unwrap();
|
||||
|
||||
leader
|
||||
.set_client_random(&mut leader_vm, Some(client_random))
|
||||
.unwrap();
|
||||
follower.set_client_random(&mut follower_vm, None).unwrap();
|
||||
leader.set_client_random(client_random).unwrap();
|
||||
follower.set_client_random(client_random).unwrap();
|
||||
|
||||
leader
|
||||
.set_server_random(&mut leader_vm, server_random)
|
||||
.unwrap();
|
||||
follower
|
||||
.set_server_random(&mut follower_vm, server_random)
|
||||
.unwrap();
|
||||
leader.set_server_random(server_random).unwrap();
|
||||
follower.set_server_random(server_random).unwrap();
|
||||
|
||||
let _ = leader_vm
|
||||
.decode(leader_output.keys.client_write_key)
|
||||
@@ -88,44 +85,61 @@ async fn prf() {
|
||||
let _ = follower_vm.decode(follower_output.keys.client_iv).unwrap();
|
||||
let _ = follower_vm.decode(follower_output.keys.server_iv).unwrap();
|
||||
|
||||
futures::join!(
|
||||
async {
|
||||
leader_vm.flush(&mut leader_ctx).await.unwrap();
|
||||
leader_vm.execute(&mut leader_ctx).await.unwrap();
|
||||
leader_vm.flush(&mut leader_ctx).await.unwrap();
|
||||
},
|
||||
async {
|
||||
follower_vm.flush(&mut follower_ctx).await.unwrap();
|
||||
follower_vm.execute(&mut follower_ctx).await.unwrap();
|
||||
follower_vm.flush(&mut follower_ctx).await.unwrap();
|
||||
}
|
||||
);
|
||||
while leader.wants_flush() || follower.wants_flush() {
|
||||
tokio::try_join!(
|
||||
async {
|
||||
leader.flush(&mut leader_vm).unwrap();
|
||||
leader_vm.execute_all(&mut leader_ctx).await
|
||||
},
|
||||
async {
|
||||
follower.flush(&mut follower_vm).unwrap();
|
||||
follower_vm.execute_all(&mut follower_ctx).await
|
||||
}
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
let cf_hs_hash = [1u8; 32];
|
||||
let sf_hs_hash = [2u8; 32];
|
||||
|
||||
leader.set_cf_hash(&mut leader_vm, cf_hs_hash).unwrap();
|
||||
leader.set_sf_hash(&mut leader_vm, sf_hs_hash).unwrap();
|
||||
leader.set_cf_hash(cf_hs_hash).unwrap();
|
||||
follower.set_cf_hash(cf_hs_hash).unwrap();
|
||||
|
||||
follower.set_cf_hash(&mut follower_vm, cf_hs_hash).unwrap();
|
||||
follower.set_sf_hash(&mut follower_vm, sf_hs_hash).unwrap();
|
||||
while leader.wants_flush() || follower.wants_flush() {
|
||||
tokio::try_join!(
|
||||
async {
|
||||
leader.flush(&mut leader_vm).unwrap();
|
||||
leader_vm.execute_all(&mut leader_ctx).await
|
||||
},
|
||||
async {
|
||||
follower.flush(&mut follower_vm).unwrap();
|
||||
follower_vm.execute_all(&mut follower_ctx).await
|
||||
}
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
let _ = leader_vm.decode(leader_output.cf_vd).unwrap();
|
||||
let _ = leader_vm.decode(leader_output.sf_vd).unwrap();
|
||||
|
||||
let _ = follower_vm.decode(follower_output.cf_vd).unwrap();
|
||||
let _ = follower_vm.decode(follower_output.sf_vd).unwrap();
|
||||
|
||||
futures::join!(
|
||||
async {
|
||||
leader_vm.flush(&mut leader_ctx).await.unwrap();
|
||||
leader_vm.execute(&mut leader_ctx).await.unwrap();
|
||||
leader_vm.flush(&mut leader_ctx).await.unwrap();
|
||||
},
|
||||
async {
|
||||
follower_vm.flush(&mut follower_ctx).await.unwrap();
|
||||
follower_vm.execute(&mut follower_ctx).await.unwrap();
|
||||
follower_vm.flush(&mut follower_ctx).await.unwrap();
|
||||
}
|
||||
);
|
||||
let sf_hs_hash = [2u8; 32];
|
||||
|
||||
leader.set_sf_hash(sf_hs_hash).unwrap();
|
||||
follower.set_sf_hash(sf_hs_hash).unwrap();
|
||||
|
||||
while leader.wants_flush() || follower.wants_flush() {
|
||||
tokio::try_join!(
|
||||
async {
|
||||
leader.flush(&mut leader_vm).unwrap();
|
||||
leader_vm.execute_all(&mut leader_ctx).await
|
||||
},
|
||||
async {
|
||||
follower.flush(&mut follower_vm).unwrap();
|
||||
follower_vm.execute_all(&mut follower_ctx).await
|
||||
}
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
let _ = leader_vm.decode(leader_output.sf_vd).unwrap();
|
||||
let _ = follower_vm.decode(follower_output.sf_vd).unwrap();
|
||||
}
|
||||
|
||||
@@ -1,24 +1,10 @@
|
||||
use derive_builder::Builder;
|
||||
//! PRF modes.
|
||||
|
||||
/// Role of this party in the PRF.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Role {
|
||||
/// The leader provides the private inputs to the PRF.
|
||||
Leader,
|
||||
/// The follower is blind to the inputs to the PRF.
|
||||
Follower,
|
||||
}
|
||||
|
||||
/// Configuration for the PRF.
|
||||
#[derive(Debug, Builder)]
|
||||
pub struct PrfConfig {
|
||||
/// The role of this party in the PRF.
|
||||
pub(crate) role: Role,
|
||||
}
|
||||
|
||||
impl PrfConfig {
|
||||
/// Creates a new builder.
|
||||
pub fn builder() -> PrfConfigBuilder {
|
||||
PrfConfigBuilder::default()
|
||||
}
|
||||
/// Modes for the PRF.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum Mode {
|
||||
/// Computes some hashes locally.
|
||||
Reduced,
|
||||
/// Computes the whole PRF in MPC.
|
||||
Normal,
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
use core::fmt;
|
||||
use std::error::Error;
|
||||
|
||||
use mpz_hash::sha256::Sha256Error;
|
||||
|
||||
/// A PRF error.
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub struct PrfError {
|
||||
@@ -20,22 +22,21 @@ impl PrfError {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn vm<E: Into<Box<dyn Error + Send + Sync>>>(err: E) -> Self {
|
||||
Self::new(ErrorKind::Vm, err)
|
||||
}
|
||||
|
||||
pub(crate) fn state(msg: impl Into<String>) -> Self {
|
||||
Self {
|
||||
kind: ErrorKind::State,
|
||||
source: Some(msg.into().into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn role(msg: impl Into<String>) -> Self {
|
||||
Self {
|
||||
kind: ErrorKind::Role,
|
||||
source: Some(msg.into().into()),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn vm<E: Into<Box<dyn Error + Send + Sync>>>(err: E) -> Self {
|
||||
Self::new(ErrorKind::Vm, err)
|
||||
impl From<Sha256Error> for PrfError {
|
||||
fn from(value: Sha256Error) -> Self {
|
||||
Self::new(ErrorKind::Hash, value)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,7 +44,7 @@ impl PrfError {
|
||||
pub(crate) enum ErrorKind {
|
||||
Vm,
|
||||
State,
|
||||
Role,
|
||||
Hash,
|
||||
}
|
||||
|
||||
impl fmt::Display for PrfError {
|
||||
@@ -51,19 +52,13 @@ impl fmt::Display for PrfError {
|
||||
match self.kind {
|
||||
ErrorKind::Vm => write!(f, "vm error")?,
|
||||
ErrorKind::State => write!(f, "state error")?,
|
||||
ErrorKind::Role => write!(f, "role error")?,
|
||||
ErrorKind::Hash => write!(f, "hash error")?,
|
||||
}
|
||||
|
||||
if let Some(ref source) = self.source {
|
||||
write!(f, " caused by: {}", source)?;
|
||||
write!(f, " caused by: {source}")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<mpz_common::ContextError> for PrfError {
|
||||
fn from(error: mpz_common::ContextError) -> Self {
|
||||
Self::new(ErrorKind::Vm, error)
|
||||
}
|
||||
}
|
||||
|
||||
177
crates/components/hmac-sha256/src/hmac.rs
Normal file
177
crates/components/hmac-sha256/src/hmac.rs
Normal file
@@ -0,0 +1,177 @@
|
||||
//! Computation of HMAC-SHA256.
|
||||
//!
|
||||
//! HMAC-SHA256 is defined as
|
||||
//!
|
||||
//! HMAC(m) = H((key' xor opad) || H((key' xor ipad) || m))
|
||||
//!
|
||||
//! * H - SHA256 hash function
|
||||
//! * key' - key padded with zero bytes to 64 bytes (we do not support longer
|
||||
//! keys)
|
||||
//! * opad - 64 bytes of 0x5c
|
||||
//! * ipad - 64 bytes of 0x36
|
||||
//! * m - message
|
||||
//!
|
||||
//! This implementation computes HMAC-SHA256 using intermediate results
|
||||
//! `outer_partial` and `inner_local`. Then HMAC(m) = H(outer_partial ||
|
||||
//! inner_local)
|
||||
//!
|
||||
//! * `outer_partial` - key' xor opad
|
||||
//! * `inner_local` - H((key' xor ipad) || m)
|
||||
|
||||
use mpz_hash::sha256::Sha256;
|
||||
use mpz_vm_core::{
|
||||
memory::{
|
||||
binary::{Binary, U8},
|
||||
Array,
|
||||
},
|
||||
Vm,
|
||||
};
|
||||
|
||||
use crate::PrfError;
|
||||
|
||||
pub(crate) const IPAD: [u8; 64] = [0x36; 64];
|
||||
pub(crate) const OPAD: [u8; 64] = [0x5c; 64];
|
||||
|
||||
/// Computes HMAC-SHA256
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `vm` - The virtual machine.
|
||||
/// * `outer_partial` - (key' xor opad)
|
||||
/// * `inner_local` - H((key' xor ipad) || m)
|
||||
pub(crate) fn hmac_sha256(
|
||||
vm: &mut dyn Vm<Binary>,
|
||||
mut outer_partial: Sha256,
|
||||
inner_local: Array<U8, 32>,
|
||||
) -> Result<Array<U8, 32>, PrfError> {
|
||||
outer_partial.update(&inner_local.into());
|
||||
outer_partial.compress(vm)?;
|
||||
outer_partial.finalize(vm).map_err(PrfError::from)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{
|
||||
hmac::hmac_sha256,
|
||||
sha256, state_to_bytes,
|
||||
test_utils::{compute_inner_local, compute_outer_partial, mock_vm},
|
||||
};
|
||||
use mpz_common::context::test_st_context;
|
||||
use mpz_hash::sha256::Sha256;
|
||||
use mpz_vm_core::{
|
||||
memory::{
|
||||
binary::{U32, U8},
|
||||
Array, MemoryExt, ViewExt,
|
||||
},
|
||||
Execute,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_hmac_reference() {
|
||||
let (inputs, references) = test_fixtures();
|
||||
|
||||
for (input, &reference) in inputs.iter().zip(references.iter()) {
|
||||
let outer_partial = compute_outer_partial(input.0.clone());
|
||||
let inner_local = compute_inner_local(input.0.clone(), &input.1);
|
||||
|
||||
let hmac = sha256(outer_partial, 64, &state_to_bytes(inner_local));
|
||||
|
||||
assert_eq!(state_to_bytes(hmac), reference);
|
||||
}
|
||||
}
|
||||
|
||||
#[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 (inputs, references) = test_fixtures();
|
||||
for (input, &reference) in inputs.iter().zip(references.iter()) {
|
||||
let outer_partial = compute_outer_partial(input.0.clone());
|
||||
let inner_local = compute_inner_local(input.0.clone(), &input.1);
|
||||
|
||||
let outer_partial_leader: Array<U32, 8> = leader.alloc().unwrap();
|
||||
leader.mark_public(outer_partial_leader).unwrap();
|
||||
leader.assign(outer_partial_leader, outer_partial).unwrap();
|
||||
leader.commit(outer_partial_leader).unwrap();
|
||||
|
||||
let inner_local_leader: Array<U8, 32> = leader.alloc().unwrap();
|
||||
leader.mark_public(inner_local_leader).unwrap();
|
||||
leader
|
||||
.assign(inner_local_leader, state_to_bytes(inner_local))
|
||||
.unwrap();
|
||||
leader.commit(inner_local_leader).unwrap();
|
||||
|
||||
let hmac_leader = hmac_sha256(
|
||||
&mut leader,
|
||||
Sha256::new_from_state(outer_partial_leader, 1),
|
||||
inner_local_leader,
|
||||
)
|
||||
.unwrap();
|
||||
let hmac_leader = leader.decode(hmac_leader).unwrap();
|
||||
|
||||
let outer_partial_follower: Array<U32, 8> = follower.alloc().unwrap();
|
||||
follower.mark_public(outer_partial_follower).unwrap();
|
||||
follower
|
||||
.assign(outer_partial_follower, outer_partial)
|
||||
.unwrap();
|
||||
follower.commit(outer_partial_follower).unwrap();
|
||||
|
||||
let inner_local_follower: Array<U8, 32> = follower.alloc().unwrap();
|
||||
follower.mark_public(inner_local_follower).unwrap();
|
||||
follower
|
||||
.assign(inner_local_follower, state_to_bytes(inner_local))
|
||||
.unwrap();
|
||||
follower.commit(inner_local_follower).unwrap();
|
||||
|
||||
let hmac_follower = hmac_sha256(
|
||||
&mut follower,
|
||||
Sha256::new_from_state(outer_partial_follower, 1),
|
||||
inner_local_follower,
|
||||
)
|
||||
.unwrap();
|
||||
let hmac_follower = follower.decode(hmac_follower).unwrap();
|
||||
|
||||
let (hmac_leader, hmac_follower) = tokio::try_join!(
|
||||
async {
|
||||
leader.execute_all(&mut ctx_a).await.unwrap();
|
||||
hmac_leader.await
|
||||
},
|
||||
async {
|
||||
follower.execute_all(&mut ctx_b).await.unwrap();
|
||||
hmac_follower.await
|
||||
}
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(hmac_leader, hmac_follower);
|
||||
assert_eq!(hmac_leader, reference);
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn test_fixtures() -> (Vec<(Vec<u8>, Vec<u8>)>, Vec<[u8; 32]>) {
|
||||
let test_vectors: Vec<(Vec<u8>, Vec<u8>)> = vec![
|
||||
(
|
||||
hex::decode("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b").unwrap(),
|
||||
hex::decode("4869205468657265").unwrap(),
|
||||
),
|
||||
(
|
||||
hex::decode("4a656665").unwrap(),
|
||||
hex::decode("7768617420646f2079612077616e7420666f72206e6f7468696e673f").unwrap(),
|
||||
),
|
||||
];
|
||||
let expected: Vec<[u8; 32]> = vec![
|
||||
hex::decode("b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7")
|
||||
.unwrap()
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
hex::decode("5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843")
|
||||
.unwrap()
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
];
|
||||
|
||||
(test_vectors, expected)
|
||||
}
|
||||
}
|
||||
@@ -1,30 +1,24 @@
|
||||
//! This module contains the protocol for computing TLS SHA-256 HMAC PRF.
|
||||
//! This crate contains the protocol for computing TLS 1.2 SHA-256 HMAC PRF.
|
||||
|
||||
#![deny(missing_docs, unreachable_pub, unused_must_use)]
|
||||
#![deny(clippy::all)]
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
mod config;
|
||||
mod error;
|
||||
mod prf;
|
||||
mod hmac;
|
||||
#[cfg(test)]
|
||||
mod test_utils;
|
||||
|
||||
pub use config::{PrfConfig, PrfConfigBuilder, PrfConfigBuilderError, Role};
|
||||
mod config;
|
||||
pub use config::Mode;
|
||||
|
||||
mod error;
|
||||
pub use error::PrfError;
|
||||
|
||||
mod prf;
|
||||
pub use prf::MpcPrf;
|
||||
|
||||
use mpz_vm_core::memory::{binary::U8, Array};
|
||||
|
||||
pub(crate) static CF_LABEL: &[u8] = b"client finished";
|
||||
pub(crate) static SF_LABEL: &[u8] = b"server finished";
|
||||
|
||||
/// Builds the circuits for the PRF.
|
||||
///
|
||||
/// This function can be used ahead of time to build the circuits for the PRF,
|
||||
/// which at the moment is CPU and memory intensive.
|
||||
pub async fn build_circuits() {
|
||||
prf::Circuits::get().await;
|
||||
}
|
||||
|
||||
/// PRF output.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct PrfOutput {
|
||||
@@ -49,176 +43,227 @@ pub struct SessionKeys {
|
||||
pub server_iv: Array<U8, 4>,
|
||||
}
|
||||
|
||||
fn sha256(mut state: [u32; 8], pos: usize, msg: &[u8]) -> [u32; 8] {
|
||||
use sha2::{
|
||||
compress256,
|
||||
digest::{
|
||||
block_buffer::{BlockBuffer, Eager},
|
||||
generic_array::typenum::U64,
|
||||
},
|
||||
};
|
||||
|
||||
let mut buffer = BlockBuffer::<U64, Eager>::default();
|
||||
buffer.digest_blocks(msg, |b| compress256(&mut state, b));
|
||||
buffer.digest_pad(0x80, &(((msg.len() + pos) * 8) as u64).to_be_bytes(), |b| {
|
||||
compress256(&mut state, &[*b])
|
||||
});
|
||||
state
|
||||
}
|
||||
|
||||
fn state_to_bytes(input: [u32; 8]) -> [u8; 32] {
|
||||
let mut output = [0_u8; 32];
|
||||
for (k, byte_chunk) in input.iter().enumerate() {
|
||||
let byte_chunk = byte_chunk.to_be_bytes();
|
||||
output[4 * k..4 * (k + 1)].copy_from_slice(&byte_chunk);
|
||||
}
|
||||
output
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{
|
||||
test_utils::{mock_vm, prf_cf_vd, prf_keys, prf_ms, prf_sf_vd},
|
||||
Mode, MpcPrf, SessionKeys,
|
||||
};
|
||||
use mpz_common::context::test_st_context;
|
||||
use mpz_garble::protocol::semihonest::{Evaluator, Generator};
|
||||
use mpz_vm_core::{
|
||||
memory::{binary::U8, Array, MemoryExt, ViewExt},
|
||||
Execute,
|
||||
};
|
||||
use rand::{rngs::StdRng, Rng, SeedableRng};
|
||||
|
||||
use hmac_sha256_circuits::{hmac_sha256_partial, prf, session_keys};
|
||||
use mpz_ot::ideal::cot::ideal_cot;
|
||||
use mpz_vm_core::{memory::correlated::Delta, prelude::*};
|
||||
use rand::{rngs::StdRng, SeedableRng};
|
||||
use rand06_compat::Rand0_6CompatExt;
|
||||
|
||||
use super::*;
|
||||
|
||||
fn compute_ms(pms: [u8; 32], client_random: [u8; 32], server_random: [u8; 32]) -> [u8; 48] {
|
||||
let (outer_state, inner_state) = hmac_sha256_partial(&pms);
|
||||
let seed = client_random
|
||||
.iter()
|
||||
.chain(&server_random)
|
||||
.copied()
|
||||
.collect::<Vec<_>>();
|
||||
let ms = prf(outer_state, inner_state, &seed, b"master secret", 48);
|
||||
ms.try_into().unwrap()
|
||||
}
|
||||
|
||||
fn compute_vd(ms: [u8; 48], label: &[u8], hs_hash: [u8; 32]) -> [u8; 12] {
|
||||
let (outer_state, inner_state) = hmac_sha256_partial(&ms);
|
||||
let vd = prf(outer_state, inner_state, &hs_hash, label, 12);
|
||||
vd.try_into().unwrap()
|
||||
}
|
||||
|
||||
#[ignore = "expensive"]
|
||||
#[tokio::test]
|
||||
async fn test_prf() {
|
||||
let mut rng = StdRng::seed_from_u64(0);
|
||||
async fn test_prf_reduced() {
|
||||
let mode = Mode::Reduced;
|
||||
test_prf(mode).await;
|
||||
}
|
||||
|
||||
let pms = [42u8; 32];
|
||||
let client_random = [69u8; 32];
|
||||
let server_random: [u8; 32] = [96u8; 32];
|
||||
let ms = compute_ms(pms, client_random, server_random);
|
||||
#[tokio::test]
|
||||
async fn test_prf_normal() {
|
||||
let mode = Mode::Normal;
|
||||
test_prf(mode).await;
|
||||
}
|
||||
|
||||
let (mut leader_ctx, mut follower_ctx) = test_st_context(128);
|
||||
async fn test_prf(mode: Mode) {
|
||||
let mut rng = StdRng::seed_from_u64(1);
|
||||
// Test input
|
||||
let pms: [u8; 32] = rng.random();
|
||||
let client_random: [u8; 32] = rng.random();
|
||||
let server_random: [u8; 32] = rng.random();
|
||||
|
||||
let delta = Delta::random(&mut rng.compat_by_ref());
|
||||
let (ot_send, ot_recv) = ideal_cot(delta.into_inner());
|
||||
let cf_hs_hash: [u8; 32] = rng.random();
|
||||
let sf_hs_hash: [u8; 32] = rng.random();
|
||||
|
||||
let mut leader_vm = Generator::new(ot_send, [0u8; 16], delta);
|
||||
let mut follower_vm = Evaluator::new(ot_recv);
|
||||
// Expected output
|
||||
let ms_expected = prf_ms(pms, client_random, server_random);
|
||||
|
||||
let leader_pms: Array<U8, 32> = leader_vm.alloc().unwrap();
|
||||
leader_vm.mark_public(leader_pms).unwrap();
|
||||
leader_vm.assign(leader_pms, pms).unwrap();
|
||||
leader_vm.commit(leader_pms).unwrap();
|
||||
let [cwk_expected, swk_expected, civ_expected, siv_expected] =
|
||||
prf_keys(ms_expected, client_random, server_random);
|
||||
|
||||
let follower_pms: Array<U8, 32> = follower_vm.alloc().unwrap();
|
||||
follower_vm.mark_public(follower_pms).unwrap();
|
||||
follower_vm.assign(follower_pms, pms).unwrap();
|
||||
follower_vm.commit(follower_pms).unwrap();
|
||||
let cwk_expected: [u8; 16] = cwk_expected.try_into().unwrap();
|
||||
let swk_expected: [u8; 16] = swk_expected.try_into().unwrap();
|
||||
let civ_expected: [u8; 4] = civ_expected.try_into().unwrap();
|
||||
let siv_expected: [u8; 4] = siv_expected.try_into().unwrap();
|
||||
|
||||
let mut leader = MpcPrf::new(PrfConfig::builder().role(Role::Leader).build().unwrap());
|
||||
let mut follower = MpcPrf::new(PrfConfig::builder().role(Role::Follower).build().unwrap());
|
||||
let cf_vd_expected = prf_cf_vd(ms_expected, cf_hs_hash);
|
||||
let sf_vd_expected = prf_sf_vd(ms_expected, sf_hs_hash);
|
||||
|
||||
let leader_output = leader.alloc(&mut leader_vm, leader_pms).unwrap();
|
||||
let follower_output = follower.alloc(&mut follower_vm, follower_pms).unwrap();
|
||||
let cf_vd_expected: [u8; 12] = cf_vd_expected.try_into().unwrap();
|
||||
let sf_vd_expected: [u8; 12] = sf_vd_expected.try_into().unwrap();
|
||||
|
||||
leader
|
||||
.set_client_random(&mut leader_vm, Some(client_random))
|
||||
// Set up vm and prf
|
||||
let (mut ctx_a, mut ctx_b) = test_st_context(128);
|
||||
let (mut leader, mut follower) = mock_vm();
|
||||
|
||||
let leader_pms: Array<U8, 32> = leader.alloc().unwrap();
|
||||
leader.mark_public(leader_pms).unwrap();
|
||||
leader.assign(leader_pms, pms).unwrap();
|
||||
leader.commit(leader_pms).unwrap();
|
||||
|
||||
let follower_pms: Array<U8, 32> = follower.alloc().unwrap();
|
||||
follower.mark_public(follower_pms).unwrap();
|
||||
follower.assign(follower_pms, pms).unwrap();
|
||||
follower.commit(follower_pms).unwrap();
|
||||
|
||||
let mut prf_leader = MpcPrf::new(mode);
|
||||
let mut prf_follower = MpcPrf::new(mode);
|
||||
|
||||
let leader_prf_out = prf_leader.alloc(&mut leader, leader_pms).unwrap();
|
||||
let follower_prf_out = prf_follower.alloc(&mut follower, follower_pms).unwrap();
|
||||
|
||||
// client_random and server_random
|
||||
prf_leader.set_client_random(client_random).unwrap();
|
||||
prf_follower.set_client_random(client_random).unwrap();
|
||||
|
||||
prf_leader.set_server_random(server_random).unwrap();
|
||||
prf_follower.set_server_random(server_random).unwrap();
|
||||
|
||||
let SessionKeys {
|
||||
client_write_key: cwk_leader,
|
||||
server_write_key: swk_leader,
|
||||
client_iv: civ_leader,
|
||||
server_iv: siv_leader,
|
||||
} = leader_prf_out.keys;
|
||||
|
||||
let mut cwk_leader = leader.decode(cwk_leader).unwrap();
|
||||
let mut swk_leader = leader.decode(swk_leader).unwrap();
|
||||
let mut civ_leader = leader.decode(civ_leader).unwrap();
|
||||
let mut siv_leader = leader.decode(siv_leader).unwrap();
|
||||
|
||||
let SessionKeys {
|
||||
client_write_key: cwk_follower,
|
||||
server_write_key: swk_follower,
|
||||
client_iv: civ_follower,
|
||||
server_iv: siv_follower,
|
||||
} = follower_prf_out.keys;
|
||||
|
||||
let mut cwk_follower = follower.decode(cwk_follower).unwrap();
|
||||
let mut swk_follower = follower.decode(swk_follower).unwrap();
|
||||
let mut civ_follower = follower.decode(civ_follower).unwrap();
|
||||
let mut siv_follower = follower.decode(siv_follower).unwrap();
|
||||
|
||||
while prf_leader.wants_flush() || prf_follower.wants_flush() {
|
||||
tokio::try_join!(
|
||||
async {
|
||||
prf_leader.flush(&mut leader).unwrap();
|
||||
leader.execute_all(&mut ctx_a).await
|
||||
},
|
||||
async {
|
||||
prf_follower.flush(&mut follower).unwrap();
|
||||
follower.execute_all(&mut ctx_b).await
|
||||
}
|
||||
)
|
||||
.unwrap();
|
||||
follower.set_client_random(&mut follower_vm, None).unwrap();
|
||||
}
|
||||
|
||||
leader
|
||||
.set_server_random(&mut leader_vm, server_random)
|
||||
let cwk_leader = cwk_leader.try_recv().unwrap().unwrap();
|
||||
let swk_leader = swk_leader.try_recv().unwrap().unwrap();
|
||||
let civ_leader = civ_leader.try_recv().unwrap().unwrap();
|
||||
let siv_leader = siv_leader.try_recv().unwrap().unwrap();
|
||||
|
||||
let cwk_follower = cwk_follower.try_recv().unwrap().unwrap();
|
||||
let swk_follower = swk_follower.try_recv().unwrap().unwrap();
|
||||
let civ_follower = civ_follower.try_recv().unwrap().unwrap();
|
||||
let siv_follower = siv_follower.try_recv().unwrap().unwrap();
|
||||
|
||||
assert_eq!(cwk_leader, cwk_follower);
|
||||
assert_eq!(swk_leader, swk_follower);
|
||||
assert_eq!(civ_leader, civ_follower);
|
||||
assert_eq!(siv_leader, siv_follower);
|
||||
|
||||
assert_eq!(cwk_leader, cwk_expected);
|
||||
assert_eq!(swk_leader, swk_expected);
|
||||
assert_eq!(civ_leader, civ_expected);
|
||||
assert_eq!(siv_leader, siv_expected);
|
||||
|
||||
// client finished
|
||||
prf_leader.set_cf_hash(cf_hs_hash).unwrap();
|
||||
prf_follower.set_cf_hash(cf_hs_hash).unwrap();
|
||||
|
||||
let cf_vd_leader = leader_prf_out.cf_vd;
|
||||
let cf_vd_follower = follower_prf_out.cf_vd;
|
||||
|
||||
let mut cf_vd_leader = leader.decode(cf_vd_leader).unwrap();
|
||||
let mut cf_vd_follower = follower.decode(cf_vd_follower).unwrap();
|
||||
|
||||
while prf_leader.wants_flush() || prf_follower.wants_flush() {
|
||||
tokio::try_join!(
|
||||
async {
|
||||
prf_leader.flush(&mut leader).unwrap();
|
||||
leader.execute_all(&mut ctx_a).await
|
||||
},
|
||||
async {
|
||||
prf_follower.flush(&mut follower).unwrap();
|
||||
follower.execute_all(&mut ctx_b).await
|
||||
}
|
||||
)
|
||||
.unwrap();
|
||||
follower
|
||||
.set_server_random(&mut follower_vm, server_random)
|
||||
}
|
||||
|
||||
let cf_vd_leader = cf_vd_leader.try_recv().unwrap().unwrap();
|
||||
let cf_vd_follower = cf_vd_follower.try_recv().unwrap().unwrap();
|
||||
|
||||
assert_eq!(cf_vd_leader, cf_vd_follower);
|
||||
assert_eq!(cf_vd_leader, cf_vd_expected);
|
||||
|
||||
// server finished
|
||||
prf_leader.set_sf_hash(sf_hs_hash).unwrap();
|
||||
prf_follower.set_sf_hash(sf_hs_hash).unwrap();
|
||||
|
||||
let sf_vd_leader = leader_prf_out.sf_vd;
|
||||
let sf_vd_follower = follower_prf_out.sf_vd;
|
||||
|
||||
let mut sf_vd_leader = leader.decode(sf_vd_leader).unwrap();
|
||||
let mut sf_vd_follower = follower.decode(sf_vd_follower).unwrap();
|
||||
|
||||
while prf_leader.wants_flush() || prf_follower.wants_flush() {
|
||||
tokio::try_join!(
|
||||
async {
|
||||
prf_leader.flush(&mut leader).unwrap();
|
||||
leader.execute_all(&mut ctx_a).await
|
||||
},
|
||||
async {
|
||||
prf_follower.flush(&mut follower).unwrap();
|
||||
follower.execute_all(&mut ctx_b).await
|
||||
}
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
let leader_cwk = leader_vm
|
||||
.decode(leader_output.keys.client_write_key)
|
||||
.unwrap();
|
||||
let leader_swk = leader_vm
|
||||
.decode(leader_output.keys.server_write_key)
|
||||
.unwrap();
|
||||
let leader_civ = leader_vm.decode(leader_output.keys.client_iv).unwrap();
|
||||
let leader_siv = leader_vm.decode(leader_output.keys.server_iv).unwrap();
|
||||
let sf_vd_leader = sf_vd_leader.try_recv().unwrap().unwrap();
|
||||
let sf_vd_follower = sf_vd_follower.try_recv().unwrap().unwrap();
|
||||
|
||||
let follower_cwk = follower_vm
|
||||
.decode(follower_output.keys.client_write_key)
|
||||
.unwrap();
|
||||
let follower_swk = follower_vm
|
||||
.decode(follower_output.keys.server_write_key)
|
||||
.unwrap();
|
||||
let follower_civ = follower_vm.decode(follower_output.keys.client_iv).unwrap();
|
||||
let follower_siv = follower_vm.decode(follower_output.keys.server_iv).unwrap();
|
||||
|
||||
futures::join!(
|
||||
async {
|
||||
leader_vm.flush(&mut leader_ctx).await.unwrap();
|
||||
leader_vm.execute(&mut leader_ctx).await.unwrap();
|
||||
leader_vm.flush(&mut leader_ctx).await.unwrap();
|
||||
},
|
||||
async {
|
||||
follower_vm.flush(&mut follower_ctx).await.unwrap();
|
||||
follower_vm.execute(&mut follower_ctx).await.unwrap();
|
||||
follower_vm.flush(&mut follower_ctx).await.unwrap();
|
||||
}
|
||||
);
|
||||
|
||||
let leader_cwk = leader_cwk.await.unwrap();
|
||||
let leader_swk = leader_swk.await.unwrap();
|
||||
let leader_civ = leader_civ.await.unwrap();
|
||||
let leader_siv = leader_siv.await.unwrap();
|
||||
|
||||
let follower_cwk = follower_cwk.await.unwrap();
|
||||
let follower_swk = follower_swk.await.unwrap();
|
||||
let follower_civ = follower_civ.await.unwrap();
|
||||
let follower_siv = follower_siv.await.unwrap();
|
||||
|
||||
let (expected_cwk, expected_swk, expected_civ, expected_siv) =
|
||||
session_keys(pms, client_random, server_random);
|
||||
|
||||
assert_eq!(leader_cwk, expected_cwk);
|
||||
assert_eq!(leader_swk, expected_swk);
|
||||
assert_eq!(leader_civ, expected_civ);
|
||||
assert_eq!(leader_siv, expected_siv);
|
||||
|
||||
assert_eq!(follower_cwk, expected_cwk);
|
||||
assert_eq!(follower_swk, expected_swk);
|
||||
assert_eq!(follower_civ, expected_civ);
|
||||
assert_eq!(follower_siv, expected_siv);
|
||||
|
||||
let cf_hs_hash = [1u8; 32];
|
||||
let sf_hs_hash = [2u8; 32];
|
||||
|
||||
leader.set_cf_hash(&mut leader_vm, cf_hs_hash).unwrap();
|
||||
leader.set_sf_hash(&mut leader_vm, sf_hs_hash).unwrap();
|
||||
|
||||
follower.set_cf_hash(&mut follower_vm, cf_hs_hash).unwrap();
|
||||
follower.set_sf_hash(&mut follower_vm, sf_hs_hash).unwrap();
|
||||
|
||||
let leader_cf_vd = leader_vm.decode(leader_output.cf_vd).unwrap();
|
||||
let leader_sf_vd = leader_vm.decode(leader_output.sf_vd).unwrap();
|
||||
|
||||
let follower_cf_vd = follower_vm.decode(follower_output.cf_vd).unwrap();
|
||||
let follower_sf_vd = follower_vm.decode(follower_output.sf_vd).unwrap();
|
||||
|
||||
futures::join!(
|
||||
async {
|
||||
leader_vm.flush(&mut leader_ctx).await.unwrap();
|
||||
leader_vm.execute(&mut leader_ctx).await.unwrap();
|
||||
leader_vm.flush(&mut leader_ctx).await.unwrap();
|
||||
},
|
||||
async {
|
||||
follower_vm.flush(&mut follower_ctx).await.unwrap();
|
||||
follower_vm.execute(&mut follower_ctx).await.unwrap();
|
||||
follower_vm.flush(&mut follower_ctx).await.unwrap();
|
||||
}
|
||||
);
|
||||
|
||||
let leader_cf_vd = leader_cf_vd.await.unwrap();
|
||||
let leader_sf_vd = leader_sf_vd.await.unwrap();
|
||||
|
||||
let follower_cf_vd = follower_cf_vd.await.unwrap();
|
||||
let follower_sf_vd = follower_sf_vd.await.unwrap();
|
||||
|
||||
let expected_cf_vd = compute_vd(ms, b"client finished", cf_hs_hash);
|
||||
let expected_sf_vd = compute_vd(ms, b"server finished", sf_hs_hash);
|
||||
|
||||
assert_eq!(leader_cf_vd, expected_cf_vd);
|
||||
assert_eq!(leader_sf_vd, expected_sf_vd);
|
||||
assert_eq!(follower_cf_vd, expected_cf_vd);
|
||||
assert_eq!(follower_sf_vd, expected_sf_vd);
|
||||
assert_eq!(sf_vd_leader, sf_vd_follower);
|
||||
assert_eq!(sf_vd_leader, sf_vd_expected);
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user