mirror of
https://github.com/alexgo-io/stacks-puppet-node.git
synced 2026-05-15 06:55:41 +08:00
Merge branch 'develop' into feat/http-rpc-refactor
This commit is contained in:
266
Cargo.lock
generated
266
Cargo.lock
generated
@@ -391,17 +391,6 @@ version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||
|
||||
[[package]]
|
||||
name = "backoff"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b62ddb9cb1ec0a098ad4bbf9344d0713fa193ae1a80af55febcff2627b6a00c1"
|
||||
dependencies = [
|
||||
"getrandom 0.2.8",
|
||||
"instant",
|
||||
"rand 0.8.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "backtrace"
|
||||
version = "0.3.67"
|
||||
@@ -412,7 +401,7 @@ dependencies = [
|
||||
"cc",
|
||||
"cfg-if 1.0.0",
|
||||
"libc",
|
||||
"miniz_oxide 0.6.2",
|
||||
"miniz_oxide",
|
||||
"object",
|
||||
"rustc-demangle",
|
||||
]
|
||||
@@ -845,7 +834,7 @@ dependencies = [
|
||||
"clap 2.34.0",
|
||||
"criterion-plot",
|
||||
"csv",
|
||||
"itertools 0.10.5",
|
||||
"itertools",
|
||||
"lazy_static",
|
||||
"num-traits",
|
||||
"oorandom",
|
||||
@@ -867,7 +856,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2673cc8207403546f45f5fd319a974b1e6983ad1a3ee7e6041650013be041876"
|
||||
dependencies = [
|
||||
"cast",
|
||||
"itertools 0.10.5",
|
||||
"itertools",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1214,16 +1203,6 @@ dependencies = [
|
||||
"static_assertions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.0.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c6c98ee8095e9d1dcbf2fcc6d95acccb90d1c81db1e44725c6a984b1dbdfb010"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"miniz_oxide 0.7.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fnv"
|
||||
version = "1.0.7"
|
||||
@@ -1239,47 +1218,6 @@ dependencies = [
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "frost-coordinator"
|
||||
version = "0.0.1"
|
||||
source = "git+https://github.com/Trust-Machines/stacks-sbtc#dbd060b99f9cdaf2c06d26d60da1f12744f1d600"
|
||||
dependencies = [
|
||||
"backoff",
|
||||
"clap 4.4.1",
|
||||
"frost-signer",
|
||||
"hashbrown 0.14.0",
|
||||
"p256k1",
|
||||
"serde",
|
||||
"thiserror",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
"wsts",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "frost-signer"
|
||||
version = "0.0.1"
|
||||
source = "git+https://github.com/Trust-Machines/stacks-sbtc#dbd060b99f9cdaf2c06d26d60da1f12744f1d600"
|
||||
dependencies = [
|
||||
"aes-gcm 0.10.2",
|
||||
"backoff",
|
||||
"bincode",
|
||||
"clap 4.4.1",
|
||||
"hashbrown 0.14.0",
|
||||
"itertools 0.11.0",
|
||||
"p256k1",
|
||||
"rand 0.8.5",
|
||||
"rand_core 0.6.4",
|
||||
"serde",
|
||||
"sha2 0.10.6",
|
||||
"thiserror",
|
||||
"toml 0.7.6",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
"ureq",
|
||||
"wsts",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fuchsia-zircon"
|
||||
version = "0.3.3"
|
||||
@@ -1736,7 +1674,7 @@ dependencies = [
|
||||
"futures-util",
|
||||
"http",
|
||||
"hyper",
|
||||
"rustls 0.21.7",
|
||||
"rustls",
|
||||
"tokio",
|
||||
"tokio-rustls",
|
||||
]
|
||||
@@ -1883,15 +1821,6 @@ dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.6"
|
||||
@@ -2053,15 +1982,6 @@ dependencies = [
|
||||
"value-bag",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "matchers"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558"
|
||||
dependencies = [
|
||||
"regex-automata",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.5.0"
|
||||
@@ -2117,15 +2037,6 @@ dependencies = [
|
||||
"adler",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
|
||||
dependencies = [
|
||||
"adler",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "0.6.23"
|
||||
@@ -2221,16 +2132,6 @@ dependencies = [
|
||||
"minimal-lexical",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nu-ansi-term"
|
||||
version = "0.46.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
|
||||
dependencies = [
|
||||
"overload",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-integer"
|
||||
version = "0.1.45"
|
||||
@@ -2302,12 +2203,6 @@ version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
|
||||
|
||||
[[package]]
|
||||
name = "overload"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
|
||||
|
||||
[[package]]
|
||||
name = "p256k1"
|
||||
version = "5.4.1"
|
||||
@@ -2319,7 +2214,7 @@ dependencies = [
|
||||
"bs58 0.4.0",
|
||||
"cc",
|
||||
"hex",
|
||||
"itertools 0.10.5",
|
||||
"itertools",
|
||||
"num-traits",
|
||||
"primitive-types",
|
||||
"proc-macro2",
|
||||
@@ -2758,15 +2653,6 @@ dependencies = [
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
|
||||
dependencies = [
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.6.28"
|
||||
@@ -2796,7 +2682,7 @@ dependencies = [
|
||||
"once_cell",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"rustls 0.21.7",
|
||||
"rustls",
|
||||
"rustls-pemfile",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -2808,7 +2694,7 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"web-sys",
|
||||
"webpki-roots 0.25.2",
|
||||
"webpki-roots",
|
||||
"winreg",
|
||||
]
|
||||
|
||||
@@ -2975,7 +2861,7 @@ dependencies = [
|
||||
"serde",
|
||||
"tempfile",
|
||||
"thiserror",
|
||||
"toml 0.5.11",
|
||||
"toml",
|
||||
"toolchain_find",
|
||||
]
|
||||
|
||||
@@ -2993,18 +2879,6 @@ dependencies = [
|
||||
"windows-sys 0.45.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls"
|
||||
version = "0.20.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fff78fc74d175294f4e83b28343315ffcfb114b156f0185e9741cb5570f50e2f"
|
||||
dependencies = [
|
||||
"log",
|
||||
"ring",
|
||||
"sct",
|
||||
"webpki",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls"
|
||||
version = "0.21.7"
|
||||
@@ -3195,15 +3069,6 @@ dependencies = [
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_stacker"
|
||||
version = "0.1.8"
|
||||
@@ -3317,15 +3182,6 @@ dependencies = [
|
||||
"keccak",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sharded-slab"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.1.0"
|
||||
@@ -3489,8 +3345,9 @@ dependencies = [
|
||||
"stackslib",
|
||||
"stx-genesis",
|
||||
"tokio",
|
||||
"toml 0.5.11",
|
||||
"toml",
|
||||
"warp",
|
||||
"wsts",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3500,8 +3357,6 @@ dependencies = [
|
||||
"bincode",
|
||||
"clap 4.4.1",
|
||||
"clarity",
|
||||
"frost-coordinator",
|
||||
"frost-signer",
|
||||
"hashbrown 0.14.0",
|
||||
"libsigner",
|
||||
"libstackerdb",
|
||||
@@ -3517,7 +3372,7 @@ dependencies = [
|
||||
"slog-term",
|
||||
"stacks-common",
|
||||
"thiserror",
|
||||
"toml 0.5.11",
|
||||
"toml",
|
||||
"wsts",
|
||||
]
|
||||
|
||||
@@ -3894,7 +3749,7 @@ version = "0.24.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081"
|
||||
dependencies = [
|
||||
"rustls 0.21.7",
|
||||
"rustls",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
@@ -3944,26 +3799,11 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c17e963a819c331dcacd7ab957d80bc2b9a9c1e71c804826d2f283dd65306542"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"toml_edit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
@@ -3972,8 +3812,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a"
|
||||
dependencies = [
|
||||
"indexmap 2.0.0",
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"winnow",
|
||||
]
|
||||
@@ -4028,36 +3866,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"valuable",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-log"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"log",
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-subscriber"
|
||||
version = "0.3.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77"
|
||||
dependencies = [
|
||||
"matchers",
|
||||
"nu-ansi-term",
|
||||
"once_cell",
|
||||
"regex",
|
||||
"sharded-slab",
|
||||
"smallvec",
|
||||
"thread_local",
|
||||
"tracing",
|
||||
"tracing-core",
|
||||
"tracing-log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4171,24 +3979,6 @@ version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
|
||||
|
||||
[[package]]
|
||||
name = "ureq"
|
||||
version = "2.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "338b31dd1314f68f3aabf3ed57ab922df95ffcd902476ca7ba3c4ce7b908c46d"
|
||||
dependencies = [
|
||||
"base64 0.13.1",
|
||||
"flate2",
|
||||
"log",
|
||||
"once_cell",
|
||||
"rustls 0.20.8",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"url",
|
||||
"webpki",
|
||||
"webpki-roots 0.22.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.3.1"
|
||||
@@ -4213,12 +4003,6 @@ version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
|
||||
|
||||
[[package]]
|
||||
name = "valuable"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
|
||||
|
||||
[[package]]
|
||||
name = "value-bag"
|
||||
version = "1.0.0-alpha.9"
|
||||
@@ -4393,25 +4177,6 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webpki"
|
||||
version = "0.22.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd"
|
||||
dependencies = [
|
||||
"ring",
|
||||
"untrusted",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webpki-roots"
|
||||
version = "0.22.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87"
|
||||
dependencies = [
|
||||
"webpki",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webpki-roots"
|
||||
version = "0.25.2"
|
||||
@@ -4650,10 +4415,10 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wsts"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9822f9052b53d23fb9535781ee904e444cb5292788ae2f08e08de27eb765b39c"
|
||||
version = "4.0.0"
|
||||
source = "git+https://github.com/Trust-Machines/wsts?tag=4.0.0rc1#8167a32c123a4769ee2704fd64f7b0d753ca0f56"
|
||||
dependencies = [
|
||||
"aes-gcm 0.10.2",
|
||||
"bs58 0.5.0",
|
||||
"hashbrown 0.14.0",
|
||||
"hex",
|
||||
@@ -4665,6 +4430,7 @@ dependencies = [
|
||||
"serde",
|
||||
"sha2 0.10.6",
|
||||
"thiserror",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -23,8 +23,6 @@ path = "src/main.rs"
|
||||
bincode = "1.3.3"
|
||||
clarity = { path = "../clarity" }
|
||||
clap = { version = "4.1.1", features = ["derive", "env"] }
|
||||
frost-coordinator = { git = "https://github.com/Trust-Machines/stacks-sbtc" }
|
||||
frost-signer = { git = "https://github.com/Trust-Machines/stacks-sbtc" }
|
||||
hashbrown = "0.14"
|
||||
libsigner = { path = "../libsigner" }
|
||||
libstackerdb = { path = "../libstackerdb" }
|
||||
@@ -39,7 +37,7 @@ slog-term = "2.6.0"
|
||||
stacks-common = { path = "../stacks-common" }
|
||||
thiserror = "1.0"
|
||||
toml = "0.5.6"
|
||||
wsts = "2.0"
|
||||
wsts = { git = "https://github.com/Trust-Machines/wsts", tag = "4.0.0rc1" }
|
||||
|
||||
[dependencies.serde_json]
|
||||
version = "1.0"
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use clarity::vm::types::QualifiedContractIdentifier;
|
||||
use frost_signer::config::{PublicKeys, SignerKeyIds};
|
||||
use hashbrown::HashMap;
|
||||
use p256k1::{ecdsa, scalar::Scalar};
|
||||
use serde::Deserialize;
|
||||
use stacks_common::types::chainstate::StacksPrivateKey;
|
||||
@@ -26,6 +26,10 @@ use std::{
|
||||
path::PathBuf,
|
||||
time::Duration,
|
||||
};
|
||||
use wsts::state_machine::PublicKeys;
|
||||
|
||||
/// List of key_ids for each signer_id
|
||||
pub type SignerKeyIds = HashMap<u32, Vec<u32>>;
|
||||
|
||||
const EVENT_TIMEOUT_MS: u64 = 5000;
|
||||
|
||||
|
||||
@@ -1,887 +0,0 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use crate::crypto::{Coordinatable, Error as CryptoError, OperationResult};
|
||||
use frost_signer::{
|
||||
net::Message,
|
||||
signing_round::{
|
||||
DkgBegin, DkgPublicShare, MessageTypes, NonceRequest, NonceResponse, Signable,
|
||||
SignatureShareRequest,
|
||||
},
|
||||
};
|
||||
use hashbrown::HashSet;
|
||||
use slog::{slog_info, slog_warn};
|
||||
use stacks_common::{error, info, warn};
|
||||
use wsts::{
|
||||
common::{PolyCommitment, PublicNonce, Signature, SignatureShare},
|
||||
compute,
|
||||
errors::AggregatorError,
|
||||
taproot::{Error as TaprootError, SchnorrProof},
|
||||
v1, Point, Scalar,
|
||||
};
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
/// The error type for the coordinator
|
||||
pub enum Error {
|
||||
/// A bad state change was made
|
||||
#[error("Bad State Change: {0}")]
|
||||
BadStateChange(String),
|
||||
/// A bad dkg_id in received message
|
||||
#[error("Bad dkg_id: got {0} expected {1}")]
|
||||
BadDkgId(u64, u64),
|
||||
/// A bad dkg_public_id in received message
|
||||
#[error("Bad dkg_public_id: got {0} expected {1}")]
|
||||
BadDkgPublicId(u64, u64),
|
||||
/// A bad sign_id in received message
|
||||
#[error("Bad sign_id: got {0} expected {1}")]
|
||||
BadSignId(u64, u64),
|
||||
/// A bad sign_nonce_id in received message
|
||||
#[error("Bad sign_nonce_id: got {0} expected {1}")]
|
||||
BadSignNonceId(u64, u64),
|
||||
/// SignatureAggregator error
|
||||
#[error("Aggregator: {0}")]
|
||||
Aggregator(AggregatorError),
|
||||
/// Taproot error
|
||||
#[error("Taproot")]
|
||||
Taproot(TaprootError),
|
||||
/// Schnorr proof failed to verify
|
||||
#[error("Schnorr Proof failed to verify")]
|
||||
SchnorrProofFailed,
|
||||
}
|
||||
|
||||
impl From<AggregatorError> for Error {
|
||||
fn from(err: AggregatorError) -> Self {
|
||||
Error::Aggregator(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TaprootError> for Error {
|
||||
fn from(err: TaprootError) -> Self {
|
||||
Error::Taproot(err)
|
||||
}
|
||||
}
|
||||
|
||||
/// The coordinator for the FROST algorithm
|
||||
pub struct Coordinator {
|
||||
current_dkg_id: u64,
|
||||
current_dkg_public_id: u64,
|
||||
current_sign_id: u64,
|
||||
current_sign_nonce_id: u64,
|
||||
total_signers: u32, // Assuming the signers cover all id:s in {1, 2, ..., total_signers}
|
||||
total_keys: u32,
|
||||
threshold: u32,
|
||||
dkg_public_shares: BTreeMap<u32, DkgPublicShare>,
|
||||
public_nonces: BTreeMap<u32, NonceResponse>,
|
||||
signature_shares: BTreeMap<u32, Vec<SignatureShare>>,
|
||||
aggregate_public_key: Point,
|
||||
signature: Signature,
|
||||
schnorr_proof: SchnorrProof,
|
||||
message_private_key: Scalar,
|
||||
ids_to_await: HashSet<u32>,
|
||||
message: Vec<u8>,
|
||||
state: State,
|
||||
}
|
||||
|
||||
impl Coordinator {
|
||||
/// Create a new coordinator
|
||||
pub fn new(
|
||||
total_signers: u32,
|
||||
total_keys: u32,
|
||||
threshold: u32,
|
||||
message_private_key: Scalar,
|
||||
) -> Self {
|
||||
Self {
|
||||
current_dkg_id: 0,
|
||||
current_dkg_public_id: 0,
|
||||
current_sign_id: 0,
|
||||
current_sign_nonce_id: 0,
|
||||
total_signers,
|
||||
total_keys,
|
||||
threshold,
|
||||
dkg_public_shares: Default::default(),
|
||||
public_nonces: Default::default(),
|
||||
signature_shares: Default::default(),
|
||||
aggregate_public_key: Point::default(),
|
||||
signature: Signature {
|
||||
R: Default::default(),
|
||||
z: Default::default(),
|
||||
},
|
||||
schnorr_proof: SchnorrProof {
|
||||
r: Default::default(),
|
||||
s: Default::default(),
|
||||
},
|
||||
message: Default::default(),
|
||||
message_private_key,
|
||||
ids_to_await: (0..total_signers).collect(),
|
||||
state: State::Idle,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Coordinator {
|
||||
/// Handle a message from the stacker-db instance
|
||||
pub fn process_message(
|
||||
&mut self,
|
||||
message: &Message,
|
||||
) -> Result<(Option<Message>, Option<OperationResult>), Error> {
|
||||
loop {
|
||||
match self.state {
|
||||
State::Idle => {
|
||||
// do nothing
|
||||
// We are the coordinator and should be the only thing triggering messages right now
|
||||
return Ok((None, None));
|
||||
}
|
||||
State::DkgPublicDistribute => {
|
||||
let message = self.start_public_shares()?;
|
||||
return Ok((Some(message), None));
|
||||
}
|
||||
State::DkgPublicGather => {
|
||||
self.gather_public_shares(message)?;
|
||||
if self.state == State::DkgPublicGather {
|
||||
// We need more data
|
||||
return Ok((None, None));
|
||||
}
|
||||
}
|
||||
State::DkgPrivateDistribute => {
|
||||
let message = self.start_private_shares()?;
|
||||
return Ok((Some(message), None));
|
||||
}
|
||||
State::DkgEndGather => {
|
||||
self.gather_dkg_end(message)?;
|
||||
if self.state == State::DkgEndGather {
|
||||
// We need more data
|
||||
return Ok((None, None));
|
||||
} else if self.state == State::Idle {
|
||||
// We are done with the DKG round! Return the operation result
|
||||
return Ok((None, Some(OperationResult::Dkg(self.aggregate_public_key))));
|
||||
}
|
||||
}
|
||||
State::NonceRequest => {
|
||||
let message = self.request_nonces()?;
|
||||
return Ok((Some(message), None));
|
||||
}
|
||||
State::NonceGather => {
|
||||
self.gather_nonces(message)?;
|
||||
if self.state == State::NonceGather {
|
||||
// We need more data
|
||||
return Ok((None, None));
|
||||
}
|
||||
}
|
||||
State::SigShareRequest => {
|
||||
let message = self.request_sig_shares()?;
|
||||
return Ok((Some(message), None));
|
||||
}
|
||||
State::SigShareGather => {
|
||||
self.gather_sig_shares(message)?;
|
||||
if self.state == State::SigShareGather {
|
||||
// We need more data
|
||||
return Ok((None, None));
|
||||
} else if self.state == State::Idle {
|
||||
// We are done with the DKG round! Return the operation result
|
||||
return Ok((
|
||||
None,
|
||||
Some(OperationResult::Sign(
|
||||
Signature {
|
||||
R: self.signature.R,
|
||||
z: self.signature.z,
|
||||
},
|
||||
SchnorrProof {
|
||||
r: self.schnorr_proof.r,
|
||||
s: self.schnorr_proof.s,
|
||||
},
|
||||
)),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Start a DKG round
|
||||
pub fn start_dkg_round(&mut self) -> Result<Message, Error> {
|
||||
self.current_dkg_id = self.current_dkg_id.wrapping_add(1);
|
||||
info!("Starting DKG round #{}", self.current_dkg_id);
|
||||
self.move_to(State::DkgPublicDistribute)?;
|
||||
self.start_public_shares()
|
||||
}
|
||||
|
||||
/// Start a signing round
|
||||
pub fn start_signing_round(&mut self) -> Result<Message, Error> {
|
||||
self.current_sign_id = self.current_sign_id.wrapping_add(1);
|
||||
info!("Starting signing round #{}", self.current_sign_id);
|
||||
self.move_to(State::NonceRequest)?;
|
||||
self.request_nonces()
|
||||
}
|
||||
|
||||
fn start_public_shares(&mut self) -> Result<Message, Error> {
|
||||
self.dkg_public_shares.clear();
|
||||
info!(
|
||||
"DKG Round #{}: Starting Public Share Distribution Round #{}",
|
||||
self.current_dkg_id, self.current_dkg_public_id
|
||||
);
|
||||
let dkg_begin = DkgBegin {
|
||||
dkg_id: self.current_dkg_id,
|
||||
};
|
||||
|
||||
let dkg_begin_message = Message {
|
||||
sig: dkg_begin.sign(&self.message_private_key).expect(""),
|
||||
msg: MessageTypes::DkgBegin(dkg_begin),
|
||||
};
|
||||
self.move_to(State::DkgPublicGather)?;
|
||||
Ok(dkg_begin_message)
|
||||
}
|
||||
|
||||
fn start_private_shares(&mut self) -> Result<Message, Error> {
|
||||
info!(
|
||||
"DKG Round #{}: Starting Private Share Distribution",
|
||||
self.current_dkg_id
|
||||
);
|
||||
let dkg_begin = DkgBegin {
|
||||
dkg_id: self.current_dkg_id,
|
||||
};
|
||||
let dkg_private_begin_msg = Message {
|
||||
sig: dkg_begin.sign(&self.message_private_key).expect(""),
|
||||
msg: MessageTypes::DkgPrivateBegin(dkg_begin),
|
||||
};
|
||||
self.move_to(State::DkgEndGather)?;
|
||||
Ok(dkg_private_begin_msg)
|
||||
}
|
||||
|
||||
fn gather_public_shares(&mut self, message: &Message) -> Result<(), Error> {
|
||||
match &message.msg {
|
||||
MessageTypes::DkgPublicEnd(dkg_end) => {
|
||||
if dkg_end.dkg_id != self.current_dkg_id {
|
||||
return Err(Error::BadDkgId(dkg_end.dkg_id, self.current_dkg_id));
|
||||
}
|
||||
self.ids_to_await.remove(&dkg_end.signer_id);
|
||||
info!(
|
||||
"DKG_Public_End round #{} from signer #{}. Waiting on {:?}",
|
||||
dkg_end.dkg_id, dkg_end.signer_id, self.ids_to_await
|
||||
);
|
||||
}
|
||||
MessageTypes::DkgPublicShare(dkg_public_share) => {
|
||||
if dkg_public_share.dkg_id != self.current_dkg_id {
|
||||
return Err(Error::BadDkgId(
|
||||
dkg_public_share.dkg_id,
|
||||
self.current_dkg_id,
|
||||
));
|
||||
}
|
||||
if dkg_public_share.dkg_public_id != self.current_dkg_public_id {
|
||||
return Err(Error::BadDkgPublicId(
|
||||
dkg_public_share.dkg_public_id,
|
||||
self.current_dkg_public_id,
|
||||
));
|
||||
}
|
||||
self.dkg_public_shares
|
||||
.insert(dkg_public_share.party_id, dkg_public_share.clone());
|
||||
|
||||
info!(
|
||||
"DKG round #{} DKG public round #{} DkgPublicShare from party #{}",
|
||||
dkg_public_share.dkg_id,
|
||||
dkg_public_share.dkg_public_id,
|
||||
dkg_public_share.party_id
|
||||
);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
if self.ids_to_await.is_empty() {
|
||||
// Calculate the aggregate public key
|
||||
let key = self
|
||||
.dkg_public_shares
|
||||
.iter()
|
||||
.fold(Point::default(), |s, (_, dps)| s + dps.public_share.A[0]);
|
||||
// check to see if aggregate public key has even y
|
||||
if key.has_even_y() {
|
||||
info!("Aggregate public key has even y coord!");
|
||||
info!("Aggregate public key: {}", key);
|
||||
self.aggregate_public_key = key;
|
||||
self.move_to(State::DkgPrivateDistribute)?;
|
||||
} else {
|
||||
warn!("DKG Round #{} Failed: Aggregate public key does not have even y coord, re-running dkg.", self.current_dkg_id);
|
||||
// TODO: SigningRound seems to break if we inc dkg_public_id
|
||||
// self.current_dkg_public_id = self.current_dkg_public_id.wrapping_add(1);
|
||||
self.move_to(State::DkgPublicDistribute)?;
|
||||
}
|
||||
self.ids_to_await = (0..self.total_signers).collect();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn gather_dkg_end(&mut self, message: &Message) -> Result<(), Error> {
|
||||
info!(
|
||||
"DKG Round #{}: waiting for Dkg End from signers {:?}",
|
||||
self.current_dkg_id, self.ids_to_await
|
||||
);
|
||||
if let MessageTypes::DkgEnd(dkg_end) = &message.msg {
|
||||
if dkg_end.dkg_id != self.current_dkg_id {
|
||||
return Err(Error::BadDkgId(dkg_end.dkg_id, self.current_dkg_id));
|
||||
}
|
||||
self.ids_to_await.remove(&dkg_end.signer_id);
|
||||
info!(
|
||||
"DKG_End round #{} from signer #{}. Waiting on {:?}",
|
||||
dkg_end.dkg_id, dkg_end.signer_id, self.ids_to_await
|
||||
);
|
||||
}
|
||||
|
||||
if self.ids_to_await.is_empty() {
|
||||
self.ids_to_await = (0..self.total_signers).collect();
|
||||
self.move_to(State::Idle)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn request_nonces(&mut self) -> Result<Message, Error> {
|
||||
info!(
|
||||
"Sign Round #{} Nonce round #{} Requesting Nonces",
|
||||
self.current_sign_id, self.current_sign_nonce_id,
|
||||
);
|
||||
let nonce_request = NonceRequest {
|
||||
dkg_id: self.current_dkg_id,
|
||||
sign_id: self.current_sign_id,
|
||||
sign_nonce_id: self.current_sign_nonce_id,
|
||||
};
|
||||
let nonce_request_msg = Message {
|
||||
sig: nonce_request.sign(&self.message_private_key).expect(""),
|
||||
msg: MessageTypes::NonceRequest(nonce_request),
|
||||
};
|
||||
self.ids_to_await = (0..self.total_signers).collect();
|
||||
self.move_to(State::NonceGather)?;
|
||||
Ok(nonce_request_msg)
|
||||
}
|
||||
|
||||
fn gather_nonces(&mut self, message: &Message) -> Result<(), Error> {
|
||||
if let MessageTypes::NonceResponse(nonce_response) = &message.msg {
|
||||
if nonce_response.dkg_id != self.current_dkg_id {
|
||||
return Err(Error::BadDkgId(nonce_response.dkg_id, self.current_dkg_id));
|
||||
}
|
||||
if nonce_response.sign_id != self.current_sign_id {
|
||||
return Err(Error::BadSignId(
|
||||
nonce_response.sign_id,
|
||||
self.current_sign_id,
|
||||
));
|
||||
}
|
||||
if nonce_response.sign_nonce_id != self.current_sign_nonce_id {
|
||||
return Err(Error::BadSignNonceId(
|
||||
nonce_response.sign_nonce_id,
|
||||
self.current_sign_nonce_id,
|
||||
));
|
||||
}
|
||||
|
||||
self.public_nonces
|
||||
.insert(nonce_response.signer_id, nonce_response.clone());
|
||||
self.ids_to_await.remove(&nonce_response.signer_id);
|
||||
info!(
|
||||
"Sign round #{} nonce round #{} NonceResponse from signer #{}. Waiting on {:?}",
|
||||
nonce_response.sign_id,
|
||||
nonce_response.sign_nonce_id,
|
||||
nonce_response.signer_id,
|
||||
self.ids_to_await
|
||||
);
|
||||
}
|
||||
if self.ids_to_await.is_empty() {
|
||||
// Calculate the aggregate nonce
|
||||
let aggregate_nonce = self.compute_aggregate_nonce();
|
||||
|
||||
// check to see if aggregate public key has even y
|
||||
if aggregate_nonce.has_even_y() {
|
||||
info!("Aggregate nonce has even y coord!");
|
||||
info!("Aggregate nonce: {}", aggregate_nonce);
|
||||
self.move_to(State::SigShareRequest)?;
|
||||
} else {
|
||||
warn!("Sign Round #{} Nonce Round #{} Failed: Aggregate nonce does not have even y coord, requesting new nonces.", self.current_sign_id, self.current_sign_nonce_id);
|
||||
self.current_sign_nonce_id += 1;
|
||||
self.move_to(State::NonceRequest)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn request_sig_shares(&mut self) -> Result<Message, Error> {
|
||||
info!(
|
||||
"Sign Round #{} Requesting Signature Shares",
|
||||
self.current_sign_id,
|
||||
);
|
||||
let nonce_responses = (0..self.total_signers)
|
||||
.map(|i| self.public_nonces[&i].clone())
|
||||
.collect::<Vec<NonceResponse>>();
|
||||
let sig_share_request = SignatureShareRequest {
|
||||
dkg_id: self.current_dkg_id,
|
||||
sign_id: self.current_sign_id,
|
||||
correlation_id: 0, // TODO: what is this?
|
||||
nonce_responses,
|
||||
message: self.message.clone(),
|
||||
};
|
||||
let sig_share_request_msg = Message {
|
||||
sig: sig_share_request.sign(&self.message_private_key).expect(""),
|
||||
msg: MessageTypes::SignShareRequest(sig_share_request), // TODO: either SigShare or SignatureShare, SignShare is right out
|
||||
};
|
||||
self.ids_to_await = (0..self.total_signers).collect();
|
||||
self.move_to(State::SigShareGather)?;
|
||||
Ok(sig_share_request_msg)
|
||||
}
|
||||
|
||||
fn gather_sig_shares(&mut self, message: &Message) -> Result<(), Error> {
|
||||
if let MessageTypes::SignShareResponse(sig_share_response) = &message.msg {
|
||||
if sig_share_response.dkg_id != self.current_dkg_id {
|
||||
return Err(Error::BadDkgId(
|
||||
sig_share_response.dkg_id,
|
||||
self.current_dkg_id,
|
||||
));
|
||||
}
|
||||
if sig_share_response.sign_id != self.current_sign_id {
|
||||
return Err(Error::BadSignId(
|
||||
sig_share_response.sign_id,
|
||||
self.current_sign_id,
|
||||
));
|
||||
}
|
||||
self.signature_shares.insert(
|
||||
sig_share_response.signer_id,
|
||||
sig_share_response.signature_shares.clone(),
|
||||
);
|
||||
self.ids_to_await.remove(&sig_share_response.signer_id);
|
||||
info!(
|
||||
"Sign round #{} SignShareResponse from signer #{}. Waiting on {:?}",
|
||||
sig_share_response.sign_id, sig_share_response.signer_id, self.ids_to_await
|
||||
);
|
||||
}
|
||||
if self.ids_to_await.is_empty() {
|
||||
// Calculate the aggregate signature
|
||||
let polys: Vec<PolyCommitment> = self
|
||||
.dkg_public_shares
|
||||
.values()
|
||||
.map(|ps| ps.public_share.clone())
|
||||
.collect();
|
||||
|
||||
let nonce_responses = (0..self.total_signers)
|
||||
.map(|i| self.public_nonces[&i].clone())
|
||||
.collect::<Vec<NonceResponse>>();
|
||||
|
||||
let nonces = nonce_responses
|
||||
.iter()
|
||||
.flat_map(|nr| nr.nonces.clone())
|
||||
.collect::<Vec<PublicNonce>>();
|
||||
|
||||
let shares = &self
|
||||
.public_nonces
|
||||
.iter()
|
||||
.flat_map(|(i, _)| self.signature_shares[i].clone())
|
||||
.collect::<Vec<SignatureShare>>();
|
||||
|
||||
info!(
|
||||
"aggregator.sign({:?}, {:?}, {:?})",
|
||||
self.message,
|
||||
nonces.len(),
|
||||
shares.len()
|
||||
);
|
||||
|
||||
let mut aggregator =
|
||||
v1::SignatureAggregator::new(self.total_keys, self.threshold, polys)?;
|
||||
|
||||
let sig = aggregator.sign(&self.message, &nonces, shares)?;
|
||||
|
||||
info!("Signature ({}, {})", sig.R, sig.z);
|
||||
|
||||
let proof = SchnorrProof::new(&sig)?;
|
||||
|
||||
info!("SchnorrProof ({}, {})", proof.r, proof.s);
|
||||
|
||||
if !proof.verify(&self.aggregate_public_key.x(), &self.message) {
|
||||
warn!("SchnorrProof failed to verify!");
|
||||
return Err(Error::SchnorrProofFailed);
|
||||
}
|
||||
|
||||
self.move_to(State::Idle)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
fn compute_aggregate_nonce(&self) -> Point {
|
||||
// XXX this needs to be key_ids for v1 and signer_ids for v2
|
||||
let party_ids = self
|
||||
.public_nonces
|
||||
.values()
|
||||
.flat_map(|pn| pn.key_ids.clone())
|
||||
.collect::<Vec<u32>>();
|
||||
let nonces = self
|
||||
.public_nonces
|
||||
.values()
|
||||
.flat_map(|pn| pn.nonces.clone())
|
||||
.collect::<Vec<PublicNonce>>();
|
||||
let (_, R) = compute::intermediate(&self.message, &party_ids, &nonces);
|
||||
|
||||
R
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
/// Coordinator states
|
||||
pub enum State {
|
||||
/// The coordinator is idle
|
||||
Idle,
|
||||
/// The coordinator is distributing public shares
|
||||
DkgPublicDistribute,
|
||||
/// The coordinator is gathering public shares
|
||||
DkgPublicGather,
|
||||
/// The coordinator is distributing private shares
|
||||
DkgPrivateDistribute,
|
||||
/// The coordinator is gathering DKG End messages
|
||||
DkgEndGather,
|
||||
/// The coordinator is requesting nonces
|
||||
NonceRequest,
|
||||
/// The coordinator is gathering nonces
|
||||
NonceGather,
|
||||
/// The coordinator is requesting signature shares
|
||||
SigShareRequest,
|
||||
/// The coordinator is gathering signature shares
|
||||
SigShareGather,
|
||||
}
|
||||
|
||||
/// The state machine trait for the frost coordinator
|
||||
pub trait StateMachine {
|
||||
/// Attempt to move the state machine to a new state
|
||||
fn move_to(&mut self, state: State) -> Result<(), Error>;
|
||||
/// Check if the state machine can move to a new state
|
||||
fn can_move_to(&self, state: &State) -> Result<(), Error>;
|
||||
}
|
||||
|
||||
impl StateMachine for Coordinator {
|
||||
fn move_to(&mut self, state: State) -> Result<(), Error> {
|
||||
self.can_move_to(&state)?;
|
||||
self.state = state;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn can_move_to(&self, state: &State) -> Result<(), Error> {
|
||||
let prev_state = &self.state;
|
||||
let accepted = match state {
|
||||
State::Idle => true,
|
||||
State::DkgPublicDistribute => {
|
||||
prev_state == &State::Idle
|
||||
|| prev_state == &State::DkgPublicGather
|
||||
|| prev_state == &State::DkgEndGather
|
||||
}
|
||||
State::DkgPublicGather => {
|
||||
prev_state == &State::DkgPublicDistribute || prev_state == &State::DkgPublicGather
|
||||
}
|
||||
State::DkgPrivateDistribute => prev_state == &State::DkgPublicGather,
|
||||
State::DkgEndGather => prev_state == &State::DkgPrivateDistribute,
|
||||
State::NonceRequest => {
|
||||
prev_state == &State::Idle
|
||||
|| prev_state == &State::DkgEndGather
|
||||
|| prev_state == &State::NonceGather
|
||||
}
|
||||
State::NonceGather => {
|
||||
prev_state == &State::NonceRequest || prev_state == &State::NonceGather
|
||||
}
|
||||
State::SigShareRequest => prev_state == &State::NonceGather,
|
||||
State::SigShareGather => {
|
||||
prev_state == &State::SigShareRequest || prev_state == &State::SigShareGather
|
||||
}
|
||||
};
|
||||
if accepted {
|
||||
info!("state change from {:?} to {:?}", prev_state, state);
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::BadStateChange(format!(
|
||||
"{:?} to {:?}",
|
||||
prev_state, state
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Coordinatable for Coordinator {
|
||||
/// Process inbound messages
|
||||
fn process_inbound_messages(
|
||||
&mut self,
|
||||
messages: Vec<Message>,
|
||||
) -> Result<(Vec<Message>, Vec<OperationResult>), crate::crypto::Error> {
|
||||
let mut outbound_messages = vec![];
|
||||
let mut operation_results = vec![];
|
||||
for message in &messages {
|
||||
let (outbound_message, operation_result) = self.process_message(message)?;
|
||||
if let Some(outbound_message) = outbound_message {
|
||||
outbound_messages.push(outbound_message);
|
||||
}
|
||||
if let Some(operation_result) = operation_result {
|
||||
operation_results.push(operation_result);
|
||||
}
|
||||
}
|
||||
Ok((outbound_messages, operation_results))
|
||||
}
|
||||
|
||||
/// Retrieve the aggregate public key
|
||||
fn get_aggregate_public_key(&self) -> Point {
|
||||
self.aggregate_public_key
|
||||
}
|
||||
|
||||
/// Trigger a DKG round
|
||||
fn start_distributed_key_generation(&mut self) -> Result<Message, CryptoError> {
|
||||
let message = self.start_dkg_round()?;
|
||||
Ok(message)
|
||||
}
|
||||
|
||||
// Trigger a signing round
|
||||
fn start_signing_message(&mut self, message: &[u8]) -> Result<Message, CryptoError> {
|
||||
self.message = message.to_vec();
|
||||
let message = self.start_signing_round()?;
|
||||
Ok(message)
|
||||
}
|
||||
|
||||
// Reset internal state
|
||||
fn reset(&mut self) {
|
||||
self.state = State::Idle;
|
||||
self.dkg_public_shares.clear();
|
||||
self.public_nonces.clear();
|
||||
self.signature_shares.clear();
|
||||
self.ids_to_await = (0..self.total_signers).collect();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
|
||||
use crate::runloop::process_inbound_messages;
|
||||
|
||||
use super::*;
|
||||
use frost_signer::{config::PublicKeys, signing_round::SigningRound};
|
||||
use hashbrown::HashMap;
|
||||
use p256k1::ecdsa;
|
||||
use rand_core::OsRng;
|
||||
|
||||
#[test]
|
||||
fn test_state_machine() {
|
||||
let mut rng = OsRng;
|
||||
let message_private_key = Scalar::random(&mut rng);
|
||||
|
||||
let mut coordinator = Coordinator::new(3, 3, 3, message_private_key);
|
||||
assert!(coordinator.can_move_to(&State::DkgPublicDistribute).is_ok());
|
||||
assert!(coordinator.can_move_to(&State::DkgPublicGather).is_err());
|
||||
assert!(coordinator
|
||||
.can_move_to(&State::DkgPrivateDistribute)
|
||||
.is_err());
|
||||
assert!(coordinator.can_move_to(&State::DkgEndGather).is_err());
|
||||
assert!(coordinator.can_move_to(&State::Idle).is_ok());
|
||||
|
||||
coordinator.move_to(State::DkgPublicDistribute).unwrap();
|
||||
assert!(coordinator
|
||||
.can_move_to(&State::DkgPublicDistribute)
|
||||
.is_err());
|
||||
assert!(coordinator.can_move_to(&State::DkgPublicGather).is_ok());
|
||||
assert!(coordinator
|
||||
.can_move_to(&State::DkgPrivateDistribute)
|
||||
.is_err());
|
||||
assert!(coordinator.can_move_to(&State::DkgEndGather).is_err());
|
||||
assert!(coordinator.can_move_to(&State::Idle).is_ok());
|
||||
|
||||
coordinator.move_to(State::DkgPublicGather).unwrap();
|
||||
assert!(coordinator.can_move_to(&State::DkgPublicDistribute).is_ok());
|
||||
assert!(coordinator.can_move_to(&State::DkgPublicGather).is_ok());
|
||||
assert!(coordinator
|
||||
.can_move_to(&State::DkgPrivateDistribute)
|
||||
.is_ok());
|
||||
assert!(coordinator.can_move_to(&State::DkgEndGather).is_err());
|
||||
assert!(coordinator.can_move_to(&State::Idle).is_ok());
|
||||
|
||||
coordinator.move_to(State::DkgPrivateDistribute).unwrap();
|
||||
assert!(coordinator
|
||||
.can_move_to(&State::DkgPublicDistribute)
|
||||
.is_err());
|
||||
assert!(coordinator.can_move_to(&State::DkgPublicGather).is_err());
|
||||
assert!(coordinator
|
||||
.can_move_to(&State::DkgPrivateDistribute)
|
||||
.is_err());
|
||||
assert!(coordinator.can_move_to(&State::DkgEndGather).is_ok());
|
||||
assert!(coordinator.can_move_to(&State::Idle).is_ok());
|
||||
|
||||
coordinator.move_to(State::DkgEndGather).unwrap();
|
||||
assert!(coordinator.can_move_to(&State::DkgPublicDistribute).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_new_coordinator() {
|
||||
let total_signers = 10;
|
||||
let total_keys = 40;
|
||||
let threshold = 28;
|
||||
let mut rng = OsRng;
|
||||
let message_private_key = Scalar::random(&mut rng);
|
||||
|
||||
let coordinator =
|
||||
Coordinator::new(total_signers, total_keys, threshold, message_private_key);
|
||||
|
||||
assert_eq!(coordinator.total_signers, total_signers);
|
||||
assert_eq!(coordinator.total_keys, total_keys);
|
||||
assert_eq!(coordinator.threshold, threshold);
|
||||
assert_eq!(coordinator.message_private_key, message_private_key);
|
||||
assert_eq!(coordinator.ids_to_await.len(), total_signers as usize);
|
||||
assert_eq!(coordinator.state, State::Idle);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_start_dkg_round() {
|
||||
let total_signers = 10;
|
||||
let total_keys = 40;
|
||||
let threshold = 28;
|
||||
let mut rng = OsRng;
|
||||
let message_private_key = Scalar::random(&mut rng);
|
||||
let mut coordinator =
|
||||
Coordinator::new(total_signers, total_keys, threshold, message_private_key);
|
||||
|
||||
let result = coordinator.start_dkg_round();
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert!(matches!(result.unwrap().msg, MessageTypes::DkgBegin(_)));
|
||||
assert_eq!(coordinator.state, State::DkgPublicGather);
|
||||
assert_eq!(coordinator.current_dkg_id, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_start_public_shares() {
|
||||
let total_signers = 10;
|
||||
let total_keys = 40;
|
||||
let threshold = 28;
|
||||
let mut rng = OsRng;
|
||||
let message_private_key = Scalar::random(&mut rng);
|
||||
let mut coordinator =
|
||||
Coordinator::new(total_signers, total_keys, threshold, message_private_key);
|
||||
coordinator.state = State::DkgPublicDistribute; // Must be in this state before calling start public shares
|
||||
|
||||
let result = coordinator.start_public_shares().unwrap();
|
||||
|
||||
assert!(matches!(result.msg, MessageTypes::DkgBegin(_)));
|
||||
assert_eq!(coordinator.state, State::DkgPublicGather);
|
||||
assert_eq!(coordinator.current_dkg_id, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_start_private_shares() {
|
||||
let total_signers = 10;
|
||||
let total_keys = 40;
|
||||
let threshold = 28;
|
||||
let mut rng = OsRng;
|
||||
let message_private_key = Scalar::random(&mut rng);
|
||||
let mut coordinator =
|
||||
Coordinator::new(total_signers, total_keys, threshold, message_private_key);
|
||||
coordinator.state = State::DkgPrivateDistribute; // Must be in this state before calling start private shares
|
||||
|
||||
let message = coordinator.start_private_shares().unwrap();
|
||||
assert!(matches!(message.msg, MessageTypes::DkgPrivateBegin(_)));
|
||||
assert_eq!(coordinator.state, State::DkgEndGather);
|
||||
assert_eq!(coordinator.current_dkg_id, 0);
|
||||
}
|
||||
|
||||
fn setup() -> (Coordinator, Vec<SigningRound>) {
|
||||
let mut rng = OsRng;
|
||||
let total_signers = 5;
|
||||
let threshold = total_signers / 10 + 7;
|
||||
let keys_per_signer = 3;
|
||||
let total_keys = total_signers * keys_per_signer;
|
||||
let key_pairs = (0..total_signers)
|
||||
.map(|_| {
|
||||
let private_key = Scalar::random(&mut rng);
|
||||
let public_key = ecdsa::PublicKey::new(&private_key).unwrap();
|
||||
(private_key, public_key)
|
||||
})
|
||||
.collect::<Vec<(Scalar, ecdsa::PublicKey)>>();
|
||||
let mut key_id: u32 = 0;
|
||||
let mut signer_ids_map = HashMap::new();
|
||||
let mut key_ids_map = HashMap::new();
|
||||
let mut key_ids = Vec::new();
|
||||
for (i, (_private_key, public_key)) in key_pairs.iter().enumerate() {
|
||||
for _ in 0..keys_per_signer {
|
||||
key_ids_map.insert(key_id + 1, *public_key);
|
||||
key_ids.push(key_id);
|
||||
key_id += 1;
|
||||
}
|
||||
signer_ids_map.insert(i as u32, *public_key);
|
||||
}
|
||||
let public_keys = PublicKeys {
|
||||
signers: signer_ids_map,
|
||||
key_ids: key_ids_map,
|
||||
};
|
||||
|
||||
let signing_rounds = key_pairs
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(signer_id, (private_key, _public_key))| {
|
||||
SigningRound::new(
|
||||
threshold,
|
||||
total_signers,
|
||||
total_keys,
|
||||
signer_id as u32,
|
||||
key_ids.clone(),
|
||||
*private_key,
|
||||
public_keys.clone(),
|
||||
)
|
||||
})
|
||||
.collect::<Vec<SigningRound>>();
|
||||
|
||||
let coordinator = Coordinator::new(total_signers, total_keys, threshold, key_pairs[0].0);
|
||||
(coordinator, signing_rounds)
|
||||
}
|
||||
|
||||
/// Helper function for feeding messages back from the processor into the signing rounds and coordinator
|
||||
fn feedback_messages(
|
||||
coordinator: &mut Coordinator,
|
||||
signing_rounds: &mut Vec<SigningRound>,
|
||||
messages: Vec<Message>,
|
||||
) -> (Vec<Message>, Vec<OperationResult>) {
|
||||
let mut inbound_messages = vec![];
|
||||
let mut feedback_messages = vec![];
|
||||
for signing_round in signing_rounds.as_mut_slice() {
|
||||
let outbound_messages =
|
||||
process_inbound_messages(signing_round, messages.clone()).unwrap();
|
||||
feedback_messages.extend_from_slice(outbound_messages.as_slice());
|
||||
inbound_messages.extend(outbound_messages);
|
||||
}
|
||||
for signing_round in signing_rounds.as_mut_slice() {
|
||||
let outbound_messages =
|
||||
process_inbound_messages(signing_round, feedback_messages.clone()).unwrap();
|
||||
inbound_messages.extend(outbound_messages);
|
||||
}
|
||||
coordinator
|
||||
.process_inbound_messages(inbound_messages)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_process_inbound_messages_dkg() {
|
||||
let (mut coordinator, mut signing_rounds) = setup();
|
||||
// We have started a dkg round
|
||||
let message = coordinator.start_dkg_round().unwrap();
|
||||
assert_eq!(coordinator.aggregate_public_key, Point::default());
|
||||
assert_eq!(coordinator.state, State::DkgPublicGather);
|
||||
// we have to loop in case we get an invalid y coord...
|
||||
loop {
|
||||
// Send the DKG Begin message to all signers and gather responses by sharing with all other signers and coordinator
|
||||
let (outbound_messages, operation_results) =
|
||||
feedback_messages(&mut coordinator, &mut signing_rounds, vec![message.clone()]);
|
||||
assert!(operation_results.is_empty());
|
||||
if coordinator.state == State::DkgEndGather {
|
||||
// Successfully got an Aggregate Public Key...
|
||||
assert_eq!(outbound_messages.len(), 1);
|
||||
match &outbound_messages[0].msg {
|
||||
MessageTypes::DkgPrivateBegin(_) => {}
|
||||
_ => {
|
||||
panic!("Expected DkgPrivateBegin message");
|
||||
}
|
||||
}
|
||||
// Send the DKG Private Begin message to all signers and share their responses with the coordinator and signers
|
||||
let (outbound_messages, operation_results) =
|
||||
feedback_messages(&mut coordinator, &mut signing_rounds, outbound_messages);
|
||||
assert!(outbound_messages.is_empty());
|
||||
assert_eq!(operation_results.len(), 1);
|
||||
match operation_results[0] {
|
||||
OperationResult::Dkg(point) => {
|
||||
assert_ne!(point, Point::default());
|
||||
assert_eq!(coordinator.aggregate_public_key, point);
|
||||
assert_eq!(coordinator.state, State::Idle);
|
||||
break;
|
||||
}
|
||||
_ => panic!("Expected Dkg Operation result"),
|
||||
}
|
||||
}
|
||||
}
|
||||
assert_ne!(coordinator.aggregate_public_key, Point::default());
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
/// FROST (Fast Reliable Optimistic Secure Threshold) signature generation module
|
||||
pub mod frost;
|
||||
|
||||
use frost::Error as FrostError;
|
||||
use frost_signer::net::Message;
|
||||
use wsts::{common::Signature, taproot::SchnorrProof, Point};
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
/// Error type for the crypto module
|
||||
pub enum Error {
|
||||
/// An error occurred in the FROST module
|
||||
#[error("An error occurred in the FROST module: {0}")]
|
||||
FrostError(#[from] FrostError),
|
||||
}
|
||||
|
||||
/// Result of a DKG or sign operation
|
||||
#[allow(dead_code)]
|
||||
pub enum OperationResult {
|
||||
/// The DKG result
|
||||
Dkg(Point),
|
||||
/// The sign result
|
||||
Sign(Signature, SchnorrProof),
|
||||
}
|
||||
|
||||
/// Coordinatable trait for handling the coordination of DKG and sign messages
|
||||
pub trait Coordinatable {
|
||||
/// Process inbound messages
|
||||
fn process_inbound_messages(
|
||||
&mut self,
|
||||
messages: Vec<Message>,
|
||||
) -> Result<(Vec<Message>, Vec<OperationResult>), Error>;
|
||||
/// Retrieve the aggregate public key
|
||||
fn get_aggregate_public_key(&self) -> Point;
|
||||
/// Trigger a DKG round
|
||||
fn start_distributed_key_generation(&mut self) -> Result<Message, Error>;
|
||||
/// Trigger a signing round
|
||||
fn start_signing_message(&mut self, _message: &[u8]) -> Result<Message, Error>;
|
||||
/// Reset internal state
|
||||
fn reset(&mut self);
|
||||
}
|
||||
@@ -7,8 +7,6 @@ Usage documentation can be found in the [README](https://github.com/Trust-Machin
|
||||
pub mod cli;
|
||||
/// The configuration module for the signer
|
||||
pub mod config;
|
||||
/// All crypto related modules
|
||||
pub mod crypto;
|
||||
/// The primary runloop for the signer
|
||||
pub mod runloop;
|
||||
/// The signer client for communicating with stackerdb/stacks nodes
|
||||
|
||||
@@ -45,7 +45,6 @@ use stacks_signer::{
|
||||
RunDkgArgs, SignArgs, StackerDBArgs,
|
||||
},
|
||||
config::{Config, Network},
|
||||
crypto::{frost::Coordinator as FrostCoordinator, OperationResult},
|
||||
runloop::{RunLoop, RunLoopCommand},
|
||||
utils::{build_signer_config_tomls, build_stackerdb_contract},
|
||||
};
|
||||
@@ -57,6 +56,10 @@ use std::{
|
||||
sync::mpsc::{channel, Receiver, Sender},
|
||||
time::Duration,
|
||||
};
|
||||
use wsts::{
|
||||
state_machine::{coordinator::Coordinator as FrostCoordinator, OperationResult},
|
||||
v2,
|
||||
};
|
||||
|
||||
struct SpawnedSigner {
|
||||
running_signer: RunningSigner<StackerDBEventReceiver, Vec<OperationResult>>,
|
||||
@@ -90,11 +93,11 @@ fn spawn_running_signer(path: &PathBuf) -> SpawnedSigner {
|
||||
let (cmd_send, cmd_recv) = channel();
|
||||
let (res_send, res_recv) = channel();
|
||||
let ev = StackerDBEventReceiver::new(vec![config.stackerdb_contract_id.clone()]);
|
||||
let runloop: RunLoop<FrostCoordinator> = RunLoop::from(&config);
|
||||
let runloop: RunLoop<FrostCoordinator<v2::Aggregator>> = RunLoop::from(&config);
|
||||
let mut signer: Signer<
|
||||
RunLoopCommand,
|
||||
Vec<OperationResult>,
|
||||
RunLoop<FrostCoordinator>,
|
||||
RunLoop<FrostCoordinator<v2::Aggregator>>,
|
||||
StackerDBEventReceiver,
|
||||
> = Signer::new(runloop, ev, cmd_recv, res_send);
|
||||
let endpoint = config.node_host;
|
||||
@@ -114,10 +117,16 @@ fn process_dkg_result(dkg_res: &[OperationResult]) {
|
||||
OperationResult::Dkg(point) => {
|
||||
println!("Received aggregate group key: {point}");
|
||||
}
|
||||
OperationResult::Sign(signature, schnorr_proof) => {
|
||||
OperationResult::Sign(signature) => {
|
||||
panic!(
|
||||
"Received unexpected signature ({},{}) and schnorr proof ({},{})",
|
||||
&signature.R, &signature.z, &schnorr_proof.r, &schnorr_proof.s
|
||||
"Received unexpected signature ({},{})",
|
||||
&signature.R, &signature.z,
|
||||
);
|
||||
}
|
||||
OperationResult::SignTaproot(schnorr_proof) => {
|
||||
panic!(
|
||||
"Received unexpected schnorr proof ({},{})",
|
||||
&schnorr_proof.r, &schnorr_proof.s,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -131,10 +140,16 @@ fn process_sign_result(sign_res: &[OperationResult]) {
|
||||
OperationResult::Dkg(point) => {
|
||||
panic!("Received unexpected aggregate group key: {point}");
|
||||
}
|
||||
OperationResult::Sign(signature, schnorr_proof) => {
|
||||
println!(
|
||||
"Received good signature ({},{}) and schnorr proof ({},{})",
|
||||
&signature.R, &signature.z, &schnorr_proof.r, &schnorr_proof.s
|
||||
OperationResult::Sign(signature) => {
|
||||
panic!(
|
||||
"Received bood signature ({},{})",
|
||||
&signature.R, &signature.z,
|
||||
);
|
||||
}
|
||||
OperationResult::SignTaproot(schnorr_proof) => {
|
||||
panic!(
|
||||
"Received unexpected schnorr proof ({},{})",
|
||||
&schnorr_proof.r, &schnorr_proof.s,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -184,7 +199,11 @@ fn handle_sign(args: SignArgs) {
|
||||
let spawned_signer = spawn_running_signer(&args.config);
|
||||
spawned_signer
|
||||
.cmd_send
|
||||
.send(RunLoopCommand::Sign { message: args.data })
|
||||
.send(RunLoopCommand::Sign {
|
||||
message: args.data,
|
||||
is_taproot: false,
|
||||
merkle_root: None,
|
||||
})
|
||||
.unwrap();
|
||||
let sign_res = spawned_signer.res_recv.recv().unwrap();
|
||||
process_sign_result(&sign_res);
|
||||
@@ -198,7 +217,11 @@ fn handle_dkg_sign(args: SignArgs) {
|
||||
spawned_signer.cmd_send.send(RunLoopCommand::Dkg).unwrap();
|
||||
spawned_signer
|
||||
.cmd_send
|
||||
.send(RunLoopCommand::Sign { message: args.data })
|
||||
.send(RunLoopCommand::Sign {
|
||||
message: args.data,
|
||||
is_taproot: false,
|
||||
merkle_root: None,
|
||||
})
|
||||
.unwrap();
|
||||
let dkg_res = spawned_signer.res_recv.recv().unwrap();
|
||||
process_dkg_result(&dkg_res);
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
use frost_signer::{
|
||||
config::PublicKeys,
|
||||
net::Message,
|
||||
signing_round::{MessageTypes, Signable, SigningRound},
|
||||
};
|
||||
use crate::{config::Config, stacks_client::StacksClient};
|
||||
use libsigner::{SignerRunLoop, StackerDBChunksEvent};
|
||||
use p256k1::ecdsa;
|
||||
use slog::{slog_debug, slog_error, slog_info, slog_warn};
|
||||
use stacks_common::{debug, error, info, warn};
|
||||
use std::{collections::VecDeque, sync::mpsc::Sender, time::Duration};
|
||||
|
||||
use crate::{
|
||||
config::Config,
|
||||
crypto::{frost::Coordinator as FrostCoordinator, Coordinatable, OperationResult},
|
||||
stacks_client::StacksClient,
|
||||
use wsts::{
|
||||
common::MerkleRoot,
|
||||
net::{Message, Packet, Signable},
|
||||
state_machine::{
|
||||
coordinator::{Coordinatable, Coordinator as FrostCoordinator},
|
||||
signer::SigningRound,
|
||||
OperationResult, PublicKeys,
|
||||
},
|
||||
v2,
|
||||
};
|
||||
|
||||
/// Which operation to perform
|
||||
@@ -24,6 +24,10 @@ pub enum RunLoopCommand {
|
||||
Sign {
|
||||
/// The bytes to sign
|
||||
message: Vec<u8>,
|
||||
/// Whether to make a taproot signature
|
||||
is_taproot: bool,
|
||||
/// Taproot merkle root
|
||||
merkle_root: Option<MerkleRoot>,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -47,7 +51,7 @@ pub struct RunLoop<C> {
|
||||
/// The signing round used to sign messages
|
||||
// TODO: update this to use frost_signer directly instead of the frost signing round
|
||||
// See: https://github.com/stacks-network/stacks-blockchain/issues/3913
|
||||
pub signing_round: SigningRound,
|
||||
pub signing_round: SigningRound<v2::Signer>,
|
||||
/// The stacks client
|
||||
pub stacks_client: StacksClient,
|
||||
/// Received Commands that need to be processed
|
||||
@@ -67,7 +71,7 @@ impl<C: Coordinatable> RunLoop<C> {
|
||||
Ok(msg) => {
|
||||
let ack = self
|
||||
.stacks_client
|
||||
.send_message(self.signing_round.signer.signer_id, msg);
|
||||
.send_message(self.signing_round.signer_id, msg);
|
||||
debug!("ACK: {:?}", ack);
|
||||
self.state = State::Dkg;
|
||||
true
|
||||
@@ -80,13 +84,20 @@ impl<C: Coordinatable> RunLoop<C> {
|
||||
}
|
||||
}
|
||||
}
|
||||
RunLoopCommand::Sign { message } => {
|
||||
RunLoopCommand::Sign {
|
||||
message,
|
||||
is_taproot,
|
||||
merkle_root,
|
||||
} => {
|
||||
info!("Signing message: {:?}", message);
|
||||
match self.coordinator.start_signing_message(message) {
|
||||
match self
|
||||
.coordinator
|
||||
.start_signing_message(message, *is_taproot, *merkle_root)
|
||||
{
|
||||
Ok(msg) => {
|
||||
let ack = self
|
||||
.stacks_client
|
||||
.send_message(self.signing_round.signer.signer_id, msg);
|
||||
.send_message(self.signing_round.signer_id, msg);
|
||||
debug!("ACK: {:?}", ack);
|
||||
self.state = State::Sign;
|
||||
true
|
||||
@@ -126,16 +137,16 @@ impl<C: Coordinatable> RunLoop<C> {
|
||||
fn process_event(
|
||||
&mut self,
|
||||
event: &StackerDBChunksEvent,
|
||||
) -> (Vec<Message>, Vec<OperationResult>) {
|
||||
) -> (Vec<Packet>, Vec<OperationResult>) {
|
||||
// Determine the current coordinator id and public key for verification
|
||||
let (coordinator_id, coordinator_public_key) =
|
||||
calculate_coordinator(&self.signing_round.public_keys);
|
||||
// Filter out invalid messages
|
||||
let inbound_messages: Vec<Message> = event
|
||||
let inbound_messages: Vec<Packet> = event
|
||||
.modified_slots
|
||||
.iter()
|
||||
.filter_map(|chunk| {
|
||||
let message = bincode::deserialize::<Message>(&chunk.data).ok()?;
|
||||
let message = bincode::deserialize::<Packet>(&chunk.data).ok()?;
|
||||
if verify_msg(
|
||||
&message,
|
||||
&self.signing_round.public_keys,
|
||||
@@ -148,11 +159,12 @@ impl<C: Coordinatable> RunLoop<C> {
|
||||
})
|
||||
.collect();
|
||||
// First process all messages as a signer
|
||||
let mut outbound_messages =
|
||||
process_inbound_messages(&mut self.signing_round, inbound_messages.clone())
|
||||
.unwrap_or_default();
|
||||
let mut outbound_messages = self
|
||||
.signing_round
|
||||
.process_inbound_messages(inbound_messages.clone())
|
||||
.unwrap_or_default();
|
||||
// If the signer is the coordinator, then next process the message as the coordinator
|
||||
let (messages, results) = if self.signing_round.signer.signer_id == coordinator_id {
|
||||
let (messages, results) = if self.signing_round.signer_id == coordinator_id {
|
||||
self.coordinator
|
||||
.process_inbound_messages(inbound_messages)
|
||||
.unwrap_or_default()
|
||||
@@ -164,7 +176,7 @@ impl<C: Coordinatable> RunLoop<C> {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Config> for RunLoop<FrostCoordinator> {
|
||||
impl From<&Config> for RunLoop<FrostCoordinator<v2::Aggregator>> {
|
||||
/// Creates new runloop from a config
|
||||
fn from(config: &Config) -> Self {
|
||||
// TODO: this should be a config option
|
||||
@@ -215,60 +227,6 @@ impl From<&Config> for RunLoop<FrostCoordinator> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Process inbound messages using the frost_signer signing round mechanism
|
||||
pub fn process_inbound_messages(
|
||||
signing_round: &mut SigningRound,
|
||||
messages: Vec<Message>,
|
||||
) -> Result<Vec<Message>, frost_signer::signing_round::Error> {
|
||||
let mut responses = vec![];
|
||||
for message in messages {
|
||||
// TODO: this code was swiped from frost-signer. Expose it there so we don't have duplicate code
|
||||
// See: https://github.com/stacks-network/stacks-blockchain/issues/3913
|
||||
let outbounds = signing_round.process(message.msg)?;
|
||||
for out in outbounds {
|
||||
let msg = Message {
|
||||
msg: out.clone(),
|
||||
sig: match out {
|
||||
MessageTypes::DkgBegin(msg) | MessageTypes::DkgPrivateBegin(msg) => msg
|
||||
.sign(&signing_round.network_private_key)
|
||||
.expect("failed to sign DkgBegin")
|
||||
.to_vec(),
|
||||
MessageTypes::DkgEnd(msg) | MessageTypes::DkgPublicEnd(msg) => msg
|
||||
.sign(&signing_round.network_private_key)
|
||||
.expect("failed to sign DkgEnd")
|
||||
.to_vec(),
|
||||
MessageTypes::DkgPublicShare(msg) => msg
|
||||
.sign(&signing_round.network_private_key)
|
||||
.expect("failed to sign DkgPublicShare")
|
||||
.to_vec(),
|
||||
MessageTypes::DkgPrivateShares(msg) => msg
|
||||
.sign(&signing_round.network_private_key)
|
||||
.expect("failed to sign DkgPrivateShare")
|
||||
.to_vec(),
|
||||
MessageTypes::NonceRequest(msg) => msg
|
||||
.sign(&signing_round.network_private_key)
|
||||
.expect("failed to sign NonceRequest")
|
||||
.to_vec(),
|
||||
MessageTypes::NonceResponse(msg) => msg
|
||||
.sign(&signing_round.network_private_key)
|
||||
.expect("failed to sign NonceResponse")
|
||||
.to_vec(),
|
||||
MessageTypes::SignShareRequest(msg) => msg
|
||||
.sign(&signing_round.network_private_key)
|
||||
.expect("failed to sign SignShareRequest")
|
||||
.to_vec(),
|
||||
MessageTypes::SignShareResponse(msg) => msg
|
||||
.sign(&signing_round.network_private_key)
|
||||
.expect("failed to sign SignShareResponse")
|
||||
.to_vec(),
|
||||
},
|
||||
};
|
||||
responses.push(msg);
|
||||
}
|
||||
}
|
||||
Ok(responses)
|
||||
}
|
||||
|
||||
impl<C: Coordinatable> SignerRunLoop<Vec<OperationResult>, RunLoopCommand> for RunLoop<C> {
|
||||
fn set_event_timeout(&mut self, timeout: Duration) {
|
||||
self.event_timeout = timeout;
|
||||
@@ -297,7 +255,7 @@ impl<C: Coordinatable> SignerRunLoop<Vec<OperationResult>, RunLoopCommand> for R
|
||||
for msg in outbound_messages {
|
||||
let ack = self
|
||||
.stacks_client
|
||||
.send_message(self.signing_round.signer.signer_id, msg);
|
||||
.send_message(self.signing_round.signer_id, msg);
|
||||
if let Ok(ack) = ack {
|
||||
debug!("ACK: {:?}", ack);
|
||||
} else {
|
||||
@@ -336,18 +294,18 @@ fn calculate_coordinator(public_keys: &PublicKeys) -> (u32, &ecdsa::PublicKey) {
|
||||
/// Temporary copy paste from frost-signer
|
||||
/// See: https://github.com/stacks-network/stacks-blockchain/issues/3913
|
||||
fn verify_msg(
|
||||
m: &Message,
|
||||
m: &Packet,
|
||||
public_keys: &PublicKeys,
|
||||
coordinator_public_key: &ecdsa::PublicKey,
|
||||
) -> bool {
|
||||
match &m.msg {
|
||||
MessageTypes::DkgBegin(msg) | MessageTypes::DkgPrivateBegin(msg) => {
|
||||
Message::DkgBegin(msg) | Message::DkgPrivateBegin(msg) => {
|
||||
if !msg.verify(&m.sig, coordinator_public_key) {
|
||||
warn!("Received a DkgPrivateBegin message with an invalid signature.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
MessageTypes::DkgEnd(msg) | MessageTypes::DkgPublicEnd(msg) => {
|
||||
Message::DkgEnd(msg) => {
|
||||
if let Some(public_key) = public_keys.signers.get(&msg.signer_id) {
|
||||
if !msg.verify(&m.sig, public_key) {
|
||||
warn!("Received a DkgPublicEnd message with an invalid signature.");
|
||||
@@ -361,45 +319,44 @@ fn verify_msg(
|
||||
return false;
|
||||
}
|
||||
}
|
||||
MessageTypes::DkgPublicShare(msg) => {
|
||||
if let Some(public_key) = public_keys.key_ids.get(&msg.party_id) {
|
||||
Message::DkgPublicShares(msg) => {
|
||||
if let Some(public_key) = public_keys.signers.get(&msg.signer_id) {
|
||||
if !msg.verify(&m.sig, public_key) {
|
||||
warn!("Received a DkgPublicShare message with an invalid signature.");
|
||||
warn!("Received a DkgPublicShares message with an invalid signature.");
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
warn!(
|
||||
"Received a DkgPublicShare message with an unknown id: {}",
|
||||
msg.party_id
|
||||
"Received a DkgPublicShares message with an unknown id: {}",
|
||||
msg.signer_id
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
MessageTypes::DkgPrivateShares(msg) => {
|
||||
Message::DkgPrivateShares(msg) => {
|
||||
// Private shares have key IDs from [0, N) to reference IDs from [1, N]
|
||||
// in Frost V4 to enable easy indexing hence ID + 1
|
||||
// TODO: Once Frost V5 is released, this off by one adjustment will no longer be required
|
||||
let key_id = msg.key_id + 1;
|
||||
if let Some(public_key) = public_keys.key_ids.get(&key_id) {
|
||||
if let Some(public_key) = public_keys.signers.get(&msg.signer_id) {
|
||||
if !msg.verify(&m.sig, public_key) {
|
||||
warn!("Received a DkgPrivateShares message with an invalid signature from key_id {} key {}", msg.key_id, &public_key);
|
||||
warn!("Received a DkgPrivateShares message with an invalid signature from signer_id {} key {}", msg.signer_id, &public_key);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
warn!(
|
||||
"Received a DkgPrivateShares message with an unknown id: {}",
|
||||
key_id
|
||||
msg.signer_id
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
MessageTypes::NonceRequest(msg) => {
|
||||
Message::NonceRequest(msg) => {
|
||||
if !msg.verify(&m.sig, coordinator_public_key) {
|
||||
warn!("Received a NonceRequest message with an invalid signature.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
MessageTypes::NonceResponse(msg) => {
|
||||
Message::NonceResponse(msg) => {
|
||||
if let Some(public_key) = public_keys.signers.get(&msg.signer_id) {
|
||||
if !msg.verify(&m.sig, public_key) {
|
||||
warn!("Received a NonceResponse message with an invalid signature.");
|
||||
@@ -413,21 +370,21 @@ fn verify_msg(
|
||||
return false;
|
||||
}
|
||||
}
|
||||
MessageTypes::SignShareRequest(msg) => {
|
||||
Message::SignatureShareRequest(msg) => {
|
||||
if !msg.verify(&m.sig, coordinator_public_key) {
|
||||
warn!("Received a SignShareRequest message with an invalid signature.");
|
||||
warn!("Received a SignatureShareRequest message with an invalid signature.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
MessageTypes::SignShareResponse(msg) => {
|
||||
Message::SignatureShareResponse(msg) => {
|
||||
if let Some(public_key) = public_keys.signers.get(&msg.signer_id) {
|
||||
if !msg.verify(&m.sig, public_key) {
|
||||
warn!("Received a SignShareResponse message with an invalid signature.");
|
||||
warn!("Received a SignatureShareResponse message with an invalid signature.");
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
warn!(
|
||||
"Received a SignShareResponse message with an unknown id: {}",
|
||||
"Received a SignatureShareResponse message with an unknown id: {}",
|
||||
msg.signer_id
|
||||
);
|
||||
return false;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
use bincode::Error as BincodeError;
|
||||
use frost_signer::{net::Message, signing_round::MessageTypes};
|
||||
use hashbrown::HashMap;
|
||||
use libsigner::{RPCError, SignerSession, StackerDBSession};
|
||||
use libstackerdb::{Error as StackerDBError, StackerDBChunkAckData, StackerDBChunkData};
|
||||
use slog::{slog_debug, slog_warn};
|
||||
use stacks_common::{debug, types::chainstate::StacksPrivateKey, warn};
|
||||
use wsts::net::{Message, Packet};
|
||||
|
||||
use crate::config::Config;
|
||||
|
||||
@@ -59,7 +59,7 @@ impl StacksClient {
|
||||
pub fn send_message(
|
||||
&mut self,
|
||||
id: u32,
|
||||
message: Message,
|
||||
message: Packet,
|
||||
) -> Result<StackerDBChunkAckData, ClientError> {
|
||||
let message_bytes = bincode::serialize(&message)?;
|
||||
let slot_id = slot_id(id, &message.msg);
|
||||
@@ -101,18 +101,17 @@ impl StacksClient {
|
||||
}
|
||||
|
||||
/// Helper function to determine the slot ID for the provided stacker-db writer id and the message type
|
||||
fn slot_id(id: u32, message: &MessageTypes) -> u32 {
|
||||
fn slot_id(id: u32, message: &Message) -> u32 {
|
||||
let slot_id = match message {
|
||||
MessageTypes::DkgBegin(_) => 0,
|
||||
MessageTypes::DkgPrivateBegin(_) => 1,
|
||||
MessageTypes::DkgEnd(_) => 2,
|
||||
MessageTypes::DkgPublicEnd(_) => 3,
|
||||
MessageTypes::DkgPublicShare(_) => 4,
|
||||
MessageTypes::DkgPrivateShares(_) => 5,
|
||||
MessageTypes::NonceRequest(_) => 6,
|
||||
MessageTypes::NonceResponse(_) => 7,
|
||||
MessageTypes::SignShareRequest(_) => 8,
|
||||
MessageTypes::SignShareResponse(_) => 9,
|
||||
Message::DkgBegin(_) => 0,
|
||||
Message::DkgPrivateBegin(_) => 1,
|
||||
Message::DkgEnd(_) => 2,
|
||||
Message::DkgPublicShares(_) => 4,
|
||||
Message::DkgPrivateShares(_) => 5,
|
||||
Message::NonceRequest(_) => 6,
|
||||
Message::NonceResponse(_) => 7,
|
||||
Message::SignatureShareRequest(_) => 8,
|
||||
Message::SignatureShareResponse(_) => 9,
|
||||
};
|
||||
SLOTS_PER_USER * id + slot_id
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ stacks-common = { path = "../../stacks-common", features = ["default", "testing"
|
||||
stacks = { package = "stackslib", path = "../../stackslib", features = ["default", "testing"] }
|
||||
stacks-signer = { path = "../../stacks-signer" }
|
||||
p256k1 = "5.4.1"
|
||||
wsts = { git = "https://github.com/Trust-Machines/wsts", tag = "4.0.0rc1" }
|
||||
|
||||
[dev-dependencies.rusqlite]
|
||||
version = "=0.24.2"
|
||||
|
||||
@@ -21,10 +21,13 @@ use stacks::chainstate::stacks::StacksPrivateKey;
|
||||
use stacks_common::types::chainstate::StacksAddress;
|
||||
use stacks_signer::{
|
||||
config::Config as SignerConfig,
|
||||
crypto::{frost::Coordinator as FrostCoordinator, OperationResult},
|
||||
runloop::RunLoopCommand,
|
||||
utils::{build_signer_config_tomls, build_stackerdb_contract},
|
||||
};
|
||||
use wsts::{
|
||||
state_machine::{coordinator::Coordinator as FrostCoordinator, OperationResult},
|
||||
v2,
|
||||
};
|
||||
|
||||
// Helper struct for holding the btc and stx neon nodes
|
||||
#[allow(dead_code)]
|
||||
@@ -42,12 +45,12 @@ fn spawn_signer(
|
||||
) -> RunningSigner<StackerDBEventReceiver, Vec<OperationResult>> {
|
||||
let config = stacks_signer::config::Config::load_from_str(data).unwrap();
|
||||
let ev = StackerDBEventReceiver::new(vec![config.stackerdb_contract_id.clone()]);
|
||||
let runloop: stacks_signer::runloop::RunLoop<FrostCoordinator> =
|
||||
let runloop: stacks_signer::runloop::RunLoop<FrostCoordinator<v2::Aggregator>> =
|
||||
stacks_signer::runloop::RunLoop::from(&config);
|
||||
let mut signer: Signer<
|
||||
RunLoopCommand,
|
||||
Vec<OperationResult>,
|
||||
stacks_signer::runloop::RunLoop<FrostCoordinator>,
|
||||
stacks_signer::runloop::RunLoop<FrostCoordinator<v2::Aggregator>>,
|
||||
StackerDBEventReceiver,
|
||||
> = Signer::new(runloop, ev, receiver, sender);
|
||||
let endpoint = config.endpoint;
|
||||
@@ -154,8 +157,8 @@ fn test_stackerdb_dkg() {
|
||||
return;
|
||||
}
|
||||
// Generate Signer Data
|
||||
let num_signers: u32 = 5;
|
||||
let num_keys: u32 = 20;
|
||||
let num_signers: u32 = 16;
|
||||
let num_keys: u32 = 4000;
|
||||
let signer_stacks_private_keys = (0..num_signers)
|
||||
.map(|_| StacksPrivateKey::new())
|
||||
.collect::<Vec<StacksPrivateKey>>();
|
||||
@@ -224,6 +227,15 @@ fn test_stackerdb_dkg() {
|
||||
coordinator_cmd_send
|
||||
.send(RunLoopCommand::Sign {
|
||||
message: vec![1, 2, 3, 4, 5],
|
||||
is_taproot: false,
|
||||
merkle_root: None,
|
||||
})
|
||||
.expect("failed to send Sign command");
|
||||
coordinator_cmd_send
|
||||
.send(RunLoopCommand::Sign {
|
||||
message: vec![1, 2, 3, 4, 5],
|
||||
is_taproot: true,
|
||||
merkle_root: None,
|
||||
})
|
||||
.expect("failed to send Sign command");
|
||||
|
||||
@@ -239,10 +251,12 @@ fn test_stackerdb_dkg() {
|
||||
info!("Received aggregate_group_key {point}");
|
||||
aggregate_group_key = Some(point);
|
||||
}
|
||||
OperationResult::Sign(sig, proof) => {
|
||||
OperationResult::Sign(sig) => {
|
||||
info!("Received Signature ({},{})", &sig.R, &sig.z);
|
||||
info!("Received SchnorrProof ({},{})", &proof.r, &proof.s);
|
||||
frost_signature = Some(sig);
|
||||
}
|
||||
OperationResult::SignTaproot(proof) => {
|
||||
info!("Received SchnorrProof ({},{})", &proof.r, &proof.s);
|
||||
schnorr_proof = Some(proof);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user