Longfellow ZK 0290cb32

Loading...
Searching...
No Matches
transcript.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_RANDOM_TRANSCRIPT_H_
16#define PRIVACY_PROOFS_ZK_LIB_RANDOM_TRANSCRIPT_H_
17
18#include <cstddef>
19#include <cstdint>
20#include <cstring>
21#include <memory>
22
23#include "random/random.h"
24#include "util/crypto.h"
25#include "util/panic.h"
26#include "util/serialization.h"
27
28 namespace proofs {
29
30/*
31 FSPRF and Transcript together used implement the Fiat-Shamir transform.
32*/
33 class FSPRF {
34 public:
35 explicit FSPRF(const uint8_t key[kPRFKeySize])
36 : prf_(key), nblock_(0), rdptr_(kPRFOutputSize) {}
37
38 // Disable copy for good measure.
39 explicit FSPRF(const FSPRF&) = delete;
40 FSPRF& operator=(const FSPRF&) = delete;
41
42 // Maximum number of blocks that can be generated using a 128-bit PRF.
43 // The limit is 2^64, but 2^40 suffices for our application.
44 constexpr static uint64_t kMaxBlocks = 0x10000000000;
45
46 void bytes(uint8_t buf[/*n*/], size_t n) {
47 while (n-- > 0) {
48 if (rdptr_ == kPRFOutputSize) {
49 refill();
50 }
51 *buf++ = saved_[rdptr_++];
52 }
53 }
54
55 private:
56 void refill() {
57 check(nblock_ < kMaxBlocks, "too many blocks");
58 uint8_t in[kPRFInputSize] = {};
59 u64_to_le(in, nblock_++);
60 prf_.Eval(saved_, in);
61 rdptr_ = 0;
62 }
63
64 PRF prf_;
65 uint64_t nblock_;
66 size_t rdptr_; // read pointer into saved[]
67 uint8_t saved_[kPRFOutputSize]; // saved pseudo-random bytes
68};
69
70 class Transcript : public RandomEngine {
71 enum { TAG_BSTR = 0, TAG_FIELD_ELEM = 1, TAG_ARRAY = 2 };
72
73 public:
74 // A transcript must be explicitly initialized so that each instance of
75 // the Random oracle is unique.
76 Transcript(const uint8_t init[], size_t init_len, size_t version = 3)
77 : sha_(), prf_(), version_(version) {
78 write(init, init_len);
79 }
80
81 // Remove default copy and move implementations.
82 Transcript(const Transcript&) = delete;
83 Transcript& operator=(const Transcript&) = delete;
84
85 // Explicit copy to avoid accidental passing by value.
86 Transcript clone() { return Transcript(sha_, version_); }
87
88 // Generate bytes by via the current FSPRF object.
89 void bytes(uint8_t buf[/*n*/], size_t n) override {
90 if (!prf_) {
91 uint8_t key[kPRFKeySize];
92 get(key);
93 prf_ = std::make_unique<FSPRF>(key);
94 }
95 prf_->bytes(buf, n);
96 }
97
98 // snapshot the hash of the transcript so far
99 void get(uint8_t key[/*kPRFKeySize*/]) {
100 check(kPRFKeySize == kSHA256DigestSize, "prf key size != digest output");
101 // fork the state because we will finalize it
102 SHA256 tmp_hash;
103 tmp_hash.CopyState(sha_);
104 tmp_hash.DigestData(key);
105 }
106
107 // Typed write operations. We tag byte-array(n), field-element, and
108 // array-of-field-element(n).
109 //
110 // We make a few arbitrary choices that make no real difference.
111 // All lengths are 64-bit. We distinguish a field element from
112 // an array of one field element, which is kind of arbitrary.
113
114 // byte string
115 void write(const uint8_t data[/*n*/], size_t n) {
116 tag(TAG_BSTR);
117 length(n);
118
119 write_untyped(data, n);
120 }
121
122 // N zero bytes
123 void write0(size_t n) {
124 tag(TAG_BSTR);
125 length(n);
126
127 uint8_t data[32] = {};
128 for (; n > 32; n -= 32) {
129 write_untyped(data, 32);
130 }
131 write_untyped(data, n);
132 }
133
134 // one field element
135 template <class Field>
136 void write(const typename Field::Elt& e, const Field& F) {
137 tag(TAG_FIELD_ELEM);
138
139 write_untyped(e, F);
140 }
141
142 // array of field elements
143 template <class Field>
144 void write(const typename Field::Elt e[/*n*/], size_t ince, size_t n,
145 const Field& F) {
146 if (version_ > 3) {
147 tag(TAG_ARRAY);
148 } else {
149 tag(1); // in version 3, the TAG_ARRAY was 1.
150 }
151 length(n);
152
153 for (size_t i = 0; i < n; ++i) {
154 write_untyped(e[i * ince], F);
155 }
156 }
157
158 private:
159 explicit Transcript(const SHA256& sha, size_t version)
160 : sha_(), version_(version) {
161 sha_.CopyState(sha);
162 }
163
164 // Output a 1-byte tag
165 void tag(size_t t) {
166 uint8_t d = static_cast<uint8_t>(t);
167 write_untyped(&d, 1);
168 }
169
170 // Output a 8-byte length. We pass the length
171 // as size_t, but we always write it as uint64_t
172 void length(size_t x) {
173 uint8_t a[8];
174 u64_to_le(a, x);
175 write_untyped(a, 8);
176 }
177
178 void write_untyped(const uint8_t data[/*n*/], size_t n) {
179 // invalidate the PRF on any writes
180 prf_.reset();
181 sha_.Update(data, n);
182 }
183
184 template <class Field>
185 void write_untyped(const typename Field::Elt& e, const Field& F) {
186 uint8_t buf[Field::kBytes];
187 F.to_bytes_field(buf, e);
188 write_untyped(buf, sizeof(buf));
189 }
190
191 SHA256 sha_;
192 std::unique_ptr<FSPRF> prf_;
193 const size_t version_; // version 4+ fixes the TAG_ARRAY typo.
194};
195} // namespace proofs
196
197#endif // PRIVACY_PROOFS_ZK_LIB_RANDOM_TRANSCRIPT_H_
proofs::PRF
Definition crypto.h:69
proofs::RandomEngine
Definition random.h:32
proofs::SHA256
Definition crypto.h:40
proofs::GF2_128::Elt
Definition gf2_128.h:63