Merge branch 'develop' into feat/http-rpc-refactor

This commit is contained in:
Jude Nelson
2023-10-03 22:08:47 +00:00
committed by GitHub
11 changed files with 146 additions and 1313 deletions

266
Cargo.lock generated
View File

@@ -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]]

View File

@@ -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"

View File

@@ -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;

View File

@@ -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());
}
}

View File

@@ -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);
}

View File

@@ -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

View File

@@ -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);

View File

@@ -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;

View File

@@ -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
}

View File

@@ -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"

View File

@@ -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);
}
}