├── .github ├── CODEOWNERS └── workflows │ ├── audit.yml │ ├── publish.yml │ ├── readme.yml │ └── rust.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── Makefile ├── README.md ├── docs └── readme.tpl ├── examples └── random-generation-protocol │ ├── Cargo.toml │ └── src │ └── lib.rs ├── round-based-derive ├── Cargo.toml └── src │ └── lib.rs ├── round-based-tests ├── Cargo.toml ├── src │ └── lib.rs └── tests │ └── rounds.rs └── round-based ├── .gitignore ├── Cargo.toml ├── README.md ├── src ├── _docs.rs ├── delivery.rs ├── lib.rs ├── party.rs ├── rounds_router │ ├── mod.rs │ ├── simple_store.rs │ └── store.rs ├── runtime.rs └── simulation.rs └── tests ├── derive.rs └── derive ├── compile-fail ├── wrong_usage.rs └── wrong_usage.stderr └── compile-pass └── correct_usage.rs /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @survived 2 | -------------------------------------------------------------------------------- /.github/workflows/audit.yml: -------------------------------------------------------------------------------- 1 | name: Security audit 2 | on: 3 | pull_request: 4 | branches: [ "*" ] 5 | paths: 6 | - '**/Cargo.toml' 7 | - '**/Cargo.lock' 8 | jobs: 9 | security_audit: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v3 13 | - uses: rustsec/audit-check@v1.4.1 14 | with: 15 | token: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | tags: 4 | - 'v*' 5 | - 'derive-v*' 6 | workflow_dispatch: 7 | 8 | name: Publish 9 | 10 | env: 11 | CARGO_TERM_COLOR: always 12 | CARGO_NET_GIT_FETCH_WITH_CLI: true 13 | 14 | jobs: 15 | publish-round-based: 16 | name: Publish round-based 17 | environment: crates.io 18 | runs-on: ubuntu-latest 19 | if: >- 20 | github.ref_type == 'tag' 21 | && startsWith(github.ref_name, 'v') 22 | steps: 23 | - uses: actions/checkout@v3 24 | - run: cargo publish -p round-based --token ${CRATES_TOKEN} 25 | env: 26 | CRATES_TOKEN: ${{ secrets.CRATES_TOKEN }} 27 | publish-derive: 28 | name: Publish round-based-derive 29 | environment: crates.io 30 | runs-on: ubuntu-latest 31 | if: >- 32 | github.ref_type == 'tag' 33 | && startsWith(github.ref_name, 'derive-v') 34 | steps: 35 | - uses: actions/checkout@v3 36 | - run: cargo publish -p round-based-derive --token ${CRATES_TOKEN} 37 | env: 38 | CRATES_TOKEN: ${{ secrets.CRATES_TOKEN }} -------------------------------------------------------------------------------- /.github/workflows/readme.yml: -------------------------------------------------------------------------------- 1 | name: Check README 2 | 3 | on: 4 | pull_request: 5 | branches: [ "*" ] 6 | 7 | env: 8 | CARGO_TERM_COLOR: always 9 | CARGO_NET_GIT_FETCH_WITH_CLI: true 10 | 11 | jobs: 12 | check_readme: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v3 16 | - name: Install cargo-hakari 17 | uses: baptiste0928/cargo-install@v1 18 | with: 19 | crate: cargo-readme 20 | - name: Check that readme matches lib.rs 21 | run: | 22 | cp README.md README-copy.md 23 | make readme 24 | diff README.md README-copy.md -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | pull_request: 5 | branches: [ "*" ] 6 | 7 | env: 8 | CARGO_TERM_COLOR: always 9 | CARGO_NET_GIT_FETCH_WITH_CLI: true 10 | 11 | jobs: 12 | build-and-test: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v3 16 | - uses: Swatinem/rust-cache@v2 17 | with: 18 | cache-on-failure: "true" 19 | - name: Build no features 20 | run: cargo build -p round-based 21 | - name: Build with tokio 22 | run: cargo build -p round-based --features runtime-tokio 23 | - name: Build with all features 24 | run: cargo build -p round-based --all-features 25 | - name: Run tests 26 | run: cargo test --all-features 27 | check-fmt: 28 | runs-on: ubuntu-latest 29 | steps: 30 | - uses: actions/checkout@v3 31 | - name: Check formatting 32 | run: cargo fmt --all -- --check 33 | check-docs: 34 | runs-on: ubuntu-latest 35 | steps: 36 | - uses: actions/checkout@v3 37 | - uses: Swatinem/rust-cache@v2 38 | with: 39 | cache-on-failure: "true" 40 | - name: Check docs 41 | run: RUSTDOCFLAGS="-D warnings" cargo doc --no-deps --all-features 42 | clippy: 43 | runs-on: ubuntu-latest 44 | steps: 45 | - uses: actions/checkout@v3 46 | - uses: Swatinem/rust-cache@v2 47 | with: 48 | cache-on-failure: "true" 49 | - name: Run clippy 50 | run: cargo clippy --all --lib 51 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | .idea 3 | 4 | .cargo/ 5 | 6 | .helix/ 7 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.21.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler" 16 | version = "1.0.2" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 19 | 20 | [[package]] 21 | name = "anyhow" 22 | version = "1.0.75" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" 25 | 26 | [[package]] 27 | name = "autocfg" 28 | version = "1.1.0" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 31 | 32 | [[package]] 33 | name = "backtrace" 34 | version = "0.3.69" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" 37 | dependencies = [ 38 | "addr2line", 39 | "cc", 40 | "cfg-if", 41 | "libc", 42 | "miniz_oxide", 43 | "object", 44 | "rustc-demangle", 45 | ] 46 | 47 | [[package]] 48 | name = "basic-toml" 49 | version = "0.1.7" 50 | source = "registry+https://github.com/rust-lang/crates.io-index" 51 | checksum = "2f2139706359229bfa8f19142ac1155b4b80beafb7a60471ac5dd109d4a19778" 52 | dependencies = [ 53 | "serde", 54 | ] 55 | 56 | [[package]] 57 | name = "block-buffer" 58 | version = "0.9.0" 59 | source = "registry+https://github.com/rust-lang/crates.io-index" 60 | checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" 61 | dependencies = [ 62 | "generic-array", 63 | ] 64 | 65 | [[package]] 66 | name = "block-buffer" 67 | version = "0.10.4" 68 | source = "registry+https://github.com/rust-lang/crates.io-index" 69 | checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" 70 | dependencies = [ 71 | "generic-array", 72 | ] 73 | 74 | [[package]] 75 | name = "bumpalo" 76 | version = "3.14.0" 77 | source = "registry+https://github.com/rust-lang/crates.io-index" 78 | checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" 79 | 80 | [[package]] 81 | name = "bytes" 82 | version = "1.5.0" 83 | source = "registry+https://github.com/rust-lang/crates.io-index" 84 | checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" 85 | 86 | [[package]] 87 | name = "cc" 88 | version = "1.0.83" 89 | source = "registry+https://github.com/rust-lang/crates.io-index" 90 | checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" 91 | dependencies = [ 92 | "libc", 93 | ] 94 | 95 | [[package]] 96 | name = "cfg-if" 97 | version = "1.0.0" 98 | source = "registry+https://github.com/rust-lang/crates.io-index" 99 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 100 | 101 | [[package]] 102 | name = "cpufeatures" 103 | version = "0.2.11" 104 | source = "registry+https://github.com/rust-lang/crates.io-index" 105 | checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0" 106 | dependencies = [ 107 | "libc", 108 | ] 109 | 110 | [[package]] 111 | name = "crypto-common" 112 | version = "0.1.6" 113 | source = "registry+https://github.com/rust-lang/crates.io-index" 114 | checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 115 | dependencies = [ 116 | "generic-array", 117 | "typenum", 118 | ] 119 | 120 | [[package]] 121 | name = "digest" 122 | version = "0.9.0" 123 | source = "registry+https://github.com/rust-lang/crates.io-index" 124 | checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" 125 | dependencies = [ 126 | "generic-array", 127 | ] 128 | 129 | [[package]] 130 | name = "digest" 131 | version = "0.10.7" 132 | source = "registry+https://github.com/rust-lang/crates.io-index" 133 | checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" 134 | dependencies = [ 135 | "block-buffer 0.10.4", 136 | "crypto-common", 137 | ] 138 | 139 | [[package]] 140 | name = "educe" 141 | version = "0.4.23" 142 | source = "registry+https://github.com/rust-lang/crates.io-index" 143 | checksum = "0f0042ff8246a363dbe77d2ceedb073339e85a804b9a47636c6e016a9a32c05f" 144 | dependencies = [ 145 | "enum-ordinalize", 146 | "proc-macro2", 147 | "quote", 148 | "syn 1.0.109", 149 | ] 150 | 151 | [[package]] 152 | name = "enum-ordinalize" 153 | version = "3.1.15" 154 | source = "registry+https://github.com/rust-lang/crates.io-index" 155 | checksum = "1bf1fa3f06bbff1ea5b1a9c7b14aa992a39657db60a2759457328d7e058f49ee" 156 | dependencies = [ 157 | "num-bigint", 158 | "num-traits", 159 | "proc-macro2", 160 | "quote", 161 | "syn 2.0.39", 162 | ] 163 | 164 | [[package]] 165 | name = "form_urlencoded" 166 | version = "1.2.1" 167 | source = "registry+https://github.com/rust-lang/crates.io-index" 168 | checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" 169 | dependencies = [ 170 | "percent-encoding", 171 | ] 172 | 173 | [[package]] 174 | name = "futures" 175 | version = "0.3.29" 176 | source = "registry+https://github.com/rust-lang/crates.io-index" 177 | checksum = "da0290714b38af9b4a7b094b8a37086d1b4e61f2df9122c3cad2577669145335" 178 | dependencies = [ 179 | "futures-channel", 180 | "futures-core", 181 | "futures-executor", 182 | "futures-io", 183 | "futures-sink", 184 | "futures-task", 185 | "futures-util", 186 | ] 187 | 188 | [[package]] 189 | name = "futures-channel" 190 | version = "0.3.29" 191 | source = "registry+https://github.com/rust-lang/crates.io-index" 192 | checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb" 193 | dependencies = [ 194 | "futures-core", 195 | "futures-sink", 196 | ] 197 | 198 | [[package]] 199 | name = "futures-core" 200 | version = "0.3.29" 201 | source = "registry+https://github.com/rust-lang/crates.io-index" 202 | checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c" 203 | 204 | [[package]] 205 | name = "futures-executor" 206 | version = "0.3.29" 207 | source = "registry+https://github.com/rust-lang/crates.io-index" 208 | checksum = "0f4fb8693db0cf099eadcca0efe2a5a22e4550f98ed16aba6c48700da29597bc" 209 | dependencies = [ 210 | "futures-core", 211 | "futures-task", 212 | "futures-util", 213 | ] 214 | 215 | [[package]] 216 | name = "futures-io" 217 | version = "0.3.29" 218 | source = "registry+https://github.com/rust-lang/crates.io-index" 219 | checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa" 220 | 221 | [[package]] 222 | name = "futures-macro" 223 | version = "0.3.29" 224 | source = "registry+https://github.com/rust-lang/crates.io-index" 225 | checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" 226 | dependencies = [ 227 | "proc-macro2", 228 | "quote", 229 | "syn 2.0.39", 230 | ] 231 | 232 | [[package]] 233 | name = "futures-sink" 234 | version = "0.3.29" 235 | source = "registry+https://github.com/rust-lang/crates.io-index" 236 | checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817" 237 | 238 | [[package]] 239 | name = "futures-task" 240 | version = "0.3.29" 241 | source = "registry+https://github.com/rust-lang/crates.io-index" 242 | checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2" 243 | 244 | [[package]] 245 | name = "futures-util" 246 | version = "0.3.29" 247 | source = "registry+https://github.com/rust-lang/crates.io-index" 248 | checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104" 249 | dependencies = [ 250 | "futures-channel", 251 | "futures-core", 252 | "futures-io", 253 | "futures-macro", 254 | "futures-sink", 255 | "futures-task", 256 | "memchr", 257 | "pin-project-lite", 258 | "pin-utils", 259 | "slab", 260 | ] 261 | 262 | [[package]] 263 | name = "generic-array" 264 | version = "0.14.7" 265 | source = "registry+https://github.com/rust-lang/crates.io-index" 266 | checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" 267 | dependencies = [ 268 | "serde", 269 | "typenum", 270 | "version_check", 271 | ] 272 | 273 | [[package]] 274 | name = "getrandom" 275 | version = "0.2.11" 276 | source = "registry+https://github.com/rust-lang/crates.io-index" 277 | checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" 278 | dependencies = [ 279 | "cfg-if", 280 | "libc", 281 | "wasi", 282 | ] 283 | 284 | [[package]] 285 | name = "gimli" 286 | version = "0.28.0" 287 | source = "registry+https://github.com/rust-lang/crates.io-index" 288 | checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" 289 | 290 | [[package]] 291 | name = "glob" 292 | version = "0.3.1" 293 | source = "registry+https://github.com/rust-lang/crates.io-index" 294 | checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" 295 | 296 | [[package]] 297 | name = "hex" 298 | version = "0.4.3" 299 | source = "registry+https://github.com/rust-lang/crates.io-index" 300 | checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" 301 | 302 | [[package]] 303 | name = "hex-literal" 304 | version = "0.3.4" 305 | source = "registry+https://github.com/rust-lang/crates.io-index" 306 | checksum = "7ebdb29d2ea9ed0083cd8cece49bbd968021bd99b0849edb4a9a7ee0fdf6a4e0" 307 | 308 | [[package]] 309 | name = "idna" 310 | version = "0.5.0" 311 | source = "registry+https://github.com/rust-lang/crates.io-index" 312 | checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" 313 | dependencies = [ 314 | "unicode-bidi", 315 | "unicode-normalization", 316 | ] 317 | 318 | [[package]] 319 | name = "itoa" 320 | version = "1.0.9" 321 | source = "registry+https://github.com/rust-lang/crates.io-index" 322 | checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" 323 | 324 | [[package]] 325 | name = "js-sys" 326 | version = "0.3.65" 327 | source = "registry+https://github.com/rust-lang/crates.io-index" 328 | checksum = "54c0c35952f67de54bb584e9fd912b3023117cbafc0a77d8f3dee1fb5f572fe8" 329 | dependencies = [ 330 | "wasm-bindgen", 331 | ] 332 | 333 | [[package]] 334 | name = "libc" 335 | version = "0.2.150" 336 | source = "registry+https://github.com/rust-lang/crates.io-index" 337 | checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" 338 | 339 | [[package]] 340 | name = "log" 341 | version = "0.4.20" 342 | source = "registry+https://github.com/rust-lang/crates.io-index" 343 | checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" 344 | 345 | [[package]] 346 | name = "matches" 347 | version = "0.1.10" 348 | source = "registry+https://github.com/rust-lang/crates.io-index" 349 | checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" 350 | 351 | [[package]] 352 | name = "memchr" 353 | version = "2.6.4" 354 | source = "registry+https://github.com/rust-lang/crates.io-index" 355 | checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" 356 | 357 | [[package]] 358 | name = "miniz_oxide" 359 | version = "0.7.1" 360 | source = "registry+https://github.com/rust-lang/crates.io-index" 361 | checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" 362 | dependencies = [ 363 | "adler", 364 | ] 365 | 366 | [[package]] 367 | name = "mio" 368 | version = "0.8.9" 369 | source = "registry+https://github.com/rust-lang/crates.io-index" 370 | checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0" 371 | dependencies = [ 372 | "libc", 373 | "wasi", 374 | "windows-sys", 375 | ] 376 | 377 | [[package]] 378 | name = "num-bigint" 379 | version = "0.4.4" 380 | source = "registry+https://github.com/rust-lang/crates.io-index" 381 | checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" 382 | dependencies = [ 383 | "autocfg", 384 | "num-integer", 385 | "num-traits", 386 | ] 387 | 388 | [[package]] 389 | name = "num-integer" 390 | version = "0.1.45" 391 | source = "registry+https://github.com/rust-lang/crates.io-index" 392 | checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" 393 | dependencies = [ 394 | "autocfg", 395 | "num-traits", 396 | ] 397 | 398 | [[package]] 399 | name = "num-traits" 400 | version = "0.2.17" 401 | source = "registry+https://github.com/rust-lang/crates.io-index" 402 | checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" 403 | dependencies = [ 404 | "autocfg", 405 | ] 406 | 407 | [[package]] 408 | name = "object" 409 | version = "0.32.1" 410 | source = "registry+https://github.com/rust-lang/crates.io-index" 411 | checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" 412 | dependencies = [ 413 | "memchr", 414 | ] 415 | 416 | [[package]] 417 | name = "once_cell" 418 | version = "1.18.0" 419 | source = "registry+https://github.com/rust-lang/crates.io-index" 420 | checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" 421 | 422 | [[package]] 423 | name = "opaque-debug" 424 | version = "0.3.0" 425 | source = "registry+https://github.com/rust-lang/crates.io-index" 426 | checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" 427 | 428 | [[package]] 429 | name = "percent-encoding" 430 | version = "2.3.1" 431 | source = "registry+https://github.com/rust-lang/crates.io-index" 432 | checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" 433 | 434 | [[package]] 435 | name = "phantom-type" 436 | version = "0.3.1" 437 | source = "registry+https://github.com/rust-lang/crates.io-index" 438 | checksum = "f710afd11c9711b04f97ab61bb9747d5a04562fdf0f9f44abc3de92490084982" 439 | dependencies = [ 440 | "educe", 441 | ] 442 | 443 | [[package]] 444 | name = "pin-project-lite" 445 | version = "0.2.13" 446 | source = "registry+https://github.com/rust-lang/crates.io-index" 447 | checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" 448 | 449 | [[package]] 450 | name = "pin-utils" 451 | version = "0.1.0" 452 | source = "registry+https://github.com/rust-lang/crates.io-index" 453 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 454 | 455 | [[package]] 456 | name = "ppv-lite86" 457 | version = "0.2.17" 458 | source = "registry+https://github.com/rust-lang/crates.io-index" 459 | checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" 460 | 461 | [[package]] 462 | name = "proc-macro2" 463 | version = "1.0.69" 464 | source = "registry+https://github.com/rust-lang/crates.io-index" 465 | checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" 466 | dependencies = [ 467 | "unicode-ident", 468 | ] 469 | 470 | [[package]] 471 | name = "quote" 472 | version = "1.0.33" 473 | source = "registry+https://github.com/rust-lang/crates.io-index" 474 | checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" 475 | dependencies = [ 476 | "proc-macro2", 477 | ] 478 | 479 | [[package]] 480 | name = "rand" 481 | version = "0.8.5" 482 | source = "registry+https://github.com/rust-lang/crates.io-index" 483 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 484 | dependencies = [ 485 | "libc", 486 | "rand_chacha", 487 | "rand_core", 488 | ] 489 | 490 | [[package]] 491 | name = "rand_chacha" 492 | version = "0.3.1" 493 | source = "registry+https://github.com/rust-lang/crates.io-index" 494 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 495 | dependencies = [ 496 | "ppv-lite86", 497 | "rand_core", 498 | ] 499 | 500 | [[package]] 501 | name = "rand_core" 502 | version = "0.6.4" 503 | source = "registry+https://github.com/rust-lang/crates.io-index" 504 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 505 | dependencies = [ 506 | "getrandom", 507 | ] 508 | 509 | [[package]] 510 | name = "random-generation-protocol" 511 | version = "0.1.0" 512 | dependencies = [ 513 | "futures", 514 | "generic-array", 515 | "hex", 516 | "rand", 517 | "rand_chacha", 518 | "round-based", 519 | "serde", 520 | "sha2 0.10.8", 521 | "thiserror", 522 | "tokio", 523 | ] 524 | 525 | [[package]] 526 | name = "ring" 527 | version = "0.16.20" 528 | source = "registry+https://github.com/rust-lang/crates.io-index" 529 | checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" 530 | dependencies = [ 531 | "cc", 532 | "libc", 533 | "once_cell", 534 | "spin 0.5.2", 535 | "untrusted 0.7.1", 536 | "web-sys", 537 | "winapi", 538 | ] 539 | 540 | [[package]] 541 | name = "ring" 542 | version = "0.17.5" 543 | source = "registry+https://github.com/rust-lang/crates.io-index" 544 | checksum = "fb0205304757e5d899b9c2e448b867ffd03ae7f988002e47cd24954391394d0b" 545 | dependencies = [ 546 | "cc", 547 | "getrandom", 548 | "libc", 549 | "spin 0.9.8", 550 | "untrusted 0.9.0", 551 | "windows-sys", 552 | ] 553 | 554 | [[package]] 555 | name = "round-based" 556 | version = "0.2.0" 557 | dependencies = [ 558 | "futures", 559 | "futures-util", 560 | "matches", 561 | "phantom-type", 562 | "round-based-derive 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 563 | "thiserror", 564 | "tokio", 565 | "tokio-stream", 566 | "tracing", 567 | "trybuild", 568 | ] 569 | 570 | [[package]] 571 | name = "round-based-derive" 572 | version = "0.2.0" 573 | dependencies = [ 574 | "proc-macro2", 575 | "quote", 576 | "syn 1.0.109", 577 | ] 578 | 579 | [[package]] 580 | name = "round-based-derive" 581 | version = "0.2.0" 582 | source = "registry+https://github.com/rust-lang/crates.io-index" 583 | checksum = "0397bf224fdbcb3b286926e43bba90a96f81a82cc630ebfc9290d18e8b6331bd" 584 | dependencies = [ 585 | "proc-macro2", 586 | "quote", 587 | "syn 1.0.109", 588 | ] 589 | 590 | [[package]] 591 | name = "round-based-tests" 592 | version = "0.1.0" 593 | dependencies = [ 594 | "anyhow", 595 | "futures", 596 | "hex", 597 | "hex-literal", 598 | "matches", 599 | "rand", 600 | "rand_chacha", 601 | "random-generation-protocol", 602 | "round-based", 603 | "rustls", 604 | "sha2 0.9.9", 605 | "thiserror", 606 | "tokio", 607 | "tokio-rustls", 608 | "url", 609 | ] 610 | 611 | [[package]] 612 | name = "rustc-demangle" 613 | version = "0.1.23" 614 | source = "registry+https://github.com/rust-lang/crates.io-index" 615 | checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" 616 | 617 | [[package]] 618 | name = "rustls" 619 | version = "0.20.9" 620 | source = "registry+https://github.com/rust-lang/crates.io-index" 621 | checksum = "1b80e3dec595989ea8510028f30c408a4630db12c9cbb8de34203b89d6577e99" 622 | dependencies = [ 623 | "log", 624 | "ring 0.16.20", 625 | "sct", 626 | "webpki", 627 | ] 628 | 629 | [[package]] 630 | name = "ryu" 631 | version = "1.0.15" 632 | source = "registry+https://github.com/rust-lang/crates.io-index" 633 | checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" 634 | 635 | [[package]] 636 | name = "sct" 637 | version = "0.7.1" 638 | source = "registry+https://github.com/rust-lang/crates.io-index" 639 | checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" 640 | dependencies = [ 641 | "ring 0.17.5", 642 | "untrusted 0.9.0", 643 | ] 644 | 645 | [[package]] 646 | name = "serde" 647 | version = "1.0.193" 648 | source = "registry+https://github.com/rust-lang/crates.io-index" 649 | checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" 650 | dependencies = [ 651 | "serde_derive", 652 | ] 653 | 654 | [[package]] 655 | name = "serde_derive" 656 | version = "1.0.193" 657 | source = "registry+https://github.com/rust-lang/crates.io-index" 658 | checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" 659 | dependencies = [ 660 | "proc-macro2", 661 | "quote", 662 | "syn 2.0.39", 663 | ] 664 | 665 | [[package]] 666 | name = "serde_json" 667 | version = "1.0.108" 668 | source = "registry+https://github.com/rust-lang/crates.io-index" 669 | checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" 670 | dependencies = [ 671 | "itoa", 672 | "ryu", 673 | "serde", 674 | ] 675 | 676 | [[package]] 677 | name = "sha2" 678 | version = "0.9.9" 679 | source = "registry+https://github.com/rust-lang/crates.io-index" 680 | checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" 681 | dependencies = [ 682 | "block-buffer 0.9.0", 683 | "cfg-if", 684 | "cpufeatures", 685 | "digest 0.9.0", 686 | "opaque-debug", 687 | ] 688 | 689 | [[package]] 690 | name = "sha2" 691 | version = "0.10.8" 692 | source = "registry+https://github.com/rust-lang/crates.io-index" 693 | checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" 694 | dependencies = [ 695 | "cfg-if", 696 | "cpufeatures", 697 | "digest 0.10.7", 698 | ] 699 | 700 | [[package]] 701 | name = "slab" 702 | version = "0.4.9" 703 | source = "registry+https://github.com/rust-lang/crates.io-index" 704 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" 705 | dependencies = [ 706 | "autocfg", 707 | ] 708 | 709 | [[package]] 710 | name = "socket2" 711 | version = "0.5.5" 712 | source = "registry+https://github.com/rust-lang/crates.io-index" 713 | checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" 714 | dependencies = [ 715 | "libc", 716 | "windows-sys", 717 | ] 718 | 719 | [[package]] 720 | name = "spin" 721 | version = "0.5.2" 722 | source = "registry+https://github.com/rust-lang/crates.io-index" 723 | checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" 724 | 725 | [[package]] 726 | name = "spin" 727 | version = "0.9.8" 728 | source = "registry+https://github.com/rust-lang/crates.io-index" 729 | checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" 730 | 731 | [[package]] 732 | name = "syn" 733 | version = "1.0.109" 734 | source = "registry+https://github.com/rust-lang/crates.io-index" 735 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 736 | dependencies = [ 737 | "proc-macro2", 738 | "quote", 739 | "unicode-ident", 740 | ] 741 | 742 | [[package]] 743 | name = "syn" 744 | version = "2.0.39" 745 | source = "registry+https://github.com/rust-lang/crates.io-index" 746 | checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" 747 | dependencies = [ 748 | "proc-macro2", 749 | "quote", 750 | "unicode-ident", 751 | ] 752 | 753 | [[package]] 754 | name = "termcolor" 755 | version = "1.4.0" 756 | source = "registry+https://github.com/rust-lang/crates.io-index" 757 | checksum = "ff1bc3d3f05aff0403e8ac0d92ced918ec05b666a43f83297ccef5bea8a3d449" 758 | dependencies = [ 759 | "winapi-util", 760 | ] 761 | 762 | [[package]] 763 | name = "thiserror" 764 | version = "1.0.50" 765 | source = "registry+https://github.com/rust-lang/crates.io-index" 766 | checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" 767 | dependencies = [ 768 | "thiserror-impl", 769 | ] 770 | 771 | [[package]] 772 | name = "thiserror-impl" 773 | version = "1.0.50" 774 | source = "registry+https://github.com/rust-lang/crates.io-index" 775 | checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" 776 | dependencies = [ 777 | "proc-macro2", 778 | "quote", 779 | "syn 2.0.39", 780 | ] 781 | 782 | [[package]] 783 | name = "tinyvec" 784 | version = "1.6.0" 785 | source = "registry+https://github.com/rust-lang/crates.io-index" 786 | checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" 787 | dependencies = [ 788 | "tinyvec_macros", 789 | ] 790 | 791 | [[package]] 792 | name = "tinyvec_macros" 793 | version = "0.1.1" 794 | source = "registry+https://github.com/rust-lang/crates.io-index" 795 | checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" 796 | 797 | [[package]] 798 | name = "tokio" 799 | version = "1.34.0" 800 | source = "registry+https://github.com/rust-lang/crates.io-index" 801 | checksum = "d0c014766411e834f7af5b8f4cf46257aab4036ca95e9d2c144a10f59ad6f5b9" 802 | dependencies = [ 803 | "backtrace", 804 | "libc", 805 | "mio", 806 | "pin-project-lite", 807 | "socket2", 808 | "tokio-macros", 809 | "windows-sys", 810 | ] 811 | 812 | [[package]] 813 | name = "tokio-macros" 814 | version = "2.2.0" 815 | source = "registry+https://github.com/rust-lang/crates.io-index" 816 | checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" 817 | dependencies = [ 818 | "proc-macro2", 819 | "quote", 820 | "syn 2.0.39", 821 | ] 822 | 823 | [[package]] 824 | name = "tokio-rustls" 825 | version = "0.23.4" 826 | source = "registry+https://github.com/rust-lang/crates.io-index" 827 | checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" 828 | dependencies = [ 829 | "rustls", 830 | "tokio", 831 | "webpki", 832 | ] 833 | 834 | [[package]] 835 | name = "tokio-stream" 836 | version = "0.1.14" 837 | source = "registry+https://github.com/rust-lang/crates.io-index" 838 | checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" 839 | dependencies = [ 840 | "futures-core", 841 | "pin-project-lite", 842 | "tokio", 843 | "tokio-util", 844 | ] 845 | 846 | [[package]] 847 | name = "tokio-util" 848 | version = "0.7.10" 849 | source = "registry+https://github.com/rust-lang/crates.io-index" 850 | checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" 851 | dependencies = [ 852 | "bytes", 853 | "futures-core", 854 | "futures-sink", 855 | "pin-project-lite", 856 | "tokio", 857 | ] 858 | 859 | [[package]] 860 | name = "tracing" 861 | version = "0.1.40" 862 | source = "registry+https://github.com/rust-lang/crates.io-index" 863 | checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" 864 | dependencies = [ 865 | "pin-project-lite", 866 | "tracing-attributes", 867 | "tracing-core", 868 | ] 869 | 870 | [[package]] 871 | name = "tracing-attributes" 872 | version = "0.1.27" 873 | source = "registry+https://github.com/rust-lang/crates.io-index" 874 | checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" 875 | dependencies = [ 876 | "proc-macro2", 877 | "quote", 878 | "syn 2.0.39", 879 | ] 880 | 881 | [[package]] 882 | name = "tracing-core" 883 | version = "0.1.32" 884 | source = "registry+https://github.com/rust-lang/crates.io-index" 885 | checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" 886 | dependencies = [ 887 | "once_cell", 888 | ] 889 | 890 | [[package]] 891 | name = "trybuild" 892 | version = "1.0.85" 893 | source = "registry+https://github.com/rust-lang/crates.io-index" 894 | checksum = "196a58260a906cedb9bf6d8034b6379d0c11f552416960452f267402ceeddff1" 895 | dependencies = [ 896 | "basic-toml", 897 | "glob", 898 | "once_cell", 899 | "serde", 900 | "serde_derive", 901 | "serde_json", 902 | "termcolor", 903 | ] 904 | 905 | [[package]] 906 | name = "typenum" 907 | version = "1.17.0" 908 | source = "registry+https://github.com/rust-lang/crates.io-index" 909 | checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" 910 | 911 | [[package]] 912 | name = "unicode-bidi" 913 | version = "0.3.13" 914 | source = "registry+https://github.com/rust-lang/crates.io-index" 915 | checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" 916 | 917 | [[package]] 918 | name = "unicode-ident" 919 | version = "1.0.12" 920 | source = "registry+https://github.com/rust-lang/crates.io-index" 921 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 922 | 923 | [[package]] 924 | name = "unicode-normalization" 925 | version = "0.1.22" 926 | source = "registry+https://github.com/rust-lang/crates.io-index" 927 | checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" 928 | dependencies = [ 929 | "tinyvec", 930 | ] 931 | 932 | [[package]] 933 | name = "untrusted" 934 | version = "0.7.1" 935 | source = "registry+https://github.com/rust-lang/crates.io-index" 936 | checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" 937 | 938 | [[package]] 939 | name = "untrusted" 940 | version = "0.9.0" 941 | source = "registry+https://github.com/rust-lang/crates.io-index" 942 | checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" 943 | 944 | [[package]] 945 | name = "url" 946 | version = "2.5.0" 947 | source = "registry+https://github.com/rust-lang/crates.io-index" 948 | checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" 949 | dependencies = [ 950 | "form_urlencoded", 951 | "idna", 952 | "percent-encoding", 953 | ] 954 | 955 | [[package]] 956 | name = "version_check" 957 | version = "0.9.4" 958 | source = "registry+https://github.com/rust-lang/crates.io-index" 959 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 960 | 961 | [[package]] 962 | name = "wasi" 963 | version = "0.11.0+wasi-snapshot-preview1" 964 | source = "registry+https://github.com/rust-lang/crates.io-index" 965 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 966 | 967 | [[package]] 968 | name = "wasm-bindgen" 969 | version = "0.2.88" 970 | source = "registry+https://github.com/rust-lang/crates.io-index" 971 | checksum = "7daec296f25a1bae309c0cd5c29c4b260e510e6d813c286b19eaadf409d40fce" 972 | dependencies = [ 973 | "cfg-if", 974 | "wasm-bindgen-macro", 975 | ] 976 | 977 | [[package]] 978 | name = "wasm-bindgen-backend" 979 | version = "0.2.88" 980 | source = "registry+https://github.com/rust-lang/crates.io-index" 981 | checksum = "e397f4664c0e4e428e8313a469aaa58310d302159845980fd23b0f22a847f217" 982 | dependencies = [ 983 | "bumpalo", 984 | "log", 985 | "once_cell", 986 | "proc-macro2", 987 | "quote", 988 | "syn 2.0.39", 989 | "wasm-bindgen-shared", 990 | ] 991 | 992 | [[package]] 993 | name = "wasm-bindgen-macro" 994 | version = "0.2.88" 995 | source = "registry+https://github.com/rust-lang/crates.io-index" 996 | checksum = "5961017b3b08ad5f3fe39f1e79877f8ee7c23c5e5fd5eb80de95abc41f1f16b2" 997 | dependencies = [ 998 | "quote", 999 | "wasm-bindgen-macro-support", 1000 | ] 1001 | 1002 | [[package]] 1003 | name = "wasm-bindgen-macro-support" 1004 | version = "0.2.88" 1005 | source = "registry+https://github.com/rust-lang/crates.io-index" 1006 | checksum = "c5353b8dab669f5e10f5bd76df26a9360c748f054f862ff5f3f8aae0c7fb3907" 1007 | dependencies = [ 1008 | "proc-macro2", 1009 | "quote", 1010 | "syn 2.0.39", 1011 | "wasm-bindgen-backend", 1012 | "wasm-bindgen-shared", 1013 | ] 1014 | 1015 | [[package]] 1016 | name = "wasm-bindgen-shared" 1017 | version = "0.2.88" 1018 | source = "registry+https://github.com/rust-lang/crates.io-index" 1019 | checksum = "0d046c5d029ba91a1ed14da14dca44b68bf2f124cfbaf741c54151fdb3e0750b" 1020 | 1021 | [[package]] 1022 | name = "web-sys" 1023 | version = "0.3.65" 1024 | source = "registry+https://github.com/rust-lang/crates.io-index" 1025 | checksum = "5db499c5f66323272151db0e666cd34f78617522fb0c1604d31a27c50c206a85" 1026 | dependencies = [ 1027 | "js-sys", 1028 | "wasm-bindgen", 1029 | ] 1030 | 1031 | [[package]] 1032 | name = "webpki" 1033 | version = "0.22.4" 1034 | source = "registry+https://github.com/rust-lang/crates.io-index" 1035 | checksum = "ed63aea5ce73d0ff405984102c42de94fc55a6b75765d621c65262469b3c9b53" 1036 | dependencies = [ 1037 | "ring 0.17.5", 1038 | "untrusted 0.9.0", 1039 | ] 1040 | 1041 | [[package]] 1042 | name = "winapi" 1043 | version = "0.3.9" 1044 | source = "registry+https://github.com/rust-lang/crates.io-index" 1045 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1046 | dependencies = [ 1047 | "winapi-i686-pc-windows-gnu", 1048 | "winapi-x86_64-pc-windows-gnu", 1049 | ] 1050 | 1051 | [[package]] 1052 | name = "winapi-i686-pc-windows-gnu" 1053 | version = "0.4.0" 1054 | source = "registry+https://github.com/rust-lang/crates.io-index" 1055 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1056 | 1057 | [[package]] 1058 | name = "winapi-util" 1059 | version = "0.1.6" 1060 | source = "registry+https://github.com/rust-lang/crates.io-index" 1061 | checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" 1062 | dependencies = [ 1063 | "winapi", 1064 | ] 1065 | 1066 | [[package]] 1067 | name = "winapi-x86_64-pc-windows-gnu" 1068 | version = "0.4.0" 1069 | source = "registry+https://github.com/rust-lang/crates.io-index" 1070 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1071 | 1072 | [[package]] 1073 | name = "windows-sys" 1074 | version = "0.48.0" 1075 | source = "registry+https://github.com/rust-lang/crates.io-index" 1076 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 1077 | dependencies = [ 1078 | "windows-targets", 1079 | ] 1080 | 1081 | [[package]] 1082 | name = "windows-targets" 1083 | version = "0.48.5" 1084 | source = "registry+https://github.com/rust-lang/crates.io-index" 1085 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 1086 | dependencies = [ 1087 | "windows_aarch64_gnullvm", 1088 | "windows_aarch64_msvc", 1089 | "windows_i686_gnu", 1090 | "windows_i686_msvc", 1091 | "windows_x86_64_gnu", 1092 | "windows_x86_64_gnullvm", 1093 | "windows_x86_64_msvc", 1094 | ] 1095 | 1096 | [[package]] 1097 | name = "windows_aarch64_gnullvm" 1098 | version = "0.48.5" 1099 | source = "registry+https://github.com/rust-lang/crates.io-index" 1100 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 1101 | 1102 | [[package]] 1103 | name = "windows_aarch64_msvc" 1104 | version = "0.48.5" 1105 | source = "registry+https://github.com/rust-lang/crates.io-index" 1106 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 1107 | 1108 | [[package]] 1109 | name = "windows_i686_gnu" 1110 | version = "0.48.5" 1111 | source = "registry+https://github.com/rust-lang/crates.io-index" 1112 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 1113 | 1114 | [[package]] 1115 | name = "windows_i686_msvc" 1116 | version = "0.48.5" 1117 | source = "registry+https://github.com/rust-lang/crates.io-index" 1118 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 1119 | 1120 | [[package]] 1121 | name = "windows_x86_64_gnu" 1122 | version = "0.48.5" 1123 | source = "registry+https://github.com/rust-lang/crates.io-index" 1124 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 1125 | 1126 | [[package]] 1127 | name = "windows_x86_64_gnullvm" 1128 | version = "0.48.5" 1129 | source = "registry+https://github.com/rust-lang/crates.io-index" 1130 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 1131 | 1132 | [[package]] 1133 | name = "windows_x86_64_msvc" 1134 | version = "0.48.5" 1135 | source = "registry+https://github.com/rust-lang/crates.io-index" 1136 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 1137 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | members = [ 4 | "round-based", 5 | "round-based-tests", 6 | "round-based-derive", 7 | "examples/random-generation-protocol", 8 | ] 9 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2023 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Zengo X 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: docs docs-open 2 | 3 | docs: 4 | RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --no-deps --all-features 5 | 6 | docs-test: 7 | RUSTDOCFLAGS="--cfg docsrs" cargo +nightly test --doc --all-features 8 | 9 | docs-open: 10 | RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --no-deps --all-features --open 11 | 12 | readme: 13 | cargo readme -i src/lib.rs -r round-based/ -t ../docs/readme.tpl --no-indent-headings \ 14 | | perl -ne 's/(? README.md 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![License: MIT](https://img.shields.io/crates/l/round-based.svg) 2 | [![Docs](https://docs.rs/round-based/badge.svg)](https://docs.rs/round-based) 3 | [![Crates io](https://img.shields.io/crates/v/round-based.svg)](https://crates.io/crates/round-based) 4 | 5 | An MPC framework that unifies and simplifies the way of developing and working with 6 | multiparty protocols (e.g. threshold signing, random beacons, etc.). 7 | 8 | ## Goals 9 | 10 | * Async friendly \ 11 | Async is the most simple and efficient way of doing networking in Rust 12 | * Simple, configurable \ 13 | Protocol can be carried out in a few lines of code: check out examples. 14 | * Independent of networking layer \ 15 | We use abstractions `Stream` and `Sink` to receive and send messages. 16 | 17 | ## Networking 18 | 19 | In order to run an MPC protocol, transport layer needs to be defined. All you have to do is to 20 | implement `Delivery` trait which is basically a stream and a sink for receiving and sending messages. 21 | 22 | Message delivery should meet certain criterias that differ from protocol to protocol (refer to 23 | the documentation of the protocol you're using), but usually they are: 24 | 25 | * Messages should be authenticated \ 26 | Each message should be signed with identity key of the sender. This implies having Public Key 27 | Infrastructure. 28 | * P2P messages should be encrypted \ 29 | Only recipient should be able to learn the content of p2p message 30 | * Broadcast channel should be reliable \ 31 | Some protocols may require broadcast channel to be reliable. Simply saying, when party receives a 32 | broadcast message over reliable channel it should be ensured that everybody else received the same 33 | message. 34 | 35 | ## Features 36 | 37 | * `dev` enables development tools such as protocol simulation 38 | * `runtime-tokio` enables tokio-specific implementation of async runtime 39 | -------------------------------------------------------------------------------- /docs/readme.tpl: -------------------------------------------------------------------------------- 1 | {{readme}} 2 | -------------------------------------------------------------------------------- /examples/random-generation-protocol/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "random-generation-protocol" 3 | version = "0.1.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | round-based = { path = "../../round-based", features = ["derive"] } 11 | 12 | tokio = { version = "1.15", features = ["rt"] } 13 | futures = "0.3" 14 | rand = "0.8" 15 | sha2 = "0.10" 16 | serde = { version = "1", features = ["derive"] } 17 | generic-array = { version = "0.14", features = ["serde"] } 18 | thiserror = "1" 19 | 20 | [dev-dependencies] 21 | round-based = { path = "../../round-based", features = ["derive", "dev"] } 22 | tokio = { version = "1.15", features = ["macros", "rt"] } 23 | hex = "0.4" 24 | rand_chacha = "0.3" 25 | -------------------------------------------------------------------------------- /examples/random-generation-protocol/src/lib.rs: -------------------------------------------------------------------------------- 1 | use futures::SinkExt; 2 | use rand::RngCore; 3 | use serde::{Deserialize, Serialize}; 4 | use sha2::{digest::Output, Digest, Sha256}; 5 | use thiserror::Error; 6 | 7 | use round_based::rounds_router::{ 8 | simple_store::{RoundInput, RoundInputError}, 9 | CompleteRoundError, RoundsRouter, 10 | }; 11 | use round_based::{Delivery, Mpc, MpcParty, MsgId, Outgoing, PartyIndex, ProtocolMessage}; 12 | 13 | #[derive(Clone, Debug, PartialEq, ProtocolMessage, Serialize, Deserialize)] 14 | pub enum Msg { 15 | CommitMsg(CommitMsg), 16 | DecommitMsg(DecommitMsg), 17 | } 18 | 19 | #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] 20 | pub struct CommitMsg { 21 | pub commitment: Output, 22 | } 23 | 24 | #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] 25 | pub struct DecommitMsg { 26 | pub randomness: [u8; 32], 27 | } 28 | 29 | pub async fn protocol_of_random_generation( 30 | party: M, 31 | i: PartyIndex, 32 | n: u16, 33 | mut rng: R, 34 | ) -> Result<[u8; 32], Error> 35 | where 36 | M: Mpc, 37 | R: RngCore, 38 | { 39 | let MpcParty { delivery, .. } = party.into_party(); 40 | let (incoming, mut outgoing) = delivery.split(); 41 | 42 | // Define rounds 43 | let mut rounds = RoundsRouter::::builder(); 44 | let round1 = rounds.add_round(RoundInput::::broadcast(i, n)); 45 | let round2 = rounds.add_round(RoundInput::::broadcast(i, n)); 46 | let mut rounds = rounds.listen(incoming); 47 | 48 | // --- The Protocol --- 49 | 50 | // 1. Generate local randomness 51 | let mut local_randomness = [0u8; 32]; 52 | rng.fill_bytes(&mut local_randomness); 53 | 54 | // 2. Commit local randomness (broadcast m=sha256(randomness)) 55 | let commitment = Sha256::digest(&local_randomness); 56 | outgoing 57 | .send(Outgoing::broadcast(Msg::CommitMsg(CommitMsg { 58 | commitment, 59 | }))) 60 | .await 61 | .map_err(Error::Round1Send)?; 62 | 63 | // 3. Receive committed randomness from other parties 64 | let commitments = rounds 65 | .complete(round1) 66 | .await 67 | .map_err(Error::Round1Receive)?; 68 | 69 | // 4. Open local randomness 70 | outgoing 71 | .send(Outgoing::broadcast(Msg::DecommitMsg(DecommitMsg { 72 | randomness: local_randomness, 73 | }))) 74 | .await 75 | .map_err(Error::Round2Send)?; 76 | 77 | // 5. Receive opened local randomness from other parties, verify them, and output protocol randomness 78 | let randomness = rounds 79 | .complete(round2) 80 | .await 81 | .map_err(Error::Round2Receive)?; 82 | 83 | let mut guilty_parties = vec![]; 84 | let mut output = local_randomness; 85 | for ((party_i, com_msg_id, commit), (_, decom_msg_id, decommit)) in commitments 86 | .into_iter_indexed() 87 | .zip(randomness.into_iter_indexed()) 88 | { 89 | let commitment_expected = Sha256::digest(&decommit.randomness); 90 | if commit.commitment != commitment_expected { 91 | guilty_parties.push(Blame { 92 | guilty_party: party_i, 93 | commitment_msg: com_msg_id, 94 | decommitment_msg: decom_msg_id, 95 | }); 96 | continue; 97 | } 98 | 99 | output 100 | .iter_mut() 101 | .zip(decommit.randomness) 102 | .for_each(|(x, r)| *x ^= r); 103 | } 104 | 105 | if !guilty_parties.is_empty() { 106 | Err(Error::PartiesOpenedRandomnessDoesntMatchCommitment { guilty_parties }) 107 | } else { 108 | Ok(output) 109 | } 110 | } 111 | 112 | #[derive(Debug, Error)] 113 | pub enum Error { 114 | #[error("send a message at round 1")] 115 | Round1Send(#[source] SendErr), 116 | #[error("receive messages at round 1")] 117 | Round1Receive(#[source] CompleteRoundError), 118 | #[error("send a message at round 2")] 119 | Round2Send(#[source] SendErr), 120 | #[error("receive messages at round 2")] 121 | Round2Receive(#[source] CompleteRoundError), 122 | 123 | #[error("malicious parties: {guilty_parties:?}")] 124 | PartiesOpenedRandomnessDoesntMatchCommitment { guilty_parties: Vec }, 125 | } 126 | 127 | #[derive(Debug)] 128 | pub struct Blame { 129 | pub guilty_party: PartyIndex, 130 | pub commitment_msg: MsgId, 131 | pub decommitment_msg: MsgId, 132 | } 133 | 134 | #[cfg(test)] 135 | mod tests { 136 | use rand::SeedableRng; 137 | use rand_chacha::ChaCha20Rng; 138 | 139 | use round_based::simulation::Simulation; 140 | 141 | use super::{protocol_of_random_generation, Msg}; 142 | 143 | #[tokio::test] 144 | async fn main() { 145 | let n: u16 = 5; 146 | 147 | let mut simulation = Simulation::::new(); 148 | let mut party_output = vec![]; 149 | 150 | for i in 0..n { 151 | let party = simulation.add_party(); 152 | let rng = ChaCha20Rng::from_entropy(); 153 | let output = protocol_of_random_generation(party, i, n, rng); 154 | party_output.push(output); 155 | } 156 | 157 | let output = futures::future::try_join_all(party_output).await.unwrap(); 158 | 159 | // Assert that all parties outputed the same randomness 160 | for i in 1..n { 161 | assert_eq!(output[0], output[usize::from(i)]); 162 | } 163 | 164 | println!("Output randomness: {}", hex::encode(&output[0])); 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /round-based-derive/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "round-based-derive" 3 | version = "0.2.0" 4 | edition = "2021" 5 | license = "MIT OR Apache-2.0" 6 | description = "Proc-macro for deriving `round-based` traits" 7 | repository = "https://github.com/ZenGo-X/round-based-protocol" 8 | 9 | [lib] 10 | proc-macro = true 11 | 12 | [dependencies] 13 | syn = "1" 14 | quote = "1" 15 | proc-macro2 = "1" 16 | -------------------------------------------------------------------------------- /round-based-derive/src/lib.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::{Span, TokenStream}; 2 | use quote::{quote, quote_spanned}; 3 | use syn::ext::IdentExt; 4 | use syn::parse::{Parse, ParseStream}; 5 | use syn::punctuated::Punctuated; 6 | use syn::spanned::Spanned; 7 | use syn::{parse_macro_input, Data, DeriveInput, Fields, Generics, Ident, Token, Variant}; 8 | 9 | #[proc_macro_derive(ProtocolMessage, attributes(protocol_message))] 10 | pub fn protocol_message(input: proc_macro::TokenStream) -> proc_macro::TokenStream { 11 | let input = parse_macro_input!(input as DeriveInput); 12 | 13 | let mut root = None; 14 | 15 | for attr in input.attrs { 16 | if !attr.path.is_ident("protocol_message") { 17 | continue; 18 | } 19 | if root.is_some() { 20 | return quote_spanned! { attr.path.span() => compile_error!("#[protocol_message] attribute appears more than once"); }.into(); 21 | } 22 | let tokens = attr.tokens.into(); 23 | root = Some(parse_macro_input!(tokens as RootAttribute)); 24 | } 25 | 26 | let root_path = root 27 | .map(|root| root.path) 28 | .unwrap_or_else(|| Punctuated::from_iter([Ident::new("round_based", Span::call_site())])); 29 | 30 | let enum_data = match input.data { 31 | Data::Enum(e) => e, 32 | Data::Struct(s) => { 33 | return quote_spanned! {s.struct_token.span => compile_error!("only enum may implement ProtocolMessage");}.into() 34 | } 35 | Data::Union(s) => { 36 | return quote_spanned! {s.union_token.span => compile_error!("only enum may implement ProtocolMessage");}.into() 37 | } 38 | }; 39 | 40 | let name = input.ident; 41 | let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); 42 | let round_method_impl = if !enum_data.variants.is_empty() { 43 | round_method(&name, enum_data.variants.iter()) 44 | } else { 45 | // Special case for empty enum. Empty protocol message is useless, but let it be 46 | quote! { match *self {} } 47 | }; 48 | 49 | let impl_protocol_message = quote! { 50 | impl #impl_generics #root_path::ProtocolMessage for #name #ty_generics #where_clause { 51 | fn round(&self) -> u16 { 52 | #round_method_impl 53 | } 54 | } 55 | }; 56 | 57 | let impl_round_message = round_messages( 58 | &root_path, 59 | &name, 60 | &input.generics, 61 | enum_data.variants.iter(), 62 | ); 63 | 64 | proc_macro::TokenStream::from(quote! { 65 | #impl_protocol_message 66 | #impl_round_message 67 | }) 68 | } 69 | 70 | fn round_method<'v>(enum_name: &Ident, variants: impl Iterator) -> TokenStream { 71 | let match_variants = (0u16..).zip(variants).map(|(i, variant)| { 72 | let variant_name = &variant.ident; 73 | match &variant.fields { 74 | Fields::Unit => quote_spanned! { 75 | variant.ident.span() => 76 | #enum_name::#variant_name => compile_error!("unit variants are not allowed in ProtocolMessage"), 77 | }, 78 | Fields::Named(_) => quote_spanned! { 79 | variant.ident.span() => 80 | #enum_name::#variant_name{..} => compile_error!("named variants are not allowed in ProtocolMessage"), 81 | }, 82 | Fields::Unnamed(unnamed) => if unnamed.unnamed.len() == 1 { 83 | quote_spanned! { 84 | variant.ident.span() => 85 | #enum_name::#variant_name(_) => #i, 86 | } 87 | } else { 88 | quote_spanned! { 89 | variant.ident.span() => 90 | #enum_name::#variant_name(..) => compile_error!("this variant must contain exactly one field to be valid ProtocolMessage"), 91 | } 92 | }, 93 | } 94 | }); 95 | quote! { 96 | match self { 97 | #(#match_variants)* 98 | } 99 | } 100 | } 101 | 102 | fn round_messages<'v>( 103 | root_path: &RootPath, 104 | enum_name: &Ident, 105 | generics: &Generics, 106 | variants: impl Iterator, 107 | ) -> TokenStream { 108 | let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); 109 | let impls = (0u16..).zip(variants).map(|(i, variant)| { 110 | let variant_name = &variant.ident; 111 | match &variant.fields { 112 | Fields::Unnamed(unnamed) if unnamed.unnamed.len() == 1 => { 113 | let msg_type = &unnamed.unnamed[0].ty; 114 | quote_spanned! { 115 | variant.ident.span() => 116 | impl #impl_generics #root_path::RoundMessage<#msg_type> for #enum_name #ty_generics #where_clause { 117 | const ROUND: u16 = #i; 118 | fn to_protocol_message(round_message: #msg_type) -> Self { 119 | #enum_name::#variant_name(round_message) 120 | } 121 | fn from_protocol_message(protocol_message: Self) -> Result<#msg_type, Self> { 122 | #[allow(unreachable_patterns)] 123 | match protocol_message { 124 | #enum_name::#variant_name(msg) => Ok(msg), 125 | _ => Err(protocol_message), 126 | } 127 | } 128 | } 129 | } 130 | } 131 | _ => quote! {}, 132 | } 133 | }); 134 | quote! { 135 | #(#impls)* 136 | } 137 | } 138 | 139 | type RootPath = Punctuated; 140 | 141 | #[allow(dead_code)] 142 | struct RootAttribute { 143 | paren: syn::token::Paren, 144 | root: kw::root, 145 | eq: Token![=], 146 | path: RootPath, 147 | } 148 | 149 | impl Parse for RootAttribute { 150 | fn parse(input: ParseStream) -> syn::Result { 151 | let content; 152 | let paren = syn::parenthesized!(content in input); 153 | let root = content.parse::()?; 154 | let eq = content.parse::()?; 155 | let path = RootPath::parse_separated_nonempty_with(&content, Ident::parse_any)?; 156 | let _ = content.parse::()?; 157 | 158 | Ok(Self { 159 | paren, 160 | root, 161 | eq, 162 | path, 163 | }) 164 | } 165 | } 166 | 167 | mod kw { 168 | syn::custom_keyword! { root } 169 | } 170 | -------------------------------------------------------------------------------- /round-based-tests/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "round-based-tests" 3 | version = "0.1.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | 11 | [dev-dependencies] 12 | tokio = { version = "1", features = ["net", "rt", "macros"] } 13 | tokio-rustls = "0.23" 14 | rustls = "0.20" 15 | anyhow = "1" 16 | rand = "0.8" 17 | sha2 = "0.9" 18 | hex-literal = "0.3" 19 | hex = "*" 20 | futures = "0.3" 21 | rand_chacha = "0.3" 22 | matches = "0.1" 23 | thiserror = "1" 24 | url = "2.2" 25 | 26 | round-based = { path = "../round-based" } 27 | random-generation-protocol = { path = "../examples/random-generation-protocol" } 28 | -------------------------------------------------------------------------------- /round-based-tests/src/lib.rs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /round-based-tests/tests/rounds.rs: -------------------------------------------------------------------------------- 1 | use std::convert::Infallible; 2 | 3 | use futures::{sink, stream, Sink, Stream}; 4 | use hex_literal::hex; 5 | use matches::assert_matches; 6 | use rand::SeedableRng; 7 | 8 | use random_generation_protocol::{ 9 | protocol_of_random_generation, CommitMsg, DecommitMsg, Error, Msg, 10 | }; 11 | use round_based::rounds_router::errors::IoError; 12 | use round_based::rounds_router::{simple_store::RoundInput, CompleteRoundError, RoundsRouter}; 13 | use round_based::{Delivery, Incoming, MessageType, MpcParty, Outgoing}; 14 | 15 | const PARTY0_SEED: [u8; 32] = 16 | hex!("6772d079d5c984b3936a291e36b0d3dc6c474e36ed4afdfc973ef79a431ca870"); 17 | const PARTY1_COMMITMENT: [u8; 32] = 18 | hex!("2a8c585d9a80cb78bc226f4ab35a75c8e5834ff77a83f41cf6c893ea0f3b2aed"); 19 | const PARTY1_RANDOMNESS: [u8; 32] = 20 | hex!("12a595f4893fdb4ab9cc38caeec5f7456acb3002ca58457c5056977ce59136a6"); 21 | const PARTY2_COMMITMENT: [u8; 32] = 22 | hex!("01274ef40aece8aa039587cc05620a19b80a5c93fbfb24a9f8e1b77b7936e47d"); 23 | const PARTY2_RANDOMNESS: [u8; 32] = 24 | hex!("6fc78a926c7eebfad4e98e796cd53b771ac5947b460567c7ea441abb957c89c7"); 25 | const PROTOCOL_OUTPUT: [u8; 32] = 26 | hex!("689a9f02229bdb36521275179676641585c4a3ce7b80ace37f0272a65e89a1c3"); 27 | const PARTY_OVERWRITES: [u8; 32] = 28 | hex!("00aa11bb22cc33dd44ee55ff6677889900aa11bb22cc33dd44ee55ff66778899"); 29 | 30 | #[tokio::test] 31 | async fn random_generation_completes() { 32 | let output = run_protocol([ 33 | Ok::<_, Infallible>(Incoming { 34 | id: 0, 35 | sender: 1, 36 | msg_type: MessageType::Broadcast, 37 | msg: Msg::CommitMsg(CommitMsg { 38 | commitment: PARTY1_COMMITMENT.into(), 39 | }), 40 | }), 41 | Ok(Incoming { 42 | id: 1, 43 | sender: 2, 44 | msg_type: MessageType::Broadcast, 45 | msg: Msg::CommitMsg(CommitMsg { 46 | commitment: PARTY2_COMMITMENT.into(), 47 | }), 48 | }), 49 | Ok(Incoming { 50 | id: 2, 51 | sender: 1, 52 | msg_type: MessageType::Broadcast, 53 | msg: Msg::DecommitMsg(DecommitMsg { 54 | randomness: PARTY1_RANDOMNESS, 55 | }), 56 | }), 57 | Ok(Incoming { 58 | id: 3, 59 | sender: 2, 60 | msg_type: MessageType::Broadcast, 61 | msg: Msg::DecommitMsg(DecommitMsg { 62 | randomness: PARTY2_RANDOMNESS, 63 | }), 64 | }), 65 | ]) 66 | .await 67 | .unwrap(); 68 | 69 | assert_eq!(output, PROTOCOL_OUTPUT); 70 | } 71 | 72 | #[tokio::test] 73 | async fn protocol_terminates_with_error_if_party_tries_to_overwrite_message_at_round1() { 74 | let output = run_protocol([ 75 | Ok::<_, Infallible>(Incoming { 76 | id: 0, 77 | sender: 1, 78 | msg_type: MessageType::Broadcast, 79 | msg: Msg::CommitMsg(CommitMsg { 80 | commitment: PARTY1_COMMITMENT.into(), 81 | }), 82 | }), 83 | Ok(Incoming { 84 | id: 1, 85 | sender: 1, 86 | msg_type: MessageType::Broadcast, 87 | msg: Msg::CommitMsg(CommitMsg { 88 | commitment: PARTY_OVERWRITES.into(), 89 | }), 90 | }), 91 | ]) 92 | .await; 93 | 94 | assert_matches!( 95 | output, 96 | Err(Error::Round1Receive(CompleteRoundError::ProcessMessage(_))) 97 | ) 98 | } 99 | 100 | #[tokio::test] 101 | async fn protocol_terminates_with_error_if_party_tries_to_overwrite_message_at_round2() { 102 | let output = run_protocol([ 103 | Ok::<_, Infallible>(Incoming { 104 | id: 0, 105 | sender: 1, 106 | msg_type: MessageType::Broadcast, 107 | msg: Msg::CommitMsg(CommitMsg { 108 | commitment: PARTY1_COMMITMENT.into(), 109 | }), 110 | }), 111 | Ok(Incoming { 112 | id: 1, 113 | sender: 2, 114 | msg_type: MessageType::Broadcast, 115 | msg: Msg::CommitMsg(CommitMsg { 116 | commitment: PARTY2_COMMITMENT.into(), 117 | }), 118 | }), 119 | Ok(Incoming { 120 | id: 2, 121 | sender: 1, 122 | msg_type: MessageType::Broadcast, 123 | msg: Msg::DecommitMsg(DecommitMsg { 124 | randomness: PARTY1_RANDOMNESS, 125 | }), 126 | }), 127 | Ok(Incoming { 128 | id: 3, 129 | sender: 1, 130 | msg_type: MessageType::Broadcast, 131 | msg: Msg::DecommitMsg(DecommitMsg { 132 | randomness: PARTY_OVERWRITES, 133 | }), 134 | }), 135 | ]) 136 | .await; 137 | 138 | assert_matches!( 139 | output, 140 | Err(Error::Round2Receive(CompleteRoundError::ProcessMessage(_))) 141 | ) 142 | } 143 | 144 | #[tokio::test] 145 | async fn protocol_terminates_if_received_message_from_unknown_sender_at_round1() { 146 | let output = run_protocol([Ok::<_, Infallible>(Incoming { 147 | id: 0, 148 | sender: 3, 149 | msg_type: MessageType::Broadcast, 150 | msg: Msg::CommitMsg(CommitMsg { 151 | commitment: PARTY1_COMMITMENT.into(), 152 | }), 153 | })]) 154 | .await; 155 | 156 | assert_matches!( 157 | output, 158 | Err(Error::Round1Receive(CompleteRoundError::ProcessMessage(_))) 159 | ) 160 | } 161 | 162 | #[tokio::test] 163 | async fn protocol_ignores_message_that_goes_to_completed_round() { 164 | let output = run_protocol([ 165 | Ok::<_, Infallible>(Incoming { 166 | id: 0, 167 | sender: 1, 168 | msg_type: MessageType::Broadcast, 169 | msg: Msg::CommitMsg(CommitMsg { 170 | commitment: PARTY1_COMMITMENT.into(), 171 | }), 172 | }), 173 | Ok(Incoming { 174 | id: 1, 175 | sender: 2, 176 | msg_type: MessageType::Broadcast, 177 | msg: Msg::CommitMsg(CommitMsg { 178 | commitment: PARTY2_COMMITMENT.into(), 179 | }), 180 | }), 181 | Ok(Incoming { 182 | id: 2, 183 | sender: 1, 184 | msg_type: MessageType::Broadcast, 185 | msg: Msg::CommitMsg(CommitMsg { 186 | commitment: PARTY_OVERWRITES.into(), 187 | }), 188 | }), 189 | Ok(Incoming { 190 | id: 3, 191 | sender: 1, 192 | msg_type: MessageType::Broadcast, 193 | msg: Msg::DecommitMsg(DecommitMsg { 194 | randomness: PARTY1_RANDOMNESS, 195 | }), 196 | }), 197 | Ok(Incoming { 198 | id: 4, 199 | sender: 2, 200 | msg_type: MessageType::Broadcast, 201 | msg: Msg::DecommitMsg(DecommitMsg { 202 | randomness: PARTY2_RANDOMNESS, 203 | }), 204 | }), 205 | ]) 206 | .await 207 | .unwrap(); 208 | 209 | assert_eq!(output, PROTOCOL_OUTPUT); 210 | } 211 | 212 | #[tokio::test] 213 | async fn protocol_ignores_io_error_if_it_is_completed() { 214 | let output = run_protocol([ 215 | Ok(Incoming { 216 | id: 0, 217 | sender: 1, 218 | msg_type: MessageType::Broadcast, 219 | msg: Msg::CommitMsg(CommitMsg { 220 | commitment: PARTY1_COMMITMENT.into(), 221 | }), 222 | }), 223 | Ok(Incoming { 224 | id: 1, 225 | sender: 2, 226 | msg_type: MessageType::Broadcast, 227 | msg: Msg::CommitMsg(CommitMsg { 228 | commitment: PARTY2_COMMITMENT.into(), 229 | }), 230 | }), 231 | Ok(Incoming { 232 | id: 2, 233 | sender: 1, 234 | msg_type: MessageType::Broadcast, 235 | msg: Msg::DecommitMsg(DecommitMsg { 236 | randomness: PARTY1_RANDOMNESS, 237 | }), 238 | }), 239 | Ok(Incoming { 240 | id: 3, 241 | sender: 2, 242 | msg_type: MessageType::Broadcast, 243 | msg: Msg::DecommitMsg(DecommitMsg { 244 | randomness: PARTY2_RANDOMNESS, 245 | }), 246 | }), 247 | Err(DummyError), 248 | ]) 249 | .await 250 | .unwrap(); 251 | 252 | assert_eq!(output, PROTOCOL_OUTPUT); 253 | } 254 | 255 | #[tokio::test] 256 | async fn protocol_terminates_with_error_if_io_error_happens_at_round2() { 257 | let output = run_protocol([ 258 | Ok(Incoming { 259 | id: 0, 260 | sender: 1, 261 | msg_type: MessageType::Broadcast, 262 | msg: Msg::CommitMsg(CommitMsg { 263 | commitment: PARTY1_COMMITMENT.into(), 264 | }), 265 | }), 266 | Ok(Incoming { 267 | id: 1, 268 | sender: 2, 269 | msg_type: MessageType::Broadcast, 270 | msg: Msg::CommitMsg(CommitMsg { 271 | commitment: PARTY2_COMMITMENT.into(), 272 | }), 273 | }), 274 | Ok(Incoming { 275 | id: 2, 276 | sender: 1, 277 | msg_type: MessageType::Broadcast, 278 | msg: Msg::DecommitMsg(DecommitMsg { 279 | randomness: PARTY1_RANDOMNESS, 280 | }), 281 | }), 282 | Err(DummyError), 283 | Ok(Incoming { 284 | id: 3, 285 | sender: 2, 286 | msg_type: MessageType::Broadcast, 287 | msg: Msg::DecommitMsg(DecommitMsg { 288 | randomness: PARTY2_RANDOMNESS, 289 | }), 290 | }), 291 | ]) 292 | .await; 293 | 294 | assert_matches!(output, Err(Error::Round2Receive(CompleteRoundError::Io(_)))); 295 | } 296 | 297 | #[tokio::test] 298 | async fn protocol_terminates_with_error_if_io_error_happens_at_round1() { 299 | let output = run_protocol([ 300 | Err(DummyError), 301 | Ok(Incoming { 302 | id: 0, 303 | sender: 1, 304 | msg_type: MessageType::Broadcast, 305 | msg: Msg::CommitMsg(CommitMsg { 306 | commitment: PARTY1_COMMITMENT.into(), 307 | }), 308 | }), 309 | Ok(Incoming { 310 | id: 1, 311 | sender: 2, 312 | msg_type: MessageType::Broadcast, 313 | msg: Msg::CommitMsg(CommitMsg { 314 | commitment: PARTY2_COMMITMENT.into(), 315 | }), 316 | }), 317 | Ok(Incoming { 318 | id: 2, 319 | sender: 1, 320 | msg_type: MessageType::Broadcast, 321 | msg: Msg::DecommitMsg(DecommitMsg { 322 | randomness: PARTY1_RANDOMNESS, 323 | }), 324 | }), 325 | Ok(Incoming { 326 | id: 3, 327 | sender: 2, 328 | msg_type: MessageType::Broadcast, 329 | msg: Msg::DecommitMsg(DecommitMsg { 330 | randomness: PARTY2_RANDOMNESS, 331 | }), 332 | }), 333 | ]) 334 | .await; 335 | 336 | assert_matches!(output, Err(Error::Round1Receive(CompleteRoundError::Io(_)))); 337 | } 338 | 339 | #[tokio::test] 340 | async fn protocol_terminates_with_error_if_unexpected_eof_happens_at_round2() { 341 | let output = run_protocol([ 342 | Ok::<_, Infallible>(Incoming { 343 | id: 0, 344 | sender: 1, 345 | msg_type: MessageType::Broadcast, 346 | msg: Msg::CommitMsg(CommitMsg { 347 | commitment: PARTY1_COMMITMENT.into(), 348 | }), 349 | }), 350 | Ok(Incoming { 351 | id: 1, 352 | sender: 2, 353 | msg_type: MessageType::Broadcast, 354 | msg: Msg::CommitMsg(CommitMsg { 355 | commitment: PARTY2_COMMITMENT.into(), 356 | }), 357 | }), 358 | Ok(Incoming { 359 | id: 2, 360 | sender: 1, 361 | msg_type: MessageType::Broadcast, 362 | msg: Msg::DecommitMsg(DecommitMsg { 363 | randomness: PARTY1_RANDOMNESS, 364 | }), 365 | }), 366 | ]) 367 | .await; 368 | 369 | assert_matches!( 370 | output, 371 | Err(Error::Round2Receive(CompleteRoundError::Io( 372 | IoError::UnexpectedEof 373 | ))) 374 | ); 375 | } 376 | 377 | #[tokio::test] 378 | async fn all_non_completed_rounds_are_terminated_with_unexpected_eof_error_if_incoming_channel_suddenly_closed( 379 | ) { 380 | let mut rounds = RoundsRouter::builder(); 381 | let round1 = rounds.add_round(RoundInput::::new(0, 3, MessageType::P2P)); 382 | let round2 = rounds.add_round(RoundInput::::new(0, 3, MessageType::P2P)); 383 | let mut rounds = rounds.listen(stream::empty::, Infallible>>()); 384 | 385 | assert_matches!( 386 | rounds.complete(round1).await, 387 | Err(CompleteRoundError::Io(IoError::UnexpectedEof)) 388 | ); 389 | assert_matches!( 390 | rounds.complete(round2).await, 391 | Err(CompleteRoundError::Io(IoError::UnexpectedEof)) 392 | ); 393 | } 394 | 395 | async fn run_protocol(incomings: I) -> Result<[u8; 32], Error> 396 | where 397 | I: IntoIterator, E>>, 398 | I::IntoIter: Send + 'static, 399 | E: std::error::Error + Send + Sync + Unpin + 'static, 400 | { 401 | let rng = rand_chacha::ChaCha8Rng::from_seed(PARTY0_SEED); 402 | 403 | let party = MpcParty::connected(MockedDelivery::new(stream::iter(incomings), sink::drain())); 404 | protocol_of_random_generation(party, 0, 3, rng).await 405 | } 406 | 407 | struct MockedDelivery { 408 | incoming: I, 409 | outgoing: O, 410 | } 411 | 412 | impl MockedDelivery { 413 | pub fn new(incoming: I, outgoing: O) -> Self { 414 | Self { incoming, outgoing } 415 | } 416 | } 417 | 418 | impl Delivery for MockedDelivery 419 | where 420 | I: Stream, IErr>> + Send + Unpin + 'static, 421 | O: Sink, Error = OErr> + Send + Unpin, 422 | IErr: std::error::Error + Send + Sync + 'static, 423 | OErr: std::error::Error + Send + Sync + 'static, 424 | { 425 | type Send = O; 426 | type Receive = I; 427 | type SendError = OErr; 428 | type ReceiveError = IErr; 429 | 430 | fn split(self) -> (Self::Receive, Self::Send) { 431 | (self.incoming, self.outgoing) 432 | } 433 | } 434 | 435 | #[derive(Debug, thiserror::Error)] 436 | #[error("dummy error")] 437 | struct DummyError; 438 | -------------------------------------------------------------------------------- /round-based/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | 4 | .idea/ 5 | -------------------------------------------------------------------------------- /round-based/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "round-based" 3 | version = "0.2.0" 4 | edition = "2021" 5 | license = "MIT OR Apache-2.0" 6 | description = "Driver for MPC protocols" 7 | repository = "https://github.com/ZenGo-X/round-based-protocol" 8 | categories = ["asynchronous", "cryptography", "network-programming"] 9 | keywords = ["round-based", "mpc", "protocol"] 10 | readme = "../README.md" 11 | 12 | [package.metadata.docs.rs] 13 | all-features = true 14 | rustdoc-args = ["--cfg", "docsrs"] 15 | 16 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 17 | 18 | [dependencies] 19 | futures-util = { version = "0.3", default-features = false, features = ["sink"] } 20 | phantom-type = "0.3" 21 | tracing = "0.1" 22 | thiserror = "1" 23 | 24 | round-based-derive = { version = "0.2", optional = true } 25 | 26 | tokio = { version = "1", features = ["rt"], optional = true } 27 | tokio-stream = { version = "0.1", features = ["sync"], optional = true } 28 | 29 | [dev-dependencies] 30 | trybuild = "1" 31 | matches = "0.1" 32 | futures = { version = "0.3", default-features = false } 33 | 34 | [features] 35 | default = [] 36 | dev = ["tokio/sync", "tokio-stream"] 37 | derive = ["round-based-derive"] 38 | runtime-tokio = ["tokio"] 39 | 40 | [[test]] 41 | name = "derive" 42 | required-features = ["derive"] 43 | -------------------------------------------------------------------------------- /round-based/README.md: -------------------------------------------------------------------------------- 1 | round-based 2 | -------------------------------------------------------------------------------- /round-based/src/_docs.rs: -------------------------------------------------------------------------------- 1 | use std::convert::Infallible; 2 | 3 | use phantom_type::PhantomType; 4 | 5 | use crate::{Delivery, Incoming, Outgoing}; 6 | 7 | pub fn fake_delivery() -> impl Delivery { 8 | struct FakeDelivery(PhantomType); 9 | impl Delivery for FakeDelivery { 10 | type Send = futures_util::sink::Drain>; 11 | type Receive = futures_util::stream::Pending, Infallible>>; 12 | 13 | type SendError = Infallible; 14 | type ReceiveError = Infallible; 15 | 16 | fn split(self) -> (Self::Receive, Self::Send) { 17 | (futures_util::stream::pending(), futures_util::sink::drain()) 18 | } 19 | } 20 | FakeDelivery(PhantomType::new()) 21 | } 22 | -------------------------------------------------------------------------------- /round-based/src/delivery.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error; 2 | 3 | use futures_util::{Sink, Stream}; 4 | 5 | /// Networking abstraction 6 | /// 7 | /// Basically, it's pair of channels: [`Stream`] for receiving messages, and [`Sink`] for sending 8 | /// messages to other parties. 9 | pub trait Delivery { 10 | /// Outgoing delivery channel 11 | type Send: Sink, Error = Self::SendError> + Unpin; 12 | /// Incoming delivery channel 13 | type Receive: Stream, Self::ReceiveError>> + Unpin; 14 | /// Error of outgoing delivery channel 15 | type SendError: Error + Send + Sync + 'static; 16 | /// Error of incoming delivery channel 17 | type ReceiveError: Error + Send + Sync + 'static; 18 | /// Returns a pair of incoming and outgoing delivery channels 19 | fn split(self) -> (Self::Receive, Self::Send); 20 | } 21 | 22 | impl Delivery for (I, O) 23 | where 24 | I: Stream, IErr>> + Unpin, 25 | O: Sink, Error = OErr> + Unpin, 26 | IErr: Error + Send + Sync + 'static, 27 | OErr: Error + Send + Sync + 'static, 28 | { 29 | type Send = O; 30 | type Receive = I; 31 | type SendError = OErr; 32 | type ReceiveError = IErr; 33 | 34 | fn split(self) -> (Self::Receive, Self::Send) { 35 | (self.0, self.1) 36 | } 37 | } 38 | 39 | /// Incoming message 40 | #[derive(Debug, Clone, Copy, Eq, PartialEq)] 41 | pub struct Incoming { 42 | /// Index of a message 43 | pub id: MsgId, 44 | /// Index of a party who sent the message 45 | pub sender: PartyIndex, 46 | /// Indicates whether it's a broadcast message (meaning that this message is received by all the 47 | /// parties), or p2p (private message sent by `sender`) 48 | pub msg_type: MessageType, 49 | /// Received message 50 | pub msg: M, 51 | } 52 | 53 | /// Message type (broadcast or p2p) 54 | #[derive(Debug, Copy, Clone, Eq, PartialEq)] 55 | pub enum MessageType { 56 | /// Message was broadcasted 57 | Broadcast, 58 | /// P2P message 59 | P2P, 60 | } 61 | 62 | /// Index of party involved in the protocol 63 | pub type PartyIndex = u16; 64 | /// ID of received message 65 | /// 66 | /// Can be used to retrieve extra information about message from delivery layer when needed. 67 | /// E.g. if malicious party is detected, we need a proof that received message was sent by this 68 | /// party, so message id should be used to retrieve signature and original message. 69 | pub type MsgId = u64; 70 | 71 | impl Incoming { 72 | /// Maps `Incoming` to `Incoming` by applying a function to the message body 73 | pub fn map(self, f: F) -> Incoming 74 | where 75 | F: FnOnce(M) -> T, 76 | { 77 | Incoming { 78 | id: self.id, 79 | sender: self.sender, 80 | msg_type: self.msg_type, 81 | msg: f(self.msg), 82 | } 83 | } 84 | 85 | /// Maps `Incoming` to `Result, E>` by applying a function `fn(M) -> Result` 86 | /// to the message body 87 | pub fn try_map(self, f: F) -> Result, E> 88 | where 89 | F: FnOnce(M) -> Result, 90 | { 91 | Ok(Incoming { 92 | id: self.id, 93 | sender: self.sender, 94 | msg_type: self.msg_type, 95 | msg: f(self.msg)?, 96 | }) 97 | } 98 | 99 | /// Converts `&Incoming` to `Incoming<&M>` 100 | pub fn as_ref(&self) -> Incoming<&M> { 101 | Incoming { 102 | id: self.id, 103 | sender: self.sender, 104 | msg_type: self.msg_type, 105 | msg: &self.msg, 106 | } 107 | } 108 | 109 | /// Checks whether it's broadcast message 110 | pub fn is_broadcast(&self) -> bool { 111 | matches!(self.msg_type, MessageType::Broadcast { .. }) 112 | } 113 | 114 | /// Checks whether it's p2p message 115 | pub fn is_p2p(&self) -> bool { 116 | matches!(self.msg_type, MessageType::P2P) 117 | } 118 | } 119 | 120 | /// Outgoing message 121 | #[derive(Debug, Clone, Copy, PartialEq)] 122 | pub struct Outgoing { 123 | /// Message destination: either one party (p2p message) or all parties (broadcast message) 124 | pub recipient: MessageDestination, 125 | /// Message being sent 126 | pub msg: M, 127 | } 128 | 129 | impl Outgoing { 130 | /// Constructs an outgoing message addressed to all parties 131 | pub fn broadcast(msg: M) -> Self { 132 | Self { 133 | recipient: MessageDestination::AllParties, 134 | msg, 135 | } 136 | } 137 | 138 | /// Constructs an outgoing message addressed to one party 139 | pub fn p2p(recipient: PartyIndex, msg: M) -> Self { 140 | Self { 141 | recipient: MessageDestination::OneParty(recipient), 142 | msg, 143 | } 144 | } 145 | 146 | /// Maps `Outgoing` to `Outgoing` by applying a function to the message body 147 | pub fn map(self, f: F) -> Outgoing 148 | where 149 | F: FnOnce(M) -> M2, 150 | { 151 | Outgoing { 152 | recipient: self.recipient, 153 | msg: f(self.msg), 154 | } 155 | } 156 | 157 | /// Converts `&Outgoing` to `Outgoing<&M>` 158 | pub fn as_ref(&self) -> Outgoing<&M> { 159 | Outgoing { 160 | recipient: self.recipient, 161 | msg: &self.msg, 162 | } 163 | } 164 | 165 | /// Checks whether it's broadcast message 166 | pub fn is_broadcast(&self) -> bool { 167 | self.recipient.is_broadcast() 168 | } 169 | 170 | /// Checks whether it's p2p message 171 | pub fn is_p2p(&self) -> bool { 172 | self.recipient.is_p2p() 173 | } 174 | } 175 | 176 | /// Destination of an outgoing message 177 | #[derive(Debug, Copy, Clone, Eq, PartialEq)] 178 | pub enum MessageDestination { 179 | /// Broadcast message 180 | AllParties, 181 | /// P2P message 182 | OneParty(PartyIndex), 183 | } 184 | 185 | impl MessageDestination { 186 | /// Returns `true` if it's p2p message 187 | pub fn is_p2p(&self) -> bool { 188 | matches!(self, MessageDestination::OneParty(_)) 189 | } 190 | /// Returns `true` if it's broadcast message 191 | pub fn is_broadcast(&self) -> bool { 192 | matches!(self, MessageDestination::AllParties { .. }) 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /round-based/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! ![License: MIT](https://img.shields.io/crates/l/round-based.svg) 2 | //! [![Docs](https://docs.rs/round-based/badge.svg)](https://docs.rs/round-based) 3 | //! [![Crates io](https://img.shields.io/crates/v/round-based.svg)](https://crates.io/crates/round-based) 4 | //! 5 | //! An MPC framework that unifies and simplifies the way of developing and working with 6 | //! multiparty protocols (e.g. threshold signing, random beacons, etc.). 7 | //! 8 | //! ## Goals 9 | //! 10 | //! * Async friendly \ 11 | //! Async is the most simple and efficient way of doing networking in Rust 12 | //! * Simple, configurable \ 13 | //! Protocol can be carried out in a few lines of code: check out examples. 14 | //! * Independent of networking layer \ 15 | //! We use abstractions [`Stream`](futures_util::Stream) and [`Sink`](futures_util::Sink) to receive and send messages. 16 | //! 17 | //! ## Networking 18 | //! 19 | //! In order to run an MPC protocol, transport layer needs to be defined. All you have to do is to 20 | //! implement [`Delivery`] trait which is basically a stream and a sink for receiving and sending messages. 21 | //! 22 | //! Message delivery should meet certain criterias that differ from protocol to protocol (refer to 23 | //! the documentation of the protocol you're using), but usually they are: 24 | //! 25 | //! * Messages should be authenticated \ 26 | //! Each message should be signed with identity key of the sender. This implies having Public Key 27 | //! Infrastructure. 28 | //! * P2P messages should be encrypted \ 29 | //! Only recipient should be able to learn the content of p2p message 30 | //! * Broadcast channel should be reliable \ 31 | //! Some protocols may require broadcast channel to be reliable. Simply saying, when party receives a 32 | //! broadcast message over reliable channel it should be ensured that everybody else received the same 33 | //! message. 34 | //! 35 | //! ## Features 36 | //! 37 | //! * `dev` enables development tools such as [protocol simulation](simulation) 38 | //! * `runtime-tokio` enables [tokio]-specific implementation of [async runtime](runtime) 39 | 40 | #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg, doc_cfg_hide))] 41 | #![deny(missing_docs)] 42 | #![forbid(unused_crate_dependencies)] 43 | 44 | /// Fixes false-positive of `unused_crate_dependencies` lint that only occure in the tests 45 | #[cfg(test)] 46 | mod false_positives { 47 | use futures as _; 48 | use trybuild as _; 49 | } 50 | 51 | mod delivery; 52 | pub mod party; 53 | pub mod rounds_router; 54 | pub mod runtime; 55 | 56 | #[cfg(feature = "dev")] 57 | pub mod simulation; 58 | 59 | pub use self::delivery::*; 60 | #[doc(no_inline)] 61 | pub use self::{ 62 | party::{Mpc, MpcParty}, 63 | rounds_router::{ProtocolMessage, RoundMessage}, 64 | }; 65 | 66 | #[doc(hidden)] 67 | pub mod _docs; 68 | 69 | /// Derives [`ProtocolMessage`] and [`RoundMessage`] traits 70 | /// 71 | /// See [`ProtocolMessage`] docs for more details 72 | #[cfg(feature = "derive")] 73 | pub use round_based_derive::ProtocolMessage; 74 | -------------------------------------------------------------------------------- /round-based/src/party.rs: -------------------------------------------------------------------------------- 1 | //! Party of MPC protocol 2 | //! 3 | //! [`MpcParty`] is party of MPC protocol, connected to network, ready to start carrying out the protocol. 4 | //! 5 | //! ```rust 6 | //! use round_based::{Mpc, MpcParty, Delivery, PartyIndex}; 7 | //! 8 | //! # struct KeygenMsg; 9 | //! # struct KeyShare; 10 | //! # struct Error; 11 | //! # type Result = std::result::Result; 12 | //! # async fn doc() -> Result<()> { 13 | //! async fn keygen(party: M, i: PartyIndex, n: u16) -> Result 14 | //! where 15 | //! M: Mpc 16 | //! { 17 | //! // ... 18 | //! # unimplemented!() 19 | //! } 20 | //! async fn connect() -> impl Delivery { 21 | //! // ... 22 | //! # round_based::_docs::fake_delivery() 23 | //! } 24 | //! 25 | //! let delivery = connect().await; 26 | //! let party = MpcParty::connected(delivery); 27 | //! 28 | //! # let (i, n) = (1, 3); 29 | //! let keyshare = keygen(party, i, n).await?; 30 | //! # Ok(()) } 31 | //! ``` 32 | 33 | use std::error::Error; 34 | 35 | use phantom_type::PhantomType; 36 | 37 | use crate::delivery::Delivery; 38 | use crate::runtime::{self, AsyncRuntime}; 39 | 40 | /// Party of MPC protocol (trait) 41 | /// 42 | /// [`MpcParty`] is the only struct that implement this trait. Motivation to have this trait is to fewer amount of 43 | /// generic bounds that are needed to be specified. 44 | /// 45 | /// Typical usage of this trait when implementing MPC protocol: 46 | /// 47 | /// ```rust 48 | /// use round_based::{Mpc, MpcParty, PartyIndex}; 49 | /// 50 | /// # struct Msg; 51 | /// async fn keygen(party: M, i: PartyIndex, n: u16) 52 | /// where 53 | /// M: Mpc 54 | /// { 55 | /// let MpcParty{ delivery, .. } = party.into_party(); 56 | /// // ... 57 | /// } 58 | /// ``` 59 | /// 60 | /// If we didn't have this trait, generics would be less readable: 61 | /// ```rust 62 | /// use round_based::{MpcParty, Delivery, runtime::AsyncRuntime, PartyIndex}; 63 | /// 64 | /// # struct Msg; 65 | /// async fn keygen(party: MpcParty, i: PartyIndex, n: u16) 66 | /// where 67 | /// D: Delivery, 68 | /// R: AsyncRuntime 69 | /// { 70 | /// // ... 71 | /// } 72 | /// ``` 73 | pub trait Mpc: internal::Sealed { 74 | /// MPC message 75 | type ProtocolMessage; 76 | /// Transport layer implementation 77 | type Delivery: Delivery< 78 | Self::ProtocolMessage, 79 | SendError = Self::SendError, 80 | ReceiveError = Self::ReceiveError, 81 | >; 82 | /// Async runtime 83 | type Runtime: AsyncRuntime; 84 | 85 | /// Sending message error 86 | type SendError: Error + Send + Sync + 'static; 87 | /// Receiving message error 88 | type ReceiveError: Error + Send + Sync + 'static; 89 | 90 | /// Converts into [`MpcParty`] 91 | fn into_party(self) -> MpcParty; 92 | } 93 | 94 | mod internal { 95 | pub trait Sealed {} 96 | } 97 | 98 | /// Party of MPC protocol 99 | #[non_exhaustive] 100 | pub struct MpcParty { 101 | /// Defines transport layer 102 | pub delivery: D, 103 | /// Defines how computationally heavy tasks should be handled 104 | pub runtime: R, 105 | _msg: PhantomType, 106 | } 107 | 108 | impl MpcParty 109 | where 110 | M: Send + 'static, 111 | D: Delivery, 112 | { 113 | /// Party connected to the network 114 | /// 115 | /// Takes the delivery object determining how to deliver/receive other parties' messages 116 | pub fn connected(delivery: D) -> Self { 117 | Self { 118 | delivery, 119 | runtime: Default::default(), 120 | _msg: PhantomType::new(), 121 | } 122 | } 123 | } 124 | 125 | impl MpcParty 126 | where 127 | M: Send + 'static, 128 | D: Delivery, 129 | { 130 | /// Specifies a [async runtime](runtime) 131 | pub fn set_runtime(self, runtime: R) -> MpcParty 132 | where 133 | R: AsyncRuntime, 134 | { 135 | MpcParty { 136 | delivery: self.delivery, 137 | runtime, 138 | _msg: self._msg, 139 | } 140 | } 141 | } 142 | 143 | impl internal::Sealed for MpcParty {} 144 | 145 | impl Mpc for MpcParty 146 | where 147 | D: Delivery, 148 | D::SendError: Error + Send + Sync + 'static, 149 | D::ReceiveError: Error + Send + Sync + 'static, 150 | R: AsyncRuntime, 151 | { 152 | type ProtocolMessage = M; 153 | type Delivery = D; 154 | type Runtime = R; 155 | 156 | type SendError = D::SendError; 157 | type ReceiveError = D::ReceiveError; 158 | 159 | fn into_party(self) -> MpcParty { 160 | self 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /round-based/src/rounds_router/mod.rs: -------------------------------------------------------------------------------- 1 | //! Routes incoming MPC messages between rounds 2 | //! 3 | //! [`RoundsRouter`] is an essential building block of MPC protocol, it processes incoming messages, groups 4 | //! them by rounds, and provides convenient API for retrieving received messages at certain round. 5 | //! 6 | //! ## Example 7 | //! 8 | //! ```rust 9 | //! use round_based::{Mpc, MpcParty, ProtocolMessage, Delivery, PartyIndex}; 10 | //! use round_based::rounds_router::{RoundsRouter, simple_store::{RoundInput, RoundMsgs}}; 11 | //! 12 | //! #[derive(ProtocolMessage)] 13 | //! pub enum Msg { 14 | //! Round1(Msg1), 15 | //! Round2(Msg2), 16 | //! } 17 | //! 18 | //! pub struct Msg1 { /* ... */ } 19 | //! pub struct Msg2 { /* ... */ } 20 | //! 21 | //! pub async fn some_mpc_protocol(party: M, i: PartyIndex, n: u16) -> Result 22 | //! where 23 | //! M: Mpc, 24 | //! { 25 | //! let MpcParty{ delivery, .. } = party.into_party(); 26 | //! 27 | //! let (incomings, _outgoings) = delivery.split(); 28 | //! 29 | //! // Build `Rounds` 30 | //! let mut rounds = RoundsRouter::builder(); 31 | //! let round1 = rounds.add_round(RoundInput::::broadcast(i, n)); 32 | //! let round2 = rounds.add_round(RoundInput::::p2p(i, n)); 33 | //! let mut rounds = rounds.listen(incomings); 34 | //! 35 | //! // Receive messages from round 1 36 | //! let msgs: RoundMsgs = rounds.complete(round1).await?; 37 | //! 38 | //! // ... process received messages 39 | //! 40 | //! // Receive messages from round 2 41 | //! let msgs = rounds.complete(round2).await?; 42 | //! 43 | //! // ... 44 | //! # todo!() 45 | //! } 46 | //! # type Output = (); 47 | //! # type Error = Box; 48 | //! ``` 49 | 50 | use std::any::Any; 51 | use std::collections::HashMap; 52 | use std::convert::Infallible; 53 | use std::fmt::Debug; 54 | use std::mem; 55 | 56 | use futures_util::{Stream, StreamExt}; 57 | use phantom_type::PhantomType; 58 | use thiserror::Error; 59 | use tracing::{debug, error, trace, trace_span, warn, Span}; 60 | 61 | use crate::Incoming; 62 | 63 | #[doc(inline)] 64 | pub use self::errors::CompleteRoundError; 65 | pub use self::store::*; 66 | 67 | pub mod simple_store; 68 | mod store; 69 | 70 | /// Routes received messages between protocol rounds 71 | /// 72 | /// See [module level](self) documentation to learn more about it. 73 | pub struct RoundsRouter { 74 | incomings: S, 75 | rounds: HashMap + Send>>>, 76 | } 77 | 78 | impl RoundsRouter { 79 | /// Instantiates [`RoundsRouterBuilder`] 80 | pub fn builder() -> RoundsRouterBuilder { 81 | RoundsRouterBuilder::new() 82 | } 83 | } 84 | 85 | impl RoundsRouter 86 | where 87 | M: ProtocolMessage, 88 | S: Stream, E>> + Unpin, 89 | { 90 | /// Completes specified round 91 | /// 92 | /// Waits until all messages at specified round are received. Returns received 93 | /// messages if round is successfully completed, or error otherwise. 94 | #[inline(always)] 95 | pub async fn complete( 96 | &mut self, 97 | round: Round, 98 | ) -> Result> 99 | where 100 | R: MessagesStore, 101 | M: RoundMessage, 102 | { 103 | let round_number = >::ROUND; 104 | let span = trace_span!("Round", n = round_number); 105 | debug!(parent: &span, "pending round to complete"); 106 | 107 | match self.complete_with_span(&span, round).await { 108 | Ok(output) => { 109 | trace!(parent: &span, "round successfully completed"); 110 | Ok(output) 111 | } 112 | Err(err) => { 113 | error!(parent: &span, %err, "round terminated with error"); 114 | Err(err) 115 | } 116 | } 117 | } 118 | 119 | async fn complete_with_span( 120 | &mut self, 121 | span: &Span, 122 | _round: Round, 123 | ) -> Result> 124 | where 125 | R: MessagesStore, 126 | M: RoundMessage, 127 | { 128 | let pending_round = >::ROUND; 129 | if let Some(output) = self.retrieve_round_output_if_its_completed::() { 130 | return output; 131 | } 132 | 133 | loop { 134 | let incoming = match self.incomings.next().await { 135 | Some(Ok(msg)) => msg, 136 | Some(Err(err)) => return Err(errors::IoError::Io(err).into()), 137 | None => return Err(errors::IoError::UnexpectedEof.into()), 138 | }; 139 | let message_round_n = incoming.msg.round(); 140 | 141 | let message_round = match self.rounds.get_mut(&message_round_n) { 142 | Some(Some(round)) => round, 143 | Some(None) => { 144 | warn!( 145 | parent: span, 146 | n = message_round_n, 147 | "got message for the round that was already completed, ignoring it" 148 | ); 149 | continue; 150 | } 151 | None => { 152 | return Err( 153 | errors::RoundsMisuse::UnregisteredRound { n: message_round_n }.into(), 154 | ) 155 | } 156 | }; 157 | if message_round.needs_more_messages().no() { 158 | warn!( 159 | parent: span, 160 | n = message_round_n, 161 | "received message for the round that was already completed, ignoring it" 162 | ); 163 | continue; 164 | } 165 | message_round.process_message(incoming); 166 | 167 | if pending_round == message_round_n { 168 | if let Some(output) = self.retrieve_round_output_if_its_completed::() { 169 | return output; 170 | } 171 | } 172 | } 173 | } 174 | 175 | fn retrieve_round_output_if_its_completed( 176 | &mut self, 177 | ) -> Option>> 178 | where 179 | R: MessagesStore, 180 | M: RoundMessage, 181 | { 182 | let round_number = >::ROUND; 183 | let round_slot = match self 184 | .rounds 185 | .get_mut(&round_number) 186 | .ok_or(errors::RoundsMisuse::UnregisteredRound { n: round_number }) 187 | { 188 | Ok(slot) => slot, 189 | Err(err) => return Some(Err(err.into())), 190 | }; 191 | let round = match round_slot 192 | .as_mut() 193 | .ok_or(errors::RoundsMisuse::RoundAlreadyCompleted) 194 | { 195 | Ok(round) => round, 196 | Err(err) => return Some(Err(err.into())), 197 | }; 198 | if round.needs_more_messages().no() { 199 | Some(Self::retrieve_round_output::(round_slot)) 200 | } else { 201 | None 202 | } 203 | } 204 | 205 | fn retrieve_round_output( 206 | slot: &mut Option + Send>>, 207 | ) -> Result> 208 | where 209 | R: MessagesStore, 210 | M: RoundMessage, 211 | { 212 | let mut round = slot.take().ok_or(errors::RoundsMisuse::UnregisteredRound { 213 | n: >::ROUND, 214 | })?; 215 | match round.take_output() { 216 | Ok(Ok(any)) => Ok(*any 217 | .downcast::() 218 | .or(Err(CompleteRoundError::from( 219 | errors::Bug::MismatchedOutputType, 220 | )))?), 221 | Ok(Err(any)) => Err(any 222 | .downcast::>() 223 | .or(Err(CompleteRoundError::from( 224 | errors::Bug::MismatchedErrorType, 225 | )))? 226 | .map_io_err(|e| match e {})), 227 | Err(err) => Err(errors::Bug::TakeRoundResult(err).into()), 228 | } 229 | } 230 | } 231 | 232 | /// Builds [`RoundsRouter`] 233 | pub struct RoundsRouterBuilder { 234 | rounds: HashMap + Send>>>, 235 | } 236 | 237 | impl RoundsRouterBuilder 238 | where 239 | M: ProtocolMessage + 'static, 240 | { 241 | /// Constructs [`RoundsRouterBuilder`] 242 | /// 243 | /// Alias to [`RoundsRouter::builder`] 244 | pub fn new() -> Self { 245 | Self { 246 | rounds: HashMap::new(), 247 | } 248 | } 249 | 250 | /// Registers new round 251 | /// 252 | /// ## Panics 253 | /// Panics if round `R` was already registered 254 | pub fn add_round(&mut self, message_store: R) -> Round 255 | where 256 | R: MessagesStore + Send + 'static, 257 | R::Output: Send, 258 | R::Error: Send, 259 | M: RoundMessage, 260 | { 261 | let overridden_round = self.rounds.insert( 262 | M::ROUND, 263 | Some(Box::new(ProcessRoundMessageImpl::InProgress { 264 | store: message_store, 265 | _ph: PhantomType::new(), 266 | })), 267 | ); 268 | if overridden_round.is_some() { 269 | panic!("round {} is overridden", M::ROUND); 270 | } 271 | Round { 272 | _ph: PhantomType::new(), 273 | } 274 | } 275 | 276 | /// Builds [`RoundsRouter`] 277 | /// 278 | /// Takes a stream of incoming messages which will be routed between registered rounds 279 | pub fn listen(self, incomings: S) -> RoundsRouter 280 | where 281 | S: Stream, E>>, 282 | { 283 | RoundsRouter { 284 | incomings, 285 | rounds: self.rounds, 286 | } 287 | } 288 | } 289 | 290 | /// A round of MPC protocol 291 | /// 292 | /// `Round` can be used to retrieve messages received at this round by calling [`RoundsRouter::complete`]. See 293 | /// [module level](self) documentation to see usage. 294 | pub struct Round { 295 | _ph: PhantomType, 296 | } 297 | 298 | trait ProcessRoundMessage { 299 | type Msg; 300 | 301 | /// Processes round message 302 | /// 303 | /// Before calling this method you must ensure that `.needs_more_messages()` returns `Yes`, 304 | /// otherwise calling this method is unexpected. 305 | fn process_message(&mut self, msg: Incoming); 306 | 307 | /// Indicated whether the store needs more messages 308 | /// 309 | /// If it returns `Yes`, then you need to collect more messages to complete round. If it's `No` 310 | /// then you need to take the round output by calling `.take_output()`. 311 | fn needs_more_messages(&self) -> NeedsMoreMessages; 312 | 313 | /// Tries to obtain round output 314 | /// 315 | /// Can be called once `process_message()` returned `NeedMoreMessages::No`. 316 | /// 317 | /// Returns: 318 | /// * `Ok(Ok(any))` — round is successfully completed, `any` needs to be downcasted to `MessageStore::Output` 319 | /// * `Ok(Err(any))` — round has terminated with an error, `any` needs to be downcasted to `CompleteRoundError` 320 | /// * `Err(err)` — couldn't retrieve the output, see [`TakeOutputError`] 321 | fn take_output(&mut self) -> Result, Box>, TakeOutputError>; 322 | } 323 | 324 | #[derive(Debug, Error)] 325 | enum TakeOutputError { 326 | #[error("output is already taken")] 327 | AlreadyTaken, 328 | #[error("output is not ready yet, more messages are needed")] 329 | NotReady, 330 | } 331 | 332 | enum ProcessRoundMessageImpl> { 333 | InProgress { store: S, _ph: PhantomType }, 334 | Completed(Result>), 335 | Gone, 336 | } 337 | 338 | impl ProcessRoundMessageImpl 339 | where 340 | S: MessagesStore, 341 | M: ProtocolMessage + RoundMessage, 342 | { 343 | fn _process_message( 344 | store: &mut S, 345 | msg: Incoming, 346 | ) -> Result<(), CompleteRoundError> { 347 | let msg = msg.try_map(M::from_protocol_message).map_err(|msg| { 348 | errors::Bug::MessageFromAnotherRound { 349 | actual_number: msg.round(), 350 | expected_round: M::ROUND, 351 | } 352 | })?; 353 | 354 | store 355 | .add_message(msg) 356 | .map_err(CompleteRoundError::ProcessMessage)?; 357 | Ok(()) 358 | } 359 | } 360 | 361 | impl ProcessRoundMessage for ProcessRoundMessageImpl 362 | where 363 | S: MessagesStore, 364 | M: ProtocolMessage + RoundMessage, 365 | { 366 | type Msg = M; 367 | 368 | fn process_message(&mut self, msg: Incoming) { 369 | let store = match self { 370 | Self::InProgress { store, .. } => store, 371 | _ => { 372 | return; 373 | } 374 | }; 375 | 376 | match Self::_process_message(store, msg) { 377 | Ok(()) => { 378 | if store.wants_more() { 379 | return; 380 | } 381 | 382 | let store = match mem::replace(self, Self::Gone) { 383 | Self::InProgress { store, .. } => store, 384 | _ => { 385 | *self = Self::Completed(Err(errors::Bug::IncoherentState { 386 | expected: "InProgress", 387 | justification: 388 | "we checked at beginning of the function that `state` is InProgress", 389 | } 390 | .into())); 391 | return; 392 | } 393 | }; 394 | 395 | match store.output() { 396 | Ok(output) => *self = Self::Completed(Ok(output)), 397 | Err(_err) => { 398 | *self = 399 | Self::Completed(Err(errors::ImproperStoreImpl::StoreDidntOutput.into())) 400 | } 401 | } 402 | } 403 | Err(err) => { 404 | *self = Self::Completed(Err(err)); 405 | } 406 | } 407 | } 408 | 409 | fn needs_more_messages(&self) -> NeedsMoreMessages { 410 | match self { 411 | Self::InProgress { .. } => NeedsMoreMessages::Yes, 412 | _ => NeedsMoreMessages::No, 413 | } 414 | } 415 | 416 | fn take_output(&mut self) -> Result, Box>, TakeOutputError> { 417 | match self { 418 | Self::InProgress { .. } => return Err(TakeOutputError::NotReady), 419 | Self::Gone => return Err(TakeOutputError::AlreadyTaken), 420 | _ => (), 421 | } 422 | match mem::replace(self, Self::Gone) { 423 | Self::Completed(Ok(output)) => Ok(Ok(Box::new(output))), 424 | Self::Completed(Err(err)) => Ok(Err(Box::new(err))), 425 | _ => unreachable!("it's checked to be completed"), 426 | } 427 | } 428 | } 429 | 430 | enum NeedsMoreMessages { 431 | Yes, 432 | No, 433 | } 434 | 435 | #[allow(dead_code)] 436 | impl NeedsMoreMessages { 437 | pub fn yes(&self) -> bool { 438 | matches!(self, Self::Yes) 439 | } 440 | pub fn no(&self) -> bool { 441 | matches!(self, Self::No) 442 | } 443 | } 444 | 445 | /// When something goes wrong 446 | pub mod errors { 447 | use thiserror::Error; 448 | 449 | use super::TakeOutputError; 450 | 451 | /// Error indicating that `Rounds` failed to complete certain round 452 | #[derive(Debug, Error)] 453 | pub enum CompleteRoundError { 454 | /// [`MessagesStore`](super::MessagesStore) failed to process this message 455 | #[error("failed to process the message")] 456 | ProcessMessage(#[source] ProcessErr), 457 | /// Receiving next message resulted into i/o error 458 | #[error("receive next message")] 459 | Io( 460 | #[source] 461 | #[from] 462 | IoError, 463 | ), 464 | /// Some implementation specific error 465 | /// 466 | /// Error may be result of improper `MessagesStore` implementation, API misuse, or bug 467 | /// in `Rounds` implementation 468 | #[error("implementation error")] 469 | Other(#[source] OtherError), 470 | } 471 | 472 | /// Error indicating that receiving next message resulted into i/o error 473 | #[derive(Error, Debug)] 474 | pub enum IoError { 475 | /// I/O error 476 | #[error("i/o error")] 477 | Io(#[source] E), 478 | /// Encountered unexpected EOF 479 | #[error("unexpected eof")] 480 | UnexpectedEof, 481 | } 482 | 483 | /// Some implementation specific error 484 | /// 485 | /// Error may be result of improper `MessagesStore` implementation, API misuse, or bug 486 | /// in `Rounds` implementation 487 | #[derive(Error, Debug)] 488 | #[error(transparent)] 489 | pub struct OtherError(OtherReason); 490 | 491 | #[derive(Error, Debug)] 492 | pub(super) enum OtherReason { 493 | #[error("improper `MessagesStore` implementation")] 494 | ImproperStoreImpl(ImproperStoreImpl), 495 | #[error("`Rounds` API misuse")] 496 | RoundsMisuse(RoundsMisuse), 497 | #[error("bug in `Rounds` (please, open a issue)")] 498 | Bug(Bug), 499 | } 500 | 501 | #[derive(Debug, Error)] 502 | pub(super) enum ImproperStoreImpl { 503 | /// Store indicated that it received enough messages but didn't output 504 | /// 505 | /// I.e. [`store.wants_more()`] returned `false`, but `store.output()` returned `Err(_)`. 506 | #[error("store didn't output")] 507 | StoreDidntOutput, 508 | } 509 | 510 | #[derive(Debug, Error)] 511 | pub(super) enum RoundsMisuse { 512 | #[error("round is already completed")] 513 | RoundAlreadyCompleted, 514 | #[error("round {n} is not registered")] 515 | UnregisteredRound { n: u16 }, 516 | } 517 | 518 | #[derive(Debug, Error)] 519 | pub(super) enum Bug { 520 | #[error( 521 | "message originates from another round: we process messages from round \ 522 | {expected_round}, got message from round {actual_number}" 523 | )] 524 | MessageFromAnotherRound { 525 | expected_round: u16, 526 | actual_number: u16, 527 | }, 528 | #[error("state is incoherent, it's expected to be {expected}: {justification}")] 529 | IncoherentState { 530 | expected: &'static str, 531 | justification: &'static str, 532 | }, 533 | #[error("mismatched output type")] 534 | MismatchedOutputType, 535 | #[error("mismatched error type")] 536 | MismatchedErrorType, 537 | #[error("take round result")] 538 | TakeRoundResult(#[source] TakeOutputError), 539 | } 540 | 541 | impl CompleteRoundError { 542 | pub(super) fn map_io_err(self, f: F) -> CompleteRoundError 543 | where 544 | F: FnOnce(IoErr) -> E, 545 | { 546 | match self { 547 | CompleteRoundError::Io(err) => CompleteRoundError::Io(err.map_err(f)), 548 | CompleteRoundError::ProcessMessage(err) => CompleteRoundError::ProcessMessage(err), 549 | CompleteRoundError::Other(err) => CompleteRoundError::Other(err), 550 | } 551 | } 552 | } 553 | 554 | impl IoError { 555 | pub(super) fn map_err(self, f: F) -> IoError 556 | where 557 | F: FnOnce(E) -> B, 558 | { 559 | match self { 560 | IoError::Io(e) => IoError::Io(f(e)), 561 | IoError::UnexpectedEof => IoError::UnexpectedEof, 562 | } 563 | } 564 | } 565 | 566 | macro_rules! impl_from_other_error { 567 | ($($err:ident),+,) => {$( 568 | impl From<$err> for CompleteRoundError { 569 | fn from(err: $err) -> Self { 570 | Self::Other(OtherError(OtherReason::$err(err))) 571 | } 572 | } 573 | )+}; 574 | } 575 | 576 | impl_from_other_error! { 577 | ImproperStoreImpl, 578 | RoundsMisuse, 579 | Bug, 580 | } 581 | } 582 | -------------------------------------------------------------------------------- /round-based/src/rounds_router/simple_store.rs: -------------------------------------------------------------------------------- 1 | //! Simple implementation of `MessagesStore` 2 | 3 | use std::iter; 4 | 5 | use thiserror::Error; 6 | 7 | use crate::{Incoming, MessageType, MsgId, PartyIndex}; 8 | 9 | use super::MessagesStore; 10 | 11 | /// Simple implementation of [MessagesStore] that waits for all parties to send a message 12 | /// 13 | /// Round is considered complete when the store received a message from every party. Note that the 14 | /// store will ignore all the messages such as `msg.sender == local_party_index`. 15 | /// 16 | /// Once round is complete, it outputs [`RoundMsgs`]. 17 | /// 18 | /// ## Example 19 | /// ```rust 20 | /// # use round_based::rounds_router::{MessagesStore, simple_store::RoundInput}; 21 | /// # use round_based::{Incoming, MessageType}; 22 | /// # fn main() -> Result<(), Box> { 23 | /// let mut input = RoundInput::<&'static str>::broadcast(1, 3); 24 | /// input.add_message(Incoming{ 25 | /// id: 0, 26 | /// sender: 0, 27 | /// msg_type: MessageType::Broadcast, 28 | /// msg: "first party message", 29 | /// })?; 30 | /// input.add_message(Incoming{ 31 | /// id: 1, 32 | /// sender: 2, 33 | /// msg_type: MessageType::Broadcast, 34 | /// msg: "third party message", 35 | /// })?; 36 | /// assert!(!input.wants_more()); 37 | /// 38 | /// let output = input.output().unwrap(); 39 | /// assert_eq!(output.clone().into_vec_without_me(), ["first party message", "third party message"]); 40 | /// assert_eq!( 41 | /// output.clone().into_vec_including_me("my msg"), 42 | /// ["first party message", "my msg", "third party message"] 43 | /// ); 44 | /// # Ok(()) } 45 | /// ``` 46 | #[derive(Debug, Clone)] 47 | pub struct RoundInput { 48 | i: PartyIndex, 49 | n: u16, 50 | messages_ids: Vec, 51 | messages: Vec>, 52 | left_messages: u16, 53 | expected_msg_type: MessageType, 54 | } 55 | 56 | /// List of received messages 57 | #[derive(Debug, Clone)] 58 | pub struct RoundMsgs { 59 | i: PartyIndex, 60 | ids: Vec, 61 | messages: Vec, 62 | } 63 | 64 | impl RoundInput { 65 | /// Constructs new messages store 66 | /// 67 | /// Takes index of local party `i` and amount of parties `n` 68 | /// 69 | /// ## Panics 70 | /// Panics if `n` is less than 2 or `i` is not in the range `[0; n)`. 71 | pub fn new(i: PartyIndex, n: u16, msg_type: MessageType) -> Self { 72 | assert!(n >= 2); 73 | assert!(i < n); 74 | 75 | Self { 76 | i, 77 | n, 78 | messages_ids: vec![0; usize::from(n) - 1], 79 | messages: iter::repeat_with(|| None) 80 | .take(usize::from(n) - 1) 81 | .collect(), 82 | left_messages: n - 1, 83 | expected_msg_type: msg_type, 84 | } 85 | } 86 | 87 | /// Construct a new store for broadcast messages 88 | /// 89 | /// The same as `RoundInput::new(i, n, MessageType::Broadcast)` 90 | pub fn broadcast(i: PartyIndex, n: u16) -> Self { 91 | Self::new(i, n, MessageType::Broadcast) 92 | } 93 | 94 | /// Construct a new store for p2p messages 95 | /// 96 | /// The same as `RoundInput::new(i, n, MessageType::P2P)` 97 | pub fn p2p(i: PartyIndex, n: u16) -> Self { 98 | Self::new(i, n, MessageType::P2P) 99 | } 100 | 101 | fn is_expected_type_of_msg(&self, msg_type: MessageType) -> bool { 102 | self.expected_msg_type == msg_type 103 | } 104 | } 105 | 106 | impl MessagesStore for RoundInput 107 | where 108 | M: 'static, 109 | { 110 | type Msg = M; 111 | type Output = RoundMsgs; 112 | type Error = RoundInputError; 113 | 114 | fn add_message(&mut self, msg: Incoming) -> Result<(), Self::Error> { 115 | if !self.is_expected_type_of_msg(msg.msg_type) { 116 | return Err(RoundInputError::MismatchedMessageType { 117 | msg_id: msg.id, 118 | expected: self.expected_msg_type, 119 | actual: msg.msg_type, 120 | } 121 | .into()); 122 | } 123 | if msg.sender == self.i { 124 | // Ignore own messages 125 | return Ok(()); 126 | } 127 | 128 | let index = usize::from(if msg.sender < self.i { 129 | msg.sender 130 | } else { 131 | msg.sender - 1 132 | }); 133 | 134 | match self.messages.get_mut(index) { 135 | Some(vacant @ None) => { 136 | *vacant = Some(msg.msg); 137 | self.messages_ids[index] = msg.id; 138 | self.left_messages -= 1; 139 | Ok(()) 140 | } 141 | Some(Some(_)) => Err(RoundInputError::AttemptToOverwriteReceivedMsg { 142 | msgs_ids: [self.messages_ids[index], msg.id], 143 | sender: msg.sender, 144 | } 145 | .into()), 146 | None => Err(RoundInputError::SenderIndexOutOfRange { 147 | msg_id: msg.id, 148 | sender: msg.sender, 149 | n: self.n, 150 | } 151 | .into()), 152 | } 153 | } 154 | 155 | fn wants_more(&self) -> bool { 156 | self.left_messages > 0 157 | } 158 | 159 | fn output(self) -> Result { 160 | if self.left_messages > 0 { 161 | Err(self) 162 | } else { 163 | Ok(RoundMsgs { 164 | i: self.i, 165 | ids: self.messages_ids, 166 | messages: self.messages.into_iter().flatten().collect(), 167 | }) 168 | } 169 | } 170 | } 171 | 172 | impl RoundMsgs { 173 | /// Returns vec of `n-1` received messages 174 | /// 175 | /// Messages appear in the list in ascending order of sender index. E.g. for n=4 and local party index i=2, 176 | /// the list would look like: `[{msg from i=0}, {msg from i=1}, {msg from i=3}]`. 177 | pub fn into_vec_without_me(self) -> Vec { 178 | self.messages 179 | } 180 | 181 | /// Returns vec of received messages plus party's own message 182 | /// 183 | /// Similar to `into_vec_without_me`, but inserts `my_msg` at position `i` in resulting list. Thus, i-th 184 | /// message in the list was received from i-th party. 185 | pub fn into_vec_including_me(mut self, my_msg: M) -> Vec { 186 | self.messages.insert(usize::from(self.i), my_msg); 187 | self.messages 188 | } 189 | 190 | /// Returns iterator over messages 191 | pub fn iter(&self) -> impl Iterator { 192 | self.messages.iter() 193 | } 194 | 195 | /// Returns iterator over received messages plus party's own message 196 | /// 197 | /// Similar to [`.iter()`](Self::iter), but inserts `my_msg` at position `i`. Thus, i-th message in the 198 | /// iterator is the message received from party `i`. 199 | pub fn iter_including_me<'m>(&'m self, my_msg: &'m M) -> impl Iterator { 200 | self.messages 201 | .iter() 202 | .take(usize::from(self.i)) 203 | .chain(iter::once(my_msg)) 204 | .chain(self.messages.iter().skip(usize::from(self.i))) 205 | } 206 | 207 | /// Returns iterator over messages with sender indexes 208 | /// 209 | /// Iterator yields `(sender_index, msg_id, message)` 210 | pub fn into_iter_indexed(self) -> impl Iterator { 211 | let parties_indexes = (0..self.i).chain(self.i + 1..); 212 | parties_indexes 213 | .zip(self.ids.into_iter()) 214 | .zip(self.messages) 215 | .map(|((party_ind, msg_id), msg)| (party_ind, msg_id, msg)) 216 | } 217 | 218 | /// Returns iterator over messages with sender indexes 219 | /// 220 | /// Iterator yields `(sender_index, msg_id, &message)` 221 | pub fn iter_indexed(&self) -> impl Iterator { 222 | let parties_indexes = (0..self.i).chain(self.i + 1..); 223 | parties_indexes 224 | .zip(&self.ids) 225 | .zip(&self.messages) 226 | .map(|((party_ind, msg_id), msg)| (party_ind, *msg_id, msg)) 227 | } 228 | } 229 | 230 | /// Error explaining why `RoundInput` wasn't able to process a message 231 | #[derive(Debug, Error)] 232 | pub enum RoundInputError { 233 | /// Party sent two messages in one round 234 | /// 235 | /// `msgs_ids` are ids of conflicting messages 236 | #[error("party {sender} tried to overwrite message")] 237 | AttemptToOverwriteReceivedMsg { 238 | /// IDs of conflicting messages 239 | msgs_ids: [MsgId; 2], 240 | /// Index of party who sent two messages in one round 241 | sender: PartyIndex, 242 | }, 243 | /// Unknown sender 244 | /// 245 | /// This error is thrown when index of sender is not in `[0; n)` where `n` is number of 246 | /// parties involved in the protocol (provided in [`RoundInput::new`]) 247 | #[error("sender index is out of range: sender={sender}, n={n}")] 248 | SenderIndexOutOfRange { 249 | /// Message ID 250 | msg_id: MsgId, 251 | /// Sender index 252 | sender: PartyIndex, 253 | /// Number of parties 254 | n: u16, 255 | }, 256 | /// Received message type doesn't match expectations 257 | /// 258 | /// For instance, this error is returned when it's expected to receive broadcast message, 259 | /// but party sent p2p message instead (which is rough protocol violation). 260 | #[error("expected message {expected:?}, got {actual:?}")] 261 | MismatchedMessageType { 262 | /// Message ID 263 | msg_id: MsgId, 264 | /// Expected type of message 265 | expected: MessageType, 266 | /// Actual type of message 267 | actual: MessageType, 268 | }, 269 | } 270 | 271 | #[cfg(test)] 272 | mod tests { 273 | use matches::assert_matches; 274 | 275 | use crate::rounds_router::store::MessagesStore; 276 | use crate::{Incoming, MessageType}; 277 | 278 | use super::{RoundInput, RoundInputError}; 279 | 280 | #[derive(Debug, Clone, PartialEq)] 281 | pub struct Msg(u16); 282 | 283 | #[test] 284 | fn store_outputs_received_messages() { 285 | let mut store = RoundInput::::new(3, 5, MessageType::P2P); 286 | 287 | let msgs = (0..5) 288 | .map(|s| Incoming { 289 | id: s.into(), 290 | sender: s, 291 | msg_type: MessageType::P2P, 292 | msg: Msg(10 + s), 293 | }) 294 | .filter(|incoming| incoming.sender != 3) 295 | .collect::>(); 296 | 297 | for msg in &msgs { 298 | assert!(store.wants_more()); 299 | store.add_message(msg.clone()).unwrap(); 300 | } 301 | 302 | assert!(!store.wants_more()); 303 | let received = store.output().unwrap(); 304 | 305 | // without me 306 | let msgs: Vec<_> = msgs.into_iter().map(|msg| msg.msg).collect(); 307 | assert_eq!(received.clone().into_vec_without_me(), msgs); 308 | 309 | // including me 310 | let received = received.into_vec_including_me(Msg(13)); 311 | assert_eq!(received[0..3], msgs[0..3]); 312 | assert_eq!(received[3], Msg(13)); 313 | assert_eq!(received[4..5], msgs[3..4]); 314 | } 315 | 316 | #[test] 317 | fn store_returns_error_if_sender_index_is_out_of_range() { 318 | let mut store = RoundInput::new(3, 5, MessageType::P2P); 319 | let error = store 320 | .add_message(Incoming { 321 | id: 0, 322 | sender: 5, 323 | msg_type: MessageType::P2P, 324 | msg: Msg(123), 325 | }) 326 | .unwrap_err(); 327 | assert_matches!( 328 | error, 329 | RoundInputError::SenderIndexOutOfRange { msg_id, sender, n } if msg_id == 0 && sender == 5 && n == 5 330 | ); 331 | } 332 | 333 | #[test] 334 | fn store_returns_error_if_incoming_msg_overwrites_already_received_one() { 335 | let mut store = RoundInput::new(0, 3, MessageType::P2P); 336 | store 337 | .add_message(Incoming { 338 | id: 0, 339 | sender: 1, 340 | msg_type: MessageType::P2P, 341 | msg: Msg(11), 342 | }) 343 | .unwrap(); 344 | let error = store 345 | .add_message(Incoming { 346 | id: 1, 347 | sender: 1, 348 | msg_type: MessageType::P2P, 349 | msg: Msg(112), 350 | }) 351 | .unwrap_err(); 352 | assert_matches!(error, RoundInputError::AttemptToOverwriteReceivedMsg { msgs_ids, sender } if msgs_ids[0] == 0 && msgs_ids[1] == 1 && sender == 1); 353 | store 354 | .add_message(Incoming { 355 | id: 2, 356 | sender: 2, 357 | msg_type: MessageType::P2P, 358 | msg: Msg(22), 359 | }) 360 | .unwrap(); 361 | 362 | let output = store.output().unwrap().into_vec_without_me(); 363 | assert_eq!(output, [Msg(11), Msg(22)]); 364 | } 365 | 366 | #[test] 367 | fn store_returns_error_if_tried_to_output_before_receiving_enough_messages() { 368 | let mut store = RoundInput::::new(3, 5, MessageType::P2P); 369 | 370 | let msgs = (0..5) 371 | .map(|s| Incoming { 372 | id: s.into(), 373 | sender: s, 374 | msg_type: MessageType::P2P, 375 | msg: Msg(10 + s), 376 | }) 377 | .filter(|incoming| incoming.sender != 3); 378 | 379 | for msg in msgs { 380 | assert!(store.wants_more()); 381 | store = store.output().unwrap_err(); 382 | 383 | store.add_message(msg).unwrap(); 384 | } 385 | 386 | let _ = store.output().unwrap(); 387 | } 388 | 389 | #[test] 390 | fn store_returns_error_if_message_type_mismatched() { 391 | let mut store = RoundInput::::p2p(3, 5); 392 | let err = store 393 | .add_message(Incoming { 394 | id: 0, 395 | sender: 0, 396 | msg_type: MessageType::Broadcast, 397 | msg: Msg(1), 398 | }) 399 | .unwrap_err(); 400 | assert_matches!( 401 | err, 402 | RoundInputError::MismatchedMessageType { 403 | msg_id: 0, 404 | expected: MessageType::P2P, 405 | actual: MessageType::Broadcast 406 | } 407 | ); 408 | 409 | let mut store = RoundInput::::broadcast(3, 5); 410 | let err = store 411 | .add_message(Incoming { 412 | id: 0, 413 | sender: 0, 414 | msg_type: MessageType::P2P, 415 | msg: Msg(1), 416 | }) 417 | .unwrap_err(); 418 | assert_matches!( 419 | err, 420 | RoundInputError::MismatchedMessageType { 421 | msg_id: 0, 422 | expected: MessageType::Broadcast, 423 | actual: MessageType::P2P, 424 | } 425 | ); 426 | for sender in 0u16..5 { 427 | store 428 | .add_message(Incoming { 429 | id: 0, 430 | sender, 431 | msg_type: MessageType::Broadcast, 432 | msg: Msg(1), 433 | }) 434 | .unwrap(); 435 | } 436 | 437 | let mut store = RoundInput::::broadcast(3, 5); 438 | let err = store 439 | .add_message(Incoming { 440 | id: 0, 441 | sender: 0, 442 | msg_type: MessageType::P2P, 443 | msg: Msg(1), 444 | }) 445 | .unwrap_err(); 446 | assert_matches!( 447 | err, 448 | RoundInputError::MismatchedMessageType { 449 | msg_id: 0, 450 | expected: MessageType::Broadcast, 451 | actual, 452 | } if actual == MessageType::P2P 453 | ); 454 | store 455 | .add_message(Incoming { 456 | id: 0, 457 | sender: 0, 458 | msg_type: MessageType::Broadcast, 459 | msg: Msg(1), 460 | }) 461 | .unwrap(); 462 | } 463 | } 464 | -------------------------------------------------------------------------------- /round-based/src/rounds_router/store.rs: -------------------------------------------------------------------------------- 1 | use crate::Incoming; 2 | 3 | /// Stores messages received at particular round 4 | /// 5 | /// In MPC protocol, party at every round usually needs to receive up to `n` messages. `MessagesStore` 6 | /// is a container that stores messages, it knows how many messages are expected to be received, 7 | /// and should implement extra measures against malicious parties (e.g. prohibit message overwrite). 8 | /// 9 | /// ## Procedure 10 | /// `MessagesStore` stores received messages. Once enough messages are received, it outputs [`MessagesStore::Output`]. 11 | /// In order to save received messages, [`.add_message(msg)`] is called. Then, [`.wants_more()`] tells whether more 12 | /// messages are needed to be received. If it returned `false`, then output can be retrieved by calling [`.output()`]. 13 | /// 14 | /// [`.add_message(msg)`]: Self::add_message 15 | /// [`.wants_more()`]: Self::wants_more 16 | /// [`.output()`]: Self::output 17 | /// 18 | /// ## Example 19 | /// [`RoundInput`](super::simple_store::RoundInput) is an simple messages store. Refer to its docs to see usage examples. 20 | pub trait MessagesStore: Sized + 'static { 21 | /// Message type 22 | type Msg; 23 | /// Store output (e.g. `Vec<_>` of received messages) 24 | type Output; 25 | /// Store error 26 | type Error; 27 | 28 | /// Adds received message to the store 29 | /// 30 | /// Returns error if message cannot be processed. Usually it means that sender behaves maliciously. 31 | fn add_message(&mut self, msg: Incoming) -> Result<(), Self::Error>; 32 | /// Indicates if store expects more messages to receive 33 | fn wants_more(&self) -> bool; 34 | /// Retrieves store output if enough messages are received 35 | /// 36 | /// Returns `Err(self)` if more message are needed to be received. 37 | /// 38 | /// If store indicated that it needs no more messages (ie `store.wants_more() == false`), then 39 | /// this function must return `Ok(_)`. 40 | fn output(self) -> Result; 41 | } 42 | 43 | /// Message of MPC protocol 44 | /// 45 | /// MPC protocols typically consist of several rounds, each round has differently typed message. 46 | /// `ProtocolMessage` and [`RoundMessage`] traits are used to examine received message: `ProtocolMessage::round` 47 | /// determines which round message belongs to, and then `RoundMessage` trait can be used to retrieve 48 | /// actual round-specific message. 49 | /// 50 | /// You should derive these traits using proc macro (requires `derive` feature): 51 | /// ```rust 52 | /// use round_based::ProtocolMessage; 53 | /// 54 | /// #[derive(ProtocolMessage)] 55 | /// pub enum Message { 56 | /// Round1(Msg1), 57 | /// Round2(Msg2), 58 | /// // ... 59 | /// } 60 | /// 61 | /// pub struct Msg1 { /* ... */ } 62 | /// pub struct Msg2 { /* ... */ } 63 | /// ``` 64 | /// 65 | /// This desugars into: 66 | /// 67 | /// ```rust 68 | /// use round_based::rounds_router::{ProtocolMessage, RoundMessage}; 69 | /// 70 | /// pub enum Message { 71 | /// Round1(Msg1), 72 | /// Round2(Msg2), 73 | /// // ... 74 | /// } 75 | /// 76 | /// pub struct Msg1 { /* ... */ } 77 | /// pub struct Msg2 { /* ... */ } 78 | /// 79 | /// impl ProtocolMessage for Message { 80 | /// fn round(&self) -> u16 { 81 | /// match self { 82 | /// Message::Round1(_) => 1, 83 | /// Message::Round2(_) => 2, 84 | /// // ... 85 | /// } 86 | /// } 87 | /// } 88 | /// impl RoundMessage for Message { 89 | /// const ROUND: u16 = 1; 90 | /// fn to_protocol_message(round_message: Msg1) -> Self { 91 | /// Message::Round1(round_message) 92 | /// } 93 | /// fn from_protocol_message(protocol_message: Self) -> Result { 94 | /// match protocol_message { 95 | /// Message::Round1(msg) => Ok(msg), 96 | /// msg => Err(msg), 97 | /// } 98 | /// } 99 | /// } 100 | /// impl RoundMessage for Message { 101 | /// const ROUND: u16 = 2; 102 | /// fn to_protocol_message(round_message: Msg2) -> Self { 103 | /// Message::Round2(round_message) 104 | /// } 105 | /// fn from_protocol_message(protocol_message: Self) -> Result { 106 | /// match protocol_message { 107 | /// Message::Round2(msg) => Ok(msg), 108 | /// msg => Err(msg), 109 | /// } 110 | /// } 111 | /// } 112 | /// ``` 113 | pub trait ProtocolMessage: Sized { 114 | /// Number of round this message originates from 115 | fn round(&self) -> u16; 116 | } 117 | 118 | /// Round message 119 | /// 120 | /// See [`ProtocolMessage`] trait documentation. 121 | pub trait RoundMessage: ProtocolMessage { 122 | /// Number of the round this message belongs to 123 | const ROUND: u16; 124 | 125 | /// Converts round message into protocol message (never fails) 126 | fn to_protocol_message(round_message: M) -> Self; 127 | /// Extracts round message from protocol message 128 | /// 129 | /// Returns `Err(protocol_message)` if `protocol_message.round() != Self::ROUND`, otherwise 130 | /// returns `Ok(round_message)` 131 | fn from_protocol_message(protocol_message: Self) -> Result; 132 | } 133 | -------------------------------------------------------------------------------- /round-based/src/runtime.rs: -------------------------------------------------------------------------------- 1 | //! Async runtime abstraction 2 | //! 3 | //! Runtime abstraction allows the MPC protocol to stay runtime-agnostic while being 4 | //! able to use features that are common to any runtime 5 | 6 | /// Async runtime abstraction 7 | /// 8 | /// Abstracts async runtime like [tokio]. Currently only exposes a [yield_now](Self::yield_now) 9 | /// function. 10 | pub trait AsyncRuntime { 11 | /// Future type returned by [yield_now](Self::yield_now) 12 | type YieldNowFuture: core::future::Future + Send + 'static; 13 | 14 | /// Yields the execution back to the runtime 15 | /// 16 | /// If the protocol performs a long computation, it might be better for performance 17 | /// to split it with yield points, so the signle computation does not starve other 18 | /// tasks. 19 | fn yield_now(&self) -> Self::YieldNowFuture; 20 | } 21 | 22 | /// [Tokio](tokio)-specific async runtime 23 | #[cfg(feature = "runtime-tokio")] 24 | #[derive(Debug, Default)] 25 | pub struct TokioRuntime; 26 | 27 | #[cfg(feature = "runtime-tokio")] 28 | impl AsyncRuntime for TokioRuntime { 29 | type YieldNowFuture = core::pin::Pin + Send>>; 30 | 31 | fn yield_now(&self) -> Self::YieldNowFuture { 32 | Box::pin(tokio::task::yield_now()) 33 | } 34 | } 35 | 36 | #[doc(inline)] 37 | pub use unknown_runtime::UnknownRuntime; 38 | 39 | /// Default runtime 40 | #[cfg(feature = "runtime-tokio")] 41 | #[cfg_attr(docsrs, doc(cfg(all())))] 42 | pub type DefaultRuntime = TokioRuntime; 43 | /// Default runtime 44 | #[cfg(not(feature = "runtime-tokio"))] 45 | #[cfg_attr(docsrs, doc(cfg(all())))] 46 | pub type DefaultRuntime = UnknownRuntime; 47 | 48 | /// Unknown async runtime 49 | pub mod unknown_runtime { 50 | /// Unknown async runtime 51 | /// 52 | /// Tries to implement runtime features using generic futures code. It's better to use 53 | /// runtime-specific implementation. 54 | #[derive(Debug, Default)] 55 | pub struct UnknownRuntime; 56 | 57 | impl super::AsyncRuntime for UnknownRuntime { 58 | type YieldNowFuture = YieldNow; 59 | 60 | fn yield_now(&self) -> Self::YieldNowFuture { 61 | YieldNow(false) 62 | } 63 | } 64 | 65 | /// Future for the `yield_now` function. 66 | pub struct YieldNow(bool); 67 | 68 | impl core::future::Future for YieldNow { 69 | type Output = (); 70 | 71 | fn poll( 72 | mut self: core::pin::Pin<&mut Self>, 73 | cx: &mut core::task::Context<'_>, 74 | ) -> core::task::Poll { 75 | if !self.0 { 76 | self.0 = true; 77 | cx.waker().wake_by_ref(); 78 | core::task::Poll::Pending 79 | } else { 80 | core::task::Poll::Ready(()) 81 | } 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /round-based/src/simulation.rs: -------------------------------------------------------------------------------- 1 | //! Multiparty protocol simulation 2 | //! 3 | //! [`Simulation`] is an essential developer tool for testing the multiparty protocol locally. 4 | //! It covers most of the boilerplate by mocking networking. 5 | //! 6 | //! ## Example 7 | //! 8 | //! ```rust 9 | //! use round_based::{Mpc, PartyIndex}; 10 | //! use round_based::simulation::Simulation; 11 | //! use futures::future::try_join_all; 12 | //! 13 | //! # type Result = std::result::Result; 14 | //! # type Randomness = [u8; 32]; 15 | //! # type Msg = (); 16 | //! // Any MPC protocol you want to test 17 | //! pub async fn protocol_of_random_generation(party: M, i: PartyIndex, n: u16) -> Result 18 | //! where M: Mpc 19 | //! { 20 | //! // ... 21 | //! # todo!() 22 | //! } 23 | //! 24 | //! async fn test_randomness_generation() { 25 | //! let n = 3; 26 | //! 27 | //! let mut simulation = Simulation::::new(); 28 | //! let mut outputs = vec![]; 29 | //! for i in 0..n { 30 | //! let party = simulation.add_party(); 31 | //! outputs.push(protocol_of_random_generation(party, i, n)); 32 | //! } 33 | //! 34 | //! // Waits each party to complete the protocol 35 | //! let outputs = try_join_all(outputs).await.expect("protocol wasn't completed successfully"); 36 | //! // Asserts that all parties output the same randomness 37 | //! for output in outputs.iter().skip(1) { 38 | //! assert_eq!(&outputs[0], output); 39 | //! } 40 | //! } 41 | //! ``` 42 | 43 | use std::pin::Pin; 44 | use std::sync::atomic::AtomicU64; 45 | use std::sync::Arc; 46 | use std::task::ready; 47 | use std::task::{Context, Poll}; 48 | 49 | use futures_util::{Sink, Stream}; 50 | use tokio::sync::broadcast; 51 | use tokio_stream::wrappers::{errors::BroadcastStreamRecvError, BroadcastStream}; 52 | 53 | use crate::delivery::{Delivery, Incoming, Outgoing}; 54 | use crate::{MessageDestination, MessageType, MpcParty, MsgId, PartyIndex}; 55 | 56 | /// Multiparty protocol simulator 57 | pub struct Simulation { 58 | channel: broadcast::Sender>>, 59 | next_party_idx: PartyIndex, 60 | next_msg_id: Arc, 61 | } 62 | 63 | impl Simulation 64 | where 65 | M: Clone + Send + Unpin + 'static, 66 | { 67 | /// Instantiates a new simulation 68 | pub fn new() -> Self { 69 | Self::with_capacity(500) 70 | } 71 | 72 | /// Instantiates a new simulation with given capacity 73 | /// 74 | /// `Simulation` stores internally all sent messages. Capacity limits size of the internal buffer. 75 | /// Because of that you might run into error if you choose too small capacity. Choose capacity 76 | /// that can fit all the messages sent by all the parties during entire protocol lifetime. 77 | /// 78 | /// Default capacity is 500 (i.e. if you call `Simulation::new()`) 79 | pub fn with_capacity(capacity: usize) -> Self { 80 | Self { 81 | channel: broadcast::channel(capacity).0, 82 | next_party_idx: 0, 83 | next_msg_id: Default::default(), 84 | } 85 | } 86 | 87 | /// Adds new party to the network 88 | pub fn add_party(&mut self) -> MpcParty> { 89 | MpcParty::connected(self.connect_new_party()) 90 | } 91 | 92 | /// Connects new party to the network 93 | /// 94 | /// Similar to [`.add_party()`](Self::add_party) but returns `MockedDelivery` instead of 95 | /// `MpcParty>` 96 | pub fn connect_new_party(&mut self) -> MockedDelivery { 97 | let local_party_idx = self.next_party_idx; 98 | self.next_party_idx += 1; 99 | 100 | MockedDelivery { 101 | incoming: MockedIncoming { 102 | local_party_idx, 103 | receiver: BroadcastStream::new(self.channel.subscribe()), 104 | }, 105 | outgoing: MockedOutgoing { 106 | local_party_idx, 107 | sender: self.channel.clone(), 108 | next_msg_id: self.next_msg_id.clone(), 109 | }, 110 | } 111 | } 112 | } 113 | 114 | impl Default for Simulation 115 | where 116 | M: Clone + Send + Unpin + 'static, 117 | { 118 | fn default() -> Self { 119 | Self::new() 120 | } 121 | } 122 | 123 | /// Mocked networking 124 | pub struct MockedDelivery { 125 | incoming: MockedIncoming, 126 | outgoing: MockedOutgoing, 127 | } 128 | 129 | impl Delivery for MockedDelivery 130 | where 131 | M: Clone + Send + Unpin + 'static, 132 | { 133 | type Send = MockedOutgoing; 134 | type Receive = MockedIncoming; 135 | type SendError = broadcast::error::SendError<()>; 136 | type ReceiveError = BroadcastStreamRecvError; 137 | 138 | fn split(self) -> (Self::Receive, Self::Send) { 139 | (self.incoming, self.outgoing) 140 | } 141 | } 142 | 143 | /// Incoming channel of mocked network 144 | pub struct MockedIncoming { 145 | local_party_idx: PartyIndex, 146 | receiver: BroadcastStream>>, 147 | } 148 | 149 | impl Stream for MockedIncoming 150 | where 151 | M: Clone + Send + 'static, 152 | { 153 | type Item = Result, BroadcastStreamRecvError>; 154 | 155 | fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 156 | loop { 157 | let msg = match ready!(Pin::new(&mut self.receiver).poll_next(cx)) { 158 | Some(Ok(m)) => m, 159 | Some(Err(e)) => return Poll::Ready(Some(Err(e))), 160 | None => return Poll::Ready(None), 161 | }; 162 | if msg.recipient.is_p2p() 163 | && msg.recipient != MessageDestination::OneParty(self.local_party_idx) 164 | { 165 | continue; 166 | } 167 | return Poll::Ready(Some(Ok(msg.msg))); 168 | } 169 | } 170 | } 171 | 172 | /// Outgoing channel of mocked network 173 | pub struct MockedOutgoing { 174 | local_party_idx: PartyIndex, 175 | sender: broadcast::Sender>>, 176 | next_msg_id: Arc, 177 | } 178 | 179 | impl Sink> for MockedOutgoing { 180 | type Error = broadcast::error::SendError<()>; 181 | 182 | fn poll_ready(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { 183 | Poll::Ready(Ok(())) 184 | } 185 | 186 | fn start_send(self: Pin<&mut Self>, msg: Outgoing) -> Result<(), Self::Error> { 187 | let msg_type = match msg.recipient { 188 | MessageDestination::AllParties => MessageType::Broadcast, 189 | MessageDestination::OneParty(_) => MessageType::P2P, 190 | }; 191 | self.sender 192 | .send(msg.map(|m| Incoming { 193 | id: self.next_msg_id.next(), 194 | sender: self.local_party_idx, 195 | msg_type, 196 | msg: m, 197 | })) 198 | .map_err(|_| broadcast::error::SendError(()))?; 199 | Ok(()) 200 | } 201 | 202 | fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context) -> Poll> { 203 | Poll::Ready(Ok(())) 204 | } 205 | 206 | fn poll_close(self: Pin<&mut Self>, _cx: &mut Context) -> Poll> { 207 | Poll::Ready(Ok(())) 208 | } 209 | } 210 | 211 | #[derive(Default)] 212 | struct NextMessageId(AtomicU64); 213 | 214 | impl NextMessageId { 215 | pub fn next(&self) -> MsgId { 216 | self.0.fetch_add(1, std::sync::atomic::Ordering::Relaxed) 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /round-based/tests/derive.rs: -------------------------------------------------------------------------------- 1 | #[test] 2 | fn compile_test() { 3 | let t = trybuild::TestCases::new(); 4 | t.compile_fail("tests/derive/compile-fail/*.rs"); 5 | t.pass("tests/derive/compile-pass/*.rs") 6 | } 7 | -------------------------------------------------------------------------------- /round-based/tests/derive/compile-fail/wrong_usage.rs: -------------------------------------------------------------------------------- 1 | use round_based::ProtocolMessage; 2 | 3 | #[derive(ProtocolMessage)] 4 | enum Msg { 5 | // Unnamed variant with single field is the only correct enum variant 6 | // that doesn't contradicts with ProtocolMessage derivation 7 | VariantA(u16), 8 | // Error: You can't have named variants 9 | VariantB { n: u32 }, 10 | // Error: Variant must have exactly 1 field 11 | VariantC(u32, String), 12 | // Error: Variant must have exactly 1 field!! 13 | VariantD(), 14 | // Error: Union variants are not permitted 15 | VariantE, 16 | } 17 | 18 | // Structure cannot implement ProtocolMessage 19 | #[derive(ProtocolMessage)] 20 | struct Msg2 { 21 | some_field: u64, 22 | } 23 | 24 | // Union cannot implement ProtocolMessage 25 | #[derive(ProtocolMessage)] 26 | union Msg3 { 27 | variant: u64, 28 | } 29 | 30 | // protocol_message is repeated twice 31 | #[derive(ProtocolMessage)] 32 | #[protocol_message(root = one)] 33 | #[protocol_message(root = two)] 34 | enum Msg4 { 35 | One(u32), 36 | Two(u16), 37 | } 38 | 39 | // ", blah blah" is not permitted input 40 | #[derive(ProtocolMessage)] 41 | #[protocol_message(root = one, blah blah)] 42 | enum Msg5 { 43 | One(u32), 44 | Two(u16), 45 | } 46 | 47 | // `protocol_message` must not be empty 48 | #[derive(ProtocolMessage)] 49 | #[protocol_message()] 50 | enum Msg6 { 51 | One(u32), 52 | Two(u16), 53 | } 54 | 55 | fn main() {} 56 | -------------------------------------------------------------------------------- /round-based/tests/derive/compile-fail/wrong_usage.stderr: -------------------------------------------------------------------------------- 1 | error: named variants are not allowed in ProtocolMessage 2 | --> tests/derive/compile-fail/wrong_usage.rs:9:5 3 | | 4 | 9 | VariantB { n: u32 }, 5 | | ^^^^^^^^ 6 | 7 | error: this variant must contain exactly one field to be valid ProtocolMessage 8 | --> tests/derive/compile-fail/wrong_usage.rs:11:5 9 | | 10 | 11 | VariantC(u32, String), 11 | | ^^^^^^^^ 12 | 13 | error: this variant must contain exactly one field to be valid ProtocolMessage 14 | --> tests/derive/compile-fail/wrong_usage.rs:13:5 15 | | 16 | 13 | VariantD(), 17 | | ^^^^^^^^ 18 | 19 | error: unit variants are not allowed in ProtocolMessage 20 | --> tests/derive/compile-fail/wrong_usage.rs:15:5 21 | | 22 | 15 | VariantE, 23 | | ^^^^^^^^ 24 | 25 | error: only enum may implement ProtocolMessage 26 | --> tests/derive/compile-fail/wrong_usage.rs:20:1 27 | | 28 | 20 | struct Msg2 { 29 | | ^^^^^^ 30 | 31 | error: only enum may implement ProtocolMessage 32 | --> tests/derive/compile-fail/wrong_usage.rs:26:1 33 | | 34 | 26 | union Msg3 { 35 | | ^^^^^ 36 | 37 | error: #[protocol_message] attribute appears more than once 38 | --> tests/derive/compile-fail/wrong_usage.rs:33:3 39 | | 40 | 33 | #[protocol_message(root = two)] 41 | | ^^^^^^^^^^^^^^^^ 42 | 43 | error: unexpected token 44 | --> tests/derive/compile-fail/wrong_usage.rs:41:30 45 | | 46 | 41 | #[protocol_message(root = one, blah blah)] 47 | | ^ 48 | 49 | error: unexpected end of input, expected `root` 50 | --> tests/derive/compile-fail/wrong_usage.rs:49:20 51 | | 52 | 49 | #[protocol_message()] 53 | | ^ 54 | -------------------------------------------------------------------------------- /round-based/tests/derive/compile-pass/correct_usage.rs: -------------------------------------------------------------------------------- 1 | use round_based::ProtocolMessage; 2 | 3 | #[derive(ProtocolMessage)] 4 | enum Msg { 5 | VariantA(u16), 6 | VariantB(String), 7 | VariantC((u16, String)), 8 | VariantD(MyStruct), 9 | } 10 | #[derive(ProtocolMessage)] 11 | #[protocol_message(root = round_based)] 12 | enum Msg2 { 13 | VariantA(u16), 14 | VariantB(String), 15 | VariantC((u16, String)), 16 | VariantD(MyStruct), 17 | } 18 | 19 | struct MyStruct(T); 20 | 21 | fn main() {} 22 | --------------------------------------------------------------------------------