Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: node verifies proofs #6735

Merged
merged 1 commit into from
May 30, 2024
Merged
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
1 change: 1 addition & 0 deletions yarn-project/aztec-node/package.json
Original file line number Diff line number Diff line change
@@ -49,6 +49,7 @@
},
"dependencies": {
"@aztec/archiver": "workspace:^",
"@aztec/bb-prover": "workspace:^",
"@aztec/circuit-types": "workspace:^",
"@aztec/circuits.js": "workspace:^",
"@aztec/ethereum": "workspace:^",
58 changes: 50 additions & 8 deletions yarn-project/aztec-node/src/aztec-node/server.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { type ArchiveSource, Archiver, KVArchiverDataStore, createArchiverClient } from '@aztec/archiver';
import { BBCircuitVerifier, TestCircuitVerifier } from '@aztec/bb-prover';
import {
AggregateTxValidator,
type AztecNode,
type FromLogType,
type GetUnencryptedLogsResponse,
@@ -24,6 +26,7 @@ import {
type TxHash,
TxReceipt,
TxStatus,
type TxValidator,
partitionReverts,
} from '@aztec/circuit-types';
import {
@@ -49,7 +52,7 @@ import { AztecLmdbStore } from '@aztec/kv-store/lmdb';
import { initStoreForRollup, openTmpStore } from '@aztec/kv-store/utils';
import { SHA256Trunc, StandardTree } from '@aztec/merkle-tree';
import { AztecKVTxPool, type P2P, createP2PClient } from '@aztec/p2p';
import { DummyProver, TxProver } from '@aztec/prover-client';
import { TxProver } from '@aztec/prover-client';
import { type GlobalVariableBuilder, SequencerClient, getGlobalVariableBuilder } from '@aztec/sequencer-client';
import { PublicProcessorFactory, WASMSimulator } from '@aztec/simulator';
import {
@@ -67,13 +70,15 @@ import {

import { type AztecNodeConfig } from './config.js';
import { getSimulationProvider } from './simulator-factory.js';
import { MetadataTxValidator } from './tx_validator/tx_metadata_validator.js';
import { TxProofValidator } from './tx_validator/tx_proof_validator.js';

/**
* The aztec node.
*/
export class AztecNodeService implements AztecNode {
constructor(
protected readonly config: AztecNodeConfig,
protected config: AztecNodeConfig,
protected readonly p2pClient: P2P,
protected readonly blockSource: L2BlockSource,
protected readonly encryptedLogsSource: L2LogsSource,
@@ -86,7 +91,8 @@ export class AztecNodeService implements AztecNode {
protected readonly version: number,
protected readonly globalVariableBuilder: GlobalVariableBuilder,
protected readonly merkleTreesDb: AztecKVStore,
private readonly prover: ProverClient,
private readonly prover: ProverClient | undefined,
private txValidator: TxValidator,
private log = createDebugLogger('aztec:node'),
) {
const message =
@@ -145,9 +151,21 @@ export class AztecNodeService implements AztecNode {
// start both and wait for them to sync from the block source
await Promise.all([p2pClient.start(), worldStateSynchronizer.start()]);

const proofVerifier = config.realProofs ? await BBCircuitVerifier.new(config) : new TestCircuitVerifier();
const txValidator = new AggregateTxValidator(
new MetadataTxValidator(config.chainId),
new TxProofValidator(proofVerifier),
);

// start the prover if we have been told to
const simulationProvider = await getSimulationProvider(config, log);
const prover = config.disableProver ? await DummyProver.new() : await TxProver.new(config, worldStateSynchronizer);
const prover = config.disableProver
? undefined
: await TxProver.new(config, await proofVerifier.getVerificationKeys(), worldStateSynchronizer);

if (!prover && !config.disableSequencer) {
throw new Error("Can't start a sequencer without a prover");
}

// now create the sequencer
const sequencer = config.disableSequencer
@@ -159,7 +177,7 @@ export class AztecNodeService implements AztecNode {
archiver,
archiver,
archiver,
prover,
prover!,
simulationProvider,
);

@@ -178,6 +196,7 @@ export class AztecNodeService implements AztecNode {
getGlobalVariableBuilder(config),
store,
prover,
txValidator,
log,
);
}
@@ -190,7 +209,7 @@ export class AztecNodeService implements AztecNode {
return this.sequencer;
}

public getProver(): ProverClient {
public getProver(): ProverClient | undefined {
return this.prover;
}

@@ -292,6 +311,13 @@ export class AztecNodeService implements AztecNode {
*/
public async sendTx(tx: Tx) {
this.log.info(`Received tx ${tx.getTxHash()}`);

const [_, invalidTxs] = await this.txValidator.validateTxs([tx]);
if (invalidTxs.length > 0) {
this.log.warn(`Rejecting tx ${tx.getTxHash()} because of validation errors`);
return;
}

await this.p2pClient!.sendTx(tx);
}

@@ -327,7 +353,7 @@ export class AztecNodeService implements AztecNode {
await this.p2pClient.stop();
await this.worldStateSynchronizer.stop();
await this.blockSource.stop();
await this.prover.stop();
await this.prover?.stop();
this.log.info(`Stopped`);
}

@@ -684,8 +710,24 @@ export class AztecNodeService implements AztecNode {
}

public async setConfig(config: Partial<SequencerConfig & ProverConfig>): Promise<void> {
const newConfig = { ...this.config, ...config };
this.sequencer?.updateSequencerConfig(config);
await this.prover.updateProverConfig(config);
await this.prover?.updateProverConfig(config);

if (newConfig.realProofs !== this.config.realProofs) {
const proofVerifier = config.realProofs ? await BBCircuitVerifier.new(newConfig) : new TestCircuitVerifier();

this.txValidator = new AggregateTxValidator(
new MetadataTxValidator(this.chainId),
new TxProofValidator(proofVerifier),
);

await this.prover?.updateProverConfig({
vks: await proofVerifier.getVerificationKeys(),
});
}

this.config = newConfig;
}

/**
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { mockTx, mockTxForRollup } from '@aztec/circuit-types';
import { Fr } from '@aztec/circuits.js';

import { MetadataTxValidator } from './tx_metadata_validator.js';

describe('MetadataTxValidator', () => {
let chainId: Fr;
let validator: MetadataTxValidator;

beforeEach(() => {
chainId = new Fr(123);
validator = new MetadataTxValidator(chainId);
});

it('allows only transactions for the right chain', async () => {
const goodTxs = [mockTx(1), mockTxForRollup(2)];
const badTxs = [mockTx(3), mockTxForRollup(4)];

goodTxs.forEach(tx => {
tx.data.constants.txContext.chainId = chainId;
});

badTxs.forEach(tx => {
tx.data.constants.txContext.chainId = chainId.add(new Fr(1));
});

await expect(validator.validateTxs([...goodTxs, ...badTxs])).resolves.toEqual([goodTxs, badTxs]);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { Tx, type TxValidator } from '@aztec/circuit-types';
import { Fr } from '@aztec/circuits.js';
import { createDebugLogger } from '@aztec/foundation/log';

export class MetadataTxValidator implements TxValidator<Tx> {
#log = createDebugLogger('aztec:sequencer:tx_validator:tx_metadata');
#chainId: Fr;

constructor(chainId: number | Fr) {
this.#chainId = new Fr(chainId);
}

validateTxs(txs: Tx[]): Promise<[validTxs: Tx[], invalidTxs: Tx[]]> {
const validTxs: Tx[] = [];
const invalidTxs: Tx[] = [];
for (const tx of txs) {
if (!this.#hasCorrectChainId(tx)) {
invalidTxs.push(tx);
continue;
}

validTxs.push(tx);
}

return Promise.resolve([validTxs, invalidTxs]);
}

#hasCorrectChainId(tx: Tx): boolean {
if (!tx.data.constants.txContext.chainId.equals(this.#chainId)) {
this.#log.warn(
`Rejecting tx ${Tx.getHash(
tx,
)} because of incorrect chain ${tx.data.constants.txContext.chainId.toNumber()} != ${this.#chainId.toNumber()}`,
);
return false;
} else {
return true;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { type ClientProtocolCircuitVerifier, Tx, type TxValidator } from '@aztec/circuit-types';
import { createDebugLogger } from '@aztec/foundation/log';

export class TxProofValidator implements TxValidator<Tx> {
#log = createDebugLogger('aztec:sequencer:tx_validator:private_proof');

constructor(private verifier: ClientProtocolCircuitVerifier) {}

async validateTxs(txs: Tx[]): Promise<[validTxs: Tx[], invalidTxs: Tx[]]> {
const validTxs: Tx[] = [];
const invalidTxs: Tx[] = [];

for (const tx of txs) {
if (await this.verifier.verifyProof(tx)) {
validTxs.push(tx);
} else {
this.#log.warn(`Rejecting tx ${Tx.getHash(tx)} for invalid proof`);
invalidTxs.push(tx);
}
}

return [validTxs, invalidTxs];
}

validateTx(tx: Tx): Promise<boolean> {
return this.verifier.verifyProof(tx);
}
}
3 changes: 3 additions & 0 deletions yarn-project/aztec-node/tsconfig.json
Original file line number Diff line number Diff line change
@@ -9,6 +9,9 @@
{
"path": "../archiver"
},
{
"path": "../bb-prover"
},
{
"path": "../circuit-types"
},
2 changes: 1 addition & 1 deletion yarn-project/aztec/src/cli/cmds/start_node.ts
Original file line number Diff line number Diff line change
@@ -88,7 +88,7 @@ export const startNode = async (
services.push({ node: nodeServer });

if (!nodeConfig.disableProver) {
const provingJobSource = createProvingJobSourceServer(node.getProver().getProvingJobSource());
const provingJobSource = createProvingJobSourceServer(node.getProver()!.getProvingJobSource());
services.push({ provingJobSource });
}

1 change: 1 addition & 0 deletions yarn-project/bb-prover/src/test/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './test_circuit_prover.js';
export * from './test_verifier.js';
12 changes: 12 additions & 0 deletions yarn-project/bb-prover/src/test/test_verifier.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { type ClientProtocolCircuitVerifier, type Tx } from '@aztec/circuit-types';
import { type VerificationKeys, getMockVerificationKeys } from '@aztec/circuits.js';

export class TestCircuitVerifier implements ClientProtocolCircuitVerifier {
verifyProof(_tx: Tx): Promise<boolean> {
return Promise.resolve(true);
}

getVerificationKeys(): Promise<VerificationKeys> {
return Promise.resolve(getMockVerificationKeys());
}
}
37 changes: 34 additions & 3 deletions yarn-project/bb-prover/src/verifier/bb_verifier.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
import { type Proof, type VerificationKeyData } from '@aztec/circuits.js';
import { type ClientProtocolCircuitVerifier, Tx } from '@aztec/circuit-types';
import { type Proof, type VerificationKeyData, type VerificationKeys } from '@aztec/circuits.js';
import { runInDirectory } from '@aztec/foundation/fs';
import { type DebugLogger, type LogFn, createDebugLogger } from '@aztec/foundation/log';
import { type ProtocolArtifact, ProtocolCircuitArtifacts } from '@aztec/noir-protocol-circuits-types';
import {
type ClientProtocolArtifact,
type ProtocolArtifact,
ProtocolCircuitArtifacts,
} from '@aztec/noir-protocol-circuits-types';

import * as fs from 'fs/promises';

import { BB_RESULT, generateContractForCircuit, generateKeyForNoirCircuit, verifyProof } from '../bb/execute.js';
import { type BBConfig } from '../config.js';
import { extractVkData } from '../verification_key/verification_key_data.js';

export class BBCircuitVerifier {
export class BBCircuitVerifier implements ClientProtocolCircuitVerifier {
private constructor(
private config: BBConfig,
private verificationKeys = new Map<ProtocolArtifact, Promise<VerificationKeyData>>(),
@@ -112,4 +117,30 @@ export class BBCircuitVerifier {

return fs.readFile(result.contractPath!, 'utf-8');
}

async verifyProof(tx: Tx): Promise<boolean> {
const { proof, enqueuedPublicFunctionCalls } = tx;
const expectedCircuit: ClientProtocolArtifact =
enqueuedPublicFunctionCalls.length > 0 ? 'PrivateKernelTailToPublicArtifact' : 'PrivateKernelTailArtifact';

try {
await this.verifyProofForCircuit(expectedCircuit, proof);
return true;
} catch (err) {
this.logger.warn(`Failed to verify ${expectedCircuit} proof for tx ${Tx.getHash(tx)}: ${String(err)}`);
return false;
}
}

async getVerificationKeys(): Promise<VerificationKeys> {
const [privateKernelCircuit, privateKernelToPublicCircuit] = await Promise.all([
this.getVerificationKeyData('PrivateKernelTailArtifact'),
this.getVerificationKeyData('PrivateKernelTailToPublicArtifact'),
]);

return {
privateKernelCircuit,
privateKernelToPublicCircuit,
};
}
}
4 changes: 3 additions & 1 deletion yarn-project/circuit-types/src/interfaces/prover-client.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { type VerificationKeys } from '@aztec/circuits.js';

import { type BlockProver } from './block-prover.js';
import { type ProvingJobSource } from './proving-job.js';

@@ -28,5 +30,5 @@ export interface ProverClient extends BlockProver {

getProvingJobSource(): ProvingJobSource;

updateProverConfig(config: Partial<ProverConfig>): Promise<void>;
updateProverConfig(config: Partial<ProverConfig & { vks: VerificationKeys }>): Promise<void>;
}
19 changes: 19 additions & 0 deletions yarn-project/circuit-types/src/interfaces/server_circuit_prover.ts
Original file line number Diff line number Diff line change
@@ -2,6 +2,7 @@ import {
type PublicInputsAndProof,
type PublicKernelNonTailRequest,
type PublicKernelTailRequest,
type Tx,
} from '@aztec/circuit-types';
import {
type BaseOrMergeRollupPublicInputs,
@@ -18,6 +19,7 @@ import {
type RootParityInputs,
type RootRollupInputs,
type RootRollupPublicInputs,
type VerificationKeys,
} from '@aztec/circuits.js';

/**
@@ -104,3 +106,20 @@ export interface PublicProver {
*/
getPublicKernelCircuitProof(publicInputs: PublicKernelCircuitPublicInputs): Promise<Proof>;
}

/**
* A verifier used by nodes to check tx proofs are valid.
*/
export interface ClientProtocolCircuitVerifier {
/**
* Verifies the private protocol circuit's proof.
* @param tx - The tx to verify the proof of
* @returns True if the proof is valid, false otherwise
*/
verifyProof(tx: Tx): Promise<boolean>;

/**
* Returns the verification keys used to verify tx proofs.
*/
getVerificationKeys(): Promise<VerificationKeys>;
}
Loading