-
Notifications
You must be signed in to change notification settings - Fork 271
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(sol-honk): integrate solidity honk verifier with bb and acir tests #7573
Changes from 41 commits
fa6a841
fabd169
43cd61a
323e7b0
ff22c01
1ed41cb
78d45b0
b7881f1
a724d30
dd85f2d
538f432
654ab55
b9fe7ba
7f94eb2
b510ca8
1da70e0
6c5da89
d131397
b914b69
1443210
ecd7af6
2f9a5d0
60e2a7a
56c28fb
3f5a478
f08a0ec
9d7a651
10a8eea
b2e9eee
eaa1c0e
4d26743
d2906cf
ebf730a
f48e25b
2547a1e
9205a31
5363e3a
2ab22da
cdfe6d6
af20b64
61d2278
241508e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
#!/bin/sh | ||
set -eu | ||
|
||
export PROOF="$(pwd)/proof" | ||
export PROOF_AS_FIELDS="$(pwd)/proof_fields.json" | ||
|
||
# Create a proof, write the solidity contract, write the proof as fields in order to extract the public inputs | ||
$BIN prove_keccak_ultra_honk -o proof | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have included a second bb command that uses the UltraKeccak flavor for proving, in its current form this is not very user friendly. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🫡 |
||
$BIN write_vk_ultra_honk -o vk | ||
$BIN proof_as_fields_honk -k vk -c $CRS_PATH -p $PROOF | ||
$BIN contract_ultra_honk -k vk -c $CRS_PATH -b ./target/program.json -o Verifier.sol | ||
|
||
# Export the paths to the environment variables for the js test runner | ||
export VERIFIER_PATH="$(pwd)/Verifier.sol" | ||
export TEST_PATH=$(realpath "../../sol-test/HonkTest.sol") | ||
export TESTING_HONK="true" | ||
|
||
# Use solcjs to compile the generated key contract with the template verifier and test contract | ||
# index.js will start an anvil, on a random port | ||
# Deploy the verifier then send a test transaction | ||
export TEST_NAME=$(basename $(pwd)) | ||
node ../../sol-test/src/index.js |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
// THIS FILE WILL NOT COMPILE BY ITSELF | ||
// Compilation is handled in `src/index.js` where solcjs gathers the dependencies | ||
|
||
pragma solidity >=0.8.4; | ||
|
||
import {HonkVerifier} from "./Verifier.sol"; | ||
|
||
contract Test { | ||
HonkVerifier verifier; | ||
|
||
constructor() { | ||
verifier = new HonkVerifier(); | ||
} | ||
|
||
function test(bytes calldata proof, bytes32[] calldata publicInputs) view public returns(bool) { | ||
return verifier.verify(proof, publicInputs); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,7 +4,8 @@ import { spawn } from "child_process"; | |
import { ethers } from "ethers"; | ||
import solc from "solc"; | ||
|
||
const NUMBER_OF_FIELDS_IN_PROOF = 93; | ||
const NUMBER_OF_FIELDS_IN_PLONK_PROOF = 93; | ||
const NUMBER_OF_FIELDS_IN_HONK_PROOF = 393; | ||
|
||
// We use the solcjs compiler version in this test, although it is slower than foundry, to run the test end to end | ||
// it simplifies of parallelising the test suite | ||
|
@@ -18,6 +19,14 @@ const NUMBER_OF_FIELDS_IN_PROOF = 93; | |
// 5. Run the test against the deployed contract | ||
// 6. Kill the anvil instance | ||
|
||
const getEnvVarCanBeUndefined = (envvar) => { | ||
const varVal = process.env[envvar]; | ||
if (!varVal) { | ||
return false; | ||
} | ||
return varVal; | ||
}; | ||
|
||
const getEnvVar = (envvar) => { | ||
const varVal = process.env[envvar]; | ||
if (!varVal) { | ||
|
@@ -30,33 +39,23 @@ const getEnvVar = (envvar) => { | |
const testName = getEnvVar("TEST_NAME"); | ||
|
||
// Get solidity files, passed into environment from `flows/sol.sh` | ||
const keyPath = getEnvVar("KEY_PATH"); | ||
const verifierPath = getEnvVar("VERIFIER_PATH"); | ||
const testPath = getEnvVar("TEST_PATH"); | ||
const basePath = getEnvVar("BASE_PATH"); | ||
const verifierPath = getEnvVar("VERIFIER_PATH"); | ||
const encoding = { encoding: "utf8" }; | ||
const [key, test, verifier, base] = await Promise.all([ | ||
fsPromises.readFile(keyPath, encoding), | ||
const [test, verifier] = await Promise.all([ | ||
fsPromises.readFile(testPath, encoding), | ||
fsPromises.readFile(verifierPath, encoding), | ||
fsPromises.readFile(basePath, encoding), | ||
]); | ||
|
||
var input = { | ||
export const compilationInput = { | ||
language: "Solidity", | ||
sources: { | ||
"Key.sol": { | ||
content: key, | ||
}, | ||
"Test.sol": { | ||
content: test, | ||
}, | ||
"Verifier.sol": { | ||
content: verifier, | ||
}, | ||
"BaseUltraVerifier.sol": { | ||
content: base, | ||
}, | ||
}, | ||
settings: { | ||
// we require the optimizer | ||
|
@@ -72,7 +71,30 @@ var input = { | |
}, | ||
}; | ||
|
||
var output = JSON.parse(solc.compile(JSON.stringify(input))); | ||
// If testing honk is set, then we compile the honk test suite | ||
const testingHonk = getEnvVarCanBeUndefined("TESTING_HONK"); | ||
const NUMBER_OF_FIELDS_IN_PROOF = testingHonk ? NUMBER_OF_FIELDS_IN_HONK_PROOF : NUMBER_OF_FIELDS_IN_PLONK_PROOF; | ||
if (!testingHonk) { | ||
|
||
const keyPath = getEnvVar("KEY_PATH"); | ||
const basePath = getEnvVar("BASE_PATH"); | ||
const [key, base] = await Promise.all( | ||
[ | ||
fsPromises.readFile(keyPath, encoding), | ||
fsPromises.readFile(basePath, encoding), | ||
] | ||
); | ||
|
||
compilationInput.sources["BaseUltraVerifier.sol"] = { | ||
content: base, | ||
}; | ||
compilationInput.sources["Key.sol"] = { | ||
content: key, | ||
}; | ||
} | ||
|
||
var output = JSON.parse(solc.compile(JSON.stringify(compilationInput))); | ||
|
||
const contract = output.contracts["Test.sol"]["Test"]; | ||
const bytecode = contract.evm.bytecode.object; | ||
const abi = contract.abi; | ||
|
@@ -127,8 +149,15 @@ const readPublicInputs = (proofAsFields) => { | |
const publicInputs = []; | ||
// A proof with no public inputs is 93 fields long | ||
const numPublicInputs = proofAsFields.length - NUMBER_OF_FIELDS_IN_PROOF; | ||
let publicInputsOffset = 0; | ||
|
||
// Honk proofs contain 3 pieces of metadata before the public inputs, while plonk does not | ||
if (testingHonk) { | ||
publicInputsOffset = 3; | ||
} | ||
|
||
for (let i = 0; i < numPublicInputs; i++) { | ||
publicInputs.push(proofAsFields[i]); | ||
publicInputs.push(proofAsFields[publicInputsOffset + i]); | ||
} | ||
return [numPublicInputs, publicInputs]; | ||
}; | ||
|
@@ -179,8 +208,20 @@ try { | |
const proofPath = getEnvVar("PROOF"); | ||
const proof = readFileSync(proofPath); | ||
|
||
// Cut the number of public inputs off of the proof string | ||
const proofStr = `0x${proof.toString("hex").substring(64 * numPublicInputs)}`; | ||
// Cut the number of public inputs out of the proof string | ||
let proofStr = proof.toString("hex"); | ||
if (testingHonk) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The verifier's interface is verify(proof, publicInputs[]). However, in it's current form honk proofs have 3 elements before the public inputs, so I (and the user) will need to slice these out in order to have proofs which can be processed by the verifier. I did not want to change the serialization of the proofs in this pr, but it is something to think about ahead of the noir switch over |
||
// Cut off the serialised buffer size at start | ||
proofStr = proofStr.substring(8); | ||
// Get the part before and after the public inputs | ||
const proofStart = proofStr.slice(0, 64 * 3); | ||
const proofEnd = proofStr.substring((64 * 3) + (64 * numPublicInputs)); | ||
proofStr = proofStart + proofEnd; | ||
} else { | ||
proofStr = proofStr.substring(64 * numPublicInputs); | ||
} | ||
|
||
proofStr = "0x" + proofStr; | ||
|
||
const key = | ||
"0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,10 +3,14 @@ | |
#include "barretenberg/common/map.hpp" | ||
#include "barretenberg/common/serialize.hpp" | ||
#include "barretenberg/dsl/acir_format/acir_format.hpp" | ||
#include "barretenberg/dsl/acir_proofs/honk_contract.hpp" | ||
#include "barretenberg/honk/proof_system/types/proof.hpp" | ||
#include "barretenberg/plonk/proof_system/proving_key/serialize.hpp" | ||
#include "barretenberg/serialize/cbind.hpp" | ||
#include "barretenberg/stdlib/honk_recursion/verifier/client_ivc_recursive_verifier.hpp" | ||
#include "barretenberg/stdlib_circuit_builders/ultra_flavor.hpp" | ||
#include "barretenberg/stdlib_circuit_builders/ultra_keccak.hpp" | ||
|
||
#include <cstddef> | ||
#ifndef DISABLE_AZTEC_VM | ||
#include "barretenberg/vm/avm_trace/avm_common.hpp" | ||
|
@@ -791,6 +795,40 @@ void contract(const std::string& output_path, const std::string& vk_path) | |
} | ||
} | ||
|
||
/** | ||
* @brief Writes a Honk 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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Typo: note unclear |
||
* 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_honk(const std::string& output_path, const std::string& vk_path) | ||
{ | ||
using VerificationKey = UltraFlavor::VerificationKey; | ||
using VerifierCommitmentKey = bb::VerifierCommitmentKey<curve::BN254>; | ||
|
||
auto g2_data = get_bn254_g2_data(CRS_PATH); | ||
srs::init_crs_factory({}, g2_data); | ||
auto vk = std::make_shared<VerificationKey>(from_buffer<VerificationKey>(read_file(vk_path))); | ||
vk->pcs_verification_key = std::make_shared<VerifierCommitmentKey>(); | ||
|
||
std::string contract = get_honk_solidity_verifier(std::move(vk)); | ||
|
||
if (output_path == "-") { | ||
writeStringToStdout(contract); | ||
vinfo("contract written to stdout"); | ||
} else { | ||
write_file(output_path, { contract.begin(), contract.end() }); | ||
vinfo("contract written to: ", output_path); | ||
} | ||
} | ||
|
||
/** | ||
* @brief Converts a proof from a byte array into a list of field elements | ||
* | ||
|
@@ -1360,6 +1398,9 @@ int main(int argc, char* argv[]) | |
} else if (command == "contract") { | ||
std::string output_path = get_option(args, "-o", "./target/contract.sol"); | ||
contract(output_path, vk_path); | ||
} else if (command == "contract_ultra_honk") { | ||
std::string output_path = get_option(args, "-o", "./target/contract.sol"); | ||
contract_honk(output_path, vk_path); | ||
} else if (command == "write_vk") { | ||
std::string output_path = get_option(args, "-o", "./target/vk"); | ||
write_vk(bytecode_path, output_path); | ||
|
@@ -1390,8 +1431,13 @@ int main(int argc, char* argv[]) | |
} else if (command == "prove_ultra_honk") { | ||
std::string output_path = get_option(args, "-o", "./proofs/proof"); | ||
prove_honk<UltraFlavor>(bytecode_path, witness_path, output_path); | ||
} else if (command == "prove_keccak_ultra_honk") { | ||
std::string output_path = get_option(args, "-o", "./proofs/proof"); | ||
prove_honk<UltraKeccakFlavor>(bytecode_path, witness_path, output_path); | ||
} else if (command == "verify_ultra_honk") { | ||
return verify_honk<UltraFlavor>(proof_path, vk_path) ? 0 : 1; | ||
} else if (command == "verify_keccak_ultra_honk") { | ||
return verify_honk<UltraKeccakFlavor>(proof_path, vk_path) ? 0 : 1; | ||
} else if (command == "write_vk_ultra_honk") { | ||
std::string output_path = get_option(args, "-o", "./target/vk"); | ||
write_vk_honk<UltraFlavor>(bytecode_path, output_path); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This line is a mistake; it's running the wrong job
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thank you