sp_domains/
bundle_producer_election.rs1use crate::{DomainId, OperatorPublicKey, ProofOfElection, StakeWeight};
2use parity_scale_codec::{Decode, Encode};
3use scale_info::TypeInfo;
4use sp_core::crypto::{VrfPublic, Wraps};
5use sp_core::sr25519::vrf::{VrfPreOutput, VrfSignature, VrfTranscript};
6use subspace_core_primitives::hashes::Blake3Hash;
7
8const VRF_TRANSCRIPT_LABEL: &[u8] = b"bundle_producer_election";
9
10pub fn make_transcript(domain_id: DomainId, global_challenge: &Blake3Hash) -> VrfTranscript {
12 VrfTranscript::new(
13 VRF_TRANSCRIPT_LABEL,
14 &[
15 (b"domain", &domain_id.to_le_bytes()),
16 (b"global_challenge", global_challenge.as_ref()),
17 ],
18 )
19}
20
21pub fn calculate_threshold(
23 operator_stake: StakeWeight,
24 total_domain_stake: StakeWeight,
25 bundle_slot_probability: (u64, u64),
26) -> u128 {
27 u128::MAX / u128::from(bundle_slot_probability.1) * u128::from(bundle_slot_probability.0)
36 / total_domain_stake
37 * operator_stake
38}
39
40pub fn is_below_threshold(vrf_output: &VrfPreOutput, threshold: u128) -> bool {
41 let vrf_pre_output = u128::from_le_bytes(
42 vrf_output
43 .0
44 .to_bytes()
45 .split_at(core::mem::size_of::<u128>())
46 .0
47 .try_into()
48 .expect("Slice splitted from VrfPreOutput must fit into u128; qed"),
49 );
50
51 vrf_pre_output < threshold
52}
53
54#[derive(Debug, Decode, Encode, TypeInfo, PartialEq, Eq, Clone)]
55pub struct BundleProducerElectionParams<Balance> {
56 pub total_domain_stake: Balance,
57 pub bundle_slot_probability: (u64, u64),
58}
59
60#[derive(Debug, Decode, Encode, TypeInfo, PartialEq, Eq, Clone)]
61pub enum ProofOfElectionError {
62 BadVrfProof,
64 ThresholdUnsatisfied,
66}
67
68pub(crate) fn verify_vrf_signature(
70 domain_id: DomainId,
71 public_key: &OperatorPublicKey,
72 vrf_signature: &VrfSignature,
73 global_challenge: &Blake3Hash,
74) -> Result<(), ProofOfElectionError> {
75 if !public_key.as_inner_ref().vrf_verify(
76 &make_transcript(domain_id, global_challenge).into(),
77 vrf_signature,
78 ) {
79 return Err(ProofOfElectionError::BadVrfProof);
80 }
81
82 Ok(())
83}
84
85pub fn check_proof_of_election(
86 operator_signing_key: &OperatorPublicKey,
87 bundle_slot_probability: (u64, u64),
88 proof_of_election: &ProofOfElection,
89 operator_stake: StakeWeight,
90 total_domain_stake: StakeWeight,
91) -> Result<(), ProofOfElectionError> {
92 proof_of_election.verify_vrf_signature(operator_signing_key)?;
93
94 let threshold =
95 calculate_threshold(operator_stake, total_domain_stake, bundle_slot_probability);
96
97 if !is_below_threshold(&proof_of_election.vrf_signature.pre_output, threshold) {
98 return Err(ProofOfElectionError::ThresholdUnsatisfied);
99 }
100
101 Ok(())
102}