mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-11 21:11:07 +00:00
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
This commit is contained in:
Generated
+15
@@ -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"
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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<Vec<Vec<u8>>, TraitError> {
|
||||
self.0.read().raw_public_keys(key_type).map_err(|e| e.into())
|
||||
}
|
||||
|
||||
fn has_keys(&self, public_keys: &[(Vec<u8>, 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<sr25519::Public> {
|
||||
self.public_keys::<sr25519::Pair>(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<bandersnatch::Public> {
|
||||
self.public_keys::<bandersnatch::Pair>(key_type)
|
||||
sp_keystore::bandersnatch_experimental_enabled! {
|
||||
fn bandersnatch_public_keys(&self, key_type: KeyTypeId) -> Vec<bandersnatch::Public> {
|
||||
self.public_keys::<bandersnatch::Pair>(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.
|
||||
fn bandersnatch_generate_new(
|
||||
&self,
|
||||
key_type: KeyTypeId,
|
||||
seed: Option<&str>,
|
||||
) -> std::result::Result<bandersnatch::Public, TraitError> {
|
||||
self.generate_new::<bandersnatch::Pair>(key_type, seed)
|
||||
}
|
||||
|
||||
fn bandersnatch_sign(
|
||||
&self,
|
||||
key_type: KeyTypeId,
|
||||
public: &bandersnatch::Public,
|
||||
msg: &[u8],
|
||||
) -> std::result::Result<Option<bandersnatch::Signature>, TraitError> {
|
||||
self.sign::<bandersnatch::Pair>(key_type, public, msg)
|
||||
}
|
||||
|
||||
fn bandersnatch_vrf_sign(
|
||||
&self,
|
||||
key_type: KeyTypeId,
|
||||
public: &bandersnatch::Public,
|
||||
data: &bandersnatch::vrf::VrfSignData,
|
||||
) -> std::result::Result<Option<bandersnatch::vrf::VrfSignature>, TraitError> {
|
||||
self.vrf_sign::<bandersnatch::Pair>(key_type, public, data)
|
||||
}
|
||||
|
||||
fn bandersnatch_vrf_output(
|
||||
&self,
|
||||
key_type: KeyTypeId,
|
||||
public: &bandersnatch::Public,
|
||||
input: &bandersnatch::vrf::VrfInput,
|
||||
) -> std::result::Result<Option<bandersnatch::vrf::VrfOutput>, TraitError> {
|
||||
self.vrf_output::<bandersnatch::Pair>(key_type, public, input)
|
||||
}
|
||||
|
||||
fn bandersnatch_ring_vrf_sign(
|
||||
&self,
|
||||
key_type: KeyTypeId,
|
||||
public: &bandersnatch::Public,
|
||||
data: &bandersnatch::vrf::VrfSignData,
|
||||
prover: &bandersnatch::ring_vrf::RingProver,
|
||||
) -> std::result::Result<Option<bandersnatch::ring_vrf::RingVrfSignature>, TraitError> {
|
||||
let sig = self
|
||||
.0
|
||||
.read()
|
||||
.key_pair_by_type::<bandersnatch::Pair>(public, key_type)?
|
||||
.map(|pair| pair.ring_vrf_sign(data, prover));
|
||||
Ok(sig)
|
||||
}
|
||||
}
|
||||
|
||||
/// 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<bandersnatch::Public, TraitError> {
|
||||
self.generate_new::<bandersnatch::Pair>(key_type, seed)
|
||||
}
|
||||
sp_keystore::bls_experimental_enabled! {
|
||||
fn bls381_public_keys(&self, key_type: KeyTypeId) -> Vec<bls381::Public> {
|
||||
self.public_keys::<bls381::Pair>(key_type)
|
||||
}
|
||||
|
||||
#[cfg(feature = "bandersnatch-experimental")]
|
||||
fn bandersnatch_sign(
|
||||
&self,
|
||||
key_type: KeyTypeId,
|
||||
public: &bandersnatch::Public,
|
||||
msg: &[u8],
|
||||
) -> std::result::Result<Option<bandersnatch::Signature>, TraitError> {
|
||||
self.sign::<bandersnatch::Pair>(key_type, public, msg)
|
||||
}
|
||||
/// 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<bls381::Public, TraitError> {
|
||||
self.generate_new::<bls381::Pair>(key_type, seed)
|
||||
}
|
||||
|
||||
#[cfg(feature = "bandersnatch-experimental")]
|
||||
fn bandersnatch_vrf_sign(
|
||||
&self,
|
||||
key_type: KeyTypeId,
|
||||
public: &bandersnatch::Public,
|
||||
data: &bandersnatch::vrf::VrfSignData,
|
||||
) -> std::result::Result<Option<bandersnatch::vrf::VrfSignature>, TraitError> {
|
||||
self.vrf_sign::<bandersnatch::Pair>(key_type, public, data)
|
||||
}
|
||||
fn bls381_sign(
|
||||
&self,
|
||||
key_type: KeyTypeId,
|
||||
public: &bls381::Public,
|
||||
msg: &[u8],
|
||||
) -> std::result::Result<Option<bls381::Signature>, TraitError> {
|
||||
self.sign::<bls381::Pair>(key_type, public, msg)
|
||||
}
|
||||
|
||||
#[cfg(feature = "bandersnatch-experimental")]
|
||||
fn bandersnatch_vrf_output(
|
||||
&self,
|
||||
key_type: KeyTypeId,
|
||||
public: &bandersnatch::Public,
|
||||
input: &bandersnatch::vrf::VrfInput,
|
||||
) -> std::result::Result<Option<bandersnatch::vrf::VrfOutput>, TraitError> {
|
||||
self.vrf_output::<bandersnatch::Pair>(key_type, public, input)
|
||||
}
|
||||
fn bls377_public_keys(&self, key_type: KeyTypeId) -> Vec<bls377::Public> {
|
||||
self.public_keys::<bls377::Pair>(key_type)
|
||||
}
|
||||
|
||||
#[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<Option<bandersnatch::ring_vrf::RingVrfSignature>, TraitError> {
|
||||
let sig = self
|
||||
.0
|
||||
.read()
|
||||
.key_pair_by_type::<bandersnatch::Pair>(public, key_type)?
|
||||
.map(|pair| pair.ring_vrf_sign(data, prover));
|
||||
Ok(sig)
|
||||
}
|
||||
/// 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<bls377::Public, TraitError> {
|
||||
self.generate_new::<bls377::Pair>(key_type, seed)
|
||||
}
|
||||
|
||||
#[cfg(feature = "bls-experimental")]
|
||||
fn bls381_public_keys(&self, key_type: KeyTypeId) -> Vec<bls381::Public> {
|
||||
self.public_keys::<bls381::Pair>(key_type)
|
||||
}
|
||||
|
||||
#[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<bls381::Public, TraitError> {
|
||||
self.generate_new::<bls381::Pair>(key_type, seed)
|
||||
}
|
||||
|
||||
#[cfg(feature = "bls-experimental")]
|
||||
fn bls381_sign(
|
||||
&self,
|
||||
key_type: KeyTypeId,
|
||||
public: &bls381::Public,
|
||||
msg: &[u8],
|
||||
) -> std::result::Result<Option<bls381::Signature>, TraitError> {
|
||||
self.sign::<bls381::Pair>(key_type, public, msg)
|
||||
}
|
||||
|
||||
#[cfg(feature = "bls-experimental")]
|
||||
fn bls377_public_keys(&self, key_type: KeyTypeId) -> Vec<bls377::Public> {
|
||||
self.public_keys::<bls377::Pair>(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<bls377::Public, TraitError> {
|
||||
self.generate_new::<bls377::Pair>(key_type, seed)
|
||||
}
|
||||
|
||||
#[cfg(feature = "bls-experimental")]
|
||||
fn bls377_sign(
|
||||
&self,
|
||||
key_type: KeyTypeId,
|
||||
public: &bls377::Public,
|
||||
msg: &[u8],
|
||||
) -> std::result::Result<Option<bls377::Signature>, TraitError> {
|
||||
self.sign::<bls377::Pair>(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 keys(&self, key_type: KeyTypeId) -> std::result::Result<Vec<Vec<u8>>, TraitError> {
|
||||
self.0.read().raw_public_keys(key_type).map_err(|e| e.into())
|
||||
}
|
||||
|
||||
fn has_keys(&self, public_keys: &[(Vec<u8>, 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<Option<bls377::Signature>, TraitError> {
|
||||
self.sign::<bls377::Pair>(key_type, public, msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
[package]
|
||||
name = "sp-consensus-sassafras"
|
||||
version = "0.3.4-dev"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
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",
|
||||
]
|
||||
@@ -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
|
||||
@@ -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<TicketClaim>,
|
||||
}
|
||||
|
||||
/// 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<AuthorityId>,
|
||||
/// Epoch randomness.
|
||||
pub randomness: Randomness,
|
||||
/// Epoch configurable parameters.
|
||||
///
|
||||
/// If not present previous epoch parameters are used.
|
||||
pub config: Option<EpochConfiguration>,
|
||||
}
|
||||
|
||||
/// 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<Self, Self::Error> {
|
||||
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<Self, Self::Error> {
|
||||
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())
|
||||
}
|
||||
}
|
||||
@@ -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<H> = sp_consensus_slots::EquivocationProof<H, AuthorityId>;
|
||||
|
||||
/// 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<AuthorityId>,
|
||||
/// 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<u8>);
|
||||
|
||||
// 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<vrf::RingContext>;
|
||||
|
||||
/// 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<TicketEnvelope>) -> bool;
|
||||
|
||||
/// Get ticket id associated to the given slot.
|
||||
fn slot_ticket_id(slot: Slot) -> Option<TicketId>;
|
||||
|
||||
/// 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<OpaqueKeyOwnershipProof>;
|
||||
|
||||
/// 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<Block::Header>,
|
||||
key_owner_proof: OpaqueKeyOwnershipProof,
|
||||
) -> bool;
|
||||
}
|
||||
}
|
||||
@@ -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())
|
||||
}
|
||||
@@ -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<Item = impl AsRef<[u8]>>,
|
||||
) -> 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)
|
||||
}
|
||||
@@ -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<Pair, SecretStringError> {
|
||||
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<M: AsRef<[u8]>>(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<u8> {
|
||||
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<T> = BoundedVec<T, ConstU32<MAX_VRF_IOS>>;
|
||||
|
||||
/// 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 `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 object contains a transcript and a sequence of [`VrfInput`]s ready to be signed.
|
||||
///
|
||||
/// 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 `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 `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<VrfInput>,
|
||||
/// Associated Fiat-Shamir transcript.
|
||||
pub inputs: VrfIosVec<VrfInput>,
|
||||
/// 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<Item = impl AsRef<[u8]>>,
|
||||
vrf_inputs: impl IntoIterator<Item = VrfInput>,
|
||||
inputs: impl IntoIterator<Item = VrfInput>,
|
||||
) -> Result<Self, ()> {
|
||||
let vrf_inputs: Vec<VrfInput> = vrf_inputs.into_iter().collect();
|
||||
if vrf_inputs.len() > MAX_VRF_IOS as usize {
|
||||
let inputs: Vec<VrfInput> = 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<Item = impl AsRef<[u8]>>,
|
||||
vrf_inputs: impl IntoIterator<Item = VrfInput>,
|
||||
inputs: impl IntoIterator<Item = VrfInput>,
|
||||
) -> Self {
|
||||
let vrf_inputs: Vec<VrfInput> = 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<VrfInput> = 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<const N: usize>(&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<VrfOutput>,
|
||||
/// VRF signature.
|
||||
pub outputs: VrfIosVec<VrfOutput>,
|
||||
/// 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<const N: usize>(&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::<arrayvec::ArrayVec<bandersnatch_vrfs::VrfPreOut, N>>()
|
||||
@@ -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<VrfOutput>,
|
||||
/// 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;
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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",
|
||||
$
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user