8 Commits

Author SHA1 Message Date
Luke Parker
8bafeab5b3 Tidy serai-signals-pallet
Adds `serai-validator-sets-pallet` and `serai-signals-pallet` to the runtime.
2025-09-16 08:45:02 -04:00
Luke Parker
3722df7326 Introduce KeyShares struct to represent the amount of key shares
Improvements, bug fixes associated.
2025-09-16 08:45:02 -04:00
Luke Parker
ddb8e1398e Finally make modular-frost work with alloc alone
Carries the update to `frost-schnorrkel` and `bitcoin-serai`.
2025-09-16 08:45:02 -04:00
Luke Parker
2be69b23b1 Tweak multiexp to compile on core
On `core`, it'll use a serial implementation of no benefit other than the fact
that when `alloc` _is_ enabled, it'll use the multi-scalar multiplication
algorithms.

`schnorr-signatures` was prior tweaked to include a shim for
`SchnorrSignature::verify` which didn't use `multiexp_vartime` yet this same
premise. Now, instead of callers writing these shims, it's within `multiexp`.
2025-09-16 08:45:02 -04:00
Luke Parker
a82ccadbb0 Correct std-shims feature flagging 2025-09-16 08:45:02 -04:00
Luke Parker
1ff2934927 cargo update 2025-09-16 08:44:54 -04:00
Luke Parker
cd4ffa862f Remove coins, validator-sets use of Substrate's event system
We've defined our own.
2025-09-15 21:32:20 -04:00
Luke Parker
c0a4d85ae6 Restore claim_deallocation call to validator-sets pallet 2025-09-15 21:32:01 -04:00
50 changed files with 1294 additions and 954 deletions

315
Cargo.lock generated
View File

