mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-12 17:01:09 +00:00
VRF refactory (#13889)
* First iteration to encapsulate schnorrkel and merlin usage * Remove schnorkel direct dependency from BABE pallet * Remove schnorrkel direct dependency from BABE client * Trivial renaming for VrfTranscript data and value * Better errors * Expose a function to get a schnorrkel friendly transcript * Keep the vrf signature stuff together (preventing some clones around) * Fix tests * Remove vrf agnostic transcript and define it as an associated type for VrfSigner and VrfVerifier * Fix babe pallet mock * Inner types are required to be public for polkadot * Update client/consensus/babe/src/verification.rs Co-authored-by: Koute <koute@users.noreply.github.com> * Nit * Remove Deref implementations * make_bytes as a method * Trigger CI --------- Co-authored-by: Koute <koute@users.noreply.github.com>
This commit is contained in:
Generated
-20
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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" }
|
||||
|
||||
@@ -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()))
|
||||
|
||||
@@ -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<B: BlockT> {
|
||||
/// 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),
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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<B: BlockT + Sized>(
|
||||
epoch: &Epoch,
|
||||
c: (u64, u64),
|
||||
) -> Result<(), Error<B>> {
|
||||
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<B: BlockT + Sized>(
|
||||
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)))?
|
||||
};
|
||||
|
||||
let threshold =
|
||||
calculate_primary_threshold(c, &epoch.authorities, pre_digest.authority_index as usize);
|
||||
|
||||
if !check_primary_threshold(&inout, threshold) {
|
||||
return Err(babe_err(Error::VRFVerificationOfBlockFailed(author.clone(), threshold)))
|
||||
}
|
||||
|
||||
Ok(())
|
||||
} else {
|
||||
Err(babe_err(Error::BadSignature(pre_hash)))
|
||||
if !AuthorityPair::verify(&signature, pre_hash, authority_id) {
|
||||
return Err(babe_err(Error::BadSignature(pre_hash)))
|
||||
}
|
||||
|
||||
let transcript = make_transcript(&epoch.randomness, pre_digest.slot, epoch_index);
|
||||
|
||||
if !authority_id.as_inner_ref().vrf_verify(&transcript, &pre_digest.vrf_signature) {
|
||||
return Err(babe_err(Error::VrfVerificationFailed))
|
||||
}
|
||||
|
||||
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<B: BlockT>(
|
||||
signature: AuthoritySignature,
|
||||
epoch: &Epoch,
|
||||
) -> Result<(), Error<B>> {
|
||||
// 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<B: BlockT>(
|
||||
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<B: BlockT>(
|
||||
signature: AuthoritySignature,
|
||||
epoch: &Epoch,
|
||||
) -> Result<(), Error<B>> {
|
||||
// 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<B: BlockT>(
|
||||
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);
|
||||
|
||||
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)))?;
|
||||
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::BadSignature(pre_hash))
|
||||
if !AuthorityPair::verify(&signature, pre_hash.as_ref(), author) {
|
||||
return Err(Error::BadSignature(pre_hash))
|
||||
}
|
||||
|
||||
let transcript = make_transcript(&epoch.randomness, pre_digest.slot, epoch_index);
|
||||
|
||||
if !author.as_inner_ref().vrf_verify(&transcript, &pre_digest.vrf_signature) {
|
||||
return Err(Error::VrfVerificationFailed)
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -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<T: CorePair + VrfSigner>(
|
||||
&self,
|
||||
key_type: KeyTypeId,
|
||||
public: &T::Public,
|
||||
transcript: &T::VrfInput,
|
||||
) -> std::result::Result<Option<T::VrfSignature>, TraitError> {
|
||||
let sig = self
|
||||
.0
|
||||
.read()
|
||||
.key_pair_by_type::<T>(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<Option<VRFSignature>, TraitError> {
|
||||
let sig = self.0.read().key_pair_by_type::<sr25519::Pair>(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<Option<sr25519::vrf::VrfSignature>, TraitError> {
|
||||
self.vrf_sign::<sr25519::Pair>(key_type, public, transcript)
|
||||
}
|
||||
|
||||
fn ed25519_public_keys(&self, key_type: KeyTypeId) -> Vec<ed25519::Public> {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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<T> = StorageValue<_, schnorrkel::Randomness, ValueQuery>;
|
||||
pub type Randomness<T> = 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<T> = StorageValue<_, schnorrkel::Randomness, ValueQuery>;
|
||||
pub(super) type NextRandomness<T> = StorageValue<_, BabeRandomness, ValueQuery>;
|
||||
|
||||
/// Next epoch authorities.
|
||||
#[pallet::storage]
|
||||
@@ -254,7 +254,7 @@ pub mod pallet {
|
||||
_,
|
||||
Twox64Concat,
|
||||
u32,
|
||||
BoundedVec<schnorrkel::Randomness, ConstU32<UNDER_CONSTRUCTION_SEGMENT_LENGTH>>,
|
||||
BoundedVec<BabeRandomness, ConstU32<UNDER_CONSTRUCTION_SEGMENT_LENGTH>>,
|
||||
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<T> =
|
||||
StorageValue<_, Option<schnorrkel::Randomness>, ValueQuery>;
|
||||
pub(super) type AuthorVrfRandomness<T> = StorageValue<_, Option<BabeRandomness>, 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<schnorrkel::Randomness> = Authorities::<T>::get()
|
||||
if let Some(vrf_signature) = pre_digest.vrf_signature() {
|
||||
let randomness: Option<BabeRandomness> = Authorities::<T>::get()
|
||||
.get(authority_index as usize)
|
||||
.and_then(|(authority, _)| {
|
||||
schnorrkel::PublicKey::from_bytes(authority.as_slice()).ok()
|
||||
})
|
||||
.and_then(|pubkey| {
|
||||
let current_slot = CurrentSlot::<T>::get();
|
||||
|
||||
let public = authority.as_inner_ref();
|
||||
let transcript = sp_consensus_babe::make_transcript(
|
||||
&Self::randomness(),
|
||||
current_slot,
|
||||
CurrentSlot::<T>::get(),
|
||||
EpochIndex::<T>::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());
|
||||
debug_assert!({
|
||||
use sp_core::crypto::VrfVerifier;
|
||||
public.vrf_verify(&transcript, &vrf_signature)
|
||||
});
|
||||
|
||||
vrf_output.0.attach_input_hash(&pubkey, transcript).ok()
|
||||
})
|
||||
.map(|inout| inout.make_bytes(sp_consensus_babe::BABE_VRF_INOUT_CONTEXT));
|
||||
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<T: Config> FindAuthor<u32> for Pallet<T> {
|
||||
fn find_author<'a, I>(digests: I) -> Option<u32>
|
||||
where
|
||||
@@ -737,7 +735,7 @@ impl<T: Config> Pallet<T> {
|
||||
<frame_system::Pallet<T>>::deposit_log(log)
|
||||
}
|
||||
|
||||
fn deposit_randomness(randomness: &schnorrkel::Randomness) {
|
||||
fn deposit_randomness(randomness: &BabeRandomness) {
|
||||
let segment_idx = SegmentIndex::<T>::get();
|
||||
let mut segment = UnderConstruction::<T>::get(&segment_idx);
|
||||
if segment.try_push(*randomness).is_ok() {
|
||||
@@ -831,7 +829,7 @@ impl<T: Config> Pallet<T> {
|
||||
|
||||
/// 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::<T>::get();
|
||||
let segment_idx: u32 = SegmentIndex::<T>::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<Item = schnorrkel::Randomness>,
|
||||
rho: impl Iterator<Item = BabeRandomness>,
|
||||
rho_size_hint: Option<usize>,
|
||||
) -> 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());
|
||||
|
||||
|
||||
@@ -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::<Randomness>(sp_consensus_babe::RANDOMNESS_VRF_CONTEXT, &transcript);
|
||||
|
||||
(signature, randomness)
|
||||
}
|
||||
|
||||
pub fn new_test_ext(authorities_len: usize) -> sp_io::TestExternalities {
|
||||
|
||||
@@ -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<T>(sp_std::marker::PhantomData<T>);
|
||||
impl<T: Config> RandomnessT<T::Hash, T::BlockNumber> for RandomnessFromTwoEpochsAgo<T> {
|
||||
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::<T>::get()[..]);
|
||||
|
||||
(T::Hashing::hash(&subject[..]), EpochStart::<T>::get().0)
|
||||
@@ -142,7 +142,7 @@ impl<T: Config> RandomnessT<T::Hash, T::BlockNumber> for RandomnessFromTwoEpochs
|
||||
impl<T: Config> RandomnessT<T::Hash, T::BlockNumber> for RandomnessFromOneEpochAgo<T> {
|
||||
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::<T>::get()[..]);
|
||||
|
||||
(T::Hashing::hash(&subject[..]), EpochStart::<T>::get().1)
|
||||
@@ -153,7 +153,7 @@ impl<T: Config> RandomnessT<Option<T::Hash>, T::BlockNumber> for ParentBlockRand
|
||||
fn random(subject: &[u8]) -> (Option<T::Hash>, T::BlockNumber) {
|
||||
let random = AuthorVrfRandomness::<T>::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[..])
|
||||
|
||||
@@ -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<F>(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);
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
[package]
|
||||
name = "sp-consensus-vrf"
|
||||
version = "0.10.0-dev"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
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",
|
||||
]
|
||||
@@ -1,3 +0,0 @@
|
||||
Primitives for VRF-based consensus engines.
|
||||
|
||||
License: Apache-2.0
|
||||
@@ -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;
|
||||
@@ -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<u8> {
|
||||
self.0.as_bytes().encode()
|
||||
}
|
||||
}
|
||||
|
||||
impl EncodeLike for VRFOutput {}
|
||||
|
||||
impl Decode for VRFOutput {
|
||||
fn decode<R: codec::Input>(i: &mut R) -> Result<Self, codec::Error> {
|
||||
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<Self, Self::Error> {
|
||||
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<core::cmp::Ordering> {
|
||||
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<u8> {
|
||||
self.0.to_bytes().encode()
|
||||
}
|
||||
}
|
||||
|
||||
impl EncodeLike for VRFProof {}
|
||||
|
||||
impl Decode for VRFProof {
|
||||
fn decode<R: codec::Input>(i: &mut R) -> Result<Self, codec::Error> {
|
||||
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<Self, Self::Error> {
|
||||
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];
|
||||
@@ -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",
|
||||
]
|
||||
|
||||
@@ -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))]
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
let mut messages: Vec<merlin::Transcript> = messages
|
||||
.into_iter()
|
||||
.map(|msg| signing_context(SIGNING_CTX).bytes(msg))
|
||||
.collect();
|
||||
/// VRF output type suitable for schnorrkel operations.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct VrfOutput(pub schnorrkel::vrf::VRFOutput);
|
||||
|
||||
schnorrkel::verify_batch(&mut messages, &sr_signatures, &sr_pub_keys, true).is_ok()
|
||||
impl Encode for VrfOutput {
|
||||
fn encode(&self) -> Vec<u8> {
|
||||
self.0.as_bytes().encode()
|
||||
}
|
||||
}
|
||||
|
||||
impl Decode for VrfOutput {
|
||||
fn decode<R: codec::Input>(i: &mut R) -> Result<Self, codec::Error> {
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
/// 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<u8> {
|
||||
self.0.to_bytes().encode()
|
||||
}
|
||||
}
|
||||
|
||||
impl Decode for VrfProof {
|
||||
fn decode<R: codec::Input>(i: &mut R) -> Result<Self, codec::Error> {
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
#[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<B: Default + AsMut<[u8]>>(
|
||||
&self,
|
||||
context: &[u8],
|
||||
transcript: &VrfTranscript,
|
||||
) -> B {
|
||||
let inout = self.0.vrf_create_hash(transcript.0.clone());
|
||||
inout.make_bytes::<B>(context)
|
||||
}
|
||||
}
|
||||
|
||||
impl Public {
|
||||
/// Generate bytes from the given VRF configuration.
|
||||
pub fn make_bytes<B: Default + AsMut<[u8]>>(
|
||||
&self,
|
||||
context: &[u8],
|
||||
transcript: &VrfTranscript,
|
||||
output: &VrfOutput,
|
||||
) -> Result<B, codec::Error> {
|
||||
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::<B>(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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -16,10 +16,9 @@
|
||||
// limitations under the License.
|
||||
|
||||
//! Keystore traits
|
||||
pub mod testing;
|
||||
pub mod vrf;
|
||||
|
||||
use crate::vrf::{VRFSignature, VRFTranscriptData};
|
||||
pub mod testing;
|
||||
|
||||
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<Option<VRFSignature>, Error>;
|
||||
transcript: &sr25519::vrf::VrfTranscript,
|
||||
) -> Result<Option<sr25519::vrf::VrfSignature>, Error>;
|
||||
|
||||
/// Returns all ed25519 public keys for the given key type.
|
||||
fn ed25519_public_keys(&self, key_type: KeyTypeId) -> Vec<ed25519::Public>;
|
||||
|
||||
@@ -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::<T>(key_type, public).map(|pair| pair.sign(msg));
|
||||
Ok(sig)
|
||||
}
|
||||
|
||||
fn vrf_sign<T: Pair + VrfSigner>(
|
||||
&self,
|
||||
key_type: KeyTypeId,
|
||||
public: &T::Public,
|
||||
transcript: &T::VrfInput,
|
||||
) -> Result<Option<T::VrfSignature>, Error> {
|
||||
let sig = self.pair::<T>(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<Option<VRFSignature>, Error> {
|
||||
let sig = self.pair::<sr25519::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<Option<sr25519::vrf::VrfSignature>, Error> {
|
||||
self.vrf_sign::<sr25519::Pair>(key_type, public, transcript)
|
||||
}
|
||||
|
||||
fn ed25519_public_keys(&self, key_type: KeyTypeId) -> Vec<ed25519::Public> {
|
||||
@@ -225,7 +228,6 @@ impl Into<KeystorePtr> 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());
|
||||
}
|
||||
|
||||
@@ -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<u8>),
|
||||
/// 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));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user