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: initial integration avm prover #4878

Merged
merged 5 commits into from
Mar 11, 2024
Merged
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
88 changes: 72 additions & 16 deletions barretenberg/cpp/src/barretenberg/bb/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,70 @@ void acvm_info(const std::string& output_path)
}
}

/**
* @brief Writes an avm proof and corresponding (incomplete) verification key to files.
*
* Communication:
* - Filesystem: The proof and vk are written to the paths output_path/proof and output_path/vk
*
* @param bytecode_path Path to the file containing the serialised bytecode
* @param calldata_path Path to the file containing the serialised calldata (could be empty)
* @param crs_path Path to the file containing the CRS (ignition is suitable for now)
* @param output_path Path to write the output proof and verification key
*/
void avm_prove(const std::filesystem::path& bytecode_path,
const std::filesystem::path& calldata_path,
const std::string& crs_path,
const std::filesystem::path& output_path)
{
// Get Bytecode
std::vector<uint8_t> const avm_bytecode = read_file(bytecode_path);
std::vector<uint8_t> call_data_bytes{};
if (std::filesystem::exists(calldata_path)) {
call_data_bytes = read_file(calldata_path);
}
IlyasRidhuan marked this conversation as resolved.
Show resolved Hide resolved
std::vector<fr> const call_data = many_from_buffer<fr>(call_data_bytes);

srs::init_crs_factory(crs_path);

// Prove execution and return vk
auto const [verification_key, proof] = avm_trace::Execution::prove(avm_bytecode, call_data);
// TODO(ilyas): <#4887>: Currently we only need these two parts of the vk, look into pcs_verification key reqs
std::vector<size_t> vk_vector = { verification_key.circuit_size, verification_key.num_public_inputs };

std::filesystem::path output_vk_path = output_path.parent_path() / "vk";
write_file(output_vk_path, to_buffer(vk_vector));
write_file(output_path, to_buffer(proof));
}

/**
* @brief Verifies an avm proof and writes the result to stdout
*
* Communication:
* - stdout: The boolean value indicating whether the proof is valid.
* - proc_exit: A boolean value is returned indicating whether the proof is valid.
IlyasRidhuan marked this conversation as resolved.
Show resolved Hide resolved
*
* @param proof_path Path to the file containing the serialised proof (the vk should also be in the parent)
*/
bool avm_verify(const std::filesystem::path& proof_path)
{
std::filesystem::path vk_path = proof_path.parent_path() / "vk";

// Actual verification temporarily stopped (#4954)
// std::vector<fr> const proof = many_from_buffer<fr>(read_file(proof_path));
//
// std::vector<uint8_t> vk_bytes = read_file(vk_path);
// auto circuit_size = from_buffer<size_t>(vk_bytes, 0);
// auto _num_public_inputs = from_buffer<size_t>(vk_bytes, sizeof(size_t));
// auto vk = AvmFlavor::VerificationKey(circuit_size, num_public_inputs);
//
// std::cout << avm_trace::Execution::verify(vk, proof);
// return avm_trace::Execution::verify(vk, proof);

std::cout << 1;
return true;
}

