Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
4 changes: 3 additions & 1 deletion l1-contracts/src/core/Rollup.sol
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ struct Config {
uint256 minimumStake;
uint256 slashingQuorum;
uint256 slashingRoundSize;
string rollupVersion;
}

/**
Expand All @@ -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;
Expand Down Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions l1-contracts/test/fees/FeeRollup.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ contract FeeRollupTest is FeeModelTestPoints, DecoderBase {
bytes32(0),
address(this),
Config({
rollupVersion: "1",
aztecSlotDuration: SLOT_DURATION,
aztecEpochDuration: EPOCH_DURATION,
targetCommitteeSize: 48,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
3 changes: 2 additions & 1 deletion l1-contracts/test/harnesses/Rollup.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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
})
)
{}
Expand Down
1 change: 1 addition & 0 deletions l1-contracts/test/harnesses/TestConstants.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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";
}
3 changes: 2 additions & 1 deletion l1-contracts/test/sparta/Sparta.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
9 changes: 8 additions & 1 deletion yarn-project/aztec-node/src/aztec-node/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
type AztecNode,
type ClientProtocolCircuitVerifier,
type EpochProofQuote,
EpochProofQuoteHasher,
type GetUnencryptedLogsResponse,
type InBlock,
type L1ToL2MessageSource,
Expand Down Expand Up @@ -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
Expand All @@ -184,6 +190,7 @@ export class AztecNodeService implements AztecNode, Traceable {
proofVerifier,
worldStateSynchronizer,
epochCache,
epochProofQuoteHasher,
telemetry,
);

Expand Down Expand Up @@ -222,7 +229,7 @@ export class AztecNodeService implements AztecNode, Traceable {
worldStateSynchronizer,
sequencer,
ethereumChain.chainInfo.id,
config.version,
+config.rollupVersion,
new GlobalVariableBuilder(config),
proofVerifier,
telemetry,
Expand Down
5 changes: 4 additions & 1 deletion yarn-project/aztec/src/sandbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,10 @@ export async function createSandbox(config: Partial<SandboxConfig> = {}) {

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,
Expand Down
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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) => {
Expand All @@ -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', () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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 {
Expand All @@ -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));
}
Expand Down Expand Up @@ -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() {
Expand Down
Original file line number Diff line number Diff line change
@@ -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}` {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we encounter something like a struct or list I would kinda expect to fail miserably. Could we specify it to explode if it ecounter tuple or tuple[] as the type.

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(),
}),
);
}
}
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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>): EpochProofQuotePayload {
return new EpochProofQuotePayload(
fields.epochToProve,
Expand Down
Original file line number Diff line number Diff line change
@@ -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';
Loading
Loading