From f1cb82fd0598b1cf3bebc05c4aa2be935d6e725f Mon Sep 17 00:00:00 2001 From: timofey Date: Mon, 11 Dec 2023 15:22:19 +0100 Subject: [PATCH] Add missing accumulator inputs to Spectre.sol (#40) * use bls accumulator instances in contract * add accumulator into rpc response * add accumulator prepending to rotation input encoding tests * compiles --------- Co-authored-by: Willem Olding Co-authored-by: ec2 --- .../tests/rotation_input_encoding.rs | 34 ++++++++++++++----- contracts/src/RotateLib.sol | 14 +++++--- contracts/src/Spectre.sol | 4 +-- .../interfaces/CommitteeUpdateVerifier.sol | 2 +- .../src/mocks/CommitteeUpdateMockVerifier.sol | 2 +- contracts/test/RotateExternal.sol | 6 ++-- .../src/gadget/crypto/sha256_wide/gate.rs | 1 - lightclient-circuits/src/util/circuit.rs | 2 +- prover/src/rpc.rs | 26 +++++++++----- prover/src/rpc_api.rs | 7 ++++ 10 files changed, 67 insertions(+), 31 deletions(-) diff --git a/contract-tests/tests/rotation_input_encoding.rs b/contract-tests/tests/rotation_input_encoding.rs index 14543727..e0e9296e 100644 --- a/contract-tests/tests/rotation_input_encoding.rs +++ b/contract-tests/tests/rotation_input_encoding.rs @@ -65,6 +65,8 @@ async fn test_rotate_public_input_evm_equivalence( path: PathBuf, ) -> anyhow::Result<()> { 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::::instance(&witness, LIMB_BITS); let finalized_block_root = witness .finalized_header @@ -79,21 +81,37 @@ async fn test_rotate_public_input_evm_equivalence( let contract = RotateExternal::deploy(ethclient, ())?.send().await?; let result = contract - .to_public_inputs(RotateInput::from(witness), finalized_block_root) + .to_public_inputs( + RotateInput::from(witness), + finalized_block_root, + solidity_encode_fr_array(&accumulator), + ) .call() .await?; - // convert each of the returned values to a field element - let result_decoded: Vec<_> = result + let result_decoded = decode_solidity_u256_array(&result); + // 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::>(), expected); + Ok(()) +} + +/// 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(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 { + 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(); - - assert_eq!(result_decoded.len(), instance[0].len()); - assert_eq!(vec![result_decoded], instance); - Ok(()) + .collect() } diff --git a/contracts/src/RotateLib.sol b/contracts/src/RotateLib.sol index 5c8fb0cd..e3582e49 100644 --- a/contracts/src/RotateLib.sol +++ b/contracts/src/RotateLib.sol @@ -16,20 +16,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) internal pure returns (uint256[65] memory) { - uint256[65] memory inputs; + function toPublicInputs(RotateInput memory args, bytes32 finalizedHeaderRoot, uint256[12] memory accumulator) internal pure returns (uint256[77] memory) { + uint256[77] memory inputs; - inputs[0] = uint256(EndianConversions.toLittleEndian(uint256(args.syncCommitteePoseidon))); + for (uint256 i = 0; i < accumulator.length; i++) { + inputs[i] = accumulator[i]; + } + + inputs[accumulator.length] = uint256(EndianConversions.toLittleEndian(uint256(args.syncCommitteePoseidon))); uint256 syncCommitteeSSZNumeric = uint256(args.syncCommitteeSSZ); for (uint256 i = 0; i < 32; i++) { - inputs[32 - i] = syncCommitteeSSZNumeric % 2 ** 8; + inputs[accumulator.length + 32 - i] = syncCommitteeSSZNumeric % 2 ** 8; syncCommitteeSSZNumeric = syncCommitteeSSZNumeric / 2 ** 8; } uint256 finalizedHeaderRootNumeric = uint256(finalizedHeaderRoot); for (uint256 j = 0; j < 32; j++) { - inputs[64 - j] = finalizedHeaderRootNumeric % 2 ** 8; + inputs[accumulator.length + 64 - j] = finalizedHeaderRootNumeric % 2 ** 8; finalizedHeaderRootNumeric = finalizedHeaderRootNumeric / 2 ** 8; } diff --git a/contracts/src/Spectre.sol b/contracts/src/Spectre.sol index 61b2f8ab..be2b6fd3 100644 --- a/contracts/src/Spectre.sol +++ b/contracts/src/Spectre.sol @@ -68,7 +68,7 @@ contract Spectre { /// @param rotateProof The proof for the rotation /// @param stepInput The input to the sync step. /// @param stepProof The proof for the sync step - function rotate(RotateLib.RotateInput calldata rotateInput, bytes calldata rotateProof, SyncStepLib.SyncStepInput calldata stepInput, bytes calldata stepProof) external { + function rotate(RotateLib.RotateInput calldata rotateInput, bytes calldata rotateProof, SyncStepLib.SyncStepInput calldata stepInput, bytes calldata stepProof, uint256[12] memory accumulator) external { // *step phase* // This allows trusting that the current sync committee has signed off on the finalizedHeaderRoot which is used as the base of the SSZ proof // that checks the new committee is in the beacon state 'next_sync_committee' field. It also allows trusting the finalizedSlot which is @@ -85,7 +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; - uint256[65] memory verifierInput = rotateInput.toPublicInputs(stepInput.finalizedHeaderRoot); + uint256[77] memory verifierInput = rotateInput.toPublicInputs(stepInput.finalizedHeaderRoot, accumulator); bool rotateSuccess = committeeUpdateVerifier.verify(verifierInput, rotateProof); if (!rotateSuccess) { revert("Rotation proof verification failed"); diff --git a/contracts/src/interfaces/CommitteeUpdateVerifier.sol b/contracts/src/interfaces/CommitteeUpdateVerifier.sol index f5c96262..981e8a6b 100644 --- a/contracts/src/interfaces/CommitteeUpdateVerifier.sol +++ b/contracts/src/interfaces/CommitteeUpdateVerifier.sol @@ -2,5 +2,5 @@ pragma solidity ^0.8.0; interface CommitteeUpdateVerifier { - function verify(uint256[65] calldata input, bytes calldata proof) external returns (bool); + function verify(uint256[77] calldata input, bytes calldata proof) external returns (bool); } diff --git a/contracts/src/mocks/CommitteeUpdateMockVerifier.sol b/contracts/src/mocks/CommitteeUpdateMockVerifier.sol index d3c9ab6d..0fdbe71a 100644 --- a/contracts/src/mocks/CommitteeUpdateMockVerifier.sol +++ b/contracts/src/mocks/CommitteeUpdateMockVerifier.sol @@ -5,7 +5,7 @@ pragma solidity ^0.8.0; import { CommitteeUpdateVerifier } from "../interfaces/CommitteeUpdateVerifier.sol"; contract CommitteeUpdateMockVerifier is CommitteeUpdateVerifier { - function verify(uint256[65] calldata _input, bytes calldata _proof) external override returns (bool) { + function verify(uint256[77] calldata _input, bytes calldata _proof) external override returns (bool) { return true; } } diff --git a/contracts/test/RotateExternal.sol b/contracts/test/RotateExternal.sol index 07d601b5..0e6ed292 100644 --- a/contracts/test/RotateExternal.sol +++ b/contracts/test/RotateExternal.sol @@ -11,10 +11,10 @@ import { RotateLib } from "../src/RotateLib.sol"; contract RotateExternal { using RotateLib for RotateLib.RotateInput; - function toPublicInputs(RotateLib.RotateInput calldata args, bytes32 finalizedHeaderRoot) public pure returns (uint256[] memory) { - uint256[65] memory commitment = args.toPublicInputs(finalizedHeaderRoot); + 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); // 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[](65); + uint256[] memory result = new uint256[](77); for (uint256 i = 0; i < commitment.length; i++) { result[i] = commitment[i]; } diff --git a/lightclient-circuits/src/gadget/crypto/sha256_wide/gate.rs b/lightclient-circuits/src/gadget/crypto/sha256_wide/gate.rs index 047145ad..c5f52093 100644 --- a/lightclient-circuits/src/gadget/crypto/sha256_wide/gate.rs +++ b/lightclient-circuits/src/gadget/crypto/sha256_wide/gate.rs @@ -71,7 +71,6 @@ impl VirtualRegionManager for ShaBitGateManager { type Config = Sha256CircuitConfig; fn assign_raw(&self, config: &Self::Config, region: &mut Region) { - // config.annotate_columns_in_region(region); let mut copy_manager = self.copy_manager.lock().unwrap(); config diff --git a/lightclient-circuits/src/util/circuit.rs b/lightclient-circuits/src/util/circuit.rs index 6fb2ff4b..76c8d37f 100644 --- a/lightclient-circuits/src/util/circuit.rs +++ b/lightclient-circuits/src/util/circuit.rs @@ -295,5 +295,5 @@ where P: AsRef, { read_pk::(fname.as_ref(), c.params()) - .expect(format!("proving key: {:?} should exist", fname.as_ref().to_str()).as_str()) + .unwrap_or_else(|_| panic!("proving key: {:?} should exist", fname.as_ref().to_str())) } diff --git a/prover/src/rpc.rs b/prover/src/rpc.rs index d5d92548..403db4c5 100644 --- a/prover/src/rpc.rs +++ b/prover/src/rpc.rs @@ -22,9 +22,10 @@ use url::Url; pub type JsonRpcServerState = Arc>; use crate::rpc_api::{ - EvmProofResult, GenProofRotationParams, GenProofRotationWithWitnessParams, GenProofStepParams, - GenProofStepWithWitnessParams, SyncCommitteePoseidonParams, SyncCommitteePoseidonResult, - EVM_PROOF_ROTATION_CIRCUIT, EVM_PROOF_ROTATION_CIRCUIT_WITH_WITNESS, EVM_PROOF_STEP_CIRCUIT, + AggregatedEvmProofResult, EvmProofResult, GenProofRotationParams, + GenProofRotationWithWitnessParams, GenProofStepParams, GenProofStepWithWitnessParams, + SyncCommitteePoseidonParams, SyncCommitteePoseidonResult, EVM_PROOF_ROTATION_CIRCUIT, + EVM_PROOF_ROTATION_CIRCUIT_WITH_WITNESS, EVM_PROOF_STEP_CIRCUIT, EVM_PROOF_STEP_CIRCUIT_WITH_WITNESS, SYNC_COMMITTEE_POSEIDON_COMPRESSED, SYNC_COMMITTEE_POSEIDON_UNCOMPRESSED, }; @@ -77,7 +78,7 @@ fn gen_evm_proof( pub(crate) async fn gen_evm_proof_rotation_circuit_handler( Params(params): Params, -) -> Result { +) -> Result { let GenProofRotationParams { spec, beacon_api } = params; // TODO: use config/build paths from CLI flags @@ -142,17 +143,20 @@ pub(crate) async fn gen_evm_proof_rotation_circuit_handler( let public_inputs = instances[0] .iter() .map(|pi| U256::from_little_endian(&pi.to_bytes())) - .collect(); + .collect_vec(); + let mut accumulator = [U256::zero(); 12]; + accumulator.clone_from_slice(&public_inputs[..12]); - Ok(EvmProofResult { + Ok(AggregatedEvmProofResult { proof, + accumulator, public_inputs, }) } pub(crate) async fn gen_evm_proof_rotation_circuit_with_witness_handler( Params(params): Params, -) -> Result { +) -> Result { let GenProofRotationWithWitnessParams { spec, light_client_update, @@ -221,9 +225,13 @@ pub(crate) async fn gen_evm_proof_rotation_circuit_with_witness_handler( let public_inputs = instances[0] .iter() .map(|pi| U256::from_little_endian(&pi.to_bytes())) - .collect(); - Ok(EvmProofResult { + .collect_vec(); + let mut accumulator = [U256::zero(); 12]; + accumulator.clone_from_slice(&public_inputs[..12]); + + Ok(AggregatedEvmProofResult { proof, + accumulator, public_inputs, }) } diff --git a/prover/src/rpc_api.rs b/prover/src/rpc_api.rs index 1f92264c..95c70a01 100644 --- a/prover/src/rpc_api.rs +++ b/prover/src/rpc_api.rs @@ -43,6 +43,13 @@ pub struct EvmProofResult { pub public_inputs: Vec, } +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct AggregatedEvmProofResult { + pub proof: Vec, + pub accumulator: [U256; 12], + pub public_inputs: Vec, +} + #[derive(Debug, Clone, Serialize, Deserialize)] pub struct SyncCommitteePoseidonParams { pub pubkeys: Vec>,