bool flag_present(std::vector<std::string>& args, const std::string& flag)
{
return std::find(args.begin(), args.end(), flag) != args.end();
Expand Down Expand Up @@ -535,22 +599,14 @@ int main(int argc, char* argv[])
std::string output_path = get_option(args, "-o", vk_path + "_fields.json");
vk_as_fields(vk_path, output_path);
} else if (command == "avm_prove") {
IlyasRidhuan marked this conversation as resolved.
Show resolved Hide resolved
std::string avm_bytecode_path = get_option(args, "-b", "./target/avm_bytecode.bin");
std::string output_path = get_option(args, "-o", "./proofs/avm_proof");
std::vector<uint8_t> call_data_bytes{};

if (flag_present(args, "-d")) {
auto const call_data_path = get_option(args, "-d", "./target/call_data.bin");
call_data_bytes = read_file(call_data_path);
}

srs::init_crs_factory("../srs_db/ignition");

std::vector<fr> const call_data = many_from_buffer<fr>(call_data_bytes);
auto const avm_bytecode = read_file(avm_bytecode_path);
auto const proof = avm_trace::Execution::run_and_prove(avm_bytecode, call_data);
std::vector<uint8_t> const proof_bytes = to_buffer(proof);
write_file(output_path, proof_bytes);
std::filesystem::path avm_bytecode_path = get_option(args, "-b", "./target/avm_bytecode.bin");
std::filesystem::path calldata_path = get_option(args, "-d", "./target/call_data.bin");
std::string crs_path = get_option(args, "-c", "../srs_db/ignition");
std::filesystem::path output_path = get_option(args, "-o", "./proofs/avm_proof");
avm_prove(avm_bytecode_path, calldata_path, crs_path, output_path);
} else if (command == "avm_verify") {
std::filesystem::path proof_path = get_option(args, "-p", "./proofs/avm_proof");
return avm_verify(proof_path) ? 0 : 1;
IlyasRidhuan marked this conversation as resolved.
Show resolved Hide resolved
} else {
std::cerr << "Unknown command: " << command << "\n";
return 1;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -369,4 +369,4 @@ template <typename FF_> class avm_aluImpl {

template <typename FF> using avm_alu = Relation<avm_aluImpl<FF>>;

} // namespace bb::Avm_vm
} // namespace bb::Avm_vm
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
#include "avm_execution.hpp"
#include "barretenberg/common/serialize.hpp"
#include "barretenberg/flavor/generated/avm_flavor.hpp"
#include "barretenberg/proof_system/circuit_builder/generated/avm_circuit_builder.hpp"
#include "barretenberg/vm/avm_trace/avm_common.hpp"
#include "barretenberg/vm/avm_trace/avm_deserialization.hpp"
#include "barretenberg/vm/avm_trace/avm_instructions.hpp"
#include "barretenberg/vm/avm_trace/avm_opcode.hpp"
#include "barretenberg/vm/avm_trace/avm_trace.hpp"
#include "barretenberg/vm/generated/avm_composer.hpp"
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <string>
#include <tuple>
#include <variant>
#include <vector>

Expand All @@ -35,7 +38,41 @@ HonkProof Execution::run_and_prove(std::vector<uint8_t> const& bytecode, std::ve

auto composer = AvmComposer();
auto prover = composer.create_prover(circuit_builder);
return prover.construct_proof();
auto verifier = composer.create_verifier(circuit_builder);
auto proof = prover.construct_proof();
return proof;
}

std::tuple<AvmFlavor::VerificationKey, HonkProof> Execution::prove(std::vector<uint8_t> const& bytecode,
std::vector<FF> const& calldata)
{
auto instructions = Deserialization::parse(bytecode);
auto trace = gen_trace(instructions, calldata);
auto circuit_builder = bb::AvmCircuitBuilder();
circuit_builder.set_trace(std::move(trace));

// Temporarily use this until #4954 is resolved
assert(circuit_builder.check_circuit());

auto composer = AvmComposer();
auto prover = composer.create_prover(circuit_builder);
auto verifier = composer.create_verifier(circuit_builder);
auto proof = prover.construct_proof();
// TODO(#4887): Might need to return PCS vk when full verify is supported
return std::make_tuple(*verifier.key, proof);
}

bool Execution::verify(AvmFlavor::VerificationKey vk, HonkProof const& proof)
{
auto verification_key = std::make_shared<AvmFlavor::VerificationKey>(vk);
AvmVerifier verifier(verification_key);
IlyasRidhuan marked this conversation as resolved.
Show resolved Hide resolved

// todo: not needed for now until we verify the PCS/pairing of the proof
// auto pcs_verification_key = std::make_unique<VerifierCommitmentKey>(verification_key->circuit_size,
// crs_factory_);
// output_state.pcs_verification_key = std::move(pcs_verification_key);

return verifier.verify_proof(proof);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ class Execution {
static std::vector<Row> gen_trace(std::vector<Instruction> const& instructions,
std::vector<FF> const& calldata = {});
static bb::HonkProof run_and_prove(std::vector<uint8_t> const& bytecode, std::vector<FF> const& calldata = {});

static std::tuple<AvmFlavor::VerificationKey, bb::HonkProof> prove(std::vector<uint8_t> const& bytecode,
std::vector<FF> const& calldata = {});
static bool verify(AvmFlavor::VerificationKey vk, HonkProof const& proof);
};

} // namespace bb::avm_trace
} // namespace bb::avm_trace
2 changes: 2 additions & 0 deletions build_manifest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ yarn-project-test:
- noir-packages
- l1-contracts
- noir-projects
- barretenberg-x86_64-linux-clang

