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

chore(bb): refactor bb CLI interface #1672

Merged
merged 8 commits into from
Aug 21, 2023
Merged
Show file tree
Hide file tree
Changes from 7 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
150 changes: 134 additions & 16 deletions circuits/cpp/barretenberg/cpp/src/barretenberg/bb/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,17 +36,41 @@ acir_format::acir_format get_constraint_system(std::string const& bytecode_path)
return acir_format::circuit_buf_to_acir_format(bytecode);
}

/**
* @brief Proves and Verifies an ACIR circuit
*
* Communication:
* - stdout: A boolean value is printed to stdout indicating whether the proof is valid
*
* @param bytecodePath Path to the file containing the serialized circuit
* @param witnessPath Path to the file containing the serialized witness
* @param recursive Whether to use recursive proof generation of non-recursive
* @return true if the proof is valid
* @return false if the proof is invalid
*/
bool proveAndVerify(const std::string& bytecodePath, const std::string& witnessPath, bool recursive)
{
auto acir_composer = new acir_proofs::AcirComposer(MAX_CIRCUIT_SIZE, verbose);
auto constraint_system = get_constraint_system(bytecodePath);
auto witness = get_witness(witnessPath);
auto proof = acir_composer->create_proof(srs::get_crs_factory(), constraint_system, witness, recursive);
auto verified = acir_composer->verify_proof(proof, recursive);
info("verified: ", verified);

std::cout << verified << std::endl;
return verified;
}

/**
* @brief Creates a proof for an ACIR circuit
*
* Communication:
* - stdout: The proof is written to stdout as a byte array
* - Filesystem: The proof is written to the path specified by outputPath
*
* @param bytecodePath Path to the file containing the serialized circuit
* @param witnessPath Path to the file containing the serialized witness
* @param recursive Whether to use recursive proof generation of non-recursive
* @param outputPath Path to write the proof to
*/
void prove(const std::string& bytecodePath,
const std::string& witnessPath,
bool recursive,
Expand All @@ -56,62 +80,153 @@ void prove(const std::string& bytecodePath,
auto constraint_system = get_constraint_system(bytecodePath);
auto witness = get_witness(witnessPath);
auto proof = acir_composer->create_proof(srs::get_crs_factory(), constraint_system, witness, recursive);

std::cout << proof << std::endl;
write_file(outputPath, proof);

info("proof written to: ", outputPath);
}

/**
* @brief Computes the number of Barretenberg specific gates needed to create a proof for the specific ACIR circuit
*
* Communication:
* - stdout: The number of gates is written to stdout
*
* @param bytecodePath Path to the file containing the serialized circuit
*/
void gateCount(const std::string& bytecodePath)
{
auto acir_composer = new acir_proofs::AcirComposer(MAX_CIRCUIT_SIZE, verbose);
auto constraint_system = get_constraint_system(bytecodePath);
acir_composer->create_circuit(constraint_system);
info("gates: ", acir_composer->get_total_circuit_size());
auto gate_count = acir_composer->get_total_circuit_size();
std::cout << gate_count << std::endl;
}

bool verify(const std::string& proof_path, bool recursive, const std::string& vk_path)
/**
* @brief Verifies a proof for an ACIR circuit
*
* Note: The fact that the proof was computed originally by parsing an ACIR circuit is not of importance
* because this method uses the verification key to verify the proof.
*
* Communication:
* - stdout: A boolean value is printed to stdout indicating whether the proof is valid
*
* @param proof_path Path to the file containing the serialized proof
* @param recursive Whether to use recursive proof generation of non-recursive
* @param vk_path Path to the file containing the serialized verification key
* @return true If the proof is valid
* @return false If the proof is invalid
kevaundray marked this conversation as resolved.
Show resolved Hide resolved
*/
void verify(const std::string& proof_path, bool recursive, const std::string& vk_path)
{
auto acir_composer = new acir_proofs::AcirComposer(MAX_CIRCUIT_SIZE, verbose);
auto vk_data = from_buffer<plonk::verification_key_data>(read_file(vk_path));
acir_composer->load_verification_key(barretenberg::srs::get_crs_factory(), std::move(vk_data));
auto verified = acir_composer->verify_proof(read_file(proof_path), recursive);
info("verified: ", verified);
return verified;

std::cout << verified << std::endl;
}

/**
* @brief Writes a verification key for an ACIR circuit to a file
*
* Communication:
* - stdout: The verification key is written to stdout as a byte array
* - Filesystem: The verification key is written to the path specified by outputPath
*
* @param bytecodePath Path to the file containing the serialized circuit
* @param outputPath Path to write the verification key to
*/
void writeVk(const std::string& bytecodePath, const std::string& outputPath)
{
auto acir_composer = new acir_proofs::AcirComposer(MAX_CIRCUIT_SIZE, verbose);
auto constraint_system = get_constraint_system(bytecodePath);
acir_composer->init_proving_key(srs::get_crs_factory(), constraint_system);
auto vk = acir_composer->init_verification_key();
write_file(outputPath, to_buffer(*vk));
auto serialized_vk = to_buffer(*vk);

std::cout << serialized_vk << std::endl;
write_file(outputPath, serialized_vk);

info("vk written to: ", outputPath);
}

/**
* @brief Writes a Solidity verifier contract for an ACIR circuit to a file
*
* Communication:
* - stdout: The Solidity verifier contract is written to stdout as a string
* - Filesystem: The Solidity verifier contract is written to the path specified by outputPath
*
* Note: The fact that the contract was computed is for an ACIR circuit is not of importance
* because this method uses the verification key to compute the Solidity verifier contract.
*
* @param output_path Path to write the contract to
* @param vk_path Path to the file containing the serialized verification key
*/
void contract(const std::string& output_path, const std::string& vk_path)
{
auto acir_composer = new acir_proofs::AcirComposer(MAX_CIRCUIT_SIZE, verbose);
auto vk_data = from_buffer<plonk::verification_key_data>(read_file(vk_path));
acir_composer->load_verification_key(barretenberg::srs::get_crs_factory(), std::move(vk_data));
auto contract = acir_composer->get_solidity_verifier();
if (output_path == "-") {
info(contract);
} else {
write_file(output_path, { contract.begin(), contract.end() });
info("contract written to: ", output_path);
}
}

std::cout << contract << std::endl;
write_file(output_path, { contract.begin(), contract.end() });

info("contract written to: ", output_path);
}
/**
* @brief Converts a proof from a byte array into a list of field elements
*
* Why is this needed?
*
* The proof computed by the non-recursive proof system is a byte array. This is fine since the proof will be verified
* either natively or in a Solidity verifier. For the recursive proof system, the proof is verified in a circuit where
* it is cheaper to work with field elements than byte arrays. This method converts the proof into a list of field
* elements which can be used in the recursive proof system.
*
* This is an optimization which unfortunately leaks through the API. The repercussions of this are that users need to
* convert proofs which are byte arrays to proofs which are lists of field elements, using the below method.
*
* Ideally, we find out what is the cost to convert this in the circuit and if it is not too expensive, we pass the
* byte array directly to the circuit and convert it there. This also applies to the `vkAsFields` method.
*
* Communication:
* - stdout: The proof as a list of field elements is written to stdout as a string
* - Filesystem: The proof as a list of field elements is written to the path specified by outputPath
*
*
* @param proof_path Path to the file containing the serialized proof
* @param vk_path Path to the file containing the serialized verification key
* @param output_path Path to write the proof to
*/
void proofAsFields(const std::string& proof_path, std::string const& vk_path, const std::string& output_path)
{
auto acir_composer = new acir_proofs::AcirComposer(MAX_CIRCUIT_SIZE, verbose);
auto vk_data = from_buffer<plonk::verification_key_data>(read_file(vk_path));
auto data = acir_composer->serialize_proof_into_fields(read_file(proof_path), vk_data.num_public_inputs);
auto json = format("[", join(map(data, [](auto fr) { return format("\"", fr, "\""); })), "]");

std::cout << json << std::endl;
write_file(output_path, { json.begin(), json.end() });

info("proof as fields written to: ", output_path);
}

