mirror of
https://github.com/tlsnotary/tlsn.git
synced 2026-01-09 13:27:59 -05:00
Compare commits
297 Commits
v0.1.0-alp
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1801c30599 | ||
|
|
0885d40ddf | ||
|
|
610411aae4 | ||
|
|
37df1baed7 | ||
|
|
aeaebc5c60 | ||
|
|
2e7e3db11d | ||
|
|
0a68837d0a | ||
|
|
0ec2392716 | ||
|
|
f99fce5b5a | ||
|
|
6b9f44e7e5 | ||
|
|
bf1cf2302a | ||
|
|
2884be17e0 | ||
|
|
df8d79c152 | ||
|
|
82d509266b | ||
|
|
d5ad768e7c | ||
|
|
d25fb320d4 | ||
|
|
0539268da7 | ||
|
|
427b2896b5 | ||
|
|
89d1e594d1 | ||
|
|
b4380f021e | ||
|
|
8a823d18ec | ||
|
|
7bcfc56bd8 | ||
|
|
2909d5ebaa | ||
|
|
7918494ccc | ||
|
|
92dd47b376 | ||
|
|
5474a748ce | ||
|
|
92da5adc24 | ||
|
|
e0ce1ad31a | ||
|
|
3b76877920 | ||
|
|
783355772a | ||
|
|
e5c59da90b | ||
|
|
f059c53c2d | ||
|
|
a1367b5428 | ||
|
|
9d8124ac9d | ||
|
|
5034366c72 | ||
|
|
afd8f44261 | ||
|
|
21086d2883 | ||
|
|
cca9a318a4 | ||
|
|
cb804a6025 | ||
|
|
9f849e7c18 | ||
|
|
389bceddef | ||
|
|
657838671a | ||
|
|
2f072b2578 | ||
|
|
33153d1124 | ||
|
|
2d399d5e24 | ||
|
|
b6d7249b6d | ||
|
|
2a8c1c3382 | ||
|
|
7c27162875 | ||
|
|
eef813712d | ||
|
|
2e94e08fa6 | ||
|
|
97d9475335 | ||
|
|
38820d6a3f | ||
|
|
af85fa100f | ||
|
|
008b901913 | ||
|
|
db85f68328 | ||
|
|
fb80aa4cc9 | ||
|
|
8dae57d6a7 | ||
|
|
f2ff4ba792 | ||
|
|
9bf3371873 | ||
|
|
9d853eb496 | ||
|
|
6923ceefd3 | ||
|
|
5239c2328a | ||
|
|
6a7c5384a9 | ||
|
|
7e469006c0 | ||
|
|
55091b5e94 | ||
|
|
bc1eba18c9 | ||
|
|
c128ab16ce | ||
|
|
a87125ff88 | ||
|
|
0933d711d2 | ||
|
|
79c230f2fa | ||
|
|
345d5d45ad | ||
|
|
55a26aad77 | ||
|
|
1132d441e1 | ||
|
|
fa2fdfd601 | ||
|
|
24e10d664f | ||
|
|
c0e084c1ca | ||
|
|
b6845dfc5c | ||
|
|
31def9ea81 | ||
|
|
878fe7e87d | ||
|
|
3348ac34b6 | ||
|
|
82767ca2d5 | ||
|
|
c9aaf2e0fa | ||
|
|
241ed3b5a3 | ||
|
|
56f088db7d | ||
|
|
f5250479bd | ||
|
|
0e2eabb833 | ||
|
|
ad530ca500 | ||
|
|
8b1cac6fe0 | ||
|
|
555f65e6b2 | ||
|
|
046485188c | ||
|
|
db53814ee7 | ||
|
|
d924bd6deb | ||
|
|
b3558bef9c | ||
|
|
33c4b9d16f | ||
|
|
edc2a1783d | ||
|
|
c2a6546deb | ||
|
|
2dfa386415 | ||
|
|
5a188e75c7 | ||
|
|
a8bf1026ca | ||
|
|
f900fc51cd | ||
|
|
6ccf102ec8 | ||
|
|
2c500b13bd | ||
|
|
2da0c242cb | ||
|
|
798c22409a | ||
|
|
3b5ac20d5b | ||
|
|
a063f8cc14 | ||
|
|
6f6b24e76c | ||
|
|
a28718923b | ||
|
|
19447aabe5 | ||
|
|
8afb7a4c11 | ||
|
|
43c6877ec0 | ||
|
|
39e14949a0 | ||
|
|
31f62982b5 | ||
|
|
6623734ca0 | ||
|
|
41e215f912 | ||
|
|
9e0f79125b | ||
|
|
7bdd3a724b | ||
|
|
baa486ccfd | ||
|
|
de7a47de5b | ||
|
|
3a57134b3a | ||
|
|
86fed1a90c | ||
|
|
82964c273b | ||
|
|
81aaa338e6 | ||
|
|
f331a7a3c5 | ||
|
|
adb407d03b | ||
|
|
3e54119867 | ||
|
|
71aa90de88 | ||
|
|
93535ca955 | ||
|
|
a34dd57926 | ||
|
|
92d7b59ee8 | ||
|
|
c8e9cb370e | ||
|
|
4dc5570a31 | ||
|
|
198e24c5e4 | ||
|
|
f16d7238e5 | ||
|
|
9253adaaa4 | ||
|
|
8c889ac498 | ||
|
|
f0e2200d22 | ||
|
|
224e41a186 | ||
|
|
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 | ||
|
|
c03418a642 | ||
|
|
7bec5a84ee | ||
|
|
85e0f5b467 | ||
|
|
cacca108ed | ||
|
|
c9592f44a1 | ||
|
|
e6be5e1cc9 | ||
|
|
d974fb71d5 | ||
|
|
c0c1c0caa1 | ||
|
|
7d88d1c20b | ||
|
|
c10c9155a7 | ||
|
|
faab999339 | ||
|
|
e6bc93c1f1 | ||
|
|
c6dc262a5e | ||
|
|
db90e28e44 | ||
|
|
30e4e37c0d | ||
|
|
6344410cad | ||
|
|
1d663596c1 | ||
|
|
2c045e5de7 | ||
|
|
38104bca1a | ||
|
|
99ba47c25d | ||
|
|
2042089132 | ||
|
|
504967d09a | ||
|
|
6e80d03ac7 | ||
|
|
b3f79a9e2b | ||
|
|
99e02fb388 | ||
|
|
6b845fd473 | ||
|
|
66db5344ac | ||
|
|
1d4c50f804 | ||
|
|
61ff3a8255 | ||
|
|
2ac9de1edd | ||
|
|
a7a8a83410 | ||
|
|
4d5102b6e1 | ||
|
|
0596a9a245 | ||
|
|
43d2c04f6f | ||
|
|
ca328fadca | ||
|
|
b724d6a1d2 | ||
|
|
dfc162929d | ||
|
|
6e20930283 | ||
|
|
7de49c8ed6 | ||
|
|
17476bc2cf | ||
|
|
b76b8314ad | ||
|
|
6ed3337739 | ||
|
|
3de203e8ac | ||
|
|
79c00fcedb | ||
|
|
e00828bd03 | ||
|
|
8f400bf1e2 | ||
|
|
53ff873b3a | ||
|
|
a4a0de02f9 | ||
|
|
80a9a61e9e | ||
|
|
67dc7c865d | ||
|
|
9bbb2fb66c | ||
|
|
32df1380a7 | ||
|
|
d179150c39 | ||
|
|
98a520ddd7 | ||
|
|
8fa593c111 | ||
|
|
0b1eef12f3 | ||
|
|
6eaf4a3d2d | ||
|
|
cc01b24759 | ||
|
|
0a3a1db520 | ||
|
|
b3316dede7 | ||
|
|
ab24a6d3aa | ||
|
|
17e31687bd | ||
|
|
b9ae8f9271 | ||
|
|
c8524d934b | ||
|
|
7f46596068 | ||
|
|
6031254963 | ||
|
|
2d44cc4b60 | ||
|
|
bdebd7a9b2 | ||
|
|
3201c38ad7 | ||
|
|
2205cb3b2c | ||
|
|
040608bb6e | ||
|
|
e14d0cf563 | ||
|
|
7377eaf661 | ||
|
|
b24aea4fad | ||
|
|
d8401ee177 | ||
|
|
44210c350b | ||
|
|
3554db83e1 | ||
|
|
2f675925d8 | ||
|
|
da0421dc6a | ||
|
|
cef1e3878a | ||
|
|
4fdce7f391 | ||
|
|
9828fbbd04 | ||
|
|
1741741945 | ||
|
|
aa189ca884 | ||
|
|
58dc575c4d | ||
|
|
bd4ff5f5b0 | ||
|
|
5698d22718 | ||
|
|
bce09b8d4c | ||
|
|
bd32c7bb67 | ||
|
|
d040341585 | ||
|
|
76969bb558 | ||
|
|
44930c0add | ||
|
|
173f59b04a | ||
|
|
d64dabbf27 | ||
|
|
c82cc4f0a3 | ||
|
|
c80b44049f | ||
|
|
bff2a5381d | ||
|
|
e19e445f7f | ||
|
|
474d22a7e3 | ||
|
|
23450a3d38 | ||
|
|
3506791df1 | ||
|
|
ea2a8fd80c | ||
|
|
c157c2d3ea | ||
|
|
dc0b887966 | ||
|
|
c431865aea | ||
|
|
9fc0d9162d | ||
|
|
f63a74efe8 | ||
|
|
f558d5bc44 | ||
|
|
68b9474015 | ||
|
|
b4334ad17d | ||
|
|
d53203e276 | ||
|
|
5c0a0309e9 | ||
|
|
a4c7760aec | ||
|
|
9e041b81e8 | ||
|
|
173945dd0a | ||
|
|
aa264a90cb | ||
|
|
3e29a5bfe5 | ||
|
|
9a081c6cbc | ||
|
|
19e9c50f35 | ||
|
|
d7bc0e5cae | ||
|
|
eec93101bc | ||
|
|
2372403d38 | ||
|
|
be24f58364 | ||
|
|
1e99db879a |
2
.dockerignore
Normal file
2
.dockerignore
Normal file
@@ -0,0 +1,2 @@
|
||||
/target
|
||||
/.git
|
||||
3
.github/codecov.yml
vendored
Normal file
3
.github/codecov.yml
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
github_checks:
|
||||
annotations: false
|
||||
comment: false
|
||||
13
.github/scripts/build-server.sh
vendored
13
.github/scripts/build-server.sh
vendored
@@ -1,13 +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
|
||||
|
||||
cd notary-server
|
||||
cargo build --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] --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 --deployment-group-name tlsn-$environment-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
|
||||
41
.github/workflows/bench.yml
vendored
Normal file
41
.github/workflows/bench.yml
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
name: Run Benchmarks (Native or Browser)
|
||||
on:
|
||||
# manual trigger
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
bench_type:
|
||||
description: "Specify the benchmark type (native or browser)"
|
||||
required: true
|
||||
default: "native"
|
||||
type: choice
|
||||
options:
|
||||
- native
|
||||
- browser
|
||||
|
||||
jobs:
|
||||
run-benchmarks:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Build Docker Image
|
||||
run: |
|
||||
docker build -t tlsn-bench . -f ./crates/harness/harness.Dockerfile
|
||||
|
||||
- name: Run Benchmarks
|
||||
run: |
|
||||
docker run --privileged -v ./crates/harness/:/benches tlsn-bench bash -c "runner setup; runner --target ${{ github.event.inputs.bench_type }} bench"
|
||||
|
||||
- name: Plot Benchmarks
|
||||
run: |
|
||||
docker run -v ./crates/harness/:/benches tlsn-bench bash -c "tlsn-harness-plot /benches/bench.toml /benches/metrics.csv --min-max-band --prover-kind ${{ github.event.inputs.bench_type }}"
|
||||
- name: Upload graphs
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: benchmark_graphs
|
||||
path: |
|
||||
./crates/harness/metrics.csv
|
||||
./crates/harness/bench.toml
|
||||
./crates/harness/runtime_vs_latency.html
|
||||
./crates/harness/runtime_vs_bandwidth.html
|
||||
87
.github/workflows/cd-server.yml
vendored
87
.github/workflows/cd-server.yml
vendored
@@ -1,87 +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 test workflow to succeed
|
||||
if: github.event_name == 'push'
|
||||
uses: lewagon/wait-on-check-action@v1.3.1
|
||||
with:
|
||||
ref: ${{ github.ref }}
|
||||
# Have to be specify '(notary-server)', as we are using matrix for build_and_test job in ci.yml, else it will fail, more details [here](https://github.com/lewagon/wait-on-check-action#check-name)
|
||||
check-name: 'Build and test (notary-server)'
|
||||
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
|
||||
components: clippy
|
||||
|
||||
- name: Use caching
|
||||
uses: Swatinem/rust-cache@v2.5.0
|
||||
with:
|
||||
workspaces: ${{ matrix.package }} -> target
|
||||
|
||||
- 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
|
||||
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 test workflow to succeed
|
||||
uses: lewagon/wait-on-check-action@v1.3.1
|
||||
with:
|
||||
ref: ${{ github.ref }}
|
||||
# Have to be specify '(notary-server)', as we are using matrix for build_and_test job in ci.yml, else it will fail, more details [here](https://github.com/lewagon/wait-on-check-action#check-name)
|
||||
check-name: 'Build and test (notary-server)'
|
||||
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@v3
|
||||
|
||||
- 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: ./notary-server/notary-server.Dockerfile
|
||||
219
.github/workflows/ci.yml
vendored
219
.github/workflows/ci.yml
vendored
@@ -7,88 +7,195 @@ on:
|
||||
tags:
|
||||
- "[v]?[0-9]+.[0-9]+.[0-9]+*"
|
||||
pull_request:
|
||||
branches:
|
||||
- dev
|
||||
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: read
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse
|
||||
# We need a higher number of parallel rayon tasks than the default (which is 4)
|
||||
# in order to prevent a deadlock, c.f.
|
||||
# - https://github.com/tlsnotary/tlsn/issues/548
|
||||
# - https://github.com/privacy-ethereum/mpz/issues/178
|
||||
# 32 seems to be big enough for the foreseeable future
|
||||
RAYON_NUM_THREADS: 32
|
||||
RUST_VERSION: 1.90.0
|
||||
|
||||
jobs:
|
||||
build_and_test:
|
||||
name: Build and test
|
||||
if: ( ! github.event.pull_request.draft )
|
||||
clippy:
|
||||
name: Clippy
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
package:
|
||||
- components/integration-tests
|
||||
- components/uid-mux
|
||||
- components/cipher
|
||||
- components/universal-hash
|
||||
- components/aead
|
||||
- components/key-exchange
|
||||
- components/point-addition
|
||||
- components/prf
|
||||
- components/tls
|
||||
- tlsn
|
||||
- notary-server
|
||||
include:
|
||||
- package: components/integration-tests
|
||||
release: true
|
||||
- package: notary-server
|
||||
release: true
|
||||
- package: tlsn
|
||||
all-features: true
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ${{ matrix.package }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install rust toolchain
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
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
|
||||
|
||||
fmt:
|
||||
name: Check formatting
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# We use nightly to support `imports_granularity` feature
|
||||
- name: Install nightly rust toolchain with rustfmt
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: nightly
|
||||
components: rustfmt
|
||||
|
||||
- name: "Check formatting"
|
||||
- name: Use caching
|
||||
uses: Swatinem/rust-cache@v2.7.7
|
||||
|
||||
- name: Check formatting
|
||||
run: cargo +nightly fmt --check --all
|
||||
|
||||
- name: Install stable rust toolchain
|
||||
build-and-test:
|
||||
name: Build and test
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install rust toolchain
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: stable
|
||||
components: clippy
|
||||
|
||||
- name: "Clippy"
|
||||
run: cargo clippy --all-features --examples -- -D warnings
|
||||
toolchain: ${{ env.RUST_VERSION }}
|
||||
|
||||
- name: Use caching
|
||||
uses: Swatinem/rust-cache@v2.5.0
|
||||
uses: Swatinem/rust-cache@v2.7.7
|
||||
|
||||
- name: Build
|
||||
run: cargo build --all-targets --locked
|
||||
|
||||
- name: Test
|
||||
run: cargo test --no-fail-fast --locked
|
||||
|
||||
wasm:
|
||||
name: Build and Test wasm
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install rust toolchain
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
workspaces: ${{ matrix.package }} -> target
|
||||
targets: wasm32-unknown-unknown
|
||||
toolchain: ${{ env.RUST_VERSION }}
|
||||
|
||||
- name: "Build"
|
||||
run: cargo build ${{ matrix.release && '--release' }}
|
||||
- name: Install nightly rust toolchain
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
targets: wasm32-unknown-unknown,x86_64-unknown-linux-gnu
|
||||
toolchain: nightly
|
||||
components: rust-src
|
||||
|
||||
- name: "Test"
|
||||
if: ${{ matrix.release != true }}
|
||||
run: cargo test --lib --bins --tests --examples --workspace
|
||||
- name: Install chromedriver
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y chromium-chromedriver
|
||||
|
||||
- name: "Test all features"
|
||||
if: ${{ matrix.release != true && matrix.all-features == true }}
|
||||
run: cargo test --lib --bins --tests --examples --workspace --all-features
|
||||
- name: Install wasm-pack
|
||||
# we install a specific version which supports custom profiles
|
||||
run: cargo install --git https://github.com/rustwasm/wasm-pack.git --rev 32e52ca
|
||||
|
||||
- name: "Integration Test"
|
||||
if: ${{ matrix.release == true }}
|
||||
run: cargo test --release --tests
|
||||
- name: Use caching
|
||||
uses: Swatinem/rust-cache@v2.7.7
|
||||
|
||||
- name: "Integration Test all features"
|
||||
if: ${{ matrix.release == true && matrix.all-features == true }}
|
||||
run: cargo test --release --tests --all-features
|
||||
- name: Build harness
|
||||
working-directory: crates/harness
|
||||
run: ./build.sh
|
||||
|
||||
- name: "Check that benches compile"
|
||||
run: cargo bench --no-run
|
||||
- name: Run tests
|
||||
working-directory: crates/harness
|
||||
run: |
|
||||
./bin/runner setup
|
||||
./bin/runner --target browser test
|
||||
|
||||
- name: Run build
|
||||
working-directory: crates/wasm
|
||||
run: ./build.sh
|
||||
|
||||
- name: Dry Run NPM Publish
|
||||
working-directory: crates/wasm/pkg
|
||||
run: npm publish --dry-run
|
||||
|
||||
- name: Save tlsn-wasm package for tagged builds
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ github.ref_name }}-tlsn-wasm-pkg
|
||||
path: ./crates/wasm/pkg
|
||||
if-no-files-found: error
|
||||
|
||||
tests-integration:
|
||||
name: Run tests release build
|
||||
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 }}
|
||||
|
||||
- name: Use caching
|
||||
uses: Swatinem/rust-cache@v2.7.7
|
||||
|
||||
- name: Run integration tests
|
||||
run: cargo test --locked --profile tests-integration --workspace --exclude tlsn-tls-client --exclude tlsn-tls-core --no-fail-fast -- --include-ignored
|
||||
|
||||
coverage:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install rust toolchain
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
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 --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
|
||||
|
||||
create-release-draft:
|
||||
name: Create Release Draft
|
||||
needs: build-and-test
|
||||
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
|
||||
|
||||
24
.github/workflows/rebase.yml
vendored
Normal file
24
.github/workflows/rebase.yml
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
name: Automatic Rebase
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created]
|
||||
jobs:
|
||||
rebase:
|
||||
name: Rebase
|
||||
runs-on: ubuntu-latest
|
||||
if: >-
|
||||
github.event.issue.pull_request != '' &&
|
||||
contains(github.event.comment.body, '/rebase') &&
|
||||
github.event.comment.author_association == 'MEMBER'
|
||||
steps:
|
||||
- name: Checkout the latest code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
fetch-depth: 0 # otherwise, you will fail to push refs to dest repo
|
||||
- name: Automatic Rebase
|
||||
uses: cirrus-actions/rebase@1.8
|
||||
with:
|
||||
autosquash: false
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
62
.github/workflows/releng.yml
vendored
Normal file
62
.github/workflows/releng.yml
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
name: Publish tlsn-wasm to NPM
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: 'Tag to publish to NPM'
|
||||
required: true
|
||||
default: 'v0.1.0-alpha.13'
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
|
||||
steps:
|
||||
- name: Find and download tlsn-wasm build from the tagged ci workflow
|
||||
id: find_run
|
||||
run: |
|
||||
# Find the workflow run ID for the tag
|
||||
RUN_ID=$(gh api \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
"/repos/tlsnotary/tlsn/actions/workflows/ci.yml/runs?per_page=100" \
|
||||
--jq '.workflow_runs[] | select(.head_branch == "${{ github.event.inputs.tag }}") | .id' | sort | tail -1)
|
||||
|
||||
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
|
||||
14
.github/workflows/rustdoc.yml
vendored
14
.github/workflows/rustdoc.yml
vendored
@@ -4,7 +4,6 @@ on:
|
||||
push:
|
||||
branches: [dev]
|
||||
pull_request:
|
||||
branches: [dev]
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
@@ -12,10 +11,9 @@ env:
|
||||
|
||||
jobs:
|
||||
rustdoc:
|
||||
if: ( ! github.event.pull_request.draft )
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install Rust Toolchain (Stable)
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
@@ -23,18 +21,12 @@ jobs:
|
||||
toolchain: stable
|
||||
|
||||
- name: "rustdoc"
|
||||
run: cd tlsn; cargo doc -p tlsn-core -p tlsn-prover -p tlsn-verifier --no-deps --all-features
|
||||
# --target-dir ${GITHUB_WORKSPACE}/docs
|
||||
|
||||
# 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\">" > tlsn/target/doc/index.html
|
||||
run: crates/wasm/build-docs.sh
|
||||
|
||||
- name: Deploy
|
||||
uses: peaceiris/actions-gh-pages@v3
|
||||
if: ${{ github.ref == 'refs/heads/dev' }}
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
publish_dir: tlsn/target/doc/
|
||||
publish_dir: target/wasm32-unknown-unknown/doc/
|
||||
# cname: rustdocs.tlsnotary.org
|
||||
|
||||
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
|
||||
47
.github/workflows/wasm.yml
vendored
47
.github/workflows/wasm.yml
vendored
@@ -1,47 +0,0 @@
|
||||
name: wasm-build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
tags:
|
||||
- "[v]?[0-9]+.[0-9]+.[0-9]+*"
|
||||
pull_request:
|
||||
branches:
|
||||
- dev
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse
|
||||
|
||||
jobs:
|
||||
build_and_test:
|
||||
name: Build for target wasm32-unknown-unknown
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
package:
|
||||
- tlsn/tlsn-core
|
||||
- tlsn/tlsn-prover
|
||||
- components/tls/tls-client
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ${{ matrix.package }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install stable rust toolchain
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
targets: wasm32-unknown-unknown
|
||||
toolchain: stable
|
||||
|
||||
- name: Use caching
|
||||
uses: Swatinem/rust-cache@v2.5.0
|
||||
with:
|
||||
workspaces: ${{ matrix.package }} -> ../target
|
||||
|
||||
- name: "Build"
|
||||
run: cargo build --target wasm32-unknown-unknown
|
||||
7
.gitignore
vendored
7
.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
|
||||
|
||||
@@ -30,3 +26,6 @@ Cargo.lock
|
||||
|
||||
# logs
|
||||
*.log
|
||||
|
||||
# metrics
|
||||
*.csv
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
# Changelog
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [Unreleased]
|
||||
@@ -16,27 +16,22 @@ keywords.
|
||||
|
||||
Try to do one pull request per change.
|
||||
|
||||
### Updating the changelog
|
||||
**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.
|
||||
|
||||
Update the changes you have made in
|
||||
[CHANGELOG](CHANGELOG.md)
|
||||
file under the **Unreleased** section.
|
||||
## Linting
|
||||
|
||||
Add the changes of your pull request to one of the following subsections,
|
||||
depending on the types of changes defined by
|
||||
[Keep a changelog](https://keepachangelog.com/en/1.0.0/):
|
||||
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:
|
||||
|
||||
- `Added` for new features.
|
||||
- `Changed` for changes in existing functionality.
|
||||
- `Deprecated` for soon-to-be removed features.
|
||||
- `Removed` for now removed features.
|
||||
- `Fixed` for any bug fixes.
|
||||
- `Security` in case of vulnerabilities.
|
||||
```sh
|
||||
cargo clippy --all-features --all-targets -- -D warnings
|
||||
```
|
||||
|
||||
If the required subsection does not exist yet under **Unreleased**, create it!
|
||||
This command will lint your code with all features and targets enabled, and treat any warnings as errors, ensuring that your code meets the required standards.
|
||||
|
||||
## Style
|
||||
|
||||
This repository includes a `rustfmt.toml` file with custom formatting settings that are automatically validated by CI before any Pull Requests (PRs) can be merged. To ensure your code adheres to these standards, format your code using this configuration before submitting a PR. We strongly recommend enabling *auto format on save* to streamline this process. In Visual Studio Code (VSCode), you can enable this feature by turning on [`editor.formatOnSave`](https://code.visualstudio.com/docs/editor/codebasics#_formatting) in the settings.
|
||||
|
||||
### Capitalization and punctuation
|
||||
|
||||
Both line comments and doc comments must be capitalized. Each sentence must end with a period.
|
||||
@@ -61,7 +56,26 @@ Comments for function arguments must adhere to this pattern:
|
||||
/// Performs a certain computation. Any other description of the function.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `arg1` - The first argument.
|
||||
/// * `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.
|
||||
|
||||
|
||||
9093
Cargo.lock
generated
Normal file
9093
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
169
Cargo.toml
Normal file
169
Cargo.toml
Normal file
@@ -0,0 +1,169 @@
|
||||
[workspace]
|
||||
members = [
|
||||
"crates/attestation",
|
||||
"crates/components/deap",
|
||||
"crates/components/cipher",
|
||||
"crates/components/hmac-sha256",
|
||||
"crates/components/key-exchange",
|
||||
"crates/core",
|
||||
"crates/data-fixtures",
|
||||
"crates/examples",
|
||||
"crates/formats",
|
||||
"crates/server-fixture/certs",
|
||||
"crates/server-fixture/server",
|
||||
"crates/tls/backend",
|
||||
"crates/tls/client",
|
||||
"crates/tls/client-async",
|
||||
"crates/tls/core",
|
||||
"crates/mpc-tls",
|
||||
"crates/tls/server-fixture",
|
||||
"crates/wasm",
|
||||
"crates/harness/core",
|
||||
"crates/harness/executor",
|
||||
"crates/harness/runner",
|
||||
"crates/harness/plot",
|
||||
"crates/tlsn",
|
||||
]
|
||||
resolver = "2"
|
||||
|
||||
[workspace.lints.rust]
|
||||
# unsafe_code = "forbid"
|
||||
|
||||
[workspace.lints.clippy]
|
||||
# enum_glob_use = "deny"
|
||||
|
||||
[profile.tests-integration]
|
||||
inherits = "release"
|
||||
opt-level = 1
|
||||
|
||||
[profile.wasm]
|
||||
inherits = "release"
|
||||
lto = true
|
||||
panic = "abort"
|
||||
codegen-units = 1
|
||||
|
||||
[workspace.dependencies]
|
||||
tls-server-fixture = { path = "crates/tls/server-fixture" }
|
||||
tlsn-attestation = { path = "crates/attestation" }
|
||||
tlsn-cipher = { path = "crates/components/cipher" }
|
||||
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-key-exchange = { path = "crates/components/key-exchange" }
|
||||
tlsn-mpc-tls = { path = "crates/mpc-tls" }
|
||||
tlsn-server-fixture = { path = "crates/server-fixture/server" }
|
||||
tlsn-server-fixture-certs = { path = "crates/server-fixture/certs" }
|
||||
tlsn-tls-backend = { path = "crates/tls/backend" }
|
||||
tlsn-tls-client = { path = "crates/tls/client" }
|
||||
tlsn-tls-client-async = { path = "crates/tls/client-async" }
|
||||
tlsn-tls-core = { path = "crates/tls/core" }
|
||||
tlsn-utils = { git = "https://github.com/tlsnotary/tlsn-utils", rev = "6168663" }
|
||||
tlsn-harness-core = { path = "crates/harness/core" }
|
||||
tlsn-harness-executor = { path = "crates/harness/executor" }
|
||||
tlsn-harness-runner = { path = "crates/harness/runner" }
|
||||
tlsn-wasm = { path = "crates/wasm" }
|
||||
tlsn = { path = "crates/tlsn" }
|
||||
|
||||
mpz-circuits = { git = "https://github.com/privacy-ethereum/mpz", tag = "v0.1.0-alpha.4" }
|
||||
mpz-memory-core = { git = "https://github.com/privacy-ethereum/mpz", tag = "v0.1.0-alpha.4" }
|
||||
mpz-common = { git = "https://github.com/privacy-ethereum/mpz", tag = "v0.1.0-alpha.4" }
|
||||
mpz-core = { git = "https://github.com/privacy-ethereum/mpz", tag = "v0.1.0-alpha.4" }
|
||||
mpz-vm-core = { git = "https://github.com/privacy-ethereum/mpz", tag = "v0.1.0-alpha.4" }
|
||||
mpz-garble = { git = "https://github.com/privacy-ethereum/mpz", tag = "v0.1.0-alpha.4" }
|
||||
mpz-garble-core = { git = "https://github.com/privacy-ethereum/mpz", tag = "v0.1.0-alpha.4" }
|
||||
mpz-ole = { git = "https://github.com/privacy-ethereum/mpz", tag = "v0.1.0-alpha.4" }
|
||||
mpz-ot = { git = "https://github.com/privacy-ethereum/mpz", tag = "v0.1.0-alpha.4" }
|
||||
mpz-share-conversion = { git = "https://github.com/privacy-ethereum/mpz", tag = "v0.1.0-alpha.4" }
|
||||
mpz-fields = { git = "https://github.com/privacy-ethereum/mpz", tag = "v0.1.0-alpha.4" }
|
||||
mpz-zk = { git = "https://github.com/privacy-ethereum/mpz", tag = "v0.1.0-alpha.4" }
|
||||
mpz-hash = { git = "https://github.com/privacy-ethereum/mpz", tag = "v0.1.0-alpha.4" }
|
||||
mpz-ideal-vm = { git = "https://github.com/privacy-ethereum/mpz", tag = "v0.1.0-alpha.4" }
|
||||
|
||||
rangeset = { version = "0.2" }
|
||||
serio = { version = "0.2" }
|
||||
spansy = { git = "https://github.com/tlsnotary/tlsn-utils", rev = "6168663" }
|
||||
uid-mux = { version = "0.2" }
|
||||
websocket-relay = { git = "https://github.com/tlsnotary/tlsn-utils", rev = "6168663" }
|
||||
|
||||
aead = { version = "0.4" }
|
||||
aes = { version = "0.8" }
|
||||
aes-gcm = { version = "0.9" }
|
||||
anyhow = { version = "1.0" }
|
||||
async-trait = { version = "0.1" }
|
||||
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" }
|
||||
digest = { version = "0.10" }
|
||||
elliptic-curve = { version = "0.13" }
|
||||
enum-try-as-inner = { version = "0.1" }
|
||||
env_logger = { version = "0.10" }
|
||||
futures = { version = "0.3" }
|
||||
futures-rustls = { version = "0.25" }
|
||||
generic-array = { version = "0.14" }
|
||||
ghash = { version = "0.5" }
|
||||
hex = { version = "0.4" }
|
||||
hmac = { version = "0.12" }
|
||||
http-body-util = { version = "0.1" }
|
||||
hyper = { version = "1.1" }
|
||||
hyper-util = { version = "0.1" }
|
||||
ipnet = { version = "2.11" }
|
||||
inventory = { version = "0.3" }
|
||||
itybity = { version = "0.2" }
|
||||
js-sys = { version = "0.3" }
|
||||
k256 = { version = "0.13" }
|
||||
log = { version = "0.4" }
|
||||
once_cell = { version = "1.19" }
|
||||
opaque-debug = { version = "0.3" }
|
||||
p256 = { version = "0.13" }
|
||||
pin-project-lite = { version = "0.2" }
|
||||
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" }
|
||||
rstest = { version = "0.17" }
|
||||
rustls = { version = "0.21" }
|
||||
rustls-pemfile = { version = "1.0" }
|
||||
rustls-webpki = { version = "0.103" }
|
||||
rustls-pki-types = { version = "1.12" }
|
||||
sct = { version = "0.7" }
|
||||
semver = { version = "1.0" }
|
||||
serde = { version = "1.0" }
|
||||
serde_json = { version = "1.0" }
|
||||
sha2 = { version = "0.10" }
|
||||
signature = { version = "2.2" }
|
||||
thiserror = { version = "1.0" }
|
||||
tiny-keccak = { version = "2.0" }
|
||||
tokio = { version = "1.38" }
|
||||
tokio-util = { version = "0.7" }
|
||||
toml = { version = "0.8" }
|
||||
tower = { version = "0.5" }
|
||||
tower-http = { version = "0.5" }
|
||||
tower-service = { version = "0.3" }
|
||||
tracing = { version = "0.1" }
|
||||
tracing-subscriber = { version = "0.3" }
|
||||
wasm-bindgen = { version = "0.2" }
|
||||
wasm-bindgen-futures = { version = "0.4" }
|
||||
web-spawn = { version = "0.2" }
|
||||
web-time = { version = "0.2" }
|
||||
webpki-roots = { version = "1.0" }
|
||||
webpki-root-certs = { version = "1.0" }
|
||||
ws_stream_wasm = { version = "0.7.5" }
|
||||
zeroize = { version = "1.8" }
|
||||
56
README.md
56
README.md
@@ -8,16 +8,18 @@
|
||||
|
||||
[mit-badge]: https://img.shields.io/badge/license-MIT-blue.svg
|
||||
[apache-badge]: https://img.shields.io/github/license/saltstack/salt
|
||||
[actions-badge]: https://github.com/tlsnotary/tlsn/actions/workflows/ci.yml/badge.svg
|
||||
[actions-url]: https://github.com/tlsnotary/tlsn/actions?query=workflow%3Arust+branch%3Adev
|
||||
[actions-badge]: https://github.com/tlsnotary/tlsn/actions/workflows/ci.yml/badge.svg?branch=dev
|
||||
[actions-url]: https://github.com/tlsnotary/tlsn/actions?query=workflow%3Aci+branch%3Adev
|
||||
|
||||
[Website](https://tlsnotary.org) |
|
||||
[Documentation](https://docs.tlsnotary.org) |
|
||||
[Documentation](https://tlsnotary.org/docs/intro) |
|
||||
[API Docs](https://tlsnotary.github.io/tlsn) |
|
||||
[Discord](https://discord.gg/9XwESXtcN7)
|
||||
|
||||
# TLSNotary
|
||||
|
||||
**Data provenance and privacy with secure multi-party computation**
|
||||
|
||||
## ⚠️ Notice
|
||||
|
||||
This project is currently under active development and should not be used in production. Expect bugs and regular major breaking changes.
|
||||
@@ -30,25 +32,41 @@ All crates in this repository are licensed under either of
|
||||
|
||||
at your option.
|
||||
|
||||
## Overview
|
||||
## Branches
|
||||
|
||||
- **tls**: Home of the TLS logic of our protocol like handshake en-/decryption, ghash, **currently outdated**
|
||||
- **utils**: Utility functions which are frequently used everywhere
|
||||
- **actors**: Provides actors, which implement protocol-specific functionality using
|
||||
the actor pattern. They usually wrap an aio module
|
||||
- **universal-hash**: Implements ghash, which is used AES-GCM. Poly-1305 coming soon.
|
||||
- **point-addition**: Used in key-exchange and allows to compute a two party sharing of
|
||||
an EC curve point
|
||||
- [`main`](https://github.com/tlsnotary/tlsn/tree/main)
|
||||
- Default branch — points to the latest release.
|
||||
- This is stable and suitable for most users.
|
||||
- [`dev`](https://github.com/tlsnotary/tlsn/tree/dev)
|
||||
- Development branch — contains the latest PRs.
|
||||
- Developers should submit their PRs against this branch.
|
||||
|
||||
### General remarks
|
||||
## Directory
|
||||
|
||||
- the TLSNotary codebase makes heavy use of async Rust. Usually an aio
|
||||
crate/module implements the network IO and wraps a core crate/module which
|
||||
provides the protocol implementation. This is a frequent pattern you will
|
||||
encounter in the codebase.
|
||||
- some protocols are implemented using the actor pattern to facilitate
|
||||
asynchronous message processing with shared state.
|
||||
- [examples](./crates/examples/): Examples on how to use the TLSNotary protocol.
|
||||
- [tlsn](./crates/tlsn/): The TLSNotary library.
|
||||
|
||||
This repository contains the source code for the Rust implementation of the TLSNotary protocol. For additional tools and implementations related to TLSNotary, visit <https://github.com/tlsnotary>. This includes repositories such as [`tlsn-js`](https://github.com/tlsnotary/tlsn-js), [`tlsn-extension`](https://github.com/tlsnotary/tlsn-extension), among others.
|
||||
|
||||
|
||||
## Development
|
||||
|
||||
> [!IMPORTANT]
|
||||
> **Note on Rust-to-WASM Compilation**: This project requires compiling Rust into WASM, which needs [`clang`](https://clang.llvm.org/) version 16.0.0 or newer. MacOS users, be aware that Xcode's default `clang` might be older. If you encounter the error `No available targets are compatible with triple "wasm32-unknown-unknown"`, it's likely due to an outdated `clang`. Updating `clang` to a newer version should resolve this issue.
|
||||
>
|
||||
> For MacOS aarch64 users, if Apple's default `clang` isn't working, try installing `llvm` via Homebrew (`brew install llvm`). You can then prioritize the Homebrew `clang` over the default macOS version by modifying your `PATH`. Add the following line to your shell configuration file (e.g., `.bashrc`, `.zshrc`):
|
||||
> ```sh
|
||||
> export PATH="/opt/homebrew/opt/llvm/bin:$PATH"
|
||||
> ```
|
||||
|
||||
If you run into this error:
|
||||
```
|
||||
Could not find directory of OpenSSL installation, and this `-sys` crate cannot
|
||||
proceed without this knowledge. If OpenSSL is installed and this crate had
|
||||
trouble finding it, you can set the `OPENSSL_DIR` environment variable for the
|
||||
compilation process.
|
||||
```
|
||||
Make sure you have the development packages of OpenSSL installed (`libssl-dev` on Ubuntu or `openssl-devel` on Fedora).
|
||||
|
||||
## Contribution
|
||||
|
||||
@@ -56,4 +74,4 @@ Unless you explicitly state otherwise, any contribution intentionally submitted
|
||||
for inclusion in the work by you, as defined in the Apache-2.0 license, shall be
|
||||
dual licensed as above, without any additional terms or conditions.
|
||||
|
||||
See [CONTRIBUTING.md](CONTRIBUTING.md).
|
||||
See [CONTRIBUTING.md](CONTRIBUTING.md).
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
export PATH=$PATH:/home/ubuntu/.cargo/bin
|
||||
|
||||
APP_NAME=$(echo $APPLICATION_NAME | awk -F- '{ print $2 }')
|
||||
|
||||
# Prepare directory
|
||||
sudo rm -rf ~/$APP_NAME/tlsn
|
||||
sudo mv ~/tlsn/ ~/$APP_NAME
|
||||
sudo mkdir -p ~/$APP_NAME/tlsn/notary-server/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-server/target/release
|
||||
chmod +x ~/$APP_NAME/tlsn/notary-server/target/release/notary-server
|
||||
|
||||
exit 0
|
||||
@@ -1,10 +0,0 @@
|
||||
#!/bin/bash
|
||||
#set -e
|
||||
|
||||
APP_NAME=$(echo $APPLICATION_NAME | awk -F- '{ print $2 }')
|
||||
|
||||
if [ ! -d $APP_NAME ]; then
|
||||
mkdir ~/$APP_NAME
|
||||
fi
|
||||
|
||||
exit 0
|
||||
@@ -1,10 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
export PATH=$PATH:/home/ubuntu/.cargo/bin
|
||||
|
||||
APP_NAME=$(echo $APPLICATION_NAME | awk -F- '{ print $2 }')
|
||||
|
||||
cd ~/$APP_NAME/tlsn/notary-server
|
||||
target/release/notary-server --config-file ~/.notary/$APP_NAME/config.yaml &> ~/$APP_NAME/tlsn/notary.log &
|
||||
|
||||
exit 0
|
||||
@@ -1,9 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
APP_NAME=$(echo $APPLICATION_NAME | awk -F- '{ print $2 }')
|
||||
|
||||
PID=$(pgrep -f notary.*$APP_NAME)
|
||||
kill -15 $PID
|
||||
|
||||
exit 0
|
||||
@@ -1,20 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# Verify proccess is running
|
||||
APP_NAME=$(echo $APPLICATION_NAME | awk -F- '{ print $2 }')
|
||||
|
||||
pgrep -f notary.*$APP_NAME
|
||||
[ $? -eq 0 ] || exit 1
|
||||
|
||||
# Verify that listening sockets exist
|
||||
if [ "$APPLICATION_NAME" == "tlsn-nightly" ]; then
|
||||
port=7048
|
||||
else
|
||||
port=7047
|
||||
fi
|
||||
|
||||
exposed_ports=$(netstat -lnt4 | egrep -cw $port)
|
||||
[ $exposed_ports -eq 1 ] || exit 1
|
||||
|
||||
exit 0
|
||||
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: appspec-scripts/before_install.sh
|
||||
timeout: 300
|
||||
runas: ubuntu
|
||||
AfterInstall:
|
||||
- location: appspec-scripts/after_install.sh
|
||||
timeout: 300
|
||||
runas: ubuntu
|
||||
ApplicationStart:
|
||||
- location: appspec-scripts/start_app.sh
|
||||
timeout: 300
|
||||
runas: ubuntu
|
||||
ApplicationStop:
|
||||
- location: appspec-scripts/stop_app.sh
|
||||
timeout: 300
|
||||
runas: ubuntu
|
||||
ValidateService:
|
||||
- location: appspec-scripts/validate_app.sh
|
||||
timeout: 300
|
||||
runas: ubuntu
|
||||
11
build_all.sh
11
build_all.sh
@@ -1,11 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
for package in components/uid-mux components/actors/actor-ot components/cipher components/universal-hash components/aead components/key-exchange components/point-addition components/prf components/tls tlsn; do
|
||||
pushd $package
|
||||
# cargo update
|
||||
cargo clean
|
||||
cargo build
|
||||
cargo test
|
||||
cargo clippy --all-features -- -D warnings || exit
|
||||
popd
|
||||
done
|
||||
@@ -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.4"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
name = "aead"
|
||||
|
||||
[features]
|
||||
default = ["mock"]
|
||||
mock = []
|
||||
tracing = [
|
||||
"dep:tracing",
|
||||
"tlsn-block-cipher/tracing",
|
||||
"tlsn-stream-cipher/tracing",
|
||||
"tlsn-universal-hash/tracing",
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
tlsn-block-cipher = { path = "../cipher/block-cipher" }
|
||||
tlsn-stream-cipher = { path = "../cipher/stream-cipher" }
|
||||
tlsn-universal-hash = { path = "../universal-hash" }
|
||||
mpz-core = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "850636f" }
|
||||
mpz-garble = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "850636f" }
|
||||
tlsn-utils-aio = { git = "https://github.com/tlsnotary/tlsn-utils", rev = "51f313d" }
|
||||
|
||||
async-trait = "0.1"
|
||||
derive_builder = "0.12"
|
||||
thiserror = "1"
|
||||
futures = "0.3"
|
||||
serde = "1"
|
||||
tracing = { version = "0.1", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "1", features = ["macros", "rt", "rt-multi-thread"] }
|
||||
aes-gcm = "0.10"
|
||||
@@ -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,101 +0,0 @@
|
||||
//! Mock implementation of AES-GCM for testing purposes.
|
||||
|
||||
use block_cipher::{BlockCipherConfig, MpcBlockCipher};
|
||||
use mpz_garble::{Decode, DecodePrivate, Execute, Memory, Prove, Verify, Vm};
|
||||
use tlsn_stream_cipher::{MpcStreamCipher, StreamCipherConfig};
|
||||
use tlsn_universal_hash::ghash::{mock_ghash_pair, GhashConfig};
|
||||
use utils_aio::duplex::MemoryDuplex;
|
||||
|
||||
use super::*;
|
||||
|
||||
/// Creates a mock AES-GCM pair.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `id` - The id of the AES-GCM instances.
|
||||
/// * `leader_vm` - The VM of the leader.
|
||||
/// * `follower_vm` - The VM of the follower.
|
||||
/// * `leader_config` - The configuration of the leader.
|
||||
/// * `follower_config` - The configuration of the follower.
|
||||
pub async fn create_mock_aes_gcm_pair<T>(
|
||||
id: &str,
|
||||
leader_vm: &mut T,
|
||||
follower_vm: &mut T,
|
||||
leader_config: AesGcmConfig,
|
||||
follower_config: AesGcmConfig,
|
||||
) -> (MpcAesGcm, MpcAesGcm)
|
||||
where
|
||||
T: Vm + Send,
|
||||
<T as Vm>::Thread: Memory + Execute + Decode + DecodePrivate + Prove + Verify + Send + Sync,
|
||||
{
|
||||
let block_cipher_id = format!("{}/block_cipher", id);
|
||||
let leader_block_cipher = MpcBlockCipher::new(
|
||||
BlockCipherConfig::builder()
|
||||
.id(block_cipher_id.clone())
|
||||
.build()
|
||||
.unwrap(),
|
||||
leader_vm.new_thread(&block_cipher_id).await.unwrap(),
|
||||
);
|
||||
let follower_block_cipher = MpcBlockCipher::new(
|
||||
BlockCipherConfig::builder()
|
||||
.id(block_cipher_id.clone())
|
||||
.build()
|
||||
.unwrap(),
|
||||
follower_vm.new_thread(&block_cipher_id).await.unwrap(),
|
||||
);
|
||||
|
||||
let stream_cipher_id = format!("{}/stream_cipher", id);
|
||||
let leader_stream_cipher = MpcStreamCipher::new(
|
||||
StreamCipherConfig::builder()
|
||||
.id(stream_cipher_id.clone())
|
||||
.build()
|
||||
.unwrap(),
|
||||
leader_vm
|
||||
.new_thread_pool(&stream_cipher_id, 4)
|
||||
.await
|
||||
.unwrap(),
|
||||
);
|
||||
let follower_stream_cipher = MpcStreamCipher::new(
|
||||
StreamCipherConfig::builder()
|
||||
.id(stream_cipher_id.clone())
|
||||
.build()
|
||||
.unwrap(),
|
||||
follower_vm
|
||||
.new_thread_pool(&stream_cipher_id, 4)
|
||||
.await
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
let (leader_ghash, follower_ghash) = mock_ghash_pair(
|
||||
GhashConfig::builder()
|
||||
.id(format!("{}/ghash", id))
|
||||
.initial_block_count(64)
|
||||
.build()
|
||||
.unwrap(),
|
||||
GhashConfig::builder()
|
||||
.id(format!("{}/ghash", id))
|
||||
.initial_block_count(64)
|
||||
.build()
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
let (leader_channel, follower_channel) = MemoryDuplex::new();
|
||||
|
||||
let leader = MpcAesGcm::new(
|
||||
leader_config,
|
||||
Box::new(leader_channel),
|
||||
Box::new(leader_block_cipher),
|
||||
Box::new(leader_stream_cipher),
|
||||
Box::new(leader_ghash),
|
||||
);
|
||||
|
||||
let follower = MpcAesGcm::new(
|
||||
follower_config,
|
||||
Box::new(follower_channel),
|
||||
Box::new(follower_block_cipher),
|
||||
Box::new(follower_stream_cipher),
|
||||
Box::new(follower_ghash),
|
||||
);
|
||||
|
||||
(leader, follower)
|
||||
}
|
||||
@@ -1,720 +0,0 @@
|
||||
//! This module provides an implementation of 2PC AES-GCM.
|
||||
|
||||
mod config;
|
||||
#[cfg(feature = "mock")]
|
||||
pub mod mock;
|
||||
mod tag;
|
||||
|
||||
pub use config::{AesGcmConfig, AesGcmConfigBuilder, AesGcmConfigBuilderError, Role};
|
||||
|
||||
use crate::{
|
||||
msg::{AeadMessage, TagShare},
|
||||
Aead, AeadChannel, AeadError,
|
||||
};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use futures::{SinkExt, StreamExt, TryFutureExt};
|
||||
|
||||
use block_cipher::{Aes128, BlockCipher};
|
||||
use mpz_core::commit::HashCommit;
|
||||
use mpz_garble::value::ValueRef;
|
||||
use tlsn_stream_cipher::{Aes128Ctr, StreamCipher};
|
||||
use tlsn_universal_hash::UniversalHash;
|
||||
use utils_aio::expect_msg_or_err;
|
||||
|
||||
pub(crate) use tag::AesGcmTagShare;
|
||||
use tag::{build_ghash_data, AES_GCM_TAG_LEN};
|
||||
|
||||
/// An implementation of 2PC AES-GCM.
|
||||
pub struct MpcAesGcm {
|
||||
config: AesGcmConfig,
|
||||
channel: AeadChannel,
|
||||
aes_block: Box<dyn BlockCipher<Aes128>>,
|
||||
aes_ctr: Box<dyn StreamCipher<Aes128Ctr>>,
|
||||
ghash: Box<dyn UniversalHash>,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for MpcAesGcm {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("MpcAesGcm")
|
||||
.field("config", &self.config)
|
||||
.field("channel", &"AeadChannel {{ ... }}")
|
||||
.field("aes_block", &"BlockCipher {{ ... }}")
|
||||
.field("aes_ctr", &"StreamCipher {{ ... }}")
|
||||
.field("ghash", &"UniversalHash {{ ... }}")
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl MpcAesGcm {
|
||||
/// Creates a new instance of [`MpcAesGcm`].
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(level = "info", skip(channel, aes_block, aes_ctr, ghash), ret)
|
||||
)]
|
||||
pub fn new(
|
||||
config: AesGcmConfig,
|
||||
channel: AeadChannel,
|
||||
aes_block: Box<dyn BlockCipher<Aes128>>,
|
||||
aes_ctr: Box<dyn StreamCipher<Aes128Ctr>>,
|
||||
ghash: Box<dyn UniversalHash>,
|
||||
) -> Self {
|
||||
Self {
|
||||
config,
|
||||
channel,
|
||||
aes_block,
|
||||
aes_ctr,
|
||||
ghash,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(level = "trace", err))]
|
||||
async fn compute_j0_share(&mut self, explicit_nonce: Vec<u8>) -> Result<Vec<u8>, AeadError> {
|
||||
let j0_share = self
|
||||
.aes_ctr
|
||||
.share_keystream_block(explicit_nonce.clone(), 1)
|
||||
.await?;
|
||||
|
||||
Ok(j0_share)
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(level = "trace", err, ret))]
|
||||
async fn compute_tag_share(
|
||||
&mut self,
|
||||
explicit_nonce: Vec<u8>,
|
||||
aad: Vec<u8>,
|
||||
ciphertext: Vec<u8>,
|
||||
) -> Result<AesGcmTagShare, AeadError> {
|
||||
let j0_share = self.compute_j0_share(explicit_nonce.clone()).await?;
|
||||
|
||||
let hash = self
|
||||
.ghash
|
||||
.finalize(build_ghash_data(aad, ciphertext))
|
||||
.await?;
|
||||
|
||||
let mut tag_share = [0u8; 16];
|
||||
tag_share.copy_from_slice(&hash[..]);
|
||||
for i in 0..16 {
|
||||
tag_share[i] ^= j0_share[i];
|
||||
}
|
||||
|
||||
Ok(AesGcmTagShare(tag_share))
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(level = "trace", err, ret))]
|
||||
async fn compute_tag(
|
||||
&mut self,
|
||||
explicit_nonce: Vec<u8>,
|
||||
ciphertext: Vec<u8>,
|
||||
aad: Vec<u8>,
|
||||
) -> Result<Vec<u8>, AeadError> {
|
||||
let tag_share = self
|
||||
.compute_tag_share(explicit_nonce, aad, ciphertext.clone())
|
||||
.await?;
|
||||
|
||||
let tag = match self.config.role() {
|
||||
Role::Leader => {
|
||||
// Send commitment of tag share to follower
|
||||
let (tag_share_decommitment, tag_share_commitment) =
|
||||
TagShare::from(tag_share).hash_commit();
|
||||
|
||||
self.channel
|
||||
.send(AeadMessage::TagShareCommitment(tag_share_commitment))
|
||||
.await?;
|
||||
|
||||
// Expect tag share from follower
|
||||
let msg = expect_msg_or_err!(self.channel, AeadMessage::TagShare)?;
|
||||
|
||||
let other_tag_share = AesGcmTagShare::from_unchecked(&msg.share)?;
|
||||
|
||||
// Send decommitment (tag share) to follower
|
||||
self.channel
|
||||
.send(AeadMessage::TagShareDecommitment(tag_share_decommitment))
|
||||
.await?;
|
||||
|
||||
tag_share + other_tag_share
|
||||
}
|
||||
Role::Follower => {
|
||||
// Wait for commitment from leader
|
||||
let commitment = expect_msg_or_err!(self.channel, AeadMessage::TagShareCommitment)?;
|
||||
|
||||
// Send tag share to leader
|
||||
self.channel
|
||||
.send(AeadMessage::TagShare(tag_share.into()))
|
||||
.await?;
|
||||
|
||||
// Expect decommitment (tag share) from leader
|
||||
let decommitment =
|
||||
expect_msg_or_err!(self.channel, AeadMessage::TagShareDecommitment)?;
|
||||
|
||||
// Verify decommitment
|
||||
decommitment.verify(&commitment).map_err(|_| {
|
||||
AeadError::ValidationError(
|
||||
"Leader tag share commitment verification failed".to_string(),
|
||||
)
|
||||
})?;
|
||||
|
||||
let other_tag_share =
|
||||
AesGcmTagShare::from_unchecked(&decommitment.into_inner().share)?;
|
||||
|
||||
tag_share + other_tag_share
|
||||
}
|
||||
};
|
||||
|
||||
Ok(tag)
|
||||
}
|
||||
|
||||
/// Splits off the tag from the end of the payload and verifies it.
|
||||
async fn _verify_tag(
|
||||
&mut self,
|
||||
explicit_nonce: Vec<u8>,
|
||||
payload: &mut Vec<u8>,
|
||||
aad: Vec<u8>,
|
||||
) -> Result<(), AeadError> {
|
||||
let purported_tag = payload.split_off(payload.len() - AES_GCM_TAG_LEN);
|
||||
|
||||
let tag = self
|
||||
.compute_tag(explicit_nonce, payload.clone(), aad)
|
||||
.await?;
|
||||
|
||||
// Reject if tag is incorrect.
|
||||
if tag != purported_tag {
|
||||
return Err(AeadError::CorruptedTag);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Aead for MpcAesGcm {
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(level = "info", err))]
|
||||
async fn set_key(&mut self, key: ValueRef, iv: ValueRef) -> Result<(), AeadError> {
|
||||
self.aes_block.set_key(key.clone());
|
||||
self.aes_ctr.set_key(key, iv);
|
||||
|
||||
// Share zero block
|
||||
let h_share = self.aes_block.encrypt_share(vec![0u8; 16]).await?;
|
||||
|
||||
self.ghash.set_key(h_share).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(level = "info", err))]
|
||||
async fn decode_key_private(&mut self) -> Result<(), AeadError> {
|
||||
self.aes_ctr
|
||||
.decode_key_private()
|
||||
.await
|
||||
.map_err(AeadError::from)
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(level = "info", err))]
|
||||
async fn decode_key_blind(&mut self) -> Result<(), AeadError> {
|
||||
self.aes_ctr
|
||||
.decode_key_blind()
|
||||
.await
|
||||
.map_err(AeadError::from)
|
||||
}
|
||||
|
||||
fn set_transcript_id(&mut self, id: &str) {
|
||||
self.aes_ctr.set_transcript_id(id)
|
||||
}
|
||||
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(level = "trace", skip(plaintext), err)
|
||||
)]
|
||||
async fn encrypt_public(
|
||||
&mut self,
|
||||
explicit_nonce: Vec<u8>,
|
||||
plaintext: Vec<u8>,
|
||||
aad: Vec<u8>,
|
||||
) -> Result<Vec<u8>, AeadError> {
|
||||
let ciphertext = self
|
||||
.aes_ctr
|
||||
.encrypt_public(explicit_nonce.clone(), plaintext)
|
||||
.await?;
|
||||
|
||||
let tag = self
|
||||
.compute_tag(explicit_nonce, ciphertext.clone(), aad)
|
||||
.await?;
|
||||
|
||||
let mut payload = ciphertext;
|
||||
payload.extend(tag);
|
||||
|
||||
Ok(payload)
|
||||
}
|
||||
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(level = "trace", skip(plaintext), err)
|
||||
)]
|
||||
async fn encrypt_private(
|
||||
&mut self,
|
||||
explicit_nonce: Vec<u8>,
|
||||
plaintext: Vec<u8>,
|
||||
aad: Vec<u8>,
|
||||
) -> Result<Vec<u8>, AeadError> {
|
||||
let ciphertext = self
|
||||
.aes_ctr
|
||||
.encrypt_private(explicit_nonce.clone(), plaintext)
|
||||
.await?;
|
||||
|
||||
let tag = self
|
||||
.compute_tag(explicit_nonce, ciphertext.clone(), aad)
|
||||
.await?;
|
||||
|
||||
let mut payload = ciphertext;
|
||||
payload.extend(tag);
|
||||
|
||||
Ok(payload)
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(level = "trace", err))]
|
||||
async fn encrypt_blind(
|
||||
&mut self,
|
||||
explicit_nonce: Vec<u8>,
|
||||
plaintext_len: usize,
|
||||
aad: Vec<u8>,
|
||||
) -> Result<Vec<u8>, AeadError> {
|
||||
let ciphertext = self
|
||||
.aes_ctr
|
||||
.encrypt_blind(explicit_nonce.clone(), plaintext_len)
|
||||
.await?;
|
||||
|
||||
let tag = self
|
||||
.compute_tag(explicit_nonce, ciphertext.clone(), aad)
|
||||
.await?;
|
||||
|
||||
let mut payload = ciphertext;
|
||||
payload.extend(tag);
|
||||
|
||||
Ok(payload)
|
||||
}
|
||||
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(level = "trace", skip(payload), err)
|
||||
)]
|
||||
async fn decrypt_public(
|
||||
&mut self,
|
||||
explicit_nonce: Vec<u8>,
|
||||
mut payload: Vec<u8>,
|
||||
aad: Vec<u8>,
|
||||
) -> Result<Vec<u8>, AeadError> {
|
||||
self._verify_tag(explicit_nonce.clone(), &mut payload, aad)
|
||||
.await?;
|
||||
|
||||
self.aes_ctr
|
||||
.decrypt_public(explicit_nonce, payload)
|
||||
.map_err(AeadError::from)
|
||||
.await
|
||||
}
|
||||
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(level = "trace", skip(payload), err)
|
||||
)]
|
||||
async fn decrypt_private(
|
||||
&mut self,
|
||||
explicit_nonce: Vec<u8>,
|
||||
mut payload: Vec<u8>,
|
||||
aad: Vec<u8>,
|
||||
) -> Result<Vec<u8>, AeadError> {
|
||||
self._verify_tag(explicit_nonce.clone(), &mut payload, aad)
|
||||
.await?;
|
||||
|
||||
self.aes_ctr
|
||||
.decrypt_private(explicit_nonce, payload)
|
||||
.map_err(AeadError::from)
|
||||
.await
|
||||
}
|
||||
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(level = "trace", skip(payload), err)
|
||||
)]
|
||||
async fn decrypt_blind(
|
||||
&mut self,
|
||||
explicit_nonce: Vec<u8>,
|
||||
mut payload: Vec<u8>,
|
||||
aad: Vec<u8>,
|
||||
) -> Result<(), AeadError> {
|
||||
self._verify_tag(explicit_nonce.clone(), &mut payload, aad)
|
||||
.await?;
|
||||
|
||||
self.aes_ctr
|
||||
.decrypt_blind(explicit_nonce, payload)
|
||||
.map_err(AeadError::from)
|
||||
.await
|
||||
}
|
||||
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(level = "trace", skip(payload), err)
|
||||
)]
|
||||
async fn verify_tag(
|
||||
&mut self,
|
||||
explicit_nonce: Vec<u8>,
|
||||
mut payload: Vec<u8>,
|
||||
aad: Vec<u8>,
|
||||
) -> Result<(), AeadError> {
|
||||
self._verify_tag(explicit_nonce.clone(), &mut payload, aad)
|
||||
.await
|
||||
}
|
||||
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(level = "trace", skip(payload), err)
|
||||
)]
|
||||
async fn prove_plaintext(
|
||||
&mut self,
|
||||
explicit_nonce: Vec<u8>,
|
||||
mut payload: Vec<u8>,
|
||||
aad: Vec<u8>,
|
||||
) -> Result<Vec<u8>, AeadError> {
|
||||
self._verify_tag(explicit_nonce.clone(), &mut payload, aad)
|
||||
.await?;
|
||||
|
||||
self.prove_plaintext_no_tag(explicit_nonce, payload).await
|
||||
}
|
||||
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(level = "trace", skip(ciphertext), err)
|
||||
)]
|
||||
async fn prove_plaintext_no_tag(
|
||||
&mut self,
|
||||
explicit_nonce: Vec<u8>,
|
||||
ciphertext: Vec<u8>,
|
||||
) -> Result<Vec<u8>, AeadError> {
|
||||
self.aes_ctr
|
||||
.prove_plaintext(explicit_nonce, ciphertext)
|
||||
.map_err(AeadError::from)
|
||||
.await
|
||||
}
|
||||
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(level = "trace", skip(payload), err)
|
||||
)]
|
||||
async fn verify_plaintext(
|
||||
&mut self,
|
||||
explicit_nonce: Vec<u8>,
|
||||
mut payload: Vec<u8>,
|
||||
aad: Vec<u8>,
|
||||
) -> Result<(), AeadError> {
|
||||
self._verify_tag(explicit_nonce.clone(), &mut payload, aad)
|
||||
.await?;
|
||||
|
||||
self.verify_plaintext_no_tag(explicit_nonce, payload).await
|
||||
}
|
||||
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(level = "trace", skip(ciphertext), err)
|
||||
)]
|
||||
async fn verify_plaintext_no_tag(
|
||||
&mut self,
|
||||
explicit_nonce: Vec<u8>,
|
||||
ciphertext: Vec<u8>,
|
||||
) -> Result<(), AeadError> {
|
||||
self.aes_ctr
|
||||
.verify_plaintext(explicit_nonce, ciphertext)
|
||||
.map_err(AeadError::from)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{mock::create_mock_aes_gcm_pair, *};
|
||||
use crate::Aead;
|
||||
|
||||
use mpz_garble::{
|
||||
protocol::deap::mock::{create_mock_deap_vm, MockFollower, MockLeader},
|
||||
Memory, Vm,
|
||||
};
|
||||
|
||||
use ::aes_gcm::{
|
||||
aead::{AeadInPlace, KeyInit},
|
||||
Aes128Gcm, Nonce,
|
||||
};
|
||||
|
||||
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, MpcAesGcm), (MockLeader, MockFollower)) {
|
||||
let (mut leader_vm, mut follower_vm) = create_mock_deap_vm("test_vm").await;
|
||||
|
||||
let leader_thread = leader_vm.new_thread("test_thread").await.unwrap();
|
||||
let leader_key = leader_thread
|
||||
.new_public_array_input::<u8>("key", key.len())
|
||||
.unwrap();
|
||||
let leader_iv = leader_thread
|
||||
.new_public_array_input::<u8>("iv", iv.len())
|
||||
.unwrap();
|
||||
|
||||
leader_thread.assign(&leader_key, key.clone()).unwrap();
|
||||
leader_thread.assign(&leader_iv, iv.clone()).unwrap();
|
||||
|
||||
let follower_thread = follower_vm.new_thread("test_thread").await.unwrap();
|
||||
let follower_key = follower_thread
|
||||
.new_public_array_input::<u8>("key", key.len())
|
||||
.unwrap();
|
||||
let follower_iv = follower_thread
|
||||
.new_public_array_input::<u8>("iv", iv.len())
|
||||
.unwrap();
|
||||
|
||||
follower_thread.assign(&follower_key, key.clone()).unwrap();
|
||||
follower_thread.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",
|
||||
&mut leader_vm,
|
||||
&mut 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();
|
||||
|
||||
((leader, follower), (leader_vm, follower_vm))
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
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), (_leader_vm, _follower_vm)) =
|
||||
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]
|
||||
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), (_leader_vm, _follower_vm)) =
|
||||
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]
|
||||
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), (_leader_vm, _follower_vm)) =
|
||||
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]
|
||||
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), (_leader_vm, _follower_vm)) =
|
||||
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!(matches!(err, AeadError::CorruptedTag));
|
||||
|
||||
let ((mut leader, mut follower), (_leader_vm, _follower_vm)) =
|
||||
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!(matches!(err, AeadError::CorruptedTag));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
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), (_leader_vm, _follower_vm)) =
|
||||
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]
|
||||
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), (_leader_vm, _follower_vm)) =
|
||||
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!(matches!(err, AeadError::CorruptedTag));
|
||||
|
||||
let ((mut leader, mut follower), (_leader_vm, _follower_vm)) =
|
||||
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!(matches!(err, AeadError::CorruptedTag));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
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), (_leader_vm, _follower_vm)) =
|
||||
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!(matches!(leader_res.unwrap_err(), AeadError::CorruptedTag));
|
||||
assert!(matches!(follower_res.unwrap_err(), AeadError::CorruptedTag));
|
||||
}
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::ops::Add;
|
||||
|
||||
use crate::AeadError;
|
||||
|
||||
pub(crate) const AES_GCM_TAG_LEN: usize = 16;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
|
||||
pub(crate) struct AesGcmTagShare(pub(crate) [u8; 16]);
|
||||
|
||||
impl AesGcmTagShare {
|
||||
pub(crate) fn from_unchecked(share: &[u8]) -> Result<Self, AeadError> {
|
||||
if share.len() != 16 {
|
||||
return Err(AeadError::ValidationError(
|
||||
"Received tag share is not 16 bytes long".to_string(),
|
||||
));
|
||||
}
|
||||
let mut result = [0u8; 16];
|
||||
result.copy_from_slice(share);
|
||||
Ok(Self(result))
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<[u8]> for AesGcmTagShare {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Add for AesGcmTagShare {
|
||||
type Output = Vec<u8>;
|
||||
|
||||
fn add(self, rhs: Self) -> Self::Output {
|
||||
self.0
|
||||
.iter()
|
||||
.zip(rhs.0.iter())
|
||||
.map(|(a, b)| a ^ b)
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
/// Builds padded data for GHASH
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(level = "trace", ret))]
|
||||
pub(crate) 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,239 +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;
|
||||
pub mod msg;
|
||||
|
||||
pub use msg::AeadMessage;
|
||||
|
||||
use async_trait::async_trait;
|
||||
|
||||
use mpz_garble::value::ValueRef;
|
||||
use utils_aio::duplex::Duplex;
|
||||
|
||||
/// A channel for sending and receiving AEAD messages.
|
||||
pub type AeadChannel = Box<dyn Duplex<AeadMessage>>;
|
||||
|
||||
/// An error that can occur during AEAD operations.
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[allow(missing_docs)]
|
||||
pub enum AeadError {
|
||||
#[error(transparent)]
|
||||
BlockCipherError(#[from] block_cipher::BlockCipherError),
|
||||
#[error(transparent)]
|
||||
StreamCipherError(#[from] tlsn_stream_cipher::StreamCipherError),
|
||||
#[error(transparent)]
|
||||
UniversalHashError(#[from] tlsn_universal_hash::UniversalHashError),
|
||||
#[error("Corrupted Tag")]
|
||||
CorruptedTag,
|
||||
#[error("Validation Error: {0}")]
|
||||
ValidationError(String),
|
||||
#[error(transparent)]
|
||||
IoError(#[from] std::io::Error),
|
||||
}
|
||||
|
||||
/// This trait defines the interface for AEADs.
|
||||
#[async_trait]
|
||||
pub trait Aead: Send {
|
||||
/// Sets the key for the AEAD.
|
||||
async fn set_key(&mut self, key: ValueRef, iv: ValueRef) -> Result<(), AeadError>;
|
||||
|
||||
/// Decodes the key for the AEAD, revealing it to this party.
|
||||
async fn decode_key_private(&mut self) -> Result<(), AeadError>;
|
||||
|
||||
/// Decodes the key for the AEAD, revealing it to the other party(s).
|
||||
async fn decode_key_blind(&mut self) -> Result<(), AeadError>;
|
||||
|
||||
/// 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);
|
||||
|
||||
/// Encrypts a plaintext message, returning the ciphertext and tag.
|
||||
///
|
||||
/// The plaintext is provided by both parties.
|
||||
///
|
||||
/// * `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>, AeadError>;
|
||||
|
||||
/// Encrypts a plaintext message, hiding it from the other party, returning the ciphertext and tag.
|
||||
///
|
||||
/// * `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>, AeadError>;
|
||||
|
||||
/// Encrypts a plaintext message provided by the other party, returning
|
||||
/// the ciphertext and tag.
|
||||
///
|
||||
/// * `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>, AeadError>;
|
||||
|
||||
/// Decrypts a ciphertext message, returning the plaintext to both parties.
|
||||
///
|
||||
/// This method checks the authenticity of the ciphertext, tag and additional data.
|
||||
///
|
||||
/// * `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>, AeadError>;
|
||||
|
||||
/// Decrypts a ciphertext message, returning the plaintext only to this party.
|
||||
///
|
||||
/// This method checks the authenticity of the ciphertext, tag and additional data.
|
||||
///
|
||||
/// * `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>, AeadError>;
|
||||
|
||||
/// Decrypts a ciphertext message, returning the plaintext only to the other party.
|
||||
///
|
||||
/// This method checks the authenticity of the ciphertext, tag and additional data.
|
||||
///
|
||||
/// * `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<(), AeadError>;
|
||||
|
||||
/// Verifies the tag of a ciphertext message.
|
||||
///
|
||||
/// This method checks the authenticity of the ciphertext, tag and additional data.
|
||||
///
|
||||
/// * `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<(), AeadError>;
|
||||
|
||||
/// 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>, AeadError>;
|
||||
|
||||
/// 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>, AeadError>;
|
||||
|
||||
/// 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<(), AeadError>;
|
||||
|
||||
/// 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<(), AeadError>;
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
//! Message types for AEAD protocols.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use mpz_core::{commit::Decommitment, hash::Hash};
|
||||
|
||||
/// Aead messages.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[allow(missing_docs)]
|
||||
pub enum AeadMessage {
|
||||
TagShareCommitment(Hash),
|
||||
TagShareDecommitment(Decommitment<TagShare>),
|
||||
TagShare(TagShare),
|
||||
}
|
||||
|
||||
/// A tag share.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct TagShare {
|
||||
/// The share of the tag.
|
||||
pub share: Vec<u8>,
|
||||
}
|
||||
|
||||
impl From<crate::aes_gcm::AesGcmTagShare> for TagShare {
|
||||
fn from(tag_share: crate::aes_gcm::AesGcmTagShare) -> Self {
|
||||
Self {
|
||||
share: tag_share.0.to_vec(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
[workspace]
|
||||
members = ["stream-cipher", "block-cipher"]
|
||||
resolver = "2"
|
||||
|
||||
[workspace.dependencies]
|
||||
# tlsn
|
||||
mpz-circuits = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "850636f" }
|
||||
mpz-garble = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "850636f" }
|
||||
tlsn-utils = { git = "https://github.com/tlsnotary/tlsn-utils", rev = "51f313d" }
|
||||
|
||||
# crypto
|
||||
aes = "0.8"
|
||||
ctr = "0.9.2"
|
||||
cipher = "0.4.3"
|
||||
|
||||
# async
|
||||
async-trait = "0.1"
|
||||
futures = "0.3"
|
||||
tokio = { version = "1", default-features = false }
|
||||
|
||||
# testing
|
||||
rstest = "0.17"
|
||||
criterion = "0.5"
|
||||
|
||||
# error/log
|
||||
thiserror = "1"
|
||||
tracing = "0.1"
|
||||
|
||||
# misc
|
||||
derive_builder = "0.12"
|
||||
@@ -1,31 +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.4"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
name = "block_cipher"
|
||||
|
||||
[features]
|
||||
default = ["mock"]
|
||||
tracing = ["dep:tracing"]
|
||||
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, optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
aes.workspace = true
|
||||
cipher.workspace = true
|
||||
tokio = { workspace = true, features = ["macros", "rt", "rt-multi-thread"] }
|
||||
@@ -1,182 +0,0 @@
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use async_trait::async_trait;
|
||||
|
||||
use mpz_garble::{value::ValueRef, Decode, DecodePrivate, Execute, Memory};
|
||||
use utils::id::NestedId;
|
||||
|
||||
use crate::{BlockCipher, BlockCipherCircuit, BlockCipherConfig, BlockCipherError};
|
||||
|
||||
struct State {
|
||||
execution_id: NestedId,
|
||||
key: Option<ValueRef>,
|
||||
}
|
||||
|
||||
/// An MPC block cipher
|
||||
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
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(level = "info", skip(executor))
|
||||
)]
|
||||
pub fn new(config: BlockCipherConfig, executor: E) -> Self {
|
||||
let execution_id = NestedId::new(&config.id).append_counter();
|
||||
Self {
|
||||
state: State {
|
||||
execution_id,
|
||||
key: None,
|
||||
},
|
||||
executor,
|
||||
_cipher: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<C, E> BlockCipher<C> for MpcBlockCipher<C, E>
|
||||
where
|
||||
C: BlockCipherCircuit,
|
||||
E: Memory + Execute + Decode + DecodePrivate + Send + Sync + Send,
|
||||
{
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(level = "info", skip(self)))]
|
||||
fn set_key(&mut self, key: ValueRef) {
|
||||
self.state.key = Some(key);
|
||||
}
|
||||
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(level = "debug", skip(self, plaintext), 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::InvalidInputLength(C::BLOCK_LEN, len))?;
|
||||
|
||||
let key = self.state.key.clone().ok_or(BlockCipherError::KeyNotSet)?;
|
||||
|
||||
let id = self.state.execution_id.increment_in_place().to_string();
|
||||
|
||||
let msg = self
|
||||
.executor
|
||||
.new_private_input::<C::BLOCK>(&format!("{}/msg", &id))?;
|
||||
let ciphertext = self
|
||||
.executor
|
||||
.new_output::<C::BLOCK>(&format!("{}/ciphertext", &id))?;
|
||||
|
||||
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())
|
||||
}
|
||||
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(level = "debug", skip(self), err)
|
||||
)]
|
||||
async fn encrypt_blind(&mut self) -> Result<Vec<u8>, BlockCipherError> {
|
||||
let key = self.state.key.clone().ok_or(BlockCipherError::KeyNotSet)?;
|
||||
|
||||
let id = self.state.execution_id.increment_in_place().to_string();
|
||||
|
||||
let msg = self
|
||||
.executor
|
||||
.new_blind_input::<C::BLOCK>(&format!("{}/msg", &id))?;
|
||||
let ciphertext = self
|
||||
.executor
|
||||
.new_output::<C::BLOCK>(&format!("{}/ciphertext", &id))?;
|
||||
|
||||
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())
|
||||
}
|
||||
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(level = "debug", skip(self, plaintext), 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::InvalidInputLength(C::BLOCK_LEN, len))?;
|
||||
|
||||
let key = self.state.key.clone().ok_or(BlockCipherError::KeyNotSet)?;
|
||||
|
||||
let id = self.state.execution_id.increment_in_place().to_string();
|
||||
|
||||
let msg = self
|
||||
.executor
|
||||
.new_public_input::<C::BLOCK>(&format!("{}/msg", &id))?;
|
||||
let ciphertext = self
|
||||
.executor
|
||||
.new_output::<C::BLOCK>(&format!("{}/ciphertext", &id))?;
|
||||
|
||||
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,163 +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;
|
||||
|
||||
use async_trait::async_trait;
|
||||
|
||||
use mpz_garble::value::ValueRef;
|
||||
|
||||
pub use crate::{
|
||||
cipher::MpcBlockCipher,
|
||||
circuit::{Aes128, BlockCipherCircuit},
|
||||
};
|
||||
pub use config::{BlockCipherConfig, BlockCipherConfigBuilder, BlockCipherConfigBuilderError};
|
||||
|
||||
/// Errors that can occur when using the block cipher
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[allow(missing_docs)]
|
||||
pub enum BlockCipherError {
|
||||
#[error(transparent)]
|
||||
MemoryError(#[from] mpz_garble::MemoryError),
|
||||
#[error(transparent)]
|
||||
ExecutionError(#[from] mpz_garble::ExecutionError),
|
||||
#[error(transparent)]
|
||||
DecodeError(#[from] mpz_garble::DecodeError),
|
||||
#[error("Cipher key not set")]
|
||||
KeyNotSet,
|
||||
#[error("Input does not match block length: expected {0}, got {1}")]
|
||||
InvalidInputLength(usize, usize),
|
||||
}
|
||||
|
||||
/// 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);
|
||||
|
||||
/// Encrypts the given plaintext keeping it hidden from the other party(s).
|
||||
///
|
||||
/// Returns the ciphertext
|
||||
///
|
||||
/// * `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
|
||||
///
|
||||
/// * `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, Vm};
|
||||
|
||||
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]
|
||||
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 (mut leader_vm, mut follower_vm) = create_mock_deap_vm("test").await;
|
||||
let leader_thread = leader_vm.new_thread("test").await.unwrap();
|
||||
let follower_thread = follower_vm.new_thread("test").await.unwrap();
|
||||
|
||||
// Key is public just for this test, typically it is private
|
||||
let leader_key = leader_thread.new_public_input::<[u8; 16]>("key").unwrap();
|
||||
let follower_key = follower_thread.new_public_input::<[u8; 16]>("key").unwrap();
|
||||
|
||||
leader_thread.assign(&leader_key, key).unwrap();
|
||||
follower_thread.assign(&follower_key, key).unwrap();
|
||||
|
||||
let mut leader = MpcBlockCipher::<Aes128, _>::new(leader_config, leader_thread);
|
||||
leader.set_key(leader_key);
|
||||
|
||||
let mut follower = MpcBlockCipher::<Aes128, _>::new(follower_config, follower_thread);
|
||||
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]
|
||||
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 (mut leader_vm, mut follower_vm) = create_mock_deap_vm("test").await;
|
||||
let leader_thread = leader_vm.new_thread("test").await.unwrap();
|
||||
let follower_thread = follower_vm.new_thread("test").await.unwrap();
|
||||
|
||||
// Key is public just for this test, typically it is private
|
||||
let leader_key = leader_thread.new_public_input::<[u8; 16]>("key").unwrap();
|
||||
let follower_key = follower_thread.new_public_input::<[u8; 16]>("key").unwrap();
|
||||
|
||||
leader_thread.assign(&leader_key, key).unwrap();
|
||||
follower_thread.assign(&follower_key, key).unwrap();
|
||||
|
||||
let mut leader = MpcBlockCipher::<Aes128, _>::new(leader_config, leader_thread);
|
||||
leader.set_key(leader_key);
|
||||
|
||||
let mut follower = MpcBlockCipher::<Aes128, _>::new(follower_config, follower_thread);
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
[package]
|
||||
name = "tlsn-stream-cipher"
|
||||
authors = ["TLSNotary Team"]
|
||||
description = "2PC stream cipher implementation"
|
||||
keywords = ["tls", "mpc", "2pc", "stream-cipher"]
|
||||
categories = ["cryptography"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
version = "0.1.0-alpha.4"
|
||||
edition = "2021"
|
||||
|
||||
[features]
|
||||
default = ["mock"]
|
||||
tracing = ["dep:tracing"]
|
||||
mock = []
|
||||
|
||||
[dependencies]
|
||||
mpz-circuits.workspace = true
|
||||
mpz-garble.workspace = true
|
||||
tlsn-utils.workspace = true
|
||||
aes.workspace = true
|
||||
ctr.workspace = true
|
||||
cipher.workspace = true
|
||||
async-trait.workspace = true
|
||||
thiserror.workspace = true
|
||||
derive_builder.workspace = true
|
||||
tracing = { workspace = true, optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
futures.workspace = true
|
||||
tokio = { workspace = true, features = ["macros", "rt", "rt-multi-thread"] }
|
||||
rstest = { workspace = true, features = ["async-timeout"] }
|
||||
criterion = { workspace = true, features = ["async_tokio"] }
|
||||
|
||||
[[bench]]
|
||||
name = "mock"
|
||||
harness = false
|
||||
@@ -1,145 +0,0 @@
|
||||
use criterion::{criterion_group, criterion_main, Criterion, Throughput};
|
||||
|
||||
use mpz_garble::{protocol::deap::mock::create_mock_deap_vm, Memory, Vm};
|
||||
use tlsn_stream_cipher::{
|
||||
Aes128Ctr, CtrCircuit, MpcStreamCipher, StreamCipher, StreamCipherConfigBuilder,
|
||||
};
|
||||
|
||||
async fn bench_stream_cipher_encrypt(thread_count: usize, len: usize) {
|
||||
let (mut leader_vm, mut follower_vm) = create_mock_deap_vm("test").await;
|
||||
|
||||
let leader_thread = leader_vm.new_thread("key_config").await.unwrap();
|
||||
let leader_key = leader_thread.new_public_input::<[u8; 16]>("key").unwrap();
|
||||
let leader_iv = leader_thread.new_public_input::<[u8; 4]>("iv").unwrap();
|
||||
|
||||
leader_thread.assign(&leader_key, [0u8; 16]).unwrap();
|
||||
leader_thread.assign(&leader_iv, [0u8; 4]).unwrap();
|
||||
|
||||
let follower_thread = follower_vm.new_thread("key_config").await.unwrap();
|
||||
let follower_key = follower_thread.new_public_input::<[u8; 16]>("key").unwrap();
|
||||
let follower_iv = follower_thread.new_public_input::<[u8; 4]>("iv").unwrap();
|
||||
|
||||
follower_thread.assign(&follower_key, [0u8; 16]).unwrap();
|
||||
follower_thread.assign(&follower_iv, [0u8; 4]).unwrap();
|
||||
|
||||
let leader_thread_pool = leader_vm
|
||||
.new_thread_pool("mock", thread_count)
|
||||
.await
|
||||
.unwrap();
|
||||
let follower_thread_pool = follower_vm
|
||||
.new_thread_pool("mock", thread_count)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let leader_config = StreamCipherConfigBuilder::default()
|
||||
.id("test".to_string())
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let follower_config = StreamCipherConfigBuilder::default()
|
||||
.id("test".to_string())
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let mut leader = MpcStreamCipher::<Aes128Ctr, _>::new(leader_config, leader_thread_pool);
|
||||
leader.set_key(leader_key, leader_iv);
|
||||
|
||||
let mut follower = MpcStreamCipher::<Aes128Ctr, _>::new(follower_config, follower_thread_pool);
|
||||
follower.set_key(follower_key, follower_iv);
|
||||
|
||||
let plaintext = vec![0u8; len];
|
||||
let explicit_nonce = vec![0u8; 8];
|
||||
|
||||
_ = tokio::try_join!(
|
||||
leader.encrypt_private(explicit_nonce.clone(), plaintext),
|
||||
follower.encrypt_blind(explicit_nonce, len)
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
_ = tokio::try_join!(leader_vm.finalize(), follower_vm.finalize()).unwrap();
|
||||
}
|
||||
|
||||
async fn bench_stream_cipher_zk(thread_count: usize, len: usize) {
|
||||
let (mut leader_vm, mut follower_vm) = create_mock_deap_vm("test").await;
|
||||
|
||||
let key = [0u8; 16];
|
||||
let iv = [0u8; 4];
|
||||
|
||||
let leader_thread = leader_vm.new_thread("key_config").await.unwrap();
|
||||
let leader_key = leader_thread.new_public_input::<[u8; 16]>("key").unwrap();
|
||||
let leader_iv = leader_thread.new_public_input::<[u8; 4]>("iv").unwrap();
|
||||
|
||||
leader_thread.assign(&leader_key, key).unwrap();
|
||||
leader_thread.assign(&leader_iv, iv).unwrap();
|
||||
|
||||
let follower_thread = follower_vm.new_thread("key_config").await.unwrap();
|
||||
let follower_key = follower_thread.new_public_input::<[u8; 16]>("key").unwrap();
|
||||
let follower_iv = follower_thread.new_public_input::<[u8; 4]>("iv").unwrap();
|
||||
|
||||
follower_thread.assign(&follower_key, key).unwrap();
|
||||
follower_thread.assign(&follower_iv, iv).unwrap();
|
||||
|
||||
let leader_thread_pool = leader_vm
|
||||
.new_thread_pool("mock", thread_count)
|
||||
.await
|
||||
.unwrap();
|
||||
let follower_thread_pool = follower_vm
|
||||
.new_thread_pool("mock", thread_count)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let leader_config = StreamCipherConfigBuilder::default()
|
||||
.id("test".to_string())
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let follower_config = StreamCipherConfigBuilder::default()
|
||||
.id("test".to_string())
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let mut leader = MpcStreamCipher::<Aes128Ctr, _>::new(leader_config, leader_thread_pool);
|
||||
leader.set_key(leader_key, leader_iv);
|
||||
|
||||
let mut follower = MpcStreamCipher::<Aes128Ctr, _>::new(follower_config, follower_thread_pool);
|
||||
follower.set_key(follower_key, follower_iv);
|
||||
|
||||
let plaintext = vec![0u8; len];
|
||||
let explicit_nonce = [0u8; 8];
|
||||
let ciphertext = Aes128Ctr::apply_keystream(&key, &iv, 2, &explicit_nonce, &plaintext).unwrap();
|
||||
|
||||
_ = tokio::try_join!(
|
||||
leader.prove_plaintext(explicit_nonce.to_vec(), plaintext),
|
||||
follower.verify_plaintext(explicit_nonce.to_vec(), ciphertext)
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
_ = tokio::try_join!(leader_vm.finalize(), follower_vm.finalize()).unwrap();
|
||||
}
|
||||
|
||||
fn criterion_benchmark(c: &mut Criterion) {
|
||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||
let thread_count = 8;
|
||||
let len = 1024;
|
||||
|
||||
let mut group = c.benchmark_group("stream_cipher/encrypt_private");
|
||||
group.throughput(Throughput::Bytes(len as u64));
|
||||
group.bench_function(format!("{}", len), |b| {
|
||||
b.to_async(&rt)
|
||||
.iter(|| async { bench_stream_cipher_encrypt(thread_count, len).await })
|
||||
});
|
||||
|
||||
drop(group);
|
||||
|
||||
let mut group = c.benchmark_group("stream_cipher/zk");
|
||||
group.throughput(Throughput::Bytes(len as u64));
|
||||
group.bench_function(format!("{}", len), |b| {
|
||||
b.to_async(&rt)
|
||||
.iter(|| async { bench_stream_cipher_zk(thread_count, len).await })
|
||||
});
|
||||
|
||||
drop(group);
|
||||
}
|
||||
|
||||
criterion_group!(benches, criterion_benchmark);
|
||||
criterion_main!(benches);
|
||||
@@ -1,127 +0,0 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use mpz_circuits::{
|
||||
types::{StaticValueType, Value},
|
||||
Circuit,
|
||||
};
|
||||
|
||||
use crate::{circuit::AES_CTR, StreamCipherError};
|
||||
|
||||
/// A counter-mode block cipher circuit.
|
||||
pub trait CtrCircuit: Default + Clone + Send + Sync + 'static {
|
||||
/// The key type
|
||||
type KEY: StaticValueType + TryFrom<Vec<u8>> + Send + Sync + 'static;
|
||||
/// The block type
|
||||
type BLOCK: StaticValueType
|
||||
+ TryFrom<Vec<u8>>
|
||||
+ TryFrom<Value>
|
||||
+ Into<Vec<u8>>
|
||||
+ Default
|
||||
+ Send
|
||||
+ Sync
|
||||
+ 'static;
|
||||
/// The IV type
|
||||
type IV: StaticValueType
|
||||
+ TryFrom<Vec<u8>>
|
||||
+ TryFrom<Value>
|
||||
+ Into<Vec<u8>>
|
||||
+ Send
|
||||
+ Sync
|
||||
+ 'static;
|
||||
/// The nonce type
|
||||
type NONCE: StaticValueType
|
||||
+ TryFrom<Vec<u8>>
|
||||
+ TryFrom<Value>
|
||||
+ Into<Vec<u8>>
|
||||
+ Clone
|
||||
+ Copy
|
||||
+ Send
|
||||
+ Sync
|
||||
+ std::fmt::Debug
|
||||
+ 'static;
|
||||
|
||||
/// The length of the key
|
||||
const KEY_LEN: usize;
|
||||
/// The length of the block
|
||||
const BLOCK_LEN: usize;
|
||||
/// The length of the IV
|
||||
const IV_LEN: usize;
|
||||
/// The length of the nonce
|
||||
const NONCE_LEN: usize;
|
||||
|
||||
/// Returns the circuit of the cipher
|
||||
fn circuit() -> Arc<Circuit>;
|
||||
|
||||
/// Applies the keystream to the message
|
||||
fn apply_keystream(
|
||||
key: &[u8],
|
||||
iv: &[u8],
|
||||
start_ctr: usize,
|
||||
explicit_nonce: &[u8],
|
||||
msg: &[u8],
|
||||
) -> Result<Vec<u8>, StreamCipherError>;
|
||||
}
|
||||
|
||||
/// A circuit for AES-128 in counter mode.
|
||||
#[derive(Default, Debug, Clone)]
|
||||
pub struct Aes128Ctr;
|
||||
|
||||
impl CtrCircuit for Aes128Ctr {
|
||||
type KEY = [u8; 16];
|
||||
type BLOCK = [u8; 16];
|
||||
type IV = [u8; 4];
|
||||
type NONCE = [u8; 8];
|
||||
|
||||
const KEY_LEN: usize = 16;
|
||||
const BLOCK_LEN: usize = 16;
|
||||
const IV_LEN: usize = 4;
|
||||
const NONCE_LEN: usize = 8;
|
||||
|
||||
fn circuit() -> Arc<Circuit> {
|
||||
AES_CTR.clone()
|
||||
}
|
||||
|
||||
fn apply_keystream(
|
||||
key: &[u8],
|
||||
iv: &[u8],
|
||||
start_ctr: usize,
|
||||
explicit_nonce: &[u8],
|
||||
msg: &[u8],
|
||||
) -> Result<Vec<u8>, StreamCipherError> {
|
||||
use ::cipher::{KeyIvInit, StreamCipher, StreamCipherSeek};
|
||||
use aes::Aes128;
|
||||
use ctr::Ctr32BE;
|
||||
|
||||
let key: &[u8; 16] = key
|
||||
.try_into()
|
||||
.map_err(|_| StreamCipherError::InvalidKeyLength {
|
||||
expected: 16,
|
||||
actual: key.len(),
|
||||
})?;
|
||||
let iv: &[u8; 4] = iv
|
||||
.try_into()
|
||||
.map_err(|_| StreamCipherError::InvalidIvLength {
|
||||
expected: 4,
|
||||
actual: iv.len(),
|
||||
})?;
|
||||
let explicit_nonce: &[u8; 8] = explicit_nonce.try_into().map_err(|_| {
|
||||
StreamCipherError::InvalidExplicitNonceLength {
|
||||
expected: 8,
|
||||
actual: explicit_nonce.len(),
|
||||
}
|
||||
})?;
|
||||
|
||||
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 buf = msg.to_vec();
|
||||
|
||||
cipher
|
||||
.try_seek(start_ctr * Self::BLOCK_LEN)
|
||||
.expect("start counter is less than keystream length");
|
||||
cipher.apply_keystream(&mut buf);
|
||||
|
||||
Ok(buf)
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
use mpz_circuits::{circuits::aes128_trace, once_cell::sync::Lazy, trace, Circuit, CircuitBuilder};
|
||||
use std::sync::Arc;
|
||||
|
||||
/// AES encrypt counter block.
|
||||
///
|
||||
/// # Inputs
|
||||
///
|
||||
/// 0. KEY: 16-byte encryption key
|
||||
/// 1. IV: 4-byte IV
|
||||
/// 2. EXPLICIT_NONCE: 8-byte explicit nonce
|
||||
/// 3. CTR: 4-byte counter
|
||||
///
|
||||
/// # Outputs
|
||||
///
|
||||
/// 0. ECB: 16-byte output
|
||||
pub(crate) static AES_CTR: Lazy<Arc<Circuit>> = Lazy::new(|| {
|
||||
let builder = CircuitBuilder::new();
|
||||
let key = builder.add_array_input::<u8, 16>();
|
||||
let iv = builder.add_array_input::<u8, 4>();
|
||||
let nonce = builder.add_array_input::<u8, 8>();
|
||||
let ctr = builder.add_array_input::<u8, 4>();
|
||||
let ecb = aes_ctr_trace(builder.state(), key, iv, nonce, ctr);
|
||||
builder.add_output(ecb);
|
||||
|
||||
Arc::new(builder.build().unwrap())
|
||||
});
|
||||
|
||||
#[trace]
|
||||
#[dep(aes_128, aes128_trace)]
|
||||
#[allow(dead_code)]
|
||||
fn aes_ctr(key: [u8; 16], iv: [u8; 4], explicit_nonce: [u8; 8], ctr: [u8; 4]) -> [u8; 16] {
|
||||
let block: Vec<_> = iv.into_iter().chain(explicit_nonce).chain(ctr).collect();
|
||||
aes_128(key, block.try_into().unwrap())
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn aes_128(key: [u8; 16], msg: [u8; 16]) -> [u8; 16] {
|
||||
use aes::{
|
||||
cipher::{BlockEncrypt, KeyInit},
|
||||
Aes128,
|
||||
};
|
||||
|
||||
let aes = Aes128::new_from_slice(&key).unwrap();
|
||||
let mut ciphertext = msg.into();
|
||||
aes.encrypt_block(&mut ciphertext);
|
||||
ciphertext.into()
|
||||
}
|
||||
|
||||
/// Builds a circuit for computing the XOR of two arrays.
|
||||
pub(crate) fn build_array_xor(len: usize) -> Arc<Circuit> {
|
||||
let builder = CircuitBuilder::new();
|
||||
let a = builder.add_vec_input::<u8>(len);
|
||||
let b = builder.add_vec_input::<u8>(len);
|
||||
let c = a.into_iter().zip(b).map(|(a, b)| a ^ b).collect::<Vec<_>>();
|
||||
builder.add_output(c);
|
||||
Arc::new(builder.build().expect("circuit is valid"))
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use derive_builder::Builder;
|
||||
use mpz_garble::value::ValueRef;
|
||||
use std::fmt::Debug;
|
||||
|
||||
use crate::CtrCircuit;
|
||||
|
||||
/// Configuration for a stream cipher.
|
||||
#[derive(Debug, Clone, Builder)]
|
||||
pub struct StreamCipherConfig {
|
||||
/// The ID of the stream cipher.
|
||||
#[builder(setter(into))]
|
||||
pub(crate) id: String,
|
||||
/// The start block counter value.
|
||||
#[builder(default = "2")]
|
||||
pub(crate) start_ctr: usize,
|
||||
/// Transcript ID used to determine the unique identifiers
|
||||
/// for the plaintext bytes during encryption and decryption.
|
||||
#[builder(setter(into), default = "\"transcript\".to_string()")]
|
||||
pub(crate) transcript_id: String,
|
||||
}
|
||||
|
||||
impl StreamCipherConfig {
|
||||
/// Creates a new builder for the stream cipher configuration.
|
||||
pub fn builder() -> StreamCipherConfigBuilder {
|
||||
StreamCipherConfigBuilder::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct KeyBlockConfig<C: CtrCircuit> {
|
||||
pub(crate) key: ValueRef,
|
||||
pub(crate) iv: ValueRef,
|
||||
pub(crate) explicit_nonce: C::NONCE,
|
||||
pub(crate) ctr: u32,
|
||||
_pd: PhantomData<C>,
|
||||
}
|
||||
|
||||
impl<C: CtrCircuit> Debug for KeyBlockConfig<C> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("KeyBlockConfig")
|
||||
.field("key", &self.key)
|
||||
.field("iv", &self.iv)
|
||||
.field("explicit_nonce", &self.explicit_nonce)
|
||||
.field("ctr", &self.ctr)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: CtrCircuit> KeyBlockConfig<C> {
|
||||
pub(crate) fn new(key: ValueRef, iv: ValueRef, explicit_nonce: C::NONCE, ctr: u32) -> Self {
|
||||
Self {
|
||||
key,
|
||||
iv,
|
||||
explicit_nonce,
|
||||
ctr,
|
||||
_pd: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) enum InputText {
|
||||
Public { ids: Vec<String>, text: Vec<u8> },
|
||||
Private { ids: Vec<String>, text: Vec<u8> },
|
||||
Blind { ids: Vec<String> },
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for InputText {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Public { ids, .. } => f
|
||||
.debug_struct("Public")
|
||||
.field("ids", ids)
|
||||
.field("text", &"{{ ... }}")
|
||||
.finish(),
|
||||
Self::Private { ids, .. } => f
|
||||
.debug_struct("Private")
|
||||
.field("ids", ids)
|
||||
.field("text", &"{{ ... }}")
|
||||
.finish(),
|
||||
Self::Blind { ids, .. } => f.debug_struct("Blind").field("ids", ids).finish(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,447 +0,0 @@
|
||||
//! This crate provides a 2PC stream cipher implementation using a block cipher in counter mode.
|
||||
//!
|
||||
//! Each party plays a specific role, either the `StreamCipherLeader` or the `StreamCipherFollower`. Both parties
|
||||
//! work together to encrypt and decrypt messages using a shared key.
|
||||
//!
|
||||
//! # Transcript
|
||||
//!
|
||||
//! Using the `record` flag, the `StreamCipherFollower` can optionally use a dedicated stream when encoding the plaintext labels, which
|
||||
//! allows the `StreamCipherLeader` to build a transcript of active labels which are pushed to the provided `TranscriptSink`.
|
||||
//!
|
||||
//! Afterwards, the `StreamCipherLeader` can create commitments to the transcript which can be used in a selective disclosure protocol.
|
||||
|
||||
#![deny(missing_docs, unreachable_pub, unused_must_use)]
|
||||
#![deny(clippy::all)]
|
||||
#![deny(unsafe_code)]
|
||||
|
||||
mod cipher;
|
||||
mod circuit;
|
||||
mod config;
|
||||
mod stream_cipher;
|
||||
|
||||
pub use self::cipher::{Aes128Ctr, CtrCircuit};
|
||||
pub use config::{StreamCipherConfig, StreamCipherConfigBuilder, StreamCipherConfigBuilderError};
|
||||
pub use stream_cipher::MpcStreamCipher;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use mpz_garble::value::ValueRef;
|
||||
|
||||
/// Error that can occur when using a stream cipher
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[allow(missing_docs)]
|
||||
pub enum StreamCipherError {
|
||||
#[error(transparent)]
|
||||
MemoryError(#[from] mpz_garble::MemoryError),
|
||||
#[error(transparent)]
|
||||
ExecutionError(#[from] mpz_garble::ExecutionError),
|
||||
#[error(transparent)]
|
||||
DecodeError(#[from] mpz_garble::DecodeError),
|
||||
#[error(transparent)]
|
||||
ProveError(#[from] mpz_garble::ProveError),
|
||||
#[error(transparent)]
|
||||
VerifyError(#[from] mpz_garble::VerifyError),
|
||||
#[error("key and iv is not set")]
|
||||
KeyIvNotSet,
|
||||
#[error("invalid key length: expected {expected}, got {actual}")]
|
||||
InvalidKeyLength { expected: usize, actual: usize },
|
||||
#[error("invalid iv length: expected {expected}, got {actual}")]
|
||||
InvalidIvLength { expected: usize, actual: usize },
|
||||
#[error("invalid explicit nonce length: expected {expected}, got {actual}")]
|
||||
InvalidExplicitNonceLength { expected: usize, actual: usize },
|
||||
#[error("missing value for {0}")]
|
||||
MissingValue(String),
|
||||
}
|
||||
|
||||
/// A trait for MPC stream ciphers.
|
||||
#[async_trait]
|
||||
pub trait StreamCipher<Cipher>: Send + Sync
|
||||
where
|
||||
Cipher: cipher::CtrCircuit,
|
||||
{
|
||||
/// Sets the key and iv for the stream cipher.
|
||||
fn set_key(&mut self, key: ValueRef, iv: ValueRef);
|
||||
|
||||
/// Decodes the key for the stream cipher, revealing it to this party.
|
||||
async fn decode_key_private(&mut self) -> Result<(), StreamCipherError>;
|
||||
|
||||
/// Decodes the key for the stream cipher, revealing it to the other party(s).
|
||||
async fn decode_key_blind(&mut self) -> Result<(), StreamCipherError>;
|
||||
|
||||
/// Sets the transcript id
|
||||
///
|
||||
/// The stream cipher 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);
|
||||
|
||||
/// Applies the keystream to the given plaintext, where all parties
|
||||
/// provide the plaintext as an input.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `explicit_nonce`: The explicit nonce to use for the keystream.
|
||||
/// * `plaintext`: The message to apply the keystream to.
|
||||
async fn encrypt_public(
|
||||
&mut self,
|
||||
explicit_nonce: Vec<u8>,
|
||||
plaintext: Vec<u8>,
|
||||
) -> Result<Vec<u8>, StreamCipherError>;
|
||||
|
||||
/// Applies the keystream to the given plaintext without revealing it
|
||||
/// to the other party(s).
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `explicit_nonce`: The explicit nonce to use for the keystream.
|
||||
/// * `plaintext`: The message to apply the keystream to.
|
||||
async fn encrypt_private(
|
||||
&mut self,
|
||||
explicit_nonce: Vec<u8>,
|
||||
plaintext: Vec<u8>,
|
||||
) -> Result<Vec<u8>, StreamCipherError>;
|
||||
|
||||
/// Applies the keystream to a plaintext provided by another party.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `explicit_nonce`: The explicit nonce to use for the keystream.
|
||||
/// * `len`: The length of the plaintext provided by another party.
|
||||
async fn encrypt_blind(
|
||||
&mut self,
|
||||
explicit_nonce: Vec<u8>,
|
||||
len: usize,
|
||||
) -> Result<Vec<u8>, StreamCipherError>;
|
||||
|
||||
/// Decrypts a ciphertext by removing the keystream, where the plaintext
|
||||
/// is revealed to all parties.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `explicit_nonce`: The explicit nonce to use for the keystream.
|
||||
/// * `ciphertext`: The ciphertext to decrypt.
|
||||
async fn decrypt_public(
|
||||
&mut self,
|
||||
explicit_nonce: Vec<u8>,
|
||||
ciphertext: Vec<u8>,
|
||||
) -> Result<Vec<u8>, StreamCipherError>;
|
||||
|
||||
/// Decrypts a ciphertext by removing the keystream, where the plaintext
|
||||
/// is only revealed to this party.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `explicit_nonce`: The explicit nonce to use for the keystream.
|
||||
/// * `ciphertext`: The ciphertext to decrypt.
|
||||
async fn decrypt_private(
|
||||
&mut self,
|
||||
explicit_nonce: Vec<u8>,
|
||||
ciphertext: Vec<u8>,
|
||||
) -> Result<Vec<u8>, StreamCipherError>;
|
||||
|
||||
/// Decrypts a ciphertext by removing the keystream, where the plaintext
|
||||
/// is not revealed to this party.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `explicit_nonce`: The explicit nonce to use for the keystream.
|
||||
/// * `ciphertext`: The ciphertext to decrypt.
|
||||
async fn decrypt_blind(
|
||||
&mut self,
|
||||
explicit_nonce: Vec<u8>,
|
||||
ciphertext: Vec<u8>,
|
||||
) -> Result<(), StreamCipherError>;
|
||||
|
||||
/// 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.
|
||||
/// * `ciphertext`: The ciphertext to decrypt and prove.
|
||||
async fn prove_plaintext(
|
||||
&mut self,
|
||||
explicit_nonce: Vec<u8>,
|
||||
ciphertext: Vec<u8>,
|
||||
) -> Result<Vec<u8>, StreamCipherError>;
|
||||
|
||||
/// 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.
|
||||
/// * `ciphertext`: The ciphertext to verify.
|
||||
async fn verify_plaintext(
|
||||
&mut self,
|
||||
explicit_nonce: Vec<u8>,
|
||||
ciphertext: Vec<u8>,
|
||||
) -> Result<(), StreamCipherError>;
|
||||
|
||||
/// Returns an additive share of the keystream block for the given explicit nonce and counter.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `explicit_nonce`: The explicit nonce to use for the keystream block.
|
||||
/// * `ctr`: The counter to use for the keystream block.
|
||||
async fn share_keystream_block(
|
||||
&mut self,
|
||||
explicit_nonce: Vec<u8>,
|
||||
ctr: usize,
|
||||
) -> Result<Vec<u8>, StreamCipherError>;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::cipher::Aes128Ctr;
|
||||
|
||||
use super::*;
|
||||
|
||||
use mpz_garble::{
|
||||
protocol::deap::mock::{
|
||||
create_mock_deap_vm, MockFollower, MockFollowerThread, MockLeader, MockLeaderThread,
|
||||
},
|
||||
Memory, Vm,
|
||||
};
|
||||
use rstest::*;
|
||||
|
||||
async fn create_test_pair<C: CtrCircuit>(
|
||||
start_ctr: usize,
|
||||
key: [u8; 16],
|
||||
iv: [u8; 4],
|
||||
thread_count: usize,
|
||||
) -> (
|
||||
(
|
||||
MpcStreamCipher<C, MockLeaderThread>,
|
||||
MpcStreamCipher<C, MockFollowerThread>,
|
||||
),
|
||||
(MockLeader, MockFollower),
|
||||
) {
|
||||
let (mut leader_vm, mut follower_vm) = create_mock_deap_vm("test").await;
|
||||
|
||||
let leader_thread = leader_vm.new_thread("key_config").await.unwrap();
|
||||
let leader_key = leader_thread.new_public_input::<[u8; 16]>("key").unwrap();
|
||||
let leader_iv = leader_thread.new_public_input::<[u8; 4]>("iv").unwrap();
|
||||
|
||||
leader_thread.assign(&leader_key, key).unwrap();
|
||||
leader_thread.assign(&leader_iv, iv).unwrap();
|
||||
|
||||
let follower_thread = follower_vm.new_thread("key_config").await.unwrap();
|
||||
let follower_key = follower_thread.new_public_input::<[u8; 16]>("key").unwrap();
|
||||
let follower_iv = follower_thread.new_public_input::<[u8; 4]>("iv").unwrap();
|
||||
|
||||
follower_thread.assign(&follower_key, key).unwrap();
|
||||
follower_thread.assign(&follower_iv, iv).unwrap();
|
||||
|
||||
let leader_thread_pool = leader_vm
|
||||
.new_thread_pool("mock", thread_count)
|
||||
.await
|
||||
.unwrap();
|
||||
let follower_thread_pool = follower_vm
|
||||
.new_thread_pool("mock", thread_count)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let leader_config = StreamCipherConfig::builder()
|
||||
.id("test")
|
||||
.start_ctr(start_ctr)
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let follower_config = StreamCipherConfig::builder()
|
||||
.id("test")
|
||||
.start_ctr(start_ctr)
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let mut leader = MpcStreamCipher::<C, _>::new(leader_config, leader_thread_pool);
|
||||
leader.set_key(leader_key, leader_iv);
|
||||
|
||||
let mut follower = MpcStreamCipher::<C, _>::new(follower_config, follower_thread_pool);
|
||||
follower.set_key(follower_key, follower_iv);
|
||||
|
||||
((leader, follower), (leader_vm, follower_vm))
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[timeout(Duration::from_millis(10000))]
|
||||
#[tokio::test]
|
||||
async fn test_stream_cipher_public() {
|
||||
let key = [0u8; 16];
|
||||
let iv = [0u8; 4];
|
||||
let explicit_nonce = [0u8; 8];
|
||||
|
||||
let msg = b"This is a test message which will be encrypted using AES-CTR.".to_vec();
|
||||
|
||||
let ((mut leader, mut follower), (_leader_vm, _follower_vm)) =
|
||||
create_test_pair::<Aes128Ctr>(1, key, iv, 8).await;
|
||||
|
||||
let leader_fut = async {
|
||||
let leader_encrypted_msg = leader
|
||||
.encrypt_public(explicit_nonce.to_vec(), msg.clone())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let leader_decrypted_msg = leader
|
||||
.decrypt_public(explicit_nonce.to_vec(), leader_encrypted_msg.clone())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
(leader_encrypted_msg, leader_decrypted_msg)
|
||||
};
|
||||
|
||||
let follower_fut = async {
|
||||
let follower_encrypted_msg = follower
|
||||
.encrypt_public(explicit_nonce.to_vec(), msg.clone())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let follower_decrypted_msg = follower
|
||||
.decrypt_public(explicit_nonce.to_vec(), follower_encrypted_msg.clone())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
(follower_encrypted_msg, follower_decrypted_msg)
|
||||
};
|
||||
|
||||
let (
|
||||
(leader_encrypted_msg, leader_decrypted_msg),
|
||||
(follower_encrypted_msg, follower_decrypted_msg),
|
||||
) = futures::join!(leader_fut, follower_fut);
|
||||
|
||||
let reference = Aes128Ctr::apply_keystream(&key, &iv, 1, &explicit_nonce, &msg).unwrap();
|
||||
|
||||
assert_eq!(leader_encrypted_msg, reference);
|
||||
assert_eq!(leader_decrypted_msg, msg);
|
||||
assert_eq!(follower_encrypted_msg, reference);
|
||||
assert_eq!(follower_decrypted_msg, msg);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[timeout(Duration::from_millis(10000))]
|
||||
#[tokio::test]
|
||||
async fn test_stream_cipher_private() {
|
||||
let key = [0u8; 16];
|
||||
let iv = [0u8; 4];
|
||||
let explicit_nonce = [1u8; 8];
|
||||
|
||||
let msg = b"This is a test message which will be encrypted using AES-CTR.".to_vec();
|
||||
|
||||
let ciphertext = Aes128Ctr::apply_keystream(&key, &iv, 1, &explicit_nonce, &msg).unwrap();
|
||||
|
||||
let ((mut leader, mut follower), (mut leader_vm, mut follower_vm)) =
|
||||
create_test_pair::<Aes128Ctr>(1, key, iv, 8).await;
|
||||
|
||||
let leader_fut = async {
|
||||
let leader_decrypted_msg = leader
|
||||
.decrypt_private(explicit_nonce.to_vec(), ciphertext.clone())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let leader_encrypted_msg = leader
|
||||
.encrypt_private(explicit_nonce.to_vec(), leader_decrypted_msg.clone())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
(leader_encrypted_msg, leader_decrypted_msg)
|
||||
};
|
||||
|
||||
let follower_fut = async {
|
||||
follower
|
||||
.decrypt_blind(explicit_nonce.to_vec(), ciphertext.clone())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
follower
|
||||
.encrypt_blind(explicit_nonce.to_vec(), msg.len())
|
||||
.await
|
||||
.unwrap()
|
||||
};
|
||||
|
||||
let ((leader_encrypted_msg, leader_decrypted_msg), follower_encrypted_msg) =
|
||||
futures::join!(leader_fut, follower_fut);
|
||||
|
||||
assert_eq!(leader_encrypted_msg, ciphertext);
|
||||
assert_eq!(leader_decrypted_msg, msg);
|
||||
assert_eq!(follower_encrypted_msg, ciphertext);
|
||||
|
||||
futures::try_join!(leader_vm.finalize(), follower_vm.finalize()).unwrap();
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[timeout(Duration::from_millis(10000))]
|
||||
#[tokio::test]
|
||||
async fn test_stream_cipher_share_key_block() {
|
||||
let key = [0u8; 16];
|
||||
let iv = [0u8; 4];
|
||||
let explicit_nonce = [0u8; 8];
|
||||
|
||||
let ((mut leader, mut follower), (_leader_vm, _follower_vm)) =
|
||||
create_test_pair::<Aes128Ctr>(1, key, iv, 8).await;
|
||||
|
||||
let leader_fut = async {
|
||||
leader
|
||||
.share_keystream_block(explicit_nonce.to_vec(), 1)
|
||||
.await
|
||||
.unwrap()
|
||||
};
|
||||
|
||||
let follower_fut = async {
|
||||
follower
|
||||
.share_keystream_block(explicit_nonce.to_vec(), 1)
|
||||
.await
|
||||
.unwrap()
|
||||
};
|
||||
|
||||
let (leader_share, follower_share) = futures::join!(leader_fut, follower_fut);
|
||||
|
||||
let key_block = leader_share
|
||||
.into_iter()
|
||||
.zip(follower_share)
|
||||
.map(|(a, b)| a ^ b)
|
||||
.collect::<Vec<u8>>();
|
||||
|
||||
let reference =
|
||||
Aes128Ctr::apply_keystream(&key, &iv, 1, &explicit_nonce, &[0u8; 16]).unwrap();
|
||||
|
||||
assert_eq!(reference, key_block);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[timeout(Duration::from_millis(10000))]
|
||||
#[tokio::test]
|
||||
async fn test_stream_cipher_zk() {
|
||||
let key = [0u8; 16];
|
||||
let iv = [0u8; 4];
|
||||
let explicit_nonce = [1u8; 8];
|
||||
|
||||
let msg = b"This is a test message which will be encrypted using AES-CTR.".to_vec();
|
||||
|
||||
let ciphertext = Aes128Ctr::apply_keystream(&key, &iv, 2, &explicit_nonce, &msg).unwrap();
|
||||
|
||||
let ((mut leader, mut follower), (mut leader_vm, mut follower_vm)) =
|
||||
create_test_pair::<Aes128Ctr>(2, key, iv, 8).await;
|
||||
|
||||
futures::try_join!(leader.decode_key_private(), follower.decode_key_blind()).unwrap();
|
||||
|
||||
futures::try_join!(
|
||||
leader.prove_plaintext(explicit_nonce.to_vec(), ciphertext.clone()),
|
||||
follower.verify_plaintext(explicit_nonce.to_vec(), ciphertext)
|
||||
)
|
||||
.unwrap();
|
||||
futures::try_join!(leader_vm.finalize(), follower_vm.finalize()).unwrap();
|
||||
}
|
||||
}
|
||||
@@ -1,842 +0,0 @@
|
||||
use async_trait::async_trait;
|
||||
use mpz_circuits::types::Value;
|
||||
use std::{collections::HashMap, fmt::Debug, marker::PhantomData};
|
||||
|
||||
use mpz_garble::{
|
||||
value::ValueRef, Decode, DecodePrivate, Execute, Memory, Prove, Thread, ThreadPool, Verify,
|
||||
};
|
||||
use utils::id::NestedId;
|
||||
|
||||
use crate::{
|
||||
cipher::CtrCircuit,
|
||||
circuit::build_array_xor,
|
||||
config::{InputText, KeyBlockConfig, StreamCipherConfig},
|
||||
StreamCipher, StreamCipherError,
|
||||
};
|
||||
|
||||
/// An MPC stream cipher.
|
||||
pub struct MpcStreamCipher<C, E>
|
||||
where
|
||||
C: CtrCircuit,
|
||||
E: Thread + Execute + Decode + DecodePrivate + Send + Sync,
|
||||
{
|
||||
config: StreamCipherConfig,
|
||||
state: State,
|
||||
thread_pool: ThreadPool<E>,
|
||||
|
||||
_cipher: PhantomData<C>,
|
||||
}
|
||||
|
||||
struct State {
|
||||
/// Encoded key and IV for the cipher.
|
||||
encoded_key_iv: Option<EncodedKeyAndIv>,
|
||||
/// Key and IV for the cipher.
|
||||
key_iv: Option<KeyAndIv>,
|
||||
/// Unique identifier for each execution of the cipher.
|
||||
execution_id: NestedId,
|
||||
/// Unique identifier for each byte in the transcript.
|
||||
transcript_counter: NestedId,
|
||||
/// Unique identifier for each byte in the ciphertext (prefixed with execution id).
|
||||
ciphertext_counter: NestedId,
|
||||
/// Persists the transcript counter for each transcript id.
|
||||
transcript_state: HashMap<String, NestedId>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct EncodedKeyAndIv {
|
||||
key: ValueRef,
|
||||
iv: ValueRef,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct KeyAndIv {
|
||||
key: Vec<u8>,
|
||||
iv: Vec<u8>,
|
||||
}
|
||||
|
||||
impl<C, E> MpcStreamCipher<C, E>
|
||||
where
|
||||
C: CtrCircuit,
|
||||
E: Thread + Execute + Prove + Verify + Decode + DecodePrivate + Send + Sync + 'static,
|
||||
{
|
||||
/// Creates a new counter-mode cipher.
|
||||
pub fn new(config: StreamCipherConfig, thread_pool: ThreadPool<E>) -> Self {
|
||||
let execution_id = NestedId::new(&config.id).append_counter();
|
||||
let transcript_counter = NestedId::new(&config.transcript_id).append_counter();
|
||||
let ciphertext_counter = execution_id.append_string("ciphertext").append_counter();
|
||||
|
||||
Self {
|
||||
config,
|
||||
state: State {
|
||||
encoded_key_iv: None,
|
||||
key_iv: None,
|
||||
execution_id,
|
||||
transcript_counter,
|
||||
ciphertext_counter,
|
||||
transcript_state: HashMap::new(),
|
||||
},
|
||||
thread_pool,
|
||||
_cipher: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns unique identifiers for the next bytes in the transcript.
|
||||
fn plaintext_ids(&mut self, len: usize) -> Vec<String> {
|
||||
(0..len)
|
||||
.map(|_| {
|
||||
self.state
|
||||
.transcript_counter
|
||||
.increment_in_place()
|
||||
.to_string()
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Returns unique identifiers for the next bytes in the ciphertext.
|
||||
fn ciphertext_ids(&mut self, len: usize) -> Vec<String> {
|
||||
(0..len)
|
||||
.map(|_| {
|
||||
self.state
|
||||
.ciphertext_counter
|
||||
.increment_in_place()
|
||||
.to_string()
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
async fn compute_keystream(
|
||||
&mut self,
|
||||
explicit_nonce: Vec<u8>,
|
||||
start_ctr: usize,
|
||||
len: usize,
|
||||
mode: ExecutionMode,
|
||||
) -> Result<ValueRef, StreamCipherError> {
|
||||
let EncodedKeyAndIv { key, iv } = self
|
||||
.state
|
||||
.encoded_key_iv
|
||||
.clone()
|
||||
.ok_or(StreamCipherError::KeyIvNotSet)?;
|
||||
|
||||
let explicit_nonce_len = explicit_nonce.len();
|
||||
let explicit_nonce: C::NONCE = explicit_nonce.try_into().map_err(|_| {
|
||||
StreamCipherError::InvalidExplicitNonceLength {
|
||||
expected: C::NONCE_LEN,
|
||||
actual: explicit_nonce_len,
|
||||
}
|
||||
})?;
|
||||
|
||||
// Divide msg length by block size rounding up
|
||||
let block_count = (len / C::BLOCK_LEN) + (len % C::BLOCK_LEN != 0) as usize;
|
||||
|
||||
let block_configs = (0..block_count)
|
||||
.map(|i| {
|
||||
KeyBlockConfig::<C>::new(
|
||||
key.clone(),
|
||||
iv.clone(),
|
||||
explicit_nonce,
|
||||
(start_ctr + i) as u32,
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let execution_id = self.state.execution_id.increment_in_place();
|
||||
|
||||
let keystream = compute_keystream(
|
||||
&mut self.thread_pool,
|
||||
execution_id,
|
||||
block_configs,
|
||||
len,
|
||||
mode,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(keystream)
|
||||
}
|
||||
|
||||
/// Applies the keystream to the provided input text.
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(level = "trace", skip(self), err)
|
||||
)]
|
||||
async fn apply_keystream(
|
||||
&mut self,
|
||||
input_text: InputText,
|
||||
keystream: ValueRef,
|
||||
mode: ExecutionMode,
|
||||
) -> Result<ValueRef, StreamCipherError> {
|
||||
let execution_id = self.state.execution_id.increment_in_place();
|
||||
|
||||
let mut scope = self.thread_pool.new_scope();
|
||||
scope.push(move |thread| {
|
||||
Box::pin(apply_keystream(
|
||||
thread,
|
||||
mode,
|
||||
execution_id,
|
||||
input_text,
|
||||
keystream,
|
||||
))
|
||||
});
|
||||
|
||||
let output_text = scope.wait().await.into_iter().next().unwrap()?;
|
||||
|
||||
Ok(output_text)
|
||||
}
|
||||
|
||||
async fn decode_public(&mut self, value: ValueRef) -> Result<Value, StreamCipherError> {
|
||||
let mut scope = self.thread_pool.new_scope();
|
||||
scope.push(move |thread| Box::pin(async move { thread.decode(&[value]).await }));
|
||||
let mut output = scope.wait().await.into_iter().next().unwrap()?;
|
||||
Ok(output.pop().unwrap())
|
||||
}
|
||||
|
||||
async fn decode_private(&mut self, value: ValueRef) -> Result<Value, StreamCipherError> {
|
||||
let mut scope = self.thread_pool.new_scope();
|
||||
scope.push(move |thread| Box::pin(async move { thread.decode_private(&[value]).await }));
|
||||
let mut output = scope.wait().await.into_iter().next().unwrap()?;
|
||||
Ok(output.pop().unwrap())
|
||||
}
|
||||
|
||||
async fn decode_blind(&mut self, value: ValueRef) -> Result<(), StreamCipherError> {
|
||||
let mut scope = self.thread_pool.new_scope();
|
||||
scope.push(move |thread| Box::pin(async move { thread.decode_blind(&[value]).await }));
|
||||
scope.wait().await.into_iter().next().unwrap()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn prove(&mut self, value: ValueRef) -> Result<(), StreamCipherError> {
|
||||
let mut scope = self.thread_pool.new_scope();
|
||||
scope.push(move |thread| Box::pin(async move { thread.prove(&[value]).await }));
|
||||
scope.wait().await.into_iter().next().unwrap()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn verify(&mut self, value: ValueRef, expected: Value) -> Result<(), StreamCipherError> {
|
||||
let mut scope = self.thread_pool.new_scope();
|
||||
scope.push(move |thread| {
|
||||
Box::pin(async move { thread.verify(&[value], &[expected]).await })
|
||||
});
|
||||
scope.wait().await.into_iter().next().unwrap()?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<C, E> StreamCipher<C> for MpcStreamCipher<C, E>
|
||||
where
|
||||
C: CtrCircuit,
|
||||
E: Thread + Execute + Prove + Verify + Decode + DecodePrivate + Send + Sync + 'static,
|
||||
{
|
||||
fn set_key(&mut self, key: ValueRef, iv: ValueRef) {
|
||||
self.state.encoded_key_iv = Some(EncodedKeyAndIv { key, iv });
|
||||
}
|
||||
|
||||
async fn decode_key_private(&mut self) -> Result<(), StreamCipherError> {
|
||||
let EncodedKeyAndIv { key, iv } = self
|
||||
.state
|
||||
.encoded_key_iv
|
||||
.clone()
|
||||
.ok_or(StreamCipherError::KeyIvNotSet)?;
|
||||
|
||||
let mut scope = self.thread_pool.new_scope();
|
||||
scope.push(move |thread| Box::pin(async move { thread.decode_private(&[key, iv]).await }));
|
||||
let output = scope.wait().await.into_iter().next().unwrap()?;
|
||||
|
||||
let [key, iv]: [_; 2] = output.try_into().expect("decoded 2 values");
|
||||
let key: Vec<u8> = key.try_into().expect("key is an array");
|
||||
let iv: Vec<u8> = iv.try_into().expect("iv is an array");
|
||||
|
||||
self.state.key_iv = Some(KeyAndIv { key, iv });
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn decode_key_blind(&mut self) -> Result<(), StreamCipherError> {
|
||||
let EncodedKeyAndIv { key, iv } = self
|
||||
.state
|
||||
.encoded_key_iv
|
||||
.clone()
|
||||
.ok_or(StreamCipherError::KeyIvNotSet)?;
|
||||
|
||||
let mut scope = self.thread_pool.new_scope();
|
||||
scope.push(move |thread| Box::pin(async move { thread.decode_blind(&[key, iv]).await }));
|
||||
scope.wait().await.into_iter().next().unwrap()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_transcript_id(&mut self, id: &str) {
|
||||
let current_id = self
|
||||
.state
|
||||
.transcript_counter
|
||||
.root()
|
||||
.expect("root id is set");
|
||||
let current_counter = self.state.transcript_counter.clone();
|
||||
self.state
|
||||
.transcript_state
|
||||
.insert(current_id.to_string(), current_counter);
|
||||
|
||||
if let Some(counter) = self.state.transcript_state.get(id) {
|
||||
self.state.transcript_counter = counter.clone();
|
||||
} else {
|
||||
self.state.transcript_counter = NestedId::new(id).append_counter();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(level = "debug", skip(self, plaintext), err)
|
||||
)]
|
||||
async fn encrypt_public(
|
||||
&mut self,
|
||||
explicit_nonce: Vec<u8>,
|
||||
plaintext: Vec<u8>,
|
||||
) -> Result<Vec<u8>, StreamCipherError> {
|
||||
let keystream = self
|
||||
.compute_keystream(
|
||||
explicit_nonce,
|
||||
self.config.start_ctr,
|
||||
plaintext.len(),
|
||||
ExecutionMode::Mpc,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let plaintext_ids = self.plaintext_ids(plaintext.len());
|
||||
let ciphertext = self
|
||||
.apply_keystream(
|
||||
InputText::Public {
|
||||
ids: plaintext_ids,
|
||||
text: plaintext,
|
||||
},
|
||||
keystream,
|
||||
ExecutionMode::Mpc,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let ciphertext: Vec<u8> = self
|
||||
.decode_public(ciphertext)
|
||||
.await?
|
||||
.try_into()
|
||||
.expect("ciphertext is array");
|
||||
|
||||
Ok(ciphertext)
|
||||
}
|
||||
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(level = "debug", skip(self, plaintext), err)
|
||||
)]
|
||||
async fn encrypt_private(
|
||||
&mut self,
|
||||
explicit_nonce: Vec<u8>,
|
||||
plaintext: Vec<u8>,
|
||||
) -> Result<Vec<u8>, StreamCipherError> {
|
||||
let keystream = self
|
||||
.compute_keystream(
|
||||
explicit_nonce,
|
||||
self.config.start_ctr,
|
||||
plaintext.len(),
|
||||
ExecutionMode::Mpc,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let plaintext_ids = self.plaintext_ids(plaintext.len());
|
||||
let ciphertext = self
|
||||
.apply_keystream(
|
||||
InputText::Private {
|
||||
ids: plaintext_ids,
|
||||
text: plaintext,
|
||||
},
|
||||
keystream,
|
||||
ExecutionMode::Mpc,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let ciphertext: Vec<u8> = self
|
||||
.decode_public(ciphertext)
|
||||
.await?
|
||||
.try_into()
|
||||
.expect("ciphertext is array");
|
||||
|
||||
Ok(ciphertext)
|
||||
}
|
||||
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(level = "debug", skip(self), err)
|
||||
)]
|
||||
async fn encrypt_blind(
|
||||
&mut self,
|
||||
explicit_nonce: Vec<u8>,
|
||||
len: usize,
|
||||
) -> Result<Vec<u8>, StreamCipherError> {
|
||||
let keystream = self
|
||||
.compute_keystream(
|
||||
explicit_nonce,
|
||||
self.config.start_ctr,
|
||||
len,
|
||||
ExecutionMode::Mpc,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let plaintext_ids = self.plaintext_ids(len);
|
||||
let ciphertext = self
|
||||
.apply_keystream(
|
||||
InputText::Blind { ids: plaintext_ids },
|
||||
keystream,
|
||||
ExecutionMode::Mpc,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let ciphertext: Vec<u8> = self
|
||||
.decode_public(ciphertext)
|
||||
.await?
|
||||
.try_into()
|
||||
.expect("ciphertext is array");
|
||||
|
||||
Ok(ciphertext)
|
||||
}
|
||||
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(level = "debug", skip(self), err)
|
||||
)]
|
||||
async fn decrypt_public(
|
||||
&mut self,
|
||||
explicit_nonce: Vec<u8>,
|
||||
ciphertext: Vec<u8>,
|
||||
) -> Result<Vec<u8>, StreamCipherError> {
|
||||
// TODO: We may want to support writing to the transcript when decrypting
|
||||
// in public mode.
|
||||
let keystream = self
|
||||
.compute_keystream(
|
||||
explicit_nonce,
|
||||
self.config.start_ctr,
|
||||
ciphertext.len(),
|
||||
ExecutionMode::Mpc,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let ciphertext_ids = self.ciphertext_ids(ciphertext.len());
|
||||
let plaintext = self
|
||||
.apply_keystream(
|
||||
InputText::Public {
|
||||
ids: ciphertext_ids,
|
||||
text: ciphertext,
|
||||
},
|
||||
keystream,
|
||||
ExecutionMode::Mpc,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let plaintext: Vec<u8> = self
|
||||
.decode_public(plaintext)
|
||||
.await?
|
||||
.try_into()
|
||||
.expect("plaintext is array");
|
||||
|
||||
Ok(plaintext)
|
||||
}
|
||||
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(level = "debug", skip(self), err)
|
||||
)]
|
||||
async fn decrypt_private(
|
||||
&mut self,
|
||||
explicit_nonce: Vec<u8>,
|
||||
ciphertext: Vec<u8>,
|
||||
) -> Result<Vec<u8>, StreamCipherError> {
|
||||
let keystream_ref = self
|
||||
.compute_keystream(
|
||||
explicit_nonce,
|
||||
self.config.start_ctr,
|
||||
ciphertext.len(),
|
||||
ExecutionMode::Mpc,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let keystream: Vec<u8> = self
|
||||
.decode_private(keystream_ref.clone())
|
||||
.await?
|
||||
.try_into()
|
||||
.expect("keystream is array");
|
||||
|
||||
let plaintext = ciphertext
|
||||
.into_iter()
|
||||
.zip(keystream)
|
||||
.map(|(c, k)| c ^ k)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Prove plaintext encrypts back to ciphertext
|
||||
let plaintext_ids = self.plaintext_ids(plaintext.len());
|
||||
let ciphertext = self
|
||||
.apply_keystream(
|
||||
InputText::Private {
|
||||
ids: plaintext_ids,
|
||||
text: plaintext.clone(),
|
||||
},
|
||||
keystream_ref,
|
||||
ExecutionMode::Prove,
|
||||
)
|
||||
.await?;
|
||||
|
||||
self.prove(ciphertext).await?;
|
||||
|
||||
Ok(plaintext)
|
||||
}
|
||||
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(level = "debug", skip(self), err)
|
||||
)]
|
||||
async fn decrypt_blind(
|
||||
&mut self,
|
||||
explicit_nonce: Vec<u8>,
|
||||
ciphertext: Vec<u8>,
|
||||
) -> Result<(), StreamCipherError> {
|
||||
let keystream_ref = self
|
||||
.compute_keystream(
|
||||
explicit_nonce,
|
||||
self.config.start_ctr,
|
||||
ciphertext.len(),
|
||||
ExecutionMode::Mpc,
|
||||
)
|
||||
.await?;
|
||||
|
||||
self.decode_blind(keystream_ref.clone()).await?;
|
||||
|
||||
// Verify the plaintext encrypts back to ciphertext
|
||||
let plaintext_ids = self.plaintext_ids(ciphertext.len());
|
||||
let ciphertext_ref = self
|
||||
.apply_keystream(
|
||||
InputText::Blind { ids: plaintext_ids },
|
||||
keystream_ref,
|
||||
ExecutionMode::Verify,
|
||||
)
|
||||
.await?;
|
||||
|
||||
self.verify(ciphertext_ref, ciphertext.into()).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn prove_plaintext(
|
||||
&mut self,
|
||||
explicit_nonce: Vec<u8>,
|
||||
ciphertext: Vec<u8>,
|
||||
) -> Result<Vec<u8>, StreamCipherError> {
|
||||
let KeyAndIv { key, iv } = self
|
||||
.state
|
||||
.key_iv
|
||||
.clone()
|
||||
.ok_or(StreamCipherError::KeyIvNotSet)?;
|
||||
|
||||
let plaintext = C::apply_keystream(
|
||||
&key,
|
||||
&iv,
|
||||
self.config.start_ctr,
|
||||
&explicit_nonce,
|
||||
&ciphertext,
|
||||
)?;
|
||||
|
||||
// Prove plaintext encrypts back to ciphertext
|
||||
let keystream = self
|
||||
.compute_keystream(
|
||||
explicit_nonce,
|
||||
self.config.start_ctr,
|
||||
plaintext.len(),
|
||||
ExecutionMode::Prove,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let plaintext_ids = self.plaintext_ids(plaintext.len());
|
||||
let ciphertext = self
|
||||
.apply_keystream(
|
||||
InputText::Private {
|
||||
ids: plaintext_ids,
|
||||
text: plaintext.clone(),
|
||||
},
|
||||
keystream,
|
||||
ExecutionMode::Prove,
|
||||
)
|
||||
.await?;
|
||||
|
||||
self.prove(ciphertext).await?;
|
||||
|
||||
Ok(plaintext)
|
||||
}
|
||||
|
||||
async fn verify_plaintext(
|
||||
&mut self,
|
||||
explicit_nonce: Vec<u8>,
|
||||
ciphertext: Vec<u8>,
|
||||
) -> Result<(), StreamCipherError> {
|
||||
let keystream = self
|
||||
.compute_keystream(
|
||||
explicit_nonce,
|
||||
self.config.start_ctr,
|
||||
ciphertext.len(),
|
||||
ExecutionMode::Verify,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let plaintext_ids = self.plaintext_ids(ciphertext.len());
|
||||
let ciphertext_ref = self
|
||||
.apply_keystream(
|
||||
InputText::Blind { ids: plaintext_ids },
|
||||
keystream,
|
||||
ExecutionMode::Verify,
|
||||
)
|
||||
.await?;
|
||||
|
||||
self.verify(ciphertext_ref, ciphertext.into()).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(level = "info", skip(self), err)
|
||||
)]
|
||||
async fn share_keystream_block(
|
||||
&mut self,
|
||||
explicit_nonce: Vec<u8>,
|
||||
ctr: usize,
|
||||
) -> Result<Vec<u8>, StreamCipherError> {
|
||||
let EncodedKeyAndIv { key, iv } = self
|
||||
.state
|
||||
.encoded_key_iv
|
||||
.clone()
|
||||
.ok_or(StreamCipherError::KeyIvNotSet)?;
|
||||
|
||||
let explicit_nonce_len = explicit_nonce.len();
|
||||
let explicit_nonce: C::NONCE = explicit_nonce.try_into().map_err(|_| {
|
||||
StreamCipherError::InvalidExplicitNonceLength {
|
||||
expected: C::NONCE_LEN,
|
||||
actual: explicit_nonce_len,
|
||||
}
|
||||
})?;
|
||||
|
||||
let block_id = self.state.execution_id.increment_in_place();
|
||||
let mut scope = self.thread_pool.new_scope();
|
||||
scope.push(move |thread| {
|
||||
Box::pin(async move {
|
||||
let key_block = compute_key_block(
|
||||
thread,
|
||||
block_id,
|
||||
KeyBlockConfig::<C>::new(key, iv, explicit_nonce, ctr as u32),
|
||||
ExecutionMode::Mpc,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let share = thread
|
||||
.decode_shared(&[key_block])
|
||||
.await?
|
||||
.into_iter()
|
||||
.next()
|
||||
.unwrap();
|
||||
|
||||
Ok::<_, StreamCipherError>(share)
|
||||
})
|
||||
});
|
||||
|
||||
let share: Vec<u8> = scope
|
||||
.wait()
|
||||
.await
|
||||
.into_iter()
|
||||
.next()
|
||||
.unwrap()?
|
||||
.try_into()
|
||||
.expect("share is an array");
|
||||
|
||||
Ok(share)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
enum ExecutionMode {
|
||||
Mpc,
|
||||
Prove,
|
||||
Verify,
|
||||
}
|
||||
|
||||
async fn apply_keystream<T: Memory + Execute + Prove + Verify + Decode + DecodePrivate + Send>(
|
||||
thread: &mut T,
|
||||
mode: ExecutionMode,
|
||||
execution_id: NestedId,
|
||||
input_text: InputText,
|
||||
keystream: ValueRef,
|
||||
) -> Result<ValueRef, StreamCipherError> {
|
||||
let input_text = match input_text {
|
||||
InputText::Public { ids, text } => {
|
||||
let refs = text
|
||||
.into_iter()
|
||||
.zip(ids)
|
||||
.map(|(byte, id)| {
|
||||
let value_ref = thread.new_public_input::<u8>(&id)?;
|
||||
thread.assign(&value_ref, byte)?;
|
||||
|
||||
Ok::<_, StreamCipherError>(value_ref)
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
thread.array_from_values(&refs)?
|
||||
}
|
||||
InputText::Private { ids, text } => {
|
||||
let refs = text
|
||||
.into_iter()
|
||||
.zip(ids)
|
||||
.map(|(byte, id)| {
|
||||
let value_ref = thread.new_private_input::<u8>(&id)?;
|
||||
thread.assign(&value_ref, byte)?;
|
||||
|
||||
Ok::<_, StreamCipherError>(value_ref)
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
thread.array_from_values(&refs)?
|
||||
}
|
||||
InputText::Blind { ids } => {
|
||||
let refs = ids
|
||||
.into_iter()
|
||||
.map(|id| thread.new_blind_input::<u8>(&id))
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
thread.array_from_values(&refs)?
|
||||
}
|
||||
};
|
||||
|
||||
let output_text = thread.new_array_output::<u8>(
|
||||
&execution_id.append_string("output").to_string(),
|
||||
input_text.len(),
|
||||
)?;
|
||||
|
||||
let circ = build_array_xor(input_text.len());
|
||||
|
||||
match mode {
|
||||
ExecutionMode::Mpc => {
|
||||
thread
|
||||
.execute(circ, &[input_text, keystream], &[output_text.clone()])
|
||||
.await?;
|
||||
}
|
||||
ExecutionMode::Prove => {
|
||||
thread
|
||||
.execute_prove(circ, &[input_text, keystream], &[output_text.clone()])
|
||||
.await?;
|
||||
}
|
||||
ExecutionMode::Verify => {
|
||||
thread
|
||||
.execute_verify(circ, &[input_text, keystream], &[output_text.clone()])
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(output_text)
|
||||
}
|
||||
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(level = "trace", skip(thread_pool), err)
|
||||
)]
|
||||
async fn compute_keystream<
|
||||
T: Thread + Memory + Execute + Prove + Verify + Decode + DecodePrivate + Send + 'static,
|
||||
C: CtrCircuit,
|
||||
>(
|
||||
thread_pool: &mut ThreadPool<T>,
|
||||
execution_id: NestedId,
|
||||
configs: Vec<KeyBlockConfig<C>>,
|
||||
len: usize,
|
||||
mode: ExecutionMode,
|
||||
) -> Result<ValueRef, StreamCipherError> {
|
||||
let mut block_id = execution_id.append_counter();
|
||||
let mut scope = thread_pool.new_scope();
|
||||
|
||||
for config in configs {
|
||||
let block_id = block_id.increment_in_place();
|
||||
scope.push(move |thread| Box::pin(compute_key_block(thread, block_id, config, mode)));
|
||||
}
|
||||
|
||||
let key_blocks = scope
|
||||
.wait()
|
||||
.await
|
||||
.into_iter()
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
// Flatten the key blocks into a single array.
|
||||
let keystream = key_blocks
|
||||
.iter()
|
||||
.flat_map(|block| block.iter())
|
||||
.take(len)
|
||||
.cloned()
|
||||
.map(|id| ValueRef::Value { id })
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut scope = thread_pool.new_scope();
|
||||
scope.push(move |thread| Box::pin(async move { thread.array_from_values(&keystream) }));
|
||||
|
||||
let keystream = scope.wait().await.into_iter().next().unwrap()?;
|
||||
|
||||
Ok(keystream)
|
||||
}
|
||||
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(level = "trace", skip(thread), err)
|
||||
)]
|
||||
async fn compute_key_block<
|
||||
T: Memory + Execute + Prove + Verify + Decode + DecodePrivate + Send,
|
||||
C: CtrCircuit,
|
||||
>(
|
||||
thread: &mut T,
|
||||
block_id: NestedId,
|
||||
config: KeyBlockConfig<C>,
|
||||
mode: ExecutionMode,
|
||||
) -> Result<ValueRef, StreamCipherError> {
|
||||
let KeyBlockConfig {
|
||||
key,
|
||||
iv,
|
||||
explicit_nonce,
|
||||
ctr,
|
||||
..
|
||||
} = config;
|
||||
|
||||
let explicit_nonce_ref = thread.new_public_input::<<C as CtrCircuit>::NONCE>(
|
||||
&block_id.append_string("explicit_nonce").to_string(),
|
||||
)?;
|
||||
let ctr_ref = thread.new_public_input::<[u8; 4]>(&block_id.append_string("ctr").to_string())?;
|
||||
let key_block =
|
||||
thread.new_output::<C::BLOCK>(&block_id.append_string("key_block").to_string())?;
|
||||
|
||||
thread.assign(&explicit_nonce_ref, explicit_nonce)?;
|
||||
thread.assign(&ctr_ref, ctr.to_be_bytes())?;
|
||||
|
||||
// Execute circuit
|
||||
match mode {
|
||||
ExecutionMode::Mpc => {
|
||||
thread
|
||||
.execute(
|
||||
C::circuit(),
|
||||
&[key, iv, explicit_nonce_ref, ctr_ref],
|
||||
&[key_block.clone()],
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
ExecutionMode::Prove => {
|
||||
thread
|
||||
.execute_prove(
|
||||
C::circuit(),
|
||||
&[key, iv, explicit_nonce_ref, ctr_ref],
|
||||
&[key_block.clone()],
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
ExecutionMode::Verify => {
|
||||
thread
|
||||
.execute_verify(
|
||||
C::circuit(),
|
||||
&[key, iv, explicit_nonce_ref, ctr_ref],
|
||||
&[key_block.clone()],
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(key_block)
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
[package]
|
||||
name = "integration-tests"
|
||||
version = "0.0.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
|
||||
|
||||
[dev-dependencies]
|
||||
mpz-garble = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "850636f" }
|
||||
mpz-ot = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "850636f" }
|
||||
mpz-share-conversion = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "850636f" }
|
||||
tlsn-block-cipher = { path = "../cipher/block-cipher" }
|
||||
tlsn-stream-cipher = { path = "../cipher/stream-cipher" }
|
||||
tlsn-universal-hash = { path = "../universal-hash" }
|
||||
tlsn-aead = { path = "../aead" }
|
||||
tlsn-key-exchange = { path = "../key-exchange" }
|
||||
tlsn-point-addition = { path = "../point-addition" }
|
||||
tlsn-hmac-sha256 = { path = "../prf/hmac-sha256" }
|
||||
tlsn-utils-aio = { git = "https://github.com/tlsnotary/tlsn-utils", rev = "51f313d" }
|
||||
|
||||
uid-mux = { path = "../uid-mux" }
|
||||
|
||||
p256 = { version = "0.13" }
|
||||
|
||||
futures = "0.3"
|
||||
rand_chacha = "0.3"
|
||||
rand = "0.8"
|
||||
|
||||
tokio = { version = "1", features = ["macros", "rt", "rt-multi-thread"] }
|
||||
tokio-util = { version = "0.7", features = ["compat"] }
|
||||
@@ -1,396 +0,0 @@
|
||||
use aead::{
|
||||
aes_gcm::{AesGcmConfig, MpcAesGcm, Role as AesGcmRole},
|
||||
Aead,
|
||||
};
|
||||
use block_cipher::{Aes128, BlockCipherConfigBuilder, MpcBlockCipher};
|
||||
use ff::Gf2_128;
|
||||
use futures::StreamExt;
|
||||
use hmac_sha256::{MpcPrf, Prf, PrfConfig, SessionKeys};
|
||||
use key_exchange::{KeyExchange, KeyExchangeConfig, Role as KeyExchangeRole};
|
||||
use mpz_garble::{config::Role as GarbleRole, protocol::deap::DEAPVm, Vm};
|
||||
use mpz_ot::{
|
||||
actor::kos::{ReceiverActor, SenderActor},
|
||||
chou_orlandi::{
|
||||
Receiver as BaseReceiver, ReceiverConfig as BaseReceiverConfig, Sender as BaseSender,
|
||||
SenderConfig as BaseSenderConfig,
|
||||
},
|
||||
kos::{Receiver, ReceiverConfig, Sender, SenderConfig},
|
||||
};
|
||||
use mpz_share_conversion as ff;
|
||||
use mpz_share_conversion::{ShareConversionReveal, ShareConversionVerify};
|
||||
use p256::{NonZeroScalar, PublicKey, SecretKey};
|
||||
use point_addition::{MpcPointAddition, Role as PointAdditionRole, P256};
|
||||
use rand::SeedableRng;
|
||||
use rand_chacha::ChaCha20Rng;
|
||||
use tlsn_stream_cipher::{Aes128Ctr, MpcStreamCipher, StreamCipherConfig};
|
||||
use tlsn_universal_hash::ghash::{Ghash, GhashConfig};
|
||||
use tokio_util::compat::TokioAsyncReadCompatExt;
|
||||
use uid_mux::{yamux, UidYamux};
|
||||
use utils_aio::{codec::BincodeMux, mux::MuxChannel};
|
||||
|
||||
const OT_SETUP_COUNT: usize = 50_000;
|
||||
|
||||
/// The following integration test checks the interplay of individual components of the TLSNotary
|
||||
/// protocol. These are:
|
||||
/// - channel multiplexing
|
||||
/// - oblivious transfer
|
||||
/// - point addition
|
||||
/// - key exchange
|
||||
/// - prf
|
||||
/// - aead cipher (stream cipher + ghash)
|
||||
#[tokio::test]
|
||||
async fn test_components() {
|
||||
let mut rng = ChaCha20Rng::seed_from_u64(0);
|
||||
|
||||
let (leader_socket, follower_socket) = tokio::io::duplex(1 << 25);
|
||||
|
||||
let mut leader_mux = UidYamux::new(
|
||||
yamux::Config::default(),
|
||||
leader_socket.compat(),
|
||||
yamux::Mode::Client,
|
||||
);
|
||||
let mut follower_mux = UidYamux::new(
|
||||
yamux::Config::default(),
|
||||
follower_socket.compat(),
|
||||
yamux::Mode::Server,
|
||||
);
|
||||
|
||||
let leader_mux_control = leader_mux.control();
|
||||
let follower_mux_control = follower_mux.control();
|
||||
|
||||
tokio::spawn(async move { leader_mux.run().await.unwrap() });
|
||||
tokio::spawn(async move { follower_mux.run().await.unwrap() });
|
||||
|
||||
let mut leader_mux = BincodeMux::new(leader_mux_control);
|
||||
let mut follower_mux = BincodeMux::new(follower_mux_control);
|
||||
|
||||
let leader_ot_sender_config = SenderConfig::default();
|
||||
let follower_ot_recvr_config = ReceiverConfig::default();
|
||||
|
||||
let follower_ot_sender_config = SenderConfig::builder().sender_commit().build().unwrap();
|
||||
let leader_ot_recvr_config = ReceiverConfig::builder().sender_commit().build().unwrap();
|
||||
|
||||
let (leader_ot_sender_sink, leader_ot_sender_stream) =
|
||||
leader_mux.get_channel("ot/0").await.unwrap().split();
|
||||
|
||||
let (follower_ot_recvr_sink, follower_ot_recvr_stream) =
|
||||
follower_mux.get_channel("ot/0").await.unwrap().split();
|
||||
|
||||
let (leader_ot_receiver_sink, leader_ot_receiver_stream) =
|
||||
leader_mux.get_channel("ot/1").await.unwrap().split();
|
||||
|
||||
let (follower_ot_sender_sink, follower_ot_sender_stream) =
|
||||
follower_mux.get_channel("ot/1").await.unwrap().split();
|
||||
|
||||
let mut leader_ot_sender_actor = SenderActor::new(
|
||||
Sender::new(
|
||||
leader_ot_sender_config,
|
||||
BaseReceiver::new(BaseReceiverConfig::default()),
|
||||
),
|
||||
leader_ot_sender_sink,
|
||||
leader_ot_sender_stream,
|
||||
);
|
||||
|
||||
let mut follower_ot_recvr_actor = ReceiverActor::new(
|
||||
Receiver::new(
|
||||
follower_ot_recvr_config,
|
||||
BaseSender::new(BaseSenderConfig::default()),
|
||||
),
|
||||
follower_ot_recvr_sink,
|
||||
follower_ot_recvr_stream,
|
||||
);
|
||||
|
||||
let mut leader_ot_recvr_actor = ReceiverActor::new(
|
||||
Receiver::new(
|
||||
leader_ot_recvr_config,
|
||||
BaseSender::new(
|
||||
BaseSenderConfig::builder()
|
||||
.receiver_commit()
|
||||
.build()
|
||||
.unwrap(),
|
||||
),
|
||||
),
|
||||
leader_ot_receiver_sink,
|
||||
leader_ot_receiver_stream,
|
||||
);
|
||||
|
||||
let mut follower_ot_sender_actor = SenderActor::new(
|
||||
Sender::new(
|
||||
follower_ot_sender_config,
|
||||
BaseReceiver::new(
|
||||
BaseReceiverConfig::builder()
|
||||
.receiver_commit()
|
||||
.build()
|
||||
.unwrap(),
|
||||
),
|
||||
),
|
||||
follower_ot_sender_sink,
|
||||
follower_ot_sender_stream,
|
||||
);
|
||||
|
||||
let leader_ot_sender = leader_ot_sender_actor.sender();
|
||||
let follower_ot_recvr = follower_ot_recvr_actor.receiver();
|
||||
|
||||
let leader_ot_recvr = leader_ot_recvr_actor.receiver();
|
||||
let follower_ot_sender = follower_ot_sender_actor.sender();
|
||||
|
||||
tokio::spawn(async move {
|
||||
leader_ot_sender_actor.setup(OT_SETUP_COUNT).await.unwrap();
|
||||
leader_ot_sender_actor.run().await.unwrap();
|
||||
});
|
||||
|
||||
tokio::spawn(async move {
|
||||
follower_ot_recvr_actor.setup(OT_SETUP_COUNT).await.unwrap();
|
||||
follower_ot_recvr_actor.run().await.unwrap();
|
||||
});
|
||||
|
||||
tokio::spawn(async move {
|
||||
leader_ot_recvr_actor.setup(OT_SETUP_COUNT).await.unwrap();
|
||||
leader_ot_recvr_actor.run().await.unwrap();
|
||||
});
|
||||
|
||||
tokio::spawn(async move {
|
||||
follower_ot_sender_actor
|
||||
.setup(OT_SETUP_COUNT)
|
||||
.await
|
||||
.unwrap();
|
||||
follower_ot_sender_actor.run().await.unwrap();
|
||||
follower_ot_sender_actor.reveal().await.unwrap();
|
||||
});
|
||||
|
||||
let mut leader_vm = DEAPVm::new(
|
||||
"vm",
|
||||
GarbleRole::Leader,
|
||||
[0u8; 32],
|
||||
leader_mux.get_channel("vm").await.unwrap(),
|
||||
Box::new(leader_mux.clone()),
|
||||
leader_ot_sender.clone(),
|
||||
leader_ot_recvr.clone(),
|
||||
);
|
||||
|
||||
let mut follower_vm = DEAPVm::new(
|
||||
"vm",
|
||||
GarbleRole::Follower,
|
||||
[1u8; 32],
|
||||
follower_mux.get_channel("vm").await.unwrap(),
|
||||
Box::new(follower_mux.clone()),
|
||||
follower_ot_sender.clone(),
|
||||
follower_ot_recvr.clone(),
|
||||
);
|
||||
|
||||
let leader_p256_sender = ff::ConverterSender::<P256, _>::new(
|
||||
ff::SenderConfig::builder().id("p256/0").build().unwrap(),
|
||||
leader_ot_sender.clone(),
|
||||
leader_mux.get_channel("p256/0").await.unwrap(),
|
||||
);
|
||||
|
||||
let leader_p256_receiver = ff::ConverterReceiver::<P256, _>::new(
|
||||
ff::ReceiverConfig::builder().id("p256/1").build().unwrap(),
|
||||
follower_ot_recvr.clone(),
|
||||
leader_mux.get_channel("p256/1").await.unwrap(),
|
||||
);
|
||||
|
||||
let follower_p256_sender = ff::ConverterSender::<P256, _>::new(
|
||||
ff::SenderConfig::builder().id("p256/1").build().unwrap(),
|
||||
leader_ot_sender.clone(),
|
||||
follower_mux.get_channel("p256/1").await.unwrap(),
|
||||
);
|
||||
|
||||
let follower_p256_receiver = ff::ConverterReceiver::<P256, _>::new(
|
||||
ff::ReceiverConfig::builder().id("p256/0").build().unwrap(),
|
||||
follower_ot_recvr.clone(),
|
||||
follower_mux.get_channel("p256/0").await.unwrap(),
|
||||
);
|
||||
|
||||
let leader_pa_sender = MpcPointAddition::new(PointAdditionRole::Leader, leader_p256_sender);
|
||||
let leader_pa_receiver = MpcPointAddition::new(PointAdditionRole::Leader, leader_p256_receiver);
|
||||
|
||||
let follower_pa_sender =
|
||||
MpcPointAddition::new(PointAdditionRole::Follower, follower_p256_sender);
|
||||
|
||||
let follower_pa_receiver =
|
||||
MpcPointAddition::new(PointAdditionRole::Follower, follower_p256_receiver);
|
||||
|
||||
let mut leader_ke = key_exchange::KeyExchangeCore::new(
|
||||
leader_mux.get_channel("ke").await.unwrap(),
|
||||
leader_pa_sender,
|
||||
leader_pa_receiver,
|
||||
leader_vm.new_thread("ke").await.unwrap(),
|
||||
KeyExchangeConfig::builder()
|
||||
.id("ke")
|
||||
.role(KeyExchangeRole::Leader)
|
||||
.build()
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
let mut follower_ke = key_exchange::KeyExchangeCore::new(
|
||||
follower_mux.get_channel("ke").await.unwrap(),
|
||||
follower_pa_sender,
|
||||
follower_pa_receiver,
|
||||
follower_vm.new_thread("ke").await.unwrap(),
|
||||
KeyExchangeConfig::builder()
|
||||
.id("ke")
|
||||
.role(KeyExchangeRole::Follower)
|
||||
.build()
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
let (leader_pms, follower_pms) =
|
||||
futures::try_join!(leader_ke.setup(), follower_ke.setup()).unwrap();
|
||||
|
||||
let mut leader_prf = MpcPrf::new(
|
||||
PrfConfig::builder()
|
||||
.role(hmac_sha256::Role::Leader)
|
||||
.build()
|
||||
.unwrap(),
|
||||
leader_vm.new_thread("prf/0").await.unwrap(),
|
||||
leader_vm.new_thread("prf/1").await.unwrap(),
|
||||
);
|
||||
let mut follower_prf = MpcPrf::new(
|
||||
PrfConfig::builder()
|
||||
.role(hmac_sha256::Role::Follower)
|
||||
.build()
|
||||
.unwrap(),
|
||||
follower_vm.new_thread("prf/0").await.unwrap(),
|
||||
follower_vm.new_thread("prf/1").await.unwrap(),
|
||||
);
|
||||
|
||||
futures::try_join!(
|
||||
leader_prf.setup(leader_pms.into_value()),
|
||||
follower_prf.setup(follower_pms.into_value())
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let block_cipher_config = BlockCipherConfigBuilder::default()
|
||||
.id("aes")
|
||||
.build()
|
||||
.unwrap();
|
||||
let leader_block_cipher = MpcBlockCipher::<Aes128, _>::new(
|
||||
block_cipher_config.clone(),
|
||||
leader_vm.new_thread("block_cipher").await.unwrap(),
|
||||
);
|
||||
let follower_block_cipher = MpcBlockCipher::<Aes128, _>::new(
|
||||
block_cipher_config,
|
||||
follower_vm.new_thread("block_cipher").await.unwrap(),
|
||||
);
|
||||
|
||||
let stream_cipher_config = StreamCipherConfig::builder()
|
||||
.id("aes-ctr")
|
||||
.transcript_id("tx")
|
||||
.build()
|
||||
.unwrap();
|
||||
let leader_stream_cipher = MpcStreamCipher::<Aes128Ctr, _>::new(
|
||||
stream_cipher_config.clone(),
|
||||
leader_vm.new_thread_pool("aes-ctr", 4).await.unwrap(),
|
||||
);
|
||||
let follower_stream_cipher = MpcStreamCipher::<Aes128Ctr, _>::new(
|
||||
stream_cipher_config,
|
||||
follower_vm.new_thread_pool("aes-ctr", 4).await.unwrap(),
|
||||
);
|
||||
|
||||
let mut leader_gf2 = ff::ConverterSender::<Gf2_128, _>::new(
|
||||
ff::SenderConfig::builder()
|
||||
.id("gf2")
|
||||
.record()
|
||||
.build()
|
||||
.unwrap(),
|
||||
leader_ot_sender.clone(),
|
||||
leader_mux.get_channel("gf2").await.unwrap(),
|
||||
);
|
||||
|
||||
let mut follower_gf2 = ff::ConverterReceiver::<Gf2_128, _>::new(
|
||||
ff::ReceiverConfig::builder()
|
||||
.id("gf2")
|
||||
.record()
|
||||
.build()
|
||||
.unwrap(),
|
||||
follower_ot_recvr.clone(),
|
||||
follower_mux.get_channel("gf2").await.unwrap(),
|
||||
);
|
||||
|
||||
let ghash_config = GhashConfig::builder()
|
||||
.id("aes_gcm/ghash")
|
||||
.initial_block_count(64)
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let leader_ghash = Ghash::new(ghash_config.clone(), leader_gf2.handle().unwrap());
|
||||
let follower_ghash = Ghash::new(ghash_config, follower_gf2.handle().unwrap());
|
||||
|
||||
let mut leader_aead = MpcAesGcm::new(
|
||||
AesGcmConfig::builder()
|
||||
.id("aes_gcm")
|
||||
.role(AesGcmRole::Leader)
|
||||
.build()
|
||||
.unwrap(),
|
||||
leader_mux.get_channel("aes_gcm").await.unwrap(),
|
||||
Box::new(leader_block_cipher),
|
||||
Box::new(leader_stream_cipher),
|
||||
Box::new(leader_ghash),
|
||||
);
|
||||
|
||||
let mut follower_aead = MpcAesGcm::new(
|
||||
AesGcmConfig::builder()
|
||||
.id("aes_gcm")
|
||||
.role(AesGcmRole::Follower)
|
||||
.build()
|
||||
.unwrap(),
|
||||
follower_mux.get_channel("aes_gcm").await.unwrap(),
|
||||
Box::new(follower_block_cipher),
|
||||
Box::new(follower_stream_cipher),
|
||||
Box::new(follower_ghash),
|
||||
);
|
||||
|
||||
let leader_private_key = SecretKey::random(&mut rng);
|
||||
let follower_private_key = SecretKey::random(&mut rng);
|
||||
let server_public_key = PublicKey::from_secret_scalar(&NonZeroScalar::random(&mut rng));
|
||||
|
||||
// Setup complete
|
||||
|
||||
let _ = tokio::try_join!(
|
||||
leader_ke.compute_client_key(leader_private_key),
|
||||
follower_ke.compute_client_key(follower_private_key)
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
leader_ke.set_server_key(server_public_key);
|
||||
|
||||
tokio::try_join!(leader_ke.compute_pms(), follower_ke.compute_pms()).unwrap();
|
||||
|
||||
let (leader_session_keys, follower_session_keys) = tokio::try_join!(
|
||||
leader_prf.compute_session_keys_private([0u8; 32], [0u8; 32]),
|
||||
follower_prf.compute_session_keys_blind()
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let SessionKeys {
|
||||
client_write_key: leader_key,
|
||||
client_iv: leader_iv,
|
||||
..
|
||||
} = leader_session_keys;
|
||||
|
||||
let SessionKeys {
|
||||
client_write_key: follower_key,
|
||||
client_iv: follower_iv,
|
||||
..
|
||||
} = follower_session_keys;
|
||||
|
||||
tokio::try_join!(
|
||||
leader_aead.set_key(leader_key, leader_iv),
|
||||
follower_aead.set_key(follower_key, follower_iv)
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let msg = vec![0u8; 4096];
|
||||
|
||||
let _ = tokio::try_join!(
|
||||
leader_aead.encrypt_private(vec![0u8; 8], msg.clone(), vec![]),
|
||||
follower_aead.encrypt_blind(vec![0u8; 8], msg.len(), vec![])
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
follower_ot_sender.shutdown().await.unwrap();
|
||||
|
||||
tokio::try_join!(leader_vm.finalize(), follower_vm.finalize()).unwrap();
|
||||
tokio::try_join!(leader_gf2.reveal(), follower_gf2.verify()).unwrap();
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
[package]
|
||||
name = "tlsn-key-exchange"
|
||||
authors = ["TLSNotary Team"]
|
||||
description = "Implementation of the TLSNotary-specific key-exchange protocol"
|
||||
keywords = ["tls", "mpc", "2pc", "pms", "key-exchange"]
|
||||
categories = ["cryptography"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
version = "0.1.0-alpha.4"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
name = "key_exchange"
|
||||
|
||||
[features]
|
||||
default = ["mock"]
|
||||
tracing = ["dep:tracing", "tlsn-point-addition/tracing"]
|
||||
mock = []
|
||||
|
||||
[dependencies]
|
||||
mpz-garble = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "850636f" }
|
||||
mpz-ot = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "850636f" }
|
||||
mpz-circuits = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "850636f" }
|
||||
tlsn-utils-aio = { git = "https://github.com/tlsnotary/tlsn-utils", rev = "51f313d" }
|
||||
mpz-share-conversion-core = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "850636f" }
|
||||
tlsn-point-addition = { path = "../point-addition" }
|
||||
p256 = { version = "0.13", features = ["ecdh"] }
|
||||
async-trait = "0.1"
|
||||
thiserror = "1"
|
||||
serde = "1"
|
||||
futures = "0.3"
|
||||
derive_builder = "0.12"
|
||||
tracing = { version = "0.1", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
rand_chacha = "0.3"
|
||||
rand_core = "0.6"
|
||||
tokio = { version = "1", features = ["macros", "rt", "rt-multi-thread"] }
|
||||
@@ -1,43 +0,0 @@
|
||||
//! This module provides the circuits used in the key exchange protocol
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use mpz_circuits::{circuits::big_num::nbyte_add_mod_trace, Circuit, CircuitBuilder};
|
||||
|
||||
/// NIST P-256 prime big-endian
|
||||
static P: [u8; 32] = [
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
];
|
||||
|
||||
/// Circuit for combining additive shares of the PMS, twice
|
||||
///
|
||||
/// # Inputs
|
||||
///
|
||||
/// 0. PMS_SHARE_A: 32 bytes PMS Additive Share
|
||||
/// 1. PMS_SHARE_B: 32 bytes PMS Additive Share
|
||||
/// 2. PMS_SHARE_C: 32 bytes PMS Additive Share
|
||||
/// 3. PMS_SHARE_D: 32 bytes PMS Additive Share
|
||||
///
|
||||
/// # Outputs
|
||||
/// 0. PMS1: Pre-master Secret = PMS_SHARE_A + PMS_SHARE_B
|
||||
/// 1. PMS2: Pre-master Secret = PMS_SHARE_C + PMS_SHARE_D
|
||||
/// 2. EQ: Equality check of PMS1 and PMS2
|
||||
pub(crate) fn build_pms_circuit() -> Arc<Circuit> {
|
||||
let builder = CircuitBuilder::new();
|
||||
let share_a = builder.add_array_input::<u8, 32>();
|
||||
let share_b = builder.add_array_input::<u8, 32>();
|
||||
let share_c = builder.add_array_input::<u8, 32>();
|
||||
let share_d = builder.add_array_input::<u8, 32>();
|
||||
|
||||
let a = nbyte_add_mod_trace(builder.state(), share_a, share_b, P);
|
||||
let b = nbyte_add_mod_trace(builder.state(), share_c, share_d, P);
|
||||
|
||||
let eq: [_; 32] = std::array::from_fn(|i| a[i] ^ b[i]);
|
||||
|
||||
builder.add_output(a);
|
||||
builder.add_output(b);
|
||||
builder.add_output(eq);
|
||||
|
||||
Arc::new(builder.build().expect("pms circuit is valid"))
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
//! This module provides the [KeyExchangeConfig] for configuration of the key exchange instance
|
||||
|
||||
use derive_builder::Builder;
|
||||
|
||||
/// Role in the key exchange protocol
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[allow(missing_docs)]
|
||||
pub enum Role {
|
||||
Leader,
|
||||
Follower,
|
||||
}
|
||||
|
||||
/// A config used for [KeyExchangeCore](super::KeyExchangeCore)
|
||||
#[derive(Debug, Clone, Builder)]
|
||||
pub struct KeyExchangeConfig {
|
||||
/// The id of this instance
|
||||
#[builder(setter(into))]
|
||||
id: String,
|
||||
/// Protocol role
|
||||
role: Role,
|
||||
}
|
||||
|
||||
impl KeyExchangeConfig {
|
||||
/// Creates a new builder for the key exchange configuration
|
||||
pub fn builder() -> KeyExchangeConfigBuilder {
|
||||
KeyExchangeConfigBuilder::default()
|
||||
}
|
||||
|
||||
/// Get the id of this instance
|
||||
pub fn id(&self) -> &str {
|
||||
&self.id
|
||||
}
|
||||
|
||||
/// Get the role of this instance
|
||||
pub fn role(&self) -> &Role {
|
||||
&self.role
|
||||
}
|
||||
}
|
||||
@@ -1,621 +0,0 @@
|
||||
//! This module implements the key exchange logic
|
||||
|
||||
use async_trait::async_trait;
|
||||
use futures::{SinkExt, StreamExt};
|
||||
use mpz_garble::{value::ValueRef, Decode, Execute, Load, Memory};
|
||||
|
||||
use mpz_share_conversion_core::fields::{p256::P256, Field};
|
||||
use p256::{EncodedPoint, PublicKey, SecretKey};
|
||||
use point_addition::PointAddition;
|
||||
use std::fmt::Debug;
|
||||
|
||||
use utils_aio::expect_msg_or_err;
|
||||
|
||||
use crate::{
|
||||
circuit::build_pms_circuit,
|
||||
config::{KeyExchangeConfig, Role},
|
||||
KeyExchange, KeyExchangeChannel, KeyExchangeError, KeyExchangeMessage, Pms,
|
||||
};
|
||||
|
||||
enum State {
|
||||
Initialized,
|
||||
Setup {
|
||||
share_a: ValueRef,
|
||||
share_b: ValueRef,
|
||||
share_c: ValueRef,
|
||||
share_d: ValueRef,
|
||||
pms_1: ValueRef,
|
||||
pms_2: ValueRef,
|
||||
eq: ValueRef,
|
||||
},
|
||||
KeyExchange {
|
||||
share_a: ValueRef,
|
||||
share_b: ValueRef,
|
||||
share_c: ValueRef,
|
||||
share_d: ValueRef,
|
||||
pms_1: ValueRef,
|
||||
pms_2: ValueRef,
|
||||
eq: ValueRef,
|
||||
},
|
||||
Complete,
|
||||
Error,
|
||||
}
|
||||
|
||||
/// The instance for performing the key exchange protocol
|
||||
///
|
||||
/// Can be either a leader or a follower depending on the `role` field in [KeyExchangeConfig]
|
||||
pub struct KeyExchangeCore<PS, PR, E> {
|
||||
/// A channel for exchanging messages between leader and follower
|
||||
channel: KeyExchangeChannel,
|
||||
/// The sender instance for performing point addition
|
||||
point_addition_sender: PS,
|
||||
/// The receiver instance for performing point addition
|
||||
point_addition_receiver: PR,
|
||||
/// MPC executor
|
||||
executor: E,
|
||||
/// The private key of the party behind this instance, either follower or leader
|
||||
private_key: Option<SecretKey>,
|
||||
/// The public key of the server
|
||||
server_key: Option<PublicKey>,
|
||||
/// The config used for the key exchange protocol
|
||||
config: KeyExchangeConfig,
|
||||
/// The state of the protocol
|
||||
state: State,
|
||||
}
|
||||
|
||||
impl<PS, PR, E> Debug for KeyExchangeCore<PS, PR, E>
|
||||
where
|
||||
PS: PointAddition<Point = EncodedPoint, XCoordinate = P256> + Send + Debug,
|
||||
PR: PointAddition<Point = EncodedPoint, XCoordinate = P256> + Send + Debug,
|
||||
E: Memory + Execute + Decode + Send,
|
||||
{
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("KeyExchangeCore")
|
||||
.field("channel", &"{{ ... }}")
|
||||
.field("point_addition_sender", &"{{ ... }}")
|
||||
.field("point_addition_receiver", &"{{ ... }}")
|
||||
.field("executor", &"{{ ... }}")
|
||||
.field("private_key", &"{{ ... }}")
|
||||
.field("server_key", &self.server_key)
|
||||
.field("config", &self.config)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<PS, PR, E> KeyExchangeCore<PS, PR, E>
|
||||
where
|
||||
PS: PointAddition<Point = EncodedPoint, XCoordinate = P256> + Send + Debug,
|
||||
PR: PointAddition<Point = EncodedPoint, XCoordinate = P256> + Send + Debug,
|
||||
E: Memory + Execute + Decode + Send,
|
||||
{
|
||||
/// Creates a new [KeyExchangeCore]
|
||||
///
|
||||
/// * `channel` - The channel for sending messages between leader and follower
|
||||
/// * `point_addition_sender` - The point addition sender instance used during key exchange
|
||||
/// * `point_addition_receiver` - The point addition receiver instance used during key exchange
|
||||
/// * `executor` - The MPC executor
|
||||
/// * `config` - The config used for the key exchange protocol
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(
|
||||
level = "info",
|
||||
skip(channel, executor, point_addition_sender, point_addition_receiver),
|
||||
ret
|
||||
)
|
||||
)]
|
||||
pub fn new(
|
||||
channel: KeyExchangeChannel,
|
||||
point_addition_sender: PS,
|
||||
point_addition_receiver: PR,
|
||||
executor: E,
|
||||
config: KeyExchangeConfig,
|
||||
) -> Self {
|
||||
Self {
|
||||
channel,
|
||||
point_addition_sender,
|
||||
point_addition_receiver,
|
||||
executor,
|
||||
private_key: None,
|
||||
server_key: None,
|
||||
config,
|
||||
state: State::Initialized,
|
||||
}
|
||||
}
|
||||
|
||||
async fn compute_pms_shares(&mut self) -> Result<(P256, P256), KeyExchangeError> {
|
||||
let state = std::mem::replace(&mut self.state, State::Error);
|
||||
|
||||
let State::Setup {
|
||||
share_a,
|
||||
share_b,
|
||||
share_c,
|
||||
share_d,
|
||||
pms_1,
|
||||
pms_2,
|
||||
eq,
|
||||
} = state
|
||||
else {
|
||||
todo!()
|
||||
};
|
||||
|
||||
let server_key = match self.config.role() {
|
||||
Role::Leader => {
|
||||
// Send server public key to follower
|
||||
if let Some(server_key) = &self.server_key {
|
||||
self.channel
|
||||
.send(KeyExchangeMessage::ServerPublicKey((*server_key).into()))
|
||||
.await?;
|
||||
|
||||
*server_key
|
||||
} else {
|
||||
return Err(KeyExchangeError::NoServerKey);
|
||||
}
|
||||
}
|
||||
Role::Follower => {
|
||||
// Receive server's public key from leader
|
||||
let message =
|
||||
expect_msg_or_err!(self.channel, KeyExchangeMessage::ServerPublicKey)?;
|
||||
let server_key = message.try_into()?;
|
||||
|
||||
self.server_key = Some(server_key);
|
||||
|
||||
server_key
|
||||
}
|
||||
};
|
||||
|
||||
let private_key = self
|
||||
.private_key
|
||||
.take()
|
||||
.ok_or(KeyExchangeError::NoPrivateKey)?;
|
||||
|
||||
// Compute the leader's/follower's share of the pre-master secret
|
||||
//
|
||||
// We need to mimic the [diffie-hellman](p256::ecdh::diffie_hellman) function without the
|
||||
// [SharedSecret](p256::ecdh::SharedSecret) wrapper, because this makes it harder to get
|
||||
// the result as an EC curve point.
|
||||
let shared_secret = {
|
||||
let public_projective = server_key.to_projective();
|
||||
(public_projective * private_key.to_nonzero_scalar().as_ref()).to_affine()
|
||||
};
|
||||
|
||||
let encoded_point = EncodedPoint::from(PublicKey::from_affine(shared_secret)?);
|
||||
let (sender_share, receiver_share) = futures::try_join!(
|
||||
self.point_addition_sender
|
||||
.compute_x_coordinate_share(encoded_point),
|
||||
self.point_addition_receiver
|
||||
.compute_x_coordinate_share(encoded_point)
|
||||
)?;
|
||||
|
||||
self.state = State::KeyExchange {
|
||||
share_a,
|
||||
share_b,
|
||||
share_c,
|
||||
share_d,
|
||||
pms_1,
|
||||
pms_2,
|
||||
eq,
|
||||
};
|
||||
|
||||
match self.config.role() {
|
||||
Role::Leader => Ok((sender_share, receiver_share)),
|
||||
Role::Follower => Ok((receiver_share, sender_share)),
|
||||
}
|
||||
}
|
||||
|
||||
async fn compute_pms_for(
|
||||
&mut self,
|
||||
pms_share1: P256,
|
||||
pms_share2: P256,
|
||||
) -> Result<Pms, KeyExchangeError> {
|
||||
let state = std::mem::replace(&mut self.state, State::Error);
|
||||
|
||||
let State::KeyExchange {
|
||||
share_a,
|
||||
share_b,
|
||||
share_c,
|
||||
share_d,
|
||||
pms_1,
|
||||
pms_2,
|
||||
eq,
|
||||
} = state
|
||||
else {
|
||||
todo!()
|
||||
};
|
||||
|
||||
let pms_share1: [u8; 32] = pms_share1
|
||||
.to_be_bytes()
|
||||
.try_into()
|
||||
.expect("pms share is 32 bytes");
|
||||
let pms_share2: [u8; 32] = pms_share2
|
||||
.to_be_bytes()
|
||||
.try_into()
|
||||
.expect("pms share is 32 bytes");
|
||||
|
||||
match self.config.role() {
|
||||
Role::Leader => {
|
||||
self.executor.assign(&share_a, pms_share1)?;
|
||||
self.executor.assign(&share_c, pms_share2)?;
|
||||
}
|
||||
Role::Follower => {
|
||||
self.executor.assign(&share_b, pms_share1)?;
|
||||
self.executor.assign(&share_d, pms_share2)?;
|
||||
}
|
||||
}
|
||||
|
||||
self.executor
|
||||
.execute(
|
||||
build_pms_circuit(),
|
||||
&[share_a, share_b, share_c, share_d],
|
||||
&[pms_1.clone(), pms_2, eq.clone()],
|
||||
)
|
||||
.await?;
|
||||
|
||||
#[cfg(feature = "tracing")]
|
||||
tracing::event!(tracing::Level::DEBUG, "Successfully executed PMS circuit!");
|
||||
|
||||
let mut outputs = self.executor.decode(&[eq]).await?;
|
||||
|
||||
let eq: [u8; 32] = outputs.remove(0).try_into().expect("eq is 32 bytes");
|
||||
|
||||
// Eq should be all zeros if pms_1 == pms_2
|
||||
if eq != [0u8; 32] {
|
||||
return Err(KeyExchangeError::CheckFailed);
|
||||
}
|
||||
|
||||
self.state = State::Complete;
|
||||
|
||||
// Both parties use pms_1 as the pre-master secret
|
||||
Ok(Pms::new(pms_1))
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<PS, PR, E> KeyExchange for KeyExchangeCore<PS, PR, E>
|
||||
where
|
||||
PS: PointAddition<Point = EncodedPoint, XCoordinate = P256> + Send + Debug,
|
||||
PR: PointAddition<Point = EncodedPoint, XCoordinate = P256> + Send + Debug,
|
||||
E: Memory + Load + Execute + Decode + Send,
|
||||
{
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(level = "info", skip(self), ret)
|
||||
)]
|
||||
fn server_key(&self) -> Option<PublicKey> {
|
||||
self.server_key
|
||||
}
|
||||
|
||||
/// Set the server's public key
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(level = "info", skip(self)))]
|
||||
fn set_server_key(&mut self, server_key: PublicKey) {
|
||||
self.server_key = Some(server_key);
|
||||
}
|
||||
|
||||
async fn setup(&mut self) -> Result<Pms, KeyExchangeError> {
|
||||
let state = std::mem::replace(&mut self.state, State::Error);
|
||||
|
||||
let State::Initialized = state else {
|
||||
return Err(KeyExchangeError::InvalidState(
|
||||
"expected to be in Initialized state".to_string(),
|
||||
));
|
||||
};
|
||||
|
||||
let (share_a, share_b, share_c, share_d) = match self.config.role() {
|
||||
Role::Leader => {
|
||||
let share_a = self
|
||||
.executor
|
||||
.new_private_input::<[u8; 32]>("pms/share_a")
|
||||
.unwrap();
|
||||
let share_b = self
|
||||
.executor
|
||||
.new_blind_input::<[u8; 32]>("pms/share_b")
|
||||
.unwrap();
|
||||
let share_c = self
|
||||
.executor
|
||||
.new_private_input::<[u8; 32]>("pms/share_c")
|
||||
.unwrap();
|
||||
let share_d = self
|
||||
.executor
|
||||
.new_blind_input::<[u8; 32]>("pms/share_d")
|
||||
.unwrap();
|
||||
|
||||
(share_a, share_b, share_c, share_d)
|
||||
}
|
||||
Role::Follower => {
|
||||
let share_a = self
|
||||
.executor
|
||||
.new_blind_input::<[u8; 32]>("pms/share_a")
|
||||
.unwrap();
|
||||
let share_b = self
|
||||
.executor
|
||||
.new_private_input::<[u8; 32]>("pms/share_b")
|
||||
.unwrap();
|
||||
let share_c = self
|
||||
.executor
|
||||
.new_blind_input::<[u8; 32]>("pms/share_c")
|
||||
.unwrap();
|
||||
let share_d = self
|
||||
.executor
|
||||
.new_private_input::<[u8; 32]>("pms/share_d")
|
||||
.unwrap();
|
||||
|
||||
(share_a, share_b, share_c, share_d)
|
||||
}
|
||||
};
|
||||
|
||||
let pms_1 = self.executor.new_output::<[u8; 32]>("pms/1")?;
|
||||
let pms_2 = self.executor.new_output::<[u8; 32]>("pms/2")?;
|
||||
let eq = self.executor.new_output::<[u8; 32]>("pms/eq")?;
|
||||
|
||||
self.executor
|
||||
.load(
|
||||
build_pms_circuit(),
|
||||
&[
|
||||
share_a.clone(),
|
||||
share_b.clone(),
|
||||
share_c.clone(),
|
||||
share_d.clone(),
|
||||
],
|
||||
&[pms_1.clone(), pms_2.clone(), eq.clone()],
|
||||
)
|
||||
.await?;
|
||||
|
||||
self.state = State::Setup {
|
||||
share_a,
|
||||
share_b,
|
||||
share_c,
|
||||
share_d,
|
||||
pms_1: pms_1.clone(),
|
||||
pms_2,
|
||||
eq,
|
||||
};
|
||||
|
||||
Ok(Pms::new(pms_1))
|
||||
}
|
||||
|
||||
/// Compute the client's public key
|
||||
///
|
||||
/// The client's public key in this context is the combined public key (EC point addition) of
|
||||
/// the leader's public key and the follower's public key.
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(level = "info", skip(self, private_key), ret, err)
|
||||
)]
|
||||
async fn compute_client_key(
|
||||
&mut self,
|
||||
private_key: SecretKey,
|
||||
) -> Result<Option<PublicKey>, KeyExchangeError> {
|
||||
let public_key = private_key.public_key();
|
||||
self.private_key = Some(private_key);
|
||||
|
||||
match self.config.role() {
|
||||
Role::Leader => {
|
||||
// Receive public key from follower
|
||||
let message =
|
||||
expect_msg_or_err!(self.channel, KeyExchangeMessage::FollowerPublicKey)?;
|
||||
let follower_public_key: PublicKey = message.try_into()?;
|
||||
|
||||
// Combine public keys
|
||||
let client_public_key = PublicKey::from_affine(
|
||||
(public_key.to_projective() + follower_public_key.to_projective()).to_affine(),
|
||||
)?;
|
||||
|
||||
Ok(Some(client_public_key))
|
||||
}
|
||||
Role::Follower => {
|
||||
// Send public key to leader
|
||||
self.channel
|
||||
.send(KeyExchangeMessage::FollowerPublicKey(public_key.into()))
|
||||
.await?;
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Computes the PMS
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(level = "info", skip(self), err)
|
||||
)]
|
||||
async fn compute_pms(&mut self) -> Result<Pms, KeyExchangeError> {
|
||||
let (pms_share1, pms_share2) = self.compute_pms_shares().await?;
|
||||
|
||||
self.compute_pms_for(pms_share1, pms_share2).await
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use mpz_garble::{
|
||||
protocol::deap::mock::{
|
||||
create_mock_deap_vm, MockFollower, MockFollowerThread, MockLeader, MockLeaderThread,
|
||||
},
|
||||
Vm,
|
||||
};
|
||||
use mpz_share_conversion_core::fields::{p256::P256, Field};
|
||||
use p256::{NonZeroScalar, PublicKey, SecretKey};
|
||||
use rand_chacha::ChaCha20Rng;
|
||||
use rand_core::SeedableRng;
|
||||
|
||||
use crate::{
|
||||
mock::{create_mock_key_exchange_pair, MockKeyExchange},
|
||||
KeyExchangeError,
|
||||
};
|
||||
|
||||
async fn create_pair() -> (
|
||||
(
|
||||
MockKeyExchange<MockLeaderThread>,
|
||||
MockKeyExchange<MockFollowerThread>,
|
||||
),
|
||||
(MockLeader, MockFollower),
|
||||
) {
|
||||
let (mut leader_vm, mut follower_vm) = create_mock_deap_vm("test").await;
|
||||
(
|
||||
create_mock_key_exchange_pair(
|
||||
"test",
|
||||
leader_vm.new_thread("ke").await.unwrap(),
|
||||
follower_vm.new_thread("ke").await.unwrap(),
|
||||
),
|
||||
(leader_vm, follower_vm),
|
||||
)
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_key_exchange() {
|
||||
let mut rng = ChaCha20Rng::from_seed([0_u8; 32]);
|
||||
|
||||
let leader_private_key = SecretKey::random(&mut rng);
|
||||
let follower_private_key = SecretKey::random(&mut rng);
|
||||
let server_public_key = PublicKey::from_secret_scalar(&NonZeroScalar::random(&mut rng));
|
||||
|
||||
let ((mut leader, mut follower), (_leader_vm, _follower_vm)) = create_pair().await;
|
||||
|
||||
let client_public_key = perform_key_exchange(
|
||||
&mut leader,
|
||||
&mut follower,
|
||||
leader_private_key.clone(),
|
||||
follower_private_key.clone(),
|
||||
server_public_key,
|
||||
)
|
||||
.await;
|
||||
|
||||
let expected_client_public_key = PublicKey::from_affine(
|
||||
(leader_private_key.public_key().to_projective()
|
||||
+ follower_private_key.public_key().to_projective())
|
||||
.to_affine(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(client_public_key, expected_client_public_key);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_compute_pms_share() {
|
||||
let mut rng = ChaCha20Rng::from_seed([0_u8; 32]);
|
||||
|
||||
let leader_private_key = SecretKey::random(&mut rng);
|
||||
let follower_private_key = SecretKey::random(&mut rng);
|
||||
let server_private_key = NonZeroScalar::random(&mut rng);
|
||||
let server_public_key = PublicKey::from_secret_scalar(&server_private_key);
|
||||
|
||||
let ((mut leader, mut follower), (_leader_vm, _follower_vm)) = create_pair().await;
|
||||
|
||||
let client_public_key = perform_key_exchange(
|
||||
&mut leader,
|
||||
&mut follower,
|
||||
leader_private_key.clone(),
|
||||
follower_private_key.clone(),
|
||||
server_public_key,
|
||||
)
|
||||
.await;
|
||||
|
||||
leader.set_server_key(server_public_key);
|
||||
|
||||
let ((l_pms1, l_pms2), (f_pms1, f_pms2)) =
|
||||
tokio::try_join!(leader.compute_pms_shares(), follower.compute_pms_shares()).unwrap();
|
||||
|
||||
let expected_ecdh_x =
|
||||
p256::ecdh::diffie_hellman(server_private_key, client_public_key.as_affine());
|
||||
|
||||
assert_eq!(
|
||||
expected_ecdh_x.raw_secret_bytes().to_vec(),
|
||||
(l_pms1 + f_pms1).to_be_bytes()
|
||||
);
|
||||
assert_eq!(
|
||||
expected_ecdh_x.raw_secret_bytes().to_vec(),
|
||||
(l_pms2 + f_pms2).to_be_bytes()
|
||||
);
|
||||
assert_eq!(l_pms1 + f_pms1, l_pms2 + f_pms2);
|
||||
assert_ne!(l_pms1, f_pms1);
|
||||
assert_ne!(l_pms2, f_pms2);
|
||||
assert_ne!(l_pms1, l_pms2);
|
||||
assert_ne!(f_pms1, f_pms2);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_compute_pms() {
|
||||
let mut rng = ChaCha20Rng::from_seed([0_u8; 32]);
|
||||
|
||||
let leader_private_key = SecretKey::random(&mut rng);
|
||||
let follower_private_key = SecretKey::random(&mut rng);
|
||||
let server_private_key = NonZeroScalar::random(&mut rng);
|
||||
let server_public_key = PublicKey::from_secret_scalar(&server_private_key);
|
||||
|
||||
let ((mut leader, mut follower), (_leader_vm, _follower_vm)) = create_pair().await;
|
||||
|
||||
_ = perform_key_exchange(
|
||||
&mut leader,
|
||||
&mut follower,
|
||||
leader_private_key.clone(),
|
||||
follower_private_key.clone(),
|
||||
server_public_key,
|
||||
)
|
||||
.await;
|
||||
|
||||
leader.set_server_key(server_public_key);
|
||||
|
||||
let (_leader_pms, _follower_pms) =
|
||||
tokio::try_join!(leader.compute_pms(), follower.compute_pms()).unwrap();
|
||||
|
||||
assert_eq!(leader.server_key.unwrap(), server_public_key);
|
||||
assert_eq!(follower.server_key.unwrap(), server_public_key);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_compute_pms_fail() {
|
||||
let mut rng = ChaCha20Rng::from_seed([0_u8; 32]);
|
||||
|
||||
let leader_private_key = SecretKey::random(&mut rng);
|
||||
let follower_private_key = SecretKey::random(&mut rng);
|
||||
let server_private_key = NonZeroScalar::random(&mut rng);
|
||||
let server_public_key = PublicKey::from_secret_scalar(&server_private_key);
|
||||
|
||||
let ((mut leader, mut follower), (_leader_vm, _follower_vm)) = create_pair().await;
|
||||
|
||||
_ = perform_key_exchange(
|
||||
&mut leader,
|
||||
&mut follower,
|
||||
leader_private_key.clone(),
|
||||
follower_private_key.clone(),
|
||||
server_public_key,
|
||||
)
|
||||
.await;
|
||||
|
||||
leader.set_server_key(server_public_key);
|
||||
|
||||
let ((mut l_pms1, l_pms2), (f_pms1, f_pms2)) =
|
||||
tokio::try_join!(leader.compute_pms_shares(), follower.compute_pms_shares()).unwrap();
|
||||
|
||||
l_pms1 = l_pms1 + P256::one();
|
||||
|
||||
let err = tokio::try_join!(
|
||||
leader.compute_pms_for(l_pms1, l_pms2),
|
||||
follower.compute_pms_for(f_pms1, f_pms2)
|
||||
)
|
||||
.unwrap_err();
|
||||
|
||||
assert!(matches!(err, KeyExchangeError::CheckFailed));
|
||||
}
|
||||
|
||||
async fn perform_key_exchange(
|
||||
leader: &mut impl KeyExchange,
|
||||
follower: &mut impl KeyExchange,
|
||||
leader_private_key: SecretKey,
|
||||
follower_private_key: SecretKey,
|
||||
server_public_key: PublicKey,
|
||||
) -> PublicKey {
|
||||
tokio::try_join!(leader.setup(), follower.setup()).unwrap();
|
||||
|
||||
let (client_public_key, _) = tokio::try_join!(
|
||||
leader.compute_client_key(leader_private_key),
|
||||
follower.compute_client_key(follower_private_key)
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
leader.set_server_key(server_public_key);
|
||||
|
||||
client_public_key.unwrap()
|
||||
}
|
||||
}
|
||||
@@ -1,108 +0,0 @@
|
||||
//! # The Key Exchange Protocol
|
||||
//!
|
||||
//! This crate implements a key exchange protocol with 3 parties, namely server, leader and
|
||||
//! follower. The goal is to end up with a shared secret (ECDH) between the server and the client.
|
||||
//! The client in this context is leader and follower combined, which means that each of them will
|
||||
//! end up with a share of the shared secret. The leader will do all the necessary communication
|
||||
//! with the server alone and forward all messages from and to the follower.
|
||||
//!
|
||||
//! A detailed description of this protocol can be found in our documentation
|
||||
//! <https://docs.tlsnotary.org/protocol/notarization/key_exchange.html>.
|
||||
|
||||
#![deny(missing_docs, unreachable_pub, unused_must_use)]
|
||||
#![deny(clippy::all)]
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
mod circuit;
|
||||
mod config;
|
||||
mod exchange;
|
||||
#[cfg(feature = "mock")]
|
||||
pub mod mock;
|
||||
pub mod msg;
|
||||
|
||||
pub use config::{
|
||||
KeyExchangeConfig, KeyExchangeConfigBuilder, KeyExchangeConfigBuilderError, Role,
|
||||
};
|
||||
pub use exchange::KeyExchangeCore;
|
||||
pub use msg::KeyExchangeMessage;
|
||||
|
||||
/// A channel for exchanging key exchange messages
|
||||
pub type KeyExchangeChannel = Box<dyn Duplex<KeyExchangeMessage>>;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use mpz_garble::value::ValueRef;
|
||||
use p256::{PublicKey, SecretKey};
|
||||
use utils_aio::duplex::Duplex;
|
||||
|
||||
/// Pre-master secret.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Pms(ValueRef);
|
||||
|
||||
impl Pms {
|
||||
/// Create a new PMS
|
||||
pub fn new(value: ValueRef) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
|
||||
/// Get the value of the PMS
|
||||
pub fn into_value(self) -> ValueRef {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
/// An error that can occur during the key exchange protocol
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[allow(missing_docs)]
|
||||
pub enum KeyExchangeError {
|
||||
#[error(transparent)]
|
||||
IOError(#[from] std::io::Error),
|
||||
#[error(transparent)]
|
||||
MemoryError(#[from] mpz_garble::MemoryError),
|
||||
#[error(transparent)]
|
||||
LoadError(#[from] mpz_garble::LoadError),
|
||||
#[error(transparent)]
|
||||
ExecutionError(#[from] mpz_garble::ExecutionError),
|
||||
#[error(transparent)]
|
||||
DecodeError(#[from] mpz_garble::DecodeError),
|
||||
#[error(transparent)]
|
||||
PointAdditionError(#[from] point_addition::PointAdditionError),
|
||||
#[error(transparent)]
|
||||
PublicKey(#[from] p256::elliptic_curve::Error),
|
||||
#[error(transparent)]
|
||||
KeyParseError(#[from] msg::KeyParseError),
|
||||
#[error("Server Key not set")]
|
||||
NoServerKey,
|
||||
#[error("Private key not set")]
|
||||
NoPrivateKey,
|
||||
#[error("invalid state: {0}")]
|
||||
InvalidState(String),
|
||||
#[error("PMS equality check failed")]
|
||||
CheckFailed,
|
||||
}
|
||||
|
||||
/// A trait for the 3-party key exchange protocol
|
||||
#[async_trait]
|
||||
pub trait KeyExchange {
|
||||
/// Get the server's public key
|
||||
fn server_key(&self) -> Option<PublicKey>;
|
||||
|
||||
/// Set the server's public key
|
||||
fn set_server_key(&mut self, server_key: PublicKey);
|
||||
|
||||
/// Performs any necessary one-time setup, returning a reference to the PMS.
|
||||
///
|
||||
/// The PMS will not be assigned until `compute_pms` is called.
|
||||
async fn setup(&mut self) -> Result<Pms, KeyExchangeError>;
|
||||
|
||||
/// Compute the client's public key
|
||||
///
|
||||
/// The client's public key in this context is the combined public key (EC point addition) of
|
||||
/// the leader's public key and the follower's public key.
|
||||
async fn compute_client_key(
|
||||
&mut self,
|
||||
private_key: SecretKey,
|
||||
) -> Result<Option<PublicKey>, KeyExchangeError>;
|
||||
|
||||
/// Computes the PMS
|
||||
async fn compute_pms(&mut self) -> Result<Pms, KeyExchangeError>;
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
//! This module provides mock types for key exchange leader and follower and a function to create
|
||||
//! such a pair
|
||||
|
||||
use crate::{KeyExchangeConfig, KeyExchangeCore, KeyExchangeMessage, Role};
|
||||
|
||||
use mpz_garble::{Decode, Execute, Memory};
|
||||
use point_addition::mock::{
|
||||
mock_point_converter_pair, MockPointAdditionReceiver, MockPointAdditionSender,
|
||||
};
|
||||
use utils_aio::duplex::MemoryDuplex;
|
||||
|
||||
/// A mock key exchange instance
|
||||
pub type MockKeyExchange<E> =
|
||||
KeyExchangeCore<MockPointAdditionSender, MockPointAdditionReceiver, E>;
|
||||
|
||||
/// Create a mock pair of key exchange leader and follower
|
||||
pub fn create_mock_key_exchange_pair<E: Memory + Execute + Decode + Send>(
|
||||
id: &str,
|
||||
leader_executor: E,
|
||||
follower_executor: E,
|
||||
) -> (MockKeyExchange<E>, MockKeyExchange<E>) {
|
||||
let (leader_pa_sender, follower_pa_recvr) = mock_point_converter_pair(&format!("{}/pa/0", id));
|
||||
let (follower_pa_sender, leader_pa_recvr) = mock_point_converter_pair(&format!("{}/pa/1", id));
|
||||
|
||||
let (leader_channel, follower_channel) = MemoryDuplex::<KeyExchangeMessage>::new();
|
||||
|
||||
let key_exchange_config_leader = KeyExchangeConfig::builder()
|
||||
.id(id)
|
||||
.role(Role::Leader)
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let key_exchange_config_follower = KeyExchangeConfig::builder()
|
||||
.id(id)
|
||||
.role(Role::Follower)
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let leader = KeyExchangeCore::new(
|
||||
Box::new(leader_channel),
|
||||
leader_pa_sender,
|
||||
leader_pa_recvr,
|
||||
leader_executor,
|
||||
key_exchange_config_leader,
|
||||
);
|
||||
|
||||
let follower = KeyExchangeCore::new(
|
||||
Box::new(follower_channel),
|
||||
follower_pa_sender,
|
||||
follower_pa_recvr,
|
||||
follower_executor,
|
||||
key_exchange_config_follower,
|
||||
);
|
||||
|
||||
(leader, follower)
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
//! This module contains the message types exchanged between user and notary
|
||||
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
|
||||
use p256::{elliptic_curve::sec1::ToEncodedPoint, PublicKey as P256PublicKey};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// A type for messages exchanged between user and notary during the key exchange protocol
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[allow(missing_docs)]
|
||||
pub enum KeyExchangeMessage {
|
||||
FollowerPublicKey(PublicKey),
|
||||
ServerPublicKey(PublicKey),
|
||||
}
|
||||
|
||||
/// A wrapper for a serialized public key
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct PublicKey {
|
||||
/// The sec1 serialized public key
|
||||
pub key: Vec<u8>,
|
||||
}
|
||||
|
||||
/// An error that can occur during parsing of a public key
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub struct KeyParseError(#[from] p256::elliptic_curve::Error);
|
||||
|
||||
impl Display for KeyParseError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "Unable to parse public key: {}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<P256PublicKey> for PublicKey {
|
||||
fn from(value: P256PublicKey) -> Self {
|
||||
let key = value.to_encoded_point(false).as_bytes().to_vec();
|
||||
PublicKey { key }
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<PublicKey> for P256PublicKey {
|
||||
type Error = KeyParseError;
|
||||
|
||||
fn try_from(value: PublicKey) -> Result<Self, Self::Error> {
|
||||
P256PublicKey::from_sec1_bytes(&value.key).map_err(Into::into)
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
[package]
|
||||
name = "tlsn-point-addition"
|
||||
authors = ["TLSNotary Team"]
|
||||
description = "Addition of EC points using 2PC, producing additive secret-shares of the resulting x-coordinate"
|
||||
keywords = ["tls", "mpc", "2pc", "ecc", "elliptic"]
|
||||
categories = ["cryptography"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
version = "0.1.0-alpha.4"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
name = "point_addition"
|
||||
|
||||
[features]
|
||||
default = ["mock"]
|
||||
mock = ["dep:mpz-core"]
|
||||
tracing = ["dep:tracing"]
|
||||
|
||||
[dependencies]
|
||||
mpz-core = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "850636f", optional = true }
|
||||
mpz-share-conversion = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "850636f" }
|
||||
mpz-share-conversion-core = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "850636f" }
|
||||
p256 = { version = "0.13", features = ["arithmetic"] }
|
||||
tracing = { version = "0.1", optional = true }
|
||||
async-trait = "0.1"
|
||||
thiserror = "1"
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "1.23", features = ["macros", "rt", "rt-multi-thread"] }
|
||||
rand_chacha = "0.3"
|
||||
rand = "0.8"
|
||||
@@ -1,131 +0,0 @@
|
||||
//! This module implements a secure two-party computation protocol for adding two private EC points
|
||||
//! and secret-sharing the resulting x coordinate (the shares are field elements of the field
|
||||
//! underlying the elliptic curve).
|
||||
//! This protocol has semi-honest security.
|
||||
//!
|
||||
//! The protocol is described in <https://docs.tlsnotary.org/protocol/notarization/key_exchange.html>
|
||||
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use super::{PointAddition, PointAdditionError};
|
||||
use async_trait::async_trait;
|
||||
use mpz_share_conversion::ShareConversion;
|
||||
use mpz_share_conversion_core::fields::{p256::P256, Field};
|
||||
use p256::EncodedPoint;
|
||||
|
||||
/// The instance used for adding the curve points
|
||||
#[derive(Debug)]
|
||||
pub struct MpcPointAddition<F, C>
|
||||
where
|
||||
F: Field,
|
||||
C: ShareConversion<F>,
|
||||
{
|
||||
/// Indicates which role this converter instance will fulfill
|
||||
role: Role,
|
||||
/// The share converter
|
||||
converter: C,
|
||||
|
||||
_field: PhantomData<F>,
|
||||
}
|
||||
|
||||
/// The role: either Leader or Follower
|
||||
///
|
||||
/// Follower needs to perform an inversion operation on the point during point addition
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum Role {
|
||||
Leader,
|
||||
Follower,
|
||||
}
|
||||
|
||||
impl Role {
|
||||
/// Adapt the point depending on the role
|
||||
///
|
||||
/// One party needs to adapt the coordinates. We decided that this is the follower's job.
|
||||
fn adapt_point<V: Field>(&self, [x, y]: [V; 2]) -> [V; 2] {
|
||||
match self {
|
||||
Role::Leader => [x, y],
|
||||
Role::Follower => [-x, -y],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<F, C> MpcPointAddition<F, C>
|
||||
where
|
||||
F: Field,
|
||||
C: ShareConversion<F> + std::fmt::Debug,
|
||||
{
|
||||
/// Create a new [MpcPointAddition] instance
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(level = "info", ret))]
|
||||
pub fn new(role: Role, converter: C) -> Self {
|
||||
Self {
|
||||
converter,
|
||||
role,
|
||||
_field: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Perform the conversion of P = A + B => P_x = a + b
|
||||
///
|
||||
/// Since we are only interested in the x-coordinate of P (for the PMS) and because elliptic
|
||||
/// curve point addition is an expensive operation in 2PC, we secret-share the x-coordinate
|
||||
/// of P as a simple addition of field elements between the two parties. So we go from an EC
|
||||
/// point addition to an addition of field elements for the x-coordinate.
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(level = "info", skip(point), err)
|
||||
)]
|
||||
async fn convert(&mut self, point: [F; 2]) -> Result<F, PointAdditionError> {
|
||||
let [x, y] = point;
|
||||
let [x_n, y_n] = self.role.adapt_point([x, y]);
|
||||
|
||||
let a2m_output = self.converter.to_multiplicative(vec![y_n, x_n]).await?;
|
||||
|
||||
let a = a2m_output[0];
|
||||
let b = a2m_output[1];
|
||||
|
||||
let c = a * b.inverse();
|
||||
let c = c * c;
|
||||
|
||||
let d = self.converter.to_additive(vec![c]).await?[0];
|
||||
let x_r = d + -x;
|
||||
|
||||
Ok(x_r)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<C> PointAddition for MpcPointAddition<P256, C>
|
||||
where
|
||||
C: ShareConversion<P256> + Send + Sync + std::fmt::Debug,
|
||||
{
|
||||
type Point = EncodedPoint;
|
||||
type XCoordinate = P256;
|
||||
|
||||
async fn compute_x_coordinate_share(
|
||||
&mut self,
|
||||
point: Self::Point,
|
||||
) -> Result<Self::XCoordinate, PointAdditionError> {
|
||||
let [x, y] = point_to_p256(point)?;
|
||||
self.convert([x, y]).await
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert the external library's point type to our library's field type
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(level = "debug", skip(point), err)
|
||||
)]
|
||||
pub(crate) fn point_to_p256(point: EncodedPoint) -> Result<[P256; 2], PointAdditionError> {
|
||||
let mut x: [u8; 32] = (*point.x().ok_or(PointAdditionError::Coordinates)?).into();
|
||||
let mut y: [u8; 32] = (*point.y().ok_or(PointAdditionError::Coordinates)?).into();
|
||||
|
||||
// reverse to little endian
|
||||
x.reverse();
|
||||
y.reverse();
|
||||
|
||||
let x = P256::try_from(x).unwrap();
|
||||
let y = P256::try_from(y).unwrap();
|
||||
|
||||
Ok([x, y])
|
||||
}
|
||||
@@ -1,115 +0,0 @@
|
||||
//! A secure two-party computation (2PC) library for converting additive shares of an elliptic
|
||||
//! curve (EC) point into additive shares of said point's x-coordinate. The additive shares of the
|
||||
//! x-coordinate are finite field elements.
|
||||
|
||||
#![deny(missing_docs, unreachable_pub, unused_must_use)]
|
||||
#![deny(clippy::all)]
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
use async_trait::async_trait;
|
||||
use mpz_share_conversion::ShareConversionError;
|
||||
use mpz_share_conversion_core::fields::Field;
|
||||
|
||||
mod conversion;
|
||||
|
||||
/// A mock implementation of the [PointAddition] trait
|
||||
#[cfg(feature = "mock")]
|
||||
pub mod mock;
|
||||
|
||||
pub use conversion::{MpcPointAddition, Role};
|
||||
pub use mpz_share_conversion_core::fields::p256::P256;
|
||||
|
||||
/// The error type for [PointAddition]
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum PointAdditionError {
|
||||
#[error(transparent)]
|
||||
ShareConversion(#[from] ShareConversionError),
|
||||
#[error("Unable to get coordinates from elliptic curve point")]
|
||||
Coordinates,
|
||||
}
|
||||
|
||||
/// A trait for secret-sharing the sum of two elliptic curve points as a sum of field elements
|
||||
///
|
||||
/// This trait is for securely secret-sharing the addition of two elliptic curve points.
|
||||
/// Let `P + Q = O = (x, y)`. Each party receives additive shares of the x-coordinate.
|
||||
#[async_trait]
|
||||
pub trait PointAddition {
|
||||
/// The elliptic curve point type
|
||||
type Point;
|
||||
/// The x-coordinate type for the finite field underlying the EC
|
||||
type XCoordinate: Field;
|
||||
|
||||
/// Adds two elliptic curve points in 2PC, returning respective secret shares
|
||||
/// of the resulting x-coordinate to both parties.
|
||||
async fn compute_x_coordinate_share(
|
||||
&mut self,
|
||||
point: Self::Point,
|
||||
) -> Result<Self::XCoordinate, PointAdditionError>;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{conversion::point_to_p256, mock::mock_point_converter_pair, PointAddition};
|
||||
use mpz_core::Block;
|
||||
use mpz_share_conversion_core::{fields::p256::P256, Field};
|
||||
use p256::{
|
||||
elliptic_curve::sec1::{FromEncodedPoint, ToEncodedPoint},
|
||||
EncodedPoint, NonZeroScalar, ProjectivePoint, PublicKey,
|
||||
};
|
||||
use rand::{Rng, SeedableRng};
|
||||
use rand_chacha::ChaCha12Rng;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_point_conversion() {
|
||||
let mut rng = ChaCha12Rng::from_seed([0_u8; 32]);
|
||||
|
||||
let p1: [u8; 32] = rng.gen();
|
||||
let p2: [u8; 32] = rng.gen();
|
||||
|
||||
let p1 = curve_point_from_be_bytes(p1);
|
||||
let p2 = curve_point_from_be_bytes(p2);
|
||||
|
||||
let p = add_curve_points(&p1, &p2);
|
||||
|
||||
let (mut c1, mut c2) = mock_point_converter_pair("test");
|
||||
|
||||
let c1_fut = c1.compute_x_coordinate_share(p1);
|
||||
let c2_fut = c2.compute_x_coordinate_share(p2);
|
||||
|
||||
let (c1_output, c2_output) = tokio::join!(c1_fut, c2_fut);
|
||||
let (c1_output, c2_output) = (c1_output.unwrap(), c2_output.unwrap());
|
||||
|
||||
assert_eq!(point_to_p256(p).unwrap()[0], c1_output + c2_output);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_point_to_p256() {
|
||||
let mut rng = ChaCha12Rng::from_seed([0_u8; 32]);
|
||||
|
||||
let p_expected: [u8; 32] = rng.gen();
|
||||
let p_expected = curve_point_from_be_bytes(p_expected);
|
||||
|
||||
let p256: [P256; 2] = point_to_p256(p_expected).unwrap();
|
||||
|
||||
let x: [u8; 32] = p256[0].to_be_bytes().try_into().unwrap();
|
||||
let y: [u8; 32] = p256[1].to_be_bytes().try_into().unwrap();
|
||||
|
||||
let p = EncodedPoint::from_affine_coordinates(&x.into(), &y.into(), false);
|
||||
|
||||
assert_eq!(p_expected, p);
|
||||
}
|
||||
|
||||
fn curve_point_from_be_bytes(bytes: [u8; 32]) -> EncodedPoint {
|
||||
let scalar = NonZeroScalar::from_repr(bytes.into()).unwrap();
|
||||
let pk = PublicKey::from_secret_scalar(&scalar);
|
||||
pk.to_encoded_point(false)
|
||||
}
|
||||
|
||||
fn add_curve_points(p1: &EncodedPoint, p2: &EncodedPoint) -> EncodedPoint {
|
||||
let p1 = ProjectivePoint::from_encoded_point(p1).unwrap();
|
||||
let p2 = ProjectivePoint::from_encoded_point(p2).unwrap();
|
||||
let p = p1 + p2;
|
||||
p.to_encoded_point(false)
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
use crate::{MpcPointAddition, Role};
|
||||
use mpz_share_conversion::{
|
||||
mock::{mock_converter_pair, MockConverterReceiver, MockConverterSender},
|
||||
ReceiverConfig, SenderConfig,
|
||||
};
|
||||
use mpz_share_conversion_core::fields::p256::P256;
|
||||
|
||||
/// A mock point addition sender implementing [MpcPointAddition] for [P256]
|
||||
pub type MockPointAdditionSender = MpcPointAddition<P256, MockConverterSender<P256>>;
|
||||
|
||||
/// A mock point addition receiver implementing [MpcPointAddition] for [P256]
|
||||
pub type MockPointAdditionReceiver = MpcPointAddition<P256, MockConverterReceiver<P256>>;
|
||||
|
||||
/// Create a pair of [MpcPointAddition] instances
|
||||
pub fn mock_point_converter_pair(id: &str) -> (MockPointAdditionSender, MockPointAdditionReceiver) {
|
||||
let (sender, receiver) = mock_converter_pair(
|
||||
SenderConfig::builder()
|
||||
.id(format!("{}/converter", id))
|
||||
.build()
|
||||
.unwrap(),
|
||||
ReceiverConfig::builder()
|
||||
.id(format!("{}/converter", id))
|
||||
.build()
|
||||
.unwrap(),
|
||||
);
|
||||
(
|
||||
MpcPointAddition::new(Role::Leader, sender),
|
||||
MpcPointAddition::new(Role::Follower, receiver),
|
||||
)
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
[workspace]
|
||||
members = ["hmac-sha256-circuits", "hmac-sha256"]
|
||||
resolver = "2"
|
||||
|
||||
[workspace.dependencies]
|
||||
# tlsn
|
||||
mpz-circuits = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "850636f" }
|
||||
mpz-garble = { git = "https://github.com/privacy-scaling-explorations/mpz", rev = "850636f" }
|
||||
|
||||
# async
|
||||
async-trait = "0.1"
|
||||
futures = "0.3"
|
||||
tokio = "1"
|
||||
|
||||
# error/log
|
||||
thiserror = "1"
|
||||
tracing = "0.1"
|
||||
|
||||
# testing
|
||||
criterion = "0.5"
|
||||
@@ -1,22 +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.4"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
name = "hmac_sha256_circuits"
|
||||
|
||||
[features]
|
||||
tracing = ["dep:tracing"]
|
||||
|
||||
[dependencies]
|
||||
mpz-circuits.workspace = true
|
||||
tracing = { workspace = true, optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
ring = "0.17"
|
||||
@@ -1,170 +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)
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(level = "trace", skip(key, builder_state))
|
||||
)]
|
||||
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)
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(level = "trace", skip(key)))]
|
||||
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
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(level = "trace", skip(builder_state, outer_state, inner_state, msg))
|
||||
)]
|
||||
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
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(level = "trace", skip(outer_state, inner_state, msg))
|
||||
)]
|
||||
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.
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(level = "info"))]
|
||||
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.
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(level = "info", skip(label)))]
|
||||
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,246 +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};
|
||||
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(level = "trace", skip(builder_state, outer_state, inner_state, seed))
|
||||
)]
|
||||
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
|
||||
}
|
||||
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(level = "trace", skip(outer_state, inner_state, seed))
|
||||
)]
|
||||
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
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(
|
||||
level = "trace",
|
||||
skip(builder_state, outer_state, inner_state, seed, label)
|
||||
)
|
||||
)]
|
||||
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
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(level = "trace", skip(outer_state, inner_state, seed, label))
|
||||
)]
|
||||
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,205 +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
|
||||
///
|
||||
/// Compute expanded p1 which consists of client_write_key + server_write_key
|
||||
/// Compute 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
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(level = "trace", skip(builder_state, pms))
|
||||
)]
|
||||
#[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.
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(level = "trace", skip(pms)))]
|
||||
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,94 +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
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(level = "trace", skip(builder_state, outer_state, inner_state, label))
|
||||
)]
|
||||
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
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(level = "trace", skip(outer_state, inner_state, label))
|
||||
)]
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
[package]
|
||||
name = "tlsn-hmac-sha256"
|
||||
authors = ["TLSNotary Team"]
|
||||
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.4"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
name = "hmac_sha256"
|
||||
|
||||
[features]
|
||||
default = ["mock"]
|
||||
tracing = ["dep:tracing", "tlsn-hmac-sha256-circuits/tracing"]
|
||||
mock = []
|
||||
|
||||
[dependencies]
|
||||
tlsn-hmac-sha256-circuits = { path = "../hmac-sha256-circuits" }
|
||||
tlsn-utils-aio = { git = "https://github.com/tlsnotary/tlsn-utils", rev = "51f313d" }
|
||||
mpz-garble.workspace = true
|
||||
mpz-circuits.workspace = true
|
||||
|
||||
async-trait.workspace = true
|
||||
futures.workspace = true
|
||||
thiserror.workspace = true
|
||||
tracing = { workspace = true, optional = true }
|
||||
derive_builder = "0.12"
|
||||
enum-try-as-inner = "0.1"
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = { workspace = true, features = ["async_tokio"] }
|
||||
tokio = { workspace = true, features = ["macros", "rt", "rt-multi-thread"] }
|
||||
|
||||
[[bench]]
|
||||
name = "prf"
|
||||
harness = false
|
||||
@@ -1,93 +0,0 @@
|
||||
use criterion::{criterion_group, criterion_main, Criterion};
|
||||
|
||||
use hmac_sha256::{MpcPrf, Prf, PrfConfig, Role};
|
||||
use mpz_garble::{protocol::deap::mock::create_mock_deap_vm, Memory, Vm};
|
||||
|
||||
#[allow(clippy::unit_arg)]
|
||||
fn criterion_benchmark(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("prf");
|
||||
group.sample_size(10);
|
||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||
|
||||
group.bench_function("prf_setup", |b| b.to_async(&rt).iter(setup));
|
||||
group.bench_function("prf", |b| b.to_async(&rt).iter(prf));
|
||||
}
|
||||
|
||||
criterion_group!(benches, criterion_benchmark);
|
||||
criterion_main!(benches);
|
||||
|
||||
async fn setup() {
|
||||
let (mut leader_vm, mut follower_vm) = create_mock_deap_vm("bench").await;
|
||||
|
||||
let mut leader = MpcPrf::new(
|
||||
PrfConfig::builder().role(Role::Leader).build().unwrap(),
|
||||
leader_vm.new_thread("prf/0").await.unwrap(),
|
||||
leader_vm.new_thread("prf/1").await.unwrap(),
|
||||
);
|
||||
let mut follower = MpcPrf::new(
|
||||
PrfConfig::builder().role(Role::Follower).build().unwrap(),
|
||||
follower_vm.new_thread("prf/0").await.unwrap(),
|
||||
follower_vm.new_thread("prf/1").await.unwrap(),
|
||||
);
|
||||
|
||||
let leader_thread = leader_vm.new_thread("setup").await.unwrap();
|
||||
let follower_thread = follower_vm.new_thread("setup").await.unwrap();
|
||||
|
||||
let leader_pms = leader_thread.new_public_input::<[u8; 32]>("pms").unwrap();
|
||||
let follower_pms = follower_thread.new_public_input::<[u8; 32]>("pms").unwrap();
|
||||
|
||||
futures::try_join!(leader.setup(leader_pms), follower.setup(follower_pms)).unwrap();
|
||||
}
|
||||
|
||||
async fn prf() {
|
||||
let (mut leader_vm, mut follower_vm) = create_mock_deap_vm("bench").await;
|
||||
|
||||
let mut leader = MpcPrf::new(
|
||||
PrfConfig::builder().role(Role::Leader).build().unwrap(),
|
||||
leader_vm.new_thread("prf/0").await.unwrap(),
|
||||
leader_vm.new_thread("prf/1").await.unwrap(),
|
||||
);
|
||||
let mut follower = MpcPrf::new(
|
||||
PrfConfig::builder().role(Role::Follower).build().unwrap(),
|
||||
follower_vm.new_thread("prf/0").await.unwrap(),
|
||||
follower_vm.new_thread("prf/1").await.unwrap(),
|
||||
);
|
||||
|
||||
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 leader_thread = leader_vm.new_thread("setup").await.unwrap();
|
||||
let follower_thread = follower_vm.new_thread("setup").await.unwrap();
|
||||
|
||||
let leader_pms = leader_thread.new_public_input::<[u8; 32]>("pms").unwrap();
|
||||
let follower_pms = follower_thread.new_public_input::<[u8; 32]>("pms").unwrap();
|
||||
|
||||
leader_thread.assign(&leader_pms, pms).unwrap();
|
||||
follower_thread.assign(&follower_pms, pms).unwrap();
|
||||
|
||||
futures::try_join!(leader.setup(leader_pms), follower.setup(follower_pms)).unwrap();
|
||||
|
||||
let (_leader_keys, _follower_keys) = futures::try_join!(
|
||||
leader.compute_session_keys_private(client_random, server_random),
|
||||
follower.compute_session_keys_blind()
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let _ = futures::try_join!(
|
||||
leader.compute_client_finished_vd_private(cf_hs_hash),
|
||||
follower.compute_client_finished_vd_blind()
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let _ = futures::try_join!(
|
||||
leader.compute_server_finished_vd_private(sf_hs_hash),
|
||||
follower.compute_server_finished_vd_blind()
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
futures::try_join!(leader_vm.finalize(), follower_vm.finalize()).unwrap();
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
use derive_builder::Builder;
|
||||
|
||||
/// Role of this party in the PRF.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Role {
|
||||
/// The leader provides the private inputs to the PRF.
|
||||
Leader,
|
||||
/// The follower is blind to the inputs to the PRF.
|
||||
Follower,
|
||||
}
|
||||
|
||||
/// Configuration for the PRF.
|
||||
#[derive(Debug, Builder)]
|
||||
pub struct PrfConfig {
|
||||
/// The role of this party in the PRF.
|
||||
pub(crate) role: Role,
|
||||
}
|
||||
|
||||
impl PrfConfig {
|
||||
/// Creates a new builder.
|
||||
pub fn builder() -> PrfConfigBuilder {
|
||||
PrfConfigBuilder::default()
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
use std::error::Error;
|
||||
|
||||
use crate::prf::state::StateError;
|
||||
|
||||
/// Errors that can occur during PRF computation.
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[allow(missing_docs)]
|
||||
pub enum PrfError {
|
||||
#[error("MPC backend error: {0:?}")]
|
||||
Mpc(Box<dyn Error + Send + Sync>),
|
||||
#[error("role error: {0:?}")]
|
||||
RoleError(String),
|
||||
#[error("Invalid state: {0}")]
|
||||
InvalidState(String),
|
||||
}
|
||||
|
||||
impl From<StateError> for PrfError {
|
||||
fn from(err: StateError) -> Self {
|
||||
PrfError::InvalidState(err.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<mpz_garble::MemoryError> for PrfError {
|
||||
fn from(err: mpz_garble::MemoryError) -> Self {
|
||||
PrfError::Mpc(Box::new(err))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<mpz_garble::LoadError> for PrfError {
|
||||
fn from(err: mpz_garble::LoadError) -> Self {
|
||||
PrfError::Mpc(Box::new(err))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<mpz_garble::ExecutionError> for PrfError {
|
||||
fn from(err: mpz_garble::ExecutionError) -> Self {
|
||||
PrfError::Mpc(Box::new(err))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<mpz_garble::DecodeError> for PrfError {
|
||||
fn from(err: mpz_garble::DecodeError) -> Self {
|
||||
PrfError::Mpc(Box::new(err))
|
||||
}
|
||||
}
|
||||
@@ -1,217 +0,0 @@
|
||||
//! This module contains the protocol for computing TLS SHA-256 HMAC PRF.
|
||||
|
||||
#![deny(missing_docs, unreachable_pub, unused_must_use)]
|
||||
#![deny(clippy::all)]
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
mod config;
|
||||
mod error;
|
||||
mod prf;
|
||||
|
||||
pub use config::{PrfConfig, PrfConfigBuilder, PrfConfigBuilderError, Role};
|
||||
pub use error::PrfError;
|
||||
pub use prf::MpcPrf;
|
||||
|
||||
use async_trait::async_trait;
|
||||
|
||||
use mpz_garble::value::ValueRef;
|
||||
|
||||
pub(crate) static CF_LABEL: &[u8] = b"client finished";
|
||||
pub(crate) static SF_LABEL: &[u8] = b"server finished";
|
||||
|
||||
/// Session keys computed by the PRF.
|
||||
#[derive(Debug)]
|
||||
pub struct SessionKeys {
|
||||
/// Client write key.
|
||||
pub client_write_key: ValueRef,
|
||||
/// Server write key.
|
||||
pub server_write_key: ValueRef,
|
||||
/// Client IV.
|
||||
pub client_iv: ValueRef,
|
||||
/// Server IV.
|
||||
pub server_iv: ValueRef,
|
||||
}
|
||||
|
||||
/// PRF trait for computing TLS PRF.
|
||||
#[async_trait]
|
||||
pub trait Prf {
|
||||
/// Performs any necessary one-time setup.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `pms` - The pre-master secret.
|
||||
async fn setup(&mut self, pms: ValueRef) -> Result<(), PrfError>;
|
||||
|
||||
/// Computes the session keys using the provided client random, server random and PMS.
|
||||
async fn compute_session_keys_private(
|
||||
&mut self,
|
||||
client_random: [u8; 32],
|
||||
server_random: [u8; 32],
|
||||
) -> Result<SessionKeys, PrfError>;
|
||||
|
||||
/// Computes the client finished verify data using the provided handshake hash.
|
||||
async fn compute_client_finished_vd_private(
|
||||
&mut self,
|
||||
handshake_hash: [u8; 32],
|
||||
) -> Result<[u8; 12], PrfError>;
|
||||
|
||||
/// Computes the server finished verify data using the provided handshake hash.
|
||||
async fn compute_server_finished_vd_private(
|
||||
&mut self,
|
||||
handshake_hash: [u8; 32],
|
||||
) -> Result<[u8; 12], PrfError>;
|
||||
|
||||
/// Computes the session keys using randoms provided by the other party.
|
||||
async fn compute_session_keys_blind(&mut self) -> Result<SessionKeys, PrfError>;
|
||||
|
||||
/// Computes the client finished verify data using the handshake hash provided by the other party.
|
||||
async fn compute_client_finished_vd_blind(&mut self) -> Result<(), PrfError>;
|
||||
|
||||
/// Computes the server finished verify data using the handshake hash provided by the other party.
|
||||
async fn compute_server_finished_vd_blind(&mut self) -> Result<(), PrfError>;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use mpz_garble::{protocol::deap::mock::create_mock_deap_vm, Decode, Memory, Vm};
|
||||
|
||||
use hmac_sha256_circuits::{hmac_sha256_partial, prf, session_keys};
|
||||
|
||||
use super::*;
|
||||
|
||||
fn compute_ms(pms: [u8; 32], client_random: [u8; 32], server_random: [u8; 32]) -> [u8; 48] {
|
||||
let (outer_state, inner_state) = hmac_sha256_partial(&pms);
|
||||
let seed = client_random
|
||||
.iter()
|
||||
.chain(&server_random)
|
||||
.copied()
|
||||
.collect::<Vec<_>>();
|
||||
let ms = prf(outer_state, inner_state, &seed, b"master secret", 48);
|
||||
ms.try_into().unwrap()
|
||||
}
|
||||
|
||||
fn compute_vd(ms: [u8; 48], label: &[u8], hs_hash: [u8; 32]) -> [u8; 12] {
|
||||
let (outer_state, inner_state) = hmac_sha256_partial(&ms);
|
||||
let vd = prf(outer_state, inner_state, &hs_hash, label, 12);
|
||||
vd.try_into().unwrap()
|
||||
}
|
||||
|
||||
#[ignore = "expensive"]
|
||||
#[tokio::test]
|
||||
async fn test_prf() {
|
||||
let pms = [42u8; 32];
|
||||
let client_random = [69u8; 32];
|
||||
let server_random: [u8; 32] = [96u8; 32];
|
||||
let ms = compute_ms(pms, client_random, server_random);
|
||||
|
||||
let (mut leader_vm, mut follower_vm) = create_mock_deap_vm("test").await;
|
||||
|
||||
let mut leader_test_thread = leader_vm.new_thread("test").await.unwrap();
|
||||
let mut follower_test_thread = follower_vm.new_thread("test").await.unwrap();
|
||||
|
||||
// Setup public PMS for testing
|
||||
let leader_pms = leader_test_thread
|
||||
.new_public_input::<[u8; 32]>("pms")
|
||||
.unwrap();
|
||||
let follower_pms = follower_test_thread
|
||||
.new_public_input::<[u8; 32]>("pms")
|
||||
.unwrap();
|
||||
|
||||
leader_test_thread.assign(&leader_pms, pms).unwrap();
|
||||
follower_test_thread.assign(&follower_pms, pms).unwrap();
|
||||
|
||||
let mut leader = MpcPrf::new(
|
||||
PrfConfig::builder().role(Role::Leader).build().unwrap(),
|
||||
leader_vm.new_thread("prf/0").await.unwrap(),
|
||||
leader_vm.new_thread("prf/1").await.unwrap(),
|
||||
);
|
||||
let mut follower = MpcPrf::new(
|
||||
PrfConfig::builder().role(Role::Follower).build().unwrap(),
|
||||
follower_vm.new_thread("prf/0").await.unwrap(),
|
||||
follower_vm.new_thread("prf/1").await.unwrap(),
|
||||
);
|
||||
|
||||
futures::try_join!(leader.setup(leader_pms), follower.setup(follower_pms)).unwrap();
|
||||
|
||||
let (leader_session_keys, follower_session_keys) = futures::try_join!(
|
||||
leader.compute_session_keys_private(client_random, server_random),
|
||||
follower.compute_session_keys_blind()
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let SessionKeys {
|
||||
client_write_key: leader_cwk,
|
||||
server_write_key: leader_swk,
|
||||
client_iv: leader_civ,
|
||||
server_iv: leader_siv,
|
||||
} = leader_session_keys;
|
||||
|
||||
let SessionKeys {
|
||||
client_write_key: follower_cwk,
|
||||
server_write_key: follower_swk,
|
||||
client_iv: follower_civ,
|
||||
server_iv: follower_siv,
|
||||
} = follower_session_keys;
|
||||
|
||||
// Decode session keys
|
||||
let (leader_session_keys, follower_session_keys) = futures::try_join!(
|
||||
async move {
|
||||
leader_test_thread
|
||||
.decode(&[leader_cwk, leader_swk, leader_civ, leader_siv])
|
||||
.await
|
||||
},
|
||||
async move {
|
||||
follower_test_thread
|
||||
.decode(&[follower_cwk, follower_swk, follower_civ, follower_siv])
|
||||
.await
|
||||
}
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let leader_cwk: [u8; 16] = leader_session_keys[0].clone().try_into().unwrap();
|
||||
let leader_swk: [u8; 16] = leader_session_keys[1].clone().try_into().unwrap();
|
||||
let leader_civ: [u8; 4] = leader_session_keys[2].clone().try_into().unwrap();
|
||||
let leader_siv: [u8; 4] = leader_session_keys[3].clone().try_into().unwrap();
|
||||
|
||||
let follower_cwk: [u8; 16] = follower_session_keys[0].clone().try_into().unwrap();
|
||||
let follower_swk: [u8; 16] = follower_session_keys[1].clone().try_into().unwrap();
|
||||
let follower_civ: [u8; 4] = follower_session_keys[2].clone().try_into().unwrap();
|
||||
let follower_siv: [u8; 4] = follower_session_keys[3].clone().try_into().unwrap();
|
||||
|
||||
let (expected_cwk, expected_swk, expected_civ, expected_siv) =
|
||||
session_keys(pms, client_random, server_random);
|
||||
|
||||
assert_eq!(leader_cwk, expected_cwk);
|
||||
assert_eq!(leader_swk, expected_swk);
|
||||
assert_eq!(leader_civ, expected_civ);
|
||||
assert_eq!(leader_siv, expected_siv);
|
||||
|
||||
assert_eq!(follower_cwk, expected_cwk);
|
||||
assert_eq!(follower_swk, expected_swk);
|
||||
assert_eq!(follower_civ, expected_civ);
|
||||
assert_eq!(follower_siv, expected_siv);
|
||||
|
||||
let cf_hs_hash = [1u8; 32];
|
||||
let sf_hs_hash = [2u8; 32];
|
||||
|
||||
let (cf_vd, _) = futures::try_join!(
|
||||
leader.compute_client_finished_vd_private(cf_hs_hash),
|
||||
follower.compute_client_finished_vd_blind()
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let expected_cf_vd = compute_vd(ms, b"client finished", cf_hs_hash);
|
||||
|
||||
assert_eq!(cf_vd, expected_cf_vd);
|
||||
|
||||
let (sf_vd, _) = futures::try_join!(
|
||||
leader.compute_server_finished_vd_private(sf_hs_hash),
|
||||
follower.compute_server_finished_vd_blind()
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let expected_sf_vd = compute_vd(ms, b"server finished", sf_hs_hash);
|
||||
|
||||
assert_eq!(sf_vd, expected_sf_vd);
|
||||
}
|
||||
}
|
||||
@@ -1,475 +0,0 @@
|
||||
use std::{
|
||||
fmt::Debug,
|
||||
sync::{Arc, OnceLock},
|
||||
};
|
||||
|
||||
use async_trait::async_trait;
|
||||
|
||||
use hmac_sha256_circuits::{build_session_keys, build_verify_data};
|
||||
use mpz_circuits::Circuit;
|
||||
use mpz_garble::{
|
||||
config::Visibility, value::ValueRef, Decode, DecodePrivate, Execute, Load, Memory,
|
||||
};
|
||||
use utils_aio::non_blocking_backend::{Backend, NonBlockingBackend};
|
||||
|
||||
use crate::{Prf, PrfConfig, PrfError, Role, SessionKeys, CF_LABEL, SF_LABEL};
|
||||
|
||||
#[cfg(feature = "tracing")]
|
||||
use tracing::instrument;
|
||||
|
||||
/// Circuit for computing TLS session keys.
|
||||
static SESSION_KEYS_CIRC: OnceLock<Arc<Circuit>> = OnceLock::new();
|
||||
/// Circuit for computing TLS client verify data.
|
||||
static CLIENT_VD_CIRC: OnceLock<Arc<Circuit>> = OnceLock::new();
|
||||
/// Circuit for computing TLS server verify data.
|
||||
static SERVER_VD_CIRC: OnceLock<Arc<Circuit>> = OnceLock::new();
|
||||
|
||||
enum Msg {
|
||||
Cf,
|
||||
Sf,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Randoms {
|
||||
pub(crate) client_random: ValueRef,
|
||||
pub(crate) server_random: ValueRef,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct HashState {
|
||||
pub(crate) ms_outer_hash_state: ValueRef,
|
||||
pub(crate) ms_inner_hash_state: ValueRef,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct VerifyData {
|
||||
pub(crate) handshake_hash: ValueRef,
|
||||
pub(crate) vd: ValueRef,
|
||||
}
|
||||
|
||||
/// MPC PRF for computing TLS HMAC-SHA256 PRF.
|
||||
pub struct MpcPrf<E> {
|
||||
config: PrfConfig,
|
||||
state: state::State,
|
||||
thread_0: E,
|
||||
thread_1: E,
|
||||
}
|
||||
|
||||
impl<E> Debug for MpcPrf<E> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("MpcPrf")
|
||||
.field("config", &self.config)
|
||||
.field("state", &self.state)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<E> MpcPrf<E>
|
||||
where
|
||||
E: Load + Memory + Execute + DecodePrivate + Send,
|
||||
{
|
||||
/// Creates a new instance of the PRF.
|
||||
pub fn new(config: PrfConfig, thread_0: E, thread_1: E) -> MpcPrf<E> {
|
||||
MpcPrf {
|
||||
config,
|
||||
state: state::State::Initialized,
|
||||
thread_0,
|
||||
thread_1,
|
||||
}
|
||||
}
|
||||
|
||||
/// Executes a circuit which computes TLS session keys.
|
||||
async fn execute_session_keys(
|
||||
&mut self,
|
||||
randoms: Option<([u8; 32], [u8; 32])>,
|
||||
) -> Result<SessionKeys, PrfError> {
|
||||
let state::SessionKeys {
|
||||
pms,
|
||||
randoms: randoms_refs,
|
||||
hash_state,
|
||||
keys,
|
||||
cf_vd,
|
||||
sf_vd,
|
||||
} = std::mem::replace(&mut self.state, state::State::Error).try_into_session_keys()?;
|
||||
|
||||
let circ = SESSION_KEYS_CIRC
|
||||
.get()
|
||||
.expect("session keys circuit is set");
|
||||
|
||||
if let Some((client_random, server_random)) = randoms {
|
||||
self.thread_0
|
||||
.assign(&randoms_refs.client_random, client_random)?;
|
||||
self.thread_0
|
||||
.assign(&randoms_refs.server_random, server_random)?;
|
||||
}
|
||||
|
||||
self.thread_0
|
||||
.execute(
|
||||
circ.clone(),
|
||||
&[pms, randoms_refs.client_random, randoms_refs.server_random],
|
||||
&[
|
||||
keys.client_write_key.clone(),
|
||||
keys.server_write_key.clone(),
|
||||
keys.client_iv.clone(),
|
||||
keys.server_iv.clone(),
|
||||
hash_state.ms_outer_hash_state.clone(),
|
||||
hash_state.ms_inner_hash_state.clone(),
|
||||
],
|
||||
)
|
||||
.await?;
|
||||
|
||||
self.state = state::State::ClientFinished(state::ClientFinished {
|
||||
hash_state,
|
||||
cf_vd,
|
||||
sf_vd,
|
||||
});
|
||||
|
||||
Ok(keys)
|
||||
}
|
||||
|
||||
async fn execute_cf_vd(
|
||||
&mut self,
|
||||
handshake_hash: Option<[u8; 32]>,
|
||||
) -> Result<Option<[u8; 12]>, PrfError> {
|
||||
let state::ClientFinished {
|
||||
hash_state,
|
||||
cf_vd,
|
||||
sf_vd,
|
||||
} = std::mem::replace(&mut self.state, state::State::Error).try_into_client_finished()?;
|
||||
|
||||
let circ = CLIENT_VD_CIRC.get().expect("client vd circuit is set");
|
||||
|
||||
if let Some(handshake_hash) = handshake_hash {
|
||||
self.thread_0
|
||||
.assign(&cf_vd.handshake_hash, handshake_hash)?;
|
||||
}
|
||||
|
||||
self.thread_0
|
||||
.execute(
|
||||
circ.clone(),
|
||||
&[
|
||||
hash_state.ms_outer_hash_state.clone(),
|
||||
hash_state.ms_inner_hash_state.clone(),
|
||||
cf_vd.handshake_hash,
|
||||
],
|
||||
&[cf_vd.vd.clone()],
|
||||
)
|
||||
.await?;
|
||||
|
||||
let vd = if handshake_hash.is_some() {
|
||||
let mut outputs = self.thread_0.decode_private(&[cf_vd.vd]).await?;
|
||||
let vd: [u8; 12] = outputs.remove(0).try_into().expect("vd is 12 bytes");
|
||||
|
||||
Some(vd)
|
||||
} else {
|
||||
self.thread_0.decode_blind(&[cf_vd.vd]).await?;
|
||||
|
||||
None
|
||||
};
|
||||
|
||||
self.state = state::State::ServerFinished(state::ServerFinished { hash_state, sf_vd });
|
||||
|
||||
Ok(vd)
|
||||
}
|
||||
|
||||
async fn execute_sf_vd(
|
||||
&mut self,
|
||||
handshake_hash: Option<[u8; 32]>,
|
||||
) -> Result<Option<[u8; 12]>, PrfError> {
|
||||
let state::ServerFinished { hash_state, sf_vd } =
|
||||
std::mem::replace(&mut self.state, state::State::Error).try_into_server_finished()?;
|
||||
|
||||
let circ = SERVER_VD_CIRC.get().expect("server vd circuit is set");
|
||||
|
||||
if let Some(handshake_hash) = handshake_hash {
|
||||
self.thread_1
|
||||
.assign(&sf_vd.handshake_hash, handshake_hash)?;
|
||||
}
|
||||
|
||||
self.thread_1
|
||||
.execute(
|
||||
circ.clone(),
|
||||
&[
|
||||
hash_state.ms_outer_hash_state,
|
||||
hash_state.ms_inner_hash_state,
|
||||
sf_vd.handshake_hash,
|
||||
],
|
||||
&[sf_vd.vd.clone()],
|
||||
)
|
||||
.await?;
|
||||
|
||||
let vd = if handshake_hash.is_some() {
|
||||
let mut outputs = self.thread_1.decode_private(&[sf_vd.vd]).await?;
|
||||
let vd: [u8; 12] = outputs.remove(0).try_into().expect("vd is 12 bytes");
|
||||
|
||||
Some(vd)
|
||||
} else {
|
||||
self.thread_1.decode_blind(&[sf_vd.vd]).await?;
|
||||
|
||||
None
|
||||
};
|
||||
|
||||
self.state = state::State::Complete;
|
||||
|
||||
Ok(vd)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<E> Prf for MpcPrf<E>
|
||||
where
|
||||
E: Memory + Load + Execute + Decode + DecodePrivate + Send,
|
||||
{
|
||||
#[cfg_attr(feature = "tracing", instrument(level = "debug", skip_all, err))]
|
||||
async fn setup(&mut self, pms: ValueRef) -> Result<(), PrfError> {
|
||||
std::mem::replace(&mut self.state, state::State::Error).try_into_initialized()?;
|
||||
|
||||
let visibility = match self.config.role {
|
||||
Role::Leader => Visibility::Private,
|
||||
Role::Follower => Visibility::Blind,
|
||||
};
|
||||
|
||||
// Perform pre-computation for all circuits.
|
||||
let (randoms, hash_state, keys) =
|
||||
setup_session_keys(&mut self.thread_0, pms.clone(), visibility).await?;
|
||||
|
||||
let (cf_vd, sf_vd) = futures::try_join!(
|
||||
setup_finished_msg(&mut self.thread_0, Msg::Cf, hash_state.clone(), visibility),
|
||||
setup_finished_msg(&mut self.thread_1, Msg::Sf, hash_state.clone(), visibility),
|
||||
)?;
|
||||
|
||||
self.state = state::State::SessionKeys(state::SessionKeys {
|
||||
pms,
|
||||
randoms,
|
||||
hash_state,
|
||||
keys,
|
||||
cf_vd,
|
||||
sf_vd,
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", instrument(level = "debug", skip_all, err))]
|
||||
async fn compute_session_keys_private(
|
||||
&mut self,
|
||||
client_random: [u8; 32],
|
||||
server_random: [u8; 32],
|
||||
) -> Result<SessionKeys, PrfError> {
|
||||
if self.config.role != Role::Leader {
|
||||
return Err(PrfError::RoleError(
|
||||
"only leader can provide inputs".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
self.execute_session_keys(Some((client_random, server_random)))
|
||||
.await
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", instrument(level = "debug", skip_all, err))]
|
||||
async fn compute_client_finished_vd_private(
|
||||
&mut self,
|
||||
handshake_hash: [u8; 32],
|
||||
) -> Result<[u8; 12], PrfError> {
|
||||
if self.config.role != Role::Leader {
|
||||
return Err(PrfError::RoleError(
|
||||
"only leader can provide inputs".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
self.execute_cf_vd(Some(handshake_hash))
|
||||
.await
|
||||
.map(|hash| hash.expect("vd is decoded"))
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", instrument(level = "debug", skip_all, err))]
|
||||
async fn compute_server_finished_vd_private(
|
||||
&mut self,
|
||||
handshake_hash: [u8; 32],
|
||||
) -> Result<[u8; 12], PrfError> {
|
||||
if self.config.role != Role::Leader {
|
||||
return Err(PrfError::RoleError(
|
||||
"only leader can provide inputs".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
self.execute_sf_vd(Some(handshake_hash))
|
||||
.await
|
||||
.map(|hash| hash.expect("vd is decoded"))
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", instrument(level = "debug", skip_all, err))]
|
||||
async fn compute_session_keys_blind(&mut self) -> Result<SessionKeys, PrfError> {
|
||||
if self.config.role != Role::Follower {
|
||||
return Err(PrfError::RoleError(
|
||||
"leader must provide inputs".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
self.execute_session_keys(None).await
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", instrument(level = "debug", skip_all, err))]
|
||||
async fn compute_client_finished_vd_blind(&mut self) -> Result<(), PrfError> {
|
||||
if self.config.role != Role::Follower {
|
||||
return Err(PrfError::RoleError(
|
||||
"leader must provide inputs".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
self.execute_cf_vd(None).await.map(|_| ())
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", instrument(level = "debug", skip(self), err))]
|
||||
async fn compute_server_finished_vd_blind(&mut self) -> Result<(), PrfError> {
|
||||
if self.config.role != Role::Follower {
|
||||
return Err(PrfError::RoleError(
|
||||
"leader must provide inputs".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
self.execute_sf_vd(None).await.map(|_| ())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) mod state {
|
||||
use super::*;
|
||||
use enum_try_as_inner::EnumTryAsInner;
|
||||
|
||||
#[derive(Debug, EnumTryAsInner)]
|
||||
#[derive_err(Debug)]
|
||||
pub(crate) enum State {
|
||||
Initialized,
|
||||
SessionKeys(SessionKeys),
|
||||
ClientFinished(ClientFinished),
|
||||
ServerFinished(ServerFinished),
|
||||
Complete,
|
||||
Error,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct SessionKeys {
|
||||
pub(crate) pms: ValueRef,
|
||||
pub(crate) randoms: Randoms,
|
||||
pub(crate) hash_state: HashState,
|
||||
pub(crate) keys: crate::SessionKeys,
|
||||
pub(crate) cf_vd: VerifyData,
|
||||
pub(crate) sf_vd: VerifyData,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct ClientFinished {
|
||||
pub(crate) hash_state: HashState,
|
||||
pub(crate) cf_vd: VerifyData,
|
||||
pub(crate) sf_vd: VerifyData,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct ServerFinished {
|
||||
pub(crate) hash_state: HashState,
|
||||
pub(crate) sf_vd: VerifyData,
|
||||
}
|
||||
}
|
||||
|
||||
async fn setup_session_keys<T: Memory + Load + Send>(
|
||||
thread: &mut T,
|
||||
pms: ValueRef,
|
||||
visibility: Visibility,
|
||||
) -> Result<(Randoms, HashState, SessionKeys), PrfError> {
|
||||
let client_random = thread.new_input::<[u8; 32]>("client_finished", visibility)?;
|
||||
let server_random = thread.new_input::<[u8; 32]>("server_finished", visibility)?;
|
||||
|
||||
let client_write_key = thread.new_output::<[u8; 16]>("client_write_key")?;
|
||||
let server_write_key = thread.new_output::<[u8; 16]>("server_write_key")?;
|
||||
let client_iv = thread.new_output::<[u8; 4]>("client_write_iv")?;
|
||||
let server_iv = thread.new_output::<[u8; 4]>("server_write_iv")?;
|
||||
|
||||
let ms_outer_hash_state = thread.new_output::<[u32; 8]>("ms_outer_hash_state")?;
|
||||
let ms_inner_hash_state = thread.new_output::<[u32; 8]>("ms_inner_hash_state")?;
|
||||
|
||||
if SESSION_KEYS_CIRC.get().is_none() {
|
||||
_ = SESSION_KEYS_CIRC.set(Backend::spawn(build_session_keys).await);
|
||||
}
|
||||
|
||||
let circ = SESSION_KEYS_CIRC
|
||||
.get()
|
||||
.expect("session keys circuit is set");
|
||||
|
||||
thread
|
||||
.load(
|
||||
circ.clone(),
|
||||
&[pms, client_random.clone(), server_random.clone()],
|
||||
&[
|
||||
client_write_key.clone(),
|
||||
server_write_key.clone(),
|
||||
client_iv.clone(),
|
||||
server_iv.clone(),
|
||||
ms_outer_hash_state.clone(),
|
||||
ms_inner_hash_state.clone(),
|
||||
],
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok((
|
||||
Randoms {
|
||||
client_random,
|
||||
server_random,
|
||||
},
|
||||
HashState {
|
||||
ms_outer_hash_state,
|
||||
ms_inner_hash_state,
|
||||
},
|
||||
SessionKeys {
|
||||
client_write_key,
|
||||
server_write_key,
|
||||
client_iv,
|
||||
server_iv,
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
async fn setup_finished_msg<T: Memory + Load + Send>(
|
||||
thread: &mut T,
|
||||
msg: Msg,
|
||||
hash_state: HashState,
|
||||
visibility: Visibility,
|
||||
) -> Result<VerifyData, PrfError> {
|
||||
let name = match msg {
|
||||
Msg::Cf => String::from("client_finished"),
|
||||
Msg::Sf => String::from("server_finished"),
|
||||
};
|
||||
|
||||
let handshake_hash =
|
||||
thread.new_input::<[u8; 32]>(&format!("{name}/handshake_hash"), visibility)?;
|
||||
let vd = thread.new_output::<[u8; 12]>(&format!("{name}/vd"))?;
|
||||
|
||||
let circ = match msg {
|
||||
Msg::Cf => &CLIENT_VD_CIRC,
|
||||
Msg::Sf => &SERVER_VD_CIRC,
|
||||
};
|
||||
|
||||
let label = match msg {
|
||||
Msg::Cf => CF_LABEL,
|
||||
Msg::Sf => SF_LABEL,
|
||||
};
|
||||
|
||||
if circ.get().is_none() {
|
||||
_ = circ.set(Backend::spawn(move || build_verify_data(label)).await);
|
||||
}
|
||||
|
||||
let circ = circ.get().expect("session keys circuit is set");
|
||||
|
||||
thread
|
||||
.load(
|
||||
circ.clone(),
|
||||
&[
|
||||
hash_state.ms_outer_hash_state,
|
||||
hash_state.ms_inner_hash_state,
|
||||
handshake_hash.clone(),
|
||||
],
|
||||
&[vd.clone()],
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(VerifyData { handshake_hash, vd })
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
[workspace]
|
||||
members = [
|
||||
"tls-client",
|
||||
"tls-backend",
|
||||
"tls-core",
|
||||
"tls-mpc",
|
||||
"tls-client-async",
|
||||
"tls-server-fixture",
|
||||
]
|
||||
resolver = "2"
|
||||
|
||||
[workspace.dependencies]
|
||||
# rand
|
||||
rand = "0.8"
|
||||
rand_chacha = "0.3"
|
||||
|
||||
# crypto
|
||||
aes = "0.8"
|
||||
aes-gcm = "0.9"
|
||||
sha2 = "0.10"
|
||||
hmac = "0.12"
|
||||
sct = "0.7"
|
||||
digest = "0.10"
|
||||
webpki = "0.22"
|
||||
webpki-roots = "0.26"
|
||||
ring = "0.17"
|
||||
p256 = "0.13"
|
||||
rustls-pemfile = "1"
|
||||
rustls = "0.20"
|
||||
async-rustls = "0.4"
|
||||
|
||||
# async
|
||||
async-trait = "0.1"
|
||||
futures = "0.3"
|
||||
tokio = "1"
|
||||
tokio-util = "0.7"
|
||||
hyper = "0.14"
|
||||
|
||||
# serialization
|
||||
bytes = "1"
|
||||
serde = "1"
|
||||
|
||||
# error/log
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = "0.3"
|
||||
thiserror = "1"
|
||||
log = "0.4"
|
||||
env_logger = "0.10"
|
||||
|
||||
# testing
|
||||
rstest = "0.12"
|
||||
|
||||
# misc
|
||||
derive_builder = "0.12"
|
||||
enum-try-as-inner = "0.1"
|
||||
web-time = "0.2"
|
||||
@@ -1,13 +0,0 @@
|
||||
/// This build script allows us to enable the `read_buf` language feature only
|
||||
/// for Rust Nightly.
|
||||
///
|
||||
/// See the comment in lib.rs to understand why we need this.
|
||||
|
||||
#[cfg_attr(feature = "read_buf", rustversion::not(nightly))]
|
||||
fn main() {}
|
||||
|
||||
#[cfg(feature = "read_buf")]
|
||||
#[rustversion::nightly]
|
||||
fn main() {
|
||||
println!("cargo:rustc-cfg=read_buf");
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
pub(crate) mod persist;
|
||||
|
||||
#[cfg(test)]
|
||||
mod persist_test;
|
||||
@@ -1,526 +0,0 @@
|
||||
use crate::{client::ServerName, ticketer::TimeBase};
|
||||
use std::cmp;
|
||||
#[cfg(feature = "tls12")]
|
||||
use std::mem;
|
||||
#[cfg(feature = "tls12")]
|
||||
use tls_core::suites::Tls12CipherSuite;
|
||||
use tls_core::{
|
||||
msgs::{
|
||||
base::{PayloadU16, PayloadU8},
|
||||
codec::{Codec, Reader},
|
||||
enums::{CipherSuite, ProtocolVersion},
|
||||
handshake::{CertificatePayload, SessionID},
|
||||
},
|
||||
suites::{SupportedCipherSuite, Tls13CipherSuite},
|
||||
};
|
||||
|
||||
// These are the keys and values we store in session storage.
|
||||
|
||||
// --- Client types ---
|
||||
/// Keys for session resumption and tickets.
|
||||
/// Matching value is a `ClientSessionValue`.
|
||||
#[derive(Debug)]
|
||||
pub struct ClientSessionKey {
|
||||
kind: &'static [u8],
|
||||
name: Vec<u8>,
|
||||
}
|
||||
|
||||
impl Codec for ClientSessionKey {
|
||||
fn encode(&self, bytes: &mut Vec<u8>) {
|
||||
bytes.extend_from_slice(self.kind);
|
||||
bytes.extend_from_slice(&self.name);
|
||||
}
|
||||
|
||||
// Don't need to read these.
|
||||
fn read(_r: &mut Reader) -> Option<Self> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl ClientSessionKey {
|
||||
pub fn session_for_server_name(server_name: &ServerName) -> Self {
|
||||
Self {
|
||||
kind: b"session",
|
||||
name: server_name.encode(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn hint_for_server_name(server_name: &ServerName) -> Self {
|
||||
Self {
|
||||
kind: b"kx-hint",
|
||||
name: server_name.encode(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ClientSessionValue {
|
||||
Tls13(Tls13ClientSessionValue),
|
||||
#[cfg(feature = "tls12")]
|
||||
Tls12(Tls12ClientSessionValue),
|
||||
}
|
||||
|
||||
impl ClientSessionValue {
|
||||
pub fn read(
|
||||
reader: &mut Reader<'_>,
|
||||
suite: CipherSuite,
|
||||
supported: &[SupportedCipherSuite],
|
||||
) -> Option<Self> {
|
||||
match supported.iter().find(|s| s.suite() == suite)? {
|
||||
SupportedCipherSuite::Tls13(inner) => {
|
||||
Tls13ClientSessionValue::read(inner, reader).map(ClientSessionValue::Tls13)
|
||||
}
|
||||
#[cfg(feature = "tls12")]
|
||||
SupportedCipherSuite::Tls12(inner) => {
|
||||
Tls12ClientSessionValue::read(inner, reader).map(ClientSessionValue::Tls12)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn common(&self) -> &ClientSessionCommon {
|
||||
match self {
|
||||
Self::Tls13(inner) => &inner.common,
|
||||
#[cfg(feature = "tls12")]
|
||||
Self::Tls12(inner) => &inner.common,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Tls13ClientSessionValue> for ClientSessionValue {
|
||||
fn from(v: Tls13ClientSessionValue) -> Self {
|
||||
Self::Tls13(v)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "tls12")]
|
||||
impl From<Tls12ClientSessionValue> for ClientSessionValue {
|
||||
fn from(v: Tls12ClientSessionValue) -> Self {
|
||||
Self::Tls12(v)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Retrieved<T> {
|
||||
pub value: T,
|
||||
retrieved_at: TimeBase,
|
||||
}
|
||||
|
||||
impl<T> Retrieved<T> {
|
||||
pub fn new(value: T, retrieved_at: TimeBase) -> Self {
|
||||
Self {
|
||||
value,
|
||||
retrieved_at,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Retrieved<&Tls13ClientSessionValue> {
|
||||
pub fn obfuscated_ticket_age(&self) -> u32 {
|
||||
let age_secs = self
|
||||
.retrieved_at
|
||||
.as_secs()
|
||||
.saturating_sub(self.value.common.epoch);
|
||||
let age_millis = age_secs as u32 * 1000;
|
||||
age_millis.wrapping_add(self.value.age_add)
|
||||
}
|
||||
}
|
||||
|
||||
impl Retrieved<ClientSessionValue> {
|
||||
pub fn tls13(&self) -> Option<Retrieved<&Tls13ClientSessionValue>> {
|
||||
match &self.value {
|
||||
ClientSessionValue::Tls13(value) => Some(Retrieved::new(value, self.retrieved_at)),
|
||||
#[cfg(feature = "tls12")]
|
||||
ClientSessionValue::Tls12(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn has_expired(&self) -> bool {
|
||||
let common = self.value.common();
|
||||
common.lifetime_secs != 0
|
||||
&& common.epoch + u64::from(common.lifetime_secs) < self.retrieved_at.as_secs()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> std::ops::Deref for Retrieved<T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.value
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Tls13ClientSessionValue {
|
||||
suite: &'static Tls13CipherSuite,
|
||||
age_add: u32,
|
||||
max_early_data_size: u32,
|
||||
pub common: ClientSessionCommon,
|
||||
}
|
||||
|
||||
impl Tls13ClientSessionValue {
|
||||
pub fn new(
|
||||
suite: &'static Tls13CipherSuite,
|
||||
ticket: Vec<u8>,
|
||||
secret: Vec<u8>,
|
||||
server_cert_chain: Vec<tls_core::key::Certificate>,
|
||||
time_now: TimeBase,
|
||||
lifetime_secs: u32,
|
||||
age_add: u32,
|
||||
max_early_data_size: u32,
|
||||
) -> Self {
|
||||
Self {
|
||||
suite,
|
||||
age_add,
|
||||
max_early_data_size,
|
||||
common: ClientSessionCommon::new(
|
||||
ticket,
|
||||
secret,
|
||||
time_now,
|
||||
lifetime_secs,
|
||||
server_cert_chain,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
/// [`Codec::read()`] with an extra `suite` argument.
|
||||
///
|
||||
/// We decode the `suite` argument separately because it allows us to
|
||||
/// decide whether we're decoding an 1.2 or 1.3 session value.
|
||||
pub fn read(suite: &'static Tls13CipherSuite, r: &mut Reader) -> Option<Self> {
|
||||
Some(Self {
|
||||
suite,
|
||||
age_add: u32::read(r)?,
|
||||
max_early_data_size: u32::read(r)?,
|
||||
common: ClientSessionCommon::read(r)?,
|
||||
})
|
||||
}
|
||||
|
||||
/// Inherent implementation of the [`Codec::get_encoding()`] method.
|
||||
///
|
||||
/// (See `read()` for why this is inherent here.)
|
||||
pub fn get_encoding(&self) -> Vec<u8> {
|
||||
let mut bytes = Vec::with_capacity(16);
|
||||
self.suite.common.suite.encode(&mut bytes);
|
||||
self.age_add.encode(&mut bytes);
|
||||
self.max_early_data_size.encode(&mut bytes);
|
||||
self.common.encode(&mut bytes);
|
||||
bytes
|
||||
}
|
||||
|
||||
pub fn max_early_data_size(&self) -> u32 {
|
||||
self.max_early_data_size
|
||||
}
|
||||
|
||||
pub fn suite(&self) -> &'static Tls13CipherSuite {
|
||||
self.suite
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Deref for Tls13ClientSessionValue {
|
||||
type Target = ClientSessionCommon;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.common
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "tls12")]
|
||||
#[derive(Debug)]
|
||||
pub struct Tls12ClientSessionValue {
|
||||
suite: &'static Tls12CipherSuite,
|
||||
pub session_id: SessionID,
|
||||
extended_ms: bool,
|
||||
pub common: ClientSessionCommon,
|
||||
}
|
||||
|
||||
#[cfg(feature = "tls12")]
|
||||
impl Tls12ClientSessionValue {
|
||||
pub fn new(
|
||||
suite: &'static Tls12CipherSuite,
|
||||
session_id: SessionID,
|
||||
ticket: Vec<u8>,
|
||||
master_secret: Vec<u8>,
|
||||
server_cert_chain: Vec<tls_core::key::Certificate>,
|
||||
time_now: TimeBase,
|
||||
lifetime_secs: u32,
|
||||
extended_ms: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
suite,
|
||||
session_id,
|
||||
extended_ms,
|
||||
common: ClientSessionCommon::new(
|
||||
ticket,
|
||||
master_secret,
|
||||
time_now,
|
||||
lifetime_secs,
|
||||
server_cert_chain,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
/// [`Codec::read()`] with an extra `suite` argument.
|
||||
///
|
||||
/// We decode the `suite` argument separately because it allows us to
|
||||
/// decide whether we're decoding an 1.2 or 1.3 session value.
|
||||
fn read(suite: &'static Tls12CipherSuite, r: &mut Reader) -> Option<Self> {
|
||||
Some(Self {
|
||||
suite,
|
||||
session_id: SessionID::read(r)?,
|
||||
extended_ms: u8::read(r)? == 1,
|
||||
common: ClientSessionCommon::read(r)?,
|
||||
})
|
||||
}
|
||||
|
||||
/// Inherent implementation of the [`Codec::get_encoding()`] method.
|
||||
///
|
||||
/// (See `read()` for why this is inherent here.)
|
||||
pub fn get_encoding(&self) -> Vec<u8> {
|
||||
let mut bytes = Vec::with_capacity(16);
|
||||
self.suite.common.suite.encode(&mut bytes);
|
||||
self.session_id.encode(&mut bytes);
|
||||
(if self.extended_ms { 1u8 } else { 0u8 }).encode(&mut bytes);
|
||||
self.common.encode(&mut bytes);
|
||||
bytes
|
||||
}
|
||||
|
||||
pub fn take_ticket(&mut self) -> Vec<u8> {
|
||||
mem::take(&mut self.common.ticket.0)
|
||||
}
|
||||
|
||||
pub fn extended_ms(&self) -> bool {
|
||||
self.extended_ms
|
||||
}
|
||||
|
||||
pub fn suite(&self) -> &'static Tls12CipherSuite {
|
||||
self.suite
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "tls12")]
|
||||
impl std::ops::Deref for Tls12ClientSessionValue {
|
||||
type Target = ClientSessionCommon;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.common
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ClientSessionCommon {
|
||||
ticket: PayloadU16,
|
||||
secret: PayloadU8,
|
||||
epoch: u64,
|
||||
lifetime_secs: u32,
|
||||
server_cert_chain: CertificatePayload,
|
||||
}
|
||||
|
||||
impl ClientSessionCommon {
|
||||
fn new(
|
||||
ticket: Vec<u8>,
|
||||
secret: Vec<u8>,
|
||||
time_now: TimeBase,
|
||||
lifetime_secs: u32,
|
||||
server_cert_chain: Vec<tls_core::key::Certificate>,
|
||||
) -> Self {
|
||||
Self {
|
||||
ticket: PayloadU16(ticket),
|
||||
secret: PayloadU8(secret),
|
||||
epoch: time_now.as_secs(),
|
||||
lifetime_secs: cmp::min(lifetime_secs, MAX_TICKET_LIFETIME),
|
||||
server_cert_chain,
|
||||
}
|
||||
}
|
||||
|
||||
/// [`Codec::read()`] is inherent here to avoid leaking the [`Codec`]
|
||||
/// implementation through [`Deref`] implementations on
|
||||
/// [`Tls12ClientSessionValue`] and [`Tls13ClientSessionValue`].
|
||||
fn read(r: &mut Reader) -> Option<Self> {
|
||||
Some(Self {
|
||||
ticket: PayloadU16::read(r)?,
|
||||
secret: PayloadU8::read(r)?,
|
||||
epoch: u64::read(r)?,
|
||||
lifetime_secs: u32::read(r)?,
|
||||
server_cert_chain: CertificatePayload::read(r)?,
|
||||
})
|
||||
}
|
||||
|
||||
/// [`Codec::encode()`] is inherent here to avoid leaking the [`Codec`]
|
||||
/// implementation through [`Deref`] implementations on
|
||||
/// [`Tls12ClientSessionValue`] and [`Tls13ClientSessionValue`].
|
||||
fn encode(&self, bytes: &mut Vec<u8>) {
|
||||
self.ticket.encode(bytes);
|
||||
self.secret.encode(bytes);
|
||||
self.epoch.encode(bytes);
|
||||
self.lifetime_secs.encode(bytes);
|
||||
self.server_cert_chain.encode(bytes);
|
||||
}
|
||||
|
||||
pub fn server_cert_chain(&self) -> &[tls_core::key::Certificate] {
|
||||
self.server_cert_chain.as_ref()
|
||||
}
|
||||
|
||||
pub fn secret(&self) -> &[u8] {
|
||||
self.secret.0.as_ref()
|
||||
}
|
||||
|
||||
pub fn ticket(&self) -> &[u8] {
|
||||
self.ticket.0.as_ref()
|
||||
}
|
||||
|
||||
/// Test only: wind back epoch by delta seconds.
|
||||
pub fn rewind_epoch(&mut self, delta: u32) {
|
||||
self.epoch -= delta as u64;
|
||||
}
|
||||
}
|
||||
|
||||
static MAX_TICKET_LIFETIME: u32 = 7 * 24 * 60 * 60;
|
||||
|
||||
/// This is the maximum allowed skew between server and client clocks, over
|
||||
/// the maximum ticket lifetime period. This encompasses TCP retransmission
|
||||
/// times in case packet loss occurs when the client sends the ClientHello
|
||||
/// or receives the NewSessionTicket, _and_ actual clock skew over this period.
|
||||
static MAX_FRESHNESS_SKEW_MS: u32 = 60 * 1000;
|
||||
|
||||
// --- Server types ---
|
||||
pub type ServerSessionKey = SessionID;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ServerSessionValue {
|
||||
pub sni: Option<webpki::DnsName>,
|
||||
pub version: ProtocolVersion,
|
||||
pub cipher_suite: CipherSuite,
|
||||
pub master_secret: PayloadU8,
|
||||
pub extended_ms: bool,
|
||||
pub client_cert_chain: Option<CertificatePayload>,
|
||||
pub alpn: Option<PayloadU8>,
|
||||
pub application_data: PayloadU16,
|
||||
pub creation_time_sec: u64,
|
||||
pub age_obfuscation_offset: u32,
|
||||
freshness: Option<bool>,
|
||||
}
|
||||
|
||||
impl Codec for ServerSessionValue {
|
||||
fn encode(&self, bytes: &mut Vec<u8>) {
|
||||
if let Some(ref sni) = self.sni {
|
||||
1u8.encode(bytes);
|
||||
let sni_bytes: &str = sni.as_ref().into();
|
||||
PayloadU8::new(Vec::from(sni_bytes)).encode(bytes);
|
||||
} else {
|
||||
0u8.encode(bytes);
|
||||
}
|
||||
self.version.encode(bytes);
|
||||
self.cipher_suite.encode(bytes);
|
||||
self.master_secret.encode(bytes);
|
||||
(if self.extended_ms { 1u8 } else { 0u8 }).encode(bytes);
|
||||
if let Some(ref chain) = self.client_cert_chain {
|
||||
1u8.encode(bytes);
|
||||
chain.encode(bytes);
|
||||
} else {
|
||||
0u8.encode(bytes);
|
||||
}
|
||||
if let Some(ref alpn) = self.alpn {
|
||||
1u8.encode(bytes);
|
||||
alpn.encode(bytes);
|
||||
} else {
|
||||
0u8.encode(bytes);
|
||||
}
|
||||
self.application_data.encode(bytes);
|
||||
self.creation_time_sec.encode(bytes);
|
||||
self.age_obfuscation_offset.encode(bytes);
|
||||
}
|
||||
|
||||
fn read(r: &mut Reader) -> Option<Self> {
|
||||
let has_sni = u8::read(r)?;
|
||||
let sni = if has_sni == 1 {
|
||||
let dns_name = PayloadU8::read(r)?;
|
||||
let dns_name = webpki::DnsNameRef::try_from_ascii(&dns_name.0).ok()?;
|
||||
Some(dns_name.into())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let v = ProtocolVersion::read(r)?;
|
||||
let cs = CipherSuite::read(r)?;
|
||||
let ms = PayloadU8::read(r)?;
|
||||
let ems = u8::read(r)?;
|
||||
let has_ccert = u8::read(r)? == 1;
|
||||
let ccert = if has_ccert {
|
||||
Some(CertificatePayload::read(r)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let has_alpn = u8::read(r)? == 1;
|
||||
let alpn = if has_alpn {
|
||||
Some(PayloadU8::read(r)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let application_data = PayloadU16::read(r)?;
|
||||
let creation_time_sec = u64::read(r)?;
|
||||
let age_obfuscation_offset = u32::read(r)?;
|
||||
|
||||
Some(Self {
|
||||
sni,
|
||||
version: v,
|
||||
cipher_suite: cs,
|
||||
master_secret: ms,
|
||||
extended_ms: ems == 1u8,
|
||||
client_cert_chain: ccert,
|
||||
alpn,
|
||||
application_data,
|
||||
creation_time_sec,
|
||||
age_obfuscation_offset,
|
||||
freshness: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ServerSessionValue {
|
||||
pub fn new(
|
||||
sni: Option<&webpki::DnsName>,
|
||||
v: ProtocolVersion,
|
||||
cs: CipherSuite,
|
||||
ms: Vec<u8>,
|
||||
client_cert_chain: Option<CertificatePayload>,
|
||||
alpn: Option<Vec<u8>>,
|
||||
application_data: Vec<u8>,
|
||||
creation_time: TimeBase,
|
||||
age_obfuscation_offset: u32,
|
||||
) -> Self {
|
||||
Self {
|
||||
sni: sni.cloned(),
|
||||
version: v,
|
||||
cipher_suite: cs,
|
||||
master_secret: PayloadU8::new(ms),
|
||||
extended_ms: false,
|
||||
client_cert_chain,
|
||||
alpn: alpn.map(PayloadU8::new),
|
||||
application_data: PayloadU16::new(application_data),
|
||||
creation_time_sec: creation_time.as_secs(),
|
||||
age_obfuscation_offset,
|
||||
freshness: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_extended_ms_used(&mut self) {
|
||||
self.extended_ms = true;
|
||||
}
|
||||
|
||||
pub fn set_freshness(mut self, obfuscated_client_age_ms: u32, time_now: TimeBase) -> Self {
|
||||
let client_age_ms = obfuscated_client_age_ms.wrapping_sub(self.age_obfuscation_offset);
|
||||
let server_age_ms =
|
||||
(time_now.as_secs().saturating_sub(self.creation_time_sec) as u32).saturating_mul(1000);
|
||||
|
||||
let age_difference = if client_age_ms < server_age_ms {
|
||||
server_age_ms - client_age_ms
|
||||
} else {
|
||||
client_age_ms - server_age_ms
|
||||
};
|
||||
|
||||
self.freshness = Some(age_difference <= MAX_FRESHNESS_SKEW_MS);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn is_fresh(&self) -> bool {
|
||||
self.freshness.unwrap_or_default()
|
||||
}
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
use super::persist::*;
|
||||
use crate::ticketer::TimeBase;
|
||||
use std::convert::TryInto;
|
||||
use tls_core::{
|
||||
key::Certificate,
|
||||
msgs::{
|
||||
codec::{Codec, Reader},
|
||||
enums::*,
|
||||
},
|
||||
suites::TLS13_AES_128_GCM_SHA256,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn clientsessionkey_is_debug() {
|
||||
let name = "hello".try_into().unwrap();
|
||||
let csk = ClientSessionKey::session_for_server_name(&name);
|
||||
println!("{:?}", csk);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clientsessionkey_cannot_be_read() {
|
||||
let bytes = [0; 1];
|
||||
let mut rd = Reader::init(&bytes);
|
||||
assert!(ClientSessionKey::read(&mut rd).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clientsessionvalue_is_debug() {
|
||||
let csv = ClientSessionValue::from(Tls13ClientSessionValue::new(
|
||||
TLS13_AES_128_GCM_SHA256.tls13().unwrap(),
|
||||
vec![],
|
||||
vec![1, 2, 3],
|
||||
vec![Certificate(b"abc".to_vec()), Certificate(b"def".to_vec())],
|
||||
TimeBase::now().unwrap(),
|
||||
15,
|
||||
10,
|
||||
128,
|
||||
));
|
||||
println!("{:?}", csv);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serversessionvalue_is_debug() {
|
||||
let ssv = ServerSessionValue::new(
|
||||
None,
|
||||
ProtocolVersion::TLSv1_3,
|
||||
CipherSuite::TLS13_AES_128_GCM_SHA256,
|
||||
vec![1, 2, 3],
|
||||
None,
|
||||
None,
|
||||
vec![4, 5, 6],
|
||||
TimeBase::now().unwrap(),
|
||||
0x12345678,
|
||||
);
|
||||
println!("{:?}", ssv);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serversessionvalue_no_sni() {
|
||||
let bytes = [
|
||||
0x00, 0x03, 0x03, 0xc0, 0x23, 0x03, 0x01, 0x02, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12,
|
||||
0x23, 0x34, 0x45, 0x56, 0x67, 0x78, 0x89, 0xfe, 0xed, 0xf0, 0x0d,
|
||||
];
|
||||
let mut rd = Reader::init(&bytes);
|
||||
let ssv = ServerSessionValue::read(&mut rd).unwrap();
|
||||
assert_eq!(ssv.get_encoding(), bytes);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serversessionvalue_with_cert() {
|
||||
let bytes = [
|
||||
0x00, 0x03, 0x03, 0xc0, 0x23, 0x03, 0x01, 0x02, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12,
|
||||
0x23, 0x34, 0x45, 0x56, 0x67, 0x78, 0x89, 0xfe, 0xed, 0xf0, 0x0d,
|
||||
];
|
||||
let mut rd = Reader::init(&bytes);
|
||||
let ssv = ServerSessionValue::read(&mut rd).unwrap();
|
||||
assert_eq!(ssv.get_encoding(), bytes);
|
||||
}
|
||||
@@ -1,236 +0,0 @@
|
||||
// This program does benchmarking of the functions in verify.rs,
|
||||
// that do certificate chain validation and signature verification.
|
||||
//
|
||||
// Note: we don't use any of the standard 'cargo bench', 'test::Bencher',
|
||||
// etc. because it's unstable at the time of writing.
|
||||
|
||||
use crate::{anchors, verify, verify::ServerCertVerifier, OwnedTrustAnchor};
|
||||
use std::convert::TryInto;
|
||||
use web_time::{Duration, Instant, SystemTime};
|
||||
|
||||
use webpki_roots;
|
||||
|
||||
fn duration_nanos(d: Duration) -> u64 {
|
||||
((d.as_secs() as f64) * 1e9 + (d.subsec_nanos() as f64)) as u64
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_reddit_cert() {
|
||||
Context::new(
|
||||
"reddit",
|
||||
"reddit.com",
|
||||
&[
|
||||
include_bytes!("testdata/cert-reddit.0.der"),
|
||||
include_bytes!("testdata/cert-reddit.1.der"),
|
||||
],
|
||||
)
|
||||
.bench(100)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_github_cert() {
|
||||
Context::new(
|
||||
"github",
|
||||
"github.com",
|
||||
&[
|
||||
include_bytes!("testdata/cert-github.0.der"),
|
||||
include_bytes!("testdata/cert-github.1.der"),
|
||||
],
|
||||
)
|
||||
.bench(100)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_arstechnica_cert() {
|
||||
Context::new(
|
||||
"arstechnica",
|
||||
"arstechnica.com",
|
||||
&[
|
||||
include_bytes!("testdata/cert-arstechnica.0.der"),
|
||||
include_bytes!("testdata/cert-arstechnica.1.der"),
|
||||
include_bytes!("testdata/cert-arstechnica.2.der"),
|
||||
include_bytes!("testdata/cert-arstechnica.3.der"),
|
||||
],
|
||||
)
|
||||
.bench(100)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_servo_cert() {
|
||||
Context::new(
|
||||
"servo",
|
||||
"servo.org",
|
||||
&[
|
||||
include_bytes!("testdata/cert-servo.0.der"),
|
||||
include_bytes!("testdata/cert-servo.1.der"),
|
||||
],
|
||||
)
|
||||
.bench(100)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_twitter_cert() {
|
||||
Context::new(
|
||||
"twitter",
|
||||
"twitter.com",
|
||||
&[
|
||||
include_bytes!("testdata/cert-twitter.0.der"),
|
||||
include_bytes!("testdata/cert-twitter.1.der"),
|
||||
],
|
||||
)
|
||||
.bench(100)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wikipedia_cert() {
|
||||
Context::new(
|
||||
"wikipedia",
|
||||
"wikipedia.org",
|
||||
&[
|
||||
include_bytes!("testdata/cert-wikipedia.0.der"),
|
||||
include_bytes!("testdata/cert-wikipedia.1.der"),
|
||||
],
|
||||
)
|
||||
.bench(100)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_google_cert() {
|
||||
Context::new(
|
||||
"google",
|
||||
"www.google.com",
|
||||
&[
|
||||
include_bytes!("testdata/cert-google.0.der"),
|
||||
include_bytes!("testdata/cert-google.1.der"),
|
||||
],
|
||||
)
|
||||
.bench(100)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hn_cert() {
|
||||
Context::new(
|
||||
"hn",
|
||||
"news.ycombinator.com",
|
||||
&[
|
||||
include_bytes!("testdata/cert-hn.0.der"),
|
||||
include_bytes!("testdata/cert-hn.1.der"),
|
||||
],
|
||||
)
|
||||
.bench(100)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_stackoverflow_cert() {
|
||||
Context::new(
|
||||
"stackoverflow",
|
||||
"stackoverflow.com",
|
||||
&[
|
||||
include_bytes!("testdata/cert-stackoverflow.0.der"),
|
||||
include_bytes!("testdata/cert-stackoverflow.1.der"),
|
||||
],
|
||||
)
|
||||
.bench(100)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_duckduckgo_cert() {
|
||||
Context::new(
|
||||
"duckduckgo",
|
||||
"duckduckgo.com",
|
||||
&[
|
||||
include_bytes!("testdata/cert-duckduckgo.0.der"),
|
||||
include_bytes!("testdata/cert-duckduckgo.1.der"),
|
||||
],
|
||||
)
|
||||
.bench(100)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rustlang_cert() {
|
||||
Context::new(
|
||||
"rustlang",
|
||||
"www.rust-lang.org",
|
||||
&[
|
||||
include_bytes!("testdata/cert-rustlang.0.der"),
|
||||
include_bytes!("testdata/cert-rustlang.1.der"),
|
||||
include_bytes!("testdata/cert-rustlang.2.der"),
|
||||
],
|
||||
)
|
||||
.bench(100)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wapo_cert() {
|
||||
Context::new(
|
||||
"wapo",
|
||||
"www.washingtonpost.com",
|
||||
&[
|
||||
include_bytes!("testdata/cert-wapo.0.der"),
|
||||
include_bytes!("testdata/cert-wapo.1.der"),
|
||||
],
|
||||
)
|
||||
.bench(100)
|
||||
}
|
||||
|
||||
struct Context {
|
||||
name: &'static str,
|
||||
domain: &'static str,
|
||||
roots: anchors::RootCertStore,
|
||||
chain: Vec<tls_core::key::Certificate>,
|
||||
now: SystemTime,
|
||||
}
|
||||
|
||||
impl Context {
|
||||
fn new(name: &'static str, domain: &'static str, certs: &[&'static [u8]]) -> Self {
|
||||
let mut roots = anchors::RootCertStore::empty();
|
||||
roots.add_server_trust_anchors(webpki_roots::TLS_SERVER_ROOTS.iter().map(|ta| {
|
||||
OwnedTrustAnchor::from_subject_spki_name_constraints(
|
||||
ta.subject.as_ref(),
|
||||
ta.subject_public_key_info.as_ref(),
|
||||
ta.name_constraints.as_ref().map(|nc| nc.as_ref()),
|
||||
)
|
||||
}));
|
||||
Self {
|
||||
name,
|
||||
domain,
|
||||
roots,
|
||||
chain: certs
|
||||
.iter()
|
||||
.copied()
|
||||
.map(|bytes| tls_core::key::Certificate(bytes.to_vec()))
|
||||
.collect(),
|
||||
now: SystemTime::UNIX_EPOCH + Duration::from_secs(1640870720),
|
||||
}
|
||||
}
|
||||
|
||||
fn bench(&self, count: usize) {
|
||||
let verifier = verify::WebPkiVerifier::new(self.roots.clone(), None);
|
||||
const SCTS: &[&[u8]] = &[];
|
||||
const OCSP_RESPONSE: &[u8] = &[];
|
||||
let mut times = Vec::new();
|
||||
|
||||
let (end_entity, intermediates) = self.chain.split_first().unwrap();
|
||||
for _ in 0..count {
|
||||
let start = Instant::now();
|
||||
let server_name = self.domain.try_into().unwrap();
|
||||
verifier
|
||||
.verify_server_cert(
|
||||
end_entity,
|
||||
intermediates,
|
||||
&server_name,
|
||||
&mut SCTS.iter().copied(),
|
||||
OCSP_RESPONSE,
|
||||
self.now,
|
||||
)
|
||||
.unwrap();
|
||||
times.push(duration_nanos(Instant::now().duration_since(start)));
|
||||
}
|
||||
|
||||
println!(
|
||||
"verify_server_cert({}): min {:?}us",
|
||||
self.name,
|
||||
times.iter().min().unwrap() / 1000
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIByjCCAVCgAwIBAgIUSA11/39PY7uM9Nc2ITnV1eHzaKYwCgYIKoZIzj0EAwIw
|
||||
HDEaMBgGA1UEAwwRcG9ueXRvd24gRUNEU0EgQ0EwHhcNMTkwNjA5MTcxNTEyWhcN
|
||||
MjkwNjA2MTcxNTEyWjAcMRowGAYDVQQDDBFwb255dG93biBFQ0RTQSBDQTB2MBAG
|
||||
ByqGSM49AgEGBSuBBAAiA2IABLsXWEKs2xXCgW1OcC63pCPjQo0q3VnPc1J24n6m
|
||||
Xwxpg398nzR4n3iHcYA0pKgEneBstSOsXOhbNZ09DAvEr3iSc8ByWWntEbWVjY3g
|
||||
9Kt6Q6Y1sXGkaUIiP9be5lIQRaNTMFEwHQYDVR0OBBYEFKD72TTU/GXhb3/D1/Z7
|
||||
hD/ZG6lKMB8GA1UdIwQYMBaAFKD72TTU/GXhb3/D1/Z7hD/ZG6lKMA8GA1UdEwEB
|
||||
/wQFMAMBAf8wCgYIKoZIzj0EAwIDaAAwZQIxAL9FtbNV7i9trxukhakfTvbXCHgE
|
||||
2pIOT5r/Vc5kSrPU4vJu2MOJz6X/JCX15IbZlQIwJxYfsD8QTQf8J9bP9Pq4SY71
|
||||
obja/vQ6UBixlRB5vDSG0UuukL4kzlyUKpHkwUcj
|
||||
-----END CERTIFICATE-----
|
||||
Binary file not shown.
@@ -1,6 +0,0 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDDl30Srs7laSdaAOzoB
|
||||
kCiehcf1HXv7NqGQBECqshrtHxGEX6bAnBB7JgyDs28NvPGhZANiAAS7F1hCrNsV
|
||||
woFtTnAut6Qj40KNKt1Zz3NSduJ+pl8MaYN/fJ80eJ94h3GANKSoBJ3gbLUjrFzo
|
||||
WzWdPQwLxK94knPAcllp7RG1lY2N4PSrekOmNbFxpGlCIj/W3uZSEEU=
|
||||
-----END PRIVATE KEY-----
|
||||
@@ -1,13 +0,0 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIB8jCCAZegAwIBAgICAxUwCgYIKoZIzj0EAwIwLjEsMCoGA1UEAwwjcG9ueXRv
|
||||
d24gRUNEU0EgbGV2ZWwgMiBpbnRlcm1lZGlhdGUwHhcNMTkwNjA5MTcxNTEyWhcN
|
||||
MjQxMTI5MTcxNTEyWjAaMRgwFgYDVQQDDA9wb255dG93biBjbGllbnQwdjAQBgcq
|
||||
hkjOPQIBBgUrgQQAIgNiAATx0R97foSC0Ra9a13pJzfI1hh3G6476MIMslLHxg5w
|
||||
wCG8k5mMHia2hGOBbdGjoY0C1wJLNrUSov5SfcsYX6/VjHQH/elmb/KOO1AGwPD7
|
||||
1yD1+DG/cjK1okLZIVhbSQyjgZswgZgwDAYDVR0TAQH/BAIwADALBgNVHQ8EBAMC
|
||||
BsAwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwIwHQYDVR0OBBYEFFBkko+0OE2piFRx
|
||||
h9m2UonFYQFEMEQGA1UdIwQ9MDuAFD93gjUQ7CX28Dy5NlFYfYh8XlKSoSCkHjAc
|
||||
MRowGAYDVQQDDBFwb255dG93biBFQ0RTQSBDQYIBezAKBggqhkjOPQQDAgNJADBG
|
||||
AiEAvyquOUQlqAWkSlfwH3nYNmmEG9CT/jjzNs1OBr1RD6ACIQDtmqdbttqgqKAZ
|
||||
Wi5lCzftwM6Hy5aA0qy1v80H4xBJyw==
|
||||
-----END CERTIFICATE-----
|
||||
@@ -1,24 +0,0 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIBuDCCAT2gAwIBAgIBezAKBggqhkjOPQQDAjAcMRowGAYDVQQDDBFwb255dG93
|
||||
biBFQ0RTQSBDQTAeFw0xOTA2MDkxNzE1MTJaFw0yOTA2MDYxNzE1MTJaMC4xLDAq
|
||||
BgNVBAMMI3Bvbnl0b3duIEVDRFNBIGxldmVsIDIgaW50ZXJtZWRpYXRlMFkwEwYH
|
||||
KoZIzj0CAQYIKoZIzj0DAQcDQgAEYtRlPykhT0YLnjcSsbe8rfmJ7ojfWuHImDGx
|
||||
DpF5vJ259giO99qFEcZTi7dNvQGBQC6bsUWddTl3Bc7gxiCr3aNeMFwwHQYDVR0O
|
||||
BBYEFD93gjUQ7CX28Dy5NlFYfYh8XlKSMCAGA1UdJQEB/wQWMBQGCCsGAQUFBwMB
|
||||
BggrBgEFBQcDAjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIB/jAKBggqhkjOPQQD
|
||||
AgNpADBmAjEAxdSnB7ryhG+y7tshwxqrFoZEWXpDLQDZGad0+Wf+7hiNoNCDDdIv
|
||||
MhYxzCDbTS/lAjEAwjsfrp4gxwoz/6fNfUvHyiA3j9jMd64tapzWy2hoqubKBEum
|
||||
EVczk9vVmsiJA5J3
|
||||
-----END CERTIFICATE-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIByjCCAVCgAwIBAgIUSA11/39PY7uM9Nc2ITnV1eHzaKYwCgYIKoZIzj0EAwIw
|
||||
HDEaMBgGA1UEAwwRcG9ueXRvd24gRUNEU0EgQ0EwHhcNMTkwNjA5MTcxNTEyWhcN
|
||||
MjkwNjA2MTcxNTEyWjAcMRowGAYDVQQDDBFwb255dG93biBFQ0RTQSBDQTB2MBAG
|
||||
ByqGSM49AgEGBSuBBAAiA2IABLsXWEKs2xXCgW1OcC63pCPjQo0q3VnPc1J24n6m
|
||||
Xwxpg398nzR4n3iHcYA0pKgEneBstSOsXOhbNZ09DAvEr3iSc8ByWWntEbWVjY3g
|
||||
9Kt6Q6Y1sXGkaUIiP9be5lIQRaNTMFEwHQYDVR0OBBYEFKD72TTU/GXhb3/D1/Z7
|
||||
hD/ZG6lKMB8GA1UdIwQYMBaAFKD72TTU/GXhb3/D1/Z7hD/ZG6lKMA8GA1UdEwEB
|
||||
/wQFMAMBAf8wCgYIKoZIzj0EAwIDaAAwZQIxAL9FtbNV7i9trxukhakfTvbXCHgE
|
||||
2pIOT5r/Vc5kSrPU4vJu2MOJz6X/JCX15IbZlQIwJxYfsD8QTQf8J9bP9Pq4SY71
|
||||
obja/vQ6UBixlRB5vDSG0UuukL4kzlyUKpHkwUcj
|
||||
-----END CERTIFICATE-----
|
||||
@@ -1,37 +0,0 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIB8jCCAZegAwIBAgICAxUwCgYIKoZIzj0EAwIwLjEsMCoGA1UEAwwjcG9ueXRv
|
||||
d24gRUNEU0EgbGV2ZWwgMiBpbnRlcm1lZGlhdGUwHhcNMTkwNjA5MTcxNTEyWhcN
|
||||
MjQxMTI5MTcxNTEyWjAaMRgwFgYDVQQDDA9wb255dG93biBjbGllbnQwdjAQBgcq
|
||||
hkjOPQIBBgUrgQQAIgNiAATx0R97foSC0Ra9a13pJzfI1hh3G6476MIMslLHxg5w
|
||||
wCG8k5mMHia2hGOBbdGjoY0C1wJLNrUSov5SfcsYX6/VjHQH/elmb/KOO1AGwPD7
|
||||
1yD1+DG/cjK1okLZIVhbSQyjgZswgZgwDAYDVR0TAQH/BAIwADALBgNVHQ8EBAMC
|
||||
BsAwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwIwHQYDVR0OBBYEFFBkko+0OE2piFRx
|
||||
h9m2UonFYQFEMEQGA1UdIwQ9MDuAFD93gjUQ7CX28Dy5NlFYfYh8XlKSoSCkHjAc
|
||||
MRowGAYDVQQDDBFwb255dG93biBFQ0RTQSBDQYIBezAKBggqhkjOPQQDAgNJADBG
|
||||
AiEAvyquOUQlqAWkSlfwH3nYNmmEG9CT/jjzNs1OBr1RD6ACIQDtmqdbttqgqKAZ
|
||||
Wi5lCzftwM6Hy5aA0qy1v80H4xBJyw==
|
||||
-----END CERTIFICATE-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIBuDCCAT2gAwIBAgIBezAKBggqhkjOPQQDAjAcMRowGAYDVQQDDBFwb255dG93
|
||||
biBFQ0RTQSBDQTAeFw0xOTA2MDkxNzE1MTJaFw0yOTA2MDYxNzE1MTJaMC4xLDAq
|
||||
BgNVBAMMI3Bvbnl0b3duIEVDRFNBIGxldmVsIDIgaW50ZXJtZWRpYXRlMFkwEwYH
|
||||
KoZIzj0CAQYIKoZIzj0DAQcDQgAEYtRlPykhT0YLnjcSsbe8rfmJ7ojfWuHImDGx
|
||||
DpF5vJ259giO99qFEcZTi7dNvQGBQC6bsUWddTl3Bc7gxiCr3aNeMFwwHQYDVR0O
|
||||
BBYEFD93gjUQ7CX28Dy5NlFYfYh8XlKSMCAGA1UdJQEB/wQWMBQGCCsGAQUFBwMB
|
||||
BggrBgEFBQcDAjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIB/jAKBggqhkjOPQQD
|
||||
AgNpADBmAjEAxdSnB7ryhG+y7tshwxqrFoZEWXpDLQDZGad0+Wf+7hiNoNCDDdIv
|
||||
MhYxzCDbTS/lAjEAwjsfrp4gxwoz/6fNfUvHyiA3j9jMd64tapzWy2hoqubKBEum
|
||||
EVczk9vVmsiJA5J3
|
||||
-----END CERTIFICATE-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIByjCCAVCgAwIBAgIUSA11/39PY7uM9Nc2ITnV1eHzaKYwCgYIKoZIzj0EAwIw
|
||||
HDEaMBgGA1UEAwwRcG9ueXRvd24gRUNEU0EgQ0EwHhcNMTkwNjA5MTcxNTEyWhcN
|
||||
MjkwNjA2MTcxNTEyWjAcMRowGAYDVQQDDBFwb255dG93biBFQ0RTQSBDQTB2MBAG
|
||||
ByqGSM49AgEGBSuBBAAiA2IABLsXWEKs2xXCgW1OcC63pCPjQo0q3VnPc1J24n6m
|
||||
Xwxpg398nzR4n3iHcYA0pKgEneBstSOsXOhbNZ09DAvEr3iSc8ByWWntEbWVjY3g
|
||||
9Kt6Q6Y1sXGkaUIiP9be5lIQRaNTMFEwHQYDVR0OBBYEFKD72TTU/GXhb3/D1/Z7
|
||||
hD/ZG6lKMB8GA1UdIwQYMBaAFKD72TTU/GXhb3/D1/Z7hD/ZG6lKMA8GA1UdEwEB
|
||||
/wQFMAMBAf8wCgYIKoZIzj0EAwIDaAAwZQIxAL9FtbNV7i9trxukhakfTvbXCHgE
|
||||
2pIOT5r/Vc5kSrPU4vJu2MOJz6X/JCX15IbZlQIwJxYfsD8QTQf8J9bP9Pq4SY71
|
||||
obja/vQ6UBixlRB5vDSG0UuukL4kzlyUKpHkwUcj
|
||||
-----END CERTIFICATE-----
|
||||
@@ -1,6 +0,0 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDALKtA1q+8ZBeLi2Gsq
|
||||
UxFTBxNPPhOuyNRkvwRKis/glf9GgEHgvM0qVaxWnRsdCE6hZANiAATx0R97foSC
|
||||
0Ra9a13pJzfI1hh3G6476MIMslLHxg5wwCG8k5mMHia2hGOBbdGjoY0C1wJLNrUS
|
||||
ov5SfcsYX6/VjHQH/elmb/KOO1AGwPD71yD1+DG/cjK1okLZIVhbSQw=
|
||||
-----END PRIVATE KEY-----
|
||||
@@ -1,8 +0,0 @@
|
||||
-----BEGIN CERTIFICATE REQUEST-----
|
||||
MIIBEzCBmQIBADAaMRgwFgYDVQQDDA9wb255dG93biBjbGllbnQwdjAQBgcqhkjO
|
||||
PQIBBgUrgQQAIgNiAATx0R97foSC0Ra9a13pJzfI1hh3G6476MIMslLHxg5wwCG8
|
||||
k5mMHia2hGOBbdGjoY0C1wJLNrUSov5SfcsYX6/VjHQH/elmb/KOO1AGwPD71yD1
|
||||
+DG/cjK1okLZIVhbSQygADAKBggqhkjOPQQDAgNpADBmAjEA8p3W7yFCJ73dOmYQ
|
||||
rpMpLkYNcfxxpNfCWgqaPyWu3UeOcHvC7ihklnFTWzpmEO+PAjEA8O5P4mXlYUtl
|
||||
Dsw8qOrqWSdQ1IykXhM4NxPOkt0TMQZvvrpSsJU6PhwSbJGjVfBR
|
||||
-----END CERTIFICATE REQUEST-----
|
||||
@@ -1,13 +0,0 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIB9zCCAZ6gAwIBAgICAcgwCgYIKoZIzj0EAwIwLjEsMCoGA1UEAwwjcG9ueXRv
|
||||
d24gRUNEU0EgbGV2ZWwgMiBpbnRlcm1lZGlhdGUwHhcNMTkwNjA5MTcxNTEyWhcN
|
||||
MjQxMTI5MTcxNTEyWjAZMRcwFQYDVQQDDA50ZXN0c2VydmVyLmNvbTBZMBMGByqG
|
||||
SM49AgEGCCqGSM49AwEHA0IABPprdHsWc3TtNne2409qO+fC9OFiiXFevQwJjUUC
|
||||
J/X0ihomRsHAnrJvcNyOEWsdu7OwOj4PD9QFMifDEHGYtHOjgcAwgb0wDAYDVR0T
|
||||
AQH/BAIwADALBgNVHQ8EBAMCBsAwHQYDVR0OBBYEFOXZcb/0+/Xql1fOb4pVblzV
|
||||
vUcZMEQGA1UdIwQ9MDuAFD93gjUQ7CX28Dy5NlFYfYh8XlKSoSCkHjAcMRowGAYD
|
||||
VQQDDBFwb255dG93biBFQ0RTQSBDQYIBezA7BgNVHREENDAygg50ZXN0c2VydmVy
|
||||
LmNvbYIVc2Vjb25kLnRlc3RzZXJ2ZXIuY29tgglsb2NhbGhvc3QwCgYIKoZIzj0E
|
||||
AwIDRwAwRAIgXONA4IOh4PbHTuK6oaHtguOIvmxxXCqp8kwJlI1e+MMCICOSrk1F
|
||||
e+VsbKeFQlJ6EM65CLTezDUIZKCmoNWvyTGy
|
||||
-----END CERTIFICATE-----
|
||||
@@ -1,24 +0,0 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIBuDCCAT2gAwIBAgIBezAKBggqhkjOPQQDAjAcMRowGAYDVQQDDBFwb255dG93
|
||||
biBFQ0RTQSBDQTAeFw0xOTA2MDkxNzE1MTJaFw0yOTA2MDYxNzE1MTJaMC4xLDAq
|
||||
BgNVBAMMI3Bvbnl0b3duIEVDRFNBIGxldmVsIDIgaW50ZXJtZWRpYXRlMFkwEwYH
|
||||
KoZIzj0CAQYIKoZIzj0DAQcDQgAEYtRlPykhT0YLnjcSsbe8rfmJ7ojfWuHImDGx
|
||||
DpF5vJ259giO99qFEcZTi7dNvQGBQC6bsUWddTl3Bc7gxiCr3aNeMFwwHQYDVR0O
|
||||
BBYEFD93gjUQ7CX28Dy5NlFYfYh8XlKSMCAGA1UdJQEB/wQWMBQGCCsGAQUFBwMB
|
||||
BggrBgEFBQcDAjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIB/jAKBggqhkjOPQQD
|
||||
AgNpADBmAjEAxdSnB7ryhG+y7tshwxqrFoZEWXpDLQDZGad0+Wf+7hiNoNCDDdIv
|
||||
MhYxzCDbTS/lAjEAwjsfrp4gxwoz/6fNfUvHyiA3j9jMd64tapzWy2hoqubKBEum
|
||||
EVczk9vVmsiJA5J3
|
||||
-----END CERTIFICATE-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIByjCCAVCgAwIBAgIUSA11/39PY7uM9Nc2ITnV1eHzaKYwCgYIKoZIzj0EAwIw
|
||||
HDEaMBgGA1UEAwwRcG9ueXRvd24gRUNEU0EgQ0EwHhcNMTkwNjA5MTcxNTEyWhcN
|
||||
MjkwNjA2MTcxNTEyWjAcMRowGAYDVQQDDBFwb255dG93biBFQ0RTQSBDQTB2MBAG
|
||||
ByqGSM49AgEGBSuBBAAiA2IABLsXWEKs2xXCgW1OcC63pCPjQo0q3VnPc1J24n6m
|
||||
Xwxpg398nzR4n3iHcYA0pKgEneBstSOsXOhbNZ09DAvEr3iSc8ByWWntEbWVjY3g
|
||||
9Kt6Q6Y1sXGkaUIiP9be5lIQRaNTMFEwHQYDVR0OBBYEFKD72TTU/GXhb3/D1/Z7
|
||||
hD/ZG6lKMB8GA1UdIwQYMBaAFKD72TTU/GXhb3/D1/Z7hD/ZG6lKMA8GA1UdEwEB
|
||||
/wQFMAMBAf8wCgYIKoZIzj0EAwIDaAAwZQIxAL9FtbNV7i9trxukhakfTvbXCHgE
|
||||
2pIOT5r/Vc5kSrPU4vJu2MOJz6X/JCX15IbZlQIwJxYfsD8QTQf8J9bP9Pq4SY71
|
||||
obja/vQ6UBixlRB5vDSG0UuukL4kzlyUKpHkwUcj
|
||||
-----END CERTIFICATE-----
|
||||
@@ -1,37 +0,0 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIB9zCCAZ6gAwIBAgICAcgwCgYIKoZIzj0EAwIwLjEsMCoGA1UEAwwjcG9ueXRv
|
||||
d24gRUNEU0EgbGV2ZWwgMiBpbnRlcm1lZGlhdGUwHhcNMTkwNjA5MTcxNTEyWhcN
|
||||
MjQxMTI5MTcxNTEyWjAZMRcwFQYDVQQDDA50ZXN0c2VydmVyLmNvbTBZMBMGByqG
|
||||
SM49AgEGCCqGSM49AwEHA0IABPprdHsWc3TtNne2409qO+fC9OFiiXFevQwJjUUC
|
||||
J/X0ihomRsHAnrJvcNyOEWsdu7OwOj4PD9QFMifDEHGYtHOjgcAwgb0wDAYDVR0T
|
||||
AQH/BAIwADALBgNVHQ8EBAMCBsAwHQYDVR0OBBYEFOXZcb/0+/Xql1fOb4pVblzV
|
||||
vUcZMEQGA1UdIwQ9MDuAFD93gjUQ7CX28Dy5NlFYfYh8XlKSoSCkHjAcMRowGAYD
|
||||
VQQDDBFwb255dG93biBFQ0RTQSBDQYIBezA7BgNVHREENDAygg50ZXN0c2VydmVy
|
||||
LmNvbYIVc2Vjb25kLnRlc3RzZXJ2ZXIuY29tgglsb2NhbGhvc3QwCgYIKoZIzj0E
|
||||
AwIDRwAwRAIgXONA4IOh4PbHTuK6oaHtguOIvmxxXCqp8kwJlI1e+MMCICOSrk1F
|
||||
e+VsbKeFQlJ6EM65CLTezDUIZKCmoNWvyTGy
|
||||
-----END CERTIFICATE-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIBuDCCAT2gAwIBAgIBezAKBggqhkjOPQQDAjAcMRowGAYDVQQDDBFwb255dG93
|
||||
biBFQ0RTQSBDQTAeFw0xOTA2MDkxNzE1MTJaFw0yOTA2MDYxNzE1MTJaMC4xLDAq
|
||||
BgNVBAMMI3Bvbnl0b3duIEVDRFNBIGxldmVsIDIgaW50ZXJtZWRpYXRlMFkwEwYH
|
||||
KoZIzj0CAQYIKoZIzj0DAQcDQgAEYtRlPykhT0YLnjcSsbe8rfmJ7ojfWuHImDGx
|
||||
DpF5vJ259giO99qFEcZTi7dNvQGBQC6bsUWddTl3Bc7gxiCr3aNeMFwwHQYDVR0O
|
||||
BBYEFD93gjUQ7CX28Dy5NlFYfYh8XlKSMCAGA1UdJQEB/wQWMBQGCCsGAQUFBwMB
|
||||
BggrBgEFBQcDAjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIB/jAKBggqhkjOPQQD
|
||||
AgNpADBmAjEAxdSnB7ryhG+y7tshwxqrFoZEWXpDLQDZGad0+Wf+7hiNoNCDDdIv
|
||||
MhYxzCDbTS/lAjEAwjsfrp4gxwoz/6fNfUvHyiA3j9jMd64tapzWy2hoqubKBEum
|
||||
EVczk9vVmsiJA5J3
|
||||
-----END CERTIFICATE-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIByjCCAVCgAwIBAgIUSA11/39PY7uM9Nc2ITnV1eHzaKYwCgYIKoZIzj0EAwIw
|
||||
HDEaMBgGA1UEAwwRcG9ueXRvd24gRUNEU0EgQ0EwHhcNMTkwNjA5MTcxNTEyWhcN
|
||||
MjkwNjA2MTcxNTEyWjAcMRowGAYDVQQDDBFwb255dG93biBFQ0RTQSBDQTB2MBAG
|
||||
ByqGSM49AgEGBSuBBAAiA2IABLsXWEKs2xXCgW1OcC63pCPjQo0q3VnPc1J24n6m
|
||||
Xwxpg398nzR4n3iHcYA0pKgEneBstSOsXOhbNZ09DAvEr3iSc8ByWWntEbWVjY3g
|
||||
9Kt6Q6Y1sXGkaUIiP9be5lIQRaNTMFEwHQYDVR0OBBYEFKD72TTU/GXhb3/D1/Z7
|
||||
hD/ZG6lKMB8GA1UdIwQYMBaAFKD72TTU/GXhb3/D1/Z7hD/ZG6lKMA8GA1UdEwEB
|
||||
/wQFMAMBAf8wCgYIKoZIzj0EAwIDaAAwZQIxAL9FtbNV7i9trxukhakfTvbXCHgE
|
||||
2pIOT5r/Vc5kSrPU4vJu2MOJz6X/JCX15IbZlQIwJxYfsD8QTQf8J9bP9Pq4SY71
|
||||
obja/vQ6UBixlRB5vDSG0UuukL4kzlyUKpHkwUcj
|
||||
-----END CERTIFICATE-----
|
||||
@@ -1,5 +0,0 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgdoMBbIGRw+L9but3
|
||||
PO4WSJfS8wbvUNrF1VuQjsDVMKmhRANCAAT6a3R7FnN07TZ3tuNPajvnwvThYolx
|
||||
Xr0MCY1FAif19IoaJkbBwJ6yb3DcjhFrHbuzsDo+Dw/UBTInwxBxmLRz
|
||||
-----END PRIVATE KEY-----
|
||||
@@ -1,7 +0,0 @@
|
||||
-----BEGIN CERTIFICATE REQUEST-----
|
||||
MIHTMHsCAQAwGTEXMBUGA1UEAwwOdGVzdHNlcnZlci5jb20wWTATBgcqhkjOPQIB
|
||||
BggqhkjOPQMBBwNCAAT6a3R7FnN07TZ3tuNPajvnwvThYolxXr0MCY1FAif19Ioa
|
||||
JkbBwJ6yb3DcjhFrHbuzsDo+Dw/UBTInwxBxmLRzoAAwCgYIKoZIzj0EAwIDSAAw
|
||||
RQIgA9G3IaH4syAQYGJ3ESqXQaoKSrZsDMBD0MgG2g2FC78CIQD+RRTETPkFq0as
|
||||
cca9W/yqg8QN/ZGzE38iEpohyGda/w==
|
||||
-----END CERTIFICATE REQUEST-----
|
||||
@@ -1,12 +0,0 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIBuDCCAT2gAwIBAgIBezAKBggqhkjOPQQDAjAcMRowGAYDVQQDDBFwb255dG93
|
||||
biBFQ0RTQSBDQTAeFw0xOTA2MDkxNzE1MTJaFw0yOTA2MDYxNzE1MTJaMC4xLDAq
|
||||
BgNVBAMMI3Bvbnl0b3duIEVDRFNBIGxldmVsIDIgaW50ZXJtZWRpYXRlMFkwEwYH
|
||||
KoZIzj0CAQYIKoZIzj0DAQcDQgAEYtRlPykhT0YLnjcSsbe8rfmJ7ojfWuHImDGx
|
||||
DpF5vJ259giO99qFEcZTi7dNvQGBQC6bsUWddTl3Bc7gxiCr3aNeMFwwHQYDVR0O
|
||||
BBYEFD93gjUQ7CX28Dy5NlFYfYh8XlKSMCAGA1UdJQEB/wQWMBQGCCsGAQUFBwMB
|
||||
BggrBgEFBQcDAjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIB/jAKBggqhkjOPQQD
|
||||
AgNpADBmAjEAxdSnB7ryhG+y7tshwxqrFoZEWXpDLQDZGad0+Wf+7hiNoNCDDdIv
|
||||
MhYxzCDbTS/lAjEAwjsfrp4gxwoz/6fNfUvHyiA3j9jMd64tapzWy2hoqubKBEum
|
||||
EVczk9vVmsiJA5J3
|
||||
-----END CERTIFICATE-----
|
||||
@@ -1,5 +0,0 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgdniIWGzkYuZcwh/H
|
||||
9hDbaITfndAs+Hin6j+0XjD01MShRANCAARi1GU/KSFPRgueNxKxt7yt+YnuiN9a
|
||||
4ciYMbEOkXm8nbn2CI732oURxlOLt029AYFALpuxRZ11OXcFzuDGIKvd
|
||||
-----END PRIVATE KEY-----
|
||||
@@ -1,7 +0,0 @@
|
||||
-----BEGIN CERTIFICATE REQUEST-----
|
||||
MIHoMIGQAgEAMC4xLDAqBgNVBAMMI3Bvbnl0b3duIEVDRFNBIGxldmVsIDIgaW50
|
||||
ZXJtZWRpYXRlMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEYtRlPykhT0YLnjcS
|
||||
sbe8rfmJ7ojfWuHImDGxDpF5vJ259giO99qFEcZTi7dNvQGBQC6bsUWddTl3Bc7g
|
||||
xiCr3aAAMAoGCCqGSM49BAMCA0cAMEQCIFeMseiKS80m8KmHkl7W8lRXavH5yx/h
|
||||
qTFM+f3T4AnZAiBRR8+rFop/TR51gISUfbMj2W3yTAGxOkCdlPgT+Jxqwg==
|
||||
-----END CERTIFICATE REQUEST-----
|
||||
@@ -1,9 +0,0 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIBTDCB/6ADAgECAhRXcvbYynz4+usVvPtJp++sBUih3TAFBgMrZXAwHDEaMBgG
|
||||
A1UEAwwRcG9ueXRvd24gRWREU0EgQ0EwHhcNMTkwODE2MTMyODUwWhcNMjkwODEz
|
||||
MTMyODUwWjAcMRowGAYDVQQDDBFwb255dG93biBFZERTQSBDQTAqMAUGAytlcAMh
|
||||
AIE4tLweIfcBGfhPqyXFp5pjVxjaiKk+9fTbRy46jAFKo1MwUTAdBgNVHQ4EFgQU
|
||||
z5b9HjkOxffbtCZhWGg+bnxuD6wwHwYDVR0jBBgwFoAUz5b9HjkOxffbtCZhWGg+
|
||||
bnxuD6wwDwYDVR0TAQH/BAUwAwEB/zAFBgMrZXADQQBNlt7z4bZ7KhzecxZEe3i5
|
||||
lH9MRqbpP9Rg4HyzAJfTzFGT183HoJiISdPLbxwMn0KaqSGlVe+9GgNKswoaRAwH
|
||||
-----END CERTIFICATE-----
|
||||
Binary file not shown.
@@ -1,3 +0,0 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MC4CAQAwBQYDK2VwBCIEIJ+e0HfxvslYFLr6ehAnsW6ZnOSu3Cume9OGJhVVLjnK
|
||||
-----END PRIVATE KEY-----
|
||||
@@ -1,11 +0,0 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIBlDCCAUagAwIBAgICAxUwBQYDK2VwMC4xLDAqBgNVBAMMI3Bvbnl0b3duIEVk
|
||||
RFNBIGxldmVsIDIgaW50ZXJtZWRpYXRlMB4XDTE5MDgxNjEzMjg1MVoXDTI1MDIw
|
||||
NTEzMjg1MVowGjEYMBYGA1UEAwwPcG9ueXRvd24gY2xpZW50MCowBQYDK2VwAyEA
|
||||
uCBt6D15TvwyMNP5/nRo9v38yGIeSGQCM1tjyzXFGHWjgZswgZgwDAYDVR0TAQH/
|
||||
BAIwADALBgNVHQ8EBAMCBsAwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwIwHQYDVR0O
|
||||
BBYEFHdQ+goYXFvGem83oCHVJ87Ak0fyMEQGA1UdIwQ9MDuAFBcSMFONOrRtwrD1
|
||||
pB1qn7lE+PGroSCkHjAcMRowGAYDVQQDDBFwb255dG93biBFZERTQSBDQYIBezAF
|
||||
BgMrZXADQQA4GfyvVIs/xQQTv3igqhzabhraQKEd4z2HHudJgdHHV7M7yfuNqN3x
|
||||
NCr3hfDfUQuEOgm02d9Q4TZfik+czUML
|
||||
-----END CERTIFICATE-----
|
||||
@@ -1,19 +0,0 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIBVzCCAQmgAwIBAgIBezAFBgMrZXAwHDEaMBgGA1UEAwwRcG9ueXRvd24gRWRE
|
||||
U0EgQ0EwHhcNMTkwODE2MTMyODUxWhcNMjkwODEzMTMyODUxWjAuMSwwKgYDVQQD
|
||||
DCNwb255dG93biBFZERTQSBsZXZlbCAyIGludGVybWVkaWF0ZTAqMAUGAytlcAMh
|
||||
AD4h3t0UCoMDGgIq4UW4P5zDngsY4vy1pE3wzLPFI4Vdo14wXDAdBgNVHQ4EFgQU
|
||||
FxIwU406tG3CsPWkHWqfuUT48aswIAYDVR0lAQH/BBYwFAYIKwYBBQUHAwEGCCsG
|
||||
AQUFBwMCMAwGA1UdEwQFMAMBAf8wCwYDVR0PBAQDAgH+MAUGAytlcANBAAZFvMek
|
||||
Z71I8CXsBmx/0E6Weoaan9mJHgKqgQdK4w4h4dRg6DjNG957IbrLFO3vZduBMnna
|
||||
qHP3xTFF+11Eyg8=
|
||||
-----END CERTIFICATE-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIBTDCB/6ADAgECAhRXcvbYynz4+usVvPtJp++sBUih3TAFBgMrZXAwHDEaMBgG
|
||||
A1UEAwwRcG9ueXRvd24gRWREU0EgQ0EwHhcNMTkwODE2MTMyODUwWhcNMjkwODEz
|
||||
MTMyODUwWjAcMRowGAYDVQQDDBFwb255dG93biBFZERTQSBDQTAqMAUGAytlcAMh
|
||||
AIE4tLweIfcBGfhPqyXFp5pjVxjaiKk+9fTbRy46jAFKo1MwUTAdBgNVHQ4EFgQU
|
||||
z5b9HjkOxffbtCZhWGg+bnxuD6wwHwYDVR0jBBgwFoAUz5b9HjkOxffbtCZhWGg+
|
||||
bnxuD6wwDwYDVR0TAQH/BAUwAwEB/zAFBgMrZXADQQBNlt7z4bZ7KhzecxZEe3i5
|
||||
lH9MRqbpP9Rg4HyzAJfTzFGT183HoJiISdPLbxwMn0KaqSGlVe+9GgNKswoaRAwH
|
||||
-----END CERTIFICATE-----
|
||||
@@ -1,30 +0,0 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIBlDCCAUagAwIBAgICAxUwBQYDK2VwMC4xLDAqBgNVBAMMI3Bvbnl0b3duIEVk
|
||||
RFNBIGxldmVsIDIgaW50ZXJtZWRpYXRlMB4XDTE5MDgxNjEzMjg1MVoXDTI1MDIw
|
||||
NTEzMjg1MVowGjEYMBYGA1UEAwwPcG9ueXRvd24gY2xpZW50MCowBQYDK2VwAyEA
|
||||
uCBt6D15TvwyMNP5/nRo9v38yGIeSGQCM1tjyzXFGHWjgZswgZgwDAYDVR0TAQH/
|
||||
BAIwADALBgNVHQ8EBAMCBsAwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwIwHQYDVR0O
|
||||
BBYEFHdQ+goYXFvGem83oCHVJ87Ak0fyMEQGA1UdIwQ9MDuAFBcSMFONOrRtwrD1
|
||||
pB1qn7lE+PGroSCkHjAcMRowGAYDVQQDDBFwb255dG93biBFZERTQSBDQYIBezAF
|
||||
BgMrZXADQQA4GfyvVIs/xQQTv3igqhzabhraQKEd4z2HHudJgdHHV7M7yfuNqN3x
|
||||
NCr3hfDfUQuEOgm02d9Q4TZfik+czUML
|
||||
-----END CERTIFICATE-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIBVzCCAQmgAwIBAgIBezAFBgMrZXAwHDEaMBgGA1UEAwwRcG9ueXRvd24gRWRE
|
||||
U0EgQ0EwHhcNMTkwODE2MTMyODUxWhcNMjkwODEzMTMyODUxWjAuMSwwKgYDVQQD
|
||||
DCNwb255dG93biBFZERTQSBsZXZlbCAyIGludGVybWVkaWF0ZTAqMAUGAytlcAMh
|
||||
AD4h3t0UCoMDGgIq4UW4P5zDngsY4vy1pE3wzLPFI4Vdo14wXDAdBgNVHQ4EFgQU
|
||||
FxIwU406tG3CsPWkHWqfuUT48aswIAYDVR0lAQH/BBYwFAYIKwYBBQUHAwEGCCsG
|
||||
AQUFBwMCMAwGA1UdEwQFMAMBAf8wCwYDVR0PBAQDAgH+MAUGAytlcANBAAZFvMek
|
||||
Z71I8CXsBmx/0E6Weoaan9mJHgKqgQdK4w4h4dRg6DjNG957IbrLFO3vZduBMnna
|
||||
qHP3xTFF+11Eyg8=
|
||||
-----END CERTIFICATE-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIBTDCB/6ADAgECAhRXcvbYynz4+usVvPtJp++sBUih3TAFBgMrZXAwHDEaMBgG
|
||||
A1UEAwwRcG9ueXRvd24gRWREU0EgQ0EwHhcNMTkwODE2MTMyODUwWhcNMjkwODEz
|
||||
MTMyODUwWjAcMRowGAYDVQQDDBFwb255dG93biBFZERTQSBDQTAqMAUGAytlcAMh
|
||||
AIE4tLweIfcBGfhPqyXFp5pjVxjaiKk+9fTbRy46jAFKo1MwUTAdBgNVHQ4EFgQU
|
||||
z5b9HjkOxffbtCZhWGg+bnxuD6wwHwYDVR0jBBgwFoAUz5b9HjkOxffbtCZhWGg+
|
||||
bnxuD6wwDwYDVR0TAQH/BAUwAwEB/zAFBgMrZXADQQBNlt7z4bZ7KhzecxZEe3i5
|
||||
lH9MRqbpP9Rg4HyzAJfTzFGT183HoJiISdPLbxwMn0KaqSGlVe+9GgNKswoaRAwH
|
||||
-----END CERTIFICATE-----
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user