hotshot_types/
light_client.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 associated with light client state
8
9use std::collections::HashMap;
10
11use alloy::primitives::U256;
12use ark_ed_on_bn254::EdwardsConfig as Config;
13use ark_ff::PrimeField;
14use ark_serialize::{CanonicalDeserialize, CanonicalSerialize};
15use jf_crhf::CRHF;
16use jf_rescue::{crhf::VariableLengthRescueCRHF, RescueError, RescueParameter};
17use jf_signature::schnorr;
18use jf_utils::to_bytes;
19use rand::SeedableRng;
20use rand_chacha::ChaCha20Rng;
21use serde::{Deserialize, Serialize};
22use tagged_base64::tagged;
23
24use crate::signature_key::BLSPubKey;
25
26/// Capacity of the stake table, used for light client
27/// TODO(Chengyu): this should be loaded from the sequencer config
28pub const STAKE_TABLE_CAPACITY: usize = 200;
29/// Base field in the prover circuit
30pub type CircuitField = ark_ed_on_bn254::Fq;
31/// Concrete type for light client state
32pub type LightClientState = GenericLightClientState<CircuitField>;
33/// Concreate type for light client state message to sign
34pub type LightClientStateMsg = GenericLightClientStateMsg<CircuitField>;
35/// Concrete type for stake table state
36pub type StakeTableState = GenericStakeTableState<CircuitField>;
37/// Signature scheme
38pub type StateSignatureScheme =
39    jf_signature::schnorr::SchnorrSignatureScheme<ark_ed_on_bn254::EdwardsConfig>;
40/// Signatures
41pub type StateSignature = schnorr::Signature<Config>;
42/// Verification key for verifying state signatures
43pub type StateVerKey = schnorr::VerKey<Config>;
44/// Signing key for signing a light client state
45pub type StateSignKey = schnorr::SignKey<ark_ed_on_bn254::Fr>;
46/// Concrete for circuit's public input
47pub type PublicInput = GenericPublicInput<CircuitField>;
48/// Key pairs for signing/verifying a light client state
49#[derive(Debug, Default, Clone)]
50pub struct StateKeyPair(pub schnorr::KeyPair<Config>);
51
52/// Request body to send to the state relay server
53#[derive(Clone, Debug, CanonicalSerialize, CanonicalDeserialize, Serialize, Deserialize)]
54pub struct StateSignatureRequestBody {
55    /// The public key associated with this request
56    pub key: StateVerKey,
57    /// The associated light client state
58    pub state: LightClientState,
59    /// The stake table used for the next HotShot block
60    pub next_stake: StakeTableState,
61    /// The associated signature of the light client state
62    pub signature: StateSignature,
63}
64
65/// The state signatures bundle is a light client state and its signatures collected
66#[derive(Clone, Debug, Serialize, Deserialize)]
67pub struct StateSignaturesBundle {
68    /// The state for this signatures bundle
69    pub state: LightClientState,
70    /// The stake table used in the next block (only different from voting_stake_table at the last block of every epoch)
71    pub next_stake: StakeTableState,
72    /// The collected signatures
73    pub signatures: HashMap<StateVerKey, StateSignature>,
74    /// Total stakes associated with the signer
75    pub accumulated_weight: U256,
76}
77
78/// A light client state
79#[tagged("LIGHT_CLIENT_STATE")]
80#[derive(
81    Clone,
82    Debug,
83    CanonicalSerialize,
84    CanonicalDeserialize,
85    Default,
86    Eq,
87    PartialEq,
88    PartialOrd,
89    Ord,
90    Hash,
91    Copy,
92)]
93pub struct GenericLightClientState<F: PrimeField> {
94    /// Current view number
95    pub view_number: u64,
96    /// Current block height
97    pub block_height: u64,
98    /// Root of the block commitment tree
99    pub block_comm_root: F,
100}
101
102pub type GenericLightClientStateMsg<F> = [F; 3];
103
104impl<F: PrimeField> From<GenericLightClientState<F>> for GenericLightClientStateMsg<F> {
105    fn from(state: GenericLightClientState<F>) -> Self {
106        [
107            F::from(state.view_number),
108            F::from(state.block_height),
109            state.block_comm_root,
110        ]
111    }
112}
113
114impl<F: PrimeField> From<&GenericLightClientState<F>> for GenericLightClientStateMsg<F> {
115    fn from(state: &GenericLightClientState<F>) -> Self {
116        [
117            F::from(state.view_number),
118            F::from(state.block_height),
119            state.block_comm_root,
120        ]
121    }
122}
123
124impl<F: PrimeField + RescueParameter> GenericLightClientState<F> {
125    pub fn new(
126        view_number: u64,
127        block_height: u64,
128        block_comm_root: &[u8],
129    ) -> anyhow::Result<Self> {
130        Ok(Self {
131            view_number,
132            block_height,
133            block_comm_root: hash_bytes_to_field(block_comm_root)?,
134        })
135    }
136}
137
138/// Stake table state
139#[tagged("STAKE_TABLE_STATE")]
140#[derive(
141    Clone,
142    Debug,
143    CanonicalSerialize,
144    CanonicalDeserialize,
145    Default,
146    Eq,
147    PartialEq,
148    PartialOrd,
149    Ord,
150    Hash,
151    Copy,
152)]
153pub struct GenericStakeTableState<F: PrimeField> {
154    /// Commitments to the table column for BLS public keys
155    pub bls_key_comm: F,
156    /// Commitments to the table column for Schnorr public keys
157    pub schnorr_key_comm: F,
158    /// Commitments to the table column for Stake amounts
159    pub amount_comm: F,
160    /// threshold
161    pub threshold: F,
162}
163
164impl<F: PrimeField> From<GenericStakeTableState<F>> for [F; 4] {
165    fn from(state: GenericStakeTableState<F>) -> Self {
166        [
167            state.bls_key_comm,
168            state.schnorr_key_comm,
169            state.amount_comm,
170            state.threshold,
171        ]
172    }
173}
174
175impl std::ops::Deref for StateKeyPair {
176    type Target = schnorr::KeyPair<Config>;
177
178    fn deref(&self) -> &Self::Target {
179        &self.0
180    }
181}
182
183impl StateKeyPair {
184    /// Generate key pairs from private signing keys
185    #[must_use]
186    pub fn from_sign_key(sk: StateSignKey) -> Self {
187        Self(schnorr::KeyPair::<Config>::from(sk))
188    }
189
190    /// Generate key pairs from `thread_rng()`
191    #[must_use]
192    pub fn generate() -> StateKeyPair {
193        schnorr::KeyPair::generate(&mut rand::thread_rng()).into()
194    }
195
196    /// Generate key pairs from seed
197    #[must_use]
198    pub fn generate_from_seed(seed: [u8; 32]) -> StateKeyPair {
199        schnorr::KeyPair::generate(&mut ChaCha20Rng::from_seed(seed)).into()
200    }
201
202    /// Generate key pairs from an index and a seed
203    #[must_use]
204    pub fn generate_from_seed_indexed(seed: [u8; 32], index: u64) -> StateKeyPair {
205        let mut hasher = blake3::Hasher::new();
206        hasher.update(&seed);
207        hasher.update(&index.to_le_bytes());
208        let new_seed = *hasher.finalize().as_bytes();
209        Self::generate_from_seed(new_seed)
210    }
211}
212
213impl From<schnorr::KeyPair<Config>> for StateKeyPair {
214    fn from(value: schnorr::KeyPair<Config>) -> Self {
215        StateKeyPair(value)
216    }
217}
218
219/// Public input to the light client state prover service
220#[derive(Clone, Debug)]
221pub struct GenericPublicInput<F: PrimeField> {
222    // new light client state
223    pub lc_state: GenericLightClientState<F>,
224    // voting stake table state
225    pub voting_st_state: GenericStakeTableState<F>,
226    // next-block stake table state
227    pub next_st_state: GenericStakeTableState<F>,
228}
229
230impl<F: PrimeField> GenericPublicInput<F> {
231    /// Construct a public input from light client state and static stake table state
232    pub fn new(
233        lc_state: GenericLightClientState<F>,
234        voting_st_state: GenericStakeTableState<F>,
235        next_st_state: GenericStakeTableState<F>,
236    ) -> Self {
237        Self {
238            lc_state,
239            voting_st_state,
240            next_st_state,
241        }
242    }
243
244    /// Convert to a vector of field elements
245    pub fn to_vec(&self) -> Vec<F> {
246        vec![
247            F::from(self.lc_state.view_number),
248            F::from(self.lc_state.block_height),
249            self.lc_state.block_comm_root,
250            self.voting_st_state.bls_key_comm,
251            self.voting_st_state.schnorr_key_comm,
252            self.voting_st_state.amount_comm,
253            self.voting_st_state.threshold,
254            self.next_st_state.bls_key_comm,
255            self.next_st_state.schnorr_key_comm,
256            self.next_st_state.amount_comm,
257            self.next_st_state.threshold,
258        ]
259    }
260}
261
262impl<F: PrimeField> From<GenericPublicInput<F>> for Vec<F> {
263    fn from(v: GenericPublicInput<F>) -> Self {
264        vec![
265            F::from(v.lc_state.view_number),
266            F::from(v.lc_state.block_height),
267            v.lc_state.block_comm_root,
268            v.voting_st_state.bls_key_comm,
269            v.voting_st_state.schnorr_key_comm,
270            v.voting_st_state.amount_comm,
271            v.voting_st_state.threshold,
272            v.next_st_state.bls_key_comm,
273            v.next_st_state.schnorr_key_comm,
274            v.next_st_state.amount_comm,
275            v.next_st_state.threshold,
276        ]
277    }
278}
279
280impl<F: PrimeField> From<Vec<F>> for GenericPublicInput<F> {
281    fn from(v: Vec<F>) -> Self {
282        let lc_state = GenericLightClientState {
283            view_number: v[0].into_bigint().as_ref()[0],
284            block_height: v[1].into_bigint().as_ref()[0],
285            block_comm_root: v[2],
286        };
287        let voting_st_state = GenericStakeTableState {
288            bls_key_comm: v[3],
289            schnorr_key_comm: v[4],
290            amount_comm: v[5],
291            threshold: v[6],
292        };
293        let next_st_state = GenericStakeTableState {
294            bls_key_comm: v[7],
295            schnorr_key_comm: v[8],
296            amount_comm: v[9],
297            threshold: v[10],
298        };
299        Self {
300            lc_state,
301            voting_st_state,
302            next_st_state,
303        }
304    }
305}
306
307pub fn hash_bytes_to_field<F: RescueParameter>(bytes: &[u8]) -> Result<F, RescueError> {
308    // make sure that `mod_order` won't happen.
309    let bytes_len = (<F as PrimeField>::MODULUS_BIT_SIZE.div_ceil(8) - 1) as usize;
310    let elem = bytes
311        .chunks(bytes_len)
312        .map(F::from_le_bytes_mod_order)
313        .collect::<Vec<_>>();
314    Ok(VariableLengthRescueCRHF::<_, 1>::evaluate(elem)?[0])
315}
316
317/// This trait is for light client use. It converts the stake table items into
318/// field elements. These items will then be digested into a part of the light client state.
319pub trait ToFieldsLightClientCompat {
320    const SIZE: usize;
321    fn to_fields(&self) -> Vec<CircuitField>;
322}
323
324impl ToFieldsLightClientCompat for StateVerKey {
325    const SIZE: usize = 2;
326    /// This should be compatible with our legacy implementation.
327    fn to_fields(&self) -> Vec<CircuitField> {
328        let p = self.to_affine();
329        vec![p.x, p.y]
330    }
331}
332
333impl ToFieldsLightClientCompat for BLSPubKey {
334    const SIZE: usize = 3;
335    /// This should be compatible with our legacy implementation.
336    fn to_fields(&self) -> Vec<CircuitField> {
337        match to_bytes!(&self.to_affine()) {
338            Ok(bytes) => {
339                vec![
340                    CircuitField::from_le_bytes_mod_order(&bytes[..31]),
341                    CircuitField::from_le_bytes_mod_order(&bytes[31..62]),
342                    CircuitField::from_le_bytes_mod_order(&bytes[62..]),
343                ]
344            },
345            Err(_) => unreachable!(),
346        }
347    }
348}