Skip to content

Commit

Permalink
support compressed step verifier in contracts
Browse files Browse the repository at this point in the history
  • Loading branch information
nulltea committed Dec 11, 2023
1 parent 054d4cd commit 3e39300
Show file tree
Hide file tree
Showing 26 changed files with 222 additions and 258 deletions.
2 changes: 1 addition & 1 deletion contract-tests/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ edition = "2021"

[dependencies]
ethers = "2.0.10"
lightclient-circuits = { workspace = true }

[dev-dependencies]
rstest = "0.18.2"
tokio = { version = "1.32.0", features = ["rt", "macros"] }
anyhow = "1.0.75"
itertools = "0.11.0"

lightclient-circuits = { workspace = true }
test-utils = { workspace = true }
halo2curves = { workspace = true }
eth-types = { workspace = true }
Expand Down
12 changes: 12 additions & 0 deletions contract-tests/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use ethers::{
providers::{Http, Provider},
signers::{LocalWallet, Signer},
};
use lightclient_circuits::halo2_proofs::halo2curves::bn256;
use std::sync::Arc;

/// Return a fresh ethereum anvil instance and client to test against
Expand All @@ -21,3 +22,14 @@ pub fn make_client() -> (
SignerMiddleware::new(provider, wallet.with_chain_id(anvil.chain_id()));
(anvil, Arc::new(client))
}

