subsoil 0.2.0

Soil primitives foundation crate
Documentation
// This file is part of Soil.

// Copyright (C) Soil contributors.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0 OR GPL-3.0-or-later WITH Classpath-exception-2.0

#[cfg(feature = "bls-experimental")]
use super::ecdsa_bls_crypto;
use super::{
	ecdsa_crypto, AuthorityIdBound, Commitment, DoubleVotingProof, ForkVotingProof,
	FutureBlockVotingProof, Payload, ValidatorSetId, VoteMessage,
};
use crate::application_crypto::{AppCrypto, AppPair, RuntimeAppPublic, Wraps};
use crate::core::{ecdsa, Pair};
use crate::runtime::traits::{BlockNumber, Header as HeaderT};

use crate::crypto_hashing::keccak_256;
use codec::Encode;
use std::{collections::HashMap, marker::PhantomData, sync::LazyLock};
use strum::IntoEnumIterator;

/// Set of test accounts using [`crate::ecdsa_crypto`] types.
#[allow(missing_docs)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, strum::Display, strum::EnumIter)]
pub enum Keyring<AuthorityId> {
	Alice,
	Bob,
	Charlie,
	Dave,
	Eve,
	Ferdie,
	One,
	Two,
	_Marker(PhantomData<AuthorityId>),
}

/// Trait representing BEEFY specific generation and signing behavior of authority id.
///
/// The trait mimics `BeefyAuthorityId` signing, but uses the private key instead of a keystore.
/// This is needed for testing purposes.
pub trait BeefySignerAuthority: AppPair {
	/// Generate and return signature for `message`.
	fn sign(&self, message: &[u8]) -> <Self as AppCrypto>::Signature;
}

impl BeefySignerAuthority for <ecdsa_crypto::AuthorityId as AppCrypto>::Pair {
	fn sign(&self, message: &[u8]) -> <Self as AppCrypto>::Signature {
		let hashed_message = keccak_256(message);
		self.as_inner_ref().sign_prehashed(&hashed_message).into()
	}
}

#[cfg(feature = "bls-experimental")]
impl BeefySignerAuthority for <ecdsa_bls_crypto::AuthorityId as AppCrypto>::Pair {
	fn sign(&self, message: &[u8]) -> <Self as AppCrypto>::Signature {
		self.as_inner_ref().sign(&message).into()
	}
}

/// Implement Keyring functionalities generically over AuthorityId
impl<AuthorityId> Keyring<AuthorityId>
where
	AuthorityId: AuthorityIdBound + From<<<AuthorityId as AppCrypto>::Pair as AppCrypto>::Public>,
	<AuthorityId as AppCrypto>::Pair: BeefySignerAuthority,
	<AuthorityId as RuntimeAppPublic>::Signature:
		Send + Sync + From<<<AuthorityId as AppCrypto>::Pair as AppCrypto>::Signature>,
{
	/// Sign `msg`.
	pub fn sign(&self, msg: &[u8]) -> <AuthorityId as RuntimeAppPublic>::Signature {
		let key_pair: <AuthorityId as AppCrypto>::Pair = self.pair();
		BeefySignerAuthority::sign(&key_pair, msg).into()
	}

	/// Return key pair.
	pub fn pair(&self) -> <AuthorityId as AppCrypto>::Pair {
		<AuthorityId as AppCrypto>::Pair::from_string(self.to_seed().as_str(), None)
			.unwrap()
			.into()
	}

	/// Return public key.
	pub fn public(&self) -> AuthorityId {
		self.pair().public().into()
	}

	/// Return seed string.
	pub fn to_seed(&self) -> String {
		format!("//{}", self)
	}

	/// Get Keyring from public key.
	pub fn from_public(who: &AuthorityId) -> Option<Keyring<AuthorityId>> {
		Self::iter().find(|k| k.public() == *who)
	}
}

static PRIVATE_KEYS: LazyLock<HashMap<Keyring<ecdsa_crypto::AuthorityId>, ecdsa_crypto::Pair>> =
	LazyLock::new(|| Keyring::iter().map(|i| (i.clone(), i.pair())).collect());
static PUBLIC_KEYS: LazyLock<HashMap<Keyring<ecdsa_crypto::AuthorityId>, ecdsa_crypto::Public>> =
	LazyLock::new(|| {
		PRIVATE_KEYS
			.iter()
			.map(|(name, pair)| (name.clone(), crate::application_crypto::Pair::public(pair)))
			.collect()
	});

impl From<Keyring<ecdsa_crypto::AuthorityId>> for ecdsa_crypto::Pair {
	fn from(k: Keyring<ecdsa_crypto::AuthorityId>) -> Self {
		k.pair()
	}
}

impl From<Keyring<ecdsa_crypto::AuthorityId>> for ecdsa::Pair {
	fn from(k: Keyring<ecdsa_crypto::AuthorityId>) -> Self {
		k.pair().into()
	}
}

impl From<Keyring<ecdsa_crypto::AuthorityId>> for ecdsa_crypto::Public {
	fn from(k: Keyring<ecdsa_crypto::AuthorityId>) -> Self {
		(*PUBLIC_KEYS).get(&k).cloned().unwrap()
	}
}

/// Create a new `VoteMessage` from commitment primitives and keyring
pub fn signed_vote<Number: BlockNumber>(
	block_number: Number,
	payload: Payload,
	validator_set_id: ValidatorSetId,
	keyring: &Keyring<ecdsa_crypto::AuthorityId>,
) -> VoteMessage<Number, ecdsa_crypto::Public, ecdsa_crypto::Signature> {
	let commitment = Commitment { validator_set_id, block_number, payload };
	let signature = keyring.sign(&commitment.encode());
	VoteMessage { commitment, id: keyring.public(), signature }
}

/// Create a new `DoubleVotingProof` based on given arguments.
pub fn generate_double_voting_proof(
	vote1: (u64, Payload, ValidatorSetId, &Keyring<ecdsa_crypto::AuthorityId>),
	vote2: (u64, Payload, ValidatorSetId, &Keyring<ecdsa_crypto::AuthorityId>),
) -> DoubleVotingProof<u64, ecdsa_crypto::Public, ecdsa_crypto::Signature> {
	let first = signed_vote(vote1.0, vote1.1, vote1.2, vote1.3);
	let second = signed_vote(vote2.0, vote2.1, vote2.2, vote2.3);
	DoubleVotingProof { first, second }
}

/// Create a new `ForkVotingProof` based on vote & canonical header.
pub fn generate_fork_voting_proof<Header: HeaderT<Number = u64>, AncestryProof>(
	vote: (u64, Payload, ValidatorSetId, &Keyring<ecdsa_crypto::AuthorityId>),
	ancestry_proof: AncestryProof,
	header: Header,
) -> ForkVotingProof<Header, ecdsa_crypto::Public, AncestryProof> {
	let signed_vote = signed_vote(vote.0, vote.1, vote.2, vote.3);
	ForkVotingProof { vote: signed_vote, ancestry_proof, header }
}

/// Create a new `ForkVotingProof` based on vote & canonical header.
pub fn generate_future_block_voting_proof(
	vote: (u64, Payload, ValidatorSetId, &Keyring<ecdsa_crypto::AuthorityId>),
) -> FutureBlockVotingProof<u64, ecdsa_crypto::Public> {
	let signed_vote = signed_vote(vote.0, vote.1, vote.2, vote.3);
	FutureBlockVotingProof { vote: signed_vote }
}