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

Step circuit instance encoding solidity (with tests) #21

Merged
merged 18 commits into from
Oct 10, 2023
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
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
6 changes: 6 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[submodule "contracts/lib/forge-std"]
path = contracts/lib/forge-std
url = https://github.com/foundry-rs/forge-std
[submodule "contracts/lib/telepathy-contracts"]
path = contracts/lib/telepathy-contracts
url = https://github.com/succinctlabs/telepathy-contracts
3 changes: 0 additions & 3 deletions contracts/.gitmodules

This file was deleted.

2 changes: 1 addition & 1 deletion contracts/foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ ffi = true
libs = ['lib']
optimizer = true
optimizer_runs = 1_000_000_000
solc = "0.8.19"
solc = "0.8.16"
fs_permissions = [{ access = "read", path = "./test/data/"}]

[profile.default.optimizer_details]
Expand Down
1 change: 1 addition & 0 deletions contracts/lib/telepathy-contracts
Submodule telepathy-contracts added at 0f3c68
3 changes: 2 additions & 1 deletion contracts/remappings.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
ds-test/=lib/forge-std/lib/ds-test/src/
forge-std/=lib/forge-std/src/
forge-std/=lib/forge-std/src/
telepathy-libs/=lib/telepathy-contracts/src/libraries/
2 changes: 1 addition & 1 deletion contracts/script/SpectreDeployLocal.s.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.19;
pragma solidity 0.8.16;

import "forge-std/Script.sol";
import "forge-std/safeconsole.sol";
Expand Down
2 changes: 1 addition & 1 deletion contracts/snark-verifiers/committee_update_aggregated.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
pragma solidity 0.8.16;

