Skip to content

Commit

Permalink
feat: extend epoch quote validation
Browse files Browse the repository at this point in the history
  • Loading branch information
Maddiaa0 committed Dec 18, 2024
1 parent 9f4f1e1 commit 8143000
Show file tree
Hide file tree
Showing 18 changed files with 189 additions and 46 deletions.
3 changes: 3 additions & 0 deletions yarn-project/aztec-node/src/aztec-node/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
type AztecNode,
type ClientProtocolCircuitVerifier,
type EpochProofQuote,
EpochProofQuoteHasher,
type GetUnencryptedLogsResponse,
type InBlock,
type L1ToL2MessageSource,
Expand Down Expand Up @@ -167,6 +168,7 @@ export class AztecNodeService implements AztecNode, Traceable {
log.warn(`Aztec node is accepting fake proofs`);
}

const epochProofQuoteHasher = new EpochProofQuoteHasher(config.l1Contracts.rollupAddress, 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 @@ -177,6 +179,7 @@ export class AztecNodeService implements AztecNode, Traceable {
proofVerifier,
worldStateSynchronizer,
epochCache,
epochProofQuoteHasher,
telemetry,
);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
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 { getHashedSignaturePayloadEthSignedMessage } from '../p2p/signature_utils.js';
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 +23,11 @@ describe('epoch proof quote', () => {
validUntilSlot: 100n,
});

quote = new EpochProofQuote(payload, Signature.random());
hasher = new EpochProofQuoteHasher(EthAddress.random(), 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 +38,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,13 +1,15 @@
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';

import { z } from 'zod';

import { Gossipable } from '../p2p/gossipable.js';
import { getHashedSignaturePayloadEthSignedMessage } from '../p2p/signature_utils.js';
import { TopicType, createTopicString } from '../p2p/topic_type.js';
import { EpochProofQuoteHasher } from './epoch_proof_quote_hasher.js';
import { EpochProofQuotePayload } from './epoch_proof_quote_payload.js';

export class EpochProofQuote extends Gossipable {
Expand All @@ -34,6 +36,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 +72,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,53 @@
import { Buffer32 } from '@aztec/foundation/buffer';

import { hashTypedData } from 'viem';

import { EpochProofQuotePayload, EthAddress } from './epoch_proof_quote_payload.js';

/**
* 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}`;
};
private readonly types: {
EpochProofQuote: {
name: string;
type: string;
}[];
};

constructor(rollupAddress: EthAddress, chainId: number) {
this.domain = {
name: 'Aztec Rollup',
version: '1',
chainId,
verifyingContract: rollupAddress.toString(),
};
this.types = {
EpochProofQuote: [
{ name: 'epochToProve', type: 'uint256' },
{ name: 'validUntilSlot', type: 'uint256' },
{ name: 'bondAmount', type: 'uint256' },
{ name: 'prover', type: 'address' },
{ name: 'basisPointFee', type: 'uint32' },
],
};
}

hash(payload: EpochProofQuotePayload): Buffer32 {
return Buffer32.fromString(
hashTypedData({
domain: this.domain,
types: this.types,
primaryType: 'EpochProofQuote',
message: payload.toViemArgs(),
}),
);
}
}
Original file line number Diff line number Diff line change
@@ -1,20 +1,31 @@
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, keccak256 } from 'viem';
import { parseAbiParameters } from 'viem';
import { z } from 'zod';

import { Signable } from '../p2p/index.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;

private typeHash: `0x${string}` = keccak256(
Buffer.from(
'EpochProofQuote(uint256 epochToProve,uint256 validUntilSlot,uint256 bondAmount,address prover,uint32 basisPointFee)',
),
);

constructor(
public readonly epochToProve: bigint,
public readonly validUntilSlot: bigint,
Expand Down Expand Up @@ -72,6 +83,19 @@ export class EpochProofQuotePayload {
);
}

getPayloadToSign(): Buffer {
const abi = parseAbiParameters('bytes32, uint256, uint256, uint256, address, uint256');
const encodedData = encodeAbiParameters(abi, [
this.typeHash,
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';
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
retryUntil,
sleep,
} from '@aztec/aztec.js';
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';
Expand Down Expand Up @@ -240,9 +241,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 } = ctx.aztecNodeConfig;
const hasher = new EpochProofQuoteHasher(rollupAddress, l1ChainId);
return EpochProofQuote.new(hasher, quotePayload, signer);
};

it('Sequencer selects best valid proving quote for each block', async () => {
Expand Down
3 changes: 3 additions & 0 deletions yarn-project/p2p/src/client/factory.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
type ClientProtocolCircuitVerifier,
EpochProofQuoteHasher,
type L2BlockSource,
P2PClientType,
type WorldStateSynchronizer,
Expand Down Expand Up @@ -39,6 +40,7 @@ export const createP2PClient = async <T extends P2PClientType>(
proofVerifier: ClientProtocolCircuitVerifier,
worldStateSynchronizer: WorldStateSynchronizer,
epochCache: EpochCache,
epochProofQuoteHasher: EpochProofQuoteHasher,
telemetry: TelemetryClient = new NoopTelemetryClient(),
deps: P2PClientDeps<T> = {},
) => {
Expand Down Expand Up @@ -76,6 +78,7 @@ export const createP2PClient = async <T extends P2PClientType>(
mempools,
l2BlockSource,
epochCache,
epochProofQuoteHasher,
proofVerifier,
worldStateSynchronizer,
store,
Expand Down
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -17,10 +16,11 @@ export function makeRandomEpochProofQuote(payload?: EpochProofQuotePayload): {
quote: EpochProofQuote;
signer: Secp256k1Signer;
} {
const hasher = new EpochProofQuoteHasher(EthAddress.random(), 1);
const signer = Secp256k1Signer.random();

return {
quote: EpochProofQuote.new(Buffer32.random(), payload ?? makeRandomEpochProofQuotePayload(), signer),
quote: EpochProofQuote.new(hasher, payload ?? makeRandomEpochProofQuotePayload(), signer),
signer,
};
}
3 changes: 3 additions & 0 deletions yarn-project/p2p/src/mocks/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
type ClientProtocolCircuitVerifier,
EpochProofQuoteHasher,
type L2BlockSource,
type P2PClientType,
type Tx,
Expand Down Expand Up @@ -103,6 +104,7 @@ export async function createTestLibP2PService<T extends P2PClientType>(
l2BlockSource: L2BlockSource,
worldStateSynchronizer: WorldStateSynchronizer,
epochCache: EpochCache,
epochProofQuoteHasher: EpochProofQuoteHasher,
mempools: MemPools<T>,
telemetry: TelemetryClient,
port: number = 0,
Expand Down Expand Up @@ -135,6 +137,7 @@ export async function createTestLibP2PService<T extends P2PClientType>(
mempools,
l2BlockSource,
epochCache,
epochProofQuoteHasher,
proofVerifier,
worldStateSynchronizer,
telemetry,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
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 { EthAddress } from '@aztec/foundation/eth-address';
import { Signature } from '@aztec/foundation/eth-signature';
Expand All @@ -10,10 +15,12 @@ import { EpochProofQuoteValidator } from './epoch_proof_quote_validator.js';
describe('EpochProofQuoteValidator', () => {
let epochCache: EpochCache;
let validator: EpochProofQuoteValidator;
let epochProofQuoteHasher: EpochProofQuoteHasher;

beforeEach(() => {
epochCache = mock<EpochCache>();
validator = new EpochProofQuoteValidator(epochCache);
epochProofQuoteHasher = mock<EpochProofQuoteHasher>();
validator = new EpochProofQuoteValidator(epochCache, epochProofQuoteHasher);
});

const makeEpochProofQuote = (epochToProve: bigint) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
import { type EpochProofQuote, type P2PValidator, PeerErrorSeverity } from '@aztec/circuit-types';
import {
type EpochProofQuote,
EpochProofQuoteHasher,
type P2PValidator,
PeerErrorSeverity,
} from '@aztec/circuit-types';
import { type EpochCache } from '@aztec/epoch-cache';

export class EpochProofQuoteValidator implements P2PValidator<EpochProofQuote> {
private epochCache: EpochCache;
private quoteHasher: EpochProofQuoteHasher;

constructor(epochCache: EpochCache) {
constructor(epochCache: EpochCache, quoteHasher: EpochProofQuoteHasher) {
this.epochCache = epochCache;
this.quoteHasher = quoteHasher;
}

validate(message: EpochProofQuote): Promise<PeerErrorSeverity | undefined> {
Expand All @@ -17,6 +24,12 @@ export class EpochProofQuoteValidator implements P2PValidator<EpochProofQuote> {
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);
}
}
Loading

0 comments on commit 8143000

Please sign in to comment.