@@ -23,11 +23,11 @@ dependencies = [
[[package]] [[package]]
name = "addr2line" name = "addr2line"
version = "0.25.0" version = "0.25.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9acbfca36652500c911ddb767ed433e3ed99b032b5d935be73c6923662db1d43" checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b"
dependencies = [ dependencies = [
"gimli 0.32.2", "gimli 0.32.3",
] ]
[[package]] [[package]]
@@ -197,9 +197,9 @@ dependencies = [
[[package]] [[package]]
name = "alloy-eips" name = "alloy-eips"
version = "1.0.30" version = "1.0.32"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a15b4b0f6bab47aae017d52bb5a739bda381553c09fb9918b7172721ef5f5de" checksum = "5cd749c57f38f8cbf433e651179fc5a676255e6b95044f467d49255d2b81725a"
dependencies = [ dependencies = [
"alloy-eip2124", "alloy-eip2124",
"alloy-eip2930", "alloy-eip2930",
@@ -219,9 +219,9 @@ dependencies = [
[[package]] [[package]]
name = "alloy-genesis" name = "alloy-genesis"
version = "1.0.30" version = "1.0.32"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33ba1cbc25a07e0142e8875fcbe80e1fdb02be8160ae186b90f4b9a69a72ed2b" checksum = "7d32cbf6c26d7d87e8a4e5925bbce41456e0bbeed95601add3443af277cd604e"
dependencies = [ dependencies = [
"alloy-eips", "alloy-eips",
"alloy-primitives", "alloy-primitives",
@@ -257,9 +257,9 @@ dependencies = [
[[package]] [[package]]
name = "alloy-json-rpc" name = "alloy-json-rpc"
version = "1.0.30" version = "1.0.32"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8882ec8e4542cfd02aadc6dccbe90caa73038f60016d936734eb6ced53d2167" checksum = "f614019a029c8fec14ae661aa7d4302e6e66bdbfb869dab40e78dcfba935fc97"
dependencies = [ dependencies = [
"alloy-primitives", "alloy-primitives",
"alloy-sol-types", "alloy-sol-types",
@@ -343,7 +343,7 @@ dependencies = [
"derive_more 2.0.1", "derive_more 2.0.1",
"foldhash 0.1.5", "foldhash 0.1.5",
"hashbrown 0.15.5", "hashbrown 0.15.5",
"indexmap 2.11.1", "indexmap 2.11.3",
"itoa", "itoa",
"k256", "k256",
"keccak-asm", "keccak-asm",
@@ -419,9 +419,9 @@ dependencies = [
[[package]] [[package]]
name = "alloy-rpc-client" name = "alloy-rpc-client"
version = "1.0.30" version = "1.0.32"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25289674cd8c58fcca2568b5350423cb0dd7bca8c596c5e2869bfe4c5c57ed14" checksum = "33732242ca63f107f5f8284190244038905fb233280f4b7c41f641d4f584d40d"
dependencies = [ dependencies = [
"alloy-json-rpc", "alloy-json-rpc",
"alloy-primitives", "alloy-primitives",
@@ -450,9 +450,9 @@ dependencies = [
[[package]] [[package]]
name = "alloy-rpc-types-debug" name = "alloy-rpc-types-debug"
version = "1.0.30" version = "1.0.32"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2fe118e6c152d54cb4549b9835fb87d38b12754bb121375183ee3ec84bd0849" checksum = "d46cb226f1c8071875f4d0d8a0eb3ac571fcc49cd3bcdc20a5818de7b6ef0634"
dependencies = [ dependencies = [
"alloy-primitives", "alloy-primitives",
"derive_more 2.0.1", "derive_more 2.0.1",
@@ -497,9 +497,9 @@ dependencies = [
[[package]] [[package]]
name = "alloy-serde" name = "alloy-serde"
version = "1.0.30" version = "1.0.32"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1b3b1078b8775077525bc9fe9f6577e815ceaecd6c412a4f3b4d8aa2836e8f6" checksum = "04dfe41a47805a34b848c83448946ca96f3d36842e8c074bcf8fa0870e337d12"
dependencies = [ dependencies = [
"alloy-primitives", "alloy-primitives",
"serde", "serde",
@@ -508,9 +508,9 @@ dependencies = [
[[package]] [[package]]
name = "alloy-signer" name = "alloy-signer"
version = "1.0.30" version = "1.0.32"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10ab1b8d4649bf7d0db8ab04e31658a6cc20364d920795484d886c35bed3bab4" checksum = "f79237b4c1b0934d5869deea4a54e6f0a7425a8cd943a739d6293afdf893d847"
dependencies = [ dependencies = [
"alloy-primitives", "alloy-primitives",
"async-trait", "async-trait",
@@ -571,7 +571,7 @@ dependencies = [
"alloy-sol-macro-input", "alloy-sol-macro-input",
"const-hex", "const-hex",
"heck 0.5.0", "heck 0.5.0",
"indexmap 2.11.1", "indexmap 2.11.3",
"proc-macro-error2", "proc-macro-error2",
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -620,9 +620,9 @@ dependencies = [
[[package]] [[package]]
name = "alloy-transport" name = "alloy-transport"
version = "1.0.30" version = "1.0.32"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dce5129146a76ca6139a19832c75ad408857a56bcd18cd2c684183b8eacd78d8" checksum = "cb43750e137fe3a69a325cd89a8f8e2bbf4f83e70c0f60fbe49f22511ca075e8"
dependencies = [ dependencies = [
"alloy-json-rpc", "alloy-json-rpc",
"alloy-primitives", "alloy-primitives",
@@ -1115,11 +1115,11 @@ dependencies = [
[[package]] [[package]]
name = "async-io" name = "async-io"
version = "2.5.0" version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19634d6336019ef220f09fd31168ce5c184b295cbf80345437cc36094ef223ca" checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc"
dependencies = [ dependencies = [
"async-lock", "autocfg",
"cfg-if", "cfg-if",
"concurrent-queue", "concurrent-queue",
"futures-io", "futures-io",
@@ -1128,7 +1128,7 @@ dependencies = [
"polling", "polling",
"rustix", "rustix",
"slab", "slab",
"windows-sys 0.60.2", "windows-sys 0.61.0",
] ]
[[package]] [[package]]
@@ -1300,7 +1300,7 @@ version = "0.72.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895"
dependencies = [ dependencies = [
"bitflags 2.9.3", "bitflags 2.9.4",
"cexpr", "cexpr",
"clang-sys", "clang-sys",
"itertools 0.13.0", "itertools 0.13.0",
@@ -1425,9 +1425,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]] [[package]]
name = "bitflags" name = "bitflags"
version = "2.9.3" version = "2.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34efbcccd345379ca2868b2b2c9d3782e9cc58ba87bc7d79d5b53d9c9ae6f25d" checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394"
[[package]] [[package]]
name = "bitvec" name = "bitvec"
@@ -1666,11 +1666,11 @@ dependencies = [
[[package]] [[package]]
name = "camino" name = "camino"
version = "1.1.12" version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd0b03af37dad7a14518b7691d81acb0f8222604ad3d1b02f6b4bed5188c0cd5" checksum = "e1de8bc0aa9e9385ceb3bf0c152e3a9b9544f6c4a912c8ae504e80c1f0368603"
dependencies = [ dependencies = [
"serde", "serde_core",
] ]
[[package]] [[package]]
@@ -1690,7 +1690,7 @@ checksum = "eee4243f1f26fc7a42710e7439c149e2b10b05472f88090acce52632f231a73a"
dependencies = [ dependencies = [
"camino", "camino",
"cargo-platform", "cargo-platform",
"semver 1.0.26", "semver 1.0.27",
"serde", "serde",
"serde_json", "serde_json",
"thiserror 1.0.69", "thiserror 1.0.69",
@@ -1912,28 +1912,27 @@ dependencies = [
[[package]] [[package]]
name = "console" name = "console"
version = "0.16.0" version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e09ced7ebbccb63b4c65413d821f2e00ce54c5ca4514ddc6b3c892fdbcbc69d" checksum = "b430743a6eb14e9764d4260d4c0d8123087d504eeb9c48f2b2a5e810dd369df4"
dependencies = [ dependencies = [
"encode_unicode", "encode_unicode",
"libc", "libc",
"once_cell", "once_cell",
"unicode-width", "unicode-width",
"windows-sys 0.60.2", "windows-sys 0.61.0",
] ]
[[package]] [[package]]
name = "const-hex" name = "const-hex"
version = "1.15.0" version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dccd746bf9b1038c0507b7cec21eb2b11222db96a2902c96e8c185d6d20fb9c4" checksum = "b6407bff74dea37e0fa3dc1c1c974e5d46405f0c987bf9997a0762adce71eda6"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"cpufeatures", "cpufeatures",
"hex",
"proptest", "proptest",
"serde", "serde_core",
] ]
[[package]] [[package]]
@@ -2084,7 +2083,7 @@ dependencies = [
"cranelift-control", "cranelift-control",
"cranelift-entity", "cranelift-entity",
"cranelift-isle", "cranelift-isle",
"gimli 0.32.2", "gimli 0.32.3",
"hashbrown 0.15.5", "hashbrown 0.15.5",
"log", "log",
"pulley-interpreter", "pulley-interpreter",
@@ -2326,11 +2325,12 @@ dependencies = [
[[package]] [[package]]
name = "cxx" name = "cxx"
version = "1.0.180" version = "1.0.184"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ecd70e33fb57b5fec1608d572bf8dc382f5385a19529056b32307a29ac329be" checksum = "be4a0beb369d20d0de6aa7084ee523e4c9a31d7d8c61ba357b119bb574d7f368"
dependencies = [ dependencies = [
"cc", "cc",
"cxx-build",
"cxxbridge-cmd", "cxxbridge-cmd",
"cxxbridge-flags", "cxxbridge-flags",
"cxxbridge-macro", "cxxbridge-macro",
@@ -2340,13 +2340,13 @@ dependencies = [
[[package]] [[package]]
name = "cxx-build" name = "cxx-build"
version = "1.0.175" version = "1.0.184"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4e2aa0ea9f398b72f329197cfad624fcb16b2538d3ffb0f71f51cd19fa2a512" checksum = "27d955b93e56a8e45cbc34df0ae920d8b5ad01541a4571222c78527c00e1a40a"
dependencies = [ dependencies = [
"cc", "cc",
"codespan-reporting", "codespan-reporting",
"indexmap 2.11.1", "indexmap 2.11.3",
"proc-macro2", "proc-macro2",
"quote", "quote",
"scratch", "scratch",
@@ -2355,13 +2355,13 @@ dependencies = [
[[package]] [[package]]
name = "cxxbridge-cmd" name = "cxxbridge-cmd"
version = "1.0.180" version = "1.0.184"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64320fd0856fdf2421f8404b67d41e91291cbcfa3d57457b390f0a2618ee9a68" checksum = "052f6c468d9dabdc2b8b228bcb2d7843b2bea0f3fb9c4e2c6ba5852574ec0150"
dependencies = [ dependencies = [
"clap", "clap",
"codespan-reporting", "codespan-reporting",
"indexmap 2.11.1", "indexmap 2.11.3",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.106", "syn 2.0.106",
@@ -2369,17 +2369,17 @@ dependencies = [
[[package]] [[package]]
name = "cxxbridge-flags" name = "cxxbridge-flags"
version = "1.0.180" version = "1.0.184"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77e40964f209961217b972415a8e3a0c23299076a0b2dfe79fa8366b5e5c833e" checksum = "0fd145fa180986cb8002c63217d03b2c782fdcd5fa323adcd1f62d2d6ece6144"
[[package]] [[package]]
name = "cxxbridge-macro" name = "cxxbridge-macro"
version = "1.0.180" version = "1.0.184"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51afdec15d8072d1b69f54f645edaf54250088a7e54c4fe493016781278136bd" checksum = "02ac4a3bc4484a2daa0a8421c9588bd26522be9682a2fe02c7087bc4e8bc3c60"
dependencies = [ dependencies = [
"indexmap 2.11.1", "indexmap 2.11.3",
"proc-macro2", "proc-macro2",
"quote", "quote",
"rustversion", "rustversion",
@@ -2666,7 +2666,7 @@ dependencies = [
"libc", "libc",
"option-ext", "option-ext",
"redox_users", "redox_users",
"windows-sys 0.60.2", "windows-sys 0.61.0",
] ]
[[package]] [[package]]
@@ -2995,7 +2995,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [ dependencies = [
"libc", "libc",
"windows-sys 0.60.2", "windows-sys 0.59.0",
] ]
[[package]] [[package]]
@@ -3377,11 +3377,11 @@ version = "0.2.0"
dependencies = [ dependencies = [
"ciphersuite 0.4.2", "ciphersuite 0.4.2",
"flexible-transcript", "flexible-transcript",
"group",
"modular-frost", "modular-frost",
"rand_core 0.6.4", "rand_core 0.6.4",
"schnorr-signatures", "schnorr-signatures",
"schnorrkel", "schnorrkel",
"std-shims",
"zeroize", "zeroize",
] ]
@@ -3640,7 +3640,7 @@ dependencies = [
"js-sys", "js-sys",
"libc", "libc",
"r-efi", "r-efi",
"wasi 0.14.5+wasi-0.2.4", "wasi 0.14.7+wasi-0.2.4",
"wasm-bindgen", "wasm-bindgen",
] ]
@@ -3672,12 +3672,12 @@ checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
[[package]] [[package]]
name = "gimli" name = "gimli"
version = "0.32.2" version = "0.32.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc6298e594375a7fead9efd5568f0a46e6a154fb6a9bdcbe3c06946ffd81a5f6" checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7"
dependencies = [ dependencies = [
"fallible-iterator", "fallible-iterator",
"indexmap 2.11.1", "indexmap 2.11.3",
"stable_deref_trait", "stable_deref_trait",
] ]
@@ -3743,7 +3743,7 @@ dependencies = [
"futures-core", "futures-core",
"futures-sink", "futures-sink",
"http", "http",
"indexmap 2.11.1", "indexmap 2.11.3",
"slab", "slab",
"tokio", "tokio",
"tokio-util", "tokio-util",
@@ -3839,9 +3839,6 @@ name = "hex"
version = "0.4.3" version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "hex-conservative" name = "hex-conservative"
@@ -3998,15 +3995,15 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
[[package]] [[package]]
name = "humantime" name = "humantime"
version = "2.2.0" version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b112acc8b3adf4b107a8ec20977da0273a8c386765a3ec0229bd500a1443f9f" checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424"
[[package]] [[package]]
name = "hybrid-array" name = "hybrid-array"
version = "0.4.0" version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6fe39a812f039072707ce38020acbab2f769087952eddd9e2b890f37654b2349" checksum = "c7116c472cf19838450b1d421b4e842569f52b519d640aee9ace1ebcf5b21051"
dependencies = [ dependencies = [
"typenum", "typenum",
] ]
@@ -4068,9 +4065,9 @@ dependencies = [
[[package]] [[package]]
name = "hyper-util" name = "hyper-util"
version = "0.1.16" version = "0.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d9b05277c7e8da2c93a568989bb6207bef0112e8d17df7a6eda4a3cf143bc5e" checksum = "3c6995591a8f1380fcb4ba966a252a4b29188d51d2b89e3a252f5305be65aea8"
dependencies = [ dependencies = [
"bytes", "bytes",
"futures-channel", "futures-channel",
@@ -4081,7 +4078,7 @@ dependencies = [
"hyper", "hyper",
"libc", "libc",
"pin-project-lite", "pin-project-lite",
"socket2 0.6.0", "socket2 0.5.10",
"tokio", "tokio",
"tower-service", "tower-service",
"tracing", "tracing",
@@ -4104,9 +4101,9 @@ dependencies = [
[[package]] [[package]]
name = "iana-time-zone" name = "iana-time-zone"
version = "0.1.63" version = "0.1.64"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb"
dependencies = [ dependencies = [
"android_system_properties", "android_system_properties",
"core-foundation-sys", "core-foundation-sys",
@@ -4279,13 +4276,14 @@ dependencies = [
[[package]] [[package]]
name = "indexmap" name = "indexmap"
version = "2.11.1" version = "2.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "206a8042aec68fa4a62e8d3f7aa4ceb508177d9324faf261e1959e495b7a1921" checksum = "92119844f513ffa41556430369ab02c295a3578af21cf945caa3e9e0c2481ac3"
dependencies = [ dependencies = [
"equivalent", "equivalent",
"hashbrown 0.15.5", "hashbrown 0.15.5",
"serde", "serde",
"serde_core",
] ]
[[package]] [[package]]
@@ -4312,7 +4310,7 @@ version = "0.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b" checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b"
dependencies = [ dependencies = [
"bitflags 2.9.3", "bitflags 2.9.4",
"cfg-if", "cfg-if",
"libc", "libc",
] ]
@@ -5015,11 +5013,11 @@ dependencies = [
[[package]] [[package]]
name = "libredox" name = "libredox"
version = "0.1.9" version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "391290121bad3d37fbddad76d8f5d1c1c314cfc646d143d7e07a3086ddff0ce3" checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb"
dependencies = [ dependencies = [
"bitflags 2.9.3", "bitflags 2.9.4",
"libc", "libc",
] ]
@@ -5443,6 +5441,7 @@ dependencies = [
"schnorr-signatures", "schnorr-signatures",
"serde_json", "serde_json",
"sha2 0.10.9", "sha2 0.10.9",
"std-shims",
"subtle", "subtle",
"thiserror 2.0.16", "thiserror 2.0.16",
"zeroize", "zeroize",
@@ -5716,8 +5715,6 @@ dependencies = [
"group", "group",
"k256", "k256",
"rand_core 0.6.4", "rand_core 0.6.4",
"rustversion",
"std-shims",
"zeroize", "zeroize",
] ]
@@ -6067,7 +6064,7 @@ checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe"
dependencies = [ dependencies = [
"crc32fast", "crc32fast",
"hashbrown 0.15.5", "hashbrown 0.15.5",
"indexmap 2.11.1", "indexmap 2.11.3",
"memchr", "memchr",
] ]
@@ -6340,9 +6337,9 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
[[package]] [[package]]
name = "pest" name = "pest"
version = "2.8.1" version = "2.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1db05f56d34358a8b1066f67cbb203ee3e7ed2ba674a6263a1d5ec6db2204323" checksum = "21e0a3a33733faeaf8651dfee72dd0f388f0c8e5ad496a3478fa5a922f49cfa8"
dependencies = [ dependencies = [
"memchr", "memchr",
"thiserror 2.0.16", "thiserror 2.0.16",
@@ -6356,7 +6353,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772"
dependencies = [ dependencies = [
"fixedbitset", "fixedbitset",
"indexmap 2.11.1", "indexmap 2.11.3",
] ]
[[package]] [[package]]
@@ -6451,16 +6448,16 @@ checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
[[package]] [[package]]
name = "polling" name = "polling"
version = "3.10.0" version = "3.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5bd19146350fe804f7cb2669c851c03d69da628803dab0d98018142aaa5d829" checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"concurrent-queue", "concurrent-queue",
"hermit-abi", "hermit-abi",
"pin-project-lite", "pin-project-lite",
"rustix", "rustix",
"windows-sys 0.60.2", "windows-sys 0.61.0",
] ]
[[package]] [[package]]
@@ -6731,7 +6728,7 @@ checksum = "6fcdab19deb5195a31cf7726a210015ff1496ba1464fd42cb4f537b8b01b471f"
dependencies = [ dependencies = [
"bit-set", "bit-set",
"bit-vec", "bit-vec",
"bitflags 2.9.3", "bitflags 2.9.4",
"lazy_static", "lazy_static",
"num-traits", "num-traits",
"rand 0.9.2", "rand 0.9.2",
@@ -6760,7 +6757,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac6c3320f9abac597dcbc668774ef006702672474aad53c6d596b62e487b40b1" checksum = "ac6c3320f9abac597dcbc668774ef006702672474aad53c6d596b62e487b40b1"
dependencies = [ dependencies = [
"heck 0.4.1", "heck 0.4.1",
"itertools 0.14.0", "itertools 0.13.0",
"log", "log",
"multimap", "multimap",
"once_cell", "once_cell",
@@ -6780,7 +6777,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9120690fafc389a67ba3803df527d0ec9cbbc9cc45e4cc20b332996dfb672425" checksum = "9120690fafc389a67ba3803df527d0ec9cbbc9cc45e4cc20b332996dfb672425"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"itertools 0.14.0", "itertools 0.13.0",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.106", "syn 2.0.106",
@@ -6875,7 +6872,7 @@ dependencies = [
"quinn-udp", "quinn-udp",
"rustc-hash", "rustc-hash",
"rustls", "rustls",
"socket2 0.6.0", "socket2 0.5.10",
"thiserror 2.0.16", "thiserror 2.0.16",
"tokio", "tokio",
"tracing", "tracing",
@@ -6912,9 +6909,9 @@ dependencies = [
"cfg_aliases", "cfg_aliases",
"libc", "libc",
"once_cell", "once_cell",
"socket2 0.6.0", "socket2 0.5.10",
"tracing", "tracing",
"windows-sys 0.60.2", "windows-sys 0.59.0",
] ]
[[package]] [[package]]
@@ -7034,7 +7031,7 @@ version = "11.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186" checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186"
dependencies = [ dependencies = [
"bitflags 2.9.3", "bitflags 2.9.4",
] ]
[[package]] [[package]]
@@ -7082,7 +7079,7 @@ version = "0.5.17"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77"
dependencies = [ dependencies = [
"bitflags 2.9.3", "bitflags 2.9.4",
] ]
[[package]] [[package]]
@@ -7161,9 +7158,9 @@ checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001"
[[package]] [[package]]
name = "resolv-conf" name = "resolv-conf"
version = "0.7.4" version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95325155c684b1c89f7765e30bc1c42e4a6da51ca513615660cb8a62ef9a88e3" checksum = "6b3789b30bd25ba102de4beabd95d21ac45b69b1be7d14522bab988c526d6799"
[[package]] [[package]]
name = "revm" name = "revm"
@@ -7348,7 +7345,7 @@ version = "7.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f64fbacb86008394aaebd3454f9643b7d5a782bd251135e17c5b33da592d84d" checksum = "1f64fbacb86008394aaebd3454f9643b7d5a782bd251135e17c5b33da592d84d"
dependencies = [ dependencies = [
"bitflags 2.9.3", "bitflags 2.9.4",
"revm-bytecode", "revm-bytecode",
"revm-primitives", "revm-primitives",
"serde", "serde",
@@ -7530,7 +7527,7 @@ version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
dependencies = [ dependencies = [
"semver 1.0.26", "semver 1.0.27",
] ]
[[package]] [[package]]
@@ -7548,11 +7545,11 @@ version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e"
dependencies = [ dependencies = [
"bitflags 2.9.3", "bitflags 2.9.4",
"errno", "errno",
"libc", "libc",
"linux-raw-sys", "linux-raw-sys",
"windows-sys 0.60.2", "windows-sys 0.59.0",
] ]
[[package]] [[package]]
@@ -7593,9 +7590,9 @@ dependencies = [
[[package]] [[package]]
name = "rustls-webpki" name = "rustls-webpki"
version = "0.103.4" version = "0.103.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc" checksum = "8572f3c2cb9934231157b45499fc41e1f58c589fdfb81a844ba873265e80f8eb"
dependencies = [ dependencies = [
"ring", "ring",
"rustls-pki-types", "rustls-pki-types",
@@ -8524,7 +8521,7 @@ dependencies = [
"async-trait", "async-trait",
"futures", "futures",
"futures-timer", "futures-timer",
"indexmap 2.11.1", "indexmap 2.11.3",
"itertools 0.14.0", "itertools 0.14.0",
"linked-hash-map", "linked-hash-map",
"parity-scale-codec", "parity-scale-codec",
@@ -8553,7 +8550,7 @@ source = "git+https://github.com/serai-dex/patch-polkadot-sdk?rev=d4624c561765c1
dependencies = [ dependencies = [
"async-trait", "async-trait",
"futures", "futures",
"indexmap 2.11.1", "indexmap 2.11.3",
"log", "log",
"parity-scale-codec", "parity-scale-codec",
"serde", "serde",
@@ -8604,11 +8601,11 @@ dependencies = [
[[package]] [[package]]
name = "schannel" name = "schannel"
version = "0.1.27" version = "0.1.28"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1"
dependencies = [ dependencies = [
"windows-sys 0.59.0", "windows-sys 0.61.0",
] ]
[[package]] [[package]]
@@ -8775,7 +8772,7 @@ version = "3.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60b369d18893388b345804dc0007963c99b7d665ae71d275812d828c6f089640" checksum = "60b369d18893388b345804dc0007963c99b7d665ae71d275812d828c6f089640"
dependencies = [ dependencies = [
"bitflags 2.9.3", "bitflags 2.9.4",
"core-foundation 0.10.1", "core-foundation 0.10.1",
"core-foundation-sys", "core-foundation-sys",
"libc", "libc",
@@ -8803,11 +8800,12 @@ dependencies = [
[[package]] [[package]]
name = "semver" name = "semver"
version = "1.0.26" version = "1.0.27"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2"
dependencies = [ dependencies = [
"serde", "serde",
"serde_core",
] ]
[[package]] [[package]]
@@ -9297,10 +9295,15 @@ dependencies = [
"ciphersuite 0.4.2", "ciphersuite 0.4.2",
"dalek-ff-group 0.5.0", "dalek-ff-group 0.5.0",
"dkg", "dkg",
"dkg-dealer",
"dkg-evrf", "dkg-evrf",
"dkg-musig",
"dkg-recovery",
"embedwards25519", "embedwards25519",
"flexible-transcript", "flexible-transcript",
"frost-schnorrkel",
"minimal-ed448", "minimal-ed448",
"modular-frost",
"multiexp", "multiexp",
"prime-field", "prime-field",
"schnorr-signatures", "schnorr-signatures",
@@ -9687,6 +9690,8 @@ dependencies = [
"parity-scale-codec", "parity-scale-codec",
"serai-abi", "serai-abi",
"serai-coins-pallet", "serai-coins-pallet",
"serai-signals-pallet",
"serai-validator-sets-pallet",
"sp-api", "sp-api",
"sp-core", "sp-core",
"sp-runtime", "sp-runtime",
@@ -9701,8 +9706,7 @@ dependencies = [
"frame-support", "frame-support",
"frame-system", "frame-system",
"parity-scale-codec", "parity-scale-codec",
"serai-in-instructions-pallet", "serai-abi",
"serai-primitives",
"serai-validator-sets-pallet", "serai-validator-sets-pallet",
"sp-core", "sp-core",
"sp-io", "sp-io",
@@ -9737,27 +9741,38 @@ dependencies = [
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.219" version = "1.0.225"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" checksum = "fd6c24dee235d0da097043389623fb913daddf92c76e9f5a1db88607a0bcbd1d"
dependencies = [ dependencies = [
"serde_core",
"serde_derive", "serde_derive",
] ]
[[package]] [[package]]
name = "serde_bytes" name = "serde_bytes"
version = "0.11.17" version = "0.11.19"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8437fd221bde2d4ca316d61b90e337e9e702b3820b87d63caa9ba6c02bd06d96" checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8"
dependencies = [ dependencies = [
"serde", "serde",
"serde_core",
]
[[package]]
name = "serde_core"
version = "1.0.225"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "659356f9a0cb1e529b24c01e43ad2bdf520ec4ceaf83047b83ddcc2251f96383"
dependencies = [
"serde_derive",
] ]
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.219" version = "1.0.225"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" checksum = "0ea936adf78b1f766949a4977b91d2f5595825bd6ec079aa9543ad2685fc4516"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -9766,15 +9781,16 @@ dependencies = [
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.143" version = "1.0.145"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d401abef1d108fbd9cbaebc3e46611f4b1021f714a0597a71f41ee463f5f4a5a" checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c"
dependencies = [ dependencies = [
"indexmap 2.11.1", "indexmap 2.11.3",
"itoa", "itoa",
"memchr", "memchr",
"ryu", "ryu",
"serde", "serde",
"serde_core",
] ]
[[package]] [[package]]
@@ -9819,7 +9835,7 @@ dependencies = [
"chrono", "chrono",
"hex", "hex",
"indexmap 1.9.3", "indexmap 1.9.3",
"indexmap 2.11.1", "indexmap 2.11.3",
"serde", "serde",
"serde_derive", "serde_derive",
"serde_json", "serde_json",
@@ -10890,7 +10906,7 @@ version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b"
dependencies = [ dependencies = [
"bitflags 2.9.3", "bitflags 2.9.4",
"core-foundation 0.9.4", "core-foundation 0.9.4",
"system-configuration-sys", "system-configuration-sys",
] ]
@@ -10933,7 +10949,7 @@ dependencies = [
"getrandom 0.3.3", "getrandom 0.3.3",
"once_cell", "once_cell",
"rustix", "rustix",
"windows-sys 0.60.2", "windows-sys 0.59.0",
] ]
[[package]] [[package]]
@@ -11201,7 +11217,7 @@ version = "0.22.27"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
dependencies = [ dependencies = [
"indexmap 2.11.1", "indexmap 2.11.3",
"serde", "serde",
"serde_spanned", "serde_spanned",
"toml_datetime", "toml_datetime",
@@ -11250,7 +11266,7 @@ version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5" checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5"
dependencies = [ dependencies = [
"bitflags 2.9.3", "bitflags 2.9.4",
"bytes", "bytes",
"http", "http",
"http-body", "http-body",
@@ -11474,9 +11490,9 @@ checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5"
[[package]] [[package]]
name = "unicode-ident" name = "unicode-ident"
version = "1.0.18" version = "1.0.19"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d"
[[package]] [[package]]
name = "unicode-joining-type" name = "unicode-joining-type"
@@ -11628,18 +11644,18 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]] [[package]]
name = "wasi" name = "wasi"
version = "0.14.5+wasi-0.2.4" version = "0.14.7+wasi-0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4494f6290a82f5fe584817a676a34b9d6763e8d9d18204009fb31dceca98fd4" checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c"
dependencies = [ dependencies = [
"wasip2", "wasip2",
] ]
[[package]] [[package]]
name = "wasip2" name = "wasip2"
version = "1.0.0+wasi-0.2.4" version = "1.0.1+wasi-0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03fa2761397e5bd52002cd7e73110c71af2109aca4e521a9f40473fe685b0a24" checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7"
dependencies = [ dependencies = [
"wit-bindgen", "wit-bindgen",
] ]
@@ -11767,10 +11783,10 @@ version = "0.236.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9b1e81f3eb254cf7404a82cee6926a4a3ccc5aad80cc3d43608a070c67aa1d7" checksum = "a9b1e81f3eb254cf7404a82cee6926a4a3ccc5aad80cc3d43608a070c67aa1d7"
dependencies = [ dependencies = [
"bitflags 2.9.3", "bitflags 2.9.4",
"hashbrown 0.15.5", "hashbrown 0.15.5",
"indexmap 2.11.1", "indexmap 2.11.3",
"semver 1.0.26", "semver 1.0.27",
"serde", "serde",
] ]
@@ -11791,14 +11807,14 @@ version = "36.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b3e1fab634681494213138ea3a18e958e5ea99da13a4a01a4b870d51a41680b" checksum = "5b3e1fab634681494213138ea3a18e958e5ea99da13a4a01a4b870d51a41680b"
dependencies = [ dependencies = [
"addr2line 0.25.0", "addr2line 0.25.1",
"anyhow", "anyhow",
"bitflags 2.9.3", "bitflags 2.9.4",
"bumpalo", "bumpalo",
"cc", "cc",
"cfg-if", "cfg-if",
"hashbrown 0.15.5", "hashbrown 0.15.5",
"indexmap 2.11.1", "indexmap 2.11.3",
"libc", "libc",
"log", "log",
"mach2", "mach2",
@@ -11837,8 +11853,8 @@ dependencies = [
"anyhow", "anyhow",
"cranelift-bitset", "cranelift-bitset",
"cranelift-entity", "cranelift-entity",
"gimli 0.32.2", "gimli 0.32.3",
"indexmap 2.11.1", "indexmap 2.11.3",
"log", "log",
"object 0.37.3", "object 0.37.3",
"postcard", "postcard",
@@ -11893,7 +11909,7 @@ dependencies = [
"cranelift-entity", "cranelift-entity",
"cranelift-frontend", "cranelift-frontend",
"cranelift-native", "cranelift-native",
"gimli 0.32.2", "gimli 0.32.3",
"itertools 0.14.0", "itertools 0.14.0",
"log", "log",
"object 0.37.3", "object 0.37.3",
@@ -12214,6 +12230,15 @@ dependencies = [
"windows-targets 0.53.3", "windows-targets 0.53.3",
] ]
[[package]]
name = "windows-sys"
version = "0.61.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e201184e40b2ede64bc2ea34968b28e33622acdbbf37104f0e4a33f7abe657aa"
dependencies = [
"windows-link 0.2.0",
]
[[package]] [[package]]
name = "windows-targets" name = "windows-targets"
version = "0.48.5" version = "0.48.5"
@@ -12421,9 +12446,9 @@ dependencies = [
[[package]] [[package]]
name = "wit-bindgen" name = "wit-bindgen"
version = "0.45.1" version = "0.46.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c573471f125075647d03df72e026074b7203790d41351cd6edc96f46bcccd36" checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
[[package]] [[package]]
name = "wyz" name = "wyz"

View File

@@ -1,6 +1,8 @@
pub use core::sync::atomic; pub use core::sync::atomic;
#[cfg(all(feature = "alloc", not(feature = "std")))] #[cfg(all(feature = "alloc", not(feature = "std")))]
pub use extern_alloc::sync::{Arc, Weak}; pub use extern_alloc::sync::{Arc, Weak};
#[cfg(feature = "std")]
pub use std::sync::{Arc, Weak};
mod mutex_shim { mod mutex_shim {
#[cfg(not(feature = "std"))] #[cfg(not(feature = "std"))]

View File

@@ -21,7 +21,7 @@ zeroize = { version = "^1.5", default-features = false, features = ["zeroize_der
thiserror = { version = "2", default-features = false } thiserror = { version = "2", default-features = false }
std-shims = { version = "0.1", path = "../../common/std-shims", default-features = false } std-shims = { version = "0.1", path = "../../common/std-shims", default-features = false, features = ["alloc"] }
borsh = { version = "1", default-features = false, features = ["derive", "de_strict_order"], optional = true } borsh = { version = "1", default-features = false, features = ["derive", "de_strict_order"], optional = true }

View File

@@ -20,7 +20,7 @@ workspace = true
zeroize = { version = "^1.5", default-features = false } zeroize = { version = "^1.5", default-features = false }
rand_core = { version = "0.6", default-features = false } rand_core = { version = "0.6", default-features = false }
std-shims = { version = "0.1", path = "../../../common/std-shims", default-features = false } std-shims = { version = "0.1", path = "../../../common/std-shims", default-features = false, features = ["alloc"] }
ciphersuite = { path = "../../ciphersuite", version = "^0.4.1", default-features = false } ciphersuite = { path = "../../ciphersuite", version = "^0.4.1", default-features = false }
dkg = { path = "../", version = "0.6", default-features = false } dkg = { path = "../", version = "0.6", default-features = false }

View File

@@ -23,7 +23,7 @@ rand_core = { version = "0.6", default-features = false, features = ["alloc"] }
zeroize = { version = "^1.5", default-features = false, features = ["alloc", "zeroize_derive"] } zeroize = { version = "^1.5", default-features = false, features = ["alloc", "zeroize_derive"] }
std-shims = { version = "0.1", path = "../../../common/std-shims", default-features = false } std-shims = { version = "0.1", path = "../../../common/std-shims", default-features = false, features = ["alloc"] }
transcript = { package = "flexible-transcript", path = "../../transcript", version = "^0.3.2", default-features = false, features = ["recommended"] } transcript = { package = "flexible-transcript", path = "../../transcript", version = "^0.3.2", default-features = false, features = ["recommended"] }

View File

@@ -23,7 +23,7 @@ rand_core = { version = "0.6", default-features = false }
zeroize = { version = "^1.5", default-features = false, features = ["zeroize_derive"] } zeroize = { version = "^1.5", default-features = false, features = ["zeroize_derive"] }
std-shims = { version = "0.1", path = "../../../common/std-shims", default-features = false } std-shims = { version = "0.1", path = "../../../common/std-shims", default-features = false, features = ["alloc"] }
multiexp = { path = "../../multiexp", version = "0.4", default-features = false } multiexp = { path = "../../multiexp", version = "0.4", default-features = false }
ciphersuite = { path = "../../ciphersuite", version = "^0.4.1", default-features = false } ciphersuite = { path = "../../ciphersuite", version = "^0.4.1", default-features = false }

View File

@@ -17,33 +17,35 @@ rustdoc-args = ["--cfg", "docsrs"]
workspace = true workspace = true
[dependencies] [dependencies]
thiserror = { version = "2", default-features = false, features = ["std"] } std-shims = { version = "0.1", path = "../../common/std-shims", default-features = false, features = ["alloc"] }
rand_core = { version = "0.6", default-features = false, features = ["std"] } thiserror = { version = "2", default-features = false }
rand_chacha = { version = "0.3", default-features = false, features = ["std"] }
zeroize = { version = "^1.5", default-features = false, features = ["std", "zeroize_derive"] } rand_core = { version = "0.6", default-features = false, features = ["alloc"] }
subtle = { version = "^2.4", default-features = false, features = ["std"] } rand_chacha = { version = "0.3", default-features = false }
hex = { version = "0.4", default-features = false, features = ["std"], optional = true } zeroize = { version = "^1.5", default-features = false, features = ["alloc", "zeroize_derive"] }
subtle = { version = "^2.4", default-features = false }
transcript = { package = "flexible-transcript", path = "../transcript", version = "^0.3.2", default-features = false, features = ["std", "recommended"] } hex = { version = "0.4", default-features = false, features = ["alloc"], optional = true }
dalek-ff-group = { path = "../dalek-ff-group", version = "0.5", default-features = false, features = ["std"], optional = true } transcript = { package = "flexible-transcript", path = "../transcript", version = "^0.3.2", default-features = false, features = ["recommended"] }
minimal-ed448 = { path = "../ed448", version = "0.4", default-features = false, features = ["std"], optional = true }
ciphersuite = { path = "../ciphersuite", version = "^0.4.1", default-features = false, features = ["std"] } dalek-ff-group = { path = "../dalek-ff-group", version = "0.5", default-features = false, features = ["alloc"], optional = true }
minimal-ed448 = { path = "../ed448", version = "0.4", default-features = false, features = ["alloc"], optional = true }
ciphersuite = { path = "../ciphersuite", version = "^0.4.1", default-features = false, features = ["alloc"] }
sha2 = { version = "0.10.0", default-features = false, optional = true } sha2 = { version = "0.10.0", default-features = false, optional = true }
elliptic-curve = { version = "0.13", default-features = false, features = ["hash2curve"], optional = true } elliptic-curve = { version = "0.13", default-features = false, features = ["hash2curve"], optional = true }
ciphersuite-kp256 = { path = "../ciphersuite/kp256", version = "0.4", default-features = false, features = ["std"], optional = true } ciphersuite-kp256 = { path = "../ciphersuite/kp256", version = "0.4", default-features = false, features = ["alloc"], optional = true }
multiexp = { path = "../multiexp", version = "0.4", default-features = false, features = ["std", "batch"] } multiexp = { path = "../multiexp", version = "0.4", default-features = false, features = ["alloc", "batch"] }
schnorr = { package = "schnorr-signatures", path = "../schnorr", version = "^0.5.1", default-features = false, features = ["std"] } schnorr = { package = "schnorr-signatures", path = "../schnorr", version = "^0.5.1", default-features = false, features = ["alloc"] }
dkg = { path = "../dkg", version = "0.6.1", default-features = false, features = ["std"] } dkg = { path = "../dkg", version = "0.6.1", default-features = false }
dkg-recovery = { path = "../dkg/recovery", version = "0.6", default-features = false, features = ["std"], optional = true } dkg-recovery = { path = "../dkg/recovery", version = "0.6", default-features = false, optional = true }
dkg-dealer = { path = "../dkg/dealer", version = "0.6", default-features = false, features = ["std"], optional = true } dkg-dealer = { path = "../dkg/dealer", version = "0.6", default-features = false, optional = true }
[dev-dependencies] [dev-dependencies]
hex = "0.4" hex = "0.4"
@@ -54,6 +56,38 @@ dkg-recovery = { path = "../dkg/recovery", default-features = false, features =
dkg-dealer = { path = "../dkg/dealer", default-features = false, features = ["std"] } dkg-dealer = { path = "../dkg/dealer", default-features = false, features = ["std"] }
[features] [features]
std = [
"std-shims/std",
"thiserror/std",
"rand_core/std",
"rand_chacha/std",
"zeroize/std",
"subtle/std",
"hex?/std",
"transcript/std",
"dalek-ff-group?/std",
"minimal-ed448?/std",
"ciphersuite/std",
"sha2?/std",
"elliptic-curve?/std",
"ciphersuite-kp256?/std",
"multiexp/std",
"schnorr/std",
"dkg/std",
"dkg-recovery?/std",
"dkg-dealer?/std",
]
ed25519 = ["dalek-ff-group"] ed25519 = ["dalek-ff-group"]
ristretto = ["dalek-ff-group"] ristretto = ["dalek-ff-group"]
@@ -63,3 +97,5 @@ p256 = ["sha2", "elliptic-curve", "ciphersuite-kp256"]
ed448 = ["minimal-ed448"] ed448 = ["minimal-ed448"]
tests = ["hex", "rand_core/getrandom", "dkg-dealer", "dkg-recovery"] tests = ["hex", "rand_core/getrandom", "dkg-dealer", "dkg-recovery"]
default = ["std"]

View File

@@ -1,5 +1,7 @@
use core::{marker::PhantomData, fmt::Debug}; use core::{marker::PhantomData, fmt::Debug};
use std::io::{self, Read, Write}; #[allow(unused_imports)]
use std_shims::prelude::*;
use std_shims::io::{self, Read, Write};
use zeroize::Zeroizing; use zeroize::Zeroizing;
use rand_core::{RngCore, CryptoRng}; use rand_core::{RngCore, CryptoRng};

View File

@@ -1,5 +1,7 @@
use core::{ops::Deref, convert::AsRef}; use core::{ops::Deref, convert::AsRef};
use std::io::{self, Read}; #[allow(unused_imports)]
use std_shims::prelude::*;
use std_shims::io::{self, Read};
use rand_core::{RngCore, CryptoRng}; use rand_core::{RngCore, CryptoRng};

View File

@@ -1,8 +1,11 @@
#![cfg_attr(docsrs, feature(doc_auto_cfg))] #![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![doc = include_str!("../README.md")] #![doc = include_str!("../README.md")]
#![cfg_attr(not(feature = "std"), no_std)]
use core::fmt::Debug; use core::fmt::Debug;
use std::collections::HashMap; #[allow(unused_imports)]
use std_shims::prelude::*;
use std_shims::collections::HashMap;
use thiserror::Error; use thiserror::Error;

View File

@@ -6,7 +6,9 @@
// Each nonce remains of the form (d, e) and made into a proper nonce with d + (e * b) // Each nonce remains of the form (d, e) and made into a proper nonce with d + (e * b)
use core::ops::Deref; use core::ops::Deref;
use std::{ #[allow(unused_imports)]
use std_shims::prelude::*;
use std_shims::{
io::{self, Read, Write}, io::{self, Read, Write},
collections::HashMap, collections::HashMap,
}; };

View File

@@ -1,5 +1,7 @@
use core::{ops::Deref, fmt::Debug}; use core::{ops::Deref, fmt::Debug};
use std::{ #[allow(unused_imports)]
use std_shims::prelude::*;
use std_shims::{
io::{self, Read, Write}, io::{self, Read, Write},
collections::HashMap, collections::HashMap,
}; };

View File

@@ -1,4 +1,4 @@
use std::collections::HashMap; use std_shims::collections::HashMap;
use rand_core::{RngCore, CryptoRng}; use rand_core::{RngCore, CryptoRng};

View File

@@ -1,4 +1,4 @@
use std::io::{self, Read}; use std_shims::io::{self, Read};
use zeroize::Zeroizing; use zeroize::Zeroizing;

View File

@@ -1,8 +1,8 @@
use core::ops::Deref; use core::ops::Deref;
use std::collections::HashMap; use std_shims::collections::HashMap;
#[cfg(test)] #[cfg(test)]
use std::str::FromStr; use core::str::FromStr;
use zeroize::Zeroizing; use zeroize::Zeroizing;

View File

@@ -17,11 +17,7 @@ rustdoc-args = ["--cfg", "docsrs"]
workspace = true workspace = true
[dependencies] [dependencies]
rustversion = "1" zeroize = { version = "^1.5", default-features = false, features = ["zeroize_derive"] }
std-shims = { path = "../../common/std-shims", version = "^0.1.1", default-features = false }
zeroize = { version = "^1.5", default-features = false, features = ["zeroize_derive", "alloc"] }
ff = { version = "0.13", default-features = false, features = ["bits"] } ff = { version = "0.13", default-features = false, features = ["bits"] }
group = { version = "0.13", default-features = false } group = { version = "0.13", default-features = false }
@@ -35,8 +31,9 @@ k256 = { version = "^0.13.1", default-features = false, features = ["arithmetic"
dalek-ff-group = { path = "../dalek-ff-group" } dalek-ff-group = { path = "../dalek-ff-group" }
[features] [features]
std = ["std-shims/std", "zeroize/std", "ff/std", "rand_core?/std"] alloc = ["zeroize/alloc"]
std = ["alloc", "zeroize/std", "ff/std", "rand_core?/std"]
batch = ["rand_core"] batch = ["alloc", "rand_core"]
default = ["std"] default = ["std"]

View File

@@ -12,5 +12,6 @@ culminating in commit
[669d2dbffc1dafb82a09d9419ea182667115df06](https://github.com/serai-dex/serai/tree/669d2dbffc1dafb82a09d9419ea182667115df06). [669d2dbffc1dafb82a09d9419ea182667115df06](https://github.com/serai-dex/serai/tree/669d2dbffc1dafb82a09d9419ea182667115df06).
Any subsequent changes have not undergone auditing. Any subsequent changes have not undergone auditing.
This library is usable under no_std, via alloc, when the default features are This library is usable under no-`std` and no-`alloc`. With the `alloc` feature,
disabled. the library is fully functional. Without the `alloc` feature, the `multiexp`
function is shimmed with a serial implementation.

View File

@@ -1,4 +1,4 @@
use std_shims::vec::Vec; use alloc::vec::Vec;
use rand_core::{RngCore, CryptoRng}; use rand_core::{RngCore, CryptoRng};

View File

@@ -2,200 +2,177 @@
#![doc = include_str!("../README.md")] #![doc = include_str!("../README.md")]
#![cfg_attr(not(feature = "std"), no_std)] #![cfg_attr(not(feature = "std"), no_std)]
#[cfg(not(feature = "std"))] #[cfg(feature = "alloc")]
#[macro_use]
extern crate alloc; extern crate alloc;
#[allow(unused_imports)]
use std_shims::prelude::*;
use std_shims::vec::Vec;
use zeroize::Zeroize; use zeroize::Zeroize;
use ff::PrimeFieldBits; use ff::PrimeFieldBits;
use group::Group; use group::Group;
#[cfg(feature = "alloc")]
mod straus; mod straus;
use straus::*; #[cfg(feature = "alloc")]
mod pippenger; mod pippenger;
use pippenger::*;
#[cfg(feature = "batch")] #[cfg(feature = "batch")]
mod batch; mod batch;
#[cfg(feature = "batch")]
pub use batch::BatchVerifier;
#[cfg(test)] #[cfg(all(test, feature = "alloc"))]
mod tests; mod tests;
// Use black_box when possible #[cfg(feature = "alloc")]
#[rustversion::since(1.66)] mod underlying {
use core::hint::black_box; use super::*;
#[rustversion::before(1.66)]
fn black_box<T>(val: T) -> T {
val
}
fn u8_from_bool(bit_ref: &mut bool) -> u8 { use core::hint::black_box;
let bit_ref = black_box(bit_ref); use alloc::{vec, vec::Vec};
let mut bit = black_box(*bit_ref); pub(crate) use straus::*;
#[allow(clippy::cast_lossless)]
let res = black_box(bit as u8);
bit.zeroize();
debug_assert!((res | 1) == 1);
bit_ref.zeroize(); pub(crate) use pippenger::*;
res
}
// Convert scalars to `window`-sized bit groups, as needed to index a table #[cfg(feature = "batch")]
// This algorithm works for `window <= 8` pub use batch::BatchVerifier;
pub(crate) fn prep_bits<G: Group<Scalar: PrimeFieldBits>>(
pairs: &[(G::Scalar, G)],
window: u8,
) -> Vec<Vec<u8>> {
let w_usize = usize::from(window);
let mut groupings = vec![]; fn u8_from_bool(bit_ref: &mut bool) -> u8 {
for pair in pairs { let bit_ref = black_box(bit_ref);
let p = groupings.len();
let mut bits = pair.0.to_le_bits();
groupings.push(vec![0; bits.len().div_ceil(w_usize)]);
for (i, mut bit) in bits.iter_mut().enumerate() { let mut bit = black_box(*bit_ref);
let mut bit = u8_from_bool(&mut bit); #[allow(clippy::cast_lossless)]
groupings[p][i / w_usize] |= bit << (i % w_usize); let res = black_box(bit as u8);
bit.zeroize(); bit.zeroize();
debug_assert!((res | 1) == 1);
bit_ref.zeroize();
res
}
// Convert scalars to `window`-sized bit groups, as needed to index a table
// This algorithm works for `window <= 8`
pub(crate) fn prep_bits<G: Group<Scalar: PrimeFieldBits>>(
pairs: &[(G::Scalar, G)],
window: u8,
) -> Vec<Vec<u8>> {
let w_usize = usize::from(window);
let mut groupings = vec![];
for pair in pairs {
let p = groupings.len();
let mut bits = pair.0.to_le_bits();
groupings.push(vec![0; bits.len().div_ceil(w_usize)]);
for (i, mut bit) in bits.iter_mut().enumerate() {
let mut bit = u8_from_bool(&mut bit);
groupings[p][i / w_usize] |= bit << (i % w_usize);
bit.zeroize();
}
}
groupings
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
enum Algorithm {
Null,
Single,
Straus(u8),
Pippenger(u8),
}
// These are 'rule of thumb's obtained via benchmarking `k256` and `curve25519-dalek`
fn algorithm(len: usize) -> Algorithm {
#[cfg(not(debug_assertions))]
if len == 0 {
Algorithm::Null
} else if len == 1 {
Algorithm::Single
} else if len < 10 {
// Straus 2 never showed a performance benefit, even with just 2 elements
Algorithm::Straus(3)
} else if len < 20 {
Algorithm::Straus(4)
} else if len < 50 {
Algorithm::Straus(5)
} else if len < 100 {
Algorithm::Pippenger(4)
} else if len < 125 {
Algorithm::Pippenger(5)
} else if len < 275 {
Algorithm::Pippenger(6)
} else if len < 400 {
Algorithm::Pippenger(7)
} else {
Algorithm::Pippenger(8)
}
#[cfg(debug_assertions)]
if len == 0 {
Algorithm::Null
} else if len == 1 {
Algorithm::Single
} else if len < 10 {
Algorithm::Straus(3)
} else if len < 80 {
Algorithm::Straus(4)
} else if len < 100 {
Algorithm::Straus(5)
} else if len < 125 {
Algorithm::Pippenger(4)
} else if len < 275 {
Algorithm::Pippenger(5)
} else if len < 475 {
Algorithm::Pippenger(6)
} else if len < 750 {
Algorithm::Pippenger(7)
} else {
Algorithm::Pippenger(8)
} }
} }
groupings /// Performs a multiexponentiation, automatically selecting the optimal algorithm based on the
} /// amount of pairs.
pub fn multiexp<G: Zeroize + Group<Scalar: Zeroize + PrimeFieldBits>>(
#[derive(Clone, Copy, PartialEq, Eq, Debug)] pairs: &[(G::Scalar, G)],
enum Algorithm { ) -> G {
Null, match algorithm(pairs.len()) {
Single, Algorithm::Null => Group::identity(),
Straus(u8), Algorithm::Single => pairs[0].1 * pairs[0].0,
Pippenger(u8), // These functions panic if called without any pairs
} Algorithm::Straus(window) => straus(pairs, window),
Algorithm::Pippenger(window) => pippenger(pairs, window),
/* }
Release (with runs 20, so all of these are off by 20x):
k256
Straus 3 is more efficient at 5 with 678µs per
Straus 4 is more efficient at 10 with 530µs per
Straus 5 is more efficient at 35 with 467µs per
Pippenger 5 is more efficient at 125 with 431µs per
Pippenger 6 is more efficient at 275 with 349µs per
Pippenger 7 is more efficient at 375 with 360µs per
dalek
Straus 3 is more efficient at 5 with 519µs per
Straus 4 is more efficient at 10 with 376µs per
Straus 5 is more efficient at 170 with 330µs per
Pippenger 5 is more efficient at 125 with 305µs per
Pippenger 6 is more efficient at 275 with 250µs per
Pippenger 7 is more efficient at 450 with 205µs per
Pippenger 8 is more efficient at 800 with 213µs per
Debug (with runs 5, so...):
k256
Straus 3 is more efficient at 5 with 2532µs per
Straus 4 is more efficient at 10 with 1930µs per
Straus 5 is more efficient at 80 with 1632µs per
Pippenger 5 is more efficient at 150 with 1441µs per
Pippenger 6 is more efficient at 300 with 1235µs per
Pippenger 7 is more efficient at 475 with 1182µs per
Pippenger 8 is more efficient at 625 with 1170µs per
dalek:
Straus 3 is more efficient at 5 with 971µs per
Straus 4 is more efficient at 10 with 782µs per
Straus 5 is more efficient at 75 with 778µs per
Straus 6 is more efficient at 165 with 867µs per
Pippenger 5 is more efficient at 125 with 677µs per
Pippenger 6 is more efficient at 250 with 655µs per
Pippenger 7 is more efficient at 475 with 500µs per
Pippenger 8 is more efficient at 875 with 499µs per
*/
fn algorithm(len: usize) -> Algorithm {
#[cfg(not(debug_assertions))]
if len == 0 {
Algorithm::Null
} else if len == 1 {
Algorithm::Single
} else if len < 10 {
// Straus 2 never showed a performance benefit, even with just 2 elements
Algorithm::Straus(3)
} else if len < 20 {
Algorithm::Straus(4)
} else if len < 50 {
Algorithm::Straus(5)
} else if len < 100 {
Algorithm::Pippenger(4)
} else if len < 125 {
Algorithm::Pippenger(5)
} else if len < 275 {
Algorithm::Pippenger(6)
} else if len < 400 {
Algorithm::Pippenger(7)
} else {
Algorithm::Pippenger(8)
} }
#[cfg(debug_assertions)] /// Performs a multiexponentiation in variable time, automatically selecting the optimal algorithm
if len == 0 { /// based on the amount of pairs.
Algorithm::Null pub fn multiexp_vartime<G: Group<Scalar: PrimeFieldBits>>(pairs: &[(G::Scalar, G)]) -> G {
} else if len == 1 { match algorithm(pairs.len()) {
Algorithm::Single Algorithm::Null => Group::identity(),
} else if len < 10 { Algorithm::Single => pairs[0].1 * pairs[0].0,
Algorithm::Straus(3) Algorithm::Straus(window) => straus_vartime(pairs, window),
} else if len < 80 { Algorithm::Pippenger(window) => pippenger_vartime(pairs, window),
Algorithm::Straus(4) }
} else if len < 100 {
Algorithm::Straus(5)
} else if len < 125 {
Algorithm::Pippenger(4)
} else if len < 275 {
Algorithm::Pippenger(5)
} else if len < 475 {
Algorithm::Pippenger(6)
} else if len < 750 {
Algorithm::Pippenger(7)
} else {
Algorithm::Pippenger(8)
} }
} }
/// Performs a multiexponentiation, automatically selecting the optimal algorithm based on the #[cfg(not(feature = "alloc"))]
/// amount of pairs. mod underlying {
pub fn multiexp<G: Zeroize + Group<Scalar: Zeroize + PrimeFieldBits>>( use super::*;
pairs: &[(G::Scalar, G)],
) -> G { /// Performs a multiexponentiation, automatically selecting the optimal algorithm based on the
match algorithm(pairs.len()) { /// amount of pairs.
Algorithm::Null => Group::identity(), pub fn multiexp<G: Zeroize + Group<Scalar: Zeroize + PrimeFieldBits>>(
Algorithm::Single => pairs[0].1 * pairs[0].0, pairs: &[(G::Scalar, G)],
// These functions panic if called without any pairs ) -> G {
Algorithm::Straus(window) => straus(pairs, window), pairs.iter().map(|(scalar, point)| *point * scalar).sum()
Algorithm::Pippenger(window) => pippenger(pairs, window), }
/// Performs a multiexponentiation in variable time, automatically selecting the optimal algorithm
/// based on the amount of pairs.
pub fn multiexp_vartime<G: Group<Scalar: PrimeFieldBits>>(pairs: &[(G::Scalar, G)]) -> G {
pairs.iter().map(|(scalar, point)| *point * scalar).sum()
} }
} }
/// Performs a multiexponentiation in variable time, automatically selecting the optimal algorithm pub use underlying::*;
/// based on the amount of pairs.
pub fn multiexp_vartime<G: Group<Scalar: PrimeFieldBits>>(pairs: &[(G::Scalar, G)]) -> G {
match algorithm(pairs.len()) {
Algorithm::Null => Group::identity(),
Algorithm::Single => pairs[0].1 * pairs[0].0,
Algorithm::Straus(window) => straus_vartime(pairs, window),
Algorithm::Pippenger(window) => pippenger_vartime(pairs, window),
}
}

View File

@@ -1,3 +1,5 @@
use alloc::vec;
use zeroize::Zeroize; use zeroize::Zeroize;
use ff::PrimeFieldBits; use ff::PrimeFieldBits;

View File

@@ -1,4 +1,4 @@
use std_shims::vec::Vec; use alloc::{vec, vec::Vec};
use zeroize::Zeroize; use zeroize::Zeroize;

View File

@@ -27,7 +27,7 @@ digest = { version = "0.11.0-rc.1", default-features = false, features = ["block
transcript = { package = "flexible-transcript", path = "../transcript", version = "^0.3.2", default-features = false, optional = true } transcript = { package = "flexible-transcript", path = "../transcript", version = "^0.3.2", default-features = false, optional = true }
ciphersuite = { path = "../ciphersuite", version = "^0.4.1", default-features = false } ciphersuite = { path = "../ciphersuite", version = "^0.4.1", default-features = false }
multiexp = { path = "../multiexp", version = "0.4", default-features = false, features = ["batch"], optional = true } multiexp = { path = "../multiexp", version = "0.4", default-features = false }
[dev-dependencies] [dev-dependencies]
hex = "0.4" hex = "0.4"
@@ -40,7 +40,7 @@ dalek-ff-group = { path = "../dalek-ff-group" }
ciphersuite = { path = "../ciphersuite" } ciphersuite = { path = "../ciphersuite" }
[features] [features]
alloc = ["zeroize/alloc", "digest/alloc", "ciphersuite/alloc", "multiexp"] alloc = ["zeroize/alloc", "digest/alloc", "ciphersuite/alloc", "multiexp/alloc", "multiexp/batch"]
aggregate = ["alloc", "transcript"] aggregate = ["alloc", "transcript"]
std = ["alloc", "std-shims/std", "rand_core/std", "zeroize/std", "transcript?/std", "ciphersuite/std", "multiexp/std"] std = ["alloc", "std-shims/std", "rand_core/std", "zeroize/std", "transcript?/std", "ciphersuite/std", "multiexp/std"]
default = ["std"] default = ["std"]

View File

@@ -23,8 +23,9 @@ use ciphersuite::{
}, },
GroupIo, GroupIo,
}; };
use multiexp::multiexp_vartime;
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
use multiexp::{multiexp_vartime, BatchVerifier}; use multiexp::BatchVerifier;
/// Half-aggregation from <https://eprint.iacr.org/2021/350>. /// Half-aggregation from <https://eprint.iacr.org/2021/350>.
#[cfg(feature = "aggregate")] #[cfg(feature = "aggregate")]
@@ -109,12 +110,7 @@ impl<C: GroupIo> SchnorrSignature<C> {
/// different keys/messages. /// different keys/messages.
#[must_use] #[must_use]
pub fn verify(&self, public_key: C::G, challenge: C::F) -> bool { pub fn verify(&self, public_key: C::G, challenge: C::F) -> bool {
let statements = self.batch_statements(public_key, challenge); multiexp_vartime(&self.batch_statements(public_key, challenge)).is_identity().into()
#[cfg(feature = "alloc")]
let res = multiexp_vartime(&statements);
#[cfg(not(feature = "alloc"))]
let res = statements.into_iter().map(|(scalar, point)| point * scalar).sum::<C::G>();
res.is_identity().into()
} }
/// Queue a signature for batch verification. /// Queue a signature for batch verification.

View File

@@ -17,18 +17,35 @@ rustdoc-args = ["--cfg", "docsrs"]
workspace = true workspace = true
[dependencies] [dependencies]
rand_core = "0.6" std-shims = { version = "0.1", default-features = false, features = ["alloc"] }
zeroize = "^1.5"
transcript = { package = "flexible-transcript", path = "../transcript", version = "^0.3.2", features = ["merlin"] } rand_core = { version = "0.6", default-features = false }
zeroize = { version = "1.5", default-features = false, features = ["zeroize_derive", "alloc"] }
group = "0.13" transcript = { package = "flexible-transcript", path = "../transcript", version = "0.3.2", default-features = false, features = ["merlin"] }
ciphersuite = { path = "../ciphersuite", version = "^0.4.1", features = ["std"] } ciphersuite = { path = "../ciphersuite", version = "0.4.1", default-features = false, features = ["alloc"] }
schnorr = { package = "schnorr-signatures", path = "../schnorr", version = "^0.5.1" } schnorr = { package = "schnorr-signatures", path = "../schnorr", version = "0.5.1", default-features = false, features = ["alloc"] }
frost = { path = "../frost", package = "modular-frost", version = "0.11.0", features = ["ristretto"] } frost = { path = "../frost", package = "modular-frost", version = "0.11.0", default-features = false, features = ["ristretto"] }
schnorrkel = { version = "0.11" } schnorrkel = { version = "0.11", default-features = false, features = ["alloc"] }
[dev-dependencies] [dev-dependencies]
frost = { path = "../frost", package = "modular-frost", features = ["tests"] } frost = { path = "../frost", package = "modular-frost", features = ["tests"] }
[features]
std = [
"std-shims/std",
"rand_core/std",
"zeroize/std",
"transcript/std",
"ciphersuite/std",
"schnorr/std",
"frost/std",
"schnorrkel/std",
]
default = ["std"]

View File

@@ -1,7 +1,10 @@
#![cfg_attr(docsrs, feature(doc_auto_cfg))] #![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![doc = include_str!("../README.md")] #![doc = include_str!("../README.md")]
#![cfg_attr(not(feature = "std"), no_std)]
use std::io::{self, Read}; #[allow(unused_imports)]
use std_shims::prelude::*;
use std_shims::io::{self, Read};
use rand_core::{RngCore, CryptoRng}; use rand_core::{RngCore, CryptoRng};

View File

@@ -1,6 +1,6 @@
use rand_core::OsRng; use rand_core::OsRng;
use group::GroupEncoding; use ciphersuite::group::GroupEncoding;
use frost::{ use frost::{
Participant, Participant,
tests::{key_gen, algorithm_machines, sign}, tests::{key_gen, algorithm_machines, sign},

View File

@@ -125,6 +125,8 @@ deny = [
{ name = "hashbrown", version = "=0.15.0" }, { name = "hashbrown", version = "=0.15.0" },
# Legacy which _no one_ should use anymore # Legacy which _no one_ should use anymore
{ name = "is-terminal", version = "*" }, { name = "is-terminal", version = "*" },
# Stop introduction into the tree without realizing it
{ name = "once_cell_polyfill", version = "*" },
] ]
[sources] [sources]

View File

@@ -16,7 +16,7 @@ rustdoc-args = ["--cfg", "docsrs"]
workspace = true workspace = true
[dependencies] [dependencies]
std-shims = { version = "0.1.1", path = "../../common/std-shims", default-features = false } std-shims = { version = "0.1.1", path = "../../common/std-shims", default-features = false, features = ["alloc"] }
thiserror = { version = "2", default-features = false } thiserror = { version = "2", default-features = false }
@@ -27,7 +27,7 @@ rand_core = { version = "0.6", default-features = false }
bitcoin = { version = "0.32", default-features = false } bitcoin = { version = "0.32", default-features = false }
k256 = { version = "^0.13.1", default-features = false, features = ["arithmetic", "bits"] } k256 = { version = "^0.13.1", default-features = false, features = ["arithmetic", "bits"] }
frost = { package = "modular-frost", path = "../../crypto/frost", version = "0.11", default-features = false, features = ["secp256k1"], optional = true } frost = { package = "modular-frost", path = "../../crypto/frost", version = "0.11", default-features = false, features = ["secp256k1"] }
hex = { version = "0.4", default-features = false, optional = true } hex = { version = "0.4", default-features = false, optional = true }
serde = { version = "1", default-features = false, features = ["derive"], optional = true } serde = { version = "1", default-features = false, features = ["derive"], optional = true }
@@ -55,7 +55,7 @@ std = [
"bitcoin/serde", "bitcoin/serde",
"k256/std", "k256/std",
"frost", "frost/std",
"hex/std", "hex/std",
"serde/std", "serde/std",

View File

@@ -1,9 +1,27 @@
#[cfg(feature = "std")] use core::fmt::Debug;
#[allow(unused_imports)]
use std_shims::prelude::*;
use std_shims::io;
use subtle::{Choice, ConstantTimeEq, ConditionallySelectable}; use subtle::{Choice, ConstantTimeEq, ConditionallySelectable};
use zeroize::Zeroizing;
use rand_core::{RngCore, CryptoRng};
use k256::{elliptic_curve::sec1::ToEncodedPoint, ProjectivePoint}; use k256::{
elliptic_curve::{ops::Reduce, sec1::ToEncodedPoint},
U256, Scalar, ProjectivePoint,
};
use bitcoin::key::XOnlyPublicKey; use bitcoin::{
hashes::{HashEngine, Hash, sha256::Hash as Sha256},
key::XOnlyPublicKey,
};
use frost::{
curve::{WrappedGroup, Secp256k1},
Participant, ThresholdKeys, ThresholdView, FrostError,
algorithm::{Hram as HramTrait, Algorithm, IetfSchnorr as FrostSchnorr},
};
/// Get the x coordinate of a non-infinity point. /// Get the x coordinate of a non-infinity point.
/// ///
@@ -21,142 +39,118 @@ pub(crate) fn x_only(key: &ProjectivePoint) -> XOnlyPublicKey {
} }
/// Return if a point must be negated to have an even Y coordinate and be eligible for use. /// Return if a point must be negated to have an even Y coordinate and be eligible for use.
#[cfg(feature = "std")]
pub(crate) fn needs_negation(key: &ProjectivePoint) -> Choice { pub(crate) fn needs_negation(key: &ProjectivePoint) -> Choice {
use k256::elliptic_curve::sec1::Tag; use k256::elliptic_curve::sec1::Tag;
u8::from(key.to_encoded_point(true).tag()).ct_eq(&u8::from(Tag::CompressedOddY)) u8::from(key.to_encoded_point(true).tag()).ct_eq(&u8::from(Tag::CompressedOddY))
} }
#[cfg(feature = "std")] /// A BIP-340 compatible HRAm for use with the modular-frost Schnorr Algorithm.
mod frost_crypto { ///
use core::fmt::Debug; /// If passed an odd nonce, the challenge will be negated.
use std_shims::{vec::Vec, io}; ///
/// If either `R` or `A` is the point at infinity, this will panic.
#[derive(Clone, Copy, Debug)]
pub struct Hram;
#[allow(non_snake_case)]
impl HramTrait<Secp256k1> for Hram {
fn hram(R: &ProjectivePoint, A: &ProjectivePoint, m: &[u8]) -> Scalar {
const TAG_HASH: Sha256 = Sha256::const_hash(b"BIP0340/challenge");
use zeroize::Zeroizing; let mut data = Sha256::engine();
use rand_core::{RngCore, CryptoRng}; data.input(TAG_HASH.as_ref());
data.input(TAG_HASH.as_ref());
data.input(&x(R));
data.input(&x(A));
data.input(m);
use bitcoin::hashes::{HashEngine, Hash, sha256::Hash as Sha256}; let c = Scalar::reduce(U256::from_be_slice(Sha256::from_engine(data).as_ref()));
// If the nonce was odd, sign `r - cx` instead of `r + cx`, allowing us to negate `s` at the
use k256::{elliptic_curve::ops::Reduce, U256, Scalar}; // end to sign as `-r + cx`
<_>::conditional_select(&c, &-c, needs_negation(R))
use frost::{ }
curve::{WrappedGroup, Secp256k1}, }
Participant, ThresholdKeys, ThresholdView, FrostError,
algorithm::{Hram as HramTrait, Algorithm, IetfSchnorr as FrostSchnorr}, /// BIP-340 Schnorr signature algorithm.
}; ///
/// This may panic if called with nonces/a group key which are the point at infinity (which have
use super::*; /// a negligible probability for a well-reasoned caller, even with malicious participants
/// present).
/// A BIP-340 compatible HRAm for use with the modular-frost Schnorr Algorithm. ///
/// /// `verify`, `verify_share` MUST be called after `sign_share` is called. Otherwise, this library
/// If passed an odd nonce, the challenge will be negated. /// MAY panic.
/// #[derive(Clone)]
/// If either `R` or `A` is the point at infinity, this will panic. pub struct Schnorr(FrostSchnorr<Secp256k1, Hram>);
#[derive(Clone, Copy, Debug)] impl Schnorr {
pub struct Hram; /// Construct a Schnorr algorithm continuing the specified transcript.
#[allow(non_snake_case)] #[allow(clippy::new_without_default)]
impl HramTrait<Secp256k1> for Hram { pub fn new() -> Schnorr {
fn hram(R: &ProjectivePoint, A: &ProjectivePoint, m: &[u8]) -> Scalar { Schnorr(FrostSchnorr::ietf())
const TAG_HASH: Sha256 = Sha256::const_hash(b"BIP0340/challenge"); }
}
let mut data = Sha256::engine();
data.input(TAG_HASH.as_ref()); impl Algorithm<Secp256k1> for Schnorr {
data.input(TAG_HASH.as_ref()); type Transcript = <FrostSchnorr<Secp256k1, Hram> as Algorithm<Secp256k1>>::Transcript;
data.input(&x(R)); type Addendum = ();
data.input(&x(A)); type Signature = [u8; 64];
data.input(m);
fn transcript(&mut self) -> &mut Self::Transcript {
let c = Scalar::reduce(U256::from_be_slice(Sha256::from_engine(data).as_ref())); self.0.transcript()
// If the nonce was odd, sign `r - cx` instead of `r + cx`, allowing us to negate `s` at the }
// end to sign as `-r + cx`
<_>::conditional_select(&c, &-c, needs_negation(R)) fn nonces(&self) -> Vec<Vec<ProjectivePoint>> {
} self.0.nonces()
} }
/// BIP-340 Schnorr signature algorithm. fn preprocess_addendum<R: RngCore + CryptoRng>(
/// &mut self,
/// This may panic if called with nonces/a group key which are the point at infinity (which have rng: &mut R,
/// a negligible probability for a well-reasoned caller, even with malicious participants keys: &ThresholdKeys<Secp256k1>,
/// present). ) {
/// self.0.preprocess_addendum(rng, keys)
/// `verify`, `verify_share` MUST be called after `sign_share` is called. Otherwise, this library }
/// MAY panic.
#[derive(Clone)] fn read_addendum<R: io::Read>(&self, reader: &mut R) -> io::Result<Self::Addendum> {
pub struct Schnorr(FrostSchnorr<Secp256k1, Hram>); self.0.read_addendum(reader)
impl Schnorr { }
/// Construct a Schnorr algorithm continuing the specified transcript.
#[allow(clippy::new_without_default)] fn process_addendum(
pub fn new() -> Schnorr { &mut self,
Schnorr(FrostSchnorr::ietf()) view: &ThresholdView<Secp256k1>,
} i: Participant,
} addendum: (),
) -> Result<(), FrostError> {
impl Algorithm<Secp256k1> for Schnorr { self.0.process_addendum(view, i, addendum)
type Transcript = <FrostSchnorr<Secp256k1, Hram> as Algorithm<Secp256k1>>::Transcript; }
type Addendum = ();
type Signature = [u8; 64]; fn sign_share(
&mut self,
fn transcript(&mut self) -> &mut Self::Transcript { params: &ThresholdView<Secp256k1>,
self.0.transcript() nonce_sums: &[Vec<<Secp256k1 as WrappedGroup>::G>],
} nonces: Vec<Zeroizing<<Secp256k1 as WrappedGroup>::F>>,
msg: &[u8],
fn nonces(&self) -> Vec<Vec<ProjectivePoint>> { ) -> <Secp256k1 as WrappedGroup>::F {
self.0.nonces() self.0.sign_share(params, nonce_sums, nonces, msg)
} }
fn preprocess_addendum<R: RngCore + CryptoRng>( fn verify(
&mut self, &self,
rng: &mut R, group_key: ProjectivePoint,
keys: &ThresholdKeys<Secp256k1>, nonces: &[Vec<ProjectivePoint>],
) { sum: Scalar,
self.0.preprocess_addendum(rng, keys) ) -> Option<Self::Signature> {
} self.0.verify(group_key, nonces, sum).map(|mut sig| {
sig.s = <_>::conditional_select(&sum, &-sum, needs_negation(&sig.R));
fn read_addendum<R: io::Read>(&self, reader: &mut R) -> io::Result<Self::Addendum> { // Convert to a Bitcoin signature by dropping the byte for the point's sign bit
self.0.read_addendum(reader) sig.serialize()[1 ..].try_into().unwrap()
} })
}
fn process_addendum(
&mut self, fn verify_share(
view: &ThresholdView<Secp256k1>, &self,
i: Participant, verification_share: ProjectivePoint,
addendum: (), nonces: &[Vec<ProjectivePoint>],
) -> Result<(), FrostError> { share: Scalar,
self.0.process_addendum(view, i, addendum) ) -> Result<Vec<(Scalar, ProjectivePoint)>, ()> {
} self.0.verify_share(verification_share, nonces, share)
fn sign_share(
&mut self,
params: &ThresholdView<Secp256k1>,
nonce_sums: &[Vec<<Secp256k1 as WrappedGroup>::G>],
nonces: Vec<Zeroizing<<Secp256k1 as WrappedGroup>::F>>,
msg: &[u8],
) -> <Secp256k1 as WrappedGroup>::F {
self.0.sign_share(params, nonce_sums, nonces, msg)
}
fn verify(
&self,
group_key: ProjectivePoint,
nonces: &[Vec<ProjectivePoint>],
sum: Scalar,
) -> Option<Self::Signature> {
self.0.verify(group_key, nonces, sum).map(|mut sig| {
sig.s = <_>::conditional_select(&sum, &-sum, needs_negation(&sig.R));
// Convert to a Bitcoin signature by dropping the byte for the point's sign bit
sig.serialize()[1 ..].try_into().unwrap()
})
}
fn verify_share(
&self,
verification_share: ProjectivePoint,
nonces: &[Vec<ProjectivePoint>],
share: Scalar,
) -> Result<Vec<(Scalar, ProjectivePoint)>, ()> {
self.0.verify_share(verification_share, nonces, share)
}
} }
} }
#[cfg(feature = "std")]
pub use frost_crypto::*;

View File

@@ -2,9 +2,6 @@
#![doc = include_str!("../README.md")] #![doc = include_str!("../README.md")]
#![cfg_attr(not(feature = "std"), no_std)] #![cfg_attr(not(feature = "std"), no_std)]
#[cfg(not(feature = "std"))]
extern crate alloc;
/// The bitcoin Rust library. /// The bitcoin Rust library.
pub use bitcoin; pub use bitcoin;

View File

@@ -1,36 +1,31 @@
#[allow(unused_imports)]
use std_shims::prelude::*;
use std_shims::{ use std_shims::{
vec::Vec,
collections::HashMap, collections::HashMap,
io::{self, Write}, io::{self, Read, Write},
}; };
#[cfg(feature = "std")]
use std::io::{Read, BufReader};
use k256::{ use k256::{
elliptic_curve::sec1::{Tag, ToEncodedPoint}, elliptic_curve::sec1::{Tag, ToEncodedPoint},
Scalar, ProjectivePoint, Scalar, ProjectivePoint,
}; };
#[cfg(feature = "std")]
use frost::{ use frost::{
curve::{WrappedGroup, GroupIo, Secp256k1}, curve::{WrappedGroup, GroupIo, Secp256k1},
ThresholdKeys, ThresholdKeys,
}; };
use bitcoin::{ use bitcoin::{
consensus::encode::serialize, key::TweakedPublicKey, OutPoint, ScriptBuf, TxOut, Transaction, hashes::Hash,
Block, key::TweakedPublicKey,
TapTweakHash,
consensus::encode::{Decodable, serialize},
OutPoint, ScriptBuf, TxOut, Transaction, Block,
}; };
#[cfg(feature = "std")]
use bitcoin::{hashes::Hash, consensus::encode::Decodable, TapTweakHash};
use crate::crypto::x_only; use crate::crypto::{x_only, needs_negation};
#[cfg(feature = "std")]
use crate::crypto::needs_negation;
#[cfg(feature = "std")]
mod send; mod send;
#[cfg(feature = "std")]
pub use send::*; pub use send::*;
/// Tweak keys to ensure they're usable with Bitcoin's Taproot upgrade. /// Tweak keys to ensure they're usable with Bitcoin's Taproot upgrade.
@@ -42,7 +37,6 @@ pub use send::*;
/// After adding an unspendable script path, the key is negated if odd. /// After adding an unspendable script path, the key is negated if odd.
/// ///
/// This has a neligible probability of returning keys whose group key is the point at infinity. /// This has a neligible probability of returning keys whose group key is the point at infinity.
#[cfg(feature = "std")]
pub fn tweak_keys(keys: ThresholdKeys<Secp256k1>) -> ThresholdKeys<Secp256k1> { pub fn tweak_keys(keys: ThresholdKeys<Secp256k1>) -> ThresholdKeys<Secp256k1> {
// Adds the unspendable script path per // Adds the unspendable script path per
// https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki#cite_note-23 // https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki#cite_note-23
@@ -118,18 +112,23 @@ impl ReceivedOutput {
} }
/// Read a ReceivedOutput from a generic satisfying Read. /// Read a ReceivedOutput from a generic satisfying Read.
#[cfg(feature = "std")]
pub fn read<R: Read>(r: &mut R) -> io::Result<ReceivedOutput> { pub fn read<R: Read>(r: &mut R) -> io::Result<ReceivedOutput> {
let offset = Secp256k1::read_F(r)?; let offset = Secp256k1::read_F(r)?;
let output;
let outpoint; struct BitcoinRead<R: Read>(R);
{ impl<R: Read> bitcoin::io::Read for BitcoinRead<R> {
let mut buf_r = BufReader::with_capacity(0, r); fn read(&mut self, buf: &mut [u8]) -> bitcoin::io::Result<usize> {
output = self
TxOut::consensus_decode(&mut buf_r).map_err(|_| io::Error::other("invalid TxOut"))?; .0
outpoint = .read(buf)
OutPoint::consensus_decode(&mut buf_r).map_err(|_| io::Error::other("invalid OutPoint"))?; .map_err(|e| bitcoin::io::Error::new(bitcoin::io::ErrorKind::Other, e.to_string()))
}
} }
let mut r = BitcoinRead(r);
let output = TxOut::consensus_decode(&mut r).map_err(|_| io::Error::other("invalid TxOut"))?;
let outpoint =
OutPoint::consensus_decode(&mut r).map_err(|_| io::Error::other("invalid OutPoint"))?;
Ok(ReceivedOutput { offset, output, outpoint }) Ok(ReceivedOutput { offset, output, outpoint })
} }

View File

@@ -1,3 +1,5 @@
#[allow(unused_imports)]
use std_shims::prelude::*;
use std_shims::{ use std_shims::{
io::{self, Read}, io::{self, Read},
collections::HashMap, collections::HashMap,

View File

@@ -237,7 +237,7 @@ mod substrate {
use scale::{Encode, Decode}; use scale::{Encode, Decode};
use sp_runtime::{ use sp_runtime::{
transaction_validity::*, transaction_validity::*,
traits::{Verify, ExtrinsicLike, Dispatchable, ValidateUnsigned, Checkable, Applyable}, traits::{Verify, ExtrinsicLike, ExtrinsicCall, Dispatchable, ValidateUnsigned, Checkable, Applyable},
Weight, Weight,
}; };
#[rustfmt::skip] #[rustfmt::skip]
@@ -318,6 +318,13 @@ mod substrate {
} }
} }
impl ExtrinsicCall for Transaction {
type Call = Self;
fn call(&self) -> &Self {
self
}
}
impl<Context: TransactionContext> GetDispatchInfo for TransactionWithContext<Context> { impl<Context: TransactionContext> GetDispatchInfo for TransactionWithContext<Context> {
fn get_dispatch_info(&self) -> DispatchInfo { fn get_dispatch_info(&self) -> DispatchInfo {
match &self.0 { match &self.0 {

View File

@@ -54,8 +54,6 @@ mod pallet {
/// The configuration of this pallet. /// The configuration of this pallet.
#[pallet::config] #[pallet::config]
pub trait Config<I: 'static = ()>: frame_system::Config<AccountId = Public> { pub trait Config<I: 'static = ()>: frame_system::Config<AccountId = Public> {
/// The event type.
type RuntimeEvent: From<Event<Self, I>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
/// What decides if mints are allowed. /// What decides if mints are allowed.
type AllowMint: AllowMint; type AllowMint: AllowMint;
} }

View File

@@ -24,7 +24,10 @@ const HUMAN_READABLE_PART: bech32::Hrp = bech32::Hrp::parse_unchecked("sri");
/// The address for an account on Serai. /// The address for an account on Serai.
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Zeroize, BorshSerialize, BorshDeserialize)] #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Zeroize, BorshSerialize, BorshDeserialize)]
#[cfg_attr(feature = "non_canonical_scale_derivations", derive(scale::Encode, scale::Decode))] #[cfg_attr(
feature = "non_canonical_scale_derivations",
derive(scale::Encode, scale::Decode, scale::MaxEncodedLen, scale::DecodeWithMemTracking)
)]
pub struct SeraiAddress(pub [u8; 32]); pub struct SeraiAddress(pub [u8; 32]);
// These share encodings as 32-byte arrays // These share encodings as 32-byte arrays

View File

@@ -6,8 +6,3 @@ pub const TARGET_BLOCK_TIME: Duration = Duration::from_secs(6);
/// The intended duration for a session. /// The intended duration for a session.
// 1 week // 1 week
pub const SESSION_LENGTH: Duration = Duration::from_secs(7 * 24 * 60 * 60); pub const SESSION_LENGTH: Duration = Duration::from_secs(7 * 24 * 60 * 60);
/// The maximum amount of key shares per set.
pub const MAX_KEY_SHARES_PER_SET: u16 = 150;
/// The maximum amount of key shares per set, as an u32.
pub const MAX_KEY_SHARES_PER_SET_U32: u32 = MAX_KEY_SHARES_PER_SET as u32;

View File

@@ -96,7 +96,7 @@ pub mod prelude {
pub use crate::coin::*; pub use crate::coin::*;
pub use crate::balance::*; pub use crate::balance::*;
pub use crate::network_id::*; pub use crate::network_id::*;
pub use crate::validator_sets::{Session, ValidatorSet, ExternalValidatorSet, Slash, SlashReport}; pub use crate::validator_sets::*;
pub use crate::instructions::*; pub use crate::instructions::*;
} }

View File

@@ -1,16 +1,48 @@
use zeroize::Zeroize; use zeroize::Zeroize;
use borsh::{BorshSerialize, BorshDeserialize}; use borsh::{BorshSerialize, BorshDeserialize};
use crate::network_id::ExternalNetworkId; use crate::{network_id::ExternalNetworkId, address::SeraiAddress};
/// The ID of an protocol.
pub type ProtocolId = [u8; 32];
/// The ID of a signal.
pub type SignalId = [u8; 32];
/// A signal. /// A signal.
#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize, BorshSerialize, BorshDeserialize)] #[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize, BorshSerialize, BorshDeserialize)]
#[cfg_attr(
feature = "non_canonical_scale_derivations",
allow(clippy::cast_possible_truncation),
derive(scale::Encode, scale::Decode, scale::MaxEncodedLen, scale::DecodeWithMemTracking)
)]
pub enum Signal { pub enum Signal {
/// A signal to retire the current protocol. /// A signal to retire the current protocol.
Retire { Retire {
/// The protocol to retire in favor of. /// The ID of the retirement signal being favored.
in_favor_of: [u8; 32], signal_id: SignalId,
}, },
/// A signal to halt an external network. /// A signal to halt an external network.
Halt(ExternalNetworkId), Halt(ExternalNetworkId),
} }
/// A retirement signal, registered on chain.
#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize, BorshSerialize, BorshDeserialize)]
#[cfg_attr(
feature = "non_canonical_scale_derivations",
derive(scale::Encode, scale::Decode, scale::MaxEncodedLen, scale::DecodeWithMemTracking)
)]
pub struct RegisteredRetirementSignal {
/// The protocol to retire in favor of.
pub in_favor_of: ProtocolId,
/// The registrant of this signal.
pub registrant: SeraiAddress,
/// The block number this was registered at.
pub registered_at: u64,
}
impl RegisteredRetirementSignal {
/// The ID of this signal.
pub fn id(&self) -> SignalId {
sp_core::blake2_256(&borsh::to_vec(self).unwrap())
}
}

View File

@@ -7,9 +7,9 @@ use ciphersuite::{group::GroupEncoding, GroupIo};
use dalek_ff_group::Ristretto; use dalek_ff_group::Ristretto;
use crate::{ use crate::{
constants::MAX_KEY_SHARES_PER_SET,
crypto::{Public, KeyPair}, crypto::{Public, KeyPair},
network_id::{ExternalNetworkId, NetworkId}, network_id::{ExternalNetworkId, NetworkId},
balance::Amount,
}; };
mod slashes; mod slashes;
@@ -103,19 +103,86 @@ impl ExternalValidatorSet {
} }
} }
/// For a set of validators whose key shares may exceed the maximum, reduce until they are less /// The representation for an amount of key shares.
/// than or equal to the maximum. #[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize, BorshSerialize, BorshDeserialize)]
/// #[cfg_attr(
/// This runs in time linear to the exceed key shares and assumes the excess fits within a usize, feature = "non_canonical_scale_derivations",
/// panicking otherwise. derive(scale::Encode, scale::Decode, scale::MaxEncodedLen)
/// )]
/// Reduction occurs by reducing each validator in a reverse round-robin. This means the worst pub struct KeyShares(pub u16);
/// validators lose their key shares first.
pub fn amortize_excess_key_shares(validators: &mut [(sp_core::sr25519::Public, u64)]) { impl KeyShares {
let total_key_shares = validators.iter().map(|(_key, shares)| shares).sum::<u64>(); /// Zero key shares.
for i in 0 .. usize::try_from(total_key_shares.saturating_sub(u64::from(MAX_KEY_SHARES_PER_SET))) pub const ZERO: KeyShares = KeyShares(0);
.unwrap() /// One key share.
{ pub const ONE: KeyShares = KeyShares(1);
validators[validators.len() - ((i % validators.len()) + 1)].1 -= 1; /// The maximum amount of key shares per set.
pub const MAX_PER_SET: u16 = 150;
/// The maximum amount of key shares per set, represented as a `u32`.
pub const MAX_PER_SET_U32: u32 = 150;
/// Create key shares from a `u16`.
///
/// This will saturate the value if the `u16` exceeds the maximum amount of key shares.
pub fn saturating_from(key_shares: u16) -> KeyShares {
KeyShares(key_shares.min(Self::MAX_PER_SET))
}
/// Create key shares from an allocation.
///
/// Presumably panics if `allocation_per_key_share` is zero.
pub fn from_allocation(allocation: Amount, allocation_per_key_share: Amount) -> Self {
Self::saturating_from(
u16::try_from(allocation.0 / allocation_per_key_share.0).unwrap_or(u16::MAX),
)
}
/// For a set of validators whose key shares may exceed the maximum, reduce until they are less
/// than or equal to the maximum.
///
/// Returns the new amount of validators with a non-zero amount of key shares.
///
/// This runs in time linear to the exceeded key shares and may panic if:
/// - The total amount of key shares exceeds `u16::MAX`.
/// - The list of validators is absurdly long
/// - The list of validators includes validators without key shares
///
/// Reduction occurs by reducing each validator in a reverse round-robin. This means the
/// validators with the least key shares are evicted first.
#[must_use]
pub fn amortize_excess(validators: &mut [(sp_core::sr25519::Public, KeyShares)]) -> usize {
let total_key_shares = validators.iter().map(|(_key, shares)| shares.0).sum::<u16>();
let mut actual_len = validators.len();
let mut offset = 1;
for _ in 0 .. usize::from(total_key_shares.saturating_sub(Self::MAX_PER_SET)) {
// If the offset exceeds the new length, reset it
if offset > actual_len {
offset = 1;
}
// Take one key share from this validator
let index = actual_len - offset;
validators[index].1 .0 -= 1;
// If they now have zero key shares, shrink the length and continue
if validators[index].1 .0 == 0 {
actual_len -= 1;
continue;
}
// Increment the offset to take from the next validator on the next iteration
offset += 1;
}
actual_len
}
}
impl TryFrom<u16> for KeyShares {
type Error = ();
fn try_from(value: u16) -> Result<Self, ()> {
if value > Self::MAX_PER_SET {
Err(())
} else {
Ok(Self(value))
}
} }
} }

View File

@@ -8,8 +8,9 @@ use borsh::{BorshSerialize, BorshDeserialize};
use sp_core::{ConstU32, bounded::BoundedVec}; use sp_core::{ConstU32, bounded::BoundedVec};
use crate::{ use crate::{
constants::{TARGET_BLOCK_TIME, SESSION_LENGTH, MAX_KEY_SHARES_PER_SET_U32}, constants::{TARGET_BLOCK_TIME, SESSION_LENGTH},
balance::Amount, balance::Amount,
validator_sets::KeyShares,
}; };
/// Each slash point is equivalent to the downtime implied by missing a block proposal. /// Each slash point is equivalent to the downtime implied by missing a block proposal.
@@ -212,7 +213,7 @@ pub struct SlashReport(
serialize_with = "crate::borsh_serialize_bounded_vec", serialize_with = "crate::borsh_serialize_bounded_vec",
deserialize_with = "crate::borsh_deserialize_bounded_vec" deserialize_with = "crate::borsh_deserialize_bounded_vec"
)] )]
pub BoundedVec<Slash, ConstU32<{ MAX_KEY_SHARES_PER_SET_U32 }>>, pub BoundedVec<Slash, ConstU32<{ KeyShares::MAX_PER_SET_U32 }>>,
); );
/// An error when converting from a `Vec`. /// An error when converting from a `Vec`.
@@ -251,7 +252,7 @@ impl SlashReport {
#[test] #[test]
fn test_penalty() { fn test_penalty() {
for validators in [1, 50, 100, crate::constants::MAX_KEY_SHARES_PER_SET] { for validators in [1, 50, 100, KeyShares::MAX_PER_SET_U32] {
let validators = NonZero::new(validators).unwrap(); let validators = NonZero::new(validators).unwrap();
// 12 hours of slash points should only decrease the rewards proportionately // 12 hours of slash points should only decrease the rewards proportionately
let twelve_hours_of_slash_points = let twelve_hours_of_slash_points =

View File

@@ -34,6 +34,8 @@ frame-support = { git = "https://github.com/serai-dex/patch-polkadot-sdk", rev =
frame-executive = { git = "https://github.com/serai-dex/patch-polkadot-sdk", rev = "d4624c561765c13b38eb566e435131a8c329a543", default-features = false } frame-executive = { git = "https://github.com/serai-dex/patch-polkadot-sdk", rev = "d4624c561765c13b38eb566e435131a8c329a543", default-features = false }
serai-coins-pallet = { path = "../coins", default-features = false } serai-coins-pallet = { path = "../coins", default-features = false }
serai-validator-sets-pallet = { path = "../validator-sets", default-features = false }
serai-signals-pallet = { path = "../signals", default-features = false }
[build-dependencies] [build-dependencies]
substrate-wasm-builder = { git = "https://github.com/serai-dex/patch-polkadot-sdk", rev = "d4624c561765c13b38eb566e435131a8c329a543" } substrate-wasm-builder = { git = "https://github.com/serai-dex/patch-polkadot-sdk", rev = "d4624c561765c13b38eb566e435131a8c329a543" }
@@ -54,6 +56,8 @@ std = [
"frame-executive/std", "frame-executive/std",
"serai-coins-pallet/std", "serai-coins-pallet/std",
"serai-validator-sets-pallet/std",
"serai-signals-pallet/std",
] ]
try-runtime = [ try-runtime = [
@@ -66,6 +70,8 @@ try-runtime = [
"frame-executive/try-runtime", "frame-executive/try-runtime",
"serai-coins-pallet/try-runtime", "serai-coins-pallet/try-runtime",
"serai-validator-sets-pallet/try-runtime",
"serai-signals-pallet/try-runtime",
] ]
runtime-benchmarks = [ runtime-benchmarks = [
@@ -73,6 +79,9 @@ runtime-benchmarks = [
"frame-system/runtime-benchmarks", "frame-system/runtime-benchmarks",
"frame-support/runtime-benchmarks", "frame-support/runtime-benchmarks",
"serai-validator-sets-pallet/runtime-benchmarks",
"serai-signals-pallet/runtime-benchmarks",
] ]
default = ["std"] default = ["std"]

View File

@@ -82,6 +82,12 @@ mod runtime {
#[runtime::pallet_index(3)] #[runtime::pallet_index(3)]
pub type LiquidityTokens = serai_coins_pallet::Pallet<Runtime, LiquidityTokensInstance>; pub type LiquidityTokens = serai_coins_pallet::Pallet<Runtime, LiquidityTokensInstance>;
#[runtime::pallet_index(4)]
pub type ValidatorSets = serai_validator_sets_pallet::Pallet<Runtime>;
#[runtime::pallet_index(5)]
pub type Signals = serai_signals_pallet::Pallet<Runtime>;
} }
impl frame_system::Config for Runtime { impl frame_system::Config for Runtime {
@@ -127,13 +133,16 @@ impl frame_system::Config for Runtime {
impl core_pallet::Config for Runtime {} impl core_pallet::Config for Runtime {}
impl serai_coins_pallet::Config<CoinsInstance> for Runtime { impl serai_coins_pallet::Config<CoinsInstance> for Runtime {
type RuntimeEvent = RuntimeEvent;
type AllowMint = serai_coins_pallet::AlwaysAllowMint; // TODO type AllowMint = serai_coins_pallet::AlwaysAllowMint; // TODO
} }
impl serai_coins_pallet::Config<LiquidityTokensInstance> for Runtime { impl serai_coins_pallet::Config<LiquidityTokensInstance> for Runtime {
type RuntimeEvent = RuntimeEvent;
type AllowMint = serai_coins_pallet::AlwaysAllowMint; type AllowMint = serai_coins_pallet::AlwaysAllowMint;
} }
impl serai_validator_sets_pallet::Config for Runtime {}
impl serai_signals_pallet::Config for Runtime {
type RetirementValidityDuration = sp_core::ConstU64<0>; // TODO
type RetirementLockInDuration = sp_core::ConstU64<0>; // TODO
}
impl From<Option<SeraiAddress>> for RuntimeOrigin { impl From<Option<SeraiAddress>> for RuntimeOrigin {
fn from(signer: Option<SeraiAddress>) -> Self { fn from(signer: Option<SeraiAddress>) -> Self {

View File

@@ -27,10 +27,9 @@ sp-io = { git = "https://github.com/serai-dex/patch-polkadot-sdk", rev = "d4624c
frame-system = { git = "https://github.com/serai-dex/patch-polkadot-sdk", rev = "d4624c561765c13b38eb566e435131a8c329a543", default-features = false } frame-system = { git = "https://github.com/serai-dex/patch-polkadot-sdk", rev = "d4624c561765c13b38eb566e435131a8c329a543", default-features = false }
frame-support = { git = "https://github.com/serai-dex/patch-polkadot-sdk", rev = "d4624c561765c13b38eb566e435131a8c329a543", default-features = false } frame-support = { git = "https://github.com/serai-dex/patch-polkadot-sdk", rev = "d4624c561765c13b38eb566e435131a8c329a543", default-features = false }
serai-primitives = { path = "../primitives", default-features = false } serai-abi = { path = "../abi", default-features = false, features = ["substrate"] }
validator-sets-pallet = { package = "serai-validator-sets-pallet", path = "../validator-sets", default-features = false } validator-sets-pallet = { package = "serai-validator-sets-pallet", path = "../validator-sets", default-features = false }
in-instructions-pallet = { package = "serai-in-instructions-pallet", path = "../in-instructions", default-features = false }
[features] [features]
std = [ std = [
@@ -42,10 +41,9 @@ std = [
"frame-system/std", "frame-system/std",
"frame-support/std", "frame-support/std",
"serai-primitives/std", "serai-abi/std",
"validator-sets-pallet/std", "validator-sets-pallet/std",
"in-instructions-pallet/std",
] ]
runtime-benchmarks = [ runtime-benchmarks = [

View File

@@ -0,0 +1 @@
# Serai Signals Pallet

View File

@@ -1,33 +1,31 @@
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![doc = include_str!("../README.md")]
#![deny(missing_docs)]
#![cfg_attr(not(feature = "std"), no_std)] #![cfg_attr(not(feature = "std"), no_std)]
#[allow( extern crate alloc;
deprecated,
unreachable_patterns, #[expect(clippy::cast_possible_truncation)]
clippy::let_unit_value,
clippy::cast_possible_truncation,
clippy::ignored_unit_patterns
)] // TODO
#[frame_support::pallet] #[frame_support::pallet]
pub mod pallet { pub mod pallet {
use sp_core::sr25519::Public; use sp_core::sr25519::Public;
use sp_io::hashing::blake2_256;
use serai_abi::{primitives::{prelude::*, signals::*}, SubstrateBlock};
use frame_system::pallet_prelude::*; use frame_system::pallet_prelude::*;
// False positive use frame_support::pallet_prelude::*;
#[allow(unused)]
use frame_support::{pallet_prelude::*, sp_runtime};
use serai_primitives::*; use validator_sets_pallet::{Config as VsConfig, Pallet as VsPallet};
use serai_signals_primitives::SignalId;
use validator_sets_pallet::{primitives::ValidatorSet, Config as VsConfig, Pallet as VsPallet};
use in_instructions_pallet::{Config as IiConfig, Pallet as InInstructions};
#[pallet::config] #[pallet::config]
pub trait Config: frame_system::Config<AccountId = Public> + VsConfig + IiConfig { pub trait Config: frame_system::Config<AccountId = Public, Block = SubstrateBlock> + VsConfig {
type RuntimeEvent: IsType<<Self as frame_system::Config>::RuntimeEvent> + From<Event<Self>>; /// How long a candidate retirement signal is valid for.
///
type RetirementValidityDuration: Get<u32>; /// This MUST be equal to the rate at which new sets are attempted.
type RetirementLockInDuration: Get<u32>; // TODO: Fetch from `validator_sets::Config`.
type RetirementValidityDuration: Get<u64>;
/// How long a retirement signal is locked-in for before retirement..
type RetirementLockInDuration: Get<u64>;
} }
#[pallet::genesis_config] #[pallet::genesis_config]
@@ -43,8 +41,12 @@ pub mod pallet {
#[pallet::genesis_build] #[pallet::genesis_build]
impl<T: Config> BuildGenesisConfig for GenesisConfig<T> { impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
fn build(&self) { fn build(&self) {
// Assert the validity duration is less than the lock-in duration so lock-in periods /*
// automatically invalidate other retirement signals Assert the validity duration is less than the lock-in duration.
This way, while the the signal is locked-in, any/all other candidate retirement signals
will expire.
*/
assert!(T::RetirementValidityDuration::get() < T::RetirementLockInDuration::get()); assert!(T::RetirementValidityDuration::get() < T::RetirementLockInDuration::get());
} }
} }
@@ -52,204 +54,200 @@ pub mod pallet {
#[pallet::pallet] #[pallet::pallet]
pub struct Pallet<T>(PhantomData<T>); pub struct Pallet<T>(PhantomData<T>);
#[derive(Clone, PartialEq, Eq, Debug, Encode, Decode, MaxEncodedLen)] /// The registered retirement signals.
pub struct RegisteredRetirementSignal<T: Config> {
in_favor_of: [u8; 32],
registrant: T::AccountId,
registered_at: BlockNumberFor<T>,
}
impl<T: Config> RegisteredRetirementSignal<T> {
fn id(&self) -> [u8; 32] {
let mut preimage = b"Signal".to_vec();
preimage.extend(&self.encode());
blake2_256(&preimage)
}
}
#[pallet::storage] #[pallet::storage]
type RegisteredRetirementSignals<T: Config> = type RegisteredRetirementSignals<T: Config> =
StorageMap<_, Blake2_128Concat, [u8; 32], RegisteredRetirementSignal<T>, OptionQuery>; StorageMap<_, Blake2_128Concat, [u8; 32], RegisteredRetirementSignal, OptionQuery>;
/// The registered favors.
#[pallet::storage] #[pallet::storage]
pub type Favors<T: Config> = StorageDoubleMap< type Favors<T: Config> = StorageDoubleMap<
_, _,
Blake2_128Concat, Blake2_128Concat,
(SignalId, NetworkId), (Signal, NetworkId),
Blake2_128Concat, Blake2_128Concat,
T::AccountId, T::AccountId,
(), (),
OptionQuery, OptionQuery,
>; >;
/// The networks in favor of a signal.
#[pallet::storage] #[pallet::storage]
pub type SetsInFavor<T: Config> = type NetworksInFavor<T: Config> =
StorageMap<_, Blake2_128Concat, (SignalId, ValidatorSet), (), OptionQuery>; StorageMap<_, Blake2_128Concat, (Signal, NetworkId), (), OptionQuery>;
/// The locked-in retirement signal.
///
/// This is in the format `(protocol_id, retiry_block)`.
#[pallet::storage] #[pallet::storage]
pub type LockedInRetirement<T: Config> = type LockedInRetirement<T: Config> =
StorageValue<_, ([u8; 32], BlockNumberFor<T>), OptionQuery>; StorageValue<_, (ProtocolId, BlockNumberFor<T>), OptionQuery>;
#[pallet::event] /// Halted networks.
#[pallet::generate_deposit(pub(super) fn deposit_event)] ///
pub enum Event<T: Config> { /// Halted networks will be halted for the remainder of this protocol's lifetime.
RetirementSignalRegistered { #[pallet::storage]
signal_id: [u8; 32], type Halted<T: Config> = StorageMap<_, Identity, ExternalNetworkId, (), OptionQuery>;
in_favor_of: [u8; 32],
registrant: T::AccountId, #[pallet::hooks]
}, impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
RetirementSignalRevoked { fn on_initialize(current_number: BlockNumberFor<T>) -> Weight {
signal_id: [u8; 32], /*
}, If this is the block at which a locked-in retirement signal has been locked-in for long
SignalFavored { enough, panic, halting the blockchain, and retiring the current protocol.
signal_id: SignalId, */
by: T::AccountId, if let Some((protocol_id, block_number)) = LockedInRetirement::<T>::get() {
for_network: NetworkId, if block_number == current_number {
}, panic!(
SetInFavor { "protocol retired in favor of {}",
signal_id: SignalId, sp_core::hexdisplay::HexDisplay::from(&protocol_id)
set: ValidatorSet, );
}, }
RetirementSignalLockedIn { }
signal_id: [u8; 32], // Using `Weight::zero()` is fine here as this is a minute operation
}, Weight::zero()
SetNoLongerInFavor { }
signal_id: SignalId,
set: ValidatorSet,
},
FavorRevoked {
signal_id: SignalId,
by: T::AccountId,
for_network: NetworkId,
},
AgainstSignal {
signal_id: SignalId,
who: T::AccountId,
for_network: NetworkId,
},
} }
#[pallet::error]
pub enum Error<T> {
RetirementSignalLockedIn,
RetirementSignalAlreadyRegistered,
NotRetirementSignalRegistrant,
NonExistentRetirementSignal,
ExpiredRetirementSignal,
NotValidator,
RevokingNonExistentFavor,
}
// 80% threshold
// TODO: Use 34% for halting a set (not 80%)
const REQUIREMENT_NUMERATOR: u64 = 4;
const REQUIREMENT_DIVISOR: u64 = 5;
impl<T: Config> Pallet<T> { impl<T: Config> Pallet<T> {
// Returns true if this network's current set is in favor of the signal. /// Tally the support for a signal by a network's current validator set.
// ///
// Must only be called for networks which have a set decided. /// This will mutate the storage with the result.
fn tally_for_network(signal_id: SignalId, network: NetworkId) -> bool { ///
let this_network_session = VsPallet::<T>::latest_decided_session(network).unwrap(); /// This returns `true` if the network is sufficiently in favor of the signal.
let this_set = ValidatorSet { network, session: this_network_session }; fn tally_for_network(signal: Signal, network: NetworkId) -> bool {
let Some(current_session) = VsPallet::<T>::current_session(network) else { return false };
let current_set = ValidatorSet { network, session: current_session };
let Some(latest_session) = VsPallet::<T>::latest_decided_session(network) else {
panic!("current session yet no latest decided session")
};
let latest_set = ValidatorSet { network, session: latest_session };
// This is a bounded O(n) (which is still acceptable) due to the infeasibility of caching /*
// here The following uses key shares, not allocations, as key shares are static while allocations
// TODO: Make caching feasible? Do a first-pass with cache then actual pass before fluctuate during the duration of a validator set.
// execution? */
let mut iter = Favors::<T>::iter_prefix_values((signal_id, network));
let mut needed_favor = (VsPallet::<T>::total_allocated_stake(network).unwrap().0 * let mut needed_favor = {
REQUIREMENT_NUMERATOR) let current = VsPallet::<T>::key_shares(current_set)
.div_ceil(REQUIREMENT_DIVISOR); .expect("current validator set without key shares set")
while iter.next().is_some() && (needed_favor != 0) { .0;
let item_key = iter.last_raw_key(); let latest = VsPallet::<T>::key_shares(latest_set)
// `.len() - 32` is safe because AccountId is bound to being Public, which is 32 bytes .expect("latest validator set without key shares set")
let account = T::AccountId::decode(&mut &item_key[(item_key.len() - 32) ..]).unwrap(); .0;
if VsPallet::<T>::in_latest_decided_set(network, account) { current.max(latest)
// This call uses the current allocation, not the allocation at the time of set };
// decision for (validator, ()) in Favors::<T>::iter_prefix((signal, network)) {
// This is deemed safe due to the validator-set pallet's deallocation scheduling /*
// unwrap is safe due to being in the latest decided set Fetch the amount of key shares the validator has.
needed_favor =
needed_favor.saturating_sub(VsPallet::<T>::allocation((network, account)).unwrap().0); This uses the minimum amount of key shares across the current validator set and the
} latest decided validator set to ensure this validator represents this network and will
continue to do so.
*/
let key_shares = {
let current = VsPallet::<T>::key_shares_possessed_by_validator(current_set, validator)
.unwrap_or(KeyShares::ZERO);
let latest = VsPallet::<T>::key_shares_possessed_by_validator(latest_set, validator)
.unwrap_or(KeyShares::ZERO);
current.0.min(latest.0)
};
let Some(still_needed_favor) = needed_favor.checked_sub(key_shares) else {
needed_favor = 0;
break;
};
needed_favor = still_needed_favor;
} }
if needed_favor == 0 { let now_in_favor = needed_favor == 0;
// Set the set as in favor until someone triggers a re-tally
// // Update the storage and emit an event, if appropriate
// Since a re-tally is an extra step we can't assume will occur, this effectively means a if now_in_favor {
// network in favor across any point in its Session is in favor for its entire Session let prior_in_favor = NetworksInFavor::<T>::contains_key((signal, network));
// While a malicious actor could increase their stake, favor a signal, then deallocate, NetworksInFavor::<T>::set((signal, network), Some(()));
// this is largely prevented by deallocation scheduling if !prior_in_favor {
// todo!("Event");
// At any given point, only just under 50% of a set can be immediately deallocated
// (if each validator has just under two key shares, they can deallocate the entire amount
// above a single key share)
//
// This means that if a signal has a 67% adoption threshold, and someone executes this
// attack, they still have a majority of the allocated stake (though less of a majority
// than desired)
//
// With the 80% threshold, removing 39.9% creates a 40.1% to 20% ratio, which is still
// the BFT threshold of 67%
if !SetsInFavor::<T>::contains_key((signal_id, this_set)) {
SetsInFavor::<T>::set((signal_id, this_set), Some(()));
Self::deposit_event(Event::SetInFavor { signal_id, set: this_set });
} }
true
} else { } else {
if SetsInFavor::<T>::contains_key((signal_id, this_set)) { #[allow(clippy::collapsible_else_if)]
// This should no longer be under the current tally if NetworksInFavor::<T>::take((signal, network)).is_some() {
SetsInFavor::<T>::remove((signal_id, this_set)); todo!("Event");
Self::deposit_event(Event::SetNoLongerInFavor { signal_id, set: this_set });
} }
false
} }
now_in_favor
} }
fn tally_for_all_networks(signal_id: SignalId) -> bool { /// Tally support for a signal across all networks, weighted by stake.
///
/// Returns `true` if the signal has sufficient support.
fn tally_for_all_networks(signal: Signal) -> bool {
let mut total_in_favor_stake = 0; let mut total_in_favor_stake = 0;
let mut total_allocated_stake = 0; let mut total_allocated_stake = 0;
for network in serai_primitives::NETWORKS { for network in NetworkId::all() {
let Some(latest_decided_session) = VsPallet::<T>::latest_decided_session(network) else { /*
continue; This doesn't consider if the latest decided validator set has considerably less stake,
}; yet the bound validators vote by the minimum of their key shares, against the maximum of
// If it has a session, it should have a total allocated stake value the total key shares, should be sufficient in this regard.
let network_stake = VsPallet::<T>::total_allocated_stake(network).unwrap(); */
if SetsInFavor::<T>::contains_key(( let network_stake =
signal_id, VsPallet::<T>::stake_for_current_validator_set(network).unwrap_or(Amount(0));
ValidatorSet { network, session: latest_decided_session }, if NetworksInFavor::<T>::contains_key((signal, network)) {
)) {
total_in_favor_stake += network_stake.0; total_in_favor_stake += network_stake.0;
} }
total_allocated_stake += network_stake.0; total_allocated_stake += network_stake.0;
} }
total_in_favor_stake >= /*
(total_allocated_stake * REQUIREMENT_NUMERATOR).div_ceil(REQUIREMENT_DIVISOR) We use a 80% threshold for retirement, calculated as defined above, but just a 34%
threshold for halting another validator set. This is representative of how 34% of
validators can cause a liveness failure during asynchronous BFT>
*/
let threshold = match signal {
Signal::Retire { .. } => (total_allocated_stake * 4) / 5,
Signal::Halt { .. } => (total_allocated_stake * 2) / 3,
};
total_in_favor_stake > threshold
} }
fn revoke_favor_internal( fn revoke_favor_internal(
account: T::AccountId, validator: T::AccountId,
signal_id: SignalId, signal: Signal,
for_network: NetworkId, for_network: NetworkId,
) -> DispatchResult { ) -> DispatchResult {
if !Favors::<T>::contains_key((signal_id, for_network), account) { if !Favors::<T>::contains_key((signal, for_network), validator) {
Err::<(), _>(Error::<T>::RevokingNonExistentFavor)?; Err::<(), _>(Error::<T>::RevokingNonExistentFavor)?;
} }
Favors::<T>::remove((signal_id, for_network), account); Favors::<T>::remove((signal, for_network), validator);
Self::deposit_event(Event::<T>::FavorRevoked { signal_id, by: account, for_network }); // TODO: Event
// tally_for_network assumes the network is active, which is implied by having prior set a
// favor for it // Update the tally for this network
// Technically, this tally may make the network in favor and justify re-tallying for all Self::tally_for_network(signal, for_network);
// networks
// Its assumed not to
Self::tally_for_network(signal_id, for_network);
Ok(()) Ok(())
} }
} }
/// An error from the `signals` pallet.
#[pallet::error]
pub enum Error<T> {
/// A retirement signal has already been locked in.
RetirementSignalLockedIn,
/// This retirement signal has already been registered.
RetirementSignalAlreadyRegistered,
/// The caller is not the registrant of the retirement signal.
NotRetirementSignalRegistrant,
/// The retirement signal does not exist.
NonExistentRetirementSignal,
/// The retirement signal has expired.
ExpiredRetirementSignal,
/// The caller is already in favor.
AlreadyInFavor,
/// Revoking favor when no favor has been expressed.
RevokingNonExistentFavor,
}
#[pallet::call] #[pallet::call]
impl<T: Config> Pallet<T> { impl<T: Config> Pallet<T> {
/// Register a retirement signal, declaring the consensus protocol this signal is in favor of. /// Register a retirement signal, declaring the consensus protocol this signal is in favor of.
@@ -257,7 +255,7 @@ pub mod pallet {
/// Retirement signals are registered so that the proposer, presumably a developer, can revoke /// Retirement signals are registered so that the proposer, presumably a developer, can revoke
/// the signal if there's a fault discovered. /// the signal if there's a fault discovered.
#[pallet::call_index(0)] #[pallet::call_index(0)]
#[pallet::weight(0)] // TODO #[pallet::weight((0, DispatchClass::Normal))] // TODO
pub fn register_retirement_signal( pub fn register_retirement_signal(
origin: OriginFor<T>, origin: OriginFor<T>,
in_favor_of: [u8; 32], in_favor_of: [u8; 32],
@@ -267,14 +265,17 @@ pub mod pallet {
Err::<(), _>(Error::<T>::RetirementSignalLockedIn)?; Err::<(), _>(Error::<T>::RetirementSignalLockedIn)?;
} }
let account = ensure_signed(origin)?; let validator = ensure_signed(origin)?;
// Bind the signal ID to the proposer /*
// This prevents a malicious actor from frontrunning a proposal, causing them to be the Bind the signal ID to the proposer.
// registrant, just to cancel it later
This prevents a malicious actor from frontrunning a proposal, causing them to be the
registrant, just to cancel it later.
*/
let signal = RegisteredRetirementSignal { let signal = RegisteredRetirementSignal {
in_favor_of, in_favor_of,
registrant: account, registrant: validator.into(),
registered_at: frame_system::Pallet::<T>::block_number(), registered_at: frame_system::Pallet::<T>::block_number(),
}; };
let signal_id = signal.id(); let signal_id = signal.id();
@@ -282,122 +283,108 @@ pub mod pallet {
if RegisteredRetirementSignals::<T>::get(signal_id).is_some() { if RegisteredRetirementSignals::<T>::get(signal_id).is_some() {
Err::<(), _>(Error::<T>::RetirementSignalAlreadyRegistered)?; Err::<(), _>(Error::<T>::RetirementSignalAlreadyRegistered)?;
} }
Self::deposit_event(Event::<T>::RetirementSignalRegistered {
signal_id,
in_favor_of,
registrant: account,
});
RegisteredRetirementSignals::<T>::set(signal_id, Some(signal)); RegisteredRetirementSignals::<T>::set(signal_id, Some(signal));
// TODO: Event
Ok(()) Ok(())
} }
/// Revoke a retirement signal.
#[pallet::call_index(1)] #[pallet::call_index(1)]
#[pallet::weight(0)] // TODO #[pallet::weight((0, DispatchClass::Normal))] // TODO
pub fn revoke_retirement_signal( pub fn revoke_retirement_signal(
origin: OriginFor<T>, origin: OriginFor<T>,
retirement_signal_id: [u8; 32], retirement_signal: [u8; 32],
) -> DispatchResult { ) -> DispatchResult {
let account = ensure_signed(origin)?; let validator = ensure_signed(origin)?;
let Some(registered_signal) = RegisteredRetirementSignals::<T>::get(retirement_signal_id) let Some(registered_signal) = RegisteredRetirementSignals::<T>::get(retirement_signal)
else { else {
return Err::<(), _>(Error::<T>::NonExistentRetirementSignal.into()); return Err::<(), _>(Error::<T>::NonExistentRetirementSignal.into());
}; };
if account != registered_signal.registrant { if SeraiAddress::from(validator) != registered_signal.registrant {
Err::<(), _>(Error::<T>::NotRetirementSignalRegistrant)?; Err::<(), _>(Error::<T>::NotRetirementSignalRegistrant)?;
} }
RegisteredRetirementSignals::<T>::remove(retirement_signal_id); RegisteredRetirementSignals::<T>::remove(retirement_signal);
// If this signal was locked in, remove it /*
// This lets a post-lock-in discovered fault be prevented from going live without If this signal was locked in, remove it.
// intervention by all validators
if LockedInRetirement::<T>::get().map(|(signal_id, _block_number)| signal_id) == This lets a post-lock-in discovered fault be prevented from going live without intervention
Some(retirement_signal_id) by a supermajority of validators.
*/
if LockedInRetirement::<T>::get().map(|(signal, _block_number)| signal) ==
Some(retirement_signal)
{ {
LockedInRetirement::<T>::kill(); LockedInRetirement::<T>::kill();
} }
Self::deposit_event(Event::<T>::RetirementSignalRevoked { signal_id: retirement_signal_id }); // TODO: Event
Ok(()) Ok(())
} }
/// Favor a signal.
#[pallet::call_index(2)] #[pallet::call_index(2)]
#[pallet::weight(0)] // TODO #[pallet::weight((0, DispatchClass::Normal))] // TODO
pub fn favor( pub fn favor(
origin: OriginFor<T>, origin: OriginFor<T>,
signal_id: SignalId, signal: Signal,
for_network: NetworkId, for_network: NetworkId,
) -> DispatchResult { ) -> DispatchResult {
let account = ensure_signed(origin)?; let validator = ensure_signed(origin)?;
// If this is a retirement signal, perform the relevant checks // Perform the relevant checks for this class of signal
if let SignalId::Retirement(signal_id) = signal_id { match signal {
// Make sure a retirement hasn't already been locked in Signal::Retire { signal_id } => {
if LockedInRetirement::<T>::exists() { // Make sure a retirement hasn't already been locked in
Err::<(), _>(Error::<T>::RetirementSignalLockedIn)?; if LockedInRetirement::<T>::exists() {
} Err::<(), _>(Error::<T>::RetirementSignalLockedIn)?;
}
// Make sure this is a registered retirement /*
// We don't have to do this for a `Halt` signal as `Halt` doesn't have the registration Make sure this is a registered retirement.
// process
let Some(registered_signal) = RegisteredRetirementSignals::<T>::get(signal_id) else {
return Err::<(), _>(Error::<T>::NonExistentRetirementSignal.into());
};
// Check the signal isn't out of date We don't have to do this for a `Halt` signal as `Halt` doesn't have the registration
// This isn't truly necessary since we only track votes from the most recent validator process.
// sets, ensuring modern relevancy */
// The reason to still have it is because locking in a dated runtime may cause a corrupt let Some(registered_signal) = RegisteredRetirementSignals::<T>::get(signal_id) else {
// blockchain and lead to a failure in system integrity return Err::<(), _>(Error::<T>::NonExistentRetirementSignal.into())
// `Halt`, which doesn't have this check, at worst causes temporary downtime };
if (registered_signal.registered_at + T::RetirementValidityDuration::get().into()) <
frame_system::Pallet::<T>::block_number() // Check the signal isn't out of date, and its tallies with it.
{ if (registered_signal.registered_at + T::RetirementValidityDuration::get()) <
Err::<(), _>(Error::<T>::ExpiredRetirementSignal)?; frame_system::Pallet::<T>::block_number()
} {
Err::<(), _>(Error::<T>::ExpiredRetirementSignal)?;
}
},
Signal::Halt { .. } => {}
} }
// Check the signer is a validator if Favors::<T>::contains_key((signal, for_network), validator) {
// Technically, in the case of Serai, this will check they're planned to be in the next set, Err::<(), _>(Error::<T>::AlreadyInFavor)?;
// not that they are in the current set
// This is a practical requirement due to the lack of tracking historical allocations, and
// fine for the purposes here
if !VsPallet::<T>::in_latest_decided_set(for_network, account) {
Err::<(), _>(Error::<T>::NotValidator)?;
} }
// Set them as in-favor // Set the validator as in favor
// Doesn't error if they already voted in order to let any validator trigger a re-tally Favors::<T>::set((signal, for_network), validator, Some(()));
if !Favors::<T>::contains_key((signal_id, for_network), account) { // TODO: Event
Favors::<T>::set((signal_id, for_network), account, Some(()));
Self::deposit_event(Event::SignalFavored { signal_id, by: account, for_network });
}
// Check if the network is in favor // Check if the network is in favor
// tally_for_network expects the network to be active, which is implied by being in the let network_in_favor = Self::tally_for_network(signal, for_network);
// latest decided set
let network_in_favor = Self::tally_for_network(signal_id, for_network);
// If this network is in favor, check if enough networks are // If this network is in favor, check if enough networks are
// We could optimize this by only running the following code when the network is *newly* in if network_in_favor && Self::tally_for_all_networks(signal) {
// favor
// Re-running the following code ensures that if networks' allocated stakes change relative
// to each other, any new votes will cause a re-tally
if network_in_favor {
// If enough are, lock in the signal // If enough are, lock in the signal
if Self::tally_for_all_networks(signal_id) { match signal {
match signal_id { Signal::Retire { signal_id } => {
SignalId::Retirement(signal_id) => { LockedInRetirement::<T>::set(Some((
LockedInRetirement::<T>::set(Some(( signal_id,
signal_id, frame_system::Pallet::<T>::block_number() + T::RetirementLockInDuration::get()
frame_system::Pallet::<T>::block_number() + )));
T::RetirementLockInDuration::get().into(), // TODO: Event
))); }
Self::deposit_event(Event::RetirementSignalLockedIn { signal_id }); Signal::Halt(network) => {
} Halted::<T>::set(network, Some(()));
SignalId::Halt(network) => { // TODO: Event
InInstructions::<T>::halt(network)?;
}
} }
} }
} }
@@ -407,75 +394,110 @@ pub mod pallet {
/// Revoke favor into an abstaining position. /// Revoke favor into an abstaining position.
#[pallet::call_index(3)] #[pallet::call_index(3)]
#[pallet::weight(0)] // TODO #[pallet::weight((0, DispatchClass::Normal))] // TODO
pub fn revoke_favor( pub fn revoke_favor(
origin: OriginFor<T>, origin: OriginFor<T>,
signal_id: SignalId, signal: Signal,
for_network: NetworkId, for_network: NetworkId,
) -> DispatchResult { ) -> DispatchResult {
if matches!(&signal_id, SignalId::Retirement(_)) && LockedInRetirement::<T>::exists() { match signal {
Err::<(), _>(Error::<T>::RetirementSignalLockedIn)?; Signal::Retire { .. } => {
if LockedInRetirement::<T>::exists() {
Err::<(), _>(Error::<T>::RetirementSignalLockedIn)?;
}
}
Signal::Halt { .. } => {}
} }
// Doesn't check the signal exists due to later checking the favor exists let validator = ensure_signed(origin)?;
// While the signal may have been revoked, making this pointless, it's not worth the storage Self::revoke_favor_internal(validator, signal, for_network)
// read on every call to check
// Since revoke will re-tally, this does technically mean a network will become in-favor of a
// revoked signal. Since revoke won't re-tally for all networks/lock-in, this is also fine
Self::revoke_favor_internal(ensure_signed(origin)?, signal_id, for_network)
} }
/// Emit an event standing against the signal. /// Emit an event standing against the signal.
/// ///
/// While disapprovals aren't tracked explicitly, this is used to at least label a validator's
/// opinion and allow better collection of data.
///
/// If the origin is currently in favor of the signal, their favor will be revoked. /// If the origin is currently in favor of the signal, their favor will be revoked.
#[pallet::call_index(4)] #[pallet::call_index(4)]
#[pallet::weight(0)] // TODO #[pallet::weight((0, DispatchClass::Normal))] // TODO
pub fn stand_against( pub fn stand_against(
origin: OriginFor<T>, origin: OriginFor<T>,
signal_id: SignalId, signal: Signal,
for_network: NetworkId, for_network: NetworkId,
) -> DispatchResult { ) -> DispatchResult {
if LockedInRetirement::<T>::exists() { match signal {
Err::<(), _>(Error::<T>::RetirementSignalLockedIn)?; Signal::Retire { .. } => {
if LockedInRetirement::<T>::exists() {
Err::<(), _>(Error::<T>::RetirementSignalLockedIn)?;
}
}
Signal::Halt { .. } => {}
} }
let account = ensure_signed(origin)?; let validator = ensure_signed(origin)?;
// If currently in favor, revoke the favor // If currently in favor, revoke the favor
if Favors::<T>::contains_key((signal_id, for_network), account) { if Favors::<T>::contains_key((signal, for_network), validator) {
Self::revoke_favor_internal(account, signal_id, for_network)?; Self::revoke_favor_internal(validator, signal, for_network)?;
} else { } else {
// Check this Signal exists (which would've been implied by Favors for it existing) // Check this Signal exists (which would've been implied by `Favors` for it existing)
if let SignalId::Retirement(signal_id) = signal_id { match signal {
if RegisteredRetirementSignals::<T>::get(signal_id).is_none() { Signal::Retire { signal_id } => {
Err::<(), _>(Error::<T>::NonExistentRetirementSignal)?; if RegisteredRetirementSignals::<T>::get(signal_id).is_none() {
Err::<(), _>(Error::<T>::NonExistentRetirementSignal)?;
}
} }
Signal::Halt { .. } => {}
} }
} }
// Emit an event that we're against the signal // Emit the event
// No actual effects happen besides this // TODO: Event
Self::deposit_event(Event::<T>::AgainstSignal { signal_id, who: account, for_network });
Ok(()) Ok(())
} }
} }
#[pallet::hooks] /* TODO
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> { #[pallet::event]
fn on_initialize(current_number: BlockNumberFor<T>) -> Weight { #[pallet::generate_deposit(pub(super) fn deposit_event)]
// If this is the block at which a locked-in signal has been set for long enough, panic pub enum Event<T: Config> {
// This will prevent this block from executing and halt the chain RetirementSignalRegistered {
if let Some((signal, block_number)) = LockedInRetirement::<T>::get() { signal: [u8; 32],
if block_number == current_number { in_favor_of: [u8; 32],
panic!( registrant: T::AccountId,
"locked-in signal {} has been set for too long", },
sp_core::hexdisplay::HexDisplay::from(&signal), RetirementSignalRevoked {
); signal_id: [u8; 32],
} },
} SignalFavored {
Weight::zero() // TODO signal_id: Signal,
} by: T::AccountId,
for_network: NetworkId,
},
SetInFavor {
signal_id: Signal,
set: ValidatorSet,
},
RetirementSignalLockedIn {
signal_id: [u8; 32],
},
SetNoLongerInFavor {
signal_id: Signal,
set: ValidatorSet,
},
FavorRevoked {
signal_id: Signal,
by: T::AccountId,
for_network: NetworkId,
},
AgainstSignal {
signal_id: Signal,
who: T::AccountId,
for_network: NetworkId,
},
} }
*/
} }
pub use pallet::*; pub use pallet::*;

View File

@@ -1,6 +1,6 @@
use sp_core::{Encode, sr25519::Public}; use sp_core::{Encode, sr25519::Public};
use serai_primitives::{constants::MAX_KEY_SHARES_PER_SET, network_id::NetworkId, balance::Amount}; use serai_primitives::{network_id::NetworkId, balance::Amount, validator_sets::KeyShares};
use frame_support::storage::{StorageMap, StoragePrefixedMap}; use frame_support::storage::{StorageMap, StoragePrefixedMap};
@@ -63,7 +63,7 @@ pub(crate) trait Allocations {
) -> impl Iterator<Item = (Public, Amount)>; ) -> impl Iterator<Item = (Public, Amount)>;
/// Calculate the expected key shares for a network, per the current allocations. /// Calculate the expected key shares for a network, per the current allocations.
fn expected_key_shares(network: NetworkId, allocation_per_key_share: Amount) -> u64; fn expected_key_shares(network: NetworkId, allocation_per_key_share: Amount) -> KeyShares;
} }
/// Reverses the lexicographic order of a given byte array. /// Reverses the lexicographic order of a given byte array.
@@ -149,17 +149,16 @@ impl<Storage: AllocationsStorage> Allocations for Storage {
.filter(move |(_key, allocation)| *allocation >= minimum_allocation) .filter(move |(_key, allocation)| *allocation >= minimum_allocation)
} }
fn expected_key_shares(network: NetworkId, allocation_per_key_share: Amount) -> u64 { fn expected_key_shares(network: NetworkId, allocation_per_key_share: Amount) -> KeyShares {
let mut total_key_shares = 0; let mut total_key_shares = 0;
for (_, amount) in Self::iter_allocations(network, allocation_per_key_share) { for (_, amount) in Self::iter_allocations(network, allocation_per_key_share) {
let key_shares = amount.0 / allocation_per_key_share.0; total_key_shares += KeyShares::from_allocation(amount, allocation_per_key_share).0;
total_key_shares += key_shares;
if total_key_shares >= u64::from(MAX_KEY_SHARES_PER_SET) { if total_key_shares >= KeyShares::MAX_PER_SET {
break; break;
} }
} }
total_key_shares KeyShares::saturating_from(total_key_shares)
} }
} }

View File

@@ -3,6 +3,7 @@
#![cfg_attr(not(feature = "std"), no_std)] #![cfg_attr(not(feature = "std"), no_std)]
extern crate alloc; extern crate alloc;
use alloc::vec::Vec;
mod embedded_elliptic_curve_keys; mod embedded_elliptic_curve_keys;
use embedded_elliptic_curve_keys::*; use embedded_elliptic_curve_keys::*;
@@ -72,7 +73,7 @@ impl<T: pallet::Config> GetValidatorCount for MembershipProof<T> {
} }
*/ */
#[expect(clippy::ignored_unit_patterns, clippy::cast_possible_truncation)] #[expect(clippy::cast_possible_truncation)]
#[frame_support::pallet] #[frame_support::pallet]
mod pallet { mod pallet {
use sp_core::sr25519::Public; use sp_core::sr25519::Public;
@@ -81,7 +82,12 @@ mod pallet {
use frame_support::pallet_prelude::*; use frame_support::pallet_prelude::*;
use serai_primitives::{ use serai_primitives::{
crypto::KeyPair, network_id::*, coin::*, balance::*, validator_sets::*, address::SeraiAddress, crypto::KeyPair,
network_id::*,
coin::*,
balance::*,
validator_sets::{Session, ExternalValidatorSet, ValidatorSet, KeyShares as KeySharesStruct},
address::SeraiAddress,
}; };
use coins_pallet::Pallet as Coins; use coins_pallet::Pallet as Coins;
@@ -89,9 +95,7 @@ mod pallet {
use super::*; use super::*;
#[pallet::config] #[pallet::config]
pub trait Config: frame_system::Config + coins_pallet::Config { pub trait Config: frame_system::Config + coins_pallet::Config<coins_pallet::CoinsInstance> {
type RuntimeEvent: IsType<<Self as frame_system::Config>::RuntimeEvent> + From<Event<Self>>;
// type ShouldEndSession: ShouldEndSession<BlockNumberFor<Self>>; // type ShouldEndSession: ShouldEndSession<BlockNumberFor<Self>>;
} }
@@ -199,10 +203,12 @@ mod pallet {
type CurrentSession<T: Config> = StorageMap<_, Identity, NetworkId, Session, OptionQuery>; type CurrentSession<T: Config> = StorageMap<_, Identity, NetworkId, Session, OptionQuery>;
#[pallet::storage] #[pallet::storage]
type LatestDecidedSession<T: Config> = StorageMap<_, Identity, NetworkId, Session, OptionQuery>; type LatestDecidedSession<T: Config> = StorageMap<_, Identity, NetworkId, Session, OptionQuery>;
#[pallet::storage]
type KeyShares<T: Config> = StorageMap<_, Identity, ValidatorSet, KeySharesStruct, OptionQuery>;
// This has to use `Identity` per the documentation of `SessionsStorage` // This has to use `Identity` per the documentation of `SessionsStorage`
#[pallet::storage] #[pallet::storage]
type SelectedValidators<T: Config> = type SelectedValidators<T: Config> =
StorageMap<_, Identity, SelectedValidatorsKey, u64, OptionQuery>; StorageMap<_, Identity, SelectedValidatorsKey, KeySharesStruct, OptionQuery>;
#[pallet::storage] #[pallet::storage]
type TotalAllocatedStake<T: Config> = StorageMap<_, Identity, NetworkId, Amount, OptionQuery>; type TotalAllocatedStake<T: Config> = StorageMap<_, Identity, NetworkId, Amount, OptionQuery>;
#[pallet::storage] #[pallet::storage]
@@ -214,15 +220,12 @@ mod pallet {
type AllocationPerKeyShare = AllocationPerKeyShare<T>; type AllocationPerKeyShare = AllocationPerKeyShare<T>;
type CurrentSession = CurrentSession<T>; type CurrentSession = CurrentSession<T>;
type LatestDecidedSession = LatestDecidedSession<T>; type LatestDecidedSession = LatestDecidedSession<T>;
type KeyShares = KeyShares<T>;
type SelectedValidators = SelectedValidators<T>; type SelectedValidators = SelectedValidators<T>;
type TotalAllocatedStake = TotalAllocatedStake<T>; type TotalAllocatedStake = TotalAllocatedStake<T>;
type DelayedDeallocations = DelayedDeallocations<T>; type DelayedDeallocations = DelayedDeallocations<T>;
} }
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {}
/* /*
/// The generated key pair for a given validator set instance. /// The generated key pair for a given validator set instance.
#[pallet::storage] #[pallet::storage]
@@ -347,6 +350,45 @@ mod pallet {
SeraiAddress::system(b"ValidatorSets").into() SeraiAddress::system(b"ValidatorSets").into()
} }
/// The current session for a network.
pub fn current_session(network: NetworkId) -> Option<Session> {
Abstractions::<T>::current_session(network)
}
/// The latest decided session for a network.
pub fn latest_decided_session(network: NetworkId) -> Option<Session> {
Abstractions::<T>::latest_decided_session(network)
}
/// The amount of key shares a validator has.
///
/// Returns `None` for historic sessions which we no longer have the data for.
pub fn key_shares(set: ValidatorSet) -> Option<KeySharesStruct> {
Abstractions::<T>::key_shares(set)
}
/// If a validator is present within the specified validator set.
///
/// This MAY return `false` for _any_ historic session, even if the validator _was_ present,
pub fn in_validator_set(set: ValidatorSet, validator: Public) -> bool {
Abstractions::<T>::in_validator_set(set, validator)
}
/// The key shares possessed by a validator, within a validator set.
///
/// This MAY return `None` for _any_ historic session, even if the validator _was_ present,
pub fn key_shares_possessed_by_validator(
set: ValidatorSet,
validator: Public,
) -> Option<KeySharesStruct> {
Abstractions::<T>::key_shares_possessed_by_validator(set, validator)
}
/// The stake for the current validator set.
pub fn stake_for_current_validator_set(network: NetworkId) -> Option<Amount> {
Abstractions::<T>::stake_for_current_validator_set(network)
}
/* /*
// is_bft returns if the network is able to survive any single node becoming byzantine. // is_bft returns if the network is able to survive any single node becoming byzantine.
fn is_bft(network: NetworkId) -> bool { fn is_bft(network: NetworkId) -> bool {
@@ -725,7 +767,7 @@ mod pallet {
impl<T: Config> Pallet<T> { impl<T: Config> Pallet<T> {
/* /*
#[pallet::call_index(0)] #[pallet::call_index(0)]
#[pallet::weight(0)] // TODO #[pallet::weight((0, DispatchClass::Operational))] // TODO
pub fn set_keys( pub fn set_keys(
origin: OriginFor<T>, origin: OriginFor<T>,
network: ExternalNetworkId, network: ExternalNetworkId,
@@ -758,7 +800,7 @@ mod pallet {
} }
#[pallet::call_index(1)] #[pallet::call_index(1)]
#[pallet::weight(0)] // TODO #[pallet::weight((0, DispatchClass::Operational))] // TODO
pub fn report_slashes( pub fn report_slashes(
origin: OriginFor<T>, origin: OriginFor<T>,
network: ExternalNetworkId, network: ExternalNetworkId,
@@ -787,7 +829,7 @@ mod pallet {
*/ */
#[pallet::call_index(2)] #[pallet::call_index(2)]
#[pallet::weight(0)] // TODO #[pallet::weight((0, DispatchClass::Normal))] // TODO
pub fn set_embedded_elliptic_curve_keys( pub fn set_embedded_elliptic_curve_keys(
origin: OriginFor<T>, origin: OriginFor<T>,
keys: serai_primitives::crypto::SignedEmbeddedEllipticCurveKeys, keys: serai_primitives::crypto::SignedEmbeddedEllipticCurveKeys,
@@ -801,50 +843,42 @@ mod pallet {
} }
#[pallet::call_index(3)] #[pallet::call_index(3)]
#[pallet::weight(0)] // TODO #[pallet::weight((0, DispatchClass::Normal))] // TODO
pub fn allocate(origin: OriginFor<T>, network: NetworkId, amount: Amount) -> DispatchResult { pub fn allocate(origin: OriginFor<T>, network: NetworkId, amount: Amount) -> DispatchResult {
let validator = ensure_signed(origin)?; let validator = ensure_signed(origin)?;
Coins::<T>::transfer_fn(validator, Self::account(), Balance { coin: Coin::Serai, amount })?; Coins::<T, coins_pallet::CoinsInstance>::transfer_fn(validator, Self::account(), Balance { coin: Coin::Serai, amount })?;
Abstractions::<T>::increase_allocation(network, validator, amount, false) Abstractions::<T>::increase_allocation(network, validator, amount, false)
.map_err(Error::<T>::AllocationError)?; .map_err(Error::<T>::AllocationError)?;
Ok(()) Ok(())
} }
#[pallet::call_index(4)] #[pallet::call_index(4)]
#[pallet::weight(0)] // TODO #[pallet::weight((0, DispatchClass::Normal))] // TODO
pub fn deallocate(origin: OriginFor<T>, network: NetworkId, amount: Amount) -> DispatchResult { pub fn deallocate(origin: OriginFor<T>, network: NetworkId, amount: Amount) -> DispatchResult {
let account = ensure_signed(origin)?; let account = ensure_signed(origin)?;
let deallocation_timeline = Abstractions::<T>::decrease_allocation(network, account, amount) let deallocation_timeline = Abstractions::<T>::decrease_allocation(network, account, amount)
.map_err(Error::<T>::DeallocationError)?; .map_err(Error::<T>::DeallocationError)?;
if matches!(deallocation_timeline, DeallocationTimeline::Immediate) { if matches!(deallocation_timeline, DeallocationTimeline::Immediate) {
Coins::<T>::transfer_fn(Self::account(), account, Balance { coin: Coin::Serai, amount })?; Coins::<T, coins_pallet::CoinsInstance>::transfer_fn(Self::account(), account, Balance { coin: Coin::Serai, amount })?;
} }
Ok(()) Ok(())
} }
/*
#[pallet::call_index(5)] #[pallet::call_index(5)]
#[pallet::weight((0, DispatchClass::Operational))] // TODO #[pallet::weight((0, DispatchClass::Normal))] // TODO
pub fn claim_deallocation( pub fn claim_deallocation(
origin: OriginFor<T>, origin: OriginFor<T>,
network: NetworkId, network: NetworkId,
session: Session, session: Session,
) -> DispatchResult { ) -> DispatchResult {
let account = ensure_signed(origin)?; let account = ensure_signed(origin)?;
let Some(amount) = Self::take_deallocatable_amount(network, session, account) else { let amount = Abstractions::<T>::claim_delayed_deallocation(account, network, session)
Err(Error::<T>::NonExistentDeallocation)? .map_err(Error::<T>::DeallocationError)?;
}; Coins::<T, coins_pallet::CoinsInstance>::transfer_fn(Self::account(), account, Balance { coin: Coin::Serai, amount })?;
Coins::<T>::transfer_fn(
Self::account(),
account,
Balance { coin: Coin::Serai, amount },
)?;
Self::deposit_event(Event::DeallocationClaimed { validator: account, network, session });
Ok(()) Ok(())
} }
*/
} }
/* /*

View File

@@ -1,10 +1,10 @@
use alloc::vec::Vec;
use sp_core::{Encode, Decode, ConstU32, sr25519::Public, bounded::BoundedVec}; use sp_core::{Encode, Decode, ConstU32, sr25519::Public, bounded::BoundedVec};
use serai_primitives::{ use serai_primitives::{
constants::{MAX_KEY_SHARES_PER_SET, MAX_KEY_SHARES_PER_SET_U32},
network_id::NetworkId, network_id::NetworkId,
balance::Amount, balance::Amount,
validator_sets::{Session, ValidatorSet, amortize_excess_key_shares}, validator_sets::{KeyShares as KeySharesStruct, Session, ValidatorSet},
}; };
use frame_support::storage::{StorageValue, StorageMap, StorageDoubleMap, StoragePrefixedMap}; use frame_support::storage::{StorageValue, StorageMap, StorageDoubleMap, StoragePrefixedMap};
@@ -12,7 +12,8 @@ use frame_support::storage::{StorageValue, StorageMap, StorageDoubleMap, Storage
use crate::{embedded_elliptic_curve_keys::EmbeddedEllipticCurveKeys, allocations::Allocations}; use crate::{embedded_elliptic_curve_keys::EmbeddedEllipticCurveKeys, allocations::Allocations};
/// The list of genesis validators. /// The list of genesis validators.
pub(crate) type GenesisValidators = BoundedVec<Public, ConstU32<{ MAX_KEY_SHARES_PER_SET_U32 }>>; pub(crate) type GenesisValidators =
BoundedVec<Public, ConstU32<{ KeySharesStruct::MAX_PER_SET_U32 }>>;
/// The key for the SelectedValidators map. /// The key for the SelectedValidators map.
pub(crate) type SelectedValidatorsKey = (ValidatorSet, [u8; 16], Public); pub(crate) type SelectedValidatorsKey = (ValidatorSet, [u8; 16], Public);
@@ -38,14 +39,23 @@ pub(crate) trait SessionsStorage: EmbeddedEllipticCurveKeys + Allocations {
/// This is opaque and to be exclusively read/write by `Sessions`. /// This is opaque and to be exclusively read/write by `Sessions`.
type LatestDecidedSession: StorageMap<NetworkId, Session, Query = Option<Session>>; type LatestDecidedSession: StorageMap<NetworkId, Session, Query = Option<Session>>;
/// The amount of key shares a validator set has.
///
/// This is opaque and to be exclusively read/write by `Sessions`.
type KeyShares: StorageMap<ValidatorSet, KeySharesStruct, Query = Option<KeySharesStruct>>;
/// The selected validators for a set. /// The selected validators for a set.
/// ///
/// This MUST be instantiated with a map using `Identity` for its hasher. /// This MUST be instantiated with a map using `Identity` for its hasher.
/// ///
/// This is opaque and to be exclusively read/write by `Sessions`. /// This is opaque and to be exclusively read/write by `Sessions`.
// The value is how many key shares the validator has. // The value is how many key shares the validator has.
type SelectedValidators: StorageMap<SelectedValidatorsKey, u64, Query = Option<u64>> #[rustfmt::skip]
+ StoragePrefixedMap<u64>; type SelectedValidators: StorageMap<
SelectedValidatorsKey,
KeySharesStruct,
Query = Option<KeySharesStruct>
> + StoragePrefixedMap<KeySharesStruct>;
/// The total allocated stake for a network. /// The total allocated stake for a network.
/// ///
@@ -64,9 +74,9 @@ fn selected_validators_key(set: ValidatorSet, key: Public) -> SelectedValidators
(set, hash, key) (set, hash, key)
} }
fn selected_validators<Storage: StoragePrefixedMap<u64>>( fn selected_validators<Storage: StoragePrefixedMap<KeySharesStruct>>(
set: ValidatorSet, set: ValidatorSet,
) -> impl Iterator<Item = (Public, u64)> { ) -> impl Iterator<Item = (Public, KeySharesStruct)> {
let mut prefix = Storage::final_prefix().to_vec(); let mut prefix = Storage::final_prefix().to_vec();
prefix.extend(&set.encode()); prefix.extend(&set.encode());
frame_support::storage::PrefixIterator::<_, ()>::new( frame_support::storage::PrefixIterator::<_, ()>::new(
@@ -77,13 +87,13 @@ fn selected_validators<Storage: StoragePrefixedMap<u64>>(
// Recover the validator's key from the storage key // Recover the validator's key from the storage key
<[u8; 32]>::try_from(&key[(key.len() - 32) ..]).unwrap().into(), <[u8; 32]>::try_from(&key[(key.len() - 32) ..]).unwrap().into(),
// Decode the key shares from the value // Decode the key shares from the value
u64::decode(&mut key_shares).unwrap(), KeySharesStruct::decode(&mut key_shares).unwrap(),
)) ))
}, },
) )
} }
fn clear_selected_validators<Storage: StoragePrefixedMap<u64>>(set: ValidatorSet) { fn clear_selected_validators<Storage: StoragePrefixedMap<KeySharesStruct>>(set: ValidatorSet) {
let mut prefix = Storage::final_prefix().to_vec(); let mut prefix = Storage::final_prefix().to_vec();
prefix.extend(&set.encode()); prefix.extend(&set.encode());
assert!(matches!( assert!(matches!(
@@ -123,6 +133,10 @@ pub enum DeallocationError {
NotEnoughAllocated, NotEnoughAllocated,
/// The remaining allocation was non-zero and would be less than a key share. /// The remaining allocation was non-zero and would be less than a key share.
RemainingAllocationLessThanKeyShare, RemainingAllocationLessThanKeyShare,
/// The delay has yet to be satisfied.
DelayNotSatisfied,
/// No delayed deallocation was present.
NoDelayedDeallocation,
} }
pub(crate) trait Sessions { pub(crate) trait Sessions {
@@ -164,6 +178,43 @@ pub(crate) trait Sessions {
validator: Public, validator: Public,
amount: Amount, amount: Amount,
) -> Result<DeallocationTimeline, DeallocationError>; ) -> Result<DeallocationTimeline, DeallocationError>;
/// Claim a delayed allocation.
///
/// This does not perform any transfers of any coins/tokens. It solely performs the book-keeping
/// of it.
fn claim_delayed_deallocation(
validator: Public,
network: NetworkId,
session: Session,
) -> Result<Amount, DeallocationError>;
/// The currently active session for a network.
fn current_session(network: NetworkId) -> Option<Session>;
/// The latest decided session for a network.
fn latest_decided_session(network: NetworkId) -> Option<Session>;
/// The amount of key shares a validator has.
///
/// Returns `None` for historic sessions which we no longer have the data for.
fn key_shares(set: ValidatorSet) -> Option<KeySharesStruct>;
/// If a validator is present within the specified validator set.
///
/// This MAY return `false` for _any_ historic session, even if the validator _was_ present,
fn in_validator_set(set: ValidatorSet, validator: Public) -> bool;
/// The key shares possessed by a validator, within a validator set.
///
/// This MAY return `None` for _any_ historic session, even if the validator _was_ present,
fn key_shares_possessed_by_validator(
set: ValidatorSet,
validator: Public,
) -> Option<KeySharesStruct>;
/// The stake for the current validator set.
fn stake_for_current_validator_set(network: NetworkId) -> Option<Amount>;
} }
impl<Storage: SessionsStorage> Sessions for Storage { impl<Storage: SessionsStorage> Sessions for Storage {
@@ -188,45 +239,40 @@ impl<Storage: SessionsStorage> Sessions for Storage {
} }
} }
let mut selected_validators = Vec::with_capacity(usize::from(MAX_KEY_SHARES_PER_SET / 2)); let mut selected_validators = Vec::with_capacity(usize::from(KeySharesStruct::MAX_PER_SET / 2));
let mut total_key_shares = 0; let mut total_key_shares = 0;
if let Some(allocation_per_key_share) = Storage::AllocationPerKeyShare::get(network) { if let Some(allocation_per_key_share) = Storage::AllocationPerKeyShare::get(network) {
for (validator, amount) in Self::iter_allocations(network, allocation_per_key_share) { for (validator, amount) in Self::iter_allocations(network, allocation_per_key_share) {
// If this allocation is absurd, causing this to not fit within a u16, bound to the max let key_shares = KeySharesStruct::from_allocation(amount, allocation_per_key_share);
let key_shares = amount.0 / allocation_per_key_share.0;
selected_validators.push((validator, key_shares)); selected_validators.push((validator, key_shares));
// We're tracking key shares as a u64 yet the max allowed is a u16, so this won't overflow total_key_shares += key_shares.0;
total_key_shares += key_shares; if total_key_shares >= KeySharesStruct::MAX_PER_SET {
if total_key_shares >= u64::from(MAX_KEY_SHARES_PER_SET) {
break; break;
} }
} }
} }
// Perform amortization if we've exceeded the maximum amount of key shares // Perform amortization if we've exceeded the maximum amount of key shares
// This is guaranteed not to cause any validators have zero key shares as we'd only be over if {
// the last-added (worst) validator had multiple key shares, meaning everyone has more shares let new_len = KeySharesStruct::amortize_excess(selected_validators.as_mut_slice());
// than we'll amortize here selected_validators.truncate(new_len);
amortize_excess_key_shares(selected_validators.as_mut_slice()); }
if include_genesis_validators { if include_genesis_validators {
let mut genesis_validators = Storage::GenesisValidators::get() let mut genesis_validators = Storage::GenesisValidators::get()
.expect("genesis validators wasn't set") .expect("genesis validators wasn't set")
.into_iter() .into_iter()
.map(|validator| (validator, 1)) .map(|validator| (validator, KeySharesStruct::ONE))
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let genesis_validator_key_shares = u64::try_from(genesis_validators.len()).unwrap(); let genesis_validator_key_shares = u16::try_from(genesis_validators.len()).unwrap();
while (total_key_shares + genesis_validator_key_shares) > u64::from(MAX_KEY_SHARES_PER_SET) { total_key_shares += genesis_validator_key_shares;
while total_key_shares > KeySharesStruct::MAX_PER_SET {
let (_key, key_shares) = selected_validators.pop().unwrap(); let (_key, key_shares) = selected_validators.pop().unwrap();
total_key_shares -= key_shares; total_key_shares -= key_shares.0;
} }
selected_validators.append(&mut genesis_validators); selected_validators.append(&mut genesis_validators);
total_key_shares += genesis_validator_key_shares;
} }
// We kept this accurate but don't actually further read from it
let _ = total_key_shares;
let latest_decided_session = Storage::LatestDecidedSession::mutate(network, |session| { let latest_decided_session = Storage::LatestDecidedSession::mutate(network, |session| {
let next_session = session.map(|session| Session(session.0 + 1)).unwrap_or(Session(0)); let next_session = session.map(|session| Session(session.0 + 1)).unwrap_or(Session(0));
*session = Some(next_session); *session = Some(next_session);
@@ -234,6 +280,10 @@ impl<Storage: SessionsStorage> Sessions for Storage {
}); });
let latest_decided_set = ValidatorSet { network, session: latest_decided_session }; let latest_decided_set = ValidatorSet { network, session: latest_decided_session };
Storage::KeyShares::insert(
latest_decided_set,
KeySharesStruct::try_from(total_key_shares).expect("amortization failure"),
);
for (key, key_shares) in selected_validators { for (key, key_shares) in selected_validators {
Storage::SelectedValidators::insert( Storage::SelectedValidators::insert(
selected_validators_key(latest_decided_set, key), selected_validators_key(latest_decided_set, key),
@@ -271,10 +321,9 @@ impl<Storage: SessionsStorage> Sessions for Storage {
// Clean-up the historic set's storage, if one exists // Clean-up the historic set's storage, if one exists
if let Some(historic_session) = current.0.checked_sub(2).map(Session) { if let Some(historic_session) = current.0.checked_sub(2).map(Session) {
clear_selected_validators::<Storage::SelectedValidators>(ValidatorSet { let historic_set = ValidatorSet { network, session: historic_session };
network, Storage::KeyShares::remove(historic_set);
session: historic_session, clear_selected_validators::<Storage::SelectedValidators>(historic_set);
});
} }
} }
@@ -308,26 +357,28 @@ impl<Storage: SessionsStorage> Sessions for Storage {
{ {
// Check the validator set's current expected key shares // Check the validator set's current expected key shares
let expected_key_shares = Self::expected_key_shares(network, allocation_per_key_share); let expected_key_shares = Self::expected_key_shares(network, allocation_per_key_share);
// Check if the top validator in this set may be faulty under this f // Check if the top validator in this set may be faulty without causing a halt under this f
let top_validator_may_be_faulty = if let Some(top_validator) = let currently_tolerates_single_point_of_failure = if let Some(top_validator) =
Self::iter_allocations(network, allocation_per_key_share).next() Self::iter_allocations(network, allocation_per_key_share).next()
{ {
let (_key, amount) = top_validator; let (_key, amount) = top_validator;
let key_shares = amount.0 / allocation_per_key_share.0; let key_shares = KeySharesStruct::from_allocation(amount, allocation_per_key_share);
key_shares <= (expected_key_shares / 3) key_shares.0 <= (expected_key_shares.0 / 3)
} else { } else {
// If there are no validators, we claim the top validator may not be faulty so the
// following check doesn't run
false false
}; };
if top_validator_may_be_faulty { // If the set currently tolerates the fault of the top validator, don't let that change
let old_key_shares = old_allocation.0 / allocation_per_key_share.0; if currently_tolerates_single_point_of_failure {
let new_key_shares = new_allocation.0 / allocation_per_key_share.0; let old_key_shares =
KeySharesStruct::from_allocation(old_allocation, allocation_per_key_share);
let new_key_shares =
KeySharesStruct::from_allocation(new_allocation, allocation_per_key_share);
// Update the amount of expected key shares per the key shares added // Update the amount of expected key shares per the key shares added
let expected_key_shares = (expected_key_shares + (new_key_shares - old_key_shares)) let expected_key_shares = KeySharesStruct::saturating_from(
.min(u64::from(MAX_KEY_SHARES_PER_SET)); expected_key_shares.0 + (new_key_shares.0 - old_key_shares.0),
);
// If the new key shares exceeds the fault tolerance, don't allow the allocation // If the new key shares exceeds the fault tolerance, don't allow the allocation
if new_key_shares > (expected_key_shares / 3) { if new_key_shares.0 > (expected_key_shares.0 / 3) {
Err(AllocationError::IntroducesSinglePointOfFailure)? Err(AllocationError::IntroducesSinglePointOfFailure)?
} }
} }
@@ -432,4 +483,45 @@ impl<Storage: SessionsStorage> Sessions for Storage {
// immediately handle the deallocation // immediately handle the deallocation
Ok(DeallocationTimeline::Immediate) Ok(DeallocationTimeline::Immediate)
} }
fn claim_delayed_deallocation(
validator: Public,
network: NetworkId,
session: Session,
) -> Result<Amount, DeallocationError> {
if Storage::CurrentSession::get(network).map(|session| session.0) <
Some(session).map(|session| session.0)
{
Err(DeallocationError::DelayNotSatisfied)?;
}
Storage::DelayedDeallocations::take(validator, session)
.ok_or(DeallocationError::NoDelayedDeallocation)
}
fn current_session(network: NetworkId) -> Option<Session> {
Storage::CurrentSession::get(network)
}
fn latest_decided_session(network: NetworkId) -> Option<Session> {
Storage::LatestDecidedSession::get(network)
}
fn key_shares(set: ValidatorSet) -> Option<KeySharesStruct> {
Storage::KeyShares::get(set)
}
fn in_validator_set(set: ValidatorSet, validator: Public) -> bool {
Storage::SelectedValidators::contains_key(selected_validators_key(set, validator))
}
fn key_shares_possessed_by_validator(
set: ValidatorSet,
validator: Public,
) -> Option<KeySharesStruct> {
Storage::SelectedValidators::get(selected_validators_key(set, validator))
}
fn stake_for_current_validator_set(network: NetworkId) -> Option<Amount> {
Storage::TotalAllocatedStake::get(network)
}
} }

View File

@@ -21,7 +21,7 @@ std-shims = { path = "../../common/std-shims", default-features = false }
flexible-transcript = { path = "../../crypto/transcript", default-features = false, features = ["recommended", "merlin"] } flexible-transcript = { path = "../../crypto/transcript", default-features = false, features = ["recommended", "merlin"] }
multiexp = { path = "../../crypto/multiexp", default-features = false, features = ["batch"], optional = true } multiexp = { path = "../../crypto/multiexp", default-features = false }
dalek-ff-group = { path = "../../crypto/dalek-ff-group", default-features = false } dalek-ff-group = { path = "../../crypto/dalek-ff-group", default-features = false }
minimal-ed448 = { path = "../../crypto/ed448", default-features = false } minimal-ed448 = { path = "../../crypto/ed448", default-features = false }
@@ -36,9 +36,13 @@ secq256k1 = { path = "../../crypto/secq256k1", default-features = false }
embedwards25519 = { path = "../../crypto/embedwards25519", default-features = false } embedwards25519 = { path = "../../crypto/embedwards25519", default-features = false }
dkg = { path = "../../crypto/dkg", default-features = false, optional = true } dkg = { path = "../../crypto/dkg", default-features = false, optional = true }
dkg-dealer = { path = "../../crypto/dkg/dealer", default-features = false, optional = true }
dkg-recovery = { path = "../../crypto/dkg/recovery", default-features = false, optional = true }
dkg-musig = { path = "../../crypto/dkg/musig", default-features = false, optional = true }
dkg-evrf = { path = "../../crypto/dkg/evrf", default-features = false, features = ["secp256k1", "ed25519"], optional = true } dkg-evrf = { path = "../../crypto/dkg/evrf", default-features = false, features = ["secp256k1", "ed25519"], optional = true }
# modular-frost = { path = "../../crypto/frost", default-features = false }
# frost-schnorrkel = { path = "../../crypto/schnorrkel", default-features = false } modular-frost = { path = "../../crypto/frost", default-features = false, optional = true }
frost-schnorrkel = { path = "../../crypto/schnorrkel", default-features = false, optional = true }
bitcoin-serai = { path = "../../networks/bitcoin", default-features = false, features = ["hazmat"], optional = true } bitcoin-serai = { path = "../../networks/bitcoin", default-features = false, features = ["hazmat"], optional = true }
@@ -46,7 +50,8 @@ bitcoin-serai = { path = "../../networks/bitcoin", default-features = false, fea
alloc = [ alloc = [
"std-shims/alloc", "std-shims/alloc",
"multiexp", "multiexp/alloc",
"multiexp/batch",
"dalek-ff-group/alloc", "dalek-ff-group/alloc",
"minimal-ed448/alloc", "minimal-ed448/alloc",
@@ -61,7 +66,13 @@ alloc = [
"embedwards25519/alloc", "embedwards25519/alloc",
"dkg", "dkg",
"dkg-dealer",
"dkg-recovery",
"dkg-musig",
"dkg-evrf", "dkg-evrf",
"modular-frost",
"frost-schnorrkel",
"bitcoin-serai", "bitcoin-serai",
] ]

View File

@@ -21,12 +21,13 @@ pub mod alloc {
pub use multiexp; pub use multiexp;
pub use dkg; pub use dkg;
pub use dkg_dealer;
pub use dkg_recovery;
pub use dkg_musig;
pub use dkg_evrf; pub use dkg_evrf;
pub use bitcoin_serai;
/*
pub use modular_frost; pub use modular_frost;
pub use frost_schnorrkel; pub use frost_schnorrkel;
*/
pub use bitcoin_serai;
} }