2 *****************************************************************************
4 Implementation of interfaces for the AS-Waksman routing gadget.
6 See as_waksman_routing_gadget.hpp .
8 *****************************************************************************
9 * @author This file is part of libsnark, developed by SCIPR Lab
10 * and contributors (see AUTHORS).
11 * @copyright MIT license (see LICENSE file)
12 *****************************************************************************/
14 #ifndef AS_WAKSMAN_ROUTING_GADGET_TCC_
15 #define AS_WAKSMAN_ROUTING_GADGET_TCC_
18 #include <libff/common/profiling.hpp>
19 #include <libsnark/common/routing_algorithms/as_waksman_routing_algorithm.hpp>
24 template<typename FieldT>
25 as_waksman_routing_gadget<FieldT>::as_waksman_routing_gadget(
26 protoboard<FieldT> &pb,
27 const size_t num_packets,
28 const std::vector<pb_variable_array<FieldT>> &routing_input_bits,
29 const std::vector<pb_variable_array<FieldT>> &routing_output_bits,
30 const std::string &annotation_prefix)
31 : gadget<FieldT>(pb, annotation_prefix)
32 , num_packets(num_packets)
33 , num_columns(as_waksman_num_columns(num_packets))
34 , routing_input_bits(routing_input_bits)
35 , routing_output_bits(routing_output_bits)
36 , packet_size(routing_input_bits[0].size())
37 , num_subpackets(libff::div_ceil(packet_size, FieldT::capacity()))
39 neighbors = generate_as_waksman_topology(num_packets);
40 routed_packets.resize(num_columns + 1);
42 /* Two pass allocation. First allocate LHS packets, then for every
43 switch either copy over the variables from previously allocated
44 to allocate target packets */
45 routed_packets[0].resize(num_packets);
46 for (size_t packet_idx = 0; packet_idx < num_packets; ++packet_idx) {
47 routed_packets[0][packet_idx].allocate(
50 FMT(annotation_prefix, " routed_packets_0_%zu", packet_idx));
53 for (size_t column_idx = 0; column_idx < num_columns; ++column_idx) {
54 routed_packets[column_idx + 1].resize(num_packets);
56 for (size_t row_idx = 0; row_idx < num_packets; ++row_idx) {
57 if (neighbors[column_idx][row_idx].first ==
58 neighbors[column_idx][row_idx].second) {
59 /* This is a straight edge, so just copy over the previously
60 * allocated subpackets */
61 routed_packets[column_idx + 1]
62 [neighbors[column_idx][row_idx].first] =
63 routed_packets[column_idx][row_idx];
65 const size_t straight_edge =
66 neighbors[column_idx][row_idx].first;
67 const size_t cross_edge = neighbors[column_idx][row_idx].second;
68 routed_packets[column_idx + 1][straight_edge].allocate(
71 FMT(annotation_prefix,
72 " routed_packets_%zu_%zu",
75 routed_packets[column_idx + 1][cross_edge].allocate(
78 FMT(annotation_prefix,
79 " routed_packets_%zu_%zu",
82 // skip the next idx, as it to refers to the same packets
88 /* create packing/unpacking gadgets */
89 pack_inputs.reserve(num_packets);
90 unpack_outputs.reserve(num_packets);
91 for (size_t packet_idx = 0; packet_idx < num_packets; ++packet_idx) {
92 pack_inputs.emplace_back(multipacking_gadget<FieldT>(
94 pb_variable_array<FieldT>(
95 routing_input_bits[packet_idx].begin(),
96 routing_input_bits[packet_idx].end()),
97 routed_packets[0][packet_idx],
99 FMT(this->annotation_prefix, " pack_inputs_%zu", packet_idx)));
100 unpack_outputs.emplace_back(multipacking_gadget<FieldT>(
102 pb_variable_array<FieldT>(
103 routing_output_bits[packet_idx].begin(),
104 routing_output_bits[packet_idx].end()),
105 routed_packets[num_columns][packet_idx],
107 FMT(this->annotation_prefix, " unpack_outputs_%zu", packet_idx)));
110 /* allocate switch bits */
111 if (num_subpackets > 1) {
112 asw_switch_bits.resize(num_columns);
114 for (size_t column_idx = 0; column_idx < num_columns; ++column_idx) {
115 for (size_t row_idx = 0; row_idx < num_packets; ++row_idx) {
116 if (neighbors[column_idx][row_idx].first !=
117 neighbors[column_idx][row_idx].second) {
118 asw_switch_bits[column_idx][row_idx].allocate(
120 FMT(annotation_prefix,
121 " asw_switch_bits_%zu_%zu",
124 // next row_idx corresponds to the same switch, so skip it
132 template<typename FieldT>
133 void as_waksman_routing_gadget<FieldT>::generate_r1cs_constraints()
135 /* packing/unpacking */
136 for (size_t packet_idx = 0; packet_idx < num_packets; ++packet_idx) {
137 pack_inputs[packet_idx].generate_r1cs_constraints(false);
138 unpack_outputs[packet_idx].generate_r1cs_constraints(true);
141 /* actual routing constraints */
142 for (size_t column_idx = 0; column_idx < num_columns; ++column_idx) {
143 for (size_t row_idx = 0; row_idx < num_packets; ++row_idx) {
144 if (neighbors[column_idx][row_idx].first ==
145 neighbors[column_idx][row_idx].second) {
146 /* if there is no switch at this position, then just continue
147 * with next row_idx */
151 if (num_subpackets == 1) {
152 /* easy case: require that
153 (cur-straight_edge)*(cur-cross_edge) = 0 for both
155 for (size_t switch_input : {row_idx, row_idx + 1}) {
156 const size_t straight_edge =
157 neighbors[column_idx][switch_input].first;
158 const size_t cross_edge =
159 neighbors[column_idx][switch_input].second;
161 this->pb.add_r1cs_constraint(
162 r1cs_constraint<FieldT>(
163 routed_packets[column_idx][switch_input][0] -
164 routed_packets[column_idx + 1][straight_edge]
166 routed_packets[column_idx][switch_input][0] -
167 routed_packets[column_idx + 1][cross_edge][0],
169 FMT(this->annotation_prefix,
170 " easy_route_%zu_%zu",
175 /* require switching bit to be boolean */
176 generate_boolean_r1cs_constraint<FieldT>(
178 asw_switch_bits[column_idx][row_idx],
179 FMT(this->annotation_prefix,
180 " asw_switch_bits_%zu_%zu",
184 /* route forward according to the switch bit */
185 for (size_t subpacket_idx = 0; subpacket_idx < num_subpackets;
188 (1-switch_bit) * (cur-straight_edge) + switch_bit *
189 (cur-cross_edge) = 0 switch_bit *
190 (cross_edge-straight_edge) = cur-straight_edge
192 for (size_t switch_input : {row_idx, row_idx + 1}) {
193 const size_t straight_edge =
194 neighbors[column_idx][switch_input].first;
195 const size_t cross_edge =
196 neighbors[column_idx][switch_input].second;
198 this->pb.add_r1cs_constraint(
199 r1cs_constraint<FieldT>(
200 asw_switch_bits[column_idx][row_idx],
201 routed_packets[column_idx + 1][cross_edge]
203 routed_packets[column_idx + 1]
206 routed_packets[column_idx][switch_input]
208 routed_packets[column_idx + 1]
211 FMT(this->annotation_prefix,
212 " route_forward_%zu_%zu_%zu",
220 /* we processed both switch inputs at once, so skip the next
227 template<typename FieldT>
228 void as_waksman_routing_gadget<FieldT>::generate_r1cs_witness(
229 const integer_permutation &permutation)
232 for (size_t packet_idx = 0; packet_idx < num_packets; ++packet_idx) {
233 pack_inputs[packet_idx].generate_r1cs_witness_from_bits();
237 as_waksman_routing routing = get_as_waksman_routing(permutation);
239 for (size_t column_idx = 0; column_idx < num_columns; ++column_idx) {
240 for (size_t row_idx = 0; row_idx < num_packets; ++row_idx) {
241 if (neighbors[column_idx][row_idx].first ==
242 neighbors[column_idx][row_idx].second) {
243 /* this is a straight edge, so just pass the values forward */
244 const size_t next = neighbors[column_idx][row_idx].first;
246 for (size_t subpacket_idx = 0; subpacket_idx < num_subpackets;
249 routed_packets[column_idx + 1][next][subpacket_idx]) =
251 routed_packets[column_idx][row_idx][subpacket_idx]);
254 if (num_subpackets > 1) {
255 /* update the switch bit */
256 this->pb.val(asw_switch_bits[column_idx][row_idx]) =
257 FieldT(routing[column_idx][row_idx] ? 1 : 0);
260 /* route according to the switch bit */
261 const bool switch_val = routing[column_idx][row_idx];
263 for (size_t switch_input : {row_idx, row_idx + 1}) {
264 const size_t straight_edge =
265 neighbors[column_idx][switch_input].first;
266 const size_t cross_edge =
267 neighbors[column_idx][switch_input].second;
269 const size_t switched_edge =
270 (switch_val ? cross_edge : straight_edge);
272 for (size_t subpacket_idx = 0;
273 subpacket_idx < num_subpackets;
276 routed_packets[column_idx + 1][switched_edge]
279 routed_packets[column_idx][switch_input]
284 /* we processed both switch inputs at once, so skip the next
292 for (size_t packet_idx = 0; packet_idx < num_packets; ++packet_idx) {
293 unpack_outputs[packet_idx].generate_r1cs_witness_from_packed();
297 template<typename FieldT>
298 void test_as_waksman_routing_gadget(
299 const size_t num_packets, const size_t packet_size)
302 "testing as_waksman_routing_gadget by routing %zu element vector of "
303 "%zu bits (Fp fits all %zu bit integers)\n",
307 protoboard<FieldT> pb;
308 integer_permutation permutation(num_packets);
309 permutation.random_shuffle();
310 libff::print_time("generated permutation");
312 std::vector<pb_variable_array<FieldT>> randbits(num_packets),
313 outbits(num_packets);
314 for (size_t packet_idx = 0; packet_idx < num_packets; ++packet_idx) {
315 randbits[packet_idx].allocate(
316 pb, packet_size, FMT("", "randbits_%zu", packet_idx));
317 outbits[packet_idx].allocate(
318 pb, packet_size, FMT("", "outbits_%zu", packet_idx));
320 for (size_t bit_idx = 0; bit_idx < packet_size; ++bit_idx) {
321 pb.val(randbits[packet_idx][bit_idx]) =
322 (rand() % 2) ? FieldT::one() : FieldT::zero();
325 libff::print_time("generated bits to be routed");
327 as_waksman_routing_gadget<FieldT> r(
328 pb, num_packets, randbits, outbits, "main_routing_gadget");
329 r.generate_r1cs_constraints();
330 libff::print_time("generated routing constraints");
332 r.generate_r1cs_witness(permutation);
333 libff::print_time("generated routing assignment");
335 printf("positive test\n");
336 assert(pb.is_satisfied());
337 for (size_t packet_idx = 0; packet_idx < num_packets; ++packet_idx) {
338 for (size_t bit_idx = 0; bit_idx < packet_size; ++bit_idx) {
340 pb.val(outbits[permutation.get(packet_idx)][bit_idx]) ==
341 pb.val(randbits[packet_idx][bit_idx]));
345 printf("negative test\n");
346 pb.val(pb_variable<FieldT>(10)) = FieldT(12345);
347 assert(!pb.is_satisfied());
350 "num_constraints = %zu, num_variables = %zu\n",
351 pb.num_constraints(),
352 pb.constraint_system.num_variables);
355 } // namespace libsnark
357 #endif // AS_WAKSMAN_ROUTING_GADGET_TCC_