diff --git a/Cargo.lock b/Cargo.lock index c0920e4dc6659..26fb7c4ae1950 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5695,7 +5695,6 @@ dependencies = [ "scale-info", "sp-application-crypto", "sp-consensus-babe", - "sp-consensus-vrf", "sp-core", "sp-io", "sp-runtime", @@ -8594,7 +8593,6 @@ dependencies = [ "fork-tree", "futures", "log", - "merlin", "num-bigint", "num-rational", "num-traits", @@ -8611,7 +8609,6 @@ dependencies = [ "sc-network-test", "sc-telemetry", "scale-info", - "schnorrkel", "sp-api", "sp-application-crypto", "sp-block-builder", @@ -8619,7 +8616,6 @@ dependencies = [ "sp-consensus", "sp-consensus-babe", "sp-consensus-slots", - "sp-consensus-vrf", "sp-core", "sp-inherents", "sp-keyring", @@ -10361,7 +10357,6 @@ name = "sp-consensus-babe" version = "0.10.0-dev" dependencies = [ "async-trait", - "merlin", "parity-scale-codec", "scale-info", "serde", @@ -10369,7 +10364,6 @@ dependencies = [ "sp-application-crypto", "sp-consensus", "sp-consensus-slots", - "sp-consensus-vrf", "sp-core", "sp-inherents", "sp-keystore", @@ -10437,18 +10431,6 @@ dependencies = [ "sp-timestamp", ] -[[package]] -name = "sp-consensus-vrf" -version = "0.10.0-dev" -dependencies = [ - "parity-scale-codec", - "scale-info", - "schnorrkel", - "sp-core", - "sp-runtime", - "sp-std", -] - [[package]] name = "sp-core" version = "7.0.0" @@ -10600,12 +10582,10 @@ name = "sp-keystore" version = "0.13.0" dependencies = [ "futures", - "merlin", "parity-scale-codec", "parking_lot 0.12.1", "rand 0.7.3", "rand_chacha 0.2.2", - "schnorrkel", "serde", "sp-core", "sp-externalities", diff --git a/Cargo.toml b/Cargo.toml index ae89750dd8f26..507d7621000b5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -190,7 +190,6 @@ members = [ "primitives/consensus/grandpa", "primitives/consensus/pow", "primitives/consensus/slots", - "primitives/consensus/vrf", "primitives/core", "primitives/core/hashing", "primitives/core/hashing/proc-macro", diff --git a/client/consensus/babe/Cargo.toml b/client/consensus/babe/Cargo.toml index d10ef21c4dad4..2382d064d0219 100644 --- a/client/consensus/babe/Cargo.toml +++ b/client/consensus/babe/Cargo.toml @@ -19,12 +19,10 @@ scale-info = { version = "2.5.0", features = ["derive"] } codec = { package = "parity-scale-codec", version = "3.2.2", features = ["derive"] } futures = "0.3.21" log = "0.4.17" -merlin = "2.0" num-bigint = "0.4.3" num-rational = "0.4.1" num-traits = "0.2.8" parking_lot = "0.12.1" -schnorrkel = { version = "0.9.1", features = ["preaudit_deprecated"] } thiserror = "1.0" fork-tree = { version = "3.0.0", path = "../../../utils/fork-tree" } prometheus-endpoint = { package = "substrate-prometheus-endpoint", version = "0.10.0-dev", path = "../../../utils/prometheus" } @@ -41,7 +39,6 @@ sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" sp-consensus = { version = "0.10.0-dev", path = "../../../primitives/consensus/common" } sp-consensus-babe = { version = "0.10.0-dev", path = "../../../primitives/consensus/babe" } sp-consensus-slots = { version = "0.10.0-dev", path = "../../../primitives/consensus/slots" } -sp-consensus-vrf = { version = "0.10.0-dev", path = "../../../primitives/consensus/vrf" } sp-core = { version = "7.0.0", path = "../../../primitives/core" } sp-inherents = { version = "4.0.0-dev", path = "../../../primitives/inherents" } sp-keystore = { version = "0.13.0", path = "../../../primitives/keystore" } diff --git a/client/consensus/babe/src/authorship.rs b/client/consensus/babe/src/authorship.rs index 3ff4c393ffd67..dc7cd10867745 100644 --- a/client/consensus/babe/src/authorship.rs +++ b/client/consensus/babe/src/authorship.rs @@ -18,17 +18,19 @@ //! BABE authority selection and slot claiming. -use super::Epoch; +use super::{Epoch, AUTHORING_SCORE_LENGTH, AUTHORING_SCORE_VRF_CONTEXT}; use codec::Encode; use sc_consensus_epochs::Epoch as EpochT; -use schnorrkel::{keys::PublicKey, vrf::VRFInOut}; use sp_application_crypto::AppCrypto; use sp_consensus_babe::{ digests::{PreDigest, PrimaryPreDigest, SecondaryPlainPreDigest, SecondaryVRFPreDigest}, - make_transcript, make_transcript_data, AuthorityId, BabeAuthorityWeight, Slot, BABE_VRF_PREFIX, + make_transcript, AuthorityId, BabeAuthorityWeight, Randomness, Slot, +}; +use sp_core::{ + blake2_256, + crypto::{ByteArray, Wraps}, + U256, }; -use sp_consensus_vrf::schnorrkel::{VRFOutput, VRFProof}; -use sp_core::{blake2_256, crypto::ByteArray, U256}; use sp_keystore::KeystorePtr; /// Calculates the primary selection threshold for a given authority, taking @@ -95,19 +97,13 @@ pub(super) fn calculate_primary_threshold( ) } -/// Returns true if the given VRF output is lower than the given threshold, -/// false otherwise. -pub(super) fn check_primary_threshold(inout: &VRFInOut, threshold: u128) -> bool { - u128::from_le_bytes(inout.make_bytes::<[u8; 16]>(BABE_VRF_PREFIX)) < threshold -} - /// Get the expected secondary author for the given slot and with given /// authorities. This should always assign the slot to some authority unless the /// authorities list is empty. pub(super) fn secondary_slot_author( slot: Slot, authorities: &[(AuthorityId, BabeAuthorityWeight)], - randomness: [u8; 32], + randomness: Randomness, ) -> Option<&AuthorityId> { if authorities.is_empty() { return None @@ -152,18 +148,14 @@ fn claim_secondary_slot( for (authority_id, authority_index) in keys { if authority_id == expected_author { let pre_digest = if author_secondary_vrf { - let transcript_data = make_transcript_data(randomness, slot, epoch_index); - let result = keystore.sr25519_vrf_sign( - AuthorityId::ID, - authority_id.as_ref(), - transcript_data, - ); - if let Ok(Some(signature)) = result { + let transcript = make_transcript(randomness, slot, epoch_index); + let result = + keystore.sr25519_vrf_sign(AuthorityId::ID, authority_id.as_ref(), &transcript); + if let Ok(Some(vrf_signature)) = result { Some(PreDigest::SecondaryVRF(SecondaryVRFPreDigest { slot, - vrf_output: VRFOutput(signature.output), - vrf_proof: VRFProof(signature.proof), authority_index: *authority_index as u32, + vrf_signature, })) } else { None @@ -247,25 +239,28 @@ fn claim_primary_slot( epoch_index = epoch.clone_for_slot(slot).epoch_index; } - for (authority_id, authority_index) in keys { - let transcript = make_transcript(randomness, slot, epoch_index); - let transcript_data = make_transcript_data(randomness, slot, epoch_index); - let result = - keystore.sr25519_vrf_sign(AuthorityId::ID, authority_id.as_ref(), transcript_data); - if let Ok(Some(signature)) = result { - let public = PublicKey::from_bytes(&authority_id.to_raw_vec()).ok()?; - let inout = match signature.output.attach_input_hash(&public, transcript) { - Ok(inout) => inout, - Err(_) => continue, - }; + let transcript = make_transcript(randomness, slot, epoch_index); + for (authority_id, authority_index) in keys { + let result = keystore.sr25519_vrf_sign(AuthorityId::ID, authority_id.as_ref(), &transcript); + if let Ok(Some(vrf_signature)) = result { let threshold = calculate_primary_threshold(c, authorities, *authority_index); - if check_primary_threshold(&inout, threshold) { + + let can_claim = authority_id + .as_inner_ref() + .make_bytes::<[u8; AUTHORING_SCORE_LENGTH]>( + AUTHORING_SCORE_VRF_CONTEXT, + &transcript, + &vrf_signature.output, + ) + .map(|bytes| u128::from_le_bytes(bytes) < threshold) + .unwrap_or_default(); + + if can_claim { let pre_digest = PreDigest::Primary(PrimaryPreDigest { slot, - vrf_output: VRFOutput(signature.output), - vrf_proof: VRFProof(signature.proof), authority_index: *authority_index as u32, + vrf_signature, }); return Some((pre_digest, authority_id.clone())) diff --git a/client/consensus/babe/src/lib.rs b/client/consensus/babe/src/lib.rs index 6327c8c657bb6..219b52294952a 100644 --- a/client/consensus/babe/src/lib.rs +++ b/client/consensus/babe/src/lib.rs @@ -86,7 +86,6 @@ use futures::{ use log::{debug, info, log, trace, warn}; use parking_lot::Mutex; use prometheus_endpoint::Registry; -use schnorrkel::SignatureError; use sc_client_api::{ backend::AuxStore, AuxDataOperations, Backend as BackendT, FinalityNotification, @@ -134,7 +133,7 @@ pub use sp_consensus_babe::{ PrimaryPreDigest, SecondaryPlainPreDigest, }, AuthorityId, AuthorityPair, AuthoritySignature, BabeApi, BabeAuthorityWeight, BabeBlockWeight, - BabeConfiguration, BabeEpochConfiguration, ConsensusLog, BABE_ENGINE_ID, VRF_OUTPUT_LENGTH, + BabeConfiguration, BabeEpochConfiguration, ConsensusLog, Randomness, BABE_ENGINE_ID, }; pub use aux_schema::load_block_weight as block_weight; @@ -149,6 +148,12 @@ mod tests; const LOG_TARGET: &str = "babe"; +/// VRF context used for slots claiming lottery. +const AUTHORING_SCORE_VRF_CONTEXT: &[u8] = b"substrate-babe-vrf"; + +/// VRF output length for slots claiming lottery. +const AUTHORING_SCORE_LENGTH: usize = 16; + /// BABE epoch information #[derive(Decode, Encode, PartialEq, Eq, Clone, Debug, scale_info::TypeInfo)] pub struct Epoch { @@ -161,7 +166,7 @@ pub struct Epoch { /// The authorities and their weights. pub authorities: Vec<(AuthorityId, BabeAuthorityWeight)>, /// Randomness for this epoch. - pub randomness: [u8; VRF_OUTPUT_LENGTH], + pub randomness: Randomness, /// Configuration of the epoch. pub config: BabeEpochConfiguration, } @@ -308,12 +313,12 @@ pub enum Error { /// No secondary author expected. #[error("No secondary author expected.")] NoSecondaryAuthorExpected, - /// VRF verification of block by author failed - #[error("VRF verification of block by author {0:?} failed: threshold {1} exceeded")] - VRFVerificationOfBlockFailed(AuthorityId, u128), /// VRF verification failed - #[error("VRF verification failed: {0:?}")] - VRFVerificationFailed(SignatureError), + #[error("VRF verification failed")] + VrfVerificationFailed, + /// Primary slot threshold too low + #[error("VRF output rejected, threshold {0} exceeded")] + VrfThresholdExceeded(u128), /// Could not fetch parent header #[error("Could not fetch parent header: {0}")] FetchParentHeader(sp_blockchain::Error), diff --git a/client/consensus/babe/src/migration.rs b/client/consensus/babe/src/migration.rs index ec864b8e5510f..2b1396c41c268 100644 --- a/client/consensus/babe/src/migration.rs +++ b/client/consensus/babe/src/migration.rs @@ -18,7 +18,7 @@ use crate::{ AuthorityId, BabeAuthorityWeight, BabeConfiguration, BabeEpochConfiguration, Epoch, - NextEpochDescriptor, VRF_OUTPUT_LENGTH, + NextEpochDescriptor, Randomness, }; use codec::{Decode, Encode}; use sc_consensus_epochs::Epoch as EpochT; @@ -36,7 +36,7 @@ pub struct EpochV0 { /// The authorities and their weights. pub authorities: Vec<(AuthorityId, BabeAuthorityWeight)>, /// Randomness for this epoch. - pub randomness: [u8; VRF_OUTPUT_LENGTH], + pub randomness: Randomness, } impl EpochT for EpochV0 { diff --git a/client/consensus/babe/src/tests.rs b/client/consensus/babe/src/tests.rs index 7f8448d91991c..740ce63676374 100644 --- a/client/consensus/babe/src/tests.rs +++ b/client/consensus/babe/src/tests.rs @@ -20,10 +20,6 @@ use super::*; use authorship::claim_slot; -use rand_chacha::{ - rand_core::{RngCore, SeedableRng}, - ChaChaRng, -}; use sc_block_builder::{BlockBuilder, BlockBuilderProvider}; use sc_client_api::{backend::TransactionFor, BlockchainEvents, Finalizer}; use sc_consensus::{BoxBlockImport, BoxJustificationImport}; @@ -33,16 +29,13 @@ use sc_network_test::{Block as TestBlock, *}; use sp_application_crypto::key_types::BABE; use sp_consensus::{DisableProofRecording, NoNetwork as DummyOracle, Proposal}; use sp_consensus_babe::{ - inherents::InherentDataProvider, make_transcript, make_transcript_data, AllowedSlots, - AuthorityId, AuthorityPair, Slot, + inherents::InherentDataProvider, make_transcript, AllowedSlots, AuthorityId, AuthorityPair, + Slot, }; use sp_consensus_slots::SlotDuration; -use sp_consensus_vrf::schnorrkel::VRFOutput; use sp_core::crypto::Pair; use sp_keyring::Sr25519Keyring; -use sp_keystore::{ - testing::MemoryKeystore, vrf::make_transcript as transcript_from_data, Keystore, -}; +use sp_keystore::{testing::MemoryKeystore, Keystore}; use sp_runtime::{ generic::{Digest, DigestItem}, traits::Block as BlockT, @@ -637,24 +630,24 @@ fn claim_vrf_check() { PreDigest::Primary(d) => d, v => panic!("Unexpected pre-digest variant {:?}", v), }; - let transcript = make_transcript_data(&epoch.randomness.clone(), 0.into(), epoch.epoch_index); + let transcript = make_transcript(&epoch.randomness.clone(), 0.into(), epoch.epoch_index); let sign = keystore - .sr25519_vrf_sign(AuthorityId::ID, &public, transcript) + .sr25519_vrf_sign(AuthorityId::ID, &public, &transcript) .unwrap() .unwrap(); - assert_eq!(pre_digest.vrf_output, VRFOutput(sign.output)); + assert_eq!(pre_digest.vrf_signature.output, sign.output); // We expect a SecondaryVRF claim for slot 1 let pre_digest = match claim_slot(1.into(), &epoch, &keystore).unwrap().0 { PreDigest::SecondaryVRF(d) => d, v => panic!("Unexpected pre-digest variant {:?}", v), }; - let transcript = make_transcript_data(&epoch.randomness.clone(), 1.into(), epoch.epoch_index); + let transcript = make_transcript(&epoch.randomness.clone(), 1.into(), epoch.epoch_index); let sign = keystore - .sr25519_vrf_sign(AuthorityId::ID, &public, transcript) + .sr25519_vrf_sign(AuthorityId::ID, &public, &transcript) .unwrap() .unwrap(); - assert_eq!(pre_digest.vrf_output, VRFOutput(sign.output)); + assert_eq!(pre_digest.vrf_signature.output, sign.output); // Check that correct epoch index has been used if epochs are skipped (primary VRF) let slot = Slot::from(103); @@ -663,13 +656,13 @@ fn claim_vrf_check() { v => panic!("Unexpected claim variant {:?}", v), }; let fixed_epoch = epoch.clone_for_slot(slot); - let transcript = make_transcript_data(&epoch.randomness.clone(), slot, fixed_epoch.epoch_index); + let transcript = make_transcript(&epoch.randomness.clone(), slot, fixed_epoch.epoch_index); let sign = keystore - .sr25519_vrf_sign(AuthorityId::ID, &public, transcript) + .sr25519_vrf_sign(AuthorityId::ID, &public, &transcript) .unwrap() .unwrap(); assert_eq!(fixed_epoch.epoch_index, 11); - assert_eq!(claim.vrf_output, VRFOutput(sign.output)); + assert_eq!(claim.vrf_signature.output, sign.output); // Check that correct epoch index has been used if epochs are skipped (secondary VRF) let slot = Slot::from(100); @@ -678,13 +671,13 @@ fn claim_vrf_check() { v => panic!("Unexpected claim variant {:?}", v), }; let fixed_epoch = epoch.clone_for_slot(slot); - let transcript = make_transcript_data(&epoch.randomness.clone(), slot, fixed_epoch.epoch_index); + let transcript = make_transcript(&epoch.randomness.clone(), slot, fixed_epoch.epoch_index); let sign = keystore - .sr25519_vrf_sign(AuthorityId::ID, &public, transcript) + .sr25519_vrf_sign(AuthorityId::ID, &public, &transcript) .unwrap() .unwrap(); assert_eq!(fixed_epoch.epoch_index, 11); - assert_eq!(pre_digest.vrf_output, VRFOutput(sign.output)); + assert_eq!(pre_digest.vrf_signature.output, sign.output); } // Propose and import a new BABE block on top of the given parent. @@ -1084,36 +1077,6 @@ async fn verify_slots_are_strictly_increasing() { propose_and_import_block(&b1, Some(999.into()), &mut proposer_factory, &mut block_import).await; } -#[test] -fn babe_transcript_generation_match() { - sp_tracing::try_init_simple(); - - let authority = Sr25519Keyring::Alice; - let _keystore = create_keystore(authority); - - let epoch = Epoch { - start_slot: 0.into(), - authorities: vec![(authority.public().into(), 1)], - randomness: [0; 32], - epoch_index: 1, - duration: 100, - config: BabeEpochConfiguration { - c: (3, 10), - allowed_slots: AllowedSlots::PrimaryAndSecondaryPlainSlots, - }, - }; - - let orig_transcript = make_transcript(&epoch.randomness.clone(), 1.into(), epoch.epoch_index); - let new_transcript = make_transcript_data(&epoch.randomness, 1.into(), epoch.epoch_index); - - let test = |t: merlin::Transcript| -> [u8; 16] { - let mut b = [0u8; 16]; - t.build_rng().finalize(&mut ChaChaRng::from_seed([0u8; 32])).fill_bytes(&mut b); - b - }; - debug_assert!(test(orig_transcript) == test(transcript_from_data(new_transcript))); -} - #[tokio::test] async fn obsolete_blocks_aux_data_cleanup() { let mut net = BabeTestNet::new(1); diff --git a/client/consensus/babe/src/verification.rs b/client/consensus/babe/src/verification.rs index 96d3961d44571..cadceb6a57510 100644 --- a/client/consensus/babe/src/verification.rs +++ b/client/consensus/babe/src/verification.rs @@ -18,8 +18,9 @@ //! Verification for BABE headers. use crate::{ - authorship::{calculate_primary_threshold, check_primary_threshold, secondary_slot_author}, - babe_err, find_pre_digest, BlockT, Epoch, Error, LOG_TARGET, + authorship::{calculate_primary_threshold, secondary_slot_author}, + babe_err, find_pre_digest, BlockT, Epoch, Error, AUTHORING_SCORE_LENGTH, + AUTHORING_SCORE_VRF_CONTEXT, LOG_TARGET, }; use log::{debug, trace}; use sc_consensus_epochs::Epoch as EpochT; @@ -32,7 +33,10 @@ use sp_consensus_babe::{ make_transcript, AuthorityId, AuthorityPair, AuthoritySignature, }; use sp_consensus_slots::Slot; -use sp_core::{ByteArray, Pair}; +use sp_core::{ + crypto::{VrfVerifier, Wraps}, + Pair, +}; use sp_runtime::{traits::Header, DigestItem}; /// BABE verification parameters @@ -155,7 +159,7 @@ fn check_primary_header( epoch: &Epoch, c: (u64, u64), ) -> Result<(), Error> { - let author = &epoch.authorities[pre_digest.authority_index as usize].0; + let authority_id = &epoch.authorities[pre_digest.authority_index as usize].0; let mut epoch_index = epoch.epoch_index; if epoch.end_slot() <= pre_digest.slot { @@ -163,28 +167,34 @@ fn check_primary_header( epoch_index = epoch.clone_for_slot(pre_digest.slot).epoch_index; } - if AuthorityPair::verify(&signature, pre_hash, author) { - let (inout, _) = { - let transcript = make_transcript(&epoch.randomness, pre_digest.slot, epoch_index); - - schnorrkel::PublicKey::from_bytes(author.as_slice()) - .and_then(|p| { - p.vrf_verify(transcript, &pre_digest.vrf_output, &pre_digest.vrf_proof) - }) - .map_err(|s| babe_err(Error::VRFVerificationFailed(s)))? - }; + if !AuthorityPair::verify(&signature, pre_hash, authority_id) { + return Err(babe_err(Error::BadSignature(pre_hash))) + } - let threshold = - calculate_primary_threshold(c, &epoch.authorities, pre_digest.authority_index as usize); + let transcript = make_transcript(&epoch.randomness, pre_digest.slot, epoch_index); - if !check_primary_threshold(&inout, threshold) { - return Err(babe_err(Error::VRFVerificationOfBlockFailed(author.clone(), threshold))) - } + if !authority_id.as_inner_ref().vrf_verify(&transcript, &pre_digest.vrf_signature) { + return Err(babe_err(Error::VrfVerificationFailed)) + } - Ok(()) - } else { - Err(babe_err(Error::BadSignature(pre_hash))) + let threshold = + calculate_primary_threshold(c, &epoch.authorities, pre_digest.authority_index as usize); + + let score = authority_id + .as_inner_ref() + .make_bytes::<[u8; AUTHORING_SCORE_LENGTH]>( + AUTHORING_SCORE_VRF_CONTEXT, + &transcript, + &pre_digest.vrf_signature.output, + ) + .map(u128::from_le_bytes) + .map_err(|_| babe_err(Error::VrfVerificationFailed))?; + + if score >= threshold { + return Err(babe_err(Error::VrfThresholdExceeded(threshold))) } + + Ok(()) } /// Check a secondary slot proposal header. We validate that the given header is @@ -197,8 +207,7 @@ fn check_secondary_plain_header( signature: AuthoritySignature, epoch: &Epoch, ) -> Result<(), Error> { - // check the signature is valid under the expected authority and - // chain state. + // check the signature is valid under the expected authority and chain state. let expected_author = secondary_slot_author(pre_digest.slot, &epoch.authorities, epoch.randomness) .ok_or(Error::NoSecondaryAuthorExpected)?; @@ -209,11 +218,11 @@ fn check_secondary_plain_header( return Err(Error::InvalidAuthor(expected_author.clone(), author.clone())) } - if AuthorityPair::verify(&signature, pre_hash.as_ref(), author) { - Ok(()) - } else { - Err(Error::BadSignature(pre_hash)) + if !AuthorityPair::verify(&signature, pre_hash.as_ref(), author) { + return Err(Error::BadSignature(pre_hash)) } + + Ok(()) } /// Check a secondary VRF slot proposal header. @@ -223,8 +232,7 @@ fn check_secondary_vrf_header( signature: AuthoritySignature, epoch: &Epoch, ) -> Result<(), Error> { - // check the signature is valid under the expected authority and - // chain state. + // check the signature is valid under the expected authority and chain state. let expected_author = secondary_slot_author(pre_digest.slot, &epoch.authorities, epoch.randomness) .ok_or(Error::NoSecondaryAuthorExpected)?; @@ -241,15 +249,15 @@ fn check_secondary_vrf_header( epoch_index = epoch.clone_for_slot(pre_digest.slot).epoch_index; } - if AuthorityPair::verify(&signature, pre_hash.as_ref(), author) { - let transcript = make_transcript(&epoch.randomness, pre_digest.slot, epoch_index); + if !AuthorityPair::verify(&signature, pre_hash.as_ref(), author) { + return Err(Error::BadSignature(pre_hash)) + } - schnorrkel::PublicKey::from_bytes(author.as_slice()) - .and_then(|p| p.vrf_verify(transcript, &pre_digest.vrf_output, &pre_digest.vrf_proof)) - .map_err(|s| babe_err(Error::VRFVerificationFailed(s)))?; + let transcript = make_transcript(&epoch.randomness, pre_digest.slot, epoch_index); - Ok(()) - } else { - Err(Error::BadSignature(pre_hash)) + if !author.as_inner_ref().vrf_verify(&transcript, &pre_digest.vrf_signature) { + return Err(Error::VrfVerificationFailed) } + + Ok(()) } diff --git a/client/keystore/src/local.rs b/client/keystore/src/local.rs index 2486b2fd045d5..3551623f332a2 100644 --- a/client/keystore/src/local.rs +++ b/client/keystore/src/local.rs @@ -20,13 +20,10 @@ use parking_lot::RwLock; use sp_application_crypto::{AppCrypto, AppPair, IsWrappedBy}; use sp_core::{ - crypto::{ByteArray, ExposeSecret, KeyTypeId, Pair as CorePair, SecretString}, + crypto::{ByteArray, ExposeSecret, KeyTypeId, Pair as CorePair, SecretString, VrfSigner}, ecdsa, ed25519, sr25519, }; -use sp_keystore::{ - vrf::{make_transcript, VRFSignature, VRFTranscriptData}, - Error as TraitError, Keystore, KeystorePtr, -}; +use sp_keystore::{Error as TraitError, Keystore, KeystorePtr}; use std::{ collections::HashMap, fs::{self, File}, @@ -100,6 +97,20 @@ impl LocalKeystore { .map(|pair| pair.sign(msg)); Ok(signature) } + + fn vrf_sign( + &self, + key_type: KeyTypeId, + public: &T::Public, + transcript: &T::VrfInput, + ) -> std::result::Result, TraitError> { + let sig = self + .0 + .read() + .key_pair_by_type::(public, key_type)? + .map(|pair| pair.vrf_sign(transcript)); + Ok(sig) + } } impl Keystore for LocalKeystore { @@ -131,14 +142,9 @@ impl Keystore for LocalKeystore { &self, key_type: KeyTypeId, public: &sr25519::Public, - transcript_data: VRFTranscriptData, - ) -> std::result::Result, TraitError> { - let sig = self.0.read().key_pair_by_type::(public, key_type)?.map(|pair| { - let transcript = make_transcript(transcript_data); - let (inout, proof, _) = pair.as_ref().vrf_sign(transcript); - VRFSignature { output: inout.to_output(), proof } - }); - Ok(sig) + transcript: &sr25519::vrf::VrfTranscript, + ) -> std::result::Result, TraitError> { + self.vrf_sign::(key_type, public, transcript) } fn ed25519_public_keys(&self, key_type: KeyTypeId) -> Vec { diff --git a/frame/babe/Cargo.toml b/frame/babe/Cargo.toml index c4f6592dba9e1..ff9e4b3aeac43 100644 --- a/frame/babe/Cargo.toml +++ b/frame/babe/Cargo.toml @@ -24,7 +24,7 @@ pallet-session = { version = "4.0.0-dev", default-features = false, path = "../s pallet-timestamp = { version = "4.0.0-dev", default-features = false, path = "../timestamp" } sp-application-crypto = { version = "7.0.0", default-features = false, path = "../../primitives/application-crypto" } sp-consensus-babe = { version = "0.10.0-dev", default-features = false, path = "../../primitives/consensus/babe" } -sp-consensus-vrf = { version = "0.10.0-dev", default-features = false, path = "../../primitives/consensus/vrf" } +sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" } sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } sp-session = { version = "4.0.0-dev", default-features = false, path = "../../primitives/session" } @@ -53,7 +53,7 @@ std = [ "scale-info/std", "sp-application-crypto/std", "sp-consensus-babe/std", - "sp-consensus-vrf/std", + "sp-core/std", "sp-io/std", "sp-runtime/std", "sp-session/std", diff --git a/frame/babe/src/lib.rs b/frame/babe/src/lib.rs index 61284267b07e7..e3ead424e7f6b 100644 --- a/frame/babe/src/lib.rs +++ b/frame/babe/src/lib.rs @@ -29,13 +29,13 @@ use frame_support::{ weights::Weight, BoundedVec, WeakBoundedVec, }; -use sp_application_crypto::ByteArray; use sp_consensus_babe::{ digests::{NextConfigDescriptor, NextEpochDescriptor, PreDigest}, AllowedSlots, BabeAuthorityWeight, BabeEpochConfiguration, ConsensusLog, Epoch, - EquivocationProof, Slot, BABE_ENGINE_ID, + EquivocationProof, Randomness as BabeRandomness, Slot, BABE_ENGINE_ID, RANDOMNESS_LENGTH, + RANDOMNESS_VRF_CONTEXT, }; -use sp_consensus_vrf::schnorrkel; +use sp_core::crypto::Wraps; use sp_runtime::{ generic::DigestItem, traits::{IsMember, One, SaturatedConversion, Saturating, Zero}, @@ -45,7 +45,7 @@ use sp_session::{GetSessionNumber, GetValidatorCount}; use sp_staking::{offence::OffenceReportSystem, SessionIndex}; use sp_std::prelude::*; -pub use sp_consensus_babe::{AuthorityId, PUBLIC_KEY_LENGTH, RANDOMNESS_LENGTH, VRF_OUTPUT_LENGTH}; +pub use sp_consensus_babe::AuthorityId; const LOG_TARGET: &str = "runtime::babe"; @@ -218,7 +218,7 @@ pub mod pallet { // variable to its underlying value. #[pallet::storage] #[pallet::getter(fn randomness)] - pub type Randomness = StorageValue<_, schnorrkel::Randomness, ValueQuery>; + pub type Randomness = StorageValue<_, BabeRandomness, ValueQuery>; /// Pending epoch configuration change that will be applied when the next epoch is enacted. #[pallet::storage] @@ -226,7 +226,7 @@ pub mod pallet { /// Next epoch randomness. #[pallet::storage] - pub(super) type NextRandomness = StorageValue<_, schnorrkel::Randomness, ValueQuery>; + pub(super) type NextRandomness = StorageValue<_, BabeRandomness, ValueQuery>; /// Next epoch authorities. #[pallet::storage] @@ -254,7 +254,7 @@ pub mod pallet { _, Twox64Concat, u32, - BoundedVec>, + BoundedVec>, ValueQuery, >; @@ -270,8 +270,7 @@ pub mod pallet { /// It is set in `on_finalize`, before it will contain the value from the last block. #[pallet::storage] #[pallet::getter(fn author_vrf_randomness)] - pub(super) type AuthorVrfRandomness = - StorageValue<_, Option, ValueQuery>; + pub(super) type AuthorVrfRandomness = StorageValue<_, Option, ValueQuery>; /// The block numbers when the last and current epoch have started, respectively `N-1` and /// `N`. @@ -358,31 +357,33 @@ pub mod pallet { ); } - if let Some((vrf_output, vrf_proof)) = pre_digest.vrf() { - let randomness: Option = Authorities::::get() + if let Some(vrf_signature) = pre_digest.vrf_signature() { + let randomness: Option = Authorities::::get() .get(authority_index as usize) .and_then(|(authority, _)| { - schnorrkel::PublicKey::from_bytes(authority.as_slice()).ok() - }) - .and_then(|pubkey| { - let current_slot = CurrentSlot::::get(); - + let public = authority.as_inner_ref(); let transcript = sp_consensus_babe::make_transcript( &Self::randomness(), - current_slot, + CurrentSlot::::get(), EpochIndex::::get(), ); // NOTE: this is verified by the client when importing the block, before - // execution. we don't run the verification again here to avoid slowing + // execution. We don't run the verification again here to avoid slowing // down the runtime. - debug_assert!(pubkey - .vrf_verify(transcript.clone(), vrf_output, vrf_proof) - .is_ok()); - - vrf_output.0.attach_input_hash(&pubkey, transcript).ok() - }) - .map(|inout| inout.make_bytes(sp_consensus_babe::BABE_VRF_INOUT_CONTEXT)); + debug_assert!({ + use sp_core::crypto::VrfVerifier; + public.vrf_verify(&transcript, &vrf_signature) + }); + + public + .make_bytes( + RANDOMNESS_VRF_CONTEXT, + &transcript, + &vrf_signature.output, + ) + .ok() + }); if let Some(randomness) = pre_digest.is_primary().then(|| randomness).flatten() { @@ -484,9 +485,6 @@ pub mod pallet { } } -/// A BABE public key -pub type BabeKey = [u8; PUBLIC_KEY_LENGTH]; - impl FindAuthor for Pallet { fn find_author<'a, I>(digests: I) -> Option where @@ -737,7 +735,7 @@ impl Pallet { >::deposit_log(log) } - fn deposit_randomness(randomness: &schnorrkel::Randomness) { + fn deposit_randomness(randomness: &BabeRandomness) { let segment_idx = SegmentIndex::::get(); let mut segment = UnderConstruction::::get(&segment_idx); if segment.try_push(*randomness).is_ok() { @@ -831,7 +829,7 @@ impl Pallet { /// Call this function exactly once when an epoch changes, to update the /// randomness. Returns the new randomness. - fn randomness_change_epoch(next_epoch_index: u64) -> schnorrkel::Randomness { + fn randomness_change_epoch(next_epoch_index: u64) -> BabeRandomness { let this_randomness = NextRandomness::::get(); let segment_idx: u32 = SegmentIndex::::mutate(|s| sp_std::mem::replace(s, 0)); @@ -990,12 +988,12 @@ where // // an optional size hint as to how many VRF outputs there were may be provided. fn compute_randomness( - last_epoch_randomness: schnorrkel::Randomness, + last_epoch_randomness: BabeRandomness, epoch_index: u64, - rho: impl Iterator, + rho: impl Iterator, rho_size_hint: Option, -) -> schnorrkel::Randomness { - let mut s = Vec::with_capacity(40 + rho_size_hint.unwrap_or(0) * VRF_OUTPUT_LENGTH); +) -> BabeRandomness { + let mut s = Vec::with_capacity(40 + rho_size_hint.unwrap_or(0) * RANDOMNESS_LENGTH); s.extend_from_slice(&last_epoch_randomness); s.extend_from_slice(&epoch_index.to_le_bytes()); diff --git a/frame/babe/src/mock.rs b/frame/babe/src/mock.rs index 94e748d0bca52..bccdb42c71ac3 100644 --- a/frame/babe/src/mock.rs +++ b/frame/babe/src/mock.rs @@ -25,10 +25,9 @@ use frame_support::{ traits::{ConstU128, ConstU32, ConstU64, GenesisBuild, KeyOwnerProofSystem, OnInitialize}, }; use pallet_session::historical as pallet_session_historical; -use sp_consensus_babe::{AuthorityId, AuthorityPair, Slot}; -use sp_consensus_vrf::schnorrkel::{VRFOutput, VRFProof}; +use sp_consensus_babe::{AuthorityId, AuthorityPair, Randomness, Slot, VrfSignature}; use sp_core::{ - crypto::{IsWrappedBy, KeyTypeId, Pair}, + crypto::{KeyTypeId, Pair, VrfSigner}, H256, U256, }; use sp_io; @@ -283,16 +282,10 @@ pub fn start_era(era_index: EraIndex) { pub fn make_primary_pre_digest( authority_index: sp_consensus_babe::AuthorityIndex, slot: sp_consensus_babe::Slot, - vrf_output: VRFOutput, - vrf_proof: VRFProof, + vrf_signature: VrfSignature, ) -> Digest { let digest_data = sp_consensus_babe::digests::PreDigest::Primary( - sp_consensus_babe::digests::PrimaryPreDigest { - authority_index, - slot, - vrf_output, - vrf_proof, - }, + sp_consensus_babe::digests::PrimaryPreDigest { authority_index, slot, vrf_signature }, ); let log = DigestItem::PreRuntime(sp_consensus_babe::BABE_ENGINE_ID, digest_data.encode()); Digest { logs: vec![log] } @@ -312,16 +305,10 @@ pub fn make_secondary_plain_pre_digest( pub fn make_secondary_vrf_pre_digest( authority_index: sp_consensus_babe::AuthorityIndex, slot: sp_consensus_babe::Slot, - vrf_output: VRFOutput, - vrf_proof: VRFProof, + vrf_signature: VrfSignature, ) -> Digest { let digest_data = sp_consensus_babe::digests::PreDigest::SecondaryVRF( - sp_consensus_babe::digests::SecondaryVRFPreDigest { - authority_index, - slot, - vrf_output, - vrf_proof, - }, + sp_consensus_babe::digests::SecondaryVRFPreDigest { authority_index, slot, vrf_signature }, ); let log = DigestItem::PreRuntime(sp_consensus_babe::BABE_ENGINE_ID, digest_data.encode()); Digest { logs: vec![log] } @@ -330,16 +317,16 @@ pub fn make_secondary_vrf_pre_digest( pub fn make_vrf_output( slot: Slot, pair: &sp_consensus_babe::AuthorityPair, -) -> (VRFOutput, VRFProof, [u8; 32]) { - let pair = sp_core::sr25519::Pair::from_ref(pair).as_ref(); +) -> (VrfSignature, Randomness) { let transcript = sp_consensus_babe::make_transcript(&Babe::randomness(), slot, 0); - let vrf_inout = pair.vrf_sign(transcript); - let vrf_randomness: sp_consensus_vrf::schnorrkel::Randomness = - vrf_inout.0.make_bytes::<[u8; 32]>(&sp_consensus_babe::BABE_VRF_INOUT_CONTEXT); - let vrf_output = VRFOutput(vrf_inout.0.to_output()); - let vrf_proof = VRFProof(vrf_inout.1); - (vrf_output, vrf_proof, vrf_randomness) + let signature = pair.as_ref().vrf_sign(&transcript); + + let randomness = pair + .as_ref() + .make_bytes::(sp_consensus_babe::RANDOMNESS_VRF_CONTEXT, &transcript); + + (signature, randomness) } pub fn new_test_ext(authorities_len: usize) -> sp_io::TestExternalities { diff --git a/frame/babe/src/randomness.rs b/frame/babe/src/randomness.rs index b223df6804c0b..b9b24786b7a74 100644 --- a/frame/babe/src/randomness.rs +++ b/frame/babe/src/randomness.rs @@ -19,7 +19,7 @@ //! randomness collected from VRF outputs. use super::{ - AuthorVrfRandomness, Config, EpochStart, NextRandomness, Randomness, VRF_OUTPUT_LENGTH, + AuthorVrfRandomness, Config, EpochStart, NextRandomness, Randomness, RANDOMNESS_LENGTH, }; use frame_support::traits::Randomness as RandomnessT; use sp_runtime::traits::{Hash, One, Saturating}; @@ -132,7 +132,7 @@ pub struct CurrentBlockRandomness(sp_std::marker::PhantomData); impl RandomnessT for RandomnessFromTwoEpochsAgo { fn random(subject: &[u8]) -> (T::Hash, T::BlockNumber) { let mut subject = subject.to_vec(); - subject.reserve(VRF_OUTPUT_LENGTH); + subject.reserve(RANDOMNESS_LENGTH); subject.extend_from_slice(&Randomness::::get()[..]); (T::Hashing::hash(&subject[..]), EpochStart::::get().0) @@ -142,7 +142,7 @@ impl RandomnessT for RandomnessFromTwoEpochs impl RandomnessT for RandomnessFromOneEpochAgo { fn random(subject: &[u8]) -> (T::Hash, T::BlockNumber) { let mut subject = subject.to_vec(); - subject.reserve(VRF_OUTPUT_LENGTH); + subject.reserve(RANDOMNESS_LENGTH); subject.extend_from_slice(&NextRandomness::::get()[..]); (T::Hashing::hash(&subject[..]), EpochStart::::get().1) @@ -153,7 +153,7 @@ impl RandomnessT, T::BlockNumber> for ParentBlockRand fn random(subject: &[u8]) -> (Option, T::BlockNumber) { let random = AuthorVrfRandomness::::get().map(|random| { let mut subject = subject.to_vec(); - subject.reserve(VRF_OUTPUT_LENGTH); + subject.reserve(RANDOMNESS_LENGTH); subject.extend_from_slice(&random); T::Hashing::hash(&subject[..]) diff --git a/frame/babe/src/tests.rs b/frame/babe/src/tests.rs index 8b63f41b37b74..8a9aa6fcc03d2 100644 --- a/frame/babe/src/tests.rs +++ b/frame/babe/src/tests.rs @@ -25,11 +25,12 @@ use frame_support::{ }; use mock::*; use pallet_session::ShouldEndSession; -use sp_consensus_babe::{AllowedSlots, BabeEpochConfiguration, Slot}; -use sp_consensus_vrf::schnorrkel::{VRFOutput, VRFProof}; +use sp_consensus_babe::{ + AllowedSlots, BabeEpochConfiguration, Slot, VrfSignature, RANDOMNESS_LENGTH, +}; use sp_core::crypto::Pair; -const EMPTY_RANDOMNESS: [u8; 32] = [ +const EMPTY_RANDOMNESS: [u8; RANDOMNESS_LENGTH] = [ 74, 25, 49, 128, 53, 97, 244, 49, 222, 202, 176, 2, 231, 66, 95, 10, 133, 49, 213, 228, 86, 161, 164, 127, 217, 153, 138, 37, 48, 192, 248, 0, ]; @@ -62,10 +63,9 @@ fn first_block_epoch_zero_start() { ext.execute_with(|| { let genesis_slot = Slot::from(100); - let (vrf_output, vrf_proof, vrf_randomness) = make_vrf_output(genesis_slot, &pairs[0]); + let (vrf_signature, vrf_randomness) = make_vrf_output(genesis_slot, &pairs[0]); - let first_vrf = vrf_output; - let pre_digest = make_primary_pre_digest(0, genesis_slot, first_vrf.clone(), vrf_proof); + let pre_digest = make_primary_pre_digest(0, genesis_slot, vrf_signature); assert_eq!(Babe::genesis_slot(), Slot::from(0)); System::reset_events(); @@ -111,8 +111,8 @@ fn current_slot_is_processed_on_initialization() { ext.execute_with(|| { let genesis_slot = Slot::from(10); - let (vrf_output, vrf_proof, vrf_randomness) = make_vrf_output(genesis_slot, &pairs[0]); - let pre_digest = make_primary_pre_digest(0, genesis_slot, vrf_output, vrf_proof); + let (vrf_signature, vrf_randomness) = make_vrf_output(genesis_slot, &pairs[0]); + let pre_digest = make_primary_pre_digest(0, genesis_slot, vrf_signature); System::reset_events(); System::initialize(&1, &Default::default(), &pre_digest); @@ -134,14 +134,14 @@ fn current_slot_is_processed_on_initialization() { fn test_author_vrf_output(make_pre_digest: F) where - F: Fn(sp_consensus_babe::AuthorityIndex, Slot, VRFOutput, VRFProof) -> sp_runtime::Digest, + F: Fn(sp_consensus_babe::AuthorityIndex, Slot, VrfSignature) -> sp_runtime::Digest, { let (pairs, mut ext) = new_test_ext_with_pairs(1); ext.execute_with(|| { let genesis_slot = Slot::from(10); - let (vrf_output, vrf_proof, vrf_randomness) = make_vrf_output(genesis_slot, &pairs[0]); - let pre_digest = make_pre_digest(0, genesis_slot, vrf_output, vrf_proof); + let (vrf_signature, vrf_randomness) = make_vrf_output(genesis_slot, &pairs[0]); + let pre_digest = make_pre_digest(0, genesis_slot, vrf_signature); System::reset_events(); System::initialize(&1, &Default::default(), &pre_digest); diff --git a/primitives/consensus/babe/Cargo.toml b/primitives/consensus/babe/Cargo.toml index d106cd0d0c875..7e171811a7fe2 100644 --- a/primitives/consensus/babe/Cargo.toml +++ b/primitives/consensus/babe/Cargo.toml @@ -15,14 +15,12 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] async-trait = { version = "0.1.57", optional = true } codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false } -merlin = { version = "2.0", default-features = false } scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } serde = { version = "1.0.136", features = ["derive"], optional = true } sp-api = { version = "4.0.0-dev", default-features = false, path = "../../api" } sp-application-crypto = { version = "7.0.0", default-features = false, path = "../../application-crypto" } sp-consensus = { version = "0.10.0-dev", optional = true, path = "../common" } sp-consensus-slots = { version = "0.10.0-dev", default-features = false, path = "../slots" } -sp-consensus-vrf = { version = "0.10.0-dev", default-features = false, path = "../vrf" } sp-core = { version = "7.0.0", default-features = false, path = "../../core" } sp-inherents = { version = "4.0.0-dev", default-features = false, path = "../../inherents" } sp-keystore = { version = "0.13.0", default-features = false, optional = true, path = "../../keystore" } @@ -35,14 +33,12 @@ default = ["std"] std = [ "async-trait", "codec/std", - "merlin/std", "scale-info/std", "serde", "sp-api/std", "sp-application-crypto/std", "sp-consensus", "sp-consensus-slots/std", - "sp-consensus-vrf/std", "sp-core/std", "sp-inherents/std", "sp-keystore", diff --git a/primitives/consensus/babe/src/digests.rs b/primitives/consensus/babe/src/digests.rs index 4364057a4b478..afc967e3af391 100644 --- a/primitives/consensus/babe/src/digests.rs +++ b/primitives/consensus/babe/src/digests.rs @@ -19,14 +19,15 @@ use super::{ AllowedSlots, AuthorityId, AuthorityIndex, AuthoritySignature, BabeAuthorityWeight, - BabeEpochConfiguration, Slot, BABE_ENGINE_ID, + BabeEpochConfiguration, Randomness, Slot, BABE_ENGINE_ID, }; -use codec::{Decode, Encode, MaxEncodedLen}; -use scale_info::TypeInfo; + +use sp_core::sr25519::vrf::VrfSignature; use sp_runtime::{DigestItem, RuntimeDebug}; use sp_std::vec::Vec; -use sp_consensus_vrf::schnorrkel::{Randomness, VRFOutput, VRFProof}; +use codec::{Decode, Encode, MaxEncodedLen}; +use scale_info::TypeInfo; /// Raw BABE primary slot assignment pre-digest. #[derive(Clone, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)] @@ -35,10 +36,8 @@ pub struct PrimaryPreDigest { pub authority_index: super::AuthorityIndex, /// Slot pub slot: Slot, - /// VRF output - pub vrf_output: VRFOutput, - /// VRF proof - pub vrf_proof: VRFProof, + /// VRF signature + pub vrf_signature: VrfSignature, } /// BABE secondary slot assignment pre-digest. @@ -62,10 +61,8 @@ pub struct SecondaryVRFPreDigest { pub authority_index: super::AuthorityIndex, /// Slot pub slot: Slot, - /// VRF output - pub vrf_output: VRFOutput, - /// VRF proof - pub vrf_proof: VRFProof, + /// VRF signature + pub vrf_signature: VrfSignature, } /// A BABE pre-runtime digest. This contains all data required to validate a @@ -118,11 +115,10 @@ impl PreDigest { } /// Returns the VRF output and proof, if they exist. - pub fn vrf(&self) -> Option<(&VRFOutput, &VRFProof)> { + pub fn vrf_signature(&self) -> Option<&VrfSignature> { match self { - PreDigest::Primary(primary) => Some((&primary.vrf_output, &primary.vrf_proof)), - PreDigest::SecondaryVRF(secondary) => - Some((&secondary.vrf_output, &secondary.vrf_proof)), + PreDigest::Primary(primary) => Some(&primary.vrf_signature), + PreDigest::SecondaryVRF(secondary) => Some(&secondary.vrf_signature), PreDigest::SecondaryPlain(_) => None, } } diff --git a/primitives/consensus/babe/src/lib.rs b/primitives/consensus/babe/src/lib.rs index e7747ac4c204a..37cf0c3f0b677 100644 --- a/primitives/consensus/babe/src/lib.rs +++ b/primitives/consensus/babe/src/lib.rs @@ -23,22 +23,17 @@ pub mod digests; pub mod inherents; -pub use merlin::Transcript; -pub use sp_consensus_vrf::schnorrkel::{ - Randomness, RANDOMNESS_LENGTH, VRF_OUTPUT_LENGTH, VRF_PROOF_LENGTH, -}; - use codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; #[cfg(feature = "std")] use serde::{Deserialize, Serialize}; -#[cfg(feature = "std")] -use sp_keystore::vrf::{VRFTranscriptData, VRFTranscriptValue}; use sp_runtime::{traits::Header, ConsensusEngineId, RuntimeDebug}; use sp_std::vec::Vec; use crate::digests::{NextConfigDescriptor, NextEpochDescriptor}; +pub use sp_core::sr25519::vrf::{VrfOutput, VrfProof, VrfSignature, VrfTranscript}; + /// Key type for BABE module. pub const KEY_TYPE: sp_core::crypto::KeyTypeId = sp_application_crypto::key_types::BABE; @@ -47,11 +42,14 @@ mod app { app_crypto!(sr25519, BABE); } -/// The prefix used by BABE for its VRF keys. -pub const BABE_VRF_PREFIX: &[u8] = b"substrate-babe-vrf"; +/// VRF context used for per-slot randomness generation. +pub const RANDOMNESS_VRF_CONTEXT: &[u8] = b"BabeVRFInOutContext"; -/// BABE VRFInOut context. -pub static BABE_VRF_INOUT_CONTEXT: &[u8] = b"BabeVRFInOutContext"; +/// VRF output length for per-slot randomness. +pub const RANDOMNESS_LENGTH: usize = 32; + +/// Randomness type required by BABE operations. +pub type Randomness = [u8; RANDOMNESS_LENGTH]; /// A Babe authority keypair. Necessarily equivalent to the schnorrkel public key used in /// the main Babe module. If that ever changes, then this must, too. @@ -96,26 +94,16 @@ pub type BabeAuthorityWeight = u64; /// of 0 (regardless of whether they are plain or vrf secondary blocks). pub type BabeBlockWeight = u32; -/// Make a VRF transcript from given randomness, slot number and epoch. -pub fn make_transcript(randomness: &Randomness, slot: Slot, epoch: u64) -> Transcript { - let mut transcript = Transcript::new(&BABE_ENGINE_ID); - transcript.append_u64(b"slot number", *slot); - transcript.append_u64(b"current epoch", epoch); - transcript.append_message(b"chain randomness", &randomness[..]); - transcript -} - /// Make a VRF transcript data container -#[cfg(feature = "std")] -pub fn make_transcript_data(randomness: &Randomness, slot: Slot, epoch: u64) -> VRFTranscriptData { - VRFTranscriptData { - label: &BABE_ENGINE_ID, - items: vec![ - ("slot number", VRFTranscriptValue::U64(*slot)), - ("current epoch", VRFTranscriptValue::U64(epoch)), - ("chain randomness", VRFTranscriptValue::Bytes(randomness.to_vec())), +pub fn make_transcript(randomness: &Randomness, slot: Slot, epoch: u64) -> VrfTranscript { + VrfTranscript::new( + &BABE_ENGINE_ID, + &[ + (b"slot number", &slot.to_le_bytes()), + (b"current epoch", &epoch.to_le_bytes()), + (b"chain randomness", randomness), ], - } + ) } /// An consensus log item for BABE. @@ -355,7 +343,7 @@ pub struct Epoch { /// The authorities and their weights. pub authorities: Vec<(AuthorityId, BabeAuthorityWeight)>, /// Randomness for this epoch. - pub randomness: [u8; VRF_OUTPUT_LENGTH], + pub randomness: Randomness, /// Configuration of the epoch. pub config: BabeEpochConfiguration, } diff --git a/primitives/consensus/vrf/Cargo.toml b/primitives/consensus/vrf/Cargo.toml deleted file mode 100644 index e7b4f0f0dec0e..0000000000000 --- a/primitives/consensus/vrf/Cargo.toml +++ /dev/null @@ -1,32 +0,0 @@ -[package] -name = "sp-consensus-vrf" -version = "0.10.0-dev" -authors = ["Parity Technologies "] -description = "Primitives for VRF based consensus" -edition = "2021" -license = "Apache-2.0" -repository = "https://github.com/paritytech/substrate/" -homepage = "https://substrate.io" -readme = "README.md" - -[package.metadata.docs.rs] -targets = ["x86_64-unknown-linux-gnu"] - -[dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false } -scale-info = { version = "2.5.0", default-features = false } -schnorrkel = { version = "0.9.1", default-features = false, features = ["preaudit_deprecated", "u64_backend"] } -sp-core = { version = "7.0.0", default-features = false, path = "../../core" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../../runtime" } -sp-std = { version = "5.0.0", default-features = false, path = "../../std" } - -[features] -default = ["std"] -std = [ - "codec/std", - "scale-info/std", - "schnorrkel/std", - "sp-core/std", - "sp-runtime/std", - "sp-std/std", -] diff --git a/primitives/consensus/vrf/README.md b/primitives/consensus/vrf/README.md deleted file mode 100644 index d66490e023b38..0000000000000 --- a/primitives/consensus/vrf/README.md +++ /dev/null @@ -1,3 +0,0 @@ -Primitives for VRF-based consensus engines. - -License: Apache-2.0 \ No newline at end of file diff --git a/primitives/consensus/vrf/src/lib.rs b/primitives/consensus/vrf/src/lib.rs deleted file mode 100644 index 84040c18590e0..0000000000000 --- a/primitives/consensus/vrf/src/lib.rs +++ /dev/null @@ -1,21 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: Apache-2.0 - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! Primitives for VRF-based consensus engines. -#![cfg_attr(not(feature = "std"), no_std)] - -pub mod schnorrkel; diff --git a/primitives/consensus/vrf/src/schnorrkel.rs b/primitives/consensus/vrf/src/schnorrkel.rs deleted file mode 100644 index be145c5b12096..0000000000000 --- a/primitives/consensus/vrf/src/schnorrkel.rs +++ /dev/null @@ -1,188 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: Apache-2.0 - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! Schnorrkel-based VRF. - -use codec::{Decode, Encode, EncodeLike, MaxEncodedLen}; -use scale_info::TypeInfo; -use schnorrkel::errors::MultiSignatureStage; -use sp_core::U512; -use sp_std::{ - ops::{Deref, DerefMut}, - prelude::*, -}; - -pub use schnorrkel::{ - vrf::{VRF_OUTPUT_LENGTH, VRF_PROOF_LENGTH}, - PublicKey, SignatureError, -}; - -/// The length of the Randomness. -pub const RANDOMNESS_LENGTH: usize = VRF_OUTPUT_LENGTH; - -/// VRF output type available for `std` environment, suitable for schnorrkel operations. -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct VRFOutput(pub schnorrkel::vrf::VRFOutput); - -impl Deref for VRFOutput { - type Target = schnorrkel::vrf::VRFOutput; - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for VRFOutput { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -impl Encode for VRFOutput { - fn encode(&self) -> Vec { - self.0.as_bytes().encode() - } -} - -impl EncodeLike for VRFOutput {} - -impl Decode for VRFOutput { - fn decode(i: &mut R) -> Result { - let decoded = <[u8; VRF_OUTPUT_LENGTH]>::decode(i)?; - Ok(Self(schnorrkel::vrf::VRFOutput::from_bytes(&decoded).map_err(convert_error)?)) - } -} - -impl MaxEncodedLen for VRFOutput { - fn max_encoded_len() -> usize { - <[u8; VRF_OUTPUT_LENGTH]>::max_encoded_len() - } -} - -impl TypeInfo for VRFOutput { - type Identity = [u8; VRF_OUTPUT_LENGTH]; - - fn type_info() -> scale_info::Type { - Self::Identity::type_info() - } -} - -impl TryFrom<[u8; VRF_OUTPUT_LENGTH]> for VRFOutput { - type Error = SignatureError; - - fn try_from(raw: [u8; VRF_OUTPUT_LENGTH]) -> Result { - schnorrkel::vrf::VRFOutput::from_bytes(&raw).map(VRFOutput) - } -} - -/// VRF proof type available for `std` environment, suitable for schnorrkel operations. -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct VRFProof(pub schnorrkel::vrf::VRFProof); - -impl PartialOrd for VRFProof { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl Ord for VRFProof { - fn cmp(&self, other: &Self) -> core::cmp::Ordering { - U512::from(self.0.to_bytes()).cmp(&U512::from(other.0.to_bytes())) - } -} - -impl Deref for VRFProof { - type Target = schnorrkel::vrf::VRFProof; - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for VRFProof { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -impl Encode for VRFProof { - fn encode(&self) -> Vec { - self.0.to_bytes().encode() - } -} - -impl EncodeLike for VRFProof {} - -impl Decode for VRFProof { - fn decode(i: &mut R) -> Result { - let decoded = <[u8; VRF_PROOF_LENGTH]>::decode(i)?; - Ok(Self(schnorrkel::vrf::VRFProof::from_bytes(&decoded).map_err(convert_error)?)) - } -} - -impl MaxEncodedLen for VRFProof { - fn max_encoded_len() -> usize { - <[u8; VRF_PROOF_LENGTH]>::max_encoded_len() - } -} - -impl TypeInfo for VRFProof { - type Identity = [u8; VRF_PROOF_LENGTH]; - - fn type_info() -> scale_info::Type { - Self::Identity::type_info() - } -} - -impl TryFrom<[u8; VRF_PROOF_LENGTH]> for VRFProof { - type Error = SignatureError; - - fn try_from(raw: [u8; VRF_PROOF_LENGTH]) -> Result { - schnorrkel::vrf::VRFProof::from_bytes(&raw).map(VRFProof) - } -} - -fn convert_error(e: SignatureError) -> codec::Error { - use MultiSignatureStage::*; - use SignatureError::*; - match e { - EquationFalse => "Signature error: `EquationFalse`".into(), - PointDecompressionError => "Signature error: `PointDecompressionError`".into(), - ScalarFormatError => "Signature error: `ScalarFormatError`".into(), - NotMarkedSchnorrkel => "Signature error: `NotMarkedSchnorrkel`".into(), - BytesLengthError { .. } => "Signature error: `BytesLengthError`".into(), - MuSigAbsent { musig_stage: Commitment } => - "Signature error: `MuSigAbsent` at stage `Commitment`".into(), - MuSigAbsent { musig_stage: Reveal } => - "Signature error: `MuSigAbsent` at stage `Reveal`".into(), - MuSigAbsent { musig_stage: Cosignature } => - "Signature error: `MuSigAbsent` at stage `Commitment`".into(), - MuSigInconsistent { musig_stage: Commitment, duplicate: true } => - "Signature error: `MuSigInconsistent` at stage `Commitment` on duplicate".into(), - MuSigInconsistent { musig_stage: Commitment, duplicate: false } => - "Signature error: `MuSigInconsistent` at stage `Commitment` on not duplicate".into(), - MuSigInconsistent { musig_stage: Reveal, duplicate: true } => - "Signature error: `MuSigInconsistent` at stage `Reveal` on duplicate".into(), - MuSigInconsistent { musig_stage: Reveal, duplicate: false } => - "Signature error: `MuSigInconsistent` at stage `Reveal` on not duplicate".into(), - MuSigInconsistent { musig_stage: Cosignature, duplicate: true } => - "Signature error: `MuSigInconsistent` at stage `Cosignature` on duplicate".into(), - MuSigInconsistent { musig_stage: Cosignature, duplicate: false } => - "Signature error: `MuSigInconsistent` at stage `Cosignature` on not duplicate".into(), - } -} - -/// Schnorrkel randomness value. Same size as `VRFOutput`. -pub type Randomness = [u8; RANDOMNESS_LENGTH]; diff --git a/primitives/core/Cargo.toml b/primitives/core/Cargo.toml index 1edd28b3fca7e..302d52f2f862b 100644 --- a/primitives/core/Cargo.toml +++ b/primitives/core/Cargo.toml @@ -44,9 +44,9 @@ bitflags = "1.3" array-bytes = { version = "4.1", optional = true } ed25519-zebra = { version = "3.1.0", default-features = false, optional = true } blake2 = { version = "0.10.4", default-features = false, optional = true } -schnorrkel = { version = "0.9.1", features = ["preaudit_deprecated", "u64_backend"], default-features = false, optional = true } libsecp256k1 = { version = "0.7", default-features = false, features = ["static-context"], optional = true } -merlin = { version = "2.0", default-features = false, optional = true } +schnorrkel = { version = "0.9.1", features = ["preaudit_deprecated", "u64_backend"], default-features = false } +merlin = { version = "2.0", default-features = false } secp256k1 = { version = "0.24.0", default-features = false, features = ["recovery", "alloc"], optional = true } ss58-registry = { version = "1.34.0", default-features = false } sp-core-hashing = { version = "5.0.0", path = "./hashing", default-features = false, optional = true } @@ -69,7 +69,7 @@ bench = false [features] default = ["std"] std = [ - "merlin?/std", + "merlin/std", "full_crypto", "log/std", "thiserror", @@ -119,10 +119,8 @@ full_crypto = [ "array-bytes", "ed25519-zebra", "blake2", - "schnorrkel", "libsecp256k1", "secp256k1", "sp-core-hashing", "sp-runtime-interface/disable_target_static_assertions", - "merlin", ] diff --git a/primitives/core/src/crypto.rs b/primitives/core/src/crypto.rs index f77e952d84546..4fec79fc6ea18 100644 --- a/primitives/core/src/crypto.rs +++ b/primitives/core/src/crypto.rs @@ -28,11 +28,8 @@ use rand::{rngs::OsRng, RngCore}; #[cfg(feature = "std")] use regex::Regex; use scale_info::TypeInfo; -/// Trait for accessing reference to `SecretString`. -pub use secrecy::ExposeSecret; -/// A store for sensitive data. #[cfg(feature = "std")] -pub use secrecy::SecretString; +pub use secrecy::{ExposeSecret, SecretString}; use sp_runtime_interface::pass_by::PassByInner; #[doc(hidden)] pub use sp_std::ops::Deref; @@ -1102,6 +1099,27 @@ impl<'a> TryFrom<&'a str> for KeyTypeId { } } +/// Trait grouping types shared by a VRF signer and verifiers. +pub trait VrfCrypto { + /// Associated signature type. + type VrfSignature; + + /// Vrf input data. Generally some form of transcript. + type VrfInput; +} + +/// VRF Signer. +pub trait VrfSigner: VrfCrypto { + /// Sign input data. + fn vrf_sign(&self, data: &Self::VrfInput) -> Self::VrfSignature; +} + +/// VRF Verifier. +pub trait VrfVerifier: VrfCrypto { + /// Verify input data signature. + fn vrf_verify(&self, data: &Self::VrfInput, signature: &Self::VrfSignature) -> bool; +} + /// An identifier for a specific cryptographic algorithm used by a key pair #[derive(Debug, Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Encode, Decode)] #[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] diff --git a/primitives/core/src/sr25519.rs b/primitives/core/src/sr25519.rs index d7c1c79c3f42b..fcd7f38a74f1f 100644 --- a/primitives/core/src/sr25519.rs +++ b/primitives/core/src/sr25519.rs @@ -15,12 +15,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -// tag::description[] //! Simple sr25519 (Schnorr-Ristretto) API. //! //! Note: `CHAIN_CODE_LENGTH` must be equal to `crate::crypto::JUNCTION_ID_LEN` //! for this to work. -// end::description[] + #[cfg(feature = "std")] use crate::crypto::Ss58Codec; #[cfg(feature = "full_crypto")] @@ -30,7 +29,6 @@ use schnorrkel::{ derive::{ChainCode, Derivation, CHAIN_CODE_LENGTH}, signing_context, ExpansionMode, Keypair, MiniSecretKey, PublicKey, SecretKey, }; -#[cfg(feature = "full_crypto")] use sp_std::vec::Vec; use crate::{ @@ -200,8 +198,6 @@ impl<'de> Deserialize<'de> for Public { } /// An Schnorrkel/Ristretto x25519 ("sr25519") signature. -/// -/// Instead of importing it for the local module, alias it to be available as a public type #[cfg_attr(feature = "full_crypto", derive(Hash))] #[derive(Encode, Decode, MaxEncodedLen, PassByInner, TypeInfo, PartialEq, Eq)] pub struct Signature(pub [u8; 64]); @@ -545,43 +541,194 @@ impl CryptoType for Pair { type Pair = Pair; } -/// Batch verification. -/// -/// `messages`, `signatures` and `pub_keys` should all have equal length. -/// -/// Returns `true` if all signatures are correct, `false` otherwise. -#[cfg(feature = "std")] -pub fn verify_batch( - messages: Vec<&[u8]>, - signatures: Vec<&Signature>, - pub_keys: Vec<&Public>, -) -> bool { - let mut sr_pub_keys = Vec::with_capacity(pub_keys.len()); - for pub_key in pub_keys { - match schnorrkel::PublicKey::from_bytes(pub_key.as_ref()) { - Ok(pk) => sr_pub_keys.push(pk), - Err(_) => return false, - }; +/// Schnorrkel VRF related types and operations. +pub mod vrf { + use super::*; + #[cfg(feature = "full_crypto")] + use crate::crypto::VrfSigner; + use crate::crypto::{VrfCrypto, VrfVerifier}; + use schnorrkel::{ + errors::MultiSignatureStage, + vrf::{VRF_OUTPUT_LENGTH, VRF_PROOF_LENGTH}, + SignatureError, + }; + + /// VRF transcript ready to be used for VRF sign/verify operations. + pub struct VrfTranscript(pub merlin::Transcript); + + impl VrfTranscript { + /// Build a new transcript ready to be used by a VRF signer/verifier. + pub fn new(label: &'static [u8], data: &[(&'static [u8], &[u8])]) -> Self { + let mut transcript = merlin::Transcript::new(label); + data.iter().for_each(|(l, b)| transcript.append_message(l, b)); + VrfTranscript(transcript) + } } - let mut sr_signatures = Vec::with_capacity(signatures.len()); - for signature in signatures { - match schnorrkel::Signature::from_bytes(signature.as_ref()) { - Ok(s) => sr_signatures.push(s), - Err(_) => return false, - }; + /// VRF signature data + #[derive(Clone, Debug, PartialEq, Eq, Encode, Decode, MaxEncodedLen, TypeInfo)] + pub struct VrfSignature { + /// The initial VRF configuration + pub output: VrfOutput, + /// The calculated VRF proof + pub proof: VrfProof, + } + + /// VRF output type suitable for schnorrkel operations. + #[derive(Clone, Debug, PartialEq, Eq)] + pub struct VrfOutput(pub schnorrkel::vrf::VRFOutput); + + impl Encode for VrfOutput { + fn encode(&self) -> Vec { + self.0.as_bytes().encode() + } + } + + impl Decode for VrfOutput { + fn decode(i: &mut R) -> Result { + let decoded = <[u8; VRF_OUTPUT_LENGTH]>::decode(i)?; + Ok(Self(schnorrkel::vrf::VRFOutput::from_bytes(&decoded).map_err(convert_error)?)) + } } - let mut messages: Vec = messages - .into_iter() - .map(|msg| signing_context(SIGNING_CTX).bytes(msg)) - .collect(); + impl MaxEncodedLen for VrfOutput { + fn max_encoded_len() -> usize { + <[u8; VRF_OUTPUT_LENGTH]>::max_encoded_len() + } + } + + impl TypeInfo for VrfOutput { + type Identity = [u8; VRF_OUTPUT_LENGTH]; + + fn type_info() -> scale_info::Type { + Self::Identity::type_info() + } + } + + /// VRF proof type suitable for schnorrkel operations. + #[derive(Clone, Debug, PartialEq, Eq)] + pub struct VrfProof(pub schnorrkel::vrf::VRFProof); + + impl Encode for VrfProof { + fn encode(&self) -> Vec { + self.0.to_bytes().encode() + } + } + + impl Decode for VrfProof { + fn decode(i: &mut R) -> Result { + let decoded = <[u8; VRF_PROOF_LENGTH]>::decode(i)?; + Ok(Self(schnorrkel::vrf::VRFProof::from_bytes(&decoded).map_err(convert_error)?)) + } + } + + impl MaxEncodedLen for VrfProof { + fn max_encoded_len() -> usize { + <[u8; VRF_PROOF_LENGTH]>::max_encoded_len() + } + } - schnorrkel::verify_batch(&mut messages, &sr_signatures, &sr_pub_keys, true).is_ok() + impl TypeInfo for VrfProof { + type Identity = [u8; VRF_PROOF_LENGTH]; + + fn type_info() -> scale_info::Type { + Self::Identity::type_info() + } + } + + #[cfg(feature = "full_crypto")] + impl VrfCrypto for Pair { + type VrfSignature = VrfSignature; + type VrfInput = VrfTranscript; + } + + #[cfg(feature = "full_crypto")] + impl VrfSigner for Pair { + fn vrf_sign(&self, transcript: &Self::VrfInput) -> Self::VrfSignature { + let (inout, proof, _) = self.0.vrf_sign(transcript.0.clone()); + VrfSignature { output: VrfOutput(inout.to_output()), proof: VrfProof(proof) } + } + } + + impl VrfCrypto for Public { + type VrfSignature = VrfSignature; + type VrfInput = VrfTranscript; + } + + impl VrfVerifier for Public { + fn vrf_verify(&self, transcript: &Self::VrfInput, signature: &Self::VrfSignature) -> bool { + schnorrkel::PublicKey::from_bytes(self) + .and_then(|public| { + public.vrf_verify(transcript.0.clone(), &signature.output.0, &signature.proof.0) + }) + .is_ok() + } + } + + fn convert_error(e: SignatureError) -> codec::Error { + use MultiSignatureStage::*; + use SignatureError::*; + match e { + EquationFalse => "Signature error: `EquationFalse`".into(), + PointDecompressionError => "Signature error: `PointDecompressionError`".into(), + ScalarFormatError => "Signature error: `ScalarFormatError`".into(), + NotMarkedSchnorrkel => "Signature error: `NotMarkedSchnorrkel`".into(), + BytesLengthError { .. } => "Signature error: `BytesLengthError`".into(), + MuSigAbsent { musig_stage: Commitment } => + "Signature error: `MuSigAbsent` at stage `Commitment`".into(), + MuSigAbsent { musig_stage: Reveal } => + "Signature error: `MuSigAbsent` at stage `Reveal`".into(), + MuSigAbsent { musig_stage: Cosignature } => + "Signature error: `MuSigAbsent` at stage `Commitment`".into(), + MuSigInconsistent { musig_stage: Commitment, duplicate: true } => + "Signature error: `MuSigInconsistent` at stage `Commitment` on duplicate".into(), + MuSigInconsistent { musig_stage: Commitment, duplicate: false } => + "Signature error: `MuSigInconsistent` at stage `Commitment` on not duplicate".into(), + MuSigInconsistent { musig_stage: Reveal, duplicate: true } => + "Signature error: `MuSigInconsistent` at stage `Reveal` on duplicate".into(), + MuSigInconsistent { musig_stage: Reveal, duplicate: false } => + "Signature error: `MuSigInconsistent` at stage `Reveal` on not duplicate".into(), + MuSigInconsistent { musig_stage: Cosignature, duplicate: true } => + "Signature error: `MuSigInconsistent` at stage `Cosignature` on duplicate".into(), + MuSigInconsistent { musig_stage: Cosignature, duplicate: false } => + "Signature error: `MuSigInconsistent` at stage `Cosignature` on not duplicate" + .into(), + } + } + + #[cfg(feature = "full_crypto")] + impl Pair { + /// Generate bytes from the given VRF configuration. + pub fn make_bytes>( + &self, + context: &[u8], + transcript: &VrfTranscript, + ) -> B { + let inout = self.0.vrf_create_hash(transcript.0.clone()); + inout.make_bytes::(context) + } + } + + impl Public { + /// Generate bytes from the given VRF configuration. + pub fn make_bytes>( + &self, + context: &[u8], + transcript: &VrfTranscript, + output: &VrfOutput, + ) -> Result { + let pubkey = schnorrkel::PublicKey::from_bytes(&self.0).map_err(convert_error)?; + let inout = output + .0 + .attach_input_hash(&pubkey, transcript.0.clone()) + .map_err(convert_error)?; + Ok(inout.make_bytes::(context)) + } + } } #[cfg(test)] -mod compatibility_test { +mod tests { use super::*; use crate::crypto::{Ss58Codec, DEV_ADDRESS, DEV_PHRASE}; use serde_json; @@ -811,4 +958,20 @@ mod compatibility_test { // Poorly-sized assert!(deserialize_signature("\"abc123\"").is_err()); } + + #[test] + fn vrf_make_bytes_matches() { + use super::vrf::*; + use crate::crypto::VrfSigner; + let pair = Pair::from_seed(b"12345678901234567890123456789012"); + let public = pair.public(); + let transcript = VrfTranscript::new(b"test", &[(b"foo", b"bar")]); + + let signature = pair.vrf_sign(&transcript); + + let ctx = b"randbytes"; + let b1 = pair.make_bytes::<[u8; 32]>(ctx, &transcript); + let b2 = public.make_bytes::<[u8; 32]>(ctx, &transcript, &signature.output).unwrap(); + assert_eq!(b1, b2); + } } diff --git a/primitives/keystore/Cargo.toml b/primitives/keystore/Cargo.toml index 543f01059412e..6b0194247219a 100644 --- a/primitives/keystore/Cargo.toml +++ b/primitives/keystore/Cargo.toml @@ -15,9 +15,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = ["derive"] } futures = "0.3.21" -merlin = { version = "2.0", default-features = false } parking_lot = { version = "0.12.1", default-features = false } -schnorrkel = { version = "0.9.1", default-features = false, features = ["preaudit_deprecated", "u64_backend"] } serde = { version = "1.0", optional = true } thiserror = "1.0" sp-core = { version = "7.0.0", default-features = false, path = "../core" } @@ -31,8 +29,6 @@ rand_chacha = "0.2.2" default = ["std"] std = [ "codec/std", - "merlin/std", - "schnorrkel/std", "serde", "sp-core/std", "sp-externalities/std", diff --git a/primitives/keystore/src/lib.rs b/primitives/keystore/src/lib.rs index 577969cdfecd8..5b41f3b80043d 100644 --- a/primitives/keystore/src/lib.rs +++ b/primitives/keystore/src/lib.rs @@ -16,10 +16,9 @@ // limitations under the License. //! Keystore traits + pub mod testing; -pub mod vrf; -use crate::vrf::{VRFSignature, VRFTranscriptData}; use sp_core::{ crypto::{ByteArray, CryptoTypeId, KeyTypeId}, ecdsa, ed25519, sr25519, @@ -87,8 +86,8 @@ pub trait Keystore: Send + Sync { &self, key_type: KeyTypeId, public: &sr25519::Public, - transcript_data: VRFTranscriptData, - ) -> Result, Error>; + transcript: &sr25519::vrf::VrfTranscript, + ) -> Result, Error>; /// Returns all ed25519 public keys for the given key type. fn ed25519_public_keys(&self, key_type: KeyTypeId) -> Vec; diff --git a/primitives/keystore/src/testing.rs b/primitives/keystore/src/testing.rs index e5107cba746f2..a6fcd6e26abe3 100644 --- a/primitives/keystore/src/testing.rs +++ b/primitives/keystore/src/testing.rs @@ -17,15 +17,13 @@ //! Types that should only be used for testing! +use crate::{Error, Keystore, KeystorePtr}; + use sp_core::{ - crypto::{ByteArray, KeyTypeId, Pair}, + crypto::{ByteArray, KeyTypeId, Pair, VrfSigner}, ecdsa, ed25519, sr25519, }; -use crate::{ - vrf::{make_transcript, VRFSignature, VRFTranscriptData}, - Error, Keystore, KeystorePtr, -}; use parking_lot::RwLock; use std::{collections::HashMap, sync::Arc}; @@ -100,6 +98,16 @@ impl MemoryKeystore { let sig = self.pair::(key_type, public).map(|pair| pair.sign(msg)); Ok(sig) } + + fn vrf_sign( + &self, + key_type: KeyTypeId, + public: &T::Public, + transcript: &T::VrfInput, + ) -> Result, Error> { + let sig = self.pair::(key_type, public).map(|pair| pair.vrf_sign(transcript)); + Ok(sig) + } } impl Keystore for MemoryKeystore { @@ -128,14 +136,9 @@ impl Keystore for MemoryKeystore { &self, key_type: KeyTypeId, public: &sr25519::Public, - transcript_data: VRFTranscriptData, - ) -> Result, Error> { - let sig = self.pair::(key_type, public).map(|pair| { - let transcript = make_transcript(transcript_data); - let (inout, proof, _) = pair.as_ref().vrf_sign(transcript); - VRFSignature { output: inout.to_output(), proof } - }); - Ok(sig) + transcript: &sr25519::vrf::VrfTranscript, + ) -> Result, Error> { + self.vrf_sign::(key_type, public, transcript) } fn ed25519_public_keys(&self, key_type: KeyTypeId) -> Vec { @@ -225,7 +228,6 @@ impl Into for MemoryKeystore { #[cfg(test)] mod tests { use super::*; - use crate::vrf::VRFTranscriptValue; use sp_core::{ sr25519, testing::{ECDSA, ED25519, SR25519}, @@ -265,23 +267,23 @@ mod tests { let secret_uri = "//Alice"; let key_pair = sr25519::Pair::from_string(secret_uri, None).expect("Generates key pair"); - let transcript_data = VRFTranscriptData { - label: b"Test", - items: vec![ - ("one", VRFTranscriptValue::U64(1)), - ("two", VRFTranscriptValue::U64(2)), - ("three", VRFTranscriptValue::Bytes("test".as_bytes().to_vec())), + let transcript = sr25519::vrf::VrfTranscript::new( + b"Test", + &[ + (b"one", &1_u64.to_le_bytes()), + (b"two", &2_u64.to_le_bytes()), + (b"three", "test".as_bytes()), ], - }; + ); - let result = store.sr25519_vrf_sign(SR25519, &key_pair.public(), transcript_data.clone()); + let result = store.sr25519_vrf_sign(SR25519, &key_pair.public(), &transcript); assert!(result.unwrap().is_none()); store .insert(SR25519, secret_uri, key_pair.public().as_ref()) .expect("Inserts unknown key"); - let result = store.sr25519_vrf_sign(SR25519, &key_pair.public(), transcript_data); + let result = store.sr25519_vrf_sign(SR25519, &key_pair.public(), &transcript); assert!(result.unwrap().is_some()); } diff --git a/primitives/keystore/src/vrf.rs b/primitives/keystore/src/vrf.rs deleted file mode 100644 index e089336c1485b..0000000000000 --- a/primitives/keystore/src/vrf.rs +++ /dev/null @@ -1,94 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: Apache-2.0 - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! VRF-specifc data types and helpers - -use codec::Encode; -use merlin::Transcript; -use schnorrkel::vrf::{VRFOutput, VRFProof}; - -/// An enum whose variants represent possible -/// accepted values to construct the VRF transcript -#[derive(Clone, Encode)] -#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] -pub enum VRFTranscriptValue { - /// Value is an array of bytes - Bytes(Vec), - /// Value is a u64 integer - U64(u64), -} -/// VRF Transcript data -#[derive(Clone, Encode)] -pub struct VRFTranscriptData { - /// The transcript's label - pub label: &'static [u8], - /// Additional data to be registered into the transcript - pub items: Vec<(&'static str, VRFTranscriptValue)>, -} -/// VRF signature data -pub struct VRFSignature { - /// The VRFOutput serialized - pub output: VRFOutput, - /// The calculated VRFProof - pub proof: VRFProof, -} - -/// Construct a `Transcript` object from data. -/// -/// Returns `merlin::Transcript` -pub fn make_transcript(data: VRFTranscriptData) -> Transcript { - let mut transcript = Transcript::new(data.label); - for (label, value) in data.items.into_iter() { - match value { - VRFTranscriptValue::Bytes(bytes) => { - transcript.append_message(label.as_bytes(), &bytes); - }, - VRFTranscriptValue::U64(val) => { - transcript.append_u64(label.as_bytes(), val); - }, - } - } - transcript -} - -#[cfg(test)] -mod tests { - use super::*; - use rand::RngCore; - use rand_chacha::{rand_core::SeedableRng, ChaChaRng}; - - #[test] - fn transcript_creation_matches() { - let mut orig_transcript = Transcript::new(b"My label"); - orig_transcript.append_u64(b"one", 1); - orig_transcript.append_message(b"two", "test".as_bytes()); - - let new_transcript = make_transcript(VRFTranscriptData { - label: b"My label", - items: vec![ - ("one", VRFTranscriptValue::U64(1)), - ("two", VRFTranscriptValue::Bytes("test".as_bytes().to_vec())), - ], - }); - let test = |t: Transcript| -> [u8; 16] { - let mut b = [0u8; 16]; - t.build_rng().finalize(&mut ChaChaRng::from_seed([0u8; 32])).fill_bytes(&mut b); - b - }; - debug_assert!(test(orig_transcript) == test(new_transcript)); - } -}