diff --git a/docker-compose.yml b/docker-compose.yml index c0928538e49f..bef8e72fe48b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,7 +7,7 @@ services: environment: LOG_LEVEL: ${LOG_LEVEL:-info} L1_CHAIN_ID: 31337 - VERSION: 1 + ROLLUP_VERSION: 1 PXE_PROVER_ENABLED: ${PXE_PROVER_ENABLED:-1} PXE_DATA_DIRECTORY: /var/lib/aztec/pxe NODE_NO_WARNINGS: 1 @@ -37,7 +37,7 @@ services: environment: LOG_LEVEL: ${LOG_LEVEL:-info} L1_CHAIN_ID: 31337 - VERSION: 1 + ROLLUP_VERSION: 1 NODE_NO_WARNINGS: 1 PROVER_REAL_PROOFS: ${PROVER_REAL_PROOFS:-1} DATA_DIRECTORY: /var/lib/aztec diff --git a/l1-contracts/src/core/Rollup.sol b/l1-contracts/src/core/Rollup.sol index 7a97711b2cca..9f210b1fbbb0 100644 --- a/l1-contracts/src/core/Rollup.sol +++ b/l1-contracts/src/core/Rollup.sol @@ -53,6 +53,7 @@ struct Config { uint256 minimumStake; uint256 slashingQuorum; uint256 slashingRoundSize; + string rollupVersion; } /** @@ -62,7 +63,7 @@ struct Config { * not giving a damn about gas costs. * @dev WARNING: This contract is VERY close to the size limit (500B at time of writing). */ -contract Rollup is EIP712("Aztec Rollup", "1"), Ownable, Leonidas, IRollup, ITestRollup { +contract Rollup is EIP712, Ownable, Leonidas, IRollup, ITestRollup { using SlotLib for Slot; using EpochLib for Epoch; using ProposeLib for ProposeArgs; @@ -110,6 +111,7 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Ownable, Leonidas, IRollup, ITes address _ares, Config memory _config ) + EIP712("Aztec Rollup", _config.rollupVersion) Ownable(_ares) Leonidas( _stakingAsset, diff --git a/l1-contracts/test/fees/FeeRollup.t.sol b/l1-contracts/test/fees/FeeRollup.t.sol index 5cba0a7467b9..630c83b47a55 100644 --- a/l1-contracts/test/fees/FeeRollup.t.sol +++ b/l1-contracts/test/fees/FeeRollup.t.sol @@ -127,6 +127,7 @@ contract FeeRollupTest is FeeModelTestPoints, DecoderBase { bytes32(0), address(this), Config({ + rollupVersion: "1", aztecSlotDuration: SLOT_DURATION, aztecEpochDuration: EPOCH_DURATION, targetCommitteeSize: 48, diff --git a/l1-contracts/test/governance/scenario/slashing/Slashing.t.sol b/l1-contracts/test/governance/scenario/slashing/Slashing.t.sol index e2a40cec054a..472b8b588a45 100644 --- a/l1-contracts/test/governance/scenario/slashing/Slashing.t.sol +++ b/l1-contracts/test/governance/scenario/slashing/Slashing.t.sol @@ -72,7 +72,8 @@ contract SlashingScenario is TestBase { aztecEpochProofClaimWindowInL2Slots: TestConstants.AZTEC_EPOCH_PROOF_CLAIM_WINDOW_IN_L2_SLOTS, minimumStake: TestConstants.AZTEC_MINIMUM_STAKE, slashingQuorum: TestConstants.AZTEC_SLASHING_QUORUM, - slashingRoundSize: TestConstants.AZTEC_SLASHING_ROUND_SIZE + slashingRoundSize: TestConstants.AZTEC_SLASHING_ROUND_SIZE, + rollupVersion: TestConstants.ROLLUP_VERSION }) }); slasher = rollup.SLASHER(); diff --git a/l1-contracts/test/harnesses/Rollup.sol b/l1-contracts/test/harnesses/Rollup.sol index 27d55a9913e3..a66d5196d6e9 100644 --- a/l1-contracts/test/harnesses/Rollup.sol +++ b/l1-contracts/test/harnesses/Rollup.sol @@ -31,7 +31,8 @@ contract Rollup is RealRollup { aztecEpochProofClaimWindowInL2Slots: TestConstants.AZTEC_EPOCH_PROOF_CLAIM_WINDOW_IN_L2_SLOTS, minimumStake: TestConstants.AZTEC_MINIMUM_STAKE, slashingQuorum: TestConstants.AZTEC_SLASHING_QUORUM, - slashingRoundSize: TestConstants.AZTEC_SLASHING_ROUND_SIZE + slashingRoundSize: TestConstants.AZTEC_SLASHING_ROUND_SIZE, + rollupVersion: TestConstants.ROLLUP_VERSION }) ) {} diff --git a/l1-contracts/test/harnesses/TestConstants.sol b/l1-contracts/test/harnesses/TestConstants.sol index aad8edd6db0d..0cb36346b6c6 100644 --- a/l1-contracts/test/harnesses/TestConstants.sol +++ b/l1-contracts/test/harnesses/TestConstants.sol @@ -12,4 +12,5 @@ library TestConstants { uint256 internal constant AZTEC_MINIMUM_STAKE = 100e18; uint256 internal constant AZTEC_SLASHING_QUORUM = 6; uint256 internal constant AZTEC_SLASHING_ROUND_SIZE = 10; + string internal constant ROLLUP_VERSION = "1"; } diff --git a/l1-contracts/test/sparta/Sparta.t.sol b/l1-contracts/test/sparta/Sparta.t.sol index eccf797ee166..ba6cd46bd5b8 100644 --- a/l1-contracts/test/sparta/Sparta.t.sol +++ b/l1-contracts/test/sparta/Sparta.t.sol @@ -124,7 +124,8 @@ contract SpartaTest is DecoderBase { aztecEpochProofClaimWindowInL2Slots: TestConstants.AZTEC_EPOCH_PROOF_CLAIM_WINDOW_IN_L2_SLOTS, minimumStake: TestConstants.AZTEC_MINIMUM_STAKE, slashingQuorum: TestConstants.AZTEC_SLASHING_QUORUM, - slashingRoundSize: TestConstants.AZTEC_SLASHING_ROUND_SIZE + slashingRoundSize: TestConstants.AZTEC_SLASHING_ROUND_SIZE, + rollupVersion: TestConstants.ROLLUP_VERSION }) }); slasher = rollup.SLASHER(); diff --git a/yarn-project/aztec-node/src/aztec-node/server.ts b/yarn-project/aztec-node/src/aztec-node/server.ts index 83a73c1335ee..f01389753881 100644 --- a/yarn-project/aztec-node/src/aztec-node/server.ts +++ b/yarn-project/aztec-node/src/aztec-node/server.ts @@ -5,6 +5,7 @@ import { type AztecNode, type ClientProtocolCircuitVerifier, type EpochProofQuote, + EpochProofQuoteHasher, type GetUnencryptedLogsResponse, type InBlock, type L1ToL2MessageSource, @@ -174,6 +175,11 @@ export class AztecNodeService implements AztecNode, Traceable { log.warn(`Aztec node is accepting fake proofs`); } + const epochProofQuoteHasher = new EpochProofQuoteHasher( + config.l1Contracts.rollupAddress, + config.rollupVersion, + config.l1ChainId, + ); const epochCache = await EpochCache.create(config.l1Contracts.rollupAddress, config, { dateProvider }); // create the tx pool and the p2p client, which will need the l2 block source @@ -184,6 +190,7 @@ export class AztecNodeService implements AztecNode, Traceable { proofVerifier, worldStateSynchronizer, epochCache, + epochProofQuoteHasher, telemetry, ); @@ -222,7 +229,7 @@ export class AztecNodeService implements AztecNode, Traceable { worldStateSynchronizer, sequencer, ethereumChain.chainInfo.id, - config.version, + +config.rollupVersion, new GlobalVariableBuilder(config), proofVerifier, telemetry, diff --git a/yarn-project/aztec/src/sandbox.ts b/yarn-project/aztec/src/sandbox.ts index 1cfb2178e071..15ada97b16cd 100644 --- a/yarn-project/aztec/src/sandbox.ts +++ b/yarn-project/aztec/src/sandbox.ts @@ -152,7 +152,10 @@ export async function createSandbox(config: Partial = {}) { if (config.enableGas) { await setupCanonicalL2FeeJuice( - new SignerlessWallet(pxe, new DefaultMultiCallEntrypoint(aztecNodeConfig.l1ChainId, aztecNodeConfig.version)), + new SignerlessWallet( + pxe, + new DefaultMultiCallEntrypoint(aztecNodeConfig.l1ChainId, aztecNodeConfig.rollupVersion), + ), aztecNodeConfig.l1Contracts.feeJuicePortalAddress, undefined, logger.info, diff --git a/yarn-project/circuit-types/src/prover_coordination/epoch_proof_quote.test.ts b/yarn-project/circuit-types/src/prover_coordination/epoch_proof_quote.test.ts index fe29fcb941d3..a1be03a45c5e 100644 --- a/yarn-project/circuit-types/src/prover_coordination/epoch_proof_quote.test.ts +++ b/yarn-project/circuit-types/src/prover_coordination/epoch_proof_quote.test.ts @@ -1,14 +1,19 @@ import { EthAddress } from '@aztec/circuits.js'; -import { Signature } from '@aztec/foundation/eth-signature'; +import { Secp256k1Signer } from '@aztec/foundation/crypto'; import { jsonParseWithSchema, jsonStringify } from '@aztec/foundation/json-rpc'; import { EpochProofQuote } from './epoch_proof_quote.js'; +import { EpochProofQuoteHasher } from './epoch_proof_quote_hasher.js'; import { EpochProofQuotePayload } from './epoch_proof_quote_payload.js'; describe('epoch proof quote', () => { let quote: EpochProofQuote; + let signer: Secp256k1Signer; + let hasher: EpochProofQuoteHasher; beforeEach(() => { + signer = Secp256k1Signer.random(); + const payload = EpochProofQuotePayload.from({ basisPointFee: 5000, bondAmount: 1000000000000000000n, @@ -17,7 +22,11 @@ describe('epoch proof quote', () => { validUntilSlot: 100n, }); - quote = new EpochProofQuote(payload, Signature.random()); + hasher = new EpochProofQuoteHasher(EthAddress.random(), 1, 1); + + const digest = hasher.hash(payload); + const signature = signer.sign(digest); + quote = new EpochProofQuote(payload, signature); }); const checkEquivalence = (serialized: EpochProofQuote, deserialized: EpochProofQuote) => { @@ -28,6 +37,10 @@ describe('epoch proof quote', () => { it('should serialize and deserialize from buffer', () => { const deserialised = EpochProofQuote.fromBuffer(quote.toBuffer()); checkEquivalence(quote, deserialised); + + // Recover the signer + const recovered = deserialised.getSender(hasher); + expect(recovered).toEqual(signer.address); }); it('should serialize and deserialize from JSON', () => { diff --git a/yarn-project/circuit-types/src/prover_coordination/epoch_proof_quote.ts b/yarn-project/circuit-types/src/prover_coordination/epoch_proof_quote.ts index 26ed60e0d35f..8742be0f2c86 100644 --- a/yarn-project/circuit-types/src/prover_coordination/epoch_proof_quote.ts +++ b/yarn-project/circuit-types/src/prover_coordination/epoch_proof_quote.ts @@ -1,5 +1,5 @@ import { Buffer32 } from '@aztec/foundation/buffer'; -import { type Secp256k1Signer, keccak256 } from '@aztec/foundation/crypto'; +import { type Secp256k1Signer, keccak256, recoverAddress } from '@aztec/foundation/crypto'; import { Signature } from '@aztec/foundation/eth-signature'; import { BufferReader, serializeToBuffer } from '@aztec/foundation/serialize'; import { type FieldsOf } from '@aztec/foundation/types'; @@ -8,6 +8,7 @@ import { z } from 'zod'; import { Gossipable } from '../p2p/gossipable.js'; import { TopicType, createTopicString } from '../p2p/topic_type.js'; +import { type EpochProofQuoteHasher } from './epoch_proof_quote_hasher.js'; import { EpochProofQuotePayload } from './epoch_proof_quote_payload.js'; export class EpochProofQuote extends Gossipable { @@ -34,6 +35,14 @@ export class EpochProofQuote extends Gossipable { return new Buffer32(keccak256(this.signature.toBuffer())); } + /** + * Return the address of the signer of the signature + */ + getSender(quoteHasher: EpochProofQuoteHasher) { + const hashed = quoteHasher.hash(this.payload); + return recoverAddress(hashed, this.signature); + } + override toBuffer(): Buffer { return serializeToBuffer(...EpochProofQuote.getFields(this)); } @@ -62,13 +71,14 @@ export class EpochProofQuote extends Gossipable { * @param signer the signer * @returns a quote with an accompanying signature */ - static new(digest: Buffer32, payload: EpochProofQuotePayload, signer: Secp256k1Signer): EpochProofQuote { + static new(hasher: EpochProofQuoteHasher, payload: EpochProofQuotePayload, signer: Secp256k1Signer): EpochProofQuote { if (!payload.prover.equals(signer.address)) { throw new Error(`Quote prover does not match signer. Prover [${payload.prover}], Signer [${signer.address}]`); } + + const digest = hasher.hash(payload); const signature = signer.sign(digest); - const quote = new EpochProofQuote(payload, signature); - return quote; + return new EpochProofQuote(payload, signature); } toViemArgs() { diff --git a/yarn-project/circuit-types/src/prover_coordination/epoch_proof_quote_hasher.ts b/yarn-project/circuit-types/src/prover_coordination/epoch_proof_quote_hasher.ts new file mode 100644 index 000000000000..f14b90b72fbf --- /dev/null +++ b/yarn-project/circuit-types/src/prover_coordination/epoch_proof_quote_hasher.ts @@ -0,0 +1,108 @@ +import { RollupAbi } from '@aztec/ethereum/contracts'; +import { Buffer32 } from '@aztec/foundation/buffer'; + +import { getAbiItem, hashTypedData, keccak256, parseAbiParameters } from 'viem'; + +import { type EpochProofQuotePayload, type EthAddress } from './epoch_proof_quote_payload.js'; + +type EpochProofQuoteTypeHash = { + EpochProofQuote: { + name: string; + type: string; + }[]; +}; + +/** + * Constructs the type hash for the EpochProofQuote struct. + * + * Given an abi type like so { + * EpochProofQuote: { + * name: 'epochToProve', + * type: 'uint256', + * } + * } + * + * it would construct the hash of the following string: + * E.g: EpochProofQuote(uint256 epochToProve) + * + * @param types - The types of the EpochProofQuote struct + */ +function constructTypeHash(types: EpochProofQuoteTypeHash): `0x${string}` { + const paramsString = types.EpochProofQuote.map(({ name, type }) => `${type} ${name}`).join(','); + const typeString = `EpochProofQuote(${paramsString})`; + return keccak256(Buffer.from(typeString)); +} + +/** + * Constructs the abi for the quoteToDigest function. + * + * Given an abi type like so { + * EpochProofQuote: { + * name: 'epochToProve', + * type: 'uint256', + * } + * } + * + * it would construct the abi of the following string: + * [name (EpochProofQuote), ...params] + * E.g: bytes32, uint256 epochToProve + * + * @param types - The types of the EpochProofQuote struct + */ +function constructHasherAbi(types: EpochProofQuoteTypeHash) { + return parseAbiParameters(`bytes32, ${types.EpochProofQuote.map(({ name, type }) => `${type} ${name}`).join(',')}`); +} + +/** + * A utility class to hash EpochProofQuotePayloads following the EIP-712 standard. + */ +export class EpochProofQuoteHasher { + // Domain information + private readonly domain: { + name: string; + version: string; + chainId: number; + verifyingContract: `0x${string}`; + }; + + /** + * Reads the abi of the quoteToDigest function from the rollup contract and extracts the types + * of the EpochProofQuote struct. + * function quoteToDigest(EpochProofQuote quote) + * + * This avoids re-declaring the types and values here that could go out of sync with + * the rollup contract + */ + static readonly types: EpochProofQuoteTypeHash = { + EpochProofQuote: getAbiItem({ abi: RollupAbi, name: 'quoteToDigest' }).inputs[0].components.map(component => ({ + name: component.name, + type: component.type, + })), + }; + + // Type hash for the types defined in the types object + static readonly EPOCH_PROOF_QUOTE_TYPE_HASH = constructTypeHash(EpochProofQuoteHasher.types); + + // Viem abi object for the types defined in the types object + static readonly HASHER_ABI = constructHasherAbi(EpochProofQuoteHasher.types); + + constructor(rollupAddress: EthAddress, version: number, chainId: number) { + this.domain = { + name: 'Aztec Rollup', + version: version.toString(), + chainId, + verifyingContract: rollupAddress.toString(), + }; + } + + hash(payload: EpochProofQuotePayload): Buffer32 { + return Buffer32.fromString( + hashTypedData({ + domain: this.domain, + types: EpochProofQuoteHasher.types, + primaryType: 'EpochProofQuote', + message: payload.toViemArgs(), + }), + ); + } +} diff --git a/yarn-project/circuit-types/src/prover_coordination/epoch_proof_quote_payload.ts b/yarn-project/circuit-types/src/prover_coordination/epoch_proof_quote_payload.ts index 71d9c7ccc8e4..0f84e23e3039 100644 --- a/yarn-project/circuit-types/src/prover_coordination/epoch_proof_quote_payload.ts +++ b/yarn-project/circuit-types/src/prover_coordination/epoch_proof_quote_payload.ts @@ -1,16 +1,21 @@ import { EthAddress } from '@aztec/foundation/eth-address'; import { schemas } from '@aztec/foundation/schemas'; import { BufferReader, serializeToBuffer } from '@aztec/foundation/serialize'; +import { hexToBuffer } from '@aztec/foundation/string'; import { type FieldsOf } from '@aztec/foundation/types'; import omit from 'lodash.omit'; import { inspect } from 'util'; +import { encodeAbiParameters } from 'viem'; import { z } from 'zod'; +import { type Signable } from '../p2p/index.js'; +import { EpochProofQuoteHasher } from './epoch_proof_quote_hasher.js'; + // Required so typescript can properly annotate the exported schema export { type EthAddress }; -export class EpochProofQuotePayload { +export class EpochProofQuotePayload implements Signable { // Cached values private asBuffer: Buffer | undefined; private size: number | undefined; @@ -72,6 +77,19 @@ export class EpochProofQuotePayload { ); } + getPayloadToSign(): Buffer { + const abi = EpochProofQuoteHasher.HASHER_ABI; + const encodedData = encodeAbiParameters(abi, [ + EpochProofQuoteHasher.EPOCH_PROOF_QUOTE_TYPE_HASH, + this.epochToProve, + this.validUntilSlot, + this.bondAmount, + this.prover.toString(), + BigInt(this.basisPointFee), + ]); + return hexToBuffer(encodedData); + } + static from(fields: FieldsOf): EpochProofQuotePayload { return new EpochProofQuotePayload( fields.epochToProve, diff --git a/yarn-project/circuit-types/src/prover_coordination/index.ts b/yarn-project/circuit-types/src/prover_coordination/index.ts index 33b8a68050b7..1297be0069e2 100644 --- a/yarn-project/circuit-types/src/prover_coordination/index.ts +++ b/yarn-project/circuit-types/src/prover_coordination/index.ts @@ -1,3 +1,4 @@ export * from './epoch_proof_quote.js'; +export * from './epoch_proof_quote_hasher.js'; export * from './epoch_proof_quote_payload.js'; export * from './epoch_proof_claim.js'; diff --git a/yarn-project/end-to-end/src/composed/integration_l1_publisher.test.ts b/yarn-project/end-to-end/src/composed/integration_l1_publisher.test.ts index 1334d39aadf0..f43f845a22fc 100644 --- a/yarn-project/end-to-end/src/composed/integration_l1_publisher.test.ts +++ b/yarn-project/end-to-end/src/composed/integration_l1_publisher.test.ts @@ -206,7 +206,7 @@ describe('L1Publisher integration', () => { makeBloatedProcessedTx({ header: prevHeader, chainId: fr(chainId), - version: fr(config.version), + version: fr(config.rollupVersion), vkTreeRoot: getVKTreeRoot(), gasSettings: GasSettings.default({ maxFeesPerGas: baseFee }), protocolContractTreeRoot, @@ -375,7 +375,7 @@ describe('L1Publisher integration', () => { const globalVariables = new GlobalVariables( new Fr(chainId), - new Fr(config.version), + new Fr(config.rollupVersion), new Fr(1 + i), new Fr(slot), new Fr(timestamp), @@ -509,7 +509,7 @@ describe('L1Publisher integration', () => { const timestamp = await rollup.read.getTimestampForSlot([slot]); const globalVariables = new GlobalVariables( new Fr(chainId), - new Fr(config.version), + new Fr(config.rollupVersion), new Fr(1), new Fr(slot), new Fr(timestamp), diff --git a/yarn-project/end-to-end/src/e2e_fees/fees_test.ts b/yarn-project/end-to-end/src/e2e_fees/fees_test.ts index 518f545c8de3..056da274d950 100644 --- a/yarn-project/end-to-end/src/e2e_fees/fees_test.ts +++ b/yarn-project/end-to-end/src/e2e_fees/fees_test.ts @@ -177,7 +177,7 @@ export class FeesTest { await setupCanonicalFeeJuice( new SignerlessWallet( context.pxe, - new DefaultMultiCallEntrypoint(context.aztecNodeConfig.l1ChainId, context.aztecNodeConfig.version), + new DefaultMultiCallEntrypoint(context.aztecNodeConfig.l1ChainId, context.aztecNodeConfig.rollupVersion), ), ); }, diff --git a/yarn-project/end-to-end/src/fixtures/utils.ts b/yarn-project/end-to-end/src/fixtures/utils.ts index aa85ef06c524..25f88e0a4f23 100644 --- a/yarn-project/end-to-end/src/fixtures/utils.ts +++ b/yarn-project/end-to-end/src/fixtures/utils.ts @@ -499,7 +499,7 @@ export async function setup( if (!config.skipProtocolContracts) { logger.verbose('Setting up Fee Juice...'); await setupCanonicalFeeJuice( - new SignerlessWallet(pxe, new DefaultMultiCallEntrypoint(config.l1ChainId, config.version)), + new SignerlessWallet(pxe, new DefaultMultiCallEntrypoint(config.l1ChainId, config.rollupVersion)), ); } diff --git a/yarn-project/end-to-end/src/prover-coordination/e2e_prover_coordination.test.ts b/yarn-project/end-to-end/src/prover-coordination/e2e_prover_coordination.test.ts index 7e70f26a566c..4e7ae97d36b9 100644 --- a/yarn-project/end-to-end/src/prover-coordination/e2e_prover_coordination.test.ts +++ b/yarn-project/end-to-end/src/prover-coordination/e2e_prover_coordination.test.ts @@ -10,6 +10,8 @@ import { retryUntil, sleep, } from '@aztec/aztec.js'; +// eslint-disable-next-line no-restricted-imports +import { EpochProofQuoteHasher } from '@aztec/circuit-types'; import { type AztecAddress, EthAddress } from '@aztec/circuits.js'; import { Buffer32 } from '@aztec/foundation/buffer'; import { times } from '@aztec/foundation/collection'; @@ -219,7 +221,7 @@ describe('e2e_prover_coordination', () => { await logState(); }; - const makeEpochProofQuote = async ({ + const makeEpochProofQuote = ({ epochToProve, validUntilSlot, bondAmount, @@ -240,9 +242,11 @@ describe('e2e_prover_coordination', () => { signer.address, basisPointFee ?? randomInt(100), ); - const digest = await rollupContract.read.quoteToDigest([quotePayload.toViemArgs()]); - return EpochProofQuote.new(Buffer32.fromString(digest), quotePayload, signer); + const { rollupAddress } = ctx.deployL1ContractsValues.l1ContractAddresses; + const { l1ChainId, rollupVersion } = ctx.aztecNodeConfig; + const hasher = new EpochProofQuoteHasher(rollupAddress, rollupVersion, l1ChainId); + return EpochProofQuote.new(hasher, quotePayload, signer); }; it('Sequencer selects best valid proving quote for each block', async () => { @@ -253,7 +257,7 @@ describe('e2e_prover_coordination', () => { await performEscrow(10000000n); // Here we are creating a proof quote for epoch 0 - const quoteForEpoch0 = await makeEpochProofQuote({ + const quoteForEpoch0 = makeEpochProofQuote({ epochToProve: 0n, validUntilSlot: BigInt(ctx.aztecNodeConfig.aztecEpochDuration + 10), bondAmount: 10000n, @@ -298,19 +302,17 @@ describe('e2e_prover_coordination', () => { const currentSlot = await getSlot(); // Now create a number of quotes, some valid some invalid for epoch 1, the lowest priced valid quote should be chosen - const validQuotes = await Promise.all( - times(3, (i: number) => - makeEpochProofQuote({ - epochToProve: 1n, - validUntilSlot: currentSlot + 2n, - bondAmount: 10000n, - basisPointFee: 10 + i, - signer: proverSigner, - }), - ), + const validQuotes = times(3, (i: number) => + makeEpochProofQuote({ + epochToProve: 1n, + validUntilSlot: currentSlot + 2n, + bondAmount: 10000n, + basisPointFee: 10 + i, + signer: proverSigner, + }), ); - const proofQuoteInvalidSlot = await makeEpochProofQuote({ + const proofQuoteInvalidSlot = makeEpochProofQuote({ epochToProve: 1n, validUntilSlot: 3n, bondAmount: 10000n, @@ -318,7 +320,7 @@ describe('e2e_prover_coordination', () => { signer: proverSigner, }); - const proofQuoteInvalidEpoch = await makeEpochProofQuote({ + const proofQuoteInvalidEpoch = makeEpochProofQuote({ epochToProve: 4n, validUntilSlot: currentSlot + 4n, bondAmount: 10000n, @@ -326,7 +328,7 @@ describe('e2e_prover_coordination', () => { signer: proverSigner, }); - const proofQuoteInsufficientBond = await makeEpochProofQuote({ + const proofQuoteInsufficientBond = makeEpochProofQuote({ epochToProve: 1n, validUntilSlot: currentSlot + 4n, bondAmount: 0n, @@ -371,7 +373,7 @@ describe('e2e_prover_coordination', () => { let currentPending = tips.pending; let currentProven = tips.proven; // Here we are creating a proof quote for epoch 0 - const quoteForEpoch0 = await makeEpochProofQuote({ + const quoteForEpoch0 = makeEpochProofQuote({ epochToProve: 0n, validUntilSlot: BigInt(ctx.aztecNodeConfig.aztecEpochDuration + 10), bondAmount: 10000n, @@ -458,7 +460,7 @@ describe('e2e_prover_coordination', () => { await expectTips({ pending: currentPending, proven: currentProven }); // Submit proof claim for the new epoch - const quoteForEpoch4 = await makeEpochProofQuote({ + const quoteForEpoch4 = makeEpochProofQuote({ epochToProve: 4n, validUntilSlot: BigInt(ctx.aztecNodeConfig.aztecEpochDuration * 4 + 10), bondAmount: 10000n, diff --git a/yarn-project/ethereum/src/config.ts b/yarn-project/ethereum/src/config.ts index 2a2bff7e4fa3..c8e740ee5b76 100644 --- a/yarn-project/ethereum/src/config.ts +++ b/yarn-project/ethereum/src/config.ts @@ -26,6 +26,8 @@ export type L1ContractsConfig = { governanceProposerQuorum: number; /** Governance proposing round size */ governanceProposerRoundSize: number; + /** The version of the rollup. */ + rollupVersion: number; }; export const DefaultL1ContractsConfig = { @@ -39,6 +41,7 @@ export const DefaultL1ContractsConfig = { slashingRoundSize: 10, governanceProposerQuorum: 6, governanceProposerRoundSize: 10, + rollupVersion: 1, } satisfies L1ContractsConfig; export const l1ContractsConfigMappings: ConfigMappingsType = { @@ -92,6 +95,12 @@ export const l1ContractsConfigMappings: ConfigMappingsType = description: 'The governance proposing round size', ...numberConfigHelper(DefaultL1ContractsConfig.governanceProposerRoundSize), }, + rollupVersion: { + env: 'ROLLUP_VERSION', + description: 'The version of the rollup.', + defaultValue: DefaultL1ContractsConfig.rollupVersion, + ...numberConfigHelper(DefaultL1ContractsConfig.rollupVersion), + }, }; export function getL1ContractsConfigEnvVars(): L1ContractsConfig { diff --git a/yarn-project/ethereum/src/contracts/rollup.ts b/yarn-project/ethereum/src/contracts/rollup.ts index cbaf9d689e09..1900e1baaca8 100644 --- a/yarn-project/ethereum/src/contracts/rollup.ts +++ b/yarn-project/ethereum/src/contracts/rollup.ts @@ -17,6 +17,8 @@ import { type DeployL1Contracts } from '../deploy_l1_contracts.js'; import { createEthereumChain } from '../ethereum_chain.js'; import { type L1ReaderConfig } from '../l1_reader.js'; +export { RollupAbi } from '@aztec/l1-artifacts'; + export class RollupContract { private readonly rollup: GetContractReturnType>; diff --git a/yarn-project/ethereum/src/deploy_l1_contracts.ts b/yarn-project/ethereum/src/deploy_l1_contracts.ts index 09e49baf2627..fc85e5e0c23a 100644 --- a/yarn-project/ethereum/src/deploy_l1_contracts.ts +++ b/yarn-project/ethereum/src/deploy_l1_contracts.ts @@ -336,6 +336,7 @@ export const deployL1Contracts = async ( minimumStake: args.minimumStake, slashingQuorum: args.slashingQuorum, slashingRoundSize: args.slashingRoundSize, + rollupVersion: args.rollupVersion, }; const rollupArgs = [ feeJuicePortalAddress.toString(), diff --git a/yarn-project/foundation/src/config/env_var.ts b/yarn-project/foundation/src/config/env_var.ts index 2fef4f1dcace..175d6d639fda 100644 --- a/yarn-project/foundation/src/config/env_var.ts +++ b/yarn-project/foundation/src/config/env_var.ts @@ -166,7 +166,7 @@ export type EnvVar = | 'VALIDATOR_DISABLED' | 'VALIDATOR_PRIVATE_KEY' | 'VALIDATOR_REEXECUTE' - | 'VERSION' + | 'ROLLUP_VERSION' | 'WS_BLOCK_CHECK_INTERVAL_MS' | 'WS_PROVEN_BLOCKS_ONLY' | 'WS_BLOCK_REQUEST_BATCH_SIZE' diff --git a/yarn-project/p2p/src/client/factory.ts b/yarn-project/p2p/src/client/factory.ts index ede72a17819a..25151bc9ab7e 100644 --- a/yarn-project/p2p/src/client/factory.ts +++ b/yarn-project/p2p/src/client/factory.ts @@ -1,5 +1,6 @@ import { type ClientProtocolCircuitVerifier, + type EpochProofQuoteHasher, type L2BlockSource, P2PClientType, type WorldStateSynchronizer, @@ -38,6 +39,7 @@ export const createP2PClient = async ( proofVerifier: ClientProtocolCircuitVerifier, worldStateSynchronizer: WorldStateSynchronizer, epochCache: EpochCache, + epochProofQuoteHasher: EpochProofQuoteHasher, telemetry: TelemetryClient = getTelemetryClient(), deps: P2PClientDeps = {}, ) => { @@ -76,6 +78,7 @@ export const createP2PClient = async ( mempools, l2BlockSource, epochCache, + epochProofQuoteHasher, proofVerifier, worldStateSynchronizer, store, diff --git a/yarn-project/p2p/src/mem_pools/epoch_proof_quote_pool/test_utils.ts b/yarn-project/p2p/src/mem_pools/epoch_proof_quote_pool/test_utils.ts index a59de8daa3c0..8a7ac38ffe60 100644 --- a/yarn-project/p2p/src/mem_pools/epoch_proof_quote_pool/test_utils.ts +++ b/yarn-project/p2p/src/mem_pools/epoch_proof_quote_pool/test_utils.ts @@ -1,6 +1,5 @@ -import { EpochProofQuote, EpochProofQuotePayload } from '@aztec/circuit-types'; +import { EpochProofQuote, EpochProofQuoteHasher, EpochProofQuotePayload } from '@aztec/circuit-types'; import { EthAddress } from '@aztec/circuits.js'; -import { Buffer32 } from '@aztec/foundation/buffer'; import { Secp256k1Signer, randomBigInt, randomInt } from '@aztec/foundation/crypto'; export function makeRandomEpochProofQuotePayload(): EpochProofQuotePayload { @@ -17,10 +16,11 @@ export function makeRandomEpochProofQuote(payload?: EpochProofQuotePayload): { quote: EpochProofQuote; signer: Secp256k1Signer; } { + const hasher = new EpochProofQuoteHasher(EthAddress.random(), 1, 1); const signer = Secp256k1Signer.random(); return { - quote: EpochProofQuote.new(Buffer32.random(), payload ?? makeRandomEpochProofQuotePayload(), signer), + quote: EpochProofQuote.new(hasher, payload ?? makeRandomEpochProofQuotePayload(), signer), signer, }; } diff --git a/yarn-project/p2p/src/mocks/index.ts b/yarn-project/p2p/src/mocks/index.ts index 179288c4f835..1b9ed60781fd 100644 --- a/yarn-project/p2p/src/mocks/index.ts +++ b/yarn-project/p2p/src/mocks/index.ts @@ -1,5 +1,6 @@ import { type ClientProtocolCircuitVerifier, + type EpochProofQuoteHasher, type L2BlockSource, type P2PClientType, type Tx, @@ -100,6 +101,7 @@ export async function createTestLibP2PService( l2BlockSource: L2BlockSource, worldStateSynchronizer: WorldStateSynchronizer, epochCache: EpochCache, + epochProofQuoteHasher: EpochProofQuoteHasher, mempools: MemPools, telemetry: TelemetryClient, port: number = 0, @@ -132,6 +134,7 @@ export async function createTestLibP2PService( mempools, l2BlockSource, epochCache, + epochProofQuoteHasher, proofVerifier, worldStateSynchronizer, telemetry, diff --git a/yarn-project/p2p/src/msg_validators/epoch_proof_quote_validator/epoch_proof_quote_validator.test.ts b/yarn-project/p2p/src/msg_validators/epoch_proof_quote_validator/epoch_proof_quote_validator.test.ts index de90b81f9c30..8d58a00ecf1a 100644 --- a/yarn-project/p2p/src/msg_validators/epoch_proof_quote_validator/epoch_proof_quote_validator.test.ts +++ b/yarn-project/p2p/src/msg_validators/epoch_proof_quote_validator/epoch_proof_quote_validator.test.ts @@ -1,7 +1,12 @@ -import { EpochProofQuote, EpochProofQuotePayload, PeerErrorSeverity } from '@aztec/circuit-types'; +import { + EpochProofQuote, + EpochProofQuoteHasher, + EpochProofQuotePayload, + PeerErrorSeverity, +} from '@aztec/circuit-types'; import { type EpochCache } from '@aztec/epoch-cache'; +import { Secp256k1Signer } from '@aztec/foundation/crypto'; import { EthAddress } from '@aztec/foundation/eth-address'; -import { Signature } from '@aztec/foundation/eth-signature'; import { mock } from 'jest-mock-extended'; @@ -10,26 +15,30 @@ import { EpochProofQuoteValidator } from './epoch_proof_quote_validator.js'; describe('EpochProofQuoteValidator', () => { let epochCache: EpochCache; let validator: EpochProofQuoteValidator; + let epochProofQuoteHasher: EpochProofQuoteHasher; + let signer: Secp256k1Signer; beforeEach(() => { epochCache = mock(); - validator = new EpochProofQuoteValidator(epochCache); + epochProofQuoteHasher = new EpochProofQuoteHasher(EthAddress.random(), 1, 1); + signer = Secp256k1Signer.random(); + validator = new EpochProofQuoteValidator(epochCache, epochProofQuoteHasher); }); - const makeEpochProofQuote = (epochToProve: bigint) => { + const makeEpochProofQuote = (epochToProve: bigint, signer: Secp256k1Signer) => { const payload = EpochProofQuotePayload.from({ basisPointFee: 5000, bondAmount: 1000000000000000000n, epochToProve, - prover: EthAddress.random(), + prover: signer.address, validUntilSlot: 100n, }); - return new EpochProofQuote(payload, Signature.random()); + return EpochProofQuote.new(epochProofQuoteHasher, payload, signer); }; it('returns high tolerance error if epoch to prove is not current or previous epoch', async () => { // Create an epoch proof quote for epoch 5 - const mockQuote = makeEpochProofQuote(5n); + const mockQuote = makeEpochProofQuote(5n, signer); // Mock epoch cache to return different epoch (epochCache.getEpochAndSlotNow as jest.Mock).mockReturnValue({ @@ -42,7 +51,7 @@ describe('EpochProofQuoteValidator', () => { it('returns no error if epoch to prove is current epoch', async () => { // Create an epoch proof quote for current epoch - const mockQuote = makeEpochProofQuote(7n); + const mockQuote = makeEpochProofQuote(7n, signer); // Mock epoch cache to return matching epoch (epochCache.getEpochAndSlotNow as jest.Mock).mockReturnValue({ @@ -55,7 +64,7 @@ describe('EpochProofQuoteValidator', () => { it('returns no error if epoch to prove is previous epoch', async () => { // Create an epoch proof quote for previous epoch - const mockQuote = makeEpochProofQuote(6n); + const mockQuote = makeEpochProofQuote(6n, signer); // Mock epoch cache to return current epoch (epochCache.getEpochAndSlotNow as jest.Mock).mockReturnValue({ diff --git a/yarn-project/p2p/src/msg_validators/epoch_proof_quote_validator/epoch_proof_quote_validator.ts b/yarn-project/p2p/src/msg_validators/epoch_proof_quote_validator/epoch_proof_quote_validator.ts index 99eec5be73d4..21f9874826db 100644 --- a/yarn-project/p2p/src/msg_validators/epoch_proof_quote_validator/epoch_proof_quote_validator.ts +++ b/yarn-project/p2p/src/msg_validators/epoch_proof_quote_validator/epoch_proof_quote_validator.ts @@ -1,11 +1,18 @@ -import { type EpochProofQuote, type P2PValidator, PeerErrorSeverity } from '@aztec/circuit-types'; +import { + type EpochProofQuote, + type EpochProofQuoteHasher, + type P2PValidator, + PeerErrorSeverity, +} from '@aztec/circuit-types'; import { type EpochCache } from '@aztec/epoch-cache'; export class EpochProofQuoteValidator implements P2PValidator { private epochCache: EpochCache; + private quoteHasher: EpochProofQuoteHasher; - constructor(epochCache: EpochCache) { + constructor(epochCache: EpochCache, quoteHasher: EpochProofQuoteHasher) { this.epochCache = epochCache; + this.quoteHasher = quoteHasher; } validate(message: EpochProofQuote): Promise { @@ -17,6 +24,12 @@ export class EpochProofQuoteValidator implements P2PValidator { return Promise.resolve(PeerErrorSeverity.HighToleranceError); } + // Check that the message signer is the prover + const signer = message.getSender(this.quoteHasher); + if (!signer.equals(message.payload.prover)) { + return Promise.resolve(PeerErrorSeverity.HighToleranceError); + } + return Promise.resolve(undefined); } } diff --git a/yarn-project/p2p/src/services/libp2p/libp2p_service.ts b/yarn-project/p2p/src/services/libp2p/libp2p_service.ts index 8c0a1b24b09b..08bb91e52261 100644 --- a/yarn-project/p2p/src/services/libp2p/libp2p_service.ts +++ b/yarn-project/p2p/src/services/libp2p/libp2p_service.ts @@ -3,6 +3,7 @@ import { BlockProposal, type ClientProtocolCircuitVerifier, EpochProofQuote, + type EpochProofQuoteHasher, type Gossipable, type L2BlockSource, MerkleTreeId, @@ -112,6 +113,7 @@ export class LibP2PService extends WithTracer implement private mempools: MemPools, private l2BlockSource: L2BlockSource, epochCache: EpochCache, + private proofQuoteHasher: EpochProofQuoteHasher, private proofVerifier: ClientProtocolCircuitVerifier, private worldStateSynchronizer: WorldStateSynchronizer, telemetry: TelemetryClient, @@ -140,7 +142,7 @@ export class LibP2PService extends WithTracer implement this.attestationValidator = new AttestationValidator(epochCache); this.blockProposalValidator = new BlockProposalValidator(epochCache); - this.epochProofQuoteValidator = new EpochProofQuoteValidator(epochCache); + this.epochProofQuoteValidator = new EpochProofQuoteValidator(epochCache, this.proofQuoteHasher); this.blockReceivedCallback = (block: BlockProposal): Promise => { this.logger.verbose( @@ -164,6 +166,7 @@ export class LibP2PService extends WithTracer implement mempools: MemPools, l2BlockSource: L2BlockSource, epochCache: EpochCache, + proofQuoteHasher: EpochProofQuoteHasher, proofVerifier: ClientProtocolCircuitVerifier, worldStateSynchronizer: WorldStateSynchronizer, store: AztecKVStore, @@ -263,6 +266,7 @@ export class LibP2PService extends WithTracer implement mempools, l2BlockSource, epochCache, + proofQuoteHasher, proofVerifier, worldStateSynchronizer, telemetry, diff --git a/yarn-project/p2p/src/services/reqresp/reqresp.integration.test.ts b/yarn-project/p2p/src/services/reqresp/reqresp.integration.test.ts index 70e6c6b51ba5..5a531e4e51c2 100644 --- a/yarn-project/p2p/src/services/reqresp/reqresp.integration.test.ts +++ b/yarn-project/p2p/src/services/reqresp/reqresp.integration.test.ts @@ -2,6 +2,7 @@ import { MockL2BlockSource } from '@aztec/archiver/test'; import { type ClientProtocolCircuitVerifier, + type EpochProofQuoteHasher, P2PClientType, PeerErrorSeverity, type Tx, @@ -50,6 +51,7 @@ describe('Req Resp p2p client integration', () => { let attestationPool: MockProxy; let epochProofQuotePool: MockProxy; let epochCache: MockProxy; + let epochProofQuoteHasher: MockProxy; let l2BlockSource: MockL2BlockSource; let kvStore: AztecKVStore; let worldState: WorldStateSynchronizer; @@ -61,6 +63,7 @@ describe('Req Resp p2p client integration', () => { attestationPool = mock(); epochProofQuotePool = mock(); epochCache = mock(); + epochProofQuoteHasher = mock(); txPool.getAllTxs.mockImplementation(() => { return [] as Tx[]; @@ -134,6 +137,7 @@ describe('Req Resp p2p client integration', () => { proofVerifier, worldState, epochCache, + epochProofQuoteHasher, undefined, deps, ); diff --git a/yarn-project/prover-node/src/factory.ts b/yarn-project/prover-node/src/factory.ts index c129933070d1..6f4225db47e1 100644 --- a/yarn-project/prover-node/src/factory.ts +++ b/yarn-project/prover-node/src/factory.ts @@ -1,20 +1,16 @@ import { type Archiver, createArchiver } from '@aztec/archiver'; import { type BlobSinkClientInterface, createBlobSinkClient } from '@aztec/blob-sink/client'; -import { type ProverCoordination, type ProvingJobBroker } from '@aztec/circuit-types'; +import { EpochProofQuoteHasher, type ProverCoordination, type ProvingJobBroker } from '@aztec/circuit-types'; import { EpochCache } from '@aztec/epoch-cache'; -import { createEthereumChain } from '@aztec/ethereum'; import { Buffer32 } from '@aztec/foundation/buffer'; import { type Logger, createLogger } from '@aztec/foundation/log'; import { type DataStoreConfig } from '@aztec/kv-store/config'; -import { RollupAbi } from '@aztec/l1-artifacts'; import { createProverClient } from '@aztec/prover-client'; import { createAndStartProvingBroker } from '@aztec/prover-client/broker'; import { L1Publisher } from '@aztec/sequencer-client'; import { type TelemetryClient, getTelemetryClient } from '@aztec/telemetry-client'; import { createWorldStateSynchronizer } from '@aztec/world-state'; -import { createPublicClient, getAddress, getContract, http } from 'viem'; - import { createBondManager } from './bond/factory.js'; import { type ProverNodeConfig, type QuoteProviderConfig } from './config.js'; import { ClaimsMonitor } from './monitors/claims-monitor.js'; @@ -54,7 +50,13 @@ export async function createProverNode( // REFACTOR: Move publisher out of sequencer package and into an L1-related package const publisher = deps.publisher ?? new L1Publisher(config, { telemetry, blobSinkClient }); + // Dependencies of the p2p client const epochCache = await EpochCache.create(config.l1Contracts.rollupAddress, config); + const epochProofQuoteHasher = new EpochProofQuoteHasher( + config.l1Contracts.rollupAddress, + config.rollupVersion, + config.l1ChainId, + ); // If config.p2pEnabled is true, createProverCoordination will create a p2p client where quotes will be shared and tx's requested // If config.p2pEnabled is false, createProverCoordination request information from the AztecNode @@ -63,11 +65,12 @@ export async function createProverNode( worldStateSynchronizer, archiver, epochCache, + epochProofQuoteHasher, telemetry, }); const quoteProvider = createQuoteProvider(config); - const quoteSigner = createQuoteSigner(config); + const quoteSigner = createQuoteSigner(config, epochProofQuoteHasher); const proverNodeConfig: ProverNodeOptions = { maxPendingJobs: config.proverNodeMaxPendingJobs, @@ -109,13 +112,9 @@ function createQuoteProvider(config: QuoteProviderConfig) { : new SimpleQuoteProvider(config.quoteProviderBasisPointFee, config.quoteProviderBondAmount); } -function createQuoteSigner(config: ProverNodeConfig) { - // REFACTOR: We need a package that just returns an instance of a rollup contract ready to use - const { l1RpcUrl: rpcUrl, l1ChainId: chainId, l1Contracts } = config; - const chain = createEthereumChain(rpcUrl, chainId); - const client = createPublicClient({ chain: chain.chainInfo, transport: http(chain.rpcUrl) }); - const address = getAddress(l1Contracts.rollupAddress.toString()); - const rollupContract = getContract({ address, abi: RollupAbi, client }); - const privateKey = config.publisherPrivateKey; - return QuoteSigner.new(Buffer32.fromString(privateKey), rollupContract); +function createQuoteSigner(config: ProverNodeConfig, epochProofQuoteHasher: EpochProofQuoteHasher) { + const { publisherPrivateKey } = config; + + const privateKey = Buffer32.fromString(publisherPrivateKey); + return QuoteSigner.new(epochProofQuoteHasher, privateKey); } diff --git a/yarn-project/prover-node/src/prover-coordination/factory.ts b/yarn-project/prover-node/src/prover-coordination/factory.ts index 6a308f0830ee..b38d9f4c01b4 100644 --- a/yarn-project/prover-node/src/prover-coordination/factory.ts +++ b/yarn-project/prover-node/src/prover-coordination/factory.ts @@ -1,6 +1,7 @@ import { type ArchiveSource, type Archiver } from '@aztec/archiver'; import { BBCircuitVerifier, TestCircuitVerifier } from '@aztec/bb-prover'; import { + type EpochProofQuoteHasher, P2PClientType, type ProverCoordination, type WorldStateSynchronizer, @@ -21,6 +22,7 @@ type ProverCoordinationDeps = { archiver?: Archiver | ArchiveSource; telemetry?: TelemetryClient; epochCache?: EpochCache; + epochProofQuoteHasher?: EpochProofQuoteHasher; }; /** @@ -43,7 +45,13 @@ export async function createProverCoordination( if (config.p2pEnabled) { log.info('Using prover coordination via p2p'); - if (!deps.archiver || !deps.worldStateSynchronizer || !deps.telemetry || !deps.epochCache) { + if ( + !deps.archiver || + !deps.worldStateSynchronizer || + !deps.telemetry || + !deps.epochCache || + !deps.epochProofQuoteHasher + ) { throw new Error('Missing dependencies for p2p prover coordination'); } @@ -55,6 +63,7 @@ export async function createProverCoordination( proofVerifier, deps.worldStateSynchronizer, deps.epochCache, + deps.epochProofQuoteHasher, deps.telemetry, ); await p2pClient.start(); diff --git a/yarn-project/prover-node/src/prover-node.test.ts b/yarn-project/prover-node/src/prover-node.test.ts index ecc182fb6ff5..ac0a2e28f050 100644 --- a/yarn-project/prover-node/src/prover-node.test.ts +++ b/yarn-project/prover-node/src/prover-node.test.ts @@ -2,6 +2,7 @@ import { EmptyL1RollupConstants, type EpochProofClaim, EpochProofQuote, + EpochProofQuoteHasher, EpochProofQuotePayload, type EpochProverManager, type L1ToL2MessageSource, @@ -18,7 +19,7 @@ import { import { type ContractDataSource, EthAddress } from '@aztec/circuits.js'; import { type EpochCache } from '@aztec/epoch-cache'; import { times } from '@aztec/foundation/collection'; -import { Signature } from '@aztec/foundation/eth-signature'; +import { Secp256k1Signer } from '@aztec/foundation/crypto'; import { makeBackoff, retry } from '@aztec/foundation/retry'; import { sleep } from '@aztec/foundation/sleep'; import { openTmpStore } from '@aztec/kv-store/lmdb'; @@ -51,6 +52,7 @@ describe('prover-node', () => { let mockCoordination: MockProxy; let quoteProvider: MockProxy; let quoteSigner: MockProxy; + let epochProofQuoteHasher: EpochProofQuoteHasher; let bondManager: MockProxy; let config: ProverNodeOptions; @@ -116,6 +118,9 @@ describe('prover-node', () => { quoteSigner = mock(); bondManager = mock(); + epochProofQuoteHasher = new EpochProofQuoteHasher(EthAddress.random(), /*version=*/ 1, /*chainId=*/ 1); + const signer = Secp256k1Signer.random(); + config = { maxPendingJobs: 3, pollingIntervalMs: 10, @@ -133,15 +138,15 @@ describe('prover-node', () => { }); // Publisher returns its sender address - address = EthAddress.random(); + address = signer.address; publisher.getSenderAddress.mockReturnValue(address); // Quote provider returns a mock partialQuote = { basisPointFee: 100, bondAmount: 0n, validUntilSlot: 30n }; quoteProvider.getQuote.mockResolvedValue(partialQuote); - // Signer returns an empty signature - quoteSigner.sign.mockImplementation(payload => Promise.resolve(new EpochProofQuote(payload, Signature.empty()))); + // Signer return the signature of quoteSigner + quoteSigner.sign.mockImplementation(payload => EpochProofQuote.new(epochProofQuoteHasher, payload, signer)); // We create 3 fake blocks with 1 tx effect each blocks = times(3, i => L2Block.random(i + 20, 1)); @@ -379,6 +384,7 @@ describe('prover-node', () => { l2BlockSource, worldState, epochCache, + epochProofQuoteHasher, mempools, getTelemetryClient(), port, diff --git a/yarn-project/prover-node/src/prover-node.ts b/yarn-project/prover-node/src/prover-node.ts index 80b0f5b19d35..d3293531785a 100644 --- a/yarn-project/prover-node/src/prover-node.ts +++ b/yarn-project/prover-node/src/prover-node.ts @@ -182,7 +182,7 @@ export class ProverNode implements ClaimsMonitorHandler, EpochMonitorHandler, Pr prover: this.publisher.getSenderAddress(), validUntilSlot: partialQuote.validUntilSlot ?? BigInt(Number.MAX_SAFE_INTEGER), // Should we constrain this? }); - const signed = await this.quoteSigner.sign(quote); + const signed = this.quoteSigner.sign(quote); // Send it to the coordinator this.log.info( diff --git a/yarn-project/prover-node/src/quote-signer.ts b/yarn-project/prover-node/src/quote-signer.ts index 0c2f4bd9c4eb..1bb4ce912de7 100644 --- a/yarn-project/prover-node/src/quote-signer.ts +++ b/yarn-project/prover-node/src/quote-signer.ts @@ -1,24 +1,15 @@ -import { EpochProofQuote, type EpochProofQuotePayload } from '@aztec/circuit-types'; -import { Buffer32 } from '@aztec/foundation/buffer'; +import { EpochProofQuote, type EpochProofQuoteHasher, type EpochProofQuotePayload } from '@aztec/circuit-types'; +import { type Buffer32 } from '@aztec/foundation/buffer'; import { Secp256k1Signer } from '@aztec/foundation/crypto'; -import { type RollupAbi } from '@aztec/l1-artifacts'; - -import { type GetContractReturnType, type PublicClient } from 'viem'; export class QuoteSigner { - constructor( - private readonly signer: Secp256k1Signer, - private readonly quoteToDigest: (payload: EpochProofQuotePayload) => Promise, - ) {} + constructor(private readonly hasher: EpochProofQuoteHasher, private readonly signer: Secp256k1Signer) {} - static new(privateKey: Buffer32, rollupContract: GetContractReturnType): QuoteSigner { - const quoteToDigest = (payload: EpochProofQuotePayload) => - rollupContract.read.quoteToDigest([payload.toViemArgs()]).then(Buffer32.fromString); - return new QuoteSigner(new Secp256k1Signer(privateKey), quoteToDigest); + static new(hasher: EpochProofQuoteHasher, privateKey: Buffer32): QuoteSigner { + return new QuoteSigner(hasher, new Secp256k1Signer(privateKey)); } - public async sign(payload: EpochProofQuotePayload) { - const digest = await this.quoteToDigest(payload); - return EpochProofQuote.new(digest, payload, this.signer); + public sign(payload: EpochProofQuotePayload) { + return EpochProofQuote.new(this.hasher, payload, this.signer); } } diff --git a/yarn-project/sequencer-client/src/config.ts b/yarn-project/sequencer-client/src/config.ts index 9db77ac13b35..a1b6a7ccdb60 100644 --- a/yarn-project/sequencer-client/src/config.ts +++ b/yarn-project/sequencer-client/src/config.ts @@ -29,8 +29,6 @@ export { SequencerConfig }; type ChainConfig = { /** The chain id of the ethereum host. */ l1ChainId: number; - /** The version of the rollup. */ - version: number; }; /** @@ -125,11 +123,6 @@ export const sequencerConfigMappings: ConfigMappingsType = { export const chainConfigMappings: ConfigMappingsType = { l1ChainId: l1ReaderConfigMappings.l1ChainId, - version: { - env: 'VERSION', - description: 'The version of the rollup.', - ...numberConfigHelper(1), - }, }; export const sequencerClientConfigMappings: ConfigMappingsType = {