Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Resolve attested/finalized header confusion in rotation flow #44

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions contract-tests/tests/rotation_input_encoding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use itertools::Itertools;
use lightclient_circuits::committee_update_circuit::CommitteeUpdateCircuit;
use lightclient_circuits::halo2_proofs::halo2curves::bn256;
use lightclient_circuits::poseidon::poseidon_committee_commitment_from_compressed;
use lightclient_circuits::witness::CommitteeRotationArgs;
use lightclient_circuits::witness::CommitteeUpdateArgs;
use rstest::rstest;
use ssz_rs::prelude::*;
use ssz_rs::Merkleized;
Expand All @@ -23,12 +23,12 @@ abigen!(
"../contracts/out/RotateExternal.sol/RotateExternal.json"
);

// CommitteeRotationArgs type produced by abigen macro matches the solidity struct type
impl<Spec: eth_types::Spec> From<CommitteeRotationArgs<Spec>> for RotateInput
// CommitteeUpdateArgs type produced by abigen macro matches the solidity struct type
impl<Spec: eth_types::Spec> From<CommitteeUpdateArgs<Spec>> for RotateInput
where
[(); Spec::SYNC_COMMITTEE_SIZE]:,
{
fn from(args: CommitteeRotationArgs<Spec>) -> Self {
fn from(args: CommitteeUpdateArgs<Spec>) -> Self {
let poseidon_commitment_be = poseidon_committee_commitment_from_compressed(
&args.pubkeys_compressed.iter().cloned().collect_vec(),
)
Expand Down Expand Up @@ -101,8 +101,8 @@ mod tests {
let accumulator = [bn256::Fr::zero(); 12]; // this can be anything.. The test is just checking it gets correctly concatenated to the start of the encoded input

let instance = CommitteeUpdateCircuit::<Minimal, bn256::Fr>::instance(&witness, LIMB_BITS);
let finalized_block_root = witness
.finalized_header
let justified_block_root = witness
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This naming is incorrect. We work with finalized and attested objects w.r.t. the light client protocol. There is no justified things in the light client protocol

.attested_header
.clone()
.hash_tree_root()
.unwrap()
Expand All @@ -116,7 +116,7 @@ mod tests {
let result = contract
.to_public_inputs(
RotateInput::from(witness),
finalized_block_root,
justified_block_root,
solidity_encode_fr_array(&accumulator),
)
.call()
Expand Down
6 changes: 3 additions & 3 deletions contracts/rust-abi/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use ethers::contract::abigen;
use itertools::Itertools;
use lightclient_circuits::{
poseidon::poseidon_committee_commitment_from_compressed,
witness::{CommitteeRotationArgs, SyncStepArgs},
witness::{CommitteeUpdateArgs, SyncStepArgs},
};
use ssz_rs::{Merkleized, Vector};
use std::ops::Deref;
Expand Down Expand Up @@ -56,11 +56,11 @@ impl<Spec: eth_types::Spec> From<SyncStepArgs<Spec>> for SyncStepInput {
}

// CommitteeRotationArgs type produced by abigen macro matches the solidity struct type
impl<Spec: eth_types::Spec> From<CommitteeRotationArgs<Spec>> for RotateInput
impl<Spec: eth_types::Spec> From<CommitteeUpdateArgs<Spec>> for RotateInput
where
[(); Spec::SYNC_COMMITTEE_SIZE]:,
{
fn from(args: CommitteeRotationArgs<Spec>) -> Self {
fn from(args: CommitteeUpdateArgs<Spec>) -> Self {
let poseidon_commitment_le = poseidon_committee_commitment_from_compressed(
&args.pubkeys_compressed.iter().cloned().collect_vec(),
)
Expand Down
10 changes: 6 additions & 4 deletions contracts/src/RotateLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@ library RotateLib {
* @notice Compute the public input commitment for the rotation
* This must always match the method used in lightclient-circuits/src/committee_udate_circuit.rs - CommitteeUpdateCircuit::instance()
* @param args The arguments for the sync step
* @param justifiedHeaderRoot The root of the header that justifies the new sync committee which going to become finalized in the adjacent step.
* @param accumulator The accumulator to correctly verify aggregated (compressed) proof.
* @return The public input commitment that can be sent to the verifier contract.
*/
function toPublicInputs(RotateInput memory args, bytes32 finalizedHeaderRoot, uint256[12] memory accumulator) internal pure returns (uint256[77] memory) {
function toPublicInputs(RotateInput memory args, bytes32 justifiedHeaderRoot, uint256[12] memory accumulator) internal pure returns (uint256[77] memory) {
uint256[77] memory inputs;

for (uint256 i = 0; i < accumulator.length; i++) {
Expand All @@ -31,10 +33,10 @@ library RotateLib {
syncCommitteeSSZNumeric = syncCommitteeSSZNumeric / 2 ** 8;
}

uint256 finalizedHeaderRootNumeric = uint256(finalizedHeaderRoot);
uint256 justifiedHeaderRootNumeric = uint256(justifiedHeaderRoot);
for (uint256 j = 0; j < 32; j++) {
inputs[accumulator.length + 64 - j] = finalizedHeaderRootNumeric % 2 ** 8;
finalizedHeaderRootNumeric = finalizedHeaderRootNumeric / 2 ** 8;
inputs[accumulator.length + 64 - j] = justifiedHeaderRootNumeric % 2 ** 8;
justifiedHeaderRootNumeric = justifiedHeaderRootNumeric / 2 ** 8;
}

return inputs;
Expand Down
1 change: 1 addition & 0 deletions contracts/src/Spectre.sol
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ contract Spectre {
// that there exists an SSZ proof that can verify this SSZ commitment to the committee is in the state
uint256 currentPeriod = getSyncCommitteePeriod(stepInput.finalizedSlot);
uint256 nextPeriod = currentPeriod + 1;
// since step proof is for the epoch after committee update the `stepInput.finalizedHeaderRoot` here is `rotateInput.justifiedHeaderRoot`.
uint256[77] memory verifierInput = rotateInput.toPublicInputs(stepInput.finalizedHeaderRoot, accumulator);
bool rotateSuccess = committeeUpdateVerifier.verify(verifierInput, rotateProof);
if (!rotateSuccess) {
Expand Down
6 changes: 3 additions & 3 deletions contracts/src/SyncStepLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,17 @@ library SyncStepLib {
* @notice Compute the public input commitment for the sync step given this input.
* This must always match the prodecure used in lightclient-circuits/src/sync_step_circuit.rs - SyncStepCircuit::instance()
* @param args The arguments for the sync step
* @param keysPoseidonCommitment The commitment to the keys used in the sync step
* @param committeePoseidon The commitment to the keys used in the sync step
* @return The public input commitment that can be sent to the verifier contract.
*/
function toInputCommitment(SyncStepInput memory args, bytes32 keysPoseidonCommitment) internal pure returns (uint256) {
function toInputCommitment(SyncStepInput memory args, bytes32 committeePoseidon) internal pure returns (uint256) {
bytes32 h = sha256(abi.encodePacked(
EndianConversions.toLittleEndian64(args.attestedSlot),
EndianConversions.toLittleEndian64(args.finalizedSlot),
EndianConversions.toLittleEndian64(args.participation),
args.finalizedHeaderRoot,
args.executionPayloadRoot,
keysPoseidonCommitment
committeePoseidon
));
uint256 commitment = uint256(EndianConversions.toLittleEndian(uint256(h)));
return commitment & ((uint256(1) << 253) - 1); // truncated to 253 bits
Expand Down
21 changes: 14 additions & 7 deletions contracts/test/RotateExternal.sol
Original file line number Diff line number Diff line change
@@ -1,18 +1,25 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;

import { RotateLib } from "../src/RotateLib.sol";
import {RotateLib} from "../src/RotateLib.sol";

/**
* @title RotateExternal
* @dev This contract exists solely for the purpose of exposing the RotateLib functions
* so they can be used in the Rust test suite. It should not be part of a production deployment
*/
* @title RotateExternal
* @dev This contract exists solely for the purpose of exposing the RotateLib functions
* so they can be used in the Rust test suite. It should not be part of a production deployment
*/
contract RotateExternal {
using RotateLib for RotateLib.RotateInput;

function toPublicInputs(RotateLib.RotateInput calldata args, bytes32 finalizedHeaderRoot, uint256[12] memory accumulator) public pure returns (uint256[] memory) {
uint256[77] memory commitment = args.toPublicInputs(finalizedHeaderRoot, accumulator);
function toPublicInputs(
RotateLib.RotateInput calldata args,
bytes32 justifiedHeaderRoot,
uint256[12] memory accumulator
) public pure returns (uint256[] memory) {
uint256[77] memory commitment = args.toPublicInputs(
justifiedHeaderRoot,
accumulator
);
// copy all elements into a dynamic array. We need to do this because ethers-rs has a bug that can't support uint256[65] return types
uint256[] memory result = new uint256[](77);
for (uint256 i = 0; i < commitment.length; i++) {
Expand Down
48 changes: 24 additions & 24 deletions lightclient-circuits/src/committee_update_circuit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ impl<S: Spec, F: Field> CommitteeUpdateCircuit<S, F> {
fn synthesize(
builder: &mut ShaCircuitBuilder<F, ShaBitGateManager<F>>,
fp_chip: &FpChip<F>,
args: &witness::CommitteeRotationArgs<S>,
args: &witness::CommitteeUpdateArgs<S>,
) -> Result<Vec<AssignedValue<F>>, Error> {
let range = fp_chip.range();

Expand All @@ -63,23 +63,23 @@ impl<S: Spec, F: Field> CommitteeUpdateCircuit<S, F> {
fq_array_poseidon(builder.main(), range.gate(), &pubkeys_x)?
};

// Finalized header
let finalized_state_root = args
.finalized_header
// Justified header
let justified_state_root = args
.attested_header
.state_root
.as_ref()
.iter()
.map(|v| builder.main().load_witness(F::from(*v as u64)))
.collect_vec();
let finalized_header_root = ssz_merkleize_chunks(
let justified_header_root = ssz_merkleize_chunks(
builder,
&sha256_chip,
[
args.finalized_header.slot.into_witness(),
args.finalized_header.proposer_index.into_witness(),
args.finalized_header.parent_root.as_ref().into_witness(),
finalized_state_root.clone().into(),
args.finalized_header.body_root.as_ref().into_witness(),
args.attested_header.slot.into_witness(),
args.attested_header.proposer_index.into_witness(),
args.attested_header.parent_root.as_ref().into_witness(),
justified_state_root.clone().into(),
args.attested_header.body_root.as_ref().into_witness(),
],
)?;

Expand All @@ -91,13 +91,13 @@ impl<S: Spec, F: Field> CommitteeUpdateCircuit<S, F> {
.iter()
.map(|w| w.clone().into_witness()),
committee_root_ssz.clone().into(),
&finalized_state_root,
&justified_state_root,
S::SYNC_COMMITTEE_PUBKEYS_ROOT_INDEX,
)?;

let public_inputs = iter::once(poseidon_commit)
.chain(committee_root_ssz)
.chain(finalized_header_root)
.chain(justified_header_root)
.collect();

Ok(public_inputs)
Expand Down Expand Up @@ -159,7 +159,7 @@ impl<S: Spec, F: Field> CommitteeUpdateCircuit<S, F> {
}

pub fn instance(
args: &witness::CommitteeRotationArgs<S>,
args: &witness::CommitteeUpdateArgs<S>,
limb_bits: usize,
) -> Vec<Vec<bn256::Fr>>
where
Expand Down Expand Up @@ -190,12 +190,12 @@ impl<S: Spec, F: Field> CommitteeUpdateCircuit<S, F> {

let ssz_root = pk_vector.hash_tree_root().unwrap();

let finalized_header_root = args.finalized_header.clone().hash_tree_root().unwrap();
let justified_header_root = args.attested_header.clone().hash_tree_root().unwrap();

let instance_vec = iter::once(poseidon_commitment)
.chain(ssz_root.as_ref().iter().map(|b| bn256::Fr::from(*b as u64)))
.chain(
finalized_header_root
justified_header_root
.as_ref()
.iter()
.map(|b| bn256::Fr::from(*b as u64)),
Expand All @@ -208,12 +208,12 @@ impl<S: Spec, F: Field> CommitteeUpdateCircuit<S, F> {

impl<S: Spec> AppCircuit for CommitteeUpdateCircuit<S, bn256::Fr> {
type Pinning = Eth2ConfigPinning;
type Witness = witness::CommitteeRotationArgs<S>;
type Witness = witness::CommitteeUpdateArgs<S>;

fn create_circuit(
stage: CircuitBuilderStage,
pinning: Option<Self::Pinning>,
witness: &witness::CommitteeRotationArgs<S>,
witness: &witness::CommitteeUpdateArgs<S>,
k: u32,
) -> Result<impl crate::util::PinnableCircuit<bn256::Fr>, Error> {
let mut builder = Eth2CircuitBuilder::<ShaBitGateManager<bn256::Fr>>::from_stage(stage)
Expand Down Expand Up @@ -252,7 +252,7 @@ mod tests {

use crate::{
aggregation_circuit::AggregationConfigPinning, util::Halo2ConfigPinning,
witness::CommitteeRotationArgs,
witness::CommitteeUpdateArgs,
};

use super::*;
Expand All @@ -271,7 +271,7 @@ mod tests {
use snark_verifier_sdk::evm::{evm_verify, gen_evm_proof_shplonk};
use snark_verifier_sdk::{halo2::aggregation::AggregationCircuit, CircuitExt, Snark};

fn load_circuit_args() -> CommitteeRotationArgs<Testnet> {
fn load_circuit_args() -> CommitteeUpdateArgs<Testnet> {
#[derive(serde::Deserialize)]
struct ArgsJson {
finalized_header: BeaconBlockHeader,
Expand All @@ -285,18 +285,18 @@ mod tests {
finalized_header,
} = serde_json::from_slice(&fs::read("../test_data/rotation_512.json").unwrap()).unwrap();

CommitteeRotationArgs {
CommitteeUpdateArgs {
pubkeys_compressed,
_spec: PhantomData,
finalized_header,
attested_header: finalized_header,
sync_committee_branch: committee_root_branch,
}
}

fn gen_application_snark(
params: &ParamsKZG<bn256::Bn256>,
pk: &ProvingKey<bn256::G1Affine>,
witness: &CommitteeRotationArgs<Testnet>,
witness: &CommitteeUpdateArgs<Testnet>,
pinning_path: &str,
) -> Snark {
CommitteeUpdateCircuit::<Testnet, Fr>::gen_snark_shplonk(
Expand Down Expand Up @@ -343,7 +343,7 @@ mod tests {
PKEY_PATH,
PINNING_PATH,
false,
&CommitteeRotationArgs::<Testnet>::default(),
&CommitteeUpdateArgs::<Testnet>::default(),
);

let witness = load_circuit_args();
Expand Down Expand Up @@ -371,7 +371,7 @@ mod tests {
APP_PK_PATH,
APP_PINNING_PATH,
false,
&CommitteeRotationArgs::<Testnet>::default(),
&CommitteeUpdateArgs::<Testnet>::default(),
);

let witness = load_circuit_args();
Expand Down
2 changes: 1 addition & 1 deletion lightclient-circuits/src/sync_step_circuit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ impl<S: Spec, F: Field> StepCircuit<S, F> {
S::FINALIZED_HEADER_INDEX,
)?;

// verify execution state root against finilized block body merkle proof
// verify execution state root against finalized block body merkle proof
verify_merkle_proof(
builder,
&sha256_chip,
Expand Down
12 changes: 7 additions & 5 deletions lightclient-circuits/src/witness/rotation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,20 @@ use sha2::{Digest, Sha256};
use std::{iter, marker::PhantomData};

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CommitteeRotationArgs<S: Spec> {
pub struct CommitteeUpdateArgs<S: Spec> {
pub pubkeys_compressed: Vec<Vec<u8>>,

pub finalized_header: BeaconBlockHeader,
// Attested header that containts the state root with new sync committee.
// This header going to become finalized in the adjacent step proof (that is submited along with this one in Spectre.sol::rotate).
pub attested_header: BeaconBlockHeader,

pub sync_committee_branch: Vec<Vec<u8>>,

#[serde(skip)]
pub _spec: PhantomData<S>,
}

impl<S: Spec> Default for CommitteeRotationArgs<S> {
impl<S: Spec> Default for CommitteeUpdateArgs<S> {
fn default() -> Self {
let dummy_x_bytes = iter::once(192).pad_using(48, |_| 0).rev().collect_vec();

Expand Down Expand Up @@ -57,7 +59,7 @@ impl<S: Spec> Default for CommitteeRotationArgs<S> {
.take(S::SYNC_COMMITTEE_SIZE)
.collect_vec(),
sync_committee_branch,
finalized_header: BeaconBlockHeader {
attested_header: BeaconBlockHeader {
state_root: state_root.as_slice().try_into().unwrap(),
..Default::default()
},
Expand Down Expand Up @@ -99,7 +101,7 @@ mod tests {
#[test]
fn test_committee_update_default_witness() {
const K: u32 = 18;
let witness = CommitteeRotationArgs::<Testnet>::default();
let witness = CommitteeUpdateArgs::<Testnet>::default();

let circuit = CommitteeUpdateCircuit::<Testnet, Fr>::create_circuit(
CircuitBuilderStage::Mock,
Expand Down
4 changes: 2 additions & 2 deletions preprocessor/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use ethereum_consensus_types::{
LightClientUpdateCapella, Root,
};
use itertools::Itertools;
use lightclient_circuits::witness::{CommitteeRotationArgs, SyncStepArgs};
use lightclient_circuits::witness::{CommitteeUpdateArgs, SyncStepArgs};
use serde::{Deserialize, Serialize};
use ssz_rs::{Node, Vector};
use std::ops::Deref;
Expand All @@ -31,7 +31,7 @@ pub async fn light_client_update_to_args<S: Spec>(
>,
pubkeys_compressed: Vector<BlsPublicKey, { S::SYNC_COMMITTEE_SIZE }>,
domain: [u8; 32],
) -> eyre::Result<(SyncStepArgs<S>, CommitteeRotationArgs<S>)>
) -> eyre::Result<(SyncStepArgs<S>, CommitteeUpdateArgs<S>)>
where
[(); S::SYNC_COMMITTEE_SIZE]:,
[(); S::FINALIZED_HEADER_DEPTH]:,
Expand Down
Loading
Loading