contract Verifier {

Expand Down
2 changes: 1 addition & 1 deletion contracts/snark-verifiers/sync_step.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
pragma solidity 0.8.16;
willemolding marked this conversation as resolved.
Show resolved Hide resolved

contract Verifier {

Expand Down
7 changes: 5 additions & 2 deletions contracts/src/Spectre.sol
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
pragma solidity 0.8.16;

import { SyncStepLib } from "./SyncStepLib.sol";

contract Spectre {

using SyncStepLib for SyncStepLib.SyncStepInput;

address public verifierContract;

constructor(address _verifierContract) {
Expand Down
43 changes: 43 additions & 0 deletions contracts/src/SyncStepLib.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.16;

import {SSZ} from "telepathy-libs/SimpleSerialize.sol";
import "forge-std/console.sol";


library SyncStepLib {
struct SyncStepInput {
uint64 attestedSlot;
uint64 finalizedSlot;
uint64 participation;
bytes32 finalizedHeaderRoot;
bytes32 executionPayloadRoot;
}

function toLittleEndian64(uint64 v) internal pure returns (bytes8) {
v = ((v & 0xFF00FF00FF00FF00) >> 8) | ((v & 0x00FF00FF00FF00FF) << 8);
v = ((v & 0xFFFF0000FFFF0000) >> 16) | ((v & 0x0000FFFF0000FFFF) << 16);
v = ((v & 0xFFFFFFFF00000000) >> 32) | ((v & 0x00000000FFFFFFFF) << 32);
return bytes8(v);
}

/**
* @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
* @return The public input commitment that can be sent to the verifier contract.
*/
function toInputCommitment(SyncStepInput memory args, bytes32 keysPoseidonCommitment) internal pure returns (uint256) {
bytes32 h = sha256(abi.encodePacked(
toLittleEndian64(args.attestedSlot),
toLittleEndian64(args.finalizedSlot),
toLittleEndian64(args.participation),
args.finalizedHeaderRoot,
args.executionPayloadRoot,
keysPoseidonCommitment
));
uint256 commitment = uint256(SSZ.toLittleEndian(uint256(h)));
return commitment & ((uint256(1) << 253) - 1); // truncated to 253 bits
}
}
2 changes: 1 addition & 1 deletion contracts/test/SpectreSyncStep.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.19;
pragma solidity 0.8.16;

import "forge-std/Script.sol";
import "forge-std/safeconsole.sol";
Expand Down
17 changes: 17 additions & 0 deletions contracts/test/SyncStepExternal.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.16;

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

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

function toInputCommitment(SyncStepLib.SyncStepInput calldata args, bytes32 keysPoseidonCommitment) public pure returns (uint256) {
return args.toInputCommitment(keysPoseidonCommitment);
}
}
3 changes: 3 additions & 0 deletions lightclient-circuits/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ rstest = "0.18.2"
test-utils = { git = "ssh://[email protected]/sygmaprotocol/Zipline.git", rev = "27e8a01" }
ethereum-consensus-types = { git = "ssh://[email protected]/sygmaprotocol/Zipline.git", rev = "27e8a01" }
light-client-verifier = { git = "ssh://[email protected]/sygmaprotocol/Zipline.git", rev = "27e8a01" }
ethers = "2.0.10"
tokio = { version = "1.32.0", features = ["rt", "macros"] }
anyhow = "1.0.75"

[features]
default = []
2 changes: 1 addition & 1 deletion lightclient-circuits/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ pub mod committee_update_circuit;
pub mod sync_step_circuit;

pub mod builder;
mod poseidon;
pub mod poseidon;
mod ssz_merkle;

pub use halo2_base::gates::builder::FlexGateConfigParams;
1 change: 0 additions & 1 deletion lightclient-circuits/src/sync_step_circuit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,6 @@ impl<S: Spec, F: Field> SyncStepCircuit<S, F> {
false,
)?
.output_bytes;

let pi_commit = truncate_sha256_into_single_elem(thread_pool.main(), range, pi_hash_bytes);

Ok(vec![pi_commit])
Expand Down
108 changes: 108 additions & 0 deletions lightclient-circuits/tests/step.rs
Original file line number Diff line number Diff line change
Expand Up @@ -507,3 +507,111 @@ fn test_eth2_spec_evm_verify(
println!("deployment_code size: {}", deployment_code.len());
snark_verifier_sdk::evm::evm_verify(deployment_code, instances, proof);
}

mod solidity_tests {
use super::*;
use ethers::{
contract::abigen,
core::utils::{Anvil, AnvilInstance},
middleware::SignerMiddleware,
providers::{Http, Provider},
signers::{LocalWallet, Signer},
};
use halo2_base::safe_types::ScalarField;
use halo2curves::group::UncompressedEncoding;
use lightclient_circuits::poseidon::fq_array_poseidon_native;
use std::sync::Arc;

/// Ensure that the instance encoding implemented in Solidity matches exactly the instance encoding expected by the circuit
#[rstest]
#[tokio::test]
async fn test_instance_commitment_evm_equivalence(
#[files("../consensus-spec-tests/tests/minimal/capella/light_client/sync/pyspec_tests/**")]
#[exclude("deneb*")]
path: PathBuf,
) -> anyhow::Result<()> {
let (witness, _) = read_test_files_and_gen_witness(path);
let instance = SyncStepCircuit::<Minimal, bn256::Fr>::instance_commitment(&witness);
let poseidon_commitment_le = extract_poseidon_committee_commitment(&witness)?;

let anvil_instance = Anvil::new().spawn();
let ethclient: Arc<SignerMiddleware<Provider<Http>, _>> = make_client(&anvil_instance);
let contract = SyncStepExternal::deploy(ethclient, ())?.send().await?;

let result = contract
.to_input_commitment(SyncStepInput::from(witness), poseidon_commitment_le)
.call()
.await?;
let mut result_bytes = [0_u8; 32];
result.to_little_endian(&mut result_bytes);

assert_eq!(bn256::Fr::from_bytes(&result_bytes).unwrap(), instance);
Ok(())
}

abigen!(
SyncStepExternal,
"../contracts/out/SyncStepExternal.sol/SyncStepExternal.json"
);

// SyncStepInput type produced by abigen macro matches the solidity struct type
impl<Spec: eth_types::Spec> From<SyncStepArgs<Spec>> for SyncStepInput {
fn from(args: SyncStepArgs<Spec>) -> Self {
let participation = args
.pariticipation_bits
.iter()
.map(|v| *v as u64)
.sum::<u64>();

let finalized_header_root: [u8; 32] = args
.finalized_header
.clone()
.hash_tree_root()
.unwrap()
.as_bytes()
.try_into()
.unwrap();

let execution_payload_root: [u8; 32] = args.execution_payload_root.try_into().unwrap();

SyncStepInput {
attested_slot: args.attested_header.slot,
finalized_slot: args.finalized_header.slot,
participation: participation,
finalized_header_root,
execution_payload_root,
}
}
}

fn extract_poseidon_committee_commitment<Spec: eth_types::Spec>(
witness: &SyncStepArgs<Spec>,
) -> anyhow::Result<[u8; 32]> {
let pubkey_affines = witness
.pubkeys_uncompressed
.iter()
.cloned()
.map(|bytes| {
halo2curves::bls12_381::G1Affine::from_uncompressed_unchecked(
&bytes.as_slice().try_into().unwrap(),
)
.unwrap()
})
.collect_vec();
let poseidon_commitment =
fq_array_poseidon_native::<bn256::Fr>(pubkey_affines.iter().map(|p| p.x)).unwrap();
Ok(poseidon_commitment.to_bytes_le().try_into().unwrap())
}

/// Return a fresh ethereum chain+client to test against
fn make_client(anvil: &AnvilInstance) -> Arc<SignerMiddleware<Provider<Http>, LocalWallet>> {
let provider = Provider::<Http>::try_from(anvil.endpoint())
.unwrap()
.interval(std::time::Duration::from_millis(10u64));
let wallet: LocalWallet = anvil.keys()[0].clone().into();

let client: SignerMiddleware<Provider<Http>, _> =
SignerMiddleware::new(provider, wallet.with_chain_id(anvil.chain_id()));
Arc::new(client)
}
}
Loading