# Builds all of yarn-project, with all developer dependencies.
# Creates a runnable container used to run tests and formatting checks.
Expand All @@ -170,6 +171,7 @@ yarn-project:
- l1-contracts
- noir-projects
- noir
- barretenberg-x86_64-linux-clang
multiarch: host

# A runnable container, sets entrypoint to be the aztec infrastructure entrypoint.
Expand Down
1 change: 1 addition & 0 deletions yarn-project/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ node_modules
.tsbuildinfo
tsconfig.tsbuildinfo
.eslintcache
simulator/target

.yarn/*
!.yarn/patches
Expand Down
8 changes: 7 additions & 1 deletion yarn-project/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ FROM --platform=linux/amd64 aztecprotocol/bb.js as bb.js
FROM --platform=linux/amd64 aztecprotocol/noir-packages as noir-packages
FROM --platform=linux/amd64 aztecprotocol/l1-contracts as contracts
FROM --platform=linux/amd64 aztecprotocol/noir-projects as noir-projects
FROM aztecprotocol/noir as noir
FROM --platform=linux/amd64 aztecprotocol/noir as noir
FROM --platform=linux/amd64 aztecprotocol/barretenberg-x86_64-linux-clang as barretenberg

FROM node:18.19.0 as builder
RUN apt update && apt install -y jq curl perl && rm -rf /var/lib/apt/lists/* && apt-get clean
Expand All @@ -14,6 +15,11 @@ COPY --from=contracts /usr/src/l1-contracts /usr/src/l1-contracts
COPY --from=noir-projects /usr/src/noir-projects /usr/src/noir-projects
# We want the native ACVM binary
COPY --from=noir /usr/src/noir/noir-repo/target/release/acvm /usr/src/noir/noir-repo/target/release/acvm
COPY --from=barretenberg /usr/src/barretenberg/cpp/build/bin/bb /usr/src/barretenberg/cpp/build/bin/bb

COPY --from=barretenberg /usr/src/barretenberg/cpp/srs_db/ /usr/src/barretenberg/cpp/srs_db/
WORKDIR /usr/src/barretenberg/cpp/srs_db
RUN ./download_ignition.sh 0

WORKDIR /usr/src/yarn-project
COPY . .
Expand Down
8 changes: 7 additions & 1 deletion yarn-project/Dockerfile.test
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ FROM --platform=linux/amd64 aztecprotocol/bb.js as bb.js
FROM --platform=linux/amd64 aztecprotocol/noir-packages as noir-packages
FROM --platform=linux/amd64 aztecprotocol/l1-contracts as contracts
FROM --platform=linux/amd64 aztecprotocol/noir-projects as noir-projects
FROM --platform=linux/amd64 aztecprotocol/barretenberg-x86_64-linux-clang as barretenberg

FROM node:18.19.0 as builder
RUN apt update && apt install -y jq curl perl && rm -rf /var/lib/apt/lists/* && apt-get clean
Expand All @@ -11,6 +12,11 @@ COPY --from=bb.js /usr/src/barretenberg/ts /usr/src/barretenberg/ts
COPY --from=noir-packages /usr/src/noir/packages /usr/src/noir/packages
COPY --from=contracts /usr/src/l1-contracts /usr/src/l1-contracts
COPY --from=noir-projects /usr/src/noir-projects /usr/src/noir-projects
COPY --from=barretenberg /usr/src/barretenberg/cpp/build/bin/bb /usr/src/barretenberg/cpp/build/bin/bb

COPY --from=barretenberg /usr/src/barretenberg/cpp/srs_db/ /usr/src/barretenberg/cpp/srs_db/
WORKDIR /usr/src/barretenberg/cpp/srs_db
RUN ./download_ignition.sh 0

WORKDIR /usr/src/yarn-project
COPY . .
Expand All @@ -31,4 +37,4 @@ RUN yarn prepare:check && yarn formatting && yarn test

# Avoid pushing some huge container back to ecr.
FROM scratch
COPY --from=builder /usr/src/yarn-project/README.md /usr/src/yarn-project/README.md
COPY --from=builder /usr/src/yarn-project/README.md /usr/src/yarn-project/README.md
72 changes: 72 additions & 0 deletions yarn-project/simulator/src/public/avm_executor.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { AztecAddress, CallContext, EthAddress, FunctionData, FunctionSelector, Header } from '@aztec/circuits.js';
import { makeHeader } from '@aztec/circuits.js/testing';
import { Fr } from '@aztec/foundation/fields';
import { AvmTestContractArtifact } from '@aztec/noir-contracts.js';

import { MockProxy, mock } from 'jest-mock-extended';

import { CommitmentsDB, PublicContractsDB, PublicStateDB } from './db.js';
import { PublicExecution } from './execution.js';
import { PublicExecutor } from './executor.js';

describe('AVM WitGen and Proof Generation', () => {
let publicState: MockProxy<PublicStateDB>;
let publicContracts: MockProxy<PublicContractsDB>;
let commitmentsDb: MockProxy<CommitmentsDB>;
let header: Header;

const callContext = CallContext.from({
msgSender: AztecAddress.random(),
storageContractAddress: AztecAddress.random(),
portalContractAddress: EthAddress.random(),
functionSelector: FunctionSelector.empty(),
isContractDeployment: false,
isDelegateCall: false,
isStaticCall: false,
startSideEffectCounter: 0,
});
const contractAddress = AztecAddress.random();

beforeEach(() => {
publicState = mock<PublicStateDB>();
publicContracts = mock<PublicContractsDB>();
commitmentsDb = mock<CommitmentsDB>();

const randomInt = Math.floor(Math.random() * 1000000);
header = makeHeader(randomInt);
}, 10000);

it('Should prove valid execution of bytecode that performs addition', async () => {
const args: Fr[] = [new Fr(1), new Fr(2)];
// Bytecode for the following contract is encoded:
// const bytecode = encodeToBytecode([
// new CalldataCopy(/*indirect=*/ 0, /*cdOffset=*/ 0, /*copySize=*/ 2, /*dstOffset=*/ 0),
// new Add(/*indirect=*/ 0, TypeTag.FIELD, /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 2),
// new Return(/*indirect=*/ 0, /*returnOffset=*/ 2, /*copySize=*/ 1),
// ]);
const bytecode: Buffer = Buffer.from('HwAAAAAAAAAAAgAAAAAAAAYAAAAAAAAAAQAAAAI3AAAAAAIAAAAB', 'base64');
Copy link
Member

