Compare commits

...

297 Commits

Author SHA1 Message Date
sinu.eth
1801c30599 fix: attestation example (#1030)
* fix(example): close prover (#1025)

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

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

* Update mpz version.

---------

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

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

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

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

* add feedback

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

* Apply fmt.

* Apply clippy fix.

* Rebase.

* Improved readme + more default loggging in prove example

* Removed wrong AI generated "learn more" links

* re-export ContentType in tlsn-core

* remove unnecessary checks from example

---------

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

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

* fix time import

* clippy

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

* bugfix for `TranscriptRefs::len`

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

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

* clean up imports

* remove remaining notarize methods

* clean up imports

* remove wasm attestation bindings

---------

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

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

* commit lock

* fix integration test

* clippy

* fix docs

* fix import

* fix wasm types

* fix doctest

* verifier config default rootstore

* fix integration test

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

* clean up dead code

* bump lock file

* rustfmt

* fix examples

* fix docs script

* clippy

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

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

* bump mpz

* increase muxer stream count

* update Cargo.lock

---------

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

* add timeout to executor shutdown

* shutdown timeout error msg

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

* delete tests.rs build artifact

* fix binary path

* seconds -> milliseconds

* update lock

* add empty tests module

* rustfmt

* ToString -> Display

* output tests module into build artifacts

* clippy

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

* remove stray dep

* move mac key into `SessionKeys`

* fix key translation

* remove dangling dep

* move ghash mod to tlsn-common

* fix clippy lints

* treat all recv recs as unauthenticated

* detach zkvm first, then prove

* decrypt with aes_gcm, decode mac key only in zkvm

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

* handle error

* fix dangling and clippy

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

* Fix test.

* Remove defualt.

---------

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

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

* Fix formatting and lints

* Address review comments

* feat(server): remove JwtClaimType config property

* Fix missing README comments

* Address review comments

* Address review comments

---------

Co-authored-by: yuroitaki <25913766+yuroitaki@users.noreply.github.com>
2025-05-28 12:51:18 +08:00
sinu.eth
31def9ea81 chore: bump prerelease version (#895) 2025-05-27 11:43:42 -07:00
sinu.eth
878fe7e87d chore: release v0.1.0-alpha.11 (#894) 2025-05-27 09:27:26 -07:00
Hendrik Eeckhaut
3348ac34b6 Release automation (#890)
* ci: create release draft for tagged builds

* Apply suggestions from code review

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: sinu.eth <65924192+sinui0@users.noreply.github.com>
2025-05-27 08:43:57 -07:00
Hendrik Eeckhaut
82767ca2d5 Automatic workflow to update main after a release (#891) 2025-05-27 09:06:38 +02:00
sinu.eth
c9aaf2e0fa refactor(mpc-tls): default to full-mpc PRF (#892) 2025-05-27 08:57:34 +02:00
sinu.eth
241ed3b5a3 chore: bump mpz to alpha.3 (#893) 2025-05-27 08:34:35 +02:00
Hendrik Eeckhaut
56f088db7d ci: build ci with explicit, fixed rust version (1.87.0) (#879) 2025-05-24 21:25:36 +02:00
Hendrik Eeckhaut
f5250479bd docs: correct notary-server command in example readme (#883) 2025-05-23 11:06:14 +02:00
yuroitaki
0e2eabb833 misc(notary): update doc, docker, tee, ci (#874)
* Update docs, docker, tee, ci.

* Restore deleted dockerfile.

* Add concurrency in readme.

* Apply suggestions.

* Correct file path.

---------

Co-authored-by: yuroitaki <>
2025-05-23 11:55:36 +08:00
sinu.eth
ad530ca500 feat: SHA256 transcript commitments (#881)
* feat: SHA256 transcript commitments

* clippy
2025-05-22 09:10:21 -07:00
sinu.eth
8b1cac6fe0 refactor(core): decouple attestation from core api (#875)
* refactor(core): decouple attestation from core api

* remove dead test

* fix encoding tree test

* clippy

* fix comment
2025-05-22 09:00:43 -07:00
Hendrik Eeckhaut
555f65e6b2 fix: expose network setting type in WASM (#880) 2025-05-22 09:35:57 +02:00
dan
046485188c chore: add Cargo.lock to .gitignore (#870) 2025-05-21 09:56:08 +00:00
th4s
db53814ee7 fix(prf): set correct default logic (#873) 2025-05-20 15:22:34 +02:00
yuroitaki
d924bd6deb misc(notary): add common crate for server and client (#871)
* Add notary-common crate.

* Add cargo lock changes.

* Add copy.

---------

Co-authored-by: yuroitaki <>
2025-05-20 12:24:27 +08:00
yuroitaki
b3558bef9c feat(notary): add support for custom extension (#872)
* Add dos extension validator.

* Revert to allow any extensions.

---------

Co-authored-by: yuroitaki <>
2025-05-20 11:19:05 +08:00
yuroitaki
33c4b9d16f chore(notary): ignore clippy warning on large enum (#869)
* Fix clippy.

* Fix clippy.

---------

Co-authored-by: yuroitaki <>
2025-05-16 08:45:29 -07:00
yuroitaki
edc2a1783d refactor(notary): default to ephemeral key, remove config file & fixtures (#818)
* Add default values, refactor.

* Prepend file paths.

* Remove config and refactor.

* Fix fmt, add missing export.

* Simplify error.

* Use serde to print.

* Update crates/notary/server/src/config.rs

Co-authored-by: dan <themighty1@users.noreply.github.com>

* fixture removal + generate signing key (#819)

* Default to ephemeral key gen, remove fixutres.

* Fix wording.

* Add configuring sig alg, comment fixes.

* Fix sig alg id parsing.

* Refactor pub key to pem.

* Return error, add test.

* Update crates/notary/server/src/signing.rs

Co-authored-by: Hendrik Eeckhaut <hendrik@eeckhaut.org>

---------

Co-authored-by: yuroitaki <>
Co-authored-by: Hendrik Eeckhaut <hendrik@eeckhaut.org>

---------

Co-authored-by: yuroitaki <>
Co-authored-by: dan <themighty1@users.noreply.github.com>
Co-authored-by: Hendrik Eeckhaut <hendrik@eeckhaut.org>
2025-05-16 19:02:20 +08:00
sinu.eth
c2a6546deb refactor(core): encode by ref and rip out dead hash functionality (#866) 2025-05-15 09:10:05 -07:00
th4s
2dfa386415 chore: bump mpz and adapt update method call in hmac-sha256 (#867)
* fix(hmac-sha256): use new `update` method from mpz-hash

* use `into` conversion
2025-05-15 15:58:32 +02:00
sinu.eth
5a188e75c7 refactor(cipher): remove contiguous memory assumption (#864)
* refactor(cipher): remove contiguous memory assumption

* fix mpc-tls and upstream crates
2025-05-13 09:41:55 -07:00
sinu.eth
a8bf1026ca feat(deap): address space mapping (#809) 2025-05-13 09:38:39 -07:00
sinu.eth
f900fc51cd chore: bump mpz to abd02e6 (#825) 2025-05-13 09:35:51 -07:00
th4s
6ccf102ec8 feat(prf): reduced MPC variant (#735)
* feat(prf): reduced MPC variant

* move sending `client_random` from `alloc` to `preprocess`

* rename `Config` -> `Mode` and rename variants

* add feedback for handling of prf config

* fix formatting to nightly

* simplify `MpcPrf`

* improve external flush handling

* improve control flow

* improved inner control flow for normal prf version

* rename leftover `config` -> `mode`

* remove unnecessary pub(crate)

* rewrite state flow for reduced prf

* improve state transition for reduced prf

* repair prf bench

* WIP: Adapting to new `Sha256` from mpz

* repair failing test

* fixed all tests

* remove output decoding for p

* do not use mod.rs file hierarchy

* remove pub(crate) from function

* improve config handling

* use `Array::try_from`

* simplify hmac to function

* remove `merge_vecs`

* move `mark_public` to allocation

* minor fixes

* simplify state logic for reduced prf even more

* simplify reduced prf even more

* set reduced prf as default

* temporarily fix commit for mpz

* add part of feedback

* simplify state transition

* adapt comment

* improve state transition in flush

* simplify flush

* fix wasm prover config

---------

Co-authored-by: sinu <65924192+sinui0@users.noreply.github.com>
2025-05-13 09:26:43 -07:00
sinu.eth
2c500b13bd chore: bump mpz to alpha.3 (#806)
* temporary remove hmac crates

* wip: adapting cipher crate...

* wip: adapting key-exchange crate...

* wip: adapt most of mpc-tls...

* adapt prover and verifier crates

* remove unnecessary rand compat import for deap

* adapt mpc-tls

* fix: endianness of key-exchange circuit

* fix: output endianness of ke circuit

* fix variable name

---------

Co-authored-by: th4s <th4s@metavoid.xyz>
2025-05-13 09:03:09 -07:00
Hendrik Eeckhaut
2da0c242cb build: Check in Cargo lock files (#742) 2025-05-12 10:22:13 +02:00
th4s
798c22409a chore(config): move defer_decryption_from_start to ProtocolConfig 2025-05-10 11:41:01 +02:00
dan
3b5ac20d5b fix(benches): browser bench fixes (#821)
* fix(benches): make browser benches work again

* Update crates/benches/binary/README.md

Co-authored-by: Hendrik Eeckhaut <hendrik@eeckhaut.org>

* Update crates/benches/browser/wasm/Cargo.toml

Co-authored-by: Hendrik Eeckhaut <hendrik@eeckhaut.org>

* add --release flag

---------

Co-authored-by: Hendrik Eeckhaut <hendrik@eeckhaut.org>
2025-05-08 06:13:15 +00:00
Hendrik Eeckhaut
a063f8cc14 ci: build gramine-sgx for dev and tagged builds only (#805) 2025-05-05 17:16:50 +02:00
dan
6f6b24e76c test: fix failing tests (#823) 2025-05-05 17:01:42 +02:00
dan
a28718923b chore(examples): inline custom crypto provider for clarity (#815)
Co-authored-by: sinu.eth <65924192+sinui0@users.noreply.github.com>
2025-04-30 06:41:07 +00:00
Hendrik Eeckhaut
19447aabe5 Tee dev cleanup (#759)
* build: added scripts for local tee/sgx development
* Improved documentation: move all explanation to one README file
2025-04-28 14:46:32 +02:00
Jakub Konka
8afb7a4c11 fix(notary): use custom HTTP header for authorization: X-API-Key (#804)
Co-authored-by: yuroitaki <25913766+yuroitaki@users.noreply.github.com>
2025-04-28 14:24:32 +08:00
dan
43c6877ec0 chore: support workspace lints in all crates (#797) 2025-04-25 13:58:26 +02:00
dan
39e14949a0 chore: add rustls licence and attribution (#795)
* chore: add rustls licence and attribution

* add missing commit
2025-04-25 07:10:49 +00:00
dan
31f62982b5 feat(wasm): allow max records config (#810) 2025-04-25 06:34:49 +00:00
yuroitaki
6623734ca0 doc(example): add comments on verifying custom extension (#788)
* Add comments.

* Fix comment.

---------

Co-authored-by: yuroitaki <>
2025-04-25 11:18:47 +08:00
Hendrik Eeckhaut
41e215f912 chore: set version number to 0.1.0-alpha.11-pre (#798) 2025-04-23 13:19:05 +02:00
dan
9e0f79125b misc(notary): improve error msg when tls is expected (#776)
* misc(notary): improve error msg when tls is expected

* change wording

* fix nested if

* process hyper error

* refactor into a fn

* fix error msg

Co-authored-by: yuroitaki <25913766+yuroitaki@users.noreply.github.com>

* do not catch hyper error

---------

Co-authored-by: yuroitaki <25913766+yuroitaki@users.noreply.github.com>
2025-04-22 12:03:23 +00:00
Hendrik Eeckhaut
7bdd3a724b fix: Add missing concurrency param in tee config (#791) 2025-04-22 11:19:35 +02:00
dan
baa486ccfd chore(examples): fix formatting (#793) 2025-04-21 08:46:28 +00:00
sinu.eth
de7a47de5b feat: expose record count config (#786)
* expose record config

* update default record counts

* make fields optional

* override record count in integration test
2025-04-18 14:58:28 +07:00
sinu.eth
3a57134b3a chore: update version to alpha.10 (#785) 2025-04-18 08:54:55 +02:00
sinu.eth
86fed1a90c refactor: remove extension api from request builder (#787) 2025-04-18 13:01:28 +07:00
sinu.eth
82964c273b feat: attestation extensions (#755)
* feat: attestation extensions

* rustfmt

* fix doctest example

* add extensions getter to public api

* add tests

* fix prover so it includes extensions
2025-04-17 23:15:27 +07:00
yuroitaki
81aaa338e6 feat(core): find set cover across different commitment kinds in TranscriptProofBuilder (#765)
* Init.

* Cover range in order of preference of kinds.

* Fix comment.

* Adjust error message.

* Return tuple from set cover and address comments.

* Fix comments.

* Update utils version.

---------

Co-authored-by: yuroitaki <>
Co-authored-by: dan <themighty1@users.noreply.github.com>
2025-04-17 15:16:06 +08:00
dan
f331a7a3c5 chore: improve naming and comments (#780) 2025-04-17 06:43:30 +00:00
dan
adb407d03b misc(core): simplify encoding logic (#781)
* perf(core): simplify encoding logic

* make constant-time
2025-04-15 14:50:53 +00:00
dan
3e54119867 feat(notary): add concurrency limit (#770)
* feat(notary): add concurrency limit

* switch to 503 status code

* remove test-api feature

* improve naming and comments

* set default concurrency to 32
2025-04-15 12:31:16 +00:00
Hendrik Eeckhaut
71aa90de88 Add tlsn-wasm to API docs (#768) 2025-04-10 13:35:20 +02:00
sinu.eth
93535ca955 feat(mpc-tls): improve error message for incorrect transcript config (#754)
* feat(mpc-tls): improve error message for incorrect transcript config

* rustfmt

---------

Co-authored-by: dan <themighty1@users.noreply.github.com>
2025-04-07 10:44:02 +00:00
sinu.eth
a34dd57926 refactor: remove utils-aio dep (#760) 2025-04-03 04:58:14 +07:00
yuroitaki
92d7b59ee8 doc(example): add minor comments (#761)
* Add comments.

* Remove commented leftover.

* Remove example tweak.

* fmt.

---------

Co-authored-by: yuroitaki <>
2025-04-02 14:29:26 +08:00
Leonid Logvinov
c8e9cb370e feat(notary): Log notarization elapsed time (#746)
* Log notarisation elapsed time

* Fix formatting

* Include time units in field name
2025-03-27 08:08:29 -07:00
dan
4dc5570a31 MIsc comments (#747)
* fix comments

* fix comment

Co-authored-by: sinu.eth <65924192+sinui0@users.noreply.github.com>

* describe all args

* change decrypted plaintext -> plaintext

* remove redundant comments

---------

Co-authored-by: sinu.eth <65924192+sinui0@users.noreply.github.com>
2025-03-27 13:42:41 +00:00
Hendrik Eeckhaut
198e24c5e4 ci: manual workflow for tlsn-wasm release (#757) 2025-03-27 14:33:46 +01:00
dan
f16d7238e5 refactor(core): DoS mitigation and additional validation (#648)
* add encoding proof validation

* check that merkle tree indices are not out of bounds

* limit opened plaintext hash data

* add test

* formatting

* bump commitment tree size cap to 30 bits

* remove unnecessary test

* fix stray lines
2025-03-27 12:54:05 +00:00
dan
9253adaaa4 fix: avoid mutating self in TagShare::add (#748) 2025-03-27 12:46:27 +00:00
Hendrik Eeckhaut
8c889ac498 ci: SGX build: drop TEE GH environment, use regular secret (#751) 2025-03-27 11:40:04 +01:00
Hendrik Eeckhaut
f0e2200d22 ci: disable codecov annotation and comments in Github (#752) 2025-03-26 14:49:14 +01:00
Hendrik Eeckhaut
224e41a186 chore: Bump version to 0.1.0-alpha.10-pre 2025-03-25 14:28:26 +01:00
Hendrik Eeckhaut
328c2af162 fix: do not enable tee_quote feature by default (#745) 2025-03-25 11:24:43 +01:00
sinu.eth
cdb80e1458 fix: compute recv record count from max_recv (#743)
* fix: compute recv record count from max_recv

* pad after check

* fix: add `max_recv` to mpc-tls integration test

---------

Co-authored-by: th4s <th4s@metavoid.xyz>
2025-03-25 11:04:07 +01:00
Hendrik Eeckhaut
eeccbef909 ci: script to patch imports in the tlsn-wasm build result (#727) 2025-03-20 21:47:47 +01:00
sinu
190b7b0bf6 ci: update tlsn-wasm release workflow 2025-03-20 11:10:28 -07:00
sinu
c70caa5ed9 chore: release v0.1.0-alpha.9 2025-03-20 11:06:57 -07:00
sinu.eth
20137b8c6c fix(notary): install libclang in docker image (#740) 2025-03-20 10:53:32 -07:00
yuroitaki
4cdd1395e8 feat(core): find set cover solution for user in TranscriptProofBuilder (#664)
* Add reveal groups of ranges.

* Reveal committed ranges given a rangeset.

* Fix test and wordings.

* Fix wordings.

* Add reveal feature for hash commitments.

* Formatting.

* Fix wording.

* Add subset check.

* Add subset check.

* Add clippy allow.

* Fix missing direction in transcript index lookup.

* Fix prune subset.

* Refactor proof_idxs.

* Throw error if only one subset detected.

* Fix superset reveal.

* Fmt.

* Refactored Ord for Idx.

* Update crates/core/src/transcript/proof.rs

Co-authored-by: dan <themighty1@users.noreply.github.com>

* Adjust example and comments.

* Adjust comments.

* Remove comment.

* Change comment style.

* Change comment.

* Add comments.

* Change to lazily check set cover.

* use rangeset and simplify

* restore examples

* fix import

* rustfmt

* clippy

---------

Co-authored-by: yuroitaki <>
Co-authored-by: dan <themighty1@users.noreply.github.com>
Co-authored-by: sinu <65924192+sinui0@users.noreply.github.com>
2025-03-20 07:55:13 -07:00
Leonid Logvinov
c1b3d64d5d feat(notary): Make logging format configurable (#719)
* Make logging format configurable

* Document logging format

* Fix formatting

* Init server config with default value
s in notary interation tests
2025-03-19 10:57:00 -07:00
sinu.eth
61ce838f8c refactor: migrate to rand 0.9 (#734)
* refactor: migrate to rand 0.9

* fix: enable wasm_js feature for getrandom

* fix: set getrandom cfg

* fix: clippy

* fix: notary server rand

* fix cargo config
2025-03-19 10:36:24 -07:00
dan
efca281222 feat: Ethereum compatible signer (#731)
* feat: add ethereum-compatible signer

* fix recovery id

* test with a reference signer
2025-03-19 10:17:47 -07:00
sinu.eth
b24041b9f5 fix: record layer handshake control flow (#733) 2025-03-17 11:04:41 -07:00
th4s
9649d6e4cf test(common): Add test for TranscriptRefs::get (#712)
* test(common): add test for transcript refs

* doc: improve doc for test

---------

Co-authored-by: Hendrik Eeckhaut <hendrik@eeckhaut.org>
Co-authored-by: sinu.eth <65924192+sinui0@users.noreply.github.com>
2025-03-17 10:02:19 -07:00
Hendrik Eeckhaut
bc69683ecf ci: build notary docker image for both dev branch and releases (#726) 2025-03-12 18:03:01 +01:00
dan
6c468a91cf test: improve test, fix grammar 2025-03-11 10:44:11 +01:00
Hendrik Eeckhaut
dcff0b9152 ci: update cache plugin 2025-03-11 09:55:12 +01:00
sinu
5f91926154 fix: allow deprecated ring (#720) 2025-03-10 12:42:31 -07:00
Hendrik Eeckhaut
0496cbaeb1 chore: Bump version to 0.1.0-alpha.9-pre 2025-03-10 08:41:18 +01:00
sinu
d8747d49e3 chore: release alpha.8 2025-03-07 14:51:11 -08:00
sinu.eth
6fe328581c chore: bump mpz to alpha.2 (#716) 2025-03-07 14:38:47 -08:00
sinu.eth
6d1140355b build: separate clippy and keep going (#715) 2025-03-07 11:15:00 -08:00
sinu.eth
5246beabf5 chore(wasm): bump web spawn to 0.2 (#714) 2025-03-07 10:57:25 -08:00
Hendrik Eeckhaut
29efc35d14 ci: create notary-server-sgx docker image 2025-03-06 11:25:53 +01:00
Hendrik Eeckhaut
32d25e5c69 fix: fixed version of time dependency (v0.3.38 has wasm issue) (#711) 2025-03-06 01:06:16 +01:00
yuroitaki
ca9d364fc9 docs: Update notary server documentation 2025-03-05 13:15:11 +01:00
sinu.eth
5cbafe17f5 chore: removed unused deps (#706) 2025-03-03 12:15:46 -08:00
sinu.eth
acabb7761b chore: delete dead code (#705) 2025-03-03 11:53:20 -08:00
sinu.eth
c384a393bf chore: bump deps (#704) 2025-03-03 11:40:31 -08:00
Hendrik Eeckhaut
be0be19018 ci: calculate SGX mr_enclave for notary server in gramine docker (#701)
* calculate SGX mr_enclave for notary server in gramine docker
* remove old tee github workflow
* attest build result for dev branch builds and releases
2025-03-03 13:29:47 +01:00
Hendrik Eeckhaut
63bd6abc5d docs: corrected example output in examples README 2025-02-26 18:56:49 +01:00
sinu.eth
cb13169b82 perf: MPC-TLS upgrade (#698)
* fix: add new Cargo.toml

* (alpha.8) - Refactor key-exchange crate (#685)

* refactor(key-exchange): adapt key-exchange to new vm

* fix: fix feature flags

* simplify

* delete old msg module

* clean up error

---------

Co-authored-by: sinu <65924192+sinui0@users.noreply.github.com>

* (alpha.8) - Refactor prf crate (#684)

* refactor(prf): adapt prf to new mpz vm

Co-authored-by: sinu <65924192+sinui0@users.noreply.github.com>

* refactor: remove preprocessing bench

* fix: fix feature flags

* clean up attributes

---------

Co-authored-by: sinu <65924192+sinui0@users.noreply.github.com>

* refactor: key exchange interface (#688)

* refactor: prf interface (#689)

* (alpha.8) - Create cipher crate (#683)

* feat(cipher): add cipher crate, replacing stream/block cipher and aead

* delete old config module

* remove mpz generics

---------

Co-authored-by: sinu <65924192+sinui0@users.noreply.github.com>

* refactor(core): decouple encoder from mpz (#692)

* WIP: Adding new encoding logic...

* feat: add new encoder

* add feedback

* rename conversions

* feat: DEAP VM (#690)

* feat: DEAP VM

* use rangeset, add desync guard

* move MPC execution up in finalization

* refactor: MPC-TLS (#693)

* refactor: MPC-TLS

Co-authored-by: th4s <th4s@metavoid.xyz>

* output key references

* bump deps

---------

Co-authored-by: th4s <th4s@metavoid.xyz>

* refactor: prover + verifier (#696)

* refactor: wasm crates (#697)

* chore: appease clippy (#699)

* chore: rustfmt

* chore: appease clippy more

* chore: more rustfmt!

* chore: clippy is stubborn

* chore: rustfmt sorting change is annoying!

* fix: remove wasm bundling hack

* fix: aes ctr test

* chore: clippy

* fix: flush client when sending close notify

* fix: failing tests

---------

Co-authored-by: th4s <th4s@metavoid.xyz>
2025-02-25 13:51:28 -08:00
mac
25d65734c0 chore: improve notary server html info (regular and TEE) 2025-02-21 14:03:47 +01:00
mac
119ae4b2a8 docs: openapi conf update for TEE quote (#651) 2025-02-21 09:04:21 +01:00
Hendrik Eeckhaut
f59153b0a0 ci: fix TEE deployments (#686)
* do not run tee-deployments builds for PR builds
* Remove AWS deployment scripts
* added missing timeout parameter to TEE config
2025-02-20 11:58:13 +01:00
Hendrik Eeckhaut
bffe9ebb0b doc: disclaimer for minor changes PRs in contribution guidelines (#691) 2025-02-04 10:02:38 +01:00
Hendrik Eeckhaut
65299d7def chore: update axum to v0.8 (#681)
chore: update `axum` to v0.8

Co-authored-by: yuroitaki <25913766+yuroitaki@users.noreply.github.com>
2025-01-08 09:24:01 +01:00
dan
c03418a642 feat: compress partial transcript (#653)
* feat: compress partial transcript

* add missing dep
2024-12-26 10:41:22 +00:00
Hendrik Eeckhaut
7bec5a84ee Added a script to set the TLSN version number of the relevant crates (#650)
build: add script to set the TLSN version number
2024-12-12 13:46:08 +01:00
Hendrik Eeckhaut
85e0f5b467 Clean up tlsn-wasm build (#553)
ci: automate tlsn-wasm build + store artifact for tags + manual workflow for npm release

+ Improved package: add description, repo information etc
2024-12-06 11:03:23 +01:00
Ryan MacArthur
cacca108ed ci: SGX 2024-12-05 08:59:19 +01:00
yuroitaki
c9592f44a1 fix: extend test certs to 100-year validity. (#667)
Co-authored-by: yuroitaki <>
2024-12-03 14:36:28 +08:00
yuroitaki
e6be5e1cc9 Fix clippy. (#666) 2024-11-29 17:33:22 +01:00
Hendrik Eeckhaut
d974fb71d5 Revert "build: Update wasm-bindgen and wasm-bindgen-rayon dependency (#662)" (#663)
This reverts commit c0c1c0caa1.
2024-11-27 09:15:09 +01:00
Hendrik Eeckhaut
c0c1c0caa1 build: Update wasm-bindgen and wasm-bindgen-rayon dependency (#662) 2024-11-25 11:46:00 +01:00
yorozunouchu
7d88d1c20b fix(notary): make TLS keys and authorization whitelist configs optional (#589)
* feat: Add optional fields for TLS private key and certificate paths

* Add optional field for whitelist_csv_path

* fix test cases for whitelist_csv_path

* fix issues pointed out by yuroitaki

* Add error handling for missing PEM paths when TLS is enabled

* Fix formatting and linting

* throw error if pem files do not exist

---------

Co-authored-by: funkyenough <14842981+funkyenough@users.noreply.github.com>
2024-11-22 18:44:06 +08:00
yuroitaki
c10c9155a7 chore: add core transcript unit tests (#649)
* Add transcript proof and lib tests.

* Init encoding tree test.

* Add encoding proof tests.

* Generalise fixture tests.

* Add seed arg to attestation fixture fn.

* Adjust cosmetics.

* Format comment.

---------

Co-authored-by: yuroitaki <>
2024-11-04 13:59:31 +08:00
dan
faab999339 Memory profiling (#658) (#660)
* (squashing to simplify rebase)
rebased on dev
reorganized files
fix gh workflow

* modify workflow

* update dockerfile

Co-authored-by: Valentin Mihov <valentin.mihov@gmail.com>
2024-10-31 09:13:23 +00:00
dan
e6bc93c1f1 Memory profiling (#658)
* (squashing to simplify rebase)
rebased on dev
reorganized files
fix gh workflow

* modify workflow

* update dockerfile
Co-authored-by: valo <valo@users.noreply.github.com>
2024-10-29 16:20:00 +00:00
Hendrik Eeckhaut
c6dc262a5e Use a local server (fixture) for the attestation example (#656)
feat: use server fixture for tlsn examples + removed Discord example

The attestation example now has different modes: json, html and authenticated
2024-10-29 14:53:01 +01:00
Ryan MacArthur
db90e28e44 feat: intel-sgx attestation 2024-10-29 14:52:47 +01:00
Hendrik Eeckhaut
30e4e37c0d Return valid json in Server fixture (#652)
fix: Server fixture now returns valid json

* return json instead of string
* removed trailing whitespace
* use a constant for the default port
* give binary a better name
2024-10-25 22:13:35 +02:00
dan
6344410cad use the latest rev of tlsn-utils (#654) 2024-10-24 13:53:26 +00:00
Hendrik Eeckhaut
1d663596c1 Do not include git commit timestamp in notary server + use git2 instead of git command + add dirty suffix (#643)
build: improved commit info on notary/info page

This changes uses git2 Rust library instead of calling out to external git
The timestamp was removed
2024-10-23 10:29:23 +02:00
yuroitaki
2c045e5de7 fix(notary): implement timeout for notarization (#639)
* Add timeout.

* Fmt.

* Fix grammar.

* Move limit to config.

* Remove extra space.

---------

Co-authored-by: yuroitaki <>
2024-10-22 15:08:19 +08:00
dan
38104bca1a style: fix grammar and wording (#647) 2024-10-22 06:31:13 +00:00
Hendrik Eeckhaut
99ba47c25d build: re-added explicit getrandom dependency in the wasm crate (#646) 2024-10-21 11:00:17 +02:00
dan
2042089132 Test/benches fixes (#636)
* add header

* combine into a single workflow

* fix wsrelay commit
2024-10-17 12:24:10 +00:00
Hendrik Eeckhaut
504967d09a Version bump correction (#644)
* build: reset version number in non-publish crates

#642
2024-10-17 12:31:07 +02:00
Hendrik Eeckhaut
6e80d03ac7 chore: Bump version to 0.1.0-alpha.8-pre (#642)
build: Bump version to 0.1.0-alpha.8-pre and set version number for benches and fixtures to 0.0.0
2024-10-16 18:53:07 +02:00
Hendrik Eeckhaut
b3f79a9e2b build: removed unused dependencies (cargo machete) 2024-10-15 19:58:30 +02:00
Hendrik Eeckhaut
99e02fb388 Add convenience script to run most ci checks locally 2024-10-14 20:23:19 +02:00
dan
6b845fd473 test: add browser benches (#570)
* refactor: modularize server-fixture

* Update crates/server-fixture/server/Cargo.toml

add newline

Co-authored-by: sinu.eth <65924192+sinui0@users.noreply.github.com>

* test: add browser benches

* fix deps

* ci: run ci workflow for all pull requests (#571)

* misc fixes

* fix clippy

* don't log a non-critical error to stderr

* use incognito (mitigates random hangs)

* add notes

* distinguish prover kind when plotting

---------

Co-authored-by: sinu.eth <65924192+sinui0@users.noreply.github.com>
Co-authored-by: Hendrik Eeckhaut <hendrik@eeckhaut.org>
Co-authored-by: Ubuntu <ubuntu@ip-10-35-1-164.eu-central-1.compute.internal>
2024-10-14 13:52:52 +00:00
Hendrik Eeckhaut
66db5344ac ci: use env variables to get git commit hash in CI 2024-10-11 16:42:10 +02:00
Kimani
1d4c50f804 feat(notary): support reading config values from CLI and env var (#605)
* feat: supports reading config values from CLI

* chore: adds config lib to cargo.toml, uses server default config values instead, removes validations in settings.rs

* chore: tries to load YAML file

* chore: tries to load YAML file

* fix: loads config.yaml properly, refactors code

* fix: removes .idea folder and moves config lib to notary-server cargo.toml

* feat: uses serde-aux to deserialize env vars port and tls-enabled from string and restores &cli_fields.config_file path and debug log

* fix: parses int and bool using try-parsing instead of serde-aux and removes unnecessary whitespaces

* chore: converts config to snake_case for consistency

* doc: adds configuration documentation, code comments and fixes linting errors

* fix: fixes ci linting formatting

* fix: fixes ci linting formatting

* fix: adjusts formatting for settings.rs and minor adjustments to documentation

* fix: uses cargo nightly to format correctly

---------

Co-authored-by: yuroitaki <25913766+yuroitaki@users.noreply.github.com>
2024-10-11 11:56:37 +08:00
Hendrik Eeckhaut
61ff3a8255 ci: Try codecov.io 2024-10-08 09:59:09 +02:00
Hendrik Eeckhaut
2ac9de1edd ci: generate coverage report 2024-10-08 09:59:09 +02:00
Artem
a7a8a83410 fix(notary): fix client issue of not being able to specify the notary url path (#614)
* (fix: client) Fixed client issue of being able to implement the path for the url

* (feat: client) Improved the code to adjust for feedback received as well as extend the path calculation to avoid adding a `/` when already starts with a `/`

* (fix: client) Fixed client issue of being able to implement the path for the url

* (feat: client) Improved the code to adjust for feedback received as well as extend the path calculation to avoid adding a `/` when already starts with a `/`

* Update crates/notary/client/src/client.rs

Co-authored-by: yuroitaki <25913766+yuroitaki@users.noreply.github.com>

* (fix: client) Renamed `path` to `path_prefix`

* (fix: client) Remove condition on the URL

* (chore: client) Fix formating

---------

Co-authored-by: yuroitaki <25913766+yuroitaki@users.noreply.github.com>
2024-10-04 14:24:48 +08:00
yuroitaki
4d5102b6e1 chore: core-rewrite unit tests (#608)
* Add tests for signing, index.

* Add error scenarios.

* Add cert tests, modify previous tests.

* Improve cert tests.

* Add tests for request.

* Fix clippy

* Fix clippy.

* Change requests test style.

* Add attestation unit tests.

* Formatting.

* Clippy.

* make data fixtures optional

---------

Co-authored-by: yuroitaki <>
Co-authored-by: sinu <65924192+sinui0@users.noreply.github.com>
2024-10-03 07:47:15 -07:00
Hendrik Eeckhaut
0596a9a245 docs: correct foldername in examples readme (#624) 2024-10-03 07:18:58 -07:00
sinu.eth
43d2c04f6f chore: prepare alpha.7 release (#620) 2024-10-03 06:37:59 -07:00
yuroitaki
ca328fadca chore(notary): change fixture pub key to compressed form (#623)
Co-authored-by: yuroitaki <>
2024-10-03 19:05:49 +08:00
sinu
b724d6a1d2 feat(wasm): expose presentation verifying key 2024-10-03 08:44:28 +02:00
Hendrik Eeckhaut
dfc162929d fix(examples): fix examples for alpha.7 release (#603)
* doc: Fix examples for alpha7 release

+ Use secp256k1 key for notary server fixture
+ fix tower issue
+ Fixed doctes issues (Avoid doc test failures when ignored tests are run)
+ Run wasm tests in incognitto mode to avoid chromiumoxide ws errors

* Added comment

* minor improvements

* formatting

* polish attestation example

* use shorthand fs write

* clean

* simplify discord example

---------

Co-authored-by: sinu <65924192+sinui0@users.noreply.github.com>
2024-10-02 20:39:47 -07:00
sinu
6e20930283 fix(wasm): remove presentation verify from test 2024-10-02 08:29:48 +02:00
sinu.eth
7de49c8ed6 docs(core): additional documentation and examples (#613) 2024-10-01 09:52:24 -07:00
sinu.eth
17476bc2cf docs: rustfmt wrap_comments (#611) 2024-10-01 07:18:51 -07:00
sinu.eth
b76b8314ad feat(wasm): clone getters (#600)
* feat(wasm): clone getters

* fix(wasm): get rid of move semantics and implement Clone

* disable test feature
2024-10-01 07:17:48 -07:00
sinu
6ed3337739 fix(core): remove serde flatten 2024-10-01 13:39:38 +02:00
Hendrik Eeckhaut
3de203e8ac fix: clippy fix too_long_first_doc_paragraph in enum_builders 2024-10-01 11:10:43 +02:00
Valentin Mihov
79c00fcedb chore: Remove clippy::blocks_in_conditions (#592)
The bug in clippy is fixed in rust 1.81, so these are not needed
any more

Co-authored-by: yuroitaki <25913766+yuroitaki@users.noreply.github.com>
2024-09-25 14:03:34 -07:00
sinu.eth
e00828bd03 fix(verifier): correct error display (#599) 2024-09-20 09:07:05 -07:00
tsukino
8f400bf1e2 fix(wasm): Attestation.deserialize should return Attestation (#597) 2024-09-20 08:57:38 -07:00
sinu.eth
53ff873b3a refactor(core): alpha.7 rewrite (#574)
* refactor(core): alpha.7 rewrite

* allow empty idx

* fix empty assumption

* further encapsulate rangeset

* added presentation, finishing touches

* remove unwrap

* update index naming

* add secp256r1 support

* add attestation to presentation output, and serde derives

* handle k256 in KeyAlgId Display

* unnecessary newline

* refactor(core): integrate rewrite changes (#584)

* refactor(core): alpha.7 rewrite

* allow empty idx

* fix empty assumption

* further encapsulate rangeset

* added presentation, finishing touches

* remove unwrap

* refactor(core): integrate rewrite changes

* remove obsolete tests

* add secp256r1 support

* update index naming

* add secp256r1 support

* add attestation to presentation output, and serde derives

* handle k256 in KeyAlgId Display

* unnecessary newline

* fix variable name

* restore changes from dev to tlsn-prover

* use CryptoProvider in config

* clippy

* more clippy
2024-09-19 07:57:54 -07:00
Hendrik Eeckhaut
a4a0de02f9 ci: run test with debug logging, not the build 2024-09-13 09:46:22 +02:00
Hendrik Eeckhaut
80a9a61e9e chore: add favicon to wasm-test-runner to avoid 404 in tests
fixes #586
2024-09-13 09:46:22 +02:00
sinu.eth
67dc7c865d chore(examples): remove examples, fix bug (#585)
* chore(examples): remove examples, fix bug

* Restored Discord example

---------

Co-authored-by: Hendrik Eeckhaut <hendrik@eeckhaut.org>
2024-09-12 11:42:44 -07:00
th4s
9bbb2fb66c feat: make defer-decryption default
* feat: add `defer` option to inner configs

- `TranscriptConfig`
- `MpcTlsLeaderConfig`
- `MpcTlsFollowerConfig`

* adapt common `ProtocolConfig` to `deferred` feature

* Adapt prover and verifier configs.

* Adapt benches.

* Adapt examples.

* Adapt `crates/tests-integration`.

* Adapt notary integration test and wasm crates.

* Fix test.

* add clarifying comments

* Add feedback.

* Improve default handling for `max_deferred_size`.

* Use default handling instead of validation.

* Add feedback.

* fix: bugfix for `notarize.rs`

* Remove defaults for `ProtocolConfigValidator`

* Set `ProtocolConfigValidator` where needed.

* Only preprocess online part for transcript.

---------

Co-authored-by: Ubuntu <ubuntu@ip-10-35-1-161.eu-central-1.compute.internal>
2024-09-11 21:49:58 +02:00
th4s
32df1380a7 fix: aead unit tests no longer deadlock
* fix: add back unit tests and configure rayon threadpool

* Use env var to set rayon threads.

* Remove `rayon` dev dependency.
2024-09-02 09:06:38 +02:00
dan
d179150c39 refactor: modularize server-fixture (#563)
* refactor: modularize server-fixture

* small fixes

* Update crates/server-fixture/server/Cargo.toml

add newline

Co-authored-by: sinu.eth <65924192+sinui0@users.noreply.github.com>

---------

Co-authored-by: Ubuntu <ubuntu@ip-10-35-1-161.eu-central-1.compute.internal>
Co-authored-by: sinu.eth <65924192+sinui0@users.noreply.github.com>
2024-08-21 08:50:08 +00:00
Hendrik Eeckhaut
98a520ddd7 doc: add section about linting to contribution guide 2024-08-21 10:11:29 +02:00
Hendrik Eeckhaut
8fa593c111 doc: added note about "format on save" to the contribution guide 2024-08-21 10:11:29 +02:00
dan
0b1eef12f3 docs: fix typos (#569)
* docs: fix typos

* Correct notary readme link.

---------

Co-authored-by: yuroitaki <>
2024-08-19 19:25:36 +08:00
yuroitaki
6eaf4a3d2d feat: add protocol configuration negotiation (#513)
* Add configuration check.

* Fix naming and comments.

* Fix clippy.

* Fix clippy using latest rust.

* Adapt new method to send message.

* Add config validator.

* Split max transcript size.

* Remove unused dependencies and redundant declarations.

* Move protocol_config out of verifier config.

* Implement default for configs.

* Apply fmt.

* Correct dependency format.

* Add ignored flag to integration test.

* Change from String to Version, more debugging.

* Use getter instead of pub.

* Move ot estimate methods to ProtocolConfig.

---------

Co-authored-by: yuroitaki <>
Co-authored-by: sinu.eth <65924192+sinui0@users.noreply.github.com>
2024-08-15 14:40:55 -07:00
th4s
cc01b24759 fix: Check if already committed in commit (#568)
Co-authored-by: sinu.eth <65924192+sinui0@users.noreply.github.com>
2024-08-15 14:29:59 -07:00
th4s
0a3a1db520 chore: Temporarily disable deadlocked tests in aead (#547)
* fix: Comment failing tests in aead

* fix: please clippy

* fix: comment `kind` function in error module

---------

Co-authored-by: sinu.eth <65924192+sinui0@users.noreply.github.com>
2024-08-15 14:21:00 -07:00
th4s
b3316dede7 fix: Repair wasm build. (#567)
Set specific version for `wasm-bindgen`.
2024-08-15 18:50:22 +02:00
th4s
ab24a6d3aa Add verify for wasm TlsProof (#560)
* Add `verify` for wasm `TlsProof`.

* refactor notary key

* return unix time

* remove unnecessary serde feature

* comment

* refactor: use public key pem instead of bytes (#561)

* refactor: use public key pem instead of bytes

* refactor: use NotaryKey as type

* fix: use String

* style: formatting

#557

---------

Co-authored-by: sinu <65924192+sinui0@users.noreply.github.com>
Co-authored-by: tsukino <87639218+0xtsukino@users.noreply.github.com>
Co-authored-by: Hendrik Eeckhaut <hendrik@eeckhaut.org>
2024-08-14 12:14:02 -07:00
dan
17e31687bd docs: describe execution modes (#464) 2024-08-06 10:02:16 +00:00
yuroitaki
b9ae8f9271 chore: extend tls certs perpetually (#555) 2024-08-06 14:55:59 +08:00
yuroitaki
c8524d934b fix: correct paths following repo reorg (#554)
* Fix paths following repo reorg.

* Apply fmt.

---------

Co-authored-by: yuroitaki <>
2024-08-02 19:45:08 +08:00
Hendrik Eeckhaut
7f46596068 docs: fix and improve interactive example (#552)
#551
2024-08-02 12:28:51 +02:00
Hendrik Eeckhaut
6031254963 chore: cleanup after workspace consolidation
* clean up github actions #537 #536
* Improved README.md (clang + openssl-dev) #536 
+ removed stale build script #537 
* chore: add build script for tlsn-wasm #536

#549
2024-08-01 14:40:59 +02:00
th4s
2d44cc4b60 docs: Fix links in README.md (#544) 2024-07-29 16:33:39 +09:00
th4s
bdebd7a9b2 Remove unused AEAD error type. (#540) 2024-07-29 16:32:04 +09:00
sinu.eth
3201c38ad7 feat(wasm): no-bundler flag (#546)
* feat(wasm): no-bundler flag

* newline
2024-07-29 16:31:26 +09:00
sinu.eth
2205cb3b2c feat(tlsn-wasm): wasm bindings (#536)
* feat(tlsn-wasm): wasm bindings

* fix wasm ci

* clippy

* clippy allow

* add build config and remove redundant to_vec
2024-07-25 14:15:11 +09:00
sinu.eth
040608bb6e fix: notary cd (#538) 2024-07-24 18:51:34 +09:00
sinu.eth
e14d0cf563 refactor: consolidate workspaces (#537)
* move crates

* remove .workspace syntax

* rename crate directories

* normalize manifests

* further normalize manifests

* newlines

* fix dependency issues

* fix import paths

* update CI

* rustfmt

* filter wasm packages

* check in fixtures

* fix rustdoc

* ignore expensive tests
2024-07-23 10:43:31 +09:00
yuroitaki
7377eaf661 fix: clippy gh action and linting (#531)
* fix: miscellaneous fixes on documentation, notary-server.

* Apply clippy fix.

* Apply clippy fix.

* Apply clippy fix.

* Apply clippy fix.

* Apply clippy fix.

* Apply clippy fix.

* Apply clippy fix.

* Apply clippy fix.

* Apply clippy fix.

* Correct ci.yml and build script.

* Apply clippy fix.

* Apply fmt.

* Correct clippy fix for tls-mpc.

* Correct clippy fix for tls-client.

* Correct clippy fix for tls-client.

* Update ci and local build.

* Revert "Merge branch 'fix/misc-fixes' into fix/gh-clippy-action"

This reverts commit 74bdb4a6f8, reversing
changes made to e361a86e6a.

* Apply clippy fix.

* Correct spacing and extra changes.

* Apply fmt.

* Change cargo hack install logic.

* Revert clippy change client buffer len.

* Revert clippy ignore.

* remove cargo hack, relax clippy lints

* remove unused features and dead tests

* appease clippy again

* remove dead features

---------

Co-authored-by: sinui0 <65924192+sinui0@users.noreply.github.com>
2024-07-22 12:44:55 +09:00
ntampakas
b24aea4fad ci: add check to identify number of stable versions deployed (#532)
Co-authored-by: yuroitaki <t.kleinchong@gmail.com>
2024-07-16 21:55:08 +08:00
yuroitaki
d8401ee177 cd: update executable path for easier debugging. (#520) 2024-07-09 18:45:17 +08:00
yuroitaki
44210c350b fix: miscellaneous fixes on documentations (#528)
* fix: miscellaneous fixes on documentation, notary-server.

* Revert notary changes.

* Add warning when TLS is off.
2024-07-09 16:03:14 +08:00
sinu.eth
3554db83e1 chore: release v0.1.0-alpha.6 (#523)
Co-authored-by: yuroitaki <t.kleinchong@gmail.com>
2024-06-26 18:01:50 +08:00
sinu.eth
2f675925d8 chore: bump tlsn-utils (#522) 2024-06-26 11:47:11 +02:00
Hendrik Eeckhaut
da0421dc6a Update Examples (#525)
* doc: Update Twitter URL to X.com and update example READMEs

+ recommend explorer instead of proof_viz
2024-06-26 11:32:40 +02:00
sinu
cef1e3878a chore: bump mpz version 2024-06-25 15:10:20 -07:00
sinu
4fdce7f391 chore: update CI config 2024-06-25 15:10:20 -07:00
sinu.eth
9828fbbd04 chore: remove stale test and clean up ci job (#514)
* chore: remove stale test and clean up ci job

* add ignored option

* comment
2024-06-25 15:10:20 -07:00
sinu.eth
1741741945 refactor(tlsn): mpz upgrade (#512)
* refactor(tlsn): mpz upgrade

* PR feedback
2024-06-25 15:10:20 -07:00
sinu.eth
aa189ca884 refactor(tls-mpc): mpz upgrade (#511)
* refactor(tls-mpc): mpz upgrade

* bump tlsn-utils version
2024-06-25 15:10:20 -07:00
sinu.eth
58dc575c4d refactor(aead): mpz upgrade (#504)
* refactor(aead): mpz upgrade

Co-authored-by: th4s <th4s@metavoid.xyz>

* Apply suggestions from code review

Co-authored-by: dan <themighty1@users.noreply.github.com>

---------

Co-authored-by: th4s <th4s@metavoid.xyz>
Co-authored-by: dan <themighty1@users.noreply.github.com>
2024-06-25 15:10:20 -07:00
th4s
bd4ff5f5b0 Adapt allocation to free squaring. (#507) 2024-06-25 15:10:20 -07:00
sinu.eth
5698d22718 refactor(prf): relax hiding of transcript hash, optimize latency (#505)
* refactor(prf): relax hiding of transcript hash, optimize latency

* PR feedback
2024-06-25 15:10:20 -07:00
sinu.eth
bce09b8d4c chore(uid-mix): delete crate, moved to tlsn-utils (#506) 2024-06-25 15:10:20 -07:00
sinu.eth
bd32c7bb67 refactor(prf): mpz upgrade (#497)
* refactor(prf): mpz upgrade

* consolidate vd methods

* Update components/prf/hmac-sha256/src/lib.rs

Co-authored-by: dan <themighty1@users.noreply.github.com>

---------

Co-authored-by: dan <themighty1@users.noreply.github.com>
2024-06-25 15:10:20 -07:00
sinu.eth
d040341585 refactor(cipher): mpz upgrade (#501)
* refactor(cipher): mpz upgrade

* add debug impl

* bug fix

* update expect messages
2024-06-25 15:10:20 -07:00
sinu.eth
76969bb558 refactor(key-exchange): mpz upgrade (#496)
* refactor: mpz upgrade key exchange

* Update components/key-exchange/src/config.rs

Co-authored-by: th4s <th4s@metavoid.xyz>

* Apply suggestions from code review

Co-authored-by: dan <themighty1@users.noreply.github.com>

* use chacha12 for tests

---------

Co-authored-by: th4s <th4s@metavoid.xyz>
Co-authored-by: dan <themighty1@users.noreply.github.com>
2024-06-25 15:10:20 -07:00
sinu.eth
44930c0add refactor(universal-hash): mpz upgrade (#502)
Co-authored-by: th4s <th4s@metavoid.xyz>
2024-06-25 15:10:20 -07:00
sinu.eth
173f59b04a chore: add rebase action (#515)
* chore: add rebase action

* Apply suggestions from code review

Co-authored-by: Hendrik Eeckhaut <hendrik@eeckhaut.org>

---------

Co-authored-by: Hendrik Eeckhaut <hendrik@eeckhaut.org>
2024-06-25 10:59:18 -07:00
yuroitaki
d64dabbf27 chore: update notary cert (#519)
* Renew notary server cert.

* Fix readme typo on ci icon.
2024-06-25 16:58:17 +08:00
yuroitaki
c82cc4f0a3 Fix cd script after notary folder restructuring. (#516) 2024-06-25 15:54:31 +08:00
yuroitaki
c80b44049f refactor: restructure notary crates (#508)
* Add tlsnotary client.

* Modify notary server test to use notary client.

* Modify examples to use notary client.

* Relocate notary client to notary folder.

* Update notary client.

* Fix ci notary name.

* Update .github/workflows/cd-server.yml

Co-authored-by: dan <themighty1@users.noreply.github.com>

* Update .github/workflows/cd.yml

Co-authored-by: dan <themighty1@users.noreply.github.com>

* Fix notary server readme.

* More fix on notary server doc.

---------

Co-authored-by: dan <themighty1@users.noreply.github.com>
2024-06-25 10:34:25 +08:00
yuroitaki
bff2a5381d Add notary client crate (#489)
* Add tlsnotary client.

* Modify notary server test to use notary client.

* Modify examples to use notary client.

* Fix formatting and comments.

* Fix PR comments.

* Fix PR comments.

* Fix formatting.

* Fix errors, tokio and comments.

* Remove optional tracing feature.

* Fix comment styles.

* Fix incorrect duplex value.
2024-06-18 17:35:43 +08:00
sinu.eth
e19e445f7f fix(tlsn-server-fixture): update cert fixtures (#499)
* fix(tlsn-server-fixture): update cert fixtures

* fix cert fmt

* qualify file name with cert suffix
2024-06-14 14:29:16 -07:00
Hendrik Eeckhaut
474d22a7e3 ci: Refactor and improve CI build (#486)
+ Make sure doctests are run in ci
+ Updated plugins
2024-05-24 21:32:34 +01:00
yuroitaki
23450a3d38 Setup discord example to show how to increase max transcript size. (#485) 2024-05-20 11:40:43 +08:00
dan
3506791df1 doc: update CONTRIBUTING.md (#483)
Add missing newline.
2024-05-08 05:35:34 +00:00
yuroitaki
ea2a8fd80c Update hyper dependencies for notary-server and all server fixtures (#480)
* Update hyper, axum dependencies for notary-server.

* Update hyper dependencies in all server fixtures.

* Correct formatting.

* Update tlsn/Cargo.toml

Co-authored-by: sinu.eth <65924192+sinui0@users.noreply.github.com>

---------

Co-authored-by: sinu.eth <65924192+sinui0@users.noreply.github.com>
2024-05-08 11:56:31 +08:00
yuroitaki
c157c2d3ea Use sync mutex for store in notary server (#482)
* Fix clippy error.

* Use sync mutex for store.
2024-05-06 12:09:29 +08:00
yuroitaki
dc0b887966 Fix clippy error from rust toolchain 1.78.0. (#481) 2024-05-03 18:52:54 +08:00
dan
c431865aea docs: fix style in components (except tls) (#477)
* docs: fix style in components (except tls)

* Update components/cipher/stream-cipher/src/lib.rs

Co-authored-by: Hendrik Eeckhaut <hendrik@eeckhaut.org>

* Update components/universal-hash/src/ghash/ghash_core/mod.rs

Co-authored-by: Hendrik Eeckhaut <hendrik@eeckhaut.org>

---------

Co-authored-by: Hendrik Eeckhaut <hendrik@eeckhaut.org>
2024-04-30 10:08:53 +00:00
dan
9fc0d9162d docs: fix style (#476) 2024-04-25 12:37:40 +00:00
dan
f63a74efe8 docs: misc fixups (#475) 2024-04-24 13:47:21 +00:00
Hendrik Eeckhaut
f558d5bc44 Update rust cache in github action (#453)
ci: Update rust cache in GitHub action and do not skip draft PRs
2024-04-24 14:12:40 +02:00
sinu.eth
68b9474015 fix: drop connection instead of manual close, enable deferred decryption (#472) 2024-04-09 08:47:53 -08:00
Christopher Chong
b4334ad17d chore: Bump versions for release alpha.5. (#470)
Co-authored-by: sinu.eth <65924192+sinui0@users.noreply.github.com>
2024-04-08 08:42:51 -08:00
Hendrik Eeckhaut
d53203e276 docs: List interative example in the examples README (#471)
#451
2024-04-08 08:31:25 -08:00
Christopher Chong
5c0a0309e9 Add api key whitelist hot reloading and small touch-up (#458)
* Add hot reload of api key, remove prover ip, move html static text.

* Add documentation.

* Toggle back config, add comments.

* Edit comment and html info.

* Edit comment.

* Change to sync mutex.
2024-04-04 17:02:22 +08:00
Christopher Chong
a4c7760aec Correct branch links in readme. (#469)
* Add branches info in readme.

* Correct branch links.
2024-03-29 14:46:58 +08:00
Christopher Chong
9e041b81e8 Add branches info in readme. (#467) 2024-03-29 11:23:43 +08:00
ntampakas
173945dd0a Deployment of multiple stable versions (#459)
* Remove cargo/bin from PATH

* Modify script to run only in nightly env

* Modify script to stop the oldest version in stable env

* Modify script to support dir preparation for the 3 latest stable versions

* Modify script to start service for the 3 latest stable versions

* Modify sercice validation script

* Create proxy modification script

* Add step in workflow to enable ssm execution against proxy + aux script

* Add running state filter when fetching InstanceID

* Enhancement of validation script

* Modify bash behavior

* Point tags/deployment to new AWS resources

* Change GH owner to production one

* Point tags to new EC2 v1

* Move all cd scripts to a new folder

* Add comment

* Add comment

* Add comment

* Add comment

* Modify scripts to support exit on error

* Check if all stable ports are in use and terminate
2024-03-29 11:10:18 +08:00
Hendrik Eeckhaut
aa264a90cb perf: Docker container for running benches + manual GitHub action (#460)
+ Plot runtime vs latency graph  and bandwidth
2024-03-26 08:44:08 +01:00
sinu.eth
3e29a5bfe5 feat: record layer preprocessing (#455)
* feat: implement record layer preprocessing

* fix ke test

* fix pa tests

* fix aead tests

* fix integration test

* Apply suggestions from code review

Co-authored-by: dan <themighty1@users.noreply.github.com>

* add mode sanity check

---------

Co-authored-by: dan <themighty1@users.noreply.github.com>
2024-03-25 08:58:52 -08:00
sinu.eth
9a081c6cbc feat: automated network benches (#457)
* feat: automated network benches

* Update tlsn/benches/src/metrics.rs

Co-authored-by: dan <themighty1@users.noreply.github.com>

* remove explicit drops

* remove unnecessary sudo

---------

Co-authored-by: dan <themighty1@users.noreply.github.com>
2024-03-20 09:01:40 -08:00
sinu.eth
19e9c50f35 fix(tlsn-formats): fix commitment error caused by empty headers (#452) 2024-02-29 21:46:42 -08:00
Hendrik Eeckhaut
d7bc0e5cae feat: interactive verifier example (#451)
#440

Co-authored-by: Christopher Chong <t.kleinchong@gmail.com>
2024-02-29 22:18:41 +01:00
Christopher Chong
eec93101bc Update repo readme. (#450)
* Update repo readme.

* Doc: Added minor improvements + a link to other repos

#353

---------

Co-authored-by: Hendrik Eeckhaut <hendrik@eeckhaut.org>
2024-02-28 11:37:11 -08:00
sinu.eth
2372403d38 feat: separate transcript size limits (#435)
* feat(tls-mpc): separate transcript size limits

* feat: separate transcript limits

* feat(tlsn-server-fixture): configurable length byte payload

* refactor(tls-mpc): use defaults in ghash setup

* fix OT estimates

* feat(notary-server): separate transcript limits

* remove dep patch

* fix notary server test
2024-02-21 12:24:42 -08:00
Hendrik Eeckhaut
be24f58364 Show basic html info response for notary server's root endpoint (#439)
feat: basic html info response for notary server's root endpoint

Co-authored-by: Christopher Chong <t.kleinchong@gmail.com>
2024-02-15 14:48:14 +01:00
Christopher Chong
1e99db879a Update notary server README for frequent q&a. (#441) 2024-02-15 16:27:41 +08:00
787 changed files with 45679 additions and 27775 deletions

2
.dockerignore Normal file
View File

@@ -0,0 +1,2 @@
/target
/.git

3
.github/codecov.yml vendored Normal file
View File

@@ -0,0 +1,3 @@
github_checks:
annotations: false
comment: false

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

169
Cargo.toml Normal file
View 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" }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,4 +0,0 @@
pub(crate) mod persist;
#[cfg(test)]
mod persist_test;

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,6 +0,0 @@
-----BEGIN PRIVATE KEY-----
MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDDl30Srs7laSdaAOzoB
kCiehcf1HXv7NqGQBECqshrtHxGEX6bAnBB7JgyDs28NvPGhZANiAAS7F1hCrNsV
woFtTnAut6Qj40KNKt1Zz3NSduJ+pl8MaYN/fJ80eJ94h3GANKSoBJ3gbLUjrFzo
WzWdPQwLxK94knPAcllp7RG1lY2N4PSrekOmNbFxpGlCIj/W3uZSEEU=
-----END PRIVATE KEY-----

View File

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

View File

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

View File

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

View File

@@ -1,6 +0,0 @@
-----BEGIN PRIVATE KEY-----
MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDALKtA1q+8ZBeLi2Gsq
UxFTBxNPPhOuyNRkvwRKis/glf9GgEHgvM0qVaxWnRsdCE6hZANiAATx0R97foSC
0Ra9a13pJzfI1hh3G6476MIMslLHxg5wwCG8k5mMHia2hGOBbdGjoY0C1wJLNrUS
ov5SfcsYX6/VjHQH/elmb/KOO1AGwPD71yD1+DG/cjK1okLZIVhbSQw=
-----END PRIVATE KEY-----

View File

@@ -1,8 +0,0 @@
-----BEGIN CERTIFICATE REQUEST-----
MIIBEzCBmQIBADAaMRgwFgYDVQQDDA9wb255dG93biBjbGllbnQwdjAQBgcqhkjO
PQIBBgUrgQQAIgNiAATx0R97foSC0Ra9a13pJzfI1hh3G6476MIMslLHxg5wwCG8
k5mMHia2hGOBbdGjoY0C1wJLNrUSov5SfcsYX6/VjHQH/elmb/KOO1AGwPD71yD1
+DG/cjK1okLZIVhbSQygADAKBggqhkjOPQQDAgNpADBmAjEA8p3W7yFCJ73dOmYQ
rpMpLkYNcfxxpNfCWgqaPyWu3UeOcHvC7ihklnFTWzpmEO+PAjEA8O5P4mXlYUtl
Dsw8qOrqWSdQ1IykXhM4NxPOkt0TMQZvvrpSsJU6PhwSbJGjVfBR
-----END CERTIFICATE REQUEST-----

View File

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

View File

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

View File

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

View File

@@ -1,5 +0,0 @@
-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgdoMBbIGRw+L9but3
PO4WSJfS8wbvUNrF1VuQjsDVMKmhRANCAAT6a3R7FnN07TZ3tuNPajvnwvThYolx
Xr0MCY1FAif19IoaJkbBwJ6yb3DcjhFrHbuzsDo+Dw/UBTInwxBxmLRz
-----END PRIVATE KEY-----

View File

@@ -1,7 +0,0 @@
-----BEGIN CERTIFICATE REQUEST-----
MIHTMHsCAQAwGTEXMBUGA1UEAwwOdGVzdHNlcnZlci5jb20wWTATBgcqhkjOPQIB
BggqhkjOPQMBBwNCAAT6a3R7FnN07TZ3tuNPajvnwvThYolxXr0MCY1FAif19Ioa
JkbBwJ6yb3DcjhFrHbuzsDo+Dw/UBTInwxBxmLRzoAAwCgYIKoZIzj0EAwIDSAAw
RQIgA9G3IaH4syAQYGJ3ESqXQaoKSrZsDMBD0MgG2g2FC78CIQD+RRTETPkFq0as
cca9W/yqg8QN/ZGzE38iEpohyGda/w==
-----END CERTIFICATE REQUEST-----

View File

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

View File

@@ -1,5 +0,0 @@
-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgdniIWGzkYuZcwh/H
9hDbaITfndAs+Hin6j+0XjD01MShRANCAARi1GU/KSFPRgueNxKxt7yt+YnuiN9a
4ciYMbEOkXm8nbn2CI732oURxlOLt029AYFALpuxRZ11OXcFzuDGIKvd
-----END PRIVATE KEY-----

View File

@@ -1,7 +0,0 @@
-----BEGIN CERTIFICATE REQUEST-----
MIHoMIGQAgEAMC4xLDAqBgNVBAMMI3Bvbnl0b3duIEVDRFNBIGxldmVsIDIgaW50
ZXJtZWRpYXRlMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEYtRlPykhT0YLnjcS
sbe8rfmJ7ojfWuHImDGxDpF5vJ259giO99qFEcZTi7dNvQGBQC6bsUWddTl3Bc7g
xiCr3aAAMAoGCCqGSM49BAMCA0cAMEQCIFeMseiKS80m8KmHkl7W8lRXavH5yx/h
qTFM+f3T4AnZAiBRR8+rFop/TR51gISUfbMj2W3yTAGxOkCdlPgT+Jxqwg==
-----END CERTIFICATE REQUEST-----

View File

@@ -1,9 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIBTDCB/6ADAgECAhRXcvbYynz4+usVvPtJp++sBUih3TAFBgMrZXAwHDEaMBgG
A1UEAwwRcG9ueXRvd24gRWREU0EgQ0EwHhcNMTkwODE2MTMyODUwWhcNMjkwODEz
MTMyODUwWjAcMRowGAYDVQQDDBFwb255dG93biBFZERTQSBDQTAqMAUGAytlcAMh
AIE4tLweIfcBGfhPqyXFp5pjVxjaiKk+9fTbRy46jAFKo1MwUTAdBgNVHQ4EFgQU
z5b9HjkOxffbtCZhWGg+bnxuD6wwHwYDVR0jBBgwFoAUz5b9HjkOxffbtCZhWGg+
bnxuD6wwDwYDVR0TAQH/BAUwAwEB/zAFBgMrZXADQQBNlt7z4bZ7KhzecxZEe3i5
lH9MRqbpP9Rg4HyzAJfTzFGT183HoJiISdPLbxwMn0KaqSGlVe+9GgNKswoaRAwH
-----END CERTIFICATE-----

View File

@@ -1,3 +0,0 @@
-----BEGIN PRIVATE KEY-----
MC4CAQAwBQYDK2VwBCIEIJ+e0HfxvslYFLr6ehAnsW6ZnOSu3Cume9OGJhVVLjnK
-----END PRIVATE KEY-----

View File

@@ -1,11 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIBlDCCAUagAwIBAgICAxUwBQYDK2VwMC4xLDAqBgNVBAMMI3Bvbnl0b3duIEVk
RFNBIGxldmVsIDIgaW50ZXJtZWRpYXRlMB4XDTE5MDgxNjEzMjg1MVoXDTI1MDIw
NTEzMjg1MVowGjEYMBYGA1UEAwwPcG9ueXRvd24gY2xpZW50MCowBQYDK2VwAyEA
uCBt6D15TvwyMNP5/nRo9v38yGIeSGQCM1tjyzXFGHWjgZswgZgwDAYDVR0TAQH/
BAIwADALBgNVHQ8EBAMCBsAwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwIwHQYDVR0O
BBYEFHdQ+goYXFvGem83oCHVJ87Ak0fyMEQGA1UdIwQ9MDuAFBcSMFONOrRtwrD1
pB1qn7lE+PGroSCkHjAcMRowGAYDVQQDDBFwb255dG93biBFZERTQSBDQYIBezAF
BgMrZXADQQA4GfyvVIs/xQQTv3igqhzabhraQKEd4z2HHudJgdHHV7M7yfuNqN3x
NCr3hfDfUQuEOgm02d9Q4TZfik+czUML
-----END CERTIFICATE-----

View File

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

View File

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