diff --git a/docs/specs/code/fs.py b/docs/specs/code/fs.py new file mode 100644 index 0000000..e6b82ab --- /dev/null +++ b/docs/specs/code/fs.py @@ -0,0 +1,183 @@ +# +# Runs in sage if the pycryptodome package is installed in the environment. +# `sage --pip install pycryptodome` +# In sage, use load("fs.py") to run this test +# +from Crypto.Cipher import AES + +import hashlib +import struct +import math + +def hash(data): + assert isinstance(data, bytes), "data not bytes" + return hashlib.sha256(data).digest() + +class FSPRF: + """ + Fiat-Shamir Pseudorandom Function object. + Produces an infinite stream of bytes organized in 16-byte blocks. + Block i = AES256(SEED, ID(i)) + """ + def __init__(self, seed: bytes): + assert len(seed) == 32, "Seed must be 32 bytes (AES-256 key size)." + self.counter = 0 + self.buffer = bytearray() + self.cipher = AES.new(seed, AES.MODE_ECB) + + def bytes(self, n: int) -> bytes: + """Returns the next n bytes in the stream.""" + # Fill buffer until we have enough bytes + while len(self.buffer) < n: + # block_id is the 16-byte little-endian representation of integer i + block_id = self.counter.to_bytes(16, 'little') + + # Block i = AES256(SEED, ID(i)) + block_output = self.cipher.encrypt(block_id) + + self.buffer.extend(block_output) + self.counter += 1 + + # Consume n bytes from the front of the buffer + result = self.buffer[:n] + self.buffer = self.buffer[n:] + return bytes(result) + + +class Transcript: + def __init__(self): + self.tr = bytearray() + self._is_initialized = False + self._fs = None + self._tr_snapshot_len = 0 + + def init(self, session_id: bytes): + """ + Initializes the transcript with a session_id. + Must be called exactly once before any other method. + """ + assert not self._is_initialized, "Transcript.init() must be called exactly once." + self._is_initialized = True + self.write_bytes(session_id) + + def write_field(self, elt, sz = 32): + assert self._is_initialized, "init not called" + self.tr.append(0x01) + self.tr.extend( int(elt).to_bytes(sz, byteorder="little")) + + def write_bytes(self, b): + assert self._is_initialized, "init not called" + self.tr.append(0x00) + # packs an unsigned long long (8 bytes) in Little Endian (<) + length_prefix = struct.pack(' FSPRF: + """ + Retrieves the current FSPRF object. + If 'write' has been called since the last retrieval, a new FSPRF + is seeded using H(tr). + """ + assert self._is_initialized, "init not called" + # If the transcript has changed, create a new FSPRF. + if self._fs is None or len(self.tr) != self._tr_snapshot_len: + # Spec: "Next, a seed is generated by applying the function H to the (entire) string tr." + seed = hash(bytes(self.tr)) + self._fs = FSPRF(seed) + self._tr_snapshot_len = len(self.tr) + + return self._fs + + def generate_nat(self, m): + """ + Generates a random natural number between 0 and m-1 inclusive via rejection sampling. + """ + assert m > 0, "m must be > 0" + + l = m.bit_length() + nbytes = math.ceil(l / 8) + mask = (1 << l) - 1 # Bitmask to isolate lower l bits + fs = self._get_fs() + while True: + b = fs.bytes(nbytes) + k = int.from_bytes(b, 'little') + r = k & mask + if r < m: + return r + + def generate_field(self, p): + fs = self._get_fs() + sz = math.ceil(p.bit_length() / 8) + while True: + b = fs.bytes(sz) + x = int.from_bytes(b, byteorder='little', signed=False) + if x < p: + return x + + def generate_nats_wo_replacement(self, m, n): + assert m > n, "invalid parameter" + A = list(range(0, m)) + for i in range(0, n): + j = i + self.generate_nat(m - i) + A[i], A[j] = A[j], A[i] + return A[:n] + + +# --- Test Example --- + +if __name__ == "__main__": + t = Transcript() + + p = 115792089210356248762697446949407573530086143415290314195533631308867097853951 + session_id = b"test" + t.init(session_id) + + arr = bytearray() + for bi in range(0, 100): + arr.append(bi) + t.write_bytes(arr) + + tv1 = [t.generate_field(p) for i in range(0,16)] + for ti in tv1: + print(hex(ti)) + + t.write_field(7) + + tv2 = [t.generate_field(p) for i in range(0,16)] + for ti in tv2: + print(hex(ti)) + + fe_array = [(8), (9)] + t.write_field_element_array(fe_array) + + tv3 = [t.generate_field(p) for i in range(0,16)] + for ti in tv3: + print(hex(ti)) + + t.write_bytes(b'nats') + + ns = [1, 1, 1, 2, 2, 2, 7, 7, 7, 7, 32, 32, 32, 32, + 256, 256, 256, 256, 1000, 10000, 60000, 65535, 100000, 100000] + nats = [t.generate_nat(n) for n in ns] + print(nats) + + t.write_bytes(b'choose') + choose_sizes = [31, 32, 63, 64, 1000, 65535] + for cs in choose_sizes: + gotc = t.generate_nats_wo_replacement(cs, 20) + print(gotc) + diff --git a/docs/specs/code/merkle.py b/docs/specs/code/merkle.py new file mode 100644 index 0000000..1766fde --- /dev/null +++ b/docs/specs/code/merkle.py @@ -0,0 +1,150 @@ +## Code from the spec for operating on MerkleTree datastructures. + +import hashlib + +def hash(data): + assert isinstance(data, bytes), "data not bytes" + return hashlib.sha256(data).digest() + +class MerkleTree: + def __init__(self, n): + self.n = n + self.a = [b''] * (2 * n) + + def set_leaf(self, pos, leaf): + """ + Sets a leaf at a specific position. + pos: 0-based index relative to the leaves (0 to n-1) + """ + assert 0 <= pos < self.n, f"{pos} is out of bounds" + self.a[pos + self.n] = leaf + + def build_tree(self): + """ + Computes the internal nodes from n-1 down to 1. + Returns the root (M.a[1]). + """ + for i in range(self.n - 1, 0, -1): + left = self.a[2 * i] + right = self.a[2 * i + 1] + + self.a[i] = hash(left + right) + + return self.a[1] + + def mark_tree(self, requested_leaves): + marked = [False] * (2 * self.n) + + for i in requested_leaves: + assert 0 <= i < self.n, f"invalid requested index {i}" + marked[i + self.n] = True + + for i in range(self.n - 1, 0, -1): + marked[i] = marked[2 * i] or marked[2 * i + 1] + + return marked + + def compressed_proof(self, requested_leaves): + """ + Generates a compressed proof for the requested leaves. + """ + proof = [] + + marked = self.mark_tree(requested_leaves) + + for i in range(self.n - 1, 0, -1): + if marked[i]: + child = 2 * i + + # If the left child is marked, we need the right child (sibling). + if marked[child]: + child += 1 + + # If the identified child/sibling is NOT marked, + # we must provide its hash in the proof so the verifier can calculate the parent. + if not marked[child]: + proof.append(self.a[child]) + + return proof + + def verify_merkle(self, root, n, k, s, indices, proof): + """ + Verifies that the provided leaves (s) at specific positions (indices) + are part of the Merkle tree defined by 'root'. + + :param root: The expected Root Hash + :param n: Total number of leaves in the tree + :param k: Number of leaves being verified + :param s: List of leaf data/hashes to verify + :param indices: List of positions for the leaves in 's' + :param proof: List of proof hashes + """ + tmp = [None] * (2 * n) + defined = [False] * (2 * n) + + proof_index = 0 + + if n != self.n: return False + + marked = self.mark_tree(indices) + + for i in range(n - 1, 0, -1): + if marked[i]: + child = 2 * i + if marked[child]: + child += 1 + + if not marked[child]: + if proof_index >= len(proof): + return False + + tmp[child] = proof[proof_index] + proof_index += 1 + defined[child] = True + + for i in range(k): + pos = indices[i] + n + tmp[pos] = s[i] + defined[pos] = True + + for i in range(n - 1, 0, -1): + if defined[2 * i] and defined[2 * i + 1]: + left = tmp[2 * i] + right = tmp[2 * i + 1] + tmp[i] = hash(left + right) + defined[i] = True + + return defined[1] and (tmp[1] == root) + + +if __name__ == "__main__": + # Example from the test vector section in the Appendix. + n = 5 + mt = MerkleTree(n) + + c0 = bytes.fromhex('4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a') + c1 = bytes.fromhex('dbc1b4c900ffe48d575b5da5c638040125f65db0fe3e24494b76ea986457d986') + c3 = bytes.fromhex('e52d9c508c502347344d8c07ad91cbd6068afc75ff6292f062a09ca381c89e71') + mt.set_leaf(0, c0) + mt.set_leaf(1, c1) + mt.set_leaf(2,bytes.fromhex('084fed08b978af4d7d196a7446a86b58009e636b611db16211b65a9aadff29c5')) + mt.set_leaf(3, c3) + mt.set_leaf(4,bytes.fromhex('e77b9a9ae9e30b0dbdb6f510a264ef9de781501d7b6b92ae89eb059c5ab743db')) + + root_hash = mt.build_tree() + + print(f"Merkle Root: {root_hash.hex()}") + + print(f"Requesting [0,1]:") + req_leaves = [0, 1] + proof = mt.compressed_proof(req_leaves) + for p in proof: + print(p.hex()) + assert mt.verify_merkle(root_hash, n, 2, [c0, c1], [0, 1], proof), "Bad proof" + + print(f"Requesting [1,3]:") + req_leaves = [1, 3] + proof = mt.compressed_proof(req_leaves) + for p in proof: + print(p.hex()) + assert mt.verify_merkle(root_hash, n, 2, [c1, c3], [1, 3], proof), "Bad proof" diff --git a/docs/specs/libzk.md b/docs/specs/libzk.md index e7ec6cb..02cb208 100644 --- a/docs/specs/libzk.md +++ b/docs/specs/libzk.md @@ -87,13 +87,13 @@ This section describes operations on and associated with polynomials that are used in the main protocol. -### Extend method in Field F_p +### Extend method in Field F_p The `extend(f, n, m)` method interprets the array `f[0..n]` as the evaluations of a polynomial `P` of degree less than `n` at the points `0,...,n-1`, and returns the evaluations of the same `P` at the points `0,...,m-1`. For sufficiently large fields `|F_p| = p >= m`, polynomial `P` is uniquely determined by the input, and thus `extend` is well defined. As there are several algorithms for efficiently performing the extend operation, the implementor can choose a suitable one. In some cases, the brute force method of using Lagrange interpolation formulas to compute each output point independently may suffice. One can employ a convolution to implement the `extend` operation, and in some cases, either the Number Theoretic Transform or Nussbaumer's algorithm can be used to efficiently compute a convolution. -### Extend method in Field GF 2^k +### Extend method in Field GF 2^k {#gf2k} The previous section described an extend method that applies to odd prime-order finite fields which contain the elements 0,1,2...,m. In the special case of GF(2^k), the extend operator is defined in an opinionated way inspired by the Additive FFT algorithm by Lin et al [@additivefft]. Lin et al. define a novel polynomial basis for polynomials as an alternative to the usual monomial @@ -176,7 +176,7 @@ At the beginning of the protocol, the transcript object must be initialized. protocol---for example, the transcript that is used for key exchange for a session can be used as the session identifier as it is guaranteed to be unique. - The `session_id` string should be exactly 32 bytes and it is appended to the end of `tr`. + The `session_id` byte string is appended to the end of `tr` following the append conventions. This method must be called exactly once before any other method on the `transcript` object is invoked. ### Writing to the transcript @@ -186,7 +186,8 @@ the Prover's messages. * `transcript.write(msg)`: appends the Prover's next message to the end of the `tr` string that is maintained by the transcript. -There are three types of messages that can be appended to the transcript: a field element, an array of bytes, or an array of field elements. +There are three types of messages that can be appended to the transcript: a field element, an array of bytes, or an array of field elements. + * To append a field element, first the byte designator `0x1` is appended to `tr`, and then the canonical byte serialization of the field element is appended to `tr`. * To append an array of bytes, first the byte designator `0x2` is @@ -199,9 +200,8 @@ elements is appended, and finally, all of the field elements in array order are serialized and appended to `tr`. ### Special rules for the first message -The `write` method for the first prover message incorporates -additional steps that enhance the correlation-intractability property -of the oracle. To process the Prover's first message: +When used in the Longfellow system, the `write` method for the first prover message incorporates +additional steps that enhance the correlation-intractability property of the oracle. To process the Prover's first message: 1. The Prover message is appended to `tr`. Specifically, the length of the message, as per the above convention, is appended, and then the bytes of the message are appended. 2. Next, an encoding of the statement to be proven, which consists of @@ -228,8 +228,7 @@ To produce the verifier's challenge message, the transcript object internally ma Each invocation of `write` defines an FSPRF object `fs` as follows. First, the append operations described above for each type of `write` are performed, resulting in a new `tr` string. -Next, a seed is generated by applying the function `H` to the (entire) string `tr`. This seed is then used to define the -FSPRF object. +Next, a seed is generated by applying the function `H` to the (entire) string `tr`. This seed is then used to define the FSPRF object. The FSPRF object is defined to produce an infinite stream of bytes that can be used to sample all of the verifier's challenges in this round. The stream is organized in blocks of 16 bytes each, numbered consecutively starting at 0. Block `i` contains @@ -256,10 +255,28 @@ The `bytes` method of the FSPRF is used by the transcript object to sample pseud pseudo-random integers via rejection sampling as follows: * `transcript.generate_nat(m)` generates a random natural between `0` and - `m-1` inclusive, as follows. + `m-1` inclusive, as follows. This method is defined when `m > 1`. - Let `l` be minimal such that `2^l >= m`. Let `nbytes = ceil(l / 8)`. - Let `b = fs.bytes(nbytes)`. Interpret bytes `b` as a little-endian integer `k`. Let `r = k mod 2^l`, i.e., mask off the high `8 * nbytes - l` bits of `k`. If `r < m` return `r`, otherwise start over. + Let `l` be minimal such that `2^l > m`. Let `nbytes = ceil(l / 8)`. + Let `b = fs.bytes(nbytes)`. Interpret bytes `b` as a little-endian integer `k`. + Pick `mask` to be the minimal bitmask such that `(n & mask) == n` and set `r = k & mask`. + If `r < m` return `r`, otherwise start over. + +``` + def generate_nat(self, m): + assert m > 0, "m must be > 0" + + l = m.bit_length() + nbytes = math.ceil(l / 8) + mask = (1 << l) - 1 + fs = self._get_fs() + while True: + b = fs.bytes(nbytes) + k = int.from_bytes(b, 'little') + r = k & mask + if r < m: + return r +``` * `transcript.generate_nats_wo_replacement(m, n)` generates a list of `n` different, random natural numbers between `0` and `m - 1` inclusive. There are many equivalent algorithms to perform this step. The following approach requires only `n` calls to the `generate_nat` method. @@ -277,9 +294,9 @@ pseudo-random integers via rejection sampling as follows: If the field `F` is `Z / (p)`, return `generate_nat(fs, p)` interpreted as a field element. - If the field is `GF(2)[X] / (X^128 + X^7 + X^2 + X + 1)`, obtain `b = fs.bytes(16)` and interpret the 128 bits of `b` as a little-endian polynomial. + If the field is `GF(2)[X] / (X^128 + X^7 + X^2 + X + 1)`, obtain `b = fs.bytes(16)` and interpret the 128 bits of `b` as a little-endian polynomial. If the field is the sub-field of the above generated by the element `g=x^{(2^{128}-1) / (2^{16}-1)}`, then obtain `b = fs.bytes(2)`. Let `b_i` represent the `i`th bit of `b` in little-endian and return the element `\sum_i b_i * \beta_i` where `\beta_i = g^i` as defined above in (#gf2k). - This document does not specify the generation of a field element for other binary fields, but extensions SHOULD follow a similar pattern. + This document does not specify the generation of a field element for other binary fields, but extensions SHOULD follow a similar pattern. * `a = transcript.generate_challenge(F, n)` generates an array of `n` field elements in the straightforward way: for `0 <= i < n` diff --git a/docs/specs/ligero.md b/docs/specs/ligero.md index af2d2bf..e4317b6 100644 --- a/docs/specs/ligero.md +++ b/docs/specs/ligero.md @@ -24,20 +24,22 @@ A tree that contains `n` leaves is represented by an array of `2 * n` message di ### Constructing a Merkle tree from `n` digests ``` -struct { - Digest a[2 * n] -} MerkleTree - -def set_leaf(M, pos, leaf) { - assert(pos < M.n) - M.a[pos + n] = leaf -} - -def build_tree(M) { - FOR M.n < i <= 1 DO - M.a[i] = hash(M.a[2 * i] || M.a[2 * i + 1]) - return M.a[1] -} +class MerkleTree: + def __init__(self, n): + self.n = n + self.a = [b''] * (2 * n) + + def set_leaf(self, pos, leaf): + assert 0 <= pos < self.n, f"{pos} is out of bounds" + self.a[pos + self.n] = leaf + + def build_tree(self): + for i in range(self.n - 1, 0, -1): + left = self.a[2 * i] + right = self.a[2 * i + 1]s + self.a[i] = hash(left + right) + + return self.a[1] ``` ### Constructing a proof of inclusion @@ -48,72 +50,77 @@ To address these inefficiencies, this section explains how to produce a batch pr It is important in this formulation to treat the input digests as a sequence, i.e. with a given order. Both the prover and verifier of this batch proof must use the same order of the `requested_leaves` array. ``` -def compressed_proof(M, requested_leaves[], n) { - marked = mark_tree(requested_leaves, n) - FOR n < i <= 1 DO - IF (marked[i]) { - child = 2 * i - IF (marked[child]) { - child += 1 - } - IF (!marked[child]) { - proof.append(M.a[child]) - } - } - return proof -} + def mark_tree(self, requested_leaves): + marked = [False] * (2 * self.n) -def mark_tree(requested_leaves[], n) { - bool marked[2 * n] // initialized to false + for i in requested_leaves: + assert 0 <= i < self.n, f"invalid requested index {i}" + marked[i + self.n] = True - for(index i : requested_leaves) - marked[i + n] = true + for i in range(self.n - 1, 0, -1): + marked[i] = marked[2 * i] or marked[2 * i + 1] - FOR n < i <= 1 DO - // mark parent if child is marked - marked[i] = marked[2 * i] || marked[2 * i + 1]; + return marked - return marked -} -``` + def compressed_proof(self, requested_leaves): + proof = [] + marked = self.mark_tree(requested_leaves) + for i in range(self.n - 1, 0, -1): + if marked[i]: + child = 2 * i -### Verifying a proof of inclusion -This section describes how to verify a compressed Merkle proof. The claim to verify is that "the commitment `root` defines an `n`-leaf Merkle tree that contains `k` digests s[0],..s[k-1] at corresponding indicies i[0],...i[k-1]." The strategy of this verification procedure is to deduce which nodes are needed along the `k` verification paths from index to root, then read these values from the purported proof, and then recompute the Merkle tree and the consistency of the `root` digest. As an optimization, the `defined[]` array avoids recomputing internal portions of the Merkle tree that are not relevant to the verification. By convention, a proof for the degenerate case of `k=0` digests is defined to fail. It is assumed that the `indicies[]` array does not contain duplicates. + # If the left child is marked, we need the right child. + if marked[child]: + child += 1 -``` -def verify_merkle(root, n, k, s[], indicies[], proof[]) { - tmp = [] - defined = [] - - proof_index = 0 - marked = mark_tree(indicies, n) - FOR n < i <= 1 DO - if (marked[i]) { - child = 2 * i - if (marked[child]) { - child += 1 - } - if (!marked[child]) { - if proof_index > |proof| { - return false - } - tmp[child] = proof[proof_index++] - defined[child] = true - } - } + # If the identified child/sibling is NOT marked, + # add its hash to the proof so the verifier can calculate the parent. + if not marked[child]: + proof.append(self.a[child]) - FOR 0 <= i < k DO - tmp[indicies[i] + n] = s[i] - defined[indicies[i] + n] = true + return proof +``` - FOR n < j <= 1 DO - if defined[2 * i] && defined[2 * i + 1] { - tmp[i] = hash(tmp[2 * i] || tmp[2 * i + 1]) - defined[i] = true - } +### Verifying a proof of inclusion +This section describes how to verify a compressed Merkle proof. The claim to verify is that "the commitment `root` defines an `n`-leaf Merkle tree that contains `k` digests `s[0], ..., s[k-1]` at corresponding indices `i[0], ..., i[k-1]`." The strategy of this verification procedure is to deduce which nodes are needed along the `k` verification paths from index to root, then read these values from the purported proof, and then recompute the Merkle tree and the consistency of the `root` digest. As an optimization, the `defined[]` array avoids recomputing internal portions of the Merkle tree that are not relevant to the verification. By convention, a proof for the degenerate case of `k=0` digests is defined to fail. It is assumed that the `indices[]` array does not contain duplicates. - return defined[1] && tmp[1] = root -} +``` + def verify_merkle(self, root, n, k, s, indices, proof): + tmp = [None] * (2 * n) + defined = [False] * (2 * n) + + proof_index = 0 + + if n != self.n: return False + + marked = self.mark_tree(indices) + + for i in range(n - 1, 0, -1): + if marked[i]: + child = 2 * i + if marked[child]: + child += 1 + + if not marked[child]: + if proof_index >= len(proof): + return False + tmp[child] = proof[proof_index] + proof_index += 1 + defined[child] = True + + for i in range(k): + pos = indices[i] + n + tmp[pos] = s[i] + defined[pos] = True + + for i in range(n - 1, 0, -1): + if defined[2 * i] and defined[2 * i + 1]: + left = tmp[2 * i] + right = tmp[2 * i + 1] + tmp[i] = hash(left + right) + defined[i] = True + + return defined[1] and (tmp[1] == root) ``` ## Common parameters @@ -288,6 +295,8 @@ def layout_quadratic_rows(T, w, lqc[]) { ## Ligero Prove This section specifies how a Ligero proof for a given sequence of linear constraints and quadratic constraints on the committed witness vector `W` is constructed. The proof consists of a low-degree test on the tableau, a linearity test, and a quadratic constraint test. + + ### Low-degree test In the low-degree test, the verifier sends a challenge vector consisting of `NROW` field elements, `u[0..NROW]`. This challenge is generated via the Fiat-Shamir transform. The prover computes the sum of `u[i]*T[i]` where `T[i]` is the i-th row of the tableau, and returns the first BLOCK elements of the result. The verifier applies the `extend` method to this response, and then verifies that the extended row is consistent with the positions of the Merkle tree that the verifier will later request from the Prover. @@ -301,14 +310,17 @@ The quadratic constraints are given as input in an array `lqc[]` that contains t In this sense, the quadratic constraints are reduced to linear constraints, and the additional requirement for the verifier to check that each index of the `Qz` row is the product of its counterpart in the `Qx` and `Qy` row. ### Selection of challenge indicies -The last step of the prove method is for the verifier to select a subset of unique indicies (i.e., they are sampled without replacement) from the range `DBLOCK...NCOL` and request that the prover open these columns of tableau `T`. These opened columns are then used to verify consistency with the previous messages sent by the prover. +The last step of the prove method is for the verifier to select a subset of unique indices (i.e., they are sampled without replacement) from the range `DBLOCK...NCOL` and request that the prover open these columns of tableau `T`. These opened columns are then used to verify consistency with the previous messages sent by the prover. ### Ligero Prover procedure +The `context` argument is application-dependent and includes information about the theorem statement that is proven. + ``` -def prove(transcript, digest, linear[], lqc[]) { +def prove(transcript, context, linear[], lqc[]) { + + transcript.write(context) u = transcript.generate_challenge([BLOCK]); - transcript.write(digest) ldt[0..BLOCK] = T[ILDT][0..BLOCK] diff --git a/docs/specs/testvectors.md b/docs/specs/testvectors.md index c6ecad6..0a3a7db 100644 --- a/docs/specs/testvectors.md +++ b/docs/specs/testvectors.md @@ -25,31 +25,9 @@ This section contains test vectors. Each test vector in specifies the configurat Let `p=115792089237316195423570985008687907853269984665640564039457584007908834671663` and `Fp` be the 4-word field defined by `p`. +### Vector 1: WriteBytes -### Vector 1: WriteAll - -- Description: Using `Fp`, the test steps are to (a) initialize the transcript object with the 4-byte string `test`, (b) write an array of bytes of size 100 that contains the integers 0, 1, 2, ..., 99, write the field element '7', write the 2-long array of field elements `[8, 9]`, and -finally (c) generate 16 field elements. The field elements should be: - - 0x9c9d62aa71f49f996a3579631f28cfb58d47177e58245b260571738893d2d156 - - 0xada427bb3f99541766d3d542587307bc5d84e3fde9ae328568797bac1499b446 - - 0x77f02b641fbd89b6d6d9d4a0f941bae6eafabee54a70e654b4546a543c15e5bd - - 0x6ac559513811b95584af9ce3ca73cea07739199ef44163072695c212ab6f7964 - - 0x6edf6128a50daf1c03fc67d720ee529a4a01d8e594d0eaa1b5189eb0df898ca4 - - 0x0c1212524eac26560099c197d2412c840992b5a7c3a39341d36a1fec62e9bc55 - - 0x0bba75f82ad1e76b9398ed1fffcc52d2bf413c31e691ff7134b0a33c0de1a236 - - 0xe8a3426744c4e2101c254f8b174383deb33e9537a30375e8fe652a02254c4df4 - - 0xf7c7bafe0efc325b9e7e579294c0c370b3e17466d91c22b215b7e73544b6bf50 - - 0xa1eaac6e545f28bab1579911c7ea000b7c4cf42202eb3ba3d304eaecfc79b8b8 - - 0x3fcd8c478cdca3ca9a37b8ec44386c32db658a51cc740d4fec99129c7cc65ad5 - - 0xf7c454d8385b4d8c13a4c37a00d63f0db4d39939759397073b501f32556c8418 - - 0x28912d46813691ed86163864eb723017ce07fdf45881a0b96234b2813b557e08 - - 0x5ec633319eaaf202af7207d544251ebcd205dc29415506eabae29770f6f16455 - - 0xdaeb005a647c61dbbf9328317ac1e40b1f467dc343da1d175899a774f8cb87a3 - - 0xb3cb99cde603369ce86e4c030f94566b6d023aa4269fc8983e40bd08de5c7fbd - -### Vector 2: WriteByteArray -- Using `Fp`, the test steps are to (a) initialize the transcript object with the 4-byte string `test`, (b) write an array of bytes of size 100 that contains the integers 0, 1, 2, ..., 99, and finally (c) generate 16 field elements: - +- Description: Using `Fp`, the test steps are to (a) initialize the transcript object with the 4-byte string `test`; (b) write an array of bytes of size 100 that contains the integers 0, 1, 2, ..., 99; (c) generate 16 field elements: - 0x8b297f0bffd583c6c6b6796385d5fd20a08665733b833970ebdd1054bbbc1b14 - 0x0667c08ad7f38efec5f30dc8aa4f20d749cdcf96d63a770f9810ac5c0ca8dcb1 - 0xc8037fc12d4da00b5dc7597e3042f33f72a06f970cb71fb6b103ebb5419d8a6b @@ -67,48 +45,58 @@ finally (c) generate 16 field elements. The field elements should be: - 0x976353931711c634f2691e507b119fd7f6e653d419a2620676122db08db18765 - 0x332729ab436dca654866a9382deaee0add6fb7e90a80261f1488e56598e8bc99 - -### Vector 3: WriteFieldElement -- Using `Fp`, the test steps are to (a) initialize the transcript object with the 4-byte string `test`, (b) write the field element '7' -and finally (c) generate 16 field elements - - - 0x79dbf7dcaadb98df53c37f79a27d83ce183df6ac0e09d9b9c94d90bdc397267e - - 0x6e1310e73726e68e7dc2d187cb88482f4230bc9347c90e040bbf50db2ed969b7 - - 0xf6045a731e0734126c6ebd450c2f6d4a25ff181fa669a2ba9a27cf3c89099f94 - - 0x3ea763e44d31e2affa8756106d6d756fd214287070b74ac75736096dad6ddc5a - - 0xafc7ef2406665ebe9591ee90b61606811ff86a836d2656e3326638f3de6de79f - - 0x756ea3b644ef82b92ec05b2d0247c108841e524d170d0c8444662e435c592031 - - 0xa3309c8b0effdbe00dc7e32069751e5d2ea42c57bd5edda4a926eebfe544defb - - 0xb5892b1d967bba61faf40df6326c2d6c3d9bd0de196292278d5872a6a0cd8dfe - - 0x4402a2bbca75741b36e5576960abe050f8a7ba2a40708d4c399311054ad98a10 - - 0xa7618dc260c840a7e7a73cff97e3aac3a9ef43b308e67cd205c0f744a64a8781 - - 0xf88ac012402776fef8b47a12157b36e237fc7a58ed807b9d416c3ac07e20c104 - - 0x08e3b0abae0dff1be9e610db25468905aaa7e88175367f815e69a3a515edba1a - - 0x1ecc211d37a4de8a310f1f3cc434112d105ae875300739c5485626820575c7f5 - - 0xf2940f2aa7e29ee971c13af8d22f78d2464874fd76f049616fffb6dc76e043cd - - 0xb15df6859bb1b5145717aa346f395798625ca0af8ff6aaa31cdacab4a3891da5 - - 0xbd9b4e85d27ff6e6e7983891e177eb25e8a6a5ecf3e1fd04e4f08c520eff6e7b - -### Vector 4: WriteFieldElementArray -- Description: Using `Fp`, the test steps are to (a) initialize the transcript object with the 4-byte string `test`, (b) write the 2-long array of field elements `[8, 9]` -and finally (c) generate 16 field elements: - - - 0xdceeffa746fa8d6733d54e90d107f219e8c54ccf95bcd038f5dc0be89d756f1c - - 0x4e92ffec4e426b8d78b04c4b73b3b1649e154cb73f929ae5835af23a7784c3da - - 0xd529ca59cb04d8015924c31148130a3b37093fa1d67b894d0dacaad68f1f3bf2 - - 0xc830d695e0a3f69e5bd7df4298b2c3a5c345bba6de89edfbc3d1255640823c77 - - 0x589ab4ce35561d8a717557ea47aa8fc8a318ae79c1a78dcfca89a77b64c3bffe - - 0xba9c43ed7fa666b9d2b5c2f13eca365ac0a8bfb3eaf66e739b3e4afae9bce824 - - 0x970c4fb3d48f76d1ee27c8e12d5c81632c736ecc12436c115a15f76ce6a01cbc - - 0x1fd18d8cc8ee9ed717d4da6da5ef2779a8c7e5cc44d10aa4c8e71f4064a310c4 - - 0x23ef27bc299da254427d8cade2256c0078f1a820a267784c663bf14b1ab643d5 - - 0xe11c54a3a5c75dda77bbece74f9a0fa58e915f927027921b4189a29296f773fd - - 0x54c8300bfa2a9fc98f8e975c0fe3dd93c8f19b0466aa490762c5653a7ed1798f - - 0x87df7994955345d30ac9a84c7bfdc00f9ec5fa54620a47eb43df05ea9e7fcd89 - - 0x3dfdc0ad656f225fdf105b3f226fd6117a3c63be174114a4c30fa7ed6e98537a - - 0x77e46ff1c83ed47d4845e13f5660e27b319b85073e1621e2a40d92f7c2ab298e - - 0x19a19a13fb10140fbd6e76c252e4339e4e306b1f7d4d1d2f275525eb3b367b9c - - 0x95beabd1f934fba041db42142d731f1e5b54cc92ec64cb7d61c01cef85a8b300 +### Vector 2: WriteFieldElement +- Starting from the state at the end of Vector 1, (a) write the field element '7' in Fp; (b) generate 16 field elements + + - 0x609db3e9a8f548df038519fa46cef23eb8c6553d3c1f698604e60a51613a738e + - 0x1cb69cb31999eb88e83c7586aac53f5e3286b084b0cf9e43619b48df01e0a310 + - 0x3bf36e3ddc690a1b12b417628c115959b373d056c90c42dc2417baf46f538868 + - 0xe336594f29dcda52e48896517b5cdb2d062ffd861ab02db5f8ca197aacc635f6 + - 0xc1f396a8bad16bb0f57da6d380402a25b571bd4691226d11449a741440e325c8 + - 0x5195336ec73751de066e3a8939b40c3c5555f1a513486dfc50dcf4c2d47e6ff2 + - 0x8dcf872f3ded2b7ed1d1ee9a2b125bedc6eacd3c09b3a4a5286d8fc2fc3a6634 + - 0x950dd2ef7be25eab686a6688497962ee4ad521da12b9ff3d8e56ad9435885b12 + - 0xe14389d1d8448678cac33fdbc9aab20dba019e75149d170dd2f353891cd4b84f + - 0xe84906c09cd6423865baf64e48027cc598d52bdb90b17524c87ea892e53b5200 + - 0x493cea587f1ec5622c04221cd6e5a41c26c1c1c24c0375f7aaa367d9678d83bc + - 0x5aca0010aced30bcb3b84a7f10ea39c4269ab7c92fcb6cff52958d8921ef2cc5 + - 0x4498fa8340f41467c0fa813bd0ca83ef6e1c4b85c7b1168a94339fd9e8296139 + - 0xf9a95b738a8e775421b1baa503abbeed2d283b236ebba25e1954b3c993d30a3d + - 0x98178711d03a0b1204ebb56b37bd3a2724dfb08e4dc925609391768b126d21f2 + - 0x79251f49534f5c4b10b798b2dbf6e80a3b07593f616ce6a9617ccc61040aac78 + +### Vector 3: WriteFieldElementArray +- Starting from the state at the end of Vector 2, (a) write the 2-long array of field elements `[8, 9]`; (b) generate 16 field elements: + + - 0xae1a921288590205fc24543303ff527476359b8db4a983b2886a133b02f3217e + - 0x8c5d52a04b295f9fdb45ab66100fa00ca32c9634aa87cbbdb2bc3e1912459feb + - 0x12f82963b5b242156f6e9eb756eddee7652b60c7d6394403f7bd995e0b9bcd9c + - 0x880aa50b049b3939055deb7933749d338bb3fb5f64a9adf95019e6cfc232995c + - 0xf8558f693f0fa6df20a37147a898fb4c678831f566d80113bbe2cdcd18285da2 + - 0xbbcc8d9b46f88bc8c6cec0ad2d5e49508b7db91d548548eddc61800de1329e1c + - 0x479a17244398caae8155a73438a22583df7de10a8a2e12ad53ddd3bc7305fac9 + - 0x9ba1917f1227932250288a843f64b4e7b7f47a5fbc16c111f6e1f76235ccf38c + - 0xd1582138045d1636fb7f677c9e8a4a4143ce2b2bb54fb4f49fb0ad1fee5df6b4 + - 0x5331e5b8508f79c017a8dfbbb805f3f8c5e3e4bc417e44849b9212439646331 + - 0xb6b95862194ca52dcaa9ee651b7fc5b708f43feae108bb9a7f95213f4d069048 + - 0xe86b1602f0a54c4e237867ebaf05e7581464fd238e50f6ed9c3cea63909c8e60 + - 0xb7280439f3b21b113ff29cefe39292d5e2d137709c3d3cec36473a0f97a24e62 + - 0xbeaa5e08257d232506fb3e46c6daa29e0859c34c7d0cd673bc6706ee261ae059 + - 0x691ead55728cd087a1952b22b6628ba4e26fbefc8debeec5e6fbc3a16f637be + - 0x47dc31f6d8bc9c44290781176df3e4b95ac8793a4a42fa5859c564d92d6d5af5 + +### Vector 4: Nat +- Starting from the state at the end of Vector 3, (a) write the 4-byte string "nats"; (b) call `generate_nat` with the following list of parameters: `[1, 1, 1, 2, 2, 2, 7, 7,7, 7, 32, 32, 32, 32, 256, 256, 256, 256, 1000, 10000, 60000, 65535, 100000, 100000]`. The result of each call should correspond to the list of results: + - `[0, 0, 0, 0, 0, 0, 3, 0, 4, 5, 10, 30, 27, 22, 100, 189, 3, 92, 999, 3105, 40886, 51590, 56367, 10678]` + +### Vector 5: Choice +- Starting from the state at the end of Vector 4, (a) write the 6-byte string "choice"; (b) perform the following calls to `generate_natgenerate_nats_wo_replacement` + - `m=31, k=20`: `[10, 29, 30, 11, 4, 15, 16, 28, 19, 21, 25, 18, 17, 3, 5, 23, 24, 22, 6, 1]` + - `m=32, k=20`: `[3, 17, 18, 8, 30, 7, 14, 19, 25, 23, 12, 4, 31, 16, 0, 6, 20, 27, 11, 10]` + - `m=63, k=20`: `[9, 56, 61, 45, 35, 53, 51, 3, 39, 32, 31, 6, 59, 58, 54, 22, 27, 62, 55, 19]` + - `m=64, k=20`: `[12, 52, 39, 17, 51, 38, 58, 2, 28, 27, 46, 63, 61, 50, 40, 55, 47, 13, 56, 32]` + - `m=1000, k=20`: `[157, 668, 572, 138, 913, 994, 797, 249, 440, 723, 489, 241, 383, 108, 710, 341, 406, 585, 42, 692]` + - `m=65535, k=20`: `[40745, 48408, 17108, 44500, 53993, 10008, 24910, 52200, 61265, 54989, 41237, 25958, 28697, 61187, 34729, 3525, 9005, 38627, 9724, 12169]` ## Test Vectors for Circuit