diff --git a/.github/changelog.sh b/.github/changelog.sh new file mode 100755 index 0000000..fa3c35a --- /dev/null +++ b/.github/changelog.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash + +m_branch=m; +changelog_file=CHANGELOG.md; + +# fetch master since we might be in a shallow clone +git fetch origin "$m_branch:$m_branch" --depth=1 + +changed=0; +for log in "$changelog_file" */"$changelog_file"; do + dir=$(dirname "$log"); + # check if version changed + if git diff "$m_branch" -- "$dir/Cargo.toml" | grep -q "^-version = "; then + # check if changelog updated + if git diff --exit-code --no-patch "$m_branch" -- "$log"; then + echo "$dir version changed, but $log is not updated" + changed=1; + fi + fi +done + +exit "$changed"; diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml new file mode 100644 index 0000000..8ed336c --- /dev/null +++ b/.github/workflows/rust.yml @@ -0,0 +1,79 @@ +name: Rust + +on: + pull_request: + branches: [ "*" ] + +env: + CARGO_TERM_COLOR: always + CARGO_NET_GIT_FETCH_WITH_CLI: true + RUSTFLAGS: -D warnings + +jobs: + check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: Swatinem/rust-cache@v2 + with: + cache-on-failure: "true" + - name: Check no features + run: cargo check --no-default-features + check-all: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: Swatinem/rust-cache@v2 + with: + cache-on-failure: "true" + - name: Check all features + run: cargo check --all-features + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: Swatinem/rust-cache@v2 + with: + cache-on-failure: "true" + - name: Run tests + run: cargo test --all-features + fmt: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Check formatting + run: cargo fmt --all -- --check + clippy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: Swatinem/rust-cache@v2 + with: + cache-on-failure: "true" + - name: Run clippy + run: cargo clippy --all --lib --all-features -- --no-deps -D clippy::all -D clippy::unwrap_used -D clippy::expect_used + clippy-tests: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: Swatinem/rust-cache@v2 + with: + cache-on-failure: "true" + - name: Run clippy tests + run: cargo clippy --tests --all-features -- -D clippy::all + check-doc: + runs-on: ubuntu-latest + steps: + - uses: dtolnay/rust-toolchain@nightly + - uses: actions/checkout@v3 + - uses: Swatinem/rust-cache@v2 + with: + cache-on-failure: "true" + - name: Check docs + run: RUSTDOCFLAGS="--cfg docsrs -D warnings" cargo +nightly doc --all-features --no-deps + check-changelog: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Check changelogs + run: ./.github/changelog.sh diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..aabd145 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,874 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "cpufeatures" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ca741a962e1b0bff6d724a1a0958b686406e853bb14061f218562e1896f95e6" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "fiat-crypto", + "group", + "rand_core", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.89", +] + +[[package]] +name = "darling" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.89", +] + +[[package]] +name = "darling_macro" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.89", +] + +[[package]] +name = "der" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" +dependencies = [ + "const-oid", + "zeroize", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.89", +] + +[[package]] +name = "educe" +version = "0.4.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f0042ff8246a363dbe77d2ceedb073339e85a804b9a47636c6e016a9a32c05f" +dependencies = [ + "enum-ordinalize", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest", + "ff", + "generic-array", + "group", + "rand_core", + "sec1", + "subtle", + "zeroize", +] + +[[package]] +name = "enum-ordinalize" +version = "3.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bf1fa3f06bbff1ea5b1a9c7b14aa992a39657db60a2759457328d7e058f49ee" +dependencies = [ + "num-bigint", + "num-traits", + "proc-macro2", + "quote", + "syn 2.0.89", +] + +[[package]] +name = "ff" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +dependencies = [ + "rand_core", + "subtle", +] + +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-sink", + "futures-task", + "pin-project-lite", + "pin-utils", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "serde", + "typenum", + "version_check", + "zeroize", +] + +[[package]] +name = "generic-ec" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de1099ac0b4d87261d67ff5d4ed400af617a1da40b58908d759b9cf5fd8ed27" +dependencies = [ + "curve25519-dalek", + "digest", + "generic-ec-core", + "generic-ec-curves", + "hex", + "phantom-type 0.4.2", + "rand_core", + "rand_hash", + "serde", + "serde_with", + "subtle", + "udigest", + "zeroize", +] + +[[package]] +name = "generic-ec-core" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcba5fdf70cc3ce5805c487f8523b4ceeb32e8ec5237c71ffd93c1ca47a97fee" +dependencies = [ + "generic-array", + "rand_core", + "serde", + "subtle", + "zeroize", +] + +[[package]] +name = "generic-ec-curves" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7c6d23001a5eb60eec2b785a63d2ca965fdfbaf3314b3b46df047398369e28" +dependencies = [ + "curve25519-dalek", + "elliptic-curve", + "generic-ec-core", + "group", + "k256", + "rand_core", + "sha2", + "subtle", + "zeroize", +] + +[[package]] +name = "generic-ec-zkp" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd3945c585fdddba3f86bda4e4cfba22d5e255001b3e145c9db305ad096c6d88" +dependencies = [ + "digest", + "generic-array", + "generic-ec", + "rand_core", + "serde", + "subtle", + "udigest", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core", + "subtle", +] + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "k256" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" +dependencies = [ + "cfg-if", + "elliptic-curve", +] + +[[package]] +name = "key-share" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ee8e510bb9f738ac400b7dedd98aeb23677c6b48cd3b1b682651ea5091f4282" +dependencies = [ + "displaydoc", + "generic-ec", + "generic-ec-zkp", + "rand_core", + "thiserror 1.0.69", +] + +[[package]] +name = "libc" +version = "0.2.164" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "433bfe06b8c75da9b2e3fbea6e5329ff87748f0b144ef75306e674c3f6f7c13f" + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "phantom-type" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f710afd11c9711b04f97ab61bb9747d5a04562fdf0f9f44abc3de92490084982" +dependencies = [ + "educe", +] + +[[package]] +name = "phantom-type" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e68f5dc797c2a743e024e1c53215474598faf0408826a90249569ad7f47adeaa" +dependencies = [ + "educe", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro2" +version = "1.0.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "307e3004becf10f5a6e0d59d20f3cd28231b0e0827a96cd3e0ce6d14bc1e4bb3" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_dev" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbee97c27dada05f03db49ffe6516872f6c926e0fd525f9ce0cb3c051adf145c" +dependencies = [ + "hex", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16bc1dd921383c6564eb0b8252f5b3f6622b84d40c6e35f5e6790e1fd7abb7a9" +dependencies = [ + "digest", + "rand_core", + "udigest", +] + +[[package]] +name = "round-based" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "079e623c882b5ec9c1a4140cb077179ea89f5352f85a7ebd879428c33ce66399" +dependencies = [ + "futures-util", + "phantom-type 0.3.1", + "round-based-derive", + "thiserror 2.0.3", + "tracing", +] + +[[package]] +name = "round-based-derive" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4afa4d5b318bcafae8a7ebc57c1cb7d4b2db7358293e34d71bfd605fd327cc13" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "subtle", + "zeroize", +] + +[[package]] +name = "semver" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" + +[[package]] +name = "serde" +version = "1.0.215" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.215" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.89", +] + +[[package]] +name = "serde_with" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07ff71d2c147a7b57362cead5e22f772cd52f6ab31cfcd9edcd7f6aeb2a0afbe" +dependencies = [ + "serde", + "serde_with_macros", +] + +[[package]] +name = "serde_with_macros" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "881b6f881b17d13214e5d494c939ebab463d01264ce1811e9d4ac3a882e7695f" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.89", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tecdh" +version = "0.1.0" +dependencies = [ + "digest", + "generic-ec", + "generic-ec-zkp", + "key-share", + "rand", + "rand_core", + "rand_dev", + "round-based", + "serde", + "sha2", + "test-case", + "thiserror 2.0.3", + "udigest", +] + +[[package]] +name = "test-case" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb2550dd13afcd286853192af8601920d959b14c401fcece38071d53bf0768a8" +dependencies = [ + "test-case-macros", +] + +[[package]] +name = "test-case-core" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adcb7fd841cd518e279be3d5a3eb0636409487998a4aff22f3de87b81e88384f" +dependencies = [ + "cfg-if", + "proc-macro2", + "quote", + "syn 2.0.89", +] + +[[package]] +name = "test-case-macros" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c89e72a01ed4c579669add59014b9a524d609c0c88c6a585ce37485879f6ffb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.89", + "test-case-core", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c006c85c7651b3cf2ada4584faa36773bd07bac24acfb39f3c431b36d7e667aa" +dependencies = [ + "thiserror-impl 2.0.3", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.89", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f077553d607adc1caf65430528a576c757a71ed73944b66ebb58ef2bbd243568" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.89", +] + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "udigest" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cd61fa9fb78569e9fe34acf0048fd8cb9ebdbacc47af740745487287043ff0" +dependencies = [ + "digest", + "udigest-derive", +] + +[[package]] +name = "udigest-derive" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "603329303137e0d59238ee4d6b9c085eada8e2a9d20666f3abd9dadf8f8543f4" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.89", +] + +[[package]] +name = "unicode-ident" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.89", +] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.89", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..fd22f9c --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "tecdh" +version = "0.1.0" +edition = "2021" + +[dependencies] +digest = "0.10" +generic-ec = { version = "0.4.5", features = ["serde", "curve-secp256k1", "hash-to-scalar"] } +generic-ec-zkp = { version = "0.4.4", features = ["serde", "udigest"] } +key-share = "0.6" +rand_core = { version = "0.6", default-features = false } +round-based = { version = "0.4", features = ["derive"] } +serde = "1" +thiserror = { version = "2" } +udigest = { version = "0.2", default-features = false, features = ["inline-struct", "derive"] } + +[dev-dependencies] +generic-ec = { version = "0.4.5", features = ["curve-ed25519"] } +key-share = { version = "0.6", features = ["spof"] } +rand = "0.8" +rand_dev = "0.1" +round-based = { version = "0.4", features = ["sim"] } +sha2 = "0.10" +test-case = "3" diff --git a/changelog.md b/changelog.md new file mode 100644 index 0000000..e1ce5d4 --- /dev/null +++ b/changelog.md @@ -0,0 +1,3 @@ +# v0.1.0 + +Initial implementation diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..e20df1a --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,167 @@ +//! This crate implements Threshold Elliptic Curve Diffie-Hellman key +//! exchange. +//! +//! Threshold means that the private key is shared between multiple parties. +//! Only when a threshold amount of parties run the protocol together, can +//! they form the diffie-hellman session key +//! +//! The procedure for running this protocol resembles tBLS signatures, and in +//! fact was directly adpated from . A big +//! difference from BLS is that since we don't need signature verification, we +//! don't need efficient pairings and can use any curve we want. + +#![warn(missing_docs, unsafe_code, unused_crate_dependencies)] +#![cfg_attr( + not(test), + deny(clippy::expect_used, clippy::unwrap_used, clippy::panic) +)] + +/// Functions to perform low-level operations. This can be misused, so they are +/// not recommended unless you know how tECDH works +pub mod lowlevel; +/// Helper types for the MPC execution +pub mod mpc; + +/// Start an MPC protocol that performs threshold ECDH with shared private key. +/// Returns the session key +/// +/// - `eid` - execution id, a nonce shared by every party +/// - `counterparty_public_key` - public key of the other party doing the key +/// exchange +/// - `i` - index of party in this protocol invocation, used for message routing +/// - `key_share` - key share to use, can be additive or SSS +/// - `participants` - which key holders are participating in the protocol, +/// given as indexes into `share_preimages` in key share. These are the +/// indexes that the parties occupied at keygen +/// - `party` - the `round-based` party +pub async fn start( + eid: &[u8], + counterparty_public_key: generic_ec::NonZero>, + i: u16, + key_share: &key_share::CoreKeyShare, + participants: &[u16], + party: M, + rng: &mut impl rand_core::CryptoRngCore, +) -> Result, mpc::Error> +where + D: digest::Digest, + E: generic_ec::Curve, + M: round_based::Mpc>, +{ + let share_preimages = key_share + .vss_setup + .as_ref() + .map({ + |vss_setup| { + participants + .iter() + .map(|i| vss_setup.I.get(usize::from(*i)).copied()) + .collect::>>() + .ok_or(mpc::Error::CreationFailed("share is not SSS")) + } + }) + .transpose()?; + let share_preimages = share_preimages.as_ref().map(|v| v.as_ref()); + let public_shares = &key_share.public_shares; + let public_shares = participants + .iter() + .map(|i| public_shares[usize::from(*i)]) + .collect::>(); + + mpc::run::( + eid, + counterparty_public_key, + &key_share.x, + i, + key_share.min_signers(), + &public_shares, + share_preimages, + party, + rng, + ) + .await +} + +/// Error for aggregation failing +#[derive(Debug, Clone, thiserror::Error)] +pub enum AggregateFailed { + /// Lagrange polynomial construction failed, probably because some points + /// repeat + #[error("lagrange interpolation failed")] + Lagrange, + /// Party ZKP verification failed + #[error("honesty verification failed for parties: {0:?}")] + Verification(Vec), +} + +#[cfg(test)] +mod test { + type E = generic_ec::curves::Secp256k1; + + #[test_case::test_case(3, 5; "t3n5")] + #[test_case::test_case(5, 5; "t5n5")] + #[test_case::test_case(3, 7; "t3n7")] + fn protocol_same_as_ecdh(t: u16, n: u16) { + let mut rng = rand_dev::DevRng::new(); + + let secret_key = generic_ec::NonZero::>::random(&mut rng); + let shares = key_share::trusted_dealer::builder::(n) + .set_threshold(Some(t)) + .set_shared_secret_key(secret_key.clone()) + .generate_shares(&mut rng) + .unwrap(); + + let data = generic_ec::Point::generator() + * generic_ec::NonZero::>::random(&mut rng); + let party_indexes = random_participants(n, t, &mut rng); + let parties = party_indexes + .iter() + .map(|x| u16::try_from(*x).unwrap()) + .collect::>(); + + let ecdhs = round_based::sim::run_with_setup( + party_indexes.iter().copied(), + |i, party, party_index| { + let mut rng = rng.fork(); + let share = &shares[party_index]; + let parties = &parties; + async move { + crate::start::( + b"test", data, i, share, parties, party, &mut rng, + ) + .await + } + }, + ) + .unwrap() + .expect_ok() + .into_vec(); + + let golden = crate::lowlevel::ecdh(data, &secret_key); + for ecdh in &ecdhs { + assert_eq!(golden, *ecdh); + } + } + + fn random_participants>( + n: Int, + t: Int, + rng: &mut impl rand::RngCore, + ) -> Vec { + let n = n.into(); + + let t = t.into(); + assert!(t <= n); + let mut r = Vec::with_capacity(t); + for _ in 0..t { + loop { + let x = rand::Rng::gen_range(rng, 0..n); + if !r.contains(&x) { + r.push(x); + break; + } + } + } + r + } +} diff --git a/src/lowlevel.rs b/src/lowlevel.rs new file mode 100644 index 0000000..44914f2 --- /dev/null +++ b/src/lowlevel.rs @@ -0,0 +1,184 @@ +use generic_ec_zkp::dlog_eq::non_interactive as dlog_eq; + +/// Compute elliptic curve diffie-hellman for the given public key received from +/// another party and a private key for this party +pub fn ecdh( + pubkey1: generic_ec::NonZero>, + privkey2: &generic_ec::NonZero>, +) -> generic_ec::NonZero> { + pubkey1 * privkey2 +} + +/// Evaluation of partial ECDH as outputted by parties, computed by +/// [`partial_ecdh`]. `t` partials can be aggregated with [`aggregate`] +#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] +#[serde(bound = "")] +pub struct PartialEvaluation { + /// Index of evaluating party + pub i: u16, + /// Partial session key + pub v: generic_ec::NonZero>, + /// ZK proof + pub pi: dlog_eq::Proof, +} + +/// Compute partial result of key exchange, with `secret_share` being a share of +/// this party's private key. Use [`aggregate`] to aggregate multiple such +/// partial evaluations into a full session key. +/// +/// Together with partial session key, a proof of honest correctness is +/// computed. Every party is required to send this proof together with their +/// partial evaluation for aggregation. +/// +/// In paper this function is called `PartialEval(x, sk_i, vk_i)`, section IV.A +/// +/// - `eid` - execution id, used to prevent replay attacks. All parties +/// computing the partial evaluations should agree on this value. This value +/// cannot be reused between executions as that leads to replay attacks +/// - `i` - index of this party among other computing parties. If `t` parties +/// are performing the partial evaluation, each index should be from `0` to +/// `t - 1`. When using [`aggregate`], partial evaluations should be sorted by +/// this index. +/// - `counterparty_public_key` - `H_1(x)` in paper, public key of the other +/// party doing the key exchange +/// - `secret_share` - `sk` from paper, share of the private key of the party +/// doing this key exchange. The `vk` argument which is present in paper but +/// not here is computed from it +pub fn partial_ecdh( + eid: &[u8], + i: u16, + counterparty_public_key: generic_ec::NonZero>, + secret_share: &generic_ec::NonZero>, + rng: &mut impl rand_core::CryptoRngCore, +) -> PartialEvaluation { + let value = counterparty_public_key * secret_share; + let proof_data = + dlog_eq::Data::from_secret_key(secret_share, counterparty_public_key.into_inner()); + let shared_state = SharedState { + eid, + prover_index: i, + }; + let proof = dlog_eq::prove::(rng, &shared_state, secret_share, proof_data); + PartialEvaluation { + i, + v: value, + pi: proof, + } +} + +/// Aggregate partial ECDH session keys into a full session key +/// +/// - `counterparty_public_key` - `H_1(x)` in paper, public key of the other +/// party doing the key exchange +/// - `public_shares` - list of public shares of parties who computed partial +/// evaluations, given in the same order as partials and share preimages. `VK` in +/// paper +/// - `partials` - `E` in paper, partial ECDH session keys +/// - `share_preimages` - should be `None` for additive key shares. For SSS, it +/// gives the points at which the keyshare values are computed, and should be in +/// the same order by participant as `partials` +/// +/// Partials, public shares and share preimages each should correspond to the +/// same party, that is all be ordered in the same way by the index of the party +/// they come from. +/// +/// In paper this function is called `Combine(pk, VK, x, E)`, section IV.A +pub fn aggregate( + eid: &[u8], + counterparty_public_key: generic_ec::NonZero>, + partials: &[PartialEvaluation], + public_shares: &[generic_ec::NonZero>], + share_preimages: Option<&[generic_ec::NonZero>]>, +) -> Result, crate::AggregateFailed> { + // Verify the proofs + let mut blame = Vec::new(); + for (partial, pub_share) in partials.iter().zip(public_shares) { + let shared_state = SharedState { + eid, + prover_index: partial.i, + }; + let data = dlog_eq::Data { + gen1: generic_ec::Point::generator().into(), + prod1: pub_share.into_inner(), + gen2: counterparty_public_key.into_inner(), + prod2: partial.v.into_inner(), + }; + if dlog_eq::verify::(&shared_state, data, partial.pi).is_err() { + blame.push(partial.i); + }; + } + // The paper suggests to continue with an honest subset, but we prefer to + // abort + if !blame.is_empty() { + return Err(crate::AggregateFailed::Verification(blame)); + } + + // Compute the aggregate session key + if let Some(share_preimages) = share_preimages { + // shamir aggregation + let lagrange_coefficients = (0..(share_preimages.len())) + .map(|j| generic_ec_zkp::polynomial::lagrange_coefficient_at_zero(j, share_preimages)) + .collect::>>() + .ok_or(crate::AggregateFailed::Lagrange)?; + Ok(generic_ec::Scalar::multiscalar_mul( + lagrange_coefficients + .into_iter() + .zip(partials.iter().map(|t| t.v)), + )) + } else { + // additive aggregation + Ok(partials.iter().map(|t| t.v).sum()) + } +} + +/// Shared state between proving in [`partial_ecdh`] and verifying in +/// [`aggregate`] +#[derive(udigest::Digestable)] +struct SharedState<'a> { + eid: &'a [u8], + prover_index: u16, +} + +#[cfg(test)] +mod test { + type E = generic_ec::curves::Secp256k1; + + #[test_case::test_case(3, 5; "t3n5")] + #[test_case::test_case(5, 5; "t5n5")] + #[test_case::test_case(3, 7; "t3n7")] + fn aggregate_same_as_ecdh(t: u16, n: u16) { + let t_ = if t == n { None } else { Some(t) }; + let t = usize::from(t); + let mut rng = rand_dev::DevRng::new(); + + let secret_key = generic_ec::NonZero::>::random(&mut rng); + let shares = key_share::trusted_dealer::builder::(n) + .set_threshold(t_) + .set_shared_secret_key(secret_key.clone()) + .generate_shares(&mut rng) + .unwrap(); + let public_shares = &shares[0].public_shares; + let share_preimages = shares[0].vss_setup.as_ref().map(|vss| &vss.I[0..t]); + + let data = generic_ec::Point::generator() + * generic_ec::NonZero::>::random(&mut rng); + let eid = b"test"; + + let ecdh = super::ecdh(data, &secret_key); + let partials = shares + .iter() + .zip(0..) + .map(|(s, i)| super::partial_ecdh::(eid, i, data, &s.x, &mut rng)) + .collect::>(); + let restored = super::aggregate::( + eid, + data, + &partials[0..t], + public_shares, + share_preimages, + ) + .unwrap(); + + assert_eq!(restored, ecdh); + } +} diff --git a/src/mpc.rs b/src/mpc.rs new file mode 100644 index 0000000..f70c01a --- /dev/null +++ b/src/mpc.rs @@ -0,0 +1,84 @@ +use crate::lowlevel; + +/// Protocol message +#[derive(round_based::ProtocolMessage, Clone, serde::Serialize, serde::Deserialize)] +#[serde(bound = "")] +pub enum Msg { + /// The only round + Partial(MsgPartial), +} + +/// Protocol message +#[derive(Clone, serde::Serialize, serde::Deserialize)] +#[serde(bound = "")] +pub struct MsgPartial { + /// Partial evaluation of party + pub evaluation: lowlevel::PartialEvaluation, +} + +#[allow(clippy::too_many_arguments)] +pub(crate) async fn run( + eid: &[u8], + other_key: generic_ec::NonZero>, + secret_share: &generic_ec::NonZero>, + i: u16, + n: u16, + public_shares: &[generic_ec::NonZero>], + share_preimages: Option<&[generic_ec::NonZero>]>, + party: M, + rng: &mut impl rand_core::CryptoRngCore, +) -> Result, Error> +where + D: digest::Digest, + E: generic_ec::Curve, + M: round_based::Mpc>, +{ + use round_based::SinkExt as _; + + let round_based::MpcParty { delivery, .. } = party.into_party(); + let (incomings, mut outgoings) = round_based::Delivery::split(delivery); + + let mut rounds = round_based::rounds_router::RoundsRouter::>::builder(); + let round = + rounds.add_round(round_based::rounds_router::simple_store::RoundInput::broadcast(i, n)); + let mut rounds = rounds.listen(incomings); + + let evaluation = lowlevel::partial_ecdh::(eid, i, other_key, secret_share, rng); + let my_partial = MsgPartial { evaluation }; + outgoings + .send(round_based::Outgoing::broadcast(Msg::Partial( + my_partial.clone(), + ))) + .await + .map_err(|e| Error::SendMessage(Box::new(e)))?; + + let partials = rounds + .complete(round) + .await + .map_err(|x| Error::RecvMessage(Box::new(x)))?; + let partials = partials + .into_iter_including_me(my_partial) + .map(|s| s.evaluation) + .collect::>(); + + lowlevel::aggregate::(eid, other_key, &partials, public_shares, share_preimages) + .map_err(Error::AggregateFailed) +} + +/// Error with a reason for protocol start or execution failure +#[derive(Debug, thiserror::Error)] +#[non_exhaustive] +pub enum Error { + /// Send message error + #[error("send message")] + SendMessage(Box), + /// Receive message error + #[error("recv message")] + RecvMessage(Box), + /// Aggregation failure + #[error("aggregation failed: {0}")] + AggregateFailed(crate::AggregateFailed), + /// Failure to start + #[error("creating protocol failed")] + CreationFailed(&'static str), +}