Skip to content
This repository has been archived by the owner on Feb 21, 2024. It is now read-only.

Commit

Permalink
Implement PublicValues retrieval from public inputs (0xPolygonZero#…
Browse files Browse the repository at this point in the history
…1405)

* Implement PublicValues retrieval from public inputs

* Use utility method

* Remove generic argument

* Typo
  • Loading branch information
Nashtare authored Dec 5, 2023
1 parent 6c3e3c0 commit 7ac6bf2
Show file tree
Hide file tree
Showing 4 changed files with 182 additions and 30 deletions.
23 changes: 9 additions & 14 deletions evm/src/fixed_recursive_verifier.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1100,13 +1100,12 @@ where
}

// Initialize the genesis state trie digest.
let genesis_state_trie_keys = TrieRootsTarget::SIZE * 2
+ BlockMetadataTarget::SIZE
+ BlockHashesTarget::BLOCK_HASHES_SIZE
..TrieRootsTarget::SIZE * 2
+ BlockMetadataTarget::SIZE
+ BlockHashesTarget::BLOCK_HASHES_SIZE
+ 8;
let genesis_state_trie_keys =
TrieRootsTarget::SIZE * 2 + BlockMetadataTarget::SIZE + BlockHashesTarget::SIZE
..TrieRootsTarget::SIZE * 2
+ BlockMetadataTarget::SIZE
+ BlockHashesTarget::SIZE
+ 8;
for (key, &value) in genesis_state_trie_keys.zip_eq(&h256_limbs::<F>(
public_values.extra_block_data.genesis_state_trie_root,
)) {
Expand All @@ -1115,9 +1114,7 @@ where

// Initialize block hashes.
let block_hashes_keys = TrieRootsTarget::SIZE * 2 + BlockMetadataTarget::SIZE
..TrieRootsTarget::SIZE * 2
+ BlockMetadataTarget::SIZE
+ BlockHashesTarget::BLOCK_HASHES_SIZE
..TrieRootsTarget::SIZE * 2 + BlockMetadataTarget::SIZE + BlockHashesTarget::SIZE
- 8;

for i in 0..public_values.block_hashes.prev_hashes.len() - 1 {
Expand All @@ -1126,10 +1123,8 @@ where
nonzero_pis.insert(block_hashes_keys.start + 8 * (i + 1) + j, targets[j]);
}
}
let block_hashes_current_start = TrieRootsTarget::SIZE * 2
+ BlockMetadataTarget::SIZE
+ BlockHashesTarget::BLOCK_HASHES_SIZE
- 8;
let block_hashes_current_start =
TrieRootsTarget::SIZE * 2 + BlockMetadataTarget::SIZE + BlockHashesTarget::SIZE - 8;
let cur_targets = h256_limbs::<F>(public_values.block_hashes.prev_hashes[255]);
for i in 0..8 {
nonzero_pis.insert(block_hashes_current_start + i, cur_targets[i]);
Expand Down
148 changes: 135 additions & 13 deletions evm/src/proof.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use ethereum_types::{Address, H256, U256};
use ethereum_types::{Address, H160, H256, U256};
use itertools::Itertools;
use plonky2::field::extension::{Extendable, FieldExtension};
use plonky2::fri::oracle::PolynomialBatch;
Expand All @@ -19,6 +19,7 @@ use serde::{Deserialize, Serialize};
use crate::all_stark::NUM_TABLES;
use crate::config::StarkConfig;
use crate::cross_table_lookup::GrandProductChallengeSet;
use crate::util::{get_h160, get_h256, h2u};

/// A STARK proof for each table, plus some metadata used to create recursive wrapper proofs.
#[derive(Debug, Clone)]
Expand Down Expand Up @@ -47,7 +48,7 @@ pub(crate) struct AllProofChallenges<F: RichField + Extendable<D>, const D: usiz
}

