hotshot_types/
stake_table.rs1use 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#[derive(Serialize, Deserialize, PartialEq, Clone, Hash, Eq)]
24#[serde(bound(deserialize = ""))]
25pub struct StakeTableEntry<K: SignatureKey> {
26 pub stake_key: K,
28 pub stake_amount: U256,
30}
31
32impl<K: SignatureKey> StakeTableEntryType<K> for StakeTableEntry<K> {
33 fn stake(&self) -> U256 {
35 self.stake_amount
36 }
37
38 fn public_key(&self) -> K {
40 self.stake_key.clone()
41 }
42}
43
44impl<K: SignatureKey> StakeTableEntry<K> {
45 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 #[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#[inline]
136pub fn one_honest_threshold(total_stake: U256) -> U256 {
137 total_stake / U256::from(3) + U256::from(1)
138}
139
140#[inline]
141pub 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#[inline]
154fn u256_to_field(amount: U256) -> CircuitField {
155 let amount_bytes: [u8; 32] = amount.to_le_bytes();
156 CircuitField::from_le_bytes_mod_order(&amount_bytes)
157}
158
159impl<TYPES: NodeType> std::iter::IntoIterator for HSStakeTable<TYPES> {
160 type Item = PeerConfig<TYPES>;
161 type IntoIter = std::vec::IntoIter<PeerConfig<TYPES>>;
162
163 fn into_iter(self) -> Self::IntoIter {
164 self.0.into_iter()
165 }
166}
167
168impl<TYPES: NodeType> HSStakeTable<TYPES> {
169 pub fn commitment(&self, stake_table_capacity: usize) -> anyhow::Result<StakeTableState> {
170 if stake_table_capacity < self.0.len() {
171 return Err(anyhow::anyhow!(
172 "Stake table over capacity: {} < {}",
173 stake_table_capacity,
174 self.0.len(),
175 ));
176 }
177 let padding_len = stake_table_capacity - self.0.len();
178 let mut bls_preimage = vec![];
179 let mut schnorr_preimage = vec![];
180 let mut amount_preimage = vec![];
181 let mut total_stake = U256::from(0);
182 for peer in &self.0 {
183 bls_preimage.extend(peer.stake_table_entry.public_key().to_fields());
184 schnorr_preimage.extend(peer.state_ver_key.to_fields());
185 amount_preimage.push(u256_to_field(peer.stake_table_entry.stake()));
186 total_stake += peer.stake_table_entry.stake();
187 }
188 bls_preimage.resize(
189 <TYPES::SignatureKey as ToFieldsLightClientCompat>::SIZE * stake_table_capacity,
190 CircuitField::default(),
191 );
192 schnorr_preimage.extend(
194 std::iter::repeat_n(TYPES::StateSignatureKey::default().to_fields(), padding_len)
195 .flatten(),
196 );
197 amount_preimage.resize(stake_table_capacity, CircuitField::default());
198 let threshold = u256_to_field(one_honest_threshold(total_stake));
199 Ok(StakeTableState {
200 bls_key_comm: VariableLengthRescueCRHF::<CircuitField, 1>::evaluate(bls_preimage)
201 .unwrap()[0],
202 schnorr_key_comm: VariableLengthRescueCRHF::<CircuitField, 1>::evaluate(
203 schnorr_preimage,
204 )
205 .unwrap()[0],
206 amount_comm: VariableLengthRescueCRHF::<CircuitField, 1>::evaluate(amount_preimage)
207 .unwrap()[0],
208 threshold,
209 })
210 }
211
212 pub fn total_stakes(&self) -> U256 {
213 self.0
214 .iter()
215 .map(|peer| peer.stake_table_entry.stake())
216 .sum()
217 }
218}
219
220pub struct StakeTableEntries<TYPES: NodeType>(
221 pub Vec<<<TYPES as NodeType>::SignatureKey as SignatureKey>::StakeTableEntry>,
222);
223
224impl<TYPES: NodeType> From<Vec<PeerConfig<TYPES>>> for StakeTableEntries<TYPES> {
225 fn from(peers: Vec<PeerConfig<TYPES>>) -> Self {
226 Self(
227 peers
228 .into_iter()
229 .map(|peer| peer.stake_table_entry)
230 .collect::<Vec<_>>(),
231 )
232 }
233}
234
235impl<TYPES: NodeType> From<HSStakeTable<TYPES>> for StakeTableEntries<TYPES> {
236 fn from(stake_table: HSStakeTable<TYPES>) -> Self {
237 Self::from(stake_table.0)
238 }
239}
240
241impl<'a, T: NodeType> FromIterator<&'a PeerConfig<T>> for StakeTableEntries<T> {
242 fn from_iter<I>(iter: I) -> Self
243 where
244 I: IntoIterator<Item = &'a PeerConfig<T>>,
245 {
246 Self(
247 iter.into_iter()
248 .map(|peer| peer.stake_table_entry.clone())
249 .collect(),
250 )
251 }
252}
253
254#[cfg(all(test, feature = "rlp"))]
255mod rlp_test {
256 use alloy_rlp::{Decodable, Encodable};
257 use zeroize::Zeroize;
258
259 use super::*;
260 use crate::{signature_key::BLSPubKey, stake_table::stake_table_entry_rlp::StakeTableEntryRlp};
261
262 #[test_log::test]
263 fn bls_stake_table_entry_rlp_round_trip_random() {
264 let entry = StakeTableEntry {
265 stake_key: BLSPubKey::generated_from_seed_indexed(
266 Default::default(),
267 Default::default(),
268 )
269 .0,
270 stake_amount: U256::ONE,
271 };
272
273 let mut bytes = vec![];
274 entry.encode(&mut bytes);
275 assert_eq!(bytes.len(), entry.length());
276
277 let mut buf = bytes.as_slice();
278 assert_eq!(entry, StakeTableEntry::decode(&mut buf).unwrap());
279 assert!(buf.is_empty());
280 }
281
282 #[test_log::test]
283 fn bls_stake_table_entry_rlp_round_trip_zero() {
284 let mut stake_key =
285 BLSPubKey::generated_from_seed_indexed(Default::default(), Default::default()).0;
286 stake_key.zeroize();
287 let entry = StakeTableEntry {
288 stake_key,
289 stake_amount: U256::ZERO,
290 };
291
292 let mut bytes = vec![];
293 entry.encode(&mut bytes);
294 assert_eq!(bytes.len(), entry.length());
295
296 let mut buf = bytes.as_slice();
297 assert_eq!(entry, StakeTableEntry::decode(&mut buf).unwrap());
298 assert!(buf.is_empty());
299 }
300
301 #[test_log::test]
302 fn bls_stake_table_entry_invalid_malformed_key() {
303 let entry = StakeTableEntryRlp {
304 stake_key: "not a key".as_bytes().to_vec(),
305 stake_amount: U256::ZERO,
306 };
307
308 let mut buf = vec![];
309 entry.encode(&mut buf);
310 let err = StakeTableEntry::<BLSPubKey>::decode(&mut buf.as_slice()).unwrap_err();
311 assert_eq!(
312 err,
313 alloy_rlp::Error::Custom("input is valid RLP but not a valid StakeTableEntry"),
314 );
315 }
316}