/**
* @brief Converts a verification key from a byte array into a list of field elements
*
* Why is this needed?
* This follows the same rationale as `proofAsFields`.
*
* Communication:
* - stdout: The verification key as a list of field elements is written to stdout as a string
* - Filesystem: The verification key as a list of field elements is written to the path specified by outputPath
*
* @param vk_path Path to the file containing the serialized verification key
* @param output_path Path to write the verification key to
*/
void vkAsFields(const std::string& vk_path, const std::string& output_path)
{
auto acir_composer = new acir_proofs::AcirComposer(MAX_CIRCUIT_SIZE, verbose);
Expand All @@ -123,7 +238,10 @@ void vkAsFields(const std::string& vk_path, const std::string& output_path)
std::rotate(data.begin(), data.end() - 1, data.end());

auto json = format("[", join(map(data, [](auto fr) { return format("\"", fr, "\""); })), "]");

std::cout << json << std::endl;
write_file(output_path, { json.begin(), json.end() });

info("vk as fields written to: ", output_path);
}

Expand Down
1 change: 1 addition & 0 deletions circuits/cpp/barretenberg/ts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
"resolve-typescript-plugin": "^2.0.1",
"ts-jest": "^29.1.0",
"ts-loader": "^9.4.2",
"tslib": "^2.6.2",
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is for #1670

