Skip to content

Commit

Permalink
Add missing accumulator inputs to Spectre.sol (#40)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>
Co-authored-by: ec2 <[email protected]>
  • Loading branch information
3 people authored Dec 11, 2023
1 parent 3cf56c7 commit f1cb82f
Show file tree
Hide file tree
Showing 10 changed files with 67 additions and 31 deletions.
34 changes: 26 additions & 8 deletions contract-tests/tests/rotation_input_encoding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<Minimal, bn256::Fr>::instance(&witness, LIMB_BITS);
let finalized_block_root = witness
.finalized_header
Expand All @@ -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::<Vec<_>>(), 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<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();

assert_eq!(result_decoded.len(), instance[0].len());
assert_eq!(vec![result_decoded], instance);
Ok(())
.collect()
}
14 changes: 9 additions & 5 deletions contracts/src/RotateLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
4 changes: 2 additions & 2 deletions contracts/src/Spectre.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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");
Expand Down
2 changes: 1 addition & 1 deletion contracts/src/interfaces/CommitteeUpdateVerifier.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
2 changes: 1 addition & 1 deletion contracts/src/mocks/CommitteeUpdateMockVerifier.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
6 changes: 3 additions & 3 deletions contracts/test/RotateExternal.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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];
}
Expand Down
1 change: 0 additions & 1 deletion lightclient-circuits/src/gadget/crypto/sha256_wide/gate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,6 @@ impl<F: Field> VirtualRegionManager<F> for ShaBitGateManager<F> {
type Config = Sha256CircuitConfig<F>;

fn assign_raw(&self, config: &Self::Config, region: &mut Region<F>) {
// config.annotate_columns_in_region(region);
let mut copy_manager = self.copy_manager.lock().unwrap();

config
Expand Down
2 changes: 1 addition & 1 deletion lightclient-circuits/src/util/circuit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -295,5 +295,5 @@ where
P: AsRef<Path>,
{
read_pk::<C>(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()))
}
26 changes: 17 additions & 9 deletions prover/src/rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,10 @@ use url::Url;
pub type JsonRpcServerState = Arc<JsonRpcServer<JsonRpcMapRouter>>;

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,
};
Expand Down Expand Up @@ -77,7 +78,7 @@ fn gen_evm_proof<C: AppCircuit>(

pub(crate) async fn gen_evm_proof_rotation_circuit_handler(
Params(params): Params<GenProofRotationParams>,
) -> Result<EvmProofResult, JsonRpcError> {
) -> Result<AggregatedEvmProofResult, JsonRpcError> {
let GenProofRotationParams { spec, beacon_api } = params;

// TODO: use config/build paths from CLI flags
Expand Down Expand Up @@ -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<GenProofRotationWithWitnessParams>,
) -> Result<EvmProofResult, JsonRpcError> {
) -> Result<AggregatedEvmProofResult, JsonRpcError> {
let GenProofRotationWithWitnessParams {
spec,
light_client_update,
Expand Down Expand Up @@ -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,
})
}
Expand Down
7 changes: 7 additions & 0 deletions prover/src/rpc_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,13 @@ pub struct EvmProofResult {
pub public_inputs: Vec<U256>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AggregatedEvmProofResult {
pub proof: Vec<u8>,
pub accumulator: [U256; 12],
pub public_inputs: Vec<U256>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SyncCommitteePoseidonParams {
pub pubkeys: Vec<Vec<u8>>,
Expand Down

0 comments on commit f1cb82f

Please sign in to comment.