diff --git a/crypto3/libs/blueprint/include/nil/blueprint/bbf/bbf_wrapper.hpp b/crypto3/libs/blueprint/include/nil/blueprint/bbf/bbf_wrapper.hpp index 996ae4e31..bbbfd3280 100644 --- a/crypto3/libs/blueprint/include/nil/blueprint/bbf/bbf_wrapper.hpp +++ b/crypto3/libs/blueprint/include/nil/blueprint/bbf/bbf_wrapper.hpp @@ -277,7 +277,7 @@ namespace nil { dynamic_lookup_tables = ct.get_dynamic_lookup_tables(); // compatibility layer: lookup constraint list - std::unordered_map, std::vector>>> + std::unordered_map, std::vector> lookup_constraints = ct.get_lookup_constraints(); std::set lookup_tables; for(const auto& [row_list, lookup_list] : lookup_constraints) { @@ -293,7 +293,7 @@ namespace nil { lookup_tables.insert(table_name); } std::size_t table_index = bp.get_reserved_indices().at(table_name); - lookup_gate.push_back({table_index,single_lookup_constraint.second}); + lookup_gate.push_back({table_index, single_lookup_constraint.second}); } auto iter = selector_to_index_map.find(row_list); diff --git a/crypto3/libs/blueprint/include/nil/blueprint/bbf/generic.hpp b/crypto3/libs/blueprint/include/nil/blueprint/bbf/generic.hpp index 78dc4878a..3188c7d97 100644 --- a/crypto3/libs/blueprint/include/nil/blueprint/bbf/generic.hpp +++ b/crypto3/libs/blueprint/include/nil/blueprint/bbf/generic.hpp @@ -175,96 +175,96 @@ namespace nil { template class context : public basic_context { // assignment-specific definition + public: + using TYPE = typename FieldType::value_type; + using assignment_type = assignment>; using assignment_description_type = nil::crypto3::zk::snark::plonk_table_description; using plonk_copy_constraint = crypto3::zk::snark::plonk_copy_constraint; - using lookup_constraint_type = std::pair>; + using lookup_input_constraints_type = std::vector; + using lookup_constraint_type = std::pair; using dynamic_lookup_table_container_type = std::map, row_selector<>>>; using basic_context::col_map; using basic_context::add_rows_to_description; - public: - using TYPE = typename FieldType::value_type; - using basic_context::get_col; - using basic_context::get_row; - using basic_context::is_allocated; - using basic_context::mark_allocated; - - public: - - context(assignment_type &assignment_table, std::size_t max_rows) - : basic_context(add_rows_to_description(assignment_table.get_description(), max_rows), max_rows) - , at(assignment_table) - { }; - - context(assignment_type &assignment_table, std::size_t max_rows, std::size_t row_shift) - : basic_context(add_rows_to_description(assignment_table.get_description(), max_rows), max_rows, row_shift) - , at(assignment_table) - { }; - - void allocate(TYPE &C, size_t col, size_t row, column_type t) { - if (is_allocated(col, row, t)) { - std::stringstream ss; - ss << "RE-allocation of " << t << " cell at col = " << col << ", row = " << row << ".\n"; - throw std::logic_error(ss.str()); - } - switch (t) { - // NB: we use get_col/get_row here because active area might differ - // from the entire assignment table, while col and row are intended - // to be _relative_ to the active area - case column_type::witness: at.witness(get_col(col,t), get_row(row)) = C; break; - case column_type::public_input: at.public_input(get_col(col,t), get_row(row)) = C; break; - case column_type::constant: - // constants should already be assigned at this point - if (C != at.constant(get_col(col,t), get_row(row))) { - BOOST_LOG_TRIVIAL(error) << "Constant " << C << "doesn't match previous assignment " - << at.constant(get_col(col,t), get_row(row)) << "\n"; - } - BOOST_ASSERT(C == at.constant(get_col(col,t), get_row(row))); - break; - } - mark_allocated(col, row, t); - } - - void copy_constrain(TYPE &A, TYPE &B) { - if (A != B) { - // NB: This might be an error, but we don't stop execution, - // because we want to be able to run tests-to-fail. - BOOST_LOG_TRIVIAL(warning) << "Assignment violates copy constraint (" << A << " != " << B << ")"; - } - } - void constrain(TYPE C, std::string constraint_name) { - if (C != 0) { - // NB: This might be an error, but we don't stop execution, - // because we want to be able to run tests-to-fail. - BOOST_LOG_TRIVIAL(warning) << "Assignment violates polynomial constraint " - << constraint_name << " (" << C << " != 0)"; - } - } - void lookup(std::vector &C, std::string table_name) { - // TODO: actually check membership of C in table? - } - void lookup_table(std::string name, std::vector W, std::size_t from_row, std::size_t num_rows) { - // most probably do nothing - } - context subcontext(const std::vector& W, std::size_t new_row_shift, std::size_t new_max_rows) { - context res = *this; - std::vector new_W = {}; - for(std::size_t i = 0; i < W.size(); i++) { - new_W.push_back(col_map[column_type::witness][W[i]]); - } - res.col_map[column_type::witness] = new_W; - res.row_shift += new_row_shift; - res.max_rows = new_max_rows; - res.current_row[column_type::witness] = 0; // reset to 0, because in the new column set everything is different - return res; - } - - context fresh_subcontext(const std::vector& W, std::size_t new_row_shift, std::size_t new_max_rows) { - context res = subcontext(W, new_row_shift, new_max_rows); - // TODO: Maybe we should create a fresh assignment table here? - return res; - } + using basic_context::get_col; + using basic_context::get_row; + using basic_context::is_allocated; + using basic_context::mark_allocated; + + context(assignment_type &assignment_table, std::size_t max_rows) + : basic_context(add_rows_to_description(assignment_table.get_description(), max_rows), max_rows) + , at(assignment_table) + { }; + + context(assignment_type &assignment_table, std::size_t max_rows, std::size_t row_shift) + : basic_context(add_rows_to_description(assignment_table.get_description(), max_rows), max_rows, row_shift) + , at(assignment_table) + { }; + + void allocate(TYPE &C, size_t col, size_t row, column_type t) { + if (is_allocated(col, row, t)) { + std::stringstream ss; + ss << "RE-allocation of " << t << " cell at col = " << col << ", row = " << row << ".\n"; + throw std::logic_error(ss.str()); + } + switch (t) { + // NB: we use get_col/get_row here because active area might differ + // from the entire assignment table, while col and row are intended + // to be _relative_ to the active area + case column_type::witness: at.witness(get_col(col,t), get_row(row)) = C; break; + case column_type::public_input: at.public_input(get_col(col,t), get_row(row)) = C; break; + case column_type::constant: + // constants should already be assigned at this point + if (C != at.constant(get_col(col,t), get_row(row))) { + BOOST_LOG_TRIVIAL(error) << "Constant " << C << "doesn't match previous assignment " + << at.constant(get_col(col,t), get_row(row)) << "\n"; + } + BOOST_ASSERT(C == at.constant(get_col(col,t), get_row(row))); + break; + } + mark_allocated(col, row, t); + } + + void copy_constrain(TYPE &A, TYPE &B) { + if (A != B) { + // NB: This might be an error, but we don't stop execution, + // because we want to be able to run tests-to-fail. + BOOST_LOG_TRIVIAL(warning) << "Assignment violates copy constraint (" << A << " != " << B << ")"; + } + } + void constrain(TYPE C, std::string constraint_name) { + if (C != 0) { + // NB: This might be an error, but we don't stop execution, + // because we want to be able to run tests-to-fail. + BOOST_LOG_TRIVIAL(warning) << "Assignment violates polynomial constraint " + << constraint_name << " (" << C << " != 0)"; + } + } + void lookup(std::vector &C, std::string table_name) { + // TODO: actually check membership of C in table? + } + void lookup_table(std::string name, std::vector W, std::size_t from_row, std::size_t num_rows) { + // most probably do nothing + } + context subcontext(const std::vector& W, std::size_t new_row_shift, std::size_t new_max_rows) { + context res = *this; + std::vector new_W = {}; + for(std::size_t i = 0; i < W.size(); i++) { + new_W.push_back(col_map[column_type::witness][W[i]]); + } + res.col_map[column_type::witness] = new_W; + res.row_shift += new_row_shift; + res.max_rows = new_max_rows; + res.current_row[column_type::witness] = 0; // reset to 0, because in the new column set everything is different + return res; + } + + context fresh_subcontext(const std::vector& W, std::size_t new_row_shift, std::size_t new_max_rows) { + context res = subcontext(W, new_row_shift, new_max_rows); + // TODO: Maybe we should create a fresh assignment table here? + return res; + } private: // reference to the actual assignment table @@ -273,6 +273,7 @@ namespace nil { template class context : public basic_context { // circuit-specific definition + public: using constraint_id_type = gate_id; using value_type = typename FieldType::value_type; using var = crypto3::zk::snark::plonk_variable; @@ -280,10 +281,12 @@ namespace nil { using plonk_copy_constraint = crypto3::zk::snark::plonk_copy_constraint; using constraints_container_type = std::map>>; using copy_constraints_container_type = std::vector; // TODO: maybe it's a set, not a vec? + using lookup_input_constraints_type = crypto3::zk::snark::lookup_input_constraints; using lookup_constraints_container_type = std::map, // - std::pair,row_selector<>>>; + std::pair>>; // ^^^ expressions, rows - using lookup_constraint_type = std::pair>; // NB: NOT exactly as plonk!!! + // NB: NOT exactly as plonk!!! + using lookup_constraint_type = std::pair; using dynamic_lookup_table_container_type = std::map, row_selector<>>>; // ^^^ name -> (columns, rows) using basic_context::col_map; @@ -294,472 +297,435 @@ namespace nil { using basic_context::row_shift; - public: - using TYPE = constraint_type; - using basic_context::get_col; - using basic_context::get_row; - using basic_context::is_allocated; - using basic_context::mark_allocated; - - public: - - /** - * \param[in] desc - this assignment table description actually shows the used part of the table, - * and 'max_rows' rows will be added to it. - */ - context(const assignment_description_type& desc, std::size_t max_rows) - : desc(add_rows_to_description(desc, max_rows)) - , basic_context(add_rows_to_description(desc, max_rows), max_rows) { - reset_storage(); - } - - /** - * \param[in] desc - this assignment table description actually shows the used part of the table, - * and 'max_rows' rows will be added to it. - */ - context(const assignment_description_type& desc, std::size_t max_rows, std::size_t row_shift) - : desc(add_rows_to_description(desc, max_rows)) - , basic_context(add_rows_to_description(desc, max_rows), max_rows, row_shift) { - reset_storage(); - } - - void allocate(TYPE &C, size_t col, size_t row, column_type t) { - if (is_allocated(col, row, t)) { - //BOOST_LOG_TRIVIAL(warning) << "RE-allocation of " << t << " cell at col = " << col << ", row = " << row << ".\n"; - } - if (t == column_type::constant) { - auto [has_vars, min_row, max_row] = expression_row_range_visitor::row_range(C); - if (has_vars) { - std::stringstream error; - error << "Trying to assign constraint " << C << " to constant cell!"; - throw std::invalid_argument(error.str()); - } - value_type C_val = C.evaluate(0, *constants_storage); - constants_storage->constant(get_col(col,t), get_row(row)) = C_val; // store the constant - } - var res = var(get_col(col,t), get_row(row), // get_col/get_row are active-area-aware - false, // false = use absolute cell address - static_cast(t)); - if ((C != TYPE()) && (t == column_type::witness)) { // TODO: TYPE() - is this ok? NB: we only constrain witnesses! - constrain(res - C,""); // TODO: maybe add a name for this constraint? - } - - C = res; - mark_allocated(col, row, t); - } - - void copy_constrain(TYPE &A, TYPE &B) { - auto is_var = expression_is_variable_visitor::is_var; - - if (!is_var(A) || !is_var(B)) { - BOOST_LOG_TRIVIAL(error) << "Copy constraint applied to non-variable: " << A << " = " << B << ".\n"; - } - BOOST_ASSERT(is_var(A) && is_var(B)); - - var A_var = boost::get>(A.get_expr()).get_vars()[0]; - var B_var = boost::get>(B.get_expr()).get_vars()[0]; - - if (A_var != B_var) { - copy_constraints->push_back({A_var,B_var}); + using TYPE = constraint_type; + using basic_context::get_col; + using basic_context::get_row; + using basic_context::is_allocated; + using basic_context::mark_allocated; + + /** + * \param[in] desc - this assignment table description actually shows the used part of the table, + * and 'max_rows' rows will be added to it. + */ + context(const assignment_description_type& desc, std::size_t max_rows) + : desc(add_rows_to_description(desc, max_rows)) + , basic_context(add_rows_to_description(desc, max_rows), max_rows) { + reset_storage(); + } + + /** + * \param[in] desc - this assignment table description actually shows the used part of the table, + * and 'max_rows' rows will be added to it. + */ + context(const assignment_description_type& desc, std::size_t max_rows, std::size_t row_shift) + : desc(add_rows_to_description(desc, max_rows)) + , basic_context(add_rows_to_description(desc, max_rows), max_rows, row_shift) { + reset_storage(); + } + + void allocate(TYPE &C, size_t col, size_t row, column_type t) { + if (is_allocated(col, row, t)) { + BOOST_LOG_TRIVIAL(warning) << "RE-allocation of " << t << " cell at col = " << col << ", row = " << row << ".\n"; + } + if (t == column_type::constant) { + auto [has_vars, min_row, max_row] = expression_row_range_visitor::row_range(C); + if (has_vars) { + std::stringstream error; + error << "Trying to assign constraint " << C << " to constant cell!"; + throw std::invalid_argument(error.str()); } + value_type C_val = C.evaluate(0, *constants_storage); + constants_storage->constant(get_col(col,t), get_row(row)) = C_val; // store the constant } - - bool is_absolute(TYPE C) { - return expression_relativity_check_visitor::is_absolute(C); - } - bool is_relative(TYPE C) { - return expression_relativity_check_visitor::is_relative(C); - } - TYPE relativize(TYPE C, int32_t shift) { - return expression_relativize_visitor::relativize(C, shift); + var res = var(get_col(col,t), get_row(row), // get_col/get_row are active-area-aware + false, // false = use absolute cell address + static_cast(t)); + if ((C != TYPE()) && (t == column_type::witness)) { // TODO: TYPE() - is this ok? NB: we only constrain witnesses! + constrain(res - C,""); // TODO: maybe add a name for this constraint? } - std::vector relativize(std::vector C, int32_t shift) { - std::vector res; - for(TYPE c_part : C) { - res.push_back(expression_relativize_visitor::relativize(c_part,shift)); - } - return res; - } - - void constrain(TYPE C, std::string constraint_name) { - if (!is_absolute(C)) { - std::stringstream ss; - ss << "Constraint " << C << " has relative variables, cannot constrain."; - throw std::logic_error(ss.str()); - } - auto [has_vars, min_row, max_row] = expression_row_range_visitor::row_range(C); - if (!has_vars) { - BOOST_LOG_TRIVIAL(error) << "Constraint " << C << " has no variables!\n"; - } - BOOST_ASSERT(has_vars); - if (max_row - min_row > 2) { - BOOST_LOG_TRIVIAL(warning) << "Constraint " << C << " spans over 3 rows!\n"; - } - std::size_t row = (min_row + max_row)/2; + C = res; + mark_allocated(col, row, t); + } - TYPE C_rel = relativize(C, -row); + void copy_constrain(TYPE &A, TYPE &B) { + auto is_var = expression_is_variable_visitor::is_var; - add_constraint(C_rel, row); + if (!is_var(A) || !is_var(B)) { + BOOST_LOG_TRIVIAL(error) << "Copy constraint applied to non-variable: " << A << " = " << B << ".\n"; } + BOOST_ASSERT(is_var(A) && is_var(B)); - // accesible only at GenerationStage::CONSTRAINTS ! - void relative_constrain(TYPE C_rel, std::size_t row) { - if (!is_relative(C_rel)) { - std::stringstream ss; - ss << "Constraint " << C_rel << " has absolute variables, cannot constrain."; - throw std::logic_error(ss.str()); - } - add_constraint(C_rel, get_row(row)); - } + var A_var = boost::get>(A.get_expr()).get_vars()[0]; + var B_var = boost::get>(B.get_expr()).get_vars()[0]; - void relative_constrain(TYPE C_rel, std::size_t start_row, std::size_t end_row) { - if (!is_relative(C_rel)) { - std::stringstream ss; - ss << "Constraint " << C_rel << " has absolute variables, cannot constrain."; - throw std::logic_error(ss.str()); - } - add_constraint(C_rel, get_row(start_row), get_row(end_row)); + if (A_var != B_var) { + copy_constraints->push_back({A_var,B_var}); } + } - void relative_constrain(TYPE C_rel, std::vector rows) { - if (!is_relative(C_rel)) { - std::stringstream ss; - ss << "Constraint " << C_rel << " has absolute variables, cannot constrain."; - throw std::logic_error(ss.str()); - } - add_constraint(C_rel, rows); - } - - void lookup(std::vector &C, std::string table_name) { - std::set base_rows = {}; - - // Choose the best row to relativize. Different expressions in a single lookup might accept - // up to 3 different rows for relativization. We take the intersection for all expressions in - // the constraint. - for(TYPE c_part : C) { - auto [has_vars, min_row, max_row] = expression_row_range_visitor::row_range(c_part); - if (has_vars) { // NB: not having variables seems to be ok for a part of a lookup expression - if (max_row - min_row > 2) { - BOOST_LOG_TRIVIAL(warning) << "Expression " << c_part << " in lookup constraint spans over 3 rows!\n"; - } - std::size_t row = (min_row + max_row)/2; - std::set current_base_rows = {row}; - if (max_row - min_row <= 1) { - current_base_rows.insert(row+1); - } - if (max_row == min_row) { - current_base_rows.insert(row-1); - } - if (base_rows.empty()) { - base_rows = current_base_rows; - } else { - std::set new_base_rows; - std::set_intersection(base_rows.begin(), base_rows.end(), - current_base_rows.begin(), current_base_rows.end(), - std::inserter(new_base_rows, new_base_rows.end())); - base_rows = new_base_rows; - } - } - } - if (base_rows.empty()) { - BOOST_LOG_TRIVIAL(error) << "Lookup constraint expressions have no variables or have incompatible spans!\n"; - } - BOOST_ASSERT(!base_rows.empty()); - - std::size_t row = (base_rows.size() == 3) ? *(std::next(base_rows.begin())) : *(base_rows.begin()); - std::vector res; - for(TYPE c_part : C) { - TYPE c_part_rel = expression_relativize_visitor::relativize(c_part, -row); - res.push_back(c_part_rel); - } - add_lookup_constraint(table_name, res, row); + bool is_absolute(TYPE C) { + return expression_relativity_check_visitor::is_absolute(C); + } + bool is_relative(TYPE C) { + return expression_relativity_check_visitor::is_relative(C); + } + TYPE relativize(TYPE C, int32_t shift) { + return expression_relativize_visitor::relativize(C, shift); + } + std::vector relativize(std::vector C, int32_t shift) { + std::vector res; + for(TYPE c_part : C) { + res.push_back(expression_relativize_visitor::relativize(c_part,shift)); } + return res; + } - // accesible only at GenerationStage::CONSTRAINTS ! - void relative_lookup(std::vector &C, std::string table_name, std::size_t row) { - for(const TYPE c_part : C) { - if (!is_relative(c_part)) { - std::stringstream ss; - ss << "Constraint " << c_part << " has absolute variables, cannot constrain."; - throw std::logic_error(ss.str()); - } - } - add_lookup_constraint(table_name, C, row); + void constrain(TYPE C, std::string constraint_name) { + if (!is_absolute(C)) { + std::stringstream ss; + ss << "Constraint " << C << " has relative variables, cannot constrain."; + throw std::logic_error(ss.str()); } - // accesible only at GenerationStage::CONSTRAINTS ! - void relative_lookup(std::vector &C, std::string table_name, std::size_t start_row, std::size_t end_row) { - for(const TYPE c_part : C) { - if (!is_relative(c_part)) { - std::stringstream ss; - ss << "Constraint " << c_part << " has absolute variables, cannot constrain."; - throw std::logic_error(ss.str()); - } - } - add_lookup_constraint(table_name, C, start_row, end_row); + auto [has_vars, min_row, max_row] = expression_row_range_visitor::row_range(C); + if (!has_vars) { + BOOST_LOG_TRIVIAL(error) << "Constraint " << C << " has no variables!\n"; } - - // accesible only at GenerationStage::CONSTRAINTS ! - void relative_lookup(std::vector &C, std::string table_name, std::vector rows) { - for(const TYPE c_part : C) { - if (!is_relative(c_part)) { - std::stringstream ss; - ss << "Constraint " << c_part << " has absolute variables, cannot constrain."; - throw std::logic_error(ss.str()); - } - } - add_lookup_constraint(table_name, C, rows); + BOOST_ASSERT(has_vars); + if (max_row - min_row > 2) { + BOOST_LOG_TRIVIAL(warning) << "Constraint " << C << " spans over 3 rows!\n"; } + std::size_t row = (min_row + max_row)/2; - void lookup_table(std::string name, std::vector W, std::size_t from_row, std::size_t num_rows) { - if (lookup_tables->find(name) != lookup_tables->end()) { - BOOST_LOG_TRIVIAL(error) << "Double declaration of dynamic lookup table '" << name << "'!\n"; - } - BOOST_ASSERT(lookup_tables->find(name) == lookup_tables->end()); - std::vector cols; - row_selector<> rows(desc.rows_amount); + TYPE C_rel = relativize(C, -row); - for(std::size_t i = 0; i < W.size(); i++) { - cols.push_back(col_map[column_type::witness][W[i]]); - } - for(std::size_t i = 0; i < num_rows; i++) { - rows.set_row(get_row(from_row + i)); // store absolute row numbers - } + add_constraint(C_rel, row); + } - lookup_tables->insert({name,{cols,rows}}); + // accesible only at GenerationStage::CONSTRAINTS ! + void relative_constrain(TYPE C_rel, std::size_t row) { + if (!is_relative(C_rel)) { + std::stringstream ss; + ss << "Constraint " << C_rel << " has absolute variables, cannot constrain."; + throw std::logic_error(ss.str()); } + add_constraint(C_rel, get_row(row)); + } - void optimize_gates() { - // NB: std::map>> constraints; - // intended to - // shift some of the constraints so that we have less selectors - /* - for(const auto& [id, data] : *constraints) { - std::cout << "Constraint: " << data.first << "\n"; - for(std::size_t row : data.second) { - std::cout << row << " "; - } - std::cout << "\n"; - } - */ + void relative_constrain(TYPE C_rel, std::size_t start_row, std::size_t end_row) { + if (!is_relative(C_rel)) { + std::stringstream ss; + ss << "Constraint " << C_rel << " has absolute variables, cannot constrain."; + throw std::logic_error(ss.str()); } + add_constraint(C_rel, get_row(start_row), get_row(end_row)); + } + - std::unordered_map, std::vector> get_constraints() { - // joins constraints with identic selectors into a single gate + void lookup(std::vector &C, std::string table_name) { + std::set base_rows = {}; - // drop the constraint_id from the stored id->(constraint,row_list) map and - // join constrains into single element if they have the same row list: - std::unordered_map, std::vector> res; - for(const auto& [id, data] : *constraints) { - auto it = res.find(data.second); - if (it == res.end()) { - res[data.second] = {data.first}; + // Choose the best row to relativize. Different expressions in a single lookup might accept + // up to 3 different rows for relativization. We take the intersection for all expressions in + // the constraint. + for(TYPE c_part : C) { + auto [has_vars, min_row, max_row] = expression_row_range_visitor::row_range(c_part); + if (has_vars) { // NB: not having variables seems to be ok for a part of a lookup expression + if (max_row - min_row > 2) { + BOOST_LOG_TRIVIAL(warning) << "Expression " << c_part << " in lookup constraint spans over 3 rows!\n"; + } + std::size_t row = (min_row + max_row)/2; + std::set current_base_rows = {row}; + if (max_row - min_row <= 1) { + current_base_rows.insert(row+1); + } + if (max_row == min_row) { + current_base_rows.insert(row-1); + } + if (base_rows.empty()) { + base_rows = current_base_rows; } else { - it->second.push_back(data.first); + std::set new_base_rows; + std::set_intersection(base_rows.begin(), base_rows.end(), + current_base_rows.begin(), current_base_rows.end(), + std::inserter(new_base_rows, new_base_rows.end())); + base_rows = new_base_rows; } } - return res; } - - std::vector& get_copy_constraints() { - return *copy_constraints; + if (base_rows.empty()) { + BOOST_LOG_TRIVIAL(error) << "Lookup constraint expressions have no variables or have incompatible spans!\n"; } + BOOST_ASSERT(!base_rows.empty()); - dynamic_lookup_table_container_type& get_dynamic_lookup_tables() { - return *lookup_tables; + std::size_t row = (base_rows.size() == 3) ? *(std::next(base_rows.begin())) : *(base_rows.begin()); + std::vector res; + for(TYPE c_part : C) { + TYPE c_part_rel = expression_relativize_visitor::relativize(c_part, -row); + res.push_back(c_part_rel); } + add_lookup_constraint(table_name, res, row); + } - std::unordered_map, std::vector> get_lookup_constraints() { - // std::map, // - // std::pair,row_selector<>>> // expressions, rows - - std::unordered_map, std::vector> res; - for(const auto& [id, data] : *lookup_constraints) { - auto it = res.find(data.second); - if (it == res.end()) { - res[data.second] = {{id.first, data.first}}; - } else { - it->second.push_back({id.first, data.first}); - } - } - - /* - for(const auto& [lcs, rows] : res) { - for(const auto& [table, cs] : lcs) { - std::cout << "Table " << table << ": "; - for(const auto& c : cs) { std::cout << c << ", "; } - } - std::cout << "Rows: "; - for(const auto& r : rows) { std::cout << r << " "; } - std::cout << "\n"; + // accesible only at GenerationStage::CONSTRAINTS ! + void relative_lookup(std::vector &C, std::string table_name, std::size_t row) { + for(const TYPE c_part : C) { + if (!is_relative(c_part)) { + std::stringstream ss; + ss << "Constraint " << c_part << " has absolute variables, cannot constrain."; + throw std::logic_error(ss.str()); } - */ - return res; } + add_lookup_constraint(table_name, C, row); + } - context subcontext(const std::vector& W, std::size_t new_row_shift, std::size_t new_max_rows) { - context res = *this; - std::vector new_W = {}; - for(std::size_t i = 0; i < W.size(); i++) { - new_W.push_back(col_map[column_type::witness][W[i]]); + void relative_lookup(std::vector &C, std::string table_name, std::size_t start_row, std::size_t end_row) { + for(const TYPE c_part : C) { + if (!is_relative(c_part)) { + std::stringstream ss; + ss << "Constraint " << c_part << " has absolute variables, cannot constrain."; + throw std::logic_error(ss.str()); } - res.col_map[column_type::witness] = new_W; - res.row_shift += new_row_shift; - res.max_rows = new_max_rows; - res.current_row[column_type::witness] = 0; // reset to 0, because in the new column set everything is different - return res; } + add_lookup_constraint(table_name, C, start_row, end_row); + } - void reset_storage() { - constraints = std::make_shared(); - copy_constraints = std::make_shared(); - lookup_constraints = std::make_shared(); - lookup_tables = std::make_shared(); - constants_storage = std::make_shared(0, 0, desc.constant_columns, 0); - is_fresh = false; + void lookup_table(std::string name, std::vector W, std::size_t from_row, std::size_t num_rows) { + if (lookup_tables->find(name) != lookup_tables->end()) { + BOOST_LOG_TRIVIAL(error) << "Double declaration of dynamic lookup table '" << name << "'!\n"; } + BOOST_ASSERT(lookup_tables->find(name) == lookup_tables->end()); + std::vector cols; + row_selector<> rows(desc.rows_amount); - auto get_constants() { - return constants_storage->constants(); + for(std::size_t i = 0; i < W.size(); i++) { + cols.push_back(col_map[column_type::witness][W[i]]); } - - // This one will create its own set of constraint storages. - context fresh_subcontext(const std::vector& W, std::size_t new_row_shift, std::size_t new_max_rows) { - context res = subcontext(W, new_row_shift, new_max_rows); - res.reset_storage(); - is_fresh = true; - return res; + for(std::size_t i = 0; i < num_rows; i++) { + rows.set_row(get_row(from_row + i)); // store absolute row numbers } + lookup_tables->insert({name,{cols,rows}}); + } - private: - - void add_constraint(TYPE &C_rel, std::size_t row) { - std::size_t stored_row = row - (is_fresh ? row_shift : 0); - constraint_id_type C_id = constraint_id_type(C_rel); - if (constraints->find(C_id) == constraints->end()) { - constraints->insert({C_id, {C_rel, row_selector<>(desc.rows_amount)}}); + void optimize_gates() { + // NB: std::map>> constraints; + // intended to + // shift some of the constraints so that we have less selectors + /* + for(const auto& [id, data] : *constraints) { + std::cout << "Constraint: " << data.first << "\n"; + for(std::size_t row : data.second) { + std::cout << row << " "; } - constraints->at(C_id).second.set_row(stored_row); + std::cout << "\n"; } + */ + } - void add_constraint(TYPE &C_rel, std::size_t start_row, std::size_t end_row) { - std::size_t stored_start_row = start_row - (is_fresh ? row_shift : 0); - std::size_t stored_end_row = end_row - (is_fresh ? row_shift : 0); - constraint_id_type C_id = constraint_id_type(C_rel); - if (constraints->find(C_id) == constraints->end()) { - constraints->insert({C_id, {C_rel, row_selector<>(desc.rows_amount)}}); - } - constraints->at(C_id).second.set_interval(stored_start_row, stored_end_row); - } + std::unordered_map, std::vector> get_constraints() { + // joins constraints with identic selectors into a single gate - void add_constraint(TYPE &C_rel, std::vector rows) { - constraint_id_type C_id = constraint_id_type(C_rel); - if (constraints->find(C_id) == constraints->end()) { - constraints->insert({C_id, {C_rel, row_selector<>(desc.rows_amount)}}); - } - for( auto row: rows ){ - constraints->at(C_id).second.set_row(get_row(row) - (is_fresh ? row_shift : 0)); + // drop the constraint_id from the stored id->(constraint,row_list) map and + // join constrains into single element if they have the same row list: + std::unordered_map, std::vector> res; + for(const auto& [id, data] : *constraints) { + auto it = res.find(data.second); + if (it == res.end()) { + res[data.second] = {data.first}; + } else { + it->second.push_back(data.first); } } + return res; + } - void add_lookup_constraint(std::string table_name, std::vector &C_rel, std::size_t row) { - std::size_t stored_row = row - (is_fresh ? row_shift : 0); - constraint_id_type C_id = constraint_id_type(C_rel); - if (lookup_constraints->find({table_name,C_id}) == lookup_constraints->end()) { - lookup_constraints->insert({{table_name,C_id}, {C_rel, row_selector<>(desc.rows_amount)}}); - } - lookup_constraints->at({table_name,C_id}).second.set_row(stored_row); - } + std::vector& get_copy_constraints() { + return *copy_constraints; + } - void add_lookup_constraint(std::string table_name, std::vector &C_rel, std::size_t start_row, std::size_t end_row) { - std::size_t stored_start_row = start_row - (is_fresh ? row_shift : 0); - std::size_t stored_end_row = end_row - (is_fresh ? row_shift : 0); - constraint_id_type C_id = constraint_id_type(C_rel); - if (lookup_constraints->find({table_name,C_id}) == lookup_constraints->end()) { - lookup_constraints->insert({{table_name,C_id}, {C_rel, row_selector<>(desc.rows_amount)}}); - } - lookup_constraints->at({table_name,C_id}).second.set_interval(stored_start_row, stored_end_row); + dynamic_lookup_table_container_type& get_dynamic_lookup_tables() { + return *lookup_tables; + } + + std::unordered_map, std::vector> get_lookup_constraints() { + // std::map, // + // std::pair,row_selector<>>> // expressions, rows + + std::unordered_map, std::vector> res; + for(const auto& [id, data] : *lookup_constraints) { + auto it = res.find(data.second); + if (it == res.end()) { + res[data.second] = {{id.first, data.first}}; + } else { + it->second.push_back({id.first, data.first}); + } } - void add_lookup_constraint(std::string table_name, std::vector &C_rel, std::vector rows) { - constraint_id_type C_id = constraint_id_type(C_rel); - if (lookup_constraints->find({table_name,C_id}) == lookup_constraints->end()) { - lookup_constraints->insert({{table_name,C_id}, {C_rel, row_selector<>(desc.rows_amount)}}); - } - for( auto row: rows ){ - lookup_constraints->at({table_name,C_id}).second.set_row(row - (is_fresh ? row_shift : 0)); + /* + for(const auto& [lcs, rows] : res) { + for(const auto& [table, cs] : lcs) { + std::cout << "Table " << table << ": "; + for(const auto& c : cs) { std::cout << c << ", "; } } + std::cout << "Rows: "; + for(const auto& r : rows) { std::cout << r << " "; } + std::cout << "\n"; } - - // Assignment description will be used when resetting the context. - assignment_description_type desc; - - // constraints (with unique id), and the rows they are applied to - std::shared_ptr constraints; - // copy constraints as in BP - std::shared_ptr copy_constraints; - // lookup constraints with table name, unique id and row list - std::shared_ptr lookup_constraints; - // dynamic lookup tables - std::shared_ptr lookup_tables; - // constants - std::shared_ptr constants_storage; - // are we in a fresh context or not - bool is_fresh; + */ + return res; + } + + context subcontext(const std::vector& W, std::size_t new_row_shift, std::size_t new_max_rows) { + context res = *this; + std::vector new_W = {}; + for(std::size_t i = 0; i < W.size(); i++) { + new_W.push_back(col_map[column_type::witness][W[i]]); + } + res.col_map[column_type::witness] = new_W; + res.row_shift += new_row_shift; + res.max_rows = new_max_rows; + res.current_row[column_type::witness] = 0; // reset to 0, because in the new column set everything is different + return res; + } + + void reset_storage() { + constraints = std::make_shared(); + copy_constraints = std::make_shared(); + lookup_constraints = std::make_shared(); + lookup_tables = std::make_shared(); + constants_storage = std::make_shared(0, 0, desc.constant_columns, 0); + is_fresh = false; + } + + auto get_constants() { + return constants_storage->constants(); + } + + // This one will create its own set of constraint storages. + context fresh_subcontext(const std::vector& W, std::size_t new_row_shift, std::size_t new_max_rows) { + context res = subcontext(W, new_row_shift, new_max_rows); + res.reset_storage(); + is_fresh = true; + return res; + } + + private: + void add_constraint(TYPE &C_rel, std::size_t row) { + std::size_t stored_row = row - (is_fresh ? row_shift : 0); + constraint_id_type C_id = constraint_id_type(C_rel); + if (constraints->find(C_id) == constraints->end()) { + constraints->insert({C_id, {C_rel, row_selector<>(desc.rows_amount)}}); + } + constraints->at(C_id).second.set_row(stored_row); + } + + void add_constraint(TYPE &C_rel, std::size_t start_row, std::size_t end_row) { + std::size_t stored_start_row = start_row - (is_fresh ? row_shift : 0); + std::size_t stored_end_row = end_row - (is_fresh ? row_shift : 0); + constraint_id_type C_id = constraint_id_type(C_rel); + if (constraints->find(C_id) == constraints->end()) { + constraints->insert({C_id, {C_rel, row_selector<>(desc.rows_amount)}}); + } + constraints->at(C_id).second.set_interval(stored_start_row, stored_end_row); + } + + void add_lookup_constraint( + std::string table_name, const lookup_input_constraints_type &C_rel, std::size_t row) { + std::size_t stored_row = row - (is_fresh ? row_shift : 0); + constraint_id_type C_id = constraint_id_type(C_rel); + std::pair key = {table_name, C_id}; + if (lookup_constraints->find(key) == lookup_constraints->end()) { + lookup_constraints->insert({ + key, + {C_rel, row_selector<>(desc.rows_amount)} + }); + } + lookup_constraints->at(key).second.set_row(stored_row); + } + + void add_lookup_constraint(std::string table_name, std::vector &C_rel, std::size_t start_row, std::size_t end_row) { + std::size_t stored_start_row = start_row - (is_fresh ? row_shift : 0); + std::size_t stored_end_row = end_row - (is_fresh ? row_shift : 0); + constraint_id_type C_id = constraint_id_type(C_rel); + if (lookup_constraints->find({table_name,C_id}) == lookup_constraints->end()) { + lookup_constraints->insert({{table_name,C_id}, {lookup_input_constraints_type(C_rel), row_selector<>(desc.rows_amount)}}); + } + lookup_constraints->at({table_name,C_id}).second.set_interval(stored_start_row, stored_end_row); + } + + void add_lookup_constraint(std::string table_name, std::vector &C_rel, std::size_t row) { + add_lookup_constraint(table_name, lookup_input_constraints_type(C_rel), row); + } + + // Assignment description will be used when resetting the context. + assignment_description_type desc; + + // constraints (with unique id), and the rows they are applied to + std::shared_ptr constraints; + // copy constraints as in BP + std::shared_ptr copy_constraints; + // lookup constraints with table name, unique id and row list + std::shared_ptr lookup_constraints; + // dynamic lookup tables + std::shared_ptr lookup_tables; + // constants + std::shared_ptr constants_storage; + // are we in a fresh context or not + bool is_fresh; }; template class generic_component { - public: - using TYPE = typename std::conditional(stage), - crypto3::zk::snark::plonk_constraint, - typename FieldType::value_type>::type; - using context_type = context; - using plonk_copy_constraint = crypto3::zk::snark::plonk_copy_constraint; + public: + using TYPE = typename std::conditional(stage), + crypto3::zk::snark::plonk_constraint, + typename FieldType::value_type>::type; + using context_type = context; + using plonk_copy_constraint = crypto3::zk::snark::plonk_copy_constraint; - private: - context_type &ct; + private: + context_type &ct; - public: - void allocate(TYPE &C, column_type t = column_type::witness) { - auto [col, row] = ct.next_free_cell(t); - ct.allocate(C, col, row, t); - } + public: + void allocate(TYPE &C, column_type t = column_type::witness) { + auto [col, row] = ct.next_free_cell(t); + ct.allocate(C, col, row, t); + } - void allocate(TYPE &C, size_t col, size_t row, column_type t = column_type::witness) { - ct.allocate(C,col,row,t); - } + void allocate(TYPE &C, size_t col, size_t row, column_type t = column_type::witness) { + ct.allocate(C,col,row,t); + } - void copy_constrain(TYPE &A, TYPE &B) { - ct.copy_constrain(A,B); - } + void copy_constrain(TYPE &A, TYPE &B) { + ct.copy_constrain(A,B); + } - void constrain(TYPE C, std::string constraint_name = "") { - ct.constrain(C, constraint_name); - } + void constrain(TYPE C, std::string constraint_name = "") { + ct.constrain(C, constraint_name); + } - void lookup(std::vector C, std::string table_name) { - ct.lookup(C,table_name); - } + void lookup(std::vector C, std::string table_name) { + ct.lookup(C,table_name); + } - void lookup(TYPE C, std::string table_name) { - std::vector input = {C}; - ct.lookup(input,table_name); - } + void lookup(TYPE C, std::string table_name) { + std::vector input = {C}; + ct.lookup(input,table_name); + } - void lookup_table(std::string name, std::vector W, std::size_t from_row, std::size_t num_rows) { - ct.lookup_table(name,W,from_row,num_rows); - } + void lookup_table(std::string name, std::vector W, std::size_t from_row, std::size_t num_rows) { + ct.lookup_table(name,W,from_row,num_rows); + } - generic_component(context_type &context_object, // context object, created outside - bool crlf = true // do we assure a component starts on a new row? Default is "yes" - ) : ct(context_object) { - if (crlf) { // TODO: Implement crlf parameter consequences - ct.new_line(column_type::witness); - } - }; + generic_component(context_type &context_object, // context object, created outside + bool crlf = true // do we assure a component starts on a new row? Default is "yes" + ) : ct(context_object) { + if (crlf) { // TODO: Implement crlf parameter consequences + ct.new_line(column_type::witness); + } + }; }; } // namespace bbf diff --git a/crypto3/libs/blueprint/include/nil/blueprint/bbf/l1_wrapper.hpp b/crypto3/libs/blueprint/include/nil/blueprint/bbf/l1_wrapper.hpp index efc1d7120..cdf14e1b3 100644 --- a/crypto3/libs/blueprint/include/nil/blueprint/bbf/l1_wrapper.hpp +++ b/crypto3/libs/blueprint/include/nil/blueprint/bbf/l1_wrapper.hpp @@ -240,8 +240,7 @@ namespace nil { dynamic_lookup_tables = ct.get_dynamic_lookup_tables(); // compatibility layer: lookup constraint list - std::unordered_map, std::vector>>> - lookup_constraints = ct.get_lookup_constraints(); + auto lookup_constraints = ct.get_lookup_constraints(); std::set lookup_tables; for(const auto& [row_list, lookup_list] : lookup_constraints) { std::vector lookup_gate; diff --git a/crypto3/libs/blueprint/test/CMakeLists.txt b/crypto3/libs/blueprint/test/CMakeLists.txt index 2898afd31..da8e7b13c 100644 --- a/crypto3/libs/blueprint/test/CMakeLists.txt +++ b/crypto3/libs/blueprint/test/CMakeLists.txt @@ -54,6 +54,7 @@ macro(define_blueprint_test test) crypto3::algebra crypto3::zk crypto3::random + crypto3::block ) set_target_properties(${full_test_name} PROPERTIES CXX_STANDARD 17) diff --git a/crypto3/libs/zk/include/nil/crypto3/zk/snark/arithmetization/plonk/lookup_constraint.hpp b/crypto3/libs/zk/include/nil/crypto3/zk/snark/arithmetization/plonk/lookup_constraint.hpp index 12584aaf3..41f9b9943 100644 --- a/crypto3/libs/zk/include/nil/crypto3/zk/snark/arithmetization/plonk/lookup_constraint.hpp +++ b/crypto3/libs/zk/include/nil/crypto3/zk/snark/arithmetization/plonk/lookup_constraint.hpp @@ -28,6 +28,7 @@ #ifndef CRYPTO3_ZK_PLONK_LOOKUP_CONSTRAINT_HPP #define CRYPTO3_ZK_PLONK_LOOKUP_CONSTRAINT_HPP +#include #include #include @@ -36,6 +37,61 @@ namespace nil { namespace zk { namespace snark { + template + class lookup_input_constraints : public std::vector> { + public: + using constraint_type = crypto3::zk::snark::plonk_constraint; + using base_type = std::vector; + using expression_type = typename constraint_type::base_type; + + // Using the base class's constructors + using std::vector::vector; // Inherit constructors + + // Constructor to initialize from std::vector + lookup_input_constraints(const base_type& other) + : base_type(other) {} + + // Multiply each element with an expression. + lookup_input_constraints& operator*=(const expression_type& other) { + for (auto& element : *this) { + element *= other; + } + return *this; + } + + lookup_input_constraints operator*(const expression_type& other) { + lookup_input_constraints result = *this; + result *= other; + return result; + } + + // Allow multiplication with any container of the same type. + template + lookup_input_constraints& operator*=(const typename std::enable_if_t< + nil::crypto3::detail::is_range::value && std::is_same::value, + Container>& other) { + if (this->size() < other.size()) + this->resize(other.size()); + + auto it1 = this->begin(); + auto it2 = other.begin(); + for (; it2 != other.end(); ++it1, ++it2) { + *it1 *= *it2; + } + return *this; + } + + template + lookup_input_constraints& operator*(const typename std::enable_if_t< + nil::crypto3::detail::is_range::value && std::is_same::value, + Container>& other) { + lookup_input_constraints result = *this; + result *= other; + return result; + } + }; + + template> class plonk_lookup_constraint { public: @@ -45,9 +101,9 @@ namespace nil { using constraint_type = plonk_constraint; std::size_t table_id; - std::vector lookup_input; + lookup_input_constraints lookup_input; - bool operator== (const plonk_lookup_constraint &other) const { + bool operator==(const plonk_lookup_constraint &other) const { return table_id == other.table_id && lookup_input == other.lookup_input; } }; diff --git a/parallel-crypto3/libs/parallel-zk/include/nil/crypto3/zk/snark/arithmetization/plonk/lookup_constraint.hpp b/parallel-crypto3/libs/parallel-zk/include/nil/crypto3/zk/snark/arithmetization/plonk/lookup_constraint.hpp index 12584aaf3..41f9b9943 100644 --- a/parallel-crypto3/libs/parallel-zk/include/nil/crypto3/zk/snark/arithmetization/plonk/lookup_constraint.hpp +++ b/parallel-crypto3/libs/parallel-zk/include/nil/crypto3/zk/snark/arithmetization/plonk/lookup_constraint.hpp @@ -28,6 +28,7 @@ #ifndef CRYPTO3_ZK_PLONK_LOOKUP_CONSTRAINT_HPP #define CRYPTO3_ZK_PLONK_LOOKUP_CONSTRAINT_HPP +#include #include #include @@ -36,6 +37,61 @@ namespace nil { namespace zk { namespace snark { + template + class lookup_input_constraints : public std::vector> { + public: + using constraint_type = crypto3::zk::snark::plonk_constraint; + using base_type = std::vector; + using expression_type = typename constraint_type::base_type; + + // Using the base class's constructors + using std::vector::vector; // Inherit constructors + + // Constructor to initialize from std::vector + lookup_input_constraints(const base_type& other) + : base_type(other) {} + + // Multiply each element with an expression. + lookup_input_constraints& operator*=(const expression_type& other) { + for (auto& element : *this) { + element *= other; + } + return *this; + } + + lookup_input_constraints operator*(const expression_type& other) { + lookup_input_constraints result = *this; + result *= other; + return result; + } + + // Allow multiplication with any container of the same type. + template + lookup_input_constraints& operator*=(const typename std::enable_if_t< + nil::crypto3::detail::is_range::value && std::is_same::value, + Container>& other) { + if (this->size() < other.size()) + this->resize(other.size()); + + auto it1 = this->begin(); + auto it2 = other.begin(); + for (; it2 != other.end(); ++it1, ++it2) { + *it1 *= *it2; + } + return *this; + } + + template + lookup_input_constraints& operator*(const typename std::enable_if_t< + nil::crypto3::detail::is_range::value && std::is_same::value, + Container>& other) { + lookup_input_constraints result = *this; + result *= other; + return result; + } + }; + + template> class plonk_lookup_constraint { public: @@ -45,9 +101,9 @@ namespace nil { using constraint_type = plonk_constraint; std::size_t table_id; - std::vector lookup_input; + lookup_input_constraints lookup_input; - bool operator== (const plonk_lookup_constraint &other) const { + bool operator==(const plonk_lookup_constraint &other) const { return table_id == other.table_id && lookup_input == other.lookup_input; } };