Skip to content

Commit e2cf771

Browse files
feat: support JSON input files for bb verify command
- Add type-specific JSON structs (VkJson, ProofJson, PublicInputsJson) with named fields - Auto-detect JSON vs binary format in verify command - Update bootstrap.sh to use new field names (.vk, .proof, .public_inputs, .hash) - Suppress nlohmann/json sign-conversion warnings for WASM builds
1 parent 9aaa001 commit e2cf771

File tree

4 files changed

+227
-54
lines changed

4 files changed

+227
-54
lines changed

barretenberg/acir_tests/bootstrap.sh

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,10 @@ function run_proof_generation {
4747
dump_fail "$prove_cmd"
4848

4949
# Extract fields from JSON output (already hex-encoded with 0x prefix)
50-
local vk_fields=$(jq -c '.fields' "$outdir/vk.json")
51-
local vk_hash_field=$(jq -c '.vk_hash' "$outdir/vk.json")
52-
local public_inputs_fields=$(jq -c '.fields' "$outdir/public_inputs.json")
53-
local proof_fields=$(jq -c '.fields' "$outdir/proof.json")
50+
local vk_fields=$(jq -c '.vk' "$outdir/vk.json")
51+
local vk_hash_field=$(jq -c '.hash' "$outdir/vk.json")
52+
local public_inputs_fields=$(jq -c '.public_inputs' "$outdir/public_inputs.json")
53+
local proof_fields=$(jq -c '.proof' "$outdir/proof.json")
5454

5555
generate_toml "$program" "$vk_fields" "$vk_hash_field" "$proof_fields" "$public_inputs_fields"
5656
}

