diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index e57b19aca..62383ae83 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -1,4 +1,4 @@ -name: checks +name: Quality Checks on: push: @@ -12,7 +12,7 @@ env: jobs: # checks markdown links link-check: - name: links + name: Links runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 @@ -25,7 +25,7 @@ jobs: # ensures proper formatting and clippy lint lint-check: - name: lint + name: Linting runs-on: ubuntu-latest steps: - name: Cancel Previous Runs diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index defa973b5..0fc02ea77 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -1,4 +1,4 @@ -name: check +name: Coverage Tests on: push: @@ -13,7 +13,7 @@ jobs: # code coverage job; moved to own workflow file due to running out of disk space. # The runner will stop working when the machine runs out of disk space. Free space left: 72 MB coverage: - name: coverage + name: Tests runs-on: ubuntu-latest steps: - name: Cancel Previous Runs @@ -43,7 +43,7 @@ jobs: run: cargo install cargo-tarpaulin - name: Run Tarpaulin - run : cargo tarpaulin --out Xml -p pallet-dkg-metadata -p pallet-dkg-proposal-handler -p pallet-dkg-proposals -p dkg-primitives -p dkg-runtime-primitives --locked --jobs 16 --timeout 3600 --skip-clean -- --test-threads 16 + run : cargo tarpaulin --out xml -p pallet-dkg-metadata -p pallet-dkg-proposal-handler -p pallet-dkg-proposals -p dkg-primitives -p dkg-runtime-primitives --locked --jobs 16 --timeout 3600 --skip-clean -- --test-threads 16 - name: Upload CodeCov uses: codecov/codecov-action@v2 diff --git a/.github/workflows/harness_stress_tests.yml b/.github/workflows/harness_stress_tests.yml index 844f3b4ad..1ba198713 100644 --- a/.github/workflows/harness_stress_tests.yml +++ b/.github/workflows/harness_stress_tests.yml @@ -12,7 +12,7 @@ env: jobs: harness: - name: Harness + name: Tests runs-on: ubuntu-latest steps: - name: Cancel Previous Runs @@ -23,14 +23,6 @@ jobs: - name: Checkout Code uses: actions/checkout@v3 - - name: Configure sccache - run: | - echo "RUSTC_WRAPPER=sccache" >> $GITHUB_ENV - echo "SCCACHE_GHA_ENABLED=true" >> $GITHUB_ENV - - - name: Run sccache-cache - uses: mozilla-actions/sccache-action@v0.0.3 - - name: Restore Cache if: always() uses: actions/cache/restore@v3 @@ -44,10 +36,10 @@ jobs: - name: Install toolchain uses: dtolnay/rust-toolchain@stable with: - toolchain: nightly + toolchain: stable - - name: Install Protobuf - run: sudo apt-get install protobuf-compiler + - name: Install Protobuf and LLVM + run: sudo apt-get install protobuf-compiler llvm libclang-dev - name: t2n3 && 1 proposal per session run: cargo run --package dkg-test-orchestrator --features=debug-tracing -- --tmp /tmp --threshold 2 --n 3 --bind 127.0.0.1:7777 --n-tests 10 -p 1 diff --git a/.github/workflows/integration_tests.yml b/.github/workflows/integration_tests.yml index bddb04381..021c4f83c 100644 --- a/.github/workflows/integration_tests.yml +++ b/.github/workflows/integration_tests.yml @@ -1,4 +1,4 @@ -name: integration tests +name: Integration Tests on: push: @@ -14,7 +14,7 @@ jobs: # dkg-substrate integration tests local_chain: name: Local Chain Tests - runs-on: ubuntu-latest + runs-on: macos-latest steps: - name: Cancel Previous Runs uses: styfle/cancel-workflow-action@0.9.1 @@ -24,14 +24,6 @@ jobs: - name: Checkout Code uses: actions/checkout@v3 - - name: Configure sccache - run: | - echo "RUSTC_WRAPPER=sccache" >> $GITHUB_ENV - echo "SCCACHE_GHA_ENABLED=true" >> $GITHUB_ENV - - - name: Run sccache-cache - uses: mozilla-actions/sccache-action@v0.0.3 - - name: Restore Cache if: always() uses: actions/cache/restore@v3 @@ -45,13 +37,13 @@ jobs: - name: Install toolchain uses: dtolnay/rust-toolchain@stable with: - toolchain: nightly + toolchain: stable - - name: Install Protobuf - run: sudo apt-get install protobuf-compiler + - name: Install Protobuf and LLVM + run: brew install protobuf llvm - name: Install cargo make - run: cargo install --force cargo-make + run: cargo install cargo-make - name: Build Binary run: cargo make build-test diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index f1b88106a..37304a4bc 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -1,4 +1,4 @@ -name: run +name: Unit Tests on: push: @@ -12,7 +12,7 @@ env: jobs: # dkg-substrate base unit tests tests: - name: tests + name: Tests runs-on: ubuntu-latest steps: - name: Cancel Previous Runs diff --git a/Cargo.lock b/Cargo.lock index 2b38a17a9..48423487c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -239,6 +239,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "allocator-api2" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" + [[package]] name = "android-tzdata" version = "0.1.1" @@ -771,7 +777,7 @@ checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" dependencies = [ "async-stream-impl", "futures-core", - "pin-project-lite 0.2.10", + "pin-project-lite 0.2.13", ] [[package]] @@ -787,9 +793,9 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.72" +version = "0.1.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc6dde6e4ed435a4c1ee4e73592f5ba9da2151af10076cc04858746af9352d09" +checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0" dependencies = [ "proc-macro2", "quote", @@ -817,7 +823,7 @@ dependencies = [ "futures-sink", "futures-util", "memchr", - "pin-project-lite 0.2.10", + "pin-project-lite 0.2.13", ] [[package]] @@ -890,7 +896,7 @@ dependencies = [ "memchr", "mime", "percent-encoding", - "pin-project-lite 0.2.10", + "pin-project-lite 0.2.13", "rustversion", "serde", "serde_json", @@ -942,7 +948,7 @@ dependencies = [ "futures-core", "getrandom 0.2.10", "instant", - "pin-project-lite 0.2.10", + "pin-project-lite 0.2.13", "rand 0.8.5", "tokio", ] @@ -1038,6 +1044,28 @@ dependencies = [ "serde", ] +[[package]] +name = "bindgen" +version = "0.64.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4243e6031260db77ede97ad86c27e501d646a27ab57b59a574f725d98ab1fb4" +dependencies = [ + "bitflags 1.3.2", + "cexpr", + "clang-sys", + "lazy_static", + "lazycell", + "log", + "peeking_take_while", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn 1.0.109", + "which", +] + [[package]] name = "bit-set" version = "0.5.3" @@ -1219,6 +1247,15 @@ dependencies = [ "sha2 0.9.9", ] +[[package]] +name = "bs58" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5353f36341f7451062466f0b755b96ac3a9547e4d7f6b70d603fc721a7d7896" +dependencies = [ + "tinyvec", +] + [[package]] name = "bstr" version = "1.6.0" @@ -1379,6 +1416,15 @@ dependencies = [ "sha2 0.9.9", ] +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + [[package]] name = "cfg-expr" version = "0.15.4" @@ -1482,6 +1528,17 @@ dependencies = [ "inout", ] +[[package]] +name = "clang-sys" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c688fc74432808e3eb684cae8830a86be1d66a2bd58e1f248ed0960a590baf6f" +dependencies = [ + "glob", + "libc", + "libloading", +] + [[package]] name = "clap" version = "2.34.0" @@ -1564,7 +1621,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b30a84aab436fcb256a2ab3c80663d8aec686e6bae12827bb05fef3e1e439c9f" dependencies = [ "bincode", - "bs58", + "bs58 0.4.0", "coins-core", "digest 0.10.7", "getrandom 0.2.10", @@ -1601,7 +1658,7 @@ checksum = "9b949a1c63fb7eb591eb7ba438746326aedf0ae843e51ec92ba6bec5bb382c4f" dependencies = [ "base64 0.21.2", "bech32", - "bs58", + "bs58 0.4.0", "digest 0.10.7", "generic-array 0.14.7", "hex", @@ -2513,6 +2570,7 @@ dependencies = [ "tracing", "uuid 1.4.1", "webb-proposals", + "wsts", ] [[package]] @@ -3988,7 +4046,7 @@ dependencies = [ "futures-io", "memchr", "parking", - "pin-project-lite 0.2.10", + "pin-project-lite 0.2.13", "waker-fn", ] @@ -4059,7 +4117,7 @@ dependencies = [ "futures-sink", "futures-task", "memchr", - "pin-project-lite 0.2.10", + "pin-project-lite 0.2.13", "pin-utils", "slab", ] @@ -4297,6 +4355,11 @@ name = "hashbrown" version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" +dependencies = [ + "ahash 0.8.3", + "allocator-api2", + "serde", +] [[package]] name = "hashers" @@ -4446,7 +4509,7 @@ checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" dependencies = [ "bytes", "http", - "pin-project-lite 0.2.10", + "pin-project-lite 0.2.13", ] [[package]] @@ -4499,7 +4562,7 @@ dependencies = [ "httparse", "httpdate", "itoa", - "pin-project-lite 0.2.10", + "pin-project-lite 0.2.13", "socket2 0.4.9", "tokio", "tower-service", @@ -5054,12 +5117,28 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + [[package]] name = "libc" version = "0.2.147" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" +[[package]] +name = "libloading" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +dependencies = [ + "cfg-if", + "winapi", +] + [[package]] name = "libm" version = "0.1.4" @@ -5194,7 +5273,7 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e2d584751cecb2aabaa56106be6be91338a60a0f4e420cf2af639204f596fc1" dependencies = [ - "bs58", + "bs58 0.4.0", "ed25519-dalek", "log", "multiaddr", @@ -6486,6 +6565,29 @@ dependencies = [ "sha2 0.10.7", ] +[[package]] +name = "p256k1" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79583032ab00c9e23e8afadf907e9cdc862c72c01fd57048fb07f1107a05c88a" +dependencies = [ + "bindgen", + "bitvec 1.0.1", + "bs58 0.4.0", + "cc", + "hex", + "itertools 0.10.5", + "num-traits", + "primitive-types", + "proc-macro2", + "quote", + "rand_core 0.6.4", + "rustfmt-wrapper", + "serde", + "sha2 0.10.7", + "syn 2.0.28", +] + [[package]] name = "p384" version = "0.11.2" @@ -7250,6 +7352,12 @@ dependencies = [ "hmac 0.12.1", ] +[[package]] +name = "peeking_take_while" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" + [[package]] name = "pem" version = "1.1.1" @@ -7417,9 +7525,9 @@ checksum = "257b64915a082f7811703966789728173279bdebb956b143dbcd23f6f970a777" [[package]] name = "pin-project-lite" -version = "0.2.10" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c40d25201921e5ff0c862a505c6557ea88568a4e3ace775ab55e93f2f4f9d57" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" [[package]] name = "pin-utils" @@ -7526,7 +7634,7 @@ dependencies = [ "concurrent-queue", "libc", "log", - "pin-project-lite 0.2.10", + "pin-project-lite 0.2.13", "windows-sys 0.48.0", ] @@ -7541,6 +7649,16 @@ dependencies = [ "universal-hash 0.4.1", ] +[[package]] +name = "polynomial" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a461f75483c9faefe81bdc7257732be9afe9953765e406f8ede2581185d66635" +dependencies = [ + "num-traits", + "serde", +] + [[package]] name = "polyval" version = "0.5.3" @@ -8276,7 +8394,7 @@ dependencies = [ "mime", "once_cell", "percent-encoding", - "pin-project-lite 0.2.10", + "pin-project-lite 0.2.13", "rustls 0.21.6", "rustls-pemfile", "serde", @@ -8513,6 +8631,19 @@ dependencies = [ "semver 1.0.18", ] +[[package]] +name = "rustfmt-wrapper" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed729e3bee08ec2befd593c27e90ca9fdd25efdc83c94c3b82eaef16e4f7406e" +dependencies = [ + "serde", + "tempfile", + "thiserror", + "toml 0.5.11", + "toolchain_find", +] + [[package]] name = "rusticata-macros" version = "4.1.0" @@ -10256,6 +10387,12 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "shlex" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7cee0529a6d40f580e7a5e6c495c8fbfe21b7b52795ed4bb5e62cdf92bc6380" + [[package]] name = "signal-hook-registry" version = "1.4.1" @@ -10645,7 +10782,7 @@ dependencies = [ "bitflags 1.3.2", "blake2 0.10.6", "bounded-collections", - "bs58", + "bs58 0.4.0", "dyn-clonable", "ed25519-zebra", "futures", @@ -10689,7 +10826,7 @@ dependencies = [ "bitflags 1.3.2", "blake2 0.10.6", "bounded-collections", - "bs58", + "bs58 0.4.0", "dyn-clonable", "ed25519-zebra", "futures", @@ -12230,20 +12367,19 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.29.1" +version = "1.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "532826ff75199d5833b9d2c5fe410f29235e25704ee5f0ef599fb51c21f4a4da" +checksum = "17ed6077ed6cd6c74735e21f37eb16dc3935f96878b1fe961074089cc80893f9" dependencies = [ - "autocfg 1.1.0", "backtrace", "bytes", "libc", "mio", "num_cpus", "parking_lot 0.12.1", - "pin-project-lite 0.2.10", + "pin-project-lite 0.2.13", "signal-hook-registry", - "socket2 0.4.9", + "socket2 0.5.3", "tokio-macros", "windows-sys 0.48.0", ] @@ -12287,7 +12423,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" dependencies = [ "futures-core", - "pin-project-lite 0.2.10", + "pin-project-lite 0.2.13", "tokio", "tokio-util", ] @@ -12314,7 +12450,7 @@ dependencies = [ "futures-core", "futures-io", "futures-sink", - "pin-project-lite 0.2.10", + "pin-project-lite 0.2.13", "tokio", "tracing", ] @@ -12379,6 +12515,19 @@ dependencies = [ "winnow", ] +[[package]] +name = "toolchain_find" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e85654a10e7a07a47c6f19d93818f3f343e22927f2fa280c84f7c8042743413" +dependencies = [ + "home", + "lazy_static", + "regex", + "semver 0.11.0", + "walkdir", +] + [[package]] name = "tower" version = "0.4.13" @@ -12388,7 +12537,7 @@ dependencies = [ "futures-core", "futures-util", "pin-project", - "pin-project-lite 0.2.10", + "pin-project-lite 0.2.13", "tokio", "tower-layer", "tower-service", @@ -12408,7 +12557,7 @@ dependencies = [ "http", "http-body", "http-range-header", - "pin-project-lite 0.2.10", + "pin-project-lite 0.2.13", "tower-layer", "tower-service", "tracing", @@ -12427,7 +12576,7 @@ dependencies = [ "http", "http-body", "http-range-header", - "pin-project-lite 0.2.10", + "pin-project-lite 0.2.13", "tower-layer", "tower-service", ] @@ -12452,7 +12601,7 @@ checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" dependencies = [ "cfg-if", "log", - "pin-project-lite 0.2.10", + "pin-project-lite 0.2.13", "tracing-attributes", "tracing-core", ] @@ -12731,7 +12880,7 @@ checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" dependencies = [ "cfg-if", "digest 0.10.7", - "rand 0.4.6", + "rand 0.8.5", "static_assertions", ] @@ -12748,18 +12897,18 @@ dependencies = [ [[package]] name = "typed-builder" -version = "0.15.1" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98d9e1c147896ed2835fccc41d0600d0b6ca68261e6ccd3691e51659ae9153b8" +checksum = "7fe83c85a85875e8c4cb9ce4a890f05b23d38cd0d47647db7895d3d2a79566d2" dependencies = [ "typed-builder-macro", ] [[package]] name = "typed-builder-macro" -version = "0.15.1" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dba2e999a7ccbeb42ede7c9824fc9d4660794e5bcd456e5d60ac17baad6e083" +checksum = "29a3151c41d0b13e3d011f98adc24434560ef06673a155a6c7f66b9879eecce2" dependencies = [ "proc-macro2", "quote", @@ -13346,9 +13495,9 @@ dependencies = [ [[package]] name = "webb" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0748c492acb6ec02479f48ef60f2ad26df72a9d61a1ce1bda913b9f334fb05d0" +checksum = "3f7957a32acb6a6415a8b8123418d57ab7d2be3379265f9ea8a407165101863a" dependencies = [ "async-trait", "ethers", @@ -13376,7 +13525,7 @@ dependencies = [ "hex-literal 0.3.4", "sp-core 21.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "tokio", - "typed-builder 0.15.1", + "typed-builder 0.15.2", "webb", "webb-proposals", "webb-relayer-config", @@ -13459,7 +13608,7 @@ dependencies = [ "sled", "tokio", "tracing", - "typed-builder 0.15.1", + "typed-builder 0.15.2", "webb", "webb-bridge-registry-backends", "webb-event-watcher-traits", @@ -13480,7 +13629,7 @@ dependencies = [ "futures", "reqwest", "serde", - "typed-builder 0.15.1", + "typed-builder 0.15.2", "webb", "webb-chains-info", "webb-relayer-store", @@ -13502,7 +13651,7 @@ dependencies = [ "sp-core 21.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "tokio", "tracing", - "typed-builder 0.15.1", + "typed-builder 0.15.2", "webb", "webb-proposals", "webb-relayer-store", @@ -14308,6 +14457,25 @@ dependencies = [ "web-sys", ] +[[package]] +name = "wsts" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08b241e8440197fcd6e214a26fcaac82d7cdfde3ae964586f4b1a7c41a21ba6f" +dependencies = [ + "bs58 0.5.0", + "hashbrown 0.14.0", + "hex", + "num-traits", + "p256k1", + "polynomial", + "primitive-types", + "rand_core 0.6.4", + "serde", + "sha2 0.10.7", + "thiserror", +] + [[package]] name = "wyz" version = "0.5.1" diff --git a/Cargo.toml b/Cargo.toml index af93812fe..e07ec6a06 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -59,7 +59,7 @@ serde = { version = "1.0", default-features = false, features = ["derive"] } humantime-serde = { version = "1.1.1", default-features = false } serde_json = "1.0.59" multi-party-ecdsa = { git = "https://github.com/webb-tools/multi-party-ecdsa.git" } -tokio = { version = "1.28.0", default-features = false, features = ["sync", "macros"] } +tokio = { version = "1.32.0", default-features = false, features = ["sync", "macros"] } tokio-util = { version = "0.7.7", default-features = false, features = ["codec"] } tokio-stream = { version = "0.1.8", features = ["sync"] } atomic = "0.5.1" @@ -195,7 +195,7 @@ xcm-builder = { git = "https://github.com/paritytech/polkadot", branch = "releas xcm-executor = { git = "https://github.com/paritytech/polkadot", branch = "release-v1.0.0", default-features = false } substrate-frame-rpc-system = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v1.0.0" } -substrate-prometheus-endpoint = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v1.0.0" } +prometheus = { package = "substrate-prometheus-endpoint", default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v1.0.0" } # Used for runtime benchmarking frame-benchmarking = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v1.0.0", default-features = false } diff --git a/Makefile.toml b/Makefile.toml index f8e1523d7..c331ccbcb 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -18,7 +18,7 @@ args = ["build", "-rp", "dkg-standalone-node", "--features=integration-tests,tes [tasks.integration-tests] command = "cargo" dependencies = ["clean-tmp", "build-test"] -args = ["run", "--bin", "dkg-integration-tests"] +args = ["run", "--release", "--bin", "dkg-integration-tests"] [tasks.alice] condition = { files_exist = ["${CARGO_MAKE_WORKING_DIRECTORY}/target/release/dkg-standalone-node"] } diff --git a/dkg-gadget/Cargo.toml b/dkg-gadget/Cargo.toml index 70e44ca5e..69f1ed626 100644 --- a/dkg-gadget/Cargo.toml +++ b/dkg-gadget/Cargo.toml @@ -24,7 +24,8 @@ scale-info = { workspace = true } curv = { workspace = true } codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } -prometheus = { package = "substrate-prometheus-endpoint", git = "https://github.com/paritytech/substrate", branch = "polkadot-v1.0.0" } +prometheus = { workspace = true } +wsts = "3.0.0" sp-api = { workspace = true } sp-application-crypto = { workspace = true } diff --git a/dkg-gadget/src/async_protocols/blockchain_interface.rs b/dkg-gadget/src/async_protocols/blockchain_interface.rs index d6fc07394..9f9599eaa 100644 --- a/dkg-gadget/src/async_protocols/blockchain_interface.rs +++ b/dkg-gadget/src/async_protocols/blockchain_interface.rs @@ -22,34 +22,30 @@ use crate::{ Client, DKGApi, DKGKeystore, }; use codec::Encode; -use curv::{elliptic::curves::Secp256k1, BigInt}; use dkg_primitives::{ gossip_messages::PublicKeyMessage, types::{DKGError, DKGMessage, SessionId, SignedDKGMessage}, - utils::convert_signature, }; use dkg_runtime_primitives::{ crypto::{AuthorityId, Public}, AggregatedPublicKeys, AuthoritySet, BatchId, MaxAuthorities, MaxProposalLength, - MaxProposalsInBatch, MaxSignatureLength, SignedProposalBatch, StoredUnsignedProposalBatch, -}; -use multi_party_ecdsa::protocols::multi_party_ecdsa::gg_2020::{ - party_i::SignatureRecid, state_machine::keygen::LocalKey, + MaxProposalsInBatch, MaxSignatureLength, SignedProposalBatch, }; use parking_lot::RwLock; use sc_client_api::Backend; use sc_keystore::LocalKeystore; +use crate::{ + async_protocols::types::{LocalKeyType, VoteResult}, + db::DKGDbBackend, +}; use sp_arithmetic::traits::AtLeast32BitUnsigned; use sp_runtime::traits::{Block, Get, NumberFor}; use std::{collections::HashMap, fmt::Debug, marker::PhantomData, sync::Arc}; use webb_proposals::Proposal; -use super::KeygenPartyId; - #[async_trait::async_trait] -#[auto_impl::auto_impl(Arc,&,&mut)] -pub trait BlockchainInterface: Send + Sync + Unpin { +pub trait BlockchainInterface: Send + Sync + Unpin + Sized + Clone { type Clock: Debug + AtLeast32BitUnsigned + Copy + Send + Sync; type GossipEngine: GossipEngineIface; type MaxProposalLength: Get + Clone + Send + Sync + std::fmt::Debug + 'static + Unpin; @@ -62,27 +58,11 @@ pub trait BlockchainInterface: Send + Sync + Unpin { message: SignedDKGMessage, ) -> Result, DKGError>; fn sign_and_send_msg(&self, unsigned_msg: DKGMessage) -> Result<(), DKGError>; - fn process_vote_result( - &self, - signature: SignatureRecid, - unsigned_proposal_batch: StoredUnsignedProposalBatch< - Self::BatchId, - Self::MaxProposalLength, - Self::MaxProposalsInBatch, - Self::Clock, - >, - session_id: SessionId, - batch_key: BatchKey, - message: BigInt, - ) -> Result<(), DKGError>; + fn process_vote_result(&self, result: VoteResult) -> Result<(), DKGError>; fn gossip_public_key(&self, key: PublicKeyMessage) -> Result<(), DKGError>; - fn store_public_key( - &self, - key: LocalKey, - session_id: SessionId, - ) -> Result<(), DKGError>; - fn get_authority_set(&self) -> Vec<(KeygenPartyId, Public)>; + fn store_public_key(&self, key: LocalKeyType, session_id: SessionId) -> Result<(), DKGError>; fn get_gossip_engine(&self) -> Option<&Self::GossipEngine>; + fn get_backend_db(&self) -> Option<&Arc>; /// Returns the present time fn now(&self) -> Self::Clock; } @@ -106,8 +86,6 @@ pub struct DKGProtocolEngine< pub db: Arc, pub gossip_engine: Arc, pub aggregated_public_keys: Arc>>, - pub best_authorities: Arc>, - pub authority_public_key: Arc, pub vote_results: Arc< RwLock< HashMap< @@ -132,6 +110,50 @@ pub struct DKGProtocolEngine< pub _pd: PhantomData<(BE, BatchId, MaxProposalsInBatch, MaxSignatureLength)>, } +impl< + B: Block, + BE, + C, + GE, + MaxProposalLength: Get + Clone + Send + Sync + std::fmt::Debug + 'static, + MaxAuthorities: Get + Clone + Send + Sync + std::fmt::Debug + 'static, + BatchId: Clone + Send + Sync + std::fmt::Debug + 'static + Unpin, + MaxProposalsInBatch: Get + Clone + Send + Sync + std::fmt::Debug + 'static + Unpin, + MaxSignatureLength: Get + Clone + Send + Sync + std::fmt::Debug + 'static + Unpin, + > Clone + for DKGProtocolEngine< + B, + BE, + C, + GE, + MaxProposalLength, + MaxAuthorities, + BatchId, + MaxProposalsInBatch, + MaxSignatureLength, + > +{ + fn clone(&self) -> Self { + Self { + backend: self.backend.clone(), + latest_header: self.latest_header.clone(), + client: self.client.clone(), + keystore: self.keystore.clone(), + db: self.db.clone(), + gossip_engine: self.gossip_engine.clone(), + aggregated_public_keys: self.aggregated_public_keys.clone(), + vote_results: self.vote_results.clone(), + is_genesis: self.is_genesis, + current_validator_set: self.current_validator_set.clone(), + local_keystore: self.local_keystore.clone(), + metrics: self.metrics.clone(), + test_bundle: self.test_bundle.clone(), + logger: self.logger.clone(), + _pd: Default::default(), + } + } +} + impl< B: Block, BE, @@ -275,29 +297,26 @@ impl BlockchainInterface Ok(()) } - fn process_vote_result( - &self, - signature: SignatureRecid, - unsigned_proposal_batch: StoredUnsignedProposalBatch< - Self::BatchId, - Self::MaxProposalLength, - Self::MaxProposalsInBatch, - Self::Clock, - >, - session_id: SessionId, - batch_key: BatchKey, - _message: BigInt, - ) -> Result<(), DKGError> { + fn process_vote_result(&self, result: VoteResult) -> Result<(), DKGError> { // Call worker.rs: handle_finished_round -> Proposal // aggregate Proposal into Vec + + let (encoded, unsigned_proposal_batch, session_id, batch_key) = match result { + VoteResult::ECDSA { signature, unsigned_proposal_batch, session_id, batch_key } => { + let encoded: Vec = signature.encode(); + (encoded, unsigned_proposal_batch, session_id, batch_key) + }, + + VoteResult::FROST { signature, unsigned_proposal_batch, session_id, batch_key } => { + let encoded: Vec = signature.encode(); + (encoded, unsigned_proposal_batch, session_id, batch_key) + }, + }; + self.logger.info(format!( - "PROCESS VOTE RESULT : session_id {session_id:?}, signature : {signature:?}" + "PROCESS VOTE RESULT : session_id {session_id:?}, signature : {encoded:?}" )); - let signature = convert_signature(&signature).ok_or_else(|| DKGError::CriticalError { - reason: "Unable to serialize signature".to_string(), - })?; - let mut signed_proposals = vec![]; // convert all unsigned proposals to signed @@ -309,17 +328,14 @@ impl BlockchainInterface .clone() .try_into() .expect("should not happen since its a valid proposal"), - signature: signature - .encode() - .try_into() - .expect("Signature exceeds runtime bounds!"), + signature: encoded.clone().try_into().expect("Signature exceeds runtime bounds!"), }); } let signed_proposal_batch = SignedProposalBatch { batch_id: unsigned_proposal_batch.batch_id, proposals: signed_proposals.try_into().expect("Proposals exceeds runtime bounds!"), - signature: signature.encode().try_into().expect("Signature exceeds runtime bounds!"), + signature: encoded.try_into().expect("Signature exceeds runtime bounds!"), }; let mut lock = self.vote_results.write(); @@ -330,22 +346,13 @@ impl BlockchainInterface if proposals_for_this_batch.len() == batch_key.len { self.logger.info(format!("All proposals have resolved for batch {batch_key:?}")); let proposals = lock.remove(&batch_key).expect("Cannot get lock on vote_results"); // safe unwrap since lock is held - std::mem::drop(lock); + drop(lock); if let Some(metrics) = self.metrics.as_ref() { metrics.dkg_signed_proposal_counter.inc_by(proposals.len() as u64); } - save_signed_proposals_in_storage::< - B, - C, - BE, - MaxProposalLength, - MaxAuthorities, - BatchId, - MaxProposalsInBatch, - MaxSignatureLength, - >( + save_signed_proposals_in_storage::( &self.get_authority_public_key(), &self.current_validator_set, &self.latest_header, @@ -381,23 +388,19 @@ impl BlockchainInterface Ok(()) } - fn store_public_key( - &self, - key: LocalKey, - session_id: SessionId, - ) -> Result<(), DKGError> { + fn store_public_key(&self, key: LocalKeyType, session_id: SessionId) -> Result<(), DKGError> { self.logger.debug(format!("Storing local key for session {session_id:?}")); self.db.store_local_key(session_id, key) } - fn get_authority_set(&self) -> Vec<(KeygenPartyId, Public)> { - (*self.best_authorities).clone() - } - fn get_gossip_engine(&self) -> Option<&Self::GossipEngine> { Some(&self.gossip_engine) } + fn get_backend_db(&self) -> Option<&Arc> { + Some(&self.db) + } + fn now(&self) -> Self::Clock { self.get_latest_block_number() } diff --git a/dkg-gadget/src/async_protocols/keygen/handler.rs b/dkg-gadget/src/async_protocols/ecdsa/keygen/handler.rs similarity index 100% rename from dkg-gadget/src/async_protocols/keygen/handler.rs rename to dkg-gadget/src/async_protocols/ecdsa/keygen/handler.rs diff --git a/dkg-gadget/src/async_protocols/keygen/mod.rs b/dkg-gadget/src/async_protocols/ecdsa/keygen/mod.rs similarity index 100% rename from dkg-gadget/src/async_protocols/keygen/mod.rs rename to dkg-gadget/src/async_protocols/ecdsa/keygen/mod.rs diff --git a/dkg-gadget/src/async_protocols/keygen/state_machine.rs b/dkg-gadget/src/async_protocols/ecdsa/keygen/state_machine.rs similarity index 96% rename from dkg-gadget/src/async_protocols/keygen/state_machine.rs rename to dkg-gadget/src/async_protocols/ecdsa/keygen/state_machine.rs index bd1c69b6d..98d9c2221 100644 --- a/dkg-gadget/src/async_protocols/keygen/state_machine.rs +++ b/dkg-gadget/src/async_protocols/ecdsa/keygen/state_machine.rs @@ -15,7 +15,7 @@ use crate::{ async_protocols::{ blockchain_interface::BlockchainInterface, state_machine::StateMachineHandler, - AsyncProtocolParameters, ProtocolType, + types::LocalKeyType, AsyncProtocolParameters, ProtocolType, }, debug_logger::DebugLogger, }; @@ -100,7 +100,9 @@ impl StateMachineHandler for Keygen { // gossip the public key at the end, storing it locally first because of causal ordering: // the handler of the gossip public key message will need access to the locally stored // public key. Thus, store the public key first, then, broadcast the message. - params.engine.store_public_key(local_key.clone(), session_id)?; + params + .engine + .store_public_key(LocalKeyType::ECDSA(local_key.clone()), session_id)?; params.engine.gossip_public_key(pub_key_msg)?; Ok(local_key) diff --git a/dkg-gadget/src/async_protocols/ecdsa/mod.rs b/dkg-gadget/src/async_protocols/ecdsa/mod.rs new file mode 100644 index 000000000..e51884adf --- /dev/null +++ b/dkg-gadget/src/async_protocols/ecdsa/mod.rs @@ -0,0 +1,2 @@ +pub mod keygen; +pub mod sign; diff --git a/dkg-gadget/src/async_protocols/sign/handler.rs b/dkg-gadget/src/async_protocols/ecdsa/sign/handler.rs similarity index 96% rename from dkg-gadget/src/async_protocols/sign/handler.rs rename to dkg-gadget/src/async_protocols/ecdsa/sign/handler.rs index 0c3c84155..e963bef3a 100644 --- a/dkg-gadget/src/async_protocols/sign/handler.rs +++ b/dkg-gadget/src/async_protocols/ecdsa/sign/handler.rs @@ -34,13 +34,17 @@ use crate::{ new_inner, remote::{MetaHandlerStatus, ShutdownReason}, state_machine::StateMachineHandler, + types::{LocalKeyType, VoteResult}, AsyncProtocolParameters, BatchKey, GenericAsyncHandler, KeygenPartyId, OfflinePartyId, ProtocolType, Threshold, }, utils::bad_actors_to_authorities, }; use dkg_logging::debug_logger::RoundsEventType; -use dkg_primitives::types::{DKGError, DKGMessage, NetworkMsgPayload, SignedDKGMessage}; +use dkg_primitives::{ + types::{DKGError, DKGMessage, NetworkMsgPayload, SignedDKGMessage}, + utils::convert_signature, +}; use dkg_runtime_primitives::{ crypto::{AuthorityId, Public}, MaxAuthorities, @@ -88,7 +92,7 @@ where let protocol = async move { let maybe_local_key = params.local_key.clone(); - if let Some(local_key) = maybe_local_key { + if let Some(LocalKeyType::ECDSA(local_key)) = maybe_local_key { let t = threshold; start_rx @@ -380,13 +384,18 @@ where reason: format!("Verification of voting stage failed with error : {err:?}"), })?; params.logger.info_signing("RD3"); - params.engine.process_vote_result( + let signature = convert_signature(&signature).ok_or_else(|| { + DKGError::CriticalError { reason: "Unable to serialize signature".to_string() } + })?; + params.logger.info_signing("RD4"); + + let result = VoteResult::ECDSA { signature, unsigned_proposal_batch, - params.session_id, + session_id: params.session_id, batch_key, - message, - )?; + }; + params.engine.process_vote_result(result)?; params.logger.round_event( &ty, RoundsEventType::ProceededToRound { session: params.session_id, round: 9999999999 }, diff --git a/dkg-gadget/src/async_protocols/sign/mod.rs b/dkg-gadget/src/async_protocols/ecdsa/sign/mod.rs similarity index 100% rename from dkg-gadget/src/async_protocols/sign/mod.rs rename to dkg-gadget/src/async_protocols/ecdsa/sign/mod.rs diff --git a/dkg-gadget/src/async_protocols/sign/state_machine.rs b/dkg-gadget/src/async_protocols/ecdsa/sign/state_machine.rs similarity index 100% rename from dkg-gadget/src/async_protocols/sign/state_machine.rs rename to dkg-gadget/src/async_protocols/ecdsa/sign/state_machine.rs diff --git a/dkg-gadget/src/async_protocols/frost/keygen/mod.rs b/dkg-gadget/src/async_protocols/frost/keygen/mod.rs new file mode 100644 index 000000000..a3bd4dcf8 --- /dev/null +++ b/dkg-gadget/src/async_protocols/frost/keygen/mod.rs @@ -0,0 +1,52 @@ +use crate::{ + async_protocols::{blockchain_interface::BlockchainInterface, types::LocalKeyType}, + dkg_modules::wt_frost::{validate_parameters, NetInterface}, + utils::SendFuture, +}; +use dkg_primitives::types::DKGError; +use dkg_runtime_primitives::{gossip_messages::PublicKeyMessage, SessionId}; +use std::pin::Pin; +use wsts::v2::Party; + +/// `party_id`: Should be in the range [0, n). For the DKG, should be our index in the best +/// authorities starting from 0. +pub fn protocol( + n: u32, + party_id: u32, + k: u32, + t: u32, + mut network: Net, + bc: BI, + session_id: SessionId, +) -> Pin>> { + Box::pin(async move { + validate_parameters(n, k, t)?; + + let mut rng = rand::rngs::OsRng; + let key_ids = crate::dkg_modules::wt_frost::generate_party_key_ids(n, k); + let our_key_ids = key_ids + .get(party_id as usize) + .ok_or_else(|| DKGError::StartKeygen { reason: "Bad party_id".to_string() })?; + + let mut party = Party::new(party_id, our_key_ids, n, k, t, &mut rng); + let public_key = + crate::dkg_modules::wt_frost::run_dkg(&mut party, &mut rng, &mut network, n as usize) + .await?; + + // Encode via serde_json + let public_key_bytes = serde_json::to_vec(&public_key) + .map_err(|err| DKGError::GenericError { reason: err.to_string() })?; + + // Gossip the public key + let pkey_message = + PublicKeyMessage { session_id, pub_key: public_key_bytes, signature: vec![] }; + + let state = party.save(); + + // Store and gossip the public key + bc.store_public_key(LocalKeyType::FROST(public_key, state), session_id)?; + bc.gossip_public_key(pkey_message)?; + + Ok(()) + }) +} diff --git a/dkg-gadget/src/async_protocols/frost/mod.rs b/dkg-gadget/src/async_protocols/frost/mod.rs new file mode 100644 index 000000000..eea7f2943 --- /dev/null +++ b/dkg-gadget/src/async_protocols/frost/mod.rs @@ -0,0 +1,153 @@ +use crate::{ + async_protocols::{blockchain_interface::BlockchainInterface, remote::AsyncProtocolRemote}, + dkg_modules::wt_frost::{FrostMessage, NetInterface}, + gossip_engine::GossipEngineIface, + worker::{DKGWorker, ProtoStageType}, + Client, +}; +use async_trait::async_trait; +use dkg_primitives::types::{DKGError, DKGMessage, NetworkMsgPayload, SignedDKGMessage, SSID}; +use dkg_runtime_primitives::{ + crypto::AuthorityId, + gossip_messages::{DKGKeygenMessage, DKGOfflineMessage}, + DKGApi, MaxAuthorities, MaxProposalLength, +}; +use sc_client_api::Backend; +use sp_core::hashing::sha2_256; +use sp_runtime::traits::{Block, Header, NumberFor}; +use std::collections::HashSet; +use tokio::sync::mpsc::UnboundedReceiver; + +pub mod keygen; +pub mod sign; + +pub struct FrostNetworkWrapper +where + B: Block, + BE: Backend, + C: Client, + GE: GossipEngineIface, +{ + pub dkg_worker: DKGWorker, + pub remote: AsyncProtocolRemote<<::Header as Header>::Number>, + pub message_receiver: UnboundedReceiver>, + pub authority_id: AuthorityId, + pub proto_hash: [u8; 32], + pub received_messages: HashSet<[u8; 32]>, + pub engine: BI, + pub ssid: SSID, +} + +impl FrostNetworkWrapper +where + B: Block, + BE: Backend + Unpin, + C: Client, + C::Api: DKGApi, MaxProposalLength, MaxAuthorities>, + GE: GossipEngineIface, +{ + pub fn new( + dkg_worker: DKGWorker, + engine: BI, + remote: AsyncProtocolRemote<<::Header as Header>::Number>, + authority_id: AuthorityId, + proto_hash: [u8; 32], + ) -> Self { + let message_receiver = + remote.rx_keygen_signing.lock().take().expect("rx_keygen_signing already taken"); + let ssid = remote.ssid; + let received_messages = HashSet::new(); + + Self { + dkg_worker, + engine, + remote, + message_receiver, + authority_id, + proto_hash, + received_messages, + ssid, + } + } +} + +#[async_trait] +impl NetInterface for FrostNetworkWrapper +where + B: Block, + BE: Backend, + C: Client, + GE: GossipEngineIface, +{ + type Error = DKGError; + + async fn next_message(&mut self) -> Result, Self::Error> { + loop { + if let Some(message) = self.message_receiver.recv().await { + // When we receive a message, it is filtered through the Job Manager, and as such + // we have these guarantees: + // * The SSID is correct, the block ID and session ID are acceptable, and the task + // hash is correct + // We do not need to check these things here, but we do need to check the signature + match self.engine.verify_signature_against_authorities(message).await { + Ok(message) => { + let message_bin = message.payload.payload(); + let message_hash = sha2_256(message_bin); + + // Check to make sure we haven't already received the message + if !self.received_messages.insert(message_hash) { + self.dkg_worker + .logger + .info("Received duplicate FROST keygen message, ignoring"); + continue + } + + let deserialized = bincode2::deserialize::(message_bin) + .map_err(|err| DKGError::GenericError { reason: err.to_string() })?; + return Ok(Some(deserialized)) + }, + Err(err) => { + self.dkg_worker.logger.info(format!( + "Received invalid FROST keygen message, ignoring: {err:?}" + )); + }, + } + } else { + return Ok(None) + } + } + } + + async fn send_message(&mut self, msg: FrostMessage) -> Result<(), Self::Error> { + let frost_msg = bincode2::serialize(&msg) + .map_err(|err| DKGError::GenericError { reason: err.to_string() })?; + + let payload = if matches!(self.remote.proto_stage_type, ProtoStageType::Signing { .. }) { + NetworkMsgPayload::Offline(DKGOfflineMessage { + key: vec![], + signer_set_id: 0, + offline_msg: frost_msg, + unsigned_proposal_hash: self.proto_hash, + }) + } else { + NetworkMsgPayload::Keygen(DKGKeygenMessage { + sender_id: 0, /* We do not care to put the sender ID in the message for + * FROST, since it is already + * inside the FrostMessage */ + keygen_msg: frost_msg, // The Frost Message + keygen_protocol_hash: self.proto_hash, + }) + }; + + let message = DKGMessage { + sender_id: self.authority_id.clone(), + recipient_id: None, // We always gossip/broadcast in FROST + payload, + session_id: self.remote.session_id, + associated_block_id: self.remote.associated_block_id, + ssid: self.remote.ssid, + }; + + self.engine.sign_and_send_msg(message) + } +} diff --git a/dkg-gadget/src/async_protocols/frost/sign/mod.rs b/dkg-gadget/src/async_protocols/frost/sign/mod.rs new file mode 100644 index 000000000..0fb06eace --- /dev/null +++ b/dkg-gadget/src/async_protocols/frost/sign/mod.rs @@ -0,0 +1,51 @@ +use crate::{ + async_protocols::{blockchain_interface::BlockchainInterface, types::VoteResult, BatchKey}, + dkg_modules::wt_frost::NetInterface, + utils::SendFuture, +}; +use codec::Encode; +use dkg_runtime_primitives::{SessionId, StoredUnsignedProposalBatch}; +use std::pin::Pin; +use wsts::{ + common::PolyCommitment, + v2::{Party, PartyState}, +}; + +// TODO prototype testing: see if random selection of keygen parties works +#[allow(clippy::too_many_arguments)] +pub fn protocol( + t: u32, + session_id: SessionId, + batch_key: BatchKey, + mut net: Net, + bc_iface: BI, + public_key: Vec, + state: PartyState, + unsigned_proposal_batch: StoredUnsignedProposalBatch< + BI::BatchId, + BI::MaxProposalLength, + BI::MaxProposalsInBatch, + BI::Clock, + >, +) -> Pin>> { + Box::pin(async move { + let mut party = Party::load(&state); + let mut rng = rand::rngs::OsRng; + let k = state.num_keys; + + // The message we are signing is the encoded unsigned_proposal_batch + let message = unsigned_proposal_batch.proposals.encode(); + + let signature = crate::dkg_modules::wt_frost::run_signing( + &mut party, &mut rng, &message, &mut net, k, t, public_key, + ) + .await?; + bc_iface.process_vote_result(VoteResult::FROST { + signature: signature.into(), + unsigned_proposal_batch, + session_id, + batch_key, + })?; + Ok(()) + }) +} diff --git a/dkg-gadget/src/async_protocols/mod.rs b/dkg-gadget/src/async_protocols/mod.rs index cc3c1dab1..5de115411 100644 --- a/dkg-gadget/src/async_protocols/mod.rs +++ b/dkg-gadget/src/async_protocols/mod.rs @@ -13,15 +13,16 @@ // limitations under the License. pub mod blockchain_interface; +pub mod ecdsa; +pub mod frost; pub mod incoming; -pub mod keygen; pub mod remote; -pub mod sign; pub mod state_machine; pub mod state_machine_wrapper; use sp_runtime::traits::Get; #[cfg(test)] pub mod test_utils; +pub mod types; use curv::elliptic::curves::Secp256k1; use dkg_primitives::{ @@ -60,7 +61,10 @@ use self::{ blockchain_interface::BlockchainInterface, remote::AsyncProtocolRemote, state_machine::StateMachineHandler, state_machine_wrapper::StateMachineWrapper, }; -use crate::{debug_logger::DebugLogger, utils::SendFuture, worker::KeystoreExt, DKGKeystore}; +use crate::{ + async_protocols::types::LocalKeyType, debug_logger::DebugLogger, utils::SendFuture, + worker::KeystoreExt, DKGKeystore, +}; use dkg_logging::debug_logger::AsyncProtocolType; use incoming::IncomingAsyncProtocolWrapper; use multi_party_ecdsa::MessageRoundID; @@ -69,7 +73,7 @@ pub struct AsyncProtocolParameters< BI: BlockchainInterface, MaxAuthorities: Get + Clone + Send + Sync + std::fmt::Debug + 'static, > { - pub engine: Arc, + pub engine: BI, pub keystore: DKGKeystore, pub current_validator_set: Arc>>, pub best_authorities: Arc>, @@ -79,7 +83,7 @@ pub struct AsyncProtocolParameters< pub batch_id_gen: Arc, pub handle: AsyncProtocolRemote, pub session_id: SessionId, - pub local_key: Option>, + pub local_key: Option, pub logger: DebugLogger, pub db: Arc, } diff --git a/dkg-gadget/src/async_protocols/test_utils.rs b/dkg-gadget/src/async_protocols/test_utils.rs index eddc429c2..f4674cc8c 100644 --- a/dkg-gadget/src/async_protocols/test_utils.rs +++ b/dkg-gadget/src/async_protocols/test_utils.rs @@ -1,17 +1,17 @@ #![allow(clippy::unwrap_used)] // allow unwraps in tests -use crate::async_protocols::{blockchain_interface::BlockchainInterface, BatchKey}; -use codec::Encode; -use curv::{elliptic::curves::Secp256k1, BigInt}; -use dkg_primitives::{ - types::{DKGError, DKGMessage, SessionId, SignedDKGMessage}, - utils::convert_signature, +use crate::{ + async_protocols::{ + blockchain_interface::BlockchainInterface, + types::{LocalKeyType, VoteResult}, + BatchKey, + }, + db::DKGDbBackend, }; +use codec::Encode; +use dkg_primitives::types::{DKGError, DKGMessage, SessionId, SignedDKGMessage}; use dkg_runtime_primitives::{ crypto::Public, gossip_messages::PublicKeyMessage, BatchId, MaxProposalLength, - MaxProposalsInBatch, MaxSignatureLength, SignedProposalBatch, StoredUnsignedProposalBatch, -}; -use multi_party_ecdsa::protocols::multi_party_ecdsa::gg_2020::{ - party_i::SignatureRecid, state_machine::keygen::LocalKey, + MaxProposalsInBatch, MaxSignatureLength, SignedProposalBatch, }; use parking_lot::Mutex; use std::{collections::HashMap, sync::Arc}; @@ -42,7 +42,7 @@ pub struct TestDummyIface { pub authority_public_key: Arc, // key is party_index, hash of data. Needed especially for local unit tests pub vote_results: VoteResults, - pub keygen_key: Arc>>>, + pub keygen_key: Arc>>, } #[async_trait::async_trait] @@ -76,53 +76,50 @@ impl BlockchainInterface for TestDummyIface { Ok(()) } - fn process_vote_result( - &self, - signature: SignatureRecid, - unsigned_proposal_batch: StoredUnsignedProposalBatch< - Self::BatchId, - Self::MaxProposalLength, - Self::MaxProposalsInBatch, - Self::Clock, - >, - _session_id: SessionId, - batch_key: BatchKey, - _message: BigInt, - ) -> Result<(), DKGError> { - let mut lock = self.vote_results.lock(); - - let signature = convert_signature(&signature).ok_or_else(|| DKGError::CriticalError { - reason: "Unable to serialize signature".to_string(), - })?; - - let mut signed_proposals = vec![]; - - // convert all unsigned proposals to signed - for unsigned_proposal in unsigned_proposal_batch.proposals.iter() { - signed_proposals.push(Proposal::Signed { - kind: unsigned_proposal.proposal.kind(), - data: unsigned_proposal - .data() - .clone() - .try_into() - .expect("should not happen since its a valid proposal"), + fn process_vote_result(&self, result: VoteResult) -> Result<(), DKGError> { + if let VoteResult::ECDSA { + signature, + unsigned_proposal_batch, + session_id: _sid, + batch_key, + } = result + { + let mut lock = self.vote_results.lock(); + + let mut signed_proposals = vec![]; + + // convert all unsigned proposals to signed + for unsigned_proposal in unsigned_proposal_batch.proposals.iter() { + signed_proposals.push(Proposal::Signed { + kind: unsigned_proposal.proposal.kind(), + data: unsigned_proposal + .data() + .clone() + .try_into() + .expect("should not happen since its a valid proposal"), + signature: signature + .encode() + .try_into() + .expect("Signature exceeds runtime bounds!"), + }); + } + + let signed_proposal_batch = SignedProposalBatch { + batch_id: unsigned_proposal_batch.batch_id, + proposals: signed_proposals.try_into().expect("Proposals exceeds runtime bounds!"), signature: signature .encode() .try_into() .expect("Signature exceeds runtime bounds!"), - }); - } + }; - let signed_proposal_batch = SignedProposalBatch { - batch_id: unsigned_proposal_batch.batch_id, - proposals: signed_proposals.try_into().expect("Proposals exceeds runtime bounds!"), - signature: signature.encode().try_into().expect("Signature exceeds runtime bounds!"), - }; + let proposals_for_this_batch = lock.entry(batch_key).or_default(); + proposals_for_this_batch.push(signed_proposal_batch); - let proposals_for_this_batch = lock.entry(batch_key).or_default(); - proposals_for_this_batch.push(signed_proposal_batch); - - Ok(()) + Ok(()) + } else { + panic!("Only ECDSA is supported in the test interface"); + } } fn gossip_public_key(&self, _key: PublicKeyMessage) -> Result<(), DKGError> { @@ -130,16 +127,16 @@ impl BlockchainInterface for TestDummyIface { Ok(()) } - fn store_public_key(&self, key: LocalKey, _: SessionId) -> Result<(), DKGError> { + fn store_public_key(&self, key: LocalKeyType, _: SessionId) -> Result<(), DKGError> { *self.keygen_key.lock() = Some(key); Ok(()) } - fn get_authority_set(&self) -> Vec<(KeygenPartyId, Public)> { - (*self.best_authorities).clone() + fn get_gossip_engine(&self) -> Option<&Self::GossipEngine> { + None } - fn get_gossip_engine(&self) -> Option<&Self::GossipEngine> { + fn get_backend_db(&self) -> Option<&Arc> { None } diff --git a/dkg-gadget/src/async_protocols/types.rs b/dkg-gadget/src/async_protocols/types.rs new file mode 100644 index 000000000..2de372457 --- /dev/null +++ b/dkg-gadget/src/async_protocols/types.rs @@ -0,0 +1,89 @@ +use crate::async_protocols::{blockchain_interface::BlockchainInterface, BatchKey}; +use codec::Encode; +use curv::elliptic::curves::Secp256k1; +use dkg_runtime_primitives::{SessionId, StoredUnsignedProposalBatch}; +use multi_party_ecdsa::protocols::multi_party_ecdsa::gg_2020::state_machine::keygen::LocalKey; +use serde::{Deserialize, Serialize}; +use std::fmt::{Debug, Formatter}; +use wsts::{ + common::{PolyCommitment, Signature}, + v2::PartyState, + Point, Scalar, +}; + +#[derive(Serialize, Deserialize)] +#[allow(clippy::large_enum_variant)] +pub enum LocalKeyType { + ECDSA(LocalKey), + FROST(Vec, PartyState), +} + +impl Debug for LocalKeyType { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + LocalKeyType::ECDSA(key) => write!(f, "ECDSA({key:?})"), + LocalKeyType::FROST(key, ..) => write!(f, "FROST({key:?})"), + } + } +} + +impl Clone for LocalKeyType { + fn clone(&self) -> Self { + match self { + LocalKeyType::ECDSA(key) => LocalKeyType::ECDSA(key.clone()), + LocalKeyType::FROST(key, state) => { + // PartyState does not impl Clone, but it does impl Serialize and Deserialize + let state = bincode2::serialize(state).expect("Failed to serialize state"); + let state = bincode2::deserialize(&state).expect("Failed to deserialize state"); + LocalKeyType::FROST(key.clone(), state) + }, + } + } +} + +pub enum VoteResult { + ECDSA { + signature: sp_core::ecdsa::Signature, + unsigned_proposal_batch: StoredUnsignedProposalBatch< + BI::BatchId, + BI::MaxProposalLength, + BI::MaxProposalsInBatch, + BI::Clock, + >, + session_id: SessionId, + batch_key: BatchKey, + }, + FROST { + signature: FrostSignature, + unsigned_proposal_batch: StoredUnsignedProposalBatch< + BI::BatchId, + BI::MaxProposalLength, + BI::MaxProposalsInBatch, + BI::Clock, + >, + session_id: SessionId, + batch_key: BatchKey, + }, +} + +#[derive(Encode)] +pub struct FrostSignature { + pub inner: sp_core::Bytes, +} + +impl From for FrostSignature { + fn from(inner: Signature) -> Self { + let intermediate = Intermediate { point: inner.R, scalar: inner.z }; + + let serialized_bytes = + bincode2::serialize(&intermediate).expect("Failed to serialize signature"); + + Self { inner: sp_core::Bytes::from(serialized_bytes) } + } +} + +#[derive(Serialize, Deserialize)] +struct Intermediate { + point: Point, + scalar: Scalar, +} diff --git a/dkg-gadget/src/db/mem.rs b/dkg-gadget/src/db/mem.rs index 3be255297..74641707c 100644 --- a/dkg-gadget/src/db/mem.rs +++ b/dkg-gadget/src/db/mem.rs @@ -3,15 +3,14 @@ use std::{collections::BTreeMap, sync::Mutex}; -use curv::elliptic::curves::Secp256k1; +use crate::async_protocols::types::LocalKeyType; use dkg_primitives::{types::DKGError, SessionId}; -use multi_party_ecdsa::protocols::multi_party_ecdsa::gg_2020::state_machine::keygen::LocalKey; type LockedMap = Mutex>; /// In Memory storage backend for DKG database, this is used for testing purposes. pub struct DKGInMemoryDb { - local_keys: LockedMap>, + local_keys: LockedMap, } impl Default for DKGInMemoryDb { @@ -29,10 +28,7 @@ impl DKGInMemoryDb { } impl super::DKGDbBackend for DKGInMemoryDb { - fn get_local_key( - &self, - session_id: SessionId, - ) -> Result>, DKGError> { + fn get_local_key(&self, session_id: SessionId) -> Result, DKGError> { let lock = self.local_keys.lock().map_err(|e| DKGError::CriticalError { reason: format!("Failed to lock local_keys: {e}"), })?; @@ -42,7 +38,7 @@ impl super::DKGDbBackend for DKGInMemoryDb { fn store_local_key( &self, session_id: SessionId, - local_key: LocalKey, + local_key: LocalKeyType, ) -> Result<(), DKGError> { let mut lock = self.local_keys.lock().map_err(|e| DKGError::CriticalError { reason: format!("Failed to lock local_keys: {e}"), diff --git a/dkg-gadget/src/db/mod.rs b/dkg-gadget/src/db/mod.rs index 582b60c84..c8bfdd7a5 100644 --- a/dkg-gadget/src/db/mod.rs +++ b/dkg-gadget/src/db/mod.rs @@ -1,14 +1,13 @@ -use curv::elliptic::curves::Secp256k1; use dkg_primitives::{types::DKGError, SessionId}; -use multi_party_ecdsa::protocols::multi_party_ecdsa::gg_2020::state_machine::keygen::LocalKey; mod mem; mod offchain_storage; +use crate::async_protocols::types::LocalKeyType; pub use mem::DKGInMemoryDb; pub use offchain_storage::DKGOffchainStorageDb; -/// A Database backend, specificly for the DKG to store and load important state +/// A Database backend, specifically for the DKG to store and load important state /// /// The backend of this database could be using a persistence store or in-memory /// ephemeral store, depending on the use case. For example, during the tests we can switch @@ -16,12 +15,11 @@ pub use offchain_storage::DKGOffchainStorageDb; #[auto_impl::auto_impl(Arc, &, &mut)] pub trait DKGDbBackend: Send + Sync + 'static { /// Returns the DKG [`LocalKey`] at specific session, if any. - fn get_local_key(&self, session_id: SessionId) - -> Result>, DKGError>; + fn get_local_key(&self, session_id: SessionId) -> Result, DKGError>; /// Stores the [`LocalKey`] at a specified session. fn store_local_key( &self, session_id: SessionId, - local_key: LocalKey, + local_key: LocalKeyType, ) -> Result<(), DKGError>; } diff --git a/dkg-gadget/src/db/offchain_storage.rs b/dkg-gadget/src/db/offchain_storage.rs index c8af2013f..62acb51ff 100644 --- a/dkg-gadget/src/db/offchain_storage.rs +++ b/dkg-gadget/src/db/offchain_storage.rs @@ -4,15 +4,13 @@ use std::sync::Arc; -use crate::debug_logger::DebugLogger; -use curv::elliptic::curves::Secp256k1; +use crate::{async_protocols::types::LocalKeyType, debug_logger::DebugLogger}; use dkg_primitives::{ types::DKGError, utils::{decrypt_data, encrypt_data}, SessionId, }; use dkg_runtime_primitives::offchain::crypto::{Pair as AppPair, Public}; -use multi_party_ecdsa::protocols::multi_party_ecdsa::gg_2020::state_machine::keygen::LocalKey; use sc_client_api::Backend; use sc_keystore::LocalKeystore; use sp_core::{offchain::OffchainStorage, Pair}; @@ -77,10 +75,7 @@ where B: Block, BE: Backend + Unpin + 'static, { - fn get_local_key( - &self, - session_id: SessionId, - ) -> Result>, DKGError> { + fn get_local_key(&self, session_id: SessionId) -> Result, DKGError> { self.logger .trace(format!("Offchain Storage : Fetching local keys for session {session_id:?}")); let db_key = keys::LocalKey::new(session_id); @@ -101,7 +96,7 @@ where fn store_local_key( &self, session_id: SessionId, - local_key: LocalKey, + local_key: LocalKeyType, ) -> Result<(), DKGError> { self.logger.trace(format!( "Offchain Storage : Store local keys for session {session_id:?}, Key : {local_key:?}" diff --git a/dkg-gadget/src/dkg_modules/mod.rs b/dkg-gadget/src/dkg_modules/mod.rs index 1db1f8505..696d7a07b 100644 --- a/dkg-gadget/src/dkg_modules/mod.rs +++ b/dkg-gadget/src/dkg_modules/mod.rs @@ -1,5 +1,5 @@ use crate::{ - async_protocols::{remote::AsyncProtocolRemote, KeygenPartyId}, + async_protocols::{remote::AsyncProtocolRemote, BatchKey, KeygenPartyId}, gossip_engine::GossipEngineIface, utils::SendFuture, worker::{DKGWorker, ProtoStageType}, @@ -34,7 +34,16 @@ pub enum KeygenProtocolSetupParameters { stage: ProtoStageType, keygen_protocol_hash: [u8; 32], }, - WTFrost {}, + WTFrost { + authority_id: AuthorityId, + best_authorities: Vec<(KeygenPartyId, Public)>, + authority_public_key: Public, + keygen_protocol_hash: [u8; 32], + threshold: u32, + session_id: SessionId, + associated_block: NumberFor, + stage: ProtoStageType, + }, } /// Setup parameters for the Signing protocol @@ -55,9 +64,23 @@ pub enum SigningProtocolSetupParameters { signing_set: Vec, associated_block_id: NumberFor, ssid: SSID, + }, + WTFrost { + authority_id: AuthorityId, unsigned_proposal_hash: [u8; 32], + unsigned_proposal_batch: StoredUnsignedProposalBatch< + BatchId, + MaxProposalLength, + MaxProposalsInBatch, + NumberFor, + >, + threshold: u32, + session_id: SessionId, + batch_key: BatchKey, + associated_block: NumberFor, + stage: ProtoStageType, + ssid: SSID, }, - WTFrost {}, } /// A type which is used directly by the Job Manager to initialize and manage the DKG protocol @@ -97,7 +120,10 @@ where { /// Loads the default DKG modules internally to be available at runtime pub fn initialize(&self, dkg_worker: DKGWorker) { - *self.dkgs.write() = vec![Arc::new(MpEcdsaDKG { dkg_worker }), Arc::new(WTFrostDKG {})] + *self.dkgs.write() = vec![ + Arc::new(MpEcdsaDKG { dkg_worker: dkg_worker.clone() }), + Arc::new(WTFrostDKG { dkg_worker }), + ] } /// Given a set of parameters, returns the keygen protocol initializer which can handle the diff --git a/dkg-gadget/src/dkg_modules/mp_ecdsa.rs b/dkg-gadget/src/dkg_modules/mp_ecdsa.rs index 2a2f622b8..b0cdb6bda 100644 --- a/dkg-gadget/src/dkg_modules/mp_ecdsa.rs +++ b/dkg-gadget/src/dkg_modules/mp_ecdsa.rs @@ -4,8 +4,6 @@ use crate::{ KeygenProtocolSetupParameters, ProtocolInitReturn, SigningProtocolSetupParameters, DKG, }, gossip_engine::GossipEngineIface, - keygen_manager::KeygenState, - signing_manager::SigningResult, worker::{DKGWorker, ProtoStageType}, Client, }; @@ -14,7 +12,6 @@ use dkg_primitives::types::{DKGError, SSID}; use dkg_runtime_primitives::{crypto::AuthorityId, DKGApi, MaxAuthorities, MaxProposalLength}; use sc_client_api::Backend; use sp_runtime::traits::{Block, NumberFor}; -use std::sync::atomic::Ordering; /// DKG module for Multi-Party ECDSA pub struct MpEcdsaDKG @@ -62,10 +59,7 @@ where KEYGEN_SSID, ) { Ok(async_proto_params) => { - let err_handler_tx = self.dkg_worker.error_handler_channel.tx.clone(); - let remote = async_proto_params.handle.clone(); - let keygen_manager = self.dkg_worker.keygen_manager.clone(); let status = match stage { ProtoStageType::KeygenGenesis => KeygenRound::Genesis, ProtoStageType::KeygenStandard => KeygenRound::Next, @@ -81,43 +75,8 @@ where keygen_protocol_hash, ) { Ok(meta_handler) => { - let logger = self.dkg_worker.logger.clone(); - let signing_manager = self.dkg_worker.signing_manager.clone(); - signing_manager.keygen_lock(); - let task = async move { - match meta_handler.await { - Ok(_) => { - keygen_manager.set_state(KeygenState::KeygenCompleted { - session_completed: session_id, - }); - let _ = keygen_manager - .finished_count - .fetch_add(1, Ordering::SeqCst); - signing_manager.keygen_unlock(); - logger.info( - "The keygen meta handler has executed successfully" - .to_string(), - ); - - Ok(()) - }, - - Err(err) => { - logger.error(format!( - "Error executing meta handler {:?}", - &err - )); - keygen_manager - .set_state(KeygenState::Failed { session_id }); - signing_manager.keygen_unlock(); - let _ = err_handler_tx.send(err.clone()); - Err(err) - }, - } - }; - self.dkg_worker.logger.debug(format!("Created Keygen Protocol task for session {session_id} with status {status:?}")); - return Some((remote, Box::pin(task))) + return Some((remote, Box::pin(meta_handler))) }, Err(err) => { @@ -155,7 +114,6 @@ where signing_set, associated_block_id, ssid, - unsigned_proposal_hash, } = params { self.dkg_worker.logger.debug(format!("{party_i:?} All Parameters: {best_authorities:?} | authority_pub_key: {authority_public_key:?} | session_id: {session_id:?} | threshold: {threshold:?} | stage: {stage:?} | unsigned_proposal_batch: {unsigned_proposal_batch:?} | signing_set: {signing_set:?} | associated_block_id: {associated_block_id:?}")); @@ -170,43 +128,14 @@ where )?; let handle = async_proto_params.handle.clone(); - - let err_handler_tx = self.dkg_worker.error_handler_channel.tx.clone(); let meta_handler = GenericAsyncHandler::setup_signing( async_proto_params, threshold, unsigned_proposal_batch, signing_set, )?; - let logger = self.dkg_worker.logger.clone(); - let signing_manager = self.dkg_worker.signing_manager.clone(); - let task = async move { - match meta_handler.await { - Ok(_) => { - logger.info("The meta handler has executed successfully"); - signing_manager - .update_local_signing_set_state(SigningResult::Success { - unsigned_proposal_hash, - }) - .await; - Ok(()) - }, - - Err(err) => { - logger.error(format!("Error executing meta handler {:?}", &err)); - signing_manager - .update_local_signing_set_state(SigningResult::Failure { - unsigned_proposal_hash, - ssid, - }) - .await; - let _ = err_handler_tx.send(err.clone()); - Err(err) - }, - } - }; - Ok((handle, Box::pin(task))) + Ok((handle, Box::pin(meta_handler))) } else { unreachable!("Should not happen (signing)") } diff --git a/dkg-gadget/src/dkg_modules/wt_frost.rs b/dkg-gadget/src/dkg_modules/wt_frost.rs index 9de6d65ea..01c33d05b 100644 --- a/dkg-gadget/src/dkg_modules/wt_frost.rs +++ b/dkg-gadget/src/dkg_modules/wt_frost.rs @@ -1,27 +1,226 @@ -use crate::dkg_modules::{ - KeygenProtocolSetupParameters, ProtocolInitReturn, SigningProtocolSetupParameters, DKG, +use crate::{ + async_protocols::{ + blockchain_interface::DKGProtocolEngine, frost::FrostNetworkWrapper, + remote::AsyncProtocolRemote, types::LocalKeyType, + }, + db::DKGDbBackend, + dkg_modules::{ + KeygenProtocolSetupParameters, ProtocolInitReturn, SigningProtocolSetupParameters, DKG, + }, + gossip_engine::GossipEngineIface, + worker::{DKGWorker, HasLatestHeader, ProtoStageType}, + Client, }; use async_trait::async_trait; -use dkg_primitives::types::DKGError; -use sp_runtime::traits::Block; +use dkg_primitives::types::{DKGError, SSID}; +use dkg_runtime_primitives::{crypto::AuthorityId, DKGApi, MaxAuthorities, MaxProposalLength}; +use itertools::Itertools; +use rand::{CryptoRng, RngCore}; +use sc_client_api::Backend; +use serde::{Deserialize, Serialize}; +use sp_arithmetic::traits::SaturatedConversion; +use sp_runtime::traits::{Block, NumberFor}; +use std::{collections::HashMap, fmt::Debug, sync::Arc}; +use wsts::{ + common::{PolyCommitment, PublicNonce, Signature, SignatureShare}, + v2, + v2::SignatureAggregator, + Scalar, +}; /// DKG module for Weighted Threshold Frost -pub struct WTFrostDKG {} +pub struct WTFrostDKG +where + B: Block, + BE: Backend, + C: Client, + GE: GossipEngineIface, +{ + pub(super) dkg_worker: DKGWorker, +} #[async_trait] -impl DKG for WTFrostDKG { +impl DKG for WTFrostDKG +where + B: Block, + BE: Backend + Unpin + 'static, + C: Client + 'static, + C::Api: DKGApi, MaxProposalLength, MaxAuthorities>, + GE: GossipEngineIface, +{ async fn initialize_keygen_protocol( &self, - _params: KeygenProtocolSetupParameters, + params: KeygenProtocolSetupParameters, ) -> Option> { - todo!() + if let KeygenProtocolSetupParameters::WTFrost { + best_authorities, + authority_id, + authority_public_key, + keygen_protocol_hash, + threshold, + session_id, + associated_block, + stage, + } = params + { + const KEYGEN_SSID: SSID = 0; + // We must construct a remote and a task to ensure compatibility with the work manager + // and future message delivery to the service + let n = best_authorities.len() as u32; + // For now, assume each party member owns n keys + let k = n; + // The party id for frost must be in [0, n). Thus, we just find out index in the best + // authorities + let party_id = best_authorities + .iter() + .find_position(|r| r.1 == authority_public_key) + .map(|r| r.0 as u32) + .expect("Authority ID not found in best authorities"); + //let at = self.dkg_worker.get_latest_block_number(); + let associated_block_id = associated_block.saturated_into(); + let authority_mapping = Default::default(); // TODO + + // Setup the remote to allow communication between the async protocol and the work + // manager + let remote = AsyncProtocolRemote::new( + associated_block, + session_id, + self.dkg_worker.logger.clone(), + associated_block_id, + KEYGEN_SSID, + stage, + authority_mapping, + ); + + // Setup the engine to allow communication between the async protocol and the blockchain + let bc_iface = DKGProtocolEngine { + backend: self.dkg_worker.backend.clone(), + latest_header: self.dkg_worker.latest_header.clone(), + client: self.dkg_worker.client.clone(), + keystore: self.dkg_worker.key_store.clone(), + db: self.dkg_worker.db.clone(), + gossip_engine: self.dkg_worker.gossip_engine.clone(), + aggregated_public_keys: self.dkg_worker.aggregated_public_keys.clone(), + current_validator_set: self.dkg_worker.current_validator_set.clone(), + local_keystore: self.dkg_worker.local_keystore.clone(), + vote_results: Arc::new(Default::default()), + is_genesis: stage == ProtoStageType::KeygenGenesis, + metrics: self.dkg_worker.metrics.clone(), + test_bundle: self.dkg_worker.test_bundle.clone(), + logger: self.dkg_worker.logger.clone(), + _pd: Default::default(), + }; + + // Allow the async protocol to communicate with the network + let network = FrostNetworkWrapper::new( + self.dkg_worker.clone(), + bc_iface.clone(), + remote.clone(), + authority_id, + keygen_protocol_hash, + ); + + let task = crate::async_protocols::frost::keygen::protocol( + n, party_id, k, threshold, network, bc_iface, session_id, + ); + + Some((remote, task)) + } else { + None + } } async fn initialize_signing_protocol( &self, - _params: SigningProtocolSetupParameters, + params: SigningProtocolSetupParameters, ) -> Result, DKGError> { - todo!() + if let SigningProtocolSetupParameters::WTFrost { + unsigned_proposal_batch, + authority_id, + unsigned_proposal_hash, + threshold, + session_id, + associated_block, + stage, + ssid, + batch_key, + } = params + { + // Load the state + let key = self.dkg_worker.db.get_local_key(session_id)?.ok_or_else(|| { + DKGError::GenericError { reason: "Cannot find FROST key".to_string() } + })?; + + let (public_key, state) = if let LocalKeyType::FROST(public_key, state) = key { + (public_key, state) + } else { + return Err(DKGError::GenericError { + reason: "The obtained local key is NOT a FROST key".to_string(), + }) + }; + + // We must construct a remote and a task to ensure compatibility with the work manager + // and future message delivery to the service + + let at = self.dkg_worker.get_latest_block_number(); + let associated_block_id = associated_block.saturated_into(); + let authority_mapping = Default::default(); // TODO + + // Setup the remote to allow communication between the async protocol and the work + // manager + let remote = AsyncProtocolRemote::new( + at, + session_id, + self.dkg_worker.logger.clone(), + associated_block_id, + ssid, + stage, + authority_mapping, + ); + + // Setup the engine to allow communication between the async protocol and the blockchain + let bc_iface = DKGProtocolEngine { + backend: self.dkg_worker.backend.clone(), + latest_header: self.dkg_worker.latest_header.clone(), + client: self.dkg_worker.client.clone(), + keystore: self.dkg_worker.key_store.clone(), + db: self.dkg_worker.db.clone(), + gossip_engine: self.dkg_worker.gossip_engine.clone(), + aggregated_public_keys: self.dkg_worker.aggregated_public_keys.clone(), + current_validator_set: self.dkg_worker.current_validator_set.clone(), + local_keystore: self.dkg_worker.local_keystore.clone(), + vote_results: Arc::new(Default::default()), + is_genesis: stage == ProtoStageType::KeygenGenesis, + metrics: self.dkg_worker.metrics.clone(), + test_bundle: self.dkg_worker.test_bundle.clone(), + logger: self.dkg_worker.logger.clone(), + _pd: Default::default(), + }; + + // Allow the async protocol to communicate with the network + let network = FrostNetworkWrapper::new( + self.dkg_worker.clone(), + bc_iface.clone(), + remote.clone(), + authority_id, + unsigned_proposal_hash, + ); + + let task = crate::async_protocols::frost::sign::protocol( + threshold, + session_id, + batch_key, + network, + bc_iface, + public_key, + state, + unsigned_proposal_batch, + ); + + Ok((remote, task)) + } else { + Err(DKGError::GenericError { reason: "Invalid parameters".to_string() }) + } } fn can_handle_keygen_request(&self, params: &KeygenProtocolSetupParameters) -> bool { @@ -32,3 +231,390 @@ impl DKG for WTFrostDKG { matches!(params, SigningProtocolSetupParameters::WTFrost { .. }) } } + +pub async fn run_dkg( + signer: &mut v2::Party, + rng: &mut RNG, + net: &mut Net, + n_signers: usize, +) -> Result, DKGError> { + // Broadcast our party_id, shares, and key_ids to each other + let party_id = signer.party_id; + let shares: HashMap = signer.get_shares().into_iter().collect(); + let key_ids = signer.key_ids.clone(); + let poly_commitment = signer.get_poly_commitment(rng); + let message = FrostMessage::DKG { + party_id, + shares: shares.clone(), + key_ids: key_ids.clone(), + poly_commitment: poly_commitment.clone(), + }; + + // Send the message + net.send_message(message).await.map_err(|err| DKGError::GenericError { + reason: format!("Error sending FROST message: {err:?}"), + })?; + + let mut received_shares = HashMap::new(); + let mut received_key_ids = HashMap::new(); + let mut received_poly_commitments = HashMap::new(); + // insert our own shared into the received map + received_shares.insert(party_id, shares); + received_key_ids.insert(party_id, key_ids); + received_poly_commitments.insert(party_id, poly_commitment); + + // Wait for n_signers to send their messages to us + while received_shares.len() < n_signers { + match net.next_message().await { + Ok(Some(FrostMessage::DKG { party_id, shares, key_ids, poly_commitment })) => + if party_id != signer.party_id { + received_shares.insert(party_id, shares); + received_key_ids.insert(party_id, key_ids); + received_poly_commitments.insert(party_id, poly_commitment); + }, + + Ok(Some(_)) | Err(_) => {}, + Ok(None) => + return Err(DKGError::GenericError { + reason: "NetListen connection died".to_string(), + }), + } + } + + // Generate the party_shares: for each key id we own, we take our received key share at that + // index + let party_shares = signer + .key_ids + .iter() + .copied() + .map(|key_id| { + let mut key_shares = HashMap::new(); + + for (id, shares) in &received_shares { + key_shares.insert(*id, shares[&key_id]); + } + + (key_id, key_shares.into_iter().collect()) + }) + .collect(); + let polys = received_poly_commitments + .iter() + .sorted_by(|a, b| a.0.cmp(b.0)) + .map(|r| r.1.clone()) + .collect_vec(); + signer + .compute_secret(&party_shares, &polys) + .map_err(|err| DKGError::GenericError { reason: err.to_string() })?; + Ok(polys) +} + +/// `threshold`: Should be the number of participants in this round, since we stop looking for +/// messages after finding the first `t` messages +pub async fn run_signing( + signer: &mut v2::Party, + rng: &mut RNG, + msg: &[u8], + net: &mut Net, + num_keys: u32, + threshold: u32, + public_key: Vec, +) -> Result { + // Broadcast the party_id, key_ids, and nonce to each other + let nonce = signer.gen_nonce(rng); + let party_id = signer.party_id; + let key_ids = signer.key_ids.clone(); + let message = FrostMessage::Sign { party_id, key_ids: key_ids.clone(), nonce: nonce.clone() }; + + // Send the message + net.send_message(message).await.map_err(|err| DKGError::GenericError { + reason: format!("Error sending FROST message: {err:?}"), + })?; + + let mut party_key_ids = HashMap::new(); + let mut party_nonces = HashMap::new(); + + party_key_ids.insert(party_id, key_ids); + party_nonces.insert(party_id, nonce); + + while party_nonces.len() < threshold as usize { + match net.next_message().await { + Ok(Some(FrostMessage::Sign { party_id: party_id_recv, key_ids, nonce })) => { + if party_id != party_id_recv { + party_key_ids.insert(party_id_recv, key_ids); + party_nonces.insert(party_id_recv, nonce); + } + }, + + Ok(Some(_)) | Err(_) => {}, + Ok(None) => + return Err(DKGError::GenericError { + reason: "NetListen connection died".to_string(), + }), + } + } + + // Sort the vecs + let party_ids = party_key_ids.keys().copied().sorted_by(|a, b| a.cmp(b)).collect_vec(); + let party_key_ids = party_key_ids + .into_iter() + .sorted_by(|a, b| a.0.cmp(&b.0)) + .flat_map(|r| r.1) + .collect_vec(); + let party_nonces = party_nonces + .into_iter() + .sorted_by(|a, b| a.0.cmp(&b.0)) + .map(|r| r.1) + .collect_vec(); + + // Generate our signature share + let signature_share = signer.sign(msg, &party_ids, &party_key_ids, &party_nonces); + let message = FrostMessage::SignFinal { party_id, signature_share: signature_share.clone() }; + // Broadcast our signature share to each other + net.send_message(message).await.map_err(|err| DKGError::GenericError { + reason: format!("Error sending FROST message: {err:?}"), + })?; + + let mut signature_shares = HashMap::new(); + signature_shares.insert(party_id, signature_share.clone()); + + // Receive n_signers number of shares + while signature_shares.len() < threshold as usize { + match net.next_message().await { + Ok(Some(FrostMessage::SignFinal { party_id: party_id_recv, signature_share })) => + if party_id != party_id_recv { + signature_shares.insert(party_id_recv, signature_share); + }, + + Ok(Some(_)) | Err(_) => {}, + Ok(None) => + return Err(DKGError::GenericError { + reason: "NetListen connection died".to_string(), + }), + } + } + + // Sort the signature shares + let signature_shares = signature_shares + .into_iter() + .sorted_by(|a, b| a.0.cmp(&b.0)) + .map(|r| r.1) + .collect_vec(); + + // Aggregate and sign to generate the signature + let mut sig_agg = SignatureAggregator::new(num_keys, threshold, public_key) + .map_err(|err| DKGError::GenericError { reason: err.to_string() })?; + + sig_agg + .sign(msg, &party_nonces, &signature_shares, &party_key_ids) + .map_err(|err| DKGError::GenericError { reason: err.to_string() }) +} + +pub fn create_signer_key_ids(signer_id: u32, keys_per_signer: u32) -> Vec { + (0..keys_per_signer).map(|i| keys_per_signer * signer_id + i).collect() +} + +/// Returns a Vec of indices that denotes which indexes within the public key vector +/// are owned by which party. +/// +/// E.g., if n=4 and k=10, +/// +/// let party_key_ids: Vec> = [ +/// [0, 1, 2].to_vec(), +/// [3, 4].to_vec(), +/// [5, 6, 7].to_vec(), +/// [8, 9].to_vec(), +/// ] +/// +/// In the above case, we go up from 0..=9 possible key ids since k=10, and +/// we have 4 grouping since n=4. We need to generalize this below +#[allow(dead_code)] +pub fn generate_party_key_ids(n: u32, k: u32) -> Vec> { + let mut result = Vec::with_capacity(n as usize); + let ids_per_party = k / n; + let mut start = 0; + + for _ in 0..n { + let end = start + ids_per_party; + let ids = (start..end).collect(); + result.push(ids); + start = end; + } + + result +} + +pub fn validate_parameters(n: u32, k: u32, t: u32) -> Result<(), DKGError> { + if k & n != 0 { + return Err(DKGError::GenericError { reason: "K % N != 0".to_string() }) + } + + if k == 0 { + return Err(DKGError::GenericError { reason: "K == 0".to_string() }) + } + + if n <= t { + return Err(DKGError::GenericError { reason: "N <= T".to_string() }) + } + + Ok(()) +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub enum FrostMessage { + DKG { + party_id: u32, + shares: HashMap, + key_ids: Vec, + poly_commitment: PolyCommitment, + }, + Sign { + party_id: u32, + key_ids: Vec, + nonce: PublicNonce, + }, + SignFinal { + party_id: u32, + signature_share: SignatureShare, + }, +} + +#[async_trait::async_trait] +pub trait NetInterface: Send { + type Error: Debug; + + async fn next_message(&mut self) -> Result, Self::Error>; + async fn send_message(&mut self, msg: FrostMessage) -> Result<(), Self::Error>; +} + +#[cfg(test)] +mod tests { + use crate::dkg_modules::wt_frost::{FrostMessage, NetInterface}; + use futures::{stream::FuturesUnordered, TryStreamExt}; + use rand::prelude::SliceRandom; + + struct TestNetworkLayer { + tx: tokio::sync::broadcast::Sender, + rx: tokio::sync::broadcast::Receiver, + } + + #[async_trait::async_trait] + impl NetInterface for TestNetworkLayer { + type Error = tokio::sync::broadcast::error::SendError; + + async fn next_message(&mut self) -> Result, Self::Error> { + Ok(self.rx.recv().await.ok()) + } + + async fn send_message(&mut self, msg: FrostMessage) -> Result<(), Self::Error> { + self.tx.send(msg).map(|_| ()) + } + } + + #[tokio::test] + async fn test_frost_n3t2k3() { + test_inner::<3, 2, 3>().await; + } + + #[tokio::test] + async fn test_frost_n10t6k10() { + test_inner::<10, 6, 10>().await; + } + + #[tokio::test] + async fn test_frost_n5t3k5() { + test_inner::<5, 3, 5>().await; + } + + #[tokio::test] + async fn test_frost_n20t15k20() { + test_inner::<20, 15, 20>().await; + } + + async fn test_inner() { + dkg_logging::setup_log(); + assert_eq!(K % N, 0); // Enforce that each party owns the same number of keys + assert_ne!(K, 0); // Enforce that K is not zero + assert!(N > T); + + // Each node creates their own party + let mut parties = Vec::new(); + let indices = super::generate_party_key_ids(N, K); + let rng = &mut rand::thread_rng(); + // In reality, the idx below would be our index in the best authorities, starting from zero + for (idx, key_indexes_owned_by_this_party) in indices.into_iter().enumerate() { + // See https://github.com/Trust-Machines/wsts/blob/037e2eb4105cf9f9b1c034ee5c1540a40123b530/src/v2.rs#L515 + // for generating the party key IDS + //let key_indexes_owned_by_this_party = super::create_signer_key_ids(idx, K); + dkg_logging::info!(target: "dkg", "keys owned by party {idx}: {key_indexes_owned_by_this_party:?}"); + parties.push(wsts::v2::Party::new( + idx as _, + &key_indexes_owned_by_this_party, + N, + K, + T, + rng, + )); + } + + // setup the network + let (tx, _) = tokio::sync::broadcast::channel(1000); + let mut networks = (0..N) + .into_iter() + .map(|_idx| TestNetworkLayer { tx: tx.clone(), rx: tx.subscribe() }) + .collect::>(); + + // Test the DKG + let dkgs = FuturesUnordered::new(); + for (party, network) in parties.iter_mut().zip(networks.iter_mut()) { + dkgs.push(Box::pin(async move { + let mut rng = rand::thread_rng(); + crate::dkg_modules::wt_frost::run_dkg(party, &mut rng, network, N as _).await + })); + } + + let mut public_keys = dkgs.try_collect::>().await.unwrap(); + for public_key in &public_keys { + assert_eq!(public_key.len(), N as usize); + for public_key0 in &public_keys { + // Assert all equal + assert!(public_key + .iter() + .zip(public_key0) + .all(|r| r.0.id.kG == r.1.id.kG && + r.0.id.id == r.1.id.id && r.0.id.kca == r.1.id.kca && + r.0.A == r.1.A)); + } + } + + let public_key = public_keys.pop().unwrap(); + + // Test the signing over an arbitrary message + let msg = b"Hello, world!"; + + // Start by choosing signers. Since our indexes, in reality, will be based on the set of + // best authorities, we will choose the best of the best of authorities, so from 0..T + let signers = FuturesUnordered::new(); + + // Prove that no matter which signing set we choose, we get a valid Schnorr signature + parties.shuffle(rng); + + for (party, network) in parties.iter_mut().zip(networks.iter_mut()).take(T as _) { + let public_key = public_key.clone(); + signers.push(Box::pin(async move { + let mut rng = rand::thread_rng(); + crate::dkg_modules::wt_frost::run_signing( + party, &mut rng, &*msg, network, K, T, public_key, + ) + .await + })); + } + + let signatures = signers.try_collect::>().await.unwrap(); + for signature0 in &signatures { + for signature1 in &signatures { + assert_eq!(signature0.R, signature1.R); + assert_eq!(signature0.z, signature1.z); + } + } + } +} diff --git a/dkg-gadget/src/keygen_manager/mod.rs b/dkg-gadget/src/keygen_manager/mod.rs index 2964d33e9..966159238 100644 --- a/dkg-gadget/src/keygen_manager/mod.rs +++ b/dkg-gadget/src/keygen_manager/mod.rs @@ -475,6 +475,39 @@ where self.work_manager.force_shutdown_all(); } + // Now, map the task to properly handle cleanup + let logger = dkg_worker.logger.clone(); + let signing_manager = dkg_worker.signing_manager.clone(); + let keygen_manager = dkg_worker.keygen_manager.clone(); + let err_handler_tx = dkg_worker.error_handler_channel.tx.clone(); + + // Prevent any signing tasks from running while the keygen is running + signing_manager.keygen_lock(); + + let task = Box::pin(async move { + match task.await { + Ok(_) => { + keygen_manager.set_state(KeygenState::KeygenCompleted { + session_completed: session_id, + }); + let _ = keygen_manager.finished_count.fetch_add(1, Ordering::SeqCst); + signing_manager.keygen_unlock(); + logger + .info("The keygen meta handler has executed successfully".to_string()); + + Ok(()) + }, + + Err(err) => { + logger.error(format!("Error executing meta handler {:?}", &err)); + keygen_manager.set_state(KeygenState::Failed { session_id }); + signing_manager.keygen_unlock(); + let _ = err_handler_tx.send(err.clone()); + Err(err) + }, + } + }); + if let Err(err) = self.push_task(handle, task) { dkg_worker.logger.error(format!( "🕸️ PARTY {party_i} | SPAWNING KEYGEN SESSION {session_id} | ERROR: {err}" diff --git a/dkg-gadget/src/signing_manager/mod.rs b/dkg-gadget/src/signing_manager/mod.rs index 2d1d3d4d2..345ba785f 100644 --- a/dkg-gadget/src/signing_manager/mod.rs +++ b/dkg-gadget/src/signing_manager/mod.rs @@ -324,7 +324,6 @@ where signing_set: signing_set.signing_set.clone(), associated_block_id: *header.number(), ssid, - unsigned_proposal_hash, }; let signing_protocol = dkg_worker @@ -334,6 +333,36 @@ where match signing_protocol.initialize_signing_protocol(params).await { Ok((handle, task)) => { + // Map the task to be compatible with this signing manager + let err_handler_tx = dkg_worker.error_handler_channel.tx.clone(); + let logger = dkg_worker.logger.clone(); + let signing_manager = dkg_worker.signing_manager.clone(); + let task = Box::pin(async move { + match task.await { + Ok(_) => { + logger.info("The meta handler has executed successfully"); + signing_manager + .update_local_signing_set_state(SigningResult::Success { + unsigned_proposal_hash, + }) + .await; + Ok(()) + }, + + Err(err) => { + logger + .error(format!("Error executing meta handler {:?}", &err)); + signing_manager + .update_local_signing_set_state(SigningResult::Failure { + unsigned_proposal_hash, + ssid, + }) + .await; + let _ = err_handler_tx.send(err.clone()); + Err(err) + }, + } + }); // Send task to the work manager. Force start if the type chain ID // is None, implying this is a proposal needed for rotating sessions // and thus a priority diff --git a/dkg-gadget/src/storage/proposals.rs b/dkg-gadget/src/storage/proposals.rs index 2cb931775..a690c0b5a 100644 --- a/dkg-gadget/src/storage/proposals.rs +++ b/dkg-gadget/src/storage/proposals.rs @@ -16,7 +16,7 @@ use codec::Encode; use dkg_runtime_primitives::{ crypto::{AuthorityId, Public}, offchain::storage_keys::OFFCHAIN_SIGNED_PROPOSALS, - AuthoritySet, DKGApi, SignedProposalBatch, + AuthoritySet, DKGApi, }; use parking_lot::RwLock; use rand::Rng; @@ -33,17 +33,13 @@ pub(crate) fn save_signed_proposals_in_storage< BE, MaxProposalLength, MaxAuthorities, - BatchId, - MaxProposalsInBatch, - MaxSignatureLength, + SignedProposal, >( authority_public_key: &Public, current_validator_set: &Arc>>, latest_header: &Arc>>, backend: &Arc, - signed_proposals: Vec< - SignedProposalBatch, - >, + signed_proposals: Vec, logger: &DebugLogger, ) where B: Block, @@ -52,11 +48,7 @@ pub(crate) fn save_signed_proposals_in_storage< MaxProposalLength: Get + Clone + Send + Sync + 'static + std::fmt::Debug + std::cmp::PartialEq, MaxAuthorities: Get + Clone + Send + Sync + 'static + std::fmt::Debug, - BatchId: Clone + Send + Sync + std::fmt::Debug + 'static + codec::Encode + codec::Decode, - MaxProposalsInBatch: - Get + Clone + Send + Sync + std::fmt::Debug + 'static + codec::Encode + codec::Decode, - MaxSignatureLength: - Get + Clone + Send + Sync + std::fmt::Debug + 'static + codec::Encode + codec::Decode, + SignedProposal: Encode, C::Api: DKGApi< B, AuthorityId, @@ -87,11 +79,6 @@ pub(crate) fn save_signed_proposals_in_storage< return } - let _current_block_number = { - let header = latest_header.as_ref().expect("Should not happen, checked above"); - header.number() - }; - if let Some(mut offchain) = backend.offchain_storage() { let old_val = offchain.get(STORAGE_PREFIX, OFFCHAIN_SIGNED_PROPOSALS); diff --git a/dkg-gadget/src/worker.rs b/dkg-gadget/src/worker.rs index 63248ce44..7da3ca31e 100644 --- a/dkg-gadget/src/worker.rs +++ b/dkg-gadget/src/worker.rs @@ -19,7 +19,6 @@ use crate::{ debug_logger::DebugLogger, }; use codec::{Codec, Encode}; -use curv::elliptic::curves::Secp256k1; use sc_network::NetworkService; use sc_network_sync::SyncingService; use sp_consensus::SyncOracle; @@ -27,7 +26,6 @@ use sp_consensus::SyncOracle; use crate::signing_manager::SigningManager; use dkg_primitives::types::SSID; use futures::StreamExt; -use multi_party_ecdsa::protocols::multi_party_ecdsa::gg_2020::state_machine::keygen::LocalKey; use parking_lot::{Mutex, RwLock}; use sc_client_api::{Backend, FinalityNotification}; use sc_keystore::LocalKeystore; @@ -57,7 +55,7 @@ use dkg_runtime_primitives::{ pub use crate::constants::worker::*; use crate::{ - async_protocols::{remote::AsyncProtocolRemote, AsyncProtocolParameters}, + async_protocols::{remote::AsyncProtocolRemote, types::LocalKeyType, AsyncProtocolParameters}, dkg_modules::DKGModules, error, gossip_engine::GossipEngineIface, @@ -353,7 +351,7 @@ where )); let params = AsyncProtocolParameters { - engine: Arc::new(DKGProtocolEngine { + engine: DKGProtocolEngine { backend: self.backend.clone(), latest_header: self.latest_header.clone(), client: self.client.clone(), @@ -361,8 +359,6 @@ where db: self.db.clone(), gossip_engine: self.gossip_engine.clone(), aggregated_public_keys: self.aggregated_public_keys.clone(), - best_authorities: best_authorities.clone(), - authority_public_key: authority_public_key.clone(), current_validator_set: self.current_validator_set.clone(), local_keystore: self.local_keystore.clone(), vote_results: Arc::new(Default::default()), @@ -371,7 +367,7 @@ where test_bundle: self.test_bundle.clone(), logger: self.logger.clone(), _pd: Default::default(), - }), + }, session_id, db: self.db.clone(), keystore: self.key_store.clone(), @@ -405,7 +401,7 @@ where fn fetch_local_keys( &self, current_session_id: SessionId, - ) -> (Option>, Option>) { + ) -> (Option, Option) { let next_session_id = current_session_id + 1; let active_local_key = self.db.get_local_key(current_session_id).ok().flatten(); let next_local_key = self.db.get_local_key(next_session_id).ok().flatten(); diff --git a/scripts/bitcoin.sh b/scripts/bitcoin.sh new file mode 100755 index 000000000..988c24027 --- /dev/null +++ b/scripts/bitcoin.sh @@ -0,0 +1,32 @@ +val_of_platform() { + case $1 in + 'darwin'*) + echo 'x86_64-apple-darwin' + ;; + 'x86_64'*) + echo 'x86_64-linux-gnu' + ;; + 'arm'*) + echo 'arm-linux-gnueabihf' + ;; + 'aarch64'*) + echo 'aarch64-linux-gnu' + ;; + *) + echo 'unknown' + ;; + esac +} +platform=$(val_of_platform $(uname -m)) +echo "Platform: ${platform} to $(uname -m) architecture" + +if [[ $OSTYPE == 'darwin'* ]] +then + # URL to Bitcoin v22.0 + curl -o bitcoin.rb https://raw.githubusercontent.com/Homebrew/homebrew-core/fa6b4765d81016166f6de2bdad96cfe914c1439f/Formula/bitcoin.rb + HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=1 brew install ./bitcoin.rb +else + curl -o bitcoin-22.0-${platform}.tar.gz https://bitcoincore.org/bin/bitcoin-core-22.0/bitcoin-22.0-${platform}.tar.gz + tar xzf bitcoin-22.0-${platform}.tar.gz + sudo install -m 0755 -o root -g root -t /usr/local/bin bitcoin-22.0/bin/* +fi