Choose a reason for hiding this comment

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

Do you think we should we create this from an opcode array then call serialize; rather than hard coding the string?

Copy link
Contributor

Choose a reason for hiding this comment

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

I would rather use HEX encoding. The goal is not to save space but make it readable for the developer.

Copy link
Contributor

Choose a reason for hiding this comment

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

Do you think we should we create this from an opcode array then call serialize; rather than hard coding the string?

He might be trying to avoid introducing a dependency to the instructions and the simulator. I think that's right because at this level of abstraction that shouldn't matter. However, having the hardcoded bytecode is also a pain if we ever change anything.

I'd suggest just removing this test and relying on the one(s) using "AvmTestContractArtifact."

Copy link
Contributor Author

@IlyasRidhuan IlyasRidhuan Mar 5, 2024

Choose a reason for hiding this comment

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

Yep i was hesitant to introduce the dependency on the simulator here.

The plan would be to use the one from the AvmTestContractArtifact when the AVM supports the MOV opcode(it's skipped below this test until then).

Once that is supported I'll happily remove this test.

In terms of the encoding, the bytecode stored in the transpiled noir contract is base64 encoded, so im keeping it consistent here with what it would receive from retrieving the contract artifact

publicContracts.getBytecode.mockResolvedValue(bytecode);
const executor = new PublicExecutor(publicState, publicContracts, commitmentsDb, header);
const functionData = FunctionData.empty();
const execution: PublicExecution = { contractAddress, functionData, args, callContext };
const [proof, vk] = await executor.getAvmProof(execution);
const valid = await executor.verifyAvmProof(vk, proof);
expect(valid).toBe(true);
});

// This is skipped as we require MOV to be implemented in the AVM
it.skip('Should prove valid execution contract function that performs addition', async () => {
const args: Fr[] = [new Fr(1), new Fr(2)];

const addArtifact = AvmTestContractArtifact.functions.find(f => f.name === 'avm_addArgsReturn')!;
const bytecode = Buffer.from(addArtifact.bytecode, 'base64');
publicContracts.getBytecode.mockResolvedValue(bytecode);
const functionData = FunctionData.fromAbi(addArtifact);
const execution: PublicExecution = { contractAddress, functionData, args, callContext };

const executor = new PublicExecutor(publicState, publicContracts, commitmentsDb, header);
const [proof, vk] = await executor.getAvmProof(execution);
const valid = await executor.verifyAvmProof(vk, proof);
expect(valid).toBe(true);
});
});
Loading
Loading