/// Memory values which are public.
#[derive(Debug, Clone, Default, Deserialize, Serialize)]
#[derive(Debug, Clone, Default, PartialEq, Eq, Deserialize, Serialize)]
pub struct PublicValues {
/// Trie hashes before the execution of the local state transition
pub trie_roots_before: TrieRoots,
Expand All @@ -61,8 +62,50 @@ pub struct PublicValues {
pub extra_block_data: ExtraBlockData,
}

impl PublicValues {
/// Extracts public values from the given public inputs of a proof.
/// Public values are always the first public inputs added to the circuit,
/// so we can start extracting at index 0.
pub fn from_public_inputs<F: RichField>(pis: &[F]) -> Self {
assert!(
pis.len()
> TrieRootsTarget::SIZE * 2
+ BlockMetadataTarget::SIZE
+ BlockHashesTarget::SIZE
+ ExtraBlockDataTarget::SIZE
- 1
);

let trie_roots_before = TrieRoots::from_public_inputs(&pis[0..TrieRootsTarget::SIZE]);
let trie_roots_after =
TrieRoots::from_public_inputs(&pis[TrieRootsTarget::SIZE..TrieRootsTarget::SIZE * 2]);
let block_metadata = BlockMetadata::from_public_inputs(
&pis[TrieRootsTarget::SIZE * 2..TrieRootsTarget::SIZE * 2 + BlockMetadataTarget::SIZE],
);
let block_hashes = BlockHashes::from_public_inputs(
&pis[TrieRootsTarget::SIZE * 2 + BlockMetadataTarget::SIZE
..TrieRootsTarget::SIZE * 2 + BlockMetadataTarget::SIZE + BlockHashesTarget::SIZE],
);
let extra_block_data = ExtraBlockData::from_public_inputs(
&pis[TrieRootsTarget::SIZE * 2 + BlockMetadataTarget::SIZE + BlockHashesTarget::SIZE
..TrieRootsTarget::SIZE * 2
+ BlockMetadataTarget::SIZE
+ BlockHashesTarget::SIZE
+ ExtraBlockDataTarget::SIZE],
);

Self {
trie_roots_before,
trie_roots_after,
block_metadata,
block_hashes,
extra_block_data,
}
}
}

/// Trie hashes.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct TrieRoots {
/// State trie hash.
pub state_root: H256,
Expand All @@ -72,6 +115,22 @@ pub struct TrieRoots {
pub receipts_root: H256,
}

impl TrieRoots {
pub fn from_public_inputs<F: RichField>(pis: &[F]) -> Self {
assert!(pis.len() == TrieRootsTarget::SIZE);

let state_root = get_h256(&pis[0..8]);
let transactions_root = get_h256(&pis[8..16]);
let receipts_root = get_h256(&pis[16..24]);

Self {
state_root,
transactions_root,
receipts_root,
}
}
}

// There should be 256 previous hashes stored, so the default should also contain 256 values.
impl Default for BlockHashes {
fn default() -> Self {
Expand All @@ -88,7 +147,7 @@ impl Default for BlockHashes {
///
/// When the block number is less than 256, dummy values, i.e. `H256::default()`,
/// should be used for the additional block hashes.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct BlockHashes {
/// The previous 256 hashes to the current block. The leftmost hash, i.e. `prev_hashes[0]`,
/// is the oldest, and the rightmost, i.e. `prev_hashes[255]` is the hash of the parent block.
Expand All @@ -97,9 +156,23 @@ pub struct BlockHashes {
pub cur_hash: H256,
}

impl BlockHashes {
pub fn from_public_inputs<F: RichField>(pis: &[F]) -> Self {
assert!(pis.len() == BlockHashesTarget::SIZE);

let prev_hashes: [H256; 256] = core::array::from_fn(|i| get_h256(&pis[8 * i..8 + 8 * i]));
let cur_hash = get_h256(&pis[2048..2056]);

Self {
prev_hashes: prev_hashes.to_vec(),
cur_hash,
}
}
}

/// Metadata contained in a block header. Those are identical between
/// all state transition proofs within the same block.
#[derive(Debug, Clone, Default, Deserialize, Serialize)]
#[derive(Debug, Clone, Default, PartialEq, Eq, Deserialize, Serialize)]
pub struct BlockMetadata {
/// The address of this block's producer.
pub block_beneficiary: Address,
Expand All @@ -123,9 +196,40 @@ pub struct BlockMetadata {
pub block_bloom: [U256; 8],
}

impl BlockMetadata {
pub fn from_public_inputs<F: RichField>(pis: &[F]) -> Self {
assert!(pis.len() == BlockMetadataTarget::SIZE);

let block_beneficiary = get_h160(&pis[0..5]);
let block_timestamp = pis[5].to_canonical_u64().into();
let block_number = pis[6].to_canonical_u64().into();
let block_difficulty = pis[7].to_canonical_u64().into();
let block_random = get_h256(&pis[8..16]);
let block_gaslimit = pis[16].to_canonical_u64().into();
let block_chain_id = pis[17].to_canonical_u64().into();
let block_base_fee =
(pis[18].to_canonical_u64() + (pis[19].to_canonical_u64() << 32)).into();
let block_gas_used = pis[20].to_canonical_u64().into();
let block_bloom = core::array::from_fn(|i| h2u(get_h256(&pis[21 + 8 * i..29 + 8 * i])));

Self {
block_beneficiary,
block_timestamp,
block_number,
block_difficulty,
block_random,
block_gaslimit,
block_chain_id,
block_base_fee,
block_gas_used,
block_bloom,
}
}
}

/// Additional block data that are specific to the local transaction being proven,
/// unlike `BlockMetadata`.
#[derive(Debug, Clone, Default, Deserialize, Serialize)]
#[derive(Debug, Clone, Default, PartialEq, Eq, Deserialize, Serialize)]
pub struct ExtraBlockData {
/// The state trie digest of the genesis block.
pub genesis_state_trie_root: H256,
Expand All @@ -142,6 +246,26 @@ pub struct ExtraBlockData {
pub gas_used_after: U256,
}

impl ExtraBlockData {
pub fn from_public_inputs<F: RichField>(pis: &[F]) -> Self {
assert!(pis.len() == ExtraBlockDataTarget::SIZE);

let genesis_state_trie_root = get_h256(&pis[0..8]);
let txn_number_before = pis[8].to_canonical_u64().into();
let txn_number_after = pis[9].to_canonical_u64().into();
let gas_used_before = pis[10].to_canonical_u64().into();
let gas_used_after = pis[11].to_canonical_u64().into();

Self {
genesis_state_trie_root,
txn_number_before,
txn_number_after,
gas_used_before,
gas_used_after,
}
}
}

/// Memory values which are public.
/// Note: All the larger integers are encoded with 32-bit limbs in little-endian order.
#[derive(Eq, PartialEq, Debug)]
Expand Down Expand Up @@ -285,7 +409,7 @@ impl PublicValuesTarget {
pis.len()
> TrieRootsTarget::SIZE * 2
+ BlockMetadataTarget::SIZE
+ BlockHashesTarget::BLOCK_HASHES_SIZE
+ BlockHashesTarget::SIZE
+ ExtraBlockDataTarget::SIZE
- 1
);
Expand All @@ -303,15 +427,13 @@ impl PublicValuesTarget {
&pis[TrieRootsTarget::SIZE * 2 + BlockMetadataTarget::SIZE
..TrieRootsTarget::SIZE * 2
+ BlockMetadataTarget::SIZE
+ BlockHashesTarget::BLOCK_HASHES_SIZE],
+ BlockHashesTarget::SIZE],
),
extra_block_data: ExtraBlockDataTarget::from_public_inputs(
&pis[TrieRootsTarget::SIZE * 2
+ BlockMetadataTarget::SIZE
+ BlockHashesTarget::BLOCK_HASHES_SIZE
&pis[TrieRootsTarget::SIZE * 2 + BlockMetadataTarget::SIZE + BlockHashesTarget::SIZE
..TrieRootsTarget::SIZE * 2
+ BlockMetadataTarget::SIZE
+ BlockHashesTarget::BLOCK_HASHES_SIZE
+ BlockHashesTarget::SIZE
+ ExtraBlockDataTarget::SIZE],
),
}
Expand Down Expand Up @@ -568,7 +690,7 @@ pub(crate) struct BlockHashesTarget {

impl BlockHashesTarget {
/// Number of `Target`s required for previous and current block hashes.
pub(crate) const BLOCK_HASHES_SIZE: usize = 2056;
pub(crate) const SIZE: usize = 2056;

/// Extracts the previous and current block hash `Target`s from the public input `Target`s.
/// The provided `pis` should start with the block hashes.
Expand Down
22 changes: 22 additions & 0 deletions evm/src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -210,3 +210,25 @@ pub(crate) fn biguint_to_mem_vec(x: BigUint) -> Vec<U256> {
pub(crate) fn h2u(h: H256) -> U256 {
U256::from_big_endian(&h.0)
}

pub(crate) fn get_h160<F: RichField>(slice: &[F]) -> H160 {
H160::from_slice(
&slice
.iter()
.rev()
.map(|x| x.to_canonical_u64() as u32)
.flat_map(|limb| limb.to_be_bytes())
.collect_vec(),
)
}

pub(crate) fn get_h256<F: RichField>(slice: &[F]) -> H256 {
H256::from_slice(
&slice
.iter()
.rev()
.map(|x| x.to_canonical_u64() as u32)
.flat_map(|limb| limb.to_be_bytes())
.collect_vec(),
)
}
19 changes: 16 additions & 3 deletions evm/tests/empty_txn_list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use plonky2_evm::all_stark::AllStark;
use plonky2_evm::config::StarkConfig;
use plonky2_evm::fixed_recursive_verifier::AllRecursiveCircuits;
use plonky2_evm::generation::{GenerationInputs, TrieInputs};
use plonky2_evm::proof::{BlockHashes, BlockMetadata, TrieRoots};
use plonky2_evm::proof::{BlockHashes, BlockMetadata, PublicValues, TrieRoots};
use plonky2_evm::Node;

type F = GoldilocksField;
Expand Down Expand Up @@ -132,8 +132,12 @@ fn test_empty_txn_list() -> anyhow::Result<()> {
timing.filter(Duration::from_millis(100)).print();
all_circuits.verify_root(root_proof.clone())?;

// Test retrieved public values from the proof public inputs.
let retrieved_public_values = PublicValues::from_public_inputs(&root_proof.public_inputs);
assert_eq!(retrieved_public_values, public_values);

// We can duplicate the proofs here because the state hasn't mutated.
let (agg_proof, public_values) = all_circuits.prove_aggregation(
let (agg_proof, agg_public_values) = all_circuits.prove_aggregation(
false,
&root_proof,
public_values.clone(),
Expand All @@ -143,9 +147,18 @@ fn test_empty_txn_list() -> anyhow::Result<()> {
)?;
all_circuits.verify_aggregation(&agg_proof)?;

let (block_proof, _) = all_circuits.prove_block(None, &agg_proof, public_values)?;
// Test retrieved public values from the proof public inputs.
let retrieved_public_values = PublicValues::from_public_inputs(&agg_proof.public_inputs);
assert_eq!(retrieved_public_values, agg_public_values);

let (block_proof, block_public_values) =
all_circuits.prove_block(None, &agg_proof, agg_public_values)?;
all_circuits.verify_block(&block_proof)?;

// Test retrieved public values from the proof public inputs.
let retrieved_public_values = PublicValues::from_public_inputs(&block_proof.public_inputs);
assert_eq!(retrieved_public_values, block_public_values);

// Get the verifier associated to these preprocessed circuits, and have it verify the block_proof.
let verifier = all_circuits.final_verifier_data();
verifier.verify(block_proof)
Expand Down

0 comments on commit 7ac6bf2

Please sign in to comment.