Skip to main content

axiom_core/
header_chain.rs

1use std::{iter, marker::PhantomData};
2
3use axiom_eth::{
4    block_header::{
5        get_block_header_rlp_max_lens_from_extra, get_boundary_block_data, EthBlockHeaderChip,
6        EthBlockHeaderWitness,
7    },
8    halo2_base::{
9        gates::{GateInstructions, RangeInstructions},
10        AssignedValue,
11        QuantumCell::Constant,
12    },
13    mpt::MPTChip,
14    rlc::circuit::builder::RlcCircuitBuilder,
15    utils::assign_vec,
16    utils::{
17        build_utils::aggregation::CircuitMetadata, eth_circuit::EthCircuitInstructions,
18        keccak::decorator::RlcKeccakCircuitImpl,
19    },
20};
21use itertools::Itertools;
22
23use crate::Field;
24
25pub type EthBlockHeaderChainCircuit<F> = RlcKeccakCircuitImpl<F, EthBlockHeaderChainInput<F>>;
26
27/// The input datum for the block header chain circuit. It is used to generate a circuit.
28///
29/// The public instances:
30/// * prev_hash (hi-lo)
31/// * end_hash (hi-lo)
32/// * solidityPacked(["uint32", "uint32"], [start_block_number, end_block_number]) (F)
33/// * merkle_roots: [HiLo<F>; max_depth + 1]
34#[derive(Clone, Debug)]
35pub struct EthBlockHeaderChainInput<F> {
36    /// The private inputs, which are the RLP encodings of the block headers
37    header_rlp_encodings: Vec<Vec<u8>>,
38    num_blocks: u32, // num_blocks in [1, 2 ** max_depth]
39    max_depth: usize,
40    /// Configuration parameters of the maximum number of bytes in the extra data field.
41    /// This is mostly to distinguish between mainnet and Goerli (or other forks).
42    max_extra_data_bytes: usize,
43    _marker: PhantomData<F>,
44}
45
46impl<F: Field> EthBlockHeaderChainInput<F> {
47    /// Handles resizing of each `header_rlp_encodings` to max length and also
48    /// resizes the number of rlp encodings to 2^max_depth.
49    pub fn new(
50        mut header_rlp_encodings: Vec<Vec<u8>>,
51        num_blocks: u32,
52        max_depth: usize,
53        max_extra_data_bytes: usize,
54    ) -> Self {
55        let (header_rlp_max_bytes, _) =
56            get_block_header_rlp_max_lens_from_extra(max_extra_data_bytes);
57        // pad to correct length with dummies
58        let dummy_block_rlp = header_rlp_encodings[0].clone();
59        header_rlp_encodings.resize(1 << max_depth, dummy_block_rlp);
60        for header_rlp in header_rlp_encodings.iter_mut() {
61            header_rlp.resize(header_rlp_max_bytes, 0u8);
62        }
63        Self {
64            header_rlp_encodings,
65            num_blocks,
66            max_depth,
67            max_extra_data_bytes,
68            _marker: PhantomData,
69        }
70    }
71}
72
73/// Data passed from phase0 to phase1
74#[derive(Clone, Debug)]
75pub struct EthBlockHeaderchainWitness<F: Field> {
76    pub max_extra_data_bytes: usize,
77    /// The chain of blocks, where the hash of block_chain\[i\] is proved to be the parent hash of block_chain\[i+1\]
78    pub block_chain: Vec<EthBlockHeaderWitness<F>>,
79    pub num_blocks_minus_one: AssignedValue<F>,
80    pub indicator: Vec<AssignedValue<F>>,
81}
82
83impl<F: Field> EthCircuitInstructions<F> for EthBlockHeaderChainInput<F> {
84    type FirstPhasePayload = EthBlockHeaderchainWitness<F>;
85
86    fn virtual_assign_phase0(
87        &self,
88        builder: &mut RlcCircuitBuilder<F>,
89        mpt: &MPTChip<F>,
90    ) -> Self::FirstPhasePayload {
91        let chip = EthBlockHeaderChip::new(mpt.rlp, self.max_extra_data_bytes);
92        let keccak = mpt.keccak();
93
94        // ======== FIRST PHASE ===========
95        let ctx = builder.base.main(0);
96        // ==== Load private inputs =====
97        let num_blocks = ctx.load_witness(F::from(self.num_blocks as u64));
98        let num_blocks_minus_one = chip.gate().sub(ctx, num_blocks, Constant(F::ONE));
99        // `num_blocks_minus_one` should be < 2^max_depth.
100        // We check this for safety, although it is not technically necessary because `num_blocks_minus_one` will equal the difference of the start, end block numbers, which are public inputs
101        chip.range().range_check(ctx, num_blocks_minus_one, self.max_depth);
102
103        // ==== Load RLP encoding and decode ====
104        let max_len = get_block_header_rlp_max_lens_from_extra(self.max_extra_data_bytes).0;
105        let block_headers = self
106            .header_rlp_encodings
107            .iter()
108            .map(|header| assign_vec(ctx, header.clone(), max_len))
109            .collect_vec();
110        let block_chain_witness =
111            chip.decompose_block_header_chain_phase0(builder, keccak, block_headers);
112        // All keccaks must be done in FirstPhase, so we compute the merkle mountain range from the RLP decoded witnesses now
113        let ctx = builder.base.main(0);
114        let num_leaves_bits = chip.gate().num_to_bits(ctx, num_blocks, self.max_depth + 1);
115        let block_hashes = block_chain_witness
116            .iter()
117            .map(|witness| witness.block_hash.output_bytes.as_ref().to_vec())
118            .collect_vec();
119        // mountain range in bytes
120        let mountain_range = keccak.merkle_mountain_range(ctx, &block_hashes, &num_leaves_bits);
121        let mountain_range = mountain_range
122            .into_iter()
123            .zip(num_leaves_bits.into_iter().rev())
124            .flat_map(|((_hash_bytes, hash_u128s), bit)| {
125                // if the bit is 0, then we set the hash root to 0
126                hash_u128s.map(|hash_u128| chip.gate().mul(ctx, hash_u128, bit))
127            })
128            .collect_vec();
129
130        let indicator =
131            chip.gate().idx_to_indicator(ctx, num_blocks_minus_one, block_chain_witness.len());
132        let (prev_block_hash, end_block_hash, block_numbers) =
133            get_boundary_block_data(ctx, chip.gate(), &block_chain_witness, &indicator);
134        let assigned_instances = iter::empty()
135            .chain(prev_block_hash)
136            .chain(end_block_hash)
137            .chain(iter::once(block_numbers))
138            .chain(mountain_range)
139            .collect_vec();
140        builder.base.assigned_instances[0] = assigned_instances;
141
142        EthBlockHeaderchainWitness {
143            max_extra_data_bytes: self.max_extra_data_bytes,
144            block_chain: block_chain_witness,
145            num_blocks_minus_one,
146            indicator,
147        }
148    }
149
150    fn virtual_assign_phase1(
151        &self,
152        builder: &mut RlcCircuitBuilder<F>,
153        mpt: &MPTChip<F>,
154        witness: Self::FirstPhasePayload,
155    ) {
156        let chip = EthBlockHeaderChip::new(mpt.rlp, witness.max_extra_data_bytes);
157        let _block_chain_trace = chip.decompose_block_header_chain_phase1(
158            builder,
159            witness.block_chain,
160            Some((witness.num_blocks_minus_one, witness.indicator)),
161        );
162    }
163}
164
165impl<F: Field> CircuitMetadata for EthBlockHeaderChainInput<F> {
166    const HAS_ACCUMULATOR: bool = false;
167    fn num_instance(&self) -> Vec<usize> {
168        vec![2 + 2 + 1 + 2 * (self.max_depth + 1)]
169    }
170}