From 4638aef7b7c0a3cb3c352e4862982ccf06bc4fe6 Mon Sep 17 00:00:00 2001 From: Denis Varlakov Date: Mon, 19 Feb 2024 11:54:44 +0100 Subject: [PATCH 1/6] First version of `givre` --- .github/workflows/publish.yml | 25 + .github/workflows/readme.yml | 24 + .github/workflows/rust.yml | 63 ++ .gitignore | 3 + Cargo.lock | 1410 ++++++++++++++++++++++++++++ Cargo.toml | 10 + Makefile | 17 + README.md | 18 + docs/readme-template | 1 + givre/Cargo.toml | 42 + givre/katex-header.html | 1 + givre/src/ciphersuite.rs | 151 +++ givre/src/ciphersuite/ed25519.rs | 80 ++ givre/src/ciphersuite/secp256k1.rs | 102 ++ givre/src/lib.rs | 143 +++ givre/src/signing.rs | 36 + givre/src/signing/aggregate.rs | 148 +++ givre/src/signing/full_signing.rs | 291 ++++++ givre/src/signing/round1.rs | 60 ++ givre/src/signing/round2.rs | 271 ++++++ givre/src/signing/utils.rs | 99 ++ katex-header.html | 50 + tests/Cargo.toml | 23 + tests/src/lib.rs | 1 + tests/tests/it/interactive.rs | 97 ++ tests/tests/it/main.rs | 77 ++ tests/tests/it/test_vectors.rs | 312 ++++++ 27 files changed, 3555 insertions(+) create mode 100644 .github/workflows/publish.yml create mode 100644 .github/workflows/readme.yml create mode 100644 .github/workflows/rust.yml create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 Makefile create mode 100644 docs/readme-template create mode 100644 givre/Cargo.toml create mode 120000 givre/katex-header.html create mode 100644 givre/src/ciphersuite.rs create mode 100644 givre/src/ciphersuite/ed25519.rs create mode 100644 givre/src/ciphersuite/secp256k1.rs create mode 100644 givre/src/lib.rs create mode 100644 givre/src/signing.rs create mode 100644 givre/src/signing/aggregate.rs create mode 100644 givre/src/signing/full_signing.rs create mode 100644 givre/src/signing/round1.rs create mode 100644 givre/src/signing/round2.rs create mode 100644 givre/src/signing/utils.rs create mode 100644 katex-header.html create mode 100644 tests/Cargo.toml create mode 100644 tests/src/lib.rs create mode 100644 tests/tests/it/interactive.rs create mode 100644 tests/tests/it/main.rs create mode 100644 tests/tests/it/test_vectors.rs diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..9211b64 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,25 @@ +on: + push: + tags: + - 'v*' + workflow_dispatch: + +name: Publish + +env: + CARGO_TERM_COLOR: always + CARGO_NET_GIT_FETCH_WITH_CLI: true + +jobs: + publish-givre: + name: Publish givre + environment: crates.io + runs-on: ubuntu-latest + if: >- + github.ref_type == 'tag' + && startsWith(github.ref_name, 'v') + steps: + - uses: actions/checkout@v3 + - run: cargo publish -p givre + env: + CARGO_REGISTRY_TOKEN: ${{ secrets.CRATES_TOKEN }} diff --git a/.github/workflows/readme.yml b/.github/workflows/readme.yml new file mode 100644 index 0000000..92a7ef9 --- /dev/null +++ b/.github/workflows/readme.yml @@ -0,0 +1,24 @@ +name: Check README + +on: + pull_request: + branches: [ "*" ] + +env: + CARGO_TERM_COLOR: always + CARGO_NET_GIT_FETCH_WITH_CLI: true + +jobs: + check_readme: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Install cargo-hakari + uses: baptiste0928/cargo-install@v1 + with: + crate: cargo-readme + - name: Check that readme matches lib.rs + run: | + cp README.md README-copy.md + make readme + diff README.md README-copy.md diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml new file mode 100644 index 0000000..e825ffa --- /dev/null +++ b/.github/workflows/rust.yml @@ -0,0 +1,63 @@ +name: Rust + +on: + pull_request: + branches: [ "*" ] + +env: + CARGO_TERM_COLOR: always + CARGO_NET_GIT_FETCH_WITH_CLI: true + +jobs: + build-bare: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: Swatinem/rust-cache@v2 + with: + cache-on-failure: "true" + - name: Build all-features + run: cargo build -p givre + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: Swatinem/rust-cache@v2 + with: + cache-on-failure: "true" + - name: Build all-features + run: cargo build -p givre --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 + 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 --workspace --all-features --no-deps diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..02db9f8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/target + +/.helix diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..70ae07f --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1410 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "backtrace" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[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 = "bumpalo" +version = "3.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" + +[[package]] +name = "bytes" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" + +[[package]] +name = "cc" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cggmp21-keygen" +version = "0.1.0" +source = "git+https://github.com/dfns/cggmp21?branch=spof#5ef39c9bcb62d189513e58884145fc34517b4892" +dependencies = [ + "digest", + "futures", + "generic-ec", + "generic-ec-zkp", + "hex", + "key-share", + "rand_core", + "round-based", + "serde", + "serde_with", + "sha2", + "slip-10", + "thiserror", + "udigest", +] + +[[package]] +name = "chrono" +version = "0.4.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bc015644b92d5890fab7489e49d21f879d5c990186827d42ec511919404f38b" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "num-traits", + "serde", + "windows-targets", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "core-foundation-sys" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" + +[[package]] +name = "cpufeatures" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +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.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a677b8922c94e01bdbb12126b0bc852f00447528dee1782229af9c720c3f348" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "fiat-crypto", + "group", + "platforms", + "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.48", +] + +[[package]] +name = "darling" +version = "0.20.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c376d08ea6aa96aafe61237c7200d1241cb177b7d3a542d791f2d118e9cbb955" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33043dcd19068b8192064c704b3f83eb464f91f1ff527b44a4e2b08d9cdb8855" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.48", +] + +[[package]] +name = "darling_macro" +version = "0.20.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5a91391accf613803c2a9bf9abccdbaa07c54b4244a5b64883f9c3c137c86be" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "der" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" +dependencies = [ + "const-oid", + "zeroize", +] + +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", + "serde", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + +[[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.48", +] + +[[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.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1676f435fc1dadde4d03e43f5d62b259e1ce5f40bd4ffb21db2b42ebe59c1382" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "futures" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" + +[[package]] +name = "futures-executor" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" + +[[package]] +name = "futures-macro" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "futures-sink" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" + +[[package]] +name = "futures-task" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" + +[[package]] +name = "futures-util" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[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.1.4" +source = "git+https://github.com/dfns/generic-ec?branch=m#ba27154f93ec2502f71063056e1da35039140a69" +dependencies = [ + "generic-ec-core", + "generic-ec-curves", + "hex", + "phantom-type 0.4.2", + "rand_core", + "serde", + "serde_with", + "subtle", + "udigest", + "zeroize", +] + +[[package]] +name = "generic-ec-core" +version = "0.1.2" +source = "git+https://github.com/dfns/generic-ec?branch=m#ba27154f93ec2502f71063056e1da35039140a69" +dependencies = [ + "generic-array", + "rand_core", + "serde", + "subtle", + "zeroize", +] + +[[package]] +name = "generic-ec-curves" +version = "0.1.3" +source = "git+https://github.com/dfns/generic-ec?branch=m#ba27154f93ec2502f71063056e1da35039140a69" +dependencies = [ + "crypto-bigint", + "curve25519-dalek", + "elliptic-curve", + "generic-ec-core", + "group", + "k256", + "rand_core", + "sha2", + "subtle", + "zeroize", +] + +[[package]] +name = "generic-ec-zkp" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f98645d68f62951789a4aec46ef4416878c2827b2353f5267cf96b096eae60d9" +dependencies = [ + "generic-array", + "generic-ec", + "rand_core", + "serde", + "subtle", + "udigest", +] + +[[package]] +name = "generic-tests" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eeb39ec0dacc89541b6eced815ab9e97f6b7d44078628abb090c6437763fd050" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "getrandom" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "gimli" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" + +[[package]] +name = "givre" +version = "0.1.0" +dependencies = [ + "cggmp21-keygen", + "digest", + "futures", + "generic-ec", + "k256", + "key-share", + "rand_core", + "round-based", + "sha2", + "static_assertions", +] + +[[package]] +name = "givre-tests" +version = "0.1.0" +dependencies = [ + "futures", + "generic-tests", + "givre", + "hex-literal", + "rand", + "rand_core", + "rand_dev", + "round-based", + "test-case", + "tokio", +] + +[[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 = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +dependencies = [ + "serde", +] + +[[package]] +name = "hex-literal" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown", + "serde", +] + +[[package]] +name = "itoa" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" + +[[package]] +name = "js-sys" +version = "0.3.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "406cda4b368d531c842222cf9d2600a9a4acce8d29423695379c6868a143a9ee" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "k256" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "956ff9b67e26e1a6a866cb758f12c6f8746208489e3e4a4b5580802f2f0a587b" +dependencies = [ + "cfg-if", + "elliptic-curve", +] + +[[package]] +name = "key-share" +version = "0.1.0" +source = "git+https://github.com/dfns/cggmp21?branch=spof#5ef39c9bcb62d189513e58884145fc34517b4892" +dependencies = [ + "generic-ec", + "generic-ec-zkp", + "hex", + "rand_core", + "serde", + "serde_with", + "slip-10", + "thiserror", +] + +[[package]] +name = "libc" +version = "0.2.153" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" + +[[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" + +[[package]] +name = "memchr" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" + +[[package]] +name = "miniz_oxide" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" +dependencies = [ + "adler", +] + +[[package]] +name = "num-bigint" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[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.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "object" +version = "0.32.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[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.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "platforms" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "626dec3cac7cc0e1577a2ec3fc496277ec2baa084bebad95bb6fdbfae235f84c" + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro2" +version = "1.0.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +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 = "round-based" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50a0cb1a40a115430c0d124ee305cf118208a37f921a744d41e84d3468a2c1d0" +dependencies = [ + "futures-util", + "phantom-type 0.3.1", + "round-based-derive", + "thiserror", + "tokio", + "tokio-stream", + "tracing", +] + +[[package]] +name = "round-based-derive" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0397bf224fdbcb3b286926e43bba90a96f81a82cc630ebfc9290d18e8b6331bd" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "ryu" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" + +[[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.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0" + +[[package]] +name = "serde" +version = "1.0.196" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.196" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "serde_json" +version = "1.0.113" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69801b70b1c3dac963ecb03a364ba0ceda9cf60c71cfe475e99864759c8b8a79" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_with" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07ff71d2c147a7b57362cead5e22f772cd52f6ab31cfcd9edcd7f6aeb2a0afbe" +dependencies = [ + "base64", + "chrono", + "hex", + "indexmap", + "serde", + "serde_json", + "serde_with_macros", + "time", +] + +[[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.48", +] + +[[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 = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "slip-10" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4670651d286d5714497971c12d36b83d75679c39da5d5ed757ff5e1e1d45855c" +dependencies = [ + "generic-array", + "generic-ec", + "hmac", + "sha2", + "subtle", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + +[[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.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[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.48", +] + +[[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.48", + "test-case-core", +] + +[[package]] +name = "thiserror" +version = "1.0.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "time" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8248b6521bb14bc45b4067159b9b6ad792e2d6d754d6c41fb50e29fefe38749" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ba3a3ef41e6672a2f0f001392bb5dcd3ff0a9992d618ca761a11c3121547774" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tokio" +version = "1.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931" +dependencies = [ + "backtrace", + "pin-project-lite", + "tokio-macros", +] + +[[package]] +name = "tokio-macros" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "tokio-stream" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", + "tokio-util", +] + +[[package]] +name = "tokio-util" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", +] + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "udigest" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8e4a5cb9599a5dc3d47f21ad605a5a14594e0b212c7627d68dd5ca9776ace2e" +dependencies = [ + "digest", + "udigest-derive", +] + +[[package]] +name = "udigest-derive" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b29f121da05aa0857e7b96cf2f8782bd4140911506518486d4a125b97d7d609" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1e124130aee3fb58c5bdd6b639a0509486b0338acaaae0c84a5124b0f588b7f" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9e7e1900c352b609c8488ad12639a311045f40a35491fb69ba8c12f758af70b" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.48", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b30af9e2d358182b5c7449424f017eba305ed32a7010509ede96cdc4696c46ed" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "642f325be6301eb8107a83d12a8ac6c1e1c54345a7ef1a9261962dfefda09e66" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f186bd2dcf04330886ce82d6f33dd75a7bfcf69ecf5763b89fcde53b6ac9838" + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" + +[[package]] +name = "zeroize" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" +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.48", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..ec2924e --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,10 @@ +[workspace] +resolver = "2" +members = [ + "givre", + "tests", +] + +[patch.crates-io.generic-ec] +git = "https://github.com/dfns/generic-ec" +branch = "m" diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..82411b2 --- /dev/null +++ b/Makefile @@ -0,0 +1,17 @@ +.PHONY: docs docs-open + +docs: + RUSTDOCFLAGS="--html-in-header katex-header.html --cfg docsrs" cargo +nightly doc --no-deps --all-features + +docs-open: + RUSTDOCFLAGS="--html-in-header katex-header.html --cfg docsrs" cargo +nightly doc --no-deps --all-features --open + +docs-private: + RUSTDOCFLAGS="--html-in-header katex-header.html --cfg docsrs" cargo +nightly doc --no-deps --all-features --document-private-items + +readme: + cargo readme --no-title -r givre -i src/lib.rs \ + | perl -ne 's/(? README.md + diff --git a/README.md b/README.md index 8b13789..84920ea 100644 --- a/README.md +++ b/README.md @@ -1 +1,19 @@ +Threshold Schnorr implementation based on [FROST IETF Draft][draft] +FROST is state of art protocol for Threshold Schnorr Signatures that supports 1-round signing (requires signers to +commit nonces ahead of time), and identifiable abort. + +This crate provides: +* Threshold and non-threshold Distributed Key Generation (DKG) \ + Note that FROST does not define DKG protocol to be used. We simply re-export DKG based on [CGGMP21] implementation + when `cggmp21-keygen` feature is enabled. Alternatively, you can use any other UC-secure DKG protocol. +* FROST Signing \ + We provide API for both manual signing execution (for better flexibility and efficiency) and interactive protocol + (for easier usability and fool-proof design), see signing module for details. +* Trusted dealer (importing key into TSS) + +This crate doesn't support (currently): +* Identifiable abort + +[CGGMP21]: https://github.com/dfns/cggmp21 +[draft]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-15.html diff --git a/docs/readme-template b/docs/readme-template new file mode 100644 index 0000000..274bb44 --- /dev/null +++ b/docs/readme-template @@ -0,0 +1 @@ +{{readme}} diff --git a/givre/Cargo.toml b/givre/Cargo.toml new file mode 100644 index 0000000..9df706b --- /dev/null +++ b/givre/Cargo.toml @@ -0,0 +1,42 @@ +[package] +name = "givre" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +cggmp21-keygen = { git = "https://github.com/dfns/cggmp21", branch = "spof", optional = true } +key-share = { git = "https://github.com/dfns/cggmp21", branch = "spof" } + +generic-ec = { version = "0.1", default-features = false } + +rand_core = { version = "0.6", default-features = false } +digest = { version = "0.10", default-features = false } + +round-based = { version = "0.2", default-features = false, features = ["derive"], optional = true } +futures = { version = "0.3", default-features = false, features = [], optional = true } + +k256 = { version = "0.13", default-features = false, features = ["hash2curve"], optional = true } +static_assertions = { version = "1.1", optional = true } +sha2 = { version = "0.10", default-features = false, optional = true } + +[dev-dependencies] +rand_core = { version = "0.6", default-features = false, features = ["getrandom"] } + +[features] +cggmp21-keygen = ["dep:cggmp21-keygen"] +full-signing = ["round-based", "futures"] + +spof = ["key-share/spof"] + +# Enabling this flag only makes the library compatible with these dependencies. Otherwise, +# for instance, if `key-share` crate has `hd-wallets` feature enabled, but `givre` doesn't, +# it'll fail to compile. +# +# Library doesn't have support of HD signing yet. +hd-wallets = ["key-share/hd-wallets", "cggmp21-keygen?/hd-wallets"] + +all-curves = ["curve-secp256k1", "curve-ed25519"] +curve-secp256k1 = ["generic-ec/curve-secp256k1", "k256", "sha2", "static_assertions"] +curve-ed25519 = ["generic-ec/curve-ed25519", "sha2"] diff --git a/givre/katex-header.html b/givre/katex-header.html new file mode 120000 index 0000000..810e68d --- /dev/null +++ b/givre/katex-header.html @@ -0,0 +1 @@ +../katex-header.html \ No newline at end of file diff --git a/givre/src/ciphersuite.rs b/givre/src/ciphersuite.rs new file mode 100644 index 0000000..4ce1d0b --- /dev/null +++ b/givre/src/ciphersuite.rs @@ -0,0 +1,151 @@ +//! FROST Ciphersuite +//! +//! Ciphersuite specifies which curve and hash primitives to use during the signing. +//! +//! Out of the box, we provide ciphersuites defined the in the draft: +//! * [Secp256k1], requires `curve-secp256k1` feature +//! * [Ed25519], requires `curve-ed25519` feature +use generic_ec::{ + errors::{InvalidPoint, InvalidScalar}, + Curve, Point, Scalar, SecretScalar, +}; +use rand_core::{CryptoRng, RngCore}; + +#[cfg(feature = "curve-ed25519")] +mod ed25519; +#[cfg(feature = "curve-secp256k1")] +mod secp256k1; + +#[cfg(feature = "curve-ed25519")] +pub use ed25519::Ed25519; +#[cfg(feature = "curve-secp256k1")] +pub use secp256k1::Secp256k1; + +/// Ciphersuite determines an underlying curve and set of cryptographic primitives +/// used in the protocol +/// +/// For the details, refer to [Section 6] of the draft +/// +/// [Section 6]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-15.html#name-ciphersuites +pub trait Ciphersuite { + /// Name of the ciphersuite, also known as `contextString` in the draft + const NAME: &'static str; + + /// Underlying curve on which signatures will be produced + type Curve: Curve; + + /// Digest that's used to feed data into [H4](Self::h4) and [H5](Self::h5) hash functions + type Digest: digest::Update + digest::FixedOutput + Clone; + + /// `H1` hash function as defined in the draft + /// + /// Accepts a list of bytestring, that'll be contatenated before hashing. + /// Returns `H1(data[0] || data[1] || ... || data[data.len() - 1])`. + fn h1(msg: &[&[u8]]) -> Scalar; + /// `H2` hash function as defined in the draft + /// + /// Accepts a list of bytestring, that'll be contatenated before hashing. + /// Returns `H2(data[0] || data[1] || ... || data[data.len() - 1])`. + fn h2(msg: &[&[u8]]) -> Scalar; + /// `H3` hash function as defined in the draft + /// + /// Accepts a list of bytestring, that'll be contatenated before hashing. + /// Returns `H3(data[0] || data[1] || ... || data[data.len() - 1])`. + fn h3(msg: &[&[u8]]) -> Scalar; + + /// `H4` hash function as defined in the draft + /// + /// Accepts a list of bytestring, that'll be contatenated before hashing. + /// Returns `H4(data[0] || data[1] || ... || data[data.len() - 1])`. + fn h4() -> Self::Digest; + /// `H5` hash function as defined in the draft + /// + /// Accepts a list of bytestring, that'll be contatenated before hashing. + /// Returns `H5(data[0] || data[1] || ... || data[data.len() - 1])`. + fn h5() -> Self::Digest; + + /// Byte array that contains bytes representation of the point + type PointBytes: AsRef<[u8]>; + /// Serializes point + fn serialize_point(point: &Point) -> Self::PointBytes; + /// Deserializes point + fn deserialize_point(bytes: &[u8]) -> Result, InvalidPoint>; + + /// Byte array that contains bytes representation of the scalar + type ScalarBytes: AsRef<[u8]>; + /// Serializes scalar + fn serialize_scalar(scalar: &Scalar) -> Self::ScalarBytes; + /// Deserializes scalar + fn deserialize_scalar(bytes: &[u8]) -> Result, InvalidScalar>; + /// Deserializes secret scalar + fn deserialize_secret_scalar(bytes: &[u8]) -> Result, InvalidScalar> { + let mut scalar = Self::deserialize_scalar(bytes)?; + Ok(SecretScalar::new(&mut scalar)) + } +} + +/// Nonce generation as defined in [Section 4.1](https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-15.html#name-nonce-generation) +/// +/// The draft specifies that this function should only accept a scalar as `additional_entropy`, however, +/// we observed that any data that's only known to the signer can be fed into this function. +pub fn generate_nonce( + rng: &mut (impl RngCore + CryptoRng), + additional_entropy: impl AdditionalEntropy, +) -> SecretScalar { + let mut random_bytes = [0u8; 32]; + rng.fill_bytes(&mut random_bytes); + + let additional_entropy = additional_entropy.to_bytes(); + + let mut hash = C::h3(&[random_bytes.as_slice(), additional_entropy.as_ref()]); + + generic_ec::SecretScalar::new(&mut hash) +} + +/// Additional entropy to [nonce generation](generate_nonce) +pub trait AdditionalEntropy { + /// Bytes arrays that fits the whole bytes representation of the entropy + type Bytes<'b>: AsRef<[u8]> + where + Self: 'b; + + /// Returns bytes representation of the entropy encoded in complience with [`C`](Ciphersuite) + fn to_bytes<'b>(&'b self) -> Self::Bytes<'b>; +} + +impl, E: Curve> AdditionalEntropy for crate::KeyShare { + type Bytes<'b> = as AdditionalEntropy>::Bytes<'b>; + fn to_bytes<'b>(&'b self) -> Self::Bytes<'b> { + AdditionalEntropy::::to_bytes(&self.x) + } +} +impl, E: Curve> AdditionalEntropy for generic_ec::Scalar { + type Bytes<'b> = C::ScalarBytes; + fn to_bytes<'b>(&'b self) -> Self::Bytes<'b> { + C::serialize_scalar(self) + } +} +impl, E: Curve> AdditionalEntropy for generic_ec::SecretScalar { + type Bytes<'b> = as AdditionalEntropy>::Bytes<'b>; + fn to_bytes<'b>(&'b self) -> Self::Bytes<'b> { + AdditionalEntropy::::to_bytes(self.as_ref()) + } +} +impl AdditionalEntropy for [u8] { + type Bytes<'b> = &'b [u8]; + fn to_bytes<'b>(&'b self) -> Self::Bytes<'b> { + self + } +} +impl AdditionalEntropy for [u8; N] { + type Bytes<'b> = &'b [u8; N]; + fn to_bytes<'b>(&'b self) -> Self::Bytes<'b> { + self + } +} +impl> AdditionalEntropy for &T { + type Bytes<'b> = >::Bytes<'b> where Self: 'b; + fn to_bytes<'b>(&'b self) -> Self::Bytes<'b> { + (*self).to_bytes() + } +} diff --git a/givre/src/ciphersuite/ed25519.rs b/givre/src/ciphersuite/ed25519.rs new file mode 100644 index 0000000..3ee3022 --- /dev/null +++ b/givre/src/ciphersuite/ed25519.rs @@ -0,0 +1,80 @@ +use digest::Digest; + +use crate::Ciphersuite; + +/// FROST(Ed25519, SHA-512) ciphersuite that produces Ed25519-compliant signatures +#[derive(Debug, Clone, Copy)] +pub struct Ed25519; + +impl Ciphersuite for Ed25519 { + const NAME: &'static str = "FROST-ED25519-SHA512-v1"; + + type Curve = generic_ec::curves::Ed25519; + type Digest = sha2::Sha512; + + fn h1(msg: &[&[u8]]) -> generic_ec::Scalar { + let mut hash = sha2::Sha512::new() + .chain_update(Self::NAME) + .chain_update(b"rho"); + for msg in msg { + hash.update(msg); + } + let hash = hash.finalize(); + + generic_ec::Scalar::from_le_bytes_mod_order(hash) + } + + fn h2(msg: &[&[u8]]) -> generic_ec::Scalar { + let mut hash = sha2::Sha512::new(); + for msg in msg { + hash.update(msg); + } + let hash = hash.finalize(); + + generic_ec::Scalar::from_le_bytes_mod_order(hash) + } + + fn h3(msg: &[&[u8]]) -> generic_ec::Scalar { + let mut hash = sha2::Sha512::new() + .chain_update(Self::NAME) + .chain_update(b"nonce"); + for msg in msg { + hash.update(msg); + } + let hash = hash.finalize(); + + generic_ec::Scalar::from_le_bytes_mod_order(hash) + } + + fn h4() -> Self::Digest { + sha2::Sha512::new() + .chain_update(Self::NAME) + .chain_update(b"msg") + } + + fn h5() -> Self::Digest { + sha2::Sha512::new() + .chain_update(Self::NAME) + .chain_update(b"com") + } + + type PointBytes = generic_ec::EncodedPoint; + fn serialize_point(point: &generic_ec::Point) -> Self::PointBytes { + point.to_bytes(true) + } + fn deserialize_point( + bytes: &[u8], + ) -> Result, generic_ec::errors::InvalidPoint> { + generic_ec::Point::from_bytes(bytes) + } + + type ScalarBytes = generic_ec::EncodedScalar; + fn serialize_scalar(scalar: &generic_ec::Scalar) -> Self::ScalarBytes { + scalar.to_le_bytes() + } + fn deserialize_scalar( + bytes: &[u8], + ) -> Result, generic_ec::errors::InvalidScalar> { + generic_ec::Scalar::from_le_bytes(bytes) + } +} diff --git a/givre/src/ciphersuite/secp256k1.rs b/givre/src/ciphersuite/secp256k1.rs new file mode 100644 index 0000000..399da08 --- /dev/null +++ b/givre/src/ciphersuite/secp256k1.rs @@ -0,0 +1,102 @@ +use digest::Digest; + +use crate::Ciphersuite; + +/// FROST(secp256k1, SHA-256) ciphersuite +#[derive(Debug, Clone, Copy)] +pub struct Secp256k1; + +impl Ciphersuite for Secp256k1 { + const NAME: &'static str = "FROST-secp256k1-SHA256-v1"; + + type Curve = generic_ec::curves::Secp256k1; + type Digest = sha2::Sha256; + + fn h1(msg: &[&[u8]]) -> generic_ec::Scalar { + hash_to_scalar(msg, &[Self::NAME.as_bytes(), b"rho"]) + } + + fn h2(msg: &[&[u8]]) -> generic_ec::Scalar { + hash_to_scalar(msg, &[Self::NAME.as_bytes(), b"chal"]) + } + + fn h3(msg: &[&[u8]]) -> generic_ec::Scalar { + hash_to_scalar(msg, &[Self::NAME.as_bytes(), b"nonce"]) + } + + fn h4() -> Self::Digest { + sha2::Sha256::new() + .chain_update(Self::NAME) + .chain_update(b"msg") + } + + fn h5() -> Self::Digest { + sha2::Sha256::new() + .chain_update(Self::NAME) + .chain_update(b"com") + } + + type PointBytes = generic_ec::EncodedPoint; + fn serialize_point(point: &generic_ec::Point) -> Self::PointBytes { + point.to_bytes(true) + } + fn deserialize_point( + bytes: &[u8], + ) -> Result, generic_ec::errors::InvalidPoint> { + generic_ec::Point::from_bytes(bytes) + } + + type ScalarBytes = generic_ec::EncodedScalar; + fn serialize_scalar(scalar: &generic_ec::Scalar) -> Self::ScalarBytes { + scalar.to_be_bytes() + } + fn deserialize_scalar( + bytes: &[u8], + ) -> Result, generic_ec::errors::InvalidScalar> { + generic_ec::Scalar::from_be_bytes(bytes) + } +} + +fn hash_to_scalar( + msgs: &[&[u8]], + dsts: &[&[u8]], +) -> generic_ec::Scalar<::Curve> { + use generic_ec::as_raw::FromRaw; + use k256::elliptic_curve::{ + generic_array::typenum::Unsigned, + hash2curve::{ExpandMsgXmd, FromOkm, GroupDigest as _}, + }; + + // According to the doc, `k256::Secp256k1::hash_to_scalar` returns error if: + // * dst.is_empty() + // * len_in_bytes == 0 + // * len_in_bytes > u16::MAX + // * len_in_bytes > 255 * HashT::OutputSize + // where len_in_bytes = ::Length + + // You can observe by looking at the module that `dst` is never empty, but also + // we enforce it via debug assert below: + debug_assert!( + dsts.iter().map(|part| part.len()).sum::() > 0, + "dst must not be empty" + ); + + // The other conditions are checked statically below + #[allow(dead_code)] + { + const LENGTH_IN_BYTES: usize = <::Length as Unsigned>::USIZE; + const SHA256_OUTPUT_SIZE: usize = + <::OutputSize as Unsigned>::USIZE; + use static_assertions as sa; + + sa::const_assert!(LENGTH_IN_BYTES > 0); + sa::const_assert!(LENGTH_IN_BYTES <= u16::MAX as _); + sa::const_assert!(LENGTH_IN_BYTES <= 255 * SHA256_OUTPUT_SIZE); + } + + // So, we can safely unwrap the result + #[allow(clippy::expect_used)] + let scalar_raw = k256::Secp256k1::hash_to_scalar::>(msgs, dsts) + .expect("should never fail"); + generic_ec::Scalar::from_raw(generic_ec::curves::Secp256k1::scalar(scalar_raw)) +} diff --git a/givre/src/lib.rs b/givre/src/lib.rs new file mode 100644 index 0000000..2df499d --- /dev/null +++ b/givre/src/lib.rs @@ -0,0 +1,143 @@ +//! Threshold Schnorr implementation based on [FROST IETF Draft][draft] +//! +//! FROST is state of art protocol for Threshold Schnorr Signatures that supports 1-round signing (requires signers to +//! [commit nonces](signing::round1) ahead of time), and identifiable abort. +//! +//! This crate provides: +//! * Threshold and non-threshold Distributed Key Generation (DKG) \ +//! Note that FROST does not define DKG protocol to be used. We simply re-export DKG based on [CGGMP21] implementation +//! when `cggmp21-keygen` feature is enabled. Alternatively, you can use any other UC-secure DKG protocol. +//! * FROST Signing \ +//! We provide API for both manual signing execution (for better flexibility and efficiency) and interactive protocol +//! (for easier usability and fool-proof design), see [mod@signing] module for details. +//! * [Trusted dealer](trusted_dealer) (importing key into TSS) +//! +//! This crate doesn't support (currently): +//! * Identifiable abort +//! +//! [CGGMP21]: https://github.com/dfns/cggmp21 +//! [draft]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-15.html + +#![forbid(unsafe_code)] +#![deny(clippy::expect_used, clippy::unwrap_used, clippy::panic)] +#![deny(missing_docs)] +#![cfg_attr(docsrs, feature(doc_auto_cfg))] + +pub use generic_ec; +#[cfg(feature = "full-signing")] +pub use round_based; + +pub mod ciphersuite; +pub mod signing; + +/// Key share +/// +/// This module re-exports type definitions from [`key_share`](::key_share) crate. +pub mod key_share { + #[doc(inline)] + pub use key_share::{ + CoreKeyShare as KeyShare, DirtyCoreKeyShare as DirtyKeyShare, DirtyKeyInfo, + InvalidCoreShare as InvalidKeyShare, KeyInfo, Validate, VssSetup, + }; +} + +/// Distributed Key Generation (DKG) protocol based on CGGMP21 paper +/// +/// CGGMP21 DKG protocol is proven to be UC-secure, which means that it can safely be composed with +/// other protocols such as FROST signing. CGGMP21 implementation is audited and heavily used +/// in production, so it should be a reasonably secure DKG implementation. +/// +/// This module just re-exports [`cggmp21_keygen`] crate when `cggmp21-keygen` feature is enabled. +#[cfg(feature = "cggmp21-keygen")] +pub mod keygen { + #[doc(inline)] + pub use cggmp21_keygen::*; +} + +#[cfg(feature = "cggmp21-keygen")] +#[doc(inline)] +pub use cggmp21_keygen::keygen; + +/// Trusted dealer +/// +/// Trusted dealer can be used to generate key shares in one place. Note +/// that in creates SPOF/T (single point of failure/trust). Trusted +/// dealer is mainly intended to be used in tests, but also could be used +/// to import a key into TSS. +/// +/// ## Example +/// Import a key into 3-out-of-5 TSS: +/// ```rust,no_run +/// # use rand_core::OsRng; +/// # let mut rng = OsRng; +/// use givre::generic_ec::{curves::Secp256k1, SecretScalar}; +/// +/// let secret_key_to_be_imported = SecretScalar::::random(&mut rng); +/// +/// let key_shares = givre::trusted_dealer::builder::(5) +/// .set_threshold(Some(3)) +/// .set_shared_secret_key(secret_key_to_be_imported) +/// .generate_shares(&mut rng)?; +/// # Ok::<_, key_share::trusted_dealer::TrustedDealerError>(()) +/// ``` +#[cfg(feature = "spof")] +pub mod trusted_dealer { + pub use key_share::trusted_dealer::*; +} + +pub use self::{ + ciphersuite::Ciphersuite, + key_share::{KeyInfo, KeyShare}, +}; + +/// Signer index +pub type SignerIndex = u16; + +/// Interactive Signing +/// +/// Can be used to carry out the full signing protocol in which each signer commits nonces, +/// produces a signature share and, optionally, aggregates all signature shares into the +/// final signature. +/// +/// This can be less efficient than doing signing manually, when you can commit nonces before +/// a message to be signed is known, but more secure, as using this function ensures that +/// protocol isn't misused (e.g. that nonce is never resused). +/// +/// ## Inputs +/// * Signer index in *signing protocol* $0 \le i < \\text{min\\_signers}$ +/// * Signer secret key share +/// * List of signer that participate in the signing, must have exactly threshold amount of signers \ +/// `signers[j]` is index which j-th signer occupied at keygen +/// * `msg` to be signed +/// +/// ## Example +/// ```rust,no_run +/// use givre::round_based; +/// use givre::ciphersuite::Secp256k1; +/// # +/// # fn retrieve_key_share() -> givre::KeyShare<::Curve> { unimplemented!() } +/// # fn join_network() -> (u16, impl round_based::Delivery) { +/// # (0, (futures::stream::pending::>(), futures::sink::drain())) +/// # } +/// # async fn __doc() -> Result<(), givre::signing::full_signing::FullSigningError> { +/// +/// let key_share = retrieve_key_share(); +/// let (i, delivery) = join_network(); +/// let signers = [0, 1, 2]; +/// let msg = b"Hello, TSS World!"; +/// +/// let party = round_based::MpcParty::connected(delivery); +/// let sig = givre::signing::(i, &key_share, &signers, msg) +/// .sign(party, &mut rand_core::OsRng) +/// .await?; +/// # Ok(()) } +/// ``` +#[cfg(feature = "full-signing")] +pub fn signing<'a, C: Ciphersuite>( + i: SignerIndex, + key_share: &'a KeyShare, + signers: &'a [SignerIndex], + msg: &'a [u8], +) -> signing::full_signing::SigningBuilder<'a, C> { + signing::full_signing::SigningBuilder::new(i, key_share, signers, msg) +} diff --git a/givre/src/signing.rs b/givre/src/signing.rs new file mode 100644 index 0000000..239faee --- /dev/null +++ b/givre/src/signing.rs @@ -0,0 +1,36 @@ +//! FROST Threshold Signing Protocol +//! +//! This crate provides two options how the protocol can be carried out: manually or interactively. +//! ## Manually +//! You can manually carry out each phase of the protocol by using [round1], [round2], [aggregate] modules. +//! This gives greater flexibility, but you must be carefull not to do anything that could harm security. +//! +//! Manual signing is done as described below. We assume presence of Coordinator, it can be either some entity +//! in the system, or it could be implemented as some sort of consensus protocol between the signers. +//! 1. Each signer commits nonces via [round1::commit] \ +//! Inputs to this phase: source of cryptographic randomness and source of additional entropy (like key share). \ +//! Message to be signed doesn't need to be known at this point yet. \ +//! Outputs: +//! * [round1::SecretNonces] that need to be kept secret +//! * [round1::PublicCommitments] that need to be sent to Coordinator +//! 2. Coordinator receives a request to sign a message `msg`. It chooses a set of signer (of size `min_signers`) who +//! will carry out signing. For each signer, it chooses a commitment previously sent by the signer. It forwards +//! signing request to each signer along with list of commitments. +//! 3. Signer receives a signing request. Signer retrieves [round1::SecretNonces] corresponding to the commitments +//! chosen by the Coordinator. Signer must delete retrieved secret nonces and make sure they can never be used +//! again. +//! 4. Each signer signs a message via [round2::sign], and sends the resulting [round2::SigShare] to Coorinator. +//! 5. Coordinator receives [round2::SigShare] from each Signer, and aggregates them via [aggregate::aggregate] +//! into a regular [aggregate::Signature]. +//! +//! ## Interactivelly +//! When `full-signing` feature is enabled, you can use [`signing`](crate::signing()) function to carry out +//! full signing protocol based on [`round_based`] framework. This provides the best security, although +//! you lose flexibility, for instance, to commit nonces before message to be signed is known. + +pub mod aggregate; +#[cfg(feature = "full-signing")] +pub mod full_signing; +pub mod round1; +pub mod round2; +mod utils; diff --git a/givre/src/signing/aggregate.rs b/givre/src/signing/aggregate.rs new file mode 100644 index 0000000..5755eb0 --- /dev/null +++ b/givre/src/signing/aggregate.rs @@ -0,0 +1,148 @@ +//! Signature shares aggregation +//! +//! In this phase, Coordinator aggregates signature shares into a regular signature. +//! +//! For more details, refer to [parent module](super) docs, or [Section 5.3] of the draft. +//! +//! [Section 5.3]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-15.html#name-signature-share-aggregation + +use core::fmt; + +use generic_ec::{Curve, Point, Scalar}; + +use crate::{Ciphersuite, SignerIndex}; + +use super::{round1::PublicCommitments, round2::SigShare, utils}; + +/// Schnorr Signature +pub struct Signature { + /// $R$ component of the signature + pub r: Point, + /// $z$ component of the signature + pub z: Scalar, +} + +impl Signature { + /// Verifies signature against a public key and a message + pub fn verify>( + &self, + public_key: &Point, + msg: &[u8], + ) -> Result<(), InvalidSignature> { + let comm_bytes = C::serialize_point(&self.r); + let pk_bytes = C::serialize_point(public_key); + let challenge = C::h2(&[comm_bytes.as_ref(), pk_bytes.as_ref(), msg]); + + let lhs = Point::generator() * &self.z; + let rhs = self.r + public_key * challenge; + + if lhs == rhs { + Ok(()) + } else { + Err(InvalidSignature) + } + } +} + +/// Aggregate [signature shares](SigShare) into a regular [Schnorr signature](Signature) +/// +/// Inputs: +/// * Public `key_info` +/// * List of signers, their commitments and signature shares +/// * `msg` being signed +/// +/// Outputs [Schnorr signature](Signature) +pub fn aggregate( + key_info: &crate::key_share::KeyInfo, + signers: &[(SignerIndex, PublicCommitments, SigShare)], + msg: &[u8], +) -> Result, AggregateError> { + // --- Retrieve and Validate Data + let mut comm_list = signers + .iter() + .map(|(j, comm, _sig_share)| { + key_info + .share_preimage(*j) + .map(|id| (id, *comm)) + .ok_or(Reason::UnknownSigner(*j)) + }) + .collect::, _>>()?; + comm_list.sort_unstable_by_key(|(i, _)| *i); + + // Check that no signer appears in the list more than once + if comm_list + .iter() + .skip(1) + .zip(&comm_list) + .any(|(current, prev)| current.0 == prev.0) + { + return Err(Reason::SameSignerTwice.into()); + } + + // --- The Aggregation + let binding_factor_list = + utils::compute_binding_factors::(key_info.shared_public_key, &comm_list, msg); + let group_commitment = + utils::compute_group_commitment(comm_list.iter().zip(&binding_factor_list).map( + |((j, comm), (_j, factor))| { + debug_assert_eq!(j, _j); + (*j, *comm, *factor) + }, + )); + let z = signers + .iter() + .map(|(_j, _comm, sig_share)| sig_share.0) + .sum(); + + Ok(Signature { + r: group_commitment, + z, + }) +} + +/// Aggregation error +#[derive(Debug)] +pub struct AggregateError(Reason); + +#[derive(Debug)] +enum Reason { + UnknownSigner(SignerIndex), + SameSignerTwice, +} + +impl From for AggregateError { + fn from(err: Reason) -> Self { + Self(err) + } +} + +impl fmt::Display for AggregateError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match &self.0 { + Reason::UnknownSigner(j) => write!(f, "unknown signer {j}"), + Reason::SameSignerTwice => { + f.write_str("same signer appears more than once in the list") + } + } + } +} + +impl std::error::Error for AggregateError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match &self.0 { + Reason::UnknownSigner(_) | Reason::SameSignerTwice => None, + } + } +} + +/// Signature verification failed +#[derive(Debug)] +pub struct InvalidSignature; + +impl fmt::Display for InvalidSignature { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("invalid signature") + } +} + +impl std::error::Error for InvalidSignature {} diff --git a/givre/src/signing/full_signing.rs b/givre/src/signing/full_signing.rs new file mode 100644 index 0000000..0a10f63 --- /dev/null +++ b/givre/src/signing/full_signing.rs @@ -0,0 +1,291 @@ +//! Interactive Signing +use core::fmt; + +use futures::SinkExt; +use generic_ec::Curve; +use rand_core::{CryptoRng, RngCore}; +use round_based::{ + rounds_router::{simple_store::RoundInput, RoundsRouter}, + Delivery, +}; + +use crate::{ + key_share::{KeyInfo, KeyShare}, + Ciphersuite, SignerIndex, +}; + +use super::{aggregate::Signature, round1::PublicCommitments, round2::SigShare}; + +/// Message of FROST Signing Protocol +#[derive(Debug, Clone, Copy, round_based::ProtocolMessage)] +pub enum Msg { + /// Round 1 message + Round1(PublicCommitments), + /// Round 2 message + Round2(SigShare), +} + +/// Builder for FROST Interactive Signing Protocol +pub struct SigningBuilder<'a, C: Ciphersuite> { + i: SignerIndex, + key_share: &'a KeyShare, + signers: &'a [SignerIndex], + msg: &'a [u8], +} + +impl<'a, C: Ciphersuite> SigningBuilder<'a, C> { + /// Constructs a signing builder + /// + /// It could be easier to use [signing](crate::signing()) function located in the crate root. + pub fn new( + i: SignerIndex, + key_share: &'a KeyShare, + signers: &'a [SignerIndex], + msg: &'a [u8], + ) -> Self { + Self { + i, + key_share, + signers, + msg, + } + } + + /// Issues signature share + /// + /// Signer will output a signature share. It'll be more efficient than [generating a full signature](Self::sign), + /// but it requires you to send collect all sig shares in one place and [aggreate](crate::signing::aggregate::aggregate) + /// them. + pub async fn issue_sig_share( + self, + party: M, + rng: &mut R, + ) -> Result, FullSigningError> + where + M: round_based::Mpc>, + R: RngCore + CryptoRng, + { + match signing::( + party, + rng, + self.i, + self.key_share, + self.signers, + self.msg, + true, + ) + .await? + { + SigningOutput::SigShare(out) => Ok(out), + _ => Err(Bug::UnexpectedOutput.into()), + } + } + + /// Executes Interactive Signing protocol + pub async fn sign( + self, + party: M, + rng: &mut R, + ) -> Result, FullSigningError> + where + M: round_based::Mpc>, + R: RngCore + CryptoRng, + { + match signing::( + party, + rng, + self.i, + self.key_share, + self.signers, + self.msg, + false, + ) + .await? + { + SigningOutput::Signature(out) => Ok(out), + _ => Err(Bug::UnexpectedOutput.into()), + } + } +} + +async fn signing( + party: M, + rng: &mut (impl RngCore + CryptoRng), + i: SignerIndex, + key_share: &KeyShare, + signers: &[SignerIndex], + msg: &[u8], + output_sig_share: bool, +) -> Result, FullSigningError> +where + C: Ciphersuite, + M: round_based::Mpc>, +{ + let n = signers + .len() + .try_into() + .map_err(|_| Reason::NOverflowsU16)?; + + if i >= n { + return Err(Reason::INotInRange.into()); + } + if key_share.min_signers() != n { + return Err(Reason::UnexpectedNumberOfSigners.into()); + } + + let round_based::MpcParty { delivery, .. } = party.into_party(); + let (incoming, mut outgoing) = delivery.split(); + + let mut rounds = RoundsRouter::>::builder(); + let round1 = rounds.add_round(RoundInput::>::broadcast(i, n)); + let round2 = rounds.add_round(RoundInput::>::broadcast(i, n)); + let mut rounds = rounds.listen(incoming); + + // Round 1 + let (nonces, commitments) = crate::signing::round1::commit::(rng, key_share); + outgoing + .send(round_based::Outgoing::broadcast(Msg::Round1(commitments))) + .await + .map_err(IoError::send)?; + + // Round 2 + let other_commitments = rounds.complete(round1).await.map_err(IoError::recv)?; + + let signers_list = signers + .iter() + .zip(other_commitments.iter_including_me(&commitments)) + .map(|(&j, &comm)| (j, comm)) + .collect::>(); + + let sig_share = crate::signing::round2::sign::(key_share, nonces, msg, &signers_list) + .map_err(Reason::Sign)?; + + if output_sig_share { + return Ok(SigningOutput::SigShare(sig_share)); + } + + outgoing + .send(round_based::Outgoing::broadcast(Msg::Round2(sig_share))) + .await + .map_err(IoError::send)?; + + // Aggregate sigature + let sig_shares = rounds.complete(round2).await.map_err(IoError::recv)?; + + let signers_list = signers_list + .into_iter() + .zip(sig_shares.iter_including_me(&sig_share)) + .map(|((j, comm), &sig_share)| (j, comm, sig_share)) + .collect::>(); + + let key_info: &KeyInfo<_> = key_share.as_ref(); + let sig = crate::signing::aggregate::aggregate::(&key_info, &signers_list, msg) + .map_err(Reason::Aggregate)?; + + Ok(SigningOutput::Signature(sig)) +} + +enum SigningOutput { + Signature(Signature), + SigShare(SigShare), +} + +/// Interactive Signing error +#[derive(Debug)] +pub struct FullSigningError(Reason); + +#[derive(Debug)] +enum Reason { + NOverflowsU16, + INotInRange, + UnexpectedNumberOfSigners, + IoError(IoError), + Sign(crate::signing::round2::SigningError), + Aggregate(crate::signing::aggregate::AggregateError), + Bug(Bug), +} + +#[derive(Debug)] +enum IoError { + Send(Box), + Recv(Box), +} + +impl IoError { + fn send(err: impl std::error::Error + Send + Sync + 'static) -> Self { + Self::Send(Box::new(err)) + } + fn recv(err: impl std::error::Error + Send + Sync + 'static) -> Self { + Self::Recv(Box::new(err)) + } +} + +#[derive(Debug)] +enum Bug { + UnexpectedOutput, +} + +impl fmt::Display for FullSigningError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match &self.0 { + Reason::NOverflowsU16 => f.write_str("number of signers overflows u16"), + Reason::INotInRange => f.write_str("signer index not in range (it must be 0 <= i < n)"), + Reason::UnexpectedNumberOfSigners => f.write_str( + "unexpected number of signers in the signing: \ + exactly `min_signers` number of signers must \ + participate in signing", + ), + Reason::IoError(IoError::Send(_)) => f.write_str("i/o error: send message"), + Reason::IoError(IoError::Recv(_)) => f.write_str("i/o error: recv message"), + Reason::Sign(_) => f.write_str("perform signing"), + Reason::Aggregate(_) => f.write_str("aggregate signature"), + Reason::Bug(_) => f.write_str("bug occurred"), + } + } +} + +impl fmt::Display for Bug { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Bug::UnexpectedOutput => f.write_str("unexpected output"), + } + } +} + +impl std::error::Error for FullSigningError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match &self.0 { + Reason::NOverflowsU16 | Reason::INotInRange | Reason::UnexpectedNumberOfSigners => None, + Reason::IoError(IoError::Send(err)) | Reason::IoError(IoError::Recv(err)) => { + Some(&**err) + } + Reason::Sign(err) => Some(err), + Reason::Aggregate(err) => Some(err), + Reason::Bug(err) => Some(err), + } + } +} + +impl std::error::Error for Bug { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Bug::UnexpectedOutput => None, + } + } +} + +impl From for FullSigningError { + fn from(err: Reason) -> Self { + Self(err) + } +} +impl From for FullSigningError { + fn from(err: Bug) -> Self { + Self(Reason::Bug(err)) + } +} +impl From for FullSigningError { + fn from(err: IoError) -> Self { + Self(Reason::IoError(err)) + } +} diff --git a/givre/src/signing/round1.rs b/givre/src/signing/round1.rs new file mode 100644 index 0000000..871c926 --- /dev/null +++ b/givre/src/signing/round1.rs @@ -0,0 +1,60 @@ +//! Round 1 - Commitment +//! +//! In the first round, signers [generate](commit) nonces and their corresponding commitments. +//! +//! For more details, refer to [parent module](super) docs, or [Section 5.1] of the draft +//! +//! [Section 5.1]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-15.html#name-round-one-commitment + +use generic_ec::{Curve, Point, SecretScalar}; +use rand_core::{CryptoRng, RngCore}; + +use crate::ciphersuite::{AdditionalEntropy, Ciphersuite}; + +/// Nonces generated by the signer that must be kept secret +#[derive(Clone)] +pub struct SecretNonces { + /// Hiding nonce + pub hiding_nonce: SecretScalar, + /// Binding nonce + pub binding_nonce: SecretScalar, +} + +impl SecretNonces { + /// Derives public commitments corresponding to the secret nonces + pub fn public_commitments(&self) -> PublicCommitments { + PublicCommitments { + hiding_comm: Point::generator() * &self.hiding_nonce, + binding_comm: Point::generator() * &self.binding_nonce, + } + } +} + +/// Commitments generated by the signer that need to be shared with other signers +#[derive(Debug, PartialEq, Eq, Copy, Clone)] +pub struct PublicCommitments { + /// Commitment to the hiding nonce + pub hiding_comm: Point, + /// Commitment to the binding nonce + pub binding_comm: Point, +} + +/// Generates nonces and their corresponding commitments +/// +/// Nonces need to be kept in secret, while commitments has to be shared with other signers. +/// +/// `additional_entropy` can be a key share or any other secret data that's known only to the signer +pub fn commit( + rng: &mut (impl RngCore + CryptoRng), + additional_entropy: &impl AdditionalEntropy, +) -> (SecretNonces, PublicCommitments) { + let hiding_nonce = crate::ciphersuite::generate_nonce::(rng, additional_entropy); + let binding_nonce = crate::ciphersuite::generate_nonce::(rng, additional_entropy); + let nonces = SecretNonces { + hiding_nonce, + binding_nonce, + }; + let comms = nonces.public_commitments(); + + (nonces, comms) +} diff --git a/givre/src/signing/round2.rs b/givre/src/signing/round2.rs new file mode 100644 index 0000000..c087ceb --- /dev/null +++ b/givre/src/signing/round2.rs @@ -0,0 +1,271 @@ +//! Round 2 - Signing +//! +//! In the second round, each signer signs a message and obtains a [signature share](SigShare). +//! +//! For more details, refer to [parent module](super) docs, or [Section 5.2] of the draft. +//! +//! [Section 5.2]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-15.html#name-round-two-signature-share-g + +use core::{fmt, iter}; + +use generic_ec::{Curve, NonZero, Point, Scalar}; + +use crate::{ciphersuite::Ciphersuite, KeyShare, SignerIndex}; + +use super::{ + round1::{PublicCommitments, SecretNonces}, + utils, +}; + +/// Partial signature +#[derive(Debug, Copy, Clone)] +pub struct SigShare(pub Scalar); + +/// Issues a partial signature on the `msg` +/// +/// Inputs: +/// * `key_share` which will be used for signing +/// * Secret `nonce` from [round 1](super::round1) +/// * `msg` to be signed +/// * List of `signers`: their indexes `0 <= i < n` that were used at keygen, +/// and their commitments to the nonces obtained at [round 1](super::round1) +/// +/// Outputs a partial signature. +/// +/// **Never reuse nonces!** Using the same nonce to sign two different messages leaks the secret share. +pub fn sign( + key_share: &KeyShare, + nonce: SecretNonces, + msg: &[u8], + signers: &[(SignerIndex, PublicCommitments)], +) -> Result, SigningError> { + // --- Retrieve and Validate Data + if signers.len() < usize::from(key_share.min_signers()) { + return Err(Reason::TooFewSigners { + min_signers: key_share.min_signers(), + n: signers.len(), + } + .into()); + } + let signer_id = key_share + .share_preimage(key_share.i) + .ok_or(Bug::RetrieveOwnShareId)?; + let mut comm_list = signers + .iter() + .map(|(j, comm)| { + if key_share.i == *j && nonce.public_commitments() != *comm { + // Commitments don't match provided nonces - invalid inputs + Err(Reason::NoncesDontMatchComm) + } else { + key_share + .share_preimage(*j) + .map(|id| (id, *comm)) + .ok_or(Reason::UnknownSigner(*j)) + } + }) + .collect::, _>>()?; + comm_list.sort_unstable_by_key(|(i, _)| *i); + + // Check that: + // 1. This signer id is in the list of participants + // 2. No signer appears in the list more than once + // + // Given that the list is sorted, the check can be done in one iteration + let mut own_index_found = None; + for (signer_index, ((j, _), com_j_minus_one)) in comm_list + .iter() + .zip(iter::once(None).chain(comm_list.iter().map(Some))) + .enumerate() + { + if *j == signer_id { + own_index_found = Some(signer_index); + } + if let Some((j_minus_one, _)) = com_j_minus_one { + if j_minus_one == j { + return Err(Reason::SameSignerTwice.into()); + } + } + } + let Some(i) = own_index_found else { + return Err(Reason::SignerNotInList.into()); + }; + + // --- The Signing + let binding_factor_list = + utils::compute_binding_factors::(key_share.shared_public_key, &comm_list, msg); + let binding_factor = binding_factor_list.get(i).ok_or(Bug::OwnBindingFactor)?.1; + debug_assert_eq!(binding_factor_list[i].0, signer_id); + + let group_commitment = + utils::compute_group_commitment(comm_list.iter().zip(&binding_factor_list).map( + |((i, comm), (_i, factor))| { + debug_assert_eq!(i, _i); + (*i, *comm, *factor) + }, + )); + + let signers_list = comm_list.iter().map(|(i, _)| *i).collect::>(); + let lambda_i = if key_share.vss_setup.is_some() { + derive_interpolating_value(&signers_list, &signer_id) + .ok_or(Reason::DeriveInterpolationValue)? + } else { + Scalar::one() + }; + + let challenge = compute_challenge::(&group_commitment, &key_share.shared_public_key, msg); + + Ok(SigShare( + nonce.hiding_nonce + + (nonce.binding_nonce * binding_factor) + + (lambda_i * &key_share.x * challenge), + )) +} + +/// Computes an interpolation value as described in [Section 4.2] +/// +/// [Section 4.2]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-15.html#name-polynomials +/// +/// Differences compared to the draft: +/// * List of signers **must be** sorted (the draft doesn't require this) \ +/// Function enforces a debug assertation to make sure that list of signers is sorted. +/// * Implemented minor optimizations that allow iterate over list of signers only once by +/// employing the fact that list of signers is sorted. +fn derive_interpolating_value( + signers_list: &[NonZero>], + x_i: &NonZero>, +) -> Option> { + debug_assert!( + utils::is_sorted(signers_list), + "signers list must be sorted" + ); + + let mut x_i_observed = false; + + let mut num = Scalar::one(); + let mut denom = NonZero::>::one(); + + for (x_j, x_j_minus_one) in signers_list + .iter() + .zip(iter::once(None).chain(signers_list.iter().map(Some))) + { + if Some(x_j) == x_j_minus_one { + return None; + } + let Some(substraction) = NonZero::from_scalar(x_j - x_i) else { + // x_i equals to x_j + x_i_observed = true; + continue; + }; + num *= x_j.as_ref(); + denom = denom * substraction; + } + + if !x_i_observed { + return None; + } + + Some(num * denom.invert()) +} + +/// Computes a challenge as described in [Section 4.6] +/// +/// [Section 4.6]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-15.html#name-signature-challenge-computa +fn compute_challenge( + group_commitment: &Point, + group_pk: &Point, + msg: &[u8], +) -> Scalar { + C::h2(&[ + C::serialize_point(&group_commitment).as_ref(), + C::serialize_point(&group_pk).as_ref(), + msg, + ]) +} + +/// Signing error +#[derive(Debug)] +pub struct SigningError(Reason); + +#[derive(Debug)] +enum Reason { + TooFewSigners { min_signers: u16, n: usize }, + UnknownSigner(SignerIndex), + SameSignerTwice, + SignerNotInList, + NoncesDontMatchComm, + DeriveInterpolationValue, + Bug(Bug), +} + +#[derive(Debug)] +enum Bug { + RetrieveOwnShareId, + OwnBindingFactor, +} + +impl fmt::Display for SigningError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.0 { + Reason::TooFewSigners { min_signers, n } => write!( + f, + "signers list contains {n} singners, although at \ + least {min_signers} must take part in the signing" + ), + Reason::UnknownSigner(j) => write!(f, "unknown signer with index {j}"), + Reason::SameSignerTwice => f.write_str( + "same signer appears more than once in the list \ + of signers", + ), + Reason::SignerNotInList => f.write_str("signer not in the list of participants"), + Reason::NoncesDontMatchComm => f.write_str("nonces don't match signer commitments"), + Reason::DeriveInterpolationValue => f.write_str( + "invalid list of signers: either this signer is \ + not in the list, or some signer in the list is \ + mentioned more than once", + ), + Reason::Bug(_) => f.write_str("bug occurred"), + } + } +} + +impl fmt::Display for Bug { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Bug::RetrieveOwnShareId => f.write_str("retrieve own share id"), + Bug::OwnBindingFactor => f.write_str("retrieve own binding factor"), + } + } +} + +impl std::error::Error for SigningError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match &self.0 { + Reason::TooFewSigners { .. } + | Reason::UnknownSigner(_) + | Reason::NoncesDontMatchComm + | Reason::DeriveInterpolationValue + | Reason::SameSignerTwice + | Reason::SignerNotInList => None, + Reason::Bug(bug) => Some(bug), + } + } +} + +impl std::error::Error for Bug { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Bug::RetrieveOwnShareId | Bug::OwnBindingFactor => None, + } + } +} + +impl From for SigningError { + fn from(err: Reason) -> Self { + SigningError(err) + } +} +impl From for SigningError { + fn from(err: Bug) -> Self { + SigningError(Reason::Bug(err)) + } +} diff --git a/givre/src/signing/utils.rs b/givre/src/signing/utils.rs new file mode 100644 index 0000000..8c654cf --- /dev/null +++ b/givre/src/signing/utils.rs @@ -0,0 +1,99 @@ +use digest::{FixedOutput, Update}; +use generic_ec::{Curve, NonZero, Point, Scalar}; + +use crate::ciphersuite::Ciphersuite; + +use super::round1::PublicCommitments; + +/// Encodes a list of commitments as described in [Section 4.3] +/// +/// [Section 4.3]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-15.html#name-list-operations +/// +/// Differences compared to the draft: +/// * Instead of returning encoded data as a string, it rather feeds it directly into the hash +pub fn encode_group_commitment_list( + mut output: C::Digest, + commitment_list: &[(NonZero>, PublicCommitments)], +) -> C::Digest { + for ( + i, + PublicCommitments { + hiding_comm, + binding_comm, + }, + ) in commitment_list + { + output.update(C::serialize_scalar(&i).as_ref()); + output.update(C::serialize_point(&hiding_comm).as_ref()); + output.update(C::serialize_point(&binding_comm).as_ref()); + } + output +} + +/// Computes binding factors as described in [Section 4.4] +/// +/// [Section 4.4]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-15.html#name-binding-factors-computation +/// +/// As stated in the draft, `commitments_list` must be sorted in ascending order by identifier. The implementation +/// makes debug assertation to make sure it holds. +/// +/// Although it's not mentioned in the draft, but note that output list is sorted by signer ID. +pub fn compute_binding_factors( + shared_pk: Point, + commitment_list: &[(NonZero>, PublicCommitments)], + msg: &[u8], +) -> Vec<(NonZero>, Scalar)> { + debug_assert!( + is_sorted_by_key(commitment_list, |(i, _)| i), + "commitments list must be sorted" + ); + + let pk_bytes = C::serialize_point(&shared_pk); + let msg_hash = C::h4().chain(msg).finalize_fixed(); + let encoded_commitment_hash = + encode_group_commitment_list::(C::h5(), commitment_list).finalize_fixed(); + + let mut binding_factor_list = Vec::with_capacity(commitment_list.len()); + for (i, _) in commitment_list { + let binding_factor = C::h1(&[ + pk_bytes.as_ref(), + &msg_hash, + &encoded_commitment_hash, + C::serialize_scalar(&i).as_ref(), + ]); + binding_factor_list.push((*i, binding_factor)) + } + + binding_factor_list +} + +/// Computes a group commitment as described in [Section 4.5] +/// +/// [Section 4.5]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-15.html#name-group-commitment-computatio +/// +/// Differences compared to the draft: +/// * Takes a single list of signers (represented as iterator) that contains their id, +/// public commitments to the nonces, and the binding factor, where as the draft +/// suggests to take two lists with the same information +pub fn compute_group_commitment( + signers_list: impl IntoIterator>, PublicCommitments, Scalar)>, +) -> Point { + signers_list + .into_iter() + .fold(Point::zero(), |acc, (_i, comm, binding_factor)| { + let binding_nonce = comm.binding_comm * binding_factor; + acc + comm.hiding_comm + binding_nonce + }) +} + +pub fn is_sorted(slice: &[T]) -> bool { + is_sorted_by_key(slice, |x| x) +} + +pub fn is_sorted_by_key(slice: &[T], f: F) -> bool +where + F: Fn(&T) -> &B, + B: Ord, +{ + slice.windows(2).all(|win| f(&win[0]) <= f(&win[1])) +} diff --git a/katex-header.html b/katex-header.html new file mode 100644 index 0000000..9d8f376 --- /dev/null +++ b/katex-header.html @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + diff --git a/tests/Cargo.toml b/tests/Cargo.toml new file mode 100644 index 0000000..d0ffb58 --- /dev/null +++ b/tests/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "givre-tests" +version = "0.1.0" +edition = "2021" +publish = false + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +givre = { path = "../givre", features = ["all-curves", "cggmp21-keygen", "spof", "hd-wallets", "full-signing"] } + +generic-tests = "0.1" +test-case = "3.3" +rand_dev = "0.1" + +rand = "0.8" +rand_core = "0.6" + +hex-literal = "0.4" + +tokio = { version = "1", features = ["macros", "rt"]} +round-based = { version = "0.2", features = ["dev"] } +futures = "0.3" diff --git a/tests/src/lib.rs b/tests/src/lib.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/tests/src/lib.rs @@ -0,0 +1 @@ + diff --git a/tests/tests/it/interactive.rs b/tests/tests/it/interactive.rs new file mode 100644 index 0000000..90855bd --- /dev/null +++ b/tests/tests/it/interactive.rs @@ -0,0 +1,97 @@ +#[generic_tests::define(attrs(test_case::case, tokio::test))] +mod generic { + use std::iter; + + use givre::Ciphersuite; + use rand::{seq::SliceRandom, Rng}; + + #[test_case::case(Some(2), 3; "t2n3")] + #[test_case::case(Some(3), 3; "t3n3")] + #[test_case::case(None, 3; "n3")] + #[test_case::case(Some(3), 5; "t3n5")] + #[test_case::case(Some(5), 5; "t5n5")] + #[test_case::case(None, 5; "n5")] + #[tokio::test] + async fn keygen_sign(t: Option, n: u16) { + let mut rng = rand_dev::DevRng::new(); + + // --- Keygen + let eid: [u8; 32] = rng.gen(); + let eid = givre::keygen::ExecutionId::new(&eid); + + let mut simulation_threshold = round_based::simulation::Simulation::new(); + let mut simulation_nonthreshold = round_based::simulation::Simulation::new(); + let keygen_executions = (0..n) + .zip(iter::repeat_with(|| { + ( + rng.fork(), + simulation_threshold.add_party(), + simulation_nonthreshold.add_party(), + ) + })) + .map( + move |(j, (mut rng, party_threshold, party_nonthreshold))| async move { + if let Some(t) = t { + givre::keygen::(eid, j, n) + .set_threshold(t) + .start(&mut rng, party_threshold) + .await + } else { + givre::keygen(eid, j, n) + .start(&mut rng, party_nonthreshold) + .await + } + }, + ); + + let key_shares: Vec> = + futures::future::try_join_all(keygen_executions) + .await + .unwrap(); + let key_shares = key_shares.as_slice(); + let key_info: &givre::KeyInfo<_> = key_shares[0].as_ref(); + + // --- Signing + + // message to be signed + let msg: [u8; 29] = rng.gen(); + + // Choose `t` signers to do signing + let t = t.unwrap_or(n); + let signers = (0..n).collect::>(); + let signers = signers + .choose_multiple(&mut rng, t.into()) + .copied() + .collect::>(); + let signers = signers.as_slice(); + + let mut simulation = round_based::simulation::Simulation::new(); + let signing_executions = (0..t) + .zip(signers) + .zip(iter::repeat_with(|| (rng.fork(), simulation.add_party()))) + .map(|((j, &index_at_keygen), (mut rng, party))| async move { + givre::signing::(j, &key_shares[usize::from(index_at_keygen)], &signers, &msg) + .sign(party, &mut rng) + .await + }); + + let sigs: Vec> = + futures::future::try_join_all(signing_executions) + .await + .unwrap(); + + sigs[0] + .verify::(&key_info.shared_public_key, &msg) + .unwrap(); + + for sig in &sigs[1..] { + assert_eq!(sigs[0].r, sig.r); + assert_eq!(sigs[0].z, sig.z); + } + } + + #[instantiate_tests()] + mod secp256k1 {} + #[instantiate_tests()] + mod ed25519 {} +} diff --git a/tests/tests/it/main.rs b/tests/tests/it/main.rs new file mode 100644 index 0000000..29d606f --- /dev/null +++ b/tests/tests/it/main.rs @@ -0,0 +1,77 @@ +mod interactive; +mod test_vectors; + +#[generic_tests::define(attrs(test_case::case))] +mod generic { + use givre::Ciphersuite; + use rand::{seq::SliceRandom, Rng}; + + #[test_case::case(Some(2), 3; "t2n3")] + #[test_case::case(Some(3), 3; "t3n3")] + #[test_case::case(None, 3; "n3")] + #[test_case::case(Some(3), 5; "t3n5")] + #[test_case::case(Some(5), 5; "t5n5")] + #[test_case::case(None, 5; "n5")] + fn sign(t: Option, n: u16) { + let mut rng = rand_dev::DevRng::new(); + + // Emulate keygen via trusted dealer + let key_shares = givre::trusted_dealer::builder::(n) + .set_threshold(t) + .generate_shares(&mut rng) + .unwrap(); + let key_info: &givre::key_share::KeyInfo<_> = key_shares[0].as_ref(); + + // List of indexes of signers who co-hold the key + let key_holders = (0..n).collect::>(); + let t = t.unwrap_or(n); + + // Choose `t` signers to perform signing + let signers = key_holders + .choose_multiple(&mut rng, t.into()) + .copied() + .collect::>(); + + // Round 1. Each signer commits + let mut secret_nonces = vec![]; + let mut public_commitments = vec![]; + + for &j in &signers { + let (nonces, commitments) = + givre::signing::round1::commit::(&mut rng, &key_shares[usize::from(j)]); + + secret_nonces.push(nonces); + public_commitments.push((j, commitments)); + } + + // Round 2. Each signer signs a message + let message: [u8; 28] = rng.gen(); + + let partial_sigs = public_commitments + .iter() + .zip(secret_nonces) + .map(|(&(j, comm), secret_nonces)| { + let sig_share = givre::signing::round2::sign::( + &key_shares[usize::from(j)], + secret_nonces, + &message, + &public_commitments, + )?; + Ok::<_, givre::signing::round2::SigningError>((j, comm, sig_share)) + }) + .collect::, _>>() + .expect("signing failed"); + + // Round 3. Aggregate sig shares + let sig = givre::signing::aggregate::aggregate::(&key_info, &partial_sigs, &message) + .expect("aggregation failed"); + + sig.verify::(&key_info.shared_public_key, &message) + .expect("invalid signature"); + } + + #[instantiate_tests()] + mod secp256k1 {} + #[instantiate_tests()] + mod ed25519 {} +} diff --git a/tests/tests/it/test_vectors.rs b/tests/tests/it/test_vectors.rs new file mode 100644 index 0000000..8b83381 --- /dev/null +++ b/tests/tests/it/test_vectors.rs @@ -0,0 +1,312 @@ +//! Test vectors can be found in [Appendix F] of the draft +//! +//! [Appendix F]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-15.html#name-test-vectors +use givre::{ + ciphersuite::Ciphersuite, + generic_ec::{NonZero, Point, Scalar}, + key_share::{KeyInfo, Validate}, + signing::round1::{PublicCommitments, SecretNonces}, +}; +use hex_literal::hex; +use rand::{CryptoRng, RngCore}; + +struct TestVector { + public_key: &'static [u8], + secret_key: &'static [u8], + + shares: [&'static [u8]; N], + + signers: [u16; T], + commit_randomness: [&'static [u8]; T], + expected_nonces: [[&'static [u8]; 2]; T], + expected_commitments: [[&'static [u8]; 2]; T], + + msg: &'static [u8], + + expected_sig_shares: [&'static [u8]; T], + + expected_sig: [&'static [u8]; 2], +} + +impl TestVector { + fn carry_out(&self) { + let (t, n): (u16, u16) = (T.try_into().unwrap(), N.try_into().unwrap()); + + let public_key = C::deserialize_point(self.public_key).unwrap(); + { + let secret_key = C::deserialize_secret_scalar(self.secret_key).unwrap(); + assert_eq!(Point::generator() * &secret_key, public_key); + } + + let shares = self + .shares + .map(|share| C::deserialize_secret_scalar(share).unwrap()); + let public_shares = shares + .clone() + .map(|share| Point::generator() * share) + .to_vec(); + + let share_preimages = (1..=n) + .map(|j| NonZero::from_scalar(Scalar::::from(j))) + .collect::>>() + .unwrap(); + + let vss_setup = givre::key_share::VssSetup { + min_signers: t, + I: share_preimages, + }; + + let key_shares = (0..n) + .zip(shares) + .map(|(i, share)| { + givre::key_share::DirtyKeyShare { + i, + key_info: givre::key_share::DirtyKeyInfo { + curve: Default::default(), + shared_public_key: public_key, + public_shares: public_shares.clone(), + vss_setup: Some(vss_setup.clone()), + chain_code: None, + }, + x: share, + } + .validate() + .unwrap() + }) + .collect::>(); + let key_info: &KeyInfo<_> = key_shares[0].as_ref(); + + // --- Round 1 + let (nonces, commitments): (Vec<_>, Vec<_>) = self + .signers + .into_iter() + .zip(self.commit_randomness) + .map(|(j, randomness)| { + givre::signing::round1::commit::( + &mut mocked_randomness(randomness), + &key_shares[usize::from(j)], + ) + }) + .unzip(); + + // check that nonces match the vector + { + let expected_nonces = + self.expected_nonces + .into_iter() + .map(|[hiding_nonce, binding_nonce]| SecretNonces { + hiding_nonce: C::deserialize_secret_scalar(hiding_nonce).unwrap(), + binding_nonce: C::deserialize_secret_scalar(binding_nonce).unwrap(), + }); + for (nonce, expected) in nonces.iter().zip(expected_nonces) { + assert_eq!(nonce.hiding_nonce.as_ref(), expected.hiding_nonce.as_ref()); + assert_eq!( + nonce.binding_nonce.as_ref(), + expected.binding_nonce.as_ref() + ); + } + } + // check that commitments match the vector + { + let expected_commitments = + self.expected_commitments + .into_iter() + .map(|[hiding_comm, binding_comm]| PublicCommitments { + hiding_comm: C::deserialize_point(hiding_comm).unwrap(), + binding_comm: C::deserialize_point(binding_comm).unwrap(), + }); + for (comm, expected) in commitments.iter().zip(expected_commitments) { + assert_eq!(*comm, expected); + } + } + + // --- Round 2 + let commitments_list = self + .signers + .into_iter() + .zip(commitments.iter().copied()) + .collect::>(); + let sig_shares = self + .signers + .into_iter() + .zip(nonces) + .zip(commitments) + .map(|((j, nonces), commitment)| { + let sig_share = givre::signing::round2::sign::( + &key_shares[usize::from(j)], + nonces, + self.msg, + &commitments_list, + ) + .unwrap(); + (j, commitment, sig_share) + }) + .collect::>(); + + // check that sig shares match the vector + { + let expected = self + .expected_sig_shares + .into_iter() + .map(|sig_share| C::deserialize_scalar(sig_share).unwrap()); + for ((_, _, sig_share), expected) in sig_shares.iter().zip(expected) { + assert_eq!(sig_share.0, expected) + } + } + + // --- Aggregate + let sig = + givre::signing::aggregate::aggregate::(&key_info, &sig_shares, self.msg).unwrap(); + + { + let r = C::deserialize_point(&self.expected_sig[0]).unwrap(); + let z = C::deserialize_scalar(&self.expected_sig[1]).unwrap(); + + assert_eq!(sig.r, r); + assert_eq!(sig.z, z); + } + } +} + +#[test] +fn secp256k1() { + TestVector { + public_key: &hex!("02f37c34b66ced1fb51c34a90bdae006901f10625cc06c4f64663b0eae87d87b4f"), + secret_key: &hex!("0d004150d27c3bf2a42f312683d35fac7394b1e9e318249c1bfe7f0795a83114"), + + shares: [ + &hex!("08f89ffe80ac94dcb920c26f3f46140bfc7f95b493f8310f5fc1ea2b01f4254c"), + &hex!("04f0feac2edcedc6ce1253b7fab8c86b856a797f44d83d82a385554e6e401984"), + &hex!("00e95d59dd0d46b0e303e500b62b7ccb0e555d49f5b849f5e748c071da8c0dbc"), + ], + + signers: [0, 2], + commit_randomness: [ + &hex!( + "7ea5ed09af19f6ff21040c07ec2d2adbd35b759da5a401d4c99dd26b82391cb2 + 47acab018f116020c10cb9b9abdc7ac10aae1b48ca6e36dc15acb6ec9be5cdc5" + ), + &hex!( + "e6cc56ccbd0502b3f6f831d91e2ebd01c4de0479e0191b66895a4ffd9b68d544 + 7203d55eb82a5ca0d7d83674541ab55f6e76f1b85391d2c13706a89a064fd5b9" + ), + ], + expected_nonces: [ + [ + &hex!("841d3a6450d7580b4da83c8e618414d0f024391f2aeb511d7579224420aa81f0"), + &hex!("8d2624f532af631377f33cf44b5ac5f849067cae2eacb88680a31e77c79b5a80"), + ], + [ + &hex!("2b19b13f193f4ce83a399362a90cdc1e0ddcd83e57089a7af0bdca71d47869b2"), + &hex!("7a443bde83dc63ef52dda354005225ba0e553243402a4705ce28ffaafe0f5b98"), + ], + ], + expected_commitments: [ + [ + &hex!("03c699af97d26bb4d3f05232ec5e1938c12f1e6ae97643c8f8f11c9820303f1904"), + &hex!("02fa2aaccd51b948c9dc1a325d77226e98a5a3fe65fe9ba213761a60123040a45e"), + ], + [ + &hex!("03077507ba327fc074d2793955ef3410ee3f03b82b4cdc2370f71d865beb926ef6"), + &hex!("02ad53031ddfbbacfc5fbda3d3b0c2445c8e3e99cbc4ca2db2aa283fa68525b135"), + ], + ], + + msg: &hex!("74657374"), + + expected_sig_shares: [ + &hex!("c4fce1775a1e141fb579944166eab0d65eefe7b98d480a569bbbfcb14f91c197"), + &hex!("0160fd0d388932f4826d2ebcd6b9eaba734f7c71cf25b4279a4ca2581e47b18d"), + ], + + expected_sig: [ + &hex!("0205b6d04d3774c8929413e3c76024d54149c372d57aae62574ed74319b5ea14d0"), + &hex!("c65dde8492a7471437e6c2fe3da49b90d23f642b5c6dbe7e36089f096dd97324"), + ], + } + .carry_out::() +} + +#[test] +fn ed25519() { + TestVector { + public_key: &hex!("15d21ccd7ee42959562fc8aa63224c8851fb3ec85a3faf66040d380fb9738673"), + secret_key: &hex!("7b1c33d3f5291d85de664833beb1ad469f7fb6025a0ec78b3a790c6e13a98304"), + + shares: [ + &hex!("929dcc590407aae7d388761cddb0c0db6f5627aea8e217f4a033f2ec83d93509"), + &hex!("a91e66e012e4364ac9aaa405fcafd370402d9859f7b6685c07eed76bf409e80d"), + &hex!("d3cb090a075eb154e82fdb4b3cb507f110040905468bb9c46da8bdea643a9a02"), + ], + + signers: [0, 2], + commit_randomness: [ + &hex!( + "0fd2e39e111cdc266f6c0f4d0fd45c947761f1f5d3cb583dfcb9bbaf8d4c9fec + 69cd85f631d5f7f2721ed5e40519b1366f340a87c2f6856363dbdcda348a7501" + ), + &hex!( + "86d64a260059e495d0fb4fcc17ea3da7452391baa494d4b00321098ed2a0062f + 13e6b25afb2eba51716a9a7d44130c0dbae0004a9ef8d7b5550c8a0e07c61775" + ), + ], + expected_nonces: [ + [ + &hex!("812d6104142944d5a55924de6d49940956206909f2acaeedecda2b726e630407"), + &hex!("b1110165fc2334149750b28dd813a39244f315cff14d4e89e6142f262ed83301"), + ], + [ + &hex!("c256de65476204095ebdc01bd11dc10e57b36bc96284595b8215222374f99c0e"), + &hex!("243d71944d929063bc51205714ae3c2218bd3451d0214dfb5aeec2a90c35180d"), + ], + ], + expected_commitments: [ + [ + &hex!("b5aa8ab305882a6fc69cbee9327e5a45e54c08af61ae77cb8207be3d2ce13de3"), + &hex!("67e98ab55aa310c3120418e5050c9cf76cf387cb20ac9e4b6fdb6f82a469f932"), + ], + [ + &hex!("cfbdb165bd8aad6eb79deb8d287bcc0ab6658ae57fdcc98ed12c0669e90aec91"), + &hex!("7487bc41a6e712eea2f2af24681b58b1cf1da278ea11fe4e8b78398965f13552"), + ], + ], + + msg: &hex!("74657374"), + + expected_sig_shares: [ + &hex!("001719ab5a53ee1a12095cd088fd149702c0720ce5fd2f29dbecf24b7281b603"), + &hex!("bd86125de990acc5e1f13781d8e32c03a9bbd4c53539bbc106058bfd14326007"), + ], + + expected_sig: [ + &hex!("36282629c383bb820a88b71cae937d41f2f2adfcc3d02e55507e2fb9e2dd3cbe"), + &hex!("bd9d2b0844e49ae0f3fa935161e1419aab7b47d21a37ebeae1f17d4987b3160b"), + ], + } + .carry_out::() +} + +fn mocked_randomness(bytes: &[u8]) -> impl RngCore + CryptoRng + '_ { + struct MockedRng<'b>(&'b [u8]); + impl<'b> RngCore for MockedRng<'b> { + fn fill_bytes(&mut self, dest: &mut [u8]) { + let len = dest.len(); + let (randomness, leftover) = self.0.split_at(len); + dest.copy_from_slice(randomness); + self.0 = leftover; + } + + fn next_u32(&mut self) -> u32 { + rand_core::impls::next_u32_via_fill(self) + } + fn next_u64(&mut self) -> u64 { + rand_core::impls::next_u64_via_fill(self) + } + fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand::Error> { + Ok(self.fill_bytes(dest)) + } + } + impl<'b> CryptoRng for MockedRng<'b> {} + + MockedRng(bytes) +} From 1e61912ae1e86c2020b767348d5236c1de1d3a75 Mon Sep 17 00:00:00 2001 From: Denis Varlakov Date: Thu, 22 Feb 2024 14:26:46 +0100 Subject: [PATCH 2/6] Add `key_share::reconstruct_secret_key` --- Cargo.lock | 4 ++-- README.md | 1 + givre/src/lib.rs | 5 +++++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 70ae07f..0d068a8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -104,7 +104,7 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "cggmp21-keygen" version = "0.1.0" -source = "git+https://github.com/dfns/cggmp21?branch=spof#5ef39c9bcb62d189513e58884145fc34517b4892" +source = "git+https://github.com/dfns/cggmp21?branch=spof#18ac63bfc1849cd01259440ae340e3a71d01ee69" dependencies = [ "digest", "futures", @@ -668,7 +668,7 @@ dependencies = [ [[package]] name = "key-share" version = "0.1.0" -source = "git+https://github.com/dfns/cggmp21?branch=spof#5ef39c9bcb62d189513e58884145fc34517b4892" +source = "git+https://github.com/dfns/cggmp21?branch=spof#18ac63bfc1849cd01259440ae340e3a71d01ee69" dependencies = [ "generic-ec", "generic-ec-zkp", diff --git a/README.md b/README.md index 84920ea..1f0b991 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ This crate provides: We provide API for both manual signing execution (for better flexibility and efficiency) and interactive protocol (for easier usability and fool-proof design), see signing module for details. * Trusted dealer (importing key into TSS) +* reconstruct_secret_key (exporting key from TSS) This crate doesn't support (currently): * Identifiable abort diff --git a/givre/src/lib.rs b/givre/src/lib.rs index 2df499d..898f07b 100644 --- a/givre/src/lib.rs +++ b/givre/src/lib.rs @@ -11,6 +11,7 @@ //! We provide API for both manual signing execution (for better flexibility and efficiency) and interactive protocol //! (for easier usability and fool-proof design), see [mod@signing] module for details. //! * [Trusted dealer](trusted_dealer) (importing key into TSS) +//! * [reconstruct_secret_key](key_share::reconstruct_secret_key) (exporting key from TSS) //! //! This crate doesn't support (currently): //! * Identifiable abort @@ -39,6 +40,10 @@ pub mod key_share { CoreKeyShare as KeyShare, DirtyCoreKeyShare as DirtyKeyShare, DirtyKeyInfo, InvalidCoreShare as InvalidKeyShare, KeyInfo, Validate, VssSetup, }; + + #[cfg(feature = "spof")] + #[doc(inline)] + pub use key_share::reconstruct_secret_key; } /// Distributed Key Generation (DKG) protocol based on CGGMP21 paper From 09e2f871bdce499b33e67f65b63d6076bf975022 Mon Sep 17 00:00:00 2001 From: Denis Varlakov Date: Thu, 29 Feb 2024 12:47:20 +0100 Subject: [PATCH 3/6] Add serde support --- Cargo.lock | 1 + givre/Cargo.toml | 4 ++++ givre/src/signing/aggregate.rs | 6 ++++++ givre/src/signing/round1.rs | 10 ++++++++++ givre/src/signing/round2.rs | 5 +++++ 5 files changed, 26 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 0d068a8..87bae45 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -539,6 +539,7 @@ dependencies = [ "key-share", "rand_core", "round-based", + "serde", "sha2", "static_assertions", ] diff --git a/givre/Cargo.toml b/givre/Cargo.toml index 9df706b..c561048 100644 --- a/givre/Cargo.toml +++ b/givre/Cargo.toml @@ -21,6 +21,8 @@ k256 = { version = "0.13", default-features = false, features = ["hash2curve"], static_assertions = { version = "1.1", optional = true } sha2 = { version = "0.10", default-features = false, optional = true } +serde = { version = "1", default-features = false, features = ["derive"], optional = true } + [dev-dependencies] rand_core = { version = "0.6", default-features = false, features = ["getrandom"] } @@ -28,6 +30,8 @@ rand_core = { version = "0.6", default-features = false, features = ["getrandom" cggmp21-keygen = ["dep:cggmp21-keygen"] full-signing = ["round-based", "futures"] +serde = ["dep:serde", "key-share/serde"] + spof = ["key-share/spof"] # Enabling this flag only makes the library compatible with these dependencies. Otherwise, diff --git a/givre/src/signing/aggregate.rs b/givre/src/signing/aggregate.rs index 5755eb0..301286e 100644 --- a/givre/src/signing/aggregate.rs +++ b/givre/src/signing/aggregate.rs @@ -14,6 +14,12 @@ use crate::{Ciphersuite, SignerIndex}; use super::{round1::PublicCommitments, round2::SigShare, utils}; +#[derive(Debug, Clone, Copy)] +#[cfg_attr( + feature = "serde", + derive(serde::Serialize, serde::Deserialize), + serde(bound = "") +)] /// Schnorr Signature pub struct Signature { /// $R$ component of the signature diff --git a/givre/src/signing/round1.rs b/givre/src/signing/round1.rs index 871c926..a139fdb 100644 --- a/givre/src/signing/round1.rs +++ b/givre/src/signing/round1.rs @@ -13,6 +13,11 @@ use crate::ciphersuite::{AdditionalEntropy, Ciphersuite}; /// Nonces generated by the signer that must be kept secret #[derive(Clone)] +#[cfg_attr( + feature = "serde", + derive(serde::Serialize, serde::Deserialize), + serde(bound = "") +)] pub struct SecretNonces { /// Hiding nonce pub hiding_nonce: SecretScalar, @@ -32,6 +37,11 @@ impl SecretNonces { /// Commitments generated by the signer that need to be shared with other signers #[derive(Debug, PartialEq, Eq, Copy, Clone)] +#[cfg_attr( + feature = "serde", + derive(serde::Serialize, serde::Deserialize), + serde(bound = "") +)] pub struct PublicCommitments { /// Commitment to the hiding nonce pub hiding_comm: Point, diff --git a/givre/src/signing/round2.rs b/givre/src/signing/round2.rs index c087ceb..7fe16da 100644 --- a/givre/src/signing/round2.rs +++ b/givre/src/signing/round2.rs @@ -19,6 +19,11 @@ use super::{ /// Partial signature #[derive(Debug, Copy, Clone)] +#[cfg_attr( + feature = "serde", + derive(serde::Serialize, serde::Deserialize), + serde(bound = "") +)] pub struct SigShare(pub Scalar); /// Issues a partial signature on the `msg` From 65a1f8f6f025d680d9fb93946c2861958f796304 Mon Sep 17 00:00:00 2001 From: Denis Varlakov Date: Thu, 29 Feb 2024 14:06:59 +0100 Subject: [PATCH 4/6] Make sure that ed25519 ciphersuite produces compliant signatures --- Cargo.lock | 61 +++++++++++++++++++++++++++++++++++ tests/Cargo.toml | 2 ++ tests/src/lib.rs | 50 ++++++++++++++++++++++++++++ tests/tests/it/interactive.rs | 4 ++- tests/tests/it/main.rs | 5 ++- 5 files changed, 120 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 87bae45..5bcd2ce 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -65,6 +65,12 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + [[package]] name = "block-buffer" version = "0.10.4" @@ -187,6 +193,7 @@ dependencies = [ "cfg-if", "cpufeatures", "curve25519-dalek-derive", + "digest", "fiat-crypto", "group", "platforms", @@ -273,6 +280,30 @@ dependencies = [ "subtle", ] +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "pkcs8", + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871" +dependencies = [ + "curve25519-dalek", + "ed25519", + "serde", + "sha2", + "subtle", + "zeroize", +] + [[package]] name = "educe" version = "0.4.23" @@ -548,6 +579,7 @@ dependencies = [ name = "givre-tests" version = "0.1.0" dependencies = [ + "ed25519-dalek", "futures", "generic-tests", "givre", @@ -788,6 +820,16 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + [[package]] name = "platforms" version = "3.3.0" @@ -1001,6 +1043,15 @@ dependencies = [ "digest", ] +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "rand_core", +] + [[package]] name = "slab" version = "0.4.9" @@ -1023,6 +1074,16 @@ dependencies = [ "subtle", ] +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + [[package]] name = "static_assertions" version = "1.1.0" diff --git a/tests/Cargo.toml b/tests/Cargo.toml index d0ffb58..11e7ad1 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -21,3 +21,5 @@ hex-literal = "0.4" tokio = { version = "1", features = ["macros", "rt"]} round-based = { version = "0.2", features = ["dev"] } futures = "0.3" + +ed25519 = { package = "ed25519-dalek", version = "2.1" } diff --git a/tests/src/lib.rs b/tests/src/lib.rs index 8b13789..f5383af 100644 --- a/tests/src/lib.rs +++ b/tests/src/lib.rs @@ -1 +1,51 @@ +use givre::{generic_ec::Point, signing::aggregate::Signature, Ciphersuite}; +pub trait ExternalVerifier: Ciphersuite { + type InvalidSig: core::fmt::Debug; + + fn verify_sig( + pk: &Point, + sig: &Signature, + msg: &[u8], + ) -> Result<(), Self::InvalidSig>; +} + +#[derive(Debug)] +pub struct InvalidSignature; + +impl ExternalVerifier for givre::ciphersuite::Ed25519 { + type InvalidSig = ed25519::SignatureError; + + fn verify_sig( + pk: &Point, + sig: &Signature, + msg: &[u8], + ) -> Result<(), ed25519::SignatureError> { + let pk = ed25519::VerifyingKey::from_bytes( + &Self::serialize_point(pk) + .as_bytes() + .try_into() + .expect("wrong size of pk"), + ) + .expect("invalid pk"); + let mut sig_bytes = [0u8; 64]; + sig_bytes[..32].copy_from_slice(&Self::serialize_point(&sig.r)); + sig_bytes[32..].copy_from_slice(&Self::serialize_scalar(&sig.z)); + let sig = ed25519::Signature::from_bytes(&sig_bytes); + + pk.verify_strict(msg, &sig) + } +} + +impl ExternalVerifier for givre::ciphersuite::Secp256k1 { + type InvalidSig = core::convert::Infallible; + + fn verify_sig( + _pk: &Point, + _sig: &Signature, + _msg: &[u8], + ) -> Result<(), Self::InvalidSig> { + // No external verifier for secp256k1 ciphersuite + Ok(()) + } +} diff --git a/tests/tests/it/interactive.rs b/tests/tests/it/interactive.rs index 90855bd..82f72c3 100644 --- a/tests/tests/it/interactive.rs +++ b/tests/tests/it/interactive.rs @@ -3,6 +3,7 @@ mod generic { use std::iter; use givre::Ciphersuite; + use givre_tests::ExternalVerifier; use rand::{seq::SliceRandom, Rng}; #[test_case::case(Some(2), 3; "t2n3")] @@ -12,7 +13,7 @@ mod generic { #[test_case::case(Some(5), 5; "t5n5")] #[test_case::case(None, 5; "n5")] #[tokio::test] - async fn keygen_sign(t: Option, n: u16) { + async fn keygen_sign(t: Option, n: u16) { let mut rng = rand_dev::DevRng::new(); // --- Keygen @@ -83,6 +84,7 @@ mod generic { sigs[0] .verify::(&key_info.shared_public_key, &msg) .unwrap(); + C::verify_sig(&key_info.shared_public_key, &sigs[0], &msg).unwrap(); for sig in &sigs[1..] { assert_eq!(sigs[0].r, sig.r); diff --git a/tests/tests/it/main.rs b/tests/tests/it/main.rs index 29d606f..9415012 100644 --- a/tests/tests/it/main.rs +++ b/tests/tests/it/main.rs @@ -4,6 +4,7 @@ mod test_vectors; #[generic_tests::define(attrs(test_case::case))] mod generic { use givre::Ciphersuite; + use givre_tests::ExternalVerifier; use rand::{seq::SliceRandom, Rng}; #[test_case::case(Some(2), 3; "t2n3")] @@ -12,7 +13,7 @@ mod generic { #[test_case::case(Some(3), 5; "t3n5")] #[test_case::case(Some(5), 5; "t5n5")] #[test_case::case(None, 5; "n5")] - fn sign(t: Option, n: u16) { + fn sign(t: Option, n: u16) { let mut rng = rand_dev::DevRng::new(); // Emulate keygen via trusted dealer @@ -68,6 +69,8 @@ mod generic { sig.verify::(&key_info.shared_public_key, &message) .expect("invalid signature"); + C::verify_sig(&key_info.shared_public_key, &sig, &message) + .expect("external verifier: invalid signature") } #[instantiate_tests()] From 76c6ad6319b348857751e04d672143ac78b40ae7 Mon Sep 17 00:00:00 2001 From: Denis Varlakov Date: Fri, 1 Mar 2024 10:11:45 +0100 Subject: [PATCH 5/6] Update givre/src/signing/full_signing.rs --- givre/src/signing/full_signing.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/givre/src/signing/full_signing.rs b/givre/src/signing/full_signing.rs index 0a10f63..b9b5f7b 100644 --- a/givre/src/signing/full_signing.rs +++ b/givre/src/signing/full_signing.rs @@ -54,7 +54,7 @@ impl<'a, C: Ciphersuite> SigningBuilder<'a, C> { /// Issues signature share /// /// Signer will output a signature share. It'll be more efficient than [generating a full signature](Self::sign), - /// but it requires you to send collect all sig shares in one place and [aggreate](crate::signing::aggregate::aggregate) + /// but it requires you to collect all sig shares in one place and [aggreate](crate::signing::aggregate::aggregate) /// them. pub async fn issue_sig_share( self, From c8142f0013304662e84b40a5e376c9978badf8f2 Mon Sep 17 00:00:00 2001 From: Denis Varlakov Date: Fri, 1 Mar 2024 10:19:42 +0100 Subject: [PATCH 6/6] Prettify usage of `utils::compute_group_commitment` --- givre/src/signing/aggregate.rs | 8 +------- givre/src/signing/round2.rs | 8 +------- givre/src/signing/utils.rs | 18 ++++++++++++------ 3 files changed, 14 insertions(+), 20 deletions(-) diff --git a/givre/src/signing/aggregate.rs b/givre/src/signing/aggregate.rs index 301286e..47f0103 100644 --- a/givre/src/signing/aggregate.rs +++ b/givre/src/signing/aggregate.rs @@ -88,13 +88,7 @@ pub fn aggregate( // --- The Aggregation let binding_factor_list = utils::compute_binding_factors::(key_info.shared_public_key, &comm_list, msg); - let group_commitment = - utils::compute_group_commitment(comm_list.iter().zip(&binding_factor_list).map( - |((j, comm), (_j, factor))| { - debug_assert_eq!(j, _j); - (*j, *comm, *factor) - }, - )); + let group_commitment = utils::compute_group_commitment(&comm_list, &binding_factor_list); let z = signers .iter() .map(|(_j, _comm, sig_share)| sig_share.0) diff --git a/givre/src/signing/round2.rs b/givre/src/signing/round2.rs index 7fe16da..3c86142 100644 --- a/givre/src/signing/round2.rs +++ b/givre/src/signing/round2.rs @@ -101,13 +101,7 @@ pub fn sign( let binding_factor = binding_factor_list.get(i).ok_or(Bug::OwnBindingFactor)?.1; debug_assert_eq!(binding_factor_list[i].0, signer_id); - let group_commitment = - utils::compute_group_commitment(comm_list.iter().zip(&binding_factor_list).map( - |((i, comm), (_i, factor))| { - debug_assert_eq!(i, _i); - (*i, *comm, *factor) - }, - )); + let group_commitment = utils::compute_group_commitment(&comm_list, &binding_factor_list); let signers_list = comm_list.iter().map(|(i, _)| *i).collect::>(); let lambda_i = if key_share.vss_setup.is_some() { diff --git a/givre/src/signing/utils.rs b/givre/src/signing/utils.rs index 8c654cf..cffa0e2 100644 --- a/givre/src/signing/utils.rs +++ b/givre/src/signing/utils.rs @@ -72,13 +72,19 @@ pub fn compute_binding_factors( /// [Section 4.5]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-15.html#name-group-commitment-computatio /// /// Differences compared to the draft: -/// * Takes a single list of signers (represented as iterator) that contains their id, -/// public commitments to the nonces, and the binding factor, where as the draft -/// suggests to take two lists with the same information -pub fn compute_group_commitment( - signers_list: impl IntoIterator>, PublicCommitments, Scalar)>, +/// * Assumes that commitments and binding factors come in the same order, i.e. `commitment_list[i].0 == binding_factor_list[i].0` +/// for all i. Assumtion is enforced via debug assertation. +pub fn compute_group_commitment<'a, E: Curve>( + commitment_list: impl IntoIterator>, PublicCommitments)>, + binding_factor_list: impl IntoIterator>, Scalar)>, ) -> Point { - signers_list + commitment_list + .into_iter() + .zip(binding_factor_list) + .map(|((i, comm), (_i, factor))| { + debug_assert_eq!(i, _i); + (*i, *comm, *factor) + }) .into_iter() .fold(Point::zero(), |acc, (_i, comm, binding_factor)| { let binding_nonce = comm.binding_comm * binding_factor;