diff --git a/l1-contracts/src/core/libraries/ConstantsGen.sol b/l1-contracts/src/core/libraries/ConstantsGen.sol index 3d488ae07d9..df3eaa40a48 100644 --- a/l1-contracts/src/core/libraries/ConstantsGen.sol +++ b/l1-contracts/src/core/libraries/ConstantsGen.sol @@ -93,7 +93,8 @@ library Constants { uint256 internal constant BLOCK_ROOT_ROLLUP_INDEX = 22; uint256 internal constant BLOCK_MERGE_ROLLUP_INDEX = 23; uint256 internal constant ROOT_ROLLUP_INDEX = 24; - uint256 internal constant BLOCK_ROOT_ROLLUP_FINAL_INDEX = 25; + uint256 internal constant BLOCK_ROOT_ROLLUP_EMPTY_INDEX = 25; + uint256 internal constant BLOCK_ROOT_ROLLUP_FINAL_INDEX = 26; uint256 internal constant FUNCTION_SELECTOR_NUM_BYTES = 4; uint256 internal constant INITIALIZATION_SLOT_SEPARATOR = 1000000000; uint256 internal constant INITIAL_L2_BLOCK_NUM = 1; diff --git a/noir-projects/noir-protocol-circuits/Nargo.template.toml b/noir-projects/noir-protocol-circuits/Nargo.template.toml index 4aa888cb7e7..9db35a94c9e 100644 --- a/noir-projects/noir-protocol-circuits/Nargo.template.toml +++ b/noir-projects/noir-protocol-circuits/Nargo.template.toml @@ -34,6 +34,7 @@ members = [ "crates/rollup-base-simulated", "crates/rollup-block-merge", "crates/rollup-block-root", + "crates/rollup-block-root-empty", "crates/rollup-block-root-final", "crates/rollup-root", ] diff --git a/noir-projects/noir-protocol-circuits/crates/rollup-block-root-empty/Nargo.toml b/noir-projects/noir-protocol-circuits/crates/rollup-block-root-empty/Nargo.toml new file mode 100644 index 00000000000..bef9a433e84 --- /dev/null +++ b/noir-projects/noir-protocol-circuits/crates/rollup-block-root-empty/Nargo.toml @@ -0,0 +1,9 @@ +[package] +name = "rollup_block_root_empty" +type = "bin" +authors = [""] +compiler_version = ">=0.18.0" + +[dependencies] +rollup_lib = { path = "../rollup-lib" } +types = { path = "../types" } diff --git a/noir-projects/noir-protocol-circuits/crates/rollup-block-root-empty/src/main.nr b/noir-projects/noir-protocol-circuits/crates/rollup-block-root-empty/src/main.nr new file mode 100644 index 00000000000..e2f80bb0ad2 --- /dev/null +++ b/noir-projects/noir-protocol-circuits/crates/rollup-block-root-empty/src/main.nr @@ -0,0 +1,6 @@ +use rollup_lib::block_root::{EmptyBlockRootRollupInputs, BlockRootOrBlockMergePublicInputs}; + +#[recursive] +fn main(inputs: EmptyBlockRootRollupInputs) -> pub BlockRootOrBlockMergePublicInputs { + inputs.empty_block_root_rollup_circuit() +} diff --git a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/abis/block_root_or_block_merge_public_inputs.nr b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/abis/block_root_or_block_merge_public_inputs.nr index 7fede7a1c1b..707f674ae1c 100644 --- a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/abis/block_root_or_block_merge_public_inputs.nr +++ b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/abis/block_root_or_block_merge_public_inputs.nr @@ -49,6 +49,12 @@ pub struct BlockRootOrBlockMergePublicInputs { prover_id: Field, // TODO(#7346): Temporarily added prover_id while we verify block-root proofs on L1 } +impl BlockRootOrBlockMergePublicInputs { + fn is_padding(self) -> bool { + self.previous_archive == self.new_archive + } +} + impl Empty for BlockRootOrBlockMergePublicInputs { fn empty() -> Self { BlockRootOrBlockMergePublicInputs { diff --git a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/block_root/empty_block_root_rollup_inputs.nr b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/block_root/empty_block_root_rollup_inputs.nr new file mode 100644 index 00000000000..3ca5cb691a0 --- /dev/null +++ b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/block_root/empty_block_root_rollup_inputs.nr @@ -0,0 +1,31 @@ +use crate::abis::block_root_or_block_merge_public_inputs::BlockRootOrBlockMergePublicInputs; +use types::{abis::{append_only_tree_snapshot::AppendOnlyTreeSnapshot, global_variables::GlobalVariables}}; +use crate::abis::block_root_or_block_merge_public_inputs::FeeRecipient; + +pub struct EmptyBlockRootRollupInputs { + archive: AppendOnlyTreeSnapshot, + block_hash: Field, + global_variables: GlobalVariables, + out_hash: Field, + vk_tree_root: Field, + // TODO(#7346): Temporarily added prover_id while we verify block-root proofs on L1 + prover_id: Field, +} + +impl EmptyBlockRootRollupInputs { + pub fn empty_block_root_rollup_circuit(self) -> BlockRootOrBlockMergePublicInputs { + BlockRootOrBlockMergePublicInputs { + previous_archive: self.archive, + new_archive: self.archive, + previous_block_hash: self.block_hash, + end_block_hash: self.block_hash, + start_global_variables: self.global_variables, + end_global_variables: self.global_variables, + out_hash: self.out_hash, + fees: [FeeRecipient::empty(); 32], + vk_tree_root: self.vk_tree_root, + prover_id: self.prover_id + } + } +} + diff --git a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/block_root/mod.nr b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/block_root/mod.nr index 71538bcb1f7..2c66ed17d6a 100644 --- a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/block_root/mod.nr +++ b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/block_root/mod.nr @@ -1,7 +1,9 @@ mod block_root_rollup_inputs; +mod empty_block_root_rollup_inputs; // Re-exports pub use block_root_rollup_inputs::BlockRootRollupInputs; +pub use empty_block_root_rollup_inputs::EmptyBlockRootRollupInputs; pub use crate::abis::block_root_or_block_merge_public_inputs::BlockRootOrBlockMergePublicInputs; mod tests { diff --git a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/components.nr b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/components.nr index d787969abd2..85e3debb8d1 100644 --- a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/components.nr +++ b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/components.nr @@ -92,12 +92,22 @@ pub fn assert_prev_block_rollups_follow_on_from_each_other( assert( left.end_global_variables.version == right.start_global_variables.version, "input blocks have different chain version" ); - assert( - left.end_global_variables.block_number + 1 == right.start_global_variables.block_number, "input block numbers do not follow on from each other" - ); - assert( - left.end_global_variables.timestamp < right.start_global_variables.timestamp, "input block timestamps do not follow on from each other" - ); + + if right.is_padding() { + assert( + left.end_global_variables.block_number == right.start_global_variables.block_number, "input block numbers do not match" + ); + assert( + left.end_global_variables.timestamp == right.start_global_variables.timestamp, "input block timestamps do not match" + ); + } else { + assert( + left.end_global_variables.block_number + 1 == right.start_global_variables.block_number, "input block numbers do not follow on from each other" + ); + assert( + left.end_global_variables.timestamp < right.start_global_variables.timestamp, "input block timestamps do not follow on from each other" + ); + } } pub fn accumulate_fees(left: BaseOrMergeRollupPublicInputs, right: BaseOrMergeRollupPublicInputs) -> Field { @@ -134,12 +144,16 @@ pub fn compute_out_hash(previous_rollup_data: [PreviousRollupData; 2]) -> Field } // TODO(Miranda): combine fns? pub fn compute_blocks_out_hash(previous_rollup_data: [PreviousRollupBlockData; 2]) -> Field { - accumulate_sha256( - [ - previous_rollup_data[0].block_root_or_block_merge_public_inputs.out_hash, - previous_rollup_data[1].block_root_or_block_merge_public_inputs.out_hash - ] - ) + if previous_rollup_data[1].block_root_or_block_merge_public_inputs.is_padding() { + previous_rollup_data[0].block_root_or_block_merge_public_inputs.out_hash + } else { + accumulate_sha256( + [ + previous_rollup_data[0].block_root_or_block_merge_public_inputs.out_hash, + previous_rollup_data[1].block_root_or_block_merge_public_inputs.out_hash + ] + ) + } } pub fn compute_kernel_out_hash(l2_to_l1_msgs: [Field; MAX_L2_TO_L1_MSGS_PER_TX]) -> Field { diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/constants.nr b/noir-projects/noir-protocol-circuits/crates/types/src/constants.nr index f5cf45fbfc6..54901f6bf9c 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/constants.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/constants.nr @@ -119,7 +119,8 @@ global MERGE_ROLLUP_INDEX: u32 = 21; global BLOCK_ROOT_ROLLUP_INDEX: u32 = 22; global BLOCK_MERGE_ROLLUP_INDEX: u32 = 23; global ROOT_ROLLUP_INDEX: u32 = 24; -global BLOCK_ROOT_ROLLUP_FINAL_INDEX: u32 = 25; +global BLOCK_ROOT_ROLLUP_EMPTY_INDEX: u32 = 25; +global BLOCK_ROOT_ROLLUP_FINAL_INDEX: u32 = 26; // MISC CONSTANTS global FUNCTION_SELECTOR_NUM_BYTES: Field = 4; diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/tests/fixtures/vk_tree.nr b/noir-projects/noir-protocol-circuits/crates/types/src/tests/fixtures/vk_tree.nr index 75e24d3c28d..3d91c8ce182 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/tests/fixtures/vk_tree.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/tests/fixtures/vk_tree.nr @@ -5,7 +5,8 @@ use crate::constants::{ EMPTY_NESTED_INDEX, PRIVATE_KERNEL_EMPTY_INDEX, PUBLIC_KERNEL_INNER_INDEX, PUBLIC_KERNEL_MERGE_INDEX, PUBLIC_KERNEL_TAIL_INDEX, BASE_PARITY_INDEX, ROOT_PARITY_INDEX, BASE_ROLLUP_INDEX, MERGE_ROLLUP_INDEX, BLOCK_ROOT_ROLLUP_INDEX, BLOCK_MERGE_ROLLUP_INDEX, - ROOT_ROLLUP_INDEX, PRIVATE_KERNEL_RESET_TINY_INDEX, BLOCK_ROOT_ROLLUP_FINAL_INDEX + ROOT_ROLLUP_INDEX, PRIVATE_KERNEL_RESET_TINY_INDEX, BLOCK_ROOT_ROLLUP_EMPTY_INDEX, + BLOCK_ROOT_ROLLUP_FINAL_INDEX }; use crate::merkle_tree::merkle_tree::MerkleTree; @@ -41,7 +42,8 @@ pub fn get_vk_merkle_tree() -> MerkleTree { leaves[BLOCK_ROOT_ROLLUP_INDEX] = 22; leaves[BLOCK_MERGE_ROLLUP_INDEX] = 23; leaves[ROOT_ROLLUP_INDEX] = 24; - leaves[BLOCK_ROOT_ROLLUP_FINAL_INDEX] = 25; + leaves[BLOCK_ROOT_ROLLUP_EMPTY_INDEX] = 25; + leaves[BLOCK_ROOT_ROLLUP_FINAL_INDEX] = 26; MerkleTree::new(leaves) } diff --git a/yarn-project/bb-prover/src/prover/bb_prover.ts b/yarn-project/bb-prover/src/prover/bb_prover.ts index 5aff5612630..40c75b28912 100644 --- a/yarn-project/bb-prover/src/prover/bb_prover.ts +++ b/yarn-project/bb-prover/src/prover/bb_prover.ts @@ -16,6 +16,7 @@ import { type BlockMergeRollupInputs, type BlockRootOrBlockMergePublicInputs, type BlockRootRollupInputs, + type EmptyBlockRootRollupInputs, EmptyNestedCircuitInputs, EmptyNestedData, Fr, @@ -57,6 +58,7 @@ import { convertBlockMergeRollupOutputsFromWitnessMap, convertBlockRootRollupInputsToWitnessMap, convertBlockRootRollupOutputsFromWitnessMap, + convertEmptyBlockRootRollupInputsToWitnessMap, convertMergeRollupInputsToWitnessMap, convertMergeRollupOutputsFromWitnessMap, convertPrivateKernelEmptyInputsToWitnessMap, @@ -380,6 +382,29 @@ export class BBNativeRollupProver implements ServerCircuitProver { return makePublicInputsAndRecursiveProof(circuitOutput, proof, verificationKey); } + /** + * Simulates the empty block root rollup circuit from its inputs. + * @param input - Inputs to the circuit. + * @returns The public inputs as outputs of the simulation. + */ + public async getEmptyBlockRootRollupProof( + input: EmptyBlockRootRollupInputs, + ): Promise> { + const { circuitOutput, proof } = await this.createRecursiveProof( + input, + 'EmptyBlockRootRollupArtifact', + RECURSIVE_PROOF_LENGTH, + convertEmptyBlockRootRollupInputsToWitnessMap, + convertBlockRootRollupOutputsFromWitnessMap, + ); + + const verificationKey = await this.getVerificationKeyDataForCircuit('BlockRootRollupArtifact'); + + await this.verifyProof('BlockRootRollupArtifact', proof.binaryProof); + + return makePublicInputsAndRecursiveProof(circuitOutput, proof, verificationKey); + } + /** * Simulates the block root rollup circuit from its inputs. * Returns a non-recursive proof to verify on L1. diff --git a/yarn-project/bb-prover/src/stats.ts b/yarn-project/bb-prover/src/stats.ts index 92bd1b38e65..1e2bc0143a3 100644 --- a/yarn-project/bb-prover/src/stats.ts +++ b/yarn-project/bb-prover/src/stats.ts @@ -15,6 +15,8 @@ export function mapProtocolArtifactNameToCircuitName( return 'merge-rollup'; case 'BlockRootRollupArtifact': return 'block-root-rollup'; + case 'EmptyBlockRootRollupArtifact': + return 'empty-block-root-rollup'; case 'BlockMergeRollupArtifact': return 'block-merge-rollup'; case 'RootRollupArtifact': diff --git a/yarn-project/bb-prover/src/test/test_circuit_prover.ts b/yarn-project/bb-prover/src/test/test_circuit_prover.ts index 6ccfb948d92..8a56a11119e 100644 --- a/yarn-project/bb-prover/src/test/test_circuit_prover.ts +++ b/yarn-project/bb-prover/src/test/test_circuit_prover.ts @@ -13,6 +13,7 @@ import { type BlockMergeRollupInputs, type BlockRootOrBlockMergePublicInputs, type BlockRootRollupInputs, + type EmptyBlockRootRollupInputs, EmptyNestedData, type KernelCircuitPublicInputs, type MergeRollupInputs, @@ -52,6 +53,7 @@ import { convertBlockMergeRollupOutputsFromWitnessMap, convertBlockRootRollupInputsToWitnessMap, convertBlockRootRollupOutputsFromWitnessMap, + convertEmptyBlockRootRollupInputsToWitnessMap, convertMergeRollupInputsToWitnessMap, convertMergeRollupOutputsFromWitnessMap, convertPrivateKernelEmptyInputsToWitnessMap, @@ -347,6 +349,41 @@ export class TestCircuitProver implements ServerCircuitProver { ); } + /** + * Simulates the empty block root rollup circuit from its inputs. + * @param input - Inputs to the circuit. + * @returns The public inputs as outputs of the simulation. + */ + @trackSpan('TestCircuitProver.getEmptyBlockRootRollupProof') + public async getEmptyBlockRootRollupProof( + input: EmptyBlockRootRollupInputs, + ): Promise> { + const timer = new Timer(); + const witnessMap = convertEmptyBlockRootRollupInputsToWitnessMap(input); + + // use WASM here as it is faster for small circuits + const witness = await this.wasmSimulator.simulateCircuit( + witnessMap, + SimulatedServerCircuitArtifacts.EmptyBlockRootRollupArtifact, + ); + + const result = convertBlockRootRollupOutputsFromWitnessMap(witness); + + this.instrumentation.recordDuration('simulationDuration', 'empty-block-root-rollup', timer); + emitCircuitSimulationStats( + 'empty-block-root-rollup', + timer.ms(), + input.toBuffer().length, + result.toBuffer().length, + this.logger, + ); + return makePublicInputsAndRecursiveProof( + result, + makeEmptyRecursiveProof(NESTED_RECURSIVE_PROOF_LENGTH), + ProtocolCircuitVks['EmptyBlockRootRollupArtifact'], + ); + } + public getBlockRootRollupFinalProof( input: BlockRootRollupInputs, ): Promise> { diff --git a/yarn-project/circuit-types/src/interfaces/proving-job.ts b/yarn-project/circuit-types/src/interfaces/proving-job.ts index c805b4cb80e..bee165bc03e 100644 --- a/yarn-project/circuit-types/src/interfaces/proving-job.ts +++ b/yarn-project/circuit-types/src/interfaces/proving-job.ts @@ -7,6 +7,7 @@ import { type BlockMergeRollupInputs, type BlockRootOrBlockMergePublicInputs, type BlockRootRollupInputs, + type EmptyBlockRootRollupInputs, type KernelCircuitPublicInputs, type MergeRollupInputs, type NESTED_RECURSIVE_PROOF_LENGTH, @@ -75,6 +76,7 @@ export enum ProvingRequestType { BASE_ROLLUP, MERGE_ROLLUP, + EMPTY_BLOCK_ROOT_ROLLUP, BLOCK_ROOT_ROLLUP, BLOCK_ROOT_ROLLUP_FINAL, BLOCK_MERGE_ROLLUP, @@ -102,6 +104,8 @@ export function mapProvingRequestTypeToCircuitName(type: ProvingRequestType): Ci return 'base-rollup'; case ProvingRequestType.MERGE_ROLLUP: return 'merge-rollup'; + case ProvingRequestType.EMPTY_BLOCK_ROOT_ROLLUP: + return 'empty-block-root-rollup'; case ProvingRequestType.BLOCK_ROOT_ROLLUP: return 'block-root-rollup'; case ProvingRequestType.BLOCK_ROOT_ROLLUP_FINAL: @@ -164,6 +168,10 @@ export type ProvingRequest = type: ProvingRequestType.BLOCK_ROOT_ROLLUP; inputs: BlockRootRollupInputs; } + | { + type: ProvingRequestType.EMPTY_BLOCK_ROOT_ROLLUP; + inputs: EmptyBlockRootRollupInputs; + } | { type: ProvingRequestType.BLOCK_ROOT_ROLLUP_FINAL; inputs: BlockRootRollupInputs; @@ -195,6 +203,7 @@ export type ProvingRequestPublicInputs = { [ProvingRequestType.BASE_ROLLUP]: PublicInputsAndRecursiveProof; [ProvingRequestType.MERGE_ROLLUP]: PublicInputsAndRecursiveProof; + [ProvingRequestType.EMPTY_BLOCK_ROOT_ROLLUP]: PublicInputsAndRecursiveProof; [ProvingRequestType.BLOCK_ROOT_ROLLUP]: PublicInputsAndRecursiveProof; [ProvingRequestType.BLOCK_ROOT_ROLLUP_FINAL]: PublicInputsAndRecursiveProof; [ProvingRequestType.BLOCK_MERGE_ROLLUP]: PublicInputsAndRecursiveProof; diff --git a/yarn-project/circuit-types/src/interfaces/server_circuit_prover.ts b/yarn-project/circuit-types/src/interfaces/server_circuit_prover.ts index 741b05e25f5..32761bb9169 100644 --- a/yarn-project/circuit-types/src/interfaces/server_circuit_prover.ts +++ b/yarn-project/circuit-types/src/interfaces/server_circuit_prover.ts @@ -12,6 +12,7 @@ import { type BlockMergeRollupInputs, type BlockRootOrBlockMergePublicInputs, type BlockRootRollupInputs, + type EmptyBlockRootRollupInputs, type KernelCircuitPublicInputs, type MergeRollupInputs, type NESTED_RECURSIVE_PROOF_LENGTH, @@ -105,6 +106,16 @@ export interface ServerCircuitProver { epochNumber?: number, ): Promise>; + /** + * Creates a proof for the given input. + * @param input - Input to the circuit. + */ + getEmptyBlockRootRollupProof( + input: EmptyBlockRootRollupInputs, + signal?: AbortSignal, + epochNumber?: number, + ): Promise>; + /** * Creates a proof for the given input. * @param input - Input to the circuit. diff --git a/yarn-project/circuit-types/src/stats/stats.ts b/yarn-project/circuit-types/src/stats/stats.ts index 30dcb9f460a..5e1bc0568e7 100644 --- a/yarn-project/circuit-types/src/stats/stats.ts +++ b/yarn-project/circuit-types/src/stats/stats.ts @@ -77,6 +77,7 @@ export type CircuitName = | 'base-rollup' | 'merge-rollup' | 'block-root-rollup' + | 'empty-block-root-rollup' | 'block-root-rollup-final' | 'block-merge-rollup' | 'root-rollup' diff --git a/yarn-project/circuits.js/src/constants.gen.ts b/yarn-project/circuits.js/src/constants.gen.ts index 2737fa9c6c5..668c1839d03 100644 --- a/yarn-project/circuits.js/src/constants.gen.ts +++ b/yarn-project/circuits.js/src/constants.gen.ts @@ -79,7 +79,8 @@ export const MERGE_ROLLUP_INDEX = 21; export const BLOCK_ROOT_ROLLUP_INDEX = 22; export const BLOCK_MERGE_ROLLUP_INDEX = 23; export const ROOT_ROLLUP_INDEX = 24; -export const BLOCK_ROOT_ROLLUP_FINAL_INDEX = 25; +export const BLOCK_ROOT_ROLLUP_EMPTY_INDEX = 25; +export const BLOCK_ROOT_ROLLUP_FINAL_INDEX = 26; export const FUNCTION_SELECTOR_NUM_BYTES = 4; export const INITIALIZATION_SLOT_SEPARATOR = 1000000000; export const INITIAL_L2_BLOCK_NUM = 1; diff --git a/yarn-project/circuits.js/src/structs/index.ts b/yarn-project/circuits.js/src/structs/index.ts index 0ff816c36cd..d9c0cdff9c4 100644 --- a/yarn-project/circuits.js/src/structs/index.ts +++ b/yarn-project/circuits.js/src/structs/index.ts @@ -78,6 +78,7 @@ export * from './rollup/base_or_merge_rollup_public_inputs.js'; export * from './rollup/base_rollup.js'; export * from './rollup/block_merge_rollup.js'; export * from './rollup/block_root_or_block_merge_public_inputs.js'; +export * from './rollup/empty_block_root_rollup_inputs.js'; export * from './rollup/block_root_rollup.js'; export * from './rollup/merge_rollup.js'; export * from './rollup/previous_rollup_block_data.js'; diff --git a/yarn-project/circuits.js/src/structs/rollup/empty_block_root_rollup_inputs.test.ts b/yarn-project/circuits.js/src/structs/rollup/empty_block_root_rollup_inputs.test.ts new file mode 100644 index 00000000000..f5b147d052a --- /dev/null +++ b/yarn-project/circuits.js/src/structs/rollup/empty_block_root_rollup_inputs.test.ts @@ -0,0 +1,18 @@ +import { makeEmptyBlockRootRollupInputs } from '../../tests/factories.js'; +import { EmptyBlockRootRollupInputs } from './empty_block_root_rollup_inputs.js'; + +describe('EmptyBlockRootRollupInputs', () => { + it(`serializes a EmptyBlockRootRollupInputs to buffer and deserializes it back`, () => { + const expected = makeEmptyBlockRootRollupInputs(); + const buffer = expected.toBuffer(); + const res = EmptyBlockRootRollupInputs.fromBuffer(buffer); + expect(res).toEqual(expected); + }); + + it(`serializes a EmptyBlockRootRollupInputs to hex string and deserializes it back`, () => { + const expected = makeEmptyBlockRootRollupInputs(); + const str = expected.toString(); + const res = EmptyBlockRootRollupInputs.fromString(str); + expect(res).toEqual(expected); + }); +}); diff --git a/yarn-project/circuits.js/src/structs/rollup/empty_block_root_rollup_inputs.ts b/yarn-project/circuits.js/src/structs/rollup/empty_block_root_rollup_inputs.ts new file mode 100644 index 00000000000..52de60ff223 --- /dev/null +++ b/yarn-project/circuits.js/src/structs/rollup/empty_block_root_rollup_inputs.ts @@ -0,0 +1,88 @@ +import { Fr } from '@aztec/foundation/fields'; +import { BufferReader, serializeToBuffer } from '@aztec/foundation/serialize'; +import { type FieldsOf } from '@aztec/foundation/types'; + +import { GlobalVariables } from '../global_variables.js'; +import { AppendOnlyTreeSnapshot } from './append_only_tree_snapshot.js'; + +/** + * Represents inputs of the empty block root rollup circuit. + */ +export class EmptyBlockRootRollupInputs { + constructor( + public readonly archive: AppendOnlyTreeSnapshot, + public readonly blockHash: Fr, + public readonly globalVariables: GlobalVariables, + public readonly outHash: Fr, + public readonly vkTreeRoot: Fr, + // // TODO(#7346): Temporarily added prover_id while we verify block-root proofs on L1 + public readonly proverId: Fr, + ) {} + + /** + * Serializes the inputs to a buffer. + * @returns - The inputs serialized to a buffer. + */ + toBuffer() { + return serializeToBuffer(...EmptyBlockRootRollupInputs.getFields(this)); + } + + /** + * Serializes the inputs to a hex string. + * @returns The instance serialized to a hex string. + */ + toString() { + return this.toBuffer().toString('hex'); + } + + /** + * Creates a new instance from fields. + * @param fields - Fields to create the instance from. + * @returns A new instance. + */ + static from(fields: FieldsOf): EmptyBlockRootRollupInputs { + return new EmptyBlockRootRollupInputs(...EmptyBlockRootRollupInputs.getFields(fields)); + } + + /** + * Extracts fields from an instance. + * @param fields - Fields to create the instance from. + * @returns An array of fields. + */ + static getFields(fields: FieldsOf) { + return [ + fields.archive, + fields.blockHash, + fields.globalVariables, + fields.outHash, + fields.vkTreeRoot, + fields.proverId, + ] as const; + } + + /** + * Deserializes the inputs from a buffer. + * @param buffer - A buffer to deserialize from. + * @returns A new RootRollupInputs instance. + */ + static fromBuffer(buffer: Buffer | BufferReader): EmptyBlockRootRollupInputs { + const reader = BufferReader.asReader(buffer); + return new EmptyBlockRootRollupInputs( + reader.readObject(AppendOnlyTreeSnapshot), + Fr.fromBuffer(reader), + GlobalVariables.fromBuffer(reader), + Fr.fromBuffer(reader), + Fr.fromBuffer(reader), + Fr.fromBuffer(reader), + ); + } + + /** + * Deserializes the inputs from a hex string. + * @param str - A hex string to deserialize from. + * @returns A new RootRollupInputs instance. + */ + static fromString(str: string) { + return EmptyBlockRootRollupInputs.fromBuffer(Buffer.from(str, 'hex')); + } +} diff --git a/yarn-project/circuits.js/src/tests/factories.ts b/yarn-project/circuits.js/src/tests/factories.ts index 074a0714bf1..490108f4920 100644 --- a/yarn-project/circuits.js/src/tests/factories.ts +++ b/yarn-project/circuits.js/src/tests/factories.ts @@ -170,6 +170,7 @@ import { FeeRecipient, } from '../structs/rollup/block_root_or_block_merge_public_inputs.js'; import { BlockRootRollupInputs } from '../structs/rollup/block_root_rollup.js'; +import { EmptyBlockRootRollupInputs } from '../structs/rollup/empty_block_root_rollup_inputs.js'; import { PreviousRollupBlockData } from '../structs/rollup/previous_rollup_block_data.js'; import { RollupValidationRequests } from '../structs/rollup_validation_requests.js'; @@ -1060,6 +1061,26 @@ export function makeBlockRootRollupInputs(seed = 0, globalVariables?: GlobalVari ); } +/** + * Makes empty block root rollup inputs. + * @param seed - The seed to use for generating the root rollup inputs. + * @param globalVariables - The global variables to use. + * @returns A block root rollup inputs. + */ +export function makeEmptyBlockRootRollupInputs( + seed = 0, + globalVariables?: GlobalVariables, +): EmptyBlockRootRollupInputs { + return new EmptyBlockRootRollupInputs( + makeAppendOnlyTreeSnapshot(seed), + fr(seed + 0x100), + globalVariables ?? makeGlobalVariables(seed + 0x200), + fr(seed + 0x300), + fr(seed + 0x400), + fr(seed + 0x500), + ); +} + export function makeRootParityInput( proofSize: PROOF_LENGTH, seed = 0, diff --git a/yarn-project/noir-protocol-circuits-types/src/artifacts.ts b/yarn-project/noir-protocol-circuits-types/src/artifacts.ts index bd47118cf1c..fa5ebf76a70 100644 --- a/yarn-project/noir-protocol-circuits-types/src/artifacts.ts +++ b/yarn-project/noir-protocol-circuits-types/src/artifacts.ts @@ -36,6 +36,7 @@ import BaseRollupJson from '../artifacts/rollup_base.json' assert { type: 'json' import BaseRollupSimulatedJson from '../artifacts/rollup_base_simulated.json' assert { type: 'json' }; import BlockMergeRollupJson from '../artifacts/rollup_block_merge.json' assert { type: 'json' }; import BlockRootRollupJson from '../artifacts/rollup_block_root.json' assert { type: 'json' }; +import EmptyBlockRootRollupJson from '../artifacts/rollup_block_root_empty.json' assert { type: 'json' }; import BlockRootRollupFinalJson from '../artifacts/rollup_block_root_final.json' assert { type: 'json' }; import MergeRollupJson from '../artifacts/rollup_merge.json' assert { type: 'json' }; import RootRollupJson from '../artifacts/rollup_root.json' assert { type: 'json' }; @@ -68,6 +69,7 @@ export type ServerProtocolArtifact = | 'BaseRollupArtifact' | 'MergeRollupArtifact' | 'BlockRootRollupArtifact' + | 'EmptyBlockRootRollupArtifact' | 'BlockRootRollupFinalArtifact' // TODO(palla/prover): Delete this artifact | 'BlockMergeRollupArtifact' | 'RootRollupArtifact'; @@ -92,6 +94,7 @@ export const ServerCircuitArtifacts: Record = { BaseRollupArtifact: keyJsonToVKData(BaseRollupVkJson), MergeRollupArtifact: keyJsonToVKData(MergeRollupVkJson), BlockRootRollupArtifact: keyJsonToVKData(BlockRootRollupVkJson), + EmptyBlockRootRollupArtifact: keyJsonToVKData(EmptyBlockRootRollupVkJson), BlockRootRollupFinalArtifact: keyJsonToVKData(BlockRootRollupFinalVkJson), BlockMergeRollupArtifact: keyJsonToVKData(BlockMergeRollupVkJson), RootRollupArtifact: keyJsonToVKData(RootRollupVkJson), @@ -135,6 +138,7 @@ export const ProtocolCircuitVkIndexes: Record = { BlockRootRollupArtifact: BLOCK_ROOT_ROLLUP_INDEX, BlockMergeRollupArtifact: BLOCK_MERGE_ROLLUP_INDEX, RootRollupArtifact: ROOT_ROLLUP_INDEX, + EmptyBlockRootRollupArtifact: BLOCK_ROOT_ROLLUP_EMPTY_INDEX, BlockRootRollupFinalArtifact: BLOCK_ROOT_ROLLUP_FINAL_INDEX, }; diff --git a/yarn-project/prover-client/src/prover-agent/agent-queue-rpc-integration.test.ts b/yarn-project/prover-client/src/prover-agent/agent-queue-rpc-integration.test.ts index cd30492f015..03f965a7b39 100644 --- a/yarn-project/prover-client/src/prover-agent/agent-queue-rpc-integration.test.ts +++ b/yarn-project/prover-client/src/prover-agent/agent-queue-rpc-integration.test.ts @@ -6,6 +6,7 @@ import { makeBaseRollupInputs, makeBlockMergeRollupInputs, makeBlockRootRollupInputs, + makeEmptyBlockRootRollupInputs, makeHeader, makeMergeRollupInputs, makePublicKernelCircuitPrivateInputs, @@ -40,6 +41,7 @@ describe('Prover agent <-> queue integration', () => { getBaseRollupProof: makeBaseRollupInputs, getRootParityProof: makeRootParityInputs, getBlockMergeRollupProof: makeBlockMergeRollupInputs, + getEmptyBlockRootRollupProof: makeEmptyBlockRootRollupInputs, getBlockRootRollupProof: makeBlockRootRollupInputs, getBlockRootRollupFinalProof: makeBlockRootRollupInputs, getEmptyPrivateKernelProof: () => @@ -73,6 +75,7 @@ describe('Prover agent <-> queue integration', () => { await queue.stop(); }); + // TODO: This test hangs instead of failing when the Inputs are not registered on the RPC wrapper it.each(Object.entries(makeInputs))('can call %s over JSON-RPC', async (fnName, makeInputs) => { const resp = await queue[fnName as keyof ServerCircuitProver](makeInputs() as any); expect(resp).toBeDefined(); diff --git a/yarn-project/prover-client/src/prover-agent/memory-proving-queue.ts b/yarn-project/prover-client/src/prover-agent/memory-proving-queue.ts index 6f54ae5fe87..ba055086efe 100644 --- a/yarn-project/prover-client/src/prover-agent/memory-proving-queue.ts +++ b/yarn-project/prover-client/src/prover-agent/memory-proving-queue.ts @@ -16,6 +16,7 @@ import type { BlockMergeRollupInputs, BlockRootOrBlockMergePublicInputs, BlockRootRollupInputs, + EmptyBlockRootRollupInputs, KernelCircuitPublicInputs, MergeRollupInputs, NESTED_RECURSIVE_PROOF_LENGTH, @@ -351,6 +352,14 @@ export class MemoryProvingQueue implements ServerCircuitProver, ProvingJobSource return this.enqueue({ type: ProvingRequestType.BLOCK_ROOT_ROLLUP, inputs: input }, signal, epochNumber); } + getEmptyBlockRootRollupProof( + input: EmptyBlockRootRollupInputs, + signal?: AbortSignal, + epochNumber?: number, + ): Promise> { + return this.enqueue({ type: ProvingRequestType.EMPTY_BLOCK_ROOT_ROLLUP, inputs: input }, signal, epochNumber); + } + getBlockRootRollupFinalProof( input: BlockRootRollupInputs, signal?: AbortSignal, diff --git a/yarn-project/prover-client/src/prover-agent/prover-agent.ts b/yarn-project/prover-client/src/prover-agent/prover-agent.ts index a4af27153d7..b9dce387e21 100644 --- a/yarn-project/prover-client/src/prover-agent/prover-agent.ts +++ b/yarn-project/prover-client/src/prover-agent/prover-agent.ts @@ -181,6 +181,10 @@ export class ProverAgent { return this.circuitProver.getMergeRollupProof(inputs); } + case ProvingRequestType.EMPTY_BLOCK_ROOT_ROLLUP: { + return this.circuitProver.getEmptyBlockRootRollupProof(inputs); + } + case ProvingRequestType.BLOCK_ROOT_ROLLUP: { return this.circuitProver.getBlockRootRollupProof(inputs); } diff --git a/yarn-project/prover-client/src/prover-agent/rpc.ts b/yarn-project/prover-client/src/prover-agent/rpc.ts index 39d9677ce77..ff97e8bb4fb 100644 --- a/yarn-project/prover-client/src/prover-agent/rpc.ts +++ b/yarn-project/prover-client/src/prover-agent/rpc.ts @@ -9,6 +9,7 @@ import { BlockMergeRollupInputs, BlockRootOrBlockMergePublicInputs, BlockRootRollupInputs, + EmptyBlockRootRollupInputs, EthAddress, Fr, Header, @@ -68,6 +69,7 @@ export function createProvingJobSourceServer(queue: ProvingJobSource): JsonRpcSe BlockRootOrBlockMergePublicInputs, BlockMergeRollupInputs, BlockRootRollupInputs, + EmptyBlockRootRollupInputs, }, {}, ); @@ -109,6 +111,7 @@ export function createProvingJobSourceClient( BlockRootOrBlockMergePublicInputs, BlockMergeRollupInputs, BlockRootRollupInputs, + EmptyBlockRootRollupInputs, }, {}, false, diff --git a/yarn-project/prover-client/src/test/mock_prover.ts b/yarn-project/prover-client/src/test/mock_prover.ts index 021832713a7..49bf7abd5bd 100644 --- a/yarn-project/prover-client/src/test/mock_prover.ts +++ b/yarn-project/prover-client/src/test/mock_prover.ts @@ -78,6 +78,16 @@ export class MockProver implements ServerCircuitProver { ); } + getEmptyBlockRootRollupProof(): Promise> { + return Promise.resolve( + makePublicInputsAndRecursiveProof( + makeBlockRootOrBlockMergeRollupPublicInputs(), + makeRecursiveProof(RECURSIVE_PROOF_LENGTH), + VerificationKeyData.makeFake(), + ), + ); + } + getBlockRootRollupProof(): Promise> { return Promise.resolve( makePublicInputsAndRecursiveProof(