pub fn decode_solidity_u256_array(uints: &[ethers::types::U256]) -> Vec<bn256::Fr> {
uints
.iter()
.map(|v| {
let mut b = [0_u8; 32];
v.to_little_endian(&mut b);
bn256::Fr::from_bytes(&b).expect("bad bn256::Fr encoding")
})
.collect()
}
54 changes: 14 additions & 40 deletions contract-tests/tests/rotation_input_encoding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,19 +29,11 @@ where
[(); Spec::SYNC_COMMITTEE_SIZE]:,
{
fn from(args: CommitteeRotationArgs<Spec>) -> Self {
let poseidon_commitment_be = poseidon_committee_commitment_from_compressed(
let poseidon_commitment = poseidon_committee_commitment_from_compressed(
&args.pubkeys_compressed.iter().cloned().collect_vec(),
)
.unwrap()
.into_iter()
.rev() // need to reverse to match the endianness of the solidity encoding
.collect_vec()
.try_into()
.unwrap();

// Endianess here is super confusing
// This should be solved by having `committee_poseidong` only be `uint256`
// See https://github.com/ChainSafe/Spectre/pull/42
);
let sync_committee_poseidon =
ethers::prelude::U256::from_little_endian(&poseidon_commitment.to_bytes());

let mut pk_vector: Vector<Vector<u8, 48>, { Spec::SYNC_COMMITTEE_SIZE }> = args
.pubkeys_compressed
Expand All @@ -61,7 +53,9 @@ where

RotateInput {
sync_committee_ssz,
sync_committee_poseidon: poseidon_commitment_be,
sync_committee_poseidon,
// this can be anything.. The test is just checking it gets correctly concatenated to the start of the encoded input
accumulator: Default::default(),
}
}
}
Expand All @@ -70,37 +64,19 @@ where
mod tests {
use super::*;

/// Convert a slice of field elements into an array of U256 ready to pass to a soliditiy call via ethers
fn solidity_encode_fr_array<const N: usize>(frs: &[bn256::Fr]) -> [ethers::types::U256; N] {
frs.iter()
.map(|v| ethers::types::U256::from_little_endian(&v.to_bytes()))
.collect_vec()
.try_into()
.expect("incompatible input slice length with return type")
}

fn decode_solidity_u256_array(uints: &[ethers::types::U256]) -> Vec<bn256::Fr> {
uints
.iter()
.map(|v| {
let mut b = [0_u8; 32];
v.to_little_endian(&mut b);
bn256::Fr::from_bytes(&b).expect("bad bn256::Fr encoding")
})
.collect()
}

#[rstest]
#[tokio::test]
async fn test_rotate_public_input_evm_equivalence(
#[files("../consensus-spec-tests/tests/minimal/capella/light_client/sync/pyspec_tests/**")]
#[exclude("deneb*")]
path: PathBuf,
) -> anyhow::Result<()> {
use contract_tests::decode_solidity_u256_array;

let (_, witness) = read_test_files_and_gen_witness(&path);
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 instance =
CommitteeUpdateCircuit::<Minimal, bn256::Fr>::get_instances(&witness, LIMB_BITS);
let finalized_block_root = witness
.finalized_header
.clone()
Expand All @@ -113,16 +89,14 @@ mod tests {
let (_anvil_instance, ethclient) = make_client();
let contract = RotateExternal::deploy(ethclient, ())?.send().await?;

let rotate_input = RotateInput::from(witness);
let result = contract
.to_public_inputs(
RotateInput::from(witness),
finalized_block_root,
solidity_encode_fr_array(&accumulator),
)
.to_public_inputs(rotate_input.clone(), finalized_block_root)
.call()
.await?;

let result_decoded = decode_solidity_u256_array(&result);
let accumulator = decode_solidity_u256_array(&rotate_input.accumulator);
// The expected result is the concatenation of the accumulator and the instance
let expected: Vec<_> = accumulator.iter().chain(instance[0].iter()).collect();
assert_eq!(result_decoded.iter().collect::<Vec<_>>(), expected);
Expand Down
39 changes: 19 additions & 20 deletions contract-tests/tests/spectre.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,13 @@ use std::path::PathBuf;
use std::sync::Arc;

use contract_tests::make_client;
use contracts::{CommitteeUpdateMockVerifier, Spectre, StepMockVerifier};
use contracts::{
CommitteeUpdateMockVerifier, Spectre, SyncStepCompressedMockVerifier, SyncStepInput,
};
use ethers::core::types::U256;
use ethers::providers::Middleware;
use rstest::rstest;
use test_utils::{
conversions::sync_input_from_args, get_initial_sync_committee_poseidon,
read_test_files_and_gen_witness,
};
use test_utils::{get_initial_sync_committee_poseidon, read_test_files_and_gen_witness};

const SLOTS_PER_EPOCH: usize = 8;
const EPOCHS_PER_SYNC_COMMITTEE_PERIOD: usize = 8;
Expand All @@ -22,7 +21,7 @@ const SLOTS_PER_SYNC_COMMITTEE_PERIOD: usize = EPOCHS_PER_SYNC_COMMITTEE_PERIOD
#[tokio::test]
async fn test_deploy_spectre() -> anyhow::Result<()> {
let (_anvil_instance, ethclient) = make_client();
let _contract = deploy_spectre_mock_verifiers(ethclient, 0, [0; 32], 0).await?;
let _contract = deploy_spectre_mock_verifiers(ethclient, 0, U256::zero(), 0).await?;
Ok(())
}

Expand Down Expand Up @@ -52,21 +51,21 @@ async fn test_contract_initialization_and_first_step(
assert_eq!(contract.head().call().await?, U256::from(0));

// call step with the input and proof
let step_input = sync_input_from_args(witness);
let step_input: SyncStepInput = witness.into();
let step_call = contract.step(step_input.clone(), Vec::new().into());
let _receipt = step_call.send().await?.confirmations(1).await?;

// post conditions
let head = U256::from(step_input.finalized_slot);
assert_eq!(contract.head().call().await?, head);
assert_eq!(
contract.block_header_roots(head).call().await?,
step_input.finalized_header_root
);
assert_eq!(
contract.execution_state_roots(head).call().await?,
step_input.execution_payload_root
);
// // post conditions
// let head = U256::from(step_input.finalized_slot);
// assert_eq!(contract.head().call().await?, head);
// assert_eq!(
// contract.block_header_roots(head).call().await?,
// step_input.finalized_header_root
// );
// assert_eq!(
// contract.execution_state_roots(head).call().await?,
// step_input.execution_payload_root
// );

Ok(())
}
Expand All @@ -79,10 +78,10 @@ async fn test_contract_initialization_and_first_step(
async fn deploy_spectre_mock_verifiers<M: Middleware + 'static>(
ethclient: Arc<M>,
initial_sync_period: usize,
initial_sync_committee_poseidon: [u8; 32],
initial_sync_committee_poseidon: U256,
slots_per_period: usize,
) -> anyhow::Result<Spectre<M>> {
let step_verifier = StepMockVerifier::deploy(ethclient.clone(), ())?
let step_verifier = SyncStepCompressedMockVerifier::deploy(ethclient.clone(), ())?
.send()
.await?;
let update_verifier = CommitteeUpdateMockVerifier::deploy(ethclient.clone(), ())?
Expand Down
18 changes: 10 additions & 8 deletions contract-tests/tests/step_input_encoding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ use contract_tests::make_client;
use eth_types::{Minimal, LIMB_BITS};
use ethers::contract::abigen;
use lightclient_circuits::halo2_proofs::halo2curves::bn256;
use lightclient_circuits::poseidon::poseidon_committee_commitment_from_uncompressed;
use lightclient_circuits::sync_step_circuit::StepCircuit;
use lightclient_circuits::witness::SyncStepArgs;
use rstest::rstest;
Expand Down Expand Up @@ -43,6 +42,7 @@ impl<Spec: eth_types::Spec> From<SyncStepArgs<Spec>> for SyncStepInput {
participation,
finalized_header_root,
execution_payload_root,
accumulator: Default::default()
}
}
}
Expand All @@ -54,21 +54,23 @@ async fn test_step_instance_commitment_evm_equivalence(
#[exclude("deneb*")]
path: PathBuf,
) -> anyhow::Result<()> {
use contract_tests::decode_solidity_u256_array;
use ethers::types::U256;

let (witness, _) = read_test_files_and_gen_witness(&path);
let instance = StepCircuit::<Minimal, bn256::Fr>::instance_commitment(&witness, LIMB_BITS);
let poseidon_commitment_le =
poseidon_committee_commitment_from_uncompressed(&witness.pubkeys_uncompressed)?;
let instance = StepCircuit::<Minimal, bn256::Fr>::get_instances(&witness, LIMB_BITS);

let (_anvil_instance, ethclient) = make_client();
let contract = SyncStepExternal::deploy(ethclient, ())?.send().await?;

let result = contract
.to_input_commitment(SyncStepInput::from(witness), poseidon_commitment_le)
.to_public_inputs(SyncStepInput::from(witness), U256::from_little_endian(&instance[0][1].to_bytes()))
.call()
.await?;
let mut result_bytes = [0_u8; 32];
result.to_little_endian(&mut result_bytes);
let result_decoded = decode_solidity_u256_array(&result);

assert_eq!(result_decoded[12], instance[0][0]); // public input commitment
assert_eq!(result_decoded[13], instance[0][1]); // committee poseidon

assert_eq!(bn256::Fr::from_bytes(&result_bytes).unwrap(), instance);
Ok(())
}
34 changes: 20 additions & 14 deletions contracts/rust-abi/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#![allow(incomplete_features)]
#![feature(generic_const_exprs)]
use ethers::contract::abigen;
use ethers::{contract::abigen, types::U256};
use itertools::Itertools;
use lightclient_circuits::{
poseidon::poseidon_committee_commitment_from_compressed,
Expand All @@ -17,6 +17,8 @@ abigen!(
"./out/committee_update_verifier.sol/Verifier.json";
StepMockVerifier,
"./out/SyncStepMockVerifier.sol/SyncStepMockVerifier.json";
SyncStepCompressedMockVerifier,
"./out/SyncStepMockVerifier.sol/SyncStepCompressedMockVerifier.json";
CommitteeUpdateMockVerifier,
"./out/CommitteeUpdateMockVerifier.sol/CommitteeUpdateMockVerifier.json";
RotateExternal,
Expand Down Expand Up @@ -51,20 +53,27 @@ impl<Spec: eth_types::Spec> From<SyncStepArgs<Spec>> for SyncStepInput {
participation,
finalized_header_root,
execution_payload_root,
..Default::default()
}
}
}

impl SyncStepInput {
pub fn set_accumulator(&mut self, accumulator: [U256; 12]) {
self.accumulator = accumulator;
}
}

// CommitteeRotationArgs type produced by abigen macro matches the solidity struct type
impl<Spec: eth_types::Spec> From<CommitteeRotationArgs<Spec>> for RotateInput
where
[(); Spec::SYNC_COMMITTEE_SIZE]:,
{
fn from(args: CommitteeRotationArgs<Spec>) -> Self {
let poseidon_commitment_le = poseidon_committee_commitment_from_compressed(
let sync_committee_poseidon = poseidon_committee_commitment_from_compressed(
&args.pubkeys_compressed.iter().cloned().collect_vec(),
)
.unwrap();
);
let sync_committee_poseidon = U256::from_little_endian(&sync_committee_poseidon.to_bytes());

let mut pk_vector: Vector<Vector<u8, 48>, { Spec::SYNC_COMMITTEE_SIZE }> = args
.pubkeys_compressed
Expand All @@ -84,17 +93,14 @@ where

RotateInput {
sync_committee_ssz,
sync_committee_poseidon: poseidon_commitment_le,
sync_committee_poseidon,
..Default::default()
}
}
}

// pub fn poseidon_committee_commitment_from_compressed(pubkeys_compressed: &[Vec<u8>]) -> [u8; 32] {
// let pubkeys_x = pubkeys_compressed.iter().cloned().map(|mut bytes| {
// bytes[0] &= 0b00011111;
// bls12_381::Fq::from_bytes_be(&bytes.try_into().unwrap())
// .expect("bad bls12_381::Fq encoding")
// });
// let poseidon_commitment = fq_array_poseidon_native::<Fr>(pubkeys_x, LIMB_BITS).unwrap();
// poseidon_commitment.to_bytes()
// }
impl RotateInput {
pub fn set_accumulator(&mut self, accumulator: [U256; 12]) {
self.accumulator = accumulator;
}
}
4 changes: 2 additions & 2 deletions contracts/script/DeploySpectre.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ pragma solidity ^0.8.0;
import "forge-std/Script.sol";

import {Spectre} from "../src/Spectre.sol";
import {Verifier as SyncStepVerifier} from "../snark-verifiers/sync_step.sol";
import {Verifier as CommitteeUpdateVerifier} from "../snark-verifiers/committee_update_verifier.sol";
import {Verifier as SyncStepVerifier} from "../snark-verifiers/sync_step.sol";

contract DeploySpectre is Script {

function run() external {
uint256 deployerPrivateKey = vm.envUint("DEPLOYER_PRIVATE_KEY");
uint256 initialSyncPeriod = vm.envUint("INITIAL_SYNC_PERIOD");
bytes32 initialCommitteePoseidon = vm.envBytes32("INITIAL_COMMITTEE_POSEIDON");
uint256 initialCommitteePoseidon = vm.envUint("INITIAL_COMMITTEE_POSEIDON");
uint256 slotsPerPeriod = vm.envUint("SLOTS_PER_PERIOD");

vm.startBroadcast(deployerPrivateKey);
Expand Down
4 changes: 2 additions & 2 deletions contracts/script/DeploySpectreTestnet.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ pragma solidity ^0.8.0;
import "forge-std/Script.sol";

import {Spectre} from "../src/Spectre.sol";
import {Verifier as SyncStepVerifier} from "../snark-verifiers/sync_step.sol";
import {Verifier as CommitteeUpdateVerifier} from "../snark-verifiers/committee_update_verifier.sol";
import {Verifier as SyncStepVerifier} from "../snark-verifiers/sync_step.sol";

contract DeploySpectre is Script {

function run() external {
uint256 deployerPrivateKey = vm.envUint("DEPLOYER_PRIVATE_KEY");
uint256 initialSyncPeriod = vm.envUint("INITIAL_SYNC_PERIOD");
bytes32 initialCommitteePoseidon = vm.envBytes32("INITIAL_COMMITTEE_POSEIDON");
uint256 initialCommitteePoseidon = vm.envUint("INITIAL_COMMITTEE_POSEIDON");
uint256 slotsPerPeriod = vm.envUint("SLOTS_PER_PERIOD");

vm.startBroadcast(deployerPrivateKey);
Expand Down
2 changes: 1 addition & 1 deletion contracts/snark-verifiers/committee_update_verifier.sol

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions contracts/snark-verifiers/sync_step.sol

Large diffs are not rendered by default.

Loading

0 comments on commit 3e39300

Please sign in to comment.