-
Notifications
You must be signed in to change notification settings - Fork 18
/
Copy pathFriLayer.sol
304 lines (263 loc) · 12 KB
/
FriLayer.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
/*
Copyright 2019-2022 StarkWare Industries Ltd.
Licensed under the Apache License, Version 2.0 (the "License").
You may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.starkware.co/open-source-license/
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions
and limitations under the License.
*/
// SPDX-License-Identifier: Apache-2.0.
pragma solidity ^0.6.12;
import "./MerkleVerifier.sol";
import "./FriTransform.sol";
/*
The main component of FRI is the FRI step which takes
the i-th layer evaluations on a coset c*<g> and produces a single evaluation in layer i+1.
To this end we have a friCtx that holds the following data:
evaluations: holds the evaluations on the coset we are currently working on.
group: holds the group <g> in bit reversed order.
halfInvGroup: holds the group <g^-1>/<-1> in bit reversed order.
(We only need half of the inverse group)
Note that due to the bit reversed order, a prefix of size 2^k of either group
or halfInvGroup has the same structure (but for a smaller group).
*/
contract FriLayer is MerkleVerifier, FriTransform {
event LogGas(string name, uint256 val);
uint256 internal constant MAX_COSET_SIZE = 2**FRI_MAX_STEP_SIZE;
// Generator of the group of size MAX_COSET_SIZE: GENERATOR_VAL**((K_MODULUS - 1)/MAX_COSET_SIZE).
uint256 internal constant FRI_GROUP_GEN =
0x5ec467b88826aba4537602d514425f3b0bdf467bbf302458337c45f6021e539;
uint256 internal constant FRI_GROUP_SIZE = 0x20 * MAX_COSET_SIZE;
uint256 internal constant FRI_CTX_TO_COSET_EVALUATIONS_OFFSET = 0;
uint256 internal constant FRI_CTX_TO_FRI_GROUP_OFFSET = FRI_GROUP_SIZE;
uint256 internal constant FRI_CTX_TO_FRI_HALF_INV_GROUP_OFFSET =
FRI_CTX_TO_FRI_GROUP_OFFSET + FRI_GROUP_SIZE;
uint256 internal constant FRI_CTX_SIZE =
FRI_CTX_TO_FRI_HALF_INV_GROUP_OFFSET + (FRI_GROUP_SIZE / 2);
/*
The FRI queue is an array of triplets (query index, FRI value, FRI inversed point).
'query index' is an adjust query index,
see adjustQueryIndicesAndPrepareEvalPoints for detail.
'FRI value' is the expected committed value at query index.
'FRI inversed point' is evaluation point corresponding to query index i.e.
inverse(
fpow(layerGenerator, bitReverse(query index - (1 << logLayerSize), logLayerSize)).
*/
uint256 internal constant FRI_QUEUE_SLOT_SIZE = 3;
// FRI_QUEUE_SLOT_SIZE_IN_BYTES cannot reference FRI_QUEUE_SLOT_SIZE as only direct constants
// are supported in assembly.
uint256 internal constant FRI_QUEUE_SLOT_SIZE_IN_BYTES = 3 * 0x20;
/*
Gathers the "cosetSize" elements that belong the coset of the first element in the FRI queue.
The elements are written to 'evaluationsOnCosetPtr'.
The coset elements are read either from the FriQueue or from the verifier channel
depending on whether the required element are in queue or not.
Returns
newFriQueueHead - The update FRI queue head i.e.
friQueueHead + FRI_QUEUE_SLOT_SIZE_IN_BYTES * (# elements that were taken from the queue).
cosetIdx - the start index of the coset that was gathered.
cosetOffset - the xInv field element that corresponds to cosetIdx.
*/
function gatherCosetInputs(
uint256 channelPtr,
uint256 friGroupPtr,
uint256 evaluationsOnCosetPtr,
uint256 friQueueHead,
uint256 cosetSize
)
internal
pure
returns (
uint256 newFriQueueHead,
uint256 cosetIdx,
uint256 cosetOffset
)
{
assembly {
let queueItemIdx := mload(friQueueHead)
// The coset index is represented by the most significant bits of the queue item index.
cosetIdx := and(queueItemIdx, not(sub(cosetSize, 1)))
let nextCosetIdx := add(cosetIdx, cosetSize)
// Get the algebraic coset offset:
// I.e. given c*g^(-k) compute c, where
// g is the generator of the coset group.
// k is bitReverse(offsetWithinCoset, log2(cosetSize)).
//
// To do this we multiply the algebraic coset offset at the top of the queue (c*g^(-k))
// by the group element that corresponds to the index inside the coset (g^k).
cosetOffset := mulmod(
// (c*g^(-k))=
mload(add(friQueueHead, 0x40)),
// (g^k)=
mload(
add(
friGroupPtr,
mul(
// offsetWithinCoset=
sub(queueItemIdx, cosetIdx),
0x20
)
)
),
K_MODULUS
)
let proofPtr := mload(channelPtr)
for {
let index := cosetIdx
} lt(index, nextCosetIdx) {
index := add(index, 1)
} {
// Inline channel operation:
// Assume we are going to read the next element from the proof.
// If this is not the case add(proofPtr, 0x20) will be reverted.
let fieldElementPtr := proofPtr
proofPtr := add(proofPtr, 0x20)
// Load the next index from the queue and check if it is our sibling.
if eq(index, queueItemIdx) {
// Take element from the queue rather than from the proof
// and convert it back to Montgomery form for Merkle verification.
fieldElementPtr := add(friQueueHead, 0x20)
// Revert the read from proof.
proofPtr := sub(proofPtr, 0x20)
// Reading the next index here is safe due to the
// delimiter after the queries.
friQueueHead := add(friQueueHead, FRI_QUEUE_SLOT_SIZE_IN_BYTES)
queueItemIdx := mload(friQueueHead)
}
// Note that we apply the modulo operation to convert the field elements we read
// from the proof to canonical representation (in the range [0, K_MODULUS - 1]).
mstore(evaluationsOnCosetPtr, mod(mload(fieldElementPtr), K_MODULUS))
evaluationsOnCosetPtr := add(evaluationsOnCosetPtr, 0x20)
}
mstore(channelPtr, proofPtr)
}
newFriQueueHead = friQueueHead;
}
/*
Returns the bit reversal of num assuming it has the given number of bits.
For example, if we have numberOfBits = 6 and num = (0b)1101 == (0b)001101,
the function will return (0b)101100.
*/
function bitReverse(uint256 num, uint256 numberOfBits)
internal
pure
returns (uint256 numReversed)
{
assert((numberOfBits == 256) || (num < 2**numberOfBits));
uint256 n = num;
uint256 r = 0;
for (uint256 k = 0; k < numberOfBits; k++) {
r = (r * 2) | (n % 2);
n = n / 2;
}
return r;
}
/*
Initializes the FRI group and half inv group in the FRI context.
*/
function initFriGroups(uint256 friCtx) internal view {
uint256 friGroupPtr = friCtx + FRI_CTX_TO_FRI_GROUP_OFFSET;
uint256 friHalfInvGroupPtr = friCtx + FRI_CTX_TO_FRI_HALF_INV_GROUP_OFFSET;
// FRI_GROUP_GEN is the coset generator.
// Raising it to the (MAX_COSET_SIZE - 1) power gives us the inverse.
uint256 genFriGroup = FRI_GROUP_GEN;
uint256 genFriGroupInv = fpow(genFriGroup, (MAX_COSET_SIZE - 1));
uint256 lastVal = ONE_VAL;
uint256 lastValInv = ONE_VAL;
assembly {
// ctx[mmHalfFriInvGroup + 0] = ONE_VAL;
mstore(friHalfInvGroupPtr, lastValInv)
// ctx[mmFriGroup + 0] = ONE_VAL;
mstore(friGroupPtr, lastVal)
// ctx[mmFriGroup + 1] = fsub(0, ONE_VAL);
mstore(add(friGroupPtr, 0x20), sub(K_MODULUS, lastVal))
}
// To compute [1, -1 (== g^n/2), g^n/4, -g^n/4, ...]
// we compute half the elements and derive the rest using negation.
uint256 halfCosetSize = MAX_COSET_SIZE / 2;
for (uint256 i = 1; i < halfCosetSize; i++) {
lastVal = fmul(lastVal, genFriGroup);
lastValInv = fmul(lastValInv, genFriGroupInv);
uint256 idx = bitReverse(i, FRI_MAX_STEP_SIZE - 1);
assembly {
// ctx[mmHalfFriInvGroup + idx] = lastValInv;
mstore(add(friHalfInvGroupPtr, mul(idx, 0x20)), lastValInv)
// ctx[mmFriGroup + 2*idx] = lastVal;
mstore(add(friGroupPtr, mul(idx, 0x40)), lastVal)
// ctx[mmFriGroup + 2*idx + 1] = fsub(0, lastVal);
mstore(add(friGroupPtr, add(mul(idx, 0x40), 0x20)), sub(K_MODULUS, lastVal))
}
}
}
/*
Computes the FRI step with eta = log2(friCosetSize) for all the live queries.
The inputs for the current layer are read from the FRI queue and the inputs
for the next layer are written to the same queue (overwriting the input).
See friVerifyLayers for the description for the FRI queue.
The function returns the number of live queries remaining after computing the FRI step.
The number of live queries decreases whenever multiple query points in the same
coset are reduced to a single query in the next FRI layer.
As the function computes the next layer it also collects that data from
the previous layer for Merkle verification.
*/
function computeNextLayer(
uint256 channelPtr,
uint256 friQueuePtr,
uint256 merkleQueuePtr,
uint256 nQueries,
uint256 friCtx,
uint256 friEvalPoint,
uint256 friCosetSize
) internal pure returns (uint256 nLiveQueries) {
uint256 evaluationsOnCosetPtr = friCtx + FRI_CTX_TO_COSET_EVALUATIONS_OFFSET;
// The inputs are read from the Fri queue and the result is written same queue.
// The inputs are never overwritten since gatherCosetInputs reads at least one element and
// transformCoset writes exactly one element.
uint256 inputPtr = friQueuePtr;
uint256 inputEnd = inputPtr + (FRI_QUEUE_SLOT_SIZE_IN_BYTES * nQueries);
uint256 ouputPtr = friQueuePtr;
do {
uint256 cosetOffset;
uint256 index;
(inputPtr, index, cosetOffset) = gatherCosetInputs(
channelPtr,
friCtx + FRI_CTX_TO_FRI_GROUP_OFFSET,
evaluationsOnCosetPtr,
inputPtr,
friCosetSize
);
// Compute the index of the coset evaluations in the Merkle queue.
index /= friCosetSize;
// Add (index, keccak256(evaluationsOnCoset)) to the Merkle queue.
assembly {
mstore(merkleQueuePtr, index)
mstore(
add(merkleQueuePtr, 0x20),
and(COMMITMENT_MASK, keccak256(evaluationsOnCosetPtr, mul(0x20, friCosetSize)))
)
}
merkleQueuePtr += MERKLE_SLOT_SIZE_IN_BYTES;
(uint256 friValue, uint256 FriInversedPoint) = transformCoset(
friCtx + FRI_CTX_TO_FRI_HALF_INV_GROUP_OFFSET,
evaluationsOnCosetPtr,
cosetOffset,
friEvalPoint,
friCosetSize
);
// Add (index, friValue, FriInversedPoint) to the FRI queue.
// Note that the index in the Merkle queue is also the index in the next FRI layer.
assembly {
mstore(ouputPtr, index)
mstore(add(ouputPtr, 0x20), friValue)
mstore(add(ouputPtr, 0x40), FriInversedPoint)
}
ouputPtr += FRI_QUEUE_SLOT_SIZE_IN_BYTES;
} while (inputPtr < inputEnd);
// Return the current number of live queries.
return (ouputPtr - friQueuePtr) / FRI_QUEUE_SLOT_SIZE_IN_BYTES;
}
}