-
Notifications
You must be signed in to change notification settings - Fork 292
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Introduces a `stdlib::DataBus` class to facilitate calldata/return_data reads/writes. The work also includes: - Circuit checker functionality for checking the validity of databus read gates (simply checks whether the {idx, value} pair in the gate corresponds to data in the corresponding bus vector). Closes AztecProtocol/barretenberg#842 - Tests for the stdlib DataBus (including failure tests and demonstration of check circuit) - Minor updates to the builder notion of DataBus; now an array of `BusVector`s to improve interface from stdlib
- Loading branch information
1 parent
b02d1e1
commit 633a711
Showing
10 changed files
with
352 additions
and
80 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
54 changes: 54 additions & 0 deletions
54
barretenberg/cpp/src/barretenberg/stdlib/primitives/databus/databus.cpp
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
#include "databus.hpp" | ||
#include "../circuit_builders/circuit_builders.hpp" | ||
|
||
namespace bb::stdlib { | ||
|
||
template <typename Builder> void databus<Builder>::bus_vector::set_values(const std::vector<field_pt>& entries_in) | ||
{ | ||
// Set the context from the input entries | ||
for (const auto& entry : entries_in) { | ||
if (entry.get_context() != nullptr) { | ||
context = entry.get_context(); | ||
break; | ||
} | ||
} | ||
// Enforce that builder context is known at this stage. Otherwise first read will fail if the index is a constant. | ||
ASSERT(context != nullptr); | ||
|
||
// Initialize the bus vector entries from the input entries which are un-normalized and possibly constants | ||
for (const auto& entry : entries_in) { | ||
if (entry.is_constant()) { // create a constant witness from the constant | ||
auto const_var_idx = context->put_constant_variable(entry.get_value()); | ||
entries.emplace_back(field_pt::from_witness_index(context, const_var_idx)); | ||
} else { // normalize the raw entry | ||
entries.emplace_back(entry.normalize()); | ||
} | ||
// Add the entry to the bus vector data | ||
context->append_to_bus_vector(bus_idx, entries.back().get_witness_index()); | ||
} | ||
length = entries.size(); | ||
} | ||
|
||
template <typename Builder> field_t<Builder> databus<Builder>::bus_vector::operator[](const field_pt& index) const | ||
{ | ||
// Ensure the read is valid | ||
auto raw_index = static_cast<size_t>(uint256_t(index.get_value()).data[0]); | ||
if (raw_index >= length) { | ||
context->failure("bus_vector: access out of bounds"); | ||
} | ||
|
||
// The read index must be a witness; if constant, add it as a constant variable | ||
uint32_t index_witness_idx = 0; | ||
if (index.is_constant()) { | ||
index_witness_idx = context->put_constant_variable(index.get_value()); | ||
} else { | ||
index_witness_idx = index.normalize().get_witness_index(); | ||
} | ||
|
||
// Read from the bus vector at the specified index. Creates a single read gate | ||
uint32_t output_idx = context->read_bus_vector(bus_idx, index_witness_idx); | ||
return field_pt::from_witness_index(context, output_idx); | ||
} | ||
|
||
template class databus<bb::GoblinUltraCircuitBuilder>; | ||
} // namespace bb::stdlib |
54 changes: 54 additions & 0 deletions
54
barretenberg/cpp/src/barretenberg/stdlib/primitives/databus/databus.hpp
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
#pragma once | ||
#include "../circuit_builders/circuit_builders_fwd.hpp" | ||
#include "../field/field.hpp" | ||
#include "barretenberg/stdlib_circuit_builders/databus.hpp" | ||
|
||
namespace bb::stdlib { | ||
|
||
template <typename Builder> class databus { | ||
public: | ||
databus() = default; | ||
|
||
private: | ||
class bus_vector { | ||
private: | ||
using field_pt = field_t<Builder>; | ||
|
||
public: | ||
bus_vector(const BusId bus_idx) | ||
: bus_idx(bus_idx){}; | ||
|
||
/** | ||
* @brief Set the entries of the bus vector from possibly unnormalized or constant inputs | ||
* @note A builder/context is assumed to be known at this stage, otherwise the first read will fail if index is | ||
* constant | ||
* | ||
* @tparam Builder | ||
* @param entries_in | ||
*/ | ||
void set_values(const std::vector<field_pt>& entries_in); | ||
|
||
/** | ||
* @brief Read from the bus vector with a witness index value. Creates a read gate | ||
* | ||
* @param index | ||
* @return field_pt | ||
*/ | ||
field_pt operator[](const field_pt& index) const; | ||
|
||
size_t size() const { return length; } | ||
Builder* get_context() const { return context; } | ||
|
||
private: | ||
mutable std::vector<field_pt> entries; // bus vector entries | ||
size_t length = 0; | ||
BusId bus_idx; // Idx of column in bus | ||
mutable Builder* context = nullptr; | ||
}; | ||
|
||
public: | ||
// The columns of the DataBus | ||
bus_vector calldata{ BusId::CALLDATA }; | ||
bus_vector return_data{ BusId::RETURNDATA }; | ||
}; | ||
} // namespace bb::stdlib |
137 changes: 137 additions & 0 deletions
137
barretenberg/cpp/src/barretenberg/stdlib/primitives/databus/databus.test.cpp
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
|
||
#include <gtest/gtest.h> | ||
|
||
#include "barretenberg/circuit_checker/circuit_checker.hpp" | ||
#include "barretenberg/numeric/random/engine.hpp" | ||
#include "barretenberg/stdlib_circuit_builders/goblin_ultra_circuit_builder.hpp" | ||
#include "databus.hpp" | ||
|
||
using Builder = GoblinUltraCircuitBuilder; | ||
using field_ct = stdlib::field_t<Builder>; | ||
using witness_ct = stdlib::witness_t<Builder>; | ||
using databus_ct = stdlib::databus<Builder>; | ||
|
||
namespace { | ||
auto& engine = numeric::get_debug_randomness(); | ||
} | ||
|
||
/** | ||
* @brief An expository test demonstrating the functionality of the databus in a small but representative use case | ||
* | ||
*/ | ||
TEST(Databus, CallDataAndReturnData) | ||
{ | ||
Builder builder; | ||
databus_ct databus; | ||
|
||
// The databus is advantageous in situations where we want to pass large amounts of public inputs between circuits | ||
// in a chain (like private function execution in Aztec) but where we only need to use a small subset of those | ||
// values in any given circuit. As an example of this utility, consider the case where the output (return data) is | ||
// defined by simply taking the last two elements of the input (calldata) and summing them together. We can use the | ||
// databus mechanism to establish that the return data was indeed formed in this way. | ||
|
||
// Define some bus data that conform to the pattern described above | ||
std::array<bb::fr, 4> raw_calldata_values = { 4, 5, 6, 7 }; | ||
std::array<bb::fr, 3> raw_return_data_values = { 4, 5, 13 }; // 13 = 6 + 7 | ||
|
||
// Populate the calldata in the databus | ||
std::vector<field_ct> calldata_values; | ||
for (auto& value : raw_calldata_values) { | ||
calldata_values.emplace_back(witness_ct(&builder, value)); | ||
} | ||
databus.calldata.set_values(calldata_values); | ||
|
||
// Populate the return data in the databus | ||
std::vector<field_ct> return_data_values; | ||
for (auto& value : raw_return_data_values) { | ||
return_data_values.emplace_back(witness_ct(&builder, value)); | ||
} | ||
databus.return_data.set_values(return_data_values); | ||
|
||
// Establish that the first two outputs are simply copied over from the inputs. Each 'copy' requires two read gates. | ||
field_ct idx_0(witness_ct(&builder, 0)); | ||
field_ct idx_1(witness_ct(&builder, 1)); | ||
databus.calldata[idx_0].assert_equal(databus.return_data[idx_0]); | ||
databus.calldata[idx_1].assert_equal(databus.return_data[idx_1]); | ||
|
||
// Get the last two entries in calldata and compute their sum | ||
field_ct idx_2(witness_ct(&builder, 2)); | ||
field_ct idx_3(witness_ct(&builder, 3)); | ||
// This line creates an arithmetic gate and two calldata read gates (via operator[]). | ||
field_ct sum = databus.calldata[idx_2] + databus.calldata[idx_3]; | ||
|
||
// Read the last index of the return data. (Creates a return data read gate via operator[]). | ||
field_ct idx(witness_ct(&builder, 2)); | ||
field_ct read_result = databus.return_data[idx]; | ||
|
||
// By construction, the last return data value is equal to the sum of the last two calldata values | ||
EXPECT_EQ(sum.get_value(), read_result.get_value()); | ||
|
||
// Asserting that 'sum' is equal to the read result completes the process of establishing that the corresponding | ||
// return data entry was formed correctly; 'sum' is equal to the read result (enforced via copy constraint) and the | ||
// read result is connected to the value in the databus return data column via the read gate. 'sum' is connected to | ||
// the calldata values via an arithmetic gate and the two calldata read gates. | ||
sum.assert_equal(read_result); | ||
|
||
EXPECT_TRUE(CircuitChecker::check(builder)); | ||
} | ||
|
||
/** | ||
* @brief A failure test demonstrating that trying to prove (via a databus read) that an erroneous value is present in | ||
* the databus will result in an invalid witness. | ||
* | ||
*/ | ||
TEST(Databus, BadReadFailure) | ||
{ | ||
Builder builder; | ||
databus_ct databus; | ||
|
||
// Populate return data with a single arbitrary value | ||
bb::fr actual_value = 13; | ||
databus.return_data.set_values({ witness_ct(&builder, actual_value) }); | ||
|
||
// Read the value from the return data | ||
size_t raw_idx = 0; // read at 0th index | ||
field_ct idx(witness_ct(&builder, raw_idx)); | ||
field_ct read_result = databus.return_data[idx]; | ||
|
||
// The result of the read should be as expected | ||
EXPECT_EQ(actual_value, read_result.get_value()); | ||
|
||
// Since the read gate implicitly created by using operator[] on return data is valid, the witness is valid | ||
EXPECT_TRUE(CircuitChecker::check(builder)); | ||
|
||
// Now assert that the read result is equal to some erroneous value. This effectively updates the return data read | ||
// gate to attest to the erroneous value being present at index 0 in the return data. | ||
field_ct erroneous_value(witness_ct(&builder, actual_value - 1)); | ||
erroneous_value.assert_equal(read_result); | ||
|
||
// Since the read gate is no longer valid, the circuit checker will fail | ||
EXPECT_FALSE(CircuitChecker::check(builder)); | ||
} | ||
|
||
/** | ||
* @brief A failure test demonstrating that a bad input-output 'copy' will lead to an invalid witness | ||
* | ||
*/ | ||
TEST(Databus, BadCopyFailure) | ||
{ | ||
Builder builder; | ||
databus_ct databus; | ||
|
||
// Populate calldata with a single input | ||
bb::fr input = 13; | ||
databus.calldata.set_values({ witness_ct(&builder, input) }); | ||
|
||
// Populate return data with an output different from the input | ||
bb::fr output = input - 1; | ||
databus.return_data.set_values({ witness_ct(&builder, output) }); | ||
|
||
// Attempt to attest that the calldata has been copied into the return data | ||
size_t raw_idx = 0; // read at 0th index | ||
field_ct idx(witness_ct(&builder, raw_idx)); | ||
databus.calldata[idx].assert_equal(databus.return_data[idx]); | ||
|
||
// Since the output data is not a copy of the input, the checker should fail | ||
EXPECT_FALSE(CircuitChecker::check(builder)); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.