diff --git a/Cargo.lock b/Cargo.lock index e08351b..16a51e8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -215,7 +215,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.119", ] [[package]] @@ -544,7 +544,7 @@ dependencies = [ "nom", "num-traits", "rusticata-macros", - "thiserror", + "thiserror 1.0.61", "time", ] @@ -732,7 +732,7 @@ checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.119", ] [[package]] @@ -748,6 +748,15 @@ dependencies = [ "pin-project-lite 0.2.14", ] +[[package]] +name = "atomic-polyfill" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4" +dependencies = [ + "critical-section", +] + [[package]] name = "atomic-take" version = "1.1.0" @@ -893,7 +902,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn 2.0.66", + "syn 2.0.119", ] [[package]] @@ -935,9 +944,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.5.0" +version = "2.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" +checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" [[package]] name = "bitvec" @@ -1181,12 +1190,12 @@ dependencies = [ "semver 1.0.23", "serde", "serde_json", - "thiserror", + "thiserror 1.0.61", ] [[package]] name = "casper-runtime" -version = "3.5.41" +version = "4.0.0" dependencies = [ "casper-runtime-constants", "frame-benchmarking", @@ -1198,41 +1207,32 @@ dependencies = [ "frame-system-benchmarking", "frame-system-rpc-runtime-api", "frame-try-runtime", - "ghost-claims", "ghost-core-primitives", + "ghost-exodus", + "ghost-governance", + "ghost-helpers", "ghost-networks", - "ghost-runtime-common", - "ghost-slow-clap", + "ghost-nomination-pools", + "ghost-nomination-pools-benchmarking", + "ghost-nomination-pools-runtime-api", "ghost-sudo", "ghost-traits", + "ghost-weaver", + "hex-literal", "log", - "pallet-alliance", "pallet-authority-discovery", "pallet-authorship", "pallet-babe", "pallet-bags-list", "pallet-balances", - "pallet-bounties", - "pallet-child-bounties", - "pallet-collective", - "pallet-core-fellowship", "pallet-election-provider-multi-phase", "pallet-election-provider-support-benchmarking", - "pallet-fast-unstake", "pallet-grandpa", "pallet-identity", - "pallet-indices", - "pallet-multisig", - "pallet-nomination-pools", - "pallet-nomination-pools-benchmarking", - "pallet-nomination-pools-runtime-api", "pallet-offences", "pallet-offences-benchmarking", "pallet-preimage", "pallet-proxy", - "pallet-ranked-collective", - "pallet-referenda", - "pallet-salary", "pallet-scheduler", "pallet-session", "pallet-session-benchmarking", @@ -1245,9 +1245,9 @@ dependencies = [ "pallet-transaction-payment-rpc-runtime-api", "pallet-treasury", "pallet-utility", - "pallet-vesting", "pallet-whitelist", "parity-scale-codec", + "runtime-common", "scale-info", "separator", "serde_json", @@ -1281,11 +1281,11 @@ dependencies = [ [[package]] name = "casper-runtime-constants" -version = "0.3.25" +version = "0.4.0" dependencies = [ "frame-support", "ghost-core-primitives", - "ghost-runtime-common", + "runtime-common", "smallvec", "sp-core 28.0.0", "sp-runtime 31.0.1", @@ -1506,7 +1506,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.119", ] [[package]] @@ -1535,6 +1535,15 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "cobs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1" +dependencies = [ + "thiserror 2.0.18", +] + [[package]] name = "codespan-reporting" version = "0.11.1" @@ -1643,6 +1652,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "const-crc32-nostd" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "808ac43170e95b11dd23d78aa9eaac5bea45776a602955552c4e833f3f0f823d" + [[package]] name = "const-oid" version = "0.9.6" @@ -1867,6 +1882,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + [[package]] name = "crossbeam-deque" version = "0.8.5" @@ -1974,16 +1995,16 @@ dependencies = [ [[package]] name = "curve25519-dalek" -version = "4.1.2" +version = "4.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a677b8922c94e01bdbb12126b0bc852f00447528dee1782229af9c720c3f348" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" dependencies = [ "cfg-if", "cpufeatures", "curve25519-dalek-derive", "digest 0.10.7", "fiat-crypto", - "platforms", + "rand_core 0.6.4", "rustc_version", "subtle 2.5.0", "zeroize", @@ -1997,7 +2018,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.119", ] [[package]] @@ -2024,7 +2045,7 @@ dependencies = [ "proc-macro2", "quote", "scratch", - "syn 2.0.66", + "syn 2.0.119", ] [[package]] @@ -2041,7 +2062,7 @@ checksum = "4b2c1c1776b986979be68bb2285da855f8d8a35851a769fca8740df7c3d07877" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.119", ] [[package]] @@ -2089,7 +2110,7 @@ dependencies = [ "proc-macro2", "quote", "strsim 0.11.1", - "syn 2.0.66", + "syn 2.0.119", ] [[package]] @@ -2111,7 +2132,7 @@ checksum = "733cabb43482b1a1b53eee8583c2b9e8684d592215ea83efd305dd31bc2f0178" dependencies = [ "darling_core 0.20.9", "quote", - "syn 2.0.66", + "syn 2.0.119", ] [[package]] @@ -2162,6 +2183,12 @@ dependencies = [ "uuid", ] +[[package]] +name = "debugless-unwrap" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f400d0750c0c069e8493f2256cb4da6f604b6d2eeb69a0ca8863acde352f8400" + [[package]] name = "der" version = "0.7.9" @@ -2206,6 +2233,17 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "derive-getters" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74ef43543e701c01ad77d3a5922755c6a1d71b22d942cb8042be4994b380caff" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "derive-syn-parse" version = "0.1.5" @@ -2225,7 +2263,7 @@ checksum = "d65d7ce8132b7c0e54497a4d9a55a1c2a0912a0d786cf894472ba818fba45762" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.119", ] [[package]] @@ -2236,7 +2274,7 @@ checksum = "62d671cc41a825ebabc75757b62d3d168c577f9149b2d49ece1dad1f72119d25" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.119", ] [[package]] @@ -2338,7 +2376,7 @@ checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.119", ] [[package]] @@ -2384,12 +2422,21 @@ dependencies = [ "proc-macro2", "quote", "regex", - "syn 2.0.66", + "syn 2.0.119", "termcolor", "toml 0.8.14", "walkdir", ] +[[package]] +name = "document-features" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" +dependencies = [ + "litrs", +] + [[package]] name = "downcast" version = "0.11.0" @@ -2489,7 +2536,7 @@ version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871" dependencies = [ - "curve25519-dalek 4.1.2", + "curve25519-dalek 4.1.3", "ed25519 2.2.3", "rand_core 0.6.4", "serde", @@ -2518,7 +2565,7 @@ version = "4.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d9ce6874da5d4415896cd45ffbc4d1cfc0c4f9c079427bd870742c30f2f65a9" dependencies = [ - "curve25519-dalek 4.1.2", + "curve25519-dalek 4.1.3", "ed25519 2.2.3", "hashbrown 0.14.5", "hex", @@ -2553,6 +2600,18 @@ dependencies = [ "zeroize", ] +[[package]] +name = "embedded-io" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced" + +[[package]] +name = "embedded-io" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" + [[package]] name = "encode_unicode" version = "0.3.6" @@ -2589,7 +2648,7 @@ dependencies = [ "heck 0.4.1", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.119", ] [[package]] @@ -2609,7 +2668,7 @@ checksum = "de0d48a183585823424a4ce1aa132d174a6a81bd540895822eb4c8373a8e49e8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.119", ] [[package]] @@ -2727,7 +2786,7 @@ dependencies = [ "prettier-please", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.119", ] [[package]] @@ -2765,7 +2824,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e182f7dbc2ef73d9ef67351c5fbbea084729c48362d3ce9dd44c28e32e277fe5" dependencies = [ "libc", - "thiserror", + "thiserror 1.0.61", ] [[package]] @@ -2940,7 +2999,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8835f84f38484cc86f110a805655697908257fb9a7af005234060891557198e9" dependencies = [ "nonempty", - "thiserror", + "thiserror 1.0.61", ] [[package]] @@ -3020,7 +3079,7 @@ dependencies = [ "sp-storage 19.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.12.0)", "sp-trie 29.0.0", "sp-wasm-interface 20.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.12.0)", - "thiserror", + "thiserror 1.0.61", "thousands", ] @@ -3032,7 +3091,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.119", ] [[package]] @@ -3173,7 +3232,7 @@ dependencies = [ "proc-macro2", "quote", "sp-crypto-hashing 0.1.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.12.0)", - "syn 2.0.66", + "syn 2.0.119", ] [[package]] @@ -3185,7 +3244,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.119", ] [[package]] @@ -3195,7 +3254,7 @@ source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.12. dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.119", ] [[package]] @@ -3254,6 +3313,69 @@ dependencies = [ "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.12.0)", ] +[[package]] +name = "frost-core" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2619366c227233c0f817ae01156bd21b8cf74d2bd96cbe0889f4c2e266724e44" +dependencies = [ + "byteorder", + "const-crc32-nostd", + "debugless-unwrap", + "derive-getters", + "document-features", + "hex", + "itertools 0.14.0", + "postcard", + "rand_core 0.6.4", + "serde", + "serdect", + "thiserror 2.0.18", + "visibility", + "zeroize", +] + +[[package]] +name = "frost-ed25519" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f73eb5fa9311d33450c2320199ad1663b5af7a50061d9627d2e0dc776f0acb27" +dependencies = [ + "curve25519-dalek 4.1.3", + "document-features", + "frost-core", + "frost-rerandomized", + "rand_core 0.6.4", + "sha2 0.10.8", +] + +[[package]] +name = "frost-rerandomized" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c5eb1ea58c0250b7ce834337f7b19e0417686d14ffc7f626137dea9149762d4" +dependencies = [ + "derive-getters", + "document-features", + "frost-core", + "hex", + "rand_core 0.6.4", +] + +[[package]] +name = "frost-secp256k1" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f6974379aee791f2f9e0db47c37d9e4c77ea8a8233e488ae8949ce4c6864e96" +dependencies = [ + "document-features", + "frost-core", + "frost-rerandomized", + "k256", + "rand_core 0.6.4", + "sha2 0.10.8", +] + [[package]] name = "fs-err" version = "2.11.0" @@ -3359,7 +3481,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.119", ] [[package]] @@ -3504,33 +3626,9 @@ dependencies = [ "polyval", ] -[[package]] -name = "ghost-claims" -version = "0.2.4" -dependencies = [ - "frame-benchmarking", - "frame-support", - "frame-system", - "hex-literal", - "libsecp256k1", - "pallet-balances", - "pallet-ranked-collective", - "pallet-vesting", - "parity-scale-codec", - "rustc-hex", - "scale-info", - "serde", - "serde_derive", - "serde_json", - "sp-core 28.0.0", - "sp-io 30.0.0", - "sp-runtime 31.0.1", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.12.0)", -] - [[package]] name = "ghost-cli" -version = "0.8.5" +version = "0.9.0" dependencies = [ "cfg-if", "clap 4.5.4", @@ -3557,7 +3655,7 @@ dependencies = [ "sp-maybe-compressed-blob", "sp-runtime 31.0.1", "substrate-build-script-utils", - "thiserror", + "thiserror 1.0.61", ] [[package]] @@ -3584,9 +3682,100 @@ dependencies = [ "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.12.0)", ] +[[package]] +name = "ghost-exodus" +version = "0.0.0" +dependencies = [ + "chacha20poly1305", + "frame-benchmarking", + "frame-executive", + "frame-support", + "frame-system", + "frost-core", + "frost-ed25519", + "frost-secp256k1", + "ghost-helpers", + "ghost-networks", + "ghost-traits", + "hex", + "hkdf", + "k256", + "log", + "num-traits", + "pallet-balances", + "pallet-session", + "pallet-staking", + "pallet-staking-reward-curve", + "parity-scale-codec", + "parking_lot 0.12.3", + "rand 0.8.5", + "rand_chacha 0.3.1", + "scale-info", + "sha2 0.10.8", + "sp-application-crypto 30.0.0", + "sp-arithmetic 23.0.0", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-keystore 0.34.0", + "sp-runtime 31.0.1", + "sp-staking", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.12.0)", + "strum 0.28.0", +] + +[[package]] +name = "ghost-exodus-runtime-api" +version = "0.0.0" +dependencies = [ + "parity-scale-codec", + "sp-api", +] + +[[package]] +name = "ghost-governance" +version = "0.3.0" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "ghost-helpers", + "ghost-networks", + "ghost-traits", + "hex", + "libsecp256k1", + "pallet-balances", + "parity-scale-codec", + "rustc-hex", + "scale-info", + "serde", + "serde_derive", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.12.0)", +] + +[[package]] +name = "ghost-helpers" +version = "0.0.5" +dependencies = [ + "frame-support", + "ghost-traits", + "num-traits", + "parity-scale-codec", + "scale-info", + "serde", + "sp-arithmetic 23.0.0", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.12.0)", + "strum 0.28.0", +] + [[package]] name = "ghost-machine-primitives" -version = "0.8.5" +version = "0.9.0" dependencies = [ "lazy_static", "sc-sysinfo", @@ -3595,7 +3784,7 @@ dependencies = [ [[package]] name = "ghost-metrics" -version = "0.8.5" +version = "0.9.0" dependencies = [ "assert_cmd", "bs58 0.5.1", @@ -3643,20 +3832,22 @@ dependencies = [ "sp-runtime 31.0.1", "sp-storage 19.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.12.0)", "subxt", - "thiserror", + "thiserror 1.0.61", "tokio", "tracing-subscriber 0.3.18", ] [[package]] name = "ghost-networks" -version = "0.1.26" +version = "0.2.7" dependencies = [ "frame-benchmarking", "frame-support", "frame-system", "ghost-core-primitives", + "ghost-helpers", "ghost-traits", + "hex", "log", "num-traits", "pallet-balances", @@ -3671,7 +3862,7 @@ dependencies = [ [[package]] name = "ghost-node" -version = "0.8.5" +version = "0.9.0" dependencies = [ "assert_cmd", "color-eyre", @@ -3685,6 +3876,58 @@ dependencies = [ "tokio", ] +[[package]] +name = "ghost-nomination-pools" +version = "25.0.0" +dependencies = [ + "frame-support", + "frame-system", + "log", + "pallet-balances", + "parity-scale-codec", + "scale-info", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", + "sp-staking", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.12.0)", + "sp-tracing 16.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.12.0)", +] + +[[package]] +name = "ghost-nomination-pools-benchmarking" +version = "26.0.0" +dependencies = [ + "frame-benchmarking", + "frame-election-provider-support", + "frame-support", + "frame-system", + "ghost-nomination-pools", + "pallet-bags-list", + "pallet-balances", + "pallet-staking", + "pallet-staking-reward-curve", + "pallet-timestamp", + "parity-scale-codec", + "scale-info", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", + "sp-runtime-interface 24.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.12.0)", + "sp-staking", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.12.0)", +] + +[[package]] +name = "ghost-nomination-pools-runtime-api" +version = "23.0.0" +dependencies = [ + "ghost-nomination-pools", + "parity-scale-codec", + "sp-api", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.12.0)", +] + [[package]] name = "ghost-remote-ext-tests-bags-list" version = "1.0.1" @@ -3702,7 +3945,7 @@ dependencies = [ [[package]] name = "ghost-rpc" -version = "0.8.5" +version = "0.9.0" dependencies = [ "ghost-core-primitives", "jsonrpsee", @@ -3729,32 +3972,9 @@ dependencies = [ "substrate-state-trie-migration-rpc", ] -[[package]] -name = "ghost-runtime-common" -version = "0.4.3" -dependencies = [ - "frame-support", - "frame-system", - "ghost-core-primitives", - "pallet-authorship", - "pallet-balances", - "pallet-election-provider-multi-phase", - "pallet-staking", - "pallet-staking-reward-fn", - "pallet-timestamp", - "pallet-transaction-payment", - "pallet-treasury", - "sp-core 28.0.0", - "sp-io 30.0.0", - "sp-runtime 31.0.1", - "sp-staking", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.12.0)", - "static_assertions", -] - [[package]] name = "ghost-service" -version = "0.8.5" +version = "0.9.0" dependencies = [ "assert_matches", "async-trait", @@ -3769,10 +3989,11 @@ dependencies = [ "frame-system-rpc-runtime-api", "futures", "ghost-core-primitives", + "ghost-exodus", "ghost-machine-primitives", "ghost-networks", "ghost-rpc", - "ghost-slow-clap", + "ghost-weaver", "hex-literal", "is_executable", "kvdb", @@ -3833,33 +4054,7 @@ dependencies = [ "sp-weights 27.0.0", "substrate-prometheus-endpoint", "tempfile", - "thiserror", -] - -[[package]] -name = "ghost-slow-clap" -version = "0.4.28" -dependencies = [ - "frame-benchmarking", - "frame-support", - "frame-system", - "ghost-networks", - "ghost-traits", - "log", - "pallet-balances", - "pallet-session", - "pallet-staking", - "pallet-staking-reward-curve", - "parity-scale-codec", - "scale-info", - "serde", - "serde_json", - "sp-application-crypto 30.0.0", - "sp-core 28.0.0", - "sp-io 30.0.0", - "sp-runtime 31.0.1", - "sp-staking", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.12.0)", + "thiserror 1.0.61", ] [[package]] @@ -3890,9 +4085,14 @@ dependencies = [ [[package]] name = "ghost-traits" -version = "0.3.32" +version = "0.4.2" dependencies = [ "frame-support", + "frost-core", + "num-traits", + "rand_chacha 0.3.1", + "sp-arithmetic 23.0.0", + "sp-core 28.0.0", "sp-runtime 31.0.1", "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.12.0)", ] @@ -3907,6 +4107,34 @@ dependencies = [ "sp-io 30.0.0", ] +[[package]] +name = "ghost-weaver" +version = "0.0.0" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "ghost-helpers", + "ghost-networks", + "ghost-traits", + "hex", + "log", + "num-traits", + "pallet-balances", + "parity-scale-codec", + "parking_lot 0.12.3", + "scale-info", + "serde", + "serde_json", + "sp-application-crypto 30.0.0", + "sp-arithmetic 23.0.0", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-keystore 0.34.0", + "sp-runtime 31.0.1", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.12.0)", +] + [[package]] name = "ghostkey" version = "0.3.16" @@ -4004,7 +4232,7 @@ dependencies = [ "pest_derive", "serde", "serde_json", - "thiserror", + "thiserror 1.0.61", ] [[package]] @@ -4022,6 +4250,15 @@ dependencies = [ "crunchy", ] +[[package]] +name = "hash32" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67" +dependencies = [ + "byteorder", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -4060,6 +4297,20 @@ dependencies = [ "hashbrown 0.14.5", ] +[[package]] +name = "heapless" +version = "0.7.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f" +dependencies = [ + "atomic-polyfill", + "hash32", + "rustc_version", + "serde", + "spin 0.9.8", + "stable_deref_trait", +] + [[package]] name = "heck" version = "0.4.1" @@ -4548,6 +4799,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.11" @@ -4608,7 +4868,7 @@ dependencies = [ "rustls-native-certs 0.7.0", "rustls-pki-types", "soketto", - "thiserror", + "thiserror 1.0.61", "tokio", "tokio-rustls 0.25.0", "tokio-util", @@ -4635,7 +4895,7 @@ dependencies = [ "rustc-hash", "serde", "serde_json", - "thiserror", + "thiserror 1.0.61", "tokio", "tokio-stream", "tracing", @@ -4654,7 +4914,7 @@ dependencies = [ "jsonrpsee-types", "serde", "serde_json", - "thiserror", + "thiserror 1.0.61", "tokio", "tower", "tracing", @@ -4671,7 +4931,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.119", ] [[package]] @@ -4690,7 +4950,7 @@ dependencies = [ "serde", "serde_json", "soketto", - "thiserror", + "thiserror 1.0.61", "tokio", "tokio-stream", "tokio-util", @@ -4708,7 +4968,7 @@ dependencies = [ "beef", "serde", "serde_json", - "thiserror", + "thiserror 1.0.61", ] [[package]] @@ -4920,7 +5180,7 @@ dependencies = [ "rand 0.8.5", "rw-stream-sink", "smallvec", - "thiserror", + "thiserror 1.0.61", "unsigned-varint", "void", ] @@ -4957,7 +5217,7 @@ dependencies = [ "quick-protobuf", "quick-protobuf-codec", "smallvec", - "thiserror", + "thiserror 1.0.61", "void", ] @@ -4975,7 +5235,7 @@ dependencies = [ "quick-protobuf", "rand 0.8.5", "sha2 0.10.8", - "thiserror", + "thiserror 1.0.61", "zeroize", ] @@ -5001,7 +5261,7 @@ dependencies = [ "rand 0.8.5", "sha2 0.10.8", "smallvec", - "thiserror", + "thiserror 1.0.61", "uint", "unsigned-varint", "void", @@ -5060,7 +5320,7 @@ dependencies = [ "sha2 0.10.8", "snow", "static_assertions", - "thiserror", + "thiserror 1.0.61", "x25519-dalek 1.1.1", "zeroize", ] @@ -5100,7 +5360,7 @@ dependencies = [ "quinn-proto", "rand 0.8.5", "rustls 0.20.9", - "thiserror", + "thiserror 1.0.61", "tokio", ] @@ -5181,7 +5441,7 @@ dependencies = [ "rcgen", "ring 0.16.20", "rustls 0.20.9", - "thiserror", + "thiserror 1.0.61", "webpki", "x509-parser 0.14.0", "yasna", @@ -5229,7 +5489,7 @@ dependencies = [ "futures", "libp2p-core", "log", - "thiserror", + "thiserror 1.0.61", "yamux", ] @@ -5239,7 +5499,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.13.1", "libc", ] @@ -5411,7 +5671,7 @@ dependencies = [ "socket2 0.5.7", "static_assertions", "str0m", - "thiserror", + "thiserror 1.0.61", "tokio", "tokio-stream", "tokio-tungstenite", @@ -5428,6 +5688,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "litrs" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" + [[package]] name = "lock_api" version = "0.4.12" @@ -5509,7 +5775,7 @@ dependencies = [ "macro_magic_core", "macro_magic_macros", "quote", - "syn 2.0.66", + "syn 2.0.119", ] [[package]] @@ -5523,7 +5789,7 @@ dependencies = [ "macro_magic_core_macros", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.119", ] [[package]] @@ -5534,7 +5800,7 @@ checksum = "9ea73aa640dc01d62a590d48c0c3521ed739d53b27f919b25c3551e233481654" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.119", ] [[package]] @@ -5545,7 +5811,7 @@ checksum = "ef9d79ae96aaba821963320eb2b6e34d17df1e5a83d8a1985c29cc5be59577b3" dependencies = [ "macro_magic_core", "quote", - "syn 2.0.66", + "syn 2.0.119", ] [[package]] @@ -5700,7 +5966,7 @@ dependencies = [ "bitflags 1.3.2", "blake2 0.10.6", "c2-chacha", - "curve25519-dalek 4.1.2", + "curve25519-dalek 4.1.3", "either", "hashlink", "lioness", @@ -5710,7 +5976,7 @@ dependencies = [ "rand_chacha 0.3.1", "rand_distr", "subtle 2.5.0", - "thiserror", + "thiserror 1.0.61", "zeroize", ] @@ -5870,7 +6136,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.119", "synstructure 0.13.1", ] @@ -5978,7 +6244,7 @@ dependencies = [ "anyhow", "byteorder", "paste", - "thiserror", + "thiserror 1.0.61", ] [[package]] @@ -5992,7 +6258,7 @@ dependencies = [ "log", "netlink-packet-core", "netlink-sys", - "thiserror", + "thiserror 1.0.61", "tokio", ] @@ -6017,7 +6283,7 @@ checksum = "a4a43439bf756eed340bdf8feba761e2d50c7d47175d87545cd5cbe4a137c4d1" dependencies = [ "cc", "libc", - "thiserror", + "thiserror 1.0.61", "winapi", ] @@ -6049,7 +6315,7 @@ version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.13.1", "cfg-if", "cfg_aliases 0.1.1", "libc", @@ -6252,7 +6518,7 @@ version = "0.10.64" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.13.1", "cfg-if", "foreign-types", "libc", @@ -6269,7 +6535,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.119", ] [[package]] @@ -6324,27 +6590,6 @@ version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" -[[package]] -name = "pallet-alliance" -version = "27.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.12.0#b4016902ac7fc1d885eae236a2f71ddc58abc2f9" -dependencies = [ - "array-bytes", - "frame-benchmarking", - "frame-support", - "frame-system", - "log", - "pallet-collective", - "pallet-identity", - "parity-scale-codec", - "scale-info", - "sp-core 28.0.0", - "sp-crypto-hashing 0.1.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.12.0)", - "sp-io 30.0.0", - "sp-runtime 31.0.1", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.12.0)", -] - [[package]] name = "pallet-authority-discovery" version = "28.0.0" @@ -6456,79 +6701,6 @@ dependencies = [ "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.12.0)", ] -[[package]] -name = "pallet-bounties" -version = "27.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.12.0#b4016902ac7fc1d885eae236a2f71ddc58abc2f9" -dependencies = [ - "frame-benchmarking", - "frame-support", - "frame-system", - "log", - "pallet-treasury", - "parity-scale-codec", - "scale-info", - "sp-core 28.0.0", - "sp-io 30.0.0", - "sp-runtime 31.0.1", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.12.0)", -] - -[[package]] -name = "pallet-child-bounties" -version = "27.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.12.0#b4016902ac7fc1d885eae236a2f71ddc58abc2f9" -dependencies = [ - "frame-benchmarking", - "frame-support", - "frame-system", - "log", - "pallet-bounties", - "pallet-treasury", - "parity-scale-codec", - "scale-info", - "sp-core 28.0.0", - "sp-io 30.0.0", - "sp-runtime 31.0.1", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.12.0)", -] - -[[package]] -name = "pallet-collective" -version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.12.0#b4016902ac7fc1d885eae236a2f71ddc58abc2f9" -dependencies = [ - "frame-benchmarking", - "frame-support", - "frame-system", - "log", - "parity-scale-codec", - "scale-info", - "sp-core 28.0.0", - "sp-io 30.0.0", - "sp-runtime 31.0.1", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.12.0)", -] - -[[package]] -name = "pallet-core-fellowship" -version = "12.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.12.0#b4016902ac7fc1d885eae236a2f71ddc58abc2f9" -dependencies = [ - "frame-benchmarking", - "frame-support", - "frame-system", - "log", - "pallet-ranked-collective", - "parity-scale-codec", - "scale-info", - "sp-arithmetic 23.0.0", - "sp-core 28.0.0", - "sp-io 30.0.0", - "sp-runtime 31.0.1", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.12.0)", -] - [[package]] name = "pallet-election-provider-multi-phase" version = "27.0.0" @@ -6566,25 +6738,6 @@ dependencies = [ "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.12.0)", ] -[[package]] -name = "pallet-fast-unstake" -version = "27.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.12.0#b4016902ac7fc1d885eae236a2f71ddc58abc2f9" -dependencies = [ - "docify", - "frame-benchmarking", - "frame-election-provider-support", - "frame-support", - "frame-system", - "log", - "parity-scale-codec", - "scale-info", - "sp-io 30.0.0", - "sp-runtime 31.0.1", - "sp-staking", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.12.0)", -] - [[package]] name = "pallet-grandpa" version = "28.0.0" @@ -6645,89 +6798,6 @@ dependencies = [ "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.12.0)", ] -[[package]] -name = "pallet-indices" -version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.12.0#b4016902ac7fc1d885eae236a2f71ddc58abc2f9" -dependencies = [ - "frame-benchmarking", - "frame-support", - "frame-system", - "parity-scale-codec", - "scale-info", - "sp-core 28.0.0", - "sp-io 30.0.0", - "sp-keyring", - "sp-runtime 31.0.1", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.12.0)", -] - -[[package]] -name = "pallet-multisig" -version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.12.0#b4016902ac7fc1d885eae236a2f71ddc58abc2f9" -dependencies = [ - "frame-benchmarking", - "frame-support", - "frame-system", - "log", - "parity-scale-codec", - "scale-info", - "sp-io 30.0.0", - "sp-runtime 31.0.1", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.12.0)", -] - -[[package]] -name = "pallet-nomination-pools" -version = "25.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.12.0#b4016902ac7fc1d885eae236a2f71ddc58abc2f9" -dependencies = [ - "frame-support", - "frame-system", - "log", - "pallet-balances", - "parity-scale-codec", - "scale-info", - "sp-core 28.0.0", - "sp-io 30.0.0", - "sp-runtime 31.0.1", - "sp-staking", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.12.0)", - "sp-tracing 16.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.12.0)", -] - -[[package]] -name = "pallet-nomination-pools-benchmarking" -version = "26.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.12.0#b4016902ac7fc1d885eae236a2f71ddc58abc2f9" -dependencies = [ - "frame-benchmarking", - "frame-election-provider-support", - "frame-support", - "frame-system", - "pallet-bags-list", - "pallet-nomination-pools", - "pallet-staking", - "parity-scale-codec", - "scale-info", - "sp-runtime 31.0.1", - "sp-runtime-interface 24.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.12.0)", - "sp-staking", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.12.0)", -] - -[[package]] -name = "pallet-nomination-pools-runtime-api" -version = "23.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.12.0#b4016902ac7fc1d885eae236a2f71ddc58abc2f9" -dependencies = [ - "pallet-nomination-pools", - "parity-scale-codec", - "sp-api", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.12.0)", -] - [[package]] name = "pallet-offences" version = "27.0.0" @@ -6801,63 +6871,6 @@ dependencies = [ "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.12.0)", ] -[[package]] -name = "pallet-ranked-collective" -version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.12.0#b4016902ac7fc1d885eae236a2f71ddc58abc2f9" -dependencies = [ - "frame-benchmarking", - "frame-support", - "frame-system", - "impl-trait-for-tuples", - "log", - "parity-scale-codec", - "scale-info", - "sp-arithmetic 23.0.0", - "sp-core 28.0.0", - "sp-io 30.0.0", - "sp-runtime 31.0.1", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.12.0)", -] - -[[package]] -name = "pallet-referenda" -version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.12.0#b4016902ac7fc1d885eae236a2f71ddc58abc2f9" -dependencies = [ - "assert_matches", - "frame-benchmarking", - "frame-support", - "frame-system", - "log", - "parity-scale-codec", - "scale-info", - "serde", - "sp-arithmetic 23.0.0", - "sp-io 30.0.0", - "sp-runtime 31.0.1", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.12.0)", -] - -[[package]] -name = "pallet-salary" -version = "13.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.12.0#b4016902ac7fc1d885eae236a2f71ddc58abc2f9" -dependencies = [ - "frame-benchmarking", - "frame-support", - "frame-system", - "log", - "pallet-ranked-collective", - "parity-scale-codec", - "scale-info", - "sp-arithmetic 23.0.0", - "sp-core 28.0.0", - "sp-io 30.0.0", - "sp-runtime 31.0.1", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.12.0)", -] - [[package]] name = "pallet-scheduler" version = "29.0.0" @@ -6946,7 +6959,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.119", ] [[package]] @@ -7067,21 +7080,6 @@ dependencies = [ "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.12.0)", ] -[[package]] -name = "pallet-vesting" -version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.12.0#b4016902ac7fc1d885eae236a2f71ddc58abc2f9" -dependencies = [ - "frame-benchmarking", - "frame-support", - "frame-system", - "log", - "parity-scale-codec", - "scale-info", - "sp-runtime 31.0.1", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.12.0)", -] - [[package]] name = "pallet-whitelist" version = "27.0.0" @@ -7285,7 +7283,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "560131c633294438da9f7c4b08189194b20946c8274c6b9e38881a7874dc8ee8" dependencies = [ "memchr", - "thiserror", + "thiserror 1.0.61", "ucd-trie", ] @@ -7309,7 +7307,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.119", ] [[package]] @@ -7350,7 +7348,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.119", ] [[package]] @@ -7398,12 +7396,6 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" -[[package]] -name = "platforms" -version = "3.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db23d408679286588f4d4644f965003d056e3dd5abcaaa938116871d7ce2fee7" - [[package]] name = "polkavm" version = "0.9.3" @@ -7468,7 +7460,7 @@ dependencies = [ "polkavm-common 0.8.0", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.119", ] [[package]] @@ -7480,7 +7472,7 @@ dependencies = [ "polkavm-common 0.9.0", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.119", ] [[package]] @@ -7490,7 +7482,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "15e85319a0d5129dc9f021c62607e0804f5fb777a05cdda44d750ac0732def66" dependencies = [ "polkavm-derive-impl 0.8.0", - "syn 2.0.66", + "syn 2.0.119", ] [[package]] @@ -7500,7 +7492,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ba81f7b5faac81e528eb6158a6f3c9e0bb1008e0ffa19653bc8dea925ecb429" dependencies = [ "polkavm-derive-impl 0.9.0", - "syn 2.0.66", + "syn 2.0.119", ] [[package]] @@ -7568,6 +7560,19 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0" +[[package]] +name = "postcard" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24" +dependencies = [ + "cobs", + "embedded-io 0.4.0", + "embedded-io 0.6.1", + "heapless", + "serde", +] + [[package]] name = "powerfmt" version = "0.2.0" @@ -7591,7 +7596,7 @@ dependencies = [ "smallvec", "symbolic-demangle", "tempfile", - "thiserror", + "thiserror 1.0.61", ] [[package]] @@ -7648,7 +7653,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22020dfcf177fcc7bf5deaf7440af371400c67c0de14c399938d8ed4fb4645d3" dependencies = [ "proc-macro2", - "syn 2.0.66", + "syn 2.0.119", ] [[package]] @@ -7668,7 +7673,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f12335488a2f3b0a83b14edad48dca9879ce89b2edd10e80237e4e852dd645e" dependencies = [ "proc-macro2", - "syn 2.0.66", + "syn 2.0.119", ] [[package]] @@ -7696,7 +7701,7 @@ dependencies = [ "futures", "futures-timer", "nanorand", - "thiserror", + "thiserror 1.0.61", "tracing", ] @@ -7706,7 +7711,7 @@ version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e17d47ce914bf4de440332250b0edd23ce48c005f59fab39d3335866b114f11a" dependencies = [ - "thiserror", + "thiserror 1.0.61", "toml 0.5.11", ] @@ -7760,14 +7765,14 @@ checksum = "834da187cfe638ae8abb0203f0b33e5ccdb02a28e7199f2f47b3e2754f50edca" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.119", ] [[package]] name = "proc-macro2" -version = "1.0.85" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" dependencies = [ "unicode-ident", ] @@ -7784,7 +7789,7 @@ dependencies = [ "memchr", "parking_lot 0.12.3", "protobuf", - "thiserror", + "thiserror 1.0.61", ] [[package]] @@ -7807,7 +7812,7 @@ checksum = "440f724eba9f6996b75d63681b0a92b06947f1457076d503a4d2e2c8f56442b8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.119", ] [[package]] @@ -7881,7 +7886,7 @@ dependencies = [ "prost 0.12.6", "prost-types 0.12.6", "regex", - "syn 2.0.66", + "syn 2.0.119", "tempfile", ] @@ -7908,7 +7913,7 @@ dependencies = [ "itertools 0.12.1", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.119", ] [[package]] @@ -7957,7 +7962,7 @@ dependencies = [ "names", "prost 0.11.9", "reqwest", - "thiserror", + "thiserror 1.0.61", "url", "winapi", ] @@ -7971,7 +7976,7 @@ dependencies = [ "log", "pprof", "pyroscope", - "thiserror", + "thiserror 1.0.61", ] [[package]] @@ -8013,7 +8018,7 @@ dependencies = [ "asynchronous-codec", "bytes", "quick-protobuf", - "thiserror", + "thiserror 1.0.61", "unsigned-varint", ] @@ -8040,7 +8045,7 @@ dependencies = [ "quinn-udp", "rustc-hash", "rustls 0.20.9", - "thiserror", + "thiserror 1.0.61", "tokio", "tracing", "webpki", @@ -8058,7 +8063,7 @@ dependencies = [ "rustc-hash", "rustls 0.20.9", "slab", - "thiserror", + "thiserror 1.0.61", "tinyvec", "tracing", "webpki", @@ -8188,7 +8193,7 @@ version = "11.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e29830cbb1290e404f24c73af91c5d8d631ce7e128691e9477556b540cd01ecd" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.13.1", ] [[package]] @@ -8240,7 +8245,7 @@ dependencies = [ "futures", "jsonrpsee", "serde_json", - "thiserror", + "thiserror 1.0.61", "tokio", "tracing", ] @@ -8269,7 +8274,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.13.1", ] [[package]] @@ -8280,7 +8285,7 @@ checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891" dependencies = [ "getrandom 0.2.15", "libredox", - "thiserror", + "thiserror 1.0.61", ] [[package]] @@ -8300,7 +8305,7 @@ checksum = "bcc303e793d3734489387d205e9b186fac9c6cfacedd98cbb2e8a5943595f3e6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.119", ] [[package]] @@ -8532,7 +8537,7 @@ dependencies = [ "netlink-packet-route", "netlink-proto", "nix 0.24.3", - "thiserror", + "thiserror 1.0.61", "tokio", ] @@ -8546,6 +8551,31 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "runtime-common" +version = "0.4.5" +dependencies = [ + "frame-support", + "frame-system", + "ghost-core-primitives", + "pallet-authorship", + "pallet-balances", + "pallet-election-provider-multi-phase", + "pallet-staking", + "pallet-staking-reward-fn", + "pallet-timestamp", + "pallet-transaction-payment", + "pallet-treasury", + "parity-scale-codec", + "scale-info", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", + "sp-staking", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.12.0)", + "static_assertions", +] + [[package]] name = "rustc-demangle" version = "0.1.24" @@ -8602,7 +8632,7 @@ version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.13.1", "errno", "libc", "linux-raw-sys 0.4.14", @@ -8778,7 +8808,7 @@ dependencies = [ "log", "sp-core 28.0.0", "sp-wasm-interface 20.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.12.0)", - "thiserror", + "thiserror 1.0.61", ] [[package]] @@ -8809,7 +8839,7 @@ dependencies = [ "sp-keystore 0.34.0", "sp-runtime 31.0.1", "substrate-prometheus-endpoint", - "thiserror", + "thiserror 1.0.61", ] [[package]] @@ -8884,7 +8914,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.119", ] [[package]] @@ -8924,7 +8954,7 @@ dependencies = [ "sp-panic-handler 13.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.12.0)", "sp-runtime 31.0.1", "sp-version", - "thiserror", + "thiserror 1.0.61", "tokio", ] @@ -9003,7 +9033,7 @@ dependencies = [ "sp-runtime 31.0.1", "sp-state-machine 0.35.0", "substrate-prometheus-endpoint", - "thiserror", + "thiserror 1.0.61", ] [[package]] @@ -9039,7 +9069,7 @@ dependencies = [ "sp-keystore 0.34.0", "sp-runtime 31.0.1", "substrate-prometheus-endpoint", - "thiserror", + "thiserror 1.0.61", ] [[package]] @@ -9061,7 +9091,7 @@ dependencies = [ "sp-core 28.0.0", "sp-keystore 0.34.0", "sp-runtime 31.0.1", - "thiserror", + "thiserror 1.0.61", ] [[package]] @@ -9118,7 +9148,7 @@ dependencies = [ "sp-keystore 0.34.0", "sp-runtime 31.0.1", "substrate-prometheus-endpoint", - "thiserror", + "thiserror 1.0.61", ] [[package]] @@ -9138,7 +9168,7 @@ dependencies = [ "sp-blockchain", "sp-core 28.0.0", "sp-runtime 31.0.1", - "thiserror", + "thiserror 1.0.61", ] [[package]] @@ -9196,7 +9226,7 @@ dependencies = [ "sc-allocator", "sp-maybe-compressed-blob", "sp-wasm-interface 20.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.12.0)", - "thiserror", + "thiserror 1.0.61", "wasm-instrument", ] @@ -9257,7 +9287,7 @@ dependencies = [ "sp-application-crypto 30.0.0", "sp-core 28.0.0", "sp-keystore 0.34.0", - "thiserror", + "thiserror 1.0.61", ] [[package]] @@ -9286,7 +9316,7 @@ dependencies = [ "sp-keystore 0.34.0", "sp-mixnet", "sp-runtime 31.0.1", - "thiserror", + "thiserror 1.0.61", ] [[package]] @@ -9331,7 +9361,7 @@ dependencies = [ "sp-core 28.0.0", "sp-runtime 31.0.1", "substrate-prometheus-endpoint", - "thiserror", + "thiserror 1.0.61", "tokio", "tokio-stream", "unsigned-varint", @@ -9396,7 +9426,7 @@ dependencies = [ "sp-blockchain", "sp-core 28.0.0", "sp-runtime 31.0.1", - "thiserror", + "thiserror 1.0.61", ] [[package]] @@ -9431,7 +9461,7 @@ dependencies = [ "sp-core 28.0.0", "sp-runtime 31.0.1", "substrate-prometheus-endpoint", - "thiserror", + "thiserror 1.0.61", "tokio", "tokio-stream", ] @@ -9467,7 +9497,7 @@ dependencies = [ "multiaddr", "multihash 0.17.0", "rand 0.8.5", - "thiserror", + "thiserror 1.0.61", ] [[package]] @@ -9563,7 +9593,7 @@ dependencies = [ "sp-rpc", "sp-runtime 31.0.1", "sp-version", - "thiserror", + "thiserror 1.0.61", ] [[package]] @@ -9613,7 +9643,7 @@ dependencies = [ "sp-rpc", "sp-runtime 31.0.1", "sp-version", - "thiserror", + "thiserror 1.0.61", "tokio", "tokio-stream", ] @@ -9676,7 +9706,7 @@ dependencies = [ "static_init", "substrate-prometheus-endpoint", "tempfile", - "thiserror", + "thiserror 1.0.61", "tokio", "tracing", "tracing-futures", @@ -9702,7 +9732,7 @@ dependencies = [ "fs4", "log", "sp-core 28.0.0", - "thiserror", + "thiserror 1.0.61", "tokio", ] @@ -9722,7 +9752,7 @@ dependencies = [ "serde_json", "sp-blockchain", "sp-runtime 31.0.1", - "thiserror", + "thiserror 1.0.61", ] [[package]] @@ -9762,7 +9792,7 @@ dependencies = [ "sc-utils", "serde", "serde_json", - "thiserror", + "thiserror 1.0.61", "wasm-timer", ] @@ -9790,7 +9820,7 @@ dependencies = [ "sp-rpc", "sp-runtime 31.0.1", "sp-tracing 16.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.12.0)", - "thiserror", + "thiserror 1.0.61", "tracing", "tracing-log 0.2.0", "tracing-subscriber 0.3.18", @@ -9804,7 +9834,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.119", ] [[package]] @@ -9831,7 +9861,7 @@ dependencies = [ "sp-tracing 16.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.12.0)", "sp-transaction-pool", "substrate-prometheus-endpoint", - "thiserror", + "thiserror 1.0.61", ] [[package]] @@ -9847,7 +9877,7 @@ dependencies = [ "sp-blockchain", "sp-core 28.0.0", "sp-runtime 31.0.1", - "thiserror", + "thiserror 1.0.61", ] [[package]] @@ -9977,8 +10007,8 @@ dependencies = [ "proc-macro2", "quote", "scale-info", - "syn 2.0.66", - "thiserror", + "syn 2.0.119", + "thiserror 1.0.61", ] [[package]] @@ -10031,7 +10061,7 @@ dependencies = [ "aead", "arrayref", "arrayvec 0.7.4", - "curve25519-dalek 4.1.2", + "curve25519-dalek 4.1.3", "getrandom_or_panic", "merlin", "rand_core 0.6.4", @@ -10075,7 +10105,7 @@ dependencies = [ "log", "rand 0.8.5", "slab", - "thiserror", + "thiserror 1.0.61", ] [[package]] @@ -10126,7 +10156,7 @@ version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c627723fd09706bacdb5cf41499e95098555af3c3c29d014dc3c458ef6be11c0" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.13.1", "core-foundation", "core-foundation-sys", "libc", @@ -10199,7 +10229,7 @@ checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.119", ] [[package]] @@ -10266,7 +10296,7 @@ checksum = "91d129178576168c589c9ec973feedf7d3126c01ac2bf08795109aa35b69fb8f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.119", ] [[package]] @@ -10407,7 +10437,7 @@ version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cae9a3fcdadafb6d97f4c0e007e4247b114ee0f119f650c3cbf3a8b3a1479694" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.13.1", ] [[package]] @@ -10572,7 +10602,7 @@ dependencies = [ "aes-gcm", "blake2 0.10.6", "chacha20poly1305", - "curve25519-dalek 4.1.2", + "curve25519-dalek 4.1.3", "rand_core 0.6.4", "ring 0.17.8", "rustc_version", @@ -10636,7 +10666,7 @@ dependencies = [ "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.12.0)", "sp-trie 29.0.0", "sp-version", - "thiserror", + "thiserror 1.0.61", ] [[package]] @@ -10650,7 +10680,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.119", ] [[package]] @@ -10765,7 +10795,7 @@ dependencies = [ "sp-database", "sp-runtime 31.0.1", "sp-state-machine 0.35.0", - "thiserror", + "thiserror 1.0.61", ] [[package]] @@ -10780,7 +10810,7 @@ dependencies = [ "sp-inherents", "sp-runtime 31.0.1", "sp-state-machine 0.35.0", - "thiserror", + "thiserror 1.0.61", ] [[package]] @@ -10870,7 +10900,7 @@ dependencies = [ "sp-storage 19.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.12.0)", "ss58-registry", "substrate-bip39 0.4.7", - "thiserror", + "thiserror 1.0.61", "tracing", "w3f-bls", "zeroize", @@ -10917,7 +10947,7 @@ dependencies = [ "sp-storage 20.0.0", "ss58-registry", "substrate-bip39 0.5.0", - "thiserror", + "thiserror 1.0.61", "tracing", "w3f-bls", "zeroize", @@ -10977,7 +11007,7 @@ source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.12. dependencies = [ "quote", "sp-crypto-hashing 0.1.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.12.0)", - "syn 2.0.66", + "syn 2.0.119", ] [[package]] @@ -10997,7 +11027,7 @@ checksum = "48d09fa0a5f7299fb81ee25ae3853d26200f7a348148aed6de76be905c007dbe" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.119", ] [[package]] @@ -11007,7 +11037,7 @@ source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.12. dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.119", ] [[package]] @@ -11017,7 +11047,7 @@ source = "git+https://github.com/paritytech/polkadot-sdk#7084463a49f2359dc2f378f dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.119", ] [[package]] @@ -11074,7 +11104,7 @@ dependencies = [ "parity-scale-codec", "scale-info", "sp-runtime 31.0.1", - "thiserror", + "thiserror 1.0.61", ] [[package]] @@ -11168,7 +11198,7 @@ name = "sp-maybe-compressed-blob" version = "11.0.0" source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.12.0#b4016902ac7fc1d885eae236a2f71ddc58abc2f9" dependencies = [ - "thiserror", + "thiserror 1.0.61", "zstd 0.12.4", ] @@ -11365,7 +11395,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.119", ] [[package]] @@ -11378,7 +11408,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.119", ] [[package]] @@ -11392,7 +11422,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.119", ] [[package]] @@ -11437,7 +11467,7 @@ dependencies = [ "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.12.0)", "sp-panic-handler 13.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.12.0)", "sp-trie 29.0.0", - "thiserror", + "thiserror 1.0.61", "tracing", "trie-db 0.29.1", ] @@ -11459,7 +11489,7 @@ dependencies = [ "sp-panic-handler 13.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "sp-std 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "sp-trie 32.0.0", - "thiserror", + "thiserror 1.0.61", "tracing", "trie-db 0.28.0", ] @@ -11470,7 +11500,7 @@ version = "10.0.0" source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.12.0#b4016902ac7fc1d885eae236a2f71ddc58abc2f9" dependencies = [ "aes-gcm", - "curve25519-dalek 4.1.2", + "curve25519-dalek 4.1.3", "ed25519-dalek 2.1.1", "hkdf", "parity-scale-codec", @@ -11484,7 +11514,7 @@ dependencies = [ "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.12.0)", "sp-runtime 31.0.1", "sp-runtime-interface 24.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.12.0)", - "thiserror", + "thiserror 1.0.61", "x25519-dalek 2.0.1", ] @@ -11551,7 +11581,7 @@ dependencies = [ "parity-scale-codec", "sp-inherents", "sp-runtime 31.0.1", - "thiserror", + "thiserror 1.0.61", ] [[package]] @@ -11629,7 +11659,7 @@ dependencies = [ "schnellru", "sp-core 28.0.0", "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.12.0)", - "thiserror", + "thiserror 1.0.61", "tracing", "trie-db 0.29.1", "trie-root", @@ -11654,7 +11684,7 @@ dependencies = [ "sp-core 31.0.0", "sp-externalities 0.27.0", "sp-std 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "thiserror", + "thiserror 1.0.61", "tracing", "trie-db 0.28.0", "trie-root", @@ -11674,7 +11704,7 @@ dependencies = [ "sp-runtime 31.0.1", "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.12.0)", "sp-version-proc-macro", - "thiserror", + "thiserror 1.0.61", ] [[package]] @@ -11685,7 +11715,7 @@ dependencies = [ "parity-scale-codec", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.119", ] [[package]] @@ -11765,6 +11795,9 @@ name = "spin" version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] [[package]] name = "spinners" @@ -11867,7 +11900,7 @@ dependencies = [ "sctp-proto", "serde", "sha-1 0.10.1", - "thiserror", + "thiserror 1.0.61", "tracing", ] @@ -11914,6 +11947,15 @@ dependencies = [ "strum_macros 0.26.3", ] +[[package]] +name = "strum" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd" +dependencies = [ + "strum_macros 0.28.0", +] + [[package]] name = "strum_macros" version = "0.24.3" @@ -11937,7 +11979,19 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.66", + "syn 2.0.119", +] + +[[package]] +name = "strum_macros" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.119", ] [[package]] @@ -11997,7 +12051,7 @@ dependencies = [ "hyper", "log", "prometheus", - "thiserror", + "thiserror 1.0.61", "tokio", ] @@ -12092,7 +12146,7 @@ dependencies = [ "subxt-lightclient", "subxt-macro", "subxt-metadata", - "thiserror", + "thiserror 1.0.61", "tokio-util", "tracing", "url", @@ -12114,8 +12168,8 @@ dependencies = [ "scale-info", "scale-typegen", "subxt-metadata", - "syn 2.0.66", - "thiserror", + "syn 2.0.119", + "thiserror 1.0.61", "tokio", ] @@ -12159,7 +12213,7 @@ dependencies = [ "serde", "serde_json", "smoldot-light", - "thiserror", + "thiserror 1.0.61", "tokio", "tokio-stream", "tracing", @@ -12177,7 +12231,7 @@ dependencies = [ "quote", "scale-typegen", "subxt-codegen", - "syn 2.0.66", + "syn 2.0.119", ] [[package]] @@ -12229,9 +12283,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.66" +version = "2.0.119" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" dependencies = [ "proc-macro2", "quote", @@ -12264,7 +12318,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.119", ] [[package]] @@ -12349,7 +12403,16 @@ version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" dependencies = [ - "thiserror-impl", + "thiserror-impl 1.0.61", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl 2.0.18", ] [[package]] @@ -12360,7 +12423,18 @@ checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.119", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", ] [[package]] @@ -12490,7 +12564,7 @@ checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.119", ] [[package]] @@ -12652,7 +12726,7 @@ version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61c5bb1d698276a2443e5ecfabc1008bf15a36c12e6a7176e7bf089ea9131140" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.13.1", "bytes", "futures-core", "futures-util", @@ -12696,7 +12770,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.119", ] [[package]] @@ -12855,7 +12929,7 @@ dependencies = [ "rand 0.8.5", "smallvec", "socket2 0.4.10", - "thiserror", + "thiserror 1.0.61", "tinyvec", "tokio", "tracing", @@ -12880,7 +12954,7 @@ dependencies = [ "once_cell", "rand 0.8.5", "smallvec", - "thiserror", + "thiserror 1.0.61", "tinyvec", "tokio", "tracing", @@ -12901,7 +12975,7 @@ dependencies = [ "parking_lot 0.12.3", "resolv-conf", "smallvec", - "thiserror", + "thiserror 1.0.61", "tokio", "tracing", "trust-dns-proto 0.22.0", @@ -12922,7 +12996,7 @@ dependencies = [ "rand 0.8.5", "resolv-conf", "smallvec", - "thiserror", + "thiserror 1.0.61", "tokio", "tracing", "trust-dns-proto 0.23.2", @@ -12955,7 +13029,7 @@ dependencies = [ "rand 0.8.5", "rustls 0.21.12", "sha1", - "thiserror", + "thiserror 1.0.61", "url", "utf-8", ] @@ -13111,6 +13185,17 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "visibility" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d674d135b4a8c1d7e813e2f8d1c9a58308aee4a680323066025e53132218bd91" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "void" version = "1.0.2" @@ -13137,7 +13222,7 @@ dependencies = [ "rand_core 0.6.4", "sha2 0.10.8", "sha3", - "thiserror", + "thiserror 1.0.61", "zeroize", ] @@ -13211,7 +13296,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.119", "wasm-bindgen-shared", ] @@ -13245,7 +13330,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.119", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -13276,7 +13361,7 @@ dependencies = [ "strum 0.24.1", "strum_macros 0.24.3", "tempfile", - "thiserror", + "thiserror 1.0.61", "wasm-opt-cxx-sys", "wasm-opt-sys", ] @@ -13443,7 +13528,7 @@ dependencies = [ "log", "object 0.30.4", "target-lexicon", - "thiserror", + "thiserror 1.0.61", "wasmparser", "wasmtime-cranelift-shared", "wasmtime-environ", @@ -13478,7 +13563,7 @@ dependencies = [ "object 0.30.4", "serde", "target-lexicon", - "thiserror", + "thiserror 1.0.61", "wasmparser", "wasmtime-types", ] @@ -13561,7 +13646,7 @@ checksum = "a4f6fffd2a1011887d57f07654dd112791e872e3ff4a2e626aee8059ee17f06f" dependencies = [ "cranelift-entity", "serde", - "thiserror", + "thiserror 1.0.61", "wasmparser", ] @@ -13961,7 +14046,7 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" dependencies = [ - "curve25519-dalek 4.1.2", + "curve25519-dalek 4.1.3", "rand_core 0.6.4", "serde", "zeroize", @@ -13981,7 +14066,7 @@ dependencies = [ "nom", "oid-registry", "rusticata-macros", - "thiserror", + "thiserror 1.0.61", "time", ] @@ -13998,7 +14083,7 @@ dependencies = [ "nom", "oid-registry", "rusticata-macros", - "thiserror", + "thiserror 1.0.61", "time", ] @@ -14048,7 +14133,7 @@ checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.119", ] [[package]] @@ -14068,7 +14153,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.119", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 8068191..6093ed5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ homepage.workspace = true [workspace.package] license = "GPL-3.0-only" authors = ["571nky", "57r37ch", "f4750"] -version = "0.8.5" +version = "0.9.0" edition = "2021" homepage = "https://ghostchain.io" repository = "https://git.ghostchain.io/ghostchain/ghost-node" @@ -67,18 +67,21 @@ serial_test = { version = "2.0.0" } color-eyre = { version = "0.6.1" } bs58 = { version = "0.5.0" } prometheus-parse = { version = "0.2.2" } +k256 = { version = "0.13.0", default-features = false } rustc-hex = { version = "2.1.0", default-features = false } log = { version = "0.4", default-features = false } num-traits = { version = "0.2.17", default-features = false } libsecp256k1 = { version = "0.7", default-features = false } bip39 = { package = "parity-bip39", version = "2.0.1" } sha3 = { version = "0.10", default-features = false } +sha2 = { version = "0.10.7", default-features = false } serde = { version = "1.0.197", default-features = false } serde_derive = { version = "1.0.117", default-features = false } serde_json = { version = "1.0.114", default-features = false } blake2-rfc = { version = "0.2.18", default-features = false } impl-serde = { version = "0.4.0", default-features = false } hex = { version = "0.4.3", default-features = false } +strum = { version = "0.28.0", default-features = false } metered = { package = "prioritized-metered-channel", version = "0.6.1", default-features = false } scale-value = { version = "0.16.0" } @@ -86,6 +89,13 @@ codec = { package = "parity-scale-codec", version = "3.6.1", default-features = scale-info = { version = "2.5.0", default-features = false } subxt = { version = "0.37.0", default-features = false } +frost-secp256k1 = { version = "2.2.0", default-features = false, features = ["serialization"] } +frost-ed25519 = { version = "2.2.0", default-features = false, features = ["serialization"] } +chacha20poly1305 = { version = "0.10.1", default-features = false, features = ["alloc"] } +frost-core = { version = "2.2.0", default-features = false } +rand_chacha = { version = "0.3.0", default-features = false } +hkdf = { version = "0.12.4", default-features = false } + # Frames frame-support = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false } frame-system = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false } @@ -141,6 +151,7 @@ pallet-authority-discovery = { git = "https://github.com/paritytech/polkadot-sdk pallet-authorship = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false } pallet-babe = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false } pallet-bags-list = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false } +pallet-bags-list-remote-tests = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false } pallet-balances = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false } pallet-bounties = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false } pallet-child-bounties = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false } @@ -155,8 +166,6 @@ pallet-grandpa = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = pallet-identity = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false } pallet-indices = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false } pallet-multisig = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false } -pallet-nomination-pools = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false } -pallet-nomination-pools-runtime-api = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false } pallet-offences = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false } pallet-preimage = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false } pallet-proxy = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false } @@ -184,8 +193,6 @@ generate-bags = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = pallet-election-provider-support-benchmarking = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false } pallet-offences-benchmarking = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false } pallet-session-benchmarking = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false } -pallet-nomination-pools-benchmarking = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false } -pallet-bags-list-remote-tests = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0", default-features = false } # Substrate Client dependencies sc-cli = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.12.0" } @@ -227,15 +234,22 @@ ghost-metrics = { path = "metrics", default-features = false } ghost-rpc = { path = "rpc", default-features = false } service = { package = "ghost-service", path = "service", default-features = false } ghost-traits = { path = "pallets/traits", default-features = false } +ghost-helpers = { path = "pallets/helpers", default-features = false } ghost-networks = { path = "pallets/networks", default-features = false } -ghost-claims = { path = "pallets/claims", default-features = false } -ghost-slow-clap = { path = "pallets/slow-clap", default-features = false } +ghost-governance = { path = "pallets/governance", default-features = false } +ghost-weaver = { path = "pallets/weaver", default-features = false } +ghost-exodus = { path = "pallets/exodus", default-features = false } ghost-sudo = { path = "pallets/sudo", default-features = false } +ghost-nomination-pools = { path = "pallets/pools", default-features = false } +ghost-nomination-pools-runtime-api = { path = "pallets/pools/runtime-api", default-features = false } +ghost-nomination-pools-benchmarking = { path = "pallets/pools/benchmarking", default-features = false } +ghost-nomination-pools-fuzzer = { path = "pallets/pools/benchmarking", default-features = false } + ghost-runtime-constants = { package = "ghost-runtime-constants", path = "runtime/ghost/constants", default-features = false } casper-runtime = { path = "runtime/casper", default-features = false } casper-runtime-constants = { package = "casper-runtime-constants", path = "runtime/casper/constants", default-features = false } primitives = { package = "ghost-core-primitives", path = "core-primitives", default-features = false } -runtime-common = { package = "ghost-runtime-common", path = "runtime/common", default-features = false } +runtime-common = { package = "runtime-common", path = "runtime/common", default-features = false } [dependencies] color-eyre = { workspace = true } @@ -271,9 +285,13 @@ members = [ "runtime/casper", "runtime/casper/constants", "pallets/networks", - "pallets/claims", - "pallets/slow-clap", + "pallets/governance", + "pallets/pools", "pallets/sudo", + "pallets/exodus", + "pallets/exodus/runtime-api", + "pallets/helpers", + "pallets/weaver", "utils/bags-list", "utils/chain-spec-builder", "utils/generate-bags", @@ -326,9 +344,12 @@ crypto-mac = { opt-level = 3 } curve25519-dalek = { opt-level = 3 } ed25519-dalek = { opt-level = 3 } flate2 = { opt-level = 3 } +frost-core = { opt-level = 3 } +frost-secp256k1 = { opt-level = 3 } futures-channel = { opt-level = 3 } hash-db = { opt-level = 3 } hashbrown = { opt-level = 3 } +hkdf = { opt-level = 3 } hmac = { opt-level = 3 } httparse = { opt-level = 3 } integer-sqrt = { opt-level = 3 } @@ -344,6 +365,7 @@ parking_lot = { opt-level = 3 } parking_lot_core = { opt-level = 3 } percent-encoding = { opt-level = 3 } primitive-types = { opt-level = 3 } +rand_chacha = { opt-level = 3 } ring = { opt-level = 3 } rustls = { opt-level = 3 } sha2 = { opt-level = 3 } diff --git a/cli/src/command.rs b/cli/src/command.rs index c283528..6a0c700 100644 --- a/cli/src/command.rs +++ b/cli/src/command.rs @@ -67,11 +67,11 @@ impl SubstrateCli for Cli { #[cfg(feature = "casper-native")] "casper" => Box::new(service::chain_spec::casper_config()?), #[cfg(feature = "casper-native")] - "casper-dev" | "dev" | "development" => { + "casper-dev" | "casper-development" => { Box::new(service::chain_spec::casper_development_config()?) } #[cfg(feature = "casper-native")] - "casper-local" | "local" => { + "casper-local" => { Box::new(service::chain_spec::casper_local_testnet_config()?) } #[cfg(feature = "casper-native")] @@ -126,7 +126,10 @@ where set_ss58_version(chain_spec); - runner.run_node_until_exit(move |config| async move { + runner.run_node_until_exit(move |mut config| async move { + config.offchain_worker.enabled = true; + config.offchain_worker.indexing_enabled = true; + let hwbench = (!cli.run.no_hardware_benchmarks) .then_some(config.database.path().map(|database_path| { let _ = std::fs::create_dir_all(&database_path); diff --git a/pallets/claims/src/benchmarking.rs b/pallets/claims/src/benchmarking.rs deleted file mode 100644 index f5a60a9..0000000 --- a/pallets/claims/src/benchmarking.rs +++ /dev/null @@ -1,59 +0,0 @@ -#![cfg(feature = "runtime-benchmarks")] - -use super::*; -use frame_benchmarking::v2::*; - -#[instance_benchmarks] -mod benchmarks { - use super::*; - use frame_support::dispatch::RawOrigin; - use pallet_ranked_collective::Pallet as Club; - - #[benchmark] - fn claim() { - let i = 1337u32; - let ethereum_secret_key = - libsecp256k1::SecretKey::parse(&keccak_256(&i.to_le_bytes())).unwrap(); - let eth_address = crate::secp_utils::eth(ðereum_secret_key); - - let balance = CurrencyOf::::minimum_balance(); - let pseudo_account: T::AccountId = Pallet::::into_account_id(eth_address).unwrap(); - let _ = CurrencyOf::::deposit_creating(&pseudo_account, balance); - Total::::put(balance); - - let pseudo_rank = 5u16; - let _ = Club::::do_add_member_to_rank(pseudo_account.clone(), pseudo_rank, false); - - let user_account: T::AccountId = account("user", i, 0); - let signature = - crate::secp_utils::sig::(ðereum_secret_key, &user_account.encode()); - - let prev_balance = CurrencyOf::::free_balance(&user_account); - let prev_rank = Club::::rank_of(&user_account); - - #[extrinsic_call] - claim( - RawOrigin::Signed(user_account.clone()), - eth_address, - signature, - ); - - assert_eq!( - CurrencyOf::::free_balance(&user_account), - prev_balance + balance - ); - assert_eq!( - CurrencyOf::::free_balance(&pseudo_account), - balance - balance - ); - - let rank = match prev_rank { - Some(current_rank) if pseudo_rank <= current_rank => Some(current_rank), - _ => Some(pseudo_rank), - }; - assert_eq!(Club::::rank_of(&user_account), rank); - assert_eq!(Club::::rank_of(&pseudo_account), None); - } - - impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::Test,); -} diff --git a/pallets/claims/src/lib.rs b/pallets/claims/src/lib.rs deleted file mode 100644 index a922a23..0000000 --- a/pallets/claims/src/lib.rs +++ /dev/null @@ -1,336 +0,0 @@ -#![cfg_attr(not(feature = "std"), no_std)] - -use frame_support::{ - ensure, - pallet_prelude::*, - traits::{ - Currency, ExistenceRequirement, Get, RankedMembers, RankedMembersSwapHandler, - VestingSchedule, - }, - DefaultNoBound, -}; -use frame_system::pallet_prelude::*; -pub use pallet::*; -use serde::{self, Deserialize, Deserializer, Serialize, Serializer}; - -use sp_io::{crypto::secp256k1_ecdsa_recover, hashing::keccak_256}; -use sp_runtime::traits::{BlockNumberProvider, CheckedDiv, CheckedSub}; -use sp_std::prelude::*; - -extern crate alloc; -#[cfg(not(feature = "std"))] -use alloc::{format, string::String}; - -mod weights; -pub use crate::weights::WeightInfo; - -mod benchmarking; -mod mock; -mod secp_utils; -mod tests; - -/// An ethereum address (i.e. 20 bytes, used to represent an Ethereum account). -/// -/// This gets serialized to the 0x-prefixed hex representation. -#[derive(Clone, Copy, PartialEq, Eq, Encode, Decode, Default, RuntimeDebug, TypeInfo)] -pub struct EthereumAddress(pub [u8; 20]); - -impl Serialize for EthereumAddress { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - let hex: String = rustc_hex::ToHex::to_hex(&self.0[..]); - serializer.serialize_str(&format!("0x{}", hex)) - } -} - -impl<'de> Deserialize<'de> for EthereumAddress { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let base_string = String::deserialize(deserializer)?; - let offset = if base_string.starts_with("0x") { 2 } else { 0 }; - let s = &base_string[offset..]; - if s.len() != 40 { - Err(serde::de::Error::custom( - "Bad length of Ethereum address (should be 42 including `0x`)", - ))?; - } - let raw: Vec = rustc_hex::FromHex::from_hex(s) - .map_err(|e| serde::de::Error::custom(format!("{:?}", e)))?; - let mut r = Self::default(); - r.0.copy_from_slice(&raw); - Ok(r) - } -} - -#[derive(Encode, Decode, Clone, TypeInfo)] -pub struct EcdsaSignature(pub [u8; 65]); - -impl PartialEq for EcdsaSignature { - fn eq(&self, other: &Self) -> bool { - &self.0[..] == &other.0[..] - } -} - -impl sp_std::fmt::Debug for EcdsaSignature { - fn fmt(&self, f: &mut sp_std::fmt::Formatter<'_>) -> sp_std::fmt::Result { - write!(f, "EcdsaSignature({:?})", &self.0[..]) - } -} - -type CurrencyOf = <>::VestingSchedule as VestingSchedule< - ::AccountId, ->>::Currency; - -type BalanceOf = - as Currency<::AccountId>>::Balance; - -type RankOf = as RankedMembers>::Rank; -type AccountIdOf = as RankedMembers>::AccountId; - -#[frame_support::pallet] -pub mod pallet { - use super::*; - - #[pallet::pallet] - #[pallet::without_storage_info] - pub struct Pallet(_); - - #[pallet::config] - pub trait Config: - frame_system::Config + pallet_ranked_collective::Config - { - type RuntimeEvent: From> - + IsType<::RuntimeEvent>; - - type VestingSchedule: VestingSchedule>; - type BlockNumberProvider: BlockNumberProvider>; - type MemberSwappedHandler: RankedMembersSwapHandler, RankOf>; - - #[pallet::constant] - type Prefix: Get<&'static [u8]>; - - #[pallet::constant] - type MaximumWithdrawAmount: Get>; - - #[pallet::constant] - type VestingBlocks: Get; - - type WeightInfo: WeightInfo; - } - - #[pallet::event] - #[pallet::generate_deposit(pub(super) fn deposit_event)] - pub enum Event, I: 'static = ()> { - Claimed { - receiver: T::AccountId, - donor: T::AccountId, - amount: BalanceOf, - rank: Option>, - }, - } - - #[pallet::error] - pub enum Error { - InvalidEthereumSignature, - InvalidEthereumAddress, - NoBalanceToClaim, - AddressDecodingFailed, - ArithmeticError, - PotUnderflow, - } - - #[pallet::storage] - pub type Total, I: 'static = ()> = StorageValue<_, BalanceOf, ValueQuery>; - - #[pallet::genesis_config] - #[derive(DefaultNoBound)] - pub struct GenesisConfig, I: 'static = ()> { - pub total: BalanceOf, - pub members_and_ranks: Vec<(T::AccountId, u16)>, - } - - #[pallet::genesis_build] - impl, I: 'static> BuildGenesisConfig for GenesisConfig { - fn build(&self) { - let cult_accounts: Vec<_> = self - .members_and_ranks - .iter() - .map(|(account_id, rank)| { - assert!( - pallet_ranked_collective::Pallet::::do_add_member_to_rank( - account_id.clone(), - *rank, - false - ) - .is_ok(), - "error during adding and promotion" - ); - account_id - }) - .collect(); - - assert!( - self.members_and_ranks.len() == cult_accounts.len(), - "duplicates in `members_and_ranks`" - ); - - Total::::put(self.total); - } - } - - #[pallet::call] - impl, I: 'static> Pallet { - #[pallet::call_index(0)] - #[pallet::weight(>::WeightInfo::claim())] - pub fn claim( - origin: OriginFor, - ethereum_address: EthereumAddress, - ethereum_signature: EcdsaSignature, - ) -> DispatchResult { - let who = ensure_signed(origin)?; - let data = who.using_encoded(to_ascii_hex); - let recovered_address = Self::recover_ethereum_address(ðereum_signature, &data) - .ok_or(Error::::InvalidEthereumSignature)?; - - ensure!( - recovered_address == ethereum_address, - Error::::InvalidEthereumAddress - ); - - Self::do_claim(who, ethereum_address) - } - } -} - -fn to_ascii_hex(data: &[u8]) -> Vec { - let mut r = Vec::with_capacity(data.len() * 2); - let mut push_nibble = |n| r.push(if n < 10 { b'0' + n } else { b'a' - 10 + n }); - for &b in data.iter() { - push_nibble(b / 16); - push_nibble(b % 16); - } - r -} - -impl, I: 'static> Pallet { - fn ethereum_signable_message(what: &[u8]) -> Vec { - let prefix = T::Prefix::get(); - let mut l = prefix.len() + what.len(); - let mut rev = Vec::new(); - while l > 0 { - rev.push(b'0' + (l % 10) as u8); - l /= 10; - } - let mut v = b"\x19Ethereum Signed Message:\n".to_vec(); - v.extend(rev.into_iter().rev()); - v.extend_from_slice(prefix); - v.extend_from_slice(what); - v - } - - fn recover_ethereum_address(s: &EcdsaSignature, what: &[u8]) -> Option { - let msg = keccak_256(&Self::ethereum_signable_message(what)); - let mut res = EthereumAddress::default(); - res.0 - .copy_from_slice(&keccak_256(&secp256k1_ecdsa_recover(&s.0, &msg).ok()?[..])[12..]); - Some(res) - } - - fn into_account_id(address: EthereumAddress) -> Result { - let mut data = [0u8; 32]; - data[0..4].copy_from_slice(b"evm:"); - data[4..24].copy_from_slice(&address.0[..]); - - let hash = sp_core::keccak_256(&data); - T::AccountId::decode(&mut &hash[..]) - } - - fn do_claim(receiver: T::AccountId, ethereum_address: EthereumAddress) -> DispatchResult { - let donor = Self::into_account_id(ethereum_address) - .ok() - .ok_or(Error::::AddressDecodingFailed)?; - - let balance_due = CurrencyOf::::free_balance(&donor); - ensure!( - balance_due >= CurrencyOf::::minimum_balance(), - Error::::NoBalanceToClaim - ); - - let new_total = Total::::get() - .checked_sub(&balance_due) - .ok_or(Error::::PotUnderflow)?; - - CurrencyOf::::transfer( - &donor, - &receiver, - balance_due, - ExistenceRequirement::AllowDeath, - )?; - - let max_amount = T::MaximumWithdrawAmount::get(); - if balance_due > max_amount { - let vesting_balance = balance_due - .checked_sub(&max_amount) - .ok_or(Error::::ArithmeticError)?; - - let vesting_blocks = T::VestingBlocks::get(); - let per_block_balance = vesting_balance - .checked_div(&vesting_blocks.into()) - .ok_or(Error::::ArithmeticError)?; - - T::VestingSchedule::add_vesting_schedule( - &receiver, - vesting_balance, - per_block_balance, - T::BlockNumberProvider::current_block_number(), - )?; - } - - let rank = if let Some(rank) = - as RankedMembers>::rank_of(&donor) - { - pallet_ranked_collective::Pallet::::do_remove_member_from_rank(&donor, rank)?; - let new_rank = - match as RankedMembers>::rank_of(&receiver) - { - Some(current_rank) if current_rank >= rank => current_rank, - Some(current_rank) if current_rank < rank => { - for _ in 0..rank - current_rank { - pallet_ranked_collective::Pallet::::do_promote_member( - receiver.clone(), - None, - false, - )?; - } - rank - } - _ => { - pallet_ranked_collective::Pallet::::do_add_member_to_rank( - receiver.clone(), - rank, - false, - )?; - rank - } - }; - >::MemberSwappedHandler::swapped(&donor, &receiver, new_rank); - Some(new_rank) - } else { - None - }; - - Total::::put(new_total); - Self::deposit_event(Event::::Claimed { - receiver, - donor, - amount: balance_due, - rank, - }); - - Ok(()) - } -} diff --git a/pallets/claims/src/mock.rs b/pallets/claims/src/mock.rs deleted file mode 100644 index c5ecf1f..0000000 --- a/pallets/claims/src/mock.rs +++ /dev/null @@ -1,281 +0,0 @@ -#![cfg(test)] - -use super::*; - -pub use crate as ghost_claims; -use frame_support::{ - derive_impl, parameter_types, - traits::{ConstU16, ConstU64, PollStatus, Polling, WithdrawReasons}, -}; -use frame_system::EnsureRootWithSuccess; -pub use pallet_ranked_collective::{Rank, TallyOf}; -use sp_runtime::{traits::Convert, BuildStorage}; - -pub mod eth_keys { - use crate::{mock::Test, EcdsaSignature, EthereumAddress}; - use codec::Encode; - use hex_literal::hex; - - pub fn total_claims() -> u64 { - 10 + 100 + 1000 - } - pub fn alice_account_id() -> ::AccountId { - 69 - } - pub fn bob_account_id() -> ::AccountId { - 1337 - } - - pub fn first_eth_public_known() -> EthereumAddress { - EthereumAddress(hex!("1A69d2D5568D1878023EeB121a73d33B9116A760")) - } - pub fn second_eth_public_known() -> EthereumAddress { - EthereumAddress(hex!("2f86cfBED3fbc1eCf2989B9aE5fc019a837A9C12")) - } - pub fn third_eth_public_known() -> EthereumAddress { - EthereumAddress(hex!("e83f67361Ac74D42A48E2DAfb6706eb047D8218D")) - } - pub fn fourth_eth_public_known() -> EthereumAddress { - EthereumAddress(hex!("827ee4ad9b259b6fa1390ed60921508c78befd63")) - } - - fn first_eth_private_key() -> libsecp256k1::SecretKey { - libsecp256k1::SecretKey::parse(&hex!( - "01c928771aea942a1e7ac06adf2b73dfbc9a25d9eaa516e3673116af7f345198" - )) - .unwrap() - } - fn second_eth_private_key() -> libsecp256k1::SecretKey { - libsecp256k1::SecretKey::parse(&hex!( - "b19a435901872f817185f7234a1484eae837613f9d10cf21927a23c2d8cb9139" - )) - .unwrap() - } - fn third_eth_private_key() -> libsecp256k1::SecretKey { - libsecp256k1::SecretKey::parse(&hex!( - "d3baf57b74d65719b2dc33f5a464176022d0cc5edbca002234229f3e733875fc" - )) - .unwrap() - } - fn fourth_eth_private_key() -> libsecp256k1::SecretKey { - libsecp256k1::SecretKey::parse(&hex!( - "c4683d566436af6b58b4a59c8f501319226e85b21869bf93d5eeb4596d4791d4" - )) - .unwrap() - } - fn wrong_eth_private_key() -> libsecp256k1::SecretKey { - libsecp256k1::SecretKey::parse(&hex!( - "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef" - )) - .unwrap() - } - - pub fn first_eth_public_key() -> EthereumAddress { - crate::secp_utils::eth(&first_eth_private_key()) - } - pub fn second_eth_public_key() -> EthereumAddress { - crate::secp_utils::eth(&second_eth_private_key()) - } - pub fn third_eth_public_key() -> EthereumAddress { - crate::secp_utils::eth(&third_eth_private_key()) - } - pub fn fourth_eth_public_key() -> EthereumAddress { - crate::secp_utils::eth(&fourth_eth_private_key()) - } - - pub fn first_account_id() -> ::AccountId { - crate::secp_utils::into_account_id::(first_eth_public_key()) - } - pub fn second_account_id() -> ::AccountId { - crate::secp_utils::into_account_id::(second_eth_public_key()) - } - pub fn third_account_id() -> ::AccountId { - crate::secp_utils::into_account_id::(third_eth_public_key()) - } - pub fn fourth_account_id() -> ::AccountId { - crate::secp_utils::into_account_id::(fourth_eth_public_key()) - } - - pub fn first_signature() -> EcdsaSignature { - crate::secp_utils::sig::(&first_eth_private_key(), &alice_account_id().encode()) - } - pub fn second_signature() -> EcdsaSignature { - crate::secp_utils::sig::(&second_eth_private_key(), &alice_account_id().encode()) - } - pub fn third_signature() -> EcdsaSignature { - crate::secp_utils::sig::(&third_eth_private_key(), &alice_account_id().encode()) - } - pub fn fourth_signature() -> EcdsaSignature { - crate::secp_utils::sig::(&fourth_eth_private_key(), &bob_account_id().encode()) - } - pub fn wrong_signature() -> EcdsaSignature { - crate::secp_utils::sig::(&wrong_eth_private_key(), &alice_account_id().encode()) - } -} - -#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] -impl frame_system::Config for Test { - type RuntimeOrigin = RuntimeOrigin; - type RuntimeCall = RuntimeCall; - type Block = Block; - type RuntimeEvent = RuntimeEvent; - type AccountData = pallet_balances::AccountData; - type MaxConsumers = frame_support::traits::ConstU32<16>; -} - -#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)] -impl pallet_balances::Config for Test { - type AccountStore = System; -} - -parameter_types! { - pub const MinVestedTransfer: u64 = 1; - pub UnvestedFundsAllowedWithdrawReasons: WithdrawReasons = - WithdrawReasons::except(WithdrawReasons::TRANSFER | WithdrawReasons::RESERVE); -} - -impl pallet_vesting::Config for Test { - type RuntimeEvent = RuntimeEvent; - type Currency = Balances; - type BlockNumberToBalance = sp_runtime::traits::ConvertInto; - type MinVestedTransfer = MinVestedTransfer; - type WeightInfo = (); - type UnvestedFundsAllowedWithdrawReasons = UnvestedFundsAllowedWithdrawReasons; - - type BlockNumberProvider = System; - const MAX_VESTING_SCHEDULES: u32 = 28; -} - -parameter_types! { - pub MinRankOfClassDelta: Rank = 1; -} - -pub struct MinRankOfClass(sp_std::marker::PhantomData); -impl> Convert for MinRankOfClass { - fn convert(a: Class) -> Rank { - a.saturating_sub(Delta::get()) - } -} - -pub struct TestPolls; -impl Polling> for TestPolls { - type Index = u8; - type Votes = u32; - type Moment = u64; - type Class = Class; - - fn classes() -> Vec { - unimplemented!() - } - - fn as_ongoing(_index: u8) -> Option<(TallyOf, Self::Class)> { - unimplemented!() - } - - fn access_poll( - _index: Self::Index, - _f: impl FnOnce(PollStatus<&mut TallyOf, Self::Moment, Self::Class>) -> R, - ) -> R { - unimplemented!() - } - - fn try_access_poll( - _index: Self::Index, - _f: impl FnOnce( - PollStatus<&mut TallyOf, Self::Moment, Self::Class>, - ) -> Result, - ) -> Result { - unimplemented!() - } - - #[cfg(feature = "runtime-benchmarks")] - fn create_ongoing(_class: Self::Class) -> Result { - unimplemented!() - } - - #[cfg(feature = "runtime-benchmarks")] - fn end_ongoing(_index: Self::Index, _approved: bool) -> Result<(), ()> { - unimplemented!() - } -} - -impl pallet_ranked_collective::Config for Test { - type WeightInfo = (); - type RuntimeEvent = RuntimeEvent; - - type AddOrigin = EnsureRootWithSuccess>; - type RemoveOrigin = EnsureRootWithSuccess>; - type PromoteOrigin = EnsureRootWithSuccess>; - type DemoteOrigin = EnsureRootWithSuccess>; - type ExchangeOrigin = EnsureRootWithSuccess>; - - type Polls = TestPolls; - type MemberSwappedHandler = (); - type MinRankOfClass = MinRankOfClass; - type VoteWeight = pallet_ranked_collective::Geometric; - #[cfg(feature = "runtime-benchmarks")] - type BenchmarkSetup = (); -} - -parameter_types! { - pub Prefix: &'static [u8] = b"AccountId:"; -} - -impl Config for Test { - type RuntimeEvent = RuntimeEvent; - type VestingSchedule = Vesting; - type BlockNumberProvider = System; - type MemberSwappedHandler = (); - - type Prefix = Prefix; - type MaximumWithdrawAmount = ConstU64<200>; - type VestingBlocks = ConstU32<80>; - - type WeightInfo = (); -} - -type Block = frame_system::mocking::MockBlock; -type Class = Rank; - -frame_support::construct_runtime!( - pub enum Test - { - System: frame_system, - Balances: pallet_balances, - Vesting: pallet_vesting, - Club: pallet_ranked_collective, - Claims: ghost_claims, - } -); - -pub fn new_test_ext() -> sp_io::TestExternalities { - let mut t = frame_system::GenesisConfig::::default() - .build_storage() - .unwrap(); - - pallet_balances::GenesisConfig:: { - balances: vec![ - (crate::mock::eth_keys::first_account_id(), 10), - (crate::mock::eth_keys::second_account_id(), 100), - (crate::mock::eth_keys::third_account_id(), 1000), - ], - } - .assimilate_storage(&mut t) - .unwrap(); - - ghost_claims::GenesisConfig:: { - total: crate::mock::eth_keys::total_claims(), - members_and_ranks: vec![ - (crate::mock::eth_keys::second_account_id(), 1), - (crate::mock::eth_keys::third_account_id(), 3), - ], - } - .assimilate_storage(&mut t) - .unwrap(); - - let mut ext = sp_io::TestExternalities::new(t); - ext.execute_with(|| { - System::set_block_number(1); - }); - ext -} diff --git a/pallets/claims/src/secp_utils.rs b/pallets/claims/src/secp_utils.rs deleted file mode 100644 index 3ba53dd..0000000 --- a/pallets/claims/src/secp_utils.rs +++ /dev/null @@ -1,35 +0,0 @@ -#![cfg(any(test, feature = "runtime-benchmarks"))] - -use crate::{keccak_256, Config, EcdsaSignature, EthereumAddress}; - -pub fn public(secret: &libsecp256k1::SecretKey) -> libsecp256k1::PublicKey { - libsecp256k1::PublicKey::from_secret_key(secret) -} - -pub fn eth(secret: &libsecp256k1::SecretKey) -> EthereumAddress { - let mut res = EthereumAddress::default(); - res.0 - .copy_from_slice(&keccak_256(&public(secret).serialize()[1..65])[12..]); - res -} - -#[cfg(test)] -pub fn into_account_id, I: 'static>(address: EthereumAddress) -> T::AccountId { - super::Pallet::::into_account_id(address).unwrap() -} - -pub fn sig, I: 'static>( - secret: &libsecp256k1::SecretKey, - what: &[u8], -) -> EcdsaSignature { - let msg = keccak_256(&super::Pallet::::ethereum_signable_message( - &crate::to_ascii_hex(what)[..], - )); - - let (sig, recovery_id) = libsecp256k1::sign(&libsecp256k1::Message::parse(&msg), secret); - - let mut r = [0u8; 65]; - r[0..64].copy_from_slice(&sig.serialize()[..]); - r[64] = recovery_id.serialize(); - EcdsaSignature(r) -} diff --git a/pallets/claims/src/tests.rs b/pallets/claims/src/tests.rs deleted file mode 100644 index 2ad35d3..0000000 --- a/pallets/claims/src/tests.rs +++ /dev/null @@ -1,285 +0,0 @@ -#![cfg(test)] - -use super::*; -use frame_support::{assert_err, assert_ok}; -use hex_literal::hex; -use mock::{ - eth_keys::{ - alice_account_id, bob_account_id, first_account_id, first_eth_public_key, - first_eth_public_known, first_signature, fourth_account_id, fourth_eth_public_key, - fourth_eth_public_known, fourth_signature, second_account_id, second_eth_public_key, - second_eth_public_known, second_signature, third_account_id, third_eth_public_key, - third_eth_public_known, third_signature, total_claims, wrong_signature, - }, - ghost_claims, new_test_ext, Balances, Claims, Club, RuntimeEvent, RuntimeOrigin, System, Test, - Vesting, -}; - -#[test] -fn serde_works() { - let x = EthereumAddress(hex!["0123456789abcdef0123456789abcdef01234567"]); - let y = serde_json::to_string(&x).unwrap(); - assert_eq!(y, "\"0x0123456789abcdef0123456789abcdef01234567\""); - let z: EthereumAddress = serde_json::from_str(&y).unwrap(); - assert_eq!(x, z); -} - -#[test] -fn known_eth_public_accounts_are_correct() { - assert_eq!(first_eth_public_key(), first_eth_public_known()); - assert_eq!(second_eth_public_key(), second_eth_public_known()); - assert_eq!(third_eth_public_key(), third_eth_public_known()); - assert_eq!(fourth_eth_public_key(), fourth_eth_public_known()); -} - -#[test] -fn basic_setup_works() { - new_test_ext().execute_with(|| { - assert_eq!(Balances::usable_balance(&alice_account_id()), 0); - assert_eq!(Balances::usable_balance(&first_account_id()), 10); - assert_eq!(Balances::usable_balance(&second_account_id()), 100); - assert_eq!(Balances::usable_balance(&third_account_id()), 1000); - - assert_eq!(Club::rank_of(&alice_account_id()), None); - assert_eq!(Club::rank_of(&first_account_id()), None); - assert_eq!(Club::rank_of(&second_account_id()), Some(1)); - assert_eq!(Club::rank_of(&third_account_id()), Some(3)); - - assert_eq!(Vesting::vesting_balance(&alice_account_id()), None); - assert_eq!(ghost_claims::Total::::get(), total_claims()); - }); -} - -#[test] -fn small_claiming_works() { - new_test_ext().execute_with(|| { - assert_ok!(Claims::claim( - RuntimeOrigin::signed(alice_account_id()), - first_eth_public_key(), - first_signature() - )); - - assert_eq!(Balances::usable_balance(&alice_account_id()), 10); - assert_eq!(Balances::usable_balance(&first_account_id()), 0); - assert_eq!(Balances::usable_balance(&second_account_id()), 100); - assert_eq!(Balances::usable_balance(&third_account_id()), 1000); - - assert_eq!(Club::rank_of(&alice_account_id()), None); - assert_eq!(Club::rank_of(&first_account_id()), None); - - assert_eq!(Vesting::vesting_balance(&alice_account_id()), None); - assert_eq!(ghost_claims::Total::::get(), total_claims() - 10); - }) -} - -#[test] -fn medium_claiming_works() { - new_test_ext().execute_with(|| { - assert_ok!(Claims::claim( - RuntimeOrigin::signed(alice_account_id()), - second_eth_public_key(), - second_signature(), - )); - - assert_eq!(Balances::usable_balance(&alice_account_id()), 100); - assert_eq!(Balances::usable_balance(&first_account_id()), 10); - assert_eq!(Balances::usable_balance(&second_account_id()), 0); - assert_eq!(Balances::usable_balance(&third_account_id()), 1000); - - assert_eq!(Club::rank_of(&alice_account_id()), Some(1)); - assert_eq!(Club::rank_of(&second_account_id()), None); - - assert_eq!(Vesting::vesting_balance(&alice_account_id()), None); - assert_eq!(ghost_claims::Total::::get(), total_claims() - 100); - }) -} - -#[test] -fn big_claiming_works() { - new_test_ext().execute_with(|| { - assert_ok!(Claims::claim( - RuntimeOrigin::signed(alice_account_id()), - third_eth_public_key(), - third_signature(), - )); - - assert_eq!(Balances::usable_balance(&alice_account_id()), 200); - assert_eq!(Balances::usable_balance(&first_account_id()), 10); - assert_eq!(Balances::usable_balance(&second_account_id()), 100); - assert_eq!(Balances::usable_balance(&third_account_id()), 0); - - assert_eq!(Club::rank_of(&alice_account_id()), Some(3)); - assert_eq!(Club::rank_of(&third_account_id()), None); - - assert_eq!(Vesting::vesting_balance(&alice_account_id()), Some(800)); - assert_eq!( - ghost_claims::Total::::get(), - total_claims() - 1000 - ); - assert_ok!(Balances::transfer_allow_death( - RuntimeOrigin::signed(alice_account_id()), - bob_account_id(), - 200 - )); - }) -} - -#[test] -fn multiple_accounts_claiming_works() { - new_test_ext().execute_with(|| { - assert_ok!(Claims::claim( - RuntimeOrigin::signed(alice_account_id()), - first_eth_public_key(), - first_signature(), - )); - assert_ok!(Claims::claim( - RuntimeOrigin::signed(alice_account_id()), - second_eth_public_key(), - second_signature(), - )); - assert_ok!(Claims::claim( - RuntimeOrigin::signed(alice_account_id()), - third_eth_public_key(), - third_signature(), - )); - - assert_eq!(Balances::usable_balance(&alice_account_id()), 310); - assert_eq!(Balances::usable_balance(&first_account_id()), 0); - assert_eq!(Balances::usable_balance(&second_account_id()), 0); - assert_eq!(Balances::usable_balance(&third_account_id()), 0); - - assert_eq!(Club::rank_of(&alice_account_id()), Some(3)); - assert_eq!(Club::rank_of(&third_account_id()), None); - - assert_eq!(Vesting::vesting_balance(&alice_account_id()), Some(800)); - assert_eq!(ghost_claims::Total::::get(), 0); - }) -} - -#[test] -fn multiple_accounts_reverese_claiming_works() { - new_test_ext().execute_with(|| { - assert_ok!(Claims::claim( - RuntimeOrigin::signed(alice_account_id()), - third_eth_public_key(), - third_signature(), - )); - assert_ok!(Claims::claim( - RuntimeOrigin::signed(alice_account_id()), - second_eth_public_key(), - second_signature(), - )); - assert_ok!(Claims::claim( - RuntimeOrigin::signed(alice_account_id()), - first_eth_public_key(), - first_signature(), - )); - - assert_eq!(Balances::usable_balance(&alice_account_id()), 310); - assert_eq!(Balances::usable_balance(&first_account_id()), 0); - assert_eq!(Balances::usable_balance(&second_account_id()), 0); - assert_eq!(Balances::usable_balance(&third_account_id()), 0); - - assert_eq!(Club::rank_of(&alice_account_id()), Some(3)); - assert_eq!(Club::rank_of(&third_account_id()), None); - - assert_eq!(Vesting::vesting_balance(&alice_account_id()), Some(800)); - assert_eq!(ghost_claims::Total::::get(), 0); - }) -} - -#[test] -fn cannot_claim_with_bad_signature() { - new_test_ext().execute_with(|| { - assert_err!( - Claims::claim( - RuntimeOrigin::signed(alice_account_id()), - first_eth_public_key(), - wrong_signature() - ), - crate::Error::::InvalidEthereumAddress - ); - - assert_eq!(Balances::usable_balance(&alice_account_id()), 0); - assert_eq!(Balances::usable_balance(&first_account_id()), 10); - assert_eq!(Balances::usable_balance(&second_account_id()), 100); - assert_eq!(Balances::usable_balance(&third_account_id()), 1000); - - assert_eq!(Club::rank_of(&alice_account_id()), None); - assert_eq!(Club::rank_of(&first_account_id()), None); - assert_eq!(Club::rank_of(&second_account_id()), Some(1)); - assert_eq!(Club::rank_of(&third_account_id()), Some(3)); - - assert_eq!(Vesting::vesting_balance(&alice_account_id()), None); - assert_eq!(ghost_claims::Total::::get(), total_claims()); - }) -} - -#[test] -fn cannot_claim_with_wrong_address() { - new_test_ext().execute_with(|| { - assert_err!( - Claims::claim( - RuntimeOrigin::signed(bob_account_id()), - first_eth_public_key(), - first_signature() - ), - crate::Error::::InvalidEthereumAddress - ); - - assert_eq!(Balances::usable_balance(&bob_account_id()), 0); - assert_eq!(Balances::usable_balance(&alice_account_id()), 0); - assert_eq!(Balances::usable_balance(&first_account_id()), 10); - assert_eq!(Balances::usable_balance(&second_account_id()), 100); - assert_eq!(Balances::usable_balance(&third_account_id()), 1000); - - assert_eq!(Club::rank_of(&alice_account_id()), None); - assert_eq!(Club::rank_of(&first_account_id()), None); - assert_eq!(Club::rank_of(&second_account_id()), Some(1)); - assert_eq!(Club::rank_of(&third_account_id()), Some(3)); - - assert_eq!(Vesting::vesting_balance(&alice_account_id()), None); - assert_eq!(ghost_claims::Total::::get(), total_claims()); - }) -} - -#[test] -fn cannot_claim_nothing() { - new_test_ext().execute_with(|| { - assert_err!( - Claims::claim( - RuntimeOrigin::signed(bob_account_id()), - fourth_eth_public_key(), - fourth_signature() - ), - crate::Error::::NoBalanceToClaim - ); - assert_eq!(Balances::usable_balance(&bob_account_id()), 0); - assert_eq!(Balances::usable_balance(&fourth_account_id()), 0); - assert_eq!(Vesting::vesting_balance(&bob_account_id()), None); - assert_eq!(ghost_claims::Total::::get(), total_claims()); - }) -} - -#[test] -fn event_emitted_during_claim() { - new_test_ext().execute_with(|| { - let account = third_account_id(); - let amount = Balances::usable_balance(&account); - let rank = Club::rank_of(&account); - - System::reset_events(); - assert_eq!(System::event_count(), 0); - assert_ok!(Claims::claim( - RuntimeOrigin::signed(alice_account_id()), - third_eth_public_key(), - third_signature(), - )); - System::assert_has_event(RuntimeEvent::Claims(crate::Event::Claimed { - receiver: alice_account_id(), - donor: account, - amount, - rank, - })); - }) -} diff --git a/pallets/exodus/Cargo.toml b/pallets/exodus/Cargo.toml new file mode 100644 index 0000000..df67f65 --- /dev/null +++ b/pallets/exodus/Cargo.toml @@ -0,0 +1,108 @@ +[package] +name = "ghost-exodus" +version = "0.0.0" +description = "Threshold signature generation with DKG included" +license.workspace = true +authors.workspace = true +edition.workspace = true +homepage.workspace = true +repository.workspace = true + +[dependencies] +log = { workspace = true } +hex = { workspace = true } +codec = { workspace = true, features = ["max-encoded-len"] } +scale-info = { workspace = true, features = ["derive"] } +strum = { workspace = true, features = ["derive"] } +num-traits = { workspace = true } + +frame-benchmarking = { workspace = true, optional = true } +frame-support = { workspace = true } +frame-system = { workspace = true } + +sp-arithmetic = { workspace = true } +sp-application-crypto = { workspace = true } +sp-runtime = { workspace = true } +sp-staking = { workspace = true } +sp-core = { workspace = true } +sp-std = { workspace = true } +sp-io = { workspace = true } + +frost-secp256k1 = { workspace = true } +frost-ed25519 = { workspace = true } +frost-core = { workspace = true } +rand_chacha = { workspace = true } +hkdf = { workspace = true } +chacha20poly1305 = { workspace = true } +sha2 = { workspace = true } +k256 = { workspace = true } +parking_lot = { workspace = true } + +ghost-traits = { workspace = true } +ghost-helpers = { workspace = true } + +pallet-balances = { workspace = true } +pallet-session = { workspace = true } +pallet-staking = { workspace = true } +ghost-networks = { workspace = true } +pallet-staking-reward-curve = { workspace = true } + +[dev-dependencies] +sp-keystore = { workspace = true } +frame-executive = { workspace = true } +rand = { workspace = true } + +[features] +default = ["std"] +std = [ + "frame-benchmarking?/std", + "scale-info/std", + "strum/std", + "log/std", + "hex/std", + "k256/std", + "num-traits/std", + "frame-support/std", + "frame-system/std", + "sha2/std", + "hkdf/std", + "chacha20poly1305/std", + "rand_chacha/std", + "sp-arithmetic/std", + "sp-application-crypto/std", + "sp-runtime/std", + "sp-staking/std", + "sp-core/std", + "sp-std/std", + "sp-io/std", + "frost-secp256k1/std", + "frost-ed25519/std", + "frost-core/std", + "ghost-traits/std", + "ghost-helpers/std", + "pallet-balances/std", + "pallet-session/std", + "pallet-staking/std", + "ghost-networks/std", + "frame-executive/std", + "sp-keystore/std", +] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", + "sp-staking/runtime-benchmarks", + "pallet-balances/runtime-benchmarks", + "pallet-staking/runtime-benchmarks", + "ghost-networks/runtime-benchmarks", +] +try-runtime = [ + "frame-support/try-runtime", + "frame-system/try-runtime", + "sp-runtime/try-runtime", + "pallet-balances/try-runtime", + "pallet-session/try-runtime", + "frame-executive/try-runtime", + "ghost-networks/try-runtime", +] \ No newline at end of file diff --git a/pallets/exodus/runtime-api/Cargo.toml b/pallets/exodus/runtime-api/Cargo.toml new file mode 100644 index 0000000..81f5832 --- /dev/null +++ b/pallets/exodus/runtime-api/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "ghost-exodus-runtime-api" +version = "0.0.0" +description = "RPC runtime API for ghost EXODUS pallet" +license.workspace = true +authors.workspace = true +edition.workspace = true +homepage.workspace = true +repository.workspace = true + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { workspace = true, features = ["derive"] } +sp-api = { workspace = true } + +[features] +default = ["std"] +std = ["codec/std", "sp-api/std"] \ No newline at end of file diff --git a/pallets/exodus/runtime-api/src/lib.rs b/pallets/exodus/runtime-api/src/lib.rs new file mode 100644 index 0000000..d16110c --- /dev/null +++ b/pallets/exodus/runtime-api/src/lib.rs @@ -0,0 +1,7 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +sp_api::decl_runtime_apis! { + pub trait ExodusApi { + fn verifying_key(network_id: NetworkId) -> Vec; + } +} diff --git a/pallets/exodus/src/benchmarking.rs b/pallets/exodus/src/benchmarking.rs new file mode 100644 index 0000000..daea21d --- /dev/null +++ b/pallets/exodus/src/benchmarking.rs @@ -0,0 +1,768 @@ +#![cfg(feature = "runtime-benchmarks")] + +use super::*; +use frame_benchmarking::v2::*; +use frame_system::RawOrigin; + +use ghost_helpers::networks::NetworkDataBuilder; + +fn prepare_pallet(authorities_len: usize) -> ( + PendingDkgAuthorities, BlockNumberFor>, + NetworkCurve, + T::AuthorityId, + AuthIndex +) { + let network_curve = NetworkCurve::Secp256k1; + let authority = T::AuthorityId::generate_pair(None); + + let authorities = vec![authority.clone(); authorities_len]; + let bounded_authorities = WeakBoundedVec::<_, T::MaxAuthorities>::try_from(authorities.clone()) + .expect("more than the maximum number of keys provided"); + + QualificationAuthorities::::insert(network_curve, bounded_authorities); + + let network_id = NetworkIdOf::::default(); + let network_data = NetworkDataBuilder::default() + .with_network_type(NetworkType::Evm) + .with_network_curve(NetworkCurve::Secp256k1) + .build(); + + T::NetworkDataHandler::register(network_id, network_data) + .expect("network should be valid for insertion"); + + let mut dkg_state = PendingDkgAuthorities::default(); + dkg_state.new_dkg(authorities_len); + + (dkg_state, network_curve, authority, 0) +} + +#[benchmarks(where T: Config)] +mod benchmarks { + use super::*; + + #[benchmark] + fn register_round0_package() -> Result<(), BenchmarkError> { + let dummy_hash = ExodusHash::repeat_byte(69); + let (mut dkg_state, network_curve, authority, authority_index) = + prepare_pallet::(1); + + dkg_state.next_phase(); + QualificationDkgState::::insert(&network_curve, dkg_state); + + let round0_package = PackageContext::default() + .with_network_curve(NetworkCurve::Secp256k1) + .with_authority_index(0) + .with_dkg_round0(dummy_hash); + + let dkg_package = DkgPackage::Round0(round0_package); + + let signature = authority + .sign(&dkg_package.encode()) + .expect("Signature should be created"); + + #[extrinsic_call] + _(RawOrigin::None, dkg_package, signature); + + let expected_hash = Round0Packages::::get(&network_curve, authority_index); + assert_eq!(expected_hash, dummy_hash); + + Ok(()) + } + + #[benchmark] + fn register_round1_package( + c: Linear<4, { T::MaxAuthorities::get() }> + ) -> Result<(), BenchmarkError> { + let max_signers = c as AuthIndex; + let min_signers = get_byzantium_threshold(max_signers); + + let (mut dkg_state, network_curve, authority, authority_index) = + prepare_pallet::(max_signers as usize); + + dkg_state.next_phase(); + dkg_state.next_phase(); + + dkg_state.insert_index(authority_index); + QualificationDkgState::::insert(&network_curve, dkg_state); + + let mut rng = Pallet::::get_offchain_rng(authority_index); + + let (_, public_package) = network_curve.dkg_part1( + authority_index, + max_signers, + min_signers, + &mut rng, + ).expect("DKG part1 should work"); + + let package_hash = ExodusHash::from(blake2_256(&public_package)); + Round0Packages::::insert(network_curve, authority_index, package_hash); + + let round1_package = PackageContext::default() + .with_network_curve(NetworkCurve::Secp256k1) + .with_authority_index(0) + .with_dkg_round1(public_package) + .expect("Round1 package should be valid"); + + let dkg_package = DkgPackage::Round1(round1_package); + + let signature = authority + .sign(&dkg_package.encode()) + .expect("Signature should be created"); + + #[extrinsic_call] + _(RawOrigin::None, dkg_package, signature); + + let participants = Round1Packages::::get(&network_curve); + assert!(participants.contains(authority_index)); + + Ok(()) + } + + #[benchmark] + fn register_encrypted_round2_packages( + c: Linear<1, { T::MaxAuthorities::get() / 8 }> + ) -> Result<(), BenchmarkError> { + let max_signers = (c * 8) as AuthIndex; + + let (mut dkg_state, network_curve, authority, authority_index) = + prepare_pallet::(max_signers as usize); + + dkg_state.next_phase(); + dkg_state.next_phase(); + dkg_state.next_phase(); + + (0..max_signers).for_each(|i| { + dkg_state.insert_index(i); + }); + + let padded_participants = dkg_state + .highest_bit::() + .map(|bit_pos| bit_pos.saturating_add(1)) + .unwrap_or_default(); + + QualificationDkgState::::insert(&network_curve, dkg_state); + + let bitmask_len = padded_participants.div_ceil(8); + let blob_len = round2_encrypted_package_size( + padded_participants, + network_curve.scalar_bytes_len(), + network_curve.header_bytes_len(), + ); + + let mut bitmask = vec![0u8; bitmask_len]; + let blob = vec![0u8; blob_len]; + + (0..max_signers).for_each(|i| { + if i != authority_index { + let byte_index = (i / 8) as usize; + let bit_index = (i % 8) as u8; + bitmask[byte_index] |= 1 << bit_index; + } + }); + + let encryption_bundle = BundledPackages { bitmask, blob }; + + let round2_package = PackageContext::default() + .with_network_curve(NetworkCurve::Secp256k1) + .with_authority_index(authority_index) + .with_dkg_round2(encryption_bundle) + .expect("Round2 package should be valid"); + + let dkg_package = DkgPackage::Round2(round2_package); + + let signature = authority + .sign(&dkg_package.encode()) + .expect("Signature should be created"); + + #[extrinsic_call] + _(RawOrigin::None, dkg_package, signature); + + let participants = Round2Packages::::get(&network_curve); + assert!(participants.contains(authority_index)); + + Ok(()) + } + + #[benchmark] + fn register_round3_complaints( + c: Linear<1, { T::MaxAuthorities::get() / 8 }> + ) -> Result<(), BenchmarkError> { + let max_signers = (c * 8) as AuthIndex; + + let (mut dkg_state, network_curve, authority, authority_index) = + prepare_pallet::(max_signers as usize); + + dkg_state.next_phase(); + dkg_state.next_phase(); + dkg_state.next_phase(); + dkg_state.next_phase(); + + (0..max_signers).for_each(|i| { + dkg_state.insert_index(i); + }); + + QualificationDkgState::::insert(&network_curve, dkg_state); + + let bitvec_len = (max_signers + 7) / 8; + let mut accused_indexes = vec![0u8; bitvec_len as usize]; + + (0..max_signers).for_each(|i| { + if i != authority_index { + let byte_index = (i / 8) as usize; + let bit_index = (i % 8) as u8; + accused_indexes[byte_index] |= 1 << bit_index; + } + }); + + let round3_package = PackageContext::default() + .with_network_curve(NetworkCurve::Secp256k1) + .with_authority_index(authority_index) + .with_dkg_round3(accused_indexes) + .expect("Round3 package should be valid"); + + let dkg_package = DkgPackage::Round3(round3_package); + + let signature = authority + .sign(&dkg_package.encode()) + .expect("Signature should be created"); + + #[extrinsic_call] + _(RawOrigin::None, dkg_package, signature); + + let complaints = Complaints::::get(&network_curve); + let my_complaints = complaints.get(&authority_index) + .expect("Comlpaints should be registered"); + + assert_eq!(my_complaints.count_ones::(), max_signers - 1); + + Ok(()) + } + + #[benchmark] + fn register_round4_justifications( + c: Linear<1, { T::MaxAuthorities::get() / 8 }> + ) -> Result<(), BenchmarkError> { + let max_signers = (c * 8) as AuthIndex; + + let (mut dkg_state, network_curve, authority, authority_index) = + prepare_pallet::(max_signers as usize); + + dkg_state.next_phase(); + dkg_state.next_phase(); + dkg_state.next_phase(); + dkg_state.next_phase(); + dkg_state.next_phase(); + + (0..max_signers).for_each(|i| { + dkg_state.insert_index(i); + }); + + let padded_participants = dkg_state + .highest_bit::() + .map(|bit_pos| bit_pos.saturating_add(1)) + .unwrap_or_default(); + + QualificationDkgState::::insert(&network_curve, dkg_state); + + let bitmask_len = padded_participants.div_ceil(8); + let blob_len = round2_package_size( + padded_participants, + network_curve.scalar_bytes_len(), + network_curve.header_bytes_len(), + ); + + let mut bitmask = vec![0u8; bitmask_len]; + let blob = vec![0u8; blob_len]; + + (0..max_signers).for_each(|i| { + if i != authority_index { + let byte_index = (i / 8) as usize; + let bit_index = (i % 8) as u8; + bitmask[byte_index] |= 1 << bit_index; + } + }); + + let justification_bundle = BundledPackages { bitmask, blob }; + + let round4_package = PackageContext::default() + .with_network_curve(NetworkCurve::Secp256k1) + .with_authority_index(authority_index) + .with_dkg_round4(justification_bundle) + .expect("Round4 package should be valid"); + + let dkg_package = DkgPackage::Round4(round4_package); + + let signature = authority + .sign(&dkg_package.encode()) + .expect("Signature should be created"); + + #[extrinsic_call] + _(RawOrigin::None, dkg_package, signature); + + let justifications = Justifications::::get(&network_curve); + let my_justifications = justifications.get(&authority_index) + .expect("Justifications should be registered"); + + assert_eq!(my_justifications.count_ones::(), max_signers - 1); + + Ok(()) + } + + #[benchmark] + fn register_round5_verifying_package() -> Result<(), BenchmarkError> { + let max_signers = 4 as AuthIndex; + + let (mut dkg_state, network_curve, authority, authority_index) = + prepare_pallet::(max_signers as usize); + + dkg_state.next_phase(); + dkg_state.next_phase(); + dkg_state.next_phase(); + dkg_state.next_phase(); + dkg_state.next_phase(); + dkg_state.next_phase(); + + (0..max_signers).for_each(|i| { + dkg_state.insert_index(i); + }); + + let dkg_index = dkg_state.get_dkg_index(); + + QualificationDkgState::::insert(&network_curve, dkg_state); + + let dummy_merkle_root = ExodusHash::repeat_byte(69); + let dummy_verifying_key = vec![69u8; network_curve.element_bytes_len()]; + + let round5_package = PackageContext::default() + .with_network_curve(NetworkCurve::Secp256k1) + .with_authority_index(authority_index) + .with_dkg_round5(dummy_verifying_key.clone(), dummy_merkle_root) + .expect("Round5 package should be valid"); + + let dkg_package = DkgPackage::Round5(round5_package); + + let signature = authority + .sign(&dkg_package.encode()) + .expect("Signature should be created"); + + #[extrinsic_call] + _(RawOrigin::None, dkg_package, signature); + + let bounded_verifying_key = + BoundedVec::>::try_from( + dummy_verifying_key, + ).expect("Bounded verifying key should be correct"); + + let metadata_hashes = [ + SubstrateBlake2Hasher::hash(dummy_merkle_root.as_ref()), + SubstrateBlake2Hasher::hash(bounded_verifying_key.as_ref()), + ]; + let verifying_hash = sequential_hash::(metadata_hashes); + + let verification_key = (network_curve, dkg_index, verifying_hash); + let participants = Verifications::::get(verification_key); + assert!(participants.contains(authority_index)); + + let consensus = VerifyingKeyConsensus::::get(&network_curve, dkg_index); + assert!(consensus.participants.contains(authority_index)); + assert_eq!(consensus.participants.count_ones::(), 1); + assert_eq!(consensus.merkle_root, dummy_merkle_root); + assert_eq!(consensus.element_bytes, bounded_verifying_key); + + Ok(()) + } + + #[benchmark] + fn register_round6_public_share_package( + c: Linear<1, { T::MaxAuthorities::get().next_power_of_two().trailing_zeros() }> + ) -> Result<(), BenchmarkError> { + let computed_max_signers = (1u32 << c) as AuthIndex; + let constant_max_signers = T::MaxAuthorities::get() as AuthIndex; + + let max_signers = sp_std::cmp::min(computed_max_signers, constant_max_signers); + + let (mut dkg_state, network_curve, authority, authority_index) = + prepare_pallet::(max_signers as usize); + + dkg_state.next_phase(); + dkg_state.next_phase(); + dkg_state.next_phase(); + dkg_state.next_phase(); + dkg_state.next_phase(); + dkg_state.next_phase(); + dkg_state.next_phase(); + + (0..max_signers).for_each(|i| { + dkg_state.insert_index(i); + }); + + let dkg_index = dkg_state.get_dkg_index(); + + QualificationDkgState::::insert(&network_curve, dkg_state); + + let dummy_value = vec![69u8; network_curve.element_bytes_len()]; + + let (dummy_merkle_root, dummy_verifying_share, dummy_merkle_proof) = + with_ciphersuite!(network_curve, |f| { + let mut dummy_values = BTreeMap::new(); + + for i in 0..max_signers { + let non_zero_index = i.saturating_add(1); + let identifier: frost_core::Identifier = + frost_core::Identifier::::try_from(non_zero_index) + .expect("Identifier should be created"); + + dummy_values.insert(identifier, dummy_value.clone()); + } + + let merkle_tree = + NetworkCurve::build_merkle_tree( + &dummy_values, + |value: &Vec| Ok(value.clone()), + ).expect("Merkle tree for verifying shares should be valid"); + + let merkle_root = merkle_tree.last().copied().unwrap(); + + let (verifying_share, merkle_proof) = NetworkCurve::generate_merkle_proof_from_tree( + &dummy_values, + &merkle_tree, + authority_index, + |value: &Vec| Ok(value.clone()) + ).expect("Merkle tree proof should be valid"); + + (merkle_root, verifying_share, merkle_proof) + }); + + VerifyingKeyConsensus::::mutate(&network_curve, &dkg_index, |consensus| { + consensus.participants.insert(authority_index); + consensus.merkle_root = dummy_merkle_root; + }); + + let round6_package = PackageContext::default() + .with_network_curve(NetworkCurve::Secp256k1) + .with_authority_index(authority_index) + .with_dkg_round6(dummy_verifying_share.clone(), dummy_merkle_proof) + .expect("Round6 package should be valid"); + + let dkg_package = DkgPackage::Round6(round6_package); + + let signature = authority + .sign(&dkg_package.encode()) + .expect("Signature should be created"); + + #[extrinsic_call] + _(RawOrigin::None, dkg_package, signature); + + let participants = VerifyingSharesParticipants::::get(&network_curve, dkg_index); + assert!(participants.contains(authority_index)); + + let bundled_verifying_share = + BoundedVec::>::try_from( + dummy_verifying_share, + ).expect("Verifying share should be bundled"); + + let verifying_share_key = (network_curve, dkg_index, authority_index); + assert_eq!( + VerifyingShares::::get(verifying_share_key).unwrap(), + bundled_verifying_share, + ); + + Ok(()) + } + + #[benchmark] + fn register_nonce_commitment() -> Result<(), BenchmarkError> { + let (mut dkg_state, network_curve, authority, authority_index) = + prepare_pallet::(1); + + loop { + if !dkg_state.is_dkg_pending() { break; } + dkg_state.next_phase(); + } + + dkg_state.insert_index(authority_index); + let exodus_session = CurrentExodus::::get(); + + let dummy_bytes = EvmBytes32::repeat_byte(69); + let dummy_nonce = vec![0u8; network_curve.element_bytes_len()]; + let request = ExodusRequest::evm_rotation(exodus_session, dummy_bytes, 0); + + let authorities = QualificationAuthorities::::get(&network_curve); + let active_dkg: ActivatedState = (&dkg_state).into(); + QualificationDkgState::::insert(&network_curve, dkg_state); + ActiveDkgAuthorities::::insert(&network_curve, active_dkg); + ActiveAuthorities::::insert(&network_curve, authorities); + ExodusRequests::::insert(network_curve, exodus_session, request); + + let nonce_package = PackageContext::default() + .with_network_curve(NetworkCurve::Secp256k1) + .with_authority_index(authority_index) + .with_exodus_nonce_commitment( + exodus_session, + dummy_nonce.clone(), + dummy_nonce.clone(), + ) + .expect("Nonce commitment package should be valid"); + + let exodus_package = ExodusPackage::NonceCommitment(nonce_package); + + let signature = authority + .sign(&exodus_package.encode()) + .expect("Signature should be created"); + + #[extrinsic_call] + _(RawOrigin::None, exodus_package, signature); + + let roast_session = RoastSessions::::get(&exodus_session, authority_index) + .expect("ROAST session should exist after nonce commitment"); + + let roast_state = RoastSessionStates::::get(&exodus_session, roast_session); + assert!(roast_state.nonce_committers.contains(authority_index)); + assert!(!roast_state.group_committers.contains(authority_index)); + assert!(!roast_state.partial_signers.contains(authority_index)); + + let registered_nonces = NonceCommitments::::get( + (exodus_session, roast_session), + authority_index, + ).expect("Nonces should be registered during nonce commitment"); + + assert_eq!(registered_nonces.hiding, dummy_nonce.clone()); + assert_eq!(registered_nonces.binding, dummy_nonce); + + Ok(()) + } + + #[benchmark] + fn register_group_commitment() -> Result<(), BenchmarkError> { + let (mut dkg_state, network_curve, authority, authority_index) = + prepare_pallet::(1); + + loop { + if !dkg_state.is_dkg_pending() { break; } + dkg_state.next_phase(); + } + + dkg_state.insert_index(authority_index); + let exodus_session = CurrentExodus::::get(); + + let dummy_bytes = EvmBytes32::repeat_byte(69); + let dummy_merkle_root = ExodusHash::repeat_byte(69); + let dummy_group_commitment = vec![0u8; network_curve.element_bytes_len()]; + let request = ExodusRequest::evm_rotation(exodus_session, dummy_bytes, 0); + + let authorities = QualificationAuthorities::::get(&network_curve); + let active_dkg: ActivatedState = (&dkg_state).into(); + QualificationDkgState::::insert(&network_curve, dkg_state); + ActiveDkgAuthorities::::insert(&network_curve, active_dkg); + ActiveAuthorities::::insert(&network_curve, authorities); + ExodusRequests::::insert(network_curve, exodus_session, request); + + let mut roast_state = RoastSessionState::>::default(); + roast_state.status = RoastStatus::GroupCommitments; + roast_state.nonce_committers.insert(authority_index); + + let roast_session = 3; + RoastSessions::::insert(&exodus_session, authority_index, roast_session); + RoastSessionStates::::insert(exodus_session, roast_session, roast_state); + + let group_package = PackageContext::default() + .with_network_curve(NetworkCurve::Secp256k1) + .with_authority_index(authority_index) + .with_exodus_group_commitment( + exodus_session, + dummy_group_commitment.clone(), + dummy_bytes, + ) + .expect("Group commitment package should be valid"); + + let exodus_package = ExodusPackage::GroupCommitment(group_package); + + let signature = authority + .sign(&exodus_package.encode()) + .expect("Signature should be created"); + + #[extrinsic_call] + _(RawOrigin::None, exodus_package, signature); + + let bounded_group_commitment = + BoundedVec::>::try_from( + dummy_group_commitment, + ).expect("Group commtiment should be valid"); + + let roast_state = RoastSessionStates::::get(&exodus_session, roast_session); + assert!(roast_state.nonce_committers.contains(authority_index)); + assert!(roast_state.group_committers.contains(authority_index)); + + let consensus = GroupCommitmentsConsensus::::get(exodus_session, roast_session); + assert!(consensus.participants.contains(authority_index)); + assert_eq!(&consensus.element_bytes, &bounded_group_commitment); + assert_eq!(consensus.merkle_root, dummy_merkle_root); + + let group_commitment_key = (exodus_session, roast_session, dummy_merkle_root, bounded_group_commitment); + let participants = GroupCommitters::::get(group_commitment_key); + assert!(participants.contains(authority_index)); + + Ok(()) + } + + #[benchmark] + fn register_signature_share() -> Result<(), BenchmarkError> { + let max_signers = T::MaxAuthorities::get(); + let threshold = get_byzantium_threshold(max_signers); + + let (mut dkg_state, network_curve, authority, authority_index) = + prepare_pallet::(max_signers as usize); + + loop { + if !dkg_state.is_dkg_pending() { break; } + dkg_state.next_phase(); + } + + dkg_state.insert_index(authority_index); + let dkg_index = dkg_state.get_dkg_index(); + let exodus_session = CurrentExodus::::get(); + + let mut dummy_element = vec![0u8; network_curve.element_bytes_len()]; + dummy_element[0] = 0x02; + let g_x_coords = [ + 0x79, 0xbe, 0x66, 0x7e, 0xf9, 0xdc, 0xbb, 0xac, + 0x55, 0xa0, 0x62, 0x95, 0xce, 0x87, 0x0b, 0x07, + 0x02, 0x9b, 0xfc, 0xdb, 0x2d, 0xce, 0x28, 0xd9, + 0x59, 0xf2, 0x81, 0x5b, 0x16, 0xf8, 0x17, 0x98 + ]; + dummy_element[1..33].copy_from_slice(&g_x_coords); + + let dummy_bytes = EvmBytes32::repeat_byte(69); + let request = ExodusRequest::evm_rotation(exodus_session, dummy_bytes, 0); + + let mut dummy_scalar = vec![0u8; network_curve.scalar_bytes_len()]; + dummy_scalar[network_curve.scalar_bytes_len() - 1] = 1; + + let dummy_signature_share = dummy_scalar.clone(); + let dummy_binding_factor = dummy_scalar.clone(); + let dummy_group_commitment = dummy_element.clone(); + + let (dummy_merkle_root, dummy_merkle_proof) = + with_ciphersuite!(network_curve, |f| { + let mut dummy_values = BTreeMap::new(); + + for i in 0..threshold { + let non_zero_index = (i as AuthIndex).saturating_add(1); + let identifier: frost_core::Identifier = + frost_core::Identifier::::try_from(non_zero_index) + .expect("Identifier should be created"); + + dummy_values.insert(identifier, dummy_binding_factor.clone()); + } + + let merkle_tree = + NetworkCurve::build_merkle_tree( + &dummy_values, + |value: &Vec| Ok(value.clone()), + ).expect("Merkle tree for binding factor should be valid"); + + let merkle_root = merkle_tree.last().copied().unwrap(); + + let (_, merkle_proof) = + NetworkCurve::generate_merkle_proof_from_tree( + &dummy_values, + &merkle_tree, + authority_index, + |value: &Vec| Ok(value.clone()) + ).expect("Merkle tree proof should be valid"); + + (merkle_root, merkle_proof) + }); + + let authorities = QualificationAuthorities::::get(&network_curve); + let active_dkg: ActivatedState = (&dkg_state).into(); + let active_dkg_index = active_dkg.get_dkg_index(); + + QualificationDkgState::::insert(&network_curve, dkg_state); + ActiveDkgAuthorities::::insert(&network_curve, active_dkg); + ActiveAuthorities::::insert(&network_curve, authorities); + ExodusRequests::::insert(network_curve, exodus_session, request); + + let mut roast_state = RoastSessionState::>::default(); + roast_state.status = RoastStatus::PartialSignatures; + roast_state.nonce_committers.insert(authority_index); + roast_state.group_committers.insert(authority_index); + + (0..threshold).for_each(|i| { + if i as AuthIndex == authority_index { return; } + roast_state.partial_signers.insert(i); + }); + + let roast_session = 3; + RoastSessions::::insert(&exodus_session, authority_index, roast_session); + RoastSessionStates::::insert(exodus_session, roast_session, roast_state); + + let bounded_group_commitment = BoundedVec::try_from(dummy_group_commitment).unwrap(); + let dummy_verifying_key = BoundedVec::try_from(dummy_element.clone()).unwrap(); + + let dummy_verifying_share = BoundedVec::try_from(dummy_element.clone()).unwrap(); + let dummy_nonce = ExodusSeparatedNonce::new( + BoundedVec::try_from(dummy_element.clone()).unwrap(), + BoundedVec::try_from(dummy_element.clone()).unwrap(), + ); + + ActiveVerifyingKey::::insert(&network_curve, dummy_verifying_key); + VerifyingShares::::insert((&network_curve, active_dkg_index, authority_index), dummy_verifying_share); + NonceCommitments::::insert((&exodus_session, &roast_session), authority_index, dummy_nonce); + + GroupCommitmentsConsensus::::mutate(&exodus_session, &roast_session, |consensus| { + consensus.element_bytes = bounded_group_commitment.clone(); + consensus.merkle_root = dummy_merkle_root; + (0..threshold).for_each(|i| { + consensus.participants.insert(i); + }); + }); + + let share_package = PackageContext::default() + .with_network_curve(NetworkCurve::Secp256k1) + .with_authority_index(authority_index) + .with_exodus_signature_share( + exodus_session, + dummy_signature_share.clone(), + dummy_merkle_proof, + dummy_binding_factor + ) + .expect("Signature share package should be valid"); + + let exodus_package = ExodusPackage::SignatureShare(share_package); + + let signature = authority + .sign(&exodus_package.encode()) + .expect("Signature should be created"); + + #[extrinsic_call] + _(RawOrigin::None, exodus_package, signature); + + let maybe_roast_session = RoastSessions::::get(&exodus_session, authority_index); + assert!(maybe_roast_session.is_none()); + + let scalar_exists = SignatureScalars::::contains_key(&exodus_session, &roast_session); + let consensus_exists = GroupCommitmentsConsensus::::contains_key(&exodus_session, &roast_session); + let committers_exists = GroupCommitters::::contains_key(( + &exodus_session, + &roast_session, + &dummy_merkle_root, + &bounded_group_commitment, + )); + + assert!(!scalar_exists); + assert!(!consensus_exists); + assert!(!committers_exists); + + assert!(!ExodusRequests::::contains_key(&network_curve, &exodus_session)); + assert!(ExodusSignedRotations::::contains_key((&exodus_session, NetworkType::Evm), dkg_index)); + + Ok(()) + } + + impl_benchmark_test_suite!( + Pallet, + crate::mock::new_test_ext_benchmarks(), + crate::mock::Runtime + ); +} diff --git a/pallets/exodus/src/error.rs b/pallets/exodus/src/error.rs new file mode 100644 index 0000000..a0fbdd6 --- /dev/null +++ b/pallets/exodus/src/error.rs @@ -0,0 +1,94 @@ +use ghost_helpers::networks::NetworkCurve; + +use crate::AuthIndex; + +#[derive(Clone, Copy, PartialEq, Eq)] +pub enum ExodusError { + InvalidParticipantId, + DeserializationError, + SerializationError, + InvalidMinSigners(AuthIndex, AuthIndex), + InvalidMaxSigners(AuthIndex), + DKGNotSupported(NetworkCurve), + IncorrectNumberOfCoefficients, + IncorrectNumberOfCommitments, + IncorrectNumberOfPackages, + IncorrectPackage, + PackageNotFound, + InvalidSecretShare, + InvalidProofOfKnowledge, + MissingCommitment, + IncorrectCommitment, + UnknownIdentifier, + IdentityCommitment, + DuplicatedIdentifier, + IncorrectNumberOfIdentifiers, + InvalidSignature, + InvalidSignatureShare, + InvalidNonceCommitments, + EncryptionFailed, + DecryptionFailed, + HKDFFailed, + InvalidNonceLength, + MalformedSigningKey, + NoStoredNetworks, + OffchainTimeoutPeriod, + BadSignature(AuthIndex), + TransactionSubmissionFailed(AuthIndex), + UnknownReceiverIndex(AuthIndex), + CouldNotConstructMessage(AuthIndex), + NothingToWrite, + UnknownPrefix, + UnexpectedRound, + InvalidMerkleProof, + InvalidIdentityElement, + NoActiveAuthorities, + Unknown, +} + +impl core::fmt::Debug for ExodusError { + fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result { + match *self { + ExodusError::InvalidParticipantId => write!(fmt, "Participant index could not be parsed to identifier."), + ExodusError::DeserializationError => write!(fmt, "Error during deserializing value."), + ExodusError::SerializationError => write!(fmt, "Error serializing value."), + ExodusError::InvalidMinSigners(min, max) => write!(fmt, "Number of min_signers ({min}) must be at least 2 and not larger than max_signers ({max})."), + ExodusError::InvalidMaxSigners(max_signers) => write!(fmt, "Number of max_signers must be at least 2, {max_signers} given."), + ExodusError::DKGNotSupported(ref curve) => write!(fmt, "The ciphersuite {curve:?} does not support DKG."), + ExodusError::IncorrectNumberOfCoefficients => write!(fmt, "Incorrect number of coefficients; should be equal to min signers."), + ExodusError::IncorrectNumberOfCommitments => write!(fmt, "Incorrect number of commitments; should be equal to min signers."), + ExodusError::InvalidNonceCommitments => write!(fmt, "Hiding nonce commitments indexes should match binding nonce commitments."), + ExodusError::InvalidProofOfKnowledge => write!(fmt, "The proof of knowledge is not valid."), + ExodusError::IncorrectNumberOfPackages => write!(fmt, "Incorrect number of packages; should be equal to max signers."), + ExodusError::IncorrectPackage => write!(fmt, "The incorrect package was specified."), + ExodusError::PackageNotFound => write!(fmt, "Round 1 package not found for Round 2 participant."), + ExodusError::InvalidSecretShare => write!(fmt, "Invalid secret share."), + ExodusError::MissingCommitment => write!(fmt, "The Signing Package must contain the participant's Commitments."), + ExodusError::IncorrectCommitment => write!(fmt, "The participant's commitment is incorrect."), + ExodusError::UnknownIdentifier => write!(fmt, "Unknown identifier."), + ExodusError::IdentityCommitment => write!(fmt, "Commitment equals the identity."), + ExodusError::DuplicatedIdentifier => write!(fmt, "Duplicated identifier."), + ExodusError::IncorrectNumberOfIdentifiers => write!(fmt, "Incorrect number of identifiers."), + ExodusError::InvalidSignature => write!(fmt, "Invalid signature."), + ExodusError::InvalidSignatureShare => write!(fmt, "Invalid signature share."), + ExodusError::EncryptionFailed => write!(fmt, "Encryption failed."), + ExodusError::DecryptionFailed => write!(fmt, "Decryption failed."), + ExodusError::HKDFFailed => write!(fmt, "HKDF failed."), + ExodusError::InvalidNonceLength => write!(fmt, "Encryption nonce has invalid length."), + ExodusError::MalformedSigningKey => write!(fmt, "Malformed signing key encoding."), + ExodusError::NoStoredNetworks => write!(fmt, "No stored networks to be worked on."), + ExodusError::OffchainTimeoutPeriod => write!(fmt, "Offchain request should be in-flight."), + ExodusError::BadSignature(auth_index) => write!(fmt, "Signature could not be created from {auth_index}."), + ExodusError::TransactionSubmissionFailed(auth_index) => write!(fmt, "Could not submit transaction from {auth_index}."), + ExodusError::UnknownReceiverIndex(receiver_index) => write!(fmt, "Unknown receiver index {receiver_index} found."), + ExodusError::CouldNotConstructMessage(auth_index) => write!(fmt, "Could not construct message during OCW from {auth_index}."), + ExodusError::NothingToWrite => write!(fmt, "Nothing to write into local storage."), + ExodusError::UnknownPrefix => write!(fmt, "Unknown prefix used for local storage."), + ExodusError::UnexpectedRound => write!(fmt, "Unexpected round provided."), + ExodusError::InvalidMerkleProof => write!(fmt, "Invalid merkle proof provided."), + ExodusError::InvalidIdentityElement => write!(fmt, "Invalid identity element provided."), + ExodusError::NoActiveAuthorities => write!(fmt, "No active authorities for signing exodus transactions."), + ExodusError::Unknown => write!(fmt, "Unknown error."), + } + } +} diff --git a/pallets/exodus/src/exodus.rs b/pallets/exodus/src/exodus.rs new file mode 100644 index 0000000..1ed7416 --- /dev/null +++ b/pallets/exodus/src/exodus.rs @@ -0,0 +1,496 @@ +use crate::{EncryptedMessage, FrostError}; +use ghost_networks::NetworkCurve; + +use chacha20poly1305::{ + aead::{self, Aead, AeadCore, KeyInit}, + Key as EncryptionKey, + Nonce as EncryptionNonce, + ChaCha20Poly1305 as EncryptionCipher, +}; + +use hkdf::Hkdf as KeyDerivation; +use sha2::{Digest, Sha256 as Hashing}; + +use rand_chacha::rand_core::{CryptoRng, RngCore}; +use sp_std::collections::btree_map::BTreeMap; + +macro_rules! with_ciphersuite { + ($curve:expr, |$alias:ident| $body:block) => { + match $curve { + ExodusCurve::Secp256k1 => { + use frost_secp256k1 as $alias; + $body + }, + NetworkCurve::Ed25519 => { + use frost_ed25519 as $alias; + $body + + } + } + } +} + +impl ExodusCurve { + pub fn dkg_verify_proof_of_knowledge( + &self, + index: u16, + round1_package_bytes: &[u8], + ) -> Result<(), FrostError> { + with_ciphersuite!(self, |f| { + let identifier = index.try_into().map_err(|_| FrostError::InvalidParticipantId)?; + let round1_package = f::keys::dkg::round1::Package::deserialize(round1_package_bytes) + .map_err(|_| FrostError::DeserializationError)?; + + frost_core::keys::dkg::verify_proof_of_knowledge( + identifier, + &round1_package.commitment(), + &round1_package.proof_of_knowledge(), + ).map_err(|_| FrostError::InvalidProofOfKnowledge) + }) + } + + pub fn verify_signature_share( + &self, + index: u16, + verifying_share_bytes: &[u8], + signature_share_bytes: &[u8], + signing_package_bytes: &[u8], + verifying_key_bytes: &[u8], + ) -> Result<(), FrostError> { + with_ciphersuite!(self, |f| { + let identifier = index.try_into().map_err(|_| FrostError::InvalidParticipantId)?; + let verifying_share = f::keys::VerifyingShare::deserialize(verifying_share_bytes) + .map_err(|_| FrostError::DeserializationError)?; + + let signature_share = f::round2::SignatureShare::deserialize(signature_share_bytes) + .map_err(|_| FrostError::DeserializationError)?; + + let signing_package = f::SigningPackage::deserialize(signing_package_bytes) + .map_err(|_| FrostError::DeserializationError)?; + + let verifying_key = f::VerifyingKey::deserialize(verifying_key_bytes) + .map_err(|_| FrostError::DeserializationError)?; + + frost_core::verify_signature_share( + identifier, + &verifying_share, + &signature_share, + &signing_package, + &verifying_key, + ).map_err(|err| match err { + f::Error::IdentityCommitment => FrostError::IdentityCommitment, + f::Error::UnknownIdentifier => FrostError::UnknownIdentifier, + f::Error::DuplicatedIdentifier => FrostError::DuplicatedIdentifier, + f::Error::InvalidSignatureShare { .. } => FrostError::InvalidSignatureShare, + _ => FrostError::Unknown, + }) + }) + } + + pub fn aggregate_signature( + &self, + signing_package_bytes: &[u8], + signature_shares_bytes: &BTreeMap>, + pubkeys_bytes: &[u8], + ) -> Result, FrostError> { + with_ciphersuite!(self, |f| { + let signing_package = f::SigningPackage::deserialize(signing_package_bytes) + .map_err(|_| FrostError::DeserializationError)?; + + let mut signature_shares = BTreeMap::new(); + for (&index, signature_share_bytes) in signature_shares_bytes.iter() { + let identifier = index.try_into().map_err(|_| FrostError::InvalidParticipantId)?; + let signature_share = f::round2::SignatureShare::deserialize(signature_share_bytes) + .map_err(|_| FrostError::DeserializationError)?; + signature_shares.insert(identifier, signature_share); + } + + let pubkeys = f::keys::PublicKeyPackage::deserialize(pubkeys_bytes) + .map_err(|_| FrostError::DeserializationError)?; + + let signature = f::aggregate(&signing_package, &signature_shares, &pubkeys) + .map_err(|err| match err { + f::Error::UnknownIdentifier => FrostError::UnknownIdentifier, + f::Error::SerializationError => FrostError::SerializationError, + f::Error::IdentityCommitment => FrostError::IdentityCommitment, + f::Error::IncorrectNumberOfIdentifiers => FrostError::IncorrectNumberOfIdentifiers, + f::Error::DuplicatedIdentifier => FrostError::DuplicatedIdentifier, + f::Error::InvalidSecretShare { .. } => FrostError::InvalidSecretShare, + f::Error::InvalidSignature { .. } => FrostError::InvalidSignature, + _ => FrostError::Unknown, + })?; + + let signature_bytes = signature.serialize() + .map_err(|_| FrostError::SerializationError)?; + + Ok(signature_bytes) + }) + } + + pub fn is_signature_valid( + &self, + pubkey_bytes: &[u8], + signature_bytes: &[u8], + message: &[u8], + ) -> bool { + with_ciphersuite!(self, |f| { + let get_verification_result = || -> Result<(), FrostError> { + let pubkeys = f::keys::PublicKeyPackage::deserialize(pubkey_bytes) + .map_err(|_| FrostError::DeserializationError)?; + + let signature = f::Signature::deserialize(signature_bytes) + .map_err(|_| FrostError::DeserializationError)?; + + pubkeys.verifying_key() + .verify(message, &signature) + .map_err(|_| FrostError::InvalidSignature)?; + + Ok(()) + }; + + get_verification_result().is_ok() + }) + } + + pub fn dkg_part1( + &self, + index: u16, + max_signers: u16, + min_signers: u16, + mut rng: R, + ) -> Result<(Vec, Vec), FrostError> { + with_ciphersuite!(self, |f| { + let identifier = index.try_into().map_err(|_| FrostError::InvalidParticipantId)?; + let (round1_secret_package, round1_package) = f::keys::dkg::part1( + identifier, + max_signers, + min_signers, + &mut rng, + ).map_err(|err| { + match err { + f::Error::InvalidMinSigners => FrostError::InvalidMinSigners(min_signers, max_signers), + f::Error::InvalidMaxSigners => FrostError::InvalidMaxSigners(max_signers), + f::Error::DKGNotSupported => FrostError::DKGNotSupported(*self), + _ => FrostError::Unknown, + } + })?; + + let round1_secret_package_bytes = round1_secret_package.serialize() + .map_err(|_| FrostError::SerializationError)?; + + + let round1_package_bytes = round1_package.serialize() + .map_err(|_| FrostError::SerializationError)?; + + Ok((round1_secret_package_bytes, round1_package_bytes)) + }) + } + + fn convert_identifier_to_index( + identifier_bytes: Vec + ) -> Result { + const SIZE: usize = core::mem::size_of::(); + + let start = identifier_bytes.len().checked_sub(SIZE) + .ok_or(FrostError::InvalidParticipantId)?; + + let bytes_array = identifier_bytes.get(start..) + .and_then(|slice| slice.try_into().ok()) + .ok_or(FrostError::InvalidParticipantId)?; + + Ok(u16::from_be_bytes(bytes_array)) + } + + pub fn dkg_part2( + &self, + round1_secret_package_bytes: &[u8], + round1_packages_bytes: &BTreeMap>, + ) -> Result<(Vec, BTreeMap>), FrostError> { + with_ciphersuite!(self, |f| { + let round1_secret_package = f::keys::dkg::round1::SecretPackage::deserialize(round1_secret_package_bytes) + .map_err(|_| FrostError::DeserializationError)?; + + let mut round1_packages = BTreeMap::new(); + for (&index, round1_package_bytes) in round1_packages_bytes.iter() { + let identifier = index.try_into().map_err(|_| FrostError::InvalidParticipantId)?; + let round1_package = f::keys::dkg::round1::Package::deserialize(round1_package_bytes) + .map_err(|_| FrostError::DeserializationError)?; + + round1_packages.insert(identifier, round1_package); + } + + let (round2_secret_package, round2_packages) = f::keys::dkg::part2( + round1_secret_package, + &round1_packages, + ).map_err(|err| match err { + f::Error::IncorrectNumberOfCommitments => FrostError::IncorrectNumberOfCommitments, + f::Error::IncorrectNumberOfPackages => FrostError::IncorrectNumberOfPackages, + f::Error::InvalidProofOfKnowledge { .. } => FrostError::InvalidProofOfKnowledge, + _ => FrostError::Unknown, + })?; + + let round2_secret_package_bytes = round2_secret_package.serialize() + .map_err(|_| FrostError::SerializationError)?; + + let mut round2_packages_bytes = BTreeMap::new(); + for (participant_identifier, round2_package) in round2_packages.iter() { + let index = Self::convert_identifier_to_index( + participant_identifier.serialize(), + )?; + + let round2_package_bytes = round2_package.serialize() + .map_err(|_| FrostError::SerializationError)?; + + round2_packages_bytes.insert(index as u16, round2_package_bytes); + } + + Ok((round2_secret_package_bytes, round2_packages_bytes)) + }) + } + + pub fn dkg_encrypt_round2_package( + &self, + cipher: &EncryptionCipher, + associated_data: &[u8], + round2_package_bytes: &[u8], + mut rng: R, + ) -> Result<(Vec, Vec), FrostError> { + let payload = aead::Payload { + msg: round2_package_bytes, + aad: associated_data, + }; + + let nonce = EncryptionCipher::generate_nonce(&mut rng); + let ciphertext = cipher.encrypt(&nonce, payload) + .map_err(|_| FrostError::EncryptionFailed)?; + + Ok((ciphertext, nonce.to_vec())) + } + + pub fn dkg_decrypt_round2_package( + &self, + cipher: &EncryptionCipher, + encrypted_message: &EncryptedMessage, + associated_data: &[u8], + ) -> Result, FrostError> { + let nonce = EncryptionNonce::from_iter(encrypted_message.nonce.iter().cloned()); + let payload = aead::Payload { + msg: &encrypted_message.ciphertext, + aad: associated_data, + }; + + cipher.decrypt(&nonce, payload) + .map_err(|_| FrostError::DecryptionFailed) + } + + pub fn prepare_additional_info( + &self, + sender_index: u16, + receiver_index: u16, + session_index: u16, + ) -> Vec { + let mut additional_info = match self { + ExodusCurve::Secp256k1 => b"EXODUS-SECP256K1-DKG-V1".to_vec(), + }; + + additional_info.extend_from_slice(&sender_index.to_be_bytes()); + additional_info.extend_from_slice(&receiver_index.to_be_bytes()); + additional_info.extend_from_slice(&session_index.to_be_bytes()); + + additional_info + } + + pub fn prepare_cipher_from_keys( + &self, + round1_receiver_package_bytes: &[u8], + round1_secret_package_bytes: &[u8], + additional_info: &[u8], + ) -> Result { + with_ciphersuite!(self, |f| { + let coefficients = + f::keys::dkg::round1::SecretPackage::deserialize(round1_secret_package_bytes) + .map_err(|_| FrostError::DeserializationError)? + .coefficients(); + + let sender_secret_key = coefficients + .first() + .ok_or(FrostError::IncorrectNumberOfCoefficients)?; + + let receiver_public_key = + f::keys::dkg::round1::Package::deserialize(round1_receiver_package_bytes) + .map_err(|_| FrostError::DeserializationError)? + .commitment() + .coefficients() + .first() + .ok_or(FrostError::MissingCommitment)? + .value(); + + let shared_secret_bytes = f::VerifyingKey::new(receiver_public_key * sender_secret_key) + .serialize() + .map_err(|_| FrostError::SerializationError)?; + + let salt = Hashing::digest(&additional_info); + let hk = KeyDerivation::::new(Some(&salt), &shared_secret_bytes); + + let mut encryption_key = EncryptionKey::default(); + hk.expand(additional_info, &mut encryption_key) + .map_err(|_| FrostError::HKDFFailed)?; + + let cipher = EncryptionCipher::new_from_slice(&encryption_key) + .expect("could not happen; length is already correct qed"); + + Ok(cipher) + }) + } + + pub fn dkg_verify_private_package( + &self, + index: u16, + round1_package_bytes: &[u8], + round2_package_bytes: &[u8], + ) -> Result<(), FrostError> { + with_ciphersuite!(self, |f| { + let identifier = index.try_into().map_err(|_| FrostError::InvalidParticipantId)?; + let commitment = f::keys::dkg::round1::Package::deserialize(round1_package_bytes) + .map(|round1_package| round1_package.commitment().clone()) + .map_err(|_| FrostError::DeserializationError)?; + + let secret_share = f::keys::dkg::round2::Package::deserialize(round2_package_bytes) + .map(|round2_package| { + let signing_share = round2_package.signing_share().clone(); + f::keys::SecretShare::new(identifier, signing_share, commitment) + }) + .map_err(|_| FrostError::DeserializationError)?; + + let _ = secret_share.verify().map_err(|_| FrostError::InvalidSecretShare)?; + + Ok(()) + }) + } + + pub fn dkg_part3( + &self, + round2_secret_package_bytes: &[u8], + round1_packages_bytes: &BTreeMap>, + round2_packages_bytes: &BTreeMap>, + ) -> Result<(Vec, Vec), FrostError> { + with_ciphersuite!(self, |f| { + let round2_secret_package = f::keys::dkg::round2::SecretPackage::deserialize(round2_secret_package_bytes) + .map_err(|_| FrostError::DeserializationError)?; + + let mut round1_packages = BTreeMap::new(); + for (&index, round1_package_bytes) in round1_packages_bytes.iter() { + let identifier = index.try_into().map_err(|_| FrostError::InvalidParticipantId)?; + let round1_package = f::keys::dkg::round1::Package::deserialize(round1_package_bytes) + .map_err(|_| FrostError::DeserializationError)?; + round1_packages.insert(identifier, round1_package); + } + + let mut round2_packages = BTreeMap::new(); + for (&index, round2_package_bytes) in round2_packages_bytes.iter() { + let identifier = index.try_into().map_err(|_| FrostError::InvalidParticipantId)?; + let round2_package = f::keys::dkg::round2::Package::deserialize(round2_package_bytes) + .map_err(|_| FrostError::DeserializationError)?; + round2_packages.insert(identifier, round2_package); + } + + let (key_package, pubkey_package) = f::keys::dkg::part3( + &round2_secret_package, + &round1_packages, + &round2_packages, + ).map_err(|err| match err { + f::Error::IncorrectNumberOfPackages => FrostError::IncorrectNumberOfPackages, + f::Error::IncorrectPackage => FrostError::IncorrectPackage, + f::Error::PackageNotFound => FrostError::PackageNotFound, + f::Error::InvalidSecretShare { .. } => FrostError::InvalidSecretShare, + f::Error::IncorrectNumberOfCommitments => FrostError::IncorrectNumberOfCommitments, + _ => FrostError::Unknown, + })?; + + let key_package_bytes = key_package.serialize() + .map_err(|_| FrostError::SerializationError)?; + + let pubkey_package_bytes = pubkey_package.serialize() + .map_err(|_| FrostError::SerializationError)?; + + Ok((key_package_bytes, pubkey_package_bytes)) + }) + } + + pub fn generate_nonce( + &self, + secret_key_package_bytes: &[u8], + mut rng: R + ) -> Result<(Vec, Vec), FrostError> { + with_ciphersuite!(self, |f| { + let signing_share = f::keys::KeyPackage::deserialize(secret_key_package_bytes) + .map(|key_package| *key_package.signing_share()) + .map_err(|_| FrostError::DeserializationError)?; + + let (signing_nonces, signing_commitments) = + f::round1::commit(&signing_share, &mut rng); + + let signing_nonces_bytes = signing_nonces.serialize() + .map_err(|_| FrostError::SerializationError)?; + + let signing_commitments_bytes = signing_commitments.serialize() + .map_err(|_| FrostError::SerializationError)?; + + Ok((signing_nonces_bytes, signing_commitments_bytes)) + }) + } + + pub fn generate_signing_package( + &self, + commitments_map_bytes: &BTreeMap>, + message: &[u8], + ) -> Result, FrostError> { + with_ciphersuite!(self, |f| { + let mut commitments_map = BTreeMap::new(); + for (&index, commitment_bytes) in commitments_map_bytes.iter() { + let identifier = index.try_into().map_err(|_| FrostError::InvalidParticipantId)?; + let commitment = f::round1::SigningCommitments::deserialize(commitment_bytes) + .map_err(|_| FrostError::DeserializationError)?; + commitments_map.insert(identifier, commitment); + } + + f::SigningPackage::new(commitments_map, message) + .serialize() + .map_err(|_| FrostError::SerializationError) + }) + } + + pub fn sign_message( + &self, + signing_package_bytes: &[u8], + signer_nonces_bytes: &[u8], + secret_key_share_bytes: &[u8] + ) -> Result, FrostError> { + with_ciphersuite!(self, |f| { + let signing_package = f::SigningPackage::deserialize(signing_package_bytes) + .map_err(|_| FrostError::DeserializationError)?; + + let signer_nonces = f::round1::SigningNonces::deserialize(signer_nonces_bytes) + .map_err(|_| FrostError::DeserializationError)?; + + let secret_key_share = f::keys::KeyPackage::deserialize(secret_key_share_bytes) + .map_err(|_| FrostError::DeserializationError)?; + + let signature_share_bytes = f::round2::sign(&signing_package, &signer_nonces, &secret_key_share) + .map(|signature_share| signature_share.serialize()) + .map_err(|err| match err { + f::Error::IncorrectNumberOfCommitments => FrostError::IncorrectNumberOfCommitments, + f::Error::MissingCommitment => FrostError::MissingCommitment, + f::Error::IncorrectCommitment => FrostError::IncorrectCommitment, + f::Error::SerializationError => FrostError::SerializationError, + f::Error::UnknownIdentifier => FrostError::UnknownIdentifier, + f::Error::IdentityCommitment => FrostError::IdentityCommitment, + f::Error::DuplicatedIdentifier => FrostError::DuplicatedIdentifier, + f::Error::IncorrectNumberOfIdentifiers => FrostError::IncorrectNumberOfIdentifiers, + _ => FrostError::Unknown, + })?; + + Ok(signature_share_bytes) + }) + } +} diff --git a/pallets/exodus/src/impls/converter.rs b/pallets/exodus/src/impls/converter.rs new file mode 100644 index 0000000..6b5e89f --- /dev/null +++ b/pallets/exodus/src/impls/converter.rs @@ -0,0 +1,26 @@ +use crate::{AuthIndex, ExodusError, NetworkCurve}; + +use ghost_traits::exodus::IdentifierConverter; + +impl IdentifierConverter for NetworkCurve { + fn convert_identifier_to_index( + identifier_bytes: &[u8], + ) -> Result { + const SIZE: usize = core::mem::size_of::(); + + let start = identifier_bytes.len().checked_sub(SIZE) + .ok_or(ExodusError::InvalidParticipantId)?; + + let bytes_array = identifier_bytes.get(start..) + .and_then(|slice| slice.try_into().ok()) + .ok_or(ExodusError::InvalidParticipantId)?; + + AuthIndex::from_be_bytes(bytes_array) + .checked_sub(1) + .ok_or(ExodusError::InvalidParticipantId) + } + + fn non_zero_index(index: AuthIndex) -> Result { + index.checked_add(1).ok_or(ExodusError::InvalidParticipantId) + } +} diff --git a/pallets/exodus/src/impls/dkg.rs b/pallets/exodus/src/impls/dkg.rs new file mode 100644 index 0000000..10938b6 --- /dev/null +++ b/pallets/exodus/src/impls/dkg.rs @@ -0,0 +1,351 @@ +use sp_std::{collections::btree_map::BTreeMap, vec::Vec, result::Result}; +use rand_chacha::rand_core::{CryptoRng, RngCore}; + +use ghost_traits::exodus::{ + DistributedKeyGeneration, IdentifierConverter, MerkleTreeBuilder, +}; + +use crate::{AuthIndex, ExodusError, NetworkCurve}; + +impl DistributedKeyGeneration for NetworkCurve { + type RoundPackages = BTreeMap>; + + type Part1Result = Result<(Vec, Vec), ExodusError>; + type Part2Result = Result<(Vec, BTreeMap>), ExodusError>; + type Part3Result = Result< + ( + Vec, Vec, + Vec<>::Hash>, + >::Hash, + ), + ExodusError + >; + + fn dkg_narrow_round_max_signers( + &self, + secret_package_bytes: &[u8], + new_max_signers: AuthIndex, + round_number: u8, + ) -> Result, ExodusError> { + with_ciphersuite!(self, |f| { + macro_rules! process_secret_package { + ( + $package_type:ty, + $field1:ident : $action1:tt, + $field2:ident : $action2:tt, + ) => {{ + let secret_package = <$package_type>::deserialize(secret_package_bytes) + .map_err(|_| ExodusError::DeserializationError)?; + + let min_signers = *secret_package.min_signers(); + + frost_core::keys::validate_num_of_signers(min_signers, new_max_signers) + .map_err(|err| match err { + f::Error::InvalidMinSigners => { + ExodusError::InvalidMinSigners(min_signers, new_max_signers) + }, + f::Error::InvalidMaxSigners => { + ExodusError::InvalidMaxSigners(new_max_signers) + }, + _ => ExodusError::Unknown, + })?; + + macro_rules! apply_action { + ($field:ident, clone) => { secret_package.$field().clone() }; + ($field:ident, value) => { secret_package.$field() }; + } + + let new_secret_package = <$package_type>::new( + secret_package.identifier().clone(), + apply_action!($field1, $action1), + apply_action!($field2, $action2), + min_signers, + new_max_signers, + ); + + new_secret_package.serialize() + .map_err(|_| ExodusError::SerializationError) + }}; + } + + match round_number { + 1 => process_secret_package!( + f::keys::dkg::round1::SecretPackage, + coefficients: value, + commitment: clone, + ), + 2 => process_secret_package!( + f::keys::dkg::round2::SecretPackage, + commitment: clone, + secret_share: value, + ), + _ => Err(ExodusError::UnexpectedRound), + } + }) + } + + fn dkg_verify_proof_of_knowledge( + &self, + index: AuthIndex, + round1_package_bytes: &[u8], + ) -> Result { + with_ciphersuite!(self, |f| { + let identifier = Self::non_zero_index(index)? + .try_into() + .map_err(|_| ExodusError::InvalidParticipantId)?; + + let round1_package = f::keys::dkg::round1::Package::deserialize(round1_package_bytes) + .map_err(|_| ExodusError::DeserializationError)?; + + let commitment = round1_package.commitment(); + + frost_core::keys::dkg::verify_proof_of_knowledge( + identifier, + &commitment, + &round1_package.proof_of_knowledge(), + ).map_err(|_| ExodusError::InvalidProofOfKnowledge)?; + + let coefficients = commitment.coefficients(); + let coefficients_len = coefficients.len() as AuthIndex; + + Ok(coefficients_len) + }) + } + + fn dkg_part1( + &self, + index: AuthIndex, + max_signers: AuthIndex, + min_signers: AuthIndex, + mut rng: R, + ) -> Self::Part1Result { + with_ciphersuite!(self, |f| { + let identifier = Self::non_zero_index(index)? + .try_into() + .map_err(|_| ExodusError::InvalidParticipantId)?; + + let (round1_secret_package, round1_package) = f::keys::dkg::part1( + identifier, + max_signers, + min_signers, + &mut rng, + ).map_err(|err| { + match err { + f::Error::InvalidMinSigners => ExodusError::InvalidMinSigners(min_signers, max_signers), + f::Error::InvalidMaxSigners => ExodusError::InvalidMaxSigners(max_signers), + f::Error::DKGNotSupported => ExodusError::DKGNotSupported(*self), + _ => ExodusError::Unknown, + } + })?; + + let round1_secret_package_bytes = round1_secret_package.serialize() + .map_err(|_| ExodusError::SerializationError)?; + + let round1_package_bytes = round1_package.serialize() + .map_err(|_| ExodusError::SerializationError)?; + + Ok((round1_secret_package_bytes, round1_package_bytes)) + }) + } + + fn dkg_part2( + &self, + round1_secret_package_bytes: &[u8], + round1_packages_bytes: &Self::RoundPackages, + ) -> Self::Part2Result { + with_ciphersuite!(self, |f| { + type InnerGroup = ::Group; + type InnerField = ::Field; + + let round1_secret_package = f::keys::dkg::round1::SecretPackage::deserialize(round1_secret_package_bytes) + .map_err(|_| ExodusError::DeserializationError)?; + + let round1_packages = round1_packages_bytes + .iter() + .map(|(&index, round1_package_bytes)| { + let identifier = Self::non_zero_index(index)? + .try_into() + .map_err(|_| ExodusError::InvalidParticipantId)?; + + let round1_package = f::keys::dkg::round1::Package::deserialize(round1_package_bytes) + .map_err(|_| ExodusError::DeserializationError)?; + + Ok((identifier, round1_package)) + }) + .collect::, ExodusError>>()?; + + let (round2_secret_package, round2_packages) = f::keys::dkg::part2( + round1_secret_package, + &round1_packages, + ).map_err(|err| match err { + f::Error::IncorrectNumberOfCommitments => ExodusError::IncorrectNumberOfCommitments, + f::Error::IncorrectNumberOfPackages => ExodusError::IncorrectNumberOfPackages, + f::Error::InvalidProofOfKnowledge { .. } => ExodusError::InvalidProofOfKnowledge, + _ => ExodusError::Unknown, + })?; + + let round2_secret_package_bytes = round2_secret_package.serialize() + .map_err(|_| ExodusError::SerializationError)?; + + let round2_packages_bytes = round2_packages + .iter() + .map(|(id, round2_package)| { + let id_scalar = id.to_scalar(); + let id_bytes = ::serialize(&id_scalar); + + let index = Self::convert_identifier_to_index(id_bytes.as_ref())?; + let round2_package_bytes = round2_package.serialize() + .map_err(|_| ExodusError::SerializationError)?; + + Ok((index, round2_package_bytes)) + }) + .collect::>, ExodusError>>()?; + + Ok((round2_secret_package_bytes, round2_packages_bytes)) + }) + } + + fn dkg_verify_private_package( + &self, + index: AuthIndex, + round1_package_bytes: &[u8], + round2_package_bytes: &[u8], + ) -> Result<(), ExodusError> { + with_ciphersuite!(self, |f| { + let identifier = Self::non_zero_index(index)? + .try_into() + .map_err(|_| ExodusError::InvalidParticipantId)?; + + let commitment = f::keys::dkg::round1::Package::deserialize(round1_package_bytes) + .map(|round1_package| round1_package.commitment().clone()) + .map_err(|_| ExodusError::DeserializationError)?; + + let secret_share = f::keys::dkg::round2::Package::deserialize(round2_package_bytes) + .map(|round2_package| { + let signing_share = round2_package.signing_share().clone(); + f::keys::SecretShare::new(identifier, signing_share, commitment) + }) + .map_err(|_| ExodusError::DeserializationError)?; + + let _ = secret_share.verify().map_err(|_| ExodusError::InvalidSecretShare)?; + + Ok(()) + }) + } + + fn dkg_part3( + &self, + authority_index: AuthIndex, + round2_secret_package_bytes: &[u8], + round1_packages_bytes: &Self::RoundPackages, + round2_packages_bytes: &Self::RoundPackages, + ) -> Self::Part3Result { + with_ciphersuite!(self, |f| { + let round2_secret_package = f::keys::dkg::round2::SecretPackage::deserialize(round2_secret_package_bytes) + .map_err(|_| ExodusError::DeserializationError)?; + + let round1_packages = round1_packages_bytes + .iter() + .map(|(&index, round1_package_bytes)| { + let identifier = Self::non_zero_index(index)? + .try_into() + .map_err(|_| ExodusError::InvalidParticipantId)?; + + let round1_package = f::keys::dkg::round1::Package::deserialize(round1_package_bytes) + .map_err(|_| ExodusError::DeserializationError)?; + + Ok((identifier, round1_package)) + }) + .collect::, ExodusError>>()?; + + let round2_packages = round2_packages_bytes + .iter() + .map(|(&index, round2_package_bytes)| { + let identifier = Self::non_zero_index(index)? + .try_into() + .map_err(|_| ExodusError::InvalidParticipantId)?; + + let round2_package = f::keys::dkg::round2::Package::deserialize(round2_package_bytes) + .map_err(|_| ExodusError::DeserializationError)?; + + Ok((identifier, round2_package)) + }) + .collect::, ExodusError>>()?; + + let (key_package, pubkey_package) = f::keys::dkg::part3( + &round2_secret_package, + &round1_packages, + &round2_packages, + ).map_err(|err| match err { + f::Error::IncorrectNumberOfPackages => ExodusError::IncorrectNumberOfPackages, + f::Error::IncorrectPackage => ExodusError::IncorrectPackage, + f::Error::PackageNotFound => ExodusError::PackageNotFound, + f::Error::InvalidSecretShare { .. } => ExodusError::InvalidSecretShare, + f::Error::IncorrectNumberOfCommitments => ExodusError::IncorrectNumberOfCommitments, + _ => ExodusError::Unknown, + })?; + + let key_package_bytes = key_package.serialize() + .map_err(|_| ExodusError::SerializationError)?; + + let verifying_shares_merkle_tree = + Self::build_merkle_tree( + pubkey_package.verifying_shares(), + |verifying_share| verifying_share.serialize() + .map_err(|_| ExodusError::SerializationError) + )?; + + let (_, merkle_proof) = + Self::generate_merkle_proof_from_tree( + pubkey_package.verifying_shares(), + &verifying_shares_merkle_tree, + authority_index, + |verifying_share| verifying_share.serialize() + .map_err(|_| ExodusError::SerializationError) + )?; + + let merkle_root = verifying_shares_merkle_tree + .last() + .copied() + .ok_or(ExodusError::IncorrectNumberOfPackages)?; + + let verifying_key_bytes = pubkey_package.verifying_key() + .serialize() + .map_err(|_| ExodusError::SerializationError)?; + + Ok(( + key_package_bytes, + verifying_key_bytes, + merkle_proof, + merkle_root, + )) + }) + } + + fn dkg_derive_verifying_share( + &self, + secret_key_package_bytes: &[u8], + ) -> Result, ExodusError> { + with_ciphersuite!(self, |f| { + let secret_key_package = + f::keys::KeyPackage::deserialize(secret_key_package_bytes) + .map_err(|_| ExodusError::DeserializationError)?; + + secret_key_package + .verifying_share() + .serialize() + .map_err(|_| ExodusError::SerializationError) + }) + } + + fn dkg_decompress_key( + &self, + verifying_key_bytes: &[u8], + ) -> Result, ExodusError> { + with_ciphersuite!(self, |f| { + let verifying_key = f::VerifyingKey::deserialize(verifying_key_bytes) + .map_err(|_| ExodusError::DeserializationError)?; + decomporess_key(verifying_key) + }) + } +} diff --git a/pallets/exodus/src/impls/ecdh.rs b/pallets/exodus/src/impls/ecdh.rs new file mode 100644 index 0000000..1ed16ce --- /dev/null +++ b/pallets/exodus/src/impls/ecdh.rs @@ -0,0 +1,123 @@ +use sp_std::{ + vec::Vec, + result::Result, + marker::PhantomData, +}; +use rand_chacha::rand_core::{CryptoRng, RngCore}; +use ghost_traits::exodus::EllipticCurveDiffieHellman; + +use hkdf::Hkdf as KeyDerivationFunction; +use sha2::Sha256 as Hashing; + +use chacha20poly1305::{ + aead::{self, Aead, KeyInit}, + Key as EncryptionKey, + Nonce as EncryptionNonce, + ChaCha20Poly1305 as EncryptionCipher, +}; + +use crate::pallet::Config; +use crate::{AuthIndex, EncryptionData, ExodusError, NetworkCurve, DkgIndex}; + +impl EllipticCurveDiffieHellman, DkgIndex, ExodusError> for NetworkCurve { + fn ecdh_encrypt_package( + &self, + cipher: &EncryptionCipher, + aad: &[u8], + msg: &[u8], + mut rng: R, + ) -> Result, ExodusError> { + let payload = aead::Payload { msg, aad }; + + let mut nonce_bytes = [0u8; crate::ENCRYPTION_NONCE_MAX_BYTES as usize]; + rng.fill_bytes(&mut nonce_bytes); + let nonce = EncryptionNonce::from_slice(&nonce_bytes); + + let ciphertext = cipher.encrypt(&nonce, payload) + .map_err(|_| ExodusError::EncryptionFailed)?; + + EncryptionData::try_new(ciphertext, nonce.as_slice()) + } + + fn ecdh_decrypt_package( + &self, + cipher: &EncryptionCipher, + encrypted_data: &EncryptionData, + aad: &[u8], + ) -> Result, ExodusError> { + let nonce = EncryptionNonce::from_iter( + encrypted_data.nonce.iter().cloned() + ); + let payload = aead::Payload { msg: &encrypted_data.ciphertext, aad }; + cipher.decrypt(&nonce, payload).map_err(|_| ExodusError::DecryptionFailed) + } + + fn ecdh_prepare_additional_info( + &self, + sender_index: AuthIndex, + receiver_index: AuthIndex, + dkg_index: DkgIndex, + _marker: PhantomData>, + ) -> Vec { + let mut additional_info = match self { + NetworkCurve::Secp256k1 => b"EXODUS-SECP256K1-DKG-V1".to_vec(), + NetworkCurve::Ed25519 => b"EXODUS-ED25519-DKG-V1".to_vec(), + }; + + additional_info.extend_from_slice(&sender_index.to_be_bytes()); + additional_info.extend_from_slice(&receiver_index.to_be_bytes()); + additional_info.extend_from_slice(&dkg_index.to_be_bytes()); + + additional_info + } + + fn ecdh_get_cipher_from_keys( + &self, + public: &[u8], + secret: &[u8], + additional_info: &[u8], + _marker: PhantomData>, + ) -> Result { + with_ciphersuite!(self, |f| { + let secret_package = f::keys::dkg::round1::SecretPackage::deserialize(secret) + .map_err(|_| ExodusError::DeserializationError)?; + let public_package = f::keys::dkg::round1::Package::deserialize(public) + .map_err(|_| ExodusError::DeserializationError)?; + + let coefficients = secret_package.coefficients(); + let local_sk = coefficients.first() + .ok_or(ExodusError::IncorrectNumberOfCoefficients)?; + let remote_pk = public_package.commitment().coefficients().first() + .ok_or(ExodusError::MissingCommitment)?.value(); + + let shared_secret_bytes = f::VerifyingKey::new(remote_pk * local_sk) + .serialize() + .map_err(|_| ExodusError::SerializationError)?; + + let local_pk: f::VerifyingKey = f::SigningKey::from_scalar(*local_sk) + .map_err(|_| ExodusError::MalformedSigningKey)? + .into(); + + let local_pk_bytes = local_pk.serialize() + .map_err(|_| ExodusError::SerializationError)?; + let remote_pk_bytes = f::VerifyingKey::new(remote_pk) + .serialize() + .map_err(|_| ExodusError::SerializationError)?; + + let mut keys = [local_pk_bytes, remote_pk_bytes]; + keys.sort(); + + let salt = keys.concat(); + let hk = KeyDerivationFunction::::new(Some(&salt), &shared_secret_bytes); + + let mut encryption_key = EncryptionKey::default(); + hk.expand(additional_info, &mut encryption_key) + .map_err(|_| ExodusError::HKDFFailed)?; + + let cipher = EncryptionCipher::new_from_slice(&encryption_key) + .expect("could not happen; length is already correct qed"); + + Ok(cipher) + }) + } +} diff --git a/pallets/exodus/src/impls/frost.rs b/pallets/exodus/src/impls/frost.rs new file mode 100644 index 0000000..7230b93 --- /dev/null +++ b/pallets/exodus/src/impls/frost.rs @@ -0,0 +1,430 @@ +use ghost_traits::exodus::{ + IdentifierConverter, MerkleTreeBuilder, + FlexibleRoundOptimizedSchnorrThresholdSignature, +}; +use sp_std::{ + vec::Vec, result::Result, + collections::{btree_set::BTreeSet, btree_map::BTreeMap}, +}; + +use rand_chacha::rand_core::{CryptoRng, RngCore}; +use ghost_helpers::networks::NetworkCurve; +use crate::{AuthIndex, ExodusHash, ExodusError}; + +impl FlexibleRoundOptimizedSchnorrThresholdSignature for NetworkCurve { + type Packages<'a> = BTreeMap; + type NoncePackages<'a> = BTreeMap; + + fn header_bytes_len(&self) -> usize { 5 } + + fn element_bytes_len(&self) -> usize { + match self { + NetworkCurve::Secp256k1 => 33, + NetworkCurve::Ed25519 => 32, + } + } + + fn scalar_bytes_len(&self) -> usize { + match self { + NetworkCurve::Secp256k1 => 32, + NetworkCurve::Ed25519 => 32, + } + } + + fn verify_signature_share( + &self, + index: AuthIndex, + participants_iter: impl Iterator, + signature_share_bytes: &[u8], + binding_factor_bytes: &[u8], + nonce_hiding_bytes: &[u8], + nonce_binding_bytes: &[u8], + group_commitment_bytes: &[u8], + verifying_share_bytes: &[u8], + verifying_key_bytes: &[u8], + message: &[u8], + ) -> Result<(), ExodusError> { + with_ciphersuite!(self, |f| { + type InnerGroup = ::Group; + type InnerField = ::Field; + + let identifier = Self::non_zero_index(index)? + .try_into() + .map_err(|_| ExodusError::InvalidParticipantId)?; + + let identifier_set = participants_iter + .filter_map(|auth_index| { + let non_zero_index = Self::non_zero_index(auth_index).ok()?; + non_zero_index.try_into().ok() + }) + .collect::>(); + + let lambda_i = + frost_core::compute_lagrange_coefficient(&identifier_set, None, identifier) + .map_err(|err| match err { + f::Error::IncorrectNumberOfIdentifiers => ExodusError::IncorrectNumberOfIdentifiers, + f::Error::UnknownIdentifier => ExodusError::UnknownIdentifier, + f::Error::DuplicatedIdentifier => ExodusError::DuplicatedIdentifier, + _ => ExodusError::Unknown, + })?; + + let signature_share = + f::round2::SignatureShare::deserialize(signature_share_bytes) + .map_err(|_| ExodusError::DeserializationError)?; + + let group_commitment_serialization = + ::Serialization::try_from(group_commitment_bytes.as_ref()) + .map_err(|_| ExodusError::DeserializationError)?; + + let group_commitment_element = + ::deserialize(&group_commitment_serialization) + .map_err(|_| ExodusError::DeserializationError)?; + + let binding_factor_serialization = + ::Serialization::try_from(binding_factor_bytes.as_ref()) + .map_err(|_| ExodusError::DeserializationError)?; + + let binding_factor_scalar = + ::deserialize(&binding_factor_serialization) + .map_err(|_| ExodusError::DeserializationError)?; + + let verifying_share = + f::keys::VerifyingShare::deserialize(verifying_share_bytes) + .map_err(|_| ExodusError::DeserializationError)?; + + let verifying_key = f::VerifyingKey::deserialize(verifying_key_bytes) + .map_err(|_| ExodusError::DeserializationError)?; + + let challenge = frost_core::challenge( + &group_commitment_element, + &verifying_key, + message, + ).map_err(|err| match err { + f::Error::SerializationError => ExodusError::SerializationError, + _ => ExodusError::Unknown, + })?; + + let hiding_element_serialization = + ::Serialization::try_from(nonce_hiding_bytes.as_ref()) + .map_err(|_| ExodusError::DeserializationError)?; + + let hiding_element = + ::deserialize(&hiding_element_serialization) + .map_err(|_| ExodusError::DeserializationError)?; + + let binding_element_serialization = + ::Serialization::try_from(nonce_binding_bytes.as_ref()) + .map_err(|_| ExodusError::DeserializationError)?; + + let binding_element = + ::deserialize(&binding_element_serialization) + .map_err(|_| ExodusError::DeserializationError)?; + + let r_i_element = hiding_element + (binding_element * binding_factor_scalar); + let commitment_share = + frost_core::round1::GroupCommitmentShare::from_element(r_i_element); + + signature_share.verify( + identifier, + &commitment_share, + &verifying_share, + lambda_i, + &challenge, + ).map_err(|err| match err { + f::Error::InvalidSignatureShare { .. } => { + ExodusError::InvalidSignatureShare + }, + _ => ExodusError::Unknown, + }) + }) + } + + fn accumulate_signature_scalar( + &self, + signature_scalar_bytes: &[u8], + signature_share_bytes: &[u8], + ) -> Result, ExodusError> { + with_ciphersuite!(self, |f| { + type InnerGroup = ::Group; + type InnerField = ::Field; + + let signature_scalar_serialization = + ::Serialization::try_from(signature_scalar_bytes) + .map_err(|_| ExodusError::DeserializationError)?; + + let mut signature_scalar = + ::deserialize(&signature_scalar_serialization) + .map_err(|_| ExodusError::DeserializationError)?; + + let signature_share_serialization = + ::Serialization::try_from(signature_share_bytes) + .map_err(|_| ExodusError::DeserializationError)?; + + let signature_share = + ::deserialize(&signature_share_serialization) + .map_err(|_| ExodusError::DeserializationError)?; + + signature_scalar += signature_share; + + Ok(::serialize(&signature_scalar).to_vec()) + }) + } + + fn is_signature_valid( + &self, + verifying_key_bytes: &[u8], + r_element_bytes: &[u8], + z_scalar_bytes: &[u8], + message: &[u8], + ) -> Result<(), ExodusError> { + with_ciphersuite!(self, |f| { + type InnerGroup = ::Group; + type InnerField = ::Field; + + let verifying_key = f::VerifyingKey::deserialize(verifying_key_bytes) + .map_err(|_| ExodusError::DeserializationError)?; + + let r_element_serialization = + ::Serialization::try_from(r_element_bytes) + .map_err(|_| ExodusError::DeserializationError)?; + + let r_element = + ::deserialize(&r_element_serialization) + .map_err(|_| ExodusError::DeserializationError)?; + + let z_scalar_serialization = + ::Serialization::try_from(z_scalar_bytes) + .map_err(|_| ExodusError::DeserializationError)?; + + let z_scalar = + ::deserialize(&z_scalar_serialization) + .map_err(|_| ExodusError::DeserializationError)?; + + let signature = f::Signature::new(r_element, z_scalar); + + verifying_key.verify(message, &signature) + .map_err(|_| ExodusError::InvalidSignature) + }) + } + + fn generate_nonce_commitment( + &self, + secret_key_package_bytes: &[u8], + mut rng: R + ) -> Result<(Vec, Vec, Vec), ExodusError> { + with_ciphersuite!(self, |f| { + let signing_share = + f::keys::KeyPackage::deserialize(secret_key_package_bytes) + .map(|key_package| *key_package.signing_share()) + .map_err(|_| ExodusError::DeserializationError)?; + + let (signing_nonce, signing_commitment) = + f::round1::commit(&signing_share, &mut rng); + + let signing_nonce_bytes = signing_nonce.serialize() + .map_err(|_| ExodusError::SerializationError)?; + + let hiding_nonce_bytes = signing_commitment.hiding() + .serialize() + .map_err(|_| ExodusError::SerializationError)?; + + let binding_nonce_bytes = signing_commitment.binding() + .serialize() + .map_err(|_| ExodusError::SerializationError)?; + + Ok((signing_nonce_bytes, hiding_nonce_bytes, binding_nonce_bytes)) + }) + } + + fn generate_group_commitment( + &self, + authority_index: AuthIndex, + nonce_commitments_bytes: &Self::NoncePackages<'_>, + verifying_key_bytes: &[u8], + message: &[u8], + ) -> Result<(Vec, Vec, Vec, ExodusHash), ExodusError> { + with_ciphersuite!(self, |f| { + type InnerGroup = ::Group; + type InnerField = ::Field; + + let verifying_key = f::VerifyingKey::deserialize(verifying_key_bytes) + .map_err(|_| ExodusError::DeserializationError)?; + + let signing_commitments = nonce_commitments_bytes + .iter() + .filter_map(|(&index, &(hiding_bytes, binding_bytes))| { + let non_zero_index = Self::non_zero_index(index).ok()?; + let identifier = non_zero_index.try_into().ok()?; + + let hiding_serialization = ::Serialization::try_from(hiding_bytes.as_ref()).ok()?; + let hiding_nonce = f::round1::NonceCommitment::deserialize(&hiding_serialization).ok()?; + + let binding_serialization = ::Serialization::try_from((*binding_bytes).as_ref()).ok()?; + let binding_nonce = f::round1::NonceCommitment::deserialize(&binding_serialization).ok()?; + + let commitments = f::round1::SigningCommitments::new(hiding_nonce, binding_nonce); + + Some((identifier, commitments)) + }) + .collect::>(); + + if nonce_commitments_bytes.len() != signing_commitments.len() { + return Err(ExodusError::InvalidParticipantId); + } + + let signing_package = f::SigningPackage::new(signing_commitments, message); + + let binding_factors_list_wrapped = + frost_core::compute_binding_factor_list(&signing_package, &verifying_key, &[]) + .map_err(|err| match err { + f::Error::SerializationError => ExodusError::SerializationError, + _ => ExodusError::Unknown, + })?; + + let group_commitment = + frost_core::compute_group_commitment(&signing_package, &binding_factors_list_wrapped) + .map_err(|err| match err { + f::Error::IdentityCommitment => ExodusError::IdentityCommitment, + f::Error::UnknownIdentifier => ExodusError::UnknownIdentifier, + _ => ExodusError::Unknown, + })?; + + let binding_factors_list = nonce_commitments_bytes + .iter() + .filter_map(|(&index, _)| { + let non_zero_index = Self::non_zero_index(index).ok()?; + let identifier = non_zero_index.try_into().ok()?; + + let binding_factor = binding_factors_list_wrapped + .get(&identifier) + .map(|binding_factor| { + let fixed_bytes = + ::Serialization::try_from( + binding_factor.serialize().as_slice() + ).ok()?; + ::deserialize(&fixed_bytes).ok() + }) + .flatten()?; + + Some((identifier, binding_factor)) + }) + .collect::>(); + + if nonce_commitments_bytes.len() != binding_factors_list.len() { + return Err(ExodusError::InvalidParticipantId); + } + + let binding_factor_tree = Self::build_merkle_tree( + &binding_factors_list, + |binding_factor| { + let serialized = ::serialize(binding_factor); + Ok(serialized.as_ref().to_vec()) + } + )?; + + let (self_binding_factor_bytes, binding_factors_proof) = + Self::generate_merkle_proof_from_tree( + &binding_factors_list, + &binding_factor_tree, + authority_index, + |binding_factor| { + let serialized = ::serialize(binding_factor); + Ok(serialized.as_ref().to_vec()) + } + )?; + + let group_commitment_bytes = + ::serialize(&group_commitment.to_element()) + .map(|serialization| serialization.to_vec()) + .map_err(|_| ExodusError::SerializationError)?; + + let binding_factors_root = binding_factor_tree + .last() + .cloned() + .ok_or(ExodusError::IncorrectNumberOfPackages)?; + + Ok(( + group_commitment_bytes, + self_binding_factor_bytes, + binding_factors_proof, + binding_factors_root, + )) + }) + } + + fn init_signing_package( + &self, + nonce_commitments_bytes: &Self::NoncePackages<'_>, + message: &[u8], + ) -> Result, ExodusError> { + with_ciphersuite!(self, |f| { + type InnerGroup = ::Group; + + let signing_commitments = nonce_commitments_bytes + .iter() + .map(|(&index, &(hiding_bytes, binding_bytes))| { + let identifier = Self::non_zero_index(index)? + .try_into() + .map_err(|_| ExodusError::InvalidParticipantId)?; + + let hiding_serialization = + ::Serialization::try_from(hiding_bytes.as_ref()) + .map_err(|_| ExodusError::DeserializationError)?; + + let hiding_nonce = + f::round1::NonceCommitment::deserialize(&hiding_serialization) + .map_err(|_| ExodusError::DeserializationError)?; + + let binding_serialization = + ::Serialization::try_from((*binding_bytes).as_ref()) + .map_err(|_| ExodusError::DeserializationError)?; + + let binding_nonce = + f::round1::NonceCommitment::deserialize(&binding_serialization) + .map_err(|_| ExodusError::DeserializationError)?; + + let commitments = f::round1::SigningCommitments::new(hiding_nonce, binding_nonce); + + Ok((identifier, commitments)) + }) + .collect::, ExodusError>>()?; + + f::SigningPackage::new(signing_commitments, message) + .serialize() + .map_err(|_| ExodusError::SerializationError) + }) + } + + fn partial_sign_message( + &self, + signing_package_bytes: &[u8], + signer_nonces_bytes: &[u8], + secret_share_bytes: &[u8], + ) -> Result, ExodusError> { + with_ciphersuite!(self, |f| { + let signing_package = f::SigningPackage::deserialize(signing_package_bytes) + .map_err(|_| ExodusError::DeserializationError)?; + + let signer_nonces = f::round1::SigningNonces::deserialize(signer_nonces_bytes) + .map_err(|_| ExodusError::DeserializationError)?; + + let secret_share = f::keys::KeyPackage::deserialize(secret_share_bytes) + .map_err(|_| ExodusError::DeserializationError)?; + + let signature_shares = + f::round2::sign(&signing_package, &signer_nonces, &secret_share) + .map_err(|err| match err { + f::Error::IncorrectNumberOfCommitments => ExodusError::IncorrectNumberOfCommitments, + f::Error::MissingCommitment => ExodusError::MissingCommitment, + f::Error::IncorrectCommitment => ExodusError::IncorrectCommitment, + f::Error::SerializationError => ExodusError::SerializationError, + f::Error::DuplicatedIdentifier => ExodusError::DuplicatedIdentifier, + f::Error::UnknownIdentifier => ExodusError::UnknownIdentifier, + f::Error::IncorrectNumberOfIdentifiers => ExodusError::IncorrectNumberOfIdentifiers, + _ => ExodusError::Unknown, + })?; + + Ok(signature_shares.serialize()) + }) + } +} diff --git a/pallets/exodus/src/impls/merkle.rs b/pallets/exodus/src/impls/merkle.rs new file mode 100644 index 0000000..009c5ed --- /dev/null +++ b/pallets/exodus/src/impls/merkle.rs @@ -0,0 +1,127 @@ +use sp_std::{collections::btree_map::BTreeMap, vec::Vec, result::Result}; + +use frost_core::{Ciphersuite, Group, Identifier}; +use ghost_helpers::SubstrateBlake2Hasher; +use ghost_traits::{ + exodus::{MerkleTreeBuilder, IdentifierConverter}, + hashing::GhostHasher, +}; + +use crate::{AuthIndex, ExodusError, NetworkCurve}; + +impl MerkleTreeBuilder for NetworkCurve { + type Hash = ::Hash; + + fn build_merkle_tree( + values: &BTreeMap, V>, + serialize_fn: F, + ) -> Result, ExodusError> + where + C: frost_core::Ciphersuite, + <::Group as Group>::Field: frost_core::Field, + F: Fn(&V) -> Result, ExodusError> + { + use ghost_helpers::merkle_tree::generate_tree; + + type CField = <::Group as Group>::Field; + + let max_index = values.keys() + .next_back() + .and_then(|id| { + let id_scalar = id.to_scalar(); + let id_bytes = as frost_core::Field>::serialize(&id_scalar); + Self::convert_identifier_to_index(id_bytes.as_ref()).ok() + }) + .ok_or(ExodusError::IncorrectNumberOfIdentifiers)?; + + generate_tree::( + max_index, + values.iter(), + |(identifier, value)| { + let id_scalar = identifier.to_scalar(); + let id_bytes = as frost_core::Field>::serialize(&id_scalar); + + let index = Self::convert_identifier_to_index(id_bytes.as_ref())?; + let value_bytes = serialize_fn(value).map_err(|_| ExodusError::SerializationError)?; + + // NOTE: revisit + // Is it possible to use [u8; CONSTANT] where constant is + // purely dependant on provided generic. + let mut preimage = Vec::::with_capacity(id_bytes.as_ref().len() + value_bytes.len()); + preimage.extend_from_slice(id_bytes.as_ref()); + preimage.extend_from_slice(value_bytes.as_ref()); + + Ok((index as usize, preimage)) + }) + } + + fn generate_merkle_proof_from_tree( + values: &BTreeMap, V>, + merkle_tree: &[Self::Hash], + authority_index: AuthIndex, + serialize_fn: F, + ) -> Result<(Vec, Vec), ExodusError> + where + C: frost_core::Ciphersuite, + <::Group as Group>::Field: frost_core::Field, + F: Fn(&V) -> Result, ExodusError> + { + use ghost_helpers::merkle_tree::generate_proof; + + type CField = <::Group as Group>::Field; + + let non_zero_index = Self::non_zero_index(authority_index)?; + let identifier = Identifier::try_from(non_zero_index) + .map_err(|_| ExodusError::InvalidParticipantId)?; + + let value = values.get(&identifier).ok_or(ExodusError::InvalidParticipantId)?; + let value_bytes = serialize_fn(&value).map_err(|_| ExodusError::SerializationError)?; + + let max_index = values.keys() + .next_back() + .and_then(|id| { + let id_scalar = id.to_scalar(); + let id_bytes = as frost_core::Field>::serialize(&id_scalar); + Self::convert_identifier_to_index(id_bytes.as_ref()).ok() + }) + .ok_or(ExodusError::IncorrectNumberOfIdentifiers)?; + + let proof = generate_proof::( + merkle_tree, + max_index, + authority_index, + ); + + Ok((value_bytes, proof)) + } + + fn verify_merkle_proof( + &self, + values: &[u8], + merkle_proof: &[Self::Hash], + merkle_root: Self::Hash, + authority_index: AuthIndex, + ) -> Result<(), ExodusError> { + use ghost_helpers::merkle_tree::verify_tree_proof; + + let preimage = with_ciphersuite!(self, |f| { + let non_zero_index = Self::non_zero_index(authority_index)?; + let identifier = f::Identifier::try_from(non_zero_index) + .map_err(|_| ExodusError::InvalidParticipantId)?; + + let mut preimage = identifier.serialize(); + preimage.extend_from_slice(values); + + Ok(preimage) + })?; + + let is_verified = verify_tree_proof::( + &preimage, + merkle_proof, + merkle_root, + authority_index, + ); + + is_verified.then(|| ()).ok_or(ExodusError::InvalidMerkleProof) + } +} diff --git a/pallets/exodus/src/impls/mod.rs b/pallets/exodus/src/impls/mod.rs new file mode 100644 index 0000000..36a7353 --- /dev/null +++ b/pallets/exodus/src/impls/mod.rs @@ -0,0 +1,67 @@ +#[macro_export] +macro_rules! with_ciphersuite { + ($curve:expr, |$alias:ident| $body:block) => { + match $curve { + NetworkCurve::Secp256k1 => { + use k256::elliptic_curve::sec1::ToEncodedPoint; + use frost_secp256k1 as $alias; + + #[allow(unused_imports)] + use frost_secp256k1::Secp256K1Sha256 as Ciphersuite; + + #[allow(dead_code)] + fn decomporess_key( + verifying_key: $alias::VerifyingKey, + ) -> Result, ExodusError> { + let element = verifying_key.to_element(); + + if element == k256::ProjectivePoint::IDENTITY { + return Err(ExodusError::InvalidIdentityElement); + } + + let mut fixed_serialized = [0; 65]; + let serialized_point = element.to_affine().to_encoded_point(false); + let serialized = serialized_point.as_bytes(); + fixed_serialized.copy_from_slice(serialized); + + Ok(fixed_serialized.to_vec()) + } + + $body + }, + NetworkCurve::Ed25519 => { + use frost_ed25519 as $alias; + + #[allow(unused_imports)] + use frost_ed25519::Ed25519Sha512 as Ciphersuite; + + #[allow(dead_code)] + fn decomporess_key( + verifying_key: $alias::VerifyingKey, + ) -> Result, ExodusError> { + verifying_key.serialize().map_err(|err| match err { + f::Error::SerializationError => ExodusError::SerializationError, + _ => ExodusError::Unknown, + }) + } + + $body + }, + } + } +} + +#[macro_use] +mod converter; + +#[macro_use] +mod merkle; + +#[macro_use] +mod dkg; + +#[macro_use] +mod ecdh; + +#[macro_use] +mod frost; diff --git a/pallets/exodus/src/lib.rs b/pallets/exodus/src/lib.rs new file mode 100644 index 0000000..bb03535 --- /dev/null +++ b/pallets/exodus/src/lib.rs @@ -0,0 +1,4916 @@ +// Ensure we're `no_std` when compiling for Wasm. +#![cfg_attr(not(feature = "std"), no_std)] + +use frame_support::{ + pallet_prelude::*, + traits::{ + Currency, DisabledValidators, ValidatorSet, ValidatorSetWithIdentification, + OneSessionHandler, WithdrawReasons, ExistenceRequirement, + }, +}; +use frame_system::{ + offchain::{SubmitTransaction, SendTransactionTypes}, + pallet_prelude::*, +}; + +use sp_runtime::{ + offchain::{ + self as rt_offchain, + storage::StorageValueRef, + storage_lock::{StorageLock, Time}, + }, + traits::UniqueSaturatedInto, + Saturating, Perbill, +}; + +use sp_std::{vec, vec::Vec, collections::btree_map::BTreeMap}; +use sp_application_crypto::RuntimeAppPublic; +use sp_runtime::traits::BlockNumberProvider; +use sp_io::hashing::blake2_256; + +use ghost_helpers::{ + get_byzantium_threshold, SubstrateBlake2Hasher, + hash_chain::sequential_hash, + networks::{NetworkData, NetworkCurve, NetworkType}, + bounded_bitmap::{validate_bitmap_sizes, BoundedBitmap}, +}; + +#[cfg(test)] +use ghost_traits::exodus::IdentifierConverter; + +use ghost_traits::{ + hashing::GhostHasher, + bounded_bitmap::{ + BoundedBitmapGenerator, BoundedBitmapWriter, BoundedBitmapReader, + BoundedBitmapIterable, + }, + exodus::{ + MerkleTreeBuilder, DistributedKeyGeneration, EllipticCurveDiffieHellman, + FlexibleRoundOptimizedSchnorrThresholdSignature, + }, + networks::{ + NetworkDataBasicHandler, NetworkDataInspectHandler, + NetworkDataMutateHandler, + }, +}; + +use rand_chacha::{ChaCha20Rng, rand_core::SeedableRng}; + +#[cfg(feature = "runtime-benchmarks")] +mod benchmarking; + +#[cfg(any(test, feature = "runtime-benchmarks"))] +mod mock; + +#[cfg(test)] +mod tests; + +#[cfg_attr(feature = "runtime-benchmarks", macro_use)] +mod impls; +mod error; +mod types; +pub mod weights; + +pub use types::*; +pub use error::ExodusError; +pub use weights::WeightInfo; + +pub mod sr25519 { + mod app_sr25519 { + use sp_application_crypto::{app_crypto, sr25519, KeyTypeId}; + const EXODUS: KeyTypeId = KeyTypeId(*b"exds"); + app_crypto!(sr25519, EXODUS); + } + + sp_application_crypto::with_pair! { + pub type AuthorityPair = app_sr25519::Pair; + } + + pub type AuthoritySignature = app_sr25519::Signature; + pub type AuthorityId = app_sr25519::Public; +} + +const MIN_LOCK_GUARD_PERIOD: u64 = 15_000; + +const DB_STORAGE_PREFIX : &[u8] = b"exodus::"; +const PALLET_LOG_TARGET : &str = "time:ghost-exodus"; +const OFFCHAIN_INDEX_KEY : &[u8] = b"dkg-round2-encrypted"; + +const DKG_ROUND0_PREFIX: &[u8] = b"dkg-round0"; +const DKG_ROUND1_PREFIX: &[u8] = b"dkg-round1"; +const DKG_ROUND2_PREFIX: &[u8] = b"dkg-round2"; +const DKG_ROUND3_PREFIX: &[u8] = b"dkg-round3"; +const DKG_ROUND4_PREFIX: &[u8] = b"dkg-round4"; +const DKG_SECRET_PREFIX: &[u8] = b"dkg-secret"; + +const EXODUS_PREFIX_NONCES: &[u8] = b"exodus-nonces"; +const EXODUS_PREFIX_SHARES: &[u8] = b"exodus-shares"; +const EXODUS_PREFIX_BLENDS: &[u8] = b"exodus-blends"; +const EXODUS_PREFIX_EXILES: &[u8] = b"exodus-exiles"; + +const MAX_MESSAGE_SIZE : u32 = 420; +const HEADER_MAX_BYTES : u32 = 1 + 4; +const ELEMENT_MAX_BYTES : u32 = 33; +const SCALAR_MAX_BYTES : u32 = 32; + +const ENCRYPTION_NONCE_MAX_BYTES : u32 = 12; +const AUTHENTICATION_TAG_MAX_BYTES : u32 = 16; + +const ROUND_NUMBER_0: u8 = 0u8; +const ROUND_NUMBER_1: u8 = 1u8; +const ROUND_NUMBER_2: u8 = 2u8; +const ROUND_NUMBER_3: u8 = 3u8; +const ROUND_NUMBER_4: u8 = 4u8; +const ROUND_NUMBER_5: u8 = 5u8; +const ROUND_NUMBER_6: u8 = 6u8; + +const fn postcard_varint_len(len: usize) -> usize { + if len <= 127 { 1 } + else if len <= 16383 { 2 } + else { 3 } +} + +const fn signature_bytes_len( + element_bytes_len: usize, + scalar_bytes_len: usize, +)-> usize { + let signature_bytes_len = element_bytes_len.saturating_add(scalar_bytes_len); + let signature_vec_prefix = postcard_varint_len(signature_bytes_len); + + signature_bytes_len.saturating_add(signature_vec_prefix) +} + +const fn round1_package_size( + commitments_count: usize, + element_bytes_len: usize, + scalar_bytes_len: usize, + header_bytes_len: usize +) -> usize { + let commitments_bytes_len = commitments_count.saturating_mul(element_bytes_len); + let commitment_vec_prefix = postcard_varint_len(commitments_count); + + signature_bytes_len(element_bytes_len, scalar_bytes_len) + .saturating_add(header_bytes_len) + .saturating_add(commitment_vec_prefix) + .saturating_add(commitments_bytes_len) +} + +const fn encrypted_ciphertext_bytes_len( + scalar_bytes_len: usize, + header_bytes_len: usize, +) -> usize { + let ciphertext_len = header_bytes_len + .saturating_add(scalar_bytes_len) + .saturating_add(AUTHENTICATION_TAG_MAX_BYTES as usize); + + let ciphertext_vec_prefix = postcard_varint_len(ciphertext_len); + + ciphertext_len.saturating_add(ciphertext_vec_prefix) +} + +const fn round2_encrypted_package_size( + participants: usize, + scalar_bytes_len: usize, + header_bytes_len: usize, +) -> usize { + let encrypted_ciphertext_bytes_len = encrypted_ciphertext_bytes_len( + scalar_bytes_len, + header_bytes_len, + ); + + let ciphertext_len = encrypted_ciphertext_bytes_len + .saturating_add(ENCRYPTION_NONCE_MAX_BYTES as usize); + + let ciphertext_vec_prefix = postcard_varint_len(ciphertext_len); + let size = ciphertext_len.saturating_add(ciphertext_vec_prefix); + + participants.saturating_mul(size) +} + +const fn round2_package_size( + participants: usize, + scalar_bytes_len: usize, + header_bytes_len: usize, +) -> usize { + let signing_share_len = header_bytes_len.saturating_add(scalar_bytes_len); + let signing_share_vec_prefix = postcard_varint_len(signing_share_len); + + let size = signing_share_vec_prefix.saturating_add(signing_share_len); + participants.saturating_mul(size) +} + +const fn nonce_commitment_package_size( + element_max_bytes: usize, + header_max_bytes: usize, +) -> usize { + header_max_bytes + .saturating_add(element_max_bytes) + .saturating_add(element_max_bytes) +} + +const fn signature_share_bytes_len( + scalar_bytes_len: usize, + header_bytes_len: usize, +) -> usize { + header_bytes_len.saturating_add(scalar_bytes_len) +} + +type DkgIndex = ghost_helpers::DkgIndexU32; +type AuthIndex = ghost_helpers::AuthIndexU16; +type BitmapChunk = ghost_helpers::BitmapChunkU32; + +type RoastSession = u16; +type ExodusSession = u64; + +type EvmAddress = sp_core::H160; +type ExodusHash = sp_core::H256; +type EvmBytes32 = sp_core::H256; + +type ParticipantsBitmap = BoundedBitmap::MaxAuthoritiesChunks>; +type BitmapByAuthority = BoundedBTreeMap, ::MaxAuthorities>; + +type QualifyingState = PendingDkgAuthorities, BlockNumberFor>; +type ActivatedState = ReadyDkgAuthorities>; + +pub type BalanceOf = + <::Currency as Currency<::AccountId>>::Balance; + +pub type NetworkIdOf = + <::NetworkDataHandler as NetworkDataBasicHandler>::NetworkId; + +pub type ValidatorId = <::ValidatorSet as ValidatorSet< + ::AccountId, +>>::ValidatorId; + +pub type IdentificationTuple = ( + ValidatorId, + <::ValidatorSet as ValidatorSetWithIdentification< + ::AccountId, + >>::Identification, +); + +type ExodusResult = Result; + +pub struct MaxAuthoritiesBitmaskSize(sp_std::marker::PhantomData); +impl Get for MaxAuthoritiesBitmaskSize { + fn get() -> u32 { + let max_authorities = T::MaxAuthorities::get(); + max_authorities.div_ceil(8) + } +} + +pub struct Round1MaxBytes(sp_std::marker::PhantomData); +impl Get for Round1MaxBytes { + fn get() -> u32 { + let max_authorities = T::MaxAuthorities::get(); + let min_authorities = get_byzantium_threshold(max_authorities); + + round1_package_size( + min_authorities as usize, + ELEMENT_MAX_BYTES as usize, + SCALAR_MAX_BYTES as usize, + HEADER_MAX_BYTES as usize, + ) as u32 + } +} + +pub struct Round2BlobMaxBytes(sp_std::marker::PhantomData); +impl Get for Round2BlobMaxBytes { + fn get() -> u32 { + let max_authorities = T::MaxAuthorities::get(); + + round2_package_size( + max_authorities as usize, + SCALAR_MAX_BYTES as usize, + HEADER_MAX_BYTES as usize + ) as u32 + } +} + +pub struct EncryptedRound2BlobMaxBytes(sp_std::marker::PhantomData); +impl Get for EncryptedRound2BlobMaxBytes { + fn get() -> u32 { + let max_authorities = T::MaxAuthorities::get(); + + round2_encrypted_package_size( + max_authorities as usize, + SCALAR_MAX_BYTES as usize, + HEADER_MAX_BYTES as usize + ) as u32 + } +} + +pub struct AccusedIndicesMaxBytes(sp_std::marker::PhantomData); +impl Get for AccusedIndicesMaxBytes { + fn get() -> u32 { + let max_authorities = T::MaxAuthorities::get(); + max_authorities.saturating_add(7).div_ceil(8) + } +} + +pub struct MerkleProofMaxSize(sp_std::marker::PhantomData); +impl Get for MerkleProofMaxSize { + fn get() -> u32 { + let max_authorities = T::MaxAuthorities::get(); + + max_authorities + .next_power_of_two() + .trailing_zeros() + .saturating_mul(ExodusHash::len_bytes() as u32) + } +} + +pub struct BindingFactorsProof(sp_std::marker::PhantomData); +impl Get for BindingFactorsProof { + fn get() -> u32 { + let max_authorities = T::MaxAuthorities::get(); + get_byzantium_threshold(max_authorities) + .next_power_of_two() + .trailing_zeros() + } +} + +pub struct NonceCommitmentMaxBytes(sp_std::marker::PhantomData); +impl Get for NonceCommitmentMaxBytes { + fn get() -> u32 { + nonce_commitment_package_size( + ELEMENT_MAX_BYTES as usize, + HEADER_MAX_BYTES as usize, + ) as u32 + } +} + +pub struct CiphertextMaxBytes(sp_std::marker::PhantomData); +impl Get for CiphertextMaxBytes { + fn get() -> u32 { + encrypted_ciphertext_bytes_len( + SCALAR_MAX_BYTES as usize, + HEADER_MAX_BYTES as usize, + ) as u32 + } +} + +pub struct SignatureShareMaxBytes(sp_std::marker::PhantomData); +impl Get for SignatureShareMaxBytes { + fn get() -> u32 { + signature_share_bytes_len( + SCALAR_MAX_BYTES as usize, + HEADER_MAX_BYTES as usize, + ) as u32 + } +} + +pub struct SignatureMaxBytes(sp_std::marker::PhantomData); +impl Get for SignatureMaxBytes { + fn get() -> u32 { + signature_bytes_len( + ELEMENT_MAX_BYTES as usize, + SCALAR_MAX_BYTES as usize, + ) as u32 + } +} + +pub struct ZeroScalarDefault(sp_std::marker::PhantomData); +impl Get>> for ZeroScalarDefault { + fn get() -> BoundedVec> { + let zero_vec = vec![0u8; SCALAR_MAX_BYTES as usize]; + BoundedVec::try_from(zero_vec).unwrap_or_default() + } +} + +pub use pallet::*; + +#[frame_support::pallet] +pub mod pallet { + use super::*; + + const STORAGE_VERSION: StorageVersion = StorageVersion::new(0); + + #[pallet::pallet] + #[pallet::storage_version(STORAGE_VERSION)] + #[pallet::without_storage_info] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: SendTransactionTypes> + frame_system::Config + core::fmt::Debug { + type RuntimeEvent: From> + + IsType<::RuntimeEvent>; + + type AuthorityId: Member + + Parameter + + RuntimeAppPublic + + Ord + + MaybeSerializeDeserialize + + MaxEncodedLen; + + type ValidatorSet: ValidatorSetWithIdentification; + type Currency: Currency; + + type NetworkDataHandler: NetworkDataInspectHandler + + NetworkDataMutateHandler> + + NetworkDataBasicHandler; + + type BlockNumberProvider: BlockNumberProvider>; + + type DisabledValidators: DisabledValidators; + + #[pallet::constant] + type UnsignedPriority: Get; + + #[pallet::constant] + type UnsignedLongevity: Get; + + #[pallet::constant] + type MaxAuthorities: Get; + + #[pallet::constant] + type MaxAuthoritiesChunks: Get; + + #[pallet::constant] + type DkgRoundPeriod: Get; + + #[pallet::constant] + type RemovalLimit: Get; + + #[pallet::constant] + type MaxCursorLen: Get; + + type WeightInfo: WeightInfo; + } + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + Round0PackageRegistered { + authority_index: AuthIndex, + network_curve: NetworkCurve, + hash: ExodusHash, + }, + Round1PackageRegistered { + authority_index: AuthIndex, + coefficients_count: AuthIndex, + network_curve: NetworkCurve, + }, + Round2PackagesRegistered { + authority_index: AuthIndex, + encrypted_count: AuthIndex, + network_curve: NetworkCurve, + }, + ComplaintsRegistered { + complaints_count: AuthIndex, + authority_index: AuthIndex, + network_curve: NetworkCurve, + }, + Round4JustificatPackage { + justifications_count: AuthIndex, + authority_index: AuthIndex, + network_curve: NetworkCurve, + }, + Round5PublicPackageRegistered { + authority_index: AuthIndex, + network_curve: NetworkCurve, + verifying_hash: ExodusHash, + }, + Round6VerifyingShareRegistered { + authority_index: AuthIndex, + network_curve: NetworkCurve, + }, + NonceCommitmentRegistered { + authority_index: AuthIndex, + exodus_session: ExodusSession, + network_curve: NetworkCurve, + roast_session: RoastSession, + }, + GroupCommitmentRegistered { + authority_index: AuthIndex, + exodus_session: ExodusSession, + network_curve: NetworkCurve, + roast_session: RoastSession, + }, + PartialSignatureRegistered { + authority_index: AuthIndex, + exodus_session: ExodusSession, + network_curve: NetworkCurve, + roast_session: RoastSession, + }, + EvmBridgeOutRegistered { + who: T::AccountId, + network_id: NetworkIdOf, + amount: BalanceOf, + commission: Perbill, + receiver: EvmAddress, + } + } + + #[pallet::error] + pub enum Error { + DkgWrongRound, + TooManyEntries, + TooManyPackages, + WrongNetworkType, + InvalidMerkleProof, + NetworkDoesNotExist, + NoActiveAuthorities, + InvalidSignatureShare, + VerifyingShareNotFound, + DkgAuthoritiesInProgress, + PackagesAlreadyRegistered, + DkgAuthoritiesNotInitialized, + + Round0PackageInvalidProof, + Round0PackageAlreadyRegistered, + Round1PackageBadHash, + Round1PackageInvalidProof, + Round1PackageAlreadyRegistered, + Round1PackageInvalidLength, + Round2PackagesInvalidProof, + Round2PackagesAlreadyRegistered, + Round2PackagesWrongCoefficients, + Round3PackageInvalidProof, + Round3PackagesAlreadyRegistere, + Round4PackageInvalidProof, + Round4PackagesAlreadyRegistered, + Round5PackageInvalidProof, + Round5PackagesAlreadyRegistered, + Round6PackageInvalidProof, + Round6PackagesAlreadyRegistered, + + ExodusMessageTooBig, + ExodusRequestNotFound, + ExodusAccumulationFailed, + ExodusIncorrectRoastStatus, + ExodusInvalidSignatureShare, + ExodusAlreadyInRoastSession, + ExodusSignedMessageAlreadyExists, + + ExodusNonceNotRegistered, + ExodusNonceAlreadyRegistered, + ExodusGroupAlreadyRegistered, + ExodusCommitmentNotRegistered, + ExodusSignedMessageAlreadyRegistered, + + ExodusNoncePackageInvalidProof, + ExodusGroupPackageInvalidProof, + ExodusSharePackageInvalidProof, + } + + #[pallet::storage] + #[pallet::getter(fn current_exodus)] + pub(super) type CurrentExodus = + StorageValue<_, ExodusSession, ValueQuery>; + + #[pallet::storage] + #[pallet::getter(fn exodus_requests)] + pub(super) type ExodusRequests = StorageDoubleMap< + _, + Twox64Concat, NetworkCurve, + Twox64Concat, ExodusSession, + ExodusRequest, BalanceOf>, + OptionQuery, + >; + + #[pallet::storage] + #[pallet::getter(fn roast_states)] + pub(super) type RoastSessionStates = StorageDoubleMap< + _, + Twox64Concat, ExodusSession, + Twox64Concat, RoastSession, + RoastSessionState>, + ValueQuery, + >; + + #[pallet::storage] + #[pallet::getter(fn nonce_commitments)] + pub(super) type NonceCommitments = StorageDoubleMap< + _, + Twox64Concat, (ExodusSession, RoastSession), + Twox64Concat, AuthIndex, + ExodusSeparatedNonce, + OptionQuery, + >; + + #[pallet::storage] + #[pallet::getter(fn group_committers)] + pub(super) type GroupCommitters = StorageNMap< + _, + ( + NMapKey, + NMapKey, + NMapKey, + NMapKey>>, + ), + ParticipantsBitmap, + ValueQuery, + >; + + #[pallet::storage] + #[pallet::getter(fn group_commitments_consensus)] + pub(super) type GroupCommitmentsConsensus = StorageDoubleMap< + _, + Twox64Concat, ExodusSession, + Twox64Concat, RoastSession, + ConsensusState>, + ValueQuery, + >; + + #[pallet::storage] + #[pallet::getter(fn signature_scalars)] + pub(super) type SignatureScalars = StorageDoubleMap< + _, + Twox64Concat, ExodusSession, + Twox64Concat, RoastSession, + BoundedVec>, + ValueQuery, + ZeroScalarDefault, + >; + + #[pallet::storage] + #[pallet::getter(fn roast_sessions)] + pub(super) type RoastSessions = StorageDoubleMap< + _, + Twox64Concat, ExodusSession, + Twox64Concat, AuthIndex, + RoastSession, + OptionQuery, + >; + + #[pallet::storage] + #[pallet::getter(fn exodus_signatured_rotations)] + pub(super) type ExodusSignedRotations = StorageDoubleMap< + _, + Twox64Concat, (ExodusSession, NetworkType), + Twox64Concat, DkgIndex, + ExodusSignedMessage, BalanceOf>, + OptionQuery, + >; + + #[pallet::storage] + #[pallet::getter(fn exodus_signed_bridges)] + pub(super) type ExodusSignedBridges = StorageDoubleMap< + _, + Twox64Concat, (ExodusSession, NetworkIdOf), + Twox64Concat, DkgIndex, + ExodusSignedMessage, BalanceOf>, + OptionQuery, + >; + + #[pallet::storage] + #[pallet::getter(fn exodus_signed_governance)] + pub(super) type ExodusSignedGovernance = StorageDoubleMap< + _, + Twox64Concat, (ExodusSession, NetworkIdOf), + Twox64Concat, DkgIndex, + ExodusSignedMessage, BalanceOf>, + OptionQuery, + >; + + #[pallet::storage] + #[pallet::getter(fn dkg_maybe_remove_cursor)] + pub(super) type DkgMaybeCursor = StorageMap< + _, + Twox64Concat, NetworkCurve, + BoundedVec, + OptionQuery, + >; + + #[pallet::storage] + #[pallet::getter(fn exodus_activity)] + pub(super) type ExodusActivity = StorageMap< + _, + Twox64Concat, AuthIndex, + BlockNumberFor, + ValueQuery, + >; + + #[pallet::storage] + #[pallet::getter(fn round0_packages)] + pub(super) type Round0Packages = StorageDoubleMap< + _, + Twox64Concat, NetworkCurve, + Twox64Concat, AuthIndex, + ExodusHash, + ValueQuery, + >; + + #[pallet::storage] + #[pallet::getter(fn round1_packages)] + pub(super) type Round1Packages = StorageMap< + _, + Twox64Concat, NetworkCurve, + ParticipantsBitmap, + ValueQuery, + >; + + #[pallet::storage] + #[pallet::getter(fn encrypted_round2_packages)] + pub(super) type Round2Packages = StorageMap< + _, + Twox64Concat, NetworkCurve, + ParticipantsBitmap, + ValueQuery, + >; + + #[pallet::storage] + #[pallet::getter(fn complaints)] + pub(super) type Complaints = StorageMap< + _, + Twox64Concat, NetworkCurve, + BitmapByAuthority, + ValueQuery, + >; + + #[pallet::storage] + #[pallet::getter(fn justifications)] + pub(super) type Justifications = StorageMap< + _, + Twox64Concat, NetworkCurve, + BitmapByAuthority, + ValueQuery, + >; + + #[pallet::storage] + #[pallet::getter(fn verifications)] + pub(super) type Verifications = StorageNMap< + _, + ( + NMapKey, + NMapKey, + NMapKey, + ), + ParticipantsBitmap, + ValueQuery, + >; + + #[pallet::storage] + #[pallet::getter(fn verifying_key_consensus)] + pub(super) type VerifyingKeyConsensus = StorageDoubleMap< + _, + Twox64Concat, NetworkCurve, + Twox64Concat, DkgIndex, + ConsensusState>, + ValueQuery, + >; + + #[pallet::storage] + #[pallet::getter(fn verifying_shares)] + pub(super) type VerifyingShares = StorageNMap< + _, + ( + NMapKey, + NMapKey, + NMapKey, + ), + BoundedVec>, + OptionQuery, + >; + + #[pallet::storage] + #[pallet::getter(fn verifying_shares_participants)] + pub(super) type VerifyingSharesParticipants = StorageDoubleMap< + _, + Twox64Concat, NetworkCurve, + Twox64Concat, DkgIndex, + ParticipantsBitmap, + ValueQuery, + >; + + #[pallet::storage] + #[pallet::getter(fn active_verifying_key)] + pub(super) type ActiveVerifyingKey = StorageMap< + _, + Twox64Concat, NetworkCurve, + BoundedVec>, + ValueQuery, + >; + + #[pallet::storage] + #[pallet::getter(fn active_authorities)] + pub(super) type ActiveAuthorities = StorageMap< + _, + Twox64Concat, NetworkCurve, + WeakBoundedVec<::AuthorityId, ::MaxAuthorities>, + ValueQuery, + >; + + #[pallet::storage] + #[pallet::getter(fn active_dkg_authority)] + pub(super) type ActiveDkgAuthorities = StorageMap< + _, + Twox64Concat, NetworkCurve, + ActivatedState, + ValueQuery, + >; + + #[pallet::storage] + #[pallet::getter(fn qualification_authorities)] + pub(super) type QualificationAuthorities = StorageMap< + _, + Twox64Concat, NetworkCurve, + WeakBoundedVec<::AuthorityId, ::MaxAuthorities>, + ValueQuery, + >; + + #[pallet::storage] + #[pallet::getter(fn qualification_dkg_state)] + pub(super) type QualificationDkgState = StorageMap< + _, + Twox64Concat, NetworkCurve, + QualifyingState, + ValueQuery, + >; + + #[pallet::genesis_config] + #[derive(frame_support::DefaultNoBound)] + pub struct GenesisConfig { + pub authorities: Vec, + } + + #[pallet::genesis_build] + impl BuildGenesisConfig for GenesisConfig { + fn build(&self) { + use strum::IntoEnumIterator; + + // NOTE: would be nice to use same logic during forkless + // upgrade in order to prevent any issues down the road. + + let block_longevity: BlockNumberFor = + T::UnsignedLongevity::get().unique_saturated_into(); + + let converted_round_period: BlockNumberFor = + T::DkgRoundPeriod::get().unique_saturated_into(); + + assert!( + block_longevity < converted_round_period, + "CRITICAL CONFIG ERROR: T::DKGRoundPeriod could not be less than expected block inclusion!", + ); + + let max_authorities = T::MaxAuthorities::get(); + validate_bitmap_sizes::>(max_authorities); + + for network_curve in NetworkCurve::iter() { + assert!( + network_curve.element_bytes_len() <= ELEMENT_MAX_BYTES as usize, + "Curve {:?} requires {} bytes for elements but ELEMENT_MAX_BYTES is {}.", + network_curve, + network_curve.element_bytes_len(), + ELEMENT_MAX_BYTES, + ); + + assert!( + network_curve.scalar_bytes_len() <= SCALAR_MAX_BYTES as usize, + "Curve {:?} requires {} bytes for scalar but SCALAR_MAX_BYTES is {}.", + network_curve, + network_curve.scalar_bytes_len(), + SCALAR_MAX_BYTES, + ); + + for network_type in T::NetworkDataHandler::iter_types_by_curve(&network_curve) { + let rotation_message_size = match network_type { + NetworkType::Evm => { + let dummy_public_address = EvmBytes32::repeat_byte(69u8); + + let dummy_exodus_request = + ExodusRequest::, BalanceOf>::evm_rotation( + 0, dummy_public_address, 0, + ); + + let mut message_buffer = [0u8; MAX_MESSAGE_SIZE as usize]; + dummy_exodus_request + .get_message(&mut message_buffer) + .map(|message| message.len() as u32) + .unwrap_or(u32::MAX) + }, + _ => 0, + }; + + assert!( + rotation_message_size <= MAX_MESSAGE_SIZE, + "Curve {:?} requires {} bytes for rotation message but MAX_MESSAGE_SIZE is {}", + network_curve, + rotation_message_size, + MAX_MESSAGE_SIZE, + ); + } + } + } + } + + #[pallet::call] + impl Pallet { + #[pallet::call_index(0)] + #[pallet::weight(( + T::WeightInfo::register_round0_package(), + DispatchClass::Normal, + Pays::No, + ))] + pub fn register_round0_package( + origin: OriginFor, + dkg_package: DkgPackage, + signature: ::Signature, + ) -> DispatchResult { + ensure_none(origin)?; + + let network_curve = dkg_package.get_network_curve(); + let authority_index = dkg_package.get_authority_index(); + + Self::validate_round0_package_size(&dkg_package, &signature) + .map_err(|_| Error::::Round0PackageInvalidProof)?; + + Self::register_latest_activity(&dkg_package)?; + + let DkgPackage::Round0(round0) = dkg_package else { + return Err(Error::::Round0PackageInvalidProof.into()); + }; + + QualificationDkgState::::try_mutate(&network_curve, + |qualification_state| -> DispatchResult { + ensure!( + qualification_state.is_zero_phase(), + Error::::DkgWrongRound, + ); + + ensure!( + !qualification_state.contains_index(authority_index), + Error::::Round0PackageAlreadyRegistered, + ); + + qualification_state.insert_index(authority_index); + Ok(()) + })?; + + Round0Packages::::insert( + &network_curve, + authority_index, + round0.package_hash, + ); + + Self::deposit_event(Event::::Round0PackageRegistered { + authority_index, + network_curve, + hash: round0.package_hash, + }); + + Ok(()) + } + + #[pallet::call_index(1)] + #[pallet::weight(( + T::WeightInfo::register_round1_package( + dkg_package.bytes_len(), + ), + DispatchClass::Normal, + Pays::No, + ))] + pub fn register_round1_package( + origin: OriginFor, + dkg_package: DkgPackage, + signature: ::Signature, + ) -> DispatchResult { + ensure_none(origin)?; + + let network_curve = dkg_package.get_network_curve(); + let authority_index = dkg_package.get_authority_index(); + + Self::validate_round1_package_size(&dkg_package, &signature) + .map_err(|_| Error::::Round1PackageInvalidProof)?; + + Self::register_latest_activity(&dkg_package)?; + + let DkgPackage::Round1(round1) = dkg_package else { + return Err(Error::::Round1PackageInvalidProof.into()); + }; + + let hash1 = Round0Packages::::get(&network_curve, authority_index); + let hash2 = ExodusHash::from(blake2_256(&round1.package)); + ensure!(!hash1.is_zero() && hash1 == hash2, Error::::Round1PackageBadHash); + + let estimated_commitment_count = + network_curve.dkg_verify_proof_of_knowledge( + authority_index, + &round1.package, + ).map_err(|_| Error::::Round1PackageInvalidProof)?; + + let state = QualificationDkgState::::get(&network_curve); + ensure!(state.is_first_phase(), Error::::DkgWrongRound); + + state.count_ones::().checked_sub(1) + .map(|needed_commitment_count| { + estimated_commitment_count + .eq(&needed_commitment_count) + .then(|| ()) + }) + .ok_or(Error::::Round1PackageInvalidLength)?; + + Round1Packages::::try_mutate( + &network_curve, + |round1_packages| -> DispatchResult { + ensure!( + !round1_packages.contains(authority_index), + Error::::Round1PackageAlreadyRegistered, + ); + round1_packages.insert(authority_index); + Ok(()) + })?; + + Self::deposit_event(Event::::Round1PackageRegistered { + authority_index, + network_curve, + coefficients_count: estimated_commitment_count, + }); + + let index_key = Self::create_offchain_dkg_key( + network_curve, + authority_index, + ROUND_NUMBER_1, + ); + + sp_io::offchain_index::set( + &index_key, + &round1.package.encode(), + ); + + Ok(()) + } + + #[pallet::call_index(2)] + #[pallet::weight(( + T::WeightInfo::register_encrypted_round2_packages( + dkg_package.bytes_len(), + ), + DispatchClass::Normal, + Pays::No, + ))] + pub fn register_encrypted_round2_packages( + origin: OriginFor, + dkg_package: DkgPackage, + signature: ::Signature, + ) -> DispatchResult { + ensure_none(origin)?; + + let network_curve = dkg_package.get_network_curve(); + let authority_index = dkg_package.get_authority_index(); + + Self::validate_round2_package_size(&dkg_package, &signature) + .map_err(|_| Error::::Round2PackagesInvalidProof)?; + + Self::register_latest_activity(&dkg_package)?; + + let DkgPackage::Round2(round2_bundle) = dkg_package else { + return Err(Error::::Round2PackagesInvalidProof.into()); + }; + + let state = QualificationDkgState::::get(&network_curve); + ensure!(state.is_second_phase(), Error::::DkgWrongRound); + + let encrypted_indexes = ParticipantsBitmap::::from_bitmask( + &round2_bundle.bundle.bitmask, + ); + + let diff = state.get_indexes() ^ &encrypted_indexes; + let only_one_lost = diff.count_ones::() == 1; + let only_authority_index = diff.contains(authority_index); + + ensure!( + only_one_lost && only_authority_index, + Error::::Round2PackagesWrongCoefficients, + ); + + Round2Packages::::try_mutate(&network_curve, + |round2_packages| -> DispatchResult { + ensure!( + !round2_packages.contains(authority_index), + Error::::Round2PackagesAlreadyRegistered, + ); + + round2_packages.insert(authority_index); + + Ok(()) + })?; + + Self::deposit_event(Event::::Round2PackagesRegistered { + encrypted_count: encrypted_indexes.count_ones::(), + authority_index, + network_curve, + }); + + let index_key = Self::create_offchain_dkg_key( + network_curve, + authority_index, + ROUND_NUMBER_2, + ); + + sp_io::offchain_index::set( + &index_key, + &round2_bundle.bundle.encode(), + ); + + Ok(()) + } + + #[pallet::call_index(3)] + #[pallet::weight(( + T::WeightInfo::register_round3_complaints( + dkg_package.bytes_len(), + ), + DispatchClass::Normal, + Pays::No, + ))] + pub fn register_round3_complaints( + origin: OriginFor, + dkg_package: DkgPackage, + signature: ::Signature, + ) -> DispatchResult { + ensure_none(origin)?; + + let network_curve = dkg_package.get_network_curve(); + let authority_index = dkg_package.get_authority_index(); + + Self::validate_round3_complaints_size(&dkg_package, &signature) + .map_err(|_| Error::::Round3PackageInvalidProof)?; + + Self::register_latest_activity(&dkg_package)?; + + let DkgPackage::Round3(round3_complaints) = dkg_package else { + return Err(Error::::Round3PackageInvalidProof.into()); + }; + + let complaints_bitmap = ParticipantsBitmap::::from_bitmask( + &round3_complaints.indices + ); + + let complaints_count: AuthIndex = complaints_bitmap.count_ones(); + let state = QualificationDkgState::::get(&network_curve); + ensure!(state.is_third_phase(), Error::::DkgWrongRound); + + Complaints::::try_mutate( + &network_curve, + |complaints| -> DispatchResult { + ensure!( + !complaints.contains_key(&authority_index), + Error::::Round3PackagesAlreadyRegistere, + ); + + complaints.try_insert(authority_index, complaints_bitmap) + .map_err(|_| Error::::TooManyPackages)?; + + Ok(()) + })?; + + Self::deposit_event(Event::::ComplaintsRegistered { + authority_index, + complaints_count, + network_curve, + }); + + Ok(()) + } + + #[pallet::call_index(4)] + #[pallet::weight(( + T::WeightInfo::register_round4_justifications( + dkg_package.bytes_len(), + ), + DispatchClass::Normal, + Pays::No, + ))] + pub fn register_round4_justifications( + origin: OriginFor, + dkg_package: DkgPackage, + signature: ::Signature, + ) -> DispatchResult { + ensure_none(origin)?; + + let network_curve = dkg_package.get_network_curve(); + let authority_index = dkg_package.get_authority_index(); + + Self::validate_round4_justifications_size(&dkg_package, &signature) + .map_err(|_| Error::::Round4PackageInvalidProof)?; + + Self::register_latest_activity(&dkg_package)?; + + let DkgPackage::Round4(round4_bundle) = dkg_package else { + return Err(Error::::Round4PackageInvalidProof.into()); + }; + + let justifications_bitmap = ParticipantsBitmap::::from_bitmask( + &round4_bundle.bundle.bitmask + ); + + let justifications_count: AuthIndex = justifications_bitmap.count_ones(); + let state = QualificationDkgState::::get(&network_curve); + ensure!(state.is_fourth_phase(), Error::::DkgWrongRound); + + Justifications::::try_mutate( + &network_curve, + |justifications| -> DispatchResult { + ensure!( + !justifications.contains_key(&authority_index), + Error::::Round4PackagesAlreadyRegistered, + ); + + justifications.try_insert(authority_index, justifications_bitmap) + .map_err(|_| Error::::TooManyPackages)?; + + Ok(()) + })?; + + Self::deposit_event(Event::::Round4JustificatPackage { + justifications_count, + authority_index, + network_curve, + }); + + let index_key = Self::create_offchain_dkg_key( + network_curve, + authority_index, + ROUND_NUMBER_4, + ); + + sp_io::offchain_index::set( + &index_key, + &round4_bundle.bundle.encode(), + ); + + Ok(()) + } + + #[pallet::call_index(5)] + #[pallet::weight(( + T::WeightInfo::register_round5_verifying_package(), + DispatchClass::Normal, + Pays::No, + ))] + pub fn register_round5_verifying_package( + origin: OriginFor, + dkg_package: DkgPackage, + signature:::Signature, + ) -> DispatchResult { + ensure_none(origin)?; + + let network_curve = dkg_package.get_network_curve(); + let authority_index = dkg_package.get_authority_index(); + + Self::validate_round5_package_size(&dkg_package, &signature) + .map_err(|_| Error::::Round5PackageInvalidProof)?; + + Self::register_latest_activity(&dkg_package)?; + + let DkgPackage::Round5(round5) = dkg_package else { + return Err(Error::::Round5PackageInvalidProof.into()); + }; + + let metadata_hashes = [ + SubstrateBlake2Hasher::hash(round5.merkle_root.as_ref()), + SubstrateBlake2Hasher::hash(round5.verifying_key.as_ref()), + ]; + let verifying_hash = sequential_hash::(metadata_hashes); + let state = QualificationDkgState::::get(&network_curve); + ensure!(state.is_fifth_phase(), Error::::DkgWrongRound); + + let dkg_index = state.get_dkg_index(); + let verification_key = (network_curve, dkg_index, verifying_hash); + + let latest_participants = Verifications::::try_mutate( + &verification_key, + |bitmap| -> Result, DispatchError> { + ensure!( + !bitmap.contains(authority_index), + Error::::Round5PackagesAlreadyRegistered, + ); + + if bitmap.is_empty() { + *bitmap = ParticipantsBitmap::::empty_from( + state.get_indexes(), + ); + } + + bitmap.insert(authority_index); + Ok(bitmap.clone()) + })?; + + VerifyingKeyConsensus::::mutate(&network_curve, &dkg_index, |state| { + let state_participants_count = state.participants.count_ones::(); + let latest_participants_count = latest_participants.count_ones::(); + + if state_participants_count < latest_participants_count { + *state = ConsensusState::new( + round5.verifying_key, + latest_participants, + round5.merkle_root, + ); + } + }); + + Self::deposit_event(Event::::Round5PublicPackageRegistered { + authority_index, + verifying_hash, + network_curve, + }); + + Ok(()) + } + + #[pallet::call_index(6)] + #[pallet::weight(( + T::WeightInfo::register_round6_public_share_package( + dkg_package.bytes_len(), + ), + DispatchClass::Normal, + Pays::No, + ))] + pub fn register_round6_public_share_package( + origin: OriginFor, + dkg_package: DkgPackage, + signature:::Signature, + ) -> DispatchResult { + ensure_none(origin)?; + + let network_curve = dkg_package.get_network_curve(); + let authority_index = dkg_package.get_authority_index(); + + Self::validate_round6_package_size(&dkg_package, &signature) + .map_err(|_| Error::::Round6PackageInvalidProof)?; + + Self::register_latest_activity(&dkg_package)?; + + let DkgPackage::Round6(round6) = dkg_package else { + return Err(Error::::Round6PackageInvalidProof.into()); + }; + + let expected_share_len = network_curve.element_bytes_len(); + ensure!( + round6.verifying_share.len() == expected_share_len, + Error::::Round6PackageInvalidProof, + ); + + let state = QualificationDkgState::::get(&network_curve); + ensure!(state.is_sixth_phase(), Error::::DkgWrongRound); + + let dkg_index = state.get_dkg_index(); + let consensus = VerifyingKeyConsensus::::get(&network_curve, &dkg_index); + + network_curve.verify_merkle_proof( + &round6.verifying_share, + &round6.merkle_proof, + consensus.merkle_root, + authority_index, + ).map_err(|_| Error::::InvalidMerkleProof)?; + + let verifying_share_key = (network_curve, dkg_index, authority_index); + + ensure!( + !VerifyingShares::::contains_key(&verifying_share_key), + Error::::Round6PackagesAlreadyRegistered + ); + + VerifyingSharesParticipants::::try_mutate( + &network_curve, + &dkg_index, + |participants| -> DispatchResult { + ensure!( + !participants.contains(authority_index), + Error::::Round6PackagesAlreadyRegistered + ); + + if participants.is_empty() { + *participants = ParticipantsBitmap::::empty_from( + &state.get_indexes() + ); + } + + participants.insert(authority_index); + Ok(()) + })?; + + VerifyingShares::::insert(&verifying_share_key, round6.verifying_share); + + Self::deposit_event(Event::::Round6VerifyingShareRegistered { + authority_index, + network_curve, + }); + + Ok(()) + } + + #[pallet::call_index(7)] + #[pallet::weight(( + T::WeightInfo::register_nonce_commitment(), + DispatchClass::Normal, + Pays::No, + ))] + pub fn register_nonce_commitment( + origin: OriginFor, + exodus_package: ExodusPackage, + signature:::Signature, + ) -> DispatchResult { + ensure_none(origin)?; + + let network_curve = exodus_package.get_network_curve(); + let authority_index = exodus_package.get_authority_index(); + + let (mut exodus_request, roast_session) = + Self::validate_exodus_nonce_commitment_size_and_get_metadata( + &exodus_package, + &signature, + ).map_err(|_| Error::::ExodusNoncePackageInvalidProof)?; + + Self::register_latest_activity(&exodus_package)?; + + let ExodusPackage::NonceCommitment(package) = exodus_package else { + return Err(Error::::ExodusNoncePackageInvalidProof.into()); + }; + + let threshold = ActiveAuthorities::::decode_len(&network_curve) + .map(|max_participants| get_byzantium_threshold(max_participants)) + .ok_or(Error::::DkgAuthoritiesNotInitialized)?; + + let exodus_session = package.session; + + ensure!( + !RoastSessions::::contains_key(&exodus_session, authority_index), + Error::::ExodusAlreadyInRoastSession, + ); + + let mut roast_state = RoastSessionStates::::get(&exodus_session, roast_session); + + ensure!( + roast_state.status == RoastStatus::NonceCommitments, + Error::::ExodusIncorrectRoastStatus, + ); + + ensure!( + !roast_state.nonce_committers.contains(authority_index), + Error::::ExodusNonceAlreadyRegistered, + ); + + ensure!( + !roast_state.group_committers.contains(authority_index), + Error::::ExodusGroupAlreadyRegistered, + ); + + ensure!( + !roast_state.partial_signers.contains(authority_index), + Error::::ExodusSignedMessageAlreadyRegistered, + ); + + roast_state.nonce_committers.insert(authority_index); + + if roast_state.nonce_committers.count_ones::() == threshold { + roast_state.status = RoastStatus::GroupCommitments; + exodus_request.next_roast_session = roast_session + .saturating_add(1); + + ExodusRequests::::insert(&network_curve, &exodus_session, exodus_request); + } + + RoastSessions::::insert(&exodus_session, &authority_index, roast_session); + RoastSessionStates::::insert(&exodus_session, &roast_session, roast_state); + NonceCommitments::::insert( + (exodus_session, roast_session), authority_index, + ExodusSeparatedNonce::new( + package.hiding_commitment, + package.binding_commitment, + ), + ); + + Self::deposit_event(Event::::NonceCommitmentRegistered { + authority_index, + exodus_session, + network_curve, + roast_session, + }); + + Ok(()) + } + + #[pallet::call_index(8)] + #[pallet::weight(( + T::WeightInfo::register_group_commitment(), + DispatchClass::Normal, + Pays::No, + ))] + pub fn register_group_commitment( + origin: OriginFor, + exodus_package: ExodusPackage, + signature:::Signature, + ) -> DispatchResult { + ensure_none(origin)?; + + let network_curve = exodus_package.get_network_curve(); + let authority_index = exodus_package.get_authority_index(); + + let roast_session = + Self::validate_exodus_group_commitment_size_and_get_metadata( + &exodus_package, + &signature, + ).map_err(|_| Error::::ExodusGroupPackageInvalidProof)?; + + Self::register_latest_activity(&exodus_package)?; + + let ExodusPackage::GroupCommitment(package) = exodus_package else { + return Err(Error::::ExodusGroupPackageInvalidProof.into()); + }; + + let max_authorities = ActiveAuthorities::::decode_len(&network_curve) + .ok_or(Error::::DkgAuthoritiesNotInitialized)?; + + let threshold = get_byzantium_threshold(max_authorities); + let exodus_session = package.session; + + ensure!( + ExodusRequests::::contains_key(&network_curve, &exodus_session), + Error::::ExodusRequestNotFound + ); + + let group_commitment_key =( + exodus_session, + roast_session, + package.binding_factors_root, + &package.group_commitment + ); + + let mut group_participants = GroupCommitters::::try_mutate( + &group_commitment_key, + |participants| -> Result, DispatchError> { + ensure!( + !participants.contains(authority_index), + Error::::ExodusGroupAlreadyRegistered, + ); + + if participants.is_empty() { + *participants = ParticipantsBitmap::::empty(max_authorities); + } + + participants.insert(authority_index); + + Ok(participants.clone()) + })?; + + let consensus_reached = group_participants + .count_ones::() + .ge(&threshold.div_ceil(2)); + + let mut roast_state = + RoastSessionStates::::get(&exodus_session, roast_session); + + ensure!( + consensus_reached || roast_state.status == RoastStatus::GroupCommitments, + Error::::ExodusIncorrectRoastStatus, + ); + + ensure!( + roast_state.nonce_committers.contains(authority_index), + Error::::ExodusNonceNotRegistered, + ); + + ensure!( + !roast_state.partial_signers.contains(authority_index), + Error::::ExodusSignedMessageAlreadyRegistered, + ); + + group_participants.insert(authority_index); + roast_state.group_committers.insert(authority_index); + + let group_participants_count = group_participants.count_ones::(); + + if group_participants_count >= threshold.div_ceil(2) { + roast_state.group_committers |= &roast_state.nonce_committers; + roast_state.status = RoastStatus::PartialSignatures; + } + + RoastSessionStates::::insert(&exodus_session, &roast_session, roast_state); + GroupCommitters::::insert(&group_commitment_key, group_participants.clone()); + GroupCommitmentsConsensus::::mutate(&exodus_session, &roast_session, |consensus| { + let consensus_count = consensus.participants + .count_ones::(); + + if consensus_count < group_participants_count { + *consensus = ConsensusState::new( + package.group_commitment, + group_participants, + package.binding_factors_root, + ); + } + }); + + Self::deposit_event(Event::::GroupCommitmentRegistered { + network_curve, + exodus_session, + authority_index, + roast_session + }); + + Ok(()) + } + + #[pallet::call_index(9)] + #[pallet::weight(( + T::WeightInfo::register_signature_share(), + DispatchClass::Normal, + Pays::No, + ))] + pub fn register_signature_share( + origin: OriginFor, + exodus_package: ExodusPackage, + signature:::Signature, + ) -> DispatchResult { + ensure_none(origin)?; + + let network_curve = exodus_package.get_network_curve(); + let authority_index = exodus_package.get_authority_index(); + + let (roast_session, active_dkg_index) = + Self::validate_exodus_signature_share_size_and_get_metadata( + &exodus_package, + &signature, + ).map_err(|_| Error::::ExodusSharePackageInvalidProof)?; + + Self::register_latest_activity(&exodus_package)?; + + let ExodusPackage::SignatureShare(package) = exodus_package else { + return Err(Error::::ExodusSharePackageInvalidProof.into()); + }; + + let threshold = ActiveAuthorities::::decode_len(&network_curve) + .map(|max_participants| get_byzantium_threshold(max_participants)) + .ok_or(Error::::DkgAuthoritiesNotInitialized)?; + + let exodus_session = package.session; + + let commitment_consensus = + GroupCommitmentsConsensus::::get(&exodus_session, &roast_session); + + let consensus_participants_count = commitment_consensus + .participants + .count_ones::(); + + ensure!( + consensus_participants_count >= threshold.div_ceil(2), + Error::::ExodusIncorrectRoastStatus, + ); + + let exodus_request = + ExodusRequests::::get(&network_curve, &exodus_session) + .ok_or(Error::::ExodusRequestNotFound)?; + + ensure!( + match exodus_request.r#type { + ExodusRequestType::EvmRotation { .. } => { + !ExodusSignedRotations::::contains_key( + (exodus_session, NetworkType::Evm), active_dkg_index, + ) + }, + ExodusRequestType::UtxoRotation => { + !ExodusSignedRotations::::contains_key( + (exodus_session, NetworkType::Utxo), active_dkg_index, + ) + } + ExodusRequestType::EvmBridgeOut { network_id, .. } | ExodusRequestType::UtxoBridgeOut { network_id, .. } => { + !ExodusSignedBridges::::contains_key( + (exodus_session, network_id), active_dkg_index, + ) + } + ExodusRequestType::EvmGovernance { network_id, .. } => { + !ExodusSignedGovernance::::contains_key( + (exodus_session, network_id), active_dkg_index, + ) + } + }, + Error::::ExodusSignedMessageAlreadyExists, + ); + + let mut roast_state = + RoastSessionStates::::get(&exodus_session, roast_session); + + ensure!( + roast_state.status == RoastStatus::PartialSignatures, + Error::::ExodusIncorrectRoastStatus, + ); + + ensure!( + roast_state.nonce_committers.contains(authority_index), + Error::::ExodusNonceNotRegistered, + ); + + ensure!( + roast_state.group_committers.contains(authority_index), + Error::::ExodusCommitmentNotRegistered, + ); + + ensure!( + !roast_state.partial_signers.contains(authority_index), + Error::::ExodusSignedMessageAlreadyRegistered, + ); + + network_curve.verify_merkle_proof( + &package.self_binding_factor, + &package.binding_factors_proof, + commitment_consensus.merkle_root, + authority_index, + ).map_err(|_| Error::::InvalidMerkleProof)?; + + let nonce_commitment = + NonceCommitments::::get((exodus_session, roast_session), authority_index) + .ok_or(Error::::ExodusNonceNotRegistered)?; + + let verifying_share = + VerifyingShares::::get((network_curve, active_dkg_index, authority_index)) + .ok_or(Error::::VerifyingShareNotFound)?; + + let verifying_key = ActiveVerifyingKey::::get(network_curve); + + let mut message_buffer = [0u8; MAX_MESSAGE_SIZE as usize]; + let message = exodus_request.get_message(&mut message_buffer) + .ok_or(Error::::ExodusMessageTooBig)?; + + let invalid_signature_share = network_curve.verify_signature_share( + authority_index, + roast_state.group_committers.iter(), + &package.signature_share, + &package.self_binding_factor, + &nonce_commitment.hiding, + &nonce_commitment.binding, + &commitment_consensus.element_bytes, + &verifying_share, + &verifying_key, + message, + ).is_err(); + + if invalid_signature_share { + #[cfg(not(feature = "runtime-benchmarks"))] + return Err(Error::::ExodusInvalidSignatureShare.into()); + } + + let signature_scalar = + SignatureScalars::::get(exodus_session, roast_session); + + let new_signature_scalar = network_curve + .accumulate_signature_scalar( + &signature_scalar[..network_curve.scalar_bytes_len()], + &package.signature_share, + ) + .map_err(|_| Error::::ExodusAccumulationFailed)?; + + roast_state.partial_signers.insert(authority_index); + + let bounded_signature_scalar = + BoundedVec::>::try_from(new_signature_scalar) + .map_err(|_| Error::::TooManyEntries)?; + + let exodus_signed_message = ExodusSignedMessage::new( + commitment_consensus.element_bytes.iter().copied::(), + bounded_signature_scalar.iter().copied::(), + exodus_request.r#type.clone(), + ).ok_or(Error::::TooManyEntries)?; + + Self::deposit_event(Event::::PartialSignatureRegistered { + authority_index, + exodus_session, + network_curve, + roast_session + }); + + RoastSessions::::remove(&exodus_session, authority_index); + + if roast_state.partial_signers.count_ones::() < threshold { + RoastSessionStates::::insert(&exodus_session, &roast_session, roast_state); + SignatureScalars::::insert( + exodus_session, roast_session, + bounded_signature_scalar, + ); + return Ok(()); + } + + RoastSessionStates::::remove(&exodus_session, &roast_session); + SignatureScalars::::remove(&exodus_session, &roast_session); + + GroupCommitmentsConsensus::::remove(&exodus_session, &roast_session); + GroupCommitters::::remove(( + &exodus_session, &roast_session, + &commitment_consensus.merkle_root, + &commitment_consensus.element_bytes, + )); + + let invalid_aggregated_signature = network_curve + .is_signature_valid( + &verifying_key, + &commitment_consensus.element_bytes, + &bounded_signature_scalar, + message, + ) + .is_err(); + + if invalid_aggregated_signature { + #[cfg(not(feature = "runtime-benchmarks"))] + return Ok(()); + } + + ExodusRequests::::remove(&network_curve, &exodus_session); + + match exodus_request.r#type { + ExodusRequestType::EvmRotation { .. } => { + ExodusSignedRotations::::insert( + (exodus_session, NetworkType::Evm), active_dkg_index, + exodus_signed_message, + ); + }, + ExodusRequestType::UtxoRotation => { + ExodusSignedRotations::::insert( + (exodus_session, NetworkType::Utxo), active_dkg_index, + exodus_signed_message, + ); + } + ExodusRequestType::EvmBridgeOut { network_id, .. } | ExodusRequestType::UtxoBridgeOut { network_id, .. } => { + ExodusSignedBridges::::insert( + (exodus_session, network_id), active_dkg_index, + exodus_signed_message, + ); + } + ExodusRequestType::EvmGovernance { network_id, .. } => { + ExodusSignedGovernance::::insert( + (exodus_session, network_id), active_dkg_index, + exodus_signed_message, + ); + } + } + + Ok(()) + } + + #[pallet::call_index(10)] + #[pallet::weight(T::WeightInfo::register_evm_bridge_out_exodus())] + pub fn register_evm_bridge_out_exodus( + origin: OriginFor, + network_id: NetworkIdOf, + amount: BalanceOf, + commission: Perbill, + receiver: EvmAddress, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + + let _imbalance = T::Currency::withdraw( + &who, + amount, + WithdrawReasons::TRANSFER, + ExistenceRequirement::AllowDeath, + )?; + + Self::do_register_evm_bridge_out_exodus( + network_id, + amount, + commission, + receiver, + )?; + + Self::deposit_event(Event::::EvmBridgeOutRegistered { + who, + network_id, + amount, + commission, + receiver, + }); + + Ok(()) + } + } + + #[pallet::hooks] + impl Hooks> for Pallet { + fn on_initialize(current_block: BlockNumberFor) -> Weight { + let mut weight = T::DbWeight::get().reads(1); + + let converted_block: usize = current_block.unique_saturated_into(); + + let network_curve = + match T::NetworkDataHandler::network_for_block(converted_block) { + Some((_, network)) => network.curve, + None => return weight, + }; + + weight.saturating_accrue(T::DbWeight::get().reads(1)); + let mut dkg_qualification = QualificationDkgState::::get(&network_curve); + + if !dkg_qualification.is_dkg_finalized() { + let qualification_len = + match QualificationAuthorities::::decode_len(&network_curve) { + Some(total_authorities) => total_authorities as AuthIndex, + None => return weight, + }; + + let min_threshold = get_byzantium_threshold(qualification_len); + + weight.saturating_accrue(T::DbWeight::get().reads(1)); + let maybe_cursor = DkgMaybeCursor::::get(&network_curve); + + let converted_round_period: BlockNumberFor = + T::DkgRoundPeriod::get().unique_saturated_into(); + + let release_block = dkg_qualification + .get_block() + .saturating_add(converted_round_period); + + if maybe_cursor.is_none() && current_block < release_block { return weight; } + + let removal_result = match dkg_qualification.get_phase() { + DkgPhase::Vacant => DkgRemovalResult::new(|| Self::prepare_round0_storage(network_curve, &mut dkg_qualification, maybe_cursor)), + DkgPhase::Round0 => DkgRemovalResult::new(|| Self::prepare_round1_storage(network_curve, &mut dkg_qualification, maybe_cursor)), + DkgPhase::Round1 => DkgRemovalResult::new(|| Self::prepare_round2_storage(network_curve, &mut dkg_qualification, maybe_cursor)), + DkgPhase::Round2 => DkgRemovalResult::new(|| Self::prepare_round3_storage(network_curve, &mut dkg_qualification, maybe_cursor)), + DkgPhase::Round3 => DkgRemovalResult::new(|| Self::prepare_round4_storage(network_curve, min_threshold, &mut dkg_qualification, maybe_cursor)), + DkgPhase::Round4 => DkgRemovalResult::new(|| Self::prepare_round5_storage(network_curve, min_threshold, &mut dkg_qualification, maybe_cursor)), + DkgPhase::Round5 => DkgRemovalResult::new(|| Self::prepare_round6_storage(network_curve, &mut dkg_qualification, maybe_cursor)), + DkgPhase::Round6 => DkgRemovalResult::new(|| Self::prepare_round7_storage(network_curve, min_threshold, &mut dkg_qualification, maybe_cursor)), + DkgPhase::Round7 => DkgRemovalResult::new(|| Self::prepare_finalization_storage(network_curve, &mut dkg_qualification, maybe_cursor)), + DkgPhase::Finalized => return weight, + }; + + weight.saturating_accrue(removal_result.used_weight()); + if !removal_result.fully_cleared() { return weight; } + + let threshold_reached = min_threshold <= dkg_qualification.count_ones::(); + + if threshold_reached || dkg_qualification.is_dkg_vacant() { + dkg_qualification.next_phase(); + } else { + dkg_qualification.restart_dkg(); + } + + dkg_qualification.set_block(current_block); + QualificationDkgState::::insert(&network_curve, dkg_qualification); + + weight.saturating_accrue(T::DbWeight::get().writes(1)) + } + + weight + } + + fn offchain_worker(current_block: BlockNumberFor) { + if !sp_io::offchain::is_validator() { + return; + } + + log::warn!(target: PALLET_LOG_TARGET, "🗺️ Exodus starting at block #{:?}", current_block); + + match Self::start_exodus(current_block) { + Ok(inner_results) => { + for result in inner_results { + match result { + Ok(action) => log::warn!(target: PALLET_LOG_TARGET, "🗺️ Exodus action '{}' at block #{:?} finished.", action, current_block), + Err(error) => log::warn!(target: PALLET_LOG_TARGET, "🗺️ Exodus failed at block #{:?}: {:?}.", current_block, error), + } + } + + }, + Err(err) => { + log::error!(target: PALLET_LOG_TARGET, "🗺️ Exodus skipped or failed initialization at {:?}: {:?}", current_block, err); + } + } + } + } + + #[pallet::validate_unsigned] + impl ValidateUnsigned for Pallet { + type Call = Call; + fn validate_unsigned(_source: TransactionSource, call: &Self::Call) -> TransactionValidity { + match call { + Call::register_round0_package { dkg_package, signature } => { + let network_curve = dkg_package.get_network_curve(); + let authority_index = dkg_package.get_authority_index(); + + Self::validate_round0_package_size(dkg_package, signature)?; + + ValidTransaction::with_tag_prefix("GhostExodus") + .priority(T::UnsignedPriority::get()) + .longevity(T::UnsignedLongevity::get()) + .and_provides((network_curve, authority_index)) + .propagate(true) + .build() + }, + Call::register_round1_package { dkg_package, signature } => { + let network_curve = dkg_package.get_network_curve(); + let authority_index = dkg_package.get_authority_index(); + + Self::validate_round1_package_size(dkg_package, signature)?; + + ValidTransaction::with_tag_prefix("GhostExodus") + .priority(T::UnsignedPriority::get()) + .longevity(T::UnsignedLongevity::get()) + .and_provides((network_curve, authority_index)) + .propagate(true) + .build() + }, + Call::register_encrypted_round2_packages { dkg_package, signature } => { + let network_curve = dkg_package.get_network_curve(); + let authority_index = dkg_package.get_authority_index(); + + Self::validate_round2_package_size( + dkg_package, + signature, + )?; + + ValidTransaction::with_tag_prefix("GhostExodus") + .priority(T::UnsignedPriority::get()) + .longevity(T::UnsignedLongevity::get()) + .and_provides((network_curve, authority_index)) + .propagate(true) + .build() + }, + Call::register_round3_complaints { dkg_package, signature } => { + let network_curve = dkg_package.get_network_curve(); + let authority_index = dkg_package.get_authority_index(); + + Self::validate_round3_complaints_size(dkg_package, signature)?; + + ValidTransaction::with_tag_prefix("GhostExodus") + .priority(T::UnsignedPriority::get()) + .longevity(T::UnsignedLongevity::get()) + .and_provides((network_curve, authority_index)) + .propagate(true) + .build() + }, + Call::register_round4_justifications { dkg_package, signature } => { + let network_curve = dkg_package.get_network_curve(); + let authority_index = dkg_package.get_authority_index(); + + Self::validate_round4_justifications_size(dkg_package, signature)?; + + ValidTransaction::with_tag_prefix("GhostExodus") + .priority(T::UnsignedPriority::get()) + .longevity(T::UnsignedLongevity::get()) + .and_provides((network_curve, authority_index)) + .propagate(true) + .build() + }, + Call::register_round5_verifying_package { dkg_package, signature } => { + let network_curve = dkg_package.get_network_curve(); + let authority_index = dkg_package.get_authority_index(); + + Self::validate_round5_package_size(dkg_package, signature)?; + + ValidTransaction::with_tag_prefix("GhostExodus") + .priority(T::UnsignedPriority::get()) + .longevity(T::UnsignedLongevity::get()) + .and_provides((network_curve, authority_index)) + .propagate(true) + .build() + }, + Call::register_round6_public_share_package { dkg_package, signature } => { + let network_curve = dkg_package.get_network_curve(); + let authority_index = dkg_package.get_authority_index(); + + Self::validate_round6_package_size(dkg_package, signature)?; + + ValidTransaction::with_tag_prefix("GhostExodus") + .priority(T::UnsignedPriority::get()) + .longevity(T::UnsignedLongevity::get()) + .and_provides((network_curve, authority_index)) + .propagate(true) + .build() + }, + Call::register_nonce_commitment { exodus_package, signature } => { + let network_curve = exodus_package.get_network_curve(); + let authority_index = exodus_package.get_authority_index(); + let exodus_session = exodus_package.get_exodus_session(); + + let (_, roast_session) = + Self::validate_exodus_nonce_commitment_size_and_get_metadata( + exodus_package, + signature, + )?; + + let nonce_metadata = (network_curve, authority_index, exodus_session, roast_session); + + ValidTransaction::with_tag_prefix("GhostExodus") + .priority(T::UnsignedPriority::get()) + .longevity(T::UnsignedLongevity::get()) + .and_provides(nonce_metadata) + .propagate(true) + .build() + }, + Call::register_group_commitment { exodus_package, signature } => { + let network_curve = exodus_package.get_network_curve(); + let authority_index = exodus_package.get_authority_index(); + let exodus_session = exodus_package.get_exodus_session(); + + let roast_session = + Self::validate_exodus_group_commitment_size_and_get_metadata( + exodus_package, + signature, + )?; + + let group_metadata = (network_curve, authority_index, exodus_session, roast_session); + + ValidTransaction::with_tag_prefix("GhostExodus") + .priority(T::UnsignedPriority::get()) + .longevity(T::UnsignedLongevity::get()) + .and_provides(group_metadata) + .propagate(true) + .build() + + } + Call::register_signature_share { exodus_package, signature } => { + let network_curve = exodus_package.get_network_curve(); + let authority_index = exodus_package.get_authority_index(); + let exodus_session = exodus_package.get_exodus_session(); + + let (roast_session, _) = + Self::validate_exodus_signature_share_size_and_get_metadata( + exodus_package, + signature, + )?; + + let signature_share_metadata = (network_curve, authority_index, exodus_session, roast_session); + + ValidTransaction::with_tag_prefix("GhostExodus") + .priority(T::UnsignedPriority::get()) + .longevity(T::UnsignedLongevity::get()) + .and_provides(signature_share_metadata) + .propagate(true) + .build() + } + _ => InvalidTransaction::Call.into(), + } + } + } +} + +impl Pallet { + fn do_register_evm_bridge_out_exodus( + network_id: NetworkIdOf, + amount: BalanceOf, + commission: Perbill, + receiver: EvmAddress, + ) -> DispatchResult { + let network = T::NetworkDataHandler::get(&network_id) + .ok_or(Error::::NetworkDoesNotExist)?; + + ensure!( + QualificationDkgState::::get(&network.curve).is_dkg_finalized(), + Error::::DkgAuthoritiesInProgress, + ); + + ensure!(network.r#type.is_evm(), Error::::WrongNetworkType); + + let exodus_session = CurrentExodus::::get(); + let request = ExodusRequest::evm_bridge_out( + exodus_session, + network_id, + amount, + commission, + receiver + ); + + ExodusRequests::::insert(&network.curve, &exodus_session, request); + CurrentExodus::::put(exodus_session.saturating_add(1)); + + Ok(()) + } + + fn prepare_round_storage( + network_curve: NetworkCurve, + maybe_cursor: Option>, + clear_fn: ClearFn, + on_complete_fn: CompleteFn, + ) -> (Weight, bool) + where + ClearFn: FnOnce(Option<&[u8]>, &mut Weight) -> Option>, + CompleteFn: FnOnce(&mut Weight), + { + let mut weight = T::DbWeight::get().reads_writes(0, 0); + + let maybe_cursor_slice: Option<&[u8]> = + maybe_cursor.as_ref().map(|v| &v[..]); + + let fully_cleared = match clear_fn(maybe_cursor_slice, &mut weight) { + Some(cursor) => { + match BoundedVec::::try_from(cursor) { + Ok(cursor) => { + DkgMaybeCursor::::insert(&network_curve, cursor)}, + Err(_) => DkgMaybeCursor::::remove(&network_curve), // never should happen + } + weight.saturating_accrue(T::DbWeight::get().writes(1)); + false + }, + None => { + on_complete_fn(&mut weight); + true + } + }; + + (weight, fully_cleared) + } + + fn prepare_round0_storage( + network_curve: NetworkCurve, + dkg_qualification: &mut QualifyingState, + maybe_cursor: Option>, + ) -> (Weight, bool) { + Self::prepare_round_storage( + network_curve, + maybe_cursor, + |cursor_slice, weight| { + let result = Round0Packages::::clear( + T::RemovalLimit::get(), + cursor_slice, + ); + + weight.saturating_accrue( + T::DbWeight::get().reads_writes( + (result.backend as u64).saturating_add(1), + result.backend as u64 + ) + ); + + result.maybe_cursor + }, + |_| { + dkg_qualification.nullify_indexes(); + }, + ) + } + + fn prepare_round1_storage( + network_curve: NetworkCurve, + dkg_qualification: &mut QualifyingState, + maybe_cursor: Option>, + ) -> (Weight, bool) { + Self::prepare_round_storage( + network_curve, + maybe_cursor, + |_, weight| { + let participants_len = dkg_qualification.get_indexes().active_bits_count(); + let empty_round1_packages = ParticipantsBitmap::::empty(participants_len); + + weight.saturating_accrue(T::DbWeight::get().writes(1)); + Round1Packages::::insert(network_curve, empty_round1_packages); + None + }, + |_| {} + ) + } + + fn prepare_round2_storage( + network_curve: NetworkCurve, + dkg_qualification: &mut QualifyingState, + maybe_cursor: Option>, + ) -> (Weight, bool) { + let participants_len = dkg_qualification.get_indexes().active_bits_count(); + Self::prepare_round_storage( + network_curve, + maybe_cursor, + |_, weight| { + weight.saturating_accrue(T::DbWeight::get().writes(1)); + let empty_round2_packages = ParticipantsBitmap::::empty(participants_len); + Round2Packages::::insert(network_curve, empty_round2_packages); + None + }, + |weight| { + weight.saturating_accrue(T::DbWeight::get().reads(1)); + let round1_participants = Round1Packages::::get(&network_curve); + dkg_qualification.intersect_indexes(&round1_participants); + }, + ) + } + + pub fn prepare_round3_storage( + network_curve: NetworkCurve, + dkg_qualification: &mut QualifyingState, + maybe_cursor: Option>, + ) -> (Weight, bool) { + Self::prepare_round_storage( + network_curve, + maybe_cursor, + |_, weight| { + weight.saturating_accrue(T::DbWeight::get().writes(1)); + Complaints::::remove(network_curve); + None + }, + |weight| { + weight.saturating_accrue(T::DbWeight::get().reads(1)); + let round2_packages = Round2Packages::::get(&network_curve); + dkg_qualification.intersect_indexes(&round2_packages); + } + ) + } + + pub fn prepare_round4_storage( + network_curve: NetworkCurve, + min_threshold: AuthIndex, + dkg_qualification: &mut QualifyingState, + maybe_cursor: Option>, + ) -> (Weight, bool) { + let dkg_index = dkg_qualification.get_dkg_index(); + + Self::prepare_round_storage( + network_curve, + maybe_cursor, + |cursor_slice, weight| { + let prev_dkg_index = dkg_index.saturating_sub(1); + let result = Verifications::::clear_prefix( + (network_curve, prev_dkg_index), + T::RemovalLimit::get(), + cursor_slice, + ); + + weight.saturating_accrue( + T::DbWeight::get().reads_writes( + (result.backend as u64).saturating_add(1), + result.backend as u64 + ) + ); + + let maybe_cursor = result.maybe_cursor; + + if maybe_cursor.is_none() { + Justifications::::remove(network_curve); + weight.saturating_accrue(T::DbWeight::get().writes(1)); + } + + maybe_cursor + }, + |weight| { + let qualification_indexes = dkg_qualification.get_indexes(); + let safe_count = min_threshold.saturating_sub(1); + + let mut offence_counters = BTreeMap::::new(); + + let mut compromised_participants = ParticipantsBitmap::::empty_from( + &qualification_indexes, + ); + let mut active_participants = ParticipantsBitmap::::empty_from( + &qualification_indexes, + ); + + weight.saturating_accrue(T::DbWeight::get().reads(1)); + let complaints = Complaints::::get(&network_curve); + + complaints.iter().for_each(|(&auth_index, bitmap)| { + active_participants.insert(auth_index); + bitmap.iter::().for_each(|index| { + offence_counters.entry(index) + .and_modify(|count| *count = count.saturating_add(1)) + .or_insert(1); + }) + }); + + offence_counters.iter() + .filter(|(_, &count)| count >= safe_count) + .for_each(|(&auth_index, _)| { + compromised_participants.insert(auth_index); + }); + + let result_mask = qualification_indexes + & active_participants + & !compromised_participants; + + dkg_qualification.intersect_indexes(&result_mask); + } + ) + } + + pub fn prepare_round5_storage( + network_curve: NetworkCurve, + min_threshold: AuthIndex, + dkg_qualification: &mut QualifyingState, + maybe_cursor: Option>, + ) -> (Weight, bool) { + let dkg_index = dkg_qualification.get_dkg_index(); + + Self::prepare_round_storage( + network_curve, + maybe_cursor, + |_, weight| { + let prev_dkg_index = dkg_index.saturating_sub(1); + + weight.saturating_accrue(T::DbWeight::get().writes(1)); + VerifyingKeyConsensus::::remove(network_curve, &prev_dkg_index); + None + }, + |weight| { + weight.saturating_accrue(T::DbWeight::get().reads(2)); + let justifications_bitmap = Justifications::::get(network_curve); + let complaints_bitmap = Complaints::::get(network_curve); + + let safe_count = min_threshold.saturating_sub(1); + let mut number_of_leaked_keys = 0; + + let qualified_indexes = dkg_qualification.get_indexes(); + + let mut failed_authorities = ParticipantsBitmap::::empty_from( + &qualified_indexes, + ); + + let mut active_authorities = ParticipantsBitmap::::empty_from( + &qualified_indexes, + ); + + let mut complaints_matrix: BTreeMap> = + BTreeMap::new(); + + complaints_bitmap.iter().for_each(|(&accuser_index, offenders_bitmap)| { + offenders_bitmap.iter().for_each(|offended_index| { + complaints_matrix + .entry(offended_index) + .or_insert_with(|| { + ParticipantsBitmap::::empty_from(&qualified_indexes) + }) + .insert(accuser_index); + }); + }); + + justifications_bitmap.iter().for_each(|(&offended_index, recipients_bitmap)| { + active_authorities.insert(offended_index); + + if recipients_bitmap.count_ones::() >= safe_count { + number_of_leaked_keys.saturating_inc(); + } + + if let Some(accusers_bitmap) = complaints_matrix.get(&offended_index) { + let successful_justifications = accusers_bitmap & recipients_bitmap; + let failed_to_justify = successful_justifications != *accusers_bitmap; + + if failed_to_justify { + failed_authorities.insert(offended_index); + } + } + }); + + let result_mask = if number_of_leaked_keys >= min_threshold { + ParticipantsBitmap::::empty_from(&qualified_indexes) + } else { + active_authorities & !failed_authorities + }; + + dkg_qualification.intersect_indexes(&result_mask); + } + ) + } + + fn prepare_round6_storage( + network_curve: NetworkCurve, + dkg_qualification: &mut QualifyingState, + maybe_cursor: Option>, + ) -> (Weight, bool) { + let dkg_index = dkg_qualification.get_dkg_index(); + let default_participants = ParticipantsBitmap::::empty_from( + &dkg_qualification.get_indexes(), + ); + + Self::prepare_round_storage( + network_curve, + maybe_cursor, + |_, weight| { + let Some(prev_dkg_index) = dkg_index.checked_sub(1) else { + return None; + }; + + weight.saturating_accrue(T::DbWeight::get().writes(1)); + VerifyingSharesParticipants::::insert( + &network_curve, + &prev_dkg_index, + default_participants, + ); + + None + }, + |weight| { + weight.saturating_accrue(T::DbWeight::get().reads(1)); + let state = VerifyingKeyConsensus::::get(&network_curve, &dkg_index); + + dkg_qualification.intersect_indexes(&state.participants); + }) + } + + fn prepare_round7_storage( + network_curve: NetworkCurve, + min_threshold: AuthIndex, + dkg_qualification: &mut QualifyingState, + maybe_cursor: Option>, + ) -> (Weight, bool) { + let dkg_index = dkg_qualification.get_dkg_index(); + + Self::prepare_round_storage( + network_curve, + maybe_cursor, + |_, _| None, + |weight| { + weight.saturating_accrue(T::DbWeight::get().reads(1)); + + let participants = + VerifyingSharesParticipants::::get(&network_curve, dkg_index); + + dkg_qualification.intersect_indexes(&participants); + + if dkg_qualification.count_ones::() < min_threshold { + dkg_qualification.nullify_indexes(); + return; + } + + weight.saturating_accrue(T::DbWeight::get().reads(1)); + let consensus = VerifyingKeyConsensus::::get(&network_curve, &dkg_index); + let verifying_key_slice = consensus.element_bytes.as_slice(); + + weight.saturating_accrue(T::DbWeight::get().reads(2)); + let mut exodus_session = CurrentExodus::::get(); + for network_type in T::NetworkDataHandler::iter_types_by_curve(&network_curve) { + let exodus_request = match network_type { + NetworkType::Evm => { + if verifying_key_slice.len() < 33 { + dkg_qualification.nullify_indexes(); + return; + } + + let parity_byte = verifying_key_slice[0]; + let parity = if parity_byte % 2 == 0 { 0u8 } else { 1u8 }; + + let public_key = EvmBytes32::from_slice(&verifying_key_slice[1..33]); + + ExodusRequest::evm_rotation(exodus_session, public_key, parity) + } + _ => { + continue; + } + }; + + weight.saturating_accrue(T::DbWeight::get().writes(1)); + ExodusRequests::::insert(&network_curve, &exodus_session, exodus_request); + + if let None = dkg_qualification + .try_push_rotation_session(exodus_session, network_type) { + break; + } + + exodus_session = exodus_session.saturating_add(1); + } + + CurrentExodus::::put(exodus_session); + weight.saturating_accrue(T::DbWeight::get().writes(1)); + + dkg_qualification.set_verifying_key(consensus.element_bytes); + }) + } + + fn prepare_finalization_storage( + network_curve: NetworkCurve, + dkg_qualification: &mut QualifyingState, + maybe_cursor: Option>, + ) -> (Weight, bool) { + Self::prepare_round_storage( + network_curve, + maybe_cursor, + |_, _| None, + |weight| { + weight.saturating_accrue(T::DbWeight::get().reads(2)); + let dkg_index = ActiveDkgAuthorities::::get(&network_curve) + .get_dkg_index(); + + let is_initial_dkg_round = + !ActiveVerifyingKey::::contains_key(network_curve); + + let all_rotations_have_signatures = dkg_qualification + .iter_rotation_sessions() + .all(|rotation_session| { + let session = rotation_session.exodus_session; + let r#type = rotation_session.network_type; + + weight.saturating_accrue(T::DbWeight::get().reads(1)); + if ExodusRequests::::contains_key(&network_curve, &session) { + return false + }; + + weight.saturating_accrue(T::DbWeight::get().reads(1)); + ExodusSignedRotations::::contains_key((session, r#type), dkg_index) + }); + + + if all_rotations_have_signatures || is_initial_dkg_round { + let verifying_key = dkg_qualification.get_verifying_key(); + let activated_state: ActivatedState = (&*dkg_qualification).into(); + + weight.saturating_accrue(T::DbWeight::get().reads(1)); + let qualified_authorities = QualificationAuthorities::::get(&network_curve); + + weight.saturating_accrue(T::DbWeight::get().writes(3)); + ActiveDkgAuthorities::::insert(&network_curve, activated_state); + ActiveVerifyingKey::::insert(&network_curve, verifying_key); + ActiveAuthorities::::set(&network_curve, qualified_authorities); + } else { + dkg_qualification.nullify_indexes(); + } + }) + } + + fn register_latest_activity( + package_ref: &impl PackageMetadata, + ) -> DispatchResult { + let authority_index = package_ref.get_authority_index(); + let current_block = T::BlockNumberProvider::current_block_number(); + + ExodusActivity::::try_mutate(&authority_index, + |stored_block| -> DispatchResult { + ensure!( + *stored_block < current_block, + Error::::PackagesAlreadyRegistered, + ); + *stored_block = current_block; + Ok(()) + })?; + + Ok(()) + } + + fn validate_round0_package_size( + dkg_package: & DkgPackage, + signature: &::Signature, + ) -> Result<(), InvalidTransaction> { + let network_curve = dkg_package.get_network_curve(); + let authority_index = dkg_package.get_authority_index(); + + if !T::NetworkDataHandler::curve_exists(&network_curve) { + return Err(InvalidTransaction::BadProof); + } + + let state = QualificationDkgState::::get(&network_curve); + + if !state.is_zero_phase() { + return Err(InvalidTransaction::BadProof); + } + + if state.contains_index(authority_index) { + return Err(InvalidTransaction::BadSigner); + } + + let authority = QualificationAuthorities::::get(&network_curve) + .get(authority_index as usize) + .cloned() + .ok_or(InvalidTransaction::BadSigner)?; + + let encoded_package = dkg_package.encode(); + if !authority.verify(&encoded_package, signature) { + return Err(InvalidTransaction::BadProof); + } + + Ok(()) + } + + fn validate_round1_package_size( + dkg_package: &DkgPackage, + signature: &::Signature, + ) -> Result<(), InvalidTransaction> { + let DkgPackage::Round1(round1) = dkg_package else { + return Err(InvalidTransaction::BadProof); + }; + + let network_curve = dkg_package.get_network_curve(); + let authority_index = dkg_package.get_authority_index(); + + if !T::NetworkDataHandler::curve_exists(&network_curve) { + return Err(InvalidTransaction::BadProof); + } + + let state = QualificationDkgState::::get(&network_curve); + + if !state.is_first_phase() { + return Err(InvalidTransaction::BadProof); + } + + if !state.contains_index(authority_index) { + return Err(InvalidTransaction::BadSigner); + } + + let authorities = QualificationAuthorities::::get(&network_curve); + let expected_count = get_byzantium_threshold(authorities.len()); + + let expected_package_len = round1_package_size( + expected_count, + network_curve.element_bytes_len(), + network_curve.scalar_bytes_len(), + network_curve.header_bytes_len(), + ); + + if round1.package.len() != expected_package_len { + return Err(InvalidTransaction::BadProof); + } + + let authority = authorities + .get(authority_index as usize) + .ok_or(InvalidTransaction::BadSigner)?; + + let encoded_package = dkg_package.encode(); + if !authority.verify(&encoded_package, signature) { + return Err(InvalidTransaction::BadProof); + } + + Ok(()) + } + + fn validate_round2_package_size( + dkg_package: &DkgPackage, + signature: &::Signature, + ) -> Result<(), InvalidTransaction> { + let DkgPackage::Round2(round2) = dkg_package else { + return Err(InvalidTransaction::BadProof); + }; + + let network_curve = dkg_package.get_network_curve(); + let authority_index = dkg_package.get_authority_index(); + + if !T::NetworkDataHandler::curve_exists(&network_curve) { + return Err(InvalidTransaction::BadProof); + } + + let state = QualificationDkgState::::get(&network_curve); + + if !state.is_second_phase() { + return Err(InvalidTransaction::BadProof); + } + + if !state.contains_index(authority_index) { + return Err(InvalidTransaction::BadSigner); + } + + let bundle_packages_count = round2.bundle.bitmask + .iter() + .map(|byte| byte.count_ones() as usize) + .sum::() + .saturating_add(1); + + if bundle_packages_count != state.count_ones::() { + return Err(InvalidTransaction::BadProof); + } + + let has_invalid_round2_recipients = round2.bundle.bitmask + .iter() + .enumerate() + .any(|(byte_idx, &byte)| { + if byte == 0 { return false; } + (0..8).any(|bit_idx| { + if (byte & (1u8 << bit_idx)) == 0 { return false; } + let global_index = byte_idx * 8 + bit_idx; + if global_index == authority_index as usize { return true; } + !state.contains_index(global_index) + }) + }); + + if has_invalid_round2_recipients { + return Err(InvalidTransaction::BadProof); + } + + let padded_participants = state.highest_bit::() + .map(|bit_pos| bit_pos.saturating_add(1)) + .unwrap_or_default(); + + let expected_blob_len = round2_encrypted_package_size( + padded_participants, + network_curve.scalar_bytes_len(), + network_curve.header_bytes_len(), + ); + + if round2.bundle.blob.len() != expected_blob_len { + return Err(InvalidTransaction::BadProof); + } + + let authority = QualificationAuthorities::::get(&network_curve) + .get(authority_index as usize) + .cloned() + .ok_or(InvalidTransaction::BadSigner)?; + + let encoded_package = dkg_package.encode(); + if !authority.verify(&encoded_package, signature) { + return Err(InvalidTransaction::BadProof); + } + + Ok(()) + } + + fn validate_round3_complaints_size( + dkg_package: &DkgPackage, + signature: &::Signature, + ) -> Result<(), InvalidTransaction> { + let DkgPackage::Round3(round3_complaints) = dkg_package else { + return Err(InvalidTransaction::BadProof); + }; + + let network_curve = dkg_package.get_network_curve(); + let authority_index = dkg_package.get_authority_index(); + + if !T::NetworkDataHandler::curve_exists(&network_curve) { + return Err(InvalidTransaction::BadProof); + } + + let state = QualificationDkgState::::get(&network_curve); + + if !state.is_third_phase() { + return Err(InvalidTransaction::BadProof); + } + + if !state.contains_index(authority_index) { + return Err(InvalidTransaction::BadSigner); + } + + let estimated_indices_len = state + .highest_bit::() + .map(|bit_index| bit_index.saturating_add(1).div_ceil(8)) + .unwrap_or_default(); + + if estimated_indices_len != round3_complaints.indices.len() { + return Err(InvalidTransaction::BadProof); + } + + let has_invalid_accused_indexes = round3_complaints.indices + .iter() + .enumerate() + .any(|(byte_idx, &byte)| { + if byte == 0 { return false; } + (0..8).any(|bit_idx| { + if (byte & (1u8 << bit_idx)) == 0 { return false; } + let global_index = byte_idx * 8 + bit_idx; + if global_index == authority_index as usize { return true; } + !state.contains_index(global_index) + }) + }); + + if has_invalid_accused_indexes { + return Err(InvalidTransaction::BadProof); + } + + let authority = QualificationAuthorities::::get(&network_curve) + .get(authority_index as usize) + .cloned() + .ok_or(InvalidTransaction::BadSigner)?; + + let encoded_package = dkg_package.encode(); + if !authority.verify(&encoded_package, signature) { + return Err(InvalidTransaction::BadProof); + } + + Ok(()) + } + + fn validate_round4_justifications_size( + dkg_package: &DkgPackage, + signature: &::Signature, + ) -> Result<(), InvalidTransaction> { + let DkgPackage::Round4(round4) = dkg_package else { + return Err(InvalidTransaction::BadProof); + }; + + let network_curve = dkg_package.get_network_curve(); + let authority_index = dkg_package.get_authority_index(); + + if !T::NetworkDataHandler::curve_exists(&network_curve) { + return Err(InvalidTransaction::BadProof); + } + + let state = QualificationDkgState::::get(&network_curve); + + if !state.is_fourth_phase() { + return Err(InvalidTransaction::BadProof); + } + + if !state.contains_index(authority_index) { + return Err(InvalidTransaction::BadSigner); + } + + let bundle_packages_count = round4.bundle.bitmask + .iter() + .map(|byte| byte.count_ones() as usize) + .sum::() + .saturating_add(1); + + if bundle_packages_count > state.count_ones::() { + return Err(InvalidTransaction::BadProof); + } + + let has_invalid_justifications = round4.bundle.bitmask + .iter() + .enumerate() + .any(|(byte_idx, &byte)| { + if byte == 0 { return false; } + (0..8).any(|bit_idx| { + if (byte & (1u8 << bit_idx)) == 0 { return false; } + let global_index = byte_idx * 8 + bit_idx; + if global_index == authority_index as usize { return true; } + !state.contains_index(global_index) + }) + }); + + if has_invalid_justifications { + return Err(InvalidTransaction::BadProof); + } + + let padded_participants = state.highest_bit::() + .map(|bit_pos| bit_pos.saturating_add(1)) + .unwrap_or_default(); + + let expected_blob_len = round2_package_size( + padded_participants, + network_curve.scalar_bytes_len(), + network_curve.header_bytes_len(), + ); + + if round4.bundle.blob.len() != expected_blob_len { + return Err(InvalidTransaction::BadProof); + } + + let authority = QualificationAuthorities::::get(&network_curve) + .get(authority_index as usize) + .cloned() + .ok_or(InvalidTransaction::BadSigner)?; + + let encoded_package = dkg_package.encode(); + if !authority.verify(&encoded_package, signature) { + return Err(InvalidTransaction::BadProof); + } + + Ok(()) + } + + fn validate_round5_package_size( + dkg_package: &DkgPackage, + signature: &::Signature, + ) -> Result<(), InvalidTransaction> { + let network_curve = dkg_package.get_network_curve(); + let authority_index = dkg_package.get_authority_index(); + + if !T::NetworkDataHandler::curve_exists(&network_curve) { + return Err(InvalidTransaction::BadProof); + } + + let state = QualificationDkgState::::get(&network_curve); + + if !state.is_fifth_phase() { + return Err(InvalidTransaction::BadProof); + } + + if !state.contains_index(authority_index) { + return Err(InvalidTransaction::BadSigner); + } + + let estimated_max_size = network_curve + .element_bytes_len() + .saturating_add(ExodusHash::len_bytes()); + + if dkg_package.bytes_len() as usize != estimated_max_size { + return Err(InvalidTransaction::BadProof); + } + + let authority = QualificationAuthorities::::get(&network_curve) + .get(authority_index as usize) + .cloned() + .ok_or(InvalidTransaction::BadSigner)?; + + let encoded_package = dkg_package.encode(); + if !authority.verify(&encoded_package, signature) { + return Err(InvalidTransaction::BadProof); + } + + Ok(()) + } + + fn validate_round6_package_size( + dkg_package: &DkgPackage, + signature: &::Signature, + ) -> Result<(), InvalidTransaction> { + let network_curve = dkg_package.get_network_curve(); + let authority_index = dkg_package.get_authority_index(); + + if !T::NetworkDataHandler::curve_exists(&network_curve) { + return Err(InvalidTransaction::BadProof); + } + + let state = QualificationDkgState::::get(&network_curve); + + if !state.is_sixth_phase() { + return Err(InvalidTransaction::BadProof); + } + + if !state.contains_index(authority_index) { + return Err(InvalidTransaction::BadSigner); + } + + let estimated_proof_count = state + .count_ones::() + .next_power_of_two() + .trailing_zeros(); + + if dkg_package.bytes_len() != estimated_proof_count { + return Err(InvalidTransaction::BadProof); + } + + let authority = QualificationAuthorities::::get(&network_curve) + .get(authority_index as usize) + .cloned() + .ok_or(InvalidTransaction::BadSigner)?; + + let encoded_package = dkg_package.encode(); + if !authority.verify(&encoded_package, signature) { + return Err(InvalidTransaction::BadProof); + } + + Ok(()) + } + + fn validate_exodus_nonce_commitment_size_and_get_metadata<'a>( + exodus_package: &'a ExodusPackage, + signature: &::Signature, + ) -> Result<(ExodusRequest, BalanceOf>, RoastSession), InvalidTransaction> { + let ExodusPackage::NonceCommitment(package) = exodus_package else { + return Err(InvalidTransaction::BadProof); + }; + + let network_curve = exodus_package.get_network_curve(); + let authority_index = exodus_package.get_authority_index(); + + if package.hiding_commitment.len() != network_curve.element_bytes_len() { + return Err(InvalidTransaction::BadProof); + } + + if package.binding_commitment.len() != network_curve.element_bytes_len() { + return Err(InvalidTransaction::BadProof); + } + + if !T::NetworkDataHandler::curve_exists(&network_curve) { + return Err(InvalidTransaction::BadProof); + } + + if QualificationDkgState::::get(&network_curve).is_dkg_pending() { + return Err(InvalidTransaction::BadProof); + } + + let active_state = ActiveDkgAuthorities::::get(&network_curve); + + if !active_state.contains_index(authority_index) { + return Err(InvalidTransaction::BadSigner); + } + + let authority = ActiveAuthorities::::get(&network_curve) + .get(authority_index as usize) + .cloned() + .ok_or(InvalidTransaction::BadSigner)?; + + let encoded_package = exodus_package.encode(); + if !authority.verify(&encoded_package, signature) { + return Err(InvalidTransaction::BadProof); + } + + let exodus_request = + ExodusRequests::::get(&network_curve, &package.session) + .ok_or(InvalidTransaction::BadProof)?; + + let roast_session = + match RoastSessions::::get(&package.session, &authority_index) { + Some(_) => return Err(InvalidTransaction::BadProof), + None => exodus_request.next_roast_session, + }; + + Ok((exodus_request, roast_session)) + } + + fn validate_exodus_group_commitment_size_and_get_metadata<'a>( + exodus_package: &'a ExodusPackage, + signature: &::Signature, + ) -> Result { + let ExodusPackage::GroupCommitment(package) = exodus_package else { + return Err(InvalidTransaction::BadProof); + }; + + let network_curve = exodus_package.get_network_curve(); + let authority_index = exodus_package.get_authority_index(); + + if !T::NetworkDataHandler::curve_exists(&network_curve) { + return Err(InvalidTransaction::BadProof); + } + + if package.group_commitment.len() != network_curve.element_bytes_len() { + return Err(InvalidTransaction::BadProof); + } + + if QualificationDkgState::::get(&network_curve).is_dkg_pending() { + return Err(InvalidTransaction::BadProof); + } + + let active_state = ActiveDkgAuthorities::::get(&network_curve); + + if !active_state.contains_index(authority_index) { + return Err(InvalidTransaction::BadSigner); + } + + let authority = ActiveAuthorities::::get(&network_curve) + .get(authority_index as usize) + .cloned() + .ok_or(InvalidTransaction::BadSigner)?; + + let encoded_package = exodus_package.encode(); + if !authority.verify(&encoded_package, signature) { + return Err(InvalidTransaction::BadProof); + } + + RoastSessions::::get(&package.session, &authority_index) + .ok_or(InvalidTransaction::BadProof) + } + + fn validate_exodus_signature_share_size_and_get_metadata<'a>( + exodus_package: &'a ExodusPackage, + signature: &::Signature, + ) -> Result<(RoastSession, DkgIndex), InvalidTransaction> { + let ExodusPackage::SignatureShare(package) = exodus_package else { + return Err(InvalidTransaction::BadProof); + }; + + let network_curve = exodus_package.get_network_curve(); + let authority_index = exodus_package.get_authority_index(); + + if package.signature_share.len() != network_curve.scalar_bytes_len() { + return Err(InvalidTransaction::BadProof); + } + + if package.self_binding_factor.len() != network_curve.scalar_bytes_len() { + return Err(InvalidTransaction::BadProof); + } + + if !T::NetworkDataHandler::curve_exists(&network_curve) { + return Err(InvalidTransaction::BadProof); + } + + let qualification_state = QualificationDkgState::::get(&network_curve); + if qualification_state.is_dkg_pending() { + return Err(InvalidTransaction::BadProof); + } + + let active_state = ActiveDkgAuthorities::::get(&network_curve); + + if !active_state.contains_index(authority_index) { + return Err(InvalidTransaction::BadSigner); + } + + let authority = ActiveAuthorities::::get(&network_curve) + .get(authority_index as usize) + .cloned() + .ok_or(InvalidTransaction::BadSigner)?; + + let encoded_package = exodus_package.encode(); + if !authority.verify(&encoded_package, signature) { + return Err(InvalidTransaction::BadProof); + } + + let roast_session = + RoastSessions::::get(&package.session, &authority_index) + .ok_or(InvalidTransaction::BadProof)?; + + Ok((roast_session, active_state.get_dkg_index())) + } + + fn start_exodus( + current_block: BlockNumberFor, + ) -> ExodusResult>>> { + let converted_block: usize = current_block.unique_saturated_into(); + let network_curve = T::NetworkDataHandler::network_for_block(converted_block) + .map(|(_, network)| network.curve) + .ok_or(ExodusError::NoStoredNetworks)?; + + let qualification = QualificationDkgState::::get(&network_curve); + + if qualification.is_dkg_pending() { + Self::dkg_start(network_curve, current_block, qualification) + } else { + Self::exodus_start(network_curve, current_block) + } + } + + fn exodus_start( + network_curve: NetworkCurve, + current_block: BlockNumberFor, + ) -> ExodusResult>>> { + let network_encoded = network_curve.encode(); + let exodus_lock_key = Self::create_storage_key(b"exodus-lock-", &network_encoded); + let lock_until = rt_offchain::Duration::from_millis(MIN_LOCK_GUARD_PERIOD); + + let mut exodus_lock = StorageLock::