Skip to main content

hotshot_types/
stake_table.rs

1// Copyright (c) 2021-2024 Espresso Systems (espressosys.com)
2// This file is part of the HotShot repository.
3
4// You should have received a copy of the MIT License
5// along with the HotShot repository. If not, see <https://mit-license.org/>.
6
7//! Types and structs related to the stake table
8
9use alloy_primitives::U256;
10use ark_ff::PrimeField;
11use derive_more::derive::{Deref, DerefMut};
12use jf_crhf::CRHF;
13use jf_rescue::crhf::VariableLengthRescueCRHF;
14use serde::{Deserialize, Serialize};
15
16use crate::{
17    NodeType, PeerConfig,
18    light_client::{CircuitField, StakeTableState, ToFieldsLightClientCompat},
19    traits::signature_key::{SignatureKey, StakeTableEntryType},
20};
21
22/// Stake table entry
23#[derive(Serialize, Deserialize, PartialEq, Clone, Hash, Eq)]
24#[serde(bound(deserialize = ""))]
25pub struct StakeTableEntry<K: SignatureKey> {
26    /// The public key
27    pub stake_key: K,
28    /// The associated stake amount
29    pub stake_amount: U256,
30}
31
32impl<K: SignatureKey> StakeTableEntryType<K> for StakeTableEntry<K> {
33    /// Get the stake amount
34    fn stake(&self) -> U256 {
35        self.stake_amount
36    }
37
38    /// Get the public key
39    fn public_key(&self) -> K {
40        self.stake_key.clone()
41    }
42}
43
44impl<K: SignatureKey> StakeTableEntry<K> {
45    /// Get the public key
46    pub fn key(&self) -> &K {
47        &self.stake_key
48    }
49}
50
51impl<K: SignatureKey> std::fmt::Debug for StakeTableEntry<K> {
52    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
53        f.debug_struct("StakeTableEntry")
54            .field("stake_key", &format_args!("{}", self.stake_key))
55            .field("stake_amount", &self.stake_amount)
56            .finish()
57    }
58}
59
60#[cfg(feature = "rlp")]
61mod stake_table_entry_rlp {
62    use alloy_rlp::{Decodable, Encodable, RlpDecodable, RlpEncodable};
63    use ark_serialize::SerializationError;
64
65    use super::*;
66
67    /// Intermediate type for serializing [`StakeTableEntry`], using [`SignatureKey::to_bytes`] for
68    /// the key.
69    #[derive(Clone, Debug, RlpDecodable, RlpEncodable)]
70    pub(super) struct StakeTableEntryRlp {
71        pub(super) stake_key: Vec<u8>,
72        pub(super) stake_amount: U256,
73    }
74
75    impl<K: SignatureKey> From<&StakeTableEntry<K>> for StakeTableEntryRlp {
76        fn from(e: &StakeTableEntry<K>) -> Self {
77            Self {
78                stake_key: e.stake_key.to_bytes(),
79                stake_amount: e.stake_amount,
80            }
81        }
82    }
83
84    impl<K: SignatureKey> TryFrom<StakeTableEntryRlp> for StakeTableEntry<K> {
85        type Error = SerializationError;
86
87        fn try_from(e: StakeTableEntryRlp) -> Result<Self, Self::Error> {
88            Ok(Self {
89                stake_key: K::from_bytes(&e.stake_key)?,
90                stake_amount: e.stake_amount,
91            })
92        }
93    }
94
95    impl<K: SignatureKey> Encodable for StakeTableEntry<K> {
96        fn encode(&self, out: &mut dyn alloy_rlp::BufMut) {
97            StakeTableEntryRlp::from(self).encode(out)
98        }
99
100        fn length(&self) -> usize {
101            StakeTableEntryRlp::from(self).length()
102        }
103    }
104
105    impl<K: SignatureKey> Decodable for StakeTableEntry<K> {
106        fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
107            let rlp = StakeTableEntryRlp::decode(buf)?;
108            rlp.try_into().map_err(|err| {
109                tracing::warn!("malformed StakeTableEntry: {err:#}");
110                alloy_rlp::Error::Custom("input is valid RLP but not a valid StakeTableEntry")
111            })
112        }
113    }
114}
115
116#[derive(Debug, Clone, PartialEq, Eq, Hash, Default, Deref, DerefMut)]
117pub struct HSStakeTable<TYPES: NodeType>(pub Vec<PeerConfig<TYPES>>);
118
119impl<TYPES: NodeType> From<Vec<PeerConfig<TYPES>>> for HSStakeTable<TYPES> {
120    fn from(peers: Vec<PeerConfig<TYPES>>) -> Self {
121        Self(peers)
122    }
123}
124
125impl<'a, T: NodeType> FromIterator<&'a PeerConfig<T>> for HSStakeTable<T> {
126    fn from_iter<I>(iter: I) -> Self
127    where
128        I: IntoIterator<Item = &'a PeerConfig<T>>,
129    {
130        Self(iter.into_iter().cloned().collect())
131    }
132}
133
134/// A helper function to compute the quorum threshold given a total amount of stake.
135#[inline]
136pub fn one_honest_threshold(total_stake: U256) -> U256 {
137    total_stake / U256::from(3) + U256::from(1)
138}
139
140#[inline]
141/// A helper function to compute the fault tolerant quorum threshold given a total amount of stake.
142pub fn supermajority_threshold(total_stake: U256) -> U256 {
143    let one = U256::ONE;
144    let two = U256::from(2);
145    let three = U256::from(3);
146    if total_stake < U256::MAX / two {
147        ((total_stake * two) / three) + one
148    } else {
149        ((total_stake / three) * two) + two
150    }
151}
152
153/// The stake needed to agree on a protocol upgrade: nine tenths of the total
154/// stake, and never less than the [`supermajority_threshold`].
155#[inline]
156pub fn upgrade_threshold(total_stake: U256) -> U256 {
157    let nine = U256::from(9);
158    let ten = U256::from(10);
159    let higher = if total_stake < U256::MAX / nine {
160        (total_stake * nine) / ten
161    } else {
162        (total_stake / ten) * nine
163    };
164    std::cmp::max(higher, supermajority_threshold(total_stake))
165}
166
167#[inline]
168fn u256_to_field(amount: U256) -> CircuitField {
169    let amount_bytes: [u8; 32] = amount.to_le_bytes();
170    CircuitField::from_le_bytes_mod_order(&amount_bytes)
171}
172
173impl<TYPES: NodeType> std::iter::IntoIterator for HSStakeTable<TYPES> {
174    type Item = PeerConfig<TYPES>;
175    type IntoIter = std::vec::IntoIter<PeerConfig<TYPES>>;
176
177    fn into_iter(self) -> Self::IntoIter {
178        self.0.into_iter()
179    }
180}
181
182impl<TYPES: NodeType> HSStakeTable<TYPES> {
183    pub fn commitment(&self, stake_table_capacity: usize) -> anyhow::Result<StakeTableState> {
184        if stake_table_capacity < self.0.len() {
185            return Err(anyhow::anyhow!(
186                "Stake table over capacity: {} < {}",
187                stake_table_capacity,
188                self.0.len(),
189            ));
190        }
191        let padding_len = stake_table_capacity - self.0.len();
192        let mut bls_preimage = vec![];
193        let mut schnorr_preimage = vec![];
194        let mut amount_preimage = vec![];
195        let mut total_stake = U256::from(0);
196        for peer in &self.0 {
197            bls_preimage.extend(peer.stake_table_entry.public_key().to_fields());
198            schnorr_preimage.extend(peer.state_ver_key.to_fields());
199            amount_preimage.push(u256_to_field(peer.stake_table_entry.stake()));
200            total_stake += peer.stake_table_entry.stake();
201        }
202        bls_preimage.resize(
203            <TYPES::SignatureKey as ToFieldsLightClientCompat>::SIZE * stake_table_capacity,
204            CircuitField::default(),
205        );
206        // Nasty tech debt
207        schnorr_preimage.extend(
208            std::iter::repeat_n(TYPES::StateSignatureKey::default().to_fields(), padding_len)
209                .flatten(),
210        );
211        amount_preimage.resize(stake_table_capacity, CircuitField::default());
212        let threshold = u256_to_field(one_honest_threshold(total_stake));
213        Ok(StakeTableState {
214            bls_key_comm: VariableLengthRescueCRHF::<CircuitField, 1>::evaluate(bls_preimage)
215                .unwrap()[0],
216            schnorr_key_comm: VariableLengthRescueCRHF::<CircuitField, 1>::evaluate(
217                schnorr_preimage,
218            )
219            .unwrap()[0],
220            amount_comm: VariableLengthRescueCRHF::<CircuitField, 1>::evaluate(amount_preimage)
221                .unwrap()[0],
222            threshold,
223        })
224    }
225
226    pub fn total_stakes(&self) -> U256 {
227        self.0
228            .iter()
229            .map(|peer| peer.stake_table_entry.stake())
230            .sum()
231    }
232}
233
234pub struct StakeTableEntries<TYPES: NodeType>(
235    pub Vec<<<TYPES as NodeType>::SignatureKey as SignatureKey>::StakeTableEntry>,
236);
237
238impl<TYPES: NodeType> From<Vec<PeerConfig<TYPES>>> for StakeTableEntries<TYPES> {
239    fn from(peers: Vec<PeerConfig<TYPES>>) -> Self {
240        Self(
241            peers
242                .into_iter()
243                .map(|peer| peer.stake_table_entry)
244                .collect::<Vec<_>>(),
245        )
246    }
247}
248
249impl<TYPES: NodeType> From<HSStakeTable<TYPES>> for StakeTableEntries<TYPES> {
250    fn from(stake_table: HSStakeTable<TYPES>) -> Self {
251        Self::from(stake_table.0)
252    }
253}
254
255impl<'a, T: NodeType> FromIterator<&'a PeerConfig<T>> for StakeTableEntries<T> {
256    fn from_iter<I>(iter: I) -> Self
257    where
258        I: IntoIterator<Item = &'a PeerConfig<T>>,
259    {
260        Self(
261            iter.into_iter()
262                .map(|peer| peer.stake_table_entry.clone())
263                .collect(),
264        )
265    }
266}
267
268#[cfg(all(test, feature = "rlp"))]
269mod rlp_test {
270    use alloy_rlp::{Decodable, Encodable};
271    use zeroize::Zeroize;
272
273    use super::*;
274    use crate::{signature_key::BLSPubKey, stake_table::stake_table_entry_rlp::StakeTableEntryRlp};
275
276    #[test_log::test]
277    fn bls_stake_table_entry_rlp_round_trip_random() {
278        let entry = StakeTableEntry {
279            stake_key: BLSPubKey::generated_from_seed_indexed(
280                Default::default(),
281                Default::default(),
282            )
283            .0,
284            stake_amount: U256::ONE,
285        };
286
287        let mut bytes = vec![];
288        entry.encode(&mut bytes);
289        assert_eq!(bytes.len(), entry.length());
290
291        let mut buf = bytes.as_slice();
292        assert_eq!(entry, StakeTableEntry::decode(&mut buf).unwrap());
293        assert!(buf.is_empty());
294    }
295
296    #[test_log::test]
297    fn bls_stake_table_entry_rlp_round_trip_zero() {
298        let mut stake_key =
299            BLSPubKey::generated_from_seed_indexed(Default::default(), Default::default()).0;
300        stake_key.zeroize();
301        let entry = StakeTableEntry {
302            stake_key,
303            stake_amount: U256::ZERO,
304        };
305
306        let mut bytes = vec![];
307        entry.encode(&mut bytes);
308        assert_eq!(bytes.len(), entry.length());
309
310        let mut buf = bytes.as_slice();
311        assert_eq!(entry, StakeTableEntry::decode(&mut buf).unwrap());
312        assert!(buf.is_empty());
313    }
314
315    #[test_log::test]
316    fn bls_stake_table_entry_invalid_malformed_key() {
317        let entry = StakeTableEntryRlp {
318            stake_key: "not a key".as_bytes().to_vec(),
319            stake_amount: U256::ZERO,
320        };
321
322        let mut buf = vec![];
323        entry.encode(&mut buf);
324        let err = StakeTableEntry::<BLSPubKey>::decode(&mut buf.as_slice()).unwrap_err();
325        assert_eq!(
326            err,
327            alloy_rlp::Error::Custom("input is valid RLP but not a valid StakeTableEntry"),
328        );
329    }
330}