Clearmatics Libsnark  0.1
C++ library for zkSNARK proofs
as_waksman_routing_gadget.tcc
Go to the documentation of this file.
1 /** @file
2  *****************************************************************************
3 
4  Implementation of interfaces for the AS-Waksman routing gadget.
5 
6  See as_waksman_routing_gadget.hpp .
7 
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  *****************************************************************************/
13 
14 #ifndef AS_WAKSMAN_ROUTING_GADGET_TCC_
15 #define AS_WAKSMAN_ROUTING_GADGET_TCC_
16 
17 #include <algorithm>
18 #include <libff/common/profiling.hpp>
19 #include <libsnark/common/routing_algorithms/as_waksman_routing_algorithm.hpp>
20 
21 namespace libsnark
22 {
23 
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()))
38 {
39  neighbors = generate_as_waksman_topology(num_packets);
40  routed_packets.resize(num_columns + 1);
41 
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(
48  pb,
49  num_subpackets,
50  FMT(annotation_prefix, " routed_packets_0_%zu", packet_idx));
51  }
52 
53  for (size_t column_idx = 0; column_idx < num_columns; ++column_idx) {
54  routed_packets[column_idx + 1].resize(num_packets);
55 
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];
64  } else {
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(
69  pb,
70  num_subpackets,
71  FMT(annotation_prefix,
72  " routed_packets_%zu_%zu",
73  column_idx + 1,
74  straight_edge));
75  routed_packets[column_idx + 1][cross_edge].allocate(
76  pb,
77  num_subpackets,
78  FMT(annotation_prefix,
79  " routed_packets_%zu_%zu",
80  column_idx + 1,
81  cross_edge));
82  // skip the next idx, as it to refers to the same packets
83  ++row_idx;
84  }
85  }
86  }
87 
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>(
93  pb,
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],
98  FieldT::capacity(),
99  FMT(this->annotation_prefix, " pack_inputs_%zu", packet_idx)));
100  unpack_outputs.emplace_back(multipacking_gadget<FieldT>(
101  pb,
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],
106  FieldT::capacity(),
107  FMT(this->annotation_prefix, " unpack_outputs_%zu", packet_idx)));
108  }
109 
110  /* allocate switch bits */
111  if (num_subpackets > 1) {
112  asw_switch_bits.resize(num_columns);
113 
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(
119  pb,
120  FMT(annotation_prefix,
121  " asw_switch_bits_%zu_%zu",
122  column_idx,
123  row_idx));
124  // next row_idx corresponds to the same switch, so skip it
125  ++row_idx;
126  }
127  }
128  }
129  }
130 }
131 
132 template<typename FieldT>
133 void as_waksman_routing_gadget<FieldT>::generate_r1cs_constraints()
134 {
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);
139  }
140 
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 */
148  continue;
149  }
150 
151  if (num_subpackets == 1) {
152  /* easy case: require that
153  (cur-straight_edge)*(cur-cross_edge) = 0 for both
154  switch inputs */
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;
160 
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]
165  [0],
166  routed_packets[column_idx][switch_input][0] -
167  routed_packets[column_idx + 1][cross_edge][0],
168  0),
169  FMT(this->annotation_prefix,
170  " easy_route_%zu_%zu",
171  column_idx,
172  switch_input));
173  }
174  } else {
175  /* require switching bit to be boolean */
176  generate_boolean_r1cs_constraint<FieldT>(
177  this->pb,
178  asw_switch_bits[column_idx][row_idx],
179  FMT(this->annotation_prefix,
180  " asw_switch_bits_%zu_%zu",
181  column_idx,
182  row_idx));
183 
184  /* route forward according to the switch bit */
185  for (size_t subpacket_idx = 0; subpacket_idx < num_subpackets;
186  ++subpacket_idx) {
187  /*
188  (1-switch_bit) * (cur-straight_edge) + switch_bit *
189  (cur-cross_edge) = 0 switch_bit *
190  (cross_edge-straight_edge) = cur-straight_edge
191  */
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;
197 
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]
202  [subpacket_idx] -
203  routed_packets[column_idx + 1]
204  [straight_edge]
205  [subpacket_idx],
206  routed_packets[column_idx][switch_input]
207  [subpacket_idx] -
208  routed_packets[column_idx + 1]
209  [straight_edge]
210  [subpacket_idx]),
211  FMT(this->annotation_prefix,
212  " route_forward_%zu_%zu_%zu",
213  column_idx,
214  switch_input,
215  subpacket_idx));
216  }
217  }
218  }
219 
220  /* we processed both switch inputs at once, so skip the next
221  * iteration */
222  ++row_idx;
223  }
224  }
225 }
226 
227 template<typename FieldT>
228 void as_waksman_routing_gadget<FieldT>::generate_r1cs_witness(
229  const integer_permutation &permutation)
230 {
231  /* pack inputs */
232  for (size_t packet_idx = 0; packet_idx < num_packets; ++packet_idx) {
233  pack_inputs[packet_idx].generate_r1cs_witness_from_bits();
234  }
235 
236  /* do the routing */
237  as_waksman_routing routing = get_as_waksman_routing(permutation);
238 
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;
245 
246  for (size_t subpacket_idx = 0; subpacket_idx < num_subpackets;
247  ++subpacket_idx) {
248  this->pb.val(
249  routed_packets[column_idx + 1][next][subpacket_idx]) =
250  this->pb.val(
251  routed_packets[column_idx][row_idx][subpacket_idx]);
252  }
253  } else {
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);
258  }
259 
260  /* route according to the switch bit */
261  const bool switch_val = routing[column_idx][row_idx];
262 
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;
268 
269  const size_t switched_edge =
270  (switch_val ? cross_edge : straight_edge);
271 
272  for (size_t subpacket_idx = 0;
273  subpacket_idx < num_subpackets;
274  ++subpacket_idx) {
275  this->pb.val(
276  routed_packets[column_idx + 1][switched_edge]
277  [subpacket_idx]) =
278  this->pb.val(
279  routed_packets[column_idx][switch_input]
280  [subpacket_idx]);
281  }
282  }
283 
284  /* we processed both switch inputs at once, so skip the next
285  * iteration */
286  ++row_idx;
287  }
288  }
289  }
290 
291  /* unpack outputs */
292  for (size_t packet_idx = 0; packet_idx < num_packets; ++packet_idx) {
293  unpack_outputs[packet_idx].generate_r1cs_witness_from_packed();
294  }
295 }
296 
297 template<typename FieldT>
298 void test_as_waksman_routing_gadget(
299  const size_t num_packets, const size_t packet_size)
300 {
301  printf(
302  "testing as_waksman_routing_gadget by routing %zu element vector of "
303  "%zu bits (Fp fits all %zu bit integers)\n",
304  num_packets,
305  packet_size,
306  FieldT::capacity());
307  protoboard<FieldT> pb;
308  integer_permutation permutation(num_packets);
309  permutation.random_shuffle();
310  libff::print_time("generated permutation");
311 
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));
319 
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();
323  }
324  }
325  libff::print_time("generated bits to be routed");
326 
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");
331 
332  r.generate_r1cs_witness(permutation);
333  libff::print_time("generated routing assignment");
334 
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) {
339  assert(
340  pb.val(outbits[permutation.get(packet_idx)][bit_idx]) ==
341  pb.val(randbits[packet_idx][bit_idx]));
342  }
343  }
344 
345  printf("negative test\n");
346  pb.val(pb_variable<FieldT>(10)) = FieldT(12345);
347  assert(!pb.is_satisfied());
348 
349  printf(
350  "num_constraints = %zu, num_variables = %zu\n",
351  pb.num_constraints(),
352  pb.constraint_system.num_variables);
353 }
354 
355 } // namespace libsnark
356 
357 #endif // AS_WAKSMAN_ROUTING_GADGET_TCC_