barretenberg/cpp/src/barretenberg/api/api_chonk.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ void write_chonk_vk(std::vector<uint8_t> bytecode, const std::filesystem::path&
3939
if (is_stdout) {
4040
write_bytes_to_stdout(response.bytes);
4141
} else if (flags.output_format == "json") {
42-
std::string json_content = build_json_output(response.fields, "vk", flags);
42+
// Note: Chonk VK doesn't have a hash, so we pass an empty string
43+
std::string json_content = VkJson::build(response.fields, "", flags.scheme);
4344
write_file(output_path / "vk.json", std::vector<uint8_t>(json_content.begin(), json_content.end()));
4445
info("VK (JSON) saved to ", output_path / "vk.json");
4546
} else {
@@ -80,7 +81,8 @@ void ChonkAPI::prove(const Flags& flags,
8081
write_bytes_to_stdout(to_buffer(proof_fields));
8182
} else if (flags.output_format == "json") {
8283
vinfo("writing Chonk proof (JSON) in directory ", output_dir);
83-
std::string json_content = build_json_output(proof_fields, "proof", flags);
84+
// Note: Chonk proof doesn't have a vk_hash, so we pass an empty string
85+
std::string json_content = ProofJson::build(proof_fields, "", flags.scheme);
8486
write_file(output_dir / "proof.json", std::vector<uint8_t>(json_content.begin(), json_content.end()));
8587
info("Proof (JSON) saved to ", output_dir / "proof.json");
8688
} else {

barretenberg/cpp/src/barretenberg/api/api_ultra_honk.cpp

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ void write_vk_outputs(const bbapi::CircuitComputeVk::Response& vk_response,
2727
{
2828
if (flags.output_format == "json") {
2929
std::string json_content =
30-
build_json_output(vk_response.fields, "vk", flags, bytes_to_hex_string(vk_response.hash));
30+
VkJson::build(vk_response.fields, bytes_to_hex_string(vk_response.hash), flags.scheme);
3131
write_file(output_dir / "vk.json", std::vector<uint8_t>(json_content.begin(), json_content.end()));
3232
info("VK (JSON) saved to ", output_dir / "vk.json");
3333
} else {
@@ -44,11 +44,11 @@ void write_proof_outputs(const bbapi::CircuitProve::Response& prove_response,
4444
{
4545
if (flags.output_format == "json") {
4646
std::string vk_hash = bytes_to_hex_string(prove_response.vk.hash);
47-
std::string proof_json = build_json_output(prove_response.proof, "proof", flags, vk_hash);
47+
std::string proof_json = ProofJson::build(prove_response.proof, vk_hash, flags.scheme);
4848
write_file(output_dir / "proof.json", std::vector<uint8_t>(proof_json.begin(), proof_json.end()));
4949
info("Proof (JSON) saved to ", output_dir / "proof.json");
5050

51-
std::string pi_json = build_json_output(prove_response.public_inputs, "public_inputs", flags);
51+
std::string pi_json = PublicInputsJson::build(prove_response.public_inputs, flags.scheme);
5252
write_file(output_dir / "public_inputs.json", std::vector<uint8_t>(pi_json.begin(), pi_json.end()));
5353
info("Public inputs (JSON) saved to ", output_dir / "public_inputs.json");
5454
} else {
@@ -119,10 +119,32 @@ bool UltraHonkAPI::verify(const Flags& flags,
119119
const std::filesystem::path& vk_path)
120120
{
121121
BB_BENCH_NAME("UltraHonkAPI::verify");
122-
// Read input files
123-
auto public_inputs = many_from_buffer<uint256_t>(read_file(public_inputs_path));
124-
auto proof = many_from_buffer<uint256_t>(read_file(proof_path));
125-
auto vk_bytes = read_vk_file(vk_path);
122+
123+
// Read and parse input files (auto-detect JSON vs binary format)
124+
std::vector<uint256_t> public_inputs;
125+
std::vector<uint256_t> proof;
126+
std::vector<uint8_t> vk_bytes;
127+
128+
auto public_inputs_content = read_file(public_inputs_path);
129+
if (auto json = try_parse_json(public_inputs_content)) {
130+
public_inputs = PublicInputsJson::parse(*json);
131+
} else {
132+
public_inputs = many_from_buffer<uint256_t>(public_inputs_content);
133+
}
134+
135+
auto proof_content = read_file(proof_path);
136+
if (auto json = try_parse_json(proof_content)) {
137+
proof = ProofJson::parse(*json);
138+
} else {
139+
proof = many_from_buffer<uint256_t>(proof_content);
140+
}
141+
142+
auto vk_content = read_file(vk_path);
143+
if (auto json = try_parse_json(vk_content)) {
144+
vk_bytes = VkJson::parse_to_bytes(*json);
145+
} else {
146+
vk_bytes = std::move(vk_content);
147+
}
126148

127149
// Convert flags to ProofSystemSettings
128150
bbapi::ProofSystemSettings settings{ .ipa_accumulation = flags.ipa_accumulation,
Lines changed: 190 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,27 @@
11
#pragma once
22

33
#include "barretenberg/api/api.hpp"
4+
#include "barretenberg/common/throw_or_abort.hpp"
45
#include "barretenberg/common/version.hpp"
6+
#include "barretenberg/numeric/uint256/uint256.hpp"
57
#include "barretenberg/serialize/msgpack.hpp"
68
#include "barretenberg/serialize/msgpack_impl.hpp"
79
#include <iomanip>
10+
#include <optional>
811
#include <sstream>
912
#include <string>
1013
#include <vector>
1114

15+
// nlohmann/json has sign-conversion warnings that fail with -Werror in WASM builds
16+
#ifdef __clang__
17+
#pragma clang diagnostic push
18+
#pragma clang diagnostic ignored "-Wsign-conversion"
19+
#endif
20+
#include <nlohmann/json.hpp>
21+
#ifdef __clang__
22+
#pragma clang diagnostic pop
23+
#endif
24+
1225
namespace bb {
1326

1427
/**
@@ -25,59 +38,195 @@ inline std::string bytes_to_hex_string(const std::vector<uint8_t>& bytes)
2538
}
2639

2740
/**
28-
* @brief Serializable structure for JSON output (msgpack-compatible)
41+
* @brief Try to parse file content as JSON
42+
*
43+
* @details Attempts to parse the content as JSON.
44+
* Returns nullopt if parsing fails, allowing fallback to binary format.
45+
*/
46+
inline std::optional<nlohmann::json> try_parse_json(const std::vector<uint8_t>& content)
47+
{
48+
// Quick check: JSON objects must start with '{' (after whitespace)
49+
for (const auto& byte : content) {
50+
if (std::isspace(static_cast<unsigned char>(byte)) != 0) {
51+
continue;
52+
}
53+
if (byte != '{') {
54+
return std::nullopt;
55+
}
56+
break;
57+
}
58+
59+
// Try to parse as JSON
60+
try {
61+
std::string str(content.begin(), content.end());
62+
return nlohmann::json::parse(str);
63+
} catch (...) {
64+
return std::nullopt;
65+
}
66+
}
67+
68+
/**
69+
* @brief Parse a hex string (with or without 0x prefix) to uint256_t
70+
*/
71+
inline uint256_t hex_string_to_uint256(const std::string& hex_str)
72+
{
73+
std::string str = hex_str;
74+
// Remove 0x prefix if present
75+
if (str.size() >= 2 && str[0] == '0' && (str[1] == 'x' || str[1] == 'X')) {
76+
str = str.substr(2);
77+
}
78+
// Pad to 64 characters (32 bytes) if needed
79+
if (str.size() < 64) {
80+
str.insert(0, 64 - str.size(), '0');
81+
}
82+
return uint256_t(str);
83+
}
84+
85+
/**
86+
* @brief Serializable structure for VK JSON output (msgpack-compatible)
2987
*/
30-
struct JsonOutput {
31-
std::vector<std::string> fields;
32-
std::string vk_hash; // Only for VK and proof (hash of VK the proof targets)
33-
std::string file_kind; // "vk", "proof", or "public_inputs"
88+
struct VkJson {
89+
std::vector<std::string> vk;
90+
std::string hash;
3491
std::string bb_version;
3592
std::string scheme;
36-
std::string verifier_target; // Optional
3793

38-
MSGPACK_FIELDS(fields, vk_hash, file_kind, bb_version, scheme, verifier_target);
94+
MSGPACK_FIELDS(vk, hash, bb_version, scheme);
95+
96+
template <typename T>
97+
static std::string build(const std::vector<T>& fields, const std::string& hash, const std::string& scheme)
98+
{
99+
std::vector<std::string> hex_fields;
100+
hex_fields.reserve(fields.size());
101+
for (const auto& field : fields) {
102+
std::stringstream ss;
103+
ss << field;
104+
hex_fields.push_back(ss.str());
105+
}
106+
107+
VkJson output{ .vk = std::move(hex_fields), .hash = hash, .bb_version = BB_VERSION, .scheme = scheme };
108+
109+
msgpack::sbuffer buffer;
110+
msgpack::pack(buffer, output);
111+
msgpack::object_handle oh = msgpack::unpack(buffer.data(), buffer.size());
112+
113+
std::stringstream ss;
114+
ss << oh.get();
115+
return ss.str();
116+
}
117+
118+
static std::vector<uint8_t> parse_to_bytes(const nlohmann::json& json)
119+
{
120+
if (!json.contains("vk") || !json["vk"].is_array()) {
121+
throw_or_abort("JSON missing 'vk' array");
122+
}
123+
std::vector<uint8_t> result;
124+
result.reserve(json["vk"].size() * 32);
125+
for (const auto& field : json["vk"]) {
126+
auto value = hex_string_to_uint256(field.get<std::string>());
127+
for (int i = 3; i >= 0; --i) {
128+
uint64_t limb = value.data[i];
129+
for (int j = 7; j >= 0; --j) {
130+
result.push_back(static_cast<uint8_t>((limb >> (j * 8)) & 0xFF));
131+
}
132+
}
133+
}
134+
return result;
135+
}
39136
};
40137

41138
/**
42-
* @brief Build JSON output string using msgpack serialization
43-
*
44-
* @tparam T Field element type (must have operator<< that outputs 0x-prefixed hex)
45-
* @param fields Vector of field elements to serialize
46-
* @param file_kind Type identifier: "vk", "proof", or "public_inputs"
47-
* @param flags API flags containing scheme and verifier_target
48-
* @param vk_hash Optional hash string for VK or proof files
49-
* @return JSON string
139+
* @brief Serializable structure for proof JSON output (msgpack-compatible)
50140
*/
51-
template <typename T>
52-
std::string build_json_output(const std::vector<T>& fields,
53-
const std::string& file_kind,
54-
const API::Flags& flags,
55-
const std::string& vk_hash = "")
56-
{
57-
std::vector<std::string> hex_fields;
58-
hex_fields.reserve(fields.size());
59-
for (const auto& field : fields) {
141+
struct ProofJson {
142+
std::vector<std::string> proof;
143+
std::string vk_hash;
144+
std::string bb_version;
145+
std::string scheme;
146+
147+
MSGPACK_FIELDS(proof, vk_hash, bb_version, scheme);
148+
149+
template <typename T>
150+
static std::string build(const std::vector<T>& fields, const std::string& vk_hash, const std::string& scheme)
151+
{
152+
std::vector<std::string> hex_fields;
153+
hex_fields.reserve(fields.size());
154+
for (const auto& field : fields) {
155+
std::stringstream ss;
156+
ss << field;
157+
hex_fields.push_back(ss.str());
158+
}
159+
160+
ProofJson output{
161+
.proof = std::move(hex_fields), .vk_hash = vk_hash, .bb_version = BB_VERSION, .scheme = scheme
162+
};
163+
164+
msgpack::sbuffer buffer;
165+
msgpack::pack(buffer, output);
166+
msgpack::object_handle oh = msgpack::unpack(buffer.data(), buffer.size());
167+
60168
std::stringstream ss;
61-
ss << field; // T's operator<< outputs "0x" prefix
62-
hex_fields.push_back(ss.str());
169+
ss << oh.get();
170+
return ss.str();
171+
}
172+
173+
static std::vector<uint256_t> parse(const nlohmann::json& json)
174+
{
175+
if (!json.contains("proof") || !json["proof"].is_array()) {
176+
throw_or_abort("JSON missing 'proof' array");
177+
}
178+
std::vector<uint256_t> result;
179+
result.reserve(json["proof"].size());
180+
for (const auto& field : json["proof"]) {
181+
result.push_back(hex_string_to_uint256(field.get<std::string>()));
182+
}
183+
return result;
63184
}
185+
};
186+
187+
/**
188+
* @brief Serializable structure for public inputs JSON output (msgpack-compatible)
189+
*/
190+
struct PublicInputsJson {
191+
std::vector<std::string> public_inputs;
192+
std::string bb_version;
193+
std::string scheme;
64194

65-
JsonOutput output{
66-
.fields = std::move(hex_fields),
67-
.vk_hash = vk_hash,
68-
.file_kind = file_kind,
69-
.bb_version = BB_VERSION,
70-
.scheme = flags.scheme,
71-
.verifier_target = flags.verifier_target,
72-
};
195+
MSGPACK_FIELDS(public_inputs, bb_version, scheme);
73196

74-
msgpack::sbuffer buffer;
75-
msgpack::pack(buffer, output);
76-
msgpack::object_handle oh = msgpack::unpack(buffer.data(), buffer.size());
197+
template <typename T> static std::string build(const std::vector<T>& fields, const std::string& scheme)
198+
{
199+
std::vector<std::string> hex_fields;
200+
hex_fields.reserve(fields.size());
201+
for (const auto& field : fields) {
202+
std::stringstream ss;
203+
ss << field;
204+
hex_fields.push_back(ss.str());
205+
}
77206

78-
std::stringstream ss;
79-
ss << oh.get();
80-
return ss.str();
81-
}
207+
PublicInputsJson output{ .public_inputs = std::move(hex_fields), .bb_version = BB_VERSION, .scheme = scheme };
208+
209+
msgpack::sbuffer buffer;
210+
msgpack::pack(buffer, output);
211+
msgpack::object_handle oh = msgpack::unpack(buffer.data(), buffer.size());
212+
213+
std::stringstream ss;
214+
ss << oh.get();
215+
return ss.str();
216+
}
217+
218+
static std::vector<uint256_t> parse(const nlohmann::json& json)
219+
{
220+
if (!json.contains("public_inputs") || !json["public_inputs"].is_array()) {
221+
throw_or_abort("JSON missing 'public_inputs' array");
222+
}
223+
std::vector<uint256_t> result;
224+
result.reserve(json["public_inputs"].size());
225+
for (const auto& field : json["public_inputs"]) {
226+
result.push_back(hex_string_to_uint256(field.get<std::string>()));
227+
}
228+
return result;
229+
}
230+
};
82231

83232
} // namespace bb

0 commit comments

Comments
 (0)