diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 00000000..5a35d4fc --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,17 @@ +# For people on MacOS that have installed gmp +# using homebrew this means we don't always have to: +# +# RUSTFLAGS="-L/opt/homebrew/lib" cargo test +# +# To get the curz-kzen library compiling with the system GMP +# library. + +[target.aarch64-apple-darwin] +rustflags = [ + "-L", "/opt/homebrew/lib", +] + +[target.x86_64-apple-darwin] +rustflags = [ + "-L", "/opt/homebrew/lib", +] diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 98c6e1e4..964df296 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -12,6 +12,7 @@ on: jobs: # checks markdown links link-check: + if: ${{ false }} name: links runs-on: ubuntu-latest steps: @@ -36,7 +37,34 @@ jobs: - uses: actions/checkout@v2 - uses: hecrj/setup-rust-action@v1 - - run: cargo check --workspace --verbose --no-default-features + - run: cargo check --workspace --verbose + + # check compilation for wasm32-unknown-unknown + webassembly-check: + name: webassembly + runs-on: ubuntu-latest + steps: + - name: Cancel Previous Runs + uses: styfle/cancel-workflow-action@0.9.1 + with: + access_token: ${{ github.token }} + + - uses: actions/checkout@v2 + - name: Install toolchain + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + + - name: Rust Cache + uses: Swatinem/rust-cache@v1.3.0 + + - name: Run Webassembly check + run: | + cargo check --target wasm32-unknown-unknown \ + --features js \ + --features num-bigint \ + --no-default-features + # ensures proper formatting and clippy lint lint-check: name: lint @@ -48,10 +76,10 @@ jobs: access_token: ${{ github.token }} - uses: actions/checkout@v2 - - name: Install latest nightly + - name: Install toolchain uses: actions-rs/toolchain@v1 with: - toolchain: nightly + toolchain: stable components: rustfmt, clippy - name: Rust Cache @@ -59,5 +87,5 @@ jobs: - name: Run Linters run: | - cargo +nightly fmt --all -- --check - cargo clippy -Zunstable-options -- -D warnings + cargo fmt --all -- --check + cargo clippy -- -D warnings diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 7faef1af..84c3c042 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -37,7 +37,7 @@ jobs: - name: Install Toolchain uses: actions-rs/toolchain@v1 with: - toolchain: nightly-2022-05-15 + toolchain: stable components: rustfmt, clippy target: wasm32-unknown-unknown override: true diff --git a/.gitignore b/.gitignore index eb5a316c..9445061f 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ target +*.bak diff --git a/Cargo.lock b/Cargo.lock index 376d42ca..9ed45b51 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "addr2line" -version = "0.17.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" dependencies = [ "gimli", ] @@ -17,23 +17,33 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "aead" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fc95d1bdb8e6666b2b217308eeeb09f2d6728d104be3e31916cc74d15420331" +dependencies = [ + "generic-array 0.14.7", +] + [[package]] name = "aead" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b613b8e1e3cf911a086f53f03bf286f52fd7a7258e4fa606f0ef220d39d8877" dependencies = [ - "generic-array 0.14.6", + "generic-array 0.14.7", ] [[package]] -name = "aead" -version = "0.5.1" +name = "aes" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c192eb8f11fc081b0fe4259ba5af04217d4e0faddd02417310a927911abd7c8" +checksum = "884391ef1066acaa41e766ba8f596341b96e93ce34f9a43e7d24bf0a0eaf0561" dependencies = [ - "crypto-common", - "generic-array 0.14.6", + "aes-soft", + "aesni", + "cipher 0.2.5", ] [[package]] @@ -49,14 +59,17 @@ dependencies = [ ] [[package]] -name = "aes" -version = "0.8.2" +name = "aes-gcm" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "433cfd6710c9986c576a25ca913c39d66a6474107b406f34f91d4a8923395241" +checksum = "5278b5fabbb9bd46e24aa69b2fdea62c99088e0a950a9be40e3e0101298f88da" dependencies = [ - "cfg-if 1.0.0", - "cipher 0.4.3", - "cpufeatures", + "aead 0.3.2", + "aes 0.6.0", + "cipher 0.2.5", + "ctr 0.6.0", + "ghash 0.3.1", + "subtle", ] [[package]] @@ -74,17 +87,32 @@ dependencies = [ ] [[package]] -name = "aes-gcm" -version = "0.10.1" +name = "aes-soft" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82e1366e0c69c9f927b1fa5ce2c7bf9eafc8f9268c0b9800729e8b267612447c" +checksum = "be14c7498ea50828a38d0e24a765ed2effe92a705885b57d029cd67d45744072" dependencies = [ - "aead 0.5.1", - "aes 0.8.2", - "cipher 0.4.3", - "ctr 0.9.2", - "ghash 0.5.0", - "subtle", + "cipher 0.2.5", + "opaque-debug 0.3.0", +] + +[[package]] +name = "aesni" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea2e11f5e94c2f7d386164cc2aa1f97823fed6f259e486940a71c174dd01b0ce" +dependencies = [ + "cipher 0.2.5", + "opaque-debug 0.3.0", +] + +[[package]] +name = "aho-corasick" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea5d730647d4fadd988536d06fecce94b7b4f2a7efdae548f1cf4b63205518ab" +dependencies = [ + "memchr", ] [[package]] @@ -98,50 +126,169 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.66" +version = "1.0.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" + +[[package]] +name = "async-channel" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" +dependencies = [ + "concurrent-queue", + "event-listener", + "futures-core", +] + +[[package]] +name = "async-executor" +version = "1.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c1da3ae8dabd9c00f453a329dfe1fb28da3c0a72e2478cdcd93171740c20499" +dependencies = [ + "async-lock", + "async-task", + "concurrent-queue", + "fastrand 2.0.1", + "futures-lite", + "slab", +] + +[[package]] +name = "async-global-executor" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1b6f5d7df27bd294849f8eec66ecfc63d11814df7a4f5d74168a2394467b776" +dependencies = [ + "async-channel", + "async-executor", + "async-io", + "async-lock", + "blocking", + "futures-lite", + "once_cell", +] + +[[package]] +name = "async-io" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" +dependencies = [ + "async-lock", + "autocfg 1.1.0", + "cfg-if 1.0.0", + "concurrent-queue", + "futures-lite", + "log", + "parking", + "polling", + "rustix 0.37.24", + "slab", + "socket2 0.4.9", + "waker-fn", +] + +[[package]] +name = "async-lock" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" +dependencies = [ + "event-listener", +] + +[[package]] +name = "async-sse" +version = "5.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e6fa871e4334a622afd6bb2f611635e8083a6f5e2936c0f90f37c7ef9856298" +dependencies = [ + "async-channel", + "futures-lite", + "http-types", + "log", + "memchr", + "pin-project-lite", +] + +[[package]] +name = "async-std" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6" +checksum = "62565bb4402e926b29953c785397c6dc0391b7b446e45008b0049eb43cec6f5d" +dependencies = [ + "async-channel", + "async-global-executor", + "async-io", + "async-lock", + "crossbeam-utils 0.8.16", + "futures-channel", + "futures-core", + "futures-io", + "futures-lite", + "gloo-timers", + "kv-log-macro", + "log", + "memchr", + "once_cell", + "pin-project-lite", + "pin-utils", + "slab", + "wasm-bindgen-futures", +] [[package]] name = "async-stream" -version = "0.3.3" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dad5c83079eae9969be7fadefe640a1c566901f05ff91ab221de4b6f68d9507e" +checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" dependencies = [ "async-stream-impl", "futures-core", + "pin-project-lite", ] [[package]] name = "async-stream-impl" -version = "0.3.3" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10f203db73a71dfa2fb6dd22763990fa26f3d2625a6da2da900d23b87d26be27" +checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.37", ] +[[package]] +name = "async-task" +version = "4.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9441c6b2fe128a7c2bf680a44c34d0df31ce09e5b7e401fcca3faa483dbc921" + [[package]] name = "async-trait" -version = "0.1.58" +version = "0.1.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e805d94e6b5001b651426cf4cd446b1ab5f319d27bab5c644f61de0a804360c" +checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.37", ] [[package]] name = "atomic" -version = "0.5.1" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b88d82667eca772c4aa12f0f1348b3ae643424c8876448f3f7bd5787032e234c" -dependencies = [ - "autocfg 1.1.0", -] +checksum = "c59bdb34bc650a32731b31bd8f0829cc15d24a708ee31559e0bb34f2bc320cba" + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "atty" @@ -149,7 +296,7 @@ version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ - "hermit-abi", + "hermit-abi 0.1.19", "libc", "winapi 0.3.9", ] @@ -171,9 +318,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "backtrace" -version = "0.3.66" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cab84319d616cfb654d03394f38ab7e6f0919e181b1b57e1fd15e7fb4077d9a7" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" dependencies = [ "addr2line", "cc", @@ -184,6 +331,12 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "base-x" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cbbc9d0964165b47557570cce6c952866c2678457aca742aafc9fb771d30270" + [[package]] name = "base16ct" version = "0.1.1" @@ -238,6 +391,12 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitflags" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" + [[package]] name = "bitvec" version = "1.0.1" @@ -269,16 +428,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" dependencies = [ "block-padding 0.2.1", - "generic-array 0.14.6", + "generic-array 0.14.7", ] [[package]] name = "block-buffer" -version = "0.10.3" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ - "generic-array 0.14.6", + "generic-array 0.14.7", ] [[package]] @@ -297,15 +456,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" [[package]] -name = "bstr" -version = "0.2.17" +name = "blocking" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" +checksum = "94c4ef1f913d78636d78d538eec1f18de81e481f44b1be0a81060090530846e1" dependencies = [ - "lazy_static", - "memchr", - "regex-automata", - "serde", + "async-channel", + "async-lock", + "async-task", + "fastrand 2.0.1", + "futures-io", + "futures-lite", + "piper", + "tracing", ] [[package]] @@ -315,7 +478,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e1ad3c6d12dfa84b0f3d33d3aafccd81a8a493c449216cb5246b66c846900f3" dependencies = [ "curv-kzen", - "generic-array 0.14.6", + "generic-array 0.14.7", "itertools 0.7.11", "serde", "serde_derive", @@ -324,9 +487,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.11.1" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" +checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" [[package]] name = "byte-tools" @@ -353,9 +516,15 @@ dependencies = [ [[package]] name = "bytes" -version = "1.2.1" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38" + +[[package]] +name = "bytes" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" [[package]] name = "cast" @@ -365,9 +534,12 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.0.74" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581f5dba903aac52ea3feb5ec4810848460ee833876f1f9b0fdeab1f19091574" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] [[package]] name = "centipede" @@ -377,7 +549,7 @@ checksum = "6c30db9451346358a37cbabe951e4dfdfb34b0c0804df4bfdfc2978cb790a512" dependencies = [ "bulletproof-kzen", "curv-kzen", - "generic-array 0.14.6", + "generic-array 0.14.7", "rayon", "serde", "serde_derive", @@ -408,8 +580,9 @@ dependencies = [ "curv-kzen", "digest 0.9.0", "fs-dkr", - "futures 0.3.25", - "generic-array 0.14.6", + "futures 0.3.28", + "generic-array 0.14.7", + "getrandom 0.2.10", "hex", "kzen-paillier", "multi-party-ecdsa", @@ -417,14 +590,14 @@ dependencies = [ "rand_chacha 0.3.1", "reqwest", "rocket", - "round-based 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "round-based", "secp256k1", "serde", "serde_json", "sha2 0.9.9", "structopt", "thiserror", - "tokio 1.21.2", + "tokio 1.32.0", "uuid 0.8.2", "zeroize", "zk-paillier", @@ -432,21 +605,20 @@ dependencies = [ [[package]] name = "cipher" -version = "0.3.0" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" +checksum = "12f8e7987cbd042a63249497f41aed09f8e65add917ea6566effbc56578d6801" dependencies = [ - "generic-array 0.14.6", + "generic-array 0.14.7", ] [[package]] name = "cipher" -version = "0.4.3" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1873270f8f7942c191139cb8a40fd228da6c3fd2fc376d7e92d47aa14aeb59e" +checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" dependencies = [ - "crypto-common", - "inout", + "generic-array 0.14.7", ] [[package]] @@ -457,7 +629,7 @@ checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" dependencies = [ "ansi_term", "atty", - "bitflags", + "bitflags 1.3.2", "strsim", "textwrap", "unicode-width", @@ -470,14 +642,29 @@ version = "0.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" dependencies = [ - "bitflags", + "bitflags 1.3.2", +] + +[[package]] +name = "concurrent-queue" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f057a694a54f12365049b0958a1685bb52d567f5593b355fbf685838e873d400" +dependencies = [ + "crossbeam-utils 0.8.16", ] [[package]] name = "const-oid" -version = "0.9.2" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "520fbf3c07483f94e3e3ca9d0cfd913d7718ef2483d2cfd91c0d9e91474ab913" +checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f" + +[[package]] +name = "const_fn" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbdcdcb6d86f71c5e97409ad45898af11cbc995b4ee8112d59095a28d376c935" [[package]] name = "cookie" @@ -485,25 +672,35 @@ version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "888604f00b3db336d2af898ec3c1d5d0ddf5e6d462220f2ededc33a87ac4bbd5" dependencies = [ - "time 0.1.44", + "time 0.1.45", "url 1.7.2", ] [[package]] name = "cookie" -version = "0.16.1" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "344adc371239ef32293cb1c4fe519592fcf21206c79c02854320afcdf3ab4917" +checksum = "03a5d7b21829bc7b4bf4754a978a241ae54ea55a40f92bb20216e54096f4b951" dependencies = [ - "aes-gcm 0.10.1", + "aes-gcm 0.8.0", "base64 0.13.1", "hkdf", - "hmac 0.12.1", - "percent-encoding 2.2.0", + "hmac 0.10.1", + "percent-encoding 2.3.0", "rand 0.8.5", - "sha2 0.10.6", - "subtle", - "time 0.3.17", + "sha2 0.9.9", + "time 0.2.27", + "version_check", +] + +[[package]] +name = "cookie" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7efb37c3e1ccb1ff97164ad95ac1606e8ccd35b3fa0a7d99a304c7f4a428cc24" +dependencies = [ + "percent-encoding 2.3.0", + "time 0.3.29", "version_check", ] @@ -520,20 +717,26 @@ dependencies = [ "publicsuffix", "serde", "serde_json", - "time 0.1.44", + "time 0.1.45", "try_from", "url 1.7.2", ] [[package]] name = "cpufeatures" -version = "0.2.5" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" +checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" dependencies = [ "libc", ] +[[package]] +name = "cpuid-bool" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcb25d077389e53838a8158c8e99174c5a9d902dee4904320db714f3c653ffba" + [[package]] name = "crc32fast" version = "1.3.2" @@ -579,16 +782,6 @@ dependencies = [ "itertools 0.10.5", ] -[[package]] -name = "crossbeam-channel" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521" -dependencies = [ - "cfg-if 1.0.0", - "crossbeam-utils 0.8.12", -] - [[package]] name = "crossbeam-deque" version = "0.7.4" @@ -602,13 +795,13 @@ dependencies = [ [[package]] name = "crossbeam-deque" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc" +checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" dependencies = [ "cfg-if 1.0.0", - "crossbeam-epoch 0.9.11", - "crossbeam-utils 0.8.12", + "crossbeam-epoch 0.9.15", + "crossbeam-utils 0.8.16", ] [[package]] @@ -628,14 +821,14 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.11" +version = "0.9.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f916dfc5d356b0ed9dae65f1db9fc9770aa2851d2662b988ccf4fe3516e86348" +checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" dependencies = [ "autocfg 1.1.0", "cfg-if 1.0.0", - "crossbeam-utils 0.8.12", - "memoffset 0.6.5", + "crossbeam-utils 0.8.16", + "memoffset 0.9.0", "scopeguard", ] @@ -663,9 +856,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.12" +version = "0.8.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edbafec5fa1f196ca66527c1b12c2ec4745ca14b50f1ad8f9f6f720b55d11fac" +checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" dependencies = [ "cfg-if 1.0.0", ] @@ -676,7 +869,7 @@ version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef2b4b23cddf68b89b8f8069890e8c270d54e2d5fe1b143820234805e4cb17ef" dependencies = [ - "generic-array 0.14.6", + "generic-array 0.14.7", "rand_core 0.6.4", "subtle", "zeroize", @@ -688,18 +881,27 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ - "generic-array 0.14.6", - "rand_core 0.6.4", + "generic-array 0.14.7", "typenum", ] +[[package]] +name = "crypto-mac" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bff07008ec701e8028e2ceb8f83f0e4274ee62bd2dbdc4fefff2e9a91824081a" +dependencies = [ + "generic-array 0.14.7", + "subtle", +] + [[package]] name = "crypto-mac" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" dependencies = [ - "generic-array 0.14.6", + "generic-array 0.14.7", "subtle", ] @@ -711,13 +913,12 @@ checksum = "e35f15e1a0699dd988fed910dd78fdc6407f44654cd12589c91fa44ea67d9159" [[package]] name = "csv" -version = "1.1.6" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22813a6dc45b335f9bade10bf7271dc477e81113e89eb251a0bc2a8a81c536e1" +checksum = "626ae34994d3d8d668f4269922248239db4ae42d538b14c398b74a52208e8086" dependencies = [ - "bstr", "csv-core", - "itoa 0.4.8", + "itoa 1.0.9", "ryu", "serde", ] @@ -731,6 +932,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "ctr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb4a30d54f7443bf3d6191dcd486aca19e67cb3c49fa7a06a319966346707e7f" +dependencies = [ + "cipher 0.2.5", +] + [[package]] name = "ctr" version = "0.8.0" @@ -741,12 +951,34 @@ dependencies = [ ] [[package]] -name = "ctr" -version = "0.9.2" +name = "curl" +version = "0.4.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +checksum = "509bd11746c7ac09ebd19f0b17782eae80aadee26237658a6b4808afb5c11a22" dependencies = [ - "cipher 0.4.3", + "curl-sys", + "libc", + "openssl-probe", + "openssl-sys", + "schannel", + "socket2 0.4.9", + "winapi 0.3.9", +] + +[[package]] +name = "curl-sys" +version = "0.4.66+curl-8.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70c44a72e830f0e40ad90dda8a6ab6ed6314d39776599a58a2e5e37fbc6db5b9" +dependencies = [ + "cc", + "libc", + "libnghttp2-sys", + "libz-sys", + "openssl-sys", + "pkg-config", + "vcpkg", + "windows-sys", ] [[package]] @@ -759,12 +991,12 @@ dependencies = [ "curve25519-dalek", "digest 0.9.0", "ff-zeroize", - "generic-array 0.14.6", + "generic-array 0.14.7", "hex", "hmac 0.11.0", "lazy_static", "merkle-cbt", - "num-bigint 0.4.3", + "num-bigint 0.4.4", "num-integer", "num-traits", "p256", @@ -807,6 +1039,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "deranged" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2696e8a945f658fd14dc3b87242e6b80cd0f36ff04ea560fa39082368847946" + [[package]] name = "derivative" version = "2.2.0" @@ -815,14 +1053,14 @@ checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] name = "devise" -version = "0.3.1" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50c7580b072f1c8476148f16e0a0d5dedddab787da98d86c5082c5e9ed8ab595" +checksum = "d6eacefd3f541c66fc61433d65e54e0e46e0a029a819a7dbbc7a7b489e8a85f8" dependencies = [ "devise_codegen", "devise_core", @@ -830,9 +1068,9 @@ dependencies = [ [[package]] name = "devise_codegen" -version = "0.3.1" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "123c73e7a6e51b05c75fe1a1b2f4e241399ea5740ed810b0e3e6cacd9db5e7b2" +checksum = "9c8cf4b8dd484ede80fd5c547592c46c3745a617c8af278e2b72bea86b2dfed6" dependencies = [ "devise_core", "quote", @@ -840,15 +1078,15 @@ dependencies = [ [[package]] name = "devise_core" -version = "0.3.1" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841ef46f4787d9097405cac4e70fb8644fc037b526e8c14054247c0263c400d0" +checksum = "35b50dba0afdca80b187392b24f2499a88c336d5a8493e4b4ccfb608708be56a" dependencies = [ - "bitflags", + "bitflags 2.4.0", "proc-macro2", "proc-macro2-diagnostics", "quote", - "syn", + "syn 2.0.37", ] [[package]] @@ -866,20 +1104,26 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" dependencies = [ - "generic-array 0.14.6", + "generic-array 0.14.7", ] [[package]] name = "digest" -version = "0.10.5" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adfbc57365a37acbd2ebf2b64d7e69bb766e2fea813521ed536f5d0520dcf86c" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer 0.10.3", + "block-buffer 0.10.4", "crypto-common", "subtle", ] +[[package]] +name = "discard" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0" + [[package]] name = "dtoa" version = "0.4.8" @@ -900,9 +1144,9 @@ dependencies = [ [[package]] name = "either" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" [[package]] name = "elliptic-curve" @@ -913,9 +1157,9 @@ dependencies = [ "base16ct", "crypto-bigint", "der", - "digest 0.10.5", + "digest 0.10.7", "ff", - "generic-array 0.14.6", + "generic-array 0.14.7", "group", "pkcs8", "rand_core 0.6.4", @@ -926,13 +1170,46 @@ dependencies = [ [[package]] name = "encoding_rs" -version = "0.8.31" +version = "0.8.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9852635589dc9f9ea1b6fe9f05b50ef208c85c834a562f0c6abb1c475736ec2b" +checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" dependencies = [ "cfg-if 1.0.0", ] +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "add4f07d43996f76ef320709726a556a9d4f965d9410d8d0271132d2f8293480" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + [[package]] name = "failure" version = "0.1.8" @@ -951,7 +1228,7 @@ checksum = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", "synstructure", ] @@ -963,13 +1240,19 @@ checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" [[package]] name = "fastrand" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" dependencies = [ "instant", ] +[[package]] +name = "fastrand" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" + [[package]] name = "ff" version = "0.12.1" @@ -1003,14 +1286,14 @@ dependencies = [ "num-traits", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] name = "figment" -version = "0.10.8" +version = "0.10.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e56602b469b2201400dec66a66aec5a9b8761ee97cd1b8c96ab2483fcc16cc9" +checksum = "4547e226f4c9ab860571e070a9034192b3175580ecea38da34fcdb53a018c9a5" dependencies = [ "atomic", "pear", @@ -1022,14 +1305,25 @@ dependencies = [ [[package]] name = "flate2" -version = "1.0.24" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6" +checksum = "c6c98ee8095e9d1dcbf2fcc6d95acccb90d1c81db1e44725c6a984b1dbdfb010" dependencies = [ "crc32fast", "miniz_oxide", ] +[[package]] +name = "flume" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bebadab126f8120d410b677ed95eee4ba6eb7c6dd8e34a5ec88a08050e26132" +dependencies = [ + "futures-core", + "futures-sink", + "spinning_top", +] + [[package]] name = "fnv" version = "1.0.7" @@ -1038,25 +1332,23 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "form_urlencoded" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" +checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" dependencies = [ - "percent-encoding 2.2.0", + "percent-encoding 2.3.0", ] [[package]] name = "fs-dkr" version = "0.1.0" -source = "git+https://github.com/webb-tools/fs-dkr#94da447f2ad32533664d43655bff518722e4fbdd" dependencies = [ "bitvec", "curv-kzen", "kzen-paillier", "multi-party-ecdsa", - "round-based 0.1.7 (git+https://github.com/webb-tools/round-based-protocol)", + "round-based", "serde", - "serde_derive", "sha2 0.9.9", "thiserror", "zeroize", @@ -1075,7 +1367,7 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" dependencies = [ - "bitflags", + "bitflags 1.3.2", "fuchsia-zircon-sys", ] @@ -1099,9 +1391,9 @@ checksum = "3a471a38ef8ed83cd6e40aa59c1ffe17db6855c18e3604d9c4ed8c08ebc28678" [[package]] name = "futures" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38390104763dc37a5145a53c29c63c1290b5d316d6086ec32c293f6736051bb0" +checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" dependencies = [ "futures-channel", "futures-core", @@ -1114,9 +1406,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed" +checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" dependencies = [ "futures-core", "futures-sink", @@ -1124,9 +1416,9 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" +checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" [[package]] name = "futures-cpupool" @@ -1140,9 +1432,9 @@ dependencies = [ [[package]] name = "futures-executor" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7acc85df6714c176ab5edf386123fafe217be88c0840ec11f199441134a074e2" +checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" dependencies = [ "futures-core", "futures-task", @@ -1151,38 +1443,53 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.25" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" + +[[package]] +name = "futures-lite" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb" +checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" +dependencies = [ + "fastrand 1.9.0", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite", + "waker-fn", +] [[package]] name = "futures-macro" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdfb8ce053d86b91919aad980c220b1fb8401a9394410e1c289ed7e66b61835d" +checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.37", ] [[package]] name = "futures-sink" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9" +checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" [[package]] name = "futures-task" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea" +checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" [[package]] name = "futures-util" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6" +checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" dependencies = [ "futures-channel", "futures-core", @@ -1198,9 +1505,9 @@ dependencies = [ [[package]] name = "generator" -version = "0.7.1" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc184cace1cea8335047a471cc1da80f18acf8a76f3bab2028d499e328948ec7" +checksum = "5cc16584ff22b460a382b7feec54b23d2908d858152e5739a120b949293bd74e" dependencies = [ "cc", "libc", @@ -1220,9 +1527,9 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.6" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", @@ -1241,46 +1548,60 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.8" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" dependencies = [ "cfg-if 1.0.0", + "js-sys", "libc", "wasi 0.11.0+wasi-snapshot-preview1", + "wasm-bindgen", ] [[package]] name = "ghash" -version = "0.4.4" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1583cc1656d7839fd3732b80cf4f38850336cdb9b8ded1cd399ca62958de3c99" +checksum = "97304e4cd182c3846f7575ced3890c53012ce534ad9114046b0a9e00bb30a375" dependencies = [ "opaque-debug 0.3.0", - "polyval 0.5.3", + "polyval 0.4.5", ] [[package]] name = "ghash" -version = "0.5.0" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d930750de5717d2dd0b8c0d42c076c0e884c81a73e6cab859bbd2339c71e3e40" +checksum = "1583cc1656d7839fd3732b80cf4f38850336cdb9b8ded1cd399ca62958de3c99" dependencies = [ "opaque-debug 0.3.0", - "polyval 0.6.0", + "polyval 0.5.3", ] [[package]] name = "gimli" -version = "0.26.2" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d" +checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" [[package]] name = "glob" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "gloo-timers" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b995a66bb87bebce9a0f4a95aed01daca4872c050bfcb21653361c03bc35e5c" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] [[package]] name = "group" @@ -1304,7 +1625,7 @@ dependencies = [ "fnv", "futures 0.1.31", "http 0.1.21", - "indexmap", + "indexmap 1.9.3", "log", "slab", "string", @@ -1323,6 +1644,12 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[package]] +name = "hashbrown" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dfda62a12f55daeae5015f81b0baea145391cb4520f86c248fc615d72640d12" + [[package]] name = "heck" version = "0.3.3" @@ -1341,6 +1668,12 @@ dependencies = [ "libc", ] +[[package]] +name = "hermit-abi" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" + [[package]] name = "hex" version = "0.4.3" @@ -1352,11 +1685,22 @@ dependencies = [ [[package]] name = "hkdf" -version = "0.12.3" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "791a029f6b9fc27657f6f188ec6e5e43f6911f6f878e0dc5501396e09809d437" +checksum = "51ab2f639c231793c5f6114bdb9bbe50a7dbbfcd7c7c6bd8475dec2d991e964f" dependencies = [ - "hmac 0.12.1", + "digest 0.9.0", + "hmac 0.10.1", +] + +[[package]] +name = "hmac" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1441c6b1e930e2817404b5046f1f989899143a12bf92de603b69f4e0aee1e15" +dependencies = [ + "crypto-mac 0.10.1", + "digest 0.9.0", ] [[package]] @@ -1365,7 +1709,7 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b" dependencies = [ - "crypto-mac", + "crypto-mac 0.11.1", "digest 0.9.0", ] @@ -1375,7 +1719,7 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "digest 0.10.5", + "digest 0.10.7", ] [[package]] @@ -1391,13 +1735,13 @@ dependencies = [ [[package]] name = "http" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" +checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" dependencies = [ - "bytes 1.2.1", + "bytes 1.5.0", "fnv", - "itoa 1.0.4", + "itoa 1.0.9", ] [[package]] @@ -1418,9 +1762,45 @@ version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" dependencies = [ - "bytes 1.2.1", - "http 0.2.8", + "bytes 1.5.0", + "http 0.2.9", + "pin-project-lite", +] + +[[package]] +name = "http-client" +version = "6.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1947510dc91e2bf586ea5ffb412caad7673264e14bb39fb9078da114a94ce1a5" +dependencies = [ + "async-std", + "async-trait", + "cfg-if 1.0.0", + "http-types", + "isahc", + "log", +] + +[[package]] +name = "http-types" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e9b187a72d63adbfba487f48095306ac823049cb504ee195541e91c7775f5ad" +dependencies = [ + "anyhow", + "async-channel", + "async-std", + "base64 0.13.1", + "cookie 0.14.4", + "futures-lite", + "infer", "pin-project-lite", + "rand 0.7.3", + "serde", + "serde_json", + "serde_qs", + "serde_urlencoded 0.7.1", + "url 2.4.1", ] [[package]] @@ -1431,9 +1811,9 @@ checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" [[package]] name = "httpdate" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hyper" @@ -1453,7 +1833,7 @@ dependencies = [ "log", "net2", "rustc_version", - "time 0.1.44", + "time 0.1.45", "tokio 0.1.22", "tokio-buf", "tokio-executor", @@ -1467,25 +1847,25 @@ dependencies = [ [[package]] name = "hyper" -version = "0.14.23" +version = "0.14.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "034711faac9d2166cb1baf1a2fb0b60b1f277f8492fd72176c17f3515e1abd3c" +checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" dependencies = [ - "bytes 1.2.1", + "bytes 1.5.0", "futures-channel", "futures-core", "futures-util", - "http 0.2.8", + "http 0.2.9", "http-body 0.4.5", "httparse", "httpdate", - "itoa 1.0.4", + "itoa 1.0.9", "pin-project-lite", - "socket2", - "tokio 1.21.2", + "socket2 0.4.9", + "tokio 1.32.0", "tower-service", "tracing", - "want 0.3.0", + "want 0.3.1", ] [[package]] @@ -1512,9 +1892,9 @@ dependencies = [ [[package]] name = "idna" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" dependencies = [ "unicode-bidi", "unicode-normalization", @@ -1522,15 +1902,31 @@ dependencies = [ [[package]] name = "indexmap" -version = "1.9.1" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg 1.1.0", - "hashbrown", + "hashbrown 0.12.3", "serde", ] +[[package]] +name = "indexmap" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8adf3ddd720272c6ea8bf59463c04e0f93d0bbf7c5439b691bca2987e0270897" +dependencies = [ + "equivalent", + "hashbrown 0.14.1", +] + +[[package]] +name = "infer" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64e9829a50b42bb782c1df523f78d332fe371b10c661e78b7a3c34b0198e9fac" + [[package]] name = "inlinable_string" version = "0.1.15" @@ -1538,21 +1934,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8fae54786f62fb2918dcfae3d568594e50eb9b5c25bf04371af6fe7516452fb" [[package]] -name = "inout" -version = "0.1.3" +name = "instant" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" dependencies = [ - "generic-array 0.14.6", + "cfg-if 1.0.0", ] [[package]] -name = "instant" -version = "0.1.12" +name = "io-lifetimes" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" dependencies = [ - "cfg-if 1.0.0", + "hermit-abi 0.3.3", + "libc", + "windows-sys", ] [[package]] @@ -1564,6 +1962,40 @@ dependencies = [ "libc", ] +[[package]] +name = "is-terminal" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" +dependencies = [ + "hermit-abi 0.3.3", + "rustix 0.38.15", + "windows-sys", +] + +[[package]] +name = "isahc" +version = "0.9.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2948a0ce43e2c2ef11d7edf6816508998d99e13badd1150be0914205df9388a" +dependencies = [ + "bytes 0.5.6", + "crossbeam-utils 0.8.16", + "curl", + "curl-sys", + "flume", + "futures-lite", + "http 0.2.9", + "log", + "once_cell", + "slab", + "sluice", + "tracing", + "tracing-futures", + "url 2.4.1", + "waker-fn", +] + [[package]] name = "itertools" version = "0.7.11" @@ -1590,24 +2022,27 @@ checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" [[package]] name = "itoa" -version = "1.0.4" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" [[package]] name = "js-sys" -version = "0.3.60" +version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" +checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" dependencies = [ "wasm-bindgen", ] [[package]] name = "keccak" -version = "0.1.2" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9b7d56ba4a8344d6be9729995e6b06f928af29998cdf79fe390cbf6b1fee838" +checksum = "8f6d5ed8676d904364de097082f4e7d240b571b67989ced0240f08b7f966f940" +dependencies = [ + "cpufeatures", +] [[package]] name = "kernel32-sys" @@ -1619,6 +2054,15 @@ dependencies = [ "winapi-build", ] +[[package]] +name = "kv-log-macro" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" +dependencies = [ + "log", +] + [[package]] name = "kzen-paillier" version = "0.4.3" @@ -1638,9 +2082,43 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.137" +version = "0.2.148" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b" + +[[package]] +name = "libnghttp2-sys" +version = "0.1.8+1.55.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fae956c192dadcdb5dace96db71fa0b827333cce7c7b38dc71446f024d8a340" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "libz-sys" +version = "1.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d97137b25e321a73eef1418d1d5d2eda4d77e12813f8e6dead84bc52c5870a7b" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "linux-raw-sys" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" + +[[package]] +name = "linux-raw-sys" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89" +checksum = "3852614a3bd9ca9804678ba6be5e3b8ce76dfc902cae004e3e0c44051b6e88db" [[package]] name = "lock_api" @@ -1653,9 +2131,9 @@ dependencies = [ [[package]] name = "lock_api" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" +checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" dependencies = [ "autocfg 1.1.0", "scopeguard", @@ -1663,11 +2141,11 @@ dependencies = [ [[package]] name = "log" -version = "0.4.17" +version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" dependencies = [ - "cfg-if 1.0.0", + "value-bag", ] [[package]] @@ -1691,14 +2169,14 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" dependencies = [ - "regex-automata", + "regex-automata 0.1.10", ] [[package]] name = "matches" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" +checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" [[package]] name = "maybe-uninit" @@ -1708,9 +2186,9 @@ checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" [[package]] name = "memchr" -version = "2.5.0" +version = "2.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" [[package]] name = "memoffset" @@ -1723,9 +2201,9 @@ dependencies = [ [[package]] name = "memoffset" -version = "0.6.5" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" dependencies = [ "autocfg 1.1.0", ] @@ -1741,9 +2219,9 @@ dependencies = [ [[package]] name = "mime" -version = "0.3.16" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "mime_guess" @@ -1757,9 +2235,9 @@ dependencies = [ [[package]] name = "miniz_oxide" -version = "0.5.4" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96590ba8f175222643a85693f33d26e9c8a015f599c216509b1a6894af675d34" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" dependencies = [ "adler", ] @@ -1785,12 +2263,11 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.5" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" +checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" dependencies = [ "libc", - "log", "wasi 0.11.0+wasi-snapshot-preview1", "windows-sys", ] @@ -1809,20 +2286,20 @@ dependencies = [ [[package]] name = "multer" -version = "2.0.4" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ed4198ce7a4cbd2a57af78d28c6fbb57d81ac5f1d6ad79ac6c5587419cbdf22" +checksum = "01acbdc23469fd8fe07ab135923371d5f5a422fbf9c522158677c8eb15bc51c2" dependencies = [ - "bytes 1.2.1", + "bytes 1.5.0", "encoding_rs", "futures-util", - "http 0.2.8", + "http 0.2.9", "httparse", "log", "memchr", "mime", "spin", - "tokio 1.21.2", + "tokio 1.32.0", "tokio-util", "version_check", ] @@ -1830,27 +2307,41 @@ dependencies = [ [[package]] name = "multi-party-ecdsa" version = "0.8.2" -source = "git+https://github.com/webb-tools/multi-party-ecdsa#facf26da1bee74f6bf10ebfba58bc8828d74c6a9" dependencies = [ + "aes-gcm 0.9.4", + "anyhow", + "async-sse", "centipede", + "criterion", "curv-kzen", "derivative", + "futures 0.3.28", + "hex", "kzen-paillier", "log", - "round-based 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.8.5", + "reqwest", + "rocket", + "round-based", + "secp256k1", "serde", + "serde_json", "sha2 0.9.9", + "structopt", "subtle", + "surf", "thiserror", + "tokio 1.32.0", + "uuid 0.8.2", "zeroize", "zk-paillier", ] [[package]] name = "net2" -version = "0.2.38" +version = "0.2.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74d0df99cfcd2530b2e694f6e17e7f37b8e26bb23983ac530c0c97408837c631" +checksum = "b13b648036a2339d06de780866fbdfda0dde886de7b3af2ddeba8b14f4ee34ac" dependencies = [ "cfg-if 0.1.10", "libc", @@ -1880,9 +2371,9 @@ dependencies = [ [[package]] name = "num-bigint" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" +checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" dependencies = [ "autocfg 1.1.0", "num-integer", @@ -1902,37 +2393,37 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" dependencies = [ "autocfg 1.1.0", ] [[package]] name = "num_cpus" -version = "1.14.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ - "hermit-abi", + "hermit-abi 0.3.3", "libc", ] [[package]] name = "object" -version = "0.29.0" +version = "0.32.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21158b2c33aa6d4561f1c0a6ea283ca92bc54802a93b263e910746d679a7eb53" +checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.16.0" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] name = "oorandom" @@ -1952,6 +2443,24 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db4d56a4c0478783083cfafcc42493dd4a981d41669da64b4572a2a089b51b1d" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "overload" version = "0.1.1" @@ -1966,7 +2475,7 @@ checksum = "51f44edd08f51e2ade572f141051021c5af22677e42b7dd28a88155151c33594" dependencies = [ "ecdsa", "elliptic-curve", - "sha2 0.10.6", + "sha2 0.10.8", ] [[package]] @@ -1984,6 +2493,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "parking" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e52c774a4c39359c1d1c52e43f73dd91a75a614652c825408eec30c95a9b2067" + [[package]] name = "parking_lot" version = "0.9.0" @@ -1991,7 +2506,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f842b1982eb6c2fe34036a4fbfb06dd185a3f5c8edfaacdf7d1ea10b07de6252" dependencies = [ "lock_api 0.3.4", - "parking_lot_core 0.6.2", + "parking_lot_core 0.6.3", "rustc_version", ] @@ -2001,15 +2516,15 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ - "lock_api 0.4.9", - "parking_lot_core 0.9.4", + "lock_api 0.4.10", + "parking_lot_core 0.9.8", ] [[package]] name = "parking_lot_core" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b876b1b9e7ac6e1a74a6da34d25c42e17e8862aa409cbbbdcfc8d86c6f3bc62b" +checksum = "bda66b810a62be75176a80873726630147a5ca780cd33921e0b5709033e66b0a" dependencies = [ "cfg-if 0.1.10", "cloudabi", @@ -2022,38 +2537,38 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.4" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dc9e0dc2adc1c69d09143aff38d3d30c5c3f0df0dad82e6d25547af174ebec0" +checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" dependencies = [ "cfg-if 1.0.0", "libc", - "redox_syscall 0.2.16", - "smallvec 1.10.0", - "windows-sys", + "redox_syscall 0.3.5", + "smallvec 1.11.1", + "windows-targets", ] [[package]] name = "pear" -version = "0.2.3" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15e44241c5e4c868e3eaa78b7c1848cadd6344ed4f54d029832d32b415a58702" +checksum = "61a386cd715229d399604b50d1361683fe687066f42d56f54be995bc6868f71c" dependencies = [ "inlinable_string", "pear_codegen", - "yansi", + "yansi 1.0.0-rc.1", ] [[package]] name = "pear_codegen" -version = "0.2.3" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82a5ca643c2303ecb740d506539deba189e16f2754040a42901cd8105d0282d0" +checksum = "da9f0f13dac8069c139e8300a6510e3f4143ecf5259c60b116a9b271b4ca0d54" dependencies = [ "proc-macro2", "proc-macro2-diagnostics", "quote", - "syn", + "syn 2.0.37", ] [[package]] @@ -2064,15 +2579,35 @@ checksum = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" [[package]] name = "percent-encoding" -version = "2.2.0" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" + +[[package]] +name = "pin-project" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" +checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.37", +] [[package]] name = "pin-project-lite" -version = "0.2.9" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" [[package]] name = "pin-utils" @@ -2080,6 +2615,17 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "piper" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "668d31b1c4eba19242f2088b2bf3316b82ca31082a8335764db4e083db7485d4" +dependencies = [ + "atomic-waker", + "fastrand 2.0.1", + "futures-io", +] + [[package]] name = "pkcs8" version = "0.9.0" @@ -2090,11 +2636,17 @@ dependencies = [ "spki", ] +[[package]] +name = "pkg-config" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" + [[package]] name = "plotters" -version = "0.3.4" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2538b639e642295546c50fcd545198c9d64ee2a38620a628724a3b266d5fbf97" +checksum = "d2c224ba00d7cadd4d5c660deaf2098e5e80e07846537c51f9cfa4be50c1fd45" dependencies = [ "num-traits", "plotters-backend", @@ -2105,41 +2657,56 @@ dependencies = [ [[package]] name = "plotters-backend" -version = "0.3.4" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "193228616381fecdc1224c62e96946dfbc73ff4384fba576e052ff8c1bea8142" +checksum = "9e76628b4d3a7581389a35d5b6e2139607ad7c75b17aed325f210aa91f4a9609" [[package]] name = "plotters-svg" -version = "0.3.3" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9a81d2759aae1dae668f783c308bc5c8ebd191ff4184aaa1b37f65a6ae5a56f" +checksum = "38f6d39893cca0701371e3c27294f09797214b86f1fb951b89ade8ec04e2abab" dependencies = [ "plotters-backend", ] [[package]] -name = "polyval" -version = "0.5.3" +name = "polling" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8419d2b623c7c0896ff2d5d96e2cb4ede590fed28fcc34934f4c33c036e620a1" +checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" dependencies = [ + "autocfg 1.1.0", + "bitflags 1.3.2", "cfg-if 1.0.0", - "cpufeatures", + "concurrent-queue", + "libc", + "log", + "pin-project-lite", + "windows-sys", +] + +[[package]] +name = "polyval" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eebcc4aa140b9abd2bc40d9c3f7ccec842679cd79045ac3a7ac698c1a064b7cd" +dependencies = [ + "cpuid-bool", "opaque-debug 0.3.0", - "universal-hash 0.4.1", + "universal-hash", ] [[package]] name = "polyval" -version = "0.6.0" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef234e08c11dfcb2e56f79fd70f6f2eb7f025c0ce2333e82f4f0518ecad30c6" +checksum = "8419d2b623c7c0896ff2d5d96e2cb4ede590fed28fcc34934f4c33c036e620a1" dependencies = [ "cfg-if 1.0.0", "cpufeatures", "opaque-debug 0.3.0", - "universal-hash 0.5.0", + "universal-hash", ] [[package]] @@ -2157,7 +2724,7 @@ dependencies = [ "proc-macro-error-attr", "proc-macro2", "quote", - "syn", + "syn 1.0.109", "version_check", ] @@ -2172,26 +2739,32 @@ dependencies = [ "version_check", ] +[[package]] +name = "proc-macro-hack" +version = "0.5.20+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" + [[package]] name = "proc-macro2" -version = "1.0.47" +version = "1.0.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" +checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328" dependencies = [ "unicode-ident", ] [[package]] name = "proc-macro2-diagnostics" -version = "0.9.1" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bf29726d67464d49fa6224a1d07936a8c08bb3fba727c7493f6cf1616fdaada" +checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.37", "version_check", - "yansi", + "yansi 1.0.0-rc.1", ] [[package]] @@ -2201,14 +2774,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95b4ce31ff0a27d93c8de1849cf58162283752f065a90d508f1105fa6c9a213f" dependencies = [ "idna 0.2.3", - "url 2.3.1", + "url 2.4.1", ] [[package]] name = "quote" -version = "1.0.21" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" dependencies = [ "proc-macro2", ] @@ -2335,7 +2908,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.8", + "getrandom 0.2.10", ] [[package]] @@ -2420,26 +2993,22 @@ dependencies = [ [[package]] name = "rayon" -version = "1.5.3" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd99e5772ead8baa5215278c9b15bf92087709e9c1b2d1f97cdb5a183c933a7d" +checksum = "9c27db03db7734835b3f53954b534c91069375ce6ccaa2e065441e07d9b6cdb1" dependencies = [ - "autocfg 1.1.0", - "crossbeam-deque 0.8.2", "either", "rayon-core", ] [[package]] name = "rayon-core" -version = "1.9.3" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "258bcdb5ac6dad48491bb2992db6b7cf74878b0384908af124823d118c99683f" +checksum = "5ce3fb6ad83f861aac485e76e1985cd109d9a3713802152be56c3b1f0e0658ed" dependencies = [ - "crossbeam-channel", - "crossbeam-deque 0.8.2", - "crossbeam-utils 0.8.12", - "num_cpus", + "crossbeam-deque 0.8.3", + "crossbeam-utils 0.8.16", ] [[package]] @@ -2459,40 +3028,43 @@ checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" [[package]] name = "redox_syscall" -version = "0.2.16" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" dependencies = [ - "bitflags", + "bitflags 1.3.2", ] [[package]] name = "ref-cast" -version = "1.0.13" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53b15debb4f9d60d767cd8ca9ef7abb2452922f3214671ff052defc7f3502c44" +checksum = "acde58d073e9c79da00f2b5b84eed919c8326832648a5b109b3fce1bb1175280" dependencies = [ "ref-cast-impl", ] [[package]] name = "ref-cast-impl" -version = "1.0.13" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abfa8511e9e94fd3de6585a3d3cd00e01ed556dc9814829280af0e8dc72a8f36" +checksum = "7f7473c2cfcf90008193dd0e3e16599455cb601a9fce322b5bb55de799664925" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.37", ] [[package]] name = "regex" -version = "1.7.0" +version = "1.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a" +checksum = "ebee201405406dbf528b8b672104ae6d6d63e6d118cb10e4d51abbc7b58044ff" dependencies = [ - "regex-syntax", + "aho-corasick", + "memchr", + "regex-automata 0.3.9", + "regex-syntax 0.7.5", ] [[package]] @@ -2501,23 +3073,31 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" dependencies = [ - "regex-syntax", + "regex-syntax 0.6.29", +] + +[[package]] +name = "regex-automata" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59b23e92ee4318893fa3fe3e6fb365258efbfe6ac6ab30f090cdcbb7aa37efa9" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.7.5", ] [[package]] name = "regex-syntax" -version = "0.6.28" +version = "0.6.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] -name = "remove_dir_all" -version = "0.5.3" +name = "regex-syntax" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" -dependencies = [ - "winapi 0.3.9", -] +checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" [[package]] name = "reqwest" @@ -2539,8 +3119,8 @@ dependencies = [ "mime_guess", "serde", "serde_json", - "serde_urlencoded", - "time 0.1.44", + "serde_urlencoded 0.5.5", + "time 0.1.45", "tokio 0.1.22", "tokio-executor", "tokio-io", @@ -2564,20 +3144,20 @@ dependencies = [ [[package]] name = "rocket" -version = "0.5.0-rc.2" +version = "0.5.0-rc.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98ead083fce4a405feb349cf09abdf64471c6077f14e0ce59364aa90d4b99317" +checksum = "58734f7401ae5cfd129685b48f61182331745b357b96f2367f01aebaf1cc9cc9" dependencies = [ "async-stream", "async-trait", "atomic", - "atty", "binascii", - "bytes 1.2.1", + "bytes 1.5.0", "either", "figment", - "futures 0.3.25", - "indexmap", + "futures 0.3.28", + "indexmap 1.9.3", + "is-terminal", "log", "memchr", "multer", @@ -2592,83 +3172,69 @@ dependencies = [ "serde_json", "state", "tempfile", - "time 0.3.17", - "tokio 1.21.2", + "time 0.3.29", + "tokio 1.32.0", "tokio-stream", "tokio-util", "ubyte", "version_check", - "yansi", + "yansi 0.5.1", ] [[package]] name = "rocket_codegen" -version = "0.5.0-rc.2" +version = "0.5.0-rc.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6aeb6bb9c61e9cd2c00d70ea267bf36f76a4cc615e5908b349c2f9d93999b47" +checksum = "7093353f14228c744982e409259fb54878ba9563d08214f2d880d59ff2fc508b" dependencies = [ "devise", "glob", - "indexmap", + "indexmap 1.9.3", "proc-macro2", "quote", "rocket_http", - "syn", + "syn 2.0.37", "unicode-xid", ] [[package]] name = "rocket_http" -version = "0.5.0-rc.2" +version = "0.5.0-rc.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ded65d127954de3c12471630bf4b81a2792f065984461e65b91d0fdaafc17a2" +checksum = "936012c99162a03a67f37f9836d5f938f662e26f2717809761a9ac46432090f4" dependencies = [ - "cookie 0.16.1", + "cookie 0.17.0", "either", - "futures 0.3.25", - "http 0.2.8", - "hyper 0.14.23", - "indexmap", + "futures 0.3.28", + "http 0.2.9", + "hyper 0.14.27", + "indexmap 1.9.3", "log", "memchr", "pear", - "percent-encoding 2.2.0", + "percent-encoding 2.3.0", "pin-project-lite", "ref-cast", "serde", - "smallvec 1.10.0", + "smallvec 1.11.1", "stable-pattern", "state", - "time 0.3.17", - "tokio 1.21.2", + "time 0.3.29", + "tokio 1.32.0", "uncased", ] -[[package]] -name = "round-based" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61d7da583ffbf4d938fb9dc60871b51769ff47e9836e323668fe2d791ca2fa06" -dependencies = [ - "async-stream", - "futures 0.3.25", - "log", - "serde", - "thiserror", - "tokio 1.21.2", -] - [[package]] name = "round-based" version = "0.1.7" source = "git+https://github.com/webb-tools/round-based-protocol#959126f9f6edce16d4ee95954091b93e33a83140" dependencies = [ "async-stream", - "futures 0.3.25", + "futures 0.3.28", "log", "serde", "thiserror", - "tokio 1.21.2", + "tokio 1.32.0", ] [[package]] @@ -2684,9 +3250,9 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.21" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" [[package]] name = "rustc_version" @@ -2697,17 +3263,44 @@ dependencies = [ "semver", ] +[[package]] +name = "rustix" +version = "0.37.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4279d76516df406a8bd37e7dff53fd37d1a093f997a3c34a5c21658c126db06d" +dependencies = [ + "bitflags 1.3.2", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys 0.3.8", + "windows-sys", +] + +[[package]] +name = "rustix" +version = "0.38.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f9da0cbd88f9f09e7814e388301c8414c51c62aa6ce1e4b5c551d49d96e531" +dependencies = [ + "bitflags 2.4.0", + "errno", + "libc", + "linux-raw-sys 0.4.8", + "windows-sys", +] + [[package]] name = "rustversion" -version = "1.0.9" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97477e48b4cf8603ad5f7aaf897467cf42ab4218a38ef76fb14c2d6773a6d6a8" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" [[package]] name = "ryu" -version = "1.0.11" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" [[package]] name = "same-file" @@ -2718,6 +3311,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "schannel" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" +dependencies = [ + "windows-sys", +] + [[package]] name = "scoped-tls" version = "1.0.1" @@ -2726,9 +3328,9 @@ checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" [[package]] name = "scopeguard" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "sec1" @@ -2738,7 +3340,7 @@ checksum = "3be24c1842290c45df0a7bf069e0c268a747ad05a192f2fd7dcfdbc1cba40928" dependencies = [ "base16ct", "der", - "generic-array 0.14.6", + "generic-array 0.14.7", "pkcs8", "subtle", "zeroize", @@ -2781,18 +3383,18 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.147" +version = "1.0.188" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965" +checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" dependencies = [ "serde_derive", ] [[package]] name = "serde_bytes" -version = "0.11.7" +version = "0.11.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfc50e8183eeeb6178dcb167ae34a8051d63535023ae38b5d8d12beae193d37b" +checksum = "ab33ec92f677585af6d88c65593ae2375adde54efdbf16d597f2cbc7a6d368ff" dependencies = [ "serde", ] @@ -2809,26 +3411,46 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.147" +version = "1.0.188" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f1d362ca8fc9c3e3a7484440752472d68a6caa98f1ab81d99b5dfe517cec852" +checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.37", ] [[package]] name = "serde_json" -version = "1.0.87" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce777b7b150d76b9cf60d28b55f5847135a003f7d7350c6be7a773508ce7d45" +checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65" dependencies = [ - "itoa 1.0.4", + "itoa 1.0.9", "ryu", "serde", ] +[[package]] +name = "serde_qs" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7715380eec75f029a4ef7de39a9200e0a63823176b759d055b613f5a87df6a6" +dependencies = [ + "percent-encoding 2.3.0", + "serde", + "thiserror", +] + +[[package]] +name = "serde_spanned" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186" +dependencies = [ + "serde", +] + [[package]] name = "serde_urlencoded" version = "0.5.5" @@ -2841,6 +3463,33 @@ dependencies = [ "url 1.7.2", ] +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa 1.0.9", + "ryu", + "serde", +] + +[[package]] +name = "sha1" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1da05c97445caa12d05e848c4a4fcbbea29e748ac28f7e80e9b010392063770" +dependencies = [ + "sha1_smol", +] + +[[package]] +name = "sha1_smol" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012" + [[package]] name = "sha2" version = "0.8.2" @@ -2868,13 +3517,13 @@ dependencies = [ [[package]] name = "sha2" -version = "0.10.6" +version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" dependencies = [ "cfg-if 1.0.0", "cpufeatures", - "digest 0.10.5", + "digest 0.10.7", ] [[package]] @@ -2891,18 +3540,18 @@ dependencies = [ [[package]] name = "sharded-slab" -version = "0.1.4" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +checksum = "c1b21f559e07218024e7e9f90f96f601825397de0e25420135f7f952453fed0b" dependencies = [ "lazy_static", ] [[package]] name = "signal-hook-registry" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" dependencies = [ "libc", ] @@ -2913,19 +3562,30 @@ version = "1.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" dependencies = [ - "digest 0.10.5", + "digest 0.10.7", "rand_core 0.6.4", ] [[package]] name = "slab" -version = "0.4.7" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" dependencies = [ "autocfg 1.1.0", ] +[[package]] +name = "sluice" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d7400c0eff44aa2fcb5e31a5f24ba9716ed90138769e4977a2ba6014ae63eb5" +dependencies = [ + "async-channel", + "futures-core", + "futures-io", +] + [[package]] name = "smallvec" version = "0.6.14" @@ -2937,25 +3597,44 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.10.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" +checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a" [[package]] name = "socket2" -version = "0.4.7" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" +checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" dependencies = [ "libc", "winapi 0.3.9", ] +[[package]] +name = "socket2" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4031e820eb552adee9295814c0ced9e5cf38ddf1e8b7d566d6de8e2538ea989e" +dependencies = [ + "libc", + "windows-sys", +] + [[package]] name = "spin" -version = "0.9.4" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f6002a767bff9e83f8eeecf883ecb8011875a21ae8da43bffb817a57e78cc09" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "spinning_top" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b9eb1a2f4c41445a3a0ff9abc5221c5fcd28e1f13cd7c0397706f9ac938ddb0" +dependencies = [ + "lock_api 0.4.10", +] [[package]] name = "spki" @@ -2976,6 +3655,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "standback" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e113fb6f3de07a243d434a56ec6f186dfd51cb08448239fe7bcae73f87ff28ff" +dependencies = [ + "version_check", +] + [[package]] name = "state" version = "0.5.3" @@ -2985,6 +3673,55 @@ dependencies = [ "loom", ] +[[package]] +name = "stdweb" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d022496b16281348b52d0e30ae99e01a73d737b2f45d38fed4edf79f9325a1d5" +dependencies = [ + "discard", + "rustc_version", + "stdweb-derive", + "stdweb-internal-macros", + "stdweb-internal-runtime", + "wasm-bindgen", +] + +[[package]] +name = "stdweb-derive" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c87a60a40fccc84bef0652345bbbbbe20a605bf5d0ce81719fc476f5c03b50ef" +dependencies = [ + "proc-macro2", + "quote", + "serde", + "serde_derive", + "syn 1.0.109", +] + +[[package]] +name = "stdweb-internal-macros" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58fa5ff6ad0d98d1ffa8cb115892b6e69d67799f6763e162a1c9db421dc22e11" +dependencies = [ + "base-x", + "proc-macro2", + "quote", + "serde", + "serde_derive", + "serde_json", + "sha1", + "syn 1.0.109", +] + +[[package]] +name = "stdweb-internal-runtime" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0" + [[package]] name = "string" version = "0.2.1" @@ -3021,7 +3758,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -3030,11 +3767,45 @@ version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" +[[package]] +name = "surf" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "718b1ae6b50351982dedff021db0def601677f2120938b070eadb10ba4038dd7" +dependencies = [ + "async-std", + "async-trait", + "cfg-if 1.0.0", + "encoding_rs", + "futures-util", + "getrandom 0.2.10", + "http-client", + "http-types", + "log", + "mime_guess", + "once_cell", + "pin-project-lite", + "serde", + "serde_json", + "web-sys", +] + [[package]] name = "syn" -version = "1.0.103" +version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7303ef2c05cd654186cb250d29049a24840ca25d2747c25c0381c8d9e2f582e8" dependencies = [ "proc-macro2", "quote", @@ -3049,7 +3820,7 @@ checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", "unicode-xid", ] @@ -3061,16 +3832,15 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tempfile" -version = "3.3.0" +version = "3.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef" dependencies = [ "cfg-if 1.0.0", - "fastrand", - "libc", - "redox_syscall 0.2.16", - "remove_dir_all", - "winapi 0.3.9", + "fastrand 2.0.1", + "redox_syscall 0.3.5", + "rustix 0.38.15", + "windows-sys", ] [[package]] @@ -3084,38 +3854,39 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.37" +version = "1.0.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" +checksum = "1177e8c6d7ede7afde3585fd2513e611227efd6481bd78d2e82ba1ce16557ed4" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.37" +version = "1.0.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" +checksum = "10712f02019e9288794769fba95cd6847df9874d49d871d062172f9dd41bc4cc" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.37", ] [[package]] name = "thread_local" -version = "1.1.4" +version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" +checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" dependencies = [ + "cfg-if 1.0.0", "once_cell", ] [[package]] name = "time" -version = "0.1.44" +version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" +checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" dependencies = [ "libc", "wasi 0.10.0+wasi-snapshot-preview1", @@ -3124,31 +3895,70 @@ dependencies = [ [[package]] name = "time" -version = "0.3.17" +version = "0.2.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376" +checksum = "4752a97f8eebd6854ff91f1c1824cd6160626ac4bd44287f7f4ea2035a02a242" dependencies = [ - "itoa 1.0.4", + "const_fn", + "libc", + "standback", + "stdweb", + "time-macros 0.1.1", + "version_check", + "winapi 0.3.9", +] + +[[package]] +name = "time" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "426f806f4089c493dcac0d24c29c01e2c38baf8e30f1b716ee37e83d200b18fe" +dependencies = [ + "deranged", + "itoa 1.0.9", "serde", "time-core", - "time-macros", + "time-macros 0.2.15", ] [[package]] name = "time-core" -version = "0.1.0" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.6" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d967f99f534ca7e495c575c62638eebc2898a8c84c119b89e250477bc4ba16b2" +checksum = "957e9c6e26f12cb6d0dd7fc776bb67a706312e7299aed74c8dd5b17ebb27e2f1" +dependencies = [ + "proc-macro-hack", + "time-macros-impl", +] + +[[package]] +name = "time-macros" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20" dependencies = [ "time-core", ] +[[package]] +name = "time-macros-impl" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd3c141a1b43194f3f56a1411225df8646c55781d5f26db825b3d98507eb482f" +dependencies = [ + "proc-macro-hack", + "proc-macro2", + "quote", + "standback", + "syn 1.0.109", +] + [[package]] name = "tinytemplate" version = "1.2.1" @@ -3170,9 +3980,9 @@ dependencies = [ [[package]] name = "tinyvec_macros" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" @@ -3195,21 +4005,20 @@ dependencies = [ [[package]] name = "tokio" -version = "1.21.2" +version = "1.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9e03c497dc955702ba729190dc4aac6f2a0ce97f913e5b1b5912fc5039d9099" +checksum = "17ed6077ed6cd6c74735e21f37eb16dc3935f96878b1fe961074089cc80893f9" dependencies = [ - "autocfg 1.1.0", - "bytes 1.2.1", + "backtrace", + "bytes 1.5.0", "libc", - "memchr", - "mio 0.8.5", + "mio 0.8.8", "num_cpus", "pin-project-lite", "signal-hook-registry", - "socket2", + "socket2 0.5.4", "tokio-macros", - "winapi 0.3.9", + "windows-sys", ] [[package]] @@ -3256,13 +4065,13 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "1.8.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484" +checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.37", ] [[package]] @@ -3286,13 +4095,13 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.11" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d660770404473ccd7bc9f8b28494a811bc18542b915c0855c51e8f419d5223ce" +checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" dependencies = [ "futures-core", "pin-project-lite", - "tokio 1.21.2", + "tokio 1.32.0", ] [[package]] @@ -3350,24 +4159,49 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.4" +version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740" +checksum = "1d68074620f57a0b21594d9735eb2e98ab38b17f80d3fcb189fca266771ca60d" dependencies = [ - "bytes 1.2.1", + "bytes 1.5.0", "futures-core", "futures-sink", "pin-project-lite", - "tokio 1.21.2", + "tokio 1.32.0", ] [[package]] name = "toml" -version = "0.5.9" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd79e69d3b627db300ff956027cc6c3798cef26d22526befdfcd12feeb6d2257" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ + "indexmap 2.0.2", "serde", + "serde_spanned", + "toml_datetime", + "winnow", ] [[package]] @@ -3383,6 +4217,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" dependencies = [ "cfg-if 1.0.0", + "log", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -3390,25 +4225,35 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.23" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" +checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.37", ] [[package]] name = "tracing-core" -version = "0.1.30" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" dependencies = [ "once_cell", "valuable", ] +[[package]] +name = "tracing-futures" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" +dependencies = [ + "pin-project", + "tracing", +] + [[package]] name = "tracing-log" version = "0.1.3" @@ -3422,16 +4267,16 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.16" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6176eae26dd70d0c919749377897b54a9276bd7061339665dd68777926b5a70" +checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77" dependencies = [ "matchers", "nu-ansi-term", "once_cell", "regex", "sharded-slab", - "smallvec 1.10.0", + "smallvec 1.11.1", "thread_local", "tracing", "tracing-core", @@ -3440,9 +4285,9 @@ dependencies = [ [[package]] name = "try-lock" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" +checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" [[package]] name = "try_from" @@ -3455,9 +4300,9 @@ dependencies = [ [[package]] name = "typenum" -version = "1.15.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "ubyte" @@ -3470,9 +4315,9 @@ dependencies = [ [[package]] name = "uncased" -version = "0.9.7" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09b01702b0fd0b3fadcf98e098780badda8742d4f4a7676615cad90e8ac73622" +checksum = "9b9bc53168a4be7402ab86c3aad243a84dd7381d09be0eddc81280c1da95ca68" dependencies = [ "serde", "version_check", @@ -3480,24 +4325,24 @@ dependencies = [ [[package]] name = "unicase" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" +checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" dependencies = [ "version_check", ] [[package]] name = "unicode-bidi" -version = "0.3.8" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" +checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" [[package]] name = "unicode-ident" -version = "1.0.5" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-normalization" @@ -3510,15 +4355,15 @@ dependencies = [ [[package]] name = "unicode-segmentation" -version = "1.10.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fdbf052a0783de01e944a6ce7a8cb939e295b1e7be835a1112c3b9a7f047a5a" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" [[package]] name = "unicode-width" -version = "0.1.10" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" +checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" [[package]] name = "unicode-xid" @@ -3532,17 +4377,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f214e8f697e925001e66ec2c6e37a4ef93f0f78c2eed7814394e10c62025b05" dependencies = [ - "generic-array 0.14.6", - "subtle", -] - -[[package]] -name = "universal-hash" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d3160b73c9a19f7e2939a2fdad446c57c1bbbbf4d919d3213ff1267a580d8b5" -dependencies = [ - "crypto-common", + "generic-array 0.14.7", "subtle", ] @@ -3559,13 +4394,14 @@ dependencies = [ [[package]] name = "url" -version = "2.3.1" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" +checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5" dependencies = [ "form_urlencoded", - "idna 0.3.0", - "percent-encoding 2.2.0", + "idna 0.4.0", + "percent-encoding 2.3.0", + "serde", ] [[package]] @@ -3583,7 +4419,7 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" dependencies = [ - "getrandom 0.2.8", + "getrandom 0.2.10", ] [[package]] @@ -3592,6 +4428,18 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +[[package]] +name = "value-bag" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d92ccd67fb88503048c01b59152a04effd0782d035a83a6d256ce6085f08f4a3" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "vec_map" version = "0.8.2" @@ -3604,14 +4452,19 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "waker-fn" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3c4517f54858c779bbcbf228f4fca63d121bf85fbecb2dc578cdf4a39395690" + [[package]] name = "walkdir" -version = "2.3.2" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" +checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" dependencies = [ "same-file", - "winapi 0.3.9", "winapi-util", ] @@ -3628,11 +4481,10 @@ dependencies = [ [[package]] name = "want" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" dependencies = [ - "log", "try-lock", ] @@ -3656,9 +4508,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.83" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" +checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" dependencies = [ "cfg-if 1.0.0", "wasm-bindgen-macro", @@ -3666,24 +4518,36 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.83" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" +checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn", + "syn 2.0.37", "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03" +dependencies = [ + "cfg-if 1.0.0", + "js-sys", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wasm-bindgen-macro" -version = "0.2.83" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" +checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3691,28 +4555,28 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.83" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" +checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.37", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.83" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" +checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" [[package]] name = "web-sys" -version = "0.3.60" +version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f" +checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" dependencies = [ "js-sys", "wasm-bindgen", @@ -3748,9 +4612,9 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" dependencies = [ "winapi 0.3.9", ] @@ -3763,103 +4627,87 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows" -version = "0.32.0" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbedf6db9096bc2364adce0ae0aa636dcd89f3c3f2cd67947062aaf0ca2a10ec" +checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" dependencies = [ - "windows_aarch64_msvc 0.32.0", - "windows_i686_gnu 0.32.0", - "windows_i686_msvc 0.32.0", - "windows_x86_64_gnu 0.32.0", - "windows_x86_64_msvc 0.32.0", + "windows-targets", ] [[package]] name = "windows-sys" -version = "0.42.0" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc 0.42.0", - "windows_i686_gnu 0.42.0", - "windows_i686_msvc 0.42.0", - "windows_x86_64_gnu 0.42.0", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc 0.42.0", + "windows-targets", ] [[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.0" +name = "windows-targets" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +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_msvc" -version = "0.32.0" +name = "windows_aarch64_gnullvm" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8e92753b1c443191654ec532f14c199742964a061be25d77d7a96f09db20bf5" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_msvc" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" - -[[package]] -name = "windows_i686_gnu" -version = "0.32.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a711c68811799e017b6038e0922cb27a5e2f43a2ddb609fe0b6f3eeda9de615" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_i686_gnu" -version = "0.42.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_msvc" -version = "0.32.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "146c11bb1a02615db74680b32a68e2d61f553cc24c4eb5b4ca10311740e44172" - -[[package]] -name = "windows_i686_msvc" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.32.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c912b12f7454c6620635bbff3450962753834be2a594819bd5e945af18ec64bc" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_x86_64_gnu" -version = "0.42.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnullvm" -version = "0.42.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_msvc" -version = "0.32.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "504a2476202769977a040c6364301a3f65d0cc9e3fb08600b2bda150a0488316" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] -name = "windows_x86_64_msvc" -version = "0.42.0" +name = "winnow" +version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" +checksum = "7c2e3184b9c4e92ad5167ca73039d0c42476302ab603e2fec4487511f38ccefc" +dependencies = [ + "memchr", +] [[package]] name = "winreg" @@ -3882,9 +4730,9 @@ dependencies = [ [[package]] name = "wyz" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30b31594f29d27036c383b53b59ed3476874d518f0efb151b27a4c275141390e" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" dependencies = [ "tap", ] @@ -3895,25 +4743,30 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" +[[package]] +name = "yansi" +version = "1.0.0-rc.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1367295b8f788d371ce2dbc842c7b709c73ee1364d30351dd300ec2203b12377" + [[package]] name = "zeroize" -version = "1.5.7" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c394b5bd0c6f669e7275d9c20aa90ae064cb22e75a1cad54e1b34088034b149f" +checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" dependencies = [ "zeroize_derive", ] [[package]] name = "zeroize_derive" -version = "1.3.2" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f8f187641dad4f680d25c4bfc4225b418165984179f26ca76ec4fb6441d3a17" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn", - "synstructure", + "syn 2.0.37", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 95e88171..689d8b9c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,32 +7,50 @@ authors = [ "Akilesh Tangella ", ] -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[workspace] +members = [ + "multi-party-ecdsa", + "fs-dkr", +] -[dependencies] -bincode = "1.3.3" -serde = { version = "1.0", features = ["derive"] } -zeroize = "1" +[workspace.dependencies] curv-kzen = { version = "0.10", default-features = false } centipede = { version = "0.3.1", default-features = false } zk-paillier = { version = "0.4.4", default-features = false } -fs-dkr = { git = "https://github.com/webb-tools/fs-dkr", default-features = false } -round-based = { version = "0.1.4", features = [] } -thiserror = "1.0.23" sha2 = "0.9" -digest = "0.9" -generic-array = "0.14" -rand_chacha = "0.3.1" -rand = "0.8.5" +serde = { version = "1.0", features = ["derive"] } +zeroize = "1" +round-based = { git = "https://github.com/webb-tools/round-based-protocol", version = "0.1.4", features = ["dev"] } +thiserror = "1.0.23" + +[workspace.dependencies.multi-party-ecdsa] +version = "0.8" +path = "multi-party-ecdsa" +default-features = false -[dependencies.paillier] +[workspace.dependencies.paillier] version = "0.4.3" package = "kzen-paillier" default-features = false -[dependencies.multi-party-ecdsa] -git = "https://github.com/webb-tools/multi-party-ecdsa" -default-features = false +[dependencies] +multi-party-ecdsa.workspace = true +curv-kzen.workspace = true +centipede.workspace = true +zk-paillier.workspace = true +sha2.workspace = true +serde.workspace = true +zeroize.workspace = true +paillier.workspace = true +round-based.workspace = true +thiserror.workspace = true +fs-dkr = { path = "fs-dkr", default-features = false } +bincode = "1.3.3" +digest = "0.9" +generic-array = "0.14" +rand_chacha = "0.3.1" +rand = "0.8.5" +getrandom = { version = "0.2", optional = true } [dev-dependencies] criterion = "0.3" @@ -49,10 +67,8 @@ anyhow = "1" structopt = "0.3" secp256k1 = { version = "0.20", features = ["global-context"]} -thiserror = "1.0.23" -round-based = { version = "0.1.4", features = ["dev"] } - [features] default = ["rust-gmp-kzen"] rust-gmp-kzen = ["fs-dkr/rust-gmp-kzen"] num-bigint = ["fs-dkr/num-bigint"] +js = ["getrandom/js"] diff --git a/fs-dkr/.github/workflows/pull_request.yml b/fs-dkr/.github/workflows/pull_request.yml new file mode 100644 index 00000000..bf45f3a6 --- /dev/null +++ b/fs-dkr/.github/workflows/pull_request.yml @@ -0,0 +1,65 @@ +name: FS-DKR Test Suite + +on: [pull_request, push] + +jobs: + check: + name: Cargo Check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + - uses: actions-rs/cargo@v1 + with: + command: check + + test: + name: Test Suite + runs-on: ${{ matrix.os }} + strategy: + matrix: + # TODO: add windows-latest after solving the gmp linking issue + os: [ macos-latest, ubuntu-latest] + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + - uses: actions-rs/cargo@v1 + with: + command: test + + fmt: + name: Rustfmt + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + - run: rustup component add rustfmt + - uses: actions-rs/cargo@v1 + with: + command: fmt + args: --all -- --check + + clippy_check: + name: Clippy Check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - uses: actions-rs/toolchain@v1 + with: + toolchain: stable + components: clippy + - uses: actions-rs/clippy@master + with: + args: --all-features --all-targets \ No newline at end of file diff --git a/fs-dkr/.gitignore b/fs-dkr/.gitignore new file mode 100644 index 00000000..088ba6ba --- /dev/null +++ b/fs-dkr/.gitignore @@ -0,0 +1,10 @@ +# Generated by Cargo +# will have compiled files and executables +/target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk diff --git a/fs-dkr/Cargo.toml b/fs-dkr/Cargo.toml new file mode 100644 index 00000000..88fd1e0c --- /dev/null +++ b/fs-dkr/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "fs-dkr" +version = "0.1.0" +authors = [ + "Omer Shlomovits ", + "Tudor Cebere ", + "Drew Stone ", +] +edition = "2021" + +[dependencies] +curv-kzen.workspace = true +zk-paillier.workspace = true +sha2.workspace = true +paillier.workspace = true +round-based.workspace = true +serde.workspace = true +zeroize.workspace = true +thiserror.workspace = true + +[dependencies.multi-party-ecdsa] +workspace = true +default-features = false + +[dependencies.bitvec] +version = "1" +default-features = false +features = ["atomic", "alloc"] + +[features] +default = ["rust-gmp-kzen"] +rust-gmp-kzen = ["curv-kzen/rust-gmp-kzen"] +num-bigint = ["curv-kzen/num-bigint"] \ No newline at end of file diff --git a/fs-dkr/LICENSE b/fs-dkr/LICENSE new file mode 100644 index 00000000..f288702d --- /dev/null +++ b/fs-dkr/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/fs-dkr/README.md b/fs-dkr/README.md new file mode 100644 index 00000000..68d3b260 --- /dev/null +++ b/fs-dkr/README.md @@ -0,0 +1,103 @@ +# FS-DKR: One Round Distributed Key Rotation + + +## Intro +In this note we aim to re-purpose the [Fouque-Stern](https://hal.inria.fr/inria-00565274/document) Distributed Key Generation (DKG) to support a secure Distributed Key Refresh (DKR). As we claim, FS-DKR is well suited for rotation of [threshold ECDSA](https://eprint.iacr.org/2020/540.pdf) keys. + + + +## Background +The FS-DKG protocol is a one round DKG based on Publicly Verifiable Secret Sharing (PVSS) and the [Paillier cryptosystem](https://en.wikipedia.org/wiki/Paillier_cryptosystem). There are two major security shortcomings to FS-DKG: +1. It introduces a factoring assumptions (DCRA) +2. it is insecure against rushing adversary + +Rushing adversary is a common assumption in Multiparty Computation (MPC). In FS-DKG, an adversary waiting to receive messages from all other parties will be able to decide on the final public key. In the worst case it can lead to a rogue-key attack, giving full control of the secret key to the attacker. This is the main reason, in our opinion, why FS-DKG, altough with prominent features, was over-looked for the past 20 years. +in this write-up we show how by adjusting FS-DKG to key rotation for threshold ecdsa the above shortcomings are avoided. + +## Our Model +We use standard proactive security assumptions. The protocol will be run by $n$ parties. We assume honest majority, that is, number of corruptions is $t<=n/2$. The adversary is malicious, and rushing. +For communication, the parties have access to a broadcast channel (can be implemented via a bulletin board). +For threshold ECDSA, we focus on [GG20](https://eprint.iacr.org/2020/540.pdf) protocol, currently considered state of the art and most widely deployed threshold ecdsa scheme (e.g. [multi-party-ecdsa](https://github.com/ZenGo-X/multi-party-ecdsa), [tss-lib](https://github.com/binance-chain/tss-lib)). + +## How To Use +### Refresh a Key +Each party calls `RefreshMessage::distribute(key)` on their `LocalKey` and broadcasts the `RefreshMessage` while saving their new `DecryptionKey`.
+After recieving all the refresh messages each party calls `RefreshMessage::collect(..)` with a vector of all the refresh messages, a mutable reference to their own key, and their new `DecryptionKey`, This will validate all the refresh messages, and if all the proofs are correct it will update the local key to contain the new decryption keys of all the parties. + +Example: +```rust +// All parties should run this +let mut party_i_key: LocalKey<_>; +let (party_i_refresh_message, party_i_new_decryption_key) = RefreshMessage::distribute(party_i_key); +broadcast(party_i_refresh_message); +let vec_refresh_messages = recv_from_broadcast(); +RefreshMessage::collect(&vec_refresh_messages, &mut party_i_key, party_i_new_decryption_key, &[])?; +``` + +### Replacing a party +Each party that wants to join first generates a `JoinMessage` via `JoinMessage::distribute()` and broadcasts it to the current parties.
+The existing parties choose the index(who are they replacing) for the joining party. +Note that this part is delicate and needs to happen outside of the library because it requires some kind of mutual agreement, and you cannot trust the new party to communicate which party are they replacing.
+After agreeing on the index each party modifies the join message to contain the index `join_message.party_index = Some(index)`.
+Each existing party calls `RefreshMessage::replace(join_message, local_key)` with the join message and its own local key, this returns a refresh message and a new decryption key, just like in a Key Refresh, and they all broadcast the `RefreshMessage`.
+Each existing party recieves all the broadcasted refresh messages and calls `RefreshMessage::collect(..)` with a vector of all the refresh messages, a mutable reference to their own key, the new `DecryptionKey`, and a slice of all the join messages(`JoinMessage`)
+This will validate both the refresh messages and the join messages and if all the proofs are correct it will update the local key both as a refresh(new decryption keys) and replace the existing parties with the new ones.
+The new party calls `join_message.collect(..)` with the broadcasted `RefreshMessage` of the existing parties and all the join messages which returns a new `LocalKey` for the new party. + +Example: +```rust +// PoV of the new party +let (join_message, new_party_decryption_key) = JoinMessage::distribute(); +broadcast(join_message); +let new_party_index = recv_broadcast(); +let vec_refresh_messages = recv_from_broadcast(); +let new_party_local_key = join_message.collect(&vec_refresh_messages, new_party_decryption_key, &[])?; + +// PoV of the other parties +let mut party_i_key: LocalKey<_>; +let mut join_message = recv_broadcast(); +let new_index = decide_who_to_replace(&join_message); +broadcast(new_index); +assert!(recv_from_broadcast().iter().all(|index| index == new_index)); +join_message.party_index = Some(new_index); +let (party_i_refresh_message, party_i_new_decryption_key) = RefreshMessage::replace(join_message, party_i_key)?; +broadcast(party_i_refresh_message); +let vec_refresh_messages = recv_from_broadcast(); +RefreshMessage::collect(&vec_refresh_messages, &mut party_i_key, party_i_new_decryption_key, &[join_message])?; +``` + +## High-level Description of FS-DKG +Here we give a short description of the FS-DKG protocol. +FS-DKG works in one round. This round includes a single broadcast message from each party $P_j$. For Setup, we assume every party in the system has a public/private key pair for Paillier encryption scheme. +At first, $P_j$ picks a random secret $s$ and secret shares it. $P_j$ publishes one set of size $t$ of commitment points $\textbf{A}$ corresponding to the polynomial coefficients: $A_i = a_iG$, and one set of $n$ commitment points $\textbf{S}$ corresponding to $n$ points on the polynomial: $S_i = \sigma_i G$. The points on the polynomial are also encrypted using the paillier keys of the receiving parties: $Enc_{pk_i}(\sigma_i)$. Finally, $P_j$ computes zero knowledge proofs $\pi_i$ to show that the paillier encryption for $P_i$ encrypts the same value commited in $S_i$. The ZK proof is a sigma protocol (can be made non-interactive using Fiat-Shamir) given in the original FS paper under the name proof of fairness. We [implemented it](https://github.com/ZenGo-X/fs-dkr/blob/main/src/proof_of_fairness.rs) under the same name. + +Verification proceeds as follows. Each party $P_j$ verifies: +1. all broadcasted proofs of fairness +2. all secret sharing schemes - computing the polynomial points "at the exponent" + +The parties define the set $\mathcal{Q}$ to be the set of the first $t+1$ parties for which all checks passed. we now show a simple optimization on how each party computes its local secret key: Each party [maps its encrypted shares](https://github.com/ZenGo-X/fs-dkr/blob/main/src/lib.rs#L181) from $\{t,n\}$ to $\{\mathcal{Q},\mathcal{Q}\}$. It then homomorphically adds all the paillier ciphertext (which is an additive homomorphic scheme) and decrypts to get the local secret key. + + +## Adjusting FS-DKG to DKR and threshold ECDSA +We will now highlight the adjustments required for FS-DKR. +In a key refresh protocol the parties start with their inputs equal to the outputs of a DKG done in the past or the output of previous DKR. Meaning, as opposed to FS-DKG protocol in which the inputs are pseudorandom such that the attacker can bias the output, for example in a rushing adversary attack, FS-DKR avoids this potential attack on FS-DKG because of the added restriction over the inputs of the attacker. Concretely, in the case the parties must reshare their DKG/DKR output secret share, all other parties already know a public commitment to the attacker secret share and can check for it. +Recall that FS-DKG is secure assuming Paillier is secure (what we called DCRA assumption). Moreover, we assumed a setup phase in which all parties generate paillier keys and share them. This fits well with threshold ECDSA: First, GG20 already requires us to assume Paillier security, therefore in this particular case, no new assumption is needed. The setup phase actually happens as part of GG20 DKG. We will use this to our advantage, running the FS-DKR using the GG20-DKG paillier keys. Obviously because we need to refresh the paillier keys as well we will also add a step to FS-DKR to generate new paillier keys and prove they were generated correctly. This is a standard proof, that can be made non-interactive. See the [zk-paillier lib](https://github.com/ZenGo-X/zk-paillier/blob/master/src/zkproofs/correct_key_ni.rs) for an implementation. + +**Adding/Removing parties:** There is a clear distinction between parties with secret shares (”Senders”) and new parties (”Receivers”). The FS-DKR protocol therefore supports adding and removing parties in a natural way: Define $\mathcal{J}>t+1$ the subset of parties participating in the protocol. To remove an existing party $P_i$, other parties exclude it from the subset $\mathcal{J}$. To add a new party, we assume the parties in $\mathcal{J}$ are aware of the new party' paillier key. In that case, the parties in $\mathcal{J}$ assign an index $i$ to the new party and broadcast the PVSS messages to it. Removal of a party is simply done by not broadcasting the encrypted messages to it. If enough parties decide on that for a party index, they will not be able to reconstruct a rotated key. + +**Identifiable Abort:** A nice property of FS-DKR is that if a party misbehaves all honest parties learn about it. This is due to the nature of PVSS used in the protocol. As GG20, our reference threshold ECDSA protocol, also have this property, it is important that identifiable abort can be guaranteed throughout the DKR as well. + +For completeness, Below is the FS-DKR protocol, written as FS-DKG with changes in red for DKR. ![](https://i.imgur.com/V50DfBz.png) +The protocol is implemented in the [ZenGo-X/fs-dkr repo](https://github.com/ZenGo-X/fs-dkr) (warning, the code is not audited yet). + + +## Related Work +Our main requirement from FS-DKR is minimal round-count. In FS-DKR the parties can pre-process all the data they need to send. Our main bottleneck is $\mathcal{O}(n^2)$ communication, which seems a standard cost in our context: It is the same asymptotic complexity as we have in GG20-DKG and GG20-Signing. + +In this section we focus on alternative protocols for DKR. Three recent results come to mind. The first one, [CGGMP20](https://eprint.iacr.org/2021/060.pdf), is another threshold ECDSA protocol with a companion refresh protocol, see figure 6 in the paper. Their protocol has the most resemblance to FS-DKR, with few notable differences. First, while FS-DKR is publicly verifiable, CGGMP20-DKR current [version](https://eprint.iacr.org/2021/060/20210118:082423) suffers from a technichal issue with its Identifiable Abort (acknowledged by the authors). Second, the paillier keys used in the CGGMP20-DKR are the new ones, while in FS-DKR, we use the old ones, already known to all, which helps us save a round of communication. Finally, CGMMP20-DKR key refresh is done by adding shares of zero while in FS-DKR we re-share existing shares. Overall we treat the similarities between the protocols as a positive signal of validation for FS-DKR. +A second protocol, by [Gurkan et. al.](https://eprint.iacr.org/2021/005), uses gossip for aggregating transcripts from the parties. However, their DKG is generating group elements secret shares and we need field elements secret shares for our threshold ECDSA. +The third relevant work is Jens Groth' [Non interactive DKG and DKR](https://eprint.iacr.org/2021/339). There, instead of paillier encryption, they use El-Gamal based encryption scheme that offers forward security. Their DKR makes the assumption that the El-Gamal decryption keys are long-term and not rotated. This assumption seems crucial for the Groth-DKG construction. In our context it means that we need to let the parties generate, store and use a new set of keypair,in addition to the Paillier keypair, and that this new keypair poses a security risk against the classical mobile adversary, which our model does not allow. As opposed to Groth-DKR, FS-DKR is reusing the existing paillier keypair and rotate it as well. In terms of efficiency - there is no complexity analysis given in the paper, however, from inspection we estimate the asymptotic complexity is comparable to FS-DKR (quadratic in the number of parties). + +## Acknowledgments +We thank Claudio Orlandi, Kobi Gurkan and Nikolaos Makriyannis for reviewing the note + diff --git a/fs-dkr/src/add_party_message.rs b/fs-dkr/src/add_party_message.rs new file mode 100644 index 00000000..ca01beb5 --- /dev/null +++ b/fs-dkr/src/add_party_message.rs @@ -0,0 +1,336 @@ +//! Message definitions for new parties that can join the protocol +//! Key points about a new party joining the refresh protocol: +//! * A new party wants to join, broadcasting a paillier ek, correctness of the +//! ek generation, +//! dlog statements and dlog proofs. +//! * All the existing parties receives the join message. We assume for now that +//! everyone accepts +//! the new party. All parties pick an index and add the new ek to their +//! LocalKey at the given index. +//! * The party index of the new party is transmitted back to the joining party +//! offchannel (it's +//! public information). +//! * All the existing parties enter the distribute phase, in which they start +//! refreshing their +//! existing keys taking into the account the join messages that they received. +//! ** All parties (including new ones) collect the refresh messages and the +//! join messages. + +use crate::{ + error::{FsDkrError, FsDkrResult}, + refresh_message::RefreshMessage, +}; +use curv::{ + arithmetic::{BasicOps, Modulo, One, Samplable, Zero}, + cryptographic_primitives::{ + hashing::Digest, + secret_sharing::feldman_vss::{ShamirSecretSharing, VerifiableSS}, + }, + elliptic::curves::{Curve, Point, Scalar}, + BigInt, +}; +use multi_party_ecdsa::protocols::multi_party_ecdsa::gg_2020::{ + party_i::{Keys, SharedKeys}, + state_machine::keygen::LocalKey, +}; +use paillier::{Decrypt, EncryptionKey, KeyGeneration, Paillier}; +use serde::{Deserialize, Serialize}; +use std::{collections::HashMap, fmt::Debug}; +use zk_paillier::zkproofs::{ + CompositeDLogProof, DLogStatement, NiCorrectKeyProof, +}; + +use crate::ring_pedersen_proof::{RingPedersenProof, RingPedersenStatement}; + +/// Message used by new parties to join the protocol. +#[derive(Clone, Deserialize, Serialize, Debug)] +#[serde(bound = "E: Curve, H: Digest + Clone")] +pub struct JoinMessage { + pub(crate) ek: EncryptionKey, + pub(crate) dk_correctness_proof: NiCorrectKeyProof, + pub(crate) party_index: Option, + pub(crate) dlog_statement: DLogStatement, + pub(crate) composite_dlog_proof_base_h1: CompositeDLogProof, + pub(crate) composite_dlog_proof_base_h2: CompositeDLogProof, + pub(crate) ring_pedersen_statement: RingPedersenStatement, + pub(crate) ring_pedersen_proof: RingPedersenProof, +} + +/// Generates the parameters needed for the h1_h2_N_tilde_vec. These parameters +/// can be seen as environment variables for each party that they agree on. In +/// this case, each new party generates it's own DlogStatements and submits it's +/// proofs +fn generate_h1_h2_n_tilde() -> (BigInt, BigInt, BigInt, BigInt, BigInt) { + let (ek_tilde, dk_tilde) = + Paillier::keypair_with_modulus_size(crate::PAILLIER_KEY_SIZE).keys(); + let one = BigInt::one(); + let phi = (&dk_tilde.p - &one) * (&dk_tilde.q - &one); + let h1 = BigInt::sample_below(&ek_tilde.n); + let (mut xhi, mut xhi_inv) = loop { + let xhi_ = BigInt::sample_below(&phi); + match BigInt::mod_inv(&xhi_, &phi) { + Some(inv) => break (xhi_, inv), + None => continue, + } + }; + let h2 = BigInt::mod_pow(&h1, &xhi, &ek_tilde.n); + xhi = BigInt::sub(&phi, &xhi); + xhi_inv = BigInt::sub(&phi, &xhi_inv); + (ek_tilde.n, h1, h2, xhi, xhi_inv) +} + +/// Generates the DlogStatement and CompositeProofs using the parameters +/// generated by [generate_h1_h2_n_tilde] +fn generate_dlog_statement_proofs( +) -> (DLogStatement, CompositeDLogProof, CompositeDLogProof) { + let (n_tilde, h1, h2, xhi, xhi_inv) = generate_h1_h2_n_tilde(); + + let dlog_statement_base_h1 = DLogStatement { + N: n_tilde.clone(), + g: h1.clone(), + ni: h2.clone(), + }; + + let dlog_statement_base_h2 = DLogStatement { + N: n_tilde, + g: h2, + ni: h1, + }; + + let composite_dlog_proof_base_h1 = + CompositeDLogProof::prove(&dlog_statement_base_h1, &xhi); + let composite_dlog_proof_base_h2 = + CompositeDLogProof::prove(&dlog_statement_base_h2, &xhi_inv); + + ( + dlog_statement_base_h1, + composite_dlog_proof_base_h1, + composite_dlog_proof_base_h2, + ) +} + +impl JoinMessage { + pub fn set_party_index(&mut self, new_party_index: u16) { + self.party_index = Some(new_party_index); + } + /// The distribute phase for a new party. This distribute phase has to + /// happen before the existing parties distribute. Calling this function + /// will generate a JoinMessage and a pair of Paillier [Keys] that are + /// going to be used when generating the [LocalKey]. + pub fn distribute() -> (Self, Keys) { + let paillier_key_pair = Keys::create(0); + let ( + dlog_statement, + composite_dlog_proof_base_h1, + composite_dlog_proof_base_h2, + ) = generate_dlog_statement_proofs(); + + let (ring_pedersen_statement, ring_pedersen_witness) = + RingPedersenStatement::generate(); + + let ring_pedersen_proof = RingPedersenProof::prove( + &ring_pedersen_witness, + &ring_pedersen_statement, + ); + + let join_message = JoinMessage { + // in a join message, we only care about the ek and the correctness + // proof + ek: paillier_key_pair.ek.clone(), + dk_correctness_proof: NiCorrectKeyProof::proof( + &paillier_key_pair.dk, + None, + ), + dlog_statement, + composite_dlog_proof_base_h1, + composite_dlog_proof_base_h2, + ring_pedersen_statement, + ring_pedersen_proof, + party_index: None, + }; + + (join_message, paillier_key_pair) + } + /// Returns the party index if it has been assigned one, throws + /// [FsDkrError::NewPartyUnassignedIndexError] otherwise + pub fn get_party_index(&self) -> FsDkrResult { + self.party_index + .ok_or(FsDkrError::NewPartyUnassignedIndexError) + } + + /// Collect phase of the protocol. Compared to the + /// [RefreshMessage::collect], this has to be tailored for a sent + /// JoinMessage on which we assigned party_index. In this collect, a + /// [LocalKey] is filled with the information provided by the + /// [RefreshMessage]s from the other parties and the other join messages + /// (multiple parties can be added/replaced at once). + pub fn collect( + &self, + refresh_messages: &[RefreshMessage], + paillier_key: Keys, + join_messages: &[JoinMessage], + new_t: u16, + new_n: u16, + current_t: u16, + ) -> FsDkrResult> { + RefreshMessage::validate_collect(refresh_messages, current_t, new_n)?; + + for refresh_message in refresh_messages.iter() { + RingPedersenProof::verify( + &refresh_message.ring_pedersen_proof, + &refresh_message.ring_pedersen_statement, + ) + .map_err(|_| { + FsDkrError::RingPedersenProofValidation { + party_index: refresh_message.party_index, + } + })?; + } + + for join_message in join_messages.iter() { + RingPedersenProof::verify( + &join_message.ring_pedersen_proof, + &join_message.ring_pedersen_statement, + ) + .map_err(|e| { + if let Some(party_index) = join_message.party_index { + FsDkrError::RingPedersenProofValidation { party_index } + } else { + e + } + })?; + } + + // check if a party_index has been assigned to the current party + let party_index = self.get_party_index()?; + + // check if a party_index has been assigned to all other new parties + // TODO: Check if no party_index collision exists + for join_message in join_messages.iter() { + join_message.get_party_index()?; + } + + let parameters = ShamirSecretSharing { + threshold: new_t, + share_count: new_n, + }; + + // generate a new share, the details can be found here https://hackmd.io/@omershlo/Hy1jBo6JY. + let (cipher_text_sum, li_vec) = RefreshMessage::get_ciphertext_sum( + refresh_messages, + party_index, + ¶meters, + &paillier_key.ek, + current_t, + ); + let new_share = Paillier::decrypt(&paillier_key.dk, cipher_text_sum) + .0 + .into_owned(); + + let new_share_fe: Scalar = Scalar::::from(&new_share); + let paillier_dk = paillier_key.dk.clone(); + let key_linear_x_i = new_share_fe.clone(); + let key_linear_y = Point::::generator() * new_share_fe.clone(); + let keys_linear = SharedKeys { + x_i: key_linear_x_i, + y: key_linear_y, + }; + let mut pk_vec: Vec<_> = (0..new_n as usize) + .map(|i| { + refresh_messages[0].points_committed_vec[i].clone() + * li_vec[0].clone() + }) + .collect(); + + #[allow(clippy::needless_range_loop)] + for i in 0..new_n as usize { + for j in 1..(current_t + 1) as usize { + pk_vec[i] = pk_vec[i].clone() + + refresh_messages[j].points_committed_vec[i].clone() + * li_vec[j].clone(); + } + } + + // check what parties are assigned in the current rotation and associate + // their paillier ek to each available party index. + + let available_parties: HashMap = refresh_messages + .iter() + .map(|msg| (msg.party_index, &msg.ek)) + .chain(std::iter::once((party_index, &paillier_key.ek))) + .chain(join_messages.iter().map(|join_message| { + (join_message.party_index.unwrap(), &join_message.ek) + })) + .collect(); + + // TODO: submit the statement the dlog proof as well! + // check what parties are assigned in the current rotation and associate + // their DLogStatements and check their CompositeDlogProofs. + let available_h1_h2_ntilde_vec: HashMap = + refresh_messages + .iter() + .map(|msg| (msg.party_index, &msg.dlog_statement)) + .chain(std::iter::once((party_index, &self.dlog_statement))) + .chain(join_messages.iter().map(|join_message| { + ( + join_message.party_index.unwrap(), + &join_message.dlog_statement, + ) + })) + .collect(); + + // generate the paillier public key vec needed for the LocalKey + // generation. + let paillier_key_vec: Vec = (1..new_n + 1) + .map(|party| { + let ek = available_parties.get(&party); + match ek { + None => EncryptionKey { + n: BigInt::zero(), + nn: BigInt::zero(), + }, + Some(key) => (*key).clone(), + } + }) + .collect(); + // generate the DLogStatement vec needed for the LocalKey generation. + let h1_h2_ntilde_vec: Vec = (1..new_n + 1) + .map(|party| { + let statement = available_h1_h2_ntilde_vec.get(&party); + + match statement { + None => generate_dlog_statement_proofs().0, + Some(dlog_statement) => (*dlog_statement).clone(), + } + }) + .collect(); + + // check if all the existing parties submitted the same public key. If + // they differ, abort. TODO: this should be verifiable? + for refresh_message in refresh_messages.iter() { + if refresh_message.public_key != refresh_messages[0].public_key { + return Err(FsDkrError::BroadcastedPublicKeyError); + } + } + + // generate the vss_scheme for the LocalKey + let (vss_scheme, _) = + VerifiableSS::::share(new_t, new_n, &new_share_fe); + // TODO: secret cleanup might be needed. + + let local_key = LocalKey { + paillier_dk, + pk_vec, + keys_linear, + paillier_key_vec, + y_sum_s: refresh_messages[0].public_key.clone(), + h1_h2_n_tilde_vec: h1_h2_ntilde_vec, + vss_scheme, + i: party_index, + t: new_t, + n: new_n, + }; + + Ok(local_key) + } +} diff --git a/fs-dkr/src/error.rs b/fs-dkr/src/error.rs new file mode 100644 index 00000000..c5d988a3 --- /dev/null +++ b/fs-dkr/src/error.rs @@ -0,0 +1,62 @@ +use serde::{Deserialize, Serialize}; +use thiserror::Error; + +pub type FsDkrResult = Result; + +#[derive(Error, Debug, Clone, Serialize, Deserialize)] +pub enum FsDkrError { + #[error("Too many malicious parties detected! Threshold {threshold:?}, Number of Refreshed Messages: {refreshed_keys:?}, Malicious parties detected when trying to refresh: malicious_parties:?")] + PartiesThresholdViolation { + threshold: u16, + refreshed_keys: usize, + // TODO: figure out how to retrieve the malicious parties indexes and + // add them to the err. malicious_parties: [usize] + }, + + #[error("Shares did not pass verification.")] + PublicShareValidationError, + + #[error("SizeMismatch error for the refresh message {refresh_message_index:?} - pdl proof length: {pdl_proof_len:?}, Points Commited Length: {points_commited_len:?}, Points Encrypted Length: {points_encrypted_len:?}")] + SizeMismatchError { + refresh_message_index: usize, + pdl_proof_len: usize, + points_commited_len: usize, + points_encrypted_len: usize, + }, + + #[error("PDLwSlack proof verification failed, results: u1 == u1_test: {is_u1_eq:?}, u2 == u2_test: {is_u2_eq:?}, u3 == u3_test: {is_u3_eq:?}")] + PDLwSlackProof { + is_u1_eq: bool, + is_u2_eq: bool, + is_u3_eq: bool, + }, + + #[error("Ring Pedersen Proof Failed")] + RingPedersenProofError, + + #[error("Range Proof failed for party: {party_index:?}")] + RangeProof { party_index: usize }, + + #[error("The Paillier moduli size of party: {party_index:?} is {moduli_size:?} bits, when it should be 2047-2048 bits")] + ModuliTooSmall { + party_index: u16, + moduli_size: usize, + }, + + #[error("Paillier verification proof failed for party {party_index:?}")] + PaillierVerificationError { party_index: u16 }, + + #[error("A new party did not receive a valid index.")] + NewPartyUnassignedIndexError, + + #[error( + "The broadcasted public key is not the same from everyone, aborting" + )] + BroadcastedPublicKeyError, + + #[error("DLog proof failed for party {party_index:?}")] + DLogProofValidation { party_index: u16 }, + + #[error("Ring pedersen proof failed for party {party_index:?}")] + RingPedersenProofValidation { party_index: u16 }, +} diff --git a/fs-dkr/src/lib.rs b/fs-dkr/src/lib.rs new file mode 100644 index 00000000..f98b985f --- /dev/null +++ b/fs-dkr/src/lib.rs @@ -0,0 +1,27 @@ +#![allow(dead_code)] +#![cfg_attr(feature = "cargo-clippy", allow(clippy::many_single_char_names))] +//! FS-DKR is a protocol for rotation of threshold ECDSA keys. +//! +//! We use standard proactive security assumptions. The protocol will be run +//! by $n$ parties. We assume honest majority, that is, number of corruptions is +//! $t<=n/2$. The adversary is malicious, and rushing. For communication, the +//! parties have access to a broadcast channel (can be implemented via a +//! bulletin board). For threshold ECDSA, we focus on GG20 protocol, currently +//! considered state of the art and most widely deployed threshold ecdsa scheme +//! (e.g. multi-party-ecdsa, tss-lib). +//! +//! Components of the library: +//! +//! * [refresh_message]: crate::refresh_message + +pub mod add_party_message; +pub mod error; +pub mod range_proofs; +pub mod refresh_message; +pub mod ring_pedersen_proof; +pub mod zk_pdl_with_slack; + +mod test; + +pub const PAILLIER_KEY_SIZE: usize = 2048; +pub const M_SECURITY: usize = 256; diff --git a/fs-dkr/src/range_proofs.rs b/fs-dkr/src/range_proofs.rs new file mode 100644 index 00000000..b9645825 --- /dev/null +++ b/fs-dkr/src/range_proofs.rs @@ -0,0 +1,788 @@ +#![allow(non_snake_case)] + +//! This file is a modified version of ING bank's range proofs implementation: +//! https://github.com/ing-bank/threshold-signatures/blob/master/src/algorithms/zkp.rs +//! +//! Zero knowledge range proofs for MtA protocol are implemented here. +//! Formal description can be found in Appendix A of https://eprint.iacr.org/2019/114.pdf +//! There are some deviations from the original specification: +//! 1) In Bob's proofs `gamma` is sampled from `[0;q^2 * N]` and `tau` from +//! `[0;q^3 * N_tilde]`. +//! 2) A non-interactive version is implemented, with challenge `e` computed via +//! Fiat-Shamir. + +// TODO: Verify this matches (if possible) range proofs from multi-party-ecdsa + +use curv::{ + arithmetic::traits::*, + cryptographic_primitives::hashing::{Digest, DigestExt}, + elliptic::curves::{Curve, Point, Scalar, Secp256k1}, + BigInt, +}; +use paillier::{EncryptionKey, Randomness}; +use serde::{Deserialize, Serialize}; +use std::{borrow::Borrow, marker::PhantomData}; +use zeroize::Zeroize; +use zk_paillier::zkproofs::DLogStatement; + +/// Represents the first round of the interactive version of the proof +#[derive(Zeroize)] +#[zeroize(drop)] +struct AliceZkpRound1 { + alpha: BigInt, + beta: BigInt, + gamma: BigInt, + ro: BigInt, + z: BigInt, + u: BigInt, + w: BigInt, +} + +impl AliceZkpRound1 { + fn from( + alice_ek: &EncryptionKey, + dlog_statement: &DLogStatement, + a: &BigInt, + q: &BigInt, + ) -> Self { + assert!( + q.bit_length() <= 256, + "We use SHA256 so we don't currently support moduli bigger than 256" + ); + let h1 = &dlog_statement.g; + let h2 = &dlog_statement.ni; + let N_tilde = &dlog_statement.N; + let alpha = BigInt::sample_below(&q.pow(3)); + let beta = BigInt::from_paillier_key(alice_ek); + let gamma = BigInt::sample_below(&(q.pow(3) * N_tilde)); + let ro = BigInt::sample_below(&(q * N_tilde)); + let z = (BigInt::mod_pow(h1, a, N_tilde) + * BigInt::mod_pow(h2, &ro, N_tilde)) + % N_tilde; + let u = ((alpha.borrow() * &alice_ek.n + 1) + * BigInt::mod_pow(&beta, &alice_ek.n, &alice_ek.nn)) + % &alice_ek.nn; + let w = (BigInt::mod_pow(h1, &alpha, N_tilde) + * BigInt::mod_pow(h2, &gamma, N_tilde)) + % N_tilde; + Self { + alpha, + beta, + gamma, + ro, + z, + u, + w, + } + } +} + +/// Represents the second round of the interactive version of the proof +struct AliceZkpRound2 { + s: BigInt, + s1: BigInt, + s2: BigInt, +} + +impl AliceZkpRound2 { + fn from( + alice_ek: &EncryptionKey, + round1: &AliceZkpRound1, + e: &BigInt, + a: &BigInt, + r: &BigInt, + ) -> Self { + Self { + s: (BigInt::mod_pow(r, e, &alice_ek.n) * round1.beta.borrow()) + % &alice_ek.n, + s1: (e * a) + round1.alpha.borrow(), + s2: (e * round1.ro.borrow()) + round1.gamma.borrow(), + } + } +} + +/// Alice's proof +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct AliceProof { + z: BigInt, + e: BigInt, + s: BigInt, + s1: BigInt, + s2: BigInt, + _phantom: PhantomData<(E, H)>, +} + +impl AliceProof { + /// verify Alice's proof using the proof and public keys + pub fn verify( + &self, + cipher: &BigInt, + alice_ek: &EncryptionKey, + dlog_statement: &DLogStatement, + ) -> bool { + let N = &alice_ek.n; + let NN = &alice_ek.nn; + let N_tilde = &dlog_statement.N; + let h1 = &dlog_statement.g; + let h2 = &dlog_statement.ni; + let Gen = alice_ek.n.borrow() + 1; + + if self.s1 > Scalar::::group_order().pow(3) { + return false; + } + + let z_e_inv = BigInt::mod_inv( + &BigInt::mod_pow(&self.z, &self.e, N_tilde), + N_tilde, + ); + let z_e_inv = match z_e_inv { + // z must be invertible, yet the check is done here + None => return false, + Some(c) => c, + }; + + let w = (BigInt::mod_pow(h1, &self.s1, N_tilde) + * BigInt::mod_pow(h2, &self.s2, N_tilde) + * z_e_inv) + % N_tilde; + + let gs1 = (self.s1.borrow() * N + 1) % NN; + let cipher_e_inv = + BigInt::mod_inv(&BigInt::mod_pow(cipher, &self.e, NN), NN); + let cipher_e_inv = match cipher_e_inv { + None => return false, + Some(c) => c, + }; + + let u = (gs1 * BigInt::mod_pow(&self.s, N, NN) * cipher_e_inv) % NN; + + let e = H::new() + .chain_bigint(N) + .chain_bigint(&Gen) + .chain_bigint(cipher) + .chain_bigint(&self.z) + .chain_bigint(&u) + .chain_bigint(&w) + .result_bigint(); + + if e != self.e { + return false; + } + + true + } + /// Create the proof using Alice's Paillier private keys and public ZKP + /// setup. Requires randomness used for encrypting Alice's secret a. + /// It is assumed that a curve order smaller than 2^256 is used.. + pub fn generate( + a: &BigInt, + cipher: &BigInt, + alice_ek: &EncryptionKey, + dlog_statement: &DLogStatement, + r: &BigInt, + ) -> Self { + let q = Scalar::::group_order(); + assert!( + q.bit_length() <= 256, + "We use SHA256 so we don't currently support moduli bigger than 256" + ); + let round1 = AliceZkpRound1::from(alice_ek, dlog_statement, a, q); + + let Gen = alice_ek.n.borrow() + 1; + let e = H::new() + .chain_bigint(&alice_ek.n) + .chain_bigint(&Gen) + .chain_bigint(cipher) + .chain_bigint(&round1.z) + .chain_bigint(&round1.u) + .chain_bigint(&round1.w) + .result_bigint(); + + let round2 = AliceZkpRound2::from(alice_ek, &round1, &e, a, r); + + Self { + z: round1.z.clone(), + e, + s: round2.s, + s1: round2.s1, + s2: round2.s2, + _phantom: PhantomData, + } + } +} + +/// Represents first round of the interactive version of the proof +struct BobZkpRound1 { + pub alpha: BigInt, + pub beta: BigInt, + pub gamma: BigInt, + pub ro: BigInt, + pub ro_prim: BigInt, + pub sigma: BigInt, + pub tau: BigInt, + pub z: BigInt, + pub z_prim: BigInt, + pub t: BigInt, + pub w: BigInt, + pub v: BigInt, + _phantom: PhantomData, +} + +impl Zeroize for BobZkpRound1 { + fn zeroize(&mut self) { + self.alpha.zeroize(); + self.beta.zeroize(); + self.gamma.zeroize(); + self.ro.zeroize(); + self.ro_prim.zeroize(); + self.sigma.zeroize(); + self.tau.zeroize(); + self.z.zeroize(); + self.z_prim.zeroize(); + self.t.zeroize(); + self.w.zeroize(); + self.v.zeroize(); + } +} + +impl Drop for BobZkpRound1 { + fn drop(&mut self) { + self.zeroize(); + } +} + +impl BobZkpRound1 { + /// `b` - Bob's secret + /// `beta_prim` - randomly chosen in `MtA` by Bob + /// `a_encrypted` - Alice's secret encrypted by Alice + fn from( + alice_ek: &EncryptionKey, + dlog_statement: &DLogStatement, + b: &Scalar, + beta_prim: &BigInt, + a_encrypted: &BigInt, + q: &BigInt, + ) -> Self { + assert!( + q.bit_length() <= 256, + "We use SHA256 so we don't currently support moduli bigger than 256" + ); + let h1 = &dlog_statement.g; + let h2 = &dlog_statement.ni; + let N_tilde = &dlog_statement.N; + let b_bn = b.to_bigint(); + + let alpha = BigInt::sample_below(&q.pow(3)); + let beta = BigInt::from_paillier_key(alice_ek); + let gamma = BigInt::sample_below(&(q.pow(2) * &alice_ek.n)); + let ro = BigInt::sample_below(&(q * N_tilde)); + let ro_prim = BigInt::sample_below(&(q.pow(3) * N_tilde)); + let sigma = BigInt::sample_below(&(q * N_tilde)); + let tau = BigInt::sample_below(&(q.pow(3) * N_tilde)); + let z = (BigInt::mod_pow(h1, &b_bn, N_tilde) + * BigInt::mod_pow(h2, &ro, N_tilde)) + % N_tilde; + let z_prim = (BigInt::mod_pow(h1, &alpha, N_tilde) + * BigInt::mod_pow(h2, &ro_prim, N_tilde)) + % N_tilde; + let t = (BigInt::mod_pow(h1, beta_prim, N_tilde) + * BigInt::mod_pow(h2, &sigma, N_tilde)) + % N_tilde; + let w = (BigInt::mod_pow(h1, &gamma, N_tilde) + * BigInt::mod_pow(h2, &tau, N_tilde)) + % N_tilde; + let v = (BigInt::mod_pow(a_encrypted, &alpha, &alice_ek.nn) + * (gamma.borrow() * &alice_ek.n + 1) + * BigInt::mod_pow(&beta, &alice_ek.n, &alice_ek.nn)) + % &alice_ek.nn; + Self { + alpha, + beta, + gamma, + ro, + ro_prim, + sigma, + tau, + z, + z_prim, + t, + w, + v, + _phantom: PhantomData, + } + } +} + +/// represents second round of the interactive version of the proof +struct BobZkpRound2 { + pub s: BigInt, + pub s1: BigInt, + pub s2: BigInt, + pub t1: BigInt, + pub t2: BigInt, + _phantom: PhantomData, +} + +impl BobZkpRound2 { + /// `e` - the challenge in interactive ZKP, the hash in non-interactive ZKP + /// `b` - Bob's secret + /// `beta_prim` - randomly chosen in `MtA` by Bob + /// `r` - randomness used by Bob on Alice's public Paillier key to encrypt + /// `beta_prim` in `MtA` + fn from( + alice_ek: &EncryptionKey, + round1: &BobZkpRound1, + e: &BigInt, + b: &Scalar, + beta_prim: &BigInt, + r: &Randomness, + ) -> Self { + let b_bn = b.to_bigint(); + Self { + s: (BigInt::mod_pow(r.0.borrow(), e, &alice_ek.n) + * round1.beta.borrow()) + % &alice_ek.n, + s1: (e * b_bn) + round1.alpha.borrow(), + s2: (e * round1.ro.borrow()) + round1.ro_prim.borrow(), + t1: (e * beta_prim) + round1.gamma.borrow(), + t2: (e * round1.sigma.borrow()) + round1.tau.borrow(), + _phantom: PhantomData, + } + } +} + +/// Additional fields in Bob's proof if MtA is run with check +pub struct BobCheck { + u: Point, + X: Point, +} + +/// Bob's regular proof +#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] +pub struct BobProof { + t: BigInt, + z: BigInt, + e: BigInt, + s: BigInt, + s1: BigInt, + s2: BigInt, + t1: BigInt, + t2: BigInt, + _phantom: PhantomData<(E, H)>, +} + +#[allow(clippy::too_many_arguments)] +impl BobProof { + pub fn verify( + &self, + a_enc: &BigInt, + mta_avc_out: &BigInt, + alice_ek: &EncryptionKey, + dlog_statement: &DLogStatement, + check: Option<&BobCheck>, + ) -> bool { + let N = &alice_ek.n; + let NN = &alice_ek.nn; + let N_tilde = &dlog_statement.N; + let h1 = &dlog_statement.g; + let h2 = &dlog_statement.ni; + + if self.s1 > Scalar::::group_order().pow(3) { + return false; + } + + let z_e_inv = BigInt::mod_inv( + &BigInt::mod_pow(&self.z, &self.e, N_tilde), + N_tilde, + ); + let z_e_inv = match z_e_inv { + // z must be invertible, yet the check is done here + None => return false, + Some(c) => c, + }; + + let z_prim = (BigInt::mod_pow(h1, &self.s1, N_tilde) + * BigInt::mod_pow(h2, &self.s2, N_tilde) + * z_e_inv) + % N_tilde; + + let mta_e_inv = + BigInt::mod_inv(&BigInt::mod_pow(mta_avc_out, &self.e, NN), NN); + let mta_e_inv = match mta_e_inv { + None => return false, + Some(c) => c, + }; + + let v = (BigInt::mod_pow(a_enc, &self.s1, NN) + * BigInt::mod_pow(&self.s, N, NN) + * (self.t1.borrow() * N + 1) + * mta_e_inv) + % NN; + + let t_e_inv = BigInt::mod_inv( + &BigInt::mod_pow(&self.t, &self.e, N_tilde), + N_tilde, + ); + let t_e_inv = match t_e_inv { + None => return false, + Some(c) => c, + }; + + let w = (BigInt::mod_pow(h1, &self.t1, N_tilde) + * BigInt::mod_pow(h2, &self.t2, N_tilde) + * t_e_inv) + % N_tilde; + + let Gen = alice_ek.n.borrow() + 1; + + let hash = H::new() + .chain_bigint(&alice_ek.n) + .chain_bigint(&Gen) + .chain_bigint(a_enc) + .chain_bigint(mta_avc_out) + .chain_bigint(&self.z) + .chain_bigint(&z_prim) + .chain_bigint(&self.t) + .chain_bigint(&v) + .chain_bigint(&w); + + let e = match check { + Some(_) => { + let X_x_coor = check.unwrap().X.x_coord().unwrap(); + let X_y_coor = check.unwrap().X.y_coord().unwrap(); + let u_x_coor = check.unwrap().u.x_coord().unwrap(); + let u_y_coor = check.unwrap().u.y_coord().unwrap(); + hash.chain_bigint(&X_x_coor) + .chain_bigint(&X_y_coor) + .chain_bigint(&u_x_coor) + .chain_bigint(&u_y_coor) + .result_bigint() + } + None => hash.result_bigint(), + }; + + if e != self.e { + return false; + } + + true + } + + pub fn generate( + a_encrypted: &BigInt, + mta_encrypted: &BigInt, + b: &Scalar, + beta_prim: &BigInt, + alice_ek: &EncryptionKey, + dlog_statement: &DLogStatement, + r: &Randomness, + check: bool, + ) -> (BobProof, Option>) { + let round1 = BobZkpRound1::from( + alice_ek, + dlog_statement, + b, + beta_prim, + a_encrypted, + Scalar::::group_order(), + ); + + let Gen = alice_ek.n.borrow() + 1; + let hash = H::new() + .chain_bigint(&alice_ek.n) + .chain_bigint(&Gen) + .chain_bigint(a_encrypted) + .chain_bigint(mta_encrypted) + .chain_bigint(&round1.z) + .chain_bigint(&round1.z_prim) + .chain_bigint(&round1.t) + .chain_bigint(&round1.v) + .chain_bigint(&round1.w); + let mut check_u = None; + let e = if check { + let (X, u) = { + let ec_gen: Point = Point::::generator().to_point(); + let alpha: Scalar = Scalar::::from(&round1.alpha); + (ec_gen.clone() * b.clone(), ec_gen.clone() * alpha) + }; + check_u = Some(u.clone()); + let X_x_coor = X.x_coord().unwrap(); + let X_y_coor = X.y_coord().unwrap(); + let u_x_coor = u.x_coord().unwrap(); + let u_y_coor = u.y_coord().unwrap(); + hash.chain_bigint(&X_x_coor) + .chain_bigint(&X_y_coor) + .chain_bigint(&u_x_coor) + .chain_bigint(&u_y_coor) + .result_bigint() + } else { + hash.result_bigint() + }; + + let round2 = BobZkpRound2::from(alice_ek, &round1, &e, b, beta_prim, r); + + ( + BobProof { + t: round1.t.clone(), + z: round1.z.clone(), + e, + s: round2.s, + s1: round2.s1, + s2: round2.s2, + t1: round2.t1, + t2: round2.t2, + _phantom: PhantomData, + }, + check_u, + ) + } +} + +/// Bob's extended proof, adds the knowledge of $`B = g^b \in \mathcal{G}`$ +#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] +pub struct BobProofExt { + proof: BobProof, + u: Point, +} + +#[allow(clippy::too_many_arguments)] +impl BobProofExt { + pub fn verify( + &self, + a_enc: &BigInt, + mta_avc_out: &BigInt, + alice_ek: &EncryptionKey, + dlog_statement: &DLogStatement, + X: &Point, + ) -> bool { + // check basic proof first + if !self.proof.verify( + a_enc, + mta_avc_out, + alice_ek, + dlog_statement, + Some(&BobCheck { + u: self.u.clone(), + X: X.clone(), + }), + ) { + return false; + } + + // fiddle with EC points + let (x1, x2) = { + let ec_gen: Point = Point::::generator().to_point(); + let s1: Scalar = Scalar::::from(&self.proof.s1); + let e: Scalar = Scalar::::from(&self.proof.e); + (ec_gen * s1, (X.clone() * e) + self.u.clone()) + }; + + if x1 != x2 { + return false; + } + + true + } + + fn generate( + a_encrypted: &BigInt, + mta_encrypted: &BigInt, + b: &Scalar, + beta_prim: &BigInt, + alice_ek: &EncryptionKey, + dlog_statement: &DLogStatement, + r: &Randomness, + ) -> BobProofExt { + // proving a basic proof (with modified hash) + let (bob_proof, u) = BobProof::generate( + a_encrypted, + mta_encrypted, + b, + beta_prim, + alice_ek, + dlog_statement, + r, + true, + ); + + BobProofExt { + proof: bob_proof, + u: u.unwrap(), + } + } +} + +/// sample random value of an element of a multiplicative group +pub trait SampleFromMultiplicativeGroup { + fn from_modulo(N: &BigInt) -> BigInt; + fn from_paillier_key(ek: &EncryptionKey) -> BigInt; +} + +impl SampleFromMultiplicativeGroup for BigInt { + fn from_modulo(N: &BigInt) -> BigInt { + let One = BigInt::one(); + loop { + let r = Self::sample_below(N); + if r.gcd(N) == One { + return r; + } + } + } + + fn from_paillier_key(ek: &EncryptionKey) -> BigInt { + Self::from_modulo(ek.n.borrow()) + } +} + +#[cfg(test)] +pub(crate) mod tests { + use super::*; + use curv::elliptic::curves::{ + secp256_k1::{Secp256k1Point, Secp256k1Scalar}, + ECScalar, + }; + use paillier::{ + traits::{Encrypt, EncryptWithChosenRandomness, KeyGeneration}, + Add, DecryptionKey, Mul, Paillier, RawCiphertext, RawPlaintext, + }; + use sha2::Sha256; + + type GE = Secp256k1Point; + type FE = Secp256k1Scalar; + + pub(crate) fn generate_init( + ) -> (DLogStatement, EncryptionKey, DecryptionKey) { + let (ek_tilde, dk_tilde) = + Paillier::keypair_with_modulus_size(crate::PAILLIER_KEY_SIZE) + .keys(); + let one = BigInt::one(); + let phi = (&dk_tilde.p - &one) * (&dk_tilde.q - &one); + let h1 = BigInt::sample_below(&ek_tilde.n); + let (xhi, _) = loop { + let xhi_ = BigInt::sample_below(&phi); + match BigInt::mod_inv(&xhi_, &phi) { + Some(inv) => break (xhi_, inv), + None => continue, + } + }; + let h2 = BigInt::mod_pow(&h1, &xhi, &ek_tilde.n); + + let (ek, dk) = + Paillier::keypair_with_modulus_size(crate::PAILLIER_KEY_SIZE) + .keys(); + let dlog_statement = DLogStatement { + g: h1, + ni: h2, + N: ek_tilde.n, + }; + (dlog_statement, ek, dk) + } + + #[test] + fn alice_zkp() { + let (dlog_statement, ek, _) = generate_init(); + + // Alice's secret value + let a = FE::random().to_bigint(); + let r = BigInt::from_paillier_key(&ek); + let cipher = Paillier::encrypt_with_chosen_randomness( + &ek, + RawPlaintext::from(a.clone()), + &Randomness::from(&r), + ) + .0 + .clone() + .into_owned(); + + let alice_proof = AliceProof::::generate( + &a, + &cipher, + &ek, + &dlog_statement, + &r, + ); + + assert!(alice_proof.verify(&cipher, &ek, &dlog_statement)); + } + + #[test] + fn bob_zkp() { + let (dlog_statement, ek, _) = generate_init(); + + (0..5).for_each(|_| { + let alice_public_key = &ek; + + // run MtA protocol with different inputs + (0..5).for_each(|_| { + // Simulate Alice + let a = FE::random().to_bigint(); + let encrypted_a = + Paillier::encrypt(alice_public_key, RawPlaintext::from(a)) + .0 + .clone() + .into_owned(); + + // Bob follows MtA + let b: Scalar = Scalar::::random(); + // E(a) * b + let b_times_enc_a = Paillier::mul( + alice_public_key, + RawCiphertext::from(encrypted_a.clone()), + RawPlaintext::from(&b.to_bigint()), + ); + let beta_prim = BigInt::sample_below(&alice_public_key.n); + let r = Randomness::sample(alice_public_key); + let enc_beta_prim = Paillier::encrypt_with_chosen_randomness( + alice_public_key, + RawPlaintext::from(&beta_prim), + &r, + ); + + let mta_out = Paillier::add( + alice_public_key, + b_times_enc_a, + enc_beta_prim, + ); + + let (bob_proof, _) = BobProof::::generate( + &encrypted_a, + &mta_out.0.clone().into_owned(), + &b, + &beta_prim, + alice_public_key, + &dlog_statement, + &r, + false, + ); + assert!(bob_proof.verify( + &encrypted_a, + &mta_out.0.clone().into_owned(), + alice_public_key, + &dlog_statement, + None + )); + + // Bob follows MtAwc + let ec_gen: Point = + Point::::generator().to_point(); + let X = ec_gen * b.clone(); + let bob_proof = BobProofExt::::generate( + &encrypted_a, + &mta_out.0.clone().into_owned(), + &b, + &beta_prim, + alice_public_key, + &dlog_statement, + &r, + ); + assert!(bob_proof.verify( + &encrypted_a, + &mta_out.0.clone().into_owned(), + alice_public_key, + &dlog_statement, + &X + )); + }); + }); + } +} diff --git a/fs-dkr/src/refresh_message.rs b/fs-dkr/src/refresh_message.rs new file mode 100644 index 00000000..4cb31e30 --- /dev/null +++ b/fs-dkr/src/refresh_message.rs @@ -0,0 +1,527 @@ +use crate::{ + add_party_message::JoinMessage, + error::{FsDkrError, FsDkrResult}, + range_proofs::AliceProof, + zk_pdl_with_slack::{PDLwSlackProof, PDLwSlackStatement, PDLwSlackWitness}, +}; +use curv::{ + arithmetic::{BitManipulation, Samplable, Zero}, + cryptographic_primitives::{ + hashing::Digest, + secret_sharing::feldman_vss::{ShamirSecretSharing, VerifiableSS}, + }, + elliptic::curves::{Curve, Point, Scalar}, + BigInt, HashChoice, +}; +use multi_party_ecdsa::protocols::multi_party_ecdsa::gg_2020::state_machine::keygen::LocalKey; +pub use paillier::DecryptionKey; +use paillier::{ + Add, Decrypt, Encrypt, EncryptWithChosenRandomness, EncryptionKey, KeyGeneration, Mul, + Paillier, Randomness, RawCiphertext, RawPlaintext, +}; +use serde::{Deserialize, Serialize}; +use std::{borrow::Borrow, collections::HashMap, fmt::Debug}; +use zeroize::Zeroize; +use zk_paillier::zkproofs::{DLogStatement, NiCorrectKeyProof, SALT_STRING}; + +use crate::ring_pedersen_proof::{RingPedersenProof, RingPedersenStatement}; + +// Everything here can be broadcasted +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(bound = "E: Curve, H: Digest + Clone")] +pub struct RefreshMessage { + pub(crate) old_party_index: u16, + pub(crate) party_index: u16, + pdl_proof_vec: Vec>, + range_proofs: Vec>, + coefficients_committed_vec: VerifiableSS, + pub(crate) points_committed_vec: Vec>, + points_encrypted_vec: Vec, + dk_correctness_proof: NiCorrectKeyProof, + pub(crate) dlog_statement: DLogStatement, + pub(crate) ek: EncryptionKey, + pub(crate) remove_party_indices: Vec, + pub(crate) public_key: Point, + pub(crate) ring_pedersen_statement: RingPedersenStatement, + pub(crate) ring_pedersen_proof: RingPedersenProof, + #[serde(skip)] + pub hash_choice: HashChoice, +} + +impl RefreshMessage { + pub fn distribute( + old_party_index: u16, + local_key: &mut LocalKey, + new_t: u16, + new_n: u16, + ) -> FsDkrResult<(RefreshMessage, DecryptionKey)> { + assert!(new_t <= new_n / 2); + let secret = local_key.keys_linear.x_i.clone(); + // secret share old key + if new_n <= new_t { + return Err(FsDkrError::NewPartyUnassignedIndexError); + } + let (vss_scheme, secret_shares) = + VerifiableSS::::share(new_t, new_n, &secret); + + local_key.vss_scheme = vss_scheme.clone(); + + // commit to points on the polynomial + let points_committed_vec: Vec<_> = (0..secret_shares.len()) + .map(|i| Point::::generator() * &secret_shares[i].clone()) + .collect(); + + // encrypt points on the polynomial using Paillier keys + let (points_encrypted_vec, randomness_vec): (Vec<_>, Vec<_>) = (0 + ..secret_shares.len()) + .map(|i| { + let randomness = + BigInt::sample_below(&local_key.paillier_key_vec[i].n); + let ciphertext = Paillier::encrypt_with_chosen_randomness( + &local_key.paillier_key_vec[i], + RawPlaintext::from(secret_shares[i].to_bigint()), + &Randomness::from(randomness.clone()), + ) + .0 + .into_owned(); + (ciphertext, randomness) + }) + .unzip(); + + // generate PDL proofs for each {point_committed, point_encrypted} pair + let pdl_proof_vec: Vec<_> = (0..secret_shares.len()) + .map(|i| { + let witness = PDLwSlackWitness { + x: secret_shares[i].clone(), + r: randomness_vec[i].clone(), + }; + let statement = PDLwSlackStatement { + ciphertext: points_encrypted_vec[i].clone(), + ek: local_key.paillier_key_vec[i].clone(), + Q: points_committed_vec[i].clone(), + G: Point::::generator().to_point(), + h1: local_key.h1_h2_n_tilde_vec[i].g.clone(), + h2: local_key.h1_h2_n_tilde_vec[i].ni.clone(), + N_tilde: local_key.h1_h2_n_tilde_vec[i].N.clone(), + }; + PDLwSlackProof::prove(&witness, &statement) + }) + .collect(); + + let range_proofs = (0..secret_shares.len()) + .map(|i| { + AliceProof::generate( + &secret_shares[i].to_bigint(), + &points_encrypted_vec[i], + &local_key.paillier_key_vec[i], + &local_key.h1_h2_n_tilde_vec[i], + &randomness_vec[i], + ) + }) + .collect(); + + let (ek, dk) = + Paillier::keypair_with_modulus_size(crate::PAILLIER_KEY_SIZE) + .keys(); + let dk_correctness_proof = NiCorrectKeyProof::proof(&dk, None); + + let (ring_pedersen_statement, ring_pedersen_witness) = + RingPedersenStatement::generate(); + + let ring_pedersen_proof = RingPedersenProof::prove( + &ring_pedersen_witness, + &ring_pedersen_statement, + ); + Ok(( + RefreshMessage { + old_party_index, + party_index: local_key.i, + pdl_proof_vec, + range_proofs, + coefficients_committed_vec: vss_scheme, + points_committed_vec, + points_encrypted_vec, + dk_correctness_proof, + dlog_statement: local_key.h1_h2_n_tilde_vec + [(local_key.i - 1) as usize] + .clone(), + ek, + remove_party_indices: Vec::new(), + public_key: local_key.y_sum_s.clone(), + ring_pedersen_statement, + ring_pedersen_proof, + hash_choice: HashChoice::new(), + }, + dk, + )) + } + + pub fn validate_collect( + refresh_messages: &[Self], + current_t: u16, + new_n: u16, + ) -> FsDkrResult<()> { + // check we got at least current threshold t + 1 refresh messages + // (i.e a quorum of existing parties has sent refresh messages). + if refresh_messages.len() <= current_t.into() { + return Err(FsDkrError::PartiesThresholdViolation { + threshold: current_t, + refreshed_keys: refresh_messages.len(), + }); + } + + // check all vectors are of same length + let reference_len = refresh_messages[0].pdl_proof_vec.len(); + + for (k, refresh_message) in refresh_messages.iter().enumerate() { + let pdl_proof_len = refresh_message.pdl_proof_vec.len(); + let points_commited_len = + refresh_message.points_committed_vec.len(); + let points_encrypted_len = + refresh_message.points_encrypted_vec.len(); + + if !(pdl_proof_len == reference_len + && points_commited_len == reference_len + && points_encrypted_len == reference_len) + { + return Err(FsDkrError::SizeMismatchError { + refresh_message_index: k, + pdl_proof_len, + points_commited_len, + points_encrypted_len, + }); + } + } + + for refresh_message in refresh_messages.iter() { + for i in 0..new_n as usize { + //TODO: we should handle the case of t( + refresh_messages: &'a [Self], + party_index: u16, + parameters: &'a ShamirSecretSharing, + ek: &'a EncryptionKey, + current_t: u16, + ) -> (RawCiphertext<'a>, Vec>) { + // TODO: check we have large enough qualified set , at least t+1 + //decrypt the new share + // we first homomorphically add all ciphertext encrypted using our + // encryption key + let ciphertext_vec: Vec<_> = (0..refresh_messages.len()) + .map(|k| { + refresh_messages[k].points_encrypted_vec + [(party_index - 1) as usize] + .clone() + }) + .collect(); + + let indices: Vec = (0..(current_t + 1) as usize) + .map(|i| refresh_messages[i].old_party_index - 1) + .collect(); + + // optimization - one decryption + let li_vec: Vec<_> = (0..current_t as usize + 1) + .map(|i| { + VerifiableSS::::map_share_to_new_params( + parameters.clone().borrow(), + indices[i], + &indices, + ) + }) + .collect(); + + let ciphertext_vec_at_indices_mapped: Vec<_> = (0..(current_t + 1) + as usize) + .map(|i| { + Paillier::mul( + ek, + RawCiphertext::from(ciphertext_vec[i].clone()), + RawPlaintext::from(li_vec[i].to_bigint()), + ) + }) + .collect(); + + let ciphertext_sum = ciphertext_vec_at_indices_mapped.iter().fold( + Paillier::encrypt(ek, RawPlaintext::from(BigInt::zero())), + |acc, x| Paillier::add(ek, acc, x.clone()), + ); + + (ciphertext_sum, li_vec) + } + + pub fn replace( + new_parties: &[JoinMessage], + key: &mut LocalKey, + old_to_new_map: &HashMap, + new_t: u16, + new_n: u16, + ) -> FsDkrResult<(Self, DecryptionKey)> { + let current_len = key.paillier_key_vec.len() as u16; + let mut paillier_key_h1_h2_n_tilde_hash_map: HashMap< + u16, + (EncryptionKey, DLogStatement), + > = HashMap::new(); + for old_party_index in old_to_new_map.keys() { + let paillier_key = key + .paillier_key_vec + .get((old_party_index - 1) as usize) + .unwrap() + .clone(); + let h1_h2_n_tilde = key + .h1_h2_n_tilde_vec + .get((old_party_index - 1) as usize) + .unwrap() + .clone(); + paillier_key_h1_h2_n_tilde_hash_map.insert( + *old_to_new_map.get(old_party_index).unwrap(), + (paillier_key, h1_h2_n_tilde), + ); + } + + for new_party_index in paillier_key_h1_h2_n_tilde_hash_map.keys() { + if *new_party_index <= current_len { + key.paillier_key_vec[(new_party_index - 1) as usize] = + paillier_key_h1_h2_n_tilde_hash_map + .get(new_party_index) + .unwrap() + .clone() + .0; + key.h1_h2_n_tilde_vec[(new_party_index - 1) as usize] = + paillier_key_h1_h2_n_tilde_hash_map + .get(new_party_index) + .unwrap() + .clone() + .1; + } else { + key.paillier_key_vec.insert( + (new_party_index - 1) as usize, + paillier_key_h1_h2_n_tilde_hash_map + .get(new_party_index) + .unwrap() + .clone() + .0, + ); + key.h1_h2_n_tilde_vec.insert( + (new_party_index - 1) as usize, + paillier_key_h1_h2_n_tilde_hash_map + .get(new_party_index) + .unwrap() + .clone() + .1, + ); + } + } + + for join_message in new_parties.iter() { + let party_index = join_message.get_party_index()?; + if party_index <= current_len { + key.paillier_key_vec[(party_index - 1) as usize] = + join_message.ek.clone(); + key.h1_h2_n_tilde_vec[(party_index - 1) as usize] = + join_message.dlog_statement.clone(); + } else { + key.paillier_key_vec.insert( + (party_index - 1) as usize, + join_message.ek.clone(), + ); + key.h1_h2_n_tilde_vec.insert( + (party_index - 1) as usize, + join_message.dlog_statement.clone(), + ); + } + } + let old_party_index = key.i; + key.i = *old_to_new_map.get(&key.i).unwrap(); + key.t = new_t; + key.n = new_n; + + RefreshMessage::distribute(old_party_index, key, new_t, new_n) + } + + pub fn collect( + refresh_messages: &[Self], + local_key: &mut LocalKey, + new_dk: DecryptionKey, + join_messages: &[JoinMessage], + current_t: u16, + ) -> FsDkrResult<()> { + let new_n = refresh_messages.len() + join_messages.len(); + RefreshMessage::validate_collect( + refresh_messages, + current_t, + new_n as u16, + )?; + + for refresh_message in refresh_messages.iter() { + for i in 0..new_n { + let statement = PDLwSlackStatement { + ciphertext: refresh_message.points_encrypted_vec[i].clone(), + ek: local_key.paillier_key_vec[i].clone(), + Q: refresh_message.points_committed_vec[i].clone(), + G: Point::::generator().to_point(), + h1: local_key.h1_h2_n_tilde_vec[i].g.clone(), + h2: local_key.h1_h2_n_tilde_vec[i].ni.clone(), + N_tilde: local_key.h1_h2_n_tilde_vec[i].N.clone(), + }; + refresh_message.pdl_proof_vec[i].verify(&statement)?; + if !refresh_message.range_proofs[i].verify( + &statement.ciphertext, + &statement.ek, + &local_key.h1_h2_n_tilde_vec[i], + ) { + return Err(FsDkrError::RangeProof { party_index: i }); + } + } + } + + // Verify ring-pedersen parameters + for refresh_message in refresh_messages.iter() { + RingPedersenProof::verify( + &refresh_message.ring_pedersen_proof, + &refresh_message.ring_pedersen_statement, + )?; + } + + for join_message in join_messages.iter() { + RingPedersenProof::verify( + &join_message.ring_pedersen_proof, + &join_message.ring_pedersen_statement, + )?; + } + + let old_ek = + local_key.paillier_key_vec[(local_key.i - 1) as usize].clone(); + let (cipher_text_sum, li_vec) = RefreshMessage::get_ciphertext_sum( + refresh_messages, + local_key.i, + &local_key.vss_scheme.parameters, + &old_ek, + current_t, + ); + + for refresh_message in refresh_messages.iter() { + if refresh_message + .dk_correctness_proof + .verify(&refresh_message.ek, SALT_STRING) + .is_err() + { + return Err(FsDkrError::PaillierVerificationError { + party_index: refresh_message.party_index, + }); + } + let n_length = refresh_message.ek.n.bit_length(); + if !(crate::PAILLIER_KEY_SIZE - 1..=crate::PAILLIER_KEY_SIZE) + .contains(&n_length) + { + return Err(FsDkrError::ModuliTooSmall { + party_index: refresh_message.party_index, + moduli_size: n_length, + }); + } + + // if the proof checks, we add the new paillier public key to the + // key + local_key.paillier_key_vec + [(refresh_message.party_index - 1) as usize] = + refresh_message.ek.clone(); + } + + for join_message in join_messages { + let party_index = join_message.get_party_index()?; + + if join_message + .dk_correctness_proof + .verify(&join_message.ek, SALT_STRING) + .is_err() + { + return Err(FsDkrError::PaillierVerificationError { + party_index, + }); + } + + // creating an inverse dlog statement + let dlog_statement_base_h2 = DLogStatement { + N: join_message.dlog_statement.N.clone(), + g: join_message.dlog_statement.ni.clone(), + ni: join_message.dlog_statement.g.clone(), + }; + if join_message + .composite_dlog_proof_base_h1 + .verify(&join_message.dlog_statement) + .is_err() + || join_message + .composite_dlog_proof_base_h2 + .verify(&dlog_statement_base_h2) + .is_err() + { + return Err(FsDkrError::DLogProofValidation { party_index }); + } + + let n_length = join_message.ek.n.bit_length(); + if !(crate::PAILLIER_KEY_SIZE - 1..=crate::PAILLIER_KEY_SIZE) + .contains(&n_length) + { + //if n_length > crate::PAILLIER_KEY_SIZE || n_length < + // crate::PAILLIER_KEY_SIZE - 1 { + return Err(FsDkrError::ModuliTooSmall { + party_index: join_message.get_party_index()?, + moduli_size: n_length, + }); + } + + // if the proof checks, we add the new paillier public key to the + // key + local_key.paillier_key_vec[(party_index - 1) as usize] = + join_message.ek.clone(); + } + + let new_share = + Paillier::decrypt(&local_key.paillier_dk, cipher_text_sum) + .0 + .into_owned(); + + let new_share_fe: Scalar = Scalar::::from(&new_share); + + // zeroize the old dk key + local_key.paillier_dk.q.zeroize(); + local_key.paillier_dk.p.zeroize(); + local_key.paillier_dk = new_dk; + + // update old key and output new key + local_key.keys_linear.x_i = new_share_fe.clone(); + local_key.keys_linear.y = Point::::generator() * new_share_fe; + + // update local key list of local public keys (X_i = g^x_i is updated by + // adding all committed points to that party) + for i in 0..refresh_messages.len() + join_messages.len() { + local_key.pk_vec.insert( + i, + refresh_messages[0].points_committed_vec[i].clone() + * li_vec[0].clone(), + ); + for j in 1..current_t as usize + 1 { + local_key.pk_vec[i] = local_key.pk_vec[i].clone() + + refresh_messages[j].points_committed_vec[i].clone() + * li_vec[j].clone(); + } + } + + Ok(()) + } +} diff --git a/fs-dkr/src/ring_pedersen_proof.rs b/fs-dkr/src/ring_pedersen_proof.rs new file mode 100644 index 00000000..ccb81fd3 --- /dev/null +++ b/fs-dkr/src/ring_pedersen_proof.rs @@ -0,0 +1,186 @@ +#![allow(non_snake_case)] + +/* + Ring Pedersen Proof + Copyright 2022 by Webb Technologies. + + ring_pedersen_proof.rs is free software: you can redistribute + it and/or modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, either + version 3 of the License, or (at your option) any later version. + @license GPL-3.0+ +*/ + +use bitvec::prelude::*; +use curv::{ + arithmetic::traits::*, + cryptographic_primitives::hashing::{Digest, DigestExt}, + elliptic::curves::Curve, + BigInt, +}; +use paillier::{EncryptionKey, KeyGeneration, Paillier}; +use serde::{Deserialize, Serialize}; +use std::marker::PhantomData; + +use crate::error::{FsDkrError, FsDkrResult}; + +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(bound = "E: Curve, H: Digest + Clone")] +pub struct RingPedersenStatement { + pub S: BigInt, + pub T: BigInt, + pub N: BigInt, + phi: BigInt, + pub ek: EncryptionKey, + #[serde(skip)] + phantom: PhantomData<(E, H)>, +} + +pub struct RingPedersenWitness { + p: BigInt, + q: BigInt, + lambda: BigInt, + phantom: PhantomData<(E, H)>, +} + +impl RingPedersenStatement { + pub fn generate() -> (Self, RingPedersenWitness) { + let (ek_tilde, dk_tilde) = + Paillier::keypair_with_modulus_size(crate::PAILLIER_KEY_SIZE) + .keys(); + let one = BigInt::one(); + let phi = (&dk_tilde.p - &one) * (&dk_tilde.q - &one); + let r = BigInt::sample_below(&ek_tilde.n); + let lambda = BigInt::sample_below(&phi); + let t = BigInt::mod_pow(&r, &BigInt::from(2), &ek_tilde.n); + let s = BigInt::mod_pow(&t, &lambda, &ek_tilde.n); + + ( + Self { + S: s, + T: t, + N: ek_tilde.clone().n, + phi, + ek: ek_tilde, + phantom: PhantomData, + }, + RingPedersenWitness { + p: dk_tilde.p, + q: dk_tilde.q, + lambda, + phantom: PhantomData, + }, + ) + } +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(bound = "E: Curve, H: Digest + Clone")] +pub struct RingPedersenProof { + A: Vec, + Z: Vec, + #[serde(skip)] + phantom: PhantomData<(E, H)>, +} + +// Link to the UC non-interactive threshold ECDSA paper +impl RingPedersenProof { + pub fn prove( + witness: &RingPedersenWitness, + statement: &RingPedersenStatement, + ) -> RingPedersenProof { + // 1. Sample alphas from 1 -> m from \phi(N) + let mut a = [(); M].map(|_| BigInt::zero()); + let mut A = [(); M].map(|_| BigInt::zero()); + let mut hash = H::new(); + for i in 0..M { + // TODO: Consider ensuring we get a unit element of this subgroup + let a_i = BigInt::sample_below(&statement.phi); + a[i] = a_i.clone(); + let A_i = BigInt::mod_pow(&statement.T, &a_i, &statement.N); + A[i] = A_i.clone(); + hash = H::chain_bigint(hash, &A_i); + } + + let e: BigInt = hash.result_bigint(); + let bitwise_e: BitVec = BitVec::from_vec(e.to_bytes()); + + let mut Z = [(); M].map(|_| BigInt::zero()); + for i in 0..M { + let e_i = if bitwise_e[i] { + BigInt::one() + } else { + BigInt::zero() + }; + let z_i = BigInt::mod_add( + &a[i], + &(e_i * &witness.lambda), + &statement.phi, + ); + Z[i] = z_i; + } + + Self { + A: A.to_vec(), + Z: Z.to_vec(), + phantom: PhantomData, + } + } + + pub fn verify( + proof: &RingPedersenProof, + statement: &RingPedersenStatement, + ) -> FsDkrResult<()> { + let mut hash = H::new(); + for i in 0..M { + hash = H::chain_bigint(hash, &proof.A[i]); + } + + let e: BigInt = hash.result_bigint(); + let bitwise_e: BitVec = BitVec::from_vec(e.to_bytes()); + + for i in 0..M { + let mut e_i = 0; + if bitwise_e[i] { + e_i = 1; + } + + if BigInt::mod_pow(&statement.T, &proof.Z[i], &statement.N) + == BigInt::mod_mul( + &proof.A[i], + &BigInt::mod_pow( + &statement.S, + &BigInt::from(e_i), + &statement.N, + ), + &statement.N, + ) + { + continue; + } else { + return Err(FsDkrError::RingPedersenProofError); + } + } + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use curv::elliptic::curves::secp256_k1::Secp256k1; + use sha2::Sha256; + + #[test] + fn test_ring_pedersen() { + let (statement, witness) = + RingPedersenStatement::::generate(); + let proof = RingPedersenProof::::prove( + &witness, &statement, + ); + assert!(RingPedersenProof::::verify( + &proof, &statement + ) + .is_ok()); + } +} diff --git a/fs-dkr/src/test.rs b/fs-dkr/src/test.rs new file mode 100644 index 00000000..6cd028f9 --- /dev/null +++ b/fs-dkr/src/test.rs @@ -0,0 +1,487 @@ +#[cfg(test)] +mod tests { + use crate::refresh_message::RefreshMessage; + use curv::{ + arithmetic::Converter, + cryptographic_primitives::secret_sharing::feldman_vss::{ + ShamirSecretSharing, VerifiableSS, + }, + elliptic::curves::{secp256_k1::Secp256k1Point, Secp256k1}, + BigInt, + }; + use multi_party_ecdsa::protocols::multi_party_ecdsa::gg_2020::{ + party_i::{verify, Keys}, + state_machine::{ + keygen::{Keygen, LocalKey}, + sign::{CompletedOfflineStage, OfflineStage, SignManual}, + }, + }; + use sha2::Sha256; + + use crate::{add_party_message::JoinMessage, error::FsDkrResult}; + use curv::{ + cryptographic_primitives::{ + hashing::Digest, proofs::sigma_dlog::DLogProof, + }, + elliptic::curves::Scalar, + }; + use paillier::DecryptionKey; + use round_based::dev::Simulation; + use std::collections::HashMap; + + type GE = Secp256k1Point; + + #[test] + fn test1() { + //simulate keygen + let t = 3; + let n = 6; + let mut keys = simulate_keygen(t, n); + + let old_keys = keys.clone(); + simulate_dkr::<{ crate::M_SECURITY }>(&mut keys, None); + + // check that sum of old keys is equal to sum of new keys + let old_linear_secret_key: Vec<_> = (0..old_keys.len()) + .map(|i| old_keys[i].keys_linear.x_i.clone()) + .collect(); + + let new_linear_secret_key: Vec<_> = (0..keys.len()) + .map(|i| keys[i].keys_linear.x_i.clone()) + .collect(); + let indices: Vec<_> = (0..(t + 1) as u16).collect(); + let vss = VerifiableSS:: { + parameters: ShamirSecretSharing { + threshold: t, + share_count: n, + }, + commitments: Vec::new(), + proof: DLogProof::::prove( + &Scalar::random(), + ), + }; + assert_eq!( + vss.reconstruct( + &indices[..], + &old_linear_secret_key[0..(t + 1) as usize] + ), + vss.reconstruct( + &indices[..], + &new_linear_secret_key[0..(t + 1) as usize] + ) + ); + assert_ne!(old_linear_secret_key, new_linear_secret_key); + } + + #[test] + fn test_sign_rotate_sign() { + let mut keys = simulate_keygen(2, 5); + let offline_sign = simulate_offline_stage(keys.clone(), &[1, 2, 3]); + simulate_signing(offline_sign, b"ZenGo"); + simulate_dkr::<{ crate::M_SECURITY }>(&mut keys, None); + let offline_sign = simulate_offline_stage(keys.clone(), &[2, 3, 4]); + simulate_signing(offline_sign, b"ZenGo"); + simulate_dkr::<{ crate::M_SECURITY }>(&mut keys, None); + let offline_sign = simulate_offline_stage(keys, &[1, 3, 5]); + simulate_signing(offline_sign, b"ZenGo"); + } + + #[test] + fn test_remove_sign_rotate_sign() { + let mut keys = simulate_keygen(2, 5); + let offline_sign = simulate_offline_stage(keys.clone(), &[1, 2, 3]); + simulate_signing(offline_sign, b"ZenGo"); + simulate_dkr_removal::<{ crate::M_SECURITY }>( + &mut keys, + [1].to_vec(), + None, + ); + let offline_sign = simulate_offline_stage(keys.clone(), &[2, 3, 4]); + simulate_signing(offline_sign, b"ZenGo"); + simulate_dkr_removal::<{ crate::M_SECURITY }>( + &mut keys, + [1, 2].to_vec(), + None, + ); + let offline_sign = simulate_offline_stage(keys, &[3, 4, 5]); + simulate_signing(offline_sign, b"ZenGo"); + } + + #[test] + fn test_add_party_with_permute() { + fn simulate_replace( + keys: &mut Vec>, + party_indices: &[u16], + old_to_new_map: &HashMap, + t: u16, + n: u16, + ) -> FsDkrResult<()> { + fn generate_join_messages_and_keys( + number_of_new_parties: usize, + ) -> (Vec>, Vec) + { + // the new party generates it's join message to start joining + // the computation + (0..number_of_new_parties) + .map(|_| JoinMessage::distribute()) + .unzip() + } + + fn generate_refresh_parties_replace( + keys: &mut [LocalKey], + old_to_new_map: &HashMap, + join_messages: &[JoinMessage], + ) -> ( + Vec>, + Vec, + ) { + let new_n = (&keys.len() + join_messages.len()) as u16; + keys.iter_mut() + .map(|key| { + RefreshMessage::replace( + join_messages, + key, + old_to_new_map, + key.t, + new_n, + ) + .unwrap() + }) + .unzip() + } + + // each party that wants to join generates a join message and a pair + // of paillier keys. + let (mut join_messages, new_keys) = + generate_join_messages_and_keys::<{ crate::M_SECURITY }>( + party_indices.len(), + ); + + // each new party has to be informed through offchannel + // communication what party index it has been assigned + // (the information is public). + for (join_message, party_index) in + join_messages.iter_mut().zip(party_indices) + { + join_message.party_index = Some(*party_index); + } + + // each existing party has to generate it's refresh message aware of + // the new parties + let (refresh_messages, dk_keys) = generate_refresh_parties_replace( + keys, + &old_to_new_map, + join_messages.as_slice(), + ); + let mut new_keys_vec: Vec<(u16, LocalKey)> = + Vec::with_capacity(keys.len() + join_messages.len()); + // all existing parties rotate aware of the join_messages + for i in 0..keys.len() as usize { + RefreshMessage::collect( + refresh_messages.as_slice(), + &mut keys[i], + dk_keys[i].clone(), + join_messages.as_slice(), + t, + ) + .expect(""); + new_keys_vec.push((keys[i].i - 1, keys[i].clone())); + } + + // all new parties generate a local key + for (join_message, dk) in join_messages.iter().zip(new_keys) { + let party_index = join_message.party_index.unwrap(); + let local_key = join_message.collect( + refresh_messages.as_slice(), + dk, + join_messages.as_slice(), + t, + n, + t, + )?; + + new_keys_vec.push((party_index - 1, local_key)); + } + + new_keys_vec.sort_by(|a, b| a.0.cmp(&b.0)); + let keys_replacements = new_keys_vec + .iter() + .map(|a| a.1.clone()) + .collect::>>(); + *keys = keys_replacements; + Ok(()) + } + + let t = 2; + let n = 7; + + let all_keys = simulate_keygen(t, n); + // Remove the 2nd and 7th party + let mut keys = all_keys.clone(); + keys.remove(6); + keys.remove(1); + + let mut old_to_new_map: HashMap = HashMap::new(); + old_to_new_map.insert(1, 4); + old_to_new_map.insert(3, 1); + old_to_new_map.insert(4, 3); + old_to_new_map.insert(5, 6); + old_to_new_map.insert(6, 5); + + // Simulate the replace + simulate_replace::<{ crate::M_SECURITY }>( + &mut keys, + &[2, 7], + &old_to_new_map, + t, + n, + ) + .unwrap(); + // check that sum of old keys is equal to sum of new keys + let old_linear_secret_key: Vec<_> = (0..all_keys.len()) + .map(|i| all_keys[i].keys_linear.x_i.clone()) + .collect(); + + let new_linear_secret_key: Vec<_> = (0..keys.len()) + .map(|i| keys[i].keys_linear.x_i.clone()) + .collect(); + let indices: Vec<_> = (0..(t + 1) as u16).collect(); + let vss = VerifiableSS:: { + parameters: ShamirSecretSharing { + threshold: t, + share_count: n, + }, + commitments: Vec::new(), + proof: DLogProof::::prove( + &Scalar::random(), + ), + }; + assert_eq!( + vss.reconstruct( + &indices[..], + &old_linear_secret_key[0..(t + 1) as usize] + ), + vss.reconstruct( + &indices[..], + &new_linear_secret_key[0..(t + 1) as usize] + ) + ); + assert_ne!(old_linear_secret_key, new_linear_secret_key); + + let offline_sign = simulate_offline_stage(keys, &[1, 2, 7]); + simulate_signing(offline_sign, b"ZenGo"); + } + + #[test] + fn test_change_threshold_sign_rotate_sign() { + let mut keys = simulate_keygen(2, 5); + let offline_sign = simulate_offline_stage(keys.clone(), &[1, 2, 3]); + simulate_signing(offline_sign, b"ZenGo"); + // Change threshold to 1 (i.e quorum size = 2). + simulate_dkr::<{ crate::M_SECURITY }>(&mut keys, Some(1)); + let offline_sign = simulate_offline_stage(keys.clone(), &[3, 4]); + simulate_signing(offline_sign, b"ZenGo"); + // Change threshold to back to 2 (i.e quorum size = 3). + simulate_dkr::<{ crate::M_SECURITY }>(&mut keys, Some(2)); + let offline_sign = simulate_offline_stage(keys, &[1, 3, 5]); + simulate_signing(offline_sign, b"ZenGo"); + } + + fn simulate_keygen(t: u16, n: u16) -> Vec> { + //simulate keygen + let mut simulation = Simulation::new(); + simulation.enable_benchmarks(false); + + for i in 1..=n { + simulation.add_party(Keygen::new(i, t, n).unwrap()); + } + + simulation.run().unwrap() + } + + fn simulate_dkr_removal( + keys: &mut Vec>, + remove_party_indices: Vec, + new_t_option: Option, + ) { + let mut broadcast_messages: HashMap< + usize, + Vec>, + > = HashMap::new(); + let mut new_dks: HashMap = HashMap::new(); + let mut refresh_messages: Vec> = + Vec::new(); + let mut party_key: HashMap> = HashMap::new(); + // TODO: Verify this is correct + let new_n = keys.len() as u16; + let current_t = keys[0].t; + let new_t = new_t_option.unwrap_or(current_t); + for key in keys.iter_mut() { + let (refresh_message, new_dk) = + RefreshMessage::distribute(key.i, key, new_t, new_n).unwrap(); + refresh_messages.push(refresh_message.clone()); + new_dks.insert(refresh_message.party_index.into(), new_dk); + party_key.insert(refresh_message.party_index.into(), key.clone()); + } + + for refresh_message in refresh_messages.iter() { + broadcast_messages + .insert(refresh_message.party_index.into(), Vec::new()); + } + + for refresh_message in refresh_messages.iter_mut() { + if !remove_party_indices + .contains(&refresh_message.party_index.into()) + { + refresh_message.remove_party_indices = + remove_party_indices.clone(); + } else { + let mut new_remove_party_indices = remove_party_indices.clone(); + new_remove_party_indices.retain(|value| { + *value != refresh_message.party_index.into() + }); + refresh_message.remove_party_indices = new_remove_party_indices; + } + + for (party_index, refresh_bucket) in broadcast_messages.iter_mut() { + if refresh_message + .remove_party_indices + .contains(&(*party_index as u16)) + { + continue; + } + refresh_bucket.push(refresh_message.clone()); + } + } + + for remove_party_index in remove_party_indices.iter() { + assert_eq!( + broadcast_messages[&(*remove_party_index as usize)].len(), + 1 + ); + } + + // keys will be updated to refreshed values + for (party, key) in party_key.iter_mut() { + if remove_party_indices.contains(&(*party as u16)) { + continue; + } + + RefreshMessage::collect( + broadcast_messages[party].clone().as_slice(), + key, + new_dks[party].clone(), + &[], + current_t, + ) + .expect(""); + } + + for remove_party_index in remove_party_indices { + let result = RefreshMessage::collect( + &broadcast_messages[&(remove_party_index as usize)], + &mut keys[remove_party_index as usize], + new_dks[&(remove_party_index as usize)].clone(), + &[], + current_t, + ); + assert!(result.is_err()); + } + } + + fn simulate_dkr( + keys: &mut Vec>, + new_t_option: Option, + ) -> ( + Vec>, + Vec, + ) { + let mut broadcast_vec: Vec> = + Vec::new(); + let mut new_dks: Vec = Vec::new(); + let keys_len = keys.len(); + let current_t = keys[0].t; + let new_t = new_t_option.unwrap_or(current_t); + for key in keys.iter_mut() { + let (refresh_message, new_dk) = + RefreshMessage::distribute(key.i, key, new_t, keys_len as u16) + .unwrap(); + broadcast_vec.push(refresh_message); + new_dks.push(new_dk); + } + + // keys will be updated to refreshed values + for i in 0..keys.len() as usize { + RefreshMessage::collect( + &broadcast_vec, + &mut keys[i], + new_dks[i].clone(), + &[], + current_t, + ) + .expect(""); + } + + (broadcast_vec, new_dks) + } + + fn simulate_offline_stage( + local_keys: Vec>, + s_l: &[u16], + ) -> Vec { + let mut simulation = Simulation::new(); + simulation.enable_benchmarks(false); + + for (i, &keygen_i) in (1..).zip(s_l) { + simulation.add_party( + OfflineStage::new( + i, + s_l.to_vec(), + local_keys[usize::from(keygen_i - 1)].clone(), + ) + .unwrap(), + ); + } + + simulation.run().unwrap() + } + + fn simulate_signing(offline: Vec, message: &[u8]) { + let message = create_hash(&[&BigInt::from_bytes(message)]); + let pk = &offline[0].public_key(); + + let parties = offline + .iter() + .map(|o| SignManual::new(message.clone(), o.clone())) + .collect::, _>>() + .unwrap(); + let (parties, local_sigs): (Vec<_>, Vec<_>) = + parties.into_iter().unzip(); + // parties.remove(0).complete(&local_sigs[1..]).unwrap(); + let local_sigs_except = |i: usize| { + let mut v = vec![]; + v.extend_from_slice(&local_sigs[..i]); + if i + 1 < local_sigs.len() { + v.extend_from_slice(&local_sigs[i + 1..]); + } + v + }; + + assert!(parties + .into_iter() + .enumerate() + .map(|(i, p)| p.complete(&local_sigs_except(i)).unwrap()) + .all(|signature| verify(&signature, &pk, &message).is_ok())); + } + + fn create_hash(big_ints: &[&BigInt]) -> BigInt { + let hasher = Sha256::new(); + + for value in big_ints { + hasher.clone().chain(&BigInt::to_bytes(value)); + } + + let result_hex = hasher.finalize(); + BigInt::from_bytes(&result_hex[..]) + } +} diff --git a/fs-dkr/src/zk_pdl_with_slack.rs b/fs-dkr/src/zk_pdl_with_slack.rs new file mode 100644 index 00000000..b70f0ae6 --- /dev/null +++ b/fs-dkr/src/zk_pdl_with_slack.rs @@ -0,0 +1,349 @@ +#![allow(non_snake_case)] + +//! We use the proof as given in proof PIi in https://eprint.iacr.org/2016/013.pdf. +//! This proof ws taken from the proof 6.3 (left side ) in https://www.cs.unc.edu/~reiter/papers/2004/IJIS.pdf +//! +//! Statement: (c, pk, Q, G) +//! witness (x, r) such that Q = xG, c = Enc(pk, x, r) +//! note that because of the range proof, the proof has a slack in the range: x +//! in [-q^3, q^3] + +use std::marker::PhantomData; + +use crate::error::{FsDkrError, FsDkrResult}; +use curv::{ + arithmetic::traits::*, + cryptographic_primitives::hashing::{Digest, DigestExt}, + elliptic::curves::{Curve, Point, Scalar, Secp256k1}, + BigInt, +}; +use paillier::EncryptionKey; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct PDLwSlackStatement { + pub ciphertext: BigInt, + pub ek: EncryptionKey, + pub Q: Point, + pub G: Point, + pub h1: BigInt, + pub h2: BigInt, + pub N_tilde: BigInt, +} +#[derive(Clone)] +pub struct PDLwSlackWitness { + pub x: Scalar, + pub r: BigInt, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(bound = "E: Curve, H: Digest + Clone")] +pub struct PDLwSlackProof { + z: BigInt, + u1: Point, + u2: BigInt, + u3: BigInt, + s1: BigInt, + s2: BigInt, + s3: BigInt, + _phantom: PhantomData, +} + +impl PDLwSlackProof { + pub fn prove( + witness: &PDLwSlackWitness, + statement: &PDLwSlackStatement, + ) -> Self { + let q3 = Scalar::::group_order().pow(3); + let q_N_tilde = Scalar::::group_order() * &statement.N_tilde; + let q3_N_tilde = &q3 * &statement.N_tilde; + + let alpha = BigInt::sample_below(&q3); + let one = BigInt::one(); + let beta = BigInt::sample_range(&one, &(&statement.ek.n - &one)); + let rho = BigInt::sample_below(&q_N_tilde); + let gamma = BigInt::sample_below(&q3_N_tilde); + + let z = commitment_unknown_order( + &statement.h1, + &statement.h2, + &statement.N_tilde, + &witness.x.to_bigint(), + &rho, + ); + let u1 = statement.G.clone() * Scalar::::from(&alpha); + let u2 = commitment_unknown_order( + &(&statement.ek.n + BigInt::one()), + &beta, + &statement.ek.nn, + &alpha, + &statement.ek.n, + ); + let u3 = commitment_unknown_order( + &statement.h1, + &statement.h2, + &statement.N_tilde, + &alpha, + &gamma, + ); + + let e = H::new() + .chain_bigint(&BigInt::from_bytes(&statement.G.to_bytes(true))) + .chain_bigint(&BigInt::from_bytes(&statement.Q.to_bytes(true))) + .chain_bigint(&statement.ciphertext) + .chain_bigint(&z) + .chain_bigint(&BigInt::from_bytes(&u1.to_bytes(true))) + .chain_bigint(&u2) + .chain_bigint(&u3) + .result_bigint(); + + let s1 = &e * witness.x.to_bigint() + alpha; + let s2 = commitment_unknown_order( + &witness.r, + &beta, + &statement.ek.n, + &e, + &BigInt::one(), + ); + let s3 = &e * rho + gamma; + + PDLwSlackProof { + z, + u1, + u2, + u3, + s1, + s2, + s3, + _phantom: PhantomData, + } + } + + pub fn verify(&self, statement: &PDLwSlackStatement) -> FsDkrResult<()> { + let e = H::new() + .chain_bigint(&BigInt::from_bytes(&statement.G.to_bytes(true))) + .chain_bigint(&BigInt::from_bytes(&statement.Q.to_bytes(true))) + .chain_bigint(&statement.ciphertext) + .chain_bigint(&self.z) + .chain_bigint(&BigInt::from_bytes(&self.u1.to_bytes(true))) + .chain_bigint(&self.u2) + .chain_bigint(&self.u3) + .result_bigint(); + + let g_s1 = statement.G.clone() * Scalar::::from(&self.s1); + let e_fe_neg = Scalar::::from(&(Scalar::::group_order() - &e)); + let y_minus_e = statement.Q.clone() * e_fe_neg; + let u1_test = g_s1 + y_minus_e; + + let u2_test_tmp = commitment_unknown_order( + &(&statement.ek.n + BigInt::one()), + &self.s2, + &statement.ek.nn, + &self.s1, + &statement.ek.n, + ); + let u2_test = commitment_unknown_order( + &u2_test_tmp, + &statement.ciphertext, + &statement.ek.nn, + &BigInt::one(), + &(-&e), + ); + + let u3_test_tmp = commitment_unknown_order( + &statement.h1, + &statement.h2, + &statement.N_tilde, + &self.s1, + &self.s3, + ); + let u3_test = commitment_unknown_order( + &u3_test_tmp, + &self.z, + &statement.N_tilde, + &BigInt::one(), + &(-&e), + ); + if self.u1 == u1_test && self.u2 == u2_test && self.u3 == u3_test { + Ok(()) + } else { + Err(FsDkrError::PDLwSlackProof { + is_u1_eq: self.u1 == u1_test, + is_u2_eq: self.u2 == u2_test, + is_u3_eq: self.u3 == u3_test, + }) + } + } +} + +pub fn commitment_unknown_order( + h1: &BigInt, + h2: &BigInt, + N_tilde: &BigInt, + x: &BigInt, + r: &BigInt, +) -> BigInt { + let h1_x = BigInt::mod_pow(h1, x, N_tilde); + let h2_r = { + if r < &BigInt::zero() { + let h2_inv = BigInt::mod_inv(h2, N_tilde).unwrap(); + BigInt::mod_pow(&h2_inv, &(-r), N_tilde) + } else { + BigInt::mod_pow(h2, r, N_tilde) + } + }; + BigInt::mod_mul(&h1_x, &h2_r, N_tilde) +} + +#[cfg(test)] +mod test { + use super::*; + use curv::{ + elliptic::curves::secp256_k1::{Secp256k1Point, Secp256k1Scalar}, + BigInt, + }; + use paillier::{ + core::Randomness, + traits::{EncryptWithChosenRandomness, KeyGeneration}, + Paillier, RawPlaintext, + }; + use sha2::Sha256; + use zk_paillier::zkproofs::{CompositeDLogProof, DLogStatement}; + + type GE = Secp256k1Point; + type FE = Secp256k1Scalar; + + #[test] + fn test_zk_pdl_with_slack() { + // N_tilde, h1, h2 generation + let (ek_tilde, dk_tilde) = + Paillier::keypair_with_modulus_size(crate::PAILLIER_KEY_SIZE) + .keys(); + // note: safe primes should be used: + // let (ek_tilde, dk_tilde) = Paillier::keypair_safe_primes().keys(); + let one = BigInt::one(); + let phi = (&dk_tilde.p - &one) * (&dk_tilde.q - &one); + let h1 = BigInt::sample_below(&phi); + let S = BigInt::from(2).pow(256 as u32); + let xhi = BigInt::sample_below(&S); + let h1_inv = BigInt::mod_inv(&h1, &ek_tilde.n).unwrap(); + let h2 = BigInt::mod_pow(&h1_inv, &xhi, &ek_tilde.n); + let statement = DLogStatement { + N: ek_tilde.n.clone(), + g: h1.clone(), + ni: h2.clone(), + }; + + let composite_dlog_proof = CompositeDLogProof::prove(&statement, &xhi); + + // generate the scalar secret and Paillier encrypt it + let (ek, _dk) = + Paillier::keypair_with_modulus_size(crate::PAILLIER_KEY_SIZE) + .keys(); + // note: safe primes should be used here as well: + // let (ek_tilde, dk_tilde) = Paillier::keypair_safe_primes().keys(); + let randomness = Randomness::sample(&ek); + let x: Scalar = Scalar::::random(); + + let Q = Point::::generator().to_point() * &x; + + let c = Paillier::encrypt_with_chosen_randomness( + &ek, + RawPlaintext::from(x.to_bigint().clone()), + &randomness, + ) + .0 + .into_owned(); + + // Generate PDL with slack statement, witness and proof + let pdl_w_slack_statement = PDLwSlackStatement { + ciphertext: c, + ek, + Q, + G: Point::::generator().to_point(), + h1, + h2, + N_tilde: ek_tilde.n, + }; + + let pdl_w_slack_witness = PDLwSlackWitness { x, r: randomness.0 }; + + let proof = PDLwSlackProof::::prove( + &pdl_w_slack_witness, + &pdl_w_slack_statement, + ); + // verify h1,h2, N_tilde + let setup_result = composite_dlog_proof.verify(&statement); + assert!(setup_result.is_ok()); + let result = proof.verify(&pdl_w_slack_statement); + assert!(result.is_ok()); + } + + #[test] + #[should_panic] + fn test_zk_pdl_with_slack_soundness() { + // N_tilde, h1, h2 generation + let (ek_tilde, dk_tilde) = + Paillier::keypair_with_modulus_size(crate::PAILLIER_KEY_SIZE) + .keys(); + // note: safe primes should be used: + // let (ek_tilde, dk_tilde) = Paillier::keypair_safe_primes().keys(); + let one = BigInt::one(); + let phi = (&dk_tilde.p - &one) * (&dk_tilde.q - &one); + let h1 = BigInt::sample_below(&phi); + let S = BigInt::from(2).pow(256 as u32); + let xhi = BigInt::sample_below(&S); + let h1_inv = BigInt::mod_inv(&h1, &ek_tilde.n).unwrap(); + let h2 = BigInt::mod_pow(&h1_inv, &xhi, &ek_tilde.n); + let statement = DLogStatement { + N: ek_tilde.n.clone(), + g: h1.clone(), + ni: h2.clone(), + }; + + let composite_dlog_proof = CompositeDLogProof::prove(&statement, &xhi); + + // generate the scalar secret and Paillier encrypt it + let (ek, _dk) = + Paillier::keypair_with_modulus_size(crate::PAILLIER_KEY_SIZE) + .keys(); + // note: safe primes should be used here as well: + // let (ek_tilde, dk_tilde) = Paillier::keypair_safe_primes().keys(); + let randomness = Randomness::sample(&ek); + let x: Scalar = Scalar::::random(); + + let Q = Point::::generator().to_point() * &x; + + // here we encrypt x + 1 instead of x: + let c = Paillier::encrypt_with_chosen_randomness( + &ek, + RawPlaintext::from(x.to_bigint().clone() + BigInt::one()), + &randomness, + ) + .0 + .into_owned(); + + // Generate PDL with slack statement, witness and proof + let pdl_w_slack_statement = PDLwSlackStatement { + ciphertext: c, + ek, + Q, + G: Point::::generator().to_point(), + h1, + h2, + N_tilde: ek_tilde.n, + }; + + let pdl_w_slack_witness = PDLwSlackWitness { x, r: randomness.0 }; + + let proof = PDLwSlackProof::::prove( + &pdl_w_slack_witness, + &pdl_w_slack_statement, + ); + // verify h1,h2, N_tilde + let setup_result = composite_dlog_proof.verify(&statement); + assert!(setup_result.is_ok()); + let result = proof.verify(&pdl_w_slack_statement); + assert!(result.is_ok()); + } +} diff --git a/multi-party-ecdsa/.github/workflows/build.yml b/multi-party-ecdsa/.github/workflows/build.yml new file mode 100644 index 00000000..cf044f76 --- /dev/null +++ b/multi-party-ecdsa/.github/workflows/build.yml @@ -0,0 +1,28 @@ +name: Build + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + schedule: + - cron: '0 5 * * *' + workflow_call: + +env: + CARGO_TERM_COLOR: always + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: Swatinem/rust-cache@v1 + - name: Build + run: cargo build --verbose + - name: Run tests + run: cargo test --verbose + - name: Check formatting + run: cargo fmt --all -- --check + - name: Run clippy + run: cargo clippy -- -D clippy::all diff --git a/multi-party-ecdsa/.github/workflows/publish.yml b/multi-party-ecdsa/.github/workflows/publish.yml new file mode 100644 index 00000000..9a2ddc31 --- /dev/null +++ b/multi-party-ecdsa/.github/workflows/publish.yml @@ -0,0 +1,19 @@ +name: Publish + +on: + push: + tags: + - v*.*.* + +jobs: + build: + uses: ZenGo-X/multi-party-ecdsa/.github/workflows/build.yml@master + publish: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Publish crate + env: + TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} + run: | + cargo publish --token "$TOKEN" diff --git a/multi-party-ecdsa/.gitignore b/multi-party-ecdsa/.gitignore new file mode 100644 index 00000000..c3063c44 --- /dev/null +++ b/multi-party-ecdsa/.gitignore @@ -0,0 +1,17 @@ +# Generated by Cargo +# will have compiled files and executables +/target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +.idea +.DS_Store + +keys*.store +signature + diff --git a/multi-party-ecdsa/Cargo.toml b/multi-party-ecdsa/Cargo.toml new file mode 100644 index 00000000..275a5cae --- /dev/null +++ b/multi-party-ecdsa/Cargo.toml @@ -0,0 +1,68 @@ +[package] +name = "multi-party-ecdsa" +version = "0.8.2" +edition = "2021" +authors = [ + "Gary ", + "Omer " +] +keywords = [ + "ecdsa", + "multi-party-ecdsa", + "signature", + "rust", + "secret-shares", + "blockchain", + "cryptography", + "cryptocurrency" +] + +homepage = "https://github.com/KZen-networks/multi-party-ecdsa" +repository = "https://github.com/KZen-networks/multi-party-ecdsa" +license = "GPL-3.0-or-later" +categories = ["cryptography"] + +[lib] +crate-type = ["lib"] + +[features] +default = ["curv-kzen/rust-gmp-kzen"] + +[dependencies] +curv-kzen.workspace = true +centipede.workspace = true +zk-paillier.workspace = true +sha2.workspace = true +paillier.workspace = true +round-based.workspace = true +thiserror.workspace = true +serde.workspace = true +zeroize.workspace = true + +subtle = { version = "2" } +derivative = "2.2.0" +log = "0.4.17" + +[dev-dependencies] +criterion = "0.3" +aes-gcm = "0.9.4" +hex = "0.4" +tokio = { version = "1", default-features = false, features = ["macros"] } +futures = "0.3" +rocket = { version = "0.5.0-rc.1", default-features = false, features = ["json"] } +reqwest = { version = "0.9", default-features = false } +uuid = { version = "0.8", features = ["v4"] } +serde_json = "1.0" +rand = "0.8" +surf = "2" +async-sse = "5" +anyhow = "1" +structopt = "0.3" +secp256k1 = { version = "0.20", features = ["global-context"]} + +thiserror = "1.0.23" +round-based = { git = "https://github.com/webb-tools/round-based-protocol", features = ["dev"] } + +[[example]] +name = "common" +crate-type = ["lib"] diff --git a/multi-party-ecdsa/LICENSE b/multi-party-ecdsa/LICENSE new file mode 100644 index 00000000..94a9ed02 --- /dev/null +++ b/multi-party-ecdsa/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/multi-party-ecdsa/README.md b/multi-party-ecdsa/README.md new file mode 100644 index 00000000..d04312e8 --- /dev/null +++ b/multi-party-ecdsa/README.md @@ -0,0 +1,155 @@ +# Multi-party ECDSA + +[![License: GPL v3](https://img.shields.io/badge/License-GPL%20v3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0) + +This project is a Rust implementation of {t,n}-threshold ECDSA (elliptic curve digital signature algorithm). + +Threshold ECDSA includes two protocols: + +- Key Generation for creating secret shares. +- Signing for using the secret shares to generate a signature. + +ECDSA is used extensively for crypto-currencies such as Bitcoin, Ethereum (secp256k1 curve), NEO (NIST P-256 curve) and much more. +This library can be used to create MultiSig and ThresholdSig crypto wallet. For a full background on threshold signatures please read our Binance academy article [Threshold Signatures Explained](https://academy.binance.com/en/articles/threshold-signatures-explained). + +## Library Introduction +The library was built with four core design principles in mind: +1. Multi-protocol support +2. Built for cryptography engineers +3. Foolproof +4. Black box use of cryptographic primitives + +To learn about the core principles as well as on the [audit](https://github.com/KZen-networks/multi-party-ecdsa/tree/master/audits) process and security of the library, please read our [Intro to multiparty ecdsa library](https://zengo.com/introducing-multi-party-ecdsa-library/) blog post. + +## Use It + + +The library implements four different protocols for threshold ECDSA. The protocols presents different tradeoffs in terms of parameters, security assumptions and efficiency. + +| Protocol | High Level code | +| -------------------------------------------- | -------------------------------------------- | +| Lindell 17 [1] | [Gotham-city](https://github.com/KZen-networks/gotham-city) (accepted to [CIW19](https://ifca.ai/fc19/ciw/program.html)) is a two party bitcoin wallet, including benchmarks. [KMS](https://github.com/KZen-networks/kms-secp256k1) is a Rust wrapper library that implements a general purpose two party key management system. [thresh-sig-js](https://github.com/KZen-networks/thresh-sig-js) is a Javascript SDK | +| Gennaro, Goldfeder 19 [2] ([video](https://www.youtube.com/watch?v=PdfDZIwuZm0)) | [tss-ecdsa-cli](https://github.com/cryptochill/tss-ecdsa-cli) is a wrapper CLI for full threshold access structure, including network and threshold HD keys ([BIP32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki)). See [Demo](https://github.com/KZen-networks/multi-party-ecdsa#run-demo) in this library to get better low level understanding| +|Castagnos et. al. 19 [3]| Currently enabled as a feature in this library. To Enable, build with `--features=cclst`. to Test, use `cargo test --features=cclst -- --test-threads=1` | +| Gennaro, Goldfeder 20 [4] | A full threshold protocol that supports identifying malicious parties. If signing fails - a list of malicious parties is returned. The protocol requires only a broadcast channel (all messages are broadcasted)| + +## Run GG20 Demo + +In the following steps we will generate 2-of-3 threshold signing key and sign a message with 2 parties. + +### Setup + +1. You need [Rust](https://rustup.rs/) and [GMP library](https://gmplib.org) (optionally) to be installed on your computer. +2. - Run `cargo build --release --examples` + - Don't have GMP installed? Use this command instead: + ```bash + cargo build --release --examples --no-default-features --features curv-kzen/num-bigint + ``` + But keep in mind that it will be less efficient. + + Either of commands will produce binaries into `./target/release/examples/` folder. +3. `cd ./target/release/examples/` + +### Start an SM server + +`./gg20_sm_manager` + +That will start an HTTP server on `http://127.0.0.1:8000`. Other parties will use that server in order to communicate with +each other. Note that communication channels are neither encrypted nor authenticated. In production, you must encrypt and +authenticate parties messages. + +### Run Keygen + +Open 3 terminal tabs for each party. Run: + +1. `./gg20_keygen -t 1 -n 3 -i 1 --output local-share1.json` +2. `./gg20_keygen -t 1 -n 3 -i 2 --output local-share2.json` +3. `./gg20_keygen -t 1 -n 3 -i 3 --output local-share3.json` + +Each command corresponds to one party. Once keygen is completed, you'll have 3 new files: +`local-share1.json`, `local-share2.json`, `local-share3.json` corresponding to local secret +share of each party. + +### Run Signing + +Since we use 2-of-3 scheme (`t=1 n=3`), any two parties can sign a message. Run: + +1. `./gg20_signing -p 1,2 -d "hello" -l local-share1.json` +2. `./gg20_signing -p 1,2 -d "hello" -l local-share2.json` + +Each party will produce a resulting signature. `-p 1,2` specifies indexes of parties +who attends in signing (each party has an associated index given at keygen, see argument +`-i`), `-l file.json` sets a path to a file with secret local share, and `-d "hello"` +is a message being signed. + +### Running Demo on different computers + +While previous steps show how to run keygen & signing on local computer, you actually can +run each party on dedicated machine. To do this, you should ensure that parties can reach +SM Server, and specify its address via command line argument, eg: + +`./gg20_keygen --address http://10.0.1.9:8000/ ...` + +## Run GG18 Demo + +The following steps are for setup, key generation with `n` parties and signing with `t+1` parties. + +### Setup + +1. We use shared state machine architecture (see [white city](https://github.com/KZen-networks/white-city)). The parameters `parties` and `threshold` can be configured by changing the file: `param`. a keygen will run with `parties` parties and signing will run with any subset of `threshold + 1` parties. `param` file should be located in the same path of the client software. + +2. Install [Rust](https://rustup.rs/). Run `cargo build --release --examples` (it will build into `/target/release/examples/`) + +3. Run the shared state machine: `./gg18_sm_manager`. By default, it's configured to be in `127.0.0.1:8000`, this can be changed in `Rocket.toml` file. The `Rocket.toml` file should be in the same folder you run `sm_manager` from. + +### KeyGen + +run `gg18_keygen_client` as follows: `./gg18_keygen_client http://127.0.0.1:8000 keys.store`. Replace IP and port with the ones configured in setup. Once `n` parties join the application will run till finish. At the end each party will get a local keys file `keys.store` (change filename in command line). This contains secret and public data of the party after keygen. The file therefore should remain private. + +### Sign + +Run `./gg18_sign_client`. The application should be in the same folder as the `keys.store` file (or custom filename generated in keygen). the application takes three arguments: `IP:port` as in keygen, `filename` and message to be signed: `./gg18_sign_client http://127.0.0.1:8001 keys.store "KZen Networks"`. The same message should be used by all signers. Once `t+1` parties join the protocol will run and will output to screen signature (R,s). + +The `./gg18_sign_client` executable initially tries to unhex its input message (the third parameter). Before running ensure two things: + +1. If you want to pass a binary message to be signed - hex it. +2. If you want to pass a textual message in a non-hex form, make sure it can't be unhexed. +Simply put, the safest way to use the signing binary is to just always hex your messages before passing them to the `./gg18_sign_client` executable. + +#### Example +To sign the message `hello world`, first calculate its hexadecimal representation. This yields the `68656c6c6f20776f726c64`. +Then, run: +```bash +./gg18_sign_client http://127.0.0.1:8000 keys.store "68656c6c6f20776f726c64" +``` + +### GG18 demo + +Run `./run.sh` (located in `/demo` folder) in the main folder. Move `params` file to the same folder as the executables (usually `/target/release/examples`). The script will spawn a shared state machine, clients in the number of parties and signing requests for the `threshold + 1` first parties. + +`gg18_sm_manager` rocket server runs in _production_ mode by default. You may modify the `./run.sh` to config it to run in different environments. For example, to run rocket server in _development_: +``` +ROCKET_ENV=development ./target/release/examples/sm_manager +``` + +## Contributions & Development Process + +The contribution workflow is described in [CONTRIBUTING.md](CONTRIBUTING.md), in addition **the [Rust utilities wiki](https://github.com/KZen-networks/rust-utils/wiki) contains information on workflow and environment set-up**. + +## License + +Multi-party ECDSA is released under the terms of the GPL-3.0 license. See [LICENSE](LICENSE) for more information. + +## Contact + +Feel free to [reach out](mailto:github@kzencorp.com) or join ZenGo X [Telegram](https://t.me/joinchat/ET1mddGXRoyCxZ-7) for discussions on code and research. + +## References + +[1] + +[2] + +[3] + +[4] diff --git a/multi-party-ecdsa/audits/REPORT_final_2019-10-22.pdf b/multi-party-ecdsa/audits/REPORT_final_2019-10-22.pdf new file mode 100644 index 00000000..9e7c2f8d Binary files /dev/null and b/multi-party-ecdsa/audits/REPORT_final_2019-10-22.pdf differ diff --git a/multi-party-ecdsa/docs/diagrams/keygen1.dot b/multi-party-ecdsa/docs/diagrams/keygen1.dot new file mode 100644 index 00000000..cea99d97 --- /dev/null +++ b/multi-party-ecdsa/docs/diagrams/keygen1.dot @@ -0,0 +1,23 @@ +// Lindell 2party ECDSA keygen party 1 +digraph { + start [shape=doublecircle] + stop [shape=doublecircle] + A + B + C + D + E + start -> A [label="create commitments, ID"] + "check1" [shape=diamond] + "check2" [shape=diamond] + A -> "check1" [label="verify_and_decommit, ID"] + "check1" -> B [label="yes,ID"] + "check1" -> stop [label=no] + B -> C [label="generate_keypair_and_encrypted_share,ID"] + C -> "check2" [label="generate_proof_correct_key, ID"] + "check2" -> D [label="yes,ID"] + "check2" -> stop [label=no] + D -> E [label="generate_range_proof,ID"] + E -> stop [label="write_to_db,ID"] + +} diff --git a/multi-party-ecdsa/docs/diagrams/keygen2.dot b/multi-party-ecdsa/docs/diagrams/keygen2.dot new file mode 100644 index 00000000..ee3a4951 --- /dev/null +++ b/multi-party-ecdsa/docs/diagrams/keygen2.dot @@ -0,0 +1,27 @@ +// Lindell 2party ECDSA keygen party 2 +digraph { + start [shape=doublecircle] + stop [shape=doublecircle] + A + B + C + D + E + start -> A [label="begin session"] + "check1" [shape=diamond] + "check2" [shape=diamond] + "check3" [shape=diamond] + A -> B [label="create, ID"] + B -> "check1" [label="verify_commitments_and_dlog_proof, ID"] + "check1" -> C [label="yes,ID"] + "check1" -> stop [label=no] + C -> D [label="generate_correct_key_challenge,ID"] + D -> "check2" [label="verify_correct_key, ID"] + "check2" -> E [label="yes,ID"] + "check2" -> stop [label=no] + E -> "check3" [label="verify_range_proof, ID"] + "check3" -> F [label="yes,ID"] + "check3" -> stop [label=no] + F -> stop [label="write_to_local_storage,ID"] + +} diff --git a/multi-party-ecdsa/docs/gg19.pdf b/multi-party-ecdsa/docs/gg19.pdf new file mode 100644 index 00000000..413ba893 Binary files /dev/null and b/multi-party-ecdsa/docs/gg19.pdf differ diff --git a/multi-party-ecdsa/examples/common.rs b/multi-party-ecdsa/examples/common.rs new file mode 100644 index 00000000..159892da --- /dev/null +++ b/multi-party-ecdsa/examples/common.rs @@ -0,0 +1,235 @@ +#![allow(dead_code)] + +use std::{env, thread, time, time::Duration}; + +use aes_gcm::{ + aead::{Aead, NewAead}, + Aes256Gcm, Nonce, +}; +use rand::{rngs::OsRng, RngCore}; + +use curv::{ + arithmetic::traits::Converter, + elliptic::curves::{secp256_k1::Secp256k1, Point, Scalar}, + BigInt, +}; + +use reqwest::Client; +use serde::{Deserialize, Serialize}; + +pub type Key = String; + +#[allow(dead_code)] +pub const AES_KEY_BYTES_LEN: usize = 32; + +#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] +pub struct AEAD { + pub ciphertext: Vec, + pub tag: Vec, +} + +#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] +pub struct PartySignup { + pub number: u16, + pub uuid: String, +} + +#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] +pub struct Index { + pub key: Key, +} + +#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] +pub struct Entry { + pub key: Key, + pub value: String, +} + +#[derive(Serialize, Deserialize)] +pub struct Params { + pub parties: String, + pub threshold: String, +} + +#[allow(dead_code)] +pub fn aes_encrypt(key: &[u8], plaintext: &[u8]) -> AEAD { + let aes_key = aes_gcm::Key::from_slice(key); + let cipher = Aes256Gcm::new(aes_key); + + let mut nonce = [0u8; 12]; + OsRng.fill_bytes(&mut nonce); + let nonce = Nonce::from_slice(&nonce); + + let ciphertext = cipher + .encrypt(nonce, plaintext) + .expect("encryption failure!"); + + AEAD { + ciphertext, + tag: nonce.to_vec(), + } +} + +#[allow(dead_code)] +pub fn aes_decrypt(key: &[u8], aead_pack: AEAD) -> Vec { + let aes_key = aes_gcm::Key::from_slice(key); + let nonce = Nonce::from_slice(&aead_pack.tag); + let gcm = Aes256Gcm::new(aes_key); + + let out = gcm.decrypt(nonce, aead_pack.ciphertext.as_slice()); + out.unwrap() +} + +pub fn postb(client: &Client, path: &str, body: T) -> Option +where + T: serde::ser::Serialize, +{ + let addr = env::args() + .nth(1) + .unwrap_or_else(|| "http://127.0.0.1:8001".to_string()); + let retries = 3; + let retry_delay = time::Duration::from_millis(250); + for _i in 1..retries { + let res = client + .post(&format!("{}/{}", addr, path)) + .json(&body) + .send(); + + if let Ok(mut res) = res { + return Some(res.text().unwrap()); + } + thread::sleep(retry_delay); + } + None +} + +pub fn broadcast( + client: &Client, + party_num: u16, + round: &str, + data: String, + sender_uuid: String, +) -> Result<(), ()> { + let key = format!("{}-{}-{}", party_num, round, sender_uuid); + let entry = Entry { key, value: data }; + + let res_body = postb(client, "set", entry).unwrap(); + serde_json::from_str(&res_body).unwrap() +} + +pub fn sendp2p( + client: &Client, + party_from: u16, + party_to: u16, + round: &str, + data: String, + sender_uuid: String, +) -> Result<(), ()> { + let key = format!("{}-{}-{}-{}", party_from, party_to, round, sender_uuid); + + let entry = Entry { key, value: data }; + + let res_body = postb(client, "set", entry).unwrap(); + serde_json::from_str(&res_body).unwrap() +} + +pub fn poll_for_broadcasts( + client: &Client, + party_num: u16, + n: u16, + delay: Duration, + round: &str, + sender_uuid: String, +) -> Vec { + let mut ans_vec = Vec::new(); + for i in 1..=n { + if i != party_num { + let key = format!("{}-{}-{}", i, round, sender_uuid); + let index = Index { key }; + loop { + // add delay to allow the server to process request: + thread::sleep(delay); + let res_body = postb(client, "get", index.clone()).unwrap(); + let answer: Result = + serde_json::from_str(&res_body).unwrap(); + if let Ok(answer) = answer { + ans_vec.push(answer.value); + println!( + "[{:?}] party {:?} => party {:?}", + round, i, party_num + ); + break; + } + } + } + } + ans_vec +} + +pub fn poll_for_p2p( + client: &Client, + party_num: u16, + n: u16, + delay: Duration, + round: &str, + sender_uuid: String, +) -> Vec { + let mut ans_vec = Vec::new(); + for i in 1..=n { + if i != party_num { + let key = format!("{}-{}-{}-{}", i, party_num, round, sender_uuid); + let index = Index { key }; + loop { + // add delay to allow the server to process request: + thread::sleep(delay); + let res_body = postb(client, "get", index.clone()).unwrap(); + let answer: Result = + serde_json::from_str(&res_body).unwrap(); + if let Ok(answer) = answer { + ans_vec.push(answer.value); + println!( + "[{:?}] party {:?} => party {:?}", + round, i, party_num + ); + break; + } + } + } + } + ans_vec +} + +pub fn check_sig( + r: &Scalar, + s: &Scalar, + msg: &BigInt, + pk: &Point, +) { + use secp256k1::{Message, PublicKey, Signature, SECP256K1}; + + let raw_msg = BigInt::to_bytes(msg); + let mut msg: Vec = Vec::new(); // padding + msg.extend(vec![0u8; 32 - raw_msg.len()]); + msg.extend(raw_msg.iter()); + + let msg = Message::from_slice(msg.as_slice()).unwrap(); + let mut raw_pk = pk.to_bytes(false).to_vec(); + if raw_pk.len() == 64 { + raw_pk.insert(0, 4u8); + } + let pk = PublicKey::from_slice(&raw_pk).unwrap(); + + let mut compact: Vec = Vec::new(); + let bytes_r = &r.to_bytes().to_vec(); + compact.extend(vec![0u8; 32 - bytes_r.len()]); + compact.extend(bytes_r.iter()); + + let bytes_s = &s.to_bytes().to_vec(); + compact.extend(vec![0u8; 32 - bytes_s.len()]); + compact.extend(bytes_s.iter()); + + let secp_sig = Signature::from_compact(compact.as_slice()).unwrap(); + + let is_correct = SECP256K1.verify(&msg, &secp_sig, &pk).is_ok(); + assert!(is_correct); +} diff --git a/multi-party-ecdsa/examples/gg20_keygen.rs b/multi-party-ecdsa/examples/gg20_keygen.rs new file mode 100644 index 00000000..63f0133c --- /dev/null +++ b/multi-party-ecdsa/examples/gg20_keygen.rs @@ -0,0 +1,62 @@ +use anyhow::{anyhow, Context, Result}; +use futures::StreamExt; +use std::path::PathBuf; +use structopt::StructOpt; + +use multi_party_ecdsa::protocols::multi_party_ecdsa::gg_2020::state_machine::keygen::Keygen; +use round_based::async_runtime::AsyncProtocol; + +mod gg20_sm_client; +use gg20_sm_client::join_computation; + +#[derive(Debug, StructOpt)] +struct Cli { + #[structopt(short, long, default_value = "http://localhost:8000/")] + address: surf::Url, + #[structopt(short, long, default_value = "default-keygen")] + room: String, + #[structopt(short, long)] + output: PathBuf, + + #[structopt(short, long)] + index: u16, + #[structopt(short, long)] + threshold: u16, + #[structopt(short, long)] + number_of_parties: u16, +} + +#[tokio::main] +async fn main() -> Result<()> { + let args: Cli = Cli::from_args(); + let mut output_file = tokio::fs::OpenOptions::new() + .write(true) + .create_new(true) + .open(args.output) + .await + .context("cannot create output file")?; + + let (_i, incoming, outgoing) = join_computation(args.address, &args.room) + .await + .context("join computation")?; + + let incoming = incoming.fuse(); + tokio::pin!(incoming); + tokio::pin!(outgoing); + + let keygen = + Keygen::new(args.index, args.threshold, args.number_of_parties)?; + let output = AsyncProtocol::new(keygen, incoming, outgoing) + .run() + .await + .map_err(|e| { + anyhow!("protocol execution terminated with error: {}", e) + })?; + let output = + serde_json::to_vec_pretty(&output).context("serialize output")?; + tokio::io::copy(&mut output.as_slice(), &mut output_file) + .await + .context("save output to file")?; + + Ok(()) +} diff --git a/multi-party-ecdsa/examples/gg20_signing.rs b/multi-party-ecdsa/examples/gg20_signing.rs new file mode 100644 index 00000000..03434b14 --- /dev/null +++ b/multi-party-ecdsa/examples/gg20_signing.rs @@ -0,0 +1,97 @@ +use std::path::PathBuf; + +use anyhow::{anyhow, Context, Result}; +use futures::{SinkExt, StreamExt, TryStreamExt}; +use structopt::StructOpt; + +use curv::{arithmetic::Converter, BigInt}; + +use multi_party_ecdsa::protocols::multi_party_ecdsa::gg_2020::state_machine::sign::{ + OfflineStage, SignManual, +}; +use round_based::async_runtime::AsyncProtocol; +use round_based::Msg; + +mod gg20_sm_client; +use gg20_sm_client::join_computation; + +#[derive(Debug, StructOpt)] +struct Cli { + #[structopt(short, long, default_value = "http://localhost:8000/")] + address: surf::Url, + #[structopt(short, long, default_value = "default-signing")] + room: String, + #[structopt(short, long)] + local_share: PathBuf, + + #[structopt(short, long, use_delimiter(true))] + parties: Vec, + #[structopt(short, long)] + data_to_sign: String, +} + +#[tokio::main] +async fn main() -> Result<()> { + let args: Cli = Cli::from_args(); + let local_share = tokio::fs::read(args.local_share) + .await + .context("cannot read local share")?; + let local_share = + serde_json::from_slice(&local_share).context("parse local share")?; + let number_of_parties = args.parties.len(); + + let (i, incoming, outgoing) = join_computation( + args.address.clone(), + &format!("{}-offline", args.room), + ) + .await + .context("join offline computation")?; + + let incoming = incoming.fuse(); + tokio::pin!(incoming); + tokio::pin!(outgoing); + + let signing = OfflineStage::new(i, args.parties, local_share)?; + let completed_offline_stage = + AsyncProtocol::new(signing, incoming, outgoing) + .run() + .await + .map_err(|e| { + anyhow!("protocol execution terminated with error: {}", e) + })?; + + let (i, incoming, outgoing) = + join_computation(args.address, &format!("{}-online", args.room)) + .await + .context("join online computation")?; + + tokio::pin!(incoming); + tokio::pin!(outgoing); + + let (signing, partial_signature) = SignManual::new( + BigInt::from_bytes(args.data_to_sign.as_bytes()), + completed_offline_stage, + )?; + + outgoing + .send(Msg { + sender: i, + receiver: None, + body: partial_signature, + }) + .await?; + + let partial_signatures: Vec<_> = incoming + .take(number_of_parties - 1) + .map_ok(|msg| msg.body) + .try_collect() + .await?; + let signature = signing + .complete(&partial_signatures) + .context("online stage failed")?; + let signature = + serde_json::to_string(&signature).context("serialize signature")?; + println!("{}", signature); + + Ok(()) +} diff --git a/multi-party-ecdsa/examples/gg20_sm_client.rs b/multi-party-ecdsa/examples/gg20_sm_client.rs new file mode 100644 index 00000000..c440d2af --- /dev/null +++ b/multi-party-ecdsa/examples/gg20_sm_client.rs @@ -0,0 +1,164 @@ +use std::convert::TryInto; + +use anyhow::{Context, Result}; +use futures::{Sink, Stream, StreamExt, TryStreamExt}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use structopt::StructOpt; + +use round_based::Msg; + +pub async fn join_computation( + address: surf::Url, + room_id: &str, +) -> Result<( + u16, + impl Stream>>, + impl Sink, Error = anyhow::Error>, +)> +where + M: Serialize + DeserializeOwned, +{ + let client = + SmClient::new(address, room_id).context("construct SmClient")?; + + // Construct channel of incoming messages + let incoming = client.subscribe().await.context("subscribe")?.and_then( + |msg| async move { + serde_json::from_str::>(&msg).context("deserialize message") + }, + ); + + // Obtain party index + let index = client.issue_index().await.context("issue an index")?; + + // Ignore incoming messages addressed to someone else + let incoming = incoming.try_filter(move |msg| { + futures::future::ready( + msg.sender != index + && (msg.receiver.is_none() || msg.receiver == Some(index)), + ) + }); + + // Construct channel of outgoing messages + let outgoing = + futures::sink::unfold(client, |client, message: Msg| async move { + let serialized = + serde_json::to_string(&message).context("serialize message")?; + client + .broadcast(&serialized) + .await + .context("broadcast message")?; + Ok::<_, anyhow::Error>(client) + }); + + Ok((index, incoming, outgoing)) +} + +pub struct SmClient { + http_client: surf::Client, +} + +impl SmClient { + pub fn new(address: surf::Url, room_id: &str) -> Result { + let config = surf::Config::new() + .set_base_url(address.join(&format!("rooms/{}/", room_id))?) + .set_timeout(None); + Ok(Self { + http_client: config.try_into()?, + }) + } + + pub async fn issue_index(&self) -> Result { + let response = self + .http_client + .post("issue_unique_idx") + .recv_json::() + .await + .map_err(|e| e.into_inner())?; + Ok(response.unique_idx) + } + + pub async fn broadcast(&self, message: &str) -> Result<()> { + self.http_client + .post("broadcast") + .body(message) + .await + .map_err(|e| e.into_inner())?; + Ok(()) + } + + pub async fn subscribe( + &self, + ) -> Result>> { + let response = self + .http_client + .get("subscribe") + .await + .map_err(|e| e.into_inner())?; + let events = async_sse::decode(response); + Ok(events.filter_map(|msg| async { + match msg { + Ok(async_sse::Event::Message(msg)) => Some( + String::from_utf8(msg.into_bytes()) + .context("SSE message is not valid UTF-8 string"), + ), + Ok(_) => { + // ignore other types of events + None + } + Err(e) => Some(Err(e.into_inner())), + } + })) + } +} + +#[derive(Deserialize, Debug)] +struct IssuedUniqueIdx { + unique_idx: u16, +} + +#[derive(StructOpt, Debug)] +struct Cli { + #[structopt(short, long)] + address: surf::Url, + #[structopt(short, long)] + room: String, + #[structopt(subcommand)] + cmd: Cmd, +} + +#[derive(StructOpt, Debug)] +enum Cmd { + Subscribe, + Broadcast { + #[structopt(short, long)] + message: String, + }, + IssueIdx, +} + +#[tokio::main] +#[allow(dead_code)] +async fn main() -> Result<()> { + let args: Cli = Cli::from_args(); + let client = + SmClient::new(args.address, &args.room).context("create SmClient")?; + match args.cmd { + Cmd::Broadcast { message } => client + .broadcast(&message) + .await + .context("broadcast message")?, + Cmd::IssueIdx => { + let index = client.issue_index().await.context("issue index")?; + println!("Index: {}", index); + } + Cmd::Subscribe => { + let messages = client.subscribe().await.context("subsribe")?; + tokio::pin!(messages); + while let Some(message) = messages.next().await { + println!("{:?}", message); + } + } + } + Ok(()) +} diff --git a/multi-party-ecdsa/examples/gg20_sm_manager.rs b/multi-party-ecdsa/examples/gg20_sm_manager.rs new file mode 100644 index 00000000..94cc46c4 --- /dev/null +++ b/multi-party-ecdsa/examples/gg20_sm_manager.rs @@ -0,0 +1,209 @@ +use std::{ + collections::hash_map::{Entry, HashMap}, + sync::{ + atomic::{AtomicU16, Ordering}, + Arc, + }, +}; + +use futures::Stream; +use rocket::{ + data::ToByteUnit, + http::Status, + request::{FromRequest, Outcome, Request}, + response::stream::{stream, Event, EventStream}, + serde::json::Json, + State, +}; +use serde::{Deserialize, Serialize}; +use tokio::sync::{Notify, RwLock}; + +#[rocket::get("/rooms//subscribe")] +async fn subscribe( + db: &State, + mut shutdown: rocket::Shutdown, + last_seen_msg: LastEventId, + room_id: &str, +) -> EventStream> { + let room = db.get_room_or_create_empty(room_id).await; + let mut subscription = room.subscribe(last_seen_msg.0); + EventStream::from(stream! { + loop { + let (id, msg) = tokio::select! { + message = subscription.next() => message, + _ = &mut shutdown => return, + }; + yield Event::data(msg) + .event("new-message") + .id(id.to_string()) + } + }) +} + +#[rocket::post("/rooms//issue_unique_idx")] +async fn issue_idx(db: &State, room_id: &str) -> Json { + let room = db.get_room_or_create_empty(room_id).await; + let idx = room.issue_unique_idx(); + Json::from(IssuedUniqueIdx { unique_idx: idx }) +} + +#[rocket::post("/rooms//broadcast", data = "")] +async fn broadcast(db: &State, room_id: &str, message: String) -> Status { + let room = db.get_room_or_create_empty(room_id).await; + room.publish(message).await; + Status::Ok +} + +struct Db { + rooms: RwLock>>, +} + +struct Room { + messages: RwLock>, + message_appeared: Notify, + subscribers: AtomicU16, + next_idx: AtomicU16, +} + +impl Db { + pub fn empty() -> Self { + Self { + rooms: RwLock::new(HashMap::new()), + } + } + + pub async fn get_room_or_create_empty(&self, room_id: &str) -> Arc { + let rooms = self.rooms.read().await; + if let Some(room) = rooms.get(room_id) { + // If no one is watching this room - we need to clean it up first + if !room.is_abandoned() { + return room.clone(); + } + } + drop(rooms); + + let mut rooms = self.rooms.write().await; + match rooms.entry(room_id.to_owned()) { + Entry::Occupied(entry) if !entry.get().is_abandoned() => { + entry.get().clone() + } + Entry::Occupied(entry) => { + let room = Arc::new(Room::empty()); + *entry.into_mut() = room.clone(); + room + } + Entry::Vacant(entry) => { + entry.insert(Arc::new(Room::empty())).clone() + } + } + } +} + +impl Room { + pub fn empty() -> Self { + Self { + messages: RwLock::new(vec![]), + message_appeared: Notify::new(), + subscribers: AtomicU16::new(0), + next_idx: AtomicU16::new(1), + } + } + + pub async fn publish(self: &Arc, message: String) { + let mut messages = self.messages.write().await; + messages.push(message); + self.message_appeared.notify_waiters(); + } + + pub fn subscribe( + self: Arc, + last_seen_msg: Option, + ) -> Subscription { + self.subscribers.fetch_add(1, Ordering::SeqCst); + Subscription { + room: self, + next_event: last_seen_msg.map(|i| i + 1).unwrap_or(0), + } + } + + pub fn is_abandoned(&self) -> bool { + self.subscribers.load(Ordering::SeqCst) == 0 + } + + pub fn issue_unique_idx(&self) -> u16 { + self.next_idx.fetch_add(1, Ordering::Relaxed) + } +} + +struct Subscription { + room: Arc, + next_event: u16, +} + +impl Subscription { + pub async fn next(&mut self) -> (u16, String) { + loop { + let history = self.room.messages.read().await; + if let Some(msg) = history.get(usize::from(self.next_event)) { + let event_id = self.next_event; + self.next_event = event_id + 1; + return (event_id, msg.clone()); + } + let notification = self.room.message_appeared.notified(); + drop(history); + notification.await; + } + } +} + +impl Drop for Subscription { + fn drop(&mut self) { + self.room.subscribers.fetch_sub(1, Ordering::SeqCst); + } +} + +/// Represents a header Last-Event-ID +struct LastEventId(Option); + +#[rocket::async_trait] +impl<'r> FromRequest<'r> for LastEventId { + type Error = &'static str; + + async fn from_request( + request: &'r Request<'_>, + ) -> Outcome { + let header = request + .headers() + .get_one("Last-Event-ID") + .map(|id| id.parse::()); + match header { + Some(Ok(last_seen_msg)) => { + Outcome::Success(LastEventId(Some(last_seen_msg))) + } + Some(Err(_parse_err)) => Outcome::Failure(( + Status::BadRequest, + "last seen msg id is not valid", + )), + None => Outcome::Success(LastEventId(None)), + } + } +} + +#[derive(Serialize, Deserialize, Debug)] +struct IssuedUniqueIdx { + unique_idx: u16, +} + +#[tokio::main] +async fn main() -> Result<(), Box> { + let figment = rocket::Config::figment().merge(( + "limits", + rocket::data::Limits::new().limit("string", 100.megabytes()), + )); + rocket::custom(figment) + .mount("/", rocket::routes![subscribe, issue_idx, broadcast]) + .manage(Db::empty()) + .launch() + .await?; + Ok(()) +} diff --git a/multi-party-ecdsa/params.json b/multi-party-ecdsa/params.json new file mode 100644 index 00000000..8dcdefd6 --- /dev/null +++ b/multi-party-ecdsa/params.json @@ -0,0 +1 @@ +{"parties":"3", "threshold":"1"} diff --git a/multi-party-ecdsa/src/gg_2020/blame.rs b/multi-party-ecdsa/src/gg_2020/blame.rs new file mode 100644 index 00000000..1312596c --- /dev/null +++ b/multi-party-ecdsa/src/gg_2020/blame.rs @@ -0,0 +1,478 @@ +#![allow(non_snake_case)] +/* + Multi-party ECDSA + + Copyright 2018 by Kzen Networks + + This file is part of Multi-party ECDSA library + (https://github.com/KZen-networks/multi-party-ecdsa) + + Multi-party ECDSA is free software: you can redistribute + it and/or modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, either + version 3 of the License, or (at your option) any later version. + + @license GPL-3.0+ +*/ +use crate::{ + protocols::multi_party_ecdsa::gg_2020::ErrorType, + utilities::mta::{MessageA, MessageB}, +}; +use curv::{ + cryptographic_primitives::proofs::sigma_ec_ddh::{ + ECDDHProof, ECDDHStatement, ECDDHWitness, + }, + elliptic::curves::{secp256_k1::Secp256k1, Point, Scalar}, + BigInt, +}; +use paillier::{ + traits::{EncryptWithChosenRandomness, Open}, + DecryptionKey, EncryptionKey, Paillier, Randomness, RawCiphertext, + RawPlaintext, +}; +use serde::{Deserialize, Serialize}; +use sha2::Sha256; + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct LocalStatePhase5 { + pub k: Scalar, + pub k_randomness: BigInt, + pub gamma: Scalar, + pub beta_randomness: Vec, + pub beta_tag: Vec, + pub encryption_key: EncryptionKey, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct GlobalStatePhase5 { + pub k_vec: Vec>, + pub k_randomness_vec: Vec, + pub gamma_vec: Vec>, + pub beta_randomness_vec: Vec>, + pub beta_tag_vec: Vec>, + pub encryption_key_vec: Vec, + // stuff to check against + pub delta_vec: Vec>, + pub g_gamma_vec: Vec>, + pub m_a_vec: Vec, + pub m_b_mat: Vec>, +} + +// TODO: check all parties submitted inputs +// TODO: if not - abort gracefully with list of parties that did not produce +// inputs +impl GlobalStatePhase5 { + pub fn local_state_to_global_state( + encryption_key_vec: &[EncryptionKey], + delta_vec: &[Scalar], //to test against delta_vec + g_gamma_vec: &[Point], /* to test against the opened + * commitment for g_gamma */ + m_a_vec: &[MessageA], // to test against broadcast message A + m_b_mat: Vec>, // to test against broadcast message B + local_state_vec: &[LocalStatePhase5], + ) -> Self { + let len = local_state_vec.len(); + let k_vec = (0..len) + .map(|i| local_state_vec[i].k.clone()) + .collect::>>(); + let k_randomness_vec = (0..len) + .map(|i| local_state_vec[i].k_randomness.clone()) + .collect::>(); + let gamma_vec = (0..len) + .map(|i| local_state_vec[i].gamma.clone()) + .collect::>>(); + let beta_randomness_vec = (0..len) + .map(|i| { + (0..len - 1) + .map(|j| { + let ind1 = if j < i { j } else { j + 1 }; + let ind2 = if j < i { i - 1 } else { i }; + local_state_vec[ind1].beta_randomness[ind2].clone() + }) + .collect::>() + }) + .collect::>>(); + let beta_tag_vec = (0..len) + .map(|i| { + (0..len - 1) + .map(|j| { + let ind1 = if j < i { j } else { j + 1 }; + let ind2 = if j < i { i - 1 } else { i }; + local_state_vec[ind1].beta_tag[ind2].clone() + }) + .collect::>() + }) + .collect::>>(); + + // let encryption_key_vec = (0..len).map(|i| + // local_state_vec[i].encryption_key.clone() + // ).collect::>(); + GlobalStatePhase5 { + k_vec, + k_randomness_vec, + gamma_vec, + beta_randomness_vec, + beta_tag_vec, + encryption_key_vec: encryption_key_vec.to_vec(), + delta_vec: delta_vec.to_vec(), + g_gamma_vec: g_gamma_vec.to_vec(), + m_a_vec: m_a_vec.to_vec(), + m_b_mat, + } + } + + pub fn phase5_blame(&self) -> Result<(), ErrorType> { + let len = self.delta_vec.len(); + let mut bad_signers_vec = Vec::new(); + + // check commitment to g_gamma + for i in 0..len { + if self.g_gamma_vec[i] != Point::generator() * &self.gamma_vec[i] { + bad_signers_vec.push(i) + } + } + + let alpha_beta_matrix = (0..len) + .map(|i| { + let message_a = MessageA::a_with_predefined_randomness( + &self.k_vec[i], + &self.encryption_key_vec[i], + &self.k_randomness_vec[i], + &[], + ); + + // check message a + if message_a.c != self.m_a_vec[i].c { + bad_signers_vec.push(i) + } + + if bad_signers_vec.is_empty() { + (0..len - 1) + .map(|j| { + let ind = if j < i { j } else { j + 1 }; + let (message_b, beta) = + MessageB::b_with_predefined_randomness( + &self.gamma_vec[ind], + &self.encryption_key_vec[i], + message_a.clone(), + &self.beta_randomness_vec[i][j], + &self.beta_tag_vec[i][j], + &[], + ) + .unwrap(); + // check message_b + if message_b.c != self.m_b_mat[i][j].c { + bad_signers_vec.push(ind) + } + + let k_i_gamma_j = + &self.k_vec[i] * &self.gamma_vec[ind]; + let alpha = k_i_gamma_j - β + + (alpha, beta) + }) + .collect::, Scalar)>>( + ) + } else { + vec![] + } + }) + .collect::, Scalar)>>>(); + + // The matrix we got: + // [P2, P1, P1, P1 ...] + // [P3, P3, P2, P2, ...] + // [P4, P4, P4, P3, ...] + // [..., ...] + // [Pn, Pn, Pn, Pn, ...] + // We have n columns, one for each party for all the times the party + // played alice. The Pi's indicate the counter party that played + // bob + + // we only proceed to check the blame if everyone opened values that are + // consistent with publicly known commitments and ciphertexts + if bad_signers_vec.is_empty() { + //reconstruct delta's + let delta_vec_reconstruct = (0..len) + .map(|i| { + let k_i_gamma_i = &self.k_vec[i] * &self.gamma_vec[i]; + + let alpha_sum = alpha_beta_matrix[i] + .iter() + .fold(Scalar::::zero(), |acc, x| acc + &x.0); + let beta_vec = (0..len - 1) + .map(|j| { + let ind1 = if j < i { j } else { j + 1 }; + let ind2 = if j < i { i - 1 } else { i }; + alpha_beta_matrix[ind1][ind2].1.clone() + }) + .collect::>>(); + + let beta_sum = beta_vec + .iter() + .fold(Scalar::::zero(), |acc, x| acc + x); + + k_i_gamma_i + alpha_sum + beta_sum + }) + .collect::>>(); + + // compare delta vec to reconstructed delta vec + + #[allow(clippy::needless_range_loop)] + for i in 0..len { + if self.delta_vec[i] != delta_vec_reconstruct[i] { + bad_signers_vec.push(i) + } + } + } + + bad_signers_vec.sort_unstable(); + bad_signers_vec.dedup(); + let err_type = ErrorType { + error_type: "phase6_blame".to_string(), + bad_actors: bad_signers_vec, + data: Vec::new(), + }; + Err(err_type) + } +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct LocalStatePhase6 { + pub k: Scalar, + pub k_randomness: BigInt, + pub miu: Vec, // we need the value before reduction + pub miu_randomness: Vec, + pub proof_of_eq_dlog: ECDDHProof, +} + +// It is assumed the second message of MtAwc (ciphertext from b to a) is +// broadcasted in the original protocol +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct GlobalStatePhase6 { + pub k_vec: Vec>, + pub k_randomness_vec: Vec, + pub miu_vec: Vec>, + pub miu_randomness_vec: Vec>, + pub g_w_vec: Vec>, + pub encryption_key_vec: Vec, + pub proof_vec: Vec>, + pub S_vec: Vec>, + pub m_a_vec: Vec, + pub m_b_mat: Vec>, +} + +impl GlobalStatePhase6 { + pub fn extract_paillier_randomness( + ciphertext: &BigInt, + dk: &DecryptionKey, + ) -> BigInt { + let raw_c = RawCiphertext::from(ciphertext.clone()); + let (_plaintext, randomness) = Paillier::open(dk, raw_c); + randomness.0 + } + + pub fn ecddh_proof( + sigma_i: &Scalar, + R: &Point, + S: &Point, + ) -> ECDDHProof { + let delta = ECDDHStatement { + g1: Point::generator().to_point(), + g2: R.clone(), + h1: Point::generator() * sigma_i, + h2: S.clone(), + }; + let w = ECDDHWitness { x: sigma_i.clone() }; + ECDDHProof::prove(&w, &delta) + } + + // TODO: check all parties submitted inputs + // TODO: if not - abort gracefully with list of parties that did not produce + // inputs + pub fn local_state_to_global_state( + encryption_key_vec: &[EncryptionKey], + S_vec: &[Point], + g_w_vec: &[Point], + m_a_vec: &[MessageA], // to test against broadcast message A + m_b_mat: Vec>, // to test against broadcast message B + local_state_vec: &[LocalStatePhase6], + ) -> Self { + let len = local_state_vec.len(); + let k_vec = (0..len) + .map(|i| local_state_vec[i].k.clone()) + .collect::>>(); + let k_randomness_vec = (0..len) + .map(|i| local_state_vec[i].k_randomness.clone()) + .collect::>(); + let proof_vec = (0..len) + .map(|i| local_state_vec[i].proof_of_eq_dlog.clone()) + .collect::>>(); + let miu_randomness_vec = (0..len) + .map(|i| { + (0..len - 1) + .map(|j| local_state_vec[i].miu_randomness[j].clone()) + .collect::>() + }) + .collect::>>(); + let miu_vec = (0..len) + .map(|i| { + (0..len - 1) + .map(|j| local_state_vec[i].miu[j].clone()) + .collect::>() + }) + .collect::>>(); + + GlobalStatePhase6 { + k_vec, + k_randomness_vec, + miu_vec, + miu_randomness_vec, + g_w_vec: g_w_vec.to_vec(), + encryption_key_vec: encryption_key_vec.to_vec(), + proof_vec, + S_vec: S_vec.to_vec(), + m_a_vec: m_a_vec.to_vec(), + m_b_mat, + } + } + + pub fn phase6_blame(&self, R: &Point) -> Result<(), ErrorType> { + let len = self.k_vec.len(); + let mut bad_signers_vec = Vec::new(); + + // check correctness of miu + for i in 0..len { + for j in 0..len - 1 { + if Paillier::encrypt_with_chosen_randomness( + &self.encryption_key_vec[i], + RawPlaintext::from(self.miu_vec[i][j].clone()), + &Randomness::from(self.miu_randomness_vec[i][j].clone()), + ) != RawCiphertext::from(self.m_b_mat[i][j].c.clone()) + { + bad_signers_vec.push(i) + } + } + } + + // check correctness of k + for i in 0..len { + if MessageA::a_with_predefined_randomness( + &self.k_vec[i], + &self.encryption_key_vec[i], + &self.k_randomness_vec[i], + &[], + ) + .c != self.m_a_vec[i].c + { + bad_signers_vec.push(i) + } + } + + // we only proceed to check the blame if everyone opened values that are + // consistent with publicly known ciphertexts sent during MtA + if bad_signers_vec.is_empty() { + // compute g_ni + let g_ni_mat = (0..len) + .map(|i| { + (0..len - 1) + .map(|j| { + let ind = if j < i { j } else { j + 1 }; + let k_i = &self.k_vec[i]; + let g_w_j = &self.g_w_vec[ind]; + let g_w_j_ki = g_w_j * k_i; + let miu: Scalar = + Scalar::::from(&self.miu_vec[i][j]); + let g_miu = Point::generator() * &miu; + g_w_j_ki - &g_miu + }) + .collect::>>() + }) + .collect::>>>(); + + // compute g_sigma_i + + let mut g_sigma_i_vec = (0..len) + .map(|i| { + let g_wi_ki = &self.g_w_vec[i] * &self.k_vec[i]; + let sum = self.miu_vec[i].iter().fold(g_wi_ki, |acc, x| { + acc + (Point::generator() + * &Scalar::::from(x)) + }); + sum + }) + .collect::>>(); + + #[allow(clippy::needless_range_loop)] + for i in 0..len { + for j in 0..len - 1 { + let ind1 = if j < i { j } else { j + 1 }; + let ind2 = if j < i { i - 1 } else { i }; + g_sigma_i_vec[i] = + &g_sigma_i_vec[i] + &g_ni_mat[ind1][ind2]; + } + } + + // check zero knowledge proof + #[allow(clippy::needless_range_loop)] + for i in 0..len { + let statement = ECDDHStatement { + g1: Point::generator().to_point(), + g2: R.clone(), + h1: g_sigma_i_vec[i].clone(), + h2: self.S_vec[i].clone(), + }; + + let result = self.proof_vec[i].verify(&statement); + if result.is_err() { + bad_signers_vec.push(i) + } + } + } + + bad_signers_vec.sort_unstable(); + bad_signers_vec.dedup(); + let err_type = ErrorType { + error_type: "phase6_blame".to_string(), + bad_actors: bad_signers_vec, + data: Vec::new(), + }; + Err(err_type) + } +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct GlobalStatePhase7 { + pub s_vec: Vec>, + pub r: Scalar, + pub R_dash_vec: Vec>, + pub m: BigInt, + pub R: Point, + pub S_vec: Vec>, +} + +impl GlobalStatePhase7 { + pub fn phase7_blame(&self) -> Result<(), ErrorType> { + let len = self.s_vec.len(); //TODO: check bounds + let mut bad_signers_vec = Vec::new(); + + for i in 0..len { + let R_si = &self.R * &self.s_vec[i]; + let R_dash_m = + &self.R_dash_vec[i] * &Scalar::::from(&self.m); + let Si_r = &self.S_vec[i] * &self.r; + let right = R_dash_m + Si_r; + let left = R_si; + if left != right { + bad_signers_vec.push(i); + } + } + + let err_type = ErrorType { + error_type: "phase7_blame".to_string(), + bad_actors: bad_signers_vec, + data: Vec::new(), + }; + Err(err_type) + } +} diff --git a/multi-party-ecdsa/src/gg_2020/mod.rs b/multi-party-ecdsa/src/gg_2020/mod.rs new file mode 100644 index 00000000..7f1ca23d --- /dev/null +++ b/multi-party-ecdsa/src/gg_2020/mod.rs @@ -0,0 +1,32 @@ +/* + Multi-party ECDSA + + Copyright 2018 by Kzen Networks + + This file is part of Multi-party ECDSA library + (https://github.com/KZen-networks/multi-party-ecdsa) + + Multi-party ECDSA is free software: you can redistribute + it and/or modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, either + version 3 of the License, or (at your option) any later version. + + @license GPL-3.0+ +*/ + +pub mod blame; +pub mod party_i; +pub mod state_machine; + +use curv::cryptographic_primitives::secret_sharing::feldman_vss::VerifiableSS as VSS; +pub type VerifiableSS = VSS; + +#[cfg(test)] +mod test; + +#[derive(Clone, Debug)] +pub struct ErrorType { + pub error_type: String, + pub bad_actors: Vec, + pub data: Vec, +} diff --git a/multi-party-ecdsa/src/gg_2020/party_i.rs b/multi-party-ecdsa/src/gg_2020/party_i.rs new file mode 100644 index 00000000..718ccd23 --- /dev/null +++ b/multi-party-ecdsa/src/gg_2020/party_i.rs @@ -0,0 +1,1048 @@ +#![allow(non_snake_case)] + +/* + Multi-party ECDSA + + Copyright 2018 by Kzen Networks + + This file is part of Multi-party ECDSA library + (https://github.com/KZen-networks/multi-party-ecdsa) + + Multi-party ECDSA is free software: you can redistribute + it and/or modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, either + version 3 of the License, or (at your option) any later version. + + @license GPL-3.0+ +*/ + +use std::fmt::Debug; + +use centipede::juggling::{ + proof_system::{Helgamalsegmented, Witness}, + segmentation::Msegmentation, +}; +use curv::{ + arithmetic::traits::*, + cryptographic_primitives::{ + commitments::{hash_commitment::HashCommitment, traits::Commitment}, + proofs::{ + sigma_correct_homomorphic_elgamal_enc::*, sigma_dlog::DLogProof, + }, + }, + elliptic::curves::{secp256_k1::Secp256k1, Curve, Point, Scalar}, + BigInt, +}; +use sha2::Sha256; + +use super::VerifiableSS; +use crate::Error::{self, InvalidSig, Phase5BadSum, Phase6Error}; +use paillier::{ + Decrypt, DecryptionKey, EncryptionKey, KeyGeneration, Paillier, + RawCiphertext, RawPlaintext, +}; + +use serde::{Deserialize, Serialize}; +use zk_paillier::zkproofs::{ + CompositeDLogProof, DLogStatement, NiCorrectKeyProof, +}; + +use crate::{ + protocols::multi_party_ecdsa::gg_2020::ErrorType, + utilities::zk_pdl_with_slack::{ + PDLwSlackProof, PDLwSlackStatement, PDLwSlackWitness, + }, +}; +use curv::cryptographic_primitives::proofs::sigma_valid_pedersen::PedersenProof; + +use std::convert::TryInto; + +const SECURITY: usize = 256; +const PAILLIER_MIN_BIT_LENGTH: usize = 2047; +const PAILLIER_MAX_BIT_LENGTH: usize = 2048; + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct Parameters { + pub threshold: u16, //t + pub share_count: u16, //n +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct Keys { + pub u_i: Scalar, + pub y_i: Point, + pub dk: DecryptionKey, + pub ek: EncryptionKey, + pub party_index: usize, + pub N_tilde: BigInt, + pub h1: BigInt, + pub h2: BigInt, + pub xhi: BigInt, + pub xhi_inv: BigInt, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct PartyPrivate { + u_i: Scalar, + x_i: Scalar, + dk: DecryptionKey, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct KeyGenBroadcastMessage1 { + pub e: EncryptionKey, + pub dlog_statement: DLogStatement, + pub com: BigInt, + pub correct_key_proof: NiCorrectKeyProof, + pub composite_dlog_proof_base_h1: CompositeDLogProof, + pub composite_dlog_proof_base_h2: CompositeDLogProof, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct KeyGenDecommitMessage1 { + pub blind_factor: BigInt, + pub y_i: Point, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct SharedKeys { + pub y: Point, + pub x_i: Scalar, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct SignKeys { + pub w_i: Scalar, + pub g_w_i: Point, + pub k_i: Scalar, + pub gamma_i: Scalar, + pub g_gamma_i: Point, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct SignBroadcastPhase1 { + pub com: BigInt, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct SignDecommitPhase1 { + pub blind_factor: BigInt, + pub g_gamma_i: Point, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct LocalSignature { + pub r: Scalar, + pub R: Point, + pub s_i: Scalar, + pub m: BigInt, + pub y: Point, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct SignatureRecid { + pub r: Scalar, + pub s: Scalar, + pub recid: u8, +} + +pub fn generate_h1_h2_N_tilde() -> (BigInt, BigInt, BigInt, BigInt, BigInt) { + // note, should be safe primes: + // let (ek_tilde, dk_tilde) = Paillier::keypair_safe_primes().keys();; + let (ek_tilde, dk_tilde) = Paillier::keypair().keys(); + let one = BigInt::one(); + let phi = (&dk_tilde.p - &one) * (&dk_tilde.q - &one); + let h1 = BigInt::sample_below(&ek_tilde.n); + let (mut xhi, mut xhi_inv) = loop { + let xhi_ = BigInt::sample_below(&phi); + match BigInt::mod_inv(&xhi_, &phi) { + Some(inv) => break (xhi_, inv), + None => continue, + } + }; + let h2 = BigInt::mod_pow(&h1, &xhi, &ek_tilde.n); + xhi = BigInt::sub(&phi, &xhi); + xhi_inv = BigInt::sub(&phi, &xhi_inv); + + (ek_tilde.n, h1, h2, xhi, xhi_inv) +} + +impl Keys { + pub fn create(index: usize) -> Self { + let u = Scalar::::random(); + let y = Point::generator() * &u; + let (ek, dk) = Paillier::keypair().keys(); + let (N_tilde, h1, h2, xhi, xhi_inv) = generate_h1_h2_N_tilde(); + + Self { + u_i: u, + y_i: y, + dk, + ek, + party_index: index, + N_tilde, + h1, + h2, + xhi, + xhi_inv, + } + } + + // we recommend using safe primes if the code is used in production + pub fn create_safe_prime(index: usize) -> Self { + let u = Scalar::::random(); + let y = Point::generator() * &u; + + let (ek, dk) = Paillier::keypair_safe_primes().keys(); + let (N_tilde, h1, h2, xhi, xhi_inv) = generate_h1_h2_N_tilde(); + + Self { + u_i: u, + y_i: y, + dk, + ek, + party_index: index, + N_tilde, + h1, + h2, + xhi, + xhi_inv, + } + } + pub fn create_from(u: Scalar, index: usize) -> Self { + let y = Point::generator() * &u; + let (ek, dk) = Paillier::keypair().keys(); + let (N_tilde, h1, h2, xhi, xhi_inv) = generate_h1_h2_N_tilde(); + + Self { + u_i: u, + y_i: y, + dk, + ek, + party_index: index, + N_tilde, + h1, + h2, + xhi, + xhi_inv, + } + } + + pub fn phase1_broadcast_phase3_proof_of_correct_key_proof_of_correct_h1h2( + &self, + ) -> (KeyGenBroadcastMessage1, KeyGenDecommitMessage1) { + let blind_factor = BigInt::sample(SECURITY); + let correct_key_proof = NiCorrectKeyProof::proof(&self.dk, None); + + let dlog_statement_base_h1 = DLogStatement { + N: self.N_tilde.clone(), + g: self.h1.clone(), + ni: self.h2.clone(), + }; + let dlog_statement_base_h2 = DLogStatement { + N: self.N_tilde.clone(), + g: self.h2.clone(), + ni: self.h1.clone(), + }; + + let composite_dlog_proof_base_h1 = + CompositeDLogProof::prove(&dlog_statement_base_h1, &self.xhi); + let composite_dlog_proof_base_h2 = + CompositeDLogProof::prove(&dlog_statement_base_h2, &self.xhi_inv); + + let com = HashCommitment::::create_commitment_with_user_defined_randomness( + &BigInt::from_bytes(self.y_i.to_bytes(true).as_ref()), + &blind_factor, + ); + let bcm1 = KeyGenBroadcastMessage1 { + e: self.ek.clone(), + dlog_statement: dlog_statement_base_h1, + com, + correct_key_proof, + composite_dlog_proof_base_h1, + composite_dlog_proof_base_h2, + }; + let decom1 = KeyGenDecommitMessage1 { + blind_factor, + y_i: self.y_i.clone(), + }; + (bcm1, decom1) + } + + pub fn phase1_verify_com_phase3_verify_correct_key_verify_dlog_phase2_distribute( + &self, + params: &Parameters, + decom_vec: &[KeyGenDecommitMessage1], + bc1_vec: &[KeyGenBroadcastMessage1], + ) -> Result< + (VerifiableSS, Vec>, usize), + ErrorType, + > { + let mut bad_actors_vec = Vec::new(); + // test length: + assert_eq!(decom_vec.len(), usize::from(params.share_count)); + assert_eq!(bc1_vec.len(), usize::from(params.share_count)); + // test paillier correct key, h1,h2 correct generation and test + // decommitments + let correct_key_correct_decom_all = (0..bc1_vec.len()) + .map(|i| { + let dlog_statement_base_h2 = DLogStatement { + N: bc1_vec[i].dlog_statement.N.clone(), + g: bc1_vec[i].dlog_statement.ni.clone(), + ni: bc1_vec[i].dlog_statement.g.clone(), + }; + let test_res_1 = + HashCommitment::::create_commitment_with_user_defined_randomness( + &BigInt::from_bytes(&decom_vec[i].y_i.to_bytes(true)), + &decom_vec[i].blind_factor, + ) == bc1_vec[i].com; + log::info!("MP-ECDSA : Round 2 : test_res_1 {:?}", test_res_1); + + if !test_res_1 { + // Do a dumb test to search if the right decommit exists + for j in 0..bc1_vec.len() { + if i != j { + let test_res_1_1 = + HashCommitment::::create_commitment_with_user_defined_randomness( + &BigInt::from_bytes(&decom_vec[j].y_i.to_bytes(true)), + &decom_vec[j].blind_factor, + ) == bc1_vec[i].com; + if test_res_1_1 { + log::info!("MP-ECDSA : Round 2 RETEST : test_res_1_1 {:?}", test_res_1_1); + log::info!("MP-ECDSA : Round 2 RETEST : i {:?}", i); + log::info!("MP-ECDSA : Round 2 RETEST : j {:?}", j); + log::info!("MP-ECDSA : Round 2 RETEST : decom_vec[i] {:?}", decom_vec[i]); + log::info!("MP-ECDSA : Round 2 RETEST : decom_vec[j] {:?}", decom_vec[j]); + log::info!("MP-ECDSA : Round 2 RETEST : bc1_vec[i] {:?}", bc1_vec[i]); + log::info!("MP-ECDSA : Round 2 RETEST : bc1_vec[j] {:?}", bc1_vec[j]); + } + } + } + } + + let test_res_2 = bc1_vec[i] + .correct_key_proof + .verify(&bc1_vec[i].e, zk_paillier::zkproofs::SALT_STRING) + .is_ok(); + log::info!("MP-ECDSA : Round 2 : test_res_2 {:?}", test_res_2); + + let test_res_3 = bc1_vec[i].e.n.bit_length() >= PAILLIER_MIN_BIT_LENGTH; + log::info!("MP-ECDSA : Round 2 : test_res_3 {:?}", test_res_3); + + let test_res_4 = bc1_vec[i].e.n.bit_length() <= PAILLIER_MAX_BIT_LENGTH; + log::info!("MP-ECDSA : Round 2 : test_res_4 {:?}", test_res_4); + + let test_res_5 = + bc1_vec[i].dlog_statement.N.bit_length() >= PAILLIER_MIN_BIT_LENGTH; + log::info!("MP-ECDSA : Round 2 : test_res_5 {:?}", test_res_5); + + let test_res_6 = + bc1_vec[i].dlog_statement.N.bit_length() <= PAILLIER_MAX_BIT_LENGTH; + log::info!("MP-ECDSA : Round 2 : test_res_6 {:?}", test_res_6); + let test_res_7 = bc1_vec[i] + .composite_dlog_proof_base_h1 + .verify(&bc1_vec[i].dlog_statement) + .is_ok(); + log::info!("MP-ECDSA : Round 2 : test_res_7 {:?}", test_res_7); + let test_res_8 = bc1_vec[i] + .composite_dlog_proof_base_h2 + .verify(&dlog_statement_base_h2) + .is_ok(); + log::info!("MP-ECDSA : Round 2 : test_res_8 {:?}", test_res_8); + + let test_res = test_res_1 + && test_res_2 + && test_res_3 + && test_res_4 + && test_res_5 + && test_res_6 + && test_res_7 + && test_res_8; + + if !test_res { + bad_actors_vec.push(i); + false + } else { + true + } + }) + .all(|x| x); + + let err_type = ErrorType { + error_type: "invalid key".to_string(), + bad_actors: bad_actors_vec, + data: Vec::new(), + }; + + let (vss_scheme, secret_shares) = VerifiableSS::share( + params.threshold, + params.share_count, + &self.u_i, + ); + if correct_key_correct_decom_all { + Ok((vss_scheme, secret_shares.to_vec(), self.party_index)) + } else { + Err(err_type) + } + } + + pub fn phase2_verify_vss_construct_keypair_phase3_pok_dlog( + &self, + params: &Parameters, + y_vec: &[Point], + secret_shares_vec: &[Scalar], + vss_scheme_vec: &[VerifiableSS], + index: usize, + ) -> Result<(SharedKeys, DLogProof), ErrorType> + { + let mut bad_actors_vec = Vec::new(); + assert_eq!(y_vec.len(), usize::from(params.share_count)); + assert_eq!(secret_shares_vec.len(), usize::from(params.share_count)); + assert_eq!(vss_scheme_vec.len(), usize::from(params.share_count)); + + let correct_ss_verify = (0..y_vec.len()) + .map(|i| { + let res = vss_scheme_vec[i] + .validate_share( + &secret_shares_vec[i], + index.try_into().unwrap(), + ) + .is_ok() + && vss_scheme_vec[i].commitments[0] == y_vec[i]; + if !res { + bad_actors_vec.push(i); + false + } else { + true + } + }) + .all(|x| x); + + let err_type = ErrorType { + error_type: "invalid vss".to_string(), + bad_actors: bad_actors_vec, + data: Vec::new(), + }; + + if correct_ss_verify { + let (head, tail) = y_vec.split_at(1); + let y = tail.iter().fold(head[0].clone(), |acc, x| acc + x); + + let x_i = secret_shares_vec + .iter() + .fold(Scalar::::zero(), |acc, x| acc + x); + let dlog_proof = DLogProof::prove(&x_i); + Ok((SharedKeys { y, x_i }, dlog_proof)) + } else { + Err(err_type) + } + } + + pub fn get_commitments_to_xi( + vss_scheme_vec: &[VerifiableSS], + ) -> Vec> { + let len = vss_scheme_vec.len(); + let (head, tail) = vss_scheme_vec.split_at(1); + let mut global_coefficients = head[0].commitments.clone(); + for vss in tail { + for (i, coefficient_commitment) in + vss.commitments.iter().enumerate() + { + global_coefficients[i] = + &global_coefficients[i] + coefficient_commitment; + } + } + + let global_vss = VerifiableSS { + parameters: vss_scheme_vec[0].parameters.clone(), + commitments: global_coefficients, + proof: vss_scheme_vec[0].proof.clone(), + }; + (1..=len) + .map(|i| global_vss.get_point_commitment(i.try_into().unwrap())) + .collect::>>() + } + + pub fn update_commitments_to_xi( + comm: &Point, + vss_scheme: &VerifiableSS, + index: usize, + s: &[usize], + ) -> Point { + let s: Vec = s.iter().map(|&i| i.try_into().unwrap()).collect(); + let li = VerifiableSS::::map_share_to_new_params( + &vss_scheme.parameters, + index.try_into().unwrap(), + s.as_slice(), + ); + comm * &li + } + + pub fn verify_dlog_proofs_check_against_vss( + params: &Parameters, + dlog_proofs_vec: &[DLogProof], + y_vec: &[Point], + vss_vec: &[VerifiableSS], + ) -> Result<(), ErrorType> { + let mut bad_actors_vec = Vec::new(); + assert_eq!(y_vec.len(), usize::from(params.share_count)); + assert_eq!(dlog_proofs_vec.len(), usize::from(params.share_count)); + let xi_commitments = Keys::get_commitments_to_xi(vss_vec); + let xi_dlog_verify = (0..y_vec.len()) + .map(|i| { + let ver_res = DLogProof::verify(&dlog_proofs_vec[i]).is_ok(); + let verify_against_vss = + xi_commitments[i] == dlog_proofs_vec[i].pk; + if !ver_res || !verify_against_vss { + bad_actors_vec.push(i); + false + } else { + true + } + }) + .all(|x| x); + + let err_type = ErrorType { + error_type: "bad dlog proof".to_string(), + bad_actors: bad_actors_vec, + data: Vec::new(), + }; + + if xi_dlog_verify { + Ok(()) + } else { + Err(err_type) + } + } +} + +impl PartyPrivate { + pub fn set_private(key: Keys, shared_key: SharedKeys) -> Self { + Self { + u_i: key.u_i, + x_i: shared_key.x_i, + dk: key.dk, + } + } + + pub fn y_i(&self) -> Point { + let g = Point::generator(); + g * &self.u_i + } + + pub fn decrypt(&self, ciphertext: BigInt) -> RawPlaintext { + Paillier::decrypt(&self.dk, &RawCiphertext::from(ciphertext)) + } + + pub fn refresh_private_key( + &self, + factor: &Scalar, + index: usize, + ) -> Keys { + let u: Scalar = &self.u_i + factor; + let y = Point::generator() * &u; + let (ek, dk) = Paillier::keypair().keys(); + + let (N_tilde, h1, h2, xhi, xhi_inv) = generate_h1_h2_N_tilde(); + + Keys { + u_i: u, + y_i: y, + dk, + ek, + party_index: index, + N_tilde, + h1, + h2, + xhi, + xhi_inv, + } + } + + // we recommend using safe primes if the code is used in production + pub fn refresh_private_key_safe_prime( + &self, + factor: &Scalar, + index: usize, + ) -> Keys { + let u: Scalar = &self.u_i + factor; + let y = Point::generator() * &u; + let (ek, dk) = Paillier::keypair_safe_primes().keys(); + + let (N_tilde, h1, h2, xhi, xhi_inv) = generate_h1_h2_N_tilde(); + + Keys { + u_i: u, + y_i: y, + dk, + ek, + party_index: index, + N_tilde, + h1, + h2, + xhi, + xhi_inv, + } + } + + // used for verifiable recovery + pub fn to_encrypted_segment( + &self, + segment_size: usize, + num_of_segments: usize, + pub_ke_y: &Point, + g: &Point, + ) -> (Witness, Helgamalsegmented) { + Msegmentation::to_encrypted_segments( + &self.u_i, + &segment_size, + num_of_segments, + pub_ke_y, + g, + ) + } + + pub fn update_private_key( + &self, + factor_u_i: &Scalar, + factor_x_i: &Scalar, + ) -> Self { + PartyPrivate { + u_i: &self.u_i + factor_u_i, + x_i: &self.x_i + factor_x_i, + dk: self.dk.clone(), + } + } +} + +impl SignKeys { + pub fn g_w_vec( + pk_vec: &[Point], + s: &[usize], + vss_scheme: &VerifiableSS, + ) -> Vec> { + let s: Vec = s.iter().map(|&i| i.try_into().unwrap()).collect(); + // TODO: check bounds + (0..s.len()) + .map(|i| { + let li = VerifiableSS::::map_share_to_new_params( + &vss_scheme.parameters, + s[i], + s.as_slice(), + ); + &pk_vec[s[i] as usize] * &li + }) + .collect::>>() + } + + pub fn create( + private_x_i: &Scalar, + vss_scheme: &VerifiableSS, + index: usize, + s: &[usize], + ) -> Self { + let s: Vec = s.iter().map(|&i| i.try_into().unwrap()).collect(); + let li = VerifiableSS::::map_share_to_new_params( + &vss_scheme.parameters, + index.try_into().unwrap(), + s.as_slice(), + ); + let w_i = li * private_x_i; + let g = Point::generator(); + let g_w_i = g * &w_i; + let gamma_i = Scalar::::random(); + let g_gamma_i = g * &gamma_i; + let k_i = Scalar::::random(); + Self { + w_i, + g_w_i, + k_i, + gamma_i, + g_gamma_i, + } + } + + pub fn phase1_broadcast( + &self, + ) -> (SignBroadcastPhase1, SignDecommitPhase1) { + let blind_factor = BigInt::sample(SECURITY); + let g = Point::generator(); + let g_gamma_i = g * &self.gamma_i; + let com = HashCommitment::::create_commitment_with_user_defined_randomness( + &BigInt::from_bytes(g_gamma_i.to_bytes(true).as_ref()), + &blind_factor, + ); + + ( + SignBroadcastPhase1 { com }, + SignDecommitPhase1 { + blind_factor, + g_gamma_i: self.g_gamma_i.clone(), + }, + ) + } + + pub fn phase2_delta_i( + &self, + alpha_vec: &[Scalar], + beta_vec: &[Scalar], + ) -> Scalar { + let vec_len = alpha_vec.len(); + assert_eq!(alpha_vec.len(), beta_vec.len()); + // assert_eq!(alpha_vec.len(), self.s.len() - 1); + let ki_gamma_i = &self.k_i * &self.gamma_i; + + (0..vec_len) + .map(|i| &alpha_vec[i] + &beta_vec[i]) + .fold(ki_gamma_i, |acc, x| acc + x) + } + + pub fn phase2_sigma_i( + &self, + miu_vec: &[Scalar], + ni_vec: &[Scalar], + ) -> Scalar { + let vec_len = miu_vec.len(); + assert_eq!(miu_vec.len(), ni_vec.len()); + //assert_eq!(miu_vec.len(), self.s.len() - 1); + let ki_w_i = &self.k_i * &self.w_i; + (0..vec_len) + .map(|i| &miu_vec[i] + &ni_vec[i]) + .fold(ki_w_i, |acc, x| acc + x) + } + + pub fn phase3_compute_t_i( + sigma_i: &Scalar, + ) -> ( + Point, + Scalar, + PedersenProof, + ) { + let g_sigma_i = Point::generator() * sigma_i; + let l = Scalar::::random(); + let h_l = Point::::base_point2() * &l; + let T = g_sigma_i + h_l; + let T_zk_proof = PedersenProof::::prove(sigma_i, &l); + + (T, l, T_zk_proof) + } + pub fn phase3_reconstruct_delta( + delta_vec: &[Scalar], + ) -> Scalar { + let sum = delta_vec + .iter() + .fold(Scalar::::zero(), |acc, x| acc + x); + sum.invert().unwrap() + } + + pub fn phase4( + delta_inv: &Scalar, + b_proof_vec: &[&DLogProof], + phase1_decommit_vec: Vec, + bc1_vec: &[SignBroadcastPhase1], + index: usize, + ) -> Result, ErrorType> { + let mut bad_actors_vec = Vec::new(); + let test_b_vec_and_com = (0..b_proof_vec.len()) + .map(|j| { + let ind = if j < index { j } else { j + 1 }; + let res = b_proof_vec[j].pk == phase1_decommit_vec[ind].g_gamma_i + && HashCommitment::::create_commitment_with_user_defined_randomness( + &BigInt::from_bytes( + phase1_decommit_vec[ind].g_gamma_i.to_bytes(true).as_ref(), + ), + &phase1_decommit_vec[ind].blind_factor, + ) == bc1_vec[ind].com; + if !res { + bad_actors_vec.push(ind); + false + } else { + true + } + }) + .all(|x| x); + + let mut g_gamma_i_iter = phase1_decommit_vec.iter(); + let head = g_gamma_i_iter.next().unwrap(); + let tail = g_gamma_i_iter; + + let err_type = ErrorType { + error_type: "bad gamma_i decommit".to_string(), + bad_actors: bad_actors_vec, + data: Vec::new(), + }; + + if test_b_vec_and_com { + Ok({ + let gamma_sum = tail + .fold(head.g_gamma_i.clone(), |acc, x| acc + &x.g_gamma_i); + // R + gamma_sum * delta_inv + }) + } else { + Err(err_type) + } + } +} + +impl LocalSignature { + pub fn phase5_proof_pdl( + R_dash: &Point, + R: &Point, + k_ciphertext: &BigInt, + ek: &EncryptionKey, + k_i: &Scalar, + k_enc_randomness: &BigInt, + dlog_statement: &DLogStatement, + ) -> PDLwSlackProof { + // Generate PDL with slack statement, witness and proof + let pdl_w_slack_statement = PDLwSlackStatement { + ciphertext: k_ciphertext.clone(), + ek: ek.clone(), + Q: R_dash.clone(), + G: R.clone(), + h1: dlog_statement.g.clone(), + h2: dlog_statement.ni.clone(), + N_tilde: dlog_statement.N.clone(), + }; + + let pdl_w_slack_witness = PDLwSlackWitness { + x: k_i.clone(), + r: k_enc_randomness.clone(), + }; + + PDLwSlackProof::prove(&pdl_w_slack_witness, &pdl_w_slack_statement) + } + + pub fn phase5_verify_pdl( + pdl_w_slack_proof_vec: &[PDLwSlackProof], + R_dash: &Point, + R: &Point, + k_ciphertext: &BigInt, + ek: &EncryptionKey, + dlog_statement: &[DLogStatement], + s: &[usize], + i: usize, + ) -> Result<(), ErrorType> { + let mut bad_actors_vec = Vec::new(); + + let num_of_other_participants = s.len() - 1; + if pdl_w_slack_proof_vec.len() != num_of_other_participants { + bad_actors_vec.push(i); + } else { + let proofs_verification = (0..pdl_w_slack_proof_vec.len()) + .map(|j| { + let ind = if j < i { j } else { j + 1 }; + let pdl_w_slack_statement = PDLwSlackStatement { + ciphertext: k_ciphertext.clone(), + ek: ek.clone(), + Q: R_dash.clone(), + G: R.clone(), + h1: dlog_statement[s[ind]].g.clone(), + h2: dlog_statement[s[ind]].ni.clone(), + N_tilde: dlog_statement[s[ind]].N.clone(), + }; + let ver_res = + pdl_w_slack_proof_vec[j].verify(&pdl_w_slack_statement); + if ver_res.is_err() { + bad_actors_vec.push(i); + false + } else { + true + } + }) + .all(|x| x); + if proofs_verification { + return Ok(()); + } + } + + let err_type = ErrorType { + error_type: "Bad PDLwSlack proof".to_string(), + bad_actors: bad_actors_vec, + data: Vec::new(), + }; + Err(err_type) + } + + pub fn phase5_check_R_dash_sum( + R_dash_vec: &[Point], + ) -> Result<(), Error> { + let sum = R_dash_vec + .iter() + .fold(Point::generator().to_point(), |acc, x| acc + x); + match sum - &Point::generator().to_point() + == Point::generator().to_point() + { + true => Ok(()), + false => Err(Phase5BadSum), + } + } + + pub fn phase6_compute_S_i_and_proof_of_consistency( + R: &Point, + T: &Point, + sigma: &Scalar, + l: &Scalar, + ) -> (Point, HomoELGamalProof) { + let S = R * sigma; + let delta = HomoElGamalStatement { + G: R.clone(), + H: Point::::base_point2().clone(), + Y: Point::generator().to_point(), + D: T.clone(), + E: S.clone(), + }; + let witness = HomoElGamalWitness { + x: l.clone(), + r: sigma.clone(), + }; + let proof = HomoELGamalProof::prove(&witness, &delta); + + (S, proof) + } + + pub fn phase6_verify_proof( + S_vec: &[Point], + proof_vec: &[HomoELGamalProof], + R_vec: &[Point], + T_vec: &[Point], + ) -> Result<(), ErrorType> { + let mut bad_actors_vec = Vec::new(); + let mut verify_proofs = true; + for i in 0..proof_vec.len() { + let delta = HomoElGamalStatement { + G: R_vec[i].clone(), + H: Point::::base_point2().clone(), + Y: Point::generator().to_point(), + D: T_vec[i].clone(), + E: S_vec[i].clone(), + }; + if proof_vec[i].verify(&delta).is_err() { + verify_proofs = false; + bad_actors_vec.push(i); + }; + } + + match verify_proofs { + true => Ok(()), + false => { + let err_type = ErrorType { + error_type: "phase6".to_string(), + bad_actors: bad_actors_vec, + data: Vec::new(), + }; + Err(err_type) + } + } + } + + pub fn phase6_check_S_i_sum( + pubkey_y: &Point, + S_vec: &[Point], + ) -> Result<(), Error> { + let sum_plus_g = S_vec + .iter() + .fold(Point::generator().to_point(), |acc, x| acc + x); + let sum = sum_plus_g - &Point::generator().to_point(); + + match &sum == pubkey_y { + true => Ok(()), + false => Err(Phase6Error), + } + } + + pub fn phase7_local_sig( + k_i: &Scalar, + message: &BigInt, + R: &Point, + sigma_i: &Scalar, + pubkey: &Point, + ) -> Self { + let m_fe = Scalar::::from(message); + let r = Scalar::::from( + &R.x_coord() + .unwrap() + .mod_floor(Scalar::::group_order()), + ); + let s_i = m_fe * k_i + &r * sigma_i; + Self { + r, + R: R.clone(), + s_i, + m: message.clone(), + y: pubkey.clone(), + } + } + + pub fn output_signature( + &self, + s_vec: &[Scalar], + ) -> Result { + let mut s = s_vec.iter().fold(self.s_i.clone(), |acc, x| acc + x); + let s_bn = s.to_bigint(); + + let r = Scalar::::from( + &self + .R + .x_coord() + .unwrap() + .mod_floor(Scalar::::group_order()), + ); + let ry: BigInt = self + .R + .y_coord() + .unwrap() + .mod_floor(Scalar::::group_order()); + + /* + Calculate recovery id - it is not possible to compute the public key out of the signature + itself. Recovery id is used to enable extracting the public key uniquely. + 1. id = R.y & 1 + 2. if (s > curve.q / 2) id = id ^ 1 + */ + let is_ry_odd = ry.test_bit(0); + let mut recid = if is_ry_odd { 1 } else { 0 }; + let s_tag_bn = Scalar::::group_order() - &s_bn; + if s_bn > s_tag_bn { + s = Scalar::::from(&s_tag_bn); + recid ^= 1; + } + let sig = SignatureRecid { r, s, recid }; + let ver = verify(&sig, &self.y, &self.m).is_ok(); + if ver { + Ok(sig) + } else { + Err(InvalidSig) + } + } +} + +pub fn verify( + sig: &SignatureRecid, + y: &Point, + message: &BigInt, +) -> Result<(), Error> { + let b = sig.s.invert().unwrap(); + let a = Scalar::::from(message); + let u1 = a * &b; + let u2 = &sig.r * &b; + + let g = Point::generator(); + let gu1 = g * u1; + let yu2 = y * &u2; + // can be faster using shamir trick + + if sig.r + == Scalar::::from( + &(gu1 + yu2) + .x_coord() + .unwrap() + .mod_floor(Scalar::::group_order()), + ) + { + Ok(()) + } else { + Err(InvalidSig) + } +} diff --git a/multi-party-ecdsa/src/gg_2020/state_machine/keygen.rs b/multi-party-ecdsa/src/gg_2020/state_machine/keygen.rs new file mode 100644 index 00000000..4810e635 --- /dev/null +++ b/multi-party-ecdsa/src/gg_2020/state_machine/keygen.rs @@ -0,0 +1,601 @@ +//! High-level keygen protocol implementation + +use std::{fmt, mem::replace, time::Duration}; + +use curv::{ + cryptographic_primitives::proofs::sigma_dlog::DLogProof, + elliptic::curves::secp256_k1::Secp256k1, +}; +use round_based::{ + containers::{ + push::{Push, PushExt}, + *, + }, + IsCritical, Msg, StateMachine, +}; +use serde::{Deserialize, Serialize}; +use sha2::Sha256; +use thiserror::Error; + +use crate::protocols::multi_party_ecdsa::gg_2020::{self, VerifiableSS}; + +mod rounds; + +use private::InternalError; +pub use rounds::{LocalKey, ProceedError}; +use rounds::{Round0, Round1, Round2, Round3, Round4}; + +/// Keygen protocol state machine +/// +/// Successfully completed keygen protocol produces [LocalKey] that can be used +/// in further [signing](super::sign) protocol. +pub struct Keygen { + round: R, + + msgs1: + Option>>, + msgs2: + Option>>, + msgs3: Option, Vec)>>>, + msgs4: Option>>>, + + msgs_queue: Vec>, + + party_i: u16, + party_n: u16, +} + +impl Keygen { + /// Constructs a party of keygen protocol + /// + /// Takes party index `i` (in range `[1; n]`), threshold value `t`, and + /// total number of parties `n`. Party index identifies this party in + /// the protocol, so it must be guaranteed to be unique. + /// + /// Returns error if: + /// * `n` is less than 2, returns [Error::TooFewParties] + /// * `t` is not in range `[1; n-1]`, returns [Error::InvalidThreshold] + /// * `i` is not in range `[1; n]`, returns [Error::InvalidPartyIndex] + pub fn new(i: u16, t: u16, n: u16) -> Result { + if n < 2 { + return Err(Error::TooFewParties); + } + if t == 0 || t >= n { + return Err(Error::InvalidThreshold); + } + if i == 0 || i > n { + return Err(Error::InvalidPartyIndex); + } + let mut state = Self { + round: R::Round0(Round0 { party_i: i, t, n }), + + msgs1: Some(Round1::expects_messages(i, n)), + msgs2: Some(Round2::expects_messages(i, n)), + msgs3: Some(Round3::expects_messages(i, n)), + msgs4: Some(Round4::expects_messages(i, n)), + + msgs_queue: vec![], + party_i: i, + party_n: n, + }; + + state.proceed_round(false)?; + Ok(state) + } + + fn gmap_queue<'a, T, F>(&'a mut self, mut f: F) -> impl Push> + 'a + where + F: FnMut(T) -> M + 'a, + { + (&mut self.msgs_queue) + .gmap(move |m: Msg| m.map_body(|m| ProtocolMessage(f(m)))) + } + + /// Proceeds round state if it received enough messages and if it's cheap to + /// compute or `may_block == true` + fn proceed_round(&mut self, may_block: bool) -> Result<()> { + let store1_wants_more = + self.msgs1.as_ref().map(|s| s.wants_more()).unwrap_or(false); + let store2_wants_more = + self.msgs2.as_ref().map(|s| s.wants_more()).unwrap_or(false); + let store3_wants_more = + self.msgs3.as_ref().map(|s| s.wants_more()).unwrap_or(false); + let store4_wants_more = + self.msgs4.as_ref().map(|s| s.wants_more()).unwrap_or(false); + + let next_state: R; + let try_again: bool = match replace(&mut self.round, R::Gone) { + R::Round0(round) if !round.is_expensive() || may_block => { + next_state = round + .proceed(self.gmap_queue(M::Round1)) + .map(R::Round1) + .map_err(Error::ProceedRound)?; + true + } + s @ R::Round0(_) => { + next_state = s; + false + } + R::Round1(round) + if !store1_wants_more + && (!round.is_expensive() || may_block) => + { + let store = + self.msgs1.take().ok_or(InternalError::StoreGone)?; + let msgs = store + .finish() + .map_err(InternalError::RetrieveRoundMessages)?; + next_state = round + .proceed(msgs, self.gmap_queue(M::Round2)) + .map(R::Round2) + .map_err(Error::ProceedRound)?; + true + } + s @ R::Round1(_) => { + next_state = s; + false + } + R::Round2(round) + if !store2_wants_more + && (!round.is_expensive() || may_block) => + { + let store = + self.msgs2.take().ok_or(InternalError::StoreGone)?; + let msgs = store + .finish() + .map_err(InternalError::RetrieveRoundMessages)?; + next_state = round + .proceed(msgs, self.gmap_queue(M::Round3)) + .map(R::Round3) + .map_err(Error::ProceedRound)?; + true + } + s @ R::Round2(_) => { + next_state = s; + false + } + R::Round3(round) + if !store3_wants_more + && (!round.is_expensive() || may_block) => + { + let store = + self.msgs3.take().ok_or(InternalError::StoreGone)?; + let msgs = store + .finish() + .map_err(InternalError::RetrieveRoundMessages)?; + next_state = round + .proceed(msgs, self.gmap_queue(M::Round4)) + .map(R::Round4) + .map_err(Error::ProceedRound)?; + true + } + s @ R::Round3(_) => { + next_state = s; + false + } + R::Round4(round) + if !store4_wants_more + && (!round.is_expensive() || may_block) => + { + let store = + self.msgs4.take().ok_or(InternalError::StoreGone)?; + let msgs = store + .finish() + .map_err(InternalError::RetrieveRoundMessages)?; + next_state = round + .proceed(msgs) + .map(R::Final) + .map_err(Error::ProceedRound)?; + true + } + s @ R::Round4(_) => { + next_state = s; + false + } + s @ R::Final(_) | s @ R::Gone => { + next_state = s; + false + } + }; + + self.round = next_state; + if try_again { + self.proceed_round(may_block) + } else { + Ok(()) + } + } +} + +impl StateMachine for Keygen { + type MessageBody = ProtocolMessage; + type Err = Error; + type Output = LocalKey; + + fn handle_incoming(&mut self, msg: Msg) -> Result<()> { + let current_round = self.current_round(); + + match msg.body { + ProtocolMessage(M::Round1(m)) => { + let store = self.msgs1.as_mut().ok_or( + Error::ReceivedOutOfOrderMessage { + current_round, + msg_round: 1, + }, + )?; + store + .push_msg(Msg { + sender: msg.sender, + receiver: msg.receiver, + body: m, + }) + .map_err(Error::HandleMessage)?; + self.proceed_round(false) + } + ProtocolMessage(M::Round2(m)) => { + let store = self.msgs2.as_mut().ok_or( + Error::ReceivedOutOfOrderMessage { + current_round, + msg_round: 2, + }, + )?; + store + .push_msg(Msg { + sender: msg.sender, + receiver: msg.receiver, + body: m, + }) + .map_err(Error::HandleMessage)?; + self.proceed_round(false) + } + ProtocolMessage(M::Round3(m)) => { + let store = self.msgs3.as_mut().ok_or( + Error::ReceivedOutOfOrderMessage { + current_round, + msg_round: 3, + }, + )?; + store + .push_msg(Msg { + sender: msg.sender, + receiver: msg.receiver, + body: m, + }) + .map_err(Error::HandleMessage)?; + self.proceed_round(false) + } + ProtocolMessage(M::Round4(m)) => { + let store = self.msgs4.as_mut().ok_or( + Error::ReceivedOutOfOrderMessage { + current_round, + msg_round: 4, + }, + )?; + store + .push_msg(Msg { + sender: msg.sender, + receiver: msg.receiver, + body: m, + }) + .map_err(Error::HandleMessage)?; + self.proceed_round(false) + } + } + } + + fn message_queue(&mut self) -> &mut Vec> { + &mut self.msgs_queue + } + + fn wants_to_proceed(&self) -> bool { + let store1_wants_more = + self.msgs1.as_ref().map(|s| s.wants_more()).unwrap_or(false); + let store2_wants_more = + self.msgs2.as_ref().map(|s| s.wants_more()).unwrap_or(false); + let store3_wants_more = + self.msgs3.as_ref().map(|s| s.wants_more()).unwrap_or(false); + let store4_wants_more = + self.msgs4.as_ref().map(|s| s.wants_more()).unwrap_or(false); + + match &self.round { + R::Round0(_) => true, + R::Round1(_) => !store1_wants_more, + R::Round2(_) => !store2_wants_more, + R::Round3(_) => !store3_wants_more, + R::Round4(_) => !store4_wants_more, + R::Final(_) | R::Gone => false, + } + } + + fn proceed(&mut self) -> Result<()> { + self.proceed_round(true) + } + + fn round_timeout(&self) -> Option { + None + } + + fn round_timeout_reached(&mut self) -> Self::Err { + panic!("no timeout was set") + } + + fn is_finished(&self) -> bool { + matches!(self.round, R::Final(_)) + } + + fn pick_output(&mut self) -> Option> { + match self.round { + R::Final(_) => (), + R::Gone => return Some(Err(Error::DoublePickOutput)), + _ => return None, + } + + match replace(&mut self.round, R::Gone) { + R::Final(result) => Some(Ok(result)), + _ => unreachable!("guaranteed by match expression above"), + } + } + + fn current_round(&self) -> u16 { + match &self.round { + R::Round0(_) => 0, + R::Round1(_) => 1, + R::Round2(_) => 2, + R::Round3(_) => 3, + R::Round4(_) => 4, + R::Final(_) | R::Gone => 5, + } + } + + fn total_rounds(&self) -> Option { + Some(4) + } + + fn party_ind(&self) -> u16 { + self.party_i + } + + fn parties(&self) -> u16 { + self.party_n + } +} + +impl super::traits::RoundBlame for Keygen { + /// Returns number of unwilling parties and a vector of their party indexes. + fn round_blame(&self) -> (u16, Vec) { + let store1_blame = + self.msgs1.as_ref().map(|s| s.blame()).unwrap_or_default(); + let store2_blame = + self.msgs2.as_ref().map(|s| s.blame()).unwrap_or_default(); + let store3_blame = + self.msgs3.as_ref().map(|s| s.blame()).unwrap_or_default(); + let store4_blame = + self.msgs4.as_ref().map(|s| s.blame()).unwrap_or_default(); + + let default = (0, vec![]); + match &self.round { + R::Round0(_) => default, + R::Round1(_) => store1_blame, + R::Round2(_) => store2_blame, + R::Round3(_) => store3_blame, + R::Round4(_) => store4_blame, + R::Final(_) | R::Gone => default, + } + } +} + +impl fmt::Debug for Keygen { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let current_round = match &self.round { + R::Round0(_) => "0", + R::Round1(_) => "1", + R::Round2(_) => "2", + R::Round3(_) => "3", + R::Round4(_) => "4", + R::Final(_) => "[Final]", + R::Gone => "[Gone]", + }; + let msgs1 = match self.msgs1.as_ref() { + Some(msgs) => format!( + "[{}/{}]", + msgs.messages_received(), + msgs.messages_total() + ), + None => "[None]".into(), + }; + let msgs2 = match self.msgs2.as_ref() { + Some(msgs) => format!( + "[{}/{}]", + msgs.messages_received(), + msgs.messages_total() + ), + None => "[None]".into(), + }; + let msgs3 = match self.msgs3.as_ref() { + Some(msgs) => format!( + "[{}/{}]", + msgs.messages_received(), + msgs.messages_total() + ), + None => "[None]".into(), + }; + let msgs4 = match self.msgs4.as_ref() { + Some(msgs) => format!( + "[{}/{}]", + msgs.messages_received(), + msgs.messages_total() + ), + None => "[None]".into(), + }; + write!( + f, + "{{Keygen at round={} msgs1={} msgs2={} msgs3={} msgs4={} queue=[len={}]}}", + current_round, + msgs1, + msgs2, + msgs3, + msgs4, + self.msgs_queue.len() + ) + } +} + +// Rounds + +enum R { + Round0(Round0), + Round1(Round1), + Round2(Round2), + Round3(Round3), + Round4(Round4), + Final(LocalKey), + Gone, +} + +// Messages + +/// Protocol message which parties send on wire +/// +/// Hides actual messages structure so it could be changed without breaking +/// semver policy. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ProtocolMessage(M); + +#[derive(Clone, Debug, Serialize, Deserialize)] +enum M { + Round1(gg_2020::party_i::KeyGenBroadcastMessage1), + Round2(gg_2020::party_i::KeyGenDecommitMessage1), + Round3((VerifiableSS, Vec)), + Round4(DLogProof), +} + +impl crate::MessageRoundID for ProtocolMessage { + fn round_id(&self) -> u16 { + match self.0 { + M::Round1(_) => 1, + M::Round2(_) => 2, + M::Round3(_) => 3, + M::Round4(_) => 4, + } + } +} + +// Error + +type Result = std::result::Result; + +/// Error type of keygen protocol +#[derive(Debug, Error)] +#[non_exhaustive] +pub enum Error { + /// Round proceeding resulted in error + #[error("proceed round: {0}")] + ProceedRound(#[source] ProceedError), + + /// Too few parties (`n < 2`) + #[error("at least 2 parties are required for keygen")] + TooFewParties, + /// Threshold value `t` is not in range `[1; n-1]` + #[error("threshold is not in range [1; n-1]")] + InvalidThreshold, + /// Party index `i` is not in range `[1; n]` + #[error("party index is not in range [1; n]")] + InvalidPartyIndex, + + /// Received message didn't pass pre-validation + #[error("received message didn't pass pre-validation: {0}")] + HandleMessage(#[source] StoreErr), + /// Received message which we didn't expect to receive now (e.g. message + /// from previous round) + #[error( + "didn't expect to receive message from round {msg_round} (being at round {current_round})" + )] + ReceivedOutOfOrderMessage { current_round: u16, msg_round: u16 }, + /// [Keygen::pick_output] called twice + #[error("pick_output called twice")] + DoublePickOutput, + + /// Some internal assertions were failed, which is a bug + #[doc(hidden)] + #[error("internal error: {0:?}")] + InternalError(InternalError), +} + +impl IsCritical for Error { + fn is_critical(&self) -> bool { + match self { + Error::ProceedRound(e) => e.is_critical(), + // These errors are not critical, because they are handled by the + // protocol and don't indicate a bug in the library. + Error::HandleMessage(e) => !matches!( + e, + StoreErr::MsgOverwrite + | StoreErr::NotForMe + | StoreErr::WantsMoreMessages + ), + Error::ReceivedOutOfOrderMessage { .. } => false, + Error::DoublePickOutput + | Error::TooFewParties + | Error::InvalidThreshold + | Error::InvalidPartyIndex + | Error::InternalError(_) => true, + } + } +} + +impl From for Error { + fn from(err: InternalError) -> Self { + Self::InternalError(err) + } +} + +mod private { + #[derive(Debug)] + #[non_exhaustive] + pub enum InternalError { + /// [Messages store](super::MessageStore) reported that it received all + /// messages it wanted to receive, but refused to return + /// message container + RetrieveRoundMessages(super::StoreErr), + #[doc(hidden)] + StoreGone, + } +} + +#[cfg(test)] +pub mod test { + use round_based::dev::Simulation; + + use super::*; + + pub fn simulate_keygen(t: u16, n: u16) -> Vec> { + let mut simulation = Simulation::new(); + simulation.enable_benchmarks(true); + + for i in 1..=n { + simulation.add_party(Keygen::new(i, t, n).unwrap()); + } + + let keys = simulation.run().unwrap(); + + println!("Benchmark results:"); + println!("{:#?}", simulation.benchmark_results().unwrap()); + + keys + } + + #[test] + fn simulate_keygen_t1_n2() { + simulate_keygen(1, 2); + } + + #[test] + fn simulate_keygen_t1_n3() { + simulate_keygen(1, 3); + } + + #[test] + fn simulate_keygen_t2_n3() { + simulate_keygen(2, 3); + } +} diff --git a/multi-party-ecdsa/src/gg_2020/state_machine/keygen/rounds.rs b/multi-party-ecdsa/src/gg_2020/state_machine/keygen/rounds.rs new file mode 100644 index 00000000..66c4486c --- /dev/null +++ b/multi-party-ecdsa/src/gg_2020/state_machine/keygen/rounds.rs @@ -0,0 +1,397 @@ +use curv::{ + arithmetic::Converter, + cryptographic_primitives::proofs::sigma_dlog::DLogProof, + elliptic::curves::{secp256_k1::Secp256k1, Curve, Point, Scalar}, + BigInt, +}; +use sha2::Sha256; + +use serde::{Deserialize, Serialize}; +use thiserror::Error; + +use paillier::{ + Decrypt, Encrypt, EncryptionKey, Paillier, RawCiphertext, RawPlaintext, +}; +use round_based::{ + containers::{ + self, push::Push, BroadcastMsgs, MessageStore, P2PMsgs, P2PMsgsStore, + Store, + }, + IsCritical, Msg, +}; +use zk_paillier::zkproofs::DLogStatement; + +use crate::protocols::multi_party_ecdsa::gg_2020::{ + self, + party_i::{KeyGenBroadcastMessage1, KeyGenDecommitMessage1, Keys}, + ErrorType, VerifiableSS, +}; + +pub struct Round0 { + pub party_i: u16, + pub t: u16, + pub n: u16, +} + +impl Round0 { + pub fn proceed(self, mut output: O) -> Result + where + O: Push>, + { + let party_keys = Keys::create(self.party_i as usize); + let (bc1, decom1) = party_keys + .phase1_broadcast_phase3_proof_of_correct_key_proof_of_correct_h1h2( + ); + + output.push(Msg { + sender: self.party_i, + receiver: None, + body: bc1.clone(), + }); + Ok(Round1 { + keys: party_keys, + bc1, + decom1, + party_i: self.party_i, + t: self.t, + n: self.n, + }) + } + pub fn is_expensive(&self) -> bool { + true + } +} + +pub struct Round1 { + keys: Keys, + bc1: KeyGenBroadcastMessage1, + decom1: KeyGenDecommitMessage1, + party_i: u16, + t: u16, + n: u16, +} + +impl Round1 { + pub fn proceed( + self, + input: BroadcastMsgs, + mut output: O, + ) -> Result + where + O: Push>, + { + output.push(Msg { + sender: self.party_i, + receiver: None, + body: self.decom1.clone(), + }); + Ok(Round2 { + keys: self.keys, + received_comm: input.into_vec_including_me(self.bc1), + decom: self.decom1, + + party_i: self.party_i, + t: self.t, + n: self.n, + }) + } + pub fn is_expensive(&self) -> bool { + false + } + pub fn expects_messages( + i: u16, + n: u16, + ) -> Store> { + containers::BroadcastMsgsStore::new(i, n) + } +} + +pub struct Round2 { + keys: gg_2020::party_i::Keys, + received_comm: Vec, + decom: KeyGenDecommitMessage1, + + party_i: u16, + t: u16, + n: u16, +} + +impl Round2 { + pub fn proceed( + self, + input: BroadcastMsgs, + mut output: O, + ) -> Result + where + O: Push, Vec)>>, + { + let params = gg_2020::party_i::Parameters { + threshold: self.t, + share_count: self.n, + }; + let received_decom = input.into_vec_including_me(self.decom); + + let vss_result = self + .keys + .phase1_verify_com_phase3_verify_correct_key_verify_dlog_phase2_distribute( + ¶ms, + &received_decom, + &self.received_comm, + ) + .map_err(ProceedError::Round2VerifyCommitments)?; + + for (i, share) in vss_result.1.iter().enumerate() { + if i + 1 == usize::from(self.party_i) { + continue; + } + + let enc_key_for_recipient = &self.received_comm[i].e; + let encrypted_share = Paillier::encrypt( + enc_key_for_recipient, + RawPlaintext::from(share.to_bigint()), + ); + output.push(Msg { + sender: self.party_i, + receiver: Some(i as u16 + 1), + body: (vss_result.0.clone(), encrypted_share.0.to_bytes()), + }) + } + + Ok(Round3 { + keys: self.keys, + + y_vec: received_decom.into_iter().map(|d| d.y_i).collect(), + bc_vec: self.received_comm, + + own_vss: vss_result.0.clone(), + own_share: vss_result.1[usize::from(self.party_i - 1)].clone(), + + party_i: self.party_i, + t: self.t, + n: self.n, + }) + } + pub fn is_expensive(&self) -> bool { + true + } + pub fn expects_messages( + i: u16, + n: u16, + ) -> Store> { + containers::BroadcastMsgsStore::new(i, n) + } +} + +pub struct Round3 { + keys: gg_2020::party_i::Keys, + + y_vec: Vec>, + bc_vec: Vec, + + own_vss: VerifiableSS, + own_share: Scalar, + + party_i: u16, + t: u16, + n: u16, +} + +impl Round3 { + pub fn proceed( + self, + input: P2PMsgs<(VerifiableSS, Vec)>, + mut output: O, + ) -> Result + where + O: Push>>, + { + let params = gg_2020::party_i::Parameters { + threshold: self.t, + share_count: self.n, + }; + let input: P2PMsgs<(VerifiableSS, Scalar)> = { + let encrypted_input = input.into_iter_indexed(); + let mut decrypted_input = P2PMsgsStore::new(self.party_i, self.n); + for (i, (vss, encrypted_share)) in encrypted_input { + let v = BigInt::from_bytes(&encrypted_share); + let c = RawCiphertext::from(v); + let raw_share: RawPlaintext<'_> = + Paillier::decrypt(&self.keys.dk, c); + let share = Scalar::from_bigint(&raw_share.0); + let _ = decrypted_input.push_msg(Msg { + sender: i, + receiver: Some(self.party_i), + body: (vss, share), + }); + } + decrypted_input.finish().unwrap() + }; + + let (vss_schemes, party_shares): (Vec<_>, Vec<_>) = input + .into_vec_including_me((self.own_vss, self.own_share)) + .into_iter() + .unzip(); + let (shared_keys, dlog_proof) = self + .keys + .phase2_verify_vss_construct_keypair_phase3_pok_dlog( + ¶ms, + &self.y_vec, + &party_shares, + &vss_schemes, + self.party_i.into(), + ) + .map_err(ProceedError::Round3VerifyVssConstruct)?; + + output.push(Msg { + sender: self.party_i, + receiver: None, + body: dlog_proof.clone(), + }); + + Ok(Round4 { + keys: self.keys.clone(), + y_vec: self.y_vec.clone(), + bc_vec: self.bc_vec, + shared_keys, + own_dlog_proof: dlog_proof, + vss_vec: vss_schemes, + + party_i: self.party_i, + t: self.t, + n: self.n, + }) + } + pub fn is_expensive(&self) -> bool { + true + } + pub fn expects_messages( + i: u16, + n: u16, + ) -> Store, Vec)>> { + containers::P2PMsgsStore::new(i, n) + } +} + +pub struct Round4 { + keys: gg_2020::party_i::Keys, + y_vec: Vec>, + bc_vec: Vec, + shared_keys: gg_2020::party_i::SharedKeys, + own_dlog_proof: DLogProof, + vss_vec: Vec>, + + party_i: u16, + t: u16, + n: u16, +} + +impl Round4 { + pub fn proceed( + self, + input: BroadcastMsgs>, + ) -> Result> { + let params = gg_2020::party_i::Parameters { + threshold: self.t, + share_count: self.n, + }; + let dlog_proofs = + input.into_vec_including_me(self.own_dlog_proof.clone()); + + Keys::verify_dlog_proofs_check_against_vss( + ¶ms, + &dlog_proofs, + &self.y_vec, + &self.vss_vec, + ) + .map_err(ProceedError::Round4VerifyDLogProof)?; + let pk_vec = (0..params.share_count as usize) + .map(|i| dlog_proofs[i].pk.clone()) + .collect::>>(); + + let paillier_key_vec = (0..params.share_count) + .map(|i| self.bc_vec[i as usize].e.clone()) + .collect::>(); + let h1_h2_n_tilde_vec = self + .bc_vec + .iter() + .map(|bc1| bc1.dlog_statement.clone()) + .collect::>(); + + let (head, tail) = self.y_vec.split_at(1); + let y_sum = tail.iter().fold(head[0].clone(), |acc, x| acc + x); + + let local_key = LocalKey { + paillier_dk: self.keys.dk, + pk_vec, + + keys_linear: self.shared_keys.clone(), + paillier_key_vec, + y_sum_s: y_sum, + h1_h2_n_tilde_vec, + + vss_scheme: self.vss_vec[usize::from(self.party_i - 1)].clone(), + + i: self.party_i, + t: self.t, + n: self.n, + }; + + Ok(local_key) + } + pub fn is_expensive(&self) -> bool { + true + } + pub fn expects_messages( + i: u16, + n: u16, + ) -> Store>> { + containers::BroadcastMsgsStore::new(i, n) + } +} + +/// Local secret obtained by party after [keygen](super::Keygen) protocol is +/// completed +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct LocalKey { + pub paillier_dk: paillier::DecryptionKey, + pub pk_vec: Vec>, + pub keys_linear: gg_2020::party_i::SharedKeys, + pub paillier_key_vec: Vec, + pub y_sum_s: Point, + pub h1_h2_n_tilde_vec: Vec, + pub vss_scheme: VerifiableSS, + pub i: u16, + pub t: u16, + pub n: u16, +} + +impl LocalKey { + /// Public key of secret shared between parties + pub fn public_key(&self) -> Point { + self.y_sum_s.clone() + } +} + +// Errors + +type Result = std::result::Result; + +/// Proceeding protocol error +/// +/// Subset of [keygen errors](enum@super::Error) that can occur at protocol +/// proceeding (i.e. after every message was received and pre-validated). +#[derive(Debug, Error)] +pub enum ProceedError { + #[error("round 2: verify commitments: {0:?}")] + Round2VerifyCommitments(ErrorType), + #[error("round 3: verify vss construction: {0:?}")] + Round3VerifyVssConstruct(ErrorType), + #[error("round 4: verify dlog proof: {0:?}")] + Round4VerifyDLogProof(ErrorType), +} + +impl IsCritical for ProceedError { + fn is_critical(&self) -> bool { + true + } +} diff --git a/multi-party-ecdsa/src/gg_2020/state_machine/mod.rs b/multi-party-ecdsa/src/gg_2020/state_machine/mod.rs new file mode 100644 index 00000000..59585288 --- /dev/null +++ b/multi-party-ecdsa/src/gg_2020/state_machine/mod.rs @@ -0,0 +1,3 @@ +pub mod keygen; +pub mod sign; +pub mod traits; diff --git a/multi-party-ecdsa/src/gg_2020/state_machine/sign.rs b/multi-party-ecdsa/src/gg_2020/state_machine/sign.rs new file mode 100644 index 00000000..f77c1a9b --- /dev/null +++ b/multi-party-ecdsa/src/gg_2020/state_machine/sign.rs @@ -0,0 +1,839 @@ +//! # High-level threshold signing protocol implementation +//! +//! Key feature of GG20 protocol is one-round online signing, meaning that every +//! party needs to broadcast just a single message to sign a data. However, it +//! still requires completing an offline computation for fixed set of parties +//! +//! ## How to get things work +//! +//! First of all, parties need to carry out distributed key generation protocol +//! (see [keygen module]). After DKG is successfully completed, it outputs +//! [LocalKey] — a party local secret share. Then you fix a set of parties who +//! will participate in threshold signing, and they run [OfflineStage] protocol. +//! `OfflineStage` implements [StateMachine] and can be executed in the same way +//! as [Keygen]. `OfflineStage` outputs a [CompletedOfflineStage]. [SignManual] +//! takes a `CompletedOfflineStage` and allows you to perform one-round signing. +//! It doesn't implement `StateMachine`, but rather provides methods to +//! construct messages and final signature manually (refer to [SignManual] +//! documentation to see how to use it). +//! +//! [keygen module]: super::keygen +//! [Keygen]: super::keygen::Keygen +//! [LocalKey]: super::keygen::LocalKey +//! [StateMachine]: round_based::StateMachine + +use std::{convert::TryFrom, mem::replace, time::Duration}; + +use round_based::{ + containers::{ + push::Push, BroadcastMsgs, MessageStore, P2PMsgs, Store, StoreErr, + }, + IsCritical, Msg, StateMachine, +}; +use serde::{Deserialize, Serialize}; +use thiserror::Error; + +use crate::utilities::mta::MessageA; + +use crate::protocols::multi_party_ecdsa::gg_2020 as gg20; +use curv::elliptic::curves::secp256_k1::Secp256k1; +use gg20::{ + party_i::{SignBroadcastPhase1, SignDecommitPhase1, SignatureRecid}, + state_machine::keygen::LocalKey, +}; + +mod fmt; +pub mod rounds; + +use crate::utilities::zk_pdl_with_slack::PDLwSlackProof; +use curv::BigInt; +use rounds::*; +pub use rounds::{ + CompletedOfflineStage, Error as ProceedError, PartialSignature, +}; + +/// Offline Stage of GG20 signing +/// +/// Successfully carried out Offline Stage will produce [CompletedOfflineStage] +/// that can be used for one-round signing multiple times. +pub struct OfflineStage { + round: OfflineR, + + msgs1: Option>>, + msgs2: Option>>, + msgs3: Option>>, + msgs4: Option>>, + msgs5: Option)>>>, + msgs6: Option>>, + + msgs_queue: MsgQueue, + + party_i: u16, + party_n: u16, +} + +impl OfflineStage { + /// Construct a party of offline stage of threshold signing protocol + /// + /// Once offline stage is finished, parties can do one-round threshold + /// signing (i.e. they only need to exchange a single set of messages). + /// + /// Takes party index `i` (in range `[1; n]`), list `s_l` of parties' + /// indexes from keygen protocol (`s_l[i]` must be an index of party `i` + /// that was used by this party in keygen protocol), and party local + /// secret share `local_key`. + /// + /// Returns error if given arguments are contradicting. + pub fn new( + i: u16, + s_l: Vec, + local_key: LocalKey, + ) -> Result { + if s_l.len() < 2 { + return Err(Error::TooFewParties); + } + if i == 0 || usize::from(i) > s_l.len() { + return Err(Error::InvalidPartyIndex); + } + + let keygen_n = local_key.n; + if s_l.iter().any(|&i| i == 0 || i > keygen_n) { + return Err(Error::InvalidSl); + } + { + // Check if s_l has duplicates + let mut s_l_sorted = s_l.clone(); + s_l_sorted.sort_unstable(); + let mut s_l_sorted_deduped = s_l_sorted.clone(); + s_l_sorted_deduped.dedup(); + + if s_l_sorted != s_l_sorted_deduped { + return Err(Error::InvalidSl); + } + } + + let n = u16::try_from(s_l.len()) + .map_err(|_| Error::TooManyParties { n: s_l.len() })?; + + Ok(Self { + round: OfflineR::R0(Round0 { i, s_l, local_key }), + + msgs1: Some(Round1::expects_messages(i, n)), + msgs2: Some(Round2::expects_messages(i, n)), + msgs3: Some(Round3::expects_messages(i, n)), + msgs4: Some(Round4::expects_messages(i, n)), + msgs5: Some(Round5::expects_messages(i, n)), + msgs6: Some(Round6::expects_messages(i, n)), + + msgs_queue: MsgQueue(vec![]), + + party_i: i, + party_n: n, + }) + } + + // fn proceed_state(&mut self, may_block: bool) -> Result<()> { + // self.proceed_round(may_block)?; + // self.proceed_decommit_round(may_block) + // } + + fn proceed_round(&mut self, may_block: bool) -> Result<()> { + let store1_wants_more = + self.msgs1.as_ref().map(|s| s.wants_more()).unwrap_or(false); + let store2_wants_more = + self.msgs2.as_ref().map(|s| s.wants_more()).unwrap_or(false); + let store3_wants_more = + self.msgs3.as_ref().map(|s| s.wants_more()).unwrap_or(false); + let store4_wants_more = + self.msgs4.as_ref().map(|s| s.wants_more()).unwrap_or(false); + let store5_wants_more = + self.msgs5.as_ref().map(|s| s.wants_more()).unwrap_or(false); + let store6_wants_more = + self.msgs6.as_ref().map(|s| s.wants_more()).unwrap_or(false); + + let next_state: OfflineR; + let try_again: bool = match replace(&mut self.round, OfflineR::Gone) { + OfflineR::R0(round) if !round.is_expensive() || may_block => { + next_state = round + .proceed(&mut self.msgs_queue) + .map(OfflineR::R1) + .map_err(Error::ProceedRound)?; + true + } + s @ OfflineR::R0(_) => { + next_state = s; + false + } + OfflineR::R1(round) + if !store1_wants_more + && (!round.is_expensive() || may_block) => + { + let store = + self.msgs1.take().ok_or(InternalError::StoreGone)?; + let msgs = store + .finish() + .map_err(InternalError::RetrieveMessagesFromStore)?; + next_state = round + .proceed(msgs, &mut self.msgs_queue) + .map(OfflineR::R2) + .map_err(Error::ProceedRound)?; + true + } + s @ OfflineR::R1(_) => { + next_state = s; + false + } + OfflineR::R2(round) + if !store2_wants_more + && (!round.is_expensive() || may_block) => + { + let store = + self.msgs2.take().ok_or(InternalError::StoreGone)?; + let msgs = store + .finish() + .map_err(InternalError::RetrieveMessagesFromStore)?; + next_state = round + .proceed(msgs, &mut self.msgs_queue) + .map(OfflineR::R3) + .map_err(Error::ProceedRound)?; + true + } + s @ OfflineR::R2(_) => { + next_state = s; + false + } + OfflineR::R3(round) + if !store3_wants_more + && (!round.is_expensive() || may_block) => + { + let store = + self.msgs3.take().ok_or(InternalError::StoreGone)?; + let msgs = store + .finish() + .map_err(InternalError::RetrieveMessagesFromStore)?; + next_state = round + .proceed(msgs, &mut self.msgs_queue) + .map(OfflineR::R4) + .map_err(Error::ProceedRound)?; + true + } + s @ OfflineR::R3(_) => { + next_state = s; + false + } + OfflineR::R4(round) + if !store4_wants_more + && (!round.is_expensive() || may_block) => + { + let store = + self.msgs4.take().ok_or(InternalError::StoreGone)?; + let msgs = store + .finish() + .map_err(InternalError::RetrieveMessagesFromStore)?; + next_state = round + .proceed(msgs, &mut self.msgs_queue) + .map(OfflineR::R5) + .map_err(Error::ProceedRound)?; + false + } + s @ OfflineR::R4(_) => { + next_state = s; + false + } + OfflineR::R5(round) + if !store5_wants_more + && (!round.is_expensive() || may_block) => + { + let store = + self.msgs5.take().ok_or(InternalError::StoreGone)?; + let msgs = store + .finish() + .map_err(InternalError::RetrieveMessagesFromStore)?; + next_state = round + .proceed(msgs, &mut self.msgs_queue) + .map(OfflineR::R6) + .map_err(Error::ProceedRound)?; + false + } + s @ OfflineR::R5(_) => { + next_state = s; + false + } + OfflineR::R6(round) + if !store6_wants_more + && (!round.is_expensive() || may_block) => + { + let store = + self.msgs6.take().ok_or(InternalError::StoreGone)?; + let msgs = store + .finish() + .map_err(InternalError::RetrieveMessagesFromStore)?; + next_state = round + .proceed(msgs) + .map(OfflineR::Finished) + .map_err(Error::ProceedRound)?; + false + } + s @ OfflineR::R6(_) => { + next_state = s; + false + } + s @ OfflineR::Finished(_) | s @ OfflineR::Gone => { + next_state = s; + false + } + }; + + self.round = next_state; + if try_again { + self.proceed_round(may_block) + } else { + Ok(()) + } + } +} + +impl StateMachine for OfflineStage { + type MessageBody = OfflineProtocolMessage; + type Err = Error; + type Output = CompletedOfflineStage; + + fn handle_incoming( + &mut self, + msg: Msg, + ) -> Result<(), Self::Err> { + let current_round = self.current_round(); + + match msg.body { + OfflineProtocolMessage(OfflineM::M1(m)) => { + let store = self.msgs1.as_mut().ok_or( + Error::ReceivedOutOfOrderMessage { + current_round, + msg_round: 1, + }, + )?; + store + .push_msg(Msg { + sender: msg.sender, + receiver: msg.receiver, + body: m, + }) + .map_err(Error::HandleMessage)?; + } + OfflineProtocolMessage(OfflineM::M2(m)) => { + let store = self.msgs2.as_mut().ok_or( + Error::ReceivedOutOfOrderMessage { + current_round, + msg_round: 2, + }, + )?; + store + .push_msg(Msg { + sender: msg.sender, + receiver: msg.receiver, + body: m, + }) + .map_err(Error::HandleMessage)?; + } + OfflineProtocolMessage(OfflineM::M3(m)) => { + let store = self.msgs3.as_mut().ok_or( + Error::ReceivedOutOfOrderMessage { + current_round, + msg_round: 2, + }, + )?; + store + .push_msg(Msg { + sender: msg.sender, + receiver: msg.receiver, + body: m, + }) + .map_err(Error::HandleMessage)?; + } + OfflineProtocolMessage(OfflineM::M4(m)) => { + let store = self.msgs4.as_mut().ok_or( + Error::ReceivedOutOfOrderMessage { + current_round, + msg_round: 2, + }, + )?; + store + .push_msg(Msg { + sender: msg.sender, + receiver: msg.receiver, + body: m, + }) + .map_err(Error::HandleMessage)?; + } + OfflineProtocolMessage(OfflineM::M5(m)) => { + let store = self.msgs5.as_mut().ok_or( + Error::ReceivedOutOfOrderMessage { + current_round, + msg_round: 2, + }, + )?; + store + .push_msg(Msg { + sender: msg.sender, + receiver: msg.receiver, + body: m, + }) + .map_err(Error::HandleMessage)?; + } + OfflineProtocolMessage(OfflineM::M6(m)) => { + let store = self.msgs6.as_mut().ok_or( + Error::ReceivedOutOfOrderMessage { + current_round, + msg_round: 2, + }, + )?; + store + .push_msg(Msg { + sender: msg.sender, + receiver: msg.receiver, + body: m, + }) + .map_err(Error::HandleMessage)?; + } + } + self.proceed_round(false) + } + + fn message_queue(&mut self) -> &mut Vec> { + &mut self.msgs_queue.0 + } + + fn wants_to_proceed(&self) -> bool { + let store1_wants_more = + self.msgs1.as_ref().map(|s| s.wants_more()).unwrap_or(false); + let store2_wants_more = + self.msgs2.as_ref().map(|s| s.wants_more()).unwrap_or(false); + let store3_wants_more = + self.msgs3.as_ref().map(|s| s.wants_more()).unwrap_or(false); + let store4_wants_more = + self.msgs4.as_ref().map(|s| s.wants_more()).unwrap_or(false); + let store5_wants_more = + self.msgs5.as_ref().map(|s| s.wants_more()).unwrap_or(false); + let store6_wants_more = + self.msgs6.as_ref().map(|s| s.wants_more()).unwrap_or(false); + + match &self.round { + OfflineR::R0(_) => true, + OfflineR::R1(_) => !store1_wants_more, + OfflineR::R2(_) => !store2_wants_more, + OfflineR::R3(_) => !store3_wants_more, + OfflineR::R4(_) => !store4_wants_more, + OfflineR::R5(_) => !store5_wants_more, + OfflineR::R6(_) => !store6_wants_more, + OfflineR::Finished(_) | OfflineR::Gone => false, + } + } + + fn proceed(&mut self) -> Result<(), Self::Err> { + self.proceed_round(true) + } + + fn round_timeout(&self) -> Option { + None + } + + fn round_timeout_reached(&mut self) -> Self::Err { + panic!("no timeout was set") + } + + fn is_finished(&self) -> bool { + matches!(&self.round, OfflineR::Finished(_)) + } + + fn pick_output(&mut self) -> Option> { + match self.round { + OfflineR::Finished(_) => (), + OfflineR::Gone => return Some(Err(Error::DoublePickOutput)), + _ => return None, + } + + match replace(&mut self.round, OfflineR::Gone) { + OfflineR::Finished(result) => Some(Ok(result)), + _ => unreachable!("guaranteed by match expression above"), + } + } + + fn current_round(&self) -> u16 { + match &self.round { + OfflineR::R0(_) => 0, + OfflineR::R1(_) => 1, + OfflineR::R2(_) => 2, + OfflineR::R3(_) => 3, + OfflineR::R4(_) => 4, + OfflineR::R5(_) => 5, + OfflineR::R6(_) => 6, + OfflineR::Finished(_) | OfflineR::Gone => 7, + } + } + + fn total_rounds(&self) -> Option { + Some(6) + } + + fn party_ind(&self) -> u16 { + self.party_i + } + + fn parties(&self) -> u16 { + self.party_n + } +} + +impl super::traits::RoundBlame for OfflineStage { + /// RoundBlame returns number of unwilling parties and a vector of their + /// party indexes. + fn round_blame(&self) -> (u16, Vec) { + let store1_blame = + self.msgs1.as_ref().map(|s| s.blame()).unwrap_or_default(); + let store2_blame = + self.msgs2.as_ref().map(|s| s.blame()).unwrap_or_default(); + let store3_blame = + self.msgs3.as_ref().map(|s| s.blame()).unwrap_or_default(); + let store4_blame = + self.msgs4.as_ref().map(|s| s.blame()).unwrap_or_default(); + let store5_blame = + self.msgs5.as_ref().map(|s| s.blame()).unwrap_or_default(); + let store6_blame = + self.msgs6.as_ref().map(|s| s.blame()).unwrap_or_default(); + + let default = (0, vec![]); + match &self.round { + OfflineR::R0(_) => default, + OfflineR::R1(_) => store1_blame, + OfflineR::R2(_) => store2_blame, + OfflineR::R3(_) => store3_blame, + OfflineR::R4(_) => store4_blame, + OfflineR::R5(_) => store5_blame, + OfflineR::R6(_) => store6_blame, + OfflineR::Finished(_) => store6_blame, + OfflineR::Gone => default, + } + } +} + +#[allow(clippy::large_enum_variant)] +enum OfflineR { + R0(Round0), + R1(Round1), + R2(Round2), + R3(Round3), + R4(Round4), + R5(Round5), + R6(Round6), + Finished(CompletedOfflineStage), + Gone, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct OfflineProtocolMessage(OfflineM); + +impl crate::MessageRoundID for OfflineProtocolMessage { + fn round_id(&self) -> u16 { + match &self.0 { + OfflineM::M1(_) => 1, + OfflineM::M2(_) => 2, + OfflineM::M3(_) => 3, + OfflineM::M4(_) => 4, + OfflineM::M5(_) => 5, + OfflineM::M6(_) => 6, + } + } +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +#[allow(clippy::large_enum_variant)] +enum OfflineM { + M1((MessageA, SignBroadcastPhase1)), + M2((GammaI, WI)), + M3((DeltaI, TI, TIProof)), + M4(SignDecommitPhase1), + M5((RDash, Vec)), + M6((SI, HEGProof)), +} + +struct MsgQueue(Vec>); + +macro_rules! make_pushable { + ($($constructor:ident $t:ty),*$(,)?) => { + $( + impl Push> for MsgQueue { + fn push(&mut self, m: Msg<$t>) { + Vec::push(&mut self.0, Msg{ + sender: m.sender, + receiver: m.receiver, + body: OfflineProtocolMessage(OfflineM::$constructor(m.body)) + }) + } + } + )* + }; +} + +make_pushable! { + M1 (MessageA, SignBroadcastPhase1), + M2 (GammaI, WI), + M3 (DeltaI, TI, TIProof), + M4 SignDecommitPhase1, + M5 (RDash, Vec), + M6 (SI, HEGProof), +} + +type Result = std::result::Result; + +#[derive(Debug, Error)] +pub enum Error { + /// Too few parties (`n < 2`) + #[error("at least 2 parties are required for signing")] + TooFewParties, + /// Too many parties. `n` must fit into `u16`, so only `n < u16::MAX` + /// values are supported. + #[error("too many parties: n={n}, n must be less than 2^16")] + TooManyParties { n: usize }, + /// Party index `i` is not in range `[1; n]` + #[error("party index is not in range [1; n]")] + InvalidPartyIndex, + /// List `s_l` is invalid. Either it contains duplicates (`exist i j. i != + /// j && s_l[i] = s_l[j]`), or contains index that is not in the range + /// `[1; keygen_n]`, `keygen_n` — number of parties participated in DKG + /// (`exist i. s_l[i] = 0 || s_l[i] > keygen_n`). + #[error("invalid s_l")] + InvalidSl, + + /// Round proceeding resulted in protocol error + #[error("proceeding round: {0}")] + ProceedRound(rounds::Error), + + /// Received message which we didn't expect to receive now (e.g. message + /// from previous round) + #[error( + "didn't expect to receive message from round {msg_round} (being at round {current_round})" + )] + ReceivedOutOfOrderMessage { current_round: u16, msg_round: u16 }, + /// Received message didn't pass pre-validation + #[error("received message didn't pass pre-validation: {0}")] + HandleMessage(#[source] StoreErr), + + /// [OfflineStage::pick_output] called twice + #[error("pick_output called twice")] + DoublePickOutput, + + /// A bug in protocol implementation + #[error("offline stage protocol bug: {0}")] + Bug(InternalError), +} + +#[derive(Debug, Error)] +pub enum InternalError { + #[error("store gone")] + StoreGone, + #[error("store reported that it's collected all the messages it needed, but refused to give received messages")] + RetrieveMessagesFromStore(StoreErr), + #[error("decommit round expected to be in NotStarted state")] + DecommitRoundWasntInInitialState, +} + +impl From for Error { + fn from(err: InternalError) -> Self { + Error::Bug(err) + } +} + +impl IsCritical for Error { + fn is_critical(&self) -> bool { + match self { + Error::TooFewParties => true, + Error::TooManyParties { .. } => true, + Error::InvalidPartyIndex => true, + Error::InvalidSl => true, + Error::ProceedRound(_) => true, + Error::ReceivedOutOfOrderMessage { .. } => false, + Error::HandleMessage(_) => false, + Error::DoublePickOutput => true, + Error::Bug(_) => true, + } + } +} + +/// Manual GG20 signing +/// +/// After you completed [OfflineStage] and got [CompletedOfflineStage], parties +/// can perform signing simply by broadcasting a single message. +/// +/// ## Example +/// ```no_run +/// # use multi_party_ecdsa::protocols::multi_party_ecdsa::gg_2020::{ +/// # state_machine::sign::{CompletedOfflineStage, SignManual, PartialSignature}, +/// # party_i::{LocalSignature, verify}, +/// # }; +/// # use curv::arithmetic::{BigInt, Converter}; +/// # type Result = std::result::Result>; +/// # fn broadcast(msg: PartialSignature) -> Result<()> { panic!() } +/// # fn wait_messages() -> Result> { panic!() } +/// # fn main() -> Result<()> { +/// # let completed_offline_stage: CompletedOfflineStage = panic!(); +/// let data = BigInt::from_bytes(b"a message"); +/// +/// // Sign a message locally +/// let (sign, msg) = SignManual::new(data.clone(), completed_offline_stage)?; +/// // Broadcast local partial signature +/// broadcast(msg)?; +/// // Collect partial signatures from other parties +/// let sigs: Vec = wait_messages()?; +/// // Complete signing +/// let signature = sign.complete(&sigs)?; +/// // Verify that signature matches joint public key +/// assert!(verify(&signature, completed_offline_stage.public_key(), &data).is_ok()); +/// # Ok(()) +/// # } +/// ``` +#[derive(Clone)] +pub struct SignManual { + state: Round7, +} + +impl SignManual { + pub fn new( + message: BigInt, + completed_offline_stage: CompletedOfflineStage, + ) -> Result<(Self, PartialSignature), SignError> { + Round7::new(&message, completed_offline_stage) + .map(|(state, m)| (Self { state }, m)) + .map_err(SignError::LocalSigning) + } + + /// `sigs` must not include partial signature produced by local party (only + /// partial signatures produced by other parties) + pub fn complete( + self, + sigs: &[PartialSignature], + ) -> Result { + self.state + .proceed_manual(sigs) + .map_err(SignError::CompleteSigning) + } +} + +#[derive(Debug, Error)] +pub enum SignError { + #[error("signing message locally: {0}")] + LocalSigning(rounds::Error), + #[error("couldn't complete signing: {0}")] + CompleteSigning(rounds::Error), +} + +#[cfg(test)] +mod test { + use curv::{ + arithmetic::Converter, + cryptographic_primitives::hashing::{Digest, DigestExt}, + }; + use round_based::dev::Simulation; + use sha2::Sha256; + + use super::*; + use gg20::{party_i::verify, state_machine::keygen::test::simulate_keygen}; + + fn simulate_offline_stage( + local_keys: Vec>, + s_l: &[u16], + ) -> Vec { + let mut simulation = Simulation::new(); + simulation.enable_benchmarks(true); + + for (i, &keygen_i) in (1..).zip(s_l) { + simulation.add_party( + OfflineStage::new( + i, + s_l.to_vec(), + local_keys[usize::from(keygen_i - 1)].clone(), + ) + .unwrap(), + ); + } + + let stages = simulation.run().unwrap(); + + println!("Benchmark results:"); + println!("{:#?}", simulation.benchmark_results().unwrap()); + + stages + } + + fn simulate_signing(offline: Vec, message: &[u8]) { + let message = Sha256::new() + .chain_bigint(&BigInt::from_bytes(message)) + .result_bigint(); + let pk = offline[0].public_key().clone(); + + let parties = offline + .iter() + .map(|o| SignManual::new(message.clone(), o.clone())) + .collect::, _>>() + .unwrap(); + let (parties, local_sigs): (Vec<_>, Vec<_>) = + parties.into_iter().unzip(); + // parties.remove(0).complete(&local_sigs[1..]).unwrap(); + let local_sigs_except = |i: usize| { + let mut v = vec![]; + v.extend_from_slice(&local_sigs[..i]); + if i + 1 < local_sigs.len() { + v.extend_from_slice(&local_sigs[i + 1..]); + } + v + }; + + assert!(parties + .into_iter() + .enumerate() + .map(|(i, p)| p.complete(&local_sigs_except(i)).unwrap()) + .all(|signature| verify(&signature, &pk, &message).is_ok())); + } + + #[test] + fn simulate_offline_stage_t1_n2_s2() { + let local_keys = simulate_keygen(1, 2); + simulate_offline_stage(local_keys, &[1, 2]); + } + + #[test] + fn simulate_offline_stage_t1_n3_s2() { + let local_keys = simulate_keygen(1, 3); + simulate_offline_stage(local_keys, &[1, 3]); + } + + #[test] + fn simulate_offline_stage_t2_n3_s3() { + let local_keys = simulate_keygen(2, 3); + simulate_offline_stage(local_keys, &[1, 2, 3]); + } + + #[test] + fn simulate_signing_t1_n2_s2() { + let local_keys = simulate_keygen(1, 2); + let offline_stage = simulate_offline_stage(local_keys, &[1, 2]); + simulate_signing(offline_stage, b"ZenGo") + } + + #[test] + fn simulate_signing_t1_n3_s2() { + let local_keys = simulate_keygen(1, 3); + let offline_stage = simulate_offline_stage(local_keys.clone(), &[1, 2]); + simulate_signing(offline_stage, b"ZenGo"); + let offline_stage = simulate_offline_stage(local_keys.clone(), &[1, 3]); + simulate_signing(offline_stage, b"ZenGo"); + let offline_stage = simulate_offline_stage(local_keys, &[2, 3]); + simulate_signing(offline_stage, b"ZenGo"); + } + + #[test] + fn simulate_signing_t2_n3_s3() { + let local_keys = simulate_keygen(2, 3); + let offline_stage = simulate_offline_stage(local_keys, &[1, 2, 3]); + simulate_signing(offline_stage, b"ZenGo") + } +} diff --git a/multi-party-ecdsa/src/gg_2020/state_machine/sign/fmt.rs b/multi-party-ecdsa/src/gg_2020/state_machine/sign/fmt.rs new file mode 100644 index 00000000..c8bf31db --- /dev/null +++ b/multi-party-ecdsa/src/gg_2020/state_machine/sign/fmt.rs @@ -0,0 +1,127 @@ +use std::fmt; + +use round_based::containers::{BroadcastMsgsStore, MessageStore, P2PMsgsStore}; + +impl fmt::Debug for super::OfflineStage { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + OfflineStageProgress::from(self).fmt(f) + } +} + +#[derive(Debug)] +#[allow(dead_code)] +pub struct OfflineStageProgress { + round: OfflineR, + + round1_msgs: ReceivedMessages, + round2_msgs: ReceivedMessages, + round3_msgs: ReceivedMessages, + round4_msgs: ReceivedMessages, + round5_msgs: ReceivedMessages, + + msgs_queue: OutgoingMessages, +} + +impl From<&super::OfflineStage> for OfflineStageProgress { + fn from(state: &super::OfflineStage) -> Self { + Self { + round: match &state.round { + super::OfflineR::R0(_) => OfflineR::R0, + super::OfflineR::R1(_) => OfflineR::R1, + super::OfflineR::R2(_) => OfflineR::R2, + super::OfflineR::R3(_) => OfflineR::R3, + super::OfflineR::R4(_) => OfflineR::R4, + super::OfflineR::R5(_) => OfflineR::R5, + super::OfflineR::R6(_) => OfflineR::R6, + super::OfflineR::Finished(_) => OfflineR::Finished, + super::OfflineR::Gone => OfflineR::Gone, + }, + + round1_msgs: ReceivedMessages::from_broadcast(state.msgs1.as_ref()), + round2_msgs: ReceivedMessages::from_p2p(state.msgs2.as_ref()), + round3_msgs: ReceivedMessages::from_broadcast(state.msgs3.as_ref()), + round4_msgs: ReceivedMessages::from_broadcast(state.msgs4.as_ref()), + round5_msgs: ReceivedMessages::from_broadcast(state.msgs5.as_ref()), + + msgs_queue: OutgoingMessages { + len: state.msgs_queue.0.len(), + }, + } + } +} + +#[derive(Debug)] +pub enum OfflineR { + R0, + R1, + R2, + R3, + R4, + R5, + R6, + Finished, + Gone, +} + +pub enum ContainerType { + P2P, + Broadcast, +} + +pub struct ReceivedMessages(Option); + +pub struct MessagesContainer { + ty: ContainerType, + total: usize, + waiting_for: Vec, +} + +impl ReceivedMessages { + fn from_broadcast(store: Option<&BroadcastMsgsStore>) -> Self { + match store { + Some(store) => ReceivedMessages(Some(MessagesContainer { + ty: ContainerType::Broadcast, + total: store.messages_total(), + waiting_for: store.blame().1, + })), + None => ReceivedMessages(None), + } + } + fn from_p2p(store: Option<&P2PMsgsStore>) -> Self { + match store { + Some(store) => ReceivedMessages(Some(MessagesContainer { + ty: ContainerType::P2P, + total: store.messages_total(), + waiting_for: store.blame().1, + })), + None => ReceivedMessages(None), + } + } +} + +impl fmt::Debug for ReceivedMessages { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match &self.0 { + Some(container) => { + let ty = match container.ty { + ContainerType::Broadcast => "bc", + ContainerType::P2P => "p2p", + }; + write!( + f, + "[{} {}/{}]", + ty, + container.total - container.waiting_for.len(), + container.total + ) + } + None => write!(f, "[gone]"), + } + } +} + +#[derive(Debug)] +#[allow(dead_code)] +pub struct OutgoingMessages { + len: usize, +} diff --git a/multi-party-ecdsa/src/gg_2020/state_machine/sign/rounds.rs b/multi-party-ecdsa/src/gg_2020/state_machine/sign/rounds.rs new file mode 100644 index 00000000..fd66bd79 --- /dev/null +++ b/multi-party-ecdsa/src/gg_2020/state_machine/sign/rounds.rs @@ -0,0 +1,794 @@ +#![allow(non_snake_case)] + +use std::{convert::TryFrom, iter}; + +use serde::{Deserialize, Serialize}; +use thiserror::Error; + +use curv::{ + elliptic::curves::{secp256_k1::Secp256k1, Point, Scalar}, + BigInt, +}; +use sha2::Sha256; + +use round_based::{ + containers::{self, push::Push, BroadcastMsgs, P2PMsgs, Store}, + Msg, +}; + +use crate::utilities::mta::{MessageA, MessageB}; + +use crate::{ + protocols::multi_party_ecdsa::gg_2020 as gg20, + utilities::zk_pdl_with_slack::PDLwSlackProof, +}; +use curv::cryptographic_primitives::proofs::{ + sigma_correct_homomorphic_elgamal_enc::HomoELGamalProof, + sigma_valid_pedersen::PedersenProof, +}; +use gg20::{ + party_i::{ + LocalSignature, SignBroadcastPhase1, SignDecommitPhase1, SignKeys, + SignatureRecid, + }, + state_machine::keygen::LocalKey, + ErrorType, +}; + +type Result = std::result::Result; + +#[derive(Serialize, Deserialize, Debug, Clone)] +#[allow(clippy::upper_case_acronyms)] +pub struct GWI(pub Point); +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct GammaI(pub MessageB); +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct WI(pub MessageB); +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct DeltaI(Scalar); +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct TI(pub Point); +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct TIProof(pub PedersenProof); +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct RDash(Point); +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct SI(pub Point); +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct HEGProof(pub HomoELGamalProof); + +pub struct Round0 { + /// Index of this party + /// + /// Must be in range `[0; n)` where `n` is number of parties involved in + /// signing. + pub i: u16, + + /// List of parties' indexes from keygen protocol + /// + /// I.e. `s_l[i]` must be an index of party `i` that was used by this party + /// in keygen protocol. + // s_l.len()` equals to `n` (number of parties involved in signing) + pub s_l: Vec, + + /// Party local secret share + pub local_key: LocalKey, +} + +impl Round0 { + pub fn proceed(self, mut output: O) -> Result + where + O: Push>, + { + let sign_keys = SignKeys::create( + &self.local_key.keys_linear.x_i, + &self.local_key.vss_scheme.clone(), + usize::from(self.s_l[usize::from(self.i - 1)]) - 1, + &self + .s_l + .iter() + .map(|&i| usize::from(i) - 1) + .collect::>(), + ); + let (bc1, decom1) = sign_keys.phase1_broadcast(); + + let party_ek = self.local_key.paillier_key_vec + [usize::from(self.local_key.i - 1)] + .clone(); + let m_a = MessageA::a( + &sign_keys.k_i, + &party_ek, + &self.local_key.h1_h2_n_tilde_vec, + ); + + output.push(Msg { + sender: self.i, + receiver: None, + body: (m_a.0.clone(), bc1.clone()), + }); + + let round1 = Round1 { + i: self.i, + s_l: self.s_l.clone(), + local_key: self.local_key, + m_a, + sign_keys, + phase1_com: bc1, + phase1_decom: decom1, + }; + + Ok(round1) + } + + pub fn is_expensive(&self) -> bool { + true + } +} + +pub struct Round1 { + i: u16, + s_l: Vec, + local_key: LocalKey, + m_a: (MessageA, BigInt), + sign_keys: SignKeys, + phase1_com: SignBroadcastPhase1, + phase1_decom: SignDecommitPhase1, +} + +impl Round1 { + pub fn proceed( + self, + input: BroadcastMsgs<(MessageA, SignBroadcastPhase1)>, + mut output: O, + ) -> Result + where + O: Push>, + { + let (m_a_vec, bc_vec): (Vec<_>, Vec<_>) = input + .into_vec_including_me(( + self.m_a.0.clone(), + self.phase1_com.clone(), + )) + .into_iter() + .unzip(); + + let mut m_b_gamma_vec = Vec::new(); + let mut beta_vec = Vec::new(); + let mut m_b_w_vec = Vec::new(); + let mut ni_vec = Vec::new(); + + let ttag = self.s_l.len(); + let l_s: Vec<_> = self + .s_l + .iter() + .cloned() + .map(|i| usize::from(i) - 1) + .collect(); + let i = usize::from(self.i - 1); + for j in 0..ttag - 1 { + let ind = if j < i { j } else { j + 1 }; + + let (m_b_gamma, beta_gamma, _beta_randomness, _beta_tag) = + MessageB::b( + &self.sign_keys.gamma_i, + &self.local_key.paillier_key_vec[l_s[ind]], + m_a_vec[ind].clone(), + &self.local_key.h1_h2_n_tilde_vec, + ) + .map_err(|e| { + Error::Round1(ErrorType { + error_type: e.to_string(), + bad_actors: vec![ind], + data: vec![], + }) + })?; + + let (m_b_w, beta_wi, _, _) = MessageB::b( + &self.sign_keys.w_i, + &self.local_key.paillier_key_vec[l_s[ind]], + m_a_vec[ind].clone(), + &self.local_key.h1_h2_n_tilde_vec, + ) + .map_err(|e| { + Error::Round1(ErrorType { + error_type: e.to_string(), + bad_actors: vec![ind], + data: vec![], + }) + })?; + + m_b_gamma_vec.push(m_b_gamma); + beta_vec.push(beta_gamma); + m_b_w_vec.push(m_b_w); + ni_vec.push(beta_wi); + } + + let party_indices = (1..=self.s_l.len()) + .map(|j| u16::try_from(j).unwrap()) + .filter(|&j| j != self.i); + for ((j, gamma_i), w_i) in + party_indices.zip(m_b_gamma_vec).zip(m_b_w_vec) + { + output.push(Msg { + sender: self.i, + receiver: Some(j), + body: (GammaI(gamma_i.clone()), WI(w_i.clone())), + }); + } + + Ok(Round2 { + i: self.i, + s_l: self.s_l, + local_key: self.local_key, + sign_keys: self.sign_keys, + m_a: self.m_a, + beta_vec, + ni_vec, + bc_vec, + m_a_vec, + phase1_decom: self.phase1_decom, + }) + } + + pub fn expects_messages( + i: u16, + n: u16, + ) -> Store> { + containers::BroadcastMsgsStore::new(i, n) + } + + pub fn is_expensive(&self) -> bool { + true + } +} + +pub struct Round2 { + i: u16, + s_l: Vec, + local_key: LocalKey, + sign_keys: SignKeys, + m_a: (MessageA, BigInt), + beta_vec: Vec>, + ni_vec: Vec>, + bc_vec: Vec, + m_a_vec: Vec, + phase1_decom: SignDecommitPhase1, +} + +impl Round2 { + pub fn proceed( + self, + input_p2p: P2PMsgs<(GammaI, WI)>, + mut output: O, + ) -> Result + where + O: Push>, // TODO: unify TI and TIProof + { + let (m_b_gamma_s, m_b_w_s): (Vec<_>, Vec<_>) = input_p2p + .into_vec() + .into_iter() + .map(|(gamma_i, w_i)| (gamma_i.0, w_i.0)) + .unzip(); + + let mut alpha_vec = Vec::new(); + let mut miu_vec = Vec::new(); + + let ttag = self.s_l.len(); + let index = usize::from(self.i) - 1; + let l_s: Vec<_> = self + .s_l + .iter() + .cloned() + .map(|i| usize::from(i) - 1) + .collect(); + let g_w_vec = SignKeys::g_w_vec( + &self.local_key.pk_vec[..], + &l_s[..], + &self.local_key.vss_scheme, + ); + for j in 0..ttag - 1 { + let ind = if j < index { j } else { j + 1 }; + let m_b = m_b_gamma_s[j].clone(); + + let alpha_ij_gamma = m_b + .verify_proofs_get_alpha( + &self.local_key.paillier_dk, + &self.sign_keys.k_i, + ) + .map_err(|e| { + Error::Round3(ErrorType { + error_type: e.to_string(), + bad_actors: vec![ind], + data: vec![], + }) + })?; + let m_b = m_b_w_s[j].clone(); + let alpha_ij_wi = m_b + .verify_proofs_get_alpha( + &self.local_key.paillier_dk, + &self.sign_keys.k_i, + ) + .map_err(|e| { + Error::Round3(ErrorType { + error_type: e.to_string(), + bad_actors: vec![ind], + data: vec![], + }) + })?; + assert_eq!(m_b.b_proof.pk, g_w_vec[ind]); //TODO: return error + + alpha_vec.push(alpha_ij_gamma.0); + miu_vec.push(alpha_ij_wi.0); + } + + let delta_i = self.sign_keys.phase2_delta_i(&alpha_vec, &self.beta_vec); + + let sigma_i = self.sign_keys.phase2_sigma_i(&miu_vec, &self.ni_vec); + let (t_i, l_i, t_i_proof) = SignKeys::phase3_compute_t_i(&sigma_i); + output.push(Msg { + sender: self.i, + receiver: None, + body: ( + DeltaI(delta_i.clone()), + TI(t_i.clone()), + TIProof(t_i_proof.clone()), + ), + }); + + Ok(Round3 { + i: self.i, + s_l: self.s_l, + local_key: self.local_key, + sign_keys: self.sign_keys, + m_a: self.m_a, + mb_gamma_s: m_b_gamma_s, + bc_vec: self.bc_vec, + m_a_vec: self.m_a_vec, + delta_i, + t_i, + l_i, + sigma_i, + t_i_proof, + phase1_decom: self.phase1_decom, + }) + } + + pub fn expects_messages(i: u16, n: u16) -> Store> { + containers::P2PMsgsStore::new(i, n) + } + + pub fn is_expensive(&self) -> bool { + true + } +} + +pub struct Round3 { + i: u16, + s_l: Vec, + local_key: LocalKey, + sign_keys: SignKeys, + m_a: (MessageA, BigInt), + mb_gamma_s: Vec, + bc_vec: Vec, + m_a_vec: Vec, + delta_i: Scalar, + t_i: Point, + l_i: Scalar, + sigma_i: Scalar, + t_i_proof: PedersenProof, + + phase1_decom: SignDecommitPhase1, +} + +impl Round3 { + pub fn proceed( + self, + input: BroadcastMsgs<(DeltaI, TI, TIProof)>, + mut output: O, + ) -> Result + where + O: Push>, + { + let (delta_vec, t_vec, t_proof_vec) = input + .into_vec_including_me(( + DeltaI(self.delta_i), + TI(self.t_i.clone()), + TIProof(self.t_i_proof), + )) + .into_iter() + .map(|(delta_i, t_i, t_i_proof)| (delta_i.0, t_i.0, t_i_proof.0)) + .unzip3(); + + for i in 0..t_vec.len() { + assert_eq!(t_vec[i], t_proof_vec[i].com); + } + + let delta_inv = SignKeys::phase3_reconstruct_delta(&delta_vec); + let ttag = self.s_l.len(); + for (idx, proof) in t_proof_vec.iter().enumerate().take(ttag) { + PedersenProof::verify(proof).map_err(|e| { + Error::Round3(ErrorType { + error_type: e.to_string(), + bad_actors: vec![idx + 1], + data: vec![], + }) + })?; + } + + output.push(Msg { + sender: self.i, + receiver: None, + body: self.phase1_decom.clone(), + }); + + Ok(Round4 { + i: self.i, + s_l: self.s_l, + local_key: self.local_key, + sign_keys: self.sign_keys, + m_a: self.m_a, + mb_gamma_s: self.mb_gamma_s, + bc_vec: self.bc_vec, + m_a_vec: self.m_a_vec, + t_i: self.t_i, + l_i: self.l_i, + sigma_i: self.sigma_i, + phase1_decom: self.phase1_decom, + delta_inv, + t_vec, + }) + } + + pub fn expects_messages( + i: u16, + n: u16, + ) -> Store> { + containers::BroadcastMsgsStore::new(i, n) + } + + pub fn is_expensive(&self) -> bool { + true + } +} + +pub struct Round4 { + i: u16, + s_l: Vec, + local_key: LocalKey, + sign_keys: SignKeys, + m_a: (MessageA, BigInt), + mb_gamma_s: Vec, + bc_vec: Vec, + m_a_vec: Vec, + t_i: Point, + l_i: Scalar, + sigma_i: Scalar, + delta_inv: Scalar, + t_vec: Vec>, + phase1_decom: SignDecommitPhase1, +} + +impl Round4 { + pub fn proceed( + self, + decommit_round1: BroadcastMsgs, + mut output: O, + ) -> Result + where + O: Push)>>, + { + let decom_vec: Vec<_> = + decommit_round1.into_vec_including_me(self.phase1_decom.clone()); + + let ttag = self.s_l.len(); + let b_proof_vec: Vec<_> = + (0..ttag - 1).map(|i| &self.mb_gamma_s[i].b_proof).collect(); + let R = SignKeys::phase4( + &self.delta_inv, + &b_proof_vec[..], + decom_vec, + &self.bc_vec, + usize::from(self.i - 1), + ) + .map_err(Error::Round5)?; + + let R_dash = &R * &self.sign_keys.k_i; + + // each party sends first message to all other parties + let mut phase5_proofs_vec = Vec::new(); + let l_s: Vec<_> = self + .s_l + .iter() + .cloned() + .map(|i| usize::from(i) - 1) + .collect(); + let index = usize::from(self.i - 1); + for j in 0..ttag - 1 { + let ind = if j < index { j } else { j + 1 }; + let proof = LocalSignature::phase5_proof_pdl( + &R_dash, + &R, + &self.m_a.0.c, + &self.local_key.paillier_key_vec[l_s[index]], + &self.sign_keys.k_i, + &self.m_a.1, + &self.local_key.h1_h2_n_tilde_vec[l_s[ind]], + ); + + phase5_proofs_vec.push(proof); + } + + output.push(Msg { + sender: self.i, + receiver: None, + body: (RDash(R_dash.clone()), phase5_proofs_vec.clone()), + }); + + Ok(Round5 { + i: self.i, + s_l: self.s_l, + local_key: self.local_key, + sign_keys: self.sign_keys, + t_vec: self.t_vec, + m_a_vec: self.m_a_vec, + t_i: self.t_i, + l_i: self.l_i, + sigma_i: self.sigma_i, + R, + R_dash, + phase5_proofs_vec, + }) + } + + pub fn expects_messages( + i: u16, + n: u16, + ) -> Store> { + containers::BroadcastMsgsStore::new(i, n) + } + + pub fn is_expensive(&self) -> bool { + true + } +} + +pub struct Round5 { + i: u16, + s_l: Vec, + local_key: LocalKey, + sign_keys: SignKeys, + t_vec: Vec>, + m_a_vec: Vec, + t_i: Point, + l_i: Scalar, + sigma_i: Scalar, + R: Point, + R_dash: Point, + phase5_proofs_vec: Vec, +} + +impl Round5 { + pub fn proceed( + self, + input: BroadcastMsgs<(RDash, Vec)>, + mut output: O, + ) -> Result + where + O: Push>, + { + let (r_dash_vec, pdl_proof_mat_inc_me): (Vec<_>, Vec<_>) = input + .into_vec_including_me((RDash(self.R_dash), self.phase5_proofs_vec)) + .into_iter() + .map(|(r_dash, pdl_proof)| (r_dash.0, pdl_proof)) + .unzip(); + + let l_s: Vec<_> = self + .s_l + .iter() + .cloned() + .map(|i| usize::from(i) - 1) + .collect(); + let ttag = self.s_l.len(); + for i in 0..ttag { + LocalSignature::phase5_verify_pdl( + &pdl_proof_mat_inc_me[i], + &r_dash_vec[i], + &self.R, + &self.m_a_vec[i].c, + &self.local_key.paillier_key_vec[l_s[i]], + &self.local_key.h1_h2_n_tilde_vec, + &l_s, + i, + ) + .map_err(Error::Round5)?; + } + LocalSignature::phase5_check_R_dash_sum(&r_dash_vec).map_err(|e| { + Error::Round5(ErrorType { + error_type: e.to_string(), + bad_actors: vec![], + data: vec![], + }) + })?; + + let (S_i, homo_elgamal_proof) = + LocalSignature::phase6_compute_S_i_and_proof_of_consistency( + &self.R, + &self.t_i, + &self.sigma_i, + &self.l_i, + ); + + output.push(Msg { + sender: self.i, + receiver: None, + body: (SI(S_i.clone()), HEGProof(homo_elgamal_proof.clone())), + }); + + Ok(Round6 { + S_i, + homo_elgamal_proof, + s_l: self.s_l, + protocol_output: CompletedOfflineStage { + i: self.i, + local_key: self.local_key, + sign_keys: self.sign_keys, + t_vec: self.t_vec, + R: self.R, + sigma_i: self.sigma_i, + }, + }) + } + + pub fn expects_messages( + i: u16, + n: u16, + ) -> Store)>> { + containers::BroadcastMsgsStore::new(i, n) + } + + pub fn is_expensive(&self) -> bool { + true + } +} + +pub struct Round6 { + S_i: Point, + homo_elgamal_proof: HomoELGamalProof, + s_l: Vec, + /// Round 6 guards protocol output until final checks are taken the place + protocol_output: CompletedOfflineStage, +} + +impl Round6 { + pub fn proceed( + self, + input: BroadcastMsgs<(SI, HEGProof)>, + ) -> Result { + let (S_i_vec, hegp_vec): (Vec<_>, Vec<_>) = input + .into_vec_including_me(( + SI(self.S_i), + HEGProof(self.homo_elgamal_proof), + )) + .into_iter() + .map(|(s_i, hegp_i)| (s_i.0, hegp_i.0)) + .unzip(); + let R_vec: Vec<_> = iter::repeat(self.protocol_output.R.clone()) + .take(self.s_l.len()) + .collect(); + + LocalSignature::phase6_verify_proof( + &S_i_vec, + &hegp_vec, + &R_vec, + &self.protocol_output.t_vec, + ) + .map_err(Error::Round6VerifyProof)?; + LocalSignature::phase6_check_S_i_sum( + &self.protocol_output.local_key.y_sum_s, + &S_i_vec, + ) + .map_err(Error::Round6CheckSig)?; + + Ok(self.protocol_output) + } + + pub fn expects_messages( + i: u16, + n: u16, + ) -> Store> { + containers::BroadcastMsgsStore::new(i, n) + } + + pub fn is_expensive(&self) -> bool { + true + } +} + +#[derive(Clone)] +#[allow(dead_code)] +pub struct CompletedOfflineStage { + i: u16, + local_key: LocalKey, + sign_keys: SignKeys, + t_vec: Vec>, + R: Point, + sigma_i: Scalar, +} + +impl CompletedOfflineStage { + pub fn public_key(&self) -> &Point { + &self.local_key.y_sum_s + } +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct PartialSignature(Scalar); + +#[derive(Clone)] +pub struct Round7 { + local_signature: LocalSignature, +} + +impl Round7 { + pub fn new( + message: &BigInt, + completed_offline_stage: CompletedOfflineStage, + ) -> Result<(Self, PartialSignature)> { + let local_signature = LocalSignature::phase7_local_sig( + &completed_offline_stage.sign_keys.k_i, + message, + &completed_offline_stage.R, + &completed_offline_stage.sigma_i, + &completed_offline_stage.local_key.y_sum_s, + ); + let partial = PartialSignature(local_signature.s_i.clone()); + Ok((Self { local_signature }, partial)) + } + + pub fn proceed_manual( + self, + sigs: &[PartialSignature], + ) -> Result { + let sigs = sigs.iter().map(|s_i| s_i.0.clone()).collect::>(); + self.local_signature + .output_signature(&sigs) + .map_err(Error::Round7) + } +} + +#[derive(Debug, Error)] +pub enum Error { + #[error("round 1: {0:?}")] + Round1(ErrorType), + #[error("round 2 stage 3: {0:?}")] + Round2Stage3(crate::Error), + #[error("round 2 stage 4: {0:?}")] + Round2Stage4(ErrorType), + #[error("round 3: {0:?}")] + Round3(ErrorType), + #[error("round 5: {0:?}")] + Round5(ErrorType), + #[error("round 6: verify proof: {0:?}")] + Round6VerifyProof(ErrorType), + #[error("round 6: check sig: {0:?}")] + Round6CheckSig(crate::Error), + #[error("round 7: {0:?}")] + Round7(crate::Error), +} + +trait IteratorExt: Iterator { + fn unzip3(self) -> (Vec, Vec, Vec) + where + Self: Iterator + Sized, + { + let (mut a, mut b, mut c) = (vec![], vec![], vec![]); + for (a_i, b_i, c_i) in self { + a.push(a_i); + b.push(b_i); + c.push(c_i); + } + (a, b, c) + } +} + +impl IteratorExt for I where I: Iterator {} diff --git a/multi-party-ecdsa/src/gg_2020/state_machine/traits.rs b/multi-party-ecdsa/src/gg_2020/state_machine/traits.rs new file mode 100644 index 00000000..af24de1c --- /dev/null +++ b/multi-party-ecdsa/src/gg_2020/state_machine/traits.rs @@ -0,0 +1,12 @@ +pub trait RoundBlame { + /// Retrieves a list of uncorporative parties + /// + /// Returns a numbers of messages yet to recieve and list of parties to send + /// messages for the current round + fn round_blame(&self) -> (u16, Vec); +} + +pub trait MessageRoundID: Clone { + /// Returns the round id of the message + fn round_id(&self) -> u16; +} diff --git a/multi-party-ecdsa/src/gg_2020/test.rs b/multi-party-ecdsa/src/gg_2020/test.rs new file mode 100644 index 00000000..76d370ad --- /dev/null +++ b/multi-party-ecdsa/src/gg_2020/test.rs @@ -0,0 +1,854 @@ +#![allow(non_snake_case)] + +use crate::protocols::multi_party_ecdsa::gg_2020::VerifiableSS; +/* + Multi-party ECDSA + + Copyright 2018 by Kzen Networks + + This file is part of Multi-party ECDSA library + (https://github.com/KZen-networks/multi-party-ecdsa) + + Multi-party ECDSA is free software: you can redistribute + it and/or modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, either + version 3 of the License, or (at your option) any later version. + + @license GPL-3.0+ +*/ +use crate::{ + protocols::multi_party_ecdsa::gg_2020::{ + blame::{ + GlobalStatePhase5, GlobalStatePhase6, GlobalStatePhase7, + LocalStatePhase5, LocalStatePhase6, + }, + party_i::{ + KeyGenBroadcastMessage1, KeyGenDecommitMessage1, Keys, + LocalSignature, Parameters, SharedKeys, SignKeys, SignatureRecid, + }, + }, + utilities::mta::{MessageA, MessageB}, +}; +use curv::arithmetic::traits::Converter; + +use crate::{ + protocols::multi_party_ecdsa::gg_2020::ErrorType, + utilities::zk_pdl_with_slack::PDLwSlackProof, +}; +use curv::{ + cryptographic_primitives::{ + hashing::{Digest, DigestExt}, + proofs::{sigma_dlog::DLogProof, sigma_valid_pedersen::PedersenProof}, + }, + elliptic::curves::{secp256_k1::Secp256k1, Point, Scalar}, +}; +use paillier::*; +use sha2::Sha256; +use zk_paillier::zkproofs::DLogStatement; + +#[test] +fn test_keygen_t1_n2() { + assert!(keygen_t_n_parties(1, 2).is_ok()); +} + +#[test] +fn test_keygen_t2_n3() { + assert!(keygen_t_n_parties(2, 3).is_ok()); +} + +#[test] +fn test_keygen_t2_n4() { + assert!(keygen_t_n_parties(2, 4).is_ok()); +} + +#[test] +fn test_sign_n2_t1_ttag1() { + let _ = sign(1, 2, 2, vec![0, 1], 0, &[0]); +} + +#[test] +fn test_sign_n5_t2_ttag4() { + let _ = sign(2, 5, 4, vec![0, 2, 3, 4], 0, &[0]); +} +#[test] +fn test_sign_n8_t4_ttag6() { + let _ = sign(4, 8, 6, vec![0, 1, 2, 4, 6, 7], 0, &[0]); +} + +// party 1 is corrupting step 5 +#[test] +fn test_sign_n2_t1_ttag1_corrupt_step5_party1() { + let res = sign(1, 2, 2, vec![0, 1], 5, &[0]); + assert!(&res.err().unwrap().bad_actors[..] == &[0]) +} + +// party 2 is corrupting step 5 +#[test] +fn test_sign_n2_t1_ttag1_corrupt_step5_party2() { + let res = sign(1, 2, 2, vec![0, 1], 5, &[1]); + assert!(&res.err().unwrap().bad_actors[..] == &[1]) +} + +// both parties are corrupting step 5 +#[test] +fn test_sign_n2_t1_ttag1_corrupt_step5_party12() { + let res = sign(1, 2, 2, vec![0, 1], 5, &[0, 1]); + assert!(&res.err().unwrap().bad_actors[..] == &[0, 1]) +} +// party 1 is corrupted +#[test] +fn test_sign_n5_t2_ttag4_corrupt_step5_party1() { + let res = sign(2, 5, 4, vec![0, 2, 3, 4], 5, &[0]); + assert!(&res.err().unwrap().bad_actors[..] == &[0]) +} + +// party 1,4 are corrupted +#[test] +fn test_sign_n5_t2_ttag4_corrupt_step5_party14() { + let res = sign(2, 5, 4, vec![0, 2, 3, 4], 5, &[0, 3]); + assert!(&res.err().unwrap().bad_actors[..] == &[0, 3]) +} + +// party 1 is corrupting step 6 +#[test] +fn test_sign_n2_t1_ttag1_corrupt_step6_party1() { + let res = sign(1, 2, 2, vec![0, 1], 6, &[0]); + assert!(&res.err().unwrap().bad_actors[..] == &[0]) +} +// party 2 is corrupting step 6 +#[test] +fn test_sign_n2_t1_ttag1_corrupt_step6_party2() { + let res = sign(1, 2, 2, vec![0, 1], 6, &[1]); + assert!(&res.err().unwrap().bad_actors[..] == &[1]) +} + +// both parties are corrupting step 6 +#[test] +fn test_sign_n2_t1_ttag1_corrupt_step6_party12() { + let res = sign(1, 2, 2, vec![0, 1], 6, &[0, 1]); + assert!(&res.err().unwrap().bad_actors[..] == &[0, 1]) +} +// party 1 is corrupted +#[test] +fn test_sign_n5_t2_ttag4_corrupt_step6_party1() { + let res = sign(2, 5, 4, vec![0, 2, 3, 4], 6, &[0]); + assert!(&res.err().unwrap().bad_actors[..] == &[0]) +} + +// party 1,4 are corrupted +#[test] +fn test_sign_n5_t2_ttag4_corrupt_step6_party14() { + let res = sign(2, 5, 4, vec![0, 2, 3, 4], 6, &[0, 3]); + assert!(&res.err().unwrap().bad_actors[..] == &[0, 3]) +} + +// party 1 is corrupting step 5 +#[test] +fn test_sign_n2_t1_ttag1_corrupt_step7_party2() { + let res = sign(1, 2, 2, vec![0, 1], 7, &[1]); + assert!(&res.err().unwrap().bad_actors[..] == &[1]) +} + +// party 2,4 are corrupted +#[test] +fn test_sign_n5_t2_ttag4_corrupt_step7_party24() { + let res = sign(2, 5, 4, vec![0, 2, 3, 4], 7, &[1, 3]); + assert!(&res.err().unwrap().bad_actors[..] == &[1, 3]) +} + +fn keygen_t_n_parties( + t: u16, + n: u16, +) -> Result< + ( + Vec, + Vec>, + Vec>, + Point, + VerifiableSS, + Vec, + Vec, + ), + ErrorType, +> { + let params = Parameters { + threshold: t, + share_count: n, + }; + let (t, n) = (t as usize, n as usize); + let party_keys_vec = (0..n).map(Keys::create).collect::>(); + + let (bc1_vec, decom_vec): (Vec<_>, Vec<_>) = party_keys_vec + .iter() + .map(|k| k.phase1_broadcast_phase3_proof_of_correct_key_proof_of_correct_h1h2()) + .unzip(); + + let e_vec = bc1_vec + .iter() + .map(|bc1| bc1.e.clone()) + .collect::>(); + let h1_h2_N_tilde_vec = bc1_vec + .iter() + .map(|bc1| bc1.dlog_statement.clone()) + .collect::>(); + let y_vec = (0..n) + .map(|i| decom_vec[i].y_i.clone()) + .collect::>>(); + let mut y_vec_iter = y_vec.iter(); + let head = y_vec_iter.next().unwrap(); + let tail = y_vec_iter; + let y_sum = tail.fold(head.clone(), |acc, x| acc + x); + + let mut vss_scheme_vec = Vec::new(); + let mut secret_shares_vec = Vec::new(); + let mut index_vec = Vec::new(); + + // TODO: find way to propagate the error and bad actors list properly + let vss_result: Vec<_> = party_keys_vec + .iter() + .map(|k| { + k.phase1_verify_com_phase3_verify_correct_key_verify_dlog_phase2_distribute( + ¶ms, &decom_vec, &bc1_vec, + ) + .expect("") + }) + .collect(); + + for (vss_scheme, secret_shares, index) in vss_result { + vss_scheme_vec.push(vss_scheme); + secret_shares_vec.push(secret_shares); // cannot unzip + index_vec.push(index as u16); + } + + let vss_scheme_for_test = vss_scheme_vec.clone(); + + let party_shares = (0..n) + .map(|i| { + (0..n) + .map(|j| { + let vec_j = &secret_shares_vec[j]; + vec_j[i].clone() + }) + .collect::>>() + }) + .collect::>>>(); + + let mut shared_keys_vec = Vec::new(); + let mut dlog_proof_vec = Vec::new(); + for (i, key) in party_keys_vec.iter().enumerate() { + let res = key.phase2_verify_vss_construct_keypair_phase3_pok_dlog( + ¶ms, + &y_vec, + &party_shares[i], + &vss_scheme_vec, + (&index_vec[i] + 1).into(), + ); + if res.is_err() { + return Err(res.err().unwrap()); + } + let (shared_keys, dlog_proof) = res.unwrap(); + shared_keys_vec.push(shared_keys); + dlog_proof_vec.push(dlog_proof); + } + + let pk_vec = (0..n) + .map(|i| dlog_proof_vec[i].pk.clone()) + .collect::>>(); + + let dlog_verification = Keys::verify_dlog_proofs_check_against_vss( + ¶ms, + &dlog_proof_vec, + &y_vec, + &vss_scheme_vec, + ); + + if dlog_verification.is_err() { + return Err(dlog_verification.err().unwrap()); + } + //test + let xi_vec = (0..=t) + .map(|i| shared_keys_vec[i].x_i.clone()) + .collect::>>(); + let x = vss_scheme_for_test[0] + .clone() + .reconstruct(&index_vec[0..=t], &xi_vec); + let sum_u_i = party_keys_vec + .iter() + .fold(Scalar::::zero(), |acc, x| acc + &x.u_i); + assert_eq!(x, sum_u_i); + + Ok(( + party_keys_vec, // Paillier keys, keypair, N, h1, h2 + shared_keys_vec, // Private shares for this MPC keypair + pk_vec, // dlog proof for x_i + y_sum, // public key for this MPC keypair. + vss_scheme_for_test[0].clone(), /* This contains the commitments for + * each initial share and the shares + * itself */ + e_vec, // paillier encryption keys. Why separate ? + h1_h2_N_tilde_vec, + )) +} + +fn sign( + t: u16, + n: u16, + ttag: u16, //number of participants + s: Vec, //participant list indexed from zero + corrupt_step: usize, + corrupted_parties: &[usize], +) -> Result { + // full key gen emulation + let ( + party_keys_vec, + shared_keys_vec, + pk_vec, + y, + vss_scheme, + ek_vec, + dlog_statement_vec, + ) = keygen_t_n_parties(t, n).unwrap(); + + // transform the t,n share to t,t+1 share. Get the public keys for the same. + let g_w_vec = SignKeys::g_w_vec(&pk_vec, &s[..], &vss_scheme); + + let private_vec = (0..shared_keys_vec.len()) + .map(|i| shared_keys_vec[i].x_i.clone()) + .collect::>(); + // make sure that we have t t); + let ttag = ttag as usize; + assert_eq!(s.len(), ttag); + + // each party creates a signing key. This happens in parallel IRL. In this + // test we create a vector of signing keys, one for each party. + // throughout i will index parties + let sign_keys_vec = (0..ttag) + .map(|i| SignKeys::create(&private_vec[s[i]], &vss_scheme, s[i], &s)) + .collect::>(); + + // each party computes [Ci,Di] = com(g^gamma_i) and broadcast the + // commitments + let (bc1_vec, decommit_vec1): (Vec<_>, Vec<_>) = + sign_keys_vec.iter().map(|k| k.phase1_broadcast()).unzip(); + + // each signer's dlog statement. in reality, parties prove statements + // using only other parties' h1,h2,N_tilde. here we also use own parameters + // for simplicity + let signers_dlog_statements = (0..ttag) + .map(|i| dlog_statement_vec[s[i]].clone()) + .collect::>(); + + // each party i BROADCASTS encryption of k_i under her Paillier key + // m_a_vec = [ma_0;ma_1;,...] + // we assume here that party sends the same encryption to all other parties. + // It should be changed to different encryption (randomness) to each counter + // party + let m_a_vec: Vec<_> = sign_keys_vec + .iter() + .enumerate() + .map(|(i, k)| { + MessageA::a( + &k.k_i, + &party_keys_vec[s[i]].ek, + &signers_dlog_statements, + ) + }) + .collect(); + + // #each party i sends responses to m_a_vec she received (one response with + // input gamma_i and one with w_i) #m_b_gamma_vec_all is a matrix where + // column i is a vector of message_b's that were sent to party i + + // aggregation of the n messages of all parties + let mut m_b_gamma_vec_all = Vec::new(); + let mut beta_vec_all = Vec::new(); + let mut m_b_w_vec_all = Vec::new(); + let mut ni_vec_all = Vec::new(); + let mut beta_randomness_vec_all = Vec::new(); //should be accessible in case of blame + let mut beta_tag_vec_all = Vec::new(); //should be accessible in case of blame + + // m_b_gamma and m_b_w are BROADCAST + for i in 0..ttag { + let mut m_b_gamma_vec = Vec::new(); + let mut beta_vec = Vec::new(); + let mut beta_randomness_vec = Vec::new(); + let mut beta_tag_vec = Vec::new(); + let mut m_b_w_vec = Vec::new(); + let mut ni_vec = Vec::new(); + + for j in 0..ttag - 1 { + let ind = if j < i { j } else { j + 1 }; + let (m_b_gamma, beta_gamma, beta_randomness, beta_tag) = + MessageB::b( + &sign_keys_vec[ind].gamma_i, + &ek_vec[s[i]], + m_a_vec[i].0.clone(), + &signers_dlog_statements, + ) + .expect("Alice's range proofs in MtA failed"); + let (m_b_w, beta_wi, _, _) = MessageB::b( + &sign_keys_vec[ind].w_i, + &ek_vec[s[i]], + m_a_vec[i].0.clone(), + &signers_dlog_statements, + ) + .expect("Alice's range proofs in MtA failed"); + + m_b_gamma_vec.push(m_b_gamma); + beta_vec.push(beta_gamma); + beta_randomness_vec.push(beta_randomness); + beta_tag_vec.push(beta_tag); + m_b_w_vec.push(m_b_w); + ni_vec.push(beta_wi); + } + m_b_gamma_vec_all.push(m_b_gamma_vec.clone()); + beta_vec_all.push(beta_vec.clone()); + beta_tag_vec_all.push(beta_tag_vec.clone()); + beta_randomness_vec_all.push(beta_randomness_vec.clone()); + m_b_w_vec_all.push(m_b_w_vec.clone()); + ni_vec_all.push(ni_vec.clone()); + } + + // Here we complete the MwA protocols by taking the mb matrices and starting + // with the first column, generating the appropriate message. The first + // column is the answers of party 1 to mb sent from other parties. + // The second column is the answers that party 2 is sending and so on. + + // TODO: simulate as IRL + let mut alpha_vec_all = Vec::new(); + let mut miu_vec_all = Vec::new(); + let mut miu_bigint_vec_all = Vec::new(); //required for the phase6 IA sub protocol + + for i in 0..ttag { + let mut alpha_vec = Vec::new(); + let mut miu_vec = Vec::new(); + let mut miu_bigint_vec = Vec::new(); //required for the phase6 IA sub protocol + + let m_b_gamma_vec_i = &m_b_gamma_vec_all[i]; + let m_b_w_vec_i = &m_b_w_vec_all[i]; + + // in case + for j in 0..ttag - 1 { + let ind = if j < i { j } else { j + 1 }; + let m_b = m_b_gamma_vec_i[j].clone(); + + // TODO: identify these errors + let alpha_ij_gamma = m_b + .verify_proofs_get_alpha( + &party_keys_vec[s[i]].dk, + &sign_keys_vec[i].k_i, + ) + .expect("wrong dlog or m_b"); + let m_b = m_b_w_vec_i[j].clone(); + let alpha_ij_wi = m_b + .verify_proofs_get_alpha( + &party_keys_vec[s[i]].dk, + &sign_keys_vec[i].k_i, + ) + .expect("wrong dlog or m_b"); + + // since we actually run two MtAwc each party needs to make sure + // that the values B are the same as the public values + // here for b=w_i the parties already know W_i = g^w_i for each + // party so this check is done here. for b = gamma_i the check will + // be later when g^gamma_i will become public + // currently we take the W_i from the other parties signing keys + // TODO: use pk_vec (first change from x_i to w_i) for this check. + assert_eq!(m_b.b_proof.pk, sign_keys_vec[ind].g_w_i); + + alpha_vec.push(alpha_ij_gamma.0); + miu_vec.push(alpha_ij_wi.0); + miu_bigint_vec.push(alpha_ij_wi.1); + } + alpha_vec_all.push(alpha_vec.clone()); + miu_vec_all.push(miu_vec.clone()); + miu_bigint_vec_all.push(miu_bigint_vec.clone()); + } + + let mut delta_vec = Vec::new(); + let mut sigma_vec = Vec::new(); + + for i in 0..ttag { + // prepare beta_vec of party_i: + let beta_vec = (0..ttag - 1) + .map(|j| { + let ind1 = if j < i { j } else { j + 1 }; + let ind2 = if j < i { i - 1 } else { i }; + + beta_vec_all[ind1][ind2].clone() + }) + .collect::>>(); + + // prepare ni_vec of party_i: + let ni_vec = (0..ttag - 1) + .map(|j| { + let ind1 = if j < i { j } else { j + 1 }; + let ind2 = if j < i { i - 1 } else { i }; + ni_vec_all[ind1][ind2].clone() + }) + .collect::>>(); + + let mut delta = + sign_keys_vec[i].phase2_delta_i(&alpha_vec_all[i], &beta_vec); + + let mut sigma = + sign_keys_vec[i].phase2_sigma_i(&miu_vec_all[i], &ni_vec); + // test wrong delta corruption + if corrupt_step == 5 && corrupted_parties.iter().any(|&x| x == i) { + delta = &delta + δ + } + // test wrong sigma corruption + if corrupt_step == 6 && corrupted_parties.iter().any(|&x| x == i) { + sigma = &sigma + σ + } + delta_vec.push(delta); + sigma_vec.push(sigma); + } + + // all parties broadcast delta_i and compute delta_i ^(-1) + let delta_inv = SignKeys::phase3_reconstruct_delta(&delta_vec); + // all parties broadcast T_i: + let mut T_vec = Vec::new(); + let mut l_vec = Vec::new(); + let mut T_proof_vec = Vec::new(); + for i in 0..ttag { + let (T_i, l_i, T_proof_i) = SignKeys::phase3_compute_t_i(&sigma_vec[i]); + T_vec.push(T_i); + l_vec.push(l_i); + T_proof_vec.push(T_proof_i); + } + // verify T_proof_vec + for i in 0..ttag { + assert_eq!(T_vec[i], T_proof_vec[i].com.clone()); + PedersenProof::verify(&T_proof_vec[i]).expect("error T proof"); + } + // de-commit to g^gamma_i from phase1, test comm correctness, and that it is + // the same value used in MtA. Return R + + let R_vec = (0..ttag) + .map(|i| { + // each party i tests all B = g^b = g ^ gamma_i she received. + let m_b_gamma_vec = &m_b_gamma_vec_all[i]; + let b_proof_vec = (0..ttag - 1) + .map(|j| &m_b_gamma_vec[j].b_proof) + .collect::>>(); + SignKeys::phase4( + &delta_inv, + &b_proof_vec, + decommit_vec1.clone(), + &bc1_vec, + i, + ) + .expect("") //TODO: propagate the error + }) + .collect::>>(); + + //new phase 5 + // all parties broadcast R_dash = k_i * R. + let R_dash_vec = (0..ttag) + .map(|i| &R_vec[i] * &sign_keys_vec[i].k_i) + .collect::>>(); + + // each party sends first message to all other parties + let mut phase5_proofs_vec: Vec> = + vec![Vec::new(); ttag]; + for i in 0..ttag { + for j in 0..ttag - 1 { + let ind = if j < i { j } else { j + 1 }; + let proof = LocalSignature::phase5_proof_pdl( + &R_dash_vec[i], + &R_vec[i], + &m_a_vec[i].0.c, + &ek_vec[s[i]], + &sign_keys_vec[i].k_i, + &m_a_vec[i].1, + &dlog_statement_vec[s[ind]], + ); + + phase5_proofs_vec[i].push(proof); + } + } + + for i in 0..ttag { + let phase5_verify_zk = LocalSignature::phase5_verify_pdl( + &phase5_proofs_vec[i], + &R_dash_vec[i], + &R_vec[i], + &m_a_vec[i].0.c, + &ek_vec[s[i]], + &dlog_statement_vec[..], + &s, + i, + ); + if phase5_verify_zk.is_err() { + return Err(phase5_verify_zk.err().unwrap()); + } + } + + //each party must run the test + let phase5_check = LocalSignature::phase5_check_R_dash_sum(&R_dash_vec); + if phase5_check.is_err() { + // initiate phase 5 blame protocol to learn which parties acted + // maliciously. each party generates local state and share with + // other parties. assuming sync communication - if a message was + // failed to arrive from a party - this party should + // automatically be blamed + let mut local_state_vec = Vec::new(); + for i in 0..ttag { + // compose beta tag vector: + let mut beta_tag_vec_to_test = Vec::new(); + let mut beta_randomness_vec_to_test = Vec::new(); + for j in 0..ttag - 1 { + let ind1 = if j < i { j } else { j + 1 }; + let ind2 = if j < i { i - 1 } else { i }; + beta_tag_vec_to_test.push(beta_tag_vec_all[ind1][ind2].clone()); + beta_randomness_vec_to_test + .push(beta_randomness_vec_all[ind1][ind2].clone()); + } + + let local_state = LocalStatePhase5 { + k: sign_keys_vec[i].k_i.clone(), + k_randomness: m_a_vec[i].1.clone(), + gamma: sign_keys_vec[i].gamma_i.clone(), + beta_randomness: beta_randomness_vec_to_test, + beta_tag: beta_tag_vec_to_test, + encryption_key: ek_vec[s[i]].clone(), + }; + local_state_vec.push(local_state); + } + //g_gamma_vec: + let g_gamma_vec = (0..decommit_vec1.len()) + .map(|i| decommit_vec1[i].g_gamma_i.clone()) + .collect::>>(); + //m_a_vec + let m_a_vec = (0..m_a_vec.len()) + .map(|i| m_a_vec[i].0.clone()) + .collect::>(); + + // reduce ek vec to only ek of participants : + let ek_vec = (0..ttag) + .map(|k| ek_vec[s[k]].clone()) + .collect::>(); + let global_state = GlobalStatePhase5::local_state_to_global_state( + &ek_vec[..], + &delta_vec, + &g_gamma_vec[..], + &m_a_vec[..], + m_b_gamma_vec_all, + &local_state_vec[..], + ); + global_state.phase5_blame()?; + } + + let mut S_vec = Vec::new(); + let mut homo_elgamal_proof_vec = Vec::new(); + for i in 0..ttag { + let (S_i, homo_elgamal_proof) = + LocalSignature::phase6_compute_S_i_and_proof_of_consistency( + &R_vec[i], + &T_vec[i], + &sigma_vec[i], + &l_vec[i], + ); + S_vec.push(S_i); + homo_elgamal_proof_vec.push(homo_elgamal_proof); + } + + LocalSignature::phase6_verify_proof( + &S_vec, + &homo_elgamal_proof_vec, + &R_vec, + &T_vec, + )?; + + let phase6_check = LocalSignature::phase6_check_S_i_sum(&y, &S_vec); + if phase6_check.is_err() { + // initiate phase 6 blame protocol to learn which parties acted + // maliciously. each party generates local state and share with + // other parties. assuming sync communication - if a message was + // failed to arrive from a party - this party should + // automatically be blamed + + let mut local_state_vec = Vec::new(); + for i in 0..ttag { + let mut miu_randomness_vec = Vec::new(); + for j in 0..ttag - 1 { + let rand = GlobalStatePhase6::extract_paillier_randomness( + &m_b_w_vec_all[i][j].c, + &party_keys_vec[s[i]].dk, + ); + miu_randomness_vec.push(rand); + } + let proof = GlobalStatePhase6::ecddh_proof( + &sigma_vec[i], + &R_vec[i], + &S_vec[i], + ); + let local_state = LocalStatePhase6 { + k: sign_keys_vec[i].k_i.clone(), + k_randomness: m_a_vec[i].1.clone(), + miu: miu_bigint_vec_all[i].clone(), + miu_randomness: miu_randomness_vec, + proof_of_eq_dlog: proof, + }; + local_state_vec.push(local_state); + } + + //m_a_vec + let m_a_vec = (0..m_a_vec.len()) + .map(|i| m_a_vec[i].0.clone()) + .collect::>(); + + // reduce ek vec to only ek of participants : + let ek_vec = (0..ttag) + .map(|k| ek_vec[s[k]].clone()) + .collect::>(); + + let global_state = GlobalStatePhase6::local_state_to_global_state( + &ek_vec[..], + &S_vec[..], + &g_w_vec[..], + &m_a_vec[..], + m_b_w_vec_all, + &local_state_vec[..], + ); + global_state.phase6_blame(&R_vec[0])?; + } + + let message: [u8; 4] = [79, 77, 69, 82]; + let message_bn = Sha256::new() + .chain_bigint(&BigInt::from_bytes(&message[..])) + .result_bigint(); + let mut local_sig_vec = Vec::new(); + let mut s_vec = Vec::new(); + // each party computes s_i + for i in 0..ttag { + let local_sig = LocalSignature::phase7_local_sig( + &sign_keys_vec[i].k_i, + &message_bn, + &R_vec[i], + &sigma_vec[i], + &y, + ); + s_vec.push(local_sig.s_i.clone()); + local_sig_vec.push(local_sig); + } + + // test corrupted local s + if corrupt_step == 7 { + for i in 0..s_vec.len() { + if corrupted_parties.iter().any(|&x| x == i) { + s_vec[i] = &s_vec[i] + &s_vec[i]; + } + } + } + + let sig = local_sig_vec[0].output_signature(&s_vec[1..]); + + // test + assert_eq!(local_sig_vec[0].y, y); + //error in phase 7: + if sig.is_err() { + let global_state = GlobalStatePhase7 { + s_vec, + r: local_sig_vec[0].r.clone(), + R_dash_vec, + m: local_sig_vec[0].m.clone(), + R: local_sig_vec[0].R.clone(), + S_vec, + }; + global_state.phase7_blame()?; + } + //for testing purposes: checking with a second verifier: + + let sig = sig.unwrap(); + check_sig(&sig.r, &sig.s, &local_sig_vec[0].m, &y); + Ok(sig) +} + +fn check_sig( + r: &Scalar, + s: &Scalar, + msg: &BigInt, + pk: &Point, +) { + use secp256k1::{Message, PublicKey, Signature, SECP256K1}; + + let raw_msg = BigInt::to_bytes(msg); + let mut msg: Vec = Vec::new(); // padding + msg.extend(vec![0u8; 32 - raw_msg.len()]); + msg.extend(raw_msg.iter()); + + let msg = Message::from_slice(msg.as_slice()).unwrap(); + let slice = pk.to_bytes(false); + let mut raw_pk = Vec::new(); + if slice.len() != 65 { + // after curv's pk_to_key_slice return 65 bytes, this can be removed + raw_pk.insert(0, 4u8); + raw_pk.extend(vec![0u8; 64 - slice.len()]); + raw_pk.extend(slice.as_ref()); + } else { + raw_pk.extend(slice.as_ref()); + } + + assert_eq!(raw_pk.len(), 65); + + let pk = PublicKey::from_slice(&raw_pk).unwrap(); + + let mut compact: Vec = Vec::new(); + let bytes_r = &r.to_bytes()[..]; + compact.extend(vec![0u8; 32 - bytes_r.len()]); + compact.extend(bytes_r.iter()); + + let bytes_s = &s.to_bytes()[..]; + compact.extend(vec![0u8; 32 - bytes_s.len()]); + compact.extend(bytes_s.iter()); + + let secp_sig = Signature::from_compact(compact.as_slice()).unwrap(); + + let is_correct = SECP256K1.verify(&msg, &secp_sig, &pk).is_ok(); + assert!(is_correct); +} +#[test] +fn test_serialize_deserialize() { + use serde_json; + + let k = Keys::create(0); + let (commit, decommit) = + k.phase1_broadcast_phase3_proof_of_correct_key_proof_of_correct_h1h2(); + + let encoded = serde_json::to_string(&commit).unwrap(); + let decoded: KeyGenBroadcastMessage1 = + serde_json::from_str(&encoded).unwrap(); + assert_eq!(commit.com, decoded.com); + + let encoded = serde_json::to_string(&decommit).unwrap(); + let decoded: KeyGenDecommitMessage1 = + serde_json::from_str(&encoded).unwrap(); + assert_eq!(decommit.y_i, decoded.y_i); +} +#[test] +fn test_small_paillier() { + // parties shouldn't be able to choose small Paillier modulus + let mut k = Keys::create(0); + // creating 2046-bit Paillier + let (ek, dk) = Paillier::keypair_with_modulus_size(2046).keys(); + k.dk = dk; + k.ek = ek; + let (commit, decommit) = + k.phase1_broadcast_phase3_proof_of_correct_key_proof_of_correct_h1h2(); + assert!(k + .phase1_verify_com_phase3_verify_correct_key_verify_dlog_phase2_distribute( + &Parameters { + threshold: 0, + share_count: 1 + }, + &[decommit], + &[commit], + ) + .is_err()); +} diff --git a/multi-party-ecdsa/src/lib.rs b/multi-party-ecdsa/src/lib.rs new file mode 100644 index 00000000..cd88363e --- /dev/null +++ b/multi-party-ecdsa/src/lib.rs @@ -0,0 +1,51 @@ +/* + Multi-party ECDSA + + Copyright 2018 by Kzen Networks + + This file is part of Multi-party ECDSA library + (https://github.com/KZen-networks/multi-party-ecdsa) + + Multi-party ECDSA is free software: you can redistribute + it and/or modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, either + version 3 of the License, or (at your option) any later version. + + @license GPL-3.0+ +*/ + +#![allow(clippy::many_single_char_names)] +#![allow(clippy::too_many_arguments)] +#![allow(clippy::type_complexity)] + +pub mod gg_2020; +#[deprecated(note = "Use top-level protocol namespace (eg: gg_2020)")] +pub mod protocols; +pub mod utilities; +use std::fmt; + +pub use gg_2020::state_machine::traits::MessageRoundID; + +#[derive(Copy, PartialEq, Eq, Clone, Debug)] +pub enum Error { + InvalidKey, + InvalidSS, + InvalidCom, + InvalidSig, + Phase5BadSum, + Phase6Error, +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use Error::*; + match *self { + InvalidKey => write!(f, "InvalidKey"), + InvalidSS => write!(f, "InvalidSS"), + InvalidCom => write!(f, "InvalidCom"), + InvalidSig => write!(f, "InvalidSig"), + Phase5BadSum => write!(f, "Phase5BadSum"), + Phase6Error => write!(f, "Phase6Error"), + } + } +} diff --git a/multi-party-ecdsa/src/protocols/mod.rs b/multi-party-ecdsa/src/protocols/mod.rs new file mode 100644 index 00000000..373276dc --- /dev/null +++ b/multi-party-ecdsa/src/protocols/mod.rs @@ -0,0 +1,17 @@ +/* + Multi-party ECDSA + + Copyright 2018 by Kzen Networks + + This file is part of Multi-party ECDSA library + (https://github.com/KZen-networks/multi-party-ecdsa) + + Multi-party ECDSA is free software: you can redistribute + it and/or modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, either + version 3 of the License, or (at your option) any later version. + + @license GPL-3.0+ +*/ + +pub mod multi_party_ecdsa; diff --git a/multi-party-ecdsa/src/protocols/multi_party_ecdsa/mod.rs b/multi-party-ecdsa/src/protocols/multi_party_ecdsa/mod.rs new file mode 100644 index 00000000..a1fcfdd0 --- /dev/null +++ b/multi-party-ecdsa/src/protocols/multi_party_ecdsa/mod.rs @@ -0,0 +1,17 @@ +/* + Multi-party ECDSA + + Copyright 2018 by Kzen Networks + + This file is part of Multi-party ECDSA library + (https://github.com/KZen-networks/multi-party-ecdsa) + + Multi-party ECDSA is free software: you can redistribute + it and/or modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, either + version 3 of the License, or (at your option) any later version. + + @license GPL-3.0+ +*/ + +pub use crate::gg_2020; diff --git a/multi-party-ecdsa/src/utilities/mod.rs b/multi-party-ecdsa/src/utilities/mod.rs new file mode 100644 index 00000000..867fce30 --- /dev/null +++ b/multi-party-ecdsa/src/utilities/mod.rs @@ -0,0 +1,3 @@ +pub mod mta; +pub mod zk_pdl; +pub mod zk_pdl_with_slack; diff --git a/multi-party-ecdsa/src/utilities/mta/mod.rs b/multi-party-ecdsa/src/utilities/mta/mod.rs new file mode 100644 index 00000000..4c37f2b6 --- /dev/null +++ b/multi-party-ecdsa/src/utilities/mta/mod.rs @@ -0,0 +1,238 @@ +/* + Multi-party ECDSA + + Copyright 2018 by Kzen Networks + + This file is part of Multi-party ECDSA library + (https://github.com/KZen-networks/multi-party-ecdsa) + + Multi-party ECDSA is free software: you can redistribute + it and/or modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, either + version 3 of the License, or (at your option) any later version. + + @license GPL-3.0+ +*/ + +/// MtA is described in https://eprint.iacr.org/2019/114.pdf section 3 +use curv::arithmetic::traits::Samplable; +use curv::{ + cryptographic_primitives::proofs::sigma_dlog::DLogProof, + elliptic::curves::{secp256_k1::Secp256k1, Point, Scalar}, + BigInt, +}; +use paillier::{ + traits::EncryptWithChosenRandomness, Add, Decrypt, DecryptionKey, + EncryptionKey, Mul, Paillier, Randomness, RawCiphertext, RawPlaintext, +}; +use zk_paillier::zkproofs::DLogStatement; + +use serde::{Deserialize, Serialize}; +use sha2::Sha256; + +use crate::{ + protocols::multi_party_ecdsa::gg_2020::party_i::PartyPrivate, + utilities::mta::range_proofs::AliceProof, + Error::{self, InvalidKey}, +}; + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct MessageA { + pub c: BigInt, // paillier encryption + pub range_proofs: Vec, /* proofs (using other parties' + * h1,h2,N_tilde) that the plaintext + * is small */ +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct MessageB { + pub c: BigInt, // paillier encryption + pub b_proof: DLogProof, + pub beta_tag_proof: DLogProof, +} + +impl MessageA { + /// Creates a new `messageA` using Alice's Paillier encryption key and + /// `dlog_statements` + /// - other parties' `h1,h2,N_tilde`s for range proofs. + /// If range proofs are not needed (one example is identification of aborts + /// where we only want to reconstruct a ciphertext), `dlog_statements` + /// can be an empty slice. + pub fn a( + a: &Scalar, + alice_ek: &EncryptionKey, + dlog_statements: &[DLogStatement], + ) -> (Self, BigInt) { + let randomness = BigInt::sample_below(&alice_ek.n); + let m_a = MessageA::a_with_predefined_randomness( + a, + alice_ek, + &randomness, + dlog_statements, + ); + (m_a, randomness) + } + + pub fn a_with_predefined_randomness( + a: &Scalar, + alice_ek: &EncryptionKey, + randomness: &BigInt, + dlog_statements: &[DLogStatement], + ) -> Self { + let c_a = Paillier::encrypt_with_chosen_randomness( + alice_ek, + RawPlaintext::from(a.to_bigint()), + &Randomness::from(randomness.clone()), + ) + .0 + .clone() + .into_owned(); + let alice_range_proofs = dlog_statements + .iter() + .map(|dlog_statement| { + AliceProof::generate( + &a.to_bigint(), + &c_a, + alice_ek, + dlog_statement, + randomness, + ) + }) + .collect::>(); + + Self { + c: c_a, + range_proofs: alice_range_proofs, + } + } +} + +impl MessageB { + pub fn b( + b: &Scalar, + alice_ek: &EncryptionKey, + m_a: MessageA, + dlog_statements: &[DLogStatement], + ) -> Result<(Self, Scalar, BigInt, BigInt), Error> { + let beta_tag = BigInt::sample_below(&alice_ek.n); + let randomness = BigInt::sample_below(&alice_ek.n); + let (m_b, beta) = MessageB::b_with_predefined_randomness( + b, + alice_ek, + m_a, + &randomness, + &beta_tag, + dlog_statements, + )?; + + Ok((m_b, beta, randomness, beta_tag)) + } + + pub fn b_with_predefined_randomness( + b: &Scalar, + alice_ek: &EncryptionKey, + m_a: MessageA, + randomness: &BigInt, + beta_tag: &BigInt, + dlog_statements: &[DLogStatement], + ) -> Result<(Self, Scalar), Error> { + if m_a.range_proofs.len() != dlog_statements.len() { + return Err(InvalidKey); + } + // verify proofs + if !m_a + .range_proofs + .iter() + .zip(dlog_statements) + .map(|(proof, dlog_statement)| { + proof.verify(&m_a.c, alice_ek, dlog_statement) + }) + .all(|x| x) + { + log::info!("MP-ECDSA : Proof Mismatch"); + return Err(InvalidKey); + }; + let beta_tag_fe = Scalar::::from(beta_tag); + let c_beta_tag = Paillier::encrypt_with_chosen_randomness( + alice_ek, + RawPlaintext::from(beta_tag), + &Randomness::from(randomness.clone()), + ); + + let b_bn = b.to_bigint(); + let b_c_a = Paillier::mul( + alice_ek, + RawCiphertext::from(m_a.c), + RawPlaintext::from(b_bn), + ); + let c_b = Paillier::add(alice_ek, b_c_a, c_beta_tag); + let beta = Scalar::::zero() - &beta_tag_fe; + let dlog_proof_b = DLogProof::prove(b); + let dlog_proof_beta_tag = DLogProof::prove(&beta_tag_fe); + + Ok(( + Self { + c: c_b.0.clone().into_owned(), + b_proof: dlog_proof_b, + beta_tag_proof: dlog_proof_beta_tag, + }, + beta, + )) + } + + pub fn verify_proofs_get_alpha( + &self, + dk: &DecryptionKey, + a: &Scalar, + ) -> Result<(Scalar, BigInt), Error> { + let alice_share = + Paillier::decrypt(dk, &RawCiphertext::from(self.c.clone())); + let g = Point::generator(); + let alpha = Scalar::::from(alice_share.0.as_ref()); + let g_alpha = g * α + let ba_btag = &self.b_proof.pk * a + &self.beta_tag_proof.pk; + if DLogProof::verify(&self.b_proof).is_ok() + && DLogProof::verify(&self.beta_tag_proof).is_ok() + // we prove the correctness of the ciphertext using this check and the proof of knowledge of dlog of beta_tag + && ba_btag == g_alpha + { + Ok((alpha, alice_share.0.into_owned())) + } else { + Err(InvalidKey) + } + } + + // another version, supporting PartyPrivate therefore binding mta to gg18. + // with the regular version mta can be used in general + pub fn verify_proofs_get_alpha_gg18( + &self, + private: &PartyPrivate, + a: &Scalar, + ) -> Result, Error> { + let alice_share = private.decrypt(self.c.clone()); + let g = Point::generator(); + let alpha = Scalar::::from(alice_share.0.as_ref()); + let g_alpha = g * α + let ba_btag = &self.b_proof.pk * a + &self.beta_tag_proof.pk; + + if DLogProof::verify(&self.b_proof).is_ok() + && DLogProof::verify(&self.beta_tag_proof).is_ok() + && ba_btag == g_alpha + { + Ok(alpha) + } else { + Err(InvalidKey) + } + } + + pub fn verify_b_against_public( + public_gb: &Point, + mta_gb: &Point, + ) -> bool { + public_gb == mta_gb + } +} + +pub mod range_proofs; +#[cfg(test)] +mod test; diff --git a/multi-party-ecdsa/src/utilities/mta/range_proofs.rs b/multi-party-ecdsa/src/utilities/mta/range_proofs.rs new file mode 100644 index 00000000..6c9194dd --- /dev/null +++ b/multi-party-ecdsa/src/utilities/mta/range_proofs.rs @@ -0,0 +1,745 @@ +#![allow(non_snake_case)] + +//! This file is a modified version of ING bank's range proofs implementation: +//! https://github.com/ing-bank/threshold-signatures/blob/master/src/algorithms/zkp.rs +//! +//! Zero knowledge range proofs for MtA protocol are implemented here. +//! Formal description can be found in Appendix A of https://eprint.iacr.org/2019/114.pdf +//! There are some deviations from the original specification: +//! 1) In Bob's proofs `gamma` is sampled from `[0;q^2 * N]` and `tau` from +//! `[0;q^3 * N_tilde]`. +//! 2) A non-interactive version is implemented, with challenge `e` computed via +//! Fiat-Shamir. + +use curv::{ + arithmetic::traits::*, + cryptographic_primitives::hashing::{Digest, DigestExt}, + elliptic::curves::{secp256_k1::Secp256k1, Point, Scalar}, + BigInt, +}; +use sha2::Sha256; + +use paillier::{EncryptionKey, Randomness}; +use zk_paillier::zkproofs::DLogStatement; + +use serde::{Deserialize, Serialize}; +use std::borrow::Borrow; +use zeroize::Zeroize; + +/// Represents the first round of the interactive version of the proof +#[derive(Zeroize)] +#[zeroize(drop)] +struct AliceZkpRound1 { + alpha: BigInt, + beta: BigInt, + gamma: BigInt, + ro: BigInt, + z: BigInt, + u: BigInt, + w: BigInt, +} + +impl AliceZkpRound1 { + fn from( + alice_ek: &EncryptionKey, + dlog_statement: &DLogStatement, + a: &BigInt, + q: &BigInt, + ) -> Self { + let h1 = &dlog_statement.g; + let h2 = &dlog_statement.ni; + let N_tilde = &dlog_statement.N; + let alpha = BigInt::sample_below(&q.pow(3)); + let beta = BigInt::from_paillier_key(alice_ek); + let gamma = BigInt::sample_below(&(q.pow(3) * N_tilde)); + let ro = BigInt::sample_below(&(q * N_tilde)); + let z = (BigInt::mod_pow(h1, a, N_tilde) + * BigInt::mod_pow(h2, &ro, N_tilde)) + % N_tilde; + let u = ((alpha.borrow() * &alice_ek.n + 1) + * BigInt::mod_pow(&beta, &alice_ek.n, &alice_ek.nn)) + % &alice_ek.nn; + let w = (BigInt::mod_pow(h1, &alpha, N_tilde) + * BigInt::mod_pow(h2, &gamma, N_tilde)) + % N_tilde; + Self { + alpha, + beta, + gamma, + ro, + z, + u, + w, + } + } +} + +/// Represents the second round of the interactive version of the proof +struct AliceZkpRound2 { + s: BigInt, + s1: BigInt, + s2: BigInt, +} + +impl AliceZkpRound2 { + fn from( + alice_ek: &EncryptionKey, + round1: &AliceZkpRound1, + e: &BigInt, + a: &BigInt, + r: &BigInt, + ) -> Self { + Self { + s: (BigInt::mod_pow(r, e, &alice_ek.n) * round1.beta.borrow()) + % &alice_ek.n, + s1: (e * a) + round1.alpha.borrow(), + s2: (e * round1.ro.borrow()) + round1.gamma.borrow(), + } + } +} + +/// Alice's proof +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct AliceProof { + z: BigInt, + e: BigInt, + s: BigInt, + s1: BigInt, + s2: BigInt, +} + +impl AliceProof { + /// verify Alice's proof using the proof and public keys + pub fn verify( + &self, + cipher: &BigInt, + alice_ek: &EncryptionKey, + dlog_statement: &DLogStatement, + ) -> bool { + let N = &alice_ek.n; + let NN = &alice_ek.nn; + let N_tilde = &dlog_statement.N; + let h1 = &dlog_statement.g; + let h2 = &dlog_statement.ni; + let Gen = alice_ek.n.borrow() + 1; + + if self.s1 > Scalar::::group_order().pow(3) { + return false; + } + + let z_e_inv = BigInt::mod_inv( + &BigInt::mod_pow(&self.z, &self.e, N_tilde), + N_tilde, + ); + let z_e_inv = match z_e_inv { + // z must be invertible, yet the check is done here + None => return false, + Some(c) => c, + }; + + let w = (BigInt::mod_pow(h1, &self.s1, N_tilde) + * BigInt::mod_pow(h2, &self.s2, N_tilde) + * z_e_inv) + % N_tilde; + + let gs1 = (self.s1.borrow() * N + 1) % NN; + let cipher_e_inv = + BigInt::mod_inv(&BigInt::mod_pow(cipher, &self.e, NN), NN); + let cipher_e_inv = match cipher_e_inv { + None => return false, + Some(c) => c, + }; + + let u = (gs1 * BigInt::mod_pow(&self.s, N, NN) * cipher_e_inv) % NN; + + let e = Sha256::new() + .chain_bigint(N) + .chain_bigint(&Gen) + .chain_bigint(cipher) + .chain_bigint(&self.z) + .chain_bigint(&u) + .chain_bigint(&w) + .result_bigint(); + if e != self.e { + return false; + } + + true + } + /// Create the proof using Alice's Paillier private keys and public ZKP + /// setup. Requires randomness used for encrypting Alice's secret a. + /// It is assumed that secp256k1 curve is used. + pub fn generate( + a: &BigInt, + cipher: &BigInt, + alice_ek: &EncryptionKey, + dlog_statement: &DLogStatement, + r: &BigInt, + ) -> Self { + let round1 = AliceZkpRound1::from( + alice_ek, + dlog_statement, + a, + Scalar::::group_order(), + ); + + let Gen = alice_ek.n.borrow() + 1; + let e = Sha256::new() + .chain_bigint(&alice_ek.n) + .chain_bigint(&Gen) + .chain_bigint(cipher) + .chain_bigint(&round1.z) + .chain_bigint(&round1.u) + .chain_bigint(&round1.w) + .result_bigint(); + + let round2 = AliceZkpRound2::from(alice_ek, &round1, &e, a, r); + + Self { + z: round1.z.clone(), + e, + s: round2.s, + s1: round2.s1, + s2: round2.s2, + } + } +} + +/// Represents first round of the interactive version of the proof +#[derive(Zeroize)] +#[zeroize(drop)] +struct BobZkpRound1 { + pub alpha: BigInt, + pub beta: BigInt, + pub gamma: BigInt, + pub ro: BigInt, + pub ro_prim: BigInt, + pub sigma: BigInt, + pub tau: BigInt, + pub z: BigInt, + pub z_prim: BigInt, + pub t: BigInt, + pub w: BigInt, + pub v: BigInt, +} + +impl BobZkpRound1 { + /// `b` - Bob's secret + /// `beta_prim` - randomly chosen in `MtA` by Bob + /// `a_encrypted` - Alice's secret encrypted by Alice + fn from( + alice_ek: &EncryptionKey, + dlog_statement: &DLogStatement, + b: &Scalar, + beta_prim: &BigInt, + a_encrypted: &BigInt, + q: &BigInt, + ) -> Self { + let h1 = &dlog_statement.g; + let h2 = &dlog_statement.ni; + let N_tilde = &dlog_statement.N; + let b_bn = b.to_bigint(); + + let alpha = BigInt::sample_below(&q.pow(3)); + let beta = BigInt::from_paillier_key(alice_ek); + let gamma = BigInt::sample_below(&(q.pow(2) * &alice_ek.n)); + let ro = BigInt::sample_below(&(q * N_tilde)); + let ro_prim = BigInt::sample_below(&(q.pow(3) * N_tilde)); + let sigma = BigInt::sample_below(&(q * N_tilde)); + let tau = BigInt::sample_below(&(q.pow(3) * N_tilde)); + let z = (BigInt::mod_pow(h1, &b_bn, N_tilde) + * BigInt::mod_pow(h2, &ro, N_tilde)) + % N_tilde; + let z_prim = (BigInt::mod_pow(h1, &alpha, N_tilde) + * BigInt::mod_pow(h2, &ro_prim, N_tilde)) + % N_tilde; + let t = (BigInt::mod_pow(h1, beta_prim, N_tilde) + * BigInt::mod_pow(h2, &sigma, N_tilde)) + % N_tilde; + let w = (BigInt::mod_pow(h1, &gamma, N_tilde) + * BigInt::mod_pow(h2, &tau, N_tilde)) + % N_tilde; + let v = (BigInt::mod_pow(a_encrypted, &alpha, &alice_ek.nn) + * (gamma.borrow() * &alice_ek.n + 1) + * BigInt::mod_pow(&beta, &alice_ek.n, &alice_ek.nn)) + % &alice_ek.nn; + Self { + alpha, + beta, + gamma, + ro, + ro_prim, + sigma, + tau, + z, + z_prim, + t, + w, + v, + } + } +} + +/// represents second round of the interactive version of the proof +struct BobZkpRound2 { + pub s: BigInt, + pub s1: BigInt, + pub s2: BigInt, + pub t1: BigInt, + pub t2: BigInt, +} + +impl BobZkpRound2 { + /// `e` - the challenge in interactive ZKP, the hash in non-interactive ZKP + /// `b` - Bob's secret + /// `beta_prim` - randomly chosen in `MtA` by Bob + /// `r` - randomness used by Bob on Alice's public Paillier key to encrypt + /// `beta_prim` in `MtA` + fn from( + alice_ek: &EncryptionKey, + round1: &BobZkpRound1, + e: &BigInt, + b: &Scalar, + beta_prim: &BigInt, + r: &Randomness, + ) -> Self { + let b_bn = b.to_bigint(); + Self { + s: (BigInt::mod_pow(r.0.borrow(), e, &alice_ek.n) + * round1.beta.borrow()) + % &alice_ek.n, + s1: (e * b_bn) + round1.alpha.borrow(), + s2: (e * round1.ro.borrow()) + round1.ro_prim.borrow(), + t1: (e * beta_prim) + round1.gamma.borrow(), + t2: (e * round1.sigma.borrow()) + round1.tau.borrow(), + } + } +} + +/// Additional fields in Bob's proof if MtA is run with check +pub struct BobCheck { + u: Point, + X: Point, +} + +/// Bob's regular proof +#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] +pub struct BobProof { + t: BigInt, + z: BigInt, + e: BigInt, + s: BigInt, + s1: BigInt, + s2: BigInt, + t1: BigInt, + t2: BigInt, +} + +#[allow(clippy::too_many_arguments)] +impl BobProof { + pub fn verify( + &self, + a_enc: &BigInt, + mta_avc_out: &BigInt, + alice_ek: &EncryptionKey, + dlog_statement: &DLogStatement, + check: Option<&BobCheck>, + ) -> bool { + let N = &alice_ek.n; + let NN = &alice_ek.nn; + let N_tilde = &dlog_statement.N; + let h1 = &dlog_statement.g; + let h2 = &dlog_statement.ni; + + if self.s1 > Scalar::::group_order().pow(3) { + return false; + } + + let z_e_inv = BigInt::mod_inv( + &BigInt::mod_pow(&self.z, &self.e, N_tilde), + N_tilde, + ); + let z_e_inv = match z_e_inv { + // z must be invertible, yet the check is done here + None => return false, + Some(c) => c, + }; + + let z_prim = (BigInt::mod_pow(h1, &self.s1, N_tilde) + * BigInt::mod_pow(h2, &self.s2, N_tilde) + * z_e_inv) + % N_tilde; + + let mta_e_inv = + BigInt::mod_inv(&BigInt::mod_pow(mta_avc_out, &self.e, NN), NN); + let mta_e_inv = match mta_e_inv { + None => return false, + Some(c) => c, + }; + + let v = (BigInt::mod_pow(a_enc, &self.s1, NN) + * BigInt::mod_pow(&self.s, N, NN) + * (self.t1.borrow() * N + 1) + * mta_e_inv) + % NN; + + let t_e_inv = BigInt::mod_inv( + &BigInt::mod_pow(&self.t, &self.e, N_tilde), + N_tilde, + ); + let t_e_inv = match t_e_inv { + None => return false, + Some(c) => c, + }; + + let w = (BigInt::mod_pow(h1, &self.t1, N_tilde) + * BigInt::mod_pow(h2, &self.t2, N_tilde) + * t_e_inv) + % N_tilde; + + let Gen = alice_ek.n.borrow() + 1; + let mut values_to_hash = vec![ + &alice_ek.n, + &Gen, + a_enc, + mta_avc_out, + &self.z, + &z_prim, + &self.t, + &v, + &w, + ]; + let e = match check { + Some(_) => { + let X_x_coor = check.unwrap().X.x_coord().unwrap(); + values_to_hash.push(&X_x_coor); + let X_y_coor = check.unwrap().X.y_coord().unwrap(); + values_to_hash.push(&X_y_coor); + let u_x_coor = check.unwrap().u.x_coord().unwrap(); + values_to_hash.push(&u_x_coor); + let u_y_coor = check.unwrap().u.y_coord().unwrap(); + values_to_hash.push(&u_y_coor); + values_to_hash + .into_iter() + .fold(Sha256::new(), |acc, b| acc.chain_bigint(b)) + .result_bigint() + } + None => values_to_hash + .into_iter() + .fold(Sha256::new(), |acc, b| acc.chain_bigint(b)) + .result_bigint(), + }; + + if e != self.e { + return false; + } + + true + } + + pub fn generate( + a_encrypted: &BigInt, + mta_encrypted: &BigInt, + b: &Scalar, + beta_prim: &BigInt, + alice_ek: &EncryptionKey, + dlog_statement: &DLogStatement, + r: &Randomness, + check: bool, + ) -> (BobProof, Option>) { + let round1 = BobZkpRound1::from( + alice_ek, + dlog_statement, + b, + beta_prim, + a_encrypted, + Scalar::::group_order(), + ); + + let Gen = alice_ek.n.borrow() + 1; + let mut values_to_hash = vec![ + &alice_ek.n, + &Gen, + a_encrypted, + mta_encrypted, + &round1.z, + &round1.z_prim, + &round1.t, + &round1.v, + &round1.w, + ]; + let mut check_u = None; + let e = if check { + let (X, u) = { + let ec_gen = Point::generator(); + let alpha = Scalar::::from(&round1.alpha); + (ec_gen * b, ec_gen * alpha) + }; + check_u = Some(u.clone()); + let X_x_coor = X.x_coord().unwrap(); + values_to_hash.push(&X_x_coor); + let X_y_coor = X.y_coord().unwrap(); + values_to_hash.push(&X_y_coor); + let u_x_coor = u.x_coord().unwrap(); + values_to_hash.push(&u_x_coor); + let u_y_coor = u.y_coord().unwrap(); + values_to_hash.push(&u_y_coor); + values_to_hash + .into_iter() + .fold(Sha256::new(), |acc, b| acc.chain_bigint(b)) + .result_bigint() + } else { + values_to_hash + .into_iter() + .fold(Sha256::new(), |acc, b| acc.chain_bigint(b)) + .result_bigint() + }; + + let round2 = BobZkpRound2::from(alice_ek, &round1, &e, b, beta_prim, r); + + ( + BobProof { + t: round1.t.clone(), + z: round1.z.clone(), + e, + s: round2.s, + s1: round2.s1, + s2: round2.s2, + t1: round2.t1, + t2: round2.t2, + }, + check_u, + ) + } +} + +/// Bob's extended proof, adds the knowledge of $`B = g^b \in \mathcal{G}`$ +#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] +pub struct BobProofExt { + proof: BobProof, + u: Point, +} + +#[allow(clippy::too_many_arguments)] +impl BobProofExt { + pub fn verify( + &self, + a_enc: &BigInt, + mta_avc_out: &BigInt, + alice_ek: &EncryptionKey, + dlog_statement: &DLogStatement, + X: &Point, + ) -> bool { + // check basic proof first + if !self.proof.verify( + a_enc, + mta_avc_out, + alice_ek, + dlog_statement, + Some(&BobCheck { + u: self.u.clone(), + X: X.clone(), + }), + ) { + return false; + } + + // fiddle with EC points + let (x1, x2) = { + let ec_gen = Point::generator(); + let s1 = Scalar::::from(&self.proof.s1); + let e = Scalar::::from(&self.proof.e); + (ec_gen * s1, (X * &e) + &self.u) + }; + + if x1 != x2 { + return false; + } + + true + } +} + +/// sample random value of an element of a multiplicative group +pub trait SampleFromMultiplicativeGroup { + fn from_modulo(N: &BigInt) -> BigInt; + fn from_paillier_key(ek: &EncryptionKey) -> BigInt; +} + +impl SampleFromMultiplicativeGroup for BigInt { + fn from_modulo(N: &BigInt) -> BigInt { + let One = BigInt::one(); + loop { + let r = Self::sample_below(N); + if r.gcd(N) == One { + return r; + } + } + } + + fn from_paillier_key(ek: &EncryptionKey) -> BigInt { + Self::from_modulo(ek.n.borrow()) + } +} + +#[cfg(test)] +pub(crate) mod tests { + use super::*; + use paillier::{ + traits::{Encrypt, EncryptWithChosenRandomness, KeyGeneration}, + Add, DecryptionKey, Mul, Paillier, RawCiphertext, RawPlaintext, + }; + + fn generate( + a_encrypted: &BigInt, + mta_encrypted: &BigInt, + b: &Scalar, + beta_prim: &BigInt, + alice_ek: &EncryptionKey, + dlog_statement: &DLogStatement, + r: &Randomness, + ) -> BobProofExt { + // proving a basic proof (with modified hash) + let (bob_proof, u) = BobProof::generate( + a_encrypted, + mta_encrypted, + b, + beta_prim, + alice_ek, + dlog_statement, + r, + true, + ); + + BobProofExt { + proof: bob_proof, + u: u.unwrap(), + } + } + + pub(crate) fn generate_init( + ) -> (DLogStatement, EncryptionKey, DecryptionKey) { + let (ek_tilde, dk_tilde) = Paillier::keypair().keys(); + let one = BigInt::one(); + let phi = (&dk_tilde.p - &one) * (&dk_tilde.q - &one); + let h1 = BigInt::sample_below(&ek_tilde.n); + let (xhi, _) = loop { + let xhi_ = BigInt::sample_below(&phi); + match BigInt::mod_inv(&xhi_, &phi) { + Some(inv) => break (xhi_, inv), + None => continue, + } + }; + let h2 = BigInt::mod_pow(&h1, &xhi, &ek_tilde.n); + + let (ek, dk) = Paillier::keypair().keys(); + let dlog_statement = DLogStatement { + g: h1, + ni: h2, + N: ek_tilde.n, + }; + (dlog_statement, ek, dk) + } + + #[test] + fn alice_zkp() { + let (dlog_statement, ek, _) = generate_init(); + + // Alice's secret value + let a = Scalar::::random().to_bigint(); + let r = BigInt::from_paillier_key(&ek); + let cipher = Paillier::encrypt_with_chosen_randomness( + &ek, + RawPlaintext::from(a.clone()), + &Randomness::from(&r), + ) + .0 + .clone() + .into_owned(); + + let alice_proof = + AliceProof::generate(&a, &cipher, &ek, &dlog_statement, &r); + + assert!(alice_proof.verify(&cipher, &ek, &dlog_statement)); + } + + #[test] + fn bob_zkp() { + let (dlog_statement, ek, _) = generate_init(); + + (0..5).for_each(|_| { + let alice_public_key = &ek; + + // run MtA protocol with different inputs + (0..5).for_each(|_| { + // Simulate Alice + let a = Scalar::::random().to_bigint(); + let encrypted_a = + Paillier::encrypt(alice_public_key, RawPlaintext::from(a)) + .0 + .clone() + .into_owned(); + + // Bob follows MtA + let b = Scalar::::random(); + // E(a) * b + let b_times_enc_a = Paillier::mul( + alice_public_key, + RawCiphertext::from(encrypted_a.clone()), + RawPlaintext::from(&b.to_bigint()), + ); + let beta_prim = BigInt::sample_below(&alice_public_key.n); + let r = Randomness::sample(alice_public_key); + let enc_beta_prim = Paillier::encrypt_with_chosen_randomness( + alice_public_key, + RawPlaintext::from(&beta_prim), + &r, + ); + + let mta_out = Paillier::add( + alice_public_key, + b_times_enc_a, + enc_beta_prim, + ); + + let (bob_proof, _) = BobProof::generate( + &encrypted_a, + &mta_out.0.clone().into_owned(), + &b, + &beta_prim, + alice_public_key, + &dlog_statement, + &r, + false, + ); + assert!(bob_proof.verify( + &encrypted_a, + &mta_out.0.clone().into_owned(), + alice_public_key, + &dlog_statement, + None + )); + + // Bob follows MtAwc + let ec_gen = Point::generator(); + let X = ec_gen * &b; + let bob_proof = generate( + &encrypted_a, + &mta_out.0.clone().into_owned(), + &b, + &beta_prim, + alice_public_key, + &dlog_statement, + &r, + ); + assert!(bob_proof.verify( + &encrypted_a, + &mta_out.0.clone().into_owned(), + alice_public_key, + &dlog_statement, + &X + )); + }); + }); + } +} diff --git a/multi-party-ecdsa/src/utilities/mta/test.rs b/multi-party-ecdsa/src/utilities/mta/test.rs new file mode 100644 index 00000000..c2e6d635 --- /dev/null +++ b/multi-party-ecdsa/src/utilities/mta/test.rs @@ -0,0 +1,22 @@ +use crate::utilities::mta::{ + range_proofs::tests::generate_init, MessageA, MessageB, +}; +use curv::elliptic::curves::{secp256_k1::Secp256k1, Scalar}; + +#[test] +fn test_mta() { + let alice_input = Scalar::::random(); + let (dlog_statement, ek_alice, dk_alice) = generate_init(); + let bob_input = Scalar::::random(); + let (m_a, _) = + MessageA::a(&alice_input, &ek_alice, &[dlog_statement.clone()]); + let (m_b, beta, _, _) = + MessageB::b(&bob_input, &ek_alice, m_a, &[dlog_statement]).unwrap(); + let alpha = m_b + .verify_proofs_get_alpha(&dk_alice, &alice_input) + .expect("wrong dlog or m_b"); + + let left = alpha.0 + beta; + let right = alice_input * bob_input; + assert_eq!(left, right); +} diff --git a/multi-party-ecdsa/src/utilities/zk_pdl/mod.rs b/multi-party-ecdsa/src/utilities/zk_pdl/mod.rs new file mode 100644 index 00000000..452a3fee --- /dev/null +++ b/multi-party-ecdsa/src/utilities/zk_pdl/mod.rs @@ -0,0 +1,275 @@ +#![allow(non_snake_case)] +/* + Multi-party ECDSA + + Copyright 2018 by Kzen Networks + + This file is part of Multi-party ECDSA library + (https://github.com/KZen-networks/multi-party-ecdsa) + + Multi-party ECDSA is free software: you can redistribute + it and/or modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, either + version 3 of the License, or (at your option) any later version. + + @license GPL-3.0+ +*/ + +//! We use the proof as given in protocol 6.1 in https://eprint.iacr.org/2017/552.pdf +//! Statement: (c, pk, Q, G) +//! witness (x, r, sk) such that Q = xG, c = Enc(pk, x, r) and Dec(sk, c) = x. +//! note that because of the range proof, the proof is sound only for x < q/3 + +use std::ops::Shl; + +use curv::{ + arithmetic::traits::*, + cryptographic_primitives::commitments::{ + hash_commitment::HashCommitment, traits::Commitment, + }, + elliptic::curves::{secp256_k1::Secp256k1, Point, Scalar}, + BigInt, +}; +use paillier::{ + Add, Decrypt, DecryptionKey, Encrypt, EncryptionKey, Mul, Paillier, + RawCiphertext, RawPlaintext, +}; +use serde::{Deserialize, Serialize}; +use sha2::Sha256; +use thiserror::Error; +use zk_paillier::zkproofs::{IncorrectProof, RangeProofNi}; + +#[derive(Error, Debug)] +pub enum ZkPdlError { + #[error("zk pdl message2 failed")] + Message2, + #[error("zk pdl finalize failed")] + Finalize, +} + +#[derive(Clone)] +pub struct PDLStatement { + pub ciphertext: BigInt, + pub ek: EncryptionKey, + pub Q: Point, + pub G: Point, +} +#[derive(Clone)] +pub struct PDLWitness { + pub x: Scalar, + pub r: BigInt, + pub dk: DecryptionKey, +} + +#[derive(Debug, Clone)] +pub struct PDLVerifierState { + pub c_tag: BigInt, + pub c_tag_tag: BigInt, + a: BigInt, + b: BigInt, + blindness: BigInt, + q_tag: Point, + c_hat: BigInt, +} + +#[derive(Debug, Clone)] +pub struct PDLProverState { + pub decommit: PDLProverDecommit, + pub alpha: BigInt, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct PDLVerifierFirstMessage { + pub c_tag: BigInt, + pub c_tag_tag: BigInt, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct PDLProverFirstMessage { + pub c_hat: BigInt, + pub range_proof: RangeProofNi, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct PDLVerifierSecondMessage { + pub a: BigInt, + pub b: BigInt, + pub blindness: BigInt, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct PDLProverDecommit { + pub q_hat: Point, + pub blindness: BigInt, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct PDLProverSecondMessage { + pub decommit: PDLProverDecommit, +} + +pub struct Prover {} +pub struct Verifier {} + +impl Verifier { + pub fn message1( + statement: &PDLStatement, + ) -> (PDLVerifierFirstMessage, PDLVerifierState) { + let a_fe = Scalar::::random(); + let a = a_fe.to_bigint(); + let q = Scalar::::group_order(); + let q_sq = q.pow(2); + let b = BigInt::sample_below(&q_sq); + let b_fe = Scalar::::from(&b); + let b_enc = + Paillier::encrypt(&statement.ek, RawPlaintext::from(b.clone())); + let ac = Paillier::mul( + &statement.ek, + RawCiphertext::from(statement.ciphertext.clone()), + RawPlaintext::from(a.clone()), + ); + let c_tag = Paillier::add(&statement.ek, ac, b_enc).0.into_owned(); + let ab_concat = a.clone() + b.clone().shl(a.bit_length()); + let blindness = BigInt::sample_below(q); + let c_tag_tag = HashCommitment::::create_commitment_with_user_defined_randomness( + &ab_concat, &blindness, + ); + let q_tag = &statement.Q * &a_fe + &statement.G * b_fe; + + ( + PDLVerifierFirstMessage { + c_tag: c_tag.clone(), + c_tag_tag: c_tag_tag.clone(), + }, + PDLVerifierState { + c_tag, + c_tag_tag, + a, + b, + blindness, + q_tag, + c_hat: BigInt::zero(), + }, + ) + } + + pub fn message2( + prover_first_messasge: &PDLProverFirstMessage, + statement: &PDLStatement, + state: &mut PDLVerifierState, + ) -> Result { + let decommit_message = PDLVerifierSecondMessage { + a: state.a.clone(), + b: state.b.clone(), + blindness: state.blindness.clone(), + }; + let range_proof_is_ok = + verify_range_proof(statement, &prover_first_messasge.range_proof) + .is_ok(); + state.c_hat = prover_first_messasge.c_hat.clone(); + if range_proof_is_ok { + Ok(decommit_message) + } else { + Err(ZkPdlError::Message2) + } + } + + pub fn finalize( + prover_first_message: &PDLProverFirstMessage, + prover_second_message: &PDLProverSecondMessage, + state: &PDLVerifierState, + ) -> Result<(), ZkPdlError> { + let c_hat_test = HashCommitment::::create_commitment_with_user_defined_randomness( + &BigInt::from_bytes(prover_second_message.decommit.q_hat.to_bytes(true).as_ref()), + &prover_second_message.decommit.blindness, + ); + + if prover_first_message.c_hat == c_hat_test + && prover_second_message.decommit.q_hat == state.q_tag + { + Ok(()) + } else { + Err(ZkPdlError::Finalize) + } + } +} + +impl Prover { + pub fn message1( + witness: &PDLWitness, + statement: &PDLStatement, + verifier_first_message: &PDLVerifierFirstMessage, + ) -> (PDLProverFirstMessage, PDLProverState) { + let c_tag = verifier_first_message.c_tag.clone(); + let alpha = Paillier::decrypt(&witness.dk, &RawCiphertext::from(c_tag)); + let alpha_fe = Scalar::::from(alpha.0.as_ref()); + let q_hat = &statement.G * alpha_fe; + let blindness = + BigInt::sample_below(Scalar::::group_order()); + let c_hat = HashCommitment::::create_commitment_with_user_defined_randomness( + &BigInt::from_bytes(q_hat.to_bytes(true).as_ref()), + &blindness, + ); + // in parallel generate range proof: + let range_proof = generate_range_proof(statement, witness); + ( + PDLProverFirstMessage { c_hat, range_proof }, + PDLProverState { + decommit: PDLProverDecommit { blindness, q_hat }, + alpha: alpha.0.into_owned(), + }, + ) + } + + pub fn message2( + verifier_first_message: &PDLVerifierFirstMessage, + verifier_second_message: &PDLVerifierSecondMessage, + witness: &PDLWitness, + state: &PDLProverState, + ) -> Result { + let ab_concat = &verifier_second_message.a + + verifier_second_message + .b + .clone() + .shl(verifier_second_message.a.bit_length()); // b|a (in the paper it is a|b) + let c_tag_tag_test = + HashCommitment::::create_commitment_with_user_defined_randomness( + &ab_concat, + &verifier_second_message.blindness, + ); + let ax1 = &verifier_second_message.a * witness.x.to_bigint(); + let alpha_test = ax1 + &verifier_second_message.b; + if alpha_test == state.alpha + && verifier_first_message.c_tag_tag == c_tag_tag_test + { + Ok(PDLProverSecondMessage { + decommit: state.decommit.clone(), + }) + } else { + Err(ZkPdlError::Message2) + } + } +} + +fn generate_range_proof( + statement: &PDLStatement, + witness: &PDLWitness, +) -> RangeProofNi { + RangeProofNi::prove( + &statement.ek, + Scalar::::group_order(), + &statement.ciphertext, + &witness.x.to_bigint(), + &witness.r, + ) +} + +fn verify_range_proof( + statement: &PDLStatement, + range_proof: &RangeProofNi, +) -> Result<(), IncorrectProof> { + range_proof.verify(&statement.ek, &statement.ciphertext) +} + +#[cfg(test)] +mod test; diff --git a/multi-party-ecdsa/src/utilities/zk_pdl/test.rs b/multi-party-ecdsa/src/utilities/zk_pdl/test.rs new file mode 100644 index 00000000..3e5a223b --- /dev/null +++ b/multi-party-ecdsa/src/utilities/zk_pdl/test.rs @@ -0,0 +1,64 @@ +#![allow(non_snake_case)] + +use curv::{ + arithmetic::traits::*, + elliptic::curves::{secp256_k1::Secp256k1, Point, Scalar}, + BigInt, +}; +use paillier::{ + core::Randomness, + traits::{EncryptWithChosenRandomness, KeyGeneration}, + Paillier, RawPlaintext, +}; + +use crate::utilities::zk_pdl::{PDLStatement, PDLWitness, Prover, Verifier}; + +#[test] +fn test_zk_pdl() { + // pre-test: + + let (ek, dk) = Paillier::keypair().keys(); + let randomness = Randomness::sample(&ek); + let x = Scalar::::random(); + let x: Scalar = + Scalar::::from(&x.to_bigint().div_floor(&BigInt::from(3))); + + let Q = Point::generator() * &x; + + let c = Paillier::encrypt_with_chosen_randomness( + &ek, + RawPlaintext::from(x.to_bigint()), + &randomness, + ) + .0 + .into_owned(); + let statement = PDLStatement { + ciphertext: c, + ek, + Q, + G: Point::generator().to_point(), + }; + let witness = PDLWitness { + x, + r: randomness.0, + dk, + }; + // + let (verifier_message1, mut verifier_state) = + Verifier::message1(&statement); + let (prover_message1, prover_state) = + Prover::message1(&witness, &statement, &verifier_message1); + let verifier_message2 = + Verifier::message2(&prover_message1, &statement, &mut verifier_state) + .expect(""); + let prover_message2 = Prover::message2( + &verifier_message1, + &verifier_message2, + &witness, + &prover_state, + ) + .expect(""); + let result = + Verifier::finalize(&prover_message1, &prover_message2, &verifier_state); + assert!(result.is_ok()); +} diff --git a/multi-party-ecdsa/src/utilities/zk_pdl_with_slack/mod.rs b/multi-party-ecdsa/src/utilities/zk_pdl_with_slack/mod.rs new file mode 100644 index 00000000..dfa737e1 --- /dev/null +++ b/multi-party-ecdsa/src/utilities/zk_pdl_with_slack/mod.rs @@ -0,0 +1,226 @@ +#![allow(non_snake_case)] +/* + Multi-party ECDSA + + Copyright 2018 by Kzen Networks + + This file is part of Multi-party ECDSA library + (https://github.com/KZen-networks/multi-party-ecdsa) + + Multi-party ECDSA is free software: you can redistribute + it and/or modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, either + version 3 of the License, or (at your option) any later version. + + @license GPL-3.0+ +*/ + +//! We use the proof as given in proof PIi in https://eprint.iacr.org/2016/013.pdf. +//! This proof ws taken from the proof 6.3 (left side ) in https://www.cs.unc.edu/~reiter/papers/2004/IJIS.pdf +//! +//! Statement: (c, pk, Q, G) +//! witness (x, r) such that Q = xG, c = Enc(pk, x, r) +//! note that because of the range proof, the proof has a slack in the range: x +//! in [-q^3, q^3] + +use curv::{ + arithmetic::traits::*, + cryptographic_primitives::hashing::{Digest, DigestExt}, + elliptic::curves::{secp256_k1::Secp256k1, Point, Scalar}, + BigInt, +}; +use paillier::EncryptionKey; +use serde::{Deserialize, Serialize}; +use sha2::Sha256; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum ZkPdlWithSlackError { + #[error("zk pdl with slack verification failed")] + Verify, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct PDLwSlackStatement { + pub ciphertext: BigInt, + pub ek: EncryptionKey, + pub Q: Point, + pub G: Point, + pub h1: BigInt, + pub h2: BigInt, + pub N_tilde: BigInt, +} +#[derive(Clone)] +pub struct PDLwSlackWitness { + pub x: Scalar, + pub r: BigInt, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct PDLwSlackProof { + z: BigInt, + u1: Point, + u2: BigInt, + u3: BigInt, + s1: BigInt, + s2: BigInt, + s3: BigInt, +} + +impl PDLwSlackProof { + pub fn prove( + witness: &PDLwSlackWitness, + statement: &PDLwSlackStatement, + ) -> Self { + let q3 = Scalar::::group_order().pow(3); + let q_N_tilde = Scalar::::group_order() * &statement.N_tilde; + let q3_N_tilde = &q3 * &statement.N_tilde; + + let alpha = BigInt::sample_below(&q3); + let one = BigInt::one(); + let beta = BigInt::sample_range(&one, &(&statement.ek.n - &one)); + let rho = BigInt::sample_below(&q_N_tilde); + let gamma = BigInt::sample_below(&q3_N_tilde); + + let z = commitment_unknown_order( + &statement.h1, + &statement.h2, + &statement.N_tilde, + &witness.x.to_bigint(), + &rho, + ); + let u1 = &statement.G * &Scalar::::from(&alpha); + let u2 = commitment_unknown_order( + &(&statement.ek.n + BigInt::one()), + &beta, + &statement.ek.nn, + &alpha, + &statement.ek.n, + ); + let u3 = commitment_unknown_order( + &statement.h1, + &statement.h2, + &statement.N_tilde, + &alpha, + &gamma, + ); + + let e = Sha256::new() + .chain_bigint(&BigInt::from_bytes( + statement.G.to_bytes(true).as_ref(), + )) + .chain_bigint(&BigInt::from_bytes( + statement.Q.to_bytes(true).as_ref(), + )) + .chain_bigint(&statement.ciphertext) + .chain_bigint(&z) + .chain_bigint(&BigInt::from_bytes(u1.to_bytes(true).as_ref())) + .chain_bigint(&u2) + .chain_bigint(&u3) + .result_bigint(); + + let s1 = &e * witness.x.to_bigint() + alpha; + let s2 = commitment_unknown_order( + &witness.r, + &beta, + &statement.ek.n, + &e, + &BigInt::one(), + ); + let s3 = &e * rho + gamma; + + PDLwSlackProof { + z, + u1, + u2, + u3, + s1, + s2, + s3, + } + } + + pub fn verify( + &self, + statement: &PDLwSlackStatement, + ) -> Result<(), ZkPdlWithSlackError> { + let e = Sha256::new() + .chain_bigint(&BigInt::from_bytes( + statement.G.to_bytes(true).as_ref(), + )) + .chain_bigint(&BigInt::from_bytes( + statement.Q.to_bytes(true).as_ref(), + )) + .chain_bigint(&statement.ciphertext) + .chain_bigint(&self.z) + .chain_bigint(&BigInt::from_bytes(self.u1.to_bytes(true).as_ref())) + .chain_bigint(&self.u2) + .chain_bigint(&self.u3) + .result_bigint(); + + let g_s1 = statement.G.clone() * &Scalar::::from(&self.s1); + let e_fe_neg: Scalar = Scalar::::from( + &(Scalar::::group_order() - &e), + ); + let y_minus_e = &statement.Q * &e_fe_neg; + let u1_test = g_s1 + y_minus_e; + + let u2_test_tmp = commitment_unknown_order( + &(&statement.ek.n + BigInt::one()), + &self.s2, + &statement.ek.nn, + &self.s1, + &statement.ek.n, + ); + let u2_test = commitment_unknown_order( + &u2_test_tmp, + &statement.ciphertext, + &statement.ek.nn, + &BigInt::one(), + &(-&e), + ); + + let u3_test_tmp = commitment_unknown_order( + &statement.h1, + &statement.h2, + &statement.N_tilde, + &self.s1, + &self.s3, + ); + let u3_test = commitment_unknown_order( + &u3_test_tmp, + &self.z, + &statement.N_tilde, + &BigInt::one(), + &(-&e), + ); + + if self.u1 == u1_test && self.u2 == u2_test && self.u3 == u3_test { + Ok(()) + } else { + Err(ZkPdlWithSlackError::Verify) + } + } +} + +pub fn commitment_unknown_order( + h1: &BigInt, + h2: &BigInt, + N_tilde: &BigInt, + x: &BigInt, + r: &BigInt, +) -> BigInt { + let h1_x = BigInt::mod_pow(h1, x, N_tilde); + let h2_r = { + if r < &BigInt::zero() { + let h2_inv = BigInt::mod_inv(h2, N_tilde).unwrap(); + BigInt::mod_pow(&h2_inv, &(-r), N_tilde) + } else { + BigInt::mod_pow(h2, r, N_tilde) + } + }; + BigInt::mod_mul(&h1_x, &h2_r, N_tilde) +} + +#[cfg(test)] +mod test; diff --git a/multi-party-ecdsa/src/utilities/zk_pdl_with_slack/test.rs b/multi-party-ecdsa/src/utilities/zk_pdl_with_slack/test.rs new file mode 100644 index 00000000..227bb4f3 --- /dev/null +++ b/multi-party-ecdsa/src/utilities/zk_pdl_with_slack/test.rs @@ -0,0 +1,134 @@ +#![allow(non_snake_case)] +use crate::utilities::zk_pdl_with_slack::*; +use curv::{ + elliptic::curves::{secp256_k1::Secp256k1, Point, Scalar}, + BigInt, +}; +use paillier::{ + core::Randomness, + traits::{EncryptWithChosenRandomness, KeyGeneration}, + Paillier, RawPlaintext, +}; +use zk_paillier::zkproofs::{CompositeDLogProof, DLogStatement}; + +#[test] +fn test_zk_pdl_with_slack() { + // N_tilde, h1, h2 generation + let (ek_tilde, dk_tilde) = Paillier::keypair().keys(); + // note: safe primes should be used: + // let (ek_tilde, dk_tilde) = Paillier::keypair_safe_primes().keys(); + let one = BigInt::one(); + let phi = (&dk_tilde.p - &one) * (&dk_tilde.q - &one); + let h1 = BigInt::sample_below(&phi); + let S = BigInt::from(2).pow(256_u32); + let xhi = BigInt::sample_below(&S); + let h1_inv = BigInt::mod_inv(&h1, &ek_tilde.n).unwrap(); + let h2 = BigInt::mod_pow(&h1_inv, &xhi, &ek_tilde.n); + let statement = DLogStatement { + N: ek_tilde.n.clone(), + g: h1.clone(), + ni: h2.clone(), + }; + + let composite_dlog_proof = CompositeDLogProof::prove(&statement, &xhi); + + // generate the scalar secret and Paillier encrypt it + let (ek, _dk) = Paillier::keypair().keys(); + // note: safe primes should be used here as well: + // let (ek_tilde, dk_tilde) = Paillier::keypair_safe_primes().keys(); + let randomness = Randomness::sample(&ek); + let x = Scalar::::random(); + + let Q = Point::generator() * &x; + + let c = Paillier::encrypt_with_chosen_randomness( + &ek, + RawPlaintext::from(x.to_bigint()), + &randomness, + ) + .0 + .into_owned(); + + // Generate PDL with slack statement, witness and proof + let pdl_w_slack_statement = PDLwSlackStatement { + ciphertext: c, + ek, + Q, + G: Point::generator().to_point(), + h1, + h2, + N_tilde: ek_tilde.n, + }; + + let pdl_w_slack_witness = PDLwSlackWitness { x, r: randomness.0 }; + + let proof = + PDLwSlackProof::prove(&pdl_w_slack_witness, &pdl_w_slack_statement); + // verify h1,h2, N_tilde + let setup_result = composite_dlog_proof.verify(&statement); + assert!(setup_result.is_ok()); + let result = proof.verify(&pdl_w_slack_statement); + assert!(result.is_ok()); +} + +#[test] +#[should_panic] +fn test_zk_pdl_with_slack_soundness() { + // N_tilde, h1, h2 generation + let (ek_tilde, dk_tilde) = Paillier::keypair().keys(); + // note: safe primes should be used: + // let (ek_tilde, dk_tilde) = Paillier::keypair_safe_primes().keys(); + let one = BigInt::one(); + let phi = (&dk_tilde.p - &one) * (&dk_tilde.q - &one); + let h1 = BigInt::sample_below(&phi); + let S = BigInt::from(2).pow(256_u32); + let xhi = BigInt::sample_below(&S); + let h1_inv = BigInt::mod_inv(&h1, &ek_tilde.n).unwrap(); + let h2 = BigInt::mod_pow(&h1_inv, &xhi, &ek_tilde.n); + let statement = DLogStatement { + N: ek_tilde.n.clone(), + g: h1.clone(), + ni: h2.clone(), + }; + + let composite_dlog_proof = CompositeDLogProof::prove(&statement, &xhi); + + // generate the scalar secret and Paillier encrypt it + let (ek, _dk) = Paillier::keypair().keys(); + // note: safe primes should be used here as well: + // let (ek_tilde, dk_tilde) = Paillier::keypair_safe_primes().keys(); + let randomness = Randomness::sample(&ek); + let x = Scalar::::random(); + + let Q = Point::generator() * &x; + + // here we encrypt x + 1 instead of x: + let c = Paillier::encrypt_with_chosen_randomness( + &ek, + RawPlaintext::from(x.to_bigint() + BigInt::one()), + &randomness, + ) + .0 + .into_owned(); + + // Generate PDL with slack statement, witness and proof + let pdl_w_slack_statement = PDLwSlackStatement { + ciphertext: c, + ek, + Q, + G: Point::generator().to_point(), + h1, + h2, + N_tilde: ek_tilde.n, + }; + + let pdl_w_slack_witness = PDLwSlackWitness { x, r: randomness.0 }; + + let proof = + PDLwSlackProof::prove(&pdl_w_slack_witness, &pdl_w_slack_statement); + // verify h1,h2, N_tilde + let setup_result = composite_dlog_proof.verify(&statement); + assert!(setup_result.is_ok()); + let result = proof.verify(&pdl_w_slack_statement); + assert!(result.is_ok()); +} diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 50318f77..bebb9cb5 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,4 +1,4 @@ [toolchain] -channel = "nightly-2022-05-15" +channel = "stable" components = ["rustfmt", "clippy"] targets = ["wasm32-unknown-unknown"] \ No newline at end of file diff --git a/rustfmt.toml b/rustfmt.toml index f4d7ad63..b6825604 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,23 +1,3 @@ # Basic -hard_tabs = true -max_width = 100 -use_small_heuristics = "Max" -# Imports -imports_granularity = "Crate" -reorder_imports = true -# Consistency -newline_style = "Unix" -# Format comments -comment_width = 100 -wrap_comments = true -# Misc -chain_width = 80 -spaces_around_ranges = false -binop_separator = "Back" -reorder_impl_items = false -match_arm_leading_pipes = "Preserve" -match_arm_blocks = false -match_block_trailing_comma = true -trailing_comma = "Vertical" -trailing_semicolon = false -use_field_init_shorthand = true \ No newline at end of file +hard_tabs = false +max_width = 80 \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 04466f89..49f84ce4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,24 +1,23 @@ /* - CGGMP Threshold ECDSA + CGGMP Threshold ECDSA - Copyright 2022 by Webb Technologies. + Copyright 2022 by Webb Technologies. - This file is part of cggmp library - (https://github.com/webb-tools/cggmp-threshold-ecdsa) + This file is part of cggmp library + (https://github.com/webb-tools/cggmp-threshold-ecdsa) - This file is derived/inspired from Multi-party ECDSA library - (https://github.com/KZen-networks/multi-party-ecdsa) + This file is derived/inspired from Multi-party ECDSA library + (https://github.com/KZen-networks/multi-party-ecdsa) - cggmp-threshold-ecdsa is free software: you can redistribute - it and/or modify it under the terms of the GNU General Public - License as published by the Free Software Foundation, either - version 3 of the License, or (at your option) any later version. + cggmp-threshold-ecdsa is free software: you can redistribute + it and/or modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, either + version 3 of the License, or (at your option) any later version. - @license GPL-3.0+ + @license GPL-3.0+ */ #![allow(non_snake_case)] -#![feature(box_patterns)] use serde::{Deserialize, Serialize}; @@ -29,31 +28,33 @@ pub mod sign; pub mod traits; pub mod utilities; +pub use multi_party_ecdsa as mpc_ecdsa; + #[derive(Copy, PartialEq, Eq, Clone, Debug)] pub enum Error { - InvalidKey, - InvalidSS, - InvalidCom, - InvalidSig, - Phase5BadSum, - Phase6Error, - AffineWithGroupComRangeProofError, + InvalidKey, + InvalidSS, + InvalidCom, + InvalidSig, + Phase5BadSum, + Phase6Error, + AffineWithGroupComRangeProofError, } #[derive(Clone, Debug)] pub struct ErrorType { - pub error_type: String, - pub bad_actors: Vec, - pub data: Vec, + pub error_type: String, + pub bad_actors: Vec, + pub data: Vec, } #[derive(Clone, Debug, Serialize, Deserialize)] pub struct ProofVerificationErrorData { - proof_symbol: String, - verifying_party: u16, + proof_symbol: String, + verifying_party: u16, } #[derive(Clone, Debug, Serialize, Deserialize)] pub struct NoOfflineStageErrorData { - l: usize, + l: usize, } diff --git a/src/party_i.rs b/src/party_i.rs index 42634ad0..82ea208a 100644 --- a/src/party_i.rs +++ b/src/party_i.rs @@ -1,244 +1,284 @@ /* - Multi-party ECDSA + Multi-party ECDSA - Copyright 2018 by Kzen Networks + Copyright 2018 by Kzen Networks - This file is part of Multi-party ECDSA library - (https://github.com/KZen-networks/multi-party-ecdsa) + This file is part of Multi-party ECDSA library + (https://github.com/KZen-networks/multi-party-ecdsa) - Multi-party ECDSA is free software: you can redistribute - it and/or modify it under the terms of the GNU General Public - License as published by the Free Software Foundation, either - version 3 of the License, or (at your option) any later version. + Multi-party ECDSA is free software: you can redistribute + it and/or modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, either + version 3 of the License, or (at your option) any later version. - @license GPL-3.0+ + @license GPL-3.0+ */ use std::fmt::Debug; use centipede::juggling::{ - proof_system::{Helgamalsegmented, Witness}, - segmentation::Msegmentation, + proof_system::{Helgamalsegmented, Witness}, + segmentation::Msegmentation, }; use curv::{ - arithmetic::traits::*, - cryptographic_primitives::{ - commitments::{hash_commitment::HashCommitment, traits::Commitment}, - proofs::{sigma_correct_homomorphic_elgamal_enc::*, sigma_dlog::DLogProof}, - secret_sharing::feldman_vss::VerifiableSS, - }, - elliptic::curves::{secp256_k1::Secp256k1, Curve, Point, Scalar}, - BigInt, + arithmetic::traits::*, + cryptographic_primitives::{ + commitments::{hash_commitment::HashCommitment, traits::Commitment}, + proofs::{ + sigma_correct_homomorphic_elgamal_enc::*, sigma_dlog::DLogProof, + }, + secret_sharing::feldman_vss::VerifiableSS, + }, + elliptic::curves::{secp256_k1::Secp256k1, Curve, Point, Scalar}, + BigInt, }; use sha2::Sha256; use crate::Error::{self, InvalidSig, Phase5BadSum, Phase6Error}; use paillier::{ - Decrypt, DecryptionKey, EncryptionKey, KeyGeneration, Paillier, RawCiphertext, RawPlaintext, + Decrypt, DecryptionKey, EncryptionKey, KeyGeneration, Paillier, + RawCiphertext, RawPlaintext, }; use serde::{Deserialize, Serialize}; -use zk_paillier::zkproofs::{CompositeDLogProof, DLogStatement, NiCorrectKeyProof}; +use zk_paillier::zkproofs::{ + CompositeDLogProof, DLogStatement, NiCorrectKeyProof, +}; use crate::{ - utilities::zk_pdl_with_slack::{PDLwSlackProof, PDLwSlackStatement, PDLwSlackWitness}, - ErrorType, + utilities::zk_pdl_with_slack::{ + PDLwSlackProof, PDLwSlackStatement, PDLwSlackWitness, + }, + ErrorType, }; use curv::cryptographic_primitives::proofs::sigma_valid_pedersen::PedersenProof; use std::convert::TryInto; +pub use crate::mpc_ecdsa::gg_2020::party_i::{ + KeyGenBroadcastMessage1, KeyGenDecommitMessage1, Parameters, SharedKeys, + SignBroadcastPhase1, SignDecommitPhase1, SignatureRecid, +}; + const SECURITY: usize = 256; const PAILLIER_MIN_BIT_LENGTH: usize = 2047; const PAILLIER_MAX_BIT_LENGTH: usize = 2048; -#[derive(Debug, Serialize, Deserialize, Clone)] -pub struct Parameters { - pub threshold: u16, //t - pub share_count: u16, //n -} - #[derive(Serialize, Deserialize, Clone, Debug)] pub struct Keys { - pub u_i: Scalar, - pub y_i: Point, - // Paillier keys - pub dk: DecryptionKey, - pub ek: EncryptionKey, - pub party_index: usize, - pub N_tilde: BigInt, - pub h1: BigInt, - pub h2: BigInt, - pub xhi: BigInt, - pub xhi_inv: BigInt, + pub u_i: Scalar, + pub y_i: Point, + // Paillier keys + pub dk: DecryptionKey, + pub ek: EncryptionKey, + pub party_index: usize, + pub N_tilde: BigInt, + pub h1: BigInt, + pub h2: BigInt, + pub xhi: BigInt, + pub xhi_inv: BigInt, } #[derive(Clone, Debug, Serialize, Deserialize)] pub struct PartyPrivate { - u_i: Scalar, - x_i: Scalar, - dk: DecryptionKey, -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct KeyGenBroadcastMessage1 { - pub e: EncryptionKey, - pub dlog_statement: DLogStatement, - pub com: BigInt, - pub correct_key_proof: NiCorrectKeyProof, - pub composite_dlog_proof_base_h1: CompositeDLogProof, - pub composite_dlog_proof_base_h2: CompositeDLogProof, -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct KeyGenDecommitMessage1 { - pub blind_factor: BigInt, - pub y_i: Point, -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct SharedKeys { - pub y: Point, - pub x_i: Scalar, + u_i: Scalar, + x_i: Scalar, + dk: DecryptionKey, } #[derive(Clone, Debug, Serialize, Deserialize)] pub struct SignKeys { - pub w_i: Scalar, - pub g_w_i: Point, - pub k_i: Scalar, - pub gamma_i: Scalar, - pub g_gamma_i: Point, + pub w_i: Scalar, + pub g_w_i: Point, + pub k_i: Scalar, + pub gamma_i: Scalar, + pub g_gamma_i: Point, } +/* #[derive(Clone, Debug, Serialize, Deserialize)] pub struct SignBroadcastPhase1 { - pub com: BigInt, + pub com: BigInt, } +*/ +/* #[derive(Clone, Debug, Serialize, Deserialize)] pub struct SignDecommitPhase1 { - pub blind_factor: BigInt, - pub g_gamma_i: Point, + pub blind_factor: BigInt, + pub g_gamma_i: Point, } +*/ #[derive(Clone, Debug, Serialize, Deserialize)] pub struct LocalSignature { - pub r: Scalar, - pub R: Point, - pub s_i: Scalar, - pub m: BigInt, - pub y: Point, + pub r: Scalar, + pub R: Point, + pub s_i: Scalar, + pub m: BigInt, + pub y: Point, } +/* #[derive(Clone, Debug, Serialize, Deserialize)] pub struct SignatureRecid { - pub r: Scalar, - pub s: Scalar, - pub recid: u8, + pub r: Scalar, + pub s: Scalar, + pub recid: u8, } +*/ pub fn generate_h1_h2_N_tilde() -> (BigInt, BigInt, BigInt, BigInt, BigInt) { - // note, should be safe primes: - // let (ek_tilde, dk_tilde) = Paillier::keypair_safe_primes().keys();; - let (ek_tilde, dk_tilde) = Paillier::keypair().keys(); - let one = BigInt::one(); - let phi = (&dk_tilde.p - &one) * (&dk_tilde.q - &one); - let h1 = BigInt::sample_below(&ek_tilde.n); - let (mut xhi, mut xhi_inv) = loop { - let xhi_ = BigInt::sample_below(&phi); - match BigInt::mod_inv(&xhi_, &phi) { - Some(inv) => break (xhi_, inv), - None => continue, - } - }; - let h2 = BigInt::mod_pow(&h1, &xhi, &ek_tilde.n); - xhi = BigInt::sub(&phi, &xhi); - xhi_inv = BigInt::sub(&phi, &xhi_inv); - - (ek_tilde.n, h1, h2, xhi, xhi_inv) + // note, should be safe primes: + // let (ek_tilde, dk_tilde) = Paillier::keypair_safe_primes().keys();; + let (ek_tilde, dk_tilde) = Paillier::keypair().keys(); + let one = BigInt::one(); + let phi = (&dk_tilde.p - &one) * (&dk_tilde.q - &one); + let h1 = BigInt::sample_below(&ek_tilde.n); + let (mut xhi, mut xhi_inv) = loop { + let xhi_ = BigInt::sample_below(&phi); + match BigInt::mod_inv(&xhi_, &phi) { + Some(inv) => break (xhi_, inv), + None => continue, + } + }; + let h2 = BigInt::mod_pow(&h1, &xhi, &ek_tilde.n); + xhi = BigInt::sub(&phi, &xhi); + xhi_inv = BigInt::sub(&phi, &xhi_inv); + + (ek_tilde.n, h1, h2, xhi, xhi_inv) } impl Keys { - pub fn create(index: usize) -> Self { - let u = Scalar::::random(); - let y = Point::generator() * &u; - let (ek, dk) = Paillier::keypair().keys(); - let (N_tilde, h1, h2, xhi, xhi_inv) = generate_h1_h2_N_tilde(); - - Self { u_i: u, y_i: y, dk, ek, party_index: index, N_tilde, h1, h2, xhi, xhi_inv } - } - - // we recommend using safe primes if the code is used in production - pub fn create_safe_prime(index: usize) -> Self { - let u = Scalar::::random(); - let y = Point::generator() * &u; - - let (ek, dk) = Paillier::keypair_safe_primes().keys(); - let (N_tilde, h1, h2, xhi, xhi_inv) = generate_h1_h2_N_tilde(); - - Self { u_i: u, y_i: y, dk, ek, party_index: index, N_tilde, h1, h2, xhi, xhi_inv } - } - pub fn create_from(u: Scalar, index: usize) -> Self { - let y = Point::generator() * &u; - let (ek, dk) = Paillier::keypair().keys(); - let (N_tilde, h1, h2, xhi, xhi_inv) = generate_h1_h2_N_tilde(); - - Self { u_i: u, y_i: y, dk, ek, party_index: index, N_tilde, h1, h2, xhi, xhi_inv } - } - - pub fn phase1_broadcast_phase3_proof_of_correct_key_proof_of_correct_h1h2( - &self, - ) -> (KeyGenBroadcastMessage1, KeyGenDecommitMessage1) { - let blind_factor = BigInt::sample(SECURITY); - let correct_key_proof = NiCorrectKeyProof::proof(&self.dk, None); - - let dlog_statement_base_h1 = - DLogStatement { N: self.N_tilde.clone(), g: self.h1.clone(), ni: self.h2.clone() }; - let dlog_statement_base_h2 = - DLogStatement { N: self.N_tilde.clone(), g: self.h2.clone(), ni: self.h1.clone() }; - - let composite_dlog_proof_base_h1 = - CompositeDLogProof::prove(&dlog_statement_base_h1, &self.xhi); - let composite_dlog_proof_base_h2 = - CompositeDLogProof::prove(&dlog_statement_base_h2, &self.xhi_inv); - - let com = HashCommitment::::create_commitment_with_user_defined_randomness( + pub fn create(index: usize) -> Self { + let u = Scalar::::random(); + let y = Point::generator() * &u; + let (ek, dk) = Paillier::keypair().keys(); + let (N_tilde, h1, h2, xhi, xhi_inv) = generate_h1_h2_N_tilde(); + + Self { + u_i: u, + y_i: y, + dk, + ek, + party_index: index, + N_tilde, + h1, + h2, + xhi, + xhi_inv, + } + } + + // we recommend using safe primes if the code is used in production + pub fn create_safe_prime(index: usize) -> Self { + let u = Scalar::::random(); + let y = Point::generator() * &u; + + let (ek, dk) = Paillier::keypair_safe_primes().keys(); + let (N_tilde, h1, h2, xhi, xhi_inv) = generate_h1_h2_N_tilde(); + + Self { + u_i: u, + y_i: y, + dk, + ek, + party_index: index, + N_tilde, + h1, + h2, + xhi, + xhi_inv, + } + } + pub fn create_from(u: Scalar, index: usize) -> Self { + let y = Point::generator() * &u; + let (ek, dk) = Paillier::keypair().keys(); + let (N_tilde, h1, h2, xhi, xhi_inv) = generate_h1_h2_N_tilde(); + + Self { + u_i: u, + y_i: y, + dk, + ek, + party_index: index, + N_tilde, + h1, + h2, + xhi, + xhi_inv, + } + } + + pub fn phase1_broadcast_phase3_proof_of_correct_key_proof_of_correct_h1h2( + &self, + ) -> (KeyGenBroadcastMessage1, KeyGenDecommitMessage1) { + let blind_factor = BigInt::sample(SECURITY); + let correct_key_proof = NiCorrectKeyProof::proof(&self.dk, None); + + let dlog_statement_base_h1 = DLogStatement { + N: self.N_tilde.clone(), + g: self.h1.clone(), + ni: self.h2.clone(), + }; + let dlog_statement_base_h2 = DLogStatement { + N: self.N_tilde.clone(), + g: self.h2.clone(), + ni: self.h1.clone(), + }; + + let composite_dlog_proof_base_h1 = + CompositeDLogProof::prove(&dlog_statement_base_h1, &self.xhi); + let composite_dlog_proof_base_h2 = + CompositeDLogProof::prove(&dlog_statement_base_h2, &self.xhi_inv); + + let com = HashCommitment::::create_commitment_with_user_defined_randomness( &BigInt::from_bytes(self.y_i.to_bytes(true).as_ref()), &blind_factor, ); - let bcm1 = KeyGenBroadcastMessage1 { - e: self.ek.clone(), - dlog_statement: dlog_statement_base_h1, - com, - correct_key_proof, - composite_dlog_proof_base_h1, - composite_dlog_proof_base_h2, - }; - let decom1 = KeyGenDecommitMessage1 { blind_factor, y_i: self.y_i.clone() }; - (bcm1, decom1) - } - - #[allow(clippy::type_complexity)] - pub fn phase1_verify_com_phase3_verify_correct_key_verify_dlog_phase2_distribute( - &self, - params: &Parameters, - decom_vec: &[KeyGenDecommitMessage1], - bc1_vec: &[KeyGenBroadcastMessage1], - ) -> Result<(VerifiableSS, Vec>, usize), ErrorType> { - let mut bad_actors_vec = Vec::new(); - // test length: - assert_eq!(decom_vec.len(), usize::from(params.share_count)); - assert_eq!(bc1_vec.len(), usize::from(params.share_count)); - // test paillier correct key, h1,h2 correct generation and test decommitments - let correct_key_correct_decom_all = (0..bc1_vec.len()) - .map(|i| { - let dlog_statement_base_h2 = DLogStatement { - N: bc1_vec[i].dlog_statement.N.clone(), - g: bc1_vec[i].dlog_statement.ni.clone(), - ni: bc1_vec[i].dlog_statement.g.clone(), - }; - let test_res = + let bcm1 = KeyGenBroadcastMessage1 { + e: self.ek.clone(), + dlog_statement: dlog_statement_base_h1, + com, + correct_key_proof, + composite_dlog_proof_base_h1, + composite_dlog_proof_base_h2, + }; + let decom1 = KeyGenDecommitMessage1 { + blind_factor, + y_i: self.y_i.clone(), + }; + (bcm1, decom1) + } + + #[allow(clippy::type_complexity)] + pub fn phase1_verify_com_phase3_verify_correct_key_verify_dlog_phase2_distribute( + &self, + params: &Parameters, + decom_vec: &[KeyGenDecommitMessage1], + bc1_vec: &[KeyGenBroadcastMessage1], + ) -> Result< + ( + VerifiableSS, + Vec>, + usize, + ), + ErrorType, + > { + let mut bad_actors_vec = Vec::new(); + // test length: + assert_eq!(decom_vec.len(), usize::from(params.share_count)); + assert_eq!(bc1_vec.len(), usize::from(params.share_count)); + // test paillier correct key, h1,h2 correct generation and test + // decommitments + let correct_key_correct_decom_all = (0..bc1_vec.len()) + .map(|i| { + let dlog_statement_base_h2 = DLogStatement { + N: bc1_vec[i].dlog_statement.N.clone(), + g: bc1_vec[i].dlog_statement.ni.clone(), + ni: bc1_vec[i].dlog_statement.g.clone(), + }; + let test_res = HashCommitment::::create_commitment_with_user_defined_randomness( &BigInt::from_bytes(&decom_vec[i].y_i.to_bytes(true)), &decom_vec[i].blind_factor, @@ -256,217 +296,271 @@ impl Keys { .composite_dlog_proof_base_h2 .verify(&dlog_statement_base_h2) .is_ok(); - if !test_res { - bad_actors_vec.push(i); - false - } else { - true - } - }) - .all(|x| x); - - let err_type = ErrorType { - error_type: "invalid key".to_string(), - bad_actors: bad_actors_vec, - data: Vec::new(), - }; - - let (vss_scheme, secret_shares) = - VerifiableSS::share(params.threshold, params.share_count, &self.u_i); - if correct_key_correct_decom_all { - Ok((vss_scheme, secret_shares.to_vec(), self.party_index)) - } else { - Err(err_type) - } - } - - pub fn phase2_verify_vss_construct_keypair_phase3_pok_dlog( - &self, - params: &Parameters, - y_vec: &[Point], - secret_shares_vec: &[Scalar], - vss_scheme_vec: &[VerifiableSS], - index: usize, - ) -> Result<(SharedKeys, DLogProof), ErrorType> { - let mut bad_actors_vec = Vec::new(); - assert_eq!(y_vec.len(), usize::from(params.share_count)); - assert_eq!(secret_shares_vec.len(), usize::from(params.share_count)); - assert_eq!(vss_scheme_vec.len(), usize::from(params.share_count)); - - let correct_ss_verify = (0..y_vec.len()) - .map(|i| { - let res = vss_scheme_vec[i] - .validate_share(&secret_shares_vec[i], index.try_into().unwrap()) - .is_ok() && vss_scheme_vec[i].commitments[0] == y_vec[i]; - if !res { - bad_actors_vec.push(i); - false - } else { - true - } - }) - .all(|x| x); - - let err_type = ErrorType { - error_type: "invalid vss".to_string(), - bad_actors: bad_actors_vec, - data: Vec::new(), - }; - - if correct_ss_verify { - let (head, tail) = y_vec.split_at(1); - let y = tail.iter().fold(head[0].clone(), |acc, x| acc + x); - - let x_i = secret_shares_vec.iter().fold(Scalar::::zero(), |acc, x| acc + x); - let dlog_proof = DLogProof::prove(&x_i); - Ok((SharedKeys { y, x_i }, dlog_proof)) - } else { - Err(err_type) - } - } - - pub fn get_commitments_to_xi( - vss_scheme_vec: &[VerifiableSS], - ) -> Vec> { - let len = vss_scheme_vec.len(); - let (head, tail) = vss_scheme_vec.split_at(1); - let mut global_coefficients = head[0].commitments.clone(); - for vss in tail { - for (i, coefficient_commitment) in vss.commitments.iter().enumerate() { - global_coefficients[i] = &global_coefficients[i] + &*coefficient_commitment; - } - } - - let global_vss = VerifiableSS { - parameters: vss_scheme_vec[0].parameters.clone(), - commitments: global_coefficients, - proof: vss_scheme_vec[0].proof.clone(), - }; - (1..=len) - .map(|i| global_vss.get_point_commitment(i.try_into().unwrap())) - .collect::>>() - } - - pub fn update_commitments_to_xi( - comm: &Point, - vss_scheme: &VerifiableSS, - index: usize, - s: &[usize], - ) -> Point { - let s: Vec = s.iter().map(|&i| i.try_into().unwrap()).collect(); - let li = VerifiableSS::::map_share_to_new_params( - &vss_scheme.parameters, - index.try_into().unwrap(), - s.as_slice(), - ); - comm * &li - } - - pub fn verify_dlog_proofs_check_against_vss( - params: &Parameters, - dlog_proofs_vec: &[DLogProof], - y_vec: &[Point], - vss_vec: &[VerifiableSS], - ) -> Result<(), ErrorType> { - let mut bad_actors_vec = Vec::new(); - assert_eq!(y_vec.len(), usize::from(params.share_count)); - assert_eq!(dlog_proofs_vec.len(), usize::from(params.share_count)); - let xi_commitments = Keys::get_commitments_to_xi(vss_vec); - let xi_dlog_verify = (0..y_vec.len()) - .map(|i| { - let ver_res = DLogProof::verify(&dlog_proofs_vec[i]).is_ok(); - let verify_against_vss = xi_commitments[i] == dlog_proofs_vec[i].pk; - if !ver_res || !verify_against_vss { - bad_actors_vec.push(i); - false - } else { - true - } - }) - .all(|x| x); - - let err_type = ErrorType { - error_type: "bad dlog proof".to_string(), - bad_actors: bad_actors_vec, - data: Vec::new(), - }; - - if xi_dlog_verify { - Ok(()) - } else { - Err(err_type) - } - } + if !test_res { + bad_actors_vec.push(i); + false + } else { + true + } + }) + .all(|x| x); + + let err_type = ErrorType { + error_type: "invalid key".to_string(), + bad_actors: bad_actors_vec, + data: Vec::new(), + }; + + let (vss_scheme, secret_shares) = VerifiableSS::share( + params.threshold, + params.share_count, + &self.u_i, + ); + if correct_key_correct_decom_all { + Ok((vss_scheme, secret_shares.to_vec(), self.party_index)) + } else { + Err(err_type) + } + } + + pub fn phase2_verify_vss_construct_keypair_phase3_pok_dlog( + &self, + params: &Parameters, + y_vec: &[Point], + secret_shares_vec: &[Scalar], + vss_scheme_vec: &[VerifiableSS], + index: usize, + ) -> Result<(SharedKeys, DLogProof), ErrorType> { + let mut bad_actors_vec = Vec::new(); + assert_eq!(y_vec.len(), usize::from(params.share_count)); + assert_eq!(secret_shares_vec.len(), usize::from(params.share_count)); + assert_eq!(vss_scheme_vec.len(), usize::from(params.share_count)); + + let correct_ss_verify = (0..y_vec.len()) + .map(|i| { + let res = vss_scheme_vec[i] + .validate_share( + &secret_shares_vec[i], + index.try_into().unwrap(), + ) + .is_ok() + && vss_scheme_vec[i].commitments[0] == y_vec[i]; + if !res { + bad_actors_vec.push(i); + false + } else { + true + } + }) + .all(|x| x); + + let err_type = ErrorType { + error_type: "invalid vss".to_string(), + bad_actors: bad_actors_vec, + data: Vec::new(), + }; + + if correct_ss_verify { + let (head, tail) = y_vec.split_at(1); + let y = tail.iter().fold(head[0].clone(), |acc, x| acc + x); + + let x_i = secret_shares_vec + .iter() + .fold(Scalar::::zero(), |acc, x| acc + x); + let dlog_proof = DLogProof::prove(&x_i); + Ok((SharedKeys { y, x_i }, dlog_proof)) + } else { + Err(err_type) + } + } + + pub fn get_commitments_to_xi( + vss_scheme_vec: &[VerifiableSS], + ) -> Vec> { + let len = vss_scheme_vec.len(); + let (head, tail) = vss_scheme_vec.split_at(1); + let mut global_coefficients = head[0].commitments.clone(); + for vss in tail { + for (i, coefficient_commitment) in + vss.commitments.iter().enumerate() + { + global_coefficients[i] = + &global_coefficients[i] + coefficient_commitment; + } + } + + let global_vss = VerifiableSS { + parameters: vss_scheme_vec[0].parameters.clone(), + commitments: global_coefficients, + proof: vss_scheme_vec[0].proof.clone(), + }; + (1..=len) + .map(|i| global_vss.get_point_commitment(i.try_into().unwrap())) + .collect::>>() + } + + pub fn update_commitments_to_xi( + comm: &Point, + vss_scheme: &VerifiableSS, + index: usize, + s: &[usize], + ) -> Point { + let s: Vec = s.iter().map(|&i| i.try_into().unwrap()).collect(); + let li = + VerifiableSS::::map_share_to_new_params( + &vss_scheme.parameters, + index.try_into().unwrap(), + s.as_slice(), + ); + comm * &li + } + + pub fn verify_dlog_proofs_check_against_vss( + params: &Parameters, + dlog_proofs_vec: &[DLogProof], + y_vec: &[Point], + vss_vec: &[VerifiableSS], + ) -> Result<(), ErrorType> { + let mut bad_actors_vec = Vec::new(); + assert_eq!(y_vec.len(), usize::from(params.share_count)); + assert_eq!(dlog_proofs_vec.len(), usize::from(params.share_count)); + let xi_commitments = Keys::get_commitments_to_xi(vss_vec); + let xi_dlog_verify = (0..y_vec.len()) + .map(|i| { + let ver_res = DLogProof::verify(&dlog_proofs_vec[i]).is_ok(); + let verify_against_vss = + xi_commitments[i] == dlog_proofs_vec[i].pk; + if !ver_res || !verify_against_vss { + bad_actors_vec.push(i); + false + } else { + true + } + }) + .all(|x| x); + + let err_type = ErrorType { + error_type: "bad dlog proof".to_string(), + bad_actors: bad_actors_vec, + data: Vec::new(), + }; + + if xi_dlog_verify { + Ok(()) + } else { + Err(err_type) + } + } } impl PartyPrivate { - pub fn set_private(key: Keys, shared_key: SharedKeys) -> Self { - Self { u_i: key.u_i, x_i: shared_key.x_i, dk: key.dk } - } - - pub fn y_i(&self) -> Point { - let g = Point::generator(); - g * &self.u_i - } - - pub fn decrypt(&self, ciphertext: BigInt) -> RawPlaintext { - Paillier::decrypt(&self.dk, &RawCiphertext::from(ciphertext)) - } - - pub fn refresh_private_key(&self, factor: &Scalar, index: usize) -> Keys { - let u: Scalar = &self.u_i + factor; - let y = Point::generator() * &u; - let (ek, dk) = Paillier::keypair().keys(); - - let (N_tilde, h1, h2, xhi, xhi_inv) = generate_h1_h2_N_tilde(); - - Keys { u_i: u, y_i: y, dk, ek, party_index: index, N_tilde, h1, h2, xhi, xhi_inv } - } - - // we recommend using safe primes if the code is used in production - pub fn refresh_private_key_safe_prime(&self, factor: &Scalar, index: usize) -> Keys { - let u: Scalar = &self.u_i + factor; - let y = Point::generator() * &u; - let (ek, dk) = Paillier::keypair_safe_primes().keys(); - - let (N_tilde, h1, h2, xhi, xhi_inv) = generate_h1_h2_N_tilde(); - - Keys { u_i: u, y_i: y, dk, ek, party_index: index, N_tilde, h1, h2, xhi, xhi_inv } - } - - // used for verifiable recovery - pub fn to_encrypted_segment( - &self, - segment_size: usize, - num_of_segments: usize, - pub_ke_y: &Point, - g: &Point, - ) -> (Witness, Helgamalsegmented) { - Msegmentation::to_encrypted_segments(&self.u_i, &segment_size, num_of_segments, pub_ke_y, g) - } - - pub fn update_private_key( - &self, - factor_u_i: &Scalar, - factor_x_i: &Scalar, - ) -> Self { - PartyPrivate { - u_i: &self.u_i + factor_u_i, - x_i: &self.x_i + factor_x_i, - dk: self.dk.clone(), - } - } + pub fn set_private(key: Keys, shared_key: SharedKeys) -> Self { + Self { + u_i: key.u_i, + x_i: shared_key.x_i, + dk: key.dk, + } + } + + pub fn y_i(&self) -> Point { + let g = Point::generator(); + g * &self.u_i + } + + pub fn decrypt(&self, ciphertext: BigInt) -> RawPlaintext { + Paillier::decrypt(&self.dk, &RawCiphertext::from(ciphertext)) + } + + pub fn refresh_private_key( + &self, + factor: &Scalar, + index: usize, + ) -> Keys { + let u: Scalar = &self.u_i + factor; + let y = Point::generator() * &u; + let (ek, dk) = Paillier::keypair().keys(); + + let (N_tilde, h1, h2, xhi, xhi_inv) = generate_h1_h2_N_tilde(); + + Keys { + u_i: u, + y_i: y, + dk, + ek, + party_index: index, + N_tilde, + h1, + h2, + xhi, + xhi_inv, + } + } + + // we recommend using safe primes if the code is used in production + pub fn refresh_private_key_safe_prime( + &self, + factor: &Scalar, + index: usize, + ) -> Keys { + let u: Scalar = &self.u_i + factor; + let y = Point::generator() * &u; + let (ek, dk) = Paillier::keypair_safe_primes().keys(); + + let (N_tilde, h1, h2, xhi, xhi_inv) = generate_h1_h2_N_tilde(); + + Keys { + u_i: u, + y_i: y, + dk, + ek, + party_index: index, + N_tilde, + h1, + h2, + xhi, + xhi_inv, + } + } + + // used for verifiable recovery + pub fn to_encrypted_segment( + &self, + segment_size: usize, + num_of_segments: usize, + pub_ke_y: &Point, + g: &Point, + ) -> (Witness, Helgamalsegmented) { + Msegmentation::to_encrypted_segments( + &self.u_i, + &segment_size, + num_of_segments, + pub_ke_y, + g, + ) + } + + pub fn update_private_key( + &self, + factor_u_i: &Scalar, + factor_x_i: &Scalar, + ) -> Self { + PartyPrivate { + u_i: &self.u_i + factor_u_i, + x_i: &self.x_i + factor_x_i, + dk: self.dk.clone(), + } + } } impl SignKeys { - pub fn g_w_vec( - pk_vec: &[Point], - s: &[usize], - vss_scheme: &VerifiableSS, - ) -> Vec> { - let s: Vec = s.iter().map(|&i| i.try_into().unwrap()).collect(); - // TODO: check bounds - (0..s.len()) + pub fn g_w_vec( + pk_vec: &[Point], + s: &[usize], + vss_scheme: &VerifiableSS, + ) -> Vec> { + let s: Vec = s.iter().map(|&i| i.try_into().unwrap()).collect(); + // TODO: check bounds + (0..s.len()) .map(|i| { let li = VerifiableSS::::map_share_to_new_params( &vss_scheme.parameters, @@ -476,96 +570,118 @@ impl SignKeys { &pk_vec[s[i] as usize] * &li }) .collect::>>() - } - - pub fn create( - private_x_i: &Scalar, - vss_scheme: &VerifiableSS, - index: usize, - s: &[usize], - ) -> Self { - let s: Vec = s.iter().map(|&i| i.try_into().unwrap()).collect(); - let li = VerifiableSS::::map_share_to_new_params( - &vss_scheme.parameters, - index.try_into().unwrap(), - s.as_slice(), - ); - let w_i = li * private_x_i; - let g = Point::generator(); - let g_w_i = g * &w_i; - let gamma_i = Scalar::::random(); - let g_gamma_i = g * &gamma_i; - let k_i = Scalar::::random(); - Self { w_i, g_w_i, k_i, gamma_i, g_gamma_i } - } - - pub fn phase1_broadcast(&self) -> (SignBroadcastPhase1, SignDecommitPhase1) { - let blind_factor = BigInt::sample(SECURITY); - let g = Point::generator(); - let g_gamma_i = g * &self.gamma_i; - let com = HashCommitment::::create_commitment_with_user_defined_randomness( + } + + pub fn create( + private_x_i: &Scalar, + vss_scheme: &VerifiableSS, + index: usize, + s: &[usize], + ) -> Self { + let s: Vec = s.iter().map(|&i| i.try_into().unwrap()).collect(); + let li = + VerifiableSS::::map_share_to_new_params( + &vss_scheme.parameters, + index.try_into().unwrap(), + s.as_slice(), + ); + let w_i = li * private_x_i; + let g = Point::generator(); + let g_w_i = g * &w_i; + let gamma_i = Scalar::::random(); + let g_gamma_i = g * &gamma_i; + let k_i = Scalar::::random(); + Self { + w_i, + g_w_i, + k_i, + gamma_i, + g_gamma_i, + } + } + + pub fn phase1_broadcast( + &self, + ) -> (SignBroadcastPhase1, SignDecommitPhase1) { + let blind_factor = BigInt::sample(SECURITY); + let g = Point::generator(); + let g_gamma_i = g * &self.gamma_i; + let com = HashCommitment::::create_commitment_with_user_defined_randomness( &BigInt::from_bytes(g_gamma_i.to_bytes(true).as_ref()), &blind_factor, ); - ( - SignBroadcastPhase1 { com }, - SignDecommitPhase1 { blind_factor, g_gamma_i: self.g_gamma_i.clone() }, - ) - } - - pub fn phase2_delta_i( - &self, - alpha_vec: &[Scalar], - beta_vec: &[Scalar], - ) -> Scalar { - let vec_len = alpha_vec.len(); - assert_eq!(alpha_vec.len(), beta_vec.len()); - // assert_eq!(alpha_vec.len(), self.s.len() - 1); - let ki_gamma_i = &self.k_i * &self.gamma_i; - - (0..vec_len) - .map(|i| &alpha_vec[i] + &beta_vec[i]) - .fold(ki_gamma_i, |acc, x| acc + x) - } - - pub fn phase2_sigma_i( - &self, - miu_vec: &[Scalar], - ni_vec: &[Scalar], - ) -> Scalar { - let vec_len = miu_vec.len(); - assert_eq!(miu_vec.len(), ni_vec.len()); - //assert_eq!(miu_vec.len(), self.s.len() - 1); - let ki_w_i = &self.k_i * &self.w_i; - (0..vec_len).map(|i| &miu_vec[i] + &ni_vec[i]).fold(ki_w_i, |acc, x| acc + x) - } - - pub fn phase3_compute_t_i( - sigma_i: &Scalar, - ) -> (Point, Scalar, PedersenProof) { - let g_sigma_i = Point::generator() * sigma_i; - let l = Scalar::::random(); - let h_l = Point::::base_point2() * &l; - let T = g_sigma_i + h_l; - let T_zk_proof = PedersenProof::::prove(sigma_i, &l); - - (T, l, T_zk_proof) - } - pub fn phase3_reconstruct_delta(delta_vec: &[Scalar]) -> Scalar { - let sum = delta_vec.iter().fold(Scalar::::zero(), |acc, x| acc + x); - sum.invert().unwrap() - } - - pub fn phase4( - delta_inv: &Scalar, - b_proof_vec: &[&DLogProof], - phase1_decommit_vec: Vec, - bc1_vec: &[SignBroadcastPhase1], - index: usize, - ) -> Result, ErrorType> { - let mut bad_actors_vec = Vec::new(); - let test_b_vec_and_com = (0..b_proof_vec.len()) + ( + SignBroadcastPhase1 { com }, + SignDecommitPhase1 { + blind_factor, + g_gamma_i: self.g_gamma_i.clone(), + }, + ) + } + + pub fn phase2_delta_i( + &self, + alpha_vec: &[Scalar], + beta_vec: &[Scalar], + ) -> Scalar { + let vec_len = alpha_vec.len(); + assert_eq!(alpha_vec.len(), beta_vec.len()); + // assert_eq!(alpha_vec.len(), self.s.len() - 1); + let ki_gamma_i = &self.k_i * &self.gamma_i; + + (0..vec_len) + .map(|i| &alpha_vec[i] + &beta_vec[i]) + .fold(ki_gamma_i, |acc, x| acc + x) + } + + pub fn phase2_sigma_i( + &self, + miu_vec: &[Scalar], + ni_vec: &[Scalar], + ) -> Scalar { + let vec_len = miu_vec.len(); + assert_eq!(miu_vec.len(), ni_vec.len()); + //assert_eq!(miu_vec.len(), self.s.len() - 1); + let ki_w_i = &self.k_i * &self.w_i; + (0..vec_len) + .map(|i| &miu_vec[i] + &ni_vec[i]) + .fold(ki_w_i, |acc, x| acc + x) + } + + pub fn phase3_compute_t_i( + sigma_i: &Scalar, + ) -> ( + Point, + Scalar, + PedersenProof, + ) { + let g_sigma_i = Point::generator() * sigma_i; + let l = Scalar::::random(); + let h_l = Point::::base_point2() * &l; + let T = g_sigma_i + h_l; + let T_zk_proof = PedersenProof::::prove(sigma_i, &l); + + (T, l, T_zk_proof) + } + pub fn phase3_reconstruct_delta( + delta_vec: &[Scalar], + ) -> Scalar { + let sum = delta_vec + .iter() + .fold(Scalar::::zero(), |acc, x| acc + x); + sum.invert().unwrap() + } + + pub fn phase4( + delta_inv: &Scalar, + b_proof_vec: &[&DLogProof], + phase1_decommit_vec: Vec, + bc1_vec: &[SignBroadcastPhase1], + index: usize, + ) -> Result, ErrorType> { + let mut bad_actors_vec = Vec::new(); + let test_b_vec_and_com = (0..b_proof_vec.len()) .map(|j| { let ind = if j < index { j } else { j + 1 }; let res = b_proof_vec[j].pk == phase1_decommit_vec[ind].g_gamma_i && @@ -584,245 +700,288 @@ impl SignKeys { }) .all(|x| x); - let mut g_gamma_i_iter = phase1_decommit_vec.iter(); - let head = g_gamma_i_iter.next().unwrap(); - let tail = g_gamma_i_iter; - - let err_type = ErrorType { - error_type: "bad gamma_i decommit".to_string(), - bad_actors: bad_actors_vec, - data: Vec::new(), - }; - - if test_b_vec_and_com { - Ok({ - let gamma_sum = tail.fold(head.g_gamma_i.clone(), |acc, x| acc + &x.g_gamma_i); - // R - gamma_sum * delta_inv - }) - } else { - Err(err_type) - } - } + let mut g_gamma_i_iter = phase1_decommit_vec.iter(); + let head = g_gamma_i_iter.next().unwrap(); + let tail = g_gamma_i_iter; + + let err_type = ErrorType { + error_type: "bad gamma_i decommit".to_string(), + bad_actors: bad_actors_vec, + data: Vec::new(), + }; + + if test_b_vec_and_com { + Ok({ + let gamma_sum = tail + .fold(head.g_gamma_i.clone(), |acc, x| acc + &x.g_gamma_i); + // R + gamma_sum * delta_inv + }) + } else { + Err(err_type) + } + } } impl LocalSignature { - pub fn phase5_proof_pdl( - R_dash: &Point, - R: &Point, - k_ciphertext: &BigInt, - ek: &EncryptionKey, - k_i: &Scalar, - k_enc_randomness: &BigInt, - dlog_statement: &DLogStatement, - ) -> PDLwSlackProof { - // Generate PDL with slack statement, witness and proof - let pdl_w_slack_statement = PDLwSlackStatement { - ciphertext: k_ciphertext.clone(), - ek: ek.clone(), - Q: R_dash.clone(), - G: R.clone(), - h1: dlog_statement.g.clone(), - h2: dlog_statement.ni.clone(), - N_tilde: dlog_statement.N.clone(), - }; - - let pdl_w_slack_witness = PDLwSlackWitness { x: k_i.clone(), r: k_enc_randomness.clone() }; - - PDLwSlackProof::prove(&pdl_w_slack_witness, &pdl_w_slack_statement) - } - - #[allow(clippy::too_many_arguments)] - pub fn phase5_verify_pdl( - pdl_w_slack_proof_vec: &[PDLwSlackProof], - R_dash: &Point, - R: &Point, - k_ciphertext: &BigInt, - ek: &EncryptionKey, - dlog_statement: &[DLogStatement], - s: &[usize], - i: usize, - ) -> Result<(), ErrorType> { - let mut bad_actors_vec = Vec::new(); - - let num_of_other_participants = s.len() - 1; - if pdl_w_slack_proof_vec.len() != num_of_other_participants { - bad_actors_vec.push(i); - } else { - let proofs_verification = (0..pdl_w_slack_proof_vec.len()) - .map(|j| { - let ind = if j < i { j } else { j + 1 }; - let pdl_w_slack_statement = PDLwSlackStatement { - ciphertext: k_ciphertext.clone(), - ek: ek.clone(), - Q: R_dash.clone(), - G: R.clone(), - h1: dlog_statement[s[ind]].g.clone(), - h2: dlog_statement[s[ind]].ni.clone(), - N_tilde: dlog_statement[s[ind]].N.clone(), - }; - let ver_res = pdl_w_slack_proof_vec[j].verify(&pdl_w_slack_statement); - if ver_res.is_err() { - bad_actors_vec.push(i); - false - } else { - true - } - }) - .all(|x| x); - if proofs_verification { - return Ok(()) - } - } - - let err_type = ErrorType { - error_type: "Bad PDLwSlack proof".to_string(), - bad_actors: bad_actors_vec, - data: Vec::new(), - }; - Err(err_type) - } - - pub fn phase5_check_R_dash_sum(R_dash_vec: &[Point]) -> Result<(), Error> { - let sum = R_dash_vec.iter().fold(Point::generator().to_point(), |acc, x| acc + x); - match sum - &Point::generator().to_point() == Point::generator().to_point() { - true => Ok(()), - false => Err(Phase5BadSum), - } - } - - pub fn phase6_compute_S_i_and_proof_of_consistency( - R: &Point, - T: &Point, - sigma: &Scalar, - l: &Scalar, - ) -> (Point, HomoELGamalProof) { - let S = R * sigma; - let delta = HomoElGamalStatement { - G: R.clone(), - H: Point::::base_point2().clone(), - Y: Point::generator().to_point(), - D: T.clone(), - E: S.clone(), - }; - let witness = HomoElGamalWitness { x: l.clone(), r: sigma.clone() }; - let proof = HomoELGamalProof::prove(&witness, &delta); - - (S, proof) - } - - pub fn phase6_verify_proof( - S_vec: &[Point], - proof_vec: &[HomoELGamalProof], - R_vec: &[Point], - T_vec: &[Point], - ) -> Result<(), ErrorType> { - let mut bad_actors_vec = Vec::new(); - let mut verify_proofs = true; - for i in 0..proof_vec.len() { - let delta = HomoElGamalStatement { - G: R_vec[i].clone(), - H: Point::::base_point2().clone(), - Y: Point::generator().to_point(), - D: T_vec[i].clone(), - E: S_vec[i].clone(), - }; - if proof_vec[i].verify(&delta).is_err() { - verify_proofs = false; - bad_actors_vec.push(i); - }; - } - - match verify_proofs { - true => Ok(()), - false => { - let err_type = ErrorType { - error_type: "phase6".to_string(), - bad_actors: bad_actors_vec, - data: Vec::new(), - }; - Err(err_type) - }, - } - } - - pub fn phase6_check_S_i_sum( - pubkey_y: &Point, - S_vec: &[Point], - ) -> Result<(), Error> { - let sum_plus_g = S_vec.iter().fold(Point::generator().to_point(), |acc, x| acc + x); - let sum = sum_plus_g - &Point::generator().to_point(); - - match &sum == pubkey_y { - true => Ok(()), - false => Err(Phase6Error), - } - } - - pub fn phase7_local_sig( - k_i: &Scalar, - message: &BigInt, - R: &Point, - sigma_i: &Scalar, - pubkey: &Point, - ) -> Self { - let m_fe = Scalar::::from(message); - let r = Scalar::::from( - &R.x_coord().unwrap().mod_floor(Scalar::::group_order()), - ); - let s_i = m_fe * k_i + &r * sigma_i; - Self { r, R: R.clone(), s_i, m: message.clone(), y: pubkey.clone() } - } - - pub fn output_signature(&self, s_vec: &[Scalar]) -> Result { - let mut s = s_vec.iter().fold(self.s_i.clone(), |acc, x| acc + x); - let s_bn = s.to_bigint(); - - let r = Scalar::::from( - &self.R.x_coord().unwrap().mod_floor(Scalar::::group_order()), - ); - let ry: BigInt = self.R.y_coord().unwrap().mod_floor(Scalar::::group_order()); - - /* - Calculate recovery id - it is not possible to compute the public key out of the signature - itself. Recovery id is used to enable extracting the public key uniquely. - 1. id = R.y & 1 - 2. if (s > curve.q / 2) id = id ^ 1 - */ - let is_ry_odd = ry.test_bit(0); - let mut recid = if is_ry_odd { 1 } else { 0 }; - let s_tag_bn = Scalar::::group_order() - &s_bn; - if s_bn > s_tag_bn { - s = Scalar::::from(&s_tag_bn); - recid ^= 1; - } - let sig = SignatureRecid { r, s, recid }; - let ver = verify(&sig, &self.y, &self.m).is_ok(); - if ver { - Ok(sig) - } else { - Err(InvalidSig) - } - } + pub fn phase5_proof_pdl( + R_dash: &Point, + R: &Point, + k_ciphertext: &BigInt, + ek: &EncryptionKey, + k_i: &Scalar, + k_enc_randomness: &BigInt, + dlog_statement: &DLogStatement, + ) -> PDLwSlackProof { + // Generate PDL with slack statement, witness and proof + let pdl_w_slack_statement = PDLwSlackStatement { + ciphertext: k_ciphertext.clone(), + ek: ek.clone(), + Q: R_dash.clone(), + G: R.clone(), + h1: dlog_statement.g.clone(), + h2: dlog_statement.ni.clone(), + N_tilde: dlog_statement.N.clone(), + }; + + let pdl_w_slack_witness = PDLwSlackWitness { + x: k_i.clone(), + r: k_enc_randomness.clone(), + }; + + PDLwSlackProof::prove(&pdl_w_slack_witness, &pdl_w_slack_statement) + } + + #[allow(clippy::too_many_arguments)] + pub fn phase5_verify_pdl( + pdl_w_slack_proof_vec: &[PDLwSlackProof], + R_dash: &Point, + R: &Point, + k_ciphertext: &BigInt, + ek: &EncryptionKey, + dlog_statement: &[DLogStatement], + s: &[usize], + i: usize, + ) -> Result<(), ErrorType> { + let mut bad_actors_vec = Vec::new(); + + let num_of_other_participants = s.len() - 1; + if pdl_w_slack_proof_vec.len() != num_of_other_participants { + bad_actors_vec.push(i); + } else { + let proofs_verification = (0..pdl_w_slack_proof_vec.len()) + .map(|j| { + let ind = if j < i { j } else { j + 1 }; + let pdl_w_slack_statement = PDLwSlackStatement { + ciphertext: k_ciphertext.clone(), + ek: ek.clone(), + Q: R_dash.clone(), + G: R.clone(), + h1: dlog_statement[s[ind]].g.clone(), + h2: dlog_statement[s[ind]].ni.clone(), + N_tilde: dlog_statement[s[ind]].N.clone(), + }; + let ver_res = + pdl_w_slack_proof_vec[j].verify(&pdl_w_slack_statement); + if ver_res.is_err() { + bad_actors_vec.push(i); + false + } else { + true + } + }) + .all(|x| x); + if proofs_verification { + return Ok(()); + } + } + + let err_type = ErrorType { + error_type: "Bad PDLwSlack proof".to_string(), + bad_actors: bad_actors_vec, + data: Vec::new(), + }; + Err(err_type) + } + + pub fn phase5_check_R_dash_sum( + R_dash_vec: &[Point], + ) -> Result<(), Error> { + let sum = R_dash_vec + .iter() + .fold(Point::generator().to_point(), |acc, x| acc + x); + match sum - &Point::generator().to_point() + == Point::generator().to_point() + { + true => Ok(()), + false => Err(Phase5BadSum), + } + } + + pub fn phase6_compute_S_i_and_proof_of_consistency( + R: &Point, + T: &Point, + sigma: &Scalar, + l: &Scalar, + ) -> (Point, HomoELGamalProof) { + let S = R * sigma; + let delta = HomoElGamalStatement { + G: R.clone(), + H: Point::::base_point2().clone(), + Y: Point::generator().to_point(), + D: T.clone(), + E: S.clone(), + }; + let witness = HomoElGamalWitness { + x: l.clone(), + r: sigma.clone(), + }; + let proof = HomoELGamalProof::prove(&witness, &delta); + + (S, proof) + } + + pub fn phase6_verify_proof( + S_vec: &[Point], + proof_vec: &[HomoELGamalProof], + R_vec: &[Point], + T_vec: &[Point], + ) -> Result<(), ErrorType> { + let mut bad_actors_vec = Vec::new(); + let mut verify_proofs = true; + for i in 0..proof_vec.len() { + let delta = HomoElGamalStatement { + G: R_vec[i].clone(), + H: Point::::base_point2().clone(), + Y: Point::generator().to_point(), + D: T_vec[i].clone(), + E: S_vec[i].clone(), + }; + if proof_vec[i].verify(&delta).is_err() { + verify_proofs = false; + bad_actors_vec.push(i); + }; + } + + match verify_proofs { + true => Ok(()), + false => { + let err_type = ErrorType { + error_type: "phase6".to_string(), + bad_actors: bad_actors_vec, + data: Vec::new(), + }; + Err(err_type) + } + } + } + + pub fn phase6_check_S_i_sum( + pubkey_y: &Point, + S_vec: &[Point], + ) -> Result<(), Error> { + let sum_plus_g = S_vec + .iter() + .fold(Point::generator().to_point(), |acc, x| acc + x); + let sum = sum_plus_g - &Point::generator().to_point(); + + match &sum == pubkey_y { + true => Ok(()), + false => Err(Phase6Error), + } + } + + pub fn phase7_local_sig( + k_i: &Scalar, + message: &BigInt, + R: &Point, + sigma_i: &Scalar, + pubkey: &Point, + ) -> Self { + let m_fe = Scalar::::from(message); + let r = Scalar::::from( + &R.x_coord() + .unwrap() + .mod_floor(Scalar::::group_order()), + ); + let s_i = m_fe * k_i + &r * sigma_i; + Self { + r, + R: R.clone(), + s_i, + m: message.clone(), + y: pubkey.clone(), + } + } + + pub fn output_signature( + &self, + s_vec: &[Scalar], + ) -> Result { + let mut s = s_vec.iter().fold(self.s_i.clone(), |acc, x| acc + x); + let s_bn = s.to_bigint(); + + let r = Scalar::::from( + &self + .R + .x_coord() + .unwrap() + .mod_floor(Scalar::::group_order()), + ); + let ry: BigInt = self + .R + .y_coord() + .unwrap() + .mod_floor(Scalar::::group_order()); + + /* + Calculate recovery id - it is not possible to compute the public key out of the signature + itself. Recovery id is used to enable extracting the public key uniquely. + 1. id = R.y & 1 + 2. if (s > curve.q / 2) id = id ^ 1 + */ + let is_ry_odd = ry.test_bit(0); + let mut recid = if is_ry_odd { 1 } else { 0 }; + let s_tag_bn = Scalar::::group_order() - &s_bn; + if s_bn > s_tag_bn { + s = Scalar::::from(&s_tag_bn); + recid ^= 1; + } + let sig = SignatureRecid { r, s, recid }; + let ver = verify(&sig, &self.y, &self.m).is_ok(); + if ver { + Ok(sig) + } else { + Err(InvalidSig) + } + } } -pub fn verify(sig: &SignatureRecid, y: &Point, message: &BigInt) -> Result<(), Error> { - let b = sig.s.invert().unwrap(); - let a = Scalar::::from(message); - let u1 = a * &b; - let u2 = &sig.r * &b; - - let g = Point::generator(); - let gu1 = g * u1; - let yu2 = y * &u2; - // can be faster using shamir trick - - if sig.r == - Scalar::::from( - &(gu1 + yu2).x_coord().unwrap().mod_floor(Scalar::::group_order()), - ) { - Ok(()) - } else { - Err(InvalidSig) - } +pub fn verify( + sig: &SignatureRecid, + y: &Point, + message: &BigInt, +) -> Result<(), Error> { + let b = sig.s.invert().unwrap(); + let a = Scalar::::from(message); + let u1 = a * &b; + let u2 = &sig.r * &b; + + let g = Point::generator(); + let gu1 = g * u1; + let yu2 = y * &u2; + // can be faster using shamir trick + + if sig.r + == Scalar::::from( + &(gu1 + yu2) + .x_coord() + .unwrap() + .mod_floor(Scalar::::group_order()), + ) + { + Ok(()) + } else { + Err(InvalidSig) + } } diff --git a/src/presign/mod.rs b/src/presign/mod.rs index 57aa3105..5935d096 100644 --- a/src/presign/mod.rs +++ b/src/presign/mod.rs @@ -1,17 +1,17 @@ /* - CGGMP Threshold ECDSA + CGGMP Threshold ECDSA - Copyright 2022 by Webb Technologies. + Copyright 2022 by Webb Technologies. - This file is part of cggmp library - (https://github.com/webb-tools/cggmp-threshold-ecdsa) + This file is part of cggmp library + (https://github.com/webb-tools/cggmp-threshold-ecdsa) - cggmp-threshold-ecdsa is free software: you can redistribute - it and/or modify it under the terms of the GNU General Public - License as published by the Free Software Foundation, either - version 3 of the License, or (at your option) any later version. + cggmp-threshold-ecdsa is free software: you can redistribute + it and/or modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, either + version 3 of the License, or (at your option) any later version. - @license GPL-3.0+ + @license GPL-3.0+ */ use std::collections::HashMap; @@ -26,15 +26,17 @@ use paillier::{DecryptionKey, EncryptionKey}; use sha2::Sha256; use crate::utilities::{ - aff_g::{ - PaillierAffineOpWithGroupComInRangeProof, PaillierAffineOpWithGroupComInRangeStatement, - }, - dec_q::{PaillierDecryptionModQProof, PaillierDecryptionModQStatement}, - enc::{PaillierEncryptionInRangeProof, PaillierEncryptionInRangeStatement}, - log_star::{ - KnowledgeOfExponentPaillierEncryptionProof, KnowledgeOfExponentPaillierEncryptionStatement, - }, - mul::{PaillierMulProof, PaillierMulStatement}, + aff_g::{ + PaillierAffineOpWithGroupComInRangeProof, + PaillierAffineOpWithGroupComInRangeStatement, + }, + dec_q::{PaillierDecryptionModQProof, PaillierDecryptionModQStatement}, + enc::{PaillierEncryptionInRangeProof, PaillierEncryptionInRangeStatement}, + log_star::{ + KnowledgeOfExponentPaillierEncryptionProof, + KnowledgeOfExponentPaillierEncryptionStatement, + }, + mul::{PaillierMulProof, PaillierMulStatement}, }; use serde::{Deserialize, Serialize}; @@ -44,181 +46,196 @@ pub mod rounds; pub mod state_machine; pub fn DEFAULT_ENCRYPTION_KEY() -> EncryptionKey { - EncryptionKey { n: BigInt::zero(), nn: BigInt::zero() } + EncryptionKey { + n: BigInt::zero(), + nn: BigInt::zero(), + } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct SSID { - // Group generator and order - pub g: Point, - pub q: BigInt, - // Parties - pub P: Vec, - pub rid: [u8; 32], - pub X: LocalKey, - pub Y: Option>, - // Pedersen parameters - pub N: BigInt, - pub S: BigInt, - pub T: BigInt, + // Group generator and order + pub g: Point, + pub q: BigInt, + // Parties + pub P: Vec, + pub rid: [u8; 32], + pub X: LocalKey, + pub Y: Option>, + // Pedersen parameters + pub N: BigInt, + pub S: BigInt, + pub T: BigInt, } impl Zeroize for SSID { - fn zeroize(&mut self) { - self.q.zeroize(); - self.P.zeroize(); - self.rid.zeroize(); - // X zeroize - self.X.paillier_dk.p.zeroize(); - self.X.paillier_dk.q.zeroize(); - // TODO: Ensure this clears memory or zeroize directly - // FIXME: This is a hack in the meantime until we are sure the memory is cleared. - self.X.pk_vec = vec![]; - - for encryption_key in self.X.paillier_key_vec.iter_mut() { - (*encryption_key).n.zeroize(); - (*encryption_key).nn.zeroize(); - } - // TODO: Zeroize directly if this is insufficient - self.X.y_sum_s = Point::zero(); - for dlog_statement in self.X.h1_h2_n_tilde_vec.iter_mut() { - (*dlog_statement).N.zeroize(); - (*dlog_statement).g.zeroize(); - (*dlog_statement).ni.zeroize(); - } - self.X.vss_scheme.parameters.threshold.zeroize(); - self.X.vss_scheme.parameters.share_count.zeroize(); - // TODO: Zeroize directly if this is insufficient - self.X.vss_scheme.commitments = vec![]; - self.X.i.zeroize(); - self.X.t.zeroize(); - self.X.n.zeroize(); - // Y zeroize - // TODO: Zeroize directly if this is insufficient - self.Y = None; - self.N.zeroize(); - self.S.zeroize(); - self.T.zeroize(); - } + fn zeroize(&mut self) { + self.q.zeroize(); + self.P.zeroize(); + self.rid.zeroize(); + // X zeroize + self.X.paillier_dk.p.zeroize(); + self.X.paillier_dk.q.zeroize(); + // TODO: Ensure this clears memory or zeroize directly + // FIXME: This is a hack in the meantime until we are sure the memory is + // cleared. + self.X.pk_vec = vec![]; + + for encryption_key in self.X.paillier_key_vec.iter_mut() { + encryption_key.n.zeroize(); + encryption_key.nn.zeroize(); + } + // TODO: Zeroize directly if this is insufficient + self.X.y_sum_s = Point::zero(); + for dlog_statement in self.X.h1_h2_n_tilde_vec.iter_mut() { + dlog_statement.N.zeroize(); + dlog_statement.g.zeroize(); + dlog_statement.ni.zeroize(); + } + self.X.vss_scheme.parameters.threshold.zeroize(); + self.X.vss_scheme.parameters.share_count.zeroize(); + // TODO: Zeroize directly if this is insufficient + self.X.vss_scheme.commitments = vec![]; + self.X.i.zeroize(); + self.X.t.zeroize(); + self.X.n.zeroize(); + // Y zeroize + // TODO: Zeroize directly if this is insufficient + self.Y = None; + self.N.zeroize(); + self.S.zeroize(); + self.T.zeroize(); + } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PreSigningSecrets { - pub x_i: BigInt, - pub y_i: Option, - pub ek: EncryptionKey, - pub dk: DecryptionKey, + pub x_i: BigInt, + pub y_i: Option, + pub ek: EncryptionKey, + pub dk: DecryptionKey, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PreSigningP2PMessage1 { - pub ssid: SSID, - pub i: u16, - pub K_i: BigInt, - pub G_i: BigInt, - pub ek: EncryptionKey, - pub psi_0_j_i: PaillierEncryptionInRangeProof, - pub enc_j_statement: PaillierEncryptionInRangeStatement, + pub ssid: SSID, + pub i: u16, + pub K_i: BigInt, + pub G_i: BigInt, + pub ek: EncryptionKey, + pub psi_0_j_i: PaillierEncryptionInRangeProof, + pub enc_j_statement: PaillierEncryptionInRangeStatement, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PreSigningP2PMessage2 { - pub ssid: SSID, - pub i: u16, - pub Gamma_i: Point, - pub D_j_i: BigInt, - pub F_j_i: BigInt, - pub D_hat_j_i: BigInt, - pub F_hat_j_i: BigInt, - pub psi_j_i: PaillierAffineOpWithGroupComInRangeProof, - pub statement_psi_j_i: PaillierAffineOpWithGroupComInRangeStatement, - pub psi_hat_j_i: PaillierAffineOpWithGroupComInRangeProof, - pub statement_psi_hat_j_i: PaillierAffineOpWithGroupComInRangeStatement, - pub psi_prime_j_i: KnowledgeOfExponentPaillierEncryptionProof, - pub statement_psi_prime_j_i: KnowledgeOfExponentPaillierEncryptionStatement, + pub ssid: SSID, + pub i: u16, + pub Gamma_i: Point, + pub D_j_i: BigInt, + pub F_j_i: BigInt, + pub D_hat_j_i: BigInt, + pub F_hat_j_i: BigInt, + pub psi_j_i: PaillierAffineOpWithGroupComInRangeProof, + pub statement_psi_j_i: + PaillierAffineOpWithGroupComInRangeStatement, + pub psi_hat_j_i: PaillierAffineOpWithGroupComInRangeProof, + pub statement_psi_hat_j_i: + PaillierAffineOpWithGroupComInRangeStatement, + pub psi_prime_j_i: KnowledgeOfExponentPaillierEncryptionProof, + pub statement_psi_prime_j_i: + KnowledgeOfExponentPaillierEncryptionStatement, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PreSigningP2PMessage3 { - pub ssid: SSID, - pub i: u16, - pub delta_i: BigInt, - pub Delta_i: Point, - pub psi_prime_prime_j_i: KnowledgeOfExponentPaillierEncryptionProof, - pub statement_psi_prime_prime_j_i: KnowledgeOfExponentPaillierEncryptionStatement, + pub ssid: SSID, + pub i: u16, + pub delta_i: BigInt, + pub Delta_i: Point, + pub psi_prime_prime_j_i: + KnowledgeOfExponentPaillierEncryptionProof, + pub statement_psi_prime_prime_j_i: + KnowledgeOfExponentPaillierEncryptionStatement, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PresigningOutput { - pub ssid: SSID, - pub R: Point, - pub i: u16, - pub k_i: BigInt, - pub chi_i: BigInt, + pub ssid: SSID, + pub R: Point, + pub i: u16, + pub k_i: BigInt, + pub chi_i: BigInt, } impl Zeroize for PresigningOutput { - fn zeroize(&mut self) { - self.ssid.zeroize(); - // TODO: zeroize R - self.R = Point::zero(); - self.i.zeroize(); - self.k_i.zeroize(); - self.chi_i.zeroize(); - } + fn zeroize(&mut self) { + self.ssid.zeroize(); + // TODO: zeroize R + self.R = Point::zero(); + self.i.zeroize(); + self.k_i.zeroize(); + self.chi_i.zeroize(); + } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PresigningTranscript { - pub ssid: SSID, - pub secrets: PreSigningSecrets, - pub eks: HashMap, - pub gamma_i: BigInt, - pub Gamma_i: Point, - pub Gammas: HashMap>, - pub Gamma: Point, - pub k_i: BigInt, - pub nu_i: BigInt, - pub rho_i: BigInt, - pub G_i: BigInt, - pub K_i: BigInt, - pub G: HashMap, - pub K: HashMap, - pub beta_i: HashMap, - pub beta_hat_i: HashMap, - pub r_i: HashMap, - pub r_hat_i: HashMap, - pub s_i: HashMap, - pub s_hat_i: HashMap, - pub delta_i: BigInt, - pub chi_i: BigInt, - pub Delta_i: Point, - pub deltas: HashMap, - pub Deltas: HashMap>, - pub delta: BigInt, - pub D_j: HashMap, - pub D_hat_j: HashMap, - pub F_j: HashMap, - pub F_hat_j: HashMap, - pub D_i: HashMap, - pub D_hat_i: HashMap, - pub F_i: HashMap, - pub F_hat_i: HashMap, - pub alpha_i: HashMap, - pub alpha_hat_i: HashMap, - pub S: HashMap, - pub T: HashMap, - pub N_hats: HashMap, + pub ssid: SSID, + pub secrets: PreSigningSecrets, + pub eks: HashMap, + pub gamma_i: BigInt, + pub Gamma_i: Point, + pub Gammas: HashMap>, + pub Gamma: Point, + pub k_i: BigInt, + pub nu_i: BigInt, + pub rho_i: BigInt, + pub G_i: BigInt, + pub K_i: BigInt, + pub G: HashMap, + pub K: HashMap, + pub beta_i: HashMap, + pub beta_hat_i: HashMap, + pub r_i: HashMap, + pub r_hat_i: HashMap, + pub s_i: HashMap, + pub s_hat_i: HashMap, + pub delta_i: BigInt, + pub chi_i: BigInt, + pub Delta_i: Point, + pub deltas: HashMap, + pub Deltas: HashMap>, + pub delta: BigInt, + pub D_j: HashMap, + pub D_hat_j: HashMap, + pub F_j: HashMap, + pub F_hat_j: HashMap, + pub D_i: HashMap, + pub D_hat_i: HashMap, + pub F_i: HashMap, + pub F_hat_i: HashMap, + pub alpha_i: HashMap, + pub alpha_hat_i: HashMap, + pub S: HashMap, + pub T: HashMap, + pub N_hats: HashMap, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct IdentifiableAbortBroadcastMessage { - pub i: u16, - pub statements_D_j_i: - HashMap<(u16, u16), PaillierAffineOpWithGroupComInRangeStatement>, - pub proofs_D_j_i: HashMap<(u16, u16), PaillierAffineOpWithGroupComInRangeProof>, - pub statement_H_i: PaillierMulStatement, - pub proof_H_i: PaillierMulProof, - pub statement_delta_i: HashMap>, - pub proof_delta_i: HashMap>, + pub i: u16, + pub statements_D_j_i: HashMap< + (u16, u16), + PaillierAffineOpWithGroupComInRangeStatement, + >, + pub proofs_D_j_i: HashMap< + (u16, u16), + PaillierAffineOpWithGroupComInRangeProof, + >, + pub statement_H_i: PaillierMulStatement, + pub proof_H_i: PaillierMulProof, + pub statement_delta_i: + HashMap>, + pub proof_delta_i: HashMap>, } diff --git a/src/presign/rounds.rs b/src/presign/rounds.rs index d95c1289..ecf7515f 100644 --- a/src/presign/rounds.rs +++ b/src/presign/rounds.rs @@ -1,595 +1,685 @@ /* - CGGMP Threshold ECDSA + CGGMP Threshold ECDSA - Copyright 2022 by Webb Technologies. + Copyright 2022 by Webb Technologies. - This file is part of cggmp library - (https://github.com/webb-tools/cggmp-threshold-ecdsa) + This file is part of cggmp library + (https://github.com/webb-tools/cggmp-threshold-ecdsa) - cggmp-threshold-ecdsa is free software: you can redistribute - it and/or modify it under the terms of the GNU General Public - License as published by the Free Software Foundation, either - version 3 of the License, or (at your option) any later version. + cggmp-threshold-ecdsa is free software: you can redistribute + it and/or modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, either + version 3 of the License, or (at your option) any later version. - @license GPL-3.0+ + @license GPL-3.0+ */ use std::{collections::HashMap, marker::PhantomData}; use crate::{ - utilities::{ - aff_g::{ - PaillierAffineOpWithGroupComInRangeProof, PaillierAffineOpWithGroupComInRangeStatement, - PaillierAffineOpWithGroupComInRangeWitness, - }, - dec_q::{ - PaillierDecryptionModQProof, PaillierDecryptionModQStatement, - PaillierDecryptionModQWitness, - }, - enc::{ - PaillierEncryptionInRangeProof, PaillierEncryptionInRangeStatement, - PaillierEncryptionInRangeWitness, - }, - log_star::{ - KnowledgeOfExponentPaillierEncryptionProof, - KnowledgeOfExponentPaillierEncryptionStatement, - KnowledgeOfExponentPaillierEncryptionWitness, - }, - mul::{PaillierMulProof, PaillierMulStatement, PaillierMulWitness}, - sample_relatively_prime_integer, L_PRIME, - }, - ErrorType, ProofVerificationErrorData, + utilities::{ + aff_g::{ + PaillierAffineOpWithGroupComInRangeProof, + PaillierAffineOpWithGroupComInRangeStatement, + PaillierAffineOpWithGroupComInRangeWitness, + }, + dec_q::{ + PaillierDecryptionModQProof, PaillierDecryptionModQStatement, + PaillierDecryptionModQWitness, + }, + enc::{ + PaillierEncryptionInRangeProof, PaillierEncryptionInRangeStatement, + PaillierEncryptionInRangeWitness, + }, + log_star::{ + KnowledgeOfExponentPaillierEncryptionProof, + KnowledgeOfExponentPaillierEncryptionStatement, + KnowledgeOfExponentPaillierEncryptionWitness, + }, + mul::{PaillierMulProof, PaillierMulStatement, PaillierMulWitness}, + sample_relatively_prime_integer, L_PRIME, + }, + ErrorType, ProofVerificationErrorData, }; use super::{ - IdentifiableAbortBroadcastMessage, PreSigningP2PMessage1, PreSigningP2PMessage2, - PreSigningP2PMessage3, PreSigningSecrets, PresigningOutput, PresigningTranscript, - DEFAULT_ENCRYPTION_KEY, SSID, + IdentifiableAbortBroadcastMessage, PreSigningP2PMessage1, + PreSigningP2PMessage2, PreSigningP2PMessage3, PreSigningSecrets, + PresigningOutput, PresigningTranscript, DEFAULT_ENCRYPTION_KEY, SSID, }; use curv::{ - arithmetic::{traits::*, Modulo, Samplable}, - cryptographic_primitives::secret_sharing::feldman_vss::VerifiableSS, - elliptic::curves::{Point, Scalar, Secp256k1}, - BigInt, + arithmetic::{traits::*, Modulo, Samplable}, + cryptographic_primitives::secret_sharing::feldman_vss::VerifiableSS, + elliptic::curves::{Point, Scalar, Secp256k1}, + BigInt, }; use paillier::{ - Add, Decrypt, EncryptWithChosenRandomness, EncryptionKey, Mul, Paillier, Randomness, - RawCiphertext, RawPlaintext, + Add, Decrypt, EncryptWithChosenRandomness, EncryptionKey, Mul, Paillier, + Randomness, RawCiphertext, RawPlaintext, }; use round_based::{ - containers::{push::Push, BroadcastMsgs, BroadcastMsgsStore, P2PMsgs, P2PMsgsStore}, - Msg, + containers::{ + push::Push, BroadcastMsgs, BroadcastMsgsStore, P2PMsgs, P2PMsgsStore, + }, + Msg, }; use sha2::Sha256; use thiserror::Error; -use super::state_machine::{Round0Messages, Round1Messages, Round2Messages, Round3Messages}; +use super::state_machine::{ + Round0Messages, Round1Messages, Round2Messages, Round3Messages, +}; pub struct Round0 { - pub ssid: SSID, - pub secrets: PreSigningSecrets, - pub S: HashMap, - pub T: HashMap, - pub N_hats: HashMap, - pub l: usize, // This is the number of presignings to run in parallel + pub ssid: SSID, + pub secrets: PreSigningSecrets, + pub S: HashMap, + pub T: HashMap, + pub N_hats: HashMap, + pub l: usize, // This is the number of presignings to run in parallel } impl Round0 { - pub fn proceed(self, mut output: O) -> Result - where - O: Push>>>, - { - // k_i <- F_q - let k_i = BigInt::sample_below(&self.ssid.q); - // gamma_i <- F_q - let gamma_i = BigInt::sample_below(&self.ssid.q); - // rho_i <- Z*_{N_i} - let rho_i = sample_relatively_prime_integer(&self.secrets.ek.n); - // nu_i <- Z*_{N_i} - let nu_i = sample_relatively_prime_integer(&self.secrets.ek.n); - // G_i = enc_i(gamma_i; nu_i) - let G_i: BigInt = Paillier::encrypt_with_chosen_randomness( - &self.secrets.ek, - RawPlaintext::from(gamma_i.clone()), - &Randomness::from(nu_i.clone()), - ) - .into(); - // K_i = enc_i(k_i; rho_i) - let K_i: BigInt = Paillier::encrypt_with_chosen_randomness( - &self.secrets.ek, - RawPlaintext::from(k_i.clone()), - &Randomness(rho_i.clone()), - ) - .into(); - let witness_psi_0_j_i = PaillierEncryptionInRangeWitness::new(k_i.clone(), rho_i.clone()); - - for j in self.ssid.P.iter() { - if *j != self.ssid.X.i { - let statement_psi_0_j_i = PaillierEncryptionInRangeStatement { - N0: self.secrets.ek.n.clone(), - NN0: self.secrets.ek.nn.clone(), - K: K_i.clone(), - s: self.S.get(j).unwrap_or(&BigInt::zero()).clone(), - t: self.T.get(j).unwrap_or(&BigInt::zero()).clone(), - N_hat: self.N_hats.get(j).unwrap_or(&BigInt::zero()).clone(), - phantom: PhantomData, - }; - let psi_0_j_i = PaillierEncryptionInRangeProof::::prove( - &witness_psi_0_j_i, - &statement_psi_0_j_i, - ); - - let body = PreSigningP2PMessage1 { - ssid: self.ssid.clone(), - i: self.ssid.X.i, - K_i: K_i.clone(), - G_i: G_i.clone(), - psi_0_j_i, - enc_j_statement: statement_psi_0_j_i, - ek: self.secrets.ek.clone(), - }; - output.push(Msg { - sender: self.ssid.X.i, - receiver: Some(*j), - body: Box::new(body), - }); - } - } - Ok(Round1 { - ssid: self.ssid, - secrets: self.secrets, - gamma_i, - k_i, - nu_i, - rho_i, - G_i, - K_i, - S: self.S, - T: self.T, - N_hats: self.N_hats, - }) - } - pub fn is_expensive(&self) -> bool { - false - } + pub fn proceed(self, mut output: O) -> Result + where + O: Push>>>, + { + // k_i <- F_q + let k_i = BigInt::sample_below(&self.ssid.q); + // gamma_i <- F_q + let gamma_i = BigInt::sample_below(&self.ssid.q); + // rho_i <- Z*_{N_i} + let rho_i = sample_relatively_prime_integer(&self.secrets.ek.n); + // nu_i <- Z*_{N_i} + let nu_i = sample_relatively_prime_integer(&self.secrets.ek.n); + // G_i = enc_i(gamma_i; nu_i) + let G_i: BigInt = Paillier::encrypt_with_chosen_randomness( + &self.secrets.ek, + RawPlaintext::from(gamma_i.clone()), + &Randomness::from(nu_i.clone()), + ) + .into(); + // K_i = enc_i(k_i; rho_i) + let K_i: BigInt = Paillier::encrypt_with_chosen_randomness( + &self.secrets.ek, + RawPlaintext::from(k_i.clone()), + &Randomness(rho_i.clone()), + ) + .into(); + let witness_psi_0_j_i = + PaillierEncryptionInRangeWitness::new(k_i.clone(), rho_i.clone()); + + for j in self.ssid.P.iter() { + if *j != self.ssid.X.i { + let statement_psi_0_j_i = PaillierEncryptionInRangeStatement { + N0: self.secrets.ek.n.clone(), + NN0: self.secrets.ek.nn.clone(), + K: K_i.clone(), + s: self.S.get(j).unwrap_or(&BigInt::zero()).clone(), + t: self.T.get(j).unwrap_or(&BigInt::zero()).clone(), + N_hat: self + .N_hats + .get(j) + .unwrap_or(&BigInt::zero()) + .clone(), + phantom: PhantomData, + }; + let psi_0_j_i = + PaillierEncryptionInRangeProof::::prove( + &witness_psi_0_j_i, + &statement_psi_0_j_i, + ); + + let body = PreSigningP2PMessage1 { + ssid: self.ssid.clone(), + i: self.ssid.X.i, + K_i: K_i.clone(), + G_i: G_i.clone(), + psi_0_j_i, + enc_j_statement: statement_psi_0_j_i, + ek: self.secrets.ek.clone(), + }; + output.push(Msg { + sender: self.ssid.X.i, + receiver: Some(*j), + body: Box::new(body), + }); + } + } + Ok(Round1 { + ssid: self.ssid, + secrets: self.secrets, + gamma_i, + k_i, + nu_i, + rho_i, + G_i, + K_i, + S: self.S, + T: self.T, + N_hats: self.N_hats, + }) + } + pub fn is_expensive(&self) -> bool { + false + } } pub struct Round1 { - pub ssid: SSID, - pub secrets: PreSigningSecrets, - pub gamma_i: BigInt, - pub k_i: BigInt, - pub nu_i: BigInt, - pub rho_i: BigInt, - pub G_i: BigInt, - pub K_i: BigInt, - pub S: HashMap, - pub T: HashMap, - pub N_hats: HashMap, + pub ssid: SSID, + pub secrets: PreSigningSecrets, + pub gamma_i: BigInt, + pub k_i: BigInt, + pub nu_i: BigInt, + pub rho_i: BigInt, + pub G_i: BigInt, + pub K_i: BigInt, + pub S: HashMap, + pub T: HashMap, + pub N_hats: HashMap, } impl Round1 { - pub fn proceed( - self, - input: P2PMsgs>, - mut output: O, - ) -> Result - where - O: Push>>>, - { - // Since x_i is a (t,n) share of x, we need to transform it to a (t,t+1) share omega_i using - // the appropriate lagrangian coefficient lambda_{i,S} as defined by GG18 and GG20. - // We then use omega_i in place of x_i for the rest of the protocol. - // Ref: https://eprint.iacr.org/2021/060.pdf (Section 1.2.8) - // Ref: https://eprint.iacr.org/2019/114.pdf (Section 4.2) - // Ref: https://eprint.iacr.org/2020/540.pdf (Section 3.2) - let lambda_i_s = VerifiableSS::::map_share_to_new_params( - &self.ssid.X.vss_scheme.parameters, - self.ssid.X.i - 1, - &self.ssid.P.iter().map(|i| i - 1).collect::>(), - ); - let omega_i = BigInt::mod_mul(&lambda_i_s.to_bigint(), &self.secrets.x_i, &self.ssid.q); - - let mut K: HashMap = HashMap::new(); - let mut G: HashMap = HashMap::new(); - let mut eks: HashMap = HashMap::new(); - // Verify P2P Messages - for msg in input.into_vec() { - // j - let j = msg.i; - // Insert K_j - K.insert(j, msg.K_i); - // Insert G_j - G.insert(j, msg.G_i); - // Insert j's Paillier encryption key - eks.insert(j, msg.ek); - let psi_0_i_j = msg.psi_0_j_i; - let enc_i_statement = msg.enc_j_statement; - // Verify psi_0_i_j proof - if PaillierEncryptionInRangeProof::::verify( - &psi_0_i_j, - &enc_i_statement, - ) - .is_err() - { - let error_data = ProofVerificationErrorData { - proof_symbol: "psi_0_i_j".to_string(), - verifying_party: self.ssid.X.i, - }; - return Err(PresignError::ProofVerificationError(ErrorType { - error_type: "enc".to_string(), - bad_actors: vec![j.into()], - data: bincode::serialize(&error_data).unwrap(), - })) - } - } - - // Gamma_i = g^{gamma_i} - let Gamma_i = - Point::::generator().as_point() * Scalar::from_bigint(&self.gamma_i); - // {beta, beta_hat, r, r_hat, s, s_hat}_i will store mapping from j to {beta, beta_hat, r, - // r_hat, s, s_hat}_i_j. - let mut beta_i: HashMap = HashMap::new(); - let mut beta_hat_i: HashMap = HashMap::new(); - let mut r_i: HashMap = HashMap::new(); - let mut r_hat_i: HashMap = HashMap::new(); - let mut s_i: HashMap = HashMap::new(); - let mut s_hat_i: HashMap = HashMap::new(); - let mut D_j: HashMap = HashMap::new(); - let mut F_j: HashMap = HashMap::new(); - let mut D_hat_j: HashMap = HashMap::new(); - let mut F_hat_j: HashMap = HashMap::new(); - - for j in self.ssid.P.iter() { - if j != &self.ssid.X.i { - // r_i_j <- Z_{N_j} - let r_i_j = - BigInt::sample_below(&eks.get(j).unwrap_or(&DEFAULT_ENCRYPTION_KEY()).n); - r_i.insert(*j, r_i_j.clone()); - // s_i_j <- Z_{N_j} - let s_i_j = - BigInt::sample_below(&eks.get(j).unwrap_or(&DEFAULT_ENCRYPTION_KEY()).n); - s_i.insert(*j, s_i_j.clone()); - // r_hat_i_j <- Z_{N_j} - let r_hat_i_j = - BigInt::sample_below(&eks.get(j).unwrap_or(&DEFAULT_ENCRYPTION_KEY()).n); - r_hat_i.insert(*j, r_hat_i_j.clone()); - // s_hat_i_j <- Z_{N_j} - let s_hat_i_j = - BigInt::sample_below(&eks.get(j).unwrap_or(&DEFAULT_ENCRYPTION_KEY()).n); - s_hat_i.insert(*j, s_hat_i_j.clone()); - let upper = BigInt::pow(&BigInt::from(2), L_PRIME as u32); - let lower = BigInt::from(-1).mul(&upper); - // beta_i_j <- [-2^L_PRIME, 2^L_PRIME] - let beta_i_j = BigInt::sample_range(&lower, &upper); - beta_i.insert(*j, beta_i_j.clone()); - // beta_hat_i_j <- [-2^L_PRIME, 2^L_PRIME] - let beta_hat_i_j = BigInt::sample_range(&lower, &upper); - beta_hat_i.insert(*j, beta_hat_i_j.clone()); - - let encrypt_minus_beta_i_j = Paillier::encrypt_with_chosen_randomness( - eks.get(j).unwrap_or(&DEFAULT_ENCRYPTION_KEY()), - RawPlaintext::from(BigInt::from(-1).mul(&beta_i_j.clone())), - &Randomness::from(s_i_j.clone()), - ); - // D_j_i = (gamma_i [.] K_j ) ⊕ enc_j(-beta_i_j; s_i_j) where [.] is Paillier - // multiplication - let D_j_i: BigInt = Paillier::add( - eks.get(j).unwrap_or(&DEFAULT_ENCRYPTION_KEY()), - Paillier::mul( - eks.get(j).unwrap_or(&DEFAULT_ENCRYPTION_KEY()), - RawCiphertext::from(K.get(j).unwrap_or(&BigInt::zero())), - RawPlaintext::from(self.gamma_i.clone()), - ), - encrypt_minus_beta_i_j, - ) - .into(); - - D_j.insert(*j, D_j_i.clone()); - - // F_j_i = enc_i(beta_i_j, r_i_j) - let F_j_i: BigInt = Paillier::encrypt_with_chosen_randomness( - &self.secrets.ek, - // To compute F_j_i, beta_i_j is NOT multiplied by -1 in the paper - // (see Figure 7, Round 2 in the paper), but that makes Π^aff-g ZK proof - // verification fail (see Figure 15 in the paper). This is because Π^aff-g - // states: - // - Y = (1 + N_1)^y ρ_y ^ N_1 - // - D = C^x (1 + N_0)^y ρ ^ N_0 - // And from Figure 7, Round 2 we can see that: - // - x = gamma_i - // - y = beta_i_j - // - ρ = s_i_j - // - ρ_y = r_i_j - // - Y = F_j_i i.e (1 + N_j)^{beta_i_j} r_i_j ^ {N_j} - // :- (1) enc_j(beta_i_j, r_i_j) - // - C = K_j - // - D = D_j_i i.e K ^ {gamma_i} (1 + N_i)^{beta_i_j} s_i_j ^ {N_i} - // :- (2) D ≡ gamma_i ⊙ K_j ⊕ enc_i(beta_i_j, s_i_j) - // - // From (1) and (2) we can see the mismatch between Π^aff-g (see Figure 15) and - // pre-signing definitions (see Figure 7, Round 2) i.e the Π^aff-g verification - // will fail unless we apply (or don't apply) negation uniformly to beta_i_j - // before encrypting it to generate F_j_i and D_j_i. - // - // We have 2 options to solve the sign mismatch: - // - (A). We can redefine y as negative beta_i_j, update F_j_i to encrypt a - // negative beta_i_j, and leave D_j_i unchanged. - // - (B). We can redefine D_j_i to encrypt a positive beta_i_j, leave y and - // F_j_i unchanged, and modify Figure 7, Round 3 to subtract beta_i_j instead - // of adding it when computing delta_i. - // - // We choose option (A) since it entails less modifications to the overall - // protocol (i.e all changes are performed in Round 2). - // - // NOTE: A similar transformation has to be applied to the "hat" variants (i.e - // beta_hat_i_j, F_hat_j_i, D_hat_j_i) as well. - // (see also https://en.wikipedia.org/wiki/Paillier_cryptosystem#Homomorphic_properties) - RawPlaintext::from(BigInt::from(-1).mul(&beta_i_j.clone())), - &Randomness::from(r_i_j.clone()), - ) - .into(); - - F_j.insert(*j, F_j_i.clone()); - - // Compute D_hat_j_i - let encrypt_minus_beta_hat_i_j = Paillier::encrypt_with_chosen_randomness( - eks.get(j).unwrap_or(&DEFAULT_ENCRYPTION_KEY()), - RawPlaintext::from(BigInt::from(-1).mul(&beta_hat_i_j)), - &Randomness::from(s_hat_i_j.clone()), - ); - // D_hat_j_i = (x_i [.] K_j ) ⊕ enc_j(-beta_hat_i_j; s_hat_i_j) where [.] is - // Paillier multiplication - let D_hat_j_i: BigInt = Paillier::add( - eks.get(j).unwrap_or(&DEFAULT_ENCRYPTION_KEY()), - Paillier::mul( - eks.get(j).unwrap_or(&DEFAULT_ENCRYPTION_KEY()), - RawCiphertext::from(K.get(j).unwrap_or(&BigInt::zero())), - // We use omega_i in place of x_i, see doc on omega_i definition for - // explanation. - RawPlaintext::from(omega_i.clone()), - ), - encrypt_minus_beta_hat_i_j, - ) - .into(); - - D_hat_j.insert(*j, D_hat_j_i.clone()); - - // F_hat_j_i = enc_i(beta_hat_i_j, r_hat_i_j) - let F_hat_j_i: BigInt = Paillier::encrypt_with_chosen_randomness( - &self.secrets.ek, - // See reasoning documented under F_j_i for why we multiply beta_hat_i_j by -1 - // before encrypting it. - RawPlaintext::from(BigInt::from(-1).mul(&beta_hat_i_j.clone())), - &Randomness::from(r_hat_i_j.clone()), - ) - .into(); - - F_hat_j.insert(*j, F_hat_j_i.clone()); - - // psi_j_i - let witness_psi_j_i = PaillierAffineOpWithGroupComInRangeWitness::new( - self.gamma_i.clone(), - // See reasoning documented under F_j_i for why we multiply beta_i_j by -1. - BigInt::from(-1).mul(&beta_i_j.clone()), - s_i_j.clone(), - r_i_j.clone(), - ); - let statement_psi_j_i = PaillierAffineOpWithGroupComInRangeStatement { - S: self.S.get(j).unwrap_or(&BigInt::zero()).clone(), - T: self.T.get(j).unwrap_or(&BigInt::zero()).clone(), - N_hat: self.N_hats.get(j).unwrap_or(&BigInt::zero()).clone(), - N0: eks.get(j).unwrap_or(&DEFAULT_ENCRYPTION_KEY()).n.clone(), - N1: self.secrets.ek.n.clone(), - NN0: eks.get(j).unwrap_or(&DEFAULT_ENCRYPTION_KEY()).nn.clone(), - NN1: self.secrets.ek.nn.clone(), - C: K.get(j).unwrap_or(&BigInt::zero()).clone(), - D: D_j_i.clone(), - Y: F_j_i.clone(), - X: Gamma_i.clone(), - ek_prover: self.secrets.ek.clone(), - ek_verifier: eks.get(j).unwrap_or(&DEFAULT_ENCRYPTION_KEY()).clone(), - phantom: PhantomData, - }; - let psi_j_i = PaillierAffineOpWithGroupComInRangeProof::::prove( - &witness_psi_j_i, - &statement_psi_j_i, - ); - - // psi_hat_j_i - let witness_psi_hat_j_i = PaillierAffineOpWithGroupComInRangeWitness::new( - // We use omega_i in place of x_i, see doc on omega_i definition for - // explanation. - omega_i.clone(), - // See reasoning documented under F_j_i for why we multiply beta_hat_i_j by -1. - BigInt::from(-1).mul(&beta_hat_i_j.clone()), - s_hat_i_j.clone(), - r_hat_i_j.clone(), - ); - let statement_psi_hat_j_i = PaillierAffineOpWithGroupComInRangeStatement { - S: self.S.get(j).unwrap_or(&BigInt::zero()).clone(), - T: self.T.get(j).unwrap_or(&BigInt::zero()).clone(), - N_hat: self.N_hats.get(j).unwrap_or(&BigInt::zero()).clone(), - N0: eks.get(j).unwrap_or(&DEFAULT_ENCRYPTION_KEY()).n.clone(), - N1: self.secrets.ek.n.clone(), - NN0: eks.get(j).unwrap_or(&DEFAULT_ENCRYPTION_KEY()).nn.clone(), - NN1: self.secrets.ek.nn.clone(), - C: K.get(j).unwrap_or(&BigInt::zero()).clone(), - D: D_hat_j_i.clone(), - Y: F_hat_j_i.clone(), - // We use omega_i in place of x_i, see doc on omega_i definition for - // explanation. - X: Point::::generator().as_point() * Scalar::from_bigint(&omega_i), - ek_prover: self.secrets.ek.clone(), - ek_verifier: eks.get(j).unwrap_or(&DEFAULT_ENCRYPTION_KEY()).clone(), - phantom: PhantomData, - }; - let psi_hat_j_i = - PaillierAffineOpWithGroupComInRangeProof::::prove( - &witness_psi_hat_j_i, - &statement_psi_hat_j_i, - ); - - // psi_prime_j_i - let witness_psi_prime_j_i = KnowledgeOfExponentPaillierEncryptionWitness::new( - self.gamma_i.clone(), - self.nu_i.clone(), - ); - let statement_psi_prime_j_i = KnowledgeOfExponentPaillierEncryptionStatement { - N0: self.secrets.ek.n.clone(), - NN0: self.secrets.ek.nn.clone(), - C: self.G_i.clone(), - X: Gamma_i.clone(), - // g is not always the secp256k1 generator, so we have to pass it explicitly. - // See [`KnowledgeOfExponentPaillierEncryptionStatement`] inline doc for g field - // for details. - g: Point::generator().to_point(), - s: self.S.get(j).unwrap_or(&BigInt::zero()).clone(), - t: self.T.get(j).unwrap_or(&BigInt::zero()).clone(), - N_hat: self.N_hats.get(j).unwrap_or(&BigInt::zero()).clone(), - phantom: PhantomData, - }; - let psi_prime_j_i = - KnowledgeOfExponentPaillierEncryptionProof::::prove( - &witness_psi_prime_j_i, - &statement_psi_prime_j_i, - ); - - // Send Message - let body = PreSigningP2PMessage2 { - ssid: self.ssid.clone(), - i: self.ssid.X.i, - Gamma_i: Gamma_i.clone(), - D_j_i: D_j_i.clone(), - F_j_i: F_j_i.clone(), - D_hat_j_i: D_hat_j_i.clone(), - F_hat_j_i: F_hat_j_i.clone(), - psi_j_i: psi_j_i.clone(), - statement_psi_j_i, - psi_hat_j_i, - statement_psi_hat_j_i, - psi_prime_j_i, - statement_psi_prime_j_i, - }; - output.push(Msg { - sender: self.ssid.X.i, - receiver: Some(*j), - body: Box::new(body), - }); - } - } - Ok(Round2 { - ssid: self.ssid, - secrets: self.secrets, - omega_i, - eks, - gamma_i: self.gamma_i, - k_i: self.k_i, - Gamma_i, - nu_i: self.nu_i, - rho_i: self.rho_i, - G_i: self.G_i, - K_i: self.K_i, - G, - K, - beta_i, - beta_hat_i, - r_i, - r_hat_i, - s_i, - s_hat_i, - D_j, - D_hat_j, - F_j, - F_hat_j, - S: self.S, - T: self.T, - N_hats: self.N_hats, - }) - } - - pub fn is_expensive(&self) -> bool { - false - } - - pub fn expects_messages(i: u16, n: u16) -> Round0Messages { - P2PMsgsStore::new(i, n) - } + pub fn proceed( + self, + input: P2PMsgs>, + mut output: O, + ) -> Result + where + O: Push>>>, + { + // Since x_i is a (t,n) share of x, we need to transform it to a (t,t+1) + // share omega_i using the appropriate lagrangian coefficient + // lambda_{i,S} as defined by GG18 and GG20. We then use omega_i + // in place of x_i for the rest of the protocol. Ref: https://eprint.iacr.org/2021/060.pdf (Section 1.2.8) + // Ref: https://eprint.iacr.org/2019/114.pdf (Section 4.2) + // Ref: https://eprint.iacr.org/2020/540.pdf (Section 3.2) + let lambda_i_s = + VerifiableSS::::map_share_to_new_params( + &self.ssid.X.vss_scheme.parameters, + self.ssid.X.i - 1, + &self.ssid.P.iter().map(|i| i - 1).collect::>(), + ); + let omega_i = BigInt::mod_mul( + &lambda_i_s.to_bigint(), + &self.secrets.x_i, + &self.ssid.q, + ); + + let mut K: HashMap = HashMap::new(); + let mut G: HashMap = HashMap::new(); + let mut eks: HashMap = HashMap::new(); + // Verify P2P Messages + for msg in input.into_vec() { + // j + let j = msg.i; + // Insert K_j + K.insert(j, msg.K_i); + // Insert G_j + G.insert(j, msg.G_i); + // Insert j's Paillier encryption key + eks.insert(j, msg.ek); + let psi_0_i_j = msg.psi_0_j_i; + let enc_i_statement = msg.enc_j_statement; + // Verify psi_0_i_j proof + if PaillierEncryptionInRangeProof::::verify( + &psi_0_i_j, + &enc_i_statement, + ) + .is_err() + { + let error_data = ProofVerificationErrorData { + proof_symbol: "psi_0_i_j".to_string(), + verifying_party: self.ssid.X.i, + }; + return Err(PresignError::ProofVerificationError(ErrorType { + error_type: "enc".to_string(), + bad_actors: vec![j.into()], + data: bincode::serialize(&error_data).unwrap(), + })); + } + } + + // Gamma_i = g^{gamma_i} + let Gamma_i = Point::::generator().as_point() + * Scalar::from_bigint(&self.gamma_i); + // {beta, beta_hat, r, r_hat, s, s_hat}_i will store mapping from j to + // {beta, beta_hat, r, r_hat, s, s_hat}_i_j. + let mut beta_i: HashMap = HashMap::new(); + let mut beta_hat_i: HashMap = HashMap::new(); + let mut r_i: HashMap = HashMap::new(); + let mut r_hat_i: HashMap = HashMap::new(); + let mut s_i: HashMap = HashMap::new(); + let mut s_hat_i: HashMap = HashMap::new(); + let mut D_j: HashMap = HashMap::new(); + let mut F_j: HashMap = HashMap::new(); + let mut D_hat_j: HashMap = HashMap::new(); + let mut F_hat_j: HashMap = HashMap::new(); + + for j in self.ssid.P.iter() { + if j != &self.ssid.X.i { + // r_i_j <- Z_{N_j} + let r_i_j = BigInt::sample_below( + &eks.get(j).unwrap_or(&DEFAULT_ENCRYPTION_KEY()).n, + ); + r_i.insert(*j, r_i_j.clone()); + // s_i_j <- Z_{N_j} + let s_i_j = BigInt::sample_below( + &eks.get(j).unwrap_or(&DEFAULT_ENCRYPTION_KEY()).n, + ); + s_i.insert(*j, s_i_j.clone()); + // r_hat_i_j <- Z_{N_j} + let r_hat_i_j = BigInt::sample_below( + &eks.get(j).unwrap_or(&DEFAULT_ENCRYPTION_KEY()).n, + ); + r_hat_i.insert(*j, r_hat_i_j.clone()); + // s_hat_i_j <- Z_{N_j} + let s_hat_i_j = BigInt::sample_below( + &eks.get(j).unwrap_or(&DEFAULT_ENCRYPTION_KEY()).n, + ); + s_hat_i.insert(*j, s_hat_i_j.clone()); + let upper = BigInt::pow(&BigInt::from(2), L_PRIME as u32); + let lower = BigInt::from(-1).mul(&upper); + // beta_i_j <- [-2^L_PRIME, 2^L_PRIME] + let beta_i_j = BigInt::sample_range(&lower, &upper); + beta_i.insert(*j, beta_i_j.clone()); + // beta_hat_i_j <- [-2^L_PRIME, 2^L_PRIME] + let beta_hat_i_j = BigInt::sample_range(&lower, &upper); + beta_hat_i.insert(*j, beta_hat_i_j.clone()); + + let encrypt_minus_beta_i_j = + Paillier::encrypt_with_chosen_randomness( + eks.get(j).unwrap_or(&DEFAULT_ENCRYPTION_KEY()), + RawPlaintext::from( + BigInt::from(-1).mul(&beta_i_j.clone()), + ), + &Randomness::from(s_i_j.clone()), + ); + // D_j_i = (gamma_i [.] K_j ) ⊕ enc_j(-beta_i_j; s_i_j) where + // [.] is Paillier multiplication + let D_j_i: BigInt = Paillier::add( + eks.get(j).unwrap_or(&DEFAULT_ENCRYPTION_KEY()), + Paillier::mul( + eks.get(j).unwrap_or(&DEFAULT_ENCRYPTION_KEY()), + RawCiphertext::from( + K.get(j).unwrap_or(&BigInt::zero()), + ), + RawPlaintext::from(self.gamma_i.clone()), + ), + encrypt_minus_beta_i_j, + ) + .into(); + + D_j.insert(*j, D_j_i.clone()); + + // F_j_i = enc_i(beta_i_j, r_i_j) + let F_j_i: BigInt = Paillier::encrypt_with_chosen_randomness( + &self.secrets.ek, + // To compute F_j_i, beta_i_j is NOT multiplied by -1 in + // the paper (see Figure 7, Round 2 in + // the paper), but that makes Π^aff-g ZK proof + // verification fail (see Figure 15 in the paper). This is + // because Π^aff-g states: + // - Y = (1 + N_1)^y ρ_y ^ N_1 + // - D = C^x (1 + N_0)^y ρ ^ N_0 + // And from Figure 7, Round 2 we can see that: + // - x = gamma_i + // - y = beta_i_j + // - ρ = s_i_j + // - ρ_y = r_i_j + // - Y = F_j_i i.e (1 + N_j)^{beta_i_j} r_i_j ^ {N_j} + // :- (1) enc_j(beta_i_j, r_i_j) + // - C = K_j + // - D = D_j_i i.e K ^ {gamma_i} (1 + N_i)^{beta_i_j} s_i_j + // ^ {N_i} + // :- (2) D ≡ gamma_i ⊙ K_j ⊕ enc_i(beta_i_j, s_i_j) + // + // From (1) and (2) we can see the mismatch between Π^aff-g + // (see Figure 15) and pre-signing + // definitions (see Figure 7, Round 2) i.e the Π^aff-g + // verification will fail unless we + // apply (or don't apply) negation uniformly to beta_i_j + // before encrypting it to generate F_j_i and D_j_i. + // + // We have 2 options to solve the sign mismatch: + // - (A). We can redefine y as negative beta_i_j, update + // F_j_i to encrypt a negative beta_i_j, and leave D_j_i + // unchanged. + // - (B). We can redefine D_j_i to encrypt a positive + // beta_i_j, leave y and F_j_i unchanged, and modify + // Figure 7, Round 3 to subtract beta_i_j instead of + // adding it when computing delta_i. + // + // We choose option (A) since it entails less modifications + // to the overall protocol (i.e all + // changes are performed in Round 2). + // + // NOTE: A similar transformation has to be applied to the + // "hat" variants (i.e beta_hat_i_j, + // F_hat_j_i, D_hat_j_i) as well. (see also https://en.wikipedia.org/wiki/Paillier_cryptosystem#Homomorphic_properties) + RawPlaintext::from(BigInt::from(-1).mul(&beta_i_j.clone())), + &Randomness::from(r_i_j.clone()), + ) + .into(); + + F_j.insert(*j, F_j_i.clone()); + + // Compute D_hat_j_i + let encrypt_minus_beta_hat_i_j = + Paillier::encrypt_with_chosen_randomness( + eks.get(j).unwrap_or(&DEFAULT_ENCRYPTION_KEY()), + RawPlaintext::from(BigInt::from(-1).mul(&beta_hat_i_j)), + &Randomness::from(s_hat_i_j.clone()), + ); + // D_hat_j_i = (x_i [.] K_j ) ⊕ enc_j(-beta_hat_i_j; s_hat_i_j) + // where [.] is Paillier multiplication + let D_hat_j_i: BigInt = Paillier::add( + eks.get(j).unwrap_or(&DEFAULT_ENCRYPTION_KEY()), + Paillier::mul( + eks.get(j).unwrap_or(&DEFAULT_ENCRYPTION_KEY()), + RawCiphertext::from( + K.get(j).unwrap_or(&BigInt::zero()), + ), + // We use omega_i in place of x_i, see doc on omega_i + // definition for explanation. + RawPlaintext::from(omega_i.clone()), + ), + encrypt_minus_beta_hat_i_j, + ) + .into(); + + D_hat_j.insert(*j, D_hat_j_i.clone()); + + // F_hat_j_i = enc_i(beta_hat_i_j, r_hat_i_j) + let F_hat_j_i: BigInt = + Paillier::encrypt_with_chosen_randomness( + &self.secrets.ek, + // See reasoning documented under F_j_i for why we + // multiply beta_hat_i_j by -1 + // before encrypting it. + RawPlaintext::from( + BigInt::from(-1).mul(&beta_hat_i_j.clone()), + ), + &Randomness::from(r_hat_i_j.clone()), + ) + .into(); + + F_hat_j.insert(*j, F_hat_j_i.clone()); + + // psi_j_i + let witness_psi_j_i = + PaillierAffineOpWithGroupComInRangeWitness::new( + self.gamma_i.clone(), + // See reasoning documented under F_j_i for why we + // multiply beta_i_j by -1. + BigInt::from(-1).mul(&beta_i_j.clone()), + s_i_j.clone(), + r_i_j.clone(), + ); + let statement_psi_j_i = + PaillierAffineOpWithGroupComInRangeStatement { + S: self.S.get(j).unwrap_or(&BigInt::zero()).clone(), + T: self.T.get(j).unwrap_or(&BigInt::zero()).clone(), + N_hat: self + .N_hats + .get(j) + .unwrap_or(&BigInt::zero()) + .clone(), + N0: eks + .get(j) + .unwrap_or(&DEFAULT_ENCRYPTION_KEY()) + .n + .clone(), + N1: self.secrets.ek.n.clone(), + NN0: eks + .get(j) + .unwrap_or(&DEFAULT_ENCRYPTION_KEY()) + .nn + .clone(), + NN1: self.secrets.ek.nn.clone(), + C: K.get(j).unwrap_or(&BigInt::zero()).clone(), + D: D_j_i.clone(), + Y: F_j_i.clone(), + X: Gamma_i.clone(), + ek_prover: self.secrets.ek.clone(), + ek_verifier: eks + .get(j) + .unwrap_or(&DEFAULT_ENCRYPTION_KEY()) + .clone(), + phantom: PhantomData, + }; + let psi_j_i = PaillierAffineOpWithGroupComInRangeProof::< + Secp256k1, + Sha256, + >::prove( + &witness_psi_j_i, &statement_psi_j_i + ); + + // psi_hat_j_i + let witness_psi_hat_j_i = + PaillierAffineOpWithGroupComInRangeWitness::new( + // We use omega_i in place of x_i, see doc on omega_i + // definition for explanation. + omega_i.clone(), + // See reasoning documented under F_j_i for why we + // multiply beta_hat_i_j by -1. + BigInt::from(-1).mul(&beta_hat_i_j.clone()), + s_hat_i_j.clone(), + r_hat_i_j.clone(), + ); + let statement_psi_hat_j_i = + PaillierAffineOpWithGroupComInRangeStatement { + S: self.S.get(j).unwrap_or(&BigInt::zero()).clone(), + T: self.T.get(j).unwrap_or(&BigInt::zero()).clone(), + N_hat: self + .N_hats + .get(j) + .unwrap_or(&BigInt::zero()) + .clone(), + N0: eks + .get(j) + .unwrap_or(&DEFAULT_ENCRYPTION_KEY()) + .n + .clone(), + N1: self.secrets.ek.n.clone(), + NN0: eks + .get(j) + .unwrap_or(&DEFAULT_ENCRYPTION_KEY()) + .nn + .clone(), + NN1: self.secrets.ek.nn.clone(), + C: K.get(j).unwrap_or(&BigInt::zero()).clone(), + D: D_hat_j_i.clone(), + Y: F_hat_j_i.clone(), + // We use omega_i in place of x_i, see doc on omega_i + // definition for explanation. + X: Point::::generator().as_point() + * Scalar::from_bigint(&omega_i), + ek_prover: self.secrets.ek.clone(), + ek_verifier: eks + .get(j) + .unwrap_or(&DEFAULT_ENCRYPTION_KEY()) + .clone(), + phantom: PhantomData, + }; + let psi_hat_j_i = PaillierAffineOpWithGroupComInRangeProof::< + Secp256k1, + Sha256, + >::prove( + &witness_psi_hat_j_i, + &statement_psi_hat_j_i, + ); + + // psi_prime_j_i + let witness_psi_prime_j_i = + KnowledgeOfExponentPaillierEncryptionWitness::new( + self.gamma_i.clone(), + self.nu_i.clone(), + ); + let statement_psi_prime_j_i = + KnowledgeOfExponentPaillierEncryptionStatement { + N0: self.secrets.ek.n.clone(), + NN0: self.secrets.ek.nn.clone(), + C: self.G_i.clone(), + X: Gamma_i.clone(), + // g is not always the secp256k1 generator, so we have + // to pass it explicitly. + // See [`KnowledgeOfExponentPaillierEncryptionStatement`] inline doc for g field + // for details. + g: Point::generator().to_point(), + s: self.S.get(j).unwrap_or(&BigInt::zero()).clone(), + t: self.T.get(j).unwrap_or(&BigInt::zero()).clone(), + N_hat: self + .N_hats + .get(j) + .unwrap_or(&BigInt::zero()) + .clone(), + phantom: PhantomData, + }; + let psi_prime_j_i = KnowledgeOfExponentPaillierEncryptionProof::< + Secp256k1, + Sha256, + >::prove( + &witness_psi_prime_j_i, + &statement_psi_prime_j_i, + ); + + // Send Message + let body = PreSigningP2PMessage2 { + ssid: self.ssid.clone(), + i: self.ssid.X.i, + Gamma_i: Gamma_i.clone(), + D_j_i: D_j_i.clone(), + F_j_i: F_j_i.clone(), + D_hat_j_i: D_hat_j_i.clone(), + F_hat_j_i: F_hat_j_i.clone(), + psi_j_i: psi_j_i.clone(), + statement_psi_j_i, + psi_hat_j_i, + statement_psi_hat_j_i, + psi_prime_j_i, + statement_psi_prime_j_i, + }; + output.push(Msg { + sender: self.ssid.X.i, + receiver: Some(*j), + body: Box::new(body), + }); + } + } + Ok(Round2 { + ssid: self.ssid, + secrets: self.secrets, + omega_i, + eks, + gamma_i: self.gamma_i, + k_i: self.k_i, + Gamma_i, + nu_i: self.nu_i, + rho_i: self.rho_i, + G_i: self.G_i, + K_i: self.K_i, + G, + K, + beta_i, + beta_hat_i, + r_i, + r_hat_i, + s_i, + s_hat_i, + D_j, + D_hat_j, + F_j, + F_hat_j, + S: self.S, + T: self.T, + N_hats: self.N_hats, + }) + } + + pub fn is_expensive(&self) -> bool { + false + } + + pub fn expects_messages(i: u16, n: u16) -> Round0Messages { + P2PMsgsStore::new(i, n) + } } pub struct Round2 { - pub ssid: SSID, - pub secrets: PreSigningSecrets, - pub omega_i: BigInt, - pub eks: HashMap, - pub gamma_i: BigInt, - pub Gamma_i: Point, - pub k_i: BigInt, - pub nu_i: BigInt, - pub rho_i: BigInt, - pub G_i: BigInt, - pub K_i: BigInt, - pub G: HashMap, - pub K: HashMap, - pub beta_i: HashMap, - pub beta_hat_i: HashMap, - pub r_i: HashMap, - pub r_hat_i: HashMap, - pub s_i: HashMap, - pub s_hat_i: HashMap, - pub D_j: HashMap, - pub D_hat_j: HashMap, - pub F_j: HashMap, - pub F_hat_j: HashMap, - pub S: HashMap, - pub T: HashMap, - pub N_hats: HashMap, + pub ssid: SSID, + pub secrets: PreSigningSecrets, + pub omega_i: BigInt, + pub eks: HashMap, + pub gamma_i: BigInt, + pub Gamma_i: Point, + pub k_i: BigInt, + pub nu_i: BigInt, + pub rho_i: BigInt, + pub G_i: BigInt, + pub K_i: BigInt, + pub G: HashMap, + pub K: HashMap, + pub beta_i: HashMap, + pub beta_hat_i: HashMap, + pub r_i: HashMap, + pub r_hat_i: HashMap, + pub s_i: HashMap, + pub s_hat_i: HashMap, + pub D_j: HashMap, + pub D_hat_j: HashMap, + pub F_j: HashMap, + pub F_hat_j: HashMap, + pub S: HashMap, + pub T: HashMap, + pub N_hats: HashMap, } impl Round2 { - pub fn proceed( - self, - input: P2PMsgs>, - mut output: O, - ) -> Result - where - O: Push>>>, - { - let mut D_i: HashMap = HashMap::new(); - let mut D_hat_i: HashMap = HashMap::new(); - let mut F_i: HashMap = HashMap::new(); - let mut F_hat_i: HashMap = HashMap::new(); - let mut Gammas: HashMap> = HashMap::new(); - - // Shift alpha_i_j to the interval {-N/2,...,N/2} (if necessary) as defined in Section 4.1 - // of the paper. - let shift_into_plus_minus_n_by_2_interval = |mut value: BigInt| -> BigInt { - if value > self.secrets.ek.n.div_floor(&BigInt::from(2)) { - value -= &self.secrets.ek.n; - } - value - }; - - for msg in input.into_vec() { - // j - let j = msg.i; - // Insert D_i_j - D_i.insert(j, msg.D_j_i); - // Insert D_hat_i_j - D_hat_i.insert(j, msg.D_hat_j_i); - // Insert F_i_j - F_i.insert(j, msg.F_j_i); - // Insert F_hat_i_j - F_hat_i.insert(j, msg.F_hat_j_i); - // Insert Gamma_j - Gammas.insert(j, msg.Gamma_i); - // Verify first aff-g - let psi_i_j = msg.psi_j_i; - let statement_psi_i_j = msg.statement_psi_j_i; - // Verify psi_i_j - if PaillierAffineOpWithGroupComInRangeProof::::verify( + pub fn proceed( + self, + input: P2PMsgs>, + mut output: O, + ) -> Result + where + O: Push>>>, + { + let mut D_i: HashMap = HashMap::new(); + let mut D_hat_i: HashMap = HashMap::new(); + let mut F_i: HashMap = HashMap::new(); + let mut F_hat_i: HashMap = HashMap::new(); + let mut Gammas: HashMap> = HashMap::new(); + + // Shift alpha_i_j to the interval {-N/2,...,N/2} (if necessary) as + // defined in Section 4.1 of the paper. + let shift_into_plus_minus_n_by_2_interval = + |mut value: BigInt| -> BigInt { + if value > self.secrets.ek.n.div_floor(&BigInt::from(2)) { + value -= &self.secrets.ek.n; + } + value + }; + + for msg in input.into_vec() { + // j + let j = msg.i; + // Insert D_i_j + D_i.insert(j, msg.D_j_i); + // Insert D_hat_i_j + D_hat_i.insert(j, msg.D_hat_j_i); + // Insert F_i_j + F_i.insert(j, msg.F_j_i); + // Insert F_hat_i_j + F_hat_i.insert(j, msg.F_hat_j_i); + // Insert Gamma_j + Gammas.insert(j, msg.Gamma_i); + // Verify first aff-g + let psi_i_j = msg.psi_j_i; + let statement_psi_i_j = msg.statement_psi_j_i; + // Verify psi_i_j + if PaillierAffineOpWithGroupComInRangeProof::::verify( &psi_i_j, &statement_psi_i_j, ) @@ -606,10 +696,10 @@ impl Round2 { })) } - // Verify psi_hat_i_j - let psi_hat_i_j = msg.psi_hat_j_i; - let statement_psi_hat_i_j = msg.statement_psi_hat_j_i; - if PaillierAffineOpWithGroupComInRangeProof::::verify( + // Verify psi_hat_i_j + let psi_hat_i_j = msg.psi_hat_j_i; + let statement_psi_hat_i_j = msg.statement_psi_hat_j_i; + if PaillierAffineOpWithGroupComInRangeProof::::verify( &psi_hat_i_j, &statement_psi_hat_i_j, ) @@ -626,10 +716,10 @@ impl Round2 { })) } - // Verify psi_prime_i_j - let psi_prime_i_j = msg.psi_prime_j_i; - let statement_psi_prime_i_j = msg.statement_psi_prime_j_i; - if KnowledgeOfExponentPaillierEncryptionProof::::verify( + // Verify psi_prime_i_j + let psi_prime_i_j = msg.psi_prime_j_i; + let statement_psi_prime_i_j = msg.statement_psi_prime_j_i; + if KnowledgeOfExponentPaillierEncryptionProof::::verify( &psi_prime_i_j, &statement_psi_prime_i_j, ) @@ -645,230 +735,256 @@ impl Round2 { data: bincode::serialize(&error_data).unwrap(), })) } - } - - // Gamma = Prod_j (Gamma_j) - let Gamma = Gammas.values().into_iter().fold(self.Gamma_i.clone(), |acc, x| acc + x); - - // Delta = Gamma^{k_i} - let Delta_i = Gamma.clone() * Scalar::from_bigint(&self.k_i); - - // {alpha, alpha_hat}_i will store mapping from j to {alpha, alpha_hat}_i_j - let mut alpha_i: HashMap = HashMap::new(); - let mut alpha_hat_i: HashMap = HashMap::new(); - for j in self.ssid.P.iter() { - if j != &self.ssid.X.i { - alpha_i.insert( - *j, - shift_into_plus_minus_n_by_2_interval( - Paillier::decrypt( - &self.secrets.dk, - RawCiphertext::from(D_i.get(j).unwrap_or(&BigInt::zero())), - ) - .into(), - ) - .mod_floor(&self.ssid.q), - ); - alpha_hat_i.insert( - *j, - shift_into_plus_minus_n_by_2_interval( - Paillier::decrypt( - &self.secrets.dk, - RawCiphertext::from(D_hat_i.get(j).unwrap_or(&BigInt::zero())), - ) - .into(), - ) - .mod_floor(&self.ssid.q), - ); - } - } - - // Sum alpha_i_j's - let sum_of_alphas = alpha_i.values().into_iter().fold(BigInt::zero(), |acc, x| acc.add(x)); - - // Sum alpha_hat_i_j's - let sum_of_alpha_hats = - alpha_hat_i.values().into_iter().fold(BigInt::zero(), |acc, x| acc.add(x)); - - // Sum beta_i_j's - let sum_of_betas = - self.beta_i.values().into_iter().fold(BigInt::zero(), |acc, x| acc.add(x)); - - // Sum beta_hat_i_j's - let sum_of_beta_hats = - self.beta_hat_i.values().into_iter().fold(BigInt::zero(), |acc, x| acc.add(x)); - - // delta_i = gamma_i * k_i + sum of alpha_i_j's + sum of beta_i_j's mod q - let delta_i = BigInt::mod_add( - &BigInt::mod_mul(&self.gamma_i, &self.k_i, &self.ssid.q), - &BigInt::mod_add(&sum_of_alphas, &sum_of_betas, &self.ssid.q), - &self.ssid.q, - ); - - // chi_i = x_i * k_i + sum of alpha_hat_i_j's + sum of beta_hat_i_j's - let chi_i = BigInt::mod_add( - // We use omega_i in place of x_i, see doc on omega_i definition (in Round 1) for - // explanation. - &BigInt::mod_mul(&self.omega_i, &self.k_i, &self.ssid.q), - &BigInt::mod_add(&sum_of_alpha_hats, &sum_of_beta_hats, &self.ssid.q), - &self.ssid.q, - ); - - for j in self.ssid.P.iter() { - if j != &self.ssid.X.i.clone() { - // Compute psi_prime_prime_j_i - let witness_psi_prime_prime_j_i = KnowledgeOfExponentPaillierEncryptionWitness::new( - self.k_i.clone(), - self.rho_i.clone(), - ); - - let statement_psi_prime_prime_j_i = - KnowledgeOfExponentPaillierEncryptionStatement { - N0: self.secrets.ek.n.clone(), - NN0: self.secrets.ek.nn.clone(), - C: self.K_i.clone(), - X: Delta_i.clone(), - // From the Delta_i = Gamma^{k_i} and Πlog∗ stating X = g^x, - // Since x = k_i and X = Delta_i, - // :- g = Gamma - // (see Figure 7, Round 3 and Figure 25 in paper). - g: Gamma.clone(), - s: self.S.get(j).unwrap_or(&BigInt::zero()).clone(), - t: self.T.get(j).unwrap_or(&BigInt::zero()).clone(), - N_hat: self.N_hats.get(j).unwrap_or(&BigInt::zero()).clone(), - phantom: PhantomData, - }; - let psi_prime_prime_j_i = - KnowledgeOfExponentPaillierEncryptionProof::::prove( - &witness_psi_prime_prime_j_i, - &statement_psi_prime_prime_j_i, - ); - - // Send Message - let body = PreSigningP2PMessage3 { - ssid: self.ssid.clone(), - i: self.ssid.X.i, - delta_i: delta_i.clone(), - Delta_i: Delta_i.clone(), - psi_prime_prime_j_i, - statement_psi_prime_prime_j_i, - }; - output.push(Msg { - sender: self.ssid.X.i, - receiver: Some(*j), - body: Box::new(body), - }); - } - } - Ok(Round3 { - ssid: self.ssid, - secrets: self.secrets, - eks: self.eks, - gamma_i: self.gamma_i, - Gamma_i: self.Gamma_i, - Gammas, - Gamma, - k_i: self.k_i, - nu_i: self.nu_i, - rho_i: self.rho_i, - G_i: self.G_i, - K_i: self.K_i, - G: self.G, - K: self.K, - beta_i: self.beta_i, - beta_hat_i: self.beta_hat_i, - r_i: self.r_i, - r_hat_i: self.r_hat_i, - s_i: self.s_i, - s_hat_i: self.s_hat_i, - delta_i, - chi_i, - Delta_i, - D_j: self.D_j, - D_hat_j: self.D_hat_j, - F_j: self.F_j, - F_hat_j: self.F_hat_j, - D_i, - D_hat_i, - F_i, - F_hat_i, - alpha_i, - alpha_hat_i, - S: self.S, - T: self.T, - N_hats: self.N_hats, - }) - } - - pub fn is_expensive(&self) -> bool { - false - } - pub fn expects_messages(i: u16, n: u16) -> Round1Messages { - P2PMsgsStore::new(i, n) - } + } + + // Gamma = Prod_j (Gamma_j) + let Gamma = + Gammas.values().fold(self.Gamma_i.clone(), |acc, x| acc + x); + + // Delta = Gamma^{k_i} + let Delta_i = Gamma.clone() * Scalar::from_bigint(&self.k_i); + + // {alpha, alpha_hat}_i will store mapping from j to {alpha, + // alpha_hat}_i_j + let mut alpha_i: HashMap = HashMap::new(); + let mut alpha_hat_i: HashMap = HashMap::new(); + for j in self.ssid.P.iter() { + if j != &self.ssid.X.i { + alpha_i.insert( + *j, + shift_into_plus_minus_n_by_2_interval( + Paillier::decrypt( + &self.secrets.dk, + RawCiphertext::from( + D_i.get(j).unwrap_or(&BigInt::zero()), + ), + ) + .into(), + ) + .mod_floor(&self.ssid.q), + ); + alpha_hat_i.insert( + *j, + shift_into_plus_minus_n_by_2_interval( + Paillier::decrypt( + &self.secrets.dk, + RawCiphertext::from( + D_hat_i.get(j).unwrap_or(&BigInt::zero()), + ), + ) + .into(), + ) + .mod_floor(&self.ssid.q), + ); + } + } + + // Sum alpha_i_j's + let sum_of_alphas = + alpha_i.values().fold(BigInt::zero(), |acc, x| acc.add(x)); + + // Sum alpha_hat_i_j's + let sum_of_alpha_hats = alpha_hat_i + .values() + .fold(BigInt::zero(), |acc, x| acc.add(x)); + + // Sum beta_i_j's + let sum_of_betas = self + .beta_i + .values() + .fold(BigInt::zero(), |acc, x| acc.add(x)); + + // Sum beta_hat_i_j's + let sum_of_beta_hats = self + .beta_hat_i + .values() + .fold(BigInt::zero(), |acc, x| acc.add(x)); + + // delta_i = gamma_i * k_i + sum of alpha_i_j's + sum of beta_i_j's mod + // q + let delta_i = BigInt::mod_add( + &BigInt::mod_mul(&self.gamma_i, &self.k_i, &self.ssid.q), + &BigInt::mod_add(&sum_of_alphas, &sum_of_betas, &self.ssid.q), + &self.ssid.q, + ); + + // chi_i = x_i * k_i + sum of alpha_hat_i_j's + sum of beta_hat_i_j's + let chi_i = BigInt::mod_add( + // We use omega_i in place of x_i, see doc on omega_i definition + // (in Round 1) for explanation. + &BigInt::mod_mul(&self.omega_i, &self.k_i, &self.ssid.q), + &BigInt::mod_add( + &sum_of_alpha_hats, + &sum_of_beta_hats, + &self.ssid.q, + ), + &self.ssid.q, + ); + + for j in self.ssid.P.iter() { + if j != &self.ssid.X.i.clone() { + // Compute psi_prime_prime_j_i + let witness_psi_prime_prime_j_i = + KnowledgeOfExponentPaillierEncryptionWitness::new( + self.k_i.clone(), + self.rho_i.clone(), + ); + + let statement_psi_prime_prime_j_i = + KnowledgeOfExponentPaillierEncryptionStatement { + N0: self.secrets.ek.n.clone(), + NN0: self.secrets.ek.nn.clone(), + C: self.K_i.clone(), + X: Delta_i.clone(), + // From the Delta_i = Gamma^{k_i} and Πlog∗ stating X = + // g^x, Since x = k_i and X = + // Delta_i, :- g = Gamma + // (see Figure 7, Round 3 and Figure 25 in paper). + g: Gamma.clone(), + s: self.S.get(j).unwrap_or(&BigInt::zero()).clone(), + t: self.T.get(j).unwrap_or(&BigInt::zero()).clone(), + N_hat: self + .N_hats + .get(j) + .unwrap_or(&BigInt::zero()) + .clone(), + phantom: PhantomData, + }; + let psi_prime_prime_j_i = + KnowledgeOfExponentPaillierEncryptionProof::< + Secp256k1, + Sha256, + >::prove( + &witness_psi_prime_prime_j_i, + &statement_psi_prime_prime_j_i, + ); + + // Send Message + let body = PreSigningP2PMessage3 { + ssid: self.ssid.clone(), + i: self.ssid.X.i, + delta_i: delta_i.clone(), + Delta_i: Delta_i.clone(), + psi_prime_prime_j_i, + statement_psi_prime_prime_j_i, + }; + output.push(Msg { + sender: self.ssid.X.i, + receiver: Some(*j), + body: Box::new(body), + }); + } + } + Ok(Round3 { + ssid: self.ssid, + secrets: self.secrets, + eks: self.eks, + gamma_i: self.gamma_i, + Gamma_i: self.Gamma_i, + Gammas, + Gamma, + k_i: self.k_i, + nu_i: self.nu_i, + rho_i: self.rho_i, + G_i: self.G_i, + K_i: self.K_i, + G: self.G, + K: self.K, + beta_i: self.beta_i, + beta_hat_i: self.beta_hat_i, + r_i: self.r_i, + r_hat_i: self.r_hat_i, + s_i: self.s_i, + s_hat_i: self.s_hat_i, + delta_i, + chi_i, + Delta_i, + D_j: self.D_j, + D_hat_j: self.D_hat_j, + F_j: self.F_j, + F_hat_j: self.F_hat_j, + D_i, + D_hat_i, + F_i, + F_hat_i, + alpha_i, + alpha_hat_i, + S: self.S, + T: self.T, + N_hats: self.N_hats, + }) + } + + pub fn is_expensive(&self) -> bool { + false + } + pub fn expects_messages(i: u16, n: u16) -> Round1Messages { + P2PMsgsStore::new(i, n) + } } pub struct Round3 { - pub ssid: SSID, - pub secrets: PreSigningSecrets, - pub eks: HashMap, - pub gamma_i: BigInt, - pub Gamma_i: Point, - pub Gammas: HashMap>, - pub Gamma: Point, - pub k_i: BigInt, - pub nu_i: BigInt, - pub rho_i: BigInt, - pub G_i: BigInt, - pub K_i: BigInt, - pub G: HashMap, - pub K: HashMap, - pub beta_i: HashMap, - pub beta_hat_i: HashMap, - pub r_i: HashMap, - pub r_hat_i: HashMap, - pub s_i: HashMap, - pub s_hat_i: HashMap, - pub delta_i: BigInt, - pub chi_i: BigInt, - pub Delta_i: Point, - pub D_j: HashMap, - pub D_hat_j: HashMap, - pub F_j: HashMap, - pub F_hat_j: HashMap, - pub D_i: HashMap, - pub D_hat_i: HashMap, - pub F_i: HashMap, - pub F_hat_i: HashMap, - pub alpha_i: HashMap, - pub alpha_hat_i: HashMap, - pub S: HashMap, - pub T: HashMap, - pub N_hats: HashMap, + pub ssid: SSID, + pub secrets: PreSigningSecrets, + pub eks: HashMap, + pub gamma_i: BigInt, + pub Gamma_i: Point, + pub Gammas: HashMap>, + pub Gamma: Point, + pub k_i: BigInt, + pub nu_i: BigInt, + pub rho_i: BigInt, + pub G_i: BigInt, + pub K_i: BigInt, + pub G: HashMap, + pub K: HashMap, + pub beta_i: HashMap, + pub beta_hat_i: HashMap, + pub r_i: HashMap, + pub r_hat_i: HashMap, + pub s_i: HashMap, + pub s_hat_i: HashMap, + pub delta_i: BigInt, + pub chi_i: BigInt, + pub Delta_i: Point, + pub D_j: HashMap, + pub D_hat_j: HashMap, + pub F_j: HashMap, + pub F_hat_j: HashMap, + pub D_i: HashMap, + pub D_hat_i: HashMap, + pub F_i: HashMap, + pub F_hat_i: HashMap, + pub alpha_i: HashMap, + pub alpha_hat_i: HashMap, + pub S: HashMap, + pub T: HashMap, + pub N_hats: HashMap, } impl Round3 { - pub fn proceed( - self, - input: P2PMsgs>, - mut output: O, - ) -> Result - where - O: Push>>>>, - { - // Mapping from j to delta_j - let mut deltas: HashMap = HashMap::new(); - // Mapping from j to Delta_j - let mut Deltas: HashMap> = HashMap::new(); - for msg in input.into_vec() { - // j - let j = msg.i; - // Verify psi_prime_prime_i_j - let psi_prime_prime_i_j = msg.psi_prime_prime_j_i; - - let statement_psi_prime_prime_i_j = msg.statement_psi_prime_prime_j_i; - - if KnowledgeOfExponentPaillierEncryptionProof::::verify( + pub fn proceed( + self, + input: P2PMsgs>, + mut output: O, + ) -> Result + where + O: Push>>>>, + { + // Mapping from j to delta_j + let mut deltas: HashMap = HashMap::new(); + // Mapping from j to Delta_j + let mut Deltas: HashMap> = HashMap::new(); + for msg in input.into_vec() { + // j + let j = msg.i; + // Verify psi_prime_prime_i_j + let psi_prime_prime_i_j = msg.psi_prime_prime_j_i; + + let statement_psi_prime_prime_i_j = + msg.statement_psi_prime_prime_j_i; + + if KnowledgeOfExponentPaillierEncryptionProof::::verify( &psi_prime_prime_i_j, &statement_psi_prime_prime_i_j, ) @@ -885,341 +1001,442 @@ impl Round3 { })) } - // Insert into deltas and Deltas - deltas.insert(j, msg.delta_i); - Deltas.insert(j, msg.Delta_i); - } - - // delta = sum of delta_j's - let delta = deltas - .values() - .into_iter() - .fold(self.delta_i.clone(), |acc, x| acc.add(x)) - .mod_floor(&self.ssid.q); - - // Compute product of Delta_j's - let product_of_Deltas = - Deltas.values().into_iter().fold(self.Delta_i.clone(), |acc, x| acc + x); - - if product_of_Deltas == - Point::::generator().as_point() * Scalar::from_bigint(&delta) - { - // R = Gamma^{delta^{-1}} - let R = self.Gamma.clone() * - Scalar::from_bigint(&BigInt::mod_inv(&delta, &self.ssid.q).unwrap()); - let presigning_output = PresigningOutput { - ssid: self.ssid.clone(), - R, - i: self.ssid.X.i, - k_i: self.k_i.clone(), - chi_i: self.chi_i.clone(), - }; - let transcript = PresigningTranscript { - ssid: self.ssid.clone(), - secrets: self.secrets, - eks: self.eks, - gamma_i: self.gamma_i, - Gamma_i: self.Gamma_i, - Gammas: self.Gammas, - Gamma: self.Gamma, - k_i: self.k_i, - nu_i: self.nu_i, - rho_i: self.rho_i, - G_i: self.G_i, - K_i: self.K_i, - G: self.G, - K: self.K, - beta_i: self.beta_i, - beta_hat_i: self.beta_hat_i, - r_i: self.r_i, - r_hat_i: self.r_hat_i, - s_i: self.s_i, - s_hat_i: self.s_hat_i, - delta_i: self.delta_i.clone(), - chi_i: self.chi_i.clone(), - Delta_i: self.Delta_i.clone(), - deltas, - Deltas, - delta, - D_j: self.D_j, - D_hat_j: self.D_hat_j, - F_j: self.F_j, - F_hat_j: self.F_hat_j, - D_i: self.D_i, - D_hat_i: self.D_hat_i, - F_i: self.F_i, - F_hat_i: self.F_hat_i, - alpha_i: self.alpha_i, - alpha_hat_i: self.alpha_hat_i, - S: self.S, - T: self.T, - N_hats: self.N_hats, - }; - - output.push(Msg { sender: self.ssid.X.i, receiver: None, body: Box::new(None) }); - - Ok(Round4 { - ssid: self.ssid, - output: Some(presigning_output), - transcript: Some(transcript), - }) - } else { - // (l,j) to proof for D_j_i - let mut proofs_D_j_i: HashMap< - (u16, u16), - PaillierAffineOpWithGroupComInRangeProof, - > = HashMap::new(); - - // (l,j) to statement for D_j_i - let mut statements_D_j_i: HashMap< - (u16, u16), - PaillierAffineOpWithGroupComInRangeStatement, - > = HashMap::new(); - - self.ssid.P.iter().zip(self.ssid.P.iter()).for_each(|(j, l)| { - if *j != self.ssid.X.i && j != l { - let D_j_i = self.D_j.get(&self.ssid.X.i.clone()).unwrap(); - - // F_j_i = enc_i(beta_i_j, r_i_j) - let F_j_i = self.F_j.get(&self.ssid.X.i).unwrap(); - - let witness_D_j_i = PaillierAffineOpWithGroupComInRangeWitness::new( - self.gamma_i.clone(), - self.beta_i.get(j).unwrap_or(&BigInt::zero()).clone(), - self.s_i.get(j).unwrap_or(&BigInt::zero()).clone(), - self.r_i.get(j).unwrap_or(&BigInt::zero()).clone(), - ); - let statement_D_j_i = PaillierAffineOpWithGroupComInRangeStatement { - S: self.S.get(l).unwrap_or(&BigInt::zero()).clone(), - T: self.T.get(l).unwrap_or(&BigInt::zero()).clone(), - N_hat: self.N_hats.get(l).unwrap_or(&BigInt::zero()).clone(), - N0: self.secrets.ek.n.clone(), - N1: self.eks.get(j).unwrap_or(&DEFAULT_ENCRYPTION_KEY()).n.clone(), - NN0: self.secrets.ek.nn.clone(), - NN1: self.eks.get(j).unwrap_or(&DEFAULT_ENCRYPTION_KEY()).nn.clone(), - C: D_j_i.clone(), - D: self.K.get(j).unwrap_or(&BigInt::zero()).clone(), - Y: F_j_i.clone(), - X: self.Gamma_i.clone(), - ek_prover: self.secrets.ek.clone(), - ek_verifier: self.eks.get(j).unwrap_or(&DEFAULT_ENCRYPTION_KEY()).clone(), - phantom: PhantomData, - }; - let D_j_i_proof = - PaillierAffineOpWithGroupComInRangeProof::::prove( - &witness_D_j_i, - &statement_D_j_i, - ); - proofs_D_j_i.insert((*l, *j), D_j_i_proof); - statements_D_j_i.insert((*l, *j), statement_D_j_i); - } - }); - - // H_i proof - let H_i_randomness: BigInt = sample_relatively_prime_integer(&self.secrets.ek.n); - let H_i: BigInt = Paillier::encrypt_with_chosen_randomness( - &self.secrets.ek, - RawPlaintext::from(BigInt::mul(&self.k_i, &self.gamma_i)), - &Randomness::from(H_i_randomness.clone()), - ) - .into(); - - let witness_H_i = - PaillierMulWitness::new(self.k_i, self.nu_i.clone(), self.nu_i.mul(&self.gamma_i)); - let statement_H_i = PaillierMulStatement { - N: self.secrets.ek.n.clone(), - NN: self.secrets.ek.nn.clone(), - C: self.G_i, - Y: self.K_i, - X: H_i.clone(), - ek_prover: self.secrets.ek.clone(), - phantom: PhantomData, - }; - - let proof_H_i = - PaillierMulProof::::prove(&witness_H_i, &statement_H_i); - - // delta_i proofs - let s_j_i = BigInt::zero(); - let ciphertext_delta_i = H_i; - let delta_i_randomness = H_i_randomness.clone(); - self.ssid.P.iter().for_each(|j| { - if *j != self.ssid.X.i { - ciphertext_delta_i - .mul(self.D_i.get(j).unwrap_or(&BigInt::zero())) - .mul(self.F_j.get(&self.ssid.X.i).unwrap_or(&BigInt::zero())); - delta_i_randomness - .mul(&self.rho_i) - .mul(&s_j_i) - .mul(self.r_i.get(j).unwrap_or(&BigInt::zero())); - } - }); - - let witness_delta_i = PaillierDecryptionModQWitness::new( - Paillier::decrypt( - &self.secrets.dk, - RawCiphertext::from(ciphertext_delta_i.clone()), - ) - .into(), - H_i_randomness, - ); - - // l to statement - let mut statement_delta_i: HashMap< - u16, - PaillierDecryptionModQStatement, - > = HashMap::new(); - - // l to proof - let mut proof_delta_i: HashMap> = - HashMap::new(); - - self.ssid.P.iter().for_each(|l| { - if *l != self.ssid.X.i { - let statement_delta_l_i = PaillierDecryptionModQStatement { - S: self.S.get(l).unwrap_or(&BigInt::zero()).clone(), - T: self.T.get(l).unwrap_or(&BigInt::zero()).clone(), - N_hat: self.N_hats.get(l).unwrap_or(&BigInt::zero()).clone(), - N0: self.secrets.ek.n.clone(), - NN0: self.secrets.ek.nn.clone(), - C: ciphertext_delta_i.clone(), - x: self.delta_i.clone(), - ek_prover: self.secrets.ek.clone(), - phantom: PhantomData, - }; - - statement_delta_i.insert(*l, statement_delta_l_i.clone()); - - proof_delta_i.insert( - *l, - PaillierDecryptionModQProof::::prove( - &witness_delta_i, - &statement_delta_l_i, - ), - ); - } - }); - - let body = Some(IdentifiableAbortBroadcastMessage { - i: self.ssid.X.i, - statements_D_j_i, - proofs_D_j_i, - statement_H_i, - proof_H_i, - statement_delta_i, - proof_delta_i, - }); - - output.push(Msg { sender: self.ssid.X.i, receiver: None, body: Box::new(body) }); - Ok(Round4 { ssid: self.ssid, output: None, transcript: None }) - } - } - - pub fn is_expensive(&self) -> bool { - false - } - pub fn expects_messages(i: u16, n: u16) -> Round2Messages { - P2PMsgsStore::new(i, n) - } + // Insert into deltas and Deltas + deltas.insert(j, msg.delta_i); + Deltas.insert(j, msg.Delta_i); + } + + // delta = sum of delta_j's + let delta = deltas + .values() + .fold(self.delta_i.clone(), |acc, x| acc.add(x)) + .mod_floor(&self.ssid.q); + + // Compute product of Delta_j's + let product_of_Deltas = + Deltas.values().fold(self.Delta_i.clone(), |acc, x| acc + x); + + if product_of_Deltas + == Point::::generator().as_point() + * Scalar::from_bigint(&delta) + { + // R = Gamma^{delta^{-1}} + let R = self.Gamma.clone() + * Scalar::from_bigint( + &BigInt::mod_inv(&delta, &self.ssid.q).unwrap(), + ); + let presigning_output = PresigningOutput { + ssid: self.ssid.clone(), + R, + i: self.ssid.X.i, + k_i: self.k_i.clone(), + chi_i: self.chi_i.clone(), + }; + let transcript = PresigningTranscript { + ssid: self.ssid.clone(), + secrets: self.secrets, + eks: self.eks, + gamma_i: self.gamma_i, + Gamma_i: self.Gamma_i, + Gammas: self.Gammas, + Gamma: self.Gamma, + k_i: self.k_i, + nu_i: self.nu_i, + rho_i: self.rho_i, + G_i: self.G_i, + K_i: self.K_i, + G: self.G, + K: self.K, + beta_i: self.beta_i, + beta_hat_i: self.beta_hat_i, + r_i: self.r_i, + r_hat_i: self.r_hat_i, + s_i: self.s_i, + s_hat_i: self.s_hat_i, + delta_i: self.delta_i.clone(), + chi_i: self.chi_i.clone(), + Delta_i: self.Delta_i.clone(), + deltas, + Deltas, + delta, + D_j: self.D_j, + D_hat_j: self.D_hat_j, + F_j: self.F_j, + F_hat_j: self.F_hat_j, + D_i: self.D_i, + D_hat_i: self.D_hat_i, + F_i: self.F_i, + F_hat_i: self.F_hat_i, + alpha_i: self.alpha_i, + alpha_hat_i: self.alpha_hat_i, + S: self.S, + T: self.T, + N_hats: self.N_hats, + }; + + output.push(Msg { + sender: self.ssid.X.i, + receiver: None, + body: Box::new(None), + }); + + Ok(Round4 { + ssid: self.ssid, + output: Some(presigning_output), + transcript: Some(transcript), + }) + } else { + // (l,j) to proof for D_j_i + let mut proofs_D_j_i: HashMap< + (u16, u16), + PaillierAffineOpWithGroupComInRangeProof, + > = HashMap::new(); + + // (l,j) to statement for D_j_i + let mut statements_D_j_i: HashMap< + (u16, u16), + PaillierAffineOpWithGroupComInRangeStatement, + > = HashMap::new(); + + self.ssid + .P + .iter() + .zip(self.ssid.P.iter()) + .for_each(|(j, l)| { + if *j != self.ssid.X.i && j != l { + let D_j_i = + self.D_j.get(&self.ssid.X.i.clone()).unwrap(); + + // F_j_i = enc_i(beta_i_j, r_i_j) + let F_j_i = self.F_j.get(&self.ssid.X.i).unwrap(); + + let witness_D_j_i = + PaillierAffineOpWithGroupComInRangeWitness::new( + self.gamma_i.clone(), + self.beta_i + .get(j) + .unwrap_or(&BigInt::zero()) + .clone(), + self.s_i + .get(j) + .unwrap_or(&BigInt::zero()) + .clone(), + self.r_i + .get(j) + .unwrap_or(&BigInt::zero()) + .clone(), + ); + let statement_D_j_i = + PaillierAffineOpWithGroupComInRangeStatement { + S: self + .S + .get(l) + .unwrap_or(&BigInt::zero()) + .clone(), + T: self + .T + .get(l) + .unwrap_or(&BigInt::zero()) + .clone(), + N_hat: self + .N_hats + .get(l) + .unwrap_or(&BigInt::zero()) + .clone(), + N0: self.secrets.ek.n.clone(), + N1: self + .eks + .get(j) + .unwrap_or(&DEFAULT_ENCRYPTION_KEY()) + .n + .clone(), + NN0: self.secrets.ek.nn.clone(), + NN1: self + .eks + .get(j) + .unwrap_or(&DEFAULT_ENCRYPTION_KEY()) + .nn + .clone(), + C: D_j_i.clone(), + D: self + .K + .get(j) + .unwrap_or(&BigInt::zero()) + .clone(), + Y: F_j_i.clone(), + X: self.Gamma_i.clone(), + ek_prover: self.secrets.ek.clone(), + ek_verifier: self + .eks + .get(j) + .unwrap_or(&DEFAULT_ENCRYPTION_KEY()) + .clone(), + phantom: PhantomData, + }; + let D_j_i_proof = + PaillierAffineOpWithGroupComInRangeProof::< + Secp256k1, + Sha256, + >::prove( + &witness_D_j_i, &statement_D_j_i + ); + proofs_D_j_i.insert((*l, *j), D_j_i_proof); + statements_D_j_i.insert((*l, *j), statement_D_j_i); + } + }); + + // H_i proof + let H_i_randomness: BigInt = + sample_relatively_prime_integer(&self.secrets.ek.n); + let H_i: BigInt = Paillier::encrypt_with_chosen_randomness( + &self.secrets.ek, + RawPlaintext::from(BigInt::mul(&self.k_i, &self.gamma_i)), + &Randomness::from(H_i_randomness.clone()), + ) + .into(); + + let witness_H_i = PaillierMulWitness::new( + self.k_i, + self.nu_i.clone(), + self.nu_i.mul(&self.gamma_i), + ); + let statement_H_i = PaillierMulStatement { + N: self.secrets.ek.n.clone(), + NN: self.secrets.ek.nn.clone(), + C: self.G_i, + Y: self.K_i, + X: H_i.clone(), + ek_prover: self.secrets.ek.clone(), + phantom: PhantomData, + }; + + let proof_H_i = PaillierMulProof::::prove( + &witness_H_i, + &statement_H_i, + ); + + // delta_i proofs + let s_j_i = BigInt::zero(); + let ciphertext_delta_i = H_i; + let delta_i_randomness = H_i_randomness.clone(); + self.ssid.P.iter().for_each(|j| { + if *j != self.ssid.X.i { + ciphertext_delta_i + .mul(self.D_i.get(j).unwrap_or(&BigInt::zero())) + .mul( + self.F_j + .get(&self.ssid.X.i) + .unwrap_or(&BigInt::zero()), + ); + delta_i_randomness + .mul(&self.rho_i) + .mul(&s_j_i) + .mul(self.r_i.get(j).unwrap_or(&BigInt::zero())); + } + }); + + let witness_delta_i = PaillierDecryptionModQWitness::new( + Paillier::decrypt( + &self.secrets.dk, + RawCiphertext::from(ciphertext_delta_i.clone()), + ) + .into(), + H_i_randomness, + ); + + // l to statement + let mut statement_delta_i: HashMap< + u16, + PaillierDecryptionModQStatement, + > = HashMap::new(); + + // l to proof + let mut proof_delta_i: HashMap< + u16, + PaillierDecryptionModQProof, + > = HashMap::new(); + + self.ssid.P.iter().for_each(|l| { + if *l != self.ssid.X.i { + let statement_delta_l_i = PaillierDecryptionModQStatement { + S: self.S.get(l).unwrap_or(&BigInt::zero()).clone(), + T: self.T.get(l).unwrap_or(&BigInt::zero()).clone(), + N_hat: self + .N_hats + .get(l) + .unwrap_or(&BigInt::zero()) + .clone(), + N0: self.secrets.ek.n.clone(), + NN0: self.secrets.ek.nn.clone(), + C: ciphertext_delta_i.clone(), + x: self.delta_i.clone(), + ek_prover: self.secrets.ek.clone(), + phantom: PhantomData, + }; + + statement_delta_i.insert(*l, statement_delta_l_i.clone()); + + proof_delta_i.insert( + *l, + PaillierDecryptionModQProof::::prove( + &witness_delta_i, + &statement_delta_l_i, + ), + ); + } + }); + + let body = Some(IdentifiableAbortBroadcastMessage { + i: self.ssid.X.i, + statements_D_j_i, + proofs_D_j_i, + statement_H_i, + proof_H_i, + statement_delta_i, + proof_delta_i, + }); + + output.push(Msg { + sender: self.ssid.X.i, + receiver: None, + body: Box::new(body), + }); + Ok(Round4 { + ssid: self.ssid, + output: None, + transcript: None, + }) + } + } + + pub fn is_expensive(&self) -> bool { + false + } + pub fn expects_messages(i: u16, n: u16) -> Round2Messages { + P2PMsgsStore::new(i, n) + } } pub struct Round4 { - ssid: SSID, - output: Option>, - transcript: Option>, + ssid: SSID, + output: Option>, + transcript: Option>, } impl Round4 { - pub fn proceed( - self, - input: BroadcastMsgs>>, - ) -> Result, PresigningTranscript)>> { - if self.output.is_some() { - Ok(Some((self.output.unwrap(), self.transcript.unwrap()))) - } else { - for msg in input.into_vec() { - let msg = msg.unwrap(); - // si stands for sender index - let si = msg.i; - let mut vec_D_si_j_proof_bad_actors: Vec = vec![]; - // Check D_i_j proofs - self.ssid.P.iter().for_each(|j| { - if *j != self.ssid.X.i { - let D_si_j_proof = msg.proofs_D_j_i.get(&(self.ssid.X.i, *j)).unwrap(); - - let statement_D_si_j = - msg.statements_D_j_i.get(&(self.ssid.X.i, *j)).unwrap(); - - if PaillierAffineOpWithGroupComInRangeProof::::verify( - D_si_j_proof, - statement_D_si_j, - ) - .is_err() - { - vec_D_si_j_proof_bad_actors.push(*j as usize); - } - } - }); - - if !vec_D_si_j_proof_bad_actors.is_empty() { - let error_data = ProofVerificationErrorData { - proof_symbol: "D_si_j".to_string(), - verifying_party: self.ssid.X.i, - }; - return Err(PresignError::ProofVerificationError(ErrorType { - error_type: "mul".to_string(), - bad_actors: vec_D_si_j_proof_bad_actors, - data: bincode::serialize(&error_data).unwrap(), - })) - } - // Check H_j proofs - let proof_H_si = msg.proof_H_i; - let statement_H_si = msg.statement_H_i; - - if PaillierMulProof::verify(&proof_H_si, &statement_H_si).is_err() { - let error_data = ProofVerificationErrorData { - proof_symbol: "H_si".to_string(), - verifying_party: self.ssid.X.i, - }; - return Err(PresignError::ProofVerificationError(ErrorType { - error_type: "mul".to_string(), - bad_actors: vec![si.into()], - data: bincode::serialize(&error_data).unwrap(), - })) - } - // Check delta_si_proof - let proof_delta_si = msg.proof_delta_i.get(&self.ssid.X.i).unwrap(); - let statement_delta_si = msg.statement_delta_i.get(&self.ssid.X.i).unwrap(); - - if PaillierDecryptionModQProof::verify(proof_delta_si, statement_delta_si).is_err() - { - let error_data = ProofVerificationErrorData { - proof_symbol: "delta_si".to_string(), - verifying_party: self.ssid.X.i, - }; - return Err(PresignError::ProofVerificationError(ErrorType { - error_type: "dec-q".to_string(), - bad_actors: vec![si.into()], - data: bincode::serialize(&error_data).unwrap(), - })) - } - } - Ok(None) - } - } - - pub fn is_expensive(&self) -> bool { - false - } - pub fn expects_messages(i: u16, n: u16) -> Round3Messages { - BroadcastMsgsStore::new(i, n) - } + pub fn proceed( + self, + input: BroadcastMsgs< + Option>, + >, + ) -> Result< + Option<(PresigningOutput, PresigningTranscript)>, + > { + if self.output.is_some() { + Ok(Some((self.output.unwrap(), self.transcript.unwrap()))) + } else { + for msg in input.into_vec() { + let msg = msg.unwrap(); + // si stands for sender index + let si = msg.i; + let mut vec_D_si_j_proof_bad_actors: Vec = vec![]; + // Check D_i_j proofs + self.ssid.P.iter().for_each(|j| { + if *j != self.ssid.X.i { + let D_si_j_proof = + msg.proofs_D_j_i.get(&(self.ssid.X.i, *j)).unwrap(); + + let statement_D_si_j = msg + .statements_D_j_i + .get(&(self.ssid.X.i, *j)) + .unwrap(); + + if PaillierAffineOpWithGroupComInRangeProof::< + Secp256k1, + Sha256, + >::verify( + D_si_j_proof, statement_D_si_j + ) + .is_err() + { + vec_D_si_j_proof_bad_actors.push(*j as usize); + } + } + }); + + if !vec_D_si_j_proof_bad_actors.is_empty() { + let error_data = ProofVerificationErrorData { + proof_symbol: "D_si_j".to_string(), + verifying_party: self.ssid.X.i, + }; + return Err(PresignError::ProofVerificationError( + ErrorType { + error_type: "mul".to_string(), + bad_actors: vec_D_si_j_proof_bad_actors, + data: bincode::serialize(&error_data).unwrap(), + }, + )); + } + // Check H_j proofs + let proof_H_si = msg.proof_H_i; + let statement_H_si = msg.statement_H_i; + + if PaillierMulProof::verify(&proof_H_si, &statement_H_si) + .is_err() + { + let error_data = ProofVerificationErrorData { + proof_symbol: "H_si".to_string(), + verifying_party: self.ssid.X.i, + }; + return Err(PresignError::ProofVerificationError( + ErrorType { + error_type: "mul".to_string(), + bad_actors: vec![si.into()], + data: bincode::serialize(&error_data).unwrap(), + }, + )); + } + // Check delta_si_proof + let proof_delta_si = + msg.proof_delta_i.get(&self.ssid.X.i).unwrap(); + let statement_delta_si = + msg.statement_delta_i.get(&self.ssid.X.i).unwrap(); + + if PaillierDecryptionModQProof::verify( + proof_delta_si, + statement_delta_si, + ) + .is_err() + { + let error_data = ProofVerificationErrorData { + proof_symbol: "delta_si".to_string(), + verifying_party: self.ssid.X.i, + }; + return Err(PresignError::ProofVerificationError( + ErrorType { + error_type: "dec-q".to_string(), + bad_actors: vec![si.into()], + data: bincode::serialize(&error_data).unwrap(), + }, + )); + } + } + Ok(None) + } + } + + pub fn is_expensive(&self) -> bool { + false + } + pub fn expects_messages(i: u16, n: u16) -> Round3Messages { + BroadcastMsgsStore::new(i, n) + } } type Result = std::result::Result; #[derive(Error, Debug, Clone)] pub enum PresignError { - #[error("Proof Verification Error")] - ProofVerificationError(ErrorType), + #[error("Proof Verification Error")] + ProofVerificationError(ErrorType), } diff --git a/src/presign/state_machine.rs b/src/presign/state_machine.rs index a652c2a7..31db8b0f 100644 --- a/src/presign/state_machine.rs +++ b/src/presign/state_machine.rs @@ -1,368 +1,490 @@ /* - CGGMP Threshold ECDSA + CGGMP Threshold ECDSA - Copyright 2022 by Webb Technologies. + Copyright 2022 by Webb Technologies. - This file is part of cggmp library - (https://github.com/webb-tools/cggmp-threshold-ecdsa) + This file is part of cggmp library + (https://github.com/webb-tools/cggmp-threshold-ecdsa) - cggmp-threshold-ecdsa is free software: you can redistribute - it and/or modify it under the terms of the GNU General Public - License as published by the Free Software Foundation, either - version 3 of the License, or (at your option) any later version. + cggmp-threshold-ecdsa is free software: you can redistribute + it and/or modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, either + version 3 of the License, or (at your option) any later version. - @license GPL-3.0+ + @license GPL-3.0+ */ use super::{ - rounds::{Round0, Round1, Round2, Round3, Round4}, - IdentifiableAbortBroadcastMessage, PreSigningP2PMessage1, PreSigningP2PMessage2, - PreSigningP2PMessage3, PreSigningSecrets, PresigningOutput, PresigningTranscript, SSID, + rounds::{Round0, Round1, Round2, Round3, Round4}, + IdentifiableAbortBroadcastMessage, PreSigningP2PMessage1, + PreSigningP2PMessage2, PreSigningP2PMessage3, PreSigningSecrets, + PresigningOutput, PresigningTranscript, SSID, }; use curv::{elliptic::curves::Secp256k1, BigInt}; use private::InternalError; use round_based::{ - containers::{ - push::{Push, PushExt}, - BroadcastMsgs, MessageStore, P2PMsgs, Store, StoreErr, - }, - IsCritical, Msg, StateMachine, + containers::{ + push::{Push, PushExt}, + BroadcastMsgs, MessageStore, P2PMsgs, Store, StoreErr, + }, + IsCritical, Msg, StateMachine, }; use serde::{Deserialize, Serialize}; use std::{collections::HashMap, fmt, mem::replace, time::Duration}; use thiserror::Error; -// NOTE: This is a hack since in the 1st round we will need to broadcast and send P2P -// messages, but the round-based library doesn't support this. So we will use the -// `P2PMsgs` to send all the data we need peers to receive. -// FIXME: If we re-design `round-based-traits` to support sending 2 types of messages -// in the same round, we can remove this hack. +// NOTE: This is a hack since in the 1st round we will need to broadcast and +// send P2P messages, but the round-based library doesn't support this. So we +// will use the `P2PMsgs` to send all the data we need peers to receive. +// FIXME: If we re-design `round-based-traits` to support sending 2 types of +// messages in the same round, we can remove this hack. pub type Round0Messages = Store>>; pub type Round1Messages = Store>>; pub type Round2Messages = Store>>; pub type Round3Messages = - Store>>>; + Store>>>; pub struct PreSigning { - // Current round - round: R, + // Current round + round: R, - // Messages - round0_msgs: Option, - round1_msgs: Option, - round2_msgs: Option, - round3_msgs: Option, + // Messages + round0_msgs: Option, + round1_msgs: Option, + round2_msgs: Option, + round3_msgs: Option, - // Message queue - msgs_queue: Vec>, + // Message queue + msgs_queue: Vec>, - party_i: u16, + party_i: u16, - party_n: u16, + party_n: u16, } impl PreSigning { - pub fn new( - ssid: SSID, - secrets: PreSigningSecrets, - S: HashMap, - T: HashMap, - N_hats: HashMap, - l: usize, - ) -> Result { - let n = ssid.P.len(); - if n < 2 { - return Err(Error::TooFewParties) - } - - let i = ssid.X.i; - - let mut state = Self { - round: R::Round0(Box::new(Round0 { ssid, secrets, S, T, N_hats, l })), - - round0_msgs: Some(Round1::expects_messages(i, n as u16)), - round1_msgs: Some(Round2::expects_messages(i, n as u16)), - round2_msgs: Some(Round3::expects_messages(i, n as u16)), - round3_msgs: Some(Round4::expects_messages(i, n as u16)), - - msgs_queue: vec![], - party_i: i, - party_n: n as u16, - }; - - state.proceed_round(false)?; - Ok(state) - } - - fn gmap_queue<'a, T, F>(&'a mut self, mut f: F) -> impl Push> + 'a - where - F: FnMut(T) -> M + 'a, - { - (&mut self.msgs_queue).gmap(move |m: Msg| m.map_body(|m| ProtocolMessage(f(m)))) - } - - /// Proceeds round state if it received enough messages and if it's cheap to compute or - /// `may_block == true` - fn proceed_round(&mut self, may_block: bool) -> Result<()> { - let store1_wants_more = self.round0_msgs.as_ref().map(|s| s.wants_more()).unwrap_or(false); - let store2_wants_more = self.round1_msgs.as_ref().map(|s| s.wants_more()).unwrap_or(false); - let store3_wants_more = self.round2_msgs.as_ref().map(|s| s.wants_more()).unwrap_or(false); - let store4_wants_more = self.round3_msgs.as_ref().map(|s| s.wants_more()).unwrap_or(false); - - let next_state: R; - - let try_again: bool = match replace(&mut self.round, R::Gone) { - R::Round0(round) if !round.is_expensive() || may_block => { - next_state = round - .proceed(self.gmap_queue(M::Round1)) - .map(|msg| R::Round1(Box::new(msg))) - .map_err(|_e| Error::ProceedRound { msg_round: 0 })?; - true - }, - s @ R::Round0(_) => { - next_state = s; - false - }, - R::Round1(round) if !store1_wants_more && (!round.is_expensive() || may_block) => { - let store = self.round0_msgs.take().ok_or(InternalError::StoreGone)?; - let msgs = store.finish().map_err(InternalError::RetrieveRoundMessages)?; - next_state = round - .proceed(msgs, self.gmap_queue(M::Round2)) - .map(|msg| R::Round2(Box::new(msg))) - .map_err(|_e| Error::ProceedRound { msg_round: 1 })?; - true - }, - s @ R::Round1(_) => { - next_state = s; - false - }, - R::Round2(round) if !store2_wants_more && (!round.is_expensive() || may_block) => { - let store = self.round1_msgs.take().ok_or(InternalError::StoreGone)?; - let msgs = store.finish().map_err(InternalError::RetrieveRoundMessages)?; - next_state = round - .proceed(msgs, self.gmap_queue(M::Round3)) - .map(|msg| R::Round3(Box::new(msg))) - .map_err(|_e| Error::ProceedRound { msg_round: 2 })?; - true - }, - s @ R::Round2(_) => { - next_state = s; - false - }, - R::Round3(round) if !store3_wants_more && (!round.is_expensive() || may_block) => { - let store = self.round2_msgs.take().ok_or(InternalError::StoreGone)?; - let msgs = store.finish().map_err(InternalError::RetrieveRoundMessages)?; - next_state = round - .proceed(msgs, self.gmap_queue(M::Round4)) - .map(|msg| R::Round4(Box::new(msg))) - .map_err(|_e| Error::ProceedRound { msg_round: 3 })?; - true - }, - s @ R::Round3(_) => { - next_state = s; - false - }, - R::Round4(round) if !store4_wants_more && (!round.is_expensive() || may_block) => { - let store = self.round3_msgs.take().ok_or(InternalError::StoreGone)?; - let msgs = store.finish().map_err(InternalError::RetrieveRoundMessages)?; - next_state = round - .proceed(msgs) - .map(|msg| R::Final(Box::new(msg))) - .map_err(|_e| Error::ProceedRound { msg_round: 4 })?; - true - }, - s @ R::Round4(_) => { - next_state = s; - false - }, - s @ R::Final(_) | s @ R::Gone => { - next_state = s; - false - }, - }; - self.round = next_state; - if try_again { - self.proceed_round(may_block) - } else { - Ok(()) - } - } + pub fn new( + ssid: SSID, + secrets: PreSigningSecrets, + S: HashMap, + T: HashMap, + N_hats: HashMap, + l: usize, + ) -> Result { + let n = ssid.P.len(); + if n < 2 { + return Err(Error::TooFewParties); + } + + let i = ssid.X.i; + + let mut state = Self { + round: R::Round0(Box::new(Round0 { + ssid, + secrets, + S, + T, + N_hats, + l, + })), + + round0_msgs: Some(Round1::expects_messages(i, n as u16)), + round1_msgs: Some(Round2::expects_messages(i, n as u16)), + round2_msgs: Some(Round3::expects_messages(i, n as u16)), + round3_msgs: Some(Round4::expects_messages(i, n as u16)), + + msgs_queue: vec![], + party_i: i, + party_n: n as u16, + }; + + state.proceed_round(false)?; + Ok(state) + } + + fn gmap_queue<'a, T, F>(&'a mut self, mut f: F) -> impl Push> + 'a + where + F: FnMut(T) -> M + 'a, + { + (&mut self.msgs_queue) + .gmap(move |m: Msg| m.map_body(|m| ProtocolMessage(f(m)))) + } + + /// Proceeds round state if it received enough messages and if it's cheap to + /// compute or `may_block == true` + fn proceed_round(&mut self, may_block: bool) -> Result<()> { + let store1_wants_more = self + .round0_msgs + .as_ref() + .map(|s| s.wants_more()) + .unwrap_or(false); + let store2_wants_more = self + .round1_msgs + .as_ref() + .map(|s| s.wants_more()) + .unwrap_or(false); + let store3_wants_more = self + .round2_msgs + .as_ref() + .map(|s| s.wants_more()) + .unwrap_or(false); + let store4_wants_more = self + .round3_msgs + .as_ref() + .map(|s| s.wants_more()) + .unwrap_or(false); + + let next_state: R; + + let try_again: bool = match replace(&mut self.round, R::Gone) { + R::Round0(round) if !round.is_expensive() || may_block => { + next_state = round + .proceed(self.gmap_queue(M::Round1)) + .map(|msg| R::Round1(Box::new(msg))) + .map_err(|_e| Error::ProceedRound { msg_round: 0 })?; + true + } + s @ R::Round0(_) => { + next_state = s; + false + } + R::Round1(round) + if !store1_wants_more + && (!round.is_expensive() || may_block) => + { + let store = + self.round0_msgs.take().ok_or(InternalError::StoreGone)?; + let msgs = store + .finish() + .map_err(InternalError::RetrieveRoundMessages)?; + next_state = round + .proceed(msgs, self.gmap_queue(M::Round2)) + .map(|msg| R::Round2(Box::new(msg))) + .map_err(|_e| Error::ProceedRound { msg_round: 1 })?; + true + } + s @ R::Round1(_) => { + next_state = s; + false + } + R::Round2(round) + if !store2_wants_more + && (!round.is_expensive() || may_block) => + { + let store = + self.round1_msgs.take().ok_or(InternalError::StoreGone)?; + let msgs = store + .finish() + .map_err(InternalError::RetrieveRoundMessages)?; + next_state = round + .proceed(msgs, self.gmap_queue(M::Round3)) + .map(|msg| R::Round3(Box::new(msg))) + .map_err(|_e| Error::ProceedRound { msg_round: 2 })?; + true + } + s @ R::Round2(_) => { + next_state = s; + false + } + R::Round3(round) + if !store3_wants_more + && (!round.is_expensive() || may_block) => + { + let store = + self.round2_msgs.take().ok_or(InternalError::StoreGone)?; + let msgs = store + .finish() + .map_err(InternalError::RetrieveRoundMessages)?; + next_state = round + .proceed(msgs, self.gmap_queue(M::Round4)) + .map(|msg| R::Round4(Box::new(msg))) + .map_err(|_e| Error::ProceedRound { msg_round: 3 })?; + true + } + s @ R::Round3(_) => { + next_state = s; + false + } + R::Round4(round) + if !store4_wants_more + && (!round.is_expensive() || may_block) => + { + let store = + self.round3_msgs.take().ok_or(InternalError::StoreGone)?; + let msgs = store + .finish() + .map_err(InternalError::RetrieveRoundMessages)?; + next_state = round + .proceed(msgs) + .map(|msg| R::Final(Box::new(msg))) + .map_err(|_e| Error::ProceedRound { msg_round: 4 })?; + true + } + s @ R::Round4(_) => { + next_state = s; + false + } + s @ R::Final(_) | s @ R::Gone => { + next_state = s; + false + } + }; + self.round = next_state; + if try_again { + self.proceed_round(may_block) + } else { + Ok(()) + } + } } impl StateMachine for PreSigning { - type MessageBody = ProtocolMessage; - type Err = Error; - type Output = Option<(PresigningOutput, PresigningTranscript)>; - - fn handle_incoming(&mut self, msg: Msg) -> Result<()> { - let current_round = self.current_round(); - - match msg.body { - ProtocolMessage(M::Round1(m)) => { - let store = self - .round0_msgs - .as_mut() - .ok_or(Error::ReceivedOutOfOrderMessage { current_round, msg_round: 1 })?; - store - .push_msg(Msg { sender: msg.sender, receiver: msg.receiver, body: *m }) - .map_err(Error::HandleMessage)?; - self.proceed_round(false) - }, - ProtocolMessage(M::Round2(m)) => { - let store = self - .round1_msgs - .as_mut() - .ok_or(Error::ReceivedOutOfOrderMessage { current_round, msg_round: 2 })?; - store - .push_msg(Msg { sender: msg.sender, receiver: msg.receiver, body: *m }) - .map_err(Error::HandleMessage)?; - self.proceed_round(false) - }, - ProtocolMessage(M::Round3(m)) => { - let store = self - .round2_msgs - .as_mut() - .ok_or(Error::ReceivedOutOfOrderMessage { current_round, msg_round: 2 })?; - store - .push_msg(Msg { sender: msg.sender, receiver: msg.receiver, body: *m }) - .map_err(Error::HandleMessage)?; - self.proceed_round(false) - }, - ProtocolMessage(M::Round4(m)) => { - let store = self - .round3_msgs - .as_mut() - .ok_or(Error::ReceivedOutOfOrderMessage { current_round, msg_round: 2 })?; - store - .push_msg(Msg { sender: msg.sender, receiver: msg.receiver, body: *m }) - .map_err(Error::HandleMessage)?; - self.proceed_round(false) - }, - } - } - - fn message_queue(&mut self) -> &mut Vec> { - &mut self.msgs_queue - } - - fn wants_to_proceed(&self) -> bool { - let store1_wants_more = self.round0_msgs.as_ref().map(|s| s.wants_more()).unwrap_or(false); - let store2_wants_more = self.round1_msgs.as_ref().map(|s| s.wants_more()).unwrap_or(false); - let store3_wants_more = self.round2_msgs.as_ref().map(|s| s.wants_more()).unwrap_or(false); - let store4_wants_more = self.round3_msgs.as_ref().map(|s| s.wants_more()).unwrap_or(false); - - match &self.round { - R::Round0(_) => true, - R::Round1(_) => !store1_wants_more, - R::Round2(_) => !store2_wants_more, - R::Round3(_) => !store3_wants_more, - R::Round4(_) => !store4_wants_more, - R::Final(_) | R::Gone => false, - } - } - - fn proceed(&mut self) -> Result<()> { - self.proceed_round(true) - } - - fn round_timeout(&self) -> Option { - None - } - - fn round_timeout_reached(&mut self) -> Self::Err { - panic!("no timeout was set") - } - - fn is_finished(&self) -> bool { - matches!(self.round, R::Final(_)) - } - - fn pick_output(&mut self) -> Option> { - match self.round { - R::Final(_) => (), - R::Gone => return Some(Err(Error::DoublePickOutput)), - _ => return None, - } - - match replace(&mut self.round, R::Gone) { - R::Final(result) => Some(Ok(*result)), - _ => unreachable!("guaranteed by match expression above"), - } - } - - fn current_round(&self) -> u16 { - match &self.round { - R::Round0(_) => 0, - R::Round1(_) => 1, - R::Round2(_) => 2, - R::Round3(_) => 3, - R::Round4(_) => 4, - R::Final(_) | R::Gone => 5, - } - } - - fn total_rounds(&self) -> Option { - Some(2) - } - - fn party_ind(&self) -> u16 { - self.party_i - } - - fn parties(&self) -> u16 { - self.party_n - } + type MessageBody = ProtocolMessage; + type Err = Error; + type Output = + Option<(PresigningOutput, PresigningTranscript)>; + + fn handle_incoming(&mut self, msg: Msg) -> Result<()> { + let current_round = self.current_round(); + + match msg.body { + ProtocolMessage(M::Round1(m)) => { + let store = self.round0_msgs.as_mut().ok_or( + Error::ReceivedOutOfOrderMessage { + current_round, + msg_round: 1, + }, + )?; + store + .push_msg(Msg { + sender: msg.sender, + receiver: msg.receiver, + body: *m, + }) + .map_err(Error::HandleMessage)?; + self.proceed_round(false) + } + ProtocolMessage(M::Round2(m)) => { + let store = self.round1_msgs.as_mut().ok_or( + Error::ReceivedOutOfOrderMessage { + current_round, + msg_round: 2, + }, + )?; + store + .push_msg(Msg { + sender: msg.sender, + receiver: msg.receiver, + body: *m, + }) + .map_err(Error::HandleMessage)?; + self.proceed_round(false) + } + ProtocolMessage(M::Round3(m)) => { + let store = self.round2_msgs.as_mut().ok_or( + Error::ReceivedOutOfOrderMessage { + current_round, + msg_round: 2, + }, + )?; + store + .push_msg(Msg { + sender: msg.sender, + receiver: msg.receiver, + body: *m, + }) + .map_err(Error::HandleMessage)?; + self.proceed_round(false) + } + ProtocolMessage(M::Round4(m)) => { + let store = self.round3_msgs.as_mut().ok_or( + Error::ReceivedOutOfOrderMessage { + current_round, + msg_round: 2, + }, + )?; + store + .push_msg(Msg { + sender: msg.sender, + receiver: msg.receiver, + body: *m, + }) + .map_err(Error::HandleMessage)?; + self.proceed_round(false) + } + } + } + + fn message_queue(&mut self) -> &mut Vec> { + &mut self.msgs_queue + } + + fn wants_to_proceed(&self) -> bool { + let store1_wants_more = self + .round0_msgs + .as_ref() + .map(|s| s.wants_more()) + .unwrap_or(false); + let store2_wants_more = self + .round1_msgs + .as_ref() + .map(|s| s.wants_more()) + .unwrap_or(false); + let store3_wants_more = self + .round2_msgs + .as_ref() + .map(|s| s.wants_more()) + .unwrap_or(false); + let store4_wants_more = self + .round3_msgs + .as_ref() + .map(|s| s.wants_more()) + .unwrap_or(false); + + match &self.round { + R::Round0(_) => true, + R::Round1(_) => !store1_wants_more, + R::Round2(_) => !store2_wants_more, + R::Round3(_) => !store3_wants_more, + R::Round4(_) => !store4_wants_more, + R::Final(_) | R::Gone => false, + } + } + + fn proceed(&mut self) -> Result<()> { + self.proceed_round(true) + } + + fn round_timeout(&self) -> Option { + None + } + + fn round_timeout_reached(&mut self) -> Self::Err { + panic!("no timeout was set") + } + + fn is_finished(&self) -> bool { + matches!(self.round, R::Final(_)) + } + + fn pick_output(&mut self) -> Option> { + match self.round { + R::Final(_) => (), + R::Gone => return Some(Err(Error::DoublePickOutput)), + _ => return None, + } + + match replace(&mut self.round, R::Gone) { + R::Final(result) => Some(Ok(*result)), + _ => unreachable!("guaranteed by match expression above"), + } + } + + fn current_round(&self) -> u16 { + match &self.round { + R::Round0(_) => 0, + R::Round1(_) => 1, + R::Round2(_) => 2, + R::Round3(_) => 3, + R::Round4(_) => 4, + R::Final(_) | R::Gone => 5, + } + } + + fn total_rounds(&self) -> Option { + Some(2) + } + + fn party_ind(&self) -> u16 { + self.party_i + } + + fn parties(&self) -> u16 { + self.party_n + } } impl crate::traits::RoundBlame for PreSigning { - fn round_blame(&self) -> (u16, Vec) { - let store1_blame = self.round0_msgs.as_ref().map(|s| s.blame()).unwrap_or_default(); - let store2_blame = self.round1_msgs.as_ref().map(|s| s.blame()).unwrap_or_default(); - let store3_blame = self.round2_msgs.as_ref().map(|s| s.blame()).unwrap_or_default(); - let store4_blame = self.round3_msgs.as_ref().map(|s| s.blame()).unwrap_or_default(); - - let default = (0, vec![]); - match &self.round { - R::Round0(_) => default, - R::Round1(_) => store1_blame, - R::Round2(_) => store2_blame, - R::Round3(_) => store3_blame, - R::Round4(_) => store4_blame, - R::Final(_) | R::Gone => default, - } - } + fn round_blame(&self) -> (u16, Vec) { + let store1_blame = self + .round0_msgs + .as_ref() + .map(|s| s.blame()) + .unwrap_or_default(); + let store2_blame = self + .round1_msgs + .as_ref() + .map(|s| s.blame()) + .unwrap_or_default(); + let store3_blame = self + .round2_msgs + .as_ref() + .map(|s| s.blame()) + .unwrap_or_default(); + let store4_blame = self + .round3_msgs + .as_ref() + .map(|s| s.blame()) + .unwrap_or_default(); + + let default = (0, vec![]); + match &self.round { + R::Round0(_) => default, + R::Round1(_) => store1_blame, + R::Round2(_) => store2_blame, + R::Round3(_) => store3_blame, + R::Round4(_) => store4_blame, + R::Final(_) | R::Gone => default, + } + } } impl fmt::Debug for PreSigning { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let current_round = match &self.round { - R::Round0(_) => "0", - R::Round1(_) => "1", - R::Round2(_) => "2", - R::Round3(_) => "3", - R::Round4(_) => "4", - R::Final(_) => "[Final]", - R::Gone => "[Gone]", - }; - let round0_msgs = match self.round0_msgs.as_ref() { - Some(msgs) => format!("[{}/{}]", msgs.messages_received(), msgs.messages_total()), - None => "[None]".into(), - }; - let round1_msgs = match self.round1_msgs.as_ref() { - Some(msgs) => format!("[{}/{}]", msgs.messages_received(), msgs.messages_total()), - None => "[None]".into(), - }; - let _round2_msgs = match self.round2_msgs.as_ref() { - Some(msgs) => format!("[{}/{}]", msgs.messages_received(), msgs.messages_total()), - None => "[None]".into(), - }; - let _round3_msgs = match self.round3_msgs.as_ref() { - Some(msgs) => format!("[{}/{}]", msgs.messages_received(), msgs.messages_total()), - None => "[None]".into(), - }; - write!( + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let current_round = match &self.round { + R::Round0(_) => "0", + R::Round1(_) => "1", + R::Round2(_) => "2", + R::Round3(_) => "3", + R::Round4(_) => "4", + R::Final(_) => "[Final]", + R::Gone => "[Gone]", + }; + let round0_msgs = match self.round0_msgs.as_ref() { + Some(msgs) => format!( + "[{}/{}]", + msgs.messages_received(), + msgs.messages_total() + ), + None => "[None]".into(), + }; + let round1_msgs = match self.round1_msgs.as_ref() { + Some(msgs) => format!( + "[{}/{}]", + msgs.messages_received(), + msgs.messages_total() + ), + None => "[None]".into(), + }; + let _round2_msgs = match self.round2_msgs.as_ref() { + Some(msgs) => format!( + "[{}/{}]", + msgs.messages_received(), + msgs.messages_total() + ), + None => "[None]".into(), + }; + let _round3_msgs = match self.round3_msgs.as_ref() { + Some(msgs) => format!( + "[{}/{}]", + msgs.messages_received(), + msgs.messages_total() + ), + None => "[None]".into(), + }; + write!( f, "{{Key refresh at round={} round0_msgs={} round1_msgs={} queue=[len={}]}}", current_round, @@ -370,34 +492,42 @@ impl fmt::Debug for PreSigning { round1_msgs, self.msgs_queue.len() ) - } + } } // Rounds enum R { - Round0(Box), - Round1(Box), - Round2(Box), - Round3(Box), - Round4(Box), - Final(Box, PresigningTranscript)>>), - Gone, + Round0(Box), + Round1(Box), + Round2(Box), + Round3(Box), + Round4(Box), + Final( + Box< + Option<( + PresigningOutput, + PresigningTranscript, + )>, + >, + ), + Gone, } // Messages /// Protocol message which parties send on wire /// -/// Hides actual messages structure so it could be changed without breaking semver policy. +/// Hides actual messages structure so it could be changed without breaking +/// semver policy. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ProtocolMessage(M); #[derive(Debug, Clone, Serialize, Deserialize)] enum M { - Round1(Box>), - Round2(Box>), - Round3(Box>), - Round4(Box>>), + Round1(Box>), + Round2(Box>), + Round3(Box>), + Round4(Box>>), } // Error @@ -408,65 +538,67 @@ type Result = std::result::Result; #[derive(Debug, Error)] #[non_exhaustive] pub enum Error { - /// Round proceeding resulted in error - #[error("proceed round: {msg_round}")] - ProceedRound { msg_round: usize }, - - /// Too few parties (`n < 2`) - #[error("at least 2 parties are required for keygen")] - TooFewParties, - /// Threshold value `t` is not in range `[1; n-1]` - #[error("threshold is not in range [1; n-1]")] - InvalidThreshold, - /// Party index `i` is not in range `[1; n]` - #[error("party index is not in range [1; n]")] - InvalidPartyIndex, - - /// Received message didn't pass pre-validation - #[error("received message didn't pass pre-validation: {0}")] - HandleMessage(#[source] StoreErr), - /// Received message which we didn't expect to receive now (e.g. message from previous round) - #[error( + /// Round proceeding resulted in error + #[error("proceed round: {msg_round}")] + ProceedRound { msg_round: usize }, + + /// Too few parties (`n < 2`) + #[error("at least 2 parties are required for keygen")] + TooFewParties, + /// Threshold value `t` is not in range `[1; n-1]` + #[error("threshold is not in range [1; n-1]")] + InvalidThreshold, + /// Party index `i` is not in range `[1; n]` + #[error("party index is not in range [1; n]")] + InvalidPartyIndex, + + /// Received message didn't pass pre-validation + #[error("received message didn't pass pre-validation: {0}")] + HandleMessage(#[source] StoreErr), + /// Received message which we didn't expect to receive now (e.g. message + /// from previous round) + #[error( "didn't expect to receive message from round {msg_round} (being at round {current_round})" )] - ReceivedOutOfOrderMessage { current_round: u16, msg_round: u16 }, - /// [Keygen::pick_output] called twice - #[error("pick_output called twice")] - DoublePickOutput, - - /// Some internal assertions were failed, which is a bug - #[doc(hidden)] - #[error("internal error: {0:?}")] - InternalError(InternalError), + ReceivedOutOfOrderMessage { current_round: u16, msg_round: u16 }, + /// [Keygen::pick_output] called twice + #[error("pick_output called twice")] + DoublePickOutput, + + /// Some internal assertions were failed, which is a bug + #[doc(hidden)] + #[error("internal error: {0:?}")] + InternalError(InternalError), } impl IsCritical for Error { - fn is_critical(&self) -> bool { - true - } + fn is_critical(&self) -> bool { + true + } } impl From for Error { - fn from(err: InternalError) -> Self { - Self::InternalError(err) - } + fn from(err: InternalError) -> Self { + Self::InternalError(err) + } } mod private { - #[derive(Debug)] - #[non_exhaustive] - pub enum InternalError { - /// [Messages store](super::MessageStore) reported that it received all messages it wanted - /// to receive, but refused to return message container - RetrieveRoundMessages(super::StoreErr), - #[doc(hidden)] - StoreGone, - } + #[derive(Debug)] + #[non_exhaustive] + pub enum InternalError { + /// [Messages store](super::MessageStore) reported that it received all + /// messages it wanted to receive, but refused to return + /// message container + RetrieveRoundMessages(super::StoreErr), + #[doc(hidden)] + StoreGone, + } } #[cfg(test)] pub mod test { - use super::*; + use super::*; use crate::utilities::sha2::Sha256; use curv::{ arithmetic::{ @@ -483,200 +615,233 @@ pub mod test { use round_based::dev::Simulation; use std::ops::Deref; - fn simulate_keygen(t: u16, n: u16) -> Vec> { - let mut simulation = Simulation::new(); - - for i in 1..=n { - simulation.add_party(Keygen::new(i, t, n).unwrap()); - } - - simulation.run().unwrap() - } - - fn simulate_presign( - inputs: Vec<( - SSID, - PreSigningSecrets, - HashMap, // S - HashMap, // T - HashMap, // N_hats - )>, - l: usize, // pre-signing index. - ) -> Vec, PresigningTranscript)>> { - let mut simulation = Simulation::new(); - - for (ssid, secrets, S, T, N_hats) in inputs { - simulation.add_party(PreSigning::new(ssid, secrets, S, T, N_hats, l).unwrap()); - } - - simulation.run().unwrap() - } - - pub fn extract_secret_key(local_keys: &[LocalKey]) -> Scalar { - let secret_shares: Vec> = - local_keys.iter().map(|key| key.keys_linear.x_i.clone()).collect(); - local_keys[0].vss_scheme.reconstruct( - &(0..local_keys.len() as u16).collect::>(), - &secret_shares.clone(), - ) - } - - pub fn extract_k( - presign_outputs: &[Option<( - PresigningOutput, - PresigningTranscript, - )>], - ) -> Scalar { - let q = Scalar::::group_order(); - Scalar::::from_bigint( - &presign_outputs - .iter() - .filter_map(|it| it.as_ref().map(|(output, _)| output.k_i.clone())) - .fold(BigInt::from(0), |acc, x| BigInt::mod_add(&acc, &x, q)), - ) - } - - // t = threshold, n = total number of parties, p = number of participants. - // NOTE: Quorum size = t + 1. - pub fn generate_parties_and_simulate_presign( - t: u16, - n: u16, - p: u16, - ) -> ( - Vec>, - Vec>, - Vec, PresigningTranscript)>>, - ) { - // Runs keygen simulation for test parameters. - let keys = simulate_keygen(t, n); - assert_eq!(keys.len(), n as usize); - - // Extracts and verifies the shared secret key. - let sec_key = extract_secret_key(&keys); - let pub_key = keys[0].public_key(); - assert_eq!(Point::::generator() * &sec_key, pub_key); - - // Verifies that transforming of x_i, which is a (t,n) share of x, into a (t,t+1) share - // omega_i using an appropriate lagrangian coefficient lambda_{i,S} as defined by GG18 and - // GG20 works. - // Ref: https://eprint.iacr.org/2021/060.pdf (Section 1.2.8) - // Ref: https://eprint.iacr.org/2019/114.pdf (Section 4.2) - // Ref: https://eprint.iacr.org/2020/540.pdf (Section 3.2) - let secret_shares: Vec> = - keys.iter().map(|key| key.keys_linear.x_i.clone()).collect(); - let omega_shares: Vec> = keys[0..p as usize] - .iter() - .enumerate() - .map(|(idx, key)| { - let x_i = secret_shares[idx].clone(); - let lambda_i_s = VerifiableSS::::map_share_to_new_params( - &key.vss_scheme.parameters, - key.i - 1, - &(0..p).collect::>(), - ); - lambda_i_s * x_i - }) - .collect(); - let omega_sec_key = omega_shares.iter().fold(Scalar::::zero(), |acc, x| acc + x); - assert_eq!(omega_sec_key, sec_key); - - // Generates auxiliary "ring" Pedersen parameters for all participants. - let mut aux_ring_pedersen_n_hat_values = HashMap::with_capacity(keys.len()); - let mut aux_ring_pedersen_s_values = HashMap::with_capacity(keys.len()); - let mut aux_ring_pedersen_t_values = HashMap::with_capacity(keys.len()); - for idx in 1..=p { - let (ring_pedersen_params, _) = RingPedersenStatement::::generate(); - aux_ring_pedersen_n_hat_values.insert(idx, ring_pedersen_params.N); - aux_ring_pedersen_s_values.insert(idx, ring_pedersen_params.S); - aux_ring_pedersen_t_values.insert(idx, ring_pedersen_params.T); - } - - // Creates pre-signing inputs and auxiliary parameters for ZK proofs. - let generator = Point::::generator().to_point(); - let group_order = Scalar::::group_order(); - let party_indices: Vec = (1..=p).collect(); - let inputs: Vec<( - SSID, - PreSigningSecrets, - HashMap, // S - HashMap, // T - HashMap, // N_hats - )> = keys[0..p as usize] - .iter() - .map(|key| { - // Creates SSID and pre-signing secrets. - // We already have Paillier keys from GG20 keygen or FS-DKR so we just reuse them. - let paillier_ek = key.paillier_key_vec[key.i as usize - 1].clone(); - let paillier_dk = key.paillier_dk.clone(); - // Composes SSID. - // See Figure 6, Round 1. - // Ref: . - let phi = (&paillier_dk.p - BigInt::one()) * (&paillier_dk.q - BigInt::one()); - let r = BigInt::sample_below(&paillier_ek.n); - let lambda = BigInt::sample_below(&phi); - let t = BigInt::mod_pow(&r, &BigInt::from(2), &paillier_ek.n); - let s = BigInt::mod_pow(&t, &lambda, &paillier_ek.n); - let ssid = SSID { - g: generator.clone(), - q: group_order.clone(), - P: party_indices.clone(), - rid: BigInt::strict_sample(256).to_bytes().try_into().unwrap(), - X: key.clone(), - Y: None, // Y is not needed for 4-round signing. - N: paillier_ek.n.clone(), - S: s, - T: t, - }; - // Composes pre-signing secrets. - let pre_sign_secrets = PreSigningSecrets { - x_i: BigInt::from_bytes(key.keys_linear.x_i.to_bytes().deref()), - y_i: None, // Y is not needed for 4-round signing. - ek: paillier_ek, - dk: paillier_dk, - }; - - ( - ssid, - pre_sign_secrets, - aux_ring_pedersen_s_values.clone(), - aux_ring_pedersen_t_values.clone(), - aux_ring_pedersen_n_hat_values.clone(), - ) - }) - .collect(); - let ssids = inputs.iter().map(|(ssid, ..)| ssid.clone()).collect(); - - // Runs pre-signing simulation for test parameters and verifies the outputs. - let outputs = simulate_presign(inputs, 1); - // Verifies that r, the x projection of R = g^k-1 is computed correctly. - let q = Scalar::::group_order(); - let r_dist = outputs[0].as_ref().unwrap().0.R.x_coord().unwrap(); - let k = extract_k(&outputs); - let r_direct = (Point::::generator() * k.invert().unwrap()).x_coord().unwrap(); - assert_eq!(r_dist, r_direct); - // Verifies that chi_i are additive shares of kx. - let k_x = &k * &sec_key; - let chi_i_sum = Scalar::::from_bigint( - &outputs - .iter() - .filter_map(|it| it.as_ref().map(|(output, _)| output.chi_i.clone())) - .fold(BigInt::from(0), |acc, x| BigInt::mod_add(&acc, &x, q)), - ); - assert_eq!(k_x, chi_i_sum); - - // Returns generated local keys, SSIDs and pre-signing outputs. - (keys, ssids, outputs) - } - - // All parties (2/2 pre-signing). - #[test] - fn presign_all_parties_works() { - generate_parties_and_simulate_presign(1, 2, 2); - } - - // Threshold pre-signing (subset of parties) - (3/4 pre-signing). - #[test] - fn presign_threshold_works() { - generate_parties_and_simulate_presign(2, 4, 3); - } + fn simulate_keygen(t: u16, n: u16) -> Vec> { + let mut simulation = Simulation::new(); + + for i in 1..=n { + simulation.add_party(Keygen::new(i, t, n).unwrap()); + } + + simulation.run().unwrap() + } + + fn simulate_presign( + inputs: Vec<( + SSID, + PreSigningSecrets, + HashMap, // S + HashMap, // T + HashMap, // N_hats + )>, + l: usize, // pre-signing index. + ) -> Vec< + Option<(PresigningOutput, PresigningTranscript)>, + > { + let mut simulation = Simulation::new(); + + for (ssid, secrets, S, T, N_hats) in inputs { + simulation.add_party( + PreSigning::new(ssid, secrets, S, T, N_hats, l).unwrap(), + ); + } + + simulation.run().unwrap() + } + + pub fn extract_secret_key( + local_keys: &[LocalKey], + ) -> Scalar { + let secret_shares: Vec> = local_keys + .iter() + .map(|key| key.keys_linear.x_i.clone()) + .collect(); + local_keys[0].vss_scheme.reconstruct( + &(0..local_keys.len() as u16).collect::>(), + &secret_shares.clone(), + ) + } + + pub fn extract_k( + presign_outputs: &[Option<( + PresigningOutput, + PresigningTranscript, + )>], + ) -> Scalar { + let q = Scalar::::group_order(); + Scalar::::from_bigint( + &presign_outputs + .iter() + .filter_map(|it| { + it.as_ref().map(|(output, _)| output.k_i.clone()) + }) + .fold(BigInt::from(0), |acc, x| BigInt::mod_add(&acc, &x, q)), + ) + } + + // t = threshold, n = total number of parties, p = number of participants. + // NOTE: Quorum size = t + 1. + pub fn generate_parties_and_simulate_presign( + t: u16, + n: u16, + p: u16, + ) -> ( + Vec>, + Vec>, + Vec< + Option<( + PresigningOutput, + PresigningTranscript, + )>, + >, + ) { + // Runs keygen simulation for test parameters. + let keys = simulate_keygen(t, n); + assert_eq!(keys.len(), n as usize); + + // Extracts and verifies the shared secret key. + let sec_key = extract_secret_key(&keys); + let pub_key = keys[0].public_key(); + assert_eq!(Point::::generator() * &sec_key, pub_key); + + // Verifies that transforming of x_i, which is a (t,n) share of x, into + // a (t,t+1) share omega_i using an appropriate lagrangian + // coefficient lambda_{i,S} as defined by GG18 and GG20 works. + // Ref: https://eprint.iacr.org/2021/060.pdf (Section 1.2.8) + // Ref: https://eprint.iacr.org/2019/114.pdf (Section 4.2) + // Ref: https://eprint.iacr.org/2020/540.pdf (Section 3.2) + let secret_shares: Vec> = + keys.iter().map(|key| key.keys_linear.x_i.clone()).collect(); + let omega_shares: Vec> = keys[0..p as usize] + .iter() + .enumerate() + .map(|(idx, key)| { + let x_i = secret_shares[idx].clone(); + let lambda_i_s = + VerifiableSS::::map_share_to_new_params( + &key.vss_scheme.parameters, + key.i - 1, + &(0..p).collect::>(), + ); + lambda_i_s * x_i + }) + .collect(); + let omega_sec_key = omega_shares + .iter() + .fold(Scalar::::zero(), |acc, x| acc + x); + assert_eq!(omega_sec_key, sec_key); + + // Generates auxiliary "ring" Pedersen parameters for all participants. + let mut aux_ring_pedersen_n_hat_values = + HashMap::with_capacity(keys.len()); + let mut aux_ring_pedersen_s_values = HashMap::with_capacity(keys.len()); + let mut aux_ring_pedersen_t_values = HashMap::with_capacity(keys.len()); + for idx in 1..=p { + let (ring_pedersen_params, _) = + RingPedersenStatement::::generate(); + aux_ring_pedersen_n_hat_values.insert(idx, ring_pedersen_params.N); + aux_ring_pedersen_s_values.insert(idx, ring_pedersen_params.S); + aux_ring_pedersen_t_values.insert(idx, ring_pedersen_params.T); + } + + // Creates pre-signing inputs and auxiliary parameters for ZK proofs. + let generator = Point::::generator().to_point(); + let group_order = Scalar::::group_order(); + let party_indices: Vec = (1..=p).collect(); + let inputs: Vec<( + SSID, + PreSigningSecrets, + HashMap, // S + HashMap, // T + HashMap, // N_hats + )> = keys[0..p as usize] + .iter() + .map(|key| { + // Creates SSID and pre-signing secrets. + // We already have Paillier keys from GG20 keygen or FS-DKR so + // we just reuse them. + let paillier_ek = + key.paillier_key_vec[key.i as usize - 1].clone(); + let paillier_dk = key.paillier_dk.clone(); + // Composes SSID. + // See Figure 6, Round 1. + // Ref: . + let phi = (&paillier_dk.p - BigInt::one()) + * (&paillier_dk.q - BigInt::one()); + let r = BigInt::sample_below(&paillier_ek.n); + let lambda = BigInt::sample_below(&phi); + let t = BigInt::mod_pow(&r, &BigInt::from(2), &paillier_ek.n); + let s = BigInt::mod_pow(&t, &lambda, &paillier_ek.n); + let ssid = SSID { + g: generator.clone(), + q: group_order.clone(), + P: party_indices.clone(), + rid: BigInt::strict_sample(256) + .to_bytes() + .try_into() + .unwrap(), + X: key.clone(), + Y: None, // Y is not needed for 4-round signing. + N: paillier_ek.n.clone(), + S: s, + T: t, + }; + // Composes pre-signing secrets. + let pre_sign_secrets = PreSigningSecrets { + x_i: BigInt::from_bytes( + key.keys_linear.x_i.to_bytes().deref(), + ), + y_i: None, // Y is not needed for 4-round signing. + ek: paillier_ek, + dk: paillier_dk, + }; + + ( + ssid, + pre_sign_secrets, + aux_ring_pedersen_s_values.clone(), + aux_ring_pedersen_t_values.clone(), + aux_ring_pedersen_n_hat_values.clone(), + ) + }) + .collect(); + let ssids = inputs.iter().map(|(ssid, ..)| ssid.clone()).collect(); + + // Runs pre-signing simulation for test parameters and verifies the + // outputs. + let outputs = simulate_presign(inputs, 1); + // Verifies that r, the x projection of R = g^k-1 is computed correctly. + let q = Scalar::::group_order(); + let r_dist = outputs[0].as_ref().unwrap().0.R.x_coord().unwrap(); + let k = extract_k(&outputs); + let r_direct = (Point::::generator() * k.invert().unwrap()) + .x_coord() + .unwrap(); + assert_eq!(r_dist, r_direct); + // Verifies that chi_i are additive shares of kx. + let k_x = &k * &sec_key; + let chi_i_sum = Scalar::::from_bigint( + &outputs + .iter() + .filter_map(|it| { + it.as_ref().map(|(output, _)| output.chi_i.clone()) + }) + .fold(BigInt::from(0), |acc, x| BigInt::mod_add(&acc, &x, q)), + ); + assert_eq!(k_x, chi_i_sum); + + // Returns generated local keys, SSIDs and pre-signing outputs. + (keys, ssids, outputs) + } + + // All parties (2/2 pre-signing). + #[test] + fn presign_all_parties_works() { + generate_parties_and_simulate_presign(1, 2, 2); + } + + // Threshold pre-signing (subset of parties) - (3/4 pre-signing). + #[test] + fn presign_threshold_works() { + generate_parties_and_simulate_presign(2, 4, 3); + } } diff --git a/src/refresh/rounds.rs b/src/refresh/rounds.rs index 150f0897..f95d8a9d 100644 --- a/src/refresh/rounds.rs +++ b/src/refresh/rounds.rs @@ -3,235 +3,285 @@ use std::collections::HashMap; use curv::elliptic::curves::Secp256k1; use fs_dkr::{add_party_message::*, error::*, refresh_message::*}; use multi_party_ecdsa::protocols::multi_party_ecdsa::gg_2020::{ - party_i::Keys, state_machine::keygen::*, + party_i::Keys, state_machine::keygen::*, }; use paillier::DecryptionKey; use round_based::{ - containers::{push::Push, BroadcastMsgs, BroadcastMsgsStore}, - Msg, + containers::{push::Push, BroadcastMsgs, BroadcastMsgsStore}, + Msg, }; use crate::utilities::sha2::Sha256; pub enum PartyType { - Existing(Box>), - New(Box<(JoinMessage, Keys, u16)>), + Existing(Box>), + New( + Box<( + JoinMessage, + Keys, + u16, + )>, + ), } use super::state_machine::{Round0Messages, Round1Messages}; pub struct Round0 { - pub local_key_option: Option>, - pub new_party_index_option: Option, - pub old_to_new_map: HashMap, - pub new_t: u16, - pub new_n: u16, - pub current_t: u16, + pub local_key_option: Option>, + pub new_party_index_option: Option, + pub old_to_new_map: HashMap, + pub new_t: u16, + pub new_n: u16, + pub current_t: u16, } impl Round0 { - pub fn proceed(self, mut output: O) -> Result - where - O: Push>>>, - { - match self.local_key_option { - Some(local_key) => { - output.push(Msg { sender: local_key.i, receiver: None, body: None }); - match self.new_party_index_option { - None => Ok(Round1 { - party_type: PartyType::Existing(Box::new(local_key)), - old_to_new_map: self.old_to_new_map, - new_t: self.new_t, - new_n: self.new_n, - current_t: self.current_t, - }), - _ => Err(FsDkrError::NewPartyUnassignedIndexError), - } - }, - None => { - let (mut join_message, paillier_keys) = JoinMessage::distribute(); - match self.new_party_index_option { - Some(new_party_index) => { - join_message.set_party_index(new_party_index); - output.push(Msg { - sender: join_message.clone().get_party_index()?, - receiver: None, - body: Some(join_message.clone()), - }); - Ok(Round1 { - party_type: PartyType::New(Box::new(( - join_message.clone(), - paillier_keys, - new_party_index, - ))), - old_to_new_map: self.old_to_new_map, - new_t: self.new_t, - new_n: self.new_n, - current_t: self.current_t, - }) - }, - None => Err(FsDkrError::NewPartyUnassignedIndexError), - } - }, - } - } - pub fn is_expensive(&self) -> bool { - false - } + pub fn proceed(self, mut output: O) -> Result + where + O: Push< + Msg< + Option< + JoinMessage< + Secp256k1, + Sha256, + { crate::utilities::STAT_PARAM }, + >, + >, + >, + >, + { + match self.local_key_option { + Some(local_key) => { + output.push(Msg { + sender: local_key.i, + receiver: None, + body: None, + }); + match self.new_party_index_option { + None => Ok(Round1 { + party_type: PartyType::Existing(Box::new(local_key)), + old_to_new_map: self.old_to_new_map, + new_t: self.new_t, + new_n: self.new_n, + current_t: self.current_t, + }), + _ => Err(FsDkrError::NewPartyUnassignedIndexError), + } + } + None => { + let (mut join_message, paillier_keys) = + JoinMessage::distribute(); + match self.new_party_index_option { + Some(new_party_index) => { + join_message.set_party_index(new_party_index); + output.push(Msg { + sender: join_message.clone().get_party_index()?, + receiver: None, + body: Some(join_message.clone()), + }); + Ok(Round1 { + party_type: PartyType::New(Box::new(( + join_message.clone(), + paillier_keys, + new_party_index, + ))), + old_to_new_map: self.old_to_new_map, + new_t: self.new_t, + new_n: self.new_n, + current_t: self.current_t, + }) + } + None => Err(FsDkrError::NewPartyUnassignedIndexError), + } + } + } + } + pub fn is_expensive(&self) -> bool { + false + } } pub struct Round1 { - pub party_type: PartyType, - pub old_to_new_map: HashMap, - new_t: u16, - new_n: u16, - current_t: u16, + pub party_type: PartyType, + pub old_to_new_map: HashMap, + new_t: u16, + new_n: u16, + current_t: u16, } impl Round1 { - pub fn proceed( - self, - input: BroadcastMsgs< - Option>, - >, - mut output: O, - ) -> Result - where - O: Push>>>, - { - let join_message_option_vec = input.into_vec(); - let mut join_message_vec: Vec< - JoinMessage, - > = Vec::new(); - for join_message_option in join_message_option_vec.into_iter().flatten() { - join_message_vec.push(join_message_option) - } - match self.party_type { - PartyType::Existing(mut local_key) => { - // Existing parties form a refresh message and broadcast it. - let old_i = local_key.i; - let join_message_slice = join_message_vec.as_slice(); - let refresh_message_result = RefreshMessage::replace( - join_message_slice, - &mut local_key, - &self.old_to_new_map, - self.new_t, - self.new_n, - ); - let refresh_message = refresh_message_result.unwrap(); - let new_paillier_dk = refresh_message.clone().1; - output.push(Msg { - sender: old_i, - receiver: None, - body: Some(refresh_message.clone().0), - }); - Ok(Round2 { - party_type: PartyType::Existing(local_key), - join_messages: join_message_vec, - refresh_message: Some(refresh_message.0), - new_paillier_decryption_key: new_paillier_dk, - new_t: self.new_t, - new_n: self.new_n, - current_t: self.current_t, - }) - }, - - PartyType::New(boxed_new) => { - let (join_message, paillier_keys, new_party_index) = *boxed_new; - // New parties don't need to form a refresh message. - output.push(Msg { - sender: join_message.get_party_index()?, - receiver: None, - body: None, - }); - Ok(Round2 { - party_type: PartyType::New(Box::new(( - join_message, - paillier_keys.clone(), - new_party_index, - ))), - join_messages: join_message_vec, - new_paillier_decryption_key: paillier_keys.dk, - refresh_message: None, - new_t: self.new_t, - new_n: self.new_n, - current_t: self.current_t, - }) - }, - } - } - - pub fn is_expensive(&self) -> bool { - false - } - - pub fn expects_messages(i: u16, n: u16) -> Round0Messages { - BroadcastMsgsStore::new(i, n) - } + pub fn proceed( + self, + input: BroadcastMsgs< + Option< + JoinMessage< + Secp256k1, + Sha256, + { crate::utilities::STAT_PARAM }, + >, + >, + >, + mut output: O, + ) -> Result + where + O: Push< + Msg< + Option< + RefreshMessage< + Secp256k1, + Sha256, + { crate::utilities::STAT_PARAM }, + >, + >, + >, + >, + { + let join_message_option_vec = input.into_vec(); + let mut join_message_vec: Vec< + JoinMessage, + > = Vec::new(); + for join_message_option in join_message_option_vec.into_iter().flatten() + { + join_message_vec.push(join_message_option) + } + match self.party_type { + PartyType::Existing(mut local_key) => { + // Existing parties form a refresh message and broadcast it. + let old_i = local_key.i; + let join_message_slice = join_message_vec.as_slice(); + let refresh_message_result = RefreshMessage::replace( + join_message_slice, + &mut local_key, + &self.old_to_new_map, + self.new_t, + self.new_n, + ); + let refresh_message = refresh_message_result.unwrap(); + let new_paillier_dk = refresh_message.clone().1; + output.push(Msg { + sender: old_i, + receiver: None, + body: Some(refresh_message.clone().0), + }); + Ok(Round2 { + party_type: PartyType::Existing(local_key), + join_messages: join_message_vec, + refresh_message: Some(refresh_message.0), + new_paillier_decryption_key: new_paillier_dk, + new_t: self.new_t, + new_n: self.new_n, + current_t: self.current_t, + }) + } + + PartyType::New(boxed_new) => { + let (join_message, paillier_keys, new_party_index) = *boxed_new; + // New parties don't need to form a refresh message. + output.push(Msg { + sender: join_message.get_party_index()?, + receiver: None, + body: None, + }); + Ok(Round2 { + party_type: PartyType::New(Box::new(( + join_message, + paillier_keys.clone(), + new_party_index, + ))), + join_messages: join_message_vec, + new_paillier_decryption_key: paillier_keys.dk, + refresh_message: None, + new_t: self.new_t, + new_n: self.new_n, + current_t: self.current_t, + }) + } + } + } + + pub fn is_expensive(&self) -> bool { + false + } + + pub fn expects_messages(i: u16, n: u16) -> Round0Messages { + BroadcastMsgsStore::new(i, n) + } } pub struct Round2 { - pub party_type: PartyType, - pub join_messages: Vec>, - pub refresh_message: - Option>, - pub new_paillier_decryption_key: DecryptionKey, - new_t: u16, - new_n: u16, - current_t: u16, + pub party_type: PartyType, + pub join_messages: + Vec>, + pub refresh_message: Option< + RefreshMessage, + >, + pub new_paillier_decryption_key: DecryptionKey, + new_t: u16, + new_n: u16, + current_t: u16, } impl Round2 { - pub fn proceed( - self, - input: BroadcastMsgs< - Option>, - >, - ) -> Result> { - let refresh_message_option_vec = input.into_vec_including_me(self.refresh_message); - let mut refresh_message_vec: Vec< - RefreshMessage, - > = Vec::new(); - for refresh_message_option in refresh_message_option_vec.into_iter().flatten() { - refresh_message_vec.push(refresh_message_option) - } - - match self.party_type { - PartyType::Existing(mut local_key) => { - let join_message_slice = self.join_messages.as_slice(); - let refresh_message_slice = refresh_message_vec.as_slice(); - RefreshMessage::collect( - refresh_message_slice, - &mut local_key, - self.new_paillier_decryption_key, - join_message_slice, - self.current_t, - )?; - Ok(*local_key) - }, - PartyType::New(boxed_new) => { - let (join_message, paillier_keys, _new_party_index) = *boxed_new; - let join_message_slice = self.join_messages.as_slice(); - let refresh_message_slice = refresh_message_vec.as_slice(); - JoinMessage::collect( - &join_message, - refresh_message_slice, - paillier_keys, - join_message_slice, - self.new_t, - self.new_n, - self.current_t, - ) - }, - } - } - - pub fn is_expensive(&self) -> bool { - false - } - pub fn expects_messages(i: u16, n: u16) -> Round1Messages { - BroadcastMsgsStore::new(i, n) - } + pub fn proceed( + self, + input: BroadcastMsgs< + Option< + RefreshMessage< + Secp256k1, + Sha256, + { crate::utilities::STAT_PARAM }, + >, + >, + >, + ) -> Result> { + let refresh_message_option_vec = + input.into_vec_including_me(self.refresh_message); + let mut refresh_message_vec: Vec< + RefreshMessage, + > = Vec::new(); + for refresh_message_option in + refresh_message_option_vec.into_iter().flatten() + { + refresh_message_vec.push(refresh_message_option) + } + + match self.party_type { + PartyType::Existing(mut local_key) => { + let join_message_slice = self.join_messages.as_slice(); + let refresh_message_slice = refresh_message_vec.as_slice(); + RefreshMessage::collect( + refresh_message_slice, + &mut local_key, + self.new_paillier_decryption_key, + join_message_slice, + self.current_t, + )?; + Ok(*local_key) + } + PartyType::New(boxed_new) => { + let (join_message, paillier_keys, _new_party_index) = + *boxed_new; + let join_message_slice = self.join_messages.as_slice(); + let refresh_message_slice = refresh_message_vec.as_slice(); + JoinMessage::collect( + &join_message, + refresh_message_slice, + paillier_keys, + join_message_slice, + self.new_t, + self.new_n, + self.current_t, + ) + } + } + } + + pub fn is_expensive(&self) -> bool { + false + } + pub fn expects_messages(i: u16, n: u16) -> Round1Messages { + BroadcastMsgsStore::new(i, n) + } } type Result = std::result::Result; diff --git a/src/refresh/state_machine.rs b/src/refresh/state_machine.rs index 2dafcbce..e4bccf0b 100644 --- a/src/refresh/state_machine.rs +++ b/src/refresh/state_machine.rs @@ -19,281 +19,357 @@ use crate::utilities::sha2::Sha256; use std::{collections::HashMap, fmt, mem::replace, time::Duration}; use thiserror::Error; -pub type Round0Messages = - Store>>>; +pub type Round0Messages = Store< + BroadcastMsgs< + Option< + JoinMessage, + >, + >, +>; pub type Round1Messages = Store< - BroadcastMsgs>>, + BroadcastMsgs< + Option< + RefreshMessage, + >, + >, >; pub struct KeyRefresh { - // Current round - round: R, + // Current round + round: R, - // Messages - round0_msgs: Option, - round1_msgs: Option, + // Messages + round0_msgs: Option, + round1_msgs: Option, - // Message queue - msgs_queue: Vec>, + // Message queue + msgs_queue: Vec>, - party_i: u16, + party_i: u16, - party_n: u16, + party_n: u16, } impl KeyRefresh { - pub fn new( - local_key_option: Option>, - new_party_index_option: Option, - old_to_new_map: &HashMap, - new_t: u16, - new_n: u16, - current_t_option: Option, - ) -> Result { - if new_n < 2 { - return Err(Error::TooFewParties) - } - if new_t == 0 || new_t >= new_n { - return Err(Error::InvalidThreshold) - } - - // Sets the party index either from the `local_key_option` or `new_party_index_option`, - // otherwise returns an error if neither is Some. - let i = match local_key_option.as_ref().map(|it| it.i).or(new_party_index_option) { - Some(it) => it, - None => return Err(Error::MissingPartyIndex), - }; - - // Sets the current/old threshold either from the `local_key_option` or `current_t_option`, - // otherwise returns an error if neither is Some. - let current_t = match local_key_option.as_ref().map(|it| it.t).or(current_t_option) { - Some(it) => it, - None => return Err(Error::UnknownCurrentThreshold), - }; - - let mut state = Self { - round: R::Round0(Box::new(Round0 { - local_key_option, - new_party_index_option, - old_to_new_map: old_to_new_map.clone(), - new_t, - new_n, - current_t, - })), - - round0_msgs: Some(Round1::expects_messages(i, new_n)), - round1_msgs: Some(Round2::expects_messages(i, new_n)), - - msgs_queue: vec![], - - party_i: i, - - party_n: new_n, - }; - - state.proceed_round(false)?; - Ok(state) - } - - fn gmap_queue<'a, T, F>(&'a mut self, mut f: F) -> impl Push> + 'a - where - F: FnMut(T) -> M + 'a, - { - (&mut self.msgs_queue).gmap(move |m: Msg| m.map_body(|m| ProtocolMessage(f(m)))) - } - - /// Proceeds round state if it received enough messages and if it's cheap to compute or - /// `may_block == true` - fn proceed_round(&mut self, may_block: bool) -> Result<()> { - let store1_wants_more = self.round0_msgs.as_ref().map(|s| s.wants_more()).unwrap_or(false); - let store2_wants_more = self.round1_msgs.as_ref().map(|s| s.wants_more()).unwrap_or(false); - - let next_state: R; - - let try_again: bool = match replace(&mut self.round, R::Gone) { - R::Round0(round) if !round.is_expensive() || may_block => { - next_state = round - .proceed(self.gmap_queue(M::Round1)) - .map(|msg| R::Round1(Box::new(msg))) - .map_err(Error::ProceedRound)?; - true - }, - s @ R::Round0(_) => { - next_state = s; - false - }, - R::Round1(round) if !store1_wants_more && (!round.is_expensive() || may_block) => { - let store = self.round0_msgs.take().ok_or(InternalError::StoreGone)?; - let msgs = store.finish().map_err(InternalError::RetrieveRoundMessages)?; - next_state = round - .proceed(msgs, self.gmap_queue(M::Round2)) - .map(|msg| R::Round2(Box::new(msg))) - .map_err(Error::ProceedRound)?; - true - }, - s @ R::Round1(_) => { - next_state = s; - false - }, - R::Round2(round) if !store2_wants_more && (!round.is_expensive() || may_block) => { - let store = self.round1_msgs.take().ok_or(InternalError::StoreGone)?; - let msgs = store.finish().map_err(InternalError::RetrieveRoundMessages)?; - next_state = round - .proceed(msgs) - .map(|msg| R::Final(Box::new(msg))) - .map_err(Error::ProceedRound)?; - true - }, - s @ R::Round2(_) => { - next_state = s; - false - }, - s @ R::Final(_) | s @ R::Gone => { - next_state = s; - false - }, - }; - self.round = next_state; - if try_again { - self.proceed_round(may_block) - } else { - Ok(()) - } - } + pub fn new( + local_key_option: Option>, + new_party_index_option: Option, + old_to_new_map: &HashMap, + new_t: u16, + new_n: u16, + current_t_option: Option, + ) -> Result { + if new_n < 2 { + return Err(Error::TooFewParties); + } + if new_t == 0 || new_t >= new_n { + return Err(Error::InvalidThreshold); + } + + // Sets the party index either from the `local_key_option` or + // `new_party_index_option`, otherwise returns an error if + // neither is Some. + let i = match local_key_option + .as_ref() + .map(|it| it.i) + .or(new_party_index_option) + { + Some(it) => it, + None => return Err(Error::MissingPartyIndex), + }; + + // Sets the current/old threshold either from the `local_key_option` or + // `current_t_option`, otherwise returns an error if neither is + // Some. + let current_t = match local_key_option + .as_ref() + .map(|it| it.t) + .or(current_t_option) + { + Some(it) => it, + None => return Err(Error::UnknownCurrentThreshold), + }; + + let mut state = Self { + round: R::Round0(Box::new(Round0 { + local_key_option, + new_party_index_option, + old_to_new_map: old_to_new_map.clone(), + new_t, + new_n, + current_t, + })), + + round0_msgs: Some(Round1::expects_messages(i, new_n)), + round1_msgs: Some(Round2::expects_messages(i, new_n)), + + msgs_queue: vec![], + + party_i: i, + + party_n: new_n, + }; + + state.proceed_round(false)?; + Ok(state) + } + + fn gmap_queue<'a, T, F>(&'a mut self, mut f: F) -> impl Push> + 'a + where + F: FnMut(T) -> M + 'a, + { + (&mut self.msgs_queue) + .gmap(move |m: Msg| m.map_body(|m| ProtocolMessage(f(m)))) + } + + /// Proceeds round state if it received enough messages and if it's cheap to + /// compute or `may_block == true` + fn proceed_round(&mut self, may_block: bool) -> Result<()> { + let store1_wants_more = self + .round0_msgs + .as_ref() + .map(|s| s.wants_more()) + .unwrap_or(false); + let store2_wants_more = self + .round1_msgs + .as_ref() + .map(|s| s.wants_more()) + .unwrap_or(false); + + let next_state: R; + + let try_again: bool = match replace(&mut self.round, R::Gone) { + R::Round0(round) if !round.is_expensive() || may_block => { + next_state = round + .proceed(self.gmap_queue(M::Round1)) + .map(|msg| R::Round1(Box::new(msg))) + .map_err(Error::ProceedRound)?; + true + } + s @ R::Round0(_) => { + next_state = s; + false + } + R::Round1(round) + if !store1_wants_more + && (!round.is_expensive() || may_block) => + { + let store = + self.round0_msgs.take().ok_or(InternalError::StoreGone)?; + let msgs = store + .finish() + .map_err(InternalError::RetrieveRoundMessages)?; + next_state = round + .proceed(msgs, self.gmap_queue(M::Round2)) + .map(|msg| R::Round2(Box::new(msg))) + .map_err(Error::ProceedRound)?; + true + } + s @ R::Round1(_) => { + next_state = s; + false + } + R::Round2(round) + if !store2_wants_more + && (!round.is_expensive() || may_block) => + { + let store = + self.round1_msgs.take().ok_or(InternalError::StoreGone)?; + let msgs = store + .finish() + .map_err(InternalError::RetrieveRoundMessages)?; + next_state = round + .proceed(msgs) + .map(|msg| R::Final(Box::new(msg))) + .map_err(Error::ProceedRound)?; + true + } + s @ R::Round2(_) => { + next_state = s; + false + } + s @ R::Final(_) | s @ R::Gone => { + next_state = s; + false + } + }; + self.round = next_state; + if try_again { + self.proceed_round(may_block) + } else { + Ok(()) + } + } } impl StateMachine for KeyRefresh { - type MessageBody = ProtocolMessage; - type Err = Error; - type Output = LocalKey; - - fn handle_incoming(&mut self, msg: Msg) -> Result<()> { - let current_round = self.current_round(); - - match msg.body { - ProtocolMessage(M::Round1(m)) => { - let store = self - .round0_msgs - .as_mut() - .ok_or(Error::ReceivedOutOfOrderMessage { current_round, msg_round: 1 })?; - store - .push_msg(Msg { sender: msg.sender, receiver: msg.receiver, body: m }) - .map_err(Error::HandleMessage)?; - self.proceed_round(false) - }, - ProtocolMessage(M::Round2(m)) => { - let store = self - .round1_msgs - .as_mut() - .ok_or(Error::ReceivedOutOfOrderMessage { current_round, msg_round: 2 })?; - store - .push_msg(Msg { sender: msg.sender, receiver: msg.receiver, body: m }) - .map_err(Error::HandleMessage)?; - self.proceed_round(false) - }, - } - } - - fn message_queue(&mut self) -> &mut Vec> { - &mut self.msgs_queue - } - - fn wants_to_proceed(&self) -> bool { - let store1_wants_more = self.round0_msgs.as_ref().map(|s| s.wants_more()).unwrap_or(false); - let store2_wants_more = self.round1_msgs.as_ref().map(|s| s.wants_more()).unwrap_or(false); - - match &self.round { - R::Round0(_) => true, - R::Round1(_) => !store1_wants_more, - R::Round2(_) => !store2_wants_more, - R::Final(_) | R::Gone => false, - } - } - - fn proceed(&mut self) -> Result<()> { - self.proceed_round(true) - } - - fn round_timeout(&self) -> Option { - None - } - - fn round_timeout_reached(&mut self) -> Self::Err { - panic!("no timeout was set") - } - - fn is_finished(&self) -> bool { - matches!(self.round, R::Final(_)) - } - - fn pick_output(&mut self) -> Option> { - match self.round { - R::Final(_) => (), - R::Gone => return Some(Err(Error::DoublePickOutput)), - _ => return None, - } - - match replace(&mut self.round, R::Gone) { - R::Final(result) => Some(Ok(*result)), - _ => unreachable!("guaranteed by match expression above"), - } - } - - fn current_round(&self) -> u16 { - match &self.round { - R::Round0(_) => 0, - R::Round1(_) => 1, - R::Round2(_) => 2, - R::Final(_) | R::Gone => 3, - } - } - - fn total_rounds(&self) -> Option { - Some(2) - } - - fn party_ind(&self) -> u16 { - self.party_i - } - - fn parties(&self) -> u16 { - self.party_n - } + type MessageBody = ProtocolMessage; + type Err = Error; + type Output = LocalKey; + + fn handle_incoming(&mut self, msg: Msg) -> Result<()> { + let current_round = self.current_round(); + + match msg.body { + ProtocolMessage(M::Round1(m)) => { + let store = self.round0_msgs.as_mut().ok_or( + Error::ReceivedOutOfOrderMessage { + current_round, + msg_round: 1, + }, + )?; + store + .push_msg(Msg { + sender: msg.sender, + receiver: msg.receiver, + body: m, + }) + .map_err(Error::HandleMessage)?; + self.proceed_round(false) + } + ProtocolMessage(M::Round2(m)) => { + let store = self.round1_msgs.as_mut().ok_or( + Error::ReceivedOutOfOrderMessage { + current_round, + msg_round: 2, + }, + )?; + store + .push_msg(Msg { + sender: msg.sender, + receiver: msg.receiver, + body: m, + }) + .map_err(Error::HandleMessage)?; + self.proceed_round(false) + } + } + } + + fn message_queue(&mut self) -> &mut Vec> { + &mut self.msgs_queue + } + + fn wants_to_proceed(&self) -> bool { + let store1_wants_more = self + .round0_msgs + .as_ref() + .map(|s| s.wants_more()) + .unwrap_or(false); + let store2_wants_more = self + .round1_msgs + .as_ref() + .map(|s| s.wants_more()) + .unwrap_or(false); + + match &self.round { + R::Round0(_) => true, + R::Round1(_) => !store1_wants_more, + R::Round2(_) => !store2_wants_more, + R::Final(_) | R::Gone => false, + } + } + + fn proceed(&mut self) -> Result<()> { + self.proceed_round(true) + } + + fn round_timeout(&self) -> Option { + None + } + + fn round_timeout_reached(&mut self) -> Self::Err { + panic!("no timeout was set") + } + + fn is_finished(&self) -> bool { + matches!(self.round, R::Final(_)) + } + + fn pick_output(&mut self) -> Option> { + match self.round { + R::Final(_) => (), + R::Gone => return Some(Err(Error::DoublePickOutput)), + _ => return None, + } + + match replace(&mut self.round, R::Gone) { + R::Final(result) => Some(Ok(*result)), + _ => unreachable!("guaranteed by match expression above"), + } + } + + fn current_round(&self) -> u16 { + match &self.round { + R::Round0(_) => 0, + R::Round1(_) => 1, + R::Round2(_) => 2, + R::Final(_) | R::Gone => 3, + } + } + + fn total_rounds(&self) -> Option { + Some(2) + } + + fn party_ind(&self) -> u16 { + self.party_i + } + + fn parties(&self) -> u16 { + self.party_n + } } impl crate::traits::RoundBlame for KeyRefresh { - fn round_blame(&self) -> (u16, Vec) { - let store1_blame = self.round0_msgs.as_ref().map(|s| s.blame()).unwrap_or_default(); - let store2_blame = self.round1_msgs.as_ref().map(|s| s.blame()).unwrap_or_default(); - - let default = (0, vec![]); - match &self.round { - R::Round0(_) => default, - R::Round1(_) => store1_blame, - R::Round2(_) => store2_blame, - R::Final(_) | R::Gone => default, - } - } + fn round_blame(&self) -> (u16, Vec) { + let store1_blame = self + .round0_msgs + .as_ref() + .map(|s| s.blame()) + .unwrap_or_default(); + let store2_blame = self + .round1_msgs + .as_ref() + .map(|s| s.blame()) + .unwrap_or_default(); + + let default = (0, vec![]); + match &self.round { + R::Round0(_) => default, + R::Round1(_) => store1_blame, + R::Round2(_) => store2_blame, + R::Final(_) | R::Gone => default, + } + } } impl fmt::Debug for KeyRefresh { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let current_round = match &self.round { - R::Round0(_) => "0", - R::Round1(_) => "1", - R::Round2(_) => "2", - R::Final(_) => "[Final]", - R::Gone => "[Gone]", - }; - let round0_msgs = match self.round0_msgs.as_ref() { - Some(msgs) => format!("[{}/{}]", msgs.messages_received(), msgs.messages_total()), - None => "[None]".into(), - }; - let round1_msgs = match self.round1_msgs.as_ref() { - Some(msgs) => format!("[{}/{}]", msgs.messages_received(), msgs.messages_total()), - None => "[None]".into(), - }; - write!( + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let current_round = match &self.round { + R::Round0(_) => "0", + R::Round1(_) => "1", + R::Round2(_) => "2", + R::Final(_) => "[Final]", + R::Gone => "[Gone]", + }; + let round0_msgs = match self.round0_msgs.as_ref() { + Some(msgs) => format!( + "[{}/{}]", + msgs.messages_received(), + msgs.messages_total() + ), + None => "[None]".into(), + }; + let round1_msgs = match self.round1_msgs.as_ref() { + Some(msgs) => format!( + "[{}/{}]", + msgs.messages_received(), + msgs.messages_total() + ), + None => "[None]".into(), + }; + write!( f, "{{Key refresh at round={} round0_msgs={} round1_msgs={} queue=[len={}]}}", current_round, @@ -301,30 +377,40 @@ impl fmt::Debug for KeyRefresh { round1_msgs, self.msgs_queue.len() ) - } + } } // Rounds enum R { - Round0(Box), - Round1(Box), - Round2(Box), - Final(Box>), - Gone, + Round0(Box), + Round1(Box), + Round2(Box), + Final(Box>), + Gone, } // Messages /// Protocol message which parties send on wire /// -/// Hides actual messages structure so it could be changed without breaking semver policy. +/// Hides actual messages structure so it could be changed without breaking +/// semver policy. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ProtocolMessage(M); #[derive(Debug, Clone, Serialize, Deserialize)] +#[allow(clippy::large_enum_variant)] enum M { - Round1(Option>), - Round2(Option>), + Round1( + Option< + JoinMessage, + >, + ), + Round2( + Option< + RefreshMessage, + >, + ), } // Error @@ -335,73 +421,75 @@ type Result = std::result::Result; #[derive(Debug, Error)] #[non_exhaustive] pub enum Error { - /// Round proceeding resulted in error - #[error("proceed round: {0}")] - ProceedRound(#[source] FsDkrError), - - /// Too few parties (`n < 2`) - #[error("at least 2 parties are required for keygen")] - TooFewParties, - /// Threshold value `t` is not in range `[1; n-1]` - #[error("threshold is not in range [1; n-1]")] - InvalidThreshold, - /// Party index `i` is not in range `[1; n]` - #[error("party index is not in range [1; n]")] - InvalidPartyIndex, - /// No index set for new party - #[error("no index set for party")] - MissingPartyIndex, - /// Party doesn't know the current threshold - #[error("party doesn't know the current threshold")] - UnknownCurrentThreshold, - - /// Received message didn't pass pre-validation - #[error("received message didn't pass pre-validation: {0}")] - HandleMessage(#[source] StoreErr), - /// Received message which we didn't expect to receive now (e.g. message from previous round) - #[error( + /// Round proceeding resulted in error + #[error("proceed round: {0}")] + ProceedRound(#[source] FsDkrError), + + /// Too few parties (`n < 2`) + #[error("at least 2 parties are required for keygen")] + TooFewParties, + /// Threshold value `t` is not in range `[1; n-1]` + #[error("threshold is not in range [1; n-1]")] + InvalidThreshold, + /// Party index `i` is not in range `[1; n]` + #[error("party index is not in range [1; n]")] + InvalidPartyIndex, + /// No index set for new party + #[error("no index set for party")] + MissingPartyIndex, + /// Party doesn't know the current threshold + #[error("party doesn't know the current threshold")] + UnknownCurrentThreshold, + + /// Received message didn't pass pre-validation + #[error("received message didn't pass pre-validation: {0}")] + HandleMessage(#[source] StoreErr), + /// Received message which we didn't expect to receive now (e.g. message + /// from previous round) + #[error( "didn't expect to receive message from round {msg_round} (being at round {current_round})" )] - ReceivedOutOfOrderMessage { current_round: u16, msg_round: u16 }, - /// [Keygen::pick_output] called twice - #[error("pick_output called twice")] - DoublePickOutput, - - /// Some internal assertions were failed, which is a bug - #[doc(hidden)] - #[error("internal error: {0:?}")] - InternalError(InternalError), + ReceivedOutOfOrderMessage { current_round: u16, msg_round: u16 }, + /// [Keygen::pick_output] called twice + #[error("pick_output called twice")] + DoublePickOutput, + + /// Some internal assertions were failed, which is a bug + #[doc(hidden)] + #[error("internal error: {0:?}")] + InternalError(InternalError), } impl IsCritical for Error { - fn is_critical(&self) -> bool { - true - } + fn is_critical(&self) -> bool { + true + } } impl From for Error { - fn from(err: InternalError) -> Self { - Self::InternalError(err) - } + fn from(err: InternalError) -> Self { + Self::InternalError(err) + } } mod private { - #[derive(Debug)] - #[non_exhaustive] - pub enum InternalError { - /// [Messages store](super::MessageStore) reported that it received all messages it wanted - /// to receive, but refused to return message container - RetrieveRoundMessages(super::StoreErr), - #[doc(hidden)] - StoreGone, - } + #[derive(Debug)] + #[non_exhaustive] + pub enum InternalError { + /// [Messages store](super::MessageStore) reported that it received all + /// messages it wanted to receive, but refused to return + /// message container + RetrieveRoundMessages(super::StoreErr), + #[doc(hidden)] + StoreGone, + } } #[cfg(test)] pub mod test { - use std::collections::HashMap; + use std::collections::HashMap; - use crate::refresh::state_machine::KeyRefresh; + use crate::refresh::state_machine::KeyRefresh; #[allow(unused_imports)] use curv::{ cryptographic_primitives::{ @@ -413,389 +501,494 @@ pub mod test { use multi_party_ecdsa::protocols::multi_party_ecdsa::gg_2020::state_machine::keygen::*; use round_based::dev::Simulation; - #[allow(dead_code)] - fn simulate_keygen(t: u16, n: u16) -> Vec> { - //simulate keygen - let mut simulation = Simulation::new(); - simulation.enable_benchmarks(false); - - for i in 1..=n { - simulation.add_party(Keygen::new(i, t, n).unwrap()); - } - - simulation.run().unwrap() - } - - // Refresh Keys: Only Existing Parties (No New Parties) - pub fn simulate_dkr_with_no_replacements( - old_local_keys: Vec>, - old_to_new_map: &HashMap, - ) -> Vec> { - let mut simulation = Simulation::new(); - simulation.enable_benchmarks(false); - - for old_local_key in old_local_keys { - simulation.add_party( - KeyRefresh::new( - Some(old_local_key.clone()), - None, - old_to_new_map, - old_local_key.t, - old_local_key.n, - None, - ) - .unwrap(), - ); - } - - simulation.run().unwrap() - } - - #[test] - pub fn test_dkr_with_no_new_parties() { - let t = 2; - let n = 5; - let local_keys = simulate_keygen(t, n); - - let old_local_keys = local_keys.clone(); - let mut old_to_new_map = HashMap::new(); - old_to_new_map.insert(1, 2); - old_to_new_map.insert(2, 1); - old_to_new_map.insert(3, 4); - old_to_new_map.insert(4, 3); - old_to_new_map.insert(5, 5); - let mut new_local_keys = simulate_dkr_with_no_replacements(local_keys, &old_to_new_map); - new_local_keys.sort_by(|a, b| a.i.cmp(&b.i)); - let old_linear_secret_key: Vec<_> = (0..old_local_keys.len()) - .map(|i| old_local_keys[i].keys_linear.x_i.clone()) - .collect(); - - let new_linear_secret_key: Vec<_> = (0..new_local_keys.len()) - .map(|i| new_local_keys[i].keys_linear.x_i.clone()) - .collect(); - let indices: Vec<_> = (0..(t + 1)).collect(); - let vss = VerifiableSS:: { - parameters: ShamirSecretSharing { threshold: t, share_count: n }, - commitments: Vec::new(), - proof: DLogProof::::prove(&Scalar::random()), - }; - - assert_eq!( - vss.reconstruct(&indices[..], &old_linear_secret_key[0..(t + 1) as usize]), - vss.reconstruct(&indices[..], &new_linear_secret_key[0..(t + 1) as usize]) - ); - assert_ne!(old_linear_secret_key, new_linear_secret_key); - } - - // Refresh Keys: All Existing Parties Stay, New Parties Join - pub fn simulate_dkr_with_new_parties( - old_local_keys: Vec>, - new_party_indices: Vec, - old_to_new_map: HashMap, - t: u16, - n: u16, - ) -> Vec> { - let mut simulation = Simulation::new(); - simulation.enable_benchmarks(false); - - for old_local_key in old_local_keys { - simulation.add_party( - KeyRefresh::new( - Some(old_local_key.clone()), - None, - &old_to_new_map, - old_local_key.clone().t, - n, - None, - ) - .unwrap(), - ); - } - - for index in new_party_indices { - simulation.add_party( - KeyRefresh::new(None, Some(index), &old_to_new_map, t, n, Some(t)).unwrap(), - ); - } - simulation.run().unwrap() - } - - #[test] - pub fn test_dkr_with_new_parties() { - let t = 2; - let n = 5; - let local_keys = simulate_keygen(t, n); - let mut old_to_new_map = HashMap::new(); - old_to_new_map.insert(1, 1); - old_to_new_map.insert(2, 2); - old_to_new_map.insert(3, 3); - old_to_new_map.insert(4, 4); - old_to_new_map.insert(5, 5); - let old_local_keys = local_keys.clone(); - let new_local_keys = - simulate_dkr_with_new_parties(local_keys, vec![6, 7], old_to_new_map, t, n + 2); - - let old_linear_secret_key: Vec<_> = (0..old_local_keys.len()) - .map(|i| old_local_keys[i].keys_linear.x_i.clone()) - .collect(); - - let new_linear_secret_key: Vec<_> = (0..new_local_keys.len()) - .map(|i| new_local_keys[i].keys_linear.x_i.clone()) - .collect(); - let indices: Vec<_> = (0..(t + 1)).collect(); - let vss = VerifiableSS:: { - parameters: ShamirSecretSharing { threshold: t, share_count: n }, - commitments: Vec::new(), - proof: DLogProof::::prove(&Scalar::random()), - }; - - let new_vss = VerifiableSS:: { - parameters: ShamirSecretSharing { threshold: t, share_count: n + 2 }, - commitments: Vec::new(), - proof: DLogProof::::prove(&Scalar::random()), - }; - - assert_eq!( - vss.reconstruct(&indices[..], &old_linear_secret_key[0..(t + 1) as usize]), - new_vss.reconstruct(&indices[..], &new_linear_secret_key[0..(t + 1) as usize]) - ); - assert_ne!(old_linear_secret_key, new_linear_secret_key); - } - - // Refresh keys with remove parties - - pub fn simulate_dkr_with_remove_parties( - old_local_keys: Vec>, - remaining_old_party_indices: Vec, - new_party_indices: Vec, - old_to_new_map: HashMap, - t: u16, - n: u16, - ) -> Vec> { - let mut simulation = Simulation::new(); - simulation.enable_benchmarks(false); - - let mut non_removed_parties_vec: Vec> = vec![]; - for old_local_key in old_local_keys { - if remaining_old_party_indices.contains(&old_local_key.i) { - non_removed_parties_vec.push(old_local_key); - } else { - } - } - - for non_removed_local_key in non_removed_parties_vec { - simulation.add_party( - KeyRefresh::new( - Some(non_removed_local_key.clone()), - None, - &old_to_new_map, - non_removed_local_key.clone().t, - n, - None, - ) - .unwrap(), - ); - } - - for index in new_party_indices { - simulation.add_party( - KeyRefresh::new(None, Some(index), &old_to_new_map, t, n, Some(t)).unwrap(), - ); - } - - simulation.run().unwrap() - } - - #[test] - pub fn test_dkr_with_remove_parties() { - let t = 2; - let n = 5; - let local_keys = simulate_keygen(t, n); - - let old_local_keys = local_keys.clone(); - let mut old_to_new_map = HashMap::new(); - old_to_new_map.insert(1, 1); - old_to_new_map.insert(2, 2); - old_to_new_map.insert(3, 3); - old_to_new_map.insert(4, 4); - let new_local_keys = simulate_dkr_with_remove_parties( - local_keys, - vec![1, 2, 3, 4], - vec![], - old_to_new_map, - t, - n - 1, - ); - - let old_linear_secret_key: Vec<_> = (0..old_local_keys.len()) - .map(|i| old_local_keys[i].keys_linear.x_i.clone()) - .collect(); - - let new_linear_secret_key: Vec<_> = (0..new_local_keys.len()) - .map(|i| new_local_keys[i].keys_linear.x_i.clone()) - .collect(); - let indices: Vec<_> = (0..(t + 1)).collect(); - let vss = VerifiableSS:: { - parameters: ShamirSecretSharing { threshold: t, share_count: n }, - commitments: Vec::new(), - proof: DLogProof::::prove(&Scalar::random()), - }; - - assert_eq!( - vss.reconstruct(&indices[..], &old_linear_secret_key[0..(t + 1) as usize]), - vss.reconstruct(&indices[..], &new_linear_secret_key[0..(t + 1) as usize]) - ); - assert_ne!(old_linear_secret_key, new_linear_secret_key); - } - - // Refresh Keys: Some Existing Parties Leave, New Parties Replace Them - pub fn simulate_dkr_with_replace_parties( - old_local_keys: Vec>, - new_party_indices: Vec, - old_to_new_map: HashMap, - t: u16, - n: u16, - ) -> Vec> { - let mut simulation = Simulation::new(); - simulation.enable_benchmarks(false); - - for old_local_key in old_local_keys { - if !new_party_indices.contains(&old_local_key.i) { - simulation.add_party( - KeyRefresh::new( - Some(old_local_key.clone()), - None, - &old_to_new_map, - old_local_key.clone().t, - n, - None, - ) - .unwrap(), - ); - } else { - } - } - - for index in new_party_indices { - simulation.add_party( - KeyRefresh::new(None, Some(index), &old_to_new_map, t, n, Some(t)).unwrap(), - ); - } - simulation.run().unwrap() - } - - #[test] - pub fn test_dkr_with_replace_parties() { - let t = 2; - let n = 5; - let local_keys = simulate_keygen(t, n); - let old_local_keys = local_keys.clone(); - let mut old_to_new_map = HashMap::new(); - old_to_new_map.insert(1, 3); - old_to_new_map.insert(3, 1); - old_to_new_map.insert(4, 5); - old_to_new_map.insert(5, 4); - let mut new_local_keys = - simulate_dkr_with_replace_parties(local_keys, vec![2, 6], old_to_new_map, t, n + 1); - new_local_keys.sort_by(|a, b| a.i.cmp(&b.i)); - let old_linear_secret_key: Vec<_> = (0..old_local_keys.len()) - .map(|i| old_local_keys[i].keys_linear.x_i.clone()) - .collect(); - - let new_linear_secret_key: Vec<_> = (0..new_local_keys.len()) - .map(|i| new_local_keys[i].keys_linear.x_i.clone()) - .collect(); - - let old_indices = vec![0, 1, 2]; - let new_indices = vec![0, 1, 2]; - let vss = VerifiableSS:: { - parameters: ShamirSecretSharing { threshold: t, share_count: n }, - commitments: Vec::new(), - proof: DLogProof::::prove(&Scalar::random()), - }; - - let new_vss = VerifiableSS:: { - parameters: ShamirSecretSharing { threshold: t, share_count: n + 1 }, - commitments: Vec::new(), - proof: DLogProof::::prove(&Scalar::random()), - }; - - assert_eq!( - vss.reconstruct(&old_indices[..], &old_linear_secret_key[0..(t + 1) as usize]), - new_vss.reconstruct(&new_indices[..], &new_linear_secret_key[0..(t + 1) as usize]) - ); - assert_ne!(old_linear_secret_key, new_linear_secret_key); - } - - // Refresh Keys: New Threshold, Only Existing Parties (No New Parties). - pub fn simulate_dkr_with_new_threshold( - old_local_keys: Vec>, - old_to_new_map: &HashMap, - new_t: u16, - ) -> Vec> { - let mut simulation = Simulation::new(); - simulation.enable_benchmarks(false); - - for old_local_key in old_local_keys { - simulation.add_party( - KeyRefresh::new( - Some(old_local_key.clone()), - None, - old_to_new_map, - new_t, - old_local_key.n, - None, - ) - .unwrap(), - ); - } - - simulation.run().unwrap() - } - - #[test] - pub fn test_dkr_with_new_threshold() { - let init_t = 2; // initial threshold. - let new_t = 1; // new/final threshold. - let n = 5; - let local_keys = simulate_keygen(init_t, n); - - let old_local_keys = local_keys.clone(); - let mut old_to_new_map = HashMap::new(); - old_to_new_map.insert(1, 2); - old_to_new_map.insert(2, 1); - old_to_new_map.insert(3, 4); - old_to_new_map.insert(4, 3); - old_to_new_map.insert(5, 5); - let mut new_local_keys = - simulate_dkr_with_new_threshold(local_keys, &old_to_new_map, new_t); - new_local_keys.sort_by(|a, b| a.i.cmp(&b.i)); - - let old_linear_secret_key: Vec<_> = (0..old_local_keys.len()) - .map(|i| old_local_keys[i].keys_linear.x_i.clone()) - .collect(); - let new_linear_secret_key: Vec<_> = (0..new_local_keys.len()) - .map(|i| new_local_keys[i].keys_linear.x_i.clone()) - .collect(); - - let old_indices: Vec<_> = (0..(init_t + 1)).collect(); - let new_indices: Vec<_> = (0..(new_t + 1)).collect(); - - let vss = VerifiableSS:: { - parameters: ShamirSecretSharing { threshold: init_t, share_count: n }, - commitments: Vec::new(), - proof: DLogProof::::prove(&Scalar::random()), - }; - let new_vss = VerifiableSS:: { - parameters: ShamirSecretSharing { threshold: new_t, share_count: n }, - commitments: Vec::new(), - proof: DLogProof::::prove(&Scalar::random()), - }; - - assert_eq!( - vss.reconstruct(&old_indices[..], &old_linear_secret_key[0..(init_t + 1) as usize]), - new_vss.reconstruct(&new_indices[..], &new_linear_secret_key[0..(new_t + 1) as usize]) - ); - assert_ne!(old_linear_secret_key, new_linear_secret_key); - } + #[allow(dead_code)] + fn simulate_keygen(t: u16, n: u16) -> Vec> { + //simulate keygen + let mut simulation = Simulation::new(); + simulation.enable_benchmarks(false); + + for i in 1..=n { + simulation.add_party(Keygen::new(i, t, n).unwrap()); + } + + simulation.run().unwrap() + } + + // Refresh Keys: Only Existing Parties (No New Parties) + pub fn simulate_dkr_with_no_replacements( + old_local_keys: Vec>, + old_to_new_map: &HashMap, + ) -> Vec> { + let mut simulation = Simulation::new(); + simulation.enable_benchmarks(false); + + for old_local_key in old_local_keys { + simulation.add_party( + KeyRefresh::new( + Some(old_local_key.clone()), + None, + old_to_new_map, + old_local_key.t, + old_local_key.n, + None, + ) + .unwrap(), + ); + } + + simulation.run().unwrap() + } + + #[test] + pub fn test_dkr_with_no_new_parties() { + let t = 2; + let n = 5; + let local_keys = simulate_keygen(t, n); + + let old_local_keys = local_keys.clone(); + let mut old_to_new_map = HashMap::new(); + old_to_new_map.insert(1, 2); + old_to_new_map.insert(2, 1); + old_to_new_map.insert(3, 4); + old_to_new_map.insert(4, 3); + old_to_new_map.insert(5, 5); + let mut new_local_keys = + simulate_dkr_with_no_replacements(local_keys, &old_to_new_map); + new_local_keys.sort_by(|a, b| a.i.cmp(&b.i)); + let old_linear_secret_key: Vec<_> = (0..old_local_keys.len()) + .map(|i| old_local_keys[i].keys_linear.x_i.clone()) + .collect(); + + let new_linear_secret_key: Vec<_> = (0..new_local_keys.len()) + .map(|i| new_local_keys[i].keys_linear.x_i.clone()) + .collect(); + let indices: Vec<_> = (0..(t + 1)).collect(); + let vss = VerifiableSS:: { + parameters: ShamirSecretSharing { + threshold: t, + share_count: n, + }, + commitments: Vec::new(), + proof: DLogProof::::prove( + &Scalar::random(), + ), + }; + + assert_eq!( + vss.reconstruct( + &indices[..], + &old_linear_secret_key[0..(t + 1) as usize] + ), + vss.reconstruct( + &indices[..], + &new_linear_secret_key[0..(t + 1) as usize] + ) + ); + assert_ne!(old_linear_secret_key, new_linear_secret_key); + } + + // Refresh Keys: All Existing Parties Stay, New Parties Join + pub fn simulate_dkr_with_new_parties( + old_local_keys: Vec>, + new_party_indices: Vec, + old_to_new_map: HashMap, + t: u16, + n: u16, + ) -> Vec> { + let mut simulation = Simulation::new(); + simulation.enable_benchmarks(false); + + for old_local_key in old_local_keys { + simulation.add_party( + KeyRefresh::new( + Some(old_local_key.clone()), + None, + &old_to_new_map, + old_local_key.clone().t, + n, + None, + ) + .unwrap(), + ); + } + + for index in new_party_indices { + simulation.add_party( + KeyRefresh::new( + None, + Some(index), + &old_to_new_map, + t, + n, + Some(t), + ) + .unwrap(), + ); + } + simulation.run().unwrap() + } + + #[test] + pub fn test_dkr_with_new_parties() { + let t = 2; + let n = 5; + let local_keys = simulate_keygen(t, n); + let mut old_to_new_map = HashMap::new(); + old_to_new_map.insert(1, 1); + old_to_new_map.insert(2, 2); + old_to_new_map.insert(3, 3); + old_to_new_map.insert(4, 4); + old_to_new_map.insert(5, 5); + let old_local_keys = local_keys.clone(); + let new_local_keys = simulate_dkr_with_new_parties( + local_keys, + vec![6, 7], + old_to_new_map, + t, + n + 2, + ); + + let old_linear_secret_key: Vec<_> = (0..old_local_keys.len()) + .map(|i| old_local_keys[i].keys_linear.x_i.clone()) + .collect(); + + let new_linear_secret_key: Vec<_> = (0..new_local_keys.len()) + .map(|i| new_local_keys[i].keys_linear.x_i.clone()) + .collect(); + let indices: Vec<_> = (0..(t + 1)).collect(); + let vss = VerifiableSS:: { + parameters: ShamirSecretSharing { + threshold: t, + share_count: n, + }, + commitments: Vec::new(), + proof: DLogProof::::prove( + &Scalar::random(), + ), + }; + + let new_vss = VerifiableSS:: { + parameters: ShamirSecretSharing { + threshold: t, + share_count: n + 2, + }, + commitments: Vec::new(), + proof: DLogProof::::prove( + &Scalar::random(), + ), + }; + + assert_eq!( + vss.reconstruct( + &indices[..], + &old_linear_secret_key[0..(t + 1) as usize] + ), + new_vss.reconstruct( + &indices[..], + &new_linear_secret_key[0..(t + 1) as usize] + ) + ); + assert_ne!(old_linear_secret_key, new_linear_secret_key); + } + + // Refresh keys with remove parties + + pub fn simulate_dkr_with_remove_parties( + old_local_keys: Vec>, + remaining_old_party_indices: Vec, + new_party_indices: Vec, + old_to_new_map: HashMap, + t: u16, + n: u16, + ) -> Vec> { + let mut simulation = Simulation::new(); + simulation.enable_benchmarks(false); + + let mut non_removed_parties_vec: Vec> = vec![]; + for old_local_key in old_local_keys { + if remaining_old_party_indices.contains(&old_local_key.i) { + non_removed_parties_vec.push(old_local_key); + } else { + } + } + + for non_removed_local_key in non_removed_parties_vec { + simulation.add_party( + KeyRefresh::new( + Some(non_removed_local_key.clone()), + None, + &old_to_new_map, + non_removed_local_key.clone().t, + n, + None, + ) + .unwrap(), + ); + } + + for index in new_party_indices { + simulation.add_party( + KeyRefresh::new( + None, + Some(index), + &old_to_new_map, + t, + n, + Some(t), + ) + .unwrap(), + ); + } + + simulation.run().unwrap() + } + + #[test] + pub fn test_dkr_with_remove_parties() { + let t = 2; + let n = 5; + let local_keys = simulate_keygen(t, n); + + let old_local_keys = local_keys.clone(); + let mut old_to_new_map = HashMap::new(); + old_to_new_map.insert(1, 1); + old_to_new_map.insert(2, 2); + old_to_new_map.insert(3, 3); + old_to_new_map.insert(4, 4); + let new_local_keys = simulate_dkr_with_remove_parties( + local_keys, + vec![1, 2, 3, 4], + vec![], + old_to_new_map, + t, + n - 1, + ); + + let old_linear_secret_key: Vec<_> = (0..old_local_keys.len()) + .map(|i| old_local_keys[i].keys_linear.x_i.clone()) + .collect(); + + let new_linear_secret_key: Vec<_> = (0..new_local_keys.len()) + .map(|i| new_local_keys[i].keys_linear.x_i.clone()) + .collect(); + let indices: Vec<_> = (0..(t + 1)).collect(); + let vss = VerifiableSS:: { + parameters: ShamirSecretSharing { + threshold: t, + share_count: n, + }, + commitments: Vec::new(), + proof: DLogProof::::prove( + &Scalar::random(), + ), + }; + + assert_eq!( + vss.reconstruct( + &indices[..], + &old_linear_secret_key[0..(t + 1) as usize] + ), + vss.reconstruct( + &indices[..], + &new_linear_secret_key[0..(t + 1) as usize] + ) + ); + assert_ne!(old_linear_secret_key, new_linear_secret_key); + } + + // Refresh Keys: Some Existing Parties Leave, New Parties Replace Them + pub fn simulate_dkr_with_replace_parties( + old_local_keys: Vec>, + new_party_indices: Vec, + old_to_new_map: HashMap, + t: u16, + n: u16, + ) -> Vec> { + let mut simulation = Simulation::new(); + simulation.enable_benchmarks(false); + + for old_local_key in old_local_keys { + if !new_party_indices.contains(&old_local_key.i) { + simulation.add_party( + KeyRefresh::new( + Some(old_local_key.clone()), + None, + &old_to_new_map, + old_local_key.clone().t, + n, + None, + ) + .unwrap(), + ); + } else { + } + } + + for index in new_party_indices { + simulation.add_party( + KeyRefresh::new( + None, + Some(index), + &old_to_new_map, + t, + n, + Some(t), + ) + .unwrap(), + ); + } + simulation.run().unwrap() + } + + #[test] + pub fn test_dkr_with_replace_parties() { + let t = 2; + let n = 5; + let local_keys = simulate_keygen(t, n); + let old_local_keys = local_keys.clone(); + let mut old_to_new_map = HashMap::new(); + old_to_new_map.insert(1, 3); + old_to_new_map.insert(3, 1); + old_to_new_map.insert(4, 5); + old_to_new_map.insert(5, 4); + let mut new_local_keys = simulate_dkr_with_replace_parties( + local_keys, + vec![2, 6], + old_to_new_map, + t, + n + 1, + ); + new_local_keys.sort_by(|a, b| a.i.cmp(&b.i)); + let old_linear_secret_key: Vec<_> = (0..old_local_keys.len()) + .map(|i| old_local_keys[i].keys_linear.x_i.clone()) + .collect(); + + let new_linear_secret_key: Vec<_> = (0..new_local_keys.len()) + .map(|i| new_local_keys[i].keys_linear.x_i.clone()) + .collect(); + + let old_indices = vec![0, 1, 2]; + let new_indices = vec![0, 1, 2]; + let vss = VerifiableSS:: { + parameters: ShamirSecretSharing { + threshold: t, + share_count: n, + }, + commitments: Vec::new(), + proof: DLogProof::::prove( + &Scalar::random(), + ), + }; + + let new_vss = VerifiableSS:: { + parameters: ShamirSecretSharing { + threshold: t, + share_count: n + 1, + }, + commitments: Vec::new(), + proof: DLogProof::::prove( + &Scalar::random(), + ), + }; + + assert_eq!( + vss.reconstruct( + &old_indices[..], + &old_linear_secret_key[0..(t + 1) as usize] + ), + new_vss.reconstruct( + &new_indices[..], + &new_linear_secret_key[0..(t + 1) as usize] + ) + ); + assert_ne!(old_linear_secret_key, new_linear_secret_key); + } + + // Refresh Keys: New Threshold, Only Existing Parties (No New Parties). + pub fn simulate_dkr_with_new_threshold( + old_local_keys: Vec>, + old_to_new_map: &HashMap, + new_t: u16, + ) -> Vec> { + let mut simulation = Simulation::new(); + simulation.enable_benchmarks(false); + + for old_local_key in old_local_keys { + simulation.add_party( + KeyRefresh::new( + Some(old_local_key.clone()), + None, + old_to_new_map, + new_t, + old_local_key.n, + None, + ) + .unwrap(), + ); + } + + simulation.run().unwrap() + } + + #[test] + pub fn test_dkr_with_new_threshold() { + let init_t = 2; // initial threshold. + let new_t = 1; // new/final threshold. + let n = 5; + let local_keys = simulate_keygen(init_t, n); + + let old_local_keys = local_keys.clone(); + let mut old_to_new_map = HashMap::new(); + old_to_new_map.insert(1, 2); + old_to_new_map.insert(2, 1); + old_to_new_map.insert(3, 4); + old_to_new_map.insert(4, 3); + old_to_new_map.insert(5, 5); + let mut new_local_keys = + simulate_dkr_with_new_threshold(local_keys, &old_to_new_map, new_t); + new_local_keys.sort_by(|a, b| a.i.cmp(&b.i)); + + let old_linear_secret_key: Vec<_> = (0..old_local_keys.len()) + .map(|i| old_local_keys[i].keys_linear.x_i.clone()) + .collect(); + let new_linear_secret_key: Vec<_> = (0..new_local_keys.len()) + .map(|i| new_local_keys[i].keys_linear.x_i.clone()) + .collect(); + + let old_indices: Vec<_> = (0..(init_t + 1)).collect(); + let new_indices: Vec<_> = (0..(new_t + 1)).collect(); + + let vss = VerifiableSS:: { + parameters: ShamirSecretSharing { + threshold: init_t, + share_count: n, + }, + commitments: Vec::new(), + proof: DLogProof::::prove( + &Scalar::random(), + ), + }; + let new_vss = VerifiableSS:: { + parameters: ShamirSecretSharing { + threshold: new_t, + share_count: n, + }, + commitments: Vec::new(), + proof: DLogProof::::prove( + &Scalar::random(), + ), + }; + + assert_eq!( + vss.reconstruct( + &old_indices[..], + &old_linear_secret_key[0..(init_t + 1) as usize] + ), + new_vss.reconstruct( + &new_indices[..], + &new_linear_secret_key[0..(new_t + 1) as usize] + ) + ); + assert_ne!(old_linear_secret_key, new_linear_secret_key); + } } diff --git a/src/sign/mod.rs b/src/sign/mod.rs index a74e4938..79bcd48c 100644 --- a/src/sign/mod.rs +++ b/src/sign/mod.rs @@ -1,17 +1,17 @@ /* - CGGMP Threshold ECDSA + CGGMP Threshold ECDSA - Copyright 2022 by Webb Technologies. + Copyright 2022 by Webb Technologies. - This file is part of cggmp library - (https://github.com/webb-tools/cggmp-threshold-ecdsa) + This file is part of cggmp library + (https://github.com/webb-tools/cggmp-threshold-ecdsa) - cggmp-threshold-ecdsa is free software: you can redistribute - it and/or modify it under the terms of the GNU General Public - License as published by the Free Software Foundation, either - version 3 of the License, or (at your option) any later version. + cggmp-threshold-ecdsa is free software: you can redistribute + it and/or modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, either + version 3 of the License, or (at your option) any later version. - @license GPL-3.0+ + @license GPL-3.0+ */ use std::collections::HashMap; @@ -21,13 +21,15 @@ use serde::{Deserialize, Serialize}; use sha2::Sha256; use crate::utilities::{ - aff_g::{ - PaillierAffineOpWithGroupComInRangeProof, PaillierAffineOpWithGroupComInRangeStatement, - }, - dec_q::{PaillierDecryptionModQProof, PaillierDecryptionModQStatement}, - mul_star::{ - PaillierMultiplicationVersusGroupProof, PaillierMultiplicationVersusGroupStatement, - }, + aff_g::{ + PaillierAffineOpWithGroupComInRangeProof, + PaillierAffineOpWithGroupComInRangeStatement, + }, + dec_q::{PaillierDecryptionModQProof, PaillierDecryptionModQStatement}, + mul_star::{ + PaillierMultiplicationVersusGroupProof, + PaillierMultiplicationVersusGroupStatement, + }, }; use crate::presign::SSID; @@ -36,27 +38,35 @@ pub mod state_machine; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct SigningBroadcastMessage1 { - pub ssid: SSID, - pub i: u16, - pub sigma_i: BigInt, + pub ssid: SSID, + pub i: u16, + pub sigma_i: BigInt, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct SigningOutput { - pub ssid: SSID, - pub m: BigInt, - pub r: BigInt, - pub sigma: BigInt, + pub ssid: SSID, + pub m: BigInt, + pub r: BigInt, + pub sigma: BigInt, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct SigningIdentifiableAbortMessage { - pub i: u16, - pub proofs_D_hat_j_i: HashMap<(u16, u16), PaillierAffineOpWithGroupComInRangeProof>, - pub statements_D_hat_j_i: - HashMap<(u16, u16), PaillierAffineOpWithGroupComInRangeStatement>, - pub proof_H_hat_i: HashMap>, - pub statement_H_hat_i: HashMap>, - pub proof_sigma_i: HashMap>, - pub statement_sigma_i: HashMap>, + pub i: u16, + pub proofs_D_hat_j_i: HashMap< + (u16, u16), + PaillierAffineOpWithGroupComInRangeProof, + >, + pub statements_D_hat_j_i: HashMap< + (u16, u16), + PaillierAffineOpWithGroupComInRangeStatement, + >, + pub proof_H_hat_i: + HashMap>, + pub statement_H_hat_i: + HashMap>, + pub proof_sigma_i: HashMap>, + pub statement_sigma_i: + HashMap>, } diff --git a/src/sign/rounds.rs b/src/sign/rounds.rs index b6a43f82..69b6505f 100644 --- a/src/sign/rounds.rs +++ b/src/sign/rounds.rs @@ -1,204 +1,238 @@ /* - CGGMP Threshold ECDSA + CGGMP Threshold ECDSA - Copyright 2022 by Webb Technologies. + Copyright 2022 by Webb Technologies. - This file is part of cggmp library - (https://github.com/webb-tools/cggmp-threshold-ecdsa) + This file is part of cggmp library + (https://github.com/webb-tools/cggmp-threshold-ecdsa) - cggmp-threshold-ecdsa is free software: you can redistribute - it and/or modify it under the terms of the GNU General Public - License as published by the Free Software Foundation, either - version 3 of the License, or (at your option) any later version. + cggmp-threshold-ecdsa is free software: you can redistribute + it and/or modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, either + version 3 of the License, or (at your option) any later version. - @license GPL-3.0+ + @license GPL-3.0+ */ use std::{collections::HashMap, marker::PhantomData}; use curv::{ - arithmetic::{traits::*, Modulo}, - elliptic::curves::{Point, Scalar, Secp256k1}, - BigInt, + arithmetic::{traits::*, Modulo}, + elliptic::curves::{Point, Scalar, Secp256k1}, + BigInt, }; use round_based::{ - containers::{push::Push, BroadcastMsgs, BroadcastMsgsStore}, - Msg, + containers::{push::Push, BroadcastMsgs, BroadcastMsgsStore}, + Msg, }; use sha2::Sha256; use paillier::*; use crate::{ - presign::{PresigningOutput, PresigningTranscript, DEFAULT_ENCRYPTION_KEY}, - utilities::{ - aff_g::{ - PaillierAffineOpWithGroupComInRangeProof, PaillierAffineOpWithGroupComInRangeStatement, - PaillierAffineOpWithGroupComInRangeWitness, - }, - dec_q::{ - PaillierDecryptionModQProof, PaillierDecryptionModQStatement, - PaillierDecryptionModQWitness, - }, - mul_star::{ - PaillierMultiplicationVersusGroupProof, PaillierMultiplicationVersusGroupStatement, - PaillierMultiplicationVersusGroupWitness, - }, - sample_relatively_prime_integer, - }, - ErrorType, NoOfflineStageErrorData, ProofVerificationErrorData, + presign::{PresigningOutput, PresigningTranscript, DEFAULT_ENCRYPTION_KEY}, + utilities::{ + aff_g::{ + PaillierAffineOpWithGroupComInRangeProof, + PaillierAffineOpWithGroupComInRangeStatement, + PaillierAffineOpWithGroupComInRangeWitness, + }, + dec_q::{ + PaillierDecryptionModQProof, PaillierDecryptionModQStatement, + PaillierDecryptionModQWitness, + }, + mul_star::{ + PaillierMultiplicationVersusGroupProof, + PaillierMultiplicationVersusGroupStatement, + PaillierMultiplicationVersusGroupWitness, + }, + sample_relatively_prime_integer, + }, + ErrorType, NoOfflineStageErrorData, ProofVerificationErrorData, }; use thiserror::Error; use zeroize::Zeroize; -use super::{SigningBroadcastMessage1, SigningIdentifiableAbortMessage, SigningOutput, SSID}; +use super::{ + SigningBroadcastMessage1, SigningIdentifiableAbortMessage, SigningOutput, + SSID, +}; use super::state_machine::{Round0Messages, Round1Messages}; pub struct Round0 { - pub ssid: SSID, - pub l: usize, // This is the number of presignings to run in parallel - pub m: BigInt, - pub presigning_data: - HashMap, PresigningTranscript)>, + pub ssid: SSID, + pub l: usize, // This is the number of presignings to run in parallel + pub m: BigInt, + pub presigning_data: HashMap< + u16, + (PresigningOutput, PresigningTranscript), + >, } impl Round0 { - pub fn proceed(mut self, mut output: O) -> Result - where - O: Push>>>, - { - // If there is a record for (l,...) - if let Some((presigning_output, presigning_transcript)) = - self.presigning_data.get_mut(&(self.l as u16)) - { - // r = R projected onto x axis - let r = presigning_output.R.x_coord().unwrap_or_else(BigInt::zero); - // sigma_i = k*m + r*chi - let sigma_i = presigning_output - .k_i - .mul(&self.m) - .add(&r.mul(&presigning_output.chi_i)) - .mod_floor(&self.ssid.q); - let body = SigningBroadcastMessage1 { - ssid: self.ssid.clone(), - i: self.ssid.X.i, - sigma_i: sigma_i.clone(), - }; - output.push(Msg { sender: self.ssid.X.i, receiver: None, body: Box::new(body) }); - // Erase output from memory - presigning_output.zeroize(); - Ok(Round1 { - ssid: self.ssid.clone(), - i: self.ssid.X.i, - presigning_transcript: presigning_transcript.clone(), - m: self.m, - r, - sigma_i, - }) - } else { - let error_data = NoOfflineStageErrorData { l: self.l }; - Err(SignError::NoOfflineStageError(ErrorType { - error_type: "mul".to_string(), - bad_actors: vec![], - data: bincode::serialize(&error_data).unwrap(), - })) - } - } - pub fn is_expensive(&self) -> bool { - false - } + pub fn proceed(mut self, mut output: O) -> Result + where + O: Push>>>, + { + // If there is a record for (l,...) + if let Some((presigning_output, presigning_transcript)) = + self.presigning_data.get_mut(&(self.l as u16)) + { + // r = R projected onto x axis + let r = presigning_output.R.x_coord().unwrap_or_else(BigInt::zero); + // sigma_i = k*m + r*chi + let sigma_i = presigning_output + .k_i + .mul(&self.m) + .add(&r.mul(&presigning_output.chi_i)) + .mod_floor(&self.ssid.q); + let body = SigningBroadcastMessage1 { + ssid: self.ssid.clone(), + i: self.ssid.X.i, + sigma_i: sigma_i.clone(), + }; + output.push(Msg { + sender: self.ssid.X.i, + receiver: None, + body: Box::new(body), + }); + // Erase output from memory + presigning_output.zeroize(); + Ok(Round1 { + ssid: self.ssid.clone(), + i: self.ssid.X.i, + presigning_transcript: presigning_transcript.clone(), + m: self.m, + r, + sigma_i, + }) + } else { + let error_data = NoOfflineStageErrorData { l: self.l }; + Err(SignError::NoOfflineStageError(ErrorType { + error_type: "mul".to_string(), + bad_actors: vec![], + data: bincode::serialize(&error_data).unwrap(), + })) + } + } + pub fn is_expensive(&self) -> bool { + false + } } pub struct Round1 { - pub ssid: SSID, - pub i: u16, - pub m: BigInt, - pub r: BigInt, - pub sigma_i: BigInt, - pub presigning_transcript: PresigningTranscript, + pub ssid: SSID, + pub i: u16, + pub m: BigInt, + pub r: BigInt, + pub sigma_i: BigInt, + pub presigning_transcript: PresigningTranscript, } impl Round1 { - pub fn proceed( - self, - input: BroadcastMsgs>, - mut output: O, - ) -> Result - where - O: Push>>>>, - { - // Mapping from j to sigma_j - let mut sigmas: HashMap = HashMap::new(); - for msg in input.into_vec() { - sigmas.insert(msg.i, msg.sigma_i); - } - let sigma: BigInt = sigmas - .values() - .into_iter() - .fold(self.sigma_i.clone(), |acc, x| acc.add(x)) - .mod_floor(&self.ssid.q); - - // Verify (r, sigma) is a valid signature - // sigma^{-1} - let sigma_inv = BigInt::mod_inv(&sigma, &self.ssid.q).unwrap(); - // m*sigma^{-1} - let m_sigma_inv = self.m.mul(&sigma_inv); - // r*sigma^{-1} - let r_sigma_inv = self.r.mul(&sigma_inv); - let g = Point::::generator(); - let X = self.ssid.X.public_key(); - let x_projection = ((g * Scalar::from_bigint(&m_sigma_inv)) + - (X * Scalar::from_bigint(&r_sigma_inv))) - .x_coord() - .unwrap_or_else(BigInt::zero); - - if self.r == x_projection { - let signing_output = - SigningOutput { ssid: self.ssid.clone(), m: self.m, r: self.r, sigma }; - output.push(Msg { sender: self.ssid.X.i, receiver: None, body: Box::new(None) }); - Ok(Round2 { ssid: self.ssid, output: Some(signing_output) }) - } else { - // (l,j) to proof for D_j_i - let mut proofs_D_hat_j_i: HashMap< - (u16, u16), - PaillierAffineOpWithGroupComInRangeProof, - > = HashMap::new(); - - // (l,j) to statement for D_j_i - let mut statements_D_hat_j_i: HashMap< - (u16, u16), - PaillierAffineOpWithGroupComInRangeStatement, - > = HashMap::new(); - - self.ssid.P.iter().zip(self.ssid.P.iter()).for_each(|(j, l)| { - if *j != self.ssid.X.i && j != l { - let D_hat_j_i = - self.presigning_transcript.D_hat_j.get(&self.ssid.X.i).unwrap().clone(); - - // F_hat_j_i = enc_i(beta_hat_i_j, r_hat_i_j) - let F_hat_j_i = - self.presigning_transcript.F_hat_j.get(&self.ssid.X.i).unwrap().clone(); - - let witness_D_hat_j_i = PaillierAffineOpWithGroupComInRangeWitness::new( - self.presigning_transcript.secrets.x_i.clone(), - self.presigning_transcript - .beta_hat_i - .get(j) - .unwrap_or(&BigInt::zero()) - .clone(), - self.presigning_transcript - .s_hat_i - .get(j) - .unwrap_or(&BigInt::zero()) - .clone(), - self.presigning_transcript - .r_hat_i - .get(j) - .unwrap_or(&BigInt::zero()) - .clone(), - ); - let statement_D_hat_j_i = + pub fn proceed( + self, + input: BroadcastMsgs>, + mut output: O, + ) -> Result + where + O: Push>>>>, + { + // Mapping from j to sigma_j + let mut sigmas: HashMap = HashMap::new(); + for msg in input.into_vec() { + sigmas.insert(msg.i, msg.sigma_i); + } + let sigma: BigInt = sigmas + .values() + .fold(self.sigma_i.clone(), |acc, x| acc.add(x)) + .mod_floor(&self.ssid.q); + + // Verify (r, sigma) is a valid signature + // sigma^{-1} + let sigma_inv = BigInt::mod_inv(&sigma, &self.ssid.q).unwrap(); + // m*sigma^{-1} + let m_sigma_inv = self.m.mul(&sigma_inv); + // r*sigma^{-1} + let r_sigma_inv = self.r.mul(&sigma_inv); + let g = Point::::generator(); + let X = self.ssid.X.public_key(); + let x_projection = ((g * Scalar::from_bigint(&m_sigma_inv)) + + (X * Scalar::from_bigint(&r_sigma_inv))) + .x_coord() + .unwrap_or_else(BigInt::zero); + + if self.r == x_projection { + let signing_output = SigningOutput { + ssid: self.ssid.clone(), + m: self.m, + r: self.r, + sigma, + }; + output.push(Msg { + sender: self.ssid.X.i, + receiver: None, + body: Box::new(None), + }); + Ok(Round2 { + ssid: self.ssid, + output: Some(signing_output), + }) + } else { + // (l,j) to proof for D_j_i + let mut proofs_D_hat_j_i: HashMap< + (u16, u16), + PaillierAffineOpWithGroupComInRangeProof, + > = HashMap::new(); + + // (l,j) to statement for D_j_i + let mut statements_D_hat_j_i: HashMap< + (u16, u16), + PaillierAffineOpWithGroupComInRangeStatement, + > = HashMap::new(); + + self.ssid + .P + .iter() + .zip(self.ssid.P.iter()) + .for_each(|(j, l)| { + if *j != self.ssid.X.i && j != l { + let D_hat_j_i = self + .presigning_transcript + .D_hat_j + .get(&self.ssid.X.i) + .unwrap() + .clone(); + + // F_hat_j_i = enc_i(beta_hat_i_j, r_hat_i_j) + let F_hat_j_i = self + .presigning_transcript + .F_hat_j + .get(&self.ssid.X.i) + .unwrap() + .clone(); + + let witness_D_hat_j_i = + PaillierAffineOpWithGroupComInRangeWitness::new( + self.presigning_transcript.secrets.x_i.clone(), + self.presigning_transcript + .beta_hat_i + .get(j) + .unwrap_or(&BigInt::zero()) + .clone(), + self.presigning_transcript + .s_hat_i + .get(j) + .unwrap_or(&BigInt::zero()) + .clone(), + self.presigning_transcript + .r_hat_i + .get(j) + .unwrap_or(&BigInt::zero()) + .clone(), + ); + let statement_D_hat_j_i = crate::utilities::aff_g::PaillierAffineOpWithGroupComInRangeStatement { S: self .presigning_transcript @@ -253,285 +287,363 @@ impl Round1 { .clone(), phantom: PhantomData, }; - let proof_D_hat_j_i = + let proof_D_hat_j_i = crate::utilities::aff_g::PaillierAffineOpWithGroupComInRangeProof::< Secp256k1, Sha256, >::prove(&witness_D_hat_j_i, &statement_D_hat_j_i); - proofs_D_hat_j_i.insert((*l, *j), proof_D_hat_j_i); - statements_D_hat_j_i.insert((*l, *j), statement_D_hat_j_i); - } - }); - - // mul* H_hat_i proof - let H_hat_i_randomness = - sample_relatively_prime_integer(&self.presigning_transcript.secrets.ek.n); - let H_hat_i: BigInt = Paillier::encrypt_with_chosen_randomness( - &self.presigning_transcript.secrets.ek, - RawPlaintext::from( - self.presigning_transcript.k_i.mul(&self.presigning_transcript.secrets.x_i), - ), - &Randomness::from(H_hat_i_randomness.clone()), - ) - .into(); - let witness_H_hat_i = PaillierMultiplicationVersusGroupWitness::new( - self.presigning_transcript.secrets.x_i.clone(), - self.presigning_transcript.rho_i.mul(&H_hat_i_randomness), - ); - - let X_i = Point::::generator() * - Scalar::from_bigint(&self.presigning_transcript.secrets.x_i); - - let mut proof_H_hat_i: HashMap< - u16, - PaillierMultiplicationVersusGroupProof, - > = HashMap::new(); - let mut statement_H_hat_i: HashMap< - u16, - PaillierMultiplicationVersusGroupStatement, - > = HashMap::new(); - - self.ssid.P.iter().for_each(|l| { - if *l != self.ssid.X.i { - let statement_H_hat_l_i = PaillierMultiplicationVersusGroupStatement { - N0: self.presigning_transcript.secrets.ek.n.clone(), - NN0: self.presigning_transcript.secrets.ek.nn.clone(), - C: self.presigning_transcript.K_i.clone(), - D: H_hat_i.clone(), - X: X_i.clone(), - N_hat: self - .presigning_transcript - .N_hats - .get(l) - .unwrap_or(&BigInt::zero()) - .clone(), - s: self.presigning_transcript.S.get(l).unwrap_or(&BigInt::zero()).clone(), - t: self.presigning_transcript.T.get(l).unwrap_or(&BigInt::zero()).clone(), - phantom: PhantomData, - }; - - statement_H_hat_i.insert(*l, statement_H_hat_l_i.clone()); - - proof_H_hat_i.insert( - *l, - PaillierMultiplicationVersusGroupProof::::prove( - &witness_H_hat_i, - &statement_H_hat_l_i, - ), - ); - } - }); - - // dec proof - let s_hat_j_i = BigInt::zero(); - let ciphertext = H_hat_i; - let ciphertext_randomness = H_hat_i_randomness; - self.ssid.P.iter().for_each(|j| { - if *j != self.ssid.X.i { - ciphertext - .mul(self.presigning_transcript.D_hat_i.get(j).unwrap_or(&BigInt::zero())) - .mul(self.presigning_transcript.F_hat_j.get(&self.ssid.X.i).unwrap()); - ciphertext_randomness - .mul(&s_hat_j_i) - .mul(self.presigning_transcript.r_hat_i.get(j).unwrap_or(&BigInt::zero())); - } - }); - - BigInt::mod_pow(&ciphertext, &self.r, &self.presigning_transcript.secrets.ek.nn); - ciphertext.mul(&BigInt::mod_pow( - &self.presigning_transcript.K_i, - &self.m, - &self.presigning_transcript.secrets.ek.nn, - )); - BigInt::mod_pow( - &ciphertext_randomness, - &self.r, - &self.presigning_transcript.secrets.ek.nn, - ); - ciphertext_randomness.mul(&BigInt::mod_pow( - &self.presigning_transcript.K_i, - &self.m, - &self.presigning_transcript.secrets.ek.nn, - )); - - let witness_sigma_i = PaillierDecryptionModQWitness::new( - Paillier::decrypt( - &self.presigning_transcript.secrets.dk, - RawCiphertext::from(ciphertext.clone()), - ) - .into(), - ciphertext_randomness, - ); - - // l to statement - let mut statement_sigma_i: HashMap< - u16, - PaillierDecryptionModQStatement, - > = HashMap::new(); - - // l to proof - let mut proof_sigma_i: HashMap> = - HashMap::new(); - - self.ssid.P.iter().for_each(|l| { - if *l != self.ssid.X.i { - let statement_sigma_l_i = PaillierDecryptionModQStatement { - S: self.presigning_transcript.S.get(l).unwrap_or(&BigInt::zero()).clone(), - T: self.presigning_transcript.T.get(l).unwrap_or(&BigInt::zero()).clone(), - N_hat: self - .presigning_transcript - .N_hats - .get(l) - .unwrap_or(&BigInt::zero()) - .clone(), - N0: self.presigning_transcript.secrets.ek.n.clone(), - NN0: self.presigning_transcript.secrets.ek.nn.clone(), - C: ciphertext.clone(), - x: self.sigma_i.clone(), - ek_prover: self.presigning_transcript.secrets.ek.clone(), - phantom: PhantomData, - }; - - statement_sigma_i.insert(*l, statement_sigma_l_i.clone()); - - proof_sigma_i.insert( - *l, - PaillierDecryptionModQProof::::prove( - &witness_sigma_i, - &statement_sigma_l_i, - ), - ); - } - }); - - let body = Some(SigningIdentifiableAbortMessage { - i: self.ssid.X.i, - proofs_D_hat_j_i, - statements_D_hat_j_i, - proof_H_hat_i, - statement_H_hat_i, - proof_sigma_i, - statement_sigma_i, - }); - output.push(Msg { sender: self.ssid.X.i, receiver: None, body: Box::new(body) }); - Ok(Round2 { ssid: self.ssid, output: None }) - } - } - - pub fn is_expensive(&self) -> bool { - false - } - - pub fn expects_messages(i: u16, n: u16) -> Round0Messages { - BroadcastMsgsStore::new(i, n) - } + proofs_D_hat_j_i.insert((*l, *j), proof_D_hat_j_i); + statements_D_hat_j_i + .insert((*l, *j), statement_D_hat_j_i); + } + }); + + // mul* H_hat_i proof + let H_hat_i_randomness = sample_relatively_prime_integer( + &self.presigning_transcript.secrets.ek.n, + ); + let H_hat_i: BigInt = Paillier::encrypt_with_chosen_randomness( + &self.presigning_transcript.secrets.ek, + RawPlaintext::from( + self.presigning_transcript + .k_i + .mul(&self.presigning_transcript.secrets.x_i), + ), + &Randomness::from(H_hat_i_randomness.clone()), + ) + .into(); + let witness_H_hat_i = PaillierMultiplicationVersusGroupWitness::new( + self.presigning_transcript.secrets.x_i.clone(), + self.presigning_transcript.rho_i.mul(&H_hat_i_randomness), + ); + + let X_i = Point::::generator() + * Scalar::from_bigint(&self.presigning_transcript.secrets.x_i); + + let mut proof_H_hat_i: HashMap< + u16, + PaillierMultiplicationVersusGroupProof, + > = HashMap::new(); + let mut statement_H_hat_i: HashMap< + u16, + PaillierMultiplicationVersusGroupStatement, + > = HashMap::new(); + + self.ssid.P.iter().for_each(|l| { + if *l != self.ssid.X.i { + let statement_H_hat_l_i = + PaillierMultiplicationVersusGroupStatement { + N0: self.presigning_transcript.secrets.ek.n.clone(), + NN0: self + .presigning_transcript + .secrets + .ek + .nn + .clone(), + C: self.presigning_transcript.K_i.clone(), + D: H_hat_i.clone(), + X: X_i.clone(), + N_hat: self + .presigning_transcript + .N_hats + .get(l) + .unwrap_or(&BigInt::zero()) + .clone(), + s: self + .presigning_transcript + .S + .get(l) + .unwrap_or(&BigInt::zero()) + .clone(), + t: self + .presigning_transcript + .T + .get(l) + .unwrap_or(&BigInt::zero()) + .clone(), + phantom: PhantomData, + }; + + statement_H_hat_i.insert(*l, statement_H_hat_l_i.clone()); + + proof_H_hat_i.insert( + *l, + PaillierMultiplicationVersusGroupProof::< + Secp256k1, + Sha256, + >::prove( + &witness_H_hat_i, &statement_H_hat_l_i + ), + ); + } + }); + + // dec proof + let s_hat_j_i = BigInt::zero(); + let ciphertext = H_hat_i; + let ciphertext_randomness = H_hat_i_randomness; + self.ssid.P.iter().for_each(|j| { + if *j != self.ssid.X.i { + ciphertext + .mul( + self.presigning_transcript + .D_hat_i + .get(j) + .unwrap_or(&BigInt::zero()), + ) + .mul( + self.presigning_transcript + .F_hat_j + .get(&self.ssid.X.i) + .unwrap(), + ); + ciphertext_randomness.mul(&s_hat_j_i).mul( + self.presigning_transcript + .r_hat_i + .get(j) + .unwrap_or(&BigInt::zero()), + ); + } + }); + + BigInt::mod_pow( + &ciphertext, + &self.r, + &self.presigning_transcript.secrets.ek.nn, + ); + ciphertext.mul(&BigInt::mod_pow( + &self.presigning_transcript.K_i, + &self.m, + &self.presigning_transcript.secrets.ek.nn, + )); + BigInt::mod_pow( + &ciphertext_randomness, + &self.r, + &self.presigning_transcript.secrets.ek.nn, + ); + ciphertext_randomness.mul(&BigInt::mod_pow( + &self.presigning_transcript.K_i, + &self.m, + &self.presigning_transcript.secrets.ek.nn, + )); + + let witness_sigma_i = PaillierDecryptionModQWitness::new( + Paillier::decrypt( + &self.presigning_transcript.secrets.dk, + RawCiphertext::from(ciphertext.clone()), + ) + .into(), + ciphertext_randomness, + ); + + // l to statement + let mut statement_sigma_i: HashMap< + u16, + PaillierDecryptionModQStatement, + > = HashMap::new(); + + // l to proof + let mut proof_sigma_i: HashMap< + u16, + PaillierDecryptionModQProof, + > = HashMap::new(); + + self.ssid.P.iter().for_each(|l| { + if *l != self.ssid.X.i { + let statement_sigma_l_i = PaillierDecryptionModQStatement { + S: self + .presigning_transcript + .S + .get(l) + .unwrap_or(&BigInt::zero()) + .clone(), + T: self + .presigning_transcript + .T + .get(l) + .unwrap_or(&BigInt::zero()) + .clone(), + N_hat: self + .presigning_transcript + .N_hats + .get(l) + .unwrap_or(&BigInt::zero()) + .clone(), + N0: self.presigning_transcript.secrets.ek.n.clone(), + NN0: self.presigning_transcript.secrets.ek.nn.clone(), + C: ciphertext.clone(), + x: self.sigma_i.clone(), + ek_prover: self + .presigning_transcript + .secrets + .ek + .clone(), + phantom: PhantomData, + }; + + statement_sigma_i.insert(*l, statement_sigma_l_i.clone()); + + proof_sigma_i.insert( + *l, + PaillierDecryptionModQProof::::prove( + &witness_sigma_i, + &statement_sigma_l_i, + ), + ); + } + }); + + let body = Some(SigningIdentifiableAbortMessage { + i: self.ssid.X.i, + proofs_D_hat_j_i, + statements_D_hat_j_i, + proof_H_hat_i, + statement_H_hat_i, + proof_sigma_i, + statement_sigma_i, + }); + output.push(Msg { + sender: self.ssid.X.i, + receiver: None, + body: Box::new(body), + }); + Ok(Round2 { + ssid: self.ssid, + output: None, + }) + } + } + + pub fn is_expensive(&self) -> bool { + false + } + + pub fn expects_messages(i: u16, n: u16) -> Round0Messages { + BroadcastMsgsStore::new(i, n) + } } pub struct Round2 { - ssid: SSID, - output: Option>, + ssid: SSID, + output: Option>, } impl Round2 { - pub fn proceed( - self, - input: BroadcastMsgs>>, - ) -> Result>> { - if self.output.is_some() { - Ok(Some(self.output.unwrap())) - } else { - for msg in input.into_vec() { - let msg = msg.unwrap(); - // si stands for sender index - let si = msg.i; - // Check D_hat_i_j proofs - let mut vec_D_hat_si_j_proof_bad_actors: Vec = vec![]; - self.ssid.P.iter().for_each(|j| { - if *j != self.ssid.X.i { - let D_hat_si_j_proof = - msg.proofs_D_hat_j_i.get(&(self.ssid.X.i, *j)).unwrap(); - - let statement_D_hat_si_j = - msg.statements_D_hat_j_i.get(&(self.ssid.X.i, *j)).unwrap(); - - if PaillierAffineOpWithGroupComInRangeProof::::verify( - D_hat_si_j_proof, - statement_D_hat_si_j, - ) - .is_err() - { - vec_D_hat_si_j_proof_bad_actors.push(*j as usize); - } - } - }); - - if !vec_D_hat_si_j_proof_bad_actors.is_empty() { - let error_data = ProofVerificationErrorData { - proof_symbol: "D_hat_si_j".to_string(), - verifying_party: self.ssid.X.i, - }; - return Err(SignError::ProofVerificationError(ErrorType { - error_type: "aff-g".to_string(), - bad_actors: vec_D_hat_si_j_proof_bad_actors, - data: bincode::serialize(&error_data).unwrap(), - })) - } - // Check H_j proofs - let proof_H_hat_si = msg.proof_H_hat_i.get(&self.ssid.X.i).unwrap(); - let statement_H_hat_si = msg.statement_H_hat_i.get(&self.ssid.X.i).unwrap(); - - if PaillierMultiplicationVersusGroupProof::verify( - proof_H_hat_si, - statement_H_hat_si, - ) - .is_err() - { - let error_data = ProofVerificationErrorData { - proof_symbol: "H_hat_si".to_string(), - verifying_party: self.ssid.X.i, - }; - return Err(SignError::ProofVerificationError(ErrorType { - error_type: "mul".to_string(), - bad_actors: vec![si.into()], - data: bincode::serialize(&error_data).unwrap(), - })) - } - // Check delta_si_proof - let proof_sigma_si = msg.proof_sigma_i.get(&self.ssid.X.i).unwrap(); - let statement_sigma_si = msg.statement_sigma_i.get(&self.ssid.X.i).unwrap(); - - if PaillierDecryptionModQProof::verify(proof_sigma_si, statement_sigma_si).is_err() - { - let error_data = ProofVerificationErrorData { - proof_symbol: "sigma_si".to_string(), - verifying_party: self.ssid.X.i, - }; - return Err(SignError::ProofVerificationError(ErrorType { - error_type: "dec-q".to_string(), - bad_actors: vec![si.into()], - data: bincode::serialize(&error_data).unwrap(), - })) - } - } - Ok(None) - } - } - - pub fn is_expensive(&self) -> bool { - false - } - pub fn expects_messages(i: u16, n: u16) -> Round1Messages { - BroadcastMsgsStore::new(i, n) - } + pub fn proceed( + self, + input: BroadcastMsgs< + Option>, + >, + ) -> Result>> { + if self.output.is_some() { + Ok(Some(self.output.unwrap())) + } else { + for msg in input.into_vec() { + let msg = msg.unwrap(); + // si stands for sender index + let si = msg.i; + // Check D_hat_i_j proofs + let mut vec_D_hat_si_j_proof_bad_actors: Vec = vec![]; + self.ssid.P.iter().for_each(|j| { + if *j != self.ssid.X.i { + let D_hat_si_j_proof = msg + .proofs_D_hat_j_i + .get(&(self.ssid.X.i, *j)) + .unwrap(); + + let statement_D_hat_si_j = msg + .statements_D_hat_j_i + .get(&(self.ssid.X.i, *j)) + .unwrap(); + + if PaillierAffineOpWithGroupComInRangeProof::< + Secp256k1, + Sha256, + >::verify( + D_hat_si_j_proof, statement_D_hat_si_j + ) + .is_err() + { + vec_D_hat_si_j_proof_bad_actors.push(*j as usize); + } + } + }); + + if !vec_D_hat_si_j_proof_bad_actors.is_empty() { + let error_data = ProofVerificationErrorData { + proof_symbol: "D_hat_si_j".to_string(), + verifying_party: self.ssid.X.i, + }; + return Err(SignError::ProofVerificationError(ErrorType { + error_type: "aff-g".to_string(), + bad_actors: vec_D_hat_si_j_proof_bad_actors, + data: bincode::serialize(&error_data).unwrap(), + })); + } + // Check H_j proofs + let proof_H_hat_si = + msg.proof_H_hat_i.get(&self.ssid.X.i).unwrap(); + let statement_H_hat_si = + msg.statement_H_hat_i.get(&self.ssid.X.i).unwrap(); + + if PaillierMultiplicationVersusGroupProof::verify( + proof_H_hat_si, + statement_H_hat_si, + ) + .is_err() + { + let error_data = ProofVerificationErrorData { + proof_symbol: "H_hat_si".to_string(), + verifying_party: self.ssid.X.i, + }; + return Err(SignError::ProofVerificationError(ErrorType { + error_type: "mul".to_string(), + bad_actors: vec![si.into()], + data: bincode::serialize(&error_data).unwrap(), + })); + } + // Check delta_si_proof + let proof_sigma_si = + msg.proof_sigma_i.get(&self.ssid.X.i).unwrap(); + let statement_sigma_si = + msg.statement_sigma_i.get(&self.ssid.X.i).unwrap(); + + if PaillierDecryptionModQProof::verify( + proof_sigma_si, + statement_sigma_si, + ) + .is_err() + { + let error_data = ProofVerificationErrorData { + proof_symbol: "sigma_si".to_string(), + verifying_party: self.ssid.X.i, + }; + return Err(SignError::ProofVerificationError(ErrorType { + error_type: "dec-q".to_string(), + bad_actors: vec![si.into()], + data: bincode::serialize(&error_data).unwrap(), + })); + } + } + Ok(None) + } + } + + pub fn is_expensive(&self) -> bool { + false + } + pub fn expects_messages(i: u16, n: u16) -> Round1Messages { + BroadcastMsgsStore::new(i, n) + } } type Result = std::result::Result; #[derive(Error, Debug, Clone)] pub enum SignError { - #[error("Proof Verification Error")] - ProofVerificationError(ErrorType), + #[error("Proof Verification Error")] + ProofVerificationError(ErrorType), - #[error("No Offline Stage Error")] - NoOfflineStageError(ErrorType), + #[error("No Offline Stage Error")] + NoOfflineStageError(ErrorType), } diff --git a/src/sign/state_machine.rs b/src/sign/state_machine.rs index 6a95dcbc..d323fc33 100644 --- a/src/sign/state_machine.rs +++ b/src/sign/state_machine.rs @@ -1,293 +1,357 @@ /* - CGGMP Threshold ECDSA + CGGMP Threshold ECDSA - Copyright 2022 by Webb Technologies. + Copyright 2022 by Webb Technologies. - This file is part of cggmp library - (https://github.com/webb-tools/cggmp-threshold-ecdsa) + This file is part of cggmp library + (https://github.com/webb-tools/cggmp-threshold-ecdsa) - cggmp-threshold-ecdsa is free software: you can redistribute - it and/or modify it under the terms of the GNU General Public - License as published by the Free Software Foundation, either - version 3 of the License, or (at your option) any later version. + cggmp-threshold-ecdsa is free software: you can redistribute + it and/or modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, either + version 3 of the License, or (at your option) any later version. - @license GPL-3.0+ + @license GPL-3.0+ */ use crate::presign::{PresigningOutput, PresigningTranscript, SSID}; use super::{ - rounds::{Round0, Round1, Round2}, - SigningBroadcastMessage1, SigningIdentifiableAbortMessage, SigningOutput, + rounds::{Round0, Round1, Round2}, + SigningBroadcastMessage1, SigningIdentifiableAbortMessage, SigningOutput, }; use curv::{elliptic::curves::Secp256k1, BigInt}; use private::InternalError; use round_based::{ - containers::{ - push::{Push, PushExt}, - BroadcastMsgs, MessageStore, Store, StoreErr, - }, - IsCritical, Msg, StateMachine, + containers::{ + push::{Push, PushExt}, + BroadcastMsgs, MessageStore, Store, StoreErr, + }, + IsCritical, Msg, StateMachine, }; use serde::{Deserialize, Serialize}; use std::{collections::HashMap, fmt, mem::replace, time::Duration}; use thiserror::Error; -// NOTE: This is a hack since in the 1st round we will need to broadcast and send P2P -// messages, but the round-based library doesn't support this. So we will use the -// `P2PMsgs` to send all the data we need peers to receive. -// FIXME: If we re-design `round-based-traits` to support sending 2 types of messages -// in the same round, we can remove this hack. -pub type Round0Messages = Store>>; -pub type Round1Messages = Store>>>; +// NOTE: This is a hack since in the 1st round we will need to broadcast and +// send P2P messages, but the round-based library doesn't support this. So we +// will use the `P2PMsgs` to send all the data we need peers to receive. +// FIXME: If we re-design `round-based-traits` to support sending 2 types of +// messages in the same round, we can remove this hack. +pub type Round0Messages = + Store>>; +pub type Round1Messages = + Store>>>; pub struct Signing { - // Current round - round: R, + // Current round + round: R, - // Messages - round0_msgs: Option, - round1_msgs: Option, + // Messages + round0_msgs: Option, + round1_msgs: Option, - // Message queue - msgs_queue: Vec>, - party_i: u16, - party_n: u16, + // Message queue + msgs_queue: Vec>, + party_i: u16, + party_n: u16, } impl Signing { - pub fn new( - ssid: SSID, - l: usize, // This is the number of presignings to run in parallel - m: BigInt, - presigning_data: HashMap< - u16, - (PresigningOutput, PresigningTranscript), - >, - ) -> Result { - let n = ssid.P.len() as u16; - let i = ssid.X.i; - if n < 2 { - return Err(Error::TooFewParties) - } - - let mut state = Self { - round: R::Round0(Box::new(Round0 { ssid, l, m, presigning_data })), - - round0_msgs: Some(Round1::expects_messages(i, n)), - round1_msgs: Some(Round2::expects_messages(i, n)), - - msgs_queue: vec![], - - party_i: i, - party_n: n, - }; - - state.proceed_round(false)?; - Ok(state) - } - - fn gmap_queue<'a, T, F>(&'a mut self, mut f: F) -> impl Push> + 'a - where - F: FnMut(T) -> M + 'a, - { - (&mut self.msgs_queue).gmap(move |m: Msg| m.map_body(|m| ProtocolMessage(f(m)))) - } - - /// Proceeds round state if it received enough messages and if it's cheap to compute or - /// `may_block == true` - fn proceed_round(&mut self, may_block: bool) -> Result<()> { - let store1_wants_more = self.round0_msgs.as_ref().map(|s| s.wants_more()).unwrap_or(false); - let store2_wants_more = self.round1_msgs.as_ref().map(|s| s.wants_more()).unwrap_or(false); - - let next_state: R; - - let try_again: bool = match replace(&mut self.round, R::Gone) { - R::Round0(round) if !round.is_expensive() || may_block => { - next_state = round - .proceed(self.gmap_queue(M::Round1)) - .map(|msg| R::Round1(Box::new(msg))) - .map_err(|_e| Error::ProceedRound { msg_round: 0 })?; - true - }, - s @ R::Round0(_) => { - next_state = s; - false - }, - R::Round1(round) if !store1_wants_more && (!round.is_expensive() || may_block) => { - let store = self.round0_msgs.take().ok_or(InternalError::StoreGone)?; - let msgs = store.finish().map_err(InternalError::RetrieveRoundMessages)?; - next_state = round - .proceed(msgs, self.gmap_queue(M::Round2)) - .map(|msg| R::Round2(Box::new(msg))) - .map_err(|_e| Error::ProceedRound { msg_round: 1 })?; - true - }, - s @ R::Round1(_) => { - next_state = s; - false - }, - R::Round2(round) if !store2_wants_more && (!round.is_expensive() || may_block) => { - let store = self.round1_msgs.take().ok_or(InternalError::StoreGone)?; - let msgs = store.finish().map_err(InternalError::RetrieveRoundMessages)?; - next_state = round - .proceed(msgs) - .map(|msg| R::Final(Box::new(msg))) - .map_err(|_e| Error::ProceedRound { msg_round: 2 })?; - true - }, - s @ R::Round2(_) => { - next_state = s; - false - }, - - s @ R::Final(_) | s @ R::Gone => { - next_state = s; - false - }, - }; - self.round = next_state; - if try_again { - self.proceed_round(may_block) - } else { - Ok(()) - } - } + pub fn new( + ssid: SSID, + l: usize, // This is the number of presignings to run in parallel + m: BigInt, + presigning_data: HashMap< + u16, + (PresigningOutput, PresigningTranscript), + >, + ) -> Result { + let n = ssid.P.len() as u16; + let i = ssid.X.i; + if n < 2 { + return Err(Error::TooFewParties); + } + + let mut state = Self { + round: R::Round0(Box::new(Round0 { + ssid, + l, + m, + presigning_data, + })), + + round0_msgs: Some(Round1::expects_messages(i, n)), + round1_msgs: Some(Round2::expects_messages(i, n)), + + msgs_queue: vec![], + + party_i: i, + party_n: n, + }; + + state.proceed_round(false)?; + Ok(state) + } + + fn gmap_queue<'a, T, F>(&'a mut self, mut f: F) -> impl Push> + 'a + where + F: FnMut(T) -> M + 'a, + { + (&mut self.msgs_queue) + .gmap(move |m: Msg| m.map_body(|m| ProtocolMessage(f(m)))) + } + + /// Proceeds round state if it received enough messages and if it's cheap to + /// compute or `may_block == true` + fn proceed_round(&mut self, may_block: bool) -> Result<()> { + let store1_wants_more = self + .round0_msgs + .as_ref() + .map(|s| s.wants_more()) + .unwrap_or(false); + let store2_wants_more = self + .round1_msgs + .as_ref() + .map(|s| s.wants_more()) + .unwrap_or(false); + + let next_state: R; + + let try_again: bool = match replace(&mut self.round, R::Gone) { + R::Round0(round) if !round.is_expensive() || may_block => { + next_state = round + .proceed(self.gmap_queue(M::Round1)) + .map(|msg| R::Round1(Box::new(msg))) + .map_err(|_e| Error::ProceedRound { msg_round: 0 })?; + true + } + s @ R::Round0(_) => { + next_state = s; + false + } + R::Round1(round) + if !store1_wants_more + && (!round.is_expensive() || may_block) => + { + let store = + self.round0_msgs.take().ok_or(InternalError::StoreGone)?; + let msgs = store + .finish() + .map_err(InternalError::RetrieveRoundMessages)?; + next_state = round + .proceed(msgs, self.gmap_queue(M::Round2)) + .map(|msg| R::Round2(Box::new(msg))) + .map_err(|_e| Error::ProceedRound { msg_round: 1 })?; + true + } + s @ R::Round1(_) => { + next_state = s; + false + } + R::Round2(round) + if !store2_wants_more + && (!round.is_expensive() || may_block) => + { + let store = + self.round1_msgs.take().ok_or(InternalError::StoreGone)?; + let msgs = store + .finish() + .map_err(InternalError::RetrieveRoundMessages)?; + next_state = round + .proceed(msgs) + .map(|msg| R::Final(Box::new(msg))) + .map_err(|_e| Error::ProceedRound { msg_round: 2 })?; + true + } + s @ R::Round2(_) => { + next_state = s; + false + } + + s @ R::Final(_) | s @ R::Gone => { + next_state = s; + false + } + }; + self.round = next_state; + if try_again { + self.proceed_round(may_block) + } else { + Ok(()) + } + } } impl StateMachine for Signing { - type MessageBody = ProtocolMessage; - type Err = Error; - type Output = Option>; - - fn handle_incoming(&mut self, msg: Msg) -> Result<()> { - let current_round = self.current_round(); - - match msg.body { - ProtocolMessage(M::Round1(m)) => { - let store = self - .round0_msgs - .as_mut() - .ok_or(Error::ReceivedOutOfOrderMessage { current_round, msg_round: 1 })?; - store - .push_msg(Msg { sender: msg.sender, receiver: msg.receiver, body: *m }) - .map_err(Error::HandleMessage)?; - self.proceed_round(false) - }, - ProtocolMessage(M::Round2(m)) => { - let store = self - .round1_msgs - .as_mut() - .ok_or(Error::ReceivedOutOfOrderMessage { current_round, msg_round: 2 })?; - store - .push_msg(Msg { sender: msg.sender, receiver: msg.receiver, body: *m }) - .map_err(Error::HandleMessage)?; - self.proceed_round(false) - }, - } - } - - fn message_queue(&mut self) -> &mut Vec> { - &mut self.msgs_queue - } - - fn wants_to_proceed(&self) -> bool { - let store1_wants_more = self.round0_msgs.as_ref().map(|s| s.wants_more()).unwrap_or(false); - let store2_wants_more = self.round1_msgs.as_ref().map(|s| s.wants_more()).unwrap_or(false); - - match &self.round { - R::Round0(_) => true, - R::Round1(_) => !store1_wants_more, - R::Round2(_) => !store2_wants_more, - R::Final(_) | R::Gone => false, - } - } - - fn proceed(&mut self) -> Result<()> { - self.proceed_round(true) - } - - fn round_timeout(&self) -> Option { - None - } - - fn round_timeout_reached(&mut self) -> Self::Err { - panic!("no timeout was set") - } - - fn is_finished(&self) -> bool { - matches!(self.round, R::Final(_)) - } - - fn pick_output(&mut self) -> Option> { - match self.round { - R::Final(_) => (), - R::Gone => return Some(Err(Error::DoublePickOutput)), - _ => return None, - } - - match replace(&mut self.round, R::Gone) { - R::Final(result) => Some(Ok(*result)), - _ => unreachable!("guaranteed by match expression above"), - } - } - - fn current_round(&self) -> u16 { - match &self.round { - R::Round0(_) => 0, - R::Round1(_) => 1, - R::Round2(_) => 2, - R::Final(_) | R::Gone => 3, - } - } - - fn total_rounds(&self) -> Option { - Some(2) - } - - fn party_ind(&self) -> u16 { - self.party_i - } - - fn parties(&self) -> u16 { - self.party_n - } + type MessageBody = ProtocolMessage; + type Err = Error; + type Output = Option>; + + fn handle_incoming(&mut self, msg: Msg) -> Result<()> { + let current_round = self.current_round(); + + match msg.body { + ProtocolMessage(M::Round1(m)) => { + let store = self.round0_msgs.as_mut().ok_or( + Error::ReceivedOutOfOrderMessage { + current_round, + msg_round: 1, + }, + )?; + store + .push_msg(Msg { + sender: msg.sender, + receiver: msg.receiver, + body: *m, + }) + .map_err(Error::HandleMessage)?; + self.proceed_round(false) + } + ProtocolMessage(M::Round2(m)) => { + let store = self.round1_msgs.as_mut().ok_or( + Error::ReceivedOutOfOrderMessage { + current_round, + msg_round: 2, + }, + )?; + store + .push_msg(Msg { + sender: msg.sender, + receiver: msg.receiver, + body: *m, + }) + .map_err(Error::HandleMessage)?; + self.proceed_round(false) + } + } + } + + fn message_queue(&mut self) -> &mut Vec> { + &mut self.msgs_queue + } + + fn wants_to_proceed(&self) -> bool { + let store1_wants_more = self + .round0_msgs + .as_ref() + .map(|s| s.wants_more()) + .unwrap_or(false); + let store2_wants_more = self + .round1_msgs + .as_ref() + .map(|s| s.wants_more()) + .unwrap_or(false); + + match &self.round { + R::Round0(_) => true, + R::Round1(_) => !store1_wants_more, + R::Round2(_) => !store2_wants_more, + R::Final(_) | R::Gone => false, + } + } + + fn proceed(&mut self) -> Result<()> { + self.proceed_round(true) + } + + fn round_timeout(&self) -> Option { + None + } + + fn round_timeout_reached(&mut self) -> Self::Err { + panic!("no timeout was set") + } + + fn is_finished(&self) -> bool { + matches!(self.round, R::Final(_)) + } + + fn pick_output(&mut self) -> Option> { + match self.round { + R::Final(_) => (), + R::Gone => return Some(Err(Error::DoublePickOutput)), + _ => return None, + } + + match replace(&mut self.round, R::Gone) { + R::Final(result) => Some(Ok(*result)), + _ => unreachable!("guaranteed by match expression above"), + } + } + + fn current_round(&self) -> u16 { + match &self.round { + R::Round0(_) => 0, + R::Round1(_) => 1, + R::Round2(_) => 2, + R::Final(_) | R::Gone => 3, + } + } + + fn total_rounds(&self) -> Option { + Some(2) + } + + fn party_ind(&self) -> u16 { + self.party_i + } + + fn parties(&self) -> u16 { + self.party_n + } } impl crate::traits::RoundBlame for Signing { - fn round_blame(&self) -> (u16, Vec) { - let store1_blame = self.round0_msgs.as_ref().map(|s| s.blame()).unwrap_or_default(); - let store2_blame = self.round1_msgs.as_ref().map(|s| s.blame()).unwrap_or_default(); - - let default = (0, vec![]); - match &self.round { - R::Round0(_) => default, - R::Round1(_) => store1_blame, - R::Round2(_) => store2_blame, - R::Final(_) | R::Gone => default, - } - } + fn round_blame(&self) -> (u16, Vec) { + let store1_blame = self + .round0_msgs + .as_ref() + .map(|s| s.blame()) + .unwrap_or_default(); + let store2_blame = self + .round1_msgs + .as_ref() + .map(|s| s.blame()) + .unwrap_or_default(); + + let default = (0, vec![]); + match &self.round { + R::Round0(_) => default, + R::Round1(_) => store1_blame, + R::Round2(_) => store2_blame, + R::Final(_) | R::Gone => default, + } + } } impl fmt::Debug for Signing { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let current_round = match &self.round { - R::Round0(_) => "0", - R::Round1(_) => "1", - R::Round2(_) => "2", - R::Final(_) => "[Final]", - R::Gone => "[Gone]", - }; - let round0_msgs = match self.round0_msgs.as_ref() { - Some(msgs) => format!("[{}/{}]", msgs.messages_received(), msgs.messages_total()), - None => "[None]".into(), - }; - let round1_msgs = match self.round1_msgs.as_ref() { - Some(msgs) => format!("[{}/{}]", msgs.messages_received(), msgs.messages_total()), - None => "[None]".into(), - }; - write!( + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let current_round = match &self.round { + R::Round0(_) => "0", + R::Round1(_) => "1", + R::Round2(_) => "2", + R::Final(_) => "[Final]", + R::Gone => "[Gone]", + }; + let round0_msgs = match self.round0_msgs.as_ref() { + Some(msgs) => format!( + "[{}/{}]", + msgs.messages_received(), + msgs.messages_total() + ), + None => "[None]".into(), + }; + let round1_msgs = match self.round1_msgs.as_ref() { + Some(msgs) => format!( + "[{}/{}]", + msgs.messages_received(), + msgs.messages_total() + ), + None => "[None]".into(), + }; + write!( f, "{{Key refresh at round={} round0_msgs={} round1_msgs={} queue=[len={}]}}", current_round, @@ -295,30 +359,31 @@ impl fmt::Debug for Signing { round1_msgs, self.msgs_queue.len() ) - } + } } // Rounds enum R { - Round0(Box), - Round1(Box), - Round2(Box), - Final(Box>>), - Gone, + Round0(Box), + Round1(Box), + Round2(Box), + Final(Box>>), + Gone, } // Messages /// Protocol message which parties send on wire /// -/// Hides actual messages structure so it could be changed without breaking semver policy. +/// Hides actual messages structure so it could be changed without breaking +/// semver policy. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ProtocolMessage(M); #[derive(Debug, Clone, Serialize, Deserialize)] enum M { - Round1(Box>), - Round2(Box>>), + Round1(Box>), + Round2(Box>>), } // Error @@ -329,151 +394,174 @@ type Result = std::result::Result; #[derive(Debug, Error)] #[non_exhaustive] pub enum Error { - /// Round proceeding resulted in error - #[error("proceed round: {msg_round}")] - ProceedRound { msg_round: usize }, - - /// Too few parties (`n < 2`) - #[error("at least 2 parties are required for keygen")] - TooFewParties, - /// Threshold value `t` is not in range `[1; n-1]` - #[error("threshold is not in range [1; n-1]")] - InvalidThreshold, - /// Party index `i` is not in range `[1; n]` - #[error("party index is not in range [1; n]")] - InvalidPartyIndex, - - /// Received message didn't pass pre-validation - #[error("received message didn't pass pre-validation: {0}")] - HandleMessage(#[source] StoreErr), - /// Received message which we didn't expect to receive now (e.g. message from previous round) - #[error( + /// Round proceeding resulted in error + #[error("proceed round: {msg_round}")] + ProceedRound { msg_round: usize }, + + /// Too few parties (`n < 2`) + #[error("at least 2 parties are required for keygen")] + TooFewParties, + /// Threshold value `t` is not in range `[1; n-1]` + #[error("threshold is not in range [1; n-1]")] + InvalidThreshold, + /// Party index `i` is not in range `[1; n]` + #[error("party index is not in range [1; n]")] + InvalidPartyIndex, + + /// Received message didn't pass pre-validation + #[error("received message didn't pass pre-validation: {0}")] + HandleMessage(#[source] StoreErr), + /// Received message which we didn't expect to receive now (e.g. message + /// from previous round) + #[error( "didn't expect to receive message from round {msg_round} (being at round {current_round})" )] - ReceivedOutOfOrderMessage { current_round: u16, msg_round: u16 }, - /// [Keygen::pick_output] called twice - #[error("pick_output called twice")] - DoublePickOutput, - - /// Some internal assertions were failed, which is a bug - #[doc(hidden)] - #[error("internal error: {0:?}")] - InternalError(InternalError), + ReceivedOutOfOrderMessage { current_round: u16, msg_round: u16 }, + /// [Keygen::pick_output] called twice + #[error("pick_output called twice")] + DoublePickOutput, + + /// Some internal assertions were failed, which is a bug + #[doc(hidden)] + #[error("internal error: {0:?}")] + InternalError(InternalError), } impl IsCritical for Error { - fn is_critical(&self) -> bool { - true - } + fn is_critical(&self) -> bool { + true + } } impl From for Error { - fn from(err: InternalError) -> Self { - Self::InternalError(err) - } + fn from(err: InternalError) -> Self { + Self::InternalError(err) + } } mod private { - #[derive(Debug)] - #[non_exhaustive] - pub enum InternalError { - /// [Messages store](super::MessageStore) reported that it received all messages it wanted - /// to receive, but refused to return message container - RetrieveRoundMessages(super::StoreErr), - #[doc(hidden)] - StoreGone, - } + #[derive(Debug)] + #[non_exhaustive] + pub enum InternalError { + /// [Messages store](super::MessageStore) reported that it received all + /// messages it wanted to receive, but refused to return + /// message container + RetrieveRoundMessages(super::StoreErr), + #[doc(hidden)] + StoreGone, + } } #[cfg(test)] mod test { - use super::*; - use crate::presign::state_machine::test::{ - extract_k, extract_secret_key, generate_parties_and_simulate_presign, - }; - use curv::{ - arithmetic::{Converter, Integer}, - elliptic::curves::{Point, Scalar}, - }; - use round_based::dev::Simulation; - - fn simulate_sign( - inputs: Vec<( - SSID, - HashMap, PresigningTranscript)>, - )>, - m: BigInt, // message digest. - l: usize, // pre-signing index. - ) -> Vec>> { - let mut simulation = Simulation::new(); - - for (ssid, presigning_data) in inputs { - simulation.add_party(Signing::new(ssid, l, m.clone(), presigning_data).unwrap()); - } - - simulation.run().unwrap() - } - - // t = threshold, n = total number of parties, p = number of participants. - // NOTE: Quorum size = t + 1. - pub fn generate_parties_and_simulate_sign(t: u16, n: u16, p: u16) { - // Runs pre-sign simulation for test parameters. - let (keys, ssids, presigning_outputs) = generate_parties_and_simulate_presign(t, n, p); - assert_eq!(keys.len(), n as usize); - assert_eq!(ssids.len(), p as usize); - - // Creates inputs for signing simulation based on test parameters and pre-signing outputs. - let pre_signing_output_idx = 1; // l in the CGGMP20 paper. - // Creates signing parameters. - let inputs: Vec<( - SSID, - HashMap, PresigningTranscript)>, - )> = presigning_outputs - .iter() - .filter_map(|it| { - it.as_ref().map(|(output, transcript)| { - let idx = output.i as usize - 1; - ( - ssids[idx].clone(), - HashMap::from([( - pre_signing_output_idx as u16, - (output.clone(), transcript.clone()), - )]), - ) - }) - }) - .collect(); - // Create SHA256 message digest. - let message = b"Hello, world!"; - use sha2::Digest; - let mut hasher = sha2::Sha256::new(); - hasher.update(message); - let message_digest = BigInt::from_bytes(&hasher.finalize()); - - // Runs signing simulation for test parameters and verifies the output signature. - let results = simulate_sign(inputs, message_digest.clone(), pre_signing_output_idx); - // Extracts signature from results. - let signature = results[0].as_ref().map(|it| (it.r.clone(), it.sigma.clone())).unwrap(); - // Verifies against expected signature. - let q = Scalar::::group_order(); - let sec_key = extract_secret_key(&keys); - let k = extract_k(&presigning_outputs); - let r_direct = (Point::::generator() * k.invert().unwrap()).x_coord().unwrap(); - let s_direct = - (k.to_bigint() * (message_digest + (&r_direct * &sec_key.to_bigint()))).mod_floor(q); - let expected_signature = (r_direct, s_direct); - assert_eq!(signature, expected_signature); - } - - // All parties (2/2 signing). - #[test] - fn sign_all_parties_works() { - generate_parties_and_simulate_sign(1, 2, 2); - } - - // Threshold signing (subset of parties) - (3/4 signing). - #[test] - fn sign_threshold_works() { - generate_parties_and_simulate_sign(2, 4, 3); - } + use super::*; + use crate::presign::state_machine::test::{ + extract_k, extract_secret_key, generate_parties_and_simulate_presign, + }; + use curv::{ + arithmetic::{Converter, Integer}, + elliptic::curves::{Point, Scalar}, + }; + use round_based::dev::Simulation; + + fn simulate_sign( + inputs: Vec<( + SSID, + HashMap< + u16, + (PresigningOutput, PresigningTranscript), + >, + )>, + m: BigInt, // message digest. + l: usize, // pre-signing index. + ) -> Vec>> { + let mut simulation = Simulation::new(); + + for (ssid, presigning_data) in inputs { + simulation.add_party( + Signing::new(ssid, l, m.clone(), presigning_data).unwrap(), + ); + } + + simulation.run().unwrap() + } + + // t = threshold, n = total number of parties, p = number of participants. + // NOTE: Quorum size = t + 1. + pub fn generate_parties_and_simulate_sign(t: u16, n: u16, p: u16) { + // Runs pre-sign simulation for test parameters. + let (keys, ssids, presigning_outputs) = + generate_parties_and_simulate_presign(t, n, p); + assert_eq!(keys.len(), n as usize); + assert_eq!(ssids.len(), p as usize); + + // Creates inputs for signing simulation based on test parameters and + // pre-signing outputs. + let pre_signing_output_idx = 1; // l in the CGGMP20 paper. + // Creates signing parameters. + let inputs: Vec<( + SSID, + HashMap< + u16, + (PresigningOutput, PresigningTranscript), + >, + )> = presigning_outputs + .iter() + .filter_map(|it| { + it.as_ref().map(|(output, transcript)| { + let idx = output.i as usize - 1; + ( + ssids[idx].clone(), + HashMap::from([( + pre_signing_output_idx as u16, + (output.clone(), transcript.clone()), + )]), + ) + }) + }) + .collect(); + // Create SHA256 message digest. + let message = b"Hello, world!"; + use sha2::Digest; + let mut hasher = sha2::Sha256::new(); + hasher.update(message); + let message_digest = BigInt::from_bytes(&hasher.finalize()); + + // Runs signing simulation for test parameters and verifies the output + // signature. + let results = simulate_sign( + inputs, + message_digest.clone(), + pre_signing_output_idx, + ); + // Extracts signature from results. + let signature = results[0] + .as_ref() + .map(|it| (it.r.clone(), it.sigma.clone())) + .unwrap(); + // Verifies against expected signature. + let q = Scalar::::group_order(); + let sec_key = extract_secret_key(&keys); + let k = extract_k(&presigning_outputs); + let r_direct = (Point::::generator() * k.invert().unwrap()) + .x_coord() + .unwrap(); + let s_direct = (k.to_bigint() + * (message_digest + (&r_direct * &sec_key.to_bigint()))) + .mod_floor(q); + let expected_signature = (r_direct, s_direct); + assert_eq!(signature, expected_signature); + } + + // All parties (2/2 signing). + #[test] + fn sign_all_parties_works() { + generate_parties_and_simulate_sign(1, 2, 2); + } + + // Threshold signing (subset of parties) - (3/4 signing). + #[test] + fn sign_threshold_works() { + generate_parties_and_simulate_sign(2, 4, 3); + } } diff --git a/src/traits.rs b/src/traits.rs index dcc0e43c..184f5fb3 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -1,25 +1,25 @@ /* - Multi-party ECDSA + Multi-party ECDSA - Copyright 2022 by Webb Technologies. + Copyright 2022 by Webb Technologies. - This file is part of cggmp library - (https://github.com/webb-tools/cggmp-threshold-ecdsa) + This file is part of cggmp library + (https://github.com/webb-tools/cggmp-threshold-ecdsa) - This file is derived/inspired from Multi-party ECDSA library - (https://github.com/KZen-networks/multi-party-ecdsa) + This file is derived/inspired from Multi-party ECDSA library + (https://github.com/KZen-networks/multi-party-ecdsa) - cggmp-threshold-ecdsa is free software: you can redistribute - it and/or modify it under the terms of the GNU General Public - License as published by the Free Software Foundation, either - version 3 of the License, or (at your option) any later version. + cggmp-threshold-ecdsa is free software: you can redistribute + it and/or modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, either + version 3 of the License, or (at your option) any later version. - @license GPL-3.0+ + @license GPL-3.0+ */ pub trait RoundBlame { - /// Retrieves a list of uncorporative parties - /// - /// Returns a numbers of messages yet to recieve and list of parties to send messages for the - /// current round - fn round_blame(&self) -> (u16, Vec); + /// Retrieves a list of uncorporative parties + /// + /// Returns a numbers of messages yet to recieve and list of parties to send + /// messages for the current round + fn round_blame(&self) -> (u16, Vec); } diff --git a/src/utilities/aff_g/mod.rs b/src/utilities/aff_g/mod.rs index a0861650..3f83886d 100644 --- a/src/utilities/aff_g/mod.rs +++ b/src/utilities/aff_g/mod.rs @@ -1,468 +1,564 @@ #![allow(non_snake_case)] /* - CGGMP Threshold ECDSA + CGGMP Threshold ECDSA - Copyright 2022 by Webb Technologies + Copyright 2022 by Webb Technologies - This file is part of CGGMP Threshold ECDSA library - (https://github.com/webb-tools/cggmp-threshold-ecdsa) + This file is part of CGGMP Threshold ECDSA library + (https://github.com/webb-tools/cggmp-threshold-ecdsa) - CGGMP Threshold ECDSA is free software: you can redistribute - it and/or modify it under the terms of the GNU General Public - License as published by the Free Software Foundation, either - version 3 of the License, or (at your option) any later version. + CGGMP Threshold ECDSA is free software: you can redistribute + it and/or modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, either + version 3 of the License, or (at your option) any later version. - @license GPL-3.0+ + @license GPL-3.0+ */ //! Paillier Affine Operation with Paillier Commitment ZK-Proof – Π^{aff-p} -//! For parameters (G, g, N0, N1), consisting of element g and in group G and Paillier -//! public keys N0, N1, verify the ciphertext C \in Z∗_{N0^2} was obtained as an affine-like -//! transformation on C0 such that the multiplicative coefficient (i.e. ε) is equal to the -//! exponent of X ∈ G in the range I, and the additive coefficient (i.e. δ) is equal to the -//! plaintext-value of Y ∈ Z_N1 and resides in the the range J. +//! For parameters (G, g, N0, N1), consisting of element g and in group G and +//! Paillier public keys N0, N1, verify the ciphertext C \in Z∗_{N0^2} was +//! obtained as an affine-like transformation on C0 such that the multiplicative +//! coefficient (i.e. ε) is equal to the exponent of X ∈ G in the range I, and +//! the additive coefficient (i.e. δ) is equal to the plaintext-value of Y ∈ +//! Z_N1 and resides in the the range J. -//! Setup: Auxiliary Paillier Modulus Nˆ and Ring-Pedersen parameters s, t ∈ Z∗_{Nˆ}. +//! Setup: Auxiliary Paillier Modulus Nˆ and Ring-Pedersen parameters s, t ∈ +//! Z∗_{Nˆ}. //! -//! Inputs: Common input is (G,g,N0,N1,C,D,Y,X) where q = |G| and g is a generator of G. -//! The Prover has secret input (x,y,ρ,ρy) such that +//! Inputs: Common input is (G,g,N0,N1,C,D,Y,X) where q = |G| and g is a +//! generator of G. The Prover has secret input (x,y,ρ,ρy) such that //! x ∈ ± 2l, y ∈ ± 2l′, g^{x} = X, (1 + N1)^{y} · ρ^{N1} = Y mod N^2, //! and //! D = C^{x} · (1+N0)^{y} · ρ^{N0} mod N0^{2}. use super::sample_relatively_prime_integer; use crate::{ - utilities::{mod_pow_with_negative, L, L_PLUS_EPSILON, L_PRIME, L_PRIME_PLUS_EPSILON}, - Error, + utilities::{ + mod_pow_with_negative, L, L_PLUS_EPSILON, L_PRIME, L_PRIME_PLUS_EPSILON, + }, + Error, }; use curv::{ - arithmetic::{traits::*, Modulo}, - cryptographic_primitives::hashing::{Digest, DigestExt}, - elliptic::curves::{Curve, Point, Scalar}, - BigInt, + arithmetic::{traits::*, Modulo}, + cryptographic_primitives::hashing::{Digest, DigestExt}, + elliptic::curves::{Curve, Point, Scalar}, + BigInt, +}; +use paillier::{ + EncryptWithChosenRandomness, EncryptionKey, Paillier, Randomness, + RawPlaintext, }; -use paillier::{EncryptWithChosenRandomness, EncryptionKey, Paillier, Randomness, RawPlaintext}; use rand::Rng; use rand_chacha::{rand_core::SeedableRng, ChaChaRng}; use serde::{Deserialize, Serialize}; use std::marker::PhantomData; #[derive(Clone, Debug, Serialize, Deserialize)] -pub struct PaillierAffineOpWithGroupComInRangeStatement { - pub S: BigInt, - pub T: BigInt, - pub N_hat: BigInt, - pub N0: BigInt, - pub N1: BigInt, - pub NN0: BigInt, - pub NN1: BigInt, - pub C: BigInt, - pub D: BigInt, - pub Y: BigInt, - pub X: Point, - pub ek_prover: EncryptionKey, - pub ek_verifier: EncryptionKey, - pub phantom: PhantomData<(E, H)>, +pub struct PaillierAffineOpWithGroupComInRangeStatement< + E: Curve, + H: Digest + Clone, +> { + pub S: BigInt, + pub T: BigInt, + pub N_hat: BigInt, + pub N0: BigInt, + pub N1: BigInt, + pub NN0: BigInt, + pub NN1: BigInt, + pub C: BigInt, + pub D: BigInt, + pub Y: BigInt, + pub X: Point, + pub ek_prover: EncryptionKey, + pub ek_verifier: EncryptionKey, + pub phantom: PhantomData<(E, H)>, } -pub struct PaillierAffineOpWithGroupComInRangeWitness { - x: BigInt, - y: BigInt, - rho: BigInt, - rho_y: BigInt, - phantom: PhantomData<(E, H)>, +pub struct PaillierAffineOpWithGroupComInRangeWitness< + E: Curve, + H: Digest + Clone, +> { + x: BigInt, + y: BigInt, + rho: BigInt, + rho_y: BigInt, + phantom: PhantomData<(E, H)>, } -impl PaillierAffineOpWithGroupComInRangeWitness { - pub fn new(x: BigInt, y: BigInt, rho: BigInt, rho_y: BigInt) -> Self { - PaillierAffineOpWithGroupComInRangeWitness { x, y, rho, rho_y, phantom: PhantomData } - } +impl + PaillierAffineOpWithGroupComInRangeWitness +{ + pub fn new(x: BigInt, y: BigInt, rho: BigInt, rho_y: BigInt) -> Self { + PaillierAffineOpWithGroupComInRangeWitness { + x, + y, + rho, + rho_y, + phantom: PhantomData, + } + } } -impl PaillierAffineOpWithGroupComInRangeStatement { - #[allow(clippy::too_many_arguments)] - pub fn generate( - S: BigInt, - T: BigInt, - N_hat: BigInt, - rho: BigInt, - rho_y: BigInt, - prover: EncryptionKey, - verifier: EncryptionKey, - C: BigInt, - ) -> (Self, PaillierAffineOpWithGroupComInRangeWitness) { - // Set up exponents - let l_exp = BigInt::pow(&BigInt::from(2), L as u32); - let lprime_exp = BigInt::pow(&BigInt::from(2), L_PRIME as u32); - // Set up moduli - let N0 = verifier.clone().n; - let NN0 = verifier.clone().nn; - let N1 = prover.clone().n; - let NN1 = prover.clone().nn; - let ek_verifier = verifier; - let ek_prover = prover; +impl + PaillierAffineOpWithGroupComInRangeStatement +{ + #[allow(clippy::too_many_arguments)] + pub fn generate( + S: BigInt, + T: BigInt, + N_hat: BigInt, + rho: BigInt, + rho_y: BigInt, + prover: EncryptionKey, + verifier: EncryptionKey, + C: BigInt, + ) -> (Self, PaillierAffineOpWithGroupComInRangeWitness) { + // Set up exponents + let l_exp = BigInt::pow(&BigInt::from(2), L as u32); + let lprime_exp = BigInt::pow(&BigInt::from(2), L_PRIME as u32); + // Set up moduli + let N0 = verifier.clone().n; + let NN0 = verifier.clone().nn; + let N1 = prover.clone().n; + let NN1 = prover.clone().nn; + let ek_verifier = verifier; + let ek_prover = prover; - let x = BigInt::sample_range(&BigInt::from(-1).mul(&l_exp), &l_exp); - let y = BigInt::sample_range(&BigInt::from(-1).mul(&lprime_exp), &lprime_exp); + let x = BigInt::sample_range(&BigInt::from(-1).mul(&l_exp), &l_exp); + let y = BigInt::sample_range( + &BigInt::from(-1).mul(&lprime_exp), + &lprime_exp, + ); - let X = Point::::generator().as_point() * Scalar::from(&x); - // Y = (1 + N1)^{y} · ρ_y^{N1} - let Y = Paillier::encrypt_with_chosen_randomness( - &ek_prover, - RawPlaintext::from(&y), - &Randomness::from(&rho_y), - ); - // (1 + N0)^y mod N0^2 - let D = { - let D_temp = Paillier::encrypt_with_chosen_randomness( - &ek_verifier, - RawPlaintext::from(&y), - &Randomness::from(&rho), - ); - BigInt::mod_mul(&mod_pow_with_negative(&C, &x, &NN0), &D_temp.into(), &NN0) - }; + let X = Point::::generator().as_point() * Scalar::from(&x); + // Y = (1 + N1)^{y} · ρ_y^{N1} + let Y = Paillier::encrypt_with_chosen_randomness( + &ek_prover, + RawPlaintext::from(&y), + &Randomness::from(&rho_y), + ); + // (1 + N0)^y mod N0^2 + let D = { + let D_temp = Paillier::encrypt_with_chosen_randomness( + &ek_verifier, + RawPlaintext::from(&y), + &Randomness::from(&rho), + ); + BigInt::mod_mul( + &mod_pow_with_negative(&C, &x, &NN0), + &D_temp.into(), + &NN0, + ) + }; - ( - Self { - S, - T, - N_hat, - N0, - N1, - NN0, - NN1, - C, - D, - Y: Y.clone().into(), - X, - ek_prover, - ek_verifier, - phantom: PhantomData, - }, - PaillierAffineOpWithGroupComInRangeWitness { x, y, rho, rho_y, phantom: PhantomData }, - ) - } + ( + Self { + S, + T, + N_hat, + N0, + N1, + NN0, + NN1, + C, + D, + Y: Y.clone().into(), + X, + ek_prover, + ek_verifier, + phantom: PhantomData, + }, + PaillierAffineOpWithGroupComInRangeWitness { + x, + y, + rho, + rho_y, + phantom: PhantomData, + }, + ) + } } #[derive(Clone, Debug, Serialize, Deserialize)] pub struct PaillierAffineOpWithGroupComInRangeCommitment { - A: BigInt, - B_x: Point, - B_y: BigInt, - E: BigInt, - F: BigInt, - big_S: BigInt, - big_T: BigInt, + A: BigInt, + B_x: Point, + B_y: BigInt, + E: BigInt, + F: BigInt, + big_S: BigInt, + big_T: BigInt, } #[derive(Clone, Debug, Serialize, Deserialize)] -pub struct PaillierAffineOpWithGroupComInRangeProof { - z1: BigInt, - z2: BigInt, - z3: BigInt, - z4: BigInt, - w: BigInt, - wy: BigInt, - commitment: PaillierAffineOpWithGroupComInRangeCommitment, - phantom: PhantomData<(E, H)>, +pub struct PaillierAffineOpWithGroupComInRangeProof +{ + z1: BigInt, + z2: BigInt, + z3: BigInt, + z4: BigInt, + w: BigInt, + wy: BigInt, + commitment: PaillierAffineOpWithGroupComInRangeCommitment, + phantom: PhantomData<(E, H)>, } // Link to the UC non-interactive threshold ECDSA paper -impl PaillierAffineOpWithGroupComInRangeProof { - pub fn prove( - witness: &PaillierAffineOpWithGroupComInRangeWitness, - statement: &PaillierAffineOpWithGroupComInRangeStatement, - ) -> PaillierAffineOpWithGroupComInRangeProof { - // Set up exponents - let l_exp = BigInt::pow(&BigInt::from(2), L as u32); - let lplus_exp = BigInt::pow(&BigInt::from(2), L_PLUS_EPSILON as u32); - let lprimeplus_exp = BigInt::pow(&BigInt::from(2), L_PRIME_PLUS_EPSILON as u32); +impl + PaillierAffineOpWithGroupComInRangeProof +{ + pub fn prove( + witness: &PaillierAffineOpWithGroupComInRangeWitness, + statement: &PaillierAffineOpWithGroupComInRangeStatement, + ) -> PaillierAffineOpWithGroupComInRangeProof { + // Set up exponents + let l_exp = BigInt::pow(&BigInt::from(2), L as u32); + let lplus_exp = BigInt::pow(&BigInt::from(2), L_PLUS_EPSILON as u32); + let lprimeplus_exp = + BigInt::pow(&BigInt::from(2), L_PRIME_PLUS_EPSILON as u32); - // α ← ± 2^{l+ε} - let alpha = BigInt::sample_range(&BigInt::from(-1).mul(&lplus_exp), &lplus_exp); - // β ← ± 2^{l'+ε} - let beta = BigInt::sample_range(&BigInt::from(-1).mul(&lprimeplus_exp), &lprimeplus_exp); - // Sample r, ry as unit values from Z_N0, Z_N1 - let r = sample_relatively_prime_integer(&statement.N0); - let ry = sample_relatively_prime_integer(&statement.N1); - // γ ← ± 2^{l+ε} · Nˆ - let gamma = BigInt::sample_range( - &BigInt::from(-1).mul(&lplus_exp).mul(&statement.N_hat), - &lplus_exp.mul(&statement.N_hat), - ); - // m ← ± 2l · Nˆ - let m = BigInt::sample_range( - &BigInt::from(-1).mul(&l_exp).mul(&statement.N_hat), - &l_exp.mul(&statement.N_hat), - ); - // δ ← ± 2^{l+ε} · Nˆ - let delta = BigInt::sample_range( - &BigInt::from(-1).mul(&lplus_exp).mul(&statement.N_hat), - &lplus_exp.mul(&statement.N_hat), - ); - // mu ← ± 2l · Nˆ - let mu = BigInt::sample_range( - &BigInt::from(-1).mul(&l_exp).mul(&statement.N_hat), - &l_exp.mul(&statement.N_hat), - ); - // A = C^α · (1 + N0)^β · r^N0 mod N0^2 - let A = { - let A_temp = Paillier::encrypt_with_chosen_randomness( - &statement.ek_verifier, - RawPlaintext::from(&beta), - &Randomness::from(&r), - ); - BigInt::mod_mul( - &mod_pow_with_negative(&statement.C, &alpha, &statement.NN0), - &A_temp.into(), - &statement.NN0, - ) - }; + // α ← ± 2^{l+ε} + let alpha = + BigInt::sample_range(&BigInt::from(-1).mul(&lplus_exp), &lplus_exp); + // β ← ± 2^{l'+ε} + let beta = BigInt::sample_range( + &BigInt::from(-1).mul(&lprimeplus_exp), + &lprimeplus_exp, + ); + // Sample r, ry as unit values from Z_N0, Z_N1 + let r = sample_relatively_prime_integer(&statement.N0); + let ry = sample_relatively_prime_integer(&statement.N1); + // γ ← ± 2^{l+ε} · Nˆ + let gamma = BigInt::sample_range( + &BigInt::from(-1).mul(&lplus_exp).mul(&statement.N_hat), + &lplus_exp.mul(&statement.N_hat), + ); + // m ← ± 2l · Nˆ + let m = BigInt::sample_range( + &BigInt::from(-1).mul(&l_exp).mul(&statement.N_hat), + &l_exp.mul(&statement.N_hat), + ); + // δ ← ± 2^{l+ε} · Nˆ + let delta = BigInt::sample_range( + &BigInt::from(-1).mul(&lplus_exp).mul(&statement.N_hat), + &lplus_exp.mul(&statement.N_hat), + ); + // mu ← ± 2l · Nˆ + let mu = BigInt::sample_range( + &BigInt::from(-1).mul(&l_exp).mul(&statement.N_hat), + &l_exp.mul(&statement.N_hat), + ); + // A = C^α · (1 + N0)^β · r^N0 mod N0^2 + let A = { + let A_temp = Paillier::encrypt_with_chosen_randomness( + &statement.ek_verifier, + RawPlaintext::from(&beta), + &Randomness::from(&r), + ); + BigInt::mod_mul( + &mod_pow_with_negative(&statement.C, &alpha, &statement.NN0), + &A_temp.into(), + &statement.NN0, + ) + }; - let B_x: Point = Point::::generator().as_point() * Scalar::from_bigint(&alpha); - // By = (1 + N1)^β · ry^N1 mod N1^2 - let B_y = Paillier::encrypt_with_chosen_randomness( - &statement.ek_prover, - RawPlaintext::from(&beta), - &Randomness::from(&ry), - ); - // E = s^α · t^γ mod Nˆ - let E = BigInt::mod_mul( - &mod_pow_with_negative(&statement.S, &alpha, &statement.N_hat), - &mod_pow_with_negative(&statement.T, &gamma, &statement.N_hat), - &statement.N_hat, - ); - // big S = s^x · t^m mod Nˆ - let big_S = BigInt::mod_mul( - &mod_pow_with_negative(&statement.S, &witness.x, &statement.N_hat), - &mod_pow_with_negative(&statement.T, &m, &statement.N_hat), - &statement.N_hat, - ); - // F = s^β · t^δ mod Nˆ - let F = BigInt::mod_mul( - &mod_pow_with_negative(&statement.S, &beta, &statement.N_hat), - &mod_pow_with_negative(&statement.T, &delta, &statement.N_hat), - &statement.N_hat, - ); - // big T = s^y · t^mu mod Nˆ - let big_T = BigInt::mod_mul( - &mod_pow_with_negative(&statement.S, &witness.y, &statement.N_hat), - &mod_pow_with_negative(&statement.T, &mu, &statement.N_hat), - &statement.N_hat, - ); - // Hash all prover messages to generate NIZK challenge - let mut e: BigInt = H::new() - .chain_bigint(&big_S) - .chain_bigint(&big_T) - .chain_bigint(&A) - .chain_point(&B_x) - .chain_bigint(&B_y.clone().into()) - .chain_bigint(&E) - .chain_bigint(&F) - .result_bigint(); - let mut rng: ChaChaRng = ChaChaRng::from_seed(e.to_bytes().try_into().unwrap()); - let val = rng.gen_range(0..2); - e = BigInt::from(val).mul(&BigInt::from(-2)).add(&BigInt::one()).mul(&e); - // Compute Fiat-Shamir commitment preimage - let commitment = PaillierAffineOpWithGroupComInRangeCommitment:: { - A, - B_x, - B_y: B_y.clone().into(), - E, - F, - big_S, - big_T, - }; - // z1 = α + ex - let z1 = BigInt::add(&alpha, &e.mul(&witness.x)); - // z2 = β + ey - let z2 = BigInt::add(&beta, &e.mul(&witness.y)); - // z3 = γ + em - let z3 = BigInt::add(&gamma, &e.mul(&m)); - // z4 = δ + (e · mu) - let z4 = BigInt::add(&delta, &e.mul(&mu)); - // w = r · rho^e mod N0 - let w = BigInt::mod_mul( - &r, - &mod_pow_with_negative(&witness.rho, &e, &statement.N0), - &statement.N0, - ); - // wy = ry · rho_y^e mod N1 - let wy = BigInt::mod_mul( - &ry, - &mod_pow_with_negative(&witness.rho_y, &e, &statement.N1), - &statement.N1, - ); + let B_x: Point = + Point::::generator().as_point() * Scalar::from_bigint(&alpha); + // By = (1 + N1)^β · ry^N1 mod N1^2 + let B_y = Paillier::encrypt_with_chosen_randomness( + &statement.ek_prover, + RawPlaintext::from(&beta), + &Randomness::from(&ry), + ); + // E = s^α · t^γ mod Nˆ + let E = BigInt::mod_mul( + &mod_pow_with_negative(&statement.S, &alpha, &statement.N_hat), + &mod_pow_with_negative(&statement.T, &gamma, &statement.N_hat), + &statement.N_hat, + ); + // big S = s^x · t^m mod Nˆ + let big_S = BigInt::mod_mul( + &mod_pow_with_negative(&statement.S, &witness.x, &statement.N_hat), + &mod_pow_with_negative(&statement.T, &m, &statement.N_hat), + &statement.N_hat, + ); + // F = s^β · t^δ mod Nˆ + let F = BigInt::mod_mul( + &mod_pow_with_negative(&statement.S, &beta, &statement.N_hat), + &mod_pow_with_negative(&statement.T, &delta, &statement.N_hat), + &statement.N_hat, + ); + // big T = s^y · t^mu mod Nˆ + let big_T = BigInt::mod_mul( + &mod_pow_with_negative(&statement.S, &witness.y, &statement.N_hat), + &mod_pow_with_negative(&statement.T, &mu, &statement.N_hat), + &statement.N_hat, + ); + // Hash all prover messages to generate NIZK challenge + let mut e: BigInt = H::new() + .chain_bigint(&big_S) + .chain_bigint(&big_T) + .chain_bigint(&A) + .chain_point(&B_x) + .chain_bigint(&B_y.clone().into()) + .chain_bigint(&E) + .chain_bigint(&F) + .result_bigint(); + let mut rng: ChaChaRng = + ChaChaRng::from_seed(e.to_bytes().try_into().unwrap()); + let val = rng.gen_range(0..2); + e = BigInt::from(val) + .mul(&BigInt::from(-2)) + .add(&BigInt::one()) + .mul(&e); + // Compute Fiat-Shamir commitment preimage + let commitment = PaillierAffineOpWithGroupComInRangeCommitment:: { + A, + B_x, + B_y: B_y.clone().into(), + E, + F, + big_S, + big_T, + }; + // z1 = α + ex + let z1 = BigInt::add(&alpha, &e.mul(&witness.x)); + // z2 = β + ey + let z2 = BigInt::add(&beta, &e.mul(&witness.y)); + // z3 = γ + em + let z3 = BigInt::add(&gamma, &e.mul(&m)); + // z4 = δ + (e · mu) + let z4 = BigInt::add(&delta, &e.mul(&mu)); + // w = r · rho^e mod N0 + let w = BigInt::mod_mul( + &r, + &mod_pow_with_negative(&witness.rho, &e, &statement.N0), + &statement.N0, + ); + // wy = ry · rho_y^e mod N1 + let wy = BigInt::mod_mul( + &ry, + &mod_pow_with_negative(&witness.rho_y, &e, &statement.N1), + &statement.N1, + ); - Self { z1, z2, z3, z4, w, wy, commitment, phantom: PhantomData } - } + Self { + z1, + z2, + z3, + z4, + w, + wy, + commitment, + phantom: PhantomData, + } + } - pub fn verify( - proof: &PaillierAffineOpWithGroupComInRangeProof, - statement: &PaillierAffineOpWithGroupComInRangeStatement, - ) -> Result<(), Error> { - // Hash all prover messages to generate NIZK challenge - let mut e: BigInt = H::new() - .chain_bigint(&proof.commitment.big_S.clone()) - .chain_bigint(&proof.commitment.big_T.clone()) - .chain_bigint(&proof.commitment.A.clone()) - .chain_point(&proof.commitment.B_x.clone()) - .chain_bigint(&proof.commitment.B_y.clone()) - .chain_bigint(&proof.commitment.E.clone()) - .chain_bigint(&proof.commitment.F.clone()) - .result_bigint(); - let mut rng: ChaChaRng = ChaChaRng::from_seed(e.to_bytes().try_into().unwrap()); - let val = rng.gen_range(0..2); - e = BigInt::from(val).mul(&BigInt::from(-2)).add(&BigInt::one()).mul(&e); + pub fn verify( + proof: &PaillierAffineOpWithGroupComInRangeProof, + statement: &PaillierAffineOpWithGroupComInRangeStatement, + ) -> Result<(), Error> { + // Hash all prover messages to generate NIZK challenge + let mut e: BigInt = H::new() + .chain_bigint(&proof.commitment.big_S.clone()) + .chain_bigint(&proof.commitment.big_T.clone()) + .chain_bigint(&proof.commitment.A.clone()) + .chain_point(&proof.commitment.B_x.clone()) + .chain_bigint(&proof.commitment.B_y.clone()) + .chain_bigint(&proof.commitment.E.clone()) + .chain_bigint(&proof.commitment.F.clone()) + .result_bigint(); + let mut rng: ChaChaRng = + ChaChaRng::from_seed(e.to_bytes().try_into().unwrap()); + let val = rng.gen_range(0..2); + e = BigInt::from(val) + .mul(&BigInt::from(-2)) + .add(&BigInt::one()) + .mul(&e); - /* - RANGE CHECKS - */ - // z1 ∈ [-2^{l+ε}, 2^{l+ε}] - assert!( - proof.z1.bit_length() <= L_PLUS_EPSILON, - "z1 is too large {:?}", - proof.z1.bit_length() - ); - // z2 ∈ [-2^{l'+ε}, 2^{l'+ε}] - assert!( - proof.z2.bit_length() <= L_PRIME_PLUS_EPSILON, - "z2 is too large {:?}", - proof.z2.bit_length() - ); + /* + RANGE CHECKS + */ + // z1 ∈ [-2^{l+ε}, 2^{l+ε}] + assert!( + proof.z1.bit_length() <= L_PLUS_EPSILON, + "z1 is too large {:?}", + proof.z1.bit_length() + ); + // z2 ∈ [-2^{l'+ε}, 2^{l'+ε}] + assert!( + proof.z2.bit_length() <= L_PRIME_PLUS_EPSILON, + "z2 is too large {:?}", + proof.z2.bit_length() + ); - /* - FIRST EQUALITY CHECK - */ - // C^{z1} · (1 + N0)^{z2} · w^{N0} =A · D^e mod N0^2 - let left_1 = { - let temp_left_1_1 = Paillier::encrypt_with_chosen_randomness( - &statement.ek_verifier, - RawPlaintext::from(&proof.z2), - &Randomness::from(&proof.w), - ); - BigInt::mod_mul( - &mod_pow_with_negative(&statement.C, &proof.z1, &statement.NN0), - &temp_left_1_1.into(), - &statement.NN0, - ) - }; - // A · D^e mod N0^2 - let right_1 = BigInt::mod_mul( - &proof.commitment.A, - &mod_pow_with_negative(&statement.D, &e, &statement.NN0), - &statement.NN0, - ); - // Assert left == right - assert!(left_1 == right_1); - /* - SECOND EQUALITY CHECK - */ - // g^{z1} = B_x ·X^e ∈ G - let left_2 = Point::::generator().as_point() * Scalar::from_bigint(&proof.z1); - let right_2 = - proof.commitment.B_x.clone() + (statement.X.clone() * Scalar::from_bigint(&e)); - // Assert left == right - assert!(left_2 == right_2); - /* - THIRD EQUALITY CHECK - */ - // (1 + N1)^{z2} · wy^{N1} = B_y · Y^e mod N1^2 - let left_3_ciphertext = Paillier::encrypt_with_chosen_randomness( - &statement.ek_prover, - RawPlaintext::from(&proof.z2), - &Randomness::from(&proof.wy), - ); - let left_3: BigInt = left_3_ciphertext.into(); - // B_y · Y^e mod N1^2 - let right_3 = BigInt::mod_mul( - &proof.commitment.B_y, - &mod_pow_with_negative(&statement.Y, &e, &statement.NN1), - &statement.NN1, - ); - // Assert left == right - assert!(left_3.mod_floor(&statement.NN1) == right_3); - /* - FOURTH EQUALITY CHECK - */ - // s^{z1} · t^{z3} = E · big_S^e mod N_hat - let left_4 = { - // s^{z1} mod N_hat^2 - let temp_left_4_1 = mod_pow_with_negative(&statement.S, &proof.z1, &statement.N_hat); - // t^{z3} mod N_hat^2 - let temp_left_4_2 = mod_pow_with_negative(&statement.T, &proof.z3, &statement.N_hat); - // s^{z1} · t^{z3} mod N_hat^2 - BigInt::mod_mul(&temp_left_4_1, &temp_left_4_2, &statement.N_hat) - }; - // E · big_S^e mod N_hat^2 - let right_4 = BigInt::mod_mul( - &proof.commitment.E, - &mod_pow_with_negative(&proof.commitment.big_S, &e, &statement.N_hat), - &statement.N_hat, - ); - // Assert left == right - assert!(left_4 == right_4); - /* - FIFTH EQUALITY CHECK - */ - // s^{z2} · t^{z4} = F · big_T^e mod N_hat - let left_5 = { - // s^{z2} mod N_hat^2 - let temp_left_5_1 = mod_pow_with_negative(&statement.S, &proof.z2, &statement.N_hat); - // t^{z4} mod N_hat^2 - let temp_left_5_2 = mod_pow_with_negative(&statement.T, &proof.z4, &statement.N_hat); - // s^{z2} · t^{z4} mod N_hat^2 - BigInt::mod_mul(&temp_left_5_1, &temp_left_5_2, &statement.N_hat) - }; - // F · big_T^e mod N_hat^2 - let right_5 = BigInt::mod_mul( - &proof.commitment.F, - &mod_pow_with_negative(&proof.commitment.big_T, &e, &statement.N_hat), - &statement.N_hat, - ); - // Assert left == right - assert!(left_5 == right_5); - Ok(()) - } + /* + FIRST EQUALITY CHECK + */ + // C^{z1} · (1 + N0)^{z2} · w^{N0} =A · D^e mod N0^2 + let left_1 = { + let temp_left_1_1 = Paillier::encrypt_with_chosen_randomness( + &statement.ek_verifier, + RawPlaintext::from(&proof.z2), + &Randomness::from(&proof.w), + ); + BigInt::mod_mul( + &mod_pow_with_negative(&statement.C, &proof.z1, &statement.NN0), + &temp_left_1_1.into(), + &statement.NN0, + ) + }; + // A · D^e mod N0^2 + let right_1 = BigInt::mod_mul( + &proof.commitment.A, + &mod_pow_with_negative(&statement.D, &e, &statement.NN0), + &statement.NN0, + ); + // Assert left == right + assert!(left_1 == right_1); + /* + SECOND EQUALITY CHECK + */ + // g^{z1} = B_x ·X^e ∈ G + let left_2 = + Point::::generator().as_point() * Scalar::from_bigint(&proof.z1); + let right_2 = proof.commitment.B_x.clone() + + (statement.X.clone() * Scalar::from_bigint(&e)); + // Assert left == right + assert!(left_2 == right_2); + /* + THIRD EQUALITY CHECK + */ + // (1 + N1)^{z2} · wy^{N1} = B_y · Y^e mod N1^2 + let left_3_ciphertext = Paillier::encrypt_with_chosen_randomness( + &statement.ek_prover, + RawPlaintext::from(&proof.z2), + &Randomness::from(&proof.wy), + ); + let left_3: BigInt = left_3_ciphertext.into(); + // B_y · Y^e mod N1^2 + let right_3 = BigInt::mod_mul( + &proof.commitment.B_y, + &mod_pow_with_negative(&statement.Y, &e, &statement.NN1), + &statement.NN1, + ); + // Assert left == right + assert!(left_3.mod_floor(&statement.NN1) == right_3); + /* + FOURTH EQUALITY CHECK + */ + // s^{z1} · t^{z3} = E · big_S^e mod N_hat + let left_4 = { + // s^{z1} mod N_hat^2 + let temp_left_4_1 = mod_pow_with_negative( + &statement.S, + &proof.z1, + &statement.N_hat, + ); + // t^{z3} mod N_hat^2 + let temp_left_4_2 = mod_pow_with_negative( + &statement.T, + &proof.z3, + &statement.N_hat, + ); + // s^{z1} · t^{z3} mod N_hat^2 + BigInt::mod_mul(&temp_left_4_1, &temp_left_4_2, &statement.N_hat) + }; + // E · big_S^e mod N_hat^2 + let right_4 = BigInt::mod_mul( + &proof.commitment.E, + &mod_pow_with_negative( + &proof.commitment.big_S, + &e, + &statement.N_hat, + ), + &statement.N_hat, + ); + // Assert left == right + assert!(left_4 == right_4); + /* + FIFTH EQUALITY CHECK + */ + // s^{z2} · t^{z4} = F · big_T^e mod N_hat + let left_5 = { + // s^{z2} mod N_hat^2 + let temp_left_5_1 = mod_pow_with_negative( + &statement.S, + &proof.z2, + &statement.N_hat, + ); + // t^{z4} mod N_hat^2 + let temp_left_5_2 = mod_pow_with_negative( + &statement.T, + &proof.z4, + &statement.N_hat, + ); + // s^{z2} · t^{z4} mod N_hat^2 + BigInt::mod_mul(&temp_left_5_1, &temp_left_5_2, &statement.N_hat) + }; + // F · big_T^e mod N_hat^2 + let right_5 = BigInt::mod_mul( + &proof.commitment.F, + &mod_pow_with_negative( + &proof.commitment.big_T, + &e, + &statement.N_hat, + ), + &statement.N_hat, + ); + // Assert left == right + assert!(left_5 == right_5); + Ok(()) + } } #[cfg(test)] mod tests { - use super::*; - use crate::utilities::{mta::range_proofs::SampleFromMultiplicativeGroup, BITS_PAILLIER}; - use curv::elliptic::curves::secp256_k1::Secp256k1; - use fs_dkr::ring_pedersen_proof::RingPedersenStatement; - use paillier::{Encrypt, KeyGeneration, Paillier, RawPlaintext}; - use sha2::Sha256; + use super::*; + use crate::utilities::{ + mta::range_proofs::SampleFromMultiplicativeGroup, BITS_PAILLIER, + }; + use curv::elliptic::curves::secp256_k1::Secp256k1; + use fs_dkr::ring_pedersen_proof::RingPedersenStatement; + use paillier::{Encrypt, KeyGeneration, Paillier, RawPlaintext}; + use sha2::Sha256; - #[test] - fn test_affine_g_proof() { - let (ring_pedersen_statement, _witness) = - RingPedersenStatement::::generate(); - let (ek_prover, _) = Paillier::keypair_with_modulus_size(BITS_PAILLIER).keys(); - let (ek_verifier, _) = Paillier::keypair_with_modulus_size(BITS_PAILLIER).keys(); + #[test] + fn test_affine_g_proof() { + let (ring_pedersen_statement, _witness) = + RingPedersenStatement::::generate(); + let (ek_prover, _) = + Paillier::keypair_with_modulus_size(BITS_PAILLIER).keys(); + let (ek_verifier, _) = + Paillier::keypair_with_modulus_size(BITS_PAILLIER).keys(); - let rho: BigInt = BigInt::from_paillier_key(&ek_verifier); - let rho_y: BigInt = BigInt::from_paillier_key(&ek_prover); - let C = Paillier::encrypt(&ek_verifier, RawPlaintext::from(BigInt::from(12))); - let (statement, witness) = - PaillierAffineOpWithGroupComInRangeStatement::::generate( - ring_pedersen_statement.S, - ring_pedersen_statement.T, - ring_pedersen_statement.N, - rho, - rho_y, - ek_prover, - ek_verifier, - C.0.into_owned(), - ); - let proof = PaillierAffineOpWithGroupComInRangeProof::::prove( + let rho: BigInt = BigInt::from_paillier_key(&ek_verifier); + let rho_y: BigInt = BigInt::from_paillier_key(&ek_prover); + let C = Paillier::encrypt( + &ek_verifier, + RawPlaintext::from(BigInt::from(12)), + ); + let (statement, witness) = PaillierAffineOpWithGroupComInRangeStatement::< + Secp256k1, + Sha256, + >::generate( + ring_pedersen_statement.S, + ring_pedersen_statement.T, + ring_pedersen_statement.N, + rho, + rho_y, + ek_prover, + ek_verifier, + C.0.into_owned(), + ); + let proof = PaillierAffineOpWithGroupComInRangeProof::::prove( &witness, &statement, ); - assert!(PaillierAffineOpWithGroupComInRangeProof::::verify( + assert!(PaillierAffineOpWithGroupComInRangeProof::::verify( &proof, &statement ) .is_ok()); - } + } } diff --git a/src/utilities/dec_q/mod.rs b/src/utilities/dec_q/mod.rs index 61a886cf..8a963633 100644 --- a/src/utilities/dec_q/mod.rs +++ b/src/utilities/dec_q/mod.rs @@ -1,29 +1,32 @@ #![allow(non_snake_case)] /* - CGGMP Threshold ECDSA + CGGMP Threshold ECDSA - Copyright 2022 by Webb Technologies + Copyright 2022 by Webb Technologies - This file is part of CGGMP Threshold ECDSA library - (https://github.com/webb-tools/cggmp-threshold-ecdsa) + This file is part of CGGMP Threshold ECDSA library + (https://github.com/webb-tools/cggmp-threshold-ecdsa) - CGGMP Threshold ECDSA is free software: you can redistribute - it and/or modify it under the terms of the GNU General Public - License as published by the Free Software Foundation, either - version 3 of the License, or (at your option) any later version. + CGGMP Threshold ECDSA is free software: you can redistribute + it and/or modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, either + version 3 of the License, or (at your option) any later version. - @license GPL-3.0+ + @license GPL-3.0+ */ use super::{sample_relatively_prime_integer, L, L_PLUS_EPSILON}; use crate::{utilities::mod_pow_with_negative, Error}; use curv::{ - arithmetic::{traits::*, Modulo}, - cryptographic_primitives::hashing::{Digest, DigestExt}, - elliptic::curves::{Curve, Scalar}, - BigInt, + arithmetic::{traits::*, Modulo}, + cryptographic_primitives::hashing::{Digest, DigestExt}, + elliptic::curves::{Curve, Scalar}, + BigInt, +}; +use paillier::{ + EncryptWithChosenRandomness, EncryptionKey, Paillier, Randomness, + RawPlaintext, }; -use paillier::{EncryptWithChosenRandomness, EncryptionKey, Paillier, Randomness, RawPlaintext}; use rand::Rng; use rand_chacha::{rand_core::SeedableRng, ChaChaRng}; use serde::{Deserialize, Serialize}; @@ -31,253 +34,301 @@ use std::marker::PhantomData; #[derive(Clone, Debug, Serialize, Deserialize)] pub struct PaillierDecryptionModQStatement { - pub S: BigInt, - pub T: BigInt, - pub N_hat: BigInt, - pub N0: BigInt, - pub NN0: BigInt, - pub C: BigInt, - pub x: BigInt, - pub ek_prover: EncryptionKey, - pub phantom: PhantomData<(E, H)>, + pub S: BigInt, + pub T: BigInt, + pub N_hat: BigInt, + pub N0: BigInt, + pub NN0: BigInt, + pub C: BigInt, + pub x: BigInt, + pub ek_prover: EncryptionKey, + pub phantom: PhantomData<(E, H)>, } pub struct PaillierDecryptionModQWitness { - y: BigInt, - rho: BigInt, - phantom: PhantomData<(E, H)>, + y: BigInt, + rho: BigInt, + phantom: PhantomData<(E, H)>, } impl PaillierDecryptionModQWitness { - pub fn new(y: BigInt, rho: BigInt) -> Self { - PaillierDecryptionModQWitness { y, rho, phantom: PhantomData } - } + pub fn new(y: BigInt, rho: BigInt) -> Self { + PaillierDecryptionModQWitness { + y, + rho, + phantom: PhantomData, + } + } } impl PaillierDecryptionModQStatement { - #[allow(clippy::too_many_arguments)] - pub fn generate( - S: BigInt, - T: BigInt, - N_hat: BigInt, - rho: BigInt, - prover: EncryptionKey, - ) -> (Self, PaillierDecryptionModQWitness) { - let ek_prover = prover.clone(); - // y <- Z_N - let y = BigInt::sample_below(&prover.n); - // C = (1 + N0)^y * rho^N0 mod N0^2 - let C = { - let C_ciphertext = Paillier::encrypt_with_chosen_randomness( - &ek_prover, - RawPlaintext::from(y.clone()), - &Randomness::from(rho.clone()), - ); - let C: BigInt = C_ciphertext.into(); - C - }; - let x = BigInt::mod_floor(&y, Scalar::::group_order()); - ( - Self { - S, - T, - N_hat, - N0: prover.n, - NN0: prover.nn, - C, - x, - ek_prover, - phantom: PhantomData, - }, - PaillierDecryptionModQWitness { y, rho, phantom: PhantomData }, - ) - } + #[allow(clippy::too_many_arguments)] + pub fn generate( + S: BigInt, + T: BigInt, + N_hat: BigInt, + rho: BigInt, + prover: EncryptionKey, + ) -> (Self, PaillierDecryptionModQWitness) { + let ek_prover = prover.clone(); + // y <- Z_N + let y = BigInt::sample_below(&prover.n); + // C = (1 + N0)^y * rho^N0 mod N0^2 + let C = { + let C_ciphertext = Paillier::encrypt_with_chosen_randomness( + &ek_prover, + RawPlaintext::from(y.clone()), + &Randomness::from(rho.clone()), + ); + let C: BigInt = C_ciphertext.into(); + C + }; + let x = BigInt::mod_floor(&y, Scalar::::group_order()); + ( + Self { + S, + T, + N_hat, + N0: prover.n, + NN0: prover.nn, + C, + x, + ek_prover, + phantom: PhantomData, + }, + PaillierDecryptionModQWitness { + y, + rho, + phantom: PhantomData, + }, + ) + } } #[derive(Clone, Debug, Serialize, Deserialize)] pub struct PaillierDecryptionModQCommitment { - A: BigInt, - gamma: BigInt, - big_S: BigInt, - big_T: BigInt, + A: BigInt, + gamma: BigInt, + big_S: BigInt, + big_T: BigInt, } #[derive(Clone, Debug, Serialize, Deserialize)] pub struct PaillierDecryptionModQProof { - z1: BigInt, - z2: BigInt, - w: BigInt, - commitment: PaillierDecryptionModQCommitment, - phantom: PhantomData<(E, H)>, + z1: BigInt, + z2: BigInt, + w: BigInt, + commitment: PaillierDecryptionModQCommitment, + phantom: PhantomData<(E, H)>, } // Link to the UC non-interactive threshold ECDSA paper impl PaillierDecryptionModQProof { - pub fn prove( - witness: &PaillierDecryptionModQWitness, - statement: &PaillierDecryptionModQStatement, - ) -> PaillierDecryptionModQProof { - // Set up exponents - let l_exp = BigInt::pow(&BigInt::from(2), L as u32); - let lplus_exp = BigInt::pow(&BigInt::from(2), L_PLUS_EPSILON as u32); - // α ← ± 2^{l+ε} - let alpha = BigInt::sample_range(&BigInt::from(-1).mul(&lplus_exp), &lplus_exp); - // mu ← ± 2l · Nˆ - let mu = BigInt::sample_range( - &BigInt::from(-1).mul(&l_exp).mul(&statement.N_hat), - &l_exp.mul(&statement.N_hat), - ); - // nu ← 2^{l+ε} · Nˆ - let nu = BigInt::sample_range( - &BigInt::from(-1).mul(&lplus_exp).mul(&statement.N_hat), - &lplus_exp.mul(&statement.N_hat), - ); - // r <- Z*_N - let r = sample_relatively_prime_integer(&statement.N0); - // big_S = s^y * t^μ mod Nˆ - let big_S = { - let s = BigInt::mod_pow(&statement.S, &witness.y, &statement.N_hat); - let t = mod_pow_with_negative(&statement.T, &mu, &statement.N_hat); - BigInt::mod_mul(&s, &t, &statement.N_hat) - }; - // big_T = s^α & t^ν mod Nˆ - let big_T = { - let s = mod_pow_with_negative(&statement.S, &alpha, &statement.N_hat); - let t = mod_pow_with_negative(&statement.T, &nu, &statement.N_hat); - BigInt::mod_mul(&s, &t, &statement.N_hat) - }; - // A = (1 + N0)^α * r^N0 mod N0^2 - let A = { - let A_ciphertext = Paillier::encrypt_with_chosen_randomness( - &statement.ek_prover, - RawPlaintext::from(alpha.clone()), - &Randomness::from(r.clone()), - ); - let A: BigInt = A_ciphertext.into(); - A.mod_floor(&statement.NN0) - }; - // gamma = alpha mod q - let gamma = BigInt::mod_floor(&alpha, Scalar::::group_order()); - // Generate NIZK challenge - let mut e = H::new() - .chain_bigint(&A) - .chain_bigint(&gamma) - .chain_bigint(&big_S) - .chain_bigint(&big_T) - .result_bigint(); - let mut rng: ChaChaRng = ChaChaRng::from_seed(e.to_bytes().try_into().unwrap()); - let val = rng.gen_range(0..2); - e = BigInt::from(val).mul(&BigInt::from(-2)).add(&BigInt::one()).mul(&e); - let commitment: PaillierDecryptionModQCommitment = - PaillierDecryptionModQCommitment { A, gamma, big_S, big_T }; - // z1 = α + e · y - let z1 = BigInt::add(&alpha, &BigInt::mul(&e, &witness.y)); - // z2 = ν + e · μ - let z2 = BigInt::add(&nu, &BigInt::mul(&e, &mu)); - // w = r · rho^e mod N0 - let w = { - let rho = mod_pow_with_negative(&witness.rho, &e, &statement.N0); - BigInt::mod_mul(&r, &rho, &statement.N0) - }; - // Return the proof - PaillierDecryptionModQProof { z1, z2, w, commitment, phantom: PhantomData } - } + pub fn prove( + witness: &PaillierDecryptionModQWitness, + statement: &PaillierDecryptionModQStatement, + ) -> PaillierDecryptionModQProof { + // Set up exponents + let l_exp = BigInt::pow(&BigInt::from(2), L as u32); + let lplus_exp = BigInt::pow(&BigInt::from(2), L_PLUS_EPSILON as u32); + // α ← ± 2^{l+ε} + let alpha = + BigInt::sample_range(&BigInt::from(-1).mul(&lplus_exp), &lplus_exp); + // mu ← ± 2l · Nˆ + let mu = BigInt::sample_range( + &BigInt::from(-1).mul(&l_exp).mul(&statement.N_hat), + &l_exp.mul(&statement.N_hat), + ); + // nu ← 2^{l+ε} · Nˆ + let nu = BigInt::sample_range( + &BigInt::from(-1).mul(&lplus_exp).mul(&statement.N_hat), + &lplus_exp.mul(&statement.N_hat), + ); + // r <- Z*_N + let r = sample_relatively_prime_integer(&statement.N0); + // big_S = s^y * t^μ mod Nˆ + let big_S = { + let s = BigInt::mod_pow(&statement.S, &witness.y, &statement.N_hat); + let t = mod_pow_with_negative(&statement.T, &mu, &statement.N_hat); + BigInt::mod_mul(&s, &t, &statement.N_hat) + }; + // big_T = s^α & t^ν mod Nˆ + let big_T = { + let s = + mod_pow_with_negative(&statement.S, &alpha, &statement.N_hat); + let t = mod_pow_with_negative(&statement.T, &nu, &statement.N_hat); + BigInt::mod_mul(&s, &t, &statement.N_hat) + }; + // A = (1 + N0)^α * r^N0 mod N0^2 + let A = { + let A_ciphertext = Paillier::encrypt_with_chosen_randomness( + &statement.ek_prover, + RawPlaintext::from(alpha.clone()), + &Randomness::from(r.clone()), + ); + let A: BigInt = A_ciphertext.into(); + A.mod_floor(&statement.NN0) + }; + // gamma = alpha mod q + let gamma = BigInt::mod_floor(&alpha, Scalar::::group_order()); + // Generate NIZK challenge + let mut e = H::new() + .chain_bigint(&A) + .chain_bigint(&gamma) + .chain_bigint(&big_S) + .chain_bigint(&big_T) + .result_bigint(); + let mut rng: ChaChaRng = + ChaChaRng::from_seed(e.to_bytes().try_into().unwrap()); + let val = rng.gen_range(0..2); + e = BigInt::from(val) + .mul(&BigInt::from(-2)) + .add(&BigInt::one()) + .mul(&e); + let commitment: PaillierDecryptionModQCommitment = + PaillierDecryptionModQCommitment { + A, + gamma, + big_S, + big_T, + }; + // z1 = α + e · y + let z1 = BigInt::add(&alpha, &BigInt::mul(&e, &witness.y)); + // z2 = ν + e · μ + let z2 = BigInt::add(&nu, &BigInt::mul(&e, &mu)); + // w = r · rho^e mod N0 + let w = { + let rho = mod_pow_with_negative(&witness.rho, &e, &statement.N0); + BigInt::mod_mul(&r, &rho, &statement.N0) + }; + // Return the proof + PaillierDecryptionModQProof { + z1, + z2, + w, + commitment, + phantom: PhantomData, + } + } - pub fn verify( - proof: &PaillierDecryptionModQProof, - statement: &PaillierDecryptionModQStatement, - ) -> Result<(), Error> { - // Compute the challenge - let mut e = H::new() - .chain_bigint(&proof.commitment.A) - .chain_bigint(&proof.commitment.gamma) - .chain_bigint(&proof.commitment.big_S) - .chain_bigint(&proof.commitment.big_T) - .result_bigint(); - let mut rng: ChaChaRng = ChaChaRng::from_seed(e.to_bytes().try_into().unwrap()); - let val = rng.gen_range(0..2); - e = BigInt::from(val).mul(&BigInt::from(-2)).add(&BigInt::one()).mul(&e); - /* - FIRST EQUALITY CHECK - (1 + N0)^z1 · w^N0 = A · C^e mod N0^2 === Enc(z1,w) = A · C^e mod N0^2 - */ - // Compute the left hand side - let left_1 = { - let left_1_ciphertext = Paillier::encrypt_with_chosen_randomness( - &statement.ek_prover, - RawPlaintext::from(proof.z1.clone()), - &Randomness::from(proof.w.clone()), - ); - let left_1: BigInt = left_1_ciphertext.into(); - left_1.mod_floor(&statement.NN0) - }; - // Compute the right hand side - let right_1 = { - let C = mod_pow_with_negative(&statement.C, &e, &statement.NN0); - BigInt::mod_mul(&proof.commitment.A, &C, &statement.NN0) - }; - // Check the equality - assert!(left_1 == right_1); - /* - SECOND EQUALITY CHECK - z1 = γ + e * x mod q - */ - // Compute the left hand side - let left_2 = proof.z1.clone().mod_floor(Scalar::::group_order()); - // Compute the right hand side - let right_2 = BigInt::mod_add( - &proof.commitment.gamma, - &BigInt::mod_mul(&e, &statement.x, Scalar::::group_order()), - Scalar::::group_order(), - ); - // Check the equality - assert!(left_2 == right_2); - /* - THIRD EQUALITY CHECK - s^z1 · t^z2 = T · S^e mod Nˆ - */ - // Compute the left hand side - let left_3 = { - let s = mod_pow_with_negative(&statement.S, &proof.z1, &statement.N_hat); - let t = mod_pow_with_negative(&statement.T, &proof.z2, &statement.N_hat); - BigInt::mod_mul(&s, &t, &statement.N_hat) - }; - // Compute the right hand side - let right_3 = { - let temp = mod_pow_with_negative(&proof.commitment.big_S, &e, &statement.N_hat); - BigInt::mod_mul(&proof.commitment.big_T, &temp, &statement.N_hat) - }; - // Check the equality - assert!(left_3 == right_3); - Ok(()) - } + pub fn verify( + proof: &PaillierDecryptionModQProof, + statement: &PaillierDecryptionModQStatement, + ) -> Result<(), Error> { + // Compute the challenge + let mut e = H::new() + .chain_bigint(&proof.commitment.A) + .chain_bigint(&proof.commitment.gamma) + .chain_bigint(&proof.commitment.big_S) + .chain_bigint(&proof.commitment.big_T) + .result_bigint(); + let mut rng: ChaChaRng = + ChaChaRng::from_seed(e.to_bytes().try_into().unwrap()); + let val = rng.gen_range(0..2); + e = BigInt::from(val) + .mul(&BigInt::from(-2)) + .add(&BigInt::one()) + .mul(&e); + /* + FIRST EQUALITY CHECK + (1 + N0)^z1 · w^N0 = A · C^e mod N0^2 === Enc(z1,w) = A · C^e mod N0^2 + */ + // Compute the left hand side + let left_1 = { + let left_1_ciphertext = Paillier::encrypt_with_chosen_randomness( + &statement.ek_prover, + RawPlaintext::from(proof.z1.clone()), + &Randomness::from(proof.w.clone()), + ); + let left_1: BigInt = left_1_ciphertext.into(); + left_1.mod_floor(&statement.NN0) + }; + // Compute the right hand side + let right_1 = { + let C = mod_pow_with_negative(&statement.C, &e, &statement.NN0); + BigInt::mod_mul(&proof.commitment.A, &C, &statement.NN0) + }; + // Check the equality + assert!(left_1 == right_1); + /* + SECOND EQUALITY CHECK + z1 = γ + e * x mod q + */ + // Compute the left hand side + let left_2 = proof.z1.clone().mod_floor(Scalar::::group_order()); + // Compute the right hand side + let right_2 = BigInt::mod_add( + &proof.commitment.gamma, + &BigInt::mod_mul(&e, &statement.x, Scalar::::group_order()), + Scalar::::group_order(), + ); + // Check the equality + assert!(left_2 == right_2); + /* + THIRD EQUALITY CHECK + s^z1 · t^z2 = T · S^e mod Nˆ + */ + // Compute the left hand side + let left_3 = { + let s = mod_pow_with_negative( + &statement.S, + &proof.z1, + &statement.N_hat, + ); + let t = mod_pow_with_negative( + &statement.T, + &proof.z2, + &statement.N_hat, + ); + BigInt::mod_mul(&s, &t, &statement.N_hat) + }; + // Compute the right hand side + let right_3 = { + let temp = mod_pow_with_negative( + &proof.commitment.big_S, + &e, + &statement.N_hat, + ); + BigInt::mod_mul(&proof.commitment.big_T, &temp, &statement.N_hat) + }; + // Check the equality + assert!(left_3 == right_3); + Ok(()) + } } #[cfg(test)] mod tests { - use super::*; - use crate::utilities::{mta::range_proofs::SampleFromMultiplicativeGroup, BITS_PAILLIER}; - use curv::elliptic::curves::secp256_k1::Secp256k1; - use fs_dkr::ring_pedersen_proof::RingPedersenStatement; - use paillier::{KeyGeneration, Paillier}; - use sha2::Sha256; + use super::*; + use crate::utilities::{ + mta::range_proofs::SampleFromMultiplicativeGroup, BITS_PAILLIER, + }; + use curv::elliptic::curves::secp256_k1::Secp256k1; + use fs_dkr::ring_pedersen_proof::RingPedersenStatement; + use paillier::{KeyGeneration, Paillier}; + use sha2::Sha256; - #[test] - fn test_paillier_decryption_modulo_q() { - let (ring_pedersen_statement, _witness) = - RingPedersenStatement::::generate(); - let (ek_prover, _) = Paillier::keypair_with_modulus_size(BITS_PAILLIER).keys(); - let rho: BigInt = BigInt::from_paillier_key(&ek_prover); - let (statement, witness) = PaillierDecryptionModQStatement::::generate( - ring_pedersen_statement.S, - ring_pedersen_statement.T, - ring_pedersen_statement.N, - rho, - ek_prover, - ); - let proof = PaillierDecryptionModQProof::::prove(&witness, &statement); - assert!( - PaillierDecryptionModQProof::::verify(&proof, &statement).is_ok() - ); - } + #[test] + fn test_paillier_decryption_modulo_q() { + let (ring_pedersen_statement, _witness) = + RingPedersenStatement::::generate(); + let (ek_prover, _) = + Paillier::keypair_with_modulus_size(BITS_PAILLIER).keys(); + let rho: BigInt = BigInt::from_paillier_key(&ek_prover); + let (statement, witness) = + PaillierDecryptionModQStatement::::generate( + ring_pedersen_statement.S, + ring_pedersen_statement.T, + ring_pedersen_statement.N, + rho, + ek_prover, + ); + let proof = PaillierDecryptionModQProof::::prove( + &witness, &statement, + ); + assert!(PaillierDecryptionModQProof::::verify( + &proof, &statement + ) + .is_ok()); + } } diff --git a/src/utilities/enc/mod.rs b/src/utilities/enc/mod.rs index c03308f4..b1d909d4 100644 --- a/src/utilities/enc/mod.rs +++ b/src/utilities/enc/mod.rs @@ -1,18 +1,18 @@ #![allow(non_snake_case)] /* - CGGMP Threshold ECDSA + CGGMP Threshold ECDSA - Copyright 2022 by Webb Technologies + Copyright 2022 by Webb Technologies - This file is part of CGGMP Threshold ECDSA library - (https://github.com/webb-tools/cggmp-threshold-ecdsa) + This file is part of CGGMP Threshold ECDSA library + (https://github.com/webb-tools/cggmp-threshold-ecdsa) - CGGMP Threshold ECDSA is free software: you can redistribute - it and/or modify it under the terms of the GNU General Public - License as published by the Free Software Foundation, either - version 3 of the License, or (at your option) any later version. + CGGMP Threshold ECDSA is free software: you can redistribute + it and/or modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, either + version 3 of the License, or (at your option) any later version. - @license GPL-3.0+ + @license GPL-3.0+ */ //! Paillier Encryption in Range ZK – Π^enc @@ -23,246 +23,301 @@ use super::sample_relatively_prime_integer; use crate::utilities::{mod_pow_with_negative, L}; use curv::{ - arithmetic::{traits::*, Modulo}, - cryptographic_primitives::hashing::{Digest, DigestExt}, - elliptic::curves::{Curve, Scalar}, - BigInt, + arithmetic::{traits::*, Modulo}, + cryptographic_primitives::hashing::{Digest, DigestExt}, + elliptic::curves::{Curve, Scalar}, + BigInt, +}; +use paillier::{ + EncryptWithChosenRandomness, EncryptionKey, Paillier, Randomness, + RawPlaintext, }; -use paillier::{EncryptWithChosenRandomness, EncryptionKey, Paillier, Randomness, RawPlaintext}; use serde::{Deserialize, Serialize}; use std::marker::PhantomData; use zk_paillier::zkproofs::IncorrectProof; #[derive(Clone, Debug, Serialize, Deserialize)] pub struct PaillierEncryptionInRangeStatement { - pub N0: BigInt, - pub NN0: BigInt, - pub K: BigInt, - pub s: BigInt, - pub t: BigInt, - pub N_hat: BigInt, - pub phantom: PhantomData<(E, H)>, + pub N0: BigInt, + pub NN0: BigInt, + pub K: BigInt, + pub s: BigInt, + pub t: BigInt, + pub N_hat: BigInt, + pub phantom: PhantomData<(E, H)>, } #[derive(Clone, Debug, Serialize, Deserialize)] pub struct PaillierEncryptionInRangeWitness { - k: BigInt, - rho: BigInt, - phantom: PhantomData<(E, H)>, + k: BigInt, + rho: BigInt, + phantom: PhantomData<(E, H)>, } impl PaillierEncryptionInRangeWitness { - pub fn new(k: BigInt, rho: BigInt) -> Self { - PaillierEncryptionInRangeWitness { k, rho, phantom: PhantomData } - } + pub fn new(k: BigInt, rho: BigInt) -> Self { + PaillierEncryptionInRangeWitness { + k, + rho, + phantom: PhantomData, + } + } } impl PaillierEncryptionInRangeStatement { - #[allow(clippy::too_many_arguments)] - pub fn generate( - rho: BigInt, - s: BigInt, - t: BigInt, - N_hat: BigInt, - paillier_key: EncryptionKey, - ) -> (Self, PaillierEncryptionInRangeWitness) { - // Set up exponents - let _l_exp = BigInt::pow(&BigInt::from(2), L as u32); - // Set up moduli - let N0 = paillier_key.clone().n; - let NN0 = paillier_key.clone().nn; - let k = BigInt::sample_below(Scalar::::group_order()); - let K: BigInt = Paillier::encrypt_with_chosen_randomness( - &paillier_key, - RawPlaintext::from(&k), - &Randomness::from(&rho), - ) - .into(); - - ( - Self { N0, NN0, K, s, t, N_hat, phantom: PhantomData }, - PaillierEncryptionInRangeWitness { k, rho, phantom: PhantomData }, - ) - } + #[allow(clippy::too_many_arguments)] + pub fn generate( + rho: BigInt, + s: BigInt, + t: BigInt, + N_hat: BigInt, + paillier_key: EncryptionKey, + ) -> (Self, PaillierEncryptionInRangeWitness) { + // Set up exponents + let _l_exp = BigInt::pow(&BigInt::from(2), L as u32); + // Set up moduli + let N0 = paillier_key.clone().n; + let NN0 = paillier_key.clone().nn; + let k = BigInt::sample_below(Scalar::::group_order()); + let K: BigInt = Paillier::encrypt_with_chosen_randomness( + &paillier_key, + RawPlaintext::from(&k), + &Randomness::from(&rho), + ) + .into(); + + ( + Self { + N0, + NN0, + K, + s, + t, + N_hat, + phantom: PhantomData, + }, + PaillierEncryptionInRangeWitness { + k, + rho, + phantom: PhantomData, + }, + ) + } } #[derive(Clone, Debug, Serialize, Deserialize)] pub struct PaillierEncryptionInRangeCommitment { - S: BigInt, - A: BigInt, - C: BigInt, + S: BigInt, + A: BigInt, + C: BigInt, } #[derive(Clone, Debug, Serialize, Deserialize)] pub struct PaillierEncryptionInRangeProof { - z_1: BigInt, - z_2: BigInt, - z_3: BigInt, - commitment: PaillierEncryptionInRangeCommitment, - phantom: PhantomData<(E, H)>, + z_1: BigInt, + z_2: BigInt, + z_3: BigInt, + commitment: PaillierEncryptionInRangeCommitment, + phantom: PhantomData<(E, H)>, } impl PaillierEncryptionInRangeProof { - #[allow(dead_code)] - pub fn prove( - witness: &PaillierEncryptionInRangeWitness, - statement: &PaillierEncryptionInRangeStatement, - ) -> Self { - // Step 1: Sample alpha between -2^{L+eps} and 2^{L+eps} - let alpha_upper = BigInt::pow(&BigInt::from(2), crate::utilities::L_PLUS_EPSILON as u32); - let alpha_lower = BigInt::from(-1).mul(&alpha_upper); - let alpha = BigInt::sample_range(&alpha_lower, &alpha_upper); - - // Step 2: mu, r, gamma - // Sample mu between -2^L * N_hat and 2^L * N_hat - let mu_upper = BigInt::mul( - &statement.N_hat, - &BigInt::pow(&BigInt::from(2), crate::utilities::L as u32), - ); - let mu_lower = BigInt::from(-1).mul(&mu_upper); - let mu = BigInt::sample_range(&mu_lower, &mu_upper); - - // γ ← ± 2^{l+ε} · Nˆ - let gamma_upper = BigInt::mul( - &statement.N_hat, - &BigInt::pow(&BigInt::from(2), crate::utilities::L_PLUS_EPSILON as u32), - ); - let gamma_lower = BigInt::from(-1).mul(&gamma_upper); - let gamma = BigInt::sample_range(&gamma_lower, &gamma_upper); - // Sample r from Z*_{N_0} - let r = sample_relatively_prime_integer(&statement.N0.clone()); - - // Step 3: S, A, C - // S = s^k t^mu mod N_hat - let S = BigInt::mod_mul( - &mod_pow_with_negative(&statement.s, &witness.k, &statement.N_hat), - &mod_pow_with_negative(&statement.t, &mu, &statement.N_hat), - &statement.N_hat, - ); - - // A = (1+N_0)^{alpha}r^{N_0} mod N_0^2 - let A: BigInt = Paillier::encrypt_with_chosen_randomness( - &EncryptionKey { n: statement.N0.clone(), nn: statement.NN0.clone() }, - RawPlaintext::from(&alpha), - &Randomness::from(&r), - ) - .into(); - - // C = s^alpha * t^gamma mod N_hat - let C = BigInt::mod_mul( - &mod_pow_with_negative(&statement.s, &alpha, &statement.N_hat), - &mod_pow_with_negative(&statement.t, &gamma, &statement.N_hat), - &statement.N_hat, - ); - - let commitment = - PaillierEncryptionInRangeCommitment { S: S.clone(), A: A.clone(), C: C.clone() }; - - // Step 4: Hash S, A, C - let e = H::new().chain_bigint(&S).chain_bigint(&A).chain_bigint(&C).result_bigint(); - - // Step 5: Compute z_1, z_2, z_3 - // z_1 = alpha + ek - let z_1 = BigInt::add(&alpha, &BigInt::mul(&e, &witness.k)); - // z_2 = r * rho^e mod N_0 - let z_2 = BigInt::mod_mul( - &r, - &mod_pow_with_negative(&witness.rho, &e, &statement.N0), - &statement.N0, - ); - // z_3 = gamma + e*mu - let z_3 = BigInt::add(&gamma, &BigInt::mul(&e, &mu)); - - Self { z_1, z_2, z_3, commitment, phantom: PhantomData } - } - - #[allow(dead_code)] - pub fn verify( - proof: &PaillierEncryptionInRangeProof, - statement: &PaillierEncryptionInRangeStatement, - ) -> Result<(), IncorrectProof> { - let e = H::new() - .chain_bigint(&proof.commitment.S) - .chain_bigint(&proof.commitment.A) - .chain_bigint(&proof.commitment.C) - .result_bigint(); - - // Equality Checks - let NN0 = statement.NN0.clone(); - // left_1 = (1+N_0)^{z_1}z_2^{N_0} mod N_0^2 - let left_1: BigInt = Paillier::encrypt_with_chosen_randomness( - &EncryptionKey { n: statement.N0.clone(), nn: NN0.clone() }, - RawPlaintext::from(&proof.z_1), - &Randomness::from(&proof.z_2), - ) - .into(); - // right_1 = A * K^e mod N_0^2 - let right_1 = BigInt::mod_mul( - &proof.commitment.A, - &mod_pow_with_negative(&statement.K, &e, &NN0), - &NN0, - ); - - // left_2 = s^z_1 t^z_3 mod N_hat - let left_2 = BigInt::mod_mul( - &mod_pow_with_negative(&statement.s, &proof.z_1, &statement.N_hat), - &mod_pow_with_negative(&statement.t, &proof.z_3, &statement.N_hat), - &statement.N_hat, - ); - // right_2 = C * S^e mod N_hat - let right_2 = BigInt::mod_mul( - &proof.commitment.C, - &mod_pow_with_negative(&proof.commitment.S, &e, &statement.N_hat), - &statement.N_hat, - ); - - if left_1.mod_floor(&NN0) != right_1 || left_2 != right_2 { - return Err(IncorrectProof) - } - - // Range Check -2^{L + eps} <= z_1 <= 2^{L+eps} - let lower_bound_check: bool = proof.z_1 >= - BigInt::from(-1) - .mul(&BigInt::pow(&BigInt::from(2), crate::utilities::L_PLUS_EPSILON as u32)); - - let upper_bound_check = - proof.z_1 <= BigInt::pow(&BigInt::from(2), crate::utilities::L_PLUS_EPSILON as u32); - - if !(lower_bound_check && upper_bound_check) { - return Err(IncorrectProof) - } - - Ok(()) - } + #[allow(dead_code)] + pub fn prove( + witness: &PaillierEncryptionInRangeWitness, + statement: &PaillierEncryptionInRangeStatement, + ) -> Self { + // Step 1: Sample alpha between -2^{L+eps} and 2^{L+eps} + let alpha_upper = BigInt::pow( + &BigInt::from(2), + crate::utilities::L_PLUS_EPSILON as u32, + ); + let alpha_lower = BigInt::from(-1).mul(&alpha_upper); + let alpha = BigInt::sample_range(&alpha_lower, &alpha_upper); + + // Step 2: mu, r, gamma + // Sample mu between -2^L * N_hat and 2^L * N_hat + let mu_upper = BigInt::mul( + &statement.N_hat, + &BigInt::pow(&BigInt::from(2), crate::utilities::L as u32), + ); + let mu_lower = BigInt::from(-1).mul(&mu_upper); + let mu = BigInt::sample_range(&mu_lower, &mu_upper); + + // γ ← ± 2^{l+ε} · Nˆ + let gamma_upper = BigInt::mul( + &statement.N_hat, + &BigInt::pow( + &BigInt::from(2), + crate::utilities::L_PLUS_EPSILON as u32, + ), + ); + let gamma_lower = BigInt::from(-1).mul(&gamma_upper); + let gamma = BigInt::sample_range(&gamma_lower, &gamma_upper); + // Sample r from Z*_{N_0} + let r = sample_relatively_prime_integer(&statement.N0.clone()); + + // Step 3: S, A, C + // S = s^k t^mu mod N_hat + let S = BigInt::mod_mul( + &mod_pow_with_negative(&statement.s, &witness.k, &statement.N_hat), + &mod_pow_with_negative(&statement.t, &mu, &statement.N_hat), + &statement.N_hat, + ); + + // A = (1+N_0)^{alpha}r^{N_0} mod N_0^2 + let A: BigInt = Paillier::encrypt_with_chosen_randomness( + &EncryptionKey { + n: statement.N0.clone(), + nn: statement.NN0.clone(), + }, + RawPlaintext::from(&alpha), + &Randomness::from(&r), + ) + .into(); + + // C = s^alpha * t^gamma mod N_hat + let C = BigInt::mod_mul( + &mod_pow_with_negative(&statement.s, &alpha, &statement.N_hat), + &mod_pow_with_negative(&statement.t, &gamma, &statement.N_hat), + &statement.N_hat, + ); + + let commitment = PaillierEncryptionInRangeCommitment { + S: S.clone(), + A: A.clone(), + C: C.clone(), + }; + + // Step 4: Hash S, A, C + let e = H::new() + .chain_bigint(&S) + .chain_bigint(&A) + .chain_bigint(&C) + .result_bigint(); + + // Step 5: Compute z_1, z_2, z_3 + // z_1 = alpha + ek + let z_1 = BigInt::add(&alpha, &BigInt::mul(&e, &witness.k)); + // z_2 = r * rho^e mod N_0 + let z_2 = BigInt::mod_mul( + &r, + &mod_pow_with_negative(&witness.rho, &e, &statement.N0), + &statement.N0, + ); + // z_3 = gamma + e*mu + let z_3 = BigInt::add(&gamma, &BigInt::mul(&e, &mu)); + + Self { + z_1, + z_2, + z_3, + commitment, + phantom: PhantomData, + } + } + + #[allow(dead_code)] + pub fn verify( + proof: &PaillierEncryptionInRangeProof, + statement: &PaillierEncryptionInRangeStatement, + ) -> Result<(), IncorrectProof> { + let e = H::new() + .chain_bigint(&proof.commitment.S) + .chain_bigint(&proof.commitment.A) + .chain_bigint(&proof.commitment.C) + .result_bigint(); + + // Equality Checks + let NN0 = statement.NN0.clone(); + // left_1 = (1+N_0)^{z_1}z_2^{N_0} mod N_0^2 + let left_1: BigInt = Paillier::encrypt_with_chosen_randomness( + &EncryptionKey { + n: statement.N0.clone(), + nn: NN0.clone(), + }, + RawPlaintext::from(&proof.z_1), + &Randomness::from(&proof.z_2), + ) + .into(); + // right_1 = A * K^e mod N_0^2 + let right_1 = BigInt::mod_mul( + &proof.commitment.A, + &mod_pow_with_negative(&statement.K, &e, &NN0), + &NN0, + ); + + // left_2 = s^z_1 t^z_3 mod N_hat + let left_2 = BigInt::mod_mul( + &mod_pow_with_negative(&statement.s, &proof.z_1, &statement.N_hat), + &mod_pow_with_negative(&statement.t, &proof.z_3, &statement.N_hat), + &statement.N_hat, + ); + // right_2 = C * S^e mod N_hat + let right_2 = BigInt::mod_mul( + &proof.commitment.C, + &mod_pow_with_negative(&proof.commitment.S, &e, &statement.N_hat), + &statement.N_hat, + ); + + if left_1.mod_floor(&NN0) != right_1 || left_2 != right_2 { + return Err(IncorrectProof); + } + + // Range Check -2^{L + eps} <= z_1 <= 2^{L+eps} + let lower_bound_check: bool = proof.z_1 + >= BigInt::from(-1).mul(&BigInt::pow( + &BigInt::from(2), + crate::utilities::L_PLUS_EPSILON as u32, + )); + + let upper_bound_check = proof.z_1 + <= BigInt::pow( + &BigInt::from(2), + crate::utilities::L_PLUS_EPSILON as u32, + ); + + if !(lower_bound_check && upper_bound_check) { + return Err(IncorrectProof); + } + + Ok(()) + } } #[cfg(test)] mod tests { - use super::*; - use crate::utilities::{mta::range_proofs::SampleFromMultiplicativeGroup, BITS_PAILLIER}; - use curv::elliptic::curves::secp256_k1::Secp256k1; - use fs_dkr::ring_pedersen_proof::RingPedersenStatement; - use paillier::{KeyGeneration, Paillier}; - use sha2::Sha256; - - #[test] - fn test_paillier_encryption_in_range_proof() { - let (ring_pedersen_statement, _witness) = - RingPedersenStatement::::generate(); - let (paillier_key, _) = Paillier::keypair_with_modulus_size(BITS_PAILLIER).keys(); - - let rho: BigInt = BigInt::from_paillier_key(&paillier_key); - let (statement, witness) = - PaillierEncryptionInRangeStatement::::generate( - rho, - ring_pedersen_statement.S, - ring_pedersen_statement.T, - ring_pedersen_statement.N, - paillier_key, - ); - let proof = - PaillierEncryptionInRangeProof::::prove(&witness, &statement); - assert!(PaillierEncryptionInRangeProof::::verify(&proof, &statement,) - .is_ok()); - } + use super::*; + use crate::utilities::{ + mta::range_proofs::SampleFromMultiplicativeGroup, BITS_PAILLIER, + }; + use curv::elliptic::curves::secp256_k1::Secp256k1; + use fs_dkr::ring_pedersen_proof::RingPedersenStatement; + use paillier::{KeyGeneration, Paillier}; + use sha2::Sha256; + + #[test] + fn test_paillier_encryption_in_range_proof() { + let (ring_pedersen_statement, _witness) = + RingPedersenStatement::::generate(); + let (paillier_key, _) = + Paillier::keypair_with_modulus_size(BITS_PAILLIER).keys(); + + let rho: BigInt = BigInt::from_paillier_key(&paillier_key); + let (statement, witness) = + PaillierEncryptionInRangeStatement::::generate( + rho, + ring_pedersen_statement.S, + ring_pedersen_statement.T, + ring_pedersen_statement.N, + paillier_key, + ); + let proof = PaillierEncryptionInRangeProof::::prove( + &witness, &statement, + ); + assert!(PaillierEncryptionInRangeProof::::verify( + &proof, &statement, + ) + .is_ok()); + } } diff --git a/src/utilities/log_star/mod.rs b/src/utilities/log_star/mod.rs index 29ac2f12..8189f617 100644 --- a/src/utilities/log_star/mod.rs +++ b/src/utilities/log_star/mod.rs @@ -1,18 +1,18 @@ #![allow(non_snake_case)] /* - CGGMP Threshold ECDSA + CGGMP Threshold ECDSA - Copyright 2022 by Webb Technologies + Copyright 2022 by Webb Technologies - This file is part of CGGMP Threshold ECDSA library - (https://github.com/webb-tools/cggmp-threshold-ecdsa) + This file is part of CGGMP Threshold ECDSA library + (https://github.com/webb-tools/cggmp-threshold-ecdsa) - CGGMP Threshold ECDSA is free software: you can redistribute - it and/or modify it under the terms of the GNU General Public - License as published by the Free Software Foundation, either - version 3 of the License, or (at your option) any later version. + CGGMP Threshold ECDSA is free software: you can redistribute + it and/or modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, either + version 3 of the License, or (at your option) any later version. - @license GPL-3.0+ + @license GPL-3.0+ */ //! Knowledge of Exponent vs Paillier Encryption – Π^{log∗} @@ -24,259 +24,329 @@ use super::sample_relatively_prime_integer; use crate::utilities::{mod_pow_with_negative, L}; use curv::{ - arithmetic::{traits::*, Modulo}, - cryptographic_primitives::hashing::{Digest, DigestExt}, - elliptic::curves::{Curve, Point, Scalar}, - BigInt, + arithmetic::{traits::*, Modulo}, + cryptographic_primitives::hashing::{Digest, DigestExt}, + elliptic::curves::{Curve, Point, Scalar}, + BigInt, +}; +use paillier::{ + EncryptWithChosenRandomness, EncryptionKey, Paillier, Randomness, + RawPlaintext, }; -use paillier::{EncryptWithChosenRandomness, EncryptionKey, Paillier, Randomness, RawPlaintext}; use serde::{Deserialize, Serialize}; use std::marker::PhantomData; use zk_paillier::zkproofs::IncorrectProof; #[derive(Clone, Debug, Serialize, Deserialize)] -pub struct KnowledgeOfExponentPaillierEncryptionStatement { - pub N0: BigInt, - pub NN0: BigInt, - pub C: BigInt, - pub X: Point, - // Πlog∗ states X = g^x. - // This g is not allows the generator of secp256k1. - // An example is 3-Round Presigning (see Figure 7, Round 3 in the paper) where: - // - x = k_i - // - X = Delta_i - // - Delta_i = Gamma^{k_i} - // :- g = Gamma - pub g: Point, - pub N_hat: BigInt, - pub s: BigInt, - pub t: BigInt, - pub phantom: PhantomData<(E, H)>, +pub struct KnowledgeOfExponentPaillierEncryptionStatement< + E: Curve, + H: Digest + Clone, +> { + pub N0: BigInt, + pub NN0: BigInt, + pub C: BigInt, + pub X: Point, + // Πlog∗ states X = g^x. + // This g is not allows the generator of secp256k1. + // An example is 3-Round Presigning (see Figure 7, Round 3 in the paper) + // where: + // - x = k_i + // - X = Delta_i + // - Delta_i = Gamma^{k_i} + // :- g = Gamma + pub g: Point, + pub N_hat: BigInt, + pub s: BigInt, + pub t: BigInt, + pub phantom: PhantomData<(E, H)>, } -pub struct KnowledgeOfExponentPaillierEncryptionWitness { - x: BigInt, - rho: BigInt, - phantom: PhantomData<(E, H)>, +pub struct KnowledgeOfExponentPaillierEncryptionWitness< + E: Curve, + H: Digest + Clone, +> { + x: BigInt, + rho: BigInt, + phantom: PhantomData<(E, H)>, } -impl KnowledgeOfExponentPaillierEncryptionWitness { - pub fn new(x: BigInt, rho: BigInt) -> Self { - KnowledgeOfExponentPaillierEncryptionWitness { x, rho, phantom: PhantomData } - } +impl + KnowledgeOfExponentPaillierEncryptionWitness +{ + pub fn new(x: BigInt, rho: BigInt) -> Self { + KnowledgeOfExponentPaillierEncryptionWitness { + x, + rho, + phantom: PhantomData, + } + } } -impl KnowledgeOfExponentPaillierEncryptionStatement { - #[allow(clippy::too_many_arguments)] - pub fn generate( - rho: BigInt, - g: Option>, - s: BigInt, - t: BigInt, - N_hat: BigInt, - paillier_key: EncryptionKey, - ) -> (Self, KnowledgeOfExponentPaillierEncryptionWitness) { - // Set up exponents - let l_exp = BigInt::pow(&BigInt::from(2), L as u32); - // Set up moduli - let N0 = paillier_key.clone().n; - let NN0 = paillier_key.nn; - let x = BigInt::sample_range(&BigInt::from(-1).mul(&l_exp), &l_exp); - let g = g.unwrap_or(Point::::generator().to_point()); - let X = &g * Scalar::from(&x); - let C: BigInt = Paillier::encrypt_with_chosen_randomness( - &EncryptionKey { n: N0.clone(), nn: NN0.clone() }, - RawPlaintext::from(&x), - &Randomness::from(&rho), - ) - .into(); - ( - Self { N0, NN0, C, X, g, N_hat, s, t, phantom: PhantomData }, - KnowledgeOfExponentPaillierEncryptionWitness { x, rho, phantom: PhantomData }, - ) - } +impl + KnowledgeOfExponentPaillierEncryptionStatement +{ + #[allow(clippy::too_many_arguments)] + pub fn generate( + rho: BigInt, + g: Option>, + s: BigInt, + t: BigInt, + N_hat: BigInt, + paillier_key: EncryptionKey, + ) -> (Self, KnowledgeOfExponentPaillierEncryptionWitness) { + // Set up exponents + let l_exp = BigInt::pow(&BigInt::from(2), L as u32); + // Set up moduli + let N0 = paillier_key.clone().n; + let NN0 = paillier_key.nn; + let x = BigInt::sample_range(&BigInt::from(-1).mul(&l_exp), &l_exp); + let g = g.unwrap_or(Point::::generator().to_point()); + let X = &g * Scalar::from(&x); + let C: BigInt = Paillier::encrypt_with_chosen_randomness( + &EncryptionKey { + n: N0.clone(), + nn: NN0.clone(), + }, + RawPlaintext::from(&x), + &Randomness::from(&rho), + ) + .into(); + ( + Self { + N0, + NN0, + C, + X, + g, + N_hat, + s, + t, + phantom: PhantomData, + }, + KnowledgeOfExponentPaillierEncryptionWitness { + x, + rho, + phantom: PhantomData, + }, + ) + } } #[derive(Clone, Debug, Serialize, Deserialize)] pub struct KnowledgeOfExponentPaillierEncryptionCommitment { - S: BigInt, - A: BigInt, - Y: Point, - D: BigInt, + S: BigInt, + A: BigInt, + Y: Point, + D: BigInt, } #[derive(Clone, Debug, Serialize, Deserialize)] -pub struct KnowledgeOfExponentPaillierEncryptionProof { - z_1: BigInt, - z_2: BigInt, - z_3: BigInt, - commitment: KnowledgeOfExponentPaillierEncryptionCommitment, - phantom: PhantomData<(E, H)>, +pub struct KnowledgeOfExponentPaillierEncryptionProof< + E: Curve, + H: Digest + Clone, +> { + z_1: BigInt, + z_2: BigInt, + z_3: BigInt, + commitment: KnowledgeOfExponentPaillierEncryptionCommitment, + phantom: PhantomData<(E, H)>, } // Link to the UC non-interactive threshold ECDSA paper -impl KnowledgeOfExponentPaillierEncryptionProof { - pub fn prove( - witness: &KnowledgeOfExponentPaillierEncryptionWitness, - statement: &KnowledgeOfExponentPaillierEncryptionStatement, - ) -> KnowledgeOfExponentPaillierEncryptionProof { - // Step 1: Sample alpha between -2^{l+ε} and 2^{l+ε} - let alpha_upper = BigInt::pow(&BigInt::from(2), crate::utilities::L_PLUS_EPSILON as u32); - let alpha_lower = BigInt::from(-1).mul(&alpha_upper); - let alpha = BigInt::sample_range(&alpha_lower, &alpha_upper); - - // Step 2: mu, r, gamma - // Sample mu between -2^L * N_hat and 2^L * N_hat - let mu_upper = BigInt::mul( - &statement.N_hat, - &BigInt::pow(&BigInt::from(2), crate::utilities::L as u32), - ); - let mu_lower = BigInt::from(-1).mul(&mu_upper); - let mu = BigInt::sample_range(&mu_lower, &mu_upper); - - // γ ← ± 2^{l+ε} · Nˆ - let gamma_upper = BigInt::mul( - &statement.N_hat, - &BigInt::pow(&BigInt::from(2), crate::utilities::L_PLUS_EPSILON as u32), - ); - let gamma_lower = BigInt::from(-1).mul(&gamma_upper); - let gamma = BigInt::sample_range(&gamma_lower, &gamma_upper); - // Sample r from Z*_{N_0} - let r = sample_relatively_prime_integer(&statement.N0.clone()); - - // S = s^x t^mu mod N_hat - let S = BigInt::mod_mul( - &mod_pow_with_negative(&statement.s, &witness.x, &statement.N_hat), - &mod_pow_with_negative(&statement.t, &mu, &statement.N_hat), - &statement.N_hat, - ); - - // A = (1+N_0)^{alpha}r^{N_0} mod N_0^2 - let A: BigInt = Paillier::encrypt_with_chosen_randomness( - &EncryptionKey { n: statement.N0.clone(), nn: statement.NN0.clone() }, - RawPlaintext::from(&alpha), - &Randomness::from(&r), - ) - .into(); - - // Y = g^alpha - let Y = &statement.g * Scalar::from_bigint(&alpha); - // D = s^alpha t^gamma mod N_hat - let D = BigInt::mod_mul( - &mod_pow_with_negative(&statement.s, &alpha, &statement.N_hat), - &mod_pow_with_negative(&statement.t, &gamma, &statement.N_hat), - &statement.N_hat, - ); - - let commitment = KnowledgeOfExponentPaillierEncryptionCommitment { - S: S.clone(), - A: A.clone(), - Y: Y.clone(), - D: D.clone(), - }; - - let e = H::new() - .chain_bigint(&S) - .chain_bigint(&A) - .chain_point(&Y) - .chain_bigint(&D) - .result_bigint(); - - // Step 5: Compute z_1, z_2, z_3 - // z_1 = alpha + ex - let z_1 = BigInt::add(&alpha, &BigInt::mul(&e, &witness.x)); - // z_2 = r * rho^e mod N_0 - let z_2 = BigInt::mod_mul( - &r, - &mod_pow_with_negative(&witness.rho, &e, &statement.N0), - &statement.N0, - ); - // z_3 = gamma + e*mu - let z_3 = BigInt::add(&gamma, &BigInt::mul(&e, &mu)); - - Self { z_1, z_2, z_3, commitment, phantom: PhantomData } - } - - pub fn verify( - proof: &KnowledgeOfExponentPaillierEncryptionProof, - statement: &KnowledgeOfExponentPaillierEncryptionStatement, - ) -> Result<(), IncorrectProof> { - let e = H::new() - .chain_bigint(&proof.commitment.S) - .chain_bigint(&proof.commitment.A) - .chain_point(&proof.commitment.Y) - .chain_bigint(&proof.commitment.D) - .result_bigint(); - - // left_1 = (1+N_0)^{z_1}z_2^{N_0} mod N_0^2 - let left_1: BigInt = Paillier::encrypt_with_chosen_randomness( - &EncryptionKey { n: statement.N0.clone(), nn: statement.NN0.clone() }, - RawPlaintext::from(&proof.z_1), - &Randomness::from(&proof.z_2), - ) - .into(); - - // right_1 = A * C^e - let right_1 = BigInt::mod_mul( - &proof.commitment.A, - &mod_pow_with_negative(&statement.C, &e, &statement.NN0), - &statement.NN0, - ); - - // left_2 = g^z_1 - let left_2 = &statement.g * Scalar::from_bigint(&proof.z_1); - // right_2 = Y * X^e - let right_2 = proof.commitment.Y.clone() + (statement.X.clone() * Scalar::from_bigint(&e)); - - // left_3 = s^z_1 t^z_3 mod N_hat - let left_3 = BigInt::mod_mul( - &mod_pow_with_negative(&statement.s, &proof.z_1, &statement.N_hat), - &mod_pow_with_negative(&statement.t, &proof.z_3, &statement.N_hat), - &statement.N_hat, - ); - - // right_3 = D * S^e mod N_hat - let right_3 = BigInt::mod_mul( - &proof.commitment.D, - &mod_pow_with_negative(&proof.commitment.S, &e, &statement.N_hat), - &statement.N_hat, - ); - - if left_1.mod_floor(&statement.NN0) != right_1 || left_2 != right_2 || left_3 != right_3 { - return Err(IncorrectProof) - } - - // Range Check -2^{L + eps} <= z_1 <= 2^{L+eps} - let lower_bound_check: bool = proof.z_1 >= - BigInt::from(-1) - .mul(&BigInt::pow(&BigInt::from(2), crate::utilities::L_PLUS_EPSILON as u32)); - - let upper_bound_check = - proof.z_1 <= BigInt::pow(&BigInt::from(2), crate::utilities::L_PLUS_EPSILON as u32); - - if !(lower_bound_check && upper_bound_check) { - return Err(IncorrectProof) - } - Ok(()) - } +impl + KnowledgeOfExponentPaillierEncryptionProof +{ + pub fn prove( + witness: &KnowledgeOfExponentPaillierEncryptionWitness, + statement: &KnowledgeOfExponentPaillierEncryptionStatement, + ) -> KnowledgeOfExponentPaillierEncryptionProof { + // Step 1: Sample alpha between -2^{l+ε} and 2^{l+ε} + let alpha_upper = BigInt::pow( + &BigInt::from(2), + crate::utilities::L_PLUS_EPSILON as u32, + ); + let alpha_lower = BigInt::from(-1).mul(&alpha_upper); + let alpha = BigInt::sample_range(&alpha_lower, &alpha_upper); + + // Step 2: mu, r, gamma + // Sample mu between -2^L * N_hat and 2^L * N_hat + let mu_upper = BigInt::mul( + &statement.N_hat, + &BigInt::pow(&BigInt::from(2), crate::utilities::L as u32), + ); + let mu_lower = BigInt::from(-1).mul(&mu_upper); + let mu = BigInt::sample_range(&mu_lower, &mu_upper); + + // γ ← ± 2^{l+ε} · Nˆ + let gamma_upper = BigInt::mul( + &statement.N_hat, + &BigInt::pow( + &BigInt::from(2), + crate::utilities::L_PLUS_EPSILON as u32, + ), + ); + let gamma_lower = BigInt::from(-1).mul(&gamma_upper); + let gamma = BigInt::sample_range(&gamma_lower, &gamma_upper); + // Sample r from Z*_{N_0} + let r = sample_relatively_prime_integer(&statement.N0.clone()); + + // S = s^x t^mu mod N_hat + let S = BigInt::mod_mul( + &mod_pow_with_negative(&statement.s, &witness.x, &statement.N_hat), + &mod_pow_with_negative(&statement.t, &mu, &statement.N_hat), + &statement.N_hat, + ); + + // A = (1+N_0)^{alpha}r^{N_0} mod N_0^2 + let A: BigInt = Paillier::encrypt_with_chosen_randomness( + &EncryptionKey { + n: statement.N0.clone(), + nn: statement.NN0.clone(), + }, + RawPlaintext::from(&alpha), + &Randomness::from(&r), + ) + .into(); + + // Y = g^alpha + let Y = &statement.g * Scalar::from_bigint(&alpha); + // D = s^alpha t^gamma mod N_hat + let D = BigInt::mod_mul( + &mod_pow_with_negative(&statement.s, &alpha, &statement.N_hat), + &mod_pow_with_negative(&statement.t, &gamma, &statement.N_hat), + &statement.N_hat, + ); + + let commitment = KnowledgeOfExponentPaillierEncryptionCommitment { + S: S.clone(), + A: A.clone(), + Y: Y.clone(), + D: D.clone(), + }; + + let e = H::new() + .chain_bigint(&S) + .chain_bigint(&A) + .chain_point(&Y) + .chain_bigint(&D) + .result_bigint(); + + // Step 5: Compute z_1, z_2, z_3 + // z_1 = alpha + ex + let z_1 = BigInt::add(&alpha, &BigInt::mul(&e, &witness.x)); + // z_2 = r * rho^e mod N_0 + let z_2 = BigInt::mod_mul( + &r, + &mod_pow_with_negative(&witness.rho, &e, &statement.N0), + &statement.N0, + ); + // z_3 = gamma + e*mu + let z_3 = BigInt::add(&gamma, &BigInt::mul(&e, &mu)); + + Self { + z_1, + z_2, + z_3, + commitment, + phantom: PhantomData, + } + } + + pub fn verify( + proof: &KnowledgeOfExponentPaillierEncryptionProof, + statement: &KnowledgeOfExponentPaillierEncryptionStatement, + ) -> Result<(), IncorrectProof> { + let e = H::new() + .chain_bigint(&proof.commitment.S) + .chain_bigint(&proof.commitment.A) + .chain_point(&proof.commitment.Y) + .chain_bigint(&proof.commitment.D) + .result_bigint(); + + // left_1 = (1+N_0)^{z_1}z_2^{N_0} mod N_0^2 + let left_1: BigInt = Paillier::encrypt_with_chosen_randomness( + &EncryptionKey { + n: statement.N0.clone(), + nn: statement.NN0.clone(), + }, + RawPlaintext::from(&proof.z_1), + &Randomness::from(&proof.z_2), + ) + .into(); + + // right_1 = A * C^e + let right_1 = BigInt::mod_mul( + &proof.commitment.A, + &mod_pow_with_negative(&statement.C, &e, &statement.NN0), + &statement.NN0, + ); + + // left_2 = g^z_1 + let left_2 = &statement.g * Scalar::from_bigint(&proof.z_1); + // right_2 = Y * X^e + let right_2 = proof.commitment.Y.clone() + + (statement.X.clone() * Scalar::from_bigint(&e)); + + // left_3 = s^z_1 t^z_3 mod N_hat + let left_3 = BigInt::mod_mul( + &mod_pow_with_negative(&statement.s, &proof.z_1, &statement.N_hat), + &mod_pow_with_negative(&statement.t, &proof.z_3, &statement.N_hat), + &statement.N_hat, + ); + + // right_3 = D * S^e mod N_hat + let right_3 = BigInt::mod_mul( + &proof.commitment.D, + &mod_pow_with_negative(&proof.commitment.S, &e, &statement.N_hat), + &statement.N_hat, + ); + + if left_1.mod_floor(&statement.NN0) != right_1 + || left_2 != right_2 + || left_3 != right_3 + { + return Err(IncorrectProof); + } + + // Range Check -2^{L + eps} <= z_1 <= 2^{L+eps} + let lower_bound_check: bool = proof.z_1 + >= BigInt::from(-1).mul(&BigInt::pow( + &BigInt::from(2), + crate::utilities::L_PLUS_EPSILON as u32, + )); + + let upper_bound_check = proof.z_1 + <= BigInt::pow( + &BigInt::from(2), + crate::utilities::L_PLUS_EPSILON as u32, + ); + + if !(lower_bound_check && upper_bound_check) { + return Err(IncorrectProof); + } + Ok(()) + } } #[cfg(test)] mod tests { - use super::*; - use crate::utilities::{mta::range_proofs::SampleFromMultiplicativeGroup, BITS_PAILLIER}; - use curv::elliptic::curves::secp256_k1::Secp256k1; - use fs_dkr::ring_pedersen_proof::RingPedersenStatement; - use paillier::{KeyGeneration, Paillier}; - use sha2::Sha256; - - #[test] - fn test_log_star_proof() { - let (ring_pedersen_statement, _witness) = - RingPedersenStatement::::generate(); - let (paillier_key, _) = Paillier::keypair_with_modulus_size(BITS_PAILLIER).keys(); - - let rho: BigInt = BigInt::from_paillier_key(&paillier_key); - let (statement, witness) = + use super::*; + use crate::utilities::{ + mta::range_proofs::SampleFromMultiplicativeGroup, BITS_PAILLIER, + }; + use curv::elliptic::curves::secp256_k1::Secp256k1; + use fs_dkr::ring_pedersen_proof::RingPedersenStatement; + use paillier::{KeyGeneration, Paillier}; + use sha2::Sha256; + + #[test] + fn test_log_star_proof() { + let (ring_pedersen_statement, _witness) = + RingPedersenStatement::::generate(); + let (paillier_key, _) = + Paillier::keypair_with_modulus_size(BITS_PAILLIER).keys(); + + let rho: BigInt = BigInt::from_paillier_key(&paillier_key); + let (statement, witness) = KnowledgeOfExponentPaillierEncryptionStatement::::generate( rho, Some(Point::::generator().to_point()), @@ -285,12 +355,13 @@ mod tests { ring_pedersen_statement.N, paillier_key, ); - let proof = KnowledgeOfExponentPaillierEncryptionProof::::prove( - &witness, &statement, - ); - assert!(KnowledgeOfExponentPaillierEncryptionProof::::verify( + let proof = KnowledgeOfExponentPaillierEncryptionProof::< + Secp256k1, + Sha256, + >::prove(&witness, &statement); + assert!(KnowledgeOfExponentPaillierEncryptionProof::::verify( &proof, &statement ) .is_ok()); - } + } } diff --git a/src/utilities/mod.rs b/src/utilities/mod.rs index 2bf95595..f5627013 100644 --- a/src/utilities/mod.rs +++ b/src/utilities/mod.rs @@ -1,6 +1,6 @@ use curv::{ - arithmetic::{BasicOps, Integer, Modulo, NumberTests, Samplable, Zero}, - BigInt, + arithmetic::{BasicOps, Integer, Modulo, NumberTests, Samplable, Zero}, + BigInt, }; pub mod aff_g; @@ -15,20 +15,24 @@ pub mod zk_pdl; pub mod zk_pdl_with_slack; pub fn sample_relatively_prime_integer(n: &BigInt) -> BigInt { - let mut sample = BigInt::sample_below(n); - while BigInt::gcd(&sample, n) != BigInt::from(1) { - sample = BigInt::sample_below(n); - } - sample + let mut sample = BigInt::sample_below(n); + while BigInt::gcd(&sample, n) != BigInt::from(1) { + sample = BigInt::sample_below(n); + } + sample } -pub fn mod_pow_with_negative(v: &BigInt, pow: &BigInt, modulus: &BigInt) -> BigInt { - if BigInt::is_negative(pow) { - let temp = BigInt::mod_pow(v, &pow.abs(), modulus); - BigInt::mod_inv(&temp, modulus).unwrap_or_else(BigInt::zero) - } else { - BigInt::mod_pow(v, pow, modulus) - } +pub fn mod_pow_with_negative( + v: &BigInt, + pow: &BigInt, + modulus: &BigInt, +) -> BigInt { + if BigInt::is_negative(pow) { + let temp = BigInt::mod_pow(v, &pow.abs(), modulus); + BigInt::mod_inv(&temp, modulus).unwrap_or_else(BigInt::zero) + } else { + BigInt::mod_pow(v, pow, modulus) + } } pub const SEC_PARAM: usize = 256; @@ -37,13 +41,14 @@ pub const OT_PARAM: usize = 128; pub const OT_BYTES: usize = OT_PARAM / 8; pub const STAT_PARAM: usize = 80; -// ZK_MOD_ITERATIONS is the number of iterations that are performed to prove the validity of -// a Paillier-Blum modulus N. -// Theoretically, the number of iterations corresponds to the statistical security parameter, -// and would be 80. -// The way it is used in the refresh protocol ensures that the prover cannot guess in advance the -// secret ρ used to instantiate the hash function. -// Since sampling primes is expensive, we argue that the security can be reduced. +// ZK_MOD_ITERATIONS is the number of iterations that are performed to prove the +// validity of a Paillier-Blum modulus N. +// Theoretically, the number of iterations corresponds to the statistical +// security parameter, and would be 80. +// The way it is used in the refresh protocol ensures that the prover cannot +// guess in advance the secret ρ used to instantiate the hash function. +// Since sampling primes is expensive, we argue that the security can be +// reduced. pub const ZK_MOD_ITERATIONS: usize = 12; #[allow(clippy::identity_op)] diff --git a/src/utilities/mta/mod.rs b/src/utilities/mta/mod.rs index 787992fa..bd24ada8 100644 --- a/src/utilities/mta/mod.rs +++ b/src/utilities/mta/mod.rs @@ -1,29 +1,29 @@ /* - Multi-party ECDSA + Multi-party ECDSA - Copyright 2018 by Kzen Networks + Copyright 2018 by Kzen Networks - This file is part of Multi-party ECDSA library - (https://github.com/KZen-networks/multi-party-ecdsa) + This file is part of Multi-party ECDSA library + (https://github.com/KZen-networks/multi-party-ecdsa) - Multi-party ECDSA is free software: you can redistribute - it and/or modify it under the terms of the GNU General Public - License as published by the Free Software Foundation, either - version 3 of the License, or (at your option) any later version. + Multi-party ECDSA is free software: you can redistribute + it and/or modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, either + version 3 of the License, or (at your option) any later version. - @license GPL-3.0+ + @license GPL-3.0+ */ /// MtA is described in https://eprint.iacr.org/2019/114.pdf section 3 use curv::arithmetic::traits::Samplable; use curv::{ - cryptographic_primitives::proofs::sigma_dlog::DLogProof, - elliptic::curves::{secp256_k1::Secp256k1, Point, Scalar}, - BigInt, + cryptographic_primitives::proofs::sigma_dlog::DLogProof, + elliptic::curves::{secp256_k1::Secp256k1, Point, Scalar}, + BigInt, }; use paillier::{ - traits::EncryptWithChosenRandomness, Add, Decrypt, DecryptionKey, EncryptionKey, Mul, Paillier, - Randomness, RawCiphertext, RawPlaintext, + traits::EncryptWithChosenRandomness, Add, Decrypt, DecryptionKey, + EncryptionKey, Mul, Paillier, Randomness, RawCiphertext, RawPlaintext, }; use zk_paillier::zkproofs::DLogStatement; @@ -31,9 +31,9 @@ use serde::{Deserialize, Serialize}; use sha2::Sha256; use crate::{ - party_i::PartyPrivate, - utilities::mta::range_proofs::AliceProof, - Error::{self, InvalidKey}, + party_i::PartyPrivate, + utilities::mta::range_proofs::AliceProof, + Error::{self, InvalidKey}, }; pub mod range_proofs; @@ -42,172 +42,196 @@ mod test; #[derive(Clone, Debug, Serialize, Deserialize)] pub struct MessageA { - pub c: BigInt, // paillier encryption - pub range_proofs: Vec, /* proofs (using other parties' h1,h2,N_tilde) that the - * plaintext is small */ + pub c: BigInt, // paillier encryption + pub range_proofs: Vec, /* proofs (using other parties' + * h1,h2,N_tilde) that the + * plaintext is small */ } #[derive(Clone, Debug, Serialize, Deserialize)] pub struct MessageB { - pub c: BigInt, // paillier encryption - pub b_proof: DLogProof, - pub beta_tag_proof: DLogProof, + pub c: BigInt, // paillier encryption + pub b_proof: DLogProof, + pub beta_tag_proof: DLogProof, } impl MessageA { - /// Creates a new `messageA` using Alice's Paillier encryption key and `dlog_statements` - /// - other parties' `h1,h2,N_tilde`s for range proofs. - /// If range proofs are not needed (one example is identification of aborts where we - /// only want to reconstruct a ciphertext), `dlog_statements` can be an empty slice. - pub fn a( - a: &Scalar, - alice_ek: &EncryptionKey, - dlog_statements: &[DLogStatement], - ) -> (Self, BigInt) { - let randomness = BigInt::sample_below(&alice_ek.n); - let m_a = MessageA::a_with_predefined_randomness(a, alice_ek, &randomness, dlog_statements); - (m_a, randomness) - } - - pub fn a_with_predefined_randomness( - a: &Scalar, - alice_ek: &EncryptionKey, - randomness: &BigInt, - dlog_statements: &[DLogStatement], - ) -> Self { - let c_a = Paillier::encrypt_with_chosen_randomness( - alice_ek, - RawPlaintext::from(a.to_bigint()), - &Randomness::from(randomness.clone()), - ) - .0 - .clone() - .into_owned(); - let alice_range_proofs = dlog_statements - .iter() - .map(|dlog_statement| { - AliceProof::generate(&a.to_bigint(), &c_a, alice_ek, dlog_statement, randomness) - }) - .collect::>(); - - Self { c: c_a, range_proofs: alice_range_proofs } - } + /// Creates a new `messageA` using Alice's Paillier encryption key and + /// `dlog_statements` + /// - other parties' `h1,h2,N_tilde`s for range proofs. + /// If range proofs are not needed (one example is identification of aborts + /// where we only want to reconstruct a ciphertext), `dlog_statements` + /// can be an empty slice. + pub fn a( + a: &Scalar, + alice_ek: &EncryptionKey, + dlog_statements: &[DLogStatement], + ) -> (Self, BigInt) { + let randomness = BigInt::sample_below(&alice_ek.n); + let m_a = MessageA::a_with_predefined_randomness( + a, + alice_ek, + &randomness, + dlog_statements, + ); + (m_a, randomness) + } + + pub fn a_with_predefined_randomness( + a: &Scalar, + alice_ek: &EncryptionKey, + randomness: &BigInt, + dlog_statements: &[DLogStatement], + ) -> Self { + let c_a = Paillier::encrypt_with_chosen_randomness( + alice_ek, + RawPlaintext::from(a.to_bigint()), + &Randomness::from(randomness.clone()), + ) + .0 + .clone() + .into_owned(); + let alice_range_proofs = dlog_statements + .iter() + .map(|dlog_statement| { + AliceProof::generate( + &a.to_bigint(), + &c_a, + alice_ek, + dlog_statement, + randomness, + ) + }) + .collect::>(); + + Self { + c: c_a, + range_proofs: alice_range_proofs, + } + } } impl MessageB { - pub fn b( - b: &Scalar, - alice_ek: &EncryptionKey, - m_a: MessageA, - dlog_statements: &[DLogStatement], - ) -> Result<(Self, Scalar, BigInt, BigInt), Error> { - let beta_tag = BigInt::sample_below(&alice_ek.n); - let randomness = BigInt::sample_below(&alice_ek.n); - let (m_b, beta) = MessageB::b_with_predefined_randomness( - b, - alice_ek, - m_a, - &randomness, - &beta_tag, - dlog_statements, - )?; - - Ok((m_b, beta, randomness, beta_tag)) - } - - pub fn b_with_predefined_randomness( - b: &Scalar, - alice_ek: &EncryptionKey, - m_a: MessageA, - randomness: &BigInt, - beta_tag: &BigInt, - dlog_statements: &[DLogStatement], - ) -> Result<(Self, Scalar), Error> { - if m_a.range_proofs.len() != dlog_statements.len() { - return Err(InvalidKey) - } - // verify proofs - if !m_a - .range_proofs - .iter() - .zip(dlog_statements) - .map(|(proof, dlog_statement)| proof.verify(&m_a.c, alice_ek, dlog_statement)) - .all(|x| x) - { - return Err(InvalidKey) - }; - let beta_tag_fe = Scalar::::from(beta_tag); - let c_beta_tag = Paillier::encrypt_with_chosen_randomness( - alice_ek, - RawPlaintext::from(beta_tag), - &Randomness::from(randomness.clone()), - ); - - let b_bn = b.to_bigint(); - let b_c_a = Paillier::mul(alice_ek, RawCiphertext::from(m_a.c), RawPlaintext::from(b_bn)); - let c_b = Paillier::add(alice_ek, b_c_a, c_beta_tag); - let beta = Scalar::::zero() - &beta_tag_fe; - let dlog_proof_b = DLogProof::prove(b); - let dlog_proof_beta_tag = DLogProof::prove(&beta_tag_fe); - - Ok(( - Self { - c: c_b.0.clone().into_owned(), - b_proof: dlog_proof_b, - beta_tag_proof: dlog_proof_beta_tag, - }, - beta, - )) - } - - pub fn verify_proofs_get_alpha( - &self, - dk: &DecryptionKey, - a: &Scalar, - ) -> Result<(Scalar, BigInt), Error> { - let alice_share = Paillier::decrypt(dk, &RawCiphertext::from(self.c.clone())); - let g = Point::generator(); - let alpha = Scalar::::from(alice_share.0.as_ref()); - let g_alpha = g * α - let ba_btag = &self.b_proof.pk * a + &self.beta_tag_proof.pk; - if DLogProof::verify(&self.b_proof).is_ok() + pub fn b( + b: &Scalar, + alice_ek: &EncryptionKey, + m_a: MessageA, + dlog_statements: &[DLogStatement], + ) -> Result<(Self, Scalar, BigInt, BigInt), Error> { + let beta_tag = BigInt::sample_below(&alice_ek.n); + let randomness = BigInt::sample_below(&alice_ek.n); + let (m_b, beta) = MessageB::b_with_predefined_randomness( + b, + alice_ek, + m_a, + &randomness, + &beta_tag, + dlog_statements, + )?; + + Ok((m_b, beta, randomness, beta_tag)) + } + + pub fn b_with_predefined_randomness( + b: &Scalar, + alice_ek: &EncryptionKey, + m_a: MessageA, + randomness: &BigInt, + beta_tag: &BigInt, + dlog_statements: &[DLogStatement], + ) -> Result<(Self, Scalar), Error> { + if m_a.range_proofs.len() != dlog_statements.len() { + return Err(InvalidKey); + } + // verify proofs + if !m_a + .range_proofs + .iter() + .zip(dlog_statements) + .map(|(proof, dlog_statement)| { + proof.verify(&m_a.c, alice_ek, dlog_statement) + }) + .all(|x| x) + { + return Err(InvalidKey); + }; + let beta_tag_fe = Scalar::::from(beta_tag); + let c_beta_tag = Paillier::encrypt_with_chosen_randomness( + alice_ek, + RawPlaintext::from(beta_tag), + &Randomness::from(randomness.clone()), + ); + + let b_bn = b.to_bigint(); + let b_c_a = Paillier::mul( + alice_ek, + RawCiphertext::from(m_a.c), + RawPlaintext::from(b_bn), + ); + let c_b = Paillier::add(alice_ek, b_c_a, c_beta_tag); + let beta = Scalar::::zero() - &beta_tag_fe; + let dlog_proof_b = DLogProof::prove(b); + let dlog_proof_beta_tag = DLogProof::prove(&beta_tag_fe); + + Ok(( + Self { + c: c_b.0.clone().into_owned(), + b_proof: dlog_proof_b, + beta_tag_proof: dlog_proof_beta_tag, + }, + beta, + )) + } + + pub fn verify_proofs_get_alpha( + &self, + dk: &DecryptionKey, + a: &Scalar, + ) -> Result<(Scalar, BigInt), Error> { + let alice_share = + Paillier::decrypt(dk, &RawCiphertext::from(self.c.clone())); + let g = Point::generator(); + let alpha = Scalar::::from(alice_share.0.as_ref()); + let g_alpha = g * α + let ba_btag = &self.b_proof.pk * a + &self.beta_tag_proof.pk; + if DLogProof::verify(&self.b_proof).is_ok() && DLogProof::verify(&self.beta_tag_proof).is_ok() // we prove the correctness of the ciphertext using this check and the proof of knowledge of dlog of beta_tag && ba_btag == g_alpha - { - Ok((alpha, alice_share.0.into_owned())) - } else { - Err(InvalidKey) - } - } - - // another version, supporting PartyPrivate therefore binding mta to gg18. - // with the regular version mta can be used in general - pub fn verify_proofs_get_alpha_gg18( - &self, - private: &PartyPrivate, - a: &Scalar, - ) -> Result, Error> { - let alice_share = private.decrypt(self.c.clone()); - let g = Point::generator(); - let alpha = Scalar::::from(alice_share.0.as_ref()); - let g_alpha = g * α - let ba_btag = &self.b_proof.pk * a + &self.beta_tag_proof.pk; - - if DLogProof::verify(&self.b_proof).is_ok() && - DLogProof::verify(&self.beta_tag_proof).is_ok() && - ba_btag == g_alpha - { - Ok(alpha) - } else { - Err(InvalidKey) - } - } - - pub fn verify_b_against_public( - public_gb: &Point, - mta_gb: &Point, - ) -> bool { - public_gb == mta_gb - } + { + Ok((alpha, alice_share.0.into_owned())) + } else { + Err(InvalidKey) + } + } + + // another version, supporting PartyPrivate therefore binding mta to gg18. + // with the regular version mta can be used in general + pub fn verify_proofs_get_alpha_gg18( + &self, + private: &PartyPrivate, + a: &Scalar, + ) -> Result, Error> { + let alice_share = private.decrypt(self.c.clone()); + let g = Point::generator(); + let alpha = Scalar::::from(alice_share.0.as_ref()); + let g_alpha = g * α + let ba_btag = &self.b_proof.pk * a + &self.beta_tag_proof.pk; + + if DLogProof::verify(&self.b_proof).is_ok() + && DLogProof::verify(&self.beta_tag_proof).is_ok() + && ba_btag == g_alpha + { + Ok(alpha) + } else { + Err(InvalidKey) + } + } + + pub fn verify_b_against_public( + public_gb: &Point, + mta_gb: &Point, + ) -> bool { + public_gb == mta_gb + } } diff --git a/src/utilities/mta/range_proofs.rs b/src/utilities/mta/range_proofs.rs index fb894258..97c417b5 100644 --- a/src/utilities/mta/range_proofs.rs +++ b/src/utilities/mta/range_proofs.rs @@ -6,14 +6,16 @@ //! Zero knowledge range proofs for MtA protocol are implemented here. //! Formal description can be found in Appendix A of https://eprint.iacr.org/2019/114.pdf //! There are some deviations from the original specification: -//! 1) In Bob's proofs `gamma` is sampled from `[0;q^2 * N]` and `tau` from `[0;q^3 * N_tilde]`. -//! 2) A non-interactive version is implemented, with challenge `e` computed via Fiat-Shamir. +//! 1) In Bob's proofs `gamma` is sampled from `[0;q^2 * N]` and `tau` from +//! `[0;q^3 * N_tilde]`. +//! 2) A non-interactive version is implemented, with challenge `e` computed via +//! Fiat-Shamir. use curv::{ - arithmetic::traits::*, - cryptographic_primitives::hashing::{Digest, DigestExt}, - elliptic::curves::{secp256_k1::Secp256k1, Point, Scalar}, - BigInt, + arithmetic::traits::*, + cryptographic_primitives::hashing::{Digest, DigestExt}, + elliptic::curves::{secp256_k1::Secp256k1, Point, Scalar}, + BigInt, }; use sha2::Sha256; @@ -28,633 +30,716 @@ use zeroize::Zeroize; #[derive(Zeroize)] #[zeroize(drop)] struct AliceZkpRound1 { - alpha: BigInt, - beta: BigInt, - gamma: BigInt, - ro: BigInt, - z: BigInt, - u: BigInt, - w: BigInt, + alpha: BigInt, + beta: BigInt, + gamma: BigInt, + ro: BigInt, + z: BigInt, + u: BigInt, + w: BigInt, } impl AliceZkpRound1 { - fn from( - alice_ek: &EncryptionKey, - dlog_statement: &DLogStatement, - a: &BigInt, - q: &BigInt, - ) -> Self { - let h1 = &dlog_statement.g; - let h2 = &dlog_statement.ni; - let N_tilde = &dlog_statement.N; - let alpha = BigInt::sample_below(&q.pow(3)); - let beta = BigInt::from_paillier_key(alice_ek); - let gamma = BigInt::sample_below(&(q.pow(3) * N_tilde)); - let ro = BigInt::sample_below(&(q * N_tilde)); - let z = (BigInt::mod_pow(h1, a, N_tilde) * BigInt::mod_pow(h2, &ro, N_tilde)) % N_tilde; - let u = ((alpha.borrow() * &alice_ek.n + 1) * - BigInt::mod_pow(&beta, &alice_ek.n, &alice_ek.nn)) % - &alice_ek.nn; - let w = - (BigInt::mod_pow(h1, &alpha, N_tilde) * BigInt::mod_pow(h2, &gamma, N_tilde)) % N_tilde; - Self { alpha, beta, gamma, ro, z, u, w } - } + fn from( + alice_ek: &EncryptionKey, + dlog_statement: &DLogStatement, + a: &BigInt, + q: &BigInt, + ) -> Self { + let h1 = &dlog_statement.g; + let h2 = &dlog_statement.ni; + let N_tilde = &dlog_statement.N; + let alpha = BigInt::sample_below(&q.pow(3)); + let beta = BigInt::from_paillier_key(alice_ek); + let gamma = BigInt::sample_below(&(q.pow(3) * N_tilde)); + let ro = BigInt::sample_below(&(q * N_tilde)); + let z = (BigInt::mod_pow(h1, a, N_tilde) + * BigInt::mod_pow(h2, &ro, N_tilde)) + % N_tilde; + let u = ((alpha.borrow() * &alice_ek.n + 1) + * BigInt::mod_pow(&beta, &alice_ek.n, &alice_ek.nn)) + % &alice_ek.nn; + let w = (BigInt::mod_pow(h1, &alpha, N_tilde) + * BigInt::mod_pow(h2, &gamma, N_tilde)) + % N_tilde; + Self { + alpha, + beta, + gamma, + ro, + z, + u, + w, + } + } } /// Represents the second round of the interactive version of the proof struct AliceZkpRound2 { - s: BigInt, - s1: BigInt, - s2: BigInt, + s: BigInt, + s1: BigInt, + s2: BigInt, } impl AliceZkpRound2 { - fn from( - alice_ek: &EncryptionKey, - round1: &AliceZkpRound1, - e: &BigInt, - a: &BigInt, - r: &BigInt, - ) -> Self { - Self { - s: (BigInt::mod_pow(r, e, &alice_ek.n) * round1.beta.borrow()) % &alice_ek.n, - s1: (e * a) + round1.alpha.borrow(), - s2: (e * round1.ro.borrow()) + round1.gamma.borrow(), - } - } + fn from( + alice_ek: &EncryptionKey, + round1: &AliceZkpRound1, + e: &BigInt, + a: &BigInt, + r: &BigInt, + ) -> Self { + Self { + s: (BigInt::mod_pow(r, e, &alice_ek.n) * round1.beta.borrow()) + % &alice_ek.n, + s1: (e * a) + round1.alpha.borrow(), + s2: (e * round1.ro.borrow()) + round1.gamma.borrow(), + } + } } /// Alice's proof #[derive(Debug, Clone, Serialize, Deserialize)] pub struct AliceProof { - z: BigInt, - e: BigInt, - s: BigInt, - s1: BigInt, - s2: BigInt, + z: BigInt, + e: BigInt, + s: BigInt, + s1: BigInt, + s2: BigInt, } impl AliceProof { - /// verify Alice's proof using the proof and public keys - pub fn verify( - &self, - cipher: &BigInt, - alice_ek: &EncryptionKey, - dlog_statement: &DLogStatement, - ) -> bool { - let N = &alice_ek.n; - let NN = &alice_ek.nn; - let N_tilde = &dlog_statement.N; - let h1 = &dlog_statement.g; - let h2 = &dlog_statement.ni; - let Gen = alice_ek.n.borrow() + 1; - - if self.s1 > Scalar::::group_order().pow(3) { - return false - } - - let z_e_inv = BigInt::mod_inv(&BigInt::mod_pow(&self.z, &self.e, N_tilde), N_tilde); - let z_e_inv = match z_e_inv { - // z must be invertible, yet the check is done here - None => return false, - Some(c) => c, - }; - - let w = (BigInt::mod_pow(h1, &self.s1, N_tilde) * - BigInt::mod_pow(h2, &self.s2, N_tilde) * - z_e_inv) % N_tilde; - - let gs1 = (self.s1.borrow() * N + 1) % NN; - let cipher_e_inv = BigInt::mod_inv(&BigInt::mod_pow(cipher, &self.e, NN), NN); - let cipher_e_inv = match cipher_e_inv { - None => return false, - Some(c) => c, - }; - - let u = (gs1 * BigInt::mod_pow(&self.s, N, NN) * cipher_e_inv) % NN; - - let e = Sha256::new() - .chain_bigint(N) - .chain_bigint(&Gen) - .chain_bigint(cipher) - .chain_bigint(&self.z) - .chain_bigint(&u) - .chain_bigint(&w) - .result_bigint(); - if e != self.e { - return false - } - - true - } - /// Create the proof using Alice's Paillier private keys and public ZKP setup. - /// Requires randomness used for encrypting Alice's secret a. - /// It is assumed that secp256k1 curve is used. - pub fn generate( - a: &BigInt, - cipher: &BigInt, - alice_ek: &EncryptionKey, - dlog_statement: &DLogStatement, - r: &BigInt, - ) -> Self { - let round1 = - AliceZkpRound1::from(alice_ek, dlog_statement, a, Scalar::::group_order()); - - let Gen = alice_ek.n.borrow() + 1; - let e = Sha256::new() - .chain_bigint(&alice_ek.n) - .chain_bigint(&Gen) - .chain_bigint(cipher) - .chain_bigint(&round1.z) - .chain_bigint(&round1.u) - .chain_bigint(&round1.w) - .result_bigint(); - - let round2 = AliceZkpRound2::from(alice_ek, &round1, &e, a, r); - - Self { z: round1.z.clone(), e, s: round2.s, s1: round2.s1, s2: round2.s2 } - } + /// verify Alice's proof using the proof and public keys + pub fn verify( + &self, + cipher: &BigInt, + alice_ek: &EncryptionKey, + dlog_statement: &DLogStatement, + ) -> bool { + let N = &alice_ek.n; + let NN = &alice_ek.nn; + let N_tilde = &dlog_statement.N; + let h1 = &dlog_statement.g; + let h2 = &dlog_statement.ni; + let Gen = alice_ek.n.borrow() + 1; + + if self.s1 > Scalar::::group_order().pow(3) { + return false; + } + + let z_e_inv = BigInt::mod_inv( + &BigInt::mod_pow(&self.z, &self.e, N_tilde), + N_tilde, + ); + let z_e_inv = match z_e_inv { + // z must be invertible, yet the check is done here + None => return false, + Some(c) => c, + }; + + let w = (BigInt::mod_pow(h1, &self.s1, N_tilde) + * BigInt::mod_pow(h2, &self.s2, N_tilde) + * z_e_inv) + % N_tilde; + + let gs1 = (self.s1.borrow() * N + 1) % NN; + let cipher_e_inv = + BigInt::mod_inv(&BigInt::mod_pow(cipher, &self.e, NN), NN); + let cipher_e_inv = match cipher_e_inv { + None => return false, + Some(c) => c, + }; + + let u = (gs1 * BigInt::mod_pow(&self.s, N, NN) * cipher_e_inv) % NN; + + let e = Sha256::new() + .chain_bigint(N) + .chain_bigint(&Gen) + .chain_bigint(cipher) + .chain_bigint(&self.z) + .chain_bigint(&u) + .chain_bigint(&w) + .result_bigint(); + if e != self.e { + return false; + } + + true + } + /// Create the proof using Alice's Paillier private keys and public ZKP + /// setup. Requires randomness used for encrypting Alice's secret a. + /// It is assumed that secp256k1 curve is used. + pub fn generate( + a: &BigInt, + cipher: &BigInt, + alice_ek: &EncryptionKey, + dlog_statement: &DLogStatement, + r: &BigInt, + ) -> Self { + let round1 = AliceZkpRound1::from( + alice_ek, + dlog_statement, + a, + Scalar::::group_order(), + ); + + let Gen = alice_ek.n.borrow() + 1; + let e = Sha256::new() + .chain_bigint(&alice_ek.n) + .chain_bigint(&Gen) + .chain_bigint(cipher) + .chain_bigint(&round1.z) + .chain_bigint(&round1.u) + .chain_bigint(&round1.w) + .result_bigint(); + + let round2 = AliceZkpRound2::from(alice_ek, &round1, &e, a, r); + + Self { + z: round1.z.clone(), + e, + s: round2.s, + s1: round2.s1, + s2: round2.s2, + } + } } /// Represents first round of the interactive version of the proof #[derive(Zeroize)] #[zeroize(drop)] struct BobZkpRound1 { - pub alpha: BigInt, - pub beta: BigInt, - pub gamma: BigInt, - pub ro: BigInt, - pub ro_prim: BigInt, - pub sigma: BigInt, - pub tau: BigInt, - pub z: BigInt, - pub z_prim: BigInt, - pub t: BigInt, - pub w: BigInt, - pub v: BigInt, + pub alpha: BigInt, + pub beta: BigInt, + pub gamma: BigInt, + pub ro: BigInt, + pub ro_prim: BigInt, + pub sigma: BigInt, + pub tau: BigInt, + pub z: BigInt, + pub z_prim: BigInt, + pub t: BigInt, + pub w: BigInt, + pub v: BigInt, } impl BobZkpRound1 { - /// `b` - Bob's secret - /// `beta_prim` - randomly chosen in `MtA` by Bob - /// `a_encrypted` - Alice's secret encrypted by Alice - fn from( - alice_ek: &EncryptionKey, - dlog_statement: &DLogStatement, - b: &Scalar, - beta_prim: &BigInt, - a_encrypted: &BigInt, - q: &BigInt, - ) -> Self { - let h1 = &dlog_statement.g; - let h2 = &dlog_statement.ni; - let N_tilde = &dlog_statement.N; - let b_bn = b.to_bigint(); - - let alpha = BigInt::sample_below(&q.pow(3)); - let beta = BigInt::from_paillier_key(alice_ek); - let gamma = BigInt::sample_below(&(q.pow(2) * &alice_ek.n)); - let ro = BigInt::sample_below(&(q * N_tilde)); - let ro_prim = BigInt::sample_below(&(q.pow(3) * N_tilde)); - let sigma = BigInt::sample_below(&(q * N_tilde)); - let tau = BigInt::sample_below(&(q.pow(3) * N_tilde)); - let z = (BigInt::mod_pow(h1, &b_bn, N_tilde) * BigInt::mod_pow(h2, &ro, N_tilde)) % N_tilde; - let z_prim = (BigInt::mod_pow(h1, &alpha, N_tilde) * - BigInt::mod_pow(h2, &ro_prim, N_tilde)) % - N_tilde; - let t = (BigInt::mod_pow(h1, beta_prim, N_tilde) * BigInt::mod_pow(h2, &sigma, N_tilde)) % - N_tilde; - let w = - (BigInt::mod_pow(h1, &gamma, N_tilde) * BigInt::mod_pow(h2, &tau, N_tilde)) % N_tilde; - let v = (BigInt::mod_pow(a_encrypted, &alpha, &alice_ek.nn) * - (gamma.borrow() * &alice_ek.n + 1) * - BigInt::mod_pow(&beta, &alice_ek.n, &alice_ek.nn)) % - &alice_ek.nn; - Self { alpha, beta, gamma, ro, ro_prim, sigma, tau, z, z_prim, t, w, v } - } + /// `b` - Bob's secret + /// `beta_prim` - randomly chosen in `MtA` by Bob + /// `a_encrypted` - Alice's secret encrypted by Alice + fn from( + alice_ek: &EncryptionKey, + dlog_statement: &DLogStatement, + b: &Scalar, + beta_prim: &BigInt, + a_encrypted: &BigInt, + q: &BigInt, + ) -> Self { + let h1 = &dlog_statement.g; + let h2 = &dlog_statement.ni; + let N_tilde = &dlog_statement.N; + let b_bn = b.to_bigint(); + + let alpha = BigInt::sample_below(&q.pow(3)); + let beta = BigInt::from_paillier_key(alice_ek); + let gamma = BigInt::sample_below(&(q.pow(2) * &alice_ek.n)); + let ro = BigInt::sample_below(&(q * N_tilde)); + let ro_prim = BigInt::sample_below(&(q.pow(3) * N_tilde)); + let sigma = BigInt::sample_below(&(q * N_tilde)); + let tau = BigInt::sample_below(&(q.pow(3) * N_tilde)); + let z = (BigInt::mod_pow(h1, &b_bn, N_tilde) + * BigInt::mod_pow(h2, &ro, N_tilde)) + % N_tilde; + let z_prim = (BigInt::mod_pow(h1, &alpha, N_tilde) + * BigInt::mod_pow(h2, &ro_prim, N_tilde)) + % N_tilde; + let t = (BigInt::mod_pow(h1, beta_prim, N_tilde) + * BigInt::mod_pow(h2, &sigma, N_tilde)) + % N_tilde; + let w = (BigInt::mod_pow(h1, &gamma, N_tilde) + * BigInt::mod_pow(h2, &tau, N_tilde)) + % N_tilde; + let v = (BigInt::mod_pow(a_encrypted, &alpha, &alice_ek.nn) + * (gamma.borrow() * &alice_ek.n + 1) + * BigInt::mod_pow(&beta, &alice_ek.n, &alice_ek.nn)) + % &alice_ek.nn; + Self { + alpha, + beta, + gamma, + ro, + ro_prim, + sigma, + tau, + z, + z_prim, + t, + w, + v, + } + } } /// represents second round of the interactive version of the proof struct BobZkpRound2 { - pub s: BigInt, - pub s1: BigInt, - pub s2: BigInt, - pub t1: BigInt, - pub t2: BigInt, + pub s: BigInt, + pub s1: BigInt, + pub s2: BigInt, + pub t1: BigInt, + pub t2: BigInt, } impl BobZkpRound2 { - /// `e` - the challenge in interactive ZKP, the hash in non-interactive ZKP - /// `b` - Bob's secret - /// `beta_prim` - randomly chosen in `MtA` by Bob - /// `r` - randomness used by Bob on Alice's public Paillier key to encrypt `beta_prim` in `MtA` - fn from( - alice_ek: &EncryptionKey, - round1: &BobZkpRound1, - e: &BigInt, - b: &Scalar, - beta_prim: &BigInt, - r: &Randomness, - ) -> Self { - let b_bn = b.to_bigint(); - Self { - s: (BigInt::mod_pow(r.0.borrow(), e, &alice_ek.n) * round1.beta.borrow()) % &alice_ek.n, - s1: (e * b_bn) + round1.alpha.borrow(), - s2: (e * round1.ro.borrow()) + round1.ro_prim.borrow(), - t1: (e * beta_prim) + round1.gamma.borrow(), - t2: (e * round1.sigma.borrow()) + round1.tau.borrow(), - } - } + /// `e` - the challenge in interactive ZKP, the hash in non-interactive ZKP + /// `b` - Bob's secret + /// `beta_prim` - randomly chosen in `MtA` by Bob + /// `r` - randomness used by Bob on Alice's public Paillier key to encrypt + /// `beta_prim` in `MtA` + fn from( + alice_ek: &EncryptionKey, + round1: &BobZkpRound1, + e: &BigInt, + b: &Scalar, + beta_prim: &BigInt, + r: &Randomness, + ) -> Self { + let b_bn = b.to_bigint(); + Self { + s: (BigInt::mod_pow(r.0.borrow(), e, &alice_ek.n) + * round1.beta.borrow()) + % &alice_ek.n, + s1: (e * b_bn) + round1.alpha.borrow(), + s2: (e * round1.ro.borrow()) + round1.ro_prim.borrow(), + t1: (e * beta_prim) + round1.gamma.borrow(), + t2: (e * round1.sigma.borrow()) + round1.tau.borrow(), + } + } } /// Additional fields in Bob's proof if MtA is run with check pub struct BobCheck { - u: Point, - X: Point, + u: Point, + X: Point, } /// Bob's regular proof #[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] pub struct BobProof { - t: BigInt, - z: BigInt, - e: BigInt, - s: BigInt, - s1: BigInt, - s2: BigInt, - t1: BigInt, - t2: BigInt, + t: BigInt, + z: BigInt, + e: BigInt, + s: BigInt, + s1: BigInt, + s2: BigInt, + t1: BigInt, + t2: BigInt, } #[allow(clippy::too_many_arguments)] impl BobProof { - pub fn verify( - &self, - a_enc: &BigInt, - mta_avc_out: &BigInt, - alice_ek: &EncryptionKey, - dlog_statement: &DLogStatement, - check: Option<&BobCheck>, - ) -> bool { - let N = &alice_ek.n; - let NN = &alice_ek.nn; - let N_tilde = &dlog_statement.N; - let h1 = &dlog_statement.g; - let h2 = &dlog_statement.ni; - - if self.s1 > Scalar::::group_order().pow(3) { - return false - } - - let z_e_inv = BigInt::mod_inv(&BigInt::mod_pow(&self.z, &self.e, N_tilde), N_tilde); - let z_e_inv = match z_e_inv { - // z must be invertible, yet the check is done here - None => return false, - Some(c) => c, - }; - - let z_prim = (BigInt::mod_pow(h1, &self.s1, N_tilde) * - BigInt::mod_pow(h2, &self.s2, N_tilde) * - z_e_inv) % N_tilde; - - let mta_e_inv = BigInt::mod_inv(&BigInt::mod_pow(mta_avc_out, &self.e, NN), NN); - let mta_e_inv = match mta_e_inv { - None => return false, - Some(c) => c, - }; - - let v = (BigInt::mod_pow(a_enc, &self.s1, NN) * - BigInt::mod_pow(&self.s, N, NN) * - (self.t1.borrow() * N + 1) * - mta_e_inv) % NN; - - let t_e_inv = BigInt::mod_inv(&BigInt::mod_pow(&self.t, &self.e, N_tilde), N_tilde); - let t_e_inv = match t_e_inv { - None => return false, - Some(c) => c, - }; - - let w = (BigInt::mod_pow(h1, &self.t1, N_tilde) * - BigInt::mod_pow(h2, &self.t2, N_tilde) * - t_e_inv) % N_tilde; - - let Gen = alice_ek.n.borrow() + 1; - let mut values_to_hash = - vec![&alice_ek.n, &Gen, a_enc, mta_avc_out, &self.z, &z_prim, &self.t, &v, &w]; - let e = match check { - Some(_) => { - let X_x_coor = check.unwrap().X.x_coord().unwrap(); - values_to_hash.push(&X_x_coor); - let X_y_coor = check.unwrap().X.y_coord().unwrap(); - values_to_hash.push(&X_y_coor); - let u_x_coor = check.unwrap().u.x_coord().unwrap(); - values_to_hash.push(&u_x_coor); - let u_y_coor = check.unwrap().u.y_coord().unwrap(); - values_to_hash.push(&u_y_coor); - values_to_hash - .into_iter() - .fold(Sha256::new(), |acc, b| acc.chain_bigint(b)) - .result_bigint() - }, - None => values_to_hash - .into_iter() - .fold(Sha256::new(), |acc, b| acc.chain_bigint(b)) - .result_bigint(), - }; - - if e != self.e { - return false - } - - true - } - - pub fn generate( - a_encrypted: &BigInt, - mta_encrypted: &BigInt, - b: &Scalar, - beta_prim: &BigInt, - alice_ek: &EncryptionKey, - dlog_statement: &DLogStatement, - r: &Randomness, - check: bool, - ) -> (BobProof, Option>) { - let round1 = BobZkpRound1::from( - alice_ek, - dlog_statement, - b, - beta_prim, - a_encrypted, - Scalar::::group_order(), - ); - - let Gen = alice_ek.n.borrow() + 1; - let mut values_to_hash = vec![ - &alice_ek.n, - &Gen, - a_encrypted, - mta_encrypted, - &round1.z, - &round1.z_prim, - &round1.t, - &round1.v, - &round1.w, - ]; - let mut check_u = None; - let e = if check { - let (X, u) = { - let ec_gen = Point::generator(); - let alpha = Scalar::::from(&round1.alpha); - (ec_gen * b, ec_gen * alpha) - }; - check_u = Some(u.clone()); - let X_x_coor = X.x_coord().unwrap(); - values_to_hash.push(&X_x_coor); - let X_y_coor = X.y_coord().unwrap(); - values_to_hash.push(&X_y_coor); - let u_x_coor = u.x_coord().unwrap(); - values_to_hash.push(&u_x_coor); - let u_y_coor = u.y_coord().unwrap(); - values_to_hash.push(&u_y_coor); - values_to_hash - .into_iter() - .fold(Sha256::new(), |acc, b| acc.chain_bigint(b)) - .result_bigint() - } else { - values_to_hash - .into_iter() - .fold(Sha256::new(), |acc, b| acc.chain_bigint(b)) - .result_bigint() - }; - - let round2 = BobZkpRound2::from(alice_ek, &round1, &e, b, beta_prim, r); - - ( - BobProof { - t: round1.t.clone(), - z: round1.z.clone(), - e, - s: round2.s, - s1: round2.s1, - s2: round2.s2, - t1: round2.t1, - t2: round2.t2, - }, - check_u, - ) - } + pub fn verify( + &self, + a_enc: &BigInt, + mta_avc_out: &BigInt, + alice_ek: &EncryptionKey, + dlog_statement: &DLogStatement, + check: Option<&BobCheck>, + ) -> bool { + let N = &alice_ek.n; + let NN = &alice_ek.nn; + let N_tilde = &dlog_statement.N; + let h1 = &dlog_statement.g; + let h2 = &dlog_statement.ni; + + if self.s1 > Scalar::::group_order().pow(3) { + return false; + } + + let z_e_inv = BigInt::mod_inv( + &BigInt::mod_pow(&self.z, &self.e, N_tilde), + N_tilde, + ); + let z_e_inv = match z_e_inv { + // z must be invertible, yet the check is done here + None => return false, + Some(c) => c, + }; + + let z_prim = (BigInt::mod_pow(h1, &self.s1, N_tilde) + * BigInt::mod_pow(h2, &self.s2, N_tilde) + * z_e_inv) + % N_tilde; + + let mta_e_inv = + BigInt::mod_inv(&BigInt::mod_pow(mta_avc_out, &self.e, NN), NN); + let mta_e_inv = match mta_e_inv { + None => return false, + Some(c) => c, + }; + + let v = (BigInt::mod_pow(a_enc, &self.s1, NN) + * BigInt::mod_pow(&self.s, N, NN) + * (self.t1.borrow() * N + 1) + * mta_e_inv) + % NN; + + let t_e_inv = BigInt::mod_inv( + &BigInt::mod_pow(&self.t, &self.e, N_tilde), + N_tilde, + ); + let t_e_inv = match t_e_inv { + None => return false, + Some(c) => c, + }; + + let w = (BigInt::mod_pow(h1, &self.t1, N_tilde) + * BigInt::mod_pow(h2, &self.t2, N_tilde) + * t_e_inv) + % N_tilde; + + let Gen = alice_ek.n.borrow() + 1; + let mut values_to_hash = vec![ + &alice_ek.n, + &Gen, + a_enc, + mta_avc_out, + &self.z, + &z_prim, + &self.t, + &v, + &w, + ]; + let e = match check { + Some(_) => { + let X_x_coor = check.unwrap().X.x_coord().unwrap(); + values_to_hash.push(&X_x_coor); + let X_y_coor = check.unwrap().X.y_coord().unwrap(); + values_to_hash.push(&X_y_coor); + let u_x_coor = check.unwrap().u.x_coord().unwrap(); + values_to_hash.push(&u_x_coor); + let u_y_coor = check.unwrap().u.y_coord().unwrap(); + values_to_hash.push(&u_y_coor); + values_to_hash + .into_iter() + .fold(Sha256::new(), |acc, b| acc.chain_bigint(b)) + .result_bigint() + } + None => values_to_hash + .into_iter() + .fold(Sha256::new(), |acc, b| acc.chain_bigint(b)) + .result_bigint(), + }; + + if e != self.e { + return false; + } + + true + } + + pub fn generate( + a_encrypted: &BigInt, + mta_encrypted: &BigInt, + b: &Scalar, + beta_prim: &BigInt, + alice_ek: &EncryptionKey, + dlog_statement: &DLogStatement, + r: &Randomness, + check: bool, + ) -> (BobProof, Option>) { + let round1 = BobZkpRound1::from( + alice_ek, + dlog_statement, + b, + beta_prim, + a_encrypted, + Scalar::::group_order(), + ); + + let Gen = alice_ek.n.borrow() + 1; + let mut values_to_hash = vec![ + &alice_ek.n, + &Gen, + a_encrypted, + mta_encrypted, + &round1.z, + &round1.z_prim, + &round1.t, + &round1.v, + &round1.w, + ]; + let mut check_u = None; + let e = if check { + let (X, u) = { + let ec_gen = Point::generator(); + let alpha = Scalar::::from(&round1.alpha); + (ec_gen * b, ec_gen * alpha) + }; + check_u = Some(u.clone()); + let X_x_coor = X.x_coord().unwrap(); + values_to_hash.push(&X_x_coor); + let X_y_coor = X.y_coord().unwrap(); + values_to_hash.push(&X_y_coor); + let u_x_coor = u.x_coord().unwrap(); + values_to_hash.push(&u_x_coor); + let u_y_coor = u.y_coord().unwrap(); + values_to_hash.push(&u_y_coor); + values_to_hash + .into_iter() + .fold(Sha256::new(), |acc, b| acc.chain_bigint(b)) + .result_bigint() + } else { + values_to_hash + .into_iter() + .fold(Sha256::new(), |acc, b| acc.chain_bigint(b)) + .result_bigint() + }; + + let round2 = BobZkpRound2::from(alice_ek, &round1, &e, b, beta_prim, r); + + ( + BobProof { + t: round1.t.clone(), + z: round1.z.clone(), + e, + s: round2.s, + s1: round2.s1, + s2: round2.s2, + t1: round2.t1, + t2: round2.t2, + }, + check_u, + ) + } } /// Bob's extended proof, adds the knowledge of $`B = g^b \in \mathcal{G}`$ #[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] pub struct BobProofExt { - proof: BobProof, - u: Point, + proof: BobProof, + u: Point, } #[allow(clippy::too_many_arguments)] impl BobProofExt { - pub fn verify( - &self, - a_enc: &BigInt, - mta_avc_out: &BigInt, - alice_ek: &EncryptionKey, - dlog_statement: &DLogStatement, - X: &Point, - ) -> bool { - // check basic proof first - if !self.proof.verify( - a_enc, - mta_avc_out, - alice_ek, - dlog_statement, - Some(&BobCheck { u: self.u.clone(), X: X.clone() }), - ) { - return false - } - - // fiddle with EC points - let (x1, x2) = { - let ec_gen = Point::generator(); - let s1 = Scalar::::from(&self.proof.s1); - let e = Scalar::::from(&self.proof.e); - (ec_gen * s1, (X * &e) + &self.u) - }; - - if x1 != x2 { - return false - } - - true - } + pub fn verify( + &self, + a_enc: &BigInt, + mta_avc_out: &BigInt, + alice_ek: &EncryptionKey, + dlog_statement: &DLogStatement, + X: &Point, + ) -> bool { + // check basic proof first + if !self.proof.verify( + a_enc, + mta_avc_out, + alice_ek, + dlog_statement, + Some(&BobCheck { + u: self.u.clone(), + X: X.clone(), + }), + ) { + return false; + } + + // fiddle with EC points + let (x1, x2) = { + let ec_gen = Point::generator(); + let s1 = Scalar::::from(&self.proof.s1); + let e = Scalar::::from(&self.proof.e); + (ec_gen * s1, (X * &e) + &self.u) + }; + + if x1 != x2 { + return false; + } + + true + } } /// sample random value of an element of a multiplicative group pub trait SampleFromMultiplicativeGroup { - fn from_modulo(N: &BigInt) -> BigInt; - fn from_paillier_key(ek: &EncryptionKey) -> BigInt; + fn from_modulo(N: &BigInt) -> BigInt; + fn from_paillier_key(ek: &EncryptionKey) -> BigInt; } impl SampleFromMultiplicativeGroup for BigInt { - fn from_modulo(N: &BigInt) -> BigInt { - let One = BigInt::one(); - loop { - let r = Self::sample_below(N); - if r.gcd(N) == One { - return r - } - } - } - - fn from_paillier_key(ek: &EncryptionKey) -> BigInt { - Self::from_modulo(ek.n.borrow()) - } + fn from_modulo(N: &BigInt) -> BigInt { + let One = BigInt::one(); + loop { + let r = Self::sample_below(N); + if r.gcd(N) == One { + return r; + } + } + } + + fn from_paillier_key(ek: &EncryptionKey) -> BigInt { + Self::from_modulo(ek.n.borrow()) + } } #[cfg(test)] pub(crate) mod tests { - use super::*; - use paillier::{ - traits::{Encrypt, EncryptWithChosenRandomness, KeyGeneration}, - Add, DecryptionKey, Mul, Paillier, RawCiphertext, RawPlaintext, - }; - - fn generate( - a_encrypted: &BigInt, - mta_encrypted: &BigInt, - b: &Scalar, - beta_prim: &BigInt, - alice_ek: &EncryptionKey, - dlog_statement: &DLogStatement, - r: &Randomness, - ) -> BobProofExt { - // proving a basic proof (with modified hash) - let (bob_proof, u) = BobProof::generate( - a_encrypted, - mta_encrypted, - b, - beta_prim, - alice_ek, - dlog_statement, - r, - true, - ); - - BobProofExt { proof: bob_proof, u: u.unwrap() } - } - - pub(crate) fn generate_init() -> (DLogStatement, EncryptionKey, DecryptionKey) { - let (ek_tilde, dk_tilde) = Paillier::keypair().keys(); - let one = BigInt::one(); - let phi = (&dk_tilde.p - &one) * (&dk_tilde.q - &one); - let h1 = BigInt::sample_below(&ek_tilde.n); - let (xhi, _) = loop { - let xhi_ = BigInt::sample_below(&phi); - match BigInt::mod_inv(&xhi_, &phi) { - Some(inv) => break (xhi_, inv), - None => continue, - } - }; - let h2 = BigInt::mod_pow(&h1, &xhi, &ek_tilde.n); - - let (ek, dk) = Paillier::keypair().keys(); - let dlog_statement = DLogStatement { g: h1, ni: h2, N: ek_tilde.n }; - (dlog_statement, ek, dk) - } - - #[test] - fn alice_zkp() { - let (dlog_statement, ek, _) = generate_init(); - - // Alice's secret value - let a = Scalar::::random().to_bigint(); - let r = BigInt::from_paillier_key(&ek); - let cipher = Paillier::encrypt_with_chosen_randomness( - &ek, - RawPlaintext::from(a.clone()), - &Randomness::from(&r), - ) - .0 - .clone() - .into_owned(); - - let alice_proof = AliceProof::generate(&a, &cipher, &ek, &dlog_statement, &r); - - assert!(alice_proof.verify(&cipher, &ek, &dlog_statement)); - } - - #[test] - fn bob_zkp() { - let (dlog_statement, ek, _) = generate_init(); - - (0..5).for_each(|_| { - let alice_public_key = &ek; - - // run MtA protocol with different inputs - (0..5).for_each(|_| { - // Simulate Alice - let a = Scalar::::random().to_bigint(); - let encrypted_a = Paillier::encrypt(alice_public_key, RawPlaintext::from(a)) - .0 - .clone() - .into_owned(); - - // Bob follows MtA - let b = Scalar::::random(); - // E(a) * b - let b_times_enc_a = Paillier::mul( - alice_public_key, - RawCiphertext::from(encrypted_a.clone()), - RawPlaintext::from(&b.to_bigint()), - ); - let beta_prim = BigInt::sample_below(&alice_public_key.n); - let r = Randomness::sample(alice_public_key); - let enc_beta_prim = Paillier::encrypt_with_chosen_randomness( - alice_public_key, - RawPlaintext::from(&beta_prim), - &r, - ); - - let mta_out = Paillier::add(alice_public_key, b_times_enc_a, enc_beta_prim); - - let (bob_proof, _) = BobProof::generate( - &encrypted_a, - &mta_out.0.clone(), - &b, - &beta_prim, - alice_public_key, - &dlog_statement, - &r, - false, - ); - assert!(bob_proof.verify( - &encrypted_a, - &mta_out.0.clone(), - alice_public_key, - &dlog_statement, - None - )); - - // Bob follows MtAwc - let ec_gen = Point::generator(); - let X = ec_gen * &b; - let bob_proof = generate( - &encrypted_a, - &mta_out.0.clone(), - &b, - &beta_prim, - alice_public_key, - &dlog_statement, - &r, - ); - assert!(bob_proof.verify( - &encrypted_a, - &mta_out.0.clone(), - alice_public_key, - &dlog_statement, - &X - )); - }); - }); - } + use super::*; + use paillier::{ + traits::{Encrypt, EncryptWithChosenRandomness, KeyGeneration}, + Add, DecryptionKey, Mul, Paillier, RawCiphertext, RawPlaintext, + }; + + fn generate( + a_encrypted: &BigInt, + mta_encrypted: &BigInt, + b: &Scalar, + beta_prim: &BigInt, + alice_ek: &EncryptionKey, + dlog_statement: &DLogStatement, + r: &Randomness, + ) -> BobProofExt { + // proving a basic proof (with modified hash) + let (bob_proof, u) = BobProof::generate( + a_encrypted, + mta_encrypted, + b, + beta_prim, + alice_ek, + dlog_statement, + r, + true, + ); + + BobProofExt { + proof: bob_proof, + u: u.unwrap(), + } + } + + pub(crate) fn generate_init( + ) -> (DLogStatement, EncryptionKey, DecryptionKey) { + let (ek_tilde, dk_tilde) = Paillier::keypair().keys(); + let one = BigInt::one(); + let phi = (&dk_tilde.p - &one) * (&dk_tilde.q - &one); + let h1 = BigInt::sample_below(&ek_tilde.n); + let (xhi, _) = loop { + let xhi_ = BigInt::sample_below(&phi); + match BigInt::mod_inv(&xhi_, &phi) { + Some(inv) => break (xhi_, inv), + None => continue, + } + }; + let h2 = BigInt::mod_pow(&h1, &xhi, &ek_tilde.n); + + let (ek, dk) = Paillier::keypair().keys(); + let dlog_statement = DLogStatement { + g: h1, + ni: h2, + N: ek_tilde.n, + }; + (dlog_statement, ek, dk) + } + + #[test] + fn alice_zkp() { + let (dlog_statement, ek, _) = generate_init(); + + // Alice's secret value + let a = Scalar::::random().to_bigint(); + let r = BigInt::from_paillier_key(&ek); + let cipher = Paillier::encrypt_with_chosen_randomness( + &ek, + RawPlaintext::from(a.clone()), + &Randomness::from(&r), + ) + .0 + .clone() + .into_owned(); + + let alice_proof = + AliceProof::generate(&a, &cipher, &ek, &dlog_statement, &r); + + assert!(alice_proof.verify(&cipher, &ek, &dlog_statement)); + } + + #[test] + fn bob_zkp() { + let (dlog_statement, ek, _) = generate_init(); + + (0..5).for_each(|_| { + let alice_public_key = &ek; + + // run MtA protocol with different inputs + (0..5).for_each(|_| { + // Simulate Alice + let a = Scalar::::random().to_bigint(); + let encrypted_a = + Paillier::encrypt(alice_public_key, RawPlaintext::from(a)) + .0 + .clone() + .into_owned(); + + // Bob follows MtA + let b = Scalar::::random(); + // E(a) * b + let b_times_enc_a = Paillier::mul( + alice_public_key, + RawCiphertext::from(encrypted_a.clone()), + RawPlaintext::from(&b.to_bigint()), + ); + let beta_prim = BigInt::sample_below(&alice_public_key.n); + let r = Randomness::sample(alice_public_key); + let enc_beta_prim = Paillier::encrypt_with_chosen_randomness( + alice_public_key, + RawPlaintext::from(&beta_prim), + &r, + ); + + let mta_out = Paillier::add( + alice_public_key, + b_times_enc_a, + enc_beta_prim, + ); + + let (bob_proof, _) = BobProof::generate( + &encrypted_a, + &mta_out.0.clone(), + &b, + &beta_prim, + alice_public_key, + &dlog_statement, + &r, + false, + ); + assert!(bob_proof.verify( + &encrypted_a, + &mta_out.0.clone(), + alice_public_key, + &dlog_statement, + None + )); + + // Bob follows MtAwc + let ec_gen = Point::generator(); + let X = ec_gen * &b; + let bob_proof = generate( + &encrypted_a, + &mta_out.0.clone(), + &b, + &beta_prim, + alice_public_key, + &dlog_statement, + &r, + ); + assert!(bob_proof.verify( + &encrypted_a, + &mta_out.0.clone(), + alice_public_key, + &dlog_statement, + &X + )); + }); + }); + } } diff --git a/src/utilities/mta/test.rs b/src/utilities/mta/test.rs index edd291d3..c2e6d635 100644 --- a/src/utilities/mta/test.rs +++ b/src/utilities/mta/test.rs @@ -1,16 +1,22 @@ -use crate::utilities::mta::{range_proofs::tests::generate_init, MessageA, MessageB}; +use crate::utilities::mta::{ + range_proofs::tests::generate_init, MessageA, MessageB, +}; use curv::elliptic::curves::{secp256_k1::Secp256k1, Scalar}; #[test] fn test_mta() { - let alice_input = Scalar::::random(); - let (dlog_statement, ek_alice, dk_alice) = generate_init(); - let bob_input = Scalar::::random(); - let (m_a, _) = MessageA::a(&alice_input, &ek_alice, &[dlog_statement.clone()]); - let (m_b, beta, _, _) = MessageB::b(&bob_input, &ek_alice, m_a, &[dlog_statement]).unwrap(); - let alpha = m_b.verify_proofs_get_alpha(&dk_alice, &alice_input).expect("wrong dlog or m_b"); + let alice_input = Scalar::::random(); + let (dlog_statement, ek_alice, dk_alice) = generate_init(); + let bob_input = Scalar::::random(); + let (m_a, _) = + MessageA::a(&alice_input, &ek_alice, &[dlog_statement.clone()]); + let (m_b, beta, _, _) = + MessageB::b(&bob_input, &ek_alice, m_a, &[dlog_statement]).unwrap(); + let alpha = m_b + .verify_proofs_get_alpha(&dk_alice, &alice_input) + .expect("wrong dlog or m_b"); - let left = alpha.0 + beta; - let right = alice_input * bob_input; - assert_eq!(left, right); + let left = alpha.0 + beta; + let right = alice_input * bob_input; + assert_eq!(left, right); } diff --git a/src/utilities/mul/mod.rs b/src/utilities/mul/mod.rs index 22d42e1f..8b966fbd 100644 --- a/src/utilities/mul/mod.rs +++ b/src/utilities/mul/mod.rs @@ -1,29 +1,32 @@ #![allow(non_snake_case)] /* - CGGMP Threshold ECDSA + CGGMP Threshold ECDSA - Copyright 2022 by Webb Technologies + Copyright 2022 by Webb Technologies - This file is part of CGGMP Threshold ECDSA library - (https://github.com/webb-tools/cggmp-threshold-ecdsa) + This file is part of CGGMP Threshold ECDSA library + (https://github.com/webb-tools/cggmp-threshold-ecdsa) - CGGMP Threshold ECDSA is free software: you can redistribute - it and/or modify it under the terms of the GNU General Public - License as published by the Free Software Foundation, either - version 3 of the License, or (at your option) any later version. + CGGMP Threshold ECDSA is free software: you can redistribute + it and/or modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, either + version 3 of the License, or (at your option) any later version. - @license GPL-3.0+ + @license GPL-3.0+ */ use super::sample_relatively_prime_integer; use crate::{utilities::mod_pow_with_negative, Error}; use curv::{ - arithmetic::{traits::*, Modulo}, - cryptographic_primitives::hashing::{Digest, DigestExt}, - elliptic::curves::Curve, - BigInt, + arithmetic::{traits::*, Modulo}, + cryptographic_primitives::hashing::{Digest, DigestExt}, + elliptic::curves::Curve, + BigInt, +}; +use paillier::{ + EncryptWithChosenRandomness, EncryptionKey, Paillier, Randomness, + RawPlaintext, }; -use paillier::{EncryptWithChosenRandomness, EncryptionKey, Paillier, Randomness, RawPlaintext}; use rand::Rng; use rand_chacha::{rand_core::SeedableRng, ChaChaRng}; use serde::{Deserialize, Serialize}; @@ -31,200 +34,233 @@ use std::marker::PhantomData; #[derive(Clone, Debug, Serialize, Deserialize)] pub struct PaillierMulStatement { - pub N: BigInt, - pub NN: BigInt, - pub C: BigInt, - pub Y: BigInt, - pub X: BigInt, - pub ek_prover: EncryptionKey, - pub phantom: PhantomData<(E, H)>, + pub N: BigInt, + pub NN: BigInt, + pub C: BigInt, + pub Y: BigInt, + pub X: BigInt, + pub ek_prover: EncryptionKey, + pub phantom: PhantomData<(E, H)>, } pub struct PaillierMulWitness { - x: BigInt, - rho: BigInt, - rho_x: BigInt, - phantom: PhantomData<(E, H)>, + x: BigInt, + rho: BigInt, + rho_x: BigInt, + phantom: PhantomData<(E, H)>, } impl PaillierMulWitness { - pub fn new(x: BigInt, rho: BigInt, rho_x: BigInt) -> Self { - PaillierMulWitness { x, rho, rho_x, phantom: PhantomData } - } + pub fn new(x: BigInt, rho: BigInt, rho_x: BigInt) -> Self { + PaillierMulWitness { + x, + rho, + rho_x, + phantom: PhantomData, + } + } } impl PaillierMulStatement { - #[allow(clippy::too_many_arguments)] - pub fn generate( - rho: BigInt, - rho_x: BigInt, - prover: EncryptionKey, - Y: BigInt, - ) -> (Self, PaillierMulWitness) { - let ek_prover = prover.clone(); - // x <- Z_N - let x = BigInt::sample_below(&prover.n); - // X = (1 + N)^x * rho_x^N mod N^2 - let X = Paillier::encrypt_with_chosen_randomness( - &ek_prover, - RawPlaintext::from(x.clone()), - &Randomness::from(rho_x.clone()), - ); - // C = Y^x * rho^N mod N^2 - let C = BigInt::mod_mul( - &BigInt::mod_pow(&Y, &x, &prover.nn), - &BigInt::mod_pow(&rho, &prover.n, &prover.nn), - &prover.nn, - ); - - ( - Self { - N: prover.n, - NN: prover.nn, - C, - Y, - X: X.clone().into(), - ek_prover, - phantom: PhantomData, - }, - PaillierMulWitness { x, rho, rho_x, phantom: PhantomData }, - ) - } + #[allow(clippy::too_many_arguments)] + pub fn generate( + rho: BigInt, + rho_x: BigInt, + prover: EncryptionKey, + Y: BigInt, + ) -> (Self, PaillierMulWitness) { + let ek_prover = prover.clone(); + // x <- Z_N + let x = BigInt::sample_below(&prover.n); + // X = (1 + N)^x * rho_x^N mod N^2 + let X = Paillier::encrypt_with_chosen_randomness( + &ek_prover, + RawPlaintext::from(x.clone()), + &Randomness::from(rho_x.clone()), + ); + // C = Y^x * rho^N mod N^2 + let C = BigInt::mod_mul( + &BigInt::mod_pow(&Y, &x, &prover.nn), + &BigInt::mod_pow(&rho, &prover.n, &prover.nn), + &prover.nn, + ); + + ( + Self { + N: prover.n, + NN: prover.nn, + C, + Y, + X: X.clone().into(), + ek_prover, + phantom: PhantomData, + }, + PaillierMulWitness { + x, + rho, + rho_x, + phantom: PhantomData, + }, + ) + } } #[derive(Clone, Debug, Serialize, Deserialize)] pub struct PaillierMulCommitment { - A: BigInt, - B: BigInt, + A: BigInt, + B: BigInt, } #[derive(Clone, Debug, Serialize, Deserialize)] pub struct PaillierMulProof { - z: BigInt, - u: BigInt, - v: BigInt, - commitment: PaillierMulCommitment, - phantom: PhantomData<(E, H)>, + z: BigInt, + u: BigInt, + v: BigInt, + commitment: PaillierMulCommitment, + phantom: PhantomData<(E, H)>, } // Link to the UC non-interactive threshold ECDSA paper impl PaillierMulProof { - pub fn prove( - witness: &PaillierMulWitness, - statement: &PaillierMulStatement, - ) -> PaillierMulProof { - // α,r,s <- Z∗_N - let alpha = sample_relatively_prime_integer(&statement.N); - let r = sample_relatively_prime_integer(&statement.N); - let s = sample_relatively_prime_integer(&statement.N); - // A = Y^α * r^N mod N^2 - let A = BigInt::mod_mul( - &mod_pow_with_negative(&statement.Y, &alpha, &statement.NN), - &BigInt::mod_pow(&r, &statement.N, &statement.NN), - &statement.NN, - ); - // B = (1 + N)^α * s^N mod N^2 - let B = { - let B_ciphertext = Paillier::encrypt_with_chosen_randomness( - &statement.ek_prover, - RawPlaintext::from(alpha.clone()), - &Randomness::from(s.clone()), - ); - let B_bigint: BigInt = B_ciphertext.into(); - B_bigint.mod_floor(&statement.NN) - }; - // e = H(A,B) - let mut e = H::new().chain_bigint(&A).chain_bigint(&B).result_bigint(); - let mut rng: ChaChaRng = ChaChaRng::from_seed(e.to_bytes().try_into().unwrap()); - let val = rng.gen_range(0..2); - e = BigInt::from(val).mul(&BigInt::from(-2)).add(&BigInt::one()).mul(&e); - let commitment: PaillierMulCommitment = PaillierMulCommitment { A, B }; - // z = α + e * x mod N - let z = BigInt::add(&alpha, &BigInt::mul(&e, &witness.x)); - // u = r * rho^e mod N - let u = BigInt::mod_mul( - &r, - &mod_pow_with_negative(&witness.rho, &e, &statement.N), - &statement.N, - ); - // v = s * rho_x^e mod N - let v = BigInt::mod_mul( - &s, - &mod_pow_with_negative(&witness.rho_x, &e, &statement.N), - &statement.N, - ); - // Return the proof - PaillierMulProof { z, u, v, commitment, phantom: PhantomData } - } - - pub fn verify( - proof: &PaillierMulProof, - statement: &PaillierMulStatement, - ) -> Result<(), Error> { - // Compute the challenge - let mut e = H::new() - .chain_bigint(&proof.commitment.A) - .chain_bigint(&proof.commitment.B) - .result_bigint(); - let mut rng: ChaChaRng = ChaChaRng::from_seed(e.to_bytes().try_into().unwrap()); - let val = rng.gen_range(0..2); - e = BigInt::from(val).mul(&BigInt::from(-2)).add(&BigInt::one()).mul(&e); - /* - FIRST EQUALITY CHECK - Y^z · u^N = A · C^e mod N^2 - */ - let left_1 = BigInt::mod_mul( - &mod_pow_with_negative(&statement.Y, &proof.z, &statement.NN), - &BigInt::mod_pow(&proof.u, &statement.N, &statement.NN), - &statement.NN, - ); - let right_1 = BigInt::mod_mul( - &proof.commitment.A, - &mod_pow_with_negative(&statement.C, &e, &statement.NN), - &statement.NN, - ); - assert!(left_1 == right_1); - /* - SECOND EQUALITY CHECK - (1 + N)^z · v^N = B · X^e mod N^2 === Enc(z,c) = B · X^e mod N^2 - */ - let left_ciphertext = Paillier::encrypt_with_chosen_randomness( - &statement.ek_prover, - RawPlaintext::from(proof.z.clone()), - &Randomness::from(proof.v.clone()), - ); - let left_2: BigInt = left_ciphertext.into(); - let right_2 = BigInt::mod_mul( - &proof.commitment.B, - &mod_pow_with_negative(&statement.X, &e, &statement.NN), - &statement.NN, - ); - assert!(left_2.mod_floor(&statement.NN) == right_2); - Ok(()) - } + pub fn prove( + witness: &PaillierMulWitness, + statement: &PaillierMulStatement, + ) -> PaillierMulProof { + // α,r,s <- Z∗_N + let alpha = sample_relatively_prime_integer(&statement.N); + let r = sample_relatively_prime_integer(&statement.N); + let s = sample_relatively_prime_integer(&statement.N); + // A = Y^α * r^N mod N^2 + let A = BigInt::mod_mul( + &mod_pow_with_negative(&statement.Y, &alpha, &statement.NN), + &BigInt::mod_pow(&r, &statement.N, &statement.NN), + &statement.NN, + ); + // B = (1 + N)^α * s^N mod N^2 + let B = { + let B_ciphertext = Paillier::encrypt_with_chosen_randomness( + &statement.ek_prover, + RawPlaintext::from(alpha.clone()), + &Randomness::from(s.clone()), + ); + let B_bigint: BigInt = B_ciphertext.into(); + B_bigint.mod_floor(&statement.NN) + }; + // e = H(A,B) + let mut e = H::new().chain_bigint(&A).chain_bigint(&B).result_bigint(); + let mut rng: ChaChaRng = + ChaChaRng::from_seed(e.to_bytes().try_into().unwrap()); + let val = rng.gen_range(0..2); + e = BigInt::from(val) + .mul(&BigInt::from(-2)) + .add(&BigInt::one()) + .mul(&e); + let commitment: PaillierMulCommitment = PaillierMulCommitment { A, B }; + // z = α + e * x mod N + let z = BigInt::add(&alpha, &BigInt::mul(&e, &witness.x)); + // u = r * rho^e mod N + let u = BigInt::mod_mul( + &r, + &mod_pow_with_negative(&witness.rho, &e, &statement.N), + &statement.N, + ); + // v = s * rho_x^e mod N + let v = BigInt::mod_mul( + &s, + &mod_pow_with_negative(&witness.rho_x, &e, &statement.N), + &statement.N, + ); + // Return the proof + PaillierMulProof { + z, + u, + v, + commitment, + phantom: PhantomData, + } + } + + pub fn verify( + proof: &PaillierMulProof, + statement: &PaillierMulStatement, + ) -> Result<(), Error> { + // Compute the challenge + let mut e = H::new() + .chain_bigint(&proof.commitment.A) + .chain_bigint(&proof.commitment.B) + .result_bigint(); + let mut rng: ChaChaRng = + ChaChaRng::from_seed(e.to_bytes().try_into().unwrap()); + let val = rng.gen_range(0..2); + e = BigInt::from(val) + .mul(&BigInt::from(-2)) + .add(&BigInt::one()) + .mul(&e); + /* + FIRST EQUALITY CHECK + Y^z · u^N = A · C^e mod N^2 + */ + let left_1 = BigInt::mod_mul( + &mod_pow_with_negative(&statement.Y, &proof.z, &statement.NN), + &BigInt::mod_pow(&proof.u, &statement.N, &statement.NN), + &statement.NN, + ); + let right_1 = BigInt::mod_mul( + &proof.commitment.A, + &mod_pow_with_negative(&statement.C, &e, &statement.NN), + &statement.NN, + ); + assert!(left_1 == right_1); + /* + SECOND EQUALITY CHECK + (1 + N)^z · v^N = B · X^e mod N^2 === Enc(z,c) = B · X^e mod N^2 + */ + let left_ciphertext = Paillier::encrypt_with_chosen_randomness( + &statement.ek_prover, + RawPlaintext::from(proof.z.clone()), + &Randomness::from(proof.v.clone()), + ); + let left_2: BigInt = left_ciphertext.into(); + let right_2 = BigInt::mod_mul( + &proof.commitment.B, + &mod_pow_with_negative(&statement.X, &e, &statement.NN), + &statement.NN, + ); + assert!(left_2.mod_floor(&statement.NN) == right_2); + Ok(()) + } } #[cfg(test)] mod tests { - use super::*; - use crate::utilities::{mta::range_proofs::SampleFromMultiplicativeGroup, BITS_PAILLIER}; - use curv::elliptic::curves::secp256_k1::Secp256k1; - use paillier::{Encrypt, KeyGeneration, Paillier, RawPlaintext}; - use sha2::Sha256; - - #[test] - fn test_paillier_mul() { - let (ek_prover, _) = Paillier::keypair_with_modulus_size(BITS_PAILLIER).keys(); - let rho: BigInt = BigInt::from_paillier_key(&ek_prover); - let rho_x: BigInt = BigInt::from_paillier_key(&ek_prover); - let Y = Paillier::encrypt(&ek_prover, RawPlaintext::from(BigInt::from(12))); - let (statement, witness) = PaillierMulStatement::::generate( - rho, - rho_x, - ek_prover, - Y.0.into_owned(), - ); - let proof = PaillierMulProof::::prove(&witness, &statement); - assert!(PaillierMulProof::::verify(&proof, &statement).is_ok()); - } + use super::*; + use crate::utilities::{ + mta::range_proofs::SampleFromMultiplicativeGroup, BITS_PAILLIER, + }; + use curv::elliptic::curves::secp256_k1::Secp256k1; + use paillier::{Encrypt, KeyGeneration, Paillier, RawPlaintext}; + use sha2::Sha256; + + #[test] + fn test_paillier_mul() { + let (ek_prover, _) = + Paillier::keypair_with_modulus_size(BITS_PAILLIER).keys(); + let rho: BigInt = BigInt::from_paillier_key(&ek_prover); + let rho_x: BigInt = BigInt::from_paillier_key(&ek_prover); + let Y = + Paillier::encrypt(&ek_prover, RawPlaintext::from(BigInt::from(12))); + let (statement, witness) = + PaillierMulStatement::::generate( + rho, + rho_x, + ek_prover, + Y.0.into_owned(), + ); + let proof = + PaillierMulProof::::prove(&witness, &statement); + assert!(PaillierMulProof::::verify( + &proof, &statement + ) + .is_ok()); + } } diff --git a/src/utilities/mul_star/mod.rs b/src/utilities/mul_star/mod.rs index 00a7c46b..25a1e787 100644 --- a/src/utilities/mul_star/mod.rs +++ b/src/utilities/mul_star/mod.rs @@ -1,18 +1,18 @@ #![allow(non_snake_case)] /* - CGGMP Threshold ECDSA + CGGMP Threshold ECDSA - Copyright 2022 by Webb Technologies + Copyright 2022 by Webb Technologies - This file is part of CGGMP Threshold ECDSA library - (https://github.com/webb-tools/cggmp-threshold-ecdsa) + This file is part of CGGMP Threshold ECDSA library + (https://github.com/webb-tools/cggmp-threshold-ecdsa) - CGGMP Threshold ECDSA is free software: you can redistribute - it and/or modify it under the terms of the GNU General Public - License as published by the Free Software Foundation, either - version 3 of the License, or (at your option) any later version. + CGGMP Threshold ECDSA is free software: you can redistribute + it and/or modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, either + version 3 of the License, or (at your option) any later version. - @license GPL-3.0+ + @license GPL-3.0+ */ //! Knowledge of Exponent vs Paillier Encryption – Π^{log∗} @@ -24,10 +24,10 @@ use super::sample_relatively_prime_integer; use crate::utilities::{mod_pow_with_negative, L}; use curv::{ - arithmetic::{traits::*, Modulo}, - cryptographic_primitives::hashing::{Digest, DigestExt}, - elliptic::curves::{Curve, Point, Scalar}, - BigInt, + arithmetic::{traits::*, Modulo}, + cryptographic_primitives::hashing::{Digest, DigestExt}, + elliptic::curves::{Curve, Point, Scalar}, + BigInt, }; use paillier::EncryptionKey; use serde::{Deserialize, Serialize}; @@ -35,252 +35,307 @@ use std::marker::PhantomData; use zk_paillier::zkproofs::IncorrectProof; #[derive(Clone, Debug, Serialize, Deserialize)] -pub struct PaillierMultiplicationVersusGroupStatement { - pub N0: BigInt, - pub NN0: BigInt, - pub C: BigInt, - pub D: BigInt, - pub X: Point, - pub N_hat: BigInt, - pub s: BigInt, - pub t: BigInt, - pub phantom: PhantomData<(E, H)>, +pub struct PaillierMultiplicationVersusGroupStatement< + E: Curve, + H: Digest + Clone, +> { + pub N0: BigInt, + pub NN0: BigInt, + pub C: BigInt, + pub D: BigInt, + pub X: Point, + pub N_hat: BigInt, + pub s: BigInt, + pub t: BigInt, + pub phantom: PhantomData<(E, H)>, } -pub struct PaillierMultiplicationVersusGroupWitness { - x: BigInt, - rho: BigInt, - phantom: PhantomData<(E, H)>, +pub struct PaillierMultiplicationVersusGroupWitness +{ + x: BigInt, + rho: BigInt, + phantom: PhantomData<(E, H)>, } -impl PaillierMultiplicationVersusGroupWitness { - pub fn new(x: BigInt, rho: BigInt) -> Self { - PaillierMultiplicationVersusGroupWitness { x, rho, phantom: PhantomData } - } +impl + PaillierMultiplicationVersusGroupWitness +{ + pub fn new(x: BigInt, rho: BigInt) -> Self { + PaillierMultiplicationVersusGroupWitness { + x, + rho, + phantom: PhantomData, + } + } } -impl PaillierMultiplicationVersusGroupStatement { - #[allow(clippy::too_many_arguments)] - pub fn generate( - rho: BigInt, - _C: BigInt, - s: BigInt, - t: BigInt, - N_hat: BigInt, - paillier_key: EncryptionKey, - ) -> (Self, PaillierMultiplicationVersusGroupWitness) { - // Set up exponents - let l_exp = BigInt::pow(&BigInt::from(2), L as u32); - // Set up moduli - let N0 = paillier_key.clone().n; - let NN0 = paillier_key.nn; - let x = BigInt::sample_range(&BigInt::from(-1).mul(&l_exp), &l_exp); - let X = Point::::generator().as_point() * Scalar::from(&x); - let C: BigInt = BigInt::zero(); - // D = C^x * rho^(N_0) mod N_0^2 - let D = BigInt::mod_mul( - &mod_pow_with_negative(&C, &x, &NN0), - &BigInt::mod_pow(&rho, &N0, &NN0), - &NN0, - ); - ( - Self { N0, NN0, C, D, X, N_hat, s, t, phantom: PhantomData }, - PaillierMultiplicationVersusGroupWitness { x, rho, phantom: PhantomData }, - ) - } +impl + PaillierMultiplicationVersusGroupStatement +{ + #[allow(clippy::too_many_arguments)] + pub fn generate( + rho: BigInt, + _C: BigInt, + s: BigInt, + t: BigInt, + N_hat: BigInt, + paillier_key: EncryptionKey, + ) -> (Self, PaillierMultiplicationVersusGroupWitness) { + // Set up exponents + let l_exp = BigInt::pow(&BigInt::from(2), L as u32); + // Set up moduli + let N0 = paillier_key.clone().n; + let NN0 = paillier_key.nn; + let x = BigInt::sample_range(&BigInt::from(-1).mul(&l_exp), &l_exp); + let X = Point::::generator().as_point() * Scalar::from(&x); + let C: BigInt = BigInt::zero(); + // D = C^x * rho^(N_0) mod N_0^2 + let D = BigInt::mod_mul( + &mod_pow_with_negative(&C, &x, &NN0), + &BigInt::mod_pow(&rho, &N0, &NN0), + &NN0, + ); + ( + Self { + N0, + NN0, + C, + D, + X, + N_hat, + s, + t, + phantom: PhantomData, + }, + PaillierMultiplicationVersusGroupWitness { + x, + rho, + phantom: PhantomData, + }, + ) + } } #[derive(Clone, Debug, Serialize, Deserialize)] pub struct PaillierMultiplicationVersusGroupCommitment { - A: BigInt, - B_x: Point, - E: BigInt, - S: BigInt, + A: BigInt, + B_x: Point, + E: BigInt, + S: BigInt, } #[derive(Clone, Debug, Serialize, Deserialize)] pub struct PaillierMultiplicationVersusGroupProof { - z_1: BigInt, - z_2: BigInt, - w: BigInt, - commitment: PaillierMultiplicationVersusGroupCommitment, - phantom: PhantomData<(E, H)>, + z_1: BigInt, + z_2: BigInt, + w: BigInt, + commitment: PaillierMultiplicationVersusGroupCommitment, + phantom: PhantomData<(E, H)>, } // Link to the UC non-interactive threshold ECDSA paper impl PaillierMultiplicationVersusGroupProof { - pub fn prove( - witness: &PaillierMultiplicationVersusGroupWitness, - statement: &PaillierMultiplicationVersusGroupStatement, - ) -> PaillierMultiplicationVersusGroupProof { - // Step 1: Sample alpha between -2^{l+ε} and 2^{l+ε} - let alpha_upper = BigInt::pow(&BigInt::from(2), crate::utilities::L_PLUS_EPSILON as u32); - let alpha_lower = BigInt::from(-1).mul(&alpha_upper); - let alpha = BigInt::sample_range(&alpha_lower, &alpha_upper); - - // Step 2: m, r, r_y gamma - // Sample mu between -2^L * N_hat and 2^L * N_hat - let m_upper = BigInt::mul( - &statement.N_hat, - &BigInt::pow(&BigInt::from(2), crate::utilities::L as u32), - ); - let m_lower = BigInt::from(-1).mul(&m_upper); - let m = BigInt::sample_range(&m_lower, &m_upper); - - // γ ← ± 2^{l+ε} · Nˆ - let gamma_upper = BigInt::mul( - &statement.N_hat, - &BigInt::pow(&BigInt::from(2), crate::utilities::L_PLUS_EPSILON as u32), - ); - let gamma_lower = BigInt::from(-1).mul(&gamma_upper); - let gamma = BigInt::sample_range(&gamma_lower, &gamma_upper); - // Sample r from Z*_{N_0} - let r = sample_relatively_prime_integer(&statement.N0.clone()); - - // A = C^alpha * r^N_0 - let A = BigInt::mod_mul( - &mod_pow_with_negative(&statement.C, &alpha, &statement.NN0), - &BigInt::mod_pow(&r, &statement.N0, &statement.NN0), - &statement.NN0, - ); - - // B_x = g^alpha - let B_x: Point = Point::::generator().as_point() * Scalar::from_bigint(&alpha); - - // E = s^alpha t^gamma mod N_hat - let E = BigInt::mod_mul( - &mod_pow_with_negative(&statement.s, &alpha, &statement.N_hat), - &mod_pow_with_negative(&statement.t, &gamma, &statement.N_hat), - &statement.N_hat, - ); - - // S = s^x t^m mod N_hat - let S = BigInt::mod_mul( - &mod_pow_with_negative(&statement.s, &witness.x, &statement.N_hat), - &mod_pow_with_negative(&statement.t, &m, &statement.N_hat), - &statement.N_hat, - ); - - let e = H::new() - .chain_bigint(&A) - .chain_point(&B_x) - .chain_bigint(&E) - .chain_bigint(&S) - .result_bigint(); - - // Step 5: Compute z_1, z_2, z_3 - // z_1 = alpha + ex - let z_1 = BigInt::add(&alpha, &BigInt::mul(&e, &witness.x)); - // z_2 = gamma + e*m - let z_2 = BigInt::add(&gamma, &BigInt::mul(&e, &m)); - // w = r * rho^e mod N_0 - let w = BigInt::mod_mul( - &r, - &mod_pow_with_negative(&witness.rho, &e, &statement.N0), - &statement.N0, - ); - let commitment = PaillierMultiplicationVersusGroupCommitment { A, B_x, E, S }; - Self { z_1, z_2, w, commitment, phantom: PhantomData } - } - - pub fn verify( - proof: &PaillierMultiplicationVersusGroupProof, - statement: &PaillierMultiplicationVersusGroupStatement, - ) -> Result<(), IncorrectProof> { - let e = H::new() - .chain_bigint(&proof.commitment.A) - .chain_point(&proof.commitment.B_x) - .chain_bigint(&proof.commitment.E) - .chain_bigint(&proof.commitment.S) - .result_bigint(); - - // left_1 = (C)^{z_1}w^{N_0} mod N_0^2 - let left_1 = BigInt::mod_mul( - &mod_pow_with_negative(&statement.C, &proof.z_1, &statement.N0), - &BigInt::mod_pow(&proof.w, &statement.N0, &statement.NN0), - &statement.NN0, - ); - - // right_1 = A * D^e - let right_1 = BigInt::mod_mul( - &proof.commitment.A, - &mod_pow_with_negative(&statement.D, &e, &statement.NN0), - &statement.NN0, - ); - - // left_2 = g^z_1 - let left_2 = Point::::generator().as_point() * Scalar::from_bigint(&proof.z_1); - // right_2 = B_x * X^e - let right_2 = - proof.commitment.B_x.clone() + (statement.X.clone() * Scalar::from_bigint(&e)); - - // left_3 = s^z_1 t^z_2 mod N_hat - let left_3 = BigInt::mod_mul( - &mod_pow_with_negative(&statement.s, &proof.z_1, &statement.N_hat), - &mod_pow_with_negative(&statement.t, &proof.z_2, &statement.N_hat), - &statement.N_hat, - ); - - // right_3 = E * S^e mod N_hat - let right_3 = BigInt::mod_mul( - &proof.commitment.E, - &mod_pow_with_negative(&proof.commitment.S, &e, &statement.N_hat), - &statement.N_hat, - ); - - if left_1 != right_1 || left_2 != right_2 || left_3 != right_3 { - return Err(IncorrectProof) - } - - // Range Check -2^{L + eps} <= z_1 <= 2^{L+eps} - let lower_bound_check: bool = proof.z_1 >= - BigInt::from(-1) - .mul(&BigInt::pow(&BigInt::from(2), crate::utilities::L_PLUS_EPSILON as u32)); - - let upper_bound_check = - proof.z_1 <= BigInt::pow(&BigInt::from(2), crate::utilities::L_PLUS_EPSILON as u32); - - if !(lower_bound_check && upper_bound_check) { - return Err(IncorrectProof) - } - Ok(()) - } + pub fn prove( + witness: &PaillierMultiplicationVersusGroupWitness, + statement: &PaillierMultiplicationVersusGroupStatement, + ) -> PaillierMultiplicationVersusGroupProof { + // Step 1: Sample alpha between -2^{l+ε} and 2^{l+ε} + let alpha_upper = BigInt::pow( + &BigInt::from(2), + crate::utilities::L_PLUS_EPSILON as u32, + ); + let alpha_lower = BigInt::from(-1).mul(&alpha_upper); + let alpha = BigInt::sample_range(&alpha_lower, &alpha_upper); + + // Step 2: m, r, r_y gamma + // Sample mu between -2^L * N_hat and 2^L * N_hat + let m_upper = BigInt::mul( + &statement.N_hat, + &BigInt::pow(&BigInt::from(2), crate::utilities::L as u32), + ); + let m_lower = BigInt::from(-1).mul(&m_upper); + let m = BigInt::sample_range(&m_lower, &m_upper); + + // γ ← ± 2^{l+ε} · Nˆ + let gamma_upper = BigInt::mul( + &statement.N_hat, + &BigInt::pow( + &BigInt::from(2), + crate::utilities::L_PLUS_EPSILON as u32, + ), + ); + let gamma_lower = BigInt::from(-1).mul(&gamma_upper); + let gamma = BigInt::sample_range(&gamma_lower, &gamma_upper); + // Sample r from Z*_{N_0} + let r = sample_relatively_prime_integer(&statement.N0.clone()); + + // A = C^alpha * r^N_0 + let A = BigInt::mod_mul( + &mod_pow_with_negative(&statement.C, &alpha, &statement.NN0), + &BigInt::mod_pow(&r, &statement.N0, &statement.NN0), + &statement.NN0, + ); + + // B_x = g^alpha + let B_x: Point = + Point::::generator().as_point() * Scalar::from_bigint(&alpha); + + // E = s^alpha t^gamma mod N_hat + let E = BigInt::mod_mul( + &mod_pow_with_negative(&statement.s, &alpha, &statement.N_hat), + &mod_pow_with_negative(&statement.t, &gamma, &statement.N_hat), + &statement.N_hat, + ); + + // S = s^x t^m mod N_hat + let S = BigInt::mod_mul( + &mod_pow_with_negative(&statement.s, &witness.x, &statement.N_hat), + &mod_pow_with_negative(&statement.t, &m, &statement.N_hat), + &statement.N_hat, + ); + + let e = H::new() + .chain_bigint(&A) + .chain_point(&B_x) + .chain_bigint(&E) + .chain_bigint(&S) + .result_bigint(); + + // Step 5: Compute z_1, z_2, z_3 + // z_1 = alpha + ex + let z_1 = BigInt::add(&alpha, &BigInt::mul(&e, &witness.x)); + // z_2 = gamma + e*m + let z_2 = BigInt::add(&gamma, &BigInt::mul(&e, &m)); + // w = r * rho^e mod N_0 + let w = BigInt::mod_mul( + &r, + &mod_pow_with_negative(&witness.rho, &e, &statement.N0), + &statement.N0, + ); + let commitment = + PaillierMultiplicationVersusGroupCommitment { A, B_x, E, S }; + Self { + z_1, + z_2, + w, + commitment, + phantom: PhantomData, + } + } + + pub fn verify( + proof: &PaillierMultiplicationVersusGroupProof, + statement: &PaillierMultiplicationVersusGroupStatement, + ) -> Result<(), IncorrectProof> { + let e = H::new() + .chain_bigint(&proof.commitment.A) + .chain_point(&proof.commitment.B_x) + .chain_bigint(&proof.commitment.E) + .chain_bigint(&proof.commitment.S) + .result_bigint(); + + // left_1 = (C)^{z_1}w^{N_0} mod N_0^2 + let left_1 = BigInt::mod_mul( + &mod_pow_with_negative(&statement.C, &proof.z_1, &statement.N0), + &BigInt::mod_pow(&proof.w, &statement.N0, &statement.NN0), + &statement.NN0, + ); + + // right_1 = A * D^e + let right_1 = BigInt::mod_mul( + &proof.commitment.A, + &mod_pow_with_negative(&statement.D, &e, &statement.NN0), + &statement.NN0, + ); + + // left_2 = g^z_1 + let left_2 = Point::::generator().as_point() + * Scalar::from_bigint(&proof.z_1); + // right_2 = B_x * X^e + let right_2 = proof.commitment.B_x.clone() + + (statement.X.clone() * Scalar::from_bigint(&e)); + + // left_3 = s^z_1 t^z_2 mod N_hat + let left_3 = BigInt::mod_mul( + &mod_pow_with_negative(&statement.s, &proof.z_1, &statement.N_hat), + &mod_pow_with_negative(&statement.t, &proof.z_2, &statement.N_hat), + &statement.N_hat, + ); + + // right_3 = E * S^e mod N_hat + let right_3 = BigInt::mod_mul( + &proof.commitment.E, + &mod_pow_with_negative(&proof.commitment.S, &e, &statement.N_hat), + &statement.N_hat, + ); + + if left_1 != right_1 || left_2 != right_2 || left_3 != right_3 { + return Err(IncorrectProof); + } + + // Range Check -2^{L + eps} <= z_1 <= 2^{L+eps} + let lower_bound_check: bool = proof.z_1 + >= BigInt::from(-1).mul(&BigInt::pow( + &BigInt::from(2), + crate::utilities::L_PLUS_EPSILON as u32, + )); + + let upper_bound_check = proof.z_1 + <= BigInt::pow( + &BigInt::from(2), + crate::utilities::L_PLUS_EPSILON as u32, + ); + + if !(lower_bound_check && upper_bound_check) { + return Err(IncorrectProof); + } + Ok(()) + } } #[cfg(test)] mod tests { - use super::*; - use crate::utilities::{mta::range_proofs::SampleFromMultiplicativeGroup, BITS_PAILLIER}; - use curv::elliptic::curves::secp256_k1::Secp256k1; - use fs_dkr::ring_pedersen_proof::RingPedersenStatement; - use paillier::{Encrypt, KeyGeneration, Paillier, RawPlaintext}; - use sha2::Sha256; - - #[test] - fn test_mul_star_proof() { - let (ring_pedersen_statement, _witness) = - RingPedersenStatement::::generate(); - let (paillier_key, _) = Paillier::keypair_with_modulus_size(BITS_PAILLIER).keys(); - - let rho: BigInt = BigInt::from_paillier_key(&paillier_key); - - let C: BigInt = - Paillier::encrypt(&paillier_key, RawPlaintext::from(BigInt::from(123))).into(); - let (statement, witness) = - PaillierMultiplicationVersusGroupStatement::::generate( - rho, - C, - ring_pedersen_statement.S, - ring_pedersen_statement.T, - ring_pedersen_statement.N, - paillier_key, - ); - let proof = PaillierMultiplicationVersusGroupProof::::prove( - &witness, &statement, - ); - assert!(PaillierMultiplicationVersusGroupProof::::verify( + use super::*; + use crate::utilities::{ + mta::range_proofs::SampleFromMultiplicativeGroup, BITS_PAILLIER, + }; + use curv::elliptic::curves::secp256_k1::Secp256k1; + use fs_dkr::ring_pedersen_proof::RingPedersenStatement; + use paillier::{Encrypt, KeyGeneration, Paillier, RawPlaintext}; + use sha2::Sha256; + + #[test] + fn test_mul_star_proof() { + let (ring_pedersen_statement, _witness) = + RingPedersenStatement::::generate(); + let (paillier_key, _) = + Paillier::keypair_with_modulus_size(BITS_PAILLIER).keys(); + + let rho: BigInt = BigInt::from_paillier_key(&paillier_key); + + let C: BigInt = Paillier::encrypt( + &paillier_key, + RawPlaintext::from(BigInt::from(123)), + ) + .into(); + let (statement, witness) = PaillierMultiplicationVersusGroupStatement::< + Secp256k1, + Sha256, + >::generate( + rho, + C, + ring_pedersen_statement.S, + ring_pedersen_statement.T, + ring_pedersen_statement.N, + paillier_key, + ); + let proof = + PaillierMultiplicationVersusGroupProof::::prove( + &witness, &statement, + ); + assert!(PaillierMultiplicationVersusGroupProof::::verify( &proof, &statement ) .is_ok()); - } + } } diff --git a/src/utilities/sha2.rs b/src/utilities/sha2.rs index 18273876..4f5528b9 100644 --- a/src/utilities/sha2.rs +++ b/src/utilities/sha2.rs @@ -6,45 +6,49 @@ use serde::{Deserialize, Serialize}; #[derive(Debug, Default, Clone, Serialize, Deserialize)] pub struct Sha256 { - #[serde(skip)] - inner: sha2::Sha256, + #[serde(skip)] + inner: sha2::Sha256, } impl Digest for Sha256 { - type OutputSize = U32; - - fn new() -> Self { - Self { inner: sha2::Sha256::new() } - } - - fn update(&mut self, data: impl AsRef<[u8]>) { - self.inner.update(data); - } - - fn chain(self, data: impl AsRef<[u8]>) -> Self - where - Self: Sized, - { - Self { inner: self.inner.chain(data) } - } - - fn finalize(self) -> Output { - self.inner.finalize() - } - - fn finalize_reset(&mut self) -> Output { - self.inner.finalize_reset() - } - - fn reset(&mut self) { - self.inner.reset(); - } - - fn output_size() -> usize { - sha2::Sha256::output_size() - } - - fn digest(data: &[u8]) -> Output { - sha2::Sha256::digest(data) - } + type OutputSize = U32; + + fn new() -> Self { + Self { + inner: sha2::Sha256::new(), + } + } + + fn update(&mut self, data: impl AsRef<[u8]>) { + self.inner.update(data); + } + + fn chain(self, data: impl AsRef<[u8]>) -> Self + where + Self: Sized, + { + Self { + inner: self.inner.chain(data), + } + } + + fn finalize(self) -> Output { + self.inner.finalize() + } + + fn finalize_reset(&mut self) -> Output { + self.inner.finalize_reset() + } + + fn reset(&mut self) { + self.inner.reset(); + } + + fn output_size() -> usize { + sha2::Sha256::output_size() + } + + fn digest(data: &[u8]) -> Output { + sha2::Sha256::digest(data) + } } diff --git a/src/utilities/zk_pdl/mod.rs b/src/utilities/zk_pdl/mod.rs index bd6bb71c..cfe85291 100644 --- a/src/utilities/zk_pdl/mod.rs +++ b/src/utilities/zk_pdl/mod.rs @@ -1,18 +1,18 @@ #![allow(non_snake_case)] /* - Multi-party ECDSA + Multi-party ECDSA - Copyright 2018 by Kzen Networks + Copyright 2018 by Kzen Networks - This file is part of Multi-party ECDSA library - (https://github.com/KZen-networks/multi-party-ecdsa) + This file is part of Multi-party ECDSA library + (https://github.com/KZen-networks/multi-party-ecdsa) - Multi-party ECDSA is free software: you can redistribute - it and/or modify it under the terms of the GNU General Public - License as published by the Free Software Foundation, either - version 3 of the License, or (at your option) any later version. + Multi-party ECDSA is free software: you can redistribute + it and/or modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, either + version 3 of the License, or (at your option) any later version. - @license GPL-3.0+ + @license GPL-3.0+ */ //! We use the proof as given in protocol 6.1 in https://eprint.iacr.org/2017/552.pdf @@ -23,13 +23,16 @@ use std::ops::Shl; use curv::{ - arithmetic::traits::*, - cryptographic_primitives::commitments::{hash_commitment::HashCommitment, traits::Commitment}, - elliptic::curves::{secp256_k1::Secp256k1, Point, Scalar}, - BigInt, + arithmetic::traits::*, + cryptographic_primitives::commitments::{ + hash_commitment::HashCommitment, traits::Commitment, + }, + elliptic::curves::{secp256_k1::Secp256k1, Point, Scalar}, + BigInt, }; use paillier::{ - Add, Decrypt, DecryptionKey, Encrypt, EncryptionKey, Mul, Paillier, RawCiphertext, RawPlaintext, + Add, Decrypt, DecryptionKey, Encrypt, EncryptionKey, Mul, Paillier, + RawCiphertext, RawPlaintext, }; use serde::{Deserialize, Serialize}; use sha2::Sha256; @@ -38,208 +41,234 @@ use zk_paillier::zkproofs::{IncorrectProof, RangeProofNi}; #[derive(Error, Debug)] pub enum ZkPdlError { - #[error("zk pdl message2 failed")] - Message2, - #[error("zk pdl finalize failed")] - Finalize, + #[error("zk pdl message2 failed")] + Message2, + #[error("zk pdl finalize failed")] + Finalize, } #[derive(Clone)] pub struct PDLStatement { - pub ciphertext: BigInt, - pub ek: EncryptionKey, - pub Q: Point, - pub G: Point, + pub ciphertext: BigInt, + pub ek: EncryptionKey, + pub Q: Point, + pub G: Point, } #[derive(Clone)] pub struct PDLWitness { - pub x: Scalar, - pub r: BigInt, - pub dk: DecryptionKey, + pub x: Scalar, + pub r: BigInt, + pub dk: DecryptionKey, } #[derive(Debug, Clone)] pub struct PDLVerifierState { - pub c_tag: BigInt, - pub c_tag_tag: BigInt, - a: BigInt, - b: BigInt, - blindness: BigInt, - q_tag: Point, - c_hat: BigInt, + pub c_tag: BigInt, + pub c_tag_tag: BigInt, + a: BigInt, + b: BigInt, + blindness: BigInt, + q_tag: Point, + c_hat: BigInt, } #[derive(Debug, Clone)] pub struct PDLProverState { - pub decommit: PDLProverDecommit, - pub alpha: BigInt, + pub decommit: PDLProverDecommit, + pub alpha: BigInt, } #[derive(Debug, Serialize, Deserialize, Clone)] pub struct PDLVerifierFirstMessage { - pub c_tag: BigInt, - pub c_tag_tag: BigInt, + pub c_tag: BigInt, + pub c_tag_tag: BigInt, } #[derive(Debug, Serialize, Deserialize, Clone)] pub struct PDLProverFirstMessage { - pub c_hat: BigInt, - pub range_proof: RangeProofNi, + pub c_hat: BigInt, + pub range_proof: RangeProofNi, } #[derive(Debug, Serialize, Deserialize, Clone)] pub struct PDLVerifierSecondMessage { - pub a: BigInt, - pub b: BigInt, - pub blindness: BigInt, + pub a: BigInt, + pub b: BigInt, + pub blindness: BigInt, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PDLProverDecommit { - pub q_hat: Point, - pub blindness: BigInt, + pub q_hat: Point, + pub blindness: BigInt, } #[derive(Debug, Serialize, Deserialize, Clone)] pub struct PDLProverSecondMessage { - pub decommit: PDLProverDecommit, + pub decommit: PDLProverDecommit, } pub struct Prover {} pub struct Verifier {} impl Verifier { - pub fn message1(statement: &PDLStatement) -> (PDLVerifierFirstMessage, PDLVerifierState) { - let a_fe = Scalar::::random(); - let a = a_fe.to_bigint(); - let q = Scalar::::group_order(); - let q_sq = q.pow(2); - let b = BigInt::sample_below(&q_sq); - let b_fe = Scalar::::from(&b); - let b_enc = Paillier::encrypt(&statement.ek, RawPlaintext::from(b.clone())); - let ac = Paillier::mul( - &statement.ek, - RawCiphertext::from(statement.ciphertext.clone()), - RawPlaintext::from(a.clone()), - ); - let c_tag = Paillier::add(&statement.ek, ac, b_enc).0.into_owned(); - let ab_concat = a.clone() + b.clone().shl(a.bit_length()); - let blindness = BigInt::sample_below(q); - let c_tag_tag = HashCommitment::::create_commitment_with_user_defined_randomness( + pub fn message1( + statement: &PDLStatement, + ) -> (PDLVerifierFirstMessage, PDLVerifierState) { + let a_fe = Scalar::::random(); + let a = a_fe.to_bigint(); + let q = Scalar::::group_order(); + let q_sq = q.pow(2); + let b = BigInt::sample_below(&q_sq); + let b_fe = Scalar::::from(&b); + let b_enc = + Paillier::encrypt(&statement.ek, RawPlaintext::from(b.clone())); + let ac = Paillier::mul( + &statement.ek, + RawCiphertext::from(statement.ciphertext.clone()), + RawPlaintext::from(a.clone()), + ); + let c_tag = Paillier::add(&statement.ek, ac, b_enc).0.into_owned(); + let ab_concat = a.clone() + b.clone().shl(a.bit_length()); + let blindness = BigInt::sample_below(q); + let c_tag_tag = HashCommitment::::create_commitment_with_user_defined_randomness( &ab_concat, &blindness, ); - let q_tag = &statement.Q * &a_fe + &statement.G * b_fe; - - ( - PDLVerifierFirstMessage { c_tag: c_tag.clone(), c_tag_tag: c_tag_tag.clone() }, - PDLVerifierState { c_tag, c_tag_tag, a, b, blindness, q_tag, c_hat: BigInt::zero() }, - ) - } - - pub fn message2( - prover_first_messasge: &PDLProverFirstMessage, - statement: &PDLStatement, - state: &mut PDLVerifierState, - ) -> Result { - let decommit_message = PDLVerifierSecondMessage { - a: state.a.clone(), - b: state.b.clone(), - blindness: state.blindness.clone(), - }; - let range_proof_is_ok = - verify_range_proof(statement, &prover_first_messasge.range_proof).is_ok(); - state.c_hat = prover_first_messasge.c_hat.clone(); - if range_proof_is_ok { - Ok(decommit_message) - } else { - Err(ZkPdlError::Message2) - } - } - - pub fn finalize( - prover_first_message: &PDLProverFirstMessage, - prover_second_message: &PDLProverSecondMessage, - state: &PDLVerifierState, - ) -> Result<(), ZkPdlError> { - let c_hat_test = HashCommitment::::create_commitment_with_user_defined_randomness( + let q_tag = &statement.Q * &a_fe + &statement.G * b_fe; + + ( + PDLVerifierFirstMessage { + c_tag: c_tag.clone(), + c_tag_tag: c_tag_tag.clone(), + }, + PDLVerifierState { + c_tag, + c_tag_tag, + a, + b, + blindness, + q_tag, + c_hat: BigInt::zero(), + }, + ) + } + + pub fn message2( + prover_first_messasge: &PDLProverFirstMessage, + statement: &PDLStatement, + state: &mut PDLVerifierState, + ) -> Result { + let decommit_message = PDLVerifierSecondMessage { + a: state.a.clone(), + b: state.b.clone(), + blindness: state.blindness.clone(), + }; + let range_proof_is_ok = + verify_range_proof(statement, &prover_first_messasge.range_proof) + .is_ok(); + state.c_hat = prover_first_messasge.c_hat.clone(); + if range_proof_is_ok { + Ok(decommit_message) + } else { + Err(ZkPdlError::Message2) + } + } + + pub fn finalize( + prover_first_message: &PDLProverFirstMessage, + prover_second_message: &PDLProverSecondMessage, + state: &PDLVerifierState, + ) -> Result<(), ZkPdlError> { + let c_hat_test = HashCommitment::::create_commitment_with_user_defined_randomness( &BigInt::from_bytes(prover_second_message.decommit.q_hat.to_bytes(true).as_ref()), &prover_second_message.decommit.blindness, ); - if prover_first_message.c_hat == c_hat_test && - prover_second_message.decommit.q_hat == state.q_tag - { - Ok(()) - } else { - Err(ZkPdlError::Finalize) - } - } + if prover_first_message.c_hat == c_hat_test + && prover_second_message.decommit.q_hat == state.q_tag + { + Ok(()) + } else { + Err(ZkPdlError::Finalize) + } + } } impl Prover { - pub fn message1( - witness: &PDLWitness, - statement: &PDLStatement, - verifier_first_message: &PDLVerifierFirstMessage, - ) -> (PDLProverFirstMessage, PDLProverState) { - let c_tag = verifier_first_message.c_tag.clone(); - let alpha = Paillier::decrypt(&witness.dk, &RawCiphertext::from(c_tag)); - let alpha_fe = Scalar::::from(alpha.0.as_ref()); - let q_hat = &statement.G * alpha_fe; - let blindness = BigInt::sample_below(Scalar::::group_order()); - let c_hat = HashCommitment::::create_commitment_with_user_defined_randomness( + pub fn message1( + witness: &PDLWitness, + statement: &PDLStatement, + verifier_first_message: &PDLVerifierFirstMessage, + ) -> (PDLProverFirstMessage, PDLProverState) { + let c_tag = verifier_first_message.c_tag.clone(); + let alpha = Paillier::decrypt(&witness.dk, &RawCiphertext::from(c_tag)); + let alpha_fe = Scalar::::from(alpha.0.as_ref()); + let q_hat = &statement.G * alpha_fe; + let blindness = + BigInt::sample_below(Scalar::::group_order()); + let c_hat = HashCommitment::::create_commitment_with_user_defined_randomness( &BigInt::from_bytes(q_hat.to_bytes(true).as_ref()), &blindness, ); - // in parallel generate range proof: - let range_proof = generate_range_proof(statement, witness); - ( - PDLProverFirstMessage { c_hat, range_proof }, - PDLProverState { - decommit: PDLProverDecommit { blindness, q_hat }, - alpha: alpha.0.into_owned(), - }, - ) - } - - pub fn message2( - verifier_first_message: &PDLVerifierFirstMessage, - verifier_second_message: &PDLVerifierSecondMessage, - witness: &PDLWitness, - state: &PDLProverState, - ) -> Result { - let ab_concat = &verifier_second_message.a + - verifier_second_message.b.clone().shl(verifier_second_message.a.bit_length()); // b|a (in the paper it is a|b) - let c_tag_tag_test = + // in parallel generate range proof: + let range_proof = generate_range_proof(statement, witness); + ( + PDLProverFirstMessage { c_hat, range_proof }, + PDLProverState { + decommit: PDLProverDecommit { blindness, q_hat }, + alpha: alpha.0.into_owned(), + }, + ) + } + + pub fn message2( + verifier_first_message: &PDLVerifierFirstMessage, + verifier_second_message: &PDLVerifierSecondMessage, + witness: &PDLWitness, + state: &PDLProverState, + ) -> Result { + let ab_concat = &verifier_second_message.a + + verifier_second_message + .b + .clone() + .shl(verifier_second_message.a.bit_length()); // b|a (in the paper it is a|b) + let c_tag_tag_test = HashCommitment::::create_commitment_with_user_defined_randomness( &ab_concat, &verifier_second_message.blindness, ); - let ax1 = &verifier_second_message.a * witness.x.to_bigint(); - let alpha_test = ax1 + &verifier_second_message.b; - if alpha_test == state.alpha && verifier_first_message.c_tag_tag == c_tag_tag_test { - Ok(PDLProverSecondMessage { decommit: state.decommit.clone() }) - } else { - Err(ZkPdlError::Message2) - } - } + let ax1 = &verifier_second_message.a * witness.x.to_bigint(); + let alpha_test = ax1 + &verifier_second_message.b; + if alpha_test == state.alpha + && verifier_first_message.c_tag_tag == c_tag_tag_test + { + Ok(PDLProverSecondMessage { + decommit: state.decommit.clone(), + }) + } else { + Err(ZkPdlError::Message2) + } + } } -fn generate_range_proof(statement: &PDLStatement, witness: &PDLWitness) -> RangeProofNi { - RangeProofNi::prove( - &statement.ek, - Scalar::::group_order(), - &statement.ciphertext, - &witness.x.to_bigint(), - &witness.r, - ) +fn generate_range_proof( + statement: &PDLStatement, + witness: &PDLWitness, +) -> RangeProofNi { + RangeProofNi::prove( + &statement.ek, + Scalar::::group_order(), + &statement.ciphertext, + &witness.x.to_bigint(), + &witness.r, + ) } fn verify_range_proof( - statement: &PDLStatement, - range_proof: &RangeProofNi, + statement: &PDLStatement, + range_proof: &RangeProofNi, ) -> Result<(), IncorrectProof> { - range_proof.verify(&statement.ek, &statement.ciphertext) + range_proof.verify(&statement.ek, &statement.ciphertext) } #[cfg(test)] diff --git a/src/utilities/zk_pdl/test.rs b/src/utilities/zk_pdl/test.rs index 0f0aadbd..3e5a223b 100644 --- a/src/utilities/zk_pdl/test.rs +++ b/src/utilities/zk_pdl/test.rs @@ -1,48 +1,64 @@ #![allow(non_snake_case)] use curv::{ - arithmetic::traits::*, - elliptic::curves::{secp256_k1::Secp256k1, Point, Scalar}, - BigInt, + arithmetic::traits::*, + elliptic::curves::{secp256_k1::Secp256k1, Point, Scalar}, + BigInt, }; use paillier::{ - core::Randomness, - traits::{EncryptWithChosenRandomness, KeyGeneration}, - Paillier, RawPlaintext, + core::Randomness, + traits::{EncryptWithChosenRandomness, KeyGeneration}, + Paillier, RawPlaintext, }; use crate::utilities::zk_pdl::{PDLStatement, PDLWitness, Prover, Verifier}; #[test] fn test_zk_pdl() { - // pre-test: + // pre-test: - let (ek, dk) = Paillier::keypair().keys(); - let randomness = Randomness::sample(&ek); - let x = Scalar::::random(); - let x: Scalar = - Scalar::::from(&x.to_bigint().div_floor(&BigInt::from(3))); + let (ek, dk) = Paillier::keypair().keys(); + let randomness = Randomness::sample(&ek); + let x = Scalar::::random(); + let x: Scalar = + Scalar::::from(&x.to_bigint().div_floor(&BigInt::from(3))); - let Q = Point::generator() * &x; + let Q = Point::generator() * &x; - let c = Paillier::encrypt_with_chosen_randomness( - &ek, - RawPlaintext::from(x.to_bigint()), - &randomness, - ) - .0 - .into_owned(); - let statement = PDLStatement { ciphertext: c, ek, Q, G: Point::generator().to_point() }; - let witness = PDLWitness { x, r: randomness.0, dk }; - // - let (verifier_message1, mut verifier_state) = Verifier::message1(&statement); - let (prover_message1, prover_state) = - Prover::message1(&witness, &statement, &verifier_message1); - let verifier_message2 = - Verifier::message2(&prover_message1, &statement, &mut verifier_state).expect(""); - let prover_message2 = - Prover::message2(&verifier_message1, &verifier_message2, &witness, &prover_state) - .expect(""); - let result = Verifier::finalize(&prover_message1, &prover_message2, &verifier_state); - assert!(result.is_ok()); + let c = Paillier::encrypt_with_chosen_randomness( + &ek, + RawPlaintext::from(x.to_bigint()), + &randomness, + ) + .0 + .into_owned(); + let statement = PDLStatement { + ciphertext: c, + ek, + Q, + G: Point::generator().to_point(), + }; + let witness = PDLWitness { + x, + r: randomness.0, + dk, + }; + // + let (verifier_message1, mut verifier_state) = + Verifier::message1(&statement); + let (prover_message1, prover_state) = + Prover::message1(&witness, &statement, &verifier_message1); + let verifier_message2 = + Verifier::message2(&prover_message1, &statement, &mut verifier_state) + .expect(""); + let prover_message2 = Prover::message2( + &verifier_message1, + &verifier_message2, + &witness, + &prover_state, + ) + .expect(""); + let result = + Verifier::finalize(&prover_message1, &prover_message2, &verifier_state); + assert!(result.is_ok()); } diff --git a/src/utilities/zk_pdl_with_slack/mod.rs b/src/utilities/zk_pdl_with_slack/mod.rs index 97181326..dfa737e1 100644 --- a/src/utilities/zk_pdl_with_slack/mod.rs +++ b/src/utilities/zk_pdl_with_slack/mod.rs @@ -1,18 +1,18 @@ #![allow(non_snake_case)] /* - Multi-party ECDSA + Multi-party ECDSA - Copyright 2018 by Kzen Networks + Copyright 2018 by Kzen Networks - This file is part of Multi-party ECDSA library - (https://github.com/KZen-networks/multi-party-ecdsa) + This file is part of Multi-party ECDSA library + (https://github.com/KZen-networks/multi-party-ecdsa) - Multi-party ECDSA is free software: you can redistribute - it and/or modify it under the terms of the GNU General Public - License as published by the Free Software Foundation, either - version 3 of the License, or (at your option) any later version. + Multi-party ECDSA is free software: you can redistribute + it and/or modify it under the terms of the GNU General Public + License as published by the Free Software Foundation, either + version 3 of the License, or (at your option) any later version. - @license GPL-3.0+ + @license GPL-3.0+ */ //! We use the proof as given in proof PIi in https://eprint.iacr.org/2016/013.pdf. @@ -20,13 +20,14 @@ //! //! Statement: (c, pk, Q, G) //! witness (x, r) such that Q = xG, c = Enc(pk, x, r) -//! note that because of the range proof, the proof has a slack in the range: x in [-q^3, q^3] +//! note that because of the range proof, the proof has a slack in the range: x +//! in [-q^3, q^3] use curv::{ - arithmetic::traits::*, - cryptographic_primitives::hashing::{Digest, DigestExt}, - elliptic::curves::{secp256_k1::Secp256k1, Point, Scalar}, - BigInt, + arithmetic::traits::*, + cryptographic_primitives::hashing::{Digest, DigestExt}, + elliptic::curves::{secp256_k1::Secp256k1, Point, Scalar}, + BigInt, }; use paillier::EncryptionKey; use serde::{Deserialize, Serialize}; @@ -35,161 +36,190 @@ use thiserror::Error; #[derive(Error, Debug)] pub enum ZkPdlWithSlackError { - #[error("zk pdl with slack verification failed")] - Verify, + #[error("zk pdl with slack verification failed")] + Verify, } #[derive(Clone, Debug, Serialize, Deserialize)] pub struct PDLwSlackStatement { - pub ciphertext: BigInt, - pub ek: EncryptionKey, - pub Q: Point, - pub G: Point, - pub h1: BigInt, - pub h2: BigInt, - pub N_tilde: BigInt, + pub ciphertext: BigInt, + pub ek: EncryptionKey, + pub Q: Point, + pub G: Point, + pub h1: BigInt, + pub h2: BigInt, + pub N_tilde: BigInt, } #[derive(Clone)] pub struct PDLwSlackWitness { - pub x: Scalar, - pub r: BigInt, + pub x: Scalar, + pub r: BigInt, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PDLwSlackProof { - z: BigInt, - u1: Point, - u2: BigInt, - u3: BigInt, - s1: BigInt, - s2: BigInt, - s3: BigInt, + z: BigInt, + u1: Point, + u2: BigInt, + u3: BigInt, + s1: BigInt, + s2: BigInt, + s3: BigInt, } impl PDLwSlackProof { - pub fn prove(witness: &PDLwSlackWitness, statement: &PDLwSlackStatement) -> Self { - let q3 = Scalar::::group_order().pow(3); - let q_N_tilde = Scalar::::group_order() * &statement.N_tilde; - let q3_N_tilde = &q3 * &statement.N_tilde; - - let alpha = BigInt::sample_below(&q3); - let one = BigInt::one(); - let beta = BigInt::sample_range(&one, &(&statement.ek.n - &one)); - let rho = BigInt::sample_below(&q_N_tilde); - let gamma = BigInt::sample_below(&q3_N_tilde); - - let z = commitment_unknown_order( - &statement.h1, - &statement.h2, - &statement.N_tilde, - &witness.x.to_bigint(), - &rho, - ); - let u1 = &statement.G * &Scalar::::from(&alpha); - let u2 = commitment_unknown_order( - &(&statement.ek.n + BigInt::one()), - &beta, - &statement.ek.nn, - &alpha, - &statement.ek.n, - ); - let u3 = commitment_unknown_order( - &statement.h1, - &statement.h2, - &statement.N_tilde, - &alpha, - &gamma, - ); - - let e = Sha256::new() - .chain_bigint(&BigInt::from_bytes(statement.G.to_bytes(true).as_ref())) - .chain_bigint(&BigInt::from_bytes(statement.Q.to_bytes(true).as_ref())) - .chain_bigint(&statement.ciphertext) - .chain_bigint(&z) - .chain_bigint(&BigInt::from_bytes(u1.to_bytes(true).as_ref())) - .chain_bigint(&u2) - .chain_bigint(&u3) - .result_bigint(); - - let s1 = &e * witness.x.to_bigint() + alpha; - let s2 = commitment_unknown_order(&witness.r, &beta, &statement.ek.n, &e, &BigInt::one()); - let s3 = &e * rho + gamma; - - PDLwSlackProof { z, u1, u2, u3, s1, s2, s3 } - } - - pub fn verify(&self, statement: &PDLwSlackStatement) -> Result<(), ZkPdlWithSlackError> { - let e = Sha256::new() - .chain_bigint(&BigInt::from_bytes(statement.G.to_bytes(true).as_ref())) - .chain_bigint(&BigInt::from_bytes(statement.Q.to_bytes(true).as_ref())) - .chain_bigint(&statement.ciphertext) - .chain_bigint(&self.z) - .chain_bigint(&BigInt::from_bytes(self.u1.to_bytes(true).as_ref())) - .chain_bigint(&self.u2) - .chain_bigint(&self.u3) - .result_bigint(); - - let g_s1 = statement.G.clone() * &Scalar::::from(&self.s1); - let e_fe_neg: Scalar = - Scalar::::from(&(Scalar::::group_order() - &e)); - let y_minus_e = &statement.Q * &e_fe_neg; - let u1_test = g_s1 + y_minus_e; - - let u2_test_tmp = commitment_unknown_order( - &(&statement.ek.n + BigInt::one()), - &self.s2, - &statement.ek.nn, - &self.s1, - &statement.ek.n, - ); - let u2_test = commitment_unknown_order( - &u2_test_tmp, - &statement.ciphertext, - &statement.ek.nn, - &BigInt::one(), - &(-&e), - ); - - let u3_test_tmp = commitment_unknown_order( - &statement.h1, - &statement.h2, - &statement.N_tilde, - &self.s1, - &self.s3, - ); - let u3_test = commitment_unknown_order( - &u3_test_tmp, - &self.z, - &statement.N_tilde, - &BigInt::one(), - &(-&e), - ); - - if self.u1 == u1_test && self.u2 == u2_test && self.u3 == u3_test { - Ok(()) - } else { - Err(ZkPdlWithSlackError::Verify) - } - } + pub fn prove( + witness: &PDLwSlackWitness, + statement: &PDLwSlackStatement, + ) -> Self { + let q3 = Scalar::::group_order().pow(3); + let q_N_tilde = Scalar::::group_order() * &statement.N_tilde; + let q3_N_tilde = &q3 * &statement.N_tilde; + + let alpha = BigInt::sample_below(&q3); + let one = BigInt::one(); + let beta = BigInt::sample_range(&one, &(&statement.ek.n - &one)); + let rho = BigInt::sample_below(&q_N_tilde); + let gamma = BigInt::sample_below(&q3_N_tilde); + + let z = commitment_unknown_order( + &statement.h1, + &statement.h2, + &statement.N_tilde, + &witness.x.to_bigint(), + &rho, + ); + let u1 = &statement.G * &Scalar::::from(&alpha); + let u2 = commitment_unknown_order( + &(&statement.ek.n + BigInt::one()), + &beta, + &statement.ek.nn, + &alpha, + &statement.ek.n, + ); + let u3 = commitment_unknown_order( + &statement.h1, + &statement.h2, + &statement.N_tilde, + &alpha, + &gamma, + ); + + let e = Sha256::new() + .chain_bigint(&BigInt::from_bytes( + statement.G.to_bytes(true).as_ref(), + )) + .chain_bigint(&BigInt::from_bytes( + statement.Q.to_bytes(true).as_ref(), + )) + .chain_bigint(&statement.ciphertext) + .chain_bigint(&z) + .chain_bigint(&BigInt::from_bytes(u1.to_bytes(true).as_ref())) + .chain_bigint(&u2) + .chain_bigint(&u3) + .result_bigint(); + + let s1 = &e * witness.x.to_bigint() + alpha; + let s2 = commitment_unknown_order( + &witness.r, + &beta, + &statement.ek.n, + &e, + &BigInt::one(), + ); + let s3 = &e * rho + gamma; + + PDLwSlackProof { + z, + u1, + u2, + u3, + s1, + s2, + s3, + } + } + + pub fn verify( + &self, + statement: &PDLwSlackStatement, + ) -> Result<(), ZkPdlWithSlackError> { + let e = Sha256::new() + .chain_bigint(&BigInt::from_bytes( + statement.G.to_bytes(true).as_ref(), + )) + .chain_bigint(&BigInt::from_bytes( + statement.Q.to_bytes(true).as_ref(), + )) + .chain_bigint(&statement.ciphertext) + .chain_bigint(&self.z) + .chain_bigint(&BigInt::from_bytes(self.u1.to_bytes(true).as_ref())) + .chain_bigint(&self.u2) + .chain_bigint(&self.u3) + .result_bigint(); + + let g_s1 = statement.G.clone() * &Scalar::::from(&self.s1); + let e_fe_neg: Scalar = Scalar::::from( + &(Scalar::::group_order() - &e), + ); + let y_minus_e = &statement.Q * &e_fe_neg; + let u1_test = g_s1 + y_minus_e; + + let u2_test_tmp = commitment_unknown_order( + &(&statement.ek.n + BigInt::one()), + &self.s2, + &statement.ek.nn, + &self.s1, + &statement.ek.n, + ); + let u2_test = commitment_unknown_order( + &u2_test_tmp, + &statement.ciphertext, + &statement.ek.nn, + &BigInt::one(), + &(-&e), + ); + + let u3_test_tmp = commitment_unknown_order( + &statement.h1, + &statement.h2, + &statement.N_tilde, + &self.s1, + &self.s3, + ); + let u3_test = commitment_unknown_order( + &u3_test_tmp, + &self.z, + &statement.N_tilde, + &BigInt::one(), + &(-&e), + ); + + if self.u1 == u1_test && self.u2 == u2_test && self.u3 == u3_test { + Ok(()) + } else { + Err(ZkPdlWithSlackError::Verify) + } + } } pub fn commitment_unknown_order( - h1: &BigInt, - h2: &BigInt, - N_tilde: &BigInt, - x: &BigInt, - r: &BigInt, + h1: &BigInt, + h2: &BigInt, + N_tilde: &BigInt, + x: &BigInt, + r: &BigInt, ) -> BigInt { - let h1_x = BigInt::mod_pow(h1, x, N_tilde); - let h2_r = { - if r < &BigInt::zero() { - let h2_inv = BigInt::mod_inv(h2, N_tilde).unwrap(); - BigInt::mod_pow(&h2_inv, &(-r), N_tilde) - } else { - BigInt::mod_pow(h2, r, N_tilde) - } - }; - BigInt::mod_mul(&h1_x, &h2_r, N_tilde) + let h1_x = BigInt::mod_pow(h1, x, N_tilde); + let h2_r = { + if r < &BigInt::zero() { + let h2_inv = BigInt::mod_inv(h2, N_tilde).unwrap(); + BigInt::mod_pow(&h2_inv, &(-r), N_tilde) + } else { + BigInt::mod_pow(h2, r, N_tilde) + } + }; + BigInt::mod_mul(&h1_x, &h2_r, N_tilde) } #[cfg(test)] diff --git a/src/utilities/zk_pdl_with_slack/test.rs b/src/utilities/zk_pdl_with_slack/test.rs index 66442731..227bb4f3 100644 --- a/src/utilities/zk_pdl_with_slack/test.rs +++ b/src/utilities/zk_pdl_with_slack/test.rs @@ -1,124 +1,134 @@ #![allow(non_snake_case)] use crate::utilities::zk_pdl_with_slack::*; use curv::{ - elliptic::curves::{secp256_k1::Secp256k1, Point, Scalar}, - BigInt, + elliptic::curves::{secp256_k1::Secp256k1, Point, Scalar}, + BigInt, }; use paillier::{ - core::Randomness, - traits::{EncryptWithChosenRandomness, KeyGeneration}, - Paillier, RawPlaintext, + core::Randomness, + traits::{EncryptWithChosenRandomness, KeyGeneration}, + Paillier, RawPlaintext, }; use zk_paillier::zkproofs::{CompositeDLogProof, DLogStatement}; #[test] fn test_zk_pdl_with_slack() { - // N_tilde, h1, h2 generation - let (ek_tilde, dk_tilde) = Paillier::keypair().keys(); - // note: safe primes should be used: - // let (ek_tilde, dk_tilde) = Paillier::keypair_safe_primes().keys(); - let one = BigInt::one(); - let phi = (&dk_tilde.p - &one) * (&dk_tilde.q - &one); - let h1 = BigInt::sample_below(&phi); - let S = BigInt::from(2).pow(256_u32); - let xhi = BigInt::sample_below(&S); - let h1_inv = BigInt::mod_inv(&h1, &ek_tilde.n).unwrap(); - let h2 = BigInt::mod_pow(&h1_inv, &xhi, &ek_tilde.n); - let statement = DLogStatement { N: ek_tilde.n.clone(), g: h1.clone(), ni: h2.clone() }; - - let composite_dlog_proof = CompositeDLogProof::prove(&statement, &xhi); - - // generate the scalar secret and Paillier encrypt it - let (ek, _dk) = Paillier::keypair().keys(); - // note: safe primes should be used here as well: - // let (ek_tilde, dk_tilde) = Paillier::keypair_safe_primes().keys(); - let randomness = Randomness::sample(&ek); - let x = Scalar::::random(); - - let Q = Point::generator() * &x; - - let c = Paillier::encrypt_with_chosen_randomness( - &ek, - RawPlaintext::from(x.to_bigint()), - &randomness, - ) - .0 - .into_owned(); - - // Generate PDL with slack statement, witness and proof - let pdl_w_slack_statement = PDLwSlackStatement { - ciphertext: c, - ek, - Q, - G: Point::generator().to_point(), - h1, - h2, - N_tilde: ek_tilde.n, - }; - - let pdl_w_slack_witness = PDLwSlackWitness { x, r: randomness.0 }; - - let proof = PDLwSlackProof::prove(&pdl_w_slack_witness, &pdl_w_slack_statement); - // verify h1,h2, N_tilde - let setup_result = composite_dlog_proof.verify(&statement); - assert!(setup_result.is_ok()); - let result = proof.verify(&pdl_w_slack_statement); - assert!(result.is_ok()); + // N_tilde, h1, h2 generation + let (ek_tilde, dk_tilde) = Paillier::keypair().keys(); + // note: safe primes should be used: + // let (ek_tilde, dk_tilde) = Paillier::keypair_safe_primes().keys(); + let one = BigInt::one(); + let phi = (&dk_tilde.p - &one) * (&dk_tilde.q - &one); + let h1 = BigInt::sample_below(&phi); + let S = BigInt::from(2).pow(256_u32); + let xhi = BigInt::sample_below(&S); + let h1_inv = BigInt::mod_inv(&h1, &ek_tilde.n).unwrap(); + let h2 = BigInt::mod_pow(&h1_inv, &xhi, &ek_tilde.n); + let statement = DLogStatement { + N: ek_tilde.n.clone(), + g: h1.clone(), + ni: h2.clone(), + }; + + let composite_dlog_proof = CompositeDLogProof::prove(&statement, &xhi); + + // generate the scalar secret and Paillier encrypt it + let (ek, _dk) = Paillier::keypair().keys(); + // note: safe primes should be used here as well: + // let (ek_tilde, dk_tilde) = Paillier::keypair_safe_primes().keys(); + let randomness = Randomness::sample(&ek); + let x = Scalar::::random(); + + let Q = Point::generator() * &x; + + let c = Paillier::encrypt_with_chosen_randomness( + &ek, + RawPlaintext::from(x.to_bigint()), + &randomness, + ) + .0 + .into_owned(); + + // Generate PDL with slack statement, witness and proof + let pdl_w_slack_statement = PDLwSlackStatement { + ciphertext: c, + ek, + Q, + G: Point::generator().to_point(), + h1, + h2, + N_tilde: ek_tilde.n, + }; + + let pdl_w_slack_witness = PDLwSlackWitness { x, r: randomness.0 }; + + let proof = + PDLwSlackProof::prove(&pdl_w_slack_witness, &pdl_w_slack_statement); + // verify h1,h2, N_tilde + let setup_result = composite_dlog_proof.verify(&statement); + assert!(setup_result.is_ok()); + let result = proof.verify(&pdl_w_slack_statement); + assert!(result.is_ok()); } #[test] #[should_panic] fn test_zk_pdl_with_slack_soundness() { - // N_tilde, h1, h2 generation - let (ek_tilde, dk_tilde) = Paillier::keypair().keys(); - // note: safe primes should be used: - // let (ek_tilde, dk_tilde) = Paillier::keypair_safe_primes().keys(); - let one = BigInt::one(); - let phi = (&dk_tilde.p - &one) * (&dk_tilde.q - &one); - let h1 = BigInt::sample_below(&phi); - let S = BigInt::from(2).pow(256_u32); - let xhi = BigInt::sample_below(&S); - let h1_inv = BigInt::mod_inv(&h1, &ek_tilde.n).unwrap(); - let h2 = BigInt::mod_pow(&h1_inv, &xhi, &ek_tilde.n); - let statement = DLogStatement { N: ek_tilde.n.clone(), g: h1.clone(), ni: h2.clone() }; - - let composite_dlog_proof = CompositeDLogProof::prove(&statement, &xhi); - - // generate the scalar secret and Paillier encrypt it - let (ek, _dk) = Paillier::keypair().keys(); - // note: safe primes should be used here as well: - // let (ek_tilde, dk_tilde) = Paillier::keypair_safe_primes().keys(); - let randomness = Randomness::sample(&ek); - let x = Scalar::::random(); - - let Q = Point::generator() * &x; - - // here we encrypt x + 1 instead of x: - let c = Paillier::encrypt_with_chosen_randomness( - &ek, - RawPlaintext::from(x.to_bigint() + BigInt::one()), - &randomness, - ) - .0 - .into_owned(); - - // Generate PDL with slack statement, witness and proof - let pdl_w_slack_statement = PDLwSlackStatement { - ciphertext: c, - ek, - Q, - G: Point::generator().to_point(), - h1, - h2, - N_tilde: ek_tilde.n, - }; - - let pdl_w_slack_witness = PDLwSlackWitness { x, r: randomness.0 }; - - let proof = PDLwSlackProof::prove(&pdl_w_slack_witness, &pdl_w_slack_statement); - // verify h1,h2, N_tilde - let setup_result = composite_dlog_proof.verify(&statement); - assert!(setup_result.is_ok()); - let result = proof.verify(&pdl_w_slack_statement); - assert!(result.is_ok()); + // N_tilde, h1, h2 generation + let (ek_tilde, dk_tilde) = Paillier::keypair().keys(); + // note: safe primes should be used: + // let (ek_tilde, dk_tilde) = Paillier::keypair_safe_primes().keys(); + let one = BigInt::one(); + let phi = (&dk_tilde.p - &one) * (&dk_tilde.q - &one); + let h1 = BigInt::sample_below(&phi); + let S = BigInt::from(2).pow(256_u32); + let xhi = BigInt::sample_below(&S); + let h1_inv = BigInt::mod_inv(&h1, &ek_tilde.n).unwrap(); + let h2 = BigInt::mod_pow(&h1_inv, &xhi, &ek_tilde.n); + let statement = DLogStatement { + N: ek_tilde.n.clone(), + g: h1.clone(), + ni: h2.clone(), + }; + + let composite_dlog_proof = CompositeDLogProof::prove(&statement, &xhi); + + // generate the scalar secret and Paillier encrypt it + let (ek, _dk) = Paillier::keypair().keys(); + // note: safe primes should be used here as well: + // let (ek_tilde, dk_tilde) = Paillier::keypair_safe_primes().keys(); + let randomness = Randomness::sample(&ek); + let x = Scalar::::random(); + + let Q = Point::generator() * &x; + + // here we encrypt x + 1 instead of x: + let c = Paillier::encrypt_with_chosen_randomness( + &ek, + RawPlaintext::from(x.to_bigint() + BigInt::one()), + &randomness, + ) + .0 + .into_owned(); + + // Generate PDL with slack statement, witness and proof + let pdl_w_slack_statement = PDLwSlackStatement { + ciphertext: c, + ek, + Q, + G: Point::generator().to_point(), + h1, + h2, + N_tilde: ek_tilde.n, + }; + + let pdl_w_slack_witness = PDLwSlackWitness { x, r: randomness.0 }; + + let proof = + PDLwSlackProof::prove(&pdl_w_slack_witness, &pdl_w_slack_statement); + // verify h1,h2, N_tilde + let setup_result = composite_dlog_proof.verify(&statement); + assert!(setup_result.is_ok()); + let result = proof.verify(&pdl_w_slack_statement); + assert!(result.is_ok()); }