diff --git a/Cargo.lock b/Cargo.lock index 24f88cf132469..7479fd1c04ecc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -19455,6 +19455,43 @@ dependencies = [ "substrate-test-runtime-client", ] +[[package]] +name = "sc-dkg" +version = "1.0.0" +dependencies = [ + "array-bytes 6.2.2", + "async-channel 1.9.0", + "async-trait", + "futures", + "log", + "parity-scale-codec", + "parking_lot 0.12.3", + "sc-block-builder", + "sc-client-api", + "sc-consensus", + "sc-network", + "sc-network-gossip", + "sc-network-sync", + "sc-network-test", + "sc-network-types", + "sc-utils", + "serde", + "sp-api 26.0.0", + "sp-application-crypto 30.0.0", + "sp-arithmetic 23.0.0", + "sp-blockchain", + "sp-consensus", + "sp-core 28.0.0", + "sp-keystore 0.34.0", + "sp-runtime 31.0.1", + "sp-tracing 16.0.0", + "substrate-prometheus-endpoint", + "substrate-test-runtime-client", + "thiserror 1.0.65", + "tokio", + "wasm-timer", +] + [[package]] name = "sc-executor" version = "0.32.0" diff --git a/Cargo.toml b/Cargo.toml index 3c25cdda2bd27..ed4ffd08fffd2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -278,6 +278,7 @@ members = [ "substrate/client/consensus/pow", "substrate/client/consensus/slots", "substrate/client/db", + "substrate/client/dkg", "substrate/client/executor", "substrate/client/executor/common", "substrate/client/executor/polkavm", diff --git a/substrate/client/dkg/Cargo.toml b/substrate/client/dkg/Cargo.toml new file mode 100644 index 0000000000000..de437a59cb583 --- /dev/null +++ b/substrate/client/dkg/Cargo.toml @@ -0,0 +1,63 @@ +[package] +name = "sc-dkg" +version = "1.0.0" +authors.workspace = true +edition.workspace = true +license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +repository.workspace = true +description = "DKG Client gadget for substrate" +homepage.workspace = true + +[lints] +workspace = true + +[dependencies] +array-bytes = { workspace = true, default-features = true } +async-channel = { workspace = true } +async-trait = { workspace = true } +codec = { features = ["derive"], workspace = true, default-features = true } +futures = { workspace = true } +log = { workspace = true, default-features = true } +parking_lot = { workspace = true, default-features = true } +prometheus-endpoint = { workspace = true, default-features = true } +rand = { workspace = true } +scale-info = { features = ["derive"], workspace = true } +sc-client-api = { workspace = true, default-features = true } +sc-consensus = { workspace = true, default-features = true } +sc-network = { workspace = true, default-features = true } +sc-network-gossip = { workspace = true, default-features = true } +sc-network-sync = { workspace = true, default-features = true } +sc-network-types = { workspace = true, default-features = true } +sc-utils = { workspace = true, default-features = true } +sp-api = { workspace = true, default-features = true } +sp-application-crypto = { workspace = true, default-features = true } +sp-arithmetic = { workspace = true, default-features = true } +sp-blockchain = { workspace = true, default-features = true } +sp-consensus = { workspace = true, default-features = true } +sp-core = { workspace = true, default-features = true } +sp-keystore = { workspace = true, default-features = true } +sp-runtime = { workspace = true, default-features = true } +thiserror = { workspace = true } +tokio = { workspace = true, default-features = true } +wasm-timer = { workspace = true } + +adkg-vrf = { git = "https://github.com/w3f/adkg-vrf.git" } + +ark-ec = { version = "0.5", default-features = false } +ark-poly = { version = "0.5", default-features = false } + + +[dev-dependencies] +sc-block-builder = { workspace = true, default-features = true } +sc-network-test = { workspace = true } +serde = { workspace = true, default-features = true } +sp-tracing = { workspace = true, default-features = true } +substrate-test-runtime-client = { workspace = true } + +[features] +# This feature adds BLS crypto primitives. It should not be used in production since +# the BLS implementation and interface may still be subject to significant change. +bls-experimental = [ + "sp-application-crypto/bls-experimental", + "sp-core/bls-experimental", +] diff --git a/substrate/client/dkg/src/communication/gossip.rs b/substrate/client/dkg/src/communication/gossip.rs new file mode 100644 index 0000000000000..50edf20c64ba8 --- /dev/null +++ b/substrate/client/dkg/src/communication/gossip.rs @@ -0,0 +1,143 @@ +use crate::{ + communication::{benefit, cost, peers::KnownPeers, notification::Dealing}, + LOG_TARGET, +}; + +use sp_runtime::traits::Hash; + +use std::{fmt::Display, marker::PhantomData, sync::Arc, time::Duration}; + +use sp_application_crypto::{AppPublic, RuntimeAppPublic}; + +use codec::{Encode, Decode, DecodeWithMemTracking}; +use scale_info::TypeInfo; + +use sc_network::{NetworkPeers, ReputationChange}; + +use parking_lot::{Mutex, RwLock}; +use wasm_timer::Instant; + +const REBROADCAST_AFTER: Duration = Duration::from_secs(5); + +#[derive(Debug, PartialEq)] +pub(super) enum Action { + // repropagate under given topic, to the given peers, applying cost/benefit to originator. + Keep(H, ReputationChange), + // discard, applying cost/benefit to originator. + Discard(ReputationChange), + // ignore, no cost/benefit applied to originator. + DiscardNoReport, +} + +/// An outcome of examining a message. +#[derive(Debug, PartialEq, Clone, Copy)] +enum Consider { + /// Accept the message. + Accept, + /// Message cannot be evaluated. Reject. + CannotEvaluate, +} + +/// DKG Dealing Message. +/// +/// A Dealing message is the dealing of a single participant in our DKG scheme with a signature attached +#[derive(Clone, Debug, Decode, DecodeWithMemTracking, Encode, PartialEq, TypeInfo)] +pub struct DealingMessage { + /// Commit to information extracted from a finalized block + pub dealing: Dealing, + /// Node authority id + pub id: Id, + /// Node signature + pub signature: Signature, +} + +/// DKG gossip message type that gets encoded and sent on the network. +#[derive(Debug, Encode, Decode)] +pub(crate) enum GossipMessage { + /// DKG message with commitment and single signature. + Dealing(DealingMessage>, AuthorityId, ::Signature>), + // TODO: Add another message? + Temp, +} + +pub trait AuthorityIdBound: + Ord + + AppPublic + + Display + + RuntimeAppPublic +{ + +} + +impl GossipMessage { + pub fn unwrap_dealing( + self + ) -> Option>, AuthorityId, ::Signature>> { + match self { + GossipMessage::Dealing(dealing) => Some(dealing), + GossipMessage::Temp => None, + } + } +} + +/// Gossip engine dealings messages topic +pub(crate) fn dealings_topic() -> H::Output +where + H: Hash, +{ + H::hash_of(b"dkg-dealing") +} + +// TEMP replace with BLS-381.. +// TODO: Goes in a primitive folder for DKG +/// DKG cryptographic types for ECDSA crypto +/// +/// This module basically introduces four crypto types: +/// - `ecdsa_crypto::Pair` +/// - `ecdsa_crypto::Public` +/// - `ecdsa_crypto::Signature` +/// - `ecdsa_crypto::AuthorityId` +/// +/// Your code should use the above types as concrete types for all crypto related +/// functionality. +pub mod ecdsa_crypto { + use super::{AuthorityIdBound, RuntimeAppPublic}; + use sp_application_crypto::{app_crypto, ecdsa}; + + // TODO: Put this in Keytypes module in crypto similar to babe beefy etc.. + // sp_application_crypto::key_types + /// Key type for DKG module. + pub const DKG: sp_core::crypto::KeyTypeId = sp_core::crypto::KeyTypeId(*b"dkgg"); + + app_crypto!(ecdsa, DKG); + + /// Identity of a DKG authority using ECDSA as its crypto. + pub type AuthorityId = Public; + + /// Signature for a DKG authority using ECDSA as its crypto. + pub type AuthoritySignature = Signature; + + impl AuthorityIdBound for AuthorityId { + + } +} + + +// TODO: Add Gossip Filters ? Which to Add? + +pub struct Filter(PhantomData); + + +/// DKG gossip validator +/// +/// Validate DKG gossip messages and produce dealings. +pub(crate) struct GossipValidator +where + H: Hash +{ + dealings_topic: H, + gossip_filter: RwLock>, + next_rebroadcast: Mutex, + known_peers: Arc>>, + network: Arc, +} \ No newline at end of file diff --git a/substrate/client/dkg/src/communication/mod.rs b/substrate/client/dkg/src/communication/mod.rs new file mode 100644 index 0000000000000..c4acbe1f58494 --- /dev/null +++ b/substrate/client/dkg/src/communication/mod.rs @@ -0,0 +1,83 @@ +pub mod notification; + +pub(crate) mod gossip; +pub(crate) mod peers; + +pub(crate) mod dkg_protocol_name { + use array_bytes::bytes2hex; + use sc_network::ProtocolName; + + // TODO: What are we exactly gossiping to other validators in the DKG? What are we sending? Are we broadcasting something or just sending to individuals? + /// DKG dealings gossip protocol name suffix. + const GOSSIP_NAME: &str = "/dkg/2"; + + /// Name of the dealings gossip protocol used by the DKG. + /// + /// Must be registered towards the networking in order for DKG participant to properly function. + pub fn gossip_protocol_name>( + genesis_hash: Hash, + // TODO: Do we need the Fork ID here? + fork_id: Option<&str>, + ) -> ProtocolName { + let genesis_hash = genesis_hash.as_ref(); + if let Some(fork_id) = fork_id { + format!("/{}/{}{}", bytes2hex("", genesis_hash), fork_id, GOSSIP_NAME).into() + } else { + format!("/{}{}", bytes2hex("", genesis_hash), GOSSIP_NAME).into() + } + } +} + +/// Returns the configuration value to put in +/// [`sc_network::config::FullNetworkConfiguration`]. +/// For standard protocol name see [`dkg_protocol_name::gossip_protocol_name`]. +pub fn dkg_peers_set_config< + B: sp_runtime::traits::Block, + N: sc_network::NetworkBackend::Hash>, +>( + gossip_protocol_name: sc_network::ProtocolName, + metrics: sc_network::service::NotificationMetrics, + peer_store_handle: std::sync::Arc, +) -> (N::NotificationProtocolConfig, Box) { + let (cfg, notification_service) = N::notification_config( + gossip_protocol_name, + Vec::new(), + 1024 * 1024, + None, + sc_network::config::SetConfig { + in_peers: 25, // TODO: This should be all the validators so how do we change this to be all validators? + out_peers: 25, // TODO: This should be all the validators so how do we change this to be all validators? + reserved_nodes: Vec::new(), + non_reserved_mode: sc_network::config::NonReservedPeerMode::Accept, + }, + metrics, + peer_store_handle, + ); + (cfg, notification_service) +} + +// cost scalars for reporting peers. +mod cost { + use sc_network::ReputationChange as Rep; + // Message that's for an outdated round. + pub(super) const OUTDATED_MESSAGE: Rep = Rep::new(-50, "DKG: Past message"); + // Message that's from the future relative to our current set-id. + pub(super) const FUTURE_MESSAGE: Rep = Rep::new(-100, "DKG: Future message"); + // DKG message dealing containing bad signature. + pub(super) const BAD_SIGNATURE: Rep = Rep::new(-100, "DKG: Bad signature"); + // Message received with dealing from participant not in validator set. + pub(super) const UNKNOWN_PARTICIPANT: Rep = Rep::new(-150, "DKG: Unknown participant"); + // Message containing invalid dealing. + pub(super) const INVALID_DEALING: Rep = Rep::new(-5000, "DKG: Invalid dealing"); + // Reputation cost per signature checked for invalid dealing. + pub(super) const PER_SIGNATURE_CHECKED: i32 = -25; + // Reputation cost per byte for un-decodable message. + pub(super) const PER_UNDECODABLE_BYTE: i32 = -5; +} + +// benefit scalars for reporting peers. +mod benefit { + use sc_network::ReputationChange as Rep; + pub(super) const DEALING_MESSAGE: Rep = Rep::new(100, "DKG: dealing message"); + pub(super) const NOT_INTERESTED: Rep = Rep::new(10, "DKG: Not interested in round"); +} \ No newline at end of file diff --git a/substrate/client/dkg/src/communication/notification.rs b/substrate/client/dkg/src/communication/notification.rs new file mode 100644 index 0000000000000..b7fcbaaabc04e --- /dev/null +++ b/substrate/client/dkg/src/communication/notification.rs @@ -0,0 +1,26 @@ +use sc_utils::notification::{NotificationSender, NotificationStream, TracingKeyStr}; +use sp_runtime::{scale_info::TypeInfo, traits::Hash}; + +// TODO: Put this somewhere else +/// DKG Dealing +#[derive(Clone, Debug, PartialEq, Eq, TypeInfo)] +pub struct Dealing { + pub dealing: T, +} + +/// The sending half of the notifications channel(s) used to send +/// notifications about DKG dealing. +pub type DkgDealingSender = NotificationSender>>; + +/// The receiving half of a notifications channel used to receive +/// notifications about DKG dealings. +pub type DkgDealingReceiver = + NotificationStream>, DkgDealingTracingKey>; + +/// Provides tracing key for DKG dealing stream. +#[derive(Clone)] +pub struct DkgDealingTracingKey; +impl TracingKeyStr for DkgDealingTracingKey { + const TRACING_KEY: &'static str = "mpsc_dkg_dealing_notification_stream"; +} + diff --git a/substrate/client/dkg/src/communication/peers.rs b/substrate/client/dkg/src/communication/peers.rs new file mode 100644 index 0000000000000..cacdc1af4a9c5 --- /dev/null +++ b/substrate/client/dkg/src/communication/peers.rs @@ -0,0 +1,58 @@ +use sc_network::ReputationChange; +use sc_network_types::PeerId; +use sp_runtime::traits::Hash; +use std::collections::HashMap; + +/// Report specifying a reputation change for a given peer. +#[derive(Debug, PartialEq)] +pub struct PeerReport { + pub who: PeerId, + pub cost_benefit: ReputationChange, +} + +struct PeerData { + // TODO: Is having a Hash enough information about a dealing of a peer? + last_dealing: H::Output, +} + +impl Default for PeerData +where + H::Output: Default +{ + fn default() -> Self { + PeerData { last_dealing: H::Output::default() } + } +} + +/// Keep a simple map of connected peers +/// and the most recent DKG dealing they provided. +pub struct KnownPeers { + live: HashMap>, +} + +impl KnownPeers { + pub fn new() -> Self { + Self { live: HashMap::new() } + } + + /// Note dealing for `peer`. + pub fn note_vote_for(&mut self, peer: PeerId, dealing: H::Output) { + let data = self.live.entry(peer).or_default(); + data.last_dealing = dealing; + } + + /// Remove connected `peer`. + pub fn remove(&mut self, peer: &PeerId) { + self.live.remove(peer); + } + + /// Answer whether `peer` is part of `KnownPeers` set. + pub fn contains(&self, peer: &PeerId) -> bool { + self.live.contains_key(peer) + } + + /// Number of peers in the set. + pub fn len(&self) -> usize { + self.live.len() + } +} \ No newline at end of file diff --git a/substrate/client/dkg/src/lib.rs b/substrate/client/dkg/src/lib.rs new file mode 100644 index 0000000000000..fa8aff451508e --- /dev/null +++ b/substrate/client/dkg/src/lib.rs @@ -0,0 +1,58 @@ +pub mod communication; + +use std::{sync::Arc, marker::PhantomData}; +use rand::rngs::OsRng; +use rand::RngCore; +use sc_network::{NotificationService, ProtocolName}; +use adkg_vrf::dkg::transcript::{DkgTranscript}; +use adkg_vrf::dkg::{Ceremony, DkgResult}; +use adkg_vrf::dkg::dealer; + +use adkg_vrf::bls::threshold::ThresholdVk; +use adkg_vrf::bls::vanilla::BlsSigner; + +use ark_ec::pairing::Pairing; +use ark_ec::{CurveGroup, PrimeGroup}; +use ark_poly::GeneralEvaluationDomain; + +#[cfg(feature = "bls-experimental")] +use sp_core::bls::bls381::*; + +const LOG_TARGET: &str = "dkg"; + +/// DKG gadget network parameters. +pub struct DkgNetworkParams { + /// Network implementing gossip, requests and sync-oracle. + pub network: Arc, + /// Syncing service implementing a sync oracle and an event stream for peers. + pub sync: Arc, + /// Handle for receiving notification events. + pub notification_service: Box, + /// Chain specific DKG gossip protocol name. See + /// [`communication::dkg_protocol_name::gossip_protocol_name`]. + pub gossip_protocol_name: ProtocolName, + + pub _phantom: PhantomData<(N, S)>, +} + +pub fn perform_dealing() -> Option> { + let mut os_rng = OsRng; + + // TODO: Get this from somewhere + let num_validators = 42; + let f = num_validators; + + let (n, t, k) = (3 * f + 1, 2 * f + 1, f + 1); + // TODO: Add real BLS keys.. These will be the bls keys for each validator + let signers: Vec> = (0..n) + .map(|_| BlsSigner::new(C::G2::generator(), &mut os_rng)) + .collect(); + let signers_pks: Vec<_> = signers.iter() + .map(|s| s.bls_pk_g2) + .collect(); + let params = Ceremony::>::setup(t, &signers_pks); + let transcript = params.deal(&mut os_rng); + + Some(transcript) +} +