From 31874ceeae6dc6dfafb66080efc7673065a2fc76 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Wed, 5 Nov 2025 18:20:23 -0500 Subject: [PATCH] serai-node which compiles and produces/finalizes blocks with `--dev` --- substrate/core/src/lib.rs | 22 +++-- substrate/node/src/chain_spec.rs | 54 +++++----- substrate/node/src/keystore.rs | 6 ++ substrate/node/src/service.rs | 9 +- substrate/primitives/Cargo.toml | 11 ++- substrate/primitives/src/crypto.rs | 153 +++++++++++++++++++++++++++-- substrate/runtime/src/lib.rs | 30 ++++-- 7 files changed, 232 insertions(+), 53 deletions(-) diff --git a/substrate/core/src/lib.rs b/substrate/core/src/lib.rs index 3abaa263..9b88713a 100644 --- a/substrate/core/src/lib.rs +++ b/substrate/core/src/lib.rs @@ -98,6 +98,13 @@ pub mod pallet { }); } + /// The code to run on genesis. + pub fn genesis() { + BlocksCommitmentMerkle::::new_expecting_none(); + BlockTransactionsCommitmentMerkle::::new_expecting_none(); + BlockEventsCommitmentMerkle::::new_expecting_none(); + } + /// The code to run when beginning execution of a transaction. /// /// The caller MUST ensure two transactions aren't simultaneously started. @@ -126,6 +133,7 @@ pub mod pallet { /// Fetch all of Serai's events. /// /// This MUST only be used for testing purposes. + #[cfg(any(feature = "std", feature = "runtime-benchmarks", test))] pub fn events() -> Vec where serai_abi::Event: TryFrom, @@ -144,15 +152,13 @@ pub struct StartOfBlock(PhantomData); impl frame_support::traits::PreInherents for StartOfBlock { fn pre_inherents() { use frame_support::pallet_prelude::Zero; + // `Pallet::genesis` is expected to be used for the genesis block + assert!(!frame_system::Pallet::::block_number().is_zero()); - if frame_system::Pallet::::block_number().is_zero() { - BlocksCommitmentMerkle::::new_expecting_none(); - } else { - let parent_hash = frame_system::Pallet::::parent_hash(); - Blocks::::set(parent_hash, Some(())); - let parent_hash: [u8; 32] = parent_hash.into(); - BlocksCommitmentMerkle::::append(&parent_hash); - } + let parent_hash = frame_system::Pallet::::parent_hash(); + Blocks::::set(parent_hash, Some(())); + let parent_hash: [u8; 32] = parent_hash.into(); + BlocksCommitmentMerkle::::append(&parent_hash); BlockTransactionsCommitmentMerkle::::new_expecting_none(); BlockEventsCommitmentMerkle::::new_expecting_none(); diff --git a/substrate/node/src/chain_spec.rs b/substrate/node/src/chain_spec.rs index f2ab2f06..2b47dff5 100644 --- a/substrate/node/src/chain_spec.rs +++ b/substrate/node/src/chain_spec.rs @@ -1,12 +1,15 @@ -#![expect(unused_imports, dead_code)] - use core::marker::PhantomData; use sp_core::Pair as PairTrait; use sc_service::ChainType; -use ciphersuite::{group::GroupEncoding, Ciphersuite}; +use rand_core::OsRng; +use zeroize::Zeroizing; +use ciphersuite::{ + group::{ff::Field, GroupEncoding}, + WrappedGroup, Ciphersuite, +}; use embedwards25519::Embedwards25519; use secq256k1::Secq256k1; @@ -25,15 +28,28 @@ fn insecure_account_from_name(name: &'static str) -> Public { sp_core::sr25519::Pair::from_string(&format!("//{name}"), None).unwrap().public().into() } -fn insecure_arbitrary_public_key_from_name(name: &'static str) -> Vec { - let key = C::hash_to_F(name.as_bytes()); - (C::generator() * key).to_bytes().as_ref().to_vec() -} - -fn insecure_embedded_elliptic_curve_keys_from_name( +fn insecure_embedded_elliptic_curve_keys( name: &'static str, ) -> Vec { - vec![] // TODO + vec![ + SignedEmbeddedEllipticCurveKeys::bitcoin( + &mut OsRng, + insecure_account_from_name(name), + &Zeroizing::new(::F::random(&mut OsRng)), + &Zeroizing::new(::F::random(&mut OsRng)), + ), + SignedEmbeddedEllipticCurveKeys::ethereum( + &mut OsRng, + insecure_account_from_name(name), + &Zeroizing::new(::F::random(&mut OsRng)), + &Zeroizing::new(::F::random(&mut OsRng)), + ), + SignedEmbeddedEllipticCurveKeys::monero( + &mut OsRng, + insecure_account_from_name(name), + &Zeroizing::new(::F::random(&mut OsRng)), + ), + ] } fn wasm_binary() -> Vec { @@ -54,9 +70,7 @@ fn devnet_genesis( ) -> RuntimeGenesisConfig { let validators = validators .iter() - .map(|name| { - (insecure_account_from_name(name), insecure_embedded_elliptic_curve_keys_from_name(name)) - }) + .map(|name| (insecure_account_from_name(name), insecure_embedded_elliptic_curve_keys(name))) .collect::>(); RuntimeGenesisConfig { @@ -80,20 +94,12 @@ fn devnet_genesis( }, signals: SignalsConfig::default(), babe: BabeConfig { - authorities: validators - .iter() - .map(|validator| (sp_core::sr25519::Public::from(validator.0).into(), 1)) - .collect(), + // We leave this empty as `serai-validator-sets-pallet` initializes the authorities + authorities: vec![], epoch_config: BABE_GENESIS_EPOCH_CONFIG, _config: PhantomData, }, - grandpa: GrandpaConfig { - authorities: validators - .into_iter() - .map(|validator| (sp_core::sr25519::Public::from(validator.0).into(), 1)) - .collect(), - _config: PhantomData, - }, + grandpa: GrandpaConfig { authorities: vec![], _config: PhantomData }, } } diff --git a/substrate/node/src/keystore.rs b/substrate/node/src/keystore.rs index e60d534b..1673950b 100644 --- a/substrate/node/src/keystore.rs +++ b/substrate/node/src/keystore.rs @@ -5,6 +5,12 @@ use sp_keystore::*; pub struct Keystore(sr25519::Pair); +impl From for Keystore { + fn from(keypair: sr25519::Pair) -> Self { + Self(keypair) + } +} + impl Keystore { pub fn from_env() -> Option { let mut key_hex = serai_env::var("KEY")?; diff --git a/substrate/node/src/service.rs b/substrate/node/src/service.rs index 9829ddff..b60051ff 100644 --- a/substrate/node/src/service.rs +++ b/substrate/node/src/service.rs @@ -92,6 +92,11 @@ pub fn new_partial( let keystore: Arc = if let Some(keystore) = crate::keystore::Keystore::from_env() { Arc::new(keystore) + } else if let Some(seed) = config.dev_key_seed.as_ref() { + Arc::new(crate::keystore::Keystore::from( + ::from_string(seed, None) + .expect("dev key had invalid seed"), + )) } else { keystore_container.keystore() }; @@ -288,10 +293,6 @@ pub fn new_full(mut config: Configuration) -> Result let role = config.role; let keystore = keystore_container; - if let Some(seed) = config.dev_key_seed.as_ref() { - let _ = - keystore.sr25519_generate_new(sp_core::crypto::key_types::AUTHORITY_DISCOVERY, Some(seed)); - } let prometheus_registry = config.prometheus_registry().cloned(); // TODO: Ensure we're considered as an authority is a validator of an external network diff --git a/substrate/primitives/Cargo.toml b/substrate/primitives/Cargo.toml index f1698ab2..90bcb035 100644 --- a/substrate/primitives/Cargo.toml +++ b/substrate/primitives/Cargo.toml @@ -16,6 +16,8 @@ rustdoc-args = ["--cfg", "docsrs"] workspace = true [dependencies] +rand_core = { version = "0.6", default-features = false } + zeroize = { version = "^1.5", features = ["derive"] } borsh = { version = "1", default-features = false, features = ["derive", "de_strict_order"] } @@ -34,23 +36,26 @@ schnorrkel = { version = "0.11", default-features = false } bech32 = { version = "0.11", default-features = false } -[dev-dependencies] -rand_core = { version = "0.6", default-features = false, features = ["std"] } - [features] std = [ + "rand_core/std", + "zeroize/std", "borsh/std", + "bitvec/std", "scale?/std", "sp-core/std", + "ciphersuite/std", "schnorr-signatures/std", "dalek-ff-group/std", "embedwards25519/std", "secq256k1/std", "dkg/std", + "schnorrkel/std", + "bech32/std" ] serde = [] diff --git a/substrate/primitives/src/crypto.rs b/substrate/primitives/src/crypto.rs index 699e991d..d4785487 100644 --- a/substrate/primitives/src/crypto.rs +++ b/substrate/primitives/src/crypto.rs @@ -1,10 +1,17 @@ -use zeroize::Zeroize; +use core::ops::Deref; + +use rand_core::{RngCore, CryptoRng}; + +use zeroize::{Zeroize, Zeroizing}; use borsh::{io, BorshSerialize, BorshDeserialize}; use sp_core::{ConstU32, bounded::BoundedVec}; use ciphersuite::{ - group::{ff::FromUniformBytes, GroupEncoding}, + group::{ + ff::{Field, PrimeField, FromUniformBytes}, + GroupEncoding, + }, WrappedGroup, GroupCanonicalEncoding, }; use embedwards25519::Embedwards25519; @@ -211,9 +218,7 @@ impl SignedEmbeddedEllipticCurveKeys { } } - /// Verify these key(s)' signature(s), returning the key(s) if valid. - pub fn verify(self, validator: Public) -> Option { - // Sample a unified challenge + fn transcript(&self, validator: Public) -> [u8; 64] { let transcript = match &self { Self::Bitcoin(e, s, e_sig, s_sig) => [ [ExternalNetworkId::Bitcoin as u8].as_slice(), @@ -237,7 +242,12 @@ impl SignedEmbeddedEllipticCurveKeys { [[ExternalNetworkId::Monero as u8].as_slice(), &validator.0, e, &e_sig[.. 32]].concat() } }; - let challenge = sp_core::hashing::blake2_512(&transcript); + sp_core::hashing::blake2_512(&transcript) + } + + /// Verify these key(s)' signature(s), returning the key(s) if valid. + pub fn verify(self, validator: Public) -> Option { + let challenge = self.transcript(validator); // Verify the Schnorr signatures match &self { @@ -275,6 +285,137 @@ impl SignedEmbeddedEllipticCurveKeys { Self::Monero(e, _) => EmbeddedEllipticCurveKeys::Monero(e), }) } + + #[doc(hidden)] + pub fn bitcoin( + rng: &mut (impl RngCore + CryptoRng), + validator: Public, + embedwards25519: &Zeroizing<::F>, + secq256k1: &Zeroizing<::F>, + ) -> Self { + let em_public_key = + (::generator() * embedwards25519.deref()).to_bytes(); + let em_nonce = Zeroizing::new(::F::random(&mut *rng)); + let em_nonce_commitment = ::generator() * em_nonce.deref(); + let mut em_sig = [0; 64]; + em_sig[.. 32].copy_from_slice(em_nonce_commitment.to_bytes().as_ref()); + + let secq_public_key = (::generator() * secq256k1.deref()).to_bytes(); + let secq_nonce = Zeroizing::new(::F::random(&mut *rng)); + let secq_nonce_commitment = ::generator() * secq_nonce.deref(); + let mut secq_sig = [0; 65]; + secq_sig[.. 33].copy_from_slice(secq_nonce_commitment.to_bytes().as_ref()); + + let challenge = + SignedEmbeddedEllipticCurveKeys::Bitcoin(em_public_key, secq_public_key, em_sig, secq_sig) + .transcript(validator); + + em_sig[32 ..].copy_from_slice( + SchnorrSignature::::sign( + embedwards25519, + em_nonce, + <::F as FromUniformBytes<_>>::from_uniform_bytes( + &challenge, + ), + ) + .s + .to_repr() + .as_ref(), + ); + + secq_sig[33 ..].copy_from_slice( + SchnorrSignature::::sign( + secq256k1, + secq_nonce, + <::F as FromUniformBytes<_>>::from_uniform_bytes(&challenge), + ) + .s + .to_repr() + .as_ref(), + ); + + SignedEmbeddedEllipticCurveKeys::Bitcoin(em_public_key, secq_public_key, em_sig, secq_sig) + } + + #[doc(hidden)] + pub fn ethereum( + rng: &mut (impl RngCore + CryptoRng), + validator: Public, + embedwards25519: &Zeroizing<::F>, + secq256k1: &Zeroizing<::F>, + ) -> Self { + let em_public_key = + (::generator() * embedwards25519.deref()).to_bytes(); + let em_nonce = Zeroizing::new(::F::random(&mut *rng)); + let em_nonce_commitment = ::generator() * em_nonce.deref(); + let mut em_sig = [0; 64]; + em_sig[.. 32].copy_from_slice(em_nonce_commitment.to_bytes().as_ref()); + + let secq_public_key = (::generator() * secq256k1.deref()).to_bytes(); + let secq_nonce = Zeroizing::new(::F::random(&mut *rng)); + let secq_nonce_commitment = ::generator() * secq_nonce.deref(); + let mut secq_sig = [0; 65]; + secq_sig[.. 33].copy_from_slice(secq_nonce_commitment.to_bytes().as_ref()); + + let challenge = + SignedEmbeddedEllipticCurveKeys::Ethereum(em_public_key, secq_public_key, em_sig, secq_sig) + .transcript(validator); + + em_sig[32 ..].copy_from_slice( + SchnorrSignature::::sign( + embedwards25519, + em_nonce, + <::F as FromUniformBytes<_>>::from_uniform_bytes( + &challenge, + ), + ) + .s + .to_repr() + .as_ref(), + ); + + secq_sig[33 ..].copy_from_slice( + SchnorrSignature::::sign( + secq256k1, + secq_nonce, + <::F as FromUniformBytes<_>>::from_uniform_bytes(&challenge), + ) + .s + .to_repr() + .as_ref(), + ); + + SignedEmbeddedEllipticCurveKeys::Ethereum(em_public_key, secq_public_key, em_sig, secq_sig) + } + + #[doc(hidden)] + pub fn monero( + rng: &mut (impl RngCore + CryptoRng), + validator: Public, + embedwards25519: &Zeroizing<::F>, + ) -> Self { + let em_public_key = + (::generator() * embedwards25519.deref()).to_bytes(); + let em_nonce = Zeroizing::new(::F::random(&mut *rng)); + let em_nonce_commitment = ::generator() * em_nonce.deref(); + let mut em_sig = [0; 64]; + em_sig[.. 32].copy_from_slice(em_nonce_commitment.to_bytes().as_ref()); + let challenge = + SignedEmbeddedEllipticCurveKeys::Monero(em_public_key, em_sig).transcript(validator); + em_sig[32 ..].copy_from_slice( + SchnorrSignature::::sign( + embedwards25519, + em_nonce, + <::F as FromUniformBytes<_>>::from_uniform_bytes( + &challenge, + ), + ) + .s + .to_repr() + .as_ref(), + ); + SignedEmbeddedEllipticCurveKeys::Monero(em_public_key, em_sig) + } } impl BorshSerialize for SignedEmbeddedEllipticCurveKeys { diff --git a/substrate/runtime/src/lib.rs b/substrate/runtime/src/lib.rs index 8b2ed48f..a9f47653 100644 --- a/substrate/runtime/src/lib.rs +++ b/substrate/runtime/src/lib.rs @@ -57,12 +57,18 @@ frame_support::parameter_types! { // TODO pub BlockLength: frame_system::limits::BlockLength = - frame_system::limits::BlockLength::max_with_normal_ratio(0, Perbill::from_percent(0)); + frame_system::limits::BlockLength::max_with_normal_ratio( + 100 * 1024, + Perbill::from_percent(75), + ); // TODO pub BlockWeights: frame_system::limits::BlockWeights = frame_system::limits::BlockWeights::with_sensible_defaults( - Weight::from_parts(0, 0), - Perbill::from_percent(0), + Weight::from_parts( + 2u64 * frame_support::weights::constants::WEIGHT_REF_TIME_PER_SECOND, + u64::MAX, + ), + Perbill::from_percent(75), ); } @@ -161,7 +167,7 @@ impl serai_validator_sets_pallet::Config for Runtime { } impl serai_signals_pallet::Config for Runtime { type RetirementValidityDuration = ConstU64<0>; // TODO - type RetirementLockInDuration = ConstU64<0>; // TODO + type RetirementLockInDuration = ConstU64<1>; // TODO } impl serai_coins_pallet::Config for Runtime { type AllowMint = serai_coins_pallet::AlwaysAllowMint; @@ -177,7 +183,8 @@ impl serai_coins_pallet::Config for Runtime { impl pallet_timestamp::Config for Runtime { type Moment = u64; type OnTimestampSet = Babe; - type MinimumPeriod = ConstU64<0>; // TODO + // TODO + type MinimumPeriod = ConstU64<{ (6 * 1000) / 2 }>; type WeightInfo = (); } @@ -197,9 +204,10 @@ impl pallet_session::Config for Runtime { type MaxAuthorities = ConstU32<{ serai_abi::primitives::validator_sets::KeyShares::MAX_PER_SET_U32 }>; impl pallet_babe::Config for Runtime { - type EpochDuration = ConstU64<0>; // TODO + // TODO + type EpochDuration = ConstU64<{ (7 * 24 * 60 * 60 * 1000) / (6 * 1000) }>; - type ExpectedBlockTime = ConstU64<0>; // TODO + type ExpectedBlockTime = ConstU64<{ 6 * 1000 }>; // TODO type EpochChangeTrigger = pallet_babe::ExternalTrigger; type WeightInfo = (); @@ -341,7 +349,13 @@ pub const BABE_GENESIS_EPOCH_CONFIG: sp_consensus_babe::BabeEpochConfiguration = sp_api::impl_runtime_apis! { impl crate::GenesisApi for Runtime { fn build(genesis: RuntimeGenesisConfig) { - ::build(&genesis) + Core::genesis(); + Core::start_transaction(); + ::build(&genesis); + Core::end_transaction([0; 32]); + < + serai_core_pallet::EndOfBlock as frame_support::traits::PostTransactions + >::post_transactions(); } }