diff --git a/Cargo.lock b/Cargo.lock
index 45903ffa7..ccbbb440c 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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]]
diff --git a/stacks-signer/Cargo.toml b/stacks-signer/Cargo.toml
index fa24a7b61..4fad7435a 100644
--- a/stacks-signer/Cargo.toml
+++ b/stacks-signer/Cargo.toml
@@ -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"
diff --git a/stacks-signer/src/config.rs b/stacks-signer/src/config.rs
index 5bfe5d002..8bd556c85 100644
--- a/stacks-signer/src/config.rs
+++ b/stacks-signer/src/config.rs
@@ -15,7 +15,7 @@
// along with this program. If not, see .
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>;
const EVENT_TIMEOUT_MS: u64 = 5000;
diff --git a/stacks-signer/src/crypto/frost.rs b/stacks-signer/src/crypto/frost.rs
deleted file mode 100644
index a313cadaa..000000000
--- a/stacks-signer/src/crypto/frost.rs
+++ /dev/null
@@ -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 for Error {
- fn from(err: AggregatorError) -> Self {
- Error::Aggregator(err)
- }
-}
-
-impl From 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,
- public_nonces: BTreeMap,
- signature_shares: BTreeMap>,
- aggregate_public_key: Point,
- signature: Signature,
- schnorr_proof: SchnorrProof,
- message_private_key: Scalar,
- ids_to_await: HashSet,
- message: Vec,
- 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, Option), 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 {
- 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 {
- 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 {
- 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 {
- 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 {
- 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 {
- 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::>();
- 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 = 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::>();
-
- let nonces = nonce_responses
- .iter()
- .flat_map(|nr| nr.nonces.clone())
- .collect::>();
-
- let shares = &self
- .public_nonces
- .iter()
- .flat_map(|(i, _)| self.signature_shares[i].clone())
- .collect::>();
-
- 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::>();
- let nonces = self
- .public_nonces
- .values()
- .flat_map(|pn| pn.nonces.clone())
- .collect::>();
- 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,
- ) -> Result<(Vec, Vec), 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 {
- let message = self.start_dkg_round()?;
- Ok(message)
- }
-
- // Trigger a signing round
- fn start_signing_message(&mut self, message: &[u8]) -> Result {
- 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) {
- 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::>();
- 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::>();
-
- 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,
- messages: Vec,
- ) -> (Vec, Vec) {
- 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());
- }
-}
diff --git a/stacks-signer/src/crypto/mod.rs b/stacks-signer/src/crypto/mod.rs
deleted file mode 100644
index 51d8c6ab4..000000000
--- a/stacks-signer/src/crypto/mod.rs
+++ /dev/null
@@ -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,
- ) -> Result<(Vec, Vec), 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;
- /// Trigger a signing round
- fn start_signing_message(&mut self, _message: &[u8]) -> Result;
- /// Reset internal state
- fn reset(&mut self);
-}
diff --git a/stacks-signer/src/lib.rs b/stacks-signer/src/lib.rs
index 7cc2f2071..2a14245b6 100644
--- a/stacks-signer/src/lib.rs
+++ b/stacks-signer/src/lib.rs
@@ -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
diff --git a/stacks-signer/src/main.rs b/stacks-signer/src/main.rs
index 1ca7b8512..aefc99338 100644
--- a/stacks-signer/src/main.rs
+++ b/stacks-signer/src/main.rs
@@ -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>,
@@ -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 = RunLoop::from(&config);
+ let runloop: RunLoop> = RunLoop::from(&config);
let mut signer: Signer<
RunLoopCommand,
Vec,
- RunLoop,
+ RunLoop>,
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);
diff --git a/stacks-signer/src/runloop.rs b/stacks-signer/src/runloop.rs
index 329dfb5c1..8035878b2 100644
--- a/stacks-signer/src/runloop.rs
+++ b/stacks-signer/src/runloop.rs
@@ -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,
+ /// Whether to make a taproot signature
+ is_taproot: bool,
+ /// Taproot merkle root
+ merkle_root: Option,
},
}
@@ -47,7 +51,7 @@ pub struct RunLoop {
/// 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,
/// The stacks client
pub stacks_client: StacksClient,
/// Received Commands that need to be processed
@@ -67,7 +71,7 @@ impl RunLoop {
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 RunLoop {
}
}
}
- 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 RunLoop {
fn process_event(
&mut self,
event: &StackerDBChunksEvent,
- ) -> (Vec, Vec) {
+ ) -> (Vec, Vec) {
// 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 = event
+ let inbound_messages: Vec = event
.modified_slots
.iter()
.filter_map(|chunk| {
- let message = bincode::deserialize::(&chunk.data).ok()?;
+ let message = bincode::deserialize::(&chunk.data).ok()?;
if verify_msg(
&message,
&self.signing_round.public_keys,
@@ -148,11 +159,12 @@ impl RunLoop {
})
.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 RunLoop {
}
}
-impl From<&Config> for RunLoop {
+impl From<&Config> for RunLoop> {
/// 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 {
}
}
-/// Process inbound messages using the frost_signer signing round mechanism
-pub fn process_inbound_messages(
- signing_round: &mut SigningRound,
- messages: Vec,
-) -> Result, 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 SignerRunLoop, RunLoopCommand> for RunLoop {
fn set_event_timeout(&mut self, timeout: Duration) {
self.event_timeout = timeout;
@@ -297,7 +255,7 @@ impl SignerRunLoop, 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;
diff --git a/stacks-signer/src/stacks_client.rs b/stacks-signer/src/stacks_client.rs
index f555a3f99..68e1cb60a 100644
--- a/stacks-signer/src/stacks_client.rs
+++ b/stacks-signer/src/stacks_client.rs
@@ -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 {
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
}
diff --git a/testnet/stacks-node/Cargo.toml b/testnet/stacks-node/Cargo.toml
index 4d63c0cf7..bb1934beb 100644
--- a/testnet/stacks-node/Cargo.toml
+++ b/testnet/stacks-node/Cargo.toml
@@ -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"
diff --git a/testnet/stacks-node/src/tests/signer.rs b/testnet/stacks-node/src/tests/signer.rs
index 15ee012f4..0bb9112df 100644
--- a/testnet/stacks-node/src/tests/signer.rs
+++ b/testnet/stacks-node/src/tests/signer.rs
@@ -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> {
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 =
+ let runloop: stacks_signer::runloop::RunLoop> =
stacks_signer::runloop::RunLoop::from(&config);
let mut signer: Signer<
RunLoopCommand,
Vec,
- stacks_signer::runloop::RunLoop,
+ stacks_signer::runloop::RunLoop>,
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::>();
@@ -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);
}
}