Longfellow ZK 0290cb32

Loading...
Searching...
No Matches
mac_circuit.h
1// Copyright 2025 Google LLC.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15#ifndef PRIVACY_PROOFS_ZK_LIB_CIRCUITS_MAC_MAC_CIRCUIT_H_
16#define PRIVACY_PROOFS_ZK_LIB_CIRCUITS_MAC_MAC_CIRCUIT_H_
17
18// Implements a message authentication code in GF 2^k for a 256-bit message.
19// The mac key is additively sampled by both the prover and verifier to ensure
20// soundness and zk.
21//
22// The mac is defined as (a_pi+a_v)*x_i = mac_i where (x_1,x_2) are 128-bit
23// portions of the hidden message. The verifier need only contribute one
24// a_v for all MACs verified in the circuit. The prover needs to commit to
25// separate a_p{i} for each portion of each message.
26//
27// The property that we need from this primitive is as follows:
28// Assume the prover has committed to a_pi and x, i.e., fix a_pi, x.
29// The probability over the verifier's random a_v that mac(x) = mac_(y)
30// if x != y is at most 2^{-128}.
31
32#include <algorithm>
33#include <cstddef>
34
35#include "circuits/compiler/compiler.h"
36#include "circuits/logic/logic.h"
37#include "gf2k/gf2_128.h"
38
39 namespace proofs {
40
41 static constexpr size_t kMACPluckerBits = 2u;
42
43// MAC: implements a MAC in GF 2^k for a 256-bit message by simulating
44// the arithmetic of the GF 2^k field. This implementation commits both
45// the prover's a_p key as well as the bits of the message. This allows
46// the MAC computation and the equality of the purported message to be verified
47// in parallel to reduce depth.
48// As an optimization, the MAC computed here is a.x instead of a.x + b. This
49// MAC is unforgeable with vhp and hiding whenever x is non-zero. The caller
50// must ensure that the MACed values are non-zero with very high probability.
51// For example, in the case of the MAC of a hash of a randomly selected message,
52// the probability of the hash being zero is quite small. This case applies for
53// signatures of messages related to credentials. As another example, the
54// device public key is an honestly-generated ECDSA key, and thus is unlikely
55// to be zero for most curves. These cases add a small error to the
56// zero-knowledge analysis of the scheme.
57 template <class Logic, class BitPlucker>
58 class MAC {
59 public:
60 using Field = typename Logic::Field;
61 using Elt = typename Field::Elt;
62 using EltW = typename Logic::EltW;
63 using Nat = typename Field::N;
64 using v8 = typename Logic::v8;
65 using v128 = typename Logic::v128;
66 using v256 = typename Logic::v256;
67 using packed_v128 = typename BitPlucker::packed_v128;
68 using packed_v256 = typename BitPlucker::packed_v256;
69
70 BitPlucker bp_;
71
72 class Witness {
73 public:
74 packed_v128 aa_[2];
75 packed_v256 xx_; // The value to be checked
76
77 template <typename T>
78 static T packed_input(QuadCircuit<typename Logic::Field>& Q) {
79 T r;
80 for (size_t i = 0; i < r.size(); ++i) {
81 r[i] = Q.input();
82 }
83 return r;
84 }
85
86 void input(const Logic& LC, QuadCircuit<typename Logic::Field>& Q) {
87 aa_[0] = packed_input<packed_v128>(Q);
88 aa_[1] = packed_input<packed_v128>(Q);
89 xx_ = packed_input<packed_v256>(Q);
90 }
91 };
92
93 explicit MAC(const Logic& lc) : bp_(lc), lc_(lc) {}
94
95 // Verifies a mac on the Field element value against the key (a_p + a_v).
96 // This method can only be called when the field is at least 256 bits, e.g.,
97 // with F_p256. In other cases, the caller should use a verify_mac method
98 // that takes the message in bit-wise form. Additionally, the order parameter
99 // is used to ensure that the message does not overflow the field.
100 void verify_mac(EltW msg, const v128 mac[/*2*/], const v128& av,
101 const Witness& vw, Nat order) const {
102 check(Field::kBits >= 256, "Field::kBits < 256");
103 v128 msg2[2];
104 unpack_msg(msg2, msg, order, vw);
105 assert_mac(mac, av, msg2, vw);
106 }
107
108 private:
109 // Checks mac[i] = (a_p + a_v)*xi[i] for i=0..1.
110 void assert_mac(const v128 mac[/*2*/], const v128& av, const v128 xi[/*2*/],
111 const Witness& vw) const {
112 v128 mv;
113 for (size_t i = 0; i < 2; ++i) {
114 v128 ap = bp_.template unpack<v128, packed_v128>(vw.aa_[i]);
115 v128 key = lc_.vxor(&av, ap);
116 lc_.gf2_128_mul(mv, key, xi[i]);
117 lc_.vassert_eq(&mac[i], mv);
118 }
119 }
120
121 void unpack_msg(v128 msg[/*2*/], EltW msgw, Nat order,
122 const Witness& vw) const {
123 v256 x = bp_.template unpack<v256, packed_v256>(vw.xx_);
124 std::copy(x.begin(), x.begin() + 128, msg[0].begin());
125 std::copy(x.begin() + 128, x.end(), msg[1].begin());
126
127 // Ensure that the incoming message does not overflow the field.
128 v256 bits_n;
129 for (size_t i = 0; i < 256; ++i) {
130 bits_n[i] = lc_.bit(order.bit(i));
131 }
132 lc_.assert1(lc_.vlt(&x, bits_n));
133
134 // Verify that the message bits in the witness correspond to msg.
135 EltW te = lc_.konst(lc_.zero());
136 Elt twok = lc_.one();
137 for (size_t i = 0; i < 256; ++i) {
138 te = lc_.axpy(&te, twok, lc_.eval(x[i]));
139 lc_.f_.add(twok, twok);
140 }
141 lc_.assert_eq(&te, msgw);
142 }
143
144 const Logic& lc_;
145};
146
147// Same MAC computation for native GF2_128 field.
148 template <class Backend, class BitPlucker>
149 class MACGF2 {
150 public:
151 using Elt = typename Logic<GF2_128<>, Backend>::Elt;
152 using EltW = typename Logic<GF2_128<>, Backend>::EltW;
153 using BitW = typename Logic<GF2_128<>, Backend>::BitW;
154
155 // In this specialization, 128 bits are stored in a native EltW.
156 using v128 = EltW;
157
158 // Message input types v8, v256 are still encoded bit-wise.
159 using v8 = typename Logic<GF2_128<>, Backend>::v8;
160 using v256 = typename Logic<GF2_128<>, Backend>::v256;
161
162 explicit MACGF2(const Logic<GF2_128<>, Backend>& lc)
163 : lc_(lc) {}
164 class Witness {
165 public:
166 EltW aa_[2];
167
168 void input(const Logic<GF2_128<>, Backend>& lc,
169 QuadCircuit<GF2_128<>>& Q) {
170 aa_[0] = Q.input();
171 aa_[1] = Q.input();
172 }
173 };
174
175 // Verify a mac on the 256-bit message msg.
176 void verify_mac(const EltW mac[/*2*/], const EltW& av, const v256& msg,
177 const Witness& vw) const {
178 // Check that mac[i] = (a_p + a_v)*mm[i] for i=0..1.
179 for (size_t i = 0; i < 2; ++i) {
180 EltW mm = pack(&msg[i * 128]);
181 EltW key = lc_.add(&av, vw.aa_[i]);
182 EltW got = lc_.mul(&key, mm);
183 lc_.assert_eq(&mac[i], got);
184 }
185 }
186
187 private:
188 // Pack a 128-bit message into a GF(2^128) field element.
189 EltW pack(const BitW msg[/*128*/]) const {
190 Elt alpha = lc_.f_.x();
191 Elt xi = lc_.f_.one();
192 EltW m = lc_.konst(0);
193 for (size_t i = 0; i < 128; ++i) {
194 m = lc_.axpy(&m, xi, lc_.eval(msg[i]));
195 xi = lc_.mulf(xi, alpha);
196 }
197 return m;
198 }
199
200 const Logic<GF2_128<>, Backend>& lc_;
201};
202
203} // namespace proofs
204
205#endif // PRIVACY_PROOFS_ZK_LIB_CIRCUITS_MAC_MAC_CIRCUIT_H_
proofs::BitPlucker
Definition bit_plucker.h:86
proofs::GF2_128
Definition gf2_128.h:35
proofs::Logic
Definition logic.h:38
proofs::MAC::Witness
Definition mac_circuit.h:72
proofs::MACGF2::Witness
Definition mac_circuit.h:164
proofs::Nat
Definition nat.h:60
proofs::QuadCircuit
Definition compiler.h:50
proofs::GF2_128::Elt
Definition gf2_128.h:63