mirror of
https://github.com/tlsnotary/tlsn.git
synced 2026-01-11 15:47:58 -05:00
Compare commits
134 Commits
poseidon_c
...
poc/verifi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
616ec1394a | ||
|
|
ef6180c313 | ||
|
|
1a80ef75f8 | ||
|
|
f2ff4ba792 | ||
|
|
9bf3371873 | ||
|
|
ba0056c8db | ||
|
|
c790b2482a | ||
|
|
0db2e6b48f | ||
|
|
9d853eb496 | ||
|
|
0c2cc6e466 | ||
|
|
6923ceefd3 | ||
|
|
5239c2328a | ||
|
|
2c048e92ed | ||
|
|
6c8cf8a182 | ||
|
|
59781d1293 | ||
|
|
43fd4d34b5 | ||
|
|
6a7c5384a9 | ||
|
|
7e469006c0 | ||
|
|
4a604c98ce | ||
|
|
55091b5e94 | ||
|
|
126ba26648 | ||
|
|
2e571b0684 | ||
|
|
4e0141f993 | ||
|
|
c9ca87f4b4 | ||
|
|
bc1eba18c9 | ||
|
|
c128ab16ce | ||
|
|
a87125ff88 | ||
|
|
0933d711d2 | ||
|
|
7e631de84a | ||
|
|
79c230f2fa | ||
|
|
345d5d45ad | ||
|
|
02cdbb8130 | ||
|
|
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 | ||
|
|
328c2af162 | ||
|
|
cdb80e1458 | ||
|
|
eeccbef909 | ||
|
|
190b7b0bf6 | ||
|
|
c70caa5ed9 | ||
|
|
20137b8c6c | ||
|
|
4cdd1395e8 | ||
|
|
c1b3d64d5d | ||
|
|
61ce838f8c | ||
|
|
efca281222 | ||
|
|
b24041b9f5 | ||
|
|
9649d6e4cf | ||
|
|
bc69683ecf | ||
|
|
6c468a91cf | ||
|
|
dcff0b9152 | ||
|
|
5f91926154 | ||
|
|
0496cbaeb1 | ||
|
|
d8747d49e3 | ||
|
|
6fe328581c | ||
|
|
6d1140355b | ||
|
|
5246beabf5 | ||
|
|
29efc35d14 | ||
|
|
32d25e5c69 | ||
|
|
ca9d364fc9 | ||
|
|
5cbafe17f5 | ||
|
|
acabb7761b | ||
|
|
c384a393bf | ||
|
|
be0be19018 | ||
|
|
63bd6abc5d | ||
|
|
cb13169b82 | ||
|
|
25d65734c0 | ||
|
|
119ae4b2a8 | ||
|
|
f59153b0a0 | ||
|
|
bffe9ebb0b | ||
|
|
65299d7def |
3
.github/codecov.yml
vendored
Normal file
3
.github/codecov.yml
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
github_checks:
|
||||
annotations: false
|
||||
comment: false
|
||||
12
.github/scripts/build-server.sh
vendored
12
.github/scripts/build-server.sh
vendored
@@ -1,12 +0,0 @@
|
||||
#!/bin/bash
|
||||
# https://github.com/tlsnotary/tlsn/pull/419
|
||||
set -ex
|
||||
|
||||
environment=$1
|
||||
|
||||
aws s3 sync .git s3://tlsn-deploy/$environment/.git --delete
|
||||
|
||||
cargo build -p notary-server --release
|
||||
aws s3 cp ./target/release/notary-server s3://tlsn-deploy/$environment/
|
||||
|
||||
exit 0
|
||||
27
.github/scripts/deploy-server.sh
vendored
27
.github/scripts/deploy-server.sh
vendored
@@ -1,27 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -ex
|
||||
|
||||
environment=$1
|
||||
branch=$2
|
||||
|
||||
INSTANCE_ID=$(aws ec2 describe-instances --filters Name=tag:Name,Values=[tlsnotary-backend-v1] Name=instance-state-name,Values=[running] --query "Reservations[*].Instances[*][InstanceId]" --output text)
|
||||
aws ec2 create-tags --resources $INSTANCE_ID --tags "Key=$environment,Value=$branch"
|
||||
|
||||
COMMIT_HASH=$(git rev-parse HEAD)
|
||||
DEPLOY_ID=$(aws deploy create-deployment --application-name tlsn-$environment-v1 --deployment-group-name tlsn-$environment-v1-group --github-location repository=$GITHUB_REPOSITORY,commitId=$COMMIT_HASH --ignore-application-stop-failures --file-exists OVERWRITE --output text)
|
||||
|
||||
while true; do
|
||||
STATUS=$(aws deploy get-deployment --deployment-id $DEPLOY_ID --query 'deploymentInfo.status' --output text)
|
||||
if [ $STATUS != "InProgress" ] && [ $STATUS != "Created" ]; then
|
||||
if [ $STATUS = "Succeeded" ]; then
|
||||
echo "SUCCESS"
|
||||
exit 0
|
||||
else
|
||||
echo "Failed"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "Deploying..."
|
||||
fi
|
||||
sleep 30
|
||||
done
|
||||
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
|
||||
33
.github/scripts/modify-proxy.sh
vendored
33
.github/scripts/modify-proxy.sh
vendored
@@ -1,33 +0,0 @@
|
||||
#!/bin/bash
|
||||
# This script is triggered by Deploy server workflow in order to send an execution command of cd-scripts/modify_proxy.sh via AWS SSM to the proxy server
|
||||
|
||||
set -e
|
||||
|
||||
GH_OWNER="tlsnotary"
|
||||
GH_REPO="tlsn"
|
||||
BACKEND_INSTANCE_ID=$(aws ec2 describe-instances --filters Name=tag:Name,Values=[tlsnotary-backend-v1] Name=instance-state-name,Values=[running] --query "Reservations[*].Instances[*][InstanceId]" --output text)
|
||||
PROXY_INSTANCE_ID=$(aws ec2 describe-instances --filters Name=tag:Name,Values=[tlsnotary-web] Name=instance-state-name,Values=[running] --query "Reservations[*].Instances[*][InstanceId]" --output text)
|
||||
TAGS=$(aws ec2 describe-instances --instance-ids $BACKEND_INSTANCE_ID --query 'Reservations[*].Instances[*].Tags')
|
||||
|
||||
TAG=$(echo $TAGS | jq -r '.[][][] | select(.Key == "stable").Value')
|
||||
PORT=$(echo $TAGS | jq -r '.[][][] | select(.Key == "port").Value')
|
||||
|
||||
COMMAND_ID=$(aws ssm send-command --document-name "AWS-RunRemoteScript" --instance-ids $PROXY_INSTANCE_ID --parameters '{"sourceType":["GitHub"],"sourceInfo":["{\"owner\":\"'${GH_OWNER}'\", \"repository\":\"'${GH_REPO}'\", \"getOptions\":\"branch:'${TAG}'\", \"path\": \"cd-scripts\"}"],"commandLine":["modify_proxy.sh '${PORT}' '${TAG}' "]}' --output text --query "Command.CommandId")
|
||||
|
||||
while true; do
|
||||
SSM_STATUS=$(aws ssm list-command-invocations --command-id $COMMAND_ID --details --query "CommandInvocations[].Status" --output text)
|
||||
|
||||
if [ $SSM_STATUS != "Success" ] && [ $SSM_STATUS != "InProgress" ]; then
|
||||
echo "Proxy modification failed"
|
||||
aws ssm list-command-invocations --command-id $COMMAND_ID --details --query "CommandInvocations[].CommandPlugins[].{Status:Status,Output:Output}"
|
||||
exit 1
|
||||
elif [ $SSM_STATUS = "Success" ]; then
|
||||
aws ssm list-command-invocations --command-id $COMMAND_ID --details --query "CommandInvocations[].CommandPlugins[].{Status:Status,Output:Output}"
|
||||
echo "Success"
|
||||
break
|
||||
fi
|
||||
|
||||
sleep 2
|
||||
done
|
||||
|
||||
exit 0
|
||||
86
.github/workflows/cd-server.yml
vendored
86
.github/workflows/cd-server.yml
vendored
@@ -1,86 +0,0 @@
|
||||
name: Deploy server
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
tags:
|
||||
- "[v]?[0-9]+.[0-9]+.[0-9]+*"
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
environment:
|
||||
description: "Environment"
|
||||
required: true
|
||||
default: "nightly"
|
||||
type: choice
|
||||
options:
|
||||
- nightly
|
||||
- stable
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
DATA_ENV: ${{ github.event.inputs.environment || 'nightly' }}
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: read
|
||||
|
||||
steps:
|
||||
- name: Manipulate Environment
|
||||
id: manipulate
|
||||
run: |
|
||||
if [ "${{ github.event_name }}" = "push" ] && [ "$GITHUB_REF_NAME" = "dev" ]; then
|
||||
echo "env=nightly" >> $GITHUB_OUTPUT
|
||||
elif [ "${{ github.event_name }}" = "push" ] && [[ "${{ github.ref }}" = "refs/tags/"* ]]; then
|
||||
echo "env=stable" >> $GITHUB_OUTPUT
|
||||
elif [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
||||
echo "env=${{ env.DATA_ENV }}" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "Operation not permitted"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Wait for integration test workflow to succeed
|
||||
if: github.event_name == 'push'
|
||||
uses: lewagon/wait-on-check-action@v1.3.1
|
||||
with:
|
||||
ref: ${{ github.ref }}
|
||||
# More details [here](https://github.com/lewagon/wait-on-check-action#check-name)
|
||||
check-name: 'Run tests release build'
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
# How frequent (in seconds) this job will call GitHub API to check the status of the job specified at 'check-name'
|
||||
wait-interval: 60
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Configure AWS Credentials
|
||||
uses: aws-actions/configure-aws-credentials@v4
|
||||
with:
|
||||
role-to-assume: arn:aws:iam::490752553772:role/tlsn-deploy-slc
|
||||
role-duration-seconds: 1800
|
||||
aws-region: eu-central-1
|
||||
|
||||
- name: Install stable rust toolchain
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: stable
|
||||
|
||||
- name: Cargo build
|
||||
run: |
|
||||
.github/scripts/build-server.sh ${{ steps.manipulate.outputs.env }}
|
||||
|
||||
- name: Trigger Deployment
|
||||
run: |
|
||||
.github/scripts/deploy-server.sh ${{ steps.manipulate.outputs.env }} $GITHUB_REF_NAME
|
||||
|
||||
- name: Modify Proxy
|
||||
if: ${{ steps.manipulate.outputs.env == 'stable' }}
|
||||
run: |
|
||||
.github/scripts/modify-proxy.sh
|
||||
52
.github/workflows/cd.yml
vendored
52
.github/workflows/cd.yml
vendored
@@ -1,52 +0,0 @@
|
||||
name: cd
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "[v]?[0-9]+.[0-9]+.[0-9]+*"
|
||||
|
||||
env:
|
||||
CONTAINER_REGISTRY: ghcr.io
|
||||
|
||||
jobs:
|
||||
build_and_publish_notary_server_image:
|
||||
name: Build and publish notary server's image
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
steps:
|
||||
- name: Wait for integration test workflow to succeed
|
||||
uses: lewagon/wait-on-check-action@v1.3.1
|
||||
with:
|
||||
ref: ${{ github.ref }}
|
||||
# More details [here](https://github.com/lewagon/wait-on-check-action#check-name)
|
||||
check-name: 'Run tests release build'
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
# How frequent (in seconds) this job will call GitHub API to check the status of the job specified at 'check-name'
|
||||
wait-interval: 60
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Log in to the Container registry
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ${{ env.CONTAINER_REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Extract metadata (tags, labels) for Docker image of notary server
|
||||
id: meta-notary-server
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: ${{ env.CONTAINER_REGISTRY }}/${{ github.repository }}/notary-server
|
||||
|
||||
- name: Build and push Docker image of notary server
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
tags: ${{ steps.meta-notary-server.outputs.tags }}
|
||||
labels: ${{ steps.meta-notary-server.outputs.labels }}
|
||||
file: ./crates/notary/server/notary-server.Dockerfile
|
||||
302
.github/workflows/ci.yml
vendored
302
.github/workflows/ci.yml
vendored
@@ -8,6 +8,11 @@ on:
|
||||
- "[v]?[0-9]+.[0-9]+.[0-9]+*"
|
||||
pull_request:
|
||||
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: read
|
||||
attestations: write
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse
|
||||
@@ -17,8 +22,29 @@ 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.87.0
|
||||
|
||||
jobs:
|
||||
clippy:
|
||||
name: Clippy
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install rust toolchain
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
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 --locked -- -D warnings
|
||||
|
||||
fmt:
|
||||
name: Check formatting
|
||||
runs-on: ubuntu-latest
|
||||
@@ -34,10 +60,11 @@ jobs:
|
||||
components: rustfmt
|
||||
|
||||
- name: Use caching
|
||||
uses: Swatinem/rust-cache@v2.7.3
|
||||
uses: Swatinem/rust-cache@v2.7.7
|
||||
|
||||
- name: Check formatting
|
||||
run: cargo +nightly fmt --check --all
|
||||
|
||||
build-and-test:
|
||||
name: Build and test
|
||||
runs-on: ubuntu-latest
|
||||
@@ -45,23 +72,20 @@ 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
|
||||
components: clippy
|
||||
toolchain: ${{ env.RUST_VERSION }}
|
||||
|
||||
- name: Use caching
|
||||
uses: Swatinem/rust-cache@v2.7.3
|
||||
|
||||
- name: Clippy
|
||||
run: cargo clippy --all-features --all-targets -- -D warnings
|
||||
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
|
||||
run: cargo test --no-fail-fast --locked
|
||||
|
||||
wasm:
|
||||
name: Build and Test wasm
|
||||
runs-on: ubuntu-latest
|
||||
@@ -69,11 +93,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
|
||||
@@ -91,22 +115,25 @@ jobs:
|
||||
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
|
||||
|
||||
- name: Use caching
|
||||
uses: Swatinem/rust-cache@v2.7.3
|
||||
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/')
|
||||
@@ -123,43 +150,248 @@ 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.3
|
||||
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 -- --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
|
||||
env:
|
||||
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:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
files: lcov.info
|
||||
fail_ci_if_error: true
|
||||
# trigger-deployment:
|
||||
# doing this here due to feedback @ https://github.com/tlsnotary/tlsn/pull/631#issuecomment-2415806267
|
||||
# needs: tests-integration
|
||||
# uses: ./.github/workflows/tee-cd.yml
|
||||
# with:
|
||||
# # what this is supposed to do -> $ref is the tag: e.g., v0.1.0-alpha.7; pass the $ref string to the cd script and update reverse proxy / deploy
|
||||
# ref: ${{ github.ref_name }}
|
||||
|
||||
build-sgx:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build-and-test
|
||||
container:
|
||||
image: rust:latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Clang
|
||||
run: |
|
||||
apt update
|
||||
apt install -y clang
|
||||
|
||||
- name: Use caching
|
||||
uses: Swatinem/rust-cache@v2.7.7
|
||||
|
||||
- name: Build Rust Binary
|
||||
run: |
|
||||
cargo build --locked --bin notary-server --release --features tee_quote
|
||||
cp --verbose target/release/notary-server $GITHUB_WORKSPACE
|
||||
|
||||
- name: Upload Binary for use in the Gramine Job
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: notary-server
|
||||
path: notary-server
|
||||
if-no-files-found: error
|
||||
|
||||
gramine-sgx:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build-sgx
|
||||
container:
|
||||
image: gramineproject/gramine:latest
|
||||
if: github.ref == 'refs/heads/dev' || (startsWith(github.ref, 'refs/tags/v') && contains(github.ref, '.'))
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Restore SGX signing key from secrets
|
||||
run: |
|
||||
mkdir -p "${HOME}/.config/gramine/"
|
||||
echo "${{ secrets.SGX_SIGNING_KEY }}" > "${HOME}/.config/gramine/enclave-key.pem"
|
||||
# verify key
|
||||
openssl rsa -in "${HOME}/.config/gramine/enclave-key.pem" -check -noout
|
||||
|
||||
- name: Download notary-server binary from build job
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: notary-server
|
||||
path: crates/notary/server/tee
|
||||
|
||||
- name: Install jq
|
||||
run: |
|
||||
apt update
|
||||
apt install -y jq
|
||||
|
||||
- name: Use Gramine to calculate measurements
|
||||
run: |
|
||||
cd crates/notary/server/tee
|
||||
|
||||
chmod +x notary-server
|
||||
|
||||
gramine-manifest \
|
||||
-Dlog_level=debug \
|
||||
-Darch_libdir=/lib/x86_64-linux-gnu \
|
||||
-Dself_exe=notary-server \
|
||||
notary-server.manifest.template \
|
||||
notary-server.manifest
|
||||
|
||||
gramine-sgx-sign \
|
||||
--manifest notary-server.manifest \
|
||||
--output notary-server.manifest.sgx
|
||||
|
||||
gramine-sgx-sigstruct-view --verbose --output-format=json notary-server.sig | tee >> notary-server-sigstruct.json
|
||||
|
||||
cat notary-server-sigstruct.json
|
||||
|
||||
mr_enclave=$(jq -r '.mr_enclave' notary-server-sigstruct.json)
|
||||
mr_signer=$(jq -r '.mr_signer' notary-server-sigstruct.json)
|
||||
|
||||
echo "mrenclave=$mr_enclave" >>"$GITHUB_OUTPUT"
|
||||
echo "#### sgx mrenclave" | tee >>$GITHUB_STEP_SUMMARY
|
||||
echo "\`\`\`mr_enclave: ${mr_enclave}\`\`\`" | tee >>$GITHUB_STEP_SUMMARY
|
||||
echo "\`\`\`mr_signer: ${mr_signer}\`\`\`" | tee >>$GITHUB_STEP_SUMMARY
|
||||
|
||||
- name: Upload notary-server and signatures
|
||||
id: upload-notary-server-sgx
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: notary-server-sgx.zip
|
||||
path: |
|
||||
crates/notary/server/tee/notary-server
|
||||
crates/notary/server/tee/notary-server-sigstruct.json
|
||||
crates/notary/server/tee/notary-server.sig
|
||||
crates/notary/server/tee/notary-server.manifest
|
||||
crates/notary/server/tee/notary-server.manifest.sgx
|
||||
crates/notary/server/tee/README.md
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Attest Build Provenance
|
||||
if: startsWith(github.ref, 'refs/tags/') || github.ref == 'refs/heads/dev'
|
||||
uses: actions/attest-build-provenance@v2
|
||||
with:
|
||||
subject-name: notary-server-sgx.zip
|
||||
subject-digest: sha256:${{ steps.upload-notary-server-sgx.outputs.artifact-digest }}
|
||||
|
||||
- uses: geekyeggo/delete-artifact@v5 # Delete notary-server from the build job, It is part of the zipfile with the signature
|
||||
with:
|
||||
name: notary-server
|
||||
|
||||
gramine-sgx-docker:
|
||||
runs-on: ubuntu-latest
|
||||
needs: gramine-sgx
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
env:
|
||||
CONTAINER_REGISTRY: ghcr.io
|
||||
if: github.ref == 'refs/heads/dev' || (startsWith(github.ref, 'refs/tags/v') && contains(github.ref, '.'))
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
sparse-checkout: './crates/notary/server/tee/notary-server-sgx.Dockerfile'
|
||||
|
||||
- name: Download notary-server-sgx.zip from gramine-sgx job
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: notary-server-sgx.zip
|
||||
path: ./notary-server-sgx
|
||||
|
||||
- name: Log in to the Container registry
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ${{ env.CONTAINER_REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Extract metadata (tags, labels) for Docker image of notary server
|
||||
id: meta-notary-server-sgx
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: ${{ env.CONTAINER_REGISTRY }}/${{ github.repository }}/notary-server-sgx
|
||||
|
||||
- name: Build and push Docker image of notary server
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
tags: ${{ steps.meta-notary-server-sgx.outputs.tags }}
|
||||
labels: ${{ steps.meta-notary-server-sgx.outputs.labels }}
|
||||
file: ./crates/notary/server/tee/notary-server-sgx.Dockerfile
|
||||
|
||||
build_and_publish_notary_server_image:
|
||||
name: Build and publish notary server's image
|
||||
runs-on: ubuntu-latest
|
||||
needs: build-and-test
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
env:
|
||||
CONTAINER_REGISTRY: ghcr.io
|
||||
if: github.ref == 'refs/heads/dev' || (startsWith(github.ref, 'refs/tags/v') && contains(github.ref, '.'))
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Log in to the Container registry
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ${{ env.CONTAINER_REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Extract metadata (tags, labels) for Docker image of notary server
|
||||
id: meta-notary-server
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: ${{ env.CONTAINER_REGISTRY }}/${{ github.repository }}/notary-server
|
||||
|
||||
- name: Build and push Docker image of notary server
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
tags: ${{ steps.meta-notary-server.outputs.tags }}
|
||||
labels: ${{ steps.meta-notary-server.outputs.labels }}
|
||||
file: ./crates/notary/server/notary-server.Dockerfile
|
||||
|
||||
create-release-draft:
|
||||
name: Create Release Draft
|
||||
needs: build_and_publish_notary_server_image
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
if: startsWith(github.ref, 'refs/tags/v') && contains(github.ref, '.')
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Create GitHub Release Draft
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
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.8-pre'
|
||||
default: 'v0.1.0-alpha.12'
|
||||
|
||||
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')
|
||||
|
||||
- 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
|
||||
|
||||
10
.github/workflows/rustdoc.yml
vendored
10
.github/workflows/rustdoc.yml
vendored
@@ -4,7 +4,6 @@ on:
|
||||
push:
|
||||
branches: [dev]
|
||||
pull_request:
|
||||
branches: [dev]
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
@@ -22,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
|
||||
42
.github/workflows/tee-ci.yml
vendored
42
.github/workflows/tee-ci.yml
vendored
@@ -1,42 +0,0 @@
|
||||
name: tee-build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "dev" ]
|
||||
pull_request:
|
||||
branches: [ "dev" ]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build-measure-emulated:
|
||||
environment: tee
|
||||
runs-on: [self-hosted, linux]
|
||||
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 }}
|
||||
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: get emulated measurement (call gramine.sh without the sgx arg)
|
||||
working-directory: ${{ github.workspace }}/crates/notary/server/tee
|
||||
run: |
|
||||
# this fails current ci because gramine.sh is part of this pr so the file doesnt exist
|
||||
# bash .github/scripts/gramine.sh
|
||||
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
|
||||
|
||||
@@ -16,6 +16,8 @@ keywords.
|
||||
|
||||
Try to do one pull request per change.
|
||||
|
||||
**Disclaimer**: While we appreciate all contributions, we do not prioritize minor grammatical fixes (e.g., correcting typos, rewording sentences) unless they significantly improve clarity in technical documentation. These contributions can be a distraction for the team. If you notice a grammatical error, please let us know on our Discord.
|
||||
|
||||
## Linting
|
||||
|
||||
Before a Pull Request (PR) can be merged, the Continuous Integration (CI) pipeline automatically lints all code using [Clippy](https://doc.rust-lang.org/stable/clippy/usage.html). To ensure your code is free of linting issues before creating a PR, run the following command:
|
||||
@@ -59,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.
|
||||
|
||||
|
||||
9506
Cargo.lock
generated
Normal file
9506
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
116
Cargo.toml
116
Cargo.toml
@@ -1,101 +1,115 @@
|
||||
[workspace]
|
||||
members = [
|
||||
"crates/benches/binary",
|
||||
"crates/benches/browser/core",
|
||||
"crates/benches/browser/native",
|
||||
"crates/benches/browser/wasm",
|
||||
"crates/benches/library",
|
||||
"crates/common",
|
||||
"crates/components/aead",
|
||||
"crates/components/block-cipher",
|
||||
"crates/components/deap",
|
||||
"crates/components/cipher",
|
||||
"crates/components/hmac-sha256",
|
||||
"crates/components/hmac-sha256-circuits",
|
||||
"crates/components/key-exchange",
|
||||
"crates/components/stream-cipher",
|
||||
"crates/components/universal-hash",
|
||||
"crates/core",
|
||||
"crates/data-fixtures",
|
||||
"crates/examples",
|
||||
"crates/formats",
|
||||
"crates/notary/client",
|
||||
"crates/notary/common",
|
||||
"crates/notary/server",
|
||||
"crates/notary/tests-integration",
|
||||
"crates/prover",
|
||||
"crates/server-fixture/certs",
|
||||
"crates/server-fixture/server",
|
||||
"crates/tests-integration",
|
||||
"crates/tls/backend",
|
||||
"crates/tls/client",
|
||||
"crates/tls/client-async",
|
||||
"crates/tls/core",
|
||||
"crates/tls/mpc",
|
||||
"crates/mpc-tls",
|
||||
"crates/tls/server-fixture",
|
||||
"crates/verifier",
|
||||
"crates/wasm",
|
||||
"crates/wasm-test-runner",
|
||||
"crates/harness/core",
|
||||
"crates/harness/executor",
|
||||
"crates/harness/runner",
|
||||
]
|
||||
resolver = "2"
|
||||
|
||||
[workspace.lints.rust]
|
||||
# unsafe_code = "forbid"
|
||||
|
||||
[workspace.lints.clippy]
|
||||
# enum_glob_use = "deny"
|
||||
|
||||
[profile.tests-integration]
|
||||
inherits = "release"
|
||||
opt-level = 1
|
||||
|
||||
[profile.release.package."tlsn-wasm"]
|
||||
opt-level = "z"
|
||||
|
||||
[profile.dev.package."tlsn-wasm"]
|
||||
debug = false
|
||||
|
||||
[workspace.dependencies]
|
||||
notary-client = { path = "crates/notary/client" }
|
||||
notary-common = { path = "crates/notary/common" }
|
||||
notary-server = { path = "crates/notary/server" }
|
||||
tls-server-fixture = { path = "crates/tls/server-fixture" }
|
||||
tlsn-aead = { path = "crates/components/aead" }
|
||||
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-block-cipher = { path = "crates/components/block-cipher" }
|
||||
tlsn-cipher = { path = "crates/components/cipher" }
|
||||
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-stream-cipher = { path = "crates/components/stream-cipher" }
|
||||
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-tls-mpc = { path = "crates/tls/mpc" }
|
||||
tlsn-universal-hash = { path = "crates/components/universal-hash" }
|
||||
tlsn-utils = { git = "https://github.com/tlsnotary/tlsn-utils", rev = "0040a00" }
|
||||
tlsn-utils-aio = { git = "https://github.com/tlsnotary/tlsn-utils", rev = "0040a00" }
|
||||
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-verifier = { path = "crates/verifier" }
|
||||
|
||||
mpz-circuits = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "b8ae7ac" }
|
||||
mpz-common = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "b8ae7ac" }
|
||||
mpz-core = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "b8ae7ac" }
|
||||
mpz-garble = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "b8ae7ac" }
|
||||
mpz-garble-core = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "b8ae7ac" }
|
||||
mpz-ole = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "b8ae7ac" }
|
||||
mpz-ot = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "b8ae7ac" }
|
||||
mpz-share-conversion = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "b8ae7ac" }
|
||||
mpz-circuits = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "ccc0057" }
|
||||
mpz-memory-core = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "ccc0057" }
|
||||
mpz-common = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "ccc0057" }
|
||||
mpz-core = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "ccc0057" }
|
||||
mpz-vm-core = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "ccc0057" }
|
||||
mpz-garble = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "ccc0057" }
|
||||
mpz-garble-core = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "ccc0057" }
|
||||
mpz-ole = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "ccc0057" }
|
||||
mpz-ot = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "ccc0057" }
|
||||
mpz-share-conversion = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "ccc0057" }
|
||||
mpz-fields = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "ccc0057" }
|
||||
mpz-zk = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "ccc0057" }
|
||||
mpz-hash = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "ccc0057" }
|
||||
|
||||
serio = { version = "0.1" }
|
||||
spansy = { git = "https://github.com/tlsnotary/tlsn-utils", rev = "0040a00" }
|
||||
uid-mux = { version = "0.1", features = ["serio"] }
|
||||
websocket-relay = { git = "https://github.com/tlsnotary/tlsn-utils", rev = "0040a00" }
|
||||
rangeset = { version = "0.2" }
|
||||
serio = { version = "0.2" }
|
||||
spansy = { git = "https://github.com/tlsnotary/tlsn-utils", rev = "6168663" }
|
||||
uid-mux = { version = "0.2" }
|
||||
websocket-relay = { git = "https://github.com/tlsnotary/tlsn-utils", rev = "6168663" }
|
||||
|
||||
aes = { version = "0.8" }
|
||||
aes-gcm = { version = "0.9" }
|
||||
anyhow = { version = "1.0" }
|
||||
async-trait = { version = "0.1" }
|
||||
async-tungstenite = { version = "0.25" }
|
||||
axum = { version = "0.7" }
|
||||
async-tungstenite = { version = "0.28.2" }
|
||||
axum = { version = "0.8" }
|
||||
bcs = { version = "0.1" }
|
||||
bincode = { version = "1.3" }
|
||||
blake3 = { version = "1.5" }
|
||||
bon = { version = "3.6" }
|
||||
bytes = { version = "1.4" }
|
||||
cfg-if = { version = "1" }
|
||||
chromiumoxide = { version = "0.7" }
|
||||
chrono = { version = "0.4" }
|
||||
cipher = { version = "0.4" }
|
||||
clap = { version = "4.5" }
|
||||
criterion = { version = "0.5" }
|
||||
ctr = { version = "0.9" }
|
||||
derive_builder = { version = "0.12" }
|
||||
@@ -107,12 +121,17 @@ 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" }
|
||||
log = { version = "0.4" }
|
||||
once_cell = { version = "1.19" }
|
||||
@@ -120,9 +139,12 @@ opaque-debug = { version = "0.3" }
|
||||
p256 = { version = "0.13" }
|
||||
pkcs8 = { version = "0.10" }
|
||||
pin-project-lite = { version = "0.2" }
|
||||
rand = { version = "0.8" }
|
||||
rand_chacha = { version = "0.3" }
|
||||
rand_core = { version = "0.6" }
|
||||
pollster = { version = "0.4" }
|
||||
rand = { version = "0.9" }
|
||||
rand_chacha = { version = "0.9" }
|
||||
rand_core = { version = "0.9" }
|
||||
rand06-compat = { version = "0.1" }
|
||||
rayon = { version = "1.10" }
|
||||
regex = { version = "1.10" }
|
||||
ring = { version = "0.17" }
|
||||
rs_merkle = { git = "https://github.com/tlsnotary/rs-merkle.git", rev = "85f3e82" }
|
||||
@@ -138,11 +160,21 @@ thiserror = { version = "1.0" }
|
||||
tokio = { version = "1.38" }
|
||||
tokio-rustls = { version = "0.24" }
|
||||
tokio-util = { version = "0.7" }
|
||||
toml = { version = "0.8" }
|
||||
tower = { version = "0.5" }
|
||||
tower-http = { version = "0.5" }
|
||||
tower-service = { version = "0.3" }
|
||||
tower-util = { version = "0.3.1" }
|
||||
tracing = { version = "0.1" }
|
||||
tracing-subscriber = { version = "0.3" }
|
||||
uuid = { version = "1.4" }
|
||||
wasm-bindgen = { version = "0.2" }
|
||||
wasm-bindgen-futures = { version = "0.4" }
|
||||
web-spawn = { version = "0.2" }
|
||||
web-time = { version = "0.2" }
|
||||
webpki = { version = "0.22" }
|
||||
webpki-roots = { version = "0.26" }
|
||||
ws_stream_tungstenite = { version = "0.13" }
|
||||
ws_stream_tungstenite = { version = "0.14" }
|
||||
# Use the patched ws_stream_wasm to fix the issue https://github.com/najamelan/ws_stream_wasm/issues/12#issuecomment-1711902958
|
||||
ws_stream_wasm = { git = "https://github.com/tlsnotary/ws_stream_wasm", rev = "2ed12aad9f0236e5321f577672f309920b2aef51" }
|
||||
zeroize = { version = "1.8" }
|
||||
|
||||
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
|
||||
@@ -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,16 +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
|
||||
|
||||
# Run the benchmark binary in memory profiling mode.
|
||||
../../../target/release/bench --memory-profiling
|
||||
|
||||
# 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,2 +0,0 @@
|
||||
# exclude any /target folders
|
||||
**/target*
|
||||
@@ -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,71 +0,0 @@
|
||||
use hmac_sha256::{MpcPrf, Prf, PrfConfig, Role};
|
||||
use mpz_common::executor::test_st_executor;
|
||||
use mpz_garble::{config::Role as DEAPRole, protocol::deap::DEAPThread, Memory};
|
||||
use mpz_ot::ideal::ot::ideal_ot;
|
||||
|
||||
pub async fn preprocess_prf_circuits() {
|
||||
let pms = [42u8; 32];
|
||||
let client_random = [69u8; 32];
|
||||
|
||||
let (leader_ctx_0, follower_ctx_0) = test_st_executor(128);
|
||||
let (leader_ctx_1, follower_ctx_1) = test_st_executor(128);
|
||||
|
||||
let (leader_ot_send_0, follower_ot_recv_0) = ideal_ot();
|
||||
let (follower_ot_send_0, leader_ot_recv_0) = ideal_ot();
|
||||
let (leader_ot_send_1, follower_ot_recv_1) = ideal_ot();
|
||||
let (follower_ot_send_1, leader_ot_recv_1) = ideal_ot();
|
||||
|
||||
let leader_thread_0 = DEAPThread::new(
|
||||
DEAPRole::Leader,
|
||||
[0u8; 32],
|
||||
leader_ctx_0,
|
||||
leader_ot_send_0,
|
||||
leader_ot_recv_0,
|
||||
);
|
||||
let leader_thread_1 = leader_thread_0
|
||||
.new_thread(leader_ctx_1, leader_ot_send_1, leader_ot_recv_1)
|
||||
.unwrap();
|
||||
|
||||
let follower_thread_0 = DEAPThread::new(
|
||||
DEAPRole::Follower,
|
||||
[0u8; 32],
|
||||
follower_ctx_0,
|
||||
follower_ot_send_0,
|
||||
follower_ot_recv_0,
|
||||
);
|
||||
let follower_thread_1 = follower_thread_0
|
||||
.new_thread(follower_ctx_1, follower_ot_send_1, follower_ot_recv_1)
|
||||
.unwrap();
|
||||
|
||||
// Set up public PMS for testing.
|
||||
let leader_pms = leader_thread_0.new_public_input::<[u8; 32]>("pms").unwrap();
|
||||
let follower_pms = follower_thread_0
|
||||
.new_public_input::<[u8; 32]>("pms")
|
||||
.unwrap();
|
||||
|
||||
leader_thread_0.assign(&leader_pms, pms).unwrap();
|
||||
|
||||
let mut leader = MpcPrf::new(
|
||||
PrfConfig::builder().role(Role::Leader).build().unwrap(),
|
||||
leader_thread_0,
|
||||
leader_thread_1,
|
||||
);
|
||||
let mut follower = MpcPrf::new(
|
||||
PrfConfig::builder().role(Role::Follower).build().unwrap(),
|
||||
follower_thread_0,
|
||||
follower_thread_1,
|
||||
);
|
||||
|
||||
futures::join!(
|
||||
async {
|
||||
leader.setup(leader_pms).await.unwrap();
|
||||
leader.set_client_random(Some(client_random)).await.unwrap();
|
||||
leader.preprocess().await.unwrap();
|
||||
},
|
||||
async {
|
||||
follower.setup(follower_pms).await.unwrap();
|
||||
follower.set_client_random(None).await.unwrap();
|
||||
follower.preprocess().await.unwrap();
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -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,31 +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"]}
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
wasm-bindgen-rayon = { version = "1.2", features = ["no-bundler"] }
|
||||
|
||||
[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, initThreadPool, init_logging } 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 initThreadPool(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,103 +0,0 @@
|
||||
//! 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;
|
||||
pub use tlsn_wasm::init_logging;
|
||||
|
||||
use anyhow::Result;
|
||||
use tracing::info;
|
||||
use wasm_bindgen::prelude::*;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub use wasm_bindgen_rayon::init_thread_pool;
|
||||
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,131 +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::join, AsyncReadExt as _, AsyncWriteExt as _};
|
||||
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?;
|
||||
|
||||
Ok::<(), anyhow::Error>(())
|
||||
};
|
||||
|
||||
let (prover_task, _) = join(prover_fut, 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,21 +1,34 @@
|
||||
[package]
|
||||
name = "tlsn-common"
|
||||
description = "Common code shared between tlsn-prover and tlsn-verifier"
|
||||
version = "0.1.0-alpha.8-pre"
|
||||
version = "0.1.0-alpha.12"
|
||||
edition = "2021"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
||||
[dependencies]
|
||||
tlsn-core = { workspace = true }
|
||||
tlsn-tls-core = { workspace = true }
|
||||
tlsn-cipher = { workspace = true }
|
||||
mpz-core = { workspace = true }
|
||||
mpz-common = { workspace = true }
|
||||
mpz-garble = { workspace = true }
|
||||
mpz-ot = { workspace = true }
|
||||
mpz-memory-core = { workspace = true }
|
||||
mpz-hash = { workspace = true }
|
||||
mpz-vm-core = { workspace = true }
|
||||
mpz-zk = { workspace = true }
|
||||
|
||||
async-trait = { workspace = true }
|
||||
derive_builder = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
ghash = { workspace = true }
|
||||
once_cell = { workspace = true }
|
||||
opaque-debug = { workspace = true }
|
||||
rand = { workspace = true }
|
||||
rangeset = { workspace = true }
|
||||
serio = { workspace = true, features = ["codec", "bincode"] }
|
||||
thiserror = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
@@ -23,5 +36,9 @@ 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 }
|
||||
|
||||
110
crates/common/src/commit.rs
Normal file
110
crates/common/src/commit.rs
Normal file
@@ -0,0 +1,110 @@
|
||||
//! Plaintext commitment and proof of encryption.
|
||||
|
||||
pub mod hash;
|
||||
|
||||
use mpz_core::bitvec::BitVec;
|
||||
use mpz_memory_core::{binary::Binary, DecodeFutureTyped};
|
||||
use mpz_vm_core::{prelude::*, Vm};
|
||||
|
||||
use crate::{
|
||||
transcript::Record,
|
||||
zk_aes_ctr::{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,
|
||||
}
|
||||
197
crates/common/src/commit/hash.rs
Normal file
197
crates/common/src/commit/hash.rs
Normal file
@@ -0,0 +1,197 @@
|
||||
//! Plaintext hash commitments.
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use mpz_core::bitvec::BitVec;
|
||||
use mpz_hash::sha256::Sha256;
|
||||
use mpz_memory_core::{
|
||||
binary::{Binary, U8},
|
||||
DecodeFutureTyped, MemoryExt, Vector,
|
||||
};
|
||||
use mpz_vm_core::{prelude::*, Vm, VmError};
|
||||
use tlsn_core::{
|
||||
hash::{Blinder, Hash, HashAlgId, TypedHash},
|
||||
transcript::{
|
||||
hash::{PlaintextHash, PlaintextHashSecret},
|
||||
Direction, Idx,
|
||||
},
|
||||
};
|
||||
|
||||
use crate::{transcript::TranscriptRefs, Role};
|
||||
|
||||
/// Future which will resolve to the committed hash values.
|
||||
#[derive(Debug)]
|
||||
pub struct HashCommitFuture {
|
||||
#[allow(clippy::type_complexity)]
|
||||
futs: Vec<(
|
||||
Direction,
|
||||
Idx,
|
||||
HashAlgId,
|
||||
DecodeFutureTyped<BitVec, Vec<u8>>,
|
||||
)>,
|
||||
}
|
||||
|
||||
impl HashCommitFuture {
|
||||
/// Tries to receive the value, returning an error if the value is not
|
||||
/// ready.
|
||||
pub fn try_recv(self) -> Result<Vec<PlaintextHash>, HashCommitError> {
|
||||
let mut output = Vec::new();
|
||||
for (direction, idx, alg, mut fut) in self.futs {
|
||||
let hash = fut
|
||||
.try_recv()
|
||||
.map_err(|_| HashCommitError::decode())?
|
||||
.ok_or_else(HashCommitError::decode)?;
|
||||
output.push(PlaintextHash {
|
||||
direction,
|
||||
idx,
|
||||
hash: TypedHash {
|
||||
alg,
|
||||
value: Hash::try_from(hash).map_err(HashCommitError::convert)?,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
Ok(output)
|
||||
}
|
||||
}
|
||||
|
||||
/// Prove plaintext hash commitments.
|
||||
pub fn prove_hash(
|
||||
vm: &mut dyn Vm<Binary>,
|
||||
refs: &TranscriptRefs,
|
||||
idxs: impl IntoIterator<Item = (Direction, Idx, HashAlgId)>,
|
||||
) -> Result<(HashCommitFuture, Vec<PlaintextHashSecret>), HashCommitError> {
|
||||
let mut futs = Vec::new();
|
||||
let mut secrets = Vec::new();
|
||||
for (direction, idx, alg, hash_ref, blinder_ref) in
|
||||
hash_commit_inner(vm, Role::Prover, refs, idxs)?
|
||||
{
|
||||
let blinder: Blinder = rand::random();
|
||||
|
||||
vm.assign(blinder_ref, blinder.as_bytes().to_vec())?;
|
||||
vm.commit(blinder_ref)?;
|
||||
|
||||
let hash_fut = vm.decode(Vector::<U8>::from(hash_ref))?;
|
||||
|
||||
futs.push((direction, idx.clone(), alg, hash_fut));
|
||||
secrets.push(PlaintextHashSecret {
|
||||
direction,
|
||||
idx,
|
||||
blinder,
|
||||
alg,
|
||||
});
|
||||
}
|
||||
|
||||
Ok((HashCommitFuture { futs }, secrets))
|
||||
}
|
||||
|
||||
/// Verify plaintext hash commitments.
|
||||
pub fn verify_hash(
|
||||
vm: &mut dyn Vm<Binary>,
|
||||
refs: &TranscriptRefs,
|
||||
idxs: impl IntoIterator<Item = (Direction, Idx, HashAlgId)>,
|
||||
) -> Result<HashCommitFuture, HashCommitError> {
|
||||
let mut futs = Vec::new();
|
||||
for (direction, idx, alg, hash_ref, blinder_ref) in
|
||||
hash_commit_inner(vm, Role::Verifier, refs, idxs)?
|
||||
{
|
||||
vm.commit(blinder_ref)?;
|
||||
|
||||
let hash_fut = vm.decode(Vector::<U8>::from(hash_ref))?;
|
||||
|
||||
futs.push((direction, idx, alg, hash_fut));
|
||||
}
|
||||
|
||||
Ok(HashCommitFuture { futs })
|
||||
}
|
||||
|
||||
/// Commit plaintext hashes of the transcript.
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn hash_commit_inner(
|
||||
vm: &mut dyn Vm<Binary>,
|
||||
role: Role,
|
||||
refs: &TranscriptRefs,
|
||||
idxs: impl IntoIterator<Item = (Direction, Idx, HashAlgId)>,
|
||||
) -> Result<Vec<(Direction, Idx, HashAlgId, Array<U8, 32>, Vector<U8>)>, HashCommitError> {
|
||||
let mut output = Vec::new();
|
||||
let mut hashers = HashMap::new();
|
||||
for (direction, idx, alg) in idxs {
|
||||
let blinder = vm.alloc_vec::<U8>(16)?;
|
||||
match role {
|
||||
Role::Prover => vm.mark_private(blinder)?,
|
||||
Role::Verifier => vm.mark_blind(blinder)?,
|
||||
}
|
||||
|
||||
let hash = match alg {
|
||||
HashAlgId::SHA256 => {
|
||||
let mut hasher = if let Some(hasher) = hashers.get(&alg).cloned() {
|
||||
hasher
|
||||
} else {
|
||||
let hasher = Sha256::new_with_init(vm).map_err(HashCommitError::hasher)?;
|
||||
hashers.insert(alg, hasher.clone());
|
||||
hasher
|
||||
};
|
||||
|
||||
for plaintext in refs.get(direction, &idx).expect("plaintext refs are valid") {
|
||||
hasher.update(&plaintext);
|
||||
}
|
||||
hasher.update(&blinder);
|
||||
hasher.finalize(vm).map_err(HashCommitError::hasher)?
|
||||
}
|
||||
alg => {
|
||||
return Err(HashCommitError::unsupported_alg(alg));
|
||||
}
|
||||
};
|
||||
|
||||
output.push((direction, idx, alg, hash, blinder));
|
||||
}
|
||||
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
/// Error type for hash commitments.
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[error(transparent)]
|
||||
pub struct HashCommitError(#[from] ErrorRepr);
|
||||
|
||||
impl HashCommitError {
|
||||
fn decode() -> Self {
|
||||
Self(ErrorRepr::Decode)
|
||||
}
|
||||
|
||||
fn convert(e: &'static str) -> Self {
|
||||
Self(ErrorRepr::Convert(e))
|
||||
}
|
||||
|
||||
fn hasher<E>(e: E) -> Self
|
||||
where
|
||||
E: Into<Box<dyn std::error::Error + Send + Sync>>,
|
||||
{
|
||||
Self(ErrorRepr::Hasher(e.into()))
|
||||
}
|
||||
|
||||
fn unsupported_alg(alg: HashAlgId) -> Self {
|
||||
Self(ErrorRepr::UnsupportedAlg { alg })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[error("hash commit error: {0}")]
|
||||
enum ErrorRepr {
|
||||
#[error("VM error: {0}")]
|
||||
Vm(VmError),
|
||||
#[error("failed to decode hash")]
|
||||
Decode,
|
||||
#[error("failed to convert hash: {0}")]
|
||||
Convert(&'static str),
|
||||
#[error("unsupported hash algorithm: {alg}")]
|
||||
UnsupportedAlg { alg: HashAlgId },
|
||||
#[error("hasher error: {0}")]
|
||||
Hasher(Box<dyn std::error::Error + Send + Sync>),
|
||||
}
|
||||
|
||||
impl From<VmError> for HashCommitError {
|
||||
fn from(value: VmError) -> Self {
|
||||
Self(ErrorRepr::Vm(value))
|
||||
}
|
||||
}
|
||||
@@ -5,16 +5,12 @@ use semver::Version;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::error::Error;
|
||||
|
||||
use crate::Role;
|
||||
|
||||
// Extra cushion room, eg. for sharing J0 blocks.
|
||||
const EXTRA_OTS: usize = 16384;
|
||||
|
||||
const OTS_PER_BYTE_SENT: usize = 8;
|
||||
|
||||
// Without deferred decryption we use 16, with it we use 8.
|
||||
const OTS_PER_BYTE_RECV_ONLINE: usize = 16;
|
||||
const OTS_PER_BYTE_RECV_DEFER: usize = 8;
|
||||
// Default is 32 bytes to decrypt the TLS protocol messages.
|
||||
const DEFAULT_MAX_RECV_ONLINE: usize = 32;
|
||||
// Default maximum number of TLS records to allow.
|
||||
//
|
||||
// This would allow for up to 50Mb upload from prover to verifier.
|
||||
const DEFAULT_RECORDS_LIMIT: usize = 256;
|
||||
|
||||
// Current version that is running.
|
||||
static VERSION: Lazy<Version> = Lazy::new(|| {
|
||||
@@ -29,12 +25,26 @@ static VERSION: Lazy<Version> = Lazy::new(|| {
|
||||
pub struct ProtocolConfig {
|
||||
/// Maximum number of bytes that can be sent.
|
||||
max_sent_data: usize,
|
||||
/// Maximum number of application data records that can be sent.
|
||||
#[builder(setter(strip_option), default)]
|
||||
max_sent_records: Option<usize>,
|
||||
/// Maximum number of bytes that can be decrypted online, i.e. while the
|
||||
/// MPC-TLS connection is active.
|
||||
#[builder(default = "0")]
|
||||
#[builder(default = "DEFAULT_MAX_RECV_ONLINE")]
|
||||
max_recv_data_online: usize,
|
||||
/// Maximum number of bytes that can be received.
|
||||
max_recv_data: usize,
|
||||
/// Maximum number of received application data records that can be
|
||||
/// decrypted online, i.e. while the MPC-TLS connection is active.
|
||||
#[builder(setter(strip_option), default)]
|
||||
max_recv_records_online: Option<usize>,
|
||||
/// Whether the `deferred decryption` feature is toggled on from the start
|
||||
/// of the MPC-TLS connection.
|
||||
#[builder(default = "true")]
|
||||
defer_decryption_from_start: bool,
|
||||
/// Network settings.
|
||||
#[builder(default)]
|
||||
network: NetworkSetting,
|
||||
/// Version that is being run by prover/verifier.
|
||||
#[builder(setter(skip), default = "VERSION.clone()")]
|
||||
version: Version,
|
||||
@@ -62,6 +72,12 @@ impl ProtocolConfig {
|
||||
self.max_sent_data
|
||||
}
|
||||
|
||||
/// Returns the maximum number of application data records that can
|
||||
/// be sent.
|
||||
pub fn max_sent_records(&self) -> Option<usize> {
|
||||
self.max_sent_records
|
||||
}
|
||||
|
||||
/// Returns the maximum number of bytes that can be decrypted online.
|
||||
pub fn max_recv_data_online(&self) -> usize {
|
||||
self.max_recv_data_online
|
||||
@@ -72,24 +88,21 @@ impl ProtocolConfig {
|
||||
self.max_recv_data
|
||||
}
|
||||
|
||||
/// Returns OT sender setup count.
|
||||
pub fn ot_sender_setup_count(&self, role: Role) -> usize {
|
||||
ot_send_estimate(
|
||||
role,
|
||||
self.max_sent_data,
|
||||
self.max_recv_data_online,
|
||||
self.max_recv_data,
|
||||
)
|
||||
/// Returns the maximum number of received application data records that
|
||||
/// can be decrypted online.
|
||||
pub fn max_recv_records_online(&self) -> Option<usize> {
|
||||
self.max_recv_records_online
|
||||
}
|
||||
|
||||
/// Returns OT receiver setup count.
|
||||
pub fn ot_receiver_setup_count(&self, role: Role) -> usize {
|
||||
ot_recv_estimate(
|
||||
role,
|
||||
self.max_sent_data,
|
||||
self.max_recv_data_online,
|
||||
self.max_recv_data,
|
||||
)
|
||||
/// Returns whether the `deferred decryption` feature is toggled on from the
|
||||
/// start of the MPC-TLS connection.
|
||||
pub fn defer_decryption_from_start(&self) -> bool {
|
||||
self.defer_decryption_from_start
|
||||
}
|
||||
|
||||
/// Returns the network settings.
|
||||
pub fn network(&self) -> NetworkSetting {
|
||||
self.network
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,8 +112,14 @@ impl ProtocolConfig {
|
||||
pub struct ProtocolConfigValidator {
|
||||
/// Maximum number of bytes that can be sent.
|
||||
max_sent_data: usize,
|
||||
/// Maximum number of application data records that can be sent.
|
||||
#[builder(default = "DEFAULT_RECORDS_LIMIT")]
|
||||
max_sent_records: usize,
|
||||
/// Maximum number of bytes that can be received.
|
||||
max_recv_data: usize,
|
||||
/// Maximum number of application data records that can be received online.
|
||||
#[builder(default = "DEFAULT_RECORDS_LIMIT")]
|
||||
max_recv_records_online: usize,
|
||||
/// Version that is being run by checker.
|
||||
#[builder(setter(skip), default = "VERSION.clone()")]
|
||||
version: Version,
|
||||
@@ -117,15 +136,28 @@ impl ProtocolConfigValidator {
|
||||
self.max_sent_data
|
||||
}
|
||||
|
||||
/// Returns the maximum number of application data records that can
|
||||
/// be sent.
|
||||
pub fn max_sent_records(&self) -> usize {
|
||||
self.max_sent_records
|
||||
}
|
||||
|
||||
/// Returns the maximum number of bytes that can be received.
|
||||
pub fn max_recv_data(&self) -> usize {
|
||||
self.max_recv_data
|
||||
}
|
||||
|
||||
/// Returns the maximum number of application data records that can
|
||||
/// be received online.
|
||||
pub fn max_recv_records_online(&self) -> usize {
|
||||
self.max_recv_records_online
|
||||
}
|
||||
|
||||
/// Performs compatibility check of the protocol configuration between
|
||||
/// prover and verifier.
|
||||
pub fn validate(&self, config: &ProtocolConfig) -> Result<(), ProtocolConfigError> {
|
||||
self.check_max_transcript_size(config.max_sent_data, config.max_recv_data)?;
|
||||
self.check_max_records(config.max_sent_records, config.max_recv_records_online)?;
|
||||
self.check_version(&config.version)?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -153,6 +185,32 @@ impl ProtocolConfigValidator {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn check_max_records(
|
||||
&self,
|
||||
max_sent_records: Option<usize>,
|
||||
max_recv_records_online: Option<usize>,
|
||||
) -> Result<(), ProtocolConfigError> {
|
||||
if let Some(max_sent_records) = max_sent_records {
|
||||
if max_sent_records > self.max_sent_records {
|
||||
return Err(ProtocolConfigError::max_record_count(format!(
|
||||
"max_sent_records {} is greater than the configured limit {}",
|
||||
max_sent_records, self.max_sent_records,
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(max_recv_records_online) = max_recv_records_online {
|
||||
if max_recv_records_online > self.max_recv_records_online {
|
||||
return Err(ProtocolConfigError::max_record_count(format!(
|
||||
"max_recv_records_online {} is greater than the configured limit {}",
|
||||
max_recv_records_online, self.max_recv_records_online,
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Checks if both versions are the same (might support check for different but
|
||||
// compatible versions in the future).
|
||||
fn check_version(&self, peer_version: &Version) -> Result<(), ProtocolConfigError> {
|
||||
@@ -167,6 +225,24 @@ impl ProtocolConfigValidator {
|
||||
}
|
||||
}
|
||||
|
||||
/// Settings for the network environment.
|
||||
///
|
||||
/// Provides optimization options to adapt the protocol to different network
|
||||
/// situations.
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
|
||||
pub enum NetworkSetting {
|
||||
/// Prefers a bandwidth-heavy protocol.
|
||||
Bandwidth,
|
||||
/// Prefers a latency-heavy protocol.
|
||||
Latency,
|
||||
}
|
||||
|
||||
impl Default for NetworkSetting {
|
||||
fn default() -> Self {
|
||||
Self::Bandwidth
|
||||
}
|
||||
}
|
||||
|
||||
/// A ProtocolConfig error.
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub struct ProtocolConfigError {
|
||||
@@ -193,6 +269,13 @@ impl ProtocolConfigError {
|
||||
}
|
||||
}
|
||||
|
||||
fn max_record_count(msg: impl Into<String>) -> Self {
|
||||
Self {
|
||||
kind: ErrorKind::MaxRecordCount,
|
||||
source: Some(msg.into().into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn version(msg: impl Into<String>) -> Self {
|
||||
Self {
|
||||
kind: ErrorKind::Version,
|
||||
@@ -204,7 +287,8 @@ impl ProtocolConfigError {
|
||||
impl fmt::Display for ProtocolConfigError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self.kind {
|
||||
ErrorKind::MaxTranscriptSize => write!(f, "max transcript size error")?,
|
||||
ErrorKind::MaxTranscriptSize => write!(f, "max transcript size exceeded")?,
|
||||
ErrorKind::MaxRecordCount => write!(f, "max record count exceeded")?,
|
||||
ErrorKind::Version => write!(f, "version error")?,
|
||||
}
|
||||
|
||||
@@ -219,45 +303,10 @@ impl fmt::Display for ProtocolConfigError {
|
||||
#[derive(Debug)]
|
||||
enum ErrorKind {
|
||||
MaxTranscriptSize,
|
||||
MaxRecordCount,
|
||||
Version,
|
||||
}
|
||||
|
||||
/// Returns an estimate of the number of OTs that will be sent.
|
||||
pub fn ot_send_estimate(
|
||||
role: Role,
|
||||
max_sent_data: usize,
|
||||
max_recv_data_online: usize,
|
||||
max_recv_data: usize,
|
||||
) -> usize {
|
||||
match role {
|
||||
Role::Prover => EXTRA_OTS,
|
||||
Role::Verifier => {
|
||||
EXTRA_OTS
|
||||
+ (max_sent_data * OTS_PER_BYTE_SENT)
|
||||
+ (max_recv_data_online * OTS_PER_BYTE_RECV_ONLINE)
|
||||
+ ((max_recv_data - max_recv_data_online) * OTS_PER_BYTE_RECV_DEFER)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns an estimate of the number of OTs that will be received.
|
||||
pub fn ot_recv_estimate(
|
||||
role: Role,
|
||||
max_sent_data: usize,
|
||||
max_recv_data_online: usize,
|
||||
max_recv_data: usize,
|
||||
) -> usize {
|
||||
match role {
|
||||
Role::Prover => {
|
||||
EXTRA_OTS
|
||||
+ (max_sent_data * OTS_PER_BYTE_SENT)
|
||||
+ (max_recv_data_online * OTS_PER_BYTE_RECV_ONLINE)
|
||||
+ ((max_recv_data - max_recv_data_online) * OTS_PER_BYTE_RECV_DEFER)
|
||||
}
|
||||
Role::Verifier => EXTRA_OTS,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
21
crates/common/src/context.rs
Normal file
21
crates/common/src/context.rs
Normal file
@@ -0,0 +1,21 @@
|
||||
//! Execution context.
|
||||
|
||||
use mpz_common::context::Multithread;
|
||||
|
||||
use crate::mux::MuxControl;
|
||||
|
||||
/// Maximum concurrency for multi-threaded context.
|
||||
pub const MAX_CONCURRENCY: usize = 8;
|
||||
|
||||
/// Builds a multi-threaded context with the given muxer.
|
||||
pub fn build_mt_context(mux: MuxControl) -> Multithread {
|
||||
let builder = Multithread::builder().mux(mux).concurrency(MAX_CONCURRENCY);
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
let builder = builder.spawn_handler(|f| {
|
||||
let _ = web_spawn::spawn(f);
|
||||
Ok(())
|
||||
});
|
||||
|
||||
builder.build().unwrap()
|
||||
}
|
||||
238
crates/common/src/encoding.rs
Normal file
238
crates/common/src/encoding.rs
Normal file
@@ -0,0 +1,238 @@
|
||||
//! Encoding commitment protocol.
|
||||
|
||||
use std::ops::Range;
|
||||
|
||||
use mpz_common::Context;
|
||||
use mpz_memory_core::{
|
||||
binary::U8,
|
||||
correlated::{Delta, Key, Mac},
|
||||
Vector,
|
||||
};
|
||||
use rand::Rng;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serio::{stream::IoStreamExt, SinkExt};
|
||||
use tlsn_core::{
|
||||
hash::HashAlgorithm,
|
||||
transcript::{
|
||||
encoding::{
|
||||
new_encoder, Encoder, EncoderSecret, EncodingCommitment, EncodingProvider,
|
||||
EncodingProviderError, EncodingTree, EncodingTreeError,
|
||||
},
|
||||
Direction, Idx,
|
||||
},
|
||||
};
|
||||
|
||||
use crate::transcript::TranscriptRefs;
|
||||
|
||||
/// Bytes of encoding, per byte.
|
||||
const ENCODING_SIZE: usize = 128;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct Encodings {
|
||||
sent: Vec<u8>,
|
||||
recv: Vec<u8>,
|
||||
}
|
||||
|
||||
/// Transfers the encodings using the provided seed and keys.
|
||||
///
|
||||
/// The keys must be consistent with the global delta used in the encodings.
|
||||
pub async fn transfer<'a>(
|
||||
ctx: &mut Context,
|
||||
refs: &TranscriptRefs,
|
||||
delta: &Delta,
|
||||
f: impl Fn(Vector<U8>) -> &'a [Key],
|
||||
) -> Result<EncodingCommitment, EncodingError> {
|
||||
let secret = EncoderSecret::new(rand::rng().random(), delta.as_block().to_bytes());
|
||||
let encoder = new_encoder(&secret);
|
||||
|
||||
let sent_keys: Vec<u8> = refs
|
||||
.sent()
|
||||
.iter()
|
||||
.copied()
|
||||
.flat_map(&f)
|
||||
.flat_map(|key| key.as_block().as_bytes())
|
||||
.copied()
|
||||
.collect();
|
||||
let recv_keys: Vec<u8> = refs
|
||||
.recv()
|
||||
.iter()
|
||||
.copied()
|
||||
.flat_map(&f)
|
||||
.flat_map(|key| key.as_block().as_bytes())
|
||||
.copied()
|
||||
.collect();
|
||||
|
||||
assert_eq!(sent_keys.len() % ENCODING_SIZE, 0);
|
||||
assert_eq!(recv_keys.len() % ENCODING_SIZE, 0);
|
||||
|
||||
let mut sent_encoding = Vec::with_capacity(sent_keys.len());
|
||||
let mut recv_encoding = Vec::with_capacity(recv_keys.len());
|
||||
|
||||
encoder.encode_range(
|
||||
Direction::Sent,
|
||||
0..sent_keys.len() / ENCODING_SIZE,
|
||||
&mut sent_encoding,
|
||||
);
|
||||
encoder.encode_range(
|
||||
Direction::Received,
|
||||
0..recv_keys.len() / ENCODING_SIZE,
|
||||
&mut recv_encoding,
|
||||
);
|
||||
|
||||
sent_encoding
|
||||
.iter_mut()
|
||||
.zip(sent_keys)
|
||||
.for_each(|(enc, key)| *enc ^= key);
|
||||
recv_encoding
|
||||
.iter_mut()
|
||||
.zip(recv_keys)
|
||||
.for_each(|(enc, key)| *enc ^= key);
|
||||
|
||||
ctx.io_mut()
|
||||
.send(Encodings {
|
||||
sent: sent_encoding,
|
||||
recv: recv_encoding,
|
||||
})
|
||||
.await?;
|
||||
|
||||
let root = ctx.io_mut().expect_next().await?;
|
||||
ctx.io_mut().send(secret.clone()).await?;
|
||||
|
||||
Ok(EncodingCommitment {
|
||||
root,
|
||||
secret: secret.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Receives the encodings using the provided MACs.
|
||||
///
|
||||
/// The MACs must be consistent with the global delta used in the encodings.
|
||||
pub async fn receive<'a>(
|
||||
ctx: &mut Context,
|
||||
hasher: &(dyn HashAlgorithm + Send + Sync),
|
||||
refs: &TranscriptRefs,
|
||||
f: impl Fn(Vector<U8>) -> &'a [Mac],
|
||||
idxs: impl IntoIterator<Item = &(Direction, Idx)>,
|
||||
) -> Result<(EncodingCommitment, EncodingTree), EncodingError> {
|
||||
let Encodings { mut sent, mut recv } = ctx.io_mut().expect_next().await?;
|
||||
|
||||
let sent_macs: Vec<u8> = refs
|
||||
.sent()
|
||||
.iter()
|
||||
.copied()
|
||||
.flat_map(&f)
|
||||
.flat_map(|mac| mac.as_bytes())
|
||||
.copied()
|
||||
.collect();
|
||||
let recv_macs: Vec<u8> = refs
|
||||
.recv()
|
||||
.iter()
|
||||
.copied()
|
||||
.flat_map(&f)
|
||||
.flat_map(|mac| mac.as_bytes())
|
||||
.copied()
|
||||
.collect();
|
||||
|
||||
assert_eq!(sent_macs.len() % ENCODING_SIZE, 0);
|
||||
assert_eq!(recv_macs.len() % ENCODING_SIZE, 0);
|
||||
|
||||
if sent.len() != sent_macs.len() {
|
||||
return Err(ErrorRepr::IncorrectMacCount {
|
||||
direction: Direction::Sent,
|
||||
expected: sent_macs.len(),
|
||||
got: sent.len(),
|
||||
}
|
||||
.into());
|
||||
}
|
||||
|
||||
if recv.len() != recv_macs.len() {
|
||||
return Err(ErrorRepr::IncorrectMacCount {
|
||||
direction: Direction::Received,
|
||||
expected: recv_macs.len(),
|
||||
got: recv.len(),
|
||||
}
|
||||
.into());
|
||||
}
|
||||
|
||||
sent.iter_mut()
|
||||
.zip(sent_macs)
|
||||
.for_each(|(enc, mac)| *enc ^= mac);
|
||||
recv.iter_mut()
|
||||
.zip(recv_macs)
|
||||
.for_each(|(enc, mac)| *enc ^= mac);
|
||||
|
||||
let provider = Provider { sent, recv };
|
||||
|
||||
let tree = EncodingTree::new(hasher, idxs, &provider)?;
|
||||
let root = tree.root();
|
||||
|
||||
ctx.io_mut().send(root.clone()).await?;
|
||||
let secret = ctx.io_mut().expect_next().await?;
|
||||
|
||||
let commitment = EncodingCommitment { root, secret };
|
||||
|
||||
Ok((commitment, tree))
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Provider {
|
||||
sent: Vec<u8>,
|
||||
recv: Vec<u8>,
|
||||
}
|
||||
|
||||
impl EncodingProvider for Provider {
|
||||
fn provide_encoding(
|
||||
&self,
|
||||
direction: Direction,
|
||||
range: Range<usize>,
|
||||
dest: &mut Vec<u8>,
|
||||
) -> Result<(), EncodingProviderError> {
|
||||
let encodings = match direction {
|
||||
Direction::Sent => &self.sent,
|
||||
Direction::Received => &self.recv,
|
||||
};
|
||||
|
||||
let start = range.start * ENCODING_SIZE;
|
||||
let end = range.end * ENCODING_SIZE;
|
||||
|
||||
if end > encodings.len() {
|
||||
return Err(EncodingProviderError);
|
||||
}
|
||||
|
||||
dest.extend_from_slice(&encodings[start..end]);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Encoding protocol error.
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[error(transparent)]
|
||||
pub struct EncodingError(#[from] ErrorRepr);
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[error("encoding protocol error: {0}")]
|
||||
enum ErrorRepr {
|
||||
#[error("I/O error: {0}")]
|
||||
Io(std::io::Error),
|
||||
#[error("incorrect MAC count for {direction}: expected {expected}, got {got}")]
|
||||
IncorrectMacCount {
|
||||
direction: Direction,
|
||||
expected: usize,
|
||||
got: usize,
|
||||
},
|
||||
#[error("encoding tree error: {0}")]
|
||||
EncodingTree(EncodingTreeError),
|
||||
}
|
||||
|
||||
impl From<std::io::Error> for EncodingError {
|
||||
fn from(value: std::io::Error) -> Self {
|
||||
Self(ErrorRepr::Io(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<EncodingTreeError> for EncodingError {
|
||||
fn from(value: EncodingTreeError) -> Self {
|
||||
Self(ErrorRepr::EncodingTree(value))
|
||||
}
|
||||
}
|
||||
39
crates/common/src/ghash.rs
Normal file
39
crates/common/src/ghash.rs
Normal file
@@ -0,0 +1,39 @@
|
||||
//! GHASH methods.
|
||||
|
||||
// This module belongs in tls/core. It was moved out here temporarily.
|
||||
|
||||
use ghash::{
|
||||
universal_hash::{KeyInit, UniversalHash as UniversalHashReference},
|
||||
GHash,
|
||||
};
|
||||
|
||||
/// Computes a GHASH tag.
|
||||
pub fn ghash(aad: &[u8], ciphertext: &[u8], key: &[u8; 16]) -> [u8; 16] {
|
||||
let mut ghash = GHash::new(key.into());
|
||||
ghash.update_padded(&build_ghash_data(aad.to_vec(), ciphertext.to_owned()));
|
||||
let out = ghash.finalize();
|
||||
out.into()
|
||||
}
|
||||
|
||||
/// Builds padded data for GHASH.
|
||||
pub fn build_ghash_data(mut aad: Vec<u8>, mut ciphertext: Vec<u8>) -> Vec<u8> {
|
||||
let associated_data_bitlen = (aad.len() as u64) * 8;
|
||||
let text_bitlen = (ciphertext.len() as u64) * 8;
|
||||
|
||||
let len_block = ((associated_data_bitlen as u128) << 64) + (text_bitlen as u128);
|
||||
|
||||
// Pad data to be a multiple of 16 bytes.
|
||||
let aad_padded_block_count = (aad.len() / 16) + (aad.len() % 16 != 0) as usize;
|
||||
aad.resize(aad_padded_block_count * 16, 0);
|
||||
|
||||
let ciphertext_padded_block_count =
|
||||
(ciphertext.len() / 16) + (ciphertext.len() % 16 != 0) as usize;
|
||||
ciphertext.resize(ciphertext_padded_block_count * 16, 0);
|
||||
|
||||
let mut data: Vec<u8> = Vec::with_capacity(aad.len() + ciphertext.len() + 16);
|
||||
data.extend(aad);
|
||||
data.extend(ciphertext);
|
||||
data.extend_from_slice(&len_block.to_be_bytes());
|
||||
|
||||
data
|
||||
}
|
||||
@@ -4,34 +4,21 @@
|
||||
#![deny(clippy::all)]
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
pub mod commit;
|
||||
pub mod config;
|
||||
pub mod context;
|
||||
pub mod encoding;
|
||||
pub mod ghash;
|
||||
pub mod msg;
|
||||
pub mod mux;
|
||||
|
||||
use serio::codec::Codec;
|
||||
|
||||
use crate::mux::MuxControl;
|
||||
|
||||
/// IO type.
|
||||
pub type Io = <serio::codec::Bincode as Codec<uid_mux::yamux::Stream>>::Framed;
|
||||
/// Base OT sender.
|
||||
pub type BaseOTSender = mpz_ot::chou_orlandi::Sender;
|
||||
/// Base OT receiver.
|
||||
pub type BaseOTReceiver = mpz_ot::chou_orlandi::Receiver;
|
||||
/// OT sender.
|
||||
pub type OTSender = mpz_ot::kos::SharedSender<BaseOTReceiver>;
|
||||
/// OT receiver.
|
||||
pub type OTReceiver = mpz_ot::kos::SharedReceiver<BaseOTSender>;
|
||||
/// MPC executor.
|
||||
pub type Executor = mpz_common::executor::MTExecutor<MuxControl>;
|
||||
/// MPC thread context.
|
||||
pub type Context = mpz_common::executor::MTContext<MuxControl, Io>;
|
||||
/// DEAP thread.
|
||||
pub type DEAPThread = mpz_garble::protocol::deap::DEAPThread<Context, OTSender, OTReceiver>;
|
||||
pub mod tag;
|
||||
pub mod transcript;
|
||||
pub mod zk_aes_ctr;
|
||||
|
||||
/// 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,
|
||||
|
||||
@@ -6,16 +6,15 @@ use futures::{
|
||||
future::{FusedFuture, FutureExt},
|
||||
AsyncRead, AsyncWrite, Future,
|
||||
};
|
||||
use serio::codec::Bincode;
|
||||
use tracing::error;
|
||||
use uid_mux::{yamux, FramedMux};
|
||||
use uid_mux::yamux;
|
||||
|
||||
use crate::Role;
|
||||
|
||||
/// Multiplexer supporting unique deterministic stream IDs.
|
||||
pub type Mux<Io> = yamux::Yamux<Io>;
|
||||
/// Multiplexer controller providing streams with a codec attached.
|
||||
pub type MuxControl = FramedMux<yamux::YamuxCtrl, Bincode>;
|
||||
/// Multiplexer controller providing streams.
|
||||
pub type MuxControl = yamux::YamuxCtrl;
|
||||
|
||||
/// Multiplexer future which must be polled for the muxer to make progress.
|
||||
pub struct MuxFuture(
|
||||
@@ -73,7 +72,7 @@ pub fn attach_mux<T: AsyncWrite + AsyncRead + Send + Unpin + 'static>(
|
||||
role: Role,
|
||||
) -> (MuxFuture, MuxControl) {
|
||||
let mut mux_config = yamux::Config::default();
|
||||
mux_config.set_max_num_streams(64);
|
||||
mux_config.set_max_num_streams(36);
|
||||
|
||||
let mux_role = match role {
|
||||
Role::Prover => yamux::Mode::Client,
|
||||
@@ -81,10 +80,10 @@ pub fn attach_mux<T: AsyncWrite + AsyncRead + Send + Unpin + 'static>(
|
||||
};
|
||||
|
||||
let mux = Mux::new(socket, mux_config, mux_role);
|
||||
let ctrl = FramedMux::new(mux.control(), Bincode);
|
||||
let ctrl = mux.control();
|
||||
|
||||
if let Role::Prover = role {
|
||||
ctrl.mux().alloc(64);
|
||||
ctrl.alloc(32);
|
||||
}
|
||||
|
||||
(MuxFuture(Box::new(mux.into_future().fuse())), ctrl)
|
||||
|
||||
157
crates/common/src/tag.rs
Normal file
157
crates/common/src/tag.rs
Normal file
@@ -0,0 +1,157 @@
|
||||
//! TLS record tag verification.
|
||||
|
||||
use crate::{ghash::ghash, transcript::Record};
|
||||
use cipher::{aes::Aes128, Cipher};
|
||||
use mpz_core::bitvec::BitVec;
|
||||
use mpz_memory_core::{
|
||||
binary::{Binary, U8},
|
||||
DecodeFutureTyped,
|
||||
};
|
||||
use mpz_vm_core::{prelude::*, Vm};
|
||||
use tls_core::cipher::make_tls12_aad;
|
||||
|
||||
/// Proves the verification of tags of the given `records`,
|
||||
/// returning a proof.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `vm` - Virtual machine.
|
||||
/// * `key_iv` - Cipher key and IV.
|
||||
/// * `mac_key` - MAC key.
|
||||
/// * `records` - Records for which the verification is to be proven.
|
||||
pub fn verify_tags(
|
||||
vm: &mut dyn Vm<Binary>,
|
||||
key_iv: (Array<U8, 16>, Array<U8, 4>),
|
||||
mac_key: Array<U8, 16>,
|
||||
records: Vec<Record>,
|
||||
) -> Result<TagProof, TagProofError> {
|
||||
let mut aes = Aes128::default();
|
||||
aes.set_key(key_iv.0);
|
||||
aes.set_iv(key_iv.1);
|
||||
|
||||
// Compute j0 blocks.
|
||||
let j0s = records
|
||||
.iter()
|
||||
.map(|rec| {
|
||||
let block = aes.alloc_ctr_block(vm).map_err(TagProofError::vm)?;
|
||||
|
||||
let explicit_nonce: [u8; 8] =
|
||||
rec.explicit_nonce
|
||||
.clone()
|
||||
.try_into()
|
||||
.map_err(|explicit_nonce: Vec<_>| ErrorRepr::ExplicitNonceLength {
|
||||
expected: 8,
|
||||
actual: explicit_nonce.len(),
|
||||
})?;
|
||||
|
||||
vm.assign(block.explicit_nonce, explicit_nonce)
|
||||
.map_err(TagProofError::vm)?;
|
||||
vm.commit(block.explicit_nonce).map_err(TagProofError::vm)?;
|
||||
|
||||
// j0's counter is set to 1.
|
||||
vm.assign(block.counter, 1u32.to_be_bytes())
|
||||
.map_err(TagProofError::vm)?;
|
||||
vm.commit(block.counter).map_err(TagProofError::vm)?;
|
||||
|
||||
let j0 = vm.decode(block.output).map_err(TagProofError::vm)?;
|
||||
|
||||
Ok(j0)
|
||||
})
|
||||
.collect::<Result<Vec<_>, TagProofError>>()?;
|
||||
|
||||
let mac_key = vm.decode(mac_key).map_err(TagProofError::vm)?;
|
||||
|
||||
Ok(TagProof {
|
||||
j0s,
|
||||
records,
|
||||
mac_key,
|
||||
})
|
||||
}
|
||||
|
||||
/// Proof of tag verification.
|
||||
#[derive(Debug)]
|
||||
#[must_use]
|
||||
pub struct TagProof {
|
||||
/// The j0 block for each record.
|
||||
j0s: Vec<DecodeFutureTyped<BitVec, [u8; 16]>>,
|
||||
records: Vec<Record>,
|
||||
/// The MAC key for tag computation.
|
||||
mac_key: DecodeFutureTyped<BitVec, [u8; 16]>,
|
||||
}
|
||||
|
||||
impl TagProof {
|
||||
/// Verifies the proof.
|
||||
pub fn verify(self) -> Result<(), TagProofError> {
|
||||
let Self {
|
||||
j0s,
|
||||
mut mac_key,
|
||||
records,
|
||||
} = self;
|
||||
|
||||
let mac_key = mac_key
|
||||
.try_recv()
|
||||
.map_err(TagProofError::vm)?
|
||||
.ok_or_else(|| ErrorRepr::NotDecoded)?;
|
||||
|
||||
for (mut j0, rec) in j0s.into_iter().zip(records) {
|
||||
let j0 = j0
|
||||
.try_recv()
|
||||
.map_err(TagProofError::vm)?
|
||||
.ok_or_else(|| ErrorRepr::NotDecoded)?;
|
||||
|
||||
let aad = make_tls12_aad(rec.seq, rec.typ, rec.version, rec.ciphertext.len());
|
||||
|
||||
let ghash_tag = ghash(aad.as_ref(), &rec.ciphertext, &mac_key);
|
||||
|
||||
let record_tag = match rec.tag.as_ref() {
|
||||
Some(tag) => tag,
|
||||
None => {
|
||||
// This will never happen, since we only call this method
|
||||
// for proofs where the records' tags are known.
|
||||
return Err(ErrorRepr::UnknownTag.into());
|
||||
}
|
||||
};
|
||||
|
||||
if *record_tag
|
||||
!= ghash_tag
|
||||
.into_iter()
|
||||
.zip(j0.into_iter())
|
||||
.map(|(a, b)| a ^ b)
|
||||
.collect::<Vec<_>>()
|
||||
{
|
||||
return Err(ErrorRepr::InvalidTag.into());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Error for [`J0Proof`].
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[error(transparent)]
|
||||
pub struct TagProofError(#[from] ErrorRepr);
|
||||
|
||||
impl TagProofError {
|
||||
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("j0 proof error: {0}")]
|
||||
enum ErrorRepr {
|
||||
#[error("value was not decoded")]
|
||||
NotDecoded,
|
||||
#[error("VM error: {0}")]
|
||||
Vm(Box<dyn std::error::Error + Send + Sync + 'static>),
|
||||
#[error("tag does not match expected")]
|
||||
InvalidTag,
|
||||
#[error("tag is not known")]
|
||||
UnknownTag,
|
||||
#[error("invalid explicit nonce length: expected {expected}, got {actual}")]
|
||||
ExplicitNonceLength { expected: usize, actual: usize },
|
||||
}
|
||||
315
crates/common/src/transcript.rs
Normal file
315
crates/common/src/transcript.rs
Normal file
@@ -0,0 +1,315 @@
|
||||
//! TLS transcript.
|
||||
|
||||
use mpz_memory_core::{
|
||||
binary::{Binary, U8},
|
||||
MemoryExt, Vector,
|
||||
};
|
||||
use mpz_vm_core::{Vm, VmError};
|
||||
use rangeset::Intersection;
|
||||
use tls_core::msgs::enums::{ContentType, ProtocolVersion};
|
||||
use tlsn_core::transcript::{Direction, Idx, PartialTranscript, Transcript};
|
||||
|
||||
/// A transcript of TLS records sent and received by the prover.
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct TlsTranscript {
|
||||
/// Sent records.
|
||||
pub sent: Vec<Record>,
|
||||
/// Received records.
|
||||
pub recv: Vec<Record>,
|
||||
}
|
||||
|
||||
impl TlsTranscript {
|
||||
/// Returns the application data transcript.
|
||||
pub fn to_transcript(&self) -> Result<Transcript, TlsTranscriptError> {
|
||||
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(ErrorRepr::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(ErrorRepr::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, TlsTranscriptError> {
|
||||
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(ErrorRepr::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(ErrorRepr::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>,
|
||||
/// Tag.
|
||||
pub tag: Option<Vec<u8>>,
|
||||
/// Version.
|
||||
pub version: ProtocolVersion,
|
||||
}
|
||||
|
||||
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 [`TlsTranscript`].
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[error(transparent)]
|
||||
pub struct TlsTranscriptError(#[from] ErrorRepr);
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[error("TLS transcript error")]
|
||||
enum ErrorRepr {
|
||||
#[error("not all application plaintext was committed to in the TLS transcript")]
|
||||
IncompleteTranscript {},
|
||||
}
|
||||
|
||||
/// Decodes the transcript.
|
||||
pub fn decode_transcript(
|
||||
vm: &mut dyn Vm<Binary>,
|
||||
sent: &Idx,
|
||||
recv: &Idx,
|
||||
refs: &TranscriptRefs,
|
||||
) -> Result<(), VmError> {
|
||||
let sent_refs = refs.get(Direction::Sent, sent).expect("index is in bounds");
|
||||
let recv_refs = refs
|
||||
.get(Direction::Received, recv)
|
||||
.expect("index is in bounds");
|
||||
|
||||
for slice in sent_refs.into_iter().chain(recv_refs) {
|
||||
// Drop the future, we don't need it.
|
||||
drop(vm.decode(slice)?);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Verifies a partial transcript.
|
||||
pub fn verify_transcript(
|
||||
vm: &mut dyn Vm<Binary>,
|
||||
transcript: &PartialTranscript,
|
||||
refs: &TranscriptRefs,
|
||||
) -> Result<(), InconsistentTranscript> {
|
||||
let sent_refs = refs
|
||||
.get(Direction::Sent, transcript.sent_authed())
|
||||
.expect("index is in bounds");
|
||||
let recv_refs = refs
|
||||
.get(Direction::Received, transcript.received_authed())
|
||||
.expect("index is in bounds");
|
||||
|
||||
let mut authenticated_data = Vec::new();
|
||||
for data in sent_refs.into_iter().chain(recv_refs) {
|
||||
let plaintext = vm
|
||||
.get(data)
|
||||
.expect("reference is valid")
|
||||
.expect("plaintext is decoded");
|
||||
authenticated_data.extend_from_slice(&plaintext);
|
||||
}
|
||||
|
||||
let mut purported_data = Vec::with_capacity(authenticated_data.len());
|
||||
for range in transcript.sent_authed().iter_ranges() {
|
||||
purported_data.extend_from_slice(&transcript.sent_unsafe()[range]);
|
||||
}
|
||||
|
||||
for range in transcript.received_authed().iter_ranges() {
|
||||
purported_data.extend_from_slice(&transcript.received_unsafe()[range]);
|
||||
}
|
||||
|
||||
if purported_data != authenticated_data {
|
||||
return Err(InconsistentTranscript {});
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Error for [`verify_transcript`].
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[error("inconsistent transcript")]
|
||||
pub struct InconsistentTranscript {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::TranscriptRefs;
|
||||
use mpz_memory_core::{binary::U8, FromRaw, Slice, Vector};
|
||||
use rangeset::RangeSet;
|
||||
use std::ops::Range;
|
||||
use tlsn_core::transcript::{Direction, Idx};
|
||||
|
||||
// TRANSCRIPT_REFS:
|
||||
//
|
||||
// 48..96 -> 6 slots
|
||||
// 112..176 -> 8 slots
|
||||
// 240..288 -> 6 slots
|
||||
// 352..392 -> 5 slots
|
||||
// 440..480 -> 5 slots
|
||||
const TRANSCRIPT_REFS: &[Range<usize>] = &[48..96, 112..176, 240..288, 352..392, 440..480];
|
||||
|
||||
const IDXS: &[Range<usize>] = &[0..4, 5..10, 14..16, 16..28];
|
||||
|
||||
// 1. Take slots 0..4, 4 slots -> 48..80 (4)
|
||||
// 2. Take slots 5..10, 5 slots -> 88..96 (1) + 112..144 (4)
|
||||
// 3. Take slots 14..16, 2 slots -> 240..256 (2)
|
||||
// 4. Take slots 16..28, 12 slots -> 256..288 (4) + 352..392 (5) + 440..464 (3)
|
||||
//
|
||||
// 5. Merge slots 240..256 and 256..288 => 240..288 and get EXPECTED_REFS
|
||||
const EXPECTED_REFS: &[Range<usize>] =
|
||||
&[48..80, 88..96, 112..144, 240..288, 352..392, 440..464];
|
||||
|
||||
#[test]
|
||||
fn test_transcript_refs_get() {
|
||||
let transcript_refs: Vec<Vector<U8>> = TRANSCRIPT_REFS
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|range| Vector::from_raw(Slice::from_range_unchecked(range)))
|
||||
.collect();
|
||||
|
||||
let transcript_refs = TranscriptRefs {
|
||||
sent: transcript_refs.clone(),
|
||||
recv: transcript_refs,
|
||||
};
|
||||
|
||||
let vm_refs = transcript_refs
|
||||
.get(Direction::Sent, &idx_fixture())
|
||||
.unwrap();
|
||||
|
||||
let expected_refs: Vec<Vector<U8>> = EXPECTED_REFS
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|range| Vector::from_raw(Slice::from_range_unchecked(range)))
|
||||
.collect();
|
||||
|
||||
assert_eq!(
|
||||
vm_refs.len(),
|
||||
expected_refs.len(),
|
||||
"Length of actual and expected refs are not equal"
|
||||
);
|
||||
|
||||
for (&expected, actual) in expected_refs.iter().zip(vm_refs) {
|
||||
assert_eq!(expected, actual);
|
||||
}
|
||||
}
|
||||
|
||||
fn idx_fixture() -> Idx {
|
||||
let set = RangeSet::from(IDXS);
|
||||
Idx::builder().union(&set).build()
|
||||
}
|
||||
}
|
||||
210
crates/common/src/zk_aes_ctr.rs
Normal file
210
crates/common/src/zk_aes_ctr.rs
Normal file
@@ -0,0 +1,210 @@
|
||||
//! Zero-knowledge AES-CTR encryption.
|
||||
|
||||
use cipher::{
|
||||
aes::{Aes128, AesError},
|
||||
Cipher, CipherError, Keystream,
|
||||
};
|
||||
use mpz_memory_core::{
|
||||
binary::{Binary, U8},
|
||||
Array, Vector,
|
||||
};
|
||||
use mpz_vm_core::{prelude::*, Vm};
|
||||
|
||||
use crate::Role;
|
||||
|
||||
type Nonce = Array<U8, 8>;
|
||||
type Ctr = Array<U8, 4>;
|
||||
type Block = Array<U8, 16>;
|
||||
|
||||
const START_CTR: u32 = 2;
|
||||
|
||||
/// ZK AES-CTR encryption.
|
||||
#[derive(Debug)]
|
||||
pub struct ZkAesCtr {
|
||||
role: Role,
|
||||
aes: Aes128,
|
||||
state: State,
|
||||
}
|
||||
|
||||
impl ZkAesCtr {
|
||||
/// Creates a new ZK AES-CTR instance.
|
||||
pub fn new(role: Role) -> Self {
|
||||
Self {
|
||||
role,
|
||||
aes: Aes128::default(),
|
||||
state: State::Init,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the role.
|
||||
pub fn role(&self) -> &Role {
|
||||
&self.role
|
||||
}
|
||||
|
||||
/// Allocates `len` bytes for encryption.
|
||||
pub fn alloc(&mut self, vm: &mut dyn Vm<Binary>, len: usize) -> Result<(), ZkAesCtrError> {
|
||||
let State::Init = self.state.take() else {
|
||||
Err(ErrorRepr::State {
|
||||
reason: "must be in init state to allocate",
|
||||
})?
|
||||
};
|
||||
|
||||
// Round up to the nearest block size.
|
||||
let len = 16 * len.div_ceil(16);
|
||||
|
||||
let input = vm.alloc_vec::<U8>(len).map_err(ZkAesCtrError::vm)?;
|
||||
let keystream = self.aes.alloc_keystream(vm, len)?;
|
||||
|
||||
match self.role {
|
||||
Role::Prover => vm.mark_private(input).map_err(ZkAesCtrError::vm)?,
|
||||
Role::Verifier => vm.mark_blind(input).map_err(ZkAesCtrError::vm)?,
|
||||
}
|
||||
|
||||
self.state = State::Ready { input, keystream };
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Sets the key and IV for the cipher.
|
||||
pub fn set_key(&mut self, key: Array<U8, 16>, iv: Array<U8, 4>) {
|
||||
self.aes.set_key(key);
|
||||
self.aes.set_iv(iv);
|
||||
}
|
||||
|
||||
/// Proves the encryption of `len` bytes.
|
||||
///
|
||||
/// Here we only assign certain values in the VM but the actual proving
|
||||
/// happens later when the plaintext is assigned and the VM is executed.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `vm` - Virtual machine.
|
||||
/// * `explicit_nonce` - Explicit nonce.
|
||||
/// * `len` - Length of the plaintext in bytes.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// A VM reference to the plaintext and the ciphertext.
|
||||
pub fn encrypt(
|
||||
&mut self,
|
||||
vm: &mut dyn Vm<Binary>,
|
||||
explicit_nonce: Vec<u8>,
|
||||
len: usize,
|
||||
) -> Result<(Vector<U8>, Vector<U8>), ZkAesCtrError> {
|
||||
let State::Ready { input, keystream } = &mut self.state else {
|
||||
Err(ErrorRepr::State {
|
||||
reason: "must be in ready state to encrypt",
|
||||
})?
|
||||
};
|
||||
|
||||
let explicit_nonce: [u8; 8] =
|
||||
explicit_nonce
|
||||
.try_into()
|
||||
.map_err(|explicit_nonce: Vec<_>| ErrorRepr::ExplicitNonceLength {
|
||||
expected: 8,
|
||||
actual: explicit_nonce.len(),
|
||||
})?;
|
||||
|
||||
let block_count = len.div_ceil(16);
|
||||
let padded_len = block_count * 16;
|
||||
let padding_len = padded_len - len;
|
||||
|
||||
if padded_len > input.len() {
|
||||
Err(ErrorRepr::InsufficientPreprocessing {
|
||||
expected: padded_len,
|
||||
actual: input.len(),
|
||||
})?
|
||||
}
|
||||
|
||||
let mut input = input.split_off(input.len() - padded_len);
|
||||
let keystream = keystream.consume(padded_len)?;
|
||||
let mut output = keystream.apply(vm, input)?;
|
||||
|
||||
// Assign counter block inputs.
|
||||
let mut ctr = START_CTR..;
|
||||
keystream.assign(vm, explicit_nonce, move || {
|
||||
ctr.next().expect("range is unbounded").to_be_bytes()
|
||||
})?;
|
||||
|
||||
// Assign zeroes to the padding.
|
||||
if padding_len > 0 {
|
||||
let padding = input.split_off(input.len() - padding_len);
|
||||
// To simplify the impl, we don't mark the padding as public, that's why only
|
||||
// the prover assigns it.
|
||||
if let Role::Prover = self.role {
|
||||
vm.assign(padding, vec![0; padding_len])
|
||||
.map_err(ZkAesCtrError::vm)?;
|
||||
}
|
||||
vm.commit(padding).map_err(ZkAesCtrError::vm)?;
|
||||
output.truncate(len);
|
||||
}
|
||||
|
||||
Ok((input, output))
|
||||
}
|
||||
}
|
||||
|
||||
enum State {
|
||||
Init,
|
||||
Ready {
|
||||
input: Vector<U8>,
|
||||
keystream: Keystream<Nonce, Ctr, Block>,
|
||||
},
|
||||
Error,
|
||||
}
|
||||
|
||||
impl State {
|
||||
fn take(&mut self) -> Self {
|
||||
std::mem::replace(self, State::Error)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for State {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
State::Init => write!(f, "Init"),
|
||||
State::Ready { .. } => write!(f, "Ready"),
|
||||
State::Error => write!(f, "Error"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Error for [`ZkAesCtr`].
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[error(transparent)]
|
||||
pub struct ZkAesCtrError(#[from] ErrorRepr);
|
||||
|
||||
impl ZkAesCtrError {
|
||||
fn vm<E>(err: E) -> Self
|
||||
where
|
||||
E: Into<Box<dyn std::error::Error + Send + Sync + 'static>>,
|
||||
{
|
||||
Self(ErrorRepr::Vm(err.into()))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[error("zk aes error")]
|
||||
enum ErrorRepr {
|
||||
#[error("invalid state: {reason}")]
|
||||
State { reason: &'static str },
|
||||
#[error("cipher error: {0}")]
|
||||
Cipher(Box<dyn std::error::Error + Send + Sync + 'static>),
|
||||
#[error("vm error: {0}")]
|
||||
Vm(Box<dyn std::error::Error + Send + Sync + 'static>),
|
||||
#[error("invalid explicit nonce length: expected {expected}, got {actual}")]
|
||||
ExplicitNonceLength { expected: usize, actual: usize },
|
||||
#[error("insufficient preprocessing: expected {expected}, got {actual}")]
|
||||
InsufficientPreprocessing { expected: usize, actual: usize },
|
||||
}
|
||||
|
||||
impl From<AesError> for ZkAesCtrError {
|
||||
fn from(err: AesError) -> Self {
|
||||
Self(ErrorRepr::Cipher(Box::new(err)))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CipherError> for ZkAesCtrError {
|
||||
fn from(err: CipherError) -> Self {
|
||||
Self(ErrorRepr::Cipher(Box::new(err)))
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
[package]
|
||||
name = "tlsn-aead"
|
||||
authors = ["TLSNotary Team"]
|
||||
description = "This crate provides an implementation of a two-party version of AES-GCM behind an AEAD trait"
|
||||
keywords = ["tls", "mpc", "2pc", "aead", "aes", "aes-gcm"]
|
||||
categories = ["cryptography"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
version = "0.1.0-alpha.8-pre"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
name = "aead"
|
||||
|
||||
[features]
|
||||
default = ["mock"]
|
||||
mock = ["mpz-common/test-utils", "dep:mpz-ot"]
|
||||
|
||||
[dependencies]
|
||||
tlsn-block-cipher = { workspace = true }
|
||||
tlsn-stream-cipher = { workspace = true }
|
||||
tlsn-universal-hash = { workspace = true }
|
||||
|
||||
mpz-common = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "b8ae7ac" }
|
||||
mpz-core = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "b8ae7ac" }
|
||||
mpz-garble = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "b8ae7ac" }
|
||||
mpz-ot = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "b8ae7ac", optional = true, features = [
|
||||
"ideal",
|
||||
] }
|
||||
|
||||
serio = { workspace = true }
|
||||
|
||||
async-trait = { workspace = true }
|
||||
derive_builder = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "1", features = ["macros", "rt", "rt-multi-thread"] }
|
||||
aes-gcm = { workspace = true }
|
||||
@@ -1,36 +0,0 @@
|
||||
use derive_builder::Builder;
|
||||
|
||||
/// Protocol role.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[allow(missing_docs)]
|
||||
pub enum Role {
|
||||
Leader,
|
||||
Follower,
|
||||
}
|
||||
|
||||
/// Configuration for AES-GCM.
|
||||
#[derive(Debug, Clone, Builder)]
|
||||
pub struct AesGcmConfig {
|
||||
/// The id of this instance.
|
||||
#[builder(setter(into))]
|
||||
id: String,
|
||||
/// The protocol role.
|
||||
role: Role,
|
||||
}
|
||||
|
||||
impl AesGcmConfig {
|
||||
/// Creates a new builder for the AES-GCM configuration.
|
||||
pub fn builder() -> AesGcmConfigBuilder {
|
||||
AesGcmConfigBuilder::default()
|
||||
}
|
||||
|
||||
/// Returns the id of this instance.
|
||||
pub fn id(&self) -> &str {
|
||||
&self.id
|
||||
}
|
||||
|
||||
/// Returns the protocol role.
|
||||
pub fn role(&self) -> &Role {
|
||||
&self.role
|
||||
}
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
/// AES-GCM error.
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub struct AesGcmError {
|
||||
kind: ErrorKind,
|
||||
#[source]
|
||||
source: Option<Box<dyn std::error::Error + Send + Sync>>,
|
||||
}
|
||||
|
||||
impl AesGcmError {
|
||||
pub(crate) fn new<E>(kind: ErrorKind, source: E) -> Self
|
||||
where
|
||||
E: Into<Box<dyn std::error::Error + Send + Sync>>,
|
||||
{
|
||||
Self {
|
||||
kind,
|
||||
source: Some(source.into()),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn kind(&self) -> ErrorKind {
|
||||
self.kind
|
||||
}
|
||||
|
||||
pub(crate) fn invalid_tag() -> Self {
|
||||
Self {
|
||||
kind: ErrorKind::Tag,
|
||||
source: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn peer(reason: impl Into<String>) -> Self {
|
||||
Self {
|
||||
kind: ErrorKind::PeerMisbehaved,
|
||||
source: Some(reason.into().into()),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn payload(reason: impl Into<String>) -> Self {
|
||||
Self {
|
||||
kind: ErrorKind::Payload,
|
||||
source: Some(reason.into().into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub(crate) enum ErrorKind {
|
||||
Io,
|
||||
BlockCipher,
|
||||
StreamCipher,
|
||||
Ghash,
|
||||
Tag,
|
||||
PeerMisbehaved,
|
||||
Payload,
|
||||
}
|
||||
|
||||
impl Display for AesGcmError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self.kind {
|
||||
ErrorKind::Io => write!(f, "io error")?,
|
||||
ErrorKind::BlockCipher => write!(f, "block cipher error")?,
|
||||
ErrorKind::StreamCipher => write!(f, "stream cipher error")?,
|
||||
ErrorKind::Ghash => write!(f, "ghash error")?,
|
||||
ErrorKind::Tag => write!(f, "payload has corrupted tag")?,
|
||||
ErrorKind::PeerMisbehaved => write!(f, "peer misbehaved")?,
|
||||
ErrorKind::Payload => write!(f, "payload error")?,
|
||||
}
|
||||
|
||||
if let Some(source) = &self.source {
|
||||
write!(f, " caused by: {}", source)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::io::Error> for AesGcmError {
|
||||
fn from(err: std::io::Error) -> Self {
|
||||
Self::new(ErrorKind::Io, err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<block_cipher::BlockCipherError> for AesGcmError {
|
||||
fn from(err: block_cipher::BlockCipherError) -> Self {
|
||||
Self::new(ErrorKind::BlockCipher, err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<tlsn_stream_cipher::StreamCipherError> for AesGcmError {
|
||||
fn from(err: tlsn_stream_cipher::StreamCipherError) -> Self {
|
||||
Self::new(ErrorKind::StreamCipher, err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<tlsn_universal_hash::UniversalHashError> for AesGcmError {
|
||||
fn from(err: tlsn_universal_hash::UniversalHashError) -> Self {
|
||||
Self::new(ErrorKind::Ghash, err)
|
||||
}
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
//! Mock implementation of AES-GCM for testing purposes.
|
||||
|
||||
use block_cipher::{BlockCipherConfig, MpcBlockCipher};
|
||||
use mpz_common::executor::{test_st_executor, STExecutor};
|
||||
use mpz_garble::protocol::deap::mock::{MockFollower, MockLeader};
|
||||
use mpz_ot::ideal::ot::ideal_ot;
|
||||
use serio::channel::MemoryDuplex;
|
||||
use tlsn_stream_cipher::{MpcStreamCipher, StreamCipherConfig};
|
||||
use tlsn_universal_hash::ghash::ideal_ghash;
|
||||
|
||||
use super::*;
|
||||
|
||||
/// Creates a mock AES-GCM pair.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `id` - The id of the AES-GCM instances.
|
||||
/// * `(leader, follower)` - The leader and follower vms.
|
||||
/// * `leader_config` - The configuration of the leader.
|
||||
/// * `follower_config` - The configuration of the follower.
|
||||
pub async fn create_mock_aes_gcm_pair(
|
||||
id: &str,
|
||||
(leader, follower): (MockLeader, MockFollower),
|
||||
leader_config: AesGcmConfig,
|
||||
follower_config: AesGcmConfig,
|
||||
) -> (
|
||||
MpcAesGcm<STExecutor<MemoryDuplex>>,
|
||||
MpcAesGcm<STExecutor<MemoryDuplex>>,
|
||||
) {
|
||||
let block_cipher_id = format!("{}/block_cipher", id);
|
||||
let (ctx_leader, ctx_follower) = test_st_executor(128);
|
||||
|
||||
let (leader_ot_send, follower_ot_recv) = ideal_ot();
|
||||
let (follower_ot_send, leader_ot_recv) = ideal_ot();
|
||||
|
||||
let block_leader = leader
|
||||
.new_thread(ctx_leader, leader_ot_send, leader_ot_recv)
|
||||
.unwrap();
|
||||
|
||||
let block_follower = follower
|
||||
.new_thread(ctx_follower, follower_ot_send, follower_ot_recv)
|
||||
.unwrap();
|
||||
|
||||
let leader_block_cipher = MpcBlockCipher::new(
|
||||
BlockCipherConfig::builder()
|
||||
.id(block_cipher_id.clone())
|
||||
.build()
|
||||
.unwrap(),
|
||||
block_leader,
|
||||
);
|
||||
let follower_block_cipher = MpcBlockCipher::new(
|
||||
BlockCipherConfig::builder()
|
||||
.id(block_cipher_id.clone())
|
||||
.build()
|
||||
.unwrap(),
|
||||
block_follower,
|
||||
);
|
||||
|
||||
let stream_cipher_id = format!("{}/stream_cipher", id);
|
||||
let leader_stream_cipher = MpcStreamCipher::new(
|
||||
StreamCipherConfig::builder()
|
||||
.id(stream_cipher_id.clone())
|
||||
.build()
|
||||
.unwrap(),
|
||||
leader,
|
||||
);
|
||||
let follower_stream_cipher = MpcStreamCipher::new(
|
||||
StreamCipherConfig::builder()
|
||||
.id(stream_cipher_id.clone())
|
||||
.build()
|
||||
.unwrap(),
|
||||
follower,
|
||||
);
|
||||
|
||||
let (ctx_a, ctx_b) = test_st_executor(128);
|
||||
let (leader_ghash, follower_ghash) = ideal_ghash(ctx_a, ctx_b);
|
||||
|
||||
let (ctx_a, ctx_b) = test_st_executor(128);
|
||||
let leader = MpcAesGcm::new(
|
||||
leader_config,
|
||||
ctx_a,
|
||||
Box::new(leader_block_cipher),
|
||||
Box::new(leader_stream_cipher),
|
||||
Box::new(leader_ghash),
|
||||
);
|
||||
|
||||
let follower = MpcAesGcm::new(
|
||||
follower_config,
|
||||
ctx_b,
|
||||
Box::new(follower_block_cipher),
|
||||
Box::new(follower_stream_cipher),
|
||||
Box::new(follower_ghash),
|
||||
);
|
||||
|
||||
(leader, follower)
|
||||
}
|
||||
@@ -1,712 +0,0 @@
|
||||
//! This module provides an implementation of 2PC AES-GCM.
|
||||
|
||||
mod config;
|
||||
mod error;
|
||||
#[cfg(feature = "mock")]
|
||||
pub mod mock;
|
||||
mod tag;
|
||||
|
||||
pub use config::{AesGcmConfig, AesGcmConfigBuilder, AesGcmConfigBuilderError, Role};
|
||||
pub use error::AesGcmError;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use block_cipher::{Aes128, BlockCipher};
|
||||
use futures::TryFutureExt;
|
||||
use mpz_common::Context;
|
||||
use mpz_garble::value::ValueRef;
|
||||
use tlsn_stream_cipher::{Aes128Ctr, StreamCipher};
|
||||
use tlsn_universal_hash::UniversalHash;
|
||||
use tracing::instrument;
|
||||
|
||||
use crate::{
|
||||
aes_gcm::tag::{compute_tag, verify_tag, TAG_LEN},
|
||||
Aead,
|
||||
};
|
||||
|
||||
/// MPC AES-GCM.
|
||||
pub struct MpcAesGcm<Ctx> {
|
||||
config: AesGcmConfig,
|
||||
ctx: Ctx,
|
||||
aes_block: Box<dyn BlockCipher<Aes128>>,
|
||||
aes_ctr: Box<dyn StreamCipher<Aes128Ctr>>,
|
||||
ghash: Box<dyn UniversalHash>,
|
||||
}
|
||||
|
||||
impl<Ctx> std::fmt::Debug for MpcAesGcm<Ctx> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("MpcAesGcm")
|
||||
.field("config", &self.config)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<Ctx: Context> MpcAesGcm<Ctx> {
|
||||
/// Creates a new instance of [`MpcAesGcm`].
|
||||
pub fn new(
|
||||
config: AesGcmConfig,
|
||||
context: Ctx,
|
||||
aes_block: Box<dyn BlockCipher<Aes128>>,
|
||||
aes_ctr: Box<dyn StreamCipher<Aes128Ctr>>,
|
||||
ghash: Box<dyn UniversalHash>,
|
||||
) -> Self {
|
||||
Self {
|
||||
config,
|
||||
ctx: context,
|
||||
aes_block,
|
||||
aes_ctr,
|
||||
ghash,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<Ctx: Context> Aead for MpcAesGcm<Ctx> {
|
||||
type Error = AesGcmError;
|
||||
|
||||
#[instrument(level = "info", skip_all, err)]
|
||||
async fn set_key(&mut self, key: ValueRef, iv: ValueRef) -> Result<(), AesGcmError> {
|
||||
self.aes_block.set_key(key.clone());
|
||||
self.aes_ctr.set_key(key, iv);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(level = "info", skip_all, err)]
|
||||
async fn decode_key_private(&mut self) -> Result<(), AesGcmError> {
|
||||
self.aes_ctr
|
||||
.decode_key_private()
|
||||
.await
|
||||
.map_err(AesGcmError::from)
|
||||
}
|
||||
|
||||
#[instrument(level = "info", skip_all, err)]
|
||||
async fn decode_key_blind(&mut self) -> Result<(), AesGcmError> {
|
||||
self.aes_ctr
|
||||
.decode_key_blind()
|
||||
.await
|
||||
.map_err(AesGcmError::from)
|
||||
}
|
||||
|
||||
fn set_transcript_id(&mut self, id: &str) {
|
||||
self.aes_ctr.set_transcript_id(id)
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(self), err)]
|
||||
async fn setup(&mut self) -> Result<(), AesGcmError> {
|
||||
self.ghash.setup().await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(self), err)]
|
||||
async fn preprocess(&mut self, len: usize) -> Result<(), AesGcmError> {
|
||||
futures::try_join!(
|
||||
// Preprocess the GHASH key block.
|
||||
self.aes_block
|
||||
.preprocess(block_cipher::Visibility::Public, 1)
|
||||
.map_err(AesGcmError::from),
|
||||
self.aes_ctr.preprocess(len).map_err(AesGcmError::from),
|
||||
self.ghash.preprocess().map_err(AesGcmError::from),
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip_all, err)]
|
||||
async fn start(&mut self) -> Result<(), AesGcmError> {
|
||||
let h_share = self.aes_block.encrypt_share(vec![0u8; 16]).await?;
|
||||
self.ghash.set_key(h_share).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip_all, err)]
|
||||
async fn encrypt_public(
|
||||
&mut self,
|
||||
explicit_nonce: Vec<u8>,
|
||||
plaintext: Vec<u8>,
|
||||
aad: Vec<u8>,
|
||||
) -> Result<Vec<u8>, AesGcmError> {
|
||||
let ciphertext = self
|
||||
.aes_ctr
|
||||
.encrypt_public(explicit_nonce.clone(), plaintext)
|
||||
.await?;
|
||||
|
||||
let tag = compute_tag(
|
||||
&mut self.ctx,
|
||||
self.aes_ctr.as_mut(),
|
||||
self.ghash.as_mut(),
|
||||
explicit_nonce,
|
||||
ciphertext.clone(),
|
||||
aad,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let mut payload = ciphertext;
|
||||
payload.extend(tag);
|
||||
|
||||
Ok(payload)
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip_all, err)]
|
||||
async fn encrypt_private(
|
||||
&mut self,
|
||||
explicit_nonce: Vec<u8>,
|
||||
plaintext: Vec<u8>,
|
||||
aad: Vec<u8>,
|
||||
) -> Result<Vec<u8>, AesGcmError> {
|
||||
let ciphertext = self
|
||||
.aes_ctr
|
||||
.encrypt_private(explicit_nonce.clone(), plaintext)
|
||||
.await?;
|
||||
|
||||
let tag = compute_tag(
|
||||
&mut self.ctx,
|
||||
self.aes_ctr.as_mut(),
|
||||
self.ghash.as_mut(),
|
||||
explicit_nonce,
|
||||
ciphertext.clone(),
|
||||
aad,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let mut payload = ciphertext;
|
||||
payload.extend(tag);
|
||||
|
||||
Ok(payload)
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip_all, err)]
|
||||
async fn encrypt_blind(
|
||||
&mut self,
|
||||
explicit_nonce: Vec<u8>,
|
||||
plaintext_len: usize,
|
||||
aad: Vec<u8>,
|
||||
) -> Result<Vec<u8>, AesGcmError> {
|
||||
let ciphertext = self
|
||||
.aes_ctr
|
||||
.encrypt_blind(explicit_nonce.clone(), plaintext_len)
|
||||
.await?;
|
||||
|
||||
let tag = compute_tag(
|
||||
&mut self.ctx,
|
||||
self.aes_ctr.as_mut(),
|
||||
self.ghash.as_mut(),
|
||||
explicit_nonce,
|
||||
ciphertext.clone(),
|
||||
aad,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let mut payload = ciphertext;
|
||||
payload.extend(tag);
|
||||
|
||||
Ok(payload)
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip_all, err)]
|
||||
async fn decrypt_public(
|
||||
&mut self,
|
||||
explicit_nonce: Vec<u8>,
|
||||
mut payload: Vec<u8>,
|
||||
aad: Vec<u8>,
|
||||
) -> Result<Vec<u8>, AesGcmError> {
|
||||
let purported_tag: [u8; TAG_LEN] = payload
|
||||
.split_off(payload.len() - TAG_LEN)
|
||||
.try_into()
|
||||
.map_err(|_| AesGcmError::payload("payload is not long enough to contain tag"))?;
|
||||
let ciphertext = payload;
|
||||
|
||||
verify_tag(
|
||||
&mut self.ctx,
|
||||
self.aes_ctr.as_mut(),
|
||||
self.ghash.as_mut(),
|
||||
*self.config.role(),
|
||||
explicit_nonce.clone(),
|
||||
ciphertext.clone(),
|
||||
aad,
|
||||
purported_tag,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let plaintext = self
|
||||
.aes_ctr
|
||||
.decrypt_public(explicit_nonce, ciphertext)
|
||||
.await?;
|
||||
|
||||
Ok(plaintext)
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip_all, err)]
|
||||
async fn decrypt_private(
|
||||
&mut self,
|
||||
explicit_nonce: Vec<u8>,
|
||||
mut payload: Vec<u8>,
|
||||
aad: Vec<u8>,
|
||||
) -> Result<Vec<u8>, AesGcmError> {
|
||||
let purported_tag: [u8; TAG_LEN] = payload
|
||||
.split_off(payload.len() - TAG_LEN)
|
||||
.try_into()
|
||||
.map_err(|_| AesGcmError::payload("payload is not long enough to contain tag"))?;
|
||||
let ciphertext = payload;
|
||||
|
||||
verify_tag(
|
||||
&mut self.ctx,
|
||||
self.aes_ctr.as_mut(),
|
||||
self.ghash.as_mut(),
|
||||
*self.config.role(),
|
||||
explicit_nonce.clone(),
|
||||
ciphertext.clone(),
|
||||
aad,
|
||||
purported_tag,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let plaintext = self
|
||||
.aes_ctr
|
||||
.decrypt_private(explicit_nonce, ciphertext)
|
||||
.await?;
|
||||
|
||||
Ok(plaintext)
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip_all, err)]
|
||||
async fn decrypt_blind(
|
||||
&mut self,
|
||||
explicit_nonce: Vec<u8>,
|
||||
mut payload: Vec<u8>,
|
||||
aad: Vec<u8>,
|
||||
) -> Result<(), AesGcmError> {
|
||||
let purported_tag: [u8; TAG_LEN] = payload
|
||||
.split_off(payload.len() - TAG_LEN)
|
||||
.try_into()
|
||||
.map_err(|_| AesGcmError::payload("payload is not long enough to contain tag"))?;
|
||||
let ciphertext = payload;
|
||||
|
||||
verify_tag(
|
||||
&mut self.ctx,
|
||||
self.aes_ctr.as_mut(),
|
||||
self.ghash.as_mut(),
|
||||
*self.config.role(),
|
||||
explicit_nonce.clone(),
|
||||
ciphertext.clone(),
|
||||
aad,
|
||||
purported_tag,
|
||||
)
|
||||
.await?;
|
||||
|
||||
self.aes_ctr
|
||||
.decrypt_blind(explicit_nonce, ciphertext)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip_all, err)]
|
||||
async fn verify_tag(
|
||||
&mut self,
|
||||
explicit_nonce: Vec<u8>,
|
||||
mut payload: Vec<u8>,
|
||||
aad: Vec<u8>,
|
||||
) -> Result<(), AesGcmError> {
|
||||
let purported_tag: [u8; TAG_LEN] = payload
|
||||
.split_off(payload.len() - TAG_LEN)
|
||||
.try_into()
|
||||
.map_err(|_| AesGcmError::payload("payload is not long enough to contain tag"))?;
|
||||
let ciphertext = payload;
|
||||
|
||||
verify_tag(
|
||||
&mut self.ctx,
|
||||
self.aes_ctr.as_mut(),
|
||||
self.ghash.as_mut(),
|
||||
*self.config.role(),
|
||||
explicit_nonce,
|
||||
ciphertext,
|
||||
aad,
|
||||
purported_tag,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip_all, err)]
|
||||
async fn prove_plaintext(
|
||||
&mut self,
|
||||
explicit_nonce: Vec<u8>,
|
||||
mut payload: Vec<u8>,
|
||||
aad: Vec<u8>,
|
||||
) -> Result<Vec<u8>, AesGcmError> {
|
||||
let purported_tag: [u8; TAG_LEN] = payload
|
||||
.split_off(payload.len() - TAG_LEN)
|
||||
.try_into()
|
||||
.map_err(|_| AesGcmError::payload("payload is not long enough to contain tag"))?;
|
||||
let ciphertext = payload;
|
||||
|
||||
verify_tag(
|
||||
&mut self.ctx,
|
||||
self.aes_ctr.as_mut(),
|
||||
self.ghash.as_mut(),
|
||||
*self.config.role(),
|
||||
explicit_nonce.clone(),
|
||||
ciphertext.clone(),
|
||||
aad,
|
||||
purported_tag,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let plaintext = self
|
||||
.aes_ctr
|
||||
.prove_plaintext(explicit_nonce, ciphertext)
|
||||
.await?;
|
||||
|
||||
Ok(plaintext)
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip_all, err)]
|
||||
async fn prove_plaintext_no_tag(
|
||||
&mut self,
|
||||
explicit_nonce: Vec<u8>,
|
||||
ciphertext: Vec<u8>,
|
||||
) -> Result<Vec<u8>, AesGcmError> {
|
||||
self.aes_ctr
|
||||
.prove_plaintext(explicit_nonce, ciphertext)
|
||||
.map_err(AesGcmError::from)
|
||||
.await
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip_all, err)]
|
||||
async fn verify_plaintext(
|
||||
&mut self,
|
||||
explicit_nonce: Vec<u8>,
|
||||
mut payload: Vec<u8>,
|
||||
aad: Vec<u8>,
|
||||
) -> Result<(), AesGcmError> {
|
||||
let purported_tag: [u8; TAG_LEN] = payload
|
||||
.split_off(payload.len() - TAG_LEN)
|
||||
.try_into()
|
||||
.map_err(|_| AesGcmError::payload("payload is not long enough to contain tag"))?;
|
||||
let ciphertext = payload;
|
||||
|
||||
verify_tag(
|
||||
&mut self.ctx,
|
||||
self.aes_ctr.as_mut(),
|
||||
self.ghash.as_mut(),
|
||||
*self.config.role(),
|
||||
explicit_nonce.clone(),
|
||||
ciphertext.clone(),
|
||||
aad,
|
||||
purported_tag,
|
||||
)
|
||||
.await?;
|
||||
|
||||
self.aes_ctr
|
||||
.verify_plaintext(explicit_nonce, ciphertext)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip_all, err)]
|
||||
async fn verify_plaintext_no_tag(
|
||||
&mut self,
|
||||
explicit_nonce: Vec<u8>,
|
||||
ciphertext: Vec<u8>,
|
||||
) -> Result<(), AesGcmError> {
|
||||
self.aes_ctr
|
||||
.verify_plaintext(explicit_nonce, ciphertext)
|
||||
.map_err(AesGcmError::from)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{
|
||||
aes_gcm::{mock::create_mock_aes_gcm_pair, AesGcmConfigBuilder, Role},
|
||||
Aead,
|
||||
};
|
||||
use ::aes_gcm::{aead::AeadInPlace, Aes128Gcm, NewAead, Nonce};
|
||||
use error::ErrorKind;
|
||||
use mpz_common::executor::STExecutor;
|
||||
use mpz_garble::{protocol::deap::mock::create_mock_deap_vm, Memory};
|
||||
use serio::channel::MemoryDuplex;
|
||||
|
||||
fn reference_impl(
|
||||
key: &[u8],
|
||||
iv: &[u8],
|
||||
explicit_nonce: &[u8],
|
||||
plaintext: &[u8],
|
||||
aad: &[u8],
|
||||
) -> Vec<u8> {
|
||||
let cipher = Aes128Gcm::new_from_slice(key).unwrap();
|
||||
let nonce = [iv, explicit_nonce].concat();
|
||||
let nonce = Nonce::from_slice(nonce.as_slice());
|
||||
|
||||
let mut ciphertext = plaintext.to_vec();
|
||||
cipher
|
||||
.encrypt_in_place(nonce, aad, &mut ciphertext)
|
||||
.unwrap();
|
||||
|
||||
ciphertext
|
||||
}
|
||||
|
||||
async fn setup_pair(
|
||||
key: Vec<u8>,
|
||||
iv: Vec<u8>,
|
||||
) -> (
|
||||
MpcAesGcm<STExecutor<MemoryDuplex>>,
|
||||
MpcAesGcm<STExecutor<MemoryDuplex>>,
|
||||
) {
|
||||
let (leader_vm, follower_vm) = create_mock_deap_vm();
|
||||
|
||||
let leader_key = leader_vm
|
||||
.new_public_array_input::<u8>("key", key.len())
|
||||
.unwrap();
|
||||
let leader_iv = leader_vm
|
||||
.new_public_array_input::<u8>("iv", iv.len())
|
||||
.unwrap();
|
||||
|
||||
leader_vm.assign(&leader_key, key.clone()).unwrap();
|
||||
leader_vm.assign(&leader_iv, iv.clone()).unwrap();
|
||||
|
||||
let follower_key = follower_vm
|
||||
.new_public_array_input::<u8>("key", key.len())
|
||||
.unwrap();
|
||||
let follower_iv = follower_vm
|
||||
.new_public_array_input::<u8>("iv", iv.len())
|
||||
.unwrap();
|
||||
|
||||
follower_vm.assign(&follower_key, key.clone()).unwrap();
|
||||
follower_vm.assign(&follower_iv, iv.clone()).unwrap();
|
||||
|
||||
let leader_config = AesGcmConfigBuilder::default()
|
||||
.id("test".to_string())
|
||||
.role(Role::Leader)
|
||||
.build()
|
||||
.unwrap();
|
||||
let follower_config = AesGcmConfigBuilder::default()
|
||||
.id("test".to_string())
|
||||
.role(Role::Follower)
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let (mut leader, mut follower) = create_mock_aes_gcm_pair(
|
||||
"test",
|
||||
(leader_vm, follower_vm),
|
||||
leader_config,
|
||||
follower_config,
|
||||
)
|
||||
.await;
|
||||
|
||||
futures::try_join!(
|
||||
leader.set_key(leader_key, leader_iv),
|
||||
follower.set_key(follower_key, follower_iv)
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
futures::try_join!(leader.setup(), follower.setup()).unwrap();
|
||||
futures::try_join!(leader.start(), follower.start()).unwrap();
|
||||
|
||||
(leader, follower)
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore = "expensive"]
|
||||
async fn test_aes_gcm_encrypt_private() {
|
||||
let key = vec![0u8; 16];
|
||||
let iv = vec![0u8; 4];
|
||||
let explicit_nonce = vec![0u8; 8];
|
||||
let plaintext = vec![1u8; 32];
|
||||
let aad = vec![2u8; 12];
|
||||
|
||||
let (mut leader, mut follower) = setup_pair(key.clone(), iv.clone()).await;
|
||||
|
||||
let (leader_ciphertext, follower_ciphertext) = tokio::try_join!(
|
||||
leader.encrypt_private(explicit_nonce.clone(), plaintext.clone(), aad.clone(),),
|
||||
follower.encrypt_blind(explicit_nonce.clone(), plaintext.len(), aad.clone())
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(leader_ciphertext, follower_ciphertext);
|
||||
assert_eq!(
|
||||
leader_ciphertext,
|
||||
reference_impl(&key, &iv, &explicit_nonce, &plaintext, &aad)
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore = "expensive"]
|
||||
async fn test_aes_gcm_encrypt_public() {
|
||||
let key = vec![0u8; 16];
|
||||
let iv = vec![0u8; 4];
|
||||
let explicit_nonce = vec![0u8; 8];
|
||||
let plaintext = vec![1u8; 32];
|
||||
let aad = vec![2u8; 12];
|
||||
|
||||
let (mut leader, mut follower) = setup_pair(key.clone(), iv.clone()).await;
|
||||
|
||||
let (leader_ciphertext, follower_ciphertext) = tokio::try_join!(
|
||||
leader.encrypt_public(explicit_nonce.clone(), plaintext.clone(), aad.clone(),),
|
||||
follower.encrypt_public(explicit_nonce.clone(), plaintext.clone(), aad.clone(),)
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(leader_ciphertext, follower_ciphertext);
|
||||
assert_eq!(
|
||||
leader_ciphertext,
|
||||
reference_impl(&key, &iv, &explicit_nonce, &plaintext, &aad)
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore = "expensive"]
|
||||
async fn test_aes_gcm_decrypt_private() {
|
||||
let key = vec![0u8; 16];
|
||||
let iv = vec![0u8; 4];
|
||||
let explicit_nonce = vec![0u8; 8];
|
||||
let plaintext = vec![1u8; 32];
|
||||
let aad = vec![2u8; 12];
|
||||
let ciphertext = reference_impl(&key, &iv, &explicit_nonce, &plaintext, &aad);
|
||||
|
||||
let (mut leader, mut follower) = setup_pair(key.clone(), iv.clone()).await;
|
||||
|
||||
let (leader_plaintext, _) = tokio::try_join!(
|
||||
leader.decrypt_private(explicit_nonce.clone(), ciphertext.clone(), aad.clone(),),
|
||||
follower.decrypt_blind(explicit_nonce.clone(), ciphertext, aad.clone(),)
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(leader_plaintext, plaintext);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore = "expensive"]
|
||||
async fn test_aes_gcm_decrypt_private_bad_tag() {
|
||||
let key = vec![0u8; 16];
|
||||
let iv = vec![0u8; 4];
|
||||
let explicit_nonce = vec![0u8; 8];
|
||||
let plaintext = vec![1u8; 32];
|
||||
let aad = vec![2u8; 12];
|
||||
let ciphertext = reference_impl(&key, &iv, &explicit_nonce, &plaintext, &aad);
|
||||
|
||||
let len = ciphertext.len();
|
||||
|
||||
// corrupt tag
|
||||
let mut corrupted = ciphertext.clone();
|
||||
corrupted[len - 1] -= 1;
|
||||
|
||||
let (mut leader, mut follower) = setup_pair(key.clone(), iv.clone()).await;
|
||||
|
||||
// leader receives corrupted tag
|
||||
let err = tokio::try_join!(
|
||||
leader.decrypt_private(explicit_nonce.clone(), corrupted.clone(), aad.clone(),),
|
||||
follower.decrypt_blind(explicit_nonce.clone(), ciphertext.clone(), aad.clone(),)
|
||||
)
|
||||
.unwrap_err();
|
||||
assert_eq!(err.kind(), ErrorKind::Tag);
|
||||
|
||||
let (mut leader, mut follower) = setup_pair(key.clone(), iv.clone()).await;
|
||||
|
||||
// follower receives corrupted tag
|
||||
let err = tokio::try_join!(
|
||||
leader.decrypt_private(explicit_nonce.clone(), ciphertext.clone(), aad.clone(),),
|
||||
follower.decrypt_blind(explicit_nonce.clone(), corrupted.clone(), aad.clone(),)
|
||||
)
|
||||
.unwrap_err();
|
||||
assert_eq!(err.kind(), ErrorKind::Tag);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore = "expensive"]
|
||||
async fn test_aes_gcm_decrypt_public() {
|
||||
let key = vec![0u8; 16];
|
||||
let iv = vec![0u8; 4];
|
||||
let explicit_nonce = vec![0u8; 8];
|
||||
let plaintext = vec![1u8; 32];
|
||||
let aad = vec![2u8; 12];
|
||||
let ciphertext = reference_impl(&key, &iv, &explicit_nonce, &plaintext, &aad);
|
||||
|
||||
let (mut leader, mut follower) = setup_pair(key.clone(), iv.clone()).await;
|
||||
|
||||
let (leader_plaintext, follower_plaintext) = tokio::try_join!(
|
||||
leader.decrypt_public(explicit_nonce.clone(), ciphertext.clone(), aad.clone(),),
|
||||
follower.decrypt_public(explicit_nonce.clone(), ciphertext, aad.clone(),)
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(leader_plaintext, plaintext);
|
||||
assert_eq!(leader_plaintext, follower_plaintext);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore = "expensive"]
|
||||
async fn test_aes_gcm_decrypt_public_bad_tag() {
|
||||
let key = vec![0u8; 16];
|
||||
let iv = vec![0u8; 4];
|
||||
let explicit_nonce = vec![0u8; 8];
|
||||
let plaintext = vec![1u8; 32];
|
||||
let aad = vec![2u8; 12];
|
||||
let ciphertext = reference_impl(&key, &iv, &explicit_nonce, &plaintext, &aad);
|
||||
|
||||
let len = ciphertext.len();
|
||||
|
||||
// Corrupt tag.
|
||||
let mut corrupted = ciphertext.clone();
|
||||
corrupted[len - 1] -= 1;
|
||||
|
||||
let (mut leader, mut follower) = setup_pair(key.clone(), iv.clone()).await;
|
||||
|
||||
// Leader receives corrupted tag.
|
||||
let err = tokio::try_join!(
|
||||
leader.decrypt_public(explicit_nonce.clone(), corrupted.clone(), aad.clone(),),
|
||||
follower.decrypt_public(explicit_nonce.clone(), ciphertext.clone(), aad.clone(),)
|
||||
)
|
||||
.unwrap_err();
|
||||
assert_eq!(err.kind(), ErrorKind::Tag);
|
||||
|
||||
let (mut leader, mut follower) = setup_pair(key.clone(), iv.clone()).await;
|
||||
|
||||
// Follower receives corrupted tag.
|
||||
let err = tokio::try_join!(
|
||||
leader.decrypt_public(explicit_nonce.clone(), ciphertext.clone(), aad.clone(),),
|
||||
follower.decrypt_public(explicit_nonce.clone(), corrupted.clone(), aad.clone(),)
|
||||
)
|
||||
.unwrap_err();
|
||||
assert_eq!(err.kind(), ErrorKind::Tag);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore = "expensive"]
|
||||
async fn test_aes_gcm_verify_tag() {
|
||||
let key = vec![0u8; 16];
|
||||
let iv = vec![0u8; 4];
|
||||
let explicit_nonce = vec![0u8; 8];
|
||||
let plaintext = vec![1u8; 32];
|
||||
let aad = vec![2u8; 12];
|
||||
let ciphertext = reference_impl(&key, &iv, &explicit_nonce, &plaintext, &aad);
|
||||
|
||||
let len = ciphertext.len();
|
||||
|
||||
let (mut leader, mut follower) = setup_pair(key.clone(), iv.clone()).await;
|
||||
|
||||
tokio::try_join!(
|
||||
leader.verify_tag(explicit_nonce.clone(), ciphertext.clone(), aad.clone()),
|
||||
follower.verify_tag(explicit_nonce.clone(), ciphertext.clone(), aad.clone())
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
//Corrupt tag.
|
||||
let mut corrupted = ciphertext.clone();
|
||||
corrupted[len - 1] -= 1;
|
||||
|
||||
let (leader_res, follower_res) = tokio::join!(
|
||||
leader.verify_tag(explicit_nonce.clone(), corrupted.clone(), aad.clone()),
|
||||
follower.verify_tag(explicit_nonce.clone(), corrupted, aad.clone())
|
||||
);
|
||||
|
||||
assert_eq!(leader_res.unwrap_err().kind(), ErrorKind::Tag);
|
||||
assert_eq!(follower_res.unwrap_err().kind(), ErrorKind::Tag);
|
||||
}
|
||||
}
|
||||
@@ -1,179 +0,0 @@
|
||||
use futures::TryFutureExt;
|
||||
use mpz_common::Context;
|
||||
use mpz_core::{
|
||||
commit::{Decommitment, HashCommit},
|
||||
hash::Hash,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serio::{stream::IoStreamExt, SinkExt};
|
||||
use std::ops::Add;
|
||||
use tlsn_stream_cipher::{Aes128Ctr, StreamCipher};
|
||||
use tlsn_universal_hash::UniversalHash;
|
||||
use tracing::instrument;
|
||||
|
||||
use crate::aes_gcm::{AesGcmError, Role};
|
||||
|
||||
pub(crate) const TAG_LEN: usize = 16;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
struct TagShare([u8; TAG_LEN]);
|
||||
|
||||
impl AsRef<[u8]> for TagShare {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Add for TagShare {
|
||||
type Output = [u8; TAG_LEN];
|
||||
|
||||
fn add(self, rhs: Self) -> Self::Output {
|
||||
core::array::from_fn(|i| self.0[i] ^ rhs.0[i])
|
||||
}
|
||||
}
|
||||
|
||||
#[instrument(level = "trace", skip_all, err)]
|
||||
async fn compute_tag_share<C: StreamCipher<Aes128Ctr> + ?Sized, H: UniversalHash + ?Sized>(
|
||||
aes_ctr: &mut C,
|
||||
hasher: &mut H,
|
||||
explicit_nonce: Vec<u8>,
|
||||
ciphertext: Vec<u8>,
|
||||
aad: Vec<u8>,
|
||||
) -> Result<TagShare, AesGcmError> {
|
||||
let (j0, hash) = futures::try_join!(
|
||||
aes_ctr
|
||||
.share_keystream_block(explicit_nonce, 1)
|
||||
.map_err(AesGcmError::from),
|
||||
hasher
|
||||
.finalize(build_ghash_data(aad, ciphertext))
|
||||
.map_err(AesGcmError::from)
|
||||
)?;
|
||||
|
||||
debug_assert!(j0.len() == TAG_LEN);
|
||||
debug_assert!(hash.len() == TAG_LEN);
|
||||
|
||||
let tag_share = core::array::from_fn(|i| j0[i] ^ hash[i]);
|
||||
|
||||
Ok(TagShare(tag_share))
|
||||
}
|
||||
|
||||
/// Computes the tag for a ciphertext and additional data.
|
||||
///
|
||||
/// The commit-reveal step is not required for computing a tag sent to the
|
||||
/// Server, as it will be able to detect if the tag is incorrect.
|
||||
#[instrument(level = "debug", skip_all, err)]
|
||||
pub(crate) async fn compute_tag<
|
||||
Ctx: Context,
|
||||
C: StreamCipher<Aes128Ctr> + ?Sized,
|
||||
H: UniversalHash + ?Sized,
|
||||
>(
|
||||
ctx: &mut Ctx,
|
||||
aes_ctr: &mut C,
|
||||
hasher: &mut H,
|
||||
explicit_nonce: Vec<u8>,
|
||||
ciphertext: Vec<u8>,
|
||||
aad: Vec<u8>,
|
||||
) -> Result<[u8; TAG_LEN], AesGcmError> {
|
||||
let tag_share = compute_tag_share(aes_ctr, hasher, explicit_nonce, ciphertext, aad).await?;
|
||||
|
||||
// TODO: The follower doesn't really need to learn the tag,
|
||||
// we could reduce some latency by not sending it.
|
||||
let io = ctx.io_mut();
|
||||
io.send(tag_share.clone()).await?;
|
||||
let other_tag_share: TagShare = io.expect_next().await?;
|
||||
|
||||
let tag = tag_share + other_tag_share;
|
||||
|
||||
Ok(tag)
|
||||
}
|
||||
|
||||
/// Verifies a purported tag against the ciphertext and additional data.
|
||||
///
|
||||
/// Verifying a tag requires a commit-reveal protocol between the leader and
|
||||
/// follower. Without it, the party which receives the other's tag share first
|
||||
/// could trivially compute a tag share which would cause an invalid message to
|
||||
/// be accepted.
|
||||
#[instrument(level = "debug", skip_all, err)]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) async fn verify_tag<
|
||||
Ctx: Context,
|
||||
C: StreamCipher<Aes128Ctr> + ?Sized,
|
||||
H: UniversalHash + ?Sized,
|
||||
>(
|
||||
ctx: &mut Ctx,
|
||||
aes_ctr: &mut C,
|
||||
hasher: &mut H,
|
||||
role: Role,
|
||||
explicit_nonce: Vec<u8>,
|
||||
ciphertext: Vec<u8>,
|
||||
aad: Vec<u8>,
|
||||
purported_tag: [u8; TAG_LEN],
|
||||
) -> Result<(), AesGcmError> {
|
||||
let tag_share = compute_tag_share(aes_ctr, hasher, explicit_nonce, ciphertext, aad).await?;
|
||||
|
||||
let io = ctx.io_mut();
|
||||
let tag = match role {
|
||||
Role::Leader => {
|
||||
// Send commitment of tag share to follower.
|
||||
let (tag_share_decommitment, tag_share_commitment) = tag_share.clone().hash_commit();
|
||||
|
||||
io.send(tag_share_commitment).await?;
|
||||
|
||||
let follower_tag_share: TagShare = io.expect_next().await?;
|
||||
|
||||
// Send decommitment (tag share) to follower.
|
||||
io.send(tag_share_decommitment).await?;
|
||||
|
||||
tag_share + follower_tag_share
|
||||
}
|
||||
Role::Follower => {
|
||||
// Wait for commitment from leader.
|
||||
let commitment: Hash = io.expect_next().await?;
|
||||
|
||||
// Send tag share to leader.
|
||||
io.send(tag_share.clone()).await?;
|
||||
|
||||
// Expect decommitment (tag share) from leader.
|
||||
let decommitment: Decommitment<TagShare> = io.expect_next().await?;
|
||||
|
||||
// Verify decommitment.
|
||||
decommitment.verify(&commitment).map_err(|_| {
|
||||
AesGcmError::peer("leader tag share commitment verification failed")
|
||||
})?;
|
||||
|
||||
let leader_tag_share = decommitment.into_inner();
|
||||
|
||||
tag_share + leader_tag_share
|
||||
}
|
||||
};
|
||||
|
||||
// Reject if tag is incorrect.
|
||||
if tag != purported_tag {
|
||||
return Err(AesGcmError::invalid_tag());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Builds padded data for GHASH.
|
||||
fn build_ghash_data(mut aad: Vec<u8>, mut ciphertext: Vec<u8>) -> Vec<u8> {
|
||||
let associated_data_bitlen = (aad.len() as u64) * 8;
|
||||
let text_bitlen = (ciphertext.len() as u64) * 8;
|
||||
|
||||
let len_block = ((associated_data_bitlen as u128) << 64) + (text_bitlen as u128);
|
||||
|
||||
// Pad data to be a multiple of 16 bytes.
|
||||
let aad_padded_block_count = (aad.len() / 16) + (aad.len() % 16 != 0) as usize;
|
||||
aad.resize(aad_padded_block_count * 16, 0);
|
||||
|
||||
let ciphertext_padded_block_count =
|
||||
(ciphertext.len() / 16) + (ciphertext.len() % 16 != 0) as usize;
|
||||
ciphertext.resize(ciphertext_padded_block_count * 16, 0);
|
||||
|
||||
let mut data: Vec<u8> = Vec::with_capacity(aad.len() + ciphertext.len() + 16);
|
||||
data.extend(aad);
|
||||
data.extend(ciphertext);
|
||||
data.extend_from_slice(&len_block.to_be_bytes());
|
||||
|
||||
data
|
||||
}
|
||||
@@ -1,255 +0,0 @@
|
||||
//! This crate provides implementations of 2PC AEADs for authenticated
|
||||
//! encryption with a shared key.
|
||||
//!
|
||||
//! Both parties can work together to encrypt and decrypt messages with
|
||||
//! different visibility configurations. See [`Aead`] for more information on
|
||||
//! the interface.
|
||||
//!
|
||||
//! For example, one party can privately provide the plaintext to encrypt, while
|
||||
//! both parties can see the ciphertext and the tag. Or, both parties can
|
||||
//! cooperate to decrypt a ciphertext and verify the tag, while only one party
|
||||
//! can see the plaintext.
|
||||
|
||||
#![deny(missing_docs, unreachable_pub, unused_must_use)]
|
||||
#![deny(clippy::all)]
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
pub mod aes_gcm;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use mpz_garble::value::ValueRef;
|
||||
|
||||
/// This trait defines the interface for AEADs.
|
||||
#[async_trait]
|
||||
pub trait Aead: Send {
|
||||
/// The error type for the AEAD.
|
||||
type Error: std::error::Error + Send + Sync + 'static;
|
||||
|
||||
/// Sets the key for the AEAD.
|
||||
async fn set_key(&mut self, key: ValueRef, iv: ValueRef) -> Result<(), Self::Error>;
|
||||
|
||||
/// Decodes the key for the AEAD, revealing it to this party.
|
||||
async fn decode_key_private(&mut self) -> Result<(), Self::Error>;
|
||||
|
||||
/// Decodes the key for the AEAD, revealing it to the other party(s).
|
||||
async fn decode_key_blind(&mut self) -> Result<(), Self::Error>;
|
||||
|
||||
/// Sets the transcript id.
|
||||
///
|
||||
/// The AEAD assigns unique identifiers to each byte of plaintext
|
||||
/// during encryption and decryption.
|
||||
///
|
||||
/// For example, if the transcript id is set to `foo`, then the first byte
|
||||
/// will be assigned the id `foo/0`, the second byte `foo/1`, and so on.
|
||||
///
|
||||
/// Each transcript id has an independent counter.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// The state of a transcript counter is preserved between calls to
|
||||
/// `set_transcript_id`.
|
||||
fn set_transcript_id(&mut self, id: &str);
|
||||
|
||||
/// Performs any necessary one-time setup for the AEAD.
|
||||
async fn setup(&mut self) -> Result<(), Self::Error>;
|
||||
|
||||
/// Preprocesses for the given number of bytes.
|
||||
async fn preprocess(&mut self, len: usize) -> Result<(), Self::Error>;
|
||||
|
||||
/// Starts the AEAD.
|
||||
///
|
||||
/// This method performs initialization for the AEAD after setting the key.
|
||||
async fn start(&mut self) -> Result<(), Self::Error>;
|
||||
|
||||
/// Encrypts a plaintext message, returning the ciphertext and tag.
|
||||
///
|
||||
/// The plaintext is provided by both parties.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `explicit_nonce` - The explicit nonce to use for encryption.
|
||||
/// * `plaintext` - The plaintext to encrypt.
|
||||
/// * `aad` - Additional authenticated data.
|
||||
async fn encrypt_public(
|
||||
&mut self,
|
||||
explicit_nonce: Vec<u8>,
|
||||
plaintext: Vec<u8>,
|
||||
aad: Vec<u8>,
|
||||
) -> Result<Vec<u8>, Self::Error>;
|
||||
|
||||
/// Encrypts a plaintext message, hiding it from the other party, returning
|
||||
/// the ciphertext and tag.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `explicit_nonce` - The explicit nonce to use for encryption.
|
||||
/// * `plaintext` - The plaintext to encrypt.
|
||||
/// * `aad` - Additional authenticated data.
|
||||
async fn encrypt_private(
|
||||
&mut self,
|
||||
explicit_nonce: Vec<u8>,
|
||||
plaintext: Vec<u8>,
|
||||
aad: Vec<u8>,
|
||||
) -> Result<Vec<u8>, Self::Error>;
|
||||
|
||||
/// Encrypts a plaintext message provided by the other party, returning
|
||||
/// the ciphertext and tag.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `explicit_nonce` - The explicit nonce to use for encryption.
|
||||
/// * `plaintext_len` - The length of the plaintext to encrypt.
|
||||
/// * `aad` - Additional authenticated data.
|
||||
async fn encrypt_blind(
|
||||
&mut self,
|
||||
explicit_nonce: Vec<u8>,
|
||||
plaintext_len: usize,
|
||||
aad: Vec<u8>,
|
||||
) -> Result<Vec<u8>, Self::Error>;
|
||||
|
||||
/// Decrypts a ciphertext message, returning the plaintext to both parties.
|
||||
///
|
||||
/// This method checks the authenticity of the ciphertext, tag and
|
||||
/// additional data.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `explicit_nonce` - The explicit nonce to use for decryption.
|
||||
/// * `payload` - The ciphertext and tag to authenticate and decrypt.
|
||||
/// * `aad` - Additional authenticated data.
|
||||
async fn decrypt_public(
|
||||
&mut self,
|
||||
explicit_nonce: Vec<u8>,
|
||||
payload: Vec<u8>,
|
||||
aad: Vec<u8>,
|
||||
) -> Result<Vec<u8>, Self::Error>;
|
||||
|
||||
/// Decrypts a ciphertext message, returning the plaintext only to this
|
||||
/// party.
|
||||
///
|
||||
/// This method checks the authenticity of the ciphertext, tag and
|
||||
/// additional data.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `explicit_nonce` - The explicit nonce to use for decryption.
|
||||
/// * `payload` - The ciphertext and tag to authenticate and decrypt.
|
||||
/// * `aad` - Additional authenticated data.
|
||||
async fn decrypt_private(
|
||||
&mut self,
|
||||
explicit_nonce: Vec<u8>,
|
||||
payload: Vec<u8>,
|
||||
aad: Vec<u8>,
|
||||
) -> Result<Vec<u8>, Self::Error>;
|
||||
|
||||
/// Decrypts a ciphertext message, returning the plaintext only to the other
|
||||
/// party.
|
||||
///
|
||||
/// This method checks the authenticity of the ciphertext, tag and
|
||||
/// additional data.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `explicit_nonce` - The explicit nonce to use for decryption.
|
||||
/// * `payload` - The ciphertext and tag to authenticate and decrypt.
|
||||
/// * `aad` - Additional authenticated data.
|
||||
async fn decrypt_blind(
|
||||
&mut self,
|
||||
explicit_nonce: Vec<u8>,
|
||||
payload: Vec<u8>,
|
||||
aad: Vec<u8>,
|
||||
) -> Result<(), Self::Error>;
|
||||
|
||||
/// Verifies the tag of a ciphertext message.
|
||||
///
|
||||
/// This method checks the authenticity of the ciphertext, tag and
|
||||
/// additional data.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `explicit_nonce` - The explicit nonce to use for decryption.
|
||||
/// * `payload` - The ciphertext and tag to authenticate and decrypt.
|
||||
/// * `aad` - Additional authenticated data.
|
||||
async fn verify_tag(
|
||||
&mut self,
|
||||
explicit_nonce: Vec<u8>,
|
||||
payload: Vec<u8>,
|
||||
aad: Vec<u8>,
|
||||
) -> Result<(), Self::Error>;
|
||||
|
||||
/// Locally decrypts the provided ciphertext and then proves in ZK to the
|
||||
/// other party(s) that the plaintext is correct.
|
||||
///
|
||||
/// Returns the plaintext.
|
||||
///
|
||||
/// This method requires this party to know the encryption key, which can be
|
||||
/// achieved by calling the `decode_key_private` method.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `explicit_nonce` - The explicit nonce to use for the keystream.
|
||||
/// * `payload` - The ciphertext and tag to decrypt and prove.
|
||||
/// * `aad` - Additional authenticated data.
|
||||
async fn prove_plaintext(
|
||||
&mut self,
|
||||
explicit_nonce: Vec<u8>,
|
||||
payload: Vec<u8>,
|
||||
aad: Vec<u8>,
|
||||
) -> Result<Vec<u8>, Self::Error>;
|
||||
|
||||
/// Locally decrypts the provided ciphertext and then proves in ZK to the
|
||||
/// other party(s) that the plaintext is correct.
|
||||
///
|
||||
/// Returns the plaintext.
|
||||
///
|
||||
/// This method requires this party to know the encryption key, which can be
|
||||
/// achieved by calling the `decode_key_private` method.
|
||||
///
|
||||
/// # WARNING
|
||||
///
|
||||
/// This method does not verify the tag of the ciphertext. Only use this if
|
||||
/// you know what you're doing.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `explicit_nonce` - The explicit nonce to use for the keystream.
|
||||
/// * `ciphertext` - The ciphertext to decrypt and prove.
|
||||
async fn prove_plaintext_no_tag(
|
||||
&mut self,
|
||||
explicit_nonce: Vec<u8>,
|
||||
ciphertext: Vec<u8>,
|
||||
) -> Result<Vec<u8>, Self::Error>;
|
||||
|
||||
/// Verifies the other party(s) can prove they know a plaintext which
|
||||
/// encrypts to the given ciphertext.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `explicit_nonce` - The explicit nonce to use for the keystream.
|
||||
/// * `payload` - The ciphertext and tag to verify.
|
||||
/// * `aad` - Additional authenticated data.
|
||||
async fn verify_plaintext(
|
||||
&mut self,
|
||||
explicit_nonce: Vec<u8>,
|
||||
payload: Vec<u8>,
|
||||
aad: Vec<u8>,
|
||||
) -> Result<(), Self::Error>;
|
||||
|
||||
/// Verifies the other party(s) can prove they know a plaintext which
|
||||
/// encrypts to the given ciphertext.
|
||||
///
|
||||
/// # WARNING
|
||||
///
|
||||
/// This method does not verify the tag of the ciphertext. Only use this if
|
||||
/// you know what you're doing.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `explicit_nonce` - The explicit nonce to use for the keystream.
|
||||
/// * `ciphertext` - The ciphertext to verify.
|
||||
async fn verify_plaintext_no_tag(
|
||||
&mut self,
|
||||
explicit_nonce: Vec<u8>,
|
||||
ciphertext: Vec<u8>,
|
||||
) -> Result<(), Self::Error>;
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
[package]
|
||||
name = "tlsn-block-cipher"
|
||||
authors = ["TLSNotary Team"]
|
||||
description = "2PC block cipher implementation"
|
||||
keywords = ["tls", "mpc", "2pc", "block-cipher"]
|
||||
categories = ["cryptography"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
version = "0.1.0-alpha.8-pre"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
name = "block_cipher"
|
||||
|
||||
[features]
|
||||
default = ["mock"]
|
||||
mock = []
|
||||
|
||||
[dependencies]
|
||||
mpz-circuits = { workspace = true }
|
||||
mpz-garble = { workspace = true }
|
||||
tlsn-utils = { workspace = true }
|
||||
async-trait = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
derive_builder = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
aes = { workspace = true }
|
||||
cipher = { workspace = true }
|
||||
tokio = { workspace = true, features = ["macros", "rt", "rt-multi-thread"] }
|
||||
@@ -1,277 +0,0 @@
|
||||
use std::{collections::VecDeque, marker::PhantomData};
|
||||
|
||||
use async_trait::async_trait;
|
||||
|
||||
use mpz_garble::{value::ValueRef, Decode, DecodePrivate, Execute, Load, Memory};
|
||||
use tracing::instrument;
|
||||
use utils::id::NestedId;
|
||||
|
||||
use crate::{BlockCipher, BlockCipherCircuit, BlockCipherConfig, BlockCipherError, Visibility};
|
||||
|
||||
#[derive(Debug)]
|
||||
struct State {
|
||||
private_execution_id: NestedId,
|
||||
public_execution_id: NestedId,
|
||||
preprocessed_private: VecDeque<BlockVars>,
|
||||
preprocessed_public: VecDeque<BlockVars>,
|
||||
key: Option<ValueRef>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct BlockVars {
|
||||
msg: ValueRef,
|
||||
ciphertext: ValueRef,
|
||||
}
|
||||
|
||||
/// An MPC block cipher.
|
||||
#[derive(Debug)]
|
||||
pub struct MpcBlockCipher<C, E>
|
||||
where
|
||||
C: BlockCipherCircuit,
|
||||
E: Memory + Execute + Decode + DecodePrivate + Send + Sync,
|
||||
{
|
||||
state: State,
|
||||
|
||||
executor: E,
|
||||
|
||||
_cipher: PhantomData<C>,
|
||||
}
|
||||
|
||||
impl<C, E> MpcBlockCipher<C, E>
|
||||
where
|
||||
C: BlockCipherCircuit,
|
||||
E: Memory + Execute + Decode + DecodePrivate + Send + Sync,
|
||||
{
|
||||
/// Creates a new MPC block cipher.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `config` - The configuration for the block cipher.
|
||||
/// * `executor` - The executor to use for the MPC.
|
||||
pub fn new(config: BlockCipherConfig, executor: E) -> Self {
|
||||
let private_execution_id = NestedId::new(&config.id)
|
||||
.append_string("private")
|
||||
.append_counter();
|
||||
let public_execution_id = NestedId::new(&config.id)
|
||||
.append_string("public")
|
||||
.append_counter();
|
||||
Self {
|
||||
state: State {
|
||||
private_execution_id,
|
||||
public_execution_id,
|
||||
preprocessed_private: VecDeque::new(),
|
||||
preprocessed_public: VecDeque::new(),
|
||||
key: None,
|
||||
},
|
||||
executor,
|
||||
_cipher: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
fn define_block(&mut self, vis: Visibility) -> BlockVars {
|
||||
let (id, msg) = match vis {
|
||||
Visibility::Private => {
|
||||
let id = self
|
||||
.state
|
||||
.private_execution_id
|
||||
.increment_in_place()
|
||||
.to_string();
|
||||
let msg = self
|
||||
.executor
|
||||
.new_private_input::<C::BLOCK>(&format!("{}/msg", &id))
|
||||
.expect("message is not defined");
|
||||
(id, msg)
|
||||
}
|
||||
Visibility::Blind => {
|
||||
let id = self
|
||||
.state
|
||||
.private_execution_id
|
||||
.increment_in_place()
|
||||
.to_string();
|
||||
let msg = self
|
||||
.executor
|
||||
.new_blind_input::<C::BLOCK>(&format!("{}/msg", &id))
|
||||
.expect("message is not defined");
|
||||
(id, msg)
|
||||
}
|
||||
Visibility::Public => {
|
||||
let id = self
|
||||
.state
|
||||
.public_execution_id
|
||||
.increment_in_place()
|
||||
.to_string();
|
||||
let msg = self
|
||||
.executor
|
||||
.new_public_input::<C::BLOCK>(&format!("{}/msg", &id))
|
||||
.expect("message is not defined");
|
||||
(id, msg)
|
||||
}
|
||||
};
|
||||
|
||||
let ciphertext = self
|
||||
.executor
|
||||
.new_output::<C::BLOCK>(&format!("{}/ciphertext", &id))
|
||||
.expect("message is not defined");
|
||||
|
||||
BlockVars { msg, ciphertext }
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<C, E> BlockCipher<C> for MpcBlockCipher<C, E>
|
||||
where
|
||||
C: BlockCipherCircuit,
|
||||
E: Memory + Load + Execute + Decode + DecodePrivate + Send + Sync + Send,
|
||||
{
|
||||
#[instrument(level = "trace", skip_all)]
|
||||
fn set_key(&mut self, key: ValueRef) {
|
||||
self.state.key = Some(key);
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip_all, err)]
|
||||
async fn preprocess(
|
||||
&mut self,
|
||||
visibility: Visibility,
|
||||
count: usize,
|
||||
) -> Result<(), BlockCipherError> {
|
||||
let key = self
|
||||
.state
|
||||
.key
|
||||
.clone()
|
||||
.ok_or_else(BlockCipherError::key_not_set)?;
|
||||
|
||||
for _ in 0..count {
|
||||
let vars = self.define_block(visibility);
|
||||
|
||||
self.executor
|
||||
.load(
|
||||
C::circuit(),
|
||||
&[key.clone(), vars.msg.clone()],
|
||||
&[vars.ciphertext.clone()],
|
||||
)
|
||||
.await?;
|
||||
|
||||
match visibility {
|
||||
Visibility::Private | Visibility::Blind => {
|
||||
self.state.preprocessed_private.push_back(vars)
|
||||
}
|
||||
Visibility::Public => self.state.preprocessed_public.push_back(vars),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip_all, err)]
|
||||
async fn encrypt_private(&mut self, plaintext: Vec<u8>) -> Result<Vec<u8>, BlockCipherError> {
|
||||
let len = plaintext.len();
|
||||
let block: C::BLOCK = plaintext
|
||||
.try_into()
|
||||
.map_err(|_| BlockCipherError::invalid_message_length::<C>(len))?;
|
||||
|
||||
let key = self
|
||||
.state
|
||||
.key
|
||||
.clone()
|
||||
.ok_or_else(BlockCipherError::key_not_set)?;
|
||||
|
||||
let BlockVars { msg, ciphertext } =
|
||||
if let Some(vars) = self.state.preprocessed_private.pop_front() {
|
||||
vars
|
||||
} else {
|
||||
self.define_block(Visibility::Private)
|
||||
};
|
||||
|
||||
self.executor.assign(&msg, block)?;
|
||||
|
||||
self.executor
|
||||
.execute(C::circuit(), &[key, msg], &[ciphertext.clone()])
|
||||
.await?;
|
||||
|
||||
let mut outputs = self.executor.decode(&[ciphertext]).await?;
|
||||
|
||||
let ciphertext: C::BLOCK = if let Ok(ciphertext) = outputs
|
||||
.pop()
|
||||
.expect("ciphertext should be present")
|
||||
.try_into()
|
||||
{
|
||||
ciphertext
|
||||
} else {
|
||||
panic!("ciphertext should be a block")
|
||||
};
|
||||
|
||||
Ok(ciphertext.into())
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip_all, err)]
|
||||
async fn encrypt_blind(&mut self) -> Result<Vec<u8>, BlockCipherError> {
|
||||
let key = self
|
||||
.state
|
||||
.key
|
||||
.clone()
|
||||
.ok_or_else(BlockCipherError::key_not_set)?;
|
||||
|
||||
let BlockVars { msg, ciphertext } =
|
||||
if let Some(vars) = self.state.preprocessed_private.pop_front() {
|
||||
vars
|
||||
} else {
|
||||
self.define_block(Visibility::Blind)
|
||||
};
|
||||
|
||||
self.executor
|
||||
.execute(C::circuit(), &[key, msg], &[ciphertext.clone()])
|
||||
.await?;
|
||||
|
||||
let mut outputs = self.executor.decode(&[ciphertext]).await?;
|
||||
|
||||
let ciphertext: C::BLOCK = if let Ok(ciphertext) = outputs
|
||||
.pop()
|
||||
.expect("ciphertext should be present")
|
||||
.try_into()
|
||||
{
|
||||
ciphertext
|
||||
} else {
|
||||
panic!("ciphertext should be a block")
|
||||
};
|
||||
|
||||
Ok(ciphertext.into())
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip_all, err)]
|
||||
async fn encrypt_share(&mut self, plaintext: Vec<u8>) -> Result<Vec<u8>, BlockCipherError> {
|
||||
let len = plaintext.len();
|
||||
let block: C::BLOCK = plaintext
|
||||
.try_into()
|
||||
.map_err(|_| BlockCipherError::invalid_message_length::<C>(len))?;
|
||||
|
||||
let key = self
|
||||
.state
|
||||
.key
|
||||
.clone()
|
||||
.ok_or_else(BlockCipherError::key_not_set)?;
|
||||
|
||||
let BlockVars { msg, ciphertext } =
|
||||
if let Some(vars) = self.state.preprocessed_public.pop_front() {
|
||||
vars
|
||||
} else {
|
||||
self.define_block(Visibility::Public)
|
||||
};
|
||||
|
||||
self.executor.assign(&msg, block)?;
|
||||
|
||||
self.executor
|
||||
.execute(C::circuit(), &[key, msg], &[ciphertext.clone()])
|
||||
.await?;
|
||||
|
||||
let mut outputs = self.executor.decode_shared(&[ciphertext]).await?;
|
||||
|
||||
let share: C::BLOCK =
|
||||
if let Ok(share) = outputs.pop().expect("share should be present").try_into() {
|
||||
share
|
||||
} else {
|
||||
panic!("share should be a block")
|
||||
};
|
||||
|
||||
Ok(share.into())
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use mpz_circuits::{
|
||||
circuits::AES128,
|
||||
types::{StaticValueType, Value},
|
||||
Circuit,
|
||||
};
|
||||
|
||||
/// A block cipher circuit.
|
||||
pub trait BlockCipherCircuit: Default + Clone + Send + Sync {
|
||||
/// The key type.
|
||||
type KEY: StaticValueType + Send + Sync;
|
||||
/// The block type.
|
||||
type BLOCK: StaticValueType + TryFrom<Vec<u8>> + TryFrom<Value> + Into<Vec<u8>> + Send + Sync;
|
||||
|
||||
/// The length of the key.
|
||||
const KEY_LEN: usize;
|
||||
/// The length of the block.
|
||||
const BLOCK_LEN: usize;
|
||||
|
||||
/// Returns the circuit of the cipher.
|
||||
fn circuit() -> Arc<Circuit>;
|
||||
}
|
||||
|
||||
/// Aes128 block cipher circuit.
|
||||
#[derive(Default, Debug, Clone)]
|
||||
pub struct Aes128;
|
||||
|
||||
impl BlockCipherCircuit for Aes128 {
|
||||
type KEY = [u8; 16];
|
||||
type BLOCK = [u8; 16];
|
||||
|
||||
const KEY_LEN: usize = 16;
|
||||
const BLOCK_LEN: usize = 16;
|
||||
|
||||
fn circuit() -> Arc<Circuit> {
|
||||
AES128.clone()
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
use derive_builder::Builder;
|
||||
|
||||
/// Configuration for a block cipher.
|
||||
#[derive(Debug, Clone, Builder)]
|
||||
pub struct BlockCipherConfig {
|
||||
/// The ID of the block cipher.
|
||||
#[builder(setter(into))]
|
||||
pub(crate) id: String,
|
||||
}
|
||||
|
||||
impl BlockCipherConfig {
|
||||
/// Creates a new builder for the block cipher configuration.
|
||||
pub fn builder() -> BlockCipherConfigBuilder {
|
||||
BlockCipherConfigBuilder::default()
|
||||
}
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
use core::fmt;
|
||||
use std::error::Error;
|
||||
|
||||
use crate::BlockCipherCircuit;
|
||||
|
||||
/// A block cipher error.
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub struct BlockCipherError {
|
||||
kind: ErrorKind,
|
||||
#[source]
|
||||
source: Option<Box<dyn Error + Send + Sync>>,
|
||||
}
|
||||
|
||||
impl BlockCipherError {
|
||||
pub(crate) fn new<E>(kind: ErrorKind, source: E) -> Self
|
||||
where
|
||||
E: Into<Box<dyn Error + Send + Sync>>,
|
||||
{
|
||||
Self {
|
||||
kind,
|
||||
source: Some(source.into()),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn key_not_set() -> Self {
|
||||
Self {
|
||||
kind: ErrorKind::Key,
|
||||
source: Some("key not set".into()),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn invalid_message_length<C: BlockCipherCircuit>(len: usize) -> Self {
|
||||
Self {
|
||||
kind: ErrorKind::Msg,
|
||||
source: Some(
|
||||
format!(
|
||||
"message length does not equal block length: {} != {}",
|
||||
len,
|
||||
C::BLOCK_LEN
|
||||
)
|
||||
.into(),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum ErrorKind {
|
||||
Vm,
|
||||
Key,
|
||||
Msg,
|
||||
}
|
||||
|
||||
impl fmt::Display for BlockCipherError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self.kind {
|
||||
ErrorKind::Vm => write!(f, "vm error")?,
|
||||
ErrorKind::Key => write!(f, "key error")?,
|
||||
ErrorKind::Msg => write!(f, "message error")?,
|
||||
}
|
||||
|
||||
if let Some(ref source) = self.source {
|
||||
write!(f, " caused by: {}", source)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<mpz_garble::MemoryError> for BlockCipherError {
|
||||
fn from(error: mpz_garble::MemoryError) -> Self {
|
||||
Self::new(ErrorKind::Vm, error)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<mpz_garble::LoadError> for BlockCipherError {
|
||||
fn from(error: mpz_garble::LoadError) -> Self {
|
||||
Self::new(ErrorKind::Vm, error)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<mpz_garble::ExecutionError> for BlockCipherError {
|
||||
fn from(error: mpz_garble::ExecutionError) -> Self {
|
||||
Self::new(ErrorKind::Vm, error)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<mpz_garble::DecodeError> for BlockCipherError {
|
||||
fn from(error: mpz_garble::DecodeError) -> Self {
|
||||
Self::new(ErrorKind::Vm, error)
|
||||
}
|
||||
}
|
||||
@@ -1,236 +0,0 @@
|
||||
//! This crate provides a 2PC block cipher implementation.
|
||||
//!
|
||||
//! Both parties work together to encrypt or share an encrypted block using a
|
||||
//! shared key.
|
||||
|
||||
#![deny(missing_docs, unreachable_pub, unused_must_use)]
|
||||
#![deny(clippy::all)]
|
||||
#![deny(unsafe_code)]
|
||||
|
||||
mod cipher;
|
||||
mod circuit;
|
||||
mod config;
|
||||
mod error;
|
||||
|
||||
use async_trait::async_trait;
|
||||
|
||||
use mpz_garble::value::ValueRef;
|
||||
|
||||
pub use crate::{
|
||||
cipher::MpcBlockCipher,
|
||||
circuit::{Aes128, BlockCipherCircuit},
|
||||
};
|
||||
pub use config::{BlockCipherConfig, BlockCipherConfigBuilder, BlockCipherConfigBuilderError};
|
||||
pub use error::BlockCipherError;
|
||||
|
||||
/// Visibility of a message plaintext.
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum Visibility {
|
||||
/// Private message.
|
||||
Private,
|
||||
/// Blind message.
|
||||
Blind,
|
||||
/// Public message.
|
||||
Public,
|
||||
}
|
||||
|
||||
/// A trait for MPC block ciphers.
|
||||
#[async_trait]
|
||||
pub trait BlockCipher<Cipher>: Send + Sync
|
||||
where
|
||||
Cipher: BlockCipherCircuit,
|
||||
{
|
||||
/// Sets the key for the block cipher.
|
||||
fn set_key(&mut self, key: ValueRef);
|
||||
|
||||
/// Preprocesses `count` blocks.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `visibility` - The visibility of the plaintext.
|
||||
/// * `count` - The number of blocks to preprocess.
|
||||
async fn preprocess(
|
||||
&mut self,
|
||||
visibility: Visibility,
|
||||
count: usize,
|
||||
) -> Result<(), BlockCipherError>;
|
||||
|
||||
/// Encrypts the given plaintext keeping it hidden from the other party(s).
|
||||
///
|
||||
/// Returns the ciphertext.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `plaintext` - The plaintext to encrypt.
|
||||
async fn encrypt_private(&mut self, plaintext: Vec<u8>) -> Result<Vec<u8>, BlockCipherError>;
|
||||
|
||||
/// Encrypts a plaintext provided by the other party(s).
|
||||
///
|
||||
/// Returns the ciphertext.
|
||||
async fn encrypt_blind(&mut self) -> Result<Vec<u8>, BlockCipherError>;
|
||||
|
||||
/// Encrypts a plaintext provided by both parties. Fails if the
|
||||
/// plaintext provided by both parties does not match.
|
||||
///
|
||||
/// Returns an additive share of the ciphertext.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `plaintext` - The plaintext to encrypt.
|
||||
async fn encrypt_share(&mut self, plaintext: Vec<u8>) -> Result<Vec<u8>, BlockCipherError>;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use mpz_garble::{protocol::deap::mock::create_mock_deap_vm, Memory};
|
||||
|
||||
use crate::circuit::Aes128;
|
||||
|
||||
use ::aes::Aes128 as TestAes128;
|
||||
use ::cipher::{BlockEncrypt, KeyInit};
|
||||
|
||||
fn aes128(key: [u8; 16], msg: [u8; 16]) -> [u8; 16] {
|
||||
let mut msg = msg.into();
|
||||
let cipher = TestAes128::new(&key.into());
|
||||
cipher.encrypt_block(&mut msg);
|
||||
msg.into()
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore = "expensive"]
|
||||
async fn test_block_cipher_blind() {
|
||||
let leader_config = BlockCipherConfig::builder().id("test").build().unwrap();
|
||||
let follower_config = BlockCipherConfig::builder().id("test").build().unwrap();
|
||||
|
||||
let key = [0u8; 16];
|
||||
|
||||
let (leader_vm, follower_vm) = create_mock_deap_vm();
|
||||
|
||||
// Key is public just for this test, typically it is private.
|
||||
let leader_key = leader_vm.new_public_input::<[u8; 16]>("key").unwrap();
|
||||
let follower_key = follower_vm.new_public_input::<[u8; 16]>("key").unwrap();
|
||||
|
||||
leader_vm.assign(&leader_key, key).unwrap();
|
||||
follower_vm.assign(&follower_key, key).unwrap();
|
||||
|
||||
let mut leader = MpcBlockCipher::<Aes128, _>::new(leader_config, leader_vm);
|
||||
leader.set_key(leader_key);
|
||||
|
||||
let mut follower = MpcBlockCipher::<Aes128, _>::new(follower_config, follower_vm);
|
||||
follower.set_key(follower_key);
|
||||
|
||||
let plaintext = [0u8; 16];
|
||||
|
||||
let (leader_ciphertext, follower_ciphertext) = tokio::try_join!(
|
||||
leader.encrypt_private(plaintext.to_vec()),
|
||||
follower.encrypt_blind()
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let expected = aes128(key, plaintext);
|
||||
|
||||
assert_eq!(leader_ciphertext, expected.to_vec());
|
||||
assert_eq!(leader_ciphertext, follower_ciphertext);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore = "expensive"]
|
||||
async fn test_block_cipher_share() {
|
||||
let leader_config = BlockCipherConfig::builder().id("test").build().unwrap();
|
||||
let follower_config = BlockCipherConfig::builder().id("test").build().unwrap();
|
||||
|
||||
let key = [0u8; 16];
|
||||
|
||||
let (leader_vm, follower_vm) = create_mock_deap_vm();
|
||||
|
||||
// Key is public just for this test, typically it is private.
|
||||
let leader_key = leader_vm.new_public_input::<[u8; 16]>("key").unwrap();
|
||||
let follower_key = follower_vm.new_public_input::<[u8; 16]>("key").unwrap();
|
||||
|
||||
leader_vm.assign(&leader_key, key).unwrap();
|
||||
follower_vm.assign(&follower_key, key).unwrap();
|
||||
|
||||
let mut leader = MpcBlockCipher::<Aes128, _>::new(leader_config, leader_vm);
|
||||
leader.set_key(leader_key);
|
||||
|
||||
let mut follower = MpcBlockCipher::<Aes128, _>::new(follower_config, follower_vm);
|
||||
follower.set_key(follower_key);
|
||||
|
||||
let plaintext = [0u8; 16];
|
||||
|
||||
let (leader_share, follower_share) = tokio::try_join!(
|
||||
leader.encrypt_share(plaintext.to_vec()),
|
||||
follower.encrypt_share(plaintext.to_vec())
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let expected = aes128(key, plaintext);
|
||||
|
||||
let result: [u8; 16] = std::array::from_fn(|i| leader_share[i] ^ follower_share[i]);
|
||||
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore = "expensive"]
|
||||
async fn test_block_cipher_preprocess() {
|
||||
let leader_config = BlockCipherConfig::builder().id("test").build().unwrap();
|
||||
let follower_config = BlockCipherConfig::builder().id("test").build().unwrap();
|
||||
|
||||
let key = [0u8; 16];
|
||||
|
||||
let (leader_vm, follower_vm) = create_mock_deap_vm();
|
||||
|
||||
// Key is public just for this test, typically it is private.
|
||||
let leader_key = leader_vm.new_public_input::<[u8; 16]>("key").unwrap();
|
||||
let follower_key = follower_vm.new_public_input::<[u8; 16]>("key").unwrap();
|
||||
|
||||
leader_vm.assign(&leader_key, key).unwrap();
|
||||
follower_vm.assign(&follower_key, key).unwrap();
|
||||
|
||||
let mut leader = MpcBlockCipher::<Aes128, _>::new(leader_config, leader_vm);
|
||||
leader.set_key(leader_key);
|
||||
|
||||
let mut follower = MpcBlockCipher::<Aes128, _>::new(follower_config, follower_vm);
|
||||
follower.set_key(follower_key);
|
||||
|
||||
let plaintext = [0u8; 16];
|
||||
|
||||
tokio::try_join!(
|
||||
leader.preprocess(Visibility::Private, 1),
|
||||
follower.preprocess(Visibility::Blind, 1)
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let (leader_ciphertext, follower_ciphertext) = tokio::try_join!(
|
||||
leader.encrypt_private(plaintext.to_vec()),
|
||||
follower.encrypt_blind()
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let expected = aes128(key, plaintext);
|
||||
|
||||
assert_eq!(leader_ciphertext, expected.to_vec());
|
||||
assert_eq!(leader_ciphertext, follower_ciphertext);
|
||||
|
||||
tokio::try_join!(
|
||||
leader.preprocess(Visibility::Public, 1),
|
||||
follower.preprocess(Visibility::Public, 1)
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let (leader_share, follower_share) = tokio::try_join!(
|
||||
leader.encrypt_share(plaintext.to_vec()),
|
||||
follower.encrypt_share(plaintext.to_vec())
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let expected = aes128(key, plaintext);
|
||||
|
||||
let result: [u8; 16] = std::array::from_fn(|i| leader_share[i] ^ follower_share[i]);
|
||||
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
}
|
||||
34
crates/components/cipher/Cargo.toml
Normal file
34
crates/components/cipher/Cargo.toml
Normal file
@@ -0,0 +1,34 @@
|
||||
[package]
|
||||
name = "tlsn-cipher"
|
||||
authors = ["TLSNotary Team"]
|
||||
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.12"
|
||||
edition = "2021"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[lib]
|
||||
name = "cipher"
|
||||
|
||||
[dependencies]
|
||||
mpz-circuits = { workspace = true }
|
||||
mpz-vm-core = { workspace = true }
|
||||
mpz-memory-core = { workspace = true }
|
||||
|
||||
async-trait = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
aes = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
mpz-garble = { workspace = true }
|
||||
mpz-common = { workspace = true }
|
||||
mpz-ot = { workspace = true }
|
||||
|
||||
tokio = { version = "1", features = ["macros", "rt", "rt-multi-thread"] }
|
||||
rand = { workspace = true }
|
||||
ctr = { workspace = true }
|
||||
cipher = { workspace = true }
|
||||
44
crates/components/cipher/src/aes/error.rs
Normal file
44
crates/components/cipher/src/aes/error.rs
Normal file
@@ -0,0 +1,44 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
/// AES error.
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub struct AesError {
|
||||
kind: ErrorKind,
|
||||
#[source]
|
||||
source: Option<Box<dyn std::error::Error + Send + Sync>>,
|
||||
}
|
||||
|
||||
impl AesError {
|
||||
pub(crate) fn new<E>(kind: ErrorKind, source: E) -> Self
|
||||
where
|
||||
E: Into<Box<dyn std::error::Error + Send + Sync>>,
|
||||
{
|
||||
Self {
|
||||
kind,
|
||||
source: Some(source.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub(crate) enum ErrorKind {
|
||||
Vm,
|
||||
Key,
|
||||
Iv,
|
||||
}
|
||||
|
||||
impl Display for AesError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self.kind {
|
||||
ErrorKind::Vm => write!(f, "vm error")?,
|
||||
ErrorKind::Key => write!(f, "key error")?,
|
||||
ErrorKind::Iv => write!(f, "iv error")?,
|
||||
}
|
||||
|
||||
if let Some(source) = &self.source {
|
||||
write!(f, " caused by: {}", source)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
375
crates/components/cipher/src/aes/mod.rs
Normal file
375
crates/components/cipher/src/aes/mod.rs
Normal file
@@ -0,0 +1,375 @@
|
||||
//! The AES-128 block cipher.
|
||||
|
||||
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 error;
|
||||
|
||||
pub use error::AesError;
|
||||
use error::ErrorKind;
|
||||
|
||||
/// Computes AES-128.
|
||||
#[derive(Default, Debug)]
|
||||
pub struct Aes128 {
|
||||
key: Option<Array<U8, 16>>,
|
||||
iv: Option<Array<U8, 4>>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Cipher for Aes128 {
|
||||
type Error = AesError;
|
||||
type Key = Array<U8, 16>;
|
||||
type Iv = Array<U8, 4>;
|
||||
type Nonce = Array<U8, 8>;
|
||||
type Counter = Array<U8, 4>;
|
||||
type Block = Array<U8, 16>;
|
||||
|
||||
fn set_key(&mut self, key: Array<U8, 16>) {
|
||||
self.key = Some(key);
|
||||
}
|
||||
|
||||
fn set_iv(&mut self, iv: Array<U8, 4>) {
|
||||
self.iv = Some(iv);
|
||||
}
|
||||
|
||||
fn key(&self) -> Option<&Array<U8, 16>> {
|
||||
self.key.as_ref()
|
||||
}
|
||||
|
||||
fn iv(&self) -> Option<&Array<U8, 4>> {
|
||||
self.iv.as_ref()
|
||||
}
|
||||
|
||||
fn alloc_block(
|
||||
&self,
|
||||
vm: &mut dyn Vm<Binary>,
|
||||
input: Array<U8, 16>,
|
||||
) -> Result<Self::Block, Self::Error> {
|
||||
let key = self
|
||||
.key
|
||||
.ok_or_else(|| AesError::new(ErrorKind::Key, "key not set"))?;
|
||||
|
||||
let output = vm
|
||||
.call(
|
||||
Call::builder(AES128.clone())
|
||||
.arg(key)
|
||||
.arg(input)
|
||||
.build()
|
||||
.expect("call should be valid"),
|
||||
)
|
||||
.map_err(|err| AesError::new(ErrorKind::Vm, err))?;
|
||||
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
fn alloc_ctr_block(
|
||||
&self,
|
||||
vm: &mut dyn Vm<Binary>,
|
||||
) -> Result<CtrBlock<Self::Nonce, Self::Counter, Self::Block>, Self::Error> {
|
||||
let key = self
|
||||
.key
|
||||
.ok_or_else(|| AesError::new(ErrorKind::Key, "key not set"))?;
|
||||
let iv = self
|
||||
.iv
|
||||
.ok_or_else(|| AesError::new(ErrorKind::Iv, "iv not set"))?;
|
||||
|
||||
let explicit_nonce: Array<U8, 8> = vm
|
||||
.alloc()
|
||||
.map_err(|err| AesError::new(ErrorKind::Vm, err))?;
|
||||
vm.mark_public(explicit_nonce)
|
||||
.map_err(|err| AesError::new(ErrorKind::Vm, err))?;
|
||||
|
||||
let counter: Array<U8, 4> = vm
|
||||
.alloc()
|
||||
.map_err(|err| AesError::new(ErrorKind::Vm, err))?;
|
||||
vm.mark_public(counter)
|
||||
.map_err(|err| AesError::new(ErrorKind::Vm, err))?;
|
||||
|
||||
let output = vm
|
||||
.call(
|
||||
Call::builder(AES128.clone())
|
||||
.arg(key)
|
||||
.arg(iv)
|
||||
.arg(explicit_nonce)
|
||||
.arg(counter)
|
||||
.build()
|
||||
.expect("call should be valid"),
|
||||
)
|
||||
.map_err(|err| AesError::new(ErrorKind::Vm, err))?;
|
||||
|
||||
Ok(CtrBlock {
|
||||
explicit_nonce,
|
||||
counter,
|
||||
output,
|
||||
})
|
||||
}
|
||||
|
||||
fn alloc_keystream(
|
||||
&self,
|
||||
vm: &mut dyn Vm<Binary>,
|
||||
len: usize,
|
||||
) -> Result<Keystream<Self::Nonce, Self::Counter, Self::Block>, Self::Error> {
|
||||
let key = self
|
||||
.key
|
||||
.ok_or_else(|| AesError::new(ErrorKind::Key, "key not set"))?;
|
||||
let iv = self
|
||||
.iv
|
||||
.ok_or_else(|| AesError::new(ErrorKind::Iv, "iv not set"))?;
|
||||
|
||||
let block_count = len.div_ceil(16);
|
||||
|
||||
let inputs = (0..block_count)
|
||||
.map(|_| {
|
||||
let explicit_nonce: Array<U8, 8> = vm
|
||||
.alloc()
|
||||
.map_err(|err| AesError::new(ErrorKind::Vm, err))?;
|
||||
let counter: Array<U8, 4> = vm
|
||||
.alloc()
|
||||
.map_err(|err| AesError::new(ErrorKind::Vm, err))?;
|
||||
|
||||
vm.mark_public(explicit_nonce)
|
||||
.map_err(|err| AesError::new(ErrorKind::Vm, err))?;
|
||||
vm.mark_public(counter)
|
||||
.map_err(|err| AesError::new(ErrorKind::Vm, err))?;
|
||||
|
||||
Ok((explicit_nonce, counter))
|
||||
})
|
||||
.collect::<Result<Vec<_>, AesError>>()?;
|
||||
|
||||
let blocks = inputs
|
||||
.into_iter()
|
||||
.map(|(explicit_nonce, counter)| {
|
||||
let output = vm
|
||||
.call(
|
||||
Call::builder(AES128.clone())
|
||||
.arg(key)
|
||||
.arg(iv)
|
||||
.arg(explicit_nonce)
|
||||
.arg(counter)
|
||||
.build()
|
||||
.expect("call should be valid"),
|
||||
)
|
||||
.map_err(|err| AesError::new(ErrorKind::Vm, err))?;
|
||||
|
||||
Ok(CtrBlock {
|
||||
explicit_nonce,
|
||||
counter,
|
||||
output,
|
||||
})
|
||||
})
|
||||
.collect::<Result<Vec<_>, AesError>>()?;
|
||||
|
||||
Ok(Keystream::new(&blocks))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::Cipher;
|
||||
use mpz_common::context::test_st_context;
|
||||
use mpz_garble::protocol::semihonest::{Evaluator, Garbler};
|
||||
use mpz_memory_core::{
|
||||
binary::{Binary, U8},
|
||||
correlated::Delta,
|
||||
Array, MemoryExt, Vector, ViewExt,
|
||||
};
|
||||
use mpz_ot::ideal::cot::ideal_cot;
|
||||
use mpz_vm_core::{Execute, Vm};
|
||||
use rand::{rngs::StdRng, SeedableRng};
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_aes_ctr() {
|
||||
let key = [42_u8; 16];
|
||||
let iv = [3_u8; 4];
|
||||
let nonce = [5_u8; 8];
|
||||
let start_counter = 3u32;
|
||||
|
||||
let (mut ctx_a, mut ctx_b) = test_st_context(8);
|
||||
let (mut gen, mut ev) = mock_vm();
|
||||
|
||||
let aes_gen = setup_ctr(key, iv, &mut gen);
|
||||
let aes_ev = setup_ctr(key, iv, &mut ev);
|
||||
|
||||
let msg = vec![42u8; 128];
|
||||
|
||||
let keystream_gen = aes_gen.alloc_keystream(&mut gen, msg.len()).unwrap();
|
||||
let keystream_ev = aes_ev.alloc_keystream(&mut ev, msg.len()).unwrap();
|
||||
|
||||
let msg_ref_gen: Vector<U8> = gen.alloc_vec(msg.len()).unwrap();
|
||||
gen.mark_public(msg_ref_gen).unwrap();
|
||||
gen.assign(msg_ref_gen, msg.clone()).unwrap();
|
||||
gen.commit(msg_ref_gen).unwrap();
|
||||
|
||||
let msg_ref_ev: Vector<U8> = ev.alloc_vec(msg.len()).unwrap();
|
||||
ev.mark_public(msg_ref_ev).unwrap();
|
||||
ev.assign(msg_ref_ev, msg.clone()).unwrap();
|
||||
ev.commit(msg_ref_ev).unwrap();
|
||||
|
||||
let mut ctr = start_counter..;
|
||||
keystream_gen
|
||||
.assign(&mut gen, nonce, move || ctr.next().unwrap().to_be_bytes())
|
||||
.unwrap();
|
||||
let mut ctr = start_counter..;
|
||||
keystream_ev
|
||||
.assign(&mut ev, nonce, move || ctr.next().unwrap().to_be_bytes())
|
||||
.unwrap();
|
||||
|
||||
let cipher_out_gen = keystream_gen.apply(&mut gen, msg_ref_gen).unwrap();
|
||||
let cipher_out_ev = keystream_ev.apply(&mut ev, msg_ref_ev).unwrap();
|
||||
|
||||
let (ct_gen, ct_ev) = tokio::try_join!(
|
||||
async {
|
||||
let out = gen.decode(cipher_out_gen).unwrap();
|
||||
gen.flush(&mut ctx_a).await.unwrap();
|
||||
gen.execute(&mut ctx_a).await.unwrap();
|
||||
gen.flush(&mut ctx_a).await.unwrap();
|
||||
out.await
|
||||
},
|
||||
async {
|
||||
let out = ev.decode(cipher_out_ev).unwrap();
|
||||
ev.flush(&mut ctx_b).await.unwrap();
|
||||
ev.execute(&mut ctx_b).await.unwrap();
|
||||
ev.flush(&mut ctx_b).await.unwrap();
|
||||
out.await
|
||||
}
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(ct_gen, ct_ev);
|
||||
|
||||
let expected = aes_apply_keystream(key, iv, nonce, start_counter as usize, msg);
|
||||
assert_eq!(ct_gen, expected);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_aes_ecb() {
|
||||
let key = [1_u8; 16];
|
||||
let input = [5_u8; 16];
|
||||
|
||||
let (mut ctx_a, mut ctx_b) = test_st_context(8);
|
||||
let (mut gen, mut ev) = mock_vm();
|
||||
|
||||
let aes_gen = setup_block(key, &mut gen);
|
||||
let aes_ev = setup_block(key, &mut ev);
|
||||
|
||||
let block_ref_gen: Array<U8, 16> = gen.alloc().unwrap();
|
||||
gen.mark_public(block_ref_gen).unwrap();
|
||||
gen.assign(block_ref_gen, input).unwrap();
|
||||
gen.commit(block_ref_gen).unwrap();
|
||||
|
||||
let block_ref_ev: Array<U8, 16> = ev.alloc().unwrap();
|
||||
ev.mark_public(block_ref_ev).unwrap();
|
||||
ev.assign(block_ref_ev, input).unwrap();
|
||||
ev.commit(block_ref_ev).unwrap();
|
||||
|
||||
let block_gen = aes_gen.alloc_block(&mut gen, block_ref_gen).unwrap();
|
||||
let block_ev = aes_ev.alloc_block(&mut ev, block_ref_ev).unwrap();
|
||||
|
||||
let (ciphertext_gen, ciphetext_ev) = tokio::try_join!(
|
||||
async {
|
||||
let out = gen.decode(block_gen).unwrap();
|
||||
gen.flush(&mut ctx_a).await.unwrap();
|
||||
gen.execute(&mut ctx_a).await.unwrap();
|
||||
gen.flush(&mut ctx_a).await.unwrap();
|
||||
out.await
|
||||
},
|
||||
async {
|
||||
let out = ev.decode(block_ev).unwrap();
|
||||
ev.flush(&mut ctx_b).await.unwrap();
|
||||
ev.execute(&mut ctx_b).await.unwrap();
|
||||
ev.flush(&mut ctx_b).await.unwrap();
|
||||
out.await
|
||||
}
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(ciphertext_gen, ciphetext_ev);
|
||||
|
||||
let expected = aes128(key, input);
|
||||
assert_eq!(ciphertext_gen, expected);
|
||||
}
|
||||
|
||||
fn mock_vm() -> (impl Vm<Binary>, impl Vm<Binary>) {
|
||||
let mut rng = StdRng::seed_from_u64(0);
|
||||
let delta = Delta::random(&mut rng);
|
||||
|
||||
let (cot_send, cot_recv) = ideal_cot(delta.into_inner());
|
||||
|
||||
let gen = Garbler::new(cot_send, [0u8; 16], delta);
|
||||
let ev = Evaluator::new(cot_recv);
|
||||
|
||||
(gen, ev)
|
||||
}
|
||||
|
||||
fn setup_ctr(key: [u8; 16], iv: [u8; 4], vm: &mut dyn Vm<Binary>) -> Aes128 {
|
||||
let key_ref: Array<U8, 16> = vm.alloc().unwrap();
|
||||
vm.mark_public(key_ref).unwrap();
|
||||
vm.assign(key_ref, key).unwrap();
|
||||
vm.commit(key_ref).unwrap();
|
||||
|
||||
let iv_ref: Array<U8, 4> = vm.alloc().unwrap();
|
||||
vm.mark_public(iv_ref).unwrap();
|
||||
vm.assign(iv_ref, iv).unwrap();
|
||||
vm.commit(iv_ref).unwrap();
|
||||
|
||||
let mut aes = Aes128::default();
|
||||
|
||||
aes.set_key(key_ref);
|
||||
aes.set_iv(iv_ref);
|
||||
|
||||
aes
|
||||
}
|
||||
|
||||
fn setup_block(key: [u8; 16], vm: &mut dyn Vm<Binary>) -> Aes128 {
|
||||
let key_ref: Array<U8, 16> = vm.alloc().unwrap();
|
||||
vm.mark_public(key_ref).unwrap();
|
||||
vm.assign(key_ref, key).unwrap();
|
||||
vm.commit(key_ref).unwrap();
|
||||
|
||||
let mut aes = Aes128::default();
|
||||
aes.set_key(key_ref);
|
||||
|
||||
aes
|
||||
}
|
||||
|
||||
fn aes_apply_keystream(
|
||||
key: [u8; 16],
|
||||
iv: [u8; 4],
|
||||
explicit_nonce: [u8; 8],
|
||||
start_ctr: usize,
|
||||
msg: Vec<u8>,
|
||||
) -> Vec<u8> {
|
||||
use ::cipher::{KeyIvInit, StreamCipher, StreamCipherSeek};
|
||||
use aes::Aes128;
|
||||
use ctr::Ctr32BE;
|
||||
|
||||
let mut full_iv = [0u8; 16];
|
||||
full_iv[0..4].copy_from_slice(&iv);
|
||||
full_iv[4..12].copy_from_slice(&explicit_nonce);
|
||||
|
||||
let mut cipher = Ctr32BE::<Aes128>::new(&key.into(), &full_iv.into());
|
||||
let mut out = msg.clone();
|
||||
|
||||
cipher
|
||||
.try_seek(start_ctr * 16)
|
||||
.expect("start counter is less than keystream length");
|
||||
cipher.apply_keystream(&mut out);
|
||||
|
||||
out
|
||||
}
|
||||
|
||||
fn aes128(key: [u8; 16], msg: [u8; 16]) -> [u8; 16] {
|
||||
use ::aes::Aes128 as TestAes128;
|
||||
use ::cipher::{BlockEncrypt, KeyInit};
|
||||
|
||||
let mut msg = msg.into();
|
||||
let cipher = TestAes128::new(&key.into());
|
||||
cipher.encrypt_block(&mut msg);
|
||||
msg.into()
|
||||
}
|
||||
}
|
||||
299
crates/components/cipher/src/lib.rs
Normal file
299
crates/components/cipher/src/lib.rs
Normal file
@@ -0,0 +1,299 @@
|
||||
//! This crate provides implementations of 2PC ciphers for encryption with a
|
||||
//! shared key.
|
||||
//!
|
||||
//! Both parties can work together to encrypt and decrypt messages with
|
||||
//! different visibility configurations. See [`Cipher`] and [`Keystream`] for
|
||||
//! more information on the interface.
|
||||
|
||||
#![deny(missing_docs, unreachable_pub, unused_must_use)]
|
||||
#![deny(clippy::all)]
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
pub mod aes;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use mpz_circuits::circuits::xor;
|
||||
use mpz_memory_core::{
|
||||
binary::{Binary, U8},
|
||||
MemoryExt, Repr, Slice, StaticSize, ToRaw, Vector,
|
||||
};
|
||||
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.
|
||||
///
|
||||
/// After setting `key` and `iv` allows to compute the keystream via
|
||||
/// [`Cipher::alloc`] or a single block in ECB mode via
|
||||
/// [`Cipher::assign_block`]. [`Keystream`] provides more tooling to compute the
|
||||
/// final cipher output in counter mode.
|
||||
#[async_trait]
|
||||
pub trait Cipher {
|
||||
/// The error type for the cipher.
|
||||
type Error: std::error::Error + Send + Sync + 'static;
|
||||
/// Cipher key.
|
||||
type Key;
|
||||
/// Cipher IV.
|
||||
type Iv;
|
||||
/// Cipher nonce.
|
||||
type Nonce;
|
||||
/// Cipher counter.
|
||||
type Counter;
|
||||
/// Cipher block.
|
||||
type Block;
|
||||
|
||||
/// Sets the key.
|
||||
fn set_key(&mut self, key: Self::Key);
|
||||
|
||||
/// Sets the initialization vector.
|
||||
fn set_iv(&mut self, iv: Self::Iv);
|
||||
|
||||
/// Returns the key reference.
|
||||
fn key(&self) -> Option<&Self::Key>;
|
||||
|
||||
/// Returns the iv reference.
|
||||
fn iv(&self) -> Option<&Self::Iv>;
|
||||
|
||||
/// Allocates a single block in ECB mode.
|
||||
fn alloc_block(
|
||||
&self,
|
||||
vm: &mut dyn Vm<Binary>,
|
||||
input: Self::Block,
|
||||
) -> Result<Self::Block, Self::Error>;
|
||||
|
||||
/// Allocates a single block in counter mode.
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn alloc_ctr_block(
|
||||
&self,
|
||||
vm: &mut dyn Vm<Binary>,
|
||||
) -> Result<CtrBlock<Self::Nonce, Self::Counter, Self::Block>, Self::Error>;
|
||||
|
||||
/// Allocates a keystream in counter mode.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `vm` - Virtual machine to allocate into.
|
||||
/// * `len` - Length of the stream in bytes.
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn alloc_keystream(
|
||||
&self,
|
||||
vm: &mut dyn Vm<Binary>,
|
||||
len: usize,
|
||||
) -> Result<Keystream<Self::Nonce, Self::Counter, Self::Block>, Self::Error>;
|
||||
}
|
||||
|
||||
/// A block in counter mode.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct CtrBlock<N, C, O> {
|
||||
/// Explicit nonce reference.
|
||||
pub explicit_nonce: N,
|
||||
/// Counter reference.
|
||||
pub counter: C,
|
||||
/// Output reference.
|
||||
pub output: O,
|
||||
}
|
||||
|
||||
/// The keystream of the cipher.
|
||||
///
|
||||
/// 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>>,
|
||||
}
|
||||
|
||||
impl<N, C, O> Default for Keystream<N, C, O> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
blocks: VecDeque::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<N, C, O> Keystream<N, C, O>
|
||||
where
|
||||
N: Repr<Binary> + StaticSize<Binary> + Copy,
|
||||
C: Repr<Binary> + StaticSize<Binary> + Copy,
|
||||
O: Repr<Binary> + StaticSize<Binary> + Copy,
|
||||
{
|
||||
/// Creates a new keystream from the provided blocks.
|
||||
pub fn new(blocks: &[CtrBlock<N, C, O>]) -> Self {
|
||||
Self {
|
||||
blocks: VecDeque::from_iter(blocks.iter().copied()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Consumes keystream material.
|
||||
///
|
||||
/// Returns the consumed keystream material, leaving the remaining material
|
||||
/// in place.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `len` - Length of the keystream in bytes to return.
|
||||
pub fn consume(&mut self, len: usize) -> Result<Self, CipherError> {
|
||||
let block_count = len.div_ceil(self.block_size());
|
||||
|
||||
if block_count > self.blocks.len() {
|
||||
return Err(CipherError::new("insufficient keystream"));
|
||||
}
|
||||
|
||||
let blocks = self.blocks.split_off(self.blocks.len() - block_count);
|
||||
|
||||
Ok(Self { blocks })
|
||||
}
|
||||
|
||||
/// Applies the keystream to the provided input.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `vm` - Virtual machine.
|
||||
/// * `input` - Input data.
|
||||
pub fn apply(
|
||||
&self,
|
||||
vm: &mut dyn Vm<Binary>,
|
||||
input: Vector<U8>,
|
||||
) -> Result<Vector<U8>, CipherError> {
|
||||
if input.len() != self.len() {
|
||||
return Err(CipherError::new("input length must match keystream length"));
|
||||
} else if self.blocks.is_empty() {
|
||||
return Err(CipherError::new("no keystream material available"));
|
||||
}
|
||||
|
||||
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 {
|
||||
let call = CallBuilder::new(xor.clone())
|
||||
.arg(block.output)
|
||||
.arg(
|
||||
input
|
||||
.get(pos..pos + self.block_size())
|
||||
.expect("input length was checked"),
|
||||
)
|
||||
.build()?;
|
||||
let output: Vector<U8> = vm.call(call).map_err(CipherError::new)?;
|
||||
outputs.push(output);
|
||||
pos += self.block_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,
|
||||
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() {
|
||||
return Err(CipherError::new("no keystream material available"));
|
||||
}
|
||||
|
||||
let block_count = len.div_ceil(self.block_size());
|
||||
if block_count != self.blocks.len() {
|
||||
return Err(CipherError::new("length does not match keystream length"));
|
||||
}
|
||||
|
||||
let mut keystream =
|
||||
flatten_blocks(vm, self.blocks.iter().map(|block| block.output.to_raw()))?;
|
||||
keystream.truncate(len);
|
||||
|
||||
Ok(keystream)
|
||||
}
|
||||
|
||||
/// Assigns the keystream inputs.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `vm` - Virtual machine.
|
||||
/// * `explicit_nonce` - Explicit nonce.
|
||||
/// * `ctr` - Counter function. The provided function will be called to
|
||||
/// assign the counter values for each block.
|
||||
pub fn assign(
|
||||
&self,
|
||||
vm: &mut dyn Vm<Binary>,
|
||||
explicit_nonce: N::Clear,
|
||||
mut ctr: impl FnMut() -> C::Clear,
|
||||
) -> Result<(), CipherError>
|
||||
where
|
||||
N::Clear: Copy,
|
||||
C::Clear: Copy,
|
||||
{
|
||||
for block in &self.blocks {
|
||||
vm.assign(block.explicit_nonce, explicit_nonce)
|
||||
.map_err(CipherError::new)?;
|
||||
vm.commit(block.explicit_nonce).map_err(CipherError::new)?;
|
||||
vm.assign(block.counter, ctr()).map_err(CipherError::new)?;
|
||||
vm.commit(block.counter).map_err(CipherError::new)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns the block size in bytes.
|
||||
fn block_size(&self) -> usize {
|
||||
O::SIZE / 8
|
||||
}
|
||||
|
||||
/// Returns the length of the keystream in bytes.
|
||||
fn len(&self) -> usize {
|
||||
self.block_size() * self.blocks.len()
|
||||
}
|
||||
}
|
||||
|
||||
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}")]
|
||||
pub struct CipherError {
|
||||
#[source]
|
||||
source: Box<dyn std::error::Error + Send + Sync>,
|
||||
}
|
||||
|
||||
impl CipherError {
|
||||
pub(crate) fn new<E>(source: E) -> Self
|
||||
where
|
||||
E: Into<Box<dyn std::error::Error + Send + Sync>>,
|
||||
{
|
||||
Self {
|
||||
source: source.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CallError> for CipherError {
|
||||
fn from(value: CallError) -> Self {
|
||||
Self::new(value)
|
||||
}
|
||||
}
|
||||
29
crates/components/deap/Cargo.toml
Normal file
29
crates/components/deap/Cargo.toml
Normal file
@@ -0,0 +1,29 @@
|
||||
[package]
|
||||
name = "tlsn-deap"
|
||||
version = "0.1.0-alpha.12"
|
||||
edition = "2021"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
mpz-core = { workspace = true }
|
||||
mpz-common = { workspace = true }
|
||||
mpz-vm-core = { workspace = true }
|
||||
rangeset = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serio = { workspace = true }
|
||||
async-trait = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
tokio = { workspace = true, features = ["sync"] }
|
||||
|
||||
[dev-dependencies]
|
||||
mpz-circuits = { workspace = true }
|
||||
mpz-garble = { workspace = true }
|
||||
mpz-ot = { workspace = true }
|
||||
mpz-zk = { workspace = true }
|
||||
|
||||
tokio = { workspace = true, features = ["macros", "rt", "rt-multi-thread"] }
|
||||
rand = { workspace = true }
|
||||
rand06-compat = { workspace = true }
|
||||
651
crates/components/deap/src/lib.rs
Normal file
651
crates/components/deap/src/lib.rs
Normal file
@@ -0,0 +1,651 @@
|
||||
//! Dual-execution with Asymmetric Privacy (DEAP) protocol.
|
||||
|
||||
#![deny(missing_docs, unreachable_pub, unused_must_use)]
|
||||
#![deny(clippy::all)]
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
mod map;
|
||||
|
||||
use std::{mem, sync::Arc};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use mpz_common::Context;
|
||||
use mpz_core::bitvec::BitVec;
|
||||
use mpz_vm_core::{
|
||||
memory::{binary::Binary, DecodeFuture, Memory, Repr, Slice, View},
|
||||
Call, Callable, Execute, Vm, VmError,
|
||||
};
|
||||
use rangeset::{Difference, RangeSet, UnionMut};
|
||||
use tokio::sync::{Mutex, MutexGuard, OwnedMutexGuard};
|
||||
|
||||
type Error = DeapError;
|
||||
|
||||
/// The role of the DEAP VM.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[allow(missing_docs)]
|
||||
pub enum Role {
|
||||
Leader,
|
||||
Follower,
|
||||
}
|
||||
|
||||
/// DEAP VM.
|
||||
#[derive(Debug)]
|
||||
pub struct Deap<Mpc, Zk> {
|
||||
role: Role,
|
||||
mpc: Arc<Mutex<Mpc>>,
|
||||
zk: Arc<Mutex<Zk>>,
|
||||
/// 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>)>,
|
||||
}
|
||||
|
||||
impl<Mpc, Zk> Deap<Mpc, Zk> {
|
||||
/// Creates a new DEAP VM.
|
||||
pub fn new(role: Role, mpc: Mpc, zk: Zk) -> Self {
|
||||
Self {
|
||||
role,
|
||||
mpc: Arc::new(Mutex::new(mpc)),
|
||||
zk: Arc::new(Mutex::new(zk)),
|
||||
memory_map: map::MemoryMap::default(),
|
||||
follower_input_ranges: RangeSet::default(),
|
||||
follower_inputs: Vec::default(),
|
||||
outputs: Vec::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the MPC and ZK VMs.
|
||||
pub fn into_inner(self) -> (Mpc, Zk) {
|
||||
(
|
||||
Arc::into_inner(self.mpc).unwrap().into_inner(),
|
||||
Arc::into_inner(self.zk).unwrap().into_inner(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the ZK VM.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the mutex is locked by another thread.
|
||||
pub fn zk(&self) -> MutexGuard<'_, Zk> {
|
||||
self.zk.try_lock().unwrap()
|
||||
}
|
||||
|
||||
/// Returns an owned mutex guard to the ZK VM.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the mutex is locked by another thread.
|
||||
pub fn zk_owned(&self) -> OwnedMutexGuard<Zk> {
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
impl<Mpc, Zk> Deap<Mpc, Zk>
|
||||
where
|
||||
Mpc: Vm<Binary> + Send + 'static,
|
||||
Zk: Vm<Binary> + Send + 'static,
|
||||
{
|
||||
/// Finalizes the DEAP VM.
|
||||
///
|
||||
/// This reveals all private inputs of the follower.
|
||||
pub async fn finalize(&mut self, ctx: &mut Context) -> Result<(), VmError> {
|
||||
let mut mpc = self.mpc.try_lock().unwrap();
|
||||
let mut zk = self.zk.try_lock().unwrap();
|
||||
|
||||
// Decode the private inputs of the follower.
|
||||
//
|
||||
// # Security
|
||||
//
|
||||
// This assumes that the decoding process is authenticated from the leader's
|
||||
// perspective. In the case of garbled circuits, the leader should be the
|
||||
// generator such that the follower proves their inputs using their committed
|
||||
// MACs.
|
||||
let input_futs = self
|
||||
.follower_inputs
|
||||
.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) {
|
||||
let input = self.memory_map.try_get(input)?;
|
||||
|
||||
// Follower has already assigned the inputs.
|
||||
if let Role::Leader = self.role {
|
||||
let value = decode
|
||||
.try_recv()
|
||||
.map_err(VmError::memory)?
|
||||
.expect("input should be decoded");
|
||||
zk.assign_raw(input, value)?;
|
||||
}
|
||||
|
||||
// Now the follower's inputs are public.
|
||||
zk.commit_raw(input)?;
|
||||
}
|
||||
|
||||
zk.execute_all(ctx).await.map_err(VmError::execute)?;
|
||||
|
||||
// Follower verifies the outputs are consistent.
|
||||
if let Role::Follower = self.role {
|
||||
for (output, mut value) in mem::take(&mut self.outputs) {
|
||||
// If the output is not available in the MPC VM, we did not execute and decode
|
||||
// it. Therefore, we do not need to check for equality.
|
||||
//
|
||||
// This can occur if some function was preprocessed but ultimately not used.
|
||||
if let Some(mpc_output) = mpc.get_raw(output)? {
|
||||
let zk_output = value
|
||||
.try_recv()
|
||||
.map_err(VmError::memory)?
|
||||
.expect("output should be decoded");
|
||||
|
||||
// Asserts equality of all the output values from both VMs.
|
||||
if zk_output != mpc_output {
|
||||
return Err(VmError::execute(Error::from(ErrorRepr::EqualityCheck)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<Mpc, Zk> Memory<Binary> for Deap<Mpc, Zk>
|
||||
where
|
||||
Mpc: Memory<Binary, Error = VmError>,
|
||||
Zk: Memory<Binary, Error = VmError>,
|
||||
{
|
||||
type Error = VmError;
|
||||
|
||||
fn is_alloc_raw(&self, slice: Slice) -> bool {
|
||||
self.mpc.try_lock().unwrap().is_alloc_raw(slice)
|
||||
}
|
||||
|
||||
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.mpc
|
||||
.try_lock()
|
||||
.unwrap()
|
||||
.assign_raw(slice, data.clone())?;
|
||||
|
||||
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_input_ranges);
|
||||
let mut zk = self.zk.try_lock().unwrap();
|
||||
for input in input_minus_follower.iter_ranges() {
|
||||
zk.commit_raw(
|
||||
self.memory_map
|
||||
.try_get(Slice::from_range_unchecked(input))?,
|
||||
)?;
|
||||
}
|
||||
|
||||
self.mpc.try_lock().unwrap().commit_raw(slice)
|
||||
}
|
||||
|
||||
fn get_raw(&self, slice: Slice) -> Result<Option<BitVec>, VmError> {
|
||||
self.mpc.try_lock().unwrap().get_raw(slice)
|
||||
}
|
||||
|
||||
fn decode_raw(&mut self, slice: Slice) -> Result<DecodeFuture<BitVec>, VmError> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Mpc, Zk> View<Binary> for Deap<Mpc, Zk>
|
||||
where
|
||||
Mpc: View<Binary, Error = VmError>,
|
||||
Zk: View<Binary, Error = VmError>,
|
||||
{
|
||||
type Error = VmError;
|
||||
|
||||
fn mark_public_raw(&mut self, slice: Slice) -> Result<(), VmError> {
|
||||
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> {
|
||||
let mut zk = self.zk.try_lock().unwrap();
|
||||
let mut mpc = self.mpc.try_lock().unwrap();
|
||||
match self.role {
|
||||
Role::Leader => {
|
||||
mpc.mark_private_raw(slice)?;
|
||||
zk.mark_private_raw(self.memory_map.try_get(slice)?)?;
|
||||
}
|
||||
Role::Follower => {
|
||||
mpc.mark_private_raw(slice)?;
|
||||
// Follower's private inputs will become public during finalization.
|
||||
zk.mark_public_raw(self.memory_map.try_get(slice)?)?;
|
||||
self.follower_input_ranges.union_mut(&slice.to_range());
|
||||
self.follower_inputs.push(slice);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn mark_blind_raw(&mut self, slice: Slice) -> Result<(), VmError> {
|
||||
let mut zk = self.zk.try_lock().unwrap();
|
||||
let mut mpc = self.mpc.try_lock().unwrap();
|
||||
match self.role {
|
||||
Role::Leader => {
|
||||
mpc.mark_blind_raw(slice)?;
|
||||
// Follower's private inputs will become public during finalization.
|
||||
zk.mark_public_raw(self.memory_map.try_get(slice)?)?;
|
||||
self.follower_input_ranges.union_mut(&slice.to_range());
|
||||
self.follower_inputs.push(slice);
|
||||
}
|
||||
Role::Follower => {
|
||||
mpc.mark_blind_raw(slice)?;
|
||||
zk.mark_blind_raw(self.memory_map.try_get(slice)?)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<Mpc, Zk> Callable<Binary> for Deap<Mpc, Zk>
|
||||
where
|
||||
Mpc: Vm<Binary>,
|
||||
Zk: Vm<Binary>,
|
||||
{
|
||||
fn call_raw(&mut self, call: Call) -> Result<Slice, VmError> {
|
||||
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)?);
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<Mpc, Zk> Execute for Deap<Mpc, Zk>
|
||||
where
|
||||
Mpc: Execute + Send + 'static,
|
||||
Zk: Execute + Send + 'static,
|
||||
{
|
||||
fn wants_flush(&self) -> bool {
|
||||
self.mpc.try_lock().unwrap().wants_flush() || self.zk.try_lock().unwrap().wants_flush()
|
||||
}
|
||||
|
||||
async fn flush(&mut self, ctx: &mut Context) -> Result<(), VmError> {
|
||||
let mut zk = self.zk.clone().try_lock_owned().unwrap();
|
||||
let mut mpc = self.mpc.clone().try_lock_owned().unwrap();
|
||||
ctx.try_join(
|
||||
async move |ctx| zk.flush(ctx).await,
|
||||
async move |ctx| mpc.flush(ctx).await,
|
||||
)
|
||||
.await
|
||||
.map_err(VmError::execute)??;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn wants_preprocess(&self) -> bool {
|
||||
self.mpc.try_lock().unwrap().wants_preprocess()
|
||||
|| self.zk.try_lock().unwrap().wants_preprocess()
|
||||
}
|
||||
|
||||
async fn preprocess(&mut self, ctx: &mut Context) -> Result<(), VmError> {
|
||||
let mut zk = self.zk.clone().try_lock_owned().unwrap();
|
||||
let mut mpc = self.mpc.clone().try_lock_owned().unwrap();
|
||||
ctx.try_join(
|
||||
async move |ctx| zk.preprocess(ctx).await,
|
||||
async move |ctx| mpc.preprocess(ctx).await,
|
||||
)
|
||||
.await
|
||||
.map_err(VmError::execute)??;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn wants_execute(&self) -> bool {
|
||||
self.mpc.try_lock().unwrap().wants_execute()
|
||||
}
|
||||
|
||||
async fn execute(&mut self, ctx: &mut Context) -> Result<(), VmError> {
|
||||
// Only MPC VM is executed until finalization.
|
||||
self.mpc.try_lock().unwrap().execute(ctx).await
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[error(transparent)]
|
||||
pub(crate) struct DeapError(#[from] ErrorRepr);
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
enum ErrorRepr {
|
||||
#[error("equality check failed")]
|
||||
EqualityCheck,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use mpz_circuits::circuits::AES128;
|
||||
use mpz_common::context::test_st_context;
|
||||
use mpz_core::Block;
|
||||
use mpz_garble::protocol::semihonest::{Evaluator, Garbler};
|
||||
use mpz_ot::ideal::{cot::ideal_cot, rcot::ideal_rcot};
|
||||
use mpz_vm_core::{
|
||||
memory::{binary::U8, correlated::Delta, Array},
|
||||
prelude::*,
|
||||
};
|
||||
use mpz_zk::{Prover, Verifier};
|
||||
use rand::{rngs::StdRng, SeedableRng};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_deap() {
|
||||
let mut rng = StdRng::seed_from_u64(0);
|
||||
let delta_mpc = Delta::random(&mut rng);
|
||||
let delta_zk = Delta::random(&mut rng);
|
||||
|
||||
let (mut ctx_a, mut ctx_b) = test_st_context(8);
|
||||
let (rcot_send, rcot_recv) = ideal_rcot(Block::ZERO, delta_zk.into_inner());
|
||||
let (cot_send, cot_recv) = ideal_cot(delta_mpc.into_inner());
|
||||
|
||||
let gb = Garbler::new(cot_send, [0u8; 16], delta_mpc);
|
||||
let ev = Evaluator::new(cot_recv);
|
||||
let prover = Prover::new(rcot_recv);
|
||||
let verifier = Verifier::new(delta_zk, rcot_send);
|
||||
|
||||
let mut leader = Deap::new(Role::Leader, gb, prover);
|
||||
let mut follower = Deap::new(Role::Follower, ev, verifier);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_deap_desync_memory() {
|
||||
let mut rng = StdRng::seed_from_u64(0);
|
||||
let delta_mpc = Delta::random(&mut rng);
|
||||
let delta_zk = Delta::random(&mut rng);
|
||||
|
||||
let (mut ctx_a, mut ctx_b) = test_st_context(8);
|
||||
let (rcot_send, rcot_recv) = ideal_rcot(Block::ZERO, delta_zk.into_inner());
|
||||
let (cot_send, cot_recv) = ideal_cot(delta_mpc.into_inner());
|
||||
|
||||
let gb = Garbler::new(cot_send, [0u8; 16], delta_mpc);
|
||||
let ev = Evaluator::new(cot_recv);
|
||||
let prover = Prover::new(rcot_recv);
|
||||
let verifier = Verifier::new(delta_zk, rcot_send);
|
||||
|
||||
let 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);
|
||||
let delta_zk = Delta::random(&mut rng);
|
||||
|
||||
let (mut ctx_a, mut ctx_b) = test_st_context(8);
|
||||
let (rcot_send, rcot_recv) = ideal_rcot(Block::ZERO, delta_zk.into_inner());
|
||||
let (cot_send, cot_recv) = ideal_cot(delta_mpc.into_inner());
|
||||
|
||||
let gb = Garbler::new(cot_send, [1u8; 16], delta_mpc);
|
||||
let ev = Evaluator::new(cot_recv);
|
||||
let prover = Prover::new(rcot_recv);
|
||||
let verifier = Verifier::new(delta_zk, rcot_send);
|
||||
|
||||
let mut leader = Deap::new(Role::Leader, gb, prover);
|
||||
let mut follower = Deap::new(Role::Follower, ev, verifier);
|
||||
|
||||
let (_, follower_res) = 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();
|
||||
|
||||
// Use different inputs in each VM.
|
||||
leader.mpc().assign(key, [42u8; 16]).unwrap();
|
||||
leader
|
||||
.zk
|
||||
.try_lock()
|
||||
.unwrap()
|
||||
.assign(key, [69u8; 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();
|
||||
drop(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
|
||||
}
|
||||
);
|
||||
|
||||
assert!(follower_res.is_err());
|
||||
}
|
||||
}
|
||||
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.8-pre"
|
||||
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,35 +5,35 @@ 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.8-pre"
|
||||
version = "0.1.0-alpha.12"
|
||||
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-garble = { workspace = true }
|
||||
mpz-vm-core = { workspace = true }
|
||||
mpz-core = { workspace = true }
|
||||
mpz-circuits = { workspace = true }
|
||||
mpz-common = { workspace = true }
|
||||
mpz-hash = { workspace = true }
|
||||
|
||||
async-trait = { workspace = true }
|
||||
derive_builder = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
sha2 = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = { workspace = true, features = ["async_tokio"] }
|
||||
mpz-common = { workspace = true, features = ["test-utils"] }
|
||||
mpz-ot = { workspace = true, features = ["ideal"] }
|
||||
mpz-garble = { workspace = true }
|
||||
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 }
|
||||
hex = { workspace = true }
|
||||
ring = { workspace = true }
|
||||
|
||||
[[bench]]
|
||||
name = "prf"
|
||||
|
||||
@@ -1,9 +1,17 @@
|
||||
#![allow(clippy::let_underscore_future)]
|
||||
|
||||
use criterion::{criterion_group, criterion_main, Criterion};
|
||||
|
||||
use hmac_sha256::{MpcPrf, Prf, PrfConfig, Role};
|
||||
use mpz_common::executor::test_mt_executor;
|
||||
use mpz_garble::{config::Role as DEAPRole, protocol::deap::DEAPThread, Memory};
|
||||
use mpz_ot::ideal::ot::ideal_ot;
|
||||
use hmac_sha256::{Mode, MpcPrf};
|
||||
use mpz_common::context::test_mt_context;
|
||||
use mpz_garble::protocol::semihonest::{Evaluator, Garbler};
|
||||
use mpz_ot::ideal::cot::ideal_cot;
|
||||
use mpz_vm_core::{
|
||||
memory::{binary::U8, correlated::Delta, Array},
|
||||
prelude::*,
|
||||
Execute,
|
||||
};
|
||||
use rand::{rngs::StdRng, SeedableRng};
|
||||
|
||||
#[allow(clippy::unit_arg)]
|
||||
fn criterion_benchmark(c: &mut Criterion) {
|
||||
@@ -11,178 +19,127 @@ fn criterion_benchmark(c: &mut Criterion) {
|
||||
group.sample_size(10);
|
||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||
|
||||
group.bench_function("prf_preprocess", |b| b.to_async(&rt).iter(preprocess));
|
||||
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 preprocess() {
|
||||
let (mut leader_exec, mut follower_exec) = test_mt_executor(128);
|
||||
|
||||
let (leader_ot_send_0, follower_ot_recv_0) = ideal_ot();
|
||||
let (follower_ot_send_0, leader_ot_recv_0) = ideal_ot();
|
||||
let (leader_ot_send_1, follower_ot_recv_1) = ideal_ot();
|
||||
let (follower_ot_send_1, leader_ot_recv_1) = ideal_ot();
|
||||
|
||||
let leader_thread_0 = DEAPThread::new(
|
||||
DEAPRole::Leader,
|
||||
[0u8; 32],
|
||||
leader_exec.new_thread().await.unwrap(),
|
||||
leader_ot_send_0,
|
||||
leader_ot_recv_0,
|
||||
);
|
||||
let leader_thread_1 = leader_thread_0
|
||||
.new_thread(
|
||||
leader_exec.new_thread().await.unwrap(),
|
||||
leader_ot_send_1,
|
||||
leader_ot_recv_1,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let follower_thread_0 = DEAPThread::new(
|
||||
DEAPRole::Follower,
|
||||
[0u8; 32],
|
||||
follower_exec.new_thread().await.unwrap(),
|
||||
follower_ot_send_0,
|
||||
follower_ot_recv_0,
|
||||
);
|
||||
let follower_thread_1 = follower_thread_0
|
||||
.new_thread(
|
||||
follower_exec.new_thread().await.unwrap(),
|
||||
follower_ot_send_1,
|
||||
follower_ot_recv_1,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let leader_pms = leader_thread_0.new_public_input::<[u8; 32]>("pms").unwrap();
|
||||
let follower_pms = follower_thread_0
|
||||
.new_public_input::<[u8; 32]>("pms")
|
||||
.unwrap();
|
||||
|
||||
let mut leader = MpcPrf::new(
|
||||
PrfConfig::builder().role(Role::Leader).build().unwrap(),
|
||||
leader_thread_0,
|
||||
leader_thread_1,
|
||||
);
|
||||
let mut follower = MpcPrf::new(
|
||||
PrfConfig::builder().role(Role::Follower).build().unwrap(),
|
||||
follower_thread_0,
|
||||
follower_thread_1,
|
||||
);
|
||||
|
||||
futures::join!(
|
||||
async {
|
||||
leader.setup(leader_pms).await.unwrap();
|
||||
leader.set_client_random(Some([0u8; 32])).await.unwrap();
|
||||
leader.preprocess().await.unwrap();
|
||||
},
|
||||
async {
|
||||
follower.setup(follower_pms).await.unwrap();
|
||||
follower.set_client_random(None).await.unwrap();
|
||||
follower.preprocess().await.unwrap();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
async fn prf() {
|
||||
let (mut leader_exec, mut follower_exec) = test_mt_executor(128);
|
||||
|
||||
let (leader_ot_send_0, follower_ot_recv_0) = ideal_ot();
|
||||
let (follower_ot_send_0, leader_ot_recv_0) = ideal_ot();
|
||||
let (leader_ot_send_1, follower_ot_recv_1) = ideal_ot();
|
||||
let (follower_ot_send_1, leader_ot_recv_1) = ideal_ot();
|
||||
|
||||
let leader_thread_0 = DEAPThread::new(
|
||||
DEAPRole::Leader,
|
||||
[0u8; 32],
|
||||
leader_exec.new_thread().await.unwrap(),
|
||||
leader_ot_send_0,
|
||||
leader_ot_recv_0,
|
||||
);
|
||||
let leader_thread_1 = leader_thread_0
|
||||
.new_thread(
|
||||
leader_exec.new_thread().await.unwrap(),
|
||||
leader_ot_send_1,
|
||||
leader_ot_recv_1,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let follower_thread_0 = DEAPThread::new(
|
||||
DEAPRole::Follower,
|
||||
[0u8; 32],
|
||||
follower_exec.new_thread().await.unwrap(),
|
||||
follower_ot_send_0,
|
||||
follower_ot_recv_0,
|
||||
);
|
||||
let follower_thread_1 = follower_thread_0
|
||||
.new_thread(
|
||||
follower_exec.new_thread().await.unwrap(),
|
||||
follower_ot_send_1,
|
||||
follower_ot_recv_1,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let leader_pms = leader_thread_0.new_public_input::<[u8; 32]>("pms").unwrap();
|
||||
let follower_pms = follower_thread_0
|
||||
.new_public_input::<[u8; 32]>("pms")
|
||||
.unwrap();
|
||||
|
||||
let mut leader = MpcPrf::new(
|
||||
PrfConfig::builder().role(Role::Leader).build().unwrap(),
|
||||
leader_thread_0,
|
||||
leader_thread_1,
|
||||
);
|
||||
let mut follower = MpcPrf::new(
|
||||
PrfConfig::builder().role(Role::Follower).build().unwrap(),
|
||||
follower_thread_0,
|
||||
follower_thread_1,
|
||||
);
|
||||
async fn prf(mode: Mode) {
|
||||
let mut rng = StdRng::seed_from_u64(0);
|
||||
|
||||
let pms = [42u8; 32];
|
||||
let client_random = [0u8; 32];
|
||||
let server_random = [1u8; 32];
|
||||
let cf_hs_hash = [2u8; 32];
|
||||
let sf_hs_hash = [3u8; 32];
|
||||
let client_random = [69u8; 32];
|
||||
let server_random: [u8; 32] = [96u8; 32];
|
||||
|
||||
futures::join!(
|
||||
async {
|
||||
leader.setup(leader_pms.clone()).await.unwrap();
|
||||
leader.set_client_random(Some(client_random)).await.unwrap();
|
||||
leader.preprocess().await.unwrap();
|
||||
},
|
||||
async {
|
||||
follower.setup(follower_pms.clone()).await.unwrap();
|
||||
follower.set_client_random(None).await.unwrap();
|
||||
follower.preprocess().await.unwrap();
|
||||
}
|
||||
);
|
||||
let (mut leader_exec, mut follower_exec) = test_mt_context(8);
|
||||
let mut leader_ctx = leader_exec.new_context().await.unwrap();
|
||||
let mut follower_ctx = follower_exec.new_context().await.unwrap();
|
||||
|
||||
leader.thread_mut().assign(&leader_pms, pms).unwrap();
|
||||
follower.thread_mut().assign(&follower_pms, pms).unwrap();
|
||||
let delta = Delta::random(&mut rng);
|
||||
let (ot_send, ot_recv) = ideal_cot(delta.into_inner());
|
||||
|
||||
let (_leader_keys, _follower_keys) = futures::try_join!(
|
||||
leader.compute_session_keys(server_random),
|
||||
follower.compute_session_keys(server_random)
|
||||
)
|
||||
.unwrap();
|
||||
let mut leader_vm = Garbler::new(ot_send, [0u8; 16], delta);
|
||||
let mut follower_vm = Evaluator::new(ot_recv);
|
||||
|
||||
let _ = futures::try_join!(
|
||||
leader.compute_client_finished_vd(cf_hs_hash),
|
||||
follower.compute_client_finished_vd(cf_hs_hash)
|
||||
)
|
||||
.unwrap();
|
||||
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 _ = futures::try_join!(
|
||||
leader.compute_server_finished_vd(sf_hs_hash),
|
||||
follower.compute_server_finished_vd(sf_hs_hash)
|
||||
)
|
||||
.unwrap();
|
||||
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();
|
||||
|
||||
futures::try_join!(
|
||||
leader.thread_mut().finalize(),
|
||||
follower.thread_mut().finalize()
|
||||
)
|
||||
.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(client_random).unwrap();
|
||||
follower.set_client_random(client_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)
|
||||
.unwrap();
|
||||
let _ = leader_vm
|
||||
.decode(leader_output.keys.server_write_key)
|
||||
.unwrap();
|
||||
let _ = leader_vm.decode(leader_output.keys.client_iv).unwrap();
|
||||
let _ = leader_vm.decode(leader_output.keys.server_iv).unwrap();
|
||||
|
||||
let _ = follower_vm
|
||||
.decode(follower_output.keys.client_write_key)
|
||||
.unwrap();
|
||||
let _ = follower_vm
|
||||
.decode(follower_output.keys.server_write_key)
|
||||
.unwrap();
|
||||
let _ = follower_vm.decode(follower_output.keys.client_iv).unwrap();
|
||||
let _ = follower_vm.decode(follower_output.keys.server_iv).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];
|
||||
|
||||
leader.set_cf_hash(cf_hs_hash).unwrap();
|
||||
follower.set_cf_hash(cf_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 _ = follower_vm.decode(follower_output.cf_vd).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();
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user