"tsconfig-paths-webpack-plugin": "^4.0.1",
"typescript": "^5.0.4",
"webpack": "^5.82.1",
Expand Down
44 changes: 26 additions & 18 deletions circuits/cpp/barretenberg/ts/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ export async function proveAndVerify(bytecodePath: string, witnessPath: string,

debug(`verifying...`);
const verified = await api.acirVerifyProof(acirComposer, proof, isRecursive);
console.log(`verified: ${verified}`);
process.stdout.write(`${verified}`);
TomAFrench marked this conversation as resolved.
Show resolved Hide resolved
return verified;
} finally {
await api.destroy();
Expand All @@ -107,8 +107,10 @@ export async function prove(
const proof = await api.acirCreateProof(acirComposer, bytecode, witness, isRecursive);
debug(`done.`);

process.stdout.write(proof);
writeFileSync(outputPath, proof);
console.log(`proof written to: ${outputPath}`);

debug(`proof written to: ${outputPath}`);
} finally {
await api.destroy();
}
Expand All @@ -117,7 +119,7 @@ export async function prove(
export async function gateCount(bytecodePath: string) {
const api = await newBarretenbergApiAsync(1);
try {
console.log(`gates: ${await getGates(bytecodePath, api)}`);
process.stdout.write(`${await getGates(bytecodePath, api)}`);
} finally {
await api.destroy();
}
Expand All @@ -128,7 +130,8 @@ export async function verify(proofPath: string, isRecursive: boolean, vkPath: st
try {
await api.acirLoadVerificationKey(acirComposer, new RawBuffer(readFileSync(vkPath)));
const verified = await api.acirVerifyProof(acirComposer, readFileSync(proofPath), isRecursive);
console.log(`verified: ${verified}`);

process.stdout.write(`${verified}`);
return verified;
} finally {
await api.destroy();
Expand All @@ -140,12 +143,11 @@ export async function contract(outputPath: string, vkPath: string) {
try {
await api.acirLoadVerificationKey(acirComposer, new RawBuffer(readFileSync(vkPath)));
const contract = await api.acirGetSolidityVerifier(acirComposer);
if (outputPath === '-') {
console.log(contract);
} else {
writeFileSync(outputPath, contract);
console.log(`contract written to: ${outputPath}`);
}
TomAFrench marked this conversation as resolved.
Show resolved Hide resolved

process.stdout.write(contract);
writeFileSync(outputPath, contract);

debug(`contract written to: ${outputPath}`);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

All input which is just "information" is printed to debug which should print to stderr, instead of console.log which is printing to stdout

} finally {
await api.destroy();
}
Expand All @@ -160,12 +162,11 @@ export async function writeVk(bytecodePath: string, crsPath: string, outputPath:

debug('initing verification key...');
const vk = await api.acirGetVerificationKey(acirComposer);
if (outputPath === '-') {
process.stdout.write(vk);
} else {
writeFileSync(outputPath, vk);
console.log(`vk written to: ${outputPath}`);
}

process.stdout.write(vk);
writeFileSync(outputPath, vk);

debug(`vk written to: ${outputPath}`);
} finally {
await api.destroy();
}
Expand All @@ -181,8 +182,11 @@ export async function proofAsFields(proofPath: string, numInnerPublicInputs: num
readFileSync(proofPath),
numInnerPublicInputs,
);
const jsonProofAsFields = JSON.stringify(proofAsFields.map(f => f.toString()));

process.stdout.write(jsonProofAsFields);
writeFileSync(outputPath, jsonProofAsFields);

writeFileSync(outputPath, JSON.stringify(proofAsFields.map(f => f.toString())));
debug('done.');
} finally {
await api.destroy();
Expand All @@ -197,7 +201,11 @@ export async function vkAsFields(vkPath: string, vkeyOutputPath: string) {
await api.acirLoadVerificationKey(acirComposer, new RawBuffer(readFileSync(vkPath)));
const [vkAsFields, vkHash] = await api.acirSerializeVerificationKeyIntoFields(acirComposer);
const output = [vkHash, ...vkAsFields].map(f => f.toString());
writeFileSync(vkeyOutputPath, JSON.stringify(output));
const jsonVKAsFields = JSON.stringify(output);

process.stdout.write(jsonVKAsFields);
writeFileSync(vkeyOutputPath, jsonVKAsFields);

debug('done.');
} finally {
await api.destroy();
Expand Down
8 changes: 8 additions & 0 deletions circuits/cpp/barretenberg/ts/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ __metadata:
ts-jest: ^29.1.0
ts-loader: ^9.4.2
tsconfig-paths-webpack-plugin: ^4.0.1
tslib: ^2.6.2
typescript: ^5.0.4
webpack: ^5.82.1
webpack-cli: ^5.1.1
Expand Down Expand Up @@ -6506,6 +6507,13 @@ __metadata:
languageName: node
linkType: hard

"tslib@npm:^2.6.2":
version: 2.6.2
resolution: "tslib@npm:2.6.2"
checksum: 329ea56123005922f39642318e3d1f0f8265d1e7fcb92c633e0809521da75eeaca28d2cf96d7248229deb40e5c19adf408259f4b9640afd20d13aecc1430f3ad
languageName: node
linkType: hard

"tsutils@npm:^3.21.0":
version: 3.21.0
resolution: "tsutils@npm:3.21.0"
Expand Down