From 8186fae0d183a1546d5c4f1b83432fd071451792 Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Thu, 31 Aug 2023 13:38:11 +0200 Subject: [PATCH] Sassafras primitives (#1249) * Introduce Sassafras primitives * Keystore workaround * Fix doc * Use in keystore * Improve bandersnatch vrf docs * Apply review suggestions * Update README * Docs improvement * Docs fix --- Cargo.lock | 15 ++ Cargo.toml | 1 + substrate/client/keystore/src/local.rs | 248 +++++++++--------- .../primitives/consensus/sassafras/Cargo.toml | 50 ++++ .../primitives/consensus/sassafras/README.md | 12 + .../consensus/sassafras/src/digests.rs | 98 +++++++ .../primitives/consensus/sassafras/src/lib.rs | 169 ++++++++++++ .../consensus/sassafras/src/ticket.rs | 91 +++++++ .../primitives/consensus/sassafras/src/vrf.rs | 104 ++++++++ substrate/primitives/core/src/bandersnatch.rs | 195 ++++++++------ substrate/primitives/core/src/crypto.rs | 2 + substrate/primitives/keystore/src/lib.rs | 13 + 12 files changed, 797 insertions(+), 201 deletions(-) create mode 100644 substrate/primitives/consensus/sassafras/Cargo.toml create mode 100644 substrate/primitives/consensus/sassafras/README.md create mode 100644 substrate/primitives/consensus/sassafras/src/digests.rs create mode 100644 substrate/primitives/consensus/sassafras/src/lib.rs create mode 100644 substrate/primitives/consensus/sassafras/src/ticket.rs create mode 100644 substrate/primitives/consensus/sassafras/src/vrf.rs diff --git a/Cargo.lock b/Cargo.lock index becee4e11c9de..0e0e47f955ad0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17049,6 +17049,21 @@ dependencies = [ "sp-std", ] +[[package]] +name = "sp-consensus-sassafras" +version = "0.3.4-dev" +dependencies = [ + "parity-scale-codec", + "scale-info", + "serde", + "sp-api", + "sp-application-crypto", + "sp-consensus-slots", + "sp-core", + "sp-runtime", + "sp-std", +] + [[package]] name = "sp-consensus-slots" version = "0.10.0-dev" diff --git a/Cargo.toml b/Cargo.toml index 82b196e7a47d6..4db27b98e9078 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -385,6 +385,7 @@ members = [ "substrate/primitives/consensus/common", "substrate/primitives/consensus/grandpa", "substrate/primitives/consensus/pow", + "substrate/primitives/consensus/sassafras", "substrate/primitives/consensus/slots", "substrate/primitives/core", "substrate/primitives/core/hashing", diff --git a/substrate/client/keystore/src/local.rs b/substrate/client/keystore/src/local.rs index 97bc7c71a4a58..c77f023e0f0af 100644 --- a/substrate/client/keystore/src/local.rs +++ b/substrate/client/keystore/src/local.rs @@ -19,10 +19,6 @@ use parking_lot::RwLock; use sp_application_crypto::{AppCrypto, AppPair, IsWrappedBy}; -#[cfg(feature = "bandersnatch-experimental")] -use sp_core::bandersnatch; -#[cfg(feature = "bls-experimental")] -use sp_core::{bls377, bls381}; use sp_core::{ crypto::{ByteArray, ExposeSecret, KeyTypeId, Pair as CorePair, SecretString, VrfSecret}, ecdsa, ed25519, sr25519, @@ -36,6 +32,14 @@ use std::{ sync::Arc, }; +sp_keystore::bandersnatch_experimental_enabled! { +use sp_core::bandersnatch; +} + +sp_keystore::bls_experimental_enabled! { +use sp_core::{bls377, bls381}; +} + use crate::{Error, Result}; /// A local based keystore that is either memory-based or filesystem-based. @@ -132,6 +136,25 @@ impl LocalKeystore { } impl Keystore for LocalKeystore { + fn insert( + &self, + key_type: KeyTypeId, + suri: &str, + public: &[u8], + ) -> std::result::Result<(), ()> { + self.0.write().insert(key_type, suri, public).map_err(|_| ()) + } + + fn keys(&self, key_type: KeyTypeId) -> std::result::Result>, TraitError> { + self.0.read().raw_public_keys(key_type).map_err(|e| e.into()) + } + + fn has_keys(&self, public_keys: &[(Vec, KeyTypeId)]) -> bool { + public_keys + .iter() + .all(|(p, t)| self.0.read().key_phrase_by_type(p, *t).ok().flatten().is_some()) + } + fn sr25519_public_keys(&self, key_type: KeyTypeId) -> Vec { self.public_keys::(key_type) } @@ -236,140 +259,113 @@ impl Keystore for LocalKeystore { Ok(sig) } - #[cfg(feature = "bandersnatch-experimental")] - fn bandersnatch_public_keys(&self, key_type: KeyTypeId) -> Vec { - self.public_keys::(key_type) - } - - /// Generate a new pair compatible with the 'bandersnatch' signature scheme. - /// - /// If `[seed]` is `Some` then the key will be ephemeral and stored in memory. - #[cfg(feature = "bandersnatch-experimental")] - fn bandersnatch_generate_new( - &self, - key_type: KeyTypeId, - seed: Option<&str>, - ) -> std::result::Result { - self.generate_new::(key_type, seed) - } - - #[cfg(feature = "bandersnatch-experimental")] - fn bandersnatch_sign( - &self, - key_type: KeyTypeId, - public: &bandersnatch::Public, - msg: &[u8], - ) -> std::result::Result, TraitError> { - self.sign::(key_type, public, msg) - } - - #[cfg(feature = "bandersnatch-experimental")] - fn bandersnatch_vrf_sign( - &self, - key_type: KeyTypeId, - public: &bandersnatch::Public, - data: &bandersnatch::vrf::VrfSignData, - ) -> std::result::Result, TraitError> { - self.vrf_sign::(key_type, public, data) - } + sp_keystore::bandersnatch_experimental_enabled! { + fn bandersnatch_public_keys(&self, key_type: KeyTypeId) -> Vec { + self.public_keys::(key_type) + } - #[cfg(feature = "bandersnatch-experimental")] - fn bandersnatch_vrf_output( - &self, - key_type: KeyTypeId, - public: &bandersnatch::Public, - input: &bandersnatch::vrf::VrfInput, - ) -> std::result::Result, TraitError> { - self.vrf_output::(key_type, public, input) - } + /// Generate a new pair compatible with the 'bandersnatch' signature scheme. + /// + /// If `[seed]` is `Some` then the key will be ephemeral and stored in memory. + fn bandersnatch_generate_new( + &self, + key_type: KeyTypeId, + seed: Option<&str>, + ) -> std::result::Result { + self.generate_new::(key_type, seed) + } - #[cfg(feature = "bandersnatch-experimental")] - fn bandersnatch_ring_vrf_sign( - &self, - key_type: KeyTypeId, - public: &bandersnatch::Public, - data: &bandersnatch::vrf::VrfSignData, - prover: &bandersnatch::ring_vrf::RingProver, - ) -> std::result::Result, TraitError> { - let sig = self - .0 - .read() - .key_pair_by_type::(public, key_type)? - .map(|pair| pair.ring_vrf_sign(data, prover)); - Ok(sig) - } + fn bandersnatch_sign( + &self, + key_type: KeyTypeId, + public: &bandersnatch::Public, + msg: &[u8], + ) -> std::result::Result, TraitError> { + self.sign::(key_type, public, msg) + } - #[cfg(feature = "bls-experimental")] - fn bls381_public_keys(&self, key_type: KeyTypeId) -> Vec { - self.public_keys::(key_type) - } + fn bandersnatch_vrf_sign( + &self, + key_type: KeyTypeId, + public: &bandersnatch::Public, + data: &bandersnatch::vrf::VrfSignData, + ) -> std::result::Result, TraitError> { + self.vrf_sign::(key_type, public, data) + } - #[cfg(feature = "bls-experimental")] - /// Generate a new pair compatible with the 'bls381' signature scheme. - /// - /// If `[seed]` is `Some` then the key will be ephemeral and stored in memory. - fn bls381_generate_new( - &self, - key_type: KeyTypeId, - seed: Option<&str>, - ) -> std::result::Result { - self.generate_new::(key_type, seed) - } + fn bandersnatch_vrf_output( + &self, + key_type: KeyTypeId, + public: &bandersnatch::Public, + input: &bandersnatch::vrf::VrfInput, + ) -> std::result::Result, TraitError> { + self.vrf_output::(key_type, public, input) + } - #[cfg(feature = "bls-experimental")] - fn bls381_sign( - &self, - key_type: KeyTypeId, - public: &bls381::Public, - msg: &[u8], - ) -> std::result::Result, TraitError> { - self.sign::(key_type, public, msg) + fn bandersnatch_ring_vrf_sign( + &self, + key_type: KeyTypeId, + public: &bandersnatch::Public, + data: &bandersnatch::vrf::VrfSignData, + prover: &bandersnatch::ring_vrf::RingProver, + ) -> std::result::Result, TraitError> { + let sig = self + .0 + .read() + .key_pair_by_type::(public, key_type)? + .map(|pair| pair.ring_vrf_sign(data, prover)); + Ok(sig) + } } - #[cfg(feature = "bls-experimental")] - fn bls377_public_keys(&self, key_type: KeyTypeId) -> Vec { - self.public_keys::(key_type) - } + sp_keystore::bls_experimental_enabled! { + fn bls381_public_keys(&self, key_type: KeyTypeId) -> Vec { + self.public_keys::(key_type) + } - #[cfg(feature = "bls-experimental")] - /// Generate a new pair compatible with the 'bls377' signature scheme. - /// - /// If `[seed]` is `Some` then the key will be ephemeral and stored in memory. - fn bls377_generate_new( - &self, - key_type: KeyTypeId, - seed: Option<&str>, - ) -> std::result::Result { - self.generate_new::(key_type, seed) - } + /// Generate a new pair compatible with the 'bls381' signature scheme. + /// + /// If `[seed]` is `Some` then the key will be ephemeral and stored in memory. + fn bls381_generate_new( + &self, + key_type: KeyTypeId, + seed: Option<&str>, + ) -> std::result::Result { + self.generate_new::(key_type, seed) + } - #[cfg(feature = "bls-experimental")] - fn bls377_sign( - &self, - key_type: KeyTypeId, - public: &bls377::Public, - msg: &[u8], - ) -> std::result::Result, TraitError> { - self.sign::(key_type, public, msg) - } + fn bls381_sign( + &self, + key_type: KeyTypeId, + public: &bls381::Public, + msg: &[u8], + ) -> std::result::Result, TraitError> { + self.sign::(key_type, public, msg) + } - fn insert( - &self, - key_type: KeyTypeId, - suri: &str, - public: &[u8], - ) -> std::result::Result<(), ()> { - self.0.write().insert(key_type, suri, public).map_err(|_| ()) - } + fn bls377_public_keys(&self, key_type: KeyTypeId) -> Vec { + self.public_keys::(key_type) + } - fn keys(&self, key_type: KeyTypeId) -> std::result::Result>, TraitError> { - self.0.read().raw_public_keys(key_type).map_err(|e| e.into()) - } + /// Generate a new pair compatible with the 'bls377' signature scheme. + /// + /// If `[seed]` is `Some` then the key will be ephemeral and stored in memory. + fn bls377_generate_new( + &self, + key_type: KeyTypeId, + seed: Option<&str>, + ) -> std::result::Result { + self.generate_new::(key_type, seed) + } - fn has_keys(&self, public_keys: &[(Vec, KeyTypeId)]) -> bool { - public_keys - .iter() - .all(|(p, t)| self.0.read().key_phrase_by_type(p, *t).ok().flatten().is_some()) + fn bls377_sign( + &self, + key_type: KeyTypeId, + public: &bls377::Public, + msg: &[u8], + ) -> std::result::Result, TraitError> { + self.sign::(key_type, public, msg) + } } } diff --git a/substrate/primitives/consensus/sassafras/Cargo.toml b/substrate/primitives/consensus/sassafras/Cargo.toml new file mode 100644 index 0000000000000..56ae0087099e1 --- /dev/null +++ b/substrate/primitives/consensus/sassafras/Cargo.toml @@ -0,0 +1,50 @@ +[package] +name = "sp-consensus-sassafras" +version = "0.3.4-dev" +authors = ["Parity Technologies "] +description = "Primitives for Sassafras consensus" +edition = "2021" +license = "Apache-2.0" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +documentation = "https://docs.rs/sp-consensus-sassafras" +readme = "README.md" +publish = false + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +scale-codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false } +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } +serde = { version = "1.0.163", default-features = false, features = ["derive"], optional = true } +sp-api = { version = "4.0.0-dev", default-features = false, path = "../../api" } +sp-application-crypto = { version = "23.0.0", default-features = false, path = "../../application-crypto", features = ["bandersnatch-experimental"] } +sp-consensus-slots = { version = "0.10.0-dev", default-features = false, path = "../slots" } +sp-core = { version = "21.0.0", default-features = false, path = "../../core", features = ["bandersnatch-experimental"] } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../runtime" } +sp-std = { version = "8.0.0", default-features = false, path = "../../std" } + +[features] +default = [ "std" ] +std = [ + "scale-codec/std", + "scale-info/std", + "serde/std", + "sp-api/std", + "sp-application-crypto/std", + "sp-consensus-slots/std", + "sp-core/std", + "sp-runtime/std", + "sp-std/std", +] + +# Serde support without relying on std features. +serde = [ + "dep:serde", + "scale-info/serde", + "sp-application-crypto/serde", + "sp-consensus-slots/serde", + "sp-core/serde", + "sp-runtime/serde", +] diff --git a/substrate/primitives/consensus/sassafras/README.md b/substrate/primitives/consensus/sassafras/README.md new file mode 100644 index 0000000000000..5024d1bf700dc --- /dev/null +++ b/substrate/primitives/consensus/sassafras/README.md @@ -0,0 +1,12 @@ +Primitives for SASSAFRAS. + +# ⚠️ WARNING ⚠️ + +The crate interfaces and structures are highly experimental and may be subject +to significant changes. + +Depends on upstream experimental feature: `bandersnatch-experimental`. + +These structs were mostly extracted from the main SASSAFRAS protocol PR: https://github.com/paritytech/substrate/pull/11879. + +Tracking issue: https://github.com/paritytech/polkadot-sdk/issues/41 diff --git a/substrate/primitives/consensus/sassafras/src/digests.rs b/substrate/primitives/consensus/sassafras/src/digests.rs new file mode 100644 index 0000000000000..95a305099de55 --- /dev/null +++ b/substrate/primitives/consensus/sassafras/src/digests.rs @@ -0,0 +1,98 @@ +// 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. + +//! Sassafras digests structures and helpers. + +use crate::{ + ticket::TicketClaim, vrf::VrfSignature, AuthorityId, AuthorityIndex, AuthoritySignature, + EpochConfiguration, Randomness, Slot, SASSAFRAS_ENGINE_ID, +}; + +use scale_codec::{Decode, Encode, MaxEncodedLen}; +use scale_info::TypeInfo; + +use sp_runtime::{DigestItem, RuntimeDebug}; +use sp_std::vec::Vec; + +/// Epoch slot claim digest entry. +/// +/// This is mandatory for each block. +#[derive(Clone, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)] +pub struct SlotClaim { + /// Authority index that claimed the slot. + pub authority_idx: AuthorityIndex, + /// Corresponding slot number. + pub slot: Slot, + /// Slot claim VRF signature. + pub vrf_signature: VrfSignature, + /// Ticket auxiliary information for claim check. + pub ticket_claim: Option, +} + +/// Information about the next epoch. +/// +/// This is mandatory in the first block of each epoch. +#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug)] +pub struct NextEpochDescriptor { + /// Authorities list. + pub authorities: Vec, + /// Epoch randomness. + pub randomness: Randomness, + /// Epoch configurable parameters. + /// + /// If not present previous epoch parameters are used. + pub config: Option, +} + +/// Runtime digest entries. +/// +/// Entries which may be generated by on-chain code. +#[derive(Decode, Encode, Clone, PartialEq, Eq)] +pub enum ConsensusLog { + /// Provides information about the next epoch parameters. + #[codec(index = 1)] + NextEpochData(NextEpochDescriptor), + /// Disable the authority with given index. + #[codec(index = 2)] + OnDisabled(AuthorityIndex), +} + +impl TryFrom<&DigestItem> for SlotClaim { + type Error = (); + fn try_from(item: &DigestItem) -> Result { + item.pre_runtime_try_to(&SASSAFRAS_ENGINE_ID).ok_or(()) + } +} + +impl From<&SlotClaim> for DigestItem { + fn from(claim: &SlotClaim) -> Self { + DigestItem::PreRuntime(SASSAFRAS_ENGINE_ID, claim.encode()) + } +} + +impl TryFrom<&DigestItem> for AuthoritySignature { + type Error = (); + fn try_from(item: &DigestItem) -> Result { + item.seal_try_to(&SASSAFRAS_ENGINE_ID).ok_or(()) + } +} + +impl From<&AuthoritySignature> for DigestItem { + fn from(signature: &AuthoritySignature) -> Self { + DigestItem::Seal(SASSAFRAS_ENGINE_ID, signature.encode()) + } +} diff --git a/substrate/primitives/consensus/sassafras/src/lib.rs b/substrate/primitives/consensus/sassafras/src/lib.rs new file mode 100644 index 0000000000000..e421e771d406a --- /dev/null +++ b/substrate/primitives/consensus/sassafras/src/lib.rs @@ -0,0 +1,169 @@ +// 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 Sassafras consensus. + +#![deny(warnings)] +#![forbid(unsafe_code, missing_docs, unused_variables, unused_imports)] +#![cfg_attr(not(feature = "std"), no_std)] + +use scale_codec::{Decode, Encode, MaxEncodedLen}; +use scale_info::TypeInfo; +use sp_core::crypto::KeyTypeId; +use sp_runtime::{ConsensusEngineId, RuntimeDebug}; +use sp_std::vec::Vec; + +pub use sp_consensus_slots::{Slot, SlotDuration}; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +pub mod digests; +pub mod ticket; +pub mod vrf; + +pub use ticket::{ + ticket_id_threshold, EphemeralPublic, EphemeralSignature, TicketBody, TicketClaim, + TicketEnvelope, TicketId, +}; + +mod app { + use sp_application_crypto::{app_crypto, bandersnatch, key_types::SASSAFRAS}; + app_crypto!(bandersnatch, SASSAFRAS); +} + +/// Key type identifier. +pub const KEY_TYPE: KeyTypeId = sp_application_crypto::key_types::SASSAFRAS; + +/// Consensus engine identifier. +pub const SASSAFRAS_ENGINE_ID: ConsensusEngineId = *b"SASS"; + +/// VRF output length for per-slot randomness. +pub const RANDOMNESS_LENGTH: usize = 32; + +/// Index of an authority. +pub type AuthorityIndex = u32; + +/// Sassafras authority keypair. Necessarily equivalent to the schnorrkel public key used in +/// the main Sassafras module. If that ever changes, then this must, too. +#[cfg(feature = "std")] +pub type AuthorityPair = app::Pair; + +/// Sassafras authority signature. +pub type AuthoritySignature = app::Signature; + +/// Sassafras authority identifier. Necessarily equivalent to the schnorrkel public key used in +/// the main Sassafras module. If that ever changes, then this must, too. +pub type AuthorityId = app::Public; + +/// Weight of a Sassafras block. +/// Primary blocks have a weight of 1 whereas secondary blocks have a weight of 0. +pub type SassafrasBlockWeight = u32; + +/// An equivocation proof for multiple block authorships on the same slot (i.e. double vote). +pub type EquivocationProof = sp_consensus_slots::EquivocationProof; + +/// Randomness required by some protocol's operations. +pub type Randomness = [u8; RANDOMNESS_LENGTH]; + +/// Configuration data that can be modified on epoch change. +#[derive( + Copy, Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, MaxEncodedLen, TypeInfo, Default, +)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct EpochConfiguration { + /// Tickets threshold redundancy factor. + pub redundancy_factor: u32, + /// Tickets attempts for each validator. + pub attempts_number: u32, +} + +/// Sassafras epoch information +#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode, TypeInfo)] +pub struct Epoch { + /// The epoch index. + pub epoch_idx: u64, + /// The starting slot of the epoch. + pub start_slot: Slot, + /// Slot duration in milliseconds. + pub slot_duration: SlotDuration, + /// Duration of epoch in slots. + pub epoch_duration: u64, + /// Authorities for the epoch. + pub authorities: Vec, + /// Randomness for the epoch. + pub randomness: Randomness, + /// Epoch configuration. + pub config: EpochConfiguration, +} + +/// An opaque type used to represent the key ownership proof at the runtime API boundary. +/// +/// The inner value is an encoded representation of the actual key ownership proof which will be +/// parameterized when defining the runtime. At the runtime API boundary this type is unknown and +/// as such we keep this opaque representation, implementors of the runtime API will have to make +/// sure that all usages of `OpaqueKeyOwnershipProof` refer to the same type. +#[derive(Decode, Encode, PartialEq, TypeInfo)] +#[repr(transparent)] +pub struct OpaqueKeyOwnershipProof(Vec); + +// Runtime API. +sp_api::decl_runtime_apis! { + /// API necessary for block authorship with Sassafras. + pub trait SassafrasApi { + /// Get ring context to be used for ticket construction and verification. + fn ring_context() -> Option; + + /// Submit next epoch validator tickets via an unsigned extrinsic. + /// This method returns `false` when creation of the extrinsics fails. + fn submit_tickets_unsigned_extrinsic(tickets: Vec) -> bool; + + /// Get ticket id associated to the given slot. + fn slot_ticket_id(slot: Slot) -> Option; + + /// Get ticket id and data associated to the given slot. + fn slot_ticket(slot: Slot) -> Option<(TicketId, TicketBody)>; + + /// Current epoch information. + fn current_epoch() -> Epoch; + + /// Next epoch information. + fn next_epoch() -> Epoch; + + /// Generates a proof of key ownership for the given authority in the current epoch. + /// + /// An example usage of this module is coupled with the session historical module to prove + /// that a given authority key is tied to a given staking identity during a specific + /// session. + /// + /// Proofs of key ownership are necessary for submitting equivocation reports. + fn generate_key_ownership_proof(authority_id: AuthorityId) -> Option; + + /// Submits an unsigned extrinsic to report an equivocation. + /// + /// The caller must provide the equivocation proof and a key ownership proof (should be + /// obtained using `generate_key_ownership_proof`). The extrinsic will be unsigned and + /// should only be accepted for local authorship (not to be broadcast to the network). This + /// method returns `false` when creation of the extrinsic fails. + /// + /// Only useful in an offchain context. + fn submit_report_equivocation_unsigned_extrinsic( + equivocation_proof: EquivocationProof, + key_owner_proof: OpaqueKeyOwnershipProof, + ) -> bool; + } +} diff --git a/substrate/primitives/consensus/sassafras/src/ticket.rs b/substrate/primitives/consensus/sassafras/src/ticket.rs new file mode 100644 index 0000000000000..d81770c96d9bb --- /dev/null +++ b/substrate/primitives/consensus/sassafras/src/ticket.rs @@ -0,0 +1,91 @@ +// 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 related to tickets. + +use crate::vrf::RingVrfSignature; +use scale_codec::{Decode, Encode, MaxEncodedLen}; +use scale_info::TypeInfo; + +pub use sp_core::ed25519::{Public as EphemeralPublic, Signature as EphemeralSignature}; + +/// Ticket identifier. +/// +/// Its value is the output of a VRF whose inputs cannot be controlled by the +/// ticket's creator (refer to [`crate::vrf::ticket_id_input`] parameters). +/// Because of this, it is also used as the ticket score to compare against +/// the epoch ticket's threshold to decide if the ticket is worth being considered +/// for slot assignment (refer to [`ticket_id_threshold`]). +pub type TicketId = u128; + +/// Ticket data persisted on-chain. +#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode, MaxEncodedLen, TypeInfo)] +pub struct TicketBody { + /// Attempt index. + pub attempt_idx: u32, + /// Ephemeral public key which gets erased when the ticket is claimed. + pub erased_public: EphemeralPublic, + /// Ephemeral public key which gets exposed when the ticket is claimed. + pub revealed_public: EphemeralPublic, +} + +/// Ticket ring vrf signature. +pub type TicketSignature = RingVrfSignature; + +/// Ticket envelope used on during submission. +#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode, MaxEncodedLen, TypeInfo)] +pub struct TicketEnvelope { + /// Ticket body. + pub body: TicketBody, + /// Ring signature. + pub signature: TicketSignature, +} + +/// Ticket claim information filled by the block author. +#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode, MaxEncodedLen, TypeInfo)] +pub struct TicketClaim { + /// Signature verified via `TicketBody::erased_public`. + pub erased_signature: EphemeralSignature, +} + +/// Computes ticket-id maximum allowed value for a given epoch. +/// +/// Only ticket identifiers below this threshold should be considered for slot +/// assignment. +/// +/// The value is computed as `TicketId::MAX*(redundancy*slots)/(attempts*validators)` +/// +/// Where: +/// - `redundancy`: redundancy factor; +/// - `slots`: number of slots in epoch; +/// - `attempts`: max number of tickets attempts per validator; +/// - `validators`: number of validators in epoch. +/// +/// If `attempts * validators = 0` then we return 0. +pub fn ticket_id_threshold( + redundancy: u32, + slots: u32, + attempts: u32, + validators: u32, +) -> TicketId { + let den = attempts as u64 * validators as u64; + let num = redundancy as u64 * slots as u64; + TicketId::max_value() + .checked_div(den.into()) + .unwrap_or_default() + .saturating_mul(num.into()) +} diff --git a/substrate/primitives/consensus/sassafras/src/vrf.rs b/substrate/primitives/consensus/sassafras/src/vrf.rs new file mode 100644 index 0000000000000..d25a656f9508f --- /dev/null +++ b/substrate/primitives/consensus/sassafras/src/vrf.rs @@ -0,0 +1,104 @@ +// 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. + +//! Utilities related to VRF input, output and signatures. + +use crate::{Randomness, TicketBody, TicketId}; +use scale_codec::Encode; +use sp_consensus_slots::Slot; +use sp_std::vec::Vec; + +pub use sp_core::bandersnatch::{ + ring_vrf::{RingContext, RingProver, RingVerifier, RingVrfSignature}, + vrf::{VrfInput, VrfOutput, VrfSignData, VrfSignature}, +}; + +fn vrf_input_from_data( + domain: &[u8], + data: impl IntoIterator>, +) -> VrfInput { + let buf = data.into_iter().fold(Vec::new(), |mut buf, item| { + let bytes = item.as_ref(); + buf.extend_from_slice(bytes); + let len = u8::try_from(bytes.len()).expect("private function with well known inputs; qed"); + buf.push(len); + buf + }); + VrfInput::new(domain, buf) +} + +/// VRF input to claim slot ownership during block production. +pub fn slot_claim_input(randomness: &Randomness, slot: Slot, epoch: u64) -> VrfInput { + vrf_input_from_data( + b"sassafras-claim-v1.0", + [randomness.as_slice(), &slot.to_le_bytes(), &epoch.to_le_bytes()], + ) +} + +/// Signing-data to claim slot ownership during block production. +pub fn slot_claim_sign_data(randomness: &Randomness, slot: Slot, epoch: u64) -> VrfSignData { + let input = slot_claim_input(randomness, slot, epoch); + VrfSignData::new_unchecked( + b"sassafras-slot-claim-transcript-v1.0", + Option::<&[u8]>::None, + Some(input), + ) +} + +/// VRF input to generate the ticket id. +pub fn ticket_id_input(randomness: &Randomness, attempt: u32, epoch: u64) -> VrfInput { + vrf_input_from_data( + b"sassafras-ticket-v1.0", + [randomness.as_slice(), &attempt.to_le_bytes(), &epoch.to_le_bytes()], + ) +} + +/// VRF input to generate the revealed key. +pub fn revealed_key_input(randomness: &Randomness, attempt: u32, epoch: u64) -> VrfInput { + vrf_input_from_data( + b"sassafras-revealed-v1.0", + [randomness.as_slice(), &attempt.to_le_bytes(), &epoch.to_le_bytes()], + ) +} + +/// Data to be signed via ring-vrf. +pub fn ticket_body_sign_data(ticket_body: &TicketBody, ticket_id_input: VrfInput) -> VrfSignData { + VrfSignData::new_unchecked( + b"sassafras-ticket-body-transcript-v1.0", + Some(ticket_body.encode().as_slice()), + Some(ticket_id_input), + ) +} + +/// Make ticket-id from the given VRF input and output. +/// +/// Input should have been obtained via [`ticket_id_input`]. +/// Output should have been obtained from the input directly using the vrf secret key +/// or from the vrf signature outputs. +pub fn make_ticket_id(input: &VrfInput, output: &VrfOutput) -> TicketId { + let bytes = output.make_bytes::<16>(b"ticket-id", input); + u128::from_le_bytes(bytes) +} + +/// Make revealed key seed from a given VRF input and ouput. +/// +/// Input should have been obtained via [`revealed_key_input`]. +/// Output should have been obtained from the input directly using the vrf secret key +/// or from the vrf signature outputs. +pub fn make_revealed_key_seed(input: &VrfInput, output: &VrfOutput) -> [u8; 32] { + output.make_bytes::<32>(b"revealed-seed", input) +} diff --git a/substrate/primitives/core/src/bandersnatch.rs b/substrate/primitives/core/src/bandersnatch.rs index c3ba7f41058e9..01f3538188a46 100644 --- a/substrate/primitives/core/src/bandersnatch.rs +++ b/substrate/primitives/core/src/bandersnatch.rs @@ -18,7 +18,7 @@ //! VRFs backed by [Bandersnatch](https://neuromancer.sk/std/bls/Bandersnatch), //! an elliptic curve built over BLS12-381 scalar field. //! -//! The primitive can operate both as a traditional VRF or as an anonymized ring VRF. +//! The primitive can operate both as a regular VRF or as an anonymized Ring VRF. #[cfg(feature = "std")] use crate::crypto::Ss58Codec; @@ -31,7 +31,7 @@ use crate::crypto::{DeriveError, DeriveJunction, Pair as TraitPair, SecretString use bandersnatch_vrfs::CanonicalSerialize; #[cfg(feature = "full_crypto")] use bandersnatch_vrfs::SecretKey; -use codec::{Decode, Encode, MaxEncodedLen}; +use codec::{Decode, Encode, EncodeLike, MaxEncodedLen}; use scale_info::TypeInfo; use sp_runtime_interface::pass_by::PassByInner; @@ -42,7 +42,7 @@ pub const CRYPTO_ID: CryptoTypeId = CryptoTypeId(*b"band"); /// Context used to produce a plain signature without any VRF input/output. #[cfg(feature = "full_crypto")] -pub const SIGNING_CTX: &[u8] = b"SigningContext"; +pub const SIGNING_CTX: &[u8] = b"BandersnatchSigningContext"; // Max ring domain size. const RING_DOMAIN_SIZE: usize = 1024; @@ -153,7 +153,8 @@ impl sp_std::fmt::Debug for Public { /// Bandersnatch signature. /// -/// The signature is created via the [`VrfSecret::vrf_sign`] using [`SIGNING_CTX`] as `label`. +/// The signature is created via the [`VrfSecret::vrf_sign`] using [`SIGNING_CTX`] as transcript +/// `label`. #[cfg_attr(feature = "full_crypto", derive(Hash))] #[derive(Clone, Copy, PartialEq, Eq, Encode, Decode, PassByInner, MaxEncodedLen, TypeInfo)] pub struct Signature([u8; SIGNATURE_SERIALIZED_LEN]); @@ -238,7 +239,7 @@ impl TraitPair for Pair { /// Make a new key pair from secret seed material. /// - /// The slice must be 64 bytes long or it will return an error. + /// The slice must be 32 bytes long or it will return an error. fn from_seed_slice(seed_slice: &[u8]) -> Result { if seed_slice.len() != SEED_SERIALIZED_LEN { return Err(SecretStringError::InvalidSeedLength) @@ -272,7 +273,6 @@ impl TraitPair for Pair { Ok((Self::from_seed(&seed), Some(seed))) } - /// Get the public key. fn public(&self) -> Public { let public = self.secret.to_public(); let mut raw = [0; PUBLIC_SERIALIZED_LEN]; @@ -282,23 +282,25 @@ impl TraitPair for Pair { Public::unchecked_from(raw) } - /// Sign raw data. + /// Sign a message. + /// + /// In practice this produce a Schnorr signature of a transcript composed by + /// the constant label [`SIGNING_CTX`] and `data` without any additional data. + /// + /// See [`vrf::VrfSignData`] for additional details. fn sign(&self, data: &[u8]) -> Signature { let data = vrf::VrfSignData::new_unchecked(SIGNING_CTX, &[data], None); self.vrf_sign(&data).signature } - /// Verify a signature on a message. - /// - /// Returns `true` if the signature is good. fn verify>(signature: &Signature, data: M, public: &Public) -> bool { let data = vrf::VrfSignData::new_unchecked(SIGNING_CTX, &[data.as_ref()], None); let signature = - vrf::VrfSignature { signature: *signature, vrf_outputs: vrf::VrfIosVec::default() }; + vrf::VrfSignature { signature: *signature, outputs: vrf::VrfIosVec::default() }; public.vrf_verify(&data, &signature) } - /// Return a vector filled with seed raw data. + /// Return a vector filled with the seed (32 bytes). fn to_raw_vec(&self) -> Vec { self.seed().to_vec() } @@ -319,7 +321,8 @@ pub mod vrf { }; /// Max number of inputs/outputs which can be handled by the VRF signing procedures. - /// The number is quite arbitrary and fullfils the current usage of the primitive. + /// + /// The number is quite arbitrary and chosen to fulfill the use cases found so far. /// If required it can be extended in the future. pub const MAX_VRF_IOS: u32 = 3; @@ -328,7 +331,7 @@ pub mod vrf { /// Can contain at most [`MAX_VRF_IOS`] elements. pub type VrfIosVec = BoundedVec>; - /// VRF input to construct a [`VrfOutput`] instance and embeddable within [`VrfSignData`]. + /// VRF input to construct a [`VrfOutput`] instance and embeddable in [`VrfSignData`]. #[derive(Clone, Debug)] pub struct VrfInput(pub(super) bandersnatch_vrfs::VrfInput); @@ -342,7 +345,9 @@ pub mod vrf { /// VRF (pre)output derived from [`VrfInput`] using a [`VrfSecret`]. /// - /// This is used to produce an arbitrary number of verifiable *random* bytes. + /// This object is used to produce an arbitrary number of verifiable pseudo random + /// bytes and is often called pre-output to emphasize that this is not the actual + /// output of the VRF but an object capable of generating the output. #[derive(Clone, Debug, PartialEq, Eq)] pub struct VrfOutput(pub(super) bandersnatch_vrfs::VrfPreOut); @@ -379,92 +384,102 @@ pub mod vrf { } } - /// A *Fiat-Shamir* transcript and a sequence of [`VrfInput`]s ready to be signed. + /// Data to be signed via one of the two provided vrf flavors. + /// + /// The object contains a transcript and a sequence of [`VrfInput`]s ready to be signed. /// - /// The `transcript` will be used as messages for the *Fiat-Shamir* - /// transform part of the scheme. This data keeps the signature secure - /// but doesn't contribute to the actual VRF output. If unsure just give - /// it a unique label depending on the actual usage of the signing data. + /// The `transcript` summarizes a set of messages which are defining a particular + /// protocol by automating the Fiat-Shamir transform for challenge generation. + /// A good explaination of the topic can be found in Merlin [docs](https://merlin.cool/) /// - /// The `vrf_inputs` is a sequence of [`VrfInput`]s to be signed and which - /// are used to construct the [`VrfOutput`]s in the signature. + /// The `inputs` is a sequence of [`VrfInput`]s which, during the signing procedure, are + /// first transformed to [`VrfOutput`]s. Both inputs and outputs are then appended to + /// the transcript before signing the Fiat-Shamir transform result (the challenge). + /// + /// In practice, as a user, all these technical details can be easily ignored. + /// What is important to remember is: + /// - *Transcript* is an object defining the protocol and used to produce the signature. This + /// object doesn't influence the `VrfOutput`s values. + /// - *Vrf inputs* is some additional data which is used to produce *vrf outputs*. This data + /// will contribute to the signature as well. #[derive(Clone)] pub struct VrfSignData { /// VRF inputs to be signed. - pub vrf_inputs: VrfIosVec, - /// Associated Fiat-Shamir transcript. + pub inputs: VrfIosVec, + /// Associated protocol transcript. pub transcript: Transcript, } impl VrfSignData { /// Construct a new data to be signed. /// - /// The `transcript_data` is used to construct the *Fiat-Shamir* `Transcript`. - /// Fails if the `vrf_inputs` yields more elements than [`MAX_VRF_IOS`] + /// Fails if the `inputs` iterator yields more elements than [`MAX_VRF_IOS`] /// - /// Refer to the [`VrfSignData`] for more details about the usage of - /// `transcript_data` and `vrf_inputs` + /// Refer to [`VrfSignData`] for details about transcript and inputs. pub fn new( - label: &'static [u8], + transcript_label: &'static [u8], transcript_data: impl IntoIterator>, - vrf_inputs: impl IntoIterator, + inputs: impl IntoIterator, ) -> Result { - let vrf_inputs: Vec = vrf_inputs.into_iter().collect(); - if vrf_inputs.len() > MAX_VRF_IOS as usize { + let inputs: Vec = inputs.into_iter().collect(); + if inputs.len() > MAX_VRF_IOS as usize { return Err(()) } - Ok(Self::new_unchecked(label, transcript_data, vrf_inputs)) + Ok(Self::new_unchecked(transcript_label, transcript_data, inputs)) } /// Construct a new data to be signed. /// - /// The `transcript_data` is used to construct the *Fiat-Shamir* `Transcript`. - /// At most the first [`MAX_VRF_IOS`] elements of `vrf_inputs` are used. + /// At most the first [`MAX_VRF_IOS`] elements of `inputs` are used. /// - /// Refer to the [`VrfSignData`] for more details about the usage of - /// `transcript_data` and `vrf_inputs` + /// Refer to [`VrfSignData`] for details about transcript and inputs. pub fn new_unchecked( - label: &'static [u8], + transcript_label: &'static [u8], transcript_data: impl IntoIterator>, - vrf_inputs: impl IntoIterator, + inputs: impl IntoIterator, ) -> Self { - let vrf_inputs: Vec = vrf_inputs.into_iter().collect(); - let vrf_inputs = VrfIosVec::truncate_from(vrf_inputs); - let mut transcript = Transcript::new_labeled(label); - transcript_data - .into_iter() - .for_each(|data| transcript.append_slice(data.as_ref())); - VrfSignData { transcript, vrf_inputs } + let inputs: Vec = inputs.into_iter().collect(); + let inputs = VrfIosVec::truncate_from(inputs); + let mut transcript = Transcript::new_labeled(transcript_label); + transcript_data.into_iter().for_each(|data| transcript.append(data.as_ref())); + VrfSignData { transcript, inputs } } - /// Append a raw message to the transcript. + /// Append a message to the transcript. pub fn push_transcript_data(&mut self, data: &[u8]) { - self.transcript.append_slice(data); + self.transcript.append(data); } - /// Append a [`VrfInput`] to the vrf inputs to be signed. + /// Tries to append a [`VrfInput`] to the vrf inputs list. /// - /// On failure, gives back the [`VrfInput`] parameter. - pub fn push_vrf_input(&mut self, vrf_input: VrfInput) -> Result<(), VrfInput> { - self.vrf_inputs.try_push(vrf_input) + /// On failure, returns back the [`VrfInput`] parameter. + pub fn push_vrf_input(&mut self, input: VrfInput) -> Result<(), VrfInput> { + self.inputs.try_push(input) } - /// Create challenge from the transcript contained within the signing data. + /// Get the challenge associated to the `transcript` contained within the signing data. + /// + /// Ignores the vrf inputs and outputs. pub fn challenge(&self) -> [u8; N] { let mut output = [0; N]; let mut transcript = self.transcript.clone(); - let mut reader = transcript.challenge(b"Prehashed for bandersnatch"); + let mut reader = transcript.challenge(b"bandersnatch challenge"); reader.read_bytes(&mut output); output } } /// VRF signature. + /// + /// Includes both the transcript `signature` and the `outputs` generated from the + /// [`VrfSignData::inputs`]. + /// + /// Refer to [`VrfSignData`] for more details. #[derive(Clone, Debug, PartialEq, Eq, Encode, Decode, MaxEncodedLen, TypeInfo)] pub struct VrfSignature { /// VRF (pre)outputs. - pub vrf_outputs: VrfIosVec, - /// VRF signature. + pub outputs: VrfIosVec, + /// Transcript signature. pub signature: Signature, } @@ -481,7 +496,7 @@ pub mod vrf { fn vrf_sign(&self, data: &Self::VrfSignData) -> Self::VrfSignature { const _: () = assert!(MAX_VRF_IOS == 3, "`MAX_VRF_IOS` expected to be 3"); // Workaround to overcome backend signature generic over the number of IOs. - match data.vrf_inputs.len() { + match data.inputs.len() { 0 => self.vrf_sign_gen::<0>(data), 1 => self.vrf_sign_gen::<1>(data), 2 => self.vrf_sign_gen::<2>(data), @@ -506,12 +521,12 @@ pub mod vrf { impl VrfPublic for Public { fn vrf_verify(&self, data: &Self::VrfSignData, signature: &Self::VrfSignature) -> bool { const _: () = assert!(MAX_VRF_IOS == 3, "`MAX_VRF_IOS` expected to be 3"); - let preouts_len = signature.vrf_outputs.len(); - if preouts_len != data.vrf_inputs.len() { + let outputs_len = signature.outputs.len(); + if outputs_len != data.inputs.len() { return false } // Workaround to overcome backend signature generic over the number of IOs. - match preouts_len { + match outputs_len { 0 => self.vrf_verify_gen::<0>(data, signature), 1 => self.vrf_verify_gen::<1>(data, signature), 2 => self.vrf_verify_gen::<2>(data, signature), @@ -525,7 +540,7 @@ pub mod vrf { impl Pair { fn vrf_sign_gen(&self, data: &VrfSignData) -> VrfSignature { let ios: Vec<_> = data - .vrf_inputs + .inputs .iter() .map(|i| self.secret.clone().0.vrf_inout(i.0.clone())) .collect(); @@ -541,7 +556,7 @@ pub mod vrf { let outputs: Vec<_> = signature.preoutputs.into_iter().map(VrfOutput).collect(); let outputs = VrfIosVec::truncate_from(outputs); - VrfSignature { signature: Signature(sign_bytes), vrf_outputs: outputs } + VrfSignature { signature: Signature(sign_bytes), outputs } } /// Generate an arbitrary number of bytes from the given `context` and VRF `input`. @@ -567,7 +582,7 @@ pub mod vrf { }; let Ok(preouts) = signature - .vrf_outputs + .outputs .iter() .map(|o| o.0.clone()) .collect::>() @@ -587,7 +602,7 @@ pub mod vrf { }; let signature = ThinVrfSignature { signature, preoutputs: preouts }; - let inputs = data.vrf_inputs.iter().map(|i| i.0.clone()); + let inputs = data.inputs.iter().map(|i| i.0.clone()); signature.verify_thin_vrf(data.transcript.clone(), inputs, &public).is_ok() } @@ -675,6 +690,8 @@ pub mod ring_vrf { } } + impl EncodeLike for RingContext {} + impl MaxEncodedLen for RingContext { fn max_encoded_len() -> usize { <[u8; RING_CONTEXT_SERIALIZED_LEN]>::max_encoded_len() @@ -695,9 +712,9 @@ pub mod ring_vrf { /// VRF (pre)outputs. pub outputs: VrfIosVec, /// Pedersen VRF signature. - signature: [u8; PEDERSEN_SIGNATURE_SERIALIZED_LEN], + pub signature: [u8; PEDERSEN_SIGNATURE_SERIALIZED_LEN], /// Ring proof. - ring_proof: [u8; RING_PROOF_SERIALIZED_LEN], + pub ring_proof: [u8; RING_PROOF_SERIALIZED_LEN], } #[cfg(feature = "full_crypto")] @@ -710,7 +727,7 @@ pub mod ring_vrf { pub fn ring_vrf_sign(&self, data: &VrfSignData, prover: &RingProver) -> RingVrfSignature { const _: () = assert!(MAX_VRF_IOS == 3, "`MAX_VRF_IOS` expected to be 3"); // Workaround to overcome backend signature generic over the number of IOs. - match data.vrf_inputs.len() { + match data.inputs.len() { 0 => self.ring_vrf_sign_gen::<0>(data, prover), 1 => self.ring_vrf_sign_gen::<1>(data, prover), 2 => self.ring_vrf_sign_gen::<2>(data, prover), @@ -725,7 +742,7 @@ pub mod ring_vrf { prover: &RingProver, ) -> RingVrfSignature { let ios: Vec<_> = data - .vrf_inputs + .inputs .iter() .map(|i| self.secret.clone().0.vrf_inout(i.0.clone())) .collect(); @@ -760,7 +777,7 @@ pub mod ring_vrf { pub fn verify(&self, data: &VrfSignData, verifier: &RingVerifier) -> bool { const _: () = assert!(MAX_VRF_IOS == 3, "`MAX_VRF_IOS` expected to be 3"); let preouts_len = self.outputs.len(); - if preouts_len != data.vrf_inputs.len() { + if preouts_len != data.inputs.len() { return false } // Workaround to overcome backend signature generic over the number of IOs. @@ -798,7 +815,7 @@ pub mod ring_vrf { let ring_signature = bandersnatch_vrfs::RingVrfSignature { signature, preoutputs, ring_proof }; - let inputs = data.vrf_inputs.iter().map(|i| i.0.clone()); + let inputs = data.inputs.iter().map(|i| i.0.clone()); ring_signature .verify_ring_vrf(data.transcript.clone(), inputs, verifier) @@ -910,11 +927,11 @@ mod tests { let signature = pair.vrf_sign(&data); let o10 = pair.make_bytes::<32>(b"ctx1", &i1); - let o11 = signature.vrf_outputs[0].make_bytes::<32>(b"ctx1", &i1); + let o11 = signature.outputs[0].make_bytes::<32>(b"ctx1", &i1); assert_eq!(o10, o11); let o20 = pair.make_bytes::<48>(b"ctx2", &i2); - let o21 = signature.vrf_outputs[1].make_bytes::<48>(b"ctx2", &i2); + let o21 = signature.outputs[1].make_bytes::<48>(b"ctx2", &i2); assert_eq!(o20, o21); } @@ -932,8 +949,7 @@ mod tests { let bytes = expected.encode(); - let expected_len = - data.vrf_inputs.len() * PREOUT_SERIALIZED_LEN + SIGNATURE_SERIALIZED_LEN + 1; + let expected_len = data.inputs.len() * PREOUT_SERIALIZED_LEN + SIGNATURE_SERIALIZED_LEN + 1; assert_eq!(bytes.len(), expected_len); let decoded = VrfSignature::decode(&mut bytes.as_slice()).unwrap(); @@ -993,6 +1009,35 @@ mod tests { assert!(!signature.verify(&data, &verifier)); } + #[test] + fn ring_vrf_make_bytes_matches() { + let ring_ctx = RingContext::new_testing(); + + let mut pks: Vec<_> = (0..16).map(|i| Pair::from_seed(&[i as u8; 32]).public()).collect(); + assert!(pks.len() <= ring_ctx.max_keyset_size()); + + let pair = Pair::from_seed(DEV_SEED); + + // Just pick one index to patch with the actual public key + let prover_idx = 3; + pks[prover_idx] = pair.public(); + + let i1 = VrfInput::new(b"dom1", b"foo"); + let i2 = VrfInput::new(b"dom2", b"bar"); + let data = VrfSignData::new_unchecked(b"mydata", &[b"tdata"], [i1.clone(), i2.clone()]); + + let prover = ring_ctx.prover(&pks, prover_idx).unwrap(); + let signature = pair.ring_vrf_sign(&data, &prover); + + let o10 = pair.make_bytes::<32>(b"ctx1", &i1); + let o11 = signature.outputs[0].make_bytes::<32>(b"ctx1", &i1); + assert_eq!(o10, o11); + + let o20 = pair.make_bytes::<48>(b"ctx2", &i2); + let o21 = signature.outputs[1].make_bytes::<48>(b"ctx2", &i2); + assert_eq!(o20, o21); + } + #[test] fn encode_decode_ring_vrf_signature() { let ring_ctx = RingContext::new_testing(); @@ -1017,7 +1062,7 @@ mod tests { let bytes = expected.encode(); - let expected_len = data.vrf_inputs.len() * PREOUT_SERIALIZED_LEN + + let expected_len = data.inputs.len() * PREOUT_SERIALIZED_LEN + PEDERSEN_SIGNATURE_SERIALIZED_LEN + RING_PROOF_SERIALIZED_LEN + 1; diff --git a/substrate/primitives/core/src/crypto.rs b/substrate/primitives/core/src/crypto.rs index 6afe4b752a690..8c7d98f00cd89 100644 --- a/substrate/primitives/core/src/crypto.rs +++ b/substrate/primitives/core/src/crypto.rs @@ -1136,6 +1136,8 @@ pub mod key_types { /// Key type for Babe module, built-in. Identified as `babe`. pub const BABE: KeyTypeId = KeyTypeId(*b"babe"); + /// Key type for Sassafras module, built-in. Identified as `sass`. + pub const SASSAFRAS: KeyTypeId = KeyTypeId(*b"sass"); /// Key type for Grandpa module, built-in. Identified as `gran`. pub const GRANDPA: KeyTypeId = KeyTypeId(*b"gran"); /// Key type for controlling an account in a Substrate runtime, built-in. Identified as `acco`. diff --git a/substrate/primitives/keystore/src/lib.rs b/substrate/primitives/keystore/src/lib.rs index 82062fe7b40a7..035af7099a6f8 100644 --- a/substrate/primitives/keystore/src/lib.rs +++ b/substrate/primitives/keystore/src/lib.rs @@ -17,6 +17,7 @@ //! Keystore traits +#[cfg(feature = "std")] pub mod testing; #[cfg(feature = "bandersnatch-experimental")] @@ -631,3 +632,15 @@ impl KeystoreExt { Self(Arc::new(keystore)) } } + +sp_core::generate_feature_enabled_macro!( + bandersnatch_experimental_enabled, + feature = "bandersnatch-experimental", + $ +); + +sp_core::generate_feature_enabled_macro!( + bls_experimental_enabled, + feature = "bls-experimental", + $ +);