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

Add compressed sync step RPC & contracts #46

Merged
merged 32 commits into from
Dec 14, 2023
Merged
Show file tree
Hide file tree
Changes from 31 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
d492efd
use max lookup bits for circuit degree
nulltea Dec 10, 2023
78a809c
remove y coord check
nulltea Dec 10, 2023
433dcc1
tweak poseidon
nulltea Dec 10, 2023
4db48e8
half number of poseidon hash inputs
nulltea Dec 10, 2023
534ce0c
add step aggregation
nulltea Dec 10, 2023
e093d12
remove committee poseidon from pi commit in contracts
nulltea Dec 11, 2023
f383f50
tweak circuit accordingly
nulltea Dec 11, 2023
b573ac8
re-gen step verifier sol
nulltea Dec 11, 2023
af76ee0
update tests
nulltea Dec 11, 2023
810ef06
Merge branch 'main' into timoftime/contracts_optimizations
nulltea Dec 11, 2023
0037389
Merge branch 'main' into timoftime/contracts_optimizations
nulltea Dec 11, 2023
4f560d9
post merge fixes
nulltea Dec 11, 2023
b79f9f6
Merge branch 'main' into timoftime/circuit_optimizations
nulltea Dec 11, 2023
d948870
cargo fix
nulltea Dec 11, 2023
ac94482
Merge branch 'main' into timoftime/circuit_optimizations
nulltea Dec 11, 2023
19a9ae5
cargo fix + fmt
nulltea Dec 11, 2023
a25cbfa
Merge branch 'main' into timoftime/contracts_optimizations
nulltea Dec 11, 2023
b3edbd5
cargo fix + fmt
nulltea Dec 11, 2023
61c94f5
Merge branch 'timoftime/circuit_optimizations' into timoftime/contrac…
nulltea Dec 11, 2023
05ff10d
Add new cli
nulltea Dec 11, 2023
7f20aaa
regenerate committee update contract
nulltea Dec 11, 2023
054d4cd
add new rpc
nulltea Dec 11, 2023
3e39300
support compressed step verifier in contracts
nulltea Dec 11, 2023
f80609f
Merge branch 'timoftime/contracts_optimizations' into timoftime/compr…
nulltea Dec 11, 2023
2d24669
Merge branch 'main' into timoftime/contracts_optimizations
nulltea Dec 12, 2023
60ab00b
Merge branch 'timoftime/contracts_optimizations' into timoftime/compr…
nulltea Dec 12, 2023
baf8cb8
gen verifier contracts
nulltea Dec 12, 2023
6fd158a
update deployment scripts
nulltea Dec 13, 2023
a1aabeb
Merge branch 'main' into timoftime/compressed_sync_step
nulltea Dec 13, 2023
123bc92
cargo fix
nulltea Dec 13, 2023
9e6ebb6
remove sync_step.sol
nulltea Dec 13, 2023
b60b6b2
refactor RPC
nulltea Dec 14, 2023
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
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()
}
34 changes: 7 additions & 27 deletions contract-tests/tests/rotation_input_encoding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ where
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
.iter()
Expand All @@ -55,6 +54,8 @@ where
RotateInput {
sync_committee_ssz,
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 @@ -63,35 +64,16 @@ 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>::get_instances(&witness, LIMB_BITS);
Expand All @@ -107,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
13 changes: 6 additions & 7 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 Down Expand Up @@ -52,7 +51,7 @@ 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?;

Expand Down Expand Up @@ -82,7 +81,7 @@ async fn deploy_spectre_mock_verifiers<M: Middleware + 'static>(
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
19 changes: 8 additions & 11 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,26 +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>::get_instances(&witness, LIMB_BITS);
let poseidon_commitment =
poseidon_committee_commitment_from_uncompressed(&witness.pubkeys_uncompressed);

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

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

assert_eq!(
bn256::Fr::from_bytes(&result_bytes).unwrap(),
instance[0][0]
);
assert_eq!(poseidon_commitment, instance[0][1]);
assert_eq!(result_decoded[12], instance[0][0]); // public input commitment
assert_eq!(result_decoded[13], instance[0][1]); // committee poseidon

Ok(())
}
20 changes: 17 additions & 3 deletions contracts/rust-abi/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ abigen!(
Spectre,
"./out/Spectre.sol/Spectre.json";
StepVerifier,
"./out/sync_step.sol/Verifier.json";
"./out/sync_step_verifier.sol/Verifier.json";
CommitteeUpdateVerifier,
"./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,10 +51,17 @@ 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
Expand Down Expand Up @@ -85,6 +92,13 @@ where
RotateInput {
sync_committee_ssz,
sync_committee_poseidon,
..Default::default()
}
}
}

impl RotateInput {
pub fn set_accumulator(&mut self, accumulator: [U256; 12]) {
self.accumulator = accumulator;
}
}
2 changes: 1 addition & 1 deletion contracts/script/DeploySpectre.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import "forge-std/Script.sol";

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

contract DeploySpectre is Script {

Expand Down
2 changes: 1 addition & 1 deletion contracts/script/DeploySpectreTestnet.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import "forge-std/Script.sol";

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

contract DeploySpectre is Script {

Expand Down
4 changes: 2 additions & 2 deletions contracts/script/deploy_testnet.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/bin/sh
cd $(git rev-parse --show-toplevel)
source .env
LOCAL_RPC_URL="http://localhost:8545"
SEPOLIA_RPC_URL="https://rpc.sepolia.org/"

forge script script/DeploySpectreTestnet.s.sol:DeploySpectre --private-key $ANVIL_PRIVATE_KEY --rpc-url $LOCAL_RPC_URL --broadcast -vvvv
forge script script/DeploySpectreTestnet.s.sol:DeploySpectre --private-key $DEPLOYER_PRIVATE_KEY --rpc-url $SEPOLIA_RPC_URL --broadcast -vvvv
23 changes: 0 additions & 23 deletions contracts/snark-verifiers/sync_step.sol

This file was deleted.

23 changes: 23 additions & 0 deletions contracts/snark-verifiers/sync_step_verifier.sol

Large diffs are not rendered by default.

13 changes: 7 additions & 6 deletions contracts/src/RotateLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ library RotateLib {
struct RotateInput {
bytes32 syncCommitteeSSZ;
uint256 syncCommitteePoseidon;
uint256[12] accumulator;
}

/**
Expand All @@ -16,24 +17,24 @@ library RotateLib {
* @param args The arguments for the sync step
* @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 finalizedHeaderRoot) internal pure returns (uint256[77] memory) {
uint256[77] memory inputs;

for (uint256 i = 0; i < accumulator.length; i++) {
inputs[i] = accumulator[i];
for (uint256 i = 0; i < args.accumulator.length; i++) {
inputs[i] = args.accumulator[i];
}

inputs[accumulator.length] = args.syncCommitteePoseidon;
inputs[args.accumulator.length] = args.syncCommitteePoseidon;

uint256 syncCommitteeSSZNumeric = uint256(args.syncCommitteeSSZ);
for (uint256 i = 0; i < 32; i++) {
inputs[accumulator.length + 32 - i] = syncCommitteeSSZNumeric % 2 ** 8;
inputs[args.accumulator.length + 32 - i] = syncCommitteeSSZNumeric % 2 ** 8;
syncCommitteeSSZNumeric = syncCommitteeSSZNumeric / 2 ** 8;
}

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

Expand Down
Loading
Loading