VRF refactory (#13889)

* First iteration to encapsulate schnorrkel and merlin usage

* Remove schnorkel direct dependency from BABE pallet

* Remove schnorrkel direct dependency from BABE client

* Trivial renaming for VrfTranscript data and value

* Better errors

* Expose a function to get a schnorrkel friendly transcript

* Keep the vrf signature stuff together (preventing some clones around)

* Fix tests

* Remove vrf agnostic transcript and define it as an associated type for VrfSigner and VrfVerifier

* Fix babe pallet mock

* Inner types are required to be public for polkadot

* Update client/consensus/babe/src/verification.rs

Co-authored-by: Koute <koute@users.noreply.github.com>

* Nit

* Remove Deref implementations

* make_bytes as a method

* Trigger CI

---------

Co-authored-by: Koute <koute@users.noreply.github.com>
This commit is contained in:
Davide Galassi
2023-04-19 11:11:47 +02:00
committed by GitHub
parent d9ad6feac0
commit bb394e08ac
28 changed files with 473 additions and 717 deletions
@@ -15,14 +15,12 @@ targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
async-trait = { version = "0.1.57", optional = true }
codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false }
merlin = { version = "2.0", default-features = false }
scale-info = { version = "2.5.0", default-features = false, features = ["derive"] }
serde = { version = "1.0.136", features = ["derive"], optional = true }
sp-api = { version = "4.0.0-dev", default-features = false, path = "../../api" }
sp-application-crypto = { version = "7.0.0", default-features = false, path = "../../application-crypto" }
sp-consensus = { version = "0.10.0-dev", optional = true, path = "../common" }
sp-consensus-slots = { version = "0.10.0-dev", default-features = false, path = "../slots" }
sp-consensus-vrf = { version = "0.10.0-dev", default-features = false, path = "../vrf" }
sp-core = { version = "7.0.0", default-features = false, path = "../../core" }
sp-inherents = { version = "4.0.0-dev", default-features = false, path = "../../inherents" }
sp-keystore = { version = "0.13.0", default-features = false, optional = true, path = "../../keystore" }
@@ -35,14 +33,12 @@ default = ["std"]
std = [
"async-trait",
"codec/std",
"merlin/std",
"scale-info/std",
"serde",
"sp-api/std",
"sp-application-crypto/std",
"sp-consensus",
"sp-consensus-slots/std",
"sp-consensus-vrf/std",
"sp-core/std",
"sp-inherents/std",
"sp-keystore",
@@ -19,14 +19,15 @@
use super::{
AllowedSlots, AuthorityId, AuthorityIndex, AuthoritySignature, BabeAuthorityWeight,
BabeEpochConfiguration, Slot, BABE_ENGINE_ID,
BabeEpochConfiguration, Randomness, Slot, BABE_ENGINE_ID,
};
use codec::{Decode, Encode, MaxEncodedLen};
use scale_info::TypeInfo;
use sp_core::sr25519::vrf::VrfSignature;
use sp_runtime::{DigestItem, RuntimeDebug};
use sp_std::vec::Vec;
use sp_consensus_vrf::schnorrkel::{Randomness, VRFOutput, VRFProof};
use codec::{Decode, Encode, MaxEncodedLen};
use scale_info::TypeInfo;
/// Raw BABE primary slot assignment pre-digest.
#[derive(Clone, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)]
@@ -35,10 +36,8 @@ pub struct PrimaryPreDigest {
pub authority_index: super::AuthorityIndex,
/// Slot
pub slot: Slot,
/// VRF output
pub vrf_output: VRFOutput,
/// VRF proof
pub vrf_proof: VRFProof,
/// VRF signature
pub vrf_signature: VrfSignature,
}
/// BABE secondary slot assignment pre-digest.
@@ -62,10 +61,8 @@ pub struct SecondaryVRFPreDigest {
pub authority_index: super::AuthorityIndex,
/// Slot
pub slot: Slot,
/// VRF output
pub vrf_output: VRFOutput,
/// VRF proof
pub vrf_proof: VRFProof,
/// VRF signature
pub vrf_signature: VrfSignature,
}
/// A BABE pre-runtime digest. This contains all data required to validate a
@@ -118,11 +115,10 @@ impl PreDigest {
}
/// Returns the VRF output and proof, if they exist.
pub fn vrf(&self) -> Option<(&VRFOutput, &VRFProof)> {
pub fn vrf_signature(&self) -> Option<&VrfSignature> {
match self {
PreDigest::Primary(primary) => Some((&primary.vrf_output, &primary.vrf_proof)),
PreDigest::SecondaryVRF(secondary) =>
Some((&secondary.vrf_output, &secondary.vrf_proof)),
PreDigest::Primary(primary) => Some(&primary.vrf_signature),
PreDigest::SecondaryVRF(secondary) => Some(&secondary.vrf_signature),
PreDigest::SecondaryPlain(_) => None,
}
}
+18 -30
View File
@@ -23,22 +23,17 @@
pub mod digests;
pub mod inherents;
pub use merlin::Transcript;
pub use sp_consensus_vrf::schnorrkel::{
Randomness, RANDOMNESS_LENGTH, VRF_OUTPUT_LENGTH, VRF_PROOF_LENGTH,
};
use codec::{Decode, Encode, MaxEncodedLen};
use scale_info::TypeInfo;
#[cfg(feature = "std")]
use serde::{Deserialize, Serialize};
#[cfg(feature = "std")]
use sp_keystore::vrf::{VRFTranscriptData, VRFTranscriptValue};
use sp_runtime::{traits::Header, ConsensusEngineId, RuntimeDebug};
use sp_std::vec::Vec;
use crate::digests::{NextConfigDescriptor, NextEpochDescriptor};
pub use sp_core::sr25519::vrf::{VrfOutput, VrfProof, VrfSignature, VrfTranscript};
/// Key type for BABE module.
pub const KEY_TYPE: sp_core::crypto::KeyTypeId = sp_application_crypto::key_types::BABE;
@@ -47,11 +42,14 @@ mod app {
app_crypto!(sr25519, BABE);
}
/// The prefix used by BABE for its VRF keys.
pub const BABE_VRF_PREFIX: &[u8] = b"substrate-babe-vrf";
/// VRF context used for per-slot randomness generation.
pub const RANDOMNESS_VRF_CONTEXT: &[u8] = b"BabeVRFInOutContext";
/// BABE VRFInOut context.
pub static BABE_VRF_INOUT_CONTEXT: &[u8] = b"BabeVRFInOutContext";
/// VRF output length for per-slot randomness.
pub const RANDOMNESS_LENGTH: usize = 32;
/// Randomness type required by BABE operations.
pub type Randomness = [u8; RANDOMNESS_LENGTH];
/// A Babe authority keypair. Necessarily equivalent to the schnorrkel public key used in
/// the main Babe module. If that ever changes, then this must, too.
@@ -96,26 +94,16 @@ pub type BabeAuthorityWeight = u64;
/// of 0 (regardless of whether they are plain or vrf secondary blocks).
pub type BabeBlockWeight = u32;
/// Make a VRF transcript from given randomness, slot number and epoch.
pub fn make_transcript(randomness: &Randomness, slot: Slot, epoch: u64) -> Transcript {
let mut transcript = Transcript::new(&BABE_ENGINE_ID);
transcript.append_u64(b"slot number", *slot);
transcript.append_u64(b"current epoch", epoch);
transcript.append_message(b"chain randomness", &randomness[..]);
transcript
}
/// Make a VRF transcript data container
#[cfg(feature = "std")]
pub fn make_transcript_data(randomness: &Randomness, slot: Slot, epoch: u64) -> VRFTranscriptData {
VRFTranscriptData {
label: &BABE_ENGINE_ID,
items: vec![
("slot number", VRFTranscriptValue::U64(*slot)),
("current epoch", VRFTranscriptValue::U64(epoch)),
("chain randomness", VRFTranscriptValue::Bytes(randomness.to_vec())),
pub fn make_transcript(randomness: &Randomness, slot: Slot, epoch: u64) -> VrfTranscript {
VrfTranscript::new(
&BABE_ENGINE_ID,
&[
(b"slot number", &slot.to_le_bytes()),
(b"current epoch", &epoch.to_le_bytes()),
(b"chain randomness", randomness),
],
}
)
}
/// An consensus log item for BABE.
@@ -355,7 +343,7 @@ pub struct Epoch {
/// The authorities and their weights.
pub authorities: Vec<(AuthorityId, BabeAuthorityWeight)>,
/// Randomness for this epoch.
pub randomness: [u8; VRF_OUTPUT_LENGTH],
pub randomness: Randomness,
/// Configuration of the epoch.
pub config: BabeEpochConfiguration,
}
@@ -1,32 +0,0 @@
[package]
name = "sp-consensus-vrf"
version = "0.10.0-dev"
authors = ["Parity Technologies <admin@parity.io>"]
description = "Primitives for VRF based consensus"
edition = "2021"
license = "Apache-2.0"
repository = "https://github.com/paritytech/substrate/"
homepage = "https://substrate.io"
readme = "README.md"
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false }
scale-info = { version = "2.5.0", default-features = false }
schnorrkel = { version = "0.9.1", default-features = false, features = ["preaudit_deprecated", "u64_backend"] }
sp-core = { version = "7.0.0", default-features = false, path = "../../core" }
sp-runtime = { version = "7.0.0", default-features = false, path = "../../runtime" }
sp-std = { version = "5.0.0", default-features = false, path = "../../std" }
[features]
default = ["std"]
std = [
"codec/std",
"scale-info/std",
"schnorrkel/std",
"sp-core/std",
"sp-runtime/std",
"sp-std/std",
]
@@ -1,3 +0,0 @@
Primitives for VRF-based consensus engines.
License: Apache-2.0
@@ -1,21 +0,0 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Primitives for VRF-based consensus engines.
#![cfg_attr(not(feature = "std"), no_std)]
pub mod schnorrkel;
@@ -1,188 +0,0 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Schnorrkel-based VRF.
use codec::{Decode, Encode, EncodeLike, MaxEncodedLen};
use scale_info::TypeInfo;
use schnorrkel::errors::MultiSignatureStage;
use sp_core::U512;
use sp_std::{
ops::{Deref, DerefMut},
prelude::*,
};
pub use schnorrkel::{
vrf::{VRF_OUTPUT_LENGTH, VRF_PROOF_LENGTH},
PublicKey, SignatureError,
};
/// The length of the Randomness.
pub const RANDOMNESS_LENGTH: usize = VRF_OUTPUT_LENGTH;
/// VRF output type available for `std` environment, suitable for schnorrkel operations.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct VRFOutput(pub schnorrkel::vrf::VRFOutput);
impl Deref for VRFOutput {
type Target = schnorrkel::vrf::VRFOutput;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for VRFOutput {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl Encode for VRFOutput {
fn encode(&self) -> Vec<u8> {
self.0.as_bytes().encode()
}
}
impl EncodeLike for VRFOutput {}
impl Decode for VRFOutput {
fn decode<R: codec::Input>(i: &mut R) -> Result<Self, codec::Error> {
let decoded = <[u8; VRF_OUTPUT_LENGTH]>::decode(i)?;
Ok(Self(schnorrkel::vrf::VRFOutput::from_bytes(&decoded).map_err(convert_error)?))
}
}
impl MaxEncodedLen for VRFOutput {
fn max_encoded_len() -> usize {
<[u8; VRF_OUTPUT_LENGTH]>::max_encoded_len()
}
}
impl TypeInfo for VRFOutput {
type Identity = [u8; VRF_OUTPUT_LENGTH];
fn type_info() -> scale_info::Type {
Self::Identity::type_info()
}
}
impl TryFrom<[u8; VRF_OUTPUT_LENGTH]> for VRFOutput {
type Error = SignatureError;
fn try_from(raw: [u8; VRF_OUTPUT_LENGTH]) -> Result<Self, Self::Error> {
schnorrkel::vrf::VRFOutput::from_bytes(&raw).map(VRFOutput)
}
}
/// VRF proof type available for `std` environment, suitable for schnorrkel operations.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct VRFProof(pub schnorrkel::vrf::VRFProof);
impl PartialOrd for VRFProof {
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for VRFProof {
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
U512::from(self.0.to_bytes()).cmp(&U512::from(other.0.to_bytes()))
}
}
impl Deref for VRFProof {
type Target = schnorrkel::vrf::VRFProof;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for VRFProof {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl Encode for VRFProof {
fn encode(&self) -> Vec<u8> {
self.0.to_bytes().encode()
}
}
impl EncodeLike for VRFProof {}
impl Decode for VRFProof {
fn decode<R: codec::Input>(i: &mut R) -> Result<Self, codec::Error> {
let decoded = <[u8; VRF_PROOF_LENGTH]>::decode(i)?;
Ok(Self(schnorrkel::vrf::VRFProof::from_bytes(&decoded).map_err(convert_error)?))
}
}
impl MaxEncodedLen for VRFProof {
fn max_encoded_len() -> usize {
<[u8; VRF_PROOF_LENGTH]>::max_encoded_len()
}
}
impl TypeInfo for VRFProof {
type Identity = [u8; VRF_PROOF_LENGTH];
fn type_info() -> scale_info::Type {
Self::Identity::type_info()
}
}
impl TryFrom<[u8; VRF_PROOF_LENGTH]> for VRFProof {
type Error = SignatureError;
fn try_from(raw: [u8; VRF_PROOF_LENGTH]) -> Result<Self, Self::Error> {
schnorrkel::vrf::VRFProof::from_bytes(&raw).map(VRFProof)
}
}
fn convert_error(e: SignatureError) -> codec::Error {
use MultiSignatureStage::*;
use SignatureError::*;
match e {
EquationFalse => "Signature error: `EquationFalse`".into(),
PointDecompressionError => "Signature error: `PointDecompressionError`".into(),
ScalarFormatError => "Signature error: `ScalarFormatError`".into(),
NotMarkedSchnorrkel => "Signature error: `NotMarkedSchnorrkel`".into(),
BytesLengthError { .. } => "Signature error: `BytesLengthError`".into(),
MuSigAbsent { musig_stage: Commitment } =>
"Signature error: `MuSigAbsent` at stage `Commitment`".into(),
MuSigAbsent { musig_stage: Reveal } =>
"Signature error: `MuSigAbsent` at stage `Reveal`".into(),
MuSigAbsent { musig_stage: Cosignature } =>
"Signature error: `MuSigAbsent` at stage `Commitment`".into(),
MuSigInconsistent { musig_stage: Commitment, duplicate: true } =>
"Signature error: `MuSigInconsistent` at stage `Commitment` on duplicate".into(),
MuSigInconsistent { musig_stage: Commitment, duplicate: false } =>
"Signature error: `MuSigInconsistent` at stage `Commitment` on not duplicate".into(),
MuSigInconsistent { musig_stage: Reveal, duplicate: true } =>
"Signature error: `MuSigInconsistent` at stage `Reveal` on duplicate".into(),
MuSigInconsistent { musig_stage: Reveal, duplicate: false } =>
"Signature error: `MuSigInconsistent` at stage `Reveal` on not duplicate".into(),
MuSigInconsistent { musig_stage: Cosignature, duplicate: true } =>
"Signature error: `MuSigInconsistent` at stage `Cosignature` on duplicate".into(),
MuSigInconsistent { musig_stage: Cosignature, duplicate: false } =>
"Signature error: `MuSigInconsistent` at stage `Cosignature` on not duplicate".into(),
}
}
/// Schnorrkel randomness value. Same size as `VRFOutput`.
pub type Randomness = [u8; RANDOMNESS_LENGTH];
+3 -5
View File
@@ -44,9 +44,9 @@ bitflags = "1.3"
array-bytes = { version = "4.1", optional = true }
ed25519-zebra = { version = "3.1.0", default-features = false, optional = true }
blake2 = { version = "0.10.4", default-features = false, optional = true }
schnorrkel = { version = "0.9.1", features = ["preaudit_deprecated", "u64_backend"], default-features = false, optional = true }
libsecp256k1 = { version = "0.7", default-features = false, features = ["static-context"], optional = true }
merlin = { version = "2.0", default-features = false, optional = true }
schnorrkel = { version = "0.9.1", features = ["preaudit_deprecated", "u64_backend"], default-features = false }
merlin = { version = "2.0", default-features = false }
secp256k1 = { version = "0.24.0", default-features = false, features = ["recovery", "alloc"], optional = true }
ss58-registry = { version = "1.34.0", default-features = false }
sp-core-hashing = { version = "5.0.0", path = "./hashing", default-features = false, optional = true }
@@ -69,7 +69,7 @@ bench = false
[features]
default = ["std"]
std = [
"merlin?/std",
"merlin/std",
"full_crypto",
"log/std",
"thiserror",
@@ -119,10 +119,8 @@ full_crypto = [
"array-bytes",
"ed25519-zebra",
"blake2",
"schnorrkel",
"libsecp256k1",
"secp256k1",
"sp-core-hashing",
"sp-runtime-interface/disable_target_static_assertions",
"merlin",
]
+22 -4
View File
@@ -28,11 +28,8 @@ use rand::{rngs::OsRng, RngCore};
#[cfg(feature = "std")]
use regex::Regex;
use scale_info::TypeInfo;
/// Trait for accessing reference to `SecretString`.
pub use secrecy::ExposeSecret;
/// A store for sensitive data.
#[cfg(feature = "std")]
pub use secrecy::SecretString;
pub use secrecy::{ExposeSecret, SecretString};
use sp_runtime_interface::pass_by::PassByInner;
#[doc(hidden)]
pub use sp_std::ops::Deref;
@@ -1102,6 +1099,27 @@ impl<'a> TryFrom<&'a str> for KeyTypeId {
}
}
/// Trait grouping types shared by a VRF signer and verifiers.
pub trait VrfCrypto {
/// Associated signature type.
type VrfSignature;
/// Vrf input data. Generally some form of transcript.
type VrfInput;
}
/// VRF Signer.
pub trait VrfSigner: VrfCrypto {
/// Sign input data.
fn vrf_sign(&self, data: &Self::VrfInput) -> Self::VrfSignature;
}
/// VRF Verifier.
pub trait VrfVerifier: VrfCrypto {
/// Verify input data signature.
fn vrf_verify(&self, data: &Self::VrfInput, signature: &Self::VrfSignature) -> bool;
}
/// An identifier for a specific cryptographic algorithm used by a key pair
#[derive(Debug, Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Encode, Decode)]
#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
+197 -34
View File
@@ -15,12 +15,11 @@
// See the License for the specific language governing permissions and
// limitations under the License.
// tag::description[]
//! Simple sr25519 (Schnorr-Ristretto) API.
//!
//! Note: `CHAIN_CODE_LENGTH` must be equal to `crate::crypto::JUNCTION_ID_LEN`
//! for this to work.
// end::description[]
#[cfg(feature = "std")]
use crate::crypto::Ss58Codec;
#[cfg(feature = "full_crypto")]
@@ -30,7 +29,6 @@ use schnorrkel::{
derive::{ChainCode, Derivation, CHAIN_CODE_LENGTH},
signing_context, ExpansionMode, Keypair, MiniSecretKey, PublicKey, SecretKey,
};
#[cfg(feature = "full_crypto")]
use sp_std::vec::Vec;
use crate::{
@@ -200,8 +198,6 @@ impl<'de> Deserialize<'de> for Public {
}
/// An Schnorrkel/Ristretto x25519 ("sr25519") signature.
///
/// Instead of importing it for the local module, alias it to be available as a public type
#[cfg_attr(feature = "full_crypto", derive(Hash))]
#[derive(Encode, Decode, MaxEncodedLen, PassByInner, TypeInfo, PartialEq, Eq)]
pub struct Signature(pub [u8; 64]);
@@ -545,43 +541,194 @@ impl CryptoType for Pair {
type Pair = Pair;
}
/// Batch verification.
///
/// `messages`, `signatures` and `pub_keys` should all have equal length.
///
/// Returns `true` if all signatures are correct, `false` otherwise.
#[cfg(feature = "std")]
pub fn verify_batch(
messages: Vec<&[u8]>,
signatures: Vec<&Signature>,
pub_keys: Vec<&Public>,
) -> bool {
let mut sr_pub_keys = Vec::with_capacity(pub_keys.len());
for pub_key in pub_keys {
match schnorrkel::PublicKey::from_bytes(pub_key.as_ref()) {
Ok(pk) => sr_pub_keys.push(pk),
Err(_) => return false,
};
/// Schnorrkel VRF related types and operations.
pub mod vrf {
use super::*;
#[cfg(feature = "full_crypto")]
use crate::crypto::VrfSigner;
use crate::crypto::{VrfCrypto, VrfVerifier};
use schnorrkel::{
errors::MultiSignatureStage,
vrf::{VRF_OUTPUT_LENGTH, VRF_PROOF_LENGTH},
SignatureError,
};
/// VRF transcript ready to be used for VRF sign/verify operations.
pub struct VrfTranscript(pub merlin::Transcript);
impl VrfTranscript {
/// Build a new transcript ready to be used by a VRF signer/verifier.
pub fn new(label: &'static [u8], data: &[(&'static [u8], &[u8])]) -> Self {
let mut transcript = merlin::Transcript::new(label);
data.iter().for_each(|(l, b)| transcript.append_message(l, b));
VrfTranscript(transcript)
}
}
let mut sr_signatures = Vec::with_capacity(signatures.len());
for signature in signatures {
match schnorrkel::Signature::from_bytes(signature.as_ref()) {
Ok(s) => sr_signatures.push(s),
Err(_) => return false,
};
/// VRF signature data
#[derive(Clone, Debug, PartialEq, Eq, Encode, Decode, MaxEncodedLen, TypeInfo)]
pub struct VrfSignature {
/// The initial VRF configuration
pub output: VrfOutput,
/// The calculated VRF proof
pub proof: VrfProof,
}
let mut messages: Vec<merlin::Transcript> = messages
.into_iter()
.map(|msg| signing_context(SIGNING_CTX).bytes(msg))
.collect();
/// VRF output type suitable for schnorrkel operations.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct VrfOutput(pub schnorrkel::vrf::VRFOutput);
schnorrkel::verify_batch(&mut messages, &sr_signatures, &sr_pub_keys, true).is_ok()
impl Encode for VrfOutput {
fn encode(&self) -> Vec<u8> {
self.0.as_bytes().encode()
}
}
impl Decode for VrfOutput {
fn decode<R: codec::Input>(i: &mut R) -> Result<Self, codec::Error> {
let decoded = <[u8; VRF_OUTPUT_LENGTH]>::decode(i)?;
Ok(Self(schnorrkel::vrf::VRFOutput::from_bytes(&decoded).map_err(convert_error)?))
}
}
impl MaxEncodedLen for VrfOutput {
fn max_encoded_len() -> usize {
<[u8; VRF_OUTPUT_LENGTH]>::max_encoded_len()
}
}
impl TypeInfo for VrfOutput {
type Identity = [u8; VRF_OUTPUT_LENGTH];
fn type_info() -> scale_info::Type {
Self::Identity::type_info()
}
}
/// VRF proof type suitable for schnorrkel operations.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct VrfProof(pub schnorrkel::vrf::VRFProof);
impl Encode for VrfProof {
fn encode(&self) -> Vec<u8> {
self.0.to_bytes().encode()
}
}
impl Decode for VrfProof {
fn decode<R: codec::Input>(i: &mut R) -> Result<Self, codec::Error> {
let decoded = <[u8; VRF_PROOF_LENGTH]>::decode(i)?;
Ok(Self(schnorrkel::vrf::VRFProof::from_bytes(&decoded).map_err(convert_error)?))
}
}
impl MaxEncodedLen for VrfProof {
fn max_encoded_len() -> usize {
<[u8; VRF_PROOF_LENGTH]>::max_encoded_len()
}
}
impl TypeInfo for VrfProof {
type Identity = [u8; VRF_PROOF_LENGTH];
fn type_info() -> scale_info::Type {
Self::Identity::type_info()
}
}
#[cfg(feature = "full_crypto")]
impl VrfCrypto for Pair {
type VrfSignature = VrfSignature;
type VrfInput = VrfTranscript;
}
#[cfg(feature = "full_crypto")]
impl VrfSigner for Pair {
fn vrf_sign(&self, transcript: &Self::VrfInput) -> Self::VrfSignature {
let (inout, proof, _) = self.0.vrf_sign(transcript.0.clone());
VrfSignature { output: VrfOutput(inout.to_output()), proof: VrfProof(proof) }
}
}
impl VrfCrypto for Public {
type VrfSignature = VrfSignature;
type VrfInput = VrfTranscript;
}
impl VrfVerifier for Public {
fn vrf_verify(&self, transcript: &Self::VrfInput, signature: &Self::VrfSignature) -> bool {
schnorrkel::PublicKey::from_bytes(self)
.and_then(|public| {
public.vrf_verify(transcript.0.clone(), &signature.output.0, &signature.proof.0)
})
.is_ok()
}
}
fn convert_error(e: SignatureError) -> codec::Error {
use MultiSignatureStage::*;
use SignatureError::*;
match e {
EquationFalse => "Signature error: `EquationFalse`".into(),
PointDecompressionError => "Signature error: `PointDecompressionError`".into(),
ScalarFormatError => "Signature error: `ScalarFormatError`".into(),
NotMarkedSchnorrkel => "Signature error: `NotMarkedSchnorrkel`".into(),
BytesLengthError { .. } => "Signature error: `BytesLengthError`".into(),
MuSigAbsent { musig_stage: Commitment } =>
"Signature error: `MuSigAbsent` at stage `Commitment`".into(),
MuSigAbsent { musig_stage: Reveal } =>
"Signature error: `MuSigAbsent` at stage `Reveal`".into(),
MuSigAbsent { musig_stage: Cosignature } =>
"Signature error: `MuSigAbsent` at stage `Commitment`".into(),
MuSigInconsistent { musig_stage: Commitment, duplicate: true } =>
"Signature error: `MuSigInconsistent` at stage `Commitment` on duplicate".into(),
MuSigInconsistent { musig_stage: Commitment, duplicate: false } =>
"Signature error: `MuSigInconsistent` at stage `Commitment` on not duplicate".into(),
MuSigInconsistent { musig_stage: Reveal, duplicate: true } =>
"Signature error: `MuSigInconsistent` at stage `Reveal` on duplicate".into(),
MuSigInconsistent { musig_stage: Reveal, duplicate: false } =>
"Signature error: `MuSigInconsistent` at stage `Reveal` on not duplicate".into(),
MuSigInconsistent { musig_stage: Cosignature, duplicate: true } =>
"Signature error: `MuSigInconsistent` at stage `Cosignature` on duplicate".into(),
MuSigInconsistent { musig_stage: Cosignature, duplicate: false } =>
"Signature error: `MuSigInconsistent` at stage `Cosignature` on not duplicate"
.into(),
}
}
#[cfg(feature = "full_crypto")]
impl Pair {
/// Generate bytes from the given VRF configuration.
pub fn make_bytes<B: Default + AsMut<[u8]>>(
&self,
context: &[u8],
transcript: &VrfTranscript,
) -> B {
let inout = self.0.vrf_create_hash(transcript.0.clone());
inout.make_bytes::<B>(context)
}
}
impl Public {
/// Generate bytes from the given VRF configuration.
pub fn make_bytes<B: Default + AsMut<[u8]>>(
&self,
context: &[u8],
transcript: &VrfTranscript,
output: &VrfOutput,
) -> Result<B, codec::Error> {
let pubkey = schnorrkel::PublicKey::from_bytes(&self.0).map_err(convert_error)?;
let inout = output
.0
.attach_input_hash(&pubkey, transcript.0.clone())
.map_err(convert_error)?;
Ok(inout.make_bytes::<B>(context))
}
}
}
#[cfg(test)]
mod compatibility_test {
mod tests {
use super::*;
use crate::crypto::{Ss58Codec, DEV_ADDRESS, DEV_PHRASE};
use serde_json;
@@ -811,4 +958,20 @@ mod compatibility_test {
// Poorly-sized
assert!(deserialize_signature("\"abc123\"").is_err());
}
#[test]
fn vrf_make_bytes_matches() {
use super::vrf::*;
use crate::crypto::VrfSigner;
let pair = Pair::from_seed(b"12345678901234567890123456789012");
let public = pair.public();
let transcript = VrfTranscript::new(b"test", &[(b"foo", b"bar")]);
let signature = pair.vrf_sign(&transcript);
let ctx = b"randbytes";
let b1 = pair.make_bytes::<[u8; 32]>(ctx, &transcript);
let b2 = public.make_bytes::<[u8; 32]>(ctx, &transcript, &signature.output).unwrap();
assert_eq!(b1, b2);
}
}
-4
View File
@@ -15,9 +15,7 @@ targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = ["derive"] }
futures = "0.3.21"
merlin = { version = "2.0", default-features = false }
parking_lot = { version = "0.12.1", default-features = false }
schnorrkel = { version = "0.9.1", default-features = false, features = ["preaudit_deprecated", "u64_backend"] }
serde = { version = "1.0", optional = true }
thiserror = "1.0"
sp-core = { version = "7.0.0", default-features = false, path = "../core" }
@@ -31,8 +29,6 @@ rand_chacha = "0.2.2"
default = ["std"]
std = [
"codec/std",
"merlin/std",
"schnorrkel/std",
"serde",
"sp-core/std",
"sp-externalities/std",
+4 -5
View File
@@ -16,10 +16,9 @@
// limitations under the License.
//! Keystore traits
pub mod testing;
pub mod vrf;
use crate::vrf::{VRFSignature, VRFTranscriptData};
pub mod testing;
use sp_core::{
crypto::{ByteArray, CryptoTypeId, KeyTypeId},
ecdsa, ed25519, sr25519,
@@ -87,8 +86,8 @@ pub trait Keystore: Send + Sync {
&self,
key_type: KeyTypeId,
public: &sr25519::Public,
transcript_data: VRFTranscriptData,
) -> Result<Option<VRFSignature>, Error>;
transcript: &sr25519::vrf::VrfTranscript,
) -> Result<Option<sr25519::vrf::VrfSignature>, Error>;
/// Returns all ed25519 public keys for the given key type.
fn ed25519_public_keys(&self, key_type: KeyTypeId) -> Vec<ed25519::Public>;
+25 -23
View File
@@ -17,15 +17,13 @@
//! Types that should only be used for testing!
use crate::{Error, Keystore, KeystorePtr};
use sp_core::{
crypto::{ByteArray, KeyTypeId, Pair},
crypto::{ByteArray, KeyTypeId, Pair, VrfSigner},
ecdsa, ed25519, sr25519,
};
use crate::{
vrf::{make_transcript, VRFSignature, VRFTranscriptData},
Error, Keystore, KeystorePtr,
};
use parking_lot::RwLock;
use std::{collections::HashMap, sync::Arc};
@@ -100,6 +98,16 @@ impl MemoryKeystore {
let sig = self.pair::<T>(key_type, public).map(|pair| pair.sign(msg));
Ok(sig)
}
fn vrf_sign<T: Pair + VrfSigner>(
&self,
key_type: KeyTypeId,
public: &T::Public,
transcript: &T::VrfInput,
) -> Result<Option<T::VrfSignature>, Error> {
let sig = self.pair::<T>(key_type, public).map(|pair| pair.vrf_sign(transcript));
Ok(sig)
}
}
impl Keystore for MemoryKeystore {
@@ -128,14 +136,9 @@ impl Keystore for MemoryKeystore {
&self,
key_type: KeyTypeId,
public: &sr25519::Public,
transcript_data: VRFTranscriptData,
) -> Result<Option<VRFSignature>, Error> {
let sig = self.pair::<sr25519::Pair>(key_type, public).map(|pair| {
let transcript = make_transcript(transcript_data);
let (inout, proof, _) = pair.as_ref().vrf_sign(transcript);
VRFSignature { output: inout.to_output(), proof }
});
Ok(sig)
transcript: &sr25519::vrf::VrfTranscript,
) -> Result<Option<sr25519::vrf::VrfSignature>, Error> {
self.vrf_sign::<sr25519::Pair>(key_type, public, transcript)
}
fn ed25519_public_keys(&self, key_type: KeyTypeId) -> Vec<ed25519::Public> {
@@ -225,7 +228,6 @@ impl Into<KeystorePtr> for MemoryKeystore {
#[cfg(test)]
mod tests {
use super::*;
use crate::vrf::VRFTranscriptValue;
use sp_core::{
sr25519,
testing::{ECDSA, ED25519, SR25519},
@@ -265,23 +267,23 @@ mod tests {
let secret_uri = "//Alice";
let key_pair = sr25519::Pair::from_string(secret_uri, None).expect("Generates key pair");
let transcript_data = VRFTranscriptData {
label: b"Test",
items: vec![
("one", VRFTranscriptValue::U64(1)),
("two", VRFTranscriptValue::U64(2)),
("three", VRFTranscriptValue::Bytes("test".as_bytes().to_vec())),
let transcript = sr25519::vrf::VrfTranscript::new(
b"Test",
&[
(b"one", &1_u64.to_le_bytes()),
(b"two", &2_u64.to_le_bytes()),
(b"three", "test".as_bytes()),
],
};
);
let result = store.sr25519_vrf_sign(SR25519, &key_pair.public(), transcript_data.clone());
let result = store.sr25519_vrf_sign(SR25519, &key_pair.public(), &transcript);
assert!(result.unwrap().is_none());
store
.insert(SR25519, secret_uri, key_pair.public().as_ref())
.expect("Inserts unknown key");
let result = store.sr25519_vrf_sign(SR25519, &key_pair.public(), transcript_data);
let result = store.sr25519_vrf_sign(SR25519, &key_pair.public(), &transcript);
assert!(result.unwrap().is_some());
}
-94
View File
@@ -1,94 +0,0 @@
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! VRF-specifc data types and helpers
use codec::Encode;
use merlin::Transcript;
use schnorrkel::vrf::{VRFOutput, VRFProof};
/// An enum whose variants represent possible
/// accepted values to construct the VRF transcript
#[derive(Clone, Encode)]
#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
pub enum VRFTranscriptValue {
/// Value is an array of bytes
Bytes(Vec<u8>),
/// Value is a u64 integer
U64(u64),
}
/// VRF Transcript data
#[derive(Clone, Encode)]
pub struct VRFTranscriptData {
/// The transcript's label
pub label: &'static [u8],
/// Additional data to be registered into the transcript
pub items: Vec<(&'static str, VRFTranscriptValue)>,
}
/// VRF signature data
pub struct VRFSignature {
/// The VRFOutput serialized
pub output: VRFOutput,
/// The calculated VRFProof
pub proof: VRFProof,
}
/// Construct a `Transcript` object from data.
///
/// Returns `merlin::Transcript`
pub fn make_transcript(data: VRFTranscriptData) -> Transcript {
let mut transcript = Transcript::new(data.label);
for (label, value) in data.items.into_iter() {
match value {
VRFTranscriptValue::Bytes(bytes) => {
transcript.append_message(label.as_bytes(), &bytes);
},
VRFTranscriptValue::U64(val) => {
transcript.append_u64(label.as_bytes(), val);
},
}
}
transcript
}
#[cfg(test)]
mod tests {
use super::*;
use rand::RngCore;
use rand_chacha::{rand_core::SeedableRng, ChaChaRng};
#[test]
fn transcript_creation_matches() {
let mut orig_transcript = Transcript::new(b"My label");
orig_transcript.append_u64(b"one", 1);
orig_transcript.append_message(b"two", "test".as_bytes());
let new_transcript = make_transcript(VRFTranscriptData {
label: b"My label",
items: vec![
("one", VRFTranscriptValue::U64(1)),
("two", VRFTranscriptValue::Bytes("test".as_bytes().to_vec())),
],
});
let test = |t: Transcript| -> [u8; 16] {
let mut b = [0u8; 16];
t.build_rng().finalize(&mut ChaChaRng::from_seed([0u8; 32])).fill_bytes(&mut b);
b
};
debug_assert!(test(orig_transcript) == test(